Is it safe to store an instance of HttpContext in a middleware?
Example:
public class TestMiddleware
{
private readonly RequestDelegate next;
private HttpContext context;
public TestMiddleware(RequestDelegate next)
{
this.next = next;
}
public async Task Invoke(HttpContext context)
{
try
{
this.context = context;
I would like to use it in other private methods to work on it, so I can either pass it around as parameter to those function or use it as shown in the example.
But is it thread safe?
But is it thread safe?
No it's not, because middleware are necessarily singletons. If you store a specific HttpContext in a shared field, it will be potentially reused during another request (which would be terrible).
Related
I'm developing .Net 6 API.
My project includes controllers, services and repositories (using dependency injection).
I also added a permission check via middleware:
Program.cs
app.UseMiddleware<AuthMiddleware>();
AuthMiddleware.cs
public class AuthMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger<EcmAuthMiddleware> _logger;
private readonly IConfiguration _config;
public AuthMiddleware(RequestDelegate next,
ILogger<AuthMiddleware> logger, IConfiguration config)
{
_next = next;
_logger = logger;
_config = config;
}
public async Task Invoke(HttpContext context, IUserApiService
userApiService)
{
...
context.Items["Instance"] = instance;
await _next(context);
}
}
From here I get the customer (and database) to run the APIs on.
Now I need to get some license information (via external API) from the newly obtained client and store it somewhere.
I tried invoking the call from the controller but would have to repeat it for almost all controllers. So I thought about transferring the call to middleware.
From the call I will have various information that I would like to store for use by the underlying levels: controllers, services and repositories. I'd rather not use the session or coookie.
Can I use only httpcontext or are there other solutions?
context.Items["LicenseInfo"] = licenseInfo;
This information is valid only for the call of an api then it should not be stored (eg Application).
EDIT:
GetLicenseInfo() must contains an external call:
string result = await _userApiService.GetUserApiResponseAsString("users/token", HttpMethod.Get, applicationId, token);
Can I use only httpcontext or are there other solutions?
There's nothing wrong with using HttpContext.Items for this. It's exactly what HttpContext.Items is for: attaching contextual data to an HTTP request. With this kind of "dictionary of objects" API, I do like to wrap my own APIs around it for type safety and simplicity:
public static class HttpContextLicenseInfoExtensions
{
public static void SetLicenceInfo(this HttpContext context, LicenseInfo licenseInfo) =>
context.Items[key] = licenseInfo;
public static LicenseInfo? TryGetLicenseInfo(this HttpContext context) =>
context.Items[key] as LicenseInfo;
public static LicenseInfo GetLicenseInfo(this HttpContext context) =>
context.TryGetLicenseInfo() ?? throw new InvalidOperationException("No license info.");
private static readonly string key = Guid.NewGuid().ToString("N");
}
// Example middleware
app.Use(async (context, next) =>
{
context.SetLicenseInfo(licenseInfo);
await next.Invoke();
});
// Example usage
var licenseInfo = HttpContext.GetLicenseInfo();
But if you really want to avoid HttpContext.Items, you can use AsyncLocal<T>. You just want to structure the API so that you set the value for a specific scope (I like to return IDisposable to un-set the value), and then you usually inject an "accessor" to read the current value. Something like this should work (using Disposable from my disposables library):
public static class AsyncLocalLicenseInfo
{
public static IDisposable Set(LicenseInfo licenseInfo)
{
var originalValue = local.Value;
local.Value = licenseInfo;
return new Disposable(() => local.Value = originalValue);
}
public static LicenseInfo? TryGet() => local.Value;
public static LicenseInfo LicenseInfo => TryGet() ?? throw new InvalidOperationException("No license info.");
private static readonly AsyncLocal<LicenseInfo> local = new();
}
// Example middleware
app.Use(async (context, next) =>
{
using var localValue = AsyncLocalLicenseInfo.Set(licenseInfo);
await next.Invoke();
});
// Example usage
var licenseInfo = AsyncLocalLicenseInfo.LicenseInfo;
If you don't like the static API, you can hide it behind an "accessor":
// Inject this into downstream types
public interface ILicenseInfoAccessor
{
LicenseInfo LicenseInfo { get; }
}
public sealed class LicenseInfoAccessor : ILicenseInfoAccessor
{
public LicenseInfo LicenseInfo => AsyncLocalLicenseInfo.LicenseInfo;
}
// Example usage
var licenseInfo = licenseInfoAccessor.LicenseInfo;
This is only an alternative to MiddleWare which might be a better option, might not, it depends on a lot of things (as in most software dev questions).
This could be the factory:
public class LicenseInfoFactory
{
public LicenseInfoFactory(IHttpContextAccessor context)
{
_context = context;
}
private readonly IHttpContextAccessor _context;
public LicenseInfo Build()
{
_context...
// api call to get/build/return LicenseInfo
}
}
And then in your startup:
services.AddHttpContextAccessor();
services.AddSingleton<LicenseInfoFactory>();
services.AddScoped<LicenseInfo>(provider => {
var factory = provider.GetRequiredService<LicenseInfoFactory>();
return factory.Build();
});
Then just inject LicenseInfo in your controllers/repositories constructors etc:
public MyController(LicenseInfo licenseInfo)
This should only make one api call per scoped request. (this is from memory, syntax might not be exact)
I'm developing a middleware which I would like to have an optional dependency on a internal logging library. In another words, if MyLoggingService is registered, great!, else, life goes on and ill log to console.
But by declaring public async Task Invoke(HttpContext httpContext, MyLoggingService logger), I get a runtime error saying that it was not registred. I tried setting a default value to null but that didn't work. Also, because its a middleware, I can't overload the Invoke method.
Is there a solution other than requesting the service collection and resolving the dependency myself?
The answer is incredibly simple:
public async Task Invoke(HttpContext httpContext, MyLoggingService logger = null)
Instead of making dependencies optional, consider:
Programming to an abstraction, e.g. IMyLoggingService
Register a Null Object implementation
For instance:
public class CustomMiddleware1 : IMiddleware
{
private readonly IMyLoggingService logger;
public CustomMiddleware1(IMyLoggingService logger) => this.logger = logger;
public async Task InvokeAsync(HttpContext context, RequestDelegate next)
{
this.logger.Log("Before");
await next(context);
this.logger.Log("After");
}
}
Null Object implementation:
public sealed class NullMyLoggingService : IMyLoggingService
{
public void Log(LogEntry e) { }
}
Registrations:
services.AddSingleton<IMyLoggingService>(new NullMyLoggingService());
app.Use<CustomMiddleware1>();
The call to AddSingleton<IMyLoggingService>(new NullMyLoggingService()) ensures a registration for IMyLoggingService always exists. This prevents complexity in consumers, who would otherwise have to add conditional logic for the case that the logger isn't present.
This null implementation can be replaced by simply adding a second IMyLoggingService after the first:
services.AddScoped<IMyLoggingService, DbMyLoggingService>();
app.Use<CustomMiddleware1>();
Why is it recommended for a middleware to be async in ASP.NET Core?
E.g. in this tutorial it is recommended to make the middleware custom and I can not understand the reason behind it.
public class MyMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger _logger;
public MyMiddleware(RequestDelegate next, ILoggerFactory logFactory)
{
_next = next;
_logger = logFactory.CreateLogger("MyMiddleware");
}
public async Task Invoke(HttpContext httpContext)
{
_logger.LogInformation("MyMiddleware executing..");
await _next(httpContext); // calling next middleware
}
}
// Extension method used to add the middleware to the HTTP request pipeline.
public static class MyMiddlewareExtensions
{
public static IApplicationBuilder UseMyMiddleware(this IApplicationBuilder builder)
{
return builder.UseMiddleware<MyMiddleware>();
}
}
According to the documentation, that is by design
The middleware class must include:
A public constructor with a parameter of type RequestDelegate.
A public method named Invoke or InvokeAsync. This method must:
Return a Task.
Accept a first parameter of type HttpContext.
Reference Write custom ASP.NET Core middleware
My understanding is that the pipeline has been designed to be async by default.
RequestDelegate which is the core of asp.net core's pipe line requires a Task in order to allow a high-performance, and modular HTTP request pipeline.
public delegate System.Threading.Tasks.Task RequestDelegate(HttpContext context);
From Comments: credit to #ScottChamberlain
The reason is that with how async was built in to asp.net core it allows for more throughput of web requests for the same hardware when comparing to a non async version.
I am using ASP.NET Core Web API, where I have Multiple independent web api projects. Before executing any of the controllers' actions, I have to check if the the logged in user is already impersonating other user (which i can get from DB) and can pass the impersonated user Id to the actions.
Since this is a piece of code that gonna be reused, I thought I can use a middleware so:
I can get the initial user login from request header
Get the impesonated User Id if any
Inject that ID in the request pipeline to make it available to the api being called
public class GetImpersonatorMiddleware
{
private readonly RequestDelegate _next;
private IImpersonatorRepo _repo { get; set; }
public GetImpersonatorMiddleware(RequestDelegate next, IImpersonatorRepo imperRepo)
{
_next = next;
_repo = imperRepo;
}
public async Task Invoke(HttpContext context)
{
//get user id from identity Token
var userId = 1;
int impersonatedUserID = _repo.GetImpesonator(userId);
//how to pass the impersonatedUserID so it can be picked up from controllers
if (impersonatedUserID > 0 )
context.Request.Headers.Add("impers_id", impersonatedUserID.ToString());
await _next.Invoke(context);
}
}
I found this Question, but that didn't address what I am looking for.
How can I pass a parameter and make it available in the request pipeline? Is it Ok to pass it in the header or there is more elegant way to do this?
You can use HttpContext.Items to pass arbitrary values inside the pipeline:
context.Items["some"] = "value";
A better solution would be to use a scoped service. Take a look at this: Per-request middleware dependencies
Your code should look like:
public class MyMiddleware
{
private readonly RequestDelegate _next;
public MyMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext httpContext, IImpersonatorRepo imperRepo)
{
imperRepo.MyProperty = 1000;
await _next(httpContext);
}
}
And then register your ImpersonatorRepo as:
services.AddScoped<IImpersonatorRepo, ImpersonatorRepo>()
I wrote my custom middleware which I add in
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
//...
app.UseAutologin();
app.UseMvc(routes =>
{
//...
So it is the last middleware before the Mvc comes into play.
In my middleware's Invoke method I want to (indirectly) access the DbContext.
public async Task Invoke(HttpContext context)
{
if (string.IsNullOrEmpty(context.User.Identity.Name))
{
var applicationContext = _serviceProvider.GetService<ApplicationDbContext>();
var signInManager = _serviceProvider.GetService<SignInManager<ApplicationUser>>();
var result = await signInManager.PasswordSignInAsync(_options.UserName, _options.Password, true, false);
}
await _next(context);
}
Nearly every time I get the following exception:
InvalidOperationException: An attempt was made to use the context
while it is being configured. A DbContext instance cannot be used
inside OnConfiguring since it is still being configured at this point.
Now this is clearly raised by the PasswordSignInAsync method. But how can I ensure that the model was created before doing such things?
Maybe I was not entirely clear: I don't want to use the DbContext myself - the PasswordSignInAsync uses it when verifying the user and password.
What if you inject the ApplicationDbContext and SignInManager<ApplicationUser> through the Invoke method:
public async Task Invoke(HttpContext context, ApplicationDbContext applicationContext, SignInManager<ApplicationUser> signInManager)
{
if (string.IsNullOrEmpty(context.User.Identity.Name))
{
var result = await signInManager.PasswordSignInAsync(_options.UserName, _options.Password, true, false);
}
await _next(context);
}
This way you the services are resolved from the correct scope. I notice you don't actually use the ApplicationDbContext anywhere, just the SignInManager. Do you really need it?
This error is likely occurring because any middleware acts as a singleton. You have to avoid using member variables in your middleware. Feel free to inject into the Task Invoke, but don't store the inject value into a member object.
See: Saving HttpContext Instance in Middleware,
Calling services in Middleware
I was able to get around this myself, by creating a class that I could then pass into other methods in my middleware:
public async Task Invoke(HttpContext context, IMetaService metaService)
{
var middler = new Middler
{
Context = context,
MetaService = metaService
};
DoSomething(middler);
}
Just by this:-
public async Task Invoke(HttpContext context)
{
var dbContext = context.RequestServices.GetRequiredService<ClinicDbContext>();
await _next(context);
}
This is the simple solution that works great for my use case.
I created a simple method I can call from anywhere in the application to easily get the database context:
public class UtilsApp
{
public static MyDbContext GetDbContext()
{
DbContextOptionsBuilder<MyDbContext> opts =
new DbContextOptionsBuilder<MyDbContext();
optionsBuilder.UseSqlServer(Program.MyDbConnectionString); // see connection string below
return new MyDbContext(opts.Options);
}
}
Then, to use it anywhere in the application:
MyDbContext dbContext = UtilsApp.GetDbContext();
I set Program.MyDbConnectionString (a public static string field) from within Startup.ConfigureServices() (which is a callback that is called from within Program.Main() via CreateHostBuilder(args).Build()). That way I can use that connection string anywhere in the application without having to repeatedly retrieve it from appsettings.json or an environment variable.