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…
  • DotNetKicks
  • LinkedIn
  • Technorati
  • msdn Social
  • del.icio.us
  • Digg
  • Reddit
  • StumbleUpon
  • Slashdot
  • Live
  • Google
  • Facebook
Categories: .NET | ASP.NET MVC | ASP.NET

Encrypted Hidden Inputs in ASP.NET MVC

by ashic 25. February 2010 02:27

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…
  • DotNetKicks
  • LinkedIn
  • Technorati
  • msdn Social
  • del.icio.us
  • Digg
  • Reddit
  • StumbleUpon
  • Slashdot
  • Live
  • Google
  • Facebook
Categories: .NET | ASP.NET | ASP.NET MVC | Security

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…
  • DotNetKicks
  • LinkedIn
  • Technorati
  • msdn Social
  • del.icio.us
  • Digg
  • Reddit
  • StumbleUpon
  • Slashdot
  • Live
  • Google
  • Facebook

Asp.net MVC 2.0 RC 2 Just Released

by ashic 4. February 2010 19:47

I was just moderating the asp.net forums and approved Phil's announcement 29 minutes ago that Asp.net MVC 2.0 RC 2 has just been released:

Hi everyone,

We released ASP.NET MVC 2 Release Candidate 2 this evening!

This release works with Visual Studio 2008 SP1 and Visual Web Developer 2008 SP1. We will be posting source code and our futures assembly soon.

Thanks,

The ASP.NET Team

Phil Haack (http://haacked.com/)
Senior Program Manager, Microsoft

Pretty cool…can’t wait for the RTW.

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

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…
  • DotNetKicks
  • LinkedIn
  • Technorati
  • msdn Social
  • del.icio.us
  • Digg
  • Reddit
  • StumbleUpon
  • Slashdot
  • Live
  • Google
  • Facebook

I’m Back!!

by ashic 8. December 2009 01:10

After a few months absence, I’m back. I’ve redesigned the site and a few more updates will hopefully come in the next few weeks.

So much has happened since my last post –

  • VS 2010 and .net 4.0 beta
  • MVC 2.0 beta
  • Silverlight 4.0 beta
  • New Bing maps
  • Azure improvements

-and that’s just a high level view :)

I’ll hopefully put up some articles to cover these soon. Stay tuned!

Share or Bookmark this post…
  • DotNetKicks
  • LinkedIn
  • Technorati
  • msdn Social
  • del.icio.us
  • Digg
  • Reddit
  • StumbleUpon
  • Slashdot
  • Live
  • Google
  • Facebook
Categories: Community

Woohoo…I’m an Asp.net MVP…and a few questions for you

by ashic 1. July 2009 08:25

I was announced asp.net MVP today. It’s great to receive the award as a recognition of my activities at the asp.net forums. I hope I can continue to help people out there and that this site will grow in the process. It’s my first time receiving the award and hopefully it won’t be the last. In an attempt to hold on to it(!), I’ll ask you guys…what should I write about? What aspects of asp.net do you want me to cover? I’ll be posting some video tutorials soon. What topics do you want me to go through? Lemme know and I just might listen ;).

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

Simple Username / Password Authentication WITHOUT Membership Providers

by ashic 19. June 2009 00:44

The Membership provider in asp.net is great. As is the Roles support. You can do some very nifty things with it with a considerable amount of ease. You can get hashed or encrypted passwords and a whole host of features that’s pretty much enterprise grade. And you get it for free. Well, not free, but it is pretty easy to set up.

But what if your website only needed two or three user accounts? What if you didn’t need all that fancy stuff. You just want to use a login system that’ll just block off an admin area or something and you want to do it quick and dirty. Maybe it’s a prototype. Maybe it’s your own little site where you just want simplicity for authentication. I’ve actually went through all the aspnet_regsql.exe trouble for a very simple site that would have just one user. Sounds like overkill? It is.

The login controls provided in asp.net 2.0 are quite handy, but they mostly work with a membership provider. The <asp:Login> control is specially helpful for creating a login screen in no time. But it too works with a Membership provider.

So how can we quickly setup a login system without using a Membership provider? And how can we use the <asp:Login> control to set up the login screen when using such a system? Read on…

Forms authentication actually allows you to store usernames and passwords in the web.config file. A typical web.config file doing this is shown here:

 

login-1

I’ve highlighted the important parts. The authentication mode needs to be Forms. The PasswordFormat is left as “Cleared” here. It can also be hashed or encrypted. I’ll make a post in the future about hashed and encrypted passwords. I’ve added a single <user> to the credentials node. You could easily add more. In the <authorization> node, I’ve allowed all users. In the <location> node for the “admin” folder, I’ve set it to deny all unauthenticated users. So, if a user is logged in, they can access the admin folder. If not, they’re asked to login.

Now, with this in place, if you were to put an <asp:Login> control on a page and expect it to work just like when using a Membership provider, then you’re in for a nasty surprise. Even if you enter the correct username and password, it’ll tell you the login has failed. Why? The reason is pretty simple. We’ve not mentioned a Membership provider to use, so asp.net defaults to the Membership provider in the machine.config, which usually is an asp.net sql membership provider drawing info from a database in App_Data. That database has no info about our user named admin with the password “pa$$w0rd!”. So, logging in fails. How do we overcome this? Simple. Just add a handler for the OnLoogingIn (NOT OnLoggedIn) event of the <asp:Login> control and in that handler, do this:

login-2

[If you don’t want the authentication cookie to persist between requests, set the second parameter of RedirectFromLoginPage to false.]

 

That’s all there is to it.

Now, that was very simple, but the question that pops up is is it secure?

Well, the answer is both a yes and a no. It’s yes in the sense that IIS will never serve anything with a .config extension from an outside request. So, users on your site will never be able to download the web.config and look at the username and passwords. The “no” part of the answer comes from the fact that if someone can look inside the web.config file, they’d be able to see the usernames and passwords. If you run your own server or have a hosted account that only you access, this is not that big a problem. [I add the latter coz no hosting company is ever going to go through the risk of looking inside of clients’ files for passwords.] For extra security though, you can use hashed or encrypted passwords in this system too, which I’ll cover in a future post.

A drawback to this is that the web.config file can get very big for a lot of users. But then again, this isn’t meant to be used for a lot of users. This is meant for very simple sites woth very few users. There are sites out there that are like this and don’t need all the Membership gloss. If your site’s like that, you can easily use this method.

 

Hope that helps.

Shout it
Share or Bookmark this post…
  • DotNetKicks
  • LinkedIn
  • Technorati
  • msdn Social
  • del.icio.us
  • Digg
  • Reddit
  • StumbleUpon
  • Slashdot
  • Live
  • Google
  • Facebook
Categories: ASP.NET

Display Modes of Validator Controls

by ashic 15. June 2009 23:04

By default, asp.net validators are positioned right next to the control they validate. You can move them in the markup, but wherever you put them, they occupy an area equal to the area required to display the Text property (or if the Text is not present, then the ErrorMessage property). We may not want that. We may want them to only occupy space when they’re displayed, or not display them at all (and showing the error message in a validation summary only). The validators have a property called display, which can be set to one of three values: Static, Dynamic and None. Setting it to Static will mean the validator will occupy space even when there’s no error. Dynamic means that it won’t occupy space when there isn’t an error, but will show up when there is. None means that the validator’s Text (or ErrorMessage) isn’t displayed at all and doesn’t occupy any space. In this case, you’ll need to use a ValidationSummary control to be able to display the ErrorMessage.

Let’s look at a bit of markup that has three required field validators having different settings for Display. To the right of each validator, I’ve added the text “dummy” to signify where the validator’s display area ends. Markup:

val-pos-1

 

Now let’s fire up the page and see what it look like in the browser:

val-pos2

Notice that since the first validator had a display of static, there’s a space equal to the space needed to display the Text of the first validator next to the first text box. The second and third validators had display set to “Static” and “None” respectively. Hence, there’s no space between the second and third textboxes and they’re respective “dummy” texts. Now, lets hit submit:

val-pos3

Notice that the Text of the first two validators are displayed next to the text boxes, but the third one’s Text is not shown at all. This is because the third validator’s display was set to none. Notice that the error message of all three validators are displayed in the validation summary.

 

Hope that helps.

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

A Cool Bing-like Search Box (Button on the Inside)

by ashic 15. June 2009 18:57

In the recent “web 2.0” sites, we see cool search boxes, with the search button appearing “inside” the text box. How’d they do that? Microsoft’s bing search also has this feature. A snapshot of the bing search box looks like this:

search

It’s actually quite simple to do, with a bit of css. First, let’s look at the markup:


<div class='search-box'>
    <asp:TextBox runat="server" ID='txt1'></asp:TextBox>
    <asp:Button runat="server" ID='btn1' Text='Search' />
</div>
 

And let’s now see the associated css:


<style type="text/css">
    .search-box input
    {
        border:none;
    }
    .search-box
    {
        display:inline;
        border:1px solid #000000;
    }
</style>
 

And you can see the result:

my-search

Sure…it’s not exactly the same, but you can play around with it, change the css here and there, use an ImageButton or whatever to make it look exactly like you want. The idea is basically the same.

Hope that helps.

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

Powered by BlogEngine.NET 1.5.1.29
Theme by Ashic Mahtab

Stats