I have the following class and interface structure and I'm having a hard time trying to get the code to do what I need.
public interface IUserManager
{
int Add(User user);
}
public class UserManagerA : IUserManager{}
public class UserManagerB : IUserManager{}
In this example I'm using Ninject as the IoC container but I'm open to changing it if some other container resolves the issue:
This is inside my NinjectWebCommon.cs:
void RegisterServices(IKernel kernel)
{
string userRole = CurrentUser.Role;//this gets the user logged in
//This is the part I do not how to do
//I wish I could just type this in:
kernel.Bind<IUserManager>().To<UserManagerA>()
.When(userRole == "RoleA"); // this doesn't work obviously
kernel.Bind<IUserManager>().To<UserManagerB>()
.When(userRole == "RoleB"); // same doesn't work
}
All of that so that in my (MVC) controller I can do this:
public class UserController
{
private readonly IUserManager _userManager;
public UserController(IUserManager userManager)
{
_userManager = userManager;
}
public ActionResult Add(User user)
{
//this would call the correct manager
//based on the userRole
_userManager.Add(user);
}
}
I've been reading articles about Abstract Factory but haven't found one that explains how to integrate the factory with the IoC container and pass a parameter obtained at run-time to resolve the implementations.
Create a class responsible for providing the correct UserManager and inject this to your controller:
public class UserManagerProvider : IUserManagerProvider
{
private readonly IContext _context;
public UserManagerProvider(IContext context)
{
_context = context;
}
public IUserManager Create(User currentUser)
{
if (currentUser.Role == "User A")
return _context.Kernel.Get<UserManagerA>();
if (currentUser.Role == "User B")
return _context.Kernel.Get<UserManagerB>();
// Or bind and resolve by name
// _context.Kernel.Get<IUserManager>(currentUser.Role);
}
}
And in controller:
private readonly IUserManager _userManager;
public UserController(IUserManagerProvider userManagerProvider)
{
_userManager = userManagerProvider.Create(CurrentUser);
}
Also, as a side note you should probably have a CurrentUserProvider responsible for getting the current user. Relying on a static method will make things difficult to unit test and you're essentially hiding a dependency in all classes that reference it:
private readonly IUserManager _userManager;
private readonly User _currentUser;
public UserController(IUserManagerProvider userManagerProvider, ICurrentUserProvider currentUserProvider)
{
_currentUser = currentUserProvider.GetUser();
_userManager = userManagerProvider.Create(_currentUser);
}
Provided the number of IUserManager implementations is not very many (not likely to reach 100 implementations), you can use a Strategy Pattern to resolve all of your UserManager instances during composition and then pick the best instance for use at runtime.
First, we need a way to map IUserManager implementations to roles.
public interface IUserManager
{
int Add(User user);
bool AppliesTo(string userRole);
}
public class UserManagerA : IUserManager
{
// Add method omitted
public bool AppliesTo(string userRole)
{
// Note that it is entirely possible to
// make this work with multiple roles and/or
// multiple conditions.
return (userRole == "RoleA");
}
}
public class UserManagerB : IUserManager
{
// Add method omitted
public bool AppliesTo(string userRole)
{
return (userRole == "RoleB");
}
}
Then we need a strategy class that simply picks the correct instance based on the userRole. The IUserManager instances are supplied by the DI container when the application is composed.
public interface IUserManagerStrategy
{
IUserManager GetManager(string userRole);
}
public class UserManagerStrategy
: IUserManagerStrategy
{
private readonly IUserManager[] userManagers;
public UserManagerStrategy(IUserManager[] userManagers)
{
if (userManagers == null)
throw new ArgumentNullException("userManagers");
this.userManagers = userManagers;
}
public IUserManager GetManager(string userRole)
{
var manager = this.userManagers.FirstOrDefault(x => x.AppliesTo(userRole));
if (manager == null && !string.IsNullOrEmpty(userRole))
{
// Note that you could optionally specify a default value
// here instead of throwing an exception.
throw new Exception(string.Format("User Manager for {0} not found", userRole));
}
return manager;
}
}
Usage
public class SomeService : ISomeService
{
private readonly IUserManagerStrategy userManagerStrategy;
public SomeService(IUserManagerStrategy userManagerStrategy)
{
if (userManagerStrategy == null)
throw new ArgumentNullException("userManagerStrategy");
this.userManagerStrategy = userManagerStrategy;
}
public void DoSomething()
{
string userRole = CurrentUser.Role;//this gets the user logged in
// Get the correct UserManger according to the role
IUserManager userManager = this.userManagerStrategy.GetManger(userRole);
// Do something with userManger
}
}
void RegisterServices(IKernel kernel)
{
kernel.Bind<IUserManager>().To<UserManagerA>();
kernel.Bind<IUserManager>().To<UserManagerB>();
// Ninject will automatically supply both IUserManager instances here
kernel.Bind<IUserManagerStrategy>().To<UserManagerStrategy>();
kernel.Bind<ISomeService>().To<SomeService>();
}
This method doesn't require you to inject the container into the application. There is no service location being used.
Note also that there is no switch case statement that would have to be modified every time you add a new UserManager to the application. The logic of when to use a UserManager is part of the UserManager implementation and the order in which the logic is executed is determined by the DI configuration.
In addition, this will work regardless of which DI container you are using.
You could combine this with the CurrentUserProvider from RagtimeWilly's answer for a clean way to get the user role into the service where this is used.
Reference: Best way to use StructureMap to implement Strategy pattern
Related
I need to access ClaimsPrincipal within the service layer of a Net Core 6 app.
I could always just builder.Services.AddTransient<IHttpContextAccessor, HttpContextAccessor>(); in the Startup.cs & go my merry way but this is a no-no. Makes it difficult to test and more importantly this is a great example of leaky abstraction.
So, now what I have is the following
public class ClaimsProvider : IClaimsProvider
{
private readonly IHttpContextAccessor _httpContextAccessor;
public ClaimsProvider(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
public ClaimsPrincipal? GetClaimsPrincipal()
{
return _httpContextAccessor.HttpContext?.User;
}
}
public interface IClaimsProvider
{
ClaimsPrincipal? GetClaimsPrincipal();
}
Within my Startup.cs AddScoped() that takes an IHttpContextAccessor and return an IClaimsProvider. Then I simply build all services against IClaimsProvider
builder.Services.AddScoped<IClaimsProvider>(provider =>
{
var httpContextAccessor = provider.GetRequiredService<IHttpContextAccessor>();
return new ClaimsProvider(httpContextAccessor);
});
And the usual route for my services where I inject it as a dependency
private readonly IClaimsProvider _claimsProvider;
public SomeService(
IWebHostEnvironment hostingEnvironment,
IMapper mapper, IClaimsProvider claimsProvider, ...)
{
_hostingEnvironment = hostingEnvironment ??
throw new ArgumentNullException(nameof(hostingEnvironment));
_mapper = mapper ??
throw new ArgumentNullException(nameof(mapper));
_claimsProvider = claimsProvider;
}
public void SomeMethod()
{
var u = _claimsProvider.GetClaimsPrincipal();
foreach (var claim in u.Claims)
{
Console.WriteLine($"{claim.Type} : {claim.Value}");
}
}
My question is that is the above approach ok? Potentially, is there any other approach that is better than the one shown above?
To prevent a leaky abstract (the need for an IHttpContextAsccessor in your service), I would recommend using the Adapter Pattern.
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddHttpContextAccessor();
services.AddScoped<IClaimsProvider, HttpContextClaimsProvider>();
}
public IClaimsProvider
{
public ClaimsPrinciple ClaimsPrinciple { get; }
}
// Adapter
public HttpContextClaimsProvider : IClaimsProvider
{
public HttpContextClaimsProvider(IHttpContextAccessor httpContext)
{
ClaimsProvider = httpContext?.User?.Principle as ClaimsPrinciple;
}
public ClaimsPrinciple ClaimsPrinciple { get; private set; }
}
public class YourService : IYourService
{
private readonly IClaimsProvider _claimsProvider;
public YourService(IClaimsProvider claimsProvider)
{
_claimsProvider= claimsProvider;
}
}
In our design each controller action receives an FooRequest. This is a POCO object where the properties are filled from the model binder by using corresponding attributes:
public class FooRequest : RequestBase
{
[FromRoute]
public int Id { get; set; }
[FromQuery]
public DateTime? Start { get; set; }
[FromBody]
public SomeComplexObject Configuration { get; set; }
}
Additionally we made a derived class using the suffix WithUser that has a ClaimsPrincipal as additional property:
public class FooRequestWithUser : FooRequest, IRequest<FooResponse>
{
public ClaimsPrincipal User { get; set; }
}
In a next step we made a helper class that provides a helper method that can receive the request instance, a claims principal and a type T:
public class RequestBase
{
public T Of<T>(ClaimsPrincipal user) where T: class, new()
{
// Check if T has base of own type
// Create instance and iterate all props to get value
// from this and and set value in instance.
// Additionally use reflection to set user property.
}
}
When our normal request class is derived from this one, we can call it within our controller and create a model containing the user as an additional property and forward it into our services by using MediatR:
public IActionResult DoFoo(FooRequest request)
{
var requestWithUser = request.Of<FooRequestWithUser>(User);
var result = mediator.Send(requestWithUser);
return Ok(result);
}
By this approach the claims principal is bound to the request consumed by the service and not something it has to additionally receive. Also it makes clear, that this request must be somehow authenticated and the service should check for some potential permissions or similar.
The approach you have described is generally considered a valid way to access the ClaimsPrincipal in the service layer of a .NET Core 6 app, as it abstracts the implementation details of the IHttpContextAccessor, making it easier to test and maintain.
An alternative approach could be to use the built-in dependency injection in ASP.NET Core to directly inject the ClaimsPrincipal into the service, without the need for a separate IClaimsProvider interface.
You can do this by registering the ClaimsPrincipal as a service in the ConfigureServices method of the Startup class.
Below is some code from a book which shows how cyclic dependencies:
public interface IAuditTrailAppender {
void Append(Entity changedEntity);
}
public class SqlAuditTrailAppender : IAuditTrailAppender {
private readonly IUserContext userContext;
private readonly CommerceContext context;
private readonly ITimeProvider timeProvider;
public SqlAuditTrailAppender(IUserContext userContext, CommerceContext context, ITimeProvider timeProvider) {
this.userContext = userContext;
this.context = context;
this.timeProvider = timeProvider;
}
public void Append(Entity changedEntity) {
AuditEntry entry = new AuditEntry {
UserId = this.userContext.CurrentUser.Id,
TimeOfChange = this.timeProvider.Now,
EntityId = entity.Id,
EntityType = entity.GetType().Name
};
this.context.AuditEntries.Add(entry);
}
}
public class AspNetUserContextAdapter : IUserContext {
private static HttpContextAccessor Accessor = new HttpContextAccessor();
private readonly IUserRepository repository;
public AspNetUserContextAdapter(IUserRepository repository) {
this.repository = repository;
}
public User CurrentUser {
get {
var user = Accessor.HttpContext.User;
string userName = user.Identity.Name;
return this.repository.GetByName(userName);
}
}
}
public class SqlUserRepository : IUserRepository {
public SqlUserRepository(CommerceContext context, IAuditTrailAppender appender) {
this.appender = appender;
this.context = context;
}
public void Update(User user) {
this.appender.Append(user);
}
public User GetById(Guid id) { ... }
public User GetByName(string name) { ... } // <--- used by CurrentUser property of AspNetUserContextAdapter
}
You can see cyclic dependencies exist as the picture below shows:
The author says "these kind of dependency cycles are typically caused by single-responsibility principle (SRP) violation. To fix it, the author adds a new interface IUserByNameRetriever:
public interface IUserByNameRetriever {
User GetByName(string name);
}
public class SqlUserByNameRetriever : IUserByNameRetriever {
public SqlUserByNameRetriever(CommerceContext context) {
this.context = context;
}
public User GetByName(string name) { ... }
}
public class SqlUserRepository : IUserRepository {
public SqlUserRepository(CommerceContext context, IAuditTrailAppender appender) {
this.appender = appender;
this.context = context;
}
public void Update(User user) {
this.appender.Append(user);
}
public User GetById(Guid id) { ... }
// public User GetByName(string name) { ... } don't need this method anymore
}
public class AspNetUserContextAdapter : IUserContext {
private static HttpContextAccessor Accessor = new HttpContextAccessor();
private readonly IUserByNameRetriever retriever;
public AspNetUserContextAdapter(IUserByNameRetriever retriever) {
this.retriever = retriever;
}
public User CurrentUser {
get {
var user = Accessor.HttpContext.User;
string userName = user.Identity.Name;
return this.retriever.GetByName(userName);
}
}
}
I can understand how the introduce of IAuditTrailAppender stops the dependency cycles, but I feel like it is just a workaround. I don't understand why the author said this dependency cycle is caused by SRP violation. Because if you look at SqlUserRepository, its GetByName method is a nature to have it in the class just like GetById method (consumers can search a user by its id, and of course, it is nature for consumers to search a user by name), I can't see why having GetByName method in SqlUserRepository is a SRP violation?
It's pretty hard to see, but the problem is with the definition of IUserRepository.
The question is: Is this object supposed to be used across multiple requests, to provide the user repository to the system as a whole, or is it created for each request to provide a user repository to that specific requests. Both types of repository are used in practice, but the original example seems to want it to be both at the same time, which is indeed an SRP problem.
If it's intended to be used across multiple requests, then it doesn't make sense that it logs its audit trail in the context of one specific user. A user repository's audit logger should not then require a IUserContext. This is not what the author has decided.
The author has decided to go the other way, and construct a IUserRepository for each request, so that all of its operations are performed in the context of the specific user that performs them -- probably a good idea. That means that the audit logger is fine, but it doesn't make sense that you would require an IUserRepository to create a user context in the first place. You can't perform any of the dangerous user repository methods without a user context to audit them against.
So you need a way to bootstrap a user context without a full repository. The narrower IUserByNameRetriever class fills this role. Either it cannot perform any sensitive operations that require logging, or it creates adequate logs through some other path that doesn't require a user-tied IAuditTrailAppender.
I have a situation where I need to instantiate my DBContext after my solution has started up. I asked this question which indicated that I could do this with a constructor argument.
It was suggested that I implement as an example this:
var connection = #"Server=(localdb)\mssqllocaldb;Database=JobsLedgerDB;Trusted_Connection=True;ConnectRetryCount=0";
var optionsBuilder = new DbContextOptionsBuilder<BloggingContext>();
optionsBuilder.UseSqlServer(connection);
using (var context = new BloggingContext(optionsBuilder.Options))
{
// do stuff
}
However I have implemented the repository pattern (for better or worst) and given my changed circumstances - not having a connection string until after the solution has run startup - I need to implement this into the base repository class and I am at a bit of a loss..
Currently I have this:
public class EntityBaseRepository<T> : IEntityBaseRepository<T> where T : class, IEntityBase, new()
{
public JobsLedgerAPIContext _context;
#region Properties
public EntityBaseRepository(JobsLedgerAPIContext context)
{
_context = context;
}
#endregion
public virtual IQueryable<T> GetAll()
{
return _context.Set<T>().AsQueryable();
}
public virtual int Count()
{
return _context.Set<T>().Count();
}
......
How do I implement this change both instantiating the DBContext in the constructor (there by bypassing the need to add the context as a service in startup) and then with the wrapping each of the virtual methods with "using" etc
EDIT.. Camilo indicated I had not identified when I have the database name.
The basic situation is that the system starts up (This is an Aurelia SPA project which is irrelevant to this issue) sends the package to the browser which shows a login screen. User logs in.. User is verified via a JWT controller.. Once verified in the controller (using a catalog database that has one table with 3 fields - username, password, database name) I use the database name to create a connection string and then instantiate my DBContext at that point.. so via a constructor.
The answers below need to be modified as the one with the factory answer (promising) has errors as discovered by this question.. Nkosi responded with an great answer to the error.
EDIT 2..
This is a response to the edited question below:
Here is my original Client Repository with :base(context) on the constructor.
using JobsLedger.DATA.Abstract;
using JobsLedger.MODEL.Entities;
namespace JobsLedger.DATA.Repositories
{
public class ClientRepository : EntityBaseRepository<Client>, IClientRepository
{
private new JobsLedgerAPIContext _context;
public ClientRepository(JobsLedgerAPIContext context) : base(context)
{
_context = context;
}
public void RelatedSuburbEntities(Suburb _suburb)
{
_context.Entry(_suburb).Reference<State>(a => a.State).Load();
}
}
}
It has a reference to the base class "context". I am not sure how to modify this given that I believe I still need that ":base(context)" at the end. As well, I have a method in this that accesses _context as well which is part of the constructor...
Further I assume that I can no longer inject the service into the controller but instead new it up once I have secured the connection string and then pass that connection string to service.
Also, Given I have now added a singleton on the startup do I need to remove the original entry? :
services.AddDbContext<JobsLedgerAPIContext>(options => options.
UseSqlServer(Configuration.GetConnectionString("DefaultConnection"), b => b.MigrationsAssembly("JobsLedger.API")));
effectively replacing it with my singleton reference as per below:
services.AddSingleton(typeof(IContextFactory<>), typeof(ContextFactory<>));
Edited
The answer has been edited to rectify the mistake spotted and
fixed by Nkosi. Thanks, #Nkosi.
Implement a factory pattern. You can create a factory, call it ContextFactory as below:
First, define the interface. Further modified, removed the connectionString parameter
public interface IContextFactory<T> where T : DbContext
{
T CreateDbContext();
}
Create a factory class that implements this interface (edited as per Nkosi answer). Further modified to inject IHttpContextAccessor
public class ContextFactory<T> : IContextFactory<T> where T : DbContext
{
private readonly HttpContext _httpContext;
public ContextFactory(IHttpContextAccessor contextAccessor)
{
_httpContext = contextAccessor.HttpContext;
}
public T CreateDbContext()
{
// retreive the connectionString from the _httpContext.Items
// this is saved in the controller action method
var connectionString = (string)_httpContext.Items["connection-string"];
var optionsBuilder = new DbContextOptionsBuilder<T>();
optionsBuilder.UseSqlServer(connectionString);
return (T)Activator.CreateInstance(typeof(T), optionsBuilder.Options);
}
}
Then modify your base repository and make the JobsLedgerAPIContext protected. This context is going to be set by the derived class. Further modified to remove the constructor. It will use the parameterless constructor.
public class EntityBaseRepository<T> : IEntityBaseRepository<T> where T : class, IEntityBase, new()
{
protected JobsLedgerApiContext Context { get; set; }
public virtual IQueryable<T> GetAll()
{
return Context.Set<T>().AsQueryable();
}
public virtual int Count()
{
return Context.Set<T>().Count();
}
}
Change your derived class to use IContextFactory. Further modified to use the _contextFactory.CreateDbContext() parameter less method
The IClientRepository should have SetContext method defined.
public class ClientRepository : EntityBaseRepository<Client>, IClientRepository
{
private readonly IContextFactory<JobsLedgerApiContext> _contextFactory;
public ClientRepository(IContextFactory<JobsLedgerApiContext> factory)
{
_contextFactory = factory;
}
// this method will set the protected Context property using the context
// created by the factory
public void SetContext()
{
Context = _contextFactory.CreateDbContext();
}
public void RelatedSuburbEntities(Suburb suburb)
{
Context.Entry(suburb).Reference<State>(a => a.State).Load();
}
}
In the controller, that receives IClientRepository instance, you can set the connection in the HttpContext.Items, which will be valid for the request. This value will then be retrieved by the ContextFactory using IHttpContextAccessor. Then you simply call the _repository.SetContext(); method on the repository.
public class HomeController : Controller
{
private readonly IClientRepository _repository;
public HomeController(IClientRepository repository)
{
_repository = repository;
}
public IActionResult Index()
{
// save the connectionString in the HttpContext.Items
HttpContext.Items["connection-string"] = "test-connection";
// set the context
_repository.SetContext();
return View();
}
}
Make sure you register the IContextFactory in ConfigureServices as open generics and Singleton as below, also register the HttpContextAccessor and IClientRepository
services.AddHttpContextAccessor();
services.AddSingleton(typeof(IContextFactory<>), typeof(ContextFactory<>));
services.AddTransient<IClientRepository, ClientRepository>();
You may define your JobsLedgerAPIContext like this:
public class JobsLedgerAPIContext : DbContext
{
// public DbSet<Job> Jobs { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer("Data Source=localhost;Integrated Security=SSPI;Initial Catalog=dotnetcore;");
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// may need to reflect entity classes and register them here.
base.OnModelCreating(modelBuilder);
}
}
In ASP.NET Core we can register all dependencies during start up, which executed when application starts. Then registered dependencies will be injected in controller constructor.
public class ReportController
{
private IReportFactory _reportFactory;
public ReportController(IReportFactory reportFactory)
{
_reportFactory = reportFactory;
}
public IActionResult Get()
{
vart report = _reportFactory.Create();
return Ok(report);
}
}
Now I want to inject different implementations of IReportFactory based on data in current request (User authorization level or some value in the querystring passed with an request).
Question: is there any built-in abstraction(middleware) in ASP.NET Core where we can register another implementation of interface?
What is the possible approach for this if there no built-in features?
Update
IReportFactory interface was used as a simple example. Actually I have bunch of low level interfaces injected in different places. And now I want that different implementation of those low level interfaces will be injected based on request data.
public class OrderController
{
private IOrderService _orderService;
public OrderController(IOrderService orderService)
{
_orderService = orderService;
}
public IActionResult Create()
{
var order = _orderService.Create();
return Ok(order);
}
}
public class OrderService
{
private OrderBuilder _orderBuilder;
private IShippingService _shippingService; // This now have many different implementations
public OrderService(
OrderBuilder _orderBuilder,
IShippingService _shippingService)
{
_orderService = orderService;
_shippingService = shippingService;
}
public Order Create()
{
var order = _orderBuilder.Build();
var order.ShippingInfo = _shippingService.Ship();
return order;
}
}
Because we know which implementation we need to use on entry point of our application (I think controller action can be considered as entry point of application), we want inject correct implementation already there - no changes required in already existed design.
No, you can't. The IServiceCollection is populated during application startup and built before Configure method is called. After that (container being built), the registrations can't be changed anymore.
You can however implement an abstract factory, be it as factory method or as an interface/class.
// Its required to register the IHttpContextAccessor first
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddScoped<IReportService>(provider => {
var httpContext = provider.GetRequired<IHttpContextAccessor>().HttpContext;
if(httpContext.User.IsAuthorized)
{
return new AuthorizedUserReportService(...);
// or resolve it provider.GetService<AuthorizedUserReportService>()
}
return new AnonymousUserReportService(...);
// or resolve it provider.GetService<AnonymousUserReportService>()
});
Alternatively use an abstract factory class
I'm afraid you can not directly acheive the goal via simple dependency injection , as the the dependency injection configured at Startup stage , in other words , all services and implementions has been configured before a request comming .
However , you can inject a Create Service delegate so that can we create the required service implemention instance in runtime .
For instance , if we have a IReportFactory Interface and two implementions as blew :
public interface IReportFactory
{
object Create();
}
public class ReportFactory1 : IReportFactory
{
public object Create()
{
return new { F = 1, };
}
}
public class ReportFactory2 : IReportFactory {
public object Create()
{
return new { F = 2, };
}
}
As we want to get the required implemention in future , we need to register the Implementions first .
services.AddScoped<ReportFactory1>();
services.AddScoped<ReportFactory2>();
and here's where the magic happens :
We don't register a IReportFactory
We just add a Func<HttpContext,IReportFactory> instead , which is a CreateReportFactoryDelegate
public delegate IReportFactory CreateReportFactoryDelegate(Microsoft.AspNetCore.Http.HttpContext context);
We need add the CreateReportFactoryDelegate to servies too.
services.AddScoped<CreateReportFactoryDelegate>(sp => {
// return the required implemention service by the context;
return context => {
// now we have the http context ,
// we can decide which factory implemention should be returned;
// ...
if (context.Request.Path.ToString().Contains("factory1")) {
return sp.GetRequiredService<ReportFactory1>();
}
return sp.GetRequiredService<ReportFactory2>();
};
});
Now , we can inject a CreateReportFactoryDelegate into controller :
public class HomeController : Controller
{
private CreateReportFactoryDelegate _createReportFactoryDelegate;
public HomeController(CreateReportFactoryDelegate createDelegate) {
this._createReportFactoryDelegate = createDelegate;
// ...
}
public async Task<IActionResult> CacheGetOrCreateAsync() {
IReportFactory reportFactory = this._createReportFactoryDelegate(this.HttpContext);
var x=reportFactory.Create();
// ...
return View("Cache", cacheEntry);
}
}
It is possible by using the HttpContextAccessor in Startup.cs
services.AddHttpContextAccessor();
services.AddScoped<IYourService>(provider =>
{
var contextAccessor = provider.GetService<IHttpContextAccessor>();
var httpContext = contextAccessor.HttpContext;
var contextVariable = httpContext. ...
// Return implementation of IYourService that corresponds to your contextVariable
});
Expanding on #JohanP comment about using IEnumerable
//Program.cs
//get the builder
var builder = WebApplication.CreateBuilder(args);
//register each type
builder.Services.AddScoped<IReport,Report1>();
builder.Services.AddScoped<IReport,Report2>();
builder.Services.AddScoped<IReport,Report3>();
//register the factory class
builder.Services.AddScoped<IReportFactory,ReportFactory>();
//IReport Interface
public interface IReport
{
string ReportType{ get; set; }
}
//ReportFactory.cs
public class ReportFactory : IReportFactory
{
private IEnumerable<IReport> _handlers;
//ctor
public ReportFactory(IEnumerable<IReport> handlers)
=> _handlers = handlers;
internal IReport? Creat(string reportType) =>
_handlers.Where(h => h.ReportType== reportType).First();
}
//Controller
public class ReportController
{
private IReportFactory _reportFactory;
public ReportController(IReportFactory reportFactory)
{
_reportFactory = reportFactory;
}
//modify to your project needs
public IActionResult Get([FromBody] string reportType)
{
if (HttpContext.User.IsAuthorized)
{
var report = _reportFactory.Create(reportType);
return Ok(report);
}
}
}
I have a custom SiteRole class that inherits RoleProvider. I inject the IUserService in the constructor in order to do a query to get all Roles.
It kept on throwing an error that the SiteRole class needed a parameterless constructor. So I ended up injecting the IUserService like this:
public SiteRole()
{
_userService = DependencyResolver.Current.GetService<IUserService>();
}
Inside the class I override the GetRolesForUser function
public override string[] GetRolesForUser(string nickname)
{
return new string[] { _userService.GetRoleForUser(nickname) };
}
The UserService calls this LINQ query in the UserRepository (This line throws System.InvalidOperationException: 'The operation cannot be completed because the DbContext has been disposed.')
public string GetRoleForUser(string nickname)
{
return DbContext.Users.Where(u => u.Nickname == nickname).FirstOrDefault().Role.Name;
}
Any hints on how I can resolve this? You cannot inject dependencies to RoleProvider via constructor so I have to use DependencyResolver.Current.GetService().
Looks like your SiteRole class has a longer lifetime than the injected IUserService. You should check if it's really the source of issue and tune lifetimes of registered services with use of your DI container API. Here are some links for different containers: Autofac, NInject, Unity, Simple injector.
And, probably, change your SiteRole type a bit — use a property to get alive UserService instead of field, with which the only instance is created at time of SiteRole instantiation.
public SiteRole()
{
// this field should be removed
// _userService = DependencyResolver.Current.GetService<IUserService>();
}
// this property should be used instead of field
private IUserService UserService
{
get { DependencyResolver.Current.GetService<IUserService>(); }
}
Or in modern syntax
private IUserService UserService => DependencyResolver.Current.GetService<IUserService>();
But be careful and check that you won't get uncontrolled count of UserService instances created, if it's for example configured as instance per call in your DI container.
I solved the problem by doing this:
virtual Owned<IUserService> ResolveUserService()
=> DependencyResolver.Current.GetService<Owned<IUserService>>();
And call the service in the GetRolesForUser Method with a Using statement:
public override string[] GetRolesForUser(string nickname)
{
using(var userService = ResolveUserService())
{
return new string[] {userService.Value.GetRoleForUser(nickname) };
}
}
On a sidenote, DbContext is constructed in the RepositoryBase. I'm using the Generic Repository Pattern for my project.
public abstract class RepositoryBase<T> where T : class
{
#region properties
private StoreEntities dataContext;
private readonly IDbSet<T> dbSet;
protected IDbFactory DbFactory
{
get;
private set;
}
protected StoreEntities DbContext
{
get { return dataContext ?? (dataContext = DbFactory.Init()); }
}
#endregion
protected RepositoryBase(IDbFactory dbFactory)
{
DbFactory = dbFactory;
dbSet = DbContext.Set<T>();
}
#region Implementation of defaults
public virtual void Add(T entity)...........
Thanks for the help!