Asp.Net Core Middleware service dependent on current User - asp.net-core

I would like to either change a request scoped service or set one in a custom middleware layer.
Specifically, I want to be able to do something like the below contrived example in Startup.cs:
public void ConfigureServices(IServiceCollection service)
{
service.AddScoped<IMyUserDependentService>((provider) => {
return new MyService());
});
}
public void Configure(...) {
//other config removed
app.Use(async (context, next) => {
var myService = context.ApplicationServices.GetService<IMyUserDependentService>();
myService.SetUser(context.User.Identity.Name);//Name is Fred
next.Invoke();
});
}
Then in the controller do this:
public class HomeController: Controller
{
public HomeController(IMyUserDependentService myService)
{
//myService.UserName should equal Fred
}
}
The problem is, that this doesn't work. myService.UserName isn't Fred in controller, it's null. I think that the IOC container is creating a new instance in the controller, and not using the one set in the middleware.
If I change the scope of the service to Transient, Fred is remembered, but that doesn't help because the service is dependent on who the current user is.
To recap, what I need is to create/or edit a service that requires the current user (or other current request variables), but am unable to work this out.
Thanks in advance!

Have you tried using context.RequestServices?

I just ran into a similar issue, I got an error like
InvalidOperationException: Cannot resolve scoped service 'IScopedService' from root provider., the exception thrown was very not well documented.
Here is how I solved it:
[Startup.cs]
services.AddScoped<IAnyScopedService, AnyScopedService>();
services.AddSingleton<ISomeOtherSingletonService, SomeOtherSingletonService>();
[MyMiddleware.cs]
public sealed class MyMiddleware
{
private readonly RequestDelegate _next;
private readonly ISomeOtherSingletonService _Svc;
public MyMiddleware(
RequestDelegate next,
ISomeOtherSingletonService svc)
{
_next = next;
_Svc = svc;
}
public async Task Invoke(HttpContext context,
IAnyScopedService scopedService)
{
// Some work with scoped service
}
}
Indeed the Middleware is instanciated only once, but called many times.
The constructor takes therefore singleton instances, where the invoke method can get scoped injected parameters.
More details on Mark Vincze post

Related

Register or remove middleware without deploying code

I have created a middleware which logs requests/response data in the database.
I want this middleware to work only when I want to troubleshoot defect or unwanted exception. The middleware should not log rest of the time.
I want a switch button which I can on or off on any controller without making any code changes and deployment.
Please suggests the ways to achieve the above.
In Program.cs, you can add conditionally a middleware like :
var builder = WebApplication.CreateBuilder(args);
...
var app = builder.Build();
if (app.Configuration.Get<bool>("MiddlewareLog.Enable"))
{
app.UseCustomeLoggerMiddleware();
}
...
To enable/disable the middleware, you only need to update the appsettings.json and restart the web api/app.
A solution is to enable/disable the middleware from a global setting. Then the controller's action can modify this global setting to enable/disable the middleware.
public class LoggerMiddleware
{
public static volatile bool Enable;
private readonly RequestDelegate _next;
public LoggerMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task InvokeAsync(HttpContext context)
{
if(Enable)
{
// Log
}
await _next(context);
}
}
[Route("logger")]
public class LoggerController : ControllerBase
{
[HttpGet]
public void EnableOrDisable(bool enable)
{
LoggerMiddleware.Enable = enable;
}
}
In the example, I use a static field, but it's possible to inject a singleton service in the middleware and the controller to share the setting.

Resolve scoped service from singleton service

Is it somehow possible to resolve a scoped service in a singleton service's method being called by a scoped one?
E.g. i have a singleton service "GlobalService" and a scoped one "UserService".
If the UserService executes a method "Job" in "GlobalService", is it somehow possible to get scoped services in this method by using Assembly.GetCallingAssembly()? Otherwise I need to pass all the required parameters.
Thank you ✌
#DeepkaMishra's answer won't work in all scenarios.
I used it myself in blazor webassembly loggingprovider and httpcontext came as null.
For more details, read this, just adding quoted text here.
Think of HttpContext as a telephone call. If you pick the phone up
when no-one has called then there is no context i.e. it is null. When
someone does call then you have a valid context. This is the same
principal for a web call. The Configure method in Startup is not a web
call and, as such, does not have a HttpContext.
Working solution, I found is provided in this.
public class PersistedConfigurationService : IPersistedConfigurationService
{
private readonly IServiceProvider _serviceProvider;
public PersistedConfigurationService(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
public async Task Foo()
{
using (var scope = _serviceProvider.CreateScope())
{
//here you can get the scoped service
var context = scope.ServiceProvider.GetRequiredService<IPersistedConfigurationDbContext>();
// do something with context
}
}
}
Singleton would have one single instance which can be used by your scoped service. Your scoped service method can use singleton service instance.
If you call a singleton service's method, you can get the scoped service object in it. You can use IHttpcontextAccessor to resolve the scoped service instance inside that method.
internal class Singleton
{
private readonly IHttpContextAccessor httpContextAccessor;
public Singleton(IHttpContextAccessor httpContextAccessor)
{
this.httpContextAccessor = httpContextAccessor;
}
public int Job()
{
return httpContextAccessor.HttpContext.RequestServices.GetRequiredService<Scoped>().MyProperty;
}
}
You would need to register these service in Startup's ConfigureServices method:
services.AddHttpContextAccessor();
services.AddScoped<Scoped>();
services.AddSingleton<Singleton>();

Autofac, thread and ISErviceScopeFactory

I have a problem: the service provider is disposed when arrives in the method.
Is this an issue or it is my fault?
My service
public class BomService
{
private readonly IServiceScopeFactory _scope;
public BomService(IServiceScopeFactory scope)
{
_scope = scope;
}
public void ImportAsync(ImportRequestDto importSettings)
{
Task.Run(async () => await ImportFile.ImportAsync<Bom, CatalogContext>(_scope));
}
}
Method
public static async Task ImportAsync<T, TContext>(IServiceScopeFactory parentScope) where T : class where TContext : DbContext
{
using var scope = parentScope.CreateScope();
var repo = scope.ServiceProvider.GetService<IGenericRepository<T, TContext>>();
}
The error:
Instances cannot be resolved and nested lifetimes cannot be created from this LifetimeScope as it has already been disposed
Do not run long running tasks in an HTTP request, these should be done in a separate process while returning a response to the client immediately.
Refer to the answer of this question
As also suggested there you can use something like Hangfire to run background processes.
Update
It is not good practice to inject IServiceScopeFactory. Like that you are implementing the Service Locator anti pattern. Instead inject the repository directly and let the DI figure out the resolution and scope.

How does Nservicebus handle nested containers on structuremap?

TL;DR; some property is not being injected into a service when it should. How does Nservicebus handle nested containers on structuremap?
Hey guys new here. I'm currently upgrading our project to NSB6. I have a couple of question on dependency injection with Structuremap.
We have some services that use to use ISendOnlyBus, which we call from inside a handler. For test purposes(I just want to get the handler working) I changed those to IMessageHandlerContext, and at the beginning of the handler I'm injecting the context to the container. It works as expected, IMessageHandlerContext is resolved on these services and I'm able to use it.
public class MyMessageHandler : IHandleMessage<IMyMessage>
{
private IContainer _container;
private ISomeService _someService;
public MyMessageHandler (IContainer container, ISomeService someService)
{
_container = container;
_someService = someService;
}
public async Task Handle(IMyMessage message, IMessageHandlerContext context)
{
_container.Inject(typeof(IMessageHandlerContext), context);
_someService.DoSomething();
}
}
public class SomeService : ISomeService
{
private IMessageHandlerContext _context;
public SomeService(IMessageHandlerContext context)
{
_context = context;
}
public void DoSomething()
{
_context.Send<ISomeMessage>(x => {
//... init message
});
}
}
Now the issue is we have an Nhibernate custom IPostUpdateEventListener over an entity that sends nservicebus messages. On version 5 it had an ISendOnlyEndpoint that it used for sending, same as the other services, but this time the context is not being resolved by Structuremap. I'm wondering how's and if nservicebus manages nested containers inside message handlers. I cannot seem to find any documentation for it.
Child containers within StructureMap do not support some of the features required by NServiceBus containers. Policies are used to configure the injection, and policies are not supported in child containers.
Policies are only applied to root container in StructureMap
Here is an issue in the NServiceBus.StructureMap repo to track it.
I'm guessing the suggested refactor of this would be
public class MyMessageHandler : IHandleMessage<IMyMessage>
{
public async Task Handle(IMyMessage message, IMessageHandlerContext context)
{
context.Resolve<ISomeService>().DoSomething(context);
}
}
public class SomeService : ISomeService
{
public void DoSomething(IMessageHandlerContext context)
{
context.Send<ISomeMessage>(x => {
//... init message
});
}
}
context.Resolve would be be an extension you write to access the container instance in the context instance.
However if you wanted to just send messages from outside a message handler you can inject IEndpointInstance or IMessageSession which is returned when you start the bus.
I think they suggest to only send messages from message handlers using the context - but sometimes that just doesn't work.

Unable to get Scoped Service in aspnetcore 1 - RC1 to work

My scoped service for some reason seems to be generating different instances of the same class when I try to access it in 2 middlewares within the same request.
Scenario: I am adding a scoped service as such:
public interface ISimplyRecorder
{
void AddInfo(string key, string value);
Dictionary<string, string> GetAllInfo();
}
public class SimplyCoreRecorderService : ISimplyRecorder
{
private Dictionary<string,string> data;
public SimplyCoreRecorderService()
{
data = new Dictionary<string, string>();
}
public void AddInfo(string key,string value)
{
data.Add("",value);
}
public Dictionary<string,string> GetAllInfo()
{
return data;
}
}
and then the following in startup.cs
services.AddScoped<ISimplyRecorder,SimplyRecorderService>();
now I am calling this service in the constructor of a sample Middleware. I am able to access the service with a new instance and add data into it and then I call await _next(context). However, when I am calling the service in my HomeController, MVC which follows the middleware above, I seem to be getting a new instance of the service even though it's the same request.
HomeController:
ISimplyRecorder _simply;
private IHostingEnvironment _env;
public HomeController(IHostingEnvironment env,ISimplyRecorder simply)
{
_simply = simply;
_env = env;
}
public IActionResult Index()
{
_simply.AddInfo("Home:Action","resulted in index action");
return View();
}
complete code available at: https://github.com/muqeet-khan/SimplyCore if someone wants to give it a go.
Middlewares are instantiated only once when it's first involved, then all the following requests are handled by that middleware instance. NOT a new middleware instance for each request.
You get your ISimplyRecorder in the constructor of the middleware and "cache" it as a private readonly variable. This means the middleware will get the ISimplyRecorder instance of the first request, then keep adding data to that instance for all the following requests rather than the new ISimplyRecorder instance for the following requests which you get in HomeController.
To solve it, you need to get ISimplyRecorder instance from the Invoke method of the middleware.
// using Microsoft.Extensions.DependencyInjection;
public async Task Invoke(HttpContext httpContext)
{
ISimplyRecorder recoder = httpContext.RequestServices.GetRequiredService<ISimplyRecorder>();
}
EDIT:
The comment of Juergen is correct, I tried it out. You may also just write like this:
public async Task Invoke(HttpContext httpContext, ISimplyRecorder recorder)
{
// recorder is from DI
}