From 8b093582e281d38da5f41aa757e11324e5234a95 Mon Sep 17 00:00:00 2001 From: Jesper Kamstrup Linnet Date: Tue, 8 Apr 2014 08:30:57 +0200 Subject: [PATCH 1/3] Runtime detection of Mono --- .../Activation/Caching/ActivationCache.cs | 410 ++++++++++++++---- src/Ninject/Activation/InstanceReference.cs | 2 +- src/Ninject/Components/ComponentContainer.cs | 4 +- .../Introspection/FormatExtensions.cs | 8 +- .../Language/ExtensionsForMemberInfo.cs | 217 ++++++--- src/Ninject/Ninject.csproj | 3 +- src/Ninject/RuntimeEnvironment.cs | 32 ++ 7 files changed, 511 insertions(+), 165 deletions(-) create mode 100644 src/Ninject/RuntimeEnvironment.cs diff --git a/src/Ninject/Activation/Caching/ActivationCache.cs b/src/Ninject/Activation/Caching/ActivationCache.cs index 48f0657c..214294e1 100644 --- a/src/Ninject/Activation/Caching/ActivationCache.cs +++ b/src/Ninject/Activation/Caching/ActivationCache.cs @@ -11,27 +11,7 @@ namespace Ninject.Activation.Caching /// public class ActivationCache : NinjectComponent, IActivationCache, IPruneable { -#if SILVERLIGHT_20 || SILVERLIGHT_30 || WINDOWS_PHONE || NETCF || MONO - /// - /// The objects that were activated as reference equal weak references. - /// - private readonly IDictionary activatedObjects = new Dictionary(new WeakReferenceEqualityComparer()); - - /// - /// The objects that were activated as reference equal weak references. - /// - private readonly IDictionary deactivatedObjects = new Dictionary(new WeakReferenceEqualityComparer()); -#else - /// - /// The objects that were activated as reference equal weak references. - /// - private readonly HashSet activatedObjects = new HashSet(new WeakReferenceEqualityComparer()); - - /// - /// The objects that were activated as reference equal weak references. - /// - private readonly HashSet deactivatedObjects = new HashSet(new WeakReferenceEqualityComparer()); -#endif + private readonly IActivationCacheImpl cacheImpl; /// /// Initializes a new instance of the class. @@ -40,8 +20,21 @@ public class ActivationCache : NinjectComponent, IActivationCache, IPruneable public ActivationCache(ICachePruner cachePruner) { cachePruner.Start(this); + +#if SILVERLIGHT_20 || SILVERLIGHT_30 || WINDOWS_PHONE || NETCF + cacheImpl = new DictionaryBasedActivationCacheImpl(); +#else + if (RuntimeEnvironment.IsMonoRuntime) + { + cacheImpl = new DictionaryBasedActivationCacheImpl(); + } + else + { + cacheImpl = new HashSetBasedActivationCacheImpl(); + } +#endif } - + /// /// Gets the activated object count. /// @@ -50,7 +43,7 @@ public int ActivatedObjectCount { get { - return this.activatedObjects.Count; + return cacheImpl.ActivatedObjectCount; } } @@ -62,24 +55,16 @@ public int DeactivatedObjectCount { get { - return this.deactivatedObjects.Count; + return cacheImpl.DeactivatedObjectCount; } } - + /// /// Clears the cache. /// public void Clear() { - lock (this.activatedObjects) - { - this.activatedObjects.Clear(); - } - - lock (this.deactivatedObjects) - { - this.deactivatedObjects.Clear(); - } + cacheImpl.Clear(); } /// @@ -88,14 +73,7 @@ public void Clear() /// The instance to be added. public void AddActivatedInstance(object instance) { - lock (this.activatedObjects) - { -#if SILVERLIGHT_20 || SILVERLIGHT_30 || WINDOWS_PHONE || NETCF || MONO - this.activatedObjects.Add(new ReferenceEqualWeakReference(instance), true); -#else - this.activatedObjects.Add(new ReferenceEqualWeakReference(instance)); -#endif - } + cacheImpl.AddActivatedInstance(instance); } /// @@ -104,14 +82,7 @@ public void AddActivatedInstance(object instance) /// The instance to be added. public void AddDeactivatedInstance(object instance) { - lock (this.deactivatedObjects) - { -#if SILVERLIGHT_20 || SILVERLIGHT_30 || WINDOWS_PHONE || NETCF || MONO - this.deactivatedObjects.Add(new ReferenceEqualWeakReference(instance), true); -#else - this.deactivatedObjects.Add(new ReferenceEqualWeakReference(instance)); -#endif - } + cacheImpl.AddDeactivatedInstance(instance); } /// @@ -123,11 +94,7 @@ public void AddDeactivatedInstance(object instance) /// public bool IsActivated(object instance) { -#if SILVERLIGHT_20 || SILVERLIGHT_30 || WINDOWS_PHONE || NETCF || MONO - return this.activatedObjects.ContainsKey(instance); -#else - return this.activatedObjects.Contains(instance); -#endif + return cacheImpl.IsActivated(instance); } /// @@ -139,11 +106,7 @@ public bool IsActivated(object instance) /// public bool IsDeactivated(object instance) { -#if SILVERLIGHT_20 || SILVERLIGHT_30 || WINDOWS_PHONE || NETCF || MONO - return this.deactivatedObjects.ContainsKey(instance); -#else - return this.deactivatedObjects.Contains(instance); -#endif + return cacheImpl.IsDeactivated(instance); } /// @@ -151,39 +114,318 @@ public bool IsDeactivated(object instance) /// public void Prune() { - lock (this.activatedObjects) + cacheImpl.Prune(); + } + + private interface IActivationCacheImpl + { + /// + /// Gets the activated object count. + /// + /// The activated object count. + int ActivatedObjectCount { get; } + + /// + /// Gets the deactivated object count. + /// + /// The deactivated object count. + int DeactivatedObjectCount { get; } + + /// + /// Clears the cache. + /// + void Clear(); + + /// + /// Adds an activated instance. + /// + /// The instance to be added. + void AddActivatedInstance(object instance); + + /// + /// Adds an deactivated instance. + /// + /// The instance to be added. + void AddDeactivatedInstance(object instance); + + /// + /// Determines whether the specified instance is activated. + /// + /// The instance. + /// + /// true if the specified instance is activated; otherwise, false. + /// + bool IsActivated(object instance); + + /// + /// Determines whether the specified instance is deactivated. + /// + /// The instance. + /// + /// true if the specified instance is deactivated; otherwise, false. + /// + bool IsDeactivated(object instance); + + /// + /// Prunes this instance. + /// + void Prune(); + } + + private class HashSetBasedActivationCacheImpl : IActivationCacheImpl + { + /// + /// The objects that were activated as reference equal weak references. + /// + private readonly HashSet activatedObjects = new HashSet(new WeakReferenceEqualityComparer()); + + /// + /// The objects that were activated as reference equal weak references. + /// + private readonly HashSet deactivatedObjects = new HashSet(new WeakReferenceEqualityComparer()); + + /// + /// Gets the activated object count. + /// + /// The activated object count. + public int ActivatedObjectCount { - RemoveDeadObjects(this.activatedObjects); + get + { + return this.activatedObjects.Count; + } } - lock (this.deactivatedObjects) + /// + /// Gets the deactivated object count. + /// + /// The deactivated object count. + public int DeactivatedObjectCount { - RemoveDeadObjects(this.deactivatedObjects); + get + { + return this.deactivatedObjects.Count; + } } - } -#if SILVERLIGHT_20 || SILVERLIGHT_30 || WINDOWS_PHONE || NETCF || MONO - /// - /// Removes all dead objects. - /// - /// The objects collection to be freed of dead objects. - private static void RemoveDeadObjects(IDictionary objects) - { - var deadObjects = objects.Where(entry => !((ReferenceEqualWeakReference)entry.Key).IsAlive).ToList(); - foreach (var deadObject in deadObjects) + /// + /// Clears the cache. + /// + public void Clear() { - objects.Remove(deadObject.Key); + lock (this.activatedObjects) + { + this.activatedObjects.Clear(); + } + + lock (this.deactivatedObjects) + { + this.deactivatedObjects.Clear(); + } + } + + /// + /// Adds an activated instance. + /// + /// The instance to be added. + public void AddActivatedInstance(object instance) + { + lock (this.activatedObjects) + { + this.activatedObjects.Add(new ReferenceEqualWeakReference(instance)); + } + } + + /// + /// Adds an deactivated instance. + /// + /// The instance to be added. + public void AddDeactivatedInstance(object instance) + { + lock (this.deactivatedObjects) + { + this.deactivatedObjects.Add(new ReferenceEqualWeakReference(instance)); + } + } + + /// + /// Determines whether the specified instance is activated. + /// + /// The instance. + /// + /// true if the specified instance is activated; otherwise, false. + /// + public bool IsActivated(object instance) + { + return this.activatedObjects.Contains(instance); + } + + /// + /// Determines whether the specified instance is deactivated. + /// + /// The instance. + /// + /// true if the specified instance is deactivated; otherwise, false. + /// + public bool IsDeactivated(object instance) + { + return this.deactivatedObjects.Contains(instance); + } + + /// + /// Prunes this instance. + /// + public void Prune() + { + lock (this.activatedObjects) + { + RemoveDeadObjects(this.activatedObjects); + } + + lock (this.deactivatedObjects) + { + RemoveDeadObjects(this.deactivatedObjects); + } + } + + /// + /// Removes all dead objects. + /// + /// The objects collection to be freed of dead objects. + private static void RemoveDeadObjects(HashSet objects) + { + objects.RemoveWhere(reference => !((ReferenceEqualWeakReference)reference).IsAlive); } } -#else - /// - /// Removes all dead objects. - /// - /// The objects collection to be freed of dead objects. - private static void RemoveDeadObjects(HashSet objects) + + private class DictionaryBasedActivationCacheImpl : IActivationCacheImpl { - objects.RemoveWhere(reference => !((ReferenceEqualWeakReference)reference).IsAlive); + /// + /// The objects that were activated as reference equal weak references. + /// + private readonly IDictionary activatedObjects = new Dictionary(new WeakReferenceEqualityComparer()); + + /// + /// The objects that were activated as reference equal weak references. + /// + private readonly IDictionary deactivatedObjects = new Dictionary(new WeakReferenceEqualityComparer()); + + /// + /// Gets the activated object count. + /// + /// The activated object count. + public int ActivatedObjectCount + { + get + { + return this.activatedObjects.Count; + } + } + + /// + /// Gets the deactivated object count. + /// + /// The deactivated object count. + public int DeactivatedObjectCount + { + get + { + return this.deactivatedObjects.Count; + } + } + + /// + /// Clears the cache. + /// + public void Clear() + { + lock (this.activatedObjects) + { + this.activatedObjects.Clear(); + } + + lock (this.deactivatedObjects) + { + this.deactivatedObjects.Clear(); + } + } + + /// + /// Adds an activated instance. + /// + /// The instance to be added. + public void AddActivatedInstance(object instance) + { + lock (this.activatedObjects) + { + this.activatedObjects.Add(new ReferenceEqualWeakReference(instance), true); + } + } + + /// + /// Adds an deactivated instance. + /// + /// The instance to be added. + public void AddDeactivatedInstance(object instance) + { + lock (this.deactivatedObjects) + { + this.deactivatedObjects.Add(new ReferenceEqualWeakReference(instance), true); + } + } + + /// + /// Determines whether the specified instance is activated. + /// + /// The instance. + /// + /// true if the specified instance is activated; otherwise, false. + /// + public bool IsActivated(object instance) + { + return this.activatedObjects.ContainsKey(instance); + } + + /// + /// Determines whether the specified instance is deactivated. + /// + /// The instance. + /// + /// true if the specified instance is deactivated; otherwise, false. + /// + public bool IsDeactivated(object instance) + { + return this.deactivatedObjects.ContainsKey(instance); + } + + /// + /// Prunes this instance. + /// + public void Prune() + { + lock (this.activatedObjects) + { + RemoveDeadObjects(this.activatedObjects); + } + + lock (this.deactivatedObjects) + { + RemoveDeadObjects(this.deactivatedObjects); + } + } + + /// + /// Removes all dead objects. + /// + /// The objects collection to be freed of dead objects. + private static void RemoveDeadObjects(IDictionary objects) + { + var deadObjects = objects.Where(entry => !((ReferenceEqualWeakReference)entry.Key).IsAlive).ToList(); + foreach (var deadObject in deadObjects) + { + objects.Remove(deadObject.Key); + } + } } -#endif } -} \ No newline at end of file +} diff --git a/src/Ninject/Activation/InstanceReference.cs b/src/Ninject/Activation/InstanceReference.cs index c2dc98a0..3e3c62c1 100644 --- a/src/Ninject/Activation/InstanceReference.cs +++ b/src/Ninject/Activation/InstanceReference.cs @@ -34,7 +34,7 @@ public class InstanceReference /// if the instance is of the specified type, otherwise . public bool Is() { -#if !SILVERLIGHT && !WINDOWS_PHONE && !NETCF && !MONO +#if !SILVERLIGHT && !WINDOWS_PHONE && !NETCF && !MONO_20 && !MONO_35 if (System.Runtime.Remoting.RemotingServices.IsTransparentProxy(Instance) && System.Runtime.Remoting.RemotingServices.GetRealProxy(Instance).GetType().Name == "RemotingProxy") { diff --git a/src/Ninject/Components/ComponentContainer.cs b/src/Ninject/Components/ComponentContainer.cs index fe12ec6d..c79adbeb 100644 --- a/src/Ninject/Components/ComponentContainer.cs +++ b/src/Ninject/Components/ComponentContainer.cs @@ -232,7 +232,7 @@ private static ConstructorInfo SelectConstructor(Type component, Type implementa return constructor; } -#if SILVERLIGHT_30 || SILVERLIGHT_20 || WINDOWS_PHONE || NETCF_35 || MONO +#if SILVERLIGHT_30 || SILVERLIGHT_20 || WINDOWS_PHONE || NETCF_35 || MONO_20 || MONO_35 private class HashSet { private IDictionary data = new Dictionary(); @@ -249,4 +249,4 @@ public bool Contains(T o) } #endif } -} \ No newline at end of file +} diff --git a/src/Ninject/Infrastructure/Introspection/FormatExtensions.cs b/src/Ninject/Infrastructure/Introspection/FormatExtensions.cs index d720e63b..c894ff49 100644 --- a/src/Ninject/Infrastructure/Introspection/FormatExtensions.cs +++ b/src/Ninject/Infrastructure/Introspection/FormatExtensions.cs @@ -156,14 +156,8 @@ public static string Format(this Type type) { var friendlyName = GetFriendlyName(type); -#if !MONO - if (friendlyName.Contains("AnonymousType")) + if (friendlyName.Contains("AnonymousType") || friendlyName.Contains("__AnonType")) return "AnonymousType"; -#else - - if (friendlyName.Contains("__AnonType")) - return "AnonymousType"; -#endif switch (friendlyName.ToLower(CultureInfo.InvariantCulture)) { diff --git a/src/Ninject/Infrastructure/Language/ExtensionsForMemberInfo.cs b/src/Ninject/Infrastructure/Language/ExtensionsForMemberInfo.cs index ee102115..54d391f1 100644 --- a/src/Ninject/Infrastructure/Language/ExtensionsForMemberInfo.cs +++ b/src/Ninject/Infrastructure/Language/ExtensionsForMemberInfo.cs @@ -2,10 +2,10 @@ // // Author: Remo Gloor (remo.gloor@bbv.ch) // Copyright (c) 2010, bbv Software Engineering AG. -// +// // Dual-licensed under the Apache License, Version 2.0, and the Microsoft Public License (Ms-PL). // See the file LICENSE.txt for details. -// +// #endregion namespace Ninject.Infrastructure.Language @@ -28,23 +28,37 @@ public static class ExtensionsForMemberInfo const BindingFlags Flags = DefaultFlags; #endif -#if !MONO - private static MethodInfo parentDefinitionMethodInfo; + private static readonly IParentDefinitionStrategy parentDefinitionStrategy; + private static readonly ICustomAttributesForMemberInfoStrategy customAttributesStrategy; - private static MethodInfo ParentDefinitionMethodInfo + static ExtensionsForMemberInfo() { - get +#if MEDIUM_TRUST + parentDefinitionStrategy = new TraversingParentDefinitionStrategy(); +#else + if (RuntimeMethodInfoBasedParentDefinitionStrategy.IsAvailable) { - if (parentDefinitionMethodInfo == null) - { - var runtimeAssemblyInfoType = typeof(MethodInfo).Assembly.GetType("System.Reflection.RuntimeMethodInfo"); - parentDefinitionMethodInfo = runtimeAssemblyInfoType.GetMethod("GetParentDefinition", Flags); - } + parentDefinitionStrategy = new RuntimeMethodInfoBasedParentDefinitionStrategy(); + } + else + { + parentDefinitionStrategy = new TraversingParentDefinitionStrategy(); + } +#endif - return parentDefinitionMethodInfo; +#if !NET_35 + if (RuntimeEnvironment.IsMonoOnFramework40OrGreater()) + { + customAttributesStrategy = new TraversingCustomAttributesStrategy(); } - } + else + { + customAttributesStrategy = new StandardDotNetCustomAttributesStrategy(); + } +#else + customAttributesStrategy = new TraversingCustomAttributesStrategy(); #endif + } /// /// Determines whether the specified member has attribute. @@ -133,17 +147,7 @@ public static bool IsPrivate(this PropertyInfo propertyInfo) /// public static object[] GetCustomAttributesExtended(this MemberInfo member, Type attributeType, bool inherited) { -#if !NET_35 && !MONO_40 - return Attribute.GetCustomAttributes(member, attributeType, inherited); -#else - var propertyInfo = member as PropertyInfo; - if (propertyInfo != null) - { - return GetCustomAttributes(propertyInfo, attributeType, inherited); - } - - return member.GetCustomAttributes(attributeType, inherited); -#endif + return customAttributesStrategy.GetCustomAttributesExtended(member, attributeType, inherited); } private static PropertyInfo GetParentDefinition(PropertyInfo property) @@ -163,25 +167,7 @@ private static PropertyInfo GetParentDefinition(PropertyInfo property) private static MethodInfo GetParentDefinition(this MethodInfo method, BindingFlags flags) { -#if MEDIUM_TRUST || MONO - var baseDefinition = method.GetBaseDefinition(); - var type = method.DeclaringType.BaseType; - MethodInfo result = null; - while (result == null && type != null) - { - result = type.GetMethods(flags).Where(m => m.GetBaseDefinition().Equals(baseDefinition)).SingleOrDefault(); - type = type.BaseType; - } - - return result; -#else - if (ParentDefinitionMethodInfo == null) - { - return null; - } - - return (MethodInfo)ParentDefinitionMethodInfo.Invoke(method, flags, null, null, CultureInfo.InvariantCulture); -#endif + return parentDefinitionStrategy.GetParentDefinition(method, flags); } private static bool IsDefined(PropertyInfo element, Type attributeType, bool inherit) @@ -212,53 +198,144 @@ private static bool IsDefined(PropertyInfo element, Type attributeType, bool inh return false; } - private static object[] GetCustomAttributes(PropertyInfo propertyInfo, Type attributeType, bool inherit) + private static void AddAttributes(List attributes, object[] customAttributes, Dictionary attributeUsages) { - if (inherit) + foreach (object attribute in customAttributes) + { + Type type = attribute.GetType(); + if (!attributeUsages.ContainsKey(type)) + { + attributeUsages[type] = InternalGetAttributeUsage(type).Inherited; + } + + if (attributeUsages[type]) + { + attributes.Add(attribute); + } + } + } + + private static AttributeUsageAttribute InternalGetAttributeUsage(Type type) + { + object[] customAttributes = type.GetCustomAttributes(typeof(AttributeUsageAttribute), true); + return (AttributeUsageAttribute)customAttributes[0]; + } + + private interface IParentDefinitionStrategy + { + MethodInfo GetParentDefinition(MethodInfo method, BindingFlags flags); + } + + private class RuntimeMethodInfoBasedParentDefinitionStrategy : IParentDefinitionStrategy + { + private static MethodInfo parentDefinitionMethodInfo; + + private MethodInfo ParentDefinitionMethodInfo { - if (InternalGetAttributeUsage(attributeType).Inherited) + get { - var attributeUsages = new Dictionary(); - var attributes = new List(); - attributes.AddRange(propertyInfo.GetCustomAttributes(attributeType, false)); - for (var info = GetParentDefinition(propertyInfo); - info != null; - info = GetParentDefinition(info)) + if (parentDefinitionMethodInfo == null) { - object[] customAttributes = info.GetCustomAttributes(attributeType, false); - AddAttributes(attributes, customAttributes, attributeUsages); + var runtimeAssemblyInfoType = typeof(MethodInfo).Assembly.GetType("System.Reflection.RuntimeMethodInfo"); + parentDefinitionMethodInfo = runtimeAssemblyInfoType.GetMethod("GetParentDefinition", Flags); } - var result = Array.CreateInstance(attributeType, attributes.Count) as object[]; - Array.Copy(attributes.ToArray(), result, result.Length); - return result; + return parentDefinitionMethodInfo; + } + } + + public MethodInfo GetParentDefinition(MethodInfo method, BindingFlags flags) + { + if (ParentDefinitionMethodInfo == null) + { + return null; } + + return (MethodInfo)ParentDefinitionMethodInfo.Invoke(method, flags, null, null, CultureInfo.InvariantCulture); } - return propertyInfo.GetCustomAttributes(attributeType, inherit); + internal static bool IsAvailable + { + get { + var runtimeAssemblyInfoType = typeof(MethodInfo).Assembly.GetType("System.Reflection.RuntimeMethodInfo"); + if (runtimeAssemblyInfoType == null) + { + return false; + } + parentDefinitionMethodInfo = runtimeAssemblyInfoType.GetMethod("GetParentDefinition", Flags); + return parentDefinitionMethodInfo != null; + } + } } - private static void AddAttributes(List attributes, object[] customAttributes, Dictionary attributeUsages) + private class TraversingParentDefinitionStrategy : IParentDefinitionStrategy { - foreach (object attribute in customAttributes) + public MethodInfo GetParentDefinition(MethodInfo method, BindingFlags flags) { - Type type = attribute.GetType(); - if (!attributeUsages.ContainsKey(type)) + var baseDefinition = method.GetBaseDefinition(); + var type = method.DeclaringType.BaseType; + MethodInfo result = null; + while (result == null && type != null) { - attributeUsages[type] = InternalGetAttributeUsage(type).Inherited; + result = type.GetMethods(flags).SingleOrDefault(m => m.GetBaseDefinition().Equals(baseDefinition)); + type = type.BaseType; } - if (attributeUsages[type]) + return result; + } + } + + private interface ICustomAttributesForMemberInfoStrategy + { + object[] GetCustomAttributesExtended(MemberInfo member, Type attributeType, bool inherited); + } + + private class TraversingCustomAttributesStrategy : ICustomAttributesForMemberInfoStrategy + { + public object[] GetCustomAttributesExtended(MemberInfo member, Type attributeType, bool inherited) + { + var propertyInfo = member as PropertyInfo; + if (propertyInfo != null) { - attributes.Add(attribute); + return GetCustomAttributes(propertyInfo, attributeType, inherited); + } + + return member.GetCustomAttributes(attributeType, inherited); + } + + private static object[] GetCustomAttributes(PropertyInfo propertyInfo, Type attributeType, bool inherit) + { + if (inherit) + { + if (InternalGetAttributeUsage(attributeType).Inherited) + { + var attributeUsages = new Dictionary(); + var attributes = new List(); + attributes.AddRange(propertyInfo.GetCustomAttributes(attributeType, false)); + for (var info = GetParentDefinition(propertyInfo); + info != null; + info = GetParentDefinition(info)) + { + object[] customAttributes = info.GetCustomAttributes(attributeType, false); + AddAttributes(attributes, customAttributes, attributeUsages); + } + + var result = Array.CreateInstance(attributeType, attributes.Count) as object[]; + Array.Copy(attributes.ToArray(), result, result.Length); + return result; + } } + + return propertyInfo.GetCustomAttributes(attributeType, inherit); } } - private static AttributeUsageAttribute InternalGetAttributeUsage(Type type) + private class StandardDotNetCustomAttributesStrategy : ICustomAttributesForMemberInfoStrategy { - object[] customAttributes = type.GetCustomAttributes(typeof(AttributeUsageAttribute), true); - return (AttributeUsageAttribute)customAttributes[0]; - } + public object[] GetCustomAttributesExtended(MemberInfo member, Type attributeType, bool inherited) + { + return Attribute.GetCustomAttributes(member, attributeType, inherited); + } + } } -} \ No newline at end of file +} diff --git a/src/Ninject/Ninject.csproj b/src/Ninject/Ninject.csproj index 199f2dbe..33f77f17 100644 --- a/src/Ninject/Ninject.csproj +++ b/src/Ninject/Ninject.csproj @@ -179,6 +179,7 @@ + @@ -284,7 +285,7 @@ -