COMException: невозможно изменить режим потока после того, как он установлен в SureCoreWebView2Async(null) — ошибочная реализация в IdentityModel.OidcClient?

Я пытаюсь внедрить функцию интерактивного входа в свое приложение .NET 6 Winforms, используя Identity Server 6 в качестве моего IDP.

Я смоделировал свой клиентский код на эталонном примере Identity Server для WinForms. Справочный пример проекта:

https://github.com/IdentityModel/IdentityModel.OidcClient.Samples/tree/main/WinFormsWebView2/WinFormsWebView2

Код эталонного проекта работает нормально; но мой проект создает это исключение из компонента пользовательского интерфейса WebView2:

COMException: невозможно изменить режим потока после того, как он установлен в SureCoreWebView2Async(null) C#

Оба проекта используют одни и те же пакеты: IdentityModel.OidcClient v5.2.1 и Microsoft.Web.WebView2 v1.0.1823.32.

Ключевое различие между этими двумя проектами заключается в том, что мой — .NET 6, тогда как эталонный проект — .NET Framework 4.7.2.

В статье SO, упомянутой выше, указанной основной причиной является вызов метода SureCoreWebView2Async компонента WebView2 из «неправильного» потока, то есть из потока, отличного от потока пользовательского интерфейса. Однако в контексте обсуждаемых здесь проектов этот вызов выполняется через стек вызовов, который состоит из чужого кода; поэтому я не уверен в лучшем пути вперед. Есть ли ошибка реализации в коде OidcClient при использовании в .NET 6; или проблема в коде эталонного образца; или я делаю что-то не так в своем коде, чего не заметил?

Это мой код Program.cs:

         internal static class Program
{
    /// <summary>
    ///  The main entry point for the application.
    /// </summary>
    [STAThread]
    static async Task Main()
    {
        // To customize application configuration such as set high DPI settings or default font,
        // see https://aka.ms/applicationconfiguration.
        ApplicationConfiguration.Initialize();

        var configuration = new ConfigurationBuilder()
            .AddJsonFile($"appsettings.json");

        var config = configuration.Build();

        Application.Run(new LandingForm(config));
    }
}

И в моей форме LandingForm

          public partial class LandingForm : Form
{
//
        private readonly WinFormsWebView winFormsWebView;

    public LandingForm()
    {
        InitializeComponent();
    }

    public LandingForm(IConfigurationRoot config)
    {
        InitializeComponent();

        this.config = config;

        this.winFormsWebView = new WinFormsWebView();

    }


    private void LandingForm_Load(object sender, EventArgs e)
    {

    }

    private async void LoginBtn_Click(object sender, EventArgs e)
    {
        var oidcOptions = new OidcClientOptions
        {
            Authority = config["IdentityServerAddress"].ToString(),
            ClientId = "xxx-client",
            Scope = "xxx-scopes",
            RedirectUri = "http://localhost/winforms.client",
            Browser = this.winFormsWebView
        };
        
        OidcClient _oidcClient = new OidcClient(oidcOptions);
                    
        LoginResult loginResult = await _oidcClient.LoginAsync();

        if (loginResult.IsError)
        {
            MessageBox.Show(loginResult.Error, "Login", MessageBoxButtons.OK, MessageBoxIcon.Error);
            return;
        }


        vr2Button.Enabled = true;
    }
}

В моем проекте код WinFormsWebView.cs идентичен коду рабочего примера (за исключением того, что он находится в другом пространстве имен), но я полностью публикую его здесь для удобства использования и добавляю комментарий, в котором возникает исключение:

          public class WinFormsWebView : IBrowser
{
    private readonly Func<Form> _formFactory;
    private BrowserOptions _options;

    public WinFormsWebView(Func<Form> formFactory)
    {
        _formFactory = formFactory;
    }

    public WinFormsWebView(string title = "Authenticating ...", int width = 1024, int height = 768)
        : this(() => new Form
        {
            Name = "WebAuthentication",
            Text = title,
            Width = width,
            Height = height
        })
    { 
    }

    public async Task<BrowserResult> InvokeAsync(BrowserOptions options, CancellationToken token = default)
    {
        _options = options;

        using (var form = _formFactory.Invoke())
        {
            using (var webView = new Microsoft.Web.WebView2.WinForms.WebView2()
            {
                Dock = DockStyle.Fill
            })
            {
                var signal = new SemaphoreSlim(0, 1);

                var browserResult = new BrowserResult
                {
                    ResultType = BrowserResultType.UserCancel
                };

                form.FormClosed += (o, e) =>
                {
                    signal.Release();
                };

                webView.NavigationStarting += (s, e) =>
                {
                    if (IsBrowserNavigatingToRedirectUri(new Uri(e.Uri)))
                    {
                        e.Cancel = true;

                        browserResult = new BrowserResult()
                        {
                            ResultType = BrowserResultType.Success,
                            Response = new Uri(e.Uri).AbsoluteUri
                        };

                        signal.Release();
                        form.Close();
                    }
                };

                try
                {
                    form.Controls.Add(webView);
                    webView.Show();

                    form.Show();

// EXCEPTION OCCURS ON THIS NEXT LINE 
                    // Initialization
                    await webView.EnsureCoreWebView2Async(null);

                    // Delete existing Cookies so previous logins won't remembered
                    webView.CoreWebView2.CookieManager.DeleteAllCookies();

                    // Navigate
                    webView.CoreWebView2.Navigate(_options.StartUrl);

                    await signal.WaitAsync();
                }
                finally
                {
                    form.Hide();
                    webView.Hide();
                }

                return browserResult;
            }
        }
    }

    private bool IsBrowserNavigatingToRedirectUri(Uri uri)
    {
        return uri.AbsoluteUri.StartsWith(_options?.EndUrl);
    }
}

Я могу представить, как основная причина, описанная в другом сообщении SO, может применяться к моему проекту, потому что в конечном итоге я вызываю SureCoreWebView2Async() из асинхронного метода LoginBtn_Click(), и поскольку этот метод является асинхронным, я думаю, он не работает на Поток пользовательского интерфейса. Однако я не совсем уверен в этом, поскольку мой код в этом отношении существенно не отличается от примера ссылочного кода, который работает нормально (не вызывает исключений).

1 ответ

В Program.cs используйте это:

      [STAThread]
        static void Main()
        {}

Вместо использования асинхронного эквивалента:

      [STAThread]
        static async Task Main()
        {}
Другие вопросы по тегам