Aspnetcore: Exception in HubConnectionBuilder.Build using .net5 preview 8

Created on 26 Aug 2020  路  13Comments  路  Source: dotnet/aspnetcore

Describe the bug

I have a working Blazor WASM project (using 3.2) that also uses SignalR to connect to a hub on the server.
When I update to .NET 5 Preview 8, I get an exception when trying to call HubConnectionBuilder.Build.

To Reproduce

I reproduced the same issue by following the instructions here (except creating the project with .NET 5.0 instead of .NET Core 3.1), and trying to run it using Visual Studio.
cc: @guardrex

Exceptions (if any)

Unhandled exception rendering component: Exception has been thrown by the target of an invocation.
System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation.
 ---> System.PlatformNotSupportedException: System.Security.Cryptography is not supported on this platform.
   at System.Security.Cryptography.X509Certificates.X509CertificateCollection..ctor()
   at Microsoft.AspNetCore.Http.Connections.Client.HttpConnectionOptions..ctor()
   at System.Reflection.RuntimeConstructorInfo.InternalInvoke(Object obj, Object[] parameters, Boolean wrapExceptions)
   --- End of inner exception stack trace ---
   at System.Reflection.RuntimeConstructorInfo.InternalInvoke(Object obj, Object[] parameters, Boolean wrapExceptions)
   at System.RuntimeType.CreateInstanceMono(Boolean nonPublic, Boolean wrapExceptions)
   at System.RuntimeType.CreateInstanceSlow(Boolean publicOnly, Boolean wrapExceptions, Boolean skipCheckThis, Boolean fillCache)
   at System.RuntimeType.CreateInstanceDefaultCtor(Boolean publicOnly, Boolean skipCheckThis, Boolean fillCache, Boolean wrapExceptions)
   at System.Activator.CreateInstance[HttpConnectionOptions]()
   at Microsoft.Extensions.Options.OptionsFactory`1[[Microsoft.AspNetCore.Http.Connections.Client.HttpConnectionOptions, Microsoft.AspNetCore.Http.Connections.Client, Version=5.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60]].CreateInstance(String name)
   at Microsoft.Extensions.Options.OptionsFactory`1[[Microsoft.AspNetCore.Http.Connections.Client.HttpConnectionOptions, Microsoft.AspNetCore.Http.Connections.Client, Version=5.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60]].Create(String name)
   at Microsoft.Extensions.Options.OptionsManager`1.<>c__DisplayClass5_0[[Microsoft.AspNetCore.Http.Connections.Client.HttpConnectionOptions, Microsoft.AspNetCore.Http.Connections.Client, Version=5.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60]].<Get>b__0()
   at System.Lazy`1[[Microsoft.AspNetCore.Http.Connections.Client.HttpConnectionOptions, Microsoft.AspNetCore.Http.Connections.Client, Version=5.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60]].ViaFactory(LazyThreadSafetyMode mode)
   at System.Lazy`1[[Microsoft.AspNetCore.Http.Connections.Client.HttpConnectionOptions, Microsoft.AspNetCore.Http.Connections.Client, Version=5.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60]].ExecutionAndPublication(LazyHelper executionAndPublication, Boolean useDefaultConstructor)
   at System.Lazy`1[[Microsoft.AspNetCore.Http.Connections.Client.HttpConnectionOptions, Microsoft.AspNetCore.Http.Connections.Client, Version=5.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60]].CreateValue()
   at System.Lazy`1[[Microsoft.AspNetCore.Http.Connections.Client.HttpConnectionOptions, Microsoft.AspNetCore.Http.Connections.Client, Version=5.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60]].get_Value()
   at Microsoft.Extensions.Options.OptionsCache`1[[Microsoft.AspNetCore.Http.Connections.Client.HttpConnectionOptions, Microsoft.AspNetCore.Http.Connections.Client, Version=5.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60]].GetOrAdd(String name, Func`1 createOptions)
   at Microsoft.Extensions.Options.OptionsManager`1[[Microsoft.AspNetCore.Http.Connections.Client.HttpConnectionOptions, Microsoft.AspNetCore.Http.Connections.Client, Version=5.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60]].Get(String name)
   at Microsoft.Extensions.Options.OptionsManager`1[[Microsoft.AspNetCore.Http.Connections.Client.HttpConnectionOptions, Microsoft.AspNetCore.Http.Connections.Client, Version=5.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60]].get_Value()
   at Microsoft.AspNetCore.Http.Connections.Client.HttpConnectionFactory..ctor(IOptions`1 options, ILoggerFactory loggerFactory)
   at System.Reflection.RuntimeConstructorInfo.InternalInvoke(Object obj, Object[] parameters, Boolean wrapExceptions)
   at System.Reflection.RuntimeConstructorInfo.DoInvoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
   at System.Reflection.RuntimeConstructorInfo.Invoke(BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitConstructor(ConstructorCallSite constructorCallSite, RuntimeResolverContext context)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2[[Microsoft.Extensions.DependencyInjection.ServiceLookup.RuntimeResolverContext, Microsoft.Extensions.DependencyInjection, Version=5.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60],[System.Object, System.Private.CoreLib, Version=5.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].VisitCallSiteMain(ServiceCallSite callSite, RuntimeResolverContext argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitCache(ServiceCallSite callSite, RuntimeResolverContext context, ServiceProviderEngineScope serviceProviderEngine, RuntimeResolverLock lockType)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitRootCache(ServiceCallSite singletonCallSite, RuntimeResolverContext context)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2[[Microsoft.Extensions.DependencyInjection.ServiceLookup.RuntimeResolverContext, Microsoft.Extensions.DependencyInjection, Version=5.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60],[System.Object, System.Private.CoreLib, Version=5.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].VisitCallSite(ServiceCallSite callSite, RuntimeResolverContext argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.Resolve(ServiceCallSite callSite, ServiceProviderEngineScope scope)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.RuntimeServiceProviderEngine.<>c__DisplayClass1_0.<RealizeService>b__1(ServiceProviderEngineScope p)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.RuntimeServiceProviderEngine.<>c__DisplayClass1_0.<RealizeService>b__0(ServiceProviderEngineScope scope)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngine.GetService(Type serviceType, ServiceProviderEngineScope serviceProviderEngineScope)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngine.GetService(Type serviceType)
   at Microsoft.Extensions.DependencyInjection.ServiceProvider.GetService(Type serviceType)
   at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetService[IConnectionFactory](IServiceProvider provider)
   at Microsoft.AspNetCore.SignalR.Client.HubConnectionBuilder.Build()
   at test.Client.Pages.Index.OnInitializedAsync()
   at Microsoft.AspNetCore.Components.ComponentBase.RunInitAndSetParametersAsync()

Further technical details

  • ASP.NET Core Runtime 5.0.0-preview.8.20407.11

  • Include the output of dotnet --info:
    .NET SDK (reflecting any global.json):
    Version: 5.0.100-preview.8.20417.9
    Commit: fc62663a35

Runtime Environment:
OS Name: Windows
OS Version: 10.0.19041
OS Platform: Windows
RID: win10-x64
Base Path: C:\Program Files\dotnet\sdk\5.0.100-preview.8.20417.9\

Host (useful for support):
Version: 5.0.0-preview.8.20407.11
Commit: bf456654f9

.NET SDKs installed:
3.1.401 [C:\Program Files\dotnet\sdk]
5.0.100-preview.8.20417.9 [C:\Program Files\dotnet\sdk]

.NET runtimes installed:
Microsoft.AspNetCore.All 2.1.21 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All]
Microsoft.AspNetCore.App 2.1.21 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 3.1.7 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 5.0.0-preview.8.20414.8 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.NETCore.App 2.1.21 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 3.1.7 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 5.0.0-preview.8.20407.11 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.WindowsDesktop.App 3.1.7 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
Microsoft.WindowsDesktop.App 5.0.0-preview.8.20411.6 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]

To install additional .NET runtimes or SDKs:
https://aka.ms/dotnet-download

  • Visual Studio 16.8 Preview 2
Done area-blazor area-signalr

Most helpful comment

Trying for a workaround:
So if you create an instance of HttpConnectionOptions without calling the constructor (and building up the properties yourself) and add it to the builder.Services collection as an IOptions<> this almost works except the HttpConnectionFactory calls the constructor on it to clone it. If you make your own implementation of IConnectionFactory and use that without calling the constructor you can make this work.

I only wanted it for a proof of concept so I'm sure there are lots of other bits broken by doing all this.

Hub setup:
```C#
var builder = new HubConnectionBuilder();
var httpConnectionOptions = HttpConnectionFactoryInternal.createHttpConnectionOptions(); // work around constructor call
httpConnectionOptions.Url = navigationManager.ToAbsoluteUri("/messageBus");
builder.Services.AddSingleton(new UriEndPoint(httpConnectionOptions.Url));
var opt = Microsoft.Extensions.Options.Options.Create(httpConnectionOptions);
builder.Services.AddSingleton(opt);
builder.Services.AddSingleton();

// normal stuff
hubConnection = builder.Build();
hubConnection.On("blah", blah);
await hubConnection.StartAsync();

Custom IConnectionFactory, mostly copy and paste from actual one here; https://github.com/dotnet/aspnetcore/blob/master/src/SignalR/clients/csharp/Http.Connections.Client/src/HttpConnectionFactory.cs
```C#
    /// <summary>
    /// A factory for creating <see cref="HttpConnection"/> instances.
    /// </summary>
    public class HttpConnectionFactoryInternal : IConnectionFactory
    {
        private readonly HttpConnectionOptions _httpConnectionOptions;
        private readonly ILoggerFactory _loggerFactory;

        /// <summary>
        /// Initializes a new instance of the <see cref="HttpConnectionFactory"/> class.
        /// </summary>
        /// <param name="options">The connection options.</param>
        /// <param name="loggerFactory">The logger factory.</param>
        public HttpConnectionFactoryInternal(ILoggerFactory loggerFactory)
        {
            _httpConnectionOptions = createHttpConnectionOptions();
            _loggerFactory = loggerFactory ?? throw new ArgumentNullException(nameof(loggerFactory));
        }

        internal static HttpConnectionOptions createHttpConnectionOptions()
        {
            var o = System.Runtime.Serialization.FormatterServices.GetUninitializedObject(typeof(HttpConnectionOptions))
                as HttpConnectionOptions;

            o.Headers = new Dictionary<string, string>();
            o.Cookies = new System.Net.CookieContainer();
            o.Transports = Microsoft.AspNetCore.Http.Connections.HttpTransports.All;
            o.DefaultTransferFormat = TransferFormat.Binary;
            o.CloseTimeout = TimeSpan.FromSeconds(5);
            return o;
        }

        /// <summary>
        /// Creates a new connection to an <see cref="UriEndPoint"/>.
        /// </summary>
        /// <param name="endPoint">The <see cref="UriEndPoint"/> to connect to.</param>
        /// <param name="cancellationToken">The token to monitor for cancellation requests. The default value is <see cref="CancellationToken.None" />.</param>
        /// <returns>
        /// A <see cref="ValueTask{TResult}" /> that represents the asynchronous connect, yielding the <see cref="ConnectionContext" /> for the new connection when completed.
        /// </returns>
        public async ValueTask<ConnectionContext> ConnectAsync(EndPoint endPoint, CancellationToken cancellationToken = default)
        {
            if (endPoint == null)
            {
                throw new ArgumentNullException(nameof(endPoint));
            }

            if (!(endPoint is UriEndPoint uriEndPoint))
            {
                throw new NotSupportedException($"The provided {nameof(EndPoint)} must be of type {nameof(UriEndPoint)}.");
            }

            if (_httpConnectionOptions.Url != null && _httpConnectionOptions.Url != uriEndPoint.Uri)
            {
                throw new InvalidOperationException($"If {nameof(HttpConnectionOptions)}.{nameof(HttpConnectionOptions.Url)} was set, it must match the {nameof(UriEndPoint)}.{nameof(UriEndPoint.Uri)} passed to {nameof(ConnectAsync)}.");
            }

            // Shallow copy before setting the Url property so we don't mutate the user-defined options object.
            var shallowCopiedOptions = ShallowCopyHttpConnectionOptions(_httpConnectionOptions);
            shallowCopiedOptions.Url = uriEndPoint.Uri;

            var connection = new HttpConnection(shallowCopiedOptions, _loggerFactory);

            try
            {
                await connection.StartAsync(cancellationToken);
                return connection;
            }
            catch
            {
                // Make sure the connection is disposed, in case it allocated any resources before failing.
                await connection.DisposeAsync();
                throw;
            }
        }

        // Internal for testing
        internal static HttpConnectionOptions ShallowCopyHttpConnectionOptions(HttpConnectionOptions options)
        {
            return options;
        }
    }

All 13 comments

/cc @marek-safar

There is still error in this library, im also blocked in preview 6 because preview 7 had problems also with this library.

It still planned to have this library working for 5.0, but it seem dead for the preview 8

https://github.com/dotnet/runtime/issues/39756
https://github.com/dotnet/runtime/issues/40076

This will need fixes on aspnetcore side unless we add back the API which I don't think it's worth it as it will not work and only increase size/memory footprint.

I'd suggest to move the ctor call into getter and mark the property as [UnsupportedOSPlatform("browser")]

@jeffhandley this is a good example of what the API analyzer should catch in our own code

Is there any workaround for getting a singalr connection from a blazor client until this is fixed?

You could try setting the HttpTransportType to LongPolling. But I haven't tested it yet myself.

HubConnection connection = new HubConnectionBuilder()
    .WithUrl(new Uri("http://127.0.0.1:5000/chathub"), HttpTransportType.LongPolling)
    .Build();

Still nothing holding pattern it is.

I waited on Preview 8 as this was supposed to be fixed, sigh

Trying for a workaround:
So if you create an instance of HttpConnectionOptions without calling the constructor (and building up the properties yourself) and add it to the builder.Services collection as an IOptions<> this almost works except the HttpConnectionFactory calls the constructor on it to clone it. If you make your own implementation of IConnectionFactory and use that without calling the constructor you can make this work.

I only wanted it for a proof of concept so I'm sure there are lots of other bits broken by doing all this.

Hub setup:
```C#
var builder = new HubConnectionBuilder();
var httpConnectionOptions = HttpConnectionFactoryInternal.createHttpConnectionOptions(); // work around constructor call
httpConnectionOptions.Url = navigationManager.ToAbsoluteUri("/messageBus");
builder.Services.AddSingleton(new UriEndPoint(httpConnectionOptions.Url));
var opt = Microsoft.Extensions.Options.Options.Create(httpConnectionOptions);
builder.Services.AddSingleton(opt);
builder.Services.AddSingleton();

// normal stuff
hubConnection = builder.Build();
hubConnection.On("blah", blah);
await hubConnection.StartAsync();

Custom IConnectionFactory, mostly copy and paste from actual one here; https://github.com/dotnet/aspnetcore/blob/master/src/SignalR/clients/csharp/Http.Connections.Client/src/HttpConnectionFactory.cs
```C#
    /// <summary>
    /// A factory for creating <see cref="HttpConnection"/> instances.
    /// </summary>
    public class HttpConnectionFactoryInternal : IConnectionFactory
    {
        private readonly HttpConnectionOptions _httpConnectionOptions;
        private readonly ILoggerFactory _loggerFactory;

        /// <summary>
        /// Initializes a new instance of the <see cref="HttpConnectionFactory"/> class.
        /// </summary>
        /// <param name="options">The connection options.</param>
        /// <param name="loggerFactory">The logger factory.</param>
        public HttpConnectionFactoryInternal(ILoggerFactory loggerFactory)
        {
            _httpConnectionOptions = createHttpConnectionOptions();
            _loggerFactory = loggerFactory ?? throw new ArgumentNullException(nameof(loggerFactory));
        }

        internal static HttpConnectionOptions createHttpConnectionOptions()
        {
            var o = System.Runtime.Serialization.FormatterServices.GetUninitializedObject(typeof(HttpConnectionOptions))
                as HttpConnectionOptions;

            o.Headers = new Dictionary<string, string>();
            o.Cookies = new System.Net.CookieContainer();
            o.Transports = Microsoft.AspNetCore.Http.Connections.HttpTransports.All;
            o.DefaultTransferFormat = TransferFormat.Binary;
            o.CloseTimeout = TimeSpan.FromSeconds(5);
            return o;
        }

        /// <summary>
        /// Creates a new connection to an <see cref="UriEndPoint"/>.
        /// </summary>
        /// <param name="endPoint">The <see cref="UriEndPoint"/> to connect to.</param>
        /// <param name="cancellationToken">The token to monitor for cancellation requests. The default value is <see cref="CancellationToken.None" />.</param>
        /// <returns>
        /// A <see cref="ValueTask{TResult}" /> that represents the asynchronous connect, yielding the <see cref="ConnectionContext" /> for the new connection when completed.
        /// </returns>
        public async ValueTask<ConnectionContext> ConnectAsync(EndPoint endPoint, CancellationToken cancellationToken = default)
        {
            if (endPoint == null)
            {
                throw new ArgumentNullException(nameof(endPoint));
            }

            if (!(endPoint is UriEndPoint uriEndPoint))
            {
                throw new NotSupportedException($"The provided {nameof(EndPoint)} must be of type {nameof(UriEndPoint)}.");
            }

            if (_httpConnectionOptions.Url != null && _httpConnectionOptions.Url != uriEndPoint.Uri)
            {
                throw new InvalidOperationException($"If {nameof(HttpConnectionOptions)}.{nameof(HttpConnectionOptions.Url)} was set, it must match the {nameof(UriEndPoint)}.{nameof(UriEndPoint.Uri)} passed to {nameof(ConnectAsync)}.");
            }

            // Shallow copy before setting the Url property so we don't mutate the user-defined options object.
            var shallowCopiedOptions = ShallowCopyHttpConnectionOptions(_httpConnectionOptions);
            shallowCopiedOptions.Url = uriEndPoint.Uri;

            var connection = new HttpConnection(shallowCopiedOptions, _loggerFactory);

            try
            {
                await connection.StartAsync(cancellationToken);
                return connection;
            }
            catch
            {
                // Make sure the connection is disposed, in case it allocated any resources before failing.
                await connection.DisposeAsync();
                throw;
            }
        }

        // Internal for testing
        internal static HttpConnectionOptions ShallowCopyHttpConnectionOptions(HttpConnectionOptions options)
        {
            return options;
        }
    }

I remember having the same error before, it reappeared yesterday as I updated VS with Preview 8. @billknye suggestion worked.

To be clear. I need Crypt for other purposes. So not what works above. I hope this makes it into RC1.

Fixed for RC1, @bfmsoft follow the Runtime issue for Crypto support in 5.0

I'm doing a POC with Blazor WebAssembly in .NET 5, and I'm getting this error too.

They say in RC1 but then again it was going to be in Preview 7 then 8...

Was this page helpful?
0 / 5 - 0 ratings