Os1r1s110 Os1r1s110 - 2 months ago 35
Ajax Question

asp.net core user notification after form post

Okay, this is my first question here so I'll try to be clear and not repeat what others have asked before. I did try to search on the forum prior to posting but I found nothing that would help me fix my problem. If I missed an answer that would have been appropriate, please gently close this question as duplicate.

So my goal is to send a notification to the user after posting a form in a partial view to tell him if it worked or if there was a problem on the back end. I also wish not to reload the whole view just to display a notification.

My question is then what is the best way to send notifications from back end to UI without refreshing whole view.

Here is what I tried so far and why it didn't work:


  1. First attempt was to add data to ViewData dictionary and pass it to the view. It works but loads the full page again, which is undesired.

  2. As I saw by searching that it's a good practice to redirect to a GET request before returning to the view, I created a redirect to a success method that would then display the ViewData dictionary but I found that it didn't work as the redirection does not let me pass anything through viewdata.

  3. Afterwards I found that I may use TempData as it lives through redirection as opposed to ViewData and ViewBag. The problem with this solution is that there are some security concerns and also I wasn't able to run my javascript on the client side except for an alert, which is not really attractive to the user.

  4. The last attempt I made was to use an Ajax request so that there would be no page refresh and I could then only send a notification to the user once the server has responded. The problem with this solution is that I can't seem to get the form data to the backend, it is always filled with null values. It is to be noted that I tried adding the [FromBody] attribute before the ContactViewModel as I saw it in other posts as being the reason why it didn't bind correctly but it did not work for me.
    Here's the code I have so far:



a. For the controller:

[HttpPost]
[AjaxOnly]
public async Task<IActionResult>SendContactRequest(ContactViewModel model)
{

if (ModelState.IsValid)
{
string recipientEmailAddresses = anyEmail@anyExtension.com;

string subject = "Website customer request from " + model.firstName + " " + model.lastName;
string message = "";
if (model.jobTitle != null)
{
message += "Job title: " + model.jobTitle;
}
message += "Request or comment: " + model.requestMsg;

// Only actually send the message if we are in production/staging or other, anything else than development
if (!_env.IsDevelopment())
{
await _emailSender.SendEmailAsync(model.contactEmail, $"{model.firstName} {model.lastName}", recipientEmailAddresses, subject, message); //using interpolated string here
}

// Sending confirmation via response.writeAsync
await Response.WriteAsync("success");
}
else
{
await Response.WriteAsync("the message could not be sent as model is not valid");
}

// this method redirects to the good section on page, but cannot pass the model along with it...
return Redirect("Index#contact");
}


b. For the client side:

<form id="contactForm" asp-controller="Home" asp-action="SendContactRequest">
<div class="form-group">
@*<label asp-for="firstName">First Name</label>*@
<input asp-for="firstName" class="form-control" placeholder="First name" />
<span asp-validation-for="firstName" class="text-warning"></span>
</div>
<div class="form-group">
@*<label asp-for="lastName">Last Name</label>*@
<input asp-for="lastName" class="form-control" placeholder="Last name" />
<span asp-validation-for="lastName" class="text-warning"></span>
</div>
<div class="form-group">
@*<label asp-for="contactEmail">Contact Email</label>*@
<input asp-for="contactEmail" class="form-control" placeholder="Full email address" />
<span asp-validation-for="contactEmail" class="text-warning"></span>
<small id="email" class="form-text text-muted">We'll never share your email with anyone else.</small>
</div>
<div class="form-group">
@*<label asp-for="jobTitle">Job title</label>*@
<input asp-for="jobTitle" class="form-control" placeholder="Job title (optional)" />
<span asp-validation-for="jobTitle" class="text-warning"></span>
</div>
<div class="form-group">
@*<label asp-for="jobTitle">Job title</label>*@
<textarea asp-for="requestMsg" class="form-control" placeholder="Request or comment" rows="8"></textarea>
<span asp-validation-for="requestMsg" class="text-warning"></span>
</div>

<button type="submit" class="btn btn-primary">Submit</button>
</form>


c. Finally for the script part (Ajax call):

// Attach a submit handler to the form
$("#contactForm").submit(function (event) {

// Stop form from submitting normally
event.preventDefault();

var formdata = new FormData(document.querySelector('#contactForm')[0]);

// Send the data using ajax
$.ajax({
type: "POST",
url: $form.attr("action"),
data: formdata,
processData: false,
contentType: false,
success: function (response) {
if (response.includes("success")) {
alert(response);
document.getElementById("contactRequestForm").reset();
}
},
error: function () {
alert("Mail not sent, please try again later \n If this error persists, please contact us directly via email @ webmaster@set-ets.com");
}
});
});

Answer

Approach 4 is what I would recommend (Ajax). You have a minor bug in your code that's posting the form data, and that's why it's not binding to your model.

// Attach a submit handler to the form
$("#contactForm").submit(function (event) {

    // Stop form from submitting normally
    event.preventDefault();

    // this won't work - take out the indexer
    // var formdata = new FormData(document.querySelector('#contactForm')[0]);

    // this should work
    var formdata = new FormData(document.querySelector('#contactForm'));


    // Send the data using ajax
    $.ajax({
        type: "POST",
        url: $form.attr("action"),
        data: formdata,
        processData: false,
        contentType: false,
        success: function (response) {
            if (response.includes("success")) {
                alert(response);
                document.getElementById("contactRequestForm").reset();
            }
        },
        error: function () {
            alert("Mail not sent, please try again later \n If this error persists, please contact us directly via email @ webmaster@set-ets.com");
        }
    });
});

However, your controller code isn't going to do what you want. You really probably want it to return some sort of json type response with any model errors, etc. But, this code should get you to the point where it's binding to the model correctly, and then you can fiddle with your controller method to return something more reasonable (not a redirect)