[Flex]Flex与.NET互操作(十):基于FluorineFx.Net的及时通信应用(Appl

mikel阅读(666)

   使用FluorineFx.Net开发的每一个实时通讯功能应用都拥有一个应用程序适配器(ApplicationAdapter),用来管 理整个实时通讯应用的生命周期,以及接受和拒绝客户端的连接等。应用程序适配器对象也就相当于是一个Flash媒体服务器应用程序的对象。

      使用FluorineFx.Net开发及时通信应用,我们可以通过ASP.NET网站来宿主,一个ASP.NET网站可以承载多个实时通讯应用程序。这些应用程序都存储ASP.NET网站的根目录下指定文件夹的根目录中。如下图示:

                               

      在开发FluorineFx的及时通信应用的时候,按照FluorineFx的目录结构配置ASP.NET的站点目录是非常简单的,只 需要在ASP.NET站点下面建立一个名为apps的目录,那么FluorineFx就会认为此目录下的每一个文件夹就表示一个FluorineFx应 用。

      我们可以通过FluorineFx来开发及时文字聊天、视频聊天、视频录制、媒体播放程序以及WebGame等类似及时应用程序。比如 我们需要开发一个视频聊天系统,OK,那便在ASP.NET站点目录下建立一个apps目录,然后在apps下建立一个名为VideoChat的目录,如 下目录结构:

                              

       在Flash或是Flex客户端,我们就可以使用下面的代码来连到这个基于FluorineFx的视频聊天应用。

private var ns:NetConnection;
public function ConnectionFxApp():void
{
    ns 
= new NetConnection();
    ns.connect(
"rtmp://localhost:1974/VideoChat");
    ns.client
=this;
    ns.addEventListener(NetStatusEvent.NET_STATUS,onNetStatusHandler);
}
private function onNetStatusHandler(evt:NetStatusEvent):void
{}

 

      每个应用程序都有其自己的应用范围和层次的根源,这和FMS、Red5是一样的规律。无论是范围还是层次,他都有一个唯一的名字(应用名、实例名等)来区分,我们也可以叫他们为应用程序的实例。这些实例可以是聊天室房间、视频流以及共享对象等。   

                                 

      这些其实是非常容易理解的,如上图示的聊天室房间分配,分别有Room1,Room2,Room3三个聊天房间,那么对于VideoChat这个聊天应用来说要进行视频聊天就有三条连接线路,分别是:

      1、http://localhost:1974/VideoChat/Room1

      2、http://localhost:1974/VideoChat/Room2

      3、http://localhost:1974/VideoChat/Room3

      除了聊天室和应用的层次外,应用程序里还有诸入视频流、共享对象等。他们可以共同应用,也可以为不同的房间分别建立不同的共享对象,如果你要想实现不同房间里的用户可以相互聊天不想让别的房间的人看到,OK,此时你就可以为该房间独立创建一个共享对象来实现。

 

      当开发程序需要去连接客户端,处理服务端事件、调用客户端方法等,就需要自定义一个应用程序适配器 (ApplicationAdapter),此适配器必须继承并实现 FluorineFx.Messaging.Adapter.ApplicationAdapter。 FluorineFx.Messaging.Adapter.ApplicationAdapter做为一个新的应用程序的基类,提供了客户端与应用程序 之间通信的接口方法、数据流以及共享对象等。它能够时时处理应用程序的启动、停止、客户端的连接和断开。

 

      在Flash或是Flex客户端,我们可以通过NetConnectin.call()调用应用程序适配器的方法。如下示例代码块:

服务器端应用适配器所定义的代码:

public class Application : ApplicationAdapter
{
   
public string Echo(string msg)
   {
      
return "Echo: " + msg;
   }
}

Flash/Flex客户端可以通过以下代码块调用服务器端的方法:

private var ns:NetConnection;
private function CallServerMethod():void
{
    nc 
= new NetConnection();
    nc.connect(
"rtmp://rtmp://localhost:1974/application");
    nc.onResult 
= function(obj) 
    {
        trace(
"The result is " + obj);
    }
    nc.call(
"Echo", nc, "Hello");
}

 

     本文就先介绍到这里,下篇将接着本篇介绍FluorineFx.NET中客户端调用服务器端方法和服务器端呼叫客户端的方法。

 

版权说明

  本文属原创文章,欢迎转载,其版权归作者和博客园共有。  

  作      者:Beniao

 文章出处:http://beniao.cnblogs.com/  或  http://www.cnblogs.com/

 

[Flex]Flex与.NET互操作(十一):基于FluorineFx.Net的及时通信应用(Rem

mikel阅读(608)

      FluorineFx.NET提供了完善的RPC(Remote Procedure Call)功能,无论是通过Flash还是Flex开发的客户端应用(.swf)都可以非常简单方便的采用RPC的方式调用.NET的服务器端方 法,.NET的服务器端同样也可以非常方便的呼叫客户端,调用客户端的方法(比如实现系统广播)。
一、客户端的RPC(客户端调用服务器端

       要想实现客户端访问服务器端的方法,首先得对ActionScript中的NetConnection比较熟悉,该类提供了一个示例方法call()专们用来做RPC访问,该方法的定义如下:

public function call(command:String, responder:Responder,  arguments):void 

 

      比如说我们在开发一个及时应用的时候,所有的客户端都需要同步服务器的系统时间,这个时候我们就可以在服务器端提供一个返回当前系统时间的方法给客户端调用。如下:

//返回当前系统时间
public string GetServerTime()
{
     
return DateTime.Now.ToString();
}

      

      有了这个方法客户端就可以通过NetConnection的call()方法实行远程调用了,并通过Responder来处理调用的结果,如果所调用的服务器端方法没有返回值,则在调用的时候可以不使用Responder。如下完整的Flex实例代码:

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" fontSize="12">
    
<mx:Script>
        
<![CDATA[
            import mx.controls.Alert;
            import mx.rpc.events.ResultEvent;
        
            
private var nc:NetConnection;
            
private var responder:Responder = new Responder(onResult,onError);
            
            
private function connectionServer(event:MouseEvent):void
            {
                nc 
= new NetConnection();
                nc.connect(
"rtmp://localhost:1617/RPCDemo","abc","123");
                nc.addEventListener(NetStatusEvent.NET_STATUS,onStatusHandler);
                nc.client 
= this;
            }
            
            
private function onStatusHandler(event:NetStatusEvent):void
            {
                
this.connStatus.text = "连接状态:" + event.info.code;
                
if(event.info.code == "NetConnection.Connect.Success")
                {
                    trace(
"连接服务器成功");
                }
            }
            
            
/* 调用服务器端方法 */
            
private function onGetServerTime(event:MouseEvent):void
            {
                nc.call(
"GetServerTime",responder);
            }
            
            
/* 处理调用服务器端方法成功时返回的结果 */
            
private function onResult(result:String):void
            {
                
this.txtResult.text = "方法GetServerTime返回结果" + result;
            }
            
            
/* 处理调用服务器端方法时候时返回的结果 */
            
private function onError(event:Event):void
            {
                trace(
"调用服务器端方法出错");
            }
        ]]
>
    
</mx:Script>
    
<mx:Button x="30" y="43" label="连接服务器" click="connectionServer(event)"/>
    
<mx:Label x="30" y="75" width="296" id="connStatus"/>
    
<mx:Button x="30" y="112" label="调用服务端方法" click="onGetServerTime(event)"/>
    
<mx:Label x="30" y="153" id="txtResult" width="296"/>
</mx:Application>

 

       FluorineFx.Net的RPC就这么简单,客户端调用服务器端的方法,直接通过当前连接的call()方法调用,使用Responder来处理调用的结果。

二、服务器端的RPC(服务器端调用客户端)

      客户端和服务器端的调用是可以双向的,服务器端也同样可以调用客户端的方法。无论是客户端还是服务器端,相互调用始终都是离不开当前的 连接对象。如上面客户端是通过当前连接的call()方法实现的调用服务器端方法,那么服务器端调用客户端的方法同样如此,也是通过当前连接对象来处理。

      FluorineFx中用于连接的接口是IConnection,只要客户端成功的连接上了服务器端,那么在服务器端就会保存所有连接 到服务器端的连接对象的各种属性。实现调用客户端方法的则是IServiceCapableConnection接 口,IServiceCapableConnection接口的定义如下所示:

      如果要想实现服务器端调用客户端的方法,则必须使用此接口来完成,需要将当前连接的接口IConnection强制转化为

IServiceCapableConnection接口,然后调用其invoke()方法来完成服务器端对客户端方法的调用。

 

/// <summary>
/// 调用客户端的方法
/// </summary>
/// <param name="connection"></param>
public void InvokeClient()
{

    IServiceCapableConnection conn 
= FluorineContext.Current.Connection as IServiceCapableConnection;
    
if (connection != null)
    {
        
object[] args = new object[] { GetServerTime() };
        conn.Invoke(
"RecServerMessage", args);
    }
}

 

      在服务器端可以通过FluorineContext.Current.Connection获取到当前客户端的连接对象,然后将其转化为IServiceCapableConnection接口对象,调用其Invoke()方法便可实现调用客户端的方法。

      这里需要注意一点就是,提供给服务器端调用的客户端方法必须是公共方法(public修饰的方法),否则服务器端会报"Not found method:方法名"的异常。如下代码块:

/**
 * 接收服务器端的调用方法
 * 
*/
public function RecServerMessage(message:Object):void
{
    Alert.show(message.toString());
}

 

      FluorineFx.Net所提供的上面这两种功能是非常实用的,特别是在开发及时应用的时候,客户端RPC可以方便是实现将客户端 是数据请求到服务器端进行处理,比如开发网络游戏中,游戏客户端的不同数据随时都在改变,那么我们就可以使用客户端RPC来实现这一需求。服务器端RPC 同样可以应用于此,游戏中的数据变化了通过客户端RPC请求到服务器端,服务器端进行相与的业务计算和处理之后将客户端需要的数据通过服务器端RPC返回 到当前请求的客户端。

 

      或许有的朋友会想或是问到,如果我要开发一个聊天室,要实现系统广播消息是不是可以使用这种方式来实现。答案是肯定的,系统要广播消 息,可以从服务器端获取到所有连接到服务器端的客户端,然后循环的调用客户端的方法来实现系统广播消息。关于如何实现系统广播这里我就不作介绍,因为 FluorineFx.Net为我们提供了更好的方式来实现系统广播。有兴趣的朋友请关注我的后续文章,在下一篇文章里我将详细介绍这方面的知识点。

 

版权说明

  本文属原创文章,欢迎转载,其版权归作者和博客园共有。  

  作      者:Beniao

 文章出处:http://beniao.cnblogs.com/  或  http://www.cnblogs.com/

 

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

mikel阅读(709)

   在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/

[MVC]Asp.net MVC生命周期

mikel阅读(920)


     public class MvcApplication : System.Web.HttpApplication
        {
            
public static void RegisterRoutes(RouteCollection routes)
            {
                routes.IgnoreRoute(
"{resource}.axd/{*pathInfo}");
               
                
//The controller route value is a special value that the System.Web.Mvc.MvcHandler class uses to call into the IControllerFactory interface.
                
//The basic route handler is an instance of IRouteHandler named MvcRouteHandler.
                
//We have complete control and could provide our own implementation of IRouteHandler if we wished.
                routes.MapRoute(
                    
"Default",                                              // Route name
                    "{controller}/{action}/{id}",                           // URL with parameters
                    new { controller = "Home", action = "Index", id = "" }  // Parameter defaults
                );
             
    
            }
    
            
protected void Application_Start()
            {
                RegisterRoutes(RouteTable.Routes);
            }
    

 

UrlRoutingMoudulePostResolveRequestCache阶段从RouteCollection中获取当前请求的RouteData.RouteData包含了一个请求处理对应的ControllerAction,RouteData这个作用贯穿请求的处理过程.RouteData中提取RouteHandler,这里默认是MvcRouteHandler,MvcRouteHandler获取HttpHandler,这里默认的是MvcHandler.

 


    public virtual void PostResolveRequestCache(HttpContextBase context)
{
    RouteData routeData 
= this.RouteCollection.GetRouteData(context);
    
if (routeData != null)
    
{
        IRouteHandler routeHandler 
= routeData.RouteHandler;
        
if (routeHandler == null)
        
{
            
throw new InvalidOperationException(string.Format(CultureInfo.CurrentUICulture, RoutingResources.UrlRoutingModule_NoRouteHandler, new object[0]));
        }

        
if (!(routeHandler is StopRoutingHandler))
        
{
            RequestContext requestContext 
= new RequestContext(context, routeData);
            IHttpHandler httpHandler 
= routeHandler.GetHttpHandler(requestContext);
            
if (httpHandler == null)
            
{
                
throw new InvalidOperationException(string.Format(CultureInfo.CurrentUICulture, RoutingResources.UrlRoutingModule_NoHttpHandler, new object[] { routeHandler.GetType() }));
            }

            RequestData data2 
= new RequestData();
            data2.OriginalPath 
= context.Request.Path;
            data2.HttpHandler 
= httpHandler;
            context.Items[_requestDataKey] 
= data2;
            context.RewritePath(
"~/UrlRouting.axd");
        }

    }

}

rh 

  

MvcHandler.ProcessRequest()中首先使用HttpContextWrapperHttpContext进行封装,封装的目的是为了解耦以获得可测试性.然后从RequestContext.RouteData中提取Controller名称.
ControllerBuilder.GetControllerFactory –> ControllerFactory.CreateController –> IController.Execute

ControllerBase实现了IController接口,Initialize时将RequestContext封装成为ControllerContext,Controller继承自ControllerBase并实现抽象方法ExecuteCore()

 

mvchttp 

 

ExecuteCore,Controller首先从RouteData中获得ActionName,然后执行ActionInvoker.InvokeAction.

ActionInvoker中我们可以看到各种Filter,这是一种AOP实践:Action方法执行的前后执行若干方法.这里有四种Filter:ActionFilters,ResultFilters,AuthorizationFilters,ExceptionFilters.这四种Filter并不是封闭的,都有对应的接口,这四个只是默认实现.Filter的执行顺序是:AuthorizationFilter—>Action Filter.OnActionExecuting—>Action Method—>ActionFilter.OnActionExecuted.InvokeActionMethodWithFilters返回的结果是ActionExecutedContext,接下来将Controller执行OnResultExecuting 方法.ActionResult执行的结果可以是ViewResult,JsonResult,RedirectResult,ContentResult,或者是自定义的Result类型.

       如果返回的类型是ViewResult,我们先看一下ViewReuslt的继承关系:ViewResult–>ViewResultBase–>ActionResult,ViewResult包含两个属性ViewViewEngineCollection,实际上是包含了两个接口的实现:IViewEngine定义了怎么定位View/Partial View.IView定义了如何RenderView.默认的实现时WebFormViewWebFormViewEngine.

Filter OnResultExecuted 最后一步了,可以这里捕获异常.上面我们说过还有ExceptionFilters,如果前面过程中的异常没有被捕获那么最终都会到冒泡到ExceptionFilters.

  • RouteData中获得ActionName
  • ActionInvoker.InvokeAction
  • 通过ControllerContext获取ControllerDescriptor
  • FindAction-获取ActionDescriptor
  • GetFilters
  • ModelBinderRequest中的数据转换成Action方法需要的参数
  • AuthorizationFilter
  • Action Filter.OnActionExecuting
  • Action
  • ActionFilter.OnActionExecuted
  • ResultFilter.OnResultExecuting
  • ActionResult Execution
  • ResultFilter.OnResultExecuted
  • WebFormViewEngine.CreateView
  • WebFormView.Render
  • ResultFilter.OnExecuted

result 

 

控制权归还到HttpApplication完成后续的生命周期.

 

嗯哼,全文完.

[Flex]Flash Builder 4 beta、Flex SDK(Gumbo)

mikel阅读(777)

详细请看:http://www.k-zone.cn/zblog/post/adobe-flash-builder-gumbo-flash-catalyst-list.html

昨天是2009年的国际儿童节,Adobe也没忘记作为不是儿童的我们,如期的发布了Adobe Flash Builder 4 beta 和 Adobe Flash Catalyst beta 这两个我认为09年上半年最为重磅级的产品:)

而昨天也只是匆忙的介绍了一下Adobe Flash Builder 4 beta 和 Adobe Flash Catalyst beta的下载地址,作为Flex程序员来说,不详细介绍一下这些工具下都对不起大家。:)

其实昨天对外发布的除了Adobe Flash Builder 4 beta 和 Adobe Flash Catalyst beta以外,还应该包括一个重量级的选手 – Flex SDK 4 ( Gumbo )

只不过Gumbo应该是旧消息了,因此才没有大书特书:)
早在MAX 2008的时候,就已经放出了Flex SDK 4 ( Gumbo )的内容,同时在Adobe驱动的open source site上面就可以下载Nightly Builds的Flex SDK 4 ( Gumbo )

Flex SDK 4 ( Gumbo )的一些知识的汇总:
Flex SDK 4 ( Gumbo )主页:
包含了Flex SDK 4 (&
nbsp;Gumbo )的功能、文档、规格等内容:
http://opensource.adobe.com/wiki/display/flexsdk/Gumbo

Flex SDK 4 ( Gumbo )的SDK下载地址:
http://opensource.adobe.com/wiki/display/flexsdk/Download+Flex+4

Adobe Flex SDK 4 ( Gumbo ) Language Reference:
http://livedocs.adobe.com/flex/gumbo/langref/index.html

Adobe Flex SDK 4 ( Gumbo )官方介绍:
http://www.adobe.com/devnet/flex/articles/flex4sdk_whatsnew.html

Flash Builder 4.0 beta的一些知识的汇总:
Flash Builder 4.0 beta是专门配合FFlex SDK 4 ( Gumbo ) ,虽然我们可以在其他版本中导入Flex SDK 4 ( Gumbo ) ,但是却无法体现Flex SDK 4 ( Gumbo ) 的一些特点。
Flash Builder 4.0 beta包含的功能:
1、支持Theme切换。
2、FXP的导入和导出。
3、FlexUnit集成。
4、ASDoc视图。
5、Data Services支持。
等等。

这里有一篇关于Flash Builder 4.0 beta最新功能的介绍:
http://www.adobe.com/devnet/flex/articles/flex4builder_whatsnew.html

这是一个关于Flash Builder 4.0 beta的video介绍。(MAX 2008的介绍,已经很

[MVC]Asp.net MVC 示例项目"Suteki.Shop"分析之---NVelocity模

mikel阅读(840)

   在Suteki.Shop中使用了NVeloctiy模版引擎,用于提供可订制的邮件模版。而邮件的功能就是当定单状
态发生变化时,系统会向买家发送邮件通知。其中的邮件信息内容就是采用NVeloctiy的模版(.vm扩展名)
进行订制的。
     因为在Sutekie.Shop的最新源码包中只是部分实现了其功能,而全部的功能还在完善中,所以要运行本
文中所说的功能,需要在下面的链接地址中下载其最新程序文件(包括单元测试文件):

    http://code.google.com/p/sutekishop/source/detail?r=282
   
     要下载的文件包括:
    

/branches/JtG_Enhancements/Suteki.Shop/Suteki.Shop/Views/EmailTemplates/OrderConfirmation.vm 
/branches/JtG_Enhancements/Suteki.Shop/Suteki.Shop/Views/EmailTemplates/OrderDispatch.vm  
/branches/JtG_Enhancements/Suteki.Shop/Suteki.Shop/Views/EmailTemplates/_orderDetails.vm
/branches/JtG_Enhancements/Suteki.Shop/Suteki.Shop/Controllers/OrderStatusController.cs  
/branches/JtG_Enhancements/Suteki.Shop/Suteki.Shop/Services/EmailService.cs 

    
     等等。
   
     当下载并覆盖(或添加)到本地项目中后,我们还需要在Castle Windsor中注册相应的EmailBuilder
组件。我们只要打开ContainerBuilder类并找到其Build方法(Suteki.Shop\ContainerBuilder.cs),并
添加如下代码:

    Component.For<IEmailService>().ImplementedBy<EmailService>().LifeStyle.Singleton
   
    注:有关Castle Windsor 的 IOC的内容我已在这篇文章中做了介绍.  

  
    最终的代码如下所示:   
 

   
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,
        Component.For
<IEmailService>().ImplementedBy<EmailService>().LifeStyle.Singleton //新加的代码
 ); 
 

 

     完成了这些工作后,我们就可以编译运行该项目了。
   
     下面我们来看一下今天的主角 EMailBuilder,其实现了使用NVelocityEngine加载模版信息并将
ViewData中的数据与模版中的指定变量进行绑定的工作。下面是其类图:
    
        
 

     下面对其中相关类和接口做一下介绍:
   
     首先是IEmailBuilder接口,该接口中只有一个方法GetEmailContent,用于将指定的NVelocity
版与ViewData的数据进行绑定,其中参数templateName就是指定的NV模版名称,而 viewdata就是服务
(EmailService)所传递过来的定单数据。

 /// <summary>
 
/// Provide the base method and property to build email
 
/// </summary>
 public interface IEmailBuilder
 {
      
/// <summary>
      
/// Get the email content
      
/// </summary>
      
/// <returns>Return the email content.</returns>
      string GetEmailContent(string templateName, IDictionary<stringobject> viewdata);
 }

 
     而做为IEmailBuilder接口的实现类,EMailBuilder 中相应的GetEamilContent方法实现代码如下:

public string GetEmailContent(string templateName, IDictionary<stringobject> viewdata)
{
    
return BuildEmail(templateName, viewdata);
}
string BuildEmail(string templateName, IDictionary<stringobject> viewdata)
{
    
if (viewdata == null)
    {
      
throw new ArgumentNullException("viewData");
    }
    
if (string.IsNullOrEmpty(templateName))
    {
      
throw new ArgumentException("TemplateName");
    }
    var template 
= ResolveTemplate(templateName);
    var context 
= new VelocityContext();
    
foreach (var key in viewdata.Keys)
    {
     context.Put(key, viewdata[key]);
    }
    
using (var writer = new StringWriter())
    {
       template.Merge(context, writer);
       
return writer.ToString();
    }
}

   

     可以看出,最终获取Email内容的工作交给了BuildEmail这个方法,其实现的逻辑还是很清晰的。
首要是判断传入参数是否为空(包括viewdata,templateName),然后调用ResolveTemplate
方法来获取指定NV模版的信息内容,其方法(ResolveTemplate)内容如下:

Template ResolveTemplate(string name)
{
    name 
= Path.Combine(templatePath, name);
    
if (!Path.HasExtension(name))
    {
      name 
+= ".vm";
    }
    
if (!velocityEngine.TemplateExists(name))
    {
      
throw new InvalidOperationException(string.Format("Could not find a template named '{0}'", name));
    }
    
return velocityEngine.GetTemplate(name);
}

   

      ResolveTemplate的工作流程即:先判断指定的模版路径中是否包括扩展名,如不包括则添加"vm"
结尾的扩展名(该扩展名是NV模版的指定扩展名)。然后继续判断指定路径下的模版文件是否存在“
TemplateExists”。并最终使用velocityEngine来完成获取模版文件内容信息的功能。

     注:velocityEngine的初始化在构造方法:
    
     EmailBuilder(IDictionary<string, object> nvelocityProperties) 中实现。

      分析完ResolveTemplate方法,再回到上面的BuildEmail方法中看一下其余的代码。运行完获取模
版信息的方法之后, 接着就是使用viewdata中的数据构造一个 VelocityContext对象并使用它来完成
与指定模版信息的绑定了。即下面的这几行代码:

   var context = new VelocityContext();
   
foreach (var key in viewdata.Keys)
   {
     context.Put(key, viewdata[key]);
   }
   
using (var writer = new StringWriter())
   {
     template.Merge(context, writer);
     
return writer.ToString();
   }

  

     到这里其本上就完成了对NV模版的数据绑定并返回其最终结果了。下面看一下Suteki.Shop是如何
使用它的。

     首先我们要看一下整个EMail通知发送体系的类图:

 
   

     注:图中右下角就是上面我们所说的那个EmailBuilder 
     
     我们要先清楚了解一下上图中的类关系:
   
     图中右上角OrderStatusController这个Controller,顾名思义其用于定单状态发生变化时的控制器操作,
它定义了几个Action方法(图中的Method),其中Dispatch方法就包括对EmailService类中“发送Dispatch通
知(SendDispatchNotification)”的方法调用,而就是该方法会最终完成对EMailBuilder调用,下面就以调用
的先后顺序依次介绍一下其实代码:

    首先是OrderStatusController中的Dispatch方法,其实现代码如下:  

  [AdministratorsOnly]
    
public class OrderStatusController : ControllerBase
    {
        
readonly IRepository<Order> orderRepository;
        
readonly IUserService userService;
        
readonly IEmailService emailService;
        
public OrderStatusController(IRepository<Order> orderRepository, IUserService userService, IEmailService emailService)
        {
            
this.orderRepository = orderRepository;
            
this.emailService = emailService;
            
this.userService = userService;
        }
        [UnitOfWork]
        
public ActionResult Dispatch(int id)
        {
            var order 
= orderRepository.GetById(id);
            
if (order.IsCreated)
            {
                order.OrderStatusId 
= OrderStatus.DispatchedId;
                order.DispatchedDate 
= DateTime.Now;
                order.UserId 
= userService.CurrentUser.UserId;
                emailService.SendDispatchNotification(order);
            }
            
return this.RedirectToAction<OrderController>(c => c.Item(order.OrderId));
        }
        
    }

    
     首先就是把Http请求过来的定单ID作为参数,并调用orderRepository.GetById方法获取该定单ID的相关
信息,然后判断其是否已被创建(IsCreated为“true”), 如果已创建就可以将当前的定单信息中的状态设
为“DispatchedId”,同时将 DispatchedDate时间设置为系统当前时间,然后是用户ID的绑定。当一切完
成后,就可以将该定单数据作为参数传递给IEmailService的SendDispatchNotification方法,以启动“发送
Email通知买家”的流程了。

     下面是接口IEmailService的实现类“EmailService”(Suteki.Shop\Services\EmailService.cs)中相应
方法的实现代码:
  

public void SendDispatchNotification(Order order)
{
    var viewdata 
= new Dictionary<stringobject>
    {
       { 
"order", order },
       { 
"shopName", baseService.ShopName }
    };
    var email 
= builder.GetEmailContent(OrderDispatchTemplate, viewdata);
    var subject 
= "{0}: Your Order has Shipped".With(baseService.ShopName);
    sender.Send(order.Email, subject, email, 
true);
}

 

     在这里就完成了对EmailBuilder类中的GetEmailContent方法的调用,并最终使用EmailSender(发送
邮件的功能类)来发送邮件(“Send方法”)给指定的买家。

     下面就完看一下其最终的运行效果,首先我们要先创建一个定单(注:创建定单的流程在本系列文章的第
一篇中已做过说明,这里就暂且略过了)。然后我们以管理员的身份登录系统,并单击顶部导航的“Online-
Shop”链接,然后点击左侧的“Orders”链接即可看到一个订单列表页面,如下图所示:

      
    

     然后点击相应的订单“Number”,就会进入到相应订单明细页面,如下:
   
      

     点击页面中的“Dispatch”链接,之后就会修改当前定单的状态同时发送相应的Email给买家了,这里为
了清楚起见,我在EmailBuilder中设置了一个断点,并截了一张图,来让大家看一下其最终返回的邮件信息内
容:

       
   
     因为我本地的机器上未安装发送Email的插件,所以无法真正将该Email发送到我指定的邮箱,以便能看到
最终效果,但这并不影响大家对本文的了解,呵呵。
   
    
    今天就先到这里了。

    
    原文链接:http://www.cnblogs.com/daizhj/archive/2009/06/03/1457521.html

    作者: daizhj,代震军,LaoD

    Tags: mvc,Suteki.Shop,NVelocity

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

[PHP]二十五个顶级PHP模板引擎整理

mikel阅读(784)

为了找到一个好的模板引擎,我在互联网上进行搜索,目前已经整理出了以下名单:

Smarty
Smarty的特点是将模板编译成PHP脚本,然后执行这些脚本。很快,非常灵活。

Heyes Template Class
一个非常容易使用,但功能强大并且快速的模板引擎,它帮助你把页面布局和设计从代码中分离。

FastTemplate
一个简单的变量插值模板类,它分析你的模板,把变量的值从HTML代码中分离处理。

ShellPage
一个简单易用的类,可以让你的整个网站布局基于模板文件,修改模板就能改变整个站点。

STP Simple Template Parser
一个简单、轻量级并且易于使用的模板分析类。它可以从多个模板中组装一个页面,把结果页面输出到浏览器或者文件系统。

OO Template Class
一个你可以用在自己程序中的面向兑现的模板类。

SimpleTemplate
一个可以创建和结构化网站的模板引擎。它可以解析和编译模板。

bTemplate
短小但是快速的模板类,允许你把PHP逻辑代码从HTML修饰代码中分离。

Savant
一个强大且轻量级的PEAR兼容模板系统。它是非编译型的,使用PHP语言本身做为它的模板语言。

ETS – easy template system
可以使用完全相同数据重组模板的模板系统。

EasyTemplatePHP
适用于你的站点的一个简单但是强大的模板系统。

vlibTemplate
一个快速、全能的模板系统,它包含一个缓存和调试类。

AvanTemplate
多字节安全的模板引擎,占用很少系统资源。它支持变量替换,内容块可以设置显示或隐藏。

Grafx Software’s Fast Template
一个修改版本的Fast Template系统,它包括缓存功能,调试控制台以及沉默去除为赋值块。

TemplatePower
一个快速、简单、功能强大的模板类。主要功能有嵌套的动态块支持,块/文件包含支持以及显示/隐藏未赋值的变量。

TagTemplate
这个库的功能被设计来使用模板文件,同时允许你从HTML文件检索信息。

htmltmpl: templating engine
一个适用于Python和PHP的模板引擎。它面向希望在项目中分离代码和设计的web应用开发人员。

PHP Class for Parsing Dreamweaver templates
一个分析Dreamweaver模板的简单类,被用于Gallery 2 和WordPress的自定义模块中。

MiniTemplator (Template Engine)
针对HTML文件的一个紧凑型模板引擎。对于模板变量和块定义它具有简单的语法。其中块可以嵌套。

Layout Solution
简化网站开发和维护。它拥有常用的变量和页面元素使你不需要重复做页面布局工作。

Cached Fast Template
它已经纳入FastTemplate,允许你缓存模板文件,甚至可以在分离的块内容上缓存不同的规格。

TinyButStrong
一个支持MySQL, Odbc, SQL-Server和ADODB的模板引擎。它包含7个方法和两个属性。

Brian Lozier’s php based template engine
只有2K大小,非常快并且是面向对象设计。

WACT
一个从设计中分离代码的模板引擎。

PHPTAL
一个PHP下面的XML/XHTML模板库。

[MVC]Asp.net MVC 示例项目"Suteki.Shop"分析之---ModelBinde

mikel阅读(876)

     在Suteki.Shop中,作者构造了一个ModelBinder基类“DataBinder”,其本身继承自IModelBinder
接口,并以此其类派生出其它一些子类类如ProductBinder等等。可以说除了极个别的地方之外,Data
Binder被用于了Suteki.Shop大多数的ModelBinder绑定场景之路。

      首先看一下其类图结构:
    
         
      作为基类,DataBinder(图中左下方)实现了将HTTP请求过来的数据转换成为模型中相应的类型。
其核心方法就是BindModel,下面做一下解释说明:

public virtual object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
            
object
 entity;
            
if(declaringAttribute == null ||
 declaringAttribute.Fetch)
            {
                entity 
=
 FetchEntity(bindingContext, controllerContext);
            }
            
else
 
            {
                entity 
=
 Activator.CreateInstance(bindingContext.ModelType);
            }        
            
try

            {
                validatingBinder.UpdateFrom(entity, controllerContext.HttpContext.Request.Form, bindingContext.ModelState, bindingContext.ModelName);
            }
            
catch(ValidationException ex) 
            {
                
//
Ignore validation exceptions – they are stored in ModelState. 
                
//The controller can access the errors by inspecting the ModelState dictionary.

            }
            
return
 entity;
    }

     首先要说明的就是declaringAttribute,这个变量其实是DataBindAttribute类型实例,其作用
是判断当前所请求过来的数据是用于创建新的Model信息还是获取并绑定已有的数据记录。该实例的
实始化是交给Accept方法来实现的(注意Accept方法的声明是在IAcceptsAttribute接口中定义的):

public void Accept(Attribute attribute)
{
     declaringAttribute 
=
 (DataBindAttribute) attribute;   
}

 
      而对该方法的调用是放在了BindUsingAttribute的GetBinder()方法中,所以这里要先看一下
BindUsingAttribute的具体实现: 

public class BindUsingAttribute : CustomModelBinderAttribute
{
        
private readonly
 Type binderType;
        
public
 BindUsingAttribute(Type binderType)
        {
            
if(!typeof
(IModelBinder).IsAssignableFrom(binderType))
            {
                
throw new InvalidOperationException("Type '{0}' does not implement IModelBinder."
.With(binderType.Name));
            }
            
this.binderType =
 binderType;
        }
        
public override
 IModelBinder GetBinder()
        {
            var binder 
=
 (IModelBinder) ServiceLocator.Current.GetInstance(binderType);
            
if(binder is
 IAcceptsAttribute)
            {
                ((IAcceptsAttribute)binder).Accept(
this
);
            }
            
return
 binder;
        }
}

 
      这个属性类也就是在Action中进行ModelBinder绑定时用到的,其类构造方法中通过一个类型参
数初始化并在GetBinder()方法中使用ServiceLocator来进行最终的ModelBinder实例化构建,注意
在该方法中有对当前binder的逻辑判断(IAcceptsAttribute),并以此来区别当前Action绑定的Model
Binder是否实现了IAcceptsAttribute接口。如果没实现或declaringAttribute.Fetch为true时,就
会在之前的DataBinder类方法“BindModel”中调用“FetchEntity()”方法,FetchEntity会从提交
的数据中的主键(PrimaryKey)中检索数据并返回该对象实例,代码如下:
 

private object FetchEntity(ModelBindingContext bindingContext, ControllerContext controllerContext)
{
        
object
 entity;
        var primaryKey 
= bindingContext.ModelType.GetPrimaryKey();//从Shop.dbml中查找相应的主键信息

        string name = bindingContext.ModelName + "." + primaryKey.Name;//拼接出要获取的主键名称

        
string rawKeyValue = controllerContext.HttpContext.Request.Form[name];
        
if (string
.IsNullOrEmpty(rawKeyValue))
        {
            
throw new InvalidOperationException("Could not find a value named '{0}'"
.With(name));
        }
        
int key =
 Convert.ToInt32(rawKeyValue);
        var repository 
=
 resolver.GetRepository(bindingContext.ModelType);
        entity 
=
 repository.GetById(key);
        
return
 entity;
}

 

     注意上面代码中的这两行:   

    var repository = resolver.GetRepository(bindingContext.ModelType);
    entity 
= repository.GetById(key);

     其通过bindingContext.ModelType方法获取指定的Model类型的Repository实例(包括该Model类型
的CRUD方法),并使用接口方法GetById()来获取最终的Model对象。

    下面就是IRepository的接口定义(当然其也定义了泛型接口,后面会提到)。 

public interface IRepository
 {
     
object GetById(int
 id);
     IQueryable GetAll();
     
void InsertOnSubmit(object
 entity);
     
void DeleteOnSubmit(object
 entity);
     [Obsolete(
"Units of Work should be managed externally to the Repository."
)]
     
void
 SubmitChanges();
 } 

 
     看到这里,我感觉用这种方式设计还是很讨巧的,因为只要每个Model都实现了相应的IRepository
接口操作类,就可以通过上面两行代码进行调用,这也同时让 FetchEntity获取了最大的稳定性,基本
上可以做到与具体的Model类解藕,呵呵。

     正如前面所说DataBinder本身就是个基类,其实现了一些默认设置和功能,并以此来简化子类的实
现代码。

     下面接着看一下其子类的实现,这里以ProductBinder(Suteki.Shop\Binders\ProductBinder.cs)
为例进行说明,ProductBinder主要实现商的信息更新,包括商品的Size, 图片等等,其实现代码如下:
    

public class ProductBinder : DataBinder
{
        
readonly IRepository<Product>
 repository;
        
readonly
 IHttpFileService httpFileService;
        
readonly IOrderableService<ProductImage>
 orderableService;
        
readonly
 ISizeService sizeService;

        public ProductBinder(IValidatingBinder validatingBinder, IRepositoryResolver resolver, IRepository<Product> repository, IHttpFileService httpFileService, IOrderableService<ProductImage> orderableService, ISizeService sizeService)

 

 

            : base(validatingBinder, resolver)
        {
            
this.repository =
 repository;
            
this.httpFileService =
 httpFileService;
            
this.orderableService =
 orderableService;
            
this.sizeService =
 sizeService;
        }
        
public override object
 BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
        {
            var product 
= base.BindModel(controllerContext, bindingContext) as
 Product;
            
if(product != null
)
            {
                UpdateImages(product, controllerContext.HttpContext.Request);
                CheckForDuplicateNames(product, bindingContext);
                UpdateSizes(controllerContext.HttpContext.Request.Form, product);
            }
            
return
 product;
        }
        
void
 UpdateSizes(NameValueCollection form, Product product)
        {
            sizeService.WithValues(form).Update(product);
        }
        
void
 UpdateImages(Product product, HttpRequestBase request)
        {
            var images 
=
 httpFileService.GetUploadedImages(request);
            var position 
=
 orderableService.NextPosition;
            
foreach (var image in
 images)
            {
                product.ProductImages.Add(
new
 ProductImage
                {
                    Image 
=
 image,
                    Position 
=
 position
                });
                position
++
;
            }
        }
        
void
 CheckForDuplicateNames(Product product, ModelBindingContext bindingContext)
        {
            
if (!string
.IsNullOrEmpty(product.Name))
            {
                
bool productWithNameAlreadyExists =

                    repository.GetAll().Any(x 
=> x.ProductId != product.ProductId && x.Name == product.Name);
                
if
 (productWithNameAlreadyExists)
                {
                    
string key = bindingContext.ModelName + ".ProductId"
;

                    bindingContext.ModelState.AddModelError(key, "Product names must be unique and there is 

                                already a product called '{0}'".With(product.Name));

                }

            }
        }
}

 

      ProductBinder的构造方法主要是用于进行单元测试,所以就不多做说明了。值得注意的是其
重写了BindModel方法,这也是我们使用继承方法的精妙所在,可以根据自己的需求“覆盖”已有
的基类方法,以此实现自己的业务逻辑。当然如果将来ProductBinder下面有子类继承的话,同时
又要定制自己的BindModel内容时,也可以如法泡制。

     另外就是在上面的CheckForDuplicateNames方法中对于成员 repository的使用,该成员的
如下:
    

IRepository<Product>  repository;

    
     其IRepository<>泛型接口的实现目的与IRepository相同,也是为了隔离“变化的需求”,同
时提高了可扩展性。

    
    下面就是IRepository<>泛型接口的声明:

public interface IRepository<T> where T : class
{
    T GetById(
int id);
    IQueryable
<T>
 GetAll();
    
void
 InsertOnSubmit(T entity);
    
void
 DeleteOnSubmit(T entity);
    [Obsolete(
"Units of Work should be managed externally to the Repository."
)]
    
void
 SubmitChanges();
}

     除了从DataBinder上继承之外,Suteki.Shop中还有两个与其继承层次相同的ModelBinder,
们是CurrentBasketBinder(购物车),OrderBinder(定单),而这两个Binder与之前所说的
DataBinder”的一个区别就是其均未实现IAcceptsAttribute接口。换句话说当使用BindUsing-
Attribute绑定到Action时,其均不会运行BindUsingAttribute类中GetBinder()方法的“IAcce-
ptsAttribute.Accept(this)语句”。

    下面是其类图:

     今天的内容看着有点复杂,但其实与前几天所说的Controller,Filter的实现方式都有些相似,
就是对于MVC所提供的类都是先做一个其类,然后在基类的基础上写新入自己想要的功能代码,这
种方式应该说是一种常识或是基本功了,好处大家也都能分析的出来,呵呵。

     好了,今天的内容就先到这里了。   
   
     原文链接:
http://www.cnblogs.com/daizhj/archive/2009/05/11/1454300.html

     作者: daizhj,代震军,LaoD

     Tags: mvc

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

[C#]Microsoft .NET PetShop 4.0 架构与技术分析(一)

mikel阅读(976)

1.框架的概要介绍

微软刚推出了基于ASP.NET 2.0下的Pet Shop 4, 该版本有了一个全新的用户界面。是研究ASP.NET 2.0的好范例啊,大家都知道,一直以来,在.NET和Java之间争论不休,到底使用哪个平台开发的企业级应用性能最好、结构最优、生产力最高。为了用事实说话,通过对项目各方面的性能评估进而在比较.NET和Java的高下。用户做比较的这个项目就是Petshop。正因为Petshop肩负着上面所说的重任,各方面必须是最优的架构设计应该是经过慎重考虑的。所以其一经推出,便成为了开发者、架构师等人学习、研究的典范。

日前微软推出了基于.NET Framework 2.0开发的Petshop 4。新的Petshop4实现了与Petshop 3相同甚至更多的特性,由于采用了Master Pages,Membership,以及Profile,SQLCacheDependency,但是代码量却减少了四分之一。同时,在事务、数据缓存、安全方面使用了.NET 2.0附带的特性,构建了一个灵活的最佳实践的应用程序。

 

他们利用了Project Conversion Wizard把项目从ASP.NET 1.1移植到了ASP.NET 2.0,然后做了以下改动:

1.用System.Transactions代替了原来的Serviced Components提供的事务功能

代码实现:PetShop.BLL.OrderSynchronous 的 public void Insert(PetShop.Model.OrderInfo order)

2.用强类型的范型集合代替了原来的弱类型集

        public IList<ProductInfo> GetProductsByCategory(string category)

        {

            // Return new if the string is empty

            if (string.IsNullOrEmpty(category))

                return new List<ProductInfo>();

 

            // Run a search against the data store

            return dal.GetProductsByCategory(category);

        }

3.采用ASP.NET 2.0 Membership来做认证和授权

4.创建了针对Oracle 10g的Custom ASP.NET 2.0 Membership Provider

5.利用ASP.NET 2.0的Custom oracle 和 SQL Server Profile Providers 做用户状态管理,包括购物车等

6.采用了Master Pages,取代了原来的用户控件,来实现统一的界面效果

7.使用了ASP.NET 2.0 Wizard控件实现check-out

8.使用了SqlCacheDependency来实现数据库层次的缓存更新(cache invalidation)功能

9.使用了消息队列来实现异时订单处理。

 

2.整体架构:


数据库:(暂略)

项目列表:从整体可以看出,Pet Shop 4的项目体系已经很庞大,考虑的方面也较3.0更全面复杂。

 

序号

项目名称

描述

1

BLL

业务逻辑层

2

CacheDependencyFactory

缓存依赖类的工厂类

3

WEB

表示层

4

DALFactory

数据层的抽象工厂

5

DBUtility

数据访问类组件

6

IBLLStrategy

同步/异步策略接口

7

ICacheDependency

缓存依赖类接口

8

IDAL

数据访问层接口定义

9

IMessaging

异时处理消息队列接口定义

10

IProfileDAL

Profile的数据访问层接口定义

11

Membership

Membership认证和授权管理

12

MessagingFactory

异时处理消息队列的抽象工厂

13

Model

业务实体

14

MSMQMessaging

异时处理消息队列的实

15

OracleDAL

Oracle数据访问层

16

OracleProfileDAL

Oracle的Profile Providers

做用户状态管理,包括购物车等

17

OrderProcessor

后台处理进程,处理订单队列

18

Profile

Profile的数据访问层

19

ProfileDALFactory

ProfileDAL的工厂类(反射创建ProfileDAL)

20

SQLProfileDAL

SQL Server 的Profile Providers

做用户状态管理,包括购物车等

21

SQLServerDAL

SQLServer数据访问层

22

TableCacheDependency

缓存依赖实现类

 

项目分解

由于整体已经有22个项目,所以,对于初学者一看就晕了,所以,我做了分解,可以大体上分几块去理解。

序号

项目名称

描述

1

WEB

表示层

2

Model

业务实体

3

BLL

业务逻辑层

4

DALFactory

数据层的抽象工厂

5

IDAL

数据访问层接口定义

6

SQLServerDAL

SQLServer数据访问层

7

OracleDAL

Oracle数据访问层

8

DBUtility

数据库访问组件基础类

9

CacheDependencyFactory

缓存依赖类的工厂类

10

ICacheDependency

缓存依赖类接口

11

TableCacheDependency

缓存依赖实现类

12

IBLLStrategy

同步/异步处理策略接口(实现在bll根据配置反射选择)

13

MessagingFactory

异时处理消息队列的抽象工厂

14

IMessaging

异时处理消息队列接口定义

15

MSMQMessaging

异时处理消息队列的实

16

Profile

Profile的数据访问层

17

ProfileDALFactory

ProfileDAL的工厂类(反射创建ProfileDAL)

18

IProfileDAL

Profile的数据访问层接口定义

19

OracleProfileDAL

Oracle的Profile Providers

做用户状态管理

20

SQLProfileDAL

SQL Server 的Profile Providers

做用户状态管理

21

Membership

Membership认证和授权管理

22

OrderProcessor

后台处理进程,处理订单队列

 

 

 

3.Petshop 4中的设计模式

工厂模式:

首当其冲的就是工厂模式,很容易就可以看出来,也是应用最多的。

DALFactory:数据访问层的抽象工厂(决定创建哪种数据库类型的数据访问层。可以选择:SQLServer,Oracle)

CacheDependencyFactory缓存依赖类的工厂类。(创建具体表的缓存依赖)

MessagingFactory 异时处理消息队列的抽象工厂(反射创建具体的异时处理类

ProfileDALFactoryProfileDAL的工厂类(反射选择创建Oracle 和SQL Server的 ProfileDAL)

 

策略模式: IorderStrategy

 

 

 

中介模式

CategoryDataProxy ItemDataProxy  ProductDataProxy

 

 

 

 

具体介绍可以参看MSDN

.NET Pet Shop 4: Migrating an ASP.NET 1.1 Application to 2.0

http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnbda/html/bdasamppet4.asp

 

下载

http://download.microsoft.com/download/8/0/1/801ff297-aea6-46b9-8e11-810df5df1032/Microsoft%20.NET%20Pet%20Shop%204.0.msi