Search code examples
c#wcfpdffilestream

WCF Streamed PDF Response


My aim is to return a PDF file stream back to the client.

So in WCF side I have:

public interface IPersonalPropertyService
    {
        [OperationContract]
        Stream GetQuotation();
    }

public class PersonalPropertyService : IPersonalPropertyService
    { 
        public Stream GetQuotation()
        {
            var filePath = HostingEnvironment.ApplicationPhysicalPath + @"Quotation.pdf";
            var fileInfo = new FileInfo(filePath);
            // check if exists
            if (!fileInfo.Exists)
                throw new FileNotFoundException("File not found");
            FileStream stm = File.Open(filePath, FileMode.Open);
            WebOperationContext.Current.OutgoingResponse.ContentType = "application/pdf";
            return stm;  
        } 
    }

The configuration part is as follows:

<system.serviceModel>
    <client>
      <endpoint
        binding="basicHttpBinding"
        bindingConfiguration="StreamedHttp"
        contract="IPersonalPropertyService" >
      </endpoint>
    </client>
    <bindings>
      <basicHttpBinding>
        <binding name="StreamedHttp" maxBufferSize="2147483647" maxReceivedMessageSize="2147483647"
          transferMode="Streamed">
          <readerQuotas maxDepth="2147483647" maxStringContentLength="2147483647"
            maxArrayLength="2147483647" maxBytesPerRead="2147483647" maxNameTableCharCount="2147483647" />
        </binding>
      </basicHttpBinding>
    </bindings>

    <behaviors>
      <serviceBehaviors>
        <behavior>
          <!-- To avoid disclosing metadata information, set the values below to false before deployment -->
          <serviceMetadata httpGetEnabled="true" 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>
  </system.serviceModel>

Now on the client side (e.g. console app), when I create the service reference, I would expect to see my basicHttpBinding StreamedHttp configuration, but instead the following config is generated:

<system.serviceModel>
        <bindings>
            <basicHttpBinding>
                <binding name="BasicHttpBinding_PersonalPropertyService" />
            </basicHttpBinding>
        </bindings>
        <client>
            <endpoint address="MyPath/PersonalPropertyService.svc"
                binding="basicHttpBinding" bindingConfiguration="BasicHttpBinding_PersonalPropertyService"
                contract="TheNamespace.ServiceReference.PersonalPropertyService"
                name="BasicHttpBinding_PersonalPropertyService" />
        </client>
    </system.serviceModel>

and I reckon that because of that reason, I am getting a ProtocolException exception saying

The content type application/pdf of the response message does not match the content type of the binding (text/xml; charset=utf-8).

How can I force the client to accept the streamed configuration defined on the WCF side?

Thank you


Solution

  • Here is a tested console app. Just create "Files" directory in your executable, put your files into it, and paste something like this http://localhost:8088/fileServer/a.pdf to your browser.

    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Linq;
    using System.Net;
    using System.ServiceModel;
    using System.ServiceModel.Channels;
    using System.ServiceModel.Web;
    using System.Text;
    using System.Web;
    
    namespace SampleWCFConsoleApplication
    {
        class Program
        {
            static void Main(string[] args)
            {
                FileServer.Start();
            }
        }
    
        [ServiceContract]
        public class FileServer
        {
            static WebServiceHost _Host;
            public static void Start()
            {
                _Host = new WebServiceHost(typeof(FileServer), new Uri("http://0.0.0.0:8088/FileServer"));
                _Host.Open();
                Console.ReadLine();
            }
    
            [OperationContract, WebGet(UriTemplate = "*")]
            public Message Get()
            {
                var ctx = WebOperationContext.Current;
    
                var fileName = Path.Combine("Files", String.Join("/", WebOperationContext.Current.IncomingRequest.UriTemplateMatch.RelativePathSegments));
    
                var fInfo = new FileInfo(fileName);
    
                var eTag = ctx.IncomingRequest.Headers[HttpRequestHeader.IfNoneMatch];
                if (!string.IsNullOrEmpty(eTag))
                {
                    if (GetETag(fInfo) == eTag)
                    {
                        ctx.OutgoingResponse.StatusCode = HttpStatusCode.NotModified;
                        return ctx.CreateTextResponse("");
                    }
                }
    
                if (fInfo.Exists == false) return ReturnError(ctx, HttpStatusCode.NotFound);
                return ReturnFile(ctx, fInfo);
            }
    
            static string GetETag(FileInfo fInfo)
            {
                return Convert.ToBase64String(Encoding.UTF8.GetBytes(fInfo.Name).Concat(BitConverter.GetBytes(fInfo.LastWriteTime.Ticks)).ToArray());
            }
    
            public static Message ReturnError(WebOperationContext ctx, HttpStatusCode statusCode)
            {
                ctx.OutgoingResponse.StatusCode = statusCode;
                return ctx.CreateTextResponse(statusCode.ToString(), "text/html");
            }
    
            static Message ReturnFile(WebOperationContext ctx, FileInfo fInfo, HttpStatusCode statusCode = HttpStatusCode.OK)
            {
                ctx.OutgoingResponse.StatusCode = statusCode;
                ctx.OutgoingResponse.ETag = GetETag(fInfo);
    
                return ctx.CreateStreamResponse(File.OpenRead(fInfo.FullName), MimeMapping.GetMimeMapping(fInfo.Name));
            }
        }
    }
    

    BTW: If you want, you can remove if-not-modified code, it is just not to resend the same file if client has uptodate version.