How many times have you found yourself doing this:
[Authorize]
public async Task<ActionResult> Foo()
{
var user = await UserManager.FindByIdAsync(User.Identity.GetUserId());
if (user == null)
{
AuthenticationManager.SignOut();
return RedirectToAction("Signin", "Account");
}
...
}
Wouldn't it be so much nicer if you could just do:
[Authorize]
[GetUser]
public async Task<ActionResult> Foo(ApplicationUser user)
{
...
}
Well, thanks to the miracles of action filters and dependency injection, specifically via Ninject, now you can.
First, our action filter:
public class GetUserAttribute : ActionFilterAttribute
{
}
public class GetUserFilter : IActionFilter
{
protected readonly ApplicationUserManager UserManager;
protected readonly IAuthenticationManager AuthenticationManager;
public GetUserFilter(ApplicationUserManager userManager, IAuthenticationManager authenticationManager)
{
this.UserManager = userManager;
this.AuthenticationManager = authenticationManager;
}
public void OnActionExecuting(ActionExecutingContext filterContext)
{
var userId = filterContext.HttpContext.User.Identity.GetUserId();
var user = AsyncHelper.RunSync<ApplicationUser>(() => UserManager.FindByIdAsync(userId));
if (user == null)
{
AuthenticationManager.SignOut();
filterContext.Result = new RedirectToRouteResult(
new RouteValueDictionary
{
{ "controller", "Account" },
{ "action", "Signin" }
}
);
}
filterContext.ActionParameters["user"] = user;
}
public void OnActionExecuted(ActionExecutedContext filterContext)
{
}
}
Let's walk through that. GetUserAttribute
is just a placeholder. We need something we can decorate our action with that has a constructor without arguments. GetUserFilter
is where the real meat happens, specifically in OnActionExecuting
. It has a constructor that accepts the necessary dependencies, which is why we can't use this directly to decorate our action. The other method OnActionExecuted
is merely required to implement the IActionFilter
interface, and does nothing.
Annoyingly, action filters in MVC 5 and previous do not support async, and to add insult to injury, the UserManager.FindByIdAsync
method has no sync version. That's where AsyncHelper
comes in. This is something I stole from the Entity Framework 6 source. EF6 actually only implements async methods. They use AsyncHelper
to run the async methods synchronously for the non-async methods. Unfortunately, AsyncHelper
has internal visibility, so we can't just reference it from EF. Here's the source, though, so you can make use of it in your own projects:
public static class AsyncHelper
{
private static readonly TaskFactory _myTaskFactory = new
TaskFactory(CancellationToken.None,
TaskCreationOptions.None,
TaskContinuationOptions.None,
TaskScheduler.Default);
public static TResult RunSync<TResult>(Func<Task<TResult>> func)
{
return AsyncHelper._myTaskFactory
.StartNew<Task<TResult>>(func)
.Unwrap<TResult>()
.GetAwaiter()
.GetResult();
}
public static void RunSync(Func<Task> func)
{
AsyncHelper._myTaskFactory
.StartNew<Task>(func)
.Unwrap()
.GetAwaiter()
.GetResult();
}
}
Finally, we just need our Ninject bindings. In App_Start\NinjectWebCommon.cs
, find the RegisterServices
method and add the following:
using Ninject.Web.Mvc;
using Ninject.Web.Mvc.FilterBindingSyntax;
...
kernel.BindFilter<GetUserFilter>(FilterScope.Controller, 0)
.WhenActionMethodHas<GetUserAttribute>();
This will get Ninject to replace our placeholder GetUser
attribute with the actual GetUserFilter
workhorse, injected with all the necessary dependencies. Obviously, you'll need to bind those dependencies, as well, but you should already be doing that if you're using Ninject. However, in case you haven't gotten that far, yet, you'll need add this to the same method:
using Microsoft.Owin.Security;
...
kernel.Bind<IAuthenticationManager>().ToMethod(c =>
HttpContext.Current.GetOwinContext().Authentication).InRequestScope();
kernel.Bind<ApplicationUserManager>().ToMethod(c =>
HttpContext.Current.GetOwinContext().GetUserManager<ApplicationUserManager>()).InRequestScope();