/// <summary> /// Creates a view model. /// </summary> /// <param name="vmId">Identifies the view model.</param> /// <param name="vmArg">Optional view model's initialization argument.</param> /// <param name="vmNamespace">Optional view model type's namespace.</param> /// <returns>View model instance.</returns> protected virtual BaseVM CreateVM(string vmId, object vmArg = null, string vmNamespace = null) { // If the namespace argument is given, try to resolve the view model type to that namespace. vmNamespace = vmNamespace ?? ExtractNamespace(ref vmArg); // If the view model Id is in the form of a delimited path, it has a master view model. BaseVM masterVM = null; var path = vmId.Split('.'); if (path.Length > 1) { // Get the master view model; create the instance if it doesn't exist. var masterVMId = vmId.Remove(vmId.LastIndexOf('.')); lock (_activeVMs) { if (!_activeVMs.ContainsKey(masterVMId)) { masterVM = CreateVM(masterVMId, null, vmNamespace); _activeVMs.TryAdd(masterVMId, new VMInfo { Instance = masterVM }); } else { masterVM = _activeVMs[masterVMId].Instance; } } vmId = vmId.Remove(0, vmId.LastIndexOf('.') + 1); } // If the view model Id contains instance Id, parse it out. string vmTypeName = vmId; string vmInstanceId = null; if (vmTypeName.Contains('$')) { path = vmTypeName.Split('$'); vmTypeName = path[0]; vmInstanceId = path[1]; } // Get the view model instance from the master view model, and if not, create it ourselves here. var vmInstance = masterVM?.GetSubVM(vmTypeName, vmInstanceId) ?? _vmFactory.GetInstance(vmTypeName, vmInstanceId, vmNamespace) ?? throw new Exception($"[dotNetify] ERROR: '{vmId}' is not a known view model! Its assembly must be registered through VMController.RegisterAssembly."); // If there are view model arguments, set them into the instance. if (vmArg is JObject) { foreach (var prop in (vmArg as JObject).Properties()) { UpdateVM(vmInstance, prop.Name, prop.Value.ToString()); } } // Pass the view model instance to the master view model. masterVM?.OnSubVMCreated(vmInstance); return(vmInstance); }
/// <summary> /// Handles a request for a view model from a browser client. /// </summary> /// <param name="connectionId">Identifies the client connection.</param> /// <param name="vmId">Identifies the view model.</param> /// <param name="vmArg">Optional view model's initialization argument.</param> public virtual void OnRequestVM(string connectionId, string vmId, object vmArg = null) { // Create a new view model instance whose class name is matching the given VMId. BaseVM vmInstance = !_activeVMs.ContainsKey(vmId) ? CreateVM(vmId, vmArg) : _activeVMs[vmId].Instance; RequestVMFilter.Invoke(vmId, vmInstance, vmArg, data => { var vmData = Serialize(vmInstance); // Send the view model data back to the browser client. _vmResponse?.Invoke(connectionId, vmId, vmData); // Reset the changed property states. vmInstance.AcceptChangedProperties(); // Add the view model instance to the controller. if (!_activeVMs.ContainsKey(vmId)) { _activeVMs.TryAdd(vmId, new VMInfo { Instance = vmInstance, ConnectionId = connectionId }); vmInstance.RequestPushUpdates += VmInstance_RequestPushUpdates; } else { _activeVMs[vmId].ConnectionId = connectionId; } // If this request causes other view models to change, push those new values back to the client. PushUpdates(); }); }
public VMInfo(string id, BaseVM instance, string connectionId, string groupName = null) { Id = id; Instance = instance; ConnectionId = connectionId; GroupName = groupName; }
/// <summary> /// Runs the view model filter. /// </summary> /// <param name="hubContext">Hub context.</param> /// <param name="vm">View model instance.</param> public void RunVMFilters(DotNetifyHubContext hubContext, BaseVM vm, NextFilterDelegate finalFilter) { var nextFilters = new Stack <NextFilterDelegate>(); nextFilters.Push(finalFilter); // Find and execute the filter that matches each view model class attribute. foreach (var attr in vm.GetType().GetTypeInfo().GetCustomAttributes().Reverse()) { var vmFilterType = typeof(IVMFilter <>).MakeGenericType(attr.GetType()); if (_vmFilterFactories.Keys.Any(t => vmFilterType.IsAssignableFrom(t))) { var vmFilter = _vmFilterFactories.FirstOrDefault(kvp => vmFilterType.IsAssignableFrom(kvp.Key)).Value(); var vmFilterInvokeMethod = vmFilterType.GetMethod(nameof(IVMFilter <Attribute> .Invoke)); if (vmFilterInvokeMethod != null) { nextFilters.Push(ctx => (Task)vmFilterInvokeMethod.Invoke(vmFilter, new object[] { attr, ctx, nextFilters.Pop() })); } } } var vmContext = new VMContext(hubContext, vm); if (nextFilters.Count > 0) { nextFilters.Pop()(vmContext); } }
/// <summary> /// Adds a runtime reactive property. /// </summary> /// <param name="vm">View model to add the property to.</param> /// <param name="propertyName">Property name.</param> /// <param name="propertyValue">Property value.</param> /// <returns>Reactive property.</returns> public static ReactiveProperty <T> AddReactiveProperty <T>(this BaseVM vm, string propertyName, T propertyValue = default(T)) { var prop = new ReactiveProperty <T>(propertyValue); prop.OnChanged(() => vm.Changed(propertyName)); vm.AddProperty(propertyName, prop); return(prop); }
/// <summary> /// Runs the filter before the view model respond to something. /// </summary> private void RunRespondingVMFilters(string vmId, BaseVM vm, object vmData, Action <object> vmAction) { try { RunVMFilters(nameof(Response_VM), vmId, vm, vmData, vmAction); } catch (Exception ex) { _hubPipeline.RunExceptionMiddleware(Context, ex); } }
/// <summary> /// Handles a request for a view model from a browser client. /// </summary> /// <param name="connectionId">Identifies the client connection.</param> /// <param name="vmId">Identifies the view model.</param> /// <param name="vmArg">Optional view model's initialization argument.</param> /// <returns>Group name, if the request is for a multicast view model associated with one.</returns> public async virtual Task <string> OnRequestVMAsync(string connectionId, string vmId, object vmArg = null) { BaseVM vmInstance = null; if (_activeVMs.ContainsKey(vmId)) { vmInstance = _activeVMs[vmId].Instance; } else { // Create a new view model instance whose class name is matching the given VMId. vmInstance = CreateVM(vmId, vmArg); await vmInstance.OnCreatedAsync(); } await RequestVMFilter.Invoke(vmId, vmInstance, vmArg, async data => { var vmData = vmInstance.Serialize(); // Send the view model data back to the browser client. await ResponseVMFilter.Invoke(vmId, vmInstance, vmData, filteredData => _vmResponse(connectionId, vmId, (string)filteredData)); // Reset the changed property states. vmInstance.AcceptChangedProperties(); // Add the view model instance to the controller. if (!_activeVMs.ContainsKey(vmId)) { var vmInfo = new VMInfo(id: vmId, instance: vmInstance, connectionId: connectionId); vmInstance.RequestPushUpdates += VmInstance_RequestPushUpdates; if (vmInstance is MulticastVM) { var multicastVM = vmInstance as MulticastVM; vmInfo.GroupName = multicastVM.GroupName; multicastVM.RequestMulticastPushUpdates += VMInstance_RequestMulticastPushUpdates; multicastVM.RequestSend += VMInstance_RequestSend; } _activeVMs.TryAdd(vmId, vmInfo); } else { _activeVMs[vmId].ConnectionId = connectionId; } // If this request causes other view models to change, push those new values back to the client. foreach (var vmInfo in _activeVMs.Values) { PushUpdates(vmInfo); } }); return(_activeVMs[vmId].GroupName); }
/// <summary> /// Runs the view model filter. /// </summary> /// <param name="vmId">Identifies the view model.</param> /// <param name="vm">View model instance.</param> /// <param name="data">View model data.</param> /// <param name="vmAction">Filter action.</param> private async Task RunVMFilters(BaseVM vm, object data, VMController.VMActionDelegate vmAction) { try { _hubContext.Data = data; await _hubPipeline.RunVMFiltersAsync(_hubContext, vm, async ctx => { await vmAction(ctx.HubContext.Data); }); } catch (TargetInvocationException ex) { throw ex.InnerException; } }
/// <summary> /// Runs the view model filter. /// </summary> /// <param name="vmId">Identifies the view model.</param> /// <param name="vm">View model instance.</param> /// <param name="vmArg">Optional view model argument.</param> private void RunVMFilters(string callType, string vmId, BaseVM vm, object data, Action <object> vmAction) { try { _hubPipeline.RunVMFilters(Context, callType, vmId, vm, data, Principal, ctx => { vmAction(ctx.HubContext.Data); return(Task.CompletedTask); }); } catch (TargetInvocationException ex) { throw ex.InnerException; } }
/// <summary> /// Runs the view model filter. /// </summary> /// <param name="vmId">Identifies the view model.</param> /// <param name="vm">View model instance.</param> /// <param name="data">View model data.</param> /// <param name="vmArg">Optional view model argument.</param> private void RunVMFilters(BaseVM vm, object data, Action <object> vmAction) { try { _hubContext.Data = data; _hubPipeline.RunVMFilters(_hubContext, vm, ctx => { vmAction(ctx.HubContext.Data); return(Task.CompletedTask); }); } catch (TargetInvocationException ex) { throw ex.InnerException; } }
/// <summary> /// Runs the filter before the view model respond to something. /// </summary> private async Task RunRespondingVMFilters(string vmId, BaseVM vm, object vmData, VMController.VMActionDelegate vmAction) { try { _hubContext = new DotNetifyHubContext(_callerContext, nameof(IDotNetifyHubMethod.Response_VM), vmId, vmData, null, Principal); await _hubPipeline.RunMiddlewaresAsync(_hubContext, async ctx => { Principal = ctx.Principal; await RunVMFilters(vm, ctx.Data, vmAction); }); } catch (Exception ex) { var finalEx = await _hubPipeline.RunExceptionMiddlewareAsync(_callerContext, ex); if (finalEx is OperationCanceledException == false && _callerContext != null) { await ResponseVMAsync(_callerContext.ConnectionId, vmId, SerializeException(finalEx)); } } }
/// <summary> /// Runs the filter before the view model respond to something. /// </summary> private void RunRespondingVMFilters(string vmId, BaseVM vm, object vmData, Action <object> vmAction) { try { _hubContext = new DotNetifyHubContext(Context, nameof(Response_VM), vmId, vmData, null, Principal); _hubPipeline.RunMiddlewares(_hubContext, ctx => { Principal = ctx.Principal; RunVMFilters(vm, ctx.Data, vmAction); return(Task.CompletedTask); }); } catch (Exception ex) { var finalEx = _hubPipeline.RunExceptionMiddleware(Context, ex); if (finalEx is OperationCanceledException == false) { Response_VM(Context.ConnectionId, vmId, SerializeException(finalEx)); } } }
/// <summary> /// Handles a request for a view model from a browser client. /// </summary> /// <param name="connectionId">Identifies the client connection.</param> /// <param name="vmId">Identifies the view model.</param> /// <param name="vmArg">Optional view model's initialization argument.</param> /// <returns>Group name, if the request is for a multicast view model associated with one.</returns> public virtual string OnRequestVM(string connectionId, string vmId, object vmArg = null) { // Create a new view model instance whose class name is matching the given VMId. BaseVM vmInstance = !_activeVMs.ContainsKey(vmId) ? CreateVM(vmId, vmArg) : _activeVMs[vmId].Instance; RequestVMFilter.Invoke(vmId, vmInstance, vmArg, data => { var vmData = vmInstance.Serialize(); // Send the view model data back to the browser client. _vmResponse?.Invoke(connectionId, vmId, vmData); // Reset the changed property states. vmInstance.AcceptChangedProperties(); // Add the view model instance to the controller. if (!_activeVMs.ContainsKey(vmId)) { _activeVMs.TryAdd(vmId, new VMInfo(id: vmId, instance: vmInstance, connectionId: connectionId)); vmInstance.RequestPushUpdates += VmInstance_RequestPushUpdates; if (vmInstance is IMulticast) { (vmInstance as IMulticast).RequestMulticastPushUpdates += VMInstance_RequestMulticastPushUpdates; } } else { _activeVMs[vmId].ConnectionId = connectionId; } // If this request causes other view models to change, push those new values back to the client. foreach (var vmInfo in _activeVMs.Values) { PushUpdates(vmInfo); } }); return(vmInstance is IMulticast ? (vmInstance as IMulticast).GroupName : null); }
/// <summary> /// Override this method to access instances of subordinates view models before they're disposed. /// </summary> /// <param name="subVM">Sub-view model instance.</param> public virtual void OnSubVMDisposing(BaseVM subVM) { }
/// <summary> /// Override this method to access new instances of subordinates view models as soon as they're created. /// </summary> /// <param name="subVM">Sub-view model instance.</param> public virtual void OnSubVMCreated(BaseVM subVM) { }
/// <summary> /// Returns whether current security context is authorized to access a view model. /// </summary> /// <param name="vmInstance">View model instance.</param> /// <returns>True if authorized.</returns> protected virtual bool IsAuthorized(BaseVM vmInstance) { var authAttr = vmInstance.GetType().GetTypeInfo().GetCustomAttribute <AuthorizeAttribute>(); return(authAttr == null || authAttr.IsAuthorized(Principal)); }
/// <summary> /// Runs the filter before the view model is updated. /// </summary> private void RunUpdatingVMFilters(string vmId, BaseVM vm, object vmData, Action <object> vmAction) => RunVMFilters(vm, vmData, vmAction);
/// <summary> /// Runs the filter before the view model is requested. /// </summary> private void RunRequestingVMFilters(string vmId, BaseVM vm, object vmArg, Action <object> vmAction) => RunVMFilters(vm, vmArg, vmAction);
/// <summary> /// Updates a value of a view model. /// </summary> /// <param name="vmInstance">View model instance.</param> /// <param name="vmPath">View model property path.</param> /// <param name="newValue">New value.</param> protected virtual void UpdateVM(BaseVM vmInstance, string vmPath, string newValue) { vmInstance.DeserializeProperty(vmPath, newValue); }
public static void RemoveList <T>(this BaseVM vm, string propName, T itemKey) => vm.ChangedProperties[propName + "_remove"] = itemKey;
/// <summary> /// Updates a value of a view model. /// </summary> /// <param name="vmInstance">View model instance.</param> /// <param name="vmPath">View model property path.</param> /// <param name="newValue">New value.</param> protected virtual void UpdateVM(BaseVM vmInstance, string vmPath, string newValue) { try { object vmObject = vmInstance; var vmType = vmObject.GetType(); var path = vmPath.Split('.'); for (int i = 0; i < path.Length; i++) { var propName = path[i]; var propInfo = vmType.GetTypeInfo().GetProperty(propName); if (propInfo == null) { throw new UnresolvedVMUpdateException(); } var propType = propInfo.PropertyType.GetTypeInfo(); if (i < path.Length - 1) { // Path that starts with $ sign means it is a key to an IEnumerable property. // By convention we expect a method whose name is in this format: // <IEnumerable property name>_get (for example: ListContent_get) // to get the object whose key matches the given value in the path. if (path[i + 1].StartsWith("$")) { var key = path[i + 1].TrimStart('$'); var methodInfo = vmType.GetTypeInfo().GetMethod(propName + "_get"); if (methodInfo == null) { throw new UnresolvedVMUpdateException(); } vmObject = methodInfo.Invoke(vmObject, new object[] { key }); if (vmObject == null) { throw new UnresolvedVMUpdateException(); } vmType = vmObject.GetType(); i++; } else { vmObject = propInfo.GetValue(vmObject); vmType = vmObject != null?vmObject.GetType() : propInfo.PropertyType; } } else if (typeof(ICommand).GetTypeInfo().IsAssignableFrom(propInfo.PropertyType) && vmObject != null) { // If the property type is ICommand, execute the command. (propInfo.GetValue(vmObject) as ICommand)?.Execute(newValue); } else if (propType.IsSubclassOf(typeof(MulticastDelegate)) && propType.GetMethod(nameof(Action.Invoke)).ReturnType == typeof(void)) { // If the property type is Action, wrap the action in a Command object and execute it. var argTypes = propType.GetGenericArguments(); var cmdType = argTypes.Length > 0 ? typeof(Command <>).MakeGenericType(argTypes) : typeof(Command); (Activator.CreateInstance(cmdType, new object[] { propInfo.GetValue(vmObject) }) as ICommand)?.Execute(newValue); } else if (propInfo.SetMethod != null && vmObject != null) { // Update the new value to the property. if (propType.IsClass && propInfo.PropertyType != typeof(string)) { propInfo.SetValue(vmObject, JsonConvert.DeserializeObject(newValue, propInfo.PropertyType)); } else { var typeConverter = TypeDescriptor.GetConverter(propInfo.PropertyType); if (typeConverter != null) { propInfo.SetValue(vmObject, typeConverter.ConvertFromString(newValue)); } } // Don't include the property we just updated in the ChangedProperties of the view model // unless the value is changed internally, so that we don't send the same value back to the client // during PushUpdates call by this VMController. var changedProperties = vmInstance.ChangedProperties; if (changedProperties.ContainsKey(vmPath) && (changedProperties[vmPath] ?? string.Empty).ToString() == newValue) { object value; changedProperties.TryRemove(vmPath, out value); } } } } catch (UnresolvedVMUpdateException) { // If we cannot resolve the property path, forward the info to the instance // to give it a chance to resolve it. vmInstance.OnUnresolvedUpdate(vmPath, newValue); } }
public static void UpdateList <T>(this BaseVM vm, string propName, T item) => vm.ChangedProperties[propName + "_update"] = item;
/// <summary> /// Runs the filter before the view model is requested. /// </summary> private Task RunRequestingVMFilters(string vmId, BaseVM vm, object vmArg, VMController.VMActionDelegate vmAction) => RunVMFilters(vm, vmArg, vmAction);
/// <summary> /// Used in CRUD operations to remove an item from a list. /// </summary> /// <typeparam name="T">Property type.</typeparam> /// <param name="expression">Expression containing property name of the list.</param> /// <param name="itemKey">Identifies the list item to be removed.</param> public static void RemoveList <T>(this BaseVM vm, Expression <Func <T> > expression, object itemKey) { var propName = ((MemberExpression)expression.Body).Member.Name; vm.RemoveList(propName, itemKey); }
public void RunVMFilters(DotNetifyHubContext hubContext, BaseVM vm, NextFilterDelegate finalFilter) { _ = RunVMFiltersAsync(hubContext, vm, finalFilter); }
/// <summary> /// Override this method to access instances of subordinates view models before they're disposed. /// </summary> /// <param name="subVM">Sub-view model instance.</param> public virtual void OnSubVMDisposing(BaseVM subVM) => (_vmInstance as IMasterVM)?.OnSubVMDisposing(subVM._vmInstance);
/// <summary> /// Creates a view model. /// </summary> /// <param name="vmId">Identifies the view model.</param> /// <param name="vmArg">Optional view model's initialization argument.</param> /// <param name="vmNamespace">Optional view model type's namespace.</param> /// <returns>View model instance.</returns> protected virtual BaseVM CreateVM(string vmId, object vmArg = null, string vmNamespace = null) { // If the namespace argument is given, try to resolve the view model type to that namespace. vmNamespace = vmNamespace ?? ExtractNamespace(ref vmArg); // If the view model Id is in the form of a delimited path, it has a master view model. BaseVM masterVM = null; var path = vmId.Split('.'); if (path.Length > 1) { // Get the master view model; create the instance if it doesn't exist. var masterVMId = vmId.Remove(vmId.LastIndexOf('.')); lock (_activeVMs) { if (!_activeVMs.ContainsKey(masterVMId)) { masterVM = CreateVM(masterVMId, null, vmNamespace); _activeVMs.TryAdd(masterVMId, new VMInfo { Instance = masterVM }); } else { masterVM = _activeVMs[masterVMId].Instance; } } vmId = vmId.Remove(0, vmId.LastIndexOf('.') + 1); } // If the view model Id contains instance Id, parse it out. string vmTypeName = vmId; string vmInstanceId = null; if (vmTypeName.Contains('$')) { path = vmTypeName.Split('$'); vmTypeName = path[0]; vmInstanceId = path[1]; } // Get the view model instance from the master view model. var vmInstance = masterVM?.GetSubVM(vmTypeName, vmInstanceId); // If still no view model instance, create it ourselves here. if (vmInstance == null) { Type vmType = null; if (vmNamespace != null) { vmType = _vmTypes.FirstOrDefault(i => i.FullName == $"{vmNamespace}.{vmTypeName}"); } vmType = vmType ?? _vmTypes.FirstOrDefault(i => i.Name == vmTypeName); if (vmType == null) { throw new Exception($"[dotNetify] ERROR: '{vmId}' is not a known view model! Its assembly must be registered through VMController.RegisterAssembly."); } try { if (vmInstanceId != null) { vmInstance = CreateInstance(vmType, new object[] { vmInstanceId }) as BaseVM; } } catch (MissingMethodException) { Trace.Fail($"[dotNetify] ERROR: '{vmTypeName}' has no constructor accepting instance ID."); } try { if (vmInstance == null) { vmInstance = CreateInstance(vmType, null) as BaseVM; } } catch (MissingMethodException) { Trace.Fail($"[dotNetify] ERROR: '{vmTypeName}' has no parameterless constructor."); } } // If there are view model arguments, set them into the instance. if (vmArg is JObject) { foreach (var prop in (vmArg as JObject).Properties()) { UpdateVM(vmInstance, prop.Name, prop.Value.ToString()); } } // Pass the view model instance to the master view model. masterVM?.OnSubVMCreated(vmInstance); return(vmInstance); }
public VMInfo(string id, BaseVM instance, string connectionId) { Id = id; Instance = instance; ConnectionId = connectionId; }
/// <summary> /// Runs the filter before the view model is updated. /// </summary> private Task RunUpdatingVMFilters(string vmId, BaseVM vm, object vmData, VMController.VMActionDelegate vmAction) => RunVMFilters(vm, vmData, vmAction);
/// <summary> /// Override this method to access new instances of subordinates view models as soon as they're created. /// </summary> /// <param name="subVM">Sub-view model instance.</param> public virtual void OnSubVMCreated(BaseVM subVM) => (_vmInstance as IMasterVM)?.OnSubVMCreated(subVM._vmInstance);