Skip to content

Framework for create injected models for rendering content

License

Notifications You must be signed in to change notification settings

bmcdavid/Renderings

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

16 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Renderings Read Me

Build status

Package Version
Renderings NuGet version
Renderings.UmbracoCms NuGet version

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.

Umbraco Getting started

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.

using DotNetStarter.Abstractions;
using DotNetStarter.Configure;
using System.Configuration;

namespace ExampleNamespace
{
    public class Application : Umbraco.Web.UmbracoApplication
    {
        private static StartupBuilder _startupBuilder; // to avoid trying to start twice

        /// <summary>
        /// Executs DotNetStarter.ApplicationContext.Startup, this class is used in the global.asax inherits
        /// </summary>
        public Application()
        {
            if (_startupBuilder != null) return;
            _startupBuilder = StartupBuilder.Create()
                .UseEnvironment(new DotNetStarter.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(new DotNetStarter.Locators.LightInjectLocatorRegistryFactory())
                    .UseLogger(new DotNetStarter.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:

<%@ Application Inherits="Full.Namespace.Of.Class.Application" Language="C#" %>

Then create a custom default controller to create the rendering models

public class CustomApplicationBaseController : RenderMvcController
{
    private readonly IRenderingCreatorScoped _RenderingCreator;

    public CustomApplicationBaseController() { }

    public CustomApplicationBaseController(IRenderingCreatorScoped renderingCreator)
    {
        _RenderingCreator = renderingCreator;
    }

    public override ActionResult Index(RenderModel model)
    {
        var rendering = BuildRendering(model.Content, model.CurrentCulture);

        if (rendering == null)
        {
            return CurrentTemplate(model); // Fallback to default behaviour
        }

        if (rendering.IsFullPage == false)
        {
            return new HttpNotFoundResult(); // don't allow non full page models to return
        }

        return CurrentTemplate(rendering);
    }

    private IUmbracoRendering BuildRendering(IPublishedContent content, CultureInfo cultureInfo)
    {
        var creator = _RenderingCreator.GetCreator<IPublishedContent>(content.DocumentTypeAlias);
        var returnModel = creator.Invoke(content) as IUmbracoRendering;

        if (returnModel is IUmbracoRenderingWithCulture cultureModel)
        {
            cultureModel.CurrentCulture = cultureInfo;
        }

        return returnModel;
    }
}

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>
public class ApplicationSetupMvc : ApplicationEventHandler
{
    protected override void ApplicationStarting(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext)
    {
        base.ApplicationStarting(umbracoApplication, applicationContext);

        // note: this will set all routes to be hijacked by this base controller
        Umbraco.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

@model RenderingsExample.Models.ViewModels.Home // where class implements IUmbracoRendering

or

@inherits Umbraco.Web.Mvc.UmbracoViewPage<T> // where T is class implementing IUmbracoRendering

Rendering Example

Renderings can be as simple or as complex as needed, below is a simple document type used to build hero slides:

[RenderingDocumentAlias("heroSlide")]
public class HeroSlide : IUmbracoRendering
{
    public HeroSlide(IPublishedContent content)
    {
        Content = content;
    }

    ///<summary>
    /// Mapped property to given IPublishedContent CMS content
    ///</summary>
    [RenderingPropertyAlias("title")]
    public string Title
    {
        get { return Content.GetPropertyValue<string>("title"); }
    }

    [RenderingPropertyAlias("description")]
    public string Description
    {
        get { return Content.GetPropertyValue<string>("description"); }
    }

    [RenderingPropertyAlias("link")]
    public RelatedLink Link
    {
        get { return Content.GetPropertyValue<RelatedLinks>("link")?.FirstOrDefault() ?? new RelatedLink() { Caption = "Link not set", Link = "#notset" }; }
    }

    [RenderingPropertyAlias("image")]
    public IPublishedContent Image
    {
        get { return Content.GetPropertyValue<IPublishedContent>("image"); }
    }

    /// <summary>
    /// Instructs the default controller to throw a HTTP 404 message
    /// </summary>
    public bool IsFullPage => false;

    public IPublishedContent Content { 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>
    public string GetPartialView(string renderTag = 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")]
public class HomeViewModel : IUmbracoRendering
{
    public HomeViewModel(IPublishedContent content, IRelatedLinksToRenderingConverterScoped relatedLinksConverterScoped)
    {
        Content = content;
        _RelatedLinksConverter = relatedLinksConverterScoped;
    }

    public bool IsFullPage => true;

    public IPublishedContent Content { get; }

    private readonly IRelatedLinksToRenderingConverterScoped _RelatedLinksConverter;

    private IEnumerable<HeroSlide> _HeroSlides;

    /// <summary>
    /// Converts a RelatedLinks property to HeroSlide sequence.
    /// </summary>
    [RenderingPropertyAlias("heroSlider")]
    public IEnumerable<HeroSlide> HeroSlides
    {
        get
        {
            if (_HeroSlides == null)
            {
                _HeroSlides = _RelatedLinksConverter
                    .ConvertLinks<HeroSlide>(Content.GetPropertyValue<RelatedLinks>("heroSlider"),
                        new Type[] { 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>
    public string GetPartialView(string renderTag = null)
    {
        return "Empty";
    }
}

Using IRenderingAliasResolver to reduce magic strings

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 IRenderingAliasResolver
Type renderingType = _RenderingliasResolver.ResolveAlias(content.DocumentTypeAlias);

Or from type to string alias (useful for searching, filtering, etc)

// returns 'heroSlide' from previous view model example
string alias = _RenderingliasResolver.ResolveType(typeof(HeroSlide));

Or get a property alias from a view model

// returns 'title' from previous HeroSlide view model example
string propertyAlias = _RenderingliasResolver.ResolvePropertyAlias<HeroSlide>(slide => slide.Title);

About

Framework for create injected models for rendering content

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published