/// <summary> /// Creates a new instance of this class /// </summary> /// <param name="jsVariableName"> /// The name of the variable to be used from the Javascript side to access the HybridMessaging /// object /// </param> public HybridMessagingHandler(string jsVariableName = "HybridMessaging") { HybridMessagingProxy = new ProxyClass(this); HybridMessagingBridge = new ClassBridge <ProxyClass>(); HybridMessagingBridge.PushJavascript += OnPushJavascript; HybridMessagingBridge.AddInstance(HybridMessagingProxy, jsVariableName); }
/// <summary> /// Initialize the handler and generates the needed Javascript code /// </summary> /// <param name="bridge">The <see cref="BridgeController" /> object requesting initialization</param> public virtual void Initialize(BridgeController bridge) { var builder = new StringBuilder(); builder.Append(ClassBridge.GenerateNameSpace(GenericType)); builder.Append(ClassBridge.GenerateProxyClass(Identification, true)); lock (Lock) { if (Methods != null) { if (Fields != null) { foreach (var field in Fields) { builder.Append(ClassBridge.GenerateProxyField(Identification, field)); } } foreach (var method in Methods) { builder.Append(ClassBridge.GenerateProxyMethod(Identification, method.Key, method.Value)); } } } OnPushJavascript(new FireJavascriptEventArgs(builder.ToString(), bridge)); }
/// <summary> /// Sends a message to the Javascript side and waits for the response /// </summary> /// <param name="messageString">Message identification, or string.Empty to send to all subscribers</param> /// <param name="argument">The argument to be send to the message subscriber</param> /// <typeparam name="TResult">The type of the expected result</typeparam> /// <typeparam name="TArgument">The type of the argument to send</typeparam> /// <returns>Returns the response of the last subscriber</returns> public virtual TResult Send <TResult, TArgument>(string messageString, TArgument argument) { var returnValue = HybridMessagingProxy.HandlerNewMessage(messageString, argument); try { return(ClassBridge.NormalizeVariable <TResult>(returnValue)); } catch (Exception) { return(default(TResult)); } }
/// <summary> /// The method to dispose this class /// </summary> protected virtual void Dispose(bool disposing) { if (Disposed) { return; } Disposed = true; if (disposing) { if (HybridMessagingBridge != null) { if (HybridMessagingProxy != null) { HybridMessagingBridge.RemoveInstance(HybridMessagingProxy); } HybridMessagingBridge.PushJavascript -= OnPushJavascript; } HybridMessagingProxy = null; HybridMessagingBridge = null; } }
/// <summary> /// Removes the requested instances /// </summary> /// <param name="instances">The instances to be removed from the Javascript side</param> /// <returns>Returns <see langword="this" /> instance to be used for other operations</returns> public virtual ClassBridge <T> RemoveInstance(params T[] instances) { foreach (var instance in instances.Where(instance => Instances.ContainsKey(instance))) { foreach (var variable in Instances[instance].ToArray()) { AddInstance(null, variable); } if (InstancesEventsDelegates.ContainsKey(instance)) { foreach (var eventDelegate in InstancesEventsDelegates[instance]) { eventDelegate.Key.RemoveEventHandler(instance, eventDelegate.Value); } InstancesEventsDelegates.Remove(instance); } Instances.Remove(instance); OnPushJavascript( new FireJavascriptEventArgs(ClassBridge.GenerateInstanceChange(Identification, GlobalPool.GetInstanceId(instance), true))); } return(this); }
/// <summary> /// Creates a new instance of this class /// </summary> /// <exception cref="InvalidGenericTypeException">Indicates that the passed generic type is not a valid class</exception> public ClassBridge() { if (!GenericType.IsClass) { throw new InvalidGenericTypeException(); } lock (Lock) { if (Methods == null) { Methods = GenericType.GetMethods() .Where( info => info.IsPublic && !info.IsGenericMethod) .ToDictionary(info => info, info => info.GetParameters()); } if (Constructors == null) { Constructors = GenericType.GetConstructors() .Where( info => info.IsPublic && !info.IsGenericMethod) .ToDictionary(info => info, info => info.GetParameters()); } if (Properties == null) { Properties = GenericType.GetProperties() .Where( info => Methods.ContainsKey(info.GetGetMethod()) || Methods.ContainsKey(info.GetSetMethod())) .ToList(); } if (Fields == null) { Fields = GenericType.GetFields().Where(info => info.IsPublic).ToList(); } if (Events == null) { Events = GenericType.GetEvents() .Where( info => Methods.ContainsKey(info.GetAddMethod()) && Methods.ContainsKey(info.GetRemoveMethod())) .ToList(); } foreach ( var info in Events.Where(info => info.GetAddMethod().IsStatic || info.GetRemoveMethod().IsStatic)) { var eventInvokerReturn = info.EventHandlerType.GetMethod("Invoke").ReturnType; if ((eventInvokerReturn == typeof(void)) || (eventInvokerReturn == typeof(object))) { var del = ClassBridge.CreateProxyDelegateForEvent(info, null, RaiseEvent); StaticEventsDelegates.Add(info, del); info.AddEventHandler(null, del); } else { var del = ClassBridge.CreateProxyDelegateForEvent(info, null, (instance, eventName, isVoid, eventArgs) => ClassBridge.NormalizeVariable(RaiseEvent(instance, eventName, isVoid, eventArgs), eventInvokerReturn, false)); StaticEventsDelegates.Add(info, del); info.AddEventHandler(null, del); } } if (SubEnumerations == null) { SubEnumerations = GenericType.GetNestedTypes(BindingFlags.Public) .Where(type => type.IsEnum) .Select(EnumBridge.FromType) .ToList(); foreach (var subEnum in SubEnumerations) { subEnum.PushJavascript += (sender, args) => OnPushJavascript(args); } } if (SubClasses == null) { SubClasses = GenericType.GetNestedTypes(BindingFlags.Public) .Where(type => type.IsClass && !typeof(Delegate).IsAssignableFrom(type)) .Select(ClassBridge.FromType) .ToList(); foreach (var subClasses in SubClasses) { subClasses.PushJavascript += (sender, args) => OnPushJavascript(args); } } } }
/// <summary> /// Registers an instance of the generic type and adds it to the list of registered instances /// </summary> /// <param name="instance">The instance of the generic type to ad</param> /// <param name="variableName">The name of the variable that is accessible from the Javascript side</param> /// <returns>Returns <see langword="this" /> instance to be used for other operations</returns> public virtual ClassBridge <T> AddInstance(T instance, string variableName) { if (instance != null) { if (!Instances.ContainsKey(instance)) { Instances.Add(instance, new List <string>()); if (!InstancesEventsDelegates.ContainsKey(instance)) { InstancesEventsDelegates.Add(instance, new Dictionary <EventInfo, Delegate>()); } foreach ( var info in Events.Where(info => !info.GetAddMethod().IsStatic&& !info.GetRemoveMethod().IsStatic)) { var eventDelegate = InstancesEventsDelegates[instance].FirstOrDefault(pair => pair.Key == info).Value; if (eventDelegate == null) { var eventInvokerReturn = info.EventHandlerType.GetMethod("Invoke").ReturnType; if ((eventInvokerReturn == typeof(void)) || (eventInvokerReturn == typeof(object))) { eventDelegate = ClassBridge.CreateProxyDelegateForEvent(info, instance, RaiseEvent); } else { eventDelegate = ClassBridge.CreateProxyDelegateForEvent(info, instance, (o, s, arg3, arg4) => ClassBridge.NormalizeVariable(RaiseEvent(o, s, arg3, arg4), eventInvokerReturn, false)); } InstancesEventsDelegates[instance].Add(info, eventDelegate); } info.AddEventHandler(instance, eventDelegate); } OnPushJavascript( new FireJavascriptEventArgs(ClassBridge.GenerateInstanceChange(Identification, GlobalPool.GetInstanceId(instance), false))); } } if (!string.IsNullOrWhiteSpace(variableName)) { foreach ( var key in Instances.Keys.Where(key => key != instance) .Where(key => Instances[key].Contains(variableName)) .ToArray()) { RemoveInstance(variableName, key as T); } if (instance != null) { if (!Instances[instance].Contains(variableName)) { Instances[instance].Add(variableName); OnPushJavascript( new FireJavascriptEventArgs(ClassBridge.GenerateInstanceVariable(Identification, GlobalPool.GetInstanceId(instance), variableName))); } } else { OnPushJavascript( new FireJavascriptEventArgs(ClassBridge.GenerateInstanceVariable(Identification, null, variableName))); } } return(this); }
/// <summary> /// Initialize the handler and generates the needed Javascript code /// </summary> /// <param name="bridge">The <see cref="BridgeController" /> object requesting initialization</param> public virtual void Initialize(BridgeController bridge) { var builder = new StringBuilder(); builder.Append(ClassBridge.GenerateNameSpace(GenericType)); builder.Append(ClassBridge.GenerateProxyClass(Identification, false)); lock (Lock) { if (Fields != null) { foreach (var field in Fields) { builder.Append(ClassBridge.GenerateProxyField(Identification, field)); } } if (Methods != null) { if (Properties != null) { foreach (var property in Properties) { var get = property.GetGetMethod(); if ((get != null) && !Methods.ContainsKey(get)) { get = null; } var set = property.GetSetMethod(); if ((set != null) && !Methods.ContainsKey(set)) { set = null; } builder.Append(ClassBridge.GenerateProxyProperty(Identification, property, get, set)); } } foreach ( var method in Methods.Where( pair => (Events == null) || !Events.Any( info => (pair.Key == info.GetAddMethod()) || (pair.Key == info.GetRemoveMethod())))) { builder.Append(ClassBridge.GenerateProxyMethod(Identification, method.Key, method.Value)); } } if (Events != null) { foreach (var info in Events) { var isStatic = info.GetAddMethod().IsStatic || info.GetRemoveMethod().IsStatic; builder.Append(ClassBridge.GenerateProxyField(Identification, info.Name, isStatic, true, new object[0])); builder.Append(ClassBridge.GenerateProxyEventMethods(Identification, info, isStatic)); } } } foreach (var instance in Instances.Where(pair => pair.Key != null).ToArray()) { var instanceId = GlobalPool.GetInstanceId(instance.Key); if (instanceId != null) { builder.AppendLine(ClassBridge.GenerateInstanceChange(Identification, instanceId, false)); foreach (var variableName in instance.Value.Where(s => !string.IsNullOrWhiteSpace(s))) { builder.AppendLine(ClassBridge.GenerateInstanceVariable(Identification, instanceId, variableName)); } } } OnPushJavascript(new FireJavascriptEventArgs(builder.ToString(), bridge)); lock (Lock) { if (SubEnumerations != null) { foreach (var subEnum in SubEnumerations) { subEnum.Initialize(bridge); } } if (SubClasses != null) { foreach (var subClass in SubClasses) { subClass.Initialize(bridge); } } } }
/// <summary> /// Handles the passed request and returns the result /// </summary> /// <param name="method">The method name to handle</param> /// <param name="parameters">The method parameters</param> /// <param name="hasResult">A boolean value indicting if the handling process resulted in a value</param> /// <returns>Returns the value that created from the handling of the request</returns> public virtual object InterceptRequest(string method, Dictionary <string, object> parameters, out bool hasResult) { hasResult = false; MethodInfo methodInfo = null; ConstructorInfo constructorInfo = null; var methodParameters = new object[0]; FieldInfo fieldInfo = null; object fieldValue = null; object classInstance = null; var methodParts = method.Split('/'); if (methodParts.Length > 1) { method = string.Join("/", methodParts, 1, methodParts.Length - 1); classInstance = Instances.FirstOrDefault( instance => GlobalPool.GetInstanceId(instance.Key) == methodParts[0]).Key; } lock (Lock) { if (string.IsNullOrWhiteSpace(method)) { if ((Constructors != null) && parameters.ContainsKey("arguments")) { var constructorParameters = parameters["arguments"] as JArray; foreach (var pair in Constructors) { var matchedArguments = 0; try { var param = new List <object>(); for (var i = 0; i < pair.Value.Length; i++) { if ((constructorParameters != null) && (i < constructorParameters.Count)) { param.Add(ClassBridge.NormalizeVariable(constructorParameters.ToArray()[i], pair.Value[i].ParameterType, true)); matchedArguments++; } else { if (pair.Value[i].IsOptional) { param.Add(pair.Value[i].DefaultValue); } else { break; } } } if ((param.Count != pair.Value.Length) || (matchedArguments != (constructorParameters?.Count ?? 0))) { continue; } methodParameters = param.ToArray(); } catch (InvalidCastException) { // ignore } constructorInfo = pair.Key; } } } else { if (Methods != null) { foreach (var pair in Methods.Where(pair => pair.Key.Name.Equals(method))) { var matchedArguments = 0; try { var param = new List <object>(); for (var i = 0; i < pair.Value.Length; i++) { if (i < parameters.Count) { if (pair.Value[i].Name == parameters.Keys.ToArray()[i]) { param.Add(ClassBridge.NormalizeVariable(parameters.Values.ToArray()[i], pair.Value[i].ParameterType, true)); matchedArguments++; } else { break; } } else { if (pair.Value[i].IsOptional) { param.Add(pair.Value[i].DefaultValue); } else { break; } } } if ((param.Count != pair.Value.Length) || (matchedArguments != parameters.Count)) { continue; } methodParameters = param.ToArray(); } catch (InvalidCastException) { // ignore } methodInfo = pair.Key; } } if ((methodInfo == null) && (Fields != null)) { foreach ( var field in Fields.Where(info => info.Name.Equals(method, StringComparison.OrdinalIgnoreCase))) { if (parameters.Keys.Contains("value")) { try { fieldValue = ClassBridge.NormalizeVariable(parameters["value"], field.FieldType, true); } catch (InvalidCastException) { // ignore } } fieldInfo = field; } } } } if (constructorInfo != null) { var newObject = constructorInfo.Invoke(methodParameters) as T; if (newObject != null) { AddInstance(newObject); var instanceId = GlobalPool.GetInstanceId(newObject); if (!string.IsNullOrWhiteSpace(instanceId)) { hasResult = true; return(instanceId); } } return(null); } if (methodInfo != null) { if (methodInfo.ReturnType != typeof(void)) { hasResult = true; return(methodInfo.Invoke(classInstance, methodParameters.ToArray())); } // Let's not block the UI/Javascript thread if there is no result for the requested method Task.Factory.StartNew(() => methodInfo.Invoke(classInstance, methodParameters.ToArray())); return(null); } if (fieldInfo != null) { if (fieldValue != null) { fieldInfo.SetValue(classInstance, fieldValue); return(null); } hasResult = true; return(fieldInfo.GetValue(classInstance)); } return(null); }
/// <summary> /// Initialize the handler and generates the needed Javascript code /// </summary> /// <param name="bridge">The <see cref="BridgeController" /> object requesting initialization</param> public virtual void Initialize(BridgeController bridge) { var identification = typeof(ProxyClass).FullName.Replace('+', '.'); var builder = new StringBuilder(); HybridMessagingBridge.Initialize(bridge); builder.Append(ClassBridge.GenerateProxyField(identification, "__subscriptions", false, true, new Dictionary <string, object>())); builder.Append(ClassBridge.GenerateProxyMethod(identification, "Subscribe", new[] { new ClassBridge.InternalParameterInfo("messageString"), new ClassBridge.InternalParameterInfo("callback") }, @" var_messageString = var_messageString.toLowerCase().trim(); if (this.__subscriptions[var_messageString] === undefined) { this.__subscriptions[var_messageString] = []; } var index = this.__subscriptions[var_messageString].indexOf(var_callback); if (index < 0) { this.__subscriptions[var_messageString].push(var_callback); return true; } return false;", false)); builder.Append(ClassBridge.GenerateProxyMethod(identification, "UnSubscribe", new[] { new ClassBridge.InternalParameterInfo("messageString"), new ClassBridge.InternalParameterInfo("callback") }, @" var_messageString = var_messageString.toLowerCase().trim(); if (this.__subscriptions[var_messageString] === undefined) { return false; } var index = this.__subscriptions[var_messageString].indexOf(var_callback); if (index < 0) { return false; } this.__subscriptions[var_messageString].splice(index, 1); if (this.__subscriptions[var_messageString].length < 1) { this.__subscriptions[var_messageString] = undefined; } return true;", false)); builder.Append(ClassBridge.GenerateProxyMethod(identification, "__raise", new[] { new ClassBridge.InternalParameterInfo("messageString"), new ClassBridge.InternalParameterInfo("arguments", true, null) }, @" var result = undefined; var_messageString = var_messageString.toLowerCase().trim(); for (var messageString in this.__subscriptions) { if (!messageString || !var_messageString || messageString == var_messageString) { for (index = 0; index < this.__subscriptions[messageString].length; ++index) { result = this.__subscriptions[messageString][index].apply(this, [ var_arguments ]); } } } return result;", false)); var instanceId = GlobalPool.GetInstanceId(HybridMessagingProxy); if (!string.IsNullOrEmpty(instanceId)) { builder.AppendFormat("\r\n{0}.__instances[\"{1}\"].add_NewMessage({0}.__instances[\"{1}\"].__raise);", identification, instanceId); } OnPushJavascript(this, new FireJavascriptEventArgs(builder.ToString())); }
/// <summary> /// The method to be called when a new message received from the Javascript side /// </summary> /// <param name="messageString">The message identification string, or string.Empty to match all</param> /// <param name="arguments">The message arguments, or null</param> /// <returns>Returns the response of the last subscriber</returns> protected virtual object OnNewMessage(string messageString, object arguments) { messageString = messageString.ToLower().Trim(); object result = null; Delegate[] delegates; lock (Lock) { delegates = Subscriptions.Where( pair => string.IsNullOrEmpty(messageString) || string.IsNullOrEmpty(pair.Key) || messageString.Equals(pair.Key, StringComparison.CurrentCulture)) .SelectMany(pair => pair.Value) .ToArray(); } foreach (var subscription in delegates) { var methodInfo = subscription.Method; var methodArguments = new List <object>(); var methodResultType = methodInfo.ReturnType; try { var methodParameters = methodInfo.GetParameters(); var failed = false; if (methodParameters.Length > 0) { foreach (var methodParameter in methodParameters) { if (methodArguments.Count == 0 && !(methodParameter.IsOptional && arguments == null)) { methodArguments.Add(arguments == null ? null : ClassBridge.NormalizeVariable(arguments, methodParameter.ParameterType, true)); } else if (methodParameter.IsOptional) { methodArguments.Add(methodParameter.DefaultValue); } else { failed = true; break; } } if (failed) { continue; } } } catch (Exception) { continue; } if (methodInfo == null) { continue; } if (methodResultType == typeof(void)) { // If we don't need the result of this method, we better run it in a new thread, so the UI thread don't get blocked Task.Factory.StartNew(() => methodInfo.Invoke(subscription.Target, methodArguments.ToArray())); } else { var methodResult = methodInfo.Invoke(subscription.Target, methodArguments.ToArray()); result = methodResult == null ? null : ClassBridge.NormalizeVariable(methodResult, methodResultType, true); } } return(result); }