其實架構的設計是可以慢慢培養的,難是難在如何判斷當下的情況,也就是說你的情況、專案的情況是如何,如何兼顧各種情況,又保有較佳的設計、維護姓、時效性,因為專案總是有 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/
如果文章對您有用,幫我點一下讚,或是點一下『我要推薦』,這會讓我更有動力的為各位讀者撰寫下一篇文章。
非常謝謝各位的支持與愛護,小弟在此位各位說聲謝謝!!! ^_^