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:
Shannon Deminick
2013-06-05 14:37:19 -10:00
parent 406edc4aab
commit 96fac60c3f
9 changed files with 196 additions and 18 deletions

View File

@@ -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()));

View File

@@ -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

View File

@@ -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");
};

View File

@@ -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;
}
};

View File

@@ -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

View File

@@ -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>

View 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
}
}

View File

@@ -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" />

View File

@@ -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)