ExceptionFilterAttribute not being invoked - asp.net-web-api2

I have the following WebApiConfig.
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.Formatters.JsonFormatter.SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/html"));
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
config.EnableSystemDiagnosticsTracing();
// config.MessageHandlers.Add(new TokenValidationHandler());
// config.MessageHandlers.Add(new LoggingHandler());
config.Filters.Add(new ApiCustomExceptionHandler());
}
And I implemented the ExceptionFilterAttribute as follows
public class ApiCustomExceptionHandler : ExceptionFilterAttribute
{
public override void OnException(HttpActionExecutedContext context)
{
var exceptionType = context.Exception.GetType();
if (exceptionType == typeof(UnauthorizedAccessException))
{
context.Response = new HttpResponseMessage(HttpStatusCode.Unauthorized);
}
My controller is declared thus:
[ApiCustomExceptionHandler]
public class UserManagementController : ApiController
{
....
}
The problem is OnException never gets invoked, when I throw a UnAuthorizedAccessException. Any idea why this is so?
-Arun

When I had this problem, it turned out actually to be an problem on one action only -- other actions in the controller were fine. The problematic method was returning a generic (in my case,IEnumerable<Dictionary<string, object>>). I fixed this by returning an array instead: Dictionary<string, object>[].
Notice that returning a generic also gives warning CA1006 if you have Static Code Analysis enabled.

Related

Overriding RouteValueDictionary in a Constraint in ASP.NET Core

I've just started using ASP.NET Core MVC and I want one route (global slug) to go to multiple controllers and actions depending on what type of page I'm serving to the user. For example, I want to use {*slug} for category and product pages.
I'm trying to override the default controller and action in a constraint.
In past versions of MVC, you could change either values["controller"] or values["action"] in a constraint and it would re-route to the appropriate controller and action.
public class Startup
{
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseMvc(routes =>
{
routes.MapRoute(
name: "Category",
template: "{*slug}",
defaults: new { controller = "Page", action = "Home" },
constraints: new { slug = new PageConstraint() }
);
});
}
}
public partial class PageConstraint : IRouteConstraint
{
public virtual bool Match(HttpContext httpContext, IRouter route, string routeKey, RouteValueDictionary values, RouteDirection routeDirection)
{
if (routeDirection == RouteDirection.UrlGeneration)
{
return true;
}
var slug = values["slug"] != null ? values["slug"].ToString() : null;
if (string.IsNullOrWhiteSpace(slug))
{
// Homepage
return true;
}
// Get category service
var categoryService = httpContext.RequestServices.GetRequiredService<ICategoryService>();
var category = categoryService.GetBySlug(slug);
if (category == null)
{
return false;
}
values["controller"] = "Category";
values["action"] = "Listing";
values["category"] = category;
return true;
}
}
In PageConstraint, I look to see if the Category exists, and if it does, it changes the Controller to Category and the Action to Listing in the RouteValueDictionary. However, when I debug the code through, it still goes to the Controller Page and the Action Home.
Anyone know why this is happening, or whether there is a better way of doing this? I know I could have one action in a controller doing all the work that the constraint is, but I would prefer to house the code in separate controllers and actions.
For your requirement, you could try to implement custom IRouter
public class RouterFromAppSettings : IRouter
{
private readonly IRouter _defaulRouter;
private readonly IConfiguration _config;
public RouterFromAppSettings(IRouter defaulRouter
, IConfiguration config)
{
_defaulRouter = defaulRouter;
_config = config;
}
public async Task RouteAsync(RouteContext context)
{
var controller = _config.GetSection("Router").GetValue<string>("Controller");
var action = _config.GetSection("Router").GetValue<string>("Action");
context.RouteData.Values["controller"] = controller;
context.RouteData.Values["action"] = action;
await _defaulRouter.RouteAsync(context);
}
public VirtualPathData GetVirtualPath(VirtualPathContext context)
{
return _defaulRouter.GetVirtualPath(context);
}
}
And then register it in Startup.cs like
app.UseMvc(routes =>
{
routes.Routes.Insert(0, new RouterFromAppSettings(routes.DefaultHandler,Configuration));
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});

Web API Route not working properly in ASP.NET MVC

This may be simple and its wierd and I cannot find out the reason why its hapenning.
I am using ASP.NET WEB API 2 Controller: I have a TransactionController, which suddenly stopped working after some modification in one of its action methods. I returned the code back, but its not working anymore.
All the other controllers are working normally. So, I decided two test it by creating two new controllers:
ValueController:
public class ValueController : ApiController
{
public List<string> GetValues()
{
return new List<string>() { "1", "2"};
}
public List<string> GetValues (int ID)
{
return new List<string>() { "1", "2" };
}
public List<string> GetValues(int UserID, DateTime CreateDate)
{
return new List<string>() { "1", "2" };
}
}
TransactionController:
public class TransactionController : ApiController
{
public List<string> GetTransaction(int ID)
{
return new List<string>() { "1", "2" };
}
}
This my webApiConfig
public static void Register(HttpConfiguration config)
{
config.EnableCors();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
Global.asax
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
WebApiConfig.Register(GlobalConfiguration.Configuration);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
}
When requesting the item controller by the url:
http://localhost:1607/api/value?ID=1, its hitting the GetValues(int ID) action
and the other actions are working fine.
But, the problem is still with the TransactionController. When requesting it using the url: http://localhost:1607/api/transaction?ID=1. Its not hitting the action.
This the response of the request:
And Its returning back the index view page in the home controller.
Any thoughts why this might happen?

Injecting Ninject dependencies into WebApiConfig in Web API 2

is it possible to inject dependencies into the WebApiConfig class using Ninject?
This is my WebApiConfig class.
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Web API routes
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
config.Services.Replace(typeof(IExceptionHandler), new ErrorHandlerMessageHandler(*NEEDS DEPENDENCY*));
}
}
And this is my NinjectHttpApplication declaration
public class WebApiApplication : NinjectHttpApplication
{
protected override void OnApplicationStarted()
{
base.OnApplicationStarted();
GlobalConfiguration.Configure(WebApiConfig.Register);
}
protected override IKernel CreateKernel()
{
var kernel = new StandardKernel();
RegisterServices(kernel);
GlobalConfiguration.Configuration.DependencyResolver = new NinjectDependencyResolver(kernel);
return kernel;
}
private void RegisterServices(IKernel kernel)
{
//bindings
}
}
In the end I didn't have to do this but I have created a blog post on how to do this here

Routing Error with WebApi 2, Castle Windsor, Glass mapper

I have setup Castle Windsor and WebApi after reading about it in the below 2 posts. Here is my a highlight of my setup:
Reference Posts:
How do I get Web API / Castle Windsor to recognize a Controller?
Dependency Injection in WebAPI with Castle Windsor
Code Setup:
public static class GlassMapperScCustom
{
public static void CastleConfig(IWindsorContainer container)
{
container.AddFacility<TypedFactoryFacility>();
var config = new Config
{
UseWindsorContructor = true
};
//MVC
container.Register(Component.For<SitecoreController>().LifestyleTransient());
container.Register(Types.FromThisAssembly().BasedOn<Controller>().LifestyleTransient());
DependencyResolver.SetResolver(new WindsorMvcDependencyResolver(container));
ControllerBuilder.Current.SetControllerFactory(new WindsorMvcControllerFactory(container.Kernel));
//WebApiInstaller
container.Register(Types.FromThisAssembly().BasedOn<ApiController>().LifestyleTransient());
var resolver = new WindsorResolver(container); //Shown Below
GlobalConfiguration.Configuration.DependencyResolver = resolver;
GlobalConfiguration.Configuration.Services.Replace(typeof(IHttpControllerActivator), new WindsorWebApiControllerActivator(resolver));
}
}
public class WindsorMvcDependencyResolver : IDependencyResolver
{
private readonly IWindsorContainer _container;
public WindsorMvcDependencyResolver(IWindsorContainer container)
{
if (container == null) throw new ArgumentNullException("container");
_container = container;
}
public object GetService(Type serviceType)
{
return _container.Kernel.HasComponent(serviceType) ? _container.Resolve(serviceType) : null;
}
public IEnumerable<object> GetServices(Type serviceType)
{
return _container.ResolveAll(serviceType).Cast<object>().ToArray();
}
}
public class WindsorMvcControllerFactory : DefaultControllerFactory
{
private readonly IKernel _kernel;
public WindsorMvcControllerFactory(IKernel kernel)
{
this._kernel = kernel;
}
public override void ReleaseController(IController controller)
{
_kernel.ReleaseComponent(controller);
}
protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType)
{
if (controllerType == null)
{
throw new HttpException(404, string.Format("The controller for path '{0}' could not be found.",
requestContext.HttpContext.Request.Path));
}
return (IController)_kernel.Resolve(controllerType);
}
}
internal class WindsorResolver : IDependencyResolver, IDependencyScope, IDisposable
{
private readonly IWindsorContainer _container;
public WindsorResolver(IWindsorContainer container)
{
this._container = container;
}
public IDependencyScope BeginScope()
{
return new WindsorDependencyScope(this._container);
}
public void Dispose()
{
this._container.Dispose();
}
public object GetService(Type serviceType)
{
if (!this._container.Kernel.HasComponent(serviceType))
return (object)null;
else
return this._container.Resolve(serviceType);
}
public IEnumerable<object> GetServices(Type serviceType)
{
if (!this._container.Kernel.HasComponent(serviceType))
return (IEnumerable<object>)new object[0];
else
return Enumerable.Cast<object>((IEnumerable)this._container.ResolveAll(serviceType));
}
}
public class WindsorWebApiControllerActivator : IHttpControllerActivator
{
private readonly IDependencyResolver _container;
public WindsorWebApiControllerActivator(IDependencyResolver container)
{
_container = container;
}
public IHttpController Create(HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor, Type controllerType)
{
var scope = _container.BeginScope();
var controller = (IHttpController)scope.GetService(controllerType);
request.RegisterForDispose(scope);
return controller;
}
}
//WebApiConfig.cs
public static class WebApiConfig
{
public static void Start()
{
GlobalConfiguration.Configure(WebApiConfig.Register);
}
public static void Register(HttpConfiguration config)
{
// initialize and map all attribute routed Web API controllers (note: this does not enable MVC attribute routing)
config.MapHttpAttributeRoutes();
config.EnsureInitialized();
//config.Routes.MapHttpRoute(
// name: "DefaultApi",
// routeTemplate: "api/{controller}/{id}",
// defaults: new {id = RouteParameter.Optional});
// force JSON responses only (no XML)
config.Formatters.Clear();
config.Formatters.Add(new JsonMediaTypeFormatter());
}
}
//Global.asax.cs
public class MvcApplication : Sitecore.Web.Application
{
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new HandleErrorAttribute());
}
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
}
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
RegisterGlobalFilters(GlobalFilters.Filters);
RegisterRoutes(RouteTable.Routes);
}
}
If I add a test ApiController and try to go to '/api/Test' it gives me a 404 everytime. I used RouteDebugger to view whats wrong and I get the below error everytime:
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
public string Get(int id)
{
return "value";
}
I am not sure where "api/sitecore" is coming from. I followed the instructions on [WebApi2 Attribute Routing with Sitecore][1] post as well but unable to get it working. Can someone point me to what I am doing wrong?
Sitecore is using /api/ as its default route URL.
Rename your controller to something else than ApiController or change Sitecore's default route in the Global.asax and web.config
From a quick glance it
looks like there are no routes registered. The only route in WebApiConfig is commented out.

ASP.NET Web API - Multiple POST methods on one controller?

I've been trying to add a second POST method to the default ValuesController class that will take an id parameter and act identical to the PUT method, like so:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Web.Http;
namespace WebCalendar.Controllers {
public class ValuesController : ApiController {
// GET /values
public IEnumerable<string> Get() {
return new string[] { "value1", "value2" };
}
// GET /values/5
public string Get(int id) {
return "value";
}
// POST /values
public void Post(string value) {
}
// POST /values/5
public void Post(int id, string value) {
Put(id, value);
}
// PUT /values/5
public void Put(int id, string value){
}
// DELETE /values/5
public void Delete(int id) {
}
}
}
Problem is, when I add this second post method, any time I make a POST request, I get the error:
"No action was found on the controller 'values' that matches the request."
If I comment out one of the methods (doesn't matter which one), POST will work with the other method. I've tried renaming the methods, and even using [HttpPost] on both of them, but nothing has worked.
How can I have more than one POST method in a single ApiController?
EDIT
Here is the only route that I'm using:
routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "{controller}/{id}",
defaults: new { controller = "values", id = RouteParameter.Optional }
);
You have to include the action in your route:
routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);