I see a lot of code examples on how to use DI in .NET Core, however none of them use constructor parameters.
For example:
Create Authorization Service
Inject the current HTTP header(X-Api-Key) in constructor
In the implementation check if I have access
Here I need to not only use DI on my IAuthorizationService but also inject the token in the constructor. I know how to do it in Ninject, however have no experience in .NET Core DI.
Here is what I have as an example.
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddEntityFrameworkSqlite();
services.AddDbContext<MainDbContext>();
services.AddScoped<IAuthorizationService, AuthorizationService>(); // Inject current HttpContext header value as a constructor?
}
I usually flow such values through a service where the data is set in a piece of middleware. For example:
An accessor class which can be injected:
public class ApiKeyAccessor
{
public string ApiKey { get; set; }
}
And a middleware which sets the API key at the beginning of the request:
public class ApiKeyMiddleware
{
private readonly RequestDelegate _next;
public ApiKeyMiddleware(RequestDelegate next)
{
_next = next;
}
public Task Invoke(HttpContext context, ApiKeyAccessor apiKeyAccessor)
{
StringValues key;
if (context.Request.Headers.TryGetValue("X-Api-Key", out key))
{
apiKeyAccessor.ApiKey = key;
return _next(context);
}
// todo: throw exception, etc..
}
}
Now all we have to is add the ApiKeyAccessor to the DI container with a scoped lifetime and add the ApiKeyMiddleware to the request execution pipeline, preferably as soon as possible.
When configured correctly, we can inject the ApiKeyAccessor instance in controllers or services:
public class AuthorizationService
{
private readonly string _apiKey;
public AuthorizationService(ApiKeyAccessor apiKeyAccessor)
{
_apiKey = apiKeyAccessor.ApiKey;
}
}
Related
In the DI container to create a singleton would the following be an ok way to do?
public void ConfigureServices(IServiceCollection services)
{
var botClient = new TelegramBotClient(_config["Tokens:Telegram"]);
services.AddSingleton(botClient);
}
TelegramBotClient is from the library that I'm using, so I can't start changing that.
A more orthadox way of handling a DI service with a configuration is to use the IOptions pattern. This way you aren't tightly coupling your startup object with the service. As it stands now, if your configuration changes, you have to modify your startup object.
A way to tackle this and keep your concerns separated, take a look at this:
TelegramBotClientService.cs
public interface ITelegramBotClientService
{
Task DoSomethingAsync();
}
public sealed class TelegramBotClientService : ITelegramBotClientService
{
private readonly TelegramConfigModel _config;
public TelegramBotClientService(IOptions<TelegramConfigModel> options)
{
_config = options.Value;
}
public Task DoSomethingAsync()
{
var token = _config.Token;
// ...
}
}
Startup.cs
// ...
public void ConfigureServices(IServiceCollection services)
{
services.Configure<TelegramConfigModel>(
Configuration.GetSection("TelegramConfig"));
services.AddSingleton<ITelegramBotClientService, TelegramBotClientService>();
}
// ...
appsettings.json
{
"TelegramConfig": {
"Token": "12345"
}
}
TelegramConfigModel.cs
public sealed class TelegramConfigModel
{
public string Token { get; set; }
}
This hasn't been tested, so there may be a typo somewhere, but, now your concerns are separated. The DI pipeline is now doing the instantiation and also injecting your configurations.
A side note
I noticed you may be injecting a singleton to maintain a bot. I would highly suggest you use IHostedService or BackgroundService and inject using AddHostedService to maintain something like a bot.
I have introduced SignalR into my ASP.NET Core 2 project, but I'm having some issues using a scoped service that I normally use in my controllers. I feel like the problem may be due to the difference in lifecycles between HTTP requests, websockets and hubs.
On each HTTP request, middleware reads the Authorization token and updates some properties (e.g. id, claims, etc.) on a scoped service (IUser) for the request. I use this service in all of my controllers with no issue. To get this to work with SignalR, I am sending an access_token query parameter and using some other middleware beforehand to add this query parameter as a header which works fine.
The problem arises when I am trying to access the IUser service in my SignalR hub. On construction of the hub, the IUser that gets injected has none of the properties set, despite the middleware for the /hub request just setting them.
If I set the service to be a singleton then it works, but IUser should never persist longer than a single request.
How should I go about setting an IUser for a specific SignalR connection?
// Startup.cs - User has several settable properties
services.AddScoped<IUser, User>();
// User Middleware
public class UserMiddleware
{
private readonly RequestDelegate _next;
public UserMiddleware(RequestDelegate next)
{
_next = next;
}
public Task Invoke(HttpContext context)
{
// retrieve service for this request
var user = context.RequestServices.GetService<IUser>();
// set some properties on the User from auth token
// e.g. User.Id = 1;
return _next(context);
}
}
[Authorize(Roles = Claim.Names.Read)]
public class MyHub : Hub
{
private readonly IUser _user;
public MyHub(IUser user)
{
// user that gets injected has null properties
_user = user;
}
public async Task Foo()
{
// do work with the _user credentials
}
}
public class DownloadController : Controller
{
private readonly IUser _user;
public DownloadController(IUser user)
{
// user gets injected and has properties set
_user = user;
}
}
Have you tried Context.GetHttpContext().RequestServices in your Hub-method? That should be the IServiceProvider of the ongoing request.
To use scope service in signalr hub, you could inject ServiceProvider and create a scope in it directly:
public class ChatHub : Hub
{
private IServiceProvider _serviceProvider;
public ChatHub(IServiceProvider serviceProvider)
{
_serviceProvider= serviceProvider;
}
public async Task Foo()
{
using (var scope = _serviceProvider.CreateScope())
{
var dbContext = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
var user = scope.ServiceProvider.GetRequiredService<IUser>();
}
}
}
We need to do some work inside a middleware according to a parameter inside an appsettings.json. There parameter can change on runtime.
For that I can set reloadOnChange at settings file registration
builder.AddJsonFile("appsettings.json",
optional: false, reloadOnChange: true)
This work in the case I use IOptionsSnapshopt inside a controller, because a controller is created per request. But a middleware is perlifetime.
I found Asp.net core 2.0 middleware - accessing config settings where is written how to access parameter from appsettings. --> But this works not if the parameter changes on runtime.
As per the documentation ASP.NET Core Middleware: Per-request dependencies
Because middleware is constructed at app startup, not per-request, scoped lifetime services used by middleware constructors are not shared with other dependency-injected types during each request. If you must share a scoped service between your middleware and other types, add these services to the Invoke method's signature. The Invoke method can accept additional parameters that are populated by dependency injection.
For example, instead of in the constructor, add IOptionsSnapshot parameter to Invoke method.
public static class HelloWorldMiddlewareExtensions
{
public static IApplicationBuilder UseHelloWorld(this IApplicationBuilder builder)
{
return builder.UseMiddleware<HelloWorldMiddleware>();
}
}
public class HelloWorldMiddleware
{
private readonly RequestDelegate _next;
public HelloWorldMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context, IOptionsSnapshopt<AppSettings> options)
{
await context.Response.WriteAsync($"PropA: {options.Value.PropA}");
}
}
public class AppSettings
{
public string PropA { get; set; }
}
I am using SimpleInjector for my DI in Mvc Core and I have a class that accepts ISession at the constructor.
public SessionAppAdminAuthorization(ISession session)
I need to register this at the DI configuration in StartUp.Configure method but I don't know how the get the scoped session variable.
container.Register<IAppAdminAuthorization>(() => {
return new SessionAppAdminAuthorization([I Need the ISession]); },
Lifestyle.Scoped);
ASP.NET Core's ISession can be accessed through the HttpContext.Session property. Since HttpContext is runtime data, the Session is as well. Runtime data should not be injected into your components' constructors, so your SessionAppAdminAuthorization should not depend on ISession directly.
The simplest fix is to let SessionAppAdminAuthorization depend on IHttpContextAccessor instead and call IHttpContextAccessor.HttpContext.Session later on. Example:
public class SessionAppAdminAuthorization : IAppAdminAuthorization
{
private readonly IHttpContextAccessor accessor;
public SessionAppAdminAuthorization(IHttpContextAccessor accessor) {
this.accessor = accessor;
}
public void DoSomethingUseful() {
if (this.accessor.HttpContext.Session.GetBoolean("IsAdmin")) {
// ...
} else {
// ...
}
}
}
Now you can make the registrations as follows:
public void ConfigureServices(IServiceCollection services)
{
// You need to register IHttpContextAccessor.
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
// ...
}
public void Configure(IApplicationBuilder app, IHostingEnvironment e, ILoggerFactory f)
{
container.RegisterSingleton(
app.ApplicationServices.GetRequiredService<IHttpContextAccessor>());
container.Register<IAppAdminAuthorization, SessionAppAdminAuthorization>();
// ...
}
Although this will effectively solve your problem, you might want to take it up one step. In general it's better to hide framework components and abstractions like IHttpContextAccessor, HttpContext and ISession from application components. Instead the Dependency Inversion Principle guides us towards application-specific abstractions implemented by adapters that allow translating these application-specific calls onto framework components. For instance:
// Application-specific abstraction (part of your application's core layer)
public interface IUserContext
{
bool IsAdmin { get; }
}
// Adapter implementation (placed in the Composition Root of your web app)
public class AspNetSessionUserContextAdapter : IUserContext
{
private readonly IHttpContextAccessor accessor;
public AspNetSessionUserContextAdapter(IHttpContextAccessor accessor) {
this.accessor = accessor;
}
public bool IsAdmin => this.accessor.HttpContext.Session.GetBoolean("IsAdmin");
}
// Improved version of SessionAppAdminAuthorization
public class SessionAppAdminAuthorization : IAppAdminAuthorization
{
private readonly IUserContext userContext;
// This class can now be moved to the business layer, since there's no
// more dependency on ASP.NET.
public SessionAppAdminAuthorization(IUserContext userContext) {
this.userContext = userContext;
}
public void DoSomethingUseful() {
if (this.userContext.IsAdmin) {
// ...
} else {
// ...
}
}
}
Registration:
public void Configure(IApplicationBuilder app, IHostingEnvironment e, ILoggerFactory f)
{
var accesr = app.ApplicationServices.GetRequiredService<IHttpContextAccessor>();
container.RegisterSingleton<IUserContext>(new AspNetSessionUserContextAdapter(accesr));
container.Register<IAppAdminAuthorization, SessionAppAdminAuthorization>();
// ...
}
Trying to do dependency injection into my SignalR Hub class using the SignalR-Server which is part of ASP.NET 5 (repo). I tried to figure this out from the tutorial at this link but I can't seem to identify how I can do this given that GlobalHost is no longer available. Here's what I'm trying to do:
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddSignalR();
services.AddSingleton<IState, State>();
}
public void Configure(IApplicationBuilder app)
{
app.UseSignalR();
}
MyHub.cs
public class MyHub : Hub
{
public IState State { get; set; }
// SignalR accepts this parameterless ctor
public MyHub()
{
}
// SignalR won't use this because it has a parameter
public MyHub(IState state)
{
State = state;
}
}
How can I get SignalR-Server to use the MyHub(IState state) constructor injecting the required dependencies?
the best way (for Asp.Net 5) create a custom resolver to DefaultDependencyResolver that receives IServiceProvider:
public class CustomSignalRDependencyResolver : DefaultDependencyResolver
{
private readonly IServiceProvider _serviceProvider;
public CustomSignalRDependencyResolver(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
public override object GetService(Type serviceType)
{
var service = _serviceProvider.GetService(serviceType);
return service ?? base.GetService(serviceType);
}
}
Then on StartUp class
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IState, State>();
//... other services
GlobalHost.DependencyResolver = new CustomSignalRDependencyResolver(services.BuildServiceProvider());
}
I managed to resolve this by adding my State class as a Singleton for IState in Startup.ConfigureServices, and then making a ServiceProvider property publicly available on my Startup.cs class. From there, I was able to GetRequiredService within the constructor of my SignalR Hub class. It isn't the ideal solution and hopefully I'll be able to adjust this to use constructor/property injection as the platform reaches RC.
Here's my code:
Startup.cs
public static IServiceProvider __serviceProvider;
public void ConfigureServices(IServiceCollection services)
{
services.AddSignalR();
services.AddSingleton<IState, State>();
__serviceProvider = services.BuildServiceProvider();
}
public void Configure(IApplicationBuilder app)
{
app.UseSignalR();
}
MyHub.cs
public class MyHub : Hub
{
public IState State { get; set; }
public MyHub()
{
State = (IState) Startup.__serviceProvider.GetRequiredService(typeof (IState));
}
public override Task OnConnected()
{
State.Clients = Clients;
State.Groups = Groups;
return base.OnConnected();
}
}
In this way, I was able to set properties and call methods on IState implementing objects from within MyHub, allowing me to persist my app state in memory.
You're very close. You just need:
public class MyHub : Hub
{
readonly IState _state;
public MyHub(IState state)
{
_state = state;
}
}
Ok. Now, I used Autofac, which I am not sure it has ASP.NET 5 integration yet. But if(for now) only target .NET 4.6, you should be fine.
I just published this repository which contains a basic project setup with SignalR and Autofac for dependency injection.
Now, I did the dependency injection setup in order to achieve the following:
be able to inject dependencies into my hub
be able to get the context for my hubs in order to send to clients from outside the hub without using GlobalHost (which is no longer available in .NET 5, but also shouldn't be used since it's a static global object)
I hope you manage to setup your project (even though I don't think you will be able to keep DNX in your build options since Autofac doesn't have the library .NET 5 ready yet.
I hope this helps! Best of luck!
https://github.com/radu-matei/SignalRDependencyInjection
EDIT: If you want to use NInject (and build your own dependency resolver if you want to target DNX, you can follow this repository from the official guys from SignalR (actually from the guy who wrote SignalR):
https://github.com/DamianEdwards/NDCLondon2013/tree/master/DependencyInjection
In this demo they use NInject to create their own dependency resolver, so you shouldn't have any problems targeting DNX if you have NInject libraries.
UPDATE: After reading a little about Dependency Injection in ASP.NET 5, it seems that it is done in an unified manner. If you haven't had a look at this article, I recommend it, even though it doesn't specifically show SignalR DI.
I have simply made constructor with dependencies. For example, I need my IUnitOfWork instance (which was configured in startup) in hub. That is working code
[HubName("receipts")]
public class ReceiptsHub : Hub
{
public IUnitOfWork<string> UnitOfWork { get; set; }
public ReceiptsHub(IUnitOfWork<string> unitOfWork) : base()
{
UnitOfWork = unitOfWork;
}
public override Task OnConnected()
{
return base.OnConnected();
}
public override Task OnDisconnected(bool stopCalled)
{
return base.OnDisconnected(stopCalled);
}
}
In .NET 5 you can directly resolve the IServiceProvider and later you can have the required service. Please check the below codes:
public class MyHub : Hub
{
public IState State { get; set; }
private readonly IServiceProvider _serviceProvider;
public MyHub(IServiceProvider serviceProvider)
{
_serviceProvider=serviceProvider;
State = _serviceProvider.GetRequiredService<IState>();
}
public override Task OnConnected()
{
State.Clients = Clients;
State.Groups = Groups;
return base.OnConnected();
}
}