I'musing ASP.NET Web API and I need to have authorization so I've created custom authorization attribute
public class CustomAuthorizationAttribute : AuthorizeAttribute
In order to inject dependency inside constructor I have following :
public CustomAuthorizationAttribute(IAccountBL accountBl)
{
_accountBL = accountBl;
}
In IAccountBL I have method which interacts with database checking if user is authorized to make request.
Inside Member API controller I've register that attribute
[CustomAuthorization]
public class MemberController : ApiController
But I get following error
Project.Account.AccountBL' does not contain a constructor that takes 0 arguments
And if I register it like
[CustomAuthorization(IAccountBL)]
Thank you
Action filters are just attributes. You do not have control over when those attributes are instantiated by the CLR. One possibility is to write a marker attribute:
public class CustomAuthorizationAttribute : Attribute { }
and then the actual action filter:
public class CustomAuthorizationFilter : ActionFilterAttribute
{
private readonly IAccountBL accountBL;
public CustomAuthorizationFilter(IAccountBL accountBL)
{
this.accountBL = accountBL;
}
public override void OnActionExecuting(HttpActionContext actionContext)
{
if (actionContext.ControllerContext.ControllerDescriptor.GetCustomAttributes<CustomAuthorizationAttribute>().Any() ||
actionContext.ActionDescriptor.GetCustomAttributes<CustomAuthorizationAttribute>().Any())
{
// here you know that the controller or action is decorated
// with the marker attribute so that you could put your code
}
}
}
and finally register it as a global action filter:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
...
IAccountBL accountBL = ...
config.Filters.Add(new CustomAuthorizationFilter(accountBL));
}
}
and finally you could use the marker attribute:
[CustomAuthorization]
public class MemberController : ApiController
{
...
}
You can get dependency in your filter by using extension method GetDependencyScope for class HttpRequestMessage. It's not a canonical way for dependency injection, but can be used as workaround. A basic example may look like this:
public Task AuthenticateAsync(HttpAuthenticationContext context, CancellationToken cancellationToken)
{
var dependencyScope = context.Request.GetDependencyScope();
var dependency = dependencyScope.GetService(typeof (MyDependencyType));
//use your dependency here
}
This method may be used with constructor injection to simplify unit testing:
public class MyAuthenticationFilter : Attribute, IAuthenticationFilter
{
private Func<HttpRequestMessage, MyDependencyType> _dependencyFactory;
public MyAuthenticationFilter() :
this(request => (MyDependencyType)request.GetDependencyScope().GetService(typeof(MyDependencyType)))
{
}
public MyAuthenticationFilter(Func<HttpRequestMessage, MyDependencyType> dependencyFactory)
{
_dependencyFactory = dependencyFactory;
}
public Task AuthenticateAsync(HttpAuthenticationContext context, CancellationToken cancellationToken)
{
var dependencyScope = context.Request.GetDependencyScope();
var dependency = dependencyFactory.Invoke(context.Request);
//use your dependency here
}
public Task ChallengeAsync(HttpAuthenticationChallengeContext context, CancellationToken cancellationToken)
{
throw new NotImplementedException();
}
public bool AllowMultiple { get; private set; }
}
If anyone finds similar issue here's how I manage to solve it.
My custom filter inherits IAutofacAuthorizationFilter. Besides this one you can also inherit IAutofacExceptionFilter and IAutofacActionFilter.
And inside my DI container I've register this filter for each controller I want to use like this
builder.Register(c => new CustomAuthorizationAttribute(c.Resolve<IAccountBL>()))
.AsWebApiAuthorizationFilterFor<MemberController>()
.InstancePerApiRequest();
If you registered your service on the application using any container, it's very easy to get the instance of your service from anywhere in the scope. Just follow the below code to get your service.
var myService = DependencyResolver.Current.GetService(typeof(IMyService)) as IMyService;
Please make sure you have included System.Web.Mvc in the file.
Happy coding!!!
Related
How can i use dependency injection in my custom attribute?
Hi,i am writing an custom attribute and i need use some interface method on my custom attribute . so need to build contractor for inject my service(interface), if i do this
when I want to use my attribute, it requires an input of that interface type, what can i do?
To be honest, I have no idea how to solve this problem.
You can use TypeFilter coupled with an attribute using normal DI with nothing special see example(async) below
Controller method decorated with attribute:
[TypeFilter(typeof(TestAttribute))]
public async Task CreateAsync()
{
//your method here
return null;
}
Attribute:
public class TestAttribute : Attribute, IAsyncAuthorizationFilter
{
private readonly IIdentityService _identityService;
public TestAttribute(IIdentityService identityService)
{
_identityService = identityService;
}
public async Task OnAuthorizationAsync(AuthorizationFilterContext context)
{
ApplicationUser user = await _identityService.CurrentUser();
if (user.TestId == null)
{
context.Result = new UnauthorizedResult();
}
}
}
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).
I have implemented the IAsyncAuthorizationFilter interface to a class; in addition, that class is derived from Attribute, so that I can mark controller classes and action methods using it. This works so far; the Task OnAuthorizationAsync(AuthorizationFilterContext context) method is called before the action method, and if the request is not authenticated, an HTTP 401 is returned for the response.
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public sealed class CustomAuthenticationAttribute : Attribute, IAsyncAuthorizationFilter
{
public async Task OnAuthorizationAsync(AuthorizationFilterContext context)
{
...
}
}
This attribute is used as follows...
[CustomAuthenticationAttribute]
public class SomeDataController : Controller
{
[HttpGet]
public async Task GetData()
{
...
}
}
Now, I want to use an application service (which obtains secret private key information required for authentication from a database) and tried to use property injection for that. Injecting dependencies via the ctor is not a good choice here since it´s implemented as an attribute. So I tried...
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public sealed class CustomAuthenticationAttribute : Attribute, IAsyncAuthorizationFilter
{
public IPrivateKeyLookupService KeyService { get; set; }
public async Task OnAuthorizationAsync(AuthorizationFilterContext context)
{
string publicKey = ...
...
var privateKey = await this.KeyService.GetPrivateKeyFrom(publicKey);
...
}
}
...but property injection does not seem to work here. The service is registered with the IoC, but properties do not get wired. This is an ASP.NET Core 1.1 project in which I use Autofac. In the Startup class my ConfigureServices method has something like that...
public void ConfigureServices(IServicesCollection collection)
{
...
var containerBuilder = new ContainerBuilder();
containerBuilder.RegisterType<AuthKeyService>().As<IPrivateKeyLookupService>();
containerBuilder.Populate(services);
this.container = containerBuilder.Build();
}
Does Autofac support auto-wiring for IAsyncAuthorizationFilter types? And if not, how could I polyfill that functionality?
It turned out to be very simple...
In ASP.NET Core there is the TypeFilter attribute which is also a filter that creates another filter (specified by Type) and satisfies its constructor arguments by involving dependency injection.
I changed the implementation of IAsyncAuthorizationFilter; removed the properties and added a ctor instead, because with TypeFilter ctor injection would work now...
public sealed class CustomAuthenticationAttribute : IAsyncAuthorizationFilter
{
private readonly IPrivateKeyLookupService keyService;
public CustomAuthenticationAttribute(
IPrivateKeyLookupService keyService)
{
this.keyService = keyService;
}
public async Task OnAuthorizationAsync(AuthorizationFilterContext context)
{
...
}
}
I also removed inheritance from Attribute because it´s not needed anymore as well as the declaration of the AttributeUsage attribute.
So, I can use my attribute as follows...
[TypeFilter(typeof(CustomAuthenticationAttribute))]
public class SomeDataController : Controller
{
[HttpGet]
public async Task GetData()
{
...
}
}
Passing additional arguments to the filter´s ctor is also possible via the object[] Arguments property of the TypeFilter class.
A normal thing one needs to do is get ISomething that you registered previously, especially when you implement custom attributes bet really anywhere in the HTTP pipeline will work.
Basically what you like to do is get your "hands" on a HttpContext and use the IServiceProvider located in HttpContext.RequestServices
From there it's all easy sailing if you included
using Microsoft.Extensions.DependencyInjection;
Let me demonstrate this in a small sample where I get to the registered IMemoryCache (you need to register it for this to work of course by adding services.AddMemoryCache(); in your startup.cs)
Here is the copy past stuff:
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.DependencyInjection;
public class SomeAttribute : Attribute,IAuthorizationFilter
{
public void OnAuthorization(AuthorizationFilterContext context )
{
IMemoryCache memory= context.HttpContext.RequestServices.GetService<IMemoryCache>();
}
}
Every call to my WebAPI may (or may not) contain the GET parameter
/api/SomeControllerFunction?loglevel=(someint)
From the function inside the controller I can initialize a LogCollector:
[HttpGet]
SomeControllerFunction(int loglevel = 0)
{
LogCollector logger = new LogCollector(loglevel)
}
To not repeat myself too often, I want to hide this in the class hierarchy by adding it into the constructor of a BaseController, from which all my controllers shall inherit:
public class BaseController: ApiController
{
internal LogCollector Logger
BaseController()
{
Logger = new LogCollector(loglevel);
}
But how can I access a GET parameter from the constructor?
Instead of using the constructor you could inject the LogCollector directly into the method. If you did want to use the constructor you should use a Di / IoC framework as that would be more appropriate.
In the example below you can use a custom ActionFilterAttribute instance which injects the Logger based the incoming (optional) log level. The log level is then defined in the route using a RouteAttribute on the action. The RouteAttribute also defines a default value for the log level so it is not required when calling that action.
LogInjectorFilterAttribute.cs
public class LogInjectorFilterAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
const string key = "loglevel";
if(actionContext.ControllerContext.RouteData.Values.ContainsKey(key))
{
var loglevel = int.Parse(actionContext.ControllerContext.RouteData.Values[key].ToString());
LogCollector logger = new LogCollector(loglevel);
actionContext.ActionArguments["logger"] = logger;
}
base.OnActionExecuting(actionContext);
}
}
HomeController.cs
[HttpGet]
[Route("api/Home/Get/{loglevel:int=1}")]
[LogInjectorFilter]
public IHttpActionResult Get(LogCollector logger)
{
}
The constructor is invoked too early, you can't access the parameters from there. However, you can override the Initialize method and retrieve the GET parameters from the context:
protected override void Initialize(HttpControllerContext controllerContext)
{
foreach (var parameter in controllerContext.Request.GetQueryNameValuePairs())
{
Debug.WriteLine(string.Format("{0} = {1}", parameter.Key, parameter.Value));
}
base.Initialize(controllerContext);
}
I am trying to inject a service into my action filter but I am not getting the required service injected in the constructor. Here is what I have:
public class EnsureUserLoggedIn : ActionFilterAttribute
{
private readonly ISessionService _sessionService;
public EnsureUserLoggedIn()
{
// I was unable able to remove the default ctor
// because of compilation error while using the
// attribute in my controller
}
public EnsureUserLoggedIn(ISessionService sessionService)
{
_sessionService = sessionService;
}
public override void OnActionExecuting(ActionExecutingContext context)
{
// Problem: _sessionService is null here
if (_sessionService.LoggedInUser == null)
{
context.HttpContext.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
context.Result = new JsonResult("Unauthorized");
}
}
}
And I am decorating my controller like so:
[Route("api/issues"), EnsureUserLoggedIn]
public class IssueController : Controller
{
}
Startup.cs
services.AddScoped<ISessionService, SessionService>();
Using these articles as reference:
ASP.NET Core Action Filters
Action filters, service filters and type filters in ASP.NET 5 and MVC 6
Using the filter as a ServiceFilter
Because the filter will be used as a ServiceType, it needs to be registered with the framework IoC. If the action filters were used directly, this would not be required.
Startup.cs
public void ConfigureServices(IServiceCollection services) {
services.AddMvc();
services.AddScoped<ISessionService, SessionService>();
services.AddScoped<EnsureUserLoggedIn>();
...
}
Custom filters are added to the MVC controller method and the controller class using the ServiceFilter attribute like so:
[ServiceFilter(typeof(EnsureUserLoggedIn))]
[Route("api/issues")]
public class IssueController : Controller {
// GET: api/issues
[HttpGet]
[ServiceFilter(typeof(EnsureUserLoggedIn))]
public IEnumerable<string> Get(){...}
}
There were other examples of
Using the filter as a global filter
Using the filter with base controllers
Using the filter with an order
Take a look, give them a try and see if that resolves your issue.
Hope this helps.
Global filters
You need to implement IFilterFactory:
public class AuthorizationFilterFactory : IFilterFactory
{
public bool IsReusable => false;
public IFilterMetadata CreateInstance(IServiceProvider serviceProvider)
{
// manually find and inject necessary dependencies.
var context = (IMyContext)serviceProvider.GetService(typeof(IMyContext));
return new AuthorizationFilter(context);
}
}
In Startup class instead of registering an actual filter you register your filter factory:
services.AddMvc(options =>
{
options.Filters.Add(new AuthorizationFilterFactory());
});
One more way for resolving this problem. You can get your service via Context as in the following code:
public override void OnActionExecuting(ActionExecutingContext context)
{
_sessionService = context.HttpContext.RequestServices.GetService<ISessionService>();
if (_sessionService.LoggedInUser == null)
{
context.HttpContext.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
context.Result = new JsonResult("Unauthorized");
}
}
Please note that you have to register this service in Startup.cs
services.AddTransient<ISessionService, SessionService>();
Example
private ILoginService _loginService;
public override void OnActionExecuting(ActionExecutingContext context)
{
_loginService = (ILoginService)context.HttpContext.RequestServices.GetService(typeof(ILoginService));
}
Hope it helps.
After reading this article ASP.NET Core - Real-World ASP.NET Core MVC Filters (Aug 2016) I implemented it like this:
In Starup.cs / ConfigureServices:
services.AddScoped<MyService>();
In MyFilterAttribute.cs:
public class MyFilterAttribute : TypeFilterAttribute
{
public MyFilterAttribute() : base(typeof (MyFilterAttributeImpl))
{
}
private class MyFilterAttributeImpl : IActionFilter
{
private readonly MyService _sv;
public MyFilterAttributeImpl(MyService sv)
{
_sv = sv;
}
public void OnActionExecuting(ActionExecutingContext context)
{
_sv.MyServiceMethod1();
}
public void OnActionExecuted(ActionExecutedContext context)
{
_sv.MyServiceMethod2();
}
}
}
In MyFooController.cs :
[MyFilter]
public IActionResult MyAction()
{
}
Edit: Passing arguments like [MyFilter("Something")] can be done using the Arguments property of the TypeFilterAttribute class: How do I add a parameter to an action filter in asp.net? (rboe's code also shows how to inject things (the same way))
While the question implicitly refers to "filters via attributes", it is still worth highlighting that adding filters "globally by type" supports DI out-of-the-box:
[For global filters added by type] any constructor dependencies will be populated by dependency injection (DI). Adding a filter by type is equivalent to filters.Add(new TypeFilterAttribute(typeof(MyFilter))).
https://learn.microsoft.com/en-us/aspnet/core/mvc/controllers/filters?view=aspnetcore-2.2#dependency-injection
With regards to attribute-based filters:
Filters that are implemented as attributes and added directly to controller classes or action methods cannot have constructor dependencies provided by dependency injection (DI). This is because attributes must have their constructor parameters supplied where they're applied. This is a limitation of how attributes work.
https://learn.microsoft.com/en-us/aspnet/core/mvc/controllers/filters?view=aspnetcore-2.2#dependency-injection
However, as mentioned in the previous answers to the OP, there are ways of indirection that can be used to achieve DI. For the sake of completeness, here are the links to the official docs:
ServiceFilterAttribute
TypeFilterAttribute
IFilterFactory implemented on your attribute