I'm using ASP.NET Core and Autofac. Almost everything is registered as per lifetime scope ("per request"). So my database context DbContext is the same instance throughout a request.
However I have a singleton which also depends on DbContext. To avoid a captive dependency, it is injected as Func<Owned<DbContext>>, which means a new DbContext instance each time.
The problem is I need the same instance, as everywhere else during the request, not a new one.
I want to avoid a captive dependency bug, but I also want the same instance. Is that possible via tagging or a custom registration?
From the comments the least "architectural" painful approach may be by creating your own Scoped<T> class which will resolve the DbContext from current HttpContext
// Use an interface, so we don't have infrastructure dependencies in our domain
public interface IScoped<T> where T : class
{
T Instance { get; }
}
// Register as singleton too.
public sealed class Scoped<T> : IScoped<T> where T : class
{
private readonly IHttpContextAccessor contextAccessor;
private HttpContext HttpContext { get; } => contextAccessor.HttpContext;
public T Instance { get; } => HttpContext.RequestServices.GetService<T>();
public Scoped(IHttpContextAccessor contextAccessor)
{
this.contextAccessor = contextAccessor ?? throw new ArgumentNullException(nameof(contextAccessor));
}
}
Register it as
// Microsoft.Extensions.DependencyInjection
services.AddSingleton(typeof(IScoped<>), typeof(Scoped<>);
// Autofac
containerBuilder.RegisterType(typeof(Scoped<>))
.As(typeof(IScoped<>));
Then inject this into your validator service.
public class CustomerValidator: AbstractValidator<Customer>
{
private readonly IScoped<AppDbContext> scopedContext;
protected AppDbContext DbContext { get } => scopedContext.Instance;
public CustomValidator(IScoped<AppDbContext> scopedContext)
{
this.scopedContext = scopedContext ?? throw new ArgumentNullException(nameof(scopedContext));
// Access DbContext via this.DbContext
}
}
This way you can inject any scoped service w/o further registrations.
Additional notes
Autofac is considered a "conformer" (see docs) DI and integrates well with ASP.NET Core and Microsoft.Extensions.DependencyInjection.
From the documentation
public IServiceProvider ConfigureServices(IServiceCollection services)
{
// Add services to the collection.
services.AddMvc();
// Create the container builder.
var builder = new ContainerBuilder();
// Register dependencies, populate the services from
// the collection, and build the container. If you want
// to dispose of the container at the end of the app,
// be sure to keep a reference to it as a property or field.
builder.RegisterType<MyType>().As<IMyType>();
builder.Populate(services);
this.ApplicationContainer = builder.Build();
// Create the IServiceProvider based on the container.
return new AutofacServiceProvider(this.ApplicationContainer);
}
There a few subtle differences to the default usage of Startup class and Microsoft.Extensions.DependencyInjection container.
ConfigureServices isn't void anymore, it returns IServiceProvider. This will tell ASP.NET Core to use the returned provider instead of DefaultServiceProvider from Microsoft.Extensions.DependencyInjection.
We return the Autofac container adapter: new AutofacServiceProvider(this.ApplicationContainer) which is the root container.
This is important to make ASP.NET Core use the container everywhere in ASP.NET Core, even inside middlewares which resolve per request dependencies via HttpContext.RequestedServices.
For that reasons you can't use .InstancePerRequest() lifetime in Autofac, because Autofac isn't in control of creating scopes and only ASP.NET Core can do it. So there is no easy way to make ASP.NET Core use Autofac's own Request lifetime.
Instead ASP.NET Core will create a new scope (using IServiceScopeFactory.CreateScope()) and use a scoped container of Autofac to resolve per-request dependencies.
Related
Problem:
We have a .NET 5 WPF application that has an EntityFramework Core entities class file DbEntities, which implements the DbContext. We use constructor injection when instantiating it. One of the options that we use is AddInterceptors in order to append an Access Token to the SqlConnection. The interceptor is called AzureAuthenticationInterceptor. When registering the service, we would like to pass in the ServiceProvider so that it is available in the interceptors constructor, which can be used to get a service that implements Access Token in-memory caching.
The reason for it is that we have a project with 50+ classes that all use the same DbEntities file, which takes 0 arguments in the constructor. This was upgraded to .NET 5 where Dependency Injection was avoided due to the work it would take to apply it to all of the forms. So, the DbEntities is instantiated in the forms with new DbEntities();.
But, in this case, we are implementing an access token cache, which needs to be registered as a service. Otherwise, if we just instantiate the cache every time we create a new DbContext, then the cache will be wiped out.
The access token in-memory cache is implemented using this method https://mderriey.com/2020/09/12/resolve-ef-core-interceptors-with-dependency-injection/
We only want to use dependency injection for the in-memory token cache. The only way we think of as a shortcut is to pass the ServiceProvider in the interceptor's constructor, but it does not appear available in the ConfigureServices method.
Question:
Is it possible to pass in the ServiceProvider? If not, is there any other way we can implement dependency injection on the interceptor without having to change 50 class files?
Program.cs
Public static void Main()
{
...
Host = Microsoft.Extensions.Hosting.Host.CreateDefaultBuilder()
.ConfigureAppConfiguration((context, builder) =>
{
builder.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true);
})
.ConfigureServices((context, services) =>
{
Configuration = context.Configuration;
ConfigureServices(Configuration, services);
})
.Build();
...
}
private static void ConfigureServices(IConfiguration objConfiguration, IServiceCollection objServices)
{
objServices.AddMemoryCache()
.AddSingleton<IAzureSqlTokenProvider, AzureIdentityAzureSqlTokenProvider>()
.Decorate<IAzureSqlTokenProvider, CacheAzureSqlTokenProvider>()
.AddSingleton(new AzureAuthenticationInterceptor(IServiceProvider_NeededHere))
;
}
DbEntities.cs
public DbEntities() :
base(new DbContextOptionsBuilder<DbEntities>()
.UseSqlServer(ConfigurationManager.ConnectionStrings["DbEntities"].ConnectionString)
.AddInterceptors(new AzureAuthenticationInterceptor())
.Options)
{ }
AzureAuthenticationInterceptor.cs
public AzureAuthenticationInterceptor(IServiceProvider objServiceProvider)
{
this.IAzureSqlTokenProvider = (IAzureSqlTokenProvider)objServiceProvider.GetService(typeof(IAzureSqlTokenProvider));
}
First, avoid injecting IServiceProvider, it is a code smell and leads to poor design.
Refactor AzureAuthenticationInterceptor.cs
public AzureAuthenticationInterceptor(IAzureSqlTokenProvider tokenProvider) {
this.IAzureSqlTokenProvider = tokenProvider;
}
So that way explicit dependencies can be injected as needed
//...
.AddSingleton<AzureAuthenticationInterceptor>()
//...
When resolving the interceptor while configuring the DbEntities
//...
services.AddDbContext<DbEntities>((provider, options) => {
options.UseSqlServer(Configuration.GetConnectionString("<connection-string-name>"));
options.AddInterceptors(provider.GetRequiredService<AzureAuthenticationInterceptor>());
});
//...
Note that if you are manually initializing the context using the default constructor, ie:new DbEntities(); Then this bypasses the opportunity to apply dependency injection via constructor injection.
I have a project in .NET 5 with RazorPages, I set this code to validate the Dependecy Injection in the Progam.cs file:
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.UseDefaultServiceProvider(options =>
{
options.ValidateOnBuild = true;
options.ValidateScopes = true;
})....
I forgot to register the service that is injected into my page, so I would have expected that when I try to start the app an error page would show this kind of problem, but I don't understand why it doesn't happen, because for example in case I don't register ILocalizerService this happens:
This is my RazorPage:
public class SignupModel : IdentityPageModel
{
[BindProperty]
public Models.Account.Signup Signup { get; set; }
private readonly CustomUserManager _userManager;
private readonly ILogger<SignupModel> _logger;
private readonly INcsService _ncsService;
public SignupModel(CustomUserManager userManager,
ILogger<SignupModel> logger,
INcsService ncsService) : base(localizerService)
{
Guard.Against.Null(userManager, nameof(userManager));
Guard.Against.Null(logger, nameof(logger));
Guard.Against.Null(ncsService, nameof(ncsService));
_userManager = userManager;
_logger = logger;
_ncsService = ncsService;
}
// Other code....
}
This is my service:
[PublicAPI]
public class NcsService : INcsService
{
private readonly IHttpClientFactory _httpClientFactory;
public NcsService(IHttpClientFactory httpClientFactory)
{
_httpClientFactory = httpClientFactory;
}
// Other code...
}
I have only registered IHttpClientFactory but not INcsService interface and implementation:
services.AddHttpClient(nameof(NcsService), client =>
{
client.BaseAddress = new Uri(ncsSettings.BaseUri);
client.DefaultRequestHeaders.Add("x-functions-key", ncsSettings.ApiKey);
client.DefaultRequestHeaders.Add("x-app-name", "TSID");
}).AddHeaderPropagation(options =>
{
options.Headers.Add("x-request-id");
options.Headers.Add("x-correlation-id");
})
.AddPolicyHandler(GetRetryPolicy());
I hope I was clear.
Thank you
The root of the issue is Microsoft's default IComponentActivator implementation (the DefaultComponentActivator). The Component Activator is in control of creating your Razor Pages, but the built-in behavior does not request those pages from the built-in container. Instead, it just creates them using Activator.CreateInstance.
This means that Blazor does not register your pages in the built-in container and because of that, the page will not be part of the container's verification process.
This is, IMO, a design flaw in Blazor, because it well known, and well understood that, in case you are using a DI Container, you should let all your application components go through the container pipeline. That's the only way that the container can give you a reasonable amount of certainty about the validity of your application components.
Blazor, however, is not the only part of the ASP.NET Core framework where this happens. ASP.NET MVC Controllers, for instance, by default aren't registered in the container, and aren't resolved from the container. This is configurable though, but since this is not the default behavior, the ValidateOnBuild gives a false sense of security.
Other containers might have a more sensible default. Simple Injector, for instance, (the container that I maintain) contains extension methods that always register all MVC controllers up front. And with the Blazor integration, similar things happen.
If you stick with the built-in container, it would be good to ensure all components are resolved from the container. With MVC this is easy, because you can simply call AddControllersAsServices. With Blazor, unfortunately, this is much more difficult, because there exists no such method as AddComponentsAsServices. This means that you have to create a custom IComponentActivator that calls back into the container. But still, you'll likely have to fallback to the original behavior using Activator.CreateInstance for all Blazor Components that are created by Microsoft, because it might be much harder to find and register them using reflection. For inspiration on how to create such custom Component Activator and register your application Blazor components, take a look at the code presented here.
I am writing a piece of middleware (maybe I want a scoped service??), I guess my plan is to have some kind of multi-tenant scenario.
If for example, I have 2 domains that respond on this service:
www.domain1.com
www.domain2.com
I want to capture the request when it starts, look at the host name that is being used and then set some other object to be available through Dependency Injection for everything further up the pipeline.
It seems that middleware should be the right way to achieve this, but not sure how to do the final step.
My options seem to be:
Middleware
Register Singleton service to access database
Register early to be the first item of middleware to capture the request.
Analyse Request Object and build custom configuration object
Add custom configuration as a scoped object to the DI container for use by other services
Service
Register Singleton service to access database
Register Singleton service for IHttpContextAccessor
Register Scoped? Service - to do equivalent of middleware
Analyse the request object and build custom configuration object
Register custom object as new scoped object in the DI container
My assumption is that the Service is able to register the custom scoped object as it is still within the ConfigureServices method of the startup.cs
However, with middleware it is initialised through the Configure method by which point the DI container has already been built?
You can use the factory-overload of AddScoped for the service you want to be different per tenant/request. Here's an example:
services.AddScoped<IServiceForTenant>(sp =>
{
var httpContextAccessor = sp.GetRequiredService<IHttpContextAccessor>();
var serviceForTenant = new ServiceForTenant();
// TODO: Use httpContextAcccessor.HttpContext to configure serviceForTenant.
return serviceForTenant;
});
For each request that comes in to your ASP.NET Core application, the code above will run when you first request IServiceForTenant in e.g. a controller. At this point, your code can read from IHttpContextAccessor.HttpContext and make whatever decisions it needs in order to create the implementation instance for IServiceForTenant. This same instance will then be used for the rest of the request (i.e. further up the pipeline).
The argument passed into AddScoped is Func<IServiceProvider, T>. All you need to provide here is a delegate of some kind, which could be done in one of many ways. Here's some examples:
You could just wrap the call into its own extension method, like this:
public static void AddServiceForTenant(this IServiceCollection services)
{
services.AddScoped<IServiceForTenant>(sp =>
{
// ...
});
}
In ConfigureServices:
services.AddServiceForTenant();
Use a class with a static method:
public static class ServiceForTenantFactory
{
public static ITenantForService Create(IServiceProvider sp)
{
// ...
}
}
In ConfigureServices:
services.AddScoped(ServiceForTenantFactory.Create);
Use a class with an instance method:
public class ServiceForTenantFactory
{
public ITenantForService Create(HttpContext httpContext)
{
// ...
}
}
In ConfigureServices:
services.AddScoped(sp =>
{
var httpContextAccessor = sp.GetRequiredService<IHttpContextAccessor>();
var serviceForTenantFactory = new ServiceForTenantFactory(); // Or use DI.
return serviceForTenantFactory.Create(httpContextAccessor.HttpContext);
});
This last option is the most flexible, as you could even resolve ServiceForTenantFactory itself from DI and it can have its own dependencies, etc. Note also that Create here takes the HttpContext directly (as an example).
As I've already said, there are yet more options than the three of shown, but this should be a good base to work with.
I have a multi-tenant MVC5 webapp, which is using Autofac v3.5.2 and Autofac.Mvc5 v3.3.4.
My Autofac DI wiring takes place in a class within my MVC project. For authentication, we are using OWIN OpenId middleware to integrate with Azure B2C. In the OWIN Startup class I need a dependency to set tenantId/clientId using information from the current request.
I try to grab the dependency via :
DependencyResolver.Current.GetService<...>();
However, this always throws an ObjectDisposedException
Instances cannot be resolved and nested lifetimes cannot be created from this LifetimeScope as it has already been disposed.
We have an ISiteContext in our application that has a request-lifetime. It gets populated with configuration values specific to the current request. I am trying to fetch these values like this:
private OpenIdConnectAuthenticationOptions CreateOptionsFromPolicy(string policy)
{
var options = new OpenIdConnectAuthenticationOptions
{
Notifications = new OpenIdConnectAuthenticationNotifications
{
...
RedirectToIdentityProvider = SetSettingsForRequest
}
}
}
private Task SetSettingsForRequest(RedirectToIdentityProviderNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions> context)
{
var siteContext = DependencyResolver.Current.GetService<ISiteContext>();
context.ProtocolMessage.ClientId = siteContext.ClientId;
return Task.FromResult(0);
}
The error happens when trying to use the DependencyResolver in SetSettingsForRequest. I have no clue as to what I am doing wrong here. Currently I have no Autofac DI setup in my Startup Configuration(IAppBuilder app) method, since this is already setup in my MVC project.
As Mickaƫl Derriey pointed out, the following code is a solution to being able to resolve request-scoped dependencies in OWIN:
public class Startup
{
public void Configuration(IAppBuilder app)
{
var builder = new ContainerBuilder();
// Register dependencies, then...
var container = builder.Build();
// Register the Autofac middleware FIRST. This also adds
// Autofac-injected middleware registered with the container.
app.UseAutofacMiddleware(container);
// ...then register your other middleware not registered
// with Autofac.
}
}
and then use in any later code to resolve a dependency:
// getting the OWIN context from the OWIN context parameter
var owinContext = context.OwinContext;
var lifetimeScope = owinContext.GetAutofacLifetimeScope();
var siteContext = lifetimeScope.GetService<ISiteContext>();
context.ProtocolMessage.ClientId = siteContext.ClientId;
Using this solution, I did not have any issues anymore resolving request-scoped dependencies, since Autofac seems to accord to the OWIN way of creating/disposing of a request scope.
I'm afraid that at this point you need to set up DI in the OWIN pipeline. It's not a difficult operation.
You'll have to:
follow the steps listed in the official documentation on OWIN integration with ASP.NET MVC
move your existing registration code (that is most probably in Global.asax.cs in Startup.cs as the documentation linked above shows
Doing this means Autofac will create the per-request lifetime scope lower in the stack, at the OWIN level. This will allow you to get hold of the HTTP request lifetime scope from the OWIN context:
private Task SetSettingsForRequest(RedirectToIdentityProviderNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions> context)
{
// getting the OWIN context from the OIDC notification context
var owinContext = context.OwinContext;
// that's an extension method provided by the Autofac OWIN integration
// see https://github.com/autofac/Autofac.Owin/blob/1e6eab35b59bc3838bbd2f6c7653d41647649b01/src/Autofac.Integration.Owin/OwinContextExtensions.cs#L19
var lifetimeScope = owinContext.GetAutofacLifetimeScope();
var siteContext = lifetimeScope.GetService<ISiteContext>();
context.ProtocolMessage.ClientId = siteContext.ClientId;
return Task.FromResult(0);
}
I hope this helps you get over that issue.
In Ninject I can get object needed for interface by using class WebContainerManager
Ninject definition:
var logManager = new LogManagerAdapter();
container.Bind<ILogManager>().ToConstant(logManager);
Ninject usage:
var log = WebContainerManager.Get<ILogManager>().GetLog(typeof(WebApiApplication));
My question is how to do the same in Autofac, to get needed class for interface?
UPDATE 1: Im using WebAPi 2, not MVC.
If you need access to Autofac container from the class that was resolved by Autofac itself, then you can specify dependency on IComponentContext that is automatically provided by Autofac.
Example:
public void SomeComponent(IComponentContext context)
{
this.context = context;
}
...
// somewhere inside SomeComponent
context.Resolve<ILogManager>();
If your code is running inside ASP.Net environment, then you most probably set its DependencyResolver, thus you can always access it like:
DependencyResolver.Current.GetService<ILogManager>();
but as it is already mentioned in other comments, Service Locator is an anti-pattern that should be avoided.
In order to integrate autofac container with standard MVC dependency resolution mechanism you need to:
install Autofac.Mvc5 nuget package
set DependencyResolver with the following code
var container = builder.Build();
DependencyResolver.SetResolver(new AutofacDependencyResolver(container));
And in case you don't mind having explicit dependency on Autofac in your application code you can access global Autofac resolver reference the same way you use Ninject WebContainerManager:
var log = AutofacDependencyResolver.Current.Resolve<ILogManager>().GetLog(typeof(WebApiApplication));
You can create your builder.
var builder = new ContainerBuilder();
// Usually you're only interested in exposing the type
// via its interface:
builder.RegisterType<SomeType>().As<IService>();
// However, if you want BOTH services (not as common)
// you can say so:
builder.RegisterType<SomeType>().AsSelf().As<IService>();
Then you will be able to build your IoC:
IContainer Container = builder.Build();
And a simple example of How to get resource from container:
// Create the scope, resolve your IService,
// use it, then dispose of the scope.
using (var scope = Container.BeginLifetimeScope())
{
var writer = scope.Resolve<IService>();
writer.DoSomething();
}