From 53fe02db7eda47af73bdb3750a3a651c060e0b1c Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Tue, 30 Apr 2013 05:22:21 -0200 Subject: [PATCH 01/11] And another update for WebPI... --- src/WebPi/parameters.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/WebPi/parameters.xml b/src/WebPi/parameters.xml index 13f20d1a93..647e60b011 100644 --- a/src/WebPi/parameters.xml +++ b/src/WebPi/parameters.xml @@ -123,7 +123,7 @@ This is used to create a login and assign permissions. The SQL tags indicates it is a parameter required for SQL. The DbAdminPassword tag indicates it should be used when the user is creating a new database. If they're not, it can be filled in with the DbUserPassword value. --> - + From bcb90d64a5fd75bffb30dce625587188a1c70ebe Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Wed, 1 May 2013 05:32:29 -0200 Subject: [PATCH 02/11] Fixes U4-1136 Class in MediaPickerDataType.cs wrongly named MemberPickerDataType + a bit of code cleanup --- .../mediapicker/MediaPickerDataType.cs | 84 +++++++------------ .../memberpicker/MemberPickerDataType.cs | 70 ++++++---------- 2 files changed, 58 insertions(+), 96 deletions(-) diff --git a/src/umbraco.editorControls/mediapicker/MediaPickerDataType.cs b/src/umbraco.editorControls/mediapicker/MediaPickerDataType.cs index fac8134eb0..7dcc8ba952 100644 --- a/src/umbraco.editorControls/mediapicker/MediaPickerDataType.cs +++ b/src/umbraco.editorControls/mediapicker/MediaPickerDataType.cs @@ -2,60 +2,40 @@ using System; namespace umbraco.editorControls.mediapicker { - /// - /// Summary description for MemberPickerDataType. - /// - public class MemberPickerDataType : cms.businesslogic.datatype.BaseDataType,interfaces.IDataType - { - private interfaces.IDataEditor _Editor; - private interfaces.IData _baseData; - private interfaces.IDataPrevalue _prevalueeditor; + //TODO: Properly rename this for a major release + public class MediaPickerDataType : MemberPickerDataType + { } - public override interfaces.IDataEditor DataEditor - { - get - { - if (_Editor == null) - _Editor = new mediaChooser(Data, - ((MediaPickerPrevalueEditor)PrevalueEditor).ShowPreview, - ((MediaPickerPrevalueEditor)PrevalueEditor).ShowAdvancedDialog); - return _Editor; - } - } + [Obsolete("Renamed to MediaPickerDataType because.. that is what it was all along")] + public class MemberPickerDataType : cms.businesslogic.datatype.BaseDataType, interfaces.IDataType + { + private interfaces.IDataEditor _editor; + private interfaces.IData _baseData; + private interfaces.IDataPrevalue _prevalueeditor; - public override interfaces.IData Data - { - get - { - if (_baseData == null) - _baseData = new cms.businesslogic.datatype.DefaultData(this); - return _baseData; - } - } + public override interfaces.IDataEditor DataEditor + { + get { return _editor ?? (_editor = new mediaChooser(Data, ((MediaPickerPrevalueEditor)PrevalueEditor).ShowPreview, ((MediaPickerPrevalueEditor)PrevalueEditor).ShowAdvancedDialog)); } + } + public override interfaces.IData Data + { + get { return _baseData ?? (_baseData = new cms.businesslogic.datatype.DefaultData(this)); } + } - public override Guid Id - { - get - { - return new Guid ("EAD69342-F06D-4253-83AC-28000225583B"); - } - } - public override string DataTypeName - { - get - { - return "Media Picker"; - } - } - public override interfaces.IDataPrevalue PrevalueEditor - { - get - { - if (_prevalueeditor == null) - _prevalueeditor = new MediaPickerPrevalueEditor(this); - return _prevalueeditor; - } - } - } + public override Guid Id + { + get { return new Guid("EAD69342-F06D-4253-83AC-28000225583B"); } + } + + public override string DataTypeName + { + get { return "Media Picker"; } + } + + public override interfaces.IDataPrevalue PrevalueEditor + { + get { return _prevalueeditor ?? (_prevalueeditor = new MediaPickerPrevalueEditor(this)); } + } + } } diff --git a/src/umbraco.editorControls/memberpicker/MemberPickerDataType.cs b/src/umbraco.editorControls/memberpicker/MemberPickerDataType.cs index 37cf84bc75..faaf9d33b4 100644 --- a/src/umbraco.editorControls/memberpicker/MemberPickerDataType.cs +++ b/src/umbraco.editorControls/memberpicker/MemberPickerDataType.cs @@ -2,52 +2,34 @@ using System; namespace umbraco.editorControls.memberpicker { - /// - /// Summary description for MemberPickerDataType. - /// - public class MemberPickerDataType : cms.businesslogic.datatype.BaseDataType,interfaces.IDataType - { - private interfaces.IDataEditor _Editor; - private interfaces.IData _baseData; - private interfaces.IDataPrevalue _prevalueeditor; + public class MemberPickerDataType : cms.businesslogic.datatype.BaseDataType, interfaces.IDataType + { + private interfaces.IDataEditor _editor; + private interfaces.IData _baseData; + private interfaces.IDataPrevalue _prevalueeditor; - public override interfaces.IDataEditor DataEditor - { - get - { - if (_Editor == null) - _Editor = new memberPicker(Data); - return _Editor; - } - } + public override interfaces.IDataEditor DataEditor + { + get { return _editor ?? (_editor = new memberPicker(Data)); } + } - public override interfaces.IData Data - { - get - { - if (_baseData == null) - _baseData = new cms.businesslogic.datatype.DefaultData(this); - return _baseData; - } - } - public override string DataTypeName - { - get {return "Member Picker";} - } + public override interfaces.IData Data + { + get { return _baseData ?? (_baseData = new cms.businesslogic.datatype.DefaultData(this)); } + } + public override string DataTypeName + { + get { return "Member Picker"; } + } - public override Guid Id - { - get {return new Guid("39F533E4-0551-4505-A64B-E0425C5CE775");} - } + public override Guid Id + { + get { return new Guid("39F533E4-0551-4505-A64B-E0425C5CE775"); } + } - public override interfaces.IDataPrevalue PrevalueEditor - { - get - { - if (_prevalueeditor == null) - _prevalueeditor = new DefaultPrevalueEditor(this,false); - return _prevalueeditor; - } - } - } + public override interfaces.IDataPrevalue PrevalueEditor + { + get { return _prevalueeditor ?? (_prevalueeditor = new DefaultPrevalueEditor(this, false)); } + } + } } From 43b2a2185e9f3d8953cb9b9e40d13c934bdfc5d7 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Wed, 1 May 2013 13:02:09 -0200 Subject: [PATCH 03/11] Fix unit test --- src/Umbraco.Tests/PluginManagerTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Tests/PluginManagerTests.cs b/src/Umbraco.Tests/PluginManagerTests.cs index 4b690d6271..b96a514520 100644 --- a/src/Umbraco.Tests/PluginManagerTests.cs +++ b/src/Umbraco.Tests/PluginManagerTests.cs @@ -303,7 +303,7 @@ namespace Umbraco.Tests public void Resolves_DataTypes() { var types = PluginManager.Current.ResolveDataTypes(); - Assert.AreEqual(37, types.Count()); + Assert.AreEqual(38, types.Count()); } [Test] From a8ab7e4c35c53735767a9349d0ff3987cb5b8175 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Wed, 1 May 2013 13:05:12 -0200 Subject: [PATCH 04/11] Open new branch From 2170380f7b0d8e17b1a733c8b07f837c6210370e Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Wed, 1 May 2013 13:07:53 -0200 Subject: [PATCH 05/11] Opening new branch From 0b6f05e63075110542036c551f94f0a8c1559434 Mon Sep 17 00:00:00 2001 From: Shannon Deminick Date: Thu, 2 May 2013 03:48:14 -1000 Subject: [PATCH 06/11] Fixes: #U4-2172 - better single threaded supported for the database instance outside http context --- .../Persistence/DefaultDatabaseFactory.cs | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/Umbraco.Core/Persistence/DefaultDatabaseFactory.cs b/src/Umbraco.Core/Persistence/DefaultDatabaseFactory.cs index 05110cab25..0a71d1388f 100644 --- a/src/Umbraco.Core/Persistence/DefaultDatabaseFactory.cs +++ b/src/Umbraco.Core/Persistence/DefaultDatabaseFactory.cs @@ -1,3 +1,4 @@ +using System; using System.Web; using Umbraco.Core.Configuration; @@ -16,7 +17,12 @@ namespace Umbraco.Core.Persistence private readonly string _connectionStringName; private readonly string _connectionString; private readonly string _providerName; - private static volatile UmbracoDatabase _globalInstance = null; + + //very important to have ThreadStatic: + // see: http://issues.umbraco.org/issue/U4-2172 + [ThreadStatic] + private static volatile UmbracoDatabase _nonHttpInstance; + private static readonly object Locker = new object(); /// @@ -55,24 +61,24 @@ namespace Umbraco.Core.Persistence //no http context, create the singleton global object if (HttpContext.Current == null) { - if (_globalInstance == null) + if (_nonHttpInstance == null) { lock (Locker) { //double check - if (_globalInstance == null) + if (_nonHttpInstance == null) { - _globalInstance = string.IsNullOrEmpty(_providerName) == false && string.IsNullOrEmpty(_providerName) == false + _nonHttpInstance = string.IsNullOrEmpty(_providerName) == false && string.IsNullOrEmpty(_providerName) == false ? new UmbracoDatabase(_connectionString, _providerName) : new UmbracoDatabase(_connectionStringName); } } } - return _globalInstance; + return _nonHttpInstance; } //we have an http context, so only create one per request - if (!HttpContext.Current.Items.Contains(typeof(DefaultDatabaseFactory))) + if (HttpContext.Current.Items.Contains(typeof(DefaultDatabaseFactory)) == false) { HttpContext.Current.Items.Add(typeof (DefaultDatabaseFactory), string.IsNullOrEmpty(_providerName) == false && string.IsNullOrEmpty(_providerName) == false From 4295cba915131662113ee2211793d65a2e76a138 Mon Sep 17 00:00:00 2001 From: Shannon Deminick Date: Sat, 4 May 2013 18:12:02 -1000 Subject: [PATCH 07/11] Updated unit tests for TypeFinder - this shows the issue with the new refactored performance changes. --- src/Umbraco.Tests/TypeFinderTests.cs | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Tests/TypeFinderTests.cs b/src/Umbraco.Tests/TypeFinderTests.cs index 1e0fe16e4c..7246ec4bc1 100644 --- a/src/Umbraco.Tests/TypeFinderTests.cs +++ b/src/Umbraco.Tests/TypeFinderTests.cs @@ -63,7 +63,8 @@ namespace Umbraco.Tests typeof(TypeFinder).Assembly, typeof(ISqlHelper).Assembly, typeof(ICultureDictionary).Assembly, - typeof(Tag).Assembly + typeof(Tag).Assembly, + typeof(UmbracoExamine.BaseUmbracoIndexer).Assembly }; } @@ -80,8 +81,12 @@ namespace Umbraco.Tests [Test] public void Find_Classes_Of_Type() { - var typesFound = TypeFinder.FindClassesOfType(_assemblies); - Assert.AreEqual(2, typesFound.Count()); + var typesFound = TypeFinder.FindClassesOfType(_assemblies); + var originalTypesFound = TypeFinderOriginal.FindClassesOfType(_assemblies); + + Assert.AreEqual(originalTypesFound.Count(), typesFound.Count()); + Assert.AreEqual(4, typesFound.Count()); + Assert.AreEqual(4, originalTypesFound.Count()); } [Test] @@ -153,6 +158,18 @@ namespace Umbraco.Tests } + public class MyTag : ITag + { + public int Id { get; private set; } + public string TagCaption { get; private set; } + public string Group { get; private set; } + } + + public class MySuperTag : MyTag + { + + } + [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] public class MyTestAttribute : Attribute { From f5994d84426c4208596dbf1fea6a70619683299b Mon Sep 17 00:00:00 2001 From: Shannon Deminick Date: Sat, 4 May 2013 19:33:49 -1000 Subject: [PATCH 08/11] Fixes up async methods in ContentTypeControlNew to ensure that the UmbracoContext is current since the actions might run on different threads and therefore some event handlers might need access to the context. --- .../controls/ContentTypeControlNew.ascx.cs | 44 ++++++++++++++++--- 1 file changed, 38 insertions(+), 6 deletions(-) diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/controls/ContentTypeControlNew.ascx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/controls/ContentTypeControlNew.ascx.cs index 521cb8114d..a62389da2c 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/controls/ContentTypeControlNew.ascx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/controls/ContentTypeControlNew.ascx.cs @@ -63,7 +63,7 @@ namespace umbraco.controls //the async saving task private Action _asyncSaveTask; //the async delete property task - private Action _asyncDeleteTask; + private Action _asyncDeleteTask; override protected void OnInit(EventArgs e) { @@ -121,23 +121,43 @@ namespace umbraco.controls get { return ((BasePage)Page).getUser(); } } + /// + /// A class to track the async state for deleting a doc type property + /// + private class DeleteAsyncState + { + public Umbraco.Web.UmbracoContext UmbracoContext { get; private set; } + public GenericPropertyWrapper GenericPropertyWrapper { get; private set; } + + public DeleteAsyncState( + Umbraco.Web.UmbracoContext umbracoContext, + GenericPropertyWrapper genericPropertyWrapper) + { + UmbracoContext = umbracoContext; + GenericPropertyWrapper = genericPropertyWrapper; + } + } + /// /// A class to track the async state for saving the doc type /// private class SaveAsyncState { public SaveAsyncState( + Umbraco.Web.UmbracoContext umbracoContext, SaveClickEventArgs saveArgs, string originalAlias, string originalName, string[] originalPropertyAliases) { + UmbracoContext = umbracoContext; SaveArgs = saveArgs; _originalAlias = originalAlias; _originalName = originalName; _originalPropertyAliases = originalPropertyAliases; } + public Umbraco.Web.UmbracoContext UmbracoContext { get; private set; } public SaveClickEventArgs SaveArgs { get; private set; } private readonly string _originalAlias; private readonly string _originalName; @@ -244,7 +264,9 @@ namespace umbraco.controls protected void save_click(object sender, System.Web.UI.ImageClickEventArgs e) { - var state = new SaveAsyncState(new SaveClickEventArgs("Saved") + var state = new SaveAsyncState( + Umbraco.Web.UmbracoContext.Current, + new SaveClickEventArgs("Saved") { IconType = BasePage.speechBubbleIcon.success }, _contentType.Alias, _contentType.Text, _contentType.PropertyTypes.Select(x => x.Alias).ToArray()); @@ -257,6 +279,9 @@ namespace umbraco.controls { Trace.Write("ContentTypeControlNew", "executing task"); + //we need to re-set the UmbracoContext since it will be nulled and our cache handlers need it + global::Umbraco.Web.UmbracoContext.Current = asyncState.UmbracoContext; + _contentType.Text = txtName.Text; _contentType.Alias = txtAlias.Text; _contentType.IconUrl = ddlIcons.SelectedValue; @@ -752,7 +777,7 @@ jQuery(document).ready(function() {{ refreshDropDowns(); }}); Trace.Write("ContentTypeControlNew", "Start async operation"); //get the args from the async state - var args = (GenericPropertyWrapper)state; + var args = (DeleteAsyncState)state; //start the task var result = _asyncDeleteTask.BeginInvoke(args, cb, args); @@ -782,16 +807,23 @@ jQuery(document).ready(function() {{ refreshDropDowns(); }}); protected void gpw_Delete(object sender, EventArgs e) { + var state = new DeleteAsyncState( + Umbraco.Web.UmbracoContext.Current, + (GenericPropertyWrapper)sender); + //Add the async operation to the page - Page.RegisterAsyncTask(new PageAsyncTask(BeginAsyncDeleteOperation, EndAsyncDeleteOperation, HandleAsyncSaveTimeout, (GenericPropertyWrapper)sender)); + Page.RegisterAsyncTask(new PageAsyncTask(BeginAsyncDeleteOperation, EndAsyncDeleteOperation, HandleAsyncSaveTimeout, state)); //create the save task to be executed async - _asyncDeleteTask = genericPropertyWrapper => + _asyncDeleteTask = asyncState => { Trace.Write("ContentTypeControlNew", "executing task"); + //we need to re-set the UmbracoContext since it will be nulled and our cache handlers need it + global::Umbraco.Web.UmbracoContext.Current = asyncState.UmbracoContext; + //delete the property - genericPropertyWrapper.GenricPropertyControl.PropertyType.delete(); + asyncState.GenericPropertyWrapper.GenricPropertyControl.PropertyType.delete(); //we need to re-generate the xml structures because we're removing a content type property RegenerateXmlCaches(); From d501fcb679fc442cb6d61a97d04f6da1531eb0e1 Mon Sep 17 00:00:00 2001 From: Shannon Deminick Date: Sun, 5 May 2013 22:57:41 -1000 Subject: [PATCH 09/11] Fixes TypeFinder issue but retains the improved performance. Adds TypeHelper tests. --- src/Umbraco.Core/TypeFinder.cs | 278 +++++++++++++++---------- src/Umbraco.Core/TypeHelper.cs | 116 ++++++++++- src/Umbraco.Tests/TypeFinderTests.cs | 6 +- src/Umbraco.Tests/TypeHelperTests.cs | 84 ++++++++ src/Umbraco.Tests/Umbraco.Tests.csproj | 1 + 5 files changed, 370 insertions(+), 115 deletions(-) create mode 100644 src/Umbraco.Tests/TypeHelperTests.cs diff --git a/src/Umbraco.Core/TypeFinder.cs b/src/Umbraco.Core/TypeFinder.cs index 883bf2176f..e8597230f3 100644 --- a/src/Umbraco.Core/TypeFinder.cs +++ b/src/Umbraco.Core/TypeFinder.cs @@ -19,8 +19,6 @@ using Umbraco.Core.IO; namespace Umbraco.Core { - //TODO: Get the App_Code stuff in here from the old one - /// /// A utility class to find all classes of a certain type by reflection in the current bin folder /// of the web application. @@ -244,42 +242,66 @@ namespace Umbraco.Core "Examine." }; + /// + /// Finds any classes derived from the type T that contain the attribute TAttribute + /// + /// + /// + /// public static IEnumerable FindClassesOfTypeWithAttribute() where TAttribute : Attribute { return FindClassesOfTypeWithAttribute(GetAssembliesWithKnownExclusions(), true); } + /// + /// Finds any classes derived from the type T that contain the attribute TAttribute + /// + /// + /// + /// + /// public static IEnumerable FindClassesOfTypeWithAttribute(IEnumerable assemblies) where TAttribute : Attribute { return FindClassesOfTypeWithAttribute(assemblies, true); } - public static IEnumerable FindClassesOfTypeWithAttribute(IEnumerable assemblies, - bool onlyConcreteClasses) - where TAttribute : Attribute + /// + /// Finds any classes derived from the type T that contain the attribute TAttribute + /// + /// + /// + /// + /// + /// + public static IEnumerable FindClassesOfTypeWithAttribute( + IEnumerable assemblies, + bool onlyConcreteClasses) + where TAttribute : Attribute + { + return FindClassesOfTypeWithAttribute(typeof (T), assemblies, onlyConcreteClasses); + } + + /// + /// Finds any classes derived from the assignTypeFrom Type that contain the attribute TAttribute + /// + /// + /// + /// + /// + /// + public static IEnumerable FindClassesOfTypeWithAttribute( + Type assignTypeFrom, + IEnumerable assemblies, + bool onlyConcreteClasses) + where TAttribute : Attribute { if (assemblies == null) throw new ArgumentNullException("assemblies"); - - // a assembly cant contain types that are assignable to a type it doesn't reference - assemblies = RemoveAssembliesThatDontReferenceAssemblyOfType(typeof (T), assemblies); - // a assembly cant contain types with a attribute it doesn't reference - assemblies = RemoveAssembliesThatDontReferenceAssemblyOfType(typeof (TAttribute), assemblies); - - var l = new List(); - foreach(var a in assemblies) - { - var types = from t in GetTypesWithFormattedException(a) - where !t.IsInterface - && typeof (T).IsAssignableFrom(t) - && t.GetCustomAttributes(false).Any() - && (!onlyConcreteClasses || (t.IsClass && !t.IsAbstract)) - select t; - l.AddRange(types); - } - - return l; + + return GetClasses(assignTypeFrom, assemblies, onlyConcreteClasses, + //the additional filter will ensure that any found types also have the attribute applied. + t => t.GetCustomAttributes(false).Any()); } /// @@ -303,7 +325,7 @@ namespace Umbraco.Core { if (assemblies == null) throw new ArgumentNullException("assemblies"); - return GetAssignablesFromType(assemblies, onlyConcreteClasses); + return GetClasses(typeof(T), assemblies, onlyConcreteClasses); } /// @@ -330,66 +352,50 @@ namespace Umbraco.Core return FindClassesWithAttribute(typeof(T), assemblies, onlyConcreteClasses); } - /// - /// Finds the classes with attribute. - /// - /// The attribute type - /// The assemblies. - /// if set to true only concrete classes. - /// - public static IEnumerable FindClassesWithAttribute(Type type, IEnumerable assemblies, - bool onlyConcreteClasses) - { - if (assemblies == null) throw new ArgumentNullException("assemblies"); - if (!TypeHelper.IsTypeAssignableFrom(type)) - throw new ArgumentException("The type specified: " + type + " is not an Attribute type"); - // a assembly cant contain types with a attribute it doesn't reference - assemblies = RemoveAssembliesThatDontReferenceAssemblyOfType(type, assemblies); + /// + /// Finds any classes with the attribute. + /// + /// The attribute type + /// The assemblies. + /// if set to true only concrete classes. + /// + public static IEnumerable FindClassesWithAttribute( + Type attributeType, + IEnumerable assemblies, + bool onlyConcreteClasses) + { + if (assemblies == null) throw new ArgumentNullException("assemblies"); - var l = new List(); - foreach (var a in assemblies) - { - var types = from t in GetTypesWithFormattedException(a) - where - !t.IsInterface && t.GetCustomAttributes(type, false).Any() && - (!onlyConcreteClasses || (t.IsClass && !t.IsAbstract)) - select t; - l.AddRange(types); - } + if (TypeHelper.IsTypeAssignableFrom(attributeType) == false) + throw new ArgumentException("The type specified: " + attributeType + " is not an Attribute type"); - return l; - } - /// - /// Removes assemblies that doesn't reference the assembly of the type we are looking for. - /// - /// - /// - /// - private static IEnumerable RemoveAssembliesThatDontReferenceAssemblyOfType(Type type, IEnumerable assemblies) - { - // Avoid scanning assembly if it doesn't contain a reference to the assembly containing the type we are looking for - // to the assembly containing the attribute we are looking for - var assemblyNameOfType = type.Assembly.GetName().Name; + var foundAssignableTypes = new List(); - return assemblies - .Where(assembly => assembly == type.Assembly - || HasReferenceToAssemblyWithName(assembly, assemblyNameOfType)).ToList(); - } - /// - /// checks if the assembly has a reference with the same name as the expected assembly name. - /// - /// - /// - /// - private static bool HasReferenceToAssemblyWithName(Assembly assembly, string expectedAssemblyName) - { - return assembly - .GetReferencedAssemblies() - .Select(a => a.Name) - .Contains(expectedAssemblyName, StringComparer.Ordinal); - } + var assemblyList = assemblies.ToArray(); - /// + //find all assembly references that are referencing the attribute type's assembly since we + //should only be scanning those assemblies because any other assembly will definitely not + //contain a class that has this attribute. + var referencedAssemblies = TypeHelper.GetReferencedAssemblies(attributeType, assemblyList); + + foreach (var a in referencedAssemblies) + { + //get all types in the assembly that are sub types of the current type + var allTypes = GetTypesWithFormattedException(a).ToArray(); + + var types = allTypes.Where(t => TypeHelper.IsNonStaticClass(t) + && (onlyConcreteClasses == false || t.IsAbstract == false) + //the type must have this attribute + && t.GetCustomAttributes(attributeType, false).Any()); + + foundAssignableTypes.AddRange(types); + } + + return foundAssignableTypes; + } + + + /// /// Finds the classes with attribute. /// /// @@ -415,34 +421,96 @@ namespace Umbraco.Core #region Private methods - /// - /// Gets a collection of assignables of type T from a collection of assemblies - /// - /// - /// - /// - /// - private static IEnumerable GetAssignablesFromType(IEnumerable assemblies, bool onlyConcreteClasses) + /// + /// Finds types that are assignable from the assignTypeFrom parameter and will scan for these types in the assembly + /// list passed in, however we will only scan assemblies that have a reference to the assignTypeFrom Type or any type + /// deriving from the base type. + /// + /// + /// + /// + /// An additional filter to apply for what types will actually be included in the return value + /// + private static IEnumerable GetClasses( + Type assignTypeFrom, + IEnumerable assemblies, + bool onlyConcreteClasses, + Func additionalFilter = null) { - return GetTypes(typeof(T), assemblies, onlyConcreteClasses); - } + //the default filter will always return true. + if (additionalFilter == null) + { + additionalFilter = type => true; + } - private static IEnumerable GetTypes(Type assignTypeFrom, IEnumerable assemblies, bool onlyConcreteClasses) - { - // a assembly cant contain types that are assignable to a type it doesn't reference - assemblies = RemoveAssembliesThatDontReferenceAssemblyOfType(assignTypeFrom, assemblies); + var foundAssignableTypes = new List(); + + var assemblyList = assemblies.ToArray(); + + //find all assembly references that are referencing the current type's assembly since we + //should only be scanning those assemblies because any other assembly will definitely not + //contain sub type's of the one we're currently looking for + var referencedAssemblies = TypeHelper.GetReferencedAssemblies(assignTypeFrom, assemblyList); + + //get a list of non-referenced assemblies (we'll use this when we recurse below) + var otherAssemblies = assemblyList.Where(x => referencedAssemblies.Contains(x) == false).ToArray(); + + //loop through the referenced assemblies + foreach (var a in referencedAssemblies) + { + //get all types in the assembly that are sub types of the current type + var allSubTypes = GetTypesWithFormattedException(a) + .Where(assignTypeFrom.IsAssignableFrom) + .ToArray(); + + //now filter the types based on the onlyConcreteClasses flag, not interfaces, not static classes + var filteredTypes = allSubTypes + .Where(t => (TypeHelper.IsNonStaticClass(t) + && (onlyConcreteClasses == false || t.IsAbstract == false) + && additionalFilter(t))) + .ToArray(); + + //add the types to our list to return + foundAssignableTypes.AddRange(filteredTypes); + + //now we need to include types that may be inheriting from sub classes of the type being searched for + //so we will search in assemblies that reference those types too. + foreach (var subTypesInAssembly in allSubTypes.GroupBy(x => x.Assembly)) + { + + //So that we are not scanning too much, we need to group the sub types: + // * if there is more than 1 sub type in the same assembly then we should only search on the 'lowest base' type. + // * We should also not search for sub types if the type is sealed since you cannot inherit from a sealed class + // * We should not search for sub types if the type is static since you cannot inherit from them. + var subTypeList = subTypesInAssembly + .Where(t => t.IsSealed == false && TypeHelper.IsStaticClass(t) == false) + .ToArray(); + + var baseClassAttempt = TypeHelper.GetLowestBaseType(subTypeList); + + //if there's a base class amongst the types then we'll only search for that type. + //otherwise we'll have to search for all of them. + var subTypesToSearch = new List(); + if (baseClassAttempt.Success) + { + subTypesToSearch.Add(baseClassAttempt.Result); + } + else + { + subTypesToSearch.AddRange(subTypeList); + } + + foreach (var typeToSearch in subTypesToSearch) + { + //recursively find the types inheriting from this sub type in the other non-scanned assemblies. + var foundTypes = GetClasses(typeToSearch, otherAssemblies, onlyConcreteClasses); + foundAssignableTypes.AddRange(foundTypes); + } + + } - var l = new List(); - foreach (var a in assemblies) - { - var types = from t in GetTypesWithFormattedException(a) - where - !t.IsInterface && assignTypeFrom.IsAssignableFrom(t) && - (!onlyConcreteClasses || (t.IsClass && !t.IsAbstract)) - select t; - l.AddRange(types); } - return l; + return foundAssignableTypes; } private static IEnumerable GetTypesWithFormattedException(Assembly a) diff --git a/src/Umbraco.Core/TypeHelper.cs b/src/Umbraco.Core/TypeHelper.cs index c2f8baa950..ce3927c8e5 100644 --- a/src/Umbraco.Core/TypeHelper.cs +++ b/src/Umbraco.Core/TypeHelper.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Concurrent; +using System.Collections.Generic; using System.Linq; using System.Reflection; @@ -11,12 +12,114 @@ namespace Umbraco.Core /// internal static class TypeHelper { - private static readonly ConcurrentDictionary, bool> TypeCheckCache = new ConcurrentDictionary, bool>(); - private static readonly ConcurrentDictionary ValueTypeCache = new ConcurrentDictionary(); - private static readonly ConcurrentDictionary ImplicitValueTypeCache = new ConcurrentDictionary(); + private static readonly ConcurrentDictionary GetFieldsCache = new ConcurrentDictionary(); private static readonly ConcurrentDictionary, PropertyInfo[]> GetPropertiesCache = new ConcurrentDictionary, PropertyInfo[]>(); + /// + /// Find all assembly references that are referencing the assignTypeFrom Type's assembly found in the assemblyList + /// + /// + /// + /// + /// + /// If the assembly of the assignTypeFrom Type is in the App_Code assembly, then we return nothing since things cannot + /// reference that assembly, same with the global.asax assembly. + /// + public static Assembly[] GetReferencedAssemblies(Type assignTypeFrom, IEnumerable assemblies) + { + //check if it is the app_code assembly. + //check if it is App_global.asax assembly + var appCodeAssembly = Assembly.Load("App_Code"); + if (assignTypeFrom.Assembly == appCodeAssembly || assignTypeFrom.Assembly.FullName.StartsWith("App_global.asax")) + { + return Enumerable.Empty().ToArray(); + } + + //find all assembly references that are referencing the current type's assembly since we + //should only be scanning those assemblies because any other assembly will definitely not + //contain sub type's of the one we're currently looking for + return assemblies + .Where(assembly => + assembly == assignTypeFrom.Assembly || HasReferenceToAssemblyWithName(assembly, assignTypeFrom.Assembly.GetName().Name)) + .ToArray(); + } + + /// + /// checks if the assembly has a reference with the same name as the expected assembly name. + /// + /// + /// + /// + private static bool HasReferenceToAssemblyWithName(Assembly assembly, string expectedAssemblyName) + { + return assembly + .GetReferencedAssemblies() + .Select(a => a.Name) + .Contains(expectedAssemblyName, StringComparer.Ordinal); + } + + /// + /// Returns true if the type is a class and is not static + /// + /// + /// + public static bool IsNonStaticClass(Type t) + { + return t.IsClass && IsStaticClass(t) == false; + } + + /// + /// Returns true if the type is a static class + /// + /// + /// + /// + /// In IL a static class is abstract and sealed + /// see: http://stackoverflow.com/questions/1175888/determine-if-a-type-is-static + /// + public static bool IsStaticClass(Type type) + { + return type.IsAbstract && type.IsSealed; + } + + /// + /// Finds a lowest base class amongst a collection of types + /// + /// + /// + /// + /// The term 'lowest' refers to the most base class of the type collection. + /// If a base type is not found amongst the type collection then an invalid attempt is returned. + /// + public static Attempt GetLowestBaseType(params Type[] types) + { + if (types.Length == 0) + { + return Attempt.False; + } + if (types.Length == 1) + { + return new Attempt(true, types[0]); + } + + foreach (var curr in types) + { + var others = types.Except(new[] {curr}); + + //is the curr type a common denominator for all others ? + var isBase = others.All(curr.IsAssignableFrom); + + //if this type is the base for all others + if (isBase) + { + return new Attempt(true, curr); + } + } + + return Attempt.False; + } + /// /// Determines whether the type is assignable from the specified implementation , /// and caches the result across the application using a . @@ -28,8 +131,7 @@ namespace Umbraco.Core /// public static bool IsTypeAssignableFrom(Type contract, Type implementation) { - // NOTE The use of a Tuple<,> here is because its Equals / GetHashCode implementation is literally 10.5x faster than KeyValuePair<,> - return TypeCheckCache.GetOrAdd(new Tuple(contract, implementation), x => x.Item1.IsAssignableFrom(x.Item2)); + return contract.IsAssignableFrom(implementation); } /// @@ -49,7 +151,7 @@ namespace Umbraco.Core /// The implementation. public static bool IsValueType(Type implementation) { - return ValueTypeCache.GetOrAdd(implementation, x => x.IsValueType || x.IsPrimitive); + return implementation.IsValueType || implementation.IsPrimitive; } /// @@ -58,7 +160,7 @@ namespace Umbraco.Core /// The implementation. public static bool IsImplicitValueType(Type implementation) { - return ImplicitValueTypeCache.GetOrAdd(implementation, x => IsValueType(implementation) || implementation.IsEnum || implementation == typeof(string)); + return IsValueType(implementation) || implementation.IsEnum || implementation == typeof (string); } public static bool IsTypeAssignableFrom(object implementation) diff --git a/src/Umbraco.Tests/TypeFinderTests.cs b/src/Umbraco.Tests/TypeFinderTests.cs index 7246ec4bc1..85b8bc9c9d 100644 --- a/src/Umbraco.Tests/TypeFinderTests.cs +++ b/src/Umbraco.Tests/TypeFinderTests.cs @@ -16,7 +16,6 @@ using SqlCE4Umbraco; using Umbraco.Core; using Umbraco.Core.IO; using Umbraco.Tests; -using Umbraco.Tests.PartialTrust; using Umbraco.Tests.TestHelpers; using Umbraco.Web.BaseRest; using umbraco; @@ -30,8 +29,9 @@ using umbraco.uicontrols; namespace Umbraco.Tests { - /// - /// Full Trust benchmark tests for typefinder and the old typefinder + + /// + /// Tests for typefinder /// [TestFixture] public class TypeFinderTests diff --git a/src/Umbraco.Tests/TypeHelperTests.cs b/src/Umbraco.Tests/TypeHelperTests.cs new file mode 100644 index 0000000000..93bde4439c --- /dev/null +++ b/src/Umbraco.Tests/TypeHelperTests.cs @@ -0,0 +1,84 @@ +using System; +using System.ComponentModel; +using System.Data.Common; +using System.Data.Odbc; +using System.Data.OleDb; +using System.Data.SqlClient; +using NUnit.Framework; +using Umbraco.Core; +using Umbraco.Tests.PartialTrust; +using Umbraco.Web; +using UmbracoExamine; +using umbraco; +using umbraco.presentation; +using umbraco.presentation.nodeFactory; +using umbraco.presentation.umbraco.Search; + +namespace Umbraco.Tests +{ + /// + /// Tests for TypeHelper + /// + [TestFixture] + public class TypeHelperTests : AbstractPartialTrustFixture + { + + [Test] + public void Is_Static_Class() + { + Assert.IsTrue(TypeHelper.IsStaticClass(typeof(TypeHelper))); + Assert.IsFalse(TypeHelper.IsStaticClass(typeof(TypeHelperTests))); + } + + [Test] + public void Find_Common_Base_Class() + { + var t1 = TypeHelper.GetLowestBaseType(typeof (OleDbCommand), + typeof (OdbcCommand), + typeof (SqlCommand)); + Assert.IsFalse(t1.Success); + + var t2 = TypeHelper.GetLowestBaseType(typeof (OleDbCommand), + typeof (OdbcCommand), + typeof (SqlCommand), + typeof (Component)); + Assert.IsTrue(t2.Success); + Assert.AreEqual(typeof(Component), t2.Result); + + var t3 = TypeHelper.GetLowestBaseType(typeof (OleDbCommand), + typeof (OdbcCommand), + typeof (SqlCommand), + typeof (Component), + typeof (Component).BaseType); + Assert.IsTrue(t3.Success); + Assert.AreEqual(typeof(MarshalByRefObject), t3.Result); + + var t4 = TypeHelper.GetLowestBaseType(typeof(OleDbCommand), + typeof(OdbcCommand), + typeof(SqlCommand), + typeof(Component), + typeof(Component).BaseType, + typeof(int)); + Assert.IsFalse(t4.Success); + + var t5 = TypeHelper.GetLowestBaseType(typeof(UmbracoEventManager)); + Assert.IsTrue(t5.Success); + Assert.AreEqual(typeof(UmbracoEventManager), t5.Result); + + var t6 = TypeHelper.GetLowestBaseType(typeof (IApplicationEventHandler), + typeof (LegacyScheduledTasks), + typeof(CacheHelperExtensions.CacheHelperApplicationEventListener)); + Assert.IsTrue(t6.Success); + Assert.AreEqual(typeof(IApplicationEventHandler), t6.Result); + + } + + public override void TestSetup() + { + } + + public override void TestTearDown() + { + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index a5e4afb56e..9d9d355ba8 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -121,6 +121,7 @@ ExamineResources.resx + From f0cacf0d522de953cf28196fad3e2244603eae53 Mon Sep 17 00:00:00 2001 From: Shannon Deminick Date: Sun, 5 May 2013 23:03:31 -1000 Subject: [PATCH 10/11] Fixed app_code assembly check. --- src/Umbraco.Core/AssemblyExtensions.cs | 36 +++++++++++++++++++++++++- src/Umbraco.Core/TypeHelper.cs | 3 +-- 2 files changed, 36 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Core/AssemblyExtensions.cs b/src/Umbraco.Core/AssemblyExtensions.cs index df4487c197..666dce18fc 100644 --- a/src/Umbraco.Core/AssemblyExtensions.cs +++ b/src/Umbraco.Core/AssemblyExtensions.cs @@ -19,7 +19,41 @@ namespace Umbraco.Core return new FileInfo(path); } - /// + /// + /// Returns true if the assembly is the App_Code assembly + /// + /// + /// + public static bool IsAppCodeAssembly(this Assembly assembly) + { + if (assembly.FullName.StartsWith("App_Code")) + { + try + { + Assembly.Load("App_Code"); + return true; + } + catch (FileNotFoundException) + { + //this will occur if it cannot load the assembly + return false; + } + } + return false; + } + + /// + /// Returns true if the assembly is the compiled global asax. + /// + /// + /// + public static bool IsGlobalAsaxAssembly(this Assembly assembly) + { + //only way I can figure out how to test is by the name + return assembly.FullName.StartsWith("App_global.asax"); + } + + /// /// Returns the file used to load the assembly /// /// diff --git a/src/Umbraco.Core/TypeHelper.cs b/src/Umbraco.Core/TypeHelper.cs index ce3927c8e5..26dd2a8402 100644 --- a/src/Umbraco.Core/TypeHelper.cs +++ b/src/Umbraco.Core/TypeHelper.cs @@ -30,8 +30,7 @@ namespace Umbraco.Core { //check if it is the app_code assembly. //check if it is App_global.asax assembly - var appCodeAssembly = Assembly.Load("App_Code"); - if (assignTypeFrom.Assembly == appCodeAssembly || assignTypeFrom.Assembly.FullName.StartsWith("App_global.asax")) + if (assignTypeFrom.Assembly.IsAppCodeAssembly() || assignTypeFrom.Assembly.IsGlobalAsaxAssembly()) { return Enumerable.Empty().ToArray(); } From 9d19ed9463a5c6f86b6a33042c2717bc47f133fb Mon Sep 17 00:00:00 2001 From: Shannon Deminick Date: Sun, 5 May 2013 23:35:51 -1000 Subject: [PATCH 11/11] Fix merge issues --- .../Persistence/DefaultDatabaseFactory.cs | 2 +- .../controls/ContentTypeControlNew.ascx.cs | 19 ++++++++++++------- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/src/Umbraco.Core/Persistence/DefaultDatabaseFactory.cs b/src/Umbraco.Core/Persistence/DefaultDatabaseFactory.cs index 0a71d1388f..bf06a66f2b 100644 --- a/src/Umbraco.Core/Persistence/DefaultDatabaseFactory.cs +++ b/src/Umbraco.Core/Persistence/DefaultDatabaseFactory.cs @@ -92,7 +92,7 @@ namespace Umbraco.Core.Persistence { if (HttpContext.Current == null) { - _globalInstance.Dispose(); + _nonHttpInstance.Dispose(); } else { diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/controls/ContentTypeControlNew.ascx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/controls/ContentTypeControlNew.ascx.cs index b87954e5b1..942c9b4025 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/controls/ContentTypeControlNew.ascx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/controls/ContentTypeControlNew.ascx.cs @@ -848,8 +848,6 @@ jQuery(document).ready(function() {{ refreshDropDowns(); }}); { var tab = gpData.Tabs.FirstOrDefault(x => x.Id == gpData.Tab); if (tab != null) - (GenericPropertyWrapper)sender); - { var caption = tab.GetRawCaption(); contentTypeItem.AddPropertyType(propertyType, caption); @@ -1077,7 +1075,7 @@ jQuery(document).ready(function() {{ refreshDropDowns(); }}); Trace.Write("ContentTypeControlNew", "Start async operation"); //get the args from the async state - var args = (GenericPropertyWrapper)state; + var args = (DeleteAsyncState)state; //start the task var result = _asyncDeleteTask.BeginInvoke(args, cb, args); @@ -1112,22 +1110,29 @@ jQuery(document).ready(function() {{ refreshDropDowns(); }}); /// protected void gpw_Delete(object sender, EventArgs e) { + var state = new DeleteAsyncState( + Umbraco.Web.UmbracoContext.Current, + (GenericPropertyWrapper)sender); + //Add the async operation to the page - Page.RegisterAsyncTask(new PageAsyncTask(BeginAsyncDeleteOperation, EndAsyncDeleteOperation, HandleAsyncSaveTimeout, (GenericPropertyWrapper)sender)); + Page.RegisterAsyncTask(new PageAsyncTask(BeginAsyncDeleteOperation, EndAsyncDeleteOperation, HandleAsyncSaveTimeout, state)); //create the save task to be executed async - _asyncDeleteTask = genericPropertyWrapper => + _asyncDeleteTask = asyncState => { Trace.Write("ContentTypeControlNew", "executing task"); + //we need to re-set the UmbracoContext since it will be nulled and our cache handlers need it + global::Umbraco.Web.UmbracoContext.Current = asyncState.UmbracoContext; + if (_contentType.ContentTypeItem is IContentType || _contentType.ContentTypeItem is IMediaType) { - _contentType.ContentTypeItem.RemovePropertyType(genericPropertyWrapper.PropertyType.Alias); + _contentType.ContentTypeItem.RemovePropertyType(asyncState.GenericPropertyWrapper.PropertyType.Alias); _contentType.Save(); } //delete the property - genericPropertyWrapper.GenricPropertyControl.PropertyType.delete(); + asyncState.GenericPropertyWrapper.GenricPropertyControl.PropertyType.delete(); //we need to re-generate the xml structures because we're removing a content type property RegenerateXmlCaches();