根据用户角色将XML站点地图加载到MvcSiteMapProvider中

本文关键字:加载 地图 MvcSiteMapProvider 站点 XML 用户 角色 | 更新日期: 2023-09-27 18:27:43

我已经安装了MvcSiteMapProvider的v4,现在我想动态加载站点地图。我的需求很简单——根据当前登录的用户角色,例如AdminSiteMap.XML和UserSiteMap.XML ,在每个页面请求上加载一个XML站点地图

看来这是可以做到的:

  • 使用asp.net MVCSiteMapProvider v4和2个站点地图
  • https://github.com/maartenba/MvcSiteMapProvider/wiki/Multiple-Sitemaps-in-One-Application

因此,基本上您需要使用DI来实现这一点(过度杀伤IMHO)。有没有可能在没有DI的情况下完成

所以当我使用ASP Boilerplate时(http://www.aspnetboilerplate.com/)我的DI是温莎城堡。

所以我通过NuGet安装了"MvcSiteMapProvider MVC5 Windsor依赖注入配置"。然而,现在当我运行应用程序时,我得到以下错误:

SiteMapLoader尚未初始化。

Check the 'MvcSiteMapProvider_UseExternalDIContainer' setting in the AppSettings section of web.config.
If the setting is set to 'false', you will need to call the MvcSiteMapProvider.DI.Composer.Compose() method at the end of Application_Start in the Global.asax file. Alternatively, if you are using .NET 4.0 or higher you can install the MvcSiteMapProvider.MVCx NuGet package corresponding to your MVC version.
If the setting is set to 'true', you must set the SiteMaps.Loader property during Application_Start in Global.asax to an instance of the built-in SiteMapLoader type or a custom ISiteMapLoader instance. This can be achieved most easily by using your external DI container.

我没有更改默认配置,并且已经确认Install()方法在公共类MvcSiteMapProviderInstaller:IWindsorInstaller中被调用,因为它在那里遇到了断点。

那么,我在这里错过了什么来完成这项工作呢。请记住,我所要做的就是根据每个请求的登录用户加载SiteMap。

****更新***

虽然它可能并不优雅,但它不需要实现DI容器所建议的大量代码。请参阅viggity在@Using Multiple MvcSiteMaps 上的回答(关于第四个问题)

根据用户角色将XML站点地图加载到MvcSiteMapProvider中

首先,每个用户1个SiteMap是可能的,但不会很好地扩展-事实上,它几乎违背了制作站点地图的目的。我不推荐这种方法,除非你确信你的网站不会有超过几十个同时使用的用户,你的网站上只有不到几百个页面,并且你的服务器上有大量的额外内存。

有更多可扩展的选项,可以根据登录的用户使节点可见/不可见。

  • 使用安全修剪。启用后,只需使用AuthorizeAttribute正确配置MVC安全性即可自动工作。AuthorizeAttribute完全支持角色。如果需要,还可以继承AuthorizeAttribute来添加自定义安全逻辑
  • 使用自定义可见性提供程序可以根据自定义条件控制每个节点是可见还是不可见
  • 自定义内置的HTML帮助程序(通过更改/Views/Shared/DisplayTemplates/文件夹中的模板)或构建自定义HTML帮助程序,以便除了SiteMap实例中的链接外,还为每个用户动态加载每个请求的链接

这些方法都不需要外部DI。

建议的方法是将每个用户都可能访问的所有节点加载到SiteMap中,然后使用安全修剪使当前用户的节点在UI上不可见。当数据更改时,可以使用SiteMapCacheReleaseAttribute使动态节点在添加到数据源后立即在UI上可见,从而强制重新加载缓存。


有了这些知识,如果你仍然想沿着你目前的路径前进,那么你已经安装了错误的NuGet包。依赖项注入的工作方式是,在项目中只需要一个组合根(即WindsorContainer的一个实例)。由于您的项目中已经有一个composition root,因此必须为Windsor安装MvcSiteMapProvider模块专用包,然后通过添加几行代码手动将模块添加到Windsor配置中。通过在package Manager控制台中运行以下命令,您可以降级到仅模块的软件包:

PM> Uninstall-Package MvcSiteMapProvider.MVC5.DI.Windsor

然后,搜索项目中声明new WindsorContainer()的位置,并将MvcSiteMapProvider模块添加到DI配置中。

// Create the DI container (typically part of your DI setup already)
var container = new WindsorContainer();

// Your existing DI configuration should typically be here...
// Setup configuration of DI
container.Install(new MvcSiteMapProviderInstaller()); // Required
container.Install(new MvcInstaller()); // Required by MVC. Typically already part of your setup (double check the contents of the module).
// Setup global sitemap loader (required)
MvcSiteMapProvider.SiteMaps.Loader = container.Resolve<ISiteMapLoader>();
// Check all configured .sitemap files to ensure they follow the XSD for MvcSiteMapProvider (optional)
var validator = container.Resolve<ISiteMapXmlValidator>();
validator.ValidateXml(HostingEnvironment.MapPath("~/Mvc.sitemap"));
// Register the Sitemaps routes for search engines (optional)
XmlSiteMapController.RegisterRoutes(RouteTable.Routes);

如果您确保WindsorConntainer项目范围内只有一个实例,并根据需要添加上面的代码,那么您应该有一个可工作的DI配置。

要为每个用户加载1个SiteMap,您需要制作一个自定义的ISiteMapCacheKeyGenerator,为每个用户返回不同的字符串。

public class UserSiteMapCacheKeyGenerator
    : ISiteMapCacheKeyGenerator
{
    public virtual string GenerateKey()
    {
        var context = HttpContext.Current;
        if (context.User.Identity.IsAuthenticated)
        {
            // Note: the way you retrieve the user name depends on whether you are using 
            // Windows or Forms authentication
            return context.User.Identity.Name;
        }
        else
        {
            return "default";
        }
    }
}

并通过在/DI/Windsor/Installers/MvcSiteMapProviderInstaller.cs上编辑Windsor模块来注入它。

var excludeTypes = new Type[] { 
    // Use this array to add types you wish to explicitly exclude from convention-based  
    // auto-registration. By default all types that either match I[TypeName] = [TypeName] or 
    // I[TypeName] = [TypeName]Adapter will be automatically wired up as long as they don't 
    // have the [ExcludeFromAutoRegistrationAttribute].
    //
    // If you want to override a type that follows the convention, you should add the name 
    // of either the implementation name or the interface that it inherits to this list and 
    // add your manual registration code below. This will prevent duplicate registrations 
    // of the types from occurring. 
    // Example:
    // typeof(SiteMap),
    // typeof(SiteMapNodeVisibilityProviderStrategy)
    typeof(SiteMapNodeUrlResolver),
    typeof(ISiteMapCacheKeyGenerator) // <-- add this line
};
// Code omitted here...

// Add this to the bottom of the module
container.Register(Component.For<ISiteMapCacheKeyGenerator>().ImplementedBy<UserSiteMapCacheKeyGenerator>();

剩下的就是使用动态节点提供程序或ISiteMapNodeProvider的实现来为每个用户动态提供节点。如果按上述方式设置,则可以通过SiteMap.CacheKey属性获取用户名。

public class SomeDynamicNodeProvider : DynamicNodeProviderBase
{
    public override IEnumerable<DynamicNode> GetDynamicNodeCollection(ISiteMapNode node)
    {
        // Get the user name
        var user = node.SiteMap.CacheKey;
        // Entities would be your entity framework context class
        // or repository.
        using (var entities = new Entities())
        {
            // Add the nodes for the current user only
            foreach (var story in entities.Stories.Where(x => x.User == user)
            {
                DynamicNode dynamicNode = new DynamicNode();
                dynamicNode.Title = story.Title;
                // The key of the node that this node will be the child of.
                // This works best if you explicitly set the key property/attribute 
                // of the parent node.
                dynamicNode.ParentKey = "Home"; 
                dynamicNode.Key = "Story_" + story.Id;
                dynamicNode.Controller = "Story";
                dynamicNode.Action = "Details";
                // Add the "id" (or any other custom route values)
                dynamicNode.RouteValues.Add("id", story.Id);
                yield return dynamicNode;
                // If you have child nodes to the current node, you can
                // nest them here by setting their ParentKey property to 
                // the same value as the dynamicNode.Key and returning them
                // using yield return.
            }
        }
    }
}

最后,将"模板"节点添加到配置中以加载动态节点。

// Set a key explicitly to attach the dynamic nodes to. 
// The key property here corresponds to the ParentKey property of the dynamic node.
<mvcSiteMapNode title="Home" controller="Home" action="Index" key="Home">
    // Use a "dummy" node for each dynamic node provider. This node won't be in the SiteMap.
    <mvcSiteMapNode dynamicNodeProvider="NamespaceName.SomeDynamicNodeProivder, AssemblyName"/>
</mvcSiteMapNode>