How do I specify different Layouts in the ASP.NET Core MVC

30,810

Solution 1

You can still do something very similar to your original approach, using ViewData to pass around the layout name (Although I would create it as a Result Filter):

public class ViewLayoutAttribute : ResultFilterAttribute
{
    private string layout;
    public ViewLayoutAttribute(string layout)
    {
        this.layout = layout;
    }

    public override void OnResultExecuting(ResultExecutingContext context)
    {
        var viewResult = context.Result as ViewResult;
        if (viewResult != null)
        {
            viewResult.ViewData["Layout"] = this.layout;
        }
    }        
}

Then in the _ViewStart.cshtml file:

@{
    Layout = (string)ViewData["Layout"] ?? "_Layout";
}

Finally, assuming you create a new layout like Views/Shared/_CleanLayout.cshtml, you can use that attribute on any controller or action:

[ViewLayout("_CleanLayout")]
public IActionResult About()
{
    //...
}

Solution 2

This is how I am using multiple layouts in my ASP.NET core MVC application.

You can try like this-

In _ViewStart.cshtml specify default _Layout like this-

@{
    Layout = "_Layout";
}

If you want to set page specific layout then in that page.cshtml, you can assign other view like this-

@{
    Layout = "~/Views/Shared/_Layout_2.cshtml";
    ViewData["Title"] = "Page Title";
}

See if this helps.

Solution 3

If you want to have a different layout based on some condition, you could use this code in the _ViewStart.cshtml file:

@{
    if (some condition)
    {
        Layout = "_Layout1";
    }
    else if (some other condition)
    {
        Layout = "_Layout2";
    }
    etc.
}

I use this in one of my projects. In my case the condition is User.IsInRole("admin") and my _ViewStart.cshtml file is as follows:

@{
    if (User.IsInRole("admin"))
    {
        Layout = "_AdminLayout";
    }
    else
    {
        Layout = "_Layout";
    }
}

Since there are only two roles in my project, which result in only one condition, this workaround is not too bad in my case. I hope someone with in a similar situation will find this useful :)

Solution 4

In ASP.NET Core 2.0, I was inspired by the answer of @Daniel J.G.

I made a ViewLayoutAttribute:

[AttributeUsage(AttributeTargets.Class)]
public class ViewLayoutAttribute : Attribute
{
    public ViewLayoutAttribute(string layoutName)
    {
        this.LayoutName = layoutName;
    }

    public string LayoutName { get; }
}

Exemple of a controller class:

[ViewLayout("_Layout2")]
public class MyController : Controller 
{
// Code
}

And I created an extension to get this very attribute from the ViewContext:

  public static class RazorExtensions
  {
        /// <summary>
        /// Gets the <see cref="ViewLayoutAttribute"/> from the current calling controller of the
        /// <see cref="ViewContext"/>.
        /// </summary>
        public static ViewLayoutAttribute GetLayoutAttribute(this ViewContext viewContext)
        {
            // See if Razor Page...
            if (viewContext.ActionDescriptor is CompiledPageActionDescriptor actionDescriptor)
            {
                // We want the attribute no matter what.
                return Attribute.GetCustomAttribute(actionDescriptor.ModelTypeInfo, typeof(ViewLayoutAttribute)) as ViewLayoutAttribute;
            }

            // See if MVC Controller...

            // Property ControllerTypeInfo can be seen on runtime.
            var controllerType = (Type)viewContext.ActionDescriptor
                .GetType()
                .GetProperty("ControllerTypeInfo")?
                .GetValue(viewContext.ActionDescriptor);

            if (controllerType != null && controllerType.IsSubclassOf(typeof(Microsoft.AspNetCore.Mvc.Controller)))
            {
                return Attribute.GetCustomAttribute(controllerType, typeof(ViewLayoutAttribute)) as ViewLayoutAttribute;
            }

            // Nothing found.
            return null;
        }
    }

And in _ViewStart.cshtml:

@using MyApp.Extensions

@{
    Layout = ViewContext.GetLayoutAttribute()?.LayoutName ?? "_Layout";
}
Share:
30,810
Wasyster
Author by

Wasyster

Updated on May 18, 2021

Comments

  • Wasyster
    Wasyster almost 3 years

    I would like to have 2 separate Layouts in my application. Let say one is for the Public section of the website and the other is empty for some reasons we need.

    Before Core I could do this to define a filter:

    public class LayoutInjecterAttribute : ActionFilterAttribute
    {
        private readonly string _masterName;
        public LayoutInjecterAttribute(string masterName)
        {
            _masterName = masterName;
        }
    
        public override void OnActionExecuted(ActionExecutedContext filterContext)
        {
            base.OnActionExecuted(filterContext);
            var result = filterContext.Result as ViewResult;
            if (result != null)
            {
                result.MasterName = _masterName;
            }
        }
    

    }

    Now the ViewResult do not have the MasterName property. Is it possible to do now, and not to use in the View the layout definition.

  • Wasyster
    Wasyster over 7 years
    Thx, but that's not what I am looking for. But interesting idea.
  • Wasyster
    Wasyster over 7 years
    I did this way, but I don't like the way. It's not elegant.
  • Naila Akbar
    Naila Akbar almost 7 years
    how do you get User object in viewStart at the start of application?
  • Kappacake
    Kappacake almost 7 years
    if you are using Razor, it should be available by default. The intellisense should even come up with the suggestion (this is within Visual Studio 2015+ that I am aware of). Let me know if you are having problems accessing this so I can give you a hand
  • ganders
    ganders almost 7 years
    This is exactly what I want to do, except I want to cache the result of User.IsInRole("admin") for the users session so it doesn't fire for every request (let it just retrieve it in the session for every subsequent request). I've implemented my own "SessionCacheService" where I have some pre-defined session variables, would that service be available in the ViewStart.cshtml file? Put dependency injection for this service in the ViewStart.cshtml?
  • Kappacake
    Kappacake almost 7 years
    You can reference any class in the project from within a view using '@'. Let's say your project "MyProject" has that "SessionCacheService" in a folder called "Cache"; you can reference it in any view (Including ViewStart.cshtml) using '@MyProject.Cache.SessionCacheService'. Does this help?
  • VSO
    VSO about 6 years
    Works for me, the other approach seems like a pain in the ass.
  • Iofacture
    Iofacture about 5 years
    Thank you - this was a very good introduction to asp.net core mvc filters / mvc lifecycle - and solved my issue all in one shot.
  • Ratul
    Ratul almost 4 years
    This helps me a lot!
  • Professor of programming
    Professor of programming almost 2 years
    You don't even need an attribute to do this, you can just set the ViewData["Layout"] directly in the action.