Search code examples
asp.net-mvcasp.net-ajax

How to call an action on ASP.NET MVC without postback?


I'm working with the WhatsApp Business Cloud API C# Wrapper Library and SignalR in a project to create a real time chat for a webpage. I've managed to separate the messages sent to the website according to the SignalR connection (such that messages sent from the site's WhatsApp account do not get mixed on different connections, which all share the same WhatsApp number), but I'm stuck regarding the sending of messages from the website to the WhatsApp account. The action that sends the messages is as follows, which is in the HomeController.cs file

/// <summary>
/// This is to handle:
/// 1. Plain Text messgaes
/// 2. Text Templates (NO params)
/// 3. Text Templates with Params
/// </summary>
/// <param name="payload"></param>
/// <returns></returns>
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> SendWhatsAppTextMessage(SendTemplateMessageViewModel payload)
{ // Functional using SendMessageController
    try
    {
        SendWhatsAppPayload sendPayload = new();
        sendPayload.SendText = new SendTextPayload()
        { ToNum = payload.RecipientPhoneNumber };
        sendPayload.SendText.PreviewUrl = false;

        if (payload.Message != null)
        {   // This is a normal plain Text Message
            sendPayload.SendText.Message = payload.Message;
        }
        else
        {   // This is a Template Test Message 
            sendPayload.Template = new WhatsappTemplate();
            sendPayload.Template.Name = payload.TemplateName;

            // CJM to add a Params Textbox on the Form                  
            if (payload.TemplateParams != null)
            {
                string strParams = payload.TemplateParams; // "Cornelius#DAFP";
                List<string> listParams = strParams.Split(new string[] { "#" }, StringSplitOptions.None).ToList();
                sendPayload.Template.Params = listParams;
            }
        }

        // Send the message and get the WAMId
        string WAMIds = _sendMessageController.GetWAMId((await _sendMessageController.SendWhatsApp_TextAsync(sendPayload)).Value);


        if (WAMIds != null)
        {
            return RedirectToAction(nameof(Index)).WithSuccess("Success", $"Successfully sent video template message with WAMId '{WAMIds}'");
        }
        else
        {
            return RedirectToAction(nameof(SendWhatsAppTemplateMessage));
        }

    }
    catch (WhatsappBusinessCloudAPIException ex)
    {
        _logger.LogError(ex, ex.Message);
        return RedirectToAction(nameof(SendWhatsAppTemplateMessage)).WithDanger("Error", ex.Message);
    }
}

And here is the view from the SendWhatsAppTextMessage.cshtml file

@model SendTemplateMessageViewModel
@{
    ViewData["Title"] = "Send WhatsApp Text Message Page";
    ViewData["CurrentPage"] = "Send WhatsApp Text Message";
    Layout = "~/Views/Shared/AdminLTE/_AdminLayout.cshtml";
    ViewData["ControllerName"] = nameof(HomeController).Replace("Controller", "");
    ViewData["ActionName"] = nameof(HomeController.SendWhatsAppTextMessage);
}

<section class="content">
    <div class="row">
        <div class="col-12">
            <div class="card card-info">
                <div class="card-header">
                    <h3 class="card-title">Send WhatsApp Text Message</h3>
                </div> <!--/. card-header -->
                <!--Form start -->
                <form asp-action="SendWhatsAppTextMessage">
                    <div asp-validation-summary="ModelOnly" class="text-danger"></div>
                    <div class="card-body">
                        <div class="form-group">
                            <label class="control-label">Recipient Phone Number</label>
                            <input asp-for="RecipientPhoneNumber" class="form-control" />
                            <span asp-validation-for="RecipientPhoneNumber" class="form-control" />
                        </div>
                        <div class="form-group">
                            <label class="control-label">Message</label>
                            <input asp-for="Message" class="form-control" />
                            <span asp-validation-for="Message" class="form-control" />
                        </div>
                    </div> <!--/. card-body -->
                    <div class="card-footer">
                        <button type="submit" name="submit" class="btn btn-primary">Send Message</button>
                    </div>
                </form>
            </div> <!-- /.card -->
        </div>
    </div>
</section>

@section Scripts {
    @{
        await Html.RenderPartialAsync("_ValidationScriptsPartial");
    }
    } 

The problem with sending messages with a button this way is that after the action is called, there is postback on the page and the SignalR connection gets reset, so I loose track of the chat on the page. What I'm trying to achieve is just to maintain the SignalR connection while sending messages from the website, so as to see the chat as usual (website user and messages from the other end).

Thanks.

EDIT:

The AJAX request is as follows

$.ajax({
    method: "POST",
    url: '@Url.Action("SendWhatsAppTextMessage", "Home")',
    data: { LinkUrl: '', MediaId: '', Message: $("#Message").val(), RecipientPhoneNumber: $("#RecipientPhoneNumber").val(), TemplateName: '', TemplateParams: '', hdnIdConexion: $("#hdnIdConexion").val() },
    dataType: "json",
    contentType: "application/json; charset=utf-8",
    success: function () {
    },
    error: function () {
        alert("No se ha podido enviar el mensaje.");
    }
});

If I delete the comma at the end of the JSON from data, It will send it correctly, but as it is, the properties of payload are null. I also tried passing the keys as strings to data, but I get the same result.


Solution

  • Your AJAX request is not working because you're not using a JSON model. If you want to work with JSON, then you need to configure your view model to handle JSON serialization.

    If not, then alter your AJAX request to the following:

        $.ajax({
            method: "POST",
            url: '@Url.Action("SendWhatsAppTextMessage", "Home")',
            data: { linkUrl: '', mediaId: '', message: $("#Message").val(), recipientPhoneNumber: $("#RecipientPhoneNumber").val(), templateName: '', templateParams: '', hdnIdConexion: $("#hdnIdConexion").val() },
            success: function () {
            },
            error: function () {
                alert("No se ha podido enviar el mensaje.");
            }
        });
    

    Notice the camel case property names instead of pascal. And the lack of JSON data type. I also got rid of the anti forgery attribute, but if you need that then refer to this post: include antiforgerytoken in ajax post ASP.NET MVC

    I tested the above using the following model:

    // I'm assuming this is similar to what you have. 
    public class SendTemplateMessageViewModel
    {
        public string LinkUrl {get; set; }
        public string MediaId {get; set; }
        public string Message {get; set; }
        public string RecipientPhoneNumber {get; set; }
        public string TemplateName {get; set; }
        public string TemplateParams {get; set; }
        public string HdnIdConexion {get; set; }
    }
    

    And it works as expected. If I were you - I would alter this model to be compatible with JSON serialization. Makes for a cleaner and clearer implementation.

    Hopefully this will get you going.