Skip to content

Controlling URL Behavior

NightOwl888 edited this page Mar 13, 2014 · 2 revisions

MvcSiteMapProvider has several options for configuring URLs.

Resolving URLs

MvcSiteMapProvider has 2 ways of creating URLs:

  1. Using .NET Routing
  2. Setting URLs Explicitly

Both of these methods both generate the URLs and provide the framework to match them against the current request, as described in Routing Basics.

Using .NET Routing

To use .NET routing, set the "controller", "action", and optionally the "area" and/or "route" attributes/properties of the node, along with any custom RouteValue information. This will cause the default SiteMapNodeUrlResolver class to call into the MVC framework's UrlHelper class to resolve the URL.

Below is some pseudo-code that mimics what the SiteMapNodeUrlResolver does. You can use this to troubleshoot your URL generation code, if needed.

// Using controller and action
var urlHelper = new UrlHelper(new System.Web.Routing.RequestContext(this.HttpContext, this.RouteData));
var url = urlHelper.Action("View", "Videos", new System.Web.Routing.RouteValueDictionary { { "id", 123 } });

// Using controller, action, and route
var urlHelper = new UrlHelper(new System.Web.Routing.RequestContext(this.HttpContext, this.RouteData));
var url = urlHelper.RouteUrl("RouteName", new System.Web.Routing.RouteValueDictionary { { "controller", "Videos" }, { "action", "View" }, { "id", 123 } });

Setting URLs Explicitly

To set a URL explicitly, you can use the "url" attribute/property of the node. The URL property supports absolute URLs, root relative URLs (beginning with /), and virtual application relative URLs (beginning with ~/).

// Absolute URL
dynamicNode.Url = "http://www.somewhere.com:123/some-application/some-page?a=b";

// Root Relative URL
dynamicNode.Url = "/some-application/some-page?a=b";

// Virtual Application Relative URL
dynamicNode.Url = "~/some-page?a=b";

Using Protocol and HostName Properties to make Absolute URLs

Clearly, hard-coding the host name is not the most maintainable option. You can also force a URL to be absolute by setting the "protocol" attribute/property of the node. This will work whether using routing or explicit URLs.

<!-- Generates "https://www.sitenamefromrequest.com/Account/Index" -->
<mvcSiteMapNode title="Account Home" controller="Account" action="Index" protocol="https"/>

You can also use the protocol of the current request by specifying "*". This can come in handy if you want to ensure that once the user enters an SSL session, they must stay in the SSL session for the remainder of their visit.

<mvcSiteMapNode title="Home" controller="Home" action="Index" protocol="*">
    <mvcSiteMapNode title="Account Home" controller="Account" action="Index" protocol="https"/>
</mvcSiteMapNode>

In the above example, if the user first visits the home page, then clicks the "Account Home" link, the Account/Index page will be requested with https. The home page link will also be created beginning with https, so if the user navigates there next, they will not leave the SSL session.

You can also use the "hostName" attribute/property to make links to other web sites within a multi-tenant application.

<mvcSiteMapNode title="Home" controller="Home" action="About" hostName="www.website1.com">
<mvcSiteMapNode title="Home" controller="Home" action="About" hostName="www.website2.com">

In the above example, both of these links go to the same controller action, but will access it through different host names. If viewing both of these links in the menu, it will select the correct link as the current node even if switching between websites as long as they are both hosted within the current application.

http://www.website1.com/Home/About
http://www.website2.com/Home/About

Using Non-Standard Ports

If using ports for HTTP and HTTPS other than the default ports of 80 and 443, extra configuration may be required in order to get your URLs to resolve to the correct port. This only applies if you are using absolute URLs and you are not using a single protocol throughout your website, but are switching between protocols, as is commonly done between HTTP and HTTPS.

Note: If you use relative URLs, you can use non-standard ports without doing any extra configuration.

IIS Host Header Names

By default, MvcSiteMapProvider will attempt to read the ports from IIS or IIS Express if your site is running on IIS or IIS Express. So, for example if IIS is configured with the following bindings, the port 8080 will be used if the domain name is www.somewhere.com and the protocol is HTTP and the port 4433 will be used if the domain name is www.somewhere.com and the protocol is HTTPS.

Type Host Name Port IP Address
http www.somewhere.com 8080 *
https www.somewhere.com 4433 *

See the documentation at Technet to configure host header names in IIS 7 or see this for IIS Express.

If configured in the table above, the following nodes will resolve their URLs as described (assuming the current host name is www.somewhere.com).

<!-- Generates "http://www.somewhere.com:8080/Account/Index" -->
<mvcSiteMapNode title="Account Home" controller="Account" action="Index" protocol="http"/>

<!-- Generates "https://www.somewhere.com:4433/Account/Index" -->
<mvcSiteMapNode title="Account Home" controller="Account" action="Index" protocol="https"/>

Note that it doesn't really matter if the site is hosted locally, MvcSiteMapProvider will use the bindings in IIS or IIS Express and doesn't make any assumptions about whether or not you are actually hosting them. For example, if www.somewhere.com is hosted somewhere else and you configure your host headers as shown in the above table, the URLs will be created to the external site by adding the host name.

<!-- local site is www.mysite.com -->

<!-- Generates "http://www.mysite.com/" -->
<mvcSiteMapNode title="Home" controller="Home" action="Index" protocol="http"/>

<!-- Generates "http://www.somewhere.com:8080/Account/Index" -->
<mvcSiteMapNode title="Account Home" controller="Account" action="Index" protocol="http" hostName="www.somewhere.com"/>

<!-- Generates "https://www.somewhere.com:4433/Account/Index" -->
<mvcSiteMapNode title="Account Home" controller="Account" action="Index" protocol="https" hostName="www.somewhere.com"/>

IBindingProvider

Note: This technique can only be done using an external DI container.

You can also provide bindings directly in your DI configuration through the IBindingProvider interface. You can implement this interface yourself if you so choose, but for convenience there is a CustomBindingProvider included in the box. This is how you would use it in your DI configuration (StructureMap example shown).

// Exclude the IBindingProvider type from auto-registration.
var excludeTypes = new Type[] { 
	typeof(IBindingProvider)
};

// Configure the bindings. Note that an exact match will be considered before "*".
var bindings = new List<IBinding>() 
{
	new Binding(hostName: "*", protocol: "http", port: 81),
	new Binding(hostName: "*", protocol: "https", port: 444),
	new Binding(hostName: "www.somewhere.com", protocol: "http", port: 8080),
	new Binding(hostName: "www.somewhere.com", protocol: "https", port: 4433),
	new Binding(hostName: "www.somewhere.com", protocol: "ftp", port: 1123)
};

// Inject the CustomBindingProvider, and supply the bindings to its constructor.
this.For<IBindingProvider>().Use<CustomBindingProvider>()
	.Ctor<IEnumerable<IBinding>>().Is(bindings);

The above configuration will use 8080 for HTTP and 4433 for HTTPS on domain www.somewhere.com. All other domains will default to 81 for HTTP and 444 for HTTPS.

Customizing URLs

If you are not satisfied with the default URL scheme, you should configure your routes in MVC to change your URLs rather than trying to extend MvcSiteMapProvider. Doing it this way will ensure that your URLs are consistent in both MVC and MvcSiteMapProvider. MVC allows you to create virtually any URL scheme and for advanced scenarios, you can inherit RouteBase to create your own route implementation.

ImageUrl Property

The ImageUrl property works differently than Url or CanonicalUrl because it builds a content URL instead of a link. What this means is that it is set up to resolve absolute URLs differently. The most convenient way to use it is to set the imageUrlHostName attribute/property to the domain name of your CDN server.

<mvcSiteMapNode title="Home" controller="Home" action="Index" imageUrl="~/Images/my-image.png" imageUrlHostName="cdn.mywebsite.com"/>

When configured like this, it will automatically match the protocol of the current request. So if you access the home page using HTTPS, the imageUrl will resolve to https://cdn.mywebsite.com/Images/my-image.png.

Do note that none of the included HTML templates use the ImageUrl, but it is available should you build a custom HTML template to use it. However, modern techniques more often use a CSS class name to identify an image in which case you should use a custom attribute instead.


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