37

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.

johnnyRose
  • 7,310
  • 17
  • 40
  • 61
Wasyster
  • 2,279
  • 4
  • 26
  • 58

6 Answers6

49

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()
{
    //...
}
Daniel J.G.
  • 34,266
  • 9
  • 112
  • 112
39

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.

Sanket
  • 19,295
  • 10
  • 71
  • 82
14

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 :)

Kappacake
  • 1,826
  • 19
  • 38
  • how do you get User object in viewStart at the start of application? – Naila Akbar Jun 18 '17 at 20:10
  • 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 – Kappacake Jun 22 '17 at 14:16
  • 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? – ganders Jul 19 '17 at 13:56
  • 1
    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? – Kappacake Jul 19 '17 at 18:44
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";
}
Emy Blacksmith
  • 755
  • 1
  • 8
  • 27
0

You can use IViewLocationExpander interface to render view as you want. Please refer this link Working with IViewLocationExpander in mvc for more details

Regards,

Rohit

Community
  • 1
  • 1
XamDev
  • 3,377
  • 12
  • 58
  • 97
0

To have a Multiple Layout in asp.net core is quite easy, follow the steps below:

Step 1: Create the layout inside the "Shared Folder located Under Views" and give it a name with prefix "_" i.e _myLoginLayout.cshtml

Step 2: Specify your layout at the beginning of the page you want to apply it e.g @{ Layout = "~/Views/Shared/_myLoginLayout.cshtml"; ViewData["Title"] = "TestMe";//you can add the title as well... } So when _ViewStart.cshtml is loading Layout for your page..it will read the Layout you specify.

HorlaCode
  • 1
  • 1