Use AsyncLocal to store request information? - c#

We are starting with ASP.NET Core 2. We need a way for each element that is involved in a request to write a message to a message handler.
Some limitations:
We won't use HttpContext.Items (HttpContext is not available in the class that we are using inside the Controller, and we don't like to forward the whole context there).
We tried to use it without dependency injection because if we have multiple different services, we will have too many parameters in the constructors.
Must also work with async/await.
We tried an approach using AsyncLocal<T>.
For that we created a class:
public class NotificationExecutionContext
{
private static readonly AsyncLocal<NotificationHandler> NotificationHandler =
new AsyncLocal<NotificationHandler>();
public static NotificationHandler Instance =>
NotificationHandler.Value ?? (NotificationHandler.Value = new NotificationHandler());
}
There will be a NotificationHandler created, which should live per-request. The NotificationHandler is a simple class where you can add/get messages to/from a collection:
public class NotificationHandler : INotificationHandler
{
public List<NotificationBase> Notifications { get; } = new List<NotificationBase>();
public void AddNotification(NotificationBase notification)
{
Notifications.Add(notification);
}
public void AddNotificationRange(List<NotificationBase> notifications)
{
Notifications.AddRange(notifications);
}
}
With this solution, I can easily get the NotificationHandler for this context and add a notification.
NotificationExecutionContext.Instance.AddNotification(new NotificationBase(){..})
Inside a middleware, we are waiting on the Response.OnStarting() event and then we take all messages from the NotificationHandler and add them the response header:
public async Task Invoke(HttpContext context)
{
var e = NotificationExecutionContext.Instance; // Required so that notification handler will be created in this context
context.Response.OnStarting((state) =>
{
List<NotificationBase> notifications = NotificationExecutionContext.Instance.Notifications;
if (notifications.Count > 0)
{
string messageString = JsonConvert.SerializeObject(notifications, Formatting.None);
context.Response.Headers.Add("NotificationHeader", messageString);
}
return Task.FromResult(0);
}, null);
await Next(context);
}
This code works, but are there pitfalls that we do not know? Or are there better solutions?

You should not use static singletons like that. Having static dependencies like that inside your code defeats the whole purpose of dependency injection. You should just embrace dependency injection here, which would make this super simple:
/* in Startup.ConfigureServices */
// register the notification handler as a scoped dependency, this automatically makes the
// instance shared per request but not outside of it
services.AddScoped<INotificationHandler, NotificationHandler>();
/* in Startup.Configure */
// register your custom middleware
app.Use<NotificationHandlerMiddleware>();
public class NotificationHandlerMiddleware
{
private readonly RequestDelegate _next;
private readonly NotificationHandler _notificationHandler;
public NotificationHandlerMiddleware(RequestDelegate next, INotificationHandler notificationHandler)
{
_next = next;
_notificationHandler = notificationHandler;
}
public void Invoke(HttpContext context)
{
// do whatever with _notificationHandler
await _next(context);
}
}
And that’s all. No need to introduce statics, but using full dependency injection making your code completely testable and all dependencies clear.
We tried to use it without dependency injection because if we have multiple different services we will have to many parameters in the constructors.
Too many constructor parameters is a clear sign for a violation of the single responsibility principle. If you find your services take many dependencies, you should consider splitting it up. You may also want to consider refactoring to facade services.

Related

Using user variables in dependency injection

I've got a scoped service, that needs to instantiate with user specific variables.
builder.Services.AddScoped<IUserService, UserService>();
UserService currently has a logger as it's constructor.
I'm currently doing the following through a factory, in a hacky way:
public class UserServiceFactory
{
private readonly ServiceProvider _sp;
private readonly DbContext _db;
public UserServiceFactory(ServiceProvider sp, DbContext db) { _sp = sp; _db = db; }
public async Task<IUserService> GetUserServiceForUserAsync(Guid userId)
{
var (apiKey, apiSecret) = await _db.FetchApiKeyAndSecretAsync(userId);
var userService = _sp.GetRequiredService<IUserService>();
userService.InitUser(apiKey, apiSecret);
return userService;
}
}
I'm running into the following problems:
I can't use builder.Services.AddScoped<IUserService, UserService>(); with string parameters, because as soon as it attempts to register in DI, it can't resolve the string parameters in the constructor, even though the only place I'm going to be initializing it will be in the factory, and I'll be providing said string parameters.
If I don't use builder.Services.AddScoped<IUserService, UserService>();, I'd need to use Activator.CreateInstance<UserService>(...), which ties a concrete implementation to this class which is not ideal. In addition, I can't track said UserService for disposal when the scope gets disposed.
It was suggested that I register a Func<> to return a user service. If I do this, I don't believe it will be a scoped service, and thus not be disposed of properly when the scope is destroyed.
The implementation of UserService is essentially an an HTTP Client, that will make requests with an apiKey and apiSecret of the IdentityUser. I'd like it to exist for the duration of the scope (In the case of asp.net core, the request, or in the case of being called from a Quartz job, the duration of the job), and then dispose afterwards.
UserService contains about 20 various methods, such as FetchAccountAsync, BuyItemAsync(itemId, quantity), SellItemAsync(itemId), which should make requests using the initialized httpclient. I'd like to avoid trying to initialize the apiKey/apiSecret in each method, because this will add a level of synchronization that I don't feel is needed. HttpClient is by default multithreaded, so my methods are fairly pain free:
Task BuyItemAsync(string itemId, int quantity)
{
var res = await _httpClient.GetAsync($"{_baseUrl}/buy?itemId={itemId}&qty={quantity}");
res.EnsureSuccessStatusCode();
}
How can I initialize my UserService with these apiKeys, apiSecrets, to be used in a scoped manner?
NOTE: Some of these details I've added based on your comments. Some of these may appear a little contrived because I don't know your full logic, context, or needs.
Design
I suggest the following
Remove the factory.
Go ahead and use builder.Services.AddScoped<IUserService, UserService>();.
Change the constructor of UserService to accept an ISecurityContext that will provide the API key and secret. This context will also be registered with AddScoped.
Have the UserService use the ISecurityContext at runtime, and remove any properties/parameters for API key and secret (if you had them).
Have the SecurityService use an IUserProvider at runtime, and remove any properties/parameters for user ID (if you had them).
This means no runtime data needs to be injected, no hacky method to expose, no factory, and no injecting the service provider.
At startup register the interfaces. It is important that they be scoped. They are going to share the lifetime, which will be short.
...
builder.Services.AddScoped<IUserService, UserService>();
builder.Services.AddScoped<ISecurityContext, SecurityContext>();
builder.Services.AddScoped<IUserProvider, UserProvider>();
Then implement the classes and a Result that can return all the contextual data.
public class Result
{
public Result(string apiKey, string apiSecret, Guid userId)
{
ApiKey = apiKey;
ApiSecret = apiSecret;
UserId = userId;
}
public string ApiKey { get; }
public string ApiSecret { get; }
public Guid UserId { get; }
}
public interface IUserProvider
{
Guid GetUserId();
}
public class UserProvider : IUserProvider
{
public async Task<Guid> GetUserId() => IdentityUser.GetUserId());
}
public interface ISecurityContext
{
Task<Result> GetApiKeyAndSecretAsync();
}
public class SecurityContext : ISecurityContext
{
private readonly DbContext _db;
private readonly IUserProvider userProvider;
// safe because this SecurityContext will be scoped!
private Result _result;
public SecurityContext(DbContext db, IUserProvider userProvider)
{
_db = db;
_userProvider = userProvider;
}
public async Task<Result> GetApiKeyAndSecretAsync()
{
if (_result != null) return _result;
var userId = _userProvider.GetUsedId();
var (apiKey, apiSecret) = await _db.FetchApiKeyAndSecretAsync(userId);
return _result = new Result(apiKey, apiSecret, userId);
}
}
public interface IUserService
{
Task DoWhatever();
}
public class UserService : IUserService
{
private readonly ISecurityContext _context;
public UserService(ISecurityContext context) => _context = context;
public async Task DoWhatever()
{
// use the security context
var result = await _context.GetApiKeyAndSecretAsync();
// use the result; e.g. pass the key/secret/user ID
// on to an HttpClient, RestClient, etc.
...
}
...
}
Usage
Using an IUserService means injecting that into your Quartz.NET job, a message handler, a web controller... wherever. In each case you may realize that one single implementation of any of these interfaces is not enough. That's OK. There are ways in dependency injection to fix that (e.g. named resolutions of multiple different concrete implementations), but I leave that to you.
Here's an example usage for a web controller.
public class MyController
{
private readonly IUserService _userService;
public MyController(IUserService userService, ...)
{
_userService = userService;
...
}
[HttpGet]
public async Task<IActionResult> GetStuff(...)
{
// gets the key and secret first time
await _userService.DoWhatever();
// uses cached versions of key, secret, guid across
// calls of _userService methods within scope
var someResult = await _userService.GetSomethingElse();
...
}
Commentary
This design has a few advantages
Security details are encapsulated behind an abstraction and not mixed into the UserService
The whole thing is more testable because the security details can be mocked when testing the UserService.
Key and secret are cached once within the scope and can be reused across methods in UserService that are invoked while in the same scope.
As #NKosi said in the comments, mixing runtime data at construction time is an anti-pattern. The link they referenced, Dependency Injection Code Smell: Injecting runtime data into components, is a good read and goes into more depth.
As you add more runtime data, you can expand the properties in Result and logic in SecurityContext or you can inject more context-like objects into UserService returning their own result-like instances.
There is a placeholder pattern that I have found useful here.
STARTUP CODE
Define dependencies in your application startup code, something like the following. Note that .NET does not allow you to run async processing in the factory method for IUserService:
app.UseMiddleware<DependencySetupMiddleware>();
services.AddSingleton(new MyDatabase());
services.AddScoped<UserServiceHolder>();
services.AddScoped<IUserService>(ctx =>
{
return ctx.GetRequiredService<UserServiceHolder>().UserService;
});
The holder class just looks like this:
public class UserServiceHolder {
public IUserService UserService { get; set; }
}
MIDDLEWARE CODE
The async processing can be done in a small middleware class. For the HTTP case you would do it like this, assuming that you get the User Id after authentication. Note that dependencies cannot be added to the .NET container at runtime, but you can update the holder object:
public class DependencySetupMiddleware
public DependencySetupMiddleware(RequestDelegate next) {
}
public async Task Invoke(HttpContext context, MyDatabase db) {
var userId = context.User.Claims.First(c => c.Type == "UserId")
var (apiKey, apiSecret) = await db.FetchApiKeyAndSecretAsync(userId);
var userService = new UserService(apiKey, apiSecret)
context.GetRequiredService<UserServiceHolder>().UserService = userService;
await next();
}
}
For Quartz you would have a similar middleware class - a Job Factory, which reads the job's user ID rather than using claims or the HTTP context.
BUSINESS LOGIC
With this code in place you can inject an IUserService into your business logic and forget about the holder class:
class MyController {
public MyController(IUserService userService) {
}
}
I think you might already have an answer here, but let me give you a working example. Here's my assumption:
I want to have an instance of a class that has all the things about the user available.
Here's the approach I used for PopForums.
Step 1: You're using some kind of built-in ASP.NET authentication, probably cookies or something external. I won't cover that here, because there are many ways to do it, but look at HttpContext.SignInAsync() for more. The important part is to use a name or identifier that will be put into the token it reads back in the next step.
Step 2: Use middleware to get your user and make it stick. You'll start with a ClaimsIdentity when you use HttpContext.AuthenticateAsync(schemeName). For example:
public async Task InvokeAsync(HttpContext context, IUserService userService)
{
var authResult = await context.AuthenticateAsync(schemeNameUsedFromSignIn);
var identity = authResult?.Principal?.Identity as ClaimsIdentity;
if (identity != null)
{
var user = userService.GetUserByName(identity.Name);
if (user != null)
{
// add claims to the identity if you want
// then stash your user object in the Items collection, which lasts the duration of the request
context.Items["TheUser"] = user;
context.User = new ClaimsPrincipal(identity);
}
}
await _next.Invoke(context);
Step 3: Enable getting the user anywhere you want by pulling it out of the context of the request, but isolate it to an interface so there are no hard dependencies. Example:
public interface IUserRetrievalShim
{
User GetUser();
}
public class UserRetrievalShim : IUserRetrievalShim
{
private readonly IHttpContextAccessor _httpContextAccessor;
public UserRetrievalShim(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
public User GetUser()
{
var user = _httpContextAccessor.HttpContext?.Items["TheUser"] as User;
return user;
}
}
What I like about this approach is that any classes up and down the dependency chain can be mocked out and unit tested without all of the HttpContext references. IHttpContextAccessor does a great job isolating it, and if it's not available, you'll get a null. And in this case, you're getting your user object, not the one tied to ASP.NET. You can still check HttpContext.User != null if you want, but this similarly will be null if there's no authenticated user. I only do the above with claims because maybe other app areas may want it.
Step 4: In your controllers, service classes or anything in between, inject IUserRetrievalShim and call its GetUser() method to get the user.
The bottom line here is that dependency injection is not the place to make the user stuff contextual. DI is purely setup and configuration, not run-time context. Use your UserService where ever you want, and combined with this shim, you can pass its ID or whatever to those service methods. You should not expect the service to be contextual out of the box by way of injection.
With that said, your User objects (not to be confused with HttpContext.User) can be composed of whatever you want, so long as you're OK with whatever the cost is to fetch that information and hydrate the object.

How to avoid using using BuildServiceProvider method at multiple places?

I have a legacy Asp.net Core 3.1 application which uses Kestrel server and all our GET and POST calls works fine. We have bunch of middlewares already on my legacy application and we use each of those middlewares for different purpose depending on what is the endpoint.
This is how our legacy application is setup as shown below. I have tried to keep things simple by keeping only important things.
Below is our BaseMiddleware class which is extended by bunch of other middlewares we have. Approx we have 10+ middlewares extending BaseMiddleware class -
BaseMiddleware.cs
public abstract class BaseMiddleware {
protected static ICatalogService catalogService;
protected static ICustomerService customerService;
private static IDictionary <string, Object> requiredServices;
private readonly RequestDelegate _next;
public abstract bool IsCorrectEndpoint(HttpContext context);
public abstract string GetEndpoint(HttpContext context);
public abstract Task HandleRequest(HttpContext context);
public BaseMiddleware(RequestDelegate next) {
var builder = new StringBuilder("");
var isMissingService = false;
foreach(var service in requiredServices) {
if (service.Value == null) {
isMissingService = true;
builder.Append(service.Key).Append(", ");
}
}
if (isMissingService) {
var errorMessage = builder.Append("cannot start server.").ToString();
throw new Exception(errorMessage);
}
_next = next;
}
public async Task Invoke(HttpContext context) {
if (IsCorrectEndpoint(context)) {
try {
await HandleRequest(context);
} catch (Exception ex) {
// handle exception here
return;
}
return;
}
await _next.Invoke(context);
}
public static void InitializeDependencies(IServiceProvider provider) {
requiredServices = new Dictionary<string, Object>();
var catalogServiceTask = Task.Run(() => provider.GetService<ICatalogService>());
var customerServiceTask = Task.Run(() => provider.GetService<ICustomerService>());
// .... few other services like above approx 10+ again
Task.WhenAll(catalogServiceTask, landingServiceTask, customerServiceTask).Wait();
requiredServices[nameof(catalogService)] = catalogService = catalogServiceTask.Result;
requiredServices[nameof(customerService)] = customerService = customerServiceTask.Result;
// ....
}
}
ICatalogService and ICustomerService are normal interfaces with some methods in them that their implementation class implements.
Below is one of our middlewares example that extend BaseMiddleware. All other middlewares follow same logic as below one -
FirstServiceMiddleware.cs
public class FirstServiceMiddleware : BaseMiddleware
{
public FirstServiceMiddleware(RequestDelegate next) : base(next) { }
public override bool IsCorrectEndpoint(HttpContext context)
{
return context.Request.Path.StartsWithSegments("/first");
}
public override string GetEndpoint(HttpContext context) => "/first";
public override async Task HandleRequest(HttpContext context)
{
context.Response.StatusCode = 200;
context.Response.ContentType = "application/json";
await context.Response.WriteAsync("Hello World!");
}
}
public static class FirstServiceMiddlewareExtension
{
public static IApplicationBuilder UseFirstService(this IApplicationBuilder builder)
{
return builder.UseMiddleware<FirstServiceMiddleware>();
}
}
Below is how my Startup class is configured -
Startup.cs
private static ILoggingService _loggingService;
public Startup(IHostingEnvironment env) {
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
.AddEnvironmentVariables();
Configuration = builder.Build();
}
public IConfigurationRoot Configuration { get; }
public void ConfigureServices(IServiceCollection services) {
services.AddResponseCompression(options =>
{
options.Providers.Add<GzipCompressionProvider>();
});
services.Configure<GzipCompressionProviderOptions>(options =>
{
options.Level = CompressionLevel.Fastest;
});
DependencyBootstrap.WireUpDependencies(services);
var provider = services.BuildServiceProvider();
if (_loggingService == null) _loggingService = provider.GetService<ILoggingService>();
//.. some other code here
BaseMiddleware.InitializeDependencies(provider);
}
public void Configure(IApplicationBuilder app, IHostApplicationLifetime lifetime) {
// old legacy middlewares
app.UseFirstService();
// .. few other middlewares here
}
And below is my DependencyBootstrap class -
DependencyBootstrap.cs
public static class DependencyBootstrap
{
//.. some constants here
public static void WireUpDependencies(IServiceCollection services)
{
ThreadPool.SetMinThreads(100, 100);
var provider = services.BuildServiceProvider();
var loggingService = provider.GetService<ILoggingService>();
// ... some other code here
try
{
WireUp(services, loggingService);
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
}
private static void WireUp(IServiceCollection services, ILoggingService loggingService)
{
// adding services here
services.AddSingleton<....>();
services.AddSingleton<....>();
//....
var localProvider = services.BuildServiceProvider();
if (IS_DEVELOPMENT)
{
processClient = null;
}
else
{
processClient = localProvider.GetService<IProcessClient>();
}
services.AddSingleton<IData, DataImpl>();
services.AddSingleton<ICatalogService, CatalogServiceImpl>();
services.AddSingleton<ICustomerService, CustomerServiceImpl>();
//.. some other services and singleton here
}
}
Problem Statement
I have recently started working with C# and asp.net core framework. I have done my reading and it looks like -
Our legacy application doesn't use Dependency Injection properly as we have lot of places using BuildServiceProvider method which causes that warning. I am not sure why we have to do it.
Also do we really need InitializeDependencies method in BaseMiddleware class? If not then how we can initialize dependencies properly? It looks like we are trying to initialize all the dependencies during server startup so that they all are ready when the call comes for any middleware. I'd like to keep this logic as it is if possible.
Currently I am confuse what is the best way to use DI in asp.net core and if my application is doing it wrong then how can I do it the right way? Above code works fine in our application from a very long time but looks like we might be using it totally wrong way of DI.
Calling BuildServiceProvider multiple times can cause serious trouble, because each call to BuildServiceProvider results in a new container instance with its own cache. This means that registrations that are expected to have the Singleton lifestyle, suddenly are created more than once. This is a problem called Ambiguous Lifestyle.
Some Singletons are stateless and for them there is no difference in creating one or a thousand. But other components that are registered as Singleton might have state, and the working of the application might (indirectly) depend on that state not being duplicated.
To make matters worse, while your application might work correctly today, this might change any time in the future when one of the third-party or framework components you depend on makes a change to one of their components in such way that it becomes a problem when that component is created multiple times.
In your example, you are resolving both ILoggingService and IProcessClient from a service provider. If the resolved components are stateless objects without stateful dependencies, there is no real harm done. But this might change when they become stateful. Again, this can happen by a change of one of its indirect dependencies, so this is something you might not be aware of. This can cause you or your team to waste many hours; such problem will likely not be easily found.
This means that the answer "simply" is to prevent calling BuildServiceProvider() to create intermediate container instances. But this might be easier said than done. In your case, however, you seem to require a dependency on ILoggerService before all dependencies are wired. A typical way to achieve this is to split the registration phase into two separate steps:
One step where you manually create those few singletons that you need before hand
Add them to your container builder (IServiceCollection)
Add all other registrations
For instance:
private ILoggingService _loggingService;
public Startup(Confiration config)
{
_loggingService = new MySpecialLoggingService(config.LogPath);
}
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton(_loggingService);
// More stuf here.
...
}
Advantage of this structure is that when a dependency is added to the constructor of this manually built MySpecialLoggingService, your code stops compiling and you're forced to look at this code. When that constructor depends on some other framework abstraction or application abstraction that isn't yet available, you know you're in trouble and need to rethink your design.
Final note, calling BuildServiceProvider multiple times isn't a bad thing per se. It is okay when you explicitly want to have multiple isolated modules in your application that each have their own state and run independently of one another. For instance when running multiple end points for multiple bounded contexts within the same process.
UPDATE
I think I am starting to understand what it is you are trying to achieve in your BaseMiddleware. It is a 'convenient' helper class that holds all the dependencies that its derivatives might need. This is probably an old design and you might be aware of this, but this base class is quite problematic. Base classes with dependencies are hardly ever a good idea, because they tend to become big, ever changing, and obfuscate the fact that their derivatives become too complex. In your case, even, you are using the Service Locator anti-pattern which is never a good idea.
Besides this, there is a lot going on in that BaseMiddleware class that—to me—makes little sense, such as:
It contains complex logic to verify whether all dependencies exist, while there are more effective ways to do so. The most effective way is to apply Constructor Injection because it will guarantee that its necessary dependencies are always available. On top of that, you can validate the IServiceCollection on build. This gives you even greater guarantees over the correctness of your DI configuration than your BaseMiddleware currently provides.
It resolves all its services in background threads, which implies that construction of those components is either heavy on CPU or I/O, which is a problem. Instead composition should be fast, because injection constructors should be simple, which allows you to compose object graph with confidence.
You do exception handling in the base class, while it is better suited to be applied at a higher level; for instance, using an outer-most piece of middleware. For the sake of simplicity, though, my next example keeps exception handling inside the base class. That's because I have no idea what kind of things you are doing in there, that could influence my answer.
As the base class is resolving from the root container, middleware classes are only able to make use of Singleton dependencies. Connecting to the database through Entity Framework, for instance, will be a problem, because DbContext classes should not be captured in Singleton consumers.
So, with the observations and advice above, I would suggest reducing the BaseMiddleware class to the following:
// Your middleware classes should implement IMiddleware; this allows middleware
// classes to be transient and have scoped dependencies.
public abstract class ImprovedBaseMiddleware : IMiddleware
{
public abstract bool IsCorrectEndpoint(HttpContext context);
public abstract string GetEndpoint(HttpContext context);
public abstract Task HandleRequest(HttpContext context);
public async Task InvokeAsync(HttpContext context, RequestDelegate next)
{
if (IsCorrectEndpoint(context)) {
try {
await HandleRequest(context);
}
catch (Exception ex) {
// handle exception here
return;
}
return;
}
await next(context);
}
}
Now based on this new base class, create your middleware implementations similar to this next example:
public class ImprovedFirstServiceMiddleware : ImprovedBaseMiddleware
{
private readonly ICatalogService _catalogService;
// Add all dependencies required by this middleware in the constructor.
public FirstServiceMiddleware(ICatalogService catalogService)
{
_catalogService = catalogService;
}
public override bool IsCorrectEndpoint(HttpContext context) =>
context.Request.Path.StartsWithSegments("/first");
public override string GetEndpoint(HttpContext context) => "/first";
public override async Task HandleRequest(HttpContext context)
{
context.Response.StatusCode = 200;
context.Response.ContentType = "application/json";
await context.Response.WriteAsync("Hello from "
+ _catalogService.SomeValue());
}
}
In your application, you can register your middleware classes as follows:
public void ConfigureServices(IServiceCollection services) {
// When middleware implements IMiddleware, it must be registered. But
// that's okay, because it allows the middleware with its
// dependencies to be 'verified on build'.
services.AddTransient<ImprovedFirstServiceMiddleware>();
// If you have many middleware classes, you can use
// Auto-Registration instead. e.g.:
var middlewareTypes =
from type in typeof(HomeController).Assembly.GetTypes()
where !type.IsAbstract && !type.IsGenericType
where typeof(IMiddleware).IsAssignableFrom(type)
select type;
foreach (var middlewareType in middlewareTypes)
services.AddTransient(middlewareType);
...
}
public void Configure(
IApplicationBuilder app, IHostApplicationLifetime lifetime)
{
// Add your middleware in the correct order as you did previously.
builder.UseMiddleware<ImprovedFirstServiceMiddleware>();
}
TIP: If you start to notice that a middleware classes get big constructors, that's likely because such class does too much and gets too complex. This means it should be refactored into multiple smaller classes. In that case, your class is exhibiting the Constructor Over-Injection code smell. There are many possible refactoring patterns and design patterns available that can get you out of this situation.

How to initialize scoped dependencies for consumers using MassTransit filters?

I would like to initialize some dependencies resolved from the MassTransit serviceProvider in the same way Asp.Net Core does with the pipeline's middlewares.
In particular I would like to inspect the incoming message before the consumer is called and extract the tenant from it (I'm currently working on a multitenant web application with single database per tenant).
With this informations I need to initialize some scoped instances (Ef Core DbContext for example).
I know that I can inject them in the Consumer through constructor but this means that I must do that everytime I write a new one, so I suppose that a filter should be the right place (correct me if I'm wrong).
The problem raises when I need to access the current consumer scope to resolve the dependencies that I need. I was thinking that the behavior of the MassTransit' pipeline was similar to the Asp.Net one regarding middleware injection but I was probably wrong.
I haven't found any documentation on how to do that clearly without cluttering the code of the filter, so any suggestion is going to be really appreciated.
This is the filter that I need to modify:
public class TenantContextInitializerFilter<T> : IFilter<T> where T : class, ConsumeContext
{
public void Probe(ProbeContext context) { }
public async Task Send(T context, IPipe<T> next)
{
//Resolve scoped instance here and do something before Consumer is called
var connectionStringProvider = scope.GetService<IConnectionStringProvider>();
await next.Send(context);
}
}
public class RegistrationsDeliveredEventConsumer : IConsumer<IRegistrationsDelivered>
{
private readonly IConnectionStringProvider _connectionStringProvider;
public RegistrationsDeliveredEventConsumer(IConnectionStringProvider connectionStringProvider)
{
//This should be the same instance that has been resolved in the filter' Send() method
_connectionStringProvider = connectionStringProvider;
}
public async Task Consume(ConsumeContext<IRegistrationsDelivered> context)
{
}
}
This is a simplified example of my code but this should be enough
There's two facets to consider: 1) are filters registered as services/pulled from the service collection when using the ASP.NET Core integration and 2) what lifetime do the filters have if they are. I'm not familiar with the MassTransit ASP.NET Core integration, but it looks like you should be good based on a cursory review. You'll need to confirm that both of those requirements are met.
For dependency injection, in general, constructor injection is the way to go unless there's a very specific need to do something different, which does not seem to be the case here. In short, you need a constructor for your filter.
What exactly you need to inject is a function of the lifetime of the filter. If it has a transient lifetime, then you can inject your scoped dependencies directly. If it has a singleton lifetime, then you'll need to inject IServiceProvider instead, and do the following whenever you need to use one of those dependencies:
using (var scope = _serviceProvider.CreateScope())
{
var dep = scope.ServiceProvider.GetRequiredService<MyDependency>();
// do something with `dep`
}
Here's a draft... I'm sure there are missing pieces, so let me know if you have questions.
public class TenantContextInitializerFilter<T> : IFilter<T> where T : class, ConsumeContext
{
private readonly Func<string, IDbConnection> _dbContextAccessor;
public void Probe(ProbeContext context) { }
public TenantContextInitializerFilter(Func<string, IDbConnection> dbContextAccessor)
{
_dbContextAccessor = dbContextAccessor;
}
public async Task Send(T context, IPipe<T> next)
{
var tenantId = ""; // place holder
using (var dbContext = _dbContextAccessor(tenantId))
{
//... do db logic
}
await next.Send(context);
}
}
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped<IConnectionStringProvider>(
provider => null /* TODO figure out how to fetch scoped instance from a cache or some storage mechanism*/);
services.AddScoped(provider =>
{
IDbConnection Accessor(string tenantId)
{
if (provider.GetService<IConnectionStringProvider>()
.TryGetConnectionString(tenantId, out var connectionString, out var providerName))
return new SqlConnection(connectionString);
throw new Exception();
}
return (Func<string, IDbConnection>)Accessor;
});
}
}

Change dependency resolution for specific scope only

I have one dependency registered as follows:
interface IDependency { }
class DependencyImpl : IDependency { }
Startup:
services.AddScoped<IDependency, DependencyImpl>();
This works as intendended as I do want to reuse the same instance in the scope of my Web API requests.
However, in one background service, I'd like to tell which instance it will resolve to:
class MyBackgroundService
{
private readonly IServiceScopeFactory _scopeFactory; // set in ctor
public void DoStuff()
{
var itens = GetItens();
var dependencyInstance = new DependencyImpl();
Parallel.ForEach(itens, (item) =>
{
using(var scope = _scopeFactory.CreateScope())
{
scope.SwapDependencyForThisScopeOnly<IDependency>( () => dependencyInstance ); // something like this
var someOtherService = scope.ServiceProvider.GetRequiredService<ItemService(); // resolve subsequent services with provided dependencyInstance
someOtherService.Process(item);
}
});
}
}
I can't reuse the same Scope because ItemService (and/or it's dependencies) uses other scoped services that can't be shared. Neither I want to replace dependency resolution for the entire application.
Is it possible to do what I want here? Does it make sense?
I'm using dotnet core 2.2 with default IoC container for that matters.
Edit in reply to #Steven: DependencyImpl contains configurations for how an item will be processed. One of those includes an relatively expensive query. DependencyImpl is also injected more than once in the graph. So, currently, it reads the configuration once, cache them in private properties, and use the cached version on subsequent reads. Because I know I'll be reusing the same configuration for all itens here, I'd like to avoid reading the configuration again for each parallel execution.
My real-world dependency is more similar to this:
interface IDependency
{
Task<Configuration> GetConfigurationAsync();
}
class DependencyImpl : IDependency
{
private readonly Configuration _configuration;
private readonly DbContext _dbContext;
ctor(DbContext dbContext)
{
_dbContext = dbContext;
}
public async Task<Configuration> GetConfigurationAsync()
{
if(_configuration is null)
{
// read configurations
}
return _configuration;
}
}
I understand that, as is, my class is not thread-safe. I'd have to force a read at the start and/or add some thread safety here.
Also, those processings used to happen during the lifetime of a web request, and the background service is the new stuff. I'd prefer to change as little of existing code as possible, because there are few tests in place, and of course time constraints from the powers-that-be.
In general, it is not a good idea to change the structure of the registered object graphs while the application is running. Not only is this hard to achieve with most containers, it is prone to suble problems that are hard to detect. I, therefore, suggest a small change in your design that change circumvents the problem you are facing.
Instead of trying to change the dependency as a whole, instead pre-populate an existing dependency with the data loaded on a a different thread.
This can be done using the following abstraction/implementation pair:
public interface IConfigurationProvider
{
Task<Configuration> GetConfigurationAsync();
}
public sealed class DatabaseConfigurationProvider : IConfigurationProvider
{
private readonly DbContext _dbContext;
public DatabaseConfigurationProvider(DbContext dbContext)
{
_dbContext = dbContext;
}
public Configuration Configuration { get; set; }
public async Task<Configuration> GetConfigurationAsync()
{
if (Configuration is null)
{
await // read configurations
}
return Configuration;
}
}
Notice the public Configuration on the DatabaseConfigurationProvider implementation, which is not on the IConfigurationProvider interface.
This is the core of the solution I'm presenting. Allow your Composition Root to set the value, without polluting your application abstractions, as application code doesn't need to overwrite the Configuration object; only the Composition Root needs to.
With this abstraction/implementation pair, the background service can look like this:
class MyBackgroundService
{
private readonly IServiceScopeFactory _scopeFactory; // set in ctor
public Task DoStuff()
{
var itens = GetItens();
// Create a scope for the root operation.
using (var scope = _scopeFactory.CreateScope())
{
// Resolve the IConfigurationProvider first to load
// the configuration once eagerly.
var provider = scope.ServiceProvider
.GetRequiredService<IConfigurationProvider>();
var configuration = await provider.GetConfigurationAsync();
Parallel.ForEach(itens, (item) => Process(configuration, item));
}
}
private void Process(Configuration configuration, Item item)
{
// Create a new scope per thread
using (var scope = _scopeFactory.CreateScope())
{
// Request the configuration implementation that allows
// setting the configuration.
var provider = scope.ServiceProvider
.GetRequiredService<DatabaseConfigurationProvider>();
// Set the configuration object for the duration of the scope
provider.Configuration = configuration;
// Resolve an object graph that depends on IConfigurationProvider.
var service = scope.ServiceProvider.GetRequiredService<ItemService>();
service.Process(item);
}
}
}
To pull this off, you need the following DI configuration:
services.AddScoped<DatabaseConfigurationProvider>();
services.AddScoped<IConfigurationProvider>(
p => p.GetRequiredService<DatabaseConfigurationProvider>());
This previous configuration registers DatabaseConfigurationProvider twice: once for its concrete type, once for its interface. The interface registration forwards the call and resolves the concrete type directly. This is a special 'trick' you have to apply when working with the MS.DI container, to prevent getting two separate DatabaseConfigurationProvider instances inside a single scope. That would completely defeat the correctness of this implementation.
Make an interface that extends IDependency and only applies to the faster implementation that you need to request, e.g., IFasterDependency. Then make a registration for IFasterDependency. That way your faster class is still an IDependency object and you won't disrupt too much existing code, but you can now request it freely.
public interface IDependency
{
// Actual, useful interface definition
}
public interface IFasterDependency : IDependency
{
// You don't actually have to define anything here
}
public class SlowClass : IDependency
{
}
// FasterClass is now a IDependencyObject, but has its own interface
// so you can register it in your dependency injection
public class FasterClass : IFasterDependency
{
}

How to specify the implementation you want to inject

I'm in the process of implementing a notification service. Essentially, customers can get notified in a number of ways, such as via email, text message, fax etc. Below is a rough implementation that is not wired together.
public class NotificationService
{
private readonly INotification _notification;
private readonly INotificationFormatter _formatter;
public NotificationService(
INotificationMethod notification,
INotificationFormatter formatter)
{
_notification = notification;
_formatter = formatter;
}
public void Notify(SomeParameterObject obj)
{
var formattedMessage = _formatter.Format(obj);
_notification.SendNotification(formattedMessage);
}
}
public interface INotificationFormatter
{
NotificationMessage Format(SomeParameterObject obj);
}
public interface INotification
{
void SendNotification();
}
public EmailNotification : INotification
{
public void SendNotification(NotificationMessage message)
{
// Use Exchange Web Services to send email
}
}
The NotificationService class essentially takes in a method of notification and a formatter. Obviously, each method of notification requires different formatting.
Based on business criteria, how do I select which implementation of INotification and NotificationFormatter I wish to use? Note that within the lifetime of the user using the application each notification will most likely be used. I say this because it's not as simple as instructing my container to inject implementation Foobar as it will change while the user is using the application.
I've thought of creating some sort of class that could handle pairs because it seems to makes sense to me that you wouldn't want use a text message notification formatter for a fax notification. However, I can't seem to wrap my head around a decent implementation of this.
I also own the book Dependency Injection in .NET by Mark Seemann. Did I perhaps miss something obvious?
Thank you.
How is it that you decide what kind of notification a user wants? If it can change while they're using your app, it seems like the NotificationService for that user msut be created anew for each notification you want to send them. That's ok - just use some sort of lookup to select a INotification impelmentation with an IoC container.
IoC's (I use AutoFac) let you use string-indexes to select a specific implementation. That string could come from a DB or whatever to represent the user's preference. Then you'd pass it to your IoC asking for an INotification 'decorated' with your string-choice. Upon startup, all the various implementations are registered with thier choice-strings.
I think you may be on to something with your 'pairs' comment - if INotificationFormat is closely tied to INotification and there is a possiblity of mixing them up then perhaps the INotification implementation itself should select its formatter.
What you need to do is to provide some kind of configuration infrastructure. For example, assuming that you want to keep the service just the way you've defined it, I would implement a factory returning an instance of NotificationService according to your model:
public struct NotificaitonSettings<T>
{
public Predicate<T> Predicate;
public NotificationService Service;
}
public class NotificationServiceFactory<T> : INotificationServiceFactory<T>
{
protected static List<NotificaitonSettings<T>> settings = new List<NotificaitonSettings<T>>();
static NotificationServiceFactory()
{
settings.Add(new NotificaitonSettings<T>
{
Predicate = m => !String.IsNullOrEmpty(m.Email),
Service = new NotificationService(new EmailNotification(), new EmailFormatter())
});
settings.Add(new NotificaitonSettings<T>
{
Predicate = m => !String.IsNullOrEmpty(m.Fax),
Service = new NotificationService(new FaxNotification(), new FaxFormatter())
});
}
public NotificationService Create(T model)
{
return settings.FirstOrDefault(s => s.Predicate(model)).Service;
}
}
This implementation configures the factory using static list, you could use a IoC container if it supports this kind of operations.

Categories