ASP.NET MVC 3 DropDownListFor strange behaviour for determining selected option

So a strange behaviour in the implementation of the HtmlHelper.DropDownListFor extension method was recently brought to my attention where the order of ‘looking for’ the selected value of the list seemed incorrect.

I would assume that the selected value would always be the selected value from the strongly typed model property (as I have clearly stated by providing a lambda which results in returning that property) but it seems the selected value will not always be the selected value from the strongly typed model as I would expect.

For example:

@model TestModel

@{
    Model.Title = "Hello";
    ViewBag.Title = "Tim";
    Layout = null;
}

@using (Html.BeginForm())
{
    @Html.DropDownListFor(p => p.Title, new SelectList(new[] { "Other", "Hello", "Brent","Tim","Smith" }, "Smith"))
}

Given the above code what would the value of the drop down list be?
a) “Smith”
b) “Tim”
c) “Hello”

Well I would expect the SelectList constructor to create a list with a selected value of “Smith” then I would expect the DropDownListFor extension method to set the selected value to be “Hello” as provided by my model.

In fact what happens is that the value from the ViewBag is taken in priority over any others!
The answer to above is actually b).

To be honest using a strongly typed view I don’t think I would ever want the extension method to take the value from the ViewBag implicitly.

Now if I remove the Title parameter in the ViewBag as done so below, what would you expect?

@model TestModel

@{
    Model.Title = "Hello";
    //ViewBag.Title = "Tim";
    Layout = null;
}

@using (Html.BeginForm())
{
    @Html.DropDownListFor(p => p.Title, new SelectList(new[] { "Other", "Hello", "Brent","Tim","Smith" }, "Smith"))
}

Using the above logic the value would be “Hello” which is correct. However this doesn’t give us an option to override the selected value as the SelectList’s selected overload does the work before handing the object off to the DropDownListFor extension method which it then ignores.

Finally if the ViewBag property is null AND my Model’s property is null then the SelectList’s selected value will be unchanged and the result will be “Smith”.

Now I looked into TextBoxFor and EditorFor, etc and they work as expected. It is only the ListBoxFor and DropDownListFor extension methods which are first checking for the value in the ViewBag before the Model.

Just something everyone needs to be aware of. I would consider this a bug. Be sure to check your naming as we had a page title in the viewbag called “ViewBag.Title” and a “Person.Title” property on our strongly typed Person model. The select list tried to set the selected value of the list to be the page’s title!.

Advertisements

Jquery AJAX call to WCF Service returning custom objects

As the web gets faster and ajax becomes more and more used you will find clients are expecting a web experience close to that of a desktop application. JQuery together with Ajax make this task a whole lot easier. I’m currently writing a control to make room bookings via calender (you can see alot of these if you search for jquery plugins) our needs are a little different so I’m rolling my own and I thought I’d show just how easy it is to add ajax functionality to your page.

We all know the update panel is a horrible control. Sure it makes life easy but it is slow, has massive viewstate issues and not very developer friendly since it doesn’t allow for much customisation.

If you haven’t had a look at JQuery yet I suggest you should head over to jquery.com and have a look. This library makes our lives as developers a whole lot easier, and is great for AJAX calls which is what I’ll be talking about today.

I’m assuming you have basic experience with WCF and JQuery, and of course you know what ajax is (if not have a look a gmail. Notice how the page doesn’t refresh when you change folders, and mail is added to your html inbox automatically.. great user experience.)

Lets set up a simple web service. Add a new item to your web project and choose WCF Service. Create a contract as shown below:


    [Serializable]
    public class BookingViewModel
    {
        public int bookingID;
        public string description;
    }

    [ServiceContract(Name = "TestBookings", Namespace = "MyNamespace")]
    public interface ITestBookings
    {
        [OperationContract]
        [WebInvoke(Method="POST",
            BodyStyle=WebMessageBodyStyle.Wrapped,
            ResponseFormat=WebMessageFormat.Json)]
        List<BookingViewModel> SearchBookings();
    }

Now above I’ve created an interface which will be my WCF contract. I’ve declared my method as Web Invokable (can be called from javascript) and will be returning values encoded in Json.

At this point we have our contract, you should create a simple class to test that we have wcf set up correctly.

    [ServiceBehavior(AddressFilterMode = AddressFilterMode.Any)]
    public class TestBookings: ITestBookings
    {
          public List<BookingViewModel> SearchBookings()
         {
               return new List<BookingViewModel>
               {
                     new BookingViewModel
                     {
                            bookingID = 100,
                            description = "bob booked hotel"
                     },
                     new BookingViewModel
                     {
                            bookingID = 101,
                            description = "tim booked hotel"
                     }
               };
         }
    }

You’ll need to set up your serviceModel and endpoints in your web.config file. Heres a sample one:


    <system.serviceModel>
        <services>
            <clear />
            <service name="Namespace.TestBookings">
                <endpoint address="" binding="webHttpBinding" behaviorConfiguration="TestBehaviour"  contract="Namepsace.ITestBookings"/>
            </service>
        </services>

        <behaviors>
            <endpointBehaviors>
                <behavior name="TestBehaviour">
                    <webHttp/>
                </behavior>
            </endpointBehaviors>
            <serviceBehaviors>
                <behavior>
                    <serviceMetadata httpGetEnabled="true" httpsGetEnabled="false" />
                    <serviceDebug includeExceptionDetailInFaults="true" />
                </behavior>
            </serviceBehaviors>
        </behaviors>
    </system.serviceModel>

Basically the important parts are the binding=”webHttpBinding” and the behaviour config for .

Now right click on your svc file, choose set as start page and then run your app. You should see the default svc page generated by .NET. If not you will have to troubleshoot I won’t go into debugging the service here as its out of scope but a quick google of the error should find what you need. Otherwise comment and I’ll see if I can help.

At this point we can use JQuery to call the WCF service


<script>
    $(document).ready(function () {
            $.ajax({
                type: "POST",
                url: "YOUR-URL/TestBookings.svc/SearchBookings",
                data: "{}",
                contentType: "application/json; charset=utf-8",
                dataType: "json",
                success: function (data) {
                    var bookings = data.SearchBookingsResult;
                    alert(bookings[0].description);
                },
                error: function (xhr) {
                    alert(xhr.responseText);
                    return;
                }
            });
});

</script>

If everything worked correctly you should get an alert stating the description of booking number 1. This is so easy, however there is so much going on in the backend for your c# objects to be projected onto javascript. The .NET team have done amazing work here. And JQuery is the icing on the cake.

Jquery Visual Studio 2010 Intellisense in Javascript (JS) file

Hey guys,
Just a little trick I came along today, If you’re after Jquery Intellisense in your js file your going to need the jquery-x.x.x-vsdoc.js file which comes with the jquery package.

Simply add this to the top of your JS file

/// <reference path="jquery-x.x.x-vsdoc.js" />

Then Ctrl+Shift+J will reset Intellisense for javascript. Now ctrl+space and you should get the usual intellisense.

You should automatically get the intellisense in your aspx file (for script tags) if referencing your normal jquery file (and the vsdoc file is in the same directory as that reference) as automatic support for that is inbuilt.

Cheers!