I am trying the MVC3 exception handling, and came with following test code:
public class HomeController : Controller {
public ActionResult Index() {
throw new ArgumentException();
return View();
}
}
My controller forces to throw exception, and in my web.config
<customErrors mode="On" defaultRedirect="~/ErrorHandler/Index">
<error statusCode="404" redirect="~/ErrorHandler/NotFound"></error>
</customErrors>
I have created another controller to server ErrorHandler/Index and ErrorHandler/NotFound request already.
With my testing, I can see the 404 code could be captured but 500 code has been ignore totally.
Anything wrong with my code?
Thanks
Hardy
Remove the following line:
filters.Add(new HandleErrorAttribute());
from the RegisterGlobalFilters method in your Global.asax.
Related
I have a WebApi 2 Application and I'm using simple injector and all is working ok.
But today I tried to use the [RoutePrefix] and [Route] attributes to resolve my routes on a particular controller and it seems that simple injector is not able to create an instance of my controller.
i'm getting this error
An error occurred when trying to create a controller of type
'NewController'. Make sure that the controller has a parameterless
public constructor. Type
'Public.API.Controllers.NewController' does not have a default
constructor
Stack trace:
at System.Web.Http.Dispatcher.DefaultHttpControllerActivator.Create(HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor, Type controllerType)
at System.Web.Http.Controllers.HttpControllerDescriptor.CreateController(HttpRequestMessage request)
at System.Web.Http.Dispatcher.HttpControllerDispatcher.<SendAsync>d__1.MoveNext()
at System.Linq.Expressions.Expression.New(Type type)
at System.Web.Http.Internal.TypeActivator.Create[TBase](Type instanceType)
at System.Web.Http.Dispatcher.DefaultHttpControllerActivator.GetInstanceOrActivator(HttpRequestMessage request, Type controllerType, Func`1& activator)
at System.Web.Http.Dispatcher.DefaultHttpControllerActivator.Create(HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor, Type controllerType)
my controller looks like this
[Authorize]
[RoutePrefix("api/New")]
public class NewController : ApiController
{
private IUserService userService;
public NewController(IUserService userService)
{
this.userService = userService;
}
[HttpPost]
[AllowAnonymous]
public async Task<IHttpActionResult> Register(ApiRegisterUserRequestModel model) {
return Content(HttpStatusCode.OK, "reponse");
}
[HttpPost]
[AllowAnonymous]
[Route("ForgotPasswordSendEmail")]
public async Task<IHttpActionResult> ForgotPasswordSendEmail(
[FromBody] ApiForgotPasswordRequestModel model)
{
var response = "cool";
return Content(HttpStatusCode.OK, response);
}
}
If I make a request to the Register action I get a response, but if I make a request to the ForgotPasswordSendEmail action then I get the error I mentioned above.
The simple injector configuration I have is the Basic setup mentioned here
https://simpleinjector.readthedocs.org/en/latest/webapiintegration.html
UPDATE
I'm using OWIN and JWT token authentication, and I have a global.asax and a startup file in my project and both were configured to use webapi.
These were my Startup class and Application_Start
//startup class
public class Startup
{
public void Configuration(IAppBuilder app)
{
SimpleInjectorWebApiInitializer.Initialize();
HttpConfiguration httpConfig = new HttpConfiguration();
ConfigureOAuthTokenGeneration(app);
ConfigureOAuthTokenConsumption(app);
WebApiConfig.Register(httpConfig);
app.UseWebApi(httpConfig);
}
}
//Application_Start
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
GlobalConfiguration.Configure(WebApiConfig.Register);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
SimpleInjector.Configure();
SimpleInjectorWebApiInitializer.Initialize();
}
I removed all of the api configuration from the startup and left if like this
public class Startup
{
public void Configuration(IAppBuilder app)
{
ConfigureOAuthTokenGeneration(app);
ConfigureOAuthTokenConsumption(app);
}
}
and it started to work.
now, why wasn't it working using the [Route] attribute and without it all was fine? it's a mystery to me.
I've been trying to reproduce this issue, but with no success. You typically get the "Make sure that the controller has a parameterless public constructor" error in case the registered IDependencyResolver.GetService returns null and the requested type does not have a default constructor.
If you, as you said, follow Simple Injector's configuration guidance, the SimpleInjectorWebApiDependencyResolver will not return null, but will either return a valid controller instance -or- will throw an expressive exception. The default guidance states that you at least do the following:
container.RegisterWebApiControllers(GlobalConfiguration.Configuration);
container.Verify();
GlobalConfiguration.Configuration.DependencyResolver =
new SimpleInjectorWebApiDependencyResolver(container);
If you use this code, it's very unlikely that you get this "Make sure that the controller has a parameterless public constructor" error. Especially in the situation where the controller can be resolved when called using one action, while it fails to initialize during another.
So please check the following:
You are using the code as shown above.
Are you sure that api/New/Register call actually goes through the NewController. You can set a break point in its constructor and the Register method.
The NewController can actually be resolved correctly when manually calling container.GetInstance<NewController>().
On my MVC application I decorated some of the methods of my controller with this:
[PrincipalPermission(SecurityAction.Demand, Role = "Administrator")]
public ActionResult Create(FormCollection collection)
{
try {
...
}
catch {
return View();
}
}
And indeed if I am not logged in or not with the correct role an exception is thrown by the MVC application. The problem is I am not getting the application redirected to an error page.
I tried creating a base controller like this:
[HandleError]
public class BaseController : Controller
{
protected override void OnException(ExceptionContext filterContext)
{
// Make use of the exception later
this.Session["ErrorException"] = filterContext.Exception;
// Mark exception as handled
filterContext.ExceptionHandled = true;
// ... logging, etc
// Redirect
filterContext.Result = this.RedirectToAction("Error", "Home");
base.OnException(filterContext);
}
}
And then adding the Error view in the Home controller as well as the actual View. The problem is that when I try this in Visual Studio I first get an exception upon entering the protected action method:
SecurityException was unhandled by the application
and then I have to do Debug|Continue and only then I am redirected to the the Error view but that is unacceptable in a production application because it should go straight to the Error view.
Just wondering why you are not just using standard AuthorizeAttribute. Pretty sure it would do that same thing and just work?
E.g.
[Authorize(Roles="Administrator")]
public ActionResult Create(FormCollection collection)
Article on general MVC security here.
http://www.codeproject.com/Articles/288631/Secure-ASP-NET-MVC3-applications
I am implementing custom errors in my MVC3 app, its switched on in the web.config:
<customErrors mode="On">
<error statusCode="403" redirect="/Errors/Http403" />
<error statusCode="500" redirect="/Errors/Http500" />
</customErrors>
My controller is very simple, with corresponding correctly named views:
public class ErrorsController : Controller
{
public ActionResult Http403()
{
return View("Http403");
}
public ActionResult Http500()
{
return View("Http500");
}
}
To test, I am throwing exceptions in another controller:
public class ThrowingController : Controller
{
public ActionResult NotAuthorised()
{
throw new HttpException(403, "");
}
public ActionResult ServerError()
{
throw new HttpException(500, "");
}
}
The 403 works - I get redirected to my custom "/Errors/Http403".
The 500 does not work - I instead get redirected to the default error page in the shared folder.
Any ideas?
I've got 500 errors up and running by using the httpErrors in addition to the standard customErros config:
<system.webServer>
<httpErrors errorMode="Custom" existingResponse="Replace">
<remove statusCode="403" subStatusCode="-1" />
<error statusCode="403" path="/Errors/Http403" responseMode="ExecuteURL" />
<remove statusCode="500" subStatusCode="-1" />
<error statusCode="500" path="/Errors/Http500" responseMode="ExecuteURL" />
</httpErrors>
</system.webServer>
And removing this line from global.asax
GlobalFilters.Filters.Add(new HandleErrorAttribute());
Its not perfect however as I'm trying to retrieve the last error which is always null.
Server.GetLastError()
See https://stackoverflow.com/a/7499406/1048369 for the most comprehensive piece on custom errors in MVC3 I have found which was of great help.
I've got the same problem, I catch the Exception directly in Global.asax in that case:
protected void Application_Error(object sender, EventArgs e)
{
Exception exception = Server.GetLastError();
Response.Clear();
HttpException httpException = exception as HttpException;
var code = httpException == null ? 500 : httpException.GetHttpCode();
// Log the exception.
if (code == 500)
logError.Error(exception);
Server.ClearError();
Context.Items["error"] = code;
RouteData routeData = new RouteData();
routeData.Values.Add("controller", "Error");
routeData.Values.Add("action", "Index");
routeData.Values.Add("code", code);
IController errorController = new ErrorController();
errorController.Execute(new RequestContext(new HttpContextWrapper(Context), routeData));
}
That redirects to my custom Error 500: /Error/Index?code=500
I've tried many elmah nugets but they didn't work with ASP.NET Web API. Does anybody knows why? Is there any work around for that?
There are two options by using ELMAH to capture exceptions in WEB API.
If you want to capture errors that are encountered in Actions and Controllers , i.e. in your business logic you can create a ActionFilterAttribute and log those exceptions to ELMAH.
example:
public class UnhandledExceptionFilter : ExceptionFilterAttribute {
public override void OnException(HttpActionExecutedContext context) {
Elmah.ErrorLog.GetDefault(HttpContext.Current).Log(new Elmah.Error(context.Exception));
}
}
Then wireup by adding that filter.
public static class WebApiConfig {
public static void Register(HttpConfiguration config) {
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
config.Filters.Add(new UnhandledExceptionFilter());
}
}
With the above approach following errors will not be handled:
Exceptions thrown from controller constructors.
Exceptions thrown from message handlers.
Exceptions thrown during routing.
Exceptions thrown during response content serialization
Reference: http://blogs.msdn.com/b/webdev/archive/2012/11/16/capturing-unhandled-exceptions-in-asp-net-web-api-s-with-elmah.aspx & http://www.asp.net/web-api/overview/error-handling/web-api-global-error-handling
In order for ELMAH to log WEB API errors at the global level such that all 500 Server errors are caught then do this:
Install nuget: https://www.nuget.org/packages/Elmah.Contrib.WebApi/
Add the below to WebApiConfig
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
...
config.Services.Add(typeof(IExceptionLogger), new ElmahExceptionLogger());
...
}
}
And if you want to show custom error messages for the 500 Server errors you can implement the new ExceptionHandler in Web Api (Note that ExceptionLogger and ExceptionHandler are different.)
class OopsExceptionHandler : ExceptionHandler
{
public override void HandleCore(ExceptionHandlerContext context)
{
context.Result = new TextPlainErrorResult
{
Request = context.ExceptionContext.Request,
Content = "Oops! Sorry! Something went wrong." +
"Please contact support#contoso.com so we can try to fix it."
};
}
private class TextPlainErrorResult : IHttpActionResult
{
public HttpRequestMessage Request { get; set; }
public string Content { get; set; }
public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
{
HttpResponseMessage response =
new HttpResponseMessage(HttpStatusCode.InternalServerError);
response.Content = new StringContent(Content);
response.RequestMessage = Request;
return Task.FromResult(response);
}
}
}
Reference: http://www.asp.net/web-api/overview/error-handling/web-api-global-error-handling
one option would be to setup a custom ExceptionFilterAttribute, override the OnException method, and signal Elmah from there. See the sample link below
Elmah WebAPI Sample
Check the below URL it describe in details how to use elmah with Web API:
http://blogs.msdn.com/b/webdev/archive/2012/11/16/capturing-unhandled-exceptions-in-asp-net-web-api-s-with-elmah.aspx
You can also use the NuGet Package below:
Install-Package Elmah.Contrib.WebApi
Usage:
Simply register it during your application's start up, or on a controller-by-controller basis.
protected void Application_Start()
{
GlobalConfiguration.Configuration.Filters.Add(new ElmahHandleErrorApiAttribute());
...
}
(from the Elmah.Contrib.WebApi GitHub repo)
For ASP.NET Web API use the Elmah.MVC nuget package, details of which are given below
Taken from : HOW TO SETUP ELMAH.MVC WITH ASP.NET MVC 4 ?
What is Elmah ?
ELMAH is an open source project whose purpose is to log and report unhandled exceptions in ASP.NET web applications.
Why to use Elmah ?
ELMAH serves as an unobtrusive interceptor of unhandled ASP.NET exceptions, those usually manifesting with the ASP.NET yellow screen of death.
So now we know what and why to use Elmah, Lets quickly get started on how to use Elmah with your ASP.NET MVC project.
Step 1: Right click on your solution and select the "Manage Nuget Packages" option
Step 2: In the Nuget Package manager search for "Elmah" and install the Elmah.MVC nuget extension.
The Nuget Package manager will download and add the required dlls and modify the web.config's <appSetting> for Elmah to work.
Step 3: That's it !! Your Elmah is now ready to test. I have generated a 404 to test if my Elmah works, ELMAH can be accessed by this url : http://yourapp.com/elmah.
Hope this helps :)
Further Reading :
Elmah on code.google.com
Elmah.MVC 2.0.2 on Nuget
Elmah.MVC on GitHub
I have an MVC3 application that works in Visual Studio, but when published to the web server returns a 404 on Requested URL: /App/Account/LogOn. The problem is I never created an Account controller or the action LogOn. I'm not sure why Account/LogOn is even loading or how to fix it. Thanks.
My global.asax.cs file looks like this:
public class MvcApplication : NinjectHttpApplication
{
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
//filters.Add(new HandleErrorAttribute());
}
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
"Default",
"{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
// Create ninject kernel
protected override IKernel CreateKernel()
{
var kernel = new StandardKernel();
// Add bindings
kernel.Bind<IEmployeeRepository>().To<EFEmployeeRepository>();
kernel.Bind<IDocumentRepository>().To<DocumentRepository>();
// Load kernel
kernel.Load(Assembly.GetExecutingAssembly());
return kernel;
}
// Replaces App_Start() when using Ninject
protected override void OnApplicationStarted()
{
base.OnApplicationStarted();
AreaRegistration.RegisterAllAreas();
RegisterGlobalFilters(GlobalFilters.Filters);
RegisterRoutes(RouteTable.Routes);
}
}
My best guess would be that this is coming from your web.config, as this is the default login page when you create a new MVC project. Its where you get redirected when you try to hit an action that has the [Authorize] attribute applied to it.
Check for a section that says:
<authentication mode="Forms">
<forms loginUrl="~/Account/LogOn" timeout="2880" />
</authentication>
If you have your own login page you need to at that URL here, otherwise, if you aren't using security, then check for actions that carry the [Authorize] attribute.