By changing some of the OpenNetCf IoC files (by removing some of the changes I made!), Adrian's managed to get 'proper' IoC working - i.e. he's using code that has constructors like:
///
/// Initializes a new instance of the class.
///
/// The service.
/// The translator.
[MvxOpenNetCfInjectionAttribute ]
public LoginModel(
IAuthenticationService service,
ITranslator translator)
{
this.service = service;
this.translator = translator;
}
This isn't the only way to do this sort of thing - I know that someone else has also got TinyIoC working really cleanly - using some techniques built around http://slodge.blogspot.co.uk/2013/01/navigating-between-viewmodels-by-more.html - hopefully will share that with you soon too.
The code changes from Adrian are in the attached gist....
But in the meantime... Adrian - thanks - a badge of awesomeness is your's :)
This file contains 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
// MvxOpenNetCfContainer.cs | |
// (c) Copyright Cirrious Ltd. http://www.cirrious.com | |
// MvvmCross is licensed using Microsoft Public License (Ms-PL) | |
// Contributions and inspirations noted in readme.md and license.txt | |
// | |
// Project Lead - Stuart Lodge, @slodge, me@slodge.com | |
#region Credit - OpenNetCf | |
// This file is based on the OpenNetCf IoC container - used under free license -see http://ioc.codeplex.com | |
// Note that this is not the standard OpenNetCf IoC container - we've removed their instance caching | |
#endregion | |
#region | |
using System; | |
using System.Collections.Generic; | |
using System.Linq; | |
using Cirrious.MvvmCross.Core; | |
using Cirrious.MvvmCross.Exceptions; | |
#endregion | |
namespace Cirrious.MvvmCross.OpenNetCfIoC | |
{ | |
using System.Reflection; | |
public sealed class MvxOpenNetCfContainer | |
: MvxSingleton<MvxOpenNetCfContainer> | |
{ | |
private readonly Dictionary<string, object> _items = new Dictionary<string, object>(); | |
private readonly object _syncRoot = new object(); | |
private readonly Dictionary<Type, Type> _toResolve = new Dictionary<Type, Type>(); | |
/// <summary> | |
/// Gets the current instance of the IOC container. | |
/// </summary> | |
/// <value>The current.</value> | |
public static MvxOpenNetCfContainer Current | |
{ | |
get { return Instance ?? new MvxOpenNetCfContainer(); } | |
} | |
/// <summary> | |
/// Registers the type. | |
/// </summary> | |
/// <typeparam name = "TFrom">The type of from.</typeparam> | |
/// <typeparam name = "TTo">The type of to.</typeparam> | |
public void RegisterServiceType<TFrom, TTo>() | |
{ | |
if (_toResolve.ContainsKey(typeof (TFrom))) | |
throw new MvxException("Type already register"); | |
Type type = typeof(TTo); | |
_toResolve.Add(typeof (TFrom), type); | |
//// Adrian Sudbury extended code 31st Jan 2013 | |
//// Here we are looking for constructors decorated with the attribute MvxOpenNetCfInjectionAttribute. | |
IEnumerable<ConstructorInfo> constructors = (type.GetConstructors( | |
BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance) | |
.Where( | |
c => | |
c.IsPublic && | |
c.GetCustomAttributes(typeof (MvxOpenNetCfInjectionAttribute), true).Any())); | |
if (constructors.Any()) | |
{ | |
//// If we find decorated constructor build type now. | |
//// This was the only way i could get it to work - | |
//// i couldnt see how to do deffered object construction. | |
MvxOpenNetCfObjectBuilder.CreateObject(type); | |
} | |
//// End of extended code. | |
} | |
/// <summary> | |
/// Registers the type. | |
/// </summary> | |
/// <param name="typeFrom">The type from.</param> | |
/// <param name="typeTo">The type to.</param> | |
public void RegisterServiceType(Type typeFrom, Type typeTo) | |
{ | |
if (_toResolve.ContainsKey(typeFrom)) | |
throw new MvxException("Type already register"); | |
_toResolve.Add(typeFrom, typeTo); | |
} | |
public bool CanResolve<TToResolve>() where TToResolve : class | |
{ | |
var typeToBuild = typeof (TToResolve); | |
if (_items.ContainsKey(typeToBuild.FullName)) | |
return true; | |
return CanCreateInstance(typeToBuild); | |
} | |
public TToResolve Resolve<TToResolve>() where TToResolve : class | |
{ | |
TToResolve toReturn; | |
if (!TryResolve(out toReturn)) | |
{ | |
throw new MvxException("Unable to Resolve IoC type {0}", typeof (TToResolve)); | |
} | |
return toReturn; | |
} | |
public bool TryResolve<TToResolve>(out TToResolve instance) where TToResolve : class | |
{ | |
var typeToBuild = typeof (TToResolve); | |
object objectReference; | |
if (_items.TryGetValue(typeToBuild.FullName, out objectReference)) | |
{ | |
instance = objectReference as TToResolve; | |
return true; | |
} | |
if (!CanCreateInstance(typeof (TToResolve))) | |
{ | |
instance = null; | |
return false; | |
} | |
instance = CreateInstance<TToResolve>(typeToBuild); | |
return true; | |
} | |
private bool CanCreateInstance(Type typeToBuild) | |
{ | |
#if NETFX_CORE | |
if (typeToBuild.GetTypeInfo().IsInterface) | |
return _toResolve.ContainsKey(typeToBuild); | |
#else | |
if (typeToBuild.IsInterface) | |
return _toResolve.ContainsKey(typeToBuild); | |
#endif | |
// note - the original opennetCf container supported direct type creation - but Mvx supports interfaces only | |
throw new MvxException("Unexpected non interface type in call to CanCreateInstance " + typeToBuild.Name); | |
} | |
private TToResolve CreateInstance<TToResolve>(Type typeToBuild) where TToResolve : class | |
{ | |
#if NETFX_CORE | |
if (typeToBuild.GetTypeInfo().IsInterface) | |
typeToBuild = _toResolve[typeToBuild]; | |
#else | |
if (typeToBuild.IsInterface) | |
typeToBuild = _toResolve[typeToBuild]; | |
#endif | |
var instance = MvxOpenNetCfObjectBuilder.CreateObject(typeToBuild); | |
return (TToResolve) instance; | |
} | |
/// <summary> | |
/// Create a new instance at each time | |
/// </summary> | |
/// <returns></returns> | |
public object GetInstance(Type typeToBuild) | |
{ | |
var instance = MvxOpenNetCfObjectBuilder.CreateObject(typeToBuild); | |
return instance; | |
} | |
/// <summary> | |
/// Create a new instance at each time | |
/// </summary> | |
/// <typeparam name = "T"></typeparam> | |
/// <returns></returns> | |
public T GetInstance<T>() | |
{ | |
var typeToBuild = typeof (T); | |
return (T) GetInstance(typeToBuild); | |
} | |
/// <summary> | |
/// Create an instance of the required type | |
/// </summary> | |
/// <param name="typeToBuild">type</param> | |
/// <returns></returns> | |
public object Resolve(Type typeToBuild) | |
{ | |
if (_items.ContainsKey(typeToBuild.FullName)) | |
return _items[typeToBuild.FullName]; | |
return CreateInstance<object>(typeToBuild); | |
} | |
/// <summary> | |
/// Registers the instance. | |
/// </summary> | |
/// <typeparam name="TInterface">The type of the interface.</typeparam> | |
/// <param name="instance">The instance.</param> | |
public void RegisterServiceInstance<TInterface>(TInterface instance) | |
{ | |
if (_items.ContainsKey(typeof (TInterface).FullName)) | |
return; | |
Add(instance, typeof (TInterface).FullName); | |
} | |
/// <summary> | |
/// Registers the instance. | |
/// </summary> | |
/// <param name="interfaceToRegister">The interface to register.</param> | |
/// <param name="instance">The instance.</param> | |
public void RegisterServiceInstance(Type interfaceToRegister, object instance) | |
{ | |
if (_items.ContainsKey(interfaceToRegister.FullName)) | |
_items.Remove(interfaceToRegister.FullName); | |
Add(instance, interfaceToRegister.FullName); | |
} | |
/// <summary> | |
/// Adds the specified item. | |
/// </summary> | |
/// <param name="item">The item.</param> | |
/// <param name="id">The id.</param> | |
internal void Add(object item, string id) | |
{ | |
if (id == null) | |
throw new ArgumentNullException("id"); | |
if (item == null) | |
throw new ArgumentNullException("item"); | |
lock (_syncRoot) | |
{ | |
_items.Add(id, item); | |
} | |
} | |
/// <summary> | |
/// Finds the type of the by. | |
/// </summary> | |
/// <typeparam name="TSearchType">The type of the search type.</typeparam> | |
/// <returns></returns> | |
public IEnumerable<TSearchType> FindByType<TSearchType>() where TSearchType : class | |
{ | |
return (_items.Where(i => i.Value is TSearchType).Select(i => i.Value as TSearchType)); | |
} | |
/// <summary> | |
/// Finds the type of the by. | |
/// </summary> | |
/// <param name="searchType">Type of the search.</param> | |
/// <returns></returns> | |
public IEnumerable<object> FindByType(Type searchType) | |
{ | |
#if NETFX_CORE | |
if (searchType.GetTypeInfo().IsValueType) | |
throw new ArgumentException("searchType must be a reference type"); | |
if (searchType.GetTypeInfo().IsInterface) | |
{ | |
return (_items.Where(i => i.Value.GetType().GetTypeInfo().ImplementedInterfaces.Contains(searchType)).Select(i => i.Value)); | |
} | |
return (_items.Where(i => i.Value.GetType().Equals(searchType)).Select(i => i.Value)); | |
#else | |
if (searchType.IsValueType) | |
throw new ArgumentException("searchType must be a reference type"); | |
if (searchType.IsInterface) | |
{ | |
return (_items.Where(i => i.Value.GetType().GetInterfaces().Contains(searchType)).Select(i => i.Value)); | |
} | |
return (_items.Where(i => i.Value.GetType().Equals(searchType)).Select(i => i.Value)); | |
#endif | |
} | |
/// <summary> | |
/// Gets the type of the resolved. | |
/// </summary> | |
/// <param name="toResolve">To resolve.</param> | |
/// <returns></returns> | |
internal object GetResolvedType(Type toResolve) | |
{ | |
if (!_toResolve.ContainsKey(toResolve)) | |
throw new MvxException("Type is not register"); | |
if (!_items.ContainsKey(toResolve.FullName)) | |
return Resolve(toResolve); | |
return _toResolve[toResolve]; | |
} | |
} | |
} |
This file contains 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
// MvxOpenNetCfObjectBuilder.cs | |
// (c) Copyright Cirrious Ltd. http://www.cirrious.com | |
// MvvmCross is licensed using Microsoft Public License (Ms-PL) | |
// Contributions and inspirations noted in readme.md and license.txt | |
// | |
// Project Lead - Stuart Lodge, @slodge, me@slodge.com | |
#region Credit - OpenNetCf | |
// This file is based on the OpenNetCf IoC container - used under free license -see http://ioc.codeplex.com | |
#endregion | |
#region | |
using System; | |
using System.Collections.Generic; | |
using System.Linq; | |
using System.Reflection; | |
using Cirrious.MvvmCross.Exceptions; | |
#endregion | |
namespace Cirrious.MvvmCross.OpenNetCfIoC | |
{ | |
internal class MvxOpenNetCfObjectBuilder | |
{ | |
private static readonly Dictionary<Type, InjectionConstructor> ConstructorCache = | |
new Dictionary<Type, InjectionConstructor>(); | |
/// <summary> | |
/// Creates the object. | |
/// </summary> | |
/// <param name = "type">The type.</param> | |
/// <returns></returns> | |
internal static object CreateObject(Type type) | |
{ | |
if (type == null) | |
throw new ArgumentNullException("type"); | |
object instance = null; | |
// first check the cache | |
if (ConstructorCache.ContainsKey(type)) | |
return CreateObjectFromCache(type); | |
#if NETFX_CORE | |
if (type.GetTypeInfo().IsInterface) | |
#else | |
if (type.IsInterface) | |
#endif | |
throw new MvxException(string.Format("Cannot create an instance of an interface ({0}).", type.Name)); | |
#if NETFX_CORE | |
var ctors = | |
(type.GetTypeInfo().DeclaredConstructors | |
.Where( | |
c => | |
c.IsPublic && c.GetCustomAttributes(typeof (MvxOpenNetCfInjectionAttribute), true).Count() > 0)); | |
#else | |
var ctors = | |
(type.GetConstructors(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance) | |
.Where( | |
c => | |
c.IsPublic && c.GetCustomAttributes(typeof (MvxOpenNetCfInjectionAttribute), true).Count() > 0)); | |
#endif | |
if (!ctors.Any()) | |
{ | |
// no injection ctor, get the default, parameterless ctor | |
#if NETFX_CORE | |
var parameterlessCtors = | |
(type.GetTypeInfo().DeclaredConstructors.Where(c => c.IsPublic && c.GetParameters().Length == 0)); | |
#else | |
var parameterlessCtors = | |
(type.GetConstructors(BindingFlags.Public | BindingFlags.NonPublic | | |
BindingFlags.Instance).Where(c => c.GetParameters().Length == 0)); | |
#endif | |
if (!parameterlessCtors.Any()) | |
{ | |
throw new ArgumentException( | |
string.Format( | |
"Type '{0}' has no public parameterless constructor or injection constructor.\r\nAre you missing the InjectionConstructor attribute?", | |
type)); | |
} | |
// create the object | |
var constructorInfo = parameterlessCtors.First(); | |
try | |
{ | |
instance = constructorInfo.Invoke(null); | |
ConstructorCache.Add(type, new InjectionConstructor {ConstructorInfo = constructorInfo}); | |
} | |
catch (TargetInvocationException ex) | |
{ | |
throw ex.InnerException; | |
} | |
} | |
else if (ctors.Count() == 1) | |
{ | |
// call the injection ctor | |
var constructorInfo = ctors.First(); | |
var parameterInfos = constructorInfo.GetParameters(); | |
var parameters = GetParameterObjectsForParameterList(parameterInfos, type.Name); | |
try | |
{ | |
instance = constructorInfo.Invoke(parameters.ToArray()); | |
ConstructorCache.Add(type, | |
new InjectionConstructor | |
{ConstructorInfo = constructorInfo, ParameterInfos = parameterInfos}); | |
} | |
catch (TargetInvocationException ex) | |
{ | |
throw ex.InnerException; | |
} | |
} | |
else | |
{ | |
throw new ArgumentException( | |
string.Format("Type '{0}' has {1} defined injection constructors. Only one is allowed", type.Name, | |
ctors.Count())); | |
} | |
//// Adrian Sudbury 31st Jan 2013 - this line is commented out in the original version! | |
DoInjections(instance); | |
return instance; | |
} | |
/// <summary> | |
/// Does the injections. | |
/// </summary> | |
/// <param name="instance">The instance.</param> | |
internal static void DoInjections(object instance) | |
{ | |
var t = instance.GetType(); | |
// look for service dependecy setters | |
#if NETFX_CORE | |
var serviceDependecyProperties = t.GetTypeInfo().DeclaredProperties | |
#else | |
var serviceDependecyProperties = t.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | | |
BindingFlags.Instance) | |
#endif | |
.Where(p => | |
p.GetCustomAttributes( | |
typeof (MvxOpenNetCfDependencyAttribute), true). | |
Count() > 0); | |
foreach (var pi in serviceDependecyProperties) | |
{ | |
var resolved = MvxOpenNetCfContainer.Current.GetResolvedType(pi.PropertyType); | |
if (resolved != null) | |
pi.SetValue(instance, resolved, null); | |
else | |
pi.SetValue(instance, CreateObject(pi.PropertyType), null); | |
} | |
} | |
/// <summary> | |
/// Creates the object from cache. | |
/// </summary> | |
/// <param name="type">The type.</param> | |
/// <returns></returns> | |
private static object CreateObjectFromCache(Type type) | |
{ | |
if (type == null) | |
throw new ArgumentNullException("type"); | |
var injectionConstructor = ConstructorCache[type]; | |
try | |
{ | |
if ((injectionConstructor.ParameterInfos == null) || (injectionConstructor.ParameterInfos.Length == 0)) | |
{ | |
return injectionConstructor.ConstructorInfo.Invoke(null); | |
} | |
var parameters = GetParameterObjectsForParameterList( | |
injectionConstructor.ParameterInfos, type.Name); | |
return injectionConstructor.ConstructorInfo.Invoke(parameters.ToArray()); | |
} | |
catch (TargetInvocationException ex) | |
{ | |
throw ex.InnerException; | |
} | |
} | |
/// <summary> | |
/// Gets the parameter objects for parameter list. | |
/// </summary> | |
/// <param name="parameterInfos">The parameter infos.</param> | |
/// <param name="typeName">Name of the type.</param> | |
/// <returns></returns> | |
private static IEnumerable<object> GetParameterObjectsForParameterList( | |
IEnumerable<ParameterInfo> parameterInfos, string typeName) | |
{ | |
if (parameterInfos == null) | |
throw new ArgumentNullException("parameterInfos"); | |
if (string.IsNullOrEmpty(typeName)) | |
throw new ArgumentNullException("typeName"); | |
var paramObjects = new List<object>(); | |
foreach (var pi in parameterInfos) | |
{ | |
#if NETFX_CORE | |
if (pi.ParameterType.GetTypeInfo().IsValueType) | |
#else | |
if (pi.ParameterType.IsValueType) | |
#endif | |
{ | |
throw new ArgumentException( | |
string.Format("Injection on type '{0}' cannot have value type parameters", | |
typeName)); | |
} | |
if (Equals(pi.ParameterType.FullName, typeof (MvxOpenNetCfContainer).FullName)) | |
{ | |
paramObjects.Add(MvxOpenNetCfContainer.Current); | |
continue; | |
} | |
// see if there is an item that matches the type | |
var itemList = MvxOpenNetCfContainer.Current.FindByType(pi.ParameterType).ToList(); | |
if (!itemList.Any()) | |
itemList.Add(MvxOpenNetCfContainer.Current.GetResolvedType(pi.ParameterType)); | |
paramObjects.Add(itemList.First()); | |
} | |
return paramObjects.ToArray(); | |
} | |
#region Nested InjectionConstructor | |
private struct InjectionConstructor | |
{ | |
public ConstructorInfo ConstructorInfo { get; set; } | |
public ParameterInfo[] ParameterInfos { get; set; } | |
} | |
#endregion | |
} | |
} |
Are you going to merge this into MvvmCross so it works like this by default?
ReplyDeleteThe code will get merged back in, yes.
ReplyDeleteCurrently undecided on where ioc should go in v3. A bit of me likes the idea of switching constructor and/or property injection, but also I toyed with the idea of moving the current Get and Register extension methods to Object.
Interested to hear opinions. 100% sure that there isn't one right answer.
I prefer construction injection over property injection.
ReplyDeleteMaybe that's because in the main that's what I always use - its like asking a Java programmer 'do you prefer Java or C#?'
To me if a class really needs something to get its job done it should be passed on the constructor - its much clearer - its saying to use me I want these things passing to me when you construct me.
I once worked on someone else's code that used property injection and totally missed that it was done this way and wasted hours on a problem just because to me it was unclear the way in which the class should be constructed/used.
maybe the last paragraph says more about me instead of coding styles :-(
asudbury