How to use AutoFac with .NET MAUI - autofac

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

Related

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

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

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

Shiny.SpeechRecognition (Plugin) integration with MvvmCross and Xamarin.Forms

I have implemented the Shiny Speech Recognition plugin in a MvvmCross Xamarin.Forms solution.
=> https://github.com/shinyorg/shiny
Nuget => https://www.nuget.org/packages/Shiny.SpeechRecognition/1.0.0.357-beta
I'm trying to use the ISpeechRecognition in the MvxViewModel constructor as it work on the Prism Sample on GitHub.
=> https://github.com/shinyorg/shinysamples/tree/master/Samples/Speech
I have implemented The integration plugin for MvvmCross.
=> https://www.nuget.org/packages/Shiny.Integrations.MvvmCross/1.0.0.375-beta
Now I want to know how I can use the Speech Recognition service.
Can I have something like:
public YourViewsModel(IMvxNavigationService navigationService, ISpeechRecognizer speechRecognizer)
{
_navigationService = navigationService;
_speechRecognizer = speechRecognizer;
}
Or maybe dependency injection with the register dependence like:
Mvx.IoCProvider.LazyConstructAndRegisterSingleton<ISpeechRecognizer, SpeechRecognizerImpl>();
What I want to know, if someone have already Shiny plugin Xamarin.Forms is how used the service ?
I have create a startup Shiny class.
public class InitializeShiny : ShinyStartup
{
public override void ConfigureServices(IServiceCollection services)
{
services.UseSpeechRecognition(); // implement Speech Recognition service.
}
}
Then initialize a top level custom Application definition.
In the OnCreate() method:
// Initialisation Shiny Plugin.
AndroidShinyHost.Init(Application, new InitializeShiny(), services =>
{
services.UseSpeechRecognition();
});
So now in our ViewModel we can inherit from ShinyMvxViewModel to start the ViewModel.
public class YourViewModel : ShinyMvxViewModel {}
All is working perfectly. The application is launched without problem.
This doesn't work:
public YourViewsModel(IMvxNavigationService navigationService, ISpeechRecognizer speechRecognizer)
I can create the constructor because it doesn't recognize ISpeechRecognizer.
Only
public YourViewsModel(IMvxNavigationService navigationService)
is working.
This doesn't work too:
private ISpeechRecognizer _speechRecognizer;
_speechRecognizer = Mvx.IoCProvider.Resolve<ISpeechRecognizer>();
The error is it doesn't have implemention for ISpeechRecognizer. I don't know.
I have initialise the lazy register in the override method.
protected override void InitializePlatformServices()
Mvx.IoCProvider.LazyConstructAndRegisterSingleton<ISpeechRecognizer, SpeechRecognizerImpl>();
Thank for the help.
Zebiphire

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

Converting From Castle Windsor To StructureMap In An MVC2 Project

I am learning about best practices in MVC2 and I am knocking off a copy of the "Who Can Help Me" project (http://whocanhelpme.codeplex.com/) off Codeplex. In it, they use Castle Windsor for their DI container. One "learning" task I am trying to do is convert this subsystem in this project to use StructureMap.
Basically, at Application_Start(), the code news up a Windsor container. Then, it goes through multiple assemblies, using MEF, in ComponentRegistrar.cs:
public static class ComponentRegistrar
{
public static void Register(IContainer container)
{
var catalog = new CatalogBuilder()
.ForAssembly(typeof(IComponentRegistrarMarker).Assembly)
.ForMvcAssembly(Assembly.GetExecutingAssembly())
.ForMvcAssembliesInDirectory(HttpRuntime.BinDirectory, "CPOP*.dll") // Won't work in Partial trust
.Build();
var compositionContainer = new CompositionContainer(catalog);
compositionContainer
.GetExports<IComponentRegistrar>()
.Each(e => e.Value.Register(container));
}
}
and any class in any assembly that has an IComponentRegistrar interface will get its Register() method run.
For example, the controller registrar's Register() method implementation basically is:
public void Register(IContainer container)
{
Assembly.GetAssembly(typeof(ControllersRegistrarMarker)).GetExportedTypes()
.Where(IsController)
.Each(type => container.AddComponentLifeStyle(
type.Name.ToLower(),
type,
LifestyleType.Transient ));
}
private static bool IsController(Type type)
{
return typeof(IController).IsAssignableFrom(type);
}
Hopefully, I am not butchering WCHM too much. I am wondering how does one do this with StructureMap? I'm assuming that I use Configure() since Initialize() resets the container on each call? Or, is tit a completely different approach? Do I need the MEF-based assembly scan, used to find all registrars and run each Register(), or is there something similar in StructureMap's Scan()?
Have a look at StructureMap's registries (http://structuremap.github.com/structuremap/RegistryDSL.htm). To control the lifecycle use something like:
For<ISomething>().Use<Something>().LifecycleIs(new SingletonLifecycle());
(Transient is the default).
When you bootstrap the container you can say:
ObjectFactory.Initialize(c => c.Scan(s => {
s.WithDefaultConventions();
s.LookForRegistries();
}
Feel dirty, answering my own question, but I did the following:
public class ControllerRegistrar : IComponentRegistrar
{
public void Register(IContainer container)
{
container.Configure(x =>
{
x.Scan(scanner =>
{
scanner.Assembly(Assembly.GetExecutingAssembly());
scanner.AddAllTypesOf<IController>().NameBy(type => type.Name.Replace("Controller", ""));
});
});
}
}
I am not 100% sure this is right, but it works. Pulled it primarily from the "Registering Types by Name" section of this StructureMap doc page.