好久没写过博客了,以往写博客都是直接在网页上写的,效率比较低。今天恰好研究了下Windows Live Writer,写博客要方便了不少。因此打算将这一年来研究的部分红果发一发。数据库
这一段时间主要是研究了服务端开发的框架,包括Web Service、Web API以及WCF,经过VS实现Web Service是最容易的,适合轻量级的Web服务,Web API由于以前用了好久的Asp.net MVC,因此学起来很快,WCF难度最大,框架也较为复杂。在学习使用WCF的过程当中趟过了不少坑,经过今天这个单服务多契约接口及用户名密码认证的实例来给本身作个背书吧。安全
以下图所示,我构建了一个名为“ywt.WcfService”的解决方案,并在其下创建了四个项目:框架
各项目的名称及功能描述以下表:async
名称 | 类型 | 功能描述 |
ywt.WcfService.Interfaces | 类库项目 | 包含全部的WcfService契约接,为简单期间,我仅作了2个契约接口 |
ywt.WcfService.SelfHost | 控制台应用程序项目 | 对WcfService服务进行自寄宿,相关的服务配置都在该项目的App.Config文件中 |
ywt.WcfService.Service | 类库项目 | 服务实现代码 |
ywt.WcfService.WinFormClient | WinForm应用程序项目 | Winform客户端程序 |
该项须要引用System.ServiceModel和System.Runtime.Serialization。包含2个接口的定义:ICalculator.cs、ILog.cs,作为演示,代码很是简单。代码分别以下所示:ide
ICalculator.csusing System; using System.Collections.Generic; using System.Linq; using System.ServiceModel; using System.Text; using System.Threading.Tasks; namespace ywt.WcfService.Interfaces { [ServiceContract] public interface ICalculator { [OperationContract] double Add(double param1, double param2); } }
ILog.cs学习
using System; using System.Collections.Generic; using System.Linq; using System.ServiceModel; using System.Text; using System.Threading.Tasks; namespace ywt.WcfService.Interfaces { [ServiceContract] public interface ILog { [OperationContract] string Log(string text); } }
该项须要引用System.ServiceModel和System.Runtime.Serialization,另外还须要引用ywt.WcfService.Interfaces接口项目。在该项目中须要实现接口项目中定义的全部接口,咱们能够经过一个服务来实现全部的接口。一个服务实现全部的接口时,可能会出现服务代码过于臃肿,不便于查看维护,咱们能够将咱们的服务实现类拆分红多个部分类,并为各个部分类的文件名(注意是文件名而不是类名)取一个对应的名称。spa
我作了2个类,文件名分别为CalculatorService.cs和LogService.cs,2个文件中分别用于实现不一样的接口。2个类文件的代码以下:操作系统
CalculatorService.cs.net
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Runtime.Serialization; using System.ServiceModel; using ywt.WcfService.Interfaces; namespace ywt.WcfService.Services { public partial class MyService : ICalculator { public double Add(double param1, double param2) { return param1+param2; } } }
LogService.cs3d
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using ywt.WcfService.Interfaces; namespace ywt.WcfService.Services { public partial class MyService : ILog { public string Log(string text) { return $"ServiceLog: {text}"; } } }
该项须要引用ywt.WcfService.Interfaces、ywt.WcfService.Service两个项目,以及System.ServiceModel、System.Runtime.Serialization、System.IdentityModel和System.IdentityModel.Selectors。我采用是消息安全模式,客户端认证方式是用户名密码,此时服务端必须设置证书,为了省去建立证书这一步,我直接使用服务端的本机localhost证书,该证书在服务端安装操做系统时自动建立。
根据以上状况,在本项目中须要作三件事情:
Program.cs
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Runtime.Serialization; using System.ServiceModel; using System.ServiceModel.Description; using System.Security.Cryptography.X509Certificates; using ywt.WcfService.Services; namespace ywt.WcfService.SeftHost { class Program { private static ServiceHost host; static void Main(string[] args) { Console.WriteLine("Wcf服务开始启动"); try { host = new ServiceHost(typeof(MyService)); ServiceCredentials scs = host.Description.Behaviors.Find<ServiceCredentials>(); if (scs == null) { scs = new ServiceCredentials(); host.Description.Behaviors.Add(scs); } scs.ServiceCertificate.SetCertificate("CN=localhost",StoreLocation.LocalMachine,StoreName.My); host.Open(); Console.WriteLine("Wcf服务启动成功"); Console.ReadKey(); } catch (Exception ex) { Console.WriteLine($"Wcf服务启动失败: {ex.Message}"); Console.ReadKey(); } finally { host.Close(); } } } }
MyUserNamePasswordValidator.cs
using System; using System.Collections.Generic; using System.IdentityModel.Selectors; using System.IdentityModel.Tokens; using System.Linq; using System.Text; using System.Threading.Tasks; namespace ywt.WcfService.SeftHost { public class MyUserNamePasswordValidator : UserNamePasswordValidator { public override void Validate(string userName, string password) { if (userName != "admin" || password != "admin") { throw new SecurityTokenValidationException("用户未得到受权"); } } } }
App.config
<?xml version="1.0" encoding="utf-8" ?> <configuration> <startup> <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" /> </startup> <system.serviceModel> <bindings> <wsHttpBinding> <binding name="msgUserNameHttp"> <security> <message clientCredentialType="UserName" negotiateServiceCredential="true"/> </security> </binding> </wsHttpBinding> </bindings> <services> <service behaviorConfiguration="behavior1" name="ywt.WcfService.Services.MyService"> <endpoint name="Calculator" address="" binding="wsHttpBinding" bindingConfiguration="msgUserNameHttp" contract="ywt.WcfService.Interfaces.ICalculator" /> <endpoint name="Log" address="" binding="wsHttpBinding" bindingConfiguration="msgUserNameHttp" contract="ywt.WcfService.Interfaces.ILog" /> <host> <baseAddresses> <add baseAddress="http://127.0.0.1:9876/MyService" /> </baseAddresses> </host> </service> </services> <behaviors> <serviceBehaviors> <behavior name="behavior1"> <serviceMetadata httpGetEnabled="true" httpGetUrl="http://127.0.0.1:9876/MyService/MEX" /> <serviceCredentials> <userNameAuthentication userNamePasswordValidationMode="Custom" customUserNamePasswordValidatorType="ywt.WcfService.SeftHost.MyUserNamePasswordValidator,ywt.WcfService.SeftHost"/> </serviceCredentials> </behavior> </serviceBehaviors> </behaviors> </system.serviceModel> </configuration>
该项须要引用System.ServiceModel、System.Runtime.Serialization,调用Wcf服务咱们采用引用代理。首先得将ywt.WcfService.SelfHost运行起来,注意不能直接在VS中直接调试运行,须要编译该项目后,找到生成的exe程序启动。随后咱们能够为当前的客户端添加服务引用:
在地址中录入正确的服务地址,而后点击转到,在服务列表框中咱们能够看到咱们的MyService,其下包含了2个咱们定义的接口。在命名空间中录入自定义的命名空间文本,假如在此处录入了WcfServices,那么实际最后完整的命名空间完整路径是:ywt.WcfService.WinFormClient.WcfServices。也就是说此处的命名空间不须要写成彻底的,VS会自动补全,将当前客户端项目的命名空间加在前面。
客户端仅添加了一个窗体,窗体上放置了2个文本框、2个Lable以及一个按钮控件。界面以下所示:
Form1.cs
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.ServiceModel.Security; using System.Text; using System.Threading.Tasks; using System.Windows.Forms; using ywt.WcfService.WinFormClient.WcfServices; namespace ywt.WcfService.WinFormClient { public partial class Form1 : Form { public Form1() { InitializeComponent(); } private async void button1_Click(object sender, EventArgs e) { CalculatorClient calculator = new CalculatorClient(); UserNamePasswordClientCredential credential = calculator.ClientCredentials.UserName; credential.UserName = "admin"; credential.Password = "admin"; LogClient log = new LogClient(); double p1, p2; double.TryParse(textBox1.Text, out p1); double.TryParse(textBox2.Text, out p2); p1=await calculator.AddAsync(p1, p2); label1.Text= p1.ToString(); credential = log.ClientCredentials.UserName; credential.UserName = "admin"; credential.Password = "admin"; label2.Text= await log.LogAsync(label1.Text); } } }
App.config
<?xml version="1.0" encoding="utf-8" ?> <configuration> <startup> <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" /> </startup> <system.serviceModel> <behaviors> <endpointBehaviors> <behavior name="NewBehavior0"> <clientCredentials> <serviceCertificate> <authentication certificateValidationMode="None" revocationMode="NoCheck" /> </serviceCertificate> </clientCredentials> </behavior> </endpointBehaviors> </behaviors> <bindings> <wsHttpBinding> <binding name="Calculator"> <security> <message clientCredentialType="UserName" /> </security> </binding> <binding name="Log"> <security> <message clientCredentialType="UserName" /> </security> </binding> </wsHttpBinding> </bindings> <client> <endpoint address="http://127.0.0.1:9876/MyService" binding="wsHttpBinding" bindingConfiguration="Calculator" contract="WcfServices.ICalculator" behaviorConfiguration="NewBehavior0" name="Calculator"> <identity> <certificate encodedValue="AwAAAAEAAAAUAAAAemLPWHcq5CeL/jln/1OjQSeKL/QgAAAAAQAAAPACAAAwggLsMIIB1KADAgECAhAdf5gB+4wxqEgNTFZJZ+SUMA0GCSqGSIb3DQEBCwUAMBQxEjAQBgNVBAMTCWxvY2FsaG9zdDAeFw0xODA0MTkxMzIzMTZaFw0yMzA0MTkwMDAwMDBaMBQxEjAQBgNVBAMTCWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK1T6iuo0M8fsCuazkDkD/sPPICeJrabdoq1B/0/xIKSNV+IFPnncPdefefh6ZOdyaPXN9yF+/v6GIveLEseQ8w0oXC5l+Eyl7kXTc3xzysLaKL/rFtjUH91+6qKjE9un5C+bVp884zQnOKhqDXxiqn6Aoem2kjAWbo0244weA2VE5kQZHAEsd2PrZpcy8gLptmtPc5Kqp1UuyVRmdTkmm2HZD3GQmgmASf5LUtgTAtcxLEjAQ4dtzyoBPnAL8meR6mgbj/JKOXutyY/QRxxfYun+sBDIJArL3tBnKQTBHJxCLuU8j0dSGYCfCyvaMNgXQWL1G4SjG9LAKQkj3c+LkcCAwEAAaM6MDgwCwYDVR0PBAQDAgSwMBMGA1UdJQQMMAoGCCsGAQUFBwMBMBQGA1UdEQQNMAuCCWxvY2FsaG9zdDANBgkqhkiG9w0BAQsFAAOCAQEAVPCHXEqNMnsZ5uCQ0y7iNvR9aQgZTGmWdn/c2GH39VqJ1bJpToTZm5SQeJYCLUW2f0bDq1JbLWSaRG8c9PREvYsIlOtrJdyhxplBOkdcs+zyx2BQC7tlCWaoDjGS1SEVAu48NrspktB6rh3KOjuoxcr5vWO1G76zYaSAQ2At/5+VIINxkg8/tk6JF3wEq63qdrRgVUCru0Yi0cVU0UViVPVWl61LrrERenRHT1YhldwwpPDQC38qLnE6YREQzEzEHEzoeBWU1dj65/X5b53v6B7jqm5cXhuAvZZMt8Kvo1HzWVwHDmOD3VMoEPR3aXCjXZ5WK9AHXsOrH3SKjPsXIQ==" /> </identity> </endpoint> <endpoint address="http://127.0.0.1:9876/MyService" binding="wsHttpBinding" bindingConfiguration="Log" contract="WcfServices.ILog" behaviorConfiguration="NewBehavior0" name="Log"> <identity> <certificate encodedValue="AwAAAAEAAAAUAAAAemLPWHcq5CeL/jln/1OjQSeKL/QgAAAAAQAAAPACAAAwggLsMIIB1KADAgECAhAdf5gB+4wxqEgNTFZJZ+SUMA0GCSqGSIb3DQEBCwUAMBQxEjAQBgNVBAMTCWxvY2FsaG9zdDAeFw0xODA0MTkxMzIzMTZaFw0yMzA0MTkwMDAwMDBaMBQxEjAQBgNVBAMTCWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK1T6iuo0M8fsCuazkDkD/sPPICeJrabdoq1B/0/xIKSNV+IFPnncPdefefh6ZOdyaPXN9yF+/v6GIveLEseQ8w0oXC5l+Eyl7kXTc3xzysLaKL/rFtjUH91+6qKjE9un5C+bVp884zQnOKhqDXxiqn6Aoem2kjAWbo0244weA2VE5kQZHAEsd2PrZpcy8gLptmtPc5Kqp1UuyVRmdTkmm2HZD3GQmgmASf5LUtgTAtcxLEjAQ4dtzyoBPnAL8meR6mgbj/JKOXutyY/QRxxfYun+sBDIJArL3tBnKQTBHJxCLuU8j0dSGYCfCyvaMNgXQWL1G4SjG9LAKQkj3c+LkcCAwEAAaM6MDgwCwYDVR0PBAQDAgSwMBMGA1UdJQQMMAoGCCsGAQUFBwMBMBQGA1UdEQQNMAuCCWxvY2FsaG9zdDANBgkqhkiG9w0BAQsFAAOCAQEAVPCHXEqNMnsZ5uCQ0y7iNvR9aQgZTGmWdn/c2GH39VqJ1bJpToTZm5SQeJYCLUW2f0bDq1JbLWSaRG8c9PREvYsIlOtrJdyhxplBOkdcs+zyx2BQC7tlCWaoDjGS1SEVAu48NrspktB6rh3KOjuoxcr5vWO1G76zYaSAQ2At/5+VIINxkg8/tk6JF3wEq63qdrRgVUCru0Yi0cVU0UViVPVWl61LrrERenRHT1YhldwwpPDQC38qLnE6YREQzEzEHEzoeBWU1dj65/X5b53v6B7jqm5cXhuAvZZMt8Kvo1HzWVwHDmOD3VMoEPR3aXCjXZ5WK9AHXsOrH3SKjPsXIQ==" /> </identity> </endpoint> </client> </system.serviceModel> </configuration>
经过以上客户端调用Wcf服务时能够看到,每当调用一个契约的本地代理时,都得传递用户名和密码,这实在是坑爹的办法,由于实际的项目中,一个服务中可能会须要实现无数个契约接口,若是每用一次契约就得传一次用户名密码,真是让人没法忍受。因此如今就有了一个新的问题,如何只需传递一次用户名和密码?网上有将用户名和密码写入SOAP消息头的作法,可是这种方法并不推荐。推荐的方式是作一个契约专门完成登陆退出,在该契约的Login方法中咱们为完成正确登陆的用户分发一个令牌(由服务端生成的具备必定时效的字符串),而后将该令牌写入SOAP消息头,随后客户端和服务端的通讯认证都由这个令牌来识别。这种模式我在Web Service、Web API里都搞过,可是WCF还没试过实现。因此也不提供代码了。