Last active
August 26, 2017 14:04
-
-
Save AlbertoMonteiro/5e27a344d6d9b2c45b646057027dcf44 to your computer and use it in GitHub Desktop.
A "multipart/form-data" media type converter to a C# class
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using System.IO; | |
using System.Linq; | |
using System.Net; | |
using System.Net.Http; | |
using System.Net.Http.Formatting; | |
using System.Net.Http.Headers; | |
using System.Reflection; | |
using System.Threading.Tasks; | |
using System.Web.Http; | |
using YourNamespace.Extensions; | |
using static System.ComponentModel.TypeDescriptor; | |
using static System.Linq.Expressions.Expression; | |
using static System.String; | |
namespace YourNamespace.MediaTypeFormatters | |
{ | |
public interface IFileInputModel | |
{ | |
FileUpload[] Anexos { get; set; } | |
} | |
public sealed class FileUpload | |
{ | |
public string FileName { get; } | |
public string Extensao { get; } | |
public string MediaType { get; } | |
public long? FileSize { get; } | |
public MemoryStream Buffer { get; } | |
public FileUpload(HttpContent fileContent) | |
{ | |
var headers = fileContent.Headers; | |
MediaType = headers.ContentType.MediaType; | |
FileName = headers.ContentDisposition.FileName?.Replace("\"", ""); | |
FileSize = headers.ContentLength; | |
Extensao = Path.GetExtension(FileName?.RemoveInvalidFilePathCharacters()); //From YourNamespace.Extensions | |
using (var imgstream = fileContent.ReadAsStreamAsync().Result) | |
Buffer = ReadFully(imgstream); | |
} | |
private static MemoryStream ReadFully(Stream input) | |
{ | |
var ms = new MemoryStream(); | |
input.CopyTo(ms); | |
ms.Seek(0, SeekOrigin.Begin); | |
return ms; | |
} | |
} | |
public class FileMediaFormatter<T> : MediaTypeFormatter | |
where T : IFileInputModel | |
{ | |
private static readonly Type Type = typeof(T); | |
private static readonly Dictionary<string, (Action<T, object> action, Type type)> Setters = FillSetters(Type.GetProperties()); | |
private static readonly Func<T> CreateNewInstance = Lambda<Func<T>>(New(Type.GetConstructors().First())).Compile(); | |
public FileMediaFormatter() | |
=> SupportedMediaTypes.Add(new MediaTypeHeaderValue("multipart/form-data")); | |
public override bool CanReadType(Type type) => type == Type; | |
public override bool CanWriteType(Type type) => false; | |
public override async Task<object> ReadFromStreamAsync(Type type, Stream readStream, HttpContent content, IFormatterLogger formatterLogger) | |
{ | |
if (content.IsMimeMultipartContent()) | |
return await ProcessMultipartContent(content); | |
throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType); | |
} | |
private static async Task<object> ProcessMultipartContent(HttpContent content) | |
{ | |
var parts = await content.ReadAsMultipartAsync(); | |
var model = CreateNewInstance(); | |
foreach (var httpContent in parts.Contents.Where(h => h.Headers.ContentType == null)) | |
{ | |
if (!Setters.ContainsKey(httpContent.Headers.ContentDisposition.Name)) | |
continue; | |
await SetValueInProperty(httpContent, model); | |
} | |
model.Anexos = parts.Contents.Where(x => !IsNullOrWhiteSpace(x.Headers.ContentType?.MediaType)).Select(fileContent => new FileUpload(fileContent)).ToArray(); | |
return model; | |
} | |
private static async Task SetValueInProperty(HttpContent httpContent, T model) | |
{ | |
var (propSetAction, propType) = Setters[httpContent.Headers.ContentDisposition.Name]; | |
var value = await httpContent.ReadAsStringAsync(); | |
if (propType.IsGenericType && Nullable.GetUnderlyingType(propType) == null) | |
{ | |
var typeArguments = propType.GetGenericArguments().FirstOrDefault(t => t != typeof(FileUpload)); | |
if (typeArguments == null || !typeof(IEnumerable<>).MakeGenericType(typeArguments).IsAssignableFrom(propType)) return; | |
if (typeof(long).IsAssignableFrom(typeArguments)) | |
propSetAction(model, IsNullOrEmpty(value) ? new List<long>() : value.Split(',').Select(long.Parse).ToList()); | |
else if (typeof(string).IsAssignableFrom(typeArguments)) | |
propSetAction(model, value.Split(',').ToList()); | |
else | |
throw new NotImplementedException($"Conversão não feita para o tipo {typeArguments.FullName}"); | |
} | |
else | |
propSetAction(model, GetConverter(propType).ConvertFromString(value)); | |
} | |
private static Dictionary<string, (Action<T, object> action, Type type)> FillSetters(IEnumerable<PropertyInfo> propertyInfos) | |
=> propertyInfos.ToDictionary(p => $"\"{p.Name}\"", p => | |
{ | |
var instance = Parameter(Type, "obj"); | |
var value = Parameter(typeof(object), "value"); | |
var action = Lambda<Action<T, object>>(Assign(Property(instance, p), Convert(value, p.PropertyType)), instance, value).Compile(); | |
return (action, p.PropertyType); | |
}); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment