DinnerNow中的WCF應用 --- 首頁數據加載

  繼上一篇(初嘗dinnernow)之後,通過配置並驅動起了web應用。從今天起本系列文章將以一個購物流程爲主線,介紹一下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的。當然這只是我的一面之詞,目前也只是猜測,如果大家有什麼意見,歡迎在回覆中進行討論:)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章