Clean way to specify options for a service when doing DI - asp.net-core

So I have a service lets say for example it's an email service on ASPNET Core.
When I add my service to the ASPNET DI container I would like to apply the following pattern on my IServiceCollection to setup my service.
public interface IEmailService
{
void SendMail(string recipient, string message);
}
public void ConfigureServices(IServiceCollection services)
{
//configures my service
services.AddEmailService<MyEmailService>(options => options.UseEmailServer(sender, smtpHost, smtpPort, smtpPassword));
}
I would like to know whats the best way to do this if possible. I am sure I would need to make an extension method for the .AddEmailService() method on IServiceCollection however anything beyond that I am not sure where to start or look.

Here's an example application with comments to let you know what the different things are doing:
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
// Add the options stuff. This will allow you to inject IOptions<T>.
services.AddOptions();
// This will take care of adding and configuring the email service.
services.AddEmailService<MyEmailService>(options =>
{
options.Host = "some-host.com";
options.Port = 25;
options.Sender = "firstname#lastname.com";
options.Username = "email";
options.Password = "sup4r-secr3t!";
});
}
public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory)
{
// Make sure we add the console logger.
loggerFactory.AddConsole();
app.Use(async (context, next) =>
{
// Retrieve the email service from the services.
var emailService = context.RequestServices.GetRequiredService<IEmailService>();
// Send the email
await emailService.SendMail("hello#recipient.com", "Hello World!");
});
}
public static void Main(string[] args)
{
WebApplication.Run<Startup>(args);
}
}
public interface IEmailService
{
Task SendMail(string recipient, string message);
}
public class EmailOptions
{
public string Sender { get; set; }
public string Host { get; set; }
public int Port { get; set; }
public string Username { get; set; }
public string Password { get; set; }
}
public class MyEmailService : IEmailService
{
public MyEmailService(IOptions<EmailOptions> options, ILogger<MyEmailService> logger)
{
Options = options; // This contains the instance we configured.
Logger = logger;
}
private IOptions<EmailOptions> Options { get; }
private ILogger<MyEmailService> Logger { get; }
public Task SendMail(string recipient, string message)
{
// Send the email
var builder = new StringBuilder();
builder.AppendLine($"Host: {Options.Value.Host}");
builder.AppendLine($"Port: {Options.Value.Port}");
builder.AppendLine($"Username: {Options.Value.Username}");
builder.AppendLine($"Password: {Options.Value.Password}");
builder.AppendLine("---------------------");
builder.AppendLine($"From: {Options.Value.Sender}");
builder.AppendLine($"To: {recipient}");
builder.AppendLine("---------------------");
builder.AppendLine($"Message: {message}");
Logger.LogInformation(builder.ToString());
return Task.FromResult(0);
}
}
public static class ServiceCollectionExtensions
{
public static IServiceCollection AddEmailService<TEmailService>(this IServiceCollection services, Action<EmailOptions> configure)
where TEmailService : class, IEmailService
{
// Configure the EmailOptions and register it in the service collection, as IOptions<EmailOptions>.
services.Configure(configure);
// Add the service itself to the collection.
return services.AddSingleton<IEmailService, TEmailService>();
}
}
And here's the application running in the console:
As you can see, the application is pulling some information from the configured EmailOptions, and some information form the arguments passed in.
EDIT: These are the required packages:
"Microsoft.AspNet.Server.Kestrel": "1.0.0-rc1-final",
"Microsoft.Extensions.OptionsModel": "1.0.0-rc1-final",
"Microsoft.Extensions.Logging.Console": "1.0.0-rc1-final"

Related

Blazor : How to read appsetting.json from a class in .NET 6?

The following is working for me, but not sure this is the right way to do use DI in .NET6 blazor.
I have the following class
public class Authentication
{
private IConfiguration _configuration;
private AppState _appState;
public Authentication(IConfiguration Configuration, AppState appState)
{
_configuration = Configuration;
_appState = appState; ;
}
public async Task<AccessToken?> getAccessToken()
{
var tokenServer = _configuration.GetValue<string>("tokenUrl");
var clientID = _configuration.GetValue<string>("ABC:ClientID");
var clientSecret = _configuration.GetValue<string>("ABC:ClientSecret");
var grantType = _configuration.GetValue<string>("ABC:GrantType");
AccessToken? accessToken = null;
.............
............
return accessToken;
}
}
in my code behind of razor page
namespace XXXXXXXXXXX.Pages
{
public partial class Index
{
[Inject]
public ILogger<Index> _Logger { get; set; }
[Inject]
public IConfiguration Configuration { get; set; }
[Inject]
public AppState _appState { get; set; }
**Authentication auth;**
protected override void OnInitialized()
{
**auth = new Authentication(Configuration, _appState);**
base.OnInitialized();
}
private async Task HandleValidSubmit()
{
_Logger.LogInformation("HandleValidSubmit called");
auth.getAccessToken();
// Process the valid form
}
}
}
My Question is I was Expecting the DI to do its magic and Insert the Dependency in my class.
but to get this working i had to write
auth = new Authentication(Configuration, _appState);
I was expecting to instantiate
using auth = new Authentication() , but this one throws compiler error.

How to get AppSetting values in startup by using services in asp.net core?

I want to get value of appsetting inside StartUp and also using services for saving them.
I create a static IServiceCollection method for AddTransient my custom service.
I define a readonly variable for keep the appsetting values. My problem is that, this service creates new instance for readonly variable, for all calling.how can I prevent this?
and I have a question that other extensions like AddOpenIdConnect, how to work with their configs, I mean how to save and use them?
this is startup:
public void ConfigureServices(IServiceCollection services){
...
services.AddMyIntegration(conf =>
{
conf.ConnectionString = Configuration.GetConnectionString("Integration");
conf.AgentApiAddress = Configuration["AgentApiAddress"];
});
}
....
public static class MyExtension
{
public static IServiceCollection AddMyIntegration(this IServiceCollection services, Action<MyConstantsProvider> myConstantsProvider)
{
services.AddTransient((t) =>
{
return new MyService(myConstantsProvider);
});
return services;
}
}
this is my service:
public class MyService
{
public readonly MyConstantsProvider Provider;
public MyService(Action<MyConstantsProvider> configure)
{
Provider = new MyConstantsProvider();
configure(Provider);
}
}
public class MyConstantsProvider
{
public string ConnectionString { get; set; }
public string AgentApiAddress { get; set; }
}
Update my question:
Finally I fixed my issue by add MyConstantsProvider as singletone instead of MyService so this creates new instance of variable at the first time in extension class:
public static class MyExtension
{
public static IServiceCollection AddMyIntegration(this IServiceCollection services, Action<MyConstantsProvider> myConstantsProvider)
{
var provider = new MyConstantsProvider();
myConstantsProvider(provider);
services.AddSingleton(provider);
services.AddTransient<MyService>();
return services;
}
}
this is MyService class:
public class MyService
{
public readonly MyConstantsProvider Provider;
public MyService(MyConstantsProvider provider)
{
Provider = provider;
}
}
I wonder why we make it so complicated ? I just saw we're trying to read appsettings later in the application somewhere, and for this, the framework have default implementation to back us up.
Our app settings might look like
{
"Catalog": {
"ConnectionString": "SomeConnection",
"AgentApiAddress": "http://somewhere.dev"
}
}
Then our class could be
public class MySetting
{
public string ConnectionString { get; set; }
public string AgentApiAddress{ get; set; }
}
Config register it in startup (or somewhere we like in .net 6)
services.Configure<MySetting>(configuration.GetSection("Catalog"));
Retrive it later in the app via DI
public class SomeService
{
private readonly MySetting _setting;
public SomeService(IOptions<MySetting> config)
{
_setting = config.Value;
}
}
For setting that can be change dynamically, take a look at IOptionsMonitor
Or that might be some special case that I miss ?

inject Database Context into Custom Attribute .NET Core

I'm creating ASP.NET Core 3.1 app, using SPA for front end. So I decided to create custom Authentication & Authorization. So I created custom attributes to give out and verify JWTs.
Lets say it looks like this:
[AttributeUsage(AttributeTargets.Method)]
public class AuthLoginAttribute : Attribute, IAuthorizationFilter
{
public async void OnAuthorization(AuthorizationFilterContext filterContext)
{
//Checking Headers..
using (var EF = new DatabaseContext)
{
user = EF.User.Where(p => (p.Email == username)).FirstOrDefault();
}
filterContext.HttpContext.Response.Headers.Add(
"AccessToken",
AccessToken.CreateAccessToken(user));
}
}
Everything was Okay, but my DatabaseContext, looked like this:
public class DatabaseContext : DbContext
{
public DbSet<User> User { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseMySQL("ConnectionString");
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
//....
}
}
I wanted to take Connection string from Appsettings.json and maybe use Dependency injection. I
Changed Startup.cs to look like this:
//...
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
services.AddDbContext<DatabaseContext>(
options => options.UseMySQL(Configuration["ConnectionStrings:ConnectionString"]));
services.Add(new ServiceDescriptor(
typeof(HMACSHA256_Algo), new HMACSHA256_Algo(Configuration)));
services.AddSpaStaticFiles(configuration =>
{
configuration.RootPath = "ClientApp/build";
});
}
//...
Changed Database Context class to this:
public class DatabaseContext : DbContext
{
public DatabaseContext(DbContextOptions<DatabaseContext> options) : base(options) { }
public DbSet<User> User { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
///..
}
}
In Controllers I injected DB context and everything works. It looks like this:
[ApiController]
[Route("API")]
public class APIController : ControllerBase
{
private DatabaseContext EF;
public WeatherForecastController(DatabaseContext ef)
{
EF = ef;
}
[HttpGet]
[Route("/API/GetSomething")]
public async Task<IEnumerable<Something>> GetSomething()
{
using(EF){
//.. this works
}
}
}
But my custom Attribute doesn't work no more. I can't declare new Database context, because it needs DatabaseContextOptions<DatabaseContext> object to declare, so how do I inject DBContext to Attribute as I did to Controller?
This doesn't work:
public class AuthLoginAttribute : Attribute, IAuthorizationFilter
{
private DatabaseContext EF;
public AuthLoginAttribute(DatabaseContext ef)
{
EF = ef;
}
public async void OnAuthorization(AuthorizationFilterContext filterContext)
{
using(EF){
}
}
}
this works with controller, but with attribute complains about there not being constructor with 0 arguments.
What you can do is utilize the RequestServices:
[AttributeUsage(AttributeTargets.Method)]
public class AuthLoginAttribute : Attribute, IAuthorizationFilter
{
public void OnAuthorization(AuthorizationFilterContext context)
{
var dbContext = context.HttpContext
.RequestServices
.GetService(typeof(DatabaseContext)) as DatabaseContext;
// your code
}
}
If you allow me to add two comments to your code:
Try not to use async void because in the event of an exception you will be very confused what is going on.
There is no need to wrap injected DbContext in a using statement like this using(EF) { .. }. You will dispose it early and this will lead to bugs later in the request. The DI container is managing the lifetime for you, trust it.

Access to the generalized collection of hubs

I am tormented by the question, if I add several hubs (hub1, hub2, ...) to the project (asp.core), can I get somewhere a generalized collection of these hubs, or their contexts? Something like:
public class SomeClass
{
private readoly IHubCollection _collection;
public SomeClass(IHubCollection collection)
=> _collection = collection;
public void SomeMethod()
{
foreach(vat hub in _collection)
{
hub.SendSomeMessage();
}
}
}
For your requirement, there are some limitions like the hub need to implement the same interfance which contains SendSomeMessage.
Try following steps below:
IHub
public interface IHub
{
void SendSomeMessage();
}
ChatHub
public class ChatHub : Hub, IHub
{
public void Send(string name, string message)
{
// Call the broadcastMessage method to update clients.
Clients.All.SendAsync("broadcastMessage", name, message);
}
public void SendSomeMessage()
{
Clients.All.SendAsync("broadcastMessage", "hub", "hello");
}
}
Register Hub
public class Startup
{
// This method gets called by the runtime. Use this method to add services to the container.
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
public void ConfigureServices(IServiceCollection services)
{
services.AddSignalR();
services.AddMvc();
services.AddSingleton<ChatHub>();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseFileServer();
app.UseSignalR(routes =>
{
routes.MapHub<ChatHub>("/chat");
});
app.UseMvcWithDefaultRoute();
}
}
UseCase
[Route("api/[controller]")]
public class ValuesController : Controller
{
private readonly IServiceProvider _serviceProvider;
public ValuesController(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
// GET: api/<controller>
[HttpGet]
public async Task<IEnumerable<string>> Get()
{
var typesFromAssemblies = Assembly.GetExecutingAssembly().GetTypes().Where(x => x.BaseType == typeof(Hub));
foreach (var type in typesFromAssemblies)
{
var hub = _serviceProvider.GetService(type) as IHub;
hub.SendSomeMessage();
}
return new string[] { "value1", "value2" };
}
}

.Net Core How to Access Configuration Anywhere in application

I have read through the documentation on the different ways to setup and access configuration in .Net Core 2.1 and also the options pattern that seems to be recommended (https://learn.microsoft.com/en-us/aspnet/core/fundamentals/configuration/options?view=aspnetcore-2.1). However, I can't seem to get what I want working:
I have done the following:
AppSettings:
{
"ConnectionStrings": {
"DefaultConnStr": "Server=(localdb)\\MSSQLLocalDB;Database=_CHANGE_ME;Trusted_Connection=True;MultipleActiveResultSets=true;Integrated Security=true",
"AW2012ConnStr": "Server=localhost;Database=AW2012;Trusted_Connection=True;MultipleActiveResultSets=true;Integrated Security=true"
}
}
MyConfig:
public class MyConfig
{
public string AWConnStr { get; }
public string DefaultConnStr { get; }
}
Startup:
public class Startup
{
public IConfiguration _config { get; set; }
public Startup(IHostingEnvironment env)
{
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true);
_config = builder.Build();
}
public void ConfigureServices(IServiceCollection services)
{
services.AddOptions();
//add config to services for dependency injection
//services.AddTransient<IMyConfig, MyConfig>();
//services.AddScoped<IMyConfig, MyConfig>();
var section = _config.GetSection("ConnectionStrings");
services.Configure<MyConfig>(section);
}
private static void HandleGetData(IApplicationBuilder app)
{
//DataHelper dataHelper = new DataHelper(_dataHelper);
var _dataHelper = app.ApplicationServices.GetService<DataHelper>();
app.Run(async context =>
{
//await context.Response.WriteAsync("<b>Get Data</b>");
//await context.Response.WriteAsync(dataHelper.GetCompetitions(context.Request.QueryString.ToString()));
await context.Response.WriteAsync(_dataHelper.GetCompetitions(context.Request.QueryString.ToString()));
});
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.Map("/Route1", HandleRoute1);
app.Map("/Route2", HandleRoute2);
app.Map("/GetData", HandleGetData);
app.Run(async (context) =>
{
await context.Response.WriteAsync("Non Mapped Default");
});
}
}
I would like to then access the configuration in any class anywhere in my code. So for example I have the following class where I would like to just read the configuration information:
public interface IDataHelper
{
string GetCompetitions(string val);
}
public class DataHelper : IDataHelper
{
private readonly MyConfig _settings;
public DataHelper(IOptions<MyConfig> options)
{
_settings = options.Value;
}
public string GetCompetitions( string queryStringVals)
{
return _settings.AWConnStr;
}
}
As shown above in my Startup class I then want to access/call something in the HandleGetData function in my startup, so that when I browse to the following route: http://localhost:xxxxx/getdata I get back the response from the Something.GetData function.
Is this correct? The problem I'm having is that when I create an instance of class Something, it is requiring me to pass in the configuration object, but doesn't that defeat the purpose of injecting it. How should I be setting this up to work similar to how DBContext gets the context injected with the configuration options. And what's the difference between services.AddTransient and services.AddScoped? I've seen both as a way to register the service.
I would say that in .Net Core application you shouldn't pass instance of IConfiguration to your controllers or other classes. You should use strongly typed settings injected through IOtions<T> instead. Applying it to your case, modify MyConfig class (also property names should match names in config, so you have to rename either config (DefaultConnection->DefaultConnStr, AW2012ConnStr->AWConnStr or properies vice versa):
public class MyConfig
{
public string AWConnStr { get; set; }
public string DefaultConnStr { get; set; }
}
Register it:
public void ConfigureServices(IServiceCollection services)
{
// in case config properties specified at root level of config file
// services.Configure<MyConfig>(Configuration);
// in case there are in some section (seems to be your case)
var section = Configuration.GetSection("ConnectionStrings");
services.Configure<MyConfig>(section);
}
Inject it to required service:
public class MyService
{
private readonly MyConfig _settings;
public MyService(IOptions<MyConfig> options)
{
_settings = options.Value;
}
}
And what's the difference between services.AddTransient and
services.AddScoped? I've seen both as a way to register the service.
Transient lifetime services are created each time they're requested.
Scoped lifetime services are created once per request.
You have to do the same thing for the Something as you did for MyConfig like:
public interface ISomething
{
string GetSomeData();
}
Then:
public class Something : ISomething
{
public IConfiguration _config { get; set; }
public Something(IConfiguration configuration)
{
_config = configuration;
}
public string GetSomeData()
{
return _config["DefaultConnStr"];
}
}
Then in the ConfigureService method of the Startup class as follows:
services.AddScoped<ISomething,Something>();
Then call the GetSomeData() as follows:
public class CallerClass
{
public ISomething _something { get; set; }
public CallerClass(ISomething something)
{
_something = something;
}
public string CallerMethod()
{
return _something.GetSomeData();
}
}
Then:
And what's the difference between services.AddTransient and services.AddScoped? I've seen both as a way to register the service.
Here is the details about this from microsoft:
Service Lifetime details in ASP.NET Core