Streaming Provider (ADO.NET Data Services)
Note
This topic describes new functionality in ADO.NET Data Services that is available as an update to the .NET Framework version 3.5 Service Pack 1. You can download and install the update from the Microsoft Download Center.
When an entity in the data model exposes one or more binary properties, the data service returns this binary data encoded as base-64 inside the entry in the response feed. Because loading and serializing large binary data in this manner can affect performance, the Open Data Protocol (OData) defines a mechanism for retrieving binary data independently of the entity to which it belongs. This is accomplished by separating the entity and the binary data into the following parts:
Media resource - binary data that belongs to an entity, such as a video, audio, image or other media resources.
Media link entry - an entity that has a reference to a related media resource.
With the ADO.NET Data Services update to the .NET Framework 3.5 SP1 installed, ADO.NET Data Services accepts and returns media resources over HTTP as binary data of a specified MIME type. You can implement a streaming data provider that enables the data service to exposes binary data as a separate media resource instead of being part of the entity itself. The streaming provider implementation supplies the data service with the media resource for a specific entity as a Stream object.
Configuring a data service to support the streaming of binary data requires the following steps:
Attribute one or more entities in the data model as a media link entry. These entities should not include the binary data to be streamed. Any binary properties of an entity are always returned in the entry as base-64 encoded binary.
Define a data service that implements the IServiceProvider interface. The data service uses the GetService(Type) implementation to access your IDataServiceStreamProvider implementation.
Implement the IDataServiceStreamProvider interface.
Enable large message streams in the Web application configuration.
You can download a sample streaming data service from the WCF Data Services Documentation Samples page. This data service, based on the Northwind sample database, uses the Entity Framework provider together with direct database access by using System.Data.SqlClient to return bitmap images as a binary stream from the Employees table.
Defining a Media Link Entry in the Data Model
The data source provider determines the way that an entity is defined as a media link entry in the data model.
Entity Framework Provider
To indicate that an entity is a media link entry, add the HasStream attribute to the entity type definition in the conceptual model, as in the following example:<EntityType Name="Employees" m:HasStream="true"> <Key> <PropertyRef Name="EmployeeID" /> </Key> <Property Name="EmployeeID" Type="Edm.Int32" Nullable="false" /> <Property Name="LastName" Type="Edm.String" Nullable="false" MaxLength="20" Unicode="true" FixedLength="false" /> <Property Name="FirstName" Type="Edm.String" Nullable="false" MaxLength="10" Unicode="true" FixedLength="false" /> <Property Name="Title" Type="Edm.String" Nullable="true" MaxLength="30" Unicode="true" FixedLength="false" /> <Property Name="TitleOfCourtesy" Type="Edm.String" Nullable="true" MaxLength="25" Unicode="true" FixedLength="false" /> <Property Name="BirthDate" Type="Edm.DateTime" Nullable="true" /> <Property Name="HireDate" Type="Edm.DateTime" Nullable="true" /> <Property Name="Address" Type="Edm.String" Nullable="true" MaxLength="60" Unicode="true" FixedLength="false" /> <Property Name="City" Type="Edm.String" Nullable="true" MaxLength="15" Unicode="true" FixedLength="false" /> <Property Name="Region" Type="Edm.String" Nullable="true" MaxLength="15" Unicode="true" FixedLength="false" /> <Property Name="PostalCode" Type="Edm.String" Nullable="true" MaxLength="10" Unicode="true" FixedLength="false" /> <Property Name="Country" Type="Edm.String" Nullable="true" MaxLength="15" Unicode="true" FixedLength="false" /> <Property Name="HomePhone" Type="Edm.String" Nullable="true" MaxLength="24" Unicode="true" FixedLength="false" /> <Property Name="Extension" Type="Edm.String" Nullable="true" MaxLength="4" Unicode="true" FixedLength="false" /> <Property Name="Notes" Type="Edm.String" Nullable="true" MaxLength="Max" Unicode="true" FixedLength="false" /> <Property Name="PhotoPath" Type="Edm.String" Nullable="true" MaxLength="255" Unicode="true" FixedLength="false" /> <NavigationProperty Name="Employees1" Relationship="NorthwindModel.FK_Employees_Employees" FromRole="Employees" ToRole="Employees1" /> <NavigationProperty Name="Employees2" Relationship="NorthwindModel.FK_Employees_Employees" FromRole="Employees1" ToRole="Employees" /> </EntityType>
You must also add the namespace xmlns:m=https://schemas.microsoft.com/ado/2007/08/dataservices/metadata to the root of the .edmx or .csdl file that defines the data model.
Notice that in this example, the Employee entity definition in the conceptual model does not include the Photo property, which was explicitly removed from the model.
Reflection Provider
To indicate that an entity is a media link entry, add the HasStreamAttribute to the class that defines the entity type in the reflection provider.
Creating the Streaming Data Service
To be able to access an IDataServiceStreamProvider implementation, the data service that you create must implement the IServiceProvider interface. The following example shows how to implement the GetService(Type) method in the NorthwindStreaming data service to return an instance of the NorthwindStreamProvider class that implements IDataServiceStreamProvider.
Public Class NorthwindStreaming
Inherits DataService(Of NorthwindEntities)
Implements IServiceProvider
' This method is called only once to initialize service-wide policies.
Public Shared Sub InitializeService(ByVal config As DataServiceConfiguration)
config.SetEntitySetAccessRule("Employees", EntitySetRights.All)
config.UseVerboseErrors = True
End Sub
#Region "IServiceProvider Members"
Public Function GetService(ByVal serviceType As Type) As Object _
Implements IServiceProvider.GetService
If serviceType Is GetType(IDataServiceStreamProvider) Then
Return (New NorthwindStreamProvider(Me.CurrentDataSource))
End If
Return Nothing
End Function
#End Region
End Class
public class NorthwindStreaming : DataService<NorthwindEntities>, IServiceProvider
{
// This method is called only once to initialize service-wide policies.
public static void InitializeService(DataServiceConfiguration config)
{
config.SetEntitySetAccessRule("Employees", EntitySetRights.All);
}
#region IServiceProvider Members
public object GetService(Type serviceType)
{
if (serviceType == typeof(IDataServiceStreamProvider))
{
return new NorthwindStreamProvider(this, this.CurrentDataSource);
}
return null;
}
#endregion
}
For general information about how to create a data service, see Configuring the Data Service (ADO.NET Data Services).
Implementing the IDataServiceStreamProvider Interface
In order to create a streaming data service, you must implement the IDataServiceStreamProvider interface. This implementation enables the data service to return binary data as a stream to the client and consume binary data as a stream sent from the client.
The data service creates an instance of the IDataServiceStreamProvider interface.
This interface specifies the following members that define the streaming provider for the data service.
Member name |
Description |
---|---|
DeleteStream(Object, DataServiceOperationContext) |
This method is invoked by the data service to delete the corresponding media resource when its media link entry is deleted. When you implement IDataServiceStreamProvider, this method contains the code that deletes the media resource associated with the supplied media link entry. |
GetReadStream(Object, String, Nullable<Boolean>, DataServiceOperationContext) |
This method is invoked by the data service to return a media resource as a stream. When you implement IDataServiceStreamProvider, this method contains the code that provides a stream that is used by the data service to the return media resource that is associated with the provided media link entry. |
GetReadStreamUri(Object, DataServiceOperationContext) |
This method is invoked by the data service to return the URI that is used to request the media resource for the media link entry. This value is used to create the src attribute in the content element of the media link entry and that is used to request the data stream. When this method returns null, the read stream URI and write stream URI are the same. |
GetStreamContentType(Object, DataServiceOperationContext) |
This method is invoked by the data service to return the Content-Type value of the media resource that is associated with the specified media link entry. |
GetStreamETag(Object, DataServiceOperationContext) |
This method is invoked by the data service to return the eTag of the data stream that is associated with the specified entity. This method is used when you manage concurrency for the binary data. When this method returns null, the data service does not track concurrency. |
GetWriteStream(Object, String, Nullable<Boolean>, DataServiceOperationContext) |
This method is invoked by the data service to obtain the stream that is used when receiving the stream sent from the client. When you implement IDataServiceStreamProvider, you must return a writable stream to which the data service writes received stream data. When you use a FileStream, you can access the persisted binary data from the file system on the server before the interface instance is disposed. |
ResolveType(String, DataServiceOperationContext) |
Returns a namespace-qualified type name that represents the type that the data service runtime must create for the media link entry that is associated with the data stream for the media resource that is being inserted. |
Enabling Large Binary Streams
When you create a data service in an ASP.NET Web application, Windows Communication Foundation (WCF) is used to provide the HTTP protocol implementation. By default, WCF limits the size of HTTP messages to only 65K bytes. To be able to stream large binary data to and from the data service, you must also configure the Web application to enable large binary files and to use streams for transfer. To do this, add the following in the <configuration /> element of the application's Web.config file:
<system.serviceModel>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true" />
<services>
<!-- The name of the service -->
<service name="NorthwindStreaming.NorthwindStreaming">
<!-- you can leave the address blank or specify your end point URI -->
<endpoint binding="webHttpBinding" bindingConfiguration="streamingProvider"
contract="System.Data.Services.IRequestHandler">
</endpoint>
</service>
</services>
<bindings>
<webHttpBinding>
<!-- Use streamed mode and set the maxReceivedMessageSize value to the
maximum size of the binary stream that you want the service to process,
in this case 1MB.-->
<binding name="streamingProvider" maxReceivedMessageSize="1000000"
transferMode="Streamed" />
</webHttpBinding>
</bindings>
</system.serviceModel>
For more information, see Streaming Message Transfer and Transport Quotas.
Using Data Streams in a Client Application
The ADO.NET Data Services client library enables you to retrieve and update a media resource as a binary stream on the client. For more information, see Working with Binary Data (ADO.NET Data Services).
Client applications that are not based on the .NET Framework can also access and change media resources over HTTP. For more information, see Special Resource Paths (ADO.NET Data Services) and Making Changes to Data (ADO.NET Data Services).
Considerations for Working with a Streaming Provider
The following are things to consider when you implement a streaming provider and when you access media resources from a data service.
MERGE requests are not supported for media resources. Use a PUT request to change the media resource of an existing entity.
A POST request cannot be used to create a new media link entry. Instead, you must issue a POST request to create a new media resource, and the data service creates a new media link entry with default values. This new entity can be updated by a subsequent MERGE or PUT request. You may also consider caching the entity and make updates in the disposer, such as setting the property value to the value of the Slug header in the POST request.
When a POST request is received, the data service calls GetWriteStream(Object, String, Nullable<Boolean>, DataServiceOperationContext) to create the media resource before it calls SaveChanges() to create the media link entry.
An implementation of GetWriteStream(Object, String, Nullable<Boolean>, DataServiceOperationContext) should not return a MemoryStream object. When you use this kind of stream, memory resource issues will occur when the service receives very large data streams.
The following are things to consider when storing media resources in a database:
A binary property that is a media resource should not be included in the data model. All properties exposed in a data model are returned in the entry in a response feed.
To improve performance with a large binary stream, we recommend that you create a custom stream class to store binary data in the database. This class is returned by your GetWriteStream(Object, String, Nullable<Boolean>, DataServiceOperationContext) implementation and sends the binary data to the database in chunks. For a SQL Server database, we recommend that you use a FILESTREAM to stream data into the database when the binary data is larger that 1MB.
Ensure that your database is designed to store the binary large streams that are to be received by your data service.
When a client sends a POST request to insert a media link entry with a media resource in a single request, GetWriteStream(Object, String, Nullable<Boolean>, DataServiceOperationContext) is called to obtain the stream before the data service inserts the new entity into the database. A streaming provider implementation must be able to handle this data service behavior. Consider using a separate data table to store the binary data or store the data stream in a file until after the entity has been inserted into the database.
When you implement the DeleteStream(Object, DataServiceOperationContext), GetReadStream(Object, String, Nullable<Boolean>, DataServiceOperationContext), or GetWriteStream(Object, String, Nullable<Boolean>, DataServiceOperationContext) methods, you must use the eTag and Content-Type values that are supplied as method parameters. Do not set eTag or Content-Type headers in your IDataServiceStreamProvider provider implementation.
See Also
Other Resources
Data Services Providers (ADO.NET Data Services)