WebService又一个不爽的地方
昨天在做项目时,发现了WebService又一个不人性化的地方,记录于此,希望能帮到遇到类似问题的同学们。
很多大型b/s项目,通常会分成几层,为了重现问题,这里我简化为三层:(以下代码仅出于演示,也许并无太大的实际用途)
1、Model层
放置一些业务需要的实体类(通常这些类要求是可序列化的,以方便后面提到的“服务层"中能被序列化后传递),这里为了演示,弄了三个类:
1.1 Person类
using System; namespace Model { [Serializable] public class Person { public Person() { } private int _Salary = 1280;//上海最低工资 /// <summary> /// 收入 /// </summary> public int Salary { get { return _Salary; } set { _Salary = value; } } private string _Name = "No Name"; /// <summary> /// 姓名 /// </summary> public string Name { get { return _Name; } set { _Name = value; } } private DateTime _Birthday = new DateTime(1900, 1, 1); /// <summary> /// 生日 /// </summary> public DateTime Birthday { get { return _Birthday; } set { _Birthday = value; } } public override string ToString() { return string.Format("Name={0},Birthday={1},Salary={2}", this.Name, this.Birthday, this.Salary); } } }
1.2、PersonQueryParameters类
对于Person类的集合(比如DataTable,List<Person>),通常会有搜索需求,而且搜索的字段要求能动态变化,为了方便起见,把一些常用的搜索参数封装在这个类里
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace Model { [Serializable] public class PersonQueryParameters { public PersonQueryParameters() { } private int _Salary_Min = Consts.SalaryMin; /// <summary> /// 工资范围(最小值) /// </summary> public int Salary_Min { get { return _Salary_Min; } set { _Salary_Min = value; } } private int _Salary_Max = Consts.SalaryMax; /// <summary> /// 工资范围(最大值) /// </summary> public int Salary_Max { get { return _Salary_Max; } set { _Salary_Max = value; } } private DateTime _Birthday_Min = Consts.BirthdayMin; /// <summary> /// 生日范围(最小值) /// </summary> public DateTime Birthday_Min { get { return _Birthday_Min; } set { _Birthday_Min = value; } } private DateTime _Birthday_Max = Consts.BirthdayMax; /// <summary> /// 生日范围(最大值) /// </summary> public DateTime Birthday_Max { get { return _Birthday_Max; } set { _Birthday_Max = value; } } public override string ToString() { return string.Format("Salary_Min={0},Salary_Max={1},Birthday_Min={2},Birthday_Max={3}", this.Salary_Min, this.Salary_Max, this.Birthday_Min, this.Birthday_Max); } } }
注:其中用了一个类Consts,下面马上会提到
1.3、Consts类
这个类只是为了方便,定义一些常量而已
using System; namespace Model { [Serializable] public static class Consts { public static readonly DateTime BirthdayMin = new DateTime(1900, 1, 1); public static readonly DateTime BirthdayMax = new DateTime(2012, 12, 24); public static readonly int SalaryMin = 1280; public static readonly int SalaryMax = 1000000; } }
2、WebService层
其它子系统需要的业务功能,以服务的形式在这一层对外公开。我们创建一个Query.asmx来提供“查询Person”的服务。(注:当然,这一层必须引用Model层)
using System; using System.Collections.Generic; using System.Linq; using System.Web.Services; using Model; namespace WebService { /// <summary> /// Summary description for Query /// </summary> [WebService(Namespace = "http://yjmyzz.cnblogs.com/")] [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)] [System.ComponentModel.ToolboxItem(false)] public class Query : System.Web.Services.WebService { [WebMethod(Description="查询Person信息")] public List<Person> QueryPerson(PersonQueryParameters pars) { //这里出于演示目的,直接构造一个List<T>做为搜索源 List<Person> lstSrc = new List<Person>(){ new Person(){ Birthday=DateTime.Parse("1980-3-5"), Name="张三", Salary=5000}, new Person(){ Birthday=DateTime.Parse("1985-6-1"), Name="李四", Salary=10000}, new Person(){ Birthday=DateTime.Parse("1975-12-8"), Name="王五", Salary=8000}, }; IEnumerable<Person> result = lstSrc.AsEnumerable(); #region 处理搜索 if (pars.Birthday_Min != Consts.BirthdayMin) { result = result.Where(c => c.Birthday >= pars.Birthday_Min); } if (pars.Birthday_Max != Consts.BirthdayMax) { result = result.Where(c => c.Birthday <= pars.Birthday_Max); } if (pars.Salary_Min != Consts.SalaryMin) { result = result.Where(c => c.Salary >= pars.Salary_Min); } if (pars.Salary_Max != Consts.SalaryMax) { result = result.Where(c => c.Salary <= pars.Salary_Max); } #endregion return result.ToList(); } } }
3、Website (UI) 层
这一层只负责UI呈现,业务功能通过请求WebService层实现。添加对WebService层Query.asmx的服务引用后,我们创建一个Default.aspx页来测试一下QueryPerson服务
using System; using Website_ASMX_No_Ref_Model.WSLayer; using Website_ASMX_No_Ref_Model.Properties; namespace Website_ASMX_No_Ref_Model { public partial class Default : System.Web.UI.Page { protected void Page_Load(object sender, EventArgs e) { using (Query service = new Query()) { //动态设置webService的asmx路径 service.Url = Settings.Default.Website_ASMX_No_Ref_Model_WSLayer_Query; PersonQueryParameters pars = new PersonQueryParameters(); //查询工资在3-5k之间的人 pars.Salary_Min = 3000; pars.Salary_Max = 5000; //同时出生时间在1979-1-1以后的人 pars.Birthday_Min = DateTime.Parse("1979-1-1"); Person[] result = service.QueryPerson(pars); } } } }
啰嗦了一堆,总算把背景交待完了,现在问题才刚开始,从UI层的代码来看,貌似一切都很完美,Model层的各种实体类定义,在UI层引用asmx服务后,被自动带到UI层了。如果我们在上面代码的"PersonQueryParameters"上右击,转到定义,会看到vs.net自动为我们生成的代码:
/// <remarks/> [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.225")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] [System.Xml.Serialization.XmlTypeAttribute(Namespace="http://yjmyzz.cnblogs.com/")] public partial class PersonQueryParameters { private int salary_MinField; private int salary_MaxField; private System.DateTime birthday_MinField; private System.DateTime birthday_MaxField; /// <remarks/> public int Salary_Min { get { return this.salary_MinField; } set { this.salary_MinField = value; } } /// <remarks/> public int Salary_Max { get { return this.salary_MaxField; } set { this.salary_MaxField = value; } } /// <remarks/> public System.DateTime Birthday_Min { get { return this.birthday_MinField; } set { this.birthday_MinField = value; } } /// <remarks/> public System.DateTime Birthday_Max { get { return this.birthday_MaxField; } set { this.birthday_MaxField = value; } } }
好吧,如果你已经迫不及待的想按F5运行一下,会发现UI层的代码始终是搜索不到任何Person记录的。问题在于:Website中的PersonQueryParameters类,已经不是Model层中的PersonQueryParameters了!(哪怕这哥俩"类名称"以及"类属性成员的名字"都完全相同)观察Model层中的PersonQueryParameters定义与Website中vs.net自动为我们生成的PersonQueryParameters定义,会发现:原来Model层中私有成员赋初始值的代码,比如
private int _Salary_Min = Consts.SalaryMin;
已经变成了
private int salary_MinField;
换句话说,属性的初始赋值丢失了!(或者说被改变了) 因此WebService中搜索部分的 if 判断语句
if (pars.Birthday_Max != Consts.BirthdayMax)
应该变成
if (pars.Birthday_Max!=default(DateTime))
这是一个比较隐藏的问题,编译期不会出现任何问题,运行时也不会报错,只能在运行时,通过调试断点才能发现。
知道了问题所在,解决办法就有了:
方法1:
model层对于“搜索参数实体类”不要给私有成员赋任何初始值。这样后面写webservice层的人,也自然不会想到用if (pars.xxx == Consts.xxx)来判断了
方法2:
如果model层的代码不允许修改,也可以修改webservice中的if语句代码,考虑到兼容性,以前类似
if (pars.Birthday_Max!=Consts.BirthdayMax)
这种代码,应该变成
if (!(pars.Birthday_Max == Consts.BirthdayMax || pars.Birthday_Max==default(DateTime)))
继续唠叨:vs.net这种“自动重复生成asmx中业务实体类定义”代码的行为,即使是UI层添加了Model层项目引用后,依然如此。但是在后续测试中发现,如果把asmx换成用wcf(.svc)来实现,在UI层添加了Model引用后,vs.net不会再重复生成相应的类定义。
有图有真相:
文中所述问题示例源代码:https://files.cnblogs.com/yjmyzz/website_test.7z
其实WebService还有其它不爽的地方,见webservice今日遇到的二个问题:DataTable + Namespace
"青山遮不住,毕竟东流去",正如IE6会被其它浏览器取代一样,asmx技术也会慢慢淡出历史舞台,建议大家对于新项目,大胆的用wcf来代替asmx吧,我会在下一篇博文中,写一个"wcf10分钟速成",帮助对于从没接触过wcf的asmx迷们,消除对wcf的恐惧,快速上手wcf.
作者:菩提树下的杨过
出处:http://yjmyzz.cnblogs.com
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
出处:http://yjmyzz.cnblogs.com
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。