分类:
2010-07-13 16:42:38
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 = "")] [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 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" ehaviorConfiguration="testWcfService.Service1Behavior"> < host> < baseAddresses> < add baseAddress = "" /> < /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="" 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; } } }