Question

I have set up a breadcrumb using the MvcSiteMapProvider. I have a structure that has some static nodes and then on some leaves there are dynamic nodes added using the asp.net-attribute.

[MvcSiteMapNode(DynamicNodeProvider = "Web.DealerNet.CommonFramework.UserDynamicNodeProvider)]

That 'works' fine, but when you look under the hood, the performance is really really bad, because on startup all users are loaded into the memory and for every user there is a node created. This is the approach that is suggested in the official docu. Is there a way to use lazy loading here? (load the properties of the user-object when its really called and not creating a node for every user ... i have like 20k users in there....)

public class UserDynamicNodeProvider : DynamicNodeProviderBase
    {
        public override IEnumerable<DynamicNode> GetDynamicNodeCollection(ISiteMapNode node)
        {

            UserDao dao = DependencyResolver.Current.GetService<UserDao>();

            // Create a node for each element 
            foreach (User user in dao.GetAll())
            {   
                DynamicNode dynamicNode = new DynamicNode();
                dynamicNode.Title = user.UserName;
                dynamicNode.Description = user.UserName;
                dynamicNode.RouteValues.Add("id", user.IdUser);
                dynamicNode.ParentKey = "TheParentKey";
                dynamicNode.Clickable = false;
                dynamicNode.Area = "MyArea";

                yield return dynamicNode;
            }

        }

    }
Was it helpful?

Solution

Lazy loading isn't currently supported (although adding request-level nodes is planned for v5).

For the time being, a list of users doesn't sound like something you will need to index in search engines (unless you happen to be building a social networking site). Each user won't need a separate entry in the /sitemap.xml endpoint. Therefore, there is little value in adding a node for every user.

If that is the case, a better approach is to use preservedRouteParameters to force all requests to match a single node and then use visibility providers and SiteMapTitleAttribute to adjust the way the Menu and SiteMapPath HTML helpers appear.

[MvcSiteMapNode(Area="MyArea", PreservedRouteParameters="id", Clickable="False")]

And in your controller action, set the title of the node (or its parent node) to the correct user.

[SiteMapTitle("UserName")] 
public ViewResult Details(int id) { 
   UserDao dao = DependencyResolver.Current.GetService<UserDao>();

   // This example assumes your user model object has a public property "UserName"
   var user = dao.Find(id); 
   return View(user); 
}

This will copy over the id on every request to make it match every request, giving the appearance of a node for every user in the breadcrumb trail. Usually, you will need to use the FilteredSiteMapNodeVisibilityProvider to hide this "ghost" node from the menu - when using this approach there is no way to list all of the users in the menu. If you need a list of all users, I would suggest making a list that loads directly from the database instead.

See some downloadable demos of this on the post How to Make MvcSiteMapProvider Remember a User's Position.

On the other hand, if you are building a social networking site, I would suggest extending the cache so it caches to disk instead of keeping it all in memory. This won't improve the initial startup time, but will keep you from using up all of the server's memory. There is an open source file cache and some instructions on how you could build your implmentation available online. If you are careful about how often you reload the cache, this approach might work for you.

Another issue that could be causing performance issues is the fact you are using DependencyResolver as a service locator (which is anti-pattern). Assuming you are using external DI, injecting your dependencies through the constructor ensures your DI container (rather than the application) is in control over the lifetime of your objects.

public class UserDynamicNodeProvider : DynamicNodeProviderBase
{
    private readonly UserDao userDao;

    public UserDynamicNodeProvider(UserDao userDao)
    {
        if (userDao == null)
            throw new ArgumentNullException("userDao");

        this.userDao = userDao;
    }

    public override IEnumerable<DynamicNode> GetDynamicNodeCollection(ISiteMapNode node)
    {
        // Create a node for each element 
        foreach (User user in this.userDao.GetAll())
        {   
            DynamicNode dynamicNode = new DynamicNode();
            dynamicNode.Title = user.UserName;
            dynamicNode.Description = user.UserName;
            dynamicNode.RouteValues.Add("id", user.IdUser);
            dynamicNode.ParentKey = "TheParentKey";
            dynamicNode.Clickable = false;
            dynamicNode.Area = "MyArea";

            yield return dynamicNode;
        }
    }
}

Microsoft discourages the use of DependencyResolver.Current within your own application code (See Professional ASP.NET MVC 4, Wrox Press, Page 308 - written by Jon Galloway, Phil Haack, Brad Wilson, and K. Scott Allen), and doing so tightly binds your class to the MVC framework.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top