From aee6156a096efec60bcd9dc0459b5cd7317bfe9c Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Thu, 6 Dec 2018 10:02:23 +0100 Subject: [PATCH] Refactored the ManifestContentAppDefinition to have a factory and a seperate data class. --- .../Manifest/ContentAppDefinitionConverter.cs | 6 +- .../Manifest/ManifestContentAppDefinition.cs | 131 +------------ .../Manifest/ManifestContentAppFactory.cs | 172 ++++++++++++++++++ src/Umbraco.Core/Manifest/ManifestParser.cs | 2 +- src/Umbraco.Core/Manifest/PackageManifest.cs | 2 +- ...AppDefinition.cs => IContentAppFactory.cs} | 4 +- src/Umbraco.Core/Umbraco.Core.csproj | 3 +- .../Manifest/ManifestContentAppTests.cs | 3 +- .../ContentAppDefinitionCollection.cs | 11 +- .../ContentAppDefinitionCollectionBuilder.cs | 10 +- ...n.cs => ContentEditorContentAppFactory.cs} | 2 +- ...ion.cs => ContentInfoContentAppFactory.cs} | 2 +- ...nition.cs => ListViewContentAppFactory.cs} | 4 +- src/Umbraco.Web/Editors/ContentController.cs | 2 +- src/Umbraco.Web/Editors/MediaController.cs | 2 +- src/Umbraco.Web/Editors/MemberController.cs | 2 +- .../Runtime/WebRuntimeComponent.cs | 6 +- src/Umbraco.Web/Umbraco.Web.csproj | 6 +- 18 files changed, 212 insertions(+), 158 deletions(-) create mode 100644 src/Umbraco.Core/Manifest/ManifestContentAppFactory.cs rename src/Umbraco.Core/Models/ContentEditing/{IContentAppDefinition.cs => IContentAppFactory.cs} (92%) rename src/Umbraco.Web/ContentApps/{ContentEditorContentAppDefinition.cs => ContentEditorContentAppFactory.cs} (95%) rename src/Umbraco.Web/ContentApps/{ContentInfoContentAppDefinition.cs => ContentInfoContentAppFactory.cs} (95%) rename src/Umbraco.Web/ContentApps/{ListViewContentAppDefinition.cs => ListViewContentAppFactory.cs} (96%) diff --git a/src/Umbraco.Core/Manifest/ContentAppDefinitionConverter.cs b/src/Umbraco.Core/Manifest/ContentAppDefinitionConverter.cs index 87f104d90e..dd167e06df 100644 --- a/src/Umbraco.Core/Manifest/ContentAppDefinitionConverter.cs +++ b/src/Umbraco.Core/Manifest/ContentAppDefinitionConverter.cs @@ -6,11 +6,11 @@ using Umbraco.Core.Serialization; namespace Umbraco.Core.Manifest { /// - /// Implements a json read converter for . + /// Implements a json read converter for . /// - internal class ContentAppDefinitionConverter : JsonReadConverter + internal class ContentAppDefinitionConverter : JsonReadConverter { - protected override IContentAppDefinition Create(Type objectType, string path, JObject jObject) + protected override ManifestContentAppDefinition Create(Type objectType, string path, JObject jObject) => new ManifestContentAppDefinition(); } } diff --git a/src/Umbraco.Core/Manifest/ManifestContentAppDefinition.cs b/src/Umbraco.Core/Manifest/ManifestContentAppDefinition.cs index d5f6c2b8c4..0667f11aab 100644 --- a/src/Umbraco.Core/Manifest/ManifestContentAppDefinition.cs +++ b/src/Umbraco.Core/Manifest/ManifestContentAppDefinition.cs @@ -31,11 +31,9 @@ namespace Umbraco.Core.Manifest /// Represents a content app definition, parsed from a manifest. /// [DataContract(Name = "appdef", Namespace = "")] - public class ManifestContentAppDefinition : IContentAppDefinition + public class ManifestContentAppDefinition { private string _view; - private ContentApp _app; - private ShowRule[] _showRules; /// /// Gets or sets the name of the content app. @@ -83,132 +81,5 @@ namespace Umbraco.Core.Manifest [DataMember(Name = "show")] public string[] Show { get; set; } = Array.Empty(); - /// - public ContentApp GetContentAppFor(object o, IEnumerable userGroups) - { - string partA, partB; - - switch (o) - { - case IContent content: - partA = "content"; - partB = content.ContentType.Alias; - break; - - case IMedia media: - partA = "media"; - partB = media.ContentType.Alias; - break; - - default: - return null; - } - - var rules = _showRules ?? (_showRules = ShowRule.Parse(Show).ToArray()); - var userGroupsList = userGroups.ToList(); - - var okRole = false; - var hasRole = false; - var okType = false; - var hasType = false; - - foreach (var rule in rules) - { - if (rule.PartA.InvariantEquals("role")) - { - // if roles have been ok-ed already, skip the rule - if (okRole) - continue; - - // remember we have role rules - hasRole = true; - - foreach (var group in userGroupsList) - { - // if the entry does not apply, skip - if (!rule.Matches("role", group.Alias)) - continue; - - // if the entry applies, - // if it's an exclude entry, exit, do not display the content app - if (!rule.Show) - return null; - - // else ok to display, remember roles are ok, break from userGroupsList - okRole = rule.Show; - break; - } - } - else // it is a type rule - { - // if type has been ok-ed already, skip the rule - if (okType) - continue; - - // remember we have type rules - hasType = true; - - // if the entry does not apply, skip it - if (!rule.Matches(partA, partB)) - continue; - - // if the entry applies, - // if it's an exclude entry, exit, do not display the content app - if (!rule.Show) - return null; - - // else ok to display, remember type rules are ok - okType = true; - } - } - - // if roles rules are specified but not ok, - // or if type roles are specified but not ok, - // cannot display the content app - if ((hasRole && !okRole) || (hasType && !okType)) - return null; - - // else - // content app can be displayed - return _app ?? (_app = new ContentApp - { - Alias = Alias, - Name = Name, - Icon = Icon, - View = View, - Weight = Weight - }); - } - - private class ShowRule - { - private static readonly Regex ShowRegex = new Regex("^([+-])?([a-z]+)/([a-z0-9_]+|\\*)$", RegexOptions.Compiled | RegexOptions.IgnoreCase); - - public bool Show { get; private set; } - public string PartA { get; private set; } - public string PartB { get; private set; } - - public bool Matches(string partA, string partB) - { - return (PartA == "*" || PartA.InvariantEquals(partA)) && (PartB == "*" || PartB.InvariantEquals(partB)); - } - - public static IEnumerable Parse(string[] rules) - { - foreach (var rule in rules) - { - var match = ShowRegex.Match(rule); - if (!match.Success) - throw new FormatException($"Illegal 'show' entry \"{rule}\" in manifest."); - - yield return new ShowRule - { - Show = match.Groups[1].Value != "-", - PartA = match.Groups[2].Value, - PartB = match.Groups[3].Value - }; - } - } - } } } diff --git a/src/Umbraco.Core/Manifest/ManifestContentAppFactory.cs b/src/Umbraco.Core/Manifest/ManifestContentAppFactory.cs new file mode 100644 index 0000000000..b44d31f0a5 --- /dev/null +++ b/src/Umbraco.Core/Manifest/ManifestContentAppFactory.cs @@ -0,0 +1,172 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.RegularExpressions; +using Umbraco.Core.Models; +using Umbraco.Core.Models.ContentEditing; +using Umbraco.Core.Models.Membership; + +namespace Umbraco.Core.Manifest +{ + // contentApps: [ + // { + // name: 'App Name', // required + // alias: 'appAlias', // required + // weight: 0, // optional, default is 0, use values between -99 and +99 + // icon: 'icon.app', // required + // view: 'path/view.htm', // required + // show: [ // optional, default is always show + // '-content/foo', // hide for content type 'foo' + // '+content/*', // show for all other content types + // '+media/*', // show for all media types + // '+role/admin' // show for admin users. Role based permissions will override others. + // ] + // }, + // ... + // ] + + /// + /// Represents a content app definition, parsed from a manifest. + /// + public class ManifestContentAppFactory : IContentAppFactory + { + private readonly ManifestContentAppDefinition _definition; + + + public ManifestContentAppFactory(ManifestContentAppDefinition definition) + { + _definition = definition; + } + + private ContentApp _app; + private ShowRule[] _showRules; + + /// + public ContentApp GetContentAppFor(object o,IEnumerable userGroups) + { + string partA, partB; + + switch (o) + { + case IContent content: + partA = "content"; + partB = content.ContentType.Alias; + break; + + case IMedia media: + partA = "media"; + partB = media.ContentType.Alias; + break; + + default: + return null; + } + + var rules = _showRules ?? (_showRules = ShowRule.Parse(_definition.Show).ToArray()); + var userGroupsList = userGroups.ToList(); + + var okRole = false; + var hasRole = false; + var okType = false; + var hasType = false; + + foreach (var rule in rules) + { + if (rule.PartA.InvariantEquals("role")) + { + // if roles have been ok-ed already, skip the rule + if (okRole) + continue; + + // remember we have role rules + hasRole = true; + + foreach (var group in userGroupsList) + { + // if the entry does not apply, skip + if (!rule.Matches("role", group.Alias)) + continue; + + // if the entry applies, + // if it's an exclude entry, exit, do not display the content app + if (!rule.Show) + return null; + + // else ok to display, remember roles are ok, break from userGroupsList + okRole = rule.Show; + break; + } + } + else // it is a type rule + { + // if type has been ok-ed already, skip the rule + if (okType) + continue; + + // remember we have type rules + hasType = true; + + // if the entry does not apply, skip it + if (!rule.Matches(partA, partB)) + continue; + + // if the entry applies, + // if it's an exclude entry, exit, do not display the content app + if (!rule.Show) + return null; + + // else ok to display, remember type rules are ok + okType = true; + } + } + + // if roles rules are specified but not ok, + // or if type roles are specified but not ok, + // cannot display the content app + if ((hasRole && !okRole) || (hasType && !okType)) + return null; + + // else + // content app can be displayed + return _app ?? (_app = new ContentApp + { + Alias = _definition.Alias, + Name = _definition.Name, + Icon = _definition.Icon, + View = _definition.View, + Weight = _definition.Weight + }); + } + + private class ShowRule + { + private static readonly Regex ShowRegex = new Regex("^([+-])?([a-z]+)/([a-z0-9_]+|\\*)$", RegexOptions.Compiled | RegexOptions.IgnoreCase); + + public bool Show { get; private set; } + public string PartA { get; private set; } + public string PartB { get; private set; } + + public bool Matches(string partA, string partB) + { + return (PartA == "*" || PartA.InvariantEquals(partA)) && (PartB == "*" || PartB.InvariantEquals(partB)); + } + + public static IEnumerable Parse(string[] rules) + { + foreach (var rule in rules) + { + var match = ShowRegex.Match(rule); + if (!match.Success) + throw new FormatException($"Illegal 'show' entry \"{rule}\" in manifest."); + + yield return new ShowRule + { + Show = match.Groups[1].Value != "-", + PartA = match.Groups[2].Value, + PartB = match.Groups[3].Value + }; + } + } + } + } +} diff --git a/src/Umbraco.Core/Manifest/ManifestParser.cs b/src/Umbraco.Core/Manifest/ManifestParser.cs index fe021fae5b..2d6ec93e14 100644 --- a/src/Umbraco.Core/Manifest/ManifestParser.cs +++ b/src/Umbraco.Core/Manifest/ManifestParser.cs @@ -99,7 +99,7 @@ namespace Umbraco.Core.Manifest var propertyEditors = new List(); var parameterEditors = new List(); var gridEditors = new List(); - var contentApps = new List(); + var contentApps = new List(); var dashboards = new List(); foreach (var manifest in manifests) diff --git a/src/Umbraco.Core/Manifest/PackageManifest.cs b/src/Umbraco.Core/Manifest/PackageManifest.cs index 95a5c01b6a..cd806ac847 100644 --- a/src/Umbraco.Core/Manifest/PackageManifest.cs +++ b/src/Umbraco.Core/Manifest/PackageManifest.cs @@ -26,7 +26,7 @@ namespace Umbraco.Core.Manifest public GridEditor[] GridEditors { get; set; } = Array.Empty(); [JsonProperty("contentApps")] - public IContentAppDefinition[] ContentApps { get; set; } = Array.Empty(); + public ManifestContentAppDefinition[] ContentApps { get; set; } = Array.Empty(); [JsonProperty("dashboards")] public ManifestDashboardDefinition[] Dashboards { get; set; } = Array.Empty(); diff --git a/src/Umbraco.Core/Models/ContentEditing/IContentAppDefinition.cs b/src/Umbraco.Core/Models/ContentEditing/IContentAppFactory.cs similarity index 92% rename from src/Umbraco.Core/Models/ContentEditing/IContentAppDefinition.cs rename to src/Umbraco.Core/Models/ContentEditing/IContentAppFactory.cs index af83c5a2f5..144f1c4f84 100644 --- a/src/Umbraco.Core/Models/ContentEditing/IContentAppDefinition.cs +++ b/src/Umbraco.Core/Models/ContentEditing/IContentAppFactory.cs @@ -1,13 +1,15 @@ using System.Collections.Generic; +using Umbraco.Core.Manifest; using Umbraco.Core.Models.Membership; namespace Umbraco.Core.Models.ContentEditing { + /// /// Represents a content app definition. /// - public interface IContentAppDefinition + public interface IContentAppFactory { /// /// Gets the content app for an object. diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 047f3f8cdd..c2b16f590a 100755 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -336,6 +336,7 @@ + @@ -380,7 +381,7 @@ - + diff --git a/src/Umbraco.Tests/Manifest/ManifestContentAppTests.cs b/src/Umbraco.Tests/Manifest/ManifestContentAppTests.cs index eed0919149..016eb4113a 100644 --- a/src/Umbraco.Tests/Manifest/ManifestContentAppTests.cs +++ b/src/Umbraco.Tests/Manifest/ManifestContentAppTests.cs @@ -67,7 +67,8 @@ namespace Umbraco.Tests.Manifest private void AssertDefinition(object source, bool expected, string[] show, IReadOnlyUserGroup[] groups) { var definition = JsonConvert.DeserializeObject("{" + (show.Length == 0 ? "" : " \"show\": [" + string.Join(",", show.Select(x => "\"" + x + "\"")) + "] ") + "}"); - var app = definition.GetContentAppFor(source, groups); + var factory = new ManifestContentAppFactory(definition); + var app = factory.GetContentAppFor(source, groups); if (expected) Assert.IsNotNull(app); else diff --git a/src/Umbraco.Web/ContentApps/ContentAppDefinitionCollection.cs b/src/Umbraco.Web/ContentApps/ContentAppDefinitionCollection.cs index 8b33fd1447..1d94c3f381 100644 --- a/src/Umbraco.Web/ContentApps/ContentAppDefinitionCollection.cs +++ b/src/Umbraco.Web/ContentApps/ContentAppDefinitionCollection.cs @@ -5,15 +5,17 @@ using Umbraco.Core; using Umbraco.Core.Composing; using Umbraco.Core.Models.ContentEditing; using Umbraco.Core.Logging; +using Umbraco.Core.Manifest; using Umbraco.Core.Models.Membership; namespace Umbraco.Web.ContentApps { - public class ContentAppDefinitionCollection : BuilderCollectionBase + public class ContentAppDefinitionCollection : BuilderCollectionBase { private readonly ILogger _logger; + private readonly IContentAppFactory _factory; - public ContentAppDefinitionCollection(IEnumerable items, ILogger logger) + public ContentAppDefinitionCollection(IEnumerable items, ILogger logger) : base(items) { _logger = logger; @@ -32,7 +34,10 @@ namespace Umbraco.Web.ContentApps public IEnumerable GetContentAppsFor(object o, IEnumerable userGroups=null) { var roles = GetCurrentUserGroups(); - var apps = this.Select(x => x.GetContentAppFor(o, roles)).WhereNotNull().OrderBy(x => x.Weight).ToList(); + + + var apps = Enumerable.Empty();// this.Select(x => x.GetContentAppFor(o, roles)).WhereNotNull().OrderBy(x => x.Weight).ToList(); + var aliases = new HashSet(); List dups = null; diff --git a/src/Umbraco.Web/ContentApps/ContentAppDefinitionCollectionBuilder.cs b/src/Umbraco.Web/ContentApps/ContentAppDefinitionCollectionBuilder.cs index 267dd2d0e7..744785bacd 100644 --- a/src/Umbraco.Web/ContentApps/ContentAppDefinitionCollectionBuilder.cs +++ b/src/Umbraco.Web/ContentApps/ContentAppDefinitionCollectionBuilder.cs @@ -5,14 +5,16 @@ using Umbraco.Core.Composing; using Umbraco.Core.Logging; using Umbraco.Core.Manifest; using Umbraco.Core.Models.ContentEditing; +using Umbraco.Core.Services; namespace Umbraco.Web.ContentApps { - public class ContentAppDefinitionCollectionBuilder : OrderedCollectionBuilderBase + public class ContentAppDefinitionCollectionBuilder : OrderedCollectionBuilderBase { public ContentAppDefinitionCollectionBuilder(IServiceContainer container) : base(container) - { } + { + } protected override ContentAppDefinitionCollectionBuilder This => this; @@ -25,14 +27,14 @@ namespace Umbraco.Web.ContentApps return new ContentAppDefinitionCollection(CreateItems(), logger); } - protected override IEnumerable CreateItems(params object[] args) + protected override IEnumerable CreateItems(params object[] args) { // get the manifest parser just-in-time - injecting it in the ctor would mean that // simply getting the builder in order to configure the collection, would require // its dependencies too, and that can create cycles or other oddities var manifestParser = Container.GetInstance(); - return base.CreateItems(args).Concat(manifestParser.Manifest.ContentApps); + return base.CreateItems(args).Concat(manifestParser.Manifest.ContentApps.Select(x=>new ManifestContentAppFactory(x))); } } } diff --git a/src/Umbraco.Web/ContentApps/ContentEditorContentAppDefinition.cs b/src/Umbraco.Web/ContentApps/ContentEditorContentAppFactory.cs similarity index 95% rename from src/Umbraco.Web/ContentApps/ContentEditorContentAppDefinition.cs rename to src/Umbraco.Web/ContentApps/ContentEditorContentAppFactory.cs index d54d1a44d4..b1d5d373c0 100644 --- a/src/Umbraco.Web/ContentApps/ContentEditorContentAppDefinition.cs +++ b/src/Umbraco.Web/ContentApps/ContentEditorContentAppFactory.cs @@ -6,7 +6,7 @@ using Umbraco.Core.Models.Membership; namespace Umbraco.Web.ContentApps { - internal class ContentEditorContentAppDefinition : IContentAppDefinition + internal class ContentEditorContentAppFactory : IContentAppFactory { // see note on ContentApp private const int Weight = -100; diff --git a/src/Umbraco.Web/ContentApps/ContentInfoContentAppDefinition.cs b/src/Umbraco.Web/ContentApps/ContentInfoContentAppFactory.cs similarity index 95% rename from src/Umbraco.Web/ContentApps/ContentInfoContentAppDefinition.cs rename to src/Umbraco.Web/ContentApps/ContentInfoContentAppFactory.cs index de490439ba..49be194349 100644 --- a/src/Umbraco.Web/ContentApps/ContentInfoContentAppDefinition.cs +++ b/src/Umbraco.Web/ContentApps/ContentInfoContentAppFactory.cs @@ -6,7 +6,7 @@ using Umbraco.Core.Models.Membership; namespace Umbraco.Web.ContentApps { - public class ContentInfoContentAppDefinition : IContentAppDefinition + public class ContentInfoContentAppFactory : IContentAppFactory { // see note on ContentApp private const int Weight = +100; diff --git a/src/Umbraco.Web/ContentApps/ListViewContentAppDefinition.cs b/src/Umbraco.Web/ContentApps/ListViewContentAppFactory.cs similarity index 96% rename from src/Umbraco.Web/ContentApps/ListViewContentAppDefinition.cs rename to src/Umbraco.Web/ContentApps/ListViewContentAppFactory.cs index 0e4c7a04b8..7421a55907 100644 --- a/src/Umbraco.Web/ContentApps/ListViewContentAppDefinition.cs +++ b/src/Umbraco.Web/ContentApps/ListViewContentAppFactory.cs @@ -9,7 +9,7 @@ using Umbraco.Web.Models.ContentEditing; namespace Umbraco.Web.ContentApps { - internal class ListViewContentAppDefinition : IContentAppDefinition + internal class ListViewContentAppFactory : IContentAppFactory { // see note on ContentApp private const int Weight = -666; @@ -17,7 +17,7 @@ namespace Umbraco.Web.ContentApps private readonly IDataTypeService _dataTypeService; private readonly PropertyEditorCollection _propertyEditors; - public ListViewContentAppDefinition(IDataTypeService dataTypeService, PropertyEditorCollection propertyEditors) + public ListViewContentAppFactory(IDataTypeService dataTypeService, PropertyEditorCollection propertyEditors) { _dataTypeService = dataTypeService; _propertyEditors = propertyEditors; diff --git a/src/Umbraco.Web/Editors/ContentController.cs b/src/Umbraco.Web/Editors/ContentController.cs index 53ecddd015..1e7e24e903 100644 --- a/src/Umbraco.Web/Editors/ContentController.cs +++ b/src/Umbraco.Web/Editors/ContentController.cs @@ -234,7 +234,7 @@ namespace Umbraco.Web.Editors public ContentItemDisplay GetRecycleBin() { var apps = new List(); - apps.Add(ListViewContentAppDefinition.CreateContentApp(Services.DataTypeService, _propertyEditors, "recycleBin", "content", Core.Constants.DataTypes.DefaultMembersListView)); + apps.Add(ListViewContentAppFactory.CreateContentApp(Services.DataTypeService, _propertyEditors, "recycleBin", "content", Core.Constants.DataTypes.DefaultMembersListView)); apps[0].Active = true; var display = new ContentItemDisplay { diff --git a/src/Umbraco.Web/Editors/MediaController.cs b/src/Umbraco.Web/Editors/MediaController.cs index 9aebc11dc6..dd224dc551 100644 --- a/src/Umbraco.Web/Editors/MediaController.cs +++ b/src/Umbraco.Web/Editors/MediaController.cs @@ -99,7 +99,7 @@ namespace Umbraco.Web.Editors public MediaItemDisplay GetRecycleBin() { var apps = new List(); - apps.Add(ListViewContentAppDefinition.CreateContentApp(Services.DataTypeService, _propertyEditors, "recycleBin", "media", Core.Constants.DataTypes.DefaultMediaListView)); + apps.Add(ListViewContentAppFactory.CreateContentApp(Services.DataTypeService, _propertyEditors, "recycleBin", "media", Core.Constants.DataTypes.DefaultMediaListView)); apps[0].Active = true; var display = new MediaItemDisplay { diff --git a/src/Umbraco.Web/Editors/MemberController.cs b/src/Umbraco.Web/Editors/MemberController.cs index 6117db8857..ae02645afa 100644 --- a/src/Umbraco.Web/Editors/MemberController.cs +++ b/src/Umbraco.Web/Editors/MemberController.cs @@ -139,7 +139,7 @@ namespace Umbraco.Web.Editors var name = foundType != null ? foundType.Name : listName; var apps = new List(); - apps.Add(ListViewContentAppDefinition.CreateContentApp(Services.DataTypeService, _propertyEditors, listName, "member", Core.Constants.DataTypes.DefaultMembersListView)); + apps.Add(ListViewContentAppFactory.CreateContentApp(Services.DataTypeService, _propertyEditors, listName, "member", Core.Constants.DataTypes.DefaultMembersListView)); apps[0].Active = true; var display = new MemberListDisplay diff --git a/src/Umbraco.Web/Runtime/WebRuntimeComponent.cs b/src/Umbraco.Web/Runtime/WebRuntimeComponent.cs index 97cebdb076..421b807c26 100644 --- a/src/Umbraco.Web/Runtime/WebRuntimeComponent.cs +++ b/src/Umbraco.Web/Runtime/WebRuntimeComponent.cs @@ -207,9 +207,9 @@ namespace Umbraco.Web.Runtime // register known content apps composition.Container.RegisterCollectionBuilder() - .Append() - .Append() - .Append(); + .Append() + .Append() + .Append(); } internal void Initialize( diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index c17536ab4d..371b34d71d 100755 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -110,6 +110,7 @@ + @@ -155,9 +156,8 @@ - - - + +