Web Service和代码生成技术的学习笔记

 最近做了个Web service的工具,用到一点代码生成的技术.
1..Net的Web Service主要有两种,ASMX的Web Service和WCF的Web Service.后者是前者的功能增强版.比如前者只支持http协议,只能建在IIS上,安全性也依赖于IIS等,后者还支持MSMQ, Enterprise Service(似乎主要是面向COM的)等,多了一些配置(如可以配置成双向的消息传送),可以设置多个EndPoint,这样对同一个服务,可以用不同的方式访问.可以建在Windows Service上或控制台程序即所谓self-hosting.两者之间有几个中间产品,如附加了SOAP Extension和后期的WSE的ASMX Web Service.

2.Web service的基本概念是:客户端和服务端通过XML进行交互,在两头通过序列化和反序列化实现XML和对象的转换.服务端将供客户调用的接口用XML描述(如标准的WSDL),客户根据描述生成本地代理,通过代理向服务端发SOAP消息,调用服务.

3.ASMX Web Service
1)在IIS上的配置:
若是IIS6.0,需要建一个虚拟目录,指向asmx文件所在的目录.若是IIS7.0,则建一个新站点.
如果ASP.Net是在IIS之前装的,那么需要运行windows/microsoft.net/framework/v2.0.57(假设是用asp.net 2.0)下的reg_iis.exe -i 注册asp.net.
若还不能识别asmx,需要在IIS里,将asmx和aspnet_isapi.dll关联.重启IIS后就可以正确解析asmx了.

 

2)基本结构:
服务端需要一个asmx文件,下面是个例子,内容很简单,就一个directive:
< %@ WebService Language="C#" CodeBehind="~/App_Code/Service.cs" Class="Service" %>

真正的实现代码放在service.cs文件里.

下面是一个简单的Service.cs文件的内容:
using System;
using System.Web;
using System.Web.Services;
using System.Web.Services.Protocols;
using webservicelib;

[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
public class Service : System.Web.Services.WebService
{
 public Service () {

 //Uncomment the following line if using designed components
 //InitializeComponent();
 }

 [WebMethod]
 public string TestNoParam() {
 return "Hello World";
 }

 [WebMethod]
 public string TestSimpleType(int intValue, string stringValue)
 {
 return "good";
 }

 [WebMethod]
 public void TestComplexType(ServiceLib lib)
 {
 //return lib.TestInt.ToString() + " " + lib.TestString;
 }
}

可以看出,首先,这个类必须继承System.Web.Services.WebService,其次,必须带有WebService属性,供客户调用的方法则带有WebMethod属性.

客户端需要一个代理类,下面是

这个代理类,如果是在Visual Studio里开发,可以加个web reference就可以自动生成,也可以用wsdl.exe命令行程序来生成.用wsdl.exe的话,主要是指定一个WSDL的Uri参数.

3)代理类的动态生成
还有一种方法,就是用程序动态生成.动态代码生成,主要是利用CodeDom和Reflection命名空间.
动态生成一个assembly有两种方法.一种是用System.Reflection.Emit命名空间里的方法先生成一个AssemblyBuilder实例,再用这个实例生成一个ModuleBuilder,再用ModuleBuilder生成TypeBuilder,也就是生成了一个类,接下来就可以用TypeBuilder里的方法生成Method等.显然这种方法比较麻烦.
还有一种是用CodeDom生成类的代码,再编译(也是用CodeDom里的方法)成assembly.CodeDom本身好比是XmlWriter,无非是提供了一种手工拼接成代码的手段.
.Net里在System.Web.Services.Description命名空间里有个ServiceDescriptionImporter类提供了生成代理类代码的支持.具体的实现代码(只列出关键部分):

using System.Xml;
using System.Xml.Schema;
using System.Xml.Serialization;
using System.Web.Services.Description;
using System.Web.Services.Discovery;
using System.Web.Services.Protocols;

......
 ServiceDescriptionImporter importer = new ServiceDescriptionImporter();
 importer.ProtocolName = "Soap";
 importer.Style = ServiceDescriptionImportStyle.Client;

 DiscoveryClientProtocol dcc = new DiscoveryClientProtocol();
 dcc.DiscoverAny(wsdlUrl);
 dcc.ResolveAll();

 foreach (object doc in dcc.Documents.Values)
 {
 if (doc is System.Web.Services.Description.ServiceDescription)
 {
 importer.AddServiceDescription(doc as System.Web.Services.Description.ServiceDescription, string.Empty, string.Empty);
 }
 else if (doc is XmlSchema)
 {
 importer.Schemas.Add(doc as XmlSchema);
 }
 }

 if (importer.ServiceDescriptions.Count == 0)
 {
 throw new Exception("给定的地址没有找到Web Service!");
 }

 CodeCompileUnit ccu = new CodeCompileUnit();
 ccu.Namespaces.Add(new CodeNamespace(""));//这里可以加上需要的命名空间
 ServiceDescriptionImportWarnings warnings = importer.Import(ccu.Namespaces[0], ccu);
 if ((warnings & ServiceDescriptionImportWarnings.NoCodeGenerated) > 0)
 {
 throw new Exception("生成代理类时出错!");
 }

如果直接生成代码,可以这样做:

Microsoft.CSharp.CSharpCodeProvider provider = new Microsoft.CSharp.CSharpCodeProvider();
StreamWriter sw = File.CreateText(<文件名>);
ICodeGenerator codeGenerator = provider.CreateGenerator(sw);
CodeGeneratorOptions codeGeneratorOptions = new CodeGeneratorOptions();
codeGenerator.GenerateCodeFromCompileUnit(ccu, sw, codeGeneratorOptions);
sw.WriteLine();
sw.Flush();

编译可以用下面的代码:
ICodeCompiler compiler = provider.CreateCompiler();
CompilerParameters options = new CompilerParameters(
new string[] {
"System.dll",
"System.Web.Services.dll",
"System.Xml.dll"

}, "c://temp//test.dll",//这是要生成的dll,如果用string.Empty,则在临时目录用一个随机产生的文件名生成dll,如果要生成exe,指定options.GenerateExecutable = true即可.
false);

options.GenerateInMemory = false;
CompilerResults results = compiler.CompileAssemblyFromDom(options, ccu);

不过这样生成的代码,打开后可以发现仅生成了方法和类的field,没有生成property,可以用下面的方法生成property:

foreach (CodeTypeDeclaration codeTypeDeclaration in ccu.Namespaces[0].Types)
{
ArrayList fields = new ArrayList();
foreach (CodeTypeMember member in codeTypeDeclaration.Members)
{
if (member is CodeMemberField)
{
fields.Add(member);
}
}

foreach (CodeMemberField field in fields)
{
CodeMemberProperty property = new CodeMemberProperty();
property.Name = field.Name;
property.Type = field.Type;
property.Attributes = MemberAttributes.Public;
property.CustomAttributes.AddRange(field.CustomAttributes);

field.Name = "_" + field.Name;
field.Attributes = MemberAttributes.Private;
field.CustomAttributes.Clear();

//为属性添加Get和Set方法
property.GetStatements.Add(
new CodeMethodReturnStatement(
new CodeFieldReferenceExpression(
new CodeThisReferenceExpression(), field.Name)));

property.SetStatements.Add(
new CodeAssignStatement(
new CodeFieldReferenceExpression(
new CodeThisReferenceExpression(), field.Name),
new CodePropertySetValueReferenceExpression()));

codeTypeDeclaration.Members.Add(property);
}

}
这种方法仅适用ASMX的Web Service.

4)用程序调用CodeDom生成的代理类
先利用Reflection找到要调用的方法,然后用Invoke方法来调用:

 public static object InvokeWebMethod(string proxyAssembly, string methodName, object[] parameters)
 {
 object returnValue = null;
 Assembly assembly = Assembly.LoadFile(proxyAssembly);

 foreach (Type type in assembly.GetTypes())
 {
 if (!type.IsSubclassOf(typeof(SoapHttpClientProtocol)))
 {
 continue;
 }

 SoapHttpClientProtocol service = Activator.CreateInstance(type) as SoapHttpClientProtocol;

 Type serviceType = service.GetType();

 foreach (MethodInfo method in serviceType.GetMethods(
 BindingFlags.Public | BindingFlags.DeclaredOnly
 | BindingFlags.Instance))
 {
 if (method.Name == methodName)
 {
 try
 {
 returnValue = method.Invoke(service, parameters);
 return returnValue;
 }
 catch (Exception ex)
 {
 throw ex;
 }
 }
 }
 }
 }

4.WCF Web Service
1)新概念:
相对于ASMX来说,增加了一些新概念:
Binding:指连接的方式,ASMX里只支持HTTP,没有这个概念.WCF里还支持Tcp,MSMQ,Enterprise Service等,所以有这个概念,指定连接所用的协议.
Endpoint:ASMX里,Web Service用一个URL来访问,也没有这个概念.WCF里支持多个访问点,所以有这个概念.
Behavior:对连接做些配置,如设置成单向或双向消息传送,支持HTTP的GET或只支持POST方式等.
2)Hosting
WCF在IIS hosting的基础上增加了Self-hosting的方式:
 public class Program
 {
 static void Main(string[] args)
 {
 using (ServiceHost serviceHost = new ServiceHost(typeof(Service1)))
 {
 serviceHost.Open();
 Console.WriteLine("Service started...");
 Console.ReadLine();
 serviceHost.Close();
 }
 }
 }
其中Service1类里存放了服务的实现代码.
3)基本结构
相对ASMX Web Service来说,WCF Web Service稍复杂一些,增加了控制手段,更加灵活.
服务端的简单代码如下:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;

namespace testWcfService
{
 // NOTE: If you change the interface name "IService1" here, you must also update the reference to "IService1" in App.config.
 [ServiceContract]
 public interface IService1
 {
 [OperationContract]
 string GetData(int value);

 [OperationContract]
 CompositeType GetDataUsingDataContract(CompositeType composite);

 // TODO: Add your service operations here
 }

 // Use a data contract as illustrated in the sample below to add composite types to service operations
 [DataContract]
 public class CompositeType
 {
 bool boolValue = true;
 string stringValue = "Hello ";

 [DataMember]
 public bool BoolValue
 {
 get { return boolValue; }
 set { boolValue = value; }
 }

 [DataMember]
 public string StringValue
 {
 get { return stringValue; }
 set { stringValue = value; }
 }
 }
}
这个是用Visual Studio的模板自动产生的代码, ServiceContract属性大体对应ASMX里的WebService属性,OperationContract对应于WebMethod属性.DataContract是新的属性,用于用户自定义类型.另外,用接口代替了类.
Endpoint和Behavior都放在app.config文件里:
< system.serviceModel>
 < services>
 < service name="testWcfService.Service1" behaviorConfiguration="testWcfService.Service1Behavior">
 < host>
 < baseAddresses>
 < add baseAddress = "http://localhost:8732/testWcfService/Service1/" />
 < /baseAddresses>
 < /host>
 < !-- Service Endpoints -->
 < !-- Unless fully qualified, address is relative to base address supplied above -->
 < endpoint address ="" binding="wsHttpBinding" contract="testWcfService.IService1">
 < !--
 Upon deployment, the following identity element should be removed or replaced to reflect the identity under which the deployed service runs. If removed, WCF will infer an appropriate identity automatically.
 -->
 < identity>
 < dns value="localhost"/>
 < /identity>
 < /endpoint>
 < !-- Metadata Endpoints -->
 < !-- The Metadata Exchange endpoint is used by the service to describe itself to clients. -->
 < !-- This endpoint does not use a secure binding and should be secured or removed before deployment -->
 < endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/>
 < /service>
 < /services>
 < behaviors>
 < serviceBehaviors>
 < behavior name="testWcfService.Service1Behavior">
 < !-- To avoid disclosing metadata information, set the value below to false and remove the metadata endpoint above before deployment -->
 < serviceMetadata httpGetEnabled="True"/>
 < !-- To receive exception details in faults for debugging purposes, set the value below to true. Set to false before deployment to avoid disclosing exception information -->
 < serviceDebug includeExceptionDetailInFaults="True" />
 < /behavior>
 < /serviceBehaviors>
 < /behaviors>
 < /system.serviceModel>"
客户端可以用Visual Studio里加Service Reference生成,也可以用svcutil.exe命令行程序生成,除了代理类之外,还会生成配置文件:
< system.serviceModel>
 < bindings>
 < wsHttpBinding>
 < binding name="WSHttpBinding_IService1" closeTimeout="00:01:00"
 openTimeout="00:01:00" receiveTimeout="00:10:00" sendTimeout="00:01:00"
 bypassProxyOnLocal="false" transactionFlow="false" hostNameComparisonMode="StrongWildcard"
 maxBufferPoolSize="524288" maxReceivedMessageSize="65536"
 messageEncoding="Text" textEncoding="utf-8" useDefaultWebProxy="true"
 allowCookies="false">
 < readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="16384"
 maxBytesPerRead="4096" maxNameTableCharCount="16384" />
 < reliableSession ordered="true" inactivityTimeout="00:10:00"
 enabled="false" />
 < security mode="Message">
 < transport clientCredentialType="Windows" proxyCredentialType="None"
 realm="" />
 < message clientCredentialType="Windows" negotiateServiceCredential="true"
 algorithmSuite="Default" establishSecurityContext="true" />
 < /security>
 < /binding>
 < /wsHttpBinding>
 < /bindings>
 < client>
 < endpoint address="http://localhost:8732/testWcfService/Service1/"
 binding="wsHttpBinding" bindingConfiguration="WSHttpBinding_IService1"
 contract="ServiceReference1.IService1" name="WSHttpBinding_IService1">
 < identity>
 < dns value="localhost" />
 < /identity>
 < /endpoint>
 < /client>
 < /system.serviceModel>
这里最重要的是Endpoint,里面包括了连接所用的地址(EndpointAddress),连接协议(Binding),代理类(接口)名(ServiceReference1.IService1)等.

4)代理类的代码生成
要用到System.ServiceModel和System.ServiceModel.Description命名空间,下面是稍加改写的MSDN上的代码:
 ArrayList endPoints = new ArrayList();
 MetadataExchangeClient mexClient = new MetadataExchangeClient(new EndpointAddress(serviceUri));//serviceUri参数是WCF Web Service Metadata的地址,在服务端的配置文件里定义,一般为Endpoint的地址加上/mex
 mexClient.ResolveMetadataReferences = true;
 MetadataSet metaDocs = mexClient.GetMetadata();
 WsdlImporter importer = new WsdlImporter(metaDocs);
 ServiceContractGenerator contractGenerator = new ServiceContractGenerator();

 Collection contracts = importer.ImportAllContracts();
 foreach (ServiceEndpoint endPoint in importer.ImportAllEndpoints())
 {

 endPoints.Add(endPoint);//将Endpoint存起来供以后调用方法时用
 }
 foreach (ContractDescription contract in contracts)
 {
 contractGenerator.GenerateServiceContractType(contract);
 }
 if (contractGenerator.Errors.Count != 0)
 {
 return "生成代码出错!";
 }
可以将生成的代码写到文件里,如MSDN示范的那样:
 CodeDomProvider provider = CodeDomProvider.CreateProvider("C#");
 CodeGeneratorOptions options = new CodeGeneratorOptions();
 IndentedTextWriter textWriter = new IndentedTextWriter(new StreamWriter(proxyClass));
 provider.GenerateCodeFromCompileUnit(contractGenerator.TargetCompileUnit, textWriter, options);
 textWriter.Close();
如果要编译,需要注意WCF是基于.Net Framework 3.0以后版本的,但是CodeDom到今天还只有2.0版本,所以编译时依赖的System.ServiceModel.dll和System.Runtime.Serialization.dll需要特别指定路径:
 CompilerParameters compileoptions = new CompilerParameters();
 compileoptions.ReferencedAssemblies.Add("System.dll");
 compileoptions.CompilerOptions = "/lib:/"" + Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles)
 + "//Reference Assemblies//Microsoft//Framework//v3.0/" ";//这个路径下包括了System.ServiceModel.dll和System.Runtime.Serialization.dll
另外,在Framework 2.0里,用的是ICodeCompiler,3.0里提示这个方法已经过时了,直接用上面生成的provider就可以了:
 compileoptions.OutputAssembly = proxyAssembly;
 compileoptions.IncludeDebugInformation = false;
 compileoptions.GenerateInMemory = false;
 compileoptions.GenerateExecutable = false;
 CodeCompileUnit[] ccus = new CodeCompileUnit[1];
 ccus[0] = contractGenerator.TargetCompileUnit;
 CompilerResults results = provider.CompileAssemblyFromDom(compileoptions, ccus);
 foreach (CompilerError err in results.Errors)
 {//下面的语句有助于调试
 string t = err.ErrorText;
 int s = err.Line;
 }
这里没有生成客户端的配置文件,但所需信息其实已经用上面的ImportAllEndpoints和mportAllContracts方法取出来了,不难利用这些信息生成配置文件,或者存在某个数据结构里,动态调用.

5)利用Reflection调用代理类:
下面是一个例子:
public static object InvokeWcfMethod(string proxyAssembly, string methodName, object[] parameters, ArrayList endPoints)//就是上面生成的Endpoints
 object returnValue = null;
 object serviceObject = null;
 Assembly assembly = Assembly.LoadFile(proxyAssembly);

 foreach (Type type in assembly.GetTypes())
 {
 if (type.IsInterface || !(type.Namespace == null))
 {
 continue;
 }

 if (endPoints != null && endPoints.Count > 0)
 {
 foreach (ServiceEndpoint endPoint in endPoints)
 {
 bool isFinished = false;
 Type contractType = assembly.GetType(endPoint.Contract.Name);

 if (contractType != null)
 {
 foreach (Type interfaceType in type.GetInterfaces())
 {
 if (interfaceType == contractType)
 {
 serviceObject = Convert.ChangeType(Activator.CreateInstance(type, new object[]{endPoint.Binding, endPoint.Address}), type);
 isFinished = true;
 break;
 }

 }
 }
 if (isFinished)
 {
 break;
 }
 }
 }

 foreach (MethodInfo method in type.GetMethods(
 BindingFlags.Public | BindingFlags.DeclaredOnly
 | BindingFlags.Instance))
 {

 if (methodName == method.Name)
 {
 try
 {
 returnValue = method.Invoke(serviceObject, parameters);
 }
 catch (Exception ex)
 {
 throw ex;
 }
 }
 }

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值