@@ -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 {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
|
||||
@@ -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");
|
||||
});
|
||||
|
||||
};
|
||||
});
|
||||
@@ -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");
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -95,8 +95,8 @@ namespace Umbraco.Web.Editors
|
||||
[ContentItemValidationFilter(typeof(ContentItemValidationHelper<IMedia>))]
|
||||
[FileUploadCleanupFilter]
|
||||
public MediaItemDisplay PostSave(
|
||||
[ModelBinder(typeof(ContentItemBinder))]
|
||||
ContentItemSave<IMedia> mediaItem)
|
||||
[ModelBinder(typeof(MediaItemBinder))]
|
||||
ContentItemSave<IMedia> 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<string, object>();
|
||||
//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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -434,7 +434,9 @@
|
||||
</Compile>
|
||||
<Compile Include="UrlHelperExtensions.cs" />
|
||||
<Compile Include="Editors\MediaController.cs" />
|
||||
<Compile Include="WebApi\Binders\ContentItemBaseBinder.cs" />
|
||||
<Compile Include="WebApi\Binders\ContentItemBinder.cs" />
|
||||
<Compile Include="WebApi\Binders\MediaItemBinder.cs" />
|
||||
<Compile Include="WebApi\Filters\ContentItemValidationFilterAttribute.cs" />
|
||||
<Compile Include="WebApi\Filters\FileUploadCleanupFilterAttribute.cs" />
|
||||
<Compile Include="WebApi\Filters\HttpQueryStringFilterAttribute.cs" />
|
||||
|
||||
152
src/Umbraco.Web/WebApi/Binders/ContentItemBaseBinder.cs
Normal file
152
src/Umbraco.Web/WebApi/Binders/ContentItemBaseBinder.cs
Normal file
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// Binds the content model to the controller action for the posted multi-part Post
|
||||
/// </summary>
|
||||
internal abstract class ContentItemBaseBinder<TPersisted> : IModelBinder
|
||||
where TPersisted : IContentBase
|
||||
{
|
||||
protected ApplicationContext ApplicationContext { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
/// <param name="applicationContext"></param>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Builds the model from the request contents
|
||||
/// </summary>
|
||||
/// <param name="request"></param>
|
||||
/// <param name="provider"></param>
|
||||
/// <returns></returns>
|
||||
private async Task<ContentItemSave<TPersisted>> 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<ContentItemSave<TPersisted>>(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<TPersisted> model);
|
||||
protected abstract TPersisted CreateNew(ContentItemSave<TPersisted> model);
|
||||
protected abstract ContentItemDto<TPersisted> Map(ContentItemSave<TPersisted> model);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// Binds the content model to the controller action for the posted multi-part Post
|
||||
/// </summary>
|
||||
internal class ContentItemBinder : IModelBinder
|
||||
internal class ContentItemBinder : ContentItemBaseBinder<IContent>
|
||||
{
|
||||
private readonly ApplicationContext _applicationContext;
|
||||
private readonly ContentModelMapper _contentModelMapper;
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
/// <param name="applicationContext"></param>
|
||||
/// <param name="contentModelMapper"></param>
|
||||
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<IContent> 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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Builds the model from the request contents
|
||||
/// </summary>
|
||||
/// <param name="request"></param>
|
||||
/// <param name="provider"></param>
|
||||
/// <returns></returns>
|
||||
private async Task<ContentItemSave<IContent>> GetModel(HttpRequestMessage request, MultipartFormDataStreamProvider provider)
|
||||
protected override IContent CreateNew(ContentItemSave<IContent> 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<ContentItemSave<IContent>>(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<IContent> Map(ContentItemSave<IContent> model)
|
||||
{
|
||||
return _contentModelMapper.ToContentItemDto(model.PersistedContent);
|
||||
}
|
||||
}
|
||||
}
|
||||
47
src/Umbraco.Web/WebApi/Binders/MediaItemBinder.cs
Normal file
47
src/Umbraco.Web/WebApi/Binders/MediaItemBinder.cs
Normal file
@@ -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<IMedia>
|
||||
{
|
||||
private readonly MediaModelMapper _mediaModelMapper;
|
||||
|
||||
public MediaItemBinder(ApplicationContext applicationContext, MediaModelMapper mediaModelMapper)
|
||||
: base(applicationContext)
|
||||
{
|
||||
_mediaModelMapper = mediaModelMapper;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
public MediaItemBinder()
|
||||
: this(ApplicationContext.Current, new MediaModelMapper(ApplicationContext.Current, new ProfileModelMapper()))
|
||||
{
|
||||
}
|
||||
|
||||
protected override IMedia GetExisting(ContentItemSave<IMedia> model)
|
||||
{
|
||||
return ApplicationContext.Services.MediaService.GetById(model.Id);
|
||||
}
|
||||
|
||||
protected override IMedia CreateNew(ContentItemSave<IMedia> 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<IMedia> Map(ContentItemSave<IMedia> model)
|
||||
{
|
||||
return _mediaModelMapper.ToMediaItemDto(model.PersistedContent);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<ContentItemValidationHelper<TPersisted>>(message);
|
||||
//actionContext.Response = actionContext.Request.CreateErrorResponse(HttpStatusCode.NotFound, message);
|
||||
//return false;
|
||||
continue;
|
||||
}
|
||||
|
||||
//get the posted value for this property
|
||||
|
||||
Reference in New Issue
Block a user