{"id":1800,"date":"2013-01-24T20:56:13","date_gmt":"2013-01-25T02:56:13","guid":{"rendered":"http:\/\/www.wiredprairie.us\/blog\/?p=1800"},"modified":"2013-01-24T20:56:13","modified_gmt":"2013-01-25T02:56:13","slug":"named-routing-with-knockout-asp-net-mvc-and-attributerouting-from-javascript","status":"publish","type":"post","link":"https:\/\/www.wiredprairie.us\/blog\/index.php\/archives\/1800","title":{"rendered":"Named routing with Knockout, ASP.NET MVC, and AttributeRouting, from JavaScript"},"content":{"rendered":"

It’s hard to describe exactly what I’ve built here, but I’m just throwing these pieces out on the Internet in case someone: a) finds them useful, or b) has a better solution.<\/p>\n

Goals:<\/p>\n

    \n
  1. Named routes, with a wee bit of a Rails feel <\/span><\/li>\n
  2. A Knockout friendly syntax for binding to the values of a model to build an href<\/span><\/li>\n
  3. Automatic replacement of values in the url with values from the model<\/li>\n
  4. JavaScript generation (OK, not really a goal, but a necessary evil I suspected)<\/li>\n<\/ol>\n

    Solution:<\/p>\n

    I started by using AttributeRouting (from<\/span> AttributeRouting.net<\/a>). It offers a simple syntax for creating routes (and naming them):<\/span><\/p>\n

    [sourcecode language=”csharp”]
    \n[GET("research\/{id}", RouteName = "research_details")]
    \npublic ActionResult Details(string id)
    \n{
    \n var model = new ResearchDetailsViewModel();
    \n[\/sourcecode]<\/p>\n

    In RouteConfig.cs, code gathers the list of named routes, and builds a snippet of JavaScript which will be inserted on every page (yes, it would be nice if this were cached, see refactoring below):<\/p>\n

    [sourcecode language=”csharp”]<\/p>\n

    var named = new List();
    \nforeach (var route in RouteTable.Routes)
    \n{
    \nAttributeRoute r = route as AttributeRoute;
    \n \u00a0 \u00a0if (r != null)
    \n \u00a0 \u00a0{
    \n \u00a0 \u00a0\u00a0 \u00a0if (!string.IsNullOrWhiteSpace(r.RouteName))
    \n \u00a0 \u00a0\u00a0 \u00a0{
    \n \u00a0 \u00a0\u00a0 \u00a0\u00a0 \u00a0named.Add(r);
    \n \u00a0 \u00a0\u00a0 \u00a0}
    \n \u00a0 \u00a0}
    \n}
    \nif (named.Count > 0)
    \n{
    \n \u00a0 \u00a0StringBuilder js = new StringBuilder();
    \n \u00a0 \u00a0foreach (var namedRoute in named)
    \n \u00a0 \u00a0{
    \n \u00a0 \u00a0\u00a0 \u00a0js.AppendFormat(@"function {0}_url(model){{ return buildUrl(""{1}"", model); }}",
    \n \u00a0 \u00a0\u00a0 \u00a0namedRoute.RouteName, namedRoute.Url);
    \n \u00a0 \u00a0\u00a0 \u00a0js.AppendLine();
    \n \u00a0 \u00a0}
    \n \u00a0 \u00a0RouteScript = js.ToString();
    \n}
    \n[\/sourcecode]<\/p>\n

    The name of the function is based on the RouteName property of the attribute.<\/p>\n

    I added a function called buildUrl\u00a0<\/strong>to a common JavaScript file:<\/p>\n

    [sourcecode language=”javascript”]<\/p>\n

    function buildUrl(url, model) {
    \n \/\/ unfixed if there’s not a thing
    \n \u00a0 if (typeof model === ‘undefined’ || model === null) { return url; }<\/p>\n

    var propValue;
    \n \u00a0 for (var propName in model) {
    \n \u00a0 \u00a0\u00a0 \u00a0 if (model.hasOwnProperty(propName)) {
    \n propValue = model[propName];
    \n \u00a0 \u00a0\u00a0 \u00a0\u00a0 if (ko) { propValue = ko.utils.unwrapObservable(propValue); }<\/p>\n

    if (typeof propValue === ‘undefined’ || propValue === null) {
    \n \u00a0 \u00a0\u00a0 \u00a0\u00a0 \u00a0\u00a0 propValue = "";
    \n } else {
    \n \u00a0 \u00a0\u00a0 \u00a0\u00a0 \u00a0 propValue = propValue.toString();
    \n \u00a0 \u00a0\u00a0 \u00a0 }
    \n \u00a0 \u00a0\u00a0 \u00a0 url = url.replace(‘{‘ + propName.toLowerCase() + ‘}’, propValue);
    \n \u00a0 }
    \n \u00a0 \u00a0}
    \n \u00a0 \u00a0return url;
    \n}
    \n[\/sourcecode]<\/p>\n

    The code above validates there is data and then proceeds to loop through every property of the model to see if there might be a replacement needed within the passed URL. If Knockout is available (if ko check), then it unwraps the value as the code needs the raw value from this point onward.<\/p>\n

    It lowercases each property name and tries a replacement.<\/p>\n

    So, if the string was \/research\/{id}\/{category}<\/strong>, and the model has properties named, Id<\/strong> and Category<\/strong>, the values of each will be substituted and returned as a full string. For example, it might be \/research\/123\/Coding<\/strong>.<\/p>\n

    Next, a simple example of using some JSON data:<\/p>\n

    [sourcecode language=”javascript”]
    \n$(function\u00a0()\u00a0{
    \n var\u00a0vm\u00a0=\u00a0{
    \n url:\u00a0function\u00a0($data)\u00a0{
    \n return\u00a0app_url(research_details_url($data));
    \n },
    \n data\u00a0:\u00a0ko.observable()
    \n };<\/p>\n

    $.getJSON(app_url("api\/research\/")).success(function\u00a0(data)\u00a0{
    \n vm.data\u00a0=\u00a0ko.mapping.fromJS(data);
    \n ko.applyBindings(vm);
    \n });
    \n});
    \n[\/sourcecode]<\/p>\n

    Then, finally, the Knockout template:<\/p>\n

    [sourcecode language=”html”]
    \n<div data-bind="foreach: data.research">
    \n <h3 class="title" data-bind="text: Title"><\/h3>
    \n <div>
    \n <a data-bind="attr: { href: $parent.url($data) }">Details<\/a>
    \n <\/div>
    \n<\/div>
    \n[\/sourcecode]<\/p>\n

    By using $parent in the template, Knockout points at the view model in this case (vm<\/strong>). On the view model, I added a function called url<\/strong>. Right now, I’ve hard coded it to point to a specific dynamically generated function called research_details_url. It’s passing the array item (which simply has an Id & Title property). So, the data-bind for the anchor (A) element assigns the value of the computation to the href attribute.<\/p>\n

    app_url:<\/p>\n

    [sourcecode language=”javascript”]<\/p>\n

    function app_url(url) {
    \nreturn "@Request.ApplicationPath" + url;
    \n}<\/p>\n

    [\/sourcecode]<\/p>\n

    As I said, it’s not super elegant, but it\u00a0achieved\u00a0my basic goals.<\/p>\n

    Elegance rating: 59%<\/p>\n

    Function rating: 100%<\/p>\n

    Need of some refactoring rating: 99%<\/p>\n","protected":false},"excerpt":{"rendered":"

    It’s hard to describe exactly what I’ve built here, but I’m just throwing these pieces out on the Internet in case someone: a) finds them useful, or b) has a better solution. Goals: Named routes, with a wee bit of a Rails feel A Knockout friendly syntax for binding to the values of a model […]<\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"spay_email":"","jetpack_publicize_message":"","jetpack_is_tweetstorm":false,"jetpack_publicize_feature_enabled":true},"categories":[4],"tags":[],"jetpack_publicize_connections":[],"jetpack_featured_media_url":"","jetpack_sharing_enabled":true,"jetpack_shortlink":"https:\/\/wp.me\/pd5QIe-t2","jetpack_likes_enabled":true,"jetpack-related-posts":[{"id":1820,"url":"https:\/\/www.wiredprairie.us\/blog\/index.php\/archives\/1820","url_meta":{"origin":1800,"position":0},"title":"Knockout binding for JavaScript route fixup","date":"January 25, 2013","format":false,"excerpt":"Part one. After the first round, I felt compelled to KnockOut the code a bit more. I\u2019d mentioned I wasn\u2019t pleased with the code exactly. It needed some refactoring. So, I\u2019ve created a new Knockout binding handler. This binding handler replaces\u00a0 named parameters with a model\u2019s properties in a path.\u2026","rel":"","context":"In "Coding"","img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":1565,"url":"https:\/\/www.wiredprairie.us\/blog\/index.php\/archives\/1565","url_meta":{"origin":1800,"position":1},"title":"Knockout.JS: Dictionary\/Index and ObservableArray, Part 2","date":"March 10, 2012","format":false,"excerpt":"Ryan suggested an alternative in a comment (and corresponding jsFiddle) to the technique that I\u2019d used in my previous Knockout.JS post. Following his suggestion, I made a few tweaks to my original function (and renamed it yet again): ko.observableArray.fn.withIndex = function (keyName) { var index = ko.computed(function () { var\u2026","rel":"","context":"In "Coding"","img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":1563,"url":"https:\/\/www.wiredprairie.us\/blog\/index.php\/archives\/1563","url_meta":{"origin":1800,"position":2},"title":"Knockout.JS: AsDictionary","date":"March 9, 2012","format":false,"excerpt":"I frequently find that I have an array of objects in JavaScript that I want to display in a particular order and also have the ability to quickly locate an object by an ID or a key (and not use the indexOf function). As my recent project is using Knockout.JS,\u2026","rel":"","context":"In "Coding"","img":{"alt_text":"","src":"https:\/\/i0.wp.com\/www.wiredprairie.us\/blog\/wp-content\/uploads\/2012\/03\/image3.png?resize=350%2C200","width":350,"height":200},"classes":[]},{"id":1171,"url":"https:\/\/www.wiredprairie.us\/blog\/index.php\/archives\/1171","url_meta":{"origin":1800,"position":3},"title":"Translating Controller, Action, and Route Data to a JavaScript Object in ASP.NET MVC 3","date":"February 2, 2011","format":false,"excerpt":"To enable a more rich JavaScript\/Ajax experience on a web page, I had need of more detailed information regarding the route that resulted in the current View being displayed. I checked around a few sites, and nothing popped out as obviously awesome. As I was putting the JavaScript in the\u2026","rel":"","context":"In "Coding"","img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":1345,"url":"https:\/\/www.wiredprairie.us\/blog\/index.php\/archives\/1345","url_meta":{"origin":1800,"position":4},"title":"Creating a simple Entity Reference system for Ember.js","date":"December 28, 2011","format":false,"excerpt":"I had some data stored in a structure similar to this: Nothing too fancy. A table of Gift(s) and a table of Person(s). Each gift had a foreign key relationship to a Person (the person who \u201cgave\u201d the gift). Since some people may give several gifts, I didn\u2019t want to\u2026","rel":"","context":"In "Coding"","img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":1442,"url":"https:\/\/www.wiredprairie.us\/blog\/index.php\/archives\/1442","url_meta":{"origin":1800,"position":5},"title":"Nest Thermostat API\/Protocol","date":"January 8, 2012","format":false,"excerpt":"While Nest Labs hasn\u2019t released a formal (documented & supported) API, I thought I\u2019d do a bit of digging to see how they\u2019re using the network and what might be achievable. A few things are going on, the majority as you\u2019d probably expect. The web interface is using a long\u2026","rel":"","context":"In "Coding"","img":{"alt_text":"image","src":"https:\/\/i0.wp.com\/www.wiredprairie.us\/blog\/wp-content\/uploads\/2012\/01\/image_thumb7.png?resize=350%2C200","width":350,"height":200},"classes":[]}],"_links":{"self":[{"href":"https:\/\/www.wiredprairie.us\/blog\/index.php\/wpjson\/wp\/v2\/posts\/1800"}],"collection":[{"href":"https:\/\/www.wiredprairie.us\/blog\/index.php\/wpjson\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.wiredprairie.us\/blog\/index.php\/wpjson\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.wiredprairie.us\/blog\/index.php\/wpjson\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/www.wiredprairie.us\/blog\/index.php\/wpjson\/wp\/v2\/comments?post=1800"}],"version-history":[{"count":19,"href":"https:\/\/www.wiredprairie.us\/blog\/index.php\/wpjson\/wp\/v2\/posts\/1800\/revisions"}],"predecessor-version":[{"id":1819,"href":"https:\/\/www.wiredprairie.us\/blog\/index.php\/wpjson\/wp\/v2\/posts\/1800\/revisions\/1819"}],"wp:attachment":[{"href":"https:\/\/www.wiredprairie.us\/blog\/index.php\/wpjson\/wp\/v2\/media?parent=1800"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.wiredprairie.us\/blog\/index.php\/wpjson\/wp\/v2\/categories?post=1800"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.wiredprairie.us\/blog\/index.php\/wpjson\/wp\/v2\/tags?post=1800"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}