- | rssFeed | My book on MSBuild and Team Build | Archives and Categories Friday, April 30, 2010

MSBuild 4.0: New command line switches

If you are using MSBuild 4.0 then you may be interested in knowing that there are a couple new switches that you can pass to msbuild.exe when you kick off a build. The new switches, /preprocess(/pp) and /detailedsummary(/ds), are more convenient then necessary.

/preprocess (/pp)

Since you can import other MSBuild files using the Import Element sometimes locating where a target, property or item is being defined can lead to a search that takes you through several files. It can be even more confusing if more than 1 file defines the property or target that you are interested in, because you may have thought that you found the right target but you may one that was overridden by another file. Now with MSBuild 4.0 you don’t have to search through all of those files. You can use the switch /preprocess switch. Here is the snippet from msbuild.exe /? describing it.

/preprocess[:file] 
 Creates a single, aggregated project file by inlining all the files that would be imported during a build, with their boundaries marked. This can be useful for figuring out what files are being imported and from where, and what they will contribute to the build. By default the output is written to the console window. If the path to an output file is provided that will be used instead.
 (Short form: /pp)
 Example:
   /pp:out.txt

When you use this the full logical project file is dumped to the console, or optionally to a file, and it includes references to where the elements are defined. For example I created the following very simple project files.

import-01.proj

<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
    <Target Name="TargetOne">
        <Message Text="From import-02.proj - TargetOne"/>
    </Target>
</Project>

import-02.proj

<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
    <Target Name="TargetTwo">
        <Message Text="From import-02.proj - TargetTwo"/>
    </Target>
</Project>

master.proj

<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="TargetOne;TargetTwo" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">

    <Target Name="TargetOne">
        <Message Text="From master.proj - TargetOne"/>
    </Target>

    <Target Name="TargetTwo">
        <Message Text="From master.proj - TargetTwo"/>
    </Target>

    <Import Project="import-01.proj"/>
    <Import Project="import-02.proj"/>
</Project>

After executing the command msbuild.exe master.proj /pp:out.xml the following was written to the out.xml file.

<?xml version="1.0" encoding="utf-8"?>
<!--
============================================================================================================================================
C:\temp\MSBuild\import\master.proj
============================================================================================================================================
-->
<Project ToolsVersion="4.0" DefaultTargets="TargetOne;TargetTwo" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <Target Name="TargetOne">
    <Message Text="From master.proj - TargetOne" />
  </Target>
  <Target Name="TargetTwo">
    <Message Text="From master.proj - TargetTwo" />
  </Target>
  <!--
============================================================================================================================================
  <Import Project="import-01.proj">

C:\temp\MSBuild\import\import-01.proj
============================================================================================================================================
-->
  <Target Name="TargetOne">
    <Message Text="From import-02.proj - TargetOne" />
  </Target>
  <!--
============================================================================================================================================
  </Import>

C:\temp\MSBuild\import\master.proj
============================================================================================================================================
-->
  <!--
============================================================================================================================================
  <Import Project="import-02.proj">

C:\temp\MSBuild\import\import-02.proj
============================================================================================================================================
-->
  <Target Name="TargetTwo">
    <Message Text="From import-02.proj - TargetTwo" />
  </Target>
  <!--
============================================================================================================================================
  </Import>

C:\temp\MSBuild\import\master.proj
============================================================================================================================================
-->
</Project>

As you can see with /pp it is very easy to see exactly what is defined where and at what location.

/detailedsummary (/ds)

Another new feature with MSBuild 4.0 is the /detailedsummary (/ds) command line switch. When you use this switch you will be shown a detailed summary (haha) of build execution. This summary includes the amount of time spent build each project file as well as the node utilization. I just preformed a build with the command msbuild RuleStack.Engine.sln /m /ds and the summary is shown below.

============================== Build Hierarchy (IDs represent configurations) =====================================================
 Id                  : Exclusive Time   Total Time   Path (Targets)
 -----------------------------------------------------------------------------------------------------------------------------------
 0                   : 0.020s           1.211s       C:\...\RuleStack.Engine.sln ()
 | 1                 : 0.667s           0.667s       C:\...\RuleStack.Engine.Common\RuleStack.Engine.Common.csproj ()
 | 3                 : 0.255s           0.718s       C:\...\Unittest\RuleStack.Engine.Tests\RuleStack.Engine.Tests.csproj ()
 | | 6               : 0.000s           0.000s       C:\...\ObjectBinder\RuleStack.ObjectBinder\RuleStack.ObjectBinder .csproj ()
 | | 5               : 0.000s           0.000s       C:\...\RuleStack.Data\RuleStack.Data.csproj ()
 | | 1               : 0.000s           0.000s       C:\...\RuleStack.Engine.Common\RuleStack.Engine.Common.csproj ()
 | | 2               : 0.000s           0.000s       C:\...\RuleStack.Engine.Backend\RuleStack.Engine.Backend.csproj ( )
 | | 8               : 0.292s           0.460s       C:\...\RuleStack.Engine.Admin.Web\RuleStack.Engine.Admin.Web.csproj ()
 | | | 24            : 0.000s           0.000s       C:\...\RuleStack.Engine.Backend\RuleStack.Engine.Backend.csproj ( GetNativeManifest)
 | | | 5             : 0.000s           0.000s       C:\...\RuleStack.Data\RuleStack.Data.csproj ()
 | | . 2             : 0.000s           0.000s       C:\...\RuleStack.Engine.Backend\RuleStack.Engine.Backend.csproj ( )
 | . 36              : 0.003s           0.003s       C:\...\RuleStack.Engine.Admin.Web\RuleStack.Engine.Admin.Web.csproj (GetNativeManifest)
 | 2                 : 0.319s           0.390s       C:\...\RuleStack.Engine.Backend\RuleStack.Engine.Backend.csproj ( )
 | | 6               : 0.000s           0.000s       C:\...\ObjectBinder\RuleStack.ObjectBinder\RuleStack.ObjectBinder.csproj ()
 | | 5               : 0.000s           0.000s       C:\...\RuleStack.Data\RuleStack.Data.csproj ()
 | | 17              : 0.002s           0.002s       C:\...\RuleStack.Data\RuleStack.Data.csproj (GetNativeManifest)
 | . 21              : 0.001s           0.001s       C:\...\RuleStack.Data\RuleStack.Data.csproj (GetCopyToOutputDirectoryItems)
 | 4                 : 0.382s           0.567s       C:\...\RuleStack.Services\RuleStack.Services.csproj ()
 | | 5               : 0.000s           0.000s       C:\...\RuleStack.Data\RuleStack.Data.csproj ()
 | | 2               : 0.000s           0.000s       C:\...\RuleStack.Engine.Backend\RuleStack.Engine.Backend.csproj ( )
 | | 24              : 0.002s           0.002s       C:\...\RuleStack.Engine.Backend\RuleStack.Engine.Backend.csproj ( GetNativeManifest)
 | . 29              : 0.001s           0.001s       C:\...\RuleStack.Engine.Backend\RuleStack.Engine.Backend.csproj ( GetCopyToOutputDirectoryItems)
 | 7                 : 0.333s           0.337s       C:\...\ObjectBinder\Test_RuleStack.ObjectBinder\Test_RuleStack.ObjectBinder.csproj ()
 | | 6               : 0.000s           0.000s       C:\...\ObjectBinder\RuleStack.ObjectBinder\RuleStack.ObjectBinder.csproj ()
 | | 13              : 0.001s           0.001s       C:\...\ObjectBinder\RuleStack.ObjectBinder\RuleStack.ObjectBinder.csproj (GetNativeManifest)
 | . 19              : 0.001s           0.001s       C:\...\ObjectBinder\RuleStack.ObjectBinder\RuleStack.ObjectBinder.csproj (GetCopyToOutputDirectoryItems)
 | 6                 : 0.210s           0.210s       C:\...\ObjectBinder\RuleStack.ObjectBinder\RuleStack.ObjectBinder.csproj ()
 | 5                 : 0.277s           0.277s       C:\...\RuleStack.Data\RuleStack.Data.csproj ()
 | . 12              : 0.000s           0.000s       C:\...\RuleStack.Engine.Common\RuleStack.Engine.Common.csproj (GetNativeManifest)
 . 43                : 0.002s           0.002s       C:\...\RuleStack.Engine.Admin.Web\RuleStack.Engine.Admin.Web.csproj.metaproj ()

 ============================== Node Utilization (IDs represent configurations) ====================================================
 Timestamp:            1       2       3       4       5       6       7       8        Duration   Cumulative
 -----------------------------------------------------------------------------------------------------------------------------------
 634081842447519669:   0       x       x       x       x       x       x       x        0.018s     0.018s
 634081842447699679:   1       x       x       x       x       x       x       x        0.461s     0.479s #########
 634081842452309943:   |       6       7       5       3       4       2       x        0.130s     0.609s ##
 634081842453610018:   |       |       |       |       8       |       |       x        0.086s     0.695s #
 634081842454470067:   |       |       |       |       |       |       |       x        0.001s     0.696s
 634081842454480067:   x       |       |       |       |       |       |       x        0.001s     0.697s
 634081842454490068:   x       |       x       |       |       x       |       x        0.001s     0.698s
 634081842454500068:   x       |       x       |       |       x       x       x        0.001s     0.699s
 634081842454510069:   x       x       7       |       |       x       x       x        0.002s     0.701s
 634081842454530070:   12      x       |       |       |       x       x       x        0.002s     0.703s
 634081842454550071:   |       13      x       |       |       x       x       x        0.001s     0.704s
 634081842454560072:   |       x       7       |       |       x       x       x        0.008s     0.712s
 634081842454640076:   |       x       |       |       x       x       x       x        0.054s     0.766s #
 634081842455180107:   |       x       |       x       x       x       2       x        0.003s     0.769s
 634081842455210109:   |       x       |       17      x       x       x       x        0.002s     0.771s
 634081842455230110:   |       x       |       x       x       x       2       x        0.036s     0.807s
 634081842455590131:   |       19      x       x       x       x       |       x        0.001s     0.808s
 634081842455600131:   |       x       7       x       x       x       |       x        0.018s     0.826s
 634081842455780142:   |       x       x       x       x       x       |       x        0.036s     0.862s
 634081842456140162:   |       x       x       21      x       x       x       x        0.001s     0.863s
 634081842456150163:   |       x       x       x       x       x       2       x        0.016s     0.879s
 634081842456310172:   |       x       x       x       8       4       x       x        0.003s     0.882s
 634081842456340174:   |       x       x       x       |       x       24      x        0.001s     0.883s
 634081842456350174:   |       x       x       x       x       x       |       x        0.001s     0.884s
 634081842456360175:   |       x       x       x       8       4       x       x        0.148s     1.032s ##
 634081842457840259:   |       x       x       x       |       x       29      x        0.001s     1.033s
 634081842457850260:   |       x       x       x       |       4       x       x        0.023s     1.056s
 634081842458080273:   |       x       x       x       |       x       x       x        0.013s     1.069s
 634081842458210281:   |       x       x       x       3       x       x       x        0.004s     1.073s
 634081842458250283:   |       x       x       x       36      x       x       x        0.003s     1.076s
 634081842458280285:   |       x       x       x       3       x       x       x        0.131s     1.207s ##
 634081842459590360:   0       x       x       x       x       x       x       x        0.001s     1.208s
 634081842459600360:   43      x       x       x       x       x       x       x        0.002s     1.210s
 634081842459620361:   0       x       x       x       x       x       x       x        0.001s     1.211s
 -----------------------------------------------------------------------------------------------------------------------------------
 Utilization:          57.8    30.3    46.9    39.6    76.5    53.6    45.4    .0       Average Utilization: 43.8

In the snippet above you should know that I replace the path to the files with to reduce the width of the output. Also the machine that I’m currently using has 8 cores so it shows 8 nodes, on your machine you may have a different number of columns for the node utilization table.

Sayed Ibrahim Hashimi

msbuild | MSBuild 4.0 Friday, April 30, 2010 1:38:03 AM (GMT Daylight Time, UTC+01:00)  #     | 
Monday, April 26, 2010

Config transformations outside of web app builds

If you are using Visual Studio 2010 then you may already be familiar with the Web.config transformations that are now available. What you might not know is that you can use that same technology to transform config files outside of the build process. You will need Visual Studio 2010 installed on the machine where you perform these transformations. It is very easy to perform these transformation as well. Let’s say that we start with the app.config file shown below.

<configuration>
    <connectionStrings>
        <clear/>
        <add name="Default" connectionString="Data Source=localhost;Initial Catalog=Sample01;Integrated Security=True;" />
    </connectionStrings>
    
    <appSettings>
        <add key="contactEmail" value="contact@demo.example.com"/>
        <add key="siteUrl" value="http://demo.example.com"/>
    </appSettings>
    
</configuration>

Then we create another file, transform.xml, which contains our transformations. That file is shown below.

<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
    <connectionStrings>
        <clear/>
        <add name="Default" connectionString="Data Source=NOT-localhost;Initial Catalog=Sample01;Integrated Security=True;" 
             xdt:Locator="Match(name)" xdt:Transform="Replace"/>
    </connectionStrings>

    <appSettings>
        <add key="contactEmail" value="contact@example.com" xdt:Locator="Match(key)" xdt:Transform="Replace"/>
        <add key="siteUrl" value="http://example.com" xdt:Locator="Match(key)" xdt:Transform="Replace"/>
    </appSettings>

</configuration>

Then we can easily execute the transformations by using MSBuild. So I created a file named trans.proj and it is shown below.

<Project ToolsVersion="4.0" DefaultTargets="Demo" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
    <UsingTask TaskName="TransformXml"
             AssemblyFile="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v10.0\Web\Microsoft.Web.Publishing.Tasks.dll"/>

    <Target Name="Demo">
        <TransformXml Source="app.config"
                      Transform="Transform.xml"
                      Destination="app.prod.config"/>
    </Target>
</Project>

This MSBuild file uses the TransformXml task which is shipped with Visual Studio 2010. We specify the source file, transform file and the destination. Pretty straight forward.

In order to execute this I open a Visual Studio 2010 command prompt, browse to the directory containing both files, and enter the following command

msbuild trans.proj /t:Demo

Once you do this then you will find the file app.prod.config with the following contents.

<configuration>
    <connectionStrings>
        <clear/>
        <add name="Default" connectionString="Data Source=NOT-localhost;Initial Catalog=Sample01;Integrated Security=True;"/>
    </connectionStrings>
    
    <appSettings>
        <add key="contactEmail" value="contact@example.com"/>
        <add key="siteUrl" value="http://example.com"/>
    </appSettings>
    
</configuration>

Sayed Ibrahim Hashimi

Config-Transformation | msbuild | MSBuild 4.0 | MSDeploy | Visual Studio | Visual Studio 2010 Monday, April 26, 2010 5:22:06 AM (GMT Daylight Time, UTC+01:00)  #     | 
Tuesday, April 20, 2010

ASP.NET MVC a better TagBuilder

The other day I was working on a site which required a pager, so I searched around a bit and I a pagerby Gunnar Peipman that looked promising. I found a few others but decided against them. Some of them loaded all of the data into memory and then paged from there, and others just flat out didn't work! In any case I had a good experience with Gunnar's. I wanted to take what Gunnar had and create a view helper using my custom view helpers. Along the way I found myself writing some code using the TagBuilderclass that just didn't sit well with me. Take a look at the snippet below which.
TagBuilder startTag = new TagBuilder("a");
startTag.Attributes.Add("href", string.Format("{0}/{1}", this.UrlPrefix, 1));
startTag.SetInnerText("<<");
startTag.Attributes.Add("title", "first page");
httpResponse.Write(startTag.ToString(TagRenderMode.Normal));

TagBuilder previous = new TagBuilder("a");
previous.Attributes.Add("href", string.Format("{0}/{1}", this.UrlPrefix, this.CurrentPage - 1));
previous.SetInnerText("<");
previous.Attributes.Add("title", "previous page");
httpResponse.Write(previous.ToString(TagRenderMode.Normal));
I didn't like the fact that I had to make a bunch of calls to the tag builder to build the HTML for me, it was just uglier than what I wanted. So I decided to create a new tag builder which places an Fluent interface on top of it (ok, maybe its just method chaining). The end result was the FluentTagBuilderclass. I couldn't extend TagBuilderbecause I wanted to change the return types, so instead I created the class to just contain one and to just pass the calls through to it. What I did was to declare all the same properties and methods that the TagBuilder had, but just change the ones who returned void to return the same object itself. So for example I created methods like.
public FluentTagBuilder AddCssClass(string value)
{
    this.TagBuilder.AddCssClass(value);
    return this;
}

public FluentTagBuilder SetInnerHtml(string innerHtml)
{
    this.TagBuilder.InnerHtml = innerHtml;
    return this;
}
With this I can chain different method calls together and create code which looks better. If you've used jQuerythen you are used to this. With this in place I was able to convert the snippet above into the following.
FluentTagBuilder startTag =
    new FluentTagBuilder("a")
    .AddAttribute("href", string.Format("{0}/{1}", this.UrlPrefix, 1))
    .SetInnerText("<<")
    .AddAttribute("title", "first page");

httpResponse.Write(startTag.ToString(TagRenderMode.Normal));

FluentTagBuilder previous =
    new FluentTagBuilder("a")
    .AddAttribute("href", string.Format("{0}/{1}", this.UrlPrefix, this.CurrentPage - 1))
    .SetInnerText("<")
    .AddAttribute("title", "previous page");

httpResponse.Write(previous.ToString(TagRenderMode.Normal));
To me this is a lot easier to read, and to create. If you agree you can grab the class and include it in your projects. Links to full source files are below.
  1. FluentTagBuilder.cs
  2. Pager.cs
Sayed Ibrahim Hashimi
ASP.NET MVC | Fluent interface | View helpers Tuesday, April 20, 2010 5:10:14 AM (GMT Daylight Time, UTC+01:00)  #     | 
Friday, April 09, 2010

Entity Framework: Externalizing filters and dynamic includes

Today a friend of mine asked me to explain (via email) a few things to another developer regarding some things I have done with the Entity Framework (EF). I figured since I already typed it up I might as well share it here as well. So here it is.

I have had 2 issues with using EF4 which I have found creative and effective work around for.

Externalizing filters

The first issue, which also exists when using other frameworks, is that you end up with an explosion of methods (and usually a lot of duplicated code). For instance let’s say you have a table to store users. This table has an ID, and Email field and, and let’s and OpenIdUrl. So you end up with the following methods.

User GetUserById(long id)

User GetUserByEmail(string email)

User GetUserByOpenIdUrl(string openIdUrl)

Usually you’ll find that each of these methods have the exact same implementation except a different where clause. Now there is a bigger problem in the fact that you cannot anticipate everything that the user may want to filter on. Because of this and the dynamic nature of IQueryable I have employed a technique using Expression Trees to allow the user to declare the exact filter to be used via a lambda expression. So for the users it’s very slick. For example I could create the following method

public User GetUser(Expression<Func<User, bool>> filter)
{
    if (filter == null)
    {
        filter = Const<User>.LinqExpression.LinqExpressionFuncAlwaysTrue;
    }

    User foundUser = null;
    using (InlineTasksContext dbContext = new InlineTasksContext())
    {
        dbContext.Connection.Open();
        foundUser = dbContext.Users.Where(filter).SingleIfExists();
    }

    return foundUser;
}

In this case I’m allowing the user to pass in a filter that is applied to the query. Here is how I could implement those three if I chose to as well.

public User GetUserById(long id)
{
    return this.GetUser(user => user.Id == id);
}
public User GetUserByEmail(string email)
{
    if (email == null) { throw new System.ArgumentNullException("email"); }

    return GetUser(user => user.Email == email);
}
public User GetUserByOpenIdUrl(string openIdUrl)
{
    if (string.IsNullOrEmpty(openIdUrl)) { throw new ArgumentNullException("openIdUrl"); }

    return GetUser(user => user.OpenIdUrl == openIdUrl);
}

Note in the first method I am using some other helpers I created. Specifically the Const<User>.LinqExpression.LinqExpressionFuncAlwaysTrue as well as the SingleIfExists

extension method. I use the AlwaysTrue expression so that way I don’t have to be worried about doing an if and having to maintain 2 select statements. And the other (SingleIfExists) is used to just return null instead of blowing up if no elements exist in the sequence.

Since they are not the purpose of this email I’ve just put them at the bottom so that you can read if you are interested.

As Keith mentioned to me a while back Expression trees are not serializable so you still end up with this problem if you are using a service based solution. But you could try and use the sample Expression Tree Serialization to take care of this for you, I’ve never tried it, but it’s on my TODO list.

.Include

My next issue is that you are forced to declare what navigation properties (i.e. table links) should be included in the query. This is done using the .Include method. So back to our GetUser example, how can a framework know what tables values should be extracted for? The end result is that the framework creator just includes a bunch of tables and 90% of the time the result is just wasted resources because the other data is never touched. Better is to provide a default which only includes the most commonly used tables and another method that allows them to specify what they want. Here is a sample for a GetTasksByUserId method

public IList<Task> GetTasksByUserId(long userId, IEnumerable<string> includeList)
{
    using (InlineTasksContext dbContext = new InlineTasksContext())
    {
        var result = (from t in dbContext.Tasks
                        .Include(includeList)
                        .Where(t=>t.User.Id == 1)
                        select t)
                        .ToList();
        return result;
    }
}
 In this example I’m using an extension method for.Include which is defined as follows.
public static ObjectQuery<T> Include<T>(this ObjectQuery<T> query, IEnumerable<string> includes)
{
    if (query == null) { throw new System.ArgumentNullException("query"); }

    if (includes != null)
    {
        foreach (string include in includes)
        {
            query.Include(include);
        }
    }
    return query;
}

So the idea is to create a framework which is both robust and easy to use, but at the same time is not overly assumptive about what the users want. I think a lot more can be done with EF and its dynamic nature.

Does that cover what you were intending Keith? Any questions/comments please send them my way.

Here are those methods I mentioned above.

public static class LinqExtensions
{
    public static T SingleIfExists<T>(this IQueryable<T> query)
    {
        if (query == null) { throw new System.ArgumentNullException("query"); }

        T result = default(T);
        if (query.Count() > 0)
        {
            result = query.Single();
        }

        return result;
    }
}
//----------------------------------------------------------------------------------------------------
public class Const<T>
{
    public static class Predicate
    {
        public static Predicate<T> AlwaysTrue
        {
            get
            {
                return x => true;
            }
        }

        public static Predicate<T> AlwaysFalse
        {
            get
            {
                return x => false;
            }
        }
    }

    public class LinqExpression
    {
        public static Expression<Func<T, bool>> LinqExpressionFuncAlwaysTrue
        {
            get
            {
                return x => true;
            }
        }
    }
}
Sayed Ibrahim Hashimi
Entity | Entity Framework | LINQ Friday, April 09, 2010 2:49:32 AM (GMT Daylight Time, UTC+01:00)  #     | 
Friday, April 02, 2010

ASP.NET MVC Route + Ajax + jQuery

The story around routing in ASP.NET MVC is pretty good. When a resource is requested the action that it is routed to is determined and the parameters to the action are initialized. Counter to that when you build a link you specify the argument values and it creates the url for you. So if your routes happen to change your application shouldn’t be bothered by that. One area which can be problematic for this is if you are making any ajax requests. What I’ve done to minimize the impact of route changes to Ajax requests is to have the script make a call to determine the correct route. In the most trivial case this is extremely easy. You call an action, which has a known route, passing into it the name of the action and controller. It returns to you the url for the route. This works really good, but the problem is when your routes have parameters, how do you handle those?

First let’s take a look at the Javascript and then the definition of the action method which it calls.

// wire up the event for the enter button
$("#searchText").keypress(function (event) {
    if (event.keyCode == 13) {
        // grab the text that is inside the box
        var text = $("#searchText").val();

        var routData = {
            controllerName: 'Search',
            actionName: 'Search',
            paramValues: "{ criteria: '" + text + "' }"
        };

        $.ajax({
            url: "/Home/MapRoute",
            data: routData,
            success: function (data) {
                window.location.href = data;
            },
            error: function (data) {
                alert('there was a failure on the internets');
            }
        });

    }
});

Here I am building a Javascript object named routeData. This is declared in JSON. One thing to pay attention to is the fact that the value for paramValues is a string containing JSON. This is passed to the controller action.

I’m using jQuery to make an Ajax request to /Home/MapRoute. This maps to the method shown below.

public JsonResult MapRoute(string actionName, string controllerName, string paramValues)
{
    JavaScriptSerializer jss = new JavaScriptSerializer();

    Dictionary<string, string> parameters = jss.Deserialize<Dictionary<string, string>>(paramValues);

    RouteValueDictionary rd = new RouteValueDictionary();
    foreach (string key in parameters.Keys)
    {
        rd.Add(key, parameters[key]);
    }

    UrlHelper urlHelper = new UrlHelper(this.Request.RequestContext);
    string url = urlHelper.Action(actionName, controllerName, rd);

    return Json(url, JsonRequestBehavior.AllowGet);
}

Here I’m using the JavaScriptSerializer to convert the JSON string into a dictionary, with string as key and value. I use that dictionary to create a RouteValueDictionary which is passed, along with other parameters, into the UrlHelper to generate the url. When you return the Json result you must specify JsonRequestBehavior.AllowGet, otherwise a 500 internal service error will be returned. I think this is new with ASP.NET 2.

When the action method returns, you can use that url. The drawback to this approach is that you will make an extra request to determine the url, but you will be sure that those urls are correct. Also you could cache the results with Output Caching since the routes won’t change.

Sayed Ibrahim Hashimi

ASP.NET MVC | Javascript | jQuery | routing Friday, April 02, 2010 6:24:30 AM (GMT Daylight Time, UTC+01:00)  #     |