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.Collections;
11 : using System.Collections.Generic;
12 : using System.Linq;
13 : using System.Runtime.Serialization;
14 : using System.Text;
15 : using Cqrs.WebApi.Formatters.FormMultipart.Infrastructure;
16 : using Cqrs.WebApi.Formatters.FormMultipart.Infrastructure.Extensions;
17 :
18 : namespace Cqrs.WebApi.Formatters.FormMultipart.Converters
19 : {
20 : /// <summary>
21 : /// Converts <see cref="object">objects</see> to multi-part form-data.
22 : /// </summary>
23 : public class ObjectToMultipartDataByteArrayConverter
24 1 : {
25 : private MultipartFormatterSettings Settings { get; set; }
26 :
27 : /// <summary>
28 : /// Instantiate and initialise a new instance of <see cref="ObjectToMultipartDataByteArrayConverter"/>
29 : /// </summary>
30 : /// <param name="settings">The <see cref="MultipartFormatterSettings"/> to use.</param>
31 1 : public ObjectToMultipartDataByteArrayConverter(MultipartFormatterSettings settings)
32 : {
33 : if (settings == null)
34 : throw new ArgumentNullException("settings");
35 :
36 : Settings = settings;
37 : }
38 :
39 : /// <summary>
40 : /// Converts the provided <paramref name="value"/> to multi-part form-data using the provided <paramref name="boundary"/>.
41 : /// </summary>
42 1 : public byte[] Convert(object value, string boundary)
43 : {
44 : if(value == null)
45 : throw new ArgumentNullException("value");
46 : if (String.IsNullOrWhiteSpace(boundary))
47 : throw new ArgumentNullException("boundary");
48 :
49 : List<KeyValuePair<string, object>> propertiesList = ConvertObjectToFlatPropertiesList(value);
50 :
51 : byte[] buffer = GetMultipartFormDataBytes(propertiesList, boundary);
52 : return buffer;
53 : }
54 :
55 : private List<KeyValuePair<string, object>> ConvertObjectToFlatPropertiesList(object value)
56 : {
57 : var propertiesList = new List<KeyValuePair<string, object>>();
58 : if (value is FormData)
59 : {
60 : FillFlatPropertiesListFromFormData((FormData) value, propertiesList);
61 : }
62 : else
63 : {
64 : FillFlatPropertiesListFromObject(value, "", propertiesList);
65 : }
66 :
67 : return propertiesList;
68 : }
69 :
70 : private void FillFlatPropertiesListFromFormData(FormData formData, List<KeyValuePair<string, object>> propertiesList)
71 : {
72 : foreach (var field in formData.Fields)
73 : {
74 : propertiesList.Add(new KeyValuePair<string, object>(field.Name, field.Value));
75 : }
76 : foreach (var field in formData.Files)
77 : {
78 : propertiesList.Add(new KeyValuePair<string, object>(field.Name, field.Value));
79 : }
80 : }
81 :
82 : private void FillFlatPropertiesListFromObject(object obj, string prefix, List<KeyValuePair<string, object>> propertiesList)
83 : {
84 : if (obj != null)
85 : {
86 : Type type = obj.GetType();
87 :
88 : if (obj is IDictionary)
89 : {
90 : var dict = obj as IDictionary;
91 : int index = 0;
92 : foreach (var key in dict.Keys)
93 : {
94 : string indexedKeyPropName = String.Format("{0}[{1}].Key", prefix, index);
95 : FillFlatPropertiesListFromObject(key, indexedKeyPropName, propertiesList);
96 :
97 : string indexedValuePropName = String.Format("{0}[{1}].Value", prefix, index);
98 : FillFlatPropertiesListFromObject(dict[key], indexedValuePropName, propertiesList);
99 :
100 : index++;
101 : }
102 : }
103 : else if (obj is ICollection && !IsByteArrayConvertableToHttpFile(obj))
104 : {
105 : var list = obj as ICollection;
106 : int index = 0;
107 : foreach (var indexedPropValue in list)
108 : {
109 : string indexedPropName = String.Format("{0}[{1}]", prefix, index);
110 : FillFlatPropertiesListFromObject(indexedPropValue, indexedPropName, propertiesList);
111 :
112 : index++;
113 : }
114 : }
115 : else if (type.IsCustomNonEnumerableType())
116 : {
117 : foreach (var propertyInfo in type.GetProperties())
118 : {
119 : if (propertyInfo.CustomAttributes.Any(attribute => attribute.AttributeType == typeof(IgnoreDataMemberAttribute)))
120 : continue;
121 : string propName = string.IsNullOrWhiteSpace(prefix)
122 : ? propertyInfo.Name
123 : : String.Format("{0}.{1}", prefix, propertyInfo.Name);
124 : object propValue = propertyInfo.GetValue(obj);
125 :
126 : FillFlatPropertiesListFromObject(propValue, propName, propertiesList);
127 : }
128 : }
129 : else
130 : {
131 : propertiesList.Add(new KeyValuePair<string, object>(prefix, obj));
132 : }
133 : }
134 : }
135 :
136 : private byte[] GetMultipartFormDataBytes(List<KeyValuePair<string, object>> postParameters, string boundary)
137 : {
138 : if (postParameters == null || !postParameters.Any())
139 : throw new Exception("Cannot convert data to multipart/form-data format. No data found.");
140 :
141 : Encoding encoding = Encoding.UTF8;
142 :
143 : using (var formDataStream = new System.IO.MemoryStream())
144 : {
145 : bool needsClrf = false;
146 :
147 : foreach (var param in postParameters)
148 : {
149 : // Add a CRLF to allow multiple parameters to be added.
150 : // Skip it on the first parameter, add it to subsequent parameters.
151 : if (needsClrf)
152 : formDataStream.Write(encoding.GetBytes("\r\n"), 0, encoding.GetByteCount("\r\n"));
153 :
154 : needsClrf = true;
155 :
156 : if (param.Value is HttpFile || IsByteArrayConvertableToHttpFile(param.Value))
157 : {
158 : HttpFile httpFileToUpload = param.Value is HttpFile
159 : ? (HttpFile) param.Value
160 : : new HttpFile(null, null, (byte[]) param.Value);
161 :
162 : // Add just the first part of this param, since we will write the file data directly to the Stream
163 : string header = string.Format
164 : (
165 : "--{0}\r\nContent-Disposition: form-data; name=\"{1}\"; filename=\"{2}\"\r\nContent-Type: {3}\r\n\r\n",
166 : boundary,
167 : param.Key,
168 : httpFileToUpload.FileName ?? param.Key,
169 : httpFileToUpload.MediaType ?? "application/octet-stream"
170 : );
171 :
172 : formDataStream.Write(encoding.GetBytes(header), 0, encoding.GetByteCount(header));
173 :
174 : // Write the file data directly to the Stream, rather than serializing it to a string.
175 : formDataStream.Write(httpFileToUpload.Buffer, 0, httpFileToUpload.Buffer.Length);
176 : }
177 : else
178 : {
179 : string objString = "";
180 : if (param.Value != null)
181 : {
182 : var typeConverter = param.Value.GetType().GetToStringConverter();
183 : if (typeConverter != null)
184 : {
185 : objString = typeConverter.ConvertToString(null, Settings.CultureInfo, param.Value);
186 : }
187 : else
188 : {
189 : throw new Exception(String.Format("Type \"{0}\" cannot be converted to string", param.Value.GetType().FullName));
190 : }
191 : }
192 :
193 : string postData = string.Format
194 : (
195 : "--{0}\r\nContent-Disposition: form-data; name=\"{1}\"\r\n\r\n{2}",
196 : boundary,
197 : param.Key,
198 : objString
199 : );
200 : formDataStream.Write(encoding.GetBytes(postData), 0, encoding.GetByteCount(postData));
201 : }
202 : }
203 :
204 : // Add the end of the request. Start with a newline
205 : string footer = "\r\n--" + boundary + "--\r\n";
206 : formDataStream.Write(encoding.GetBytes(footer), 0, encoding.GetByteCount(footer));
207 :
208 : byte[] formData = formDataStream.ToArray();
209 :
210 : return formData;
211 : }
212 : }
213 :
214 : private bool IsByteArrayConvertableToHttpFile(object value)
215 : {
216 : return value is byte[] && Settings.SerializeByteArrayAsHttpFile;
217 : }
218 : }
219 : }
|