Try to understand what lifetime scope means in IoC (with Autofac)? - c#

At first I did confidentially suppose that I could understand it, but via some simple example with Autofac, it appeared that I might understand it wrong, here is the code that I've tried:
//register the service
autofacBuilder.RegisterType<MyService>()
.As<IMyService>().InstancePerLifetimeScope();
//testing code
void _test1()
{
var myService = autofacContainer.Resolve<IMyService>();
}
void _test2()
{
_test1();
var myService = autofacContainer.Resolve<IMyService>();
}
Test it by running _test2() and you can simply check the instances resolved in the 2 methods.
So with the code above, I understand the myService in _test1 and myService in _test2 should be different. Because I think the lifetime scope of myService in _test1 should be just in that method while the lifetime scope of myService in _test2 should be in _test2 as well. So we have 2 different scopes here, but somehow the resolved instances of myService are the same one.
So could you please explain that issue to me, what does lifetime scope exactly mean here? inside one same class? or something even larger?

You're confusing c# scopes and autofac's scopes. It's like comparing apples and a fence. :) They are just different and have nothing to do with each other.
So, to clarify it please look at basic examples below. Please pay attention that the scopes should actually be destroyed by you if you are the one who started them as it is done in example 1. In other examples I skipped that for brevity.
// example 1
autofacBuilder.RegisterType<MyService>().As<IMyService>().InstancePerLifetimeScope();
var container = autofacBuilder.Build();
void _test1(IComponentContext scope){
var myService = scope.Resolve<IMyService>();
}
void _test2(IComponentContext scope){
// the same scope is used here and in _test1()
// this means that the service instance will be the same here and there
_test1(scope);
var myService = scope.Resolve<IMyService>();
}
// it's only here that DI lifetime scope starts
using (var scope = container.BeginLifetimeScope()) {
_test2(scope);
}
// example 2
autofacBuilder.RegisterType<MyService>().As<IMyService>().InstancePerLifetimeScope();
var container = autofacBuilder.Build();
void _test1(IComponentContext scope){
var myService = scope.Resolve<IMyService>();
}
void _test2(IComponentContext scope){
// now new scope is used in _test1() call
// this means that instances will be different here and there since they are resolved from different scopes
_test1(scope.BeginLifetimeScope());
var myService = scope.Resolve<IMyService>();
}
var scope = container.BeginLifetimeScope();
_test2(scope);
// example 3
// NOTE THIS!
autofacBuilder.RegisterType<MyService>().As<IMyService>().InstancePerDependency();
var container = autofacBuilder.Build();
void _test1(IComponentContext scope){
var myService = scope.Resolve<IMyService>();
}
void _test2(IComponentContext scope){
// the same scope is used here and in _test1()
// but now service instances will be different even though they are resolved from the same scope
// since registration directs to create new instance each time the service is requested.
_test1(scope);
var myService = scope.Resolve<IMyService>();
}
var scope = container.BeginLifetimeScope();
_test2(scope);
// example 4
autofacBuilder.RegisterType<MyService>().As<IMyService>().SingleInstance();
var container = autofacBuilder.Build();
void _test0(IComponentContext scope){
var myService = scope.Resolve<IMyService>();
}
void _test1(IComponentContext scope){
_test0(scope.BeginLifetimeScope());
var myService = scope.Resolve<IMyService>();
}
void _test2(IComponentContext scope){
// all the scopes used here and in other calls are different now
// but the service instance will be the same in all of them even though it is requested from different scopes
// since registration directs to get the same instance each time the service is requested regardless of the lifetime scope.
_test1(scope.BeginLifetimeScope());
var myService = scope.Resolve<IMyService>();
}
var scope = container.BeginLifetimeScope();
_test2(scope);

Related

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

Why does DI return the same DbContext for two different scopes?

I'm trying to add a test to an ASP.NET Core project where an object is created in one scope and then read in another scope. This is to simulate a user creating an object in one POST request and then reading it in another GET Request. However, I'm having trouble properly simulating this scenario.
I have this in my test code
SomeDbContext firstContext;
bool isSame;
using (var scope = someServiceProvider.CreateScope()) {
firstContext = someServiceProvider.GetService<SomeDbContext>();
}
using (var scope = someServiceProvider.CreateScope()) {
var secondContext = someServiceProvider.GetService<SomeDbContext>();
isSame = firstContext == secondContext; //should be false, right?
}
I expect isSame to have a value of false when the code above executes but it's actually true. Why is that? SomeDbContext has a lifetime of scoped when registering it with AddDbContext() so it should be destroyed when its scope is disposed and recreated in the second scope.
Your test is incorrect. Although you are creating two separate scopes, you're not actually using them. Here's a working version:
SomeDbContext firstContext;
bool isSame;
using (var scope = someServiceProvider.CreateScope()) {
firstContext = scope.ServiceProvider.GetService<SomeDbContext>();
}
using (var scope = someServiceProvider.CreateScope()) {
var secondContext = scope.ServiceProvider.GetService<SomeDbContext>();
isSame = firstContext == secondContext; //should be false, right?
}
Note how scope.ServiceProvider is used instead of someServiceProvider when resolving dependencies.
The closest thing I can find in the docs is Call services from main. Although the example shows the Main method, it does also demonstrate how the IServiceProvider that gets used comes from the scope itself.

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.

Why is Structuremap's Transient lifecycle treated like ContainerScoped on nested containers?

I have the following class declaration and unit test:
public class Blah { }
[TestMethod]
public void TestMethod1()
{
var container = new Container();
var instance1 = container.GetInstance<Blah>();
var instance2 = container.GetInstance<Blah>();
var areBothInstancesSame = instance1 == instance2;
var nested = container.GetNestedContainer();
var nestedInstance1 = nested.GetInstance<Blah>();
var nestedInstance2 = nested.GetInstance<Blah>();
var areBothNestedInstancesSame = nestedInstance1 == nestedInstance2;
}
When I run this test, areBothInstancesSame is false but areBothNestedInstancesSame is true.
I also tested this inside a Web Api controller action:
public class Blah { }
public IHttpActionResult GetBlah()
{
var scope = this.Request.GetDependencyScope();
var instance1 = (Blah)scope.GetService(typeof(Blah));
var instance2 = (Blah)scope.GetService(typeof(Blah));
var areBothInstancesSame = instance1 == instance2;
return this.Ok();
}
And again, areBothInstancesSame is true.
I see this described in Structuremap's documentation, so I believe it's working as intended, but I don't understand why this is intended or how to get the nested container that Web Api automatically creates to return a new instance for each service with a Transient lifecycle.
Can anyone help me understand: 1) why this is the intended default behavior and how to make the nested container return a new instance every time; or 2) why it's obvious that I should never want the nested container to return a new instance every time?
Thanks
The best answer I can give is that the word nested refers rather to container's services and not necessarily to container hierarchy as it may seem (that is why child containers exist also)
Getting a service instance from a normal container will create a new instance along with the full object graph, with all required nested services inside. No mater how may times some transient service is nested inside the object graph, only one instance is created for that service type and reused within the entire graph.
For a nested container the transient instances behave as they belong to(are nested inside) the same object graph because it's purpose is to be used within one logical request.
maybe this example will help with the usage of nested containers http://structuremap.github.io/the-container/nested-containers/#sec5
basically nested containers exist to ensure transient services will not get a new instance with each GetService call.
To make a nested container return a new instance every time you should register the service as AlwaysUnique

Creating a new instance of an object each time method is called

Messenger.Default.Register<OpenWindowMessage>(this, message =>
{
var adventurerWindowVM = SimpleIoc.Default.GetInstance<AdventurerViewModel>();
adventurerWindowVM.Adv = message.Argument;
var adventurerWindow = new AdventurerView()
{
DataContext = adventurerWindowVM
};
adventurerWindow.Show();
});
This code is fairly simple; it just opens a new window and sets the DataContext of the new window. The problem I'm having is that if I execute this twice, the content of the first instance will be overwritten and be set to that of the second since adventurerWindowVM is the DataContext of both windows and it is overwritten each time this code is called. I'm looking for a way to prevent this; I'd like to be able to open multiple windows using this message and have each of them be unique, but thus far I haven't figured out a way to do so. Any advice would be greatly appreciated. I apologize for the vague title; I was unsure of what to name this question. (Also, I know that this isn't a method. What would this block of code be called?)
Update: I'm using MVVM Light and my code is based off of an example somebody provided for me in this answer: https://stackoverflow.com/a/16994523/1667020
Here is some code from my ViewModelLocator.cs
public ViewModelLocator()
{
_main = new MainViewModel();
ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);
SimpleIoc.Default.Register<GameViewModel>();
SimpleIoc.Default.Register<AdventurerViewModel>();
}
Having given the other answer, I guess I can say the IoC container used here is just SimpleIoC from MvvmLight and to get a new instance of the VM on every GetInstance(...) all you need to do is pass in a unique key every time when trying to resolve an instance of the VM.
So you can switch
var adventurerWindowVM = SimpleIoc.Default.GetInstance<AdventurerViewModel>();
to
var adventurerWindowVM = SimpleIoc.Default.GetInstance<AdventurerViewModel>(System.Guid.NewGuid().ToString());
However as mentioned by the author of MVVMLight Here these VM's will get cached and we need to get rid of them when no longer needed. In your case probably when the Window is closed.
Thus I'd have that entire lambda something like:
Messenger.Default.Register<OpenWindowMessage>(this, message =>
{
var uniqueKey = System.Guid.NewGuid().ToString();
var adventurerWindowVM = SimpleIoc.Default.GetInstance<AdventurerViewModel>(uniqueKey);
adventurerWindowVM.Adv = message.Argument;
var adventurerWindow = new AdventurerView()
{
DataContext = adventurerWindowVM
};
adventurerWindow.Closed += (sender, args) => SimpleIoc.Default.Unregister(uniqueKey);
adventurerWindow.Show();
});
Note:
While this is somewhat longer 3 lines compared to just creating a new VM yourself with (new AdventurerViewModel()) I still favor this because if you use an IoC container to manage LifeTime of your VM's, then have it manage them completely. Don't really like mix-n-match when not needed. Rather keep the IoC Container doing what it's meant to do.
If you need more control over VM injection and Life-time management look at more sophisticated Ioc controllers such as Unity. SimpleIoC was just meant to be a simple get your feet "wet" in IoC kind of container and it does a very good job in that regard.
I think you are trying to use the same instance of your ViewModel with multiple views. So the views will obviously overwrite each others viewmodel contents.
What if you do this;
Messenger.Default.Register<OpenWindowMessage>(this, message =>
{
var adventurerWindowVM = new AdventurerViewModel();
adventurerWindowVM.Adv = message.Argument;
var adventurerWindow = new AdventurerView()
{
DataContext = adventurerWindowVM
};
adventurerWindow.Show();
});
It's a method call, passing in an anonymous method using a lambda expression.
It looks like you are getting your AdventurerViewModel from some sort of IoC container. How is the IoC container configured? In particular, what is the scope of the objects it gives you back? If you have the IoC configured to create objects in singleton scope, for example, then you will always get back a reference to the same object each time. You may need to configure the scope of the object in your IoC container so that it gives you back a fresh copy every time.
How you do that will depend on your IoC container. Without knowing which IoC framework you are using or seeing its configuration, it's impossible to make any further comment.
My advice would be to create an extension method for SimpleIOC. Something like this:
public static T CreateInstance<T>(this SimpleIoc simpleIoc)
{
// TODO implement
}
You already know the method to get the same instance; extended SimpleIoc with a method to create a new instance:
T instance = SimpleIoc.Default.GetInstance<T>();
T createdInstance = SimpleIoc.Defalt.CreateInstance<T>();
If you are not familiar with extension methods, see Extension Methods Demystified
The implementation:
Of type T, get the constructor.
If there is more than one constructor: either throw exception, or decide which constructor to use. Simple method: use the same method that is used in SimpleIoc.GetInstance, with an attribute. More elaborate method: try to find out if you can find registered elements that match one of the constructors. This is not explained here.
Once you've found the constructor that you need, get its parameters.
Ask SimpleIoc for instances of this parameter, or if they should be new also, ask SimpleIoc to create new instances.
CreateInstance
.
public static T CreateInstance<T>(this SimpleIoc ioc)
{
return (T)ioc.CreateInstance(typeof(T));
}
public static object CreateInstance(this SimpleIoc ioc, Type type)
{
ConstructorInfo constructor = ioc.GetConstructor(type);
IEnumerable<object> constructorParameterValues = ioc.GetParameters(constructor);
constructor.Invoke(constructorParameterValues.ToArray());
}
To decide which constructor to use:
private static ConstructorInfo GetConstructor(this SimpleIoc ioc, Type type)
{
ConstructorInfo[] constructors = type.GetConstructors();
ConstructorInfo constructorToUse;
if (constructorInfo.Length > 1)
{
// Decide which constructor to use; not explained here
// use Attribute like SimpleIoc.GetInstance?
// other method: use SimpleIoc.IsRegistered to check which Parameters
// are registered: use ConstructorInfo.GetParameters()
constructorToUse =
}
else
constructorToUse = constructoInfo[0];
return constructorToUse;
}
To get the values of the parameters in the constructor, we need to decide whether we want existing values from Ioc, or create new values:
public static IEnumerable<object> GetParameterValues(this simpleIoc ioc,
ConstructorInfo constructor)
{
IEnumerable<Type> parameterTypes = contructor.GetParameters()
.Select(parameter => parameter.ParameterType);
return ioc.GetInstances(parameterTypes);
}
public static IEnumerable<object> GetInstances(this SimpleIoc ioc,
IEnumerable<Type> types)
{
// TODO: decide if we want an existing instance from ioc,
// or a new one
// use existing instance:
return types.Select(type => ioc.GetInstance(type));
// or create a new instance:
return types.Select(type => ioc.CreateInstance(type));
}
This seems like a lot of code, but most of it is comment and most methods are one liners.

Categories

Resources