Line data Source code
1 : #region Copyright
2 : // // -----------------------------------------------------------------------
3 : // // <copyright company="Chinchilla Software Limited">
4 : // // Copyright Chinchilla Software Limited. All rights reserved.
5 : // // </copyright>
6 : // // -----------------------------------------------------------------------
7 : #endregion
8 :
9 : using System;
10 : using System.IO;
11 : using System.Linq;
12 : using System.Net;
13 : using System.Net.Http;
14 : using System.Net.Http.Formatting;
15 : using System.Net.Http.Headers;
16 : using System.Text;
17 : using System.Threading.Tasks;
18 : using Cqrs.WebApi.Formatters.FormMultipart.Converters;
19 : using Cqrs.WebApi.Formatters.FormMultipart.Infrastructure;
20 : using Cqrs.WebApi.Formatters.FormMultipart.Infrastructure.Logger;
21 :
22 : namespace Cqrs.WebApi.Formatters.FormMultipart
23 : {
24 : /// <summary>
25 : /// Represents the <see cref="MediaTypeFormatter"/> class to handle multi-part form-data.
26 : /// </summary>
27 : public class FormMultipartEncodedMediaTypeFormatter : MediaTypeFormatter
28 1 : {
29 : private const string SupportedMediaType = "multipart/form-data";
30 :
31 : private readonly MultipartFormatterSettings Settings;
32 :
33 : /// <summary>
34 : /// Instantiate and initialise a new instance of <see cref="FormMultipartEncodedMediaTypeFormatter"/>
35 : /// </summary>
36 : /// <param name="settings">The <see cref="MultipartFormatterSettings"/> to use.</param>
37 1 : public FormMultipartEncodedMediaTypeFormatter(MultipartFormatterSettings settings = null)
38 : {
39 : Settings = settings ?? new MultipartFormatterSettings();
40 : SupportedMediaTypes.Add(new MediaTypeHeaderValue(SupportedMediaType));
41 : }
42 :
43 : /// <summary>
44 : /// Queries whether this <see cref="MediaTypeFormatter"/> can deserialise an object of the specified type.
45 : /// </summary>
46 : /// <param name="type">The <see cref="Type"/> to deserialise.</param>
47 : /// <returns>true if the <see cref="MediaTypeFormatter"/> can deserialise the <paramref name="type"/>; otherwise, false.</returns>
48 1 : public override bool CanReadType(Type type)
49 : {
50 : return true;
51 : }
52 :
53 : /// <summary>
54 : /// Queries whether this <see cref="MediaTypeFormatter"/> can serialise an object of the specified type.
55 : /// </summary>
56 : /// <param name="type">The <see cref="Type"/> to serialise.</param>
57 : /// <returns>true if the <see cref="MediaTypeFormatter"/> can serialise the <paramref name="type"/>; otherwise, false.</returns>
58 1 : public override bool CanWriteType(Type type)
59 : {
60 : return true;
61 : }
62 :
63 : /// <summary>
64 : /// Sets the default headers for content that will be formatted using this formatter.
65 : /// This method is called from the <see cref="ObjectContent"/> constructor..
66 : /// This implementation sets the Content-Type header to the value of <paramref name="mediaType"/> if it is not null.
67 : /// If it is null it sets the Content-Type to the default media type of this formatter.
68 : /// If the Content-Type does not specify a charset it will set it using this formatters configured <see cref="Encoding"/>.
69 : /// </summary>
70 : /// <param name="type">The <see cref="Type"/> of the object being serialized. See <see cref="ObjectContent"/>.</param>
71 : /// <param name="headers">The content headers that should be configured.</param>
72 : /// <param name="mediaType">The authoritative media type. Can be null.</param>
73 1 : public override void SetDefaultContentHeaders(Type type, HttpContentHeaders headers, MediaTypeHeaderValue mediaType)
74 : {
75 : base.SetDefaultContentHeaders(type, headers, mediaType);
76 :
77 : //need add boundary
78 : //(if add when fill SupportedMediaTypes collection in class constructor then receive post with another boundary will not work - Unsupported Media Type exception will thrown)
79 : if (headers.ContentType == null)
80 : {
81 : headers.ContentType = new MediaTypeHeaderValue(SupportedMediaType);
82 : }
83 : if (!String.Equals(headers.ContentType.MediaType, SupportedMediaType, StringComparison.OrdinalIgnoreCase))
84 : {
85 : throw new Exception("Not a Multipart Content");
86 : }
87 : if (headers.ContentType.Parameters.All(m => m.Name != "boundary"))
88 : {
89 : headers.ContentType.Parameters.Add(new NameValueHeaderValue("boundary", "Cqrs.WebApi.Formatters.FormMultipartBoundary1q2w3e"));
90 : }
91 : }
92 :
93 : /// <summary>
94 : /// Asynchronously deserialises an object of the specified type.
95 : /// </summary>
96 : /// <param name="type">The <see cref="Type"/> of the object to deserialise.</param>
97 : /// <param name="readStream">The <see cref="Stream"/> to read.</param>
98 : /// <param name="content">The <see cref="HttpContent"/>, if available. It may be null.</param>
99 : /// <param name="formatterLogger">The <see cref="IFormatterLogger"/> to log events to.</param>
100 : /// <returns>A <see cref="Task"/> whose result will be an object of the given type.</returns>
101 1 : public override async Task<object> ReadFromStreamAsync(Type type, Stream readStream, HttpContent content, IFormatterLogger formatterLogger)
102 : {
103 : var httpContentToFormDataConverter = new HttpContentToFormDataConverter();
104 : FormData multipartFormData = await httpContentToFormDataConverter.Convert(content);
105 :
106 : IFormDataConverterLogger logger;
107 : if (formatterLogger != null)
108 : logger = new FormatterLoggerAdapter(formatterLogger);
109 : else
110 : logger = new FormDataConverterLogger();
111 :
112 : var dataToObjectConverter = new FormDataToObjectConverter(multipartFormData, logger, Settings);
113 : object result = dataToObjectConverter.Convert(type);
114 :
115 : logger.EnsureNoErrors();
116 :
117 : return result;
118 : }
119 :
120 : /// <summary>
121 : /// Asynchronously writes an object of the specified type.
122 : /// </summary>
123 : /// <param name="type">The <see cref="Type"/> of the object to write.</param>
124 : /// <param name="value">The object value to write. It may be null.</param>
125 : /// <param name="writeStream">The <see cref="Stream"/> to write to.</param>
126 : /// <param name="content">The <see cref="HttpContent"/>, if available. It may be null.</param>
127 : /// <param name="transportContext">The <see cref="TransportContext"/> if available. It may be null.</param>
128 : /// <returns>A <see cref="Task"/> that will perform the write.</returns>
129 1 : public override async Task WriteToStreamAsync(Type type, object value, Stream writeStream, HttpContent content, TransportContext transportContext)
130 : {
131 : if (!content.IsMimeMultipartContent())
132 : {
133 : throw new Exception("Not a Multipart Content");
134 : }
135 :
136 : var boudaryParameter = content.Headers.ContentType.Parameters.FirstOrDefault(m => m.Name == "boundary" && !String.IsNullOrWhiteSpace(m.Value));
137 : if (boudaryParameter == null)
138 : {
139 : throw new Exception("multipart boundary not found");
140 : }
141 :
142 : var objectToMultipartDataByteArrayConverter = new ObjectToMultipartDataByteArrayConverter(Settings);
143 : byte[] multipartData = objectToMultipartDataByteArrayConverter.Convert(value, boudaryParameter.Value);
144 :
145 : await writeStream.WriteAsync(multipartData, 0, multipartData.Length);
146 :
147 : content.Headers.ContentLength = multipartData.Length;
148 : }
149 : }
150 : }
|