How do I send a complex JSON object from an HTML form using SYNCHRONOUS page POST?

18,058

Solution 1

While Mythz suggestion of posting JSV values would work, it can sometimes be cumbersome to build and maintain complex JSV in JavaScript. For example, you may have to deal with escaping user inputted data, and syntax issues can be hard to track

This solution looks complex but it's really not, and is highly re-usable and only requires that you can send encoded JSON, which from jQuery is very easy.

Full Demo Source Code Here

Process - How it works:

  1. Your jQuery creates the DTO using the form data on submit
  2. The DTO is encoded as JSON, simply using JSON.stringify(data)
  3. The JSON is sent in a hidden form as the value of Data field
  4. A server DTO attribute filter deserializes the JSON value of Data field into your DTO
  5. Your service see the DTO populated as normal.

Filter Attribute:

My solution sends the DTO object encoded as JSON in a form post to the service. Then a simple filter intercepts the request and populates the DTO from the JSON payload.

public class GetFromJsonVariableAttribute : Attribute, IHasRequestFilter
{
    string _variableName;

    public GetFromJsonVariableAttribute(string variableName = "Data")
    {
        _variableName = variableName;
    }

    public void RequestFilter(IRequest req, IResponse res, object requestDto)
    {
        // Convert the JSON payload to DTO format
        var payload = req.GetParam(_variableName);
        if(payload != null)
            requestDto = JsonSerializer.DeserializeFromString(payload, requestDto.GetType());
    }

    public int Priority { get { return int.MinValue; } }
    IHasRequestFilter IHasRequestFilter.Copy() { return this; }
}

Usage

Then to use you simply add the attribute to your DTO. Data is the name of the form variable that will hold the JSON payload. You can choose any name you want here.

[GetFromJsonVariable("Data")]
[Route("/Customers","POST")]
public class CreateCustomerRequest : IReturnVoid
{
    public Customer Customer { get; set; }
    public Name Name { get; set; }
}

Client Side (jQuery):

  1. Get your form values
  2. Build the required DTO structure as a JavaScript object
  3. Convert that DTO to a JSON string
  4. Set your hidden form value to the DTO string & submit
$("#CreateCustomer").on("submit", function(){

    // Get the form values into simple key value array
    var values = {};
    $.each($(this).serializeArray(), function(){ values[this.name] = this.value; });

    // Prepare the DTO
    var data = {
        Customer: {
            Company: values["Company"],
            RegionCode: values["RegionCode"]
        },
        Name: {
            First: values["First"],
            Last: values["Last"]
        }
    };

    // Convert it to JSON
    $('#PayloadForm [name="Data"]').val(JSON.stringify(data));
    $('#PayloadForm').submit();
    return false;
});

With the HTML create the form your user will interact with, with no action, but is linked to the jQuery submit event code; And a hidden form that will actually perform the synchronous POST. Note the attribute Data matches that of the attribute the payload should be received on

<form id="CreateCustomer">
    <input type="text" name="Company" value="TheCompany" /><br/>
    <input type="text" name="RegionCode" value="AU_NSW" /><br/>
    <input type="text" name="First" value="Jimi" /><br/>
    <input type="text" name="Last" value="Hendrix" /><br/>
    <input type="submit" value="Submit" />
</form>

<!-- This form is hidden -->
<form action="/Customers" method="POST" id="PayloadForm">
    <input type="hidden" name="Data" value="">
</form>

Solution 2

ServiceStack can POST complex types using the JSV Format, e.g:

<input name="Customer" value="{Company:TheCompany,RegionCode:AU_NSW}" />
<input name="Name" value="{First:Jimi,Last:Hendrix}" />

Otherwise you can send complex types using JSON, e.g. with jQuery's $.ajax:

$.ajax({ 
   type: 'POST',
   contentType: 'application/json; charset=utf-8',
   url: "http://host/myservice",
   dataType: 'json',
   data: JSON.stringify({Customer:{Company:'x',RegionCode:'x'}}),
   success: function(response){ ... }
});

Although for maximum interoperability you should strive to keep your Request DTO's flat, e.g:

<form id="theForm" ...>
  <input name="Company" value="TheCompany" />
  <input name="RegionCode" value="AU_NSW" />
  <input name="FirstName" value="Jimi" />
  <input name="LastName" value="Hendrix" />
</form>

Which you can then POST as-is which the browser will do using the x-www-form-urlencoded Content-Type, or even ajaxify using ServiceStack's ss-utils.js bindForm method, e.g:

$("#theForm").bindForm();
Share:
18,058
McNulty
Author by

McNulty

Frontend Developer. HTML, CSS / LESS, Javascript, Angular, Bootstrap

Updated on June 27, 2022

Comments

  • McNulty
    McNulty almost 2 years

    My server is using ServiceStack and would like to receive some data like this:

    {
        Customer: {
            Company: "TheCompany",
            RegionCode: "AU_NSW"
        },
        Name: {
            First: "Jimi",
            Last: "Hendrix"
        }
    
    }
    

    I have a form which has these fields and I can easily grab the data using JQuery, make the nested JSON object and use $.post to send it through.

    But I don't want to send it as AJAX, because I want the entire page to submit and then the browser to show the server's response as a new page. Just classic form post behaviour.

    I have tried embedding the complex json as strings inside hidden form fields - No cigar.

    I have also looked to see if ServiceStack has any naming conventions such that I could call my form fields "Name[First]" and have ServiceStack put the right values in their correct nested structure - no cigar on that either.

    1. Is there a way I can attach a JSON object to a form's POST data just before that form sends the data through? or
    2. Is there a way I can do a full page submission with jQuery (so I can send a complex nested JSON whilst still having the normal "page-submit" behaviour)?
    • Admin
      Admin about 10 years
      Does your AJAX approach work? If so, can you post the code here so we can see what does work, and make suggestions from that?
    • Scott
      Scott almost 10 years
      Hello. Did you ever get this working?
    • McNulty
      McNulty almost 10 years
      Hi all, thanks for all the suggestions. The specs for this project changed a day after I put this question up, so I never tested any solutions - but Scott's looks pretty good.