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

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

Related

Handle exception in Html.BeginForm MVC

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/

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

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.

MVC #Html.ActionLink no-op from controller

I have an #Html.ActionLink inside of a partial view that when clicked I'd like to have either send the user to another view or stay on the current view without changing anything. Is this possible?
Our controller looks like:
public ActionResult Edit(int id)
{
if (ShouldAllowEdit(id))
{
return this.View("Edit", ...edit stuff...)
}
return ????????
}
We tried return new EmptyResult(); but that just dumps the user to a blank page.
This is a little different approach to the issue, but it should do what you want.
Instead of giving the user a link to navigate to, do an ajax call on link/button click, and do the id check. Return either the url to navigate to in a JsonResult, or nothing if the id is invalid.
On return of the ajax call, navigate to the url if appropriate.
(swap out the hard coded ids and the == 0 with your ShouldAllowEdit function in the example of course)
In the View:
<div class="btn btn-danger" id="myButton">Button</div>
#section scripts{
<script>
$("#myButton").click(function () {
$.ajax("#Url.Action("Edit", new { id = 0 })", { type : "POST" })
.success(function (data) {
if (data.url !== "") {
window.location.href = data.url;
}
});
});
</script>
}
In the controller:
[HttpPost]
public JsonResult Edit(int id)
{
if (id == 0)
{
return Json(new {url = ""});
}
else
{
return Json(new { url = Url.Action("EditPage", new { id = id }) });
}
}
An answer is to redirect to the view action - and maybe give some feed back why they failed.
public ActionResult Edit(int id)
{
if (ShouldAllowEdit(id))
{
return this.View("Edit", ...edit stuff...)
}
ModelState.AddModelError("id", "Not allowed to edit this item");
return RedirectToAction(Edit(id));
}
If the user clicks a link they will be taken away. They might be sent back right to the same page, but the page will unload, be requested from the server again, and then re-rendered in the browser. If you don't want that to happen, you don't give the user the link in the first place. In other words, conditionally render the link or not based on the user's roles or whatever.
#if (userCanEdit)
{
#Html.ActionLink(...)
}
Where userCanEdit is whatever logic you need to make that determination.
If the user fails whatever check you determine, then they don't get the link. Simple.
However, since there's malicious people in the world, you can't just leave it entirely there. There's potential for the user to figure out the link to edit something and go there manually. So, to prevent that you check for the edit permission in your action (like you've already got in your code sample), but if the user is not allowed, then you just return a forbidden status code:
return new HttpStatusCodeResult(HttpStatusCode.Forbidden);
Or
return new HttpStatusCodeResult(403);
They both do the same thing.
UPDATE
Based on your comment above, it appears that the user is normally allowed to edit but can't in a particular instance because another user is editing. A 403 Forbidden is not appropriate in that case, so really all you've got is a simple redirect back to the page they were on, perhaps with a message explaining why they're back there.
TempData["EditErrorMessage"] = "Sorry another user is editing that right now.";
return RedirectToAction("Index");

How to return file to user and update UI

My problem is following:
I have mvc3 project. Problem is in the excel sheet load page. First I want to upload excel sheet to database. Next I create pdf based on that excel sheet and return that pdf to user. Last I want to update rows in ui, that new excel sheet has loaded.
So the main problem is that I cant update rows after passing the pdf-document to user.
This is short example what I tried. First my client side of upload. (I am using Microsoft.Web.Helpers.FileUpload component):
#using (Html.BeginForm("NewFile", "LoadData", FormMethod.Post, new { enctype = "multipart/form-data" }))
{
#FileUpload.GetHtml(initialNumberOfFiles: 1, allowMoreFilesToBeAdded: false, includeFormTag: false, addText: "Add Files", uploadText: "Upload File")
<input type="submit" name="submit" id="submit" value="Lataa tiedot" />
}
After user has pressed submit button should happen that functionality what I introduced before in my text. This is how I load excel sheet and create pdf-document:
[HttpPost]
public ActionResult NewFile(IEnumerable<HttpPostedFileBase> fileUpload, bool checkPrintAlso, bool chkBigCards, int hiddenCustomer, string comment)
{
try
{
foreach (var file in fileUpload)
{
if (file != null && file.ContentLength > 0)
{
var excelService = new ExcelService();
var trackingCodes = excelService.NewLoadData(file, User.Identity.Name, comment, hiddenCustomer);
if (checkPrintAlso)
{
CreatePdf(trackingCodes, chkBigCards);
return new EmptyResult();
}
}
return RedirectToAction("Index", error);
}
catch (Exception e)
{
}
}
If I return something else than null or emptyResult pdf does not anymore return to user. This is how I createPdf and write data to response:
[HttpPost]
public void CreatePdf(IEnumerable<TrackingCode> trackingCodes, bool isBig)
{
//This creates pdf
var blackGinBarCodePdf = new BlackGinBarcodePdf(trackingCodes, isBig);
Response.ContentType = "application/pdf";
Response.AddHeader("Content-Disposition", string.Format(CultureInfo.InvariantCulture, "attachment;filename=Receipt-{0}.pdf", "briefcases"));
Response.BinaryWrite((byte[])blackGinBarCodePdf);
}
I have tried to do handling jquery and ajax that I could proceed handling after that pdf document ends up to the user, but when I do for example ajax that posting operation it is not returning succeed message. It will return error code 0 on Mozilla and IE returns code 500(internal server error). This is how I have tried to do post on ajax:
$("#submit").click(function ()
{
$.ajax(
{
url: "/LoadData/NewFile",
type: 'post',
error: function (xhr, ajaxOptions, thrownError)
{
alert(xhr.status);
alert(thrownError);
},
success: function (data) { }
})
//add row to ui!
;
});
So hope that someone understand my problem. Once more, Step what I need to do after clicking button.
1. LoadExcel to db
2. Parsing excel to pdf
3. Return pdf to user.
4. update ui
(Problem is updating ui after returning pdf to user)
I really preciated, if someone can help me. Im really confused how can I do that?
You can't return file and some other data in the one response (well, actually you can do it but it won't be correctly handled by browser). Correct way is to:
Save your PDF (or just excel) somewhere on the server (filesystem /
database)
Return JSON (or some other result) with result status and some ID of your file
Make request from the client (not AJAX, just simple change window.location or post some form to some URL) to some URL with your file ID
Return FileResult (http://msdn.microsoft.com/en-us/library/system.web.mvc.fileresult.aspx) from action for this URL

Categories

Resources