Click here to Skip to main content
13,737,943 members
Click here to Skip to main content
Add your own
alternative version

Tagged as

Stats

6.5K views
9 bookmarked
Posted 25 Jan 2018
Licenced MIT

Creating an Asynchronous AuthorizeAttribute in MVC

, 25 Jan 2018
Rate this:
Please Sign up or sign in to vote.
How to create an asynchronous AuthorizeAttribute in MVC

A couple days ago, I needed to call a remote web API call in my AuthorizeAttribute sometimes, but as mentioned in this (Is it possible to use async/await in MVC 4 AuthorizeAttribute?) StackOverflow question (and other forums), it isn’t supported but is in the newer .NET Core. Unfortunately, the project I needed this on was traditional MVC so I was left still finding a way Smile.

Running Asynchronous Methods in C# Synchronous

For the longest time, to achieve running async functionality synchronously, I've used an async helper class, not sure this is the exact place I found it, but Chris McKee currently hosts a version on GitHub gists but for convenience and in case it goes away, I have hosted it on mine as well as you can see below:

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
    {
        /// <summary>
        /// Execute's an async Task<T> method which has a void return value synchronously
        /// </summary>
        /// <param name="task">Task<T> method to execute</param>
        public static void RunSync(Func<Task> 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);
        }

        /// <summary>
        /// Execute's an async Task<T> method which has a T return type synchronously
        /// </summary>
        /// <typeparam name="T">Return Type</typeparam>
        /// <param name="task">Task<T> method to execute</param>
        /// <returns></returns>
        public static T RunSync<T>(Func<Task<T>> 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<Tuple<SendOrPostCallback, object>> items =
                new Queue<Tuple<SendOrPostCallback, object>>();

            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<SendOrPostCallback, object> task = null;
                    lock (items)
                    {
                        if (items.Count > 0)
                        {
                            task = items.Dequeue();
                        }
                    }
                    if (task != null)
                    {
                        task.Item1(task.Item2);
                        if (InnerException != null) // the method threw an exception
                        {
                            throw new AggregateException
                            ("AsyncHelpers.Run method threw an exception.", InnerException);
                        }
                    }
                    else
                    {
                        workItemsWaiting.WaitOne();
                    }
                }
            }

            public override SynchronizationContext CreateCopy()
            {
                return this;
            }
        }
    }
}

The code allows you to easily wrap up async code and runs it properly synchronously. A sample of how to do this is below:

AsyncHelpers.RunSync(MyMethodAsync);

Now that we have the utility out the way, let's look at what this post is actually solving.

Creating Your Async AuthorizeAttribute

It's worth knowing that this bit of code magic does work everywhere you need to have async code and isn't specific to auth attribute.

Basically, what we need to do is in the standard OnAuthorization override, we'll add code like above that will just call an async OnAuthorization method and then dump all our logic in there to keep things cleaner.

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();
            
            // do something with profile
        }

        public int AllowedRole { get; set; }
        public int[] AllowedRoles { get; set; }
    }
}

That's it, with this code, you will have no problem calling external APIs when trying to call async code would generally cause deadlocks. Your usage of the attribute will be as you would a normal AuthorizeAttribute.

Conclusion

Some of you might be thinking why is this necessary when you could always just do synchronous API calls in your MVC project, although you aren't wrong in my situation the framework components I was using only supported async so I was forced down this path to re-write the framework component that would probably have taken a lot longer.

License

This article, along with any associated source code and files, is licensed under The MIT License

Share

About the Author

Gordon Beeming (RSA)
Software Developer Nologo Studios
South Africa South Africa
Gordon Beeming works at Nologo Studios in the sunny city of Durban, South Africa. He is the Lead for the Data and Services Team and has a strong focus on Developer Efficiencies and R&D. When he's not hacking away at a keyboard in Visual Studio he'll generally be relaxing with his family or hitting the black top getting in some mileage. He is a Visual Studio ALM Rangers, Visual Studio ALM MVP.

http://beeming.net

You may also be interested in...

Pro

Comments and Discussions

 
-- There are no messages in this forum --
Permalink | Advertise | Privacy | Cookies | Terms of Use | Mobile
Web01-2016 | 2.8.180920.1 | Last Updated 26 Jan 2018
Article Copyright 2018 by Gordon Beeming (RSA)
Everything else Copyright © CodeProject, 1999-2018
Layout: fixed | fluid