Skip to content

Instantly share code, notes, and snippets.

@ashmind
Last active August 29, 2015 14:14
Show Gist options
  • Save ashmind/20b234e2fd69db542beb to your computer and use it in GitHub Desktop.
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)
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
};
}
}
}
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; }
}
}
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; }
}
}
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