背景:
这边需要用C#call一个webservice的接口,推过去的参数以JSON的格式传递过去。
说实话,接口写了很多,全部是封装好的,直接调用,把对象传进去就好了,所以要写个从无到有的,有点蒙蔽,只能百度自己查了。
1.接口调用地址验证
拿到人家的接口,首先要验证接口是否正确。
在浏览器中输入该地址:返回结果如下,正常返回,能看到里面所有方法
2.验证调用方法是否正确
刚开始我用postman,按照百度,输入request地址,选择post方式,在Headers中配置“Content-Type”等,结果点击send之后,返回的记过就是soap:Fault,没办法,主要是postman里面设置的东西太多了,估计我哪项没设置好导致无法正确读取我传过去的参数,然后百度又百度不到怎么设置参数的。
然后改用SoapUI就可以了,官网下载SoapUI,安装完毕运行,新建SOAP项目:
随便起个名称TEST,然后在Initial WSDL中输入刚才在浏览器中验证的接口地址,点击OK
在左侧菜单栏中,可以看到此接口的所有16个方法,与浏览器中看到的一致
选择你需要call的方法双击Request1,将直接跳出你需要传输过去的XML,里面包含了需要填入的2个参数。
但是我被接口方给的接口文档混淆了,文档上写传过去的必须是JSON格式,百度了好久,都找不到WebService传参传Json的例子,全是传XML的,后来才知道,WebService传参就是以XML格式传过去的,即使文档里面说的JSON格式,实际上也是一个JSON格式的字符串放在XML的参数里而已。
如下,文档说传过去的参数必须是JSON格式,所实际上就是在 < paras>? paras>中放入JSON格式的字符串。
然后又遇到一个问题, < ValidateData>? ValidateData>是什么鬼?接口文档中压根没有,所以只能绕N久找到接口的给出方问了,这个应该是对应验证XML的一个参数,只要传固定值就好了(总结经验,call接口必须得找给出接口的人,面对面测试,否则自己肯兹肯兹半天都不一定搞得出来,这边就是,文档中就压根没给出ValidateData的参数)
写入2个参数,第一个参数跟接口方讨论了下,是固定值;第二个参数就是按照接口文档给出的规范,传过去一个JSON类型的字符串,输入2个参数,点击运行,可以看到,调用接口方法后正确返回结果。
3.接口调用方法正确后,开始写代码如何调用该方法了。
网上查了下C#调用webService接口的方式有3种方式,看下面链接,写的很详细:(但是动态调用我按照他里面写的,就是无法调用,代码走到调用生成的.dll文件一直是null,不知道为啥)
https://www.jb51.net/article/190211.htm
因为要测试代码,所以我先随便建立了一个“Windows窗体应用程序”,随便添加个button,在点击事件里面来走下代码测试下结果:
3.1.静态调用
静态调用最简单,如果是给定接口的URL(即我这边的案例),直接引用即可
刚开始我以为静态调用无法更改url地址,实际上并不是,在静态引用后,vs会很智能的自动将地址配置到配置文件中,这时候更改url地址,即配置文件中的地址了。
右击引用,选择添加服务引用
将给的接口地址输入,点击“转到”,则可以看到“服务”中的接口了,然后自己修改个命名空间即可,注:有些接口文档给出的地址可能需要自己补上尾部“?WSDL”
然后你回看到引用的接口已经被添加到项目中,类似于一个类文件,或者说就可以把“ServiceReference1”看做一个封装好的接口类就行了
双击ServiceReference1,可以看到所有的方法都在里面
找到方法名
双击方法名,进入“Reference.cs”文件,们可以看到里面的所有方法了。
然后找到实现该方法名的真正方法“WebBuilderWebServiceForZTBClient”
实例化那个接口类:“接口类名.方法名”。
最后根据接口文档,调用业务方法“client.InformationDel(validateData, paras);”。
针对“client.InformationDel(validateData, paras);”,我们按F12进入到“Reference.cs”文件,可以看到InformationDel()删除方法与上面我们用SoapUI测试的传入参数是一致的
与SoapUI测试的输入XML参数一致,也是2个参数:“ValidateData”和“paras”
点击button完成点击事件,可以看到输出结果是成功的,传过来也是JSON字符串,需要的话,可以直接把它转成对象。
最后我们在“Reference.cs”文件里面,拉倒最上面可以看到,里面有个“ConfigurationName”下面的方法全部按照这个方法名来调用的,这个实际上就是vs自动智能帮忙生成的配置文件的Url
进入配置文件,可以看到自动配置好的地址,如果ip变了,只要配置这边就OK了,无需重新生成引用文件了
3.2.动态调用
刚开始我以为静态调用是接口地址固定调用,而动态调用时接口地址变动的调用,但是实际上,静态调用也是可以配置地址的,如3.1最后写的一点,实际上静态调用指的是直接引用接口地址,动态调用是代码生成的.dll文件,然后引用该文件,实现dll中的类,调用里面的方法。
根据百度的参考,就是调用不起来,尝试了不同的人写的都不行,最后没法办了只好采用静态调用,直到写这个笔记之前,想找到原因,然后又找了个人写的,居然可以了,发现可能是@namespace命名的问题,同样的方法,我按照下面这位博主的方法,就调用成功了,看了下好像就@namespace命名不同,其他都一样。
https://www.cnblogs.com/ruochen/archive/2007/12/11/990427.html
直接上代码,就是上面的连接
private void button7_Click(object sender, EventArgs e)
{
string validateData = "epointoa@83OZsT5IdXL2uwu_XMvEfpdpzrc=@MjE0NzQ4MzY0Nw==";
string paras = "[" +
"{" +
"'siteGuid': '123'," +
"'infoId': 'SCJG20990040'," +
"}" +
"]";
//这里的namespace是需引用的webservices的命名空间,在这里是写死的,大家可以加一个参数从外面传进来。
string @namespace = "client";
try
{
string url= "http://192.168.0.1:8081/EpointWebBuilder/services/WebBuilderWebServiceForZTB";
//获取WSDL
WebClient wc = new WebClient();
Stream stream = wc.OpenRead(url + "?WSDL");
ServiceDescription sd = ServiceDescription.Read(stream);
string classname = sd.Services[0].Name;
ServiceDescriptionImporter sdi = new ServiceDescriptionImporter();
sdi.AddServiceDescription(sd, "", "");
CodeNamespace cn = new CodeNamespace(@namespace);
//生成客户端代理类代码
CodeCompileUnit ccu = new CodeCompileUnit();
ccu.Namespaces.Add(cn);
sdi.Import(cn, ccu);
CSharpCodeProvider csc = new CSharpCodeProvider();
ICodeCompiler icc = csc.CreateCompiler();
//设定编译参数
CompilerParameters cplist = new 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");
//编译代理类
CompilerResults cr = icc.CompileAssemblyFromDom(cplist, ccu);
if (true == 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("InformationDel");
object obj1 = mi.Invoke(obj, new object[] { validateData, paras });
MessageBox.Show($"方法返回值:{obj1.ToString()}"); 输出
}
catch (Exception ex)
{
string expt = ex.ToString();
}
}
结果也一样成功了
4.查看接口方结果。
完成以上3步,实际上OK了,需要查看的就是对方接口功能是否正确。
坑爹的就是,明明返回true,但是接口的业务就是没有完成,无法看到预期结果。
最后还是找对方,联合测试,对方查看,返回true是因为数据已经成功插入到他们的数据库了,但是业务没完成,是因为里面的“地区”字段传空了,那边不知道把内容关联到哪个地区,所有没有显示最终结果。我只能表示“呵呵”了,既然“地区”字段必须要有值,那你接口文档为何要写允许为空?关键还是返回true?
5.总结
即使接口文档写的很详细,涉及到其他系统,还是得找到对方接口的开发,很重要,至少能节约半天以上时间。