Line data Source code
1 : using System;
2 : using System.Collections.Generic;
3 : using System.Linq;
4 : using System.Reflection;
5 : using Cqrs.WebApi.Formatters.FormMultipart.Infrastructure;
6 : using Cqrs.WebApi.Formatters.FormMultipart.Infrastructure.Extensions;
7 : using Cqrs.WebApi.Formatters.FormMultipart.Infrastructure.Logger;
8 :
9 : namespace Cqrs.WebApi.Formatters.FormMultipart.Converters
10 : {
11 : public class FormDataToObjectConverter
12 0 : {
13 : private readonly FormData _sourceData;
14 :
15 : private readonly IFormDataConverterLogger _logger;
16 :
17 : private readonly MultipartFormatterSettings _settings;
18 :
19 0 : public FormDataToObjectConverter(FormData sourceData, IFormDataConverterLogger logger, MultipartFormatterSettings settings)
20 : {
21 : if (sourceData == null)
22 : throw new ArgumentNullException("sourceData");
23 : if (logger == null)
24 : throw new ArgumentNullException("logger");
25 : if (settings == null)
26 : throw new ArgumentNullException("settings");
27 :
28 : _settings = settings;
29 : _sourceData = sourceData;
30 : _logger = logger;
31 : }
32 :
33 0 : public object Convert(Type destinationType)
34 : {
35 : if (destinationType == null)
36 : throw new ArgumentNullException("destinationType");
37 :
38 : if (destinationType == typeof(FormData))
39 : return _sourceData;
40 :
41 : var objResult = CreateObject(destinationType);
42 : return objResult;
43 : }
44 :
45 : private object CreateObject(Type destinationType, string propertyName = "")
46 : {
47 : object propValue = null;
48 :
49 : if (propertyName == null)
50 : {
51 : propertyName = "";
52 : }
53 :
54 : object buf;
55 : if (TryGetAsNotIndexedListOrArray(destinationType, propertyName, out buf)
56 : || TryGetFromFormData(destinationType, propertyName, out buf)
57 : || TryGetAsGenericDictionary(destinationType, propertyName, out buf)
58 : || TryGetAsIndexedGenericListOrArray(destinationType, propertyName, out buf)
59 : || TryGetAsCustomType(destinationType, propertyName, out buf))
60 : {
61 : propValue = buf;
62 : }
63 : else if (IsNotNullableValueType(destinationType)
64 : && IsNeedValidateMissedProperty(propertyName))
65 : {
66 : _logger.LogError(propertyName, "The value is required.");
67 : }
68 : else if (!IsFileOrConvertableFromString(destinationType))
69 : {
70 : _logger.LogError(propertyName, String.Format("Cannot parse type \"{0}\".", destinationType.FullName));
71 : }
72 :
73 : return propValue;
74 : }
75 :
76 : private bool TryGetAsNotIndexedListOrArray(Type destinationType, string propertyName, out object propValue)
77 : {
78 : propValue = null;
79 :
80 : Type genericListItemType;
81 : bool isGenericList = IsGenericListOrArray(destinationType, out genericListItemType);
82 : if (isGenericList)
83 : {
84 : var items = GetNotIndexedListItems(propertyName, genericListItemType);
85 : propValue = MakeList(genericListItemType, destinationType, items, propertyName);
86 : }
87 :
88 : return propValue != null;
89 : }
90 :
91 : private List<object> GetNotIndexedListItems(string propertyName, Type genericListItemType)
92 : {
93 : List<object> res;
94 : if (!TryGetListFromFormData(genericListItemType, propertyName, out res))
95 : {
96 : TryGetListFromFormData(genericListItemType, propertyName + "[]", out res);
97 : }
98 :
99 : return res ?? new List<object>();
100 : }
101 :
102 : private bool TryGetFromFormData(Type destinationType, string propertyName, out object propValue)
103 : {
104 : propValue = null;
105 : List<object> values;
106 : if (TryGetListFromFormData(destinationType, propertyName, out values))
107 : {
108 : propValue = values.FirstOrDefault();
109 : return true;
110 : }
111 : return false;
112 : }
113 :
114 : private bool TryGetListFromFormData(Type destinationType, string propertyName, out List<object> propValue)
115 : {
116 : bool existsInFormData = false;
117 : propValue = null;
118 :
119 : if (destinationType == typeof(HttpFile) || destinationType == typeof(byte[]))
120 : {
121 : var files = _sourceData.GetFiles(propertyName, _settings.CultureInfo);
122 : if (files.Any())
123 : {
124 : existsInFormData = true;
125 : propValue = new List<object>();
126 :
127 : foreach (var httpFile in files)
128 : {
129 : var item = destinationType == typeof(byte[])
130 : ? httpFile.Buffer
131 : : (object)httpFile;
132 :
133 : propValue.Add(item);
134 : }
135 : }
136 : }
137 : else
138 : {
139 : var values = _sourceData.GetValues(propertyName, _settings.CultureInfo);
140 : if (values.Any())
141 : {
142 : existsInFormData = true;
143 : propValue = new List<object>();
144 :
145 : foreach (var value in values)
146 : {
147 : object val;
148 : if(TryConvertFromString(destinationType, propertyName, value, out val))
149 : {
150 : propValue.Add(val);
151 : }
152 : }
153 : }
154 : }
155 :
156 : return existsInFormData;
157 : }
158 :
159 : private bool TryConvertFromString(Type destinationType, string propertyName, string val, out object propValue)
160 : {
161 : propValue = null;
162 : var typeConverter = destinationType.GetFromStringConverter();
163 : if (typeConverter == null)
164 : {
165 : _logger.LogError(propertyName, "Cannot find type converter for field - " + propertyName);
166 : }
167 : else
168 : {
169 : try
170 : {
171 : propValue = typeConverter.ConvertFromString(val, _settings.CultureInfo);
172 : return true;
173 : }
174 : catch (Exception ex)
175 : {
176 : _logger.LogError(propertyName, String.Format("Error parsing field \"{0}\": {1}", propertyName, ex.Message));
177 : }
178 : }
179 : return false;
180 : }
181 :
182 : private bool TryGetAsGenericDictionary(Type destinationType, string propertyName, out object propValue)
183 : {
184 : propValue = null;
185 : Type keyType, valueType;
186 : bool isGenericDictionary = IsGenericDictionary(destinationType, out keyType, out valueType);
187 : if (isGenericDictionary)
188 : {
189 : var dictType = typeof(Dictionary<,>).MakeGenericType(new[] { keyType, valueType });
190 : var add = dictType.GetMethod("Add");
191 :
192 : var pValue = Activator.CreateInstance(dictType);
193 :
194 : int index = 0;
195 : string origPropName = propertyName;
196 : bool isFilled = false;
197 : while (true)
198 : {
199 : string propertyKeyName = String.Format("{0}[{1}].Key", origPropName, index);
200 : var objKey = CreateObject(keyType, propertyKeyName);
201 : if (objKey != null)
202 : {
203 : string propertyValueName = String.Format("{0}[{1}].Value", origPropName, index);
204 : var objValue = CreateObject(valueType, propertyValueName);
205 :
206 : if (objValue != null)
207 : {
208 : add.Invoke(pValue, new[] { objKey, objValue });
209 : isFilled = true;
210 : }
211 : }
212 : else
213 : {
214 : break;
215 : }
216 : index++;
217 : }
218 :
219 : if (isFilled || IsRootProperty(propertyName))
220 : {
221 : propValue = pValue;
222 : }
223 : }
224 :
225 : return isGenericDictionary;
226 : }
227 :
228 : private bool TryGetAsIndexedGenericListOrArray(Type destinationType, string propertyName, out object propValue)
229 : {
230 : propValue = null;
231 : Type genericListItemType;
232 : bool isGenericList = IsGenericListOrArray(destinationType, out genericListItemType);
233 : if (isGenericList)
234 : {
235 : var items = GetIndexedListItems(propertyName, genericListItemType);
236 : propValue = MakeList(genericListItemType, destinationType, items, propertyName);
237 : }
238 :
239 : return isGenericList;
240 : }
241 :
242 : private object MakeList(Type genericListItemType, Type destinationType, List<object> listItems, string propertyName)
243 : {
244 : object result = null;
245 :
246 : if (listItems.Any() || IsRootProperty(propertyName))
247 : {
248 : var listType = typeof(List<>).MakeGenericType(genericListItemType);
249 :
250 : var add = listType.GetMethod("Add");
251 : var pValue = Activator.CreateInstance(listType);
252 :
253 : foreach (var listItem in listItems)
254 : {
255 : add.Invoke(pValue, new[] { listItem });
256 : }
257 :
258 : if (destinationType.IsArray)
259 : {
260 : var toArrayMethod = listType.GetMethod("ToArray");
261 : result = toArrayMethod.Invoke(pValue, new object[0]);
262 : }
263 : else
264 : {
265 : result = pValue;
266 : }
267 : }
268 :
269 : return result;
270 : }
271 :
272 : private List<object> GetIndexedListItems(string origPropName, Type genericListItemType)
273 : {
274 : var res = new List<object>();
275 : int index = 0;
276 : while (true)
277 : {
278 : var propertyName = String.Format("{0}[{1}]", origPropName, index);
279 : var objValue = CreateObject(genericListItemType, propertyName);
280 : if (objValue != null)
281 : {
282 : res.Add(objValue);
283 : }
284 : else
285 : {
286 : break;
287 : }
288 :
289 : index++;
290 : }
291 : return res;
292 : }
293 :
294 : private bool TryGetAsCustomType(Type destinationType, string propertyName, out object propValue)
295 : {
296 : propValue = null;
297 : bool isCustomNonEnumerableType = destinationType.IsCustomNonEnumerableType();
298 : if (isCustomNonEnumerableType && IsRootPropertyOrAnyChildPropertiesExistsInFormData(propertyName))
299 : {
300 : propValue = Activator.CreateInstance(destinationType);
301 : foreach (PropertyInfo propertyInfo in destinationType.GetProperties().Where(m => m.SetMethod != null))
302 : {
303 : var propName = (!String.IsNullOrEmpty(propertyName) ? propertyName + "." : "") + propertyInfo.Name;
304 :
305 : var objValue = CreateObject(propertyInfo.PropertyType, propName);
306 : if (objValue != null)
307 : {
308 : propertyInfo.SetValue(propValue, objValue);
309 : }
310 : }
311 : }
312 : return isCustomNonEnumerableType;
313 : }
314 :
315 :
316 : private bool IsGenericDictionary(Type type, out Type keyType, out Type valueType)
317 : {
318 : Type iDictType = type.GetInterface(typeof (IDictionary<,>).Name);
319 : if (iDictType != null)
320 : {
321 : var types = iDictType.GetGenericArguments();
322 : if (types.Length == 2)
323 : {
324 : keyType = types[0];
325 : valueType = types[1];
326 : return true;
327 : }
328 : }
329 :
330 : keyType = null;
331 : valueType = null;
332 : return false;
333 : }
334 :
335 : private bool IsGenericListOrArray(Type type, out Type itemType)
336 : {
337 : if (type.GetInterface(typeof(IDictionary<,>).Name) == null) //not a dictionary
338 : {
339 : if (type.IsArray)
340 : {
341 : itemType = type.GetElementType();
342 : return true;
343 : }
344 :
345 : Type iListType = type.GetInterface(typeof(ICollection<>).Name);
346 : if (iListType != null)
347 : {
348 : Type[] genericArguments = iListType.GetGenericArguments();
349 : if (genericArguments.Length == 1)
350 : {
351 : itemType = genericArguments[0];
352 : return true;
353 : }
354 : }
355 : }
356 :
357 : itemType = null;
358 : return false;
359 : }
360 :
361 : private bool IsFileOrConvertableFromString(Type type)
362 : {
363 : if (type == typeof (HttpFile))
364 : return true;
365 :
366 : return type.GetFromStringConverter() != null;
367 : }
368 :
369 : private bool IsNotNullableValueType(Type type)
370 : {
371 : if (!type.IsValueType)
372 : return false;
373 :
374 : return Nullable.GetUnderlyingType(type) == null;
375 : }
376 :
377 : private bool IsNeedValidateMissedProperty(string propertyName)
378 : {
379 : return _settings.ValidateNonNullableMissedProperty
380 : && !IsIndexedProperty(propertyName)
381 : && IsRootPropertyOrAnyParentsPropertyExistsInFormData(propertyName);
382 : }
383 :
384 : private bool IsRootPropertyOrAnyParentsPropertyExistsInFormData(string propertyName)
385 : {
386 : string parentName = "";
387 : if (propertyName != null)
388 : {
389 : int lastDotIndex = propertyName.LastIndexOf('.');
390 : if (lastDotIndex >= 0)
391 : {
392 : parentName = propertyName.Substring(0, lastDotIndex);
393 : }
394 : }
395 :
396 : bool result = IsRootPropertyOrAnyChildPropertiesExistsInFormData(parentName);
397 : return result;
398 : }
399 :
400 : private bool IsRootPropertyOrAnyChildPropertiesExistsInFormData(string propertyName)
401 : {
402 : if (IsRootProperty(propertyName))
403 : return true;
404 :
405 : string prefixWithDot = propertyName + ".";
406 : bool result = _sourceData.GetAllKeys().Any(m => m.StartsWith(prefixWithDot, true, _settings.CultureInfo));
407 : return result;
408 : }
409 :
410 : private bool IsRootProperty(string propertyName)
411 : {
412 : return propertyName == "";
413 : }
414 :
415 : private bool IsIndexedProperty(string propName)
416 : {
417 : return propName != null && propName.EndsWith("]");
418 : }
419 : }
420 : }
|