ASP.NET MVC 3 작업 필터에 종속성 삽입. 이 접근 방식의 문제점은 무엇입니까?
여기에 설정이 있습니다. 서비스 인스턴스가 필요한 작업 필터가 있다고 가정 해 보겠습니다.
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
나는 긍정적 인 것은 아니지만, 빈 생성자 ( 속성 부분)를 사용하고 실제로 값을 주입하는 생성자를 가질 수 있다고 믿습니다
(
필터 부분). *
편집 : 약간의 내용을 읽은 후이를 수행하는 데 허용되는 방법은 속성 주입을 통한 것으로 보입니다.
public class MyActionFilter : ActionFilterAttribute
{
[Injected]
public IMyService MyService {get;set;}
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
MyService.DoSomething();
base.OnActionExecuting(filterContext);
}
}
Regarding the why not use a Service Locator question: It mostly just reduces the flexibility of your dependency injection. For example, what if you were injecting a logging service, and you wanted to automatically give the logging service the name of the class it's being injected into? If you use constructor injection, that would work great. If you're using a Dependency Resolver/Service Locator, you'd be out of luck.
Update
Since this got accepted as the answer, I'd like to go on the record to say that I prefer Mark Seeman's approach because it separates the Action Filter responsibility away from the Attribute. Furthermore, Ninject's MVC3 extension has some very powerful ways to configure action filters via bindings. See the following references for more details:
- https://github.com/ninject/ninject.web.mvc/wiki/Dependency-injection-for-filters
- https://github.com/ninject/ninject.web.mvc/wiki/Conditional-bindings-for-filters
- https://github.com/ninject/ninject.web.mvc/wiki/Filter-configurations
Update 2
As @usr pointed out in the comments below, ActionFilterAttribute
s are instantiated when the class is loaded, and they last the entire lifetime of the application. If the IMyService
interface is not supposed to be a Singleton, then it ends up being a Captive Dependency. If its implementation isn't thread-safe, you could be in for a lot of pain.
Whenever you have a dependency with a shorter lifespan than your class's expected lifespan, it's wise to inject a factory to produce that dependency on-demand, rather than injecting it directly.
Yes, there are downsides, as there are lots of issues with IDependencyResolver itself, and to those you can add the use of a Singleton Service Locator, as well as Bastard Injection.
A better option is to implement the filter as a normal class into which you can inject whichever services you'd like:
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
}
}
Notice how the filter examines the filterContext to determine whether or not the behavior should be applied.
This means that you can still use attributes to control whether or not the filter should be applied:
public class MyActionFilterAttribute : Attribute { }
However, now that attribute is completely inert.
The filter can be composed with the required dependency and added to the global filters in global.asax:
GlobalFilters.Filters.Add(new MyActionFilter(new MyService()));
For a more detailed example of this technique, although applied to ASP.NET Web API instead of MVC, see this article: http://blog.ploeh.dk/2014/06/13/passive-attributes
The solution Mark Seemann suggested seems elegant. However pretty complex for a simple problem. Using the framework by implementing AuthorizeAttribute feels more natural.
My solution was to create a AuthorizeAttribute with a static delegate factory to a service registered in global.asax. It works for any DI container and feels slightly better than a Service Locator.
In global.asax:
MyAuthorizeAttribute.AuthorizeServiceFactory = () => Container.Resolve<IAuthorizeService>();
My custom attribute class:
[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);
}
}
'Nice programing' 카테고리의 다른 글
앱 충돌 방지를 위해 try / catch 사용 (0) | 2020.10.14 |
---|---|
Github : 내 저장소를 체크 아웃하는 방법 (0) | 2020.10.14 |
Facebook의 Graph API 호출 제한은 얼마입니까? (0) | 2020.10.14 |
Android : 애니메이션을 사용하여보기 표시 / 숨기기 (0) | 2020.10.14 |
Bitbucket에서 git을 사용하여 Heroku에 배포 (0) | 2020.10.14 |