Using the ASP.NET Web API UrlHelper
This article describes how to use the UrlHelper class in WebApi to create URL’s that can be used to invoke ApiController methods. This UrlHelper class uses the application’s route tables to compose the URL, avoiding the need to hard-code specific URL’s into the app.
In our example scenario, we’ll expose these concepts:
- A “Store” class holds multiple “Products”
- A store repository manages multiple stores
- A store controller exposes a Web API to the store repository
- Each store has a unique URL
We need UrlHelper in this scenario because we want to expose a unique URL for each store, allowing clients to do basic create/read/update/delete operations on a single store. We won’t know each store’s address at compile time because they will be created dynamically.
Getting started
The following sample code was built using Visual Studio 2010 and the latest bits of WebApi built each night which you can get here. Or if you prefer, you can use VS 2012 RC and get VS 2012 RC bits here.
We’ll start using File New Project | Web | Mvc4 | WebApi to create a new MVC 4 ASP.Net Web Api project, and they we’ll add to it.
Creating the routes to reach the stores
In this app we need to expose a unique URL for each separate store. The easiest way to do this is to assign a unique ID to each store when it is created and to use that as part of the route information in the store.
When we created the default WebApi project above, the template automatically setup this route for us:
1: public static class WebApiConfig
2: {
3: public static void Register(HttpConfiguration config)
4: {
5: config.Routes.MapHttpRoute(
6: name: "DefaultApi",
7: routeTemplate: "api/{controller}/{id}",
8: defaults: new { id = RouteParameter.Optional }
9: );
10: }
11: }
That route is exactly what we need, so we’ll leave it unchanged. It effectively says the canonical route will for this WebApi will be:
- The constant “api”, followed by,
- The name of the ApiController, optionally followed by,
- The unique ID of a store
An example of such a URL is https://localhost:20480/Store/5 which will invoke a method on a StoreController with the ID == 5
Creating the models
Let’s create some very simple models in our project’s Models folder:
1: public class Store
2: {
3: public string Id { get; set; }
4: public string Url { get; set; }
5: public List<string> Products { get; set; }
6: }
1: public interface IStoreRepository
2: {
3: Store CreateStore();
4: IEnumerable<Store> GetStores();
5: Store GetStore(string id);
6: bool DeleteStore(Store store);
7: void AddProduct(Store store, string product);
8: void RemoveProduct(Store store, string product);
9: }
Creating the StoreController
Now let’s create the StoreController itself by using “Add New Item | Controller” in the Controllers folder. For now, we’ll just expose the basic methods to create a new store and to retrieve the list of all stores.
1: public class StoreController : ApiController
2: {
3: public IStoreRepository Repository { get; set; }
4:
5: [HttpPost]
6: public Store CreateStore()
7: {
8: Store store = Repository.CreateStore();
9: store.Url = Url.Link("DefaultApi", new { controller = "Store", id = store.Id });
10: return store;
11: }
12:
13: [HttpGet]
14: public IEnumerable<Store> GetStores()
15: {
16: return Repository.GetStores().ToArray();
17: }
18: }
Already we can see how to use UrlHelper in this line. Every ApiController instance is given a UrlHelper instance available through the “Url” property. The StoreController uses it to create the unique URL for the new store. The meaning of that line of code is:
- Url.Link – the UrlHelper method to create a URL
- “DefaultApi” – the name of the route. This matches the value passed to MapHttpRoute in WebConfig.cs above
- new { controller = “Store”, id = store.Id } – the set of “route values” to use to compose the URL
If you’re not already familiar with MVC routes, the route values may look a little strange to you. It is just a way to use C#’s anonymous types to compose a set of name/value pairs. The Link() method supports an overload that accepts an IDictionary<string, object> if you prefer. This is the normal pattern to pass values to the MVC routing layer and is not unique to WebApi.
In this case, the call to the Link() method effectively says “Using the route called ‘DefaultApi’ create a URL that will invoke the StoreController and pass the store’s ID to it.”
If the store’s ID had been 99, the resulting URL would have been something like https://localhost:20480/Store/99
Note: How the StoreController gets the IStoreRepository instance is not important to this sample. Refer to this blog for an example of how to use Dependency Injection to provide it at runtime. Not shown in this example is an implementation of IStoreRepository that simply keeps Store instances in memory.
Run the app
At this point, we have all we need to run the app. Start it with F5, observe the normal MVC Home page, and then use Fiddler to issue a request to create a new store. This POST request will invoke the StoreController.CreateStore() method. The response is the new Store object represented in Json. The Url field (https://localhost:20480/Store/3) shows the value returned from UrlHelper.Link().
Add the store-specific methods to StoreController
So now we have a controller that can manufacture new Store objects and return a unique URL for each one. Let’s complete this example by extending StoreController to contain the methods to operate on those individual stores:
1: public class StoreController : ApiController
2: {
3: // GET api/Store/id -- returns the Store from its Id
4: [HttpGet]
5: public Store GetStore(string id)
6: {
7: return EnsureStoreFromId(id);
8: }
9:
10: // DELETE api/Store/id -- deletes the store identified by Id
11: [HttpDelete]
12: public void DeleteStore(string id)
13: {
14: Store store = EnsureStoreFromId(id);
15: bool existed = Repository.DeleteStore(store);
16: if (!existed)
17: {
18: throw new HttpResponseException(HttpStatusCode.NotFound);
19: }
20: }
21:
22: // PUT api/Store/id?product=Xyz -- adds product Xyz to store identified by Id
23: [HttpPut]
24: public void AddProduct(string id, string product)
25: {
26: Store store = EnsureStoreFromId(id);
27: Repository.AddProduct(store, product);
28: }
29:
30: // DELETE api/Store/id?product=Xyz -- removes product Xyz to store identified by Id
31: [HttpDelete]
32: public void RemoveProduct(string id, string product)
33: {
34: Store store = EnsureStoreFromId(id);
35: Repository.RemoveProduct(store, product);
36: }
37:
38: private Store EnsureStoreFromId(string id)
39: {
40: Store store = Repository.GetStore(id);
41: if (store == null)
42: {
43: throw new HttpResponseException(HttpStatusCode.NotFound);
44: }
45:
46: return store;
47: }
48: }
Each of these new StoreController methods accept a string ID – which is the unique Store ID created by the repository. So when a request such as GET https://localhost:20480/Store/3 is sent, the existing route information tells WebApi to invoke StoreController.GetStore() and to model-bind the string ID parameter from the route information contained in the URL.
Let’s run the app again to create a new store and add a new product:
First, we issue a PUT https://localhost:20480/api/Store/3?product=Bicycle to add a Bicycle to store #3:
And then we issue a GET to https://localhost:20480/api/Store/3 to examine the contents of store #3:
Using the MVC UrlHelper to reach ApiControllers
MVC 4 exposes 2 kinds of controllers – the conventional MVC Controller and the new WebApi ApiController. The MVC controllers are normally used for the presentation level and usually have corresponding “views” to display their respective “models”. In contrast, the ApiController is used for the “web api” part of the application, and generally exposes actions to bind to the Http GET, PUT, POST, DELETE (etc) methods.
An MVC 4 app can have a mix of either of these controller types. In fact, it is not unusual for the MVC controllers to be aware of one or more web api’s and to want to render URL links to them.
In the example above, we showed an ApiController creating a URL to reach a specific action on an ApiController. To do this, it used a UrlHelper instance available directly from the executing ApiController.
MVC Controllers have a similar concept – the Controller instance contains a Url property that returns a UrlHelper instance. In this case, this UrlHelper is a different class than the WebApi one because it already existed before WebApi. But they are very similar in concept and functionality.
Let’s modify our app so that the MVC HomeController can render URL links to the StoreController:
1: public class HomeController : Controller
2: {
3: public ActionResult Index()
4: {
5: return View(InMemoryStoreRepository.Instance);
6: }
7:
8: public ActionResult CreateStore()
9: {
10: Store store = InMemoryStoreRepository.Instance.CreateStore();
11:
12: string storeLink = Url.HttpRouteUrl("DefaultApi", new { controller = "Store", id = store.Id });
13: store.Url = new Uri(Request.Url, storeLink).AbsoluteUri.ToString();
14:
15: return this.RedirectToAction("Index", "Home");
16: }
17: }
The highlighted lines above do exactly what we did before in the StoreController – create a new store and then create a URL that will direct a request to the StoreController to operate on that store. It looks slightly different because the MVC UrlHelper already existed before WebApi, and backwards compatibility was important.
The HttpRouteUrl() method was added to the existing MVC UrlHelper to deal with ApiController routes. The example above effectively says “I want a URL to an ApiController using the route named ‘DefaultApi’ to invoke the StoreController, with the given ID”.
Summary
As you saw in this example, the UrlHelper class exists to help compose URL’s using the application’s routing information. There are 2 version of UrlHelper – the existing MVC version and the new WebApi version. Both are available from their respective controller instance, and both can be used to create URL’s.
Comments
Anonymous
March 29, 2015
check some list of Overloaded URLHelper @ mvc4all.com/.../urlhelper-in-mvcAnonymous
March 29, 2015
visit mvc4all.com for information on URLHelper