美文网首页
Web Service的创建和访问

Web Service的创建和访问

作者: Mrgz | 来源:发表于2017-12-21 23:35 被阅读0次

    为了实现不同系统间的业务数据交换,我们需要访问对方的接口,也需要开放出自己的接口。.Net的web service挺好用的,不管是创建还是调用都是相当容易。

    创建Web Service

    • 添加Web服务(ASMX)

    默认的大概就这个样子吧,例子是个很好的东西。

    /// <summary>
    /// WebService1 的摘要说明
    /// </summary>
    [WebService(Namespace = "http://tempuri.org/")]
    [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
    [System.ComponentModel.ToolboxItem(false)]
    // 若要允许使用 ASP.NET AJAX 从脚本中调用此 Web 服务,请取消注释以下行。 
    // [System.Web.Script.Services.ScriptService]
    public class WebService1 : System.Web.Services.WebService
    {
        [WebMethod]
        public string HelloWorld()
        {
            return "Hello World";
        }
    }
    
    • 创建Web Method

    依葫芦画瓢,添加方法并为方法添加WebMethod特性。当需要实现的服务比较复杂,类型比较多,但是调用比较集中,可以考虑一个Web Method承载多个服务,把服务名包含在参数中,这要可以避免到处都是Web Method,数据格式也比较统一,写说明文档的时候深有感受。

    /// <summary>
    /// TestService1 把服务名做成一个参数
    /// </summary>
    /// <param name="service">服务名</param>
    /// <param name="data">数据</param>
    /// <param name="verifyCode">校验码</param>
    /// <returns></returns>
    [WebMethod]
    public string TestService1(string service,string data,string verifyCode)
    {
        return null;
    }
    /// <summary>
    /// TestService2 把服务名包含在主数据内
    /// </summary>
    /// <param name="data">数据</param>
    /// <param name="verifyCode">校验码</param>
    /// <returns></returns>
    [WebMethod]
    public string TestService2(string data,string verifyCode)
    {
        return null;
    }
    

    这两种方案我也挺纠结的,一扯就多了,最近用了第二种方案做接口有点后悔了,所以我们以第一种方案为例。

    访问接口的请求需要验明正身(这里考虑的是用户名密码这种),但本着不传密码本身的思想,数据在发送前需要把数据和密码计算成一个校验码,类似上面的考虑,也有两种方案,贴一个上来。

    /// <summary>
    /// TestService3 把用户ID做成一个参数
    /// </summary>
    /// <param name="service">服务名</param>
    /// <param name="userId">用户ID</param>
    /// <param name="data">数据</param>
    /// <param name="verifyCode">校验码</param>
    /// <returns></returns>
    [WebMethod]
    public string TestService3(string service,string userId, string data, string verifyCode)
    {
        return null;
    }
    

    是否应该写在同一个Web Method中可以看服务类型是不是能够分到一个大类里面,而参数怎样组成可以根据对安全和便捷的要求。对数据安全性要求不高的时候,可以直接用两个参数,一是包含服务名和用户ID在内的主数据,二是可以结合主数据进行身份验证的校验码。

    至于主数据的结构,见过JSON和XML,其中XML偏多。不知怎么的,感觉XML与Web Service更般配。但是显然XML结构比JSON更笨重,因为XML每个节点前后都有节点名称,而JSON一个属性一个名称;生成XML格式数据也比生成JSON格式数据麻烦,大部分XML的特别的东西没有体现出来,我们只需要一个简单的统一的数据格式标准而已。
    后来遇到另一种XML数据格式的结构,把数据中的变量全部做成XML节点的属性,XML节点变少了,也算是一种方案。

    • 添加调用方法

    刚才新添加的Web Service默认支持soap1.1,soap1.2访问方法。如果需要支持POST方法,需要修改config文件,在system.web节点中增加配置。

    <system.web>
      <webServices>
        <protocols>
          <add name="HttpGet"/>
          <add name="HttpPost"/>
        </protocols>
      </webServices>
    </system.web>
    

    访问Web Service

    浏览器打开Web Service的路径,可以看到访问方法示例,soap1.1,soap1.2,post,照着样子拼凑出来就可以调用。另外可以添加web引用,添加服务引用,制作dll的方式调用。
    以下的例子用的Web Service创建为

    /// <summary>
    /// TestService 的摘要说明
    /// </summary>
    [WebService(Namespace = "http://tempuri.org/")]
    [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
    [System.ComponentModel.ToolboxItem(false)]
    // 若要允许使用 ASP.NET AJAX 从脚本中调用此 Web 服务,请取消注释以下行。 
    // [System.Web.Script.Services.ScriptService]
    public class TestServices : System.Web.Services.WebService
    {
    
        [WebMethod]
        public string HelloWorld(string name)
        {
            return "Hello "+name;
        }
    }
    

    忍不住说一句,python调用不要太容易,顿时感觉写C#好苦逼

    from suds.client import Client  
    url = 'http://www.webxml.com.cn/WebServices/WeatherWebService.asmx?wsdl"  
    client = Client(url)
    result = client.service.HelloWorld('A')
    print(result)
    
    • 添加服务引用

    添加服务引用之后,config文件内将在configuration节点下自动生成

    <system.serviceModel>
        <bindings>
            <basicHttpBinding>
                <binding name="TestServicesSoap" />
            </basicHttpBinding>
        </bindings>
        <client>
            <endpoint address="http://localhost:1029/TestServices.asmx" binding="basicHttpBinding"
                bindingConfiguration="TestServicesSoap" contract="ServiceReference1.TestServicesSoap"
                name="TestServicesSoap" />
        </client>
    </system.serviceModel>
    

    调用

    ServiceReference1.TestServicesSoapClient client = new ServiceReference1.TestServicesSoapClient();
    string content = client.HelloWorld("甲");
    

    当前项目直接调用不会有什么问题
    如果项目A添加了服务引用,项目B引用项目A,会出现类似于下面这种错误:

    在 ServiceModel 客户端配置部分中,找不到引用协定“ServiceReference1.TestServicesSoap”的默认终结点元素。这可能是因为未找到应用程序的配置文件,或者是因为客户端元素中找不到与此协定匹配的终结点元素。

    解决办法,将刚才那段配置信息复制到项目B的config文件中就可以了。

    如果要在项目B中直接调用项目A中的服务引用,需要在项目B中添加引用system.serviceModel。

    • 添加Web引用

    添加Web引用之后,config文件内将在configuration节点下自动生成

    <applicationSettings>
        <service.Properties.Settings>
            <setting name="service_localhost_TestServices" serializeAs="String">
                <value>http://localhost:1029/TestServices.asmx</value>
            </setting>
        </service.Properties.Settings>
    </applicationSettings>
    

    调用

    WebService1.TestServices service = new WebService1.TestServices();
    //service.Url = "http://localhost:1031/TestServices.asmx";
    string content = service.HelloWorld("甲");
    

    当项目B调用项目A时将不需要修改配置文件

    • 制作成dll文件

    使用vs命令行wsdl制作dll,步骤:
    1.浏览器打开http://localhost:1029/TestServices.asmx?wsdl,并保存为.wsdl文件,如:TestServices.wsdl
    2.执行命令wsdl /namespace:Services.Test TestServices.wsdl,将生成文件TestServices.cs
    3.执行命令csc /out:Services.TestServices.dll /t:library TestServices.cs,将生成文件Services.TestServices.dll

    访问Web Service直接引用Services.TestServices.dll,干净利落。

    Services.Test.TestServices service = new Services.Test.TestServices();
    //service.Url = "http://localhost:1031/TestServices.asmx";
    string content =  service.HelloWorld("甲");
    

    其它非.Net的Web Service制作dll也可以用类似的方案。

    当wsdl文件带有wsdl:import时,需要把import的文件也下载下来,否则wsdl命令会执行出错,一个一个找import难免比较麻烦,这里有个取巧的方法,添加web引用或者服务引用都会生成全部的wsdl文件,找到对应目录拷贝出来或者直接定位到对应目录生成dll即可。

    • post方法

    用Post方法调用需要注意几点
    1.ContentType设置为application/x-www-form-urlencoded
    2.Request流用System.Text.Encoding.Default编码
    3.Response流用System.Text.Encoding.UTF8解码
    4.键值对的值一定要UrlEncode编码

    因此简单的访问方式就出来了

    string url = "http://localhost:1031/TestServices.asmx" + "/HelloWorld";
    string data = "name=" + HttpUtility.UrlEncode("甲");
    string content;
    WebRequest req = HttpWebRequest.Create(url);
    req.Method = "POST";
    req.ContentType = "application/x-www-form-urlencoded";
    using (StreamWriter sw = new StreamWriter(req.GetRequestStream(), System.Text.Encoding.Default))
    {
        sw.Write(data);
    }
    using (StreamReader sr = new StreamReader(req.GetResponse().GetResponseStream(), System.Text.Encoding.UTF8))
    {
        content = sr.ReadToEnd();
    }
    
    • 调用动态生成的Web Service

    WebService不适合高频动态生成,偶尔需要调用可以用动态生成

    public static object InvokeWebService(string url, string methodname, object[] args)
    {
        //namespace是需引用的webservices的命名空间
        string @namespace = "client";
    
        //获取WSDL
        System.Net.WebClient wc = new System.Net.WebClient();
        System.IO.Stream stream = wc.OpenRead(url + "?WSDL");
        System.Web.Services.Description.ServiceDescription sd = System.Web.Services.Description.ServiceDescription.Read(stream);
        string classname = sd.Services[0].Name;
        System.Web.Services.Description.ServiceDescriptionImporter sdi = new System.Web.Services.Description.ServiceDescriptionImporter();
        sdi.AddServiceDescription(sd, "", "");
        System.CodeDom.CodeNamespace cn = new System.CodeDom.CodeNamespace(@namespace);
    
        //生成客户端代理类代码
        System.CodeDom.CodeCompileUnit ccu = new System.CodeDom.CodeCompileUnit();
        ccu.Namespaces.Add(cn);
        sdi.Import(cn, ccu);
    
        //设定编译参数
        System.CodeDom.Compiler.CompilerParameters cplist = new System.CodeDom.Compiler.CompilerParameters();
        cplist.GenerateExecutable = false;
        cplist.GenerateInMemory = true;
        cplist.ReferencedAssemblies.Add("System.dll");
        cplist.ReferencedAssemblies.Add("System.XML.dll");
        cplist.ReferencedAssemblies.Add("System.Web.Services.dll");
        cplist.ReferencedAssemblies.Add("System.Data.dll");
    
        //编译代理类
        Microsoft.CSharp.CSharpCodeProvider csc = new Microsoft.CSharp.CSharpCodeProvider();
        System.CodeDom.Compiler.CompilerResults cr = csc.CompileAssemblyFromDom(cplist, ccu);
    
        if (cr.Errors.HasErrors)
        {
            //处理错误信息
            System.Text.StringBuilder sb = new System.Text.StringBuilder();
            foreach (System.CodeDom.Compiler.CompilerError ce in cr.Errors)
            {
                sb.Append(ce.ToString());
                sb.Append(System.Environment.NewLine);
            }
            throw new Exception(sb.ToString());
        }
    
        //生成代理实例,并调用方法
        System.Reflection.Assembly assembly = cr.CompiledAssembly;
        Type t = assembly.GetType(@namespace + "." + classname, true, true);
        object obj = Activator.CreateInstance(t);
        System.Reflection.MethodInfo mi = t.GetMethod(methodname);
    
        return mi.Invoke(obj, args);
    }
    

    看不懂没关系,直接调用,好好使用,不要把内存撑着了。

    string url = "http://localhost:1031/TestServices.asmx";
    string method = "HelloWorld";
    string name = "甲";
    string content = InvokeWebService(url, method, new object[] { name }) as string;
    
    • 补充

    1.对于接口相同的多套系统,每个系统都对接一遍不现实,只是URL不同。其中web引用和dll都有属性URL,可以修改,服务引用貌似不能修改URL。如果对方系统更新,Web引用和服务引用都可以直接更新,dll不能直接更新,需要重新制作。

    2.若是在C#中调用Java的webservice,可能会遇到一个问题,java只能获取到string类型参数的值,int,double等在服务器端得到null。非string类型的属性同时生成两个属性:"属性××"、"属性××Specified"。而"属性××Specified"是一个bool类型,只有这个属性被设置成true时,"属性××"的值才会被序列化成xml传递。因此需要注意修改cs文件,把"属性××Specified"删掉。如果是服务引用和web引用,可以直接找到Reference.cs进行修改,如果是制作dll,需要在生成的cs文件上修改。
    第一次遇到这个问题时某人给我的解答:http://www.cnblogs.com/zhbsh/archive/2013/04/22/3035477.html
    顺手贴上微软的解释:https://msdn.microsoft.com/zh-cn/library/system.xml.xmlattribute.specified(v=vs.110).aspx

    3.如果有留意会发现web引用的Reference.cs文件与wsdl命令生成的cs文件惊人的相似,因此制作dll还有个取巧的方法,先添加web引用,然后csc命令打包成dll。

    4.哪天想起再补充

    相关文章

      网友评论

          本文标题:Web Service的创建和访问

          本文链接:https://www.haomeiwen.com/subject/idjzwxtx.html