Skip to content

Using Custom Attributes on a Node

NightOwl888 edited this page May 14, 2014 · 3 revisions

MvcSiteMapProvider supports adding user-defined data to the nodes. This is useful if you need to make customizations that require extra values to be configured for an individual node.

Default Behavior

By default, when you add a custom attribute, it is automatically added to both the Attributes and RouteValues dictionaries of the node. This is because for MVC the primary usage of a custom attribute is to add a custom route value. When values are added to RouteValues, they also automatically show up in the URL that is generated.

For example, if you were to add "id" and "type" attributes to your node, the result would be that you would have "id" and "type" attributes in both the Attributes and RouteValues properties of the ISiteMapNode instance.

<mvcSiteMapNode title="Home" controller="Home" action="Index">
    <mvcSiteMapNode title="Flashlight with Remote Control" controller="Product" action="Details" id="45" type="camping-supplies"/>
</mvcSiteMapNode>

The "Flashlight with Remote Control" node would therefore have a URL that is resolved with both "id" and "type" parameters in it. Exactly how the URL gets resolved is controlled by your ASP.NET route configuration. You can change the ASP.NET route to get a different result.

If you use the above example with the default MVC route, you will get a URL generated as "/Product/Details/45?type=camping-supplies".

Ignoring RouteValues

You can specify to exclude adding the attribute to the RouteValues dictionary so it doesn't affect the generated URL. This allows you to specify attributes that can be used for other purposes, such as providing additional data for use in an HTML helper template.

To exclude the attribute "type" in our example above example, you can add the name of the attribute to the "MvcSiteMapProvider_AttributesToIgnore" appSettings value in the web.config file.

<appSettings>
    <add key="MvcSiteMapProvider_AttributesToIgnore" value="type" />
</appSettings>

If using an external DI container, this setting can be found on the constructor of the ReservedAttributeNameProvider in a parameter named "attributesToIgnore", which is type IEnumerable<string>.

Now when the URL is generated, it will no longer include the "type" attribute: "/Product/Details/45".

MvcSiteMapNodeAttribute Considerations

With MvcSiteMapNodeAttribute, adding custom attributes works exactly the same way. However, due to data type restrictions of .NET attributes, the MvcSiteMapNodeAttribute.Attributes property must be declared as a JSON string.

[MvcSiteMapNode(Title = "Flashlight with Remote Control", Attributes = @"{ ""id"": ""45"", ""type"": ""camping-supplies"" }")]

Making Use of a Custom Attribute

Whether or not the "type" parameter is added to the "AttributesToIgnore" setting, it is made available in the Attributes property of the node for use in your views and controller actions. For example, we can use the "type" attribute we set previously to set the CSS class of our node in a custom HTML helper template.

@model MvcSiteMapProvider.Web.Html.Models.SiteMapNodeModel
@using System.Web.Mvc.Html
@using MvcSiteMapProvider.Web.Html.Models

@if (Model.IsCurrentNode && Model.SourceMetadata["HtmlHelper"].ToString() != "MvcSiteMapProvider.Web.Html.MenuHelper")  { 
    <text>@Model.Title</text>
} else if (Model.IsClickable) { 
    if (string.IsNullOrEmpty(Model.Description))
    {
        if (!Model.Attributes.ContainsKey("type"))
        {
            <a href="@Model.Url">@Model.Title</a>
        }
        else
        {
            <a href="@Model.Url" class="@Model.Attributes["type"].ToString()">@Model.Title</a>
        }
    }
    else
    {
        if (!Model.Attributes.ContainsKey("type"))
        {
            <a href="@Model.Url" title="@Model.Description">@Model.Title</a>
        }
        else
        {
            <a href="@Model.Url" title="@Model.Description" class="@Model.Attributes["type"].ToString()">@Model.Title</a>
        }
    }
} else { 
    <text>@Model.Title</text>
}

Caching Behavior

When a custom attribute is added via XML, MvcSiteMapNodeAttribute, Dynamic Node Provider, or ISiteMapNodeProvider, it is added to the shared cache that is available for every HTTP request for any user.

However, when a custom attribute is set using the static MvcSiteMapProvider.SiteMaps class, the value that is set will exist for the current HTTP request only. This can be useful if you have user-specific data and you want to override or set the value for the current request only. This will not affect any other user on the web site.

MvcSiteMapProvider.SiteMaps.Current.CurrentNode.Attributes["type"] = "special-equipment";

In addition to setting values for the current request, you can also delete a value for the current request and it will automatically be restored to the state of the shared cache when the request is finished processing.

MvcSiteMapProvider.SiteMaps.Current.CurrentNode.Attributes.Remove("type");

Custom Objects

Since the datatype of the Attributes dictionary is IDictionary<string, object>, you can use it to attach your own custom objects to any node. This makes it possible to extend the SiteMapNode object to meet many custom requirements.

public class MyCustomData
{
    public string MyName { get; set; }
    public string MyValue { get; set; }
}

MvcSiteMapProvider.SiteMaps.Current.CurrentNode.Attributes.Add("someCustomData", new MyCustomData { MyName = "Some Name", MyValue = "Some Value" });

Note that care must be taken if you decide to add your own objects from Dynamic Node Provider or ISiteMapNodeProvider that you do not change the values in your objects (unless it is intended and you do your own thread locking) because the change will affect every user on the web site. A good practice to follow is to make your properties only settable from the constructor of your custom object so they cannot be changed accidentally.

public class MyCustomData
{
    public MyCustomData(string myName, string myValue)
    {
        this.MyName = myName;
        this.MyValue = myValue;
    }

    public string MyName { get; private set; }
    public string MyValue { get; private set; }
}

public class ProductDynamicNodeProvider 
    : DynamicNodeProviderBase 
{ 
    public override IEnumerable<DynamicNode> GetDynamicNodeCollection(ISiteMapNode node) 
    { 
        using (var db = new MyEntities())
        {
            // Create a node for each product 
            foreach (var product in db.Products) 
            { 
                var dynamicNode = new DynamicNode(); 
                dynamicNode.Title = product.Name; 
                dynamicNode.ParentKey = "Category_" + product.CategoryId; 
                dynamicNode.RouteValues.Add("id", product.Id);
                dynamicNode.Attributes.Add("someCustomData", new MyCustomData(product.MyName, product.MyValue));

                yield return node;
            }
        }
    } 
}

Want to contribute? See our Contributing to MvcSiteMapProvider guide.



Version 3.x Documentation


Unofficial Documentation and Resources

Other places around the web have some documentation that is helpful for getting started and finding answers that are not found here.

Tutorials and Demos

Version 4.x
Version 3.x

Forums and Q & A Sites

Other Blog Posts

Clone this wiki locally