Last active
August 29, 2015 14:14
-
-
Save ashmind/20b234e2fd69db542beb to your computer and use it in GitHub Desktop.
WebApi Patch<T> Design (at the moment needs AshMind.Extensions and InfoOf from NuGet)
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.Linq; | |
using AshMind.Extensions; | |
using Newtonsoft.Json.Serialization; | |
namespace PatchDesignDemo { | |
public class ContractResolverWithPatchSupport : DefaultContractResolver { | |
private readonly IContractResolver _inner; | |
public ContractResolverWithPatchSupport(IContractResolver inner) { | |
_inner = inner; | |
} | |
protected override JsonContract CreateContract(Type objectType) { | |
var contract = _inner.ResolveContract(objectType); | |
if (!objectType.IsGenericTypeDefinedAs(typeof(Patch<>))) | |
return contract; | |
return CreatePatchContract(objectType, contract); | |
} | |
private JsonContract CreatePatchContract(Type patchType, JsonContract defaultPatchContract) { | |
var targetType = patchType.GetGenericArguments()[0]; | |
var targetContract = (JsonObjectContract) _inner.ResolveContract(targetType); | |
var patchContract = new JsonObjectContract(patchType) { | |
DefaultCreator = defaultPatchContract.DefaultCreator, | |
DefaultCreatorNonPublic = defaultPatchContract.DefaultCreatorNonPublic, | |
CreatedType = defaultPatchContract.CreatedType | |
}; | |
var valueProviderType = typeof(PatchValueProvider<>).MakeGenericType(targetType); | |
foreach (var targetProperty in targetContract.Properties) { | |
var patchProperty = Clone(targetProperty); | |
patchProperty.ValueProvider = (IValueProvider) Activator.CreateInstance(valueProviderType, | |
targetProperty.PropertyName, | |
patchProperty.ValueProvider | |
); | |
patchContract.Properties.Add(patchProperty); | |
} | |
return patchContract; | |
} | |
// TODO: Extension method or submit to JSON.NET itself | |
private static JsonProperty Clone(JsonProperty property) { | |
return new JsonProperty { | |
Converter = property.Converter, | |
DeclaringType = property.DeclaringType, | |
DefaultValue = property.DefaultValue, | |
DefaultValueHandling = property.DefaultValueHandling, | |
GetIsSpecified = property.GetIsSpecified, | |
HasMemberAttribute = property.HasMemberAttribute, | |
Ignored = property.Ignored, | |
IsReference = property.IsReference, | |
ItemConverter = property.ItemConverter, | |
ItemIsReference = property.ItemIsReference, | |
ItemReferenceLoopHandling = property.ItemReferenceLoopHandling, | |
ItemTypeNameHandling = property.ItemTypeNameHandling, | |
MemberConverter = property.MemberConverter, | |
NullValueHandling = property.NullValueHandling, | |
ObjectCreationHandling = property.ObjectCreationHandling, | |
Order = property.Order, | |
PropertyName = property.PropertyName, | |
PropertyType = property.PropertyType, | |
Readable = property.Readable, | |
ReferenceLoopHandling = property.ReferenceLoopHandling, | |
Required = property.Required, | |
SetIsSpecified = property.SetIsSpecified, | |
ShouldSerialize = property.ShouldSerialize, | |
TypeNameHandling = property.TypeNameHandling, | |
UnderlyingName = property.UnderlyingName, | |
ValueProvider = property.ValueProvider, | |
Writable = property.Writable | |
}; | |
} | |
} | |
} |
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.Linq; | |
using System.Linq.Expressions; | |
using AshMind.Extensions; | |
using InfoOf; | |
namespace PatchDesignDemo { | |
public class Patch<TTarget> { | |
public Patch() { | |
Values = new Dictionary<string, PatchValue<TTarget>>(); | |
} | |
// In case you want to validate something before applying the patch | |
public TResult GetValue<TResult>(Expression<Func<TTarget, TResult>> property) { | |
var name = Info.PropertyOf(property).Name; | |
TResult result; | |
if (!TryGetValue(name, out result)) | |
throw new Exception("Could not find property '" + name + "' in patch object."); | |
return result; | |
} | |
public TResult GetValueOrDefault<TResult>(Expression<Func<TTarget, TResult>> property) { | |
var name = Info.PropertyOf(property).Name; | |
TResult result; | |
if (!TryGetValue(name, out result)) | |
return default(TResult); | |
return result; | |
} | |
private bool TryGetValue<TResult>(string name, out TResult result) { | |
var patchValue = Values.GetValueOrDefault(name); | |
if (patchValue == null) { | |
result = default(TResult); | |
return false; | |
} | |
result = (TResult)patchValue.Value; | |
return true; | |
} | |
public void Apply(TTarget @object) { | |
foreach (var pair in Values) { | |
pair.Value.Apply(@object); | |
} | |
} | |
public IDictionary<string, PatchValue<TTarget>> Values { get; private set; } | |
} | |
} |
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.Linq; | |
namespace PatchDesignDemo { | |
public class PatchValue<TTarget> { | |
public PatchValue(object value, Action<TTarget> apply) { | |
Value = value; | |
Apply = apply; | |
} | |
public object Value { get; private set; } | |
public Action<TTarget> Apply { get; private set; } | |
} | |
} |
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 Newtonsoft.Json.Serialization; | |
namespace PatchDesignDemo { | |
public class PatchValueProvider<T> : IValueProvider { | |
private readonly string _propertyName; | |
private readonly IValueProvider _inner; | |
public PatchValueProvider(string propertyName, IValueProvider inner) { | |
_propertyName = propertyName; | |
_inner = inner; | |
} | |
public void SetValue(object target, object value) { | |
var patch = (Patch<T>)target; | |
patch.Values.Add(_propertyName, new PatchValue<T>( | |
value, actualTarget => _inner.SetValue(actualTarget, value) | |
)); | |
} | |
public object GetValue(object target) { | |
throw new NotSupportedException(); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment