七、高级功能

这是本书的最后一章,在这一章中我们将探索 WCF 和 Web API 中的一些高级概念。

在本章中,我们将涵盖以下几点:

  • 使用 WCF 服务的最佳实践
  • 使用 ASP 的最佳实践。 净 Web API

WCF 的最佳实践

Windows Communication Foundation(WCF)是统一的编程模型,用于构建面向服务的应用。 WCF 提供了一个强大的框架来设计、构建、配置和部署基于SOA的应用,其中 SOA 代表面向服务的架构

微软在 2006 年发布了 WCF,最初的代号为Indigo,作为。net Framework 3.0 的一部分。

WCF 使开发人员能够构建安全、可靠和事务性的解决方案,这些解决方案可以跨不同平台集成,并与现有投资提供高度的互操作性。

WCF 的核心理念可以归结为以下三个关键概念,即 ABC:

  • 地址
  • 绑定
  • 合同

在本节中,我们将探索使用 WCF 创建应用时需要考虑的最佳实践。

WCF 安全问题

在本节中,我们将探索如何为我们的 WCF 服务实现健壮的安全性。 我们将从对 WCF 绑定的简要介绍开始讨论。

绑定

绑定用于指定传输通道(HTTP、TCP、管道和消息队列)和协议(安全性、可靠性和事务流)。 绑定由绑定元素组成,还包括消息编码元素(文本/XML、MTOM 和二进制)。 这些绑定元素表示端点如何与服务消费者通信。 WCF 支持 9 个内置绑定。 一个绑定必须包含至少一个传输绑定元素、一个编码绑定元素和一个或多个其他传输协议绑定,例如安全性和可靠性。 注意,需要在服务器和客户端中指定的绑定信息是不同的; 也就是说,您必须在 WCF 服务的配置文件中以及在 WCF 服务客户端的配置文件中指定绑定信息。

在 WCF 中,WCF 配置方案的三个主要部分是 ServiceModel、bindings 和 services。

本质上,绑定是端点的一个属性,您可以使用它来配置服务的传输协议、编码和安全规范。 现在,我应该在什么时候使用哪个绑定? 以下是经验法则:

  • WsHttpBinding:如果您需要通过 Internet 公开您的服务,您可以使用这种类型的绑定。
  • basicHttpBinding:如果您需要向旧客户端(如 ASMX web 服务)公开 WCF 服务,您应该选择这种类型的绑定。 WsHttpBindingbasicHttpBinding之间的主要区别之一是消息安全性。
  • WsFederationHttpBinding:这是一种特殊类型的 WS 绑定,它提供了对联邦安全性的支持。
  • NetTcpBinding:如果需要在内部网中支持 WCF 客户端,您可以使用这种类型的绑定。 这是可用的最优化和最快的绑定,并支持可靠性、事务和安全性。 NetTcpBinding提供对 TCP 协议和二进制以及编码方法的支持。
  • NetPeerTcpBinding:这个绑定提供了对netTcpBinding特性的支持,并且对于使用 WCF 服务的点对点环境来说更加安全。
  • netNamedPipeBinding:如果您需要在同一台机器上支持 WCF 客户端,那么这种类型的绑定是一个很好的选择。
  • netMsmqBinding:需要支持未连接排队呼叫时,可以选择该绑定类型。
  • wsDualHttpBinding:如果您想为服务和客户端之间的双向通信提供支持,您可以选择这种类型的绑定。 这种结合具有WsHttpBinding的所有特征; 此外,它还支持双工消息交换模式(MEP)。

下表提供了 WCF 中绑定的比较:

|

绑定

|

配置

|

Protocol /运输

|

安全

|

事务

|

双工

| | --- | --- | --- | --- | --- | --- | | BasicHttpBinding | 基本概要 1.1 | 使用 HTTP / HTTPS | 没有一个 | … | … | | WSHttpBinding | WS | HTTP、HTTPS、TCP | 消息 | 是的 | … | | WSDualHttpBinding | WS | HTTP, HTTPS | 消息 | 是的 | 是的 | | netttcpbinding | net | 命名管道 | 运输 | 是的 | 是的 | | NetNamedPipeBinding | net | 命名管道 | 运输 | 是的 | 是的 | | NetMsmqBinding | net | MSMQ | 运输 | 是的 | 没有 | | WSFederationHttpBinding | ws - federation | HTTP, HTTPS | 消息 | 是的 | 没有 | | NetPeerTcpBinding | 同行 | TCP | 运输 | … | 是的 | | MsmqIntegrationBinding | MSMQ | MSMQ | 运输 | 是的 | … |

注意事项

您还可以使用Custom绑定,它允许使用不同绑定元素的组合创建自定义绑定。

WCF 安全性

当您使用 WCF 服务时,数据和信息的机密性和完整性至关重要。 您创建服务操作,然后将它们公开给外部世界。 有多种方法可以保护你的 WCF 服务,如下:

  • 使用身份验证
  • 使用授权
  • 使用证书
  • 使用传输级别安全性
  • 使用消息级安全性
  • 使用的口令安全

此外,您可以在两个级别上在 WCF 中提供安全性。 您可以在传输级别或消息级别提供安全性。 这两种级别各有利弊。

传输安全性依赖于传输。 它提供互操作性和改进的性能,当您发送的消息通过中间系统路由,并且服务和客户机都位于内部网中时,应该使用它。 然而,与消息安全性相比,传输安全性提供的凭证支持最少。

消息级安全性

在消息级安全性中,用户的凭据被封装在服务器和客户机之间传递的消息中。 消息安全性适用于需要将消息转发到其他 WCF 服务或通过中间系统路由的情况。 但是,由于加密和签名每条消息所需的开销,消息安全性与传输安全性相比要慢一些。 另外,消息安全性不支持与旧 ASMX 客户机的互操作性。 消息安全支持的证书类型为 Windows、无、证书、用户名和令牌。

下面的代码片段演示了如何通过使用wsHttpBinding绑定保护消息来实现消息级安全性:

<wsHttpBinding>
  <binding name = "wsHttp">
    <security mode = "Message">
      <message clientCredentialType = "UserToken"/>
    </security>
  </binding>
</wsHttpBinding>

如果你在消息安全模式下使用证书安全,下面是如何保护你的 WCF 服务:

<bindings>
  <wsHttpBinding>
    <binding name="wsHttpEndpointBinding">
      <security>      
        <message clientCredentialType="Certificate" />
      </security>
    </binding>
  </wsHttpBinding>
</bindings>

注意事项

要在接口或操作级别上指定消息安全保护级别,必须使用[ServiceContract(ProtectionLevel)]属性并设置保护级别。 您可以从任何支持的保护级别中进行选择,即NoneSignEncryptAndSign

通过以下步骤在 WCF 4.5 中实现消息级安全:

  1. 使用makecert.exe工具为服务器和客户机(服务提供者和服务使用者)创建证书。
  2. 使用 Visual Studio 2013 IDE 创建一个 WCF 应用。
  3. 在服务器的配置文件中指定必要的绑定行为,如下所示:

    ```

    ```

  4. 创建 WCF 客户端应用,并配置客户端证书凭据,代码如下:

    <behaviors> <endpointBehaviors> <behavior name="CustomBehavior"> <clientCredentials> <clientCertificate findValue="DemoWCFClient" x509FindType="FindBySubjectName" storeLocation="CurrentUser" storeName="My" /> <serviceCertificate> <authentication certificateValidationMode="PeerTrust"/> </serviceCertificate> </clientCredentials> </behavior> </endpointBehaviors> </behaviors> <client> <endpoint address="http://localhost:1234/DemoService.svc" binding="wsHttpBinding" bindingConfiguration="WSEndpoint" contract="Packt.Services.IDemoService" name="WSEndpoint" behaviorConfiguration="CustomBehavior"> <identity> <dns value="DemoWCFServer"/> </identity> </endpoint> </client>

你完成了!

使用 FaultContract 属性

下面的代码片段创建了一个将保护级别设置为Sign的接口:

[ServiceContract(ProtectionLevel=ProtectionLevel.Sign]
  public interface ISecurityService
  {
    [OperationContract]
    [FaultContract(typeof(FaultDetails))]
    UserLoginHistory GetUserLoginHistory(Int32 userID);       
  }

类用于指定一个或多个 SOAP 默认值,当对服务方法的调用在运行时遇到错误时返回这些默认值。 下面的代码片段指定了一个将保护级别设置为Sign的操作:

[OperationContract(ProtectionLevel=ProtectionLevel.Sign]

服务方法返回用户的登录历史详细信息,将其用户 ID 作为参数传递。

类实现了ISecurityService接口(服务契约),并定义了getUserLoginInformation方法(操作契约)。 下面的代码片段展示了SecurityService类的外观:

public class SecurityService : ISecurityService
  {
    public UserLoginHistory GetUserLoginHistory(Int32 userID)
    {
      try
      {
        return new UserLoginHistory { UserID = userID };
      }
      catch 
      { 
        FaultDetails faultObject = new FaultDetails();
        faultObject.FaultID = 1;
        faultObject.FaultMessage = "The User ID you entered is invalid...";

        throw new FaultException<FaultDetails> (faultObject, new FaultReason(faultObject.FaultMessage)); 
      }    
    }
  }

正如您可以在前面的代码片段中看到的,GetUserLoginHistory操作契约返回一个UserLoginHistory的实例。 我跳过了检索作为参数传递的用户 ID 的用户登录历史的代码。

我们需要两个消息契约,一个用于存储用户的登录历史,另一个用于错误异常发生时的错误详细信息。 MessageBodyMemberAttribute类属于。net Framework 4.5,用于指定将一个成员序列化为 SOAP 主体中的一个元素。

下面的代码片段是我们需要使用的两个消息契约的代码:

[MessageContract]
public class UserLoginHistory
{
  [MessageBodyMemberAttribute(Order = 1, ProtectionLevel = ProtectionLevel.None)]
  public Int32 UserID { get; set; }

  [MessageBodyMemberAttribute(Order = 2, ProtectionLevel = ProtectionLevel.Sign)]
  public DateTime LoginTime { get; set; }

  [MessageBodyMemberAttribute(Order = 2, ProtectionLevel = ProtectionLevel.EncryptAndSign)]
  public DateTime Password { get; set; }
}

[MessageContract]
public class FaultDetails
{
  [MessageBodyMemberAttribute(Order = 1, ProtectionLevel = ProtectionLevel.None)]
  public Int32 FaultID
  {
    get;
    set;
  }

  [MessageBodyMemberAttribute(Order = 2, ProtectionLevel = ProtectionLevel.None)]
  public string FaultMessage
  {
    get;
    set;
  }
}

添加服务引用后,您可以使用以下代码从客户端应用中使用该服务:

try
{
  ChannelFactory<ISecurityService> factory =
  new ChannelFactory<ISecurityService>("WSHttpBinding_ISecurityService",
  new EndpointAddress("http://localhost/Packt/SecurityService.svc"));

  ISecurityService proxy = factory.CreateChannel();
  UserLoginHistory userLoginHistoryObj = proxy.GetUserLoginHistory(3);
}
catch (FaultException<FaultDetails> faultExceptionInstance)
{
  //Some code
}

注意事项

您可以通过编程方式关闭 WCF 中的安全性。 一个例子如下:

bindingObject.Security.Mode = SecurityMode.None;

传输级安全性

与消息安全性相比,传输安全性的工作速度要快得多,而且传输级别的安全性是协议独立的。

在传输安全模式下实现basicHttpBindingWsHttpBinding绑定时,可用的客户端凭据类型包括:

  • :无安全性; 安全性被关闭
  • Basic:基本认证只适用于 HTTP 协议。 在这里,根据活动目录对客户机进行身份验证
  • 摘要:这种类型的身份验证类似于 Basic,但在此选项中,凭据以散列形式发送,而不是以明文形式发送
  • NTLM:这也只适用于 HTTP 协议,并且客户端使用 Windows 帐户进行身份验证
  • Windows:在此选项中,使用 Windows 令牌对活动目录进行身份验证
  • Certificate:使用服务证书进行认证,使用 HTTP 协议时使用 SSL 证书进行认证

实现传输级安全性

下面的代码片段演示了如何使用以下代码实现传输安全性:

NetTcpBinding netTcpBinding = new NetTcpBinding(SecurityMode.TransportWithMessageCredential);
netTcpBinding.Security.Transport.ClientCredentialType = TcpClientCredentialType.Windows;
netTcpBinding.Security.Message.ClientCredentialType = MessageCredentialType.Certificate;
Uri adddress = new Uri("net.tcp://Tcp");
ServiceHost serviceHost = new ServiceHost(typeof(SecurityService), adddress);
serviceHost.Credentials.ServiceCertificate.SetCertificate(StoreLocation.LocalMachine, StoreName.My,X509FindType.FindByIssuerName, "Contoso.com"); 
serviceHost.AddServiceEndpoint(typeof(ISecurityService), b, "SecurityService");
serviceHost.Open();
Console.WriteLine("Service Started..........");
Console.Read0Line();

注意事项

注意,在默认情况下,netTcpBinding使用传输安全性。 这意味着您应该将客户端凭据配置为使用证书安全性。

要通过配置实现传输级安全性,需要在配置文件中指定安全模式,如下代码片段所示:

<bindings>
  <wsHttpBinding>
    <binding name="TransportSecurity">
      <security mode="Transport">
        <transport clientCredentialType="None"/>
      </security>
    </binding>
  </wsHttpBinding>
</bindings>

提示

要在 Windows 中使用netTcpBinding以提高传输安全性,您可以使用以下代码:

<bindings>
  <netTcpBinding>
    <binding name="PacktTcpBinding">
      <security mode="TransportWithMessageCredential" >
        <transport clientCredentialType="Windows" />
        <message clientCredentialType="Certificate" />
      </security>
    </binding>
  </netTcpBinding>
</bindings>

如果您使用 HTTPS 协议,您应该将服务行为上的httpGetEnabled更改为httpsGetEnabled,如下代码片段所示:

<behaviors>
  <serviceBehaviors>
    <behavior name="Packt.SecureService.SecurityServiceBehavior">
      <!-- To avoid disclosing metadata information, set the value below to false and remove the metadata endpoint above before deployment -->
      <serviceMetadata httpsGetEnabled="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="false"/>
    </behavior>
  </serviceBehaviors>
</behaviors>

下一个步骤是指定支持安全通信的端点。 请参考以下代码:

<services>
  <service name="Packt.SecureService.SecurityService" behaviorConfiguration="Packt.SecureService.SecurityServiceBehavior" >
    <!-- Service Endpoints -->
    <endpoint address="http://localhost/Packt/SecurityService.svc" binding="wsHttpBinding" bindingConfiguration="TransportSecurity" contract="Packt.SecureService.ISecurityService"/>
    <endpoint address="mex" binding="mexHttpsBinding" contract="IMetadataExchange"/>
  </service>
</services>

现在您应该在 IIS 中托管您的服务。 要做到这一点,右键单击解决方案资源管理器窗口中的服务项目,在Web选项卡中,选择使用本地 IIS Web 服务器单选按钮,如下图所示:

Implementing transport-level security

你还应该通过点击create virtual directory按钮来创建一个虚拟目录,如上面的截图所示。 下一个屏幕是这样的:

Implementing transport-level security

注意事项

注意,为了从 Visual Studio IDE 在 IIS 中托管一个服务,你应该在管理员模式下打开 Visual Studio 2012 IDE。

您可以进入“Internet Services Manager”窗口,将 SSL 证书关联到网站。 您还应该为您的网站启用 SSL 绑定。

使用 WCF 服务的最佳实践

在使用 WCF 服务时,你应该记住以下几点:

  • 不要在using语句中放置代理。
  • 使用FaultExceptions类来处理服务异常。 您应该使用FaultContracts类向服务使用者返回错误信息。
  • 使用消息日志记录服务操作。
  • 使用每个调用实例模型总是更好的。
  • 使用 WCF 工具,如SvcUtil.exeSvcConfigEditor.exeSvcTraceViewer.exe
  • 您应该保护日志文件不受未经授权的访问,并且日志文件不应该包含敏感信息。
  • 使用适当的身份验证机制对服务使用者进行身份验证。
  • 使用字符串密码,并保护对凭据存储区的访问。
  • 使用 IIS 来托管您的服务,除非您想使用 IIS 不支持的传输协议。
  • 在服务器端验证输入参数,不要只依赖于客户端验证。
  • 定义可维护的服务和数据契约版本控制。
  • 明确定义名称空间以避免冲突。
  • 加密包含敏感数据的配置部分。
  • 您应该通过配置管理绑定和端点信息,而不是通过代码。
  • 在类库中定义服务,而不是直接在宿主项目中定义服务。
  • 在服务契约定义中包含FaultContract属性。
  • 使用静态代理类代替ChannelFactory类。
  • 如果必须频繁调用服务方法,请使用缓存来存储客户端代理。
  • 使用 X509 证书而不是 NTLM 证书。
  • 只有在使用传输或消息级安全保护元数据交换端点之后,才应该发布元数据。
  • 您应该更喜欢数据契约而不是可序列化类型。
  • 尽可能使用 WAS 托管和 IIS 托管外部仅 http 服务。
  • 您可以使用协议缓冲区 WCF 服务来获得更好的性能。 您可以通过http://www.developer.com/net/net/working-with-protobuf-services-in-.net.html了解更多关于协议缓冲区的信息,以及如何在 WCF 服务中使用它们。

使用 ASP 的最佳实践。 净 Web API

ASP.NET Web API 是在。NET 框架上构建 RESTful 应用的理想平台。 ASP.NET Web API 是一个可以用来构建Http服务的框架,无论REST还是RPC——它是微软对 RFC 的最好实现。 它允许 IIS 和自托管,并且是异步的。 Web API 是灵活的,并提供了对关注点分离的支持。 它使您能够直接通过 HTTP 协议向 Web 公开应用、数据和服务。 ASP.NET Web API 依赖于基本的协议和格式,如 HTTP、WebSockets、SSL、JQuery、JSON 和 XML。 不支持更高级别的协议,如可靠消息传递或事务。

下面快速浏览一下使用 Web API 时可以遵循的最佳实践和技巧:

  • 你应该使用一个自定义的基础 WebApiController,在那里你可以抽象控制器的特性和行为
  • 使用 URL 帮助器来过滤所有的图像 URL
  • 总是安装MvcRoutingShim插件以避免多个 HTTP 模块的微妙和混乱行为
  • 建议为每个资源创建一个单独的控制器

下面的代码是对我们在本书前面创建的 Web API 的 Base API 控制器的快速查看。 BaseApiController类扩展ApiController类,实现IBaseApiController接口。

public interface IBaseApiController : IDisposable
{
  Int32 ID { get; set; }        
}

public class BaseApiController<T> : ApiController where T : class, IBaseApiController
{
  BaseRepository<SecurityEntities> repository = null;
  protected string[] includesArray { get; set; }
  public BaseApiController()
  {
    repository = new BaseRepository<SecurityEntities>("SecurityEntities");
  }

  public virtual IEnumerable<T> Get()
  {
    return repository.GetData<T>(includesArray);
  }

  public virtual T Get(Int32 id)
  {
    return repository.SearchData<T>(t => t.ID == id, includesArray);
  }

  public virtual Int32 Post([FromBody]T value)
  {
    return repository.EditData<T>(value);
  }

  public virtual Int32 Put([FromBody]T value)
  {
    return repository.CreateData<T>(value);
  }

  public virtual Int32 Delete([FromBody]T value)
  {
    return repository.RemoveData<T>(value);
  }
}

参考文献

http://msdn.microsoft.com/en-us/magazine/cc163394.aspx

http://msdn.microsoft.com/en-us/library/ms732362.aspx

http://msdn.microsoft.com/en-us/library/ms733099.aspx

http://msdn.microsoft.com/en-us/magazine/cc163382.aspx

http://msdn.microsoft.com/en-us/library/ff405740.aspx

http://www.codemag.com/article/0611051

http://wcf.codeplex.com/wikipage?title=WCF%20HTTP

总结

WCF 为在一个保护伞下统一许多技术提供了一个平台。 它可以用于设计和实现独立于平台的、可扩展的和可伸缩的服务。 ASP.NET Web API 是一个轻量级的基于 Web 的框架,它使用 HTTP 作为应用协议。 在本章中,我们讨论了 WCF 服务和 Web API 可以采用的最佳实践,以增强安全性、可伸缩性和性能。