1. Original Entry + Comments2. Write a Comment3. Preview Comment
New comments for this entry are disabled.


May 26, 2011  |  Constructing site URLs in ASP.NET Razor  |  106899 hit(s)

The other day I showed how to send a confirmation email when people register on your site. In the example, I created a link that points to a confirmation page on my site, and I hard-coded the URL of that link. This isn't such a great idea; it makes the code non-portable should you decide to use it in a different site. If you only have one place in your site where you're constructing a link, maybe that isn't a huge problem, but in general, hard-coding things like your site's address will end up biting you. (Ask me how I know.)

There are a couple of ways to get around this.

Hard-coding in a central location


One approach is to go ahead and hard-code the site's URL, but to do it in only one central place where you can easily change it if you want to. (Semi-hard-coding it?) In ASP.NET Web Pages applications, you can do this in the _AppStart.cshtml (or _AppStart.vbhtml) file using the AppState object. This object just stores information that you want to have available anywhere in the site. This would be a typical way to store something global like the site's URL:

@{
WebSecurity.InitializeDatabaseConnection("TestMembership",
"UserProfile",
"UserId",
"Email",
true);

WebMail.SmtpServer = "mySMTPServer";
WebMail.EnableSsl = true;
WebMail.UserName = "myUserName";
WebMail.Password = "myPassword";
WebMail.From = "mike@mikepope.com";

AppState["siteURL"] = "http://www.mikepope.com/TestMembership/";
}
The name of the key you use for the data in AppState (here, "siteURL") is arbitrary, as long as it's unique in the site. When you want to use the data, you can do something like the following — this is an updated version of the code from the example for creating the confirmation link:

var confirmationUrl = AppState["siteURL"] 
+ "ConfirmAccount?confirmation="
+ HttpUtility.UrlEncode(confirmationToken);

It might occur to you that you could also store the name of the confirmation page (here, ConfirmAccount) in the AppState object in _AppStart.cshtml. You bet — anything that a) you use in more than one place and/or that b) might not be the same in every site. In fact, in _AppStart.cshtml you might go to this extreme:

@{
// Other stuff here ...

AppState["siteURL"] = "http://www.mikepope.com/TestMembership/";
AppState["confirmationPageName"] = "ConfirmAccount";
AppState["confirmationQueryStringVariableName"] = "confirmation";
}

Then in page code, you could to this:

var confirmationUrl = AppState["siteURL"].ToString() 
+ AppState["confirmationPageName"].ToString()
+ "?"
+ AppState["confirmationQueryStringVariableName"].ToString()
+ "="
+ HttpUtility.UrlEncode(confirmationToken);

This results in confirmationUrl having the following value (the actual confirmation value would differ each time, of course):

http://www.mikepope.com/TestMembership/ConfirmAccount?confirmation=LR9888HmlCEkiL3%2fGdUY5g%3d%3d

(All those ToString methods, btw, are because data in the AppState object is typed as Object, and you can't concatenate objects to objects; ASP.NET cannot deduce in this case that you are working only with strings.)

Anyway, you get the idea. How fine-grained you want to get in storing the component pieces would be up to you, depending on what you need. For example, would you ever need to translate the term "ConfirmAccount" for your users? Easy to do if it's specified in just the one place in your site. Etc.

I should note for the sake of completeness that in ASP.NET sites of yore — that is, before Web Pages/Razor — the way to do this was to create an application setting in the Web.config file. That's just as possible as ever, but there's been a de-emphasis on using the Web.config file in latter days. If you're interested, tho, have a look at ConfigurationManager.AppSettings Property.

Getting the site information dynamically


An alternative is to get the site's URL in code. The Request object has a property named Url that gets the current page's URL. The Url property is of type Uri, a class that has many, many ways to slice and dice URLs. In fact, it has so many that it's not necessarily obvious how to just get the current site's URL. A couple of examples just to give you a flavor (here, the = sign means "returns as an example"):

Request.Url.AbsoluteUri = http://localhost:62263/TestMembership/UrlStuff.cshtml
Request.Url.DnsSafeHost = localhost
Request.Url.LocalPath = /TestMembership/UrlStuff.cshtml
Request.Url.Port = 62263
Request.Url.Scheme = http

To get the domain (and port, if applicable), here's a way that I learned from some code in the Starter Site template:

Request.Url.GetComponents(UriComponents.SchemeAndServer, 
UriFormat.Unescaped)

(You see what I mean about not being obvious.) If you're running locally in WebMatrix (ie, using IIS Express), the preceding expression returns this:

http://localhost:62263

Notice that this returns the port number if there is one, how clever.

Remember that what you want is the complete URL that includes the domain, site name, the path to the confirmation page, and (pant-pant) the confirmation token:

http://www.mikepope.com/TestMembership/ConfirmAccount?confirmation=LR9888HmlCEkiL3%2fGdUY5g%3d%3d

To achieve the objective here (create the URL for a confirmation link), you might do the following:

var siteUrl = Request.Url.GetComponents(UriComponents.SchemeAndServer, 
UriFormat.Unescaped);
var confirmationUrl = sitetUrl
+ VirtualPathUtility.ToAbsolute("~/ConfirmAccount?confirmation="
+ HttpUtility.UrlEncode(confirmationToken));

The first line is the bit I showed just a second ago that gets the domain. The second line combines that URL with the (partially hard-coded) name of the confirmation page. The secret sauce here is the VirtualPathUtility.ToAbsolute method. That takes a relative path and returns a path starts at the site root (this is known as the virtual path, in case that's not clear); you can then whack that onto the domain. For example, this expression:

VirtualPathUtility.ToAbsolute("~/Account/Confirm")

Returns this:

/TestMembership/Account/Confirm

The ~ operator is ASP.NET-talk for "current site root". Notice the leading slash.

The rest of the URL is put together the same as before. For maximum flexibility, you could do the same trick you saw above of putting the name of the confirmation page and query-string variable even into _AppStart.cshtml — that is, using AppState to store the names in ConfirmAccount?confirmation and so on.

Further reading:

Adding Website Startup Code (_AppStart files)
ASP.NET Web Site Paths
Working with File and Folder Paths in Code
Request.Url property
Uri class