locked
Please help with Form Creation and Model Validation on Modal Dialog RRS feed

  • Question

  • User-121299044 posted

    Sorry to bother you, I'm Newbie! What I'm trying to do is to create a model, with proper DataAnnotations or meta-information for validation, then create a button from my main page, open a modal window using fancybox ajax, then show the form, validate and send the data using jquery ajax.

    THE PROBLEM: After I created the form and a view model (called AddContactModel with 2 items, a list of names and the Contact model for the form), , successfully validated the model using jquery validation unobtrusive but when sending the data, each field is called like this Contact_Name instead of Name, when sending it, it fails because is expecting Name, not Contact.FirstName or Contact_FirstName... this is because the Model has 2 items, basically, the view model is like this:

    //--- My contact model...

    public class Contact{
    [Required]
    public string FirstName{get;set}
    [Required]
    public string LastName{get;set}
    }

    //--- View Model... public class AddContactModel { public List<string> names { get; set; } public Contact contact { get; set; } } //--- On my controller... SHOW THE FORM [HttpGet] public IActionResult AddContact() { AddContactModel mymodel = new AddContactModel(); mymodel.names= new List<string>(){"Dog","Cat","Other"} mymodel.contact = new Contact(); return PartialView("AddContactForm", model: mymodel); }

    //--- On my controller RECEIVING...

    [HttpPost]
    public JsonResult AddContact(Contact contact) {
    if (!ModelState.IsValid) {
    Response.StatusCode = 400;
    return Json(new { status = "failure", message = "Wrong data received, expecting a Contact." });
    }
    return Json(new { status = "success", message = "Well done!" });
    }

    Now what happens is, when I create the form partial view, in this case is called "AddContactForm", I have something similar to this:

    @model AddContactModel
    
    <form id="newcontactform">
    
    <div class="form-group">
       <label asp-for="@Model.contact.FirstName" class=""></label>
       <input asp-for="@Model.contact.FirstName" class="form-control"/>
       <span class="text-danger" asp-validation-for="@Model.contact.FirstName"></span>
    </div>

    <div class="form-group">
    <label asp-for="@Model.contact.LastName" class=""></label>
    <input asp-for="@Model.contact.LastName" class="form-control"/>
    <span class="text-danger" asp-validation-for="@Model.contact.LastName"></span>
    </div>

    <button class="btn btn-primary" id="sendForm">Save Contact</button>
    </form>

    <script>

    //Activate unobtrusive validation on this form
    // (as the script was loaded on the main page, remember this is a modal, loaded on demand)

    $.validator.unobtrusive.parse("#newcontactform");

    $('#sendForm').bind('click', function (event) {
    event.preventDefault();
    var myform = $("#newcontactform");
    var validator = myform.validate().form();
    if (forma.valid()) {
    $.ajax({
    type: "POST",
    url: '@Url.Action("AddContact")',
    processData: false,
    contentType: false,
    data: forma.serialize() });
    }
    });

    </script>

    The generated code result is a form with fields like this:

    ...
    <input class="form-control valid" type="text" data-val="true" data-val-required="" id="contact_FirstName" name="contact.FirstName">
    <input class="form-control valid" type="text" data-val="true" data-val-required="" id="contact_LastName" name="contact.LastName">
    ...

    The problem starts with the name of the tex inputs, as the view model has 2 items, one is a list of strings and the other is the Contact, the field now is named contact_FIELD and contact.FIELD, when I serialize the form to send it to the controller using POST, the server model validation report as an invalid model, the problem I notice is the contact_ part, because the data goes something like this

    contact.FirstName: Jhone
    contact.LastName: Wayne

    The only way to make this work is to loop on the form elements, and create a new FormData javascript object, then supress the "contact." part but I consider this to be so outdated, like the way we used to do in the first days of the web...  in other words, basically I have to build a new object that looks like the following in order to be accepted on the POST action method that is expecting a Contact object.

    FirstName:Jhon
    LastName:Wayne

    If the validation is done so wonderfully, is there a way to make the form view to produce inputs with the field name only? or if this is not possible, is there a way to adapt the receiving action method of the POST to adapt to the name change? 

    Thank you for all your help!

    Saturday, January 11, 2020 5:06 AM

All replies

  • User475983607 posted

    , is there a way to make the form view to produce inputs with the field name only? or if this is not possible, is there a way to adapt the receiving action method of the POST to adapt to the name change? 

    JSON serialize the model.

    $('#sendForm').bind('click', function (event) {
        event.preventDefault();
        var myform = $("#newcontactform");
        var validator = myform.validate().form();
    
        var data = {
    	    FirstName : $('#FirstName').val(),
    	    LastName : $('#LastName').val()
    	};
    
        if (forma.valid()) {
            $.ajax({
                type: "POST",
                url: '@Url.Action("AddContact")',
                contentType: 'application/json',
                dataType: "json"
                data: JSON.stringify(data) });
        }
    }); 

    Saturday, January 11, 2020 1:24 PM
  • User-121299044 posted

    Thank you for your answer @mgebhard .  Even that you did teach the proper way to convert a javascript object to JSON, it does not resolve the main question, how to avoid creating the data object itself... if we could call the form fields just with the name, there should be no reason for us to create it, and also, think about the complexity of getting, for example, a select dropdown value... if the model has a big number of fields, we must add more problems... 

    Saturday, January 11, 2020 6:07 PM
  • User475983607 posted

    Thank you for your answer @mgebhard .  Even that you did teach the proper way to convert a javascript object to JSON, it does not resolve the main question, how to avoid creating the data object itself... if we could call the form fields just with the name, there should be no reason for us to create it, and also, think about the complexity of getting, for example, a select dropdown value... if the model has a big number of fields, we must add more problems... 

    I'm not sure what problem you are trying to solve.  

    In the old days programmers had to write repetitive code to convert POST parameters to an object.  Model binding solves this but there are a few rules to follow.  Anyway, it's possible to fetch the POST parameter directly from the request if that's what you are trying to do.

    string fieldName = Request.Form["fieldName"];

    Saturday, January 11, 2020 7:57 PM
  • User-121299044 posted

    Thank you @mgebhard My main question is... why do we have to re-create the FormData object and manually add each, as you posted in your code...

    FirstName : $('#FirstName').val(),

    Is there a way to automate this so we can provide the object the server side expect? the validation was made via javascript, now the server side expects an object that meets the rules we provided... asking for too much?

    Saturday, January 11, 2020 8:56 PM
  • User475983607 posted

    Thank you @mgebhard My main question is... why do we have to re-create the FormData object and manually add each, as you posted in your code...

    FirstName : $('#FirstName').val(),

    Is there a way to automate this so we can provide the object the server side expect? the validation was made via javascript, now the server side expects an object that meets the rules we provided... asking for too much?

    The concept is pretty simple.  The field names must match the Action's input property names for model binding to work.  Either update the form input names to match the model, update the model to match the input names, or create a different View Model.

    Sunday, January 12, 2020 1:31 PM
  • User-474980206 posted

    just change your controller to have a post back model that matches:

    public class ContractRq
    {
       public Contact Contact {get; set;}
    }
    [HttpPost] 
    public JsonResult AddContact(ContactRq rq) {
    {
        var contact = rq.Contact;
    

    Monday, January 13, 2020 3:15 AM
  • User-121299044 posted

    Thank you Bruce! interesting... didn't think this way. will try and post here.

    Monday, January 13, 2020 3:24 AM
  • User711641945 posted

    Hi sipi.perez

    What is your forma.serialize()?

    You could console.log() to see the data you passed on the console platform in the browser.And here is a working demo that you coud try this:

    var myform = $("#newcontactform");
    console.log(myform.serialize());  //get the data on the console platform: contact.FirstName=Wayne&contact.LastName=Wayne
    $.ajax({
        type: "POST",
        url: '@Url.Action("AddContact")'+'?'+myform.serialize(),  
        processData: false,
        contentType: false
    });

    Action:

    [HttpPost] 
    public JsonResult AddContact(Contact contact)
    {}

    Best Regards,

    Rena

    Monday, January 13, 2020 9:20 AM
  • User-121299044 posted

    hey Bruce! Unfortunately, this approach did not work, I tried to create a nested object as you said but it does not work, also tried to create a nested property of the same type, tried different names, nothing... :-(

    My form generated input fields calling them "NewContact.XXXXX", so when I serialize them I get the following:

    {"NewContact.ContactId":"1000000","NewContact.BusinessName":"Tax-bookkeeping.net","NewContact.Phone":"8326419682",...}

    If I submit this, it does not work, as it is expecting ContactId, BusinessName, and Phone without the "NewContact." part.

    If I send the JSON this way it works: {ContactId: "1000000", BusinessName: "Tax-bookkeeping.net", Phone: "8326419682",…}

    My partial solution is to create a new javascript object that goes field by field, replaces part I don't want in the name so it looks like the one on top, then it works... 

    Thanks for your help.

    Wednesday, January 15, 2020 3:33 PM
  • User475983607 posted

    The concept is very simple.  The form field names must match the action input property names.  There are several places to make these changes; the View, Action method signature, or model.

    This overrides the tag helper and sets the name.

    <input asp-for="@Model.contact.FirstName" name="FirstName" class="form-control"/>

    Or don't use the asp-for if you know your design expects a different property name.

    <input value="@Model.contact.FirstName" name="FirstName" class="form-control"/>

    Change the action input parameter to match the expected complex type.

    [HttpPost] 
    public JsonResult AddContact(AddContactModel contact) {
       if (!ModelState.IsValid) { 
          Response.StatusCode = 400; 
          return Json(new { status = "failure", message = "Wrong data received, expecting a Contact." });
        }
       return Json(new { status = "success", message = "Well done!" });
    }

    Similarly you can change the View Model to Contact...

    @model Contact 

    ... and pass...

    public List<string> names { get; set; }

    in the current context using the ViewData.

    https://docs.microsoft.com/en-us/aspnet/core/mvc/views/overview?view=aspnetcore-3.1

    Wednesday, January 15, 2020 3:54 PM