Web Service的创建和访问

2017-12-21  本文已影响0人  Mrgz

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

创建Web Service

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

/// <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";
    }
}

依葫芦画瓢,添加方法并为方法添加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引用之后,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时将不需要修改配置文件

使用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方法调用需要注意几点
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();
}

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.哪天想起再补充

上一篇下一篇

猜你喜欢

热点阅读