Ensuring the same DbContext within a SCOPED Request - c#

In Entity Framework (EF) Core, SCOPED OBJECTS are the same within a request (but different across different requests). Calling AddDbContext is supposed to be SCOPED by-default...so I am expecting each DbContext instance to be the same instance when marked as SCOPED...and it's not.
I know this because every DbContext handed-up using Dependency Injection (DI) has a different ContextId...and "save changes" no longer works across all Repository's in my UnitOfWork. As such, it seems like DbContext creation is acting TRANSIENT not SCOPED.
Q: How do I guarantee each instance of the concrete DbContext is the same object in EF Core's DI-model?
Why do I want this?
Calling the UnitOfWork's "save changes" used to work across all Repository's...but not anymore because each DbContxet is different (and has a separate change tracker)
Lamar Service Registry Code:
public class ContainerRegistry : ServiceRegistry
{
public ContainerRegistry()
{
Scan(scan =>
{
scan.TheCallingAssembly();
scan.WithDefaultConventions();
scan.LookForRegistries();
scan.SingleImplementationsOfInterface();
});
// --------
// DATABASE
//ForSingletonOf<WorkflowComponentDbContext>(); //<-- Doesnt work b/c each DbContext is still a separate instance
For<DbContext>().Use<WorkflowComponentDbContext>();
For(typeof(IAuditableRepository<>)).Use(typeof(GenericAuditableRepository<>));
// Policies (are used to map Constructor args)
Policies.Add<GenericRepositoryConfiguredInstancePolicy>();
Policies.Add<UnitOfWorkConfiguredInstancePolicy>();
}
}
Host Builder Code:
private IHostBuilder CreateHostBuilder(string[] args)
{
var builder = new HostBuilder()
.ConfigureAppConfiguration((hostingContext, config) =>
{
config.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true);
})
.UseServiceProviderFactory<ServiceRegistry>(new LamarServiceProviderFactory())
.ConfigureServices((hostContext, services) =>
{
var connectionString = hostContext.Configuration.GetConnectionString(JsonSettings.ConnectionStrings.WorkflowComponentDb);
services.AddLamar(IoC.Build());
services.AddScoped<IWindowsIdentityHelper, WindowsIdentityHelper>();
// This is supposedly SCOPED by-default?
// And while, this passes-back OPTIONS correctly...it isn't passing a "singleton object" throughout the request
services.AddDbContext<ProjectManagementDbContext>((provider, options) =>
{
options.UseSqlServer(connectionString);
});
services.AddDbContext<WorkflowComponentDbContext>((provider, options) =>
{
options.UseSqlServer(connectionString);
});
// This doesnt work either b/c it hands-back a new instacne of the Factoty each time (I tested this)....
//services.AddDbContextFactory<WorkflowComponentDbContext, WorkflowComponentDbContextFactory>((provider, options) =>
//{
// options.UseSqlServer(connectionString);
//}, ServiceLifetime.Scoped);
});
return builder;
}
LOW-TECH OPTION: Pass-in the IContainer
I really don't want to do this...but can
// -----
// NOTE: Some code omitted for brevity
public class WorkflowComponentUnitOfWork : IUnitOfWork
{
// OPTION: I could pass the IContainer to build some dependecies?
public WorkflowComponentUnitOfWork(DbContext dbContext, IContainer container)
{
DbContext = dbContext;
ContextType = new GenericAuditableRepository<ContextType>(DbContext);
ContextType.AuditResolver = container.GetRequiredService<IAuditResolverOf<ContextType>>();
ObjectState = new GenericAuditableRepository<ObjectState>(DbContext);
ObjectState.AuditResolver = container.GetRequiredService<IAuditResolverOf<ObjectState>>();
ObjectStateEvent = new GenericAuditableRepository<ObjectStateEvent>(DbContext);
ObjectStateEvent.AuditResolver = container.GetRequiredService<IAuditResolverOf<ObjectStateEvent>>();
Workflow = new GenericAuditableRepository<Workflow>(DbContext);
Workflow.AuditResolver = container.GetRequiredService<IAuditResolverOf<Workflow>>();
WorkflowEvent = new GenericAuditableRepository<WorkflowEvent>(DbContext);
WorkflowEvent.AuditResolver = container.GetRequiredService<IAuditResolverOf<WorkflowEvent>>();
WorkflowTransition = new GenericAuditableRepository<WorkflowTransition>(DbContext);
WorkflowTransition.AuditResolver = container.GetRequiredService<IAuditResolverOf<WorkflowTransition>>();
}
public virtual void SubmitChanges()
{
DbContext.SaveChanges();
}
}
LOW-TECH OPTION: Call "save changes" across all repository's
I really don't want to do this...but can
// -----
// NOTE: Some code omitted for brevity
public class WorkflowComponentUnitOfWork : IUnitOfWork
{
[SetterProperty]
public IAuditableRepository<ContextType> ContextType { get; set; }
[SetterProperty]
public IAuditableRepository<ObjectState> ObjectState { get; set; }
[SetterProperty]
public IAuditableRepository<ObjectStateEvent> ObjectStateEvent { get; set; }
[SetterProperty]
public IAuditableRepository<Workflow> Workflow { get; set; }
[SetterProperty]
public IAuditableRepository<WorkflowEvent> WorkflowEvent { get; set; }
[SetterProperty]
public IAuditableRepository<WorkflowTransition> WorkflowTransition { get; set; }
// OPTION: I could call "Save Changes" across each Repository
public virtual void SubmitChanges()
{
ContextType.SaveChanges();
ObjectState.SaveChanges();
ObjectStateEvent.SaveChanges();
Workflow.SaveChanges();
WorkflowEvent.SaveChanges();
WorkflowTransition.SaveChanges();
}
}
UPDATES:
Using the following does not work...
For<DbContext>().Use<WorkflowComponentDbContext>().Scoped();

As an answer to your question: The documentation here (https://jasperfx.github.io/lamar/guide/ioc/lifetime.html) adds an extra .Scoped() to the For<I>().Use<T>().. Looks like you should add this to your Lamar-definition of ProjectManagementDbContext.
Additionally: Why do you use services.AddDbContext<ProjectManagementDbContext>(..) and For<DbContext>().Use<ProjectManagementDbContext>(). There is an overload for AddDbContext<T1, T2>(..).
As an advice (from my own error): The way you have to setup your DI would mean, that all DbContexts share state. Consider refactoring your code to be able use transient DbContexts (from the names given maybe your repositories are just wrapping DbSets ... but that's just a guess).

This is the correct code conceptually:
For<DbContext>().Use<WorkflowComponentDbContext>().Scoped()
When StructureMap / Lamar was first created, an HTTP request was processed on a single thread. In those days Scoped objects used thread local storage.
C# usage has switched to an async await model since then. So it is possible that you are getting a problem after an await call, when code resumes on a new thread. This might cause a new DbContext to be created. I could be wrong - but worth checking if this is the cause.
If so, you may need to create a custom scope that stores objects against the HTTP request. I ran into this a while back with Spring Boot, and had to write this custom scope.

Related

NET Core dependency injection - resolve service or configuration based on dependent class

Assuming this use case:
You've got two classes X and Y that depends on a configuration of type Config
public class X
{
public X(IOptions<Config> config)
{
}
}
public class Y
{
public Y(IOptions<Config> config)
{
}
}
Now, you want to create each an instance of X and Y, but with different configurations. What would be the right way to register this?
From everything I read, the only way to solve this would be by adding some sort of "naming" for the different configuration instances and resolve them via a custom resolver:
public delegate Config ServiceResolver(string key);
services.AddTransient<ServiceResolver>(serviceProvider => key =>
{
switch (key)
{
case "A":
return ... (whatever to get the first config instance);
case "B":
return ... (whatever to get the second config instance);
default:
throw new KeyNotFoundException();
}
});
However, this means that the implementation of each X and Y must know about details about how to get the configurations:
They must know the correct name (A or B) and
they must know the ConfigResolver type, which is only an implementation detail/helper class for the sake of dependency injection.
This problem hits even harder if you need to go through several stages of dependencies, like
Config (A) Config (B)
| |
v v
Service Service
| |
v v
X Y
My feeling is, there should be a better way to solve this.
Like some form of receipent dependent service factory:
Host.CreateDefaultBuilder(args).ConfigureServices((context, services) => {
services.Configure<Config>(context.Configuration.GetSection("ConfigA")).For<X>();
services.Configure<Config>(context.Configuration.GetSection("ConfigB")).For<Y>();
});
and maybe
Host.CreateDefaultBuilder(args).ConfigureServices((context, services) => {
services.AddTransient<Service>((services, receiverType) => {
if(receiverType == typeof(X)) {
... resolve a service instance;
}
else {
... resolve some other service instance;
}
});
});
So, is there just some feature I missed until now? Is my understanding of the situation totaly misguided? Or is this really a feature that should be, but has not been added until now?
EDIT:
To make my point clearer: Just assume that X and Y are classes of a third-party library. Their constructors signature cannot be changed by you, as you don't have access to the source code.
So, how would you set this up in a way that you can get each an instance of X with ConfigA and an instance of Y with ConfigB?
Another EDIT 2023-01-02:
Happy new year everyone :)
Seems I have to describe a bit better what's my problem. This is not constrained to IOptions/configurations, but more a general question about where to decide about which service to inject and how it is configured.
Assume I have two a congress location with 2 stages. I call them "bigStage" and "smallStage", but in the end they've got the same implementation. I also got two speakers invited, called "loadSpeaker" and "quietSpeaker", but at this moment in time I don't know which one will speak on which of the two stages.
So I decide I've got this setup:
class Stage {
public Stage(string name, ISpeaker speaker) {
...
}
}
class Speaker: ISpeaker {
public Speaker(string name) {
...
}
}
Now, at the latest time possible, I want to compose my final setup so that I've got 2 Stages (called bigStage and smallStage) and their assigned Speakers (loudSpeaker on bigStage and quietSpeaker on smallStage). This composition/assignment should completely happen in my composition root, so that no code changes have to happen in the rest of my code. How can I do that?
I suggest to use a factory for your Service:
class X {
private readonly Service _service;
public X(ServiceFactory serviceFactory) {
_service = serviceFactory.Create<X>();
}
}
class Service {
private readonly Config _config;
public Service(Config config) { _config = config; }
}
class ServiceFactory {
private readonly IConfiguration _configuration;
/* other Service dependencies would also be injected here */
public ServiceFactory(IConfiguration configuration, /* Service dependencies */) {
_configuration = configuration;
...
}
public Service Create<T>() {
return Create(typeof(T));
}
public Service Create(Type type) {
var configName = switch typeof(T) {
X => "ConfigX",
Y => "ConfigY",
default => throw new Exception()
};
var config = _configuration.GetSection(configName).Get<Config>();
return new Service(config, /* other dependencies */);
}
}
The switch statement can be replaced with a Dictionary<Type, string> or Dictionary<string, string> if you would want to export this dictionary to IConfiguration.
Getting the Config can be also cached for performance (don't forget the thread safety)
So the "trick" to all of this is... you have to piggy back onto ~something to make a decision on which one IMySomething . when you register multiple IMySomething(s).
The factory above where you switch/case on the object.TYPE....is one way.
But it is "fragile", IMHO. Or at the very last, violates the Open/Closed principle of SOLID, as you have to keep editing the code to add a new case-statement.
So I also think you want a Factory.......BUT I do not like "hard coding" the values of the switch/case statements.
So if you follow my IShipper example:
Using a Strategy and Factory Pattern with Dependency Injection
I think you want to create a
IShipperFactory
and inject the IEnumerable of "IShipper"(s).
..
Then you will use your IShipperFactory... when registering your things that need an IShipper.
This does cause a small "ripple" because you need access to the IShipperFactory....to do (later) IoC registrations.
But it would be "clean" and have good separations of concerns.
Let me pseudo code it.
public interface IShipper (from other article)
3 concretes (Usps, FedEx, Ups)
public interface IShipperFactory()
public IShipper GetAnIShipper(string key)
..
public class ShipperFactoryConcrete
(from other article, inject multiple IShippers here)
public IShipper GetAnIShipper(string key)
// look through the injected IShippers to find a match, or else throw exception.
.....
public interface IOrderProcessor
..
public class WestCoastOrderProcessor : IOrderProcessor
/* c-stor */
public WestCoastOrderProcessor(IShipper aSingleShipper)
public class EastCoastOrderProcessor : IOrderProcessor
/* c-stor */
public WestCoastOrderProcessor(IShipper aSingleShipper)
........
Ok, so we decide at compile-time, we want to define the "best" IShipper for the EastCoastOrderProcessor and WestCoastOrderProcessor. (making up some kind of example here)
So need need to IoC register.
from the other article:
cont.RegisterType<IShipper, FedExShipper>(FedExShipper.FriendlyName);
cont.RegisterType<IShipper, UspsShipper>(UspsShipper.FriendlyName);
cont.RegisterType<IShipper, UpsShipper>(UpsShipper.FriendlyName);
now it gets a little "off beaten path".
See:
https://stackoverflow.com/a/53885374/214977
and
// so this is a cart-horse situation, where we need something from the IoC container.... to complete the IoC registrations.
IShipperFactory sf = services.GetRequiredService<IShipperFactory>(); // see https://learn.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-7.0#resolve-a-service-at-app-start-up
.. and now we IoC register...but we specify specific values for the constructor. please see the SOF (214977), for syntax-sugar hints. the below is definately pseduo code.....
_serviceCollection.AddSingleton<IOrderProcesor>(x =>
ActivatorUtilities.CreateInstance<EastCoastOrderProcessor>(x, sf.GetAnIShipper(FedExShipper.ShipperName));
);
_serviceCollection.AddSingleton<IOrderProcesor>(x =>
ActivatorUtilities.CreateInstance<WestCoastOrderProcessor>(x, sf.GetAnIShipper(UspsShipper.ShipperName));
);
APPEND:ONE:
Another "trick" .. if you have a code base that you cannot change is.
The "proxy design pattern":
The Proxy design pattern provides a surrogate or placeholder for
another object to control access to it.
https://www.dofactory.com/net/proxy-design-pattern
public EastCoastOrderProcessorProxy
private readonly ThirdPartyOrderProcessor innerThirdPartyOrderProcessor;
public EastCoastOrderProcessor(ThirdPartyOrderProcessor innerThirdPartyOrderProcessor)
{
this.innerThirdPartyOrderProcessor = innerThirdPartyOrderProcessor;
}
..
public WestCoastOrderProcessorProxy
private readonly ThirdPartyOrderProcessor innerThirdPartyOrderProcessor;
public EastCoastOrderProcessor(ThirdPartyOrderProcessor innerThirdPartyOrderProcessor)
{
this.innerThirdPartyOrderProcessor = innerThirdPartyOrderProcessor;
}
So while you cannot change the ThirdPartyOrderProcessor, you can write 1:N wrapper-proxies around it.
The simplest solution I can think of, without using named options inside of your service classes, is moving the selection of the configuration object from the class constructor to the composition root of the application.
This way, your service class simply receives a configuration object as a constructor parameter and it is not aware of the underlying configuration infrastructure.
The composition root, which is in charge of composing the objects which make your application, do know about the configuration infrastructure and picks the right configuration object for your services.
In order to implement this pattern, you need to define an option class as the first step. This option class is needed in order to leverage the options pattern support offered by ASP.NET core. You will only use this class at the composition root level.
public sealed class LayoutOptions
{
public const string Layout = "Layout";
public const string Basic = "Basic";
public const string Complex = "Complex";
public string Name { get; set; } = default!;
public string Color { get; set; } = default!;
public int NumberOfColumns { get; set; }
}
Then you need to define a class which represents the configuration object for your services. This is basically a strongly typed configuration object used to configure your services. This object is built strating from the options class, notice that you don't need to make it identical to the options class itself.
public sealed class LayoutConfiguration
{
public string Name { get; }
public string Color { get; }
public LayoutConfiguration(string name, string color)
{
Name = name;
Color = color;
}
}
Now you need to define your service classes. These types are configured by using the LayoutConfiguration configuration class. Each service class will be properly configured by the composition root of the application, by using the proper named options.
public interface ILayoutService
{
string GetLayoutDescription();
}
public sealed class BasicLayoutService : ILayoutService
{
private readonly LayoutConfiguration _config;
public BasicLayoutService(LayoutConfiguration config)
{
_config = config ?? throw new ArgumentNullException(nameof(config));
}
public string GetLayoutDescription() =>
$"Basic layout description. Name: '{_config.Name}' Color: '{_config.Color}'";
}
public sealed class ComplexLayoutService : ILayoutService
{
private readonly LayoutConfiguration _config;
public ComplexLayoutService(LayoutConfiguration config)
{
_config = config ?? throw new ArgumentNullException(nameof(config));
}
public string GetLayoutDescription() =>
$"Complex layout description. Name: '{_config.Name}' Color: '{_config.Color}'";
}
You can also defined a couple of controllers, that you can use to test this implementation and be user that your services are wired-up correctly by the composition root of the application:
[ApiController]
[Route("[controller]")]
public sealed class BasicLayoutController : ControllerBase
{
private readonly BasicLayoutService _basicLayoutService;
public BasicLayoutController(BasicLayoutService basicLayoutService)
{
_basicLayoutService = basicLayoutService ?? throw new ArgumentNullException(nameof(basicLayoutService));
}
[HttpGet("description")]
public string GetDescription() => _basicLayoutService.GetLayoutDescription();
}
[ApiController]
[Route("[controller]")]
public sealed class ComplexLayoutController : ControllerBase
{
private readonly ComplexLayoutService _complexLayoutService;
public ComplexLayoutController(ComplexLayoutService complexLayoutService)
{
_complexLayoutService = complexLayoutService ?? throw new ArgumentNullException(nameof(complexLayoutService));
}
[HttpGet("description")]
public string GetDescription() => _complexLayoutService.GetLayoutDescription();
}
This is the most important part. Put this registration code inside the Program.cs class (which is the composition root for an ASP.NET core 6 application):
// Configure named options
builder.Services.Configure<LayoutOptions>(
LayoutOptions.Basic,
builder.Configuration.GetSection($"{LayoutOptions.Layout}:{LayoutOptions.Basic}")
);
builder.Services.Configure<LayoutOptions>(
LayoutOptions.Complex,
builder.Configuration.GetSection($"{LayoutOptions.Layout}:{LayoutOptions.Complex}")
);
// Register the BasicLayoutService by picking the right configuration
builder
.Services
.AddScoped(serviceProvider =>
{
// Get named options
var layoutOptions = serviceProvider.GetRequiredService<IOptionsSnapshot<LayoutOptions>>();
var basicLayoutOptions = layoutOptions.Get(LayoutOptions.Basic);
// Create strongly typed configuration object from named options
var configuration = new LayoutConfiguration(
basicLayoutOptions.Name,
basicLayoutOptions.Color);
// Creates new instance of BasicLayoutService using the service provider and the configuration object
return ActivatorUtilities.CreateInstance<BasicLayoutService>(
serviceProvider,
configuration);
});
// Register the ComplexLayoutService by picking the right configuration
builder
.Services
.AddScoped(serviceProvider =>
{
// Get named options
var layoutOptions = serviceProvider.GetRequiredService<IOptionsSnapshot<LayoutOptions>>();
var complexLayoutOptions = layoutOptions.Get(LayoutOptions.Complex);
// Create strongly typed configuration object from named options
var configuration = new LayoutConfiguration(
complexLayoutOptions.Name,
complexLayoutOptions.Color);
// Creates new instance of ComplexLayoutService using the service provider and the configuration object
return ActivatorUtilities.CreateInstance<ComplexLayoutService>(
serviceProvider,
configuration);
});
You can now test this implementation. As an example, you can set the following configuration in appsettings.json:
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"Layout": {
"Basic": {
"Name": "Basic Layout",
"Color": "red",
"NumberOfColumns": 2
},
"Complex": {
"Name": "Complex Layout",
"Color": "blue",
"NumberOfColumns": 3
}
}
}
If you run this application and you issue a GET request to /BasicLayout/description, you ge the following response:
Basic layout description. Name: 'Basic Layout' Color: 'red'
If you issue a GET request to /ComplexLayout/description the response you get is:
Complex layout description. Name: 'Complex Layout' Color: 'blue'
A final note on the service lifetime for BasicLayoutService and ComplexLayoutService. In my example I decided to register them as scoped services, because you may want to recompute the configuration object for them (LayoutConfiguration) for each incoming request. This is useful if your configuration may change over time. If this is not the case, you can safely register them as singleton services. That's up to you and depends on your requirements.

Dependency Injection and Service Lifetimes [duplicate]

In my Asp.Net Core App I need a singleton service that I can reuse for the lifetime of the application. To construct it, I need a DbContext (from the EF Core), but it is a scoped service and not thread safe.
Therefore I am using the following pattern to construct my singleton service. It looks kinda hacky, therefore I was wondering whether this is an acceptable approach and won't lead to any problems?
services.AddScoped<IPersistedConfigurationDbContext, PersistedConfigurationDbContext>();
services.AddSingleton<IPersistedConfigurationService>(s =>
{
ConfigModel currentConfig;
using (var scope = s.CreateScope())
{
var dbContext = scope.ServiceProvider.GetRequiredService<IPersistedConfigurationDbContext>();
currentConfig = dbContext.retrieveConfig();
}
return new PersistedConfigurationService(currentConfig);
});
...
public class ConfigModel
{
string configParam { get; set; }
}
What you're doing is not good and can definitely lead to issues. Since this is being done in the service registration, the scoped service is going to be retrieve once when your singleton is first injected. In other words, this code here is only going to run once for the lifetime of the service you're registering, which since it's a singleton, means it's only going to happen once, period. Additionally, the context you're injecting here only exists within the scope you've created, which goes away as soon as the using statement closes. As such, by the time you actually try to use the context in your singleton, it will have been disposed, and you'll get an ObjectDisposedException.
If you need to use a scoped service inside a singleton, then you need to inject IServiceProvider into the singleton. Then, you need to create a scope and pull out your context when you need to use it, and this will need to be done every time you need to use it. For example:
public class PersistedConfigurationService : IPersistedConfigurationService
{
private readonly IServiceProvider _serviceProvider;
public PersistedConfigurationService(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
public async Task Foo()
{
using (var scope = _serviceProvider.CreateScope())
{
var context = scope.ServiceProvider.GetRequiredService<IPersistedConfigurationDbContext>();
// do something with context
}
}
}
Just to emphasize, again, you will need to do this in each method that needs to utilize the scoped service (your context). You cannot persist this to an ivar or something. If you're put off by the code, you should be, as this is an antipattern. If you must get a scoped service in a singleton, you have no choice, but more often than not, this is a sign of bad design. If a service needs to use scoped services, it should almost invariably be scoped itself, not singleton. There's only a few cases where you truly need a singleton lifetime, and those mostly revolve around dealing with semaphores or other state that needs to be persisted throughout the life of the application. Unless there's a very good reason to make your service a singleton, you should opt for scoped in all cases; scoped should be the default lifetime unless you have a reason to do otherwise.
Although Dependency injection: Service lifetimes documentation in ASP.NET Core says:
It's dangerous to resolve a scoped service from a singleton. It may cause the service to have incorrect state when processing subsequent requests.
But in your case this is not the issue. Actually you are not resolving the scoped service from singleton. Its just getting an instance of scoped service from singleton whenever it requires. So your code should work properly without any disposed context error!
But another potential solution can be using IHostedService. Here is the details about it:
Consuming a scoped service in a background task (IHostedService)
Looking at the name of this service - I think what you need is a custom configuration provider that loads configuration from database at startup (once only). Why don't you do something like following instead? It is a better design, more of a framework compliant approach and also something that you can build as a shared library that other people can also benefit from (or you can benefit from in multiple projects).
public class Program
{
public static void Main(string[] args)
{
CreateWebHostBuilder(args).Build().Run();
}
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>()
.ConfigureAppConfiguration((context, config) =>
{
var builtConfig = config.Build();
var persistentConfigBuilder = new ConfigurationBuilder();
var connectionString = builtConfig["ConnectionString"];
persistentStorageBuilder.AddPersistentConfig(connectionString);
var persistentConfig = persistentConfigBuilder.Build();
config.AddConfiguration(persistentConfig);
});
}
Here - AddPersistentConfig is an extension method built as a library that looks like this.
public static class ConfigurationBuilderExtensions
{
public static IConfigurationBuilder AddPersistentConfig(this IConfigurationBuilder configurationBuilder, string connectionString)
{
return configurationBuilder.Add(new PersistentConfigurationSource(connectionString));
}
}
class PersistentConfigurationSource : IConfigurationSource
{
public string ConnectionString { get; set; }
public PersistentConfigurationSource(string connectionString)
{
ConnectionString = connectionString;
}
public IConfigurationProvider Build(IConfigurationBuilder builder)
{
return new PersistentConfigurationProvider(new DbContext(ConnectionString));
}
}
class PersistentConfigurationProvider : ConfigurationProvider
{
private readonly DbContext _context;
public PersistentConfigurationProvider(DbContext context)
{
_context = context;
}
public override void Load()
{
// Using _dbContext
// Load Configuration as valuesFromDb
// Set Data
// Data = valuesFromDb.ToDictionary<string, string>...
}
}

How to handle event invocation between injected classes in .NET Core Console App

I've a .Net Core(3.1) Console App, that has 2 service classes, one has an event and other listens to it with a handler to that event. I've setup getting the DI containers but the event field is always null, so not able to call its Invoke(). Any pointers on what am I missing in setting up the services in ConfigureServices() that involves event handling. Below is the complete test code:
public class RefreshEventArgs : EventArgs
{
public string RefreshEventData { get; set; }
}
public interface INotifierService
{
event EventHandler<RefreshEventArgs> RefreshEventHandler;
}
public class NotifierService : INotifierService
{
public event EventHandler<RefreshEventArgs> RefreshEventHandler;
public RefreshEventArgs RefreshEventData { get; set; }
// GeneralAppSettings is a POCO class to read all appsettings.json key values.
private readonly IOptions<GeneralAppSettings> myAppSettings;
public NotifierService(IOptions<GeneralAppSettings> appSettings)
{
myAppSettings = appSettings;
}
public void RunInvokingRefreshEvent()
{
RefreshEventData = new RefreshEventArgs();
RefreshEventData.RefreshEventData = "somedata";
// Main problem! In the below line, RefreshEventHandler is null all the time
RefreshEventHandler?.Invoke(this, RefreshEventData);
}
public void SomeBackgroundThreadMonitorsExternalEvents()
{
// Some external events triggers below method
RunInvokingRefreshEvent();
}
}
Refresh Service
public interface IRefreshService
{
void Refresh(RefreshEventArgs eventData = null);
}
public class RefresherService : IRefreshService
{
private readonly IOptions<GeneralAppSettings> myAppSettings;
private readonly INotifierService notify;
public RefresherService(IOptions<GeneralAppSettings> _appSettings, INotifierService _notifyService)
{
myAppSettings = _appSettings;
notify = _notifyService;
notify.RefreshEventHandler += _notify_RefreshEventHandler;
}
private void _notify_RefreshEventHandler(object sender, RefreshEventArgs e)
{
// Call Refresh() based say based on a config value from myAppSettings
Refresh(e);
}
public void Refresh(RefreshEventArgs eventData = null)
{
// final business logic processing based on eventData
}
}
public class GeneralAppSettings // POCO
{
public string SomeConfigKeyInAppSettingsJson { get; set; }
}
Program
class Program
{
public static IConfiguration Configuration { get; set; }
static void Main(string[] args)
{
// read appsettings
var builder = new ConfigurationBuilder().SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddEnvironmentVariables();
Configuration = builder.Build();
// Host builder, setting up container
var host = Host.CreateDefaultBuilder()
.ConfigureAppConfiguration((hostingContext, config) =>
{
config.AddConfiguration(Configuration);
})
.ConfigureServices((context, services) =>
{
services.Configure<GeneralAppSettings>(Configuration.GetSection("GeneralAppSettings"));
services.AddSingleton<INotifierService, NotifierService>();
services.AddSingleton<IRefreshService, RefresherService>();
})
.Build();
// Need to get NotifierService instance to run some initial logic, so using ActivatorUtilities
var svc = ActivatorUtilities.GetServiceOrCreateInstance<NotifierService>(host.Services);
svc.SomeBackgroundThreadMonitorsExternalEvents();
// Need to get RefresherService instance to have initial Refresh logic so using ActivatorUtilities
var refresh = ActivatorUtilities.GetServiceOrCreateInstance<RefresherService>(host.Services);
refresh.Refresh(null);
// need to keep this main thread alive
Thread.Sleep(Timeout.Infinite);
}
}
When you request something from the DI container, you must request the "Service" type (the interface or first/only generic argument). If you request a type that's not been registered and you use ActivatorUtilities, it will create an instance if and only if all the types required to construct it are available. What's happening to you is you are getting two distinct objects (one registered as the interface and one pseudo-registered as the concrete type)! It doesn't matter that your class implements the interface and you've used it as the "Implementation" type in the registration. DI is always based on the service type and you've not registered any services of type NotifierService directly.
Your problem is that you have a weird coupling between your classes and the method you want to call on NotifierService isn't actually part of the interface. The usual trick would be to just register and request the concrete type as the service type:
services.AddSingleton<NotiferService>();
//...
var notifier = services.GetService<NotifierService>();
That would work, except now you haven't registered INotifierService for injection into the RefresherService.
Never fear, we have a work around. Register the concrete type as a singleton and then use a factory to register the interface:
// register the concrete type directly
services.AddSingleton<NotifierService>();
// use a factory to register the interface
services.AddSingleton<INotifierService>(sp => sp.GetRequiredService<NotifierService>());
Now, the same instance will be returned whether you are requesting the interface or the concrete type. You no longer need to use ActivatorUtilities either (in fact you shouldn't)--you can now use the host's services directly:
var notifier = host.Services.GetRequiredService<NotifierService>();
notifier.SomeBackgroundThreadMonitorsExternalEvents();
All that said, you're project is a perfect candidate for an IHostedService/BackgroundService. You can restructure it a bit (splitting NotifierService into two classes: one with just the event and the other for the background service) such that you'll then only be dealing with interfaces and you'd be able to actually call Host.Run() which will in turn wait for shutdown. This is the standard pattern for things like this, rather than abusing the Host simply for the DI container and including the weird Thread.Sleep.

PostSharp in a multitenant solution

I'm currently looking to add PostSharp logging (and possibly other custom aspects) to an ASP.NET Core API project. The problem I'm running into is that we have a multitenant design where the tenant info is stored in the user claims, and there doesn't seem to be a good way to get at the current session from a PostSharp aspect, so there doesn't seem to be a good way to access the proper tenant's database.
Am I just barking up the wrong tree? Is there a different AOP framework I should be looking at?
For completeness' sake, here's the solution I came up with, which uses both PostSharp and AutoFac.
PostSharp aspects can create properties on classes that they're applied to at compile time. Using AutoFac's InjectUnsetProperties function, we can inject properly-scoped members into these classes even if we don't know about them at compile time.
So, we set up our PostSharp aspect:
[PSerializable]
public class LoggingAspect : OnMethodBoundaryAspect, IInstanceScopedAspect
{
[IntroduceMember(Visibility = Visibility.Public, OverrideAction = MemberOverrideAction.Ignore)]
[CopyCustomAttributes(typeof(ImportMemberAttribute))]
public IInjectedObject InjectedObject { get; set; }
[ImportMember("InjectedObject", IsRequired = true)]
public Property<IInjectedObject> InjectedObjectProperty;
public override void OnEntry(MethodExecutionArgs args)
{
var data = InjectedObjectProperty.Get().MyData;
Debug.Print($"OnEntry: {args.Method.Name}, Data: {data}\n");
}
public object CreateInstance(AdviceArgs adviceArgs)
{
return MemberwiseClone();
}
public void RuntimeInitializeInstance()
{
}
}
then register the service that we want to use the aspect on in our Startup method:
public IServiceProvider ConfigureServices(IServiceCollection services)
{
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
var builder = new ContainerBuilder();
builder.Populate(services);
builder.RegisterType<TestService>().As<ITestService>()
.InstancePerLifetimeScope()
.OnActivated(e => e.Context.InjectUnsetProperties(e.Instance))
;
builder.RegisterType<InjectedObject>().As<IInjectedObject>()
.InstancePerLifetimeScope()
;
var container = builder.Build();
return new AutofacServiceProvider(container);
}
and add the aspect to the method we want to log:
public class TestService : ITestService
{
public TestService()
{
Debug.Print("TestService ctor\n");
}
private int _myData = 100;
[LoggingAspect]
public int GetData()
{
return _myData++;
}
}
When the service is created during a request, a new one is created scoped to that request, and it gets a new IInjectedObject stuck into it which is also scoped to the request, even though the IInjectedObject property doesn't appear in our source code.

The context cannot be used while the model is being created. EF-Core ASP.net Core2.2

I have seen many posts talking about the problem but none of them fixed my problem
the scenario
DB Layer with API Controllers
IDataRepository
DataManagers
Code
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
// Add framework services.
services.AddDbContext<ApplicationContext>(opts => opts.UseSqlServer(Configuration["ConnectionString:LawyerApplicationDB"]), ServiceLifetime.Transient);
services.AddSingleton(typeof(IDataRepository<Clients, long>), typeof(ClientManager));
services.AddSingleton(typeof(IDataRepository<Nationality, long>), typeof(NationalityManager));
services.AddMvc();
}
ApplicationContext
public class ApplicationContext: DbContext
{
public ApplicationContext(DbContextOptions opts) : base(opts)
{
}
public DbSet<Clients> Clients { get; set; }
public DbSet<Nationality> Nationalities { get; set; }
}
The Manager where the error Appear
public class NationalityManager : IDataRepository<Nationality, long>
{
private ApplicationContext ctx; //not static
public NationalityManager(ApplicationContext c)
{
ctx = c;
}
public Nationality Get(long id)
{
var nationality = ctx.Nationalities.FirstOrDefault(b => b.Id == id);
return nationality;
}
public IEnumerable<Nationality> GetAll()
{
var nationalities = ctx.Nationalities.ToList();
return nationalities;
}
the error appear first time and the grid does not show data if i refresh the page the data will show
what i do wrong
this is the tutorial i used Building An ASP.NET Core Application With Web API And Code First Development
Thank you for your help
You've fallen into a classic situation where you are keeping the context around too long.
Because NationalityManager is registered as a singleton it doesn't matter that your context was registered as transient. Something with a short lifetime injected into something with a long lifetime effectively means the short lifetime is extended by the lifetime of the longer lived thing.
You can either make your manager object shorter lived or you can inject a context factory into your manager. The context factory ensures your (what should be short lived) context is created when its needed.
When you have API calls coming in at the same time, they are attempting to use the non-thread safe context simultaneously. The first call is setting up the model, and then here comes another call wanting to use the model while its being setup.
Prior to EF Core, I've addressed this issue with the original EF designed for .NET Framework. It might give you some more background.

Categories

Resources