Selecting ListView Items with CheckBoxes

by ashic 14. August 2010 04:57

Often we need to enable users to select multiple items from a ListView control and do a batch operation on them. While the ListView control does enable selection, it only supports selecting one row (item) at a time. This article shows a nice, easy and reusable way to enable multiple selection with checkboxes.

The Project

I've created a very simple "Empty ASP.NET WebForms Application" and added a standard Default.aspx page. I've created an App_Data folder and added a database called PeopleDB.mdf into it. The database consists of one simple table holding the Id, Name and AwesomePoints of a number of people* and also if they are attending some event. We want to list them in a simple ListView with pagination, and be able to select some of them and set all of the selected people's AwesomePoints in one go. We also want to be able to select some other people and set the boolean stating whether or not they're attending.

*these people are purely fictional and any resemblance to real people are entirely coincidental ;)

The Page Layout

The page layout is fairly simple. It's ugly and OMG I'm using tables for layout – help – the Earth is gonna explode…yeah right. At least it's apparent what needs doing. We show a list of people and provide checkboxes for each row to select the person for either batch update of their AwesomePoints or batch update of their IsAttending value. At the end of the page, we provide controls to actually carry out the batch updates. Here's the page body's markup so far:


<body>
    <form id="form1" runat="server">
    <div>
        <asp:ListView runat="server" DataKeyNames='Id' ID='lvPeople' ItemPlaceholderID='ph1'>
            <LayoutTemplate>
                <table>
                    <thead>
                        <tr>
                            <th>Name</th>
                            <th>Awesome Points</th>
                            <th>Is Attending</th>
                            <th>Select for Updating Awesome Points</th>
                            <th>Select for Updating Attendee Status</th>
                        </tr>
                    </thead>
                    <tbody>
                        <asp:PlaceHolder runat="server" ID='ph1' />
                    </tbody>
                </table>
            </LayoutTemplate>
            <ItemTemplate>
                <tr>
                    <td><%# Eval("Name") %></td>
                    <td><%# Eval("AwesomePoints") %></td>
                    <td><%# Eval("IsAttending") %></td>
                    <td><asp:CheckBox runat="server" ID='chkAwesomePoints' /></td>
                    <td><asp:CheckBox runat="server" ID='chkAttending' /></td>
                </tr>
            </ItemTemplate>
        </asp:ListView
        &lt;div>
            <asp:DataPager ID="DataPager1" runat="server" PagedControlID='lvPeople' PageSize='5'>
                <Fields>
                    <asp:NumericPagerField />
                </Fields>
            </asp:DataPager>
        </div>
        <br />
        <table border='none'>
            <tbody>
                <tr>
                    <td>Set Awesome Points to: <asp:TextBox runat="server" ID='txtAwesomePoints' /></td>
                    <td><asp:Button Text="Update Awesome Points" runat="server" /></td>
                </tr>
                <tr>
                    <td>Set Attendee Status to: <asp:CheckBox runat="server" ID='chkAttending' /></td>
                    <td><asp:Button Text="Update Attending Status" runat="server" /></td>
                </tr>
            </tbody>
        </table>
    </div>
    </form>
</body>

Notice that I've set the DataKeyNames fo the ListView to Id. I'll explain this part later.

The DataSource

We have the ListView, we now need to add a datasource. I could easily use a SqlDataSource or whatever else I want. But since I'll be updating the database as well, I'll just add an Entity Framework model to the project (and use an EntityDataSource control to populate the ListView). Right click the project in solution explorer and select "Add New Item". I chose ADO.NET Entity Data Model from the popup and name it PeopleModel.edmx.

1_adding_EF

From the next window, I select "Generate From Database". I get an option to select the connection to be used. Since I don't have one to the mdf file in App_Data, I select "New Connection". I ensure that the "Microsft SQL Serv Database File (SqlClient)" is selected and I "Browse" to my .mdf file in the App_Code folder. You could obviously use other databases and you'd configure the connection here. Finishing this dialog takes you back to the previous one, where you can click "Next".

2_EF_connection

The next window gives you options of selecting a subset or all of the tables, sprocs and views in the target database. We're only interested in the People table, so select it and click "Finish".

3_select_table

And that's it – our EF4 model is done. Let's now add an EntityDataSource to the page.To do this, add the following markup right after the ListView:


<asp:EntityDataSource runat="server" ID='edsPeople'
    ConnectionString="name=PeopleDBEntities"
    DefaultContainerName="PeopleDBEntities" EnableFlattening="False"
    EntitySetName="People"></asp:EntityDataSource>

(If you're using the webforms designer to hook up the datasource, be sure to compile the project before attempting to do so. Without a recompile, the required metadata would not be available to the user.)

Next, set the DataSourceId of the ListView to match the Id of the EntityDataSource:


<asp:ListView runat="server" ID='lvPeople' DataKeyNames='Id' ItemPlaceholderID='ph1' DataSourceID='edsPeople'>

At this point, we can run the page to see this:

4_initial_results

 

The ListView Extension

Add a folder called Extensions and in it, create a class called ListViewExtensions with the following code:



public static class ListViewExtensions
{
    public static List<DataKey> GetSelectedDataKeys(this ListView control, string checkBoxId)
    {
        return control.Items.Where(x => IsChecked(x, checkBoxId))
           .Select(x => control.DataKeys[x.DisplayIndex])
           .ToList();
    }

    private static bool IsChecked(ListViewDataItem item, string checkBoxId)
    {
        var control = item.FindControl(checkBoxId) as CheckBox;
        if (control == null)
        {
            return false;
        }

        return control.Checked;
    }
}

This class is the heart of what we're trying to do. It basically finds the checkbox with the Id passed in and returns the DataKey of every item in the ListView that has said checkbox checked. Remember the DataKeyNames property of the ListView that we set earlier? This is where that comes in. You can pass the names of the properties of each data item that you wish to have access to later on to the DataKeyNames. In our case, we only need the Id property, as such we passed in "Id". If for some reason, we wanted the AwesomePoints property too, we'd pass in "Id, AwesomePoints". Notice that our extension method returns a list of DataKeys. What is a DataKey? It's basically an object that holds the values of the properties mentioned in DataKeyNames. It has two properties – Value and Values. Value hold the value of the first data key property. So, if we had passed in "Id, AwesomePoints", Value would hold the value of the Id property for the associated item. Values on the other hand has an ordered dictionary. You would then find the value of the Id property in Values[0] and the value of the AwesomePoints property in Values[1] and so on.

Putting it to Work

We'll now add two handlers for the two button click events:



protected void UpdateAwesomePointsButton_Click(object sender, EventArgs e)
{
    var selectedKeys = lvPeople.GetSelectedDataKeys("chkAwesomePoints");
    var selectedIds = selectedKeys.Select(x => new Guid(x.Value.ToString()));

    if (selectedIds.Count() > 0)
    {
        var targetPoints = int.Parse(txtAwesomePoints.Text);
        var db = new PeopleDBEntities();

        db.People.Where(x => selectedIds.Contains(x.Id))
            .ToList()
            .ForEach(x => x.AwesomePoints = targetPoints);

        db.SaveChanges();
        lvPeople.DataBind();
    }

}

protected void UpdateAttendeeStatusButton_Click(object sender, EventArgs e)
{
    var selectedKeys = lvPeople.GetSelectedDataKeys("chkAttending");
    var selectedIds = selectedKeys.Select(x => new Guid(x.Value.ToString()));

    if (selectedIds.Count() > 0)
    {
        var targetValue = chkAttending.Checked;
        var db = new PeopleDBEntities();

        db.People.Where(x => selectedIds.Contains(x.Id))
            .ToList()
            .ForEach(x => x.IsAttending = targetValue);

        db.SaveChanges();
        lvPeople.DataBind();
    }
}

All we've done here is used our extension method to get the datakeys of the selected items in the ListView and have updated those items accordingly. Granted there's no validation or anything fancy going on – hey, this is a demo – it shows how we can know which items of a ListView have been selected.

Source

You can download the source by clicking here.

Share or Bookmark this post…
  • Facebook
  • DotNetKicks
  • Digg
  • LinkedIn
  • Technorati
  • del.icio.us
  • Google
  • Live
  • Tumblr
  • msdn Social
  • Ping.fm
  • Reddit
  • Slashdot
  • StumbleUpon
  • TwitThis
Categories: .NET | ASP.NET

ASP.NET Routing with IIS 7 – Remember Your Modules

by ashic 26. July 2010 13:04

ASP.NET 4.0 introduces routing and it works very well. With IIS 7, there are some interesting configuration options that can help improve your site's performance. When done wrong, they can have unexpected side effects. And we're going to look at one of them today.

Extensionless Urls and Wildcard Mappings

Before IIS 7, the easy way to get extensionless urls when working with routing would be to have a wildcard mapping (yes, I know there are other ways with ISAPI handlers and stuff, but that's not too simple). A wildcard mapping would simply put every request for every file through the asp.net pipeline running all the managed modules and stuff. This does carry with it a certain performance hit – whether that hit applies to you or not, you'd have had to test for yourself. There's a good chance that you'd need admin privileges to do this, which is another problem if you're on shared hosting. Even without routing, there are a ton of different things that need to be set in IIS, with no simple way of overriding in your individual application (without IIS access).

Enter IIS 7

IIS 7 reads in a section of the web.config file called <system.webserver>. The <system.webserver> node goes directly inside the <configuration> node ([i.e. it's parallel <system.web>). This section is totally ignored by previous versions and can enable you to set settings that previously required IIS access or some not so nice workarounds to do (if at all possible). In this section, you can tell ASP.NET to run certain modules, not run others – or even to run all managed modules for every request. The latter feature is also the easiest to set up, and simply goes like this:


<system.webServer>
  <modules runAllManagedModulesForAllRequests="true">
   </modules>
</system.webServer>

That would do exactly what you'd expect – it'll run all managed modules for all requests. That's kind of like the wildcard mappings. Not the question is, do we need to run every single managed module for every request? Probably not. So, this can be a chance for improvement. We only want routing to run for all requests – and that should give us extensionless urls. So let's do that now:

An Improvement

We'll now tell IIS 7 to not run all managed modules for all requests.


<system.webServer>
  <modules>
   </modules>
</system.webServer>

That was simple, but that now means that the routing module isn't running. Let's make that run for all requests. We can do so by doing this:


<system.webServer>
  <modules>
    <remove name="UrlRoutingModule-4.0" />
    <add name="UrlRoutingModule-4.0" type="System.Web.Routing.UrlRoutingModule" preCondition="" />
  </modules>
</system.webServer>

With this, our routing should work as expected, while not loading all the other managed modules for every request. So alls well, right?

Hang on a Mo'

While what we just did is better in the sense that it will only do routing for all requests (which we need for extensionless urls), we have potentially just introduced a bug. We're only running routing for every request, and all the other managed modules will only be run for known ASP.NET resources (for example, .aspx files). Read that again – other than routing, all the other managed modules are only be run for requests to known ASP.NET resources like .aspx files. Say, we have a page at http://mysite.com/folder/index.aspx. If we target that url, all shall be fine – IIS will see that the requested file has an aspx extension and it will run the required managed modules. Now, let's say we use routing to route requests for http://mysite.com/foo to that same page. If we request that url, IIS will look at it and will think it's not for ASP.NET. Since we configured the routing module to run, that will run and the request will get processed correctly by /folder/index.aspx. Keep in mind though, that since we aren't running all managed modules for all requests and don't have a wildcard mapping, IIS will not cause the other managed modules to fire. What does this mean? It means that commonly used functionality might suddenly disappear. If our /folder/index.aspx page uses Session, then we'll get an exception. (The error message for that particular one will keep you guessing – it tells you that you need to set enableSession to true!) In other words, if the page uses Session and if it's requested via it's actual path (i.e. ~/folder/index.aspx with the .aspx extension), then it'll run fine – but if it's requested with ~/foo (i.e. the extensionless route), then trying to access Session will fail.

So How do We Fix it?

One obvious way would be to enable all managed modules for all requests. But if we wish to follow our previous approach of only loading the required modules and not any more, then we can do this:


<system.webServer>
  <modules >
    <remove name="UrlRoutingModule-4.0" />
    <add name="UrlRoutingModule-4.0" type="System.Web.Routing.UrlRoutingModule" preCondition="" />
    <remove name="Session"/>
    <add name="Session" type="System.Web.SessionState.SessionStateModule" preCondition=""/>
  </modules>
</system.webServer>

In other words, we're first removing the Session module and then adding it in again with a blank precondition. This will ensure that the Session module also gets run for all requests and not just for known ASP.NET resources. As such, Session will work regardless of whether you're using a real path or a routed path. Keep in mind though, that if the concerned module is loaded in IIS, then we must ensure we use the exact same name in the <remove> tag(s). There is a bit of magic stringy nature to this, but luckily, you can lookup the module name easily in IIS. In IIS 7 manager, just select the server from the left and double click on "Modules" from the centre pane. You should see a list of all loaded modules with the first column showing the names. That name is what you should give the <remove> tag in the web.config file. Remember, you'll need to do this for all the modules you intend to have running. These include - but are not limited to – Session, Forms Authentication, Windows Authentication etc.

So next time you're doing "optimized" routing and your routed pages don't seem to be working right, check to see if the modules are in fact getting loaded.

Share or Bookmark this post…
  • Facebook
  • DotNetKicks
  • Digg
  • LinkedIn
  • Technorati
  • del.icio.us
  • Google
  • Live
  • Tumblr
  • msdn Social
  • Ping.fm
  • Reddit
  • Slashdot
  • StumbleUpon
  • TwitThis
Categories: .NET | ASP.NET | IIS 7

Anonymous Types are Internal, C# 4.0 Dynamic Beware!

by ashic 26. May 2010 14:10

C# 4.0 has introduced the dynamic keyword. You can declare a variable as dynamic and regardless of what can be inferred at compile time, you can access any properties and call any methods and your code will still compile. Resolving of those properties and methods will be done at runtime. If at runtime, they aren't found, you'd get a runtime exception. If they are found, your code will run fine.

An Example

Consider a class called Person that has a property called Name. Consider the following piece of code:



Person p = new Person { Name = "John" };
object o = p;
dynamic d = o;
Console.WriteLine(d.Name);

On line 2, we're assigning p to an object reference o. Before C# 4.0, if you wanted to get the value of o's Name (and it does have a Name, since we know we assigned a Person object to o) you would have had to resort to reflection. With C# 4.0 however, we can use a dynamic variable like we did on line 3, and simply access the Name property on the dynamic variable. This program would compile fine, but it would require that the object assigned to d must (at runtime) have a property called Name. If it doesn't, it'll throw an exception.

So basically, we can assign a dynamic variable with any object and call properties and methods we expect it to have and it should run fine. Right? Not quite.

The Problem

The dynamic approach has one limitation – it must know the type of object it's dealing with. This isn't a problem for most cases, but it can be an issue when dealing with anonymous types. Let's get started building the project so you can see what I mean.

The Project

Create a basic .Net 4.0 console application called DynamicTest. Add a class library called ClassLibrary1 to the solution. Make class1.cs look like this:



namespace ClassLibrary1
{
    public class Person
    {
        public string Name { get; set; }
        public int Age { get; set; }
    } 

    public class Repository
    {
        Person _person = new Person { Name = "Adam", Age = 21 };
       
        public object GetPerson()
        {
            return _person;
        }

        public object GetPersonWrappedInAnonymousType()
        {
            return new { Person = _person };
        }
    }
}

We have a repository that has two methods. One simply returns a person as an object while the other wraps it in an anonymous type and returns an instance of that anonymous type. Going back to the main project's Program.cs, add the following code:



class Program
{
    static void Main(string[] args)
    {
        new Program().Run();
    }    

    private void Run()
    {
        var rep = new Repository();

        dynamic data = rep.GetPerson();
        Console.WriteLine(data.Name); 

        dynamic data2 = rep.GetPersonWrappedInAnonymousType();
        Console.WriteLine(data2.Person.Name);
    }
}



That looks simple enough. data and data2 are both dynamic variables. As such, their Name and Person.Name properties will be resolved at runtime. The program compiles fine, but when we run it, we get this:

Adam

Unhandled Exception: Microsoft.CSharp.RuntimeBinder.RuntimeBinderException: 'obj
ect' does not contain a definition for 'Person'
   at CallSite.Target(Closure , CallSite , Object )
   at System.Dynamic.UpdateDelegates.UpdateAndExecute1[T0,TRet](CallSite site, T
0 arg0)
   at DynamicTest.Program.Run() in F:\MyTests\C#\DynamicTest\DynamicTest\Program
.cs:line 24
   at DynamicTest.Program.Main(String[] args) in F:\MyTests\C#\DynamicTest\Dynam
icTest\Program.cs:line 13
Press any key to continue . . .

How come? The call to data.Name resolved fine, but the call to data2.Person failed. The error message states that the Person property could not be found at runtime. We can obviously see by looking at the code that data2 most definitely has a Person property and that in turn has a Name property. [And if you don't trust your eyes, you can run in debug mode, set a breakpoint just after data2 is initialized and if you use the watch window to view the properties of data2, you'll see that data2 does indeed have a data2.Person property but if you type in "data2.Person" in the watch window, you'll get the same exception…magic!]. So what gives?

Explanation

The reason the call to data2.Person fails is that the type information of data2 is not available at runtime. The reason it's not available is because anonymous types are not public. When the method is returning an instance of that anonymous type, it's returning a System.Object which references an instance of an anonymous type -  a type who's info isn't available to the main program. The dynamic runtime tries to find a property called Person on the object, but can't resolve it from the type information it has. As such, it throws an exception. The call to data.Name works fine since Person is a public class, that information is available and can be easily resolved.

This can affect you in any of the following cases (if not more):

1. You're returning a non-public, non-internal type using System.Object.
2. You're returning a non-public, non-internal derived type via a public Base type and accessing a property in the derived type that's not in the base type.
3. You're returning anything wrapped inside an anonymous type from a different assembly.

i.e. you need to be able to access the type info for the object at the point where its property / method is being resolved at runtime.

Solution

The solution is actually quite simple. All we have to do is open up AssemplyInfo.cs of the ClassLibrary1 project and add the following line to it:


[assembly:InternalsVisibleTo("DynamicTest")]


What this does is allow the main project to "look into" the internal types of ClassLibrary1. This means the dynamic runtime can find the type information of the anonymous type being returned and data2.Person.Name resolves perfectly. Anonymous types are internal. Adding that piece of code makes the type information "visible" to our main project.

Drawback

As you can see, the solution means you actually have to have control over the class library's source code for this to work. If you don't, then I guess you'd need to resort to good old fashioned reflection.

Application

This approach can come in handy for unit testing. You would expose the internal types of the assembly under test to the tests, and as such should be able to take advantage of the dynamic functionality of C# 4.0 when evaluating results from methods that return anonymous types.

Source

download

Shout it kick it on DotNetKicks.com

---------------------------------------------------------

Questions  and comments relating to this article are welcome. Comments completely unrelated to the article and posted with the sole intention of putting your link here are not.

If you spam, your comment will not be approved, will be deleted and your IP blocked. I maintain my site almost daily and such comments – even if they pass the spam filter – will get removed as soon as possible. If this gets too tedious, I may disable comments entirely. Please don't ruin it for everybody else.

---------------------------------------------------------

Share or Bookmark this post…
  • Facebook
  • DotNetKicks
  • Digg
  • LinkedIn
  • Technorati
  • del.icio.us
  • Google
  • Live
  • Tumblr
  • msdn Social
  • Ping.fm
  • Reddit
  • Slashdot
  • StumbleUpon
  • TwitThis
Tags: , ,
Categories: C# | .NET

ASP.NET MVC – Unit Testing JsonResult Returning Anonymous Types

by ashic 25. May 2010 15:41

Unit testing of ASP.NET MVC JsonResults can be a source of confusion. The problem arises from the fact that an Action Method itself doesn't produce any html / json / string output – it simply returns an Action Result. ASP.NET MVC then calls the ExecuteResult() method on that Action Result. The ExecuteResult() method is what causes output to be written to the Response stream. Let's take the following controller and action method for example:



public class HomeController : Controller
{
    public ActionResult Index()
    {
        var people = new List<Person>
        {
             new Person{ Name = "Adam", Age=21},
             new Person{ Name = "Eve", Age=20},
             new Person{ Name = "Joe", Age=50}
        }; 

        return Json(new
        {
            People = people,
            Count = people.Count,
            Success = true
        }, JsonRequestBehavior.AllowGet);
    }
}

Here's the (very simple) Person class:



public class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
}

As you can see, the Index method is simply returning an anonymous type which holds a list of people, the count, indicates success and wraps the whole thing in a JsonResult. When the Index() action method executes (i.e. is called from the browser), it produces the following json:

{"People":[{"Name":"Adam","Age":21},{"Name":"Eve","Age":20},{"Name":"Joe","Age":50}],"Count":3,"Success":true}

Nothing new there. Now let's say we wish to unit test this method [I know, I know…in a real app, the list of people would come from a service or repository or whatever…but for unit tests, you'd have mock whatevers or stub whatevers and would know what the data being returned would be]. We naively write something like this:



[TestMethod]
public void IndexTestWhichShouldFail()
{
    //arrange
    HomeController controller = new HomeController();

    //act
    var result = controller.Index() as JsonResult;

    //assert
    Assert.AreEqual(@"{""People"":[{""Name"":""Adam"",""Age"":21},{""Name"":""Eve"",""Age"":20},{""Name"":""Joe"",""Age"":50}],""Count"":3,""Success"":true}",
        result.Data.ToString());
}

We run the test, and it fails miserably:

Assert.AreEqual failed. Expected:<{"People":[{"Name":"Adam","Age":21},{"Name":"Eve","Age":20},{"Name":"Joe","Age":50}],"Count":3,"Success":true}>. Actual:<{ People = System.Collections.Generic.List`1[MvcApplication1.Models.Person], Count = 3, Success = True }>.

What went wrong? The reason that the test failed was because we were comparing apples to oranges. We expected a json string of results, but we were passing the ToString() value of the JsonResult's Data property. But shouldn't Data.ToString() simply give us the json string? Erm…no. The Data property simply holds the object passed into the Json() method in Index(). As such, it's simply an instance of an anonymous type. Calling ToString() on it does not cause serialization to json…it simply tells .NET to convert it to a string. The Actual value of the test result above shows what that looks like (i.e. instead of the People list getting serialzed to json, we get an ugly List`1 of Person). So how come it gives us proper json when running from a browser? The reason is that ASP.NET MVC takes the JsonResult and calls its ExecuteResult() method, which (among other things) writes the json to the Response.

So how do we unit test that?

Well, if we were returning non-anonymous types, then we could simply cast JsonResult.Data to the expected type and check its properties. But we're returning an anonymous type, aren't we?

So how do we unit test that?

Well, there're three approaches we can take.

Approach 1: The Easy Way

We know that we have the object and we know what the json for it should be. We can just serialize the object and see if it matches what we expect. Here's the test:



[TestMethod()]
public void IndexTest()
{
    //arrange
    HomeController controller = new HomeController(); 
   
    //act
    var result = controller.Index() as JsonResult;
    var serializer = new JavaScriptSerializer();
    var output = serializer.Serialize(result.Data);

    //assert
    Assert.AreEqual(@"{""People"":[{""Name"":""Adam"",""Age"":21},{""Name"":""Eve"",""Age"":20},{""Name"":""Joe"",""Age"":50}],""Count"":3,""Success"":true}",
        output);
}

Nothing complicated there. It's basically doing the same thing as the ExecuteResult() method of JsonResult does (in terms of producing the Json string via the JavaScriptSerializer). Now some of you may argue that this is not a unit test as it tests serialization as well as the method under test (i.e. Index). I would disagree. The reason being that you should only be testing your code. Testing JavaScriptSerializer should not be your focus when you're testing the Index method. And since checking the serialzed result is easier for you (i.e. you know what the json should be), you may as well check against that.

Approach 2: The "Pure" Way

This approach executes pretty much what the MVC framework does to produce output. For this, we're going to use Moq to fake a controller context and response and capture the content written to Response during execution in a StringBuilder. We will execute the result with the fake context, which will fill our StringBuilder with the serialized output. We will then check to see if it is what we want it to be. Here's the test:



[TestMethod()]
public void IndexTestWithMock()
{
    //arrange
    HomeController controller = new HomeController();
    var result = controller.Index();
    var sb = new StringBuilder();
    Mock<HttpResponseBase> response = new Mock<HttpResponseBase>();
    response.Setup(x => x.Write(It.IsAny<string>())).Callback<string>(y =>
    {
        sb.Append(y);
    });
    Mock<ControllerContext> controllerContext = new Mock<ControllerContext>();
    controllerContext.Setup(x => x.HttpContext.Response).Returns(response.Object);

    //act
    result.ExecuteResult(controllerContext.Object);

    //assert
    Assert.AreEqual(@"{""People"":[{""Name"":""Adam"",""Age"":21},{""Name"":""Eve"",""Age"":20},{""Name"":""Joe"",""Age"":50}],""Count"":3,""Success"":true}",
        sb.ToString());
}

Notice that our "act" of the test is not when we call controller.Index(), rather when we call ExecuteResult() on the JsonResult. In the previous approach, we simply called into the controller's action method. In this approach, we are actually doing what MVC does when dealing with a Request (at least, parts of it). To make it play the way we want it to, we created Mocks that behave the way we want them to. [Of course, the "arrange", "act" and "assert" parts are just nomenclature and one could say that the "act" is when Index() is called and everything afterwards is part of "assert"…]

Approach 3: Reflection

The previous two approaches compared the generated json against the expected json. As I said, that might upset some people as it kind of tests serialization too. I'm fine with it, but if someone wanted to test the JsonResult itself and not the generated output, one approach they can take is reflection. Here's a test that does just that:



[TestMethod]
public void IndexTestWithReflection()
{
    //arrange
    HomeController controller = new HomeController();

    //act
    var result = controller.Index() as JsonResult;

    //assert
    var data = result.Data;
    var type = data.GetType();
    var countPropertyInfo = type.GetProperty("Count");
    var expectedCount = countPropertyInfo.GetValue(data, null);

    Assert.AreEqual(3, expectedCount);
}

Since result.Data is an instance of an anonymous type, you can't really cast it to anything useful. You can check the values of result.Data via reflection. It can be a bit messy, but you could write a utility library to get to the properties in an easier fashion. There is one problem though…you're checking for strings (i.e. the property "Count") and as such, refactorings like renaming of a property will cause your tests to fail.

Approach 4: Dynamic

One way to avoid reflection would be to use .Net 4.0's dynamic capabilities. This does, however, require a bit of tinkering. We'll need to open up AssemblyInfo.cs of the MVC application and add this to the bottom:


[assembly:InternalsVisibleTo("TestProject1")]

Why we need to do this is explained in this article.

Once that's done, we can write our test like this:



[TestMethod]
public void IndexTestWithDynamic()
{
    //arrange
    HomeController controller = new HomeController();    

    //act
    var result = controller.Index() as JsonResult;    

    //assert
    dynamic data = result.Data; 

    Assert.AreEqual(3, data.Count);
    Assert.IsTrue(data.Success);
    Assert.AreEqual("Adam", data.People[0].Name);
}

Note how we're avoiding reflection, but still are testing the data that went into the JsonResult as opposed to the json string gotten from executing the JsonResult.


All three of these approaches will work. Pick whichever one suits your ideologies.

And yes, I'm aware that the Index() method would be better off returning a PagedList<Person>, and that Success=true is for the most part redundant (if there was an error, it should throw an exception and the calling javascript code should be able to understand that there was an error). The Index() method is for illustrative purposes only ;)

Update

I've added a fourth approach (i.e. dynamic). The source code download below contains the update.

Code

Click here to download the source code.

Shout it kick it on DotNetKicks.com

---------------------------------------------------------

Questions  and comments relating to this article are welcome. Comments completely unrelated to the article and posted with the sole intention of putting your link here are not.

If you spam, your comment will not be approved, will be deleted and your IP blocked. I maintain my site almost daily and such comments – even if they pass the spam filter – will get removed as soon as possible. If this gets too tedious, I may disable comments entirely. Please don't ruin it for everybody else.

---------------------------------------------------------

Share or Bookmark this post…
  • Facebook
  • DotNetKicks
  • Digg
  • LinkedIn
  • Technorati
  • del.icio.us
  • Google
  • Live
  • Tumblr
  • msdn Social
  • Ping.fm
  • Reddit
  • Slashdot
  • StumbleUpon
  • TwitThis
Categories: .NET | ASP.NET | ASP.NET MVC

Fetching a Property Value via Reflection

by ashic 10. May 2010 21:31

Reflection is a method by which we can use metadata at runtime to dynamically create an instance of a type, bind the type to an existing object, or get the type from an existing object and invoke its methods or access its fields and properties. It has many uses, ranging from allowing usage of attributes to creating instances of dynamically generated classes and using them. You can even use reflection to read and modify private member variables of an object. Today, we're going to look at a very simple example which will allow us to read the value of a public property given an instance.

The Class

Let's take a very simple class:



namespace MyNamespace
{
    public class MyClass
    {
        public int MyProperty { get; set; }
    }
}

Note, that I've put it in a separate namespace to show that it's easy to deal with this when using reflection.

The Runner

We'll run everything in a very simple console application:



public class Program
{
    static void Main(string[] args)
    {
        new Program().Run();
    }

    private void Run()
    {
        MyClass o = new MyClass { MyProperty = 25 };
        object value = GetPropertyValue(o, "MyProperty");
        Console.WriteLine(value);
    }
}

We're basically creating an instance of MyClass and passing the instance and the string "MyProperty" to a method called GetProperty. The method is returning an object (which we could have cast to anything we needed). We're passing the returned object to Console.WriteLine.

The Method

This is where we use reflection to find the value of the "MyProperty" property given an instance. Add the following method to the class:



private object GetPropertyValue(object o, string propertyName)
{
    Type type = o.GetType();
    PropertyInfo info = type.GetProperty(propertyName);
    object value = info.GetValue(o, null);
    return value;
}

We're getting the type information by calling GetType() on the object. Next, we call GetProperty() on the type information. Note, this only gives us information about the property and not the property itself. We then have to call GetValue() on the property information passing in the instance from which to get the actual value.

And that's all it takes :)

PS: You'll need a reference to System.Reflection to use Reflection.

PPS: Yes, this is a very very simple example of using reflection, but I found quite a few questions over at the forums today that would be answered by this. We all have to start somewhere, right?

Share or Bookmark this post…
  • Facebook
  • DotNetKicks
  • Digg
  • LinkedIn
  • Technorati
  • del.icio.us
  • Google
  • Live
  • Tumblr
  • msdn Social
  • Ping.fm
  • Reddit
  • Slashdot
  • StumbleUpon
  • TwitThis
Categories: .NET | ASP.NET | C#

Encrypted Hidden Redux : Let's Get Salty

by ashic 13. March 2010 09:37

This article builds on the ideas presented in my earlier article http://www.heartysoft.com/post/2010/02/25/Encrypted-Hidden-Inputs-in-ASPNET-MVC.aspx

If you haven't read that yet, I'd recommend doing so before proceeding.

Problems with the Previous Approach

The approach outlined in the previous article is pretty secure and easy to use. However, there are a few issues that can be improved upon:

  1. Security: The approach is using a symmetric encryption process with a fixed private key. Asymmetric encryption would no doubt provide more security, but the performance costs and hassle is probably not worth it. The problem with symmetric encryption is that brute force attacks can potentially break it. As such, using only a fixed encryption key could be a security issue. Given enough time and test pages, the encrypted values could lead to the leaking of the private key. This would result in the whole process being compromised. A couple of people provided feedback on this and were concerned. Still, such brute force attacks could take hundreds of years as the attacker would be required to test at least 2^56 keys. That's not an easy task, but we can use some salting techniques to make it even harder (a lot harder) to break. This would still be within the realms of symmetric encryption but would provide (a LOT of) added security. I'll explain the salting technique later.
  2. CSRFs: With the previous approach, an attacker could take the encrypted value and submit it as part of another request from his browser. The system would decrypt and use the value as normal. The previous process expected that the developer would use ASP.NET MVC's AntiForgeryToken to prevent CSRFs. In other words, the process did not prevent CSRFs on its own, but expected the developer to handle them by other means. With the new process outlined in this article, this will no longer be necessary.
  3. Encryption and Hashing method: The previous process used Triple DES for encryption and MD5 for hashing. The new process uses Rijndael encryption and SHA256 hashing. Rijndael is approved by the US government and is natively supported by the .Net framework. If the encryption and decryption are both going to be done on Windows boxes, then Rijndael is definitely the encryption algo to use over TDES. Similarly. SHA256 has a managed implementation and is superior to MD5.
  4. Code Smell: The previous implementation kind of had some code smells. Stuff was there where it shouldn't have been and it just didn't feel right. I reworked the design and although all smells aren't gone (hey, it's a demo!), a lot of the stench has been removed.

So What's Our Salt?

A salt is basically some random bytes added into our actual data during the encryption process. Having access to the key and the salt enables complete decryption. Having access to one but not the other doesn't. The salt needs to be random enough so that different users have different ones. We still need the salt to facilitate decryption on the server. A common technique is to store part of the salt at the server and part of it in the html sent to the browser. Other techniques generate the salts using some logic and sends some or none of the salt to the client. The bottom line is that the salt needs to be random so that an attacker can't simply take some values from a legitimate page output and then post it himself. The salt still needs to be available on the server when a legitimate user submits the page. ASP.NET MVC actually has such a per user per session value baked right in. It's the AntiForgeryToken. However there are a couple of issues that prevent us from using it directly:

1. The AntiForgeryToken helper uses some internal classes to generate the token data. As it's internal to MVC, we can't directly access the token data. We need to generate the AntiForgeryToken input tag and parse its value.

2. The AntiForgeryToken value is per user per session. If the developer puts an AntiForgeryToken on the page, then that value will be visible to the attacker. That essentially hands the attacker the salt. This is not acceptable. Luckily, the AntiForgeryToken helper can accept any string as its salt (yes, its own salt – not our final salt). We can pass a fixed string as the salt and that would ensure that using AntiForgeryTokens on the page doesn't interfere with or expose our salt.

As I said, the AntiForgeryToken's value is constant for the same user in the same browser session and the same "salt" parameter passed to the helper. But if the user opens another browser window or another user uses the system, then the value would be completely different. With this approach, we don't need to send any of the salt to the browser as all of it can be generated on the server on page submission. Only the encrypted values are ever sent to the client.

The Project

Since quite a bit of things have changed since the previous approach, I'll start afresh. First thing to do is create an MVC 2 project called "HiddenEncryptDemo".

I installed MVC 2 RC 2 before installing VS 2010 RC. If you have some other combination, then slight changes might be necessary, although the core ideas will still work (even with MVC 1.0).

Create a Models folder and add in a class called Computer:


public class Computer
{
    public string Setting { get; set; }
}

The reason I'm adding this class is to show that our approach will work not just on simple action parameters, but also on more complex types without a need for a separate model binder.

Next, add this to the Index.aspx view (in the /Views/Home folder):


<% Html.BeginForm("Something", "Home", FormMethod.Post); %>
    <%= Html.Hidden("computer.Setting", "hello world!") %>
    <input type="submit" value='submit' />
<% Html.EndForm(); %>

After that, add the following method to the Home controller:


[HttpPost]
public ActionResult Something(Computer computer)
{
    ViewData["Message"] = computer.Setting;
    return View("Index");
}

Nothing fancy, we're just setting the ViewData["Message"] to the computer.Setting value. Notice that in the code we added to Index.aspx, the form submitted to "Something" (the name of our action) and the name of the hidden input was "computer.Setting". The default model binder will find a value for "computer.Something" in the request parameters and upon seeing that it can set the Setting property of the computer parameter, it's going to set computer.Setting to the value it found (in our case, "hello world!"). If you run the project, you should see the index page with a submit button. Clicking the button will result in a post to the server where the Something action will get called. The Something action will then set the ViewData["Message"] and return the Index view. As such, you will see the words "hello world!" displayed on the page:

hello

On the initial Index page (or the page got after clicking the button), if you right click anywhere and select view source, you should see this (among other things):

hidden

Notice how the helper added a hidden input control, set its id to "computer_Setting" and name to "computer.Setting". The name is used as the key of the data in the post variables. Also notice how the value is "hello world!". The value is clearly visible to anyone looking at the source. Our goal is to come up with a way to hide that value from clear sight while at the same time ensuring everything works as smoothly and easily as it just did. A bonus would be if no change was required in the controller's code (i.e. no specific attributes needed on the consuming action or controller, no special model binding needed for the parameter etc.) – it would be unobtrusive. These are the exact requirements of the previous approach, but today, we want to add one more – the encryption should use a different salt for each user's session so that brute force approaches of uncovering the encryption key will fail and CSRFs will be prevented by default.

The Settings Provider

In the previous article, we handled settings from within the encryption provider. This seemed kind of messy and I decided to use a separate settings provider. Add a "Helpers" folder to the project and add the interface ISettingsProvider to it:


public interface ISettingsProvider
{
    byte[] EncryptionKey { get; }
    string EncryptionPrefix { get; }
    string SaltGeneratorKey { get; }
}

Next, add an implementation for the interface. The SettingsProvider class looks like this:


namespace HiddenEncryptDemo.Helpers
{
    using System.Configuration;
    using System.Security.Cryptography;
    using System.Text;
   
    public class SettingsProvider : ISettingsProvider
    {
        private static readonly byte[] _encryptionKey;
        private static readonly string _encryptionPrefix;
        private static readonly string _saltGeneratorKey;

        static SettingsProvider()
        {
            //read settings from configuration
            var useHashingString = ConfigurationManager.AppSettings["UseHashingForEncryption"];
            bool useHashing = true;
            if (string.Compare(useHashingString, "false", true) == 0)
            {
                useHashing = false;
            }

            _encryptionPrefix = ConfigurationManager.AppSettings["EncryptionPrefix"];
            if (string.IsNullOrWhiteSpace(_encryptionPrefix))
            {
                _encryptionPrefix = "encryptedHidden_";
            }

            _saltGeneratorKey = ConfigurationManager.AppSettings["EncryptionSaltGeneratorKey"];
            if (string.IsNullOrWhiteSpace(_saltGeneratorKey))
            {
                _saltGeneratorKey = "encryptionSaltKey";
            }

            var key = ConfigurationManager.AppSettings["EncryptionKey"];

            if (useHashing)
            {
                var hash = new SHA256Managed();
                _encryptionKey = hash.ComputeHash(UTF8Encoding.UTF8.GetBytes(key));
                hash.Clear();
                hash.Dispose();
            }
            else
            {
                _encryptionKey = UTF8Encoding.UTF8.GetBytes(key);
            }
        }

        #region ISettingsProvider Members

        public byte[] EncryptionKey
        {
            get { return _encryptionKey; }
        }

        public string EncryptionPrefix
        {
            get { return _encryptionPrefix; }
        }

        public string SaltGeneratorKey
        {
            get { return _saltGeneratorKey; }
        }

        #endregion
    }
}

Basically, we're just reading in some settings from the web.config file in the static constructor and returning the static values from the instance based getters. Notice that if UseHashingForEncryption setting is set, we first hash the encryption key in web.config using SHA256Managed before using it. The EncryptionPrefix is a prefix used to identify hidden inputs which were previously encrypted. The SaltGeneratorKey is a key used in getting the AntiForgeryToken.

The Encryption Provider

Add an interface called IEncryptString:


public interface IEncryptString : IDisposable
{
    string Encrypt(string value);
    string Decrypt(string value);
}

And an implementation called RijndaelStringEncrypter:


namespace HiddenEncryptDemo.Helpers
{
    using System;
    using System.Security.Cryptography;
    using System.Text;

    public class RijndaelStringEncrypter : IEncryptString
    {
        private RijndaelManaged _encryptionProvider;
        private ICryptoTransform _encrypter;
        private ICryptoTransform _decrypter;
        private byte[] _key;
        private byte[] _iv;

        public RijndaelStringEncrypter(ISettingsProvider settings, string salt)
        {
            _encryptionProvider = new RijndaelManaged();
            var saltBytes = UTF8Encoding.UTF8.GetBytes(salt);
            var derivedbytes = new Rfc2898DeriveBytes(settings.EncryptionKey, saltBytes, 3);
            _key = derivedbytes.GetBytes(_encryptionProvider.KeySize / 8);
            _iv = derivedbytes.GetBytes(_encryptionProvider.BlockSize / 8);
        }

        #region IEncryptString Members

        public string Encrypt(string value)
        {
            var valueBytes = UTF8Encoding.UTF8.GetBytes(value);

            if (_encrypter == null)
            {
                _encrypter = _encryptionProvider.CreateEncryptor(_key, _iv);
            }

            var encryptedBytes = _encrypter.TransformFinalBlock(valueBytes, 0, valueBytes.Length);
            var encrypted = Convert.ToBase64String(encryptedBytes);

            return encrypted;
        }

        public string Decrypt(string value)
        {
            var valueBytes = Convert.FromBase64String(value);

            if (_decrypter == null)
            {
                _decrypter = _encryptionProvider.CreateDecryptor(_key, _iv);
            }

            var decryptedBytes = _decrypter.TransformFinalBlock(valueBytes, 0, valueBytes.Length);
            var decrypted = UTF8Encoding.UTF8.GetString(decryptedBytes);

            return decrypted;
        }

        #endregion

        #region IDisposable Members

        public void Dispose()
        {
            if (_encrypter != null)
            {
                _encrypter.Dispose();
                _encrypter = null;
            }

            if (_decrypter != null)
            {
                _decrypter.Dispose();
                _decrypter = null;
            }

            if (_encryptionProvider != null)
            {
                _encryptionProvider.Clear();
                _encryptionProvider.Dispose();
                _encryptionProvider = null;
            }
        }

        #endregion
    }
}

This is a sginificant change from our previous encryption method. Previously, we created a single pair of ICryptoTransforms for encryption and decryption based on the encryption key. Since we need to provide different salts for encryption for different users and different sessions, that approach will no longer work. In the new approach, each instance of the RijndaelStringEncrypter has its own salt value. As a consequence, each instance needs its own pair of ICryptoTransforms. Looking at the constructor code:


public RijndaelStringEncrypter(ISettingsProvider settings, string salt)
{
    _encryptionProvider = new RijndaelManaged();
    var saltBytes = UTF8Encoding.UTF8.GetBytes(salt);
    var derivedbytes = new Rfc2898DeriveBytes(settings.EncryptionKey, saltBytes, 3);
    _key = derivedbytes.GetBytes(_encryptionProvider.KeySize / 8);
    _iv = derivedbytes.GetBytes(_encryptionProvider.BlockSize / 8);
}

we see that we're storing the key and iv bytes as instance variables. Please note that simply getting the bytes for the salt string using UTF8Encoding.UTF8.GetBytes(salt) is not good enough. The values generated by AntiForgeryToken - while different for each user and session – can be very similar. As such, if you're encrypting a small word (for example "hello") and use the encoding derived bytes, the first few bytes may be so similar that the encrypted output of "hello" might appear the same in the html output for different users / sessions. As such, we're using Rfc2898DerivedBytes to ensure that only the exact salt and encryption key produce the same iv bytes. Another thing to notice is that we're using the encryption key itself as the password passed to the Rfc2898DerivedBytes constructor. This essentially means that the encryption key and the passed in salt string is used to derive the actual iv (salt) used for encryption.

The Html Helper

Add a file called InputExtensions.cs and add the following code to it:


namespace HiddenEncryptDemo.Helpers
{
    using System.Web.Mvc;
    using System.Web.Mvc.Html;
    using System.Text.RegularExpressions;

    public static class InputExtensions
    {
        public static HeartysoftHtmlHelper Heartysoft(this HtmlHelper helper)
        {
            return new HeartysoftHtmlHelper(helper);
        }
    }

    public partial class HeartysoftHtmlHelper
    {
        private readonly HtmlHelper _helper;
        private readonly ISettingsProvider _settings;

        private static Regex _valueExtractorRegex = new Regex(".*value=\"(.+)\"/*", RegexOptions.Compiled);

        public HeartysoftHtmlHelper(HtmlHelper helper)
            : this(helper, new SettingsProvider())
        {
        }

        public HeartysoftHtmlHelper(HtmlHelper helper, ISettingsProvider settings)
        {
            _helper = helper;
            _settings = settings;
        }

        public string GetAntiForgeryToken(string salt)
        {
            var input = _helper.AntiForgeryToken(salt).ToString();
            var match = _valueExtractorRegex.Match(input);
            return match.Groups[1].Value;
        }

        public MvcHtmlString EncryptedHidden(string name, object value)
        {
            if (value == null)
            {
                value = string.Empty;
            }

            var strValue = value.ToString();
            string salt = GetAntiForgeryToken(_settings.SaltGeneratorKey);

            var encrypter = new RijndaelStringEncrypter(_settings, salt);
            var encryptedValue = encrypter.Encrypt(strValue);
            encrypter.Dispose();

            var encodedValue = _helper.Encode(encryptedValue);
            var newName = string.Concat(_settings.EncryptionPrefix, name);

            return _helper.Hidden(newName, encodedValue);
        }
    }
}

Let's dissect the helper a bit. There are basically two important methods. The first is GetAntiForgeryToken:


public string GetAntiForgeryToken(string salt)
{
    var input = _helper.AntiForgeryToken(salt).ToString();
    var match = _valueExtractorRegex.Match(input);
    return match.Groups[1].Value;
}

This get's the anti forgery token string given a specific salt. As I mentioned before, the AntiForgeryToken helper gets its value from a class internal to ASP.NET MVC and thus we can't get the value directly. To work around this, I'm using the helper to het the html of the anti forgery token hidden input and using a Regex to parse its value. The other interesting method is the EncryptedHidden function:


public MvcHtmlString EncryptedHidden(string name, object value)
{
    if (value == null)
    {
        value = string.Empty;
    }

    var strValue = value.ToString();
    string salt = GetAntiForgeryToken(_settings.SaltGeneratorKey);

    var encrypter = new RijndaelStringEncrypter(_settings, salt);
    var encryptedValue = encrypter.Encrypt(strValue);
    encrypter.Dispose();

    var encodedValue = _helper.Encode(encryptedValue);
    var newName = string.Concat(_settings.EncryptionPrefix, name);

    return _helper.Hidden(newName, encodedValue);
}

This method uses the previous one to get the salt, creates the encrypter and encrypts the input. It then uses the regular Hidden() helper to return the <input> tag with the value set to the encrypted data.

Configuration

Go to web.config and ensure that under <configuration>, the following entries are present:


<appSettings>
    <add key="EncryptionKey" value="asdjahsdkhaksj dkashdkhak sdhkahsdkha kjsdhkasd"/>
</appSettings>

The View

At this point, go to Index.aspx and change the html of the form to:


<% Html.BeginForm("Something", "Home", FormMethod.Post); %>
    <%= Html.Heartysoft().EncryptedHidden("computer.Setting", "hello world!") %>
    <%--<%= Html.Hidden("computer.Setting", "hello world!") %>--%>
    <input type="submit" value='submit' />
<% Html.EndForm(); %>

I've just commented out the old helper and used our new helper. If you run the page and view the source, you should see this:


<input id="encryptedHidden_computer_Setting" name="encryptedHidden_computer.Setting" type="hidden" value="GlafeVH/jrfim7gXfqhSHg==" />

If you refresh the page and view source, you should see this:


<input id="encryptedHidden_computer_Setting" name="encryptedHidden_computer.Setting" type="hidden" value="GlafeVH/jrfim7gXfqhSHg==" />

i.e. the value of the hidden input has not changed. If you open the same page in a different browser or close and open the browser or open the same page in a new window (not tab as sessions are shared across tabs), then you should see this:


<input id="encryptedHidden_computer_Setting" name="encryptedHidden_computer.Setting" type="hidden" value="IM94hlaZP4HmqOhxw7Ewyg==" />

Notice how the value is different from the previous value. If you kept the previous browser open, then refreshing the page and viewing source, you should see this:


<input id="encryptedHidden_computer_Setting" name="encryptedHidden_computer.Setting" type="hidden" value="GlafeVH/jrfim7gXfqhSHg==" />

In other words, each session is getting a unique value that's different from the value got in a different session. As such, we can say that the encryption is using a per session salt which makes brute force attacks that much harder.

Please note that since the salt is per session, the exact values of the hidden inputs will vary greatly from the ones shown here. What's important is that in the same session, the value is the same, but the value differs from session to session.

The Controller Factory

We now need to actually decrypt values when the page is posted back. We do this via a custom ControllerFactory. Our controller factory will look for any encrypted input parameters, decrypt them and add them to RouteData using their original names so that they're available to the model binder. Add a class called DecryptingControllerFactory:


namespace HiddenEncryptDemo.Helpers
{
    using System.Linq;
    using System.Web.Mvc;
    using System.IO; 
   
    public class DecryptingControllerFactory : DefaultControllerFactory
    {
        private ISettingsProvider _settings = new SettingsProvider();

        public override IController CreateController(System.Web.Routing.RequestContext requestContext, string controllerName)
        {
            var parameters = requestContext.HttpContext.Request.Params;
            var encryptedParamKeys = parameters.AllKeys.Where(x => x.StartsWith(_settings.EncryptionPrefix)).ToList(); 

            IEncryptString decrypter = null;

            foreach (var key in encryptedParamKeys)
            {
                if (decrypter == null)
                {
                    decrypter = GetDecrypter(requestContext);
                }

                var oldKey = key.Replace(_settings.EncryptionPrefix, string.Empty);
                var oldValue = decrypter.Decrypt(parameters[key]);
                requestContext.RouteData.Values[oldKey] =  oldValue;
            }

            if (decrypter != null)
            {
                decrypter.Dispose();
            }

            return base.CreateController(requestContext, controllerName);
        }

        private IEncryptString GetDecrypter(System.Web.Routing.RequestContext requestContext)
        {
            var salt = GetCurrentSalt(requestContext);
            var decrypter = new RijndaelStringEncrypter(_settings, salt);
            return decrypter;
        }

        private string GetCurrentSalt(System.Web.Routing.RequestContext requestContext)
        {
            var controllerContext = new ControllerContext();
            controllerContext.RequestContext = requestContext;
            var viewContext = new ViewContext(controllerContext, new WebFormView("dummy"),
                new ViewDataDictionary(), new TempDataDictionary(), TextWriter.Null);

            var helper = new HtmlHelper(viewContext, new ViewPage());
            var afToken = helper.Heartysoft().GetAntiForgeryToken(_settings.SaltGeneratorKey);
            return afToken;
        }
    }
}

The controller factory is quite simple. It first checks the request parameters to see if there are any that have a key with a prefix equal to the EncryptionPrefix provided by the settings provider. If there are, we cycle through each and add an entry into RouteData with the key set to the parameter's key minus the prefix and the value set to the decrypted value. We create the decrypter only if needed so that if there aren't any encrypted hidden inputs, we don't waste any resources. Furthermore, if we do need a decrypter, we create an instance and reuse it for all the encrypted parameters for the request. Since the salt is per session, reusing the decrypter for all the encrypted parameters in the request is safe and reduces resource usage. To create the decrypter, we need the salt value that has to be equal to the salt used for encrypting. This is where the very interesting GetCurrentSalt method comes in:


private string GetCurrentSalt(System.Web.Routing.RequestContext requestContext)
{
    var controllerContext = new ControllerContext();
    controllerContext.RequestContext = requestContext;
    var viewContext = new ViewContext(controllerContext, new WebFormView("dummy"),
        new ViewDataDictionary(), new TempDataDictionary(), TextWriter.Null);

    var helper = new HtmlHelper(viewContext, new ViewPage());
    var afToken = helper.Heartysoft().GetAntiForgeryToken(_settings.SaltGeneratorKey);
    return afToken;
}

What this method is doing is it's faking a view context and controller context, setting the controller context's request context to that of the current request and using the fake view context and controller context to create a new instance of the HtmlHelper class. Then, it calls our previously coded GetAntiForgeryToken helper to get the value of the salt string. This is a very useful technique of creating an instance of the HtmlHelper class and can be used to create helpers outside of view code. Over at the ASP.NET forums, Brad Wilson expressed concerns that passing in TextWriter.Null may cause problems, but I believe this is not so. TextWriter.Null provides a TextWriter instance that can be written to but not read from. I don't think any Html helpers should be reading from the TextWriter – they should only be writing to it. And even if there are such helpers, AntiForgeryToken() and our GetAntiForgeryToken() do not read from the TextWriter, so this is perfectly safe for our purposes.

The end result of all of this is that if there are encrypted hidden inputs, then they are decrypted and the entries with original names (i.e. wothout the prefix) are added to RouteData. This ensures the original entries are available for ModelBinding purposes. The last change you need to do is open up the global.asax.cs file and ensure the Application_Start looks like this:


protected void Application_Start()
{
    AreaRegistration.RegisterAllAreas();
    RegisterRoutes(RouteTable.Routes);
    ControllerBuilder.Current.SetControllerFactory(typeof(DecryptingControllerFactory));
}

Running the Page

We'll need to stop the dev server (if you're using IIS, simply restart it). We need to do this because we made changes to the global.asax.cs page. Right click the dev server's icon in your traybar and click stop:

stop-server

You can now simply hit ctrl + F5 in VS to run the page. Click the button and you should see this:

result

Run the page in another browser and click the button. You should see the same results. Viewing the source, you'll see that while pages processed in the same session always has the same encrypted value, a different session in a different browser produces a different encrypted value. Still, when either value is posted to the server, the decrypting controller factory correctly decrypts the values and we get our original value in RouteData. Neither the encryption key nor the encryption iv is ever passed to the client and thus the process is much more secure than our previous approach.

And that's all there is to enable an unobtrusive, easy to use, secure, reusable and salted hidden inputs in ASP.NET MVC. On top of that, it should perform better since we're using managed Rijndael encryption which does not call out to non-managed resources like TDES does.

Possible Questions

I would recommend you first read the "possible questions" section of the previous article. Other than those:

3. Is the data really secure?
The previous approach was pretty secure. As I mentioned, an attacker would need at least 2^56 attempts. This new approach makes it much much more harder for the attacker to succeed. So, it's even more secure. The previous approach didn't tackle CSRFs but relied on the developer using AntiForgeryToken on the pages and the associated ValidateAntiForgeryToken attribute on the controller action being posted to. This approach handles CSRFs itself.

8. How is the performance?
The performance is pretty good. The controller factory only creates a decrypter when needed and reuses it for decrypting all encrypted parameters. We do, however, need to create an encrypter for each call to Html.Heartyfoft().EncryptedHidden(). A way to negate this would be to use some sort of per request IOC container so that one instance is used for processing a whole request. In my opinion, helpers should not have state, but if you want, implementing that part should be easy.

Source Code

Download

kick it on DotNetKicks.com Shout it

---------------------------------------------------------

Questions  and comments relating to this article are welcome. Comments completely unrelated to the article and posted with the sole intention of putting your link here are not.

If you spam, your comment will not be approved, will be deleted and your IP blocked. I maintain my site almost daily and such comments – even if they pass the spam filter – will get removed as soon as possible. If this gets too tedious, I may disable comments entirely. Please don't ruin it for everybody else.

---------------------------------------------------------

Share or Bookmark this post…
  • Facebook
  • DotNetKicks
  • Digg
  • LinkedIn
  • Technorati
  • del.icio.us
  • Google
  • Live
  • Tumblr
  • msdn Social
  • Ping.fm
  • Reddit
  • Slashdot
  • StumbleUpon
  • TwitThis
Categories: .NET | ASP.NET MVC | ASP.NET | Security

MVC 2 RC 2 Templated Helper Bug and a Potential Solution

by ashic 1. March 2010 13:43

The Problem

ASP.NET MVC 2 introduces templated helpers. They're a convenient and type safe way to render model data. There is a potential problem in the way in which the system processes the lambda expression passed in to the helper. The problem causes overridden properties to be ignored and uses the base class' declaration of the property instead. This can have adverse reactions where the client side validation feature of ASP.NET MVC 2 is used.

An Example

Create a very simple BasePerson class:


public class BasePerson
{
    public virtual string FirstName { get; set; }
}

Create a Person class that derives from the BasePerson class, but adds some data annotations (of course, you could do anything here that you required):


public class Person : BasePerson
{
    [Required(ErrorMessage = "FirstName is required.")]
    public override string FirstName { get; set; }
}

Add a very simple controller:


public class HomeController : Controller
{
    public ActionResult Index()
    {
        var p = new Person();
        return View(p);
    }
}

Create the Index.htm view:


<%@ Page Language="C#" Inherits="System.Web.Mvc.ViewPage<MvcApplication1.Models.BasePerson>" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
    <title>Index</title>
    <script src="/Scripts/MicrosoftAjax.js" type="text/javascript"></script>
    <script src="/Scripts/MicrosoftMvcAjax.js" type="text/javascript"></script>
    <script src="/Scripts/MicrosoftMvcValidation.js" type="text/javascript"></script>
</head>
<body>
    <% Html.EnableClientValidation(); %>
    <% Html.BeginForm(); %>
    <div>
        <%= Html.TextBoxFor(x=>x.FirstName) %>       
        <%= Html.ValidationMessageFor(x=>x.FirstName) %>
        <input type='submit' value='submit' />
    </div>
    <% Html.EndForm(); %>
</body>
</html>

You could have made the ViewPage of type Person, instead of BasePerson, but the error would have still been present.

If you run the project now, the rendered html responsible for client side validation would look like this:


//<![CDATA[ if (!window.mvcClientValidationMetadata) { window.mvcClientValidationMetadata = []; } window.mvcClientValidationMetadata.push({"Fields":
[{"FieldName":"FirstName","ReplaceValidationMessageContents":true,
"ValidationMessageId":"form0_FirstName_validationMessage","ValidationRules":[]}],
"FormId":"form0","ReplaceValidationSummary":false}); //]]>

As you can see, the validation rules are empty and as such, there's no client side validation occurring. To see this, just click the submit button. The form will post back without any problems. That should not be happening if the FirstName property is empty.

The Reason

The cause of the bug is a single line of code in the method FromLambdaExpression in the file ModelMetaData.cs in the source code of MVC 2 RC 2. Here's the relevant portion of code:


MemberExpression memberExpression = (MemberExpression)expression.Body;
string propertyName = memberExpression.Member is PropertyInfo ? memberExpression.Member.Name : null;
Type containerType = memberExpression.Member.DeclaringType;

While this looks pretty normal, the third line is the culprit. The DeclaringType refers to the class that declares a member – it's not necessarily the type of the class that's in the Model of our ViewData. In our example, although we passed in a Person object as the model, the DeclaringType of FirstName will always be BasePerson. The consequence of using the DeclaringType member to get the containerType is that the attribute added to the overriden FirstName of the Person class is totally ignored. Any other attributes on an overridden property will also be ignored.

Note that if you were to use TextBoxFor(model=>model) or TextBox("FirstName"), then this would not be a problem until and unless
somethingFor(x=>x.FirstName)
is encountered.
Also, server side validation isn't affected by the bug as the bug appears to be centred on that specific third line of code.

A Possible Solution

The main reason for the problem is that FromLambdaExpression is not using the runtime type of the container. Using the runtime type can be achieved in a few lines of code. Let's first add a helper method:


private static ParameterExpression GetParameterExpression(Expression expression)
{
    while (expression.NodeType == ExpressionType.MemberAccess)
    {
        expression = ((MemberExpression)expression).Expression;
    }
    if (expression.NodeType == ExpressionType.Parameter)
    {
        return (ParameterExpression)expression;
    }
    return null;
}

I've taken the GetParameterExpression method from here:
http://geekswithblogs.net/EltonStoneman/archive/
2009/11/05/retrieving-nested-properties-from-lambda-expressions.aspx

Credits for that go to Elton Stoneman

The last thing to do is to add a bit of code to retrieve the runtime type of the container:


MemberExpression memberExpression = (MemberExpression) expression.Body;
string propertyName = memberExpression.Member is PropertyInfo ? memberExpression.Member.Name : null;
Type containerType = memberExpression.Member.DeclaringType;

bool isContainerValueType = typeof (TParameter).IsValueType;

if(isContainerValueType || (isContainerValueType == false && container != null))
{
    var parameter = GetParameterExpression(memberExpression.Expression);
    var lambda = Expression.Lambda(memberExpression.Expression, parameter);
    var compiled = lambda.Compile();
    var containerInstance = compiled.DynamicInvoke(container);
    containerType = containerInstance.GetType();
}

Running the page and viewing the source, we should see this:


//<![CDATA[ if (!window.mvcClientValidationMetadata) { window.mvcClientValidationMetadata = []; } window.mvcClientValidationMetadata.push({"Fields":
[{"FieldName":"FirstName","ReplaceValidationMessageContents":true,
"ValidationMessageId":"form0_FirstName_validationMessage",
"ValidationRules":[{"ErrorMessage":"FirstName is required.","ValidationParameters":{},
"ValidationType":"required"}]}],"FormId":"form0","ReplaceValidationSummary":false}); //]]>

As you can see, all the client validation info is now rendered.

Explanation

What we've done here isn't really complicated. We're basically taking the old expression (say x=>x.Dummy.Car.Name), getting a lambda delegate for all but the last part of the old expression (so x=>x.Dummy.Car). We're then invoking the delegate with viewData.Model (which is stored in the container variable by code earlier in the method). This gives us the instance of the entity holding the property to be displayed (so in this case, Model.Dummy.Car where the property to be displayed is Model.Dummy.Car.Name). After that, we call GetType() on the instance to get the runtime type. In our coded example, this gives Person and not BasePerson. As such, the validation script rendered honours the [Required] attribute on the overridden FirstName property of the Person class.

I've also added some checks to handle null values for the Model. This can happen if an invalid string is entered for a bool, for example. In these cases, we fall back to the expression derived type. Some unit tests fail without this check. Initially, I also checked to ensure that parameter was not null, but it seems that's not required as it's handled earlier (and all the unit tests pass without this check).

And yes, this method will work on complex types with deep nesting. Not just on direct members of the Model.

Possible Problem

I can think of one area where this method may cause problems. If you're encapsulating TempData into a viewmodel to be rendered, then this code will cause a read on the TempData, causing it to expire. As such it won't get rendered onto the page. Of course, if you didn't use this approach, then the TempData would vanish immediately after rendering to the page. So why would you want to use TempData for this in the first place? Use ViewModels and ViewData for rendering. Don't use TempData in the View.

Does It Break Anything

After making the changes to the MVC 2 RC 2 source code, all existing 2,281 unit tests of the ASP.NET MVC 2 RC 2 solution still pass. As such, I'm assuming nothing is getting broken.

Does It Solve Anything Else

I don't know. If there're other places that depend on FromLambdaExpression, this should fix them as well. Other than that, can't really say.

kick it on DotNetKicks.com  Shout it

---------------------------------------------------------

Questions  and comments relating to this article are welcome.
Comments completely unrelated to the article and posted with the sole intention of putting your link here are not.

If you spam, your comment will not be approved, will be deleted and your IP blocked. I maintain my site almost daily and such comments – even if they pass the spam filter – will get removed as soon as possible. If this gets too tedious, I may disable comments entirely. Please don't ruin it for everybody else.

---------------------------------------------------------

Share or Bookmark this post…
  • Facebook
  • DotNetKicks
  • Digg
  • LinkedIn
  • Technorati
  • del.icio.us
  • Google
  • Live
  • Tumblr
  • msdn Social
  • Ping.fm
  • Reddit
  • Slashdot
  • StumbleUpon
  • TwitThis
Categories: .NET | ASP.NET MVC | ASP.NET

Encrypted Hidden Inputs in ASP.NET MVC

by ashic 25. February 2010 02:27

I have written an update to this article that shows a better way of doing what is presented here. The core concepts are pretty much the same and I would request you to read this one before the improved version.

There are many situations where we need to persist some data during the user's session. There are a few options in achieving this:

1. Session: Using the Session object will persist the data in server resources. By default, they are persisted to server RAM. Using Session is very simple as to persist, you just go Session["key"] = "value, and to retrieve, var val = Session["key"] as [type]. There are a couple of problems in this approach, however. Firstly, it consumes server resources. If you anticipate large volumes of traffic, you can't store the data in the server's RAM cause it'll simply be too much of a resource hog. Second, if you're using a web farm setup where multiple physical machines (or virtual machines) are hosting your site, then data stored on one machine's RAM will not be available to the other machine. So, if requests from the same user go to different machines, complications are bound to arise. The workaround to this can be using a state server. That also needs setting it up and maintaining, load balancing etc. Furthermore, using a state server would result in a (however small) time delay where state information is persisted and retrieved during requests.

2. Cookies: Information can easily be persisted by using cookies. You can't rely on them though, as many users have them disabled – and they're very easy to disable.

3. Hidden Inputs: Using this method, you store the data as the value of hidden input controls. These values get posted during form submission and you can retrieve them easily. The data is stored in the html sent to the browser and thus does not consume any server resources. There's a slight amount of work required to store the value and retrieve it, but other than that, it's an ideal way to persist session specific data in a scalable way. We are going to explore this method in this article.

I don't discuss ViewState and ControlState here as the discussion is about ASP.NET MVC and not Webforms.

The Problem

Although hidden inputs sound great, there's one major flaw. The user can just go to the browser's "view source" option and actually see what value is persisted. This may not be a problem if you're simply storing a user's colour choice for example, but for sensitive data (for example, some id) that needs to be hidden from prying eyes (of the user and / or eavesdroppers), it can be a major issue.

The Solution

We try to solve the security issue by creating an Html Helper which will encrypt the data to be stored in the hidden input control. When the data is posted back, we need to ensure that consuming the data in the hidden input control is no more difficult than using a plain hidden input. To achieve this, we create a custom controller factory.

The Project

Let's create a default ASP.NET MVC 2 Web Application in VS 2010. Name it "HiddenEncryptDemo".

I installed MVC 2 RC 2 before installing VS 2010 RC. If you have some other combination, slight changes may be necessary.

The first thing I'm going to do is to create a very simple Computer.cs class in the Model folder:


public class Computer
    {
        public string Setting { get; set; }
    }

The reason I'm adding this class is to show that our approach will work not just on simple action parameters, but also on more complex types without a need for a separate model binder.

Next, add this to the Index.aspx view (in the /Views/Home folder):


<% Html.BeginForm("Something", "Home", FormMethod.Post); %>
        <%= Html.Hidden("computer.Setting", "hello world!") %>
        <input type="submit" value='submit' />
<% Html.EndForm(); %>

After that, add the following method to the Home controller:


        [HttpPost]
        public ActionResult Something(Computer computer)
        {
            ViewData["Message"] = computer.Setting;
            return View("Index");
        }

Nothing fancy, we're just setting the ViewData["Message"] to the computer.Setting value. Notice that in the code we added to Index.aspx, the form submitted to "Something" (the name of our action) and the name of the hidden input was "computer.Setting". The default model binder will find a value for "computer.Something" in the request parameters and upon seeing that it can set the Setting property of the computer parameter, it's going to set computer.Setting to the value it found (in our case, "hello world!"). If you run the project, you should see the index page with a submit button. Clicking the button will result in a post to the server where the Something action will get called. The Something action will then set the ViewData["Message"] and return the Index view. As such, you will see the words "hello world!" displayed on the page:

hello

On the initial Index page (or the page got after clicking the button), if you right click anywhere and select view source, you should see this (among other things):

hidden

Notice how the helper added a hidden input control, set its id to "computer_Setting" and name to "computer.Setting". The name is used as the key of the data in the post variables. Also notice how the value is "hello world!". The value is clearly visible to anyone looking at the source. Our goal is to come up with a way to hide that value from clear sight while at the same time ensuring everything works as smoothly and easily as it just did. A bonus would be if no change was required in the controller's code (i.e. no specific attributes needed on the consuming action or controller, no special model binding needed for the parameter etc.) – it would be unobtrusive.

The Encryption Provider

The first thing we'll need is an encrypting mechanism that's easy to use. Create a new folder in the project called "Helpers". [I know, I know, I should structure it better – but this is a demo :) ] Add a new interface called IEncryptString to the folder. The interface is very simple:


public interface IEncryptString
{
    string Encrypt(string value);
    string Decrypt(string value);
    string Prefix { get; }
}

Add an implementation of that interface called ConfigurationBasedEncryptionProvider:


public class ConfigurationBasedStringEncrypter : IEncryptString
{
    private static readonly ICryptoTransform _encrypter;
    private static readonly ICryptoTransform _decrypter;
    private static string _prefix;
    static ConfigurationBasedStringEncrypter()
    {
        //read settings from configuration
        var key = ConfigurationManager.AppSettings["EncryptionKey"];
        var useHashingString = ConfigurationManager.AppSettings["UseHashingForEncryption"];
        bool useHashing = true;
        if (string.Compare(useHashingString, "false", true) == 0)
        {
            useHashing = false;
        }

        var prefix = ConfigurationManager.AppSettings["EncryptionPrefix"];
        if (string.IsNullOrWhiteSpace(prefix))
        {
            _prefix = "encryptedHidden_";
        }
        byte[] keyArray = null;

        if (useHashing)
        {
            var hashmd5 = new MD5CryptoServiceProvider();
            keyArray = hashmd5.ComputeHash(UTF8Encoding.UTF8.GetBytes(key));
            hashmd5.Clear();
        }
        else
        {
            keyArray = UTF8Encoding.UTF8.GetBytes(key);
        }

        TripleDESCryptoServiceProvider tdes = new TripleDESCryptoServiceProvider();
        tdes.Key = keyArray;
        tdes.Mode = CipherMode.ECB;
        tdes.Padding = PaddingMode.PKCS7;

        _encrypter = tdes.CreateEncryptor();
        _decrypter = tdes.CreateDecryptor();

        tdes.Clear();
    }

    #region IEncryptionSettingsProvider Members

    public string Encrypt(string value)
    {
        var bytes = UTF8Encoding.UTF8.GetBytes(value);
        var encryptedBytes = _encrypter.TransformFinalBlock(bytes, 0, bytes.Length);
        var encrypted = Convert.ToBase64String(encryptedBytes);
        return encrypted;
    }

    public string Decrypt(string value)
    {
        var bytes = Convert.FromBase64String(value);
        var decryptedBytes = _decrypter.TransformFinalBlock(bytes, 0, bytes.Length);
        var decrypted = UTF8Encoding.UTF8.GetString(decryptedBytes);
        return decrypted;
    }

    public string Prefix
    {
        get { return _prefix; }
    }

    #endregion

}

You can see that in the class' static constructor, I'm reading in some configuration settings. While this goes against "convention over configuration", extensibility isn't really needed in terms of encryption keys. I though about using the machine key as the encryption key, but considering sever farm environments and other issues that could arise, I just chose to go with an app setting instead. Anyway, you can see that I'm using the configuration data to set up a couple of ICryptoTransforms that will be used for encrypting and decrypting. The class has two instance methods, Encrypt and Decrypt, which do pretty much what you'd expect. I'll explain the prefix part later.

The Html Helper

With the encryption provider out of the way, let's add our Html Helper. Create a new file called InputExtensions.cs in our Helpers folder and add the following code:


namespace HiddenEncryptDemo.Helpers
{
    using System;
    using System.Web.Mvc;
    using System.Web.Mvc.Html;

    public static class InputExtensions
    {
        public static HeartysoftHtmlHelper Heartysoft(this HtmlHelper helper)
        {
            return new HeartysoftHtmlHelper(helper);
        }
    }

    public partial class HeartysoftHtmlHelper
    {
        private readonly HtmlHelper _helper;
        private static readonly IEncryptString _encrypter = new ConfigurationBasedStringEncrypter();

        public HeartysoftHtmlHelper(HtmlHelper helper)
        {
            _helper = helper;
        }
        public MvcHtmlString EncryptedHidden(string name, object value)
        {
            if (value == null)
            {
                value = string.Empty;
            }

            string strValue = value.ToString();
            var encryptedValue = _encrypter.Encrypt(strValue);
            var encodedValue = _helper.Encode(encryptedValue);
            var newName = string.Concat(_encrypter.Prefix, name);

            return _helper.Hidden(newName, encodedValue);
        }
    }
}

Here, I'm firstly creating a "Heartysoft" helper which in turn has an EncryptedHidden method. This approach ensures that my helpers don't conflict with other helpers and in the view, I can do this: Html.Heartysoft().HiddenEncrypt(…). The EncryptedHidden helper is pretty simple. It takes the name and value to be used and encrypts the value. It also adds the prefix to the name. Remember our EncryptionProvider had a Prefix property? Here's where it comes in. So, if you pass in "computer.Name", the hidden input would have a name of "encryptedHidden_computer.Name" and id of "encryptedHidden_computer_Name". Of course, if you specify a different prefix in configuration, that would have been used instead. The prefix is used to determine which inputs need decrypted down the line. At the end of the EncryptedHidden method, we're using the default Html helper's Hidden method to generate the <input> tag.

Configuration

Go to web.config and ensure that under <configuration>, the following entries are present:


<appSettings>
        <add key="EncryptionKey" value="asdjahsdkhaksj dkashdkhak sdhkahsdkha kjsdhkasd"/>
</appSettings>

The View

At this point, go to Index.aspx and change the html of the form to:


<% Html.BeginForm("Something", "Home", FormMethod.Post); %>
       <%= Html.Heartysoft().EncryptedHidden("computer.Setting", "hello world!") %>
       <%--<%= Html.Hidden("computer.Setting", "hello world!") %>--%>
       <input type="submit" value='submit' />
   <% Html.EndForm(); %>

I've just commented out the old helper and used our new helper. If you run the page and click the button, you should see this:

encrypted-res

Notice that the message has disappeared. Viewing the html source, you should see that the html tag now looks like this:


<input id="encryptedHidden_computer_Setting" name="encryptedHidden_computer.Setting" type="hidden" value="TA9p71fDeNMfjlCPGOtK9Q==" />

Notice that although the "hello world!" value has been encrypted, the name has changed to "encryptedHidden_computer.Setting". When the form is posted back after the button click, the input parameter is thus "encryptedHidden_computer.Setting". Since there's no longer a "computer.Setting" input parameter, the model binder doesn't assign the computer parameter's Setting property with any value. The ViewData["Message"] thus stays empty and we se a blank on the result page.

So, our final challenge is to ensure that the model binder finds the original value. This will be done through a custom controller factory.

The Controller Factory

Our controller factory will look for any encrypted input parameters, decrypt them and add them to RouteData using their original names so that they're available to the model binder. Add a class called DecryptingControllerFactory:


public class DecryptingControllerFactory : DefaultControllerFactory
{
    private static IEncryptString _decrypter = new ConfigurationBasedStringEncrypter();

    public override IController CreateController(System.Web.Routing.RequestContext requestContext, string controllerName)
    {
        var parameters = requestContext.HttpContext.Request.Params;
        var encryptedParamKeys = parameters.AllKeys.Where(x => x.StartsWith(_decrypter.Prefix)).ToList();
        foreach (var key in encryptedParamKeys)
        {
            var oldKey = key.Replace(_decrypter.Prefix, string.Empty);
            var oldValue = _decrypter.Decrypt(parameters[key]);
            requestContext.RouteData.Values[oldKey] =  oldValue;
        }

        return base.CreateController(requestContext, controllerName);
    }
}

As you can see, we're identifying encrypted input parameters by checking which keys start with our encryption prefix. For each match, we get the value by decrypting and the original name by removing the prefix. We're not really changing the generated controller in any way – the controller factory just acts as a point in the request-response cycle where we can add parameters that are to be consumed by the model binder.

The last change you need to do is open up the global.asax.cs file and ensure the Application_Start looks like this:


protected void Application_Start()
{
    AreaRegistration.RegisterAllAreas();
    RegisterRoutes(RouteTable.Routes);
    ControllerBuilder.Current.SetControllerFactory(typeof(DecryptingControllerFactory));
}

We're just registering our controller factory on that last line.

Running the Page

We'll need to stop the dev server (if you're using IIS, simply restart it). We need to do this because we made changes to the global.asax.cs page. Right click the dev server's icon in your traybar and click stop:

stop-server

You can now simply hit ctrl + F5 in VS to run the page. Click the button and you should see this:

result

Finally, view the html source in the browser, and you should see that the hidden input's value is encrypted:


<input id="encryptedHidden_computer_Setting" name="encryptedHidden_computer.Setting" type="hidden" value="TA9p71fDeNMfjlCPGOtK9Q==" />

That means that our helper has encrypted the value for us and on postback, the controller factory is decrypting the value and adding it to the input parameters so that the model binder can correctly do its thing.

Possible Questions

1. Phil Haack already has a blog post stating how to add action method parameters here:
http://haacked.com/archive/2010/02/21/manipulating-action-method-parameters.aspx
Why didn't you use that method to add the input parameter?
The method described in that post uses an action filter. There are two downsides to that. First, you'd need to go about and decorating your controllers and actions with some [Decrypt] attribute. That's obtrusive. Second-and-more-important, action filters run after model binding. That would work for simple parameters for action methods, but for complex parameters (i.e. viewmodels) or parameters that need complex model binding, that simply won't work. My approach here adds the input parameter before the model binding stage and thus, the model binders can access the correct values without changing anything.

2. Why didn't you use a ModelBinder attribute for decryption?
That could have worked, but would have required you to use the model binder whenever you intended to use the Html.Heartysoft().EncryptedHidden() helper. That's obtrusive and forgetting to set an attribute could have caused nightmares. Furthermore, your other model binders would not have recognised the values. The approach outlined here will work with any and all existing model binders.

3. Is the data really secure?
The value sent to the browser is encrypted using a key. The key is never sent to the browser. As long as the key is secure, the data is as secure as the encryption is.

4. Your code structure is crappy.
It's a demo…deal with it ;) The code is pretty straight forward and you can use your our design patterns and abstractions to make it more your-framework-friendly.

5. I changed the key but the change isn't "happening". What gives?
The encryption key and whether to use hashing or not settings are read during the static constructor of our ConfigurationBasedEncryptionProvider. If you need to change the values, restart the server. I figured you didn't need to change these at runtime and thus put them in the static constructor so that they are read in only once.

6. Why a ControllerFactory? Why not in Begin_Request in global.asax.cs?
It seems that adding the parameters in Begin_Request doesn't seem to persist further down the line. Meaning, I tried that and the new parameters added seem to disappear. Hence the controller factory. Adding the parameters at that stage seems to allow the model binder access to the parameters without any problems. Perhaps a custom ActionInvoker would also have worked, but would have needed setting on each controller – or in a custom controller factory. If the latter, then why bother with the ActionInvoker when the factory can add the parameters itself?

7. Why do you add the parameters to RouteData and not Request[…] or Request.Params[…] or Request.Form[…] etc.?
Coz those are readonly.

Source Code

Download

kick it on DotNetKicks.com  Shout it

---------------------------------------------------------

Questions  and comments relating to this article are welcome. Comments completely unrelated to the article and posted with the sole intention of putting your link here are not.

If you spam, your comment will not be approved, will be deleted and your IP blocked. I maintain my site almost daily and such comments – even if they pass the spam filter – will get removed as soon as possible. If this gets too tedious, I may disable comments entirely. Please don't ruin it for everybody else.

---------------------------------------------------------

Share or Bookmark this post…
  • Facebook
  • DotNetKicks
  • Digg
  • LinkedIn
  • Technorati
  • del.icio.us
  • Google
  • Live
  • Tumblr
  • msdn Social
  • Ping.fm
  • Reddit
  • Slashdot
  • StumbleUpon
  • TwitThis

Exposing a WCF Service for Ajax and Silverlight

by ashic 22. February 2010 04:44

One of the most attractive features of WCF is that you get to write a service once and expose it to be used by multiple clients using different technologies and protocols. How a WCF service is exposed can be changed simply by changing the configuration file, without needing to recompile the code. In this article, we're going to create a WCF service and expose it to Ajax. We'll test the Ajax service by calling it from ASP.NET Ajax 4.0 and jQuery. We'll then expose the service to Silverlight through binary encoding, which will increase data transfer efficiency. Finally, we'll create a small Silverlight 3.0 application that will call the service.

Note: I'm using VS 2010 RC, ASP.NET Ajax 4.0, jQuery 1.4.2 and Silverlight 3.0. Everything WCF related should work similarly for other versions, but some changes may be necessary. Features specific to a version (for example client templates in ASP.NET Ajax 4.0) will obviously not work in previous versions.

The Service

Open up VS 2010 and create a new project. I'm using the ASP.NET Empty Web Application template. Name the project WCFEndpoints.

NewProject

This gives us an empty ASP.NET application with the following (very simple) web.config:


<?xml version="1.0"?> <!--
  For more information on how to configure your ASP.NET application, please visit
http://go.microsoft.com/fwlink/?LinkId=169433
  --> <configuration>
    <system.web>
        <compilation debug="true" targetFramework="4.0" />
    </system.web> </configuration>

Add a folder called Model to the project and add the following two classes:


using System;

namespace WCFEndpoints.Model
{
    public class Book
    {
        public int BookId { get; set; }
        public string Title { get; set; }
        public string Author { get; set; }
        public decimal Price { get; set; }
        public DateTime PublishDate { get; set; }
    }
}

 


using System;
using System.Collections.Generic;
using System.Linq;

namespace WCFEndpoints.Model
{
    public class BookService
    {
        public IEnumerable<Book> GetBooks()
        {
            yield return new Book { BookId = 1, Title = "LOTR", Author = "Tolkien", Price = 180.00M, PublishDate = new DateTime(1954, 1, 1) };
            yield return new Book { BookId = 2, Title = "C#", Author = "Hejlsberg", Price = 280.00M, PublishDate = new DateTime(2003, 1, 1) };
            yield return new Book { BookId = 3, Title = "DDD", Author = "Evans", Price = 200.00M, PublishDate = new DateTime(2003, 8, 30) };
            yield return new Book { BookId = 4, Title = "Architecture", Author = "Fowler", Price = 250.00M, PublishDate = new DateTime(2002, 11, 15) };
        }

        public IEnumerable<Book> GetBooksPublishedAfter(DateTime date)
        {
            return GetBooks().Where(x => x.PublishDate > date);
        }
    }
}

Note: I'm not exactly following best practices in terms of the model. I just wanted to keep that part simple as it's not the focus of the article.

Right click the WCFEndpoints project (not solution) in the solution explorer and select "Add New Item". From there, add a new "AJAX-enabled WCF Service" called LibraryService.

AddService

 

Open the newly generated LibraryService.svc.cs file and modify it so that it looks like this:


using System;
using System.Collections.Generic;
using System.Linq;
using System.ServiceModel;
using System.ServiceModel.Activation;
using WCFEndpoints.Model;

namespace WCFEndpoints
{
    [ServiceContract(Namespace = "Library")]
    [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
    public class LibraryService
    {
        BookService svc = new BookService();

        [OperationContract]
        public List<Book> GetAllBooks()
        {
            return svc.GetBooks().ToList();
        }

        [OperationContract]
        public List<Book> GetBooksPublishedAfter(DateTime date)
        {
            return svc.GetBooksPublishedAfter(date).ToList();
        }
    }
}

That's all we are going to write for the service. The service is currently usable from an Ajax client and we'll only need to change the configuration in web.config to expose it to Silverlight. Let's take a quick look at the auto generated WCF specific section the VS created for us when adding the service:


<configuration>
    <system.web>
        <compilation debug="true" targetFramework="4.0" />
    </system.web>     <system.serviceModel>
        <behaviors>
            <endpointBehaviors>
                <behavior name="WCFEndpoints.LibraryServiceAspNetAjaxBehavior">
                    <enableWebScript />
                </behavior>
            </endpointBehaviors>
        </behaviors>
        <serviceHostingEnvironment aspNetCompatibilityEnabled="true"
            multipleSiteBindingsEnabled="true" />
        <services>
            <service name="WCFEndpoints.LibraryService">
                <endpoint address="" behaviorConfiguration="WCFEndpoints.LibraryServiceAspNetAjaxBehavior"
                    binding="webHttpBinding" contract="WCFEndpoints.LibraryService" />
            </service>
        </services>
    </system.serviceModel>
</configuration>

The WCF sepcific section is the part inside the <system.serviceModel> tags. The first change we're going to do is to rename "WCFEndpoints.LibraryServiceAspNetAjaxBehavior" to "AjaxBehavior". Just make sure you change both instances. Next, we're going to add a service behaviour and assign it to our service. The service behaviour we're adding will allow us to publish the service metadata via HTTP GET. We will also change the address of the endpoint to "ajax" (I will explain this later).  After these changes, the <system.serviceModel> section will look like this:


<system.serviceModel>
        <behaviors>
            <endpointBehaviors>
                <behavior name="AjaxBehavior">
                    <enableWebScript />
                </behavior>
            </endpointBehaviors>
          <serviceBehaviors>
            <behavior name="metadataGetBehavior">
              <serviceMetadata httpGetEnabled="true"/>
              <serviceDebug includeExceptionDetailInFaults="true"></serviceDebug>
            </behavior>           
          </serviceBehaviors>
        </behaviors>
        <serviceHostingEnvironment aspNetCompatibilityEnabled="true"
            multipleSiteBindingsEnabled="true" />
        <services>
            <service name="WCFEndpoints.LibraryService" behaviorConfiguration="metadataGetBehavior">
                <endpoint address="ajax" behaviorConfiguration="AjaxBehavior"
                    binding="webHttpBinding" contract="WCFEndpoints.LibraryService" />
            </service>
        </services>
    </system.serviceModel>

Save the config file, right click the LibraryService.svc file in solution explorer and select "View in Browser". If everything went ok, you should see something like this:

browserTest

 

Ajax Test 1: ASP.NET Ajax

I'll be using the latest release of ASP.NET Ajax (currently 0911 beta). If you don't have it, download it from http://ajax.codeplex.com/, extract the zip, create a new folder called Scripts in the main project, add a subfolder called MSAjax and paste in everything inside the Scripts folder of the extracted zip to the MSAjax folder. Add a new htm file called Index.htm to the project. I'm using a plain htm file to show the pure client side aspects of ASP.NET Ajax. First, we're going to create a client side template:


<fieldset>
    <legend>All Books</legend>
    <div>
        <ul id='books-template' class='sys-template'>
            <li>{{Title}} - {{Author}}, {{ String.format("${0:n}", Price) }}, {{ PublishDate.format("dd-MMM-yyyy")
                }} </li>
        </ul>
    </div>
</fieldset>

Next, add the following style declaration:


<style type="text/css">
       .sys-template, .hidden
       {
           display: none;
       }
</style>

Add a reference to Start.js:


<script src="/Scripts/MSAjax/Start.js" type="text/javascript"></script>

Add the following javascript in a <script> tag inside the <head>:


Sys.require([Sys.scripts.WebServices, Sys.components.dataView, Sys.scripts.Globalization], function () {
           Sys.create.dataView("#books-template", {
               'dataProvider': '/LibraryService.svc/ajax',
               'fetchOperation': 'GetAllBooks',
               'autoFetch': true
           });
       });

What this piece of code is doing is it's loading the scripts necessary for calling web services, using the dataview and formatting asynchronously and when all are loaded, it's creating a dataview that calls out to our service's GetAllBooks method. When it gets back the result, it uses our template to add a <li> for each Book returned. Notice that for the dataProvider property, I specify '/LibraryService.svc/ajax'. We specified "ajax" as the address for our service's Ajax endpoint in web.config – this is where that comes in. Had we kept it empty, we would have set the dataProvider to '/LibraryService.svc'. Since our endpoint's address is "ajax", we're specifying 'LibraryService.svc/ajax'. The reason we're doing this is that later on we'll add another endpoint that will use binary encoding to be consumed by Silverlight 3.

At this point, if you run the page, you should see something like this:

results1

Look at that – less than 10 lines of html and very little javascript and you're displaying a formatted list of results got from a web service call. And you had to do zero html building in javascript. Splendid :)

Let's take this one step further and add a calendar which would allow the user to select a date and a button that, when clicked, will call the 'GetBooksPublishedAfter' method of our web service and display only the books that were published after the selected date. Add the following html right after the closing </fieldset> tag:


<br />
   <fieldset>
       <legend>Books Published After:</legend>
       <div>
           Please select a date:
           <input type='text' id='txtDate' /><br />
           <input type='button' id='btnFilter' value='fetch books' />
       </div>
       <div>
           <ul id='filtered-books'>
               <li class='hidden'></li>
           </ul>
       </div>
   </fieldset>

We want to attach a calendar, which is part of the Ajax Control Toolkit, to the textbox. In order to do so, we need to ensure that the script loader can load the calendar script on demand and that the css for the calendar is loaded. Add the following two lines right after the registration of Start.js:


<script src="/Scripts/MSAjax/extended/ExtendedControls.js" type="text/javascript"></script>
<link href="/Scripts/MSAjax/extended/Calendar/Calendar.css" rel="stylesheet" type="text/css" />

The Start.js file has logic to load scripts in parallel and on-the-fly as well as data about the core Ajax Library scripts and jQuery. It does not contain data about the toolkit controls. This data is present in ExtendedControls.js. Unfortunately, the script loader currently cannot load css at runtime. It is a feature the ASP.NET Ajax team are looking into. For the time being, we'll need to specify the css file explicitly.

To attach a calendar to the textbox, add the following javascript:


Sys.require([Sys.components.calendar], function () {
           var selectedDate = Date.parseInvariant('1-1-2000', 'd-M-yyyy');
           Sys.create.calendar('#txtDate', {
               'format': 'dd-MM-yyyy',
               'selectedDate': selectedDate,
               'id': 'cal-txtDate'
           });
       });

Finally, we need to bind some code to the button's click handler so that when it's clicked, our web service will be called and the UI updated:


Sys.addHandler('#btnFilter', 'click', function () {
               var date = $find('cal-txtDate').get_selectedDate();
               Sys.create.dataView('#filtered-books', {
                   'dataProvider': '/LibraryService.svc/ajax',
                   'fetchOperation': 'GetBooksPublishedAfter',
                   'fetchParameters': { 'date': date },
                   'itemTemplate': '#books-template',
                   'autoFetch': true
               });
           });

Notice that we're using our previously declared template and passing it in via the itemTemplate parameter. This is quite nice (and DRY) and ensures that we don't have to repeat the template just because the data changes. Since the code is getting slightly complex, here's the whole page for your convenience:


<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title></title>
    <style type="text/css">
        .sys-template, .hidden
        {
            display: none;
        }
    </style>
    <script src="/Scripts/MSAjax/Start.js" type="text/javascript"></script>
    <script src="/Scripts/MSAjax/extended/ExtendedControls.js" type="text/javascript"></script>
    <link href="/Scripts/MSAjax/extended/Calendar/Calendar.css" rel="stylesheet" type="text/css" />
    <script type='text/javascript'>         //initialize the first dataview
        Sys.require([Sys.scripts.WebServices, Sys.components.dataView, Sys.scripts.Globalization], function () {
            Sys.create.dataView("#books-template", {
                'dataProvider': '/LibraryService.svc/ajax',
                'fetchOperation': 'GetAllBooks',
                'autoFetch': true
            });
        });         //initialize the calender and button handler
        Sys.require([Sys.components.calendar], function () {
            var selectedDate = Date.parseInvariant('1-1-2000', 'd-M-yyyy');
            Sys.create.calendar('#txtDate', {
                'format': 'dd-MM-yyyy',
                'selectedDate': selectedDate,
                'id': 'cal-txtDate'
            });             //add the handler, call the service when the button is clicked and update the UI.
            Sys.addHandler('#btnFilter', 'click', function () {
                var date = $find('cal-txtDate').get_selectedDate();
                Sys.create.dataView('#filtered-books', {
                    'dataProvider': '/LibraryService.svc/ajax',
                    'fetchOperation': 'GetBooksPublishedAfter',
                    'fetchParameters': { 'date': date },
                    'itemTemplate': '#books-template',
                    'autoFetch': true
                });
            });
        });     </script>
</head>
<body>
    <fieldset>
        <legend>All Books</legend>
        <div>
            <ul id='books-template' class='sys-template'>
                <li>{{Title}} - {{Author}}, {{ String.format("${0:n}", Price) }}, {{ PublishDate.format("dd-MMM-yyyy")
                    }} </li>
            </ul>
        </div>
    </fieldset>
    <br />
    <fieldset>
        <legend>Books Published After:</legend>
        <div>
            Please select a date:
            <input type='text' id='txtDate' /><br />
            <input type='button' id='btnFilter' value='fetch books' />
        </div>
        <div>
            <ul id='filtered-books'>
                <li class='hidden'></li>
            </ul>
        </div>
    </fieldset>
</body>
</html>

Running the page, you should see the textbox initialized to 1-1-2000. Clicking in the textbox, you should see a nice little popup calendar which lets you select a date. Clicking on the button will show you the results of the filtered query. This should look something like this:

results2

 

Ajax Test 2: jQuery

Add a new page for the jQuery test. Call it Index2.htm. Add the following html:


<fieldset>
       <legend>All Books</legend>
       <div id='all-books'></div>
</fieldset>

We'll also add the script loader in ASP.NET Ajax, so add the following script reference:


<script src="/Scripts/MSAjax/Start.js" type="text/javascript"></script>

Download the latest version of jQuery (currently 1.4.2) from www.jquery.com and add it to the Scripts folder. With that done, add the following javascript:


Sys.loadScripts(['/Scripts/jquery-1.4.2.js'], function () {

           $(function () {
               $.post('/LibraryService.svc/ajax/GetAllBooks', null, function (res) {
                   Sys.require([Sys.scripts.Serialization, Sys.scripts.Globalization], function () {
                       res = Sys.Serialization.JavaScriptSerializer.deserialize(res);
                       var data = res.d; //data is in d
                       var list = $('<ul></ul>');

                       for (var i = 0; i < data.length; i++) {
                           list.append('<li>' + data[i].Title + '- ' + data[i].Author + ', $' + data[i].Price.format('n') + ', ' + data[i].PublishDate.format('dd-MMM-yyyy'));
                       }
                       $('#all-books').empty().html(list);
                   });
               }, 'text');
           });

       });

I'm loading jQuery via the script loader and making a $.post to the web service. I'm using POST as that's what WCF supports out of the box for web service calls. In the callback function, I'm cheating slightly as I'm using the ASP.NET Ajax JavaScriptSerializer to deserialize the response. There are other ways of deserializing the response (check out http://www.west-wind.com/weblog/posts/324917.aspx), but using the ASP.NET Ajax deserializer is so simple and easy, I decided to use that one. Do we need to deserialize all the time? Well, no. You would need it when returning any DateTime object from WCF. JSON doesn't have a native way to express dates. ASP.NET Ajax has a way of serializing dates to strings so that they can be deserialized to a javascript Date object if the serializer being used knows about it. And the Sys.Serialization.JavaScriptSerializer does know about it. I'm also referencing Sys.scripts.Globalization to be able to format the price and publish date information.

Another thing to notice is that in the $.post call, I'm mentioning 'text' as the type of data returned. The reason I'm specifying 'text' and not 'json' is that we're deserializing the response right after getting the response. If we had specified 'json', we would be trying to deserialize a javascript object, which doesn't make sense.

Lastly, after deserializing the result, we're only working with the result's "d" member. The reason for this is that WCF envelopes all returned data into a variable called "d". So, we get our books array in res.d and not res. The "d" envelope is used to prevent some javascript exploits.

If we run the page now, we should see this:

results3

 

For the next part, add the following html after the closing </fieldset> tag:


<fieldset>
       <legend>Filtered Books</legend>
       <div>
           Please select a date:
           <input type='text' id='txtDate' /><br />
           <input type='button' id='btnFilter' value='fetch books' />
       </div>
       <div id='filtered-books></div>
</fieldset>

Add the calender to the textbox just like we did before. Since we're already using jQuery, this becomes even easier. Add the reference to ExtenderControls.js and the Calendar.css:


<script src="/Scripts/MSAjax/extended/ExtendedControls.js" type="text/javascript"></script>
<link href="/Scripts/MSAjax/extended/Calendar/Calendar.css" rel="stylesheet" type="text/css" />

The following javascript will then attach a calendar to the textbox:


Sys.require([Sys.components.calendar], function () {
                var selectedDate = Date.parseInvariant('1-1-2000', 'd-M-yyyy');
                $('#txtDate').calendar({
                    'format': 'dd-MM-yyyy',
                    'selectedDate': selectedDate,
                    'id': 'cal-txtDate'
                });
            });

We now need to add the event handler so that clicking the button will fire the service call and update the UI:


$('#btnFilter').click(function () {
                        var date = $find('cal-txtDate').get_selectedDate();
                        var params = { 'date': date} ;
                        var serializedParams = Sys.Serialization.JavaScriptSerializer.serialize(params);
                        $.ajax({
                            'url': '/LibraryService.svc/ajax/GetBooksPublishedAfter',
                            'data': serializedParams,
                            'type': 'POST',
                            'processData': false,
                            'contentType': 'application/json',
                            'timeout': 10000,
                            'dataType': 'text', //not JSON, we'll process
                            '
success': function (res) {
                                Sys.require([Sys.scripts.Serialization, Sys.scripts.Globalization], function () {
                                    res = Sys.Serialization.JavaScriptSerializer.deserialize(res);
                                    var data = res.d; //data is in d
                                    var list = $('
<ul></ul>');

                                    for (var i = 0; i < data.length; i++) {
                                        list.append('<li>' + data[i].Title + '- ' + data[i].Author + ', $' + data[i].Price.format('n') + ', ' + data[i].PublishDate.format('dd-MMM-yyyy'));
                                    }
                                    $('
#filtered-books').empty().html(list);
                                });
                            },
                            '
error': function () {
                                alert('
An error occurred.');
                            }
                        });
                    });

Again, I'm serializing the input parameters to the web service call using the ASP.NET Ajax serializer. We need to serialize in cases where we're using dates or of we have deep nested javascript objects. Since we're passing in the parameter as a simple string (i.e. the output of serialization), we need to tell WCF that that's actually a JSON string. For this, we need to use $.ajax and not $.post. With $.ajax, we get to specify the contentType as 'application/json'. Again, we're specifying the dataType as 'text' for the same reason as before. The rest should be self explanatory.

Here's the whole page code at a glance:


<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title></title>
    <script src="/Scripts/MSAjax/Start.js" type="text/javascript"></script>
    <script type="text/javascript" src="/Scripts/MSAjax/extended/ExtendedControls.js"></script>
    <link href="/Scripts/MSAjax/extended/Calendar/Calendar.css" rel="stylesheet" type="text/css" />
    <script type='text/javascript'>         Sys.loadScripts(['/Scripts/jquery-1.4.2.js'], function () {
            $(function () {
                $.post('/LibraryService.svc/ajax/GetAllBooks', null, function (res) {
                    Sys.require([Sys.scripts.Serialization, Sys.scripts.Globalization], function () {
                        res = Sys.Serialization.JavaScriptSerializer.deserialize(res);
                        var data = res.d; //data is in d
                        var list = $('<ul></ul>');                         for (var i = 0; i < data.length; i++) {
                            list.append('<li>' + data[i].Title + '- ' + data[i].Author + ', $' + data[i].Price.format('n') + ', ' + data[i].PublishDate.format('dd-MMM-yyyy'));
                        }
                        $('#all-books').empty().html(list);
                    });
                }, 'text');
            });             $(function () {
                Sys.require([Sys.components.calendar], function () {
                    var selectedDate = Date.parseInvariant('1-1-2000', 'd-M-yyyy');
                    $('#txtDate').calendar({
                        'format': 'dd-MM-yyyy',
                        'selectedDate': selectedDate,
                        'id': 'cal-txtDate'
                    });                     $('#btnFilter').click(function () {
                        var date = $find('cal-txtDate').get_selectedDate();
                        var params = { 'date': date} ;
                        var serializedParams = Sys.Serialization.JavaScriptSerializer.serialize(params);
                        $.ajax({
                            'url': '/LibraryService.svc/ajax/GetBooksPublishedAfter',
                            'data': serializedParams,
                            'type': 'POST',
                            'processData': false,
                            'contentType': 'application/json',
                            'timeout': 10000,
                            'dataType': 'text', //not JSON, we'll process
                            'success': function (res) {
                                Sys.require([Sys.scripts.Serialization, Sys.scripts.Globalization], function () {
                                    res = Sys.Serialization.JavaScriptSerializer.deserialize(res);
                                    var data = res.d; //data is in d
                                    var list = $('<ul></ul>'); 

                                    for (var i = 0; i < data.length; i++) {
                                        list.append('<li>' + data[i].Title + '- ' + data[i].Author + ', $' + data[i].Price.format('n') + ', ' + data[i].PublishDate.format('dd-MMM-yyyy'));
                                    }
                                    $('#filtered-books').empty().html(list);
                                });
                            },
                            'error': function () {
                                alert('An error occurred.');
                            }
                        });
                    });
                });
            });         });
    </script>
</head>
<body>
    <fieldset>
        <legend>All Books</legend>
        <div id='all-books'>
        </div>
    </fieldset>
    <br />
    <fieldset>
        <legend>Filtered Books</legend>
        <div>
            Please select a date:
            <input type='text' id='txtDate' /><br />
            <input type='button' id='btnFilter' value='fetch books' />
        </div>
        <div id='filtered-books'>
        </div>
    </fieldset>
</body>
</html>

Running the page, selecting a date and clicking the button should show something like this:

results4

 

Summary of Ajax Tests

Our Ajax-exposed WCF service is callable both from ASP.NET Ajax and jQuery. While jQuery is usually a lot leaner than ASP.NET Ajax in terms of things like animation and DOM manipulation, the new features of ASP.NET Ajax 4.0 make the latter a very attractive library for cleaner client template based databinding deriving its data from a service call. While jQuery also supports templates via plugins, ASP.NET Ajax 4.0's dataview control makes fetching data and displaying it in a reusable template quite elegant. The mere use of the word "elegant" relating to ASP.NET Ajax would have caused rounds of laughter in the past. v4.0 is looking quite good indeed.

Furthermore, WCF services handle dates in a specific way and also add some security to network calls. ASP.NET Ajax handles these issues seamlessly. Using jQuery, we had to pass and receive raw data and as such had to process the data before sending and after receiving. I used ASP.NET's serializer for this, although you could just as easily have used modified JSON2 (like Rick Strahl mentions in the previously mentioned link) or any other suitable technique. But the bottom line is that you would have had to handle these things which ASP.NET Ajax handles for you out of the box.

By this, I'm in no way saying that ASP.NET Ajax is better than jQuery or that jQuery is dead – far from it. What I'm saying is that there are things jQuery does way way better than ASP.NET Ajax, but handling WCF calls is not one of them. Those thinking about the size of the ASP.NET Ajax Library should rest assured that the on demand and in-parallel loading of only the required parts of the ASP.NET Ajax Library will ensure script downloads are very fast. Microsoft acknowledges that jQuery is a really really useful library and as such has adopted jQuery into the ASP.NET developer's toolbox. And with ASP.NET Ajax 4.0, it does seem like the two will finally complement each other.

Exposing to Silverlight

We'll now modify our configuration to support binary encoding for Silverlight. Please note that since we exposed our service's metadata for HTTP-GET via the "metadataGetBehavior" service behaviour, our service is already exposed to Silverlight. What we want to do now is expose the service for binary encoding. This will mean that data transfers are done in binary format, which is much more efficient than basic http. The first step in doing this is to add a custom binding. Right after our closing </behaviors> tag, add the following xml:


<bindings>
      <customBinding>
        <binding name="binaryEncoding">
          <binaryMessageEncoding />
          <httpTransport />
        </binding>
      </customBinding>
    </bindings>

The next step is to add a new endpoint for our service. Add the following to the <endpoints> section:


<endpoint address="binary"
                  binding="customBinding" bindingConfiguration="binaryEncoding"
                  contract="WCFEndpoints.LibraryService" />

Notice that we're specifying "customBinding" for the binding and passing in "binaryEncoding" for the binding configuration. "binaryEncoding" is the binding configuration we added to the <customBinding> node in the <bindings> section.

And that's all it takes to enable binary encoding on a WCF sercive. No messy attributes or changing the C# code – just a few lines of configuration. Your WCF section of the web.config should now look like this:


<system.serviceModel>
    <behaviors>
      <endpointBehaviors>
        <behavior name="AjaxBehavior">
          <enableWebScript />
        </behavior>
      </endpointBehaviors>
      <serviceBehaviors>
        <behavior name="metadataGetBehavior">
          <serviceMetadata httpGetEnabled="true"/>
          <serviceDebug includeExceptionDetailInFaults="true"></serviceDebug>
        </behavior>
      </serviceBehaviors>
    </behaviors>
    <bindings>
      <customBinding>
        <binding name="binaryEncoding">
          <binaryMessageEncoding />
          <httpTransport />
        </binding>
      </customBinding>
    </bindings>
    <serviceHostingEnvironment aspNetCompatibilityEnabled="true"
        multipleSiteBindingsEnabled="true" />
    <services>
      <service name="WCFEndpoints.LibraryService" behaviorConfiguration="metadataGetBehavior">
        <endpoint address="ajax" behaviorConfiguration="AjaxBehavior"
            binding="webHttpBinding" contract="WCFEndpoints.LibraryService" />
        <endpoint address="binary"
                  binding="customBinding" bindingConfiguration="binaryEncoding"
                  contract="WCFEndpoints.LibraryService" />
      </service>
    </services>
  </system.serviceModel>

Silverlight Test

Let's create the Silverlight project. Add one named SilverlightWCFClient to the solution:

SL_Add

Keep the defaults for the popup that appears:

SL_Add2

Before we go any further, we'll want our main application to launch from the same port on the dev server. To do this, right click the WCFEndpoints project and select properties. From the Web tab, select the "Specific port" radio button, assign a port of your choice (that's free) and save.

port

Now right click the SilverlightWCFClient project in the solution explorer and select "Add Service Reference". Click on the "Discover" button. You should see something like this:

add_svc_ref

To ensure your metadata is discoverable, click on LibraryService.svc from the left pane and expand the tree. If everything's ok, you should get a tree on the left and a list of operations on the right. Remember that we added a "metadataGetBehavior"? That ensures that our service is discoverable by the Add Service Reference tool (and any other tools that consume wsdl). Name the service namespace "LibraryServiceReference" and click Ok.

add_svc_ref2

The tool generates some proxy classes, details of which can be seen in the object browser by double clicking the LibraryServiceReference:

svc_ref_object

It also adds a ServiceReferences.ClientConfig file:


<configuration>
    <system.serviceModel>
        <bindings>
            <customBinding>
                <binding name="CustomBinding_LibraryService">
                    <binaryMessageEncoding />
                    <httpTransport maxReceivedMessageSize="2147483647" maxBufferSize="2147483647" />
                </binding>
            </customBinding>
        </bindings>
        <client>
            <endpoint address="http://localhost:12171/LibraryService.svc/binary"
                binding="customBinding" bindingConfiguration="CustomBinding_LibraryService"
                contract="LibraryServiceReference.LibraryService" name="CustomBinding_LibraryService" />
        </client>
    </system.serviceModel>
</configuration>

Notice that the binding for the endpoint also has "binaryMessageEncoding" and "httpTransport". The tool was smart enough to see that our service had a binary endpoint and used that to ensure optimum data transfer efficiency. Also note the the endpoint address specifies the server and port number. It's for this reason that we explicitly specified a fixed port number for our web project.

I won't walk you through creating the Silverlight page that consumes the service. The code is pretty trivial and you can download the whole project's source from the link at the end of the article. After creating the page, if we launch SilverlightWCFClientTestPage.html (of the WCFEndpoints project) and click the button, we get the following output:

SL

Running the page in Firefox with Firebug's Net tab activated, we should see this (after clicking the button):

firebug1

Notice that the last two items are "POST binary". Expanding either one and selecting the Post tab, we get this:

firebug2

That shows us that the request data is being passed to the web service in binary format. Unfortunately, the version of firebug I'm using can't interpret the binary response, so the Response tab is pretty useless. Of course, had we been using basic http binding, we would have seen text data in the response, so our "useless" Response tab is actually suggesting that the response is not in a textual format.

Conclusion

In this article, we've seen how we can create a WCF service by coding once and expose it to different technologies merely by changing the configuration. We created the service and exposed it's metadata via Http GET. We had it expose to Ajax clients via configuration. We then created two plain html pages. In the first, we used ASP.NET Ajax 4.0's new features to consume the Ajax service quite elegantly. In the second, we used jQuery. In doing so, we saw how we needed to serialize / deserialize inputs / outputs to / from a WCF service when using jQuery. We used ASP.NET Ajax's serialization capabilities to do this. We then proceeded to add an endpoint to the WCF service that would allow communication in the more efficient binary format. Finally, we used firebug to show that the data transfer was indeed being done via binary and not basic http.

Source Code

Download

----------------------------------------------------------------------------------

Questions relating to this article are welcome. Comments completely unrelated to the article and posted with the sole intention of putting your link here are not. If you spam, your comment will not be approved, will be deleted and your IP blocked. I maintain my site almost daily and such comments – even if they pass the spam filter – will get removed as soon as possible. If this gets too tedious, I may disable comments entirely. Please don't ruin it for everybody else.

----------------------------------------------------------------------------------

Share or Bookmark this post…
  • Facebook
  • DotNetKicks
  • Digg
  • LinkedIn
  • Technorati
  • del.icio.us
  • Google
  • Live
  • Tumblr
  • msdn Social
  • Ping.fm
  • Reddit
  • Slashdot
  • StumbleUpon
  • TwitThis

Ajax, Asp.net Ajax, jQuery – Misconceptions and Facts

by ashic 9. December 2009 08:19

There seems to be some confusion among a lot of developers about Asp.net Ajax. This article will hopefully clear some doubts regarding what it is, what it isn’t and where jQuery comes in.

Ajax

Ajax stands for Asynchronous JavaScript And Xml. Ajax is used to asynchronously make a server side request from a web page. It does not need a full page refresh (a full postback in Asp.net terms) and can result in a few benefits:

1. The user does not have to see the whole page flash blank for every little thing that requires server side processing.

2. Only the required data is sent from the server to the client in response to an Ajax request. This reduces network traffic.

3. Only the required data is processed. So processing of other parts of the page is not needed. This reduces the server workload.

Microsoft created the XMLHTTP ActiveX control way back in 1999. Other browsers have added similar functionality in a native object called XmlHttpRequest (XHR). These objects are used to create and carry out Ajax requests. The technology was being used before the term Ajax officially came into being, and it became widely known when Google used it with GMail and Google Maps. Jesse James Garrett coined the name Ajax feeling the need for an appropriate shorthand. Presumably, he thought of the name while taking a shower in 2005.

Although the name suggests JavaScript and XML – and initially XML was used to transfer the data – nowadays data can be passed in other formats as well as XML. For instance, it’s very common to pass data between the server and client using JSON (JavaScript Object Notation). And although the XHR object is usually used to make Ajax calls, other methods like using an IFrame or dynamically created <script> tags can also be seen.

What Ajax Can’t Do

There are a few limitations to Ajax calls. The major ones are:

1. It can’t make a cross domain call. The request must be made to a url within the same domain. So, if your domain is www.yoursite.com, the request must go to a url within www.yoursite.com. It can’t go to www.darth-vader.com or anywhere else. [There are workarounds, like using JSONP – although whether JSONP is Ajax is subject to speculation.]

2. It can never ever upload a file to the server.

3. It can’t access files on the client machine.

4. Any other limitations that JavaScript has.

Asp.net Ajax

Asp.net Ajax is Microsoft’s framework for Ajax and JavaScript development. Although it’s name is Asp.net Ajax, it can do much much more than just Ajax. It has always consisted of client side JavaScript and integration into Asp.net WebForms. This has been a main point of confusion and caused many people to think that Asp.net Ajax can only be used from Asp.net. Along with that, there’s also the Asp.net Ajax Control Toolkit (ACT), which adds a bunch of functionality and features on top of Asp.net Ajax. If this all seems confusing, it’s probably because it is. And Microsoft has addressed this. The Asp.net Ajax framework has recently been reorganized and the new version is currently in Beta. To make things simpler, these are now the key aspects of Asp.net Ajax:

  • Asp.net Ajax Library: This is a reorganization or the existing client side features (along with quite a few new ones) of Asp.net Ajax. This is a stand alone JavaScript library just like jQuery, scriptaculous, mootools etc. Although called Asp.net Ajax, it is in fact an entire JavaScript library that supports Object Oriented JavaScript, client templates, web service calls, animation, JSONP and a whole host of other things. It can be used with server side Asp.net, Asp.net MVC – even plain Html. Want to use it on a php page? Go right on ahead. It will work perfectly without .Net even installed on the server. This is completely client side, with no server dependencies. To add to that, the Asp.net Ajax Control Toolkit (ACT) has been incorporated into this library. So, you can use stuff like the CalenderExtender, ModalPopupExtender, HoverMenuExtender etc. without needing to use Asp.net. The Asp.net Ajax Library project is open source and maintained by the Codeplex Foundation (Microsoft recently donated the project to the Codeplex Foundation). You can find more information about the library (including detailed tutorials) at http://www.asp.net/ajaxlibrary/.

 

  • Server Side Asp.net: Asp.net Ajax has rich server side integration into Asp.net. To utilize these features, you will need to be using Asp.net. The ScriptManager, UpdatePanel, UpdateProgress and other controls allow developers to use features like partial page updates, managing browser history, script combining, web service script registration etc. without having to write any JavaScript. Asp.net Ajax enables Ajax features while retaining drag and drop RAD functionality in WebForms. It also provides an infrastructure on which to build extender controls, script controls and components – custom server controls that use the Asp.net Ajax Library (the client side library mentioned above) – and takes care of registering the required scripts and resources at runtime. All the developer using the control needs to do is drag and drop the control onto a WebForms page with a ScriptManager and set the properties. The framework will do the rest.

 

  • Ajax Control Toolkit: The Ajax Control Toolkit (ACT) adds a bunch of features and controls to Asp.net Ajax. Previously, this was a stand alone project hosted at codeplex. It was primarily used in WebForms scenarios. With the new-and-currently-in-beta version, the ACT has been incorporated into the Asp.net Ajax Library. All the controls of the toolkit are all present and can be used entirely from script. The ACT also has some server side components which can be used to make custom Asp.net Ajax server side control development easier. In addition, the server side components provide drag and drop functionality to all the controls and extenders in the client side ACT library. This makes using the controls easier in a WebForms environment, taking advantage of the ScriptManager control. While very easy to use with WebForms, it’s also very simple to use entirely from script (like in Asp.net MVC or plain Html). This has been a major improvement in the latest beta as it was quite cumbersome to use the toolkit before.

 

  • jQuery: jQuery is another JavaScript library, just like the Asp.net Ajax library. It’s free, open source and the brainchild of John Resig, who initially released it in 2006. So why are we talking about it here? Well, jQuery has quite a few benefits and is a great JavaScript library. It’s very lightweight, has some awesome selector syntax as well as chaining support for expressions. Again, why am I mentioning this here? The Asp.net Ajax team was looking to incorporate similar features in the client side library of Asp.net Ajax. jQuery was gaining real popularity at that time. It could do complex JavaScript tasks with very little code. The motto of jQuery is “write less, do more”. Microsoft was also pushing the early versions of Asp.net MVC. Asp.net MVC really needed a JavaScript library that could handle Ajax, JSON and client side manipulation in a simple manner. More and more developers started using jQuery for this. It was decided that jQuery would be officially considered a part of Asp.net development. It was initially included into the Asp.net MVC project template, and since then has been included in Asp.net Ajax as well. All the controls in the ACT now work seamlessly with jQuery (in the latest beta of the Ajax Library). Microsoft also pledged bug reports, patches, tests etc. to the jQuery project. One thing to note is that although jQuery support is included in the Asp.net Ajax Library, you do not need to be using jQuery to use Asp.net Ajax (or even the client side Asp.net Ajax Library). Microsoft recommends using them both together as it will make development easier with less need to reinvent the wheel. jQuery is already a rocket powered missile in a tiny package. As such, jQuery complements Asp.net Ajax and using one does not force you to restrain from the other.

 

  • Microsoft Ajax Content Delivery Network (CDN): Microsoft has CDN servers around the globe. And it hosts all of the JavaScript files in the Microsoft Ajax Library (including those for the ACT), jQuery, jQuery validation plug-in and the Asp.net Ajax scripts for Asp.net MVC on these servers. The files are cached and delivered to users of your website from their nearest location. This has a a few benefits:
    • The files reach the website user faster.
    • The files, once delivered, are cached on the browser too. Any other domain that uses the same file causes the browser to use the local cached version (since the file’s url is the CDN url, the domain remains constant). Hence, other websites using the CDN will load faster too.
    • The files are delivered from Microsoft’s servers. This reduces your server network bandwidth usage and reduces costs for you.
    • The CDN supports Http and Https. This means website users won’t get a pesky message asking them whether or not they want to show non-secure items.

You can learn more about the CDN here: http://www.asp.net/ajaxlibrary/CDN.ashx

So that’s it for an overview of what Asp.net Ajax actually is. If you wish to learn more, you can find a whole host of resources here: http://www.asp.net/ajax/

kick it on DotNetKicks.com
Shout it
Share or Bookmark this post…
  • Facebook
  • DotNetKicks
  • Digg
  • LinkedIn
  • Technorati
  • del.icio.us
  • Google
  • Live
  • Tumblr
  • msdn Social
  • Ping.fm
  • Reddit
  • Slashdot
  • StumbleUpon
  • TwitThis

Powered by BlogEngine.NET 1.6.1.0
Theme by Ashic Mahtab

Need an expert?

Ashic Mahtab
ashic@live.com
(+44) 07879927393

Stats

Featured Ads

 

Donations

I maintain this site and create all content entirely in my own time just to help you guys out. If you find the stuff helpful, or cool or just like what you see, I'd appreciate you chipping in to help out with the hosting costs. It's easy to do so - just click the button below - no amount is too low :)