Freedom Friday POC of native code first - limited to test project.
Making a few corrections to the serialization attributes after having tested ContentType Serialization. Enabling bulk saving of new ContentTypes with Parent/Composition dependencies.
This commit is contained in:
@@ -7,9 +7,18 @@ namespace Umbraco.Core.Models
|
||||
/// Enum for the various statuses a Content object can have
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
[DataContract(IsReference = true)]
|
||||
[DataContract]
|
||||
public enum ContentStatus
|
||||
{
|
||||
Unpublished, Published, Expired, Trashed, AwaitingRelease
|
||||
[EnumMember]
|
||||
Unpublished,
|
||||
[EnumMember]
|
||||
Published,
|
||||
[EnumMember]
|
||||
Expired,
|
||||
[EnumMember]
|
||||
Trashed,
|
||||
[EnumMember]
|
||||
AwaitingRelease
|
||||
}
|
||||
}
|
||||
@@ -27,7 +27,7 @@ namespace Umbraco.Core.Models
|
||||
/// <summary>
|
||||
/// Gets or sets the alias of the default Template.
|
||||
/// </summary>
|
||||
[DataMember]
|
||||
[IgnoreDataMember]
|
||||
public ITemplate DefaultTemplate
|
||||
{
|
||||
get { return AllowedTemplates.FirstOrDefault(x => x != null && x.Id == DefaultTemplateId); }
|
||||
|
||||
@@ -1,19 +1,21 @@
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.Serialization;
|
||||
using Umbraco.Core.Models.EntityBase;
|
||||
using Umbraco.Core.Models.Membership;
|
||||
|
||||
namespace Umbraco.Core.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents an abstract class for base ContentType properties and methods
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
[DataContract(IsReference = true)]
|
||||
public abstract class ContentTypeBase : Entity, IContentTypeBase
|
||||
{
|
||||
private int _parentId;
|
||||
private Lazy<int> _parentId;
|
||||
private string _name;
|
||||
private int _level;
|
||||
private string _path;
|
||||
@@ -31,7 +33,7 @@ namespace Umbraco.Core.Models
|
||||
|
||||
protected ContentTypeBase(int parentId)
|
||||
{
|
||||
_parentId = parentId;
|
||||
_parentId = new Lazy<int>(() => parentId);
|
||||
_allowedContentTypes = new List<ContentTypeSort>();
|
||||
_propertyGroups = new PropertyGroupCollection();
|
||||
}
|
||||
@@ -64,14 +66,19 @@ namespace Umbraco.Core.Models
|
||||
[DataMember]
|
||||
public virtual int ParentId
|
||||
{
|
||||
get { return _parentId; }
|
||||
get { return _parentId.Value; }
|
||||
set
|
||||
{
|
||||
_parentId = value;
|
||||
_parentId = new Lazy<int>(() => value);
|
||||
OnPropertyChanged(ParentIdSelector);
|
||||
}
|
||||
}
|
||||
|
||||
public void SetLazyParentId(Lazy<int> id)
|
||||
{
|
||||
_parentId = id;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the name of the current entity
|
||||
/// </summary>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.Serialization;
|
||||
@@ -8,6 +9,8 @@ namespace Umbraco.Core.Models
|
||||
/// <summary>
|
||||
/// Represents an abstract class for composition specific ContentType properties and methods
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
[DataContract(IsReference = true)]
|
||||
public abstract class ContentTypeCompositionBase : ContentTypeBase, IContentTypeComposition
|
||||
{
|
||||
private List<IContentTypeComposition> _contentTypeComposition;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Umbraco.Core.Models.EntityBase;
|
||||
using System;
|
||||
using Umbraco.Core.Models.EntityBase;
|
||||
|
||||
namespace Umbraco.Core.Models
|
||||
{
|
||||
@@ -10,11 +11,16 @@ namespace Umbraco.Core.Models
|
||||
/// <summary>
|
||||
/// Gets or sets the Id of the ContentType
|
||||
/// </summary>
|
||||
public int Id { get; set; }
|
||||
public Lazy<int> Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Sort Order of the ContentType
|
||||
/// </summary>
|
||||
public int SortOrder { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Alias of the ContentType
|
||||
/// </summary>
|
||||
public string Alias { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -11,12 +11,16 @@ namespace Umbraco.Core.Models
|
||||
/// but will be saved under the Ntext column.
|
||||
/// </remarks>
|
||||
[Serializable]
|
||||
[DataContract(IsReference = true)]
|
||||
[DataContract]
|
||||
public enum DataTypeDatabaseType
|
||||
{
|
||||
[EnumMember]
|
||||
Integer,
|
||||
[EnumMember]
|
||||
Date,
|
||||
[EnumMember]
|
||||
Nvarchar,
|
||||
[EnumMember]
|
||||
Ntext /*, Object*/
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Umbraco.Core.Models.EntityBase;
|
||||
using Umbraco.Core.Models.Membership;
|
||||
|
||||
@@ -86,6 +87,8 @@ namespace Umbraco.Core.Models
|
||||
/// <summary>
|
||||
/// Gets an enumerable list of Property Types aggregated for all groups
|
||||
/// </summary>
|
||||
IEnumerable<PropertyType> PropertyTypes { get; }
|
||||
IEnumerable<PropertyType> PropertyTypes { get; }
|
||||
|
||||
void SetLazyParentId(Lazy<int> id);
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,7 @@ namespace Umbraco.Core.Models
|
||||
/// Represents a collection of <see cref="PropertyGroup"/> objects
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
[DataContract(IsReference = true)]
|
||||
[DataContract]
|
||||
public class PropertyGroupCollection : KeyedCollection<string, PropertyGroup>, INotifyCollectionChanged
|
||||
{
|
||||
private readonly ReaderWriterLockSlim _addLocker = new ReaderWriterLockSlim();
|
||||
|
||||
@@ -25,7 +25,7 @@ namespace Umbraco.Core.Models
|
||||
private int _sortOrder;
|
||||
private string _validationRegExp;
|
||||
|
||||
public PropertyType(DataTypeDefinition dataTypeDefinition)
|
||||
public PropertyType(IDataTypeDefinition dataTypeDefinition)
|
||||
{
|
||||
if(dataTypeDefinition.HasIdentity)
|
||||
DataTypeId = dataTypeDefinition.Id;
|
||||
|
||||
@@ -12,7 +12,7 @@ namespace Umbraco.Core.Models
|
||||
/// Represents a collection of <see cref="PropertyType"/> objects
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
[DataContract(IsReference = true)]
|
||||
[DataContract]
|
||||
public class PropertyTypeCollection : KeyedCollection<string, PropertyType>, INotifyCollectionChanged
|
||||
{
|
||||
private readonly ReaderWriterLockSlim _addLocker = new ReaderWriterLockSlim();
|
||||
|
||||
@@ -31,6 +31,9 @@ namespace Umbraco.Core.Models
|
||||
Key = name.EncodeAsGuid();
|
||||
_name = name;
|
||||
_alias = alias;
|
||||
|
||||
CreateDate = DateTime.UtcNow;
|
||||
UpdateDate = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
[DataMember]
|
||||
|
||||
@@ -90,7 +90,7 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
//Insert collection of allowed content types
|
||||
foreach (var allowedContentType in entity.AllowedContentTypes)
|
||||
{
|
||||
Database.Insert(new ContentTypeAllowedContentTypeDto { Id = entity.Id, AllowedId = allowedContentType.Id, SortOrder = allowedContentType.SortOrder });
|
||||
Database.Insert(new ContentTypeAllowedContentTypeDto { Id = entity.Id, AllowedId = allowedContentType.Id.Value, SortOrder = allowedContentType.SortOrder });
|
||||
}
|
||||
|
||||
var propertyFactory = new PropertyGroupFactory(nodeDto.NodeId);
|
||||
@@ -145,7 +145,7 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
//Insert collection of allowed content types
|
||||
foreach (var allowedContentType in entity.AllowedContentTypes)
|
||||
{
|
||||
Database.Insert(new ContentTypeAllowedContentTypeDto { Id = entity.Id, AllowedId = allowedContentType.Id, SortOrder = allowedContentType.SortOrder });
|
||||
Database.Insert(new ContentTypeAllowedContentTypeDto { Id = entity.Id, AllowedId = allowedContentType.Id.Value, SortOrder = allowedContentType.SortOrder });
|
||||
}
|
||||
|
||||
//Check Dirty properties for Tabs/Groups and PropertyTypes - insert and delete accordingly
|
||||
@@ -202,7 +202,7 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
allowedContentTypesSql.Where("[cmsContentTypeAllowedContentType].[Id] = @Id", new { Id = id });
|
||||
|
||||
var allowedContentTypeDtos = Database.Fetch<ContentTypeAllowedContentTypeDto>(allowedContentTypesSql);
|
||||
return allowedContentTypeDtos.Select(x => new ContentTypeSort { Id = x.AllowedId, SortOrder = x.SortOrder }).ToList();
|
||||
return allowedContentTypeDtos.Select(x => new ContentTypeSort { Id = new Lazy<int>(() => x.AllowedId), SortOrder = x.SortOrder }).ToList();
|
||||
}
|
||||
|
||||
protected PropertyGroupCollection GetPropertyGroupCollection(int id)
|
||||
|
||||
@@ -532,7 +532,7 @@ namespace Umbraco.Core.Services
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This method ensures that Content is saved lazily, so a new graph of <see cref="IContent"/>
|
||||
/// objects can be saved in bulk. But not that objects are saved one at a time to ensure Ids.
|
||||
/// objects can be saved in bulk. But note that objects are saved one at a time to ensure Ids.
|
||||
/// </remarks>
|
||||
/// <param name="contents">Collection of Lazy <see cref="IContent"/> to save</param>
|
||||
/// <param name="userId">Optional Id of the User saving the Content</param>
|
||||
|
||||
@@ -109,6 +109,26 @@ namespace Umbraco.Core.Services
|
||||
_unitOfWork.Commit();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Saves a collection of lazy loaded <see cref="IContentType"/> objects.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This method ensures that ContentType is saved lazily, so a new graph of <see cref="IContentType"/>
|
||||
/// objects can be saved in bulk. But note that objects are saved one at a time to ensure Ids.
|
||||
/// </remarks>
|
||||
/// <param name="contentTypes">Collection of Lazy <see cref="IContentType"/> to save</param>
|
||||
/// <param name="userId">Optional Id of the User saving the ContentTypes</param>
|
||||
public void Save(IEnumerable<Lazy<IContentType>> contentTypes, int userId = -1)
|
||||
{
|
||||
var repository = RepositoryResolver.ResolveByType<IContentTypeRepository, IContentType, int>(_unitOfWork);
|
||||
foreach (var content in contentTypes)
|
||||
{
|
||||
content.Value.CreatorId = 0;
|
||||
repository.AddOrUpdate(content.Value);
|
||||
_unitOfWork.Commit();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deletes a single <see cref="IContentType"/> object
|
||||
/// </summary>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Umbraco.Core.Models;
|
||||
|
||||
@@ -48,6 +49,17 @@ namespace Umbraco.Core.Services
|
||||
/// <param name="contentTypes">Collection of <see cref="IContentType"/> to save</param>
|
||||
void Save(IEnumerable<IContentType> contentTypes);
|
||||
|
||||
/// <summary>
|
||||
/// Saves a collection of lazy loaded <see cref="IContentType"/> objects.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This method ensures that ContentType is saved lazily, so a new graph of <see cref="IContentType"/>
|
||||
/// objects can be saved in bulk. But note that objects are saved one at a time to ensure Ids.
|
||||
/// </remarks>
|
||||
/// <param name="contentTypes">Collection of Lazy <see cref="IContentType"/> to save</param>
|
||||
/// <param name="userId">Optional Id of the User saving the ContentTypes</param>
|
||||
void Save(IEnumerable<Lazy<IContentType>> contentTypes, int userId = -1);
|
||||
|
||||
/// <summary>
|
||||
/// Deletes a single <see cref="IContentType"/> object
|
||||
/// </summary>
|
||||
|
||||
@@ -322,6 +322,17 @@ namespace Umbraco.Core
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Splits a Pascal cased string into a phrase seperated by spaces.
|
||||
/// </summary>
|
||||
/// <param name="phrase">String to split</param>
|
||||
/// <returns></returns>
|
||||
public static string SplitPascalCasing(this string phrase)
|
||||
{
|
||||
string result = Regex.Replace(phrase, "([a-z](?=[A-Z])|[A-Z](?=[A-Z][a-z]))", "$1 ");
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts the phrase to specified convention.
|
||||
/// </summary>
|
||||
|
||||
23
src/Umbraco.Tests/CodeFirst/Attributes/AliasAttribute.cs
Normal file
23
src/Umbraco.Tests/CodeFirst/Attributes/AliasAttribute.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
using System;
|
||||
|
||||
namespace Umbraco.Tests.CodeFirst.Attributes
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
|
||||
public class AliasAttribute : Attribute
|
||||
{
|
||||
public AliasAttribute(string @alias)
|
||||
{
|
||||
Alias = alias;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or Sets the Alias of the Property
|
||||
/// </summary>
|
||||
public string Alias { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or Sets an optional name of the Property
|
||||
/// </summary>
|
||||
public string Name { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
using System;
|
||||
|
||||
namespace Umbraco.Tests.CodeFirst.Attributes
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
|
||||
public class ContentTypeAttribute : Attribute
|
||||
{
|
||||
public ContentTypeAttribute(string @alias)
|
||||
{
|
||||
Alias = alias;
|
||||
|
||||
IconUrl = "folder.gif";
|
||||
Thumbnail = "folder.png";
|
||||
Description = "";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Alias of the ContentType
|
||||
/// </summary>
|
||||
public string Alias { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the optional Name of the ContentType
|
||||
/// </summary>
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the optional Description of the ContentType
|
||||
/// </summary>
|
||||
public string Description { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the optional IconUrl of the ContentType
|
||||
/// </summary>
|
||||
public string IconUrl { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the optional Thumbnail of the ContentType
|
||||
/// </summary>
|
||||
public string Thumbnail { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the optional array of Allowed Child ContentTypes of the ContentType
|
||||
/// </summary>
|
||||
public Type[] AllowedChildContentTypes { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the optional array of Allowed Template names of the ContentType
|
||||
/// </summary>
|
||||
public string[] AllowedTemplates { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
using System;
|
||||
|
||||
namespace Umbraco.Tests.CodeFirst.Attributes
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
|
||||
public class DescriptionAttribute : Attribute
|
||||
{
|
||||
public DescriptionAttribute(string description)
|
||||
{
|
||||
Description = description;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Description of the Property
|
||||
/// </summary>
|
||||
public string Description { get; private set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
using System;
|
||||
|
||||
namespace Umbraco.Tests.CodeFirst.Attributes
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
|
||||
public class PropertyTypeAttribute : Attribute
|
||||
{
|
||||
public PropertyTypeAttribute(Type type)
|
||||
{
|
||||
Type = type;
|
||||
PropertyGroup = "Generic Properties";
|
||||
Mandatory = false;
|
||||
ValidationRegExp = string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Type of the DataType
|
||||
/// </summary>
|
||||
public Type Type { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Name of the PropertyGroup that this PropertyType belongs to
|
||||
/// </summary>
|
||||
public string PropertyGroup { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Boolean indicating that a value is required for this PropertyType
|
||||
/// </summary>
|
||||
public bool Mandatory { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Regular Expression for validating PropertyType's values
|
||||
/// </summary>
|
||||
public string ValidationRegExp { get; set; }
|
||||
}
|
||||
}
|
||||
14
src/Umbraco.Tests/CodeFirst/Attributes/RichtextAttribute.cs
Normal file
14
src/Umbraco.Tests/CodeFirst/Attributes/RichtextAttribute.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using System;
|
||||
using umbraco.editorControls.tinyMCE3;
|
||||
|
||||
namespace Umbraco.Tests.CodeFirst.Attributes
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
|
||||
public class RichtextAttribute : PropertyTypeAttribute
|
||||
{
|
||||
public RichtextAttribute()
|
||||
: base(typeof(tinyMCE3dataType))
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
18
src/Umbraco.Tests/CodeFirst/Attributes/SortOrderAttribute.cs
Normal file
18
src/Umbraco.Tests/CodeFirst/Attributes/SortOrderAttribute.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using System;
|
||||
|
||||
namespace Umbraco.Tests.CodeFirst.Attributes
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = false)]
|
||||
public class SortOrderAttribute : Attribute
|
||||
{
|
||||
public SortOrderAttribute(int order)
|
||||
{
|
||||
Order = order;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the sort order of the Property
|
||||
/// </summary>
|
||||
public int Order { get; private set; }
|
||||
}
|
||||
}
|
||||
224
src/Umbraco.Tests/CodeFirst/CodeFirstTests.cs
Normal file
224
src/Umbraco.Tests/CodeFirst/CodeFirstTests.cs
Normal file
@@ -0,0 +1,224 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Configuration;
|
||||
using Umbraco.Core.IO;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.ObjectResolution;
|
||||
using Umbraco.Core.Serialization;
|
||||
using Umbraco.Tests.CodeFirst.Definitions;
|
||||
using Umbraco.Tests.CodeFirst.TestModels;
|
||||
using Umbraco.Tests.TestHelpers;
|
||||
using Umbraco.Tests.TestHelpers.Entities;
|
||||
using umbraco.editorControls.tinyMCE3;
|
||||
using umbraco.interfaces;
|
||||
|
||||
namespace Umbraco.Tests.CodeFirst
|
||||
{
|
||||
[TestFixture]
|
||||
public class CodeFirstTests : BaseDatabaseFactoryTest
|
||||
{
|
||||
[SetUp]
|
||||
public override void Initialize()
|
||||
{
|
||||
UmbracoSettings.SettingsFilePath = IOHelper.MapPath(SystemDirectories.Config + Path.DirectorySeparatorChar, false);
|
||||
|
||||
//this ensures its reset
|
||||
PluginManager.Current = new PluginManager();
|
||||
|
||||
//for testing, we'll specify which assemblies are scanned for the PluginTypeResolver
|
||||
PluginManager.Current.AssembliesToScan = new[]
|
||||
{
|
||||
typeof(IDataType).Assembly,
|
||||
typeof(tinyMCE3dataType).Assembly
|
||||
};
|
||||
|
||||
DataTypesResolver.Current = new DataTypesResolver(
|
||||
PluginManager.Current.ResolveDataTypes());
|
||||
|
||||
base.Initialize();
|
||||
|
||||
var serviceStackSerializer = new ServiceStackJsonSerializer();
|
||||
SerializationService = new SerializationService(serviceStackSerializer);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Can_Resolve_ContentType_From_Decorated_Home_Model()
|
||||
{
|
||||
var modelType = typeof(Home);
|
||||
var contentType = ContentTypeDefinitionFactory.GetContentTypeDefinition(modelType);
|
||||
|
||||
Assert.That(contentType, Is.Not.Null);
|
||||
Assert.That(contentType.Value.PropertyGroups, Is.Not.Null);
|
||||
Assert.That(contentType.Value.PropertyTypes.Any(), Is.True);
|
||||
Assert.That(contentType.Value.PropertyTypes.Count(), Is.EqualTo(2));
|
||||
|
||||
var result = SerializationService.ToStream(contentType.Value);
|
||||
var xml = result.ResultStream.ToJsonString();
|
||||
Console.WriteLine(xml);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Can_Resolve_ContentType_From_Decorated_ContentPage_Model()
|
||||
{
|
||||
var modelType = typeof(ContentPage);
|
||||
var contentType = ContentTypeDefinitionFactory.GetContentTypeDefinition(modelType);
|
||||
|
||||
Assert.That(contentType, Is.Not.Null);
|
||||
Assert.That(contentType.Value.PropertyGroups, Is.Not.Null);
|
||||
Assert.That(contentType.Value.PropertyGroups.Any(), Is.True);
|
||||
Assert.That(contentType.Value.PropertyGroups.Count(), Is.EqualTo(1));
|
||||
Assert.That(contentType.Value.PropertyTypes.Any(), Is.True);
|
||||
Assert.That(contentType.Value.PropertyTypes.Count(), Is.EqualTo(1));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Can_Resolve_ContentType_From_PlainPocoType_Model()
|
||||
{
|
||||
var modelType = typeof(PlainPocoType);
|
||||
var contentType = ContentTypeDefinitionFactory.GetContentTypeDefinition(modelType);
|
||||
|
||||
Assert.That(contentType, Is.Not.Null);
|
||||
Assert.That(contentType.Value.PropertyGroups, Is.Not.Null);
|
||||
Assert.That(contentType.Value.PropertyTypes.Any(), Is.True);
|
||||
Assert.That(contentType.Value.PropertyGroups.Count(), Is.EqualTo(1));
|
||||
Assert.That(contentType.Value.PropertyTypes.Count(), Is.EqualTo(5));
|
||||
|
||||
var result = SerializationService.ToStream(contentType.Value);
|
||||
var xml = result.ResultStream.ToJsonString();
|
||||
Console.WriteLine(xml);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Can_Retrieve_ContentTypes_After_Resolving()
|
||||
{
|
||||
ContentTypeDefinitionFactory.ClearContentTypeCache();
|
||||
|
||||
var modelType = typeof(Home);
|
||||
var contentType = ContentTypeDefinitionFactory.GetContentTypeDefinition(modelType);
|
||||
var mappedContentTypes = ContentTypeDefinitionFactory.RetrieveMappedContentTypes();
|
||||
|
||||
Assert.That(mappedContentTypes, Is.Not.Null);
|
||||
Assert.That(mappedContentTypes.Any(), Is.True);
|
||||
Assert.That(mappedContentTypes.Count(), Is.EqualTo(2));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Can_Resolve_Existing_ContentType_With_Decorated_Model()
|
||||
{
|
||||
var textPage = MockedContentTypes.CreateTextpageContentType();
|
||||
ServiceContext.ContentTypeService.Save(textPage);
|
||||
|
||||
var modelType = typeof(TextPage);
|
||||
var contentType = ContentTypeDefinitionFactory.GetContentTypeDefinition(modelType);
|
||||
|
||||
Assert.That(contentType.Value.Id, Is.EqualTo(textPage.Id));
|
||||
|
||||
ServiceContext.ContentTypeService.Save(contentType.Value);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Can_Save_Models_To_Database()
|
||||
{
|
||||
ContentTypeDefinitionFactory.ClearContentTypeCache();
|
||||
|
||||
var homeModel = typeof(Home);
|
||||
var textPageModel = typeof(TextPage);
|
||||
var homeContentType = ContentTypeDefinitionFactory.GetContentTypeDefinition(homeModel);
|
||||
var textPageContentType = ContentTypeDefinitionFactory.GetContentTypeDefinition(textPageModel);
|
||||
|
||||
var mappedContentTypes = ContentTypeDefinitionFactory.RetrieveMappedContentTypes().ToList();
|
||||
ServiceContext.ContentTypeService.Save(mappedContentTypes);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Can_Resolve_Parent_Child_ContentTypes_And_Save_To_Database()
|
||||
{
|
||||
ContentTypeDefinitionFactory.ClearContentTypeCache();
|
||||
|
||||
var simplemodel = typeof(SimpleContentPage);
|
||||
var model = typeof(AdvancedContentPage);
|
||||
var sContentType = ContentTypeDefinitionFactory.GetContentTypeDefinition(simplemodel);
|
||||
var aContentType = ContentTypeDefinitionFactory.GetContentTypeDefinition(model);
|
||||
|
||||
var mappedContentTypes = ContentTypeDefinitionFactory.RetrieveMappedContentTypes().ToList();
|
||||
ServiceContext.ContentTypeService.Save(mappedContentTypes);
|
||||
|
||||
var type1 = ServiceContext.ContentTypeService.GetContentType(1045);
|
||||
var type2 = ServiceContext.ContentTypeService.GetContentType(1046);
|
||||
|
||||
Assert.That(type1, Is.Not.Null);
|
||||
Assert.That(type2, Is.Not.Null);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Can_Resolve_And_Save_Decorated_Model_To_Database()
|
||||
{
|
||||
ContentTypeDefinitionFactory.ClearContentTypeCache();
|
||||
|
||||
var model = typeof(DecoratedModelPage);
|
||||
var modelContentType = ContentTypeDefinitionFactory.GetContentTypeDefinition(model);
|
||||
|
||||
var mappedContentTypes = ContentTypeDefinitionFactory.RetrieveMappedContentTypes().ToList();
|
||||
ServiceContext.ContentTypeService.Save(mappedContentTypes);
|
||||
|
||||
var type1 = ServiceContext.ContentTypeService.GetContentType(1047);
|
||||
|
||||
Assert.That(type1, Is.Not.Null);
|
||||
Assert.That(type1.PropertyGroups.Count(), Is.EqualTo(2));
|
||||
Assert.That(type1.PropertyTypes.Count(), Is.EqualTo(4));
|
||||
|
||||
}
|
||||
|
||||
private SerializationService SerializationService { get; set; }
|
||||
|
||||
private static int[] GetTopologicalSortOrder(IList<DependencyField> fields)
|
||||
{
|
||||
var g = new TopologicalSorter(fields.Count());
|
||||
var _indexes = new Dictionary<string, int>();
|
||||
|
||||
//add vertices
|
||||
for (int i = 0; i < fields.Count(); i++)
|
||||
{
|
||||
_indexes[fields[i].Alias.ToLower()] = g.AddVertex(i);
|
||||
}
|
||||
|
||||
//add edges
|
||||
for (int i = 0; i < fields.Count; i++)
|
||||
{
|
||||
if (fields[i].DependsOn != null)
|
||||
{
|
||||
for (int j = 0; j < fields[i].DependsOn.Length; j++)
|
||||
{
|
||||
g.AddEdge(i,
|
||||
_indexes[fields[i].DependsOn[j].ToLower()]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int[] result = g.Sort();
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
[TearDown]
|
||||
public override void TearDown()
|
||||
{
|
||||
DatabaseContext.Database.Dispose();
|
||||
|
||||
//reset the app context
|
||||
DataTypesResolver.Reset();
|
||||
ApplicationContext.Current = null;
|
||||
Resolution.IsFrozen = false;
|
||||
|
||||
string path = TestHelper.CurrentAssemblyDirectory;
|
||||
AppDomain.CurrentDomain.SetData("DataDirectory", null);
|
||||
|
||||
ServiceContext = null;
|
||||
SerializationService = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
7
src/Umbraco.Tests/CodeFirst/ContentTypeBase.cs
Normal file
7
src/Umbraco.Tests/CodeFirst/ContentTypeBase.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
namespace Umbraco.Tests.CodeFirst
|
||||
{
|
||||
public abstract class ContentTypeBase
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,383 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Configuration;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Tests.CodeFirst.Attributes;
|
||||
using umbraco.interfaces;
|
||||
|
||||
namespace Umbraco.Tests.CodeFirst.Definitions
|
||||
{
|
||||
public static class ContentTypeDefinitionFactory
|
||||
{
|
||||
private static ConcurrentDictionary<string, DependencyField> _contentTypeCache = new ConcurrentDictionary<string, DependencyField>();
|
||||
|
||||
public static Lazy<IContentType> GetContentTypeDefinition(Type modelType)
|
||||
{
|
||||
//Check for BaseType different from ContentTypeBase
|
||||
bool hasParent = modelType.BaseType != null && modelType.BaseType != typeof(ContentTypeBase) && modelType.BaseType != typeof(object);
|
||||
var parent = new Lazy<IContentType>();
|
||||
if(hasParent)
|
||||
{
|
||||
var isResolved = _contentTypeCache.ContainsKey(modelType.BaseType.FullName);
|
||||
parent = isResolved
|
||||
? _contentTypeCache[modelType.BaseType.FullName].ContentType
|
||||
: GetContentTypeDefinition(modelType.BaseType);
|
||||
}
|
||||
|
||||
var contentTypeAttribute = modelType.FirstAttribute<ContentTypeAttribute>();
|
||||
var contentTypeAlias = contentTypeAttribute == null ? modelType.Name.ToUmbracoAlias() : contentTypeAttribute.Alias;
|
||||
//Check if ContentType already exists by looking it up by Alias.
|
||||
var existing = ServiceFactory.ContentTypeService.GetContentType(contentTypeAlias);
|
||||
|
||||
Lazy<IContentType> contentType = contentTypeAttribute == null
|
||||
? PlainPocoConvention(modelType, existing)
|
||||
: ContentTypeConvention(contentTypeAttribute, modelType, existing);
|
||||
|
||||
var definitions = new List<PropertyDefinition>();
|
||||
int order = 0;
|
||||
var objProperties = modelType.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly).ToList();
|
||||
foreach (var propertyInfo in objProperties)
|
||||
{
|
||||
var definition = new PropertyDefinition();
|
||||
|
||||
var aliasAttribute = propertyInfo.FirstAttribute<AliasAttribute>();
|
||||
definition.Alias = PropertyTypeAliasConvention(aliasAttribute, propertyInfo.Name);
|
||||
definition.Name = PropertyTypeNameConvention(aliasAttribute, propertyInfo.Name);
|
||||
|
||||
var descriptionAttribute = propertyInfo.FirstAttribute<DescriptionAttribute>();
|
||||
definition.Description = descriptionAttribute != null ? descriptionAttribute.Description : string.Empty;
|
||||
|
||||
var sortOrderAttribute = propertyInfo.FirstAttribute<SortOrderAttribute>();
|
||||
definition.Order = sortOrderAttribute != null ? sortOrderAttribute.Order : order;
|
||||
|
||||
var propertyTypeAttribute = propertyInfo.FirstAttribute<PropertyTypeAttribute>();
|
||||
definition.Mandatory = propertyTypeAttribute != null && propertyTypeAttribute.Mandatory;
|
||||
definition.ValidationRegExp = propertyTypeAttribute == null ? string.Empty : propertyTypeAttribute.ValidationRegExp;
|
||||
definition.PropertyGroup = propertyTypeAttribute == null ? "Generic Properties" : propertyTypeAttribute.PropertyGroup;
|
||||
definition.DataTypeDefinition = DataTypeConvention(propertyTypeAttribute, propertyInfo.PropertyType);
|
||||
|
||||
//RichtextAttribute convention
|
||||
var richtextAttribute = propertyInfo.FirstAttribute<RichtextAttribute>();
|
||||
if(richtextAttribute != null)
|
||||
{
|
||||
definition.DataTypeDefinition = DataTypeConvention(richtextAttribute, propertyInfo.PropertyType);
|
||||
}
|
||||
|
||||
definitions.Add(definition);
|
||||
order++;
|
||||
}
|
||||
|
||||
//Loop through definitions for PropertyGroups and create those that not already exists
|
||||
var groupDefinitions = definitions.DistinctBy(d => d.PropertyGroup);
|
||||
foreach (var groupDefinition in groupDefinitions)
|
||||
{
|
||||
var groupExists = contentType.Value.PropertyGroups.Contains(groupDefinition.PropertyGroup);
|
||||
if(groupExists == false)
|
||||
{
|
||||
var propertyGroup = new PropertyGroup {Name = groupDefinition.PropertyGroup};
|
||||
contentType.Value.PropertyGroups.Add(propertyGroup);
|
||||
}
|
||||
}
|
||||
|
||||
//Loop through definitions for PropertyTypes and add them to the correct PropertyGroup
|
||||
foreach (var definition in definitions)
|
||||
{
|
||||
var group = contentType.Value.PropertyGroups.First(x => x.Name == definition.PropertyGroup);
|
||||
//Check if a PropertyType with the same alias already exists, as we don't want to override existing ones
|
||||
if(group.PropertyTypes.Contains(definition.Alias)) continue;
|
||||
|
||||
var propertyType = new PropertyType(definition.DataTypeDefinition)
|
||||
{
|
||||
Mandatory = definition.Mandatory,
|
||||
ValidationRegExp = definition.ValidationRegExp,
|
||||
SortOrder = definition.Order,
|
||||
Alias = definition.Alias,
|
||||
Name = definition.Name
|
||||
};
|
||||
|
||||
group.PropertyTypes.Add(propertyType);
|
||||
}
|
||||
|
||||
//If current ContentType has a Parent the ParentId should be set and the ContentType added to the composition.
|
||||
if(hasParent)
|
||||
{
|
||||
contentType.Value.SetLazyParentId(new Lazy<int>(() => parent.Value.Id));
|
||||
contentType.Value.AddContentType(parent.Value);
|
||||
}
|
||||
//Add the resolved ContentType to the internal cache
|
||||
var field = new DependencyField {ContentType = contentType, Alias = contentType.Value.Alias};
|
||||
var dependencies = new List<string>();
|
||||
if(hasParent)
|
||||
dependencies.Add(parent.Value.Alias);
|
||||
if(contentType.Value.AllowedContentTypes.Any())
|
||||
{
|
||||
dependencies.AddRange(contentType.Value.AllowedContentTypes.Select(allowed => allowed.Alias));
|
||||
}
|
||||
field.DependsOn = dependencies.ToArray();
|
||||
_contentTypeCache.AddOrUpdate(modelType.FullName, field, (x, y) => field);
|
||||
return contentType;
|
||||
}
|
||||
|
||||
private static int[] GetTopologicalSortOrder(IList<DependencyField> fields)
|
||||
{
|
||||
var g = new TopologicalSorter(fields.Count());
|
||||
var _indexes = new Dictionary<string, int>();
|
||||
|
||||
//add vertices
|
||||
for (int i = 0; i < fields.Count(); i++)
|
||||
{
|
||||
_indexes[fields[i].Alias.ToLower()] = g.AddVertex(i);
|
||||
}
|
||||
|
||||
//add edges
|
||||
for (int i = 0; i < fields.Count; i++)
|
||||
{
|
||||
if (fields[i].DependsOn != null)
|
||||
{
|
||||
for (int j = 0; j < fields[i].DependsOn.Length; j++)
|
||||
{
|
||||
g.AddEdge(i,
|
||||
_indexes[fields[i].DependsOn[j].ToLower()]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int[] result = g.Sort();
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
public static IEnumerable<Lazy<IContentType>> RetrieveMappedContentTypes()
|
||||
{
|
||||
var fields = _contentTypeCache.Select(x => x.Value).ToList();
|
||||
int[] sortOrder = GetTopologicalSortOrder(fields);
|
||||
var list = new List<Lazy<IContentType>>();
|
||||
for (int i = 0; i < sortOrder.Length; i++)
|
||||
{
|
||||
var field = fields[sortOrder[i]];
|
||||
list.Add(field.ContentType);
|
||||
Console.WriteLine(field.Alias);
|
||||
if (field.DependsOn != null)
|
||||
foreach (var item in field.DependsOn)
|
||||
{
|
||||
Console.WriteLine(" -{0}", item);
|
||||
}
|
||||
}
|
||||
list.Reverse();
|
||||
return list;
|
||||
}
|
||||
|
||||
public static void ClearContentTypeCache()
|
||||
{
|
||||
_contentTypeCache.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convention to get a DataTypeDefinition from the PropertyTypeAttribute or the type of the property itself
|
||||
/// </summary>
|
||||
/// <param name="attribute"></param>
|
||||
/// <param name="type"></param>
|
||||
/// <returns></returns>
|
||||
private static IDataTypeDefinition DataTypeConvention(PropertyTypeAttribute attribute, Type type)
|
||||
{
|
||||
if(attribute != null)
|
||||
{
|
||||
var instance = Activator.CreateInstance(attribute.Type);
|
||||
var dataType = instance as IDataType;
|
||||
return GetDataTypeByControlId(dataType.Id);
|
||||
}
|
||||
|
||||
return TypeToPredefinedDataTypeConvention(type);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convention to get predefined DataTypeDefinitions based on the Type of the property
|
||||
/// </summary>
|
||||
/// <param name="type"></param>
|
||||
/// <returns></returns>
|
||||
private static IDataTypeDefinition TypeToPredefinedDataTypeConvention(Type type)
|
||||
{
|
||||
if(type == typeof(bool))
|
||||
{
|
||||
return GetDataTypeByControlId(new Guid("38b352c1-e9f8-4fd8-9324-9a2eab06d97a"));// Yes/No DataType
|
||||
}
|
||||
if(type == typeof(int))
|
||||
{
|
||||
return GetDataTypeByControlId(new Guid("1413afcb-d19a-4173-8e9a-68288d2a73b8"));// Number DataType
|
||||
}
|
||||
if(type == typeof(DateTime))
|
||||
{
|
||||
return GetDataTypeByControlId(new Guid("23e93522-3200-44e2-9f29-e61a6fcbb79a"));// Date Picker DataType
|
||||
}
|
||||
|
||||
return GetDataTypeByControlId(new Guid("ec15c1e5-9d90-422a-aa52-4f7622c63bea"));// Standard textfield
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="IDataTypeDefinition"/> from the DataTypeService by its control Id
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <returns></returns>
|
||||
private static IDataTypeDefinition GetDataTypeByControlId(Guid id)
|
||||
{
|
||||
//TODO Create Definition if none exists
|
||||
var definition = ServiceFactory.DataTypeService.GetDataTypeDefinitionByControlId(id);
|
||||
return definition.FirstOrDefault();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convention to get the Alias of the PropertyType from the AliasAttribute or the property itself
|
||||
/// </summary>
|
||||
/// <param name="attribute"></param>
|
||||
/// <param name="propertyName"></param>
|
||||
/// <returns></returns>
|
||||
private static string PropertyTypeAliasConvention(AliasAttribute attribute, string propertyName)
|
||||
{
|
||||
return attribute == null ? propertyName.ToUmbracoAlias() : attribute.Alias;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convention to get the Name of the PropertyType from the AliasAttribute or the property itself
|
||||
/// </summary>
|
||||
/// <param name="attribute"></param>
|
||||
/// <param name="propertyName"></param>
|
||||
/// <returns></returns>
|
||||
private static string PropertyTypeNameConvention(AliasAttribute attribute, string propertyName)
|
||||
{
|
||||
if (attribute == null)
|
||||
return propertyName.SplitPascalCasing();
|
||||
|
||||
return string.IsNullOrEmpty(attribute.Name) ? propertyName.SplitPascalCasing() : attribute.Name;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convention that converts a class decorated with the ContentTypeAttribute to an initial ContentType
|
||||
/// </summary>
|
||||
/// <param name="attribute"><see cref="ContentTypeAttribute"/> to use for mapping a <see cref="IContentType"/></param>
|
||||
/// <param name="modelType">Type of the current class</param>
|
||||
/// <param name="existing"> </param>
|
||||
/// <returns>A Lazy <see cref="IContentType"/></returns>
|
||||
private static Lazy<IContentType> ContentTypeConvention(ContentTypeAttribute attribute, Type modelType, IContentType existing)
|
||||
{
|
||||
var children = attribute.AllowedChildContentTypes == null
|
||||
? new List<ContentTypeSort>()
|
||||
: AllowedChildContentTypesConvention(
|
||||
attribute.AllowedChildContentTypes, modelType);
|
||||
|
||||
var templates = attribute.AllowedTemplates == null
|
||||
? new List<ITemplate>()
|
||||
: AllowedTemplatesConvention(attribute.AllowedTemplates);
|
||||
|
||||
if(existing != null)
|
||||
{
|
||||
if (children.Any())
|
||||
existing.AllowedContentTypes = children;
|
||||
|
||||
if (templates.Any())
|
||||
existing.AllowedTemplates = templates;
|
||||
|
||||
return new Lazy<IContentType>(() => existing);
|
||||
}
|
||||
|
||||
var contentType = new ContentType(-1)
|
||||
{
|
||||
Alias = attribute.Alias,
|
||||
Description = attribute.Description,
|
||||
Icon = attribute.IconUrl,
|
||||
Thumbnail = attribute.Thumbnail,
|
||||
Name = string.IsNullOrEmpty(attribute.Name)
|
||||
? modelType.Name.SplitPascalCasing()
|
||||
: attribute.Name,
|
||||
AllowedContentTypes = children,
|
||||
AllowedTemplates = templates
|
||||
};
|
||||
|
||||
return new Lazy<IContentType>(() => contentType);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convention to resolve referenced templates
|
||||
/// </summary>
|
||||
/// <param name="templateNames"></param>
|
||||
/// <returns></returns>
|
||||
private static IEnumerable<ITemplate> AllowedTemplatesConvention(IEnumerable<string> templateNames)
|
||||
{
|
||||
var templates = new List<ITemplate>();
|
||||
var engine = UmbracoSettings.DefaultRenderingEngine;
|
||||
foreach (var templateName in templateNames)
|
||||
{
|
||||
var @alias = engine == RenderingEngine.Mvc
|
||||
? templateName.Replace(".cshtml", "").Replace(".vbhtml", "")
|
||||
: templateName.Replace(".masterpage", "");
|
||||
var name = engine == RenderingEngine.Mvc
|
||||
? string.Concat(@alias, ".cshtml")
|
||||
: string.Concat(@alias, ".masterpage");
|
||||
|
||||
var template = ServiceFactory.FileService.GetTemplateByAlias(@alias);
|
||||
if(template == null)
|
||||
{
|
||||
template = new Template(string.Empty, name, @alias) { CreatorId = 0, Content = string.Empty};
|
||||
ServiceFactory.FileService.SaveTemplate(template);
|
||||
}
|
||||
templates.Add(template);
|
||||
}
|
||||
return templates;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convention to resolve referenced child content types
|
||||
/// </summary>
|
||||
/// <param name="types"></param>
|
||||
/// <param name="currentType"> </param>
|
||||
/// <returns></returns>
|
||||
private static IEnumerable<ContentTypeSort> AllowedChildContentTypesConvention(IEnumerable<Type> types, Type currentType)
|
||||
{
|
||||
var contentTypeSorts = new List<ContentTypeSort>();
|
||||
int order = 0;
|
||||
foreach (var type in types)
|
||||
{
|
||||
if(type == currentType) continue;//If the referenced type is equal to the current type we skip it to avoid a circular dependency
|
||||
|
||||
var contentTypeSort = new ContentTypeSort();
|
||||
var isResolved = _contentTypeCache.ContainsKey(type.FullName);
|
||||
var lazy = isResolved ? _contentTypeCache[type.FullName].ContentType : GetContentTypeDefinition(type);
|
||||
contentTypeSort.Id = new Lazy<int>(() => lazy.Value.Id);
|
||||
contentTypeSort.Alias = lazy.Value.Alias;
|
||||
contentTypeSort.SortOrder = order;
|
||||
contentTypeSorts.Add(contentTypeSort);
|
||||
order++;
|
||||
}
|
||||
return contentTypeSorts;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convention that converts a simple POCO to an initial ContentType
|
||||
/// </summary>
|
||||
/// <param name="modelType">Type of the object to map to a <see cref="IContentType"/></param>
|
||||
/// <param name="existing"> </param>
|
||||
/// <returns>A Lazy <see cref="IContentType"/></returns>
|
||||
private static Lazy<IContentType> PlainPocoConvention(Type modelType, IContentType existing)
|
||||
{
|
||||
if(existing != null)
|
||||
return new Lazy<IContentType>(() => existing);
|
||||
|
||||
var contentType = new ContentType(-1)
|
||||
{
|
||||
Alias = modelType.Name.ToUmbracoAlias(),
|
||||
Description = string.Empty,
|
||||
Icon = "folder.gif",
|
||||
Thumbnail = "folder.png",
|
||||
Name = modelType.Name.SplitPascalCasing(),
|
||||
AllowedTemplates = new List<ITemplate>(),
|
||||
AllowedContentTypes = new List<ContentTypeSort>()
|
||||
};
|
||||
|
||||
return new Lazy<IContentType>(() => contentType);
|
||||
}
|
||||
}
|
||||
}
|
||||
12
src/Umbraco.Tests/CodeFirst/Definitions/DependencyField.cs
Normal file
12
src/Umbraco.Tests/CodeFirst/Definitions/DependencyField.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
using System;
|
||||
using Umbraco.Core.Models;
|
||||
|
||||
namespace Umbraco.Tests.CodeFirst.Definitions
|
||||
{
|
||||
public class DependencyField
|
||||
{
|
||||
public string Alias { get; set; }
|
||||
public string[] DependsOn { get; set; }
|
||||
public Lazy<IContentType> ContentType { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
using System;
|
||||
using Umbraco.Core.Models;
|
||||
|
||||
namespace Umbraco.Tests.CodeFirst.Definitions
|
||||
{
|
||||
public class PropertyDefinition
|
||||
{
|
||||
public string Alias { get; set; }
|
||||
|
||||
public string Name { get; set; }
|
||||
|
||||
public string Description { get; set; }
|
||||
|
||||
public int Order { get; set; }
|
||||
|
||||
public IDataTypeDefinition DataTypeDefinition { get; set; }
|
||||
|
||||
public string PropertyGroup { get; set; }
|
||||
|
||||
public bool Mandatory { get; set; }
|
||||
|
||||
public string ValidationRegExp { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
using System;
|
||||
using Umbraco.Tests.CodeFirst.Attributes;
|
||||
using umbraco.editorControls.textfield;
|
||||
|
||||
namespace Umbraco.Tests.CodeFirst.TestModels
|
||||
{
|
||||
public class AdvancedContentPage : SimpleContentPage
|
||||
{
|
||||
[PropertyType(typeof(TextFieldDataType), PropertyGroup = "Content")]
|
||||
public string Author { get; set; }
|
||||
|
||||
public DateTime PublishDate { get; set; }
|
||||
}
|
||||
}
|
||||
10
src/Umbraco.Tests/CodeFirst/TestModels/ContentPage.cs
Normal file
10
src/Umbraco.Tests/CodeFirst/TestModels/ContentPage.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
using Umbraco.Tests.CodeFirst.Attributes;
|
||||
|
||||
namespace Umbraco.Tests.CodeFirst.TestModels
|
||||
{
|
||||
public class ContentPage : ContentTypeBase
|
||||
{
|
||||
[Richtext(PropertyGroup = "Content")]
|
||||
public string BodyContent { get; set; }
|
||||
}
|
||||
}
|
||||
21
src/Umbraco.Tests/CodeFirst/TestModels/DecoratedModelPage.cs
Normal file
21
src/Umbraco.Tests/CodeFirst/TestModels/DecoratedModelPage.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
using System;
|
||||
using Umbraco.Tests.CodeFirst.Attributes;
|
||||
using umbraco.editorControls.textfield;
|
||||
|
||||
namespace Umbraco.Tests.CodeFirst.TestModels
|
||||
{
|
||||
[ContentType("modelPage", AllowedChildContentTypes = new[] { typeof(ContentPage) }, AllowedTemplates = new[]{"umbMaster"})]
|
||||
public class DecoratedModelPage
|
||||
{
|
||||
[PropertyType(typeof(TextFieldDataType), PropertyGroup = "Content")]
|
||||
public string Author { get; set; }
|
||||
|
||||
[PropertyType(typeof(TextFieldDataType), PropertyGroup = "Content")]
|
||||
public string Title { get; set; }
|
||||
|
||||
[Richtext(PropertyGroup = "Content")]
|
||||
public string BodyContent { get; set; }
|
||||
|
||||
public DateTime PublishDate { get; set; }
|
||||
}
|
||||
}
|
||||
17
src/Umbraco.Tests/CodeFirst/TestModels/Home.cs
Normal file
17
src/Umbraco.Tests/CodeFirst/TestModels/Home.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using Umbraco.Tests.CodeFirst.Attributes;
|
||||
using umbraco.editorControls.textfield;
|
||||
using umbraco.editorControls.textfieldmultiple;
|
||||
|
||||
namespace Umbraco.Tests.CodeFirst.TestModels
|
||||
{
|
||||
[ContentType("home", AllowedChildContentTypes = new[] { typeof(ContentPage) })]
|
||||
public class Home
|
||||
{
|
||||
[PropertyType(typeof(TextFieldDataType))]
|
||||
public string SiteName { get; set; }
|
||||
|
||||
[Alias("umbSiteDescription", Name = "Site Description")]
|
||||
[PropertyType(typeof(textfieldMultipleDataType))]
|
||||
public string SiteDescription { get; set; }
|
||||
}
|
||||
}
|
||||
17
src/Umbraco.Tests/CodeFirst/TestModels/PlainPocoType.cs
Normal file
17
src/Umbraco.Tests/CodeFirst/TestModels/PlainPocoType.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using System;
|
||||
|
||||
namespace Umbraco.Tests.CodeFirst.TestModels
|
||||
{
|
||||
public class PlainPocoType
|
||||
{
|
||||
public string Title { get; set; }
|
||||
|
||||
public string Author { get; set; }
|
||||
|
||||
public bool IsFinished { get; set; }
|
||||
|
||||
public int Weight { get; set; }
|
||||
|
||||
public DateTime PublishDate { get; set; }
|
||||
}
|
||||
}
|
||||
14
src/Umbraco.Tests/CodeFirst/TestModels/SimpleContentPage.cs
Normal file
14
src/Umbraco.Tests/CodeFirst/TestModels/SimpleContentPage.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using Umbraco.Tests.CodeFirst.Attributes;
|
||||
using umbraco.editorControls.textfield;
|
||||
|
||||
namespace Umbraco.Tests.CodeFirst.TestModels
|
||||
{
|
||||
public class SimpleContentPage
|
||||
{
|
||||
[PropertyType(typeof(TextFieldDataType), PropertyGroup = "Content")]
|
||||
public string Title { get; set; }
|
||||
|
||||
[Richtext(PropertyGroup = "Content")]
|
||||
public string BodyContent { get; set; }
|
||||
}
|
||||
}
|
||||
14
src/Umbraco.Tests/CodeFirst/TestModels/TextPage.cs
Normal file
14
src/Umbraco.Tests/CodeFirst/TestModels/TextPage.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using Umbraco.Tests.CodeFirst.Attributes;
|
||||
using umbraco.editorControls.textfield;
|
||||
|
||||
namespace Umbraco.Tests.CodeFirst.TestModels
|
||||
{
|
||||
public class TextPage
|
||||
{
|
||||
[PropertyType(typeof(TextFieldDataType), PropertyGroup = "Content")]
|
||||
public string Author { get; set; }
|
||||
|
||||
[PropertyType(typeof(TextFieldDataType), PropertyGroup = "Content")]
|
||||
public string Title { get; set; }
|
||||
}
|
||||
}
|
||||
118
src/Umbraco.Tests/CodeFirst/TopologicalSorter.cs
Normal file
118
src/Umbraco.Tests/CodeFirst/TopologicalSorter.cs
Normal file
@@ -0,0 +1,118 @@
|
||||
using System;
|
||||
|
||||
namespace Umbraco.Tests.CodeFirst
|
||||
{
|
||||
public class TopologicalSorter
|
||||
{
|
||||
#region - Private Members -
|
||||
|
||||
private readonly int[] _vertices; // list of vertices
|
||||
private readonly int[,] _matrix; // adjacency matrix
|
||||
private int _numVerts; // current number of vertices
|
||||
private readonly int[] _sortedArray;
|
||||
|
||||
#endregion
|
||||
|
||||
#region - CTors -
|
||||
|
||||
public TopologicalSorter(int size)
|
||||
{
|
||||
_vertices = new int[size];
|
||||
_matrix = new int[size, size];
|
||||
_numVerts = 0;
|
||||
for (int i = 0; i < size; i++)
|
||||
for (int j = 0; j < size; j++)
|
||||
_matrix[i, j] = 0;
|
||||
_sortedArray = new int[size]; // sorted vert labels
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region - Public Methods -
|
||||
|
||||
public int AddVertex(int vertex)
|
||||
{
|
||||
_vertices[_numVerts++] = vertex;
|
||||
return _numVerts - 1;
|
||||
}
|
||||
|
||||
public void AddEdge(int start, int end)
|
||||
{
|
||||
_matrix[start, end] = 1;
|
||||
}
|
||||
|
||||
public int[] Sort() // toplogical sort
|
||||
{
|
||||
while (_numVerts > 0) // while vertices remain,
|
||||
{
|
||||
// get a vertex with no successors, or -1
|
||||
int currentVertex = noSuccessors();
|
||||
if (currentVertex == -1) // must be a cycle
|
||||
throw new Exception("Graph has cycles");
|
||||
|
||||
// insert vertex label in sorted array (start at end)
|
||||
_sortedArray[_numVerts - 1] = _vertices[currentVertex];
|
||||
|
||||
deleteVertex(currentVertex); // delete vertex
|
||||
}
|
||||
|
||||
// vertices all gone; return sortedArray
|
||||
return _sortedArray;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region - Private Helper Methods -
|
||||
|
||||
// returns vert with no successors (or -1 if no such verts)
|
||||
private int noSuccessors()
|
||||
{
|
||||
for (int row = 0; row < _numVerts; row++)
|
||||
{
|
||||
bool isEdge = false; // edge from row to column in adjMat
|
||||
for (int col = 0; col < _numVerts; col++)
|
||||
{
|
||||
if (_matrix[row, col] > 0) // if edge to another,
|
||||
{
|
||||
isEdge = true;
|
||||
break; // this vertex has a successor try another
|
||||
}
|
||||
}
|
||||
if (!isEdge) // if no edges, has no successors
|
||||
return row;
|
||||
}
|
||||
return -1; // no
|
||||
}
|
||||
|
||||
private void deleteVertex(int delVert)
|
||||
{
|
||||
// if not last vertex, delete from vertexList
|
||||
if (delVert != _numVerts - 1)
|
||||
{
|
||||
for (int j = delVert; j < _numVerts - 1; j++)
|
||||
_vertices[j] = _vertices[j + 1];
|
||||
|
||||
for (int row = delVert; row < _numVerts - 1; row++)
|
||||
moveRowUp(row, _numVerts);
|
||||
|
||||
for (int col = delVert; col < _numVerts - 1; col++)
|
||||
moveColLeft(col, _numVerts - 1);
|
||||
}
|
||||
_numVerts--; // one less vertex
|
||||
}
|
||||
|
||||
private void moveRowUp(int row, int length)
|
||||
{
|
||||
for (int col = 0; col < length; col++)
|
||||
_matrix[row, col] = _matrix[row + 1, col];
|
||||
}
|
||||
|
||||
private void moveColLeft(int col, int length)
|
||||
{
|
||||
for (int row = 0; row < length; row++)
|
||||
_matrix[row, col] = _matrix[row, col + 1];
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -89,6 +89,25 @@
|
||||
<ItemGroup>
|
||||
<Compile Include="Auditing\AuditTests.cs" />
|
||||
<Compile Include="BusinessLogic\DictionaryTest.cs" />
|
||||
<Compile Include="CodeFirst\Attributes\AliasAttribute.cs" />
|
||||
<Compile Include="CodeFirst\Attributes\ContentTypeAttribute.cs" />
|
||||
<Compile Include="CodeFirst\Attributes\DescriptionAttribute.cs" />
|
||||
<Compile Include="CodeFirst\Attributes\PropertyTypeAttribute.cs" />
|
||||
<Compile Include="CodeFirst\Attributes\RichtextAttribute.cs" />
|
||||
<Compile Include="CodeFirst\Attributes\SortOrderAttribute.cs" />
|
||||
<Compile Include="CodeFirst\CodeFirstTests.cs" />
|
||||
<Compile Include="CodeFirst\ContentTypeBase.cs" />
|
||||
<Compile Include="CodeFirst\Definitions\ContentTypeDefinitionFactory.cs" />
|
||||
<Compile Include="CodeFirst\Definitions\DependencyField.cs" />
|
||||
<Compile Include="CodeFirst\Definitions\PropertyDefinition.cs" />
|
||||
<Compile Include="CodeFirst\TestModels\AdvancedContentPage.cs" />
|
||||
<Compile Include="CodeFirst\TestModels\ContentPage.cs" />
|
||||
<Compile Include="CodeFirst\TestModels\DecoratedModelPage.cs" />
|
||||
<Compile Include="CodeFirst\TestModels\Home.cs" />
|
||||
<Compile Include="CodeFirst\TestModels\PlainPocoType.cs" />
|
||||
<Compile Include="CodeFirst\TestModels\SimpleContentPage.cs" />
|
||||
<Compile Include="CodeFirst\TestModels\TextPage.cs" />
|
||||
<Compile Include="CodeFirst\TopologicalSorter.cs" />
|
||||
<Compile Include="Configurations\FileSystemProviderTests.cs" />
|
||||
<Compile Include="Configurations\RepositorySettingsTests.cs" />
|
||||
<Compile Include="ContentStores\PublishMediaStoreTests.cs" />
|
||||
|
||||
Reference in New Issue
Block a user