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.

posted @ 2011-05-12 09:10  菩提树下的杨过  阅读(2101)  评论(0编辑  收藏  举报