ASP.NET MVC and DataTables, cannot redirect to custom error page - datatables

I have a problem when combining ASP.NET custom errors and use DataTables in a view.
If DataTables tries to get data from the server and an error occurs, first, the error is caught by ASP.NET MVC in Application_Error and from there it is passed to the Index action in the ErrorController with the exception information.
However, the error page is not displayed, instead the browser jumps to the DataTables error handling in javascript.
I can force the page to be displayed, as in the code below (window.location), but I would like the error page with the original exception to be displayed. If I omit the window.location part, no error page is displayed, even though the Index action in ErrorController is triggered.
Here is my code:
Global.asax:
protected void Application_Error(object sender, EventArgs e)
{
Exception exception = Server.GetLastError();
Server.ClearError();
// Redirect to error page
var routeData = new RouteData();
routeData.Values.Add("controller", "Error");
if (exception.GetType() == typeof(HttpException))
{
// Save error log
Log.WriteLog(exception, "", Session["userName"] as string);
var code = ((HttpException)exception).GetHttpCode();
if (code == 404)
{
routeData.Values.Add("action", "NotFound");
}
else if (code == 403 || code == 401)
{
routeData.Values.Add("action", "NotAuthorized");
}
else
{
routeData.Values.Add("action", "BadRequest");
routeData.Values.Add("statusCode", 400);
}
}
else
{
routeData.Values.Add("action", "Index");
routeData.Values.Add("statusCode", 500);
}
routeData.Values.Add("exception", exception);
// Redirect to error pages
Response.TrySkipIisCustomErrors = true;
IController controller = new ErrorController();
controller.Execute(new RequestContext(new HttpContextWrapper(Context), routeData));
Response.End();
}
ErrorController:
public ActionResult Index(dynamic exception, int? statusCode = 500)
{
Response.StatusCode = statusCode.Value;
Response.TrySkipIisCustomErrors = true;
ViewBag.Message = (exception is String[]) ? exception[0] : exception.Message;
return View("Error500");
}
DataTables error handling:
$.fn.dataTable.ext.errMode = 'throw';
$("#tblMSLs").on('error.dt', function (e, settings, techNote, message) {
console.log('Error in DataTables tblMSLs: ', message);
window.location.href = `${webOrigin}Error?exception=${message}&statusCode=500`;
});

Related

Net Maui android app shows blank page after login (Azure B2C) the next time after a successful login and logout

I am doing a login flow in .net maui, first time I try the flow works properly showing the landing page, after a successful logout, I try login again and one blank page is shown instead of the landing page.
I am using shell and the login code is the following
public partial class LoginPageViewModel : BaseViewModel
{
private readonly IAuthNService _authNService;
public LoginPageViewModel(IAuthNService authNService)
{
_authNService = authNService;
}
[ICommand]
async void Login()
{
try
{
var result = await _authNService.LoginAsync(CancellationToken.None);
var token = result?.IdToken;
if (token != null)
{
var handler = new JwtSecurityTokenHandler();
var data = handler.ReadJwtToken(token);
var claims = data.Claims.ToList();
if (data != null)
{
await Shell.Current.GoToAsync($"//{nameof(DashboardPage)}", true);
}
}
}
catch (MsalClientException ex)
{
await Toast.Make(ex.Message).Show();
}
catch (Exception ex)
{
await Toast.Make(ex.Message).Show();
}
}
}

Custom 401 Error Working Locally but not on server

I have defined my Custom Error pages in my global.asax.cs and can get them working locally in IIS Express with VS 2013, but when I publish to the server running IIS 7 the 401 and 403 errors do not display the custom error page but rather return a 500 and not the custom 500 page either. I've tried many solutions found here on SO but none have worked. Anyone have any thoughts?
Web.Config portions:
<system.web>
<customErrors mode="On" defaultRedirect="~/Error">
</customErrors>
</system.web>
<system.webServer>
<httpErrors existingResponse="PassThrough" />
</system.webServer>
Global.asax portions:
protected void Application_Error()
{
string descriptor = null;
string errMsg = null;
string additionalInfo = null;
string title = null;
Exception lastError = null;
Exception ex = null;
if (HttpContext.Current.Server.GetLastError() != null)
{
ex = HttpContext.Current.Server.GetLastError().GetBaseException();
if (ex.GetType() == typeof (HttpException))
{
HttpException httpException = (HttpException) ex;
switch (httpException.GetHttpCode())
{
case 404:
title = "Not Found";
descriptor = "Page Not Found";
errMsg =
"The page you requested could not be found, either contact your webmaster or try again. Use your browsers Back button to navigate to the page you have prevously come from.";
additionalInfo =
"We are working hard to correct this issue. Please wait a few moments and try your search again.";
lastError = new Exception(errMsg);
break;
case 500:
title = "Server Error";
descriptor = "Oooops, Something went wrong!";
errMsg = "You have experienced a technical error. We apologize.";
additionalInfo =
"We are working hard to correct this issue. Please wait a few moments and try your search again.";
lastError = new Exception(errMsg);
break;
}
CallErrorController(descriptor, additionalInfo, title, lastError, httpException.GetHttpCode());
}
}
}
protected void Application_EndRequest(object sender, EventArgs e)
{
if (Response.StatusCode == 401 || Response.StatusCode == 403)
{
Response.ClearContent();
string additionalInfo = null;
string title = null;
const string errMsg =
#"You have attempted to access a resource for which you do not have the proper authorization or which is not available from your location.
If you received this message by clicking on a link on this website, please report it to the webmaster. Use your browsers Back
button to navigate to the page you have previously come from.";
const string descriptor = "Access is Denied";
title = descriptor;
Exception lastError = new Exception(errMsg);
CallErrorController(descriptor, additionalInfo, title, lastError, Response.StatusCode);
}
}
private void CallErrorController(string descriptor, string additionalInfo, string title, Exception lastError, int statusCode)
{
Server.ClearError();
Response.TrySkipIisCustomErrors = true;
HttpContextWrapper contextWrapper = new HttpContextWrapper(this.Context);
RouteData routeData = new RouteData();
routeData.Values.Add("controller", "Error");
routeData.Values.Add("action", "Index");
routeData.Values.Add("statusCode", statusCode);
routeData.Values.Add("exception", lastError);
routeData.Values.Add("descriptor", descriptor);
routeData.Values.Add("additionalInfo", additionalInfo);
routeData.Values.Add("title", title);
routeData.Values.Add("isAjaxRequet", contextWrapper.Request.IsAjaxRequest());
IController controller = new ErrorController();
RequestContext requestContext = new RequestContext(contextWrapper, routeData);
controller.Execute(requestContext);
Response.End();
}
ErrorController:
public class ErrorController : Controller
{
public ActionResult Index(int statusCode, Exception exception, string descriptor, string additionalInfo, string title, bool isAjaxRequet)
{
if (!isAjaxRequet)
{
ErrorModel model = new ErrorModel { HttpStatusCode = statusCode, Exception = exception, Descriptor = descriptor, AdditionalInfo = additionalInfo, Title = title};
return View("Error", model);
}
else
{
// Otherwise, if it was an AJAX request, return an anon type with the message from the exception
var errorObjet = new { message = exception.Message };
return Json(errorObjet, JsonRequestBehavior.AllowGet);
}
}
}
Running Locally:
Running From the Server:
Try adding this to your web.config
<configuration>
... //all the other gubbins that goes in your web config
<system.webServer>
<httpErrors errorMode="Detailed" />
</system.webServer>
</configuration>
Or turning on detailed errors via the iis adamin module

How to write Unit test for Action that throw HttpException with StatusCode 404

I have a below action in a controller which throw HttpException with status code 404:
public async Task<ActionResult> Edit(int id)
{
Project proj = await _service.GetProjectById(id);
if( proj == null)
{
throw new HttpException(404, "Project not found.");
}
}
To test this scenario, I have written below test case where I am catching AggregationException and rethrowing InnerException which is expected as HttpException:
[TestMethod]
[ExpectedException(typeof(HttpException),"Project not found.")]
public void Edit_Project_Load_InCorrect_Value()
{
Task<ActionResult> task = _projectController.Edit(3);
try
{
ViewResult result = task.Result as ViewResult;
Assert.AreEqual("NotFound", result.ViewName, "Incorrect Page title");
}
catch (AggregateException ex)
{
throw ex.InnerException;
}
}
This test run succefully and return ExpectedException. I have two questions here:
Is this right approach for writing unit test or there is more
gracious way of testing it.
Is this possible to check in Unit Test
that user is getting correct error page( NotFound in this case).
There is a nicer way to test this. We wrote a class called AssertHelpers.cs that has this method in it. The reason this is nicer than ExpectedException is that ExpectedException does not actually verify it was thrown, it just allows the test to pass when it is thrown.
For example, if you change your 404 code to return 200 your test will not fail.
public static void RaisesException<TException>(Action dataFunction, string exceptionIdentifier = null)
{
bool threwException = false;
try
{
dataFunction();
}
catch (Exception e)
{
threwException = true;
Assert.IsInstanceOfType(e, typeof(TException));
if (exceptionIdentifier != null)
Assert.AreEqual(exceptionIdentifier, e.Message);
}
if (!threwException)
Assert.Fail("Expected action to raise exception with message: " + exceptionIdentifier);
}

How to make action to be called by only the mvc application, Not by direct url request

In my mvc application I handle 404 and 505 errors by checking response code in Application_EndRequest() method in global.asax.cs and returning custom 404/error page like below
protected void Application_EndRequest()
{
if (Context.Response.StatusCode == 404)
{
Response.Clear();
var rd = new RouteData();
rd.Values["controller"] = "Errors";
rd.Values["action"] = "PageNotFound";
IController c = new ErrorsController();
c.Execute(new RequestContext(new HttpContextWrapper(Context), rd));
}
else if (Context.Response.StatusCode == 500)
{
Response.Clear();
var rd = new RouteData();
rd.Values["controller"] = "Errors";
rd.Values["action"] = "ServerError";
IController c = new ErrorsController();
c.Execute(new RequestContext(new HttpContextWrapper(Context), rd));
}
}
}
And I have Errors controller like below
public ActionResult PageNotFound()
{
Response.StatusCode = (int)HttpStatusCode.NotFound;
return View();
}
public ActionResult ServerError()
{
Response.StatusCode = (int)HttpStatusCode.InternalServerError;
return View("Error");
}
Now when I type the url in addressbar like http://mydomain/errors/servererror, It displays my current custom error page
So I want to prevent user by accessing this page, so how can I accomplish that?
P.S.
So far I have tried to solve this by configuring route named "Errors/ServerError" and redirecting it to somewhere else, but I think there may be another good way out there..

How do I log EntityValidation errors using ELMAH MVC?

I've been writing an application using MVC4 and EF5.x, and using ELMAH for logging exceptions for review. We recently released the application, and as expected the ELMAH log filled up with several dozen exceptions. Great (and not)! The problem is that one of those exceptions is
System.Data.Entity.Validation.DbEntityValidationException
Validation failed for one or more entities.
See 'EntityValidationErrors' property for more details.
Of course, there's no way to see the EntityValidationErrors property for more details and the stack trace wraps up to my SubmitChanges()
I know ELMAH has the capability of allowing us to raise our own exceptions, and in some way customize what gets logged and how. Unfortunately, I'm still very new to ELMAH and MVC and a Google search didn't turn up anything relevant. I did find a blog article on logging EntityValidationErrors, and the author specifically mentioned that he would post how to do so in ELMAH but that was posted in September of 2012 and I didn't see anything since then.
Any help would be greatly appreciated!
Probably the best thing to do in this case would be to wrap your context.SaveChanges(); call in a try...catch block and then log the individual items from the ValidationExceptions. Something like the following should get you started:
try
{
context.SaveChanges();
}
catch (DbEntityValidationException ve)
{
var error = ve.EntityValidationErrors.First().ValidationErrors.First();
var msg = String.Format("Validation Error :: {0} - {1}",
error.PropertyName, error.ErrorMessage);
var elmahException = new Exception(msg);
Elmah.ErrorSignal.FromCurrentContext().Raise(elmahException);
}
How about this extension method based on the above..
public static void SaveChangesWithBetterValidityException(this DbContext context)
{
try
{
context.SaveChanges();
}
catch (DbEntityValidationException ve)
{
var errors = new List<string>();
foreach (var e in ve.EntityValidationErrors)
{
errors.AddRange(e.ValidationErrors.Select(e2 => string.Join("Validation Error :: ", e2.PropertyName, " : ", e2.ErrorMessage)));
}
var error = string.Join("\r\n", errors);
var betterException = new Exception(error, ve);
throw betterException;
}
}
Elmah will then have a much better exception in it's log
I added the following to my Global.asax.cs in order to forward all DbEntityValidationException exceptions to Elmah across my MVC application:
private void ElmahEntityValidationException()
{
var dbEntityValidationException = Server.GetLastError() as DbEntityValidationException;
if (dbEntityValidationException != null)
{
var errors = new List<string>();
foreach (var entityError in dbEntityValidationException.EntityValidationErrors)
{
errors.AddRange(entityError.ValidationErrors.Select(e2 => string.Join("Validation Error :: ", e2.PropertyName, " : ", e2.ErrorMessage)));
}
var error = string.Join("\r\n", errors);
var betterException = new Exception(error, dbEntityValidationException);
Elmah.ErrorSignal.FromCurrentContext().Raise(betterException);
}
}
protected void Application_Error(object sender, EventArgs e)
{
ElmahEntityValidationException();
}
Some of this code was reused from #Paige Cook's and #Original10's posts.
Re-throwing as per the code below is not perfect (although I don't mind resetting the call stack here, as Elmah's logged details of the address posted to will show me what lead to the exception) and you will have to work out your own security implications, but this is fairly concise & meets my needs:
try
{
return base.SaveChanges();
}
catch (DbEntityValidationException e)
{
var de = new DetailedEntityValidationException(e);
throw de;
}
public class DetailedEntityValidationException : Exception
{
public DetailedEntityValidationException(DbEntityValidationException ve)
: base(ve.Message + ":\r\n\t-" + string.Join(new string('-',20) + "\r\n\t-", ve.EntityValidationErrors.Select(ev=>string.Join("\r\n\t-",ev.ValidationErrors.Select(e=>e.ErrorMessage)))))
{}
}
Here is my implementation for Global Web API solution for Elmah and EF Validation errors:
public class ElmahHandleWebApiErrorAttribute : ExceptionFilterAttribute
{
public override void OnException(HttpActionExecutedContext context)
{
var e = context.Exception;
// Try parse as entity error (i'm not sure of performance implications here)
var efValidationError = e as DbEntityValidationException;
if (efValidationError == null)
{
RaiseErrorSignal(e);
}
else
{
RaiseEntityFrameWorkValidationErrorSignal(efValidationError);
}
}
private static bool RaiseErrorSignal(Exception e)
{
var context = HttpContext.Current;
if (context == null)
return false;
var signal = ErrorSignal.FromContext(context);
if (signal == null)
return false;
signal.Raise(e, context);
return true;
}
private static bool RaiseEntityFrameWorkValidationErrorSignal(DbEntityValidationException e)
{
var context = HttpContext.Current;
if (context == null)
return false;
var signal = ErrorSignal.FromContext(context);
if (signal == null)
return false;
//Taken from post above
var errors = new List<string>();
foreach (var entityError in e.EntityValidationErrors)
{
errors.AddRange(entityError.ValidationErrors.Select(e2 => string.Join("Validation Error :: ", e2.PropertyName, " : ", e2.ErrorMessage)));
}
var error = string.Join("\r\n", errors);
var betterException = new Exception(error, e);
signal.Raise(betterException, context);
return true;
}
}
and then I register the attribute in the WebApiConfig.cs file under App_Start
config.Filters.Add(new ElmahHandleWebApiErrorAttribute());