From a3d674f57453e5240a6cebff0dfd85f68f61a5dd Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 27 Sep 2013 16:59:38 +1000 Subject: [PATCH] Updated the saving model and validator inheritance chain to better support members, have the members editor display data and posting/validating data - now need to get the correct fields being displayed and the correct tab/layout but coming along very nicely! --- .../Services/ContentTypeService.cs | 29 ++++++++ .../Services/IContentTypeService.cs | 14 ++++ .../src/common/resources/content.resource.js | 12 ++- .../src/common/resources/media.resource.js | 12 ++- .../src/common/resources/member.resource.js | 13 +++- .../services/umbrequesthelper.service.js | 31 ++++++-- .../src/common/services/util.service.js | 74 +++++++++++++------ src/Umbraco.Web/Editors/ContentController.cs | 2 +- .../Editors/ContentControllerBase.cs | 4 +- .../Editors/ContentPostValidateAttribute.cs | 2 +- .../Editors/DataTypeValidateAttribute.cs | 2 +- src/Umbraco.Web/Editors/MediaController.cs | 2 +- .../Editors/MediaPostValidateAttribute.cs | 2 +- src/Umbraco.Web/Editors/MemberController.cs | 63 ++++++++++++++++ .../Models/ContentEditing/ContentItemBasic.cs | 4 +- .../Models/ContentEditing/ContentItemSave.cs | 32 +++++--- .../Models/ContentEditing/EntityBasic.cs | 2 +- .../Models/ContentEditing/MemberSave.cs | 20 +++++ .../Models/Mapping/MemberModelMapper.cs | 4 +- .../Mapping/TabsAndPropertiesResolver.cs | 2 +- src/Umbraco.Web/Umbraco.Web.csproj | 2 + .../WebApi/Binders/ContentItemBaseBinder.cs | 19 ++--- .../WebApi/Binders/ContentItemBinder.cs | 10 +-- .../WebApi/Binders/MediaItemBinder.cs | 10 +-- .../WebApi/Binders/MemberBinder.cs | 44 +++++++++++ .../Filters/ContentItemValidationHelper.cs | 12 +-- .../FilterAllowedOutgoingContentAttribute.cs | 2 +- 27 files changed, 341 insertions(+), 84 deletions(-) create mode 100644 src/Umbraco.Web/Models/ContentEditing/MemberSave.cs create mode 100644 src/Umbraco.Web/WebApi/Binders/MemberBinder.cs diff --git a/src/Umbraco.Core/Services/ContentTypeService.cs b/src/Umbraco.Core/Services/ContentTypeService.cs index f3017089d7..afd7da9efe 100644 --- a/src/Umbraco.Core/Services/ContentTypeService.cs +++ b/src/Umbraco.Core/Services/ContentTypeService.cs @@ -347,6 +347,35 @@ namespace Umbraco.Core.Services } } + /// + /// Gets an object by its Id + /// + /// Id of the to retrieve + /// + public IMemberType GetMemberType(int id) + { + using (var repository = _repositoryFactory.CreateMemberTypeRepository(_uowProvider.GetUnitOfWork())) + { + return repository.Get(id); + } + } + + /// + /// Gets an object by its Alias + /// + /// Alias of the to retrieve + /// + public IMemberType GetMemberType(string alias) + { + using (var repository = _repositoryFactory.CreateMemberTypeRepository(_uowProvider.GetUnitOfWork())) + { + var query = Query.Builder.Where(x => x.Alias == alias); + var contentTypes = repository.GetByQuery(query); + + return contentTypes.FirstOrDefault(); + } + } + /// /// Gets an object by its Id /// diff --git a/src/Umbraco.Core/Services/IContentTypeService.cs b/src/Umbraco.Core/Services/IContentTypeService.cs index 9e7849e33c..9f596e465e 100644 --- a/src/Umbraco.Core/Services/IContentTypeService.cs +++ b/src/Umbraco.Core/Services/IContentTypeService.cs @@ -67,6 +67,20 @@ namespace Umbraco.Core.Services /// Optional Id of the User deleting the ContentTypes void Delete(IEnumerable contentTypes, int userId = 0); + /// + /// Gets an object by its Id + /// + /// Id of the to retrieve + /// + IMemberType GetMemberType(int id); + + /// + /// Gets an object by its Alias + /// + /// Alias of the to retrieve + /// + IMemberType GetMemberType(string alias); + /// /// Gets an object by its Id /// diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js index ad4cff3c70..45b696edb2 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js @@ -27,11 +27,17 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { /** internal method process the saving of data and post processing the result */ function saveContentItem(content, action, files) { - return umbRequestHelper.postSaveContent( - umbRequestHelper.getApiUrl( + return umbRequestHelper.postSaveContent({ + restApiUrl: umbRequestHelper.getApiUrl( "contentApiBaseUrl", "PostSave"), - content, action, files); + content: content, + action: action, + files: files, + dataFormatter: function (c, a) { + return umbDataFormatter.formatContentPostData(c, a); + } + }); } return { diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/media.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/media.resource.js index fc9cc117a0..951a4d182f 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/media.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/media.resource.js @@ -7,11 +7,17 @@ function mediaResource($q, $http, umbDataFormatter, umbRequestHelper) { /** internal method process the saving of data and post processing the result */ function saveMediaItem(content, action, files) { - return umbRequestHelper.postSaveContent( - umbRequestHelper.getApiUrl( + return umbRequestHelper.postSaveContent({ + restApiUrl: umbRequestHelper.getApiUrl( "mediaApiBaseUrl", "PostSave"), - content, action, files); + content: content, + action: action, + files: files, + dataFormatter: function (c, a) { + return umbDataFormatter.formatMediaPostData(c, a); + } + }); } return { diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/member.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/member.resource.js index f5f32037ad..d879aa322c 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/member.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/member.resource.js @@ -7,11 +7,18 @@ function memberResource($q, $http, umbDataFormatter, umbRequestHelper) { /** internal method process the saving of data and post processing the result */ function saveMember(content, action, files) { - return umbRequestHelper.postSaveContent( - umbRequestHelper.getApiUrl( + + return umbRequestHelper.postSaveContent({ + restApiUrl: umbRequestHelper.getApiUrl( "memberApiBaseUrl", "PostSave"), - content, action, files); + content: content, + action: action, + files: files, + dataFormatter: function(c, a) { + return umbDataFormatter.formatMemberPostData(c, a); + } + }); } return { diff --git a/src/Umbraco.Web.UI.Client/src/common/services/umbrequesthelper.service.js b/src/Umbraco.Web.UI.Client/src/common/services/umbrequesthelper.service.js index 9b52da35ad..d44bb9ccb5 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/umbrequesthelper.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/umbrequesthelper.service.js @@ -151,26 +151,43 @@ function umbRequestHelper($http, $q, umbDataFormatter, angularHelper, dialogServ }, /** Used for saving media/content specifically */ - postSaveContent: function (restApiUrl, content, action, files) { + postSaveContent: function (args) { + + if (!args.restApiUrl) { + throw "args.restApiUrl is a required argument"; + } + if (!args.content) { + throw "args.content is a required argument"; + } + if (!args.action) { + throw "args.action is a required argument"; + } + if (!args.files) { + throw "args.files is a required argument"; + } + if (!args.dataFormatter) { + throw "args.dataFormatter is a required argument"; + } + var deferred = $q.defer(); //save the active tab id so we can set it when the data is returned. - var activeTab = _.find(content.tabs, function (item) { + var activeTab = _.find(args.content.tabs, function (item) { return item.active; }); - var activeTabIndex = (activeTab === undefined ? 0 : _.indexOf(content.tabs, activeTab)); + var activeTabIndex = (activeTab === undefined ? 0 : _.indexOf(args.content.tabs, activeTab)); //save the data this.postMultiPartRequest( - restApiUrl, - { key: "contentItem", value: umbDataFormatter.formatContentPostData(content, action) }, + args.restApiUrl, + { key: "contentItem", value: args.dataFormatter(args.content, args.action) }, function (data, formData) { //now add all of the assigned files - for (var f in files) { + for (var f in args.files) { //each item has a property id and the file object, we'll ensure that the id is suffixed to the key // so we know which property it belongs to on the server side - formData.append("file_" + files[f].id, files[f].file); + formData.append("file_" + args.files[f].id, args.files[f].file); } }, diff --git a/src/Umbraco.Web.UI.Client/src/common/services/util.service.js b/src/Umbraco.Web.UI.Client/src/common/services/util.service.js index 21e81d1ac3..09f4ecf067 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/util.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/util.service.js @@ -196,24 +196,45 @@ function umbDataFormatter() { return saveModel; }, - /** formats the display model used to display the content to the model used to save the content */ - formatContentPostData: function (displayModel, action) { + /** formats the display model used to display the member to the model used to save the member */ + formatMemberPostData: function(displayModel, action) { + //this is basically the same as for media but we need to explicitly add the username,email to the save model + var saveModel = this.formatMediaPostData(displayModel, action); + + var genericTab = _.find(displayModel.tabs, function (item) { + return item.id === 0; + }); + + var propLogin = _.find(genericTab.properties, function (item) { + return item.alias === "_umb_login"; + }); + var propEmail = _.find(genericTab.properties, function (item) { + return item.alias === "_umb_email"; + }); + saveModel.email = propEmail.value; + saveModel.username = propLogin.value; + + return saveModel; + }, + + /** formats the display model used to display the media to the model used to save the media */ + formatMediaPostData: function(displayModel, action) { //NOTE: the display model inherits from the save model so we can in theory just post up the display model but // we don't want to post all of the data as it is unecessary. var saveModel = { id: displayModel.id, properties: [], name: displayModel.name, - contentTypeAlias : displayModel.contentTypeAlias, + contentTypeAlias: displayModel.contentTypeAlias, parentId: displayModel.parentId, //set the action on the save model action: action }; - + _.each(displayModel.tabs, function (tab) { - + _.each(tab.properties, function (prop) { - + //don't include the custom generic tab properties if (!prop.alias.startsWith("_umb_")) { saveModel.properties.push({ @@ -222,25 +243,36 @@ function umbDataFormatter() { value: prop.value }); } - else { - //here we need to map some of our internal properties to the content save item - - switch (prop.alias) { - case "_umb_expiredate": - saveModel.expireDate = prop.value; - break; - case "_umb_releasedate": - saveModel.releaseDate = prop.value; - break; - case "_umb_template": - saveModel.templateAlias = prop.value; - break; - } - } }); }); + return saveModel; + }, + + /** formats the display model used to display the content to the model used to save the content */ + formatContentPostData: function (displayModel, action) { + + //this is basically the same as for media but we need to explicitly add some extra properties + var saveModel = this.formatMediaPostData(displayModel, action); + + var genericTab = _.find(displayModel.tabs, function (item) { + return item.id === 0; + }); + + var propExpireDate = _.find(genericTab.properties, function(item) { + return item.alias === "_umb_expiredate"; + }); + var propReleaseDate = _.find(genericTab.properties, function (item) { + return item.alias === "_umb_releasedate"; + }); + var propTemplate = _.find(genericTab.properties, function (item) { + return item.alias === "_umb_template"; + }); + saveModel.expireDate = propExpireDate.value; + saveModel.releaseDate = propReleaseDate.value; + saveModel.templateAlias = propTemplate.value; + return saveModel; } }; diff --git a/src/Umbraco.Web/Editors/ContentController.cs b/src/Umbraco.Web/Editors/ContentController.cs index 39ca4704b4..d27b8b268f 100644 --- a/src/Umbraco.Web/Editors/ContentController.cs +++ b/src/Umbraco.Web/Editors/ContentController.cs @@ -166,7 +166,7 @@ namespace Umbraco.Web.Editors [ContentPostValidate] public ContentItemDisplay PostSave( [ModelBinder(typeof(ContentItemBinder))] - ContentItemSave contentItem) + ContentItemSave contentItem) { //If we've reached here it means: // * Our model has been bound diff --git a/src/Umbraco.Web/Editors/ContentControllerBase.cs b/src/Umbraco.Web/Editors/ContentControllerBase.cs index 6413173ce8..73e66bcc44 100644 --- a/src/Umbraco.Web/Editors/ContentControllerBase.cs +++ b/src/Umbraco.Web/Editors/ContentControllerBase.cs @@ -52,7 +52,7 @@ namespace Umbraco.Web.Editors return errorResponse; } - protected void UpdateName(ContentItemSave contentItem) + protected void UpdateName(ContentBaseItemSave contentItem) where TPersisted : IContentBase { //Don't update the name if it is empty @@ -78,7 +78,7 @@ namespace Umbraco.Web.Editors return null; } - protected void MapPropertyValues(ContentItemSave contentItem) + protected void MapPropertyValues(ContentBaseItemSave contentItem) where TPersisted : IContentBase { //Map the property values diff --git a/src/Umbraco.Web/Editors/ContentPostValidateAttribute.cs b/src/Umbraco.Web/Editors/ContentPostValidateAttribute.cs index 1736b57dd9..79a6dd3836 100644 --- a/src/Umbraco.Web/Editors/ContentPostValidateAttribute.cs +++ b/src/Umbraco.Web/Editors/ContentPostValidateAttribute.cs @@ -53,7 +53,7 @@ namespace Umbraco.Web.Editors public override void OnActionExecuting(HttpActionContext actionContext) { - var contentItem = (ContentItemSave)actionContext.ActionArguments["contentItem"]; + var contentItem = (ContentItemSave)actionContext.ActionArguments["contentItem"]; //We now need to validate that the user is allowed to be doing what they are doing. //Based on the action we need to check different permissions. diff --git a/src/Umbraco.Web/Editors/DataTypeValidateAttribute.cs b/src/Umbraco.Web/Editors/DataTypeValidateAttribute.cs index a5be7c118d..2ae24717bd 100644 --- a/src/Umbraco.Web/Editors/DataTypeValidateAttribute.cs +++ b/src/Umbraco.Web/Editors/DataTypeValidateAttribute.cs @@ -59,7 +59,7 @@ namespace Umbraco.Web.Editors switch (dataType.Action) { case ContentSaveAction.Save: - persisted = DataTypeService.GetDataTypeDefinitionById(dataType.Id); + persisted = DataTypeService.GetDataTypeDefinitionById(Convert.ToInt32(dataType.Id)); if (persisted == null) { var message = string.Format("Data type with id: {0} was not found", dataType.Id); diff --git a/src/Umbraco.Web/Editors/MediaController.cs b/src/Umbraco.Web/Editors/MediaController.cs index b32b709df5..4f21a4e8a3 100644 --- a/src/Umbraco.Web/Editors/MediaController.cs +++ b/src/Umbraco.Web/Editors/MediaController.cs @@ -201,7 +201,7 @@ namespace Umbraco.Web.Editors [MediaPostValidate] public MediaItemDisplay PostSave( [ModelBinder(typeof(MediaItemBinder))] - ContentItemSave contentItem) + MediaItemSave contentItem) { //If we've reached here it means: // * Our model has been bound diff --git a/src/Umbraco.Web/Editors/MediaPostValidateAttribute.cs b/src/Umbraco.Web/Editors/MediaPostValidateAttribute.cs index 4790a9122f..31a581eac2 100644 --- a/src/Umbraco.Web/Editors/MediaPostValidateAttribute.cs +++ b/src/Umbraco.Web/Editors/MediaPostValidateAttribute.cs @@ -45,7 +45,7 @@ namespace Umbraco.Web.Editors public override void OnActionExecuting(HttpActionContext actionContext) { - var mediaItem = (ContentItemSave)actionContext.ActionArguments["contentItem"]; + var mediaItem = (MediaItemSave)actionContext.ActionArguments["contentItem"]; //We now need to validate that the user is allowed to be doing what they are doing. //Then if it is new, we need to lookup those permissions on the parent. diff --git a/src/Umbraco.Web/Editors/MemberController.cs b/src/Umbraco.Web/Editors/MemberController.cs index a67f472f1a..a424324b3c 100644 --- a/src/Umbraco.Web/Editors/MemberController.cs +++ b/src/Umbraco.Web/Editors/MemberController.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Text; using System.Threading.Tasks; using System.Web.Http; +using System.Web.Http.ModelBinding; using AutoMapper; using Examine.LuceneEngine.SearchCriteria; using Examine.SearchCriteria; @@ -11,7 +12,9 @@ using Umbraco.Core.Models; using Umbraco.Web.WebApi; using Umbraco.Web.Models.ContentEditing; using Umbraco.Web.Mvc; +using Umbraco.Web.WebApi.Binders; using Umbraco.Web.WebApi.Filters; +using umbraco; using Constants = Umbraco.Core.Constants; using Examine; using System.Web.Security; @@ -67,5 +70,65 @@ namespace Umbraco.Web.Editors } } + + /// + /// Saves member + /// + /// + [FileUploadCleanupFilter] + public MemberDisplay PostSave( + [ModelBinder(typeof(MemberBinder))] + MemberSave contentItem) + { + //If we've reached here it means: + // * Our model has been bound + // * and validated + // * any file attachments have been saved to their temporary location for us to use + // * we have a reference to the DTO object and the persisted object + // * Permissions are valid + + UpdateName(contentItem); + + MapPropertyValues(contentItem); + + //We need to manually check the validation results here because: + // * We still need to save the entity even if there are validation value errors + // * Depending on if the entity is new, and if there are non property validation errors (i.e. the name is null) + // then we cannot continue saving, we can only display errors + // * If there are validation errors and they were attempting to publish, we can only save, NOT publish and display + // a message indicating this + if (!ModelState.IsValid) + { + if (ValidationHelper.ModelHasRequiredForPersistenceErrors(contentItem) + && (contentItem.Action == ContentSaveAction.SaveNew)) + { + //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 validation response + var forDisplay = Mapper.Map(contentItem.PersistedContent); + forDisplay.Errors = ModelState.ToErrorDictionary(); + throw new HttpResponseException(Request.CreateValidationErrorResponse(forDisplay)); + } + } + + //save the item + Services.MemberService.Save(contentItem.PersistedContent); + + //return the updated model + 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); + + //put the correct msgs in + switch (contentItem.Action) + { + case ContentSaveAction.Save: + case ContentSaveAction.SaveNew: + display.AddSuccessNotification(ui.Text("speechBubbles", "editMemberSaved"), ui.Text("speechBubbles", "editMemberSaved")); + break; + } + + return display; + } } } diff --git a/src/Umbraco.Web/Models/ContentEditing/ContentItemBasic.cs b/src/Umbraco.Web/Models/ContentEditing/ContentItemBasic.cs index 927983e4fe..560f997bde 100644 --- a/src/Umbraco.Web/Models/ContentEditing/ContentItemBasic.cs +++ b/src/Umbraco.Web/Models/ContentEditing/ContentItemBasic.cs @@ -34,7 +34,7 @@ namespace Umbraco.Web.Models.ContentEditing [DataMember(Name = "sortOrder")] public int SortOrder { get; set; } - + protected bool Equals(ContentItemBasic other) { return Id == other.Id; @@ -50,7 +50,7 @@ namespace Umbraco.Web.Models.ContentEditing public override int GetHashCode() { - return Id; + return Id.GetHashCode(); } } diff --git a/src/Umbraco.Web/Models/ContentEditing/ContentItemSave.cs b/src/Umbraco.Web/Models/ContentEditing/ContentItemSave.cs index 9c04a3adcf..6f05f38e7f 100644 --- a/src/Umbraco.Web/Models/ContentEditing/ContentItemSave.cs +++ b/src/Umbraco.Web/Models/ContentEditing/ContentItemSave.cs @@ -8,13 +8,13 @@ using Umbraco.Core.Models; namespace Umbraco.Web.Models.ContentEditing { /// - /// A model representing a content item to be saved + /// A model representing a content base item to be saved /// [DataContract(Name = "content", Namespace = "")] - public class ContentItemSave : ContentItemBasic, IHaveUploadedFiles - where TPersisted : IContentBase + public abstract class ContentBaseItemSave : ContentItemBasic, IHaveUploadedFiles + where TPersisted : IContentBase { - public ContentItemSave() + protected ContentBaseItemSave() { UploadedFiles = new List(); } @@ -26,6 +26,25 @@ namespace Umbraco.Web.Models.ContentEditing [Required] public ContentSaveAction Action { get; set; } + [IgnoreDataMember] + public List UploadedFiles { get; private set; } + } + + /// + /// A model representing a media item to be saved + /// + [DataContract(Name = "content", Namespace = "")] + public class MediaItemSave : ContentBaseItemSave + { + + } + + /// + /// A model representing a content item to be saved + /// + [DataContract(Name = "content", Namespace = "")] + public class ContentItemSave : ContentBaseItemSave + { /// /// The template alias to save /// @@ -38,10 +57,5 @@ namespace Umbraco.Web.Models.ContentEditing [DataMember(Name = "expireDate")] public DateTime? ExpireDate { get; set; } - /// - /// The collection of files uploaded - /// - [JsonIgnore] - public List UploadedFiles { get; private set; } } } \ No newline at end of file diff --git a/src/Umbraco.Web/Models/ContentEditing/EntityBasic.cs b/src/Umbraco.Web/Models/ContentEditing/EntityBasic.cs index 8e08352ecb..d07955060f 100644 --- a/src/Umbraco.Web/Models/ContentEditing/EntityBasic.cs +++ b/src/Umbraco.Web/Models/ContentEditing/EntityBasic.cs @@ -18,7 +18,7 @@ namespace Umbraco.Web.Models.ContentEditing [DataMember(Name = "id", IsRequired = true)] [Required] - public int Id { get; set; } + public object Id { get; set; } [DataMember(Name = "icon")] public string Icon { get; set; } diff --git a/src/Umbraco.Web/Models/ContentEditing/MemberSave.cs b/src/Umbraco.Web/Models/ContentEditing/MemberSave.cs new file mode 100644 index 0000000000..198d489d83 --- /dev/null +++ b/src/Umbraco.Web/Models/ContentEditing/MemberSave.cs @@ -0,0 +1,20 @@ +using System.Runtime.Serialization; +using Umbraco.Core.Models; +using Umbraco.Core.Models.Validation; + +namespace Umbraco.Web.Models.ContentEditing +{ + /// + /// A model representing a member to be saved + /// + public class MemberSave : ContentBaseItemSave + { + [DataMember(Name = "username", IsRequired = true)] + [RequiredForPersistence(AllowEmptyStrings = false, ErrorMessage = "Required")] + public string Username { get; set; } + + [DataMember(Name = "email", IsRequired = true)] + [RequiredForPersistence(AllowEmptyStrings = false, ErrorMessage = "Required")] + public string Email { get; set; } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Models/Mapping/MemberModelMapper.cs b/src/Umbraco.Web/Models/Mapping/MemberModelMapper.cs index d1689e6eb2..61a7a07c50 100644 --- a/src/Umbraco.Web/Models/Mapping/MemberModelMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/MemberModelMapper.cs @@ -45,7 +45,7 @@ namespace Umbraco.Web.Models.Mapping expression => expression.MapFrom(content => content.ContentType.Alias)); //FROM IMember TO ContentItemDto - config.CreateMap>() + config.CreateMap>() .ForMember( dto => dto.Owner, expression => expression.ResolveUsing>()); @@ -77,7 +77,7 @@ namespace Umbraco.Web.Models.Mapping }, new ContentPropertyDisplay { - Alias = string.Format("{0}template", Constants.PropertyEditors.InternalGenericPropertiesPrefix), + Alias = string.Format("{0}email", Constants.PropertyEditors.InternalGenericPropertiesPrefix), Label = ui.Text("general", "email"), Value = display.Email, View = "textbox" diff --git a/src/Umbraco.Web/Models/Mapping/TabsAndPropertiesResolver.cs b/src/Umbraco.Web/Models/Mapping/TabsAndPropertiesResolver.cs index be4246d71a..25038fc08e 100644 --- a/src/Umbraco.Web/Models/Mapping/TabsAndPropertiesResolver.cs +++ b/src/Umbraco.Web/Models/Mapping/TabsAndPropertiesResolver.cs @@ -46,7 +46,7 @@ namespace Umbraco.Web.Models.Mapping { Alias = string.Format("{0}id", Constants.PropertyEditors.InternalGenericPropertiesPrefix), Label = "Id", - Value = display.Id.ToInvariantString(), + Value = Convert.ToInt32(display.Id).ToInvariantString(), View = labelEditor }, new ContentPropertyDisplay diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 4b64498c0b..26941aa33a 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -315,6 +315,7 @@ + @@ -369,6 +370,7 @@ + diff --git a/src/Umbraco.Web/WebApi/Binders/ContentItemBaseBinder.cs b/src/Umbraco.Web/WebApi/Binders/ContentItemBaseBinder.cs index d1e843b6ef..2d374fc529 100644 --- a/src/Umbraco.Web/WebApi/Binders/ContentItemBaseBinder.cs +++ b/src/Umbraco.Web/WebApi/Binders/ContentItemBaseBinder.cs @@ -33,8 +33,9 @@ namespace Umbraco.Web.WebApi.Binders /// /// Binds the content model to the controller action for the posted multi-part Post /// - internal abstract class ContentItemBaseBinder : IModelBinder + internal abstract class ContentItemBaseBinder : IModelBinder where TPersisted : class, IContentBase + where TModelSave : ContentBaseItemSave { protected ApplicationContext ApplicationContext { get; private set; } @@ -69,7 +70,7 @@ namespace Umbraco.Web.WebApi.Binders } //now that everything is binded, validate the properties - var contentItemValidator = new ContentItemValidationHelper(ApplicationContext); + var contentItemValidator = new ContentItemValidationHelper(ApplicationContext); contentItemValidator.ValidateItem(actionContext, x.Result); bindingContext.Model = x.Result; @@ -87,7 +88,7 @@ namespace Umbraco.Web.WebApi.Binders /// /// /// - private async Task> GetModel(HttpActionContext actionContext, ModelBindingContext bindingContext, MultipartFormDataStreamProvider provider) + private async Task GetModel(HttpActionContext actionContext, ModelBindingContext bindingContext, MultipartFormDataStreamProvider provider) { var request = actionContext.Request; @@ -115,13 +116,13 @@ namespace Umbraco.Web.WebApi.Binders var contentItem = result.FormData["contentItem"]; //deserialize into our model - var model = JsonConvert.DeserializeObject>(contentItem); + var model = JsonConvert.DeserializeObject(contentItem); //get the default body validator and validate the object var bodyValidator = actionContext.ControllerContext.Configuration.Services.GetBodyModelValidator(); var metadataProvider = actionContext.ControllerContext.Configuration.Services.GetModelMetadataProvider(); //all validation errors will not contain a prefix - bodyValidator.Validate(model, typeof (ContentItemSave), metadataProvider, actionContext, ""); + bodyValidator.Validate(model, typeof(TModelSave), metadataProvider, actionContext, ""); //get the files foreach (var file in result.FileData) @@ -187,7 +188,7 @@ namespace Umbraco.Web.WebApi.Binders /// /// /// - private static void MapPropertyValuesFromSaved(ContentItemSave saveModel, ContentItemDto dto) + private static void MapPropertyValuesFromSaved(TModelSave saveModel, ContentItemDto dto) { foreach (var p in saveModel.Properties) { @@ -195,8 +196,8 @@ namespace Umbraco.Web.WebApi.Binders } } - protected abstract TPersisted GetExisting(ContentItemSave model); - protected abstract TPersisted CreateNew(ContentItemSave model); - protected abstract ContentItemDto MapFromPersisted(ContentItemSave model); + protected abstract TPersisted GetExisting(TModelSave model); + protected abstract TPersisted CreateNew(TModelSave model); + protected abstract ContentItemDto MapFromPersisted(TModelSave model); } } \ No newline at end of file diff --git a/src/Umbraco.Web/WebApi/Binders/ContentItemBinder.cs b/src/Umbraco.Web/WebApi/Binders/ContentItemBinder.cs index c3c3e2bf84..5afc7cee19 100644 --- a/src/Umbraco.Web/WebApi/Binders/ContentItemBinder.cs +++ b/src/Umbraco.Web/WebApi/Binders/ContentItemBinder.cs @@ -7,7 +7,7 @@ using Umbraco.Web.Models.Mapping; namespace Umbraco.Web.WebApi.Binders { - internal class ContentItemBinder : ContentItemBaseBinder + internal class ContentItemBinder : ContentItemBaseBinder { public ContentItemBinder(ApplicationContext applicationContext) @@ -23,12 +23,12 @@ namespace Umbraco.Web.WebApi.Binders { } - protected override IContent GetExisting(ContentItemSave model) + protected override IContent GetExisting(ContentItemSave model) { - return ApplicationContext.Services.ContentService.GetById(model.Id); + return ApplicationContext.Services.ContentService.GetById(Convert.ToInt32(model.Id)); } - protected override IContent CreateNew(ContentItemSave model) + protected override IContent CreateNew(ContentItemSave model) { var contentType = ApplicationContext.Services.ContentTypeService.GetContentType(model.ContentTypeAlias); if (contentType == null) @@ -38,7 +38,7 @@ namespace Umbraco.Web.WebApi.Binders return new Content(model.Name, model.ParentId, contentType); } - protected override ContentItemDto MapFromPersisted(ContentItemSave model) + protected override ContentItemDto MapFromPersisted(ContentItemSave model) { return Mapper.Map>(model.PersistedContent); } diff --git a/src/Umbraco.Web/WebApi/Binders/MediaItemBinder.cs b/src/Umbraco.Web/WebApi/Binders/MediaItemBinder.cs index 81f7bba8fb..726994d43e 100644 --- a/src/Umbraco.Web/WebApi/Binders/MediaItemBinder.cs +++ b/src/Umbraco.Web/WebApi/Binders/MediaItemBinder.cs @@ -7,7 +7,7 @@ using Umbraco.Web.Models.Mapping; namespace Umbraco.Web.WebApi.Binders { - internal class MediaItemBinder : ContentItemBaseBinder + internal class MediaItemBinder : ContentItemBaseBinder { public MediaItemBinder(ApplicationContext applicationContext) : base(applicationContext) @@ -22,12 +22,12 @@ namespace Umbraco.Web.WebApi.Binders { } - protected override IMedia GetExisting(ContentItemSave model) + protected override IMedia GetExisting(MediaItemSave model) { - return ApplicationContext.Services.MediaService.GetById(model.Id); + return ApplicationContext.Services.MediaService.GetById(Convert.ToInt32(model.Id)); } - protected override IMedia CreateNew(ContentItemSave model) + protected override IMedia CreateNew(MediaItemSave model) { var contentType = ApplicationContext.Services.ContentTypeService.GetMediaType(model.ContentTypeAlias); if (contentType == null) @@ -37,7 +37,7 @@ namespace Umbraco.Web.WebApi.Binders return new Core.Models.Media(model.Name, model.ParentId, contentType); } - protected override ContentItemDto MapFromPersisted(ContentItemSave model) + protected override ContentItemDto MapFromPersisted(MediaItemSave model) { return Mapper.Map>(model.PersistedContent); } diff --git a/src/Umbraco.Web/WebApi/Binders/MemberBinder.cs b/src/Umbraco.Web/WebApi/Binders/MemberBinder.cs new file mode 100644 index 0000000000..ee02c4e076 --- /dev/null +++ b/src/Umbraco.Web/WebApi/Binders/MemberBinder.cs @@ -0,0 +1,44 @@ +using System; +using AutoMapper; +using Umbraco.Core; +using Umbraco.Core.Models; +using Umbraco.Web.Models.ContentEditing; + +namespace Umbraco.Web.WebApi.Binders +{ + internal class MemberBinder : ContentItemBaseBinder + { + public MemberBinder(ApplicationContext applicationContext) + : base(applicationContext) + { + } + + /// + /// Constructor + /// + public MemberBinder() + : this(ApplicationContext.Current) + { + } + + protected override IMember GetExisting(MemberSave model) + { + return ApplicationContext.Services.MemberService.GetByUsername(model.Username); + } + + protected override IMember CreateNew(MemberSave model) + { + var contentType = ApplicationContext.Services.ContentTypeService.GetMemberType(model.ContentTypeAlias); + if (contentType == null) + { + throw new InvalidOperationException("No member type found wth alias " + model.ContentTypeAlias); + } + return new Member(model.Name, model.ParentId, contentType, new PropertyCollection()); + } + + protected override ContentItemDto MapFromPersisted(MemberSave model) + { + return Mapper.Map>(model.PersistedContent); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/WebApi/Filters/ContentItemValidationHelper.cs b/src/Umbraco.Web/WebApi/Filters/ContentItemValidationHelper.cs index adc12b58d7..a68eb2cbbf 100644 --- a/src/Umbraco.Web/WebApi/Filters/ContentItemValidationHelper.cs +++ b/src/Umbraco.Web/WebApi/Filters/ContentItemValidationHelper.cs @@ -16,12 +16,14 @@ namespace Umbraco.Web.WebApi.Filters /// A validation helper class used with ContentItemValidationFilterAttribute to be shared between content, media, etc... /// /// + /// /// /// If any severe errors occur then the response gets set to an error and execution will not continue. Property validation /// errors will just be added to the ModelState. /// - internal class ContentItemValidationHelper + internal class ContentItemValidationHelper where TPersisted : class, IContentBase + where TModelSave : ContentBaseItemSave { private readonly ApplicationContext _applicationContext; @@ -38,10 +40,10 @@ namespace Umbraco.Web.WebApi.Filters public void ValidateItem(HttpActionContext actionContext, string argumentName) { - var contentItem = actionContext.ActionArguments[argumentName] as ContentItemSave; + var contentItem = actionContext.ActionArguments[argumentName] as TModelSave; if (contentItem == null) { - actionContext.Response = actionContext.Request.CreateErrorResponse(HttpStatusCode.BadRequest, "No " + typeof(ContentItemSave) + " found in request"); + actionContext.Response = actionContext.Request.CreateErrorResponse(HttpStatusCode.BadRequest, "No " + typeof(TModelSave) + " found in request"); return; } @@ -49,7 +51,7 @@ namespace Umbraco.Web.WebApi.Filters } - public void ValidateItem(HttpActionContext actionContext, ContentItemSave contentItem) + public void ValidateItem(HttpActionContext actionContext, TModelSave contentItem) { //now do each validation step if (ValidateExistingContent(contentItem, actionContext) == false) return; @@ -119,7 +121,7 @@ namespace Umbraco.Web.WebApi.Filters if (editor == null) { var message = string.Format("The property editor with alias: {0} was not found for property with id {1}", p.DataType.PropertyEditorAlias, p.Id); - LogHelper.Warn>(message); + LogHelper.Warn>(message); //actionContext.Response = actionContext.Request.CreateErrorResponse(HttpStatusCode.NotFound, message); //return false; continue; diff --git a/src/Umbraco.Web/WebApi/Filters/FilterAllowedOutgoingContentAttribute.cs b/src/Umbraco.Web/WebApi/Filters/FilterAllowedOutgoingContentAttribute.cs index 8193b528f6..354313517e 100644 --- a/src/Umbraco.Web/WebApi/Filters/FilterAllowedOutgoingContentAttribute.cs +++ b/src/Umbraco.Web/WebApi/Filters/FilterAllowedOutgoingContentAttribute.cs @@ -57,7 +57,7 @@ namespace Umbraco.Web.WebApi.Filters var toRemove = new List(); foreach(dynamic item in items) { - var nodePermission = permissions.Where(x => x.EntityId == item.Id).ToArray(); + var nodePermission = permissions.Where(x => x.EntityId == Convert.ToInt32(item.Id)).ToArray(); //if there are no permissions for this id then we need to check what the user's default // permissions are. if (nodePermission.Any() == false)