Instantly share code, notes, and snippets.
Last active
April 16, 2017 20:54
-
Star
0
(0)
You must be signed in to star a gist -
Fork
0
(0)
You must be signed in to fork a gist
-
Save sgoguen/7e44aa43538da427611e1d96721c906b to your computer and use it in GitHub Desktop.
This file contains hidden or 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
public class Nav { | |
public static HelperResult NavAndPrint(object obj, string path) { | |
var printer = Nav.GetPrinter().SetValue(obj); | |
return printer.NavigateTo(path).TryToShowObject(); | |
} | |
private static UiContext GetPrinter() { | |
return new UiContext( | |
displayRules: new Func<UiContext, Maybe<HelperResult>>[] { | |
TryBasicTypes, TryPrintMethod, TrySeqDefault, TryDefault | |
}, | |
navRules: new Func<string, object, Maybe<object>>[] { | |
TryGetProperty, TryIndexer, TryDictionary, | |
TryGetQueryable, TryGetArray, TryGetEnumerable | |
} | |
); | |
} | |
private static object CatchError<T>(Func<T> f) { | |
try { | |
return f(); | |
} catch (Exception ex) { | |
return ex; | |
} | |
} | |
private static Maybe<object> TryGetProperty(string name, object input) { | |
var getter = GetMemberGetters(input).FirstOrDefault(g => g.Name == name); | |
if (getter == null) | |
return None<object>(); | |
return Some(getter.GetPropValue(input)); | |
} | |
private static Maybe<object> TryGetArray(string index, object input) { | |
try { | |
int rowIndex; | |
if (!int.TryParse(index, out rowIndex)) | |
return None<object>(); | |
var arr = input as object[]; | |
if (arr == null || rowIndex > arr.Length) return None<object>(); | |
return Some(arr[rowIndex]); | |
} catch (Exception ex) { | |
return None<object>(); | |
} | |
} | |
private static Maybe<object> TryGetEnumerable(string index, object input) { | |
try { | |
int rowIndex; | |
if (!int.TryParse(index, out rowIndex)) | |
return None<object>(); | |
var arr = (input as IEnumerable).OfType<object>(); | |
return Some(arr.Skip(rowIndex).First()); | |
} catch (Exception ex) { | |
return None<object>(); | |
} | |
} | |
private static Maybe<object> TryGetQueryable(string index, object input) { | |
try { | |
int rowIndex; | |
if (!int.TryParse(index, out rowIndex)) | |
return None<object>(); | |
var arr = (input as IQueryable).OfType<object>(); | |
return Some(arr.Skip(rowIndex).First()); | |
} catch (Exception) { | |
return None<object>(); | |
} | |
} | |
private static Maybe<object> TryIndexer(string index, object input) { | |
if (input == null) return None<object>(); | |
var indexer = GetIndexer(input.GetType()); | |
if (!indexer.HasValue) return None<object>(); | |
return Some(indexer.Value(index, input)); | |
} | |
private static Maybe<object> TryDictionary(string index, object input) { | |
try { | |
var dict = input as IDictionary; | |
if (dict == null || !dict.Contains(index)) | |
return None<object>(); | |
return Some(dict[index]); | |
} catch (Exception) { | |
return None<object>(); | |
} | |
} | |
private static bool IsBasicProperty(PropertyInfo p) { | |
return p.IsSpecialName == false && p.GetIndexParameters().Length == 0 | |
&& p.CanRead && p.IsSpecialName == false; | |
} | |
private static bool IsPrimitive(object o) => (o == null) || IsPrimitive(o.GetType()); | |
private static bool IsPrimitive(Type t) { | |
var list = new[] { | |
typeof(int), typeof(string), typeof(DateTime), typeof(long), typeof(uint), typeof(char), | |
typeof(double), typeof(decimal), typeof(uint), typeof(byte), typeof(float), typeof(sbyte), typeof(short), | |
typeof(ulong), typeof(ushort), typeof(bool) | |
}; | |
return list.Contains(t); | |
} | |
private static bool IsList(Type t) { | |
if (IsPrimitive(t)) | |
return false; | |
return t.IsArray || t.GetInterfaces().Contains(typeof(IEnumerable)); | |
} | |
private static MemberGetter[] GetMemberGetters(object o) { | |
if (o == null) | |
return new MemberGetter[] { }; | |
return GetMemberGetters(o.GetType()); | |
} | |
private static MemberGetter[] GetMemberGetters(Type objectType) { | |
var flags = BindingFlags.Public | BindingFlags.Instance; | |
var properties = from p in objectType.GetProperties(flags) | |
where IsBasicProperty(p) | |
let param = Expression.Parameter(typeof(object), "x") | |
let cparam = Expression.Convert(param, objectType) | |
let isType = Expression.TypeIs(param, objectType) | |
let getProp = Expression.Convert(Expression.Property(cparam, p), typeof(object)) | |
let check = Expression.Lambda<Func<object, object>>(Expression.Condition(isType, getProp, Expression.Constant(null)), param).Compile() | |
select new MemberGetter { | |
Name = p.Name, | |
Type = p.PropertyType, | |
GetPropValue = check | |
}; | |
var fields = from f in objectType.GetFields(flags) | |
select new MemberGetter { | |
Name = f.Name, | |
Type = f.FieldType, | |
GetPropValue = new Func<object, object>(o => f.GetValue(o)) | |
}; | |
return properties.Concat(fields).ToArray(); | |
} | |
private static Maybe<Func<string, object, object>> GetIndexer(Type t) { | |
if (t == null) | |
return None<Func<string, object, object>>(); | |
var method = (from m in t.GetMethods() | |
where m.IsSpecialName && m.Name == "get_Item" | |
let parameters = m.GetParameters() | |
where parameters.Length == 1 | |
where parameters[0].ParameterType == typeof(string) | |
let indexParam = Expression.Parameter(typeof(string), "index") | |
let objectParam = Expression.Parameter(typeof(object), "obj") | |
let objToType = Expression.Convert(objectParam, t) | |
let isType = Expression.TypeIs(objectParam, t) | |
let call = Expression.Call(objToType, m, indexParam) | |
let convertCall = Expression.Convert(call, typeof(object)) | |
let getIndex = Expression.Lambda<Func<string, object, object>>( | |
Expression.Condition(isType, convertCall, Expression.Constant(null)), | |
indexParam, objectParam).Compile() | |
select getIndex | |
).FirstOrDefault(); | |
if (method == null) | |
return None<Func<string, object, object>>(); | |
return Some(method); | |
} | |
public class MemberGetter { | |
public string Name { get; set; } | |
public Type Type { get; set; } | |
public bool IsPrimitive => IsPrimitive(Type); | |
public bool IsList => IsList(Type); | |
public Func<object, object> GetPropValue { get; set; } | |
} | |
private static Maybe<T> Some<T>(T value) => new Maybe<T>(value); | |
private static Maybe<T> None<T>() => new Maybe<T>(false); | |
public struct Maybe<T> { | |
public bool HasValue { get; private set; } | |
public T Value { get; private set; } | |
public Maybe(bool ignore) { | |
HasValue = false; | |
Value = default(T); | |
} | |
public Maybe(T value) { | |
HasValue = true; | |
Value = value; | |
} | |
} | |
public struct UiContext { | |
private Func<UiContext, Maybe<HelperResult>>[] DisplayRules; | |
private Func<string, object, Maybe<object>>[] NavRules; | |
public readonly string[] Path; | |
public readonly object Value; | |
public readonly Type ObjectType; | |
public UiContext(Func<UiContext, Maybe<HelperResult>>[] displayRules, Func<string, object, Maybe<object>>[] navRules) { | |
DisplayRules = displayRules; | |
NavRules = navRules; | |
Path = new string[] { }; | |
Value = null; | |
ObjectType = null; | |
} | |
private UiContext(object value, string[] path, Func<UiContext, Maybe<HelperResult>>[] displayRules, Func<string, object, Maybe<object>>[] navRules) { | |
DisplayRules = displayRules; | |
NavRules = navRules; | |
Path = path; | |
Value = value; | |
ObjectType = value?.GetType(); | |
} | |
public UiContext SetValue(object value) { | |
return new UiContext(value, this.Path, this.DisplayRules, this.NavRules); | |
} | |
public UiContext SetDisplayRules(params Func<UiContext, Maybe<HelperResult>>[] displayRules) { | |
DisplayRules = displayRules; | |
return this; | |
} | |
public UiContext SetNavRules(params Func<string, object, Maybe<object>>[] navRules) { | |
NavRules = navRules; | |
return this; | |
} | |
public UiContext AddPath(string path, object value) { | |
var newPath = Path.Concat(new string[] { path }).ToArray(); | |
return new UiContext(value, newPath, DisplayRules, NavRules); | |
} | |
public HelperResult TryToShowObject() { | |
foreach (var rule in DisplayRules) { | |
var result = rule(this); | |
if (result.HasValue) { | |
return result.Value; | |
} | |
} | |
throw new Exception(String.Format("Error displaying object with {0} rules", DisplayRules.Length)); | |
} | |
private Maybe<UiContext> TryNavRules(string path) { | |
foreach (var f in NavRules) { | |
var r = f(path, this.Value); | |
if (r.HasValue) | |
return Some(this.AddPath(path, r.Value)); | |
} | |
return None<UiContext>(); | |
} | |
public UiContext NavigateTo(string path) { | |
path = path ?? ""; | |
var paths = path.Split('/').Where(p => !String.IsNullOrWhiteSpace(p)).ToArray(); | |
return NavigatePath(paths); | |
} | |
public UiContext NavigatePath(string[] path) { | |
var obj = this.Value; | |
if (obj == null || path == null) return this.SetValue(null); | |
var currentPath = new List<string>(); | |
var result = this; | |
foreach (var p in path) { | |
if (result.Value == null) | |
return result; | |
currentPath.Add(p); | |
//var resultType = result.GetType(); | |
// We want to try fetching the next object by checking things in this order: | |
// Property,dictionary,array,list,iqueryable,ienumerable | |
try { | |
var r = result.TryNavRules(p); | |
if (r.HasValue) { | |
result = r.Value; | |
continue; | |
} | |
} catch (Exception ex) { | |
throw new Exception($"Member not found {String.Join("/", currentPath.ToArray())}"); | |
} | |
throw new Exception($"Member not found {String.Join("/", currentPath.ToArray())}"); | |
} | |
return result; | |
} | |
} | |
private static Maybe<HelperResult> TryPrintMethod(UiContext context) { | |
var method = context.ObjectType.GetMethods().FirstOrDefault(m => m.Name == "Print"); | |
if (method == null || method.IsSpecialName) return None<HelperResult>(); | |
if (method.ReturnType != typeof(HelperResult) && method.ReturnType != typeof(IHtmlString)) return None<HelperResult>(); | |
if (method.GetParameters().Length != 0) return None<HelperResult>(); | |
var result = method.Invoke(context.Value, new object[] { }); | |
if (result == null) return None<HelperResult>(); | |
if (result is IHtmlString) { | |
var htmlString = (IHtmlString)result; | |
return Some(new HelperResult(a => a.Write(htmlString.ToHtmlString()))); | |
} | |
return Some((HelperResult)result); | |
} | |
private static Maybe<HelperResult> TryBasicTypes(UiContext context) { | |
if (context.ObjectType == null || IsPrimitive(context.ObjectType)) { | |
return Some(ShowValue(context)); | |
} else if (context.Value is HelperResult) { | |
return Some((HelperResult)context.Value); | |
} else if (context.Value is IHtmlString) { | |
var htmlString = (IHtmlString)context.Value; | |
return Some(new HelperResult(tw => tw.Write(htmlString.ToHtmlString()))); | |
} else { | |
return None<HelperResult>(); | |
} | |
} | |
private static HelperResult ShowValue(UiContext context) { | |
if (context.Value == null) { | |
return new HelperResult(tw => tw.Write("<span > (null) </ span >")); | |
} else if (IsPrimitive(context.Value)) { | |
return new HelperResult(tw => tw.Write($"<span>{context.Value}</span>")); | |
} else { | |
var url = $"/Dev/{String.Join("/", context.Path)}"; | |
return new HelperResult(tw => tw.Write($"<a href=\"{url}\">{context.Value.GetType().Name}...</a>")); | |
} | |
} | |
private static Maybe<HelperResult> TryTable(UiContext context) { | |
if (context.Value is System.Data.Linq.ITable) { | |
var table = (System.Data.Linq.ITable)context.Value; | |
var metaType = table.Context.Mapping.GetMetaType(table.ElementType); | |
return Some(DumpToResult(metaType)); | |
} | |
return None<HelperResult>(); | |
} | |
private static Maybe<HelperResult> TrySeqDefault(UiContext context) { | |
if (context.Value is IQueryable) { | |
//var list = ((IQueryable) context.Value).OfType<object>().Take(100); | |
return Some(DumpToResult(context.Value)); | |
} | |
return None<HelperResult>(); | |
} | |
private static Maybe<HelperResult> TryDefault(UiContext context) { | |
return Some(DefaultObject(context)); | |
} | |
private static HelperResult DefaultObject(UiContext context) { | |
return DumpToResult(from p in GetMemberGetters(context.Value) | |
let newContext = context.AddPath(p.Name, p.GetPropValue(context.Value)) | |
let value = ShowValue(newContext) | |
select new { p.Name, Value = value }); | |
} | |
private static HelperResult DumpToResult<T>(T obj) { | |
return new HelperResult(tw => tw.Write(Util.ToHtmlString(obj))); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment