1

I have a search form, which will do some client side validation, call the server action, do some server side validation, and then return either the original view (with the modelstate errors) OR the search results.

For this approach to work, i would need to render the view to a string, and return a Json result. I could probably do that with one of the answers in this question: Render a view as a string

However, if i'm having to implement my own 'RenderView' type function, is this really the best way to do this? Or is there a better design decision for implementing this kind of functionality? Any help would be greatly appreciated.

An abstraction of the code is listed below for reference;

Controller:

public ActionResult Index()
{
    return View(new SearchModel());
}

public ActionResult Search(SearchModel criteria)
{
    if (!ModelState.IsValid)
        return Json(new { HasErrors = true, Result = RenderViewToString("Index", criteria) });

    var results = {Do Search...};
    return PartialView("SearchResults", results);
}

View:

@using(Html.BeginForm(...))
{
    {form fields...}
    {submit button...}
}
<div id="search-results"></div>

<script type="text/javascript">
    $(document).on("submit", "form", function(e) {
        if (!$(this).valid()) return false;

        e.preventDefault(); // Submit the form with ajax instead.
        $.ajax({
            url: this.action,
            type: this.method
            data: $(this).serialize(),
            success: function(data) {
                if (data.HasErrors) {
                    $(document).html(data.Result);
                }
                else {
                    $("#search-results").html(data);
                }
            }
        });
     });
</script>
Community
  • 1
  • 1
Luke
  • 288
  • 2
  • 16

2 Answers2

3

I've marked Kiyarash's answer as correct, as it put me on the right track. Here is what i actually used though: (Please note that this will only show the last error for each field - I will be adding some logic into it, so that it shows multiple error messages).

if (!ModelState.IsValid)
{
    return Json(new
    {
        HasErrors = true,
        Errors = ModelState.ToDictionary(
            ms => ms.Key,
            ms => ms.Value.Errors.Select(e => e.ErrorMessage).ToArray()
        ).Where(ms => ms.Value.Count() > 0)
    }, JsonRequestBehavior.AllowGet);
}

$.ajax({
    ...
    success: function(data) {
        if (data.HasErrors) {
            showErrorMessages(data.Errors, $("form"));
        }
        else {
            $("#search-results").html(data);
        }
    }
});

function showErrorMessages(errors, context) {
    $.each(errors, function (i, error) {
        $("[data-valmsg-for='" + error.Key + "']", context).text(error.Value)
            .removeClass("field-validation-valid")
            .addClass("field-validation-error");
    });
}
Luke
  • 288
  • 2
  • 16
2

First of all you need this jquery function to add error in validation summary

$.fn.addNewErrorMessage = function (message) {
    $(this).find('.validation-summary-valid').removeClass('validation-summary-valid')
        .addClass('validation-summary-errors');
    $(this).find(".validation-summary-errors ul").append("<li>" + message + "</li>");
}

Then you need make a list of error and return it as a JSON format like this code in action.

if (!modelState.IsValid)
{
    var errors = ModelState.ToDictionary(kvp => kvp.Key,
           kvp => kvp.Value.Errors
                      .Select(e => e.ErrorMessage).ToArray())
                      .Where(m => m.Value.Count() > 0);
     return Json(new {HasErrors = true,Errors = errors});
}

After that in success function in ajax use addNewErrorMessage function to show error messages

$.ajax({
        url: this.action,
        type: this.method
        data: $(this).serialize(),
        success: function(data) {
            if (data.HasErrors) {
                for(int i; i<data.Errors.length)
                {
                    $('form').addNewErrorMessage(data.Errors[i].Value);
                }
            }
            else {
                $("#search-results").html(data);
            }
        }
Kiarash Alinasab
  • 794
  • 1
  • 4
  • 16
  • Is there a way to select each fields error, and apply it its respective field (rather than a summary)? If i can select the property name along with the error, then i could use the 'data-valmsg-for' data attribute to select and place the error. – Luke May 15 '17 at 05:01
  • Nevermind; I checked - the Key has the property name. So i should be able to make this work. – Luke May 15 '17 at 05:07
  • I think you can select specific error by using data.Errors[i].Key – Kiarash Alinasab May 15 '17 at 05:07