├── .editorconfig ├── .gitignore ├── .nuget ├── EPiServer.DeveloperTools.2.1.0.nupkg ├── EPiServer.DeveloperTools.2.2.0.nupkg ├── NuGet.Config ├── NuGet.exe ├── NuGet.targets ├── build-packages.ps1 └── packages.config ├── .vs └── config │ └── applicationhost.config ├── CHANGELOG.md ├── DeveloperTools.sln ├── EPiServer.DeveloperTools ├── Constants.cs ├── CopyZipFiles.targets ├── EPiServer.DeveloperTools.Views │ └── Views │ │ ├── AppEnvironment │ │ └── Index.cshtml │ │ ├── ContentTypeAnalyzer │ │ └── Index.cshtml │ │ ├── Default │ │ └── Index.cshtml │ │ ├── IOC │ │ └── Index.cshtml │ │ ├── LocalObjectCache │ │ └── Index.cshtml │ │ ├── LogViewer │ │ └── Index.cshtml │ │ ├── MemoryDump │ │ └── Index.cshtml │ │ ├── ModuleDependencies │ │ └── Index.cshtml │ │ ├── RemoteEvent │ │ └── Index.cshtml │ │ ├── RevertToDefault │ │ └── Index.cshtml │ │ ├── Routes │ │ └── Index.cshtml │ │ ├── Shared │ │ └── _ShellLayout.cshtml │ │ ├── StartupPerf │ │ └── Index.cshtml │ │ ├── Templates │ │ └── Index.cshtml │ │ ├── ViewLocations │ │ └── Index.cshtml │ │ └── _ViewStart.cshtml ├── EPiServer.DeveloperTools.csproj ├── Features │ ├── AppEnvironment │ │ ├── AppEnvironmentController.cs │ │ └── AppEnvironmentModel.cs │ ├── Common │ │ ├── DefaultController.cs │ │ └── DeveloperToolsController.cs │ ├── ContentTypeAnalyzer │ │ ├── ContentTypeAnalyzerController.cs │ │ └── ContentTypeAnalyzerModel.cs │ ├── IoC │ │ ├── IOCController.cs │ │ ├── IOCModel.cs │ │ └── ServiceCollectionClosure.cs │ ├── LocalObjectCache │ │ ├── LocalObjectCacheController.cs │ │ └── LocalObjectCacheModel.cs │ ├── LogViewer │ │ ├── LogSettingsAndEvents.cs │ │ ├── LogViewerController.cs │ │ └── LoggerSettings.cs │ ├── MemoryDump │ │ ├── MemoryDumpController.cs │ │ └── MemoryDumpModel.cs │ ├── ModuleDependencies │ │ ├── ExtractedModuleInfo.cs │ │ ├── ModuleDependenciesController.cs │ │ ├── ModuleDependency.cs │ │ ├── ModuleDependencyViewModel.cs │ │ └── ModuleInfo.cs │ ├── RemoteEvent │ │ ├── RemoteEventController.cs │ │ └── RemoteEventModel.cs │ ├── RevertToDefault │ │ └── RevertToDefaultController.cs │ ├── Routes │ │ ├── RouteModel.cs │ │ └── RoutesController.cs │ ├── StartupPerf │ │ ├── StartupPerfController.cs │ │ └── TimeMetersModel.cs │ ├── Templates │ │ ├── TemplatesController.cs │ │ └── TemplatesModel.cs │ └── ViewLocations │ │ ├── ViewEngineLocationsModel.cs │ │ └── ViewLocationsController.cs ├── Infrastructure │ ├── Configuration │ │ ├── IServiceCollectionExtensions.cs │ │ └── OptimizelyDeveloperToolsOptions.cs │ ├── EnumerableExtensions.cs │ ├── Initialization │ │ └── ApplicationBuilderExtensions.cs │ ├── ObjectExtensions.cs │ └── RollingMemoryAppender.cs ├── MenuProvider.cs ├── Properties │ └── PublishProfiles │ │ └── FolderProfile.pubxml ├── copy-to-sandbox.ps1 ├── module │ └── module.config ├── pack.proj └── readme.txt ├── LICENSE ├── README.md ├── images └── icon.png ├── packages └── repositories.config └── tests ├── DeveloperTools.AlloySandbox ├── .gitignore ├── Assets │ └── scss │ │ ├── inc │ │ ├── _blocks.scss │ │ ├── _content.scss │ │ ├── _footer.scss │ │ ├── _general.scss │ │ └── _header.scss │ │ ├── style.scss │ │ └── theme.scss ├── Business │ ├── Channels │ │ ├── DisplayResolutionBase.cs │ │ ├── DisplayResolutions.cs │ │ ├── MobileChannel.cs │ │ └── WebChannel.cs │ ├── ContentExtensions.cs │ ├── ContentLocator.cs │ ├── EditorDescriptors │ │ ├── ContactPageSelectionFactory.cs │ │ └── ContactPageSelector.cs │ ├── IModifyLayout.cs │ ├── Initialization │ │ └── CustomizedRenderingInitialization.cs │ ├── PageContextActionFilter.cs │ ├── PageTypeExtensions.cs │ ├── PageViewContextFactory.cs │ ├── Rendering │ │ ├── AlloyContentAreaRenderer.cs │ │ ├── ErrorHandlingContentRenderer.cs │ │ ├── IContainerPage.cs │ │ ├── ICustomCssInContentArea.cs │ │ ├── SiteViewEngineLocationExpander.cs │ │ └── TemplateCoordinator.cs │ └── UIDescriptors │ │ └── ContainerPageUIDescriptor.cs ├── Components │ ├── ContactBlockViewComponent.cs │ ├── ImageFileViewComponent.cs │ ├── PageListBlockViewComponent.cs │ └── VideoFileViewComponent.cs ├── Controllers │ ├── DefaultPageController.cs │ ├── PageControllerBase.cs │ ├── PreviewController.cs │ ├── SearchPageController.cs │ └── StartPageController.cs ├── DeveloperTools.AlloySandbox.csproj ├── Extensions │ ├── ServiceCollectionExtensions.cs │ └── ViewContextExtension.cs ├── Globals.cs ├── Helpers │ ├── CategorizableExtensions.cs │ ├── HtmlHelpers.cs │ └── UrlHelpers.cs ├── Models │ ├── Blocks │ │ ├── ButtonBlock.cs │ │ ├── ContactBlock.cs │ │ ├── EditorialBlock.cs │ │ ├── JumbotronBlock.cs │ │ ├── PageListBlock.cs │ │ ├── SiteBlockData.cs │ │ ├── SiteLogotypeBlock.cs │ │ ├── TeaserBlock.cs │ │ └── _ReadMe.txt │ ├── LoginViewModel.cs │ ├── Media │ │ ├── GenericMedia.cs │ │ ├── ImageFile.cs │ │ ├── VectorImageFile.cs │ │ └── VideoFile.cs │ ├── Pages │ │ ├── AllPropertiesTestPage.cs │ │ ├── ArticlePage.cs │ │ ├── ContactPage.cs │ │ ├── ContainerPage.cs │ │ ├── IHasRelatedContent.cs │ │ ├── ISearchPage.cs │ │ ├── LandingPage.cs │ │ ├── NewsPage.cs │ │ ├── ProductPage.cs │ │ ├── SearchPage.cs │ │ ├── SitePageData.cs │ │ ├── StandardPage.cs │ │ ├── StartPage.cs │ │ └── _ReadMe.txt │ ├── SiteContentType.cs │ ├── SiteImageUrl.cs │ └── ViewModels │ │ ├── ContactBlockModel.cs │ │ ├── ContentRenderingErrorModel.cs │ │ ├── IPageViewModel.cs │ │ ├── ImageViewModel.cs │ │ ├── LayoutModel.cs │ │ ├── PageListModel.cs │ │ ├── PageViewModel.cs │ │ ├── PreviewModel.cs │ │ ├── SearchContentModel.cs │ │ └── VideoViewModel.cs ├── Program.cs ├── Properties │ ├── PublishProfiles │ │ └── FolderProfile.pubxml │ └── launchSettings.json ├── README.md ├── Resources │ └── Translations │ │ ├── ContentTypeNames.xml │ │ ├── Display.xml │ │ ├── EditorHints.xml │ │ ├── GroupNames.xml │ │ ├── PropertyNames.xml │ │ ├── Views.xml │ │ └── _ReadMe.txt ├── Startup.cs ├── Views │ ├── ArticlePage │ │ └── Index.cshtml │ ├── LandingPage │ │ └── Index.cshtml │ ├── NewsPage │ │ └── Index.cshtml │ ├── Preview │ │ └── Index.cshtml │ ├── ProductPage │ │ └── Index.cshtml │ ├── SearchPage │ │ └── Index.cshtml │ ├── Shared │ │ ├── Blocks │ │ │ ├── ButtonBlock.cshtml │ │ │ ├── EditorialBlock.cshtml │ │ │ ├── JumbotronBlock.cshtml │ │ │ ├── NoRenderer.cshtml │ │ │ ├── SiteLogotypeBlock.cshtml │ │ │ ├── TeaserBlock.cshtml │ │ │ └── TeaserBlockWide.cshtml │ │ ├── Breadcrumbs.cshtml │ │ ├── Components │ │ │ ├── ContactBlock │ │ │ │ └── Default.cshtml │ │ │ ├── ImageFile │ │ │ │ └── Default.cshtml │ │ │ ├── PageListBlock │ │ │ │ └── Default.cshtml │ │ │ └── VideoFile │ │ │ │ └── Default.cshtml │ │ ├── DisplayTemplates │ │ │ ├── ContactPage.cshtml │ │ │ ├── DateTime.cshtml │ │ │ ├── Image.cshtml │ │ │ ├── StringsCollection.cshtml │ │ │ └── _ReadMe.txt │ │ ├── Footer.cshtml │ │ ├── Header.cshtml │ │ ├── Layouts │ │ │ ├── _LeftNavigation.cshtml │ │ │ ├── _Root.cshtml │ │ │ └── _TwoPlusOne.cshtml │ │ ├── PagePartials │ │ │ ├── ContactPage.cshtml │ │ │ ├── Page.cshtml │ │ │ └── PageWide.cshtml │ │ ├── Readonly.cshtml │ │ ├── SubNavigation.cshtml │ │ ├── TemplateError.cshtml │ │ └── TemplateHint.cshtml │ ├── StandardPage │ │ └── Index.cshtml │ ├── StartPage │ │ └── Index.cshtml │ ├── _ReadMe.txt │ ├── _ViewImports.cshtml │ └── _viewstart.cshtml ├── appsettings.Development.json ├── appsettings.json ├── compilerconfig.json ├── compilerconfig.json.defaults ├── nuget.config └── wwwroot │ ├── css │ ├── bootstrap.min.css │ ├── images │ │ ├── ui-bg_flat_0_aaaaaa_40x100.png │ │ ├── ui-bg_flat_75_ffffff_40x100.png │ │ ├── ui-bg_glass_55_fbf9ee_1x400.png │ │ ├── ui-bg_glass_65_ffffff_1x400.png │ │ ├── ui-bg_glass_75_dadada_1x400.png │ │ ├── ui-bg_glass_75_e6e6e6_1x400.png │ │ ├── ui-bg_highlight-soft_75_cccccc_1x100.png │ │ ├── ui-icons_222222_256x240.png │ │ ├── ui-icons_2e83ff_256x240.png │ │ ├── ui-icons_454545_256x240.png │ │ ├── ui-icons_888888_256x240.png │ │ └── ui-icons_cd0a0a_256x240.png │ ├── style.css │ ├── style.min.css │ ├── theme.css │ └── theme.min.css │ ├── favicon.ico │ ├── gfx │ ├── logotype.png │ ├── page-type-thumbnail-article.png │ ├── page-type-thumbnail-contact.png │ ├── page-type-thumbnail-product.png │ ├── page-type-thumbnail-standard.png │ └── page-type-thumbnail.png │ └── js │ └── bootstrap.bundle.min.js └── DeveloperTools.Tests ├── DeveloperTools.Tests.csproj ├── ListTests.cs ├── app.config └── packages.config /.gitignore: -------------------------------------------------------------------------------- 1 | #OS junk files 2 | [Tt]humbs.db 3 | *.DS_Store 4 | 5 | #Visual Studio files 6 | *.[Oo]bj 7 | *.user 8 | *.aps 9 | *.pch 10 | *.vspscc 11 | *.vssscc 12 | *_i.c 13 | *_p.c 14 | *.ncb 15 | *.suo 16 | *.tlb 17 | *.tlh 18 | *.bak 19 | *.[Cc]ache 20 | *.ilk 21 | *.log 22 | *.lib 23 | *.sbr 24 | *.sdf 25 | *.opensdf 26 | *.unsuccessfulbuild 27 | *.nupkg 28 | ipch/ 29 | obj/ 30 | [Bb]in 31 | [Dd]ebug*/ 32 | [Rr]elease*/ 33 | Ankh.NoLoad 34 | 35 | #MonoDevelop 36 | *.pidb 37 | *.userprefs 38 | 39 | #Tooling 40 | _ReSharper*/ 41 | *.resharper 42 | [Tt]est[Rr]esult* 43 | *.sass-cache 44 | 45 | #Project files 46 | [Bb]uild/ 47 | 48 | #Subversion files 49 | .svn 50 | 51 | # Office Temp Files 52 | ~$* 53 | 54 | #NuGet 55 | packages/ 56 | 57 | #ncrunch 58 | *ncrunch* 59 | *crunch*.local.xml 60 | 61 | # visual studio database projects 62 | *.dbmdl 63 | 64 | #Test files 65 | *.testsettings 66 | 67 | #Visual Studio cache 68 | .vs 69 | */.vs/ -------------------------------------------------------------------------------- /.nuget/EPiServer.DeveloperTools.2.1.0.nupkg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/episerver/DeveloperTools/9bcc6ec6c7be5f5f5e98debd7344913bf0739449/.nuget/EPiServer.DeveloperTools.2.1.0.nupkg -------------------------------------------------------------------------------- /.nuget/EPiServer.DeveloperTools.2.2.0.nupkg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/episerver/DeveloperTools/9bcc6ec6c7be5f5f5e98debd7344913bf0739449/.nuget/EPiServer.DeveloperTools.2.2.0.nupkg -------------------------------------------------------------------------------- /.nuget/NuGet.Config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.nuget/NuGet.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/episerver/DeveloperTools/9bcc6ec6c7be5f5f5e98debd7344913bf0739449/.nuget/NuGet.exe -------------------------------------------------------------------------------- /.nuget/build-packages.ps1: -------------------------------------------------------------------------------- 1 | .\nuget.exe pack ..\DeveloperTools\DeveloperTools.csproj -Properties Configuration=Release 2 | -------------------------------------------------------------------------------- /.nuget/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # CHANELOG 2 | 3 | ## [4.0.0] 4 | - CMS12 compatible 5 | 6 | ## [3.5.0] 7 | - Starting from v3.5 EPiServer DeveloperTools package is distributed as expanded (no zip file) add-on. 8 | Add-on is not zipped due to issue with Razor ViewEngine and web.config file (more details here - https://world.episerver.com/forum/developer-forum/Developer-to-developer/Thread-Container/2019/4/issue-with-razor-views-while-developing-custom-add-on/#204171). 9 | NB! If you have installed EPiServer.DeveloperTools previously and upgrading to v3.5 or above - delete EPiServer.DeveloperTools.zip file! 10 | -------------------------------------------------------------------------------- /EPiServer.DeveloperTools/Constants.cs: -------------------------------------------------------------------------------- 1 | namespace EPiServer.DeveloperTools; 2 | 3 | public static class Constants 4 | { 5 | public const string PolicyName = "optimizely:developertools"; 6 | public const string ModuleName = "EPiServer.DeveloperTools"; 7 | } 8 | -------------------------------------------------------------------------------- /EPiServer.DeveloperTools/CopyZipFiles.targets: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | -------------------------------------------------------------------------------- /EPiServer.DeveloperTools/EPiServer.DeveloperTools.Views/Views/AppEnvironment/Index.cshtml: -------------------------------------------------------------------------------- 1 | @using System.Collections 2 | @model EPiServer.DeveloperTools.Features.AppEnvironment.AppEnvironmentModel 3 | 4 |
5 |

Loaded Assemblies

6 |

7 | Dumps all loaded assemblies in the current AppDomain 8 |

9 |
10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | @foreach (var entry in Model.Assemblies) 22 | { 23 | 24 | 25 | 26 | 27 | 28 | 29 | } 30 | 31 |
NameAssemblyVersionFileVersionAreaName
@entry.Name@entry.AssemblyVersion@entry.FileVersion@entry.Location
32 |
33 |

Environment Variables

34 | @{ var variables = Environment.GetEnvironmentVariables(); } 35 |

36 |     @foreach (DictionaryEntry entry in variables)
37 |     {
38 |         
39 |             
40 |             
41 |         
42 |     }
43 |     
@entry.Key@entry.Value
44 | 45 |
46 |

Connection Strings

47 |

48 |     @foreach (var entry in Model.ConnectionString)
49 |     {
50 |         
51 |             
52 |             
53 |         
54 |     }
55 |     
@entry.Key@entry.Value
56 | 57 |

Misc

58 |

59 |     @foreach (var entry in Model.Misc)
60 |     {
61 |         
62 |             
63 |             
64 |         
65 |     }
66 |     
@entry.Key@entry.Value
67 |
68 | -------------------------------------------------------------------------------- /EPiServer.DeveloperTools/EPiServer.DeveloperTools.Views/Views/ContentTypeAnalyzer/Index.cshtml: -------------------------------------------------------------------------------- 1 | @model EPiServer.DeveloperTools.Features.ContentTypeAnalyzer.ContentTypeAnalyzerModel 2 | 3 |
4 |

Content Type Analyzer

5 |

6 | Show content type Synchronization status during initialization (if the content type be changed from admin you need to start the site to see changes). 7 |

8 |
9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | @foreach (var m in Model.ContentTypes) 24 | { 25 | 26 | 27 | 28 | 29 | 30 | 41 | 42 | 43 | } 44 | 45 |
TypeDisplayNameNameSynchronizationStatusConflictsDescription
@m.Type@m.DisplayName@m.Name@m.State 31 | @if (m.HasConflict) 32 | { 33 |
    34 | @foreach (var c in m.Conflicts) 35 | { 36 |
  • @c.Name - Code = "@c.ContentTypeModelValue" vs DB = "@c.ContentTypeValue"
  • 37 | } 38 |
39 | } 40 |
@m.Description
46 |
47 | 48 | @section AdditionalScripts 49 | { 50 | 60 | } 61 | -------------------------------------------------------------------------------- /EPiServer.DeveloperTools/EPiServer.DeveloperTools.Views/Views/Default/Index.cshtml: -------------------------------------------------------------------------------- 1 | 

Welcome to Optimizely DeveloperTools!

2 |
3 |

Set of tools that will help you to diagnose or view internals of your site.

4 |

DISCLAIMER: Use at your own risk!

5 | -------------------------------------------------------------------------------- /EPiServer.DeveloperTools/EPiServer.DeveloperTools.Views/Views/IOC/Index.cshtml: -------------------------------------------------------------------------------- 1 | @model EPiServer.DeveloperTools.Features.IoC.IOCModel 2 | 3 |
4 |

IoC Container

5 |

6 | Dumps all types registered in the Dependency Injection container (aka Service Collection) used by Optimizely. 7 |

8 |
9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | @foreach (var entry in Model.IOCEntries) 22 | { 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | } 31 | 32 |
PluginTypeConcreteTypeImplementationFactoryImplementationInstanceScope
@entry.PluginType@entry.ConcreteType@entry.ImplementationFactory@entry.ImplementationInstance@entry.Scope
33 |
34 | -------------------------------------------------------------------------------- /EPiServer.DeveloperTools/EPiServer.DeveloperTools.Views/Views/MemoryDump/Index.cshtml: -------------------------------------------------------------------------------- 1 | @*@using System.Collections 2 | @using DeveloperTools.Models 3 | @using EPiServer.Shell 4 | @inherits System.Web.Mvc.WebViewPage 5 | 6 | @{ 7 | Layout = Paths.ToResource("EPiServer.DeveloperTools", "Views/Shared/DevToolsMaster.cshtml"); 8 | } 9 | 10 |
11 |

Memory Dump

12 |

13 | A tool that you can dump memory with different flag that can be analyzed with Windbg or similar tool. 14 |

15 |
16 | 17 |
18 | @using (Html.BeginForm("Index", "MemoryDump", new { }, FormMethod.Post)) 19 | { 20 | @Html.Label("File Path"): @Html.TextBox("filepath", null, new { style="width:30%" }) 21 | @Html.Label("DumpType"): @Html.DropDownListFor(m => m.SelectedDumpValue, MemoryDumpModel.GetDumpTypes()) 22 | 23 | 24 | 25 | 26 | } 27 |

28 | @if(!string.IsNullOrEmpty(Model.Name)) 29 | { 30 |

@string.Format("A File with name {0} has been created under path {1}", Model.Name, Model.FilePath)
31 | } 32 |

33 |
*@ -------------------------------------------------------------------------------- /EPiServer.DeveloperTools/EPiServer.DeveloperTools.Views/Views/RevertToDefault/Index.cshtml: -------------------------------------------------------------------------------- 1 | @*@using System.Collections 2 | @using DeveloperTools.Models 3 | @using EPiServer.Shell 4 | @inherits System.Web.Mvc.WebViewPage> 5 | 6 | @{ 7 | Layout = Paths.ToResource("EPiServer.DeveloperTools", "Views/Shared/DevToolsMaster.cshtml"); 8 | } 9 | 10 |
11 |

Revert Content Types to Default

12 |

13 | Resets overridden values stored in the database for a content type and all its properties. 14 |

15 |
16 |
17 | @using (Html.BeginForm("Index", "RevertToDefault", new { }, FormMethod.Post)) 18 | { 19 | @Html.AntiForgeryToken() 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | @foreach (var m in Model) 34 | { 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | } 44 | 45 |
SelectedDisplayNameFullNameModelTypeIDIdentity
@m.DisplayName@m.FullName@m.ModelType@m.ID@m.GUID
46 |
47 | 48 | 49 | 50 | } 51 |
52 | 53 | @section Scripts { 54 | 72 | }*@ -------------------------------------------------------------------------------- /EPiServer.DeveloperTools/EPiServer.DeveloperTools.Views/Views/Routes/Index.cshtml: -------------------------------------------------------------------------------- 1 | @model IEnumerable 2 | 3 |
4 |

Routes

5 |

6 | Dumps all endpoints registered in the application. 7 |

8 |
9 |
10 | @*
11 | @using (Html.BeginForm("Index", "Routes", new { }, FormMethod.Post)) 12 | { 13 | @Html.AntiForgeryToken() 14 | @Html.Label("Url"): @Html.TextBox("url", "http://", new { style="width:80%" }) 15 | 16 | 17 | 18 | 19 | } 20 |
*@ 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | @foreach (var entry in Model) 36 | { 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | } 47 | 48 |
OrderNameUrlRouteHandlerMethodsParametersDefaults
@entry.Order@entry.Name@entry.Url@entry.RouteHandler@entry.Methods@entry.Parameters@entry.Defaults
49 |
50 | -------------------------------------------------------------------------------- /EPiServer.DeveloperTools/EPiServer.DeveloperTools.Views/Views/Shared/_ShellLayout.cshtml: -------------------------------------------------------------------------------- 1 | @using EPiServer.Framework.Web.Resources 2 | @using EPiServer.Shell.Navigation 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | Optimizely DeveloperTools 11 | 12 | 13 | 43 | @await RenderSectionAsync("AdditionalStyles", false) 44 | 45 | 46 | @Html.AntiForgeryToken() 47 | @Html.Raw(Html.CreatePlatformNavigationMenu()) 48 | 49 |
50 |
51 | 52 |
53 | @RenderBody() 54 |
55 | 56 | 57 | 58 | 59 | @ClientResources.RenderResources("developertools", new[] { ClientResourceType.Script }) 60 | 61 | 62 | @if (IsSectionDefined("AdditionalScripts")) 63 | { 64 | @await RenderSectionAsync("AdditionalScripts", false) 65 | } 66 | else 67 | { 68 | 77 | } 78 | 79 | -------------------------------------------------------------------------------- /EPiServer.DeveloperTools/EPiServer.DeveloperTools.Views/Views/StartupPerf/Index.cshtml: -------------------------------------------------------------------------------- 1 | @model IEnumerable 2 | 3 |
4 |

Startup Performance

5 |

Displays timing measurements from the initialization process, can be used to find modules causing slow startups.

6 |

Number of time meters: @Model.Count(). Total time: @TimeSpan.FromMilliseconds(Model.Sum(tm => tm.ElapsedMilliseconds)).TotalSeconds.ToString("N") s.

7 |
8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | @foreach (var m in Model) 20 | { 21 | 22 | 23 | 24 | 25 | 26 | 27 | } 28 | 29 |
AssemblyTypeActionTime (ms)
@m.AssemblyName@m.TypeName@m.MethodName@m.ElapsedMilliseconds
30 |
31 | 32 | @section AdditionalScripts 33 | { 34 | 44 | } 45 | -------------------------------------------------------------------------------- /EPiServer.DeveloperTools/EPiServer.DeveloperTools.Views/Views/Templates/Index.cshtml: -------------------------------------------------------------------------------- 1 | @model EPiServer.DeveloperTools.Features.Templates.TemplatesModel 2 | 3 |
4 |

Templates

5 |

Show a list of all templates registered in the system by querying each content type for its templates and then making a summarized list of the unique templates found.

6 |
7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | @{ 25 | var index = 1; 26 | } 27 | @foreach (var a in Model.Templates) 28 | { 29 | var i = 0; 30 | 31 | @foreach (var t in a.Value) 32 | { 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | i++; 47 | } 48 | 49 | index++; 50 | } 51 | 52 |
Nr.ContentModelTypeNameCategoryInheritTagsAvailableWithoutTagPathModelTypeTemplateType
@index@(i == 0 ? a.Key.ModelType.FullName : string.Empty)@t.DisplayName (@t.Name)@t.TemplateTypeCategory@(t.Inherit ? "yes" : "no")@(t.Tags == null ? "" : string.Join(",", t.Tags))@(t.AvailableWithoutTag ? "Yes" : "No")@t.Path@t.ModelType@t.TemplateType
53 |
54 | -------------------------------------------------------------------------------- /EPiServer.DeveloperTools/EPiServer.DeveloperTools.Views/Views/_ViewStart.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | Layout = "Shared/_ShellLayout.cshtml"; 3 | } -------------------------------------------------------------------------------- /EPiServer.DeveloperTools/EPiServer.DeveloperTools.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Library 5 | net6.0 6 | 4.1.0 7 | 4.1.0 8 | 4.1.0.0 9 | 4.1.0.0 10 | false 11 | true 12 | EPiServer.DeveloperTools 13 | Optimizely Developer Tools (EXPERIMENTAL) 14 | icon.png 15 | EPiServer 16 | EXPERIMENTAL! Allows to run various support tools on the site. Currently not officially supported by EPiServer Support Services. Use at your own risk! 17 | Optimizely Episerver SQL Dependency Injection DeveloperTools Diagnostics Debugging 18 | https://github.com/episerver/DeveloperTools 19 | https://github.com/episerver/DeveloperTools 20 | Apache-2.0 21 | false 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | Always 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | powershell.exe -noexit -file "$(ProjectDir)copy-to-sandbox.ps1" 58 | 59 | 60 | -------------------------------------------------------------------------------- /EPiServer.DeveloperTools/Features/AppEnvironment/AppEnvironmentModel.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace EPiServer.DeveloperTools.Features.AppEnvironment; 4 | 5 | public class AppEnvironmentModel 6 | { 7 | public IEnumerable Assemblies { get; set; } 8 | public IReadOnlyDictionary Misc { get; set; } = new Dictionary(); 9 | public IReadOnlyDictionary ConnectionString { get; set; } 10 | } 11 | 12 | public class AssemblyInfo 13 | { 14 | public string Name { get; set; } 15 | public string AssemblyVersion { get; set; } 16 | public string FileVersion { get; set; } 17 | public string Location { get; set; } 18 | } 19 | -------------------------------------------------------------------------------- /EPiServer.DeveloperTools/Features/Common/DefaultController.cs: -------------------------------------------------------------------------------- 1 | using EPiServer.Cms.Shell.UI.Controllers.Internal; 2 | using Microsoft.AspNetCore.Mvc; 3 | 4 | namespace EPiServer.DeveloperTools.Features.Common; 5 | 6 | public class DefaultController : BaseController 7 | { 8 | [HttpGet] 9 | public IActionResult Index() 10 | { 11 | return View(); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /EPiServer.DeveloperTools/Features/Common/DeveloperToolsController.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Authorization; 2 | using Microsoft.AspNetCore.Mvc; 3 | 4 | namespace EPiServer.DeveloperTools.Features.Common; 5 | 6 | [Authorize(Constants.PolicyName)] 7 | public class DeveloperToolsController : Controller { } 8 | -------------------------------------------------------------------------------- /EPiServer.DeveloperTools/Features/ContentTypeAnalyzer/ContentTypeAnalyzerModel.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | 4 | namespace EPiServer.DeveloperTools.Features.ContentTypeAnalyzer; 5 | 6 | public class ContentTypeAnalyzerModel 7 | { 8 | public ContentTypeAnalyzerModel() 9 | { 10 | ContentTypes = Enumerable.Empty(); 11 | } 12 | 13 | public IEnumerable ContentTypes { get; set; } 14 | 15 | public class ContentTypeModel 16 | { 17 | public ContentTypeModel() 18 | { 19 | Conflicts = Enumerable.Empty(); 20 | } 21 | 22 | public string Type { get; set; } 23 | public string DisplayName { get; set; } 24 | public string Name { get; set; } 25 | public string State { get; set; } 26 | public bool HasConflict { get; set; } 27 | public string Description { get; set; } 28 | public IEnumerable Conflicts { get; set; } 29 | } 30 | 31 | public class ConflictModel 32 | { 33 | public string Name { get; set; } 34 | public string ContentTypeModelValue { get; set; } 35 | public string ContentTypeValue { get; set; } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /EPiServer.DeveloperTools/Features/IoC/IOCController.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using EPiServer.DeveloperTools.Features.Common; 3 | using Microsoft.AspNetCore.Mvc; 4 | 5 | namespace EPiServer.DeveloperTools.Features.IoC 6 | { 7 | public class IOCController : DeveloperToolsController 8 | { 9 | private readonly ServiceCollectionClosure _closure; 10 | 11 | public IOCController(ServiceCollectionClosure closure) 12 | { 13 | _closure = closure; 14 | } 15 | 16 | public ActionResult Index() 17 | { 18 | var model = new IOCModel(); 19 | 20 | var services = _closure.Services; 21 | 22 | model.IOCEntries = services.Select(s => new IOCEntry 23 | { 24 | PluginType = s.ServiceType.FullName, 25 | ConcreteType = s.ImplementationType?.FullName, 26 | ImplementationFactory = s.ImplementationFactory?.Method.ToString(), 27 | ImplementationInstance = s.ImplementationInstance?.GetType().FullName, 28 | Scope = s.Lifetime.ToString() 29 | }); 30 | 31 | return View(model); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /EPiServer.DeveloperTools/Features/IoC/IOCModel.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace EPiServer.DeveloperTools.Features.IoC; 4 | 5 | public class IOCModel 6 | { 7 | public IEnumerable IOCEntries { get; set; } = new List(); 8 | } 9 | 10 | public class IOCEntry 11 | { 12 | public bool IsDefault { get; set; } 13 | public string PluginType { get; set; } 14 | public string ConcreteType { get; set; } 15 | public string ImplementationFactory { get; set; } 16 | public string Scope { get; set; } 17 | public string ImplementationInstance { get; set; } 18 | } 19 | -------------------------------------------------------------------------------- /EPiServer.DeveloperTools/Features/IoC/ServiceCollectionClosure.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | 3 | namespace EPiServer.DeveloperTools.Features.IoC; 4 | 5 | public class ServiceCollectionClosure 6 | { 7 | public ServiceCollectionClosure(IServiceCollection services) { Services = services; } 8 | public IServiceCollection Services { get; } 9 | } 10 | -------------------------------------------------------------------------------- /EPiServer.DeveloperTools/Features/LocalObjectCache/LocalObjectCacheModel.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Microsoft.AspNetCore.Mvc.Rendering; 3 | 4 | namespace EPiServer.DeveloperTools.Features.LocalObjectCache; 5 | 6 | public class LocalObjectCacheModel 7 | { 8 | public IEnumerable CachedItems { get; set; } = new List(); 9 | 10 | public string FilteredBy { get; set; } 11 | 12 | public IEnumerable Choices { get; set; } = new List(); 13 | 14 | public bool ViewObjectSize { get; set; } 15 | } 16 | 17 | public class LocalObjectCacheModelItem 18 | { 19 | public string Key { get; set; } 20 | 21 | public object Value { get; set; } 22 | 23 | public long Size { get; set; } 24 | } 25 | -------------------------------------------------------------------------------- /EPiServer.DeveloperTools/Features/LogViewer/LogSettingsAndEvents.cs: -------------------------------------------------------------------------------- 1 | namespace EPiServer.DeveloperTools.Features.LogViewer 2 | { 3 | public class LogSettingAndEvents 4 | { 5 | public LogSettingAndEvents() 6 | { 7 | //LoggingEvents = Enumerable.Empty(); 8 | LoggerSetting = new LoggerSettings(); 9 | } 10 | 11 | public bool IsStarted { get; set; } 12 | //public IEnumerable LoggingEvents { get; set; } 13 | public LoggerSettings LoggerSetting { get; set; } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /EPiServer.DeveloperTools/Features/LogViewer/LoggerSettings.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Reflection; 5 | using EPiServer.Logging; 6 | using Microsoft.AspNetCore.Mvc.Rendering; 7 | 8 | namespace EPiServer.DeveloperTools.Features.LogViewer 9 | { 10 | [Serializable] 11 | public class LoggerSettings 12 | { 13 | static List _levels; 14 | 15 | public LoggerSettings() 16 | { 17 | LevelValue = Level.Critical.ToString(); 18 | StartDate = DateTime.MinValue; 19 | EndDate = DateTime.MaxValue; 20 | } 21 | 22 | public Level Level 23 | { 24 | get 25 | { 26 | //if(string.IsNullOrEmpty(LevelValue)) 27 | //{ 28 | // return Level.All; 29 | //} 30 | return 31 | (Level) 32 | typeof(Level) 33 | .GetFields(BindingFlags.Static | BindingFlags.Public) 34 | .First(p => string.Equals(p.Name, LevelValue, StringComparison.OrdinalIgnoreCase)) 35 | .GetValue(null); 36 | } 37 | } 38 | 39 | public string LevelValue { get; set; } 40 | public string LoggerName { get; set; } 41 | public string ThreadName { get; set; } 42 | public string TypeOfException { get; set; } 43 | public DateTime StartDate { get; set; } 44 | public DateTime EndDate { get; set; } 45 | public string UserName { get; set; } 46 | 47 | public static IEnumerable GetLevels() 48 | { 49 | return _levels ?? (_levels = new List 50 | { 51 | //CreateSelectItem(Level.All), 52 | CreateSelectItem(Level.Critical), 53 | CreateSelectItem(Level.Error), 54 | //CreateSelectItem(Level.Fatal), 55 | CreateSelectItem(Level.Debug), 56 | //CreateSelectItem(Level.Info), 57 | //CreateSelectItem(Level.Warn) 58 | }); 59 | } 60 | 61 | private static SelectListItem CreateSelectItem(Level level) 62 | { 63 | var sel = new SelectListItem 64 | { 65 | Text = level.ToString(), 66 | Value = level.ToString() 67 | }; 68 | 69 | return sel; 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /EPiServer.DeveloperTools/Features/MemoryDump/MemoryDumpController.cs: -------------------------------------------------------------------------------- 1 | //using System; 2 | //using System.Diagnostics; 3 | //using System.IO; 4 | //using System.Runtime.InteropServices; 5 | //using DeveloperTools.Models; 6 | //using EPiServer.DeveloperTools.Features.Common; 7 | //using EPiServer.Web; 8 | //using Microsoft.AspNetCore.Mvc; 9 | 10 | //namespace EPiServer.DeveloperTools.Features.MemoryDump 11 | //{ 12 | // public class MemoryDumpController : DeveloperToolsController 13 | // { 14 | // public ActionResult Index() 15 | // { 16 | // return View(new MemoryDumpModel()); 17 | // } 18 | 19 | // [HttpPost, ActionName("Index")] 20 | // public ActionResult DumpMemory(MemoryDumpModel memoryDumpModel) 21 | // { 22 | // if (string.IsNullOrEmpty(memoryDumpModel.FilePath)) 23 | // { 24 | // memoryDumpModel.FilePath = VirtualPathUtilityEx.RebasePhysicalPath("[appDataPath]\\Dumps"); 25 | // } 26 | // if (!Directory.Exists(memoryDumpModel.FilePath)) 27 | // { 28 | // Directory.CreateDirectory(memoryDumpModel.FilePath); 29 | // } 30 | 31 | // var timeforfileName = DateTime.Now.ToString().Replace('/', '_').Replace(':', '_'); 32 | // var name = string.Concat(memoryDumpModel.FilePath.LastIndexOf('/') == -1 ? memoryDumpModel.FilePath + '\\' : memoryDumpModel.FilePath, 33 | // Process.GetCurrentProcess().ProcessName + "_" + timeforfileName, 34 | // ".dmp"); 35 | // MiniDump.WriteDump(name, memoryDumpModel.SelectedDumpType); 36 | // memoryDumpModel.Name = name; 37 | 38 | // return View(memoryDumpModel); 39 | // } 40 | // } 41 | //} 42 | -------------------------------------------------------------------------------- /EPiServer.DeveloperTools/Features/ModuleDependencies/ExtractedModuleInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace EPiServer.DeveloperTools.Features.ModuleDependencies 5 | { 6 | internal class ExtractedModuleInfo 7 | { 8 | public Type Type { get; set; } 9 | 10 | public List Dependencies { get; set; } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /EPiServer.DeveloperTools/Features/ModuleDependencies/ModuleDependency.cs: -------------------------------------------------------------------------------- 1 | namespace EPiServer.DeveloperTools.Features.ModuleDependencies; 2 | 3 | public class ModuleDependency 4 | { 5 | public string From { get; set; } 6 | 7 | public string To { get; set; } 8 | } 9 | -------------------------------------------------------------------------------- /EPiServer.DeveloperTools/Features/ModuleDependencies/ModuleDependencyViewModel.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace EPiServer.DeveloperTools.Features.ModuleDependencies; 4 | 5 | public class ModuleDependencyViewModel 6 | { 7 | public ICollection Nodes { get; set; } 8 | 9 | public ICollection Links { get; set; } 10 | 11 | public bool ShowAll { get; set; } 12 | } 13 | -------------------------------------------------------------------------------- /EPiServer.DeveloperTools/Features/ModuleDependencies/ModuleInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace EPiServer.DeveloperTools.Features.ModuleDependencies; 4 | 5 | public class ModuleInfo 6 | { 7 | public ModuleInfo() 8 | { 9 | Size = 10; 10 | } 11 | 12 | public string Id { get; set; } 13 | 14 | public int Group { get; set; } 15 | 16 | public int Size { get; set; } 17 | 18 | public Type ModuleType { get; set; } 19 | 20 | public string Label { get; set; } 21 | 22 | public string Title { get; set; } 23 | } 24 | -------------------------------------------------------------------------------- /EPiServer.DeveloperTools/Features/RemoteEvent/RemoteEventModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using EPiServer.Events.Clients.Internal; 5 | 6 | namespace EPiServer.DeveloperTools.Features.RemoteEvent; 7 | 8 | [Serializable] 9 | public class RemoteEventsModel 10 | { 11 | public IEnumerable RemoteEventModel { get; set; } = Enumerable.Empty(); 12 | public long TotalNumberOfSentEvent { get; set; } 13 | public long TotalNumberOfReceivedEvent { get; set; } 14 | public SendRemoteEventModel SendRemoteEventModel { get; set; } = new(); 15 | public IEnumerable ActiveServers { get; set; } 16 | public IEnumerable ServerState { get; set; } 17 | public string ProviderName { get; set; } 18 | public string ProviderType { get; set; } 19 | public bool Enabled { get; set; } 20 | } 21 | 22 | [Serializable] 23 | public class RemoteEventModel 24 | { 25 | public string Name { get; set; } 26 | public string Guid { get; set; } 27 | public long NumberOfSent { get; set; } 28 | public long NumberOfReceived { get; set; } 29 | } 30 | 31 | [Serializable] 32 | public class SendRemoteEventModel 33 | { 34 | public Guid EventId { get; set; } 35 | public string Param { get; set; } 36 | public int NumberOfEvents { get; set; } 37 | public int NumberOfEventsSent { get; set; } 38 | } 39 | -------------------------------------------------------------------------------- /EPiServer.DeveloperTools/Features/RevertToDefault/RevertToDefaultController.cs: -------------------------------------------------------------------------------- 1 | //using System; 2 | //using System.Collections.Generic; 3 | //using System.Linq; 4 | //using EPiServer.DataAbstraction; 5 | //using EPiServer.DeveloperTools.Features.Common; 6 | //using EPiServer.ServiceLocation; 7 | //using Microsoft.AspNetCore.Mvc; 8 | 9 | //namespace EPiServer.DeveloperTools.Features.RevertToDefault 10 | //{ 11 | // public class RevertToDefaultController : DeveloperToolsController 12 | // { 13 | // static readonly List _SystemContentTypes = new List 14 | // { 15 | // "3fa7d9e7-877b-11d3-827c-00a024cacfcb", 16 | // "3fa7d9e8-877b-11d3-827c-00a024cacfcb", 17 | // "52f8d1e9-6d87-4db6-a465-41890289fb78" 18 | // }; 19 | 20 | // private readonly IContentTypeRepository _contentTypeRepository = ServiceLocator.Current.GetInstance(); 21 | // private readonly IPropertyDefinitionRepository _propertyDefinitionRepository = ServiceLocator.Current.GetInstance(); 22 | 23 | // protected virtual IEnumerable ContentTypes 24 | // { 25 | // get { return _contentTypeRepository.List().Where(c => !SystemGuids.Contains(c.GUID.ToString())); } 26 | // } 27 | 28 | // protected virtual IEnumerable SystemGuids => _SystemContentTypes; 29 | 30 | // public ActionResult Index() 31 | // { 32 | // return View(ContentTypes); 33 | // } 34 | 35 | // [HttpPost, ValidateAntiForgeryToken] 36 | // public ActionResult Index(Guid[] selectedObjects) 37 | // { 38 | // var contentTypeRepository = ServiceLocator.Current.GetInstance(); 39 | // foreach (var id in selectedObjects) 40 | // { 41 | // var ct = contentTypeRepository.Load(id); 42 | // var writableContentType = ct.CreateWritableClone() as ContentType; 43 | // writableContentType.ResetContentType(); 44 | // foreach (var propDef in writableContentType.PropertyDefinitions) 45 | // { 46 | // RevertToDefaultPropertyDefinition(propDef); 47 | // } 48 | // contentTypeRepository.Save(writableContentType); 49 | // SaveAvailablePageTypes(writableContentType as PageType); 50 | // } 51 | 52 | // return View("Index", ContentTypes); 53 | // } 54 | 55 | // private void RevertToDefaultPropertyDefinition(PropertyDefinition propeDef) 56 | // { 57 | // propeDef.ResetPropertyDefinition(); 58 | // _propertyDefinitionRepository.Save(propeDef); 59 | // } 60 | 61 | // private void SaveAvailablePageTypes(ContentType contentType) 62 | // { 63 | // var pageType = contentType as PageType; 64 | // if (pageType != null) 65 | // { 66 | // var availablePageTypeRepository = ServiceLocator.Current.GetInstance(); 67 | // var settings = new AvailableSetting { Availability = 0 }; 68 | // availablePageTypeRepository.RegisterSetting(pageType, settings); 69 | // } 70 | // } 71 | // } 72 | //} 73 | -------------------------------------------------------------------------------- /EPiServer.DeveloperTools/Features/Routes/RouteModel.cs: -------------------------------------------------------------------------------- 1 | namespace EPiServer.DeveloperTools.Features.Routes; 2 | 3 | public class RouteModel 4 | { 5 | public string Order { get; set; } 6 | public string Name { get; set; } 7 | public string Url { get; set; } 8 | public string Defaults { get; set; } 9 | public string RouteHandler { get; set; } 10 | public string Methods { get; set; } 11 | public string Parameters { get; set; } 12 | } 13 | -------------------------------------------------------------------------------- /EPiServer.DeveloperTools/Features/StartupPerf/StartupPerfController.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using EPiServer.DeveloperTools.Features.Common; 4 | using EPiServer.Framework.Initialization; 5 | using Microsoft.AspNetCore.Mvc; 6 | 7 | namespace EPiServer.DeveloperTools.Features.StartupPerf; 8 | 9 | public class StartupPerfController : DeveloperToolsController 10 | { 11 | [HttpGet] 12 | public ActionResult Index() 13 | { 14 | var allTimers = TimeMeters.GetAllRegistered(); 15 | var result = new List(); 16 | result.AddRange(ConvertToModel(allTimers)); 17 | 18 | return View("Index", result.OrderByDescending(t => t.ElapsedMilliseconds)); 19 | } 20 | 21 | private IEnumerable ConvertToModel(IEnumerable allTimers) 22 | { 23 | foreach (var timer in allTimers) 24 | { 25 | foreach (var counter in timer.Counters) 26 | { 27 | yield return new TimeMetersModel 28 | { 29 | AssemblyName = timer.Owner.Assembly.GetName().Name, 30 | TypeName = timer.Owner.Name, 31 | MethodName = counter.Key, 32 | ElapsedMilliseconds = counter.Value.ElapsedMilliseconds 33 | }; 34 | } 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /EPiServer.DeveloperTools/Features/StartupPerf/TimeMetersModel.cs: -------------------------------------------------------------------------------- 1 | namespace EPiServer.DeveloperTools.Features.StartupPerf; 2 | 3 | public class TimeMetersModel 4 | { 5 | public string AssemblyName { get; set; } 6 | public string TypeName { get; set; } 7 | public string MethodName { get; set; } 8 | public long ElapsedMilliseconds { get; set; } 9 | } 10 | -------------------------------------------------------------------------------- /EPiServer.DeveloperTools/Features/Templates/TemplatesController.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using EPiServer.DataAbstraction; 3 | using EPiServer.DeveloperTools.Features.Common; 4 | using Microsoft.AspNetCore.Mvc; 5 | 6 | namespace EPiServer.DeveloperTools.Features.Templates; 7 | 8 | public class TemplatesController : DeveloperToolsController 9 | { 10 | private readonly ContentTypeModelRepository _contentTypeModelRepository; 11 | private readonly ITemplateRepository _templateRepository; 12 | 13 | public TemplatesController(ITemplateRepository templateRepository, ContentTypeModelRepository contentTypeModelRepository) 14 | { 15 | _templateRepository = templateRepository; 16 | _contentTypeModelRepository = contentTypeModelRepository; 17 | } 18 | 19 | public ActionResult Index() 20 | { 21 | var model = new TemplatesModel 22 | { 23 | Templates = _contentTypeModelRepository 24 | .List() 25 | .Where(ct => ct != null) 26 | .Select(ct => new { ContentType = ct, Templates = _templateRepository.List(ct.ModelType) }) 27 | .ToDictionary(arg => arg.ContentType, arg => arg.Templates) 28 | }; 29 | 30 | return View(model); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /EPiServer.DeveloperTools/Features/Templates/TemplatesModel.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using EPiServer.DataAbstraction; 3 | using EPiServer.DataAbstraction.RuntimeModel; 4 | 5 | namespace EPiServer.DeveloperTools.Features.Templates; 6 | 7 | public class TemplatesModel 8 | { 9 | public Dictionary> Templates { get; set; } = new(); 10 | } 11 | -------------------------------------------------------------------------------- /EPiServer.DeveloperTools/Features/ViewLocations/ViewEngineLocationsModel.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace EPiServer.DeveloperTools.Features.ViewLocations; 4 | 5 | public class ViewEngineLocationsModel 6 | { 7 | public List AreaViewLocationFormats { get; } = new(); 8 | public List AreaPageViewLocationFormats { get; } = new(); 9 | public List PageViewLocationFormats { get; } = new(); 10 | public List ViewLocations { get; } = new(); 11 | public List ExpandedLocations { get; } = new(); 12 | } 13 | 14 | public class ViewEngineLocationItemModel 15 | { 16 | public ViewEngineLocationItemModel(string location, string engineName) 17 | { 18 | Location = location; 19 | EngineName = engineName; 20 | } 21 | 22 | public string EngineName { get; } 23 | public string Location { get; } 24 | } 25 | 26 | public class ExpandedLocationItemModel 27 | { 28 | public ExpandedLocationItemModel(string areaName, string controllerName, string pageName, string viewName) 29 | { 30 | AreaName = areaName; 31 | ControllerName = controllerName; 32 | PageName = pageName; 33 | ViewName = viewName; 34 | } 35 | 36 | public string AreaName { get; } 37 | public string ControllerName { get; } 38 | public string PageName { get; } 39 | public string ViewName { get; } 40 | } 41 | -------------------------------------------------------------------------------- /EPiServer.DeveloperTools/Features/ViewLocations/ViewLocationsController.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Reflection; 5 | using EPiServer.DeveloperTools.Features.Common; 6 | using EPiServer.DeveloperTools.Infrastructure; 7 | using Microsoft.AspNetCore.DataProtection.KeyManagement; 8 | using Microsoft.AspNetCore.Mvc; 9 | using Microsoft.AspNetCore.Mvc.Razor; 10 | using Microsoft.AspNetCore.Mvc.ViewEngines; 11 | using Microsoft.Extensions.Caching.Memory; 12 | using Microsoft.Extensions.Options; 13 | using Newtonsoft.Json.Linq; 14 | 15 | namespace EPiServer.DeveloperTools.Features.ViewLocations; 16 | 17 | public class ViewLocationsController : DeveloperToolsController 18 | { 19 | private readonly IOptions _razorOptions; 20 | private readonly ICompositeViewEngine _viewEngine; 21 | 22 | public ViewLocationsController(IOptions razorOptions, ICompositeViewEngine viewEngine) 23 | { 24 | _razorOptions = razorOptions; 25 | _viewEngine = viewEngine; 26 | } 27 | 28 | public ActionResult Index() 29 | { 30 | var options = _razorOptions.Value; 31 | var model = new ViewEngineLocationsModel(); 32 | 33 | model.AreaViewLocationFormats.AddRange(options.AreaViewLocationFormats.Select(f => new ViewEngineLocationItemModel(f, "Razor"))); 34 | model.AreaPageViewLocationFormats.AddRange(options.AreaPageViewLocationFormats.Select(f => new ViewEngineLocationItemModel(f, "Razor"))); 35 | model.ViewLocations.AddRange(options.ViewLocationFormats.Select(f => new ViewEngineLocationItemModel(f, "Razor"))); 36 | model.PageViewLocationFormats.AddRange(options.PageViewLocationFormats.Select(f => new ViewEngineLocationItemModel(f, "Razor"))); 37 | 38 | foreach (var engine in _viewEngine.ViewEngines) 39 | { 40 | var cacheFieldInfo = engine.GetType().GetProperty("ViewLookupCache", BindingFlags.NonPublic | BindingFlags.Instance); 41 | if (cacheFieldInfo != null) 42 | { 43 | if (cacheFieldInfo.GetValue(engine) is IMemoryCache cache) 44 | { 45 | var entriesFieldInfo = cache.GetType().GetField("_entries", BindingFlags.NonPublic | BindingFlags.Instance); 46 | if (entriesFieldInfo != null) 47 | { 48 | if (entriesFieldInfo.GetValue(cache) is IDictionary entries) 49 | { 50 | foreach (var entry in entries.Keys) 51 | { 52 | model.ExpandedLocations.Add( 53 | new ExpandedLocationItemModel( 54 | entry.GetProperty("AreaName")?.ToString(), 55 | entry.GetProperty("ControllerName")?.ToString(), 56 | entry.GetProperty("PageName")?.ToString(), 57 | entry.GetProperty("ViewName")?.ToString())); 58 | } 59 | } 60 | } 61 | } 62 | } 63 | } 64 | 65 | return View(model); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /EPiServer.DeveloperTools/Infrastructure/Configuration/IServiceCollectionExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using EPiServer.DeveloperTools.Features.IoC; 4 | using EPiServer.Shell.Modules; 5 | using Microsoft.AspNetCore.Authorization; 6 | using Microsoft.Extensions.Configuration; 7 | using Microsoft.Extensions.DependencyInjection; 8 | 9 | namespace EPiServer.DeveloperTools.Infrastructure.Configuration; 10 | 11 | public static class IServiceCollectionExtensions 12 | { 13 | private static readonly Action DefaultPolicy = p => p.RequireRole("Administrators"); 14 | 15 | public static IServiceCollection AddOptimizelyDeveloperTools(this IServiceCollection services) 16 | { 17 | return services.AddOptimizelyDeveloperTools(_ => { }, DefaultPolicy); 18 | } 19 | 20 | public static IServiceCollection AddOptimizelyDeveloperTools( 21 | this IServiceCollection services, 22 | Action setupAction) 23 | { 24 | return services.AddOptimizelyDeveloperTools(setupAction, DefaultPolicy); 25 | } 26 | 27 | public static IServiceCollection AddOptimizelyDeveloperTools( 28 | this IServiceCollection services, 29 | Action setupAction, 30 | Action configurePolicy) 31 | { 32 | services.AddSingleton(new ServiceCollectionClosure(services)); 33 | 34 | services.Configure( 35 | pm => 36 | { 37 | if (!pm.Items.Any(i => i.Name.Equals(Constants.ModuleName, StringComparison.OrdinalIgnoreCase))) 38 | { 39 | pm.Items.Add(new ModuleDetails { Name = Constants.ModuleName }); 40 | } 41 | }); 42 | 43 | services.AddOptions().Configure((options, configuration) => 44 | { 45 | setupAction(options); 46 | configuration.GetSection("EPiServer:DeveloperToolsOptions").Bind(options); 47 | }); 48 | 49 | services.AddAuthorization(options => 50 | { 51 | options.AddPolicy(Constants.PolicyName, configurePolicy); 52 | }); 53 | 54 | return services; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /EPiServer.DeveloperTools/Infrastructure/Configuration/OptimizelyDeveloperToolsOptions.cs: -------------------------------------------------------------------------------- 1 | namespace EPiServer.DeveloperTools.Infrastructure.Configuration; 2 | 3 | public class OptimizelyDeveloperToolsOptions { } 4 | -------------------------------------------------------------------------------- /EPiServer.DeveloperTools/Infrastructure/EnumerableExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace EPiServer.DeveloperTools.Infrastructure; 5 | 6 | public static class EnumerableExtensions 7 | { 8 | public static IEnumerable DistinctBy( 9 | this IEnumerable source, 10 | Func keySelector) 11 | { 12 | var knownKeys = new HashSet(); 13 | foreach (var element in source) 14 | { 15 | if (knownKeys.Add(keySelector(element))) 16 | { 17 | yield return element; 18 | } 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /EPiServer.DeveloperTools/Infrastructure/Initialization/ApplicationBuilderExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Builder; 2 | 3 | namespace EPiServer.DeveloperTools.Infrastructure.Initialization; 4 | 5 | public static class ApplicationBuilderExtensions 6 | { 7 | public static IApplicationBuilder UseOptimizelyDeveloperTools(this IApplicationBuilder app) 8 | { 9 | var services = app.ApplicationServices; 10 | 11 | return app; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /EPiServer.DeveloperTools/Infrastructure/ObjectExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reflection; 3 | 4 | namespace EPiServer.DeveloperTools.Infrastructure; 5 | 6 | public static class ObjectExtensions 7 | { 8 | public static object GetProperty(this object entry, string propertyName) 9 | { 10 | return GetProperty(entry, propertyName, BindingFlags.Public | BindingFlags.Instance); 11 | } 12 | 13 | public static object GetProperty(this object entry, string propertyName, BindingFlags bindingFlags) 14 | { 15 | if (entry == null) 16 | { 17 | throw new ArgumentNullException(nameof(entry)); 18 | } 19 | 20 | var viewNameFieldInfo = entry.GetType().GetProperty(propertyName, bindingFlags); 21 | if (viewNameFieldInfo != null) 22 | { 23 | var propertyValue = viewNameFieldInfo.GetValue(entry); 24 | return propertyValue; 25 | } 26 | 27 | return null; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /EPiServer.DeveloperTools/Infrastructure/RollingMemoryAppender.cs: -------------------------------------------------------------------------------- 1 | namespace EPiServer.DeveloperTools.Infrastructure 2 | { 3 | /// 4 | /// Simple appender to avoid filling up memory 5 | /// 6 | //public class RollingMemoryAppender : AppenderSkeleton 7 | //{ 8 | // private const int MAX_EVENTS = 10000; 9 | // ConcurrentQueue _q = new ConcurrentQueue(); 10 | 11 | // public RollingMemoryAppender() 12 | // { 13 | // _q = new ConcurrentQueue(); 14 | // } 15 | 16 | // public bool IsStarted { get; set; } = true; 17 | 18 | // public virtual LoggingEvent[] GetEvents() 19 | // { 20 | // return _q.ToArray(); 21 | // } 22 | 23 | // protected override void Append(LoggingEvent loggingEvent) 24 | // { 25 | // if(!IsStarted) 26 | // { 27 | // return; 28 | // } 29 | 30 | // loggingEvent.Fix = FixFlags.All; 31 | // _q.Enqueue(loggingEvent); 32 | 33 | // if(_q.Count > MAX_EVENTS) 34 | // { 35 | // //Just restart with a few events from the previous queue 36 | // _q = new ConcurrentQueue(_q.Take(100)); 37 | // } 38 | // } 39 | //} 40 | } 41 | -------------------------------------------------------------------------------- /EPiServer.DeveloperTools/MenuProvider.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using EPiServer.Shell; 3 | using EPiServer.Shell.Navigation; 4 | 5 | namespace EPiServer.DeveloperTools; 6 | 7 | [MenuProvider] 8 | public class MenuProvider : IMenuProvider 9 | { 10 | private const string MenuPath = "/cms/DeveloperTools"; 11 | 12 | public IEnumerable GetMenuItems() 13 | { 14 | // Create the top menu section 15 | var developerSection = new UrlMenuItem("Developer Tools", MenuPaths.Global + MenuPath, Paths.ToResource(GetType(), "default")) 16 | { 17 | SortIndex = 100, 18 | AuthorizationPolicy = Constants.PolicyName 19 | }; 20 | 21 | return new MenuItem[] 22 | { 23 | developerSection, 24 | CreateUrlMenuItem("Welcome", "default", 10), 25 | CreateUrlMenuItem("Startup Perf", "StartupPerf", 20), 26 | CreateUrlMenuItem("IoC", "IOC", 30), 27 | CreateUrlMenuItem("App Environment", "AppEnvironment", 40), 28 | CreateUrlMenuItem("Templates", "Templates", 50), 29 | //CreateUrlMenuItem("Revert Content Types", "RevertToDefault", 60), 30 | CreateUrlMenuItem("Content Type Analyzer", "ContentTypeAnalyzer", 70), 31 | //CreateUrlMenuItem("Log Viewer", "LogViewer", 80), 32 | //CreateUrlMenuItem("Memory Dump", "MemoryDump", 90), 33 | CreateUrlMenuItem("Remote Events", "RemoteEvent", 100), 34 | CreateUrlMenuItem("Routes", "Routes", 110), 35 | CreateUrlMenuItem("View Locations", "ViewLocations", 120), 36 | CreateUrlMenuItem("Module Dependencies", "ModuleDependencies", 130), 37 | CreateUrlMenuItem("Local Object Cache", "LocalObjectCache", 140) 38 | }; 39 | } 40 | 41 | protected virtual UrlMenuItem CreateUrlMenuItem(string title, string path, int index) 42 | { 43 | var link = new UrlMenuItem( 44 | title, 45 | MenuPaths.Global + MenuPath + "/" + path, 46 | Paths.ToResource(GetType(), path) + "/") 47 | { 48 | AuthorizationPolicy = Constants.PolicyName, 49 | SortIndex = index, 50 | Alignment = MenuItemAlignment.Left 51 | }; 52 | 53 | return link; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /EPiServer.DeveloperTools/Properties/PublishProfiles/FolderProfile.pubxml: -------------------------------------------------------------------------------- 1 |  2 | 5 | 6 | 7 | Release 8 | Any CPU 9 | bin\Release\net6.0\publish\ 10 | FileSystem 11 | <_TargetId>Folder 12 | 13 | -------------------------------------------------------------------------------- /EPiServer.DeveloperTools/copy-to-sandbox.ps1: -------------------------------------------------------------------------------- 1 | # go up to project root 2 | 3 | cd ..\..\.. 4 | 5 | Copy-Item modules\_protected\EPiServer.DeveloperTools\* ..\tests\DeveloperTools.AlloySandbox\modules\_protected\EPiServer.DeveloperTools -Recurse 6 | -------------------------------------------------------------------------------- /EPiServer.DeveloperTools/module/module.config: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /EPiServer.DeveloperTools/pack.proj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 21 | 22 | 23 | 24 | 25 | 26 | $(MSBuildProjectDirectory)\..\ 27 | $(SolutionDir)\tmp 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 42 | 43 | 44 | 45 | 46 | 47 | true 48 | content\modules\_protected\$(MSBuildProjectName)\;contentFiles\any\any\modules\_protected\$(MSBuildProjectName)\ 49 | true 50 | None 51 | 52 | 53 | true 54 | build\net6.0\$(MSBuildProjectName).targets 55 | true 56 | None 57 | 58 | 59 | -------------------------------------------------------------------------------- /EPiServer.DeveloperTools/readme.txt: -------------------------------------------------------------------------------- 1 | # Episerver Developer Tools - Developer Friendly Toolbelt 2 | 3 | DISCLAIMER: USE AT YOUR OWN RISK! 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Developer Tools project 2 | ============== 3 | 4 | Download latest build on [NuGet](https://nuget.optimizely.com/package/?id=EPiServer.DeveloperTools) or [under releases](https://github.com/episerver/DeveloperTools/releases) 5 | 6 | Experimental project to build small tools useful for developers. Install as an add-on in Optimizely CMS 12 (or later). 7 | When installed you must be part of the Administrators group to use the tool, a new menu "Developer" should appear in the top menu. 8 | 9 | # DISCLAIMER 10 | Remember, use at your own risk - this is not a supported product! 11 | 12 | ## Current Features 13 | 14 | * View contents of the Dependency Injection container 15 | * View Content Type sync state between Code and DB 16 | * View templates for content 17 | * View ASP.NET routes 18 | * View loaded assemblies in AppDomain 19 | * View startup time for initialization modules 20 | * View remote event statistics, provider and servers 21 | * View all registered view engines 22 | * View local object cache content (with option to remove items) 23 | 24 | ## Getting Started 25 | 26 | To get started with Optimizely developer tools - all you need to do is to add it to your project and use it :) 27 | 28 | ```csharp 29 | public void ConfigureServices(IServiceCollection services) 30 | { 31 | ... 32 | services.AddOptimizelyDeveloperTools(); 33 | } 34 | 35 | public void Configure(IApplicationBuilder app, IWebHostEnvironment env) 36 | { 37 | ... 38 | app.UseOptimizelyDeveloperTools(); 39 | } 40 | ``` 41 | 42 | ## How Risky it is to install on production? 43 | You can read more in depth analysis of toolset and it's side-effects [here](https://tech-fellow.eu/2019/02/14/how-risky-are-episerver-developertools-on-production-environment/). 44 | 45 | ## Contributing? 46 | 47 | ### Sandbox Site 48 | Sandbox site credentials: admin/P@ssword1! 49 | 50 | ### Building 51 | Post build event is copying Razor views from source project (DeveloperTools/) to test sandbox site (tests/DeveloperTools.SandboxSite). 52 | If it fails to execute - please create an GitHub issue. 53 | -------------------------------------------------------------------------------- /images/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/episerver/DeveloperTools/9bcc6ec6c7be5f5f5e98debd7344913bf0739449/images/icon.png -------------------------------------------------------------------------------- /packages/repositories.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | -------------------------------------------------------------------------------- /tests/DeveloperTools.AlloySandbox/.gitignore: -------------------------------------------------------------------------------- 1 | App_Data/ 2 | node_modules/ 3 | artifacts/ 4 | 5 | # Visual Studio files 6 | *.user 7 | *.lock.json 8 | .vscode/ 9 | .vs/ 10 | .nuget/ 11 | packages/ 12 | bin/ 13 | obj/ 14 | 15 | # Optimizely files 16 | *license.config 17 | modules/ 18 | 19 | # OS crap 20 | .DS_Store 21 | thumbs.db 22 | ehthumbs.db 23 | desktop.ini 24 | -------------------------------------------------------------------------------- /tests/DeveloperTools.AlloySandbox/Assets/scss/inc/_blocks.scss: -------------------------------------------------------------------------------- 1 | .block { 2 | margin-bottom: 1.5rem; 3 | } 4 | 5 | .jumbotronblock { 6 | color: #fff; 7 | position: relative; 8 | 9 | > div { 10 | background-size: cover; 11 | background-position: 50% 50%; 12 | position: relative; 13 | } 14 | 15 | .jumbotron-dimmer { 16 | background: #000; 17 | position: absolute; 18 | width: 100%; 19 | height: 100%; 20 | top: 0; 21 | left: 0; 22 | opacity: .4; 23 | } 24 | 25 | .jumbotron-inner { 26 | position: relative; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /tests/DeveloperTools.AlloySandbox/Assets/scss/inc/_content.scss: -------------------------------------------------------------------------------- 1 | .content { 2 | margin-bottom: 1.5rem; 3 | } 4 | -------------------------------------------------------------------------------- /tests/DeveloperTools.AlloySandbox/Assets/scss/inc/_footer.scss: -------------------------------------------------------------------------------- 1 | footer { 2 | padding: 1.5rem; 3 | margin-top: 1.5rem; 4 | } 5 | -------------------------------------------------------------------------------- /tests/DeveloperTools.AlloySandbox/Assets/scss/inc/_general.scss: -------------------------------------------------------------------------------- 1 | img, video { 2 | max-width: 100%; 3 | } 4 | 5 | .teaserblock { 6 | >div { 7 | height: 100%; 8 | } 9 | } 10 | 11 | 12 | -------------------------------------------------------------------------------- /tests/DeveloperTools.AlloySandbox/Assets/scss/inc/_header.scss: -------------------------------------------------------------------------------- 1 | header { 2 | padding: 1.5rem; 3 | margin-bottom: 1.5rem; 4 | 5 | .logo { 6 | padding-right: 1rem; 7 | 8 | img { 9 | max-height: 100px; 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /tests/DeveloperTools.AlloySandbox/Assets/scss/style.scss: -------------------------------------------------------------------------------- 1 | @import url("https://cdn.jsdelivr.net/npm/bootstrap-icons@1.5.0/font/bootstrap-icons.css"); 2 | 3 | @import "inc/general"; 4 | @import "inc/header"; 5 | @import "inc/footer"; 6 | @import "inc/content"; 7 | @import "inc/blocks"; 8 | -------------------------------------------------------------------------------- /tests/DeveloperTools.AlloySandbox/Business/Channels/DisplayResolutionBase.cs: -------------------------------------------------------------------------------- 1 | using EPiServer.Framework.Localization; 2 | using EPiServer.Web; 3 | 4 | namespace DeveloperTools.AlloySandbox.Business.Channels; 5 | 6 | /// 7 | /// Base class for all resolution definitions 8 | /// 9 | public abstract class DisplayResolutionBase : IDisplayResolution 10 | { 11 | private readonly LocalizationService _localizationService; 12 | protected DisplayResolutionBase(LocalizationService localizationService, string name, int width, int height) 13 | { 14 | _localizationService = localizationService; 15 | Id = GetType().FullName; 16 | Name = Translate(name); 17 | Width = width; 18 | Height = height; 19 | } 20 | 21 | /// 22 | /// Gets the unique ID for this resolution 23 | /// 24 | public string Id { get; protected set; } 25 | 26 | /// 27 | /// Gets the name of resolution 28 | /// 29 | public string Name { get; protected set; } 30 | 31 | /// 32 | /// Gets the resolution width in pixels 33 | /// 34 | public int Width { get; protected set; } 35 | 36 | /// 37 | /// Gets the resolution height in pixels 38 | /// 39 | public int Height { get; protected set; } 40 | 41 | private string Translate(string resurceKey) 42 | { 43 | if (!_localizationService.TryGetString(resurceKey, out var value)) 44 | { 45 | value = resurceKey; 46 | } 47 | 48 | return value; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /tests/DeveloperTools.AlloySandbox/Business/Channels/DisplayResolutions.cs: -------------------------------------------------------------------------------- 1 | using EPiServer.Framework.Localization; 2 | 3 | namespace DeveloperTools.AlloySandbox.Business.Channels; 4 | 5 | /// 6 | /// Defines resolution for desktop displays 7 | /// 8 | public class StandardResolution : DisplayResolutionBase 9 | { 10 | public StandardResolution(LocalizationService localizationService) 11 | : base(localizationService, "/resolutions/standard", 1366, 768) 12 | { 13 | } 14 | } 15 | 16 | /// 17 | /// Defines resolution for a horizontal iPad 18 | /// 19 | public class IpadHorizontalResolution : DisplayResolutionBase 20 | { 21 | public IpadHorizontalResolution(LocalizationService localizationService) 22 | : base(localizationService, "/resolutions/ipadhorizontal", 1024, 768) 23 | { 24 | } 25 | } 26 | 27 | /// 28 | /// Defines resolution for a vertical iPhone 5s 29 | /// 30 | public class IphoneVerticalResolution : DisplayResolutionBase 31 | { 32 | public IphoneVerticalResolution(LocalizationService localizationService) 33 | : base(localizationService, "/resolutions/iphonevertical", 320, 568) 34 | { 35 | } 36 | } 37 | 38 | /// 39 | /// Defines resolution for a vertical Android handheld device 40 | /// 41 | public class AndroidVerticalResolution : DisplayResolutionBase 42 | { 43 | public AndroidVerticalResolution(LocalizationService localizationService) 44 | : base(localizationService, "/resolutions/androidvertical", 480, 800) 45 | { 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /tests/DeveloperTools.AlloySandbox/Business/Channels/MobileChannel.cs: -------------------------------------------------------------------------------- 1 | using EPiServer.Web; 2 | using Wangkanai.Detection.Models; 3 | using Wangkanai.Detection.Services; 4 | 5 | namespace DeveloperTools.AlloySandbox.Business.Channels; 6 | 7 | // 8 | // Defines the 'Mobile' content channel 9 | // 10 | public class MobileChannel : DisplayChannel 11 | { 12 | public const string Name = "mobile"; 13 | 14 | public override string ChannelName => Name; 15 | 16 | public override string ResolutionId => typeof(IphoneVerticalResolution).FullName; 17 | 18 | public override bool IsActive(HttpContext context) 19 | { 20 | var detection = context.RequestServices.GetRequiredService(); 21 | return detection.Device.Type == Device.Mobile; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /tests/DeveloperTools.AlloySandbox/Business/Channels/WebChannel.cs: -------------------------------------------------------------------------------- 1 | using EPiServer.Web; 2 | using Wangkanai.Detection.Models; 3 | using Wangkanai.Detection.Services; 4 | 5 | namespace DeveloperTools.AlloySandbox.Business.Channels; 6 | 7 | /// 8 | /// Defines the 'Web' content channel 9 | /// 10 | public class WebChannel : DisplayChannel 11 | { 12 | public override string ChannelName => "web"; 13 | 14 | public override bool IsActive(HttpContext context) 15 | { 16 | var detection = context.RequestServices.GetRequiredService(); 17 | return detection.Device.Type == Device.Desktop; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /tests/DeveloperTools.AlloySandbox/Business/ContentExtensions.cs: -------------------------------------------------------------------------------- 1 | using EPiServer.Filters; 2 | using EPiServer.Framework.Web; 3 | using EPiServer.ServiceLocation; 4 | 5 | namespace DeveloperTools.AlloySandbox.Business; 6 | 7 | /// 8 | /// Extension methods for content 9 | /// 10 | public static class ContentExtensions 11 | { 12 | /// 13 | /// Filters content which should not be visible to the user. 14 | /// 15 | public static IEnumerable FilterForDisplay(this IEnumerable contents, bool requirePageTemplate = false, bool requireVisibleInMenu = false) 16 | where T : IContent 17 | { 18 | var accessFilter = new FilterAccess(); 19 | var publishedFilter = new FilterPublished(); 20 | contents = contents.Where(x => !publishedFilter.ShouldFilter(x) && !accessFilter.ShouldFilter(x)); 21 | if (requirePageTemplate) 22 | { 23 | var templateFilter = ServiceLocator.Current.GetInstance(); 24 | templateFilter.TemplateTypeCategories = TemplateTypeCategories.Request; 25 | contents = contents.Where(x => !templateFilter.ShouldFilter(x)); 26 | } 27 | if (requireVisibleInMenu) 28 | { 29 | contents = contents.Where(x => VisibleInMenu(x)); 30 | } 31 | return contents; 32 | } 33 | 34 | private static bool VisibleInMenu(IContent content) 35 | { 36 | if (content is not PageData page) 37 | { 38 | return true; 39 | } 40 | 41 | return page.VisibleInMenu; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /tests/DeveloperTools.AlloySandbox/Business/EditorDescriptors/ContactPageSelectionFactory.cs: -------------------------------------------------------------------------------- 1 | using EPiServer.ServiceLocation; 2 | using EPiServer.Shell.ObjectEditing; 3 | 4 | namespace DeveloperTools.AlloySandbox.Business.EditorDescriptors; 5 | 6 | /// 7 | /// Provides a list of options corresponding to ContactPage pages on the site 8 | /// 9 | /// 10 | [ServiceConfiguration] 11 | public class ContactPageSelectionFactory : ISelectionFactory 12 | { 13 | private readonly ContentLocator _contentLocator; 14 | 15 | public ContactPageSelectionFactory(ContentLocator contentLocator) 16 | { 17 | _contentLocator = contentLocator; 18 | } 19 | 20 | public IEnumerable GetSelections(ExtendedMetadata metadata) 21 | { 22 | var contactPages = _contentLocator.GetContactPages(); 23 | 24 | return new List(contactPages.Select(c => new SelectItem { Value = c.PageLink, Text = c.Name })); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /tests/DeveloperTools.AlloySandbox/Business/EditorDescriptors/ContactPageSelector.cs: -------------------------------------------------------------------------------- 1 | using EPiServer.Shell.ObjectEditing; 2 | using EPiServer.Shell.ObjectEditing.EditorDescriptors; 3 | 4 | namespace DeveloperTools.AlloySandbox.Business.EditorDescriptors; 5 | 6 | /// 7 | /// Registers an editor to select a ContactPage for a PageReference property using a dropdown 8 | /// 9 | [EditorDescriptorRegistration( 10 | TargetType = typeof(PageReference), 11 | UIHint = Globals.SiteUIHints.Contact)] 12 | public class ContactPageSelector : EditorDescriptor 13 | { 14 | public override void ModifyMetadata(ExtendedMetadata metadata, IEnumerable attributes) 15 | { 16 | SelectionFactoryType = typeof(ContactPageSelectionFactory); 17 | 18 | ClientEditingClass = "epi-cms/contentediting/editors/SelectionEditor"; 19 | 20 | base.ModifyMetadata(metadata, attributes); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /tests/DeveloperTools.AlloySandbox/Business/IModifyLayout.cs: -------------------------------------------------------------------------------- 1 | using DeveloperTools.AlloySandbox.Models.ViewModels; 2 | 3 | namespace DeveloperTools.AlloySandbox.Business; 4 | 5 | /// 6 | /// Defines a method which may be invoked by PageContextActionFilter allowing controllers 7 | /// to modify common layout properties of the view model. 8 | /// 9 | internal interface IModifyLayout 10 | { 11 | void ModifyLayout(LayoutModel layoutModel); 12 | } 13 | -------------------------------------------------------------------------------- /tests/DeveloperTools.AlloySandbox/Business/Initialization/CustomizedRenderingInitialization.cs: -------------------------------------------------------------------------------- 1 | using DeveloperTools.AlloySandbox.Business.Rendering; 2 | using EPiServer.Framework; 3 | using EPiServer.Framework.Initialization; 4 | using EPiServer.ServiceLocation; 5 | using EPiServer.Web; 6 | using EPiServer.Web.Mvc; 7 | using EPiServer.Web.Mvc.Html; 8 | 9 | namespace DeveloperTools.AlloySandbox.Business.Initialization; 10 | 11 | /// 12 | /// Module for customizing templates and rendering. 13 | /// 14 | [ModuleDependency(typeof(InitializationModule))] 15 | public class CustomizedRenderingInitialization : IConfigurableModule 16 | { 17 | public void ConfigureContainer(ServiceConfigurationContext context) 18 | { 19 | // Implementations for custom interfaces can be registered here. 20 | context.ConfigurationComplete += (o, e) => 21 | // Register custom implementations that should be used in favour of the default implementations 22 | context.Services.AddTransient() 23 | .AddTransient(); 24 | } 25 | 26 | public void Initialize(InitializationEngine context) => 27 | context.Locate.Advanced.GetInstance().TemplateResolved += TemplateCoordinator.OnTemplateResolved; 28 | 29 | public void Uninitialize(InitializationEngine context) => 30 | context.Locate.Advanced.GetInstance().TemplateResolved -= TemplateCoordinator.OnTemplateResolved; 31 | } 32 | -------------------------------------------------------------------------------- /tests/DeveloperTools.AlloySandbox/Business/PageContextActionFilter.cs: -------------------------------------------------------------------------------- 1 | using DeveloperTools.AlloySandbox.Models.Pages; 2 | using DeveloperTools.AlloySandbox.Models.ViewModels; 3 | using EPiServer.Web.Routing; 4 | using Microsoft.AspNetCore.Mvc; 5 | using Microsoft.AspNetCore.Mvc.Filters; 6 | 7 | namespace DeveloperTools.AlloySandbox.Business; 8 | 9 | /// 10 | /// Intercepts actions with view models of type IPageViewModel and populates the view models 11 | /// Layout and Section properties. 12 | /// 13 | /// 14 | /// This filter frees controllers for pages from having to care about common context needed by layouts 15 | /// and other page framework components allowing the controllers to focus on the specifics for the page types 16 | /// and actions that they handle. 17 | /// 18 | public class PageContextActionFilter : IResultFilter 19 | { 20 | private readonly PageViewContextFactory _contextFactory; 21 | public PageContextActionFilter(PageViewContextFactory contextFactory) 22 | { 23 | _contextFactory = contextFactory; 24 | } 25 | 26 | public void OnResultExecuting(ResultExecutingContext context) 27 | { 28 | var controller = context.Controller as Controller; 29 | var viewModel = controller?.ViewData.Model; 30 | 31 | if (viewModel is IPageViewModel model) 32 | { 33 | var currentContentLink = context.HttpContext.GetContentLink(); 34 | 35 | var layoutModel = model.Layout ?? _contextFactory.CreateLayoutModel(currentContentLink, context.HttpContext); 36 | 37 | if (context.Controller is IModifyLayout layoutController) 38 | { 39 | layoutController.ModifyLayout(layoutModel); 40 | } 41 | 42 | model.Layout = layoutModel; 43 | 44 | if (model.Section == null) 45 | { 46 | model.Section = _contextFactory.GetSection(currentContentLink); 47 | } 48 | } 49 | } 50 | 51 | public void OnResultExecuted(ResultExecutedContext context) 52 | { 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /tests/DeveloperTools.AlloySandbox/Business/PageTypeExtensions.cs: -------------------------------------------------------------------------------- 1 | using EPiServer.ServiceLocation; 2 | 3 | namespace DeveloperTools.AlloySandbox.Business; 4 | 5 | /// 6 | /// Provides extension methods for types intended to be used when working with page types 7 | /// 8 | public static class PageTypeExtensions 9 | { 10 | /// 11 | /// Returns the definition for a specific page type 12 | /// 13 | /// 14 | /// 15 | public static PageType GetPageType(this Type pageType) 16 | { 17 | var pageTypeRepository = ServiceLocator.Current.GetInstance>(); 18 | 19 | return pageTypeRepository.Load(pageType); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /tests/DeveloperTools.AlloySandbox/Business/Rendering/AlloyContentAreaRenderer.cs: -------------------------------------------------------------------------------- 1 | using EPiServer.Core.Html.StringParsing; 2 | using EPiServer.Web.Mvc.Html; 3 | using Microsoft.AspNetCore.Mvc.Rendering; 4 | using static DeveloperTools.AlloySandbox.Globals; 5 | 6 | namespace DeveloperTools.AlloySandbox.Business.Rendering; 7 | 8 | /// 9 | /// Extends the default to apply custom CSS classes to each . 10 | /// 11 | public class AlloyContentAreaRenderer : ContentAreaRenderer 12 | { 13 | protected override string GetContentAreaItemCssClass(IHtmlHelper htmlHelper, ContentAreaItem contentAreaItem) 14 | { 15 | var baseItemClass = base.GetContentAreaItemCssClass(htmlHelper, contentAreaItem); 16 | var tag = GetContentAreaItemTemplateTag(htmlHelper, contentAreaItem); 17 | 18 | return $"block {baseItemClass} {GetTypeSpecificCssClasses(contentAreaItem)} {GetCssClassForTag(tag)} {tag}"; 19 | } 20 | 21 | /// 22 | /// Gets a CSS class used for styling based on a tag name (ie a Bootstrap class name) 23 | /// 24 | /// Any tag name available, see 25 | private static string GetCssClassForTag(string tagName) 26 | { 27 | if (string.IsNullOrEmpty(tagName)) 28 | { 29 | return string.Empty; 30 | } 31 | 32 | return tagName.ToLowerInvariant() switch 33 | { 34 | ContentAreaTags.FullWidth => "col-12", 35 | ContentAreaTags.WideWidth => "col-12 col-md-8", 36 | ContentAreaTags.HalfWidth => "col-12 col-sm-6", 37 | ContentAreaTags.NarrowWidth => "col-12 col-sm-6 col-md-4", 38 | _ => string.Empty, 39 | }; 40 | } 41 | 42 | private static string GetTypeSpecificCssClasses(ContentAreaItem contentAreaItem) 43 | { 44 | var content = contentAreaItem.GetContent(); 45 | var cssClass = content == null ? string.Empty : content.GetOriginalType().Name.ToLowerInvariant(); 46 | 47 | if (content is ICustomCssInContentArea customClassContent && 48 | !string.IsNullOrWhiteSpace(customClassContent.ContentAreaCssClass)) 49 | { 50 | cssClass += $" {customClassContent.ContentAreaCssClass}"; 51 | } 52 | 53 | return cssClass; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /tests/DeveloperTools.AlloySandbox/Business/Rendering/ErrorHandlingContentRenderer.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using DeveloperTools.AlloySandbox.Helpers; 3 | using DeveloperTools.AlloySandbox.Models.ViewModels; 4 | using EPiServer.Web.Mvc; 5 | using Microsoft.AspNetCore.Mvc.Rendering; 6 | 7 | namespace DeveloperTools.AlloySandbox.Business.Rendering; 8 | 9 | /// 10 | /// Wraps an MvcContentRenderer and adds error handling to ensure that blocks and other content 11 | /// rendered as parts of pages won't crash the entire page if a non-critical exception occurs while rendering it. 12 | /// 13 | /// 14 | /// Prints an error message for editors so that they can easily report errors to developers. 15 | /// 16 | public class ErrorHandlingContentRenderer : IContentRenderer 17 | { 18 | private readonly MvcContentRenderer _mvcRenderer; 19 | 20 | public ErrorHandlingContentRenderer(MvcContentRenderer mvcRenderer) 21 | { 22 | _mvcRenderer = mvcRenderer; 23 | } 24 | 25 | /// 26 | /// Renders the contentData using the wrapped renderer and catches common, non-critical exceptions. 27 | /// 28 | public async Task RenderAsync(IHtmlHelper helper, IContentData contentData, TemplateModel templateModel) 29 | { 30 | try 31 | { 32 | await _mvcRenderer.RenderAsync(helper, contentData, templateModel); 33 | } 34 | catch (Exception ex) when (!Debugger.IsAttached) 35 | { 36 | switch (ex) 37 | { 38 | case NullReferenceException: 39 | case ArgumentException: 40 | case ApplicationException: 41 | case InvalidOperationException: 42 | case NotImplementedException: 43 | case IOException: 44 | case EPiServerException: 45 | HandlerError(helper, contentData, ex); 46 | break; 47 | default: 48 | throw; 49 | } 50 | } 51 | } 52 | 53 | private static void HandlerError(IHtmlHelper helper, IContentData contentData, Exception renderingException) 54 | { 55 | if (helper.ViewContext.IsInEditMode()) 56 | { 57 | var errorModel = new ContentRenderingErrorModel(contentData, renderingException); 58 | helper.RenderPartialAsync("TemplateError", errorModel).GetAwaiter().GetResult(); 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /tests/DeveloperTools.AlloySandbox/Business/Rendering/IContainerPage.cs: -------------------------------------------------------------------------------- 1 | namespace DeveloperTools.AlloySandbox.Business.Rendering; 2 | 3 | /// 4 | /// Marker interface for content types which should not be handled by DefaultPageController. 5 | /// 6 | internal interface IContainerPage 7 | { 8 | } 9 | -------------------------------------------------------------------------------- /tests/DeveloperTools.AlloySandbox/Business/Rendering/ICustomCssInContentArea.cs: -------------------------------------------------------------------------------- 1 | namespace DeveloperTools.AlloySandbox.Business.Rendering; 2 | 3 | /// 4 | /// Defines a property for CSS class(es) which will be added to the class 5 | /// attribute of containing elements when rendered in a content area with a size tag. 6 | /// 7 | internal interface ICustomCssInContentArea 8 | { 9 | string ContentAreaCssClass { get; } 10 | } 11 | -------------------------------------------------------------------------------- /tests/DeveloperTools.AlloySandbox/Business/Rendering/SiteViewEngineLocationExpander.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc.Razor; 2 | 3 | namespace DeveloperTools.AlloySandbox.Business.Rendering; 4 | 5 | public class SiteViewEngineLocationExpander : IViewLocationExpander 6 | { 7 | private static readonly string[] AdditionalPartialViewFormats = new[] 8 | { 9 | TemplateCoordinator.BlockFolder + "{0}.cshtml", 10 | TemplateCoordinator.PagePartialsFolder + "{0}.cshtml" 11 | }; 12 | 13 | public IEnumerable ExpandViewLocations(ViewLocationExpanderContext context, IEnumerable viewLocations) 14 | { 15 | foreach (var location in viewLocations) 16 | { 17 | yield return location; 18 | } 19 | 20 | for (var i = 0; i < AdditionalPartialViewFormats.Length; i++) 21 | { 22 | yield return AdditionalPartialViewFormats[i]; 23 | } 24 | } 25 | 26 | public void PopulateValues(ViewLocationExpanderContext context) { } 27 | } 28 | -------------------------------------------------------------------------------- /tests/DeveloperTools.AlloySandbox/Business/UIDescriptors/ContainerPageUIDescriptor.cs: -------------------------------------------------------------------------------- 1 | using EPiServer.Shell; 2 | using DeveloperTools.AlloySandbox.Models.Pages; 3 | 4 | namespace DeveloperTools.AlloySandbox.Business.UIDescriptors; 5 | 6 | /// 7 | /// Describes how the UI should appear for content. 8 | /// 9 | [UIDescriptorRegistration] 10 | public class ContainerPageUIDescriptor : UIDescriptor 11 | { 12 | public ContainerPageUIDescriptor() 13 | : base(ContentTypeCssClassNames.Container) 14 | { 15 | DefaultView = CmsViewNames.AllPropertiesView; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /tests/DeveloperTools.AlloySandbox/Components/ContactBlockViewComponent.cs: -------------------------------------------------------------------------------- 1 | using DeveloperTools.AlloySandbox.Helpers; 2 | using DeveloperTools.AlloySandbox.Models.Blocks; 3 | using DeveloperTools.AlloySandbox.Models.Pages; 4 | using DeveloperTools.AlloySandbox.Models.ViewModels; 5 | using EPiServer.Web; 6 | using EPiServer.Web.Mvc; 7 | using Microsoft.AspNetCore.Html; 8 | using Microsoft.AspNetCore.Mvc; 9 | 10 | namespace DeveloperTools.AlloySandbox.Components; 11 | 12 | public class ContactBlockViewComponent : BlockComponent 13 | { 14 | private readonly IContentLoader _contentLoader; 15 | private readonly IPermanentLinkMapper _permanentLinkMapper; 16 | 17 | public ContactBlockViewComponent(IContentLoader contentLoader, IPermanentLinkMapper permanentLinkMapper) 18 | { 19 | _contentLoader = contentLoader; 20 | _permanentLinkMapper = permanentLinkMapper; 21 | } 22 | 23 | protected override IViewComponentResult InvokeComponent(ContactBlock currentContent) 24 | { 25 | ContactPage contactPage = null; 26 | if (!ContentReference.IsNullOrEmpty(currentContent.ContactPageLink)) 27 | { 28 | contactPage = _contentLoader.Get(currentContent.ContactPageLink); 29 | } 30 | 31 | var linkUrl = GetLinkUrl(currentContent); 32 | 33 | var model = new ContactBlockModel 34 | { 35 | Heading = currentContent.Heading, 36 | Image = currentContent.Image, 37 | ContactPage = contactPage, 38 | LinkUrl = GetLinkUrl(currentContent), 39 | LinkText = currentContent.LinkText, 40 | ShowLink = linkUrl != null 41 | }; 42 | 43 | // As we're using a separate view model with different property names than the content object 44 | // we connect the view models properties with the content objects so that they can be edited. 45 | ViewData.GetEditHints() 46 | .AddConnection(x => x.Heading, x => x.Heading) 47 | .AddConnection(x => x.Image, x => x.Image) 48 | .AddConnection(x => (object)x.ContactPage, x => x.ContactPageLink) 49 | .AddConnection(x => x.LinkText, x => x.LinkText); 50 | 51 | return View(model); 52 | } 53 | 54 | private IHtmlContent GetLinkUrl(ContactBlock contactBlock) 55 | { 56 | if (contactBlock.LinkUrl != null && !contactBlock.LinkUrl.IsEmpty()) 57 | { 58 | var linkUrl = contactBlock.LinkUrl.ToString(); 59 | 60 | // If the url maps to a page on the site we convert it from the internal (permanent, GUID-like) format 61 | // to the human readable and pretty public format 62 | var linkMap = _permanentLinkMapper.Find(new UrlBuilder(linkUrl)); 63 | if (linkMap != null && !ContentReference.IsNullOrEmpty(linkMap.ContentReference)) 64 | { 65 | return new HtmlString(Url.PageLinkUrl(linkMap.ContentReference)); 66 | } 67 | 68 | return new HtmlString(contactBlock.LinkUrl.ToString()); 69 | } 70 | 71 | return null; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /tests/DeveloperTools.AlloySandbox/Components/ImageFileViewComponent.cs: -------------------------------------------------------------------------------- 1 | using DeveloperTools.AlloySandbox.Models.Media; 2 | using DeveloperTools.AlloySandbox.Models.ViewModels; 3 | using EPiServer.Web.Mvc; 4 | using EPiServer.Web.Routing; 5 | using Microsoft.AspNetCore.Mvc; 6 | 7 | namespace DeveloperTools.AlloySandbox.Components; 8 | 9 | /// 10 | /// Controller for the image file. 11 | /// 12 | public class ImageFileViewComponent : PartialContentComponent 13 | { 14 | private readonly UrlResolver _urlResolver; 15 | 16 | public ImageFileViewComponent(UrlResolver urlResolver) 17 | { 18 | _urlResolver = urlResolver; 19 | } 20 | 21 | /// 22 | /// The index action for the image file. Creates the view model and renders the view. 23 | /// 24 | /// The current image file. 25 | protected override IViewComponentResult InvokeComponent(ImageFile currentContent) 26 | { 27 | var model = new ImageViewModel 28 | { 29 | Url = _urlResolver.GetUrl(currentContent.ContentLink), 30 | Name = currentContent.Name, 31 | Copyright = currentContent.Copyright 32 | }; 33 | 34 | return View(model); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /tests/DeveloperTools.AlloySandbox/Components/PageListBlockViewComponent.cs: -------------------------------------------------------------------------------- 1 | using DeveloperTools.AlloySandbox.Business; 2 | using DeveloperTools.AlloySandbox.Models.Blocks; 3 | using DeveloperTools.AlloySandbox.Models.ViewModels; 4 | using EPiServer.Filters; 5 | using EPiServer.Web.Mvc; 6 | using Microsoft.AspNetCore.Mvc; 7 | 8 | namespace DeveloperTools.AlloySandbox.Components; 9 | 10 | public class PageListBlockViewComponent : BlockComponent 11 | { 12 | private readonly ContentLocator _contentLocator; 13 | private readonly IContentLoader _contentLoader; 14 | 15 | public PageListBlockViewComponent(ContentLocator contentLocator, IContentLoader contentLoader) 16 | { 17 | _contentLocator = contentLocator; 18 | _contentLoader = contentLoader; 19 | } 20 | 21 | protected override IViewComponentResult InvokeComponent(PageListBlock currentContent) 22 | { 23 | var pages = FindPages(currentContent); 24 | 25 | pages = Sort(pages, currentContent.SortOrder); 26 | 27 | if (currentContent.Count > 0) 28 | { 29 | pages = pages.Take(currentContent.Count); 30 | } 31 | 32 | var model = new PageListModel(currentContent) 33 | { 34 | Pages = pages.Cast() 35 | }; 36 | 37 | ViewData.GetEditHints() 38 | .AddConnection(x => x.Heading, x => x.Heading); 39 | 40 | return View(model); 41 | } 42 | 43 | private IEnumerable FindPages(PageListBlock currentBlock) 44 | { 45 | IEnumerable pages; 46 | var listRoot = currentBlock.Root; 47 | 48 | if (currentBlock.Recursive) 49 | { 50 | if (currentBlock.PageTypeFilter != null) 51 | { 52 | pages = _contentLocator.FindPagesByPageType(listRoot, true, currentBlock.PageTypeFilter.ID); 53 | } 54 | else 55 | { 56 | pages = _contentLocator.GetAll(listRoot); 57 | } 58 | } 59 | else 60 | { 61 | if (currentBlock.PageTypeFilter != null) 62 | { 63 | pages = _contentLoader 64 | .GetChildren(listRoot) 65 | .Where(p => p.ContentTypeID == currentBlock.PageTypeFilter.ID); 66 | } 67 | else 68 | { 69 | pages = _contentLoader.GetChildren(listRoot); 70 | } 71 | } 72 | 73 | if (currentBlock.CategoryFilter != null && currentBlock.CategoryFilter.Any()) 74 | { 75 | pages = pages.Where(x => x.Category.Intersect(currentBlock.CategoryFilter).Any()); 76 | } 77 | 78 | return pages; 79 | } 80 | 81 | private static IEnumerable Sort(IEnumerable pages, FilterSortOrder sortOrder) 82 | { 83 | var sortFilter = new FilterSort(sortOrder); 84 | sortFilter.Sort(new PageDataCollection(pages.ToList())); 85 | return pages; 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /tests/DeveloperTools.AlloySandbox/Components/VideoFileViewComponent.cs: -------------------------------------------------------------------------------- 1 | using DeveloperTools.AlloySandbox.Models.Media; 2 | using DeveloperTools.AlloySandbox.Models.ViewModels; 3 | using EPiServer.Web.Mvc; 4 | using EPiServer.Web.Routing; 5 | using Microsoft.AspNetCore.Mvc; 6 | 7 | namespace DeveloperTools.AlloySandbox.Components; 8 | 9 | /// 10 | /// Controller for the video file. 11 | /// 12 | public class VideoFileViewComponent : PartialContentComponent 13 | { 14 | private readonly UrlResolver _urlResolver; 15 | 16 | public VideoFileViewComponent(UrlResolver urlResolver) 17 | { 18 | _urlResolver = urlResolver; 19 | } 20 | 21 | /// 22 | /// The index action for the video file. Creates the view model and renders the view. 23 | /// 24 | /// The current video file. 25 | protected override IViewComponentResult InvokeComponent(VideoFile currentContent) 26 | { 27 | var model = new VideoViewModel 28 | { 29 | Url = _urlResolver.GetUrl(currentContent.ContentLink), 30 | PreviewImageUrl = ContentReference.IsNullOrEmpty(currentContent.PreviewImage) 31 | ? null 32 | : _urlResolver.GetUrl(currentContent.PreviewImage), 33 | }; 34 | 35 | return View(model); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /tests/DeveloperTools.AlloySandbox/Controllers/DefaultPageController.cs: -------------------------------------------------------------------------------- 1 | using EPiServer.Framework.DataAnnotations; 2 | using DeveloperTools.AlloySandbox.Models.Pages; 3 | using DeveloperTools.AlloySandbox.Models.ViewModels; 4 | using Microsoft.AspNetCore.Mvc; 5 | 6 | namespace DeveloperTools.AlloySandbox.Controllers; 7 | 8 | /// 9 | /// Concrete controller that handles all page types that don't have their own specific controllers. 10 | /// 11 | /// 12 | /// Note that as the view file name is hard coded it won't work with DisplayModes (ie Index.mobile.cshtml). 13 | /// For page types requiring such views add specific controllers for them. Alternatively the Index action 14 | /// could be modified to set ControllerContext.RouteData.Values["controller"] to type name of the currentPage 15 | /// argument. That may however have side effects. 16 | /// 17 | [TemplateDescriptor(Inherited = true)] 18 | public class DefaultPageController : PageControllerBase 19 | { 20 | public ViewResult Index(SitePageData currentPage) 21 | { 22 | var model = CreateModel(currentPage); 23 | return View($"~/Views/{currentPage.GetOriginalType().Name}/Index.cshtml", model); 24 | } 25 | 26 | /// 27 | /// Creates a PageViewModel where the type parameter is the type of the page. 28 | /// 29 | /// 30 | /// Used to create models of a specific type without the calling method having to know that type. 31 | /// 32 | private static IPageViewModel CreateModel(SitePageData page) 33 | { 34 | var type = typeof(PageViewModel<>).MakeGenericType(page.GetOriginalType()); 35 | return Activator.CreateInstance(type, page) as IPageViewModel; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /tests/DeveloperTools.AlloySandbox/Controllers/PageControllerBase.cs: -------------------------------------------------------------------------------- 1 | using DeveloperTools.AlloySandbox.Business; 2 | using DeveloperTools.AlloySandbox.Models.Pages; 3 | using DeveloperTools.AlloySandbox.Models.ViewModels; 4 | using EPiServer.ServiceLocation; 5 | using EPiServer.Shell.Security; 6 | using EPiServer.Web.Mvc; 7 | using EPiServer.Web.Routing; 8 | using Microsoft.AspNetCore.Mvc; 9 | 10 | namespace DeveloperTools.AlloySandbox.Controllers; 11 | 12 | /// 13 | /// All controllers that renders pages should inherit from this class so that we can 14 | /// apply action filters, such as for output caching site wide, should we want to. 15 | /// 16 | public abstract class PageControllerBase : PageController, IModifyLayout 17 | where T : SitePageData 18 | { 19 | protected readonly Injected UISignInManager; 20 | 21 | /// 22 | /// Signs out the current user and redirects to the Index action of the same controller. 23 | /// 24 | /// 25 | /// There's a log out link in the footer which should redirect the user to the same page. 26 | /// As we don't have a specific user/account/login controller but rely on the login URL for 27 | /// forms authentication for login functionality we add an action for logging out to all 28 | /// controllers inheriting from this class. 29 | /// 30 | public async Task Logout() 31 | { 32 | await UISignInManager.Service.SignOutAsync(); 33 | return Redirect(HttpContext.RequestServices.GetService().GetUrl(PageContext.ContentLink, PageContext.LanguageID)); 34 | } 35 | 36 | public virtual void ModifyLayout(LayoutModel layoutModel) 37 | { 38 | if (PageContext.Page is SitePageData page) 39 | { 40 | layoutModel.HideHeader = page.HideSiteHeader; 41 | layoutModel.HideFooter = page.HideSiteFooter; 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /tests/DeveloperTools.AlloySandbox/Controllers/SearchPageController.cs: -------------------------------------------------------------------------------- 1 | using DeveloperTools.AlloySandbox.Models.Pages; 2 | using DeveloperTools.AlloySandbox.Models.ViewModels; 3 | using Microsoft.AspNetCore.Mvc; 4 | 5 | namespace DeveloperTools.AlloySandbox.Controllers; 6 | 7 | public class SearchPageController : PageControllerBase 8 | { 9 | public ViewResult Index(SearchPage currentPage, string q) 10 | { 11 | var model = new SearchContentModel(currentPage) 12 | { 13 | Hits = Enumerable.Empty(), 14 | NumberOfHits = 0, 15 | SearchServiceDisabled = true, 16 | SearchedQuery = q 17 | }; 18 | 19 | return View(model); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /tests/DeveloperTools.AlloySandbox/Controllers/StartPageController.cs: -------------------------------------------------------------------------------- 1 | using DeveloperTools.AlloySandbox.Models.Pages; 2 | using DeveloperTools.AlloySandbox.Models.ViewModels; 3 | using EPiServer.Shell.Navigation; 4 | using EPiServer.Web; 5 | using EPiServer.Web.Mvc; 6 | using Microsoft.AspNetCore.Mvc; 7 | 8 | namespace DeveloperTools.AlloySandbox.Controllers; 9 | 10 | public class StartPageController : PageControllerBase 11 | { 12 | public StartPageController(MenuAssembler assembler) 13 | { 14 | var menu = assembler.GetMenuHierarchy("/global", 100); 15 | } 16 | 17 | public IActionResult Index(StartPage currentPage) 18 | { 19 | var model = PageViewModel.Create(currentPage); 20 | 21 | // Check if it is the StartPage or just a page of the StartPage type. 22 | if (SiteDefinition.Current.StartPage.CompareToIgnoreWorkID(currentPage.ContentLink)) 23 | { 24 | // Connect the view models logotype property to the start page's to make it editable 25 | var editHints = ViewData.GetEditHints, StartPage>(); 26 | editHints.AddConnection(m => m.Layout.Logotype, p => p.SiteLogotype); 27 | editHints.AddConnection(m => m.Layout.ProductPages, p => p.ProductPageLinks); 28 | editHints.AddConnection(m => m.Layout.CompanyInformationPages, p => p.CompanyInformationPageLinks); 29 | editHints.AddConnection(m => m.Layout.NewsPages, p => p.NewsPageLinks); 30 | editHints.AddConnection(m => m.Layout.CustomerZonePages, p => p.CustomerZonePageLinks); 31 | } 32 | 33 | return View(model); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /tests/DeveloperTools.AlloySandbox/DeveloperTools.AlloySandbox.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | net6.0 4 | disable 5 | enable 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /tests/DeveloperTools.AlloySandbox/Extensions/ServiceCollectionExtensions.cs: -------------------------------------------------------------------------------- 1 | using DeveloperTools.AlloySandbox.Business; 2 | using DeveloperTools.AlloySandbox.Business.Channels; 3 | using DeveloperTools.AlloySandbox.Business.Rendering; 4 | using EPiServer.Web; 5 | using Microsoft.AspNetCore.Mvc; 6 | using Microsoft.AspNetCore.Mvc.Razor; 7 | 8 | namespace DeveloperTools.AlloySandbox.Extensions; 9 | 10 | public static class ServiceCollectionExtensions 11 | { 12 | public static IServiceCollection AddAlloy(this IServiceCollection services) 13 | { 14 | services.Configure(options => options.ViewLocationExpanders.Add(new SiteViewEngineLocationExpander())); 15 | 16 | services.Configure(displayOption => 17 | { 18 | displayOption.Add("full", "/displayoptions/full", Globals.ContentAreaTags.FullWidth, string.Empty, "epi-icon__layout--full"); 19 | displayOption.Add("wide", "/displayoptions/wide", Globals.ContentAreaTags.WideWidth, string.Empty, "epi-icon__layout--wide"); 20 | displayOption.Add("half", "/displayoptions/half", Globals.ContentAreaTags.HalfWidth, string.Empty, "epi-icon__layout--half"); 21 | displayOption.Add("narrow", "/displayoptions/narrow", Globals.ContentAreaTags.NarrowWidth, string.Empty, "epi-icon__layout--narrow"); 22 | }); 23 | 24 | services.Configure(options => options.Filters.Add()); 25 | 26 | services.AddDisplayResolutions(); 27 | services.AddDetection(); 28 | 29 | return services; 30 | } 31 | 32 | private static void AddDisplayResolutions(this IServiceCollection services) 33 | { 34 | services.AddSingleton(); 35 | services.AddSingleton(); 36 | services.AddSingleton(); 37 | services.AddSingleton(); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /tests/DeveloperTools.AlloySandbox/Extensions/ViewContextExtension.cs: -------------------------------------------------------------------------------- 1 | using EPiServer.Web; 2 | using Microsoft.AspNetCore.Mvc.Controllers; 3 | using Microsoft.AspNetCore.Mvc.Rendering; 4 | 5 | namespace DeveloperTools.AlloySandbox.Helpers; 6 | 7 | /// 8 | /// Extension methods on request Context such as et/Set Node, Lang, Controller 9 | /// 10 | public static class ViewContextExtension 11 | { 12 | 13 | /// 14 | /// Determine if the the controller is in the preview mode. 15 | /// 16 | /// 17 | /// 18 | public static bool IsPreviewMode(this ViewContext viewContext) 19 | => viewContext.IsInEditMode() && (viewContext.ActionDescriptor as ControllerActionDescriptor)?.ControllerName == "Preview"; 20 | 21 | /// 22 | /// Determines if the request context is in edit mode. 23 | /// 24 | /// The request context 25 | /// trueIf the context is in edit mode; otherwise false 26 | public static bool IsInEditMode(this ViewContext viewContext) 27 | { 28 | var mode = viewContext.HttpContext.RequestServices.GetRequiredService().CurrentMode; 29 | return mode is ContextMode.Edit or ContextMode.Preview; 30 | } 31 | } 32 | 33 | -------------------------------------------------------------------------------- /tests/DeveloperTools.AlloySandbox/Globals.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | 3 | namespace DeveloperTools.AlloySandbox; 4 | 5 | public class Globals 6 | { 7 | public const string LoginPath = "/util/login"; 8 | 9 | /// 10 | /// Group names for content types and properties 11 | /// 12 | [GroupDefinitions] 13 | public static class GroupNames 14 | { 15 | [Display(Name = "Contact", Order = 1)] 16 | public const string Contact = "Contact"; 17 | 18 | [Display(Name = "Default", Order = 2)] 19 | public const string Default = "Default"; 20 | 21 | [Display(Name = "Metadata", Order = 3)] 22 | public const string MetaData = "Metadata"; 23 | 24 | [Display(Name = "News", Order = 4)] 25 | public const string News = "News"; 26 | 27 | [Display(Name = "Products", Order = 5)] 28 | public const string Products = "Products"; 29 | 30 | [Display(Name = "SiteSettings", Order = 6)] 31 | public const string SiteSettings = "SiteSettings"; 32 | 33 | [Display(Name = "Specialized", Order = 7)] 34 | public const string Specialized = "Specialized"; 35 | } 36 | 37 | /// 38 | /// Tags to use for the main widths used in the Bootstrap HTML framework 39 | /// 40 | public static class ContentAreaTags 41 | { 42 | public const string FullWidth = "full"; 43 | public const string WideWidth = "wide"; 44 | public const string HalfWidth = "half"; 45 | public const string NarrowWidth = "narrow"; 46 | public const string NoRenderer = "norenderer"; 47 | } 48 | 49 | /// 50 | /// Names used for UIHint attributes to map specific rendering controls to page properties 51 | /// 52 | public static class SiteUIHints 53 | { 54 | public const string Contact = "contact"; 55 | public const string Strings = "StringList"; 56 | public const string StringsCollection = "StringsCollection"; 57 | } 58 | 59 | /// 60 | /// Virtual path to folder with static graphics, such as "/gfx/" 61 | /// 62 | public const string StaticGraphicsFolderPath = "/gfx/"; 63 | } 64 | -------------------------------------------------------------------------------- /tests/DeveloperTools.AlloySandbox/Helpers/CategorizableExtensions.cs: -------------------------------------------------------------------------------- 1 | using EPiServer.ServiceLocation; 2 | 3 | namespace DeveloperTools.AlloySandbox.Helpers; 4 | 5 | /// 6 | /// Provides extension methods for categorizable content 7 | /// 8 | /// ICategorizable content includes for example pages and blocks. 9 | public static class CategorizableExtensions 10 | { 11 | /// 12 | /// Returns the CSS classes (if any) associated with the theme(s) of the content, as decided by its categories 13 | /// 14 | /// 15 | /// CSS classes associated with the content's theme(s), or an empty string array if no theme is applicable 16 | /// Content's categorization may map to more than one theme. This method assumes there are website categories called "Meet", "Track", and "Plan" 17 | public static string[] GetThemeCssClassNames(this ICategorizable content) 18 | { 19 | if (content.Category == null) 20 | { 21 | return Array.Empty(); 22 | } 23 | 24 | // Although with some overhead, a HashSet allows us to ensure we never add a CSS class more than once 25 | var cssClasses = new HashSet(); 26 | var categoryRepository = ServiceLocator.Current.GetInstance(); 27 | 28 | foreach (var categoryName in content.Category.Select(category => categoryRepository.Get(category).Name.ToLowerInvariant())) 29 | { 30 | switch (categoryName) 31 | { 32 | case "meet": 33 | cssClasses.Add("theme1"); 34 | break; 35 | case "track": 36 | cssClasses.Add("theme2"); 37 | break; 38 | case "plan": 39 | cssClasses.Add("theme3"); 40 | break; 41 | default: 42 | break; 43 | } 44 | } 45 | 46 | return cssClasses.ToArray(); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /tests/DeveloperTools.AlloySandbox/Helpers/UrlHelpers.cs: -------------------------------------------------------------------------------- 1 | using EPiServer.ServiceLocation; 2 | using EPiServer.Web.Routing; 3 | using Microsoft.AspNetCore.Mvc; 4 | 5 | namespace DeveloperTools.AlloySandbox.Helpers; 6 | 7 | public static class UrlHelpers 8 | { 9 | /// 10 | /// Returns the target URL for a ContentReference. Respects the page's shortcut setting 11 | /// so if the page is set as a shortcut to another page or an external URL that URL 12 | /// will be returned. 13 | /// 14 | public static string PageLinkUrl(this IUrlHelper urlHelper, ContentReference contentLink) 15 | { 16 | if (ContentReference.IsNullOrEmpty(contentLink)) 17 | { 18 | return string.Empty; 19 | } 20 | 21 | var contentLoader = ServiceLocator.Current.GetInstance(); 22 | var page = contentLoader.Get(contentLink); 23 | 24 | return PageLinkUrl(urlHelper, page); 25 | } 26 | 27 | /// 28 | /// Returns the target URL for a page. Respects the page's shortcut setting 29 | /// so if the page is set as a shortcut to another page or an external URL that URL 30 | /// will be returned. 31 | /// 32 | public static string PageLinkUrl(this IUrlHelper urlHelper, PageData page) 33 | { 34 | var urlResolver = urlHelper.ActionContext.HttpContext.RequestServices.GetRequiredService(); 35 | switch (page.LinkType) 36 | { 37 | case PageShortcutType.Normal: 38 | case PageShortcutType.FetchData: 39 | return urlResolver.GetUrl(page.ContentLink); 40 | 41 | case PageShortcutType.Shortcut: 42 | if (page.Property["PageShortcutLink"] is PropertyPageReference shortcutProperty && 43 | !ContentReference.IsNullOrEmpty(shortcutProperty.ContentLink)) 44 | { 45 | return urlHelper.PageLinkUrl(shortcutProperty.ContentLink); 46 | } 47 | break; 48 | 49 | case PageShortcutType.External: 50 | return page.LinkURL; 51 | case PageShortcutType.Inactive: 52 | break; 53 | default: 54 | break; 55 | } 56 | return string.Empty; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /tests/DeveloperTools.AlloySandbox/Models/Blocks/ButtonBlock.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | 3 | namespace DeveloperTools.AlloySandbox.Models.Blocks; 4 | 5 | /// 6 | /// Used to insert a link which is styled as a button 7 | /// 8 | [SiteContentType(GUID = "426CF12F-1F01-4EA0-922F-0778314DDAF0")] 9 | [SiteImageUrl] 10 | public class ButtonBlock : SiteBlockData 11 | { 12 | [Display(Order = 1, GroupName = SystemTabNames.Content)] 13 | [Required] 14 | public virtual string ButtonText { get; set; } 15 | 16 | [Display(Order = 2, GroupName = SystemTabNames.Content)] 17 | [Required] 18 | public virtual Url ButtonLink { get; set; } 19 | } 20 | -------------------------------------------------------------------------------- /tests/DeveloperTools.AlloySandbox/Models/Blocks/ContactBlock.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | using EPiServer.Web; 3 | 4 | namespace DeveloperTools.AlloySandbox.Models.Blocks; 5 | 6 | /// 7 | /// Used to present contact information with a call-to-action link 8 | /// 9 | /// Actual contact details are retrieved from a contact page specified using the ContactPageLink property 10 | [SiteContentType(GUID = "7E932EAF-6BC2-4753-902A-8670EDC5F363")] 11 | [SiteImageUrl] 12 | public class ContactBlock : SiteBlockData 13 | { 14 | [Display( 15 | GroupName = SystemTabNames.Content, 16 | Order = 1)] 17 | [CultureSpecific] 18 | [UIHint(UIHint.Image)] 19 | public virtual ContentReference Image { get; set; } 20 | 21 | [Display( 22 | GroupName = SystemTabNames.Content, 23 | Order = 2)] 24 | [CultureSpecific] 25 | public virtual string Heading { get; set; } 26 | 27 | /// 28 | /// Gets or sets the contact page from which contact information should be retrieved 29 | /// 30 | [Display( 31 | GroupName = SystemTabNames.Content, 32 | Order = 3)] 33 | [UIHint(Globals.SiteUIHints.Contact)] 34 | public virtual PageReference ContactPageLink { get; set; } 35 | 36 | [Display( 37 | GroupName = SystemTabNames.Content, 38 | Order = 4)] 39 | [CultureSpecific] 40 | public virtual string LinkText { get; set; } 41 | 42 | [Display( 43 | GroupName = SystemTabNames.Content, 44 | Order = 5)] 45 | [CultureSpecific] 46 | public virtual Url LinkUrl { get; set; } 47 | } 48 | -------------------------------------------------------------------------------- /tests/DeveloperTools.AlloySandbox/Models/Blocks/EditorialBlock.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | 3 | namespace DeveloperTools.AlloySandbox.Models.Blocks; 4 | 5 | /// 6 | /// Used to insert editorial content edited using a rich-text editor 7 | /// 8 | [SiteContentType( 9 | GUID = "67F617A4-2175-4360-975E-75EDF2B924A7", 10 | GroupName = SystemTabNames.Content)] 11 | [SiteImageUrl] 12 | public class EditorialBlock : SiteBlockData 13 | { 14 | [Display(GroupName = SystemTabNames.Content)] 15 | [CultureSpecific] 16 | public virtual XhtmlString MainBody { get; set; } 17 | } 18 | -------------------------------------------------------------------------------- /tests/DeveloperTools.AlloySandbox/Models/Blocks/JumbotronBlock.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | using EPiServer.Web; 3 | 4 | namespace DeveloperTools.AlloySandbox.Models.Blocks; 5 | 6 | /// 7 | /// Used for a primary message on a page, commonly used on start pages and landing pages 8 | /// 9 | [SiteContentType( 10 | GroupName = Globals.GroupNames.Specialized, 11 | GUID = "9FD1C860-7183-4122-8CD4-FF4C55E096F9")] 12 | [SiteImageUrl] 13 | public class JumbotronBlock : SiteBlockData 14 | { 15 | [Display( 16 | GroupName = SystemTabNames.Content, 17 | Order = 1 18 | )] 19 | [CultureSpecific] 20 | [UIHint(UIHint.Image)] 21 | public virtual ContentReference Image { get; set; } 22 | 23 | /// 24 | /// Gets or sets a description for the image, for example used as the alt text for the image when rendered 25 | /// 26 | [Display( 27 | GroupName = SystemTabNames.Content, 28 | Order = 1 29 | )] 30 | [CultureSpecific] 31 | [UIHint(UIHint.Textarea)] 32 | public virtual string ImageDescription 33 | { 34 | get 35 | { 36 | var propertyValue = this["ImageDescription"] as string; 37 | 38 | // Return image description with fall back to the heading if no description has been specified 39 | return string.IsNullOrWhiteSpace(propertyValue) ? Heading : propertyValue; 40 | } 41 | set => this["ImageDescription"] = value; 42 | } 43 | 44 | [Display( 45 | GroupName = SystemTabNames.Content, 46 | Order = 1 47 | )] 48 | [CultureSpecific] 49 | public virtual string Heading { get; set; } 50 | 51 | [Display( 52 | GroupName = SystemTabNames.Content, 53 | Order = 2 54 | )] 55 | [CultureSpecific] 56 | [UIHint(UIHint.Textarea)] 57 | public virtual string SubHeading { get; set; } 58 | 59 | [Display( 60 | GroupName = SystemTabNames.Content, 61 | Order = 3 62 | )] 63 | [CultureSpecific] 64 | [Required] 65 | public virtual string ButtonText { get; set; } 66 | 67 | // The link must be required as an anchor tag requires an href in order to be valid and focusable 68 | [Display( 69 | GroupName = SystemTabNames.Content, 70 | Order = 4 71 | )] 72 | [CultureSpecific] 73 | [Required] 74 | public virtual Url ButtonLink { get; set; } 75 | } 76 | -------------------------------------------------------------------------------- /tests/DeveloperTools.AlloySandbox/Models/Blocks/PageListBlock.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | using System.ComponentModel.DataAnnotations; 3 | using EPiServer.Filters; 4 | 5 | namespace DeveloperTools.AlloySandbox.Models.Blocks; 6 | 7 | /// 8 | /// Used to insert a list of pages, for example a news list 9 | /// 10 | [SiteContentType(GUID = "30685434-33DE-42AF-88A7-3126B936AEAD")] 11 | [SiteImageUrl] 12 | public class PageListBlock : SiteBlockData 13 | { 14 | [Display( 15 | GroupName = SystemTabNames.Content, 16 | Order = 1)] 17 | [CultureSpecific] 18 | public virtual string Heading { get; set; } 19 | 20 | [Display( 21 | GroupName = SystemTabNames.Content, 22 | Order = 2)] 23 | [DefaultValue(false)] 24 | public virtual bool IncludePublishDate { get; set; } 25 | 26 | /// 27 | /// Gets or sets whether a page introduction/description should be included in the list 28 | /// 29 | [Display( 30 | GroupName = SystemTabNames.Content, 31 | Order = 3)] 32 | [DefaultValue(true)] 33 | public virtual bool IncludeIntroduction { get; set; } 34 | 35 | [Display( 36 | GroupName = SystemTabNames.Content, 37 | Order = 4)] 38 | [DefaultValue(3)] 39 | [Required] 40 | public virtual int Count { get; set; } 41 | 42 | [Display( 43 | GroupName = SystemTabNames.Content, 44 | Order = 4)] 45 | [DefaultValue(FilterSortOrder.PublishedDescending)] 46 | [UIHint("SortOrder")] 47 | [BackingType(typeof(PropertyNumber))] 48 | public virtual FilterSortOrder SortOrder { get; set; } 49 | 50 | [Display( 51 | GroupName = SystemTabNames.Content, 52 | Order = 5)] 53 | [Required] 54 | public virtual PageReference Root { get; set; } 55 | 56 | [Display( 57 | GroupName = SystemTabNames.Content, 58 | Order = 6)] 59 | public virtual PageType PageTypeFilter { get; set; } 60 | 61 | [Display( 62 | GroupName = SystemTabNames.Content, 63 | Order = 7)] 64 | public virtual CategoryList CategoryFilter { get; set; } 65 | 66 | [Display( 67 | GroupName = SystemTabNames.Content, 68 | Order = 8)] 69 | public virtual bool Recursive { get; set; } 70 | 71 | /// 72 | /// Sets the default property values on the content data. 73 | /// 74 | /// Type of the content. 75 | public override void SetDefaultValues(ContentType contentType) 76 | { 77 | base.SetDefaultValues(contentType); 78 | 79 | Count = 3; 80 | IncludeIntroduction = true; 81 | IncludePublishDate = false; 82 | SortOrder = FilterSortOrder.PublishedDescending; 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /tests/DeveloperTools.AlloySandbox/Models/Blocks/SiteBlockData.cs: -------------------------------------------------------------------------------- 1 | namespace DeveloperTools.AlloySandbox.Models.Blocks; 2 | 3 | /// 4 | /// Base class for all block types on the site 5 | /// 6 | public abstract class SiteBlockData : BlockData 7 | { 8 | } 9 | -------------------------------------------------------------------------------- /tests/DeveloperTools.AlloySandbox/Models/Blocks/SiteLogotypeBlock.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | using EPiServer.Shell.ObjectEditing; 3 | using EPiServer.Web; 4 | 5 | namespace DeveloperTools.AlloySandbox.Models.Blocks; 6 | 7 | /// 8 | /// Used to provide a composite property on the start page to set site logotype settings 9 | /// 10 | [SiteContentType( 11 | GUID = "09854019-91A5-4B93-8623-17F038346001", 12 | AvailableInEditMode = false)] // Should not be created and added to content areas by editors, the SiteLogotypeBlock is only used as a property type 13 | [SiteImageUrl] 14 | public class SiteLogotypeBlock : SiteBlockData 15 | { 16 | /// 17 | /// Gets the site logotype URL 18 | /// 19 | /// If not specified a default logotype will be used 20 | [DefaultDragAndDropTarget] 21 | [UIHint(UIHint.Image)] 22 | public virtual Url Url 23 | { 24 | get 25 | { 26 | var url = this.GetPropertyValue(b => b.Url); 27 | 28 | return url == null || url.IsEmpty() 29 | ? new Url("/gfx/logotype.png") 30 | : url; 31 | } 32 | set => this.SetPropertyValue(b => b.Url, value); 33 | } 34 | 35 | [CultureSpecific] 36 | public virtual string Title { get; set; } 37 | } 38 | -------------------------------------------------------------------------------- /tests/DeveloperTools.AlloySandbox/Models/Blocks/TeaserBlock.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | using EPiServer.Web; 3 | 4 | namespace DeveloperTools.AlloySandbox.Models.Blocks; 5 | 6 | /// 7 | /// Used to provide a stylized entry point to a page on the site 8 | /// 9 | [SiteContentType(GUID = "EB67A99A-E239-41B8-9C59-20EAA5936047")] // BEST PRACTICE TIP: Always assign a GUID explicitly when creating a new block type 10 | [SiteImageUrl] // Use site's default thumbnail 11 | public class TeaserBlock : SiteBlockData 12 | { 13 | [CultureSpecific] 14 | [Required(AllowEmptyStrings = false)] 15 | [Display( 16 | GroupName = SystemTabNames.Content, 17 | Order = 1)] 18 | public virtual string Heading { get; set; } 19 | 20 | [CultureSpecific] 21 | [Required(AllowEmptyStrings = false)] 22 | [Display( 23 | GroupName = SystemTabNames.Content, 24 | Order = 2)] 25 | [UIHint(UIHint.Textarea)] 26 | public virtual string Text { get; set; } 27 | 28 | [CultureSpecific] 29 | [Required(AllowEmptyStrings = false)] 30 | [UIHint(UIHint.Image)] 31 | [Display( 32 | GroupName = SystemTabNames.Content, 33 | Order = 3)] 34 | public virtual ContentReference Image { get; set; } 35 | 36 | [Display( 37 | GroupName = SystemTabNames.Content, 38 | Order = 4)] 39 | public virtual PageReference Link { get; set; } 40 | } 41 | -------------------------------------------------------------------------------- /tests/DeveloperTools.AlloySandbox/Models/Blocks/_ReadMe.txt: -------------------------------------------------------------------------------- 1 | This folder contains all block types. 2 | 3 | Blocks should be named with a suffix of "Block", such as "TeaserBlock" or "NewsListBlock". 4 | 5 | Default block controls should be named with a suffix of "Control", 6 | such as "TeaserBlockControl" or "NewsListBlockControl". -------------------------------------------------------------------------------- /tests/DeveloperTools.AlloySandbox/Models/LoginViewModel.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | 3 | namespace DeveloperTools.AlloySandbox.Models; 4 | 5 | public class LoginViewModel 6 | { 7 | [Required] 8 | public string Username { get; set; } 9 | 10 | [Required] 11 | public string Password { get; set; } 12 | } 13 | -------------------------------------------------------------------------------- /tests/DeveloperTools.AlloySandbox/Models/Media/GenericMedia.cs: -------------------------------------------------------------------------------- 1 | namespace DeveloperTools.AlloySandbox.Models.Media; 2 | 3 | [ContentType(GUID = "EE3BD195-7CB0-4756-AB5F-E5E223CD9820")] 4 | public class GenericMedia : MediaData 5 | { 6 | /// 7 | /// Gets or sets the description. 8 | /// 9 | public virtual string Description { get; set; } 10 | } 11 | -------------------------------------------------------------------------------- /tests/DeveloperTools.AlloySandbox/Models/Media/ImageFile.cs: -------------------------------------------------------------------------------- 1 | using EPiServer.Framework.DataAnnotations; 2 | 3 | namespace DeveloperTools.AlloySandbox.Models.Media; 4 | 5 | [ContentType(GUID = "0A89E464-56D4-449F-AEA8-2BF774AB8730")] 6 | [MediaDescriptor(ExtensionString = "jpg,jpeg,jpe,ico,gif,bmp,png")] 7 | public class ImageFile : ImageData 8 | { 9 | /// 10 | /// Gets or sets the copyright. 11 | /// 12 | /// 13 | /// The copyright. 14 | /// 15 | public virtual string Copyright { get; set; } 16 | } 17 | -------------------------------------------------------------------------------- /tests/DeveloperTools.AlloySandbox/Models/Media/VectorImageFile.cs: -------------------------------------------------------------------------------- 1 | using EPiServer.Framework.Blobs; 2 | using EPiServer.Framework.DataAnnotations; 3 | 4 | namespace DeveloperTools.AlloySandbox.Models.Media; 5 | 6 | [ContentType(GUID = "F522B459-EB27-462C-B216-989FC7FF9448")] 7 | [MediaDescriptor(ExtensionString = "svg")] 8 | public class VectorImageFile : ImageData 9 | { 10 | /// 11 | /// Gets the generated thumbnail for this media. 12 | /// 13 | public override Blob Thumbnail => BinaryData; 14 | } 15 | -------------------------------------------------------------------------------- /tests/DeveloperTools.AlloySandbox/Models/Media/VideoFile.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | using EPiServer.Framework.DataAnnotations; 3 | using EPiServer.Web; 4 | 5 | namespace DeveloperTools.AlloySandbox.Models.Media; 6 | 7 | [ContentType(GUID = "85468104-E06F-47E5-A317-FC9B83D3CBA6")] 8 | [MediaDescriptor(ExtensionString = "flv,mp4,webm")] 9 | public class VideoFile : VideoData 10 | { 11 | /// 12 | /// Gets or sets the copyright. 13 | /// 14 | public virtual string Copyright { get; set; } 15 | 16 | /// 17 | /// Gets or sets the URL to the preview image. 18 | /// 19 | [UIHint(UIHint.Image)] 20 | public virtual ContentReference PreviewImage { get; set; } 21 | } 22 | -------------------------------------------------------------------------------- /tests/DeveloperTools.AlloySandbox/Models/Pages/ArticlePage.cs: -------------------------------------------------------------------------------- 1 | namespace DeveloperTools.AlloySandbox.Models.Pages; 2 | 3 | /// 4 | /// Used primarily for publishing news articles on the website 5 | /// 6 | [SiteContentType( 7 | GroupName = Globals.GroupNames.News, 8 | GUID = "AEECADF2-3E89-4117-ADEB-F8D43565D2F4")] 9 | [SiteImageUrl(Globals.StaticGraphicsFolderPath + "page-type-thumbnail-article.png")] 10 | public class ArticlePage : StandardPage 11 | { 12 | public override void SetDefaultValues(ContentType contentType) 13 | { 14 | base.SetDefaultValues(contentType); 15 | 16 | VisibleInMenu = false; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /tests/DeveloperTools.AlloySandbox/Models/Pages/ContactPage.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | using DeveloperTools.AlloySandbox.Business.Rendering; 3 | using EPiServer.Web; 4 | 5 | namespace DeveloperTools.AlloySandbox.Models.Pages; 6 | 7 | /// 8 | /// Represents contact details for a contact person 9 | /// 10 | [SiteContentType( 11 | GUID = "F8D47655-7B50-4319-8646-3369BA9AF05B", 12 | GroupName = Globals.GroupNames.Specialized)] 13 | [SiteImageUrl(Globals.StaticGraphicsFolderPath + "page-type-thumbnail-contact.png")] 14 | public class ContactPage : SitePageData, IContainerPage 15 | { 16 | [Display(GroupName = Globals.GroupNames.Contact)] 17 | [UIHint(UIHint.Image)] 18 | public virtual ContentReference Image { get; set; } 19 | 20 | [Display(GroupName = Globals.GroupNames.Contact)] 21 | public virtual string Phone { get; set; } 22 | 23 | [Display(GroupName = Globals.GroupNames.Contact)] 24 | [EmailAddress] 25 | public virtual string Email { get; set; } 26 | } 27 | -------------------------------------------------------------------------------- /tests/DeveloperTools.AlloySandbox/Models/Pages/ContainerPage.cs: -------------------------------------------------------------------------------- 1 | using DeveloperTools.AlloySandbox.Business.Rendering; 2 | 3 | namespace DeveloperTools.AlloySandbox.Models.Pages; 4 | 5 | /// 6 | /// Used to logically group pages in the page tree 7 | /// 8 | [SiteContentType( 9 | GUID = "D178950C-D20E-4A46-90BD-5338B2424745", 10 | GroupName = Globals.GroupNames.Specialized)] 11 | [SiteImageUrl] 12 | public class ContainerPage : SitePageData, IContainerPage 13 | { 14 | } 15 | -------------------------------------------------------------------------------- /tests/DeveloperTools.AlloySandbox/Models/Pages/IHasRelatedContent.cs: -------------------------------------------------------------------------------- 1 | namespace DeveloperTools.AlloySandbox.Models.Pages; 2 | 3 | public interface IHasRelatedContent 4 | { 5 | ContentArea RelatedContentArea { get; } 6 | } 7 | -------------------------------------------------------------------------------- /tests/DeveloperTools.AlloySandbox/Models/Pages/ISearchPage.cs: -------------------------------------------------------------------------------- 1 | namespace DeveloperTools.AlloySandbox.Models.Pages; 2 | 3 | /// 4 | /// Marker interface for search implementation 5 | /// 6 | public interface ISearchPage 7 | { 8 | } 9 | -------------------------------------------------------------------------------- /tests/DeveloperTools.AlloySandbox/Models/Pages/LandingPage.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | 3 | namespace DeveloperTools.AlloySandbox.Models.Pages; 4 | 5 | /// 6 | /// Used for campaign or landing pages, commonly used for pages linked in online advertising such as AdWords 7 | /// 8 | [SiteContentType( 9 | GUID = "DBED4258-8213-48DB-A11F-99C034172A54", 10 | GroupName = Globals.GroupNames.Specialized)] 11 | [SiteImageUrl] 12 | public class LandingPage : SitePageData 13 | { 14 | [Display( 15 | GroupName = SystemTabNames.Content, 16 | Order = 310)] 17 | [CultureSpecific] 18 | public virtual ContentArea MainContentArea { get; set; } 19 | 20 | public override void SetDefaultValues(ContentType contentType) 21 | { 22 | base.SetDefaultValues(contentType); 23 | 24 | HideSiteFooter = true; 25 | HideSiteHeader = true; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /tests/DeveloperTools.AlloySandbox/Models/Pages/NewsPage.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | using DeveloperTools.AlloySandbox.Business; 3 | using DeveloperTools.AlloySandbox.Models.Blocks; 4 | using EPiServer.Filters; 5 | using EPiServer.Framework.Localization; 6 | using EPiServer.ServiceLocation; 7 | 8 | namespace DeveloperTools.AlloySandbox.Models.Pages; 9 | 10 | /// 11 | /// Presents a news section including a list of the most recent articles on the site 12 | /// 13 | [SiteContentType(GUID = "638D8271-5CA3-4C72-BABC-3E8779233263")] 14 | [SiteImageUrl] 15 | public class NewsPage : StandardPage 16 | { 17 | [Display( 18 | GroupName = SystemTabNames.Content, 19 | Order = 305)] 20 | public virtual PageListBlock NewsList { get; set; } 21 | 22 | public override void SetDefaultValues(ContentType contentType) 23 | { 24 | base.SetDefaultValues(contentType); 25 | 26 | NewsList.Count = 20; 27 | NewsList.Heading = ServiceLocator.Current.GetInstance().GetString("/newspagetemplate/latestnews"); 28 | NewsList.IncludeIntroduction = true; 29 | NewsList.IncludePublishDate = true; 30 | NewsList.Recursive = true; 31 | NewsList.PageTypeFilter = typeof(ArticlePage).GetPageType(); 32 | NewsList.SortOrder = FilterSortOrder.PublishedDescending; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /tests/DeveloperTools.AlloySandbox/Models/Pages/ProductPage.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | using DeveloperTools.AlloySandbox.Models.Blocks; 3 | 4 | namespace DeveloperTools.AlloySandbox.Models.Pages; 5 | 6 | /// 7 | /// Used to present a single product 8 | /// 9 | [SiteContentType( 10 | GUID = "17583DCD-3C11-49DD-A66D-0DEF0DD601FC", 11 | GroupName = Globals.GroupNames.Products)] 12 | [SiteImageUrl(Globals.StaticGraphicsFolderPath + "page-type-thumbnail-product.png")] 13 | [AvailableContentTypes( 14 | Availability = Availability.Specific, 15 | IncludeOn = new[] { typeof(StartPage) })] 16 | public class ProductPage : StandardPage, IHasRelatedContent 17 | { 18 | [Required] 19 | [Display(Order = 305)] 20 | [UIHint(Globals.SiteUIHints.StringsCollection)] 21 | [CultureSpecific] 22 | public virtual IList UniqueSellingPoints { get; set; } 23 | 24 | [Display( 25 | GroupName = SystemTabNames.Content, 26 | Order = 330)] 27 | [CultureSpecific] 28 | [AllowedTypes(new[] { typeof(IContentData) }, new[] { typeof(JumbotronBlock) })] 29 | public virtual ContentArea RelatedContentArea { get; set; } 30 | } 31 | -------------------------------------------------------------------------------- /tests/DeveloperTools.AlloySandbox/Models/Pages/SearchPage.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | using DeveloperTools.AlloySandbox.Models.Blocks; 3 | 4 | namespace DeveloperTools.AlloySandbox.Models.Pages; 5 | 6 | /// 7 | /// Used to provide on-site search 8 | /// 9 | [SiteContentType( 10 | GUID = "AAC25733-1D21-4F82-B031-11E626C91E30", 11 | GroupName = Globals.GroupNames.Specialized)] 12 | [SiteImageUrl] 13 | public class SearchPage : SitePageData, IHasRelatedContent, ISearchPage 14 | { 15 | [Display( 16 | GroupName = SystemTabNames.Content, 17 | Order = 310)] 18 | [CultureSpecific] 19 | [AllowedTypes(new[] { typeof(IContentData) }, new[] { typeof(JumbotronBlock) })] 20 | public virtual ContentArea RelatedContentArea { get; set; } 21 | } 22 | -------------------------------------------------------------------------------- /tests/DeveloperTools.AlloySandbox/Models/Pages/SitePageData.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | using DeveloperTools.AlloySandbox.Business.Rendering; 3 | using EPiServer.SpecializedProperties; 4 | using EPiServer.Web; 5 | 6 | namespace DeveloperTools.AlloySandbox.Models.Pages; 7 | 8 | /// 9 | /// Base class for all page types 10 | /// 11 | public abstract class SitePageData : PageData, ICustomCssInContentArea 12 | { 13 | [Display( 14 | GroupName = Globals.GroupNames.MetaData, 15 | Order = 100)] 16 | [CultureSpecific] 17 | public virtual string MetaTitle 18 | { 19 | get 20 | { 21 | var metaTitle = this.GetPropertyValue(p => p.MetaTitle); 22 | 23 | // Use explicitly set meta title, otherwise fall back to page name 24 | return !string.IsNullOrWhiteSpace(metaTitle) 25 | ? metaTitle 26 | : PageName; 27 | } 28 | set => this.SetPropertyValue(p => p.MetaTitle, value); 29 | } 30 | 31 | [Display( 32 | GroupName = Globals.GroupNames.MetaData, 33 | Order = 200)] 34 | [CultureSpecific] 35 | [BackingType(typeof(PropertyStringList))] 36 | public virtual IList MetaKeywords { get; set; } 37 | 38 | [Display( 39 | GroupName = Globals.GroupNames.MetaData, 40 | Order = 300)] 41 | [CultureSpecific] 42 | [UIHint(UIHint.Textarea)] 43 | public virtual string MetaDescription { get; set; } 44 | 45 | [Display( 46 | GroupName = Globals.GroupNames.MetaData, 47 | Order = 400)] 48 | [CultureSpecific] 49 | public virtual bool DisableIndexing { get; set; } 50 | 51 | [Display( 52 | GroupName = SystemTabNames.Content, 53 | Order = 100)] 54 | [UIHint(UIHint.Image)] 55 | public virtual ContentReference PageImage { get; set; } 56 | 57 | [Display( 58 | GroupName = SystemTabNames.Content, 59 | Order = 200)] 60 | [CultureSpecific] 61 | [UIHint(UIHint.Textarea)] 62 | public virtual string TeaserText 63 | { 64 | get 65 | { 66 | var teaserText = this.GetPropertyValue(p => p.TeaserText); 67 | 68 | // Use explicitly set teaser text, otherwise fall back to description 69 | return !string.IsNullOrWhiteSpace(teaserText) 70 | ? teaserText 71 | : MetaDescription; 72 | } 73 | set => this.SetPropertyValue(p => p.TeaserText, value); 74 | } 75 | 76 | [Display( 77 | GroupName = SystemTabNames.Settings, 78 | Order = 200)] 79 | [CultureSpecific] 80 | public virtual bool HideSiteHeader { get; set; } 81 | 82 | [Display( 83 | GroupName = SystemTabNames.Settings, 84 | Order = 300)] 85 | [CultureSpecific] 86 | public virtual bool HideSiteFooter { get; set; } 87 | 88 | public string ContentAreaCssClass => "teaserblock"; 89 | } 90 | -------------------------------------------------------------------------------- /tests/DeveloperTools.AlloySandbox/Models/Pages/StandardPage.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | 3 | namespace DeveloperTools.AlloySandbox.Models.Pages; 4 | 5 | /// 6 | /// Used for the pages mainly consisting of manually created content such as text, images, and blocks 7 | /// 8 | [SiteContentType(GUID = "9CCC8A41-5C8C-4BE0-8E73-520FF3DE8267")] 9 | [SiteImageUrl(Globals.StaticGraphicsFolderPath + "page-type-thumbnail-standard.png")] 10 | public class StandardPage : SitePageData 11 | { 12 | [Display( 13 | GroupName = SystemTabNames.Content, 14 | Order = 310)] 15 | [CultureSpecific] 16 | public virtual XhtmlString MainBody { get; set; } 17 | 18 | [Display( 19 | GroupName = SystemTabNames.Content, 20 | Order = 320)] 21 | public virtual ContentArea MainContentArea { get; set; } 22 | } 23 | -------------------------------------------------------------------------------- /tests/DeveloperTools.AlloySandbox/Models/Pages/StartPage.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | using DeveloperTools.AlloySandbox.Models.Blocks; 3 | using EPiServer.SpecializedProperties; 4 | 5 | namespace DeveloperTools.AlloySandbox.Models.Pages; 6 | 7 | /// 8 | /// Used for the site's start page and also acts as a container for site settings 9 | /// 10 | [ContentType( 11 | GUID = "19671657-B684-4D95-A61F-8DD4FE60D559", 12 | GroupName = Globals.GroupNames.Specialized)] 13 | [SiteImageUrl] 14 | [AvailableContentTypes( 15 | Availability.Specific, 16 | Include = new[] 17 | { 18 | typeof(ContainerPage), 19 | typeof(ProductPage), 20 | typeof(StandardPage), 21 | typeof(ISearchPage), 22 | typeof(LandingPage), 23 | typeof(ContentFolder) }, // Pages we can create under the start page... 24 | ExcludeOn = new[] 25 | { 26 | typeof(ContainerPage), 27 | typeof(ProductPage), 28 | typeof(StandardPage), 29 | typeof(ISearchPage), 30 | typeof(LandingPage) 31 | })] // ...and underneath those we can't create additional start pages 32 | public class StartPage : SitePageData 33 | { 34 | [Display( 35 | GroupName = SystemTabNames.Content, 36 | Order = 320)] 37 | [CultureSpecific] 38 | public virtual ContentArea MainContentArea { get; set; } 39 | 40 | [Display(GroupName = Globals.GroupNames.SiteSettings, Order = 300)] 41 | public virtual LinkItemCollection ProductPageLinks { get; set; } 42 | 43 | [Display(GroupName = Globals.GroupNames.SiteSettings, Order = 350)] 44 | public virtual LinkItemCollection CompanyInformationPageLinks { get; set; } 45 | 46 | [Display(GroupName = Globals.GroupNames.SiteSettings, Order = 400)] 47 | public virtual LinkItemCollection NewsPageLinks { get; set; } 48 | 49 | [Display(GroupName = Globals.GroupNames.SiteSettings, Order = 450)] 50 | public virtual LinkItemCollection CustomerZonePageLinks { get; set; } 51 | 52 | [Display(GroupName = Globals.GroupNames.SiteSettings)] 53 | public virtual PageReference GlobalNewsPageLink { get; set; } 54 | 55 | [Display(GroupName = Globals.GroupNames.SiteSettings)] 56 | public virtual PageReference ContactsPageLink { get; set; } 57 | 58 | [Display(GroupName = Globals.GroupNames.SiteSettings)] 59 | public virtual PageReference SearchPageLink { get; set; } 60 | 61 | [Display(GroupName = Globals.GroupNames.SiteSettings)] 62 | public virtual SiteLogotypeBlock SiteLogotype { get; set; } 63 | } 64 | -------------------------------------------------------------------------------- /tests/DeveloperTools.AlloySandbox/Models/Pages/_ReadMe.txt: -------------------------------------------------------------------------------- 1 | This folder contains all page types. 2 | 3 | Pages should be named with a suffix of "Page", such as "StandardPage" or "ProductPage". 4 | 5 | Default page templates should be named with a suffix of "Template", 6 | such as "StandardPageTemplate" or "ProductPageTemplate". 7 | -------------------------------------------------------------------------------- /tests/DeveloperTools.AlloySandbox/Models/SiteContentType.cs: -------------------------------------------------------------------------------- 1 | namespace DeveloperTools.AlloySandbox.Models; 2 | 3 | /// 4 | /// Attribute used for site content types to set default attribute values 5 | /// 6 | public class SiteContentType : ContentTypeAttribute 7 | { 8 | public SiteContentType() 9 | { 10 | GroupName = Globals.GroupNames.Default; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /tests/DeveloperTools.AlloySandbox/Models/SiteImageUrl.cs: -------------------------------------------------------------------------------- 1 | namespace DeveloperTools.AlloySandbox.Models; 2 | 3 | /// 4 | /// Attribute to set the default thumbnail for site page and block types 5 | /// 6 | public class SiteImageUrl : ImageUrlAttribute 7 | { 8 | /// 9 | /// The parameterless constructor will initialize a SiteImageUrl attribute with a default thumbnail 10 | /// 11 | public SiteImageUrl() 12 | : base("/gfx/page-type-thumbnail.png") 13 | { 14 | } 15 | 16 | public SiteImageUrl(string path) 17 | : base(path) 18 | { 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /tests/DeveloperTools.AlloySandbox/Models/ViewModels/ContactBlockModel.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | using DeveloperTools.AlloySandbox.Models.Pages; 3 | using EPiServer.Web; 4 | using Microsoft.AspNetCore.Html; 5 | 6 | namespace DeveloperTools.AlloySandbox.Models.ViewModels; 7 | 8 | public class ContactBlockModel 9 | { 10 | [UIHint(UIHint.Image)] 11 | public ContentReference Image { get; set; } 12 | 13 | public string Heading { get; set; } 14 | 15 | public string LinkText { get; set; } 16 | 17 | public IHtmlContent LinkUrl { get; set; } 18 | 19 | public bool ShowLink { get; set; } 20 | 21 | public ContactPage ContactPage { get; set; } 22 | } 23 | -------------------------------------------------------------------------------- /tests/DeveloperTools.AlloySandbox/Models/ViewModels/ContentRenderingErrorModel.cs: -------------------------------------------------------------------------------- 1 | namespace DeveloperTools.AlloySandbox.Models.ViewModels; 2 | 3 | public class ContentRenderingErrorModel 4 | { 5 | public ContentRenderingErrorModel(IContentData contentData, Exception exception) 6 | { 7 | if (contentData is IContent content) 8 | { 9 | ContentName = content.Name; 10 | } 11 | else 12 | { 13 | ContentName = string.Empty; 14 | } 15 | 16 | ContentTypeName = contentData.GetOriginalType().Name; 17 | 18 | Exception = exception; 19 | } 20 | 21 | public string ContentName { get; set; } 22 | 23 | public string ContentTypeName { get; set; } 24 | 25 | public Exception Exception { get; set; } 26 | } 27 | -------------------------------------------------------------------------------- /tests/DeveloperTools.AlloySandbox/Models/ViewModels/IPageViewModel.cs: -------------------------------------------------------------------------------- 1 | using DeveloperTools.AlloySandbox.Models.Pages; 2 | 3 | namespace DeveloperTools.AlloySandbox.Models.ViewModels; 4 | 5 | /// 6 | /// Defines common characteristics for view models for pages, including properties used by layout files. 7 | /// 8 | /// 9 | /// Views which should handle several page types (T) can use this interface as model type rather than the 10 | /// concrete PageViewModel class, utilizing the that this interface is covariant. 11 | /// 12 | public interface IPageViewModel where T : SitePageData 13 | { 14 | T CurrentPage { get; } 15 | 16 | LayoutModel Layout { get; set; } 17 | 18 | IContent Section { get; set; } 19 | } 20 | -------------------------------------------------------------------------------- /tests/DeveloperTools.AlloySandbox/Models/ViewModels/ImageViewModel.cs: -------------------------------------------------------------------------------- 1 | namespace DeveloperTools.AlloySandbox.Models.ViewModels; 2 | 3 | /// 4 | /// View model for the image file 5 | /// 6 | public class ImageViewModel 7 | { 8 | /// 9 | /// Gets or sets the URL to the image. 10 | /// 11 | public string Url { get; set; } 12 | 13 | /// 14 | /// Gets or sets the name of the image. 15 | /// 16 | public string Name { get; set; } 17 | 18 | /// 19 | /// Gets or sets the copyright information of the image. 20 | /// 21 | public string Copyright { get; set; } 22 | } 23 | -------------------------------------------------------------------------------- /tests/DeveloperTools.AlloySandbox/Models/ViewModels/LayoutModel.cs: -------------------------------------------------------------------------------- 1 | using DeveloperTools.AlloySandbox.Models.Blocks; 2 | using EPiServer.SpecializedProperties; 3 | using Microsoft.AspNetCore.Html; 4 | 5 | namespace DeveloperTools.AlloySandbox.Models.ViewModels; 6 | 7 | public class LayoutModel 8 | { 9 | public SiteLogotypeBlock Logotype { get; set; } 10 | 11 | public IHtmlContent LogotypeLinkUrl { get; set; } 12 | 13 | public bool HideHeader { get; set; } 14 | 15 | public bool HideFooter { get; set; } 16 | 17 | public LinkItemCollection ProductPages { get; set; } 18 | 19 | public LinkItemCollection CompanyInformationPages { get; set; } 20 | 21 | public LinkItemCollection NewsPages { get; set; } 22 | 23 | public LinkItemCollection CustomerZonePages { get; set; } 24 | 25 | public bool LoggedIn { get; set; } 26 | 27 | public HtmlString LoginUrl { get; set; } 28 | 29 | public HtmlString LogOutUrl { get; set; } 30 | 31 | public HtmlString SearchActionUrl { get; set; } 32 | 33 | public bool IsInReadonlyMode { get; set; } 34 | } 35 | -------------------------------------------------------------------------------- /tests/DeveloperTools.AlloySandbox/Models/ViewModels/PageListModel.cs: -------------------------------------------------------------------------------- 1 | using DeveloperTools.AlloySandbox.Models.Blocks; 2 | 3 | namespace DeveloperTools.AlloySandbox.Models.ViewModels; 4 | 5 | public class PageListModel 6 | { 7 | public PageListModel(PageListBlock block) 8 | { 9 | Heading = block.Heading; 10 | ShowIntroduction = block.IncludeIntroduction; 11 | ShowPublishDate = block.IncludePublishDate; 12 | } 13 | public string Heading { get; set; } 14 | 15 | public IEnumerable Pages { get; set; } 16 | 17 | public bool ShowIntroduction { get; set; } 18 | 19 | public bool ShowPublishDate { get; set; } 20 | } 21 | -------------------------------------------------------------------------------- /tests/DeveloperTools.AlloySandbox/Models/ViewModels/PageViewModel.cs: -------------------------------------------------------------------------------- 1 | using DeveloperTools.AlloySandbox.Models.Pages; 2 | 3 | namespace DeveloperTools.AlloySandbox.Models.ViewModels; 4 | 5 | public class PageViewModel : IPageViewModel where T : SitePageData 6 | { 7 | public PageViewModel(T currentPage) 8 | { 9 | CurrentPage = currentPage; 10 | } 11 | 12 | public T CurrentPage { get; private set; } 13 | 14 | public LayoutModel Layout { get; set; } 15 | 16 | public IContent Section { get; set; } 17 | } 18 | 19 | public static class PageViewModel 20 | { 21 | /// 22 | /// Returns a PageViewModel of type . 23 | /// 24 | /// 25 | /// Convenience method for creating PageViewModels without having to specify the type as methods can use type inference while constructors cannot. 26 | /// 27 | public static PageViewModel Create(T page) 28 | where T : SitePageData => new(page); 29 | } 30 | -------------------------------------------------------------------------------- /tests/DeveloperTools.AlloySandbox/Models/ViewModels/PreviewModel.cs: -------------------------------------------------------------------------------- 1 | using DeveloperTools.AlloySandbox.Models.Pages; 2 | 3 | namespace DeveloperTools.AlloySandbox.Models.ViewModels; 4 | 5 | public class PreviewModel : PageViewModel 6 | { 7 | public PreviewModel(SitePageData currentPage, IContent previewContent) 8 | : base(currentPage) 9 | { 10 | PreviewContent = previewContent; 11 | Areas = new List(); 12 | } 13 | 14 | public IContent PreviewContent { get; set; } 15 | 16 | public List Areas { get; set; } 17 | 18 | public class PreviewArea 19 | { 20 | public bool Supported { get; set; } 21 | 22 | public string AreaName { get; set; } 23 | 24 | public string AreaTag { get; set; } 25 | 26 | public ContentArea ContentArea { get; set; } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /tests/DeveloperTools.AlloySandbox/Models/ViewModels/SearchContentModel.cs: -------------------------------------------------------------------------------- 1 | using DeveloperTools.AlloySandbox.Models.Pages; 2 | 3 | namespace DeveloperTools.AlloySandbox.Models.ViewModels; 4 | 5 | public class SearchContentModel : PageViewModel 6 | { 7 | public SearchContentModel(SearchPage currentPage) 8 | : base(currentPage) 9 | { 10 | } 11 | 12 | public bool SearchServiceDisabled { get; set; } 13 | 14 | public string SearchedQuery { get; set; } 15 | 16 | public int NumberOfHits { get; set; } 17 | 18 | public IEnumerable Hits { get; set; } 19 | 20 | public class SearchHit 21 | { 22 | public string Title { get; set; } 23 | 24 | public string Url { get; set; } 25 | 26 | public string Excerpt { get; set; } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /tests/DeveloperTools.AlloySandbox/Models/ViewModels/VideoViewModel.cs: -------------------------------------------------------------------------------- 1 | namespace DeveloperTools.AlloySandbox.Models.ViewModels; 2 | 3 | /// 4 | /// View model for the video file 5 | /// 6 | public class VideoViewModel 7 | { 8 | /// 9 | /// Gets or sets the URL to the video. 10 | /// 11 | public string Url { get; set; } 12 | 13 | /// 14 | /// Gets or sets the URL to a preview image for the video. 15 | /// 16 | public string PreviewImageUrl { get; set; } 17 | } 18 | -------------------------------------------------------------------------------- /tests/DeveloperTools.AlloySandbox/Program.cs: -------------------------------------------------------------------------------- 1 | namespace DeveloperTools.AlloySandbox; 2 | 3 | public class Program 4 | { 5 | public static void Main(string[] args) => CreateHostBuilder(args).Build().Run(); 6 | 7 | public static IHostBuilder CreateHostBuilder(string[] args) => 8 | Host.CreateDefaultBuilder(args) 9 | .ConfigureCmsDefaults() 10 | .ConfigureWebHostDefaults(webBuilder => webBuilder.UseStartup()); 11 | } 12 | -------------------------------------------------------------------------------- /tests/DeveloperTools.AlloySandbox/Properties/PublishProfiles/FolderProfile.pubxml: -------------------------------------------------------------------------------- 1 |  2 | 5 | 6 | 7 | false 8 | false 9 | true 10 | Release 11 | Any CPU 12 | FileSystem 13 | bin\Release\net6.0\publish\ 14 | FileSystem 15 | <_TargetId>Folder 16 | 17 | -------------------------------------------------------------------------------- /tests/DeveloperTools.AlloySandbox/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "DeveloperTools.AlloySandbox": { 4 | "commandName": "Project", 5 | "launchBrowser": true, 6 | "applicationUrl": "https://localhost:5000/", 7 | "environmentVariables": { 8 | "ASPNETCORE_ENVIRONMENT": "Development" 9 | } 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /tests/DeveloperTools.AlloySandbox/README.md: -------------------------------------------------------------------------------- 1 | # Alloy MVC template 2 | 3 | This template should not be seen as best practices, but as a great way to learn and test Optimizely CMS. 4 | 5 | ## How to run 6 | 7 | Chose one of the following options to get started. 8 | 9 | ### Windows 10 | 11 | Prerequisities 12 | - .NET SDK 6+ 13 | - SQL Server 2016 Express LocalDB (or later) 14 | 15 | ```bash 16 | $ dotnet run 17 | ```` 18 | 19 | ### Any OS with Docker 20 | 21 | Prerequisities 22 | - Docker 23 | - Enable Docker support when applying the template 24 | 25 | ```bash 26 | $ docker-compose up 27 | ```` 28 | 29 | > Note that this Docker setup is just configured for local development. Follow this [guide to enable HTTPS](https://github.com/dotnet/dotnet-docker/blob/main/samples/run-aspnetcore-https-development.md). 30 | 31 | ### Any OS with external database server 32 | 33 | Prerequisities 34 | - .NET SDK 6+ 35 | - SQL Server 2016 (or later) on a external server, e.g. Azure SQL 36 | 37 | Create an empty database on the external database server and update the connection string accordingly. 38 | 39 | ```bash 40 | $ dotnet run 41 | ```` 42 | -------------------------------------------------------------------------------- /tests/DeveloperTools.AlloySandbox/Resources/Translations/Display.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Mobile 7 | 8 | 9 | Web 10 | 11 | 12 | 13 | Full 14 | Wide 15 | Half 16 | Narrow 17 | 18 | 19 | Android vertical (480x800) 20 | iPad horizontal (1024x768) 21 | iPhone vertical (320x568) 22 | Standard (1366x768) 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /tests/DeveloperTools.AlloySandbox/Resources/Translations/EditorHints.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Button Text 7 | 8 | 9 | This block type does not have a renderer for this type of content area. 10 | 11 | 12 | 13 | The block '{0}' when displayed as {1} 14 | The block '{0}' cannot be displayed as {1} 15 | No renderer found for '{0}' 16 | 17 | 18 | Error while rendering {0} {1} 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /tests/DeveloperTools.AlloySandbox/Resources/Translations/GroupNames.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Default 7 | 8 | 9 | News 10 | 11 | 12 | Products 13 | 14 | 15 | SEO 16 | 17 | 18 | Site settings 19 | 20 | 21 | Specialized 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /tests/DeveloperTools.AlloySandbox/Resources/Translations/Views.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | E-mail 6 | No contact selected 7 | Phone 8 | 9 |
10 | The Company 11 | Customer Zone 12 | Log in 13 | Log out 14 | News & Events 15 | Products 16 |
17 | 18 | Search 19 | 20 | 21 | Latest news 22 | News list will be empty since no list root has been set 23 | 24 | 25 | EPiServer Search is not configured or is not active for this website. 26 | hits 27 | Search result 28 | resulted in 29 | Search 30 | Your search for 31 | no 32 | 33 |
34 |
35 | -------------------------------------------------------------------------------- /tests/DeveloperTools.AlloySandbox/Resources/Translations/_ReadMe.txt: -------------------------------------------------------------------------------- 1 | All language files in this folder are included in the LocalizationService. 2 | 3 | The path to this folder is configured in EPiServerFramework.config: 4 | 5 | 6 | 7 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /tests/DeveloperTools.AlloySandbox/Startup.cs: -------------------------------------------------------------------------------- 1 | using DeveloperTools.AlloySandbox.Extensions; 2 | using EPiServer.Cms.Shell; 3 | using EPiServer.Cms.UI.AspNetIdentity; 4 | using EPiServer.DeveloperTools.Infrastructure.Configuration; 5 | using EPiServer.DeveloperTools.Infrastructure.Initialization; 6 | using EPiServer.Scheduler; 7 | using EPiServer.Web.Routing; 8 | 9 | namespace DeveloperTools.AlloySandbox; 10 | 11 | public class Startup 12 | { 13 | private readonly IWebHostEnvironment _webHostingEnvironment; 14 | 15 | public Startup(IWebHostEnvironment webHostingEnvironment) 16 | { 17 | _webHostingEnvironment = webHostingEnvironment; 18 | } 19 | 20 | public void ConfigureServices(IServiceCollection services) 21 | { 22 | if (_webHostingEnvironment.IsDevelopment()) 23 | { 24 | AppDomain.CurrentDomain.SetData("DataDirectory", Path.Combine(_webHostingEnvironment.ContentRootPath, "App_Data")); 25 | 26 | services.Configure(options => options.Enabled = false); 27 | } 28 | 29 | services 30 | .AddCmsAspNetIdentity() 31 | .AddCms() 32 | .AddAlloy() 33 | .AddAdminUserRegistration() 34 | .AddEmbeddedLocalization(); 35 | 36 | // Required by Wangkanai.Detection 37 | services.AddDetection(); 38 | 39 | services.AddSession(options => 40 | { 41 | options.IdleTimeout = TimeSpan.FromSeconds(10); 42 | options.Cookie.HttpOnly = true; 43 | options.Cookie.IsEssential = true; 44 | }); 45 | 46 | services.AddOptimizelyDeveloperTools(); 47 | } 48 | 49 | public void Configure(IApplicationBuilder app, IWebHostEnvironment env) 50 | { 51 | if (env.IsDevelopment()) 52 | { 53 | app.UseDeveloperExceptionPage(); 54 | } 55 | 56 | // Required by Wangkanai.Detection 57 | app.UseDetection(); 58 | app.UseSession(); 59 | 60 | app.UseStaticFiles(); 61 | app.UseRouting(); 62 | app.UseAuthentication(); 63 | app.UseAuthorization(); 64 | 65 | app.UseOptimizelyDeveloperTools(); 66 | 67 | app.UseEndpoints(endpoints => 68 | { 69 | endpoints.MapContent(); 70 | }); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /tests/DeveloperTools.AlloySandbox/Views/ArticlePage/Index.cshtml: -------------------------------------------------------------------------------- 1 | @model PageViewModel 2 | 3 | @{ Layout = "~/Views/Shared/Layouts/_LeftNavigation.cshtml"; } 4 | 5 |
6 |

x.CurrentPage.PageName)>@Model.CurrentPage.PageName

7 |

x.CurrentPage.MetaDescription)>@Model.CurrentPage.MetaDescription

8 |
9 | @Html.PropertyFor(m => m.CurrentPage.MainBody) 10 |
11 |
12 | 13 | @Html.PropertyFor(x => x.CurrentPage.MainContentArea, new { CssClass = "row" }) 14 | -------------------------------------------------------------------------------- /tests/DeveloperTools.AlloySandbox/Views/LandingPage/Index.cshtml: -------------------------------------------------------------------------------- 1 | @model PageViewModel 2 | 3 |
4 | @Html.PropertyFor(x => x.CurrentPage.MainContentArea, new { CssClass = "row" }) 5 |
6 | -------------------------------------------------------------------------------- /tests/DeveloperTools.AlloySandbox/Views/NewsPage/Index.cshtml: -------------------------------------------------------------------------------- 1 | @model PageViewModel 2 | 3 | @{ Layout = "~/Views/Shared/Layouts/_LeftNavigation.cshtml"; } 4 | 5 |
6 |

x.CurrentPage.PageName)>@Model.CurrentPage.PageName

7 |

x.CurrentPage.MetaDescription)>@Model.CurrentPage.MetaDescription

8 |
9 | @Html.PropertyFor(m => m.CurrentPage.MainBody) 10 |
11 | @Html.PropertyFor(x => x.CurrentPage.NewsList) 12 |
13 | 14 | @Html.PropertyFor(x => x.CurrentPage.MainContentArea, new { CssClass = "row" }) 15 | -------------------------------------------------------------------------------- /tests/DeveloperTools.AlloySandbox/Views/Preview/Index.cshtml: -------------------------------------------------------------------------------- 1 | @model PreviewModel 2 | 3 | @foreach (var area in Model.Areas) 4 | { 5 | if (area.Supported) 6 | { 7 | @await Html.PartialAsync("TemplateHint", string.Format(System.Globalization.CultureInfo.CurrentUICulture, LocalizationService.Current.GetString("/preview/heading"), Model.PreviewContent.Name, LocalizationService.Current.GetString(area.AreaName))) 8 | 9 |
10 | @Html.DisplayFor(x => area.ContentArea, new { Tag = area.AreaTag }) 11 |
12 | } 13 | else 14 | { 15 | @await Html.PartialAsync("TemplateHint", string.Format(System.Globalization.CultureInfo.CurrentUICulture, LocalizationService.Current.GetString("/preview/norenderer"), Model.PreviewContent.Name, LocalizationService.Current.GetString(area.AreaName)))} 16 | } 17 | 18 | @if (!Model.Areas.Any()) 19 | { 20 | @await Html.PartialAsync("TemplateHint", string.Format(System.Globalization.CultureInfo.CurrentUICulture, LocalizationService.Current.GetString("/preview/norendereratall"), Model.PreviewContent.Name)) 21 | } 22 | -------------------------------------------------------------------------------- /tests/DeveloperTools.AlloySandbox/Views/ProductPage/Index.cshtml: -------------------------------------------------------------------------------- 1 | @model PageViewModel 2 | 3 | @{ Layout = "~/Views/Shared/Layouts/_TwoPlusOne.cshtml"; } 4 | 5 |
6 |

x.CurrentPage.PageName)>@Model.CurrentPage.PageName

7 |

x.CurrentPage.MetaDescription)>@Model.CurrentPage.MetaDescription

8 |
9 | @Html.PropertyFor(m => m.CurrentPage.MainBody) 10 |
11 |
12 | 13 | @Html.PropertyFor(x => x.CurrentPage.MainContentArea, new { CssClass = "row" }) 14 | 15 | @section RelatedContent 16 | { 17 |
x.CurrentPage.PageImage)> 18 | 19 |
20 | 21 |
22 |

x.CurrentPage.PageName)>@Model.CurrentPage.PageName

23 | @Html.PropertyFor(x => x.CurrentPage.UniqueSellingPoints) 24 |
25 | 26 | @Html.PropertyFor(x => x.CurrentPage.RelatedContentArea, new { Aside = true }) 27 | } 28 | -------------------------------------------------------------------------------- /tests/DeveloperTools.AlloySandbox/Views/SearchPage/Index.cshtml: -------------------------------------------------------------------------------- 1 | @model SearchContentModel 2 | 3 | @{ 4 | Layout = "~/Views/Shared/Layouts/_TwoPlusOne.cshtml"; 5 | } 6 | 7 |
8 |
9 | @{ 10 | using (Html.BeginForm(null, null, Html.ViewContext.IsInEditMode() ? FormMethod.Post : FormMethod.Get, new { @action = Model.Layout.SearchActionUrl })) 11 | { 12 | 13 | 14 | } 15 | } 16 |
17 |
18 | 19 | @if (Model.Hits != null) 20 | { 21 |
22 |
23 |

@Html.Translate("/searchpagetemplate/result")

24 |

25 | @Html.Translate("/searchpagetemplate/searchfor") @Model.SearchedQuery 26 | @Html.Translate("/searchpagetemplate/resultedin") 27 | @if (Model.NumberOfHits > 0) 28 | { 29 | @Model.NumberOfHits 30 | } 31 | else 32 | { 33 | @Html.Translate("/searchpagetemplate/zero") 34 | } 35 | @Html.Translate("/searchpagetemplate/hits") 36 |

37 |
38 |
39 | 40 |
41 |
42 | @foreach (var hit in Model.Hits) 43 | { 44 |
45 |

@hit.Title

46 |

@hit.Excerpt

47 |
48 |
49 | } 50 |
51 |
52 | 53 | } 54 | 55 | @if (Model.SearchServiceDisabled && Html.ViewContext.IsInEditMode()) 56 | { 57 | @await Html.PartialAsync("TemplateHint", Html.Translate("/searchpagetemplate/disabled").ToString()) 58 | } 59 | -------------------------------------------------------------------------------- /tests/DeveloperTools.AlloySandbox/Views/Shared/Blocks/ButtonBlock.cshtml: -------------------------------------------------------------------------------- 1 | @model ButtonBlock 2 | 3 | m.ButtonText)> 4 | @{ 5 | var buttonText = string.IsNullOrWhiteSpace(Model.ButtonText) 6 | ? LocalizationService.Current.GetString("/blocks/buttonblockcontrol/buttondefaulttext") 7 | : Model.ButtonText; 8 | } 9 | @buttonText 10 | 11 | -------------------------------------------------------------------------------- /tests/DeveloperTools.AlloySandbox/Views/Shared/Blocks/EditorialBlock.cshtml: -------------------------------------------------------------------------------- 1 | @model EditorialBlock 2 | 3 |
x.MainBody)> 4 | @Html.DisplayFor(x => Model.MainBody) 5 |
6 | -------------------------------------------------------------------------------- /tests/DeveloperTools.AlloySandbox/Views/Shared/Blocks/JumbotronBlock.cshtml: -------------------------------------------------------------------------------- 1 | @model JumbotronBlock 2 | 3 |
4 |
5 |
6 |

m.Heading)>@Model.Heading

7 |

m.SubHeading)>@Model.SubHeading

8 | m.ButtonText)>@Model.ButtonText 9 |
10 |
11 | -------------------------------------------------------------------------------- /tests/DeveloperTools.AlloySandbox/Views/Shared/Blocks/NoRenderer.cshtml: -------------------------------------------------------------------------------- 1 | @await Html.PartialAsync("TemplateHint", Html.Translate("/blocks/norenderer/message").ToString()) 2 | -------------------------------------------------------------------------------- /tests/DeveloperTools.AlloySandbox/Views/Shared/Blocks/SiteLogotypeBlock.cshtml: -------------------------------------------------------------------------------- 1 | @model SiteLogotypeBlock 2 | 3 | -------------------------------------------------------------------------------- /tests/DeveloperTools.AlloySandbox/Views/Shared/Blocks/TeaserBlock.cshtml: -------------------------------------------------------------------------------- 1 | @using EPiServer.Core 2 | 3 | @model TeaserBlock 4 | 5 |
6 | @*Link the teaser block only if a link has been set and not displayed in preview*@ 7 | @using (Html.BeginConditionalLink( 8 | !ContentReference.IsNullOrEmpty(Model.Link) && !(Html.ViewContext.IsPreviewMode()), 9 | Url.PageLinkUrl(Model.Link), 10 | Model.Heading)) 11 | { 12 |
x.Image)> 13 | 14 |
15 | 16 |

x.Heading)>@Model.Heading

17 |

x.Text)>@Model.Text

18 | } 19 |
20 | -------------------------------------------------------------------------------- /tests/DeveloperTools.AlloySandbox/Views/Shared/Blocks/TeaserBlockWide.cshtml: -------------------------------------------------------------------------------- 1 | @model TeaserBlock 2 | 3 |
4 |
x.Image)> 5 | 6 |
7 |
8 | @*Link the teaser block only if a link has been set and not displayed in preview*@ 9 | @using (Html.BeginConditionalLink( 10 | !ContentReference.IsNullOrEmpty(Model.Link) && !(Html.ViewContext.IsPreviewMode()), 11 | Url.PageLinkUrl(Model.Link), 12 | Model.Heading)) 13 | { 14 |

x.Heading)>@Model.Heading

15 |

x.Text)>@Model.Text

16 | } 17 |
18 |
19 | -------------------------------------------------------------------------------- /tests/DeveloperTools.AlloySandbox/Views/Shared/Breadcrumbs.cshtml: -------------------------------------------------------------------------------- 1 | @using EPiServer.Core 2 | @using EPiServer.Web 3 | 4 | @*Helper used as template for a page in the bread crumb, recursively triggering the rendering of the next page*@ 5 | 6 | @{ 7 | HelperResult ItemTemplate(HtmlHelpers.MenuItem breadCrumbItem) 8 | { 9 | if (breadCrumbItem.Selected) 10 | { 11 | if (breadCrumbItem.Page.HasTemplate() && !breadCrumbItem.Page.ContentLink.CompareToIgnoreWorkID(Model.CurrentPage.ContentLink)) 12 | { 13 | 16 | } 17 | else 18 | { 19 | 22 | } 23 | 24 | if (!breadCrumbItem.Page.ContentLink.CompareToIgnoreWorkID(Model.CurrentPage.ContentLink)) 25 | { 26 | @Html.MenuList(breadCrumbItem.Page.ContentLink, ItemTemplate) 27 | } 28 | } 29 | 30 | return new HelperResult(w => Task.CompletedTask); 31 | } 32 | } 33 | 34 | 42 | -------------------------------------------------------------------------------- /tests/DeveloperTools.AlloySandbox/Views/Shared/Components/ContactBlock/Default.cshtml: -------------------------------------------------------------------------------- 1 | @model ContactBlockModel 2 | 3 |
4 | @Html.PropertyFor(x => x.Image) 5 |

x.Heading)>@Model.Heading

6 | @Html.PropertyFor(x => x.ContactPage) 7 | @if(Model.ShowLink) 8 | { 9 | x.LinkText)> 10 | @Model.LinkText 11 | 12 | } 13 |
14 | -------------------------------------------------------------------------------- /tests/DeveloperTools.AlloySandbox/Views/Shared/Components/ImageFile/Default.cshtml: -------------------------------------------------------------------------------- 1 | @model ImageViewModel 2 | 3 | @Model.Name 4 | -------------------------------------------------------------------------------- /tests/DeveloperTools.AlloySandbox/Views/Shared/Components/PageListBlock/Default.cshtml: -------------------------------------------------------------------------------- 1 | @model PageListModel 2 | @{ 3 | var isAside = ViewData["Aside"] != null && (bool)ViewData["Aside"]; 4 | } 5 | 6 | @Html.FullRefreshPropertiesMetaData(new[] { "IncludePublishDate", "IncludeIntroduction", "Count", "SortOrder", "Root", "PageTypeFilter", "CategoryFilter", "Recursive" }) 7 | 8 |

x.Heading)>@Model.Heading

9 | 10 | @foreach (var page in Model.Pages) 11 | { 12 | if (isAside) 13 | { 14 |
15 |

@Html.PageLink(page)

16 | 17 | @if (Model.ShowPublishDate && page.StartPublish.HasValue) 18 | { 19 |

@Html.DisplayFor(x => page.StartPublish)

20 | } 21 | 22 | @if (Model.ShowIntroduction && page is SitePageData teaserPage) 23 | { 24 |

@teaserPage.TeaserText

25 | } 26 |
27 | } 28 | else 29 | { 30 | 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /tests/DeveloperTools.AlloySandbox/Views/Shared/Components/VideoFile/Default.cshtml: -------------------------------------------------------------------------------- 1 | @model VideoViewModel 2 | 3 | 6 | -------------------------------------------------------------------------------- /tests/DeveloperTools.AlloySandbox/Views/Shared/DisplayTemplates/ContactPage.cshtml: -------------------------------------------------------------------------------- 1 | @model ContactPage 2 | 3 |

4 | @if(Model != null) 5 | { 6 | @Model.PageName
7 | @Html.Translate("/contact/phone")@: : @Model.Phone
8 | @Html.Translate("/contact/email")@: : @Html.DisplayFor(m => m.Email) 9 | } 10 | else 11 | { 12 | @Html.Translate("/contact/noneselected") 13 | } 14 |

15 | -------------------------------------------------------------------------------- /tests/DeveloperTools.AlloySandbox/Views/Shared/DisplayTemplates/DateTime.cshtml: -------------------------------------------------------------------------------- 1 | @model DateTime 2 | 3 | @Model.ToString("d MMMM yyyy") 4 | -------------------------------------------------------------------------------- /tests/DeveloperTools.AlloySandbox/Views/Shared/DisplayTemplates/Image.cshtml: -------------------------------------------------------------------------------- 1 | @model EPiServer.Core.ContentReference 2 | 3 | @if (Model != null) 4 | { 5 | 6 | } 7 | -------------------------------------------------------------------------------- /tests/DeveloperTools.AlloySandbox/Views/Shared/DisplayTemplates/StringsCollection.cshtml: -------------------------------------------------------------------------------- 1 | @model IEnumerable 2 | 3 | @if(Model != null && Model.Any()) 4 | { 5 |
    6 | @foreach(var stringValue in Model) 7 | { 8 |
  • @stringValue
  • 9 | } 10 |
11 | } 12 | -------------------------------------------------------------------------------- /tests/DeveloperTools.AlloySandbox/Views/Shared/DisplayTemplates/_ReadMe.txt: -------------------------------------------------------------------------------- 1 | The views in this folder are used when rendering properties using Html.DisplayFor and Html.PropertyFor. 2 | Display templates are selected based on the type name of the property and, optionally, by UIHint and DataType attributes added to the property. 3 | Note that the CMS adds a number of view templates which do not exist in this folder but found through a view engine which the CMS adds at start up. 4 | Those view templates can be found in \Application\Util\Views\Shared\DisplayTemplates. Views in this folder takes precedence meaning 5 | that we can override those templates, which is currently done for content areas. -------------------------------------------------------------------------------- /tests/DeveloperTools.AlloySandbox/Views/Shared/Footer.cshtml: -------------------------------------------------------------------------------- 1 | @model IPageViewModel 2 | 3 |
4 |
5 |
6 |
7 |

@Html.Translate("/footer/products")

8 | @Html.PropertyFor(x => x.Layout.ProductPages) 9 |
10 |
11 |

@Html.Translate("/footer/company")

12 | @Html.PropertyFor(x => x.Layout.CompanyInformationPages) 13 |
14 |
15 |

@Html.Translate("/footer/news")

16 | @Html.PropertyFor(x => x.Layout.NewsPages) 17 |
18 |
19 |

@Html.Translate("/footer/customerzone")

20 | @Html.PropertyFor(x => x.Layout.CustomerZonePages) 21 |
    22 |
  • 23 | @if (Model.Layout.LoggedIn) 24 | { 25 | @Html.ContentLink(LocalizationService.Current.GetString("/footer/logout"), "Logout") } 26 | else 27 | { 28 | if (!Model.Layout.IsInReadonlyMode) 29 | { 30 | @Html.Translate("/footer/login") 31 | } 32 | } 33 |
  • 34 |
35 |
36 |
37 |
38 |
39 | -------------------------------------------------------------------------------- /tests/DeveloperTools.AlloySandbox/Views/Shared/Header.cshtml: -------------------------------------------------------------------------------- 1 | @using EPiServer.Web 2 | 3 | @model IPageViewModel 4 | 5 | @{ 6 | HelperResult ItemTemplate(HtmlHelpers.MenuItem item) 7 | { 8 | 11 | return new HelperResult(w => Task.CompletedTask); 12 | } 13 | } 14 | 15 |
16 |
17 | 32 |
33 |
34 | 35 | @*@{ using (Html.BeginForm(null, null, Html.ViewContext.IsInEditMode() ? FormMethod.Post : FormMethod.Get, new { @action = Model.Layout.SearchActionUrl })) 36 | { 37 | 38 | 39 | } 40 | }*@ 41 | -------------------------------------------------------------------------------- /tests/DeveloperTools.AlloySandbox/Views/Shared/Layouts/_LeftNavigation.cshtml: -------------------------------------------------------------------------------- 1 | @model IPageViewModel 2 | 3 | @{ Layout = "~/Views/Shared/Layouts/_Root.cshtml"; } 4 | 5 | @{await Html.RenderPartialAsync("Breadcrumbs", Model);} 6 | 7 |
8 |
9 | @{await Html.RenderPartialAsync("SubNavigation", Model);} 10 | @RenderSection("RelatedContent", false) 11 |
12 | 13 |
14 | @RenderBody() 15 |
16 |
17 | -------------------------------------------------------------------------------- /tests/DeveloperTools.AlloySandbox/Views/Shared/Layouts/_Root.cshtml: -------------------------------------------------------------------------------- 1 | @using EPiServer.Framework.Web.Mvc.Html 2 | 3 | @model IPageViewModel 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | @Model.CurrentPage.MetaTitle 12 | @if (Model.CurrentPage.MetaKeywords != null && Model.CurrentPage.MetaKeywords.Count > 0) 13 | { 14 | 15 | } 16 | @if (!string.IsNullOrWhiteSpace(Model.CurrentPage.MetaDescription)) 17 | { 18 | 19 | } 20 | @Html.CanonicalLink() 21 | @Html.AlternateLinks() 22 | 23 | 24 | 25 | 26 | @Html.RequiredClientResources("Header") 27 | 28 | 29 | 30 | @if (Model.Layout.IsInReadonlyMode) 31 | { 32 | await Html.RenderPartialAsync("Readonly", Model); 33 | } 34 | 35 | @await Html.RenderEPiServerQuickNavigatorAsync() 36 | 37 | @Html.FullRefreshPropertiesMetaData() 38 | 39 | @if (!Model.Layout.HideHeader) 40 | { 41 | await Html.RenderPartialAsync("Header", Model); 42 | } 43 | 44 |
45 | @RenderBody() 46 |
47 | 48 | @if (!Model.Layout.HideFooter) 49 | { 50 | await Html.RenderPartialAsync("Footer", Model); 51 | } 52 | 53 | @Html.RequiredClientResources("Footer") 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /tests/DeveloperTools.AlloySandbox/Views/Shared/Layouts/_TwoPlusOne.cshtml: -------------------------------------------------------------------------------- 1 | @model IPageViewModel 2 | 3 | @{ 4 | Layout = "~/Views/Shared/Layouts/_Root.cshtml"; 5 | } 6 | 7 | @{await Html.RenderPartialAsync("Breadcrumbs");} 8 | 9 |
10 |
11 | @RenderBody() 12 |
13 | 14 |
15 | @if (IsSectionDefined("RelatedContent")) 16 | { 17 | @RenderSection("RelatedContent") 18 | } 19 | else if (Model.CurrentPage is IHasRelatedContent) 20 | { 21 | @Html.PropertyFor(x => ((IHasRelatedContent)x.CurrentPage).RelatedContentArea, new { Aside = true }) 22 | } 23 |
24 |
25 | -------------------------------------------------------------------------------- /tests/DeveloperTools.AlloySandbox/Views/Shared/PagePartials/ContactPage.cshtml: -------------------------------------------------------------------------------- 1 | @model ContactPage 2 | 3 |
4 | 5 |

@Model.PageName

6 |

@Model.TeaserText

7 |

8 | @Model.Email 9 | @Model.Phone 10 |

11 |
12 | -------------------------------------------------------------------------------- /tests/DeveloperTools.AlloySandbox/Views/Shared/PagePartials/Page.cshtml: -------------------------------------------------------------------------------- 1 | @using EPiServer.Core 2 | 3 | @model SitePageData 4 | 5 |
6 | @using (Html.BeginConditionalLink(Model.HasTemplate(), Url.PageLinkUrl(Model), Model.PageName)) 7 | { 8 |
9 | @Html.DisplayFor(m => m.PageImage) 10 |
11 |

@Model.PageName

12 |

@Model.TeaserText

13 | } 14 |
15 | -------------------------------------------------------------------------------- /tests/DeveloperTools.AlloySandbox/Views/Shared/PagePartials/PageWide.cshtml: -------------------------------------------------------------------------------- 1 | @using EPiServer.Core 2 | 3 | @model SitePageData 4 | 5 |
6 |
7 |
8 | 9 |
10 |
11 | @using (Html.BeginConditionalLink(Model.HasTemplate(), Url.PageLinkUrl(Model), Model.PageName)) 12 | { 13 |

@Model.PageName

14 |

@Model.TeaserText

15 | } 16 |
17 |
18 |
19 | -------------------------------------------------------------------------------- /tests/DeveloperTools.AlloySandbox/Views/Shared/Readonly.cshtml: -------------------------------------------------------------------------------- 1 | @model IPageViewModel 2 | 3 |
4 | -------------------------------------------------------------------------------- /tests/DeveloperTools.AlloySandbox/Views/Shared/SubNavigation.cshtml: -------------------------------------------------------------------------------- 1 | @model IPageViewModel 2 | 3 | @{ 4 | HelperResult ItemTemplate(HtmlHelpers.MenuItem item) 5 | { 6 | 17 | return new HelperResult(w => Task.CompletedTask); 18 | } 19 | } 20 | 21 | 24 | -------------------------------------------------------------------------------- /tests/DeveloperTools.AlloySandbox/Views/Shared/TemplateError.cshtml: -------------------------------------------------------------------------------- 1 | @model ContentRenderingErrorModel 2 |
3 |

@string.Format(System.Globalization.CultureInfo.CurrentUICulture, LocalizationService.Current.GetString("/renderingerror/heading"), Model.ContentTypeName, Model.ContentName)

4 |

5 | @Model.Exception.Message
6 | @Model.Exception.StackTrace 7 |

8 |
9 | -------------------------------------------------------------------------------- /tests/DeveloperTools.AlloySandbox/Views/Shared/TemplateHint.cshtml: -------------------------------------------------------------------------------- 1 | @model string 2 |

@Model

3 | -------------------------------------------------------------------------------- /tests/DeveloperTools.AlloySandbox/Views/StandardPage/Index.cshtml: -------------------------------------------------------------------------------- 1 | @model PageViewModel 2 | 3 | @{ Layout = "~/Views/Shared/Layouts/_LeftNavigation.cshtml"; } 4 | 5 |
6 |

x.CurrentPage.PageName)>@Model.CurrentPage.PageName

7 |

x.CurrentPage.MetaDescription)>@Model.CurrentPage.MetaDescription

8 |
9 | @Html.PropertyFor(m => m.CurrentPage.MainBody) 10 |
11 |
12 | 13 | @Html.PropertyFor(x => x.CurrentPage.MainContentArea, new { CssClass = "row" }) 14 | -------------------------------------------------------------------------------- /tests/DeveloperTools.AlloySandbox/Views/StartPage/Index.cshtml: -------------------------------------------------------------------------------- 1 | @model PageViewModel 2 | 3 |
4 | @Html.PropertyFor(x => x.CurrentPage.MainContentArea, new { CssClass = "row" }) 5 |
6 | -------------------------------------------------------------------------------- /tests/DeveloperTools.AlloySandbox/Views/_ReadMe.txt: -------------------------------------------------------------------------------- 1 | View locations in Alloy follows a number of conventions in addition to the default ASP.NET MVC conventions: 2 | * Views for pages and blocks with their own controllers use standard ASP.NET MVC conventions - /.cshtml 3 | * Page types which don't have their own controller are mapped to /Index.cshtml by DefaultPageController 4 | * Views for block types which don't have their own controllers are found in Shared/Blocks 5 | * Partial views for page types which don't have their own controllers for partial requests are found in Shared/PagePartials -------------------------------------------------------------------------------- /tests/DeveloperTools.AlloySandbox/Views/_ViewImports.cshtml: -------------------------------------------------------------------------------- 1 | @using DeveloperTools.AlloySandbox 2 | @using DeveloperTools.AlloySandbox.Extensions 3 | @using DeveloperTools.AlloySandbox.Helpers 4 | @using DeveloperTools.AlloySandbox.Models.Blocks 5 | @using DeveloperTools.AlloySandbox.Models.Media 6 | @using DeveloperTools.AlloySandbox.Models.Pages 7 | @using DeveloperTools.AlloySandbox.Models.ViewModels 8 | @using EPiServer.Framework.Localization 9 | @using EPiServer.Web.Mvc.Html 10 | @using EPiServer.Shell.Web.Mvc.Html 11 | @using EPiServer.Core 12 | @using EPiServer.Web 13 | @using EPiServer.Web.Mvc 14 | @using EPiServer.Web.Routing 15 | 16 | @using Microsoft.AspNetCore.Mvc.Razor 17 | @using Microsoft.AspNetCore.Html 18 | 19 | @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers 20 | -------------------------------------------------------------------------------- /tests/DeveloperTools.AlloySandbox/Views/_viewstart.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | Layout = "~/Views/Shared/Layouts/_Root.cshtml"; 3 | } 4 | -------------------------------------------------------------------------------- /tests/DeveloperTools.AlloySandbox/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "EPiServer": "Information", 7 | "Microsoft.Hosting.Lifetime": "Information" 8 | } 9 | }, 10 | "ConnectionStrings": { 11 | "EPiServerDB": "Server=.\\sqlexpress;Initial Catalog=DeveloperTools.AlloySandbox;Integrated Security=True;Connect Timeout=30;Encrypt=True;TrustServerCertificate=True;" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /tests/DeveloperTools.AlloySandbox/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Warning", 5 | "Microsoft": "Warning", 6 | "EPiServer": "Warning", 7 | "Microsoft.Hosting.Lifetime": "Information" 8 | } 9 | }, 10 | "AllowedHosts": "*", 11 | "EPiServer": { 12 | "Cms": { 13 | "MappedRoles": { 14 | "Items": { 15 | "CmsEditors": { 16 | "MappedRoles": [ "WebEditors", "WebAdmins" ] 17 | }, 18 | "CmsAdmins": { 19 | "MappedRoles": [ "WebAdmins" ] 20 | } 21 | } 22 | } 23 | }, 24 | "DeveloperToolsOptions": {} 25 | } 26 | } -------------------------------------------------------------------------------- /tests/DeveloperTools.AlloySandbox/compilerconfig.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "inputFile": "Assets/scss/style.scss", 4 | "outputFile": "wwwroot/css/style.css" 5 | }, 6 | { 7 | "inputFile": "Assets/scss/theme.scss", 8 | "outputFile": "wwwroot/css/theme.css" 9 | } 10 | ] 11 | -------------------------------------------------------------------------------- /tests/DeveloperTools.AlloySandbox/compilerconfig.json.defaults: -------------------------------------------------------------------------------- 1 | { 2 | "compilers": { 3 | "less": { 4 | "autoPrefix": "", 5 | "cssComb": "none", 6 | "ieCompat": true, 7 | "strictMath": false, 8 | "strictUnits": false, 9 | "relativeUrls": true, 10 | "rootPath": "", 11 | "sourceMapRoot": "", 12 | "sourceMapBasePath": "", 13 | "sourceMap": false 14 | }, 15 | "sass": { 16 | "autoPrefix": "", 17 | "includePath": "", 18 | "indentType": "space", 19 | "indentWidth": 2, 20 | "outputStyle": "nested", 21 | "Precision": 5, 22 | "relativeUrls": true, 23 | "sourceMapRoot": "", 24 | "lineFeed": "", 25 | "sourceMap": false 26 | }, 27 | "stylus": { 28 | "sourceMap": false 29 | }, 30 | "babel": { 31 | "sourceMap": false 32 | }, 33 | "coffeescript": { 34 | "bare": false, 35 | "runtimeMode": "node", 36 | "sourceMap": false 37 | }, 38 | "handlebars": { 39 | "root": "", 40 | "noBOM": false, 41 | "name": "", 42 | "namespace": "", 43 | "knownHelpersOnly": false, 44 | "forcePartial": false, 45 | "knownHelpers": [], 46 | "commonjs": "", 47 | "amd": false, 48 | "sourceMap": false 49 | } 50 | }, 51 | "minifiers": { 52 | "css": { 53 | "enabled": true, 54 | "termSemicolons": true, 55 | "gzip": false 56 | }, 57 | "javascript": { 58 | "enabled": true, 59 | "termSemicolons": true, 60 | "gzip": false 61 | } 62 | } 63 | } -------------------------------------------------------------------------------- /tests/DeveloperTools.AlloySandbox/nuget.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /tests/DeveloperTools.AlloySandbox/wwwroot/css/images/ui-bg_flat_0_aaaaaa_40x100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/episerver/DeveloperTools/9bcc6ec6c7be5f5f5e98debd7344913bf0739449/tests/DeveloperTools.AlloySandbox/wwwroot/css/images/ui-bg_flat_0_aaaaaa_40x100.png -------------------------------------------------------------------------------- /tests/DeveloperTools.AlloySandbox/wwwroot/css/images/ui-bg_flat_75_ffffff_40x100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/episerver/DeveloperTools/9bcc6ec6c7be5f5f5e98debd7344913bf0739449/tests/DeveloperTools.AlloySandbox/wwwroot/css/images/ui-bg_flat_75_ffffff_40x100.png -------------------------------------------------------------------------------- /tests/DeveloperTools.AlloySandbox/wwwroot/css/images/ui-bg_glass_55_fbf9ee_1x400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/episerver/DeveloperTools/9bcc6ec6c7be5f5f5e98debd7344913bf0739449/tests/DeveloperTools.AlloySandbox/wwwroot/css/images/ui-bg_glass_55_fbf9ee_1x400.png -------------------------------------------------------------------------------- /tests/DeveloperTools.AlloySandbox/wwwroot/css/images/ui-bg_glass_65_ffffff_1x400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/episerver/DeveloperTools/9bcc6ec6c7be5f5f5e98debd7344913bf0739449/tests/DeveloperTools.AlloySandbox/wwwroot/css/images/ui-bg_glass_65_ffffff_1x400.png -------------------------------------------------------------------------------- /tests/DeveloperTools.AlloySandbox/wwwroot/css/images/ui-bg_glass_75_dadada_1x400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/episerver/DeveloperTools/9bcc6ec6c7be5f5f5e98debd7344913bf0739449/tests/DeveloperTools.AlloySandbox/wwwroot/css/images/ui-bg_glass_75_dadada_1x400.png -------------------------------------------------------------------------------- /tests/DeveloperTools.AlloySandbox/wwwroot/css/images/ui-bg_glass_75_e6e6e6_1x400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/episerver/DeveloperTools/9bcc6ec6c7be5f5f5e98debd7344913bf0739449/tests/DeveloperTools.AlloySandbox/wwwroot/css/images/ui-bg_glass_75_e6e6e6_1x400.png -------------------------------------------------------------------------------- /tests/DeveloperTools.AlloySandbox/wwwroot/css/images/ui-bg_highlight-soft_75_cccccc_1x100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/episerver/DeveloperTools/9bcc6ec6c7be5f5f5e98debd7344913bf0739449/tests/DeveloperTools.AlloySandbox/wwwroot/css/images/ui-bg_highlight-soft_75_cccccc_1x100.png -------------------------------------------------------------------------------- /tests/DeveloperTools.AlloySandbox/wwwroot/css/images/ui-icons_222222_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/episerver/DeveloperTools/9bcc6ec6c7be5f5f5e98debd7344913bf0739449/tests/DeveloperTools.AlloySandbox/wwwroot/css/images/ui-icons_222222_256x240.png -------------------------------------------------------------------------------- /tests/DeveloperTools.AlloySandbox/wwwroot/css/images/ui-icons_2e83ff_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/episerver/DeveloperTools/9bcc6ec6c7be5f5f5e98debd7344913bf0739449/tests/DeveloperTools.AlloySandbox/wwwroot/css/images/ui-icons_2e83ff_256x240.png -------------------------------------------------------------------------------- /tests/DeveloperTools.AlloySandbox/wwwroot/css/images/ui-icons_454545_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/episerver/DeveloperTools/9bcc6ec6c7be5f5f5e98debd7344913bf0739449/tests/DeveloperTools.AlloySandbox/wwwroot/css/images/ui-icons_454545_256x240.png -------------------------------------------------------------------------------- /tests/DeveloperTools.AlloySandbox/wwwroot/css/images/ui-icons_888888_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/episerver/DeveloperTools/9bcc6ec6c7be5f5f5e98debd7344913bf0739449/tests/DeveloperTools.AlloySandbox/wwwroot/css/images/ui-icons_888888_256x240.png -------------------------------------------------------------------------------- /tests/DeveloperTools.AlloySandbox/wwwroot/css/images/ui-icons_cd0a0a_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/episerver/DeveloperTools/9bcc6ec6c7be5f5f5e98debd7344913bf0739449/tests/DeveloperTools.AlloySandbox/wwwroot/css/images/ui-icons_cd0a0a_256x240.png -------------------------------------------------------------------------------- /tests/DeveloperTools.AlloySandbox/wwwroot/css/style.css: -------------------------------------------------------------------------------- 1 | @import url("https://cdn.jsdelivr.net/npm/bootstrap-icons@1.5.0/font/bootstrap-icons.css"); 2 | img, video { 3 | max-width: 100%; } 4 | 5 | .teaserblock > div { 6 | height: 100%; } 7 | 8 | header { 9 | padding: 1.5rem; 10 | margin-bottom: 1.5rem; } 11 | header .logo { 12 | padding-right: 1rem; } 13 | header .logo img { 14 | max-height: 100px; } 15 | 16 | footer { 17 | padding: 1.5rem; 18 | margin-top: 1.5rem; } 19 | 20 | .content { 21 | margin-bottom: 1.5rem; } 22 | 23 | .block { 24 | margin-bottom: 1.5rem; } 25 | 26 | .jumbotronblock { 27 | color: #fff; 28 | position: relative; } 29 | .jumbotronblock > div { 30 | background-size: cover; 31 | background-position: 50% 50%; 32 | position: relative; } 33 | .jumbotronblock .jumbotron-dimmer { 34 | background: #000; 35 | position: absolute; 36 | width: 100%; 37 | height: 100%; 38 | top: 0; 39 | left: 0; 40 | opacity: .4; } 41 | .jumbotronblock .jumbotron-inner { 42 | position: relative; } 43 | -------------------------------------------------------------------------------- /tests/DeveloperTools.AlloySandbox/wwwroot/css/style.min.css: -------------------------------------------------------------------------------- 1 | @import url("https://cdn.jsdelivr.net/npm/bootstrap-icons@1.5.0/font/bootstrap-icons.css");img,video{max-width:100%;}.teaserblock>div{height:100%;}header{padding:1.5rem;margin-bottom:1.5rem;}header .logo{padding-right:1rem;}header .logo img{max-height:100px;}footer{padding:1.5rem;margin-top:1.5rem;}.content{margin-bottom:1.5rem;}.block{margin-bottom:1.5rem;}.jumbotronblock{color:#fff;position:relative;}.jumbotronblock>div{background-size:cover;background-position:50% 50%;position:relative;}.jumbotronblock .jumbotron-dimmer{background:#000;position:absolute;width:100%;height:100%;top:0;left:0;opacity:.4;}.jumbotronblock .jumbotron-inner{position:relative;} -------------------------------------------------------------------------------- /tests/DeveloperTools.AlloySandbox/wwwroot/favicon.ico: -------------------------------------------------------------------------------- 1 | h( ...>>>Ɋ9777000$$$666!!!### -------------------------------------------------------------------------------- /tests/DeveloperTools.AlloySandbox/wwwroot/gfx/logotype.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/episerver/DeveloperTools/9bcc6ec6c7be5f5f5e98debd7344913bf0739449/tests/DeveloperTools.AlloySandbox/wwwroot/gfx/logotype.png -------------------------------------------------------------------------------- /tests/DeveloperTools.AlloySandbox/wwwroot/gfx/page-type-thumbnail-article.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/episerver/DeveloperTools/9bcc6ec6c7be5f5f5e98debd7344913bf0739449/tests/DeveloperTools.AlloySandbox/wwwroot/gfx/page-type-thumbnail-article.png -------------------------------------------------------------------------------- /tests/DeveloperTools.AlloySandbox/wwwroot/gfx/page-type-thumbnail-contact.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/episerver/DeveloperTools/9bcc6ec6c7be5f5f5e98debd7344913bf0739449/tests/DeveloperTools.AlloySandbox/wwwroot/gfx/page-type-thumbnail-contact.png -------------------------------------------------------------------------------- /tests/DeveloperTools.AlloySandbox/wwwroot/gfx/page-type-thumbnail-product.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/episerver/DeveloperTools/9bcc6ec6c7be5f5f5e98debd7344913bf0739449/tests/DeveloperTools.AlloySandbox/wwwroot/gfx/page-type-thumbnail-product.png -------------------------------------------------------------------------------- /tests/DeveloperTools.AlloySandbox/wwwroot/gfx/page-type-thumbnail-standard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/episerver/DeveloperTools/9bcc6ec6c7be5f5f5e98debd7344913bf0739449/tests/DeveloperTools.AlloySandbox/wwwroot/gfx/page-type-thumbnail-standard.png -------------------------------------------------------------------------------- /tests/DeveloperTools.AlloySandbox/wwwroot/gfx/page-type-thumbnail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/episerver/DeveloperTools/9bcc6ec6c7be5f5f5e98debd7344913bf0739449/tests/DeveloperTools.AlloySandbox/wwwroot/gfx/page-type-thumbnail.png -------------------------------------------------------------------------------- /tests/DeveloperTools.Tests/DeveloperTools.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net6.0 5 | false 6 | 7 | 8 | 9 | 10 | 11 | 12 | all 13 | runtime; build; native; contentfiles; analyzers; buildtransitive 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /tests/DeveloperTools.Tests/ListTests.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using EPiServer.DeveloperTools.Features.ModuleDependencies; 4 | using Xunit; 5 | 6 | namespace DeveloperTools.Tests; 7 | 8 | public class ListTests 9 | { 10 | [Fact] 11 | public void UnionTest() 12 | { 13 | var l1 = new List { new() { Id = "KEY1" }, new() { Id = "KEY2" } }; 14 | var l2 = new List { new() { Id = "KEY2" }, new() { Id = "KEY3" } }; 15 | 16 | var result = l1.Union(l2).DistinctBy(_ => _.Id); 17 | 18 | Assert.Equal(3, result.Count()); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /tests/DeveloperTools.Tests/app.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /tests/DeveloperTools.Tests/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | --------------------------------------------------------------------------------