ASP.NET MVC 3 작업 필터에 종속성 삽입. 이 접근 방식의 문제점은 무엇입니까?


78

여기에 설정이 있습니다. 서비스 인스턴스가 필요한 작업 필터가 있다고 가정 해 보겠습니다.

public interface IMyService
{
   void DoSomething();
}

public class MyService : IMyService
{
   public void DoSomething(){}
}

그런 다음 해당 서비스의 인스턴스가 필요한 ActionFilter가 있습니다.

public class MyActionFilter : ActionFilterAttribute
{
   private IMyService _myService; // <--- How do we get this injected

   public override void OnActionExecuting(ActionExecutingContext filterContext)
   {
       _myService.DoSomething();
       base.OnActionExecuting(filterContext);
   }
}

MVC 1/2에서 의존성을 액션 필터에 주입하는 것은 엉덩이에 약간의 고통이었습니다. 여기서 볼 수 있듯이 가장 일반적인 방법은 사용자 지정 작업 호출자를 사용하는 것이었다 : http://www.jeremyskinner.co.uk/2008/11/08/dependency-injection-with-aspnet-mvc-action-filters/ 이 해결 방법의 주된 동기는 다음과 같은 접근 방식이 컨테이너와 조잡하고 긴밀한 결합으로 간주 되었기 때문입니다.

public class MyActionFilter : ActionFilterAttribute
{
   private IMyService _myService;

   public MyActionFilter()
      :this(MyStaticKernel.Get<IMyService>()) //using Ninject, but would apply to any container
   {

   }

   public MyActionFilter(IMyService myService)
   {
      _myService = myService;
   }

   public override void OnActionExecuting(ActionExecutingContext filterContext)
   {
       _myService.DoSomething();
       base.OnActionExecuting(filterContext);
   }
}

여기서는 생성자 주입을 사용하고 생성자를 오버로드하여 컨테이너를 사용하고 서비스를 주입합니다. 컨테이너를 ActionFilter와 밀접하게 연결하는 것에 동의합니다.

그래도 내 질문은 이것이다 : 이제 ASP.NET MVC 3에서 사용되는 컨테이너의 추상화가있는 곳 (DependencyResolver를 통해)이 모든 고리가 여전히 필요합니까? 내가 시연하도록 허용 :

public class MyActionFilter : ActionFilterAttribute
{
   private IMyService _myService;

   public MyActionFilter()
      :this(DependencyResolver.Current.GetService(typeof(IMyService)) as IMyService)
   {

   }

   public MyActionFilter(IMyService myService)
   {
      _myService = myService;
   }

   public override void OnActionExecuting(ActionExecutingContext filterContext)
   {
       _myService.DoSomething();
       base.OnActionExecuting(filterContext);
   }
}

이제 일부 순수 주의자들이 이것을 비웃을 수도 있다는 것을 알고 있지만, 진지하게 단점은 무엇일까요? 테스트 시간에 IMyService를 사용하고 그런 방식으로 모의 서비스를 삽입하는 생성자를 사용할 수 있으므로 여전히 테스트 할 수 있습니다. DependencyResolver를 사용하고 있기 때문에 DI 컨테이너 구현에 묶여 있지 않은데,이 접근 방식에 단점이 있습니까?

여기에 새로운 IFilterProvider 인터페이스를 사용하여 MVC3에서이 작업을 수행하는 또 다른 좋은 방법이 있습니다. http://www.thecodinghumanist.com/blog/archives/2011/1/27/structuremap-action-filters-and-dependency-injection-in -asp-net-mvc-3


내 게시물에 링크 해 주셔서 감사합니다. :). 나는 이것이 괜찮을 것이라고 생각한다. 올해 초의 블로그 게시물에도 불구하고 실제로 MVC 3에 포함 된 DI의 열렬한 팬은 아니며 최근에 사용하지 않았습니다. 작동하는 것처럼 보이지만 때때로 어색함을 느낍니다.
Mallioch 2011 년

Ninject를 사용하는 경우 가능한 접근 방식이 될 수 있습니다. stackoverflow.com/questions/6193414/…
Robin van der Knaap

+1, 서비스 로케이터는 많은 사람들이 안티 패턴으로 간주하지만, 단순성과 종속성이 IOC 컨테이너 한곳에서 해결된다는 사실 때문에 Marks보다 접근 방식을 선호한다고 생각하지만 Mark의 예에서는 부트 스트 래퍼와 전역 필터를 등록 할 때 두 곳에서 해결해야합니다.
magritte

당신은 여전히 당신이 원하는 "DependencyResolver.Current.GetService (유형) 언제든지 사용할 수 있습니다.
머트 인 Susur에게

답변:


31

나는 긍정적이지 않지만 빈 생성자 ( 속성 부분)를 사용하고 실제로 값을 주입하는 생성자를 가질 수 있다고 믿습니다 ( 필터 부분). *

편집 : 약간의 내용을 읽은 후이를 수행하는 허용 된 방법은 속성 주입을 통한 것으로 보입니다.

public class MyActionFilter : ActionFilterAttribute
{
    [Injected]
    public IMyService MyService {get;set;}
    
    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        MyService.DoSomething();
        base.OnActionExecuting(filterContext);
    }
}

서비스 로케이터 질문을 사용하지 않는 이유 와 관련하여 : 대부분 종속성 주입의 유연성을 줄입니다. 예를 들어 로깅 서비스를 주입하고 있고 로깅 서비스에 주입되는 클래스의 이름을 자동으로 제공하려면 어떻게해야합니까? 생성자 주입을 사용하면 잘 작동합니다. Dependency Resolver / Service Locator를 사용하고 있다면 운이 좋지 않을 것입니다.

최신 정보

이것이 답으로 받아 들여 졌기 때문에, 나는 Mark Seeman의 접근 방식을 선호한다고 말하고 싶습니다. 이것은 Action Filter 책임을 Attribute와 분리하기 때문입니다. 또한 Ninject의 MVC3 확장에는 바인딩을 통해 작업 필터를 구성하는 매우 강력한 방법이 있습니다. 자세한 내용은 다음 참조를 참조하십시오.

업데이트 2

@usr이 아래 주석에서 지적했듯이 ActionFilterAttributes는 클래스가로드 될 때 인스턴스화되며 애플리케이션의 전체 수명 동안 지속됩니다. IMyService인터페이스가 Singleton이 아니어야 한다면 Captive Dependency가 됩니다. 구현이 스레드로부터 안전하지 않으면 많은 고통을 겪을 수 있습니다.

클래스의 예상 수명보다 수명이 짧은 종속성이있을 때마다 직접 주입하는 것보다 필요에 따라 해당 종속성을 생성하도록 팩토리를 주입하는 것이 좋습니다.


유연성 부족에 대한 의견을 다시 작성하십시오. DependencyResolver 뒤에는이를 구동하는 실제 IOC 컨테이너가 있으므로 개체를 빌드 할 때 원하는 사용자 지정 논리를 바로 추가 할 수 있습니다. 내가 당신의 요점을 따르는 지 잘 모르겠습니다 .....
BFree

@BFree :를 호출 할 때 DependencyResolver.GetService바인딩 메서드는이 종속성이 어떤 클래스에 주입되는지 알지 못합니다. IMyService특정 유형의 작업 필터에 대해 다르게 만들고 싶다면 어떻게해야 합니까? 또는 내 대답에서 말했듯이 MyService구현에 특수 인수를 제공하여 어떤 클래스에 주입되었는지 (로거에 유용함) 알려주려면 어떻게해야합니까?
StriplingWarrior 2011-08-25

좋아, 내가 엉망진창을했는데, 당신은 100 % 맞아요. 현재 해결책이 일어나고있는 "문맥"을 얻을 수있는 방법이 없습니다. 그렇습니다. 그것은 단점입니다. 좋은 지적. 그래도 Inject 속성을 추가하는 것은 내 DependencyResolver 접근 방식이 아닌 특정 컨테이너의 구현에 서비스를 연결하기 때문에 추악하다고 주장합니다. 이 질문을 잠시 열어 두겠습니다. 더 많은 의견을 듣고 싶습니다. 감사!
BFree

3
작업 필터는 MVC 3의 요청간에 공유됩니다. 이것은 스레드에 매우 안전하지 않습니다.
usr

3
네, 반대표를 제거했습니다. 적절하지 않았습니다. 이 댓글은 결국 삭제하겠습니다. 필터를 싱글 톤으로 만들기위한 MVC3 변경은 긍정적 인 가치가없고 매우 위험합니다. 제 의도는 다른 사람들이 생산 과정에서 그 사실을 알게 될 때 문제를 해결하는 것이 었습니다.
usr

92

예, IDependencyResolver 자체에 많은 문제가 있고 , 이러한 문제Singleton Service Locator 및 Bastard Injection 사용을 추가 할 수 있기 때문에 단점 이 있습니다 .

더 나은 옵션은 필터를 원하는 서비스를 삽입 할 수있는 일반 클래스로 구현하는 것입니다.

public class MyActionFilter : IActionFilter
{
    private readonly IMyService myService;

    public MyActionFilter(IMyService myService)
    {
        this.myService = myService;
    }

    public void OnActionExecuting(ActionExecutingContext filterContext)
    {
        if(this.ApplyBehavior(filterContext))
            this.myService.DoSomething();
    }

    public void OnActionExecuted(ActionExecutedContext filterContext)
    {
        if(this.ApplyBehavior(filterContext))
            this.myService.DoSomething();
    }

    private bool ApplyBehavior(ActionExecutingContext filterContext)
    {
        // Look for a marker attribute in the filterContext or use some other rule
        // to determine whether or not to apply the behavior.
    }

    private bool ApplyBehavior(ActionExecutedContext filterContext)
    {
        // Same as above
    }
}

필터가 filterContext를 검사하여 동작을 적용해야하는지 여부를 확인하는 방법을 확인하십시오.

즉, 필터를 적용할지 여부를 제어하기 위해 속성을 계속 사용할 수 있습니다.

public class MyActionFilterAttribute : Attribute { }

그러나 이제 그 속성은 완전히 비활성입니다.

필터는 필수 종속성으로 구성되고 global.asax의 전역 필터에 추가 될 수 있습니다.

GlobalFilters.Filters.Add(new MyActionFilter(new MyService()));

MVC 대신 ASP.NET Web API에 적용되었지만이 기술에 대한 자세한 예제는 다음 문서를 참조하십시오. http://blog.ploeh.dk/2014/06/13/passive-attributes


22
단점이 무엇인지 물었습니다. 단점은 코드 기반의 유지 관리 가능성이 감소하지만 구체적으로 느껴지지는 않습니다 . 이것은 당신을 깜짝 놀라게하는 것입니다. 당신이 제안한 일을하면 경쟁 조건이 생기거나 CPU가 과열되거나 새끼 고양이가 죽을 것이라고 말할 수 없습니다. 그런 일은 일어나지 않을 것이지만, 적절한 디자인 패턴을 따르지 않고 안티 패턴을 피하면 코드가 손상되고 4 년 후 애플리케이션을 처음부터 다시 작성하고 싶을 것입니다. ).
Mark Seemann

4
+1은 속성 코드에서 작업 필터 코드를 분리하는 방법을 보여줍니다. 이 방법은 관심사 분리를 위해서만 선호합니다. 나는 질문의 "이것에 무엇이 잘못되었는지"부분에 대한 모호함에 대한 OP의 좌절감에 감사드립니다. 무언가를 안티 패턴이라고 부르는 것은 쉽지만 그의 특정 코드가 안티 패턴에 대한 대부분의 인수 (단위 테스트 가능성, 구성을 통한 바인딩 등)를 처리 할 때이 패턴이 코드를 썩게 만드는 이유 를 아는 것이 좋습니다. "순수한"코드보다 빠릅니다. 내가 당신과 동의하지 않는다는 것은 아닙니다. 나는 당신의 책 BTW를 즐겼습니다.
StriplingWarrior 2011-08-25

5
@BFree : 그건 그렇고, Remo Gloor는 Ninject의 MVC3 확장으로 환상적인 작업을 수행했습니다. github.com/ninject/ninject.web.mvc/wiki/… 는 Ninject 바인딩을 사용하여 필터를 전역 적으로 등록 할 필요없이 컨트롤러 또는 특정 속성이있는 동작에 적용되는 동작 필터를 정의하는 방법을 설명합니다. 이것은 IoC의 전체 포인트 인 Ninject 바인딩으로 더 많은 제어를 전달합니다.
StriplingWarrior

1
메소드 구현 방법-스케치 : filterContext의 일부인 ActionDescriptor가 ICustomAttributeProvider를 구현하므로 여기에서 마커 속성을 가져올 수 있습니다.
마크 시만

1
: @ 마크 지금에서 4 년 당신이 처음부터 응용 프로그램을 다시 작성하는 것이 좋습니다 (그러나 이해 관계자 당신을 못하게) -하거나 TTM가 너무 길기 때문에 그와 제품 다이를 할 수 있습니다.
요한 게렐

7

Mark Seemann이 제안한 솔루션은 우아해 보입니다. 그러나 간단한 문제에는 꽤 복잡합니다. AuthorizeAttribute를 구현하여 프레임 워크를 사용하는 것이 더 자연스럽게 느껴집니다.

내 솔루션은 global.asax에 등록 된 서비스에 대한 정적 위임 팩토리를 사용하여 AuthorizeAttribute를 만드는 것이 었습니다. 모든 DI 컨테이너에서 작동하며 Service Locator보다 약간 더 좋습니다.

global.asax에서 :

MyAuthorizeAttribute.AuthorizeServiceFactory = () => Container.Resolve<IAuthorizeService>();

내 사용자 정의 속성 클래스 :

[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)]
public class MyAuthorizeAttribute : AuthorizeAttribute
{
    public static Func<IAuthorizeService> AuthorizeServiceFactory { get; set; } 

    protected override bool AuthorizeCore(HttpContextBase httpContext)
    {
        return AuthorizeServiceFactory().AuthorizeCore(httpContext);
    }
}

Service Locator를 MyAuthorizeAttribute와 많이 연결하지 않기 때문에이 코드가 마음에 듭니다.
Akira Yamamoto
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.