ASP.NET MVC 는 확장을 손쉽게 할 수 있도록 많은 기능을 가지고 있습니다. 특히 내부에 숨기지 않고, Component 들을 자유스럽게 확장할 수 있도록 API 를 열어준 게 상당히 인상 깊습니다. (그 동안의 Microsoft사의 행태와는 달리)
인터넷 예제, 특히 Phill Haack 는 NInject 에 의한 예제를 많이 제공하는 데, Castle.Windsor 로 확장하는 예제는 Windsor Tutorial = ASP.NET MVC 3 application (To be seen) 가 가장 잘 되어 있는 것 같습니다.
저는 Castle.Windsor 공식 사이트의 Tutorial과는 약간 차이가 있지만, 유사하게 한번 만들어 봤습니다.
1. Controller 를 제공할 IWindsorInstaller를 구현합니다.
단 여기서 Lifestyle은 PerWebRequest가 아닌 Transient 이어야만 합니다. WebRequest 한번에 여러 번의 Controller 를 사용할 경우가 있기 때문입니다.
public class ControllerWindsorInstaller : IWindsorInstaller
{
#region << logger >>
private static readonly NLog.Logger log = NLog.LogManager.GetCurrentClassLogger();
private static readonly bool IsDebugEnabled = log.IsDebugEnabled;
#endregion
public void Install(IWindsorContainer container, IConfigurationStore store)
{
if(IsDebugEnabled)
log.Debug("System.Web.Mvc.IController 서비스의 모든 구현 클래스를 Windsor Container에 등록합니다...");
container.Register(
AllTypes
.FromThisAssembly()
.BasedOn<System.Web.Mvc.IController>()
.WithServiceFromInterface(typeof(System.Web.Mvc.IController))
.LifestyleTransient()
);
}
}
2. IWindsor Container 를 생성하고, 관련 IWindsorInstaller를 설치하도록 합니다.
이런 작업은 global.asax.cs 의 Application_Start() 에서 수행해주셔야 합니다. 우선 Container 를 빌드하는 코드를 보시면
protected override IWindsorContainer SetUpContainer()
{
if(IsDebugEnabled)
log.Debug("Windsor Container를 생성하고, Component를 등록합니다...");
var container = new WindsorContainer();
container.Install(Configuration.FromAppConfig(),
FromAssembly.This(),
FromAssembly.Containing<SportsStore.Domain.Services.IOrderService>(),
FromAssembly.Containing<NSoft.NFramework.ISerializer>());
return container;
}
와 같이 됩니다. 위의 코드에서 container.Install() 메소드에서, 여러 Assembly에서 IWindsorInstaller 를 구현한 클래스를 찾아 Container에 등록을 수행하도록 합니다.
만약 위의 코드가 복잡하고, 귀찮다면 FromAssembly.FromThisApplication() 으로 대체하면 현 Application이 참조하는 모든 Assembly에서 IWindsorInstaller 를 찾아서 등록하게 됩니다.
3. WindorContollerFactory 구현
IController를 Castle.Windsor Container로부터 제공받도록 하는 클래스를 구현합니다.
Castle.Windsor Container로부터 Resolve하는 메소드는 IoC 라는 Helper class에서 수행합니다만, 그냥 1번에서 만든 container를 static 으로 가지면서, 사용하시면 됩니다.
public class WindsorControllerFactory : DefaultControllerFactory
{
#region << logger >>
private static readonly NLog.Logger log = NLog.LogManager.GetCurrentClassLogger();
private static readonly bool IsDebugEnabled = log.IsDebugEnabled;
#endregion
protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType)
{
//if(controllerType == null)
// throw new System.Web.HttpException(404, string.Format(NotFoundController, requestContext.HttpContext.Request.Path));
if(controllerType == null || !controllerType.HasInterface(typeof(IController)))
return base.GetControllerInstance(requestContext, controllerType);
if(IsDebugEnabled)
log.Debug("경로 [{0}]에 해당하는 Controller [{1}]를 생성합니다...", requestContext.HttpContext.Request.Path, controllerType);
return IoC.Resolve<IController>(controllerType.FullName);
}
}
4. 마지막으로 WindsorControllerFactory 를 MVC 시스템의 기본 Controller Factory로 설정합니다.
이 작업은 global.asax.cs의 Application_Start() 메소드에서 한번만 수행하도록 합니다.
public override void Application_Start(object sender, EventArgs e)
{
base.Application_Start(sender, e);
AreaRegistration.RegisterAllAreas();
BundleTable.Bundles.EnableDefaultBundles();
RegisterGlobalFilters(GlobalFilters.Filters);
RegisterRoutes(RouteTable.Routes);
// NOTE: ControllerFactory 를 Castle.Windsor Container로부터 생성되도록 한다.
//
ControllerBuilder.Current.SetControllerFactory(new WindsorControllerFactory());
ModelBinders.Binders.Add(typeof(Cart), new CartModelBinder());
// NOTE: FluentValidation 을 이용하여, Client-side 및 Server-side 모두 Validation이 가능하도록 합니다.
FluentValidationModelValidatorProvider.Configure();
}
사실 IoC/DI 의 어떤 라이브러리라도, 쉽게 구현이 가능하도록 되어있어서, NInject 라이브러리 예제를 Castle.Windsor로 변환하는 작업이 의외로 쉬웠습니다.
기존에 Castle.Windsor 도 xml 로 configuration 을 많이 했었는데, 이제는 Code로 설정하는 Fluent API 방식이 더 유리하더군요.
다음 번에는 MVC 에서 제공하는 기본 Validation 보다 FluentValidation 에서 제공하는 기능에 대해 알아보도록 하겠습니다. (제가 Attribute 사용보다 Class 를 만드는 걸 선호해서요^^)
댓글 없음:
댓글 쓰기