2

I'm attempting to use the new ASP.NET Identity 2.0 authentication system(s) in a WebForms application, but I'm having trouble validating a user before allowing the data source for users to save.

The trouble stems from calling IIdentityValidator.ValidateAsync from the data source's OnUpdating event. The markup is functionally identical to the default Dynamic Data templates (except for the addition of Async="true"), with a few customizations in the code behind. Basically, I manually set the MetaTable for the request (since this page is a replacement for one of my dynamic data routes, but I'd like to keep the benefit of scaffolded properties) and I've added the DetailsDataSource_Updating event. Though the code sample below successfully saves the user to our database, the following error is usually thrown before returning to the client:

"An asynchronous module or handler completed while an asynchronous operation was still pending."

I've spent a considerable amount of time attempting to get this to work, but have yet to find a solution that does not lock up the page or throw the above error. I fear that I am completely misunderstanding async/await in WebForms, or worse, that async/await is only really usable for database queries/binding outside of MVC.

public partial class Edit : System.Web.UI.Page
{
    protected UserManager manager;
    protected CustomMetaTable table;

    protected void Page_Init(object sender, EventArgs e)
    {
        manager = UserManager.GetManager(Context.GetOwinContext());
        table = Global.DefaultModel.GetTable(typeof(User)) as CustomMetaTable;
        DynamicDataRouteHandler.SetRequestMetaTable(Context, table);
        FormView1.SetMetaTable(table);
        DetailsDataSource.EntityTypeFilter = table.EntityType.Name;
    }

    protected void Page_Load(object sender, EventArgs e)
    {
        Title = table.EntityName;
        DetailsDataSource.Include = table.ForeignKeyColumnsNames;
    }

    protected void FormView1_ItemCommand(object sender, FormViewCommandEventArgs e)
    {
        if (e.CommandName == DataControlCommands.CancelCommandName)
        {
            Response.Redirect(table.ListActionPath);
        }
    }

    protected void FormView1_ItemUpdated(object sender, FormViewUpdatedEventArgs e)
    {
        if (e.Exception == null || e.ExceptionHandled)
        {
            Response.Redirect(table.ListActionPath);
        }
    }

    protected async void DetailsDataSource_Updating(object sender, Microsoft.AspNet.EntityDataSource.EntityDataSourceChangingEventArgs e)
    {
        IdentityResult result = await manager.UserValidator.ValidateAsync(e.Entity as User);
        if (!result.Succeeded)
        {
            e.Cancel = true;
        }
    }
Kyle
  • 680
  • 1
  • 7
  • 16
  • 4
    This article may be of use: http://www.hanselman.com/blog/TheMagicOfUsingAsynchronousMethodsInASPNET45PlusAnImportantGotcha.aspx – Ant P May 27 '14 at 17:23
  • @AntP So I'm basically limited to `Page.RegisterAsyncTask` for any async processing? – Kyle May 27 '14 at 18:00
  • Normally when a EventHandler calls the method and in that method e.Cancel = true is set it cancels the further event, however when using async the method the EventHandler caller doesn't wait for this and continuing the event and any time later the e.Cancel = true is called. – Martijn van Put May 27 '14 at 21:04
  • With WebForms you have to take in account the page lifecycle. You go through all events to gather the new state of the page. You process that state to get the new state of the system (this is the only part that can be asynchronous). You produce the new page. – Paulo Morgado May 28 '14 at 10:24
  • @PauloMorgado Basically, yes to the question that `Page.RegisterAsyncTask` is the only viable async method for WebForms. – Kyle May 28 '14 at 14:06
  • `Page.RegisterAsyncTask` would probably the easiest method for WebForms, but you might also be use `AsyncManager` (I myself haven't tried it with WebForms). – noseratio May 29 '14 at 04:51
  • See also http://stackoverflow.com/questions/9561632/how-to-use-async-await-to-achieve-asynchronous-page-in-asp-net-webform/42046203#42046203 – Michael Freidgeim Feb 04 '17 at 22:15

1 Answers1

2

In the process of writing a new UserValidator with a synchronous Validate method, I found a class in the Identity assembly which is used in all the synchronous wrappers for UserManager and RoleManager. I copied this class into my project and it has allowed me to consume async methods synchronously with only a few exceptions (the primary exception seems to be avoided by assigning the result to a variable before referencing it elsewhere).

internal 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 _myTaskFactory.StartNew(func).Unwrap().GetAwaiter().GetResult();
    }

    public static void RunSync(Func<Task> func)
    {
        _myTaskFactory.StartNew(func).Unwrap().GetAwaiter().GetResult();
    }
}

Usage:

AsyncHelper.RunSync(() => manager.UserValidator.ValidateAsync(e.Entity as User));
Kyle
  • 680
  • 1
  • 7
  • 16
  • 1
    For other methods how to call asyncronous methods from syncronous see http://stackoverflow.com/questions/9343594/how-to-call-asynchronous-method-from-synchronous-method-in-c – Michael Freidgeim Feb 04 '17 at 22:19