');
- }
- }
- clsName = '';
- if (prevMonth.year() < year || (prevMonth.year() === year && prevMonth.month() < month)) {
- clsName += ' old';
- } else if (prevMonth.year() > year || (prevMonth.year() === year && prevMonth.month() > month)) {
- clsName += ' new';
- }
- if (prevMonth.isSame(moment({y: picker.date.year(), M: picker.date.month(), d: picker.date.date()}))) {
- clsName += ' active';
- }
- if (isInDisableDates(prevMonth, 'day') || !isInEnableDates(prevMonth)) {
- clsName += ' disabled';
- }
- if (picker.options.showToday === true) {
- if (prevMonth.isSame(moment(), 'day')) {
- clsName += ' today';
- }
- }
- if (picker.options.daysOfWeekDisabled) {
- for (i = 0; i < picker.options.daysOfWeekDisabled.length; i++) {
- if (prevMonth.day() === picker.options.daysOfWeekDisabled[i]) {
- clsName += ' disabled';
- break;
- }
- }
- }
- row.append('
' + prevMonth.date() + '
');
-
- currentDate = prevMonth.date();
- prevMonth.add(1, 'd');
-
- if (currentDate === prevMonth.date()) {
- prevMonth.add(1, 'd');
- }
- }
- picker.widget.find('.datepicker-days tbody').empty().append(html);
- currentYear = picker.date.year();
- months = picker.widget.find('.datepicker-months').find('th:eq(1)').text(year).end().find('span').removeClass('active');
- if (currentYear === year) {
- months.eq(picker.date.month()).addClass('active');
- }
- if (year - 1 < startYear) {
- picker.widget.find('.datepicker-months th:eq(0)').addClass('disabled');
- }
- if (year + 1 > endYear) {
- picker.widget.find('.datepicker-months th:eq(2)').addClass('disabled');
- }
- for (i = 0; i < 12; i++) {
- if ((year === startYear && startMonth > i) || (year < startYear)) {
- $(months[i]).addClass('disabled');
- } else if ((year === endYear && endMonth < i) || (year > endYear)) {
- $(months[i]).addClass('disabled');
- }
- }
-
- html = '';
- year = parseInt(year / 10, 10) * 10;
- yearCont = picker.widget.find('.datepicker-years').find(
- 'th:eq(1)').text(year + '-' + (year + 9)).parents('table').find('td');
- picker.widget.find('.datepicker-years').find('th').removeClass('disabled');
- if (startYear > year) {
- picker.widget.find('.datepicker-years').find('th:eq(0)').addClass('disabled');
- }
- if (endYear < year + 9) {
- picker.widget.find('.datepicker-years').find('th:eq(2)').addClass('disabled');
- }
- year -= 1;
- for (i = -1; i < 11; i++) {
- html += '' + year + '';
- year += 1;
- }
- yearCont.html(html);
- },
-
- fillHours = function () {
- moment.locale(picker.options.language);
- var table = picker.widget.find('.timepicker .timepicker-hours table'), html = '', current, i, j;
- table.parent().hide();
- if (picker.use24hours) {
- current = 0;
- for (i = 0; i < 6; i += 1) {
- html += '
';
- for (j = 0; j < 4; j += 1) {
- html += '
' + padLeft(current.toString()) + '
';
- current++;
- }
- html += '
';
- }
- }
- else {
- current = 1;
- for (i = 0; i < 3; i += 1) {
- html += '
';
- for (j = 0; j < 4; j += 1) {
- html += '
' + padLeft(current.toString()) + '
';
- current++;
- }
- html += '
';
- }
- }
- table.html(html);
- },
-
- fillMinutes = function () {
- var table = picker.widget.find('.timepicker .timepicker-minutes table'), html = '', current = 0, i, j, step = picker.options.minuteStepping;
- table.parent().hide();
- if (step === 1) {
- step = 5;
- }
- for (i = 0; i < Math.ceil(60 / step / 4) ; i++) {
- html += '
';
- for (j = 0; j < 4; j += 1) {
- if (current < 60) {
- html += '
' + padLeft(current.toString()) + '
';
- current += step;
- } else {
- html += '
';
- }
- }
- html += '
';
- }
- table.html(html);
- },
-
- fillSeconds = function () {
- var table = picker.widget.find('.timepicker .timepicker-seconds table'), html = '', current = 0, i, j;
- table.parent().hide();
- for (i = 0; i < 3; i++) {
- html += '
diff --git a/src/Umbraco.Web/Actions/ActionCollection.cs b/src/Umbraco.Web/Actions/ActionCollection.cs
index 0e33f03103..89ac8a59f4 100644
--- a/src/Umbraco.Web/Actions/ActionCollection.cs
+++ b/src/Umbraco.Web/Actions/ActionCollection.cs
@@ -21,18 +21,20 @@ namespace Umbraco.Web.Actions
internal IEnumerable GetByLetters(IEnumerable letters)
{
+ var actions = this.ToArray(); // no worry: internally, it's already an array
return letters
.Where(x => x.Length == 1)
- .Select(x => this.FirstOrDefault(y => y.Letter == x[0]))
+ .Select(x => actions.FirstOrDefault(y => y.Letter == x[0]))
.WhereNotNull()
.ToList();
}
internal IReadOnlyList FromEntityPermission(EntityPermission entityPermission)
{
+ var actions = this.ToArray(); // no worry: internally, it's already an array
return entityPermission.AssignedPermissions
.Where(x => x.Length == 1)
- .SelectMany(x => this.Where(y => y.Letter == x[0]))
+ .SelectMany(x => actions.Where(y => y.Letter == x[0]))
.WhereNotNull()
.ToList();
}
diff --git a/src/Umbraco.Web/Actions/ActionCollectionBuilder.cs b/src/Umbraco.Web/Actions/ActionCollectionBuilder.cs
index 55fa1c2099..ec1a9210a7 100644
--- a/src/Umbraco.Web/Actions/ActionCollectionBuilder.cs
+++ b/src/Umbraco.Web/Actions/ActionCollectionBuilder.cs
@@ -2,7 +2,6 @@
using System.Collections.Generic;
using System.Linq;
using Umbraco.Core.Composing;
-
namespace Umbraco.Web.Actions
{
internal class ActionCollectionBuilder : LazyCollectionBuilderBase
diff --git a/src/Umbraco.Web/Composing/Composers/ControllersComposer.cs b/src/Umbraco.Web/Composing/Composers/ControllersComposer.cs
index 83a52456a1..0565e0d863 100644
--- a/src/Umbraco.Web/Composing/Composers/ControllersComposer.cs
+++ b/src/Umbraco.Web/Composing/Composers/ControllersComposer.cs
@@ -1,44 +1,84 @@
-using System.Reflection;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
using System.Web.Http.Controllers;
using System.Web.Mvc;
using Umbraco.Core.Components;
using Umbraco.Core.Composing;
+using Umbraco.Web.Mvc;
+using Umbraco.Web.WebApi;
namespace Umbraco.Web.Composing.Composers
{
internal static class ControllersComposer
{
///
- /// Registers all IControllers using the TypeLoader for scanning and caching found instances for the calling assembly
+ /// Registers Umbraco controllers.
///
- public static Composition ComposeMvcControllers(this Composition composition, Assembly assembly)
+ public static Composition ComposeUmbracoControllers(this Composition composition, Assembly umbracoWebAssembly)
{
- //TODO: We've already scanned for UmbracoApiControllers and SurfaceControllers - should we scan again
- // for all controllers? Seems like we should just do this once and then filter. That said here we are
- // only scanning our own single assembly. Hrm.
+ // notes
+ //
+ // We scan and auto-registers:
+ // - every IController and IHttpController that *we* have in Umbraco.Web
+ // - PluginController and UmbracoApiController in every assembly
+ //
+ // We do NOT scan:
+ // - any IController or IHttpController (anything not PluginController nor UmbracoApiController), outside of Umbraco.Web
+ // which means that users HAVE to explicitly register their own non-Umbraco controllers
+ //
+ // This is because we try to achieve a balance between "simple" and "fast. Scanning for PluginController or
+ // UmbracoApiController is fast-ish because they both are IDiscoverable. Scanning for IController or IHttpController
+ // is a full, non-cached scan = expensive, we do it only for 1 assembly.
+ //
+ // TODO
+ // find a way to scan for IController *and* IHttpController in one single pass
+ // or, actually register them manually so don't require a full scan for these
+ // 5 are IController but not PluginController
+ // Umbraco.Web.Mvc.RenderMvcController
+ // Umbraco.Web.Install.Controllers.InstallController
+ // Umbraco.Web.Macros.PartialViewMacroController
+ // Umbraco.Web.Editors.PreviewController
+ // Umbraco.Web.Editors.BackOfficeController
+ // 9 are IHttpController but not UmbracoApiController
+ // Umbraco.Web.Controllers.UmbProfileController
+ // Umbraco.Web.Controllers.UmbLoginStatusController
+ // Umbraco.Web.Controllers.UmbRegisterController
+ // Umbraco.Web.Controllers.UmbLoginController
+ // Umbraco.Web.Mvc.RenderMvcController
+ // Umbraco.Web.Install.Controllers.InstallController
+ // Umbraco.Web.Macros.PartialViewMacroController
+ // Umbraco.Web.Editors.PreviewController
+ // Umbraco.Web.Editors.BackOfficeController
- composition.RegisterControllers(assembly);
+ // scan and register every IController in Umbraco.Web
+ var umbracoWebControllers = composition.TypeLoader.GetTypes(specificAssemblies: new[] { umbracoWebAssembly });
+ //foreach (var controller in umbracoWebControllers.Where(x => !typeof(PluginController).IsAssignableFrom(x)))
+ // Current.Logger.Debug(typeof(LightInjectExtensions), "IController NOT PluginController: " + controller.FullName);
+ composition.RegisterControllers(umbracoWebControllers);
+
+ // scan and register every PluginController in everything (PluginController is IDiscoverable and IController)
+ var nonUmbracoWebPluginController = composition.TypeLoader.GetTypes().Where(x => x.Assembly != umbracoWebAssembly);
+ composition.RegisterControllers(nonUmbracoWebPluginController);
+
+ // scan and register every IHttpController in Umbraco.Web
+ var umbracoWebHttpControllers = composition.TypeLoader.GetTypes(specificAssemblies: new[] { umbracoWebAssembly });
+ //foreach (var controller in umbracoWebControllers.Where(x => !typeof(UmbracoApiController).IsAssignableFrom(x)))
+ // Current.Logger.Debug(typeof(LightInjectExtensions), "IHttpController NOT UmbracoApiController: " + controller.FullName);
+ composition.RegisterControllers(umbracoWebHttpControllers);
+
+ // scan and register every UmbracoApiController in everything (UmbracoApiController is IDiscoverable and IHttpController)
+ var nonUmbracoWebApiControllers = composition.TypeLoader.GetTypes().Where(x => x.Assembly != umbracoWebAssembly);
+ composition.RegisterControllers(nonUmbracoWebApiControllers);
+
return composition;
}
- ///
- /// Registers all IHttpController using the TypeLoader for scanning and caching found instances for the calling assembly
- ///
- public static Composition ComposeApiControllers(this Composition composition, Assembly assembly)
+ private static void RegisterControllers(this Composition composition, IEnumerable controllerTypes)
{
- //TODO: We've already scanned for UmbracoApiControllers and SurfaceControllers - should we scan again
- // for all controllers? Seems like we should just do this once and then filter. That said here we are
- // only scanning our own single assembly. Hrm.
-
- composition.RegisterControllers(assembly);
- return composition;
- }
-
- private static void RegisterControllers(this Composition composition, Assembly assembly)
- {
- var types = composition.TypeLoader.GetTypes(specificAssemblies: new[] { assembly });
- foreach (var type in types)
- composition.Register(type, Lifetime.Request);
+ foreach (var controllerType in controllerTypes)
+ composition.Register(controllerType, Lifetime.Request);
}
}
}
diff --git a/src/Umbraco.Web/Editors/DictionaryController.cs b/src/Umbraco.Web/Editors/DictionaryController.cs
index 7d846e68ec..cd3141c7b9 100644
--- a/src/Umbraco.Web/Editors/DictionaryController.cs
+++ b/src/Umbraco.Web/Editors/DictionaryController.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web.Http;
@@ -194,7 +195,7 @@ namespace Umbraco.Web.Editors
const int level = 0;
- foreach (var dictionaryItem in Services.LocalizationService.GetRootDictionaryItems())
+ foreach (var dictionaryItem in Services.LocalizationService.GetRootDictionaryItems().OrderBy(ItemSort()))
{
var item = Mapper.Map(dictionaryItem);
item.Level = 0;
@@ -220,8 +221,7 @@ namespace Umbraco.Web.Editors
///
private void GetChildItemsForList(IDictionaryItem dictionaryItem, int level, List list)
{
- foreach (var childItem in Services.LocalizationService.GetDictionaryItemChildren(
- dictionaryItem.Key))
+ foreach (var childItem in Services.LocalizationService.GetDictionaryItemChildren(dictionaryItem.Key).OrderBy(ItemSort()))
{
var item = Mapper.Map(childItem);
item.Level = level;
@@ -230,5 +230,7 @@ namespace Umbraco.Web.Editors
GetChildItemsForList(childItem, level + 1, list);
}
}
+
+ private Func ItemSort() => item => item.ItemKey;
}
}
diff --git a/src/Umbraco.Web/Runtime/WebRuntimeComponent.cs b/src/Umbraco.Web/Runtime/WebRuntimeComponent.cs
index 1e5dd51c7d..601692b0e9 100644
--- a/src/Umbraco.Web/Runtime/WebRuntimeComponent.cs
+++ b/src/Umbraco.Web/Runtime/WebRuntimeComponent.cs
@@ -121,8 +121,7 @@ namespace Umbraco.Web.Runtime
composition.ConfigureForWeb();
composition
- .ComposeMvcControllers(GetType().Assembly)
- .ComposeApiControllers(GetType().Assembly);
+ .ComposeUmbracoControllers(GetType().Assembly);
composition.WithCollectionBuilder()
.Add(() => composition.TypeLoader.GetTypes()); // fixme which searchable trees?!
diff --git a/src/Umbraco.Web/Trees/DictionaryTreeController.cs b/src/Umbraco.Web/Trees/DictionaryTreeController.cs
index d0a7fce3ad..cac2e7f435 100644
--- a/src/Umbraco.Web/Trees/DictionaryTreeController.cs
+++ b/src/Umbraco.Web/Trees/DictionaryTreeController.cs
@@ -2,6 +2,7 @@
using System.Linq;
using System.Net.Http.Formatting;
using Umbraco.Core;
+using Umbraco.Core.Models;
using Umbraco.Core.Services;
using Umbraco.Web.Actions;
@@ -52,10 +53,12 @@ namespace Umbraco.Web.Trees
var nodes = new TreeNodeCollection();
+ Func ItemSort() => item => item.ItemKey;
+
if (id == Constants.System.Root.ToInvariantString())
{
nodes.AddRange(
- Services.LocalizationService.GetRootDictionaryItems().Select(
+ Services.LocalizationService.GetRootDictionaryItems().OrderBy(ItemSort()).Select(
x => CreateTreeNode(
x.Id.ToInvariantString(),
id,
@@ -71,7 +74,7 @@ namespace Umbraco.Web.Trees
if (parentDictionary == null)
return nodes;
- nodes.AddRange(Services.LocalizationService.GetDictionaryItemChildren(parentDictionary.Key).ToList().OrderByDescending(item => item.Key).Select(
+ nodes.AddRange(Services.LocalizationService.GetDictionaryItemChildren(parentDictionary.Key).ToList().OrderBy(ItemSort()).Select(
x => CreateTreeNode(
x.Id.ToInvariantString(),
id,