Calling an event when triggering an API - c#

I created a notification Event that I need to trigger within my post API after its completion.
The event Args are:
public class OrderCreationEvents : EventArgs
{
public string PickUpLocation { get; set; }
public string CustumerId { get; set; }
public OrderCreationEvents(string _pickUpLocation, string _custumerId)
{
PickUpLocation = _pickUpLocation;
CustumerId = _custumerId;
}
}
The notification Service interface is:
public interface IOrderCreatedService
{
void OnOrderCreation(object sender, OrderCreationEvents args);
}
The Interface's implementation is:
public class OrderNotificationService : IOrderCreatedService
{
private readonly IUnitOfWork _unitOfWork;
public OrderNotificationService(IUnitOfWork unitOfWork)
{
_unitOfWork = unitOfWork;
}
public async void OnOrderCreation(object sender, OrderCreationEvents args)
{
var note = new Notification { CustomerId = args.CustumerId, PickUpLocation = args.PickUpLocation };
await _unitOfWork.Notifications.Insert(note);
await _unitOfWork.Save();
}
}
Finally, when I create the post endpoint, I need to trigger this event
[HttpPost]
public async Task<IActionResult> Create(OrderIDTO orderIDTO)
{
var order = new Order {...};
await _unitOfWork.Orders.Insert(order);
//Calling the Notification Service
var note = new OrderNotificationService(_unitOfWork);
return RedirectToAction("index");
}
How do I trigger the OnOrderCreation mehtod inside the Create endpoint?

You also can follow Notification with MediatR which is really easy to use and understand.
This Link Can help to implement MediatR notification in your controller.
but, if you want to use MediatR notification in your scenario follow this:
public class OrderNotification : IAsyncNotification
{
public string PickUpLocation { get; set; }
public string CustumerId { get; set; }
}
public class OrderNotificationService : IAsyncNotificationHandler<OrderNotification>
{
private readonly IUnitOfWork _unitOfWork;
public OrderNotificationService(IUnitOfWork unitOfWork)
{
_unitOfWork = unitOfWork;
}
public async Task Handle(OrderNotification notification)
{
var note = new Notification { CustomerId = notification.CustumerId, PickUpLocation = notification.PickUpLocation };
await _unitOfWork.Notifications.Insert(note);
await _unitOfWork.Save();
}
}
and your controller :
private IMediator _mediator;
public constructor(IMediator mediator)
{
_mediator = mediator;
}
[HttpPost]
public async Task<IActionResult> Create(OrderIDTO orderIDTO)
{
var order = new Order { ...};
await _unitOfWork.Orders.Insert(order);
var note = new OrderNotification
{
CustomerId = order.CustomerId,
PickUpLocation = order.PickUpLocation
};
await _mediator.PublishAsync(note);
return RedirectToAction("index");
}
Remember before this, you need to install and setup the packages:
Assuming you have created an ASP.Net Core project in Visual Studio, the next step is installing the following NuGet packages.
MediatR
MediatR.Extensions.Microsoft.DependencyInjection
To do that, you can either use the NuGet Package Manager or the NuGet Package Manager Console.
now Configure MediatR in ASP.Net Core
Once the two packages mentioned in the earlier section have been successfully installed in your project, the next step is to configure MediatR in the Startup class. To do this, you should write the following code in the ConfigureServices method. Note that the ConfigureServices method is used to add services at runtime to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddMediatR(typeof(Startup));
services.AddMvc().SetCompatibilityVersion
(CompatibilityVersion.Version_2_2);
}

I not 100% sure if i got what you want. but i think you do not want to use events. simply you can call the OnOrderCreation method like this:
var note = new OrderNotificationService(_unitOfWork);
await note.OnOrderCreation(this, new OrderCreationEvents {.....})

Related

MediatR library: following the DRY principle

I use library MediatR in my ASP.NET Core application.
I have the following entity Ad:
public class Ad
{
public Guid AdId { get; set; }
public AdType AdType { get; set; }
public double Cost { get; set; }
public string Content { get; set; }
// ...
}
public enum AdType
{
TextAd,
HtmlAd,
BannerAd,
VideoAd
}
I want to introduce the ability to create a new ad. To do so, I've created the following command:
public class CreateAdCommand : IRequest<Guid>
{
public AdType AdType { get; set; }
public double Cost { get; set; }
public string Content { get; set; }
public class Handler : IRequestHandler<CreateAdCommand, Guid>
{
private readonly MyDbContext _context;
public Handler(MyDbContext context)
{
_context = context;
}
public async Task<Guid> Handle(CreateAdCommand request, CancellationToken cancellationToken)
{
var ad = new Ad {AdType = request.AdType, Cost = request.Cost, Content = request.Content};
_context.Ads.Add(ad);
_context.SaveChangesAsync();
return ad.AdId;
}
}
}
This code works great. But here is a huge problem: each ad-type has some additional logic to the ad creation process (e.g., when creating the ad of type TextAd we need to find the keywords in the content of the ad). The simplest solution is:
public async Task<Guid> Handle(CreateAdCommand request, CancellationToken cancellationToken)
{
var ad = new Ad {AdType = request.AdType, Cost = request.Cost, Content = request.Content};
_context.Ads.Add(ad);
_context.SaveChangesAsync();
switch (request.AdType)
{
case AdType.TextAd:
// Some additional logic here...
break;
case AdType.HtmlAd:
// Some additional logic here...
break;
case AdType.BannerAd:
// Some additional logic here...
break;
case AdType.VideoAd:
// Some additional logic here...
break;
}
return ad.AdId;
}
This solution violates the Open Closed Principle (when I create a new ad-type, I need to create a new case inside of CreateAdCommand).
I have another idea. I can create a separate command for each ad-type (e.g., CreateTextAdCommand, CreateHtmlAdCommand, CreateBannerAdCommand, CreateVideoAdCommand). This solution follows the Open Closed Principle (when I create a new ad-type, I need to create a new command for this ad-type - I don't need to change the existing code).
public class CreateTextAdCommand : IRequest<Guid>
{
public double Cost { get; set; }
public string Content { get; set; }
public class Handler : IRequestHandler<CreateTextAdCommand, Guid>
{
private readonly MyDbContext _context;
public Handler(MyDbContext context)
{
_context = context;
}
public async Task<Guid> Handle(CreateTextAdCommand request, CancellationToken cancellationToken)
{
var ad = new Ad {AdType = AdType.TextAd, Cost = request.Cost, Content = request.Content};
_context.Ads.Add(ad);
await _context.SaveChangesAsync();
// Some additional logic here ...
return ad.AdId;
}
}
}
public class CreateHtmlAdCommand : IRequest<Guid>
{
public double Cost { get; set; }
public string Content { get; set; }
public class Handler : IRequestHandler<CreateHtmlAdCommand, Guid>
{
private readonly MyDbContext _context;
public Handler(MyDbContext context)
{
_context = context;
}
public async Task<Guid> Handle(CreateHtmlAdCommand request, CancellationToken cancellationToken)
{
var ad = new Ad {AdType = AdType.HtmlAd, Cost = request.Cost, Content = request.Content};
_context.Ads.Add(ad);
await _context.SaveChangesAsync();
// Some additional logic here ...
return ad.AdId;
}
}
}
// The same for CreateBannerAdCommand and CreateVideoAdCommand.
This solution follows the Open Closed Principle, but violates the DRY principle. How can I solve this problem?
If you stick to your second approach, you can levarage MediatR 'Behaviors' (https://github.com/jbogard/MediatR/wiki/Behaviors). They act like pipelines, where you can offload common behavior into a commonly used handler.
To do this, create a marker interface
interface ICreateAdCommand {}
Now let each concreate command inherit from it
public class CreateTextAdCommand : ICreateAdCommand
{
public readonly string AdType {get;} = AdType.Text
}
public class CreateHtmltAdCommand : ICreateAdCommand
{
public readonly string AdType {get;} = AdType.Html
}
/*...*/
You could combine this or replace this with a common abstract base class, to avoid repetition of common properties. This is up to you.
Now we create the handler for our behavior:
public class CreateAdBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
where TReq : ICreateAdCommand
{
public CreateAdBehavior()
{
//wire up dependencies.
}
public async Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate<TResponse> next)
{
var ad = new Ad {AdType = request.AdType, Cost = request.Cost, Content = request.Content};
_context.Ads.Add(ad);
await _context.SaveChangesAsync();
//go on with the next step in the pipeline
var response = await next();
return response;
}
}
Now wire up this behavior. In asp.net core this would be in your startup.cs
services.AddTransient(typeof(IPipelineBehavior<,>), typeof(CreateAdBehavior<,>));
At this stage, everytime any of your IRequests implement ICreateAdCommand, it would automatically call the handler above and after this is done it would call the next behavior in line, or if there is none, the actual handler.
Your specific handler for, let's say a HtmlAd would now roughly look like this:
public class CreateHtmlAdCommand : IRequest<Guid>
{
public class Handler : IRequestHandler<CreateHtmlAdCommand, Guid>
{
private readonly MyDbContext _context;
public Handler(MyDbContext context)
{
_context = context;
}
public async Task<Guid> Handle(CreateHtmlAdCommand request, CancellationToken cancellationToken)
{
// Some additional logic here ...
}
}
}
** Update **
If you want to drag data across the pipeline, you can leverage the actual request object.
public abstract class IRequestWithItems
{
public IDictionary<string, object> Items {get;} = new Dictionary<string,object>();
}
Now in your CreateAdBehavior, you can create your ad and store it in the dictionary, to retrieve it in the next handler:
var ad = { ... }
await _context.SaveChangesAsync();
items["newlyCreatedAd"] = ad;
And in the actual Task<Guid> Handle() method, you have now the ad at your disposal, without looping back to your database to retrieve it again.
Details from the author: https://jimmybogard.com/sharing-context-in-mediatr-pipelines/

How do I configure scoped filters to work the same for consumers as for activities

I have managed to configure scoped services together with scoped filters for consumers, meaning that I can set a value to a scoped service in a filter implementing IFilter<ConsumeContext<T>> and registering the filter with UseConsumeFilter. The filter sets a value in my scoped service and after that the scoped service can be injected into my consumer and still have the value set.
I have tried to do the same thing for activities using IFilter<ExecuteContext<TArguments>> and registering my filter with UseExecuteActivityFilter.
The values set in the ExecuteActivityContext are not reachable in the Activity. I think they become two different DI scopes. I'll share the code from my activity and consumer implementations and maybe there is something missing in the activity one. I have tried to only keep the important part so if there is illegal syntax somewhere it's from me trying to clean up the code for SO.
Is this me using DI in a wrong way or something thats bugged with DI for activities? I tried following the "Scoped Filters" documentation on masstransits website. I'm on .net core 3.1 and masstransit 7.0.4.
Scoped service used for testing
//Interface
public interface IContextService
{
string TenantId { get; set; }
}
//DI registration
services.AddScoped<IContextService, ContextService>();
Activity configuration, this is not working
//Filter
public class RetreiveContextExecuteFilter<TArguments> : IFilter<ExecuteContext<TArguments>>
where TArguments : class
{
public IContextService _contextService { get; }
public RetreiveContextExecuteFilter(IContextService contextService)
{
_contextService = contextService;
}
public async Task Send(ExecuteContext<TArguments> context, IPipe<ExecuteContext<TArguments>> next)
{
_contextService.tenantId = "test-tenant";
await next.Send(context);
}
public void Probe(ProbeContext context)
{
var scope = context.CreateFilterScope("testcontextinformation");
}
}
//Activity
public class ExampleActivity
: IExecuteActivity<ExampleActivityArguments>
{
private readonly IContextService _contextService;
public ExampleActivity(IContextService contextService)
{
_contextService = contextService;
}
public async Task<ExecutionResult> Execute(ExecuteContext<ExampleActivityArguments> context)
{
var tenant = _contextService.tenantId; //Empty
}
}
//DI
services.AddMassTransit(cfg =>
{
cfg.AddActivitiesFromNamespaceContaining<ExampleActivity>();
services.TryAddSingleton(KebabCaseEndpointNameFormatter.Instance);
cfg.UsingRabbitMq(ConfigureBus);
});
private static void ConfigureBus(IBusRegistrationContext context, IRabbitMqBusFactoryConfigurator configurator)
{
configurator.ConfigureEndpoints(context);
configurator.UseExecuteActivityFilter(typeof(RetreiveContextExecuteFilter<>), context);
}
Consumer configuration, this is working
//Filter definition
public class RetreiveContextConsumeFilter<T> : IFilter<ConsumeContext<T>>
where T : class
{
public IContextService _contextService { get; }
public RetreiveContextConsumeFilter(IContextService contextService)
{
_contextService = contextService;
}
public Task Send(ConsumeContext<T> context, IPipe<ConsumeContext<T>> next)
{
_contextService.TenantId = "test tenant";
return next.Send(context);
}
public void Probe(ProbeContext context)
{
context.CreateFilterScope("contextinformation");
}
}
//Consumer
public class ExampleConsumer
: IConsumer<ExampleEvent>
{
private readonly IContextService _contextService;
public ExampleConsumer(IContextService contextService)
{
_contextService = contextService;
}
public async Task Consume(ConsumeContext<ExampleEvent> context)
{
var id = _contextService.TenantId(); //Correct value
}
}
//DI
services.AddMassTransit(cfg =>
{
cfg.AddConsumersFromNamespaceContaining<ExampleConsumer>();
services.TryAddSingleton(KebabCaseEndpointNameFormatter.Instance);
cfg.UsingRabbitMq(ConfigureBus);
});
private static void ConfigureBus(IBusRegistrationContext context, IRabbitMqBusFactoryConfigurator configurator)
{
configurator.ConfigureEndpoints(context);
configurator.UseConsumeFilter(typeof(RetreiveContextConsumeFilter<>), context);
}
First guess, is that your configuration order is incorrect. MassTransit builds pipelines, and you are configuring your endpoints before the filter, which is going to make the filter run after the endpoints. That's my guess.
Change the consumer to:
configurator.UseConsumeFilter(typeof(RetreiveContextConsumeFilter<>), context);
configurator.ConfigureEndpoints(context);
Change the activity to:
configurator.UseExecuteActivityFilter(typeof(RetreiveContextExecuteFilter<>), context);
configurator.ConfigureEndpoints(context);

.net core 3.0 Constructor parameter problem

There is no argument given that corresponds to the required formal parameter 'userRoleService' of 'AuthorizeUserAttribute.AuthorizeUserAttribute(string, IUserRoleService, IModuleService, IUserService)'
AuthorizationController.cs
[AuthorizeUserAttribute("User.Edit")]
public ActionResult UserAuthorizationEdit()
AuthorizeUserAttribute.cs
public string Action { get; set; }
private IUserRoleService _userRoleService;
private IModuleService _moduleService;
private IUserService _userService;
public AuthorizeUserAttribute(IUserRoleService userRoleService, IModuleService moduleService, IUserService userService)
{
_userRoleService = userRoleService;
_moduleService = moduleService;
_userService = userService;
}
When I try to add constructor,controller side says write constructor as a parameter. How Can i change interface to a constructor
You need to use
[TypeFileter(typeof(AuthorizeUser),Arguments = new object[] { "User.Edit" }))]
public ActionResult UserAuthorizationEdit(int userId,
RoleRegisterDto authorizationModel)
in order to dependency injection can inject your services.
If you want to uses interfaces via class constructor using DI,you need to pass the parameter with the same type from custom attribute on controller side.
To avoid doing that, you could register your interfaces as services and get them using below code without constructor injection.For example:
1.Interface
public interface IUserRoleService
{
List<string> GetValues();
}
public class UserRoleService : IUserRoleService
{
private List<string> _privateList = new List<string>();
public List<string> GetValues()
{
_privateList.Add("test");
return _privateList;
}
}
2.In startup:
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IUserRoleService, UserRoleService>();
}
3.Custom Authorization Attribute
public class AuthorizeUserAttribute:AuthorizeAttribute, IAsyncAuthorizationFilter
{
public string Action { get; set; }
public AuthorizeUserAttribute(string action)
{
Action = action;
}
public async Task OnAuthorizationAsync(AuthorizationFilterContext authorizationFilterContext)
{
var x = authorizationFilterContext.HttpContext.RequestServices.GetRequiredService<IUserRoleService>();
var y = x.GetValues();
}
}
4.Action
[AuthorizeUserAttribute("User.Edit")]
public ActionResult UserAuthorizationEdit()

Add custom properties to telemetry request at controller level

I am trying to add specific properties to telemetry request for every route.
After digging a bit, I've found that I can create my own custom TelemetryInitializer by implementing ITelemetryInitializer.
By doing this I've managed to add global properties to the request.
However, I still need to add specific properties at the controller level.
Do you have any idea how can I achieve this?
I've tried to inject TelemetryClient into the controller, but if I use it the properties are shared between requests.
This is how I've tried to log in the controller:
private TelemetryClient telemetryClient;
public ValueController(TelemetryClient telemetryClient)
{
this.telemetryClient = telemetryClient;
}
[HttpGet]
public async Task<IActionResult> RouteOne([FromQuery(Name = "param1")]string param1, [FromQuery(Name = "param2")]string param2)
{
telemetryClient.Context.GlobalProperties["param1"] = param1;
telemetryClient.Context.GlobalProperties["param2"] = param2;
}
[HttpGet]
public async Task<IActionResult> RouteTwo([FromQuery(Name = "param3")]string param3, [FromQuery(Name = "param4")]string param4)
{
telemetryClient.Context.GlobalProperties["param3"] = param3;
telemetryClient.Context.GlobalProperties["param4"] = param4;
}
And this is the implementation of ITelemetryInitializer:
public class CustomPropertiesTelemetryInitializer : ITelemetryInitializer
{
private readonly IHttpContextAccessor httpContextAccessor;
public CustomPropertiesTelemetryInitializer(IHttpContextAccessor httpContextAccessor)
{
this.httpContextAccessor = httpContextAccessor;
}
public void Initialize(ITelemetry telemetry)
{
telemetry.Context.GlobalProperties["RequestId"] = httpContextAccessor.HttpContext.GetProperty("requestId");
telemetry.Context.GlobalProperties["Ip"] = httpContextAccessor.HttpContext?.Connection.RemoteIpAddress.ToString();
telemetry.Context.GlobalProperties["RoutePath"] = httpContextAccessor.HttpContext?.Request.Path;
}
}
If the properties you added are always like "paramxxx", then there is a workaround(but it's really not very elegant).
In the controller constructor, check the GlobalProperties if it contains key like "paramxxx":
public ValueController(TelemetryClient telemetryClient)
{
this.telemetryClient = telemetryClient;
var props = this.telemetryClient.Context.GlobalProperties;
foreach (var p in props)
{
if (p.Key.Contains("param"))
{
props.Remove(p.Key);
}
}
}
The key here is to use the DI framework. You can use it to get request-scoped data or services into your ITelemetryInitializer.
(These examples are based on the standard ASP.Net Dependency Injection framework. This pattern should work with any DI framework, but will need to be adjusted slightly.)
First, create a class to represent your request-scoped telemetry. I've used a simple DTO, but this could also be a service that knows how to fetch/generate the data itself. Register it using AddScoped. "Scoped" means that a new instance will be created for each HTTP request, and then that instance will be re-used within that request.
Because I used a DTO, I didn't bother with an interface--you should use an interface if the class contains any logic you'll want to mock in unit tests.
public class RequestScopedTelemetry
{
public string MyCustomProperty { get; set; }
}
services.AddScoped<RequestScopedTelemetry>();
Now, create the ITelemetryInitializer and register it as a singleton. App Insights will discover and use it through the DI framework.
class RequestScopedTelemetryInitializer : ITelemetryInitializer
{
readonly IHttpContextAccessor httpContextAccessor;
public RequestScopedTelemetryInitializer(IHttpContextAccessor httpContextAccessor)
=> this.httpContextAccessor = httpContextAccessor;
public void Initialize(ITelemetry telemetry)
{
// Attempt to resolve the request-scoped telemetry from the DI container
var requestScopedTelemetry = httpContextAccessor
.HttpContext?
.RequestServices?
.GetService<RequestScopedTelemetry>();
// RequestScopedTelemetry is only available within an active request scope
// If no telemetry available, just move along...
if (requestScopedTelemetry == null)
return;
// If telemetry was available, add it to the App Insights telemetry collection
telemetry.Context.GlobalProperties[nameof(RequestScopedTelemetry.MyCustomProperty)]
= requestScopedTelemetry.MyCustomProperty;
}
}
services.AddSingleton<ITelemetryInitializer, RequestScopedTelemetryInitializer>();
Finally, in your controller method, set your per-request values. This part isn't necessary if your telemetry class is able to fetch or generate the data itself.
public class ExampleController : ControllerBase
{
readonly RequestScopedTelemetry telemetry;
public ValuesController(RequestScopedTelemetry telemetry)
=> this.telemetry = telemetry;
[HttpGet]
public ActionResult Get()
{
telemetry.MyCustomProperty = "MyCustomValue";
// Do what you want to
return Ok();
}
}
In order to add per request data into telemetry, you need to have a way to share data within the request. A reliable way is by using HttpContent.Items property, which is basically a Dictionary.
You can create a service to keep a Dictionary inside HttpContent.Items with all custom data you want in telemetry (key prefix is used to ensure we only read the things we want later in Initializer):
public class LogTelemetryRequest
{
private const string KEY_PREFIX = "CustomTelemetryData_";
private readonly IHttpContextAccessor _httpContextAccessor;
public LogTelemetryRequest(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
public void AddProperty(string key, string value)
{
_httpContextAccessor.HttpContext.Items[KEY_PREFIX + key] = value;
}
}
Register this as scoped in Startup.cs:
services.AddScoped<LogTelemetryRequest>();
Use it in your controller:
private LogTelemetryRequest logTelemetryRequest;
public ValueController(LogTelemetryRequest logTelemetryRequest)
{
this.logTelemetryRequest = logTelemetryRequest;
}
[HttpGet]
public async Task<IActionResult> RouteOne([FromQuery(Name = "param1")]string param1, [FromQuery(Name = "param2")]string param2)
{
// telemetryClient.Context.GlobalProperties["param1"] = param1;
// telemetryClient.Context.GlobalProperties["param2"] = param2;
logTelemetryRequest.AddProperty("param1", param1);
logTelemetryRequest.AddProperty("param2", param2);
}
Then read it within initializer:
public class AddCustomTelemetryInitializer : ITelemetryInitializer
{
private const string KEY_PREFIX = "CustomTelemetryData_";
private readonly IHttpContextAccessor _httpContextAccessor;
public AddCustomTelemetryInitializer(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
public void Initialize(ITelemetry telemetry)
{
var requestTelemetry = telemetry as RequestTelemetry;
if (requestTelemetry == null) return;
foreach (var item in _httpContextAccessor.HttpContext.Items)
{
if (item.Key is string key && key.StartsWith(KEY_PREFIX))
requestTelemetry.Properties.Add(key, item.Value.ToString());
}
}
}
Ideally LogTelemetryRequest should be registered using an interface, and the key prefix should be a single shared constant, didn't do for the sake of simplicity.

How to convert this static class method into a dependency injection? (Specific code included)

(I am sure that I formatted the question badly, I would be happy to revise and fix depending on comments)
I have a static class and I am trying to improve the design with dependency injection. I don't necessarily want this class to be static anymore because I will be using .NET Core, which promotes dependency injection over static class situations.
The simplified code in .NET (not Core):
public static class Utils
{
public static readonly string tokenUrl = ConfigurationManager.AppSettings["tokenUrl"];
public static readonly string tokenKey = ConfigurationManager.AppSettings["tokenKey"];
public async static Task<bool> SendEmail(Email email)
{
var http = new HttpClient();
http.DefaultRequestHeaders.Add("subscription-key", tokenKey);
try
{
await http.PostAsync(tokenUrl + "email", new StringContent(JsonConvert.SerializeObject(email), Encoding.UTF8, "application/json"));
}
catch (Exception e)
{
return false;
}
return true;
}
}
For ConfigurationManager.AppSettings (it does not exist in .NET Core), I am planning to use the method in this link: http://www.danylkoweb.com/Blog/no-configurationmanager-in-aspnet-core-GC
However, for converting this (SendMail) method into a dependency injection, I am quite lost. I have read many examples and articles and I understand the logic of dependency injection but I don't know how to convert this static class into a proper dependency injection. There are other methods in the same Utils class but this is the simplest one and I hope to figure out the others using this one.
An approach that I was thinking off was:
public interface ISendMail
{
FormSettings ConfigSettings { get; set; }
Task<bool> SendEmail(IOptions<FormSettings> settings, Email email);
}
and:
public class SendEmail : ISendMail
{
public async static Task<bool> SendEmail(IOptions<FormSettings> settings, Email email)
{
//do same things
}
}
but I am CLEARLY lost with this because it does not even make sense. Another approach that I was thinking of was:
public class SendEmail
{
FormSettings ConfigSettings { get; set; }
protected Email email = null;
public SendEmail(IOptions<FormSettings> settings, Email email)
{
ConfigSettings = settings.Value;
this.email = email;
}
public async static Task<bool> SendEmailAction()
{
//do same things with "email" and "ConfigSettings"
}
}
I know I am giving a lot of code here and I wasn't sure if I should ask about this in "Code Review" or something. My biggest concern is not the FormSettings part but implementing the functionality of SendEmail in a dependency injection format.
Shortly, how can I convert this "SendEmail" class into a format where I can use it with .NET Core, without having a static class? This particular method does not require change with .NET Core but my other methods do, that is why I am trying to get rid of the static class approach.
I can exclude the tokenUrl and tokenKey parts and simplify the problem if requested, I am just quite lost as to how to approach this situation.
What should do this class? Sending email, right? So interface:
public interface IEmailSender
{
Task<bool> Send(Email email);
}
How we can implement it? Like this:
public class MyEmailSenderOne : IEmailSender
{
public static readonly string tokenUrl = ConfigurationManager.AppSettings["tokenUrl"];
public static readonly string tokenKey = ConfigurationManager.AppSettings["tokenKey"];
public async Task<bool> Send(Email email)
{
var http = new HttpClient();
http.DefaultRequestHeaders.Add("subscription-key", tokenKey);
try
{
await http.PostAsync(tokenUrl + "email", new StringContent(JsonConvert.SerializeObject(email), Encoding.UTF8, "application/json"));
}
catch (Exception e)
{
return false;
}
return true;
}
}
or
public class MyAnotherAwesomeEmailSender : IEmailSender
{
public async Task<bool> Send(Email email)
{
// send with different way
return true;
}
}
How we can inject this?
public class SomeClass
{
private IEmailSender _sender;
public SomeClass(IEmailSender sender)
{
_sender = sender;
}
public void Foo()
{
// do smth useful
_sender.Send(new Email());
}
}
UPD.
Because your email settings persistant (will not change during lifetime), and because this settings related ONLY to your implementation of IEMailSender, you should to inject them in your implementation. Just think about = why caller code (Controller) should know about how your implementation works?
So
public class MyEmailSenderOne : IEmailSender
{
private FormSettings _settings;
public MyEmailSenderOne(IOptions<FormSettings> settings)
{
_settings = settings.Value;
}
public async Task<bool> Send(Email email)
{
var http = new HttpClient();
http.DefaultRequestHeaders.Add("subscription-key", _settings.tokenApiKey);
try
{
await http.PostAsync(_settings.tokenApiUrl + "email", new StringContent(JsonConvert.SerializeObject(email), Encoding.UTF8, "application/json"));
}
catch (Exception e)
{
return false;
}
return true;
}
}
And, controller now dint know about any settings for your implementation, and it looks like
public class CommunicationsController : Controller
{
private IEmailSender _sender;
public CommunicationsController(IEmailSender sender)
{
_sender = sender;
}
public async Task<ActionResult> ContactUsFormSubmit(ContactUs request)
{
...
request.EmailSent = await _sender.SendEmail(new Email() { TemplateId = 3, Body = JsonConvert.SerializeObject(request) });
...
}
}
As you can see, controller is very clean now and you can easily change your implementation of IEmailSender to any other without changing Controller code. This is one of advantages of using DI.
Based on tym32167's answer, I was able to implement the IEmailSender functionality (finally). I will still choose his answer as the correct answer but this is how I implemented dependency injection.
Please read the link I provided in the question, if you'd like to know more about the IOptions and FormSettings class that I am using.
Here is the interface and the class:
public interface IEmailSender
{
Task<bool> SendEmail(Email email, FormSettings settings);
}
public class EmailSender : IEmailSender
{
FormSettings ConfigSettings { get; set; }
public async Task<bool> SendEmail(Email email, FormSettings settings)
{
var http = new HttpClient();
http.DefaultRequestHeaders.Add("subscription-key", settings.tokenApiKey);
try
{
await http.PostAsync(settings.tokenApiUrl + "email", new StringContent(JsonConvert.SerializeObject(email), Encoding.UTF8, "application/json"));
}
catch (Exception e)
{
return false;
}
return true;
}
}
In controller injection:
public class CommunicationsController : Controller
{
private IEmailSender _sender;
private FormSettings ConfigSettings { get; set; }
public CommunicationsController(IEmailSender sender, IOptions<FormSettings> settings)
{
_sender = sender;
ConfigSettings = settings.Value;
}
public async Task<ActionResult> ContactUsFormSubmit(ContactUs request)
{
...
request.EmailSent = await _sender.SendEmail(new Email() { TemplateId = 3, Body = JsonConvert.SerializeObject(request) }, ConfigSettings);
...
}
Here is FormSettings just for easy reference in case the link dies:
public class FormSettings
{
public string tokenApiUrl { get; set; }
public string tokenApiKey { get; set; }
}
I hope I didn't miss any details, so far it didn't give me any errors but since we do not have unit testing in the project, I won't be able to test immediately. Please let me know if there is something missing with the answer.

Categories