流提供程序(WCF 数据服务)

数据服务可公开二进制大型对象数据。 此二进制数据可以表示视频和音频流、图像、文档文件或其他类型的二进制媒体。 当数据模型中的某个实体包括一个或多个二进制属性时,数据服务会在响应源的入口内以 base-64 编码形式返回此二进制数据。 由于以这种方式加载和序列化大型二进制数据会影响到性能,因此开放式数据协议 (OData) 定义了一种机制,该机制独立于二进制数据所属的实体来检索二进制数据。 这一点是通过将实体和二进制数据分隔到一个或多个数据流来实现的。

OData 协议支持下列两种机制用于将二进制数据作为与实体相关的流公开:

  • 媒体资源/媒体链接项

    Atom 发布协议 (AtomPub) 定义了一种机制,将二进制数据作为介质资源与数据源中的条目关联,该条目称为介质链接项。 只能有一个媒体资源为给定的媒体链接条目定义。 媒体资源可以被视为一个实体的默认流。 OData从 AtomPub 继承此流的行为。

  • 命名资源流

    从 OData 版本 3 开始,一个实体可以有多个相关的资源流,其可以按名称访问流。 这种机制不依赖 AtomPub,所以已命名资源流可用并且不是媒体链接条目。 媒体链接条目也可有命名流。 有关更多信息,请参见文章命名资源流

利用 WCF 数据服务,可通过实现流数据提供程序定义二进制资源流。 流提供程序实现以 Stream 对象的形式向数据服务提供与特定实体关联的流。

将数据服务配置为支持二进制数据流需要以下步骤:

  1. 具有相关资源流的数据模型中的实体归类。 每个数据提供程序具有其自己的要求,并且每种二进制资源流通过不同的机制在数据模型中定义。

  2. 实现以下流提供程序接口:

  3. 定义一个实现 IServiceProvider 接口的数据服务。 数据服务使用 GetService 实现访问流数据提供程序实现。 此方法返回适当的流提供程序实现。

  4. 在 Web 应用程序配置中启用大型消息流。

  5. 启用对服务器上或数据源中的二进制资源的访问。

本主题中的示例基于示例流照片服务,该服务会在数据服务流提供程序系列:实现流提供程序(第一部分)一文中进行深入讨论。 MSDN 代码库的流照片数据服务示例页上提供了此示例服务的源代码。

定义数据模型中的资源流

在数据模型中定义二进制资源流的方式取决于该流是媒体资源还是命名资源流,以及所使用的数据源提供程序。 下面的示例显示数据服务返回的元数据中媒体资源的定义,其中 PhotoInfo 是一个媒体链接条目,也有一个命名的资源流 (Thumbnail) 已定义:

<EntityType Name="PhotoInfo" m:HasStream="true">
  <Key>
    <PropertyRef Name="PhotoId" />
  </Key>
  <Property Name="PhotoId" Type="Edm.Int32" Nullable="false" 
            p9:StoreGeneratedPattern="Identity" 
            xmlns:p9="https://schemas.microsoft.com/ado/2009/02/edm/annotation" />
  <Property Name="FileName" Type="Edm.String" Nullable="false" />
  <Property Name="FileSize" Type="Edm.Int32" Nullable="true" />
  <Property Name="DateTaken" Type="Edm.DateTime" Nullable="true" />
  <Property Name="TakenBy" Type="Edm.String" Nullable="true" />
  <Property Name="DateAdded" Type="Edm.DateTime" Nullable="false" />
  <Property Name="Exposure" Type="PhotoData.Exposure" Nullable="false" />
  <Property Name="Dimensions" Type="PhotoData.Dimensions" Nullable="false" />
  <Property Name="DateModified" Type="Edm.DateTime" Nullable="false" />
  <Property Name="Comments" Type="Edm.String" Nullable="true" MaxLength="Max" 
            Unicode="true" FixedLength="false" />
  <Property Name="ContentType" Type="Edm.String" Nullable="true" MaxLength="50" 
            Unicode="true" FixedLength="false" />
  <Property Name="Thumbnail" Type="Edm.Stream" Nullable="false" />
</EntityType>

实体框架提供程序

当使用 实体框架 提供程序时,您通过以下方式之一定义一个数据模型本身中的二进制资源流中,具体取决于流的种类:

  • 媒体资源流:

    若要指示某个实体为具有相关媒体资源的媒体链接入口,需将 HasStream 特性添加到概念模型中的相应实体类型定义。 您还必须添加引用到命名空间 xmlns:m=https://schemas.microsoft.com/ado/2007/08/dataservices/metadata,添加到媒体链接条目或其在数据模型中的一个父元素。

  • 命名资源流:

    属于媒体链接条目的命名流定义为概念模型中类型 Stream 的属性。

    重要

    Stream 数据类型受 实体数据模型 (EDM) 支持(自版本 2.2 起)。然而,在 .NET Framework 4 中 实体框架 不支持此版本的 EDM。作为一种解决方法,可通过应用 NamedStreamAttribute 定义数据模型中的命名资源流为代表实体的数据模型对象层中的类。在该属性中,name 参数为流的名称。我们建议您向实体类添加此属性,方法是创建一个部分类定义,其保存在单独的代码文件中 ;否则通过 实体框架 重新生成实体数据类时,会改写此自定义。如同反射提供程序,数据服务在数据模型中生成所提供的 name(类型 Stream)的属性。

有关 公开媒体资源是通过使用 实体框架 提供程序,请参见文章数据服务流提供程序系列:实现流提供程序(第 1 部分)

反射提供程序

当使用反射提供程序时,您确定一个二进制资源流所属实体类型,方法是通过下列方法之一应用属性至类型为实体的类,具体取决于流类型:

  • 媒体资源流:

    应用 HasStreamAttribute 以定义属于类型的媒体资源(默认)流(其为媒体链接条目)。

  • 命名资源流:

    应用 NamedStreamAttribute 来定义一个命名的资源流,其属于实体类型,其中所提供的 name 参数是流的名称。

自定义数据服务提供程序

使用自定义服务提供程序时,可实现 IDataServiceMetadataProvider 接口来定义数据服务的元数据。 有关更多信息,请参见自定义数据服务提供程序(WCF 数据服务)。 您可以通过以下方式之一指示二进制资源流属于 ResourceType

实现流提供程序接口

若要创建支持二进制数据流的数据服务,必须至少实现 IDataServiceStreamProvider 接口。 有了此实现,数据服务能够以流的形式将二进制数据返回给客户端,并使用从客户端发送的流形式的二进制数据。 为支持命名的溪流,还必须实现 IDataServiceStreamProvider2 接口。 每当数据服务需要访问流形式的二进制数据时,都会创建一个相应接口实例。

IDataServiceStreamProvider

IDataServiceStreamProvider 接口指定以下成员:

成员名称

描述

DeleteStream

当删除媒体资源的媒体链接入口时,数据服务将调用此方法来删除相应媒体资源和命名流。 当实现 IDataServiceStreamProvider 时,此方法将包含会删除与所提供媒体链接入口关联的所有流二进制数据的代码。

GetReadStream

数据服务调用此方法来以流的形式返回媒体资源。 当实现 IDataServiceStreamProvider 时,此方法将包含提供流的代码,数据服务使用提供的流返回与所提供媒体链接入口关联的媒体资源。

GetReadStreamUri

数据服务调用此方法,来返回用于请求媒体链接入口的媒体资源的 URI。 使用此值可以在媒体链接入口的内容元素中创建 src 特性,也可以请求数据流。 当此方法返回 null 时,数据服务自动确定 URI。 需要向客户端提供不使用流提供程序直接访问二进制数据的权限时,使用此方法。

GetStreamContentType

数据服务调用此方法,来返回与指定媒体链接入口关联的媒体资源的 Content-Type 值。

GetStreamETag

数据服务调用此方法,来返回与指定实体关联的数据流的 eTag。 管理二进制数据的并发性时使用此方法。 如果此方法返回 null ,则数据服务不会跟踪并发。

GetWriteStream

数据服务调用此方法,来获取在接收从客户端发送的流时所使用的流。 当实现 IDataServiceStreamProvider 时,您必须返回一个可写入流,以便数据服务将接收到的流数据写入到其中。

ResolveType

返回一个命名空间限定的类型名称,该名称表示数据服务运行时必须为媒体链接入口创建的类型,该媒体链接入口与正在插入的媒体资源的数据流相关联。

IDataServiceStreamProvider2

IDataServiceStreamProvider2 接口指定下列成员,其过载 IDataServiceStreamProvider 的成员以采用为命名资源流的 ResourceProperty 参数:

成员名称

描述

GetReadStream(Object, ResourceProperty, String, Nullable<Boolean>, DataServiceOperationContext)

数据服务调用此方法来以返回命名流。 当实现 IDataServiceStreamProvider2 时,此方法将包含提供流的代码,数据服务使用提供的流返回与所提供媒体链接入口关联的所请求命名流。

GetReadStreamUri(Object, ResourceProperty, DataServiceOperationContext)

数据服务调用此方法,来返回用于请求媒体链接入口的特定命名流的 URI。 此值用于创建 atom:link 元素,它用于请求命名流。 当此方法返回 null 时,数据服务自动确定 URI。 需要向客户端提供不使用流提供程序直接访问二进制数据的权限时,使用此方法。

GetStreamContentType(Object, ResourceProperty, DataServiceOperationContext)

数据服务调用此方法,来返回与指定媒体链接入口关联的特定命名流的 Content-Type 值。

GetStreamETag(Object, ResourceProperty, DataServiceOperationContext)

数据服务调用此方法,来返回与指定实体关联的特定命名流的 eTag。 管理二进制数据的并发性时使用此方法。 如果此方法返回 null ,则数据服务不会跟踪并发。

GetWriteStream(Object, ResourceProperty, String, Nullable<Boolean>, DataServiceOperationContext)

数据服务调用此方法,来获取在接收从客户端发送的命名流时所使用的流。 当实现 IDataServiceStreamProvider2 时,您必须返回一个可写入流,以便数据服务将接收到的流数据写入到其中。

创建流数据服务

若要向 WCF 数据服务 运行时提供访问 IDataServiceStreamProviderIDataServiceStreamProvider2 实现的权限,所创建的数据服务还必须实现 IServiceProvider 接口。 下面的示例演示如何实现 GetService 方法,以返回实现 IDataServiceStreamProviderIDataServiceStreamProvider2 的 PhotoServiceStreamProvider 类的实例。

Partial Public Class PhotoData
    Inherits DataService(Of PhotoDataContainer)
    Implements IServiceProvider

    ' This method is called only once to initialize service-wide policies.
    Public Shared Sub InitializeService(ByVal config As DataServiceConfiguration)
        config.SetEntitySetAccessRule("PhotoInfo", _
            EntitySetRights.ReadMultiple Or _
            EntitySetRights.ReadSingle Or _
            EntitySetRights.AllWrite)

        ' Named streams require version 3 of the OData protocol.
        config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V3
    End Sub
#Region "IServiceProvider Members"
    Public Function GetService(ByVal serviceType As Type) As Object _
    Implements IServiceProvider.GetService
        If serviceType Is GetType(IDataServiceStreamProvider) _
            Or serviceType Is GetType(IDataServiceStreamProvider2) Then
            Return New PhotoServiceStreamProvider(Me.CurrentDataSource)
        End If
        Return Nothing
    End Function
#End Region
End Class
public partial class PhotoData : DataService<PhotoDataContainer>, IServiceProvider
{
    // This method is called only once to initialize service-wide policies.
    public static void InitializeService(DataServiceConfiguration config)
    {
        config.SetEntitySetAccessRule("PhotoInfo",
            EntitySetRights.ReadMultiple |
            EntitySetRights.ReadSingle |
            EntitySetRights.AllWrite);

        // Named resource streams require version 3 of the OData protocol.
        config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V3;
    }
    public object GetService(Type serviceType)
    {
        if (serviceType == typeof(IDataServiceStreamProvider2))
        {
            // Return the stream provider to the data service.
            return new PhotoServiceStreamProvider(this.CurrentDataSource);
        }

        return null;
    }
}

有关如何创建数据服务的一般信息,请参见配置数据服务(WCF 数据服务)

在宿主环境中启用大型二进制数据流

在 ASP.NET Web 应用程序中创建数据服务时,使用 Windows Communication Foundation (WCF) 可提供 HTTP 协议实现。默认情况下,WCF 将 HTTP 消息的大小限制为仅 65K 字节。 为了使大型二进制数据能够流入或流出数据服务,还必须将 Web 应用程序配置为启用大型二进制文件并使用流进行转换。 为此,请将以下内容添加到应用程序的 Web.config 文件的 <configuration /> 元素中:

   <system.serviceModel>
        <serviceHostingEnvironment aspNetCompatibilityEnabled="true"/>
        <services>
            <!-- The name of the service -->
            <service name="PhotoService.PhotoData">
                <!--you can leave the address blank or specify your end point URI-->
                <endpoint binding="webHttpBinding" bindingConfiguration="higherMessageSize" 
                  contract="System.Data.Services.IRequestHandler"></endpoint>
            </service>
        </services>
        <bindings>
            <webHttpBinding>
                <!-- configure the maxReceivedMessageSize value to suit the max size of 
   the request (in bytes) you want the service to receive-->
                <binding name="higherMessageSize" transferMode="Streamed"  
                 maxReceivedMessageSize="2147483647"/>
            </webHttpBinding>
        </bindings>
    </system.serviceModel>

备注

您必须使用 TransferMode.Streamed 传输模式,以确保由 WCF 对请求和响应消息中的二进制数据进行流式处理且不进行缓冲。

有关更多信息,请参见Streaming Message TransferTransport Quotas

默认情况下,Internet 信息服务 (IIS) 还将请求的大小限制为 4MB。 在 IIS 上运行时,若要允许您的数据服务接收超过 4MB 的流,则还必须在 <system.web /> 配置部分设置 httpRuntime Element 的 maxRequestLength 属性,如以下示例所示:

   <system.web>
        <!-- maxRequestLength (in KB): default=4000 (4MB); max size=2048MB. -->
        <httpRuntime maxRequestLength="2000000"/>
  </system.web>

在客户端应用程序中使用数据流

您可以直接通过使用以下 URI 之一访问二进制数据流:

  • 媒体资源流:

    https://localhost/PhotoService/PhotoData.svc/PhotoInfo(1)/$value
    
  • 命名资源流:

    https://localhost/PhotoService/PhotoData.svc/PhotoInfo(1)/Thumbnail
    

通过 WCF 数据服务 客户端库,您可以在客户端上以二进制数据流的形式检索和更新这些公开的资源。 有关更多信息,请参见使用二进制数据(WCF 数据服务)

使用流提供程序时的注意事项

以下是在实现流提供程序时以及从数据服务访问媒体资源时要考虑的一些事项。

  • 当支持媒体资源或命名资源流时,您必须实现 IDataServiceStreamProvider 接口。 为支持命名资源流,还必须实现 IDataServiceStreamProvider2 接口。

  • 当您定义属于媒体链接条目的命名资源流时,该数据模型中定义的实体不应包含具有与流名称相同的属性。

  • 对于媒体资源不支持 MERGE 请求。 使用 PUT 请求可更改现有实体的媒体资源。

  • POST 请求不能用于创建新的媒体链接入口。 相反,您必须发出一个 POST 请求来创建新的媒体资源,而数据服务器则会使用默认值创建新的媒体链接入口。 这个新实体可由后续的 MERGE 或 PUT 请求进行更新。 您可能还应考虑缓存该实体并在处置器中进行更新,例如将属性值设置为 POST 请求中的 Slug 标头的值。

  • 收到 POST 请求时,数据服务将先调用 GetWriteStream 来创建媒体资源,然后再调用 SaveChanges 来创建媒体链接入口。

  • GetWriteStream 的实现不应返回 MemoryStream 对象。 如果使用这种类型的流,则在服务接收到非常大的数据流时将会释放内存资源。

  • 以下是在数据库中存储媒体资源时应考虑的事项:

    • 数据模型中不应包括作为媒体资源的二进制属性。 数据模型中公开的所有属性都会在响应源的入口中返回。

    • 为了提高使用大型二进制数据流时的性能,建议您创建一个自定义流类来存储数据库中的二进制数据。 此类由 GetWriteStream 实现返回并将二进制数据分块区发送到数据库。 对于 SQL Server 数据库,当二进制数据大于 1MB 时,建议您使用 FILESTREAM 以流形式将数据传入数据库中。

    • 确保您的数据库设计为存储将由数据服务接收的大型二进制数据流。

    • 当客户端发送 POST 请求以便将媒体链接入口与媒体资源一起插入到单个请求时,将先调用 GetWriteStream 以获取流,然后数据服务才将新实体插入到数据库。 流提供程序实现必须能够处理此数据服务行为。 请考虑使用单独的数据表来存储二进制数据或者在文件中存储数据流,直至已在数据库中插入实体。

  • 当实现 DeleteStreamGetReadStreamGetWriteStream 方法时,您必须使用以方法参数的形式提供的 eTag 和 Content-Type 值。 不要在 IDataServiceStreamProvider 提供程序实现中设置 eTag 或 Content-Type 标头。

  • 默认情况下,客户端通过使用分块的 HTTP 传输编码发送大型二进制数据流。 由于 ASP.NET Development Server 不支持此类编码方式,因此您无法使用此 Web 服务器来承载必须接受大型二进制数据流的流数据服务。有关 ASP.NET Development Server 的更多信息,请参见 Web Servers in Visual Web Developer

版本控制要求

流提供程序具有以下 OData 协议版本控制要求:

  • 流提供程序要求数据服务支持 OData 协议的 2.0 版本以及更高版本。

  • 对命名流的支持要求客户端和数据服务都支持 OData 协议的 3.0 版本以及更高版本。

有关更多信息,请参见数据服务版本管理(WCF 数据服务)

请参阅

概念

数据服务提供程序 (WCF Data Services)

自定义数据服务提供程序(WCF 数据服务)

使用二进制数据(WCF 数据服务)