So close to having data saving but now we have issues with HttpContext.Current because we are running async processes... fail.
This commit is contained in:
@@ -11,9 +11,9 @@ namespace Umbraco.Web.UI.App_Plugins.MyPackage.PropertyEditors
|
||||
{
|
||||
protected override ValueEditor CreateValueEditor()
|
||||
{
|
||||
if (HttpContext.Current == null)
|
||||
if (UmbracoContext.Current == null || UmbracoContext.Current.HttpContext == null)
|
||||
{
|
||||
throw new InvalidOperationException("This property editor only works in a web context");
|
||||
throw new InvalidOperationException("This property editor only works in an umbraco web context");
|
||||
}
|
||||
|
||||
var urlHelper = new UrlHelper(new RequestContext(new HttpContextWrapper(HttpContext.Current), new RouteData()));
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
@using Umbraco.Core
|
||||
@using System.Web.Mvc.Html
|
||||
@using Umbraco.Core
|
||||
@using ClientDependency.Core
|
||||
@using ClientDependency.Core.Mvc
|
||||
@inherits System.Web.Mvc.WebViewPage
|
||||
@{
|
||||
Layout = null;
|
||||
@@ -18,6 +21,8 @@
|
||||
|
||||
@*Currently this needs to be loaded before anything*@
|
||||
<script src="@Url.Action("ServerVariables", "BackOffice")" type="text/javascript"></script>
|
||||
|
||||
|
||||
|
||||
<style type="text/css">
|
||||
.validation-message
|
||||
|
||||
@@ -177,15 +177,18 @@ angular.module("umbraco").controller("Umbraco.Editors.ContentEditController", fu
|
||||
}
|
||||
|
||||
$scope.saveAndPublish = function (cnt) {
|
||||
cnt.publishDate = new Date();
|
||||
contentResource.publishContent(cnt);
|
||||
|
||||
notifications.success("Published", "Content has been saved and published");
|
||||
|
||||
contentResource.publishContent(cnt)
|
||||
.then(function (data) {
|
||||
//now we need to re-set the content model since the server will have updated it
|
||||
$scope.content = data;
|
||||
notifications.success("Published", "Content has been saved and published");
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
$scope.save = function (cnt) {
|
||||
cnt.updateDate = new Date();
|
||||
|
||||
|
||||
contentResource.saveContent(cnt);
|
||||
notifications.success("Saved", "Content has been saved");
|
||||
};
|
||||
|
||||
@@ -129,14 +129,18 @@ define(['app', 'angular'], function (app, angular) {
|
||||
/**
|
||||
* @ngdoc factory
|
||||
* @name umbraco.resources.contentResource
|
||||
* @description Loads in data for content
|
||||
* @description Loads/saves in data for content
|
||||
**/
|
||||
function contentResource($q, $http) {
|
||||
function contentResource($q, $http, umbDataFormatter, umbRequestHelper) {
|
||||
|
||||
/** internal method to get the api url */
|
||||
function getContentUrl(contentId) {
|
||||
return Umbraco.Sys.ServerVariables.contentEditorApiBaseUrl + "GetContent?id=" + contentId;
|
||||
}
|
||||
/** internal method to get the api url for publishing */
|
||||
function getSaveUrl() {
|
||||
return Umbraco.Sys.ServerVariables.contentEditorApiBaseUrl + "PostSaveContent";
|
||||
}
|
||||
|
||||
return {
|
||||
getContent: function (id) {
|
||||
@@ -271,13 +275,58 @@ define(['app', 'angular'], function (app, angular) {
|
||||
return collection;
|
||||
},
|
||||
|
||||
//saves or updates a content object
|
||||
saveContent: function (content) {
|
||||
alert("Saved: " + JSON.stringify(content));
|
||||
/** saves or updates a content object */
|
||||
saveContent: function (content) {
|
||||
|
||||
var deferred = $q.defer();
|
||||
|
||||
//save the data
|
||||
umbRequestHelper.postMultiPartRequest(
|
||||
getSaveUrl(content.id),
|
||||
umbDataFormatter.formatContentPostData(content, "save"),
|
||||
function (data) {
|
||||
//TODO: transform the request callback and add the files associated with the request
|
||||
},
|
||||
function (data, status, headers, config) {
|
||||
//success callback
|
||||
|
||||
//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;
|
||||
},
|
||||
|
||||
/** saves and publishes a content object */
|
||||
publishContent: function (content) {
|
||||
alert("Published: " + JSON.stringify(content));
|
||||
|
||||
var deferred = $q.defer();
|
||||
|
||||
//save the data
|
||||
umbRequestHelper.postMultiPartRequest(
|
||||
getSaveUrl(content.id),
|
||||
{ key: "contentItem", value: umbDataFormatter.formatContentPostData(content, "publish") },
|
||||
function (data) {
|
||||
//TODO: transform the request callback and add the files associated with the request
|
||||
},
|
||||
function (data, status, headers, config) {
|
||||
//success callback
|
||||
|
||||
//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;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
@@ -1,6 +1,101 @@
|
||||
'use strict';
|
||||
define(['app', 'angular', 'underscore'], function (app, angular, underscore) {
|
||||
|
||||
/**
|
||||
* @ngdoc factory
|
||||
* @name umbraco.services:umbRequestHelper
|
||||
* @description A helper object used for sending requests to the server
|
||||
**/
|
||||
function umbRequestHelper($http) {
|
||||
return {
|
||||
/** Posts a multi-part mime request to the server */
|
||||
postMultiPartRequest: function (url, jsonData, transformCallback, successCallback, failureCallback) {
|
||||
|
||||
//validate input, jsonData can be an array of key/value pairs or just one key/value pair.
|
||||
if (!jsonData) throw "jsonData cannot be null";
|
||||
if (angular.isArray(jsonData)) {
|
||||
_.each(jsonData, function (item) {
|
||||
if (!item.key || !item.value) throw "jsonData array item must have both a key and a value property";
|
||||
})
|
||||
}
|
||||
else if (!jsonData.key || !jsonData.value) throw "jsonData object must have both a key and a value property";
|
||||
|
||||
|
||||
$http({
|
||||
method: 'POST',
|
||||
url: url,
|
||||
//IMPORTANT!!! You might think this should be set to 'multipart/form-data' but this is not true because when we are sending up files
|
||||
// the request needs to include a 'boundary' parameter which identifies the boundary name between parts in this multi-part request
|
||||
// and setting the Content-type manually will not set this boundary parameter. For whatever reason, setting the Content-type to 'false'
|
||||
// will force the request to automatically populate the headers properly including the boundary parameter.
|
||||
headers: { 'Content-Type': false },
|
||||
transformRequest: function (data) {
|
||||
var formData = new FormData();
|
||||
//add the json data
|
||||
if (angular.isArray(data)) {
|
||||
_.each(data, function (item) {
|
||||
formData.append(item.key, !angular.isString(item.value) ? angular.toJson(item.value) : item.value);
|
||||
});
|
||||
}
|
||||
else {
|
||||
formData.append(data.key, !angular.isString(data.value) ? angular.toJson(data.value) : data.value);
|
||||
}
|
||||
|
||||
//call the callback
|
||||
if (transformCallback) {
|
||||
transformCallback.apply(this, [formData]);
|
||||
}
|
||||
|
||||
return formData;
|
||||
},
|
||||
data: jsonData
|
||||
}).
|
||||
success(function (data, status, headers, config) {
|
||||
if (successCallback) {
|
||||
successCallback.apply(this, [data, status, headers, config]);
|
||||
}
|
||||
}).
|
||||
error(function (data, status, headers, config) {
|
||||
if (failureCallback) {
|
||||
failureCallback.apply(this, [data, status, headers, config]);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
angular.module('umbraco').factory('umbRequestHelper', umbRequestHelper);
|
||||
|
||||
/**
|
||||
* @ngdoc factory
|
||||
* @name umbraco.services:umbDataFormatter
|
||||
* @description A helper object used to format/transform JSON Umbraco data, mostly used for persisting data to the server
|
||||
**/
|
||||
function umbDataFormatter() {
|
||||
return {
|
||||
/** formats the display model used to display the content to the model used to save the content */
|
||||
formatContentPostData: 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: [],
|
||||
//set the action on the save model
|
||||
action: action
|
||||
};
|
||||
_.each(displayModel.tabs, function(tab) {
|
||||
_.each(tab.properties, function (prop) {
|
||||
saveModel.properties.push({
|
||||
id: prop.id,
|
||||
value: prop.value
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
return saveModel;
|
||||
}
|
||||
};
|
||||
}
|
||||
angular.module('umbraco').factory('umbDataFormatter', umbDataFormatter);
|
||||
|
||||
/**
|
||||
* @ngdoc factory
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.Serialization;
|
||||
using Newtonsoft.Json;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Umbraco.Web.Models.ContentEditing
|
||||
{
|
||||
@@ -13,6 +15,13 @@ namespace Umbraco.Web.Models.ContentEditing
|
||||
UploadedFiles = new List<ContentItemFile>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The action to perform when saving this content item
|
||||
/// </summary>
|
||||
[DataMember(Name = "action", IsRequired = true)]
|
||||
[Required]
|
||||
public ContentSaveAction Action { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The collection of files uploaded
|
||||
/// </summary>
|
||||
|
||||
11
src/Umbraco.Web/Models/ContentEditing/ContentSaveAction.cs
Normal file
11
src/Umbraco.Web/Models/ContentEditing/ContentSaveAction.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
namespace Umbraco.Web.Models.ContentEditing
|
||||
{
|
||||
/// <summary>
|
||||
/// The action associated with saving a content item
|
||||
/// </summary>
|
||||
public enum ContentSaveAction
|
||||
{
|
||||
Save,
|
||||
Publish
|
||||
}
|
||||
}
|
||||
@@ -293,6 +293,7 @@
|
||||
<Compile Include="Cache\UserTypeCacheRefresher.cs" />
|
||||
<Compile Include="Configuration\WebRouting.cs" />
|
||||
<Compile Include="Editors\ContentTypeApiController.cs" />
|
||||
<Compile Include="Models\ContentEditing\ContentSaveAction.cs" />
|
||||
<Compile Include="Models\ContentEditing\ContentTypeBasic.cs" />
|
||||
<Compile Include="Models\ContentEditing\Tab.cs" />
|
||||
<Compile Include="Models\ContentEditing\UserBasic.cs" />
|
||||
|
||||
@@ -54,7 +54,7 @@ namespace Umbraco.Web.WebApi.Binders
|
||||
Directory.CreateDirectory(root);
|
||||
var provider = new MultipartFormDataStreamProvider(root);
|
||||
|
||||
var task = Task.Run(() => GetModel(actionContext.Request.Content, provider))
|
||||
var task = Task.Run(() => GetModel(actionContext.Request, provider))
|
||||
.ContinueWith(x =>
|
||||
{
|
||||
if (x.IsFaulted && x.Exception != null)
|
||||
@@ -72,11 +72,16 @@ namespace Umbraco.Web.WebApi.Binders
|
||||
/// <summary>
|
||||
/// Builds the model from the request contents
|
||||
/// </summary>
|
||||
/// <param name="content"></param>
|
||||
/// <param name="request"></param>
|
||||
/// <param name="provider"></param>
|
||||
/// <returns></returns>
|
||||
private async Task<ContentItemSave> GetModel(HttpContent content, MultipartFormDataStreamProvider provider)
|
||||
private async Task<ContentItemSave> 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)
|
||||
|
||||
Reference in New Issue
Block a user