venerdì 10 febbraio 2017

Web API - POST Content and Data with a MultipartFormDataStreamProvider

C# .NET Framework 4.0, Web API, Newtonsoft.Json.dll

I want to send content file and data to a Web API service, in this case a pdf document and some metadata about it.
In first time, I have developed a custom MultipartFormDataStreamProvider

// We implement MultipartFormDataStreamProvider to override the filename of File which
// will be stored on server, or else the default name will be of the format like Body-
// Part_{GUID}. In the following implementation we simply get the FileName from
// ContentDisposition Header of the Request Body.
public class CustomMultipartFormDataStreamProvider : MultipartFormDataStreamProvider
{
    public CustomMultipartFormDataStreamProvider(string path) : base(path)
    {}

    public override string GetLocalFileName(HttpContentHeaders headers)
   {
        return headers.ContentDisposition.FileName.Replace("\"", string.Empty);
   }             

   /// <summary>
   /// Gets the attachment.
   /// </summary>
   /// <returns></returns>
   public AttachmentModel GetAttachment()
   {
       try
       {
                // This is a custom object.
                AttachmentModel attachment = null;

                var key = FormData.AllKeys[0];

                var strings = FormData.GetValues(key);
                if (strings != null)
                {
                    attachment = JsonConvert.DeserializeObject<AttachmentModel>(strings[0]);
                }

                return attachment;
        }
        catch (Exception ex)
        {
            return null;
         }
    }

Then I ‘ve developed this ApiController:

public class AttachmentController : ApiController
{
[HttpPost]
[Authorize]
public async Task<HttpResponseMessage> Post()
{
    string imageFile = null;
    FileStream fileStream = null;
    try
    {
        #region Check validity

        // Check whether the POST operation is MultiPart?
        if (!Request.Content.IsMimeMultipartContent())
        {                                                                            
            return Request.CreateResponse(HttpStatusCode.OK, "File format not valid.");
        }

        // This is a folder where we save the attachment temporarily.
        string documentUploadPath = System.Configuration.ConfigurationManager.AppSettings["DocumentUploadPath"];
        string root = string.Concat(HostingEnvironment.MapPath("~/"), documentUploadPath);

        CustomMultipartFormDataStreamProvider provider = new CustomMultipartFormDataStreamProvider(root);
        
        // Read all contents of multipart message into CustomMultipartFormDataStreamProvider.
        await Request.Content.ReadAsMultipartAsync(provider);

        imageFile = provider.FileData[0].LocalFileName;

        if (string.IsNullOrEmpty(imageFile))
        {                                                                            
            return Request.CreateResponse(HttpStatusCode.OK, "File not found.");
        }

        try
        {
            fileStream = new FileStream(imageFile, FileMode.Open);
        }
        catch
        {                                                                            
            return Request.CreateResponse(HttpStatusCode.OK, "Attachment not recognized.");
        }
       
        // This is a custom object.
         AttachmentModel Attachment = provider.GetAttachment();

        if (Attachment == null)
        {                                                                            
            return Request.CreateResponse(HttpStatusCode.OK, "Attachment not recognized.");
        }

        MemoryStream memoryStream = new MemoryStream();
        fileStream.CopyTo(memoryStream);
        Attachment.Content = memoryStream.ToArray();

        #endregion

        // Other internal consideration and operations.
        // ...
        
        var response = Request.CreateResponse(HttpStatusCode.OK, “OK”;

        return response;
    }
    catch (Exception ex)
    {                                                             
        HttpError httpError = new HttpError {Message = "Internal error."};
        return Request.CreateErrorResponse(HttpStatusCode.InternalServerError, httpError);
    }
    finally
    {
        if (imageFile != null && File.Exists(imageFile))
        {
            if (fileStream != null)
            {
                fileStream.Close();
                fileStream.Dispose();
            }
            File.Delete(imageFile);
        }
    }
 }
  
Ok. For testing I have used fiddler with a script like this:


Content-Type: multipart/form-data; boundary=-------------------------acebdf13572468
Host: localhost
Connection: keep-alive
Cache-Control: max-age=0
Accept: application/json, text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.153 Safari/537.36
Accept-Encoding: gzip,deflate,sdch
Accept-Language: it-IT,en;q=0.8
Content-Length: 225317

Request body:

---------------------------acebdf13572468
Content-Disposition: form-data; name="fieldNameHere"; filename="test.pdf"
Content-Type: application/pdf

<@INCLUDE *C:\temp\test.pdf*@>
---------------------------acebdf13572468
Content-Disposition: form-data; name="attachment"

{
  "IdProtocol": "1",
  "application": "TEST",
  "filename": "test.pdf"
}
---------------------------acebdf13572468--

It is very important to close the request with these two minus signs!



Finally a C# function for calling this web service:

private bool SendAttachment(AttachmentModel attachment)
{
     // This is a custom object.
     // AttachmentModel attachment

     HttpClient client = new HttpClient();
                                              
     using (var content = new MultipartFormDataContent())
     {
         var values = new[]
         {
             new KeyValuePair<string, string>("attachment", JsonConvert.SerializeObject(attachment))
         };

         foreach (var keyValuePair in values)
         {
             content.Add(new StringContent(keyValuePair.Value), keyValuePair.Key);
         }

         var fileContent = new ByteArrayContent(attachment.Content);
         fileContent.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment")
         {
             FileName = attachment.NomeFile
         };
         content.Add(fileContent);

         var requestUri = "http://localhost/AttachmentManagerService/api/attachment";
         var result = client.PostAsync(requestUri, content);
         result.Wait();

         if (result.Result.IsSuccessStatusCode)
         {
             // check json format
             string responseContent = result.Result.Content.ReadAsStringAsync().Result;

             // if parse not throw exceptions, deserialize string...
                                                                             
             return true;
         }
         else
         {
             return false;
         }
     }
 }

I hope it is clear, otherwise if you want to also send me an email.


Kind regards!