Handle exception in Html.BeginForm MVC - c#

I have a form in a view that is marked with #html.beginForm. This form consists of dropdowns, text boxes and a button. The drop downs are populated dynamically through ajax call. ie. selection of one value from the drop down triggers an ajax call and dynamically populates all the drop down boxes.
This form has a button to post and bring up another form. The problem that I am having is that when an exception happens in the controller, how do i show the error message to the user and preserve the form values filled out by the user?
Here is my view:
<div class="col-lg-6">
<div class="panel panel-default animated zoomInDown" style="padding-top:25px">
<div class="fa fa-spinner fa-spin" id="dashSpinner" style="display:none;text-align:center;"></div>
#using (Html.BeginForm("Create", "MyEntities", FormMethod.Post, new { id = "createEntityForm" }))
{
/*Form consists of dropdowns and text boxes*/
}
</div>
Here is my controller:
[HttpPost]
public ActionResult Create([Bind(Include = Entities)] EntityModel model)
{
try
{
//If everything goes well, redirect to another form
return RedirectToAction("AnotherForm", "Event", new { id = eventid });
}
}
catch (Exception e)
{
//Catch exception and show it to the user?
log.Error(e);
model.Error = e.Message;
}
return View(model);
}
Here is my ajax call to show error message to the user
$("#createEntityForm").on("submit", function (e) {
$("#dashSpinner").show();
debugger;
$.ajax({
url: this.action,
type: this.method,
data: $(this).serialize(),
//success: function (response) {
// debugger;
//},
error: function (response) {
debugger;
$("#dashSpinner").hide();
swal({
title: "Error",
text: "You cannot take this type of action on this event.",
type: "error",
showConfirmButton: true
});
}
});
$("#dashSpinner").hide();
});

The thing that triggers the Error event is getting a non 2xx status code.
Alter your ActionMethod to use a non-2xx status code. You can do this by using Response.StatusCode = 500;.
You are also always returning a view - if you want to show just an error message it may be easier to return a JsonResult and then update your error handling to just show this error. In that case your ActionMethod catch statement could become:
catch (Exception e)
{
log.Error(e);
Response.StatusCode = 500;
Response.TrySkipIisCustomErrors = true;
return Json(new { message = e.Message } );
}
You then need to update your JQuery error handler to show the message as currently it will always show the same error ("You cannot take this type of action on this event."). To do this you need to output the message sent as part of the JSON payload.
error: function (response) {
$("#dashSpinner").hide();
swal({
title: "Error",
text: response.responseJSON.message,
type: "error",
showConfirmButton: true
});
}

The jQuery.ajax error callback is executed when server response has status code different than 200.
You should return response using HttpStatusCodeResult (msdn) or setting Response.StatusCode (msdn) in catch block.

At the Controller
[HttpPost]
public ActionResult Create([Bind(Include = Entities)] EntityModel model)
{
//If something goes wrong return to the form with the errors.
if(!ModelState.isValid()) return Create(model);
//If everything goes well, redirect to another form
return RedirectToAction("AnotherForm", "Event", new { id =
return View(model);
}
You can check too unobtrusive for jquery at the client side. https://exceptionnotfound.net/asp-net-mvc-demystified-unobtrusive-validation/

Related

Pass last insert id to toastr - Asp.Net MVC 4

I am new to MVC and trying to pass the last created Id (once the save button has been clicked in the form).
Can anyone please tell me if it is possible to pass this value to the toastr display, and how this can be done, so once the save button is pressed it returns that Id number?
Additionally to my comment, here's a more complex answer.
Roughly it contains the following items:
Views: CreateItem, NewItemHandler
Controllers: ItemHandler
Javascript: site.js and jQuery
The CreateItem view is the dialog where the user enters their item values. In my case a simple form with two input fields and the mandatory submit button.
#{
ViewBag.Title = "CreateItem";
}
<h2>CreateItem</h2>
<form id="newItemForm">
Item name: <input id="itemname" type="text" name="fname"><br>
Item weight: <input id="itemweight" type="text" name="lname"><br>
<input type="submit" value="Submit">
</form>
The JavaScript should stop the redirection when clicking on submit, this is done by returning false within $("newItemForm").submit(...). Furthermore we no need to tell the server that it needs to create our item, so we have to create our own submit request, which I did with jQuery.post():
$('#newItemForm').submit(function () {
sendPostAndShowResult();
return false;
});
function sendPostAndShowResult() {
var name = $("#itemname").text();
var weight = $("#itemweight").text();
$.post("/Item/NewItemHandler",
{ "name": name, "weight": weight }
).done(function (data) {
alert("The ID of your new item is: " + $.trim(data)); //replace with toast
})
.fail(function () {
alert("Error while processing the request!");
});
}
Just a hint: I didn't use toast here, since I never used it, but I guess it shouldn't be too difficult to adapt.
The final piece of the puzzle is the NewItemHandler, which creates the item, figures out the ID and returns the value:
The View is quite easy. Since we don't need a Layout, it has been set to "".
#{
Layout = "";
}
#Html.Raw(Session["ItemID"])
As you see, we just need to get the "ItemID" into our Session object, this is done by the Controller.
[HttpPost]
public ActionResult NewItemHandler(string name, string weight)
{
int id = GenerateNewItem(name, weight);
Session["ItemID"] = id;
return View();
}
EDIT: I tried to adapt this approach to your solution:
You need to remove the return RedirectToAction() with return View(); in your Controller. This then returns (Save.cshtml) a response, with the ID in an ohterwise empty file (Layout = "").
Your Save.cshtml is empty I guess, so replace it with
#{
Layout = "";
}
#Html.Raw(Session["ItemID"])
In your controller the Save Method should look remotely like this.
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Save(BidstonHwrc bidstonhwrc)
{
_context.BidstonHwrc.Add(bidstonhwrc);
try
{
_context.SaveChanges(); //either all changes are made or none at all
}
catch (Exception e)
{
Console.WriteLine(e);
}
int id = bidstonhwrc.Id;
Session["ItemID"] = id;
return View();
}
In your MCN Form you need to give your <form> tag an ID, via Razor:
#using (Html.BeginForm("Save", "BidstonHwrc",FormMethod.Post, new { id = "SaveBidstonHwrc" }))
The javascript code should look like this, simply adapt the IDs:
$('#SaveBidstonHwrc').submit(function () {
sendPostAndShowResult();
return false;
});
function sendPostAndShowResult() {
//foreach Model/ViewModel Property one line e.g.
var Id = $("Id").text();
var McnNumber = $("McnNumber").text();
$.post("/BidstonHwrc/Save",
{ "Id": Id, "McnNumber": McnNumber }
).done(function (data) {
alert("The ID of your new item is: " + $.trim(data)); //replace with toast
$(location).attr('href', '/Home/Index') //Redirect to Home
})
.fail(function () {
alert("Error while processing the request!");
});
}
I uploaded a project that should represent your solution a bit.
You can download it here (28MB): Project download

Exception handling for file deletion and showing a message using ValidationMessage()

This question continues from a previous question I posted at the link below:
Passing a filename to a view
In summary: I am trying to delete a file. I now have all the necessary functionality working thanks to the answer at the above post. However, occasionally, if the file was in use (which would happen if I downloaded the file prior to deleting for example), I would get an IOException.
I want to handle this exception and display a message back to the user when this happens.
I tried debugging the jQuery code, but whenever I put a breakpoint on it, I start skipping through 1000's of lines of jQuery libraries. So as a quick alternative I just put alerts everywhere.
I discovered that most of this code is not being executed - so I put comments beside where I think I should be seeing messages but am not. So as a result I am unable to debug or figure out what the code is supposed to be doing.
So my first question is how to get the exception showing. It seems that even though this is Ajax, the whole page still refreshes, which to me is not the expected behaviour for Ajax calls (so if an error is showing its maybe lost when the page refreshes). However, with all the alerts, I should see the error somewhere, but I am not. I have purposefully altered the working code to always throw an exception for now. Again, the delete functionality works, its the error reporting that fails.
My next question is to have a brief explanation of why each section of the code (where highlighted) is needed as I don't know why those sections exist and I cant figure it out because I cant debug into or show alerts for them.
Below is the index view and the code in question:
#model IEnumerable<FileInfo>
#{
ViewBag.Title = "File List";
}
<h2>Index</h2>
<p>#Html.ActionLink("Upload", "Upload")</p>
<p>#Html.ValidationSummary(true, "", new { #class = "text-danger" })</p>
<p>#Html.ValidationMessage("Name", new { #class = "text-danger" })</p>
<span class="message text-danger"></span>
<table class="table">
<tr>
<th>File Name</th>
<th>Actions</th>
</tr>
#foreach (FileInfo file in Model)
{
<tr>
<td>#file.Name</td>
<td>
<form class="deleteform">
#Html.AntiForgeryToken()
<input type="hidden" name="fileName" value="#file.Name" />
<input type="submit" value="delete" />
</form>
</td>
<td>#Html.ActionLink("Download", "Download", new { fileName = file.Name })</td>
</tr>
}
</table>
#section Scripts {
#Scripts.Render("~/bundles/jqueryval")
<script type="text/javascript">
$(document).ready()
{
var url = '#Url.Action("Delete", "FileManagement")';
$('.deleteform').submit(function ()
{
alert(".deleteform.submit entered...");//This alert shows
return confirm("are you sure ...");//This alert shows
var formData = $(this).serialize();
alert(formData);//This does NOT show.
var row = $(this).closest('tr');
alert(row);//This does NOT show.
$.post(url, formData, function (response)
{
alert("$.post() entered...");//This does NOT show.
alert(response);//This does NOT show.
if (response)
{
alert("response true");//This does NOT show.
alert(response);//This does NOT show.
//row.remove(); //This code actually works even though the alert above does not show.
} else
{
alert("response false");//This does NOT show. - I dont know what this section of code is for.
//alert("Error 1 - display message");
// Oops - display message?
}
alert("$.post() finished...");//This does NOT show.
}).fail(function (response)
{
alert("$.fail() entered...");//This does NOT show. - I dont know what this section of code is for.
alert("Error 2 - display another message");//This does NOT show.
// Oops
alert("$.fail() finished...");//This does NOT show.
});
return false; // cancel the default submit
alert(".deleteform.submit finished...");//This does NOT show.
});
}
</script>
}
Below is the controller for this view and the delete actions:
public ActionResult Index()
{
DirectoryInfo dirInfo = new DirectoryInfo(Server.MapPath("~/UserFiles"));
List<FileInfo> files = dirInfo.GetFiles().ToList();
return View(files);
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Delete(string fileName)
{
var path = Path.Combine(Server.MapPath("~/UserFiles"), fileName);
if (System.IO.File.Exists(path))
{
try
{
//System.IO.File.Delete(path);
throw new IOException("Hello - Test Message...");
}
catch (IOException e)
{
Response.StatusCode = 500;
Response.StatusDescription = e.Message;
//ModelState.AddModelError("Name", e.Message);
}
}
else
return Json(null);
//return HttpNotFound();
return Json(true);
//return RedirectToAction("Index");
}
I would appreciate any help on this.
You cannot use return confirm("are you sure ..."); in the .submit() handler because it either returns false which cancels everything, or it returns true in which case you will be making a normal submit. In either case, it exits the function.
You need to change the script to
$('.deleteform').submit(function () {
if (confirm("....") {
// make you ajax call here
}
return false;
});
You also need to modify your controller code. Currently your last line (return Json(true);) can never be executed, and the code in the if block is always returning an error, so will always go to the .fail() function. Generally, you should not return the specific details of exceptions your code throws (this just exposes it to malicious users) and it is better to return a more general error, or return null and hard code the error message in the script. There are various ways you can handle this, including
try
{
// delete the file
return Json(true); // to indicate success
}
catch (.... )
{
return Json(null); // indicate failure
}
which in the script means
if (response) { // this will be executed if you returned true
... // delete the row
} else { // this will be executed if you returned null
... // display a message to the user
}
and the .fail() function will be executed if an exception is throw on the server which you have not caught.
Alternatively you could return on object with properties for Success and Message which gives you a bit more control over the message, for example
return Json(new { Success = true });
// or
return Json(new { Success = false, Message = "The file does not exist"});
// or
retrn Json(new { Success = false, Mesage = "The file is in use, please try again late" });
and the in the script
$.post(url, formData, function (response) {
if (response.Success == 'False') {
var message = response.Message; // display it

Call form OnFailure handler from controller, ASP.NET MVC

I'd so appreciate if someone could advise.
I have an Ajax form:
#using (Ajax.BeginForm("Edit", null, new AjaxOptions() {
UpdateTargetId = updateRegion,
InsertionMode = InsertionMode.Replace,
OnFailure = "formFailure" }))
{}
Its UpdateTargetId differs based on the current User role:
#{
if (curUser.IsInputer)
{
updateRegion = "content";
}
else if (curUser.IsAuthorizer)
{
updateRegion = "mainPane";
}
}
If the modelstate is invalid I'd like to return view in mainPane always:
<script>
function formFailure(result)
{
$("#mainPane").html(result.responseText);
}
</script>
However onFailure is not called when ModelState is invalid. For this, I set error code in controller:
public ActionResult Edit(ContractModel entity)
{
if(ModelState.IsValid)
{
if(curUser.isInputer) { return RedirectToAction("SomeAction");}
if(curUser.Authorizer) { return RedirectToAction("OtherAction");}
}
Response.StatusCode = 500;//internal server error to fire OnFailure of form
return PartialView(entity);
}
Then I get the desired result, i.e. I see the model with its errors in mainPane div and internal server error in browser console. However, it works this way when I run the application locally, when I publish and run it on the server, I see the error 500 Internal server error, instead of the partial view. Is there any workaround?
EDIT:
As an option I tried to check Model for errors in form OnSuccess handler:
var isValid = #Html.Raw(Json.Encode(ViewData.ModelState.IsValid));
if (!isValid) {
$("#mainPane").html(result.responseText);
}
But I still get the view in "content" div. Why is this happening?
Here is an option:
Dont use a 500 status error. That is for reporting something going very wrong with your application.
That is not what has happened here, you simply have a form validation error.
Update your server side code to this:
public ActionResult Edit(ContractModel entity)
{
if(ModelState.IsValid)
{
if(curUser.isInputer) { return RedirectToAction("SomeAction");}
if(curUser.Authorizer) { return RedirectToAction("OtherAction");}
}
if(!ModelState.IsValid)
{
entity.FormError = true; // you will need to add this attribute to your model
}
return PartialView(entity);
}
Then in your partial view, put something like this:
if(Model.FormError)
{
#Html.HiddenFor(m => m.FormError)
}
Change your form to handle the OnComplete event instead of OnFailure event. This is because your ajax post is not failing. Its returning successfully and reporting there was a validation error with the forms input:
#using (Ajax.BeginForm("Edit", null, new AjaxOptions() { OnSuccess = "handleFormResponse" }))
{}
Change your handler script to something like this. This will check the response to see if contains a form error
<script>
function handleFormResponse(result)
{
$(result).find("#FormError").length > 0)
{
$("#mainPane").html(result);
}
else
{
$("##updateRegion").html(result);
}
}
</script>
This is likely happening because the server is interpreting the 500 as an unhanded error and returning the default 500 error since your customErrors setting in the web config likely gets flipped to false when you publish the application in release mode.
Edit: Removed mention of using another status code as it caused more confusion and does not solve the problem.

Fields still contain form data when new model instance is passed to partial view

I have a partialview which post to server using jquery. The posting is working. However, when returning, I am returning partialviewresult with blank model (new model), but the return HTML is still containing the data previously post. Any idea on clearing the data on the return?
$("#btnSend").click(function (e) {
e.preventDefault();
if($('#frmCompose').valid()) {
$.ajax({
type: "POST",
url: '#Url.Action("PartialCompose", "Message")',
dataType: "html",
data: $('#frmCompose').serialize(),
success: function (result) {
$("#divTab2").html(result);
},
error: function (xhr, s, e) {
alert('Error');
alert(xhr.responseText);
}
});
}
});
Here is the Action:
[SessionExpireFilterAttribute]
[HttpPost] // POST: /message/partialcompose
public PartialViewResult PartialCompose(_MessageExt model)
{
_MessageExtDAL __DAL = new _MessageExtDAL(base.LoginTimeZoneMin);
try
{
model.MessageId = 0;
model.AccountId = base.LoginUser.AccountId;
model.EditBy = base.LoginUser.UserId;
__DAL.Send(model, Config.SQLConn);
}
catch (Exception ex)
{
base.Prompt = ex.Message;
}
finally
{
__DAL = null;
}
return PartialView(new _MessageExt());
}
This is due to the fact that you are returning the same view directly from the POST action and the form values still exist in ModelState.
This is by design, primarily for the purpose of displaying validation errors in conjunction with the original form data. Values in ModelState have a higher precedence than those in the model object passed to the view, so if both exist, the values in ModelState will be used.
The following should see the fields cleared:
ModelState.Clear();
return PartialView(new _MessageExt());

Is there anyway to return raw text instead of html from a server side exception in asp.net-mvc on an ajax call?

i see that i get a big html text when i return a HttpException() from an ajax call.
if i do something like this in my controller:
if (errors.Count > 0)
{
throw new HttpException(404, "This is my custom error msg");
}
i want a nice simple way to parse out that Error Message on the javascript side. Right now when i look at the callback on the client side with something like this
function decodeErrorMessage(jqXHR, textStatus, errorThrown) {
i see that jqxHR = "3", textStatus = a very long html doc (with a a call stack and error message and errorThrown is "error"
what is the best way to simply pass back and show an error from an http exception?
Instead of throwing the exception in the controller, catch it and set the response code for the AJAX response:
View logic setup with JQuery:
$("#btnRefresh").live("click", function(e) {
$.ajax({
type: "POST",
url: '#Href("~/Home/Refresh")',
data: "reportId=#Model.Id"
})
.done(function(message) {
alert(message);
})
.fail(function(serverResponse) {
alert("Error occurred while processing request: " + serverResponse.responseText);
});
e.preventDefault();
});
Then in your Controller code you catch the exception instead of throwing it:
[HttpPost, VerifyReportAccess]
public ActionResult Refresh(Guid reportId)
{
string message;
try
{
message = _publisher.RequestRefresh(reportId);
}
catch(Exception ex)
{
HttpContext.Response.StatusCode = (Int32)HttpStatusCode.BadRequest;
message = ex.Message;
}
return Json(message);
}

Categories

Resources