fbdegroot
8/26/2014 - 6:19 PM

Abstract model binding for Web API

Abstract model binding for Web API

using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Formatting;
using System.Net.Http.Headers;
using System.Reflection;
using System.Threading.Tasks;

public class AbstractMediaTypeFormatter : FormUrlEncodedMediaTypeFormatter
{
	public AbstractMediaTypeFormatter()
	{
		SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/json"));
	}

	public override bool CanReadType(Type type)
	{
		return type.IsAbstract;
	}

	public async override Task<object> ReadFromStreamAsync(Type type, Stream readStream, HttpContent content, IFormatterLogger formatterLogger)
	{
		object result = null;

		await Task.Run(() => {
			using (StreamReader reader = new StreamReader(readStream)) {
				string json = reader.ReadToEnd();

				Type typeToCreate = GetType(json, type);
				if (typeToCreate != null)
					result = JsonConvert.DeserializeObject(json, typeToCreate);
			}
		});

		return result;
	}

	// helpers
	private static ICustomTypeDescriptor GetTypeDescription(Type type)
	{
		return new AssociatedMetadataTypeTypeDescriptionProvider(type).GetTypeDescriptor(type);
	}

	private static Type GetType(string json, Type abstractType) {
		List<string> jsonProperties = JObject.Parse(json)
			.Properties()
			.Select(x => x.Name)
			.ToList();

		return Assembly.GetExecutingAssembly()
			.GetTypes()
			.Where(x => x.IsSubclassOf(abstractType))
			.Select(x => new {
				Type = x,
				Percentage = GetSuccessPercentage(x, jsonProperties)
			})
			.Where(x => x.Percentage >= 25)
			.OrderByDescending(x => x.Percentage)
			.Select(x => x.Type)
			.FirstOrDefault();
	}

	private static int GetSuccessPercentage(Type type, List<string> jsonProperties)
	{
		List<string> properties = GetTypeDescription(type).GetProperties().Cast<PropertyDescriptor>().Select(x => x.Name).ToList();

		int mappableProperties = jsonProperties.Count(x => properties.Any(y => y.Equals(x, StringComparison.InvariantCultureIgnoreCase)));

		return (int)(((decimal)mappableProperties / jsonProperties.Count) * 100m);
	}
}