Managing Lifetime Scopes with Autofac - c#

Context: Owin (self-host) + WebApi + UseAutofacMiddleware + UseAutofacWebApi
What I'm trying to do is:
Register an ILog instance in the app startup container.
For each request, register a new ILog instance wrapping the "root" instance, so that each middleware and/or per-request services can use it.
var containerBuilder = new ContainerBuilder();
containerBuilder.RegisterInstance(log).As<ILog>();
containerBuilder.Register(ctx => {
var rootLog = ctx.Resolve<ILog>();
return new PrependStringLog(rootLog, "request: ");
}).InstancePerRequest();
However, Autofac complains about circular dependencies when instancing middleware having an ILog in their constructors.
If I name the "root log", and resolve with the given name, everything works as expected.
containerBuilder.RegisterInstance(log)
.Named("root", typeof(ILog));
containerBuilder.Register(ctx => {
var rootLog = ctx.ResolveNamed<ILog>("root");
return new PrependStringLog(rootLog, "request: ");
}).InstancePerRequest();
Am I forced to use a named instance to make it work?

Autofac uses the latest registered service when a component request for a service.
In your case, the latest ILog registered is a lambda expression :
containerBuilder.Register(ctx => {
var rootLog = ctx.Resolve<ILog>();
return new PrependStringLog(rootLog, "request: ");
}).InstancePerRequest();
This lambda expression request an ILog which is what Autofac is trying to build : that's why it detects a circular dependency.
The easiest way to avoid the circular dependency is to make your registration not rely on itself. This is what you do by resolving a named ILog and this is the solution I recommend.
In your case, you can also directly use the root log without resolving it :
containerBuilder.RegisterInstance(rootLog).As<ILog>();
containerBuilder.Register(ctx => {
return new PrependStringLog(rootLog, "request: ");
}).InstancePerRequest();

Related

Getting an instance of BusHealth using unity container

How to get an instance of bushealth using Unity container IoC in Prism or without using a IoC container? Mass Transit relies on Microsoft Dependency Injection, however I am using Bus Factory to create an instance of the bus and its always not feasbile to use DI for legacy code.
_busControl = Bus.Factory.CreateUsingRabbitMq(sbc =>
{
sbc.Host(host, h =>
{
h.Username(username);
h.Password(password);
h.RequestedConnectionTimeout(timeout);
});
sbc.ReceiveEndpoint(e =>
{
...
});
You can either create an instance of BusHealth passing the bus instance on the constructor or you can register IBusHealth as a service in your container, with the BusHealth implementation type (which will require IBus to also be registered, as it is a required constructor dependency).
Then you can use the IBusHealth interface in your application to check bus health.
var busHealth = new BusHealth("bus");
_busControl = Bus.Factory.CreateUsingRabbitMq(sbc =>
{
sbc.ConnectBusObserver(busHealth);
sbc.ConnectEndpointConfigurationObserver(busHealth);
sbc.Host(host, h =>
{
h.Username(username);
h.Password(password);
h.RequestedConnectionTimeout(timeout);
});
sbc.ReceiveEndpoint(e =>
{
// ...
});
});
Of course, this doesn't explain how the bus/health objects get into your container, but I think you get the idea.

Can Cosmos SDK3 Container be a singleton?

I would like to reduce load on Azure Cosmos DB SQL-API, which is called from a .NET Core Web API with dependency injection.
In App Insights, I have noticed that every call to the Web API results in GetDatabase and GetCollection calls to Cosmos which can take 5s to run when Cosmos is under heavy load.
I have made CosmosClient a singleton (e.g advice here - https://learn.microsoft.com/en-us/azure/cosmos-db/performance-tips-dotnet-sdk-v3-sql)
However I could not find any advice for whether the Database or Container objects could also be singletons so these are created for each request to the Web API.
I check for the existence of the database and collection (e.g. following advice here - https://learn.microsoft.com/en-us/dotnet/api/microsoft.azure.cosmos.cosmosclient.getdatabase?view=azure-dotnet#remarks and https://learn.microsoft.com/en-us/dotnet/api/microsoft.azure.cosmos.cosmosclient.getcontainer?view=azure-dotnet#remarks)
This means that for every request to the Web API, the following code is run
var databaseResponse = await this.cosmosClient.CreateDatabaseIfNotExistsAsync(
this.databaseConfiguration.DatabaseName,
throughput: this.databaseConfiguration.DatabaseLevelThroughput);
var database = databaseResponse.Database;
var containerResponse = await database.CreateContainerIfNotExistsAsync(containerId, partitionKey);
var container = containerResponse.Container;
Can I make Database and Container singletons and add them to the DI to be injected like CosmosClient in order to reduce the number of calls to GetDatabase and GetCollection seen in App Insights?
According to the latest Microsoft documentation, you create a CosmosClient Service singleton, which owns the Containers you will be working with. In affect making the Containers singletons as well.
First, make your interface contract:
public interface ICosmosDbService
{
// identify the database CRUD operations you need
}
Second, define your Service based on the contract:
public class CosmosDbService : ICosmosDbService
{
private Container _container;
public CosmosDbService(CosmosClient dbClient, string databaseName, string containerName)
{
this._container = dbClient.GetContainer(databaseName, containerName);
}
// your database CRUD operations go here using the Container field(s)
}
Third, create a method in your Startup class to return a CosmosClient:
private static async Task<CosmosDbService> InitializeCosmosClientAsync(IConfigurationSection cosmosConfig)
{
var databaseName = cosmosConfig.GetSection("DatabaseName").Value;
var containerName = cosmosConfig.GetSection("ContainerName").Value;
var account = cosmosConfig.GetSection("Account").Value;
var key = cosmosConfig.GetSection("Key").Value;
var client = new Microsoft.Azure.Cosmos.CosmosClient(account, key);
var database = await client.CreateDatabaseIfNotExistsAsync(databaseName);
await database.Database.CreateContainerIfNotExistsAsync(containerName, "/id");
return new CosmosDbService(client, databaseName, containerName);
}
Finally, add your CosmosClient to the ServiceCollection:
public void ConfigureServices(IServiceCollection services)
{
var cosmosConfig = this.Configuration.GetSection("CosmosDb");
var cosmosClient = InitializeCosmosClientAsync(cosmosConfig).GetAwaiter().GetResult();
services.AddSingleton<ICosmosDbService>(cosmosClient);
}
Now your CosmosClient has only been created once (with all the Containers), and it will be reused each time you get it through Dependency Injection.
You don't need to call CreateIfNotExistsAsync every time, if you know that they are available, you can use CosmosClient.GetContainer(dbName, containerName) which is a lightweight proxy class.
Unless you are expecting the database and containers to be deleted dynamically at some point?
CreateDatabaseIfNotExistsAsync should only be called once as it is just a setup step for DB configuration.
You'd better create a DbService to persist the container object. And inject the DbService into each services instead of the DB client

SimpleInjector Lazy in a Reflection

We are using SimpleInjector as a Dependency Injector, and we are registering all interface types using assembly iteration.
public static void RegisterInterfaceTypes(this Container container, Assembly assembly)
{
assembly.GetExportedTypes()
.Select(t => new {
Type = t,
Interface = t.GetInterfaces().FirstOrDefault()
})
.ToList()
.ForEach(t =>
{
container.Register(t.Interface, t.Type, Lifestyle.Transient);
});
}
We also have lazy classes to register. We can register these classes like below one by one. But we want to register all lazy types with similar iteration using reflection.
container.Register(() => new Lazy<ICommonBusiness>(container.GetInstance<CommonBusiness>));
You can make use of the ResolveUnregisteredType extension method to make last-minute registrations for resolve Lazy<T> dependencies:
Source:
public static void AllowResolvingLazyFactories(this Container container)
{
container.ResolveUnregisteredType += (sender, e) =>
{
if (e.UnregisteredServiceType.IsGenericType &&
e.UnregisteredServiceType.GetGenericTypeDefinition() == typeof(Lazy<>))
{
Type serviceType = e.UnregisteredServiceType.GetGenericArguments()[0];
InstanceProducer registration = container.GetRegistration(serviceType, true);
Type funcType = typeof(Func<>).MakeGenericType(serviceType);
Type lazyType = typeof(Lazy<>).MakeGenericType(serviceType);
var factoryDelegate = Expression.Lambda(funcType, registration.BuildExpression()).Compile();
var lazyConstructor = (
from ctor in lazyType.GetConstructors()
where ctor.GetParameters().Length == 1
where ctor.GetParameters()[0].ParameterType == funcType
select ctor)
.Single();
var expression = Expression.New(lazyConstructor, Expression.Constant(factoryDelegate));
var lazyRegistration = registration.Lifestyle.CreateRegistration(
serviceType: lazyType,
instanceCreator: Expression.Lambda<Func<object>>(expression).Compile(),
container: container);
e.Register(lazyRegistration);
}
};
}
Usage:
container.AllowResolvingLazyFactories();
But please note the warnings from the documentation:
Warning: Registering [Lazy<T>] by default is a design smell. The use of [Lazy<T>] makes your design harder to follow and your system harder to maintain and test. Your system should only have a few of those [...] at most. If you have many constructors in your system that depend on a [Lazy<T>], please take a good look at your dependency strategy. The following article goes into details about why [this is] a design smell.
Warning: [...] the constructors of your components should be simple, reliable and quick (as explained in this blog post by Mark Seemann). That would remove the need for lazy initialization. For more information about creating an application and container configuration that can be successfully verified, please read the How To Verify the container’s configuration.

Using two Simple Injector container instances with different lifestyles in c#

I have a web forms application in the Global.asax of which I am buiding the Simple Injector container like below. The reason I am doing two is because I am using Hangfire to schedule recurring jobs and it does not take the Scoped lifestyle which I currently have for the application since it runs as a background worked thread. I am getting the below error when I am creating two instances of the container for my EF entities.
The relationship between the two objects cannot be defined because they are attached to different ObjectContext objects
Can someone please tell me how I can have two containers with different lifestyles registered in my web forms applictaion.
ContainerConfig.BuildContainer();
var container = ContainerConfig.BuildContainerJobs();
public static Container BuildContainer()
{
var container = new Container();
container.Options.DefaultScopedLifestyle = new ExecutionContextScopeLifestyle();
container.Register<TraceTimer>(Lifestyle.Scoped);
container.Register<Entities>(() => new Entities(), Lifestyle.Scoped);
container.Register<ReferenceDataCache>(
() => ReferenceDataCacheFactory.Create(), Lifestyle.Scoped);
var adapter = new SimpleInjectorAdapter(container);
ServiceLocator.SetLocatorProvider(() => (IServiceLocator)adapter);
ExecutionContextScopeManager.Current = (IExecutionContextScopeManager)adapter;
return container;
}
public static Container BuildContainerJobs()
{
var container = new Container();
container.Options.DefaultScopedLifestyle = new ExecutionContextScopeLifestyle();
container.Register<Entities>(() => new Entities(), Lifestyle.Transient);
container.Register<ReferenceDataCache>(
() => ReferenceDataCacheFactory.Create(), Lifestyle.Transient);
var adapter = new SimpleInjectorAdapter(container);
ServiceLocator.SetLocatorProvider(() => (IServiceLocator)adapter);
ExecutionContextScopeManager.Current = (IExecutionContextScopeManager)adapter;
return container;
}
Global.asax code for registering
ContainerConfig.BuildContainer();
var container = ContainerConfig.BuildContainerJobs();
var options = new SqlServerStorageOptions
{
QueuePollInterval = TimeSpan.FromMinutes(5) // Default value
};
GlobalConfiguration.Configuration
.UseSqlServerStorage("Jobs",options);
GlobalConfiguration.Configuration.UseDefaultActivator();
GlobalConfiguration.Configuration.UseActivator(new SimpleInjectorJobActivator(container));
GlobalJobFilters.Filters.Add(new SimpleInjectorAsyncScopeFilterAttribute(container));
JobsHelper.SetRecurringJob();
_backgroundJobServer = new BackgroundJobServer();
This exception is not thrown by Simple Injector but by Entity Framework. This exception is typically caused by using instances of entities that are created with one DbContext inside another DbContext.
Unfortunately I can't be more specific and pinpoint where you are going wrong and how to fix this, because your question does not contain the appropriate details.

Autofac WCF registration exception with svc-less service

I'm trying to set up Autofac as my DI container for a new WCF project I am working on. We're working with a svc-less configuration and self hosting. Without Autofac and simply using poor-man's DI, everything is working exactly as desired, but when I add Autofac into the mix, something is going awry.
The code I am using to set everything up is:
public class CustomServiceHostFactory : ServiceHostFactory
{
protected override ServiceHost CreateServiceHost(Type serviceType, Uri[] baseAddresses)
{
var container = InitializeDIContainer();
var customHost = new CustomServiceHost(serviceType, baseAddresses);
// Exception is being thrown on the line below
customHost.AddDependencyInjectionBehavior(serviceType, container);
return customHost;
}
private static IContainer InitializeDIContainer()
{
var builder = new ContainerBuilder();
builder.RegisterAssemblyTypes(typeof (AccountRepository).Assembly)
.Where(t => t.Name.EndsWith("Repository"))
.As(t => t.GetInterfaces().FirstOrDefault(
i => i.Name == "I" + t.Name));
builder.RegisterAssemblyTypes(typeof (AccountService).Assembly)
.Where(t => t.Name.EndsWith("Service"))
.As(t => t.GetInterfaces().FirstOrDefault(
i => i.Name == "I" + t.Name));
builder.RegisterType<tktktktkDbContext>().As<IDataContextAsync>();
builder.RegisterType<UnitOfWork>().As<IUnitOfWorkAsync>();
var container = builder.Build();
return container;
}
}
When this runs, I am getting the following exception:
An exception of type 'System.ArgumentException' occurred in Autofac.Integration.Wcf.dll but was not handled in user code.
Additional information: The service contract type 'tktktktk.Services.AccountClassService' has not been registered in the container.
When I put a breakpoint in the code and inspect the container, I can see all of my services, including the 'tktktktk.Services.AccountClassService' object, in the ComponentRegistry.Registrations collection.
I've tried reworking my web.config file to use the "Autofac.Integration.Wcf.AutofacServiceHostFactory" factory, but then my application fails before it even gets to this point.
I'm thinking that I missed a step somewhere, but am at a loss for what. Any assistance would be greatly appreciated!
UPDATE
I modified my web.config to use "Autofac.Integration.Wcf.AutofacServiceHostFactory" as indicated. I am now getting the following error:
WebHost failed to process a request.
Sender Information: System.ServiceModel.ServiceHostingEnvironment+HostingManager/45653674
Exception: System.ServiceModel.ServiceActivationException: The service '/Account/AccountClassService.svc' cannot be activated due to an exception during compilation. The exception message is: The AutofacServiceHost.Container static property must be set before services can be instantiated.. ---> System.InvalidOperationException: The AutofacServiceHost.Container static property must be set before services can be instantiated.
UPDATE 2
I tried what is suggested in the answer below, and I got the same error as above. One thing I noted, When I added AutofacServiceHost.Container = container to my code, that would not compile. I switched that to AutofacHostFactory.Container and it compiled fine. Also, with the web.config changed to use Autofac.Integration.Wcf.AutofacServiceHostFactory, I no longer hit any breakpoints in this code, suggesting that it is being bypassed completely now.
I also attempted svc-less configuration. I followed the steps in the documentation and received this error message:
The service 'MyService' configured for WCF is not registered with the Autofac container.
It turns out everything was registered correctly however the "add factory" element in web.config needs the assembly name of the service just as the .svc file would if it existed:
<add factory="Autofac.Integration.Wcf.AutofacServiceHostFactory" relativeAddress="~/MyService.svc" service="NameSpace.Service, AssemblyName" />
The Autofac documentation does not mention this or show it in the example. Also, Visual Studio will complain and underline the attribute however the service will run correctly.
Here's the situation you're using your custom host, on the other hand the Autofac have it's builtin ServiceHost provided as out of the box feature.
You need to set the property Container of AutofacServiceHost class:
public class CustomServiceHostFactory : ServiceHostFactory
{
protected override ServiceHost CreateServiceHost(Type serviceType, Uri[] baseAddresses)
{
var container = InitializeDIContainer();
// Add this line and try again.
AutofacServiceHost.Container = container;
var customHost = new CustomServiceHost(serviceType, baseAddresses);
customHost.AddDependencyInjectionBehavior(serviceType, container);
return customHost;
}
private static IContainer InitializeDIContainer()
{
var builder = new ContainerBuilder();
builder.RegisterAssemblyTypes(typeof (AccountRepository).Assembly)
.Where(t => t.Name.EndsWith("Repository"))
.As(t => t.GetInterfaces().FirstOrDefault(
i => i.Name == "I" + t.Name));
builder.RegisterAssemblyTypes(typeof (AccountService).Assembly)
.Where(t => t.Name.EndsWith("Service"))
.As(t => t.GetInterfaces().FirstOrDefault(
i => i.Name == "I" + t.Name));
builder.RegisterType<tktktktkDbContext>().As<IDataContextAsync>();
builder.RegisterType<UnitOfWork>().As<IUnitOfWorkAsync>();
var container = builder.Build();
return container;
}
}
I ran into this problem myself. I managed to solve this by using the Named style registration
```
builder.RegisterType<YourServiceType>()
.Named<object>("myservice");
//and in your config:..
<serviceHostingEnvironment multipleSiteBindingsEnabled="true" >
<serviceActivations>
<add relativeAddress="~/serviceurl.svc" service="myservice" factory="Autofac.Integration.Wcf.AutofacServiceHostFactory"/>
</serviceActivations>
</serviceHostingEnvironment>
```
Note that in my case i am not using apsnetcompatibilitymode.
I think its a bug in the AutofacHostFactory, its using the wrong servicename identifier to resolve the wcfservice type.
Using the Named registration style, your basically making sure that the type the registration is known under and the name used by the AutofacHostFactory match up

Categories

Resources