Errors with custom HandleErrorAttribute does not work - asp.net-mvc-3

I've read many articles and several post (including here in stackoverflow) but do not know what I'm doing wrong.
Here my code:
Global.asax.cs
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new HandleErrorAttribute());
}
ErrorControler.cs
public class ErrorController : Controller
{
public ActionResult Error404()
{
return View();
}
public ActionResult Error500()
{
return View();
}
}
Web.config
<customErrors mode="On" defaultRedirect="~/Error/Error500">
<error statusCode="404" redirect="~/Error/Error404"/>
</customErrors>
MyController.cs
public ActionResult Index()
{
using (var db = new DataContext())
{
int a = 2, b = 0;
var r = a / b;
return View(r);
}
}
Error500.cshtml
#model System.Web.Mvc.HandleErrorInfo
#{
ViewBag.Title = "Erro";
}
<h2>#ViewBag.Title</h2>
<div id="error-info">
<p>Ocorreu um erro inexperado na página <a class="error-url" href="#Response["aspxerrorpath"]" title="Origem do erro">#Response["aspxerrorpath"]</a></p>
<p>Se o erro persistir, por-favor, entre em contato com os administradores do site.</p>
<div class="error-details">
<p>#Model.Exception.Message</p>
</div>
</div>
When I try to access the path /MyController the following message appears:
Server Error in '/' Application.
The resource cannot be found.
Description: HTTP 404. The resource you are looking for (or one of its
dependencies) could have been removed, had its name changed, or is
temporarily unavailable. Please review the following URL and make
sure that it is spelled correctly.
Requested URL: /Error/Error500
I would like that happen when an error on any controller, if the http status code has not been informed in web.config, it redirects to the default view Error500
In this article, for example, it handles DbException errors, but would like to handle any type of error.
Errors of type 404 (page not found) works perfectly. The user is redirected to the page Error404.cshtml

If you want this to happen remove/comment the following line from your Global.asax:
filters.Add(new HandleErrorAttribute());
You basically have to choose whether you want ASP.NET to handle your errors (the <customErrors> section in your web.config) or ASp.NET MVC (the global HandleErrorAttribute action filter, which by the way requires you to turn on custom errors in web.config)
Or checkout an alternative method for handling errors in ASP.NET MVC (with this approach you still have to remove the line I showed from your Global.asax).

Related

HttpContext.Session.Abandon() doesn't work in MVC core. Session.clear doesn't log my user out

I get an error that says "ISession does not contain a definition for 'Abandon' and no accessible extension method 'Abandon' accepting a first argument of type 'ISession' could be found".
I have tried using session.clear but even after logging out if I open the website the user is logged in.
This is the error I get
This is how I have implemented Session in my ASP .NET CORE project:
Create a SessionTimeout filter:
public class SessionTimeout : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext context)
{
if (context.HttpContext.Session == null ||!context.HttpContext.Session.TryGetValue("UserID", out byte[] val))
{
context.Result =
new RedirectToRouteResult(new RouteValueDictionary(new
{
controller = "Pages",
action = "SessionTimeout"
}));
}
base.OnActionExecuting(context);
}
}
Register this filter in your Startup.cs:
In your ConfigureServices method:
services.AddSession(options =>
{
options.IdleTimeout = TimeSpan.FromMinutes(10);
});
In your Configure add:
app.UseSession();
And finally decorate your class/method with your filter like:
[SessionTimeout]
public class DashboardController : Controller
To destroy your session based on Logout event from your View:
public IActionResult Logout()
{
HttpContext.Session.Clear();
return RedirectToAction("Login", new { controller = "Pages" });
}
It seems my session is being stored in cookies and not getting cleared/deleted when used session.clear()
so I've used this and it seems to work like a charm.
foreach (var cookie in Request.Cookies.Keys)
{
if (cookie == ".AspNetCore.Session")
Response.Cookies.Delete(cookie);
}
HttpContext.Session.Clear() wasn't working for me on my live site in the Controller for my Account/Logout page.
I found out that setting a href of /Account/Logout/ was the problem. I changed my links to /Account/Logout.
If you're having Session problems in .NET Core 3.1 then try this. It may also account for why I couldn't get Cookie Authentication to work - I gave up in the end and switched to using Sessions.

"Down for Maintenance" page for Asp.net MVC web app

I have the following requirements
Set a value in web.config and enable maintenance mode
All non-ajax requests should be shown a custom error page, with the http status code set to 503. The url of page should be retained.
All ajax requests should be responded with http status code 503
I should have an opportunity to do some basic logging to a file. Log the url and the user Identity if he happened to be logged into the app
I am using ELMAH to track/log all unhandled exceptions. The mechanism for implementing maintenance mode shouldn't need me to not use ELMAH
I have "runAllManagedModulesForAllRequests" set to true. this was originally done for use with RequestReduce. we no longer use it, but I am hesitant to reset its value to false. I am not sure if any other library needs it.
Once I realized there is nothing built in which supports the above requirements, I felt I had the following two options in front of me (App_offile.html won't work for me).
an HttpModule
an MVC ActionFilter
I dropped the MVC ActionFilter as I couldn't figure out how to guarantee it to run before any authentication/authorization filters. I have a custom authentication filter which hits the db. The idea behind the maintenance mode is the db might be offline, yet the web-app shouldn't show a 500 custom error page, but a 503 custom error page.
I wrote the following httpmodule and added in my web.config. It works for ajax requests. It kinda works for non-ajax requests. All requests get redirected to the 503 error page. The side-effect is all requests for static content also result in a 503. My error page thus is shown unstyled :(
// the http module
public class MaintenanceModeModule : IHttpModule
{
private static bool _isUnderMaintenance;
static MaintenanceModeModule()
{
var valueStr = (ConfigurationManager.AppSettings["UnderMaintenance"] ?? (false).ToString());
bool underMaintenance;
bool.TryParse(valueStr, out underMaintenance);
_isUnderMaintenance = underMaintenance;
}
public void Init(HttpApplication application)
{
application.BeginRequest += OnBeginRequest;
}
private void OnBeginRequest(object sender, EventArgs e)
{
var application = (HttpApplication) sender;
var request = application.Request;
var response = application.Response;
if (_isUnderMaintenance == false)
{
return;
}
application.Context.Items["under_maintenance"] = true; // used later
if (request.Url.PathAndQuery == "/503") // the url of the action that renders the custom error page
{
return;
}
const int statusCode = (int) HttpStatusCode.ServiceUnavailable;
const string statusMessage = "Temporarily down for maintenance";
var requestWrapper = new HttpRequestWrapper(request);
if (requestWrapper.IsAjaxRequest())
{
response.Clear();
response.ClearContent();
response.ClearHeaders();
response.StatusCode = statusCode;
response.TrySkipIisCustomErrors = true;
response.StatusDescription = statusMessage;
response.End();
return;
}
// doesn't work, shows the Yellow Screen of Death (YSoD)
// application.Context.Server.Transfer("~/503", preserveForm: true);
// doesn't work, shows the Yellow Screen of Death (YSoD)
// throw new HttpException(statusCode, statusMessage);
response.Redirect("~/503");
}
public void Dispose()
{
}
}
...
// web.config
// only the relevant portions of each section is shown
<appSettings>
<add key="UnderMaintenance" value="true" />
</appSettings>
<customErrors mode="On"> <!-- Custom errors are on, even then I was seeing YSoDs during my attempts -->
<error statusCode="404" redirect="404" />
<error statusCode="503" redirect="503" />
</customErrors>
<system.webServer>
<httpErrors existingResponse="PassThrough">
</httpErrors>
<modules runAllManagedModulesForAllRequests="true">
<add name="MaintenanceMode" type="WebApp.Code.MvcInfrastructure.MaintenanceModeModule" />
</modules>
</system.webServer>
...
// route config
routes.MapRoute("503Error", "503", new { controller = "Error", action = "UnderMaintenance" });
...
// error controller
// the authentication filter skips authentication if the allowanonymous attribute is present
[AllowAnonymous]
public class ErrorController : CustomBaseController
{
public ErrorController(AppConfig appConfig)
: base(appConfig)
{
}
public ActionResult UnderMaintenance()
{
// behind the scenes reads the value from HttpContext.Items.
// This was set during the execution of the httpmodule
if (AppConfig.UnderMaintenance == false)
{
return new RedirectResult("~/");
}
Response.StatusCode = (int) HttpStatusCode.ServiceUnavailable;
Response.TrySkipIisCustomErrors = true;
// the actual content of the view is not relevant now
return View("Error503");
}
}
The problems with this approach,
Each non-ajax request is responded with a 302 and then a 503
The URL requested by the browser is not retained
It returns a 503 for all static assets as well
The code I wrote and web.config settings I enabled are all cobbled together from various sources. I am not fully sure what those settings do or what the recommended way is. Please feel free to answer with a completely different method, as long as it can meet the requirements stated.

keep getting The view "Error" not found when using Elmah and asp.net mvc 4

I am using Elmah 1.2 as the logging framework for my asp.net mvc 4 application.
in the web.config file, I set customErrors mode to on.
<customErrors mode="On" defaultRedirect="/Error">
<error statusCode="404" redirect="/Error/NotFound" />
</customErrors>
I also created a custom HandleErrorAttribute, copied the code from this link.
http://joel.net/logging-errors-with-elmah-in-asp.net-mvc-3--part-4--handleerrorattribute
In my Home controller, i just throw an exception to test the logging framework.
public ActionResult About()
{
throw new Exception("this is a buggggggggggggg");
ViewBag.Message = "Your app description page.";
return View();
}
"this is a buggggggggggggg" is logged in the database, great, it works. then there's another error also logged, and I didnt expect that to happen.
The view 'Error' or its master was not found or no view engine supports the searched locations. The following locations were searched: ~/Views/Home/Error.aspx ~/Views/Home/Error.ascx ~/Views/Shared/Error.aspx ~/Views/Shared/Error.ascx ~/Views/Home/Error.cshtml ~/Views/Home/Error.vbhtml ~/Views/Shared/Error.cshtml ~/Views/Shared/Error.vbhtml
Update:
follow Tim's suggestion, then it causes another issue.
If I create a Error.cshtml in the shared folder. when unhandled exception happens, it will show this Error.cshtml file, not "/Error" page. I have customErrors enabled. They should all get redirected to "/Error" page.
We created an empty MVC5 app and added ELMAH to it. We also were receiving the extra error you described even though we did not add the HandleErrorAttribute. After some research I found the nuget package Elmah.MVC which adds some additional configuration settings. In the appSettings section of web.config you will find these 2 lines:
<appSettings>
<add key="elmah.mvc.disableHandler" value="false" />
<add key="elmah.mvc.disableHandleErrorFilter" value="false" />
</appSettings>
These 2 keys default to "false". I changed their values to "true" and the extra logged exception went away.
I am developing an application using ASP.NET MVC 5 RC and I use Elmah too for error logging. I am using too a custom error handling attribute to redirect errors to a custom action on a custom controller, but mine doesn't look like the one shown in the link you provided.
However I had the same problem: Elmah was properly logging the error, but was also adding a "Error view not found" entry. I solved this by adding the following line to the OnException method on the attribute:
filterContext.ExceptionHandled = true;
For completeness, this is the complete code for the custom error handling attribute I am using:
public class CustomHandleErrorAttribute: HandleErrorAttribute
{
public override void OnException(ExceptionContext filterContext)
{
filterContext.ExceptionHandled = true;
if(filterContext.HttpContext.Request.IsAjaxRequest()) {
filterContext.HttpContext.Response.StatusCode =
(int)HttpStatusCode.InternalServerError;
filterContext.Result = new ContentResult() {
Content = "Server error",
ContentType = "text/plain"
};
}
else {
filterContext.Result = new RedirectToRouteResult(
"Default",
new System.Web.Routing.RouteValueDictionary(new
{
controller = "Error",
action = "ApplicationError"
}));
}
}
}

Breeze executeQuery Q promise fails CORS

I'm having a problem with consuming OData Services using Breeze, I set up a Web API OData service by following this guide, from Fiddler it works excellent as expected, but when I try to use it with breeze it fails and gives an error message of "OK":
[Q] Unhandled rejection reasons (should be empty):Error: OK
Using fiddler I see it goes and queries for metadata and then it queries for the entities which are returned correctly, what could be the problem here?
breeze.config.initializeAdapterInstances({ dataService: "OData" });
var manager = new breeze.EntityManager(serverAddress);
var query = new breeze.EntityQuery.from("Laboratories");
manager.executeQuery(query).then(function (data) {
ko.applyBindings(data);
}).fail(function (e) {
alert(e);
});
I enabled CORS by using the nightly build of ASP.NET Web API CORS support, it all works fine and I can retrieve the entities since I can see in fiddler that they are returned ... it's just that it doesn't go to the then promise instead it lands in fail.
UPDATE:
In response to #Ward testing from newly created projects I did the following:
PROJECT 1
Created a Web API Project.
Added Microsoft ASP.MET Web API Cross-Origin Resource Sharing (CORS) Reference from Nuget.
Added the following Controller:
namespace CORSBreezeTest1.Controllers
{
public class ValuesController : EntitySetController<Value, int>
{
ValuesDbContext _context = new ValuesDbContext();
[Queryable]
public override IQueryable<Value> Get()
{
return _context.Values;
}
protected override Value GetEntityByKey(int key)
{
return _context.Values.Find(key);
}
protected override Value CreateEntity(Value entity)
{
Value value = _context.Values.Find(entity.Id);
if (value != null)
{
throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.Conflict));
}
_context.Values.Add(entity);
_context.SaveChanges();
return entity;
}
protected override int GetKey(Value entity)
{
return entity.Id;
}
protected override void Dispose(bool disposing)
{
_context.Dispose();
base.Dispose(disposing);
}
}
}
And the following Code First Database:
namespace CORSBreezeTest1
{
public class ValuesDbContext : DbContext
{
public ValuesDbContext()
: base("DefaultConnection")
{
}
public DbSet<Value> Values { get; set; }
}
public class Value
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public string Name { get; set; }
public int Quantity { get; set; }
}
}
Added the following lines in WebApiConfig.cs
public static void Register(HttpConfiguration config)
{
// Default code left out here ...
config.Routes.MapODataRoute("Values", "odata", GetEdmModel());
config.EnableQuerySupport();
config.EnableCors(new EnableCorsAttribute("*", "*", "*"));
}
private static IEdmModel GetEdmModel()
{
ODataModelBuilder builder = new ODataConventionModelBuilder();
builder.Namespace = "CORSBreezeTest1";
builder.EntitySet<Value>("Values");
return builder.GetEdmModel();
}
PROJECT 2
Then created another Web API Project.
Added Breeze for ASP.NET Web API Projects Nuget Package
Added datajs Nuget Package.
Added the following lines of code to Index.cshtml:
<p data-bind="visible: !results">Fetching data ... </p>
<ul data-bind="foreach: results, visible: results" style="display: none">
<li>
<span data-bind="text: Name"></span>
<span data-bind="text: Quantity"></span>
</li>
</ul>
#section Scripts {
<script src="~/Scripts/knockout-2.2.0.debug.js"></script>
<script src="~/Scripts/q.js"></script>
<script src="~/Scripts/datajs-1.1.0.js"></script>
<script src="~/Scripts/breeze.min.js"></script>
<script type="text/javascript">
$(function () {
breeze.config.initializeAdapterInstances({ dataService: "OData" });
var manager = new breeze.EntityManager("http://serverAddress/odata")
var query = new breeze.EntityQuery.from("Values");
manager.executeQuery(query).then(function (data) {
ko.applyBindings(data);
}).fail(function (e) {
alert(e);
});
});
</script>
}
Tested as is and it worked since both websites are on localhost.
Published PROJECT 1 to a web server so that the test will actually see different origins, and tested.
And this is what Nugget saw:
The first request headers are OPTIONS
OPTIONS /odata/Values HTTP/1.1
And the second request headers are GET
GET /odata/Values HTTP/1.1
And if I change my fail code to:
fail(function (e) {
ko.applyBindings(e.body.value);
});
And my knockout code to:
<p data-bind="visible: !$data">Fetching data ... </p>
<ul data-bind="foreach: $data, visible: $data" style="display: none">
<li>
<span data-bind="text: Name"></span>
<span data-bind="text: Quantity"></span>
</li>
</ul>
Voila! It came through with the data:
And this is what the Console saw:
SEC7118: XMLHttpRequest for http://serverAddress/odata/$metadata required Cross Origin Resource Sharing (CORS).
localhost:53317
SEC7119: XMLHttpRequest for http://serverAddress/odata/$metadata required CORS preflight.
localhost:53317
SEC7118: XMLHttpRequest for http://serverAddress/odata/Values required Cross Origin Resource Sharing (CORS).
localhost:53317
SEC7119: XMLHttpRequest for http://serverAddress/odata/Values required CORS preflight.
localhost:53317
[Q] Unhandled rejection reasons (should be empty):Error: OK
PROJECTS 1 & 2 using the BreezeControllerAttribute
If I in another test add a new controller following Breeze Nuget example and add Breeze for ASP.NET Web API project Nuget package and add the following controller:
namespace CORSBreezeTest1.Controllers
{
[BreezeController]
public class BreezeValuesController : ApiController
{
readonly EFContextProvider<ValuesDbContext> _context =
new EFContextProvider<ValuesDbContext>();
[HttpGet]
public string Metadata()
{
return _context.Metadata();
}
[HttpGet]
public IQueryable<Value> Values()
{
return _context.Context.Values;
}
[HttpPost]
public SaveResult SaveChanges(JObject saveBundle)
{
return _context.SaveChanges(saveBundle);
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
}
}
}
And then modify the client as following:
//breeze.config.initializeAdapterInstances({ dataService: "OData" });
var manager = new breeze.EntityManager("http://serverAddress/breeze/BreezeValues")
Then the requests change:
And everything works ... I'm not sure if in part is something that EntitySetController handles requests differently or is Breeze making different requests when changing the dataService.
I don't know the answer yet. But there are a couple of misunderstandings in your question. First, CORS is a browser convention. It is a (relatively) safe way to work around the browser "Same Origin" policy.
That's a browser policy. Fiddler is not a browser. It is not constrained by "Same Origin" policies and can happily read and write across domain. So you can't tell if the server is properly configured for CORS based on what happens in Fiddler.
And of course "the same code in the same site that hosts the Web API works flawlessly"; you aren't violating the "Same Origin" policy so CORS is not involved.
You need to test your server configuration by writing a browser client application that launches from a site other than the host of your Web API. It doesn't have to be a breeze client. A simple AJAX call to an endpoint will do. You might as well write another simple, non-Breeze Web API controller while you're at it. Keep both controllers really simple. Keep your test client really simple.
My bet is that you will have the same trouble with both breeze-enabled and vanilla Web API controller. You'll power through that. And when you do, it should work for both the breeze and vanilla controllers. If you can show that your client works with one but not the other, come back and give us the code.
Sorry for your pains.
The only way to get it to work was to use the BreezeControllerAttribute from Breeze.WebApi following the breeze exact way of using the api. Not using EntitySetController and going back to a regular ApiController. Detailed explanation in the question itself.
[BreezeController]
public class BreezeValuesController : ApiController
{
// Methods here
}
You just need to add this extra parameter DataServiceVersion, MaxDataServiceVersion configuring enableCors.
config.EnableCors(new EnableCorsAttribute("*", "*", "*", "DataServiceVersion, MaxDataServiceVersion"));

How can I show Authenticated but UNAUTHORIZED users an unauthorized page MVC 3?

I have an application where some users belong to a Role, but may not actually have access to certain data within a URL. For instance the following url is open to all users
/Library/GetFile/1
However, some users may not have access to file1, but I can't use the Authorize attribute to detect that. I want instead to redirect those users to an unauthorized or accessdenied page. I'm using Forms Authentication and my config is set up like this
<authentication mode="Forms">
<forms loginUrl="~/Home/Index" timeout="2880" />
</authentication>
my custom errors block is like this
<customErrors mode="On" defaultRedirect="Error" redirectMode="ResponseRewrite" >
<error statusCode="401" redirect="Unauthorized"/>
</customErrors>
I am attempting to return the HttpUnauthorizedResult if the user does not have access, but I just get redirected to the login page, which isn't valid here because the User is Authenticated already.
It appears that the HttpUnauthorizedResult is setting the HTTP Response Code to 401 which Forms Authentication is hijacking and sending the user to the Login page.
Throwing the UnauthorizedAccessException doesn't seem to work either always redirecting the user to an IIS Error page even though I've updated my RegisterGlobalFilters to
filters.Add(new HandleErrorAttribute
{
ExceptionType = typeof(UnauthorizedAccessException),
View = "Unauthorized",
Order = 3
});
If I change UnauthorizedAccessException to a custom Exception the redirect works and for now that's what I've done.
Your solution is similar to mine except that I did this:
Create a custom exception, UnauthorizedDataAccessException.
Create a custom exception filter (so that it could log the invalid access attempt).
Register my custom exception attribute as a global filter in App_start.
Create a marker interface, ISecureOwner and added it to my entity.
Add a secure 'Load' extension method to my repository, which throws the exception if the current user is not the owner of the entity that was loaded. For this to work, entity has to implement ISecureOwner that returns the id of the user that saved the entity.
Note that this just shows a pattern: the details of how you implement GetSecureUser and what you use to retrieve data will vary. However, although this pattern is okay for a small app, it is a bit of hack, since that kind of security should be implemented deep down at the data level, using ownership groups in the database, which is another question :)
public class UnauthorizedDataAccessException : Exception
{
// constructors
}
public class UnauthorizedDataAccessAttribute : HandleErrorAttribute
{
public override void OnException(ExceptionContext filterContext)
{
if (filterContext.Exception.GetType() == Typeof(UnauthorizedDataAccessException))
{
// log error
filterContext.ExceptionHandled = true;
filterContext.Result = new RedirectToRouteResult(new RouteValueDictionary(new { controller = "Error", action = "UnauthorizedDataAccess" }));
}
else
{
base.OnException(filterContext);
}
}
// marker interface for entity and extension method
public interface ISecureOwner
{
Guid OwnerId { get; }
}
// extension method
public static T SecureFindOne<T>(this IRepository repository, Guid id) where T : class, ISecureOwner, new()
{
var user = GetSecureUser();
T entity = repository.FindOne<T>(id);
if (entity.OwnerId != user.GuidDatabaseId)
{
throw new UnauthorizedDataAccessException(string.Format("User id '{0}' attempted to access entity type {1}, id {2} but was not the owner. The real owner id is {3}.", user.GuidDatabaseId, typeof(T).Name, id, entity.OwnerId));
}
return entity;
}
// Register in global.asax
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
var filter = new UnauthorizedDataAccessAttribute { ExceptionType = typeof(UnauthorizedDataAccessException) };
filters.Add(filter);
filters.Add(new HandleErrorAttribute());
}
// Usage:
var ownedThing = myRepository.SecureFindOne<myEntity>(id))
You can restrict access to certain roles. If an unauthorized role tries to access a resource you can redirect them to a specific url.
Look at this other SO question: attribute-for-net-mvc-controller-action-method, there are good answers there.
You can check in your code if a user belongs to a role:
User.IsInRole("RoleToTest");
you can also apply attributes to your controllers/action methods. Anyhow it is all explained in the link I specified above.
* EDIT *
You could override OnException in your base Controller. Implement a custom exception, e.g., AccessNotAuthorizedAccessException.
In OnExcepton, if you detect your custom exception, just redirect to a friendly url that shows the 'Not authorized...' message.

Resources