1. How do I inject our service to get exiting recipient?
When I try this
public class NewsletterSubscriberProvider : IRecipientListProvider
{
public string DisplayName => "Demo Provider";
public string DisplayNameLocalizationKey => "site/demoProvider";
public string Prefix => "demo";
public bool CanRedirectToEdit => false;
public NewsletterSubscriberProvider(ISubscriberService subscriptionService)
{
}
}
I am getting an error on startup about injecting into singleton.
2. Why there are two places where to assign recipient model (and email):
public List<EmailReceiver> GetReceiversForList(RecipientListIdentifier listId, GetReceiversForListParams parameters)
{
var list = new List<EmailReceiver>();
list.Add(new EmailReceiver(new EmailReceiverIdentifier(Prefix, 1), "[email protected]", "Foo Bar")); // <- here
if (listId.Identifier == "2")
list.Add(new EmailReceiver(new EmailReceiverIdentifier(Prefix, 2), "[email protected]", "Foo Bar"));
return list;
}
public RecipientProviderRecipientDataModel GetDataModel(EmailReceiverIdentifier receiverId)
{
var model = new RecipientProviderRecipientDataModel();
model.Email = "[email protected]"; // <- and here
model.ProviderRecipientId = receiverId.ToString();
model.Model = new InMemoryRecipient(1975, "London", "Test Company Inc.");
return model;
}
3. Is there any way of seeing the custom recipients in the backoffice under recipients/ mailing lists?
Before they are actually used in the campaign?
I cannot see any recipient there even though the NewsletterSubscriberProvider seems implemented. GetLists and GetReceiversForList are hit only upon sending an email.
Excided to see that you're looking to create your own Recipient List Provider and sorry that the documentation left you with questions. I'll make sure to update it based on our conversation here.
Let me take your questions one by one.
1. How do I inject our service to get exiting recipients?
This is probably because you have registered your ISubscriberService as either Scoped or Transient, we create a Singleton for the Provider - is there anyway you can make your service a Singleton as well?
2. Why there are two places where to assign recipient model (and email)
This is a little unclear and I'll make sure to update the documentation around this.
GetReceiversForList Is used when the send-out engine creates the queue for the campaign based on the selected lists. This queue only contains a limited amount of information about the recipients to be able to exclude duplicates etc.
GetDataModel Is called during send out by the rendering engine for each recipient passing the EmailReceiverIdentifier that you created in GetReceiversForList. This method should return more details (if any) about the recipient. The model returned from this method is used e.g for Merge Fields and is also passed to any macros inside the email. If you want to create merge fields based on this model you need to implement a Merge Field Provider as well.
if(request.RecipientProviderRecipientDataModel.Model is InMemoryRecipient recipient)
If you don't need extra information for merge-fields etc. you don't have to return a detailed model here.
3. Is there any way of seeing the custom recipients in the backoffice under recipients/ mailing lists?
No. we only show the mailing lists that are built into the package. If you need to display the recipients from your custom source you would need to create some kind of custom view for this. Most of the time there is already some other way to display this information (e.g. Umbraco Members, webshop, CRM etc).
4. Do we need to implement our Email Service Provider?
There is a built-in Email Service Provider to send via SMTP that you can configure in the Workspace-administration. If you want to send via some kind of API or similar you could create your own provider but this is not mandatory.
We do not fall back to any settings in appSettings at the moment so if you want to send via SMTP you need to configure this in the Workspace administration as well.
Another thing to notice is that you need to activate any custom Recipient List Provider or Merge Field Provider for your workspace in the administration section as well.
I hope that my answers bring some more clarity please feel free to reach out at any time!
I am not sure how we can populate our Model merge fields in GetDataModel(EmailReceiverIdentifier receiverId)?
I understand that we need to do our own query to get full subscribers data from our own source in order to populate our custom Model in GetDataModel, is that correct?
The problem is that this function:
public RecipientProviderRecipientDataModel GetDataModel(EmailReceiverIdentifier receiverId)
{
var model = new RecipientProviderRecipientDataModel();
model.Email = "[email protected]";
model.ProviderRecipientId = receiverId.ToString();
// The "Model"-property is used to pass a custom model that can be used inside
// a IMergeFieldProvider to translate properties into Merge Fields.
model.Model = new ClientRecipient("PromoCode", "Firstname", "Surname", "CountryName", DateTime.Now, DateTime.Now);
return model;
}
only receives EmailReceiverIdentifier receiverId as parameter (=provider prefix + some id - id of what? - we always get 1here) so we:
a. need to get the email of the recipient from the receivers list - how? if we only have some unidentified id and the provider prefix?
b. only then we can potentially query our own data to get the particular properties for our custom model by the recipient's email address.
Or is there any other way of matching the EmailReceiverIdentifier with the recipient's id from the NS send list?
Yes, you need to get the full details (that you care about in the context of the package) in this method.
If you always get 1 as the parameter you need to check the code that creates the EmailReceiverIdentifier (should be the GetReceiversForList-method), this method needs to create a unique identifier for the recipient that you can use in GetDataModel.
2.2
Because they are different concepts and in terms of code they are separated.
2.3
Its use to find a recipient by it's email, we use this for some features related to transactional emails to identify if a recipient exists or not. It should be safe to just return null from this method if you can't query your data source by email.
Basically, the merge field provider would get a reference to the model that you create and extract the values from there. The Merge Field Provider is also responsible to expose a list of properties that it uses so that the e-mail editor (and other parts of the UI) can show properties and options for the user to pick from.
It is quite limiting to use singletons as for example is not possible to use UmbracoHelper which is a trouble for use when we get to getting data for our subsccribers.
Also on the Umbraco documentation it is not recommended neither:
Generally speaking, if you are writing software these days you should
be using Dependency Injection principles. If you do this, you probably
aren't using Singleton or Statics (and for the most part you shouldn't
be!). Since Umbraco 9 comes with dependency injection out of the box,
there really isn't any reason to use singletons or statics. It makes
your code very difficult to test but more importantly using Singletons
and Statics in your code makes it very hard to manage, APIs become
leaky, and ultimately you'll end up with more problems than when you
started.
Let look at your problem here and I'm sure that we can find a solution.
While probably not the "best approach", there are plenty of ways to use a scoped or transient service even inside a singleton-lifetime dependency if needed - a common approach is to use the IServiceProvider as a dependency to the Singleton. One example from the Umbraco core can be found here:
Note that the ctor takes in an IServiceProvider that can be used to resolve Transient, Scoped, and Singleton dependencies.
The documentation that you're referring to has been around since Umbraco 6/7 and has only been slightly tweaked for the modern .NET-versions so I would argue that it's misleading and that the conclusions that you draw based on it are not accurate. The main takeaway should be that you should not implement your OWN singleton patterns (rather use the DI-containers singleton lifetime) and you should be careful with how use use static properties since they are shared by all threads.
There is nothing wrong at all with using singletons with DI. Umbraco uses this heavily in the core for almost all services and repositories:
The reason behind this is of course that a singleton lifetime is better for performance since the instance is reused which will not put the same pressure on the GC (Garbage Collector). So while a solution with mostly transient dependencies might be "easier" to work with it would not be ideal from a performance standpoint.
If you need to use the UmbracoHelper I'm assuming that you want to query the published content cache or media. The UmbracoHelper is just a "convenience"-class that is using other services under the hood. The UmbracoHelper is a scoped dependency and in Umbraco this means that is tied to a given HTTP-request. It contains methods that for example depend on the current locale of the http-request (for dictionaries).
Since Newsletter Studio runs the send-out process in a background job the UmbracoHelper should not be used. The better approach is to use the IUmbracoContextFactory to get a reference to the UmbracoContext. I would argue that this is the best practice for any service that you use both on the front end or in background jobs as this approach will work with both.
So in your services, event-handler or whatever, you should avoid depending on the UmbracoHelperor IUmbracoHelperAccesor. Your class should take in the IUmbracoContextFactory as a dependency and when you need to access the cache you would do something like this:
How to plug our custom recipients into IRecipientListProvider?
I am trying Newsletter Studio and went through the documentation: https://www.newsletterstudio.org/documentation/package/10.0.0/ to understand how it works.
All pretty straightforward until we need to extend it.
Trying to use custom
RecipientListProvider
as per the example here: https://www.newsletterstudio.org/documentation/package/10.0.0/develop/recipient-list-providers/ but it raises multiple questions:1. How do I inject our service to get exiting recipient?
When I try this
I am getting an error on startup about injecting into singleton.
2. Why there are two places where to assign recipient model (and email):
3. Is there any way of seeing the custom recipients in the backoffice under recipients/ mailing lists?
Before they are actually used in the campaign?
I cannot see any recipient there even though the
NewsletterSubscriberProvider
seems implemented.GetLists
andGetReceiversForList
are hit only upon sending an email.4. Do we need to implement our Email Service Provider? as per here: https://www.newsletterstudio.org/documentation/package/10.0.0/develop/email-service-providers/
or it is automatically picked up from our
appsettings
by the default email sender configured inSettings
section for the workspace?Thanks
Hi!
Excided to see that you're looking to create your own Recipient List Provider and sorry that the documentation left you with questions. I'll make sure to update it based on our conversation here.
Let me take your questions one by one.
1. How do I inject our service to get exiting recipients?
This is probably because you have registered your
ISubscriberService
as either Scoped or Transient, we create a Singleton for the Provider - is there anyway you can make your service a Singleton as well?2. Why there are two places where to assign recipient model (and email)
This is a little unclear and I'll make sure to update the documentation around this.
GetReceiversForList
Is used when the send-out engine creates the queue for the campaign based on the selected lists. This queue only contains a limited amount of information about the recipients to be able to exclude duplicates etc.GetDataModel
Is called during send out by the rendering engine for each recipient passing theEmailReceiverIdentifier
that you created inGetReceiversForList
. This method should return more details (if any) about the recipient. The model returned from this method is used e.g for Merge Fields and is also passed to any macros inside the email. If you want to create merge fields based on this model you need to implement aMerge Field Provider
as well.Notice the type-check in the example here:
https://www.newsletterstudio.org/documentation/package/10.0.0/develop/merge-field-providers/
If you don't need extra information for merge-fields etc. you don't have to return a detailed model here.
3. Is there any way of seeing the custom recipients in the backoffice under recipients/ mailing lists?
No. we only show the mailing lists that are built into the package. If you need to display the recipients from your custom source you would need to create some kind of custom view for this. Most of the time there is already some other way to display this information (e.g. Umbraco Members, webshop, CRM etc).
4. Do we need to implement our Email Service Provider?
There is a built-in Email Service Provider to send via SMTP that you can configure in the Workspace-administration. If you want to send via some kind of API or similar you could create your own provider but this is not mandatory.
Example of custom provider: https://github.com/enkelmedia/NewsletterStudio.Plugins.Mailjet
We do not fall back to any settings in appSettings at the moment so if you want to send via SMTP you need to configure this in the Workspace administration as well.
Another thing to notice is that you need to activate any custom Recipient List Provider or Merge Field Provider for your workspace in the administration section as well.
I hope that my answers bring some more clarity please feel free to reach out at any time!
Thanks very much for quick reply!
We'll see if we can make DI working from our side and will test the recipients provider.
Ok, we've solved 1 & noted 3.
In regards of point 2:
1.
Model
issueI am not sure how we can populate our
Model
merge fields inGetDataModel(EmailReceiverIdentifier receiverId)
?I understand that we need to do our own query to get full subscribers data from our own source in order to populate our custom
Model
inGetDataModel
, is that correct?The problem is that this function:
only receives
EmailReceiverIdentifier receiverId
as parameter (=provider prefix + some id - id of what? - we always get1
here) so we:a. need to get the email of the recipient from the receivers list - how? if we only have some unidentified id and the provider prefix?
b. only then we can potentially query our own data to get the particular properties for our custom model by the recipient's email address.
Or is there any other way of matching the
EmailReceiverIdentifier
with the recipient's id from the NS send list?Btw we could get emails from
but it reads
listId
which we don't have in2.
Email
issueWhy do we need to populate the receiver's email again in here:
in this line:
as the email that is send to is from the receivers' list, and this one seems to be ignored?
3. What is this function for?
or can we use it to populate our model, in this case - where do we get
Guid? workspaceKey
from? And see question 1.Hi!
2.1
Yes, you need to get the full details (that you care about in the context of the package) in this method.
If you always get 1 as the parameter you need to check the code that creates the
EmailReceiverIdentifier
(should be theGetReceiversForList
-method), this method needs to create a unique identifier for the recipient that you can use in GetDataModel.2.2
Because they are different concepts and in terms of code they are separated.
2.3
Its use to find a recipient by it's email, we use this for some features related to transactional emails to identify if a recipient exists or not. It should be safe to just return null from this method if you can't query your data source by email.
A side note:
The merge fields are extracted from the model using a "Merge Field Provider", have a look at this documentation: https://www.newsletterstudio.org/documentation/package/12.0.0/develop/merge-field-providers/.
Basically, the merge field provider would get a reference to the model that you create and extract the values from there. The Merge Field Provider is also responsible to expose a list of properties that it uses so that the e-mail editor (and other parts of the UI) can show properties and options for the user to pick from.
Just a side comment on the issue 1:
It is quite limiting to use singletons as for example is not possible to use
UmbracoHelper
which is a trouble for use when we get to getting data for our subsccribers.Also on the Umbraco documentation it is not recommended neither:
https://docs.umbraco.com/umbraco-cms/v/10.latest-lts/reference/common-pitfalls#static-references-to-scoped-instances-such-as-umbracohelper
Maybe you could consider to replace singletons with scoped services in the future releases?
Hi!
Let look at your problem here and I'm sure that we can find a solution.
While probably not the "best approach", there are plenty of ways to use a scoped or transient service even inside a singleton-lifetime dependency if needed - a common approach is to use the IServiceProvider as a dependency to the Singleton. One example from the Umbraco core can be found here:
https://github.com/umbraco/Umbraco-CMS/blob/contrib/src/Umbraco.Core/IO/MediaFileManager.cs
Note that the ctor takes in an
IServiceProvider
that can be used to resolve Transient, Scoped, and Singleton dependencies.The documentation that you're referring to has been around since Umbraco 6/7 and has only been slightly tweaked for the modern .NET-versions so I would argue that it's misleading and that the conclusions that you draw based on it are not accurate. The main takeaway should be that you should not implement your OWN singleton patterns (rather use the DI-containers singleton lifetime) and you should be careful with how use use static properties since they are shared by all threads.
There is nothing wrong at all with using singletons with DI. Umbraco uses this heavily in the core for almost all services and repositories:
https://github.com/umbraco/Umbraco-CMS/blob/223598b037408f4204f1175a092296a73186a262/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs#L279
The reason behind this is of course that a singleton lifetime is better for performance since the instance is reused which will not put the same pressure on the GC (Garbage Collector). So while a solution with mostly transient dependencies might be "easier" to work with it would not be ideal from a performance standpoint.
If you need to use the UmbracoHelper I'm assuming that you want to query the published content cache or media. The UmbracoHelper is just a "convenience"-class that is using other services under the hood. The UmbracoHelper is a scoped dependency and in Umbraco this means that is tied to a given HTTP-request. It contains methods that for example depend on the current locale of the http-request (for dictionaries).
Since Newsletter Studio runs the send-out process in a background job the UmbracoHelper should not be used. The better approach is to use the IUmbracoContextFactory to get a reference to the UmbracoContext. I would argue that this is the best practice for any service that you use both on the front end or in background jobs as this approach will work with both.
https://docs.umbraco.com/umbraco-cms/v/10.latest-lts/implementation/services#accessing-published-content-outside-of-a-http-request
So in your services, event-handler or whatever, you should avoid depending on the
UmbracoHelper
orIUmbracoHelperAccesor
. Your class should take in theIUmbracoContextFactory
as a dependency and when you need to access the cache you would do something like this:Please give this a try and let me know if you still have any issues - I'm more than happy to help =D
// m
is working on a reply...