From bf1f78e3aa7447ba6ce3006d98d29f0793cc0a21 Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 25 Jul 2013 16:08:18 +1000 Subject: [PATCH] Finished content and media model mapping using AutoMapper and now the tabs display composite properties correctly. --- .../Mapping/ContentWebModelMappingTests.cs | 143 ++++---- src/Umbraco.Web/Editors/ContentController.cs | 12 +- src/Umbraco.Web/Editors/MediaController.cs | 21 +- .../Models/Mapping/ContentModelMapper.cs | 123 ++++--- .../Mapping/ContentPropertyBasicConverter.cs | 49 +++ .../ContentPropertyDisplayConverter.cs | 44 +++ .../Mapping/ContentPropertyDtoConverter.cs | 35 ++ .../Mapping/ContentPropertyModelMapper.cs | 36 ++ .../Models/Mapping/CreatorResolver.cs | 18 + .../Models/Mapping/MediaModelMapper.cs | 64 ++-- .../Models/Mapping/NewContentMapper.cs | 314 ------------------ .../Models/Mapping/OwnerResolver.cs | 20 ++ .../Mapping/TabsAndPropertiesResolver.cs | 68 ++++ src/Umbraco.Web/Umbraco.Web.csproj | 10 +- .../WebApi/Binders/MediaItemBinder.cs | 9 +- 15 files changed, 481 insertions(+), 485 deletions(-) create mode 100644 src/Umbraco.Web/Models/Mapping/ContentPropertyBasicConverter.cs create mode 100644 src/Umbraco.Web/Models/Mapping/ContentPropertyDisplayConverter.cs create mode 100644 src/Umbraco.Web/Models/Mapping/ContentPropertyDtoConverter.cs create mode 100644 src/Umbraco.Web/Models/Mapping/ContentPropertyModelMapper.cs create mode 100644 src/Umbraco.Web/Models/Mapping/CreatorResolver.cs delete mode 100644 src/Umbraco.Web/Models/Mapping/NewContentMapper.cs create mode 100644 src/Umbraco.Web/Models/Mapping/OwnerResolver.cs create mode 100644 src/Umbraco.Web/Models/Mapping/TabsAndPropertiesResolver.cs diff --git a/src/Umbraco.Tests/Models/Mapping/ContentWebModelMappingTests.cs b/src/Umbraco.Tests/Models/Mapping/ContentWebModelMappingTests.cs index e9e8121860..b951380698 100644 --- a/src/Umbraco.Tests/Models/Mapping/ContentWebModelMappingTests.cs +++ b/src/Umbraco.Tests/Models/Mapping/ContentWebModelMappingTests.cs @@ -91,72 +91,19 @@ namespace Umbraco.Tests.Models.Mapping AssertContentItem(result, content); } - #region Assertions - private void AssertBasics(ContentItemBasic result, TPersisted content) - where T : ContentPropertyBasic - where TPersisted : IContentBase - { - Assert.AreEqual(content.Id, result.Id); - Assert.AreEqual(0, result.Owner.UserId); - Assert.AreEqual("admin", result.Owner.Name); - Assert.AreEqual(content.ParentId, result.ParentId); - Assert.AreEqual(content.UpdateDate, result.UpdateDate); - Assert.AreEqual(content.CreateDate, result.CreateDate); - Assert.AreEqual(content.Name, result.Name); - Assert.AreEqual(content.Properties.Count(), result.Properties.Count()); - } - - private void AssertBasicProperty(ContentItemBasic result, Property p) - where T : ContentPropertyBasic - where TPersisted : IContentBase - { - var pDto = result.Properties.SingleOrDefault(x => x.Alias == p.Alias); - Assert.IsNotNull(pDto); - Assert.AreEqual(p.Alias, pDto.Alias); - Assert.AreEqual(p.Id, pDto.Id); - Assert.AreEqual(p.Value, pDto.Value); - } - - private void AssertProperty(ContentItemBasic result, Property p) - where TPersisted : IContentBase - { - AssertBasicProperty(result, p); - - var pDto = result.Properties.SingleOrDefault(x => x.Alias == p.Alias); - Assert.IsNotNull(pDto); - Assert.AreEqual(p.PropertyType.Mandatory, pDto.IsRequired); - Assert.AreEqual(p.PropertyType.ValidationRegExp, pDto.ValidationRegExp); - Assert.AreEqual(p.PropertyType.Description, pDto.Description); - Assert.AreEqual(p.PropertyType.Name, pDto.Label); - Assert.AreEqual(ApplicationContext.Services.DataTypeService.GetDataTypeDefinitionById(p.PropertyType.DataTypeDefinitionId), pDto.DataType); - Assert.AreEqual(PropertyEditorResolver.Current.GetById(p.PropertyType.DataTypeId), pDto.PropertyEditor); - } - - private void AssertContentItem(ContentItemBasic result, T content) - where T : IContentBase - { - AssertBasics(result, content); - - foreach (var p in content.Properties) - { - AssertProperty(result, p); - } - } - #endregion - [Test] public void To_Display_Model() { var contentType = MockedContentTypes.CreateSimpleContentType(); var content = MockedContent.CreateSimpleContent(contentType); - var mapper = new ContentModelMapper(ApplicationContext, new UserModelMapper()); - var result = Mapper.Map(content); - Assert.AreEqual(content.Name, result.Name); - Assert.AreEqual(content.Id, result.Id); - Assert.AreEqual(content.Properties.Count(), result.Properties.Count()); + AssertBasics(result, content); + foreach (var p in content.Properties) + { + AssertDisplayProperty(result, p, ApplicationContext); + } Assert.AreEqual(content.PropertyGroups.Count(), result.Tabs.Count() - 1); Assert.IsTrue(result.Tabs.Any(x => x.Label == "Generic properties")); Assert.IsTrue(result.Tabs.First().IsActive); @@ -189,16 +136,86 @@ namespace Umbraco.Tests.Models.Mapping //ensure that nothing is marked as dirty content.ResetDirtyProperties(false); - var mapper = new ContentModelMapper(ApplicationContext, new UserModelMapper()); - var result = Mapper.Map(content); - Assert.AreEqual(content.Name, result.Name); - Assert.AreEqual(content.Id, result.Id); - Assert.AreEqual(content.Properties.Count(), result.Properties.Count()); + AssertBasics(result, content); + foreach (var p in content.Properties) + { + AssertDisplayProperty(result, p, ApplicationContext); + } Assert.AreEqual(content.PropertyGroups.Count(), result.Tabs.Count() - 1); Assert.IsTrue(result.Tabs.Any(x => x.Label == "Generic properties")); Assert.AreEqual(2, result.Tabs.Where(x => x.Label == "Generic properties").SelectMany(x => x.Properties).Count()); } + + #region Assertions + + private void AssertDisplayProperty(ContentItemBasic result, Property p, ApplicationContext applicationContext) + where T : ContentPropertyDisplay + where TPersisted : IContentBase + { + AssertBasicProperty(result, p); + + var pDto = result.Properties.SingleOrDefault(x => x.Alias == p.Alias); + Assert.IsNotNull(pDto); + pDto.Alias = p.Alias; + pDto.Description = p.PropertyType.Description; + pDto.Label = p.PropertyType.Name; + pDto.Config = applicationContext.Services.DataTypeService.GetPreValuesByDataTypeId(p.PropertyType.DataTypeDefinitionId); + + } + + private void AssertBasics(ContentItemBasic result, TPersisted content) + where T : ContentPropertyBasic + where TPersisted : IContentBase + { + Assert.AreEqual(content.Id, result.Id); + Assert.AreEqual(0, result.Owner.UserId); + Assert.AreEqual("admin", result.Owner.Name); + Assert.AreEqual(content.ParentId, result.ParentId); + Assert.AreEqual(content.UpdateDate, result.UpdateDate); + Assert.AreEqual(content.CreateDate, result.CreateDate); + Assert.AreEqual(content.Name, result.Name); + Assert.AreEqual(content.Properties.Count(), result.Properties.Count()); + } + + private void AssertBasicProperty(ContentItemBasic result, Property p) + where T : ContentPropertyBasic + where TPersisted : IContentBase + { + var pDto = result.Properties.SingleOrDefault(x => x.Alias == p.Alias); + Assert.IsNotNull(pDto); + Assert.AreEqual(p.Alias, pDto.Alias); + Assert.AreEqual(p.Id, pDto.Id); + + Assert.IsTrue(p.Value == null ? pDto.Value == string.Empty : pDto.Value == p.Value); + } + + private void AssertProperty(ContentItemBasic result, Property p) + where TPersisted : IContentBase + { + AssertBasicProperty(result, p); + + var pDto = result.Properties.SingleOrDefault(x => x.Alias == p.Alias); + Assert.IsNotNull(pDto); + Assert.AreEqual(p.PropertyType.Mandatory, pDto.IsRequired); + Assert.AreEqual(p.PropertyType.ValidationRegExp, pDto.ValidationRegExp); + Assert.AreEqual(p.PropertyType.Description, pDto.Description); + Assert.AreEqual(p.PropertyType.Name, pDto.Label); + Assert.AreEqual(ApplicationContext.Services.DataTypeService.GetDataTypeDefinitionById(p.PropertyType.DataTypeDefinitionId), pDto.DataType); + Assert.AreEqual(PropertyEditorResolver.Current.GetById(p.PropertyType.DataTypeId), pDto.PropertyEditor); + } + + private void AssertContentItem(ContentItemBasic result, T content) + where T : IContentBase + { + AssertBasics(result, content); + + foreach (var p in content.Properties) + { + AssertProperty(result, p); + } + } + #endregion } } diff --git a/src/Umbraco.Web/Editors/ContentController.cs b/src/Umbraco.Web/Editors/ContentController.cs index 900fab5090..735f5b47d4 100644 --- a/src/Umbraco.Web/Editors/ContentController.cs +++ b/src/Umbraco.Web/Editors/ContentController.cs @@ -27,14 +27,12 @@ namespace Umbraco.Web.Editors /// [PluginController("UmbracoApi")] public class ContentController : ContentControllerBase - { - private readonly ContentModelMapper _contentModelMapper; - + { /// /// Constructor /// public ContentController() - : this(UmbracoContext.Current, new ContentModelMapper(UmbracoContext.Current.Application, new UserModelMapper())) + : this(UmbracoContext.Current) { } @@ -42,11 +40,9 @@ namespace Umbraco.Web.Editors /// Constructor /// /// - /// - internal ContentController(UmbracoContext umbracoContext, ContentModelMapper contentModelMapper) + internal ContentController(UmbracoContext umbracoContext) : base(umbracoContext) - { - _contentModelMapper = contentModelMapper; + { } public IEnumerable GetByIds([FromUri]int[] ids) diff --git a/src/Umbraco.Web/Editors/MediaController.cs b/src/Umbraco.Web/Editors/MediaController.cs index 7ea0df7f7e..430867451a 100644 --- a/src/Umbraco.Web/Editors/MediaController.cs +++ b/src/Umbraco.Web/Editors/MediaController.cs @@ -4,6 +4,7 @@ using System.Net; using System.Net.Http; using System.Web.Http; using System.Web.Http.ModelBinding; +using AutoMapper; using Umbraco.Core; using Umbraco.Core.Logging; using Umbraco.Core.Models; @@ -34,13 +35,11 @@ namespace Umbraco.Web.Editors [PluginController("UmbracoApi")] public class MediaController : ContentControllerBase { - private readonly MediaModelMapper _mediaModelMapper; - /// /// Constructor /// public MediaController() - : this(UmbracoContext.Current, new MediaModelMapper(UmbracoContext.Current.Application, new UserModelMapper())) + : this(UmbracoContext.Current) { } @@ -48,11 +47,9 @@ namespace Umbraco.Web.Editors /// Constructor /// /// - /// - internal MediaController(UmbracoContext umbracoContext, MediaModelMapper mediaModelMapper) + internal MediaController(UmbracoContext umbracoContext) : base(umbracoContext) { - _mediaModelMapper = mediaModelMapper; } /// @@ -70,7 +67,7 @@ namespace Umbraco.Web.Editors } var emptyContent = new Core.Models.Media("Empty", parentId, contentType); - return _mediaModelMapper.ToMediaItemDisplay(emptyContent); + return Mapper.Map(emptyContent); } /// @@ -85,7 +82,7 @@ namespace Umbraco.Web.Editors { HandleContentNotFound(id); } - return _mediaModelMapper.ToMediaItemDisplay(foundContent); + return Mapper.Map(foundContent); } /// @@ -94,7 +91,7 @@ namespace Umbraco.Web.Editors public IEnumerable> GetRootMedia() { return Services.MediaService.GetRootMedia() - .Select(x => _mediaModelMapper.ToMediaItemSimple(x)); + .Select(Mapper.Map>); } /// @@ -103,7 +100,7 @@ namespace Umbraco.Web.Editors public IEnumerable> GetChildren(int parentId) { return Services.MediaService.GetChildren(parentId) - .Select(x => _mediaModelMapper.ToMediaItemSimple(x)); + .Select(Mapper.Map>); } /// @@ -138,7 +135,7 @@ namespace Umbraco.Web.Editors { //ok, so the absolute mandatory data is invalid and it's new, we cannot actually continue! // add the modelstate to the outgoing object and throw a 403 - var forDisplay = _mediaModelMapper.ToMediaItemDisplay(contentItem.PersistedContent); + var forDisplay = Mapper.Map(contentItem.PersistedContent); forDisplay.Errors = ModelState.ToErrorDictionary(); throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.Forbidden, forDisplay)); } @@ -148,7 +145,7 @@ namespace Umbraco.Web.Editors Services.MediaService.Save(contentItem.PersistedContent); //return the updated model - var display = _mediaModelMapper.ToMediaItemDisplay(contentItem.PersistedContent); + var display = Mapper.Map(contentItem.PersistedContent); //lasty, if it is not valid, add the modelstate to the outgoing object and throw a 403 HandleInvalidModelState(display); diff --git a/src/Umbraco.Web/Models/Mapping/ContentModelMapper.cs b/src/Umbraco.Web/Models/Mapping/ContentModelMapper.cs index efd7dd2c22..d9fab62343 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentModelMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentModelMapper.cs @@ -1,64 +1,83 @@ -using System.Text; -using System.Threading.Tasks; +using System; +using System.Linq.Expressions; +using AutoMapper; using Umbraco.Core; using Umbraco.Core.Models; -using Umbraco.Core.PropertyEditors; +using Umbraco.Core.Models.Mapping; using Umbraco.Web.Models.ContentEditing; namespace Umbraco.Web.Models.Mapping { - internal class ContentModelMapper : BaseContentModelMapper + /// + /// Declares how model mappings for content + /// + internal class ContentModelMapper : MapperConfiguration { - - public ContentModelMapper(ApplicationContext applicationContext, UserModelMapper userMapper) - : base(applicationContext, userMapper) + public override void ConfigureMappings(IConfiguration config, ApplicationContext applicationContext) { + //FROM IContent TO ContentItemDisplay + config.CreateMap() + .ForMember( + dto => dto.Owner, + expression => expression.ResolveUsing>()) + .ForMember( + dto => dto.Updator, + expression => expression.ResolveUsing()) + .ForMember( + dto => dto.Icon, + expression => expression.MapFrom(content => content.ContentType.Icon)) + .ForMember( + dto => dto.ContentTypeAlias, + expression => expression.MapFrom(content => content.ContentType.Alias)) + .ForMember( + dto => dto.PublishDate, + expression => expression.MapFrom(content => GetPublishedDate(content, applicationContext))) + .ForMember(display => display.Properties, expression => expression.Ignore()) + .ForMember(display => display.Tabs, expression => expression.ResolveUsing()); + + //FROM IContent TO ContentItemBasic + config.CreateMap>() + .ForMember( + dto => dto.Owner, + expression => expression.ResolveUsing>()) + .ForMember( + dto => dto.Updator, + expression => expression.ResolveUsing()) + .ForMember( + dto => dto.Icon, + expression => expression.MapFrom(content => content.ContentType.Icon)) + .ForMember( + dto => dto.ContentTypeAlias, + expression => expression.MapFrom(content => content.ContentType.Alias)); + + //FROM IContent TO ContentItemDto + config.CreateMap>() + .ForMember( + dto => dto.Owner, + expression => expression.ResolveUsing>()); + + } + /// + /// Gets the published date value for the IContent object + /// + /// + /// + /// + private static DateTime? GetPublishedDate(IContent content, ApplicationContext applicationContext) + { + if (content.Published) + { + return content.UpdateDate; + } + if (content.HasPublishedVersion()) + { + var published = applicationContext.Services.ContentService.GetPublishedVersion(content.Id); + return published.UpdateDate; + } + return null; + } - //public ContentItemBasic ToContentItemSimple(IContent content) - //{ - // var result = base.ToContentItemSimpleBase(content); - // result.ContentTypeAlias = content.ContentType.Alias; - // result.Icon = content.ContentType.Icon; - // result.Updator = UserMapper.ToUserBasic(content.GetWriterProfile()); - // return result; - //} - - //public ContentItemDisplay ToContentItemDisplay(IContent content) - //{ - // //create the list of tabs for properties assigned to tabs. - // var tabs = GetTabs(content); - - // var result = CreateContent(content, (display, originalContent) => - // { - // //fill in the rest - // display.Updator = UserMapper.ToUserBasic(content.GetWriterProfile()); - // display.ContentTypeAlias = content.ContentType.Alias; - // display.Icon = content.ContentType.Icon; - - // //set display props after the normal properties are alraedy mapped - // display.Name = originalContent.Name; - // display.Tabs = tabs; - // //look up the published version of this item if it is not published - // if (content.Published) - // { - // display.PublishDate = content.UpdateDate; - // } - // else if (content.HasPublishedVersion()) - // { - // var published = ApplicationContext.Services.ContentService.GetPublishedVersion(content.Id); - // display.PublishDate = published.UpdateDate; - // } - // else - // { - // display.PublishDate = null; - // } - - // }, null, false); - - // return result; - //} - } -} +} \ No newline at end of file diff --git a/src/Umbraco.Web/Models/Mapping/ContentPropertyBasicConverter.cs b/src/Umbraco.Web/Models/Mapping/ContentPropertyBasicConverter.cs new file mode 100644 index 0000000000..145cd4bd30 --- /dev/null +++ b/src/Umbraco.Web/Models/Mapping/ContentPropertyBasicConverter.cs @@ -0,0 +1,49 @@ +using System; +using AutoMapper; +using Umbraco.Core; +using Umbraco.Core.Models; +using Umbraco.Core.PropertyEditors; +using Umbraco.Web.Models.ContentEditing; + +namespace Umbraco.Web.Models.Mapping +{ + /// + /// Creates a base generic ContentPropertyBasic from a Property + /// + /// + internal class ContentPropertyBasicConverter : TypeConverter + where T : ContentPropertyBasic, new() + { + protected override T ConvertCore(Property property) + { + var editor = PropertyEditorResolver.Current.GetById(property.PropertyType.DataTypeId); + if (editor == null) + { + //TODO: Remove this check as we shouldn't support this at all! + var legacyEditor = DataTypesResolver.Current.GetById(property.PropertyType.DataTypeId); + if (legacyEditor == null) + { + throw new NullReferenceException("The property editor with id " + property.PropertyType.DataTypeId + " does not exist"); + } + + var legacyResult = new T + { + Id = property.Id, + Value = property.Value == null ? "" : property.Value.ToString(), + Alias = property.Alias + }; + return legacyResult; + } + var result = new T + { + Id = property.Id, + Value = editor.ValueEditor.SerializeValue(property.Value), + Alias = property.Alias + }; + + result.PropertyEditor = editor; + + return result; + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Models/Mapping/ContentPropertyDisplayConverter.cs b/src/Umbraco.Web/Models/Mapping/ContentPropertyDisplayConverter.cs new file mode 100644 index 0000000000..2371bdc4f6 --- /dev/null +++ b/src/Umbraco.Web/Models/Mapping/ContentPropertyDisplayConverter.cs @@ -0,0 +1,44 @@ +using Umbraco.Core; +using Umbraco.Core.Configuration; +using Umbraco.Core.Models; +using Umbraco.Web.Models.ContentEditing; + +namespace Umbraco.Web.Models.Mapping +{ + /// + /// Creates a ContentPropertyDto from a Property + /// + internal class ContentPropertyDisplayConverter : ContentPropertyBasicConverter + { + private readonly ApplicationContext _applicationContext; + + public ContentPropertyDisplayConverter(ApplicationContext applicationContext) + { + _applicationContext = applicationContext; + } + + protected override ContentPropertyDisplay ConvertCore(Property originalProp) + { + var display = base.ConvertCore(originalProp); + + //set the display properties after mapping + display.Alias = originalProp.Alias; + display.Description = originalProp.PropertyType.Description; + display.Label = originalProp.PropertyType.Name; + display.Config = _applicationContext.Services.DataTypeService.GetPreValuesByDataTypeId(originalProp.PropertyType.DataTypeDefinitionId); + if (display.PropertyEditor == null) + { + //if there is no property editor it means that it is a legacy data type + // we cannot support editing with that so we'll just render the readonly value view. + display.View = GlobalSettings.Path.EnsureEndsWith('/') + + "views/propertyeditors/umbraco/readonlyvalue/readonlyvalue.html"; + } + else + { + display.View = display.PropertyEditor.ValueEditor.View; + } + + return display; + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Models/Mapping/ContentPropertyDtoConverter.cs b/src/Umbraco.Web/Models/Mapping/ContentPropertyDtoConverter.cs new file mode 100644 index 0000000000..c89c0e7a18 --- /dev/null +++ b/src/Umbraco.Web/Models/Mapping/ContentPropertyDtoConverter.cs @@ -0,0 +1,35 @@ +using Umbraco.Core; +using Umbraco.Core.Models; +using Umbraco.Core.PropertyEditors; +using Umbraco.Web.Models.ContentEditing; + +namespace Umbraco.Web.Models.Mapping +{ + /// + /// Creates a ContentPropertyDto from a Property + /// + internal class ContentPropertyDtoConverter : ContentPropertyBasicConverter + { + private readonly ApplicationContext _applicationContext; + + public ContentPropertyDtoConverter(ApplicationContext applicationContext) + { + _applicationContext = applicationContext; + } + + protected override ContentPropertyDto ConvertCore(Property originalProperty) + { + var propertyDto = base.ConvertCore(originalProperty); + + propertyDto.IsRequired = originalProperty.PropertyType.Mandatory; + propertyDto.ValidationRegExp = originalProperty.PropertyType.ValidationRegExp; + propertyDto.Alias = originalProperty.Alias; + propertyDto.Description = originalProperty.PropertyType.Description; + propertyDto.Label = originalProperty.PropertyType.Name; + propertyDto.DataType = _applicationContext.Services.DataTypeService.GetDataTypeDefinitionById(originalProperty.PropertyType.DataTypeDefinitionId); + propertyDto.PropertyEditor = PropertyEditorResolver.Current.GetById(originalProperty.PropertyType.DataTypeId); + + return propertyDto; + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Models/Mapping/ContentPropertyModelMapper.cs b/src/Umbraco.Web/Models/Mapping/ContentPropertyModelMapper.cs new file mode 100644 index 0000000000..7138b3b703 --- /dev/null +++ b/src/Umbraco.Web/Models/Mapping/ContentPropertyModelMapper.cs @@ -0,0 +1,36 @@ +using AutoMapper; +using Umbraco.Core; +using Umbraco.Core.Models; +using Umbraco.Core.Models.Mapping; +using Umbraco.Web.Models.ContentEditing; + +namespace Umbraco.Web.Models.Mapping +{ + /// + /// A mapper which declares how to map content properties. These mappings are shared among media (and probably members) which is + /// why they are in their own mapper + /// + internal class ContentPropertyModelMapper : MapperConfiguration + { + public override void ConfigureMappings(IConfiguration config, ApplicationContext applicationContext) + { + //FROM Property TO ContentPropertyBasic + config.CreateMap>() + .ForMember(tab => tab.Label, expression => expression.MapFrom(@group => @group.Name)) + .ForMember(tab => tab.IsActive, expression => expression.UseValue(true)) + .ForMember(tab => tab.Properties, expression => expression.Ignore()); + + //FROM Property TO ContentPropertyBasic + config.CreateMap() + .ConvertUsing>(); + + //FROM Property TO ContentPropertyDto + config.CreateMap() + .ConvertUsing(new ContentPropertyDtoConverter(applicationContext)); + + //FROM Property TO ContentPropertyDisplay + config.CreateMap() + .ConvertUsing(new ContentPropertyDisplayConverter(applicationContext)); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Models/Mapping/CreatorResolver.cs b/src/Umbraco.Web/Models/Mapping/CreatorResolver.cs new file mode 100644 index 0000000000..0dc182ec79 --- /dev/null +++ b/src/Umbraco.Web/Models/Mapping/CreatorResolver.cs @@ -0,0 +1,18 @@ +using AutoMapper; +using Umbraco.Core.Models; +using Umbraco.Core.Models.Membership; +using Umbraco.Web.Models.ContentEditing; + +namespace Umbraco.Web.Models.Mapping +{ + /// + /// Maps the Creator for content + /// + internal class CreatorResolver : ValueResolver + { + protected override UserBasic ResolveCore(IContent source) + { + return Mapper.Map(source.GetWriterProfile()); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Models/Mapping/MediaModelMapper.cs b/src/Umbraco.Web/Models/Mapping/MediaModelMapper.cs index 55926afa6d..f610c08926 100644 --- a/src/Umbraco.Web/Models/Mapping/MediaModelMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/MediaModelMapper.cs @@ -3,45 +3,53 @@ using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; +using AutoMapper; using Umbraco.Core; using Umbraco.Core.Models; +using Umbraco.Core.Models.Mapping; using Umbraco.Core.PropertyEditors; using Umbraco.Web.Models.ContentEditing; namespace Umbraco.Web.Models.Mapping { - internal class MediaModelMapper : BaseContentModelMapper + /// + /// Declares model mappings for media. + /// + internal class NewMediaModelMapper : MapperConfiguration { - public MediaModelMapper(ApplicationContext applicationContext, UserModelMapper userMapper) - : base(applicationContext, userMapper) + public override void ConfigureMappings(IConfiguration config, ApplicationContext applicationContext) { - } + //FROM IMedia TO MediaItemDisplay + config.CreateMap() + .ForMember( + dto => dto.Owner, + expression => expression.ResolveUsing>()) + .ForMember( + dto => dto.Icon, + expression => expression.MapFrom(content => content.ContentType.Icon)) + .ForMember( + dto => dto.ContentTypeAlias, + expression => expression.MapFrom(content => content.ContentType.Alias)) + .ForMember(display => display.Properties, expression => expression.Ignore()) + .ForMember(display => display.Tabs, expression => expression.ResolveUsing()); - public ContentItemBasic ToMediaItemSimple(IMedia content) - { - var result = base.ToContentItemSimpleBase(content); - result.ContentTypeAlias = content.ContentType.Alias; - result.Icon = content.ContentType.Icon; - return result; - } + //FROM IMedia TO ContentItemBasic + config.CreateMap>() + .ForMember( + dto => dto.Owner, + expression => expression.ResolveUsing>()) + .ForMember( + dto => dto.Icon, + expression => expression.MapFrom(content => content.ContentType.Icon)) + .ForMember( + dto => dto.ContentTypeAlias, + expression => expression.MapFrom(content => content.ContentType.Alias)); - public MediaItemDisplay ToMediaItemDisplay(IMedia media) - { - //create the list of tabs for properties assigned to tabs. - var tabs = GetTabs(media); - - var result = CreateContent(media, (display, originalContent) => - { - //fill in the rest - display.ContentTypeAlias = media.ContentType.Alias; - display.Icon = media.ContentType.Icon; - - //set display props after the normal properties are alraedy mapped - display.Name = originalContent.Name; - display.Tabs = tabs; - }, null, false); - - return result; + //FROM IMedia TO ContentItemDto + config.CreateMap>() + .ForMember( + dto => dto.Owner, + expression => expression.ResolveUsing>()); } } diff --git a/src/Umbraco.Web/Models/Mapping/NewContentMapper.cs b/src/Umbraco.Web/Models/Mapping/NewContentMapper.cs deleted file mode 100644 index 443da7b913..0000000000 --- a/src/Umbraco.Web/Models/Mapping/NewContentMapper.cs +++ /dev/null @@ -1,314 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq.Expressions; -using AutoMapper; -using Umbraco.Core; -using Umbraco.Core.Configuration; -using Umbraco.Core.Models; -using Umbraco.Core.Models.Mapping; -using Umbraco.Core.Models.Membership; -using Umbraco.Core.PropertyEditors; -using Umbraco.Web.Models.ContentEditing; -using System.Linq; - -namespace Umbraco.Web.Models.Mapping -{ - internal class NewContentMapper : MapperConfiguration - { - public override void ConfigureMappings(IConfiguration config, ApplicationContext applicationContext) - { - //FROM Property TO ContentPropertyBasic - config.CreateMap>() - .ForMember(tab => tab.Label, expression => expression.MapFrom(@group => @group.Name)) - .ForMember(tab => tab.IsActive, expression => expression.UseValue(true)) - .ForMember(tab => tab.Properties, expression => expression.Ignore()); - - //FROM Property TO ContentPropertyBasic - config.CreateMap() - .ConvertUsing>(); - - //FROM Property TO ContentPropertyDto - config.CreateMap() - .ConvertUsing(new ContentPropertyDtoConverter(applicationContext)); - - //FROM Property TO ContentPropertyDisplay - config.CreateMap() - .ConvertUsing(new ContentPropertyDisplayConverter(applicationContext)); - - //FROM IContent TO ContentItemDisplay - config.CreateMap() - .ForMember( - dto => dto.Owner, - expression => expression.ResolveUsing>()) - .ForMember( - dto => dto.Updator, - expression => expression.ResolveUsing()) - .ForMember( - dto => dto.Icon, - expression => expression.MapFrom(content => content.ContentType.Icon)) - .ForMember( - dto => dto.ContentTypeAlias, - expression => expression.MapFrom(content => content.ContentType.Alias)) - .ForMember( - dto => dto.PublishDate, - expression => expression.MapFrom(content => GetPublishedDate(content, applicationContext))) - .ForMember(display => display.Properties, expression => expression.Ignore()) - .ForMember(display => display.Tabs, expression => expression.Ignore()) - .AfterMap((content, display) => MapTabsAndProperties(content, display)); - - //FROM IContent TO ContentItemBasic - config.CreateMap>() - .ForMember( - dto => dto.Owner, - expression => expression.ResolveUsing>()) - .ForMember( - dto => dto.Updator, - expression => expression.ResolveUsing()) - .ForMember( - dto => dto.Icon, - expression => expression.MapFrom(content => content.ContentType.Icon)) - .ForMember( - dto => dto.ContentTypeAlias, - expression => expression.MapFrom(content => content.ContentType.Alias)); - - //FROM IMedia TO ContentItemBasic - config.CreateMap>() - .ForMember( - dto => dto.Owner, - expression => expression.ResolveUsing>()) - .ForMember( - dto => dto.Icon, - expression => expression.MapFrom(content => content.ContentType.Icon)) - .ForMember( - dto => dto.ContentTypeAlias, - expression => expression.MapFrom(content => content.ContentType.Alias)); - - //FROM IContent TO ContentItemDto - config.CreateMap>() - .ForMember( - dto => dto.Owner, - expression => expression.ResolveUsing>()); - - //FROM IMedia TO ContentItemDto - config.CreateMap>() - .ForMember( - dto => dto.Owner, - expression => expression.ResolveUsing>()); - } - - /// - /// Gets the published date value for the IContent object - /// - /// - /// - /// - private static DateTime? GetPublishedDate(IContent content, ApplicationContext applicationContext) - { - if (content.Published) - { - return content.UpdateDate; - } - if (content.HasPublishedVersion()) - { - var published = applicationContext.Services.ContentService.GetPublishedVersion(content.Id); - return published.UpdateDate; - } - return null; - } - - private static void MapTabsAndProperties(IContentBase content, TabbedContentItem display) - { - var aggregateTabs = new List>(); - - //now we need to aggregate the tabs and properties since we might have duplicate tabs (based on aliases) because - // of how content composition works. - foreach (var propertyGroups in content.PropertyGroups.GroupBy(x => x.Name)) - { - var aggregateProperties = new List(); - - //there will always be one group with a null parent id (the top-most) - //then we'll iterate over all of the groups and ensure the properties are - //added in order so that when they render they are rendered with highest leve - //parent properties first. - int? currentParentId = null; - for (var i = 0; i < propertyGroups.Count(); i++) - { - var current = propertyGroups.Single(x => x.ParentId == currentParentId); - aggregateProperties.AddRange( - Mapper.Map, IEnumerable>( - content.GetPropertiesForGroup(current))); - currentParentId = current.Id; - } - - //then we'll just use the root group's data to make the composite tab - var rootGroup = propertyGroups.Single(x => x.ParentId == null); - aggregateTabs.Add(new Tab - { - Id = rootGroup.Id, - Alias = rootGroup.Name, - Label = rootGroup.Name, - Properties = aggregateProperties, - IsActive = false - }); - } - - //now add the generic properties tab for any properties that don't belong to a tab - var orphanProperties = content.GetNonGroupedProperties(); - - //now add the generic properties tab - aggregateTabs.Add(new Tab - { - Id = 0, - Label = "Generic properties", - Alias = "Generic properties", - Properties = Mapper.Map, IEnumerable>(orphanProperties) - }); - - //set the first tab to active - aggregateTabs.First().IsActive = true; - - display.Tabs = aggregateTabs; - } - - } - - - - internal class ContentDisplayConverter : TypeConverter - { - protected override ContentItemDisplay ConvertCore(IContent source) - { - throw new NotImplementedException(); - } - } - - /// - /// Maps the Creator for content - /// - internal class CreatorResolver : ValueResolver - { - protected override UserBasic ResolveCore(IContent source) - { - return Mapper.Map(source.GetWriterProfile()); - } - } - - /// - /// Maps the Owner for IContentBase - /// - /// - internal class OwnerResolver : ValueResolver - where TPersisted : IContentBase - { - protected override UserBasic ResolveCore(TPersisted source) - { - return Mapper.Map(source.GetCreatorProfile()); - } - } - - /// - /// Creates a ContentPropertyDto from a Property - /// - internal class ContentPropertyDisplayConverter : ContentPropertyBasicConverter - { - private readonly ApplicationContext _applicationContext; - - public ContentPropertyDisplayConverter(ApplicationContext applicationContext) - { - _applicationContext = applicationContext; - } - - protected override ContentPropertyDisplay ConvertCore(Property originalProp) - { - var display = base.ConvertCore(originalProp); - - //set the display properties after mapping - display.Alias = originalProp.Alias; - display.Description = originalProp.PropertyType.Description; - display.Label = originalProp.PropertyType.Name; - display.Config = _applicationContext.Services.DataTypeService.GetPreValuesByDataTypeId(originalProp.PropertyType.DataTypeDefinitionId); - if (display.PropertyEditor == null) - { - //if there is no property editor it means that it is a legacy data type - // we cannot support editing with that so we'll just render the readonly value view. - display.View = GlobalSettings.Path.EnsureEndsWith('/') + - "views/propertyeditors/umbraco/readonlyvalue/readonlyvalue.html"; - } - else - { - display.View = display.PropertyEditor.ValueEditor.View; - } - - return display; - } - } - - /// - /// Creates a ContentPropertyDto from a Property - /// - internal class ContentPropertyDtoConverter : ContentPropertyBasicConverter - { - private readonly ApplicationContext _applicationContext; - - public ContentPropertyDtoConverter(ApplicationContext applicationContext) - { - _applicationContext = applicationContext; - } - - protected override ContentPropertyDto ConvertCore(Property originalProperty) - { - var propertyDto = base.ConvertCore(originalProperty); - - propertyDto.IsRequired = originalProperty.PropertyType.Mandatory; - propertyDto.ValidationRegExp = originalProperty.PropertyType.ValidationRegExp; - propertyDto.Alias = originalProperty.Alias; - propertyDto.Description = originalProperty.PropertyType.Description; - propertyDto.Label = originalProperty.PropertyType.Name; - propertyDto.DataType = _applicationContext.Services.DataTypeService.GetDataTypeDefinitionById(originalProperty.PropertyType.DataTypeDefinitionId); - propertyDto.PropertyEditor = PropertyEditorResolver.Current.GetById(originalProperty.PropertyType.DataTypeId); - - return propertyDto; - } - } - - /// - /// Creates a base generic ContentPropertyBasic from a Property - /// - /// - internal class ContentPropertyBasicConverter : TypeConverter - where T : ContentPropertyBasic, new() - { - protected override T ConvertCore(Property property) - { - var editor = PropertyEditorResolver.Current.GetById(property.PropertyType.DataTypeId); - if (editor == null) - { - //TODO: Remove this check as we shouldn't support this at all! - var legacyEditor = DataTypesResolver.Current.GetById(property.PropertyType.DataTypeId); - if (legacyEditor == null) - { - throw new NullReferenceException("The property editor with id " + property.PropertyType.DataTypeId + " does not exist"); - } - - var legacyResult = new T - { - Id = property.Id, - Value = property.Value == null ? "" : property.Value.ToString(), - Alias = property.Alias - }; - return legacyResult; - } - var result = new T - { - Id = property.Id, - Value = editor.ValueEditor.SerializeValue(property.Value), - Alias = property.Alias - }; - - result.PropertyEditor = editor; - - return result; - } - } - -} \ No newline at end of file diff --git a/src/Umbraco.Web/Models/Mapping/OwnerResolver.cs b/src/Umbraco.Web/Models/Mapping/OwnerResolver.cs new file mode 100644 index 0000000000..0577063d36 --- /dev/null +++ b/src/Umbraco.Web/Models/Mapping/OwnerResolver.cs @@ -0,0 +1,20 @@ +using AutoMapper; +using Umbraco.Core.Models; +using Umbraco.Core.Models.Membership; +using Umbraco.Web.Models.ContentEditing; + +namespace Umbraco.Web.Models.Mapping +{ + /// + /// Maps the Owner for IContentBase + /// + /// + internal class OwnerResolver : ValueResolver + where TPersisted : IContentBase + { + protected override UserBasic ResolveCore(TPersisted source) + { + return Mapper.Map(source.GetCreatorProfile()); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Models/Mapping/TabsAndPropertiesResolver.cs b/src/Umbraco.Web/Models/Mapping/TabsAndPropertiesResolver.cs new file mode 100644 index 0000000000..c943cf8ff0 --- /dev/null +++ b/src/Umbraco.Web/Models/Mapping/TabsAndPropertiesResolver.cs @@ -0,0 +1,68 @@ +using System.Collections.Generic; +using System.Linq; +using AutoMapper; +using Umbraco.Core.Models; +using Umbraco.Web.Models.ContentEditing; + +namespace Umbraco.Web.Models.Mapping +{ + /// + /// Creates the tabs collection with properties assigned for display models + /// + internal class TabsAndPropertiesResolver : ValueResolver>> + { + protected override IEnumerable> ResolveCore(IContentBase content) + { + var aggregateTabs = new List>(); + + //now we need to aggregate the tabs and properties since we might have duplicate tabs (based on aliases) because + // of how content composition works. + foreach (var propertyGroups in content.PropertyGroups.GroupBy(x => x.Name)) + { + var aggregateProperties = new List(); + + //there will always be one group with a null parent id (the top-most) + //then we'll iterate over all of the groups and ensure the properties are + //added in order so that when they render they are rendered with highest leve + //parent properties first. + int? currentParentId = null; + for (var i = 0; i < propertyGroups.Count(); i++) + { + var current = propertyGroups.Single(x => x.ParentId == currentParentId); + aggregateProperties.AddRange( + Mapper.Map, IEnumerable>( + content.GetPropertiesForGroup(current))); + currentParentId = current.Id; + } + + //then we'll just use the root group's data to make the composite tab + var rootGroup = propertyGroups.Single(x => x.ParentId == null); + aggregateTabs.Add(new Tab + { + Id = rootGroup.Id, + Alias = rootGroup.Name, + Label = rootGroup.Name, + Properties = aggregateProperties, + IsActive = false + }); + } + + //now add the generic properties tab for any properties that don't belong to a tab + var orphanProperties = content.GetNonGroupedProperties(); + + //now add the generic properties tab + aggregateTabs.Add(new Tab + { + Id = 0, + Label = "Generic properties", + Alias = "Generic properties", + Properties = Mapper.Map, IEnumerable>(orphanProperties) + }); + + //set the first tab to active + aggregateTabs.First().IsActive = true; + + return aggregateTabs; + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index cb33192d9f..927a6490de 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -321,13 +321,19 @@ - + + + + + - + + + diff --git a/src/Umbraco.Web/WebApi/Binders/MediaItemBinder.cs b/src/Umbraco.Web/WebApi/Binders/MediaItemBinder.cs index 4281a31fe7..81f7bba8fb 100644 --- a/src/Umbraco.Web/WebApi/Binders/MediaItemBinder.cs +++ b/src/Umbraco.Web/WebApi/Binders/MediaItemBinder.cs @@ -8,20 +8,17 @@ using Umbraco.Web.Models.Mapping; namespace Umbraco.Web.WebApi.Binders { internal class MediaItemBinder : ContentItemBaseBinder - { - private readonly MediaModelMapper _mediaModelMapper; - - public MediaItemBinder(ApplicationContext applicationContext, MediaModelMapper mediaModelMapper) + { + public MediaItemBinder(ApplicationContext applicationContext) : base(applicationContext) { - _mediaModelMapper = mediaModelMapper; } /// /// Constructor /// public MediaItemBinder() - : this(ApplicationContext.Current, new MediaModelMapper(ApplicationContext.Current, new UserModelMapper())) + : this(ApplicationContext.Current) { }