Web Api 2.0 Ajax.beginform with validation?

799 views Asked by At

I am trying to learn about responsive pages and how that can all work with C# servers and Web API 2.0 and I am being tempted by Ajax.BeginForm. Following tutorials I have got to this point:

The .cshtml:

@model LearningCancerAPICalls.Models.Player
<div id="report">
<div id="projectReport">
<div>
@{
    Html.EnableClientValidation();
}
<a id="contact-us">Share Score!</a>
<div id="contact-form" class="hidden" title="Online Request Form">
    @using (Ajax.BeginForm("", "", null, new AjaxOptions
    {
        HttpMethod = "POST",
        Url = "http://localhost:52316/api/Player",
        OnSuccess = "OnSuccess",
        OnFailure = "OnFailure",
        InsertionMode = InsertionMode.Replace,
        UpdateTargetId = "reportContent"
    }, new { id = "formId", name = "frmStandingAdd" }))
    {
        @Html.ValidationSummary(true)
        @Html.LabelFor(m => m.PlayerName);
        @Html.TextBoxFor(m => m.PlayerName);
        @Html.ValidationMessageFor(model => model.PlayerName)

        @Html.LabelFor(m => m.Email);
        @Html.TextBoxFor(m => m.Email);
        @Html.ValidationMessageFor(model => model.Email)

        @Html.HiddenFor(m => m.PlayerId);
        @Html.Hidden( "PlayerId");
        <input type="submit" name="submit" value="Ok" />

        <div id="divResponse">
        </div>
    }
</div>
        </div>
    </div>
    <div id="reportContent">
    </div>
</div>

<script src="@Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/jquery.validate.unobtrusive.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/jquery.unobtrusive-ajax.min.js")" type="text/javascript"></script>
<script>
    function OnSuccess() {
        alert('Success');
        resetValidation();
    }
    function OnFailure(ajaxContext) {

        $('#projectReport').html(ajaxContext.responseText);
        $('#reportContent').empty();


        alert('Failure');
    }
    function resetValidation() {
        //Removes validation from input-fields
        $('.input-validation-error').addClass('input-validation-valid');
        $('.input-validation-error').removeClass('input-validation-error');
        //Removes validation message after input-fields
        $('.field-validation-error').addClass('field-validation-valid');
        $('.field-validation-error').removeClass('field-validation-error');
        //Removes validation summary 
        $('.validation-summary-errors').addClass('validation-summary-valid');
        $('.validation-summary-errors').removeClass('validation-summary-errors');

    }

</script>

the Controller:

 [ResponseType(typeof(void))]
        public IHttpActionResult PostPlayer(LearningCancerAPICalls.Models.Player player)
        {
            if (!ModelState.IsValid)
            {
                return BadRequest(ModelState);
            }

            var model = LeaderBoardContext.Current.Players.FirstOrDefault(x => x.PlayerId == player.PlayerId);
            if (model != null)
            {
                model.PlayerName = player.PlayerName;
                model.Email = player.Email;
                LeaderBoardContext.Current.Entry<Player>(player).State = EntityState.Modified;
            }
            else
            {
                LeaderBoardContext.Current.Players.Add(player);
            }


            try
            {
                LeaderBoardContext.Current.SaveChanges();
            }
            catch (DbUpdateConcurrencyException)
            {
                return BadRequest(ModelState);
            }
            return StatusCode(HttpStatusCode.NoContent);
        }

The Class:

public class Player
{
    [Key]
    public int PlayerId{ get; set; }

    [Display(Name = "Player Name")]
    [Required(ErrorMessage = "The Player name is required")]
    public string PlayerName { get; set; }

    [Display(Name = "Email address")]
    [Required(ErrorMessage = "The email address is required")]
    [Email(ErrorMessage = "The email address is not valid")]
    public string Email { get; set; }
}

Based on various meldings of tutorials & stack O Q&As:

  1. Ajax Helper with MVC tutorial
  2. Clearing validation from ajax form
  3. Ajax begin form and validation
  4. AJax begin form and Web API 2.0

But the part I'm stuck on is 'nice' validation. Currently, the net result of the above is on incorrect entry, the two fields disappear and just the message is displayed. Which based on the above code actually makes sense. Do I have to fill in the validation fields manually, if so is there a simple trick or does it have to be home rolled? Have MS let Ajax.BeginForm falls out of favor (I can't find many tutorials on how to use it with Web API 2.0?.

For reference this is what it looks like Pre-Ok Button enter image description here Post-Ok Button enter image description here

1

There are 1 answers

0
chrispepper1989 On

So I discovered the "Email" tag that I was using from a DataAnnotation package just doesn't seem to work. Swapping it for the EmailAddress tag and cleaning up other bits of the code solved my problems!

so now the simple version of my code looks like this:

    @model LearningCancerAPICalls.Models.Player
@{
    Layout = null;
}

<div id="Aj3">
    @{ var ajaxOptions = new AjaxOptions
       {

           HttpMethod = "POST",
           Url = "http://localhost:52316/api/Player",
           OnSuccess = "OnSuccess",
           OnFailure = "OnFailure",
           OnBegin = "OnBegin"

       }; }

    @using (Ajax.BeginForm("", "", ajaxOptions, new {novalidate = ""}))
    {
        @Html.LabelFor(m => m.PlayerName)
        @Html.TextBoxFor(m => m.PlayerName)

        @Html.ValidationMessageFor(m => m.PlayerName)
        <br/>
        @Html.LabelFor(m => m.Email)
        @Html.TextBoxFor(m => m.Email)
        @Html.ValidationMessageFor(m => m.Email)
        @Html.HiddenFor(m => m.PlayerId)
        <input type="submit"/>
    }
</div>


<script src="@Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/jquery.validate.unobtrusive.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/jquery.unobtrusive-ajax.min.js")" type="text/javascript"></script>

<script>
    function OnBegin() {

    }
    function OnSuccess() {

    }
    function OnFailure(ajaxContext) {


    }
</script>

And that works, basically, there is no real difference between HTML.BeginForm and AJAX.BeginForm when it comes to client side validation, nor is there any difference between AJAX.Helper with WebAPI and AJAX.Helper with standard MVC controllers.

To ensure client side validation works, add the following to your web.config:

  <appSettings>
    <add key="ClientValidationEnabled" value="true" /> 
    <add key="UnobtrusiveJavaScriptEnabled" value="true" />

  </appSettings>

I also cleaned up a bit by adding the scripts to a bundle in BundleConfig.cs

bundles.Add(new ScriptBundle("~/bundles/jqueryval").Include(
                "~/Scripts/jquery.validate.js",
                "~/Scripts/jquery.validate.unobtrusive.js"));

And replaced by list of JS files with

@Scripts.Render("~/bundles/jqueryval")

This solves all client side validation but what about additional model validation from WebAPI? well, it needs a little bit more work, you need to decode JSON coming back and add it manually, but other than that, there is no difference :)

We had an in-house reusable function for the model state as it happened, I can't post the full code but it's more or less:

    json = $.parseJSON(data.responseText);

    $.each(json.ModelState, function (key, value) {
     //get element
     //add field error classes
    }

Alternatively, you may choose in your webApi to send something that isnt the model state, you could send entirely different JSON to handle. These would go into the "OnFailure" Json function. With additional clearing logic in the "OnBegin" function

p.s. the "novalidation" attribute is added to stop Chrome et al from adding their own validation, see:

Disable validation of HTML5 form elements