从这里出发,力争做一位新手中的高手....——lxinxuan's Blog

posts - 170, comments - 427, trackbacks - 26, articles - 0
  博客园 :: 首页 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理
        Demo下载:http://files.cnblogs.com/lxinxuan/wa.rar
       最近一个项目要用到webservice调用业务层类,刚开始的时候遇到了一点小麻烦,经过这两天的总结和实践,终于总结出几个比较常见的情况下的解决方法。
        不知道大家是怎么解决,可能太简单了,所以没有觉得它是一个问题。反正我在博客园中没有搜索到相关的帖子。
        说实话,以前并没有真正开发过涉及webservice的项目,顶多也就是看看msdn,写点小程序,当时并没有发现问题,因为传递的参数和返回值都是简单数据类型,所以并没有发现本文提及的问题——使用自定义类。
         所谓自定义类,不知道我有没有表达清楚,这里指的就是petshop中的Model层实体类了。
         比如以下代码:
using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;

namespace Model
{
    [Serializable]
    
public class Student
    
{
        
private string stuName;

        
public Student()
        
{ }

        
public string StuName
        
{
            
get return this.stuName; }
            
set this.stuName = value; }
        }

    }

}


webservice传递的内容必须是可序列化的,不管是参数还是返回值。上面定义的实体类Student,在类定义之前标示了[Serializable],指明可序列化的。但当涉及到实体类集合的时候,如果使用IList<Student>来表示,就会抱错,原因是IList是不可以序列化的,这种情况下,我们就可以使用System.Collections.ObjectModel.Collection<Student>来表示一个实体类集合。这里给出了两种可能出现的实体类和实体类集合,以下就开始说明各种解决方法:

1、把实体类集合,作为Object[]传递。
      这种情况下,我们必须使用webservice中的实体类,传递的是实体类集合对应的Object[]传递,WebService中方法的参数类型是ArrayList。
比如WebService中的方法是:
[XmlInclude(typeof(Student))]
        [WebMethod]
        
public string HelloStus(ArrayList stuList)
        
{
            BLL.Class1 cls 
= new BLL.Class1();
            
return cls.GetName(stuList);
        }
别漏了[XmlInclude(typeof(Student))]这一行,不然在表现层就引用不到WebService中的实体类了。
这个时候,在表现层添加web引用,表现层中的调用代码如下:(参考Demo中的button1_Click()方法)
/// <summary>
        
/// 必须使用webservice中的实体类,传递实体类集合,作为Object[]传递,WebService中的参数类型是ArrayList,并提供一个将集合转化为Object[]的公共类
        
/// </summary>
        
/// <param name="sender"></param>
        
/// <param name="e"></param>

        private void button1_Click(object sender, EventArgs e)
        
{
            
string str = "";

            localhost.Student stuInfo1 
= new localhost.Student();
            stuInfo1.StuName 
= "lxinxuan";
            localhost.Student stuInfo2 
= new localhost.Student();
            stuInfo2.StuName 
= "www.cnblogs.com/lxinxuan";

            IList
<localhost.Student> stuList = new List<localhost.Student>();
            stuList.Add(stuInfo1);
            stuList.Add(stuInfo2);

            
object[] array = this.ConvertToArray<localhost.Student>(stuList);//这是一个将集合转换为Objec[]的泛型方法
            str = ser.HelloStus(array);//传递Object[],返回值是StuName的值

            MessageBox.Show(str);
        }

//这是一个将集合转换为Objec[]的泛型方法
 private object[] ConvertToArray<T>(IList<T> tList)
        
{
            
object[] array = new object[tList.Count];
            
int i = 0;
            
foreach (T t in tList)
            
{
                array[i] 
= t;
                i
++;
            }

            
return array;
        }


2、传递单个实体类,使用WebService中的实体类
这种情况下,可以看作是情况1的特例——只有一个元素的数组。
当然,这种情况下我们可以换一种做法——使用WebService中的实体类。
先看webservice中的代码:
[XmlInclude(typeof(Student))]
        [WebMethod]
        
public string HelloStu(Student stuInfo)
        
{
            
return stuInfo.StuName;
        }
同样必须添加这一行代码[XmlInclude(typeof(Student))]。
然后调用代码是:
 /// <summary>
        
/// 传递单个实体类,使用WebService中的实体类
        
/// </summary>
        
/// <param name="sender"></param>
        
/// <param name="e"></param>

        private void button2_Click(object sender, EventArgs e)
        
{
            
string str = "";
            localhost.Student stuInfo1 
= new localhost.Student();//注意,这里调用了webservice中的实体类,而不是Model中的实体类。否则出错。
            stuInfo1.StuName 
= "lxinxuan";
            str 
= ser.HelloStu(stuInfo1);//传递webservice中的实体类
            MessageBox.Show(str);
        }

3、传递实体类构成的Collection。这是和情况1类似的情形,只是传递的类型不一样。可以对照一下。
这种情况下,必须通过修改Reference.cs的代码,不过每次更新都要重新修改,而且必须每个类修改,比较麻烦!不推荐使用,这不知道是哪位仁兄想出来的方法,我也是看了人家的做法才总结出来的,不过能去修改Reference.cs的代码,已经说明钻研精神了,鼓励下。
同样先给出webservice中方法的代码:
[WebMethod]
        
public string HelloStusByList(Collection<Student> stuList)//这里参数类型是Collection
        {
            BLL.Class1 cls 
= new BLL.Class1();
            
return cls.GetName(stuList);
        }
方法的参数是Collection,在添加了webservice之后,Reference.cs中的对应方法的参数变成了student[],数组!!webservice和数组走得真近阿。。。这里将Reference.cs中的方法HelloStusByList的参数类型student[]改为Collection<localhost.Student>,如下所示。
表示层调用代码:
/// <summary>
        
/// 传递实体类构成的Collection,通过修改Reference.cs的代码,不过每次更新WebService之后都要重新修改,而且必须每个类修改,麻烦
        
/// </summary>
        
/// <param name="sender"></param>
        
/// <param name="e"></param>

        private void button3_Click(object sender, EventArgs e)
        
{
            
string str = "";

            localhost.Student stuInfo1 
= new localhost.Student();
            stuInfo1.StuName 
= "lxinxuan";
            localhost.Student stuInfo2 
= new localhost.Student();
            stuInfo2.StuName 
= "www.cnblogs.com/lxinxuan";

            Collection
<localhost.Student> stuList = new Collection<localhost.Student>();
            stuList.Add(stuInfo1);
            stuList.Add(stuInfo2);

            str 
= ser.HelloStusByList(stuList);//默认情况下,这里HelloStusByList方法的参数是Student[],通过手动修改为Collection,就可以了

            MessageBox.Show(str);
        }

4、先将实体类集合序列化为表现为xml格式的string,然后在webservice中反序列化成Collection<>(注意:不可以是IList<>),然后再传递给业务层对象。
[2007-5-25修改:博友“代码乱了”提出,可以采用二进制序列化。确实是的,这里的xml序列化和binary序列化都是可以的,只是我为了调试时跟踪信息方便,才用了xml序列化。这里不再罗列出来。谢谢“代码乱了”]
[WebMethod]
        
public string HelloStusByCollection(string sXml)
        
{
            BLL.Class1 cls 
= new BLL.Class1();
            Collection
<Student> stuList = cls.DeSerializerCollection<Student>(sXml, typeof(Collection<Student>));//先反序列化为Collection
            
return cls.GetName(stuList);
        }
DeserializerCollection方法代码如下:
        /// <summary>
        
/// 
        
/// </summary>
        
/// <typeparam name="T"></typeparam>
        
/// <param name="sXml"></param>
        
/// <param name="type"></param>
        
/// <returns></returns>

        public Collection<T> DeSerializerCollection<T>(string sXml, Type type)
        
{
            XmlReader reader 
= XmlReader.Create(new StringReader(sXml));
            System.Xml.Serialization.XmlSerializer serializer 
= new System.Xml.Serialization.XmlSerializer(type);
           
            
object obj = serializer.Deserialize(reader);
            
return (Collection<T>)obj;
        }

表现层调用代码如下:
/// <summary>
        
/// 先将实体类集合序列化为string,然后在webservice中反序列化成Collection<>,然后再传递给业务层对象
        
/// </summary>
        
/// <param name="sender"></param>
        
/// <param name="e"></param>

        private void button4_Click(object sender, EventArgs e)
        
{
            
string str = "";

            Student stuInfo1 
= new Student();
            stuInfo1.StuName 
= "lxinxuan";
            Student stuInfo2 
= new Student();
            stuInfo2.StuName 
= "www.cnblogs.com/lxinxuan";

            Collection
<Student> stuList = new Collection<Student>();
            stuList.Add(stuInfo1);
            stuList.Add(stuInfo2);

            
string stuString = this.Serializer<Collection<Student>>(stuList);//先序列化为xml文件格式的string
            str 
= ser.HelloStusByCollection(stuString);
            MessageBox.Show(str);
        }
Serialize方法代码如下:
/// <summary>
        
/// 实体类集合序列化为字符串
        
/// </summary>
        
/// <typeparam name="T"></typeparam>
        
/// <param name="objToXml"></param>
        
/// <returns></returns>

        public string Serializer<T>(T objToXml)
        
{
            System.IO.StringWriter writer 
= new System.IO.StringWriter();
            System.Xml.Serialization.XmlSerializer serializer 
= new System.Xml.Serialization.XmlSerializer(objToXml.GetType());
            serializer.Serialize(writer, objToXml);
            
return writer.GetStringBuilder().ToString();
        }

5、这种情况就是情况4的特例,序列化一个实体类并传递,方法类似,就不写出来,参见Demo代码。

大概就是这些了,当然传递DataSet是最传统最好的办法了,呵呵~

Feedback

#1楼   回复  引用  查看    

2007-05-24 13:39 by 天河里的一粒沙      
你那样还不如都放在一个IList 里传过去再反序列化呢.

#2楼[楼主]   回复  引用  查看    

2007-05-24 13:41 by ColdDog      
@天河里的一粒沙
你试试看IList能通过WebService传得过去吗?行的话就没那么多麻烦事了

#3楼   回复  引用    

2007-05-24 13:57 by blogtalker[未注册用户]
great..........

#4楼   回复  引用    

2007-05-24 14:56 by A.Z[未注册用户]
IList<T> ---- List<T>
ICollection<T> ---- Collection<T>
IList<T> : ICollection<T>, ...


#5楼   回复  引用    

2007-05-24 15:03 by 预备役中尉
呵呵,近来服务的项目也正在写这些,把业务逻辑包装成WEB服务提供给外部(JAVA)用户,一粒沙说的是不对的.参数不能是泛型和接口,只能是强类型,如List而不是IList,返回类型不能是接口类型,如List<DeviceBid>而不是IList<DeviceBid>...

#6楼   回复  引用    

2007-05-24 17:50 by .Net开发资源中心[未注册用户]
支持无私奉献的共享精神:
http://***/gb2312/blogs/20070524/247/WebService中使用自定义类的解决方法_5种.aspx

#7楼[楼主]   回复  引用  查看    

2007-05-24 18:40 by ColdDog      
@预备役中尉
嗯,是的,你说的没错~
@.Net开发资源中心
多谢支持!

#8楼   回复  引用  查看    

2007-05-24 19:55 by Bruce Lee      
用java调还是比较麻烦,今天在给自己的。net webservice写java的调用样例代码,搞死了。

#9楼   回复  引用  查看    

2007-05-25 09:26 by 代码乱了      
为什么不用binary序列化呢?

#10楼[楼主]   回复  引用  查看    

2007-05-25 09:40 by ColdDog      
@代码乱了
谢谢提醒,xml序列化和binary序列化都是可以的,上面的情况4,我只列出了xml序列化,其实binary序列化也一样的,只是我在调试的时候,为了跟踪信息的方便,才使用xml序列化。
我修改了原文,谢谢!

#11楼   回复  引用  查看    

2007-05-25 12:26 by 代码乱了      
@ColdDog
:)
很全面,学习了

#12楼   回复  引用  查看    

2007-05-25 12:29 by 代码乱了      
@ColdDog
其实两种序列化都可以的,不过xml序列化可能有一些麻烦,譬如涉及到多层继承关系,和复杂的属性的时候,没有binary序列化来的方便,但是xml序列化跨平台上就有很大用处,如果是需要跨平台还是用xml序列化好一些

#13楼   回复  引用    

2007-05-25 13:54 by Tiu[未注册用户]
我用过List,能成功返回

#14楼[楼主]   回复  引用  查看    

2007-05-25 13:59 by ColdDog      
@Tiu
List可以,IList就不可以,就像前面A.Z 说的那样~

#15楼   回复  引用    

2007-11-18 23:16 by 天道酬请[未注册用户]
你好,如果自定义类里包含IList类型的对象,该怎么在webservice中使用?

PS:Demo现在无法下载了

#16楼   回复  引用    

2007-11-19 23:52 by xuanyuancl[未注册用户]
--引用--------------------------------------------------
天道酬请: 你好,如果自定义类里包含IList类型的对象,该怎么在webservice中使用?

PS:Demo现在无法下载了
--------------------------------------------------------
可以下的,用讯雷下(还是讯雷比较小强,下不了的资源它还是表现不俗的)
***********************************************
顺便问下ColdDog,请问这几种方法有没有数据传输的效率差异呀?

#17楼[楼主]   回复  引用  查看    

2007-11-21 14:00 by ColdDog      
@天道酬请:
不好意思,自定义类里包含IList类型的对象这个情况我没有测试过,晚上回去试下,同时把误删掉的文件上传,可能之前不小心删了。。。

@xuanyuancl:
这几种方法数据传输的效率差异,我还没有具体测试过,你能帮我测试下吗?顺便回来贴出来:)呵呵,我最近没有整这个东西了,所以都没有关注。

#18楼   回复  引用    

2008-02-15 14:36 by 匿名[未注册用户]
@ColdDog
好文章, 谢谢分享!

有个小小的问题,为什么你要推荐第四种方法呢.

我测试下,第四中方法比前面的直接使用类要慢.

#19楼   回复  引用    

2008-02-16 09:21 by 未登录的ColdDog[未注册用户]
@匿名
你好!第四种方法是最可行的,不管是序列化为xml或者二进制。
因为处理起来比较方便,前面1、2、3种方法自不必说,几乎不会有人去用的。当然,如果用DataSet这种方法是最直接的,但你知道,处理DataSet的数据类型远比处理自定义实体类(或者实体类集合)来得麻烦,不是吗?

#20楼   回复  引用    

2008-04-28 17:35 by EX[未注册用户]
我没办法实现在类似传DATASET一样,客户端代理不生成类定义代码,这样服务器和客户端都使用一个类订义???

#21楼   回复  引用  查看    

2008-08-07 14:32 by KymoWang      
我对webservice不熟,但是我试验了一下,不用这么麻烦呀,

localhost.Student s = new localhost.Service().GetStudent();
Response.Write(s.Id + " : " + s.Name);

localhost.Student[] l = new localhost.Service().GetStudentList();
foreach (localhost.Student s1 in l)
{
Response.Write(s1.Id + ":" + s1.Name);
}

#22楼   回复  引用  查看    

2008-12-17 09:57 by Sail      
继续

#23楼   回复  引用    

2008-12-30 00:59 by persialee[未注册用户]
Dear,lxinxuan:
我将自定义的类作为一个单独的项目“MODEL”, 客户端是Winform。在客户端中使用生成的代理类。
序列化:
List<EmployeInfo> list = ....
string sxml = Serializer<List<EmployeInfo>>(list);

在客户端使用反序列化:
string sxml = ws.GetEmployeList();
List<EmployeInfo> employes =ws.DeSerializerCollection<EmployeInfo>(sxml,typeof(List<EmployeInfo>));
这种情况的时候,反序列化属性值是null,我查了一下,发现是客户端的EmployeInfo是ws代理类生成的实体类,而服务器端序列化时的EmployeInfo是"MODEL"里面的实体类。
请问您有什么好的解决办法吗?谢谢赐教。。。。

#24楼[楼主]   回复  引用  查看    

2008-12-30 10:08 by ColdDog      
@persialee
不好意思,之前理解错了您的意思~
您说的情况是客户端调用,这个情况我没有尝试过。

#25楼   回复  引用    

2009-03-11 16:11 by 文件下载不了[未注册用户]
文件下载不了,案例文件下载不了,请检查。。
或者发一份给我也行

#26楼   回复  引用    

2009-05-25 08:44 by rzwjs
demo不能下载了,可以给我发一份吗?我QQ:57927098



发表评论

昵称: [登录] [注册]

主页:

邮箱:(仅博主可见)

评论内容:

  登录  注册

[使用Ctrl+Enter键快速提交评论]

0 758317




相关文章:

相关链接: