From c35369fabc99819ba8e2052a691a5c5380372f86 Mon Sep 17 00:00:00 2001 From: Louis DeJardin Date: Fri, 25 Jun 2010 10:11:50 -0700 Subject: [PATCH] Roughing in extensibility model for content type definition editor IEventHandler method invocation will aggregate return values of type IEnumerable Added IContentDefinitionEditorEvents and ContentDefinitionEditorEventsBase to framework assembly ContentTypes editor fires events to build and apply additional type and typepart TemplateViewModel classes As an example, added some content type settings to devtools "ShowDebugLinks" In content type definition builder moved DisplayedAs(name) into it's own method Added Html.RenderTemplates helper to execute renderpartial with prefix directly to bypass (for some cases) Html.EditorXxx, Html.DisplayXxx --HG-- branch : dev --- .../Blueprint/ShellDescriptorManagerTests.cs | 4 +- src/Orchard.Tests/Events/EventTests.cs | 64 +++++++++++- .../EditorTemplates/ContentTypePart.ascx | 13 --- .../Controllers/AdminController.cs | 98 +++++++++++++++---- .../Orchard.ContentTypes.csproj | 1 + .../ViewModels/EditTypeViewModel.cs | 5 + .../Views/Admin/Edit.ascx | 5 +- .../Views/EditorTemplates/Part.ascx | 4 +- .../Handlers/DebugLinkHandler.cs | 8 +- .../Orchard.DevTools/Orchard.DevTools.csproj | 2 + .../Settings/DevToolsSettings.cs | 30 ++++++ .../DefinitionTemplates/DevToolsSettings.ascx | 6 ++ src/Orchard.Web/Orchard.Web.csproj | 4 +- .../Builders/ContentTypeDefinitionBuilder.cs | 5 +- .../IContentDefinitionEditorEvents.cs | 40 ++++++++ .../MetaData/Models/SettingsDictionary.cs | 14 +-- .../MetaData/Services/SettingsFormatter.cs | 2 +- src/Orchard/Events/DefaultOrchardEventBus.cs | 59 +++++++---- src/Orchard/Events/EventsInterceptor.cs | 37 ++++++- src/Orchard/Events/IEventBus.cs | 5 +- .../Mvc/Html/TemplateViewModelExtensions.cs | 34 +++++++ src/Orchard/Orchard.Framework.csproj | 2 + 22 files changed, 370 insertions(+), 72 deletions(-) delete mode 100644 src/Orchard.Web/Core/Contents/Views/EditorTemplates/ContentTypePart.ascx create mode 100644 src/Orchard.Web/Modules/Orchard.DevTools/Settings/DevToolsSettings.cs create mode 100644 src/Orchard.Web/Modules/Orchard.DevTools/Views/DefinitionTemplates/DevToolsSettings.ascx create mode 100644 src/Orchard/ContentManagement/MetaData/IContentDefinitionEditorEvents.cs create mode 100644 src/Orchard/Mvc/Html/TemplateViewModelExtensions.cs diff --git a/src/Orchard.Tests.Modules/Settings/Blueprint/ShellDescriptorManagerTests.cs b/src/Orchard.Tests.Modules/Settings/Blueprint/ShellDescriptorManagerTests.cs index 6a2d67d09..be1d6f822 100644 --- a/src/Orchard.Tests.Modules/Settings/Blueprint/ShellDescriptorManagerTests.cs +++ b/src/Orchard.Tests.Modules/Settings/Blueprint/ShellDescriptorManagerTests.cs @@ -1,4 +1,5 @@ using System; +using System.Collections; using System.Collections.Generic; using System.Linq; using Autofac; @@ -28,9 +29,10 @@ namespace Orchard.Tests.Modules.Settings.Blueprint { public void Notify_Obsolete(string messageName, IDictionary eventData) { } - public void Notify(string messageName, Dictionary eventData) { + public IEnumerable Notify(string messageName, Dictionary eventData) { LastMessageName = messageName; LastEventData = eventData; + return new object[0]; } } diff --git a/src/Orchard.Tests/Events/EventTests.cs b/src/Orchard.Tests/Events/EventTests.cs index c846a5b39..585876a0a 100644 --- a/src/Orchard.Tests/Events/EventTests.cs +++ b/src/Orchard.Tests/Events/EventTests.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Linq; using Autofac; using NUnit.Framework; using Orchard.Events; @@ -13,10 +14,13 @@ namespace Orchard.Tests.Events { [SetUp] public void Init() { - var builder = new ContainerBuilder(); _eventHandler = new StubEventHandler(); - builder.RegisterInstance(_eventHandler).As(); + + var builder = new ContainerBuilder(); builder.RegisterType().As(); + builder.RegisterType().As(); + builder.RegisterInstance(_eventHandler).As(); + _container = builder.Build(); _eventBus = _container.Resolve(); } @@ -139,6 +143,25 @@ namespace Orchard.Tests.Events { Assert.Throws(() => _eventBus.Notify("StubEventHandlerIncrement", new Dictionary())); } + [Test] + public void InterceptorCanCoerceResultingCollection() { + var data = new object[]{"5","18","2"}; + var adjusted = EventsInterceptor.Adjust(data, typeof(IEnumerable)); + Assert.That(data, Is.InstanceOf>()); + Assert.That(data, Is.Not.InstanceOf>()); + Assert.That(adjusted, Is.InstanceOf>()); + } + + [Test] + public void EnumerableResultsAreTreatedLikeSelectMany() { + var results = _eventBus.Notify("ITestEventHandler.Gather", new Dictionary { { "a", 42 }, { "b", "alpha" } }).Cast(); + Assert.That(results.Count(), Is.EqualTo(3)); + Assert.That(results, Has.Some.EqualTo("42")); + Assert.That(results, Has.Some.EqualTo("alpha")); + Assert.That(results, Has.Some.EqualTo("[42,alpha]")); + } + + public interface ITestEventHandler : IEventHandler { void Increment(); void Sum(int a); @@ -146,6 +169,7 @@ namespace Orchard.Tests.Events { void Sum(int a, int b, int c); void Substract(int a, int b); void Concat(string a, string b, string c); + IEnumerable Gather(int a, string b); } public class StubEventHandler : ITestEventHandler { @@ -162,7 +186,7 @@ namespace Orchard.Tests.Events { } public void Sum(int a, int b) { - Result = 2 * ( a + b ); + Result = 2 * (a + b); } public void Sum(int a, int b, int c) { @@ -176,7 +200,39 @@ namespace Orchard.Tests.Events { public void Concat(string a, string b, string c) { Summary = a + b + c; } - } + public IEnumerable Gather(int a, string b) { + yield return String.Format("[{0},{1}]", a, b); + } + } + public class StubEventHandler2 : ITestEventHandler { + public void Increment() { + throw new NotImplementedException(); + } + + public void Sum(int a) { + throw new NotImplementedException(); + } + + public void Sum(int a, int b) { + throw new NotImplementedException(); + } + + public void Sum(int a, int b, int c) { + throw new NotImplementedException(); + } + + public void Substract(int a, int b) { + throw new NotImplementedException(); + } + + public void Concat(string a, string b, string c) { + throw new NotImplementedException(); + } + + public IEnumerable Gather(int a, string b) { + return new[] { a.ToString(), b }; + } + } } } diff --git a/src/Orchard.Web/Core/Contents/Views/EditorTemplates/ContentTypePart.ascx b/src/Orchard.Web/Core/Contents/Views/EditorTemplates/ContentTypePart.ascx deleted file mode 100644 index 2cb7e4dc6..000000000 --- a/src/Orchard.Web/Core/Contents/Views/EditorTemplates/ContentTypePart.ascx +++ /dev/null @@ -1,13 +0,0 @@ -<%@ Control Language="C#" Inherits="Orchard.Mvc.ViewUserControl" %> -<%@ Import Namespace="Orchard.ContentManagement.MetaData.Models" %> -
-

<%:Model.PartDefinition.Name %>

-
- <%--// these inline forms can't be here. should probably have some JavaScript in here to build up the forms and add the "remove" link. - // get the antiforgery token from the edit type form and mark up the part in a semantic way so I can get some info from the DOM --%> - <% using (Html.BeginFormAntiForgeryPost(Url.Action("RemovePart", new { area = "Contents" }), FormMethod.Post, new {@class = "inline link"})) { %> - <%=Html.Hidden("name", Model.PartDefinition.Name, new { id = "" }) %> - - <% } %> -
-
\ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.ContentTypes/Controllers/AdminController.cs b/src/Orchard.Web/Modules/Orchard.ContentTypes/Controllers/AdminController.cs index 1781e039c..9bfc9cf20 100644 --- a/src/Orchard.Web/Modules/Orchard.ContentTypes/Controllers/AdminController.cs +++ b/src/Orchard.Web/Modules/Orchard.ContentTypes/Controllers/AdminController.cs @@ -1,7 +1,11 @@ -using System.Linq; +using System; +using System.Collections.Generic; +using System.Linq; using System.Web.Mvc; using Orchard.ContentManagement; +using Orchard.ContentManagement.MetaData; using Orchard.ContentManagement.MetaData.Models; +using Orchard.ContentManagement.ViewModels; using Orchard.ContentTypes.Services; using Orchard.ContentTypes.ViewModels; using Orchard.Data; @@ -14,20 +18,26 @@ namespace Orchard.ContentTypes.Controllers { public class AdminController : Controller { private readonly INotifier _notifier; private readonly IContentDefinitionService _contentDefinitionService; + private readonly IContentDefinitionManager _contentDefinitionManager; private readonly IContentManager _contentManager; private readonly ITransactionManager _transactionManager; + private readonly IContentDefinitionEditorEvents _extendViewModels; public AdminController( IOrchardServices orchardServices, INotifier notifier, IContentDefinitionService contentDefinitionService, + IContentDefinitionManager contentDefinitionManager, IContentManager contentManager, - ITransactionManager transactionManager) { + ITransactionManager transactionManager, + IContentDefinitionEditorEvents extendViewModels) { Services = orchardServices; _notifier = notifier; _contentDefinitionService = contentDefinitionService; + _contentDefinitionManager = contentDefinitionManager; _contentManager = contentManager; _transactionManager = transactionManager; + _extendViewModels = extendViewModels; T = NullLocalizer.Instance; Logger = NullLogger.Instance; } @@ -72,6 +82,19 @@ namespace Orchard.ContentTypes.Controllers { return RedirectToAction("Index"); } + class Updater : IUpdateModel { + public AdminController Thunk { get; set; } + public Func _prefix = x => x; + + public bool TryUpdateModel(TModel model, string prefix, string[] includeProperties, string[] excludeProperties) where TModel : class { + return Thunk.TryUpdateModel(model, _prefix(prefix), includeProperties, excludeProperties); + } + + public void AddModelError(string key, LocalizedString errorMessage) { + Thunk.ModelState.AddModelError(_prefix(key), errorMessage.ToString()); + } + } + public ActionResult Edit(string id) { if (!Services.Authorizer.Authorize(Permissions.CreateContentTypes, T("Not allowed to edit a content type."))) return new HttpUnauthorizedResult(); @@ -81,7 +104,19 @@ namespace Orchard.ContentTypes.Controllers { if (contentTypeDefinition == null) return new NotFoundResult(); - return View(new EditTypeViewModel(contentTypeDefinition)); + var viewModel = new EditTypeViewModel(contentTypeDefinition); + viewModel.Parts = viewModel.Parts.ToArray(); + + viewModel.Templates = _extendViewModels.TypeEditor(contentTypeDefinition); + var entries = viewModel.Parts.Join(contentTypeDefinition.Parts, + m => m.PartDefinition.Name, + d => d.PartDefinition.Name, + (model, definition) => new { model, definition }); + foreach (var entry in entries) { + entry.model.Templates = _extendViewModels.TypePartEditor(entry.definition); + } + + return View(viewModel); } [HttpPost, ActionName("Edit")] @@ -94,26 +129,53 @@ namespace Orchard.ContentTypes.Controllers { if (contentTypeDefinition == null) return new NotFoundResult(); + var updater = new Updater { Thunk = this }; + var viewModel = new EditTypeViewModel(); TryUpdateModel(viewModel); - if (!ModelState.IsValid) - return Edit(id); - var contentTypeDefinitionParts = viewModel.Parts.Select(GenerateTypePart).ToList(); - if (viewModel.Fields.Any()) - contentTypeDefinitionParts.Add(GenerateTypePart(viewModel)); + _contentDefinitionManager.AlterTypeDefinition(id, typeBuilder => { - //todo: apply the changes along the lines of but definately not resembling - // for now this _might_ just get a little messy -> - _contentDefinitionService.AlterTypeDefinition( - new ContentTypeDefinition( - viewModel.Name, - viewModel.DisplayName, - contentTypeDefinitionParts, - viewModel.Settings - ) - ); + typeBuilder.DisplayedAs(viewModel.DisplayName); + + // allow extensions to alter type configuration + viewModel.Templates = _extendViewModels.TypeEditorUpdate(typeBuilder, updater); + + foreach (var entry in viewModel.Parts.Select((part, index) => new { part, index })) { + var partViewModel = entry.part; + + // enable updater to be aware of changing part prefix + var firstHalf = "Parts[" + entry.index + "]."; + updater._prefix = secondHalf => firstHalf + secondHalf; + + // allow extensions to alter typePart configuration + typeBuilder.WithPart(entry.part.PartDefinition.Name, typePartBuilder => { + partViewModel.Templates = _extendViewModels.TypePartEditorUpdate(typePartBuilder, updater); + }); + } + }); + + + if (!ModelState.IsValid) { + _transactionManager.Cancel(); + return View(viewModel); + } + + //var contentTypeDefinitionParts = viewModel.Parts.Select(GenerateTypePart).ToList(); + //if (viewModel.Fields.Any()) + // contentTypeDefinitionParts.Add(GenerateTypePart(viewModel)); + + ////todo: apply the changes along the lines of but definately not resembling + //// for now this _might_ just get a little messy -> + //_contentDefinitionService.AlterTypeDefinition( + // new ContentTypeDefinition( + // viewModel.Name, + // viewModel.DisplayName, + // contentTypeDefinitionParts, + // viewModel.Settings + // ) + // ); return RedirectToAction("Index"); } diff --git a/src/Orchard.Web/Modules/Orchard.ContentTypes/Orchard.ContentTypes.csproj b/src/Orchard.Web/Modules/Orchard.ContentTypes/Orchard.ContentTypes.csproj index 52587c273..1b4a2bc2b 100644 --- a/src/Orchard.Web/Modules/Orchard.ContentTypes/Orchard.ContentTypes.csproj +++ b/src/Orchard.Web/Modules/Orchard.ContentTypes/Orchard.ContentTypes.csproj @@ -108,6 +108,7 @@ + diff --git a/src/Orchard.Web/Modules/Orchard.ContentTypes/ViewModels/EditTypeViewModel.cs b/src/Orchard.Web/Modules/Orchard.ContentTypes/ViewModels/EditTypeViewModel.cs index 5ff54a160..89203c4aa 100644 --- a/src/Orchard.Web/Modules/Orchard.ContentTypes/ViewModels/EditTypeViewModel.cs +++ b/src/Orchard.Web/Modules/Orchard.ContentTypes/ViewModels/EditTypeViewModel.cs @@ -1,12 +1,14 @@ using System.Collections.Generic; using System.Linq; using Orchard.ContentManagement.MetaData.Models; +using Orchard.ContentManagement.ViewModels; using Orchard.Mvc.ViewModels; namespace Orchard.ContentTypes.ViewModels { public class EditTypeViewModel : BaseViewModel { public EditTypeViewModel() { Settings = new SettingsDictionary(); + Fields = new List(); Parts = new List(); } public EditTypeViewModel(ContentTypeDefinition contentTypeDefinition) { @@ -19,6 +21,8 @@ namespace Orchard.ContentTypes.ViewModels { public string Name { get; set; } public string DisplayName { get; set; } + public IEnumerable Templates { get; set; } + public SettingsDictionary Settings { get; set; } public IEnumerable Fields { get; set; } public IEnumerable Parts { get; set; } @@ -47,6 +51,7 @@ namespace Orchard.ContentTypes.ViewModels { public EditPartViewModel PartDefinition { get; set; } public SettingsDictionary Settings { get; set; } + public IEnumerable Templates { get; set; } } public class EditPartViewModel : BaseViewModel { diff --git a/src/Orchard.Web/Modules/Orchard.ContentTypes/Views/Admin/Edit.ascx b/src/Orchard.Web/Modules/Orchard.ContentTypes/Views/Admin/Edit.ascx index e32d26d0c..4e7299714 100644 --- a/src/Orchard.Web/Modules/Orchard.ContentTypes/Views/Admin/Edit.ascx +++ b/src/Orchard.Web/Modules/Orchard.ContentTypes/Views/Admin/Edit.ascx @@ -1,5 +1,6 @@ <%@ Control Language="C#" Inherits="Orchard.Mvc.ViewUserControl" %> -<%@ Import Namespace="Orchard.Core.Contents.ViewModels" %><% +<%@ Import Namespace="Orchard.Core.Contents.ViewModels" %> +<%@ Import Namespace="Orchard.ContentTypes.ViewModels" %><% Html.RegisterStyle("admin.css"); %>

<%:Html.TitleForPage(T("Edit Content Type").ToString())%>

<% @@ -13,7 +14,7 @@ using (Html.BeginFormAntiForgeryPost()) { %> <%:Html.TextBoxFor(m => m.Name, new {@class = "textMedium", disabled = "disabled"}) %> <%:Html.HiddenFor(m => m.Name) %> - <%:Html.EditorFor(m => m.Settings) %> + <% Html.RenderTemplate(Model.Templates); %>

<%:T("Parts") %>

<%: Html.ActionLink(T("Add").Text, "AddPart", new { }, new { @class = "button" }) %>
<%:Html.EditorFor(m => m.Parts, "Parts", "") %> diff --git a/src/Orchard.Web/Modules/Orchard.ContentTypes/Views/EditorTemplates/Part.ascx b/src/Orchard.Web/Modules/Orchard.ContentTypes/Views/EditorTemplates/Part.ascx index b50b43632..53e44cb7b 100644 --- a/src/Orchard.Web/Modules/Orchard.ContentTypes/Views/EditorTemplates/Part.ascx +++ b/src/Orchard.Web/Modules/Orchard.ContentTypes/Views/EditorTemplates/Part.ascx @@ -1,5 +1,6 @@ <%@ Control Language="C#" Inherits="Orchard.Mvc.ViewUserControl" %> <%@ Import Namespace="Orchard.Core.Contents.ViewModels" %> +<%@ Import Namespace="Orchard.ContentTypes.ViewModels" %>

<%:Model.PartDefinition.Name %>

@@ -11,7 +12,8 @@ <% } %> --%>
- <%:Html.EditorFor(m => m.Settings, "Settings", "") %> + <% Html.RenderTemplate(Model.Templates); %> +

<%:T("Global configuration") %>

<%:Html.ActionLink(T("Edit").Text, "EditPart", new { area = "Orchard.ContentTypes", id = Model.PartDefinition.Name }) %>
<%:Html.DisplayFor(m => m.PartDefinition.Settings, "Settings", "PartDefinition") %> diff --git a/src/Orchard.Web/Modules/Orchard.DevTools/Handlers/DebugLinkHandler.cs b/src/Orchard.Web/Modules/Orchard.DevTools/Handlers/DebugLinkHandler.cs index c0b69812d..1348ce75f 100644 --- a/src/Orchard.Web/Modules/Orchard.DevTools/Handlers/DebugLinkHandler.cs +++ b/src/Orchard.Web/Modules/Orchard.DevTools/Handlers/DebugLinkHandler.cs @@ -7,10 +7,14 @@ namespace Orchard.DevTools.Handlers { [UsedImplicitly] public class DebugLinkHandler : ContentHandler { protected override void BuildDisplayModel(BuildDisplayModelContext context) { - context.AddDisplay(new TemplateViewModel(new ShowDebugLink { ContentItem = context.ContentItem }) { TemplateName="Parts/DevTools.ShowDebugLink", ZoneName = "recap", Position = "9999" }); + var devToolsSettings = context.ContentItem.TypeDefinition.Settings.GetModel(); + if (devToolsSettings.ShowDebugLinks) + context.AddDisplay(new TemplateViewModel(new ShowDebugLink { ContentItem = context.ContentItem }) { TemplateName = "Parts/DevTools.ShowDebugLink", ZoneName = "recap", Position = "9999" }); } protected override void BuildEditorModel(BuildEditorModelContext context) { - context.AddEditor(new TemplateViewModel(new ShowDebugLink { ContentItem = context.ContentItem }) { TemplateName = "Parts/DevTools.ShowDebugLink", ZoneName = "recap", Position = "9999" }); + var devToolsSettings = context.ContentItem.TypeDefinition.Settings.GetModel(); + if (devToolsSettings.ShowDebugLinks) + context.AddEditor(new TemplateViewModel(new ShowDebugLink { ContentItem = context.ContentItem }) { TemplateName = "Parts/DevTools.ShowDebugLink", ZoneName = "recap", Position = "9999" }); } } } \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.DevTools/Orchard.DevTools.csproj b/src/Orchard.Web/Modules/Orchard.DevTools/Orchard.DevTools.csproj index 8ed2402e5..d348d426b 100644 --- a/src/Orchard.Web/Modules/Orchard.DevTools/Orchard.DevTools.csproj +++ b/src/Orchard.Web/Modules/Orchard.DevTools/Orchard.DevTools.csproj @@ -80,6 +80,7 @@ + @@ -91,6 +92,7 @@ + diff --git a/src/Orchard.Web/Modules/Orchard.DevTools/Settings/DevToolsSettings.cs b/src/Orchard.Web/Modules/Orchard.DevTools/Settings/DevToolsSettings.cs new file mode 100644 index 000000000..cb8b46aac --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.DevTools/Settings/DevToolsSettings.cs @@ -0,0 +1,30 @@ +using System.Collections.Generic; +using Orchard.ContentManagement; +using Orchard.ContentManagement.MetaData; +using Orchard.ContentManagement.MetaData.Builders; +using Orchard.ContentManagement.MetaData.Models; +using Orchard.ContentManagement.ViewModels; + +namespace Orchard.DevTools.Settings { + + + public class DevToolsSettings { + public bool ShowDebugLinks { get; set; } + } + + public class DevToolsSettingsHooks : ContentDefinitionEditorEventsBase { + public override IEnumerable TypeEditor(ContentTypeDefinition definition) { + var model = definition.Settings.GetModel(); + yield return DefinitionTemplate(model); + } + + public override IEnumerable TypeEditorUpdate(ContentTypeDefinitionBuilder builder, IUpdateModel updateModel) { + var model = new DevToolsSettings(); + updateModel.TryUpdateModel(model, "DevToolsSettings", null, null); + builder + .WithSetting("DevToolsSettings.ShowDebugLinks", model.ShowDebugLinks ? true.ToString() : null); + + yield return DefinitionTemplate(model); + } + } +} diff --git a/src/Orchard.Web/Modules/Orchard.DevTools/Views/DefinitionTemplates/DevToolsSettings.ascx b/src/Orchard.Web/Modules/Orchard.DevTools/Views/DefinitionTemplates/DevToolsSettings.ascx new file mode 100644 index 000000000..0e80c365d --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.DevTools/Views/DefinitionTemplates/DevToolsSettings.ascx @@ -0,0 +1,6 @@ +<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl" %> +
+ <%:Html.LabelFor(m=>m.ShowDebugLinks) %> + <%:Html.EditorFor(m=>m.ShowDebugLinks) %> + <%:Html.ValidationMessageFor(m=>m.ShowDebugLinks) %> +
diff --git a/src/Orchard.Web/Orchard.Web.csproj b/src/Orchard.Web/Orchard.Web.csproj index 8b22c45bb..6708ef6e6 100644 --- a/src/Orchard.Web/Orchard.Web.csproj +++ b/src/Orchard.Web/Orchard.Web.csproj @@ -100,7 +100,9 @@ - + + Designer + diff --git a/src/Orchard/ContentManagement/MetaData/Builders/ContentTypeDefinitionBuilder.cs b/src/Orchard/ContentManagement/MetaData/Builders/ContentTypeDefinitionBuilder.cs index 96fc52c37..dfe1747aa 100644 --- a/src/Orchard/ContentManagement/MetaData/Builders/ContentTypeDefinitionBuilder.cs +++ b/src/Orchard/ContentManagement/MetaData/Builders/ContentTypeDefinitionBuilder.cs @@ -40,7 +40,7 @@ namespace Orchard.ContentManagement.MetaData.Builders { return this; } - public ContentTypeDefinitionBuilder DisplayedAs(string displayName ) { + public ContentTypeDefinitionBuilder DisplayedAs(string displayName) { _displayName = displayName; return this; } @@ -84,9 +84,12 @@ namespace Orchard.ContentManagement.MetaData.Builders { protected readonly SettingsDictionary _settings; protected PartConfigurer(ContentTypeDefinition.Part part) { + Name = part.PartDefinition.Name; _settings = new SettingsDictionary(part.Settings.ToDictionary(kv => kv.Key, kv => kv.Value)); } + public string Name { get; private set; } + public PartConfigurer WithSetting(string name, string value) { _settings[name] = value; return this; diff --git a/src/Orchard/ContentManagement/MetaData/IContentDefinitionEditorEvents.cs b/src/Orchard/ContentManagement/MetaData/IContentDefinitionEditorEvents.cs new file mode 100644 index 000000000..fdcd05a87 --- /dev/null +++ b/src/Orchard/ContentManagement/MetaData/IContentDefinitionEditorEvents.cs @@ -0,0 +1,40 @@ +using System.Collections.Generic; +using System.Linq; +using Orchard.ContentManagement.MetaData.Builders; +using Orchard.ContentManagement.MetaData.Models; +using Orchard.ContentManagement.ViewModels; +using Orchard.Events; + +namespace Orchard.ContentManagement.MetaData { + public interface IContentDefinitionEditorEvents : IEventHandler { + IEnumerable TypeEditor(ContentTypeDefinition definition); + IEnumerable TypePartEditor(ContentTypeDefinition.Part definition); + + IEnumerable TypeEditorUpdate(ContentTypeDefinitionBuilder builder, IUpdateModel updateModel); + IEnumerable TypePartEditorUpdate(ContentTypeDefinitionBuilder.PartConfigurer builder, IUpdateModel updateModel); + } + + public abstract class ContentDefinitionEditorEventsBase : IContentDefinitionEditorEvents { + public virtual IEnumerable TypeEditor(ContentTypeDefinition definition) { + return Enumerable.Empty(); + } + + public virtual IEnumerable TypePartEditor(ContentTypeDefinition.Part definition) { + return Enumerable.Empty(); + } + + public virtual IEnumerable TypeEditorUpdate(ContentTypeDefinitionBuilder builder, IUpdateModel updateModel) { + return Enumerable.Empty(); + } + + public virtual IEnumerable TypePartEditorUpdate(ContentTypeDefinitionBuilder.PartConfigurer builder, IUpdateModel updateModel) { + return Enumerable.Empty(); + } + + protected static TemplateViewModel DefinitionTemplate(TModel model) { + return new TemplateViewModel(model, typeof(TModel).Name) { + TemplateName = "DefinitionTemplates/" + typeof(TModel).Name + }; + } + } +} \ No newline at end of file diff --git a/src/Orchard/ContentManagement/MetaData/Models/SettingsDictionary.cs b/src/Orchard/ContentManagement/MetaData/Models/SettingsDictionary.cs index f237d5427..e1f413cc3 100644 --- a/src/Orchard/ContentManagement/MetaData/Models/SettingsDictionary.cs +++ b/src/Orchard/ContentManagement/MetaData/Models/SettingsDictionary.cs @@ -3,22 +3,22 @@ using System.Web.Mvc; namespace Orchard.ContentManagement.MetaData.Models { public class SettingsDictionary : Dictionary { - public SettingsDictionary() {} - public SettingsDictionary(IDictionary dictionary) : base(dictionary) {} + public SettingsDictionary() { } + public SettingsDictionary(IDictionary dictionary) : base(dictionary) { } - public T GetModel() { - return GetModel(null); + public T GetModel() where T : class, new() { + return GetModel(typeof(T).Name); } - public T GetModel(string key) { + public T GetModel(string key) where T : class, new() { var binder = new DefaultModelBinder(); var controllerContext = new ControllerContext(); var context = new ModelBindingContext { - ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(null, typeof (T)), + ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(null, typeof(T)), ModelName = key, ValueProvider = new DictionaryValueProvider(this, null) }; - return (T) binder.BindModel(controllerContext, context); + return (T)binder.BindModel(controllerContext, context) ?? new T(); } } } \ No newline at end of file diff --git a/src/Orchard/ContentManagement/MetaData/Services/SettingsFormatter.cs b/src/Orchard/ContentManagement/MetaData/Services/SettingsFormatter.cs index 6c070c93d..065d060c2 100644 --- a/src/Orchard/ContentManagement/MetaData/Services/SettingsFormatter.cs +++ b/src/Orchard/ContentManagement/MetaData/Services/SettingsFormatter.cs @@ -19,7 +19,7 @@ namespace Orchard.ContentManagement.MetaData.Services { if (source == null) return new XElement("settings"); - return new XElement("settings", source.Select(kv => new XAttribute(XmlConvert.EncodeLocalName(kv.Key), kv.Value))); + return new XElement("settings", source.Where(kv => kv.Value != null).Select(kv => new XAttribute(XmlConvert.EncodeLocalName(kv.Key), kv.Value))); } } } diff --git a/src/Orchard/Events/DefaultOrchardEventBus.cs b/src/Orchard/Events/DefaultOrchardEventBus.cs index c84f917ee..f78b2d6ac 100644 --- a/src/Orchard/Events/DefaultOrchardEventBus.cs +++ b/src/Orchard/Events/DefaultOrchardEventBus.cs @@ -1,4 +1,5 @@ using System; +using System.Collections; using System.Collections.Generic; using System.Linq; using System.Reflection; @@ -18,9 +19,13 @@ namespace Orchard.Events { public ILogger Logger { get; set; } public Localizer T { get; set; } - #region Implementation of IEventBus - public void Notify(string messageName, Dictionary eventData) { + public IEnumerable Notify(string messageName, Dictionary eventData) { + // call ToArray to ensure evaluation has taken place + return NotifyHandlers(messageName, eventData).ToArray(); + } + + private IEnumerable NotifyHandlers(string messageName, Dictionary eventData) { string[] parameters = messageName.Split('.'); if (parameters.Length != 2) { throw new ArgumentException(messageName + T(" is not formatted correctly")); @@ -30,40 +35,61 @@ namespace Orchard.Events { var eventHandlers = _eventHandlers(); foreach (var eventHandler in eventHandlers) { - try { - TryInvoke(eventHandler, interfaceName, methodName, eventData); - } - catch(Exception ex) { - Logger.Error(ex, "{2} thrown from {0} by {1}", - messageName, - eventHandler.GetType().FullName, - ex.GetType().Name); + IEnumerable returnValue; + if (TryNotifyHandler(eventHandler, messageName, interfaceName, methodName, eventData, out returnValue)) { + if (returnValue != null) { + foreach (var value in returnValue) { + yield return value; + } + } } } } - private static void TryInvoke(IEventHandler eventHandler, string interfaceName, string methodName, IDictionary arguments) { + private bool TryNotifyHandler(IEventHandler eventHandler, string messageName, string interfaceName, string methodName, Dictionary eventData, out IEnumerable returnValue) { + try { + return TryInvoke(eventHandler, interfaceName, methodName, eventData, out returnValue); + } + catch (Exception ex) { + Logger.Error(ex, "{2} thrown from {0} by {1}", + messageName, + eventHandler.GetType().FullName, + ex.GetType().Name); + + returnValue = null; + return false; + } + } + + private static bool TryInvoke(IEventHandler eventHandler, string interfaceName, string methodName, IDictionary arguments, out IEnumerable returnValue) { Type type = eventHandler.GetType(); foreach (var interfaceType in type.GetInterfaces()) { if (String.Equals(interfaceType.Name, interfaceName, StringComparison.OrdinalIgnoreCase)) { - TryInvokeMethod(eventHandler, interfaceType, methodName, arguments); - break; + return TryInvokeMethod(eventHandler, interfaceType, methodName, arguments, out returnValue); } } + returnValue = null; + return false; } - private static void TryInvokeMethod(IEventHandler eventHandler, Type interfaceType, string methodName, IDictionary arguments) { + private static bool TryInvokeMethod(IEventHandler eventHandler, Type interfaceType, string methodName, IDictionary arguments, out IEnumerable returnValue) { MethodInfo method = GetMatchingMethod(eventHandler, interfaceType, methodName, arguments); if (method != null) { List parameters = new List(); foreach (var methodParameter in method.GetParameters()) { parameters.Add(arguments[methodParameter.Name]); } - method.Invoke(eventHandler, parameters.ToArray()); + var result = method.Invoke(eventHandler, parameters.ToArray()); + returnValue = result as IEnumerable; + if (returnValue == null && result != null) + returnValue = new[] { result }; + return true; } + returnValue = null; + return false; } - private static MethodInfo GetMatchingMethod(IEventHandler eventHandler, Type interfaceType, string methodName, IDictionary arguments) { + private static MethodInfo GetMatchingMethod(IEventHandler eventHandler, Type interfaceType, string methodName, IDictionary arguments) { List allMethods = new List(interfaceType.GetMethods()); List candidates = new List(allMethods); @@ -89,6 +115,5 @@ namespace Orchard.Events { return null; } - #endregion } } diff --git a/src/Orchard/Events/EventsInterceptor.cs b/src/Orchard/Events/EventsInterceptor.cs index f40fbdbbb..def96f5a8 100644 --- a/src/Orchard/Events/EventsInterceptor.cs +++ b/src/Orchard/Events/EventsInterceptor.cs @@ -1,4 +1,8 @@ -using System.Linq; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; using Castle.Core.Interceptor; namespace Orchard.Events { @@ -17,7 +21,36 @@ namespace Orchard.Events { .Select((parameter, index) => new { parameter.Name, Value = invocation.Arguments[index] }) .ToDictionary(kv => kv.Name, kv => kv.Value); - _eventBus.Notify(interfaceName + "." + methodName, data); + var results = _eventBus.Notify(interfaceName + "." + methodName, data); + + invocation.ReturnValue = Adjust(results, invocation.Method.ReturnType); + } + + public static object Adjust(IEnumerable results, Type returnType) { + if (returnType == typeof(void) || + results == null || + results.GetType() == returnType) { + return results; + } + + // acquire method: + // static IEnumerable IEnumerable.OfType(this IEnumerable source) + // where T is from returnType's IEnumerable + var enumerableOfTypeT = typeof(Enumerable).GetGenericMethod("OfType", returnType.GetGenericArguments(), new[] { typeof(IEnumerable) }, typeof(IEnumerable<>)); + return enumerableOfTypeT.Invoke(null, new[] { results }); } } + + public static class Extensions { + public static MethodInfo GetGenericMethod(this Type t, string name, Type[] genericArgTypes, Type[] argTypes, Type returnType) { + return (from m in t.GetMethods(BindingFlags.Public | BindingFlags.Static) + where m.Name == name && + m.GetGenericArguments().Length == genericArgTypes.Length && + m.GetParameters().Select(pi => pi.ParameterType).SequenceEqual(argTypes) && + (m.ReturnType.IsGenericType && !m.ReturnType.IsGenericTypeDefinition ? returnType.GetGenericTypeDefinition() : m.ReturnType) == returnType + select m).Single().MakeGenericMethod(genericArgTypes); + + } + } + } diff --git a/src/Orchard/Events/IEventBus.cs b/src/Orchard/Events/IEventBus.cs index 9d0e2d159..2567f64f6 100644 --- a/src/Orchard/Events/IEventBus.cs +++ b/src/Orchard/Events/IEventBus.cs @@ -1,7 +1,8 @@ -using System.Collections.Generic; +using System.Collections; +using System.Collections.Generic; namespace Orchard.Events { public interface IEventBus : IDependency { - void Notify(string messageName, Dictionary eventData); + IEnumerable Notify(string messageName, Dictionary eventData); } } diff --git a/src/Orchard/Mvc/Html/TemplateViewModelExtensions.cs b/src/Orchard/Mvc/Html/TemplateViewModelExtensions.cs new file mode 100644 index 000000000..24cbb667f --- /dev/null +++ b/src/Orchard/Mvc/Html/TemplateViewModelExtensions.cs @@ -0,0 +1,34 @@ +using System.Collections.Generic; +using System.Web.Mvc; +using System.Web.Mvc.Html; +using Orchard.ContentManagement.ViewModels; + +namespace Orchard.Mvc.Html { + public static class TemplateViewModelExtensions { + public static void RenderTemplates(this HtmlHelper html, IEnumerable templates) { + if (templates == null) + return; + + foreach (var template in templates) { + html.RenderTemplates(template); + } + } + + public static void RenderTemplates(this HtmlHelper html, TemplateViewModel template) { + if (template.WasUsed) + return; + + template.WasUsed = true; + + var templateInfo = html.ViewContext.ViewData.TemplateInfo; + var htmlFieldPrefix = templateInfo.HtmlFieldPrefix; + try { + templateInfo.HtmlFieldPrefix = templateInfo.GetFullHtmlFieldName(template.Prefix); + html.RenderPartial(template.TemplateName, template.Model); + } + finally { + templateInfo.HtmlFieldPrefix = htmlFieldPrefix; + } + } + } +} \ No newline at end of file diff --git a/src/Orchard/Orchard.Framework.csproj b/src/Orchard/Orchard.Framework.csproj index b7a9e4984..418cd1813 100644 --- a/src/Orchard/Orchard.Framework.csproj +++ b/src/Orchard/Orchard.Framework.csproj @@ -310,6 +310,7 @@ Code + @@ -445,6 +446,7 @@ +