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

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

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

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

Register more than one mock or service using AutoMock.GetLoose() (Autofac.Extras.Moq)

I'm in the process of upgrading our Autofac.Extras.Moq library to the latest version (6.0.0) within our Unit Test project. After upgrading, I noticed tests using: var mock = AutoMock.GetLoose(), no longer supported the "Provide" method. So I started digging into the documentation for some sort of workaround.
After taking a look at the Getting Started docs (https://autofaccn.readthedocs.io/en/v5.2.0/integration/moq.html#getting-started) I've noticed there is a new way of registering mocks and dependent services using AutoMock.GetLoose(cfg => cfg.RegisterMock(mockA)). However, some of our tests require more than one Mock injected, and it's not clear to me how to do this.
Take for example:
[Test]
public void Test()
{
var mockA = new Mock();
mockA.Setup(x => x.RunA());
var mockB = new Mock();
mockB.Setup(x => x.RunB());
// mockA is automatically registered as providing IServiceA
using (var mock = AutoMock.GetLoose(cfg => cfg.RegisterMock(mockA)))
{
// mockA will be injected into TestComponent as IServiceA
var component = mock.Create();
// ...and the rest of the test
}
}
How would I register both mockA and mockB?
Thanks.
Have you tried putting both registrations in the GetLoose lambda?
[Test]
public void Test()
{
var mockA = new Mock();
mockA.Setup(x => x.RunA());
var mockB = new Mock();
mockB.Setup(x => x.RunB());
// Register both mocks here:
using (var mock = AutoMock.GetLoose(cfg => {
cfg.RegisterMock(mockA);
cfg.RegisterMock(mockB);
}))
{
// ...and the rest of the test
}
}
If you tried this and it didn't work, you should update your question to include both:
That you tried it AND
What the exception message or incorrect result was
Otherwise, if this works... 🎉

How to use an mvvmcross plugin such as the file plugin

I'm using mvvmcross version 6.4.1 to develop an app for IOS, Android, and WPF.
I've searched all over for my to use plugins. There seems to be no code examples. The documentation said to install the nuget in both my core and ui application projects. Which I did. Is there any special IOC registration/setup/or loading that needs to be done before I can use the plugin and how do I go about using the plugin? Do they get injected in the constructor or Do I have to manually pull them from the IOC container or new () them up.
I've installed nuget for the File plugin into my WPF UI and Core project. I added the IMvxFileStore to one of my core project's service constructor thinking it automagically gets added to the DI container, but it doesn't seem to get injected.
namespace My.Core.Project.Services
{
public class SomeService : ISomeService
{
private IMvxFileStore mvxFileStore;
public SomeService(IMvxFileStore mvxFileStore)
{
this.mvxFileStore = mvxFileStore;
}
public string SomeMethod(string somePath)
{
mvxFileStore.TryReadTextFile(somePath, out string content);
return content;
}
}
}
App.xaml.cs
using MvvmCross.Core;
using MvvmCross.Platforms.Wpf.Views;
...
public partial class App : MvxApplicatin
{
protected override void RegisterSetup()
{
this.RegisterSetupType<Setup<Core.App>>();
}
}
App.cs
using MvvmCross;
using MvvmCross.ViewModels;
using My.Core.Project.Services;
public class App: MvxApplication
{
public override void Initialize()
{
Mvx.IocProvider.RegisterType<ISomeService, SomeService>();
RegisterCustomAppStart<AppStart>();
}
}
AppStart.cs
using MvvmCross.Exceptions;
using MvvmCross.Navigation;
using MvvmCross.ViewModels;
using My.Core.Project.ViewModels;
using System;
using System.Threading.Tasks;
....
public class AppStart : MvxAppStart
{
public AppStart(IMvxApplication application, IMvxNavigationService navigationService) : base(application, navigationService)
{}
public override Task NavigateToFirstViewModel(object hint = null)
{
try {
return NavigationService.Navigate<FirstPageViewModel>();
} catch {
throw e.MvxWrap("Some error message {0}", typeof(FirstPageViewModel).Name);
}
}
}
Setup.cs in WPF project
using MvvmCross;
using MvvmCross.Base;
using MvvmCross.Platforms.Wpf.Core;
using MvvmCross.Plugin.File;
using MvvmCross.Plugin.Json;
using MvvmCross.ViewModels;
using My.Wpf.Project.Services;
...
public class Setup<T> : MvxWpfSetup
{
public Setup() : base() {}
protected override IMvxApplication CreateApp()
{
return new Core.App();
}
protected override void InitializeFirstChange()
{
base.InitializeFirstChange();
Mvx.IocProvider.RegisterType<ISomeWpfSpecificService>(() => new SomeWpfSpecificService());
}
protected override void InitializeLastChange()
{
base.InitializeLastChange();
}
}
I'm expecting my service to load but instead, I get the error message
MvxIoCResolveException: Failed to resolve parameter for parameter mvxJsonConverter of type IMvxJsonConverter
NOTE: I get the same error message for both File and Json plugin, The plugin that gets listed first in the constructor gets the error message when the app trys to load.
Am I properly using or loading the plugin?
UPDATE: I manually registered the Plugins in the UI Setup.cs and it is working but I am not sure if this is the proper way to do it.
WPF UI project Setup.cs
using MvvmCross;
using MvvmCross.Base;
using MvvmCross.Platforms.Wpf.Core;
using MvvmCross.Plugin.File;
using MvvmCross.Plugin.Json;
using MvvmCross.ViewModels;
using My.Wpf.Project.Services;
...
public class Setup<T> : MvxWpfSetup
{
public Setup() : base() {}
protected override IMvxApplication CreateApp()
{
return new Core.App();
}
protected override void InitializeFirstChange()
{
base.InitializeFirstChange();
Mvx.IocProvider.RegisterType<ISomeWpfSpecificService>(() => new SomeWpfSpecificService());
Mvx.IoCProvider.RegisterType<IMvxFileStore, MvxFileStoreBase>();
Mvx.IoCProvider.RegisterType<IMvxJsonConverter, MvxJsonConverter>();
}
protected override void InitializeLastChange()
{
base.InitializeLastChange();
}
}
Yes you are using the plugin properly and I think that for now your solution to manually register your plugin is viable.
The root of the problem is located in the MvxSetup class. This class contains the method LoadPlugins which is responsible for loading the MvvmCross plugins which are referenced by your UI project. This is how LoadPlugins determines what plugins to load:
Get all assemblies that have been loaded into the execution context of the application domain.
Find types within these assemblies which contain the MvxPluginAttribute.
Now the problem occurs in step 1. In a .NET framework project, by default, your referenced assemblies won't be loaded into the execution context until you actually use them in your code. So if you don't use something from your MvvmCross.Plugin.File reference in your UI project it won't be loaded into your execution context and it won't be found in step 1 and thus it won't be registered by LoadPlugins. (good read: when does a .NET assembly Dependency get loaded)
One way I have tested this is by doing this:
protected override void InitializeFirstChance()
{
// Because a type of the MvvmCross.Plugin.File.Platforms.Wpf reference is
// used here the assembly will now get loaded in the execution context
var throwaway = typeof(Plugin);
base.InitializeFirstChance();
}
With the above code you don't have to manually register the Plugin.
There has been a pull request to fix this in the MvvmCross framework but this has been reverted later since it caused problems on other platforms.
In other platforms the plugin assemblies will get loaded into the execution context without any tricks so I would say updating the MvvmCross documentation stating you have to register your plugin manually for WPF would be useful for other developers in the future.

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.