本篇介绍Domain Model模式,一个银行领域建模,涉及帐号的创建以及帐号之间的现金转账的小程序。
1. POCO 和 PI
前面提到过,Domain Model 与 Active Record 模式不同,它不知道持久化。术语持久化(persistence ignorance,PI)表示普通CLR对象(plain old common runtime object,POCO)业务实体的朴实本质。通常Repository模式(后续中将会介绍该模式)将 Domain Model 的业务对象持久化。
2. 示例程序
创建名为Terminator.Practice.DomainModel的解决方案,并添加下面的类库项目:
Terminator.Practice.DomainModel.Model -- 包含应用程序内所有的业务逻辑。领域对象将存放在此处,并与其他对象建立关系,从而表示应用程序正则构建的银行领域。该项目还将以接口的形式为领域对象持久化和检索定义契约,采用Repository模式来实现所有的持久化管理需求。Model项目不会引用其他任何项目,从而确保:让它与任何基础设施关注点保持隔离,只关注业务领域。
Terminator.Practice.DomainModel.AppService -- 充当应用程序的接口(API)。表示层将通过消息(简单的数据传输对象,后续中将会介绍消息传送模式)与 AppService 通信。AppService 还将定义视图模型,这些是领域模型的展开视图,只用于数据显示(后续中将会更详细的介绍该方式)。
Terminator.Practice.DomainModel.Repository -- 包含Model项目中定义的资源库接口的具体实现。Repository引用了Model项目,从而从数据库提取并持久化领域对象,Repository项目只关注领域对象持久化和检索的责任。
添加一个新的Web应用程序 Terminator.Practice.DomainModel.UI.Web -- Web项目负责应用程序的表示和用户体验需求,这个项目只与
AppService交互,并接收专门为用户体验视图创建的强类型视图模型。
对Repository项目添加对Model项目的引用,AppService项目添加对Model和Repository项目的引用,Web项目添加对AppService项目的引用。
创建名为BankAccount.mdf的数据库,创建两张表:
列名 | 数据类型 | 是否允许空 |
BankAccountId | uniqueidentifier,Primary key | False |
Balance | money | False |
CustomerRef | nvarchar(50) | False |
列名 | 数据类型 | 是否允许空 |
BankAccountId | uniqueidentifier | False |
Deposit | money | False |
Withdrawal | money | False |
Reference | nvarchar(50) | False |
Date | datetime | False |
现在开始领域建模,在这个场景中,BankAccount为发生的每个动作创建一个Transaction对象,类似于操作日志的功能。
在Model项目中添加一个新类Transaction:

1 using System; 2 3 namespace Terminator.Practice.DomainModel.Model 4 { 5 public class Transaction 6 { 7 public Transaction(decimal deposit, decimal withdrawal, string reference, DateTime date) 8 { 9 Deposit = deposit; 10 Withdrawal = withdrawal; 11 Reference = reference; 12 Date = date; 13 } 14 15 /// <summary> 16 /// 存款金额 17 /// </summary> 18 public decimal Deposit 19 { get; internal set; } 20 /// <summary> 21 /// 取出金额 22 /// </summary> 23 public decimal Withdrawal 24 { get; internal set; } 25 26 public string Reference 27 { get; internal set; } 28 29 public DateTime Date 30 { get; internal set; } 31 } 32 }
注:Transaction对象被称为值对象,这是DDD(领域驱动设计)中的一个术语,后续中将会介绍DDD。
添加第二个类 BankAccount:

1 using System; 2 using System.Collections.Generic; 3 4 namespace Terminator.Practice.DomainModel.Model 5 { 6 public class BankAccount 7 { 8 private readonly IList<Transaction> _transactions; 9 10 public BankAccount() : this(Guid.NewGuid(), 0, new List<Transaction>(), "") 11 { 12 _transactions.Add(new Transaction(0m, 0m, "account created", DateTime.Now)); 13 } 14 15 public BankAccount(Guid Id, decimal balance, IList<Transaction> transactions, string customerRef) 16 { 17 AccountNo = Id; 18 Balance = balance; 19 _transactions = transactions; 20 CustomerRef = customerRef; 21 } 22 23 public Guid AccountNo { get; internal set; } 24 /// <summary> 25 /// 账户金额 26 /// </summary> 27 public decimal Balance { get; internal set; } 28 29 public string CustomerRef { get; set; } 30 31 /// <summary> 32 /// 是否可以取出某个金额 33 /// </summary> 34 /// <param name="amount"></param> 35 /// <returns></returns> 36 public bool CanWithdraw(decimal amount) 37 { 38 return (Balance >= amount); 39 } 40 41 /// <summary> 42 /// 取出某金额 43 /// </summary> 44 /// <param name="amount"></param> 45 /// <param name="reference"></param> 46 public void Withdraw(decimal amount, string reference) 47 { 48 if (!CanWithdraw(amount)) 49 { 50 throw new InsufficientFundsException(); 51 } 52 Balance -= amount; 53 _transactions.Add(new Transaction(0m, amount, reference, DateTime.Now)); 54 } 55 56 /// <summary> 57 /// 存款 58 /// </summary> 59 /// <param name="amount"></param> 60 /// <param name="reference"></param> 61 public void Deposit(decimal amount, string reference) 62 { 63 Balance += amount; 64 _transactions.Add(new Transaction(amount, 0m, reference, DateTime.Now)); 65 } 66 67 public IEnumerable<Transaction> GetTransactions() 68 { 69 return _transactions; 70 } 71 } 72 }
BankAccount类有3个简单的方法:CanWithdraw,Withdraw,Deposit。因为有一个CanWithdraw方法,所以应该期望调用代码在尝试从某个帐号取出现金时使用Test-Doer(先测试再执行)模式:
if(myBankAccount.CanWithdraw(amount)) { myBankAccount.Withdraw(amount); }
如果在不进行检查的情况下对一个没有足够现金金额的帐号使用Withdraw方法,则应该抛出一个异常,为此,定义一个异常类,向Model项目添加InsufficientFundsException类:
using System; namespace Terminator.Practice.DomainModel.Model { public class InsufficientFundsException : ApplicationException { } }
现在需要某种方法来持久化BankAccount和Transaction,因为不希望污染Domain Model项目,所以添加Repository的接口来定义契约,以满足实体的持久化和检索需求,这就是前面提到的PI和POCO的概念。
创建一个带有如下契约的接口IBankAccountRepository:
using System; using System.Collections.Generic; namespace Terminator.Practice.DomainModel.Model { public interface IBankAccountRepository { void Add(BankAccount bankAccount); void Save(BankAccount bankAccount); IEnumerable<BankAccount> FindAll(); BankAccount FindBy(Guid AccountId); } }
向Model项目中添加BankAccountService类:

1 using System; 2 3 namespace Terminator.Practice.DomainModel.Model 4 { 5 public class BankAccountService 6 { 7 private readonly IBankAccountRepository _bankAccountRepository; 8 9 public BankAccountService(IBankAccountRepository bankAccountRepository) 10 { 11 _bankAccountRepository = bankAccountRepository; 12 } 13 14 public void Transfer(Guid accountNoTo, Guid accountNoFrom, decimal amount) 15 { 16 BankAccount bankAccountTo = _bankAccountRepository.FindBy(accountNoTo); 17 BankAccount bankAccountFrom = _bankAccountRepository.FindBy(accountNoFrom); 18 19 if (!bankAccountFrom.CanWithdraw(amount)) 20 { 21 throw new InsufficientFundsException(); 22 } 23 bankAccountTo.Deposit(amount, "From Acc " + bankAccountFrom.CustomerRef + " "); 24 bankAccountFrom.Withdraw(amount, "Transfer To Acc " + bankAccountTo.CustomerRef + " "); 25 26 _bankAccountRepository.Save(bankAccountTo); 27 _bankAccountRepository.Save(bankAccountFrom); 28 } 29 } 30 }
在BankAccountService 的当前实现中,在保存两个银行帐号之间发生的任何错误均会让数据处于非法状态,因此我们需要一个事务保护机制(本篇没有这么做),后续中将会介绍UoW(工作单元模式),它能够确保所有的事务作为一个原子动作进行提交,万一出现异常就回滚。
现在可以编写方法来持久化BankAccount和Transaction对象,在Repository项目添加一个BankAccountRepository类,该类将实现接口IBankAccountRepository:

1 using System; 2 using System.Collections.Generic; 3 using System.Data.SqlClient; 4 using System.Data; 5 using System.Configuration; 6 using Terminator.Practice.DomainModel.Model; 7 8 namespace Terminator.Practice.DomainModel.Repository 9 { 10 public class BankAccountRepository : IBankAccountRepository 11 { 12 private readonly string _connectionString; 13 14 public BankAccountRepository() 15 { 16 _connectionString = ConfigurationManager.ConnectionStrings["BankAccountConnectionString"].ConnectionString; 17 } 18 19 public void Add(BankAccount bankAccount) 20 { 21 string insertSql = @"INSERT INTO BankAccounts(BankAccountID, Balance, CustomerRef) 22 VALUES(@BankAccountID, @Balance, @CustomerRef)"; 23 using (SqlConnection connection = new SqlConnection(_connectionString)) 24 { 25 SqlCommand command = connection.CreateCommand(); 26 command.CommandText = insertSql; 27 28 SetCommandParametersForInsertUpdateTo(bankAccount, command); 29 30 connection.Open(); 31 32 command.ExecuteNonQuery(); 33 } 34 35 UpdateTransactionsFor(bankAccount); 36 } 37 38 public void Save(BankAccount bankAccount) 39 { 40 string bankAccoutnUpdateSql = "UPDATE BankAccounts " + 41 "SET Balance = @Balance, CustomerRef= @CustomerRef " + 42 "WHERE BankAccountID = @BankAccountID;"; 43 44 using (SqlConnection connection = new SqlConnection(_connectionString)) 45 { 46 SqlCommand command = connection.CreateCommand(); 47 command.CommandText = bankAccoutnUpdateSql; 48 49 SetCommandParametersForInsertUpdateTo(bankAccount, command); 50 51 connection.Open(); 52 53 command.ExecuteNonQuery(); 54 } 55 56 UpdateTransactionsFor(bankAccount); 57 } 58 59 private static void SetCommandParametersForInsertUpdateTo(BankAccount bankAccount, SqlCommand command) 60 { 61 command.Parameters.Add(new SqlParameter("@BankAccountID", bankAccount.AccountNo)); 62 command.Parameters.Add(new SqlParameter("@Balance", bankAccount.Balance)); 63 command.Parameters.Add(new SqlParameter("@CustomerRef", bankAccount.CustomerRef)); 64 } 65 66 private void UpdateTransactionsFor(BankAccount bankAccount) 67 { 68 string deleteTransactionSQl = "DELETE Transactions WHERE BankAccountId = @BankAccountId;"; 69 70 using (SqlConnection connection = new SqlConnection(_connectionString)) 71 { 72 SqlCommand command = connection.CreateCommand(); 73 command.CommandText = deleteTransactionSQl; 74 command.Parameters.Add(new SqlParameter("@BankAccountID", bankAccount.AccountNo)); 75 connection.Open(); 76 command.ExecuteNonQuery(); 77 } 78 79 string insertTransactionSql = "INSERT INTO Transactions " + 80 "(BankAccountID, Deposit, Withdrawal, Reference, [Date]) VALUES " + 81 "(@BankAccountID, @Deposit, @Withdrawal, @Reference, @Date)"; 82 83 foreach (Transaction tran in bankAccount.GetTransactions()) 84 { 85 using (SqlConnection connection = new SqlConnection(_connectionString)) 86 { 87 SqlCommand command = connection.CreateCommand(); 88 command.CommandText = insertTransactionSql; 89 90 command.Parameters.Add(new SqlParameter("@BankAccountID", bankAccount.AccountNo)); 91 command.Parameters.Add(new SqlParameter("@Deposit", tran.Deposit)); 92 command.Parameters.Add(new SqlParameter("@Withdrawal", tran.Withdrawal)); 93 command.Parameters.Add(new SqlParameter("@Reference", tran.Reference)); 94 command.Parameters.Add(new SqlParameter("@Date", tran.Date)); 95 96 connection.Open(); 97 command.ExecuteNonQuery(); 98 } 99 } 100 } 101 102 public IEnumerable<BankAccount> FindAll() 103 { 104 IList<BankAccount> accounts; 105 106 string queryString = "SELECT * FROM dbo.Transactions INNER JOIN " + 107 "dbo.BankAccounts ON dbo.Transactions.BankAccountId = dbo.BankAccounts.BankAccountId " + 108 "ORDER BY dbo.BankAccounts.BankAccountId;"; 109 110 using (SqlConnection connection = new SqlConnection(_connectionString)) 111 { 112 SqlCommand command = connection.CreateCommand(); 113 command.CommandText = queryString; 114 115 connection.Open(); 116 117 using (SqlDataReader reader = command.ExecuteReader()) 118 { 119 accounts = CreateListOfAccountsFrom(reader); 120 } 121 } 122 return accounts; 123 } 124 125 private IList<BankAccount> CreateListOfAccountsFrom(IDataReader datareader) 126 { 127 IList<BankAccount> accounts = new List<BankAccount>(); 128 string id = ""; 129 IList<Transaction> transactions = new List<Transaction>(); 130 131 while (datareader.Read()) 132 { 133 if (id != datareader["BankAccountId"].ToString()) 134 { 135 id = datareader["BankAccountId"].ToString(); 136 transactions = new List<Transaction>(); 137 BankAccount bankAccount = new BankAccount(new Guid(id), Decimal.Parse(datareader["Balance"].ToString()), transactions, datareader["CustomerRef"].ToString()); 138 accounts.Add(bankAccount); 139 } 140 transactions.Add(CreateTransactionFrom(datareader)); 141 } 142 143 return accounts; 144 } 145 146 private Transaction CreateTransactionFrom(IDataRecord rawData) 147 { 148 return new Transaction(Decimal.Parse(rawData["Deposit"].ToString()), 149 Decimal.Parse(rawData["Withdrawal"].ToString()), 150 rawData["Reference"].ToString(), 151 DateTime.Parse(rawData["Date"].ToString())); 152 } 153 154 155 public BankAccount FindBy(Guid AccountId) 156 { 157 BankAccount account; 158 159 string queryString = "SELECT * FROM dbo.Transactions INNER JOIN " + 160 "dbo.BankAccounts ON dbo.Transactions.BankAccountId = dbo.BankAccounts.BankAccountId " + 161 "WHERE dbo.BankAccounts.BankAccountId = @BankAccountId;"; 162 163 using (SqlConnection connection = new SqlConnection(_connectionString)) 164 { 165 SqlCommand command = connection.CreateCommand(); 166 command.CommandText = queryString; 167 168 SqlParameter Idparam = new SqlParameter("@BankAccountId", AccountId); 169 command.Parameters.Add(Idparam); 170 171 connection.Open(); 172 173 using (SqlDataReader reader = command.ExecuteReader()) 174 { 175 account = CreateListOfAccountsFrom(reader)[0]; 176 } 177 } 178 return account; 179 } 180 } 181 }
由于Repository项目需要访问web.config,所以需要添加对System.Configuration的引用。
注:在后续中将介绍一些常见的对象关系映射器,能够节省编写ADO.NET基础设施代码的时间
现在可以为客户端添加一个服务层,让其与系统已一种简单的方式进行交互。
在AppService项目中添加一个新文件夹 ViewModel,并向其中添加两个类BankAccountView 和 TransactionView:
using System; using System.Collections.Generic; namespace Terminator.Practice.DomainModel.AppService.ViewModels { public class BankAccountView { public Guid AccountNo { get; set; } public string Balance { get; set; } public string CustomerRef { get; set; } public IList<TransactionView> Transactions { get; set; } } }
using System; namespace Terminator.Practice.DomainModel.AppService.ViewModels { public class TransactionView { public string Deposit { get; set; } public string Withdrawal { get; set; } public string Reference { get; set; } public DateTime Date { get; set; } } }
BankAccountView 和 TransactionView 提供了领域模型用于表示的展开视图,为了将领域实体转换成数据传输视图模型,需要映射器类(后续中将会介绍一种将这个过程自动化的方法)。
在AppService项目根目录创建一个新类ViewMapper:
using System.Collections.Generic; using Terminator.Practice.DomainModel.AppService.ViewModels; using Terminator.Practice.DomainModel.Model; namespace Terminator.Practice.DomainModel.AppService { public static class ViewMapper { public static TransactionView CreateTransactionViewFrom(Transaction tran) { return new TransactionView { Deposit = tran.Deposit.ToString("C"), Withdrawal = tran.Withdrawal.ToString("C"), Reference = tran.Reference, Date = tran.Date }; } public static BankAccountView CreateBankAccountViewFrom(BankAccount acc) { return new BankAccountView { AccountNo = acc.AccountNo, Balance = acc.Balance.ToString("C"), CustomerRef = acc.CustomerRef, Transactions = new List<TransactionView>() }; } } }
向AppService项目中再添加一个文件夹Messages,该文件夹将包含所有用来与服务层通信的Request-Reponse模式对象,因为所有的Reponse都共享一组相同的属性,所以可以创建一个基类ResponseBase:
namespace Terminator.Practice.DomainModel.AppService.Messages { public abstract class ResponseBase { public bool Success { get; set; } public string Message { get; set; } } }
Success属性表示被调用的方法是否成功运行,而Message属性包含该方法运行结果的详细信息。
在Messages文件夹添加几个类:BankAccountCreateRequest、BankAccountCreateResponse、DepositRequest、FindAllBankAccountResponse、FindBankAccountResponse、TransferRequest、TransferResponse、WithdrawalRequest
namespace Terminator.Practice.DomainModel.AppService.Messages { public class BankAccountCreateRequest { public string CustomerName { get; set; } } }
using System; namespace Terminator.Practice.DomainModel.AppService.Messages { public class BankAccountCreateResponse : ResponseBase { public Guid BankAccountId { get; set; } } }
using System; namespace Terminator.Practice.DomainModel.AppService.Messages { public class DepositRequest { public Guid AccountId { get; set; } public decimal Amount { get; set; } } }
using System.Collections.Generic; using Terminator.Practice.DomainModel.AppService.ViewModels; namespace Terminator.Practice.DomainModel.AppService.Messages { public class FindAllBankAccountResponse : ResponseBase { public IList<BankAccountView> BankAccountView { get; set; } } }
using Terminator.Practice.DomainModel.AppService.ViewModels; namespace Terminator.Practice.DomainModel.AppService.Messages { public class FindBankAccountResponse : ResponseBase { public BankAccountView BankAccount { get; set; } } }
using System; namespace Terminator.Practice.DomainModel.AppService.Messages { public class TransferRequest { public Guid AccountIdTo { get; set; } public Guid AccountIdFrom { get; set; } public decimal Amount { get; set; } } }
namespace Terminator.Practice.DomainModel.AppService.Messages { public class TransferResponse : ResponseBase { } }
using System; namespace Terminator.Practice.DomainModel.AppService.Messages { public class WithdrawalRequest { public Guid AccountId { get; set; } public decimal Amount { get; set; } } }
消息传送对象就位之后,就可以添加服务类来协调对领域实体(服务和资源库)的方法调用,在AppService项目的根目录添加一个新类ApplicationBankAccountService:

1 using System; 2 using System.Collections.Generic; 3 using Terminator.Practice.DomainModel.AppService.Messages; 4 using Terminator.Practice.DomainModel.AppService.ViewModels; 5 using Terminator.Practice.DomainModel.Model; 6 using Terminator.Practice.DomainModel.Repository; 7 8 namespace Terminator.Practice.DomainModel.AppService 9 { 10 public class ApplicationBankAccountService 11 { 12 private readonly BankAccountService _bankAccountService; 13 private readonly IBankAccountRepository _bankRepository; 14 15 public ApplicationBankAccountService() : 16 this(new BankAccountRepository(), new BankAccountService(new BankAccountRepository())) 17 { } 18 19 public ApplicationBankAccountService(IBankAccountRepository bankRepository, BankAccountService bankAccountService) 20 { 21 _bankRepository = bankRepository; 22 _bankAccountService = bankAccountService; 23 } 24 25 public ApplicationBankAccountService(BankAccountService bankAccountService, IBankAccountRepository bankRepository) 26 { 27 _bankAccountService = bankAccountService; 28 _bankRepository = bankRepository; 29 } 30 31 public BankAccountCreateResponse CreateBankAccount(BankAccountCreateRequest bankAccountCreateRequest) 32 { 33 BankAccountCreateResponse bankAccountCreateResponse = new BankAccountCreateResponse(); 34 BankAccount bankAccount = new BankAccount(); 35 36 bankAccount.CustomerRef = bankAccountCreateRequest.CustomerName; 37 _bankRepository.Add(bankAccount); 38 39 bankAccountCreateResponse.BankAccountId = bankAccount.AccountNo; 40 bankAccountCreateResponse.Success = true; 41 42 return bankAccountCreateResponse; 43 } 44 45 public void Deposit(DepositRequest depositRequest) 46 { 47 BankAccount bankAccount = _bankRepository.FindBy(depositRequest.AccountId); 48 49 bankAccount.Deposit(depositRequest.Amount, ""); 50 51 _bankRepository.Save(bankAccount); 52 } 53 54 public void Withdrawal(WithdrawalRequest withdrawalRequest) 55 { 56 BankAccount bankAccount = _bankRepository.FindBy(withdrawalRequest.AccountId); 57 58 bankAccount.Withdraw(withdrawalRequest.Amount, ""); 59 60 _bankRepository.Save(bankAccount); 61 } 62 63 public TransferResponse Transfer(TransferRequest request) 64 { 65 TransferResponse response = new TransferResponse(); 66 67 try 68 { 69 _bankAccountService.Transfer(request.AccountIdTo, request.AccountIdFrom, request.Amount); 70 response.Success = true; 71 } 72 catch (InsufficientFundsException) 73 { 74 response.Message = "There is not enough funds in account on: " + request.AccountIdFrom.ToString(); 75 response.Success = false; 76 } 77 return response; 78 } 79 80 public FindAllBankAccountResponse GetAllBankAccounts() 81 { 82 FindAllBankAccountResponse FindAllBankAccountResponse = new FindAllBankAccountResponse(); 83 IList<BankAccountView> bankAccountViews = new List<BankAccountView>(); 84 FindAllBankAccountResponse.BankAccountView = bankAccountViews; 85 86 foreach (BankAccount acc in _bankRepository.FindAll()) 87 { 88 bankAccountViews.Add(ViewMapper.CreateBankAccountViewFrom(acc)); 89 } 90 91 return FindAllBankAccountResponse; 92 } 93 94 public FindBankAccountResponse GetBankAccountBy(Guid Id) 95 { 96 FindBankAccountResponse bankAccountResponse = new FindBankAccountResponse(); 97 BankAccount acc = _bankRepository.FindBy(Id); 98 BankAccountView bankAccountView = ViewMapper.CreateBankAccountViewFrom(acc); 99 100 foreach (Transaction tran in acc.GetTransactions()) 101 { 102 bankAccountView.Transactions.Add(ViewMapper.CreateTransactionViewFrom(tran)); 103 } 104 105 bankAccountResponse.BankAccount = bankAccountView; 106 107 return bankAccountResponse; 108 } 109 } 110 }
ApplicationBankAccountService类协调应用程序活动并将所有的业务任务委托给领域模型,该层并不包含任何业务逻辑,有助于防止任何与业务无关的代码污染领域模型项目,该层还将领域实体转换成数据传输对象,从而保护领域的内部操作,并为一起工作的表示层提供了一个易于使用的API。
为了简单起见,这里选择了简陋的依赖注入方式并对默认的构造器硬编码,以便使用已经编码的资源库领域服务实现,后续中将介绍IoC和IoC容器来提供类的依赖关系。
最后就是创建用户界面,从而能够创建帐号并执行交易。

1 <%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="Terminator.Practice.DomainModel.UI.Web._Default" %> 2 3 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> 4 5 <html xmlns="http://www.w3.org/1999/xhtml" > 6 <head runat="server"> 7 <title></title> 8 </head> 9 <body> 10 <form id="form1" runat="server"> 11 <div> 12 13 <fieldset> 14 <legend>Create New Account</legend> 15 <p> 16 Customer Ref: 17 <asp:TextBox ID="txtCustomerRef" runat="server"></asp:TextBox> 18 19 <asp:Button ID="btCreateAccount" runat="server" Text="Create Account" 20 onclick="btCreateAccount_Click" /> 21 </p> 22 </fieldset> 23 24 <fieldset> 25 <legend>Account Detail</legend> 26 <p> 27 <asp:DropDownList AutoPostBack="true" ID="ddlBankAccounts" runat="server" 28 onselectedindexchanged="ddlBankAccounts_SelectedIndexChanged"></asp:DropDownList> 29 </p> 30 <p> 31 Account No: 32 <asp:Label ID="lblAccountNo" runat="server" /> 33 </p> 34 <p> 35 Customer Ref: 36 <asp:Label ID="lblCustomerRef" runat="server" /> 37 </p> 38 <p> 39 Balance: 40 <asp:Label ID="lblBalance" runat="server" /> 41 </p> 42 <p> 43 Amount 44 ¥<asp:TextBox ID="txtAmount" runat="server" Width="60px"></asp:TextBox> 45 46 <asp:Button ID="btnWithdrawal" runat="server" Text="Withdrawal" 47 onclick="btnWithdrawal_Click" /> 48 <asp:Button ID="btnDeposit" runat="server" Text="Deposit" 49 onclick="btnDeposit_Click" /> 50 </p> 51 <p> 52 Transfer 53 ¥<asp:TextBox ID="txtAmountToTransfer" runat="server" Width="60px"></asp:TextBox> 54 55 to 56 <asp:DropDownList AutoPostBack="true" ID="ddlBankAccountsToTransferTo" runat="server"></asp:DropDownList> 57 <asp:Button ID="btnTransfer" runat="server" Text="Commit" 58 onclick="btnTransfer_Click" /> 59 </p> 60 <p> 61 Transactions</p> 62 <asp:Repeater ID="rptTransactions" runat="server"> 63 <HeaderTemplate> 64 <table> 65 <tr> 66 <td>deposit</td> 67 <td>withdrawal</td> 68 <td>reference</td> 69 </tr> 70 </HeaderTemplate> 71 <ItemTemplate> 72 <tr> 73 <td><%# Eval("Deposit") %></td> 74 <td><%# Eval("Withdrawal") %></td> 75 <td><%# Eval("Reference") %></td> 76 <td><%# Eval("Date") %></td> 77 </tr> 78 </ItemTemplate> 79 <FooterTemplate> 80 </table> 81 </FooterTemplate> 82 </asp:Repeater> 83 </fieldset> 84 </div> 85 </form> 86 </body> 87 </html>

1 using System; 2 using System.Web.UI.WebControls; 3 using Terminator.Practice.DomainModel.AppService; 4 using Terminator.Practice.DomainModel.AppService.Messages; 5 using Terminator.Practice.DomainModel.AppService.ViewModels; 6 7 namespace Terminator.Practice.DomainModel.UI.Web 8 { 9 public partial class _Default : System.Web.UI.Page 10 { 11 protected void Page_Load(object sender, EventArgs e) 12 { 13 if (!Page.IsPostBack) 14 ShowAllAccounts(); 15 } 16 17 private void ShowAllAccounts() 18 { 19 ddlBankAccounts.Items.Clear(); 20 21 FindAllBankAccountResponse response = new ApplicationBankAccountService().GetAllBankAccounts(); 22 ddlBankAccounts.Items.Add(new ListItem("Select An Account", "")); 23 24 foreach (BankAccountView accView in response.BankAccountView) 25 { 26 ddlBankAccounts.Items.Add(new ListItem(accView.CustomerRef, accView.AccountNo.ToString())); 27 } 28 } 29 30 protected void btCreateAccount_Click(object sender, EventArgs e) 31 { 32 BankAccountCreateRequest createAccountRequest = new BankAccountCreateRequest(); 33 createAccountRequest.CustomerName = txtCustomerRef.Text; 34 ApplicationBankAccountService service = new ApplicationBankAccountService(); 35 36 service.CreateBankAccount(createAccountRequest); 37 38 ShowAllAccounts(); 39 } 40 41 protected void ddlBankAccounts_SelectedIndexChanged(object sender, EventArgs e) 42 { 43 DisplaySelectedAccount(); 44 } 45 46 private void DisplaySelectedAccount() 47 { 48 if (ddlBankAccounts.SelectedValue != "") 49 { 50 ApplicationBankAccountService service = new ApplicationBankAccountService(); 51 FindBankAccountResponse response = service.GetBankAccountBy(new Guid(ddlBankAccounts.SelectedValue)); 52 BankAccountView accView = response.BankAccount; 53 54 lblAccountNo.Text = accView.AccountNo.ToString(); 55 lblBalance.Text = accView.Balance; 56 lblCustomerRef.Text = accView.CustomerRef; 57 58 rptTransactions.DataSource = accView.Transactions; 59 rptTransactions.DataBind(); 60 61 FindAllBankAccountResponse allAccountsResponse = service.GetAllBankAccounts(); 62 63 ddlBankAccountsToTransferTo.Items.Clear(); 64 65 foreach (BankAccountView acc in allAccountsResponse.BankAccountView) 66 { 67 if (acc.AccountNo.ToString() != ddlBankAccounts.SelectedValue) 68 ddlBankAccountsToTransferTo.Items.Add(new ListItem(acc.CustomerRef, acc.AccountNo.ToString())); 69 } 70 } 71 } 72 73 protected void btnWithdrawal_Click(object sender, EventArgs e) 74 { 75 ApplicationBankAccountService service = new ApplicationBankAccountService(); 76 WithdrawalRequest request = new WithdrawalRequest(); 77 Guid AccId = new Guid(ddlBankAccounts.SelectedValue); 78 request.AccountId = AccId; 79 request.Amount = Decimal.Parse(txtAmount.Text); 80 81 service.Withdrawal(request); 82 DisplaySelectedAccount(); 83 } 84 85 protected void btnDeposit_Click(object sender, EventArgs e) 86 { 87 ApplicationBankAccountService service = new ApplicationBankAccountService(); 88 DepositRequest request = new DepositRequest(); 89 Guid AccId = new Guid(ddlBankAccounts.SelectedValue); 90 request.AccountId = AccId; 91 request.Amount = Decimal.Parse(txtAmount.Text); 92 93 service.Deposit(request); 94 DisplaySelectedAccount(); 95 } 96 97 protected void btnTransfer_Click(object sender, EventArgs e) 98 { 99 ApplicationBankAccountService service = new ApplicationBankAccountService(); 100 TransferRequest request = new TransferRequest(); 101 request.AccountIdFrom = new Guid(ddlBankAccounts.SelectedValue); 102 request.AccountIdTo = new Guid(ddlBankAccountsToTransferTo.SelectedValue); 103 request.Amount = Decimal.Parse(txtAmountToTransfer.Text); 104 105 service.Transfer(request); 106 DisplaySelectedAccount(); 107 108 } 109 } 110 }

1 //------------------------------------------------------------------------------ 2 // <自动生成> 3 // 此代码由工具生成。 4 // 5 // 对此文件的更改可能会导致不正确的行为,并且如果 6 // 重新生成代码,这些更改将会丢失。 7 // </自动生成> 8 //------------------------------------------------------------------------------ 9 10 namespace Terminator.Practice.DomainModel.UI.Web { 11 12 13 public partial class _Default { 14 15 /// <summary> 16 /// form1 控件。 17 /// </summary> 18 /// <remarks> 19 /// 自动生成的字段。 20 /// 若要进行修改,请将字段声明从设计器文件移到代码隐藏文件。 21 /// </remarks> 22 protected global::System.Web.UI.HtmlControls.HtmlForm form1; 23 24 /// <summary> 25 /// txtCustomerRef 控件。 26 /// </summary> 27 /// <remarks> 28 /// 自动生成的字段。 29 /// 若要进行修改,请将字段声明从设计器文件移到代码隐藏文件。 30 /// </remarks> 31 protected global::System.Web.UI.WebControls.TextBox txtCustomerRef; 32 33 /// <summary> 34 /// btCreateAccount 控件。 35 /// </summary> 36 /// <remarks> 37 /// 自动生成的字段。 38 /// 若要进行修改,请将字段声明从设计器文件移到代码隐藏文件。 39 /// </remarks> 40 protected global::System.Web.UI.WebControls.Button btCreateAccount; 41 42 /// <summary> 43 /// ddlBankAccounts 控件。 44 /// </summary> 45 /// <remarks> 46 /// 自动生成的字段。 47 /// 若要进行修改,请将字段声明从设计器文件移到代码隐藏文件。 48 /// </remarks> 49 protected global::System.Web.UI.WebControls.DropDownList ddlBankAccounts; 50 51 /// <summary> 52 /// lblAccountNo 控件。 53 /// </summary> 54 /// <remarks> 55 /// 自动生成的字段。 56 /// 若要进行修改,请将字段声明从设计器文件移到代码隐藏文件。 57 /// </remarks> 58 protected global::System.Web.UI.WebControls.Label lblAccountNo; 59 60 /// <summary> 61 /// lblCustomerRef 控件。 62 /// </summary> 63 /// <remarks> 64 /// 自动生成的字段。 65 /// 若要进行修改,请将字段声明从设计器文件移到代码隐藏文件。 66 /// </remarks> 67 protected global::System.Web.UI.WebControls.Label lblCustomerRef; 68 69 /// <summary> 70 /// lblBalance 控件。 71 /// </summary> 72 /// <remarks> 73 /// 自动生成的字段。 74 /// 若要进行修改,请将字段声明从设计器文件移到代码隐藏文件。 75 /// </remarks> 76 protected global::System.Web.UI.WebControls.Label lblBalance; 77 78 /// <summary> 79 /// txtAmount 控件。 80 /// </summary> 81 /// <remarks> 82 /// 自动生成的字段。 83 /// 若要进行修改,请将字段声明从设计器文件移到代码隐藏文件。 84 /// </remarks> 85 protected global::System.Web.UI.WebControls.TextBox txtAmount; 86 87 /// <summary> 88 /// btnWithdrawal 控件。 89 /// </summary> 90 /// <remarks> 91 /// 自动生成的字段。 92 /// 若要进行修改,请将字段声明从设计器文件移到代码隐藏文件。 93 /// </remarks> 94 protected global::System.Web.UI.WebControls.Button btnWithdrawal; 95 96 /// <summary> 97 /// btnDeposit 控件。 98 /// </summary> 99 /// <remarks> 100 /// 自动生成的字段。 101 /// 若要进行修改,请将字段声明从设计器文件移到代码隐藏文件。 102 /// </remarks> 103 protected global::System.Web.UI.WebControls.Button btnDeposit; 104 105 /// <summary> 106 /// txtAmountToTransfer 控件。 107 /// </summary> 108 /// <remarks> 109 /// 自动生成的字段。 110 /// 若要进行修改,请将字段声明从设计器文件移到代码隐藏文件。 111 /// </remarks> 112 protected global::System.Web.UI.WebControls.TextBox txtAmountToTransfer; 113 114 /// <summary> 115 /// ddlBankAccountsToTransferTo 控件。 116 /// </summary> 117 /// <remarks> 118 /// 自动生成的字段。 119 /// 若要进行修改,请将字段声明从设计器文件移到代码隐藏文件。 120 /// </remarks> 121 protected global::System.Web.UI.WebControls.DropDownList ddlBankAccountsToTransferTo; 122 123 /// <summary> 124 /// btnTransfer 控件。 125 /// </summary> 126 /// <remarks> 127 /// 自动生成的字段。 128 /// 若要进行修改,请将字段声明从设计器文件移到代码隐藏文件。 129 /// </remarks> 130 protected global::System.Web.UI.WebControls.Button btnTransfer; 131 132 /// <summary> 133 /// rptTransactions 控件。 134 /// </summary> 135 /// <remarks> 136 /// 自动生成的字段。 137 /// 若要进行修改,请将字段声明从设计器文件移到代码隐藏文件。 138 /// </remarks> 139 protected global::System.Web.UI.WebControls.Repeater rptTransactions; 140 } 141 }
web.config数据库连接字符串配置
<connectionStrings> <add name="BankAccountConnectionString" connectionString="Data Source=.\SQLEXPRESS;AttachDbFilename=|DataDirectory|\BankAccount.mdf;Integrated Security=True;User Instance=True" providerName="System.Data.SqlClient"/> </connectionStrings>
解决方案结构图
程序运行图:
最后附上demo源码