Duplicate registration exception when using different ports
See original GitHub issueI’m trying to migrate an existing .NET Framework application to using CoreWCF.
There are multiple services in the application, with each service binding to a different port. This
works fine using the original WCF libraries (using a ServiceHost
instead of ASP.NET Core), but
using CoreWCF I run into issues.
Without using UseNetTcp
, adding multiple TCP service endpoints on different ports works fine.
However, these ports are not actually opened.
With UseNetTcp
, adding one TCP service endpoint for one service works fine, but as soon as I try
to add another service with its own TCP service endpoint (on a different port), I get an exception
saying a registration already exists for URI 'net.tcp://localhost:8051/MyService1/svc'
.
Creating the web host builder:
IWebHost webHost = WebHost.CreateDefaultBuilder( ).UseStartup< Startup >( ).UseNetTcp( ).Build( );
webHost.Run( );
A simplified version of the Startup
class:
public class Startup
{
public void ConfigureServices(
IServiceCollection services )
{
services.AddServiceModelServices( ).AddServiceModelMetadata( );
}
public void Configure(
IApplicationBuilder app,
IHostingEnvironment env )
{
app.UseServiceModel(
builder =>
{
foreach ( EServiceType serviceType in new[ ]
{
EServiceType.One,
EServiceType.Two,
} )
{
string hostName = Properties.Host;
string servicePath = Properties.UriPath( serviceType );
int tcpPort = Properties.TcpPort( serviceType );
SecurityMode = Properties.TcpSecurityMode( serviceType );
int maxResponseSize = Properties.MaxResponseSize( serviceType ) );
Type implementation = Type.GetType( Properties.ServiceImplementation( serviceType ), false );
Type contractType = Type.GetType( Properties.ServiceContract( serviceType ), false );
Uri tcpAddress = new Uri( $"net.tcp://{hostName}:{tcpPort}/{servicePath}/svc" );
builder.AddService( implementation );
builder.AddServiceEndpoint(
implementation,
contractType,
new NetTcpBinding( tcpSecurityMode )
{
MaxBufferPoolSize = maxResponseSize,
MaxBufferSize = maxResponseSize,
MaxReceivedMessageSize = maxResponseSize,
ReaderQuotas = new XmlDictionaryReaderQuotas
{
MaxStringContentLength = maxResponseSize,
MaxBytesPerRead = maxResponseSize,
MaxArrayLength = maxResponseSize,
MaxDepth = 64
},
OpenTimeout = Properties.OpenTimeout,
CloseTimeout = Properties.CloseTimeout,
SendTimeout = Properties.SendTimeout,
ReceiveTimeout = Properties.ReceiveTimeout
},
tcpAddress,
tcpAddress );
} );
}
}
The exception stacktrace:
System.InvalidOperationException: A registration already exists for URI 'net.tcp://localhost:8051/MyService/svc'.
at CoreWCF.Channels.UriPrefixTable`1.RegisterUri(Uri uri, HostNameComparisonMode hostNameComparisonMode, TItem item)
at CoreWCF.Channels.Framing.NetMessageFramingConnectionHandler.BuildAddressTable(IServiceProvider services)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitScoped(ScopedCallSite scopedCallSite, ServiceProviderEngineScope scope)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.DynamicServiceProviderEngine.<>c__DisplayClass1_0.<RealizeService>b__0(ServiceProviderEngineScope scope)
at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(IServiceProvider provider, Type serviceType)
at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService[T](IServiceProvider provider)
at System.EventHandler.Invoke(Object sender, EventArgs e)
at CoreWCF.Channels.CommunicationObject.OnOpened()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at CoreWCF.Configuration.WrappingIServer.<StartAsync>d__9`1.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at Microsoft.AspNetCore.Hosting.Internal.WebHost.<StartAsync>d__26.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at Microsoft.AspNetCore.Hosting.WebHostExtensions.<RunAsync>d__5.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at Microsoft.AspNetCore.Hosting.WebHostExtensions.<RunAsync>d__4.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at Microsoft.AspNetCore.Hosting.WebHostExtensions.Run(IWebHost host)
Issue Analytics
- State:
- Created a year ago
- Comments:7 (4 by maintainers)
Top GitHub Comments
For now the fastest path to a solution is to run multiple ASP.NET Core instances in the same app, one per port. This is similar to running a separate ServiceHost instance per port. I don’t know how well that will interact with UseWindowsService though. You might be able to have just one Host instance be the one that manages it being a Windows Service. I don’t know anything about that feature or how it interacts with Windows, but I presume it handles shutting down of the service gracefully when the service is stopped. You could use the IApplicationLifetime hook of the “primary” service to then trigger cleanly shutting down the others.
Each service endpoint needs a unique url. When you connect to net.tcp://localhost:8068/mrs, it doesn’t know whether you are connecting to the endpoint for ICalculate or ICalculate2 as they’ve both been registered for the same url. Under the hood, it’s looking up the EndpointDispatcher in a filter table, and it does this by using the endpoint url the client sends on connection. It found two matching endpoints and has no info to pick one over the other.
If you want ICalculate and ICalculate2 to appear at the same endpoint address, you can create a new interface with no methods which derives from both of them.
Then have a single AddServiceEndpoint call with this new interface at a single address. On the client side you can use just ICalculate or just ICalculate2 and the service will look like it only implements that one interface.
Otherwise, use mrs and mrs2 as the url’s for the 2 endpoints so they are unique.