Skip to content

Instantly share code, notes, and snippets.

@AlbertoMonteiro
Last active August 26, 2017 14:04
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save AlbertoMonteiro/5e27a344d6d9b2c45b646057027dcf44 to your computer and use it in GitHub Desktop.
Save AlbertoMonteiro/5e27a344d6d9b2c45b646057027dcf44 to your computer and use it in GitHub Desktop.
A "multipart/form-data" media type converter to a C# class
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