.NET Core with Autofac DI - c#

I want to integrate Autofac to my API. Solution is split on several projects so that everything stays decoupled. I have set up my configure services like this:
public IServiceProvider ConfigureServices(IServiceCollection services)
{
// Add framework services.
...
...
// Autofac
var builder = new ContainerBuilder();
builder.RegisterType<RouteRepository>().As<IRouteRepository>();
builder.Populate(services);
ApplicationContainer = builder.Build();
return new AutofacServiceProvider(ApplicationContainer);
}
However now with this code integrated, my API won't start anymore. If I start it in debug mode, I get no errors, but I don't get response either.
API landing route is pretty straightforward:
public IActionResult GetIndex()
{
return Ok("You are seeing this because controller is working!");
}
Also, what might be connected to the problem is that RouteRepository takes one variable as an argument in the constructor and I don't know where can I define what will be passed through? There is no config file by default.

If you're saying that you have one dependency for your RouteRepository, then you have to notify Autofac container how to resolve that:
// singletone
builder.RegisterInstance(new TaskRepository())
.As<ITaskRepository>();
// or instance based creation
builder.Register(c => new LogManager(DateTime.Now))
.As<ILogger>();
Or Autofac couldn't resolve your type.

Related

Configure a logging provider for a different service collection

This is an ASP.NET application in .NET 6. There's a wizard interface where the user inputs some data and then based on the input, I set up a new dependency injection container with the services that are required to complete the task. My problem is that the ILogger<> instances coming out of this second container don't use the custom ILoggingProvider that I set up.
Program.cs:
var builder = WebApplication.CreateBuilder(args);
builder.Logging.ClearProviders();
builder.Logging.AddDebug();
builder.Logging.AddSignalRLogging();
public static class ILoggingBuilderExtensions
{
public static ILoggingBuilder AddSignalRLogging(this ILoggingBuilder builder)
=> builder.AddProvider(new SignalRLoggerProvider(builder.Services))
.AddFilter<SignalRLoggerProvider>("MyNamespace", LogLevel.Information);
}
The SignalRLoggerProvider comes from How to implement an ILogger to send messages to a SignalR Hub?.
Controller:
IServiceCollection services = new ServiceCollection();
services.AddLogging();
services.AddSignalR();
services.AddSingleton(_sheetsClient); // this was injected into the controller
services.AddSingleton<ITeamReaderService>(new PostedTeamReaderService(model.Divisions));
string[] divisionNames = model.Divisions.Keys.ToArray();
foreach (string divisionName in divisionNames)
{
services.AddSingleton<IDivisionSheetService>(provider => new DivisionSheetService(divisionName,
provider.GetRequiredService<StandingsRequestCreatorFactory>(),
provider.GetRequiredService<ISheetsClient>(),
provider.GetRequiredService<IOptionsMonitor<ScoreSheetConfiguration>>(),
provider.GetRequiredService<ILogger<DivisionSheetService>>())
);
}
I know my provider works because when I log things in a controller whose dependencies were injected from the HostBuilder's service collection (_sheetsClient), those messages work correctly. In classes that come from this other container (DivisionSheetService), those log messages go nowhere and when I view the ILogger instance in the debugger, it shows that it has no logger that it's writing to.
So it must be the case that my custom logging provider is unknown to the second container, but I can't figure out how to register it there.
Thanks in advance.
Since you're creating a new ServiceCollection from scratch, you also need to add the logging infrastructure from scratch:
IServiceCollection services = new ServiceCollection();
services.AddLogging(builder => builder.AddDebug().AddSignalRLogging());

Unit Testing with ILoggerProvider

I have the code like this in my unit test setup:
IServiceCollection services = new ServiceCollection();
services.AddSingleton<IDependency, Dependency>();
services.AddLogging();
services.RemoveAll<ILoggerProvider>();
services.AddSingleton<ILoggerProvider, MyCustomProvider>(provider =>
{
var myDependency = provider.GetService<IDependency>();
return new MyCustomProvider(myDependency );
});
var serviceProvider = services.BuildServiceProvider();
var logger = serviceProvider.GetService<ILogger>();
When I run this logger is null.
The lambda for the DI factory to create MyCustomProvider is never called. Nor is the constructor to MyCustomProvider nor the constructor of the class I made that implements ILogger.
I am guessing that I am missing a step to wire this up. But there is a lot of conflicting documentation out there between .Net Core 1, 2 and 3.
What do I need to do to wire up my provider the .Net Core 3 way? (Such that I can get an ILogger that uses it.)
NOTES:
I don't call LoggingBuilder.AddProvider. But if you look at the source code for that extension, it just calls AddSingleton, which is what I do, but with a factory. (Got the idea here.)
Similarly services.RemoveAll<ILoggerProvider>() is the same as calling ClearProviders.
My goal is to simulate an injection of an ILogger into a class constructor.
For Example:
public class SomeClass
{
public SomeClass(ILogger<SomeClass> logger)
{
// Do Stuff
}
}
Somehow this is done without indicating any provider. I would like to make it be the same in my unit test. (Just use my custom provider.)
The reason your logger variable is null in the example is because ILogger isn't actually registered as a service. If you look at the source of AddLogging you can see it only registers ILogger<> and ILoggerFactory. If you've ever tried to accept an ILogger instead of an ILogger<MyClass> via the .NET Core DI you will have run into the following exception*:
System.InvalidOperationException: 'Unable to resolve service for type
'Microsoft.Extensions.Logging.ILogger' while attempting to activate
'Your.Service'
Based on this, your testing code is flawed as you'd never have received an ILogger in the first place. To see that you're code is in fact working, modify your testing code so that it retrieves an ILogger<SomeClass> instead. The result of your variable will be non-null and a break point set in your provider's constructor will be hit:
// Get*Required*Service won't throw
var logger = serviceProvider.GetRequiredService<ILogger<Program>>();
If you want to be able to inject ILogger, you will need to register it separately with a default category name**:
services.AddSingleton<ILoggerProvider, MyCustomProvider>(); // no need for lambda
services.AddSingleton<ILogger>(sp =>
sp.GetService<ILoggerFactory>().CreateLogger("Default")
);
The following will both now work and use your custom provider:
var loggerA = serviceProvider.GetRequiredService<ILogger<Program>>();
var loggerB = serviceProvider.GetRequiredService<ILogger>();
I have my own custom provider/factory/logger that I use to inject xUnit's ITestOutputHelper into a custom logger following the same registration pattern as you, so I know from personal experience that this works. But I've also tested that your specific code is functional (mocking out my own IDependency)--the above code is executed and breakpoints set in the constructor of MyCustomProvider and the service registration are hit. Additionally, if I inject the logger into a class it's hit as well (as expected).
Your comment "Somehow this is done without indicating any provider" is misinformed, because you do in fact have a provider registered! But even in a scenario where all providers were cleared and no new ones were added, you'd still get a non-null logger. This is because LoggerFactory just loops through each provider to build up a new logger wrapper around them. If there's no providers then you essentially get a no-op logger.
* This is unfortunate as some tools (like R#) will suggest converting the parameter to the base type ILogger which then breaks DI!
** A default category name is required since there is no generic type argument to pass to ILoggerFactory.CreateLogger. For the generic ILogger<T> the category name is always a variation of T's name--but we obviously don't get that with the non-generic version. If I had to guess, this is probably why they don't register an implementation of ILogger by default.
NET CORE Testing I'm doing like this:
in the constructor of the test class (or where you do your DI mapping)
public class UnitTest1
{
public IConfigurationRoot Configuration { get; set; }
private readonly IDisponibilitaService _disponibilitaService;
public UnitTest1()
{
var services = new ServiceCollection();
Configuration = new ConfigurationBuilder()
.SetBasePath(Path.Combine(Path.DirectorySeparatorChar.ToString(), "directory", "Kalliope_CTI", "backend", "KalliopeCTITestProject"))
.AddJsonFile("testconfig.json", optional: false, reloadOnChange: true)
.Build();
services.AddSingleton<ILoggerFactory, NullLoggerFactory>(); //< -- HERE TRY TO SET NullLoggerFactory
var serviceProvider = services
.AddOptions()
.BuildServiceProvider();
}
//// your test methods
}
Hope it helps you!!
It may be a shot in the dark, but you can try adding your ILoggerProvider to the ILoggerFactory:
var loggerFactory = serviceProvider.GetService<ILoggerFactory();
var loggerProvider = serviceProvider.GetService<ILoggerProvider>();
loggerFactory.AddProvider(loggerProvider);
In case it was constructed sooner than you have registered your provider, it might not be aware of it's existance.
Alternatively, would it work, if you also tell it, how to resolve the ILogger?:
...
services.AddSingleton<ILoggerProvider, MyCustomProvider>(provider =>
{
var myDependency = provider.GetService<IDependency>();
return new MyCustomProvider(myDependency );
});
services.AddSingleton(typeof(ILogger<>), provider =>
{
var loggerProvider = provider.GetService<ILoggerProvider>();
return loggerProvider.CreateLogger("TestLogger");
});
...
Finally... is there a reason, why you can't just inject ILoggerProvider into your tests, and call loggerProvider.CreateLogger(<test_class_name>) to get the logger?

Middleware add new dependency to DI Container or Services

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.

Inject DataProtection based on injected user options IOptions<> in ConfigureServices

I'm a little bit confused here regarding how to inject DataProtection in ConfigureServices(IServiceCollection services) based on user settings being as well injected from services.Configure<UserSettingsConfig>(Configuration.GetSection("UserSettings"));.
The values appName_from_appsettings_json and dirInfo_from_appsettings_json below should be coming from the injected UserSettingsConfig and would be accessible anywhere else by injecting IOptions<UserSettingsConfig> but not here.
public void ConfigureServices(IServiceCollection services)
{
// Add framework services.
services.Configure<UserSettingsConfig>(Configuration.GetSection("UserSettings"));
services.AddMvc();
services.AddScoped<DevOnlyActionFilter>();
services.AddDataProtection()
.SetApplicationName(appName_from_appsettings_json)
.PersistKeysToFileSystem(dirInfo_from_appsettings_json);
}
I've found ways to achieve my goals without using DI with code like var sharedDataProtectionAppName = configuration.GetValue<string>("UserSettings:SharedDataProtection:ApplicationName");
I had the feeling I have found the solution in this article http://andrewlock.net/access-services-inside-options-and-startup-using-configureoptions/ by it seems like I can't figure out how to apply it to my case. I would need a way to inject DataProtection based on values from the injected IOptions<UserSettingsConfig>. What would be the cleanest way to do that in your opinion?
UPDATE: I found a solution based on that type of code that I could be calling from ConfigureServices, but I still wonder if it's the best way.
var userSettingsConfig = services.BuildServiceProvider().GetServices<IOptions<UserSettingsConfig>>().First();
You could also use the extension method .Bind(). This method will try to bind the value to the Configuration object by matching the keys from the configuration.
// Add framework services.
var userSettingsConfig = new UserSettingsConfig();
Configuration.GetSection("UserSettings").Bind(userSettingsConfig);
services.Configure<UserSettingsConfig>(Configuration.GetSection("UserSettings"));
services.AddMvc();
services.AddScoped<DevOnlyActionFilter>();
services.AddDataProtection()
.SetApplicationName(userSettingsConfig.appName)
.PersistKeysToFileSystem(userSettingsConfig.DirInfo);

How to get container for Autofac for WebAPI2?

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();
}

Categories

Resources