How to Resolve/inject Dependency for Attribute based class? - c#

I am creating the following attribute class in an ASP.NET Core 6 Web API.
I need to make a database call from this class and want to get dbContext which is already registered with dependency container.
How do I inject the dbcontext into my customAuthorization class ?
[AttributeUsage(AttributeTargets.Class)]
public class CustomAuthorization : Attribute, IAuthorizationFilter
{
private readonly IServiceProvider _serviceProvider;
public CustomAuthorization(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
public void OnAuthorization(AuthorizationFilterContext filterContext)
{
...................
}
}
[CustomAuthorization()]
public class OrganizationController : CustomControllerBaseClass
{
// .......
}

IAuthorizationFitler has a OnAuthorization(AuthorizationFilterContext filterContext) method, doesn't it?
You could use the filterContext.HttpContext.ServiceProvider.GetRequiredService....
[AttributeUsage(AttributeTargets.Class)]
public class CustomAuthorization : Attribute, IAuthorizationFilter
{
public void OnAuthorization(AuthorizationFilterContext filterContext)
{
var context = filterContext.HttpContext.ServiceProvider.GetRequiredService<Your_DbContext>();
...
}
}
Though, I don't know the scope of the AuthorizationFilter, whether it's singleton, scoped or transient: if it's singleton, you could create a scope on your own:
using(var scope = filterContext.HttpContext.ServiceProvider.CreateScope())
{
var context = scope.ServiceProvider.GetRequiredService<Your_DbContext>();
...
}
I don't know whether I'm wrong somewhere OR there is an easier approach, but this is my quick response.

Related

How to use a generic base controller with ActionFilters

I'm using ILogger<T> and want it to be included in my base controller since every controller should use it.
I'm defining each derived class as UsersController : BaseController<UsersController>
In my base class I'm assigning ILogger<T> which works fine until I try to access the controller in my ActionFilter which I need to know how to cast it.
How do I achieve this or do it in a better way that accomplishes the same thing.
// Base Controller
public class BaseController<T> : ControllerBase
{
protected readonly IOptions<AppSettings> _appSettings;
protected readonly ILogger<T> _logger;
}
// Instance Controller
public class UsersController : BaseController<UsersController>
{
public UsersController(ILogger<UsersController> logger,
IOptions<AppSettings> appSettings)
: base(logger, appSettings)
{
...
}
// ActionFilter used to set viewbag on all pages/layout pages
public class ViewBagActionFilter : ActionFilterAttribute
{
public override void OnResultExecuting(ResultExecutingContext context)
{
// Can't use typeof() or nameof()
if (context.Controller.GetType().BaseType.Name == "BaseController")
{
// This doesn't work either
var controller = context.Controller as BaseController;
controller.ViewBag.Username = controller?.User?.Username;
}
base.OnResultExecuting(context);
}
}
I don't know if this will help but ILogger<T> inherits from ILogger so you could just have your base controller not be generic:
public class BaseController : ControllerBase
{
protected readonly IOptions<AppSettings> _appSettings;
protected readonly ILogger _logger;
}
and the constructor in your inherited controller will still work.
To make it clear, what I meant.
Instead of using BaseController-Class in your Action-Filter, just use ControllerBase... it already has the ViewBag-Property, hasn’t it?
This is my suggestion:
// Base Controller
public class BaseController<T> : ControllerBase
{
protected readonly IOptions<AppSettings> _appSettings;
protected readonly ILogger<T> _logger;
}
// Instance Controller
public class UsersController : BaseController<UsersController>
{
public UsersController(ILogger<UsersController> logger,
IOptions<AppSettings> appSettings)
: base(logger, appSettings)
{
...
}
// ActionFilter used to set viewbag on all pages/layout pages
public class ViewBagActionFilter : ActionFilterAttribute
{
public override void OnResultExecuting(ResultExecutingContext context)
{
// This could work I think, but untested
var controller = context.Controller as ControllerBase;
controller.ViewBag.Username = controller?.User?.Username;
base.OnResultExecuting(context);
}
}
Edit:
If you have Properties, that need to be accessible in Filters, you can define an Interface like this:
public interface IAccessible
{
// your Properties here
string UserName { get;}
dynamic ViewBag { get;}
//...
}
And implement it in your BaseController... you see, the interface doesn’t have to be generic...
Then you can use it in you filter like:
var controller = context.Controller as IAccessible;
controller.ViewBag.Username = controller?.Username;

Ninject to Simple Injector: Register ActionFilter with Controller Attribute (with params)

I have an WebApi application that uses Simple Injector and I'm trying to configure a particular filter with controller attribute (with parameters). I have this configuration working in another project that uses Ninject, but I don't know how to do this on Simple Injector.
public enum UserType {
Director,
Developer,
Leader
}
My controller:
[RequiresAtLeastOneOfUserTypes(UserType.Developer, UserType.Leader)]
public class MyController : Controller
{
...
}
My Attribute:
public sealed class RequiresAtLeastOneOfUserTypesAttribute : Attribute
{
public UserType[] TypesToBeVerified { get; set; }
public RequiresAtLeastOneOfUserTypesAttribute(params UserType[] typesToBeVerified)
{
TypesToBeVerified = typesToBeVerified;
}
}
My Filter:
public class RequiresAtLeastOneOfUserTypesFilter : IActionFilter
{
private readonly IUser _user;
private readonly UserType[] _typesToBeVerified;
protected RequiresAtLeastOneOfUserTypesFilter(IUser user, params UserType[] typesToBeVerified)
{
_user = user;
_typesToBeVerified = typesToBeVerified;
}
public void OnActionExecuting(ActionExecutingContext filterContext)
{
bool authorized = _user.HasAtLeastOneOfTypes(_typesToBeVerified);
if (!authorized)
{
throw new ForbiddenUserException();
}
}
public void OnActionExecuted(ActionExecutedContext filterContext)
{
// do nothing
}
}
And finally my Ninject configuration:
this.BindFilter<RequiresAtLeastOneOfUserTypesFilter>(FilterScope.Controller, 0)
.WhenControllerHas<RequiresAtLeastOneOfUserTypesAttribute>()
.WithConstructorArgumentFromControllerAttribute<RequiresAtLeastOneOfUserTypesAttribute>(
"typesToBeVerified",
attribute => attribute.typesToBeVerified);
My question is: How can I do this configuration using Simple Injector?
The Simple Injector Web API integration packages don't contain an integration feature for action filters as Ninject's integration package does. But such integration can be built in a few lines of code.
There are a few options here. The first option is to revert to resolving services directly from inside your action filter, as demonstrated inside the documentation. This approach is fine when you have a single filter class, but isn't the cleanest approach, and would force you to make changes to your already created filter attribute.
As a second option you can, therefore, create a action filter proxy class, that is able to forward the call to your real filter class, which can than be resolved by Simple Injector:
public class ActionFilterProxy<T> : IActionFilter
where T : IActionFilter
{
public ActionFilterProxy(Container container) => _container = container;
public void OnActionExecuting(ActionExecutingContext filterContext) =>
_container.GetInstance<T>().OnActionExecuting(filterContext);
public void OnActionExecuted(ActionExecutedContext filterContext) =>
_container.GetInstance<T>().OnActionExecuted(filterContext);
}
Using this proxy, you can make the following configuration:
GlobalConfiguration.Configuration.Filters.Add(
new ActionFilterProxy<RequiresAtLeastOneOfUserTypesFilter>(container));
container.Register<RequiresAtLeastOneOfUserTypesFilter>();
This still forces you to make a change to RequiresAtLeastOneOfUserTypesFilter, because Simple Injector can't provide the attribute's information (the UserType[]) to RequiresAtLeastOneOfUserTypesFilter's constructor. Instead,you can change RequiresAtLeastOneOfUserTypesFilter to the following:
public class RequiresAtLeastOneOfUserTypesFilter : IActionFilter
{
private readonly IUser _user;
public RequiresAtLeastOneOfUserTypesFilter(IUser user) => _user = user;
public void OnActionExecuting(ActionExecutingContext filterContext)
{
// Get the attribute from the controller here
var attribute = filterContext.ActionDescriptor.ControllerDescriptor
.GetCustomAttribute<RequiresAtLeastOneOfUserTypesAttribute>();
bool authorized = _user.HasAtLeastOneOfTypes(attribute.TypesToBeVerified);
if (!authorized)
{
throw new ForbiddenUserException();
}
}
public void OnActionExecuted(ActionExecutedContext filterContext)
{
}
}
A third option to use is the one referred to in the documentation, which is described in this blog post, which discusses a model where you place your filters behind an application-specific abstraction and allow them to be Auto-Registered. It uses the a similar proxy approach. This method is useful when you have multiple/many filters that need to be applied (where their order of execution is irrelevant).

How can I get a new ApiAuthorizationDbContext in my Controller

The ApiAuthorizationDbContext is my default DBContext.
Now how do I get this context in a controller?
I can create the normal DBContext with new DbContext() but with the ApiAuthorizationDbContext I have to give options where I don't know how to get them.
My ApiAuthorizationDbContext:
public class ApplicationDbContext : ApiAuthorizationDbContext<ApplicationUser>
{
public DbSet<Tenant> Tenants { get; set; }
public DbSet<SiteSettings> SiteSettings { get; set; }
public ApplicationDbContext(
DbContextOptions options,
IOptions<OperationalStoreOptions> operationalStoreOptions) : base(options, operationalStoreOptions)
{
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
if (!optionsBuilder.IsConfigured)
{
AppSettings.DbOptions(optionsBuilder);
}
}
}
My Controller:
public class TenantHelper
{
private readonly ApplicationDbContext _context;
public TenantHelper(ApplicationDbContext context)
{
_context = context;
}
public static List<Tenant> GetAllTenants()
{
List<Tenant> tenants = new List<Tenant>();
tenants = _context.Tenants.ToList();
return tenants;
}
}
Dependency injection in your application allows you to use any object as a set of functionalities that can be reused by multiple objects, to do add your database context as an dependency injection you should add it to the Startup.cs file that the .NetCore Web Applications default template creates.
using Microsoft.Extensions.DependencyInjection;
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ApplicationDbContext>();
}
Now your context can be injected in any object constructor of your application and you can save it as a property to be accessed at any point of that object scope
public class MyController
{
private readonly ApplicationDbContext _dbContext;
public MyController(ApplicationDbContext dbContext)
{
_dbContext = dbContext;
}
private void MethodA()
{
//accessing dbcontext
_dbContext.MyTable.ToList();
}
}
Edit: Apparently OP meant that he wants his TenantHelper to accessible on all his application, still is a problem that dependency injection solves but just a quick rework needs to be done.
public void ConfigureServices(IServiceCollection services)
{
//This adds your object as a reusable set of functions that is initialized for every different request
services.AddScoped<TenantHelper>();
}
Now do the same process to inject your TenantHelper in your other code as you did on the ApplicationDbContext
Create library with you ApiAuthorizationDbContext and put reference from this lib in your project with controller

InstancePerRequest DbContext ASP.NET MVC

Registering DbContext in ASP.NET MVC Application as InstancePerRequest. (IoC Autofac)
builder.RegisterType<ADbContext>().As<IADbContext>().InstancePerRequest();
Using inside BService
public class BService : IBService
{
readonly IADbContext _dbContext;
public BService(IADbContext dbContext)
{
_dbContext = dbContext;
}
}
Trying to register IBService as Singleton.
builder.RegisterType<BService>().As<IBService>().SingleInstance();
Obviously, this gives me an error
No scope with a tag matching 'AutofacWebRequest' is visible from the scope in which the instance was requested.
Simplest solution is to register IBService as InstancePerRequest, but there is no reason having PerRequest IBService rather than error message mentioned above.
How can i use PerRequest DbContext inside Singleton service ?
First attempt, you can inject IContainer into BService. But this will look like Service locator pattern, which is not good. Otherwise, you can define factory interface
public interface IFactory<T>
{
T GetInstance();
}
Then implement it and register
public class SimpleFactory<T> : IFactory<T>
{
private IContainer _container;
public SimpleFactory(IContainer container)
{
_container = container;
}
public T GetInstance()
{
return _container.Resolve<T>();
}
}
public class DbContextFactory : SimpleFactory<IADbContext>
{
public DbContextFactory(IContainer container):base(container)
{
}
}
Finally, use this factory in your singletone
public class BService : IBService
{
IADbContext _dbContext => _dbContextFactory.GetInstance();
IFactory<IADbContext> _dbContextFactory
public BService(IFactory<IADbContext> dbContextFactory)
{
_dbContextFactory = dbContextFactory;
}
}
Each time, when you want to acess to context inside singletone, it will pass this request to IoC container, which able to return context per request.

IServiceProvider in ASP.NET Core

I starting to learn changes in ASP.NET 5(vNext)
and cannot find how to get IServiceProvider, for example in "Model"'s method
public class Entity
{
public void DoSomething()
{
var dbContext = ServiceContainer.GetService<DataContext>(); //Where is ServiceContainer or something like that ?
}
}
I know, we configuring services at startup, but where all service collection staying or IServiceProvider?
You have to bring in Microsoft.Extensions.DependencyInjection namespace to gain access to the generic
GetService<T>();
extension method that should be used on
IServiceProvider
Also note that you can directly inject services into controllers in ASP.NET 5. See below example.
public interface ISomeService
{
string ServiceValue { get; set; }
}
public class ServiceImplementation : ISomeService
{
public ServiceImplementation()
{
ServiceValue = "Injected from Startup";
}
public string ServiceValue { get; set; }
}
Startup.cs
public void ConfigureService(IServiceCollection services)
{
...
services.AddSingleton<ISomeService, ServiceImplementation>();
}
HomeController
using Microsoft.Extensions.DependencyInjection;
...
public IServiceProvider Provider { get; set; }
public ISomeService InjectedService { get; set; }
public HomeController(IServiceProvider provider, ISomeService injectedService)
{
Provider = provider;
InjectedService = Provider.GetService<ISomeService>();
}
Either approach can be used to get access to the service. Additional service extensions for Startup.cs
AddInstance<IService>(new Service())
A single instance is given all the time. You are responsible for initial object creation.
AddSingleton<IService, Service>()
A single instance is created and it acts like a singleton.
AddTransient<IService, Service>()
A new instance is created every time it is injected.
AddScoped<IService, Service>()
A single instance is created inside of the current HTTP Request scope. It is equivalent to Singleton in the current scope context.
Updated 18 October 2018
See: aspnet GitHub - ServiceCollectionServiceExtensions.cs
I don't think it is a good idea for an entity (or a model) to have access to any service.
Controllers, on the other hand, do have access to any registered service in their constructors, and you don't have to worry about it.
public class NotifyController : Controller
{
private static IEmailSender emailSender = null;
protected static ISessionService session = null;
protected static IMyContext dbContext = null;
protected static IHostingEnvironment hostingEnvironment = null;
public NotifyController(
IEmailSender mailSenderService,
IMyContext context,
IHostingEnvironment env,
ISessionService sessionContext)
{
emailSender = mailSenderService;
dbContext = context;
hostingEnvironment = env;
session = sessionContext;
}
}
use GetRequiredService instead of GetService, like the example on ASP.NET Core tutorials ( https://learn.microsoft.com/en-us/aspnet/core/tutorials/first-mvc-app/working-with-sql )
documentation on the method:
https://learn.microsoft.com/en-us/aspnet/core/api/microsoft.extensions.dependencyinjection.serviceproviderserviceextensions#Microsoft_Extensions_DependencyInjection_ServiceProviderServiceExtensions_GetRequiredService__1_System_IServiceProvider_
using Microsoft.Extensions.DependencyInjection;
using (var context = new ApplicationDbContext(serviceProvicer.GetRequiredService<DbContextOptions<ApplicationDbContext>>()))
Do not use GetService()
The difference between GetService and GetRequiredService is related with exception.
GetService() returns null if a service does not exist.
GetRequiredService() will throw exception.
public static class ServiceProviderServiceExtensions
{
public static T GetService<T>(this IServiceProvider provider)
{
return (T)provider.GetService(typeof(T));
}
public static T GetRequiredService<T>(this IServiceProvider provider)
{
return (T)provider.GetRequiredService(typeof(T));
}
}
Generally you want to have the DI do its thing and inject that for you:
public class Entity
{
private readonly IDataContext dbContext;
// The DI will auto inject this for you
public class Entity(IDataContext dbContext)
{
this.dbContext = dbContext;
}
public void DoSomething()
{
// dbContext is already populated for you
var something = dbContext.Somethings.First();
}
}
However, Entity would have to be automatically instantiated for you... like a Controller or a ViewComponent. If you need to manually instantiate this from a place where this dbContext is not available to you, then you can do this:
using Microsoft.Extensions.PlatformAbstractions;
public class Entity
{
private readonly IDataContext dbContext;
public class Entity()
{
this.dbContext = (IDataContext)CallContextServiceLocator.Locator.ServiceProvider
.GetService(typeof(IDataContext));
}
public void DoSomething()
{
var something = dbContext.Somethings.First();
}
}
But just to emphasize, this is considered an anti-pattern and should be avoided unless absolutely necessary. And... at the risk of making some pattern people really upset... if all else fails, you can add a static IContainer in a helper class or something and assign it in your StartUp class in the ConfigureServices method: MyHelper.DIContainer = builder.Build(); And this is a really ugly way to do it, but sometimes you just need to get it working.
I think the OP is getting confused. Entities should be as “thin” as possible. They should try not to contain logic, and or external references other than navigation properties. Look up some common patterns like repository pattern which helps to abstract your logic away from the entities themselves
Instead of getting your service inline, try injecting it into the constructor.
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddTransient(typeof(DataContext));
}
}
public class Entity
{
private DataContext _context;
public Entity(DataContext context)
{
_context = context;
}
public void DoSomething()
{
// use _context here
}
}
I also suggest reading up on what AddTransient means, as it will have a significant impact on how your application shares instances of DbContext. This is a pattern called Dependency Injection. It takes a while to get used to, but you will never want to go back once you do.

Categories

Resources