Skip to content

Commit

Permalink
Implemented view model factory.
Browse files Browse the repository at this point in the history
dicky authored and dicky committed Sep 21, 2018
1 parent 74db5cc commit 9d39cad
Showing 10 changed files with 160 additions and 34 deletions.
15 changes: 12 additions & 3 deletions DotNetifyLib.Core/BaseVM/TypeHelper.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/*
/*
Copyright 2017 Dicky Suryadi
Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,13 +16,14 @@ limitations under the License.

using System;
using System.Linq;
using System.Reflection;

namespace DotNetify
{
/// <summary>
/// Provides Type abstraction for both static and runtime view model type.
/// </summary>
internal class TypeHelper
public class TypeHelper
{
private bool _isRuntimeType;
private Func<object[], object> _factoryDelegate;
@@ -37,18 +38,26 @@ internal class TypeHelper
/// </summary>
public string Name { get; }

/// <summary>
/// Whether it's a multicast type.
/// </summary>
public bool IsMulticast => typeof(IMulticast).GetTypeInfo().IsAssignableFrom(Type);

/// <summary>
/// Full type name.
/// </summary>
public string FullName { get; }

public static implicit operator Type(TypeHelper typeHelper) => typeHelper.Type;

public static implicit operator TypeHelper(Type type) => new TypeHelper(type);

public static bool operator ==(TypeHelper lhs, TypeHelper rhs) => IsEqual(lhs, rhs);

public static bool operator !=(TypeHelper lhs, TypeHelper rhs) => !IsEqual(lhs, rhs);

public override bool Equals(object obj) => obj is TypeHelper ? IsEqual(this, obj as TypeHelper) : base.Equals(obj);

public override int GetHashCode() => base.GetHashCode();

/// <summary>
@@ -97,4 +106,4 @@ private static bool IsEqual(TypeHelper lhs, TypeHelper rhs)
return lhs._isRuntimeType == rhs._isRuntimeType && lhs.Type == rhs.Type;
}
}
}
}
31 changes: 31 additions & 0 deletions DotNetifyLib.Core/IVMFactory.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
Copyright 2018 Dicky Suryadi
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

namespace DotNetify
{
/// <summary>
/// Provides view models.
/// </summary>
public interface IVMFactory
{
/// <summary>
/// Creates a view model.
/// </summary>
/// <param name="key">Identifies the object.</param>
/// <returns>View model.</returns>
BaseVM GetInstance(string vmTypeName, string vmInstanceId = null, string vmNamespace = null);
}
}
12 changes: 9 additions & 3 deletions DotNetifyLib.Core/VMController.cs
Original file line number Diff line number Diff line change
@@ -57,6 +57,11 @@ public partial class VMController : IDisposable

#region Fields

/// <summary>
/// View model factory.
/// </summary>
private readonly IVMFactory _vmFactory;

/// <summary>
/// Dependency injection service scope.
/// </summary>
@@ -115,7 +120,7 @@ protected internal class VMInfo
public IServiceProvider ServiceProvider => _serviceScope?.ServiceProvider;

// Default constructor.
public VMController()
internal VMController()
{
RequestVMFilter = (string vmId, BaseVM vm, object vmArg, Action<object> vmAction) => vmAction(vmArg);
UpdateVMFilter = (string vmId, BaseVM vm, object vmData, Action<object> vmAction) => vmAction(vmData);
@@ -127,9 +132,10 @@ public VMController()
/// </summary>
/// <param name="vmResponse">Function invoked by the view model to provide response back to the client.</param>
/// <param name="serviceScope">Dependency injection service scope.</param>
public VMController(VMResponseDelegate vmResponse, IVMServiceScope serviceScope = null) : this()
public VMController(VMResponseDelegate vmResponse, IVMFactory vmFactory, IVMServiceScope serviceScope = null) : this()
{
_vmResponse = vmResponse ?? throw new ArgumentNullException(nameof(vmResponse));
_vmFactory = vmFactory;
_serviceScope = serviceScope;
}

@@ -311,7 +317,7 @@ protected virtual BaseVM CreateVM(string vmId, object vmArg = null, string vmNam

// Get the view model instance from the master view model, and if not, create it ourselves here.
var vmInstance = masterVM?.GetSubVM(vmTypeName, vmInstanceId)
?? VMFactory.Create(VMTypes, vmTypeName, vmInstanceId, vmNamespace)
?? _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.
11 changes: 9 additions & 2 deletions DotNetifyLib.Core/VMControllerFactory.cs
Original file line number Diff line number Diff line change
@@ -28,6 +28,11 @@ public class VMControllerFactory : IVMControllerFactory
/// </summary>
private readonly IMemoryCache _controllersCache;

/// <summary>
/// For creating view model instances.
/// </summary>
private readonly IVMFactory _vmFactory;

/// <summary>
/// For creating dependency injection service scope.
/// </summary>
@@ -47,11 +52,13 @@ public class VMControllerFactory : IVMControllerFactory
/// Constructor.
/// </summary>
/// <param name="memoryCache">Memory cache for storing the view model controllers.</param>
/// <param name="vmFactory">Factory for view models.</param>
/// <param name="serviceScopeFactory">Factory for dependency injection service scope.</param>
public VMControllerFactory(IMemoryCache memoryCache, IVMServiceScopeFactory serviceScopeFactory)
public VMControllerFactory(IMemoryCache memoryCache, IVMFactory vmFactory, IVMServiceScopeFactory serviceScopeFactory)
{
_controllersCache = memoryCache ?? throw new ArgumentNullException("No service of type IMemoryCache has been registered.");
_serviceScopeFactory = serviceScopeFactory;
_vmFactory = vmFactory;
}

/// <summary>
@@ -66,7 +73,7 @@ public VMController GetInstance(string key)

if (!cache.TryGetValue(key, out Lazy<VMController> cachedValue))
{
cachedValue = new Lazy<VMController>(() => new VMController(ResponseDelegate, _serviceScopeFactory.CreateScope()));
cachedValue = new Lazy<VMController>(() => new VMController(ResponseDelegate, _vmFactory, _serviceScopeFactory.CreateScope()));
cache.Set(key, cachedValue, GetCacheEntryOptions());
}
return cachedValue?.Value;
82 changes: 70 additions & 12 deletions DotNetifyLib.Core/VMFactory.cs
Original file line number Diff line number Diff line change
@@ -1,27 +1,75 @@
using System;
/*
Copyright 2018 Dicky Suryadi
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;

namespace DotNetify
{
public class VMFactory
/// <summary>
/// Provides view models.
/// </summary>
public class VMFactory : IVMFactory
{
/// <summary>
/// Registered view model types.
/// </summary>
private IEnumerable<TypeHelper> VMTypes => VMController.VMTypes;

/// <summary>
/// For caching multicast view models.
/// </summary>
private IMemoryCache _memoryCache;

/// <summary>
/// Constructor.
/// </summary>
/// <param name="memoryCache">Memory cache for caching multicast view models.</param>
public VMFactory(IMemoryCache memoryCache)
{
_memoryCache = memoryCache;
}

/// <summary>
/// Creates a view model instance from a list of registered types.
/// </summary>
/// <param name="registeredTypes">Registered view model types.</param>
/// <param name="vmTypeName">View model type name.</param>
/// <param name="vmInstanceId">Optional view model instance identifier.</param>
/// <param name="vmNamespace">Optional view model type namespace.</param>
/// <returns></returns>
internal static BaseVM Create(IEnumerable<TypeHelper> registeredTypes, string vmTypeName, string vmInstanceId = null, string vmNamespace = null)
public BaseVM GetInstance(string vmTypeName, string vmInstanceId = null, string vmNamespace = null)
{
var vmType = GetVMTypeHelper(registeredTypes, vmTypeName, vmNamespace);
var vmType = GetVMTypeHelper(vmTypeName, vmNamespace);
if (vmType == null)
return null;

return vmType.IsMulticast ? GetMulticastInstance(vmType, vmInstanceId) : Create(vmType, vmInstanceId);
}

/// <summary>
/// Creates a view model instance.
/// </summary>
/// <param name="vmType">View model type helper.</param>
/// <param name="vmInstanceId">Optional view model instance identifier.</param>
/// <returns>View model instance.</returns>
private BaseVM Create(TypeHelper vmType, string vmInstanceId)
{
object[] arg = vmInstanceId != null ? new object[] { vmInstanceId } : null;
try
{
@@ -31,26 +79,36 @@ internal static BaseVM Create(IEnumerable<TypeHelper> registeredTypes, string vm
catch (MissingMethodException)
{
if (arg != null)
Trace.Fail($"[dotNetify] ERROR: '{vmTypeName}' has no constructor accepting instance ID.");
Trace.Fail($"[dotNetify] ERROR: '{vmType.Name}' has no constructor accepting instance ID.");
else
Trace.Fail($"[dotNetify] ERROR: '{vmTypeName}' has no parameterless constructor.");
Trace.Fail($"[dotNetify] ERROR: '{vmType.Name}' has no parameterless constructor.");
}

return null;
}

/// <summary>
/// Gets a multicast instance or creates a new one.
/// </summary>
/// <param name="vmType">View model type helper.</param>
/// <param name="vmInstanceId">Optional view model instance identifier.</param>
/// <returns>View model instance.</returns>
private BaseVM GetMulticastInstance(TypeHelper vmType, string vmInstanceId)
{
return Create(vmType, vmInstanceId);
}

/// <summary>
/// Returns a helper object that can create instances of a given view model type.
/// </summary>
/// <param name="registeredTypes">Registered view model types.</param>
/// <param name="vmTypeName">View model type name.</param>
/// <param name="vmNamespace">Optional view model type namespace.</param>
/// <returns></returns>
private static TypeHelper GetVMTypeHelper(IEnumerable<TypeHelper> registeredTypes, string vmTypeName, string vmNamespace)
/// <returns>View model type helper.</returns>
private TypeHelper GetVMTypeHelper(string vmTypeName, string vmNamespace)
{
return vmNamespace != null ?
registeredTypes.FirstOrDefault(i => i.FullName == $"{vmNamespace}.{vmTypeName}") :
registeredTypes.FirstOrDefault(i => i.Name == vmTypeName);
VMTypes.FirstOrDefault(i => i.FullName == $"{vmNamespace}.{vmTypeName}") :
VMTypes.FirstOrDefault(i => i.Name == vmTypeName);
}
}
}
Original file line number Diff line number Diff line change
@@ -31,6 +31,9 @@ public static IServiceCollection AddDotNetify(this IServiceCollection services)
// Add view model controller factory, to be injected to dotNetify's signalR hub.
services.AddSingleton<IVMControllerFactory, VMControllerFactory>();

// Add view model factory.
services.AddSingleton<IVMFactory, VMFactory>();

// Add the dependency injection service scope factory for view model controllers.
services.AddSingleton<IVMServiceScopeFactory, VMServiceScopeFactory>();

8 changes: 4 additions & 4 deletions UnitTests/MockDotNetifyHub.cs
Original file line number Diff line number Diff line change
@@ -18,15 +18,15 @@ public class MockDotNetifyHub
private List<Tuple<Type, Func<IMiddlewarePipeline>>> _middlewareFactories = new List<Tuple<Type, Func<IMiddlewarePipeline>>>();
private Dictionary<Type, Func<IVMFilter>> _vmFilterFactories = new Dictionary<Type, Func<IVMFilter>>();
private Func<Type, object[], object> _factoryMethod = (type, args) => VMController.CreateInstance(type, args);
private string _mockConnectionId = Guid.NewGuid().ToString();
private IVMFactory _vmFactory = new VMFactory(new MemoryCache());

public string ConnectionId => _mockConnectionId;
public string ConnectionId { get; } = Guid.NewGuid().ToString();

public event EventHandler<Tuple<string, string>> Response;

public static IMemoryCache CreateMemoryCache() => new MemoryCache();

private class MemoryCache : IMemoryCache
internal class MemoryCache : IMemoryCache
{
private Dictionary<string, object> _cache = new Dictionary<string, object>();

@@ -143,7 +143,7 @@ public override void Abort()
public MockDotNetifyHub Create()
{
_hub = new DotNetifyHub(
new VMControllerFactory(_memoryCache, _serviceScopeFactory) { ResponseDelegate = ResponseVM },
new VMControllerFactory(_memoryCache, _vmFactory, _serviceScopeFactory) { ResponseDelegate = ResponseVM },
new HubServiceProvider(),
new HubPrincipalAccessor(),
new HubPipeline(_middlewareFactories, _vmFilterFactories),
17 changes: 10 additions & 7 deletions UnitTests/MockVMController.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using DotNetify;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using DotNetify;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using static UnitTests.MockDotNetifyHub;

namespace UnitTests
{
@@ -24,7 +25,9 @@ public void Handler(string connectionId, string vmId, string vmData)
}

public T GetVM<T>() where T : INotifyPropertyChanged => JsonConvert.DeserializeObject<T>(_vmData);
public T GetVMProperty<T>(string propName) => (T) VMData[propName]?.ToObject(typeof(T));

public T GetVMProperty<T>(string propName) => (T)VMData[propName]?.ToObject(typeof(T));

public void Reset()
{
_vmId = string.Empty;
@@ -54,7 +57,7 @@ public class MockVMController<TViewModel> where TViewModel : INotifyPropertyChan
{
_response.Handler(connectionId, vmId, vmData);
OnResponse?.Invoke(this, vmData);
});
}, new VMFactory(new MemoryCache()));
}

public Response RequestVM()
@@ -83,4 +86,4 @@ public void DisposeVM(string vmId = null)
_vmController.OnDisposeVM("conn1", vmId ?? _vmId);
}
}
}
}
3 changes: 2 additions & 1 deletion UnitTests/VMControllerFactoryTest.cs
Original file line number Diff line number Diff line change
@@ -35,7 +35,8 @@ public void VMControllerFactory()
{
var id1 = "1";
var id2 = "2";
var factory = new VMControllerFactory(new MemoryCache(), new ServiceScopeFactory());
var memoryCache = new MemoryCache();
var factory = new VMControllerFactory(memoryCache, new VMFactory(memoryCache), new ServiceScopeFactory());
factory.ResponseDelegate = (string connectionId, string vmId, string vmData) => { };

Assert.IsNotNull(factory as IVMControllerFactory);
Loading
Oops, something went wrong.

0 comments on commit 9d39cad

Please sign in to comment.