Autofac ServiceLocator issue in release v2.4.5.724 - autofac

I have just upgraded to MVC 3 and likewise need to upgrade Autofac.
The following code was working, but now fails with this error -
This resolve operation has already
ended. When registering components
using lambdas, the IComponentContext
'c' parameter to the lambda cannot be
stored. Instead, either resolve
IComponentContext again from 'c', or
resolve a Func<> based factory to
create subsequent components from.
public static IServiceLocator Locator;
public class ServiceA : IServiceA
{
}
public interface IServiceA
{
}
[Test]
public void TestAutofacServiceLocator()
{
// This resolve operation has already ended. When registering components using lambdas, the IComponentContext 'c' parameter to the lambda cannot be stored.
// Instead, either resolve IComponentContext again from 'c', or resolve a Func<> based factory to create subsequent components from.
var builder = new ContainerBuilder();
builder.RegisterType<ServiceA>().As<IServiceA>();
builder.Register(c => Locator = new AutofacServiceLocator(c)).As<IServiceLocator>().SingleInstance();
var container = builder.Build();
container.Resolve<IServiceLocator>();
var x = Locator.GetInstance<IServiceA>();
Assert.NotNull(x);
}
How should I resgister IServiceLocator?
I looked at the answer to question autofac registration issue in release v2.4.5.724 but I'm still confused.

I really should have read Nick's error message, the answer was in the message.
Fixed !!
builder.Register(c => Locator = new AutofacServiceLocator(c.Resolve()))
.As().SingleInstance();

Related

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

What is the Best Practice to configure StackExchange.Redis with Autofac?

In the StackExchange.Redis docs it is recommended to only create one and reuse the connection to Redis.
Azure Redis best practices recommends using the following pattern:
private static Lazy<ConnectionMultiplexer> lazyConnection = new Lazy<ConnectionMultiplexer>(() =>
{
return ConnectionMultiplexer.Connect("cachename.redis.cache.windows.net,ssl=true,abortConnect=false,password=password");
});
public static ConnectionMultiplexer Connection
{
get
{
return lazyConnection.Value;
}
}
but how should I get this working with Autofac where I want the configuration to be set in the web/app config files?
I currently have a RedisCacheProvider:
private readonly ConnectionMultiplexer _connection;
public RedisCacheProvider(string config)
{
_connection = ConnectionMultiplexer.Connect(config);
}
and in my Autofac config:
builder.RegisterType<RedisCacheProvider>().As<ICacheProvider>().WithParameter("config", "localhost");
My thinking is, I should change my RedisCacheProvider to take in a ConnectionMultiplexer which is passed in via the static variable?
Update: My Solution so far:
My RedisCacheProvider (injecting an interface here allows me to mock the connection in unit tests):
private readonly IConnectionMultiplexer _connection;
public RedisCacheProvider(IConnectionMultiplexer connection)
{
_connection = connection;
}
RedisConnection class to hold the static property and read config from config file:
public class RedisConnection
{
private static readonly Lazy<ConnectionMultiplexer> LazyConnection =
new Lazy<ConnectionMultiplexer>(
() => ConnectionMultiplexer.Connect(ConfigurationManager.AppSettings["RedisCache"]));
public static ConnectionMultiplexer Connection
{
get
{
return LazyConnection.Value;
}
}
}
Registration in an Autofac Module:
builder.RegisterType<RedisCacheProvider>().As<ICacheProvider>()
.WithParameter(new TypedParameter(
typeof(IConnectionMultiplexer),
RedisConnection.Connection));
Autofac supports Implicit Relationship Types and Lazy<> evaluation is supported out of the box.
So after you register your RedisCacheProvider as in your example, that is
builder
.RegisterType<RedisCacheProvider>()
.As<ICacheProvider>()
.WithParameter("config", "localhost");
you can resolve it like below:
container.Resolve<Lazy<ICacheProvider>>();
But do not forget that default Autofac lifetime scope is InstancePerDependency(transient). That is, you will get new instance of RedisCacheProvider everytime you resolve it or whenever it is provided to other component as dependency. To fix this you need to specify its lifetime scope explicitly. For instance, to make it singleton you need to change registration as below:
builder
.RegisterType<RedisCacheProvider>()
.As<ICacheProvider>()
.WithParameter("config", "localhost")
.SingleInstance();
Another assumption here is that RedisCacheProvider is the only component where Redis connection is used. If it is not the case then you should better let Autofac manage Redis connection's life scope (which is a better idea anyway) and get the connection as a dependency in RedisCacheProvider. That is:
public RedisCacheProvider(IConnectionMultiplexer connection)
{
this.connection = connection;
}
....
builder
.Register(cx => ConnectionMultiplexer.Connect("localhost"))
.As<IConnectionMultiplexer>()
.SingleInstance();
builder
.RegisterType<RedisCacheProvider>()
.As<ICacheProvider>();

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

autofac registration issue in release v2.4.5.724

I have the following registration
builder.Register<Func<Type, IRequestHandler>>(
c => request => (IRequestHandler)c.Resolve(request));
Basically I am trying to register a factory method that resolves an instance of IRequestHandler from a given type.
This works fine until the version 2.4.3.700. But now I am getting a the following error..
Cannot access a disposed object.
Object name: 'This resolve operation has already ended. When
registering components using lambdas,
the IComponentContext 'c' parameter to
the lambda cannot be stored. Instead,
either resolve IComponentContext again
from 'c', or resolve a Func<> based
factory to create subsequent
components from.'.
UPDATE
I was trying to limit autofac's exposure to the rest of the projects in the solution. Nick, thanks for the hint, now my registration looks like this...
builder.Register<Func<Type,IRequestHandler>>(c =>
{
var handlers = c.Resolve<IIndex<Type,RequestHandler>>();
return request => handlers[request];
});
The c in this expression is a temporary, so this code while previously functional, is broken. Autofac 2.4.5 detects this problem while earlier versions silently ignored it.
To fix the issue, explicitly resolve IComponentContext:
builder.Register<Func<Type, IRequestHandler>>(c => {
var ctx = c.Resolve<IComponentContext>();
return request => (IRequestHandler)ctx.Resolve(request));
});
The functionality you're emulating here might be better represented using keys and indexes, e.g. see Interrupted chain of IoC or http://code.google.com/p/autofac/wiki/TypedNamedAndKeyedServices.
I had a similar problem as the user6130. I wanted to avoid using IIndex in my class implementation and pass in a service resolver into my constructor instead.
So now I have my service implementation with the following constructor:
public MvcMailer(Converter<string, MailerBase> mailerResolver)
{
_resolver = mailerResolver;
}
I wanted to used keyed services without directly relying on the Autofac namespace. I was getting the same error until I restructured the configuration as such.
1) Scan for all my mailer implementations and index via class name (could be improved)
builder.RegisterAssemblyTypes(System.Reflection.Assembly.GetExecutingAssembly())
.Where(t => t.Name.EndsWith("Mailer")).Keyed<Mvc.Mailer.MailerBase>(t => t.Name.Replace("Mailer", "").ToLower());
2) Register the converter in Autofac config
builder.Register<System.Converter<string,Mvc.Mailer.MailerBase>>(c => {
var all = c.Resolve<Autofac.Features.Indexed.IIndex<string,Mvc.Mailer.MailerBase>>();
return delegate(string key)
{
return all[key];
};
});
3) Register like other types of components and let Autofac handle the Converter injection
builder.RegisterType<Mailers.MvcMailer>().As<Mailers.IMailer>();