Posting AJAX Requests to ASP.NET Core MVC

Introduction

In the past, I’ve had trouble doing something that is apparently simple: invoking a simple action method in a controller using AJAX. Although it is indeed simple, when using jQuery, it may require some attention, hence this post.

In this example I will show how to do something simple: how to invoke an action that just adds two integers and returns the result using jQuery AJAX. This can be extended to any other kind of data, mind you, it is not tied to simple data types.

Prerequisites

We will be using .NET Core (or .NET 5/6, doesn’t really matter for the purpose of this sample) and jQuery 3.x, although it should work the same with 2.x. There is no special setup required other than adding controllers and views to dependency injection (or full MVC, if you prefer).

Controller Side

The controller part is simple, but we have two options:

  1. We can have multiple parameters
  2. Or we can have a single parameter of a data type that holds all of the data that we wish to send

Generally, option #2 is more extensible, for we can add more parameters easily, without changing the action method’s signature, but I will show here how it works with both options.

Multiple Parameters

Our action method will look like this:

[HttpPost]
public IActionResult Add(int a, int b)
{
    return Json(a + b);
}

And that’s it, pretty simple, the [HttpPost] attribute, as you probably know, is an action attribute that says that this action can only be called as a result of an HTTP POST.

Let’s see now what the single parameter version would look like.

Single Parameter

public record AddRequest(int A, int B);

[HttpPost]
public IActionResult Add([FromBody] AddRequest req)
{
    return Json(req.A + req.B);
}

Here we see a few more things:

  1. A record declaration for a type that contains two properties, A and B; I could have made it a regular class, but records are so much more fun! Winking smile You will notice that these two properties, although cased differently, are the same as the parameters on the previous example
  2. The parameter is decorated with a [FromBody] attribute. This one instructs ASP.NET Core MVC to provide the value for the parameter from the body of the request. Its usage could be skipped if we instead decorated the whole controller class with an [ApiController] attribute, but that is beyond the scope of this post

Client Side

On the client side, we also need to match the decision we made on the controller side, regarding how we defined the parameters.

Multiple Parameters

The JavaScript version goes like this:

function calculate() {     

const data = {  a: 1,         b: 2     };
$.ajax({      url: '/Home/Add',         type: 'POST',         data: data,         success: function(result, status, xhr) { /*hooray!*/ },         error: function(xhr, status, error) { /*houston, we have a problem!*/ }     }); 

}

Single Parameter

And the single one is just two small changes:

function calculate() {
     const data = {         a: 1,         b: 2     };
$.ajax({         url: '/Home/Add',         type: 'POST',         data: JSON.stringify(data),         contentType: 'application/json',         success: function(result, status, xhr) { /*hooray!*/ },         error: function(xhr, status, error) { /*houston, we have a problem!*/ }       }); }

As you can see, the difference is that here you need to explicitly serialize the data to JSON using the built-in JSON.stringify function and then also explicitly provide the content type application/json.

Pitfalls

There are a few possible pitfalls:

  1. You may get HTTP 400 Bad Request when you try to POST to your action method. This is likely because of Anti Cross Site Scripting Forgery (XSRF) protection, which is enabled by default in ASP.NET Core MVC. Please refer to my other post here, or, TL; DR, apply an [IgnoreAntiforgeryToken] to the action method
  2. When using the single parameter option, you may be getting a null value in the action method as the parameter value. This is likely the result of being unable to deserialize the contents that you are sending. Please ensure that you are allowing case-insensitive JSON serialization (the default) and that the parameters are well defined. See also here

Conclusion

AJAX has been around for quite a while and is a fun and safe option, but it has its tricks. Hope you find this post helpful!

Defining ASP.NET Update Panel Template Contents Dynamically

The ASP.NET UpdatePanel was introduced with the ASP.NET 2.0 AJAX Extensions almost a century ago (kidding, but almost feels like it!Winking smile). It allows us to have AJAX-style effects (partial page loads) with very little effort.

The UpdatePanel has a property, ContentTemplate, which is, well, a template, which means it can only be set through markup – not so good for dynamic contents -, or by implementing our own ITemplate class – slightly more work, but this can be reused in other scenarios where an ITemplate is required:

public class Template : Control, ITemplate

{

    public void InstantiateIn(Control container)

    {

        var controls = this.Controls.OfType<Control>().ToList();

 

        for (var i = 0; i < controls.Count; i++)

        {

            var control = controls[i];

            container.Controls.Add(control);

        }

    }

}

 

protected override void OnLoad(EventArgs e)

{

    var tb = new TextBox { ID = "time" };

    var timer = new Timer { Interval = 1000, Enabled = true };

    timer.Tick += timer_Tick;

 

    var tmp = new Template();

    tmp.Controls.Add(tb);

    tmp.Controls.Add(timer);

 

    var up = new UpdatePanel();

    up.ContentTemplate = tmp;

 

    this.Form.Controls.Add(up);

 

    base.OnLoad(e);

}

 

void timer_Tick(object sender, EventArgs e)

{

    var time = (sender as Control).NamingContainer.FindControl("time") as TextBox;

    time.Text = DateTime.Now.ToString();

}

There is also a property, ContentTemplateContainer, which is a control to which you can add your controls, so that they are added to the UpdatePanel. This is exactly what we need:

protected override OnLoad(EventArgs e)

{

    var tb = new TextBox { ID = "time" };

    var timer = new Timer { Interval = 1000, Enabled = true };

    timer.Tick += this.timer_Tick;

    

    var up = new UpdatePanel();

    up.ContentTemplateContainer.Controls.Add(tb);

    up.ContentTemplateContainer.Controls.Add(timer);

    

    this.Form.Controls.Add(up);

    

    base.OnLoad(e);

}