其實架構的設計是可以慢慢培養的,難是難在如何判斷當下的情況,也就是說你的情況、專案的情況是如何,如何兼顧各種情況,又保有較佳的設計、維護姓、時效性,因為專案總是有 Schedule 的,同時有要考慮專案成員的 Skill。這就是架構設計的翹楚,因為許多狀況都是非關技術,但也不代表技術不重要,技術可以靠書本取得,但許多當下判斷你得靠經驗來判斷,這就不是書本上會告訴你的了。
在去年,筆者至新竹講了一場關於『架構設計好簡單- 如何快速從Web Form 變成 ASP.NET MVC』課程,課程中,筆者以北風資料庫 Northwind 中簡單的訂單系統為例,講解以分層方式切割現有的 ASP.NET Web Form 專案,將邏輯以 Cus.Business 切割出來、原本資料存取切出 Cus.DataAccess,定義 Interface、Cus.Services 層、Cus.ViewModels 層,再寫一個空的 ASP.NET MVC 4 的專案來存取 Cus.Services 層即可。
在開始之前,我們還有些準備工作要進行,因為並不是重無到有的設計,而是架構的轉換,所以我們必須先了解該網站現有的 Known-How,接著再套用適合的設計,注意,是適合的設計。
首先可分兩步進行:
一、了解網站的原始需求
如何了解原始網站的需求、如何透過頂層思考方式切入進來,可參考『架構設計好簡單- 如何快速從Web Form 變成 ASP.NET MVC』課程的投影片。其中有詳盡的說明。
原始畫面:
A. 原始畫面 (執行時期)
主畫面
新增訂單資料畫面
B. 原始專案畫面
原本是一個 ASP.NET Web Form 的專案
需求解釋如下:
1. 這是XX供應商的網頁,他們可以替他們的客戶下訂單
2. 主畫面可以查詢既有的所有客戶清單,目前已下的訂單有哪一些
3. 主畫面有新增訂單的按鈕。
二、尋找適合的架構
所謂的適合的架構也可以說是修改最少的架構,且須要具備易修改、易擴充等特性,且不更動原有邏輯,原有的邏輯只是搬動過去而已,之後擴充該邏輯我們可以透過新增 class (類別)、Services (服務) 的方式,甚至套用 Autofac ,直接動態 Load() 加入新的 Assembly,也就是說,我也不去修改原有的程式碼,除非真的有需要,比如:Database 增加欄位,那就得在既有的 SQL 敘述中增加該欄位。而本篇的做法只是考量專案成員 Skill 而使用的叫簡單做法,Autofac 就留給下一篇吧。
註解:
沒有絕對完美的架構,只有在這個情境下適不適合而已。尤其在做專案時,要怎麼找出最省時、省力、又兼顧團隊的 Skill、以後維護也方便的方式,這端看我們自己怎麼拿捏分寸。91 哥有一篇文章 [ALM]如何協助團隊改善開發體質,成功導入改善的工具或流程 裡面解釋的很棒。
接著,我們可以開始進行實際的原始架構的分析、轉換作業。同樣的,我們依照以下步驟依序地來進行。
一、了解原始網站的程式碼架構
原本網站其實不算複雜,在 Visual Studio 下檢視如下:
二、分離出原始實際存取資料庫的 DataAccess
首先建立一個新的 Cus.DataAccess 專案,並將原始程式碼中在 Models\Data 下面的 DalCusOrders.cs & DBConn.cs 分離出並放置在他的 Orders 資料夾下面。
三、分析在 Cus.DataAccess 中的 DalCusOrders.cs 中有哪幾種類型的服務
以原始程式碼來說,只以一個 DalCusOrders.cs 來提供服務,以此網站來說,他是訂單的網頁,它除了訂單、也有客戶 Customer 、與產品項目、產品負責人員工清單Employees、貨運清單等來源,如下為 DalCusOrders.cs 原始碼:
1: using System;
2: using System.Collections.Generic;
3: using System.Data;
4: using System.Data.SqlClient;
5: using System.Linq;
6: using System.Web;
7: using UseSQLWebApplication1.Models.Data;
   8:   
	9: namespace UseSQLWebApplication1.Models
  10:  {
	11: public class DalCusOrders
  12:      {
	13: #region 取得所有的 Customer, ContactName 的連絡資訊.
14: /// <summary>
15: /// 取得所有的 Customer, ContactName 的連絡資訊.
16: /// </summary>
17: /// <returns></returns>
18: public DataTable GetCustomerList()
  19:          {
	20: DataAccess dal = new DataAccess();
21: string SqlStatement =
22: @"select DISTINCT C.CustomerID, C.ContactName from Customers C";
23: return dal.Query(SqlStatement).Tables[0];
  24:          }
	25: #endregion
  26:   
	27: #region 使用客戶代碼取得客戶的聯絡人名稱
28: /// <summary>
29: /// 使用客戶代碼取得客戶的聯絡人名稱
30: /// </summary>
31: /// <param name="CustomerId"></param>
32: /// <returns></returns>
33: public object GetCustomerByCustomerID(string CustomerId)
  34:          {
	35: DataAccess dal = new DataAccess();
36: string SqlStatement =
37: @"select TOP 1 C.ContactName from Customers C WHERE C.CustomerID=@CustomerID";
38: SqlParameter[] SqlParames = new SqlParameter[] { new SqlParameter("@CustomerID", CustomerId) };
39: return dal.GetExecuteScalar(SqlStatement, SqlParames);
  40:          }
	41: #endregion
  42:   
	43: #region 取得特定客戶的所有訂單.
44: /// <summary>
45: /// 取得特定客戶的所有訂單.
46: /// </summary>
47: /// <param name="CusID"></param>
48: /// <returns></returns>
49: public DataTable GetByCusID(string CusID)
  50:          {
	51: DataAccess dal = new DataAccess();
52: string SqlStatement = @"SELECT Customers.CustomerID, Customers.CompanyName, Customers.ContactName, Customers.City,
  53:                              [Order Details].OrderID, [Order Details].UnitPrice, Products.ProductID, Products.ProductName, Orders.OrderDate
	
  54:  FROM              Orders INNER JOIN
	
  55:                              [Order Details] ON Orders.OrderID = [Order Details].OrderID INNER JOIN
	
  56:                              Products ON [Order Details].ProductID = Products.ProductID INNER JOIN
	
  57:                              Customers ON Orders.CustomerID = Customers.CustomerID
	
  58:  WHERE          (Orders.CustomerID = @CustomerID)";
	
  59:   
	60: SqlParameter[] SqlParames = new SqlParameter[] {
61: new SqlParameter("@CustomerID", CusID)
  62:              };
	63: return dal.Query(SqlStatement, SqlParames).Tables[0];
  64:          }
	65: #endregion
  66:   
	67: #region 取得產品清單
68: /// <summary>
69: /// 取得產品清單
70: /// </summary>
71: /// <returns></returns>
72: public DataTable GetProducts()
  73:          {
	74: DataAccess dal = new DataAccess();
75: string SqlStatement = @"select P.ProductID, P.ProductName, P.UnitPrice from Products P
  76:  ORDER by P.ProductName";
	77: return dal.Query(SqlStatement).Tables[0];
  78:          }
	79: #endregion
  80:   
	81: #region 取得貨運清單
82: /// <summary>
83: /// 取得貨運清單
84: /// </summary>
85: /// <returns></returns>
86: public DataTable GetShippers()
  87:          {
	88: DataAccess dal = new DataAccess();
89: string SqlStatement = @"select S.ShipperID, S.CompanyName from [Shippers] S";
90: return dal.Query(SqlStatement).Tables[0];
  91:          }
	92: #endregion
  93:   
	94: #region 取得產品金額 by 產品Id.
95: /// <summary>
96: /// 取得產品金額 by 產品Id.
97: /// </summary>
98: /// <param name="ProudctID"></param>
99: /// <returns></returns>
100: public object GetProductPriceByProductID(int ProductID)
 101:          {
	102: DataAccess dal = new DataAccess();
103: string SqlStatement = @"select P.UnitPrice from Products P
 104:  WHERE P.ProductID=@ProductID";
	105: return dal.GetExecuteScalar(SqlStatement, new SqlParameter[] {
106: new SqlParameter("@ProductID", ProductID)
 107:              });
	
 108:          }
	109: #endregion
 110:   
	111: #region 取得產品負責人員工清單
112: /// <summary>
113: /// 取得產品負責人員工清單
114: /// </summary>
115: /// <returns></returns>
116: public DataTable GetEmployees()
 117:          {
	118: DataAccess dal = new DataAccess();
119: string SqlStatement = @"select E.EmployeeID, E.FirstName from Employees E";
120: return dal.Query(SqlStatement).Tables[0];
 121:          }
	122: #endregion
 123:   
	124: #region 新增一筆訂單資料.
125: /// <summary>
126: /// 新增一筆訂單資料.
127: /// </summary>
128: /// <param name="orders"></param>
129: /// <returns></returns>
130: public int AddOrder(Orders orders)
 131:          {
	132: string SqlOrder = @"INSERT INTO [Orders]
 133:             ([CustomerID]
	
 134:             ,[EmployeeID]
	
 135:             ,[OrderDate]
	
 136:             ,[RequiredDate]
	
 137:             ,[ShippedDate]
	
 138:             ,[Freight]
	
 139:             ,[ShipName])
	
 140:       VALUES
	
 141:             (@CustomerID
	
 142:             ,@EmployeeID
	
 143:             ,@OrderDate
	
 144:             ,@RequiredDate
	
 145:             ,@ShippedDate
	
 146:             ,@Freight
	
 147:             ,@ShipName);select @@IDENTITY";
	
 148:   
	149: string SqlOrderDetails = @"INSERT INTO [dbo].[Order Details]
 150:             ([OrderID]
	
 151:             ,[ProductID]
	
 152:             ,[UnitPrice]
	
 153:             ,[Quantity]
	
 154:             ,[Discount])
	
 155:       VALUES
	
 156:             (@OrderID
	
 157:             ,@ProductID
	
 158:             ,@UnitPrice
	
 159:             ,@Quantitys
	
 160:             ,@Discount)";
	
 161:   
	162: SqlConnection connection = new SqlConnection(new DBConn().ConnectionString);
163: SqlTransaction tran = null;
164: try
 165:              {
	166: DataAccess dal = new DataAccess();
167: int result = 0;
 168:                  connection.Open();
	
 169:                  tran = connection.BeginTransaction();
	170: SqlCommand cmd = new SqlCommand(SqlOrder, connection, tran);
171: SqlParameter[] ParamOrder = new SqlParameter[] {
172: new SqlParameter("@CustomerID", orders.CustomerID),
173: new SqlParameter("@EmployeeID", orders.EmployeeID),
174: new SqlParameter("@OrderDate", orders.OrderDate),
175: new SqlParameter("@RequiredDate", orders.RequiredDate),
176: new SqlParameter("@ShippedDate", orders.ShippedDate),
177: new SqlParameter("@Freight", orders.Freight),
178: new SqlParameter("@ShipName", orders.ShipName)
 179:                  };
	180: object identity = dal.ExecuteScalar(cmd, SqlOrder, CommandType.Text, ref connection, ref tran, ParamOrder);
 181:                  Order_Details order_detail = orders.ORDER_DETAILS.FirstOrDefault();
	182: if(order_detail!=null)
 183:                  {
	184: SqlParameter[] ParamOrderDetails = new SqlParameter[] {
185: new SqlParameter("@OrderID", identity),
186: new SqlParameter("@ProductID", order_detail.ProductID),
187: new SqlParameter("@UnitPrice", order_detail.UnitPrice),
188: new SqlParameter("@Quantitys", order_detail.Quantity),
189: new SqlParameter("@Discount", order_detail.Discount)
 190:                      };
	191: result += dal.ExecuteSQL(cmd, SqlOrderDetails, CommandType.Text, ref connection, ref tran, ParamOrderDetails);
 192:                  }
	
 193:                  
	
 194:                  tran.Commit();
	195: return result;
 196:              }
	197: catch(Exception ex)
 198:              {
	
 199:                  tran.Rollback();
	200: throw ex;
 201:              }
	202: finally
 203:              {
	204: if (connection.State != ConnectionState.Closed)
 205:                      connection.Close();
	
 206:                  connection.Dispose();
	
 207:              }
	
 208:          }
	209: #endregion
 210:      }
	
 211:  }
筆者先以服務種類、也就是 Method 的服務類型來分門別類,此時,我們可以發現若依據服務的對象可以區分為 Customer (客戶)、Employee (員工)、 Order (訂單)、Product (產品)、Shipper (貨運) 等等。
四、建立 Cus.Interfaces 專案
如上,依據服務對象我們發現有五大服務,因此我們設計了: ICustomer、IEmployee、 IOrder、IProduct、IShipper 五大 Interface ,之後由 Services 專案提供出來。
這五大 Interface 的內容分別如下:
各位從如上的程式碼中可以看的出來,這些介面,其實也都是原本 DAL 中所提供出來 Method 的服務。這裡只是分門別類而已。
建立起來的專案長相如下:
五、建立 Cus.Business 專案
由於此專案其實也沒什麼商業邏輯,但難保以後不會增加其他相關的商業邏輯,所以,還是得要建立一個 Business 專案,只不過這裡的Cus.Business 只會補足待會 Cus.Services 專案所需要的相關實作,且使用 Cus.DataAccess 為主要來源。
所以這個專案只會有一個 BizCustomerOrder.cs 檔案,內容的實作如下程式碼:
1: using Cus.DataAccess.Order;
2: using Cus.Interfaces.Order;
3: using Cus.Models.Entities;
4: using Cus.ViewModels;
5: using System;
6: using System.Collections.Generic;
7: using System.Data;
8: using System.Linq;
9: using System.Text;
10: using WistronITs.Data.DAL;
  11:   
	12: namespace Cus.Business.Order
  13:  {
	14: public class BizCustomerOrder: ICustomer, IOrder, IProduct, IEmployee, IShipper
  15:      {
	16: private DalCusOrders _context = null;
17: protected DalCusOrders Context
  18:          {
	
  19:              get
	
  20:              {
	21: if(_context==null)
22: _context = new DalCusOrders();
23: return _context;
  24:              }
	
  25:          }
	
  26:   
	27: #region Gelis DAL Framework
28: private MSSQLObject _MSSql = null;
29: protected MSSQLObject MSSql
  30:          {
	
  31:              get 
	
  32:              { 
	33: if(_MSSql==null)
34: _MSSql = new MSSQLObject(new WistronITs.Data.DAL.DataAccess());
35: return _MSSql;
  36:              }
	
  37:          }
	38: #endregion
  39:   
	40: public BizCustomerOrder() { }
41: public IEnumerable<CusOrders> GetByCusID(string CusID)
  42:          {
	43: return MSSql.GetEnumerableByDataTable<CusOrders>(Context.GetByCusID(CusID));
  44:          }
	
  45:   
	46: public IEnumerable<Customers> GetCustomerList()
  47:          {
	48: return MSSql.GetEnumerableByDataTable<Customers>(Context.GetCustomerList());
  49:          }
	
  50:   
	51: public object GetCustomerByCustomerID(string CustomerId)
  52:          {
	53: return Context.GetCustomerByCustomerID(CustomerId);
  54:          }
	
  55:   
	56: public object GetProductPriceByProductID(int ProductID)
  57:          {
	58: return Context.GetProductPriceByProductID(ProductID);
  59:          }
	
  60:   
	61: public IEnumerable<Products> GetProducts()
  62:          {
	63: return MSSql.GetEnumerableByDataTable<Products>(Context.GetProducts());
  64:          }
	
  65:   
	66: public int AddOrder(Orders orders)
  67:          {
	68: return Context.AddOrder(orders);
  69:          }
	
  70:   
	71: public IEnumerable<Employees> GetEmployees()
  72:          {
	73: return MSSql.GetEnumerableByDataTable<Employees>(Context.GetEmployees());
  74:          }
	
  75:   
	76: public IEnumerable<Shippers> GetShippers()
  77:          {
	78: return MSSql.GetEnumerableByDataTable<Shippers>(Context.GetShippers());
  79:          }
	
  80:      }
	
  81:  }
上面程式碼其實非常的精簡,其中,使用了我自己 DAL Framework 中所提供的 GetEnumerableByDataTable<T>() 方法,幫我來將任意 DataTable 轉換為 IEnumerable<T> 型態的資料回來。
這個 Cus.Business 專案的長相如下:
而另一個 EdmCustomerOrder.cs 是以 Entity Framework 為 DAL 的實作。
六、建立 Cus.Services 專案
接著就是建立 Cus.Services 的服務專案了,前面 Cus.Business 已經完成了實作,現在只要完成 Cus.Services ,那麼前端不管是哪一類的應用程式只要知道 Cus.Services 所提供的服務即可,不需要知道後端的實作為何。如果我們再將 Cus.Services 開放一組 Web API 出來,那麼前端不見得一定是 ASP.NET MVC,甚至是手機、平板、其他平台的應用程式都沒有關係。
在這個專案中,筆者根據服務類型拆五個檔案:
- CustomerService.cs
1: public class CustomerService2: {3: private ICustomer _Customer = null;4: public CustomerService(ICustomer Customer)5: {6: _Customer = Customer;7: }8:9: public IEnumerable<CusOrders> GetByCusID(string CusID)10: {11: return _Customer.GetByCusID(CusID);12: }13:14: public IEnumerable<Customers> GetCustomerList()15: {16: return _Customer.GetCustomerList();17: }18:19: public object GetCustomerByCustomerID(string CustomerId)20: {21: return _Customer.GetCustomerByCustomerID(CustomerId);22: }23: }
- EmployeeService.cs
1: public class EmployeeService2: {3: private IEmployee _Employee = null;4: public EmployeeService(IEmployee Employee)5: {6: _Employee = Employee;7: }8:9: public IEnumerable<Employees> GetEmployees()10: {11: return _Employee.GetEmployees();12: }13: }
- OrderService.cs
1: public class OrderService2: {3: private IOrder _Order = null;4: public OrderService(IOrder Order)5: {6: _Order = Order;7: }8:9: public int AddOrder(Orders orders)10: {11: return _Order.AddOrder(orders);12: }13: }
- ProductService.cs
1: public class ProductService2: {3: private IProduct _Product = null;4: public ProductService(IProduct Product)5: {6: _Product = Product;7: }8:9: public object GetProductPriceByProductID(int ProductID)10: {11: return _Product.GetProductPriceByProductID(ProductID);12: }13:14: public IEnumerable<Products> GetProducts()15: {16: return _Product.GetProducts();17: }18: }
- ShipperService.cs
1: public class ShipperService2: {3: private IShipper _Shipper = null;4: public ShipperService(IShipper Shipper)5: {6: _Shipper = Shipper;7: }8:9: public IEnumerable<Shippers> GetShippers()10: {11: return _Shipper.GetShippers();12: }13: }
各位會發現,程式碼都只是叫用介面的方法而已。
七、建立 Cus.ViewModels 專案,使用原始畫面來思考設計ViewModels
ViewModel 筆者暫時將 DropDownList & GridView 的資料放置在 QueryViewModel.cs 中,一次都由 Server 撈回來。
1: using Cus.Models.Entities;
2: using System;
3: using System.Collections.Generic;
4: using System.Linq;
5: using System.Web;
   6:   
	7: namespace Cus.ViewModels
   8:  {
	9: public class QueryViewModel
  10:      {
	11: /// <summary>
12: /// 主畫面 Grid
13: /// </summary>
14: public IEnumerable<CusOrders> CUS_Ordes { get; set; }
15: /// <summary>
16: /// 客戶下拉清單
17: /// </summary>
18: public IEnumerable<Customers> CustomerList { get; set; }
19: public QueryParam QUERY_PARAM { get; set; }
  20:      }
	
  21:  }
其中,Customers 類別欄位為:
1: public class Customers
   2:      {
	3: public string CustomerID { get; set; }
4: public string CompanyName { get; set; }
5: public string ContactName { get; set; }
6: public string ContactTitle { get; set; }
7: public string Address { get; set; }
8: public string City { get; set; }
9: public string Region { get; set; }
10: public string PostalCode { get; set; }
11: public string Country { get; set; }
12: public string Phone { get; set; }
13: public string Fax { get; set; }
  14:      }
CusOrder 類別欄位為:
1: public class CusOrders
   2:      {
	3: public string CustomerID { get; set; }
4: public string CompanyName { get; set; }
5: public string ContactName { get; set; }
6: public string City { get; set; }
7: public int OrderID { get; set; }
8: public decimal UnitPrice { get; set; }
9: public int ProductID { get; set; }
10: public string ProductName { get; set; }
11: public DateTime OrderDate { get; set; }
  12:      }
再來,最重要的算是新增訂單的 ViewModel 了。
1: /// <summary>
2: /// 新增訂單資料的ViewModel
3: /// </summary>
4: public class AddOrderViewModel
   5:      {
	6: public int OrderID { get; set; }
7: public string CustomerID { get; set; }
8: public string ContactName { get; set; }
9: public IEnumerable<Customers> CustomerList { get; set; }
10: public int ProductID { get; set; }
11: public IEnumerable<Products> ProductList { get; set; }
12: public int EmployeeID { get; set; }
13: public IEnumerable<Employees> EmployeeList { get; set; }
14: public string City { get; set; }
15: public decimal UnitPrice { get; set; }
16: public short Quantity { get; set; }
17: public int ShipperID { get; set; }
18: public IEnumerable<Shippers> ShipperList { get; set; }
19: public string ShipperAddress { get; set; }
20: public DateTime OrderDate { get; set; }
  21:      }
八、建立 ASP.NET MVC 專案 (UseSQLMvc4Application1)
有了 ViewModel 後,那麼現在就只要在這個全新的 MVC 專案中,設計好 Controller 與 View 即可,因為服務都由 Cus.Services 中所提供的,所以基本上只要在 Controller 完成既有的畫面流程,剩下的只是重新拉 View 畫面而已。
首先,在 Controller 資料夾下面增加 CusController.cs 的 Controller 檔案,針對這個需求非常的簡單,我們只需要幾個 Action ,一個是首頁的查詢 Index()、Index(查詢條件) 與 新增訂單的 AddOrder()、AddOrder(AddOrderViewModel OrderViewModel) 即可。
由於 Controller 的部分真的非常的易懂,所以筆者直接列出程式碼給各位參考,如下:
1: using Cus.Business.Order;
2: using Cus.Models.Entities;
3: using Cus.Services.Order;
4: using Cus.ViewModels;
5: using System;
6: using System.Collections.Generic;
7: using System.Linq;
8: using System.Web;
9: using System.Web.Mvc;
  10:   
	11: namespace UseSQLMvc4Application1.Controllers
  12:  {
	13: public class CusController : Controller
  14:      {
	15: //
16: // GET: /Cus/
17: CustomerService context = new CustomerService(new BizCustomerOrder());
18: ProductService productContext = new ProductService(new BizCustomerOrder());
19: EmployeeService employeeContext = new EmployeeService(new BizCustomerOrder());
20: ShipperService shipperContext = new ShipperService(new BizCustomerOrder());
21: OrderService orderContext = new OrderService(new BizCustomerOrder());
  22:   
	23: #region CustomerDropDownList
24: private MultiSelectList GetCustomerDropDown(string SelectedValue)
  25:          {
	26: return new MultiSelectList(
  27:                  context.GetCustomerList(), 
	28: "CustomerID",
29: "ContactName", new string[] { SelectedValue });
  30:          }
	31: #endregion
  32:   
	33: public ActionResult Index()
  34:          {
	35: QueryViewModel query = new QueryViewModel();
36: query.CUS_Ordes = context.GetByCusID(string.Empty);
37: //保存下拉清單資料
38: ViewBag.CustomerLists = GetCustomerDropDown(string.Empty);
39: return View(query);
  40:          }
	
  41:   
	
  42:          [HttpPost]
	43: public ActionResult Index(QueryViewModel param)
  44:          {
	45: string _param = param.QUERY_PARAM.CustomerID;
46: QueryViewModel query = new QueryViewModel();
  47:              query.CUS_Ordes = context.GetByCusID(_param);
	48: //保存下拉清單資料
  49:              ViewBag.CustomerLists = GetCustomerDropDown(_param);
	50: return View(query);
  51:          }
	
  52:   
	53: public ActionResult AddOrder()
  54:          {
	
  55:              AddOrderViewModel addOrder = GetAddOrderViewModel();
	56: return View(addOrder);
  57:          }
	
  58:   
	59: private AddOrderViewModel GetAddOrderViewModel()
  60:          {
	61: AddOrderViewModel addOrder = new AddOrderViewModel()
  62:              {
	
  63:                  CustomerList = context.GetCustomerList(),
	
  64:                  OrderDate = DateTime.Now,
	
  65:                  ProductList = productContext.GetProducts(),
	
  66:                  EmployeeList = employeeContext.GetEmployees(),
	
  67:                  ShipperList = shipperContext.GetShippers(),
	
  68:                  Quantity = 1
	
  69:              };
	70: return addOrder;
  71:          }
	
  72:   
	
  73:          [HttpPost]
	74: public ActionResult AddOrder(AddOrderViewModel OrderViewModel)
  75:          {
	76: Orders order = new Orders()
  77:              {
	
  78:                  CustomerID = OrderViewModel.CustomerID,
	
  79:                  EmployeeID = OrderViewModel.EmployeeID,
	
  80:                  OrderDate = DateTime.Now,
	
  81:                  RequiredDate = DateTime.Now.AddDays(7),
	
  82:                  ShippedDate = DateTime.Now.AddDays(2),
	
  83:                  Freight = 20,
	
  84:                  ShipName = shipperContext.GetShippers().Where(c => c.ShipperID == OrderViewModel.ShipperID).FirstOrDefault().CompanyName,
	85: ORDER_DETAILS = new List<Order_Details>(new Order_Details[] {
86: new Order_Details() {
  87:                          ProductID = OrderViewModel.ProductID,
	
  88:                          Quantity = OrderViewModel.Quantity,
	89: UnitPrice = decimal.Parse(productContext.GetProductPriceByProductID(OrderViewModel.ProductID).ToString()),
  90:                          Discount = 1
	
  91:                      }
	
  92:                  })
	
  93:              };
	94: int result = orderContext.AddOrder(order);
95: if (result > 0)
96: return RedirectToAction("Index");
97: else
98: return View();
  99:          }
	
 100:      }
	
 101:  }
而 View 只會有兩個,一個就是首頁的查詢 Index.cshtml
   1:  @model Cus.ViewModels.QueryViewModel
	
   2:   
	
   3:  @{
	
   4:      ViewBag.Title = "Index";
	
   5:  }
	
   6:   
	7: <h2>Index</h2>
   8:   
	
   9:  @using (Html.BeginForm("Index", "Cus"))
	
  10:  {
	11: <span>客戶連絡名稱:</span>@Html.DropDownListFor(model => model.QUERY_PARAM.CustomerID, ViewBag.CustomerLists as MultiSelectList)
12: <br />
13: <input type="submit" value="查詢客戶訂單" /><br />
14: <p>
  15:      @Html.ActionLink("新增訂單", "AddOrder")
	16: </p>
  17:  }
	
  18:   
	19: <table>
20: <tr>
21: <th>
22: @Html.DisplayNameFor(model => model.CUS_Ordes.FirstOrDefault().CustomerID)
23: </th>
24: <th>
25: @Html.DisplayNameFor(model => model.CUS_Ordes.FirstOrDefault().CompanyName)
26: </th>
27: <th>
28: @Html.DisplayNameFor(model => model.CUS_Ordes.FirstOrDefault().ContactName)
29: </th>
30: <th>
31: @Html.DisplayNameFor(model => model.CUS_Ordes.FirstOrDefault().City)
32: </th>
33: <th>
34: @Html.DisplayNameFor(model => model.CUS_Ordes.FirstOrDefault().OrderID)
35: </th>
36: <th>
37: @Html.DisplayNameFor(model => model.CUS_Ordes.FirstOrDefault().UnitPrice)
38: </th>
39: <th>
40: @Html.DisplayNameFor(model => model.CUS_Ordes.FirstOrDefault().ProductID)
41: </th>
42: <th>
43: @Html.DisplayNameFor(model => model.CUS_Ordes.FirstOrDefault().ProductName)
44: </th>
45: <th>
46: @Html.DisplayNameFor(model => model.CUS_Ordes.FirstOrDefault().OrderDate)
47: </th>
48: <th></th>
49: </tr>
  50:   
	
  51:  @foreach (var item in Model.CUS_Ordes) {
	52: <tr>
53: <td>
54: @Html.DisplayFor(modelItem => item.CustomerID)
55: </td>
56: <td>
57: @Html.DisplayFor(modelItem => item.CompanyName)
58: </td>
59: <td>
60: @Html.DisplayFor(modelItem => item.ContactName)
61: </td>
62: <td>
63: @Html.DisplayFor(modelItem => item.City)
64: </td>
65: <td>
66: @Html.DisplayFor(modelItem => item.OrderID)
67: </td>
68: <td>
69: @Html.DisplayFor(modelItem => item.UnitPrice)
70: </td>
71: <td>
72: @Html.DisplayFor(modelItem => item.ProductID)
73: </td>
74: <td>
75: @Html.DisplayFor(modelItem => item.ProductName)
76: </td>
77: <td>
78: @Html.DisplayFor(modelItem => item.OrderDate)
79: </td>
80: <td>
  81:              @Html.ActionLink("Edit", "Edit", new { /* id=item.PrimaryKey */ }) |
	
  82:              @Html.ActionLink("Details", "Details", new { /* id=item.PrimaryKey */ }) |
	
  83:              @Html.ActionLink("Delete", "Delete", new { /* id=item.PrimaryKey */ })
	84: </td>
85: </tr>
  86:  }
	
  87:   
	88: </table>
另一個就是新增訂單 AddOrder.cshtml
   1:  @model Cus.ViewModels.AddOrderViewModel
	
   2:   
	
   3:  @{
	
   4:      ViewBag.Title = "AddOrder";
	
   5:  }
	
   6:   
	7: <h2>AddOrder</h2>
   8:   
	
   9:  @using (Html.BeginForm("AddOrder", "Cus"))
	
  10:  {
	
  11:      @Html.AntiForgeryToken()
	
  12:      @Html.ValidationSummary(true)
	
  13:   
	14: <fieldset>
15: <legend>AddOrderViewModel</legend>
  16:   
	17: <div class="editor-label">
  18:              聯絡人名稱:
	19: </div>
20: <div class="editor-field">
21: @Html.DropDownListFor(model => model.CustomerID, new MultiSelectList(Model.CustomerList, "CustomerID", "ContactName"))
22: @Html.ValidationMessageFor(model => model.CustomerID)
23: </div>
  24:   
	25: <div class="editor-label">
  26:              產品名稱:
	27: </div>
28: <div class="editor-field">
29: @*@Html.EditorFor(model => model.ProductName)*@
30: @Html.DropDownListFor(model => model.ProductID, new MultiSelectList(Model.ProductList, "ProductID", "ProductName"))
31: @Html.ValidationMessageFor(model => model.ProductID)
32: </div>
  33:   
	34: <div class="editor-label">
  35:              產品接洽人員:
	36: </div>
37: <div class="editor-field">
38: @Html.DropDownListFor(model => model.EmployeeID, new MultiSelectList(Model.EmployeeList, "EmployeeID", "FirstName"))
39: @Html.ValidationMessageFor(model => model.EmployeeID)
40: </div>
  41:   
	42: <div class="editor-label">
  43:              數量:
	44: </div>
45: <div class="editor-field">
46: @Html.EditorFor(model => model.Quantity)
47: @Html.ValidationMessageFor(model => model.Quantity)
48: </div>
  49:   
	50: <div class="editor-label">
  51:              貨運名稱:
	52: </div>
53: <div class="editor-field">
54: @Html.DropDownListFor(model => model.ShipperID, new MultiSelectList(Model.ShipperList, "ShipperID", "CompanyName"))
55: @Html.ValidationMessageFor(model => model.ShipperID)
56: </div>
  57:   
	58: <div class="editor-label">
59: @Html.LabelFor(model => model.ShipperAddress)
60: </div>
61: <div class="editor-field">
62: @Html.EditorFor(model => model.ShipperAddress)
63: @Html.ValidationMessageFor(model => model.ShipperAddress)
64: </div>
  65:   
	66: <div class="editor-label">
67: @Html.LabelFor(model => model.OrderDate)
68: </div>
69: <div class="editor-field">
70: @Html.TextBoxFor(model => model.OrderDate, string.Format("{0:yyyy/MM/dd}", Model.OrderDate))
71: </div>
  72:   
	73: <p>
74: <input type="submit" value="Create" />
75: </p>
76: </fieldset>
  77:  }
	
  78:   
	79: <div>
  80:      @Html.ActionLink("Back to List", "Index")
	81: </div>
  82:   
	
  83:  @section Scripts {
	
  84:      @Scripts.Render("~/bundles/jqueryval")
	
  85:  }
執行的畫面如下:
查詢訂單,如同原本 Web Form 畫面一樣,我可以透過客戶查詢訂單,也有新增訂單的按鈕。
新增訂單畫面
而在這樣的設計下,原本的 Web Form 想要存取 Cus.Services 也只需要在極少的修改之下就完成了,因為Web Form 的 GridView也可以接受 IEnumerable<Customers> 型態的資料作為 DataSource。
不過在這個範例中並未使用如 Autofac 等 IoC 套件將 Controller 與 Cus.Services 的耦合度降低。下一篇,筆者將介紹如何再將這個範例改以 Autofac 來實現,使用 Autofac 的話,Cus.Services 的部分就不會這麼設計了,也不會有 Cus.Interface 這個專案了。不過這就留給下一次吧 ^_^
總結:
	
	其實架構的設計是可以慢慢培養的,難是難在如何判斷當下的情況,也就是說你的情況、專案的情況是如何,如何兼顧各種情況,又保有較佳的設計、維護姓、時效性,因為專案總是有 Schedule 的,同時有要考慮專案成員的 Skill。這就是架構設計的翹楚,因為許多狀況都是非關技術,但也不代表技術不重要,技術可以靠書本取得,但許多當下判斷你得靠經驗來判斷,這就不是書本上會告訴你的了。
 
簽名:
學習是一趟奇妙的旅程
這當中,有辛苦、有心酸、也有成果。有時也會有瓶頸。要能夠繼續勇往直前就必須保有一顆最熱誠的心。
軟體開發之路(FB 社團):https://www.facebook.com/groups/361804473860062/
Gelis 程式設計訓練營(粉絲團):https://www.facebook.com/gelis.dev.learning/
如果文章對您有用,幫我點一下讚,或是點一下『我要推薦』,這會讓我更有動力的為各位讀者撰寫下一篇文章。
非常謝謝各位的支持與愛護,小弟在此位各位說聲謝謝!!! ^_^






















