Asp.net MVC 示例项目"Suteki.Shop"分析之---IOC(控制反转)

      在Suteki.Shop中,未使用微软自已的Unity框架来实现IOC,而是使用了大名鼎鼎Castle Windsor。
因为引用了Windsor,就有必要简要介绍一下。而我的理解,这个IOC容器(Container)包括下面几个
重要概念:
 

      容器(Container):Windsor是一个反转控制容器。它创建在一个微内核的基础之上,这个微内
核能够扫描类并且试图找到这些类用到哪些对象引用、对象依赖,然后把这些依赖信息提供给类使用

      组件(Component):也就是我们通常所说的业务逻辑单元及相应的功能实现,组件是一个可复
用的代码单元。它应该实现并暴露为一个服务。组件是实现一个服务或接口的类

      服务(Service) :也就是相应的组件接口或N个Component按业务逻辑组合而成的业务逻辑接口。
接口是服务的规范,它创建一个抽象层,你可以轻松的替换服务的实现

      扩张单元插件(Facilities):提供(可扩张)容器以管理组件
      
     我们可以直接使用组件(会在下面的内容中提到),也可以把组件转换成相应的服务接口来使用。
 
     还记得上一篇文章中提到的Service吗? 说白了,它就是一个服务。而Suteki.Shop做的更“夸张”,
只要是带有业务逻辑性质的功能代码都可以被视为Component或服务,比如说前几篇文章中所提到的
Filter,ModelBinder。甚至是服务组件初始化的辅助类(WindsorServiceLocator)也一并拿下。


     为了便于理解,下面就到Suteki.Shop中看一下其是如何做的:)
   
     首先我们看一下整个Suteki.Shop项目启动的入口,同时这也是Windsor IOC容器初始化的起点。
而这块功能代码是放在了Global.asax(Suteki.Shop\Global.asax)中的Application_Start方法中
实现的,下面是该方法的声明:

protected void Application_Start(object sender, EventArgs e)
{
    RouteManager.RegisterRoutes(RouteTable.Routes);
    InitializeWindsor();
}

 

     代码中的RouteManager.RegisterRoutes是实现对Route规则的绑定,而规则的内容是被硬编码到
RouteManager中实现的。关于Route的资料网上有不少,园子里也有不少朋友写过,这里就不做说明了。

     接就上面方法就会运行InitializeWindsor(),这就是Windsor容器初始化的方法:

/// <summary>
/// This web application uses the Castle Project's IoC container, Windsor see:
/// http://www.castleproject.org/container/index.html
/// </summary>
protected virtual void InitializeWindsor()
{
    
if (container == null)
    {
        
// create a new Windsor Container
        container = ContainerBuilder.Build("Configuration\\Windsor.config"); 

        WcfConfiguration.ConfigureContainer(container);

        ServiceLocator.SetLocatorProvider(() 
=> container.Resolve<IServiceLocator>());
        
// set the controller factory to the Windsor controller factory (in MVC Contrib)
        System.Web.Mvc.ControllerBuilder.Current.SetControllerFactory(new WindsorControllerFactory(container));
    }
}

 

      注:“Configuration\\Windsor.config”中的内容较长,主要是一些XML配置节点。大家可以抽时
间阅读一下即可。

     这个方法是今天讲解的主要内容,下面就介绍一下其中的代码。
   
     首先是判断container(IWindsorContainer类型)是否为空,如果容器为空则创建并初始化该容器。
也就是调用ContainerBuilder(Suteki.Shop\ContainerBuilder)类的Build方法来从外部的config文件
中加载默认信息。我们这里就看一下Build方法的实现:

public static IWindsorContainer Build(string configPath)
{
        var container 
= new WindsorContainer(new XmlInterpreter(configPath));

        
// register handler selectors
        container.Kernel.AddHandlerSelector(new UrlBasedComponentSelector(
            
typeof(IBaseControllerService),
            
typeof(IImageFileService),
            
typeof(IConnectionStringProvider)
            ));

        
// automatically register controllers
        container.Register(AllTypes
            .Of
<Controller>()
            .FromAssembly(Assembly.GetExecutingAssembly())
            .Configure(c 
=> c.LifeStyle.Transient.Named(c.Implementation.Name.ToLower())));

        container.Register(
            Component.For
<IUnitOfWorkManager>().ImplementedBy<LinqToSqlUnitOfWorkManager>().LifeStyle.Transient,
            Component.For
<IFormsAuthentication>().ImplementedBy<FormsAuthenticationWrapper>(),
            Component.For
<IServiceLocator>().Instance(new WindsorServiceLocator(container)),
            Component.For
<AuthenticateFilter>().LifeStyle.Transient,
            Component.For
<UnitOfWorkFilter>().LifeStyle.Transient,
            Component.For
<DataBinder>().LifeStyle.Transient,
            Component.For
<LoadUsingFilter>().LifeStyle.Transient,
            Component.For
<CurrentBasketBinder>().LifeStyle.Transient,
            Component.For
<ProductBinder>().LifeStyle.Transient,
            Component.For
<OrderBinder>().LifeStyle.Transient,
            Component.For
<IOrderSearchService>().ImplementedBy<OrderSearchService>().LifeStyle.Transient,
            Component.For
<IEmailBuilder>().ImplementedBy<EmailBuilder>().LifeStyle.Singleton
        );

        
return container;
}

     

      首先是读入指定配置文件的XML结点信息,将构造一个 WindsorContainer实现,同时在其微内核中
添加“容器处理组件”的方式(AddHandlerSelector),注意这种处理方式是按我们在业务逻辑中规定
的方式处理的。

      紧跟着又向该容器中注册了Controller,而且配置属性的LifeStyle被指定为Transient类型,这里
有必要介绍一下Castle容器的组件生存周期,主要有如下几种:   

    Singleton : 容器中只有一个实例将被创建
    Transient : 每次请求创建一个新实例
    PerThread: 每线程中只存在一个实例
    PerWebRequest : 每次web请求创建一个新实例
    Pooled :使用"池化"方式管理组件,可使用PooledWithSize方法设置池的相关属性。

     

      可以看到在本项目中,组件的生命周期基本上都被指定成为Transient类型,即当请求发生时创建,
在处理结束后销毁。

      接着再看一下该方法的其余代码,也就是对ModelBinder,Filter,Service这类业务逻辑的组件注
册。同时我们看到有的组类在进行接口注册的同时还被绑定了默认的实现类,其这种硬编码的方法是
是一种“可选”方式。


      说完了Build方法之前,再回到Global.asax文件中的InitializeWindsor方法,看一下其余的代码。
我们看到这样一行:
 

     WcfConfiguration.ConfigureContainer(container);

    
      类WcfConfiguration的ConfigureContainer方法就是继续向当前创建的容器中添加组件,而这次要
加入的组件是Windows Live Writer的IMetaWeblog接口实现类,如下:

public static class WcfConfiguration
{
    
public static void ConfigureContainer(IWindsorContainer container)
    {
        var returnFaults 
= new ServiceDebugBehavior { IncludeExceptionDetailInFaults = true };

        container.AddFacility
<WcfFacility>(f =>
        {
            f.Services.AspNetCompatibility 
= AspNetCompatibilityRequirementsMode.Required;
            f.DefaultBinding 
= new XmlRpcHttpBinding();
        })
            .Register(
                Component.For
<IServiceBehavior>().Instance(returnFaults),
                Component.For
<XmlRpcEndpointBehavior>(),
                Component.For
<IMetaWeblog>().ImplementedBy<MetaWeblogWcf>().Named("metaWebLog").LifeStyle.Transient
                );

    }
}

 
      如前面所说的,扩张单元插件(Facilities)可以在不更改原有组件的基础上注入你所需要的功能代码,
这里就使用了其AddFacility方法来添加扩展单元来注册并管理我们的Windows Live Writer组件。

      下面继分析InitializeWindsor方法中的其余代码,看完了ConfigureContainer方法,接着就是下面这
一行代码了:

     ServiceLocator.SetLocatorProvider(() => container.Resolve<IServiceLocator>());


  
      刚看到这一行让我感觉似曾相识,记得以前在看Oxite的Global.asax中也看过类似的这样一行代码。

     ServiceLocator.SetLocatorProvider(() => new UnityServiceLocator(container));  

     
      只不过那个项目中用的是 Unity而不是Castle Windsor。但实际的功能是一样的。即完成对容器中服务
地址的解析绑定。有了它,就可以通过Microsoft.Practices.ServiceLocation.ServiceLocatorImplBase
中所定义的方法如:DoGetInstanceDoGetAllInstances 来获取相应的服务组件(集合)的实例。

     比如本项目中的DoGetInstance及DoGetAllInstances()实现代码如下:

    (文件位于:Suteki.Common\Windsor\WindsorServiceLocator.cs):

protected override object DoGetInstance(Type serviceType, string key)
{
    
if (key != null)
        
return container.Resolve(key, serviceType);
    
return container.Resolve(serviceType);
}

/// <summary>
/// When implemented by inheriting classes, this method will do the actual work of
/// resolving all the requested service instances.
/// </summary>
/// <param name="serviceType">Type of service requested.</param>
/// <returns>
/// Sequence of service instance objects.
/// </returns>
protected override IEnumerable<object> DoGetAllInstances(Type serviceType)
{
    
return (object[])container.ResolveAll(serviceType);
}

        
        
      注,对该WindsorServiceLocator类的IOC绑定在ContainerBuilder.Build中,如下:  

 
  container.Register(
       Component.For
<IUnitOfWorkManager>().ImplementedBy<LinqToSqlUnitOfWorkManager>().LifeStyle.Transient,
       Component.For
<IFormsAuthentication>().ImplementedBy<FormsAuthenticationWrapper>(),
       Component.For
<IServiceLocator>().Instance(new WindsorServiceLocator(container)),
 

     
     
      而InitializeWindsor方法中的最后一行代码如下:    

     System.Web.Mvc.ControllerBuilder.Current.SetControllerFactory(new WindsorControllerFactory(container));

     
      这里要说明的是WindsorControllerFactory这个类是在 MvcContrib项目中提供的,用于构造一个
Castle项目类型的Controller工厂。
     
    
      好了,今天的内容可能有点杂,主要就是Castle闹的,因为其中有一些概念要了解之后才能把项目中
的代码看懂。不过从整体上来看,Suteki.Shop的IOC机制实现的还是很清晰的,没什么太难看的代码。

      今天就先到这里了。
    
     
   
      原文链接: http://www.cnblogs.com/daizhj/archive/2009/05/26/1456678.html

      作者: daizhj,代震军,LaoD

      Tags: mvc,Suteki.Shop,Castle,IOC

      网址: http://daizhj.cnblogs.com/
  
   

posted @ 2009-05-26 08:51  代震军  阅读(6306)  评论(7编辑  收藏  举报