| Package | Version |
|---|---|
| Renderings | |
| Renderings.UmbracoCms |
Requires DotNetStarter to run.
The goal of this package is to provide developers a framework for creating view models that support DI (dependency injection) using DotNetStarter. This package will not create document types from code, it will rather wrap IPublishedContent in a POCO for strongly typed view models to use in razor files.
The POCO view models need the following attribute:
[RenderingDocumentAlias("aliasString")]These models will then get discovered by the startup process and registered to the DotNetStarter container.
To use this package you need to install the following NuGet packages:
- Renderings.UmbracoCms
- DotNetStarter.Extensions.WebApi
- DotNetStarter.Extensions.Mvc
- DotNetStarter.DryIoc or DotnetStarter.StructureMap (either one is fine)
- NOTE The container package dependencies may need to be updated to resolve issues.
Create a custom global.asax class inheriting from Umbraco.Web.UmbracoApplication and in the constructor execute DotNetStarter.ApplicationContext.Startup.
usingDotNetStarter.Abstractions;usingDotNetStarter.Configure;usingSystem.Configuration;namespaceExampleNamespace{publicclassApplication:Umbraco.Web.UmbracoApplication{privatestaticStartupBuilder_startupBuilder;// to avoid trying to start twice/// <summary>/// Executs DotNetStarter.ApplicationContext.Startup, this class is used in the global.asax inherits/// </summary>publicApplication(){if(_startupBuilder!=null)return;_startupBuilder=StartupBuilder.Create().UseEnvironment(newDotNetStarter.StartupEnvironmentWeb(environmentName:ConfigurationManager.AppSettings["UmbracoEnv"])).ConfigureAssemblies(assemblies =>{assemblies.WithDiscoverableAssemblies().WithAssemblyFromType<Umbraco.Web.UmbracoApplication>()// Scan for backoffice controllers// add additional umbraco plugins, which inject controllers//.WithAssemblyFromType<Umbraco.Forms.Web.Controllers.UmbracoFormsController>()//.WithAssemblyFromType<Diplo.TraceLogViewer.Controllers.TraceLogTreeController>().WithAssemblyFromType<Application>();//types in this project}).OverrideDefaults(defaults =>{defaults// note: Only one locator is needed, and each of these implementations may also be passed an already configured DI container instance//.UseLocatorRegistryFactory(new DotNetStarter.Locators.DryIocLocatorFactory())//.UseLocatorRegistryFactory(new DotNetStarter.Locators.StructureMapFactory()).UseLocatorRegistryFactory(newDotNetStarter.Locators.LightInjectLocatorRegistryFactory()).UseLogger(newDotNetStarter.StringLogger(LogLevel.Error,1024000));// clears log after 1MB}).Build();_startupBuilder.Run();}}}Update the global.asax file's Inherits to use the full namespace of our custom class as noted below:
<%@ ApplicationInherits="Full.Namespace.Of.Class.Application"Language="C#"%>Then create a custom default controller to create the rendering models
publicclassCustomApplicationBaseController:RenderMvcController{privatereadonlyIRenderingCreatorScoped_RenderingCreator;publicCustomApplicationBaseController(){}publicCustomApplicationBaseController(IRenderingCreatorScopedrenderingCreator){_RenderingCreator=renderingCreator;}publicoverrideActionResultIndex(RenderModelmodel){varrendering=BuildRendering(model.Content,model.CurrentCulture);if(rendering==null){returnCurrentTemplate(model);// Fallback to default behaviour}if(rendering.IsFullPage==false){returnnewHttpNotFoundResult();// don't allow non full page models to return}returnCurrentTemplate(rendering);}privateIUmbracoRenderingBuildRendering(IPublishedContentcontent,CultureInfocultureInfo){varcreator=_RenderingCreator.GetCreator<IPublishedContent>(content.DocumentTypeAlias);varreturnModel=creator.Invoke(content)asIUmbracoRendering;if(returnModelisIUmbracoRenderingWithCulturecultureModel){cultureModel.CurrentCulture=cultureInfo;}returnreturnModel;}}Finally hijack the default MVC controller for Umbraco page content
/// <summary>/// This class registers the base application controller and setups up error page routing/// </summary>publicclassApplicationSetupMvc:ApplicationEventHandler{protectedoverridevoidApplicationStarting(UmbracoApplicationBaseumbracoApplication,ApplicationContextapplicationContext){base.ApplicationStarting(umbracoApplication,applicationContext);// note: this will set all routes to be hijacked by this base controllerUmbraco.Web.Mvc.DefaultRenderMvcControllerResolver.Current.SetDefaultControllerType(typeof(CustomApplicationBaseController));}}Also note, razor views will need to use one of the follow instead of @inherits Umbraco.Web.Mvc.TemplatePage
@modelRenderingsExample.Models.ViewModels.Home // where class implements IUmbracoRenderingor
@inheritsUmbraco.Web.Mvc.UmbracoViewPage<T>// where T is class implementing IUmbracoRenderingRenderings can be as simple or as complex as needed, below is a simple document type used to build hero slides:
[RenderingDocumentAlias("heroSlide")]publicclassHeroSlide:IUmbracoRendering{publicHeroSlide(IPublishedContentcontent){Content=content;}///<summary>/// Mapped property to given IPublishedContent CMS content///</summary>[RenderingPropertyAlias("title")]publicstringTitle{get{returnContent.GetPropertyValue<string>("title");}}[RenderingPropertyAlias("description")]publicstringDescription{get{returnContent.GetPropertyValue<string>("description");}}[RenderingPropertyAlias("link")]publicRelatedLinkLink{get{returnContent.GetPropertyValue<RelatedLinks>("link")?.FirstOrDefault()??newRelatedLink(){Caption="Link not set",Link="#notset"};}}[RenderingPropertyAlias("image")]publicIPublishedContentImage{get{returnContent.GetPropertyValue<IPublishedContent>("image");}}/// <summary>/// Instructs the default controller to throw a HTTP 404 message/// </summary>publicboolIsFullPage=>false;publicIPublishedContentContent{get;}/// <summary>/// Part of the IViewModel interface, which is a simplified template engine,/// allowing view models to decide how to render in partial views./// </summary>/// <param name="renderTag"></param>/// <returns></returns>publicstringGetPartialView(stringrenderTag=null){return"~/Views/Partials/HeroSlide.cshtml";}}View models can then be referenced using the built-in related links property as shown below on an example homepage document type:
[RenderingDocumentAlias("home")]publicclassHomeViewModel:IUmbracoRendering{publicHomeViewModel(IPublishedContentcontent,IRelatedLinksToRenderingConverterScopedrelatedLinksConverterScoped){Content=content;_RelatedLinksConverter=relatedLinksConverterScoped;}publicboolIsFullPage=>true;publicIPublishedContentContent{get;}privatereadonlyIRelatedLinksToRenderingConverterScoped_RelatedLinksConverter;privateIEnumerable<HeroSlide>_HeroSlides;/// <summary>/// Converts a RelatedLinks property to HeroSlide sequence./// </summary>[RenderingPropertyAlias("heroSlider")]publicIEnumerable<HeroSlide>HeroSlides{get{if(_HeroSlides==null){_HeroSlides=_RelatedLinksConverter.ConvertLinks<HeroSlide>(Content.GetPropertyValue<RelatedLinks>("heroSlider"),newType[]{typeof(HeroSlide)});}return_HeroSlides;}}/// <summary>/// We don't want to reuse homepage, so return "Empty" which corresponds to /Views/Partials/Empty.cshtml/// </summary>/// <param name="renderTag"></param>/// <returns></returns>publicstringGetPartialView(stringrenderTag=null){return"Empty";}}The sole purpose of the IRenderingAliasResolver is eliminate retyping document and property aliases throughout the code base. It can return a view model type from a string alias (hint: use IPublishedContent for this) or return a string alias for a given view model type.
For example getting a rendering type from alias:
// where _RenderingliasResolver is injected IRenderingAliasResolverTyperenderingType=_RenderingliasResolver.ResolveAlias(content.DocumentTypeAlias);Or from type to string alias (useful for searching, filtering, etc)
// returns 'heroSlide' from previous view model examplestringalias=_RenderingliasResolver.ResolveType(typeof(HeroSlide));Or get a property alias from a view model
// returns 'title' from previous HeroSlide view model examplestringpropertyAlias=_RenderingliasResolver.ResolvePropertyAlias<HeroSlide>(slide =>slide.Title);