using System; using System.Collections.Generic; using System.Reflection; namespace Emby.Server.Implementations.Services { /// /// Serializer cache of delegates required to create a type from a string map (e.g. for REST urls) /// public class StringMapTypeDeserializer { internal class PropertySerializerEntry { public PropertySerializerEntry(Action propertySetFn, Func propertyParseStringFn) { PropertySetFn = propertySetFn; PropertyParseStringFn = propertyParseStringFn; } public Action PropertySetFn; public Func PropertyParseStringFn; public Type PropertyType; } private readonly Type type; private readonly Dictionary propertySetterMap = new Dictionary(StringComparer.OrdinalIgnoreCase); public Func GetParseFn(Type propertyType) { if (propertyType == typeof(string)) return s => s; return _GetParseFn(propertyType); } private readonly Func _CreateInstanceFn; private readonly Func> _GetParseFn; public StringMapTypeDeserializer(Func createInstanceFn, Func> getParseFn, Type type) { _CreateInstanceFn = createInstanceFn; _GetParseFn = getParseFn; this.type = type; foreach (var propertyInfo in RestPath.GetSerializableProperties(type)) { var propertySetFn = TypeAccessor.GetSetPropertyMethod(type, propertyInfo); var propertyType = propertyInfo.PropertyType; var propertyParseStringFn = GetParseFn(propertyType); var propertySerializer = new PropertySerializerEntry(propertySetFn, propertyParseStringFn) { PropertyType = propertyType }; propertySetterMap[propertyInfo.Name] = propertySerializer; } } public object PopulateFromMap(object instance, IDictionary keyValuePairs) { string propertyName = null; string propertyTextValue = null; PropertySerializerEntry propertySerializerEntry = null; if (instance == null) instance = _CreateInstanceFn(type); foreach (var pair in keyValuePairs) { propertyName = pair.Key; propertyTextValue = pair.Value; if (string.IsNullOrEmpty(propertyTextValue)) { continue; } if (!propertySetterMap.TryGetValue(propertyName, out propertySerializerEntry)) { if (propertyName == "v") { continue; } continue; } if (propertySerializerEntry.PropertySetFn == null) { continue; } if (propertySerializerEntry.PropertyType == typeof(bool)) { //InputExtensions.cs#530 MVC Checkbox helper emits extra hidden input field, generating 2 values, first is the real value propertyTextValue = LeftPart(propertyTextValue, ','); } var value = propertySerializerEntry.PropertyParseStringFn(propertyTextValue); if (value == null) { continue; } propertySerializerEntry.PropertySetFn(instance, value); } return instance; } public static string LeftPart(string strVal, char needle) { if (strVal == null) return null; var pos = strVal.IndexOf(needle); return pos == -1 ? strVal : strVal.Substring(0, pos); } } internal class TypeAccessor { public static Action GetSetPropertyMethod(Type type, PropertyInfo propertyInfo) { if (!propertyInfo.CanWrite || propertyInfo.GetIndexParameters().Length > 0) return null; var setMethodInfo = propertyInfo.SetMethod; return (instance, value) => setMethodInfo.Invoke(instance, new[] { value }); } } }