Подтвердить что ты не робот

Как использовать инъекцию зависимостей с атрибутом?

В проекте 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, который я сделал, и у него нет поддержки вложения свойств (пока). Конечно, если бы я пошел по маршруту впрыска имущества, мне пришлось бы добавить поддержку для него (что мне нужно было бы провести некоторое исследование).

Какой лучший способ внести что-то в класс атрибута?

4b9b3361

Ответ 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";
    }
}

Вышеприведенный пример демонстрирует один способ - для случая редкого использования - вы можете вставлять зависимости в атрибут. Если вы выясните действительную причину этого, отправьте его в комментариях.