Wednesday, January 22, 2014

Export Dynamic Word document in Web API

There may be scenarios where you need to dynamically generate word document (which is not saved on the web server) and download it using Web API. In this blog I will show you how to generate a word document and download that word document using Web API.

First we need create an in memory word document. This word document will not be saved on the web server. As we are creating word document on a web server, let use OpenXml. Here is the code which creates a “Hello World” in memory document

using (MemoryStream ms = new MemoryStream())
{
    using (WordprocessingDocument package = WordprocessingDocument.Create(ms, WordprocessingDocumentType.Document))
    { 
        MainDocumentPart mainPart = package.AddMainDocumentPart();
        mainPart.Document = new Document();
        var body = new Body();
        Paragraph paragraph = new Paragraph();
        Run run = new Run(new Text("Hello World!"));
        paragraph.Append(run);
        body.Append(paragraph);
        mainPart.Document.Append(body);
        package.Close();
    }
}

Once the document is created we can push the document through the response stream using the below code:

HttpResponseMessage result = new HttpResponseMessage(HttpStatusCode.OK);
result.Content = new ByteArrayContent(ms.ToArray());
result.Content.Headers.ContentType = new MediaTypeHeaderValue("application/vnd.ms-word");
result.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment");
result.Content.Headers.ContentDisposition.FileName = "HelloWorld.docx";
return result;

Here is the total WebAPI code:
public HttpResponseMessage Get()
{
    using (MemoryStream ms = new MemoryStream())
    {

        using (WordprocessingDocument package = WordprocessingDocument.Create(ms, WordprocessingDocumentType.Document))
        {

            MainDocumentPart mainPart = package.AddMainDocumentPart();
            mainPart.Document = new Document();
            var body = new Body();
            Paragraph paragraph = new Paragraph();
            Run run = new Run(new Text("Hello World!"));
            //run.Append(new Text("Hello World!"));
            paragraph.Append(run);
            body.Append(paragraph);
            mainPart.Document.Append(body);
            package.Close();
        }

        HttpResponseMessage result = new HttpResponseMessage(HttpStatusCode.OK);
        result.Content = new ByteArrayContent(ms.ToArray());
        result.Content.Headers.ContentType = new MediaTypeHeaderValue("application/vnd.ms-word");
        result.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment");
        result.Content.Headers.ContentDisposition.FileName = "HelloWorld.docx";
        return result;
    }
}

34 comments:

  1. You are a hero! I tried a number of methods to get this done, and your code is the only example that works for me. Much appreciated.

    ReplyDelete
  2. Hi Prasanna,

    One of my sample web method function is as below -
    [httppost]
    [Actionname()]
    [WebMethod]
    _
    Public Function DownloadImages(ByVal downloadImageRequest As String) As HttpContent
    {
    // code
    }

    However on trying to refer this web service in my asp.net client , am getting the below error

    Server Error in ‘/’ Application.
    ——————————————————————————–

    To be XML serializable, types which inherit from IEnumerable must have an implementation of Add(System.Object) at all levels of their inheritance hierarchy. System.Net.Http.Headers.HttpContentHeaders does not implement Add(System.Object).

    I need to return a multipart response and our HTTP request/response are using HTTP POST. I think we cannot use HTTPResponseMessage as the return type when the function is decorated with WebMethod attribute. Please help.

    ReplyDelete
    Replies
    1. HttpResponseMessage is part of WebAPI (System.Net.Http) and is not by default available in Web Method.

      Based on the code you posted, it seems that you want to download an image from web method. In that scenario your return type should be a byte array
      [WebMethod]
      public byte[] GetFile(string fileName)

      For more details on how to download a file from Web Method check the MSDN documentation

      http://msdn.microsoft.com/en-us/library/aa528822.aspx?cs-save-lang=1&cs-lang=csharp#code-snippet-1

      Delete
    2. Hi Prasanna,

      Though my function name is like that, my original requirement is to return a multi-part response containing binary data as well as an xml string. Hence within this function , my code is as below. Note: I have added the dll for HttpResponseMessage from nuget. The problem I am facing is when i include the [webmethod] attribute in the function, am getting the above error while trying to refer this webservice in my asp.net client.. And this issue is happening only when i return HTTPResponseMessage. I need to know either how to solve this error or any alternate way to return a multipart response.

      byte[] ImageData = null;
      ImageData = File.ReadAllBytes(“D:\\Tulips.jpg”);
      MultipartFormDataContent multipartContent = new MultipartFormDataContent();
      HttpResponseMessage result = new HttpResponseMessage(HttpStatusCode.OK);

      ByteArrayContent fileContent = new ByteArrayContent(ImageData);
      fileContent.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue(“image/jpeg”);
      fileContent.Headers.ContentLength = ImageData.Length;
      multipartContent.Add(fileContent);

      StringContent body = new StringContent(“xml”);
      body.Headers.ContentType = “xxx-xml”;
      multipartContent.Add(body);

      result.Content = multipartContent;.
      return Result; // returning HTTPResposneMessage

      Pleas help..

      Thanks,
      Divya

      Delete
    3. Hi Prasanna,

      My original requirement is to return a multipart response with image binary data and string xml. Please find the code as below. Note: I have the dll for HttpResponseMessage with me ( from nuget). I am facing this issue when I use WebMethod attribute to my downloadimage() returning an HttpResponseMessage. I noticed that this issue wont be there if we are either not using WebMethod attribute/ or not returning an HttpResponseMessage object. This error will come only when we try to add the service in our asp.net client.

      My code

      byte[] ImageData = null;
      ImageData = File.ReadAllBytes(“D:\\Tulips.jpg”);
      MultipartFormDataContent multipartContent = new MultipartFormDataContent();
      HttpResponseMessage result = new HttpResponseMessage(HttpStatusCode.OK);

      ByteArrayContent fileContent = new ByteArrayContent(ImageData);
      fileContent.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue(“image/jpeg”);
      fileContent.Headers.ContentLength = ImageData.Length;
      multipartContent.Add(fileContent);

      StringContent body = new StringContent(“xml”);
      body.Headers.ContentType = “xxx-xml”;
      multipartContent.Add(body);

      result.Content = multipartContent;.
      return result

      Any help would be appreciated.

      thanks,
      Divya

      Delete
    4. Divya
      I got an email with your question but the comment is not there on the site.
      Anyway try this:
      From your web method write the response directly to the response stream using HttpContext.Current.Response.Write (...). Do this instead of returning the HttpResponseMessage.
      Check this link for sample on how to return a multipart response
      http://www.motobit.com/tips/detpg_multiple-files-one-request/

      Also just be cautious that multipart response is not supported by all browsers. Instead of this I recommend to zip your multiple responses and send one zipped response.

      Delete
    5. Thanks for your response. Can you clarify the following details

      a) Can't I work with HttpResponseMessage like in the example you have shown( I am working with VS 2010. Does that pose a limitation in using that object. Isn't it possible to resolve the error that I am getting while using HttpResponseMessage)
      b) This should be my multi - part format

      HTTP/1.0 200 OK
      Date: Wed, 03 Mar 2004 14:45:11 GMT
      Server: xxx
      Connection: close
      Content-Type: application/xxx-multipart
      Content-Length: 6851

      Content-Type: image/jpeg
      Content-Length: 5123
      CommandID: asset14521

      <5123 bytes of jpeg data>
      Content-Type: xxx-xml
      Content-Length: 1523

      ?<xml…

      In this case, the first header paragraph would automatically be included by ASP.Net IIS server right? So i wont have to explicitly add that. I just need to add the remaining header right? Please confirm

      Thanks,
      Divya

      Delete
    6. a) HttpResponseMessage was designed for WebAPI and I not sure whether WebMethod understands this.

      b) In a multipart response I think you need to specify all the headers.

      Delete
    7. Hi Prasanna,

      After going through the link you have provided, a doubt arised in my mind. If we implement code like

      Response.BinaryWrite( image array)
      Resposne.Write( string xml).

      Here we can't interrupt the binary writing process to send the xml. right? Will this send the entire response all at once. I belive no. Please correct me if I am wrong?

      Divya

      Delete
  3. Every content type should be separated by a boundary. If you do a BinaryWrite & Write, you may get an invalid content type error

    ReplyDelete
    Replies
    1. So in such a case how can i send the entire content in a single write. In order to write binary data, we would require BinaryWrite anyway

      Delete
  4. Hi Prasanna,

    Can you clarify if we can use HTTPResponseMessage in an ASP.Net MVC web application. I am thinking to switch from normal web service using webmethod to mvc web application

    ReplyDelete
  5. HttpResponseMessage is part of Web API stack. Web API can work side by side MVC. So you can use HttpResponseMessage in a MVC web application. When you are defining controller in a MVC application, just use Web API controller template.

    Also here is a sample code, I got of Internet for creating a multipart response in Web API

    HttpResponseMessage response = Request.CreateResponse(HttpStatusCode.OK);
    var content = new MultipartContent();
    content.Add(new ObjectContent(data, new JsonMediaTypeFormatter()));
    content.Add(new StreamContent(image));
    response.Content = content;
    return response;

    ReplyDelete
  6. Hi Prasanna,

    I have a query.I have implemented a webservice - Test.Asmx which expose 2 web methods say: A() and B(). I was under assumption that the client team would be using our exposed web methods. But later came to know that they would just POST request to url :http:\\\\ webservice.asmx. In such a case with minimal effort how can I can use my exisitng code to handle this? Currently I have created web service in ASP.Net 2.0 . I am allowed to work in VS2010. Please help

    Thanks

    ReplyDelete