Accessing DbContext in Middleware in ASP.NET 5 - c#

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.

Related

asp.net core (3+) shared context on CreateScope()

When I try to run a background task, I always create a new scope inside that task. With the update to 3+, it seems that within the new create scope, there is a reference to the original request. The following code would break on the Debugger.Break() statement:
public class TestController : Controller
{
public readonly IServiceScopeFactory ServiceScopeFactory;
public TestController(
IServiceScopeFactory serviceScopeFactory)
{
this.ServiceScopeFactory = serviceScopeFactory;
}
// GET
public IActionResult Index()
{
Task.Run(() =>
{
using (var scope = ServiceScopeFactory.CreateScope())
{
var actionContextAccessor = scope.ServiceProvider.GetService<IActionContextAccessor>();
var actionContext = actionContextAccessor.ActionContext;
if (actionContext.ActionDescriptor != null)
Debugger.Break();
}
});
return Content("Test");
}
}
The startup looks like this:
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddSingleton<IActionContextAccessor, ActionContextAccessor>();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
}
}
The problem is that httpContext is shared with the new create scope. When one of the scopes is being disposed of, it affects the other scope. For example with IUrlHelper, which results in an "IFeatureCollection has been disposed of".
For test sake, I added a test if the httpContext would be the same. And it seems it is!
public IActionResult Index()
{
// Just for testing
var originalContext = this.HttpContext;
Task.Run(() =>
{
using (var scope = ServiceScopeFactory.CreateScope())
{
// Make sure the original request was disposed
Thread.Sleep(1000);
var actionContextAccessor = scope.ServiceProvider.GetService<IActionContextAccessor>();
var actionContext = actionContextAccessor.ActionContext;
if (originalContext == actionContext.HttpContext)
Debugger.Break();
}
});
return Content("Test");
}
For me, this seems like odd behaviour, cause I would except the new scope not to have the same httpContext. It should be a NEW scope. Should the scope be created in another way?
Found solution
In my production code I use a transient ActionContext scope, which attempt to detect if it's dealing with a request, or a background scope as followed:
services.AddSingleton<IActionContextAccessor, ActionContextAccessor>()
.AddTransient<ActionContext>((s) => {
var actionContextAccessor = serviceProvider.GetRequiredService<IActionContextAccessor>();
var actionContext = actionContextAccessor?.ActionContext;
// Create custom actioncontext
if (actionContext == null) {
// create a manual actionContext
}
return actionContext;
});
This doesn't seem to work anymore. The solution seems to be too validate if the httpContext exist through the IHttpContextAccessor:
services.AddSingleton<IActionContextAccessor, ActionContextAccessor>()
.AddTransient<ActionContext>((s) => {
var currentContextAccess = serviceProvider.GetService<IHttpContextAccessor>();
if (currentContextAccess.HttpContext == null) {
// create a manual actionContext
...
return actionContext;
}
var actionContextAccessor = serviceProvider.GetRequiredService<IActionContextAccessor>();
return actionContextAccessor.ActionContext;
});
For me, this seems like odd behaviour, cause I would except the scope not to be. Should the scope be created in another way?
Why odd? IActionContextAccessor is a singleton (same as IHttpContextAccessor is), so its normal to return the same instance even inside a newly created scope.
Since you are not awaiting, the Task.Run, your request will finish before the task is finished. How do you want to access the HttpContext after the request is done? It's only valid during the request. You have to get all the required data prior to spinning up the new Task and pass the values you need to the background task.
HttpContext is only valid for the duration of the request and since you dont await it, request ends early.
And what your code does is undefined behavior, see David's Guidelines
Do not access the HttpContext from multiple threads in parallel. It is not thread safe.
Do not use the HttpContext after the request is complete

Cannot access a disposed object in ASP.NET Core when injecting DbContext

On an ASP.NET Core project I have the following on Startup:
services.AddDbContext<Context>(x => x.UseSqlServer(connectionString));
services.AddTransient<IValidationService, ValidationService>();
services.AddTransient<IValidator<Model>, ModelValidator>();
The ValidationService is as follows:
public interface IValidationService {
Task<List<Error>> ValidateAsync<T>(T model);
}
public class ValidationService : IValidationService {
private readonly IServiceProvider _provider;
public ValidationService(IServiceProvider provider) {
_provider = provider;
}
public async Task<List<Error>> ValidateAsync<T>(T model) {
IValidator<T> validator = _provider.GetRequiredService<IValidator<T>>();
return await validator.ValidateAsync(model);
}
}
And the ModelValidator is as follows:
public class ModelValidator : AbstractValidator<Model> {
public ModelValidator(Context context) {
// Some code using context
}
}
When I inject a IValidationService in a controller and use it as:
List<Error> errors = await _validator.ValidateAsync(order);
I get the error:
System.ObjectDisposedException: Cannot access a disposed object. A
common cause of this error is disposing a context that was resolved
from dependency injection and then later trying to use the same
context instance elsewhere in your application. This may occur is you
are calling Dispose() on the context, or wrapping the context in a
using statement. If you are using dependency injection, you should
let the dependency injection container take care of disposing context
instances. Object name: 'Context'.
Any idea why I am having this error when using Context inside ModelValidator.
How to fix this?
UPDATE
So I changed the code to:
services.AddScoped<IValidationService, ValidationService>();
services.AddScoped<IValidator<Model>, ModelValidator>();
But I get the same error ...
UPDATE - Seed Data Code inside Configure method on Startup
So on Configure method I have:
if (hostingEnvironment.IsDevelopment())
applicationBuilder.SeedData();
And the SeedData extension is:
public static class DataSeedExtensions {
private static IServiceProvider _provider;
public static void SeedData(this IApplicationBuilder builder) {
_provider = builder.ApplicationServices;
_type = type;
using (Context context = (Context)_provider.GetService<Context>()) {
await context.Database.MigrateAsync();
// Insert data code
}
}
What am I missing?
UPDATE - A possible solution
Changing my Seed method to the following seems to work:
using (IServiceScope scope =
_provider.GetRequiredService<IServiceScopeFactory>().CreateScope()) {
Context context = _provider.GetService<Context>();
// Insert data in database
}
Just a guess in what causes your error:
You are using DI and async calls. If, somewhere in your call stack, you return a void instead of Task, you get the described behavior. At that point, the call is ended and the context disposed. So check if you have an async call that returns a void instead of Task. If you change the return value, the ObjectDisposedException is probably fixed.
public static class DataSeedExtensions {
private static IServiceProvider _provider;
public static async Task SeedData(this IApplicationBuilder builder) { //This line of code
_provider = builder.ApplicationServices;
_type = type;
using (Context context = (Context)_provider.GetService<Context>()) {
await context.Database.MigrateAsync();
// Insert data code
}
}
And in configure:
if (hostingEnvironment.IsDevelopment()){
await applicationBuilder.SeedData();
}
Blog post on how to fix this error: Cannot access a disposed object in ASP.NET Core when injecting DbContext
I had a similar issue working with asp.net core. I have an async POST method in my controller and when it returns void I will have this exception. After I changed the POST method return a TASK the problem was solved.
Change from:
public async void PostAsync([FromBody] Model yourmodel)
To
public async Task PostAsync([FromBody] Model yourmodel)
Update for ASP.NET Core 2.1
In ASP.NET Core 2.1 the methods changed slightly. The general method is similar to the 2.0, just the methods name and return types have been changed.
public static void Main(string[] args)
{
CreateWebHostBuilder(args)
.Build()
.Seed();
}
public static IWebHostBuilder CreateWebHostBuilder(string[] args)
{
return new WebHostBuilder()
...; // Do not call .Build() here
}
Applies for ASP.NET Core 2.0
With ASP.NET Core 2.0 there have been some changes in how EF Core tools (dotnet ef migrations etc.) determine the DbContext and connection string at design time.
The below answer leads that the migrations and seeding are applied when calling any of the dotnet ef xxx commands.
The new pattern for getting a design time instance for the EF Core tools is by using an BuildHostWeb static method.
As per this announcement, EF Core will now use the static BuildWebHost method which configures the whole application, but doesn't run it.
public class Program
{
public static void Main(string[] args)
{
var host = BuildWebHost(args);
host.Run();
}
// Tools will use this to get application services
public static IWebHost BuildWebHost(string[] args) =>
new WebHostBuilder()
.UseKestrel()
.UseContentRoot(Directory.GetCurrentDirectory())
.UseIISIntegration()
.UseStartup<Startup>()
.Build();
}
Replace this in your old Main method
public static void Main(string[] args)
{
var host = BuildWebHost(args)
.Seed();
host.Run();
}
Where Seed is an extension method:
public static IWebHost Seed(this IWebHost webhost)
{
using (var scope = webhost.Services.GetService<IServiceScopeFactory>().CreateScope())
{
// alternatively resolve UserManager instead and pass that if only think you want to seed are the users
using (var dbContext = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>())
{
SeedData.SeedAsync(dbContext).GetAwaiter().GetResult();
}
}
}
public static class SeedData
{
public static async Task SeedAsync(ApplicationDbContext dbContext)
{
dbContext.Users.Add(new User { Id = 1, Username = "admin", PasswordHash = ... });
}
}
Old Answer, still applies to ASP.NET Core 1.x
There is a semi-official pattern on how to seed Entity Framework Core in ASP.NET Core application you should apply, because during application startup there is no Request and hence no RequestServices (which resolves scoped services).
In essence it boils down to creating a new scope, resolve the types you need and dispose the scope again once you're finished.
// serviceProvider is app.ApplicationServices from Configure(IApplicationBuilder app) method
using (var serviceScope = serviceProvider.GetRequiredService<IServiceScopeFactory>().CreateScope())
{
var db = serviceScope.ServiceProvider.GetService<AppDbContext>();
if (await db.Database.EnsureCreatedAsync())
{
await SeedDatabase(db);
}
}
One of the reasons directly resolving a service via app.ApplicationServices.GetService<MyService>() is that ApplicationServices is the application (or lifetime) scope provider and the services resolved here stay alive until the application is shut down.
Usually the scoped container will resolve from it's parent container, if the object already exists there. So if you instantiate the DbContext this way in the application, it will be available in ApplicationServices container and when a request happens, a child container will be created.
Now when resolving the DbContext it won't be resolved as scoped, because it already exists in the parent container, so the instance of the parent container will be returned instead. But since it has been disposed during the seeding, it won't be accessible.
A scope container is nothing else then a singleton container with limited lifetime.
So never resolve scoped services in Application startup w/o using the pattern above of first creating a scope and resolving from it.
If you are using any async void please replace it with async Task
Had the same issue. Hope this helps someone. In addition to making the method async and return a Task, you need to make sure that the method will also be awaited wherever you are calling it.
the problem is that DBContext is scoped per request by default, but you have things that depend on it scoped as transient, so they do not have the same scope and DBContext may be disposed before you are done using it
Similar to Yang Zhang, I had to change my controller function
From:
public IActionResult MyFunc([FromBody]string apiKey)
To:
public async Task<IActionResult> MyFunc([FromBody]string apiKey)
I'd like to share my solution for those who are trying to start a background task in their controllers. That means you want to start a task and don't want to wait for the result like audit logging to database. If you are creating a task and try to do database operations in that task you will receive this error;
Cannot access a disposed object. A common cause of this error is disposing a context that was resolved from dependency injection and then later trying to use the same context instance elsewhere in your application. This may occur if you are calling Dispose() on the context, or wrapping the context in a using statement. If you are using dependency injection, you should let the dependency injection container take care of disposing context instances.\r\nObject name: 'DBContext'.
Already explained in details. Find it here
In my case, it wasn't an Async problem, but the code had a
using (DataContext dc=dataContext) {}
block, and of course, the context was disposed after that.
In my case the controller method was async and it was returning a task but inside that I had 2 await calls. First await calls gets some data from a service and second await call writes to the DB using EF. I had to remove the await from this second call and only then it worked. I didn't remove async/await from method signatures. I just called the second method without await.
I was facing a similar error and later was able to resolve it.
I was calling the async method without using await.
old code
var newUser = _repo.Register(newUserToCreate);
with the fix made
var newUser = await _repo.Register(newUserToCreate);

How to get HttpContext.Current in ASP.NET Core? [duplicate]

This question already has answers here:
Access the current HttpContext in ASP.NET Core
(7 answers)
Closed 6 years ago.
We are currently rewriting/converting our ASP.NET WebForms application using ASP.NET Core. Trying to avoid re-engineering as much as possible.
There is a section where we use HttpContext in a class library to check the current state. How can I access HttpContext.Current in .NET Core 1.0?
var current = HttpContext.Current;
if (current == null)
{
// do something here
// string connection = Configuration.GetConnectionString("MyDb");
}
I need to access this in order to construct current application host.
$"{current.Request.Url.Scheme}://{current.Request.Url.Host}{(current.Request.Url.Port == 80 ? "" : ":" + current.Request.Url.Port)}";
As a general rule, converting a Web Forms or MVC5 application to ASP.NET Core will require a significant amount of refactoring.
HttpContext.Current was removed in ASP.NET Core. Accessing the current HTTP context from a separate class library is the type of messy architecture that ASP.NET Core tries to avoid. There are a few ways to re-architect this in ASP.NET Core.
HttpContext property
You can access the current HTTP context via the HttpContext property on any controller. The closest thing to your original code sample would be to pass HttpContext into the method you are calling:
public class HomeController : Controller
{
public IActionResult Index()
{
MyMethod(HttpContext);
// Other code
}
}
public void MyMethod(Microsoft.AspNetCore.Http.HttpContext context)
{
var host = $"{context.Request.Scheme}://{context.Request.Host}";
// Other code
}
HttpContext parameter in middleware
If you're writing custom middleware for the ASP.NET Core pipeline, the current request's HttpContext is passed into your Invoke method automatically:
public Task Invoke(HttpContext context)
{
// Do something with the current HTTP context...
}
HTTP context accessor
Finally, you can use the IHttpContextAccessor helper service to get the HTTP context in any class that is managed by the ASP.NET Core dependency injection system. This is useful when you have a common service that is used by your controllers.
Request this interface in your constructor:
public MyMiddleware(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
You can then access the current HTTP context in a safe way:
var context = _httpContextAccessor.HttpContext;
// Do something with the current HTTP context...
IHttpContextAccessor isn't always added to the service container by default, so register it in ConfigureServices just to be safe:
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpContextAccessor();
// if < .NET Core 2.2 use this
//services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
// Other code...
}
Necromancing.
YES YOU CAN, and this is how.
A secret tip for those migrating large junks chunks of code:
The following method is an evil carbuncle of a hack which is actively engaged in carrying out the express work of satan (in the eyes of .NET Core framework developers), but it works:
In public class Startup
add a property
public IConfigurationRoot Configuration { get; }
And then add a singleton IHttpContextAccessor to DI in ConfigureServices.
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<Microsoft.AspNetCore.Http.IHttpContextAccessor, Microsoft.AspNetCore.Http.HttpContextAccessor>();
Then in Configure
public void Configure(
IApplicationBuilder app
,IHostingEnvironment env
,ILoggerFactory loggerFactory
)
{
add the DI Parameter IServiceProvider svp, so the method looks like:
public void Configure(
IApplicationBuilder app
,IHostingEnvironment env
,ILoggerFactory loggerFactory
,IServiceProvider svp)
{
Next, create a replacement class for System.Web:
namespace System.Web
{
namespace Hosting
{
public static class HostingEnvironment
{
public static bool m_IsHosted;
static HostingEnvironment()
{
m_IsHosted = false;
}
public static bool IsHosted
{
get
{
return m_IsHosted;
}
}
}
}
public static class HttpContext
{
public static IServiceProvider ServiceProvider;
static HttpContext()
{ }
public static Microsoft.AspNetCore.Http.HttpContext Current
{
get
{
// var factory2 = ServiceProvider.GetService<Microsoft.AspNetCore.Http.IHttpContextAccessor>();
object factory = ServiceProvider.GetService(typeof(Microsoft.AspNetCore.Http.IHttpContextAccessor));
// Microsoft.AspNetCore.Http.HttpContextAccessor fac =(Microsoft.AspNetCore.Http.HttpContextAccessor)factory;
Microsoft.AspNetCore.Http.HttpContext context = ((Microsoft.AspNetCore.Http.HttpContextAccessor)factory).HttpContext;
// context.Response.WriteAsync("Test");
return context;
}
}
} // End Class HttpContext
}
Now in Configure, where you added the IServiceProvider svp, save this service provider into the static variable "ServiceProvider" in the just created dummy class System.Web.HttpContext (System.Web.HttpContext.ServiceProvider)
and set HostingEnvironment.IsHosted to true
System.Web.Hosting.HostingEnvironment.m_IsHosted = true;
this is essentially what System.Web did, just that you never saw it (I guess the variable was declared as internal instead of public).
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory, IServiceProvider svp)
{
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
loggerFactory.AddDebug();
ServiceProvider = svp;
System.Web.HttpContext.ServiceProvider = svp;
System.Web.Hosting.HostingEnvironment.m_IsHosted = true;
app.UseCookieAuthentication(new CookieAuthenticationOptions()
{
AuthenticationScheme = "MyCookieMiddlewareInstance",
LoginPath = new Microsoft.AspNetCore.Http.PathString("/Account/Unauthorized/"),
AccessDeniedPath = new Microsoft.AspNetCore.Http.PathString("/Account/Forbidden/"),
AutomaticAuthenticate = true,
AutomaticChallenge = true,
CookieSecure = Microsoft.AspNetCore.Http.CookieSecurePolicy.SameAsRequest
, CookieHttpOnly=false
});
Like in ASP.NET Web-Forms, you'll get a NullReference when you're trying to access a HttpContext when there is none, such as it used to be in Application_Start in global.asax.
I stress again, this only works if you actually added
services.AddSingleton<Microsoft.AspNetCore.Http.IHttpContextAccessor, Microsoft.AspNetCore.Http.HttpContextAccessor>();
like I wrote you should.
Welcome to the ServiceLocator pattern within the DI pattern ;)
For risks and side effects, ask your resident doctor or pharmacist - or study the sources of .NET Core at github.com/aspnet, and do some testing.
Perhaps a more maintainable method would be adding this helper class
namespace System.Web
{
public static class HttpContext
{
private static Microsoft.AspNetCore.Http.IHttpContextAccessor m_httpContextAccessor;
public static void Configure(Microsoft.AspNetCore.Http.IHttpContextAccessor httpContextAccessor)
{
m_httpContextAccessor = httpContextAccessor;
}
public static Microsoft.AspNetCore.Http.HttpContext Current
{
get
{
return m_httpContextAccessor.HttpContext;
}
}
}
}
And then calling HttpContext.Configure in Startup->Configure
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory, IServiceProvider svp)
{
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
loggerFactory.AddDebug();
System.Web.HttpContext.Configure(app.ApplicationServices.
GetRequiredService<Microsoft.AspNetCore.Http.IHttpContextAccessor>()
);
There is a solution to this if you really need a static access to the current context.
In Startup.Configure(….)
app.Use(async (httpContext, next) =>
{
CallContext.LogicalSetData("CurrentContextKey", httpContext);
try
{
await next();
}
finally
{
CallContext.FreeNamedDataSlot("CurrentContextKey");
}
});
And when you need it you can get it with :
HttpContext context = CallContext.LogicalGetData("CurrentContextKey") as HttpContext;
I hope that helps. Keep in mind this workaround is when you don’t have a choice. The best practice is to use de dependency injection.

Scoped service in controller is different from the service called in the middleware

I have a custom Authentication Middelware which uses my custom service injected in the constructor.
In MyAuthenticationHandler I am calling a method of MyService which sets a property value.
_myService.SetCompany(company);
company is loaded in the authentication handler and is not null. However when I try to access the value from the controller I find that MyService has been reinitialized.
This is how it's set in Startup.cs
services.AddScoped<IMyService, MyFactory>();
Middleware is only initialized once, when you register. You need to resolve your dependency in the Invoke method.
public class MyMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger _logger;
public MyMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context)
{
var service = context.RequestServices.GetService<IMyService>();
service.SetCompany("My Company");
await _next.Invoke(context);
}
}
Now the service is properly resolved per request, rather than per application life time.
Edit:
i.e. in order to have your middleware be called after the authorization middelware is called you'd do something like this in your Configure(IAppBuilder app) method:
app.UseCookieAuthentication(options => { ... });
app.UseJwtBearerAuthentication(options => { ... });
app.UseMiddleware<MyMiddleware>(options => { ... });
Then on a request, first the cookie middleware will be called. If it can handle the scheme and it fails, then following middlewares won't be executed. If it can't handle the scheme, next one will be called (jwt bearer). If that passes, the next middleware (MyMiddleware) will be called.
In other words, when your MyMiddleware.Invoke() method is being called, the user has been authenticated.
You can inject the service directly in the Invoke signature.
From the official doc here:
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.
So in your case:
public async Task Invoke(HttpContext context, IMyService service)
{
service.SetCompany("My Company");
await _next.Invoke(context);
}
will work.

AddScoped service is not retaining settings throughout the request

I have created an Interface
public interface ICurrentUser
{
Task<bool> Set(UserAuth user);
User Get();
}
and a class
public class CurrentUserSvc : Interface.ICurrentUser
{
private User _u;
private UserAuth _ua;
private AppDbContext db;
public CurrentUserSvc(AppDbContext db) {
this.db = db;
}
public User Get()
{
return _u;
}
public async Task<bool> Set(UserAuth ua)
{
_ua = ua; // this is the default EntityFramework IdentityUser
_u = await db.AppUsers // this is my applicaiton's 'extra settings'
// user used to ensure passowrd fields are
// not passed about everywhere
.Where(u => u.UserID == _ua.UserID)
.SingleAsync();
return true;
}
}
In Startup.cs I set
services.AddScoped<ICurrentUser, CurrentUserSvc>();
// I also add a service which will be used later in a scoped
// lifecycle (though I've also tried transient on that one)
services.AddScoped<IProductDbSvc, ProductDbSvc>();
Later I call to a piece of middleware:
public async Task<Task> Invoke(HttpContext hc)
{
if (hc.User.Identity.IsAuthenticated) {
UserAuth iu = await _um.FindByIdAsync(hc.User.GetUserId());
await _cus.Set(iu);
}
// the values are definitely set correctly here.
// I have inspected them during debug
return _next(hc);
}
Later still I try to access the content of the CurrentUserSvc I try to access the current user via the GET
public ProductDbSvc(AppDbContext db, ICurrentUser cu){
this.db = db;
this.cu = cu;
// the values in cu are NULL here. Get() returns null
this.CurrentUser = cu.Get();
}
but the result of Get() is null I was expecting that a Scoped param would retain the values set earlier in the request lifecycle.
What am I missing? Is there some other way to ensure the scoped-singleton retains the user data throughout the application's lifecycle.
UPDATE: I've created a generic project that illustrates this problem generically. https://github.com/AlexChesser/AspnetIdentitySample
check out the repo
build and run in visualstudio or DNX
register a local user
try to view the service on http://localhost:5000/api/currentuser
You'll notice that within the DEBUG output you can see that the correct user details are set, but within the actual controller itself the values returned are null.
UPDATE 2 the working sample is on this branch in github https://github.com/AlexChesser/AspnetIdentitySample/tree/dependencyinjectionscoped
UPDATE 3 turns out scoped parameters can be injected into the INVOKE method of custom middleware as well. https://github.com/AlexChesser/AspnetIdentitySample/commit/25b010a5ae45678c137b2ad05c53ccd659a29101 altering the invoke method will allow for scoped parameters to be injected correctly.
public async Task Invoke(HttpContext httpContext,
ICurrentUserService cus,
UserManager<ApplicationUser> um)
{
if (httpContext.User.Identity.IsAuthenticated)
{
ApplicationUser au = await um.FindByIdAsync(httpContext.User.GetUserId());
await cus.Set(au);
}
await _next(httpContext);
}
UPDATE 4 - I discovered an issue with my middleware signature last night which is pretty important. Code above has been edited to the correct form. Specifically the method was Task<Task> and return _next(...)
This was resulting in a "whitescreen" death on certain page loads (async called badly will not throw a stack trace)
By altering to a Task and using await next(...) the code functions properly and eliminates the intermittent whitescreen death caused by badly implemented async in dotnet5.
DbContext is a scoped service and as well as your CurrentUserSvc is a scoped service. Middlewares are instantiated only once for the whole running time of the app, so they are singleton essentially. So you need to remove both DbContext and CurrentUserSvc from being constructor injected here.
Instead you can use HttpContext's RequestServices property (which returns a IServiceProvider) to resolve both the DbContext and CurrentUserSvc services.
In the middleware, inject a dependency to IServiceProvider, rather than ICurrentUser. Then in the Invoke get the current user via serviceProvider.GetRequiredService<ICurrentUser>();

Categories

Resources