user1400995
user1400995

Reputation:

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

Upvotes: 1

Views: 1260

Answers (1)

Eser
Eser

Reputation: 12546

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.

Upvotes: 5

Related Questions