Showing posts with label AJAX. Show all posts
Showing posts with label AJAX. Show all posts

Getting ASP.NET MVC action exception message on jQuery $.ajax fail error callback

While testing an app I came across an error: I passed a string parameter to an action method responsible for loading an existing object by name on the database side. The problem is that there was no such object on the database. To make things even more difficult to handle this action method returns a PartialView that gets loaded in the view side using jQuery $.ajax. So how and what should I return to the caller if an exception happens while processing the request inside that action method? That’s a good question.

I tried something like this:

public ActionResult SubCatList(string subcat)
{
    var sub = Database.GetLogSubCategory(subcat);

    if(sub == null)
    {
        throw new EntryNotFoundException(String.Format("SubCategory {0} does not exist", subcat));
    }

    var subcatItems = Database.GetListItemsForSubCategory(sub);

    return PartialView(subcatItems);
}

My intention was to get that beautiful formatted exception message inside $.ajax fail callback using the error parameter like this:

$.ajax({
        url: '/api/subcatlist/subcatlist?subcat=' + subcat,
        type: 'GET',
        cache: false,
        success: function(html)
        {
            $("body").prepend(html);

// Do something more here...
} }).fail(function(xhr, textStatus, error) { displayMessage(error, 'danger'); }); }

This definitely DOES NOT WORK out of the box! So what’s the problem? The problem is that when checking/debugging the JavaScript using Firebug or Google Chrome,  the error parameter is set with a default IIS error message: Internal Server Error for the standard 500 error code. The exception message gets populated inside the xhr parameter instead in the middle of the standard page’s HTML code used by IIS.

Looking around I found something that could help in this case:

Handling Exceptions using jQuery and ASP.NET MVC

Improving the code shared on the above post, the basic idea is to have a common JsonResult action method in a BaseController for example that sets the Response.StatusDescription directly like this:

public JsonResult ThrowJsonError(Exception e)
{
    Logger.Error(e.Message, e);

    Response.StatusCode = (int)System.Net.HttpStatusCode.BadRequest;
    Response.StatusDescription = e.Message;

    return Json(new { Message = e.Message }, JsonRequestBehavior.AllowGet);
}

Now let’s use this beauty:

public ActionResult SubCatList(string subcat)
{
    var sub = Database.GetLogSubCategory(subcat);

    if(sub == null)
    {
        return ThrowJsonError(
            new EntryNotFoundException(String.Format("SubCategory {0} does not exist", subcat)));
    }

    var subcatItems = Database.GetListItemsForSubCategory(sub);

    return PartialView(subcatItems);
}

Now that beautiful formatted exception message is populated in the error parameter in $.ajax fail callback.

Objective achieved! Hope it’s useful it the future. Happy coding and 2014 for everyone…

Detecting and fixing Ajax.BeginForm and Partial View stale data

This blog post describes a technical hurdle and how to solve it with some (actually 2) clever lines of code.

In a recent ASP.NET MVC application called Vizion Logs, me and my buddy Mario Brenes from IT Vizion were facing a difficult situation: our Save action method returns a PartialView but the Model value passed to this partial view was not being displayed correctly under the covers, that is, the View was keeping the previous/stale Model value. This was completely ruining our business logic on the Controller side. This was a nasty problem because the first time the view/form is rendered the Model has an ID property set to -1. After post backing the Model and saving it we then pass this same Model to the partial view but now it has the ID property set to the ID automatically generated by an Oracle sequence defined in the database side. We count on this ID property value for subsequent save operations in which case this specific Model will be updated and not saved again as a new row in the database.

It’s worth mentioning that we use that Save action method to handle both Saving and Updating the log objects.

Here you can see the form being constructed with an Ajax.BeginForm on the View side:

@using(Ajax.BeginForm("Save", Model.Field,
    new AjaxOptions
    {
        HttpMethod = "POST",
        UpdateTargetId = "logs",
        InsertionMode = InsertionMode.Replace,
        OnFailure = "handleFailure",
        OnSuccess = "setupForm();parseFormForUnobtrusiveValidation();",
    }))

We did a Google Hangout to investigate what was going on… two minds working together to detect a problem is better than just one. It was Pair Programming (remotely) at its best. These hangouts with Mario are helping me to exercise my English too – WOOT! It’s a double gain or why not say a bargain!?
During this investigation Mario kept asking me questions that led us to the correct places in the code path. This allowed us to solve the problem in less than 30 minutes…

At first we tested the condition that was leading to the buggy path: saving the logs for the first time worked great but then hitting the save button a second time was causing duplicate entries on the database instead of updating the log entries already created.

As our Save method counts on the log’s ID property for subsequent saves (updates to be technically correct) it was fair enough to check the model that was being passed to the partial view right after hitting the save button for the first time. We saw that it had the ID property set correctly to the ID generated by the database. So this was the expected behavior. On the view side we use @Html.HiddenFor(l => l.ID) to keep the ID property as part of the post back values. This is important because with this we know when inside the Save action method that that specific log entry must be updated and not saved. A log entry must be created when it has an ID = -1 and it must be updated when ID != -1. Now what was left for us to check was see if the correct ID was present in the HTML generated by the partial view. Bingo: the ID property was still -1. Houston we have a problem. Now let us look at the jquery.unobtrusive-ajax.js file that handles the Ajax.BeginForm. What interest us on this JavaScript file is the asyncOnSuccess method:

function asyncOnSuccess(element, data, contentType) {
    var mode;

    if (contentType.indexOf("application/x-javascript") !== -1) {  // jQuery already executes JavaScript for us
        return;
    }

    mode = (element.getAttribute("data-ajax-mode") || "").toUpperCase();
$(element.getAttribute(
"data-ajax-update")).each(function (i, update) { var top; switch (mode) { case "BEFORE": top = update.firstChild; $("<div />").html(data).contents().each(function () { update.insertBefore(this, top); }); break; case "AFTER": $("<div />").html(data).contents().each(function () { update.appendChild(this); }); break; default: $(update).html(data); break; } }); }

We placed a breakpoint in this method and analyzed the data parameter that contains the HTML that’s going to be rendered by the partial view. To our surprise we saw that the ID property was still –1 despite it being 10 for example on the Controller side right before passing the model to the PartialView.

Jesus Christ… this is crazy! Confused smile How can that be the case?!

Well I thought… this sounds like a cache problem. This was the first time I was using the Ajax.BeginForm construct. Before using it I was used to use plain @Html.BeginForm and jquery $.ajax calls and as such I had never experienced this let’s say “bug” before. With this caching hint on mind I Googled about the problem and AS ALWAYS StackOverflow is our best friend. I found this question:

MVC Partial View Model Not Refreshing

jgauffin tells us to use the OutputCacheAttribute to disable cache for that specific action method and also clear the ModelState object. Something like this:

[OutputCache(NoStore = true, Duration = 0, VaryByParam = "*")]
public ActionResult Save(LogFormViewModel model)
{   
    // Custom Save/Update logic here...
    
    ModelState.Clear();
    
    return PartialView(model);
}

After decorating our Save action method with the OutputCache attribute and calling ModelState.Clear() we retested the form and to our surprise it just worked and we told each other: that’s great buddy!

We just solved the problem with some clever lines of code thanks to our collaborative thinking and to StackOverflow that never ceases to amaze me. For 99% of questions I have I can be sure that they’ve already been answered there – it’s just a matter of using the correct wording in a Google Search, but hold on: before searching you must first know why and what you need to search for. I think this is one lesson learned with this post.

That’s it. Hope this post is useful to the next soul/spirit/human being that might stumble on this.

Data binding view values to an array parameter in ASP.NET MVC

If you want the source codez, jump here.

This looks like something so easy to be done and at the same time I guess a lot of people fail the first time they try to get this working. This question at StackOverflow proves that. So to help the fellow developers out there I put together a concise ASP.NET MVC 4 sample application to show how to data bind/postback view values to an array parameter in an controller action method. I show two ways of doing this: using a traditional/full postback and an AJAX/partial postback. This way you can choose the best option for you.

Use case
User wants to be able to select 3 values ( at least 1 being mandatory Surprised smile ) in the view using dropdowns and data bind the selected value(s) to an array that will be treated inside the controller.

User Interface
While implementing the code I thought why not make the app a little bit fancier and give it some meaning and so I complemented the sample with color picker dropdowns. This is the UI:

ASP.NET MVC Array Data Binding sample app UI

Code
ASP.NET MVC needs the generated form elements to follow a certain format so that the model binder can do its job, that is, understand what you’re trying to do; otherwise when you debug the code you’ll just get a null value in the array parameter or ViewModel array property inside you controller action. This means that the model binder didn’t understand your posted data and I’m sure you thought it would. I’ve already been in this situation…

So the question is: How to make the model binder understand the view data? Let’s get to the code…

The sample’s code is well documented, so I’ll just copy/paste the important parts here. Just read the comments to understand what’s going on…

In the view side, you must create the input elements following this format:

@* Pay special attention to how we must name the form fields.
selectedColors is the name of a parameter defined in the controller action that will receive the form values. It’s necessary to suffix the name with [n], where n must be zero-based and unbroken. *@ @for (int i = 0; i < 3; i++) { @Html.DropDownList("selectedColors["+i+"]", Model.Colors, "-- Pick a Color --") }

In the controller side, you must have an action method with an array parameter named exactly like the name given to the dropdowns on the view side ( selectedColors ) like this:

/// <summary>
/// This action method works with a simple int[] array that's not part of a ViewModel.
/// </summary>
/// <param name="selectedColors">Simple int[] array</param>
/// <returns>ArrayPostResult view</returns>
[HttpPost]
public ActionResult TestArrayPost(int[] selectedColors)
{
    if (Request.IsAjaxRequest())
    {
        ViewBag.PostBack = "AJAX";

        return PartialView("ArrayPostResult", selectedColors);
    }

    ViewBag.PostBack = "Full";

    return View("ArrayPostResult", selectedColors);
}

The above code should take care of handling the full postback scenario.

To do an async/partial postback using AJAX you can use this jQuery code as an example:

$("#submit-ajax").click(function () {

    var selectedColors = [];

    // For each select HTML element/dropdown,
    // push its selected value into selectedColors
    $('select option:selected').each(function () {
        selectedColors.push($(this).val());
    });

    $.ajax({
        type: "POST",
        url: "/Home/TestArrayPost",
        dataType: "html",
        traditional: true,
        data: { selectedColors: selectedColors },
        success: function (response) {

            $("#result").html(response).fadeIn();

            // Hiding the GO back button...
            $("#result").find("#go-back").hide();

        },
        error: function (xhr, textStatus, exceptionThrown) {
            $('#result').html(exceptionThrown);
        }
    });
});

Note
If you want to postback the values to an array property that’s inside your ViewModel, for example:

public int[] SelectedColors { get; set; }

You can do this in the view side:

@for (int i = 0; i < 3; i++)
{
    @Html.DropDownListFor(m => m.SelectedColors[i], Model.Colors, "-- Pick a Color --")
} 

Then on the controller side, you’d have this:

[HttpPost]
public ActionResult TestArrayPost(ColorsModel model)
{
    if (Request.IsAjaxRequest())
    {
        ViewBag.PostBack = "AJAX";

        return PartialView("ArrayPostResult", model.SelectedColors);
    }

    ViewBag.PostBack = "Full";

    return View("ArrayPostResult", model.SelectedColors);
}

Source code
The code uses a bit of jQuery to control the UI logic and to postback the values through AJAX.

It’s hosted at GitHub and so you can navigate through it easily here.

You can download the Visual Studio 2012 solution here:

https://github.com/leniel/AspNetMvcArrayDataBinding/archive/master.zip

Hope it helps.

References
To implement this sample app I Googled some of the concepts employed like setting the background-color of select/dropdown options based on the option’s text. Pretty interesting stuff…

The following is a list of links that helped me:

Model Binding To A List
ASP.NET Wire Format for Model Binding to Arrays, Lists, Collections, Dictionaries
Drop-down Lists and ASP.NET MVC
@Html.DropDownListFor; How to set different background color for each item in DDL?
How do I convert an enum to a list in C#?