diff --git a/src/Orchard.Web/Modules/Orchard.Blogs/Commands/BlogWidgetCommands.cs b/src/Orchard.Web/Modules/Orchard.Blogs/Commands/BlogWidgetCommands.cs new file mode 100644 index 000000000..af390fd45 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Blogs/Commands/BlogWidgetCommands.cs @@ -0,0 +1,165 @@ +using System; +using System.Linq; +using Orchard.Blogs.Models; +using Orchard.Blogs.Services; +using Orchard.Commands; +using Orchard.ContentManagement; +using Orchard.ContentManagement.Aspects; +using Orchard.Core.Common.Models; +using Orchard.Security; +using Orchard.Settings; +using Orchard.Widgets.Models; +using Orchard.Widgets.Services; + +namespace Orchard.Blogs.Commands { + public class BlogWidgetCommands : DefaultOrchardCommandHandler { + private readonly IWidgetsService _widgetsService; + private readonly IBlogService _blogService; + private readonly ISiteService _siteService; + private readonly IMembershipService _membershipService; + private readonly IContentManager _contentManager; + + private BlogPart blog; + + public BlogWidgetCommands( + IWidgetsService widgetsService, + IBlogService blogService, + ISiteService siteService, + IMembershipService membershipService, + IContentManager contentManager) { + _widgetsService = widgetsService; + _blogService = blogService; + _siteService = siteService; + _membershipService = membershipService; + _contentManager = contentManager; + + RenderTitle = true; + } + + [OrchardSwitch] + public string Title { get; set; } + + [OrchardSwitch] + public string Name { get; set; } + + [OrchardSwitch] + public bool RenderTitle { get; set; } + + [OrchardSwitch] + public string Zone { get; set; } + + [OrchardSwitch] + public string Position { get; set; } + + [OrchardSwitch] + public string Layer { get; set; } + + [OrchardSwitch] + public string Identity { get; set; } + + [OrchardSwitch] + public string Owner { get; set; } + + [OrchardSwitch] + public string BlogPath { get; set; } + + [OrchardSwitch] + public int BlogId { get; set; } + + [OrchardSwitch] + public string Count { get; set; } + + [CommandName("blog widget create recentblogposts")] + [CommandHelp("blog widget create recentblogposts /Title: /Name:<name> /Zone:<zone> /Position:<position> /Layer:<layer> (/BlogId:<id> | /BlogPath:<path>) [/Identity:<identity>] [/RenderTitle:true|false] [/Owner:<owner>] [/Count:<count>]\r\n\t" + "Creates a new widget")] + [OrchardSwitches("Title,Name,Zone,Position,Layer,BlogId,BlogPath,Identity,Owner,RenderTitle,Count")] + public void CreateRecentBlogPostsWidget() { + var type = "RecentBlogPosts"; + + var widget = CreateStandardWidget(type); + if (widget == null) { + return; + } + + widget.As<RecentBlogPostsPart>().BlogId = blog.Id; + + // Setting count to 0 means all posts. It's an optional parameter and defaults to 5. + if (!string.IsNullOrWhiteSpace(Count)) { + int CountAsNumber = 0; + if (Int32.TryParse(Count, out CountAsNumber)) { + widget.As<RecentBlogPostsPart>().Count = CountAsNumber; + } + } + + _contentManager.Publish(widget.ContentItem); + Context.Output.WriteLine(T("{0} widget created successfully.", type).Text); + } + + [CommandName("blog widget create blogarchives")] + [CommandHelp("blog widget create blogarchives /Title:<title> /Name:<name> /Zone:<zone> /Position:<position> /Layer:<layer> (/BlogId:<id> | /BlogPath:<path>) [/Identity:<identity>] [/RenderTitle:true|false] [/Owner:<owner>]\r\n\t" + "Creates a new widget")] + [OrchardSwitches("Title,Name,Zone,Position,Layer,BlogId,BlogPath,Identity,Owner,RenderTitle")] + public void CreateBlogArchivesWidget() { + var type = "BlogArchives"; + + var widget = CreateStandardWidget(type); + if (widget == null) { + return; + } + + widget.As<BlogArchivesPart>().BlogId = blog.Id; + + _contentManager.Publish(widget.ContentItem); + Context.Output.WriteLine(T("{0} widget created successfully.", type).Text); + } + + private WidgetPart CreateStandardWidget(string type) { + var widgetTypeNames = _widgetsService.GetWidgetTypeNames().ToList(); + if (!widgetTypeNames.Contains(type)) { + Context.Output.WriteLine(T("Creating widget failed: type {0} was not found. Supported widget types are: {1}.", + type, + string.Join(" ", widgetTypeNames))); + return null; + } + + var layer = GetLayer(Layer); + if (layer == null) { + Context.Output.WriteLine(T("Creating {0} widget failed: layer {1} was not found.", type, Layer)); + return null; + } + + blog = GetBlog(BlogId, BlogPath); + if (blog == null) { + Context.Output.WriteLine(T("Creating {0} widget failed: blog was not found.", type)); + return null; + } + + var widget = _widgetsService.CreateWidget(layer.ContentItem.Id, type, T(Title).Text, Position, Zone); + + if (!String.IsNullOrWhiteSpace(Name)) { + widget.Name = Name.Trim(); + } + + widget.RenderTitle = RenderTitle; + + if (String.IsNullOrEmpty(Owner)) { + Owner = _siteService.GetSiteSettings().SuperUser; + } + var owner = _membershipService.GetUser(Owner); + widget.As<ICommonPart>().Owner = owner; + + if (widget.Has<IdentityPart>() && !String.IsNullOrEmpty(Identity)) { + widget.As<IdentityPart>().Identifier = Identity; + } + + return widget; + } + + private LayerPart GetLayer(string layer) { + var layers = _widgetsService.GetLayers(); + return layers.FirstOrDefault(layerPart => String.Equals(layerPart.Name, layer, StringComparison.OrdinalIgnoreCase)); + } + + private BlogPart GetBlog(int blogId, string blogPath) { + return _contentManager.Get<BlogPart>(blogId) ?? _blogService.Get(blogPath); + } + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Blogs/Orchard.Blogs.csproj b/src/Orchard.Web/Modules/Orchard.Blogs/Orchard.Blogs.csproj index 25f36dc12..d4b202396 100644 --- a/src/Orchard.Web/Modules/Orchard.Blogs/Orchard.Blogs.csproj +++ b/src/Orchard.Web/Modules/Orchard.Blogs/Orchard.Blogs.csproj @@ -93,6 +93,7 @@ </ItemGroup> <ItemGroup> <Compile Include="AdminMenu.cs" /> + <Compile Include="Commands\BlogWidgetCommands.cs" /> <Compile Include="Controllers\RemoteBlogPublishingController.cs" /> <Compile Include="Drivers\BlogArchivesPartDriver.cs" /> <Compile Include="Drivers\RemoteBlogPublishingDriver.cs" /> diff --git a/src/Orchard.Web/Modules/Orchard.Blogs/Services/BlogService.cs b/src/Orchard.Web/Modules/Orchard.Blogs/Services/BlogService.cs index ed3a16a34..b431dabca 100644 --- a/src/Orchard.Web/Modules/Orchard.Blogs/Services/BlogService.cs +++ b/src/Orchard.Web/Modules/Orchard.Blogs/Services/BlogService.cs @@ -17,7 +17,7 @@ namespace Orchard.Blogs.Services { private readonly ShellSettings _shellSettings; private readonly IShellDescriptorManager _shellDescriptorManager; private readonly HashSet<int> _processedBlogParts = new HashSet<int>(); - IPathResolutionService _pathResolutionService; + private readonly IPathResolutionService _pathResolutionService; public BlogService( IContentManager contentManager, diff --git a/src/Orchard.Web/Modules/Orchard.OutputCache/Controllers/AdminController.cs b/src/Orchard.Web/Modules/Orchard.OutputCache/Controllers/AdminController.cs index ef78b6522..7ed0c5e44 100644 --- a/src/Orchard.Web/Modules/Orchard.OutputCache/Controllers/AdminController.cs +++ b/src/Orchard.Web/Modules/Orchard.OutputCache/Controllers/AdminController.cs @@ -89,6 +89,7 @@ namespace Orchard.OutputCache.Controllers { DefaultMaxAge = settings.DefaultMaxAge, VaryByQueryStringParameters = settings.VaryByQueryStringParameters, VaryByRequestHeaders = settings.VaryByRequestHeaders, + VaryByRequestCookies = settings.VaryByRequestCookies, IgnoredUrls = settings.IgnoredUrls, IgnoreNoCache = settings.IgnoreNoCache, VaryByCulture = settings.VaryByCulture, @@ -116,6 +117,7 @@ namespace Orchard.OutputCache.Controllers { settings.DefaultMaxAge = model.DefaultMaxAge; settings.VaryByQueryStringParameters = model.VaryByQueryStringParameters; settings.VaryByRequestHeaders = model.VaryByRequestHeaders; + settings.VaryByRequestCookies = model.VaryByRequestCookies; settings.IgnoredUrls = model.IgnoredUrls; settings.IgnoreNoCache = model.IgnoreNoCache; settings.VaryByCulture = model.VaryByCulture; diff --git a/src/Orchard.Web/Modules/Orchard.OutputCache/Filters/OutputCacheFilter.cs b/src/Orchard.Web/Modules/Orchard.OutputCache/Filters/OutputCacheFilter.cs index 620fb6a03..8ca1703f2 100644 --- a/src/Orchard.Web/Modules/Orchard.OutputCache/Filters/OutputCacheFilter.cs +++ b/src/Orchard.Web/Modules/Orchard.OutputCache/Filters/OutputCacheFilter.cs @@ -397,6 +397,12 @@ namespace Orchard.OutputCache.Filters { result["HEADER:" + varyByRequestHeader] = requestHeaders[varyByRequestHeader]; } + // Vary by configured cookies. + var requestCookies = filterContext.RequestContext.HttpContext.Request.Cookies; + foreach (var varyByRequestCookies in CacheSettings.VaryByRequestCookies) { + if (requestCookies[varyByRequestCookies] != null) + result["COOKIE:" + varyByRequestCookies] = requestCookies[varyByRequestCookies].Value; + } // Vary by request culture if configured. if (CacheSettings.VaryByCulture) { diff --git a/src/Orchard.Web/Modules/Orchard.OutputCache/Models/CacheSettings.cs b/src/Orchard.Web/Modules/Orchard.OutputCache/Models/CacheSettings.cs index 62075c24d..7797d7382 100644 --- a/src/Orchard.Web/Modules/Orchard.OutputCache/Models/CacheSettings.cs +++ b/src/Orchard.Web/Modules/Orchard.OutputCache/Models/CacheSettings.cs @@ -14,6 +14,7 @@ namespace Orchard.OutputCache.Models { VaryByQueryStringParameters = String.IsNullOrWhiteSpace(part.VaryByQueryStringParameters) ? null : part.VaryByQueryStringParameters.Split(new[] { "," }, StringSplitOptions.RemoveEmptyEntries).Select(s => s.Trim()).ToArray(); VaryByRequestHeaders = String.IsNullOrWhiteSpace(part.VaryByRequestHeaders) ? new HashSet<string>() : new HashSet<string>(part.VaryByRequestHeaders.Split(new[] { "," }, StringSplitOptions.RemoveEmptyEntries).Select(s => s.Trim()).ToArray()); VaryByRequestHeaders.Add("HOST"); // Always vary by host name/tenant. + VaryByRequestCookies = String.IsNullOrWhiteSpace(part.VaryByRequestCookies) ? new HashSet<string>() : new HashSet<string>(part.VaryByRequestCookies.Split(new[] { "," }, StringSplitOptions.RemoveEmptyEntries).Select(s => s.Trim()).ToArray()); IgnoredUrls = String.IsNullOrWhiteSpace(part.IgnoredUrls) ? null : part.IgnoredUrls.Split(new[] { "\n" }, StringSplitOptions.RemoveEmptyEntries).Select(s => s.Trim()).ToArray(); IgnoreNoCache = part.IgnoreNoCache; VaryByCulture = part.VaryByCulture; @@ -27,6 +28,7 @@ namespace Orchard.OutputCache.Models { public int DefaultMaxAge { get; private set; } public IEnumerable<string> VaryByQueryStringParameters { get; private set; } public ISet<string> VaryByRequestHeaders { get; private set; } + public ISet<string> VaryByRequestCookies { get; private set; } public IEnumerable<string> IgnoredUrls { get; private set; } public bool IgnoreNoCache { get; private set; } public bool VaryByCulture { get; private set; } diff --git a/src/Orchard.Web/Modules/Orchard.OutputCache/Models/CacheSettingsPart.cs b/src/Orchard.Web/Modules/Orchard.OutputCache/Models/CacheSettingsPart.cs index b11eb3fae..69e8441fd 100644 --- a/src/Orchard.Web/Modules/Orchard.OutputCache/Models/CacheSettingsPart.cs +++ b/src/Orchard.Web/Modules/Orchard.OutputCache/Models/CacheSettingsPart.cs @@ -44,6 +44,11 @@ namespace Orchard.OutputCache.Models { } } + public string VaryByRequestCookies { + get { return this.Retrieve(x => x.VaryByRequestCookies); } + set { this.Store(x => x.VaryByRequestCookies, value); } + } + public string IgnoredUrls { get { return this.Retrieve(x => x.IgnoredUrls); } set { this.Store(x => x.IgnoredUrls, value); } diff --git a/src/Orchard.Web/Modules/Orchard.OutputCache/ViewModels/IndexViewModel.cs b/src/Orchard.Web/Modules/Orchard.OutputCache/ViewModels/IndexViewModel.cs index 1cda10ada..b79ff5509 100644 --- a/src/Orchard.Web/Modules/Orchard.OutputCache/ViewModels/IndexViewModel.cs +++ b/src/Orchard.Web/Modules/Orchard.OutputCache/ViewModels/IndexViewModel.cs @@ -11,6 +11,7 @@ namespace Orchard.OutputCache.ViewModels { [Range(0, Int32.MaxValue), Required] public int DefaultMaxAge { get; set; } public string VaryByQueryStringParameters { get; set; } public string VaryByRequestHeaders { get; set; } + public string VaryByRequestCookies { get; set; } public string IgnoredUrls { get; set; } public bool IgnoreNoCache { get; set; } public bool VaryByCulture { get; set; } diff --git a/src/Orchard.Web/Modules/Orchard.OutputCache/Views/Admin/Index.cshtml b/src/Orchard.Web/Modules/Orchard.OutputCache/Views/Admin/Index.cshtml index d2bf836de..9eb8a529f 100644 --- a/src/Orchard.Web/Modules/Orchard.OutputCache/Views/Admin/Index.cshtml +++ b/src/Orchard.Web/Modules/Orchard.OutputCache/Views/Admin/Index.cshtml @@ -46,7 +46,13 @@ @Html.TextBoxFor(m => m.VaryByRequestHeaders, new { @class = "text medium" }) <span class="hint">@T("When defined, using comma separated values, sets caching to vary by the specified request headers.")</span> </fieldset> - + + <fieldset> + <label>@T("Vary Cookie String Parameters")</label> + @Html.TextBoxFor(m => m.VaryByRequestCookies, new { @class = "text medium" }) + <span class="hint">@T("When defined, using comma separated values, sets caching to vary via specified cookie string parameters")</span> + </fieldset> + <fieldset> <label>@T("Ignored URLs")</label> @Html.TextAreaFor(m => m.IgnoredUrls, new { @class = "text medium" }) diff --git a/src/Orchard.Web/Modules/Orchard.Roles/Services/RoleService.cs b/src/Orchard.Web/Modules/Orchard.Roles/Services/RoleService.cs index a7c3ce0f7..3240669e0 100644 --- a/src/Orchard.Web/Modules/Orchard.Roles/Services/RoleService.cs +++ b/src/Orchard.Web/Modules/Orchard.Roles/Services/RoleService.cs @@ -46,7 +46,7 @@ namespace Orchard.Roles.Services { public IEnumerable<RoleRecord> GetRoles() { var roles = from role in _roleRepository.Table select role; - return roles.ToList(); + return roles; } public RoleRecord GetRole(int id) { diff --git a/src/Orchard.Web/Modules/Orchard.Setup/Recipes/blog.recipe.xml b/src/Orchard.Web/Modules/Orchard.Setup/Recipes/blog.recipe.xml index 49e8befb6..e5a5d69ed 100644 --- a/src/Orchard.Web/Modules/Orchard.Setup/Recipes/blog.recipe.xml +++ b/src/Orchard.Web/Modules/Orchard.Setup/Recipes/blog.recipe.xml @@ -56,13 +56,13 @@ layer create Anonymous /LayerRule:"not authenticated" /Description:"The widgets in this layer are displayed when the user is anonymous" layer create Disabled /LayerRule:"false" /Description:"The widgets in this layer are never displayed" layer create TheHomepage /LayerRule:"url '~/'" /Description:"The widgets in this layer are displayed on the home page" - widget create RecentBlogPosts /Title:"Recent Blog Posts" /Zone:"AsideSecond" /Position:"5" /Layer:"TheHomepage" /Identity:"RecentBlogPosts1" - widget create BlogArchives /Title:"Blog Archives" /Zone:"AsideSecond" /Position:"6" /Layer:"TheHomepage" /Identity:"BlogArchives1" - widget create TagCloud /Title:"Blog Post Tags" /Zone:"AsideSecond" /Position:"7" /Layer:"TheHomepage" /Identity:"TagCloud1" + blog create /Title:"Blog" /Homepage:true /Description:"This is your Orchard Blog." + blog widget create RecentBlogPosts /Title:"Recent Blog Posts" /Zone:"AsideSecond" /Position:"5" /Layer:"TheHomepage" /Identity:"RecentBlogPosts1" /BlogPath:"" + blog widget create BlogArchives /Title:"Blog Archives" /Zone:"AsideSecond" /Position:"60" /Layer:"TheHomepage" /Identity:"BlogArchives1" /BlogPath:"" + tags widget create TagCloud /Title:"Blog Post Tags" /Zone:"AsideSecond" /Position:"70" /Layer:"TheHomepage" /Identity:"TagCloud1" /Slug:"/" site setting set baseurl theme activate "The Theme Machine" menu create /MenuName:"Main Menu" - blog create /Title:"Blog" /Homepage:true /Description:"This is your Orchard Blog." menuitem create /MenuPosition:"0" /MenuText:"Home" /Url:"~/" /MenuName:"Main Menu" widget create MenuWidget /Title:"Main Menu" /RenderTitle:false /Zone:"Navigation" /Position:"1" /Layer:"Default" /Identity:"MenuWidget1" /MenuName:"Main Menu" </Command> diff --git a/src/Orchard.Web/Modules/Orchard.Tags/Commands/TagsWidgetCommands.cs b/src/Orchard.Web/Modules/Orchard.Tags/Commands/TagsWidgetCommands.cs new file mode 100644 index 000000000..0e9b79662 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Tags/Commands/TagsWidgetCommands.cs @@ -0,0 +1,116 @@ +using System; +using System.Linq; +using Orchard.Commands; +using Orchard.ContentManagement; +using Orchard.ContentManagement.Aspects; +using Orchard.Core.Common.Models; +using Orchard.Security; +using Orchard.Settings; +using Orchard.Tags.Models; +using Orchard.Widgets.Models; +using Orchard.Widgets.Services; + +namespace Orchard.Tags.Commands { + public class TagWidgetCommands : DefaultOrchardCommandHandler { + private readonly IWidgetsService _widgetsService; + private readonly ISiteService _siteService; + private readonly IMembershipService _membershipService; + private readonly IContentManager _contentManager; + + public TagWidgetCommands( + IWidgetsService widgetsService, + ISiteService siteService, + IMembershipService membershipService, + IContentManager contentManager) { + _widgetsService = widgetsService; + _siteService = siteService; + _membershipService = membershipService; + _contentManager = contentManager; + + RenderTitle = true; + } + + [OrchardSwitch] + public string Title { get; set; } + + [OrchardSwitch] + public string Name { get; set; } + + [OrchardSwitch] + public bool RenderTitle { get; set; } + + [OrchardSwitch] + public string Zone { get; set; } + + [OrchardSwitch] + public string Position { get; set; } + + [OrchardSwitch] + public string Layer { get; set; } + + [OrchardSwitch] + public string Identity { get; set; } + + [OrchardSwitch] + public string Owner { get; set; } + + [OrchardSwitch] + public string Slug { get; set; } + + [OrchardSwitch] + public string Buckets { get; set; } + + [CommandName("tags widget create tagcloud")] + [CommandHelp("tags widget create tagcloud /Title:<title> /Name:<name> /Zone:<zone> /Position:<position> /Layer:<layer> [/Identity:<identity>] [/RenderTitle:true|false] [/Owner:<owner>] [/Slug:<slug>] [/Buckets:<number>]\r\n\t" + "Creates a new widget")] + [OrchardSwitches("Title,Name,Zone,Position,Layer,Buckets,Identity,Owner,RenderTitle,Slug")] + public void CreateTagsCloudWidget() { + var type = "TagCloud"; + + var layer = GetLayer(Layer); + if (layer == null) { + Context.Output.WriteLine(T("Creating {0} widget failed: layer {1} was not found.", type, Layer)); + return; + } + + var widget = _widgetsService.CreateWidget(layer.ContentItem.Id, type, T(Title).Text, Position, Zone); + + if (!String.IsNullOrWhiteSpace(Name)) { + widget.Name = Name.Trim(); + } + + widget.RenderTitle = RenderTitle; + + if (String.IsNullOrEmpty(Owner)) { + Owner = _siteService.GetSiteSettings().SuperUser; + } + var owner = _membershipService.GetUser(Owner); + widget.As<ICommonPart>().Owner = owner; + + if (widget.Has<IdentityPart>() && !String.IsNullOrEmpty(Identity)) { + widget.As<IdentityPart>().Identifier = Identity; + } + + if (widget == null) { + return; + } + + widget.As<TagCloudPart>().Slug = Slug; + + // It's an optional parameter and defaults to 5. + if (!string.IsNullOrWhiteSpace(Buckets)) { + int BucketsAsNumber = 0; + if (Int32.TryParse(Buckets, out BucketsAsNumber)) { + widget.As<TagCloudPart>().Buckets = BucketsAsNumber; + } + } + + _contentManager.Publish(widget.ContentItem); + Context.Output.WriteLine(T("{0} widget created successfully.", type).Text); + } + + private LayerPart GetLayer(string layer) { + var layers = _widgetsService.GetLayers(); + return layers.FirstOrDefault(layerPart => String.Equals(layerPart.Name, layer, StringComparison.OrdinalIgnoreCase)); + } + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Tags/Orchard.Tags.csproj b/src/Orchard.Web/Modules/Orchard.Tags/Orchard.Tags.csproj index 77aef27b5..c78f9ef38 100644 --- a/src/Orchard.Web/Modules/Orchard.Tags/Orchard.Tags.csproj +++ b/src/Orchard.Web/Modules/Orchard.Tags/Orchard.Tags.csproj @@ -99,6 +99,7 @@ </ItemGroup> <ItemGroup> <Compile Include="AdminMenu.cs" /> + <Compile Include="Commands\TagsWidgetCommands.cs" /> <Compile Include="Controllers\AdminController.cs" /> <Compile Include="Drivers\TagCloudDriver.cs" /> <Compile Include="Feeds\TagFeedQuery.cs" /> @@ -195,6 +196,7 @@ <ItemGroup> <Content Include="packages.config" /> </ItemGroup> + <ItemGroup /> <PropertyGroup> <VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">10.0</VisualStudioVersion> <VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath> diff --git a/src/Orchard.Web/Modules/Orchard.Widgets/Filters/WidgetFilter.cs b/src/Orchard.Web/Modules/Orchard.Widgets/Filters/WidgetFilter.cs index 7535cf1a7..20b570714 100644 --- a/src/Orchard.Web/Modules/Orchard.Widgets/Filters/WidgetFilter.cs +++ b/src/Orchard.Web/Modules/Orchard.Widgets/Filters/WidgetFilter.cs @@ -59,7 +59,7 @@ namespace Orchard.Widgets.Filters { foreach (var widgetPart in widgetParts) { var commonPart = widgetPart.As<ICommonPart>(); if (commonPart == null || commonPart.Container == null) { - Logger.Warning("The widget '{0}' is has no assigned layer or the layer does not exist.", widgetPart.Title); + Logger.Warning("The widget '{0}' has no assigned layer or the layer does not exist.", widgetPart.Title); continue; }