將「留言板」改為 WCF 多層架構,包含五個專案:
| 專案 | 類型 | 角色 |
|---|---|---|
| WebSite | 網站 | 表現層 (留言板 UI) |
| Contract | 類別庫 | 定義資料存取服務契約 (介面) |
| Service | 類別庫 | 實作資料存取服務 |
| Entity | 類別庫 | 留言板實體類別 (Linq to SQL 對應) |
| Host | 主控台 | 承載 WCF 服務 (Self-host) |
參考關係:A→B,D;B→D,System.ServiceModel;C→B,D,System.ServiceModel,System.Data.Linq;D→System.Data.Linq;E→C,System.ServiceModel。
使用 sqlmetal 命令列工具:
sqlmetal /conn:server=xxx;database=GuestBook;uid=xxx;pwd=xxx /map:c:\guestbook.map /code:c:\guestbook.cs /serialization:Unidirectional
/serialization:Unidirectional 自動加上 [DataContract]/[DataMember] 供 WCF 序列化。guestbook.cs 加入 Entity 專案;guestbook.map 部署至服務端。[ServiceContract]
public interface IDataAccess {
[OperationContract] void SendMessage(TbGuestBook gb);
[OperationContract] List<TbGuestBook> GetData();
[OperationContract] void DeleteMessage(string ID);
[OperationContract] void SendReply(TbGuestBook gb);
}
[ServiceBehavior(IncludeExceptionDetailInFaults = true)]
public class DataAccess : IDataAccess {
GuestBook ctx;
public DataAccess() {
XmlMappingSource xms = XmlMappingSource.FromXml(File.ReadAllText(@"c:\guestbook.map"));
ctx = new GuestBook("server=...;database=GuestBook;uid=sa;pwd=...", xms);
ctx.Log = Console.Out; // 輸出 SQL 供除錯
}
public void SendMessage(TbGuestBook gb) { ctx.TbGuestBook.Add(gb); ctx.SubmitChanges(); }
public List<TbGuestBook> GetData() {
return (from gb in ctx.TbGuestBook orderby gb.PostTime descending select gb).ToList();
}
public void DeleteMessage(string ID) {
var gb = ctx.TbGuestBook.Single(m => m.ID == new Guid(ID));
ctx.TbGuestBook.Remove(gb); ctx.SubmitChanges();
}
public void SendReply(TbGuestBook gb) {
var record = ctx.TbGuestBook.Single(m => m.ID == gb.ID);
record.IsReplied = true; record.Reply = gb.Reply; ctx.SubmitChanges();
/* 替代寫法:ctx.ExecuteCommand("update ...", gb.Reply, gb.ID); // 僅 UPDATE 不 SELECT */
}
}
static void Main() {
Uri uri = new Uri("net.tcp://localhost:8080/DataAccessService");
using (var sh = new ServiceHost(typeof(DataAccess), uri)) {
sh.AddServiceEndpoint(typeof(IDataAccess), new NetTcpBinding(), "");
sh.Opened += (s,e) => Console.WriteLine("服務已啟動");
sh.Open(); Console.ReadLine();
}
}
App_Code/GetService.cs 建立 ChannelFactory 動態產生 Proxy:
public static IDataAccess GetDataAccessService() {
var sep = new ServiceEndpoint(
ContractDescription.GetContract(typeof(IDataAccess)),
new NetTcpBinding(),
new EndpointAddress("net.tcp://localhost:8080/DataAccessService"));
return new ChannelFactory<IDataAccess>(sep).CreateChannel();
}
前台頁面 Default.aspx.cs:
protected void btn_SendMessage_Click(...) {
var gb = new TbGuestBook { ID=Guid.NewGuid(), UserName=tb_UserName.Text, Message=tb_Message.Text, PostTime=DateTime.Now, IsReplied=false };
GetService.GetDataAccessService().SendMessage(gb); SetBind();
}
private void SetBind() { rpt_Message.DataSource = GetService.GetDataAccessService().GetData(); rpt_Message.DataBind(); }
管理頁面 Admin.aspx.cs 處理刪除/回覆:
protected void rpt_Message_ItemCommand(...) {
if (e.CommandName=="DeleteMessage") GetService.GetDataAccessService().DeleteMessage(e.CommandArgument.ToString());
if (e.CommandName=="SendReply") {
var gb = new TbGuestBook { ID=new Guid(e.CommandArgument.ToString()), Reply=((TextBox)e.Item.FindControl("tb_Reply")).Text };
GetService.GetDataAccessService().SendReply(gb);
}
SetBind();
}
本系列至此結束。若需更完整範例 (Linq/WCF/MVC 結合),可下載對應原始碼 (框架示範,功能未完整)。