using Newtonsoft.Json; using System; using System.Collections.Generic; using System.Reflection; using System.Runtime.InteropServices.WindowsRuntime; using System.Threading.Tasks; using Windows.Foundation; using Windows.Foundation.Metadata; namespace Monaco.Helpers { /// /// Class to aid in accessing WinRT values from JavaScript. /// Not Thread Safe. /// [AllowForWeb] public sealed class ParentAccessor : IDisposable { private WeakReference parent; private Type typeinfo; private Dictionary actions; private Dictionary>> events; private List Assemblies { get; set; } = new List(); /// /// Constructs a new reflective parent Accessor for the provided object. /// /// Object to provide Property Access. public ParentAccessor(IParentAccessorAcceptor parent) { this.parent = new WeakReference(parent); typeinfo = parent.GetType(); actions = new Dictionary(); events = new Dictionary>>(); } /// /// Registers an action from the .NET side which can be called from within the JavaScript code. /// /// String Key. /// Action to perform. internal void RegisterAction(string name, Action action) { actions[name] = action; } /// /// Registers an event from the .NET side which can be called with the given jsonified string arguments within the JavaScript code. /// /// String Key. /// Event to call. internal void RegisterEvent(string name, Func> function) { events[name] = function; } /// /// Calls an Event registered before wit hthe . /// /// Name of event to call. /// JSON string Parameters. /// public IAsyncOperation CallEvent(string name, [ReadOnlyArray] string[] parameters) { if (events.ContainsKey(name)) { return events[name]?.Invoke(parameters).AsAsyncOperation(); } return (new Task(() => { return null; })).AsAsyncOperation(); } /// /// Adds an Assembly to use for looking up types by name for . /// /// Assembly to add. internal void AddAssemblyForTypeLookup(Assembly assembly) { Assemblies.Add(assembly); } /// /// Calls an Action registered before with . /// /// String Key. /// True if method was found in registration. public bool CallAction(string name) { if (actions.ContainsKey(name)) { actions[name]?.Invoke(); return true; } return false; } /// /// Returns the winrt primative object value for the specified Property. /// /// Property name on Parent Object. /// Property Value or null. public object GetValue(string name) { if (parent.TryGetTarget(out IParentAccessorAcceptor tobj)) { var propinfo = typeinfo.GetProperty(name); return propinfo?.GetValue(tobj); } return null; } public string GetJsonValue(string name) { if (parent.TryGetTarget(out IParentAccessorAcceptor tobj)) { var propinfo = typeinfo.GetProperty(name); var obj = propinfo?.GetValue(tobj); return JsonConvert.SerializeObject(obj, new JsonSerializerSettings() { NullValueHandling = NullValueHandling.Ignore }); } return "{}"; } /// /// Returns the winrt primative object value for a child property off of the specified Property. /// /// Useful for providing complex types to users of Parent but still access primatives in JavaScript. /// /// Parent Property name. /// Property's Property name to retrieve. /// Value of Child Property or null. public object GetChildValue(string name, string child) { if (parent.TryGetTarget(out IParentAccessorAcceptor tobj)) { // TODO: Support params for multi-level digging? var propinfo = typeinfo.GetProperty(name); var prop = propinfo?.GetValue(tobj); if (prop != null) { var childinfo = prop.GetType().GetProperty(child); return childinfo?.GetValue(prop); } } return null; } /// /// Sets the value for the specified Property. /// /// Parent Property name. /// Value to set. public void SetValue(string name, object value) { if (parent.TryGetTarget(out IParentAccessorAcceptor tobj)) { var propinfo = typeinfo.GetProperty(name); // TODO: Cache these? tobj.IsSettingValue = true; propinfo?.SetValue(tobj, value); tobj.IsSettingValue = false; } } /// /// Sets the value for the specified Property after deserializing the value as the given type name. /// /// /// /// public void SetValue(string name, string value, string type) { if (parent.TryGetTarget(out IParentAccessorAcceptor tobj)) { var propinfo = typeinfo.GetProperty(name); var typeobj = LookForTypeByName(type); var obj = JsonConvert.DeserializeObject(value, typeobj); tobj.IsSettingValue = true; propinfo?.SetValue(tobj, obj); tobj.IsSettingValue = false; } } private Type LookForTypeByName(string name) { // First search locally var result = Type.GetType(name); if (result != null) { return result; } // Search in Other Assemblies foreach (var assembly in Assemblies) { foreach (var typeInfo in assembly.ExportedTypes) { if (typeInfo.Name == name) { return typeInfo; } } } return null; } public void Dispose() { if (actions != null) { actions.Clear(); } actions = null; if (events != null) { events.Clear(); } events = null; } } //// TODO: Find better approach than this. Issue #21. /// /// Interface used on objects to be accessed. /// public interface IParentAccessorAcceptor { /// /// Property to tell object the value is being set by ParentAccessor. /// bool IsSettingValue { get; set; } } }