URL generation in ASP.NET MVC without a HttpContext

A peculiarity of URL generation in ASP.NET MVC that often bugged me is that you cannot generate URLs outside of an ASP.NET HttpContext. All built in URL generation classes (HtmlHelper, UrlHelper, LinkBuilder etc.) will throw an exception if called without the right parameters or when HttpContext.Current is null.

ASP.NET MVC

This is only because these classes need to know the path of the ASP.NET application they are running in, in order to generate correct relative URLs. For example, a typical website URL can have this form:

http://www.example.com/path/to/application/route/page?parameter=value

In this case our ASP.NET application is hosted under the /path/to/application path. All requests to paths under that application will be forwarded to our app instance and then handled by the ASP.NET routing module. In order to generate links to another page, you can use the full absolute path:

http://www.example.com/path/to/application/route2

or a relative one:

../route2

or, as MVC always does, links that start at the root folder of the host:

/path/to/application/route2

Thus the problem lies in the fact that the host name and the application path can only be found in the current HttpRequest instance. As long as MVC continues to generate URLs in that form, we'll have to manually supply the correct application path in some way when we try to use the URL builders outside of a HttpContext (this is mostly the case when you generate e-mails on a background thread or similar scenarios).

An easy way to do it, albeit not always correct of course, is to save the application path during the first HTTP request to the website and store it either in-memory or to the database. Then, when you need to generate an URL you simply retrieve the original value and use it.

Store the application path

The app path can be read simply by getting the string value directly from the HttpRequest instance:

string appPath = HttpContext.Current.Request.ApplicationPath;

You can put this code in your HttpApplication code, which usually runs inside the first HTTP request to the website. However, this will not be always the case in .NET 4.0, which actually supports applications that "warm up" and start without a request. In that case your best bet is to register a specific HTTP module that ensures that the application path is stored correctly after the first request.

Generate URLs

Before being able to use our stored application path, you'll need some ugly code that fakes a real HttpContext using a provided path:

public class FakeHttpContext : HttpContextBase {
    public FakeHttpContext(string applicationPath) {
        _req = new FakeHttpRequest(applicationPath);
    }
    
    HttpRequestBase _req;
    public override HttpRequestBase Request {
        get {
            return _req;
        }
    }

    HttpResponseBase _resp = new FakeHttpResponse();
    public override HttpResponseBase Response {
        get {
            return _resp;
        }
    }

    class FakeHttpRequest : HttpRequestBase {
        public FakeHttpRequest(string appPath) {
            if (appPath == null)
                throw new ArgumentNullException();

            _appPath = appPath;
        }

        string _appPath;
        public override string ApplicationPath {
            get {
                return _appPath;
            }
        }
    }

    class FakeHttpResponse : HttpResponseBase {
        public override string ApplyAppPathModifier(string virtualPath) {
            return virtualPath;
        }
    }
}

Then we'll use the Microsoft.Web.Mvc.LinkBuilder class to generate URLs from lambda expressions to our controllers. You should be able to use similar code when using other classes to generate URLs however.

public class UrlWriter {
    protected RequestContext Context {
        get {
            if (HttpContext.Current == null) {
                //Build fake HttpContext only if necessary
                var appPath = ...; //get application path from data store
                return new RequestContext(new FakeHttpContext(appPath), new RouteData());
            }

            //Use the real HttpContext
            return new RequestContext(new HttpContextWrapper(HttpContext.Current), new RouteData());
        }
    }
    
    public string Link<TController>(Expression<Action<TController>> action)
        where TController : Controller {
        
        return LinkBuilder.BuildUrlFromExpression<TController>(this.Context, RouteTable.Routes, action);
    }
}

And that's all you need to generate correct links from background threads.

The solution is far from perfect however: if your application starts before you can store the application path (which could be possible in .NET 4.0), you'll end up with a null application path. Make sure to handle that case.

In some cases you might have more than one application path that points to the same ASP.NET application. In that case you'll also need to make sure that the stored path is correct (perhaps in such cases it's easier to just store the path in your web.config).