DinnerNow中的WCF应用 --- 首页数据加载

简介:

从今天起本系列文章将以一个购物流程为主线,介绍一下DinnerNow是如何使用WCF,LINQ,ASP.NET Ajax Extensions等技术来架构应用的。

  首先请用VS2008打开下面两个解决方案:
     安装目录下\solution\DinnerNow - Web\DinnerNow - Web.sln
                  \solution\DinnerNow - ServicePortfolio2\DinnerNow - ServicePortfolio2.sln

     这是关于DinnerNow - Web.sln中项目的说明:
DinnerNow.WebUX 项目包括表示层(UI)的应用逻辑,WCF客户端调用的CS文件(CODE文件夹下)
     DinnerNow.Web 项目则提供了一些简单的变量声明和定义,相关的CS代码并不多.
  Microsoft.DPE.Samples.CardSpace 是一些关于Card Space数据访问和操作的封装和实例代码.


     因此目前网站上的主要代码和功能实现都集中在了DinnerNow.WebUX这个项目.

  为了完整的演示一个购买流程,本人将会以执行页面为单位.逐个说明相关页面的程序执行逻辑和功能实现.

  在介绍之前,请大家先看一下DinnerNow的系统架构图.相信这会对我们从整体上把握这个产品提供一个切入点.相关图示如下:

   
      

     首先运行网站的首页http://localhost/dinnernow/default.aspx,如下图:
 

  上图中红框标记部分的部分页面页容如下(SearchBar.ascx):
                      
 

< table border = " 0 "  cellspacing = " 2 "  cellpadding = " 2 " >
    
< tr >
        
< td align = " right "  nowrap = " nowrap "   class = " boldWhite " > Food Type  </ td >
        
< td align = " left " >
            
< asp:ObjectDataSource ID = " RestaurantCategoryDataSource "  runat = " server "  SelectMethod = " SelectAll "  TypeName = " DinnerNow.RestaurantCategoryDataSource " />
            
< asp:DropDownList ID = " restaurantCategoryList "  runat = " server "  
                DataSourceID
= " RestaurantCategoryDataSource "  DataTextField = " Description "  
                DataValueField
= " RestaurantId " />                         
        
</ td >
    
</ tr >
</ table >

< table border = " 0 "  cellspacing = " 2 "  cellpadding = " 2 " >
    
< tr >
        
< td align = " right "   class = " boldWhite " > Meal </ td >
        
< td align = " left " >
            
< asp:ObjectDataSource ID = " MenuTypeDataSource "  runat = " server "  SelectMethod = " SelectAll "  TypeName = " DinnerNow.MenuTypeDataSource " />
            
< asp:DropDownList ID = " menuTypeList "  runat = " server "  
                DataSourceID
= " MenuTypeDataSource "  DataTextField = " MenuTypeName "  
                DataValueField
= " MenuTypeName "   />                         
        
</ td >
    
</ tr >
</ table >


     可以看出菜单下拉框选项使用ObjectDataSource方式进行加载,而页面代码中的下列两条语句是所加载类型的说明:
     TypeName="DinnerNow.RestaurantCategoryDataSource'
     TypeName="DinnerNow.MenuTypeDataSource"

   这两个类型我们可以在下列路径下找到:
     DinnerNow.WebUX\Code\DataSources\RestaurantCategoryDataSource.cs
     DinnerNow.WebUX\Code\DataSources\MenuTypeDataSource.cs

     它们两个的功能就是调用相应的SelectAll方法如下(仅以MenuTypeDataSource.cs为例):

     MenuTypeDataSource.cs

    

public  IEnumerable < RestaurantCategory >  SelectAll()
{
    
try
    
{
        
using (MenuSearchServiceClient client = new MenuSearchServiceClient("WSHttpBinding_IMenuSearchService"))
        
{
            
return client.GetRestaurantCategories();
        }

    }

    
catch (Exception)
    
{
       
//@TODO: Need to put some error handling in here
    }

    
return null;
}


      因为代码太简单没什么可说的,下面就根据其所请求的服务绑定项"WSHttpBinding_IMenuSearchService", 在web.config中查找到如下配置节:    

< endpoint address = " http://localhost/DinnerNow/service/MenuSearch.svc "  binding = " wsHttpBinding "
  bindingConfiguration
= " WSHttpBinding_IMenuSearchService "  contract = " MenuSearchService.IMenuSearchService "
  name
= " WSHttpBinding_IMenuSearchService " >
  
< identity >
    
< servicePrincipalName value = " host "   />
  
</ identity >
</ endpoint >  

     而相关的MenuSearch.svc(执行)文件就是其所引用的服务地址.好的,看清了这一块之后,我们切换到刚才所说的第二个解决方案中(DinnerNow - ServicePortfolio2.sln),看一下这个SVC中是如何执行相应逻辑的:)

    在DinnerNow - ServicePortfolio2.sln中的DinnerNow.ServiceHost项目是服务配置站点,我们可从该站点的web.config文件中找出如下内容:
     ...... 

< service behaviorConfiguration = " DinnerNow.Services.MenuSearchServiceBehavior "
   name
= " DinnerNow.Services.MenuSearchService " >
   
< endpoint address = ""  binding = " wsHttpBinding "  contract = " DinnerNow.Services.IMenuSearchService "   />
   
< endpoint address = " mex "  binding = " mexHttpBinding "  contract = " IMetadataExchange "   />
   
< endpoint address = " ajax "  behaviorConfiguration = " DinnerNow.Services.MenuSearchServiceAjax "  
   binding
= " webHttpBinding "  bindingConfiguration = " AjaxBinding "  
   contract
= " DinnerNow.Services.IMenuSearchService "   />
</ service >

     ......

     这里定义了当前服务所使用的contract接口(MenuSearchService)以及所使用的服务MenuSearchService(业务逻辑),
而有关这两部分内容定义如下:

   

[ServiceContract(Namespace  =   " DinnerNow.Services " )]
public   interface  IMenuSearchService
{
    [OperationContract]
    [WebGet]
    IEnumerable
<MenuType> GetMenuTypes();

    [OperationContract]
    [WebGet]
    IEnumerable
<RestaurantCategory> GetRestaurantCategories();

    [OperationContract]
    [WebGet]
    IEnumerable
<RestaurantHeader> FindRestaurant(string postalCode, string menuType, string restaurantCategoryId, string deadline);

    [OperationContract]
    [WebGet]
    IEnumerable
<RestaurantMenuItem> GetMenuItemsForMenu(string restaurantId, string menuType);
}


    该接口定义了搜索菜单的数据获取方法,相信大家通过字面就能看出个大概了,所以我就不多说什么了.
    下面主要说一下MenuSearchService.cs文件(DinnerNow.Services项目下):

   

[AspNetCompatibilityRequirements(RequirementsMode  =  AspNetCompatibilityRequirementsMode.Allowed)]
public   class  MenuSearchService : IMenuSearchService
{
    
IMenuSearchService Members
}

   因为我们在网站客户端调用的是如下方法:    

using  (MenuSearchServiceClient client  =   new  MenuSearchServiceClient( " WSHttpBinding_IMenuSearchService " ))
{
    
return client.GetMenuTypes();
}

    所以对这个方法的使用应该就是对菜单类型数据的加载,而相应的方法定义在DinnerNow.Business\Menu.cs文件中:       

public  IEnumerable < DinnerNow.Business.Data.MenuType >  GetMenuTypes()
{
    var s 
= (from m in db.Menus
            select 
new DinnerNow.Business.Data.MenuType()
            
{
                MenuTypeName 
= m.MenuType.Trim() 
            }
).Distinct();
    
return s.ToList();
}

  这里使用了linq to sql来执行数据的操作。可以这么说, DinnerNow的数据访问和操作基本上都是使用LINQ语法完成的.

     它的作用相当于如下语句(即找出不重复的菜单类型):

  SELECT DISTINCT [t1].[value] AS [MenuTypeName] FROM (SELECT LTRIM(RTRIM([t0].[MenuType])) AS [value]
   FROM [dbo].[Menu] AS [t0]) AS [t1]
    

     这样,对首页的整个数据加载过程就完成了,当然页面上的数据查询操作又是如何进行的呢?

     下面就来说明一下这方面的业务执行流程:

    请再切换回DinnerNow - Web.sln解决方案,还是刚才的那个SearchBar.ascx页面,下面的代码即是完成了搜索提交以及查询操作(详情见注释):

 

< asp:ScriptManagerProxy ID = " ScriptManagerProxy1 "  runat = " server " >
    
< services >
        
< asp:ServiceReference Path = " ~/service/MenuSearch.svc/ajax "   />
    
</ services >
</ asp:ScriptManagerProxy >

   

< script type = " text/javascript " >
function searchButton_Click()
{   
    var DeadLine 
=  $ get ( " <%= deadlineSelect.ClientID %> " ).value;
    
if  (DeadLine == " -1 "   ||  DeadLine == null )
    {
        DeadLine
= " 90 " ;
    }
    
    var MenuType 
=  $ get ( " <%= menuTypeList.ClientID %> " ).value.trim();
    var PostalCode 
=  $ get ( " <%= postalCodeTextBox.ClientID %> " ).value;
    var RestaurantCategory 
=  $ get ( " <%= restaurantCategoryList.ClientID %> " ).value;
    
    var searchUrl 
=   " search.aspx " ;
    var path 
=  document.location.pathname.toLowerCase();
    var isInSearchAspx 
=  path.length >= searchUrl.length  &&  path.substr(path.length  -  searchUrl.length,searchUrl.length)  ==  searchUrl;

    
if  ( ! isInSearchAspx)  // 当前页面是否为搜索页(search.aspx)
    {
        var href 
=   " search.aspx?PostalCode= " + PostalCode + " &MenuType= " + MenuType + " &RestaurantCategory= " + RestaurantCategory + " &DeadLine= " + DeadLine;
        document.location.href 
=  href;      // 当不在搜索页面则将查询参数绑定后跳转到搜索页面   
    }
    
else
    {
        var service 
=   new  DinnerNow.Services.IMenuSearchService();
// 如果在搜索页面,则调用下面的JS方法来查找相当的记录            
        service.FindRestaurant(PostalCode, MenuType, RestaurantCategory, DeadLine, restaurantSearch_onSuccess, restaurantSearch_onFailed,  null );
    }
    
return   false ;
}                    
function restaurantSearch_onSuccess(result) 
// 查询成功
{
    
if  ( typeof (onRestaurantSeachSuccess) != " #ff0000 " )
    {
        onRestaurantSeachSuccess(result);
    }
}
function restaurantSearch_onFailed(result) 
// 查询失败
{
    alert(
" The search has failed " );
}



</ script >

     因为使用了ASP.NET Ajax Extensions,所以上面的代码段里的service.FindRestaurant(PostalCode, MenuType, RestaurantCategory, DeadLine, restaurantSearch_onSuccess, restaurantSearch_onFailed, null);
     写法很接近于我们习惯的C#。

     而实际的JS方法如下:

 

FindRestaurant:function(postalCode,menuType,restaurantCategoryId,deadline,succeededCallback, failedCallback, userContext) {
///   <param name="postalCode" type="String"> System.String </param>
///   <param name="menuType" type="String"> System.String </param>
///   <param name="restaurantCategoryId" type="String"> System.String </param>
///   <param name="deadline" type="String"> System.String </param>
///   <param name="succeededCallback" type="Function" optional="true" mayBeNull="true"></param>
///   <param name="failedCallback" type="Function" optional="true" mayBeNull="true"></param>
///   <param name="userContext" optional="true" mayBeNull="true"></param>
return   this ._invoke( this ._get_path(),  ' FindRestaurant ' , true ,{postalCode:postalCode,menuType:menuType,restaurantCategoryId:restaurantCategoryId,deadline:deadline},succeededCallback,failedCallback,userContext); }

  

     上面代码中的_invoke就是完成一个ajax请求的方法.而succeededCallback和succeededCallback方法分别是ajax成功或失败后的回调函数参数,也是本例中的方法restaurantSearch_onSuccess,restaurantSearch_onFailed.

    而最终ajax请求会成为对如下方法的调用(DinnerNow.Business\Menu.cs文件中):    

public  IEnumerable < DinnerNow.Business.Data.RestaurantHeader >  FindRestaurant( string  postalCode,  string  menuType, Guid restaurantCategoryId,  int  deadline)
{
    var results 
=  from r  in  db.Restaurants
                  join m 
in  db.Menus on r.RestaurantId equals m.RestaurantId
                  
where  m.MenuType  ==  menuType
                  
&&  r.PostalCode  ==  postalCode
                  
&&  r.RestaurantCategoryId  ==  restaurantCategoryId
                  select 
new  Business.Data.RestaurantHeader()
                  {
                      LogoImageLocation 
=  r.LogoImageLocation,
                      Name 
=  r.Name,
                      RestaurantId 
=  r.RestaurantId
                  };

    
return  results.ToList();
}

     这个LINQ语句相当于如下SQL语句(Restaurant,RestaurantId联表查询):
SELECT [t0].[RestaurantId], [t0].[Name], [t0].[LogoImageLocation] FROM [dbo].[Restaurant] AS [t0]
INNER JOIN [dbo].[Menu] AS [t1] ON [t0].[RestaurantId] = [t1].[RestaurantId]
WHERE ([t1].[MenuType] = @p0) AND ([t0].[PostalCode] = @p1) AND ([t0].[RestaurantCategoryId] = @p2)

  
     在搜索这个地方使用了AJAX,主要是为了UE(用户体验).当然在DinnerNow中还有一些地方如选餐, 支付等也使用了AJAX,相信也是出于这方面的考虑)

     说到了这里,今天的内容就要告一段落了.纵观DinnerNow的架构,可以说访问数据库的操作基本上都以LINQ To Sql实现方式。而业务流程(服务)则采用WCF的方式进行封装和调用.而网站上只保留了显示逻辑及AJAX请求操作.这样可以说做到了将数据访问层与业务逻辑层的分离.同时也便于团队开发并进行相应分工。

     因为本人认为可以将开发小组成员分为三组:

  A组负责数据访问接口和相关数据操作(采用LINQ)
      B组负责设计业务流程组织(采用WCF, 后面的购买流程中使用了WWF,将会在下文中详加说明)
     C组负责前台程序逻辑设计包括ajax调用等等

      当然这种分工的好处是让小组成员的长处都能得到发挥,必定有专攻数据操作访问,也有专攻SOA的.有专功LINQ,也有熟练WCF和WF的。当然这只是我的一面之词,目前也只是猜测,如果大家有什么意见,欢迎在回复中进行讨论:)



本文转自 daizhenjun 51CTO博客,原文链接:http://blog.51cto.com/daizhj/337665,如需转载请自行联系原作者

相关文章
|
数据库 数据安全/隐私保护
|
开发框架 数据安全/隐私保护
|
Web App开发 测试技术
|
.NET 开发框架 数据格式
WCF技术剖析之六:为什么在基于ASP.NET应用寄宿(Hosting)下配置的BaseAddress无效
原文:WCF技术剖析之六:为什么在基于ASP.NET应用寄宿(Hosting)下配置的BaseAddress无效 本篇文章来源于几天前一个朋友向我咨询的问题。问题是这样的,他说他采用ASP.NET应用程序的方式对定义的WCF服务进行寄宿(Hosting),并使用配置的方式对服务的BaseAddress进行了设置,但是在创建ServiceHost的时候却抛出InvalidOperationException,并提示相应Address Scheme的BaseAddress找不到。
747 0