2
votes

I have an application in ASP.NET Core MVC (dnx46) RC1 with an AuthorizationHandler:

public class AppSumAuthAuthorizationHandler : AuthorizationHandler<AppSumAuthRequirement>
{
    private readonly IUserRepository _userRepository;
    private readonly IUserRoleRepository _userRoleRepository;

    public AppSumAuthAuthorizationHandler(IUserRepository userRepository, IUserRoleRepository userRoleRepository)
    {
        _userRepository = userRepository;
        _userRoleRepository = userRoleRepository;
    }
    protected override async void Handle(AuthorizationContext context, AppSumAuthRequirement requirement)
    {
        await HandleAsync(context,requirement);
    }

    protected override async Task HandleAsync(AuthorizationContext context, AppSumAuthRequirement requirement)
    {
        var currentUserName = context.User.Identity.Name;
        var currentUser = await _userRepository.GetAsync(u => u.UserName == context.User.Identity.Name);

        // Create user that does not yet exist
        if(currentUser == null)
        {
            var user = new User(currentUserName);
            /* Temporary add SysAdmin role */
            using(new CreatedBySystemProvider(_userRepository))
            {
                _userRepository.Add(user);
                await _userRepository.SaveChangesAsync();
                if (string.Equals(currentUserName, @"BIJTJES\NilsG", StringComparison.CurrentCultureIgnoreCase))
                {
                    user.AddRole(1);
                }
                currentUser = await _userRepository.GetAsync(u => u.Id == user.Id);
            }
        }
        var resource = (Microsoft.AspNet.Mvc.Filters.AuthorizationContext) context.Resource;
        var controllerActionDescriptor = resource.ActionDescriptor  as ControllerActionDescriptor;
        var controllerName = controllerActionDescriptor.ControllerName;
        var actionName = controllerActionDescriptor.Name;
        string moduleName;
        try
        {
            // Get the name of the module
            moduleName = ((ModuleAttribute)controllerActionDescriptor.ControllerTypeInfo.GetCustomAttributes(false).First(a => a.GetType().Name == "ModuleAttribute")).ModuleName;
        }
        catch(InvalidOperationException ex)
        {
            context.Fail();
            throw new InvalidOperationException($"The Module Attribute is required on basecontroller {controllerName}.", ex);
        }

        var access = new Access(moduleName, controllerName, actionName);

        if (await currentUser.HasPermissionTo(UrlAccessLevel.Access).OnAsync(access))
        {
            context.Succeed(requirement);
        }
        else
        {
            context.Fail();
        }
    }
}

The requirement class is empty:

public interface IAppSumAuthRequirement : IAuthorizationRequirement
{

}
public class AppSumAuthRequirement : IAppSumAuthRequirement
{

}

The Module attribute is also nothing special:

public class ModuleAttribute : Attribute
{
    public string ModuleName { get; private set; }
    public ModuleAttribute(string moduleName)
    {
        ModuleName = moduleName;
    }

    public override string ToString()
    {
        return ModuleName;
    }
}

The exception filter:

    public class JsonExceptionFilterAttribute : ExceptionFilterAttribute
{
    public override void OnException(ExceptionContext context)
    {
        var exception = context.Exception;
        context.HttpContext.Response.StatusCode = 500;
        context.Result = new JsonResult(new Error
        {
            Message = exception.Message,
            InnerException = exception.InnerException?.InnerException?.Message,
            Data = exception.Data,
            ErrorCode = exception.HResult,
            Source = exception.Source,
            Stacktrace = exception.StackTrace,
            ErrorType = exception.GetType().ToString()
    });
    }
}

and policy are configured in my Startup.cs:

public IServiceProvider ConfigureServices(IServiceCollection services)
    {
        services.AddMvc(options =>
        {
            options.Filters.Add(new JsonExceptionFilterAttribute());
            options.ModelBinders.Insert(0, new NullableIntModelBinder());
        }).AddJsonOptions(options => {
            options.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore;
            options.SerializerSettings.ContractResolver = new Newtonsoft.Json.Serialization.CamelCasePropertyNamesContractResolver();
        });

        // Security
        services.AddAuthorization(options =>
        {
            options.AddPolicy("AppSumAuth",
                policy => policy.Requirements.Add(new AppSumAuthRequirement()));
        });
}

and the policy is set on all controllers, by inheriting BaseController:

[Authorize(Policy = "AppSumAuth")]
public class BaseController : Controller
{
    public BaseController()
    {

    }
}

So, in my handler, I get the controllername, actionname and modulename (from the attribute set on the controllers):

[Module("Main")]

When this attribute is not set on a controller, I would like to catch the exception and report this back to the developer calling the controller and deny access. To do this, I've added:

        catch(InvalidOperationException ex)
        {
            context.Fail();
            throw new InvalidOperationException($"The Module Attribute is required on basecontroller {controllerName}.", ex);
        }

The JsonExceptionFilter is called perfectly when there is an exception in the controllers. It is however not called when there is an error in the AuthorizationHandler.


So the question:

How can I get the Exceptions to be caught by the JsonExceptionFilter? What am I doing wrong?

Solution:

Startup.cs:

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
    {
        // For Windows Auth!
        app.UseIISPlatformHandler();

        app.UseStaticFiles();

        app.UseExceptionHandler(AppSumExceptionMiddleware.JsonHandler());

        app.UseMvc();
    }

And my middleware:

public class AppSumExceptionMiddleware
{
    public static Action<IApplicationBuilder> JsonHandler()
    {
        return errorApp =>
        {
            errorApp.Run(async context =>
            {
                var exception = context.Features.Get<IExceptionHandlerFeature>();
                if (exception != null)
                {
                    var exceptionJson = Encoding.UTF8.GetBytes(
                        JsonConvert.SerializeObject(new AppSumException(exception.Error), 
                        new JsonSerializerSettings
                        {
                            ContractResolver = new CamelCasePropertyNamesContractResolver()
                        })
                    );
                    context.Response.ContentType = "application/json";
                    await context.Response.Body.WriteAsync(exceptionJson, 0, exceptionJson.Length);
                }
            });
        };
    }
}
1
Please, don't use MVC6 tags anymore. It's for a future version of ASP.NET MVC based on the old webstack (MVC5). ASP.NET Core is a complete new and incompatible, portable version based on .NET Core. Use asp.net-core-mvc and/or asp.net-core tags instead and your question is more likely to be found by people who can help you with the issue. Also switch to ASP.NET Core 1.0 ASAP. DNX isn't supported anymore and all versions beyond RC1 only run on dotnet-cli based tooling chain. The sooner you switch the less pain you'll have switching over to new tooling - Tseng
Hello I am using " var ex = context.Features.Get<IExceptionHandlerFeature>(); to get the stack trace and inner exception but it is showing null value. can you please help me. - Gaurav_0093

1 Answers

4
votes

Action filter can be used as a method filter, controller filter, or global filter only for MVC HTTP requests. In your case you need to use a middleware, as

Middleware is component that "sit" on the HTTP pipeline and examine all requests and responses.

As you want to works with exception, you may use ready-to-use ExceptionHandler middleware:

        app.UseExceptionHandler(errorApp =>
        {
            errorApp.Run(async context =>
            {
                context.Response.StatusCode = 500; // for example

                var error = context.Features.Get<IExceptionHandlerFeature>();
                if (error != null)
                {
                    var ex = error.Error;
                    // custom logic
                }
            });
        });