From 3fee64d6eb3403625218c3edaee15e3344a27abe Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 11 Jun 2013 01:56:30 +0200 Subject: [PATCH] Got media saving now. Signed-off-by: Shannon --- .../build/belle/js/umbraco.resources.js | 84 +--------- .../build/belle/js/umbraco.services.js | 46 +++++- .../src/common/resources/content.resource.js | 41 +---- .../src/common/resources/media.resource.js | 43 +---- .../src/common/services/utill.service.js | 46 +++++- .../views/content/contentedit.controller.js | 15 +- .../src/views/media/mediaedit.controller.js | 7 +- .../PropertyEditors/FileUploadValueEditor.cs | 7 + .../PropertyEditors/Js/FileUploadEditor.js | 2 +- src/Umbraco.Web/Editors/MediaController.cs | 16 +- src/Umbraco.Web/Umbraco.Web.csproj | 2 + .../WebApi/Binders/ContentItemBaseBinder.cs | 152 ++++++++++++++++++ .../WebApi/Binders/ContentItemBinder.cs | 146 ++--------------- .../WebApi/Binders/MediaItemBinder.cs | 47 ++++++ .../ContentItemValidationFilterAttribute.cs | 7 +- 15 files changed, 348 insertions(+), 313 deletions(-) create mode 100644 src/Umbraco.Web/WebApi/Binders/ContentItemBaseBinder.cs create mode 100644 src/Umbraco.Web/WebApi/Binders/MediaItemBinder.cs diff --git a/src/Umbraco.Web.UI.Client/build/belle/js/umbraco.resources.js b/src/Umbraco.Web.UI.Client/build/belle/js/umbraco.resources.js index fee6daa5e8..cd3dde73d2 100644 --- a/src/Umbraco.Web.UI.Client/build/belle/js/umbraco.resources.js +++ b/src/Umbraco.Web.UI.Client/build/belle/js/umbraco.resources.js @@ -27,46 +27,7 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { } /** internal method process the saving of data and post processing the result */ function saveContentItem(content, action, files) { - 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) { - return item.active; - }); - var activeTabIndex = (activeTab === undefined ? 0 : _.indexOf(content.tabs, activeTab)); - - //save the data - umbRequestHelper.postMultiPartRequest( - getSaveUrl(content.id), - { key: "contentItem", value: umbDataFormatter.formatContentPostData(content, action) }, - function (data, formData) { - //now add all of the assigned files - for (var f in 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); - } - - }, - function (data, status, headers, config) { - //success callback - - //reset the tabs and set the active one - _.each(data.tabs, function (item) { - item.active = false; - }); - data.tabs[activeTabIndex].active = true; - - //the data returned is the up-to-date data so the UI will refresh - deferred.resolve(data); - }, - function (data, status, headers, config) { - //failure callback - - deferred.reject('Failed to publish data for content id ' + content.id); - }); - - return deferred.promise; + return umbRequestHelper.postSaveContent(getSaveUrl(content.id), content, action, files); } return { @@ -238,7 +199,7 @@ angular.module('umbraco.resources').factory('contentTypeResource', contentTypeRe * @name umbraco.resources.treeResource * @description Loads in data for trees **/ -function mediaResource($q, $http) { +function mediaResource($q, $http, umbDataFormatter, umbRequestHelper) { /** internal method to get the api url */ function getMediaUrl(contentId) { @@ -261,46 +222,7 @@ function mediaResource($q, $http) { /** internal method process the saving of data and post processing the result */ function saveMediaItem(content, action, files) { - 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) { - return item.active; - }); - var activeTabIndex = (activeTab === undefined ? 0 : _.indexOf(content.tabs, activeTab)); - - //save the data - umbRequestHelper.postMultiPartRequest( - getSaveUrl(content.id), - { key: "mediaItem", value: umbDataFormatter.formatContentPostData(content, action) }, - function (data, formData) { - //now add all of the assigned files - for (var f in 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); - } - - }, - function (data, status, headers, config) { - //success callback - - //reset the tabs and set the active one - _.each(data.tabs, function (item) { - item.active = false; - }); - data.tabs[activeTabIndex].active = true; - - //the data returned is the up-to-date data so the UI will refresh - deferred.resolve(data); - }, - function (data, status, headers, config) { - //failure callback - - deferred.reject('Failed to save data for media id ' + content.id); - }); - - return deferred.promise; + return umbRequestHelper.postSaveContent(getSaveUrl(content.id), content, action, files); } return { diff --git a/src/Umbraco.Web.UI.Client/build/belle/js/umbraco.services.js b/src/Umbraco.Web.UI.Client/build/belle/js/umbraco.services.js index 249ceeb849..c94d35a3c3 100644 --- a/src/Umbraco.Web.UI.Client/build/belle/js/umbraco.services.js +++ b/src/Umbraco.Web.UI.Client/build/belle/js/umbraco.services.js @@ -590,8 +590,52 @@ angular.module('umbraco.services') * @name umbraco.services:umbRequestHelper * @description A helper object used for sending requests to the server **/ -function umbRequestHelper($http) { +function umbRequestHelper($http, $q, umbDataFormatter) { return { + + postSaveContent: function (restApiUrl, content, action, files) { + 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) { + return item.active; + }); + var activeTabIndex = (activeTab === undefined ? 0 : _.indexOf(content.tabs, activeTab)); + + //save the data + this.postMultiPartRequest( + restApiUrl, + { key: "contentItem", value: umbDataFormatter.formatContentPostData(content, action) }, + function (data, formData) { + //now add all of the assigned files + for (var f in 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); + } + + }, + function (data, status, headers, config) { + //success callback + + //reset the tabs and set the active one + _.each(data.tabs, function (item) { + item.active = false; + }); + data.tabs[activeTabIndex].active = true; + + //the data returned is the up-to-date data so the UI will refresh + deferred.resolve(data); + }, + function (data, status, headers, config) { + //failure callback + + deferred.reject('Failed to save data for media id ' + content.id); + }); + + return deferred.promise; + }, + /** Posts a multi-part mime request to the server */ postMultiPartRequest: function (url, jsonData, transformCallback, successCallback, failureCallback) { 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 4a4f541b16..b358c6d490 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 @@ -19,46 +19,7 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { } /** internal method process the saving of data and post processing the result */ function saveContentItem(content, action, files) { - 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) { - return item.active; - }); - var activeTabIndex = (activeTab === undefined ? 0 : _.indexOf(content.tabs, activeTab)); - - //save the data - umbRequestHelper.postMultiPartRequest( - getSaveUrl(content.id), - { key: "contentItem", value: umbDataFormatter.formatContentPostData(content, action) }, - function (data, formData) { - //now add all of the assigned files - for (var f in 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); - } - - }, - function (data, status, headers, config) { - //success callback - - //reset the tabs and set the active one - _.each(data.tabs, function (item) { - item.active = false; - }); - data.tabs[activeTabIndex].active = true; - - //the data returned is the up-to-date data so the UI will refresh - deferred.resolve(data); - }, - function (data, status, headers, config) { - //failure callback - - deferred.reject('Failed to publish data for content id ' + content.id); - }); - - return deferred.promise; + return umbRequestHelper.postSaveContent(getSaveUrl(content.id), content, action, files); } 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 35ecf79223..628fc6ee91 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 @@ -3,7 +3,7 @@ * @name umbraco.resources.treeResource * @description Loads in data for trees **/ -function mediaResource($q, $http) { +function mediaResource($q, $http, umbDataFormatter, umbRequestHelper) { /** internal method to get the api url */ function getMediaUrl(contentId) { @@ -26,46 +26,7 @@ function mediaResource($q, $http) { /** internal method process the saving of data and post processing the result */ function saveMediaItem(content, action, files) { - 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) { - return item.active; - }); - var activeTabIndex = (activeTab === undefined ? 0 : _.indexOf(content.tabs, activeTab)); - - //save the data - umbRequestHelper.postMultiPartRequest( - getSaveUrl(content.id), - { key: "mediaItem", value: umbDataFormatter.formatContentPostData(content, action) }, - function (data, formData) { - //now add all of the assigned files - for (var f in 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); - } - - }, - function (data, status, headers, config) { - //success callback - - //reset the tabs and set the active one - _.each(data.tabs, function (item) { - item.active = false; - }); - data.tabs[activeTabIndex].active = true; - - //the data returned is the up-to-date data so the UI will refresh - deferred.resolve(data); - }, - function (data, status, headers, config) { - //failure callback - - deferred.reject('Failed to save data for media id ' + content.id); - }); - - return deferred.promise; + return umbRequestHelper.postSaveContent(getSaveUrl(content.id), content, action, files); } return { diff --git a/src/Umbraco.Web.UI.Client/src/common/services/utill.service.js b/src/Umbraco.Web.UI.Client/src/common/services/utill.service.js index 5821914796..28b4a1d94d 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/utill.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/utill.service.js @@ -5,8 +5,52 @@ * @name umbraco.services:umbRequestHelper * @description A helper object used for sending requests to the server **/ -function umbRequestHelper($http) { +function umbRequestHelper($http, $q, umbDataFormatter) { return { + + postSaveContent: function (restApiUrl, content, action, files) { + 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) { + return item.active; + }); + var activeTabIndex = (activeTab === undefined ? 0 : _.indexOf(content.tabs, activeTab)); + + //save the data + this.postMultiPartRequest( + restApiUrl, + { key: "contentItem", value: umbDataFormatter.formatContentPostData(content, action) }, + function (data, formData) { + //now add all of the assigned files + for (var f in 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); + } + + }, + function (data, status, headers, config) { + //success callback + + //reset the tabs and set the active one + _.each(data.tabs, function (item) { + item.active = false; + }); + data.tabs[activeTabIndex].active = true; + + //the data returned is the up-to-date data so the UI will refresh + deferred.resolve(data); + }, + function (data, status, headers, config) { + //failure callback + + deferred.reject('Failed to save data for media id ' + content.id); + }); + + return deferred.promise; + }, + /** Posts a multi-part mime request to the server */ postMultiPartRequest: function (url, jsonData, transformCallback, successCallback, failureCallback) { diff --git a/src/Umbraco.Web.UI.Client/src/views/content/contentedit.controller.js b/src/Umbraco.Web.UI.Client/src/views/content/contentedit.controller.js index 7c03acb194..757a2224d0 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/contentedit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/content/contentedit.controller.js @@ -30,13 +30,20 @@ angular.module("umbraco") $scope.saveAndPublish = function (cnt) { cnt.publishDate = new Date(); - contentResource.publishContent(cnt, $routeParams.create, $scope.files); - notificationsService.success("Published", "Content has been saved and published"); + contentResource.publishContent(cnt, $routeParams.create, $scope.files) + .then(function(data) { + $scope.content = data; + notificationsService.success("Published", "Content has been saved and published"); + }); }; $scope.save = function (cnt) { cnt.updateDate = new Date(); - contentResource.saveContent(cnt, $routeParams.create, $scope.files); - notificationsService.success("Saved", "Content has been saved"); + contentResource.saveContent(cnt, $routeParams.create, $scope.files) + .then(function (data) { + $scope.content = data; + notificationsService.success("Saved", "Content has been saved"); + }); + }; }); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/views/media/mediaedit.controller.js b/src/Umbraco.Web.UI.Client/src/views/media/mediaedit.controller.js index dc3c0a9a9c..9d0f0d8af9 100644 --- a/src/Umbraco.Web.UI.Client/src/views/media/mediaedit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/media/mediaedit.controller.js @@ -28,8 +28,11 @@ function mediaEditController($scope, $routeParams, mediaResource, notificationsS $scope.save = function (cnt) { cnt.updateDate = new Date(); - mediaResource.saveMedia(cnt, $routeParams.create, $scope.files); - notificationsService.success("Saved", "Media has been saved"); + mediaResource.saveMedia(cnt, $routeParams.create, $scope.files) + .then(function (data) { + $scope.content = data; + notificationsService.success("Saved", "Media has been saved"); + }); }; } diff --git a/src/Umbraco.Web.UI/App_Plugins/MyPackage/PropertyEditors/FileUploadValueEditor.cs b/src/Umbraco.Web.UI/App_Plugins/MyPackage/PropertyEditors/FileUploadValueEditor.cs index f109aaf2d5..785a108397 100644 --- a/src/Umbraco.Web.UI/App_Plugins/MyPackage/PropertyEditors/FileUploadValueEditor.cs +++ b/src/Umbraco.Web.UI/App_Plugins/MyPackage/PropertyEditors/FileUploadValueEditor.cs @@ -117,6 +117,13 @@ namespace Umbraco.Web.UI.App_Plugins.MyPackage.PropertyEditors //TODO: We need to remove any files that were previously persisted but are no longer persisted. FOr example, if we // uploaded 5 files before and then only uploaded 3, then the last two should be deleted. + //NOTE: We will save a simple string if there is only one media item, this is mostly for backwards compatibility and for + // the current media pickers to work. + if (newValue.Count == 1) + { + return newValue[0]["file"].ToString(); + } + return newValue.ToString(Formatting.None); } } diff --git a/src/Umbraco.Web.UI/App_Plugins/MyPackage/PropertyEditors/Js/FileUploadEditor.js b/src/Umbraco.Web.UI/App_Plugins/MyPackage/PropertyEditors/Js/FileUploadEditor.js index cef520f6c2..b976f7bdeb 100644 --- a/src/Umbraco.Web.UI/App_Plugins/MyPackage/PropertyEditors/Js/FileUploadEditor.js +++ b/src/Umbraco.Web.UI/App_Plugins/MyPackage/PropertyEditors/Js/FileUploadEditor.js @@ -12,7 +12,7 @@ define(['namespaceMgr'], function () { //for legacy data, this will not be an array, just a string so convert to an array if (!$scope.model.value.startsWith('[')) { - $scope.model.value = "[file: '" + $scope.model.value + "']"; + $scope.model.value = "[{\"file\": \"" + $scope.model.value + "\"}]"; } $scope.persistedFiles = _.map(angular.fromJson($scope.model.value), function (item) { diff --git a/src/Umbraco.Web/Editors/MediaController.cs b/src/Umbraco.Web/Editors/MediaController.cs index 2856b97d56..b3c56d77cb 100644 --- a/src/Umbraco.Web/Editors/MediaController.cs +++ b/src/Umbraco.Web/Editors/MediaController.cs @@ -95,8 +95,8 @@ namespace Umbraco.Web.Editors [ContentItemValidationFilter(typeof(ContentItemValidationHelper))] [FileUploadCleanupFilter] public MediaItemDisplay PostSave( - [ModelBinder(typeof(ContentItemBinder))] - ContentItemSave mediaItem) + [ModelBinder(typeof(MediaItemBinder))] + ContentItemSave contentItem) { //If we've reached here it means: // * Our model has been bound @@ -106,16 +106,16 @@ namespace Umbraco.Web.Editors //Now, we just need to save the data - //Save the property values - foreach (var p in mediaItem.ContentDto.Properties) + //Save the property values (for properties that have a valid editor ... not legacy) + foreach (var p in contentItem.ContentDto.Properties.Where(x => x.PropertyEditor != null)) { //get the dbo property - var dboProperty = mediaItem.PersistedContent.Properties[p.Alias]; + var dboProperty = contentItem.PersistedContent.Properties[p.Alias]; //create the property data to send to the property editor var d = new Dictionary(); //add the files if any - var files = mediaItem.UploadedFiles.Where(x => x.PropertyId == p.Id).ToArray(); + var files = contentItem.UploadedFiles.Where(x => x.PropertyId == p.Id).ToArray(); if (files.Any()) { d.Add("files", files); @@ -127,10 +127,10 @@ namespace Umbraco.Web.Editors } //save the item - Services.MediaService.Save(mediaItem.PersistedContent); + Services.MediaService.Save(contentItem.PersistedContent); //return the updated model - return _mediaModelMapper.ToMediaItemDisplay(mediaItem.PersistedContent); + return _mediaModelMapper.ToMediaItemDisplay(contentItem.PersistedContent); } } } diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 5cc169c0a4..2e965d2279 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -434,7 +434,9 @@ + + diff --git a/src/Umbraco.Web/WebApi/Binders/ContentItemBaseBinder.cs b/src/Umbraco.Web/WebApi/Binders/ContentItemBaseBinder.cs new file mode 100644 index 0000000000..f003536e72 --- /dev/null +++ b/src/Umbraco.Web/WebApi/Binders/ContentItemBaseBinder.cs @@ -0,0 +1,152 @@ +using System.IO; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Threading.Tasks; +using System.Web; +using System.Web.Http; +using System.Web.Http.Controllers; +using System.Web.Http.ModelBinding; +using Newtonsoft.Json; +using Umbraco.Core; +using Umbraco.Core.Models; +using Umbraco.Web.Models.ContentEditing; +using Task = System.Threading.Tasks.Task; + +namespace Umbraco.Web.WebApi.Binders +{ + /// + /// Binds the content model to the controller action for the posted multi-part Post + /// + internal abstract class ContentItemBaseBinder : IModelBinder + where TPersisted : IContentBase + { + protected ApplicationContext ApplicationContext { get; private set; } + + /// + /// Constructor + /// + /// + internal ContentItemBaseBinder(ApplicationContext applicationContext) + { + ApplicationContext = applicationContext; + } + + public bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext) + { + //NOTE: Validation is done in the filter + if (actionContext.Request.Content.IsMimeMultipartContent() == false) + { + throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType); + } + + var root = HttpContext.Current.Server.MapPath("~/App_Data/TEMP/FileUploads"); + //ensure it exists + Directory.CreateDirectory(root); + var provider = new MultipartFormDataStreamProvider(root); + + var task = Task.Run(() => GetModel(actionContext.Request, provider)) + .ContinueWith(x => + { + if (x.IsFaulted && x.Exception != null) + { + throw x.Exception; + } + bindingContext.Model = x.Result; + }); + + task.Wait(); + + return bindingContext.Model != null; + } + + /// + /// Builds the model from the request contents + /// + /// + /// + /// + private async Task> GetModel(HttpRequestMessage request, MultipartFormDataStreamProvider provider) + { + //IMPORTANT!!! We need to ensure the umbraco context here because this is running in an async thread + UmbracoContext.EnsureContext(request.Properties["MS_HttpContext"] as HttpContextBase, ApplicationContext.Current); + + var content = request.Content; + + var result = await content.ReadAsMultipartAsync(provider); + + if (result.FormData["contentItem"] == null) + { + throw new HttpResponseException( + new HttpResponseMessage(HttpStatusCode.BadRequest) + { + ReasonPhrase = "The request was not formatted correctly and is missing the 'contentItem' parameter" + }); + } + + //get the string json from the request + var contentItem = result.FormData["contentItem"]; + + //transform the json into an object + var model = JsonConvert.DeserializeObject>(contentItem); + + //get the files + foreach (var file in result.FileData) + { + //The name that has been assigned in JS has 2 parts and the second part indicates the property id + // for which the file belongs. + var parts = file.Headers.ContentDisposition.Name.Trim(new char[] { '\"' }).Split('_'); + if (parts.Length != 2) + { + throw new HttpResponseException( + new HttpResponseMessage(HttpStatusCode.BadRequest) + { + ReasonPhrase = "The request was not formatted correctly the file name's must be underscore delimited" + }); + } + int propertyId; + if (int.TryParse(parts[1], out propertyId) == false) + { + throw new HttpResponseException( + new HttpResponseMessage(HttpStatusCode.BadRequest) + { + ReasonPhrase = "The request was not formatted correctly the file name's 2nd part must be an integer" + }); + } + + var fileName = file.Headers.ContentDisposition.FileName.Trim(new char[] {'\"'}); + + model.UploadedFiles.Add(new ContentItemFile + { + TempFilePath = file.LocalFileName, + PropertyId = propertyId, + FileName = fileName + }); + } + + if (model.Action == ContentSaveAction.Publish || model.Action == ContentSaveAction.Save) + { + //finally, let's lookup the real content item and create the DTO item + model.PersistedContent = GetExisting(model); + } + else + { + //we are creating new content + model.PersistedContent = CreateNew(model); + } + + model.ContentDto = Map(model); + //we will now assign all of the values in the 'save' model to the DTO object + foreach (var p in model.Properties) + { + model.ContentDto.Properties.Single(x => x.Id == p.Id).Value = p.Value; + } + + return model; + } + + protected abstract TPersisted GetExisting(ContentItemSave model); + protected abstract TPersisted CreateNew(ContentItemSave model); + protected abstract ContentItemDto Map(ContentItemSave 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 074dbaf37d..66ce8ec263 100644 --- a/src/Umbraco.Web/WebApi/Binders/ContentItemBinder.cs +++ b/src/Umbraco.Web/WebApi/Binders/ContentItemBinder.cs @@ -1,38 +1,18 @@ using System; -using System.IO; -using System.Linq; -using System.Net; -using System.Net.Http; -using System.Threading.Tasks; -using System.Web; -using System.Web.Http; -using System.Web.Http.Controllers; -using System.Web.Http.ModelBinding; -using Newtonsoft.Json; using Umbraco.Core; using Umbraco.Core.Models; using Umbraco.Web.Models.ContentEditing; using Umbraco.Web.Models.Mapping; -using Task = System.Threading.Tasks.Task; namespace Umbraco.Web.WebApi.Binders { - /// - /// Binds the content model to the controller action for the posted multi-part Post - /// - internal class ContentItemBinder : IModelBinder + internal class ContentItemBinder : ContentItemBaseBinder { - private readonly ApplicationContext _applicationContext; private readonly ContentModelMapper _contentModelMapper; - /// - /// Constructor - /// - /// - /// - internal ContentItemBinder(ApplicationContext applicationContext, ContentModelMapper contentModelMapper) + public ContentItemBinder(ApplicationContext applicationContext, ContentModelMapper contentModelMapper) + : base(applicationContext) { - _applicationContext = applicationContext; _contentModelMapper = contentModelMapper; } @@ -44,122 +24,24 @@ namespace Umbraco.Web.WebApi.Binders { } - public bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext) + protected override IContent GetExisting(ContentItemSave model) { - //NOTE: Validation is done in the filter - if (!actionContext.Request.Content.IsMimeMultipartContent()) - { - throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType); - } - - var root = HttpContext.Current.Server.MapPath("~/App_Data/TEMP/FileUploads"); - //ensure it exists - Directory.CreateDirectory(root); - var provider = new MultipartFormDataStreamProvider(root); - - var task = Task.Run(() => GetModel(actionContext.Request, provider)) - .ContinueWith(x => - { - if (x.IsFaulted && x.Exception != null) - { - throw x.Exception; - } - bindingContext.Model = x.Result; - }); - - task.Wait(); - - return bindingContext.Model != null; + return ApplicationContext.Services.ContentService.GetById(model.Id); } - /// - /// Builds the model from the request contents - /// - /// - /// - /// - private async Task> GetModel(HttpRequestMessage request, MultipartFormDataStreamProvider provider) + protected override IContent CreateNew(ContentItemSave model) { - //IMPORTANT!!! We need to ensure the umbraco context here because this is running in an async thread - UmbracoContext.EnsureContext(request.Properties["MS_HttpContext"] as HttpContextBase, ApplicationContext.Current); - - var content = request.Content; - - var result = await content.ReadAsMultipartAsync(provider); - - if (result.FormData["contentItem"] == null) + var contentType = ApplicationContext.Services.ContentTypeService.GetContentType(model.ContentTypeAlias); + if (contentType == null) { - throw new HttpResponseException( - new HttpResponseMessage(HttpStatusCode.BadRequest) - { - ReasonPhrase = "The request was not formatted correctly and is missing the 'contentItem' parameter" - }); + throw new InvalidOperationException("No content type found wth alias " + model.ContentTypeAlias); } + return new Content(model.Name, model.ParentId, contentType); + } - //get the string json from the request - var contentItem = result.FormData["contentItem"]; - - //transform the json into an object - var model = JsonConvert.DeserializeObject>(contentItem); - - //get the files - foreach (var file in result.FileData) - { - //The name that has been assigned in JS has 2 parts and the second part indicates the property id - // for which the file belongs. - var parts = file.Headers.ContentDisposition.Name.Trim(new char[] { '\"' }).Split('_'); - if (parts.Length != 2) - { - throw new HttpResponseException( - new HttpResponseMessage(HttpStatusCode.BadRequest) - { - ReasonPhrase = "The request was not formatted correctly the file name's must be underscore delimited" - }); - } - int propertyId; - if (!int.TryParse(parts[1], out propertyId)) - { - throw new HttpResponseException( - new HttpResponseMessage(HttpStatusCode.BadRequest) - { - ReasonPhrase = "The request was not formatted correctly the file name's 2nd part must be an integer" - }); - } - - var fileName = file.Headers.ContentDisposition.FileName.Trim(new char[] {'\"'}); - - model.UploadedFiles.Add(new ContentItemFile - { - TempFilePath = file.LocalFileName, - PropertyId = propertyId, - FileName = fileName - }); - } - - if (model.Action == ContentSaveAction.Publish || model.Action == ContentSaveAction.Save) - { - //finally, let's lookup the real content item and create the DTO item - model.PersistedContent = _applicationContext.Services.ContentService.GetById(model.Id); - } - else - { - //we are creating new content - var contentType = _applicationContext.Services.ContentTypeService.GetContentType(model.ContentTypeAlias); - if (contentType == null) - { - throw new InvalidOperationException("No content type found wth alias " + model.ContentTypeAlias); - } - model.PersistedContent = new Content(model.Name, model.ParentId, contentType); - } - - model.ContentDto = _contentModelMapper.ToContentItemDto(model.PersistedContent); - //we will now assign all of the values in the 'save' model to the DTO object - foreach (var p in model.Properties) - { - model.ContentDto.Properties.Single(x => x.Id == p.Id).Value = p.Value; - } - - return model; + protected override ContentItemDto Map(ContentItemSave model) + { + return _contentModelMapper.ToContentItemDto(model.PersistedContent); } } } \ No newline at end of file diff --git a/src/Umbraco.Web/WebApi/Binders/MediaItemBinder.cs b/src/Umbraco.Web/WebApi/Binders/MediaItemBinder.cs new file mode 100644 index 0000000000..a25ef39206 --- /dev/null +++ b/src/Umbraco.Web/WebApi/Binders/MediaItemBinder.cs @@ -0,0 +1,47 @@ +using System; +using Umbraco.Core; +using Umbraco.Core.Models; +using Umbraco.Web.Models.ContentEditing; +using Umbraco.Web.Models.Mapping; + +namespace Umbraco.Web.WebApi.Binders +{ + internal class MediaItemBinder : ContentItemBaseBinder + { + private readonly MediaModelMapper _mediaModelMapper; + + public MediaItemBinder(ApplicationContext applicationContext, MediaModelMapper mediaModelMapper) + : base(applicationContext) + { + _mediaModelMapper = mediaModelMapper; + } + + /// + /// Constructor + /// + public MediaItemBinder() + : this(ApplicationContext.Current, new MediaModelMapper(ApplicationContext.Current, new ProfileModelMapper())) + { + } + + protected override IMedia GetExisting(ContentItemSave model) + { + return ApplicationContext.Services.MediaService.GetById(model.Id); + } + + protected override IMedia CreateNew(ContentItemSave model) + { + var contentType = ApplicationContext.Services.ContentTypeService.GetMediaType(model.ContentTypeAlias); + if (contentType == null) + { + throw new InvalidOperationException("No content type found wth alias " + model.ContentTypeAlias); + } + return new Core.Models.Media(model.Name, model.ParentId, contentType); + } + + protected override ContentItemDto Map(ContentItemSave model) + { + return _mediaModelMapper.ToMediaItemDto(model.PersistedContent); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/WebApi/Filters/ContentItemValidationFilterAttribute.cs b/src/Umbraco.Web/WebApi/Filters/ContentItemValidationFilterAttribute.cs index 37dde4c8fd..3069df8139 100644 --- a/src/Umbraco.Web/WebApi/Filters/ContentItemValidationFilterAttribute.cs +++ b/src/Umbraco.Web/WebApi/Filters/ContentItemValidationFilterAttribute.cs @@ -6,6 +6,7 @@ using System.Net.Http; using System.Web.Http.Controllers; using System.Web.Http.Filters; using Umbraco.Core; +using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.PropertyEditors; using Umbraco.Web.Models.ContentEditing; @@ -96,8 +97,10 @@ namespace Umbraco.Web.WebApi.Filters if (editor == null) { var message = string.Format("The property editor with id: {0} was not found for property with id {1}", p.DataType.ControlId, p.Id); - actionContext.Response = actionContext.Request.CreateErrorResponse(HttpStatusCode.NotFound, message); - return false; + LogHelper.Warn>(message); + //actionContext.Response = actionContext.Request.CreateErrorResponse(HttpStatusCode.NotFound, message); + //return false; + continue; } //get the posted value for this property