繼上一篇(初嘗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#region IMenuSearchService Members
public IEnumerable<Business.Data.MenuType> GetMenuTypes()
{
Business.Menu menu = new DinnerNow.Business.Menu();
return menu.GetMenuTypes();
}
public IEnumerable<Business.Data.RestaurantCategory> GetRestaurantCategories()
{
Business.Menu menu = new DinnerNow.Business.Menu();
return menu.GetRestaurantCategories();
}
public IEnumerable<DinnerNow.Business.Data.RestaurantHeader> FindRestaurant(string postalCode,
string menuType, string restaurantCategoryId, string deadline)
{
Business.Menu menu = new DinnerNow.Business.Menu();
Trace.Write("");
return menu.FindRestaurant(postalCode, menuType, new Guid(restaurantCategoryId),
int.Parse(deadline,CultureInfo.CurrentCulture));
}
public IEnumerable<DinnerNow.Business.Data.RestaurantMenuItem> GetMenuItemsForMenu(string restaurantId, string menuType)
{
Business.Menu menu = new DinnerNow.Business.Menu();
return menu.GetMenuItemsForMenu(new Guid(restaurantId), menuType);
}
#endregion
}
因為我們在網站客戶端調用的是如下方法:
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 ToSql實現方式。而業務流程(服務)則采用WCF的方式進行封裝和調用.而網站上只保留了顯示邏輯及AJAX請求操作.這樣可以說做到了將數據訪問層與業務邏輯層的分離.同時也便於團隊開發並進行相應分工。
因為本人認為可以將開發小組成員分為三組:
A組負責數據訪問接口和相關數據操作(采用LINQ)B組負責設計業務流程組織(采用WCF, 後面的購買流程中使用了WWF,將會在下文中詳加說明)C組負責前台程序邏輯設計包括ajax調用等等當然這種分工的好處是讓小組成員的長處都能得到發揮,必定有專攻數據操作訪問,也有專攻SOA的.有專功LINQ,也有熟練WCF和WF的。當然這只是我的一面之詞,目前也只是猜測,如果大家有什麼意見,歡迎在回復中進行討論:)