Как использовать инъекцию зависимостей с атрибутом?
В проекте MVC, который я создаю, у меня есть следующий RequirePermissionAttribute
, который помещается в любое действие, которое требует определенных разрешений (в этом примере было упрощено):
public class RequirePermissionAttribute : ActionFilterAttribute, IAuthorizationFilter
{
public Operation Permissions { get; set; }
public RequirePermissionAttribute() { }
public RequirePermissionAttribute(Operation permissions)
{
this.Permissions = permissions;
}
public bool AuthorizeCore(HttpContextBase httpContext)
{
IAuthorizationService authServ = new ASPNETAuthorizationService();
return authServ.Authorize(httpContext);
}
public void OnAuthorization(AuthorizationContext filterContext)
{
Enforce.ArgNotNull(filterContext);
if (this.AuthorizeCore(filterContext.HttpContext))
{
// code snipped.
}
else
{
// code snipped.
}
}
}
Таким образом, очевидно, что проблема заключается в том, что мой атрибут authorize имеет зависимость от созданного мной ASPNETAuthorizationService
. Я не могу идти по пути конструктора, так как атрибуты проверяются во время компиляции.
Одна вещь, которую я упоминаю, я использую свой собственный маленький IoC, который я сделал, и у него нет поддержки вложения свойств (пока). Конечно, если бы я пошел по маршруту впрыска имущества, мне пришлось бы добавить поддержку для него (что мне нужно было бы провести некоторое исследование).
Какой лучший способ внести что-то в класс атрибута?
Ответы
Ответ 1
Я изначально думал, что это невозможно, но я стою исправлено. Вот пример с Ninject:
http://codeclimber.net.nz/archive/2009/02/10/how-to-use-ninject-to-inject-dependencies-into-asp.net-mvc.aspx
Обновление 2016-10-13
Это довольно старый вопрос, и рамки сильно изменились. Теперь Ninject позволяет вам добавлять привязки к определенным фильтрам на основе наличия определенных атрибутов с таким кодом:
// LogFilter is applied to controllers that have the LogAttribute
this.BindFilter<LogFilter>(FilterScope.Controller, 0)
.WhenControllerHas<LogAttribute>()
.WithConstructorArgument("logLevel", Level.Info);
// LogFilter is applied to actions that have the LogAttribute
this.BindFilter<LogFilter>(FilterScope.Action, 0)
.WhenActionHas<LogAttribute>()
.WithConstructorArgument("logLevel", Level.Info);
// LogFilter is applied to all actions of the HomeController
this.BindFilter<LogFilter>(FilterScope.Action, 0)
.WhenControllerTypeIs<HomeController>()
.WithConstructorArgument("logLevel", Level.Info);
// LogFilter is applied to all Index actions
this.BindFilter(FilterScope.Action, 0)
.When((controllerContext, actionDescriptor) =>
actionDescriptor.ActionName == "Index")
.WithConstructorArgument("logLevel", Level.Info);
Это соответствует принципу, аргументированному Mark Seeman и автором Simple Injector, который заключается в том, что вы должны сохранять логику своего фильтра действий отдельно от пользовательского класса атрибутов.
MVC 5 и 6 также значительно упрощают вносить значения в атрибуты, чем это было раньше. Тем не менее, отделить ваш фильтр действий от вашего атрибута действительно лучший подход.
Ответ 2
Какой лучший способ внести что-то в класс атрибута?
Строго говоря, мы не можем использовать инъекцию зависимостей для инъекции зависимости в атрибут. Атрибуты для метаданных не являются поведением. [AttributeSpecification()]
поощряет это, запрещая ссылочные типы в качестве аргументов.
То, что вы, вероятно, ищете, это использовать атрибут и фильтр вместе, а затем вставлять зависимости в фильтр. Атрибут добавляет метаданные, которые определяют, применять ли фильтр, и фильтр получает вложенные зависимости.
Как использовать инъекцию зависимостей с атрибутом?
Есть очень мало причин для этого.
Тем не менее, если вы намерены вводить атрибут, вы можете использовать ASP.NET Core MVC IApplicationModelProvider
. Структура передает зависимости в конструктор поставщика, и поставщик может передавать зависимости к свойствам или методам атрибута.
В своем автозагрузке зарегистрируйте своего провайдера.
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Mvc.ApplicationModels;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.TryAddEnumerable(ServiceDescriptor.Transient
<IApplicationModelProvider, MyApplicationModelProvider>());
services.AddMvc();
}
public void Configure(IApplicationBuilder app)
{
app.UseMvc();
}
}
Используйте инъекцию конструктора в провайдере и передайте эти зависимости атрибуту.
using System.Linq;
using Microsoft.AspNetCore.Mvc.ApplicationModels;
using Microsoft.AspNetCore.Mvc.Routing;
public class MyApplicationModelProvider : IApplicationModelProvider
{
private IUrlHelperFactory _urlHelperFactory;
// constructor injection
public MyApplicationModelProvider(IUrlHelperFactory urlHelperFactory)
{
_urlHelperFactory = urlHelperFactory;
}
public int Order { get { return -1000 + 10; } }
public void OnProvidersExecuted(ApplicationModelProviderContext context)
{
foreach (var controllerModel in context.Result.Controllers)
{
// pass the depencency to controller attibutes
controllerModel.Attributes
.OfType<MyAttribute>().ToList()
.ForEach(a => a.UrlHelperFactory = _urlHelperFactory);
// pass the dependency to action attributes
controllerModel.Actions.SelectMany(a => a.Attributes)
.OfType<MyAttribute>().ToList()
.ForEach(a => a.UrlHelperFactory = _urlHelperFactory);
}
}
public void OnProvidersExecuting(ApplicationModelProviderContext context)
{
// intentionally empty
}
}
Создайте атрибут с публичными сеттерами, которые могут получать зависимости.
using System;
using Microsoft.AspNetCore.Mvc.Routing;
public sealed class MyAttribute : Attribute
{
private string _someParameter;
public IUrlHelperFactory UrlHelperFactory { get; set; }
public MyAttribute(string someParameter)
{
_someParameter = someParameter;
}
}
Применить атрибут к контроллеру или к действию.
using Microsoft.AspNetCore.Mvc;
[Route("api/[controller]")]
[MyAttribute("SomeArgument")]
public class ValuesController : Controller
{
[HttpGet]
[MyAttribute("AnotherArgument")]
public string Get()
{
return "Foobar";
}
}
Вышеприведенный пример демонстрирует один способ - для случая редкого использования - вы можете вставлять зависимости в атрибут. Если вы выясните действительную причину этого, отправьте его в комментариях.