COMException: невозможно изменить режим потока после того, как он установлен в SureCoreWebView2Async(null) — ошибочная реализация в IdentityModel.OidcClient?
Я пытаюсь внедрить функцию интерактивного входа в свое приложение .NET 6 Winforms, используя Identity Server 6 в качестве моего IDP.
Я смоделировал свой клиентский код на эталонном примере Identity Server для WinForms. Справочный пример проекта:
Код эталонного проекта работает нормально; но мой проект создает это исключение из компонента пользовательского интерфейса WebView2:
Оба проекта используют одни и те же пакеты: 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()
{}