According to Microsoft, you can return a Stream object from a WCF service1, 2 and everything will work easy-peasy. Regrettably, I have not found this to be the case when returning a Stream to a remote file from either a file server or a web page. Instead, the connection to the Stream is closed by the server before the client can access it.
A simple solution would be a temporary file, but because it would be I/O intensive (not to mention a security risk) to copy the contents of the Stream to a temporary file on the WCF server and return a Stream to that file instead, we need another way to accomplish this task. The only solution I’ve been able to find is to create a .NET Assembly connector for BCS and return the Stream from there.
To do this, change your WCF service to return an array of bytes instead of a Stream. From your connector assembly, return a MemoryStream of the incoming byte array from the service. This wrapper will ensure that the Stream is copied into RAM for the connector before BCS gets to it, eliminating the connection closed error. However, the caveat here is that if you have any other methods the make up this content type or are associated to it, they’ll need to be wrapped inside this assembly as well. It’s duplication of code, but you still have the flexibility brought about by the WCF service. Here’s how I’ve implemented it for a Document class below:
public static Stream GetDocumentStream(int id)
{
using (ChannelFactory<IPublicWebService> factory = PublicWebServiceConnector.GetChannelFactory())
{
IPublicWebService service = factory.CreateChannel();
byte[] bytes = service.GetDocumentBytes(id);
return new MemoryStream(bytes);
}
}
private static ChannelFactory<IPublicWebService> GetChannelFactory()
{
XmlDictionaryReaderQuotas readerQuotas = new XmlDictionaryReaderQuotas()
{
MaxArrayLength = 10485760,
MaxStringContentLength = 10485760
};
return new ChannelFactory<IPublicWebService>(new BasicHttpBinding()
{
MaxBufferPoolSize = 10485760,
MaxBufferSize = 10485760,
MaxReceivedMessageSize = 10485760,
ReaderQuotas = readerQuotas
}, new EndpointAddress(“<Endpoint Address>”));
}
Fortunately, the XML for your StreamAccessor doesn’t need to change and can simply be copied over from the BCS model for the WCF service. There’s no need to worry about MIME types or the like, unless you wish to retain the file name. Windows and search indexing will pick up the MIME type from the Stream automatically. Here’s the pertinent BCS model for the GetDocumentStream method above:
<MethodName=“GetDocumentStream“DefaultDisplayName=“Get Document Stream“IsStatic=“true“>
<Parameters>
<ParameterName=“id“Direction=“In“DefaultDisplayName=“Id“>
<TypeDescriptorName=“Id“TypeName=“System.Int32“IsCollection=“false“IdentifierName=“Id“DefaultDisplayName=“Id“ />
</Parameter>
<ParameterName=“GetDocumentStream“Direction=“Return“DefaultDisplayName=“Get Document Stream“>
<TypeDescriptorName=“DocumentStream“TypeName=“System.IO.Stream“DefaultDisplayName=“Document Stream“ />
</Parameter>
</Parameters>
<MethodInstances>
<MethodInstanceName=“GetDocumentStreamInstance“Type=“StreamAccessor“ReturnParameterName=“GetDocumentStream“ReturnTypeDescriptorPath=“DocumentStream“DefaultDisplayName=“Get Document Stream“Default=“true“ />
</MethodInstances>
</Method>
Examples
Eample Code
Citations:
- Code Snippet: Implementing a StreamAccessor [microsoft.com]
- XML Snippet: Modeling a StreamAccessor Method [microsoft.com]