Using Multiple MvcSiteMaps

2019-01-27 19:29发布

问题:

I've recently hit a road block trying to use the MvcSiteMapProvider.

In my application, I have three distinct areas: Landing, Application and Administration. I currently have implemented the MvcSiteMapProvider and it works amazingly, but what I'm trying to do now - is use the Html MvcSiteMap Helper and specify a different map provider depending on the area that I'm in.

So, when I'm:

  • In the "Admin" area - I want to use the provider named "AdminSiteMapProvider".
  • In the "Application" area - I want to use the provider named "AppSiteMapProvider".

I've tried the following:

Shared -> _AppLayout.cshtml

@Html.Partial("_Menu")

Shared -> _Menu.cshtml

@{
if (HttpContext.Current != null && HttpContext.Current.Handler is System.Web.Mvc.MvcHandler)
{
    var handler = HttpContext.Current.Handler as System.Web.Mvc.MvcHandler;
    var currentArea = handler.RequestContext.RouteData.Values["area"] ?? string.Empty;
    if (!string.IsNullOrEmpty(currentArea.ToString()))
    {
        <text>@Html.MvcSiteMap("AppSiteMapProvider").Menu()</text>
    }
    else if (currentArea.ToString() == "Admin")
    {
        <text>@Html.MvcSiteMap("AdminSiteMapProvider").Menu()</text>    
    }
}    

}

Any suggestions? I don't want to have to copy/paste the _AppLayout.cshtml content into a new master just for one area, I'd rather it select the right provider dynamically.

回答1:

Add something like this in your web.config file (a new provider for each area):

<siteMap defaultProvider="AppSiteMapProvider" enabled="true">
  <providers>
    <clear />
    <add name="AppSiteMapProvider" type="MvcSiteMapProvider.DefaultSiteMapProvider, MvcSiteMapProvider" siteMapFile="~/Mvc.Sitemap" securityTrimmingEnabled="true" cacheDuration="5" enableLocalization="true" scanAssembliesForSiteMapNodes="true" includeAssembliesForScan="" excludeAssembliesForScan="" attributesToIgnore="visibility" nodeKeyGenerator="MvcSiteMapProvider.DefaultNodeKeyGenerator, MvcSiteMapProvider" controllerTypeResolver="MvcSiteMapProvider.DefaultControllerTypeResolver, MvcSiteMapProvider" actionMethodParameterResolver="MvcSiteMapProvider.DefaultActionMethodParameterResolver, MvcSiteMapProvider" aclModule="MvcSiteMapProvider.DefaultAclModule, MvcSiteMapProvider" siteMapNodeUrlResolver="MvcSiteMapProvider.DefaultSiteMapNodeUrlResolver, MvcSiteMapProvider" siteMapNodeVisibilityProvider="MvcSiteMapProvider.DefaultSiteMapNodeVisibilityProvider, MvcSiteMapProvider" siteMapProviderEventHandler="MvcSiteMapProvider.DefaultSiteMapProviderEventHandler, MvcSiteMapProvider" />
    <add name="AdminSiteMapProvider" type="MvcSiteMapProvider.DefaultSiteMapProvider, MvcSiteMapProvider" siteMapFile="~/Areas/Admin/Mvc.Sitemap" securityTrimmingEnabled="true" cacheDuration="5" enableLocalization="true" scanAssembliesForSiteMapNodes="true" includeAssembliesForScan="" excludeAssembliesForScan="" attributesToIgnore="visibility" nodeKeyGenerator="MvcSiteMapProvider.DefaultNodeKeyGenerator, MvcSiteMapProvider" controllerTypeResolver="MvcSiteMapProvider.DefaultControllerTypeResolver, MvcSiteMapProvider" actionMethodParameterResolver="MvcSiteMapProvider.DefaultActionMethodParameterResolver, MvcSiteMapProvider" aclModule="MvcSiteMapProvider.DefaultAclModule, MvcSiteMapProvider" siteMapNodeUrlResolver="MvcSiteMapProvider.DefaultSiteMapNodeUrlResolver, MvcSiteMapProvider" siteMapNodeVisibilityProvider="MvcSiteMapProvider.DefaultSiteMapNodeVisibilityProvider, MvcSiteMapProvider" siteMapProviderEventHandler="MvcSiteMapProvider.DefaultSiteMapProviderEventHandler, MvcSiteMapProvider" />
  </providers>
</siteMap>

Put this in your common masterpage (same thing for the menu):

var currentArea = (string)ViewContext.RouteData.DataTokens["area"];
if (string.IsNullOrWhiteSpace(currentArea))
{
    <text>@Html.MvcSiteMap("AppSiteMapProvider").SiteMapTitle()</text>
}
else if (currentArea.ToString() == "Admin")
{
    <text>@Html.MvcSiteMap("AdminSiteMapProvider").SiteMapTitle()</text>
}

And finaly create a sitemap file for each area.

It works for me. Hope it helps.



回答2:

Multiple Sitemaps in One Application explains how this is done in v4, which has changed quite a bit from the accepted answer - which was for v3 and prior.

The primary difference is that now multiple sitemaps are configured with DI, and you need to implement ISiteMapCacheKeyGenerator and/or ISiteMapCacheKeyToBuilderSetMapper, which are small classes to tell MvcSiteMapProvider how to map the incoming HTTP requests to each sitemap.



回答3:

I'm using version 4, for which the named providers apparently doesn't work. The prescribed way to have multiple sitemaps in v4 frankly scared the bejeebus out of me and was way more work than I wanted.

Per the @NightOwl888's suggestion comment on his own answer, I used the named helpers option in v4. I still only have one mvc.sitemap file, but I have mutually exclusive visibility options.

Step 1: add this setting to in web.config

<add key="MvcSiteMapProvider_DefaultSiteMapNodeVisibiltyProvider" value="MvcSiteMapProvider.FilteredSiteMapNodeVisibilityProvider, MvcSiteMapProvider"/>

Step 2: pick the names of your different "menus" and apply them to the visibility attribute on each node. In my case I had "Regular" and "Admin". Again, all of these are in the same mvc.sitemap file.

<mvcSiteMapNode title="Reports" controller="Report" action="List" visibility="Regular,!*"/>
<mvcSiteMapNode title="Downloads" controller="Download" action="List" visibility="Regular,!*"/>
<mvcSiteMapNode title="Documents" controller="Document" action="List" visibility="Regular,!*"/>

<mvcSiteMapNode title="Users" controller="User" action="List" visibility="Admin,!*"/>
<mvcSiteMapNode title="Projects" controller="Project" action="List" visibility="Admin,!*"/>
<mvcSiteMapNode title="Misc" clickable="false" visibility="Admin,!*">
    <mvcSiteMapNode title="Reports" controller="Report" action="List" visibility="Admin,!*"/>
    <mvcSiteMapNode title="Downloads" controller="Download" action="List" visibility="Admin,!*"/>
    <mvcSiteMapNode title="Documents" controller="Document" action="List" visibility="Admin,!*"/>
</mvcSiteMapNode>

You'll note, that the Reports, Downloads and Document links are available to both regular users and admin users, but since admin rarely uses these options, I wanted to put them in the Misc submenu.

Step 3: in your _Layout.cshtml, decide which menu you want to display.

@if(User.IsInRole("Admin"))
{
    @Html.MvcSiteMap().Menu("BootstrapMenuHelperModel", false, new { name = "Admin" })
}
else
{
    @Html.MvcSiteMap().Menu("BootstrapMenuHelperModel", false, new { name = "Regular" })
}

I used this bootstrap/sitemap tutorial, if you aren't I think you can just call @Html.MvcSiteMap().Menu(new { name = "MENUNAME" })



回答4:

This is what I ended up doing (based on this question). it's a little simpler, hope it helps. I just named my site map provider after the area, or "default".

<ul id="menu">
@{
    // gets a different sitemap for each area (or the default one)
    var _siteMap = ViewContext.RouteData.DataTokens["area"] 
                        as string ?? "Default";
    var sm = Html.MvcSiteMap(_siteMap);
    @sm.Menu(sm.Provider.RootNode, true, true, 2);
}
</ul>