架構設計好簡單系列(4) - 軟體架構設計的翹楚(上)

其實架構的設計是可以慢慢培養的,難是難在如何判斷當下的情況,也就是說你的情況、專案的情況是如何,如何兼顧各種情況,又保有較佳的設計、維護姓、時效性,因為專案總是有 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. 原始畫面 (執行時期)

主畫面

SNAGHTML19206f

新增訂單資料畫面

image

 

B. 原始專案畫面

原本是一個 ASP.NET Web Form 的專案

image

 

需求解釋如下:

1. 這是XX供應商的網頁,他們可以替他們的客戶下訂單

2. 主畫面可以查詢既有的所有客戶清單,目前已下的訂單有哪一些

3. 主畫面有新增訂單的按鈕。

 

二、尋找適合的架構

所謂的適合的架構也可以說是修改最少的架構,且須要具備易修改、易擴充等特性,且不更動原有邏輯,原有的邏輯只是搬動過去而已,之後擴充該邏輯我們可以透過新增 class (類別)、Services (服務) 的方式,甚至套用 Autofac ,直接動態 Load() 加入新的 Assembly,也就是說,我也不去修改原有的程式碼,除非真的有需要,比如:Database 增加欄位,那就得在既有的 SQL 敘述中增加該欄位。而本篇的做法只是考量專案成員 Skill 而使用的叫簡單做法,Autofac 就留給下一篇吧。

 


註解:

沒有絕對完美的架構,只有在這個情境下適不適合而已。尤其在做專案時,要怎麼找出最省時、省力、又兼顧團隊的 Skill、以後維護也方便的方式,這端看我們自己怎麼拿捏分寸。91 哥有一篇文章 [ALM]如何協助團隊改善開發體質,成功導入改善的工具或流程  裡面解釋的很棒。


 

接著,我們可以開始進行實際的原始架構的分析、轉換作業。同樣的,我們依照以下步驟依序地來進行。

 

一、了解原始網站的程式碼架構

原本網站其實不算複雜,在 Visual Studio 下檢視如下:

image

 

二、分離出原始實際存取資料庫的 DataAccess

首先建立一個新的 Cus.DataAccess 專案,並將原始程式碼中在 Models\Data 下面的 DalCusOrders.cs & DBConn.cs 分離出並放置在他的 Orders 資料夾下面。

image

 

三、分析在 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 的內容分別如下:

  • ICustomer
    image

 

  • IEmployee
    image

 

  • IOrder
    image

 

  • IProduct
    image

 

  • IShipper
    image

各位從如上的程式碼中可以看的出來,這些介面,其實也都是原本 DAL 中所提供出來 Method 的服務。這裡只是分門別類而已。

建立起來的專案長相如下:

image

 

五、建立 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 專案的長相如下:

image

而另一個 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 CustomerService
   2:      {
   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 EmployeeService
   2:      {
   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 OrderService
   2:      {
   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 ProductService
   2:      {
   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 ShipperService
   2:      {
   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 畫面一樣,我可以透過客戶查詢訂單,也有新增訂單的按鈕。

SNAGHTML124b912

新增訂單畫面

SNAGHTML124fdad

 

而在這樣的設計下,原本的 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/


 

如果文章對您有用,幫我點一下讚,或是點一下『我要推薦,這會讓我更有動力的為各位讀者撰寫下一篇文章。

非常謝謝各位的支持與愛護,小弟在此位各位說聲謝謝!!! ^_^