Gets all list views working correctly with content apps, fixes various issues, cleans up a bunch of code and reduces amount of service locator.

This commit is contained in:
Shannon
2018-07-17 14:23:07 +10:00
parent 45f959fb6b
commit e9752cd5e1
25 changed files with 189 additions and 230 deletions

View File

@@ -205,8 +205,6 @@ namespace Umbraco.Core.Migrations.Install
{
_database.Insert(Constants.DatabaseSchema.Tables.PropertyTypeGroup, "id", false, new PropertyTypeGroupDto { Id = 3, ContentTypeNodeId = 1032, Text = "Image", SortOrder = 1, UniqueId = new Guid(Constants.PropertyTypeGroups.Image) });
_database.Insert(Constants.DatabaseSchema.Tables.PropertyTypeGroup, "id", false, new PropertyTypeGroupDto { Id = 4, ContentTypeNodeId = 1033, Text = "File", SortOrder = 1, UniqueId = new Guid(Constants.PropertyTypeGroups.File) });
//TODO: Need a migration to remove this
//_database.Insert(Constants.DatabaseSchema.Tables.PropertyTypeGroup, "id", false, new PropertyTypeGroupDto { Id = 5, ContentTypeNodeId = 1031, Text = "Contents", SortOrder = 1, UniqueId = new Guid(Constants.PropertyTypeGroups.Contents) });
//membership property group
_database.Insert(Constants.DatabaseSchema.Tables.PropertyTypeGroup, "id", false, new PropertyTypeGroupDto { Id = 11, ContentTypeNodeId = 1044, Text = "Membership", SortOrder = 1, UniqueId = new Guid(Constants.PropertyTypeGroups.Membership) });
}
@@ -221,8 +219,6 @@ namespace Umbraco.Core.Migrations.Install
_database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 24, UniqueId = 24.ToGuid(), DataTypeId = -90, ContentTypeId = 1033, PropertyTypeGroupId = 4, Alias = Constants.Conventions.Media.File, Name = "Upload file", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.Nothing });
_database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 25, UniqueId = 25.ToGuid(), DataTypeId = -92, ContentTypeId = 1033, PropertyTypeGroupId = 4, Alias = Constants.Conventions.Media.Extension, Name = "Type", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.Nothing });
_database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 26, UniqueId = 26.ToGuid(), DataTypeId = Constants.DataTypes.LabelBigint, ContentTypeId = 1033, PropertyTypeGroupId = 4, Alias = Constants.Conventions.Media.Bytes, Name = "Size", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.Nothing });
//TODO: Need a migration to remove this
//_database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 27, UniqueId = 27.ToGuid(), DataTypeId = Constants.DataTypes.DefaultMediaListView, ContentTypeId = 1031, PropertyTypeGroupId = 5, Alias = "contents", Name = "Contents:", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.Nothing });
//membership property types
_database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 28, UniqueId = 28.ToGuid(), DataTypeId = -89, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Constants.Conventions.Member.Comments, Name = Constants.Conventions.Member.CommentsLabel, SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.Nothing });
_database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 29, UniqueId = 29.ToGuid(), DataTypeId = Constants.DataTypes.LabelInt, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Constants.Conventions.Member.FailedPasswordAttempts, Name = Constants.Conventions.Member.FailedPasswordAttemptsLabel, SortOrder = 1, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.Nothing });

View File

@@ -389,6 +389,8 @@ namespace Umbraco.Examine
//the value of the field 'as-is'.
foreach (var value in e.IndexItem.ValueSet.Values.ToList()) //ToList here to make a diff collection else we'll get collection modified errors
{
if (value.Value == null) continue;
if (value.Value.Count > 0)
{
if (value.Value.First() is string str)

View File

@@ -113,9 +113,6 @@ function valFormManager(serverValidationManager, $rootScope, $log, $timeout, not
var nextPath = nextLocation.split("#")[1];
//TODO: We need to check for the query strings that cause the route, if they are just the
//nonRoutingQueryStrings when we shouldn't show the confirm route change
if (nextPath) {
if (navigationService.isRouteChangingNavigation(currentLocation, nextLocation)) {

View File

@@ -22,18 +22,9 @@ function ContentRecycleBinController($scope, $routeParams, contentResource, navi
$routeParams.id = "-20";
contentResource.getRecycleBin().then(function (result) {
//we'll get the 'content item' for the recycle bin, we know that it will contain a single tab and a
// single property, so we'll extract that property (list view) and use it's data.
var listproperty = result.tabs[0].properties[0];
_.each(listproperty.config, function (val, key) {
$scope.model.config[key] = val;
});
$scope.listViewPath = 'views/propertyeditors/listview/listview.html';
$scope.content = result;
});
$scope.model = { config: { entityType: $routeParams.section, layouts: [] } };
// sync tree node
navigationService.syncTree({ tree: "content", path: ["-1", $routeParams.id], forceReload: false });

View File

@@ -1,19 +1,20 @@
<umb-editor-view>
<form ng-controller="Umbraco.Editors.Content.RecycleBinController">
<form ng-controller="Umbraco.Editors.Content.RecycleBinController" val-form-manager>
<umb-editor-header
name="page.name"
name-locked="page.nameLocked"
hide-icon="true"
hide-description="true"
hide-alias="true">
<umb-editor-header name="page.name"
name-locked="page.nameLocked"
hide-icon="true"
hide-description="true"
hide-alias="true">
</umb-editor-header>
<umb-editor-container>
<ng-include
src="listViewPath">
</ng-include>
<div class="umb-editor-sub-views">
<div id="sub-view-{{$index}}" ng-repeat="app in content.apps track by app.alias">
<umb-editor-sub-view model="app" content="content" />
</div>
</div>
</umb-editor-container>
</form>
</umb-editor-view>
</umb-editor-view>

View File

@@ -22,18 +22,9 @@ function MediaRecycleBinController($scope, $routeParams, mediaResource, navigati
$routeParams.id = "-21";
mediaResource.getRecycleBin().then(function (result) {
//we'll get the 'content item' for the recycle bin, we know that it will contain a single tab and a
// single property, so we'll extract that property (list view) and use it's data.
var listproperty = result.tabs[0].properties[0];
_.each(listproperty.config, function (val, key) {
$scope.model.config[key] = val;
});
$scope.listViewPath = 'views/propertyeditors/listview/listview.html';
$scope.content = result;
});
$scope.model = { config: { entityType: $routeParams.section, layouts: [] } };
// sync tree node
navigationService.syncTree({ tree: "media", path: ["-1", $routeParams.id], forceReload: false });

View File

@@ -1,16 +1,20 @@
<umb-editor-view>
<form ng-controller="Umbraco.Editors.Media.RecycleBinController">
<umb-editor-header
name="page.name"
name-locked="page.nameLocked"
hide-icon="true"
hide-description="true"
hide-alias="true">
<form ng-controller="Umbraco.Editors.Media.RecycleBinController" val-form-manager>
<umb-editor-header name="page.name"
name-locked="page.nameLocked"
hide-icon="true"
hide-description="true"
hide-alias="true">
</umb-editor-header>
<umb-editor-container>
<div ng-include="listViewPath"></div>
<div class="umb-editor-sub-views">
<div id="sub-view-{{$index}}" ng-repeat="app in content.apps track by app.alias">
<umb-editor-sub-view model="app" content="content" />
</div>
</div>
</umb-editor-container>
</form>

View File

@@ -4,33 +4,26 @@
<form novalidate name="contentForm" ng-show="!page.loading" val-form-manager>
<umb-editor-view umb-tabs>
<umb-editor-view umb-tabs>
<umb-editor-header
name="content.name"
name-locked="page.lockedName"
tabs="content.tabs"
menu="page.menu"
hide-icon="true"
hide-description="true"
hide-alias="true">
</umb-editor-header>
<umb-editor-header name="content.name"
name-locked="page.lockedName"
tabs="content.tabs"
menu="page.menu"
hide-icon="true"
hide-description="true"
hide-alias="true">
</umb-editor-header>
<umb-editor-container>
<umb-editor-container>
<div class="umb-editor-sub-views">
<div id="sub-view-{{$index}}" ng-repeat="app in content.apps track by app.alias">
<umb-editor-sub-view model="app" content="content" />
</div>
</div>
</umb-editor-container>
<umb-tabs-content view="true" class="form-horizontal">
<umb-tab id="tab{{tab.id}}" rel="{{tab.id}}" ng-repeat="tab in content.tabs">
<umb-property property="property" ng-repeat="property in tab.properties">
<umb-property-editor model="property"></umb-property-editor>
</umb-property>
</umb-tab>
</umb-tabs-content>
</umb-editor-container>
</umb-editor-view>
</umb-editor-view>
</form>

View File

@@ -0,0 +1,7 @@
<div>
<umb-property ng-repeat="property in model.viewModel track by property.alias" property="property">
<umb-property-editor model="property"></umb-property-editor>
</umb-property>
</div>

View File

@@ -10,7 +10,7 @@
<umb-editor-sub-header-content-left>
<umb-editor-sub-header-section ng-if="(listViewAllowedTypes && listViewAllowedTypes.length > 0 && !isAnythingSelected()) && currentNodePermissions.canCreate">
<umb-editor-sub-header-section ng-if="(listViewAllowedTypes && listViewAllowedTypes.length > 0 && !isAnythingSelected()) && (entityType != 'content' || currentNodePermissions.canCreate)">
<div class="btn-group" ng-show="listViewAllowedTypes.length > 1">
<a class="btn btn-success dropdown-toggle" ng-click="page.createDropdownOpen = !page.createDropdownOpen" ng-href="">
<localize key="actions_create">Create</localize>

View File

@@ -30,6 +30,7 @@ using Umbraco.Web._Legacy.Actions;
using Constants = Umbraco.Core.Constants;
using ContentVariation = Umbraco.Core.Models.ContentVariation;
using Language = Umbraco.Web.Models.ContentEditing.Language;
using Umbraco.Core.PropertyEditors;
namespace Umbraco.Web.Editors
{
@@ -46,11 +47,13 @@ namespace Umbraco.Web.Editors
public class ContentController : ContentControllerBase
{
private readonly IPublishedSnapshotService _publishedSnapshotService;
private readonly PropertyEditorCollection _propertyEditors;
public ContentController(IPublishedSnapshotService publishedSnapshotService)
public ContentController(IPublishedSnapshotService publishedSnapshotService, PropertyEditorCollection propertyEditors)
{
if (publishedSnapshotService == null) throw new ArgumentNullException(nameof(publishedSnapshotService));
_publishedSnapshotService = publishedSnapshotService;
_propertyEditors = propertyEditors ?? throw new ArgumentNullException(nameof(propertyEditors));
}
/// <summary>
@@ -215,14 +218,14 @@ namespace Umbraco.Web.Editors
/// <returns></returns>
public ContentItemDisplay GetRecycleBin()
{
var apps = new List<ContentApp>();
apps.AppendListViewApp(Services.DataTypeService, _propertyEditors, "recycleBin", "content");
apps[0].Active = true;
var display = new ContentItemDisplay
{
Id = Constants.System.RecycleBinContent,
//Alias = "recycleBin",
ParentId = -1,
//Name = Services.TextService.Localize("general/recycleBin"),
ContentTypeAlias = "recycleBin",
//CreateDate = DateTime.Now,
IsContainer = true,
Path = "-1," + Constants.System.RecycleBinContent,
ContentVariants = new List<ContentVariantDisplay>
@@ -232,12 +235,10 @@ namespace Umbraco.Web.Editors
CreateDate = DateTime.Now,
Name = Services.TextService.Localize("general/recycleBin")
}
}
},
ContentApps = apps
};
//TODO: Change this over to use "Content Apps"
TabsAndPropertiesResolver.AddListView(display.ContentVariants.First(), "content", "recycleBin", Services.DataTypeService, Services.TextService);
return display;
}

View File

@@ -34,6 +34,7 @@ using Umbraco.Core.Persistence;
using Umbraco.Core.Configuration.UmbracoSettings;
using Umbraco.Core.Models.Editors;
using Umbraco.Core.Models.Validation;
using Umbraco.Core.PropertyEditors;
namespace Umbraco.Web.Editors
{
@@ -46,6 +47,11 @@ namespace Umbraco.Web.Editors
[MediaControllerControllerConfiguration]
public class MediaController : ContentControllerBase
{
public MediaController(PropertyEditorCollection propertyEditors)
{
_propertyEditors = propertyEditors ?? throw new ArgumentNullException(nameof(propertyEditors));
}
/// <summary>
/// Configures this controller with a custom action selector
/// </summary>
@@ -89,6 +95,9 @@ namespace Umbraco.Web.Editors
/// <returns></returns>
public MediaItemDisplay GetRecycleBin()
{
var apps = new List<ContentApp>();
apps.AppendListViewApp(Services.DataTypeService, _propertyEditors, "recycleBin", "media");
apps[0].Active = true;
var display = new MediaItemDisplay
{
Id = Constants.System.RecycleBinMedia,
@@ -98,12 +107,10 @@ namespace Umbraco.Web.Editors
ContentTypeAlias = "recycleBin",
CreateDate = DateTime.Now,
IsContainer = true,
Path = "-1," + Constants.System.RecycleBinMedia
Path = "-1," + Constants.System.RecycleBinMedia,
ContentApps = apps
};
//TODO: Change this over to use "Content Apps"
TabsAndPropertiesResolver.AddListView(display, "media", "recycleBin", Services.DataTypeService, Services.TextService);
return display;
}
@@ -222,6 +229,8 @@ namespace Umbraco.Web.Editors
#region GetChildren
private int[] _userStartNodes;
private readonly PropertyEditorCollection _propertyEditors;
protected int[] UserStartNodes
{
get { return _userStartNodes ?? (_userStartNodes = Security.CurrentUser.CalculateMediaStartNodeIds(Services.EntityService)); }

View File

@@ -26,6 +26,8 @@ using Umbraco.Web.Mvc;
using Umbraco.Web.WebApi.Binders;
using Umbraco.Web.WebApi.Filters;
using Constants = Umbraco.Core.Constants;
using System.Collections.Generic;
using Umbraco.Core.PropertyEditors;
namespace Umbraco.Web.Editors
{
@@ -38,7 +40,13 @@ namespace Umbraco.Web.Editors
[OutgoingNoHyphenGuidFormat]
public class MemberController : ContentControllerBase
{
public MemberController(PropertyEditorCollection propertyEditors)
{
_propertyEditors = propertyEditors ?? throw new ArgumentNullException(nameof(propertyEditors));
}
private readonly MembershipProvider _provider = Core.Security.MembershipProviderExtensions.GetMembersMembershipProvider();
private readonly PropertyEditorCollection _propertyEditors;
/// <summary>
/// Returns the currently configured membership scenario for members in umbraco
@@ -127,6 +135,10 @@ namespace Umbraco.Web.Editors
var foundType = Services.MemberTypeService.Get(listName);
var name = foundType != null ? foundType.Name : listName;
var apps = new List<ContentApp>();
apps.AppendListViewApp(Services.DataTypeService, _propertyEditors, listName, "member");
apps[0].Active = true;
var display = new MemberListDisplay
{
ContentTypeAlias = listName,
@@ -135,12 +147,10 @@ namespace Umbraco.Web.Editors
IsContainer = true,
Name = listName == Constants.Conventions.MemberTypes.AllMembersListId ? "All Members" : name,
Path = "-1," + listName,
ParentId = -1
ParentId = -1,
ContentApps = apps
};
//TODO: Change this over to use "Content Apps"
TabsAndPropertiesResolver.AddListView(display, "member", listName, Services.DataTypeService, Services.TextService);
return display;
}

View File

@@ -25,6 +25,12 @@ namespace Umbraco.Web.Models.ContentEditing
/// </summary>
[DataMember(Name = "viewModel")]
public object ViewModel { get; set; }
/// <summary>
/// Normally reserved for Angular to deal with but in some cases this can be set on the server side
/// </summary>
[DataMember(Name = "active")]
public bool Active { get; set; }
}
}

View File

@@ -1,4 +1,5 @@
using System.Runtime.Serialization;
using System.Collections.Generic;
using System.Runtime.Serialization;
using Umbraco.Core.Models;
namespace Umbraco.Web.Models.ContentEditing
@@ -9,5 +10,7 @@ namespace Umbraco.Web.Models.ContentEditing
[DataContract(Name = "content", Namespace = "")]
public class MemberListDisplay : ContentItemDisplayBase<ContentPropertyDisplay, IMember>
{
[DataMember(Name = "apps")]
public IEnumerable<ContentApp> ContentApps { get; set; }
}
}

View File

@@ -47,7 +47,7 @@ namespace Umbraco.Web.Models.Mapping
if (source.ContentType.IsContainer)
{
//If it's a container then add the list view app and view model
apps.Add(this.CreateListViewApp(_dataTypeService, _propertyEditorCollection, source.ContentType.Alias, "content"));
apps.AppendListViewApp(_dataTypeService, _propertyEditorCollection, source.ContentType.Alias, "content");
}
return apps;

View File

@@ -11,17 +11,13 @@ namespace Umbraco.Web.Models.Mapping
internal static class ContentAppResolverExtensions
{
/// <summary>
/// Extension method to create a list view app for the content app collection
/// Helper method to append a list view app to the content app collection
/// </summary>
/// <typeparam name="TContent"></typeparam>
/// <typeparam name="TDisplay"></typeparam>
/// <param name="resolver"></param>
public static ContentApp CreateListViewApp<TContent, TDisplay>(
this IValueResolver<TContent, TDisplay, IEnumerable<ContentApp>> resolver,
public static void AppendListViewApp(
this ICollection<ContentApp> list,
IDataTypeService dataTypeService, PropertyEditorCollection propertyEditors,
string contentTypeAlias,
string entityType)
where TContent: IContentBase
string contentTypeAlias, string entityType)
{
var listViewApp = new ContentApp
{
@@ -74,7 +70,7 @@ namespace Umbraco.Web.Models.Mapping
}
};
return listViewApp;
list.Add(listViewApp);
}
}

View File

@@ -39,9 +39,9 @@ namespace Umbraco.Web.Models.Mapping
{
var apps = new List<ContentApp>();
if (source.ContentType.IsContainer || source.ContentType.Alias == Umbraco.Core.Constants.Conventions.MediaTypes.Folder)
if (source.ContentType.IsContainer || source.ContentType.Alias == Core.Constants.Conventions.MediaTypes.Folder)
{
apps.Add(this.CreateListViewApp(_dataTypeService, _propertyEditorCollection, source.ContentType.Alias, "media"));
apps.AppendListViewApp(_dataTypeService, _propertyEditorCollection, source.ContentType.Alias, "media");
}
else
{

View File

@@ -54,12 +54,7 @@ namespace Umbraco.Web.Models.Mapping
.ForMember(dest => dest.AdditionalData, opt => opt.Ignore())
.ForMember(dest => dest.ContentType, opt => opt.ResolveUsing(mediaTypeBasicResolver))
.ForMember(dest => dest.MediaLink, opt => opt.ResolveUsing(content => string.Join(",", content.GetUrls(UmbracoConfig.For.UmbracoSettings().Content, logger))))
.ForMember(dest => dest.ContentApps, opt => opt.ResolveUsing(mediaAppResolver))
.AfterMap((media, display) =>
{
//if (media.ContentType.IsContainer)
// TabsAndPropertiesResolver.AddListView(display, "media", dataTypeService, textService);
});
.ForMember(dest => dest.ContentApps, opt => opt.ResolveUsing(mediaAppResolver));
//FROM IMedia TO ContentItemBasic<ContentPropertyBasic, IMedia>
CreateMap<IMedia, ContentItemBasic<ContentPropertyBasic, IMedia>>()

View File

@@ -26,84 +26,7 @@ namespace Umbraco.Web.Models.Mapping
{
IgnoreProperties = ignoreProperties ?? throw new ArgumentNullException(nameof(ignoreProperties));
}
//TODO: Get rid of this it should not be needed anymore since list views are dynamically added to "content apps"
internal static void AddListView(
ITabbedContent<ContentPropertyDisplay> display,
string contentTypeAlias, string entityType,
IDataTypeService dataTypeService, ILocalizedTextService localizedTextService)
{
int dtdId;
var customDtdName = Constants.Conventions.DataTypes.ListViewPrefix + contentTypeAlias;
switch (entityType)
{
case "content":
dtdId = Constants.DataTypes.DefaultContentListView;
break;
case "media":
dtdId = Constants.DataTypes.DefaultMediaListView;
break;
case "member":
dtdId = Constants.DataTypes.DefaultMembersListView;
break;
default:
throw new ArgumentOutOfRangeException(nameof(entityType), "entityType does not match a required value");
}
//first try to get the custom one if there is one
var dt = dataTypeService.GetDataType(customDtdName)
?? dataTypeService.GetDataType(dtdId);
if (dt == null)
{
throw new InvalidOperationException("No list view data type was found for this document type, ensure that the default list view data types exists and/or that your custom list view data type exists");
}
var editor = Current.PropertyEditors[dt.EditorAlias];
if (editor == null)
{
throw new NullReferenceException("The property editor with alias " + dt.EditorAlias + " does not exist");
}
//TODO: We need to move this logic elsewhere, we don't want to have to add a list view to a tab that will
// get removed later and magically move to the ListView ContentApp. The ListView ContentApp will need to
// manage this data somehow so a content app will also need to be able to specify it's own model.
//var listViewTab = new Tab<ContentPropertyDisplay>
//{
// Alias = Constants.Conventions.PropertyGroups.ListViewGroupName,
// Label = localizedTextService.Localize("content/childItems"),
// Id = display.Tabs.Count() + 1,
// IsActive = true
//};
//var listViewConfig = editor.GetConfigurationEditor().ToConfigurationEditor(dt.Configuration);
////add the entity type to the config
//listViewConfig["entityType"] = entityType;
////Override Tab Label if tabName is provided
//if (listViewConfig.ContainsKey("tabName"))
//{
// var configTabName = listViewConfig["tabName"];
// if (configTabName != null && string.IsNullOrWhiteSpace(configTabName.ToString()) == false)
// listViewTab.Label = configTabName.ToString();
//}
//var listViewProperties = new List<ContentPropertyDisplay>();
//listViewProperties.Add(new ContentPropertyDisplay
//{
// Alias = $"{Constants.PropertyEditors.InternalGenericPropertiesPrefix}containerView",
// Label = "",
// Value = null,
// View = editor.GetValueEditor().View,
// HideLabel = true,
// Config = listViewConfig
//});
//listViewTab.Properties = listViewProperties;
//SetChildItemsTabPosition(display, listViewConfig, listViewTab);
}
private static int GetTabNumberFromConfig(IDictionary<string, object> listViewConfig)
{
if (!listViewConfig.TryGetValue("displayAtTabNumber", out var displayTabNum))

View File

@@ -43,7 +43,20 @@ namespace Umbraco.Web.WebApi.Binders
where TPersisted : class, IContentBase
where TModelSave : ContentBaseItemSave<TPersisted>
{
protected ServiceContext Services => Current.Services; // fixme - inject
protected Core.Logging.ILogger Logger { get; }
protected ServiceContext Services { get; }
protected IUmbracoContextAccessor UmbracoContextAccessor { get; }
public ContentItemBaseBinder() : this(Current.Logger, Current.Services, Current.UmbracoContextAccessor)
{
}
public ContentItemBaseBinder(Core.Logging.ILogger logger, ServiceContext services, IUmbracoContextAccessor umbracoContextAccessor)
{
Logger = logger;
Services = services;
UmbracoContextAccessor = umbracoContextAccessor;
}
public bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext)
{
@@ -58,29 +71,18 @@ namespace Umbraco.Web.WebApi.Binders
Directory.CreateDirectory(root);
var provider = new MultipartFormDataStreamProvider(root);
var task = Task.Run(() => GetModelAsync(actionContext, bindingContext, provider))
.ContinueWith(x =>
{
if (x.IsFaulted && x.Exception != null)
{
throw x.Exception;
}
//now that everything is binded, validate the properties
var contentItemValidator = GetValidationHelper();
contentItemValidator.ValidateItem(actionContext, x.Result);
bindingContext.Model = x.Result;
});
task.Wait();
var model = GetModel(actionContext, bindingContext, provider);
//now that everything is binded, validate the properties
var contentItemValidator = GetValidationHelper();
contentItemValidator.ValidateItem(actionContext, model);
bindingContext.Model = model;
return bindingContext.Model != null;
}
protected virtual ContentItemValidationHelper<TPersisted, TModelSave> GetValidationHelper()
{
return new ContentItemValidationHelper<TPersisted, TModelSave>();
return new ContentItemValidationHelper<TPersisted, TModelSave>(Logger, UmbracoContextAccessor);
}
/// <summary>
@@ -90,29 +92,13 @@ namespace Umbraco.Web.WebApi.Binders
/// <param name="bindingContext"></param>
/// <param name="provider"></param>
/// <returns></returns>
private async Task<TModelSave> GetModelAsync(HttpActionContext actionContext, ModelBindingContext bindingContext, MultipartFormDataStreamProvider provider)
private TModelSave GetModel(HttpActionContext actionContext, ModelBindingContext bindingContext, MultipartFormDataStreamProvider provider)
{
// note
//
// for some reason, due to the way we do async, HttpContext.Current is null
// which means that the 'current' UmbracoContext is null too since we are using HttpContextUmbracoContextAccessor
// trying to 'EnsureContext' fails because that accessor cannot access the HttpContext either to register the current UmbracoContext
//
// so either we go with an HybridUmbracoContextAccessor that relies on a ThreadStatic variable when HttpContext.Current is null
// and I don't like it
// or
// we try to force-set the current http context, because, hey... it's there.
// and then there is no need to event 'Ensure' anything
// read http://stackoverflow.com/questions/1992141/how-do-i-get-an-httpcontext-object-from-httpcontextbase-in-asp-net-mvc-1
// what's below works but I cannot say I am proud of it
var request = actionContext.Request;
var httpContext = (HttpContextBase) request.Properties["MS_HttpContext"];
HttpContext.Current = httpContext.ApplicationInstance.Context;
var content = request.Content;
var result = await content.ReadAsMultipartAsync(provider);
var result = content.ReadAsMultipartAsync(provider).Result;
if (result.FormData["contentItem"] == null)
{

View File

@@ -5,7 +5,10 @@ using System.Net.Http;
using System.Web.Http.Controllers;
using AutoMapper;
using Umbraco.Core;
using Umbraco.Core.Logging;
using Umbraco.Core.Models;
using Umbraco.Core.Services;
using Umbraco.Web.Composing;
using Umbraco.Web.Models.ContentEditing;
using Umbraco.Web.Models.Mapping;
using Umbraco.Web.WebApi.Filters;
@@ -14,9 +17,18 @@ namespace Umbraco.Web.WebApi.Binders
{
internal class ContentItemBinder : ContentItemBaseBinder<IContent, ContentItemSave>
{
public ContentItemBinder() : this(Current.Logger, Current.Services, Current.UmbracoContextAccessor)
{
}
public ContentItemBinder(Core.Logging.ILogger logger, ServiceContext services, IUmbracoContextAccessor umbracoContextAccessor)
: base(logger, services, umbracoContextAccessor)
{
}
protected override ContentItemValidationHelper<IContent, ContentItemSave> GetValidationHelper()
{
return new ContentValidationHelper();
return new ContentValidationHelper(Logger, UmbracoContextAccessor);
}
protected override IContent GetExisting(ContentItemSave model)
@@ -49,6 +61,10 @@ namespace Umbraco.Web.WebApi.Binders
internal class ContentValidationHelper : ContentItemValidationHelper<IContent, ContentItemSave>
{
public ContentValidationHelper(ILogger logger, IUmbracoContextAccessor umbracoContextAccessor) : base(logger, umbracoContextAccessor)
{
}
/// <summary>
/// Validates that the correct information is in the request for saving a culture variant
/// </summary>

View File

@@ -2,6 +2,8 @@
using AutoMapper;
using Umbraco.Core;
using Umbraco.Core.Models;
using Umbraco.Core.Services;
using Umbraco.Web.Composing;
using Umbraco.Web.Models.ContentEditing;
using Umbraco.Web.Models.Mapping;
@@ -9,6 +11,15 @@ namespace Umbraco.Web.WebApi.Binders
{
internal class MediaItemBinder : ContentItemBaseBinder<IMedia, MediaItemSave>
{
public MediaItemBinder() : this(Current.Logger, Current.Services, Current.UmbracoContextAccessor)
{
}
public MediaItemBinder(Core.Logging.ILogger logger, ServiceContext services, IUmbracoContextAccessor umbracoContextAccessor)
: base(logger, services, umbracoContextAccessor)
{
}
protected override IMedia GetExisting(MediaItemSave model)
{
return Services.MediaService.GetById(Convert.ToInt32(model.Id));

View File

@@ -19,14 +19,25 @@ using Umbraco.Core.Models.Membership;
using Umbraco.Core.Services.Implement;
using Umbraco.Web;
using Umbraco.Web.Composing;
using Umbraco.Core.Logging;
namespace Umbraco.Web.WebApi.Binders
{
internal class MemberBinder : ContentItemBaseBinder<IMember, MemberSave>
{
public MemberBinder() : this(Current.Logger, Current.Services, Current.UmbracoContextAccessor)
{
}
public MemberBinder(ILogger logger, ServiceContext services, IUmbracoContextAccessor umbracoContextAccessor)
: base(logger, services, umbracoContextAccessor)
{
}
protected override ContentItemValidationHelper<IMember, MemberSave> GetValidationHelper()
{
return new MemberValidationHelper();
return new MemberValidationHelper(Logger, UmbracoContextAccessor);
}
/// <summary>
@@ -179,6 +190,10 @@ namespace Umbraco.Web.WebApi.Binders
/// </summary>
internal class MemberValidationHelper : ContentItemValidationHelper<IMember, MemberSave>
{
public MemberValidationHelper(ILogger logger, IUmbracoContextAccessor umbracoContextAccessor) : base(logger, umbracoContextAccessor)
{
}
/// <summary>
/// We need to manually validate a few things here like email and login to make sure they are valid and aren't duplicates
/// </summary>
@@ -244,12 +259,9 @@ namespace Umbraco.Web.WebApi.Binders
propertiesToValidate.RemoveAll(property => property.Alias == remove);
}
var umbCtx = Current.UmbracoContext; // fixme inject?
//if the user doesn't have access to sensitive values, then we need to validate the incoming properties to check
//if a sensitive value is being submitted.
if (umbCtx.Security.CurrentUser.HasAccessToSensitiveData() == false)
if (UmbracoContextAccessor.UmbracoContext.Security.CurrentUser.HasAccessToSensitiveData() == false)
{
var sensitiveProperties = postedItem.PersistedContent.ContentType
.PropertyTypes.Where(x => postedItem.PersistedContent.ContentType.IsSensitiveProperty(x.Alias))

View File

@@ -26,6 +26,14 @@ namespace Umbraco.Web.WebApi.Filters
where TPersisted : class, IContentBase
where TModelSave : ContentBaseItemSave<TPersisted>
{
protected IUmbracoContextAccessor UmbracoContextAccessor { get; }
protected ILogger Logger { get; }
public ContentItemValidationHelper(ILogger logger, IUmbracoContextAccessor umbracoContextAccessor)
{
Logger = logger ?? throw new ArgumentNullException(nameof(logger));
UmbracoContextAccessor = umbracoContextAccessor ?? throw new ArgumentNullException(nameof(umbracoContextAccessor));
}
/// <summary>
/// Validates the content item and updates the Response and ModelState accordingly
@@ -94,7 +102,7 @@ namespace Umbraco.Web.WebApi.Filters
/// <param name="persistedProperties"></param>
/// <param name="actionContext"></param>
/// <returns></returns>
protected bool ValidateProperties(List<ContentPropertyBasic> postedProperties , List<Property> persistedProperties, HttpActionContext actionContext)
protected bool ValidateProperties(List<ContentPropertyBasic> postedProperties, List<Property> persistedProperties, HttpActionContext actionContext)
{
foreach (var p in postedProperties)
{
@@ -131,7 +139,8 @@ namespace Umbraco.Web.WebApi.Filters
if (editor == null)
{
var message = $"Could not find property editor \"{p.DataType.EditorAlias}\" for property with id {p.Id}.";
Current.Logger.Warn<ContentItemValidationHelper<TPersisted, TModelSave>>(message);
Logger.Warn<ContentItemValidationHelper<TPersisted, TModelSave>>(message);
continue;
}