在.NET Core中,async/await 已经得到原生支持,但在.NET Framework的MVC4中,这并不是一个内置的功能。尽管如此,仍然可以通过一些技巧来实现异步代码的同步执行。本文将介绍如何在不升级到.NET Core的情况下,使用异步代码编写AuthorizeAttribute。
在.NET Framework中,异步方法的同步执行通常需要借助一些辅助类。例如,Chris McKee在GitHub上分享了一个名为AsyncHelpers的类,该类可以帮助实现这一功能。以下是该类的一个实现示例:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Web;
namespace GordonBeeming.ApiHelpers
{
public static class AsyncHelpers
{
public static void RunSync(Func task)
{
var oldContext = SynchronizationContext.Current;
var synch = new ExclusiveSynchronizationContext();
SynchronizationContext.SetSynchronizationContext(synch);
synch.Post(async _ =>
{
try
{
await task();
}
catch (Exception e)
{
synch.InnerException = e;
throw;
}
finally
{
synch.EndMessageLoop();
}
}, null);
synch.BeginMessageLoop();
SynchronizationContext.SetSynchronizationContext(oldContext);
}
public static T RunSync(Func> task)
{
var oldContext = SynchronizationContext.Current;
var synch = new ExclusiveSynchronizationContext();
SynchronizationContext.SetSynchronizationContext(synch);
T ret = default(T);
synch.Post(async _ =>
{
try
{
ret = await task();
}
catch (Exception e)
{
synch.InnerException = e;
throw;
}
finally
{
synch.EndMessageLoop();
}
}, null);
synch.BeginMessageLoop();
SynchronizationContext.SetSynchronizationContext(oldContext);
return ret;
}
private class ExclusiveSynchronizationContext : SynchronizationContext
{
private bool done;
public Exception InnerException { get; set; }
readonly AutoResetEvent workItemsWaiting = new AutoResetEvent(false);
readonly Queue> items = new Queue>();
public override void Send(SendOrPostCallback d, object state)
{
throw new NotSupportedException("We cannot send to our same thread");
}
public override void Post(SendOrPostCallback d, object state)
{
lock (items)
{
items.Enqueue(Tuple.Create(d, state));
}
workItemsWaiting.Set();
}
public void EndMessageLoop()
{
Post(_ => done = true, null);
}
public void BeginMessageLoop()
{
while (!done)
{
Tuple task = null;
lock (items)
{
if (items.Count > 0)
{
task = items.Dequeue();
}
}
if (task != null)
{
task.Item1(task.Item2);
if (InnerException != null)
{
throw new AggregateException("AsyncHelpers.Run method threw an exception.", InnerException);
}
}
else
{
workItemsWaiting.WaitOne();
}
}
}
public override SynchronizationContext CreateCopy()
{
return this;
}
}
}
}
通过这个辅助类,可以轻松地将异步代码包装成同步执行。例如:
AsyncHelpers.RunSync(MyMethodAsync);
接下来,将探讨本文的核心内容——创建异步AuthorizeAttribute。
值得注意的是,上述代码不仅适用于AuthorizeAttribute,它在任何需要异步代码的场景中都可以使用。在标准的OnAuthorization方法中,通过调用异步的OnAuthorization方法,并将所有逻辑放在其中,以保持代码的清晰性。
using nologo.Chassis.Part.Identity;
using nologo.Common.Core;
using System;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using System.Web.Http.Controllers;
using System.Web.Mvc;
namespace GordonBeeming.Attributes
{
public class AuthorizeAsyncAttribute : AuthorizeAttribute
{
public override void OnAuthorization(AuthorizationContext filterContext)
{
AsyncHelpers.RunSync(() => OnAuthorizationAsync(filterContext));
}
public async Task OnAuthorizationAsync(AuthorizationContext filterContext)
{
var profile = await ProfileHelper.GetFromApi();
// 根据profile执行一些操作
}
public int AllowedRole { get; set; }
public int[] AllowedRoles { get; set; }
}
}