I have implemented a multi-tenant view engine similar to what is described here:
Which let me override the search locations for view like this:
MasterLocationFormats = new[]
{
"~/Views/%1/{1}/{0}.cshtml",
"~/Views/%1/Shared/{0}.cshtml",
"~/Views/Default/{1}/{0}.cshtml",
"~/Views/Default/Shared/{0}.cshtml",
};
In which the %1
is replaced with the correct folder for the active tenant. This is working just fine exception one problem. When I define the Layout path on my view like this:
Layout = "~/Views/Default/Shared/_MyLyout.cshtml";
It kind of defeats the purpose of having the multi-tenancy since I have have to hard code the exact location of the layout page. I want to be able to do something like this:
Layout = "~/Views/%1/Shared/_MyLyout.cshtml";
If I wanted to allow tenants to have their one layout pages, how would I go about supporting this?
I have tried fiddling with the view engine methods that I overrode:
But nothing seems to point itself towards being able to dynamically specify the layout page.
Update:
Here's what I have working so far. I used the answer to this question https://stackoverflow.com/a/9288455/292578 slightly modified to create a HTML helper:
public static string GetLayoutPageForTenant( this HtmlHelper html, string LayoutPageName )
{
var layoutLocationFormats = new[]
{
"~/Views/{2}/{1}/{0}.cshtml",
"~/Views/{2}/Shared/{0}.cshtml",
"~/Views/Default/{1}/{0}.cshtml",
"~/Views/Default/Shared/{0}.cshtml",
};
var controller = html.ViewContext.Controller as MultiTenantController;
if( controller != null )
{
var tenantName = controller.GetTenantSchema();
var controllerName = html.ViewContext.RouteData.Values["Controller"].ToString();
foreach( var item in layoutLocationFormats )
{
var resolveLayoutUrl = string.Format( item, LayoutPageName, controllerName, tenantName );
var fullLayoutPath = HostingEnvironment.IsHosted ? HostingEnvironment.MapPath( resolveLayoutUrl ) : System.IO.Path.GetFullPath( resolveLayoutUrl );
if( File.Exists( fullLayoutPath ) ) return resolveLayoutUrl;
}
}
throw new Exception( "Page not found." );
}
which is similar to what saravanan suggested. Then I can set the layout in my view with this code:
Layout = Html.GetLayoutPageForTenant( "_Home" );
Unfortunately, this duplicates the work that the custom view engine is doing which seems like the wrong way to go.
I would like to propose the following idea,
In the _ViewStart.cshtml
file, where we set the layout pages, you can use something like this, with the idea of the Tenant based layout url or the folder name is being filled in the controller by fetching from the DB.
@{
Layout = ViewBag.TenantLayoutPageUrl;
}
or
@{
Layout = string.Format("~/Views/{0}/Shared/_MyLyout.cshtml",ViewBag.TenantId);
}
If you have some static Tenant data representations, like a static Identity
class that will keep track of your tenant's customization, we can use that and minimize the round trip to the db.
Please share your idea on this implementation so it will be useful for the community
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With