Upload XML sitemap to MvcSiteMapProvider based on user role

I installed v4 from MvcSiteMapProvider and now I want to dynamically load the Sitemap. My needs are simple - upload an XML sitemap for each request to the page, based on the user's current role, for example. AdminSiteMap.xml and UserSiteMap.xml

It looks like this can be done:

So basically you need to use DI to achieve this ( overkill IMHO ). Any chance this can be done without DI?

Since I use ASP Boilerplate ( http://www.aspnetboilerplate.com/ ), I have Castle Windsor as my DI.

So, I installed the "MvcSiteMapProvider MVC5 Windsor Dependency Injection Configuration" through NuGet. However, now when I run the application, I get the following error:

SiteMapLoader was not initialized.

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. 

I did not change the default configuration and confirmed that the Install () method is called in the public class MvcSiteMapProviderInstaller: IWindsorInstaller when it hits the breakpoint.

So what I am missing here is to make this work. Remember that all I'm trying to do is download SiteMap based on the registered user for each request.

**** UPDATE ****

Although it may not be elegant, it does not require the huge amount of code suggested by implementing the DI container. See the answer about wiggit (about 4th down) at @ Using multiple MvcSiteMaps

+2
source share
1 answer

First of all, 1 SiteMap for each user is possible, but it will not scale very well - in fact, it largely defeats the goal of creating a site map. I would not recommend this approach if you are not sure that your site will not have more than several tens of simultaneous users, you will have less than several hundred pages on the site, and you have additional resources on the server.

There are more scalable options to make the nodes visible / invisible according to which the user is logged in.

  • Use Security Crop . When it is turned on, it automatically works only by properly setting up MVC protection using AuthorizeAttribute . AuthorizeAttribute has full role support. You can also inherit AuthorizeAttribute to add custom security logic if you need to.
  • Use custom visibility providers to control whether each node is visible or invisible according to user criteria.
  • Set up embedded HTML helpers (by changing templates in the /Views/Shared/DisplayTemplates/ ) or create custom HTML helpers to dynamically load links per request for each user, in addition to links from a SiteMap instance.

None of these approaches require an external DI.

The recommended approach is to load all the nodes in SiteMap that every user can get, and then use security trimming to make the nodes for the current user invisible in the user interface. You can force the cache to reload when data changes using SiteMapCacheReleaseAttribute to make the dynamic nodes displayed in the user interface immediately after adding them to the data source.


With this in mind, if you still want to follow the path you are currently in, you have installed the wrong NuGet package. The way to load dependencies is that you need exactly 1 root composition in your project (i.e. one instance of WindsorContainer). Since you already have the composition root in your project, you only need to install the MvcSiteMapProvider module module for Windsor, and then manually add the module to the Windsor configuration by adding a few lines of code. You can upgrade to a package of modules only by running this command in the package manager console:

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

Then search where new WindsorContainer() declared in your project, and add the MvcSiteMapProvider module to the DI configuration.

 // 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); 

If you make sure that there is only one instance of WindsorConntainer for the entire project and add the above code, you must have a working DI configuration.

To load 1 SiteMap for each user, you need to create your own ISiteMapCacheKeyGenerator, which returns a different row for each user.

 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"; } } } 

And add it by editing the Windsor module in /DI/Windsor/Installers/MvcSiteMapProviderInstaller.cs .

 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>(); 

It remains only to use dynamic node providers or implementations of ISiteMapNodeProvider to dynamically provide nodes to the user. If you configured it above, you can get the username through the SiteMap.CacheKey property.

 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. } } } } 

And finally, add your β€œtemplate” nodes to your configuration to load dynamic nodes.

 // 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> 
+5
source

Source: https://habr.com/ru/post/958658/


All Articles