Adding Autofac to .NET core 6.0 using the new single file template - autofac

I am trying to add Autofac to a .Net 6.0 web API.
I'm using the latest ASP.NET Core Web API template that generates a single start-up Program.cs file.
Installed Autofac versions:
Autofac 6.3.0
Autofac.Extensions.DependancyInjection (7.2.0-preview.1)
Installed .Net 6.0 versions:
Microsoft.AspNetCore.App 6.0.0-rc.2.21480.10
Microsoft.NETCore.App 6.0.0-rc.2.21480.5
Microsoft.WindowsDesktop.App 6.0.0-rc.2.21501.6
Just in case of doubt, this is the entire content of the Program.cs file (yes, no namespaces or class definition. Only a Program.cs file and no StartUp class or Startup.cs file)
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run()
I've tried looking through the latest Autofac documentation but the examples there, despite saying for .Net Core 3.x and later don't seem to fit with .Net 6.0 code. I can't figure out how to add Autofac into the middleware pipeline.
Any help is greatly appreciated.
Code snippet from Autofac website
public class Program
{
public static void Main(string[] args)
{
// ASP.NET Core 3.0+:
// The UseServiceProviderFactory call attaches the
// Autofac provider to the generic hosting mechanism.
var host = Host.CreateDefaultBuilder(args)
.UseServiceProviderFactory(new AutofacServiceProviderFactory())
.ConfigureWebHostDefaults(webHostBuilder => {
webHostBuilder
.UseContentRoot(Directory.GetCurrentDirectory())
.UseIISIntegration()
.UseStartup<Startup>();
})
.Build();
host.Run();
}
}
Autofac documention:
https://docs.autofac.org/en/latest/integration/aspnetcore.html#asp-net-core-3-0-and-generic-hosting

I found this.
https://learn.microsoft.com/en-us/aspnet/core/migration/50-to-60-samples?view=aspnetcore-5.0
var builder = WebApplication.CreateBuilder(args);
builder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory());
// Register services directly with Autofac here. Don't
// call builder.Populate(), that happens in AutofacServiceProviderFactory.
builder.Host.ConfigureContainer<ContainerBuilder>(builder => builder.RegisterModule(new MyApplicationModule()));
var app = builder.Build();

At "Program.cs"
You'll find
var builder = WebApplication.CreateBuilder(args);
Add below
builder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory())
.ConfigureContainer<ContainerBuilder>(builder =>
{
builder.RegisterModule(new AutofacBusinessModule());
});
Answer above, I've assumed that you had everything else set up.
I'm using Autofac and Autofact.Extras.DynamicProxy
Sharing below my AutofacBusinessModule just for clarification.
public class AutofacBusinessModule : Module
{
protected override void Load(ContainerBuilder builder)
{
builder.RegisterType<ProductManager>().As<IProductService>().SingleInstance();
builder.RegisterType<EfProductDal>().As<IProductDal>().SingleInstance();
}
}

I have attached examples of both manual declarations and Reflection API of how to add Autofac to .NET Core 6.0
Call UseServiceProviderFactory on the Host sub-property
Call ConfigureContainer on the Host sub-property
Declare your services and their lifetime
Example of a manual services declaration
var builder = WebApplication.CreateBuilder(args);
// Call UseServiceProviderFactory on the Host sub property
builder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory());
// Call ConfigureContainer on the Host sub property
builder.Host.ConfigureContainer<ContainerBuilder>(builder =>
{
// Declare your services with proper lifetime
builder.RegisterType<AppLogger>().As<IAppLogger>().SingleInstance();
builder.RegisterType<DataAccess>().As<IDataAccess>().InstancePerLifetimeScope();
});
Example of Assembly scanning "Reflection API"
var builder = WebApplication.CreateBuilder(args);
// Call UseServiceProviderFactory on the Host sub property
builder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory());
// Call ConfigureContainer on the Host sub property
builder.Host.ConfigureContainer<ContainerBuilder>(builder =>
{
builder.RegisterAssemblyTypes(Assembly.Load(nameof(DemoLibrary))).Where(t => t.Namespace?.Contains("Practicing.Services") == true)
.As(t => t.GetInterfaces().FirstOrDefault(i => i.Name == "I" + t.Name));
});

Related

How to use AutoFac with .NET MAUI

A few years ago I created a suite of nuget packages that I use for CQRS which use AutoFac modules to wire up the internals. I'd like to use that in my .NET MAUI development so I've updated them to .NET 6.0 and they link in with my MAUI project nicely, but I'm uncertain what's missing from my registrations. My framework's AutoFac Module registers an IDateTimeService but when I add that to a registered class' constructor it can't be resolved.
So, following the AutoFac guide for .NET Core I've added the Populate call and then Load the AutoFac module.
using Autofac;
using Autofac.Extensions.DependencyInjection;
using Pages;
using Perigee.Framework.Services;
using Services;
using ViewModels;
public static class MauiProgram
{
public static MauiApp CreateMauiApp()
{
var builder = MauiApp.CreateBuilder();
builder
.UseMauiApp<App>()
.ConfigureFonts(fonts =>
{
fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold");
});
builder.Services.AddSingleton<IAppNavigationService, AppNavigationService>();
builder.Services.AddSingleton<AppShellViewModel>();
builder.Services.AddTransient<MainPageViewModel>();
builder.Services.AddSingleton<AppShell>();
builder.Services.AddSingleton<MainPage>();
// Hook in AutoFac for the PerigeeFramework services
var autofacBuilder = new ContainerBuilder();
autofacBuilder.Populate(builder.Services);
autofacBuilder.RegisterModule(new ServicesModule());
autofacBuilder.Build(); // Invokes the Load method on the registered Modules.
return builder.Build();
}
}
The AutoFac Module starts like this:
public class ServicesModule : Module
{
protected override void Load(ContainerBuilder builder)
{
builder.Register(c =>
{
var config = c.IsRegistered<IDateTimeConfig>() ? c.Resolve<IDateTimeConfig>() : null;
return new DateTimeService(config);
}).As<IDateTimeService>().InstancePerLifetimeScope();
and this is the definition of AppShellViewModel
public AppShellViewModel(IDateTimeService dateTimeService)
which is injected into the AppShell:
public partial class AppShell : Shell
{
public AppShell(AppShellViewModel viewModel)
{
InitializeComponent();
BindingContext = viewModel;
At run time the IDateTimeService doesn't resolve. I've also tried just registering with AutoFac without a module and it won't resolve:
// Hook in AutoFac for the PerigeeFramework services
var autofacBuilder = new ContainerBuilder();
autofacBuilder.Populate(builder.Services);
autofacBuilder.RegisterType<DateTimeService>().As<IDateTimeService>().SingleInstance();
var cont = autofacBuilder.Build();
return builder.Build();
}
The key reason I needed something other than .NET DI was because the architecture leverages decorators, which SimpleInjector and AutfoFac provide out of the box so I chose AutoFac. In either case I need to use this "crosswire" approach to use AutoFac and .NET DI as MAUI is using the built in one. Does anyone know what step I'm missing that is preventing the registrations from an AutoFac module from appearing in the IServiceCollection, or can I completely replace the .NET DI with AutoFac on the MauiApp?
EDIT:
I've put together a trimmed down version of my app. I figured maybe I need to pass a new AutoFacServiceProvider through to the App and the ISomeService does resolve when registered with AutoFac
But the call to MainPage = serviceProvider.GetService<AppShell>() fails to resolve if I try to inject ISomeService into another registered class. If the service is registered with the standard DI it will work.
Anyone know how to propogate the AutoFac Service Provider as the one Maui will use? The project is here
The MauiAppBuilder (called from the MauiProgram) has a method called ConfigureContainer, it takes an IServiceProvider factory that Autofac provides as AutofacServiceProviderFactory and optionally it can take an Action<ContainerBuilder> delegate, where you can define your configuration.
In your case that could look like this:
public static class MauiProgram
{
public static MauiApp CreateMauiApp()
{
var builder = MauiApp.CreateBuilder();
builder
.UseMauiApp<App>()
.ConfigureFonts(fonts =>
{
fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold");
})
.ConfigureContainer(new AutofacServiceProviderFactory(), autofacBuilder => {
// Registrations
// Don't call the autofacBuilder.Build() here - it is called behind the scenes
});
return builder.Build();
}
}

EF Core Migrations in Azure Function startup

According to https://learn.microsoft.com/en-us/azure/azure-functions/functions-dotnet-dependency-injection the service provider should not be used until AFTER the startup has completed running. Indeed, if I try to get a registered service it will fail.
Example:
[assembly: FunctionsStartup(typeof(Startup))]
namespace Fx {
public sealed class Startup : FunctionsStartup {
public override void Configure(IFunctionsHostBuilder builder) {
var configurationBuilder = new ConfigurationBuilder();
configurationBuilder.AddEnvironmentVariables();
var configuration = configurationBuilder.Build();
builder.Services.AddInfrastructure(configuration);
builder.Services.AddApplication();
var serviceProvider = builder.Services.BuildServiceProvider();
DependencyInjection.AddDatabase(serviceProvider).GetAwaiter().GetResult();
}
}
}
public static class DependencyInjection {
public static async Task AddDatabase(IServiceProvider services) {
using var scope = services.CreateScope();
var serviceProvider = scope.ServiceProvider;
var context = serviceProvider.GetRequiredService<ApplicationDbContext>();
//Error generated here
if (context.Database.IsSqlServer()) {
await context.Database.MigrateAsync();
}
await ApplicationDbContextSeed.SeedSamplePersonnelDataAsync(context);
}
public static IServiceCollection AddInfrastructure(
this IServiceCollection services,
IConfiguration configuration) {
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(configuration.GetConnectionString("DefaultConnection"),
b => b.MigrationsAssembly(typeof(ApplicationDbContext).Assembly.FullName)));
services.AddScoped<IApplicationDbContext>(provider => provider.GetService<ApplicationDbContext>());
return services;
}
}
This produces the following error
Microsoft.EntityFrameworkCore: No database provider has been configured for this DbContext. A provider can be configured by overriding the DbContext.OnConfiguring method or by using AddDbContext on the application service provider. If AddDbContext is used, then also ensure that your DbContext type accepts a DbContextOptions<TContext> object in its constructor and passes it to the base constructor for DbContext.
Is there a good option for migrating and seeding during startup?
The easiest way I found to run code after startup was by registering a custom IWebJobsStartup by using the WebJobsStartupAttribute (the FunctionsStartupAttribute actually also inherits from this attribute). In the WebJobsStartup class you'll need to register your extension using the AddExtension where you are able to use dependency injection and seed your database. My code:
[assembly: WebJobsStartup(typeof(DbInitializationService), "DbSeeder")]
namespace Our.Database.Seeder
{
public class DbInitializationService : IWebJobsStartup
{
public void Configure(IWebJobsBuilder builder)
{
builder.AddExtension<DbSeedConfigProvider>();
}
}
[Extension("DbSeed")]
internal class DbSeedConfigProvider : IExtensionConfigProvider
{
private readonly IServiceScopeFactory _scopeFactory;
public DbSeedConfigProvider(IServiceScopeFactory scopeFactory)
{
_scopeFactory = scopeFactory;
}
public void Initialize(ExtensionConfigContext context)
{
using var scope = _scopeFactory.CreateScope();
var dbContext = scope.ServiceProvider.GetService<YourDbContext>();
dbContext.Database.EnsureCreated();
// Further DB seeding, etc.
}
}
}
According to your code, I assume that you're building something aligned to the CleanArchitecture Repository on Github. https://github.com/jasontaylordev/CleanArchitecture
The main difference between this repo and your apporach, is that you're obviously not using ASP.NET, which is not a problem at all, but requires a little bit more configuration work.
The article already mentioned (https://markheath.net/post/ef-core-di-azure-functions) refers another blogpost (https://dev.to/azure/using-entity-framework-with-azure-functions-50aa), which briefly explains that EntityFramework Migrations are not capable of auto-discovering your migrations in an Azure Function. Therefore, you need to implement an instance of IDesignTimeDbContextFactory. I also stumbled upon it in the microsoft docs:
https://learn.microsoft.com/en-us/ef/core/cli/dbcontext-creation?tabs=dotnet-core-cli#from-a-design-time-factory
You could for example place it inside your Infrastructure\Persistence\Configurations folder. (Once again, I'm only assuming that you're following the CleanArchitecture repo structure)
DI in AZURE Functions
Caveats
A series of registration steps run before and after the runtime processes the startup class. Therefore, keep in mind the following items:
The startup class is meant for only setup and registration. Avoid using services registered at startup during the startup process. For instance, don't try to log a message in a logger that is being registered during startup. This point of the registration process is too early for your services to be available for use. After the Configure method is run, the Functions runtime continues to register additional dependencies, which can affect how your services operate.
The dependency injection container only holds explicitly registered types. The only services available as injectable types are what are setup in the Configure method. As a result, Functions-specific types like BindingContext and ExecutionContext aren't available during setup or as injectable types

How get register dbcontext in startup class for reference?

I configure my DbContext with dependency injection in Startup, actually i need send my registered dbcontext to my class handler (EventBusExtension.GetHandlers()) but i dont know how get directly the context registered:
public void ConfigureServices(IServiceCollection services)
{
...
var dbContextOptions = new DbContextOptionsBuilder<cataDBContext>()
.UseSqlServer(Configuration.GetConnectionString("SqlServerConnect"))
.Options;
//*****************************************************************************
services.AddSingleton(dbContextOptions);
// Finally register the DbContextOptions:
services.AddSingleton<cataDBContextOptions>();
// This Factory is used to create the DbContext from the custom DbContextOptions:
services.AddSingleton<IContextDBFactory, ContextDBFactory>();
// Finally Add the Applications DbContext:
services.AddDbContext<cataDBContext>();
services.AddEventBusHandling(EventBusExtension.GetHandlers(Configuration));
...
}
How i can get and send the context to EventBusExtension.GetHandlers() ?
For how to get the instance in the Startup,you could use the following code:
//1.Register the service
services.AddDbContext<MyDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("YourConnnectionString")));
//2.Build an intermediate service provider
var sp = services.BuildServiceProvider();
//3.Resolve the services from the service provider
var myDbContext = sp.GetService<MyDbContext>();
//4.then you could pass the myDbContext to the EventBusExtension.GetHandlers()
The accepted anser works but as mentioned in comments and this microsoft document ASP0000 calling 'BuildServiceProvider' from application code results in an additional copy of singleton services being created.
Calling BuildServiceProvider creates a second container, which can create torn singletons and cause references to object graphs across multiple containers.
A correct way to get LoginPath is to use the options pattern's built-in support for DI.
For example to use your dbContext to get all active Host URLs to apply in CORs instead of using services.AddCors(.... you can use this code :
services.AddOptions<CorsOptions>()
.Configure<ApplicationDbContext>(
(options, db) =>
{
options.AddPolicy("AllowOrigin", builder =>
builder.WithOrigins(db.Set<MyHostsEntity>().Where(e => e.IsActive).Select(e => e.Url).ToArray())
.AllowAnyMethod()
.AllowAnyHeader()
.AllowCredentials()
);
}
);

Add a self hosted SignalR server to a .Net Core Worker Service

I'm trying to extend a .NET Core Worker Service (<Project Sdk="Microsoft.NET.Sdk.Worker">) with SignalR (self hosted web app).
All the examples/tutorials/docs I have found are based on web applications, so they don't fit my case.
This is what I've done until now:
MyService Program.cs:
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.UseWindowsService()
.ConfigureServices((hostContext, services) =>
{
services.AddHostedService<Worker>();
DependencyBuilder.Build(hostContext, services); // inject the stuff I need in my service
// create a SignalR Web host
SignalRWebHostCreator.CreateHost(services, "http://localhost:8090", (endpoints) => {
endpoints.MapHub<MyHub>("/result");
});
});
}
and the class I want to use to "extend" the servie with a SignalR server application.
public class SignalRWebHostCreator
{
public static void CreateHost(IServiceCollection services, string serviceUrl, Action<IEndpointRouteBuilder> mapHubs)
{
services.AddSignalR(); // is it ok here ?
WebHost.CreateDefaultBuilder()
.UseUrls(serviceUrl)
.Configure((IApplicationBuilder app) => {
app.UseRouting();
app.Map("/check", config => { // just a test: it works!
config.Run(async context =>
{
context.Response.ContentType = "text/plain";
byte[] data = System.Text.Encoding.UTF8.GetBytes("OK");
await context.Response.Body.WriteAsync(data, 0, data.Length);
await context.Response.Body.FlushAsync();
});
});
app.UseEndpoints(endpoints =>
{
//endpoints.MapHub<ClockHub>("/hubs/clock"); // ERROR
//endpoints.MapHub<PingHub>("/ping"); // ERROR
//mapHubs(endpoints); // ERROR
});
})
.Build().Run();
}
}
(ClockHub is taken from MS example and PingHub is another simple Hub I tried to use instead of my "injected" Hubs)
It starts the web application properly and it responds properly to the url http://localhost:8090/check.
When I uncomment the calls to enpoint.MapHub() or my cusom Actions I have this error:
System.InvalidOperationException: 'Unable to find the required services. Please add all the required services by calling 'IServiceCollection.AddSignalR' inside the call to 'ConfigureServices(...)' in the application startup code.'
2nd try:
Seems like service.AddSignalR() is not doing its job, so I added this in SignalRWebHostCreator:
.Configure((IApplicationBuilder app) => {
app.ApplicationServices = services.BuildServiceProvider();
and now I have this error:
System.InvalidOperationException: 'Unable to resolve service for type 'System.Diagnostics.DiagnosticListener' while attempting to activate 'Microsoft.AspNetCore.Routing.EndpointRoutingMiddleware'.'
that at least has a callstack:
_This exception was originally thrown at this call stack:
Microsoft.Extensions.Internal.ActivatorUtilities.ConstructorMatcher.CreateInstance(System.IServiceProvider)
Microsoft.Extensions.Internal.ActivatorUtilities.CreateInstance(System.IServiceProvider, System.Type, object[])
Microsoft.AspNetCore.Builder.UseMiddlewareExtensions.UseMiddleware.AnonymousMethod__0(Microsoft.AspNetCore.Http.RequestDelegate)
Microsoft.AspNetCore.Builder.ApplicationBuilder.Build()
Microsoft.AspNetCore.Hosting.WebHost.BuildApplication()
Microsoft.AspNetCore.Hosting.WebHost.StartAsync(System.Threading.CancellationToken)
System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(System.Threading.Tasks.Task)
System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(System.Threading.Tasks.Task)
System.Runtime.CompilerServices.TaskAwaiter.GetResult()_
If I add services.AddSingleton(new System.Diagnostics.DiagnosticListener("diagnostic listener")); I can use endpoints.MapHub(..) without errors but now a call to http://8090/check returns a 500 internal error, so I don't think this is the right way to solve the issue.
I found some example using WebApp from Microsoft.Owin.hosting.
It requires Microsoft.Owin.4.1.0, Microsoft.Owin.Hosting.4.1.0 and Owin.1.0.0 and the last one require Net Framework 4.6.1, I don't want this.
I have included Microsoft.AspNetCore.Owin.3.1.2 (100% .NET Core) but that does not offer WebApp or something similar.
I started experiencing same error when I upgraded nuget EFCore package to new version.
I noticed, that in my bin directory, a System.Diagnostics.DiagnosticSource.dll appeared, while when I downgraded - it disappeared.
I suspect, that DiagnosticListener type from old assembly version is registered in DI container, while on activation newer version is expected. Or reverse - I didn't dig that deep.
My solution was to revert EFCore to match Product version of System.Diagnostics.DiagnosticSource.dll so it will not appear in bin folder.

Meta<T, TMetadata> broken in Autofac 3+?

I have some code that registers types with strongly typed metadata. It looks something like this:
class Foo { }
public interface IFooMetadata
{
int Position { get; }
}
[TestFixture]
public class MyTestFixture
{
[Test]
public void Test()
{
var builder = new ContainerBuilder();
builder.RegisterType<Foo>()
.AsSelf()
.WithMetadata<IFooMetadata>(m => m.For(x => x.Position, 1));
using (var container = builder.Build())
{
var fooWithMeta = container.Resolve<Meta<Foo, IFooMetadata>>();
}
}
}
I've just updated my code to use the current version of Autofac (3.0.2) and it seems that any types that are registered this way cannot be resolved (ComponentNotRegisteredException).
I wrote the above test and it passes with Autofac 2.6.1.841, but throws a ComponentNotRegisteredException in 3.0.2.
Am I missing something? Is Meta<T, TMetadata> still the way to go, or is there a new way to do this?
There are a lots of breaking changes in Autofac 3.0.
So the Interface Based Metadata support was moved out from the Autofac core to the MEF integration package.
So you need to get the Autofac.Mef package and call the RegisterMetadataRegistrationSources() extension method on the builder as described in the documentation.
var builder = new ContainerBuilder();
builder.RegisterMetadataRegistrationSources()
builder.RegisterType<Foo>()
.AsSelf()
.WithMetadata<IFooMetadata>(m => m.For(x => x.Position, 1));
You can read about more this breaking change in this article: Autofac 3.0 Beta packages available on NuGet