前言:
关于不同框架实现同一个TaskVision:
前面DebugLZQ先是用WPF(没有使用MVVM,因为前面使用MVVM实现过过点餐系统),因而这个关键点就放在了WPF的Binding上面;
然后用普通的Winform,没有加入任何模式,实现了相同的功能。因此这个重点放在了DataGridView的总结,以及WinForm自定义控件实现类似WPF控件上面。
本篇博文使用标准的三层架构,重新实现这个TaskVision。因而重点放在三层架构方面、为了体现三层的各层间低耦合的特点,博文下半部分会将DAL换成WebService,并实现多语言。数据库依然是原来的SQL Server 2008.
标准的是这样的:
在软件体系设计中,分层式结构式最常见也是最重要的一种结构。MS推荐的分层结构一般分为三层,从上到下依次为UI、BLL、DAL。
理解软件分层的概念有助于理解各种大量应用的模式结构,如MVC、MVVM等。以及GOF其他的一些等等。理解了三层,其他的理解起来会方便很多,因为其中贯通的都是:表现层的解耦。个人理解:模式间的区别是:根据具体的技术框架特点,决定表现层解耦方法的不同,由不断的最佳实践,总结出了各种不同的模式。即前面DebugLZQ也说的:表现层的持续解耦,带来的模式的转变!
传统的三层式这样的:
解释一下:UI层调用BLL层,传递的参数为UI层控件的属性值;BLL层调用DAL层,并加入相应的逻辑;DAL层则负责对数据库访问的封装(DAL层直接封装数据库访问,箭头所示)。
我理解的规则:
1.UI层不包括任何BL;
2.设计从BLL出发,而不是从UI出发,BLL实现所有的BL。
3.DAL应该做到一定程度上与系统无关。即这一层可抽离。
4.不可跨层调用;
满足以上规则,就可以认为这个项目是三层了。
(维基百科给出的)优点:
实际是这样的
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Windows.Forms; using Models; namespace TaskVision_V2 { public partial class Login : Form { public Login() { InitializeComponent(); } private void button1_Click(object sender, EventArgs e) { Models.UserLoginModel userLoginModel = new UserLoginModel() { UserName=txtUserName.Text.Trim(), Password=txtPassword.Text, RememberPassword=checkBox1.Checked}; if (BLL.UserService.Login(userLoginModel)) { //FormMain formMain = new FormMain(); //formMain.Show(); Global.userName = txtUserName.Text; this.DialogResult = DialogResult.OK; } else { MessageBox.Show("用户名或密码错误!"); } } private void button2_Click(object sender, EventArgs e) { this.Close(); } } }
Models:UserLoginModel.cs(叫UserModel更贴切,Model对应数据库表+判断逻辑控件value)
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace Models { public class UserLoginModel { public string Id { get; set; } public string UserName { get; set; } public string Password { get; set; } public bool RememberPassword { get; set; } } }
BLL:UserService.cs
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Data.SqlClient; using System.Data; namespace BLL { public class UserService { /// <summary> /// User Login /// </summary> private static string loginSQLText = "select * from tb_UserInfo where UserName=@UserName and Password=@Password"; public static bool Login(Models.UserLoginModel userLoginModel) { if (userLoginModel.RememberPassword) { //todo: } SqlParameter[] parameters = new SqlParameter[] { new SqlParameter("@UserName",userLoginModel.UserName),new SqlParameter("@Password",userLoginModel.Password)}; return DAL.DataAccess.ExecuteReader(loginSQLText, parameters); } /// <summary> /// Get All User /// </summary> private static string getUserSQLText = "select distinct UserName from tb_UserInfo"; public static DataTable GetUser() { return DAL.DataAccess.GetDataTable(getUserSQLText); } } }
DAL:DataAccess.cs
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Data; using System.Data.SqlClient; namespace DAL { public static class DataAccess { public static DataTable GetDataTable(string sqlText, params SqlParameter[] parameters) { return AdoDotNetClassLibrary.SQLHelper.GetDataTable(sqlText, parameters); } public static int ExecuteNonQuery(string sqlText,params SqlParameter[] parameters) { return AdoDotNetClassLibrary.SQLHelper.ExecuteNonQuery(sqlText,parameters); } public static bool ExecuteReader(string sqlText, SqlParameter[] parameters) { return AdoDotNetClassLibrary.SQLHelper.ExecuteReader(sqlText, parameters); } } }
AdoDotNetClassLibrary:SQLHelper.cs
using System.Data; using System.Data.SqlClient; namespace AdoDotNetClassLibrary { public static class SQLHelper { public const string connectionString = @"server=LocalHost;database=TaskVision;Trusted_Connection=SSPI"; public static DataTable GetDataTable(string sqlText,params SqlParameter[] parameters) { using (SqlConnection conn = new SqlConnection(connectionString)) { SqlDataAdapter sda = new SqlDataAdapter(sqlText, conn); foreach (SqlParameter parameter in parameters) { sda.SelectCommand.Parameters.Add(parameter); } DataTable dt = new DataTable(); sda.Fill(dt); return dt; } } public static int ExecuteNonQuery(string sqlText,params SqlParameter[] parameters) { using (SqlConnection conn = new SqlConnection(connectionString)) { SqlCommand cmd = new SqlCommand(sqlText, conn); foreach (SqlParameter parameter in parameters) { cmd.Parameters.Add(parameter); } conn.Open(); int temp = cmd.ExecuteNonQuery(); return temp; } } public static bool ExecuteReader(string sqlText, params SqlParameter[] parameters) { using (SqlConnection conn = new SqlConnection(connectionString)) { conn.Open(); SqlCommand cmd = new SqlCommand(sqlText, conn); foreach (SqlParameter parameter in parameters) { cmd.Parameters.Add(parameter); } using (SqlDataReader reader = cmd.ExecuteReader()) { if (reader.Read()) return true; return false; } } } } }
数据由Webservice提供
注意点:
由于webservice序列化问题,SqlParameter不能直接传递,因此改用strng代替下;
http://forums.asp.net/t/1335298.aspx!需要为DataTable设置名称,dt.TableName="myTable";
需要将DAL引用webservice生成的config文件拷贝到UI工程中(注意:不是webservice项目的config)。否则会报告无法找到EndPoint运行时错误。
由此只要:
添加:AdoDotNetWebService--service1.asmx
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Services; using System.Data; using System.Data.SqlClient; using System.Collections; namespace AdoDotNetWebService { /// <summary> /// Summary description for Service1 /// </summary> [WebService(Namespace = "http://tempuri.org/")] [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)] [System.ComponentModel.ToolboxItem(false)] // To allow this Web Service to be called from script, using ASP.NET AJAX, uncomment the following line. // [System.Web.Script.Services.ScriptService] public class Service1 : System.Web.Services.WebService { [WebMethod] public string HelloWorld() { return "Hello World"; } public const string connectionString = @"server=LocalHost;database=TaskVision;Trusted_Connection=SSPI"; [WebMethod] public DataTable GetDataTable(string sqlText, params string[] parameters) { using (SqlConnection conn = new SqlConnection(connectionString)) { SqlDataAdapter sda = new SqlDataAdapter(sqlText, conn); for(int i=0;i<parameters.Length;i=i+2) { sda.SelectCommand.Parameters.Add(new SqlParameter(parameters[i],parameters[i+1])); } DataTable dt = new DataTable(); dt.TableName = "MyTable";//OMG! sda.Fill(dt); return dt; } } [WebMethod] public int ExecuteNonQuery(string sqlText, params string[] parameters) { using (SqlConnection conn = new SqlConnection(connectionString)) { SqlCommand cmd = new SqlCommand(sqlText, conn); for (int i = 0; i < parameters.Length; i = i + 2) { cmd.Parameters.Add(new SqlParameter(parameters[i],parameters[i+1])); } conn.Open(); int temp = cmd.ExecuteNonQuery(); return temp; } } [WebMethod] public bool ExecuteReader(string sqlText, params string[] parameters) { using (SqlConnection conn = new SqlConnection(connectionString)) { conn.Open(); SqlCommand cmd = new SqlCommand(sqlText, conn); for (int i = 0; i < parameters.Length; i = i + 2) { cmd.Parameters.Add(new SqlParameter(parameters[i], parameters[i + 1])); } using (SqlDataReader reader = cmd.ExecuteReader()) { if (reader.Read()) return true; return false; } } } } }
修改DAL:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Data; using System.Data.SqlClient; using System.Collections; namespace DAL { public static class DataAccess { //webserviceclient public static ServiceReference1.Service1SoapClient client = new ServiceReference1.Service1SoapClient(); public static DataTable GetDataTable(string sqlText, params SqlParameter[] parameters) { //return AdoDotNetClassLibrary.SQLHelper.GetDataTable(sqlText, parameters); ArrayList ps = new ArrayList(); for (int i = 0; i < parameters.Length; i++) { ps.Add(parameters[i].ParameterName); ps.Add(parameters[i].Value); } string[] ps1 = (string[])ps.ToArray(typeof(string)); return client.GetDataTable(sqlText, ps1); } public static int ExecuteNonQuery(string sqlText, params SqlParameter[] parameters) { //return AdoDotNetClassLibrary.SQLHelper.ExecuteNonQuery(sqlText,parameters); ArrayList ps = new ArrayList(); for (int i = 0; i < parameters.Length; i++) { ps.Add(parameters[i].ParameterName); ps.Add(parameters[i].Value); } string[] ps1 = (string[])ps.ToArray(typeof(string)); return client.ExecuteNonQuery(sqlText, ps1); } public static bool ExecuteReader(string sqlText, params SqlParameter[] parameters) { //return AdoDotNetClassLibrary.SQLHelper.ExecuteReader(sqlText, parameters); ArrayList ps = new ArrayList(); for (int i = 0; i < parameters.Length; i++) { ps.Add(parameters[i].ParameterName); ps.Add(parameters[i].Value); } string[] ps1 = (string[])ps.ToArray(typeof(string)); return client.ExecuteReader(sqlText, ps1); } } }
其他层保持不变!
实现多语言
非常简单。步骤如下:
1.设置Form的Localizable属性为True。
2.为每个Form添加对应的资源文件,命名有要求:以Login.cs窗体为例,资源名为Login.en.resx/Login.zh-CHS.resx(需要查阅具体语言的代码)。添加完成后VS自动将该资源文件添加到对应的窗体下。
3.编辑添加的资源文件,注意:不同String类型资源文件的Name在不同的resx中必须相同。目的是为了保证编码方便。
4.在Form_Load中根据CurrentUICulture为 Form的控件Text赋值(是用的是resx中string的Name,赋的是Value)
以Login为例。
1.设置Localizable属性为True。
2.添加资源文件:
添加代码:
private void Login_Load(object sender, EventArgs e) { Thread.CurrentThread.CurrentUICulture = new System.Globalization.CultureInfo("en"); UpDataMainFormUILanguage(); } private void UpDataMainFormUILanguage() { ResourceManager rm = new ResourceManager(typeof(Login)); UpdateLoginUILanguage(rm); } private void UpdateLoginUILanguage(ResourceManager rm) { this.Text = rm.GetString("Login"); lblUserName.Text = rm.GetString("UserName"); lblPassword.Text = rm.GetString("Password"); checkBox1.Text = rm.GetString("RememberPassword"); btnLogin.Text = rm.GetString("Login"); btnCancel.Text = rm.GetString("Cancel"); }
效果如下:
小结:
博文重点在于说明三层架构:UI层构建Model,传递给BLL层,BLL层利用UI层传过来的Model的属性作为参数调用DAL层,DAL层是对具体数据库访问方式的封装,真正的数据库访问则由具体的类库、服务等提供。关于三层架构也许很多人的理解都可能不够标准,我是这么认为的。附加介绍了WebService作为数据库提供程序的方法,需要注意的地方。以及实现多语言的一种参考方法。
感触:
Baidu不靠谱!多问Google、多用英文问问题!