I am attempting to transition away from WCF to move our codebase across to .NET Core. Our services are all hosted as Windows services at present so am trying to self-host the gRPC service as well (rather than building AspNetCore applications). I have successfully built a server using Grpc.Core.Server, and the client side as well with Grpc.Net.Client.GrpcChannel, see the code snippets below for reference.
Server:
var builder = ServerServiceDefinition.CreateBuilder();
// Binder is a small class ripped from the CodeFirst example
var binder = new Binder();
binder.Bind(builder, serviceType, service: serv);
var serverServiceDefinition = builder.Build();
var server = new Grpc.Core.Server
{
Services = { serverServiceDefinition },
Ports = { new ServerPort(host, port, ServerCredentials.Insecure) }
};
server.Start();
Client:
var channel = GrpcChannel.ForAddress(Uri, new GrpcChannelOptions()
{
//HttpHandler = new GrpcWebHandler(new System.Net.Http.HttpClientHandler())
});
var service = channel.CreateGrpcService<TService>();
However because our applications are still running in .Net Framework 4.8 I get the runtime exception when testing out this code:
System.PlatformNotSupportedException : gRPC requires extra configuration on .NET implementations that don't support gRPC over HTTP/2. An HTTP provider must be specified using GrpcChannelOptions.HttpHandler.The configured HTTP provider must either support HTTP/2 or be configured to use gRPC-Web. See https://aka.ms/aspnet/grpc/netstandard for details.
That leads me to add in the Grpc.Net.Client.Web.GrpcWebHandler on the client side to switch over to Grpc-web as per the link in the error.
However, I am now struggling to do the equivalent for the server to support Grpc-web. The guide here suggests to either (1) use Grpc.AspNetCore.Web or (2) use "Envoy proxy" to get the server supporting it. The problem with (1) is that I'm not using AspNetCore so I don't think this solution is appropriate, and I can't find any lightweight/easy way to do (2) in a simple C# solution.
Without the server-side support added, I get this exception:
Grpc.Core.RpcException : Status(StatusCode="Internal", Detail="Error starting gRPC call. HttpRequestException: An error occurred while sending the request. WebException: The server committed a protocol violation. Section=ResponseStatusLine", DebugException="System.Net.Http.HttpRequestException: An error occurred while sending the request.
Which I assume is obviously related to the fact the server isn't supporting the Grpc-web requests. So I am at a bit of a dead end with regards to this now. I feel I need to work out how to self-host AspNetCore servers and move to that instead of Grpc.Core.Server, which will open up option (1), but I am finding little to no evidence that is actually possible.
So I guess my main question is: Is there any way to support Grpc-web clients in a server hosted via Grpc.Core.Server?
And if the answer is no --> How can I self-host a GRPC server that will support Grpc-web clients?
As per this getting started guide I have discovered protobuf-net.Grpc.Native which appears to solve the problem I have at the moment. I also discovered I was missing a default constructor for my [DataContract], which I think was unrelated to the errors I was receiving but may have been contributing.
Related
I'm trying to connect to a remote Dynamics CRM instance and getting this exception on the ServiceClient constructor:
Failed to connect to Dataverse
Inner Exception 1: One or more errors occurred.
Inner exception 2: Need a non-empty authority
Parameter name: Authority
Key here is that it works fine from my dev machine--the error only occurs when I move the code to another server.
Here's the code:
string crmConnectionString =
$"AuthType=OAuth;Username=user#contoso.com;Password=whatever;Url=my-app.crm.dynamics.com;LoginPrompt=Never";
using (ServiceClient service = new ServiceClient(crmConnectionString)) // throws here
I used Wireshark to sniff the data and noticed the working server is sending the client hello using TLS v1.2, whereas the failing server is sending a slightly shorter (fewer bytes) hello using TLS v1. Could the issue be related to this and, if so, how do I fix it?
I have confirmed that TLS 1.2 is indeed required when communicating with online Dynamics 365. The solution in my case was to add this line directly above the constructor:
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
This forces the protocol to TLS 1.2 and allows the code to work on both servers.
Note that there are probably better ways to solve this, such as upgrading your OS to get the newer TLS. That way your code won't be stuck on TLS 1.2 when newer versions become available. But the code addition is a potentially quick way forward for those who need it.
More info here and here.
I created a very simple ASP.NET Core app with SignalR with Visual Studio using a Web App MVC application template with the following customization:
added a reference to #microsoft/signalr library via libman,
referenced <script src="~/lib/microsoft-signalr/signalr.min.js"></script> in _Layout.cshtml,
added the required SignalR services in Startup.cs and created an empty Hub, exposed in the following way:
app.UseEndpoints(endpoints =>
{
endpoints.MapHub<MyHub>("hub/remote");
// ... MVC router definitions
}
created the SignalR connection in JS:
const connection =
new signalR.HubConnectionBuilder()
.withUrl("/hub/remote")
.configureLogging(signalR.LogLevel.Trace)
.withAutomaticReconnect()
.build();
connection.start().then(() => console.log("Connected."));
Then I launched the MVC app and everything started without an error.
However, I took a closer look at SignalR log messages:
I believe this is the SignalR's internal heartbeat that keeps the connection alive.
I wonder why does it take 4-5s between sending the message and receiving the response?
I also tried using SignalR in a more complex application and from time to time I even started receiving "Reconnecting" events, as the load was significantly larger there.
That makes me feel that I do something wrong while configuring the connection, but no idea what exactly.
"Connection Slow" isn't an event in ASP.NET Core SignalR.
The heartbeats are not directly related to each other, so the gaps between client and server pings are normal.
I am doing some tests with Grpc and I realize that there is this two types of channels, but I don't know the difference.
But when I am working with certificates, with Grpc.Net.Client.GrpcChannel I can set the certificates but I get an error that the DNS is not solved. If I use Grpc.Core.Channel, I can call to the service, but I get an error because of the certificates, with the error - HTTP/2 over TLS was not negotiated on an HTTP/2-only endpoint.
Which is the difference between the 2 channels?
Thanks.
Grpc.Core.Channel is based on C Core libraries that forms the base codebase for all the language variants it supports (C++,C#, PHP, Objective-C, Python, Ruby etc)
Grpc.Net.Client.GrpcChannel is built for.NET Core using the familiar HttpClient object which supports Http/2 now.
The Homepage of grpc-dotnet states that:
GRPC for .NET does not replace gRPC for C# (gRPC C# API over native
C-core binaries). These implementations coexist and share many of the
same APIs to avoid lock-in. There are currently no plans for one
implementation to replace the other one. gRPC for C# is the
recommended solution for frameworks that gRPC for .NET does not
support, such as .NET Framework.
When you inspect the code for Grpc.Net.Client.GrpcChannel, you can see an internal Httpclient object being used to make async calls and cancel pending requests.
The code for Grpc.Core.Channel seems to delegate its calls to the natively generated grpc code. This is about as far as I got in the limited time I could spend on it.
Interestingly, in the ssl validation part of the Net.Client.GrpcChannel, it actually states that it uses HttpClient in the exception messaging.
if (!string.IsNullOrEmpty(rootCertificates) ||
keyCertificatePair != null ||
verifyPeerCallback != null)
{
throw new InvalidOperationException(
$"{nameof(SslCredentials)} with non-null arguments is not supported by {nameof(GrpcChannel)}. " +
$"{nameof(GrpcChannel)} uses HttpClient to make gRPC calls and HttpClient automatically loads root certificates from the operating system certificate store. " +
$"Client certificates should be configured on HttpClient. See https://aka.ms/AA6we64 for details.");
}
I am trying to make a call from my WPF project to an API that I created in ASP.NET Core. When the call is made to the Web API end point, it is returning an error: Unable to connect to the remote server with the inner error being SocketException: No connection could be made because the target machine actively refused it.
This is all being done on the localhost.
The code that is creating this call is(the second line throws the exception):
string RequestUri = "api/Class/GetEverythingDue";
HttpResponseMessage response = await myClient.GetAsync(RequestUri);
With myClient being an HttpClient that I configured like so:
this.myClient = new HttpClient();
myClient.BaseAddress = new Uri("http://localhost:56030/");
myClient.DefaultRequestHeaders.Accept.Clear();
myClient.DefaultRequestHeaders.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json"));
I realize there are several questions with similar problems, I just haven't seen any yet that have to do with .NET Core. I just wanted to make sure that the problem wasn't due to using WPF and .NET Core together and it was due to them being incompatible.
Any help is appreciated.
Thanks
The exception usually means either the firewall is stopping you or the remote server is not listening to the port. An error with your code wouldn't throw such an exception unless you closed the connection and then tried to access it.
I have created 2 ServiceStack applications that run as Windows services via TopShelf and make use of one RabbitMQ server. Unfortunately when I start the second application the following exception occurs:
Exception in Rabbit MQ Server: The AMQP operation was interrupted: AMQP close-reason, initiated by Peer, code=406, text="PRECONDITION_FAILED - cannot redeclare exchange 'mx.servicestack.topic' in vhost '/' with different type, durable, internal or autodelete value"
The startup code contains the following code:
App 1
...
var rabbitMqServer = new RabbitMqServer();
rabbitMqServer.RegisterHandler<BusMessages.CrawlRequest>(
n =>
{
var request = n.GetBody();
this.Crawl(request);
return null;
});
rabbitMqServer.Start();
...
App 2
...
var rabbitMqServer = new RabbitMqServer();
rabbitMqServer.RegisterHandler<SendMailRequest>(
message =>
{
SendMail(message.GetBody());
return null;
});
rabbitMqServer.Start();
...
The problem seems to be with the exchange named mx.servicestack.topic, which is defaulted by ServiceStack. Does anyone know a solution to circumvent this or change the Exchange name so I can use multiple (rather default) ServiceStack applications in combination with the same RabbitMQ server?
Update
As I was looking into it more deeply it seemed to be a bug in ServiceStack.RabbitMq v4.0.31 (used in App 1). In that version the default exchange mx.servicestack.topic is added as a fanout exchange type instead of a topic exchange type. App 2 was using ServiceStack.RabbitMq v4.0.40 which tries to add/use the exchange mx.servicestack.topic as a topic exchange type, as it should be. Upgrading the ServiceStack packages to version 4.0.40 for App 1 fixed this issue.
I prefer the way of segregation for different applications like Alain explains in his answer https://stackoverflow.com/a/31209330/1278669.
However, for different applications working in the same (small) customers' domain it's very doable to use the default exchanges like ServiceStack creates.
Last but not least, I found a dirty workaround to get App 2 running next to App 1 without upgrading the ServiceStack packages of App 1. That's done by doing the following:
...
QueueNames.ExchangeTopic = "mx.App2.topic";
var rabbitMqServer = new RabbitMqServer();
...
You need multiple vhosts in the RabbitMQ server to segregate your ServiceStack applications.
Instead of amqp://localhost:5672 you can use amqp://localhost:5672/vhostname when configuring your RabbitMqServer as described here:
https://github.com/ServiceStack/ServiceStack/wiki/Rabbit-MQ
In a practical deployment the RabbitMQ server wouldn't be on localhost. I'm using that above as a short step from where you currently are using the built-in default which is amqp://localhost:5672 when invoking new RabbitMqServer().
Virtual hosts need to be added on the RabbitMQ server ahead of time and users need to be created for them separately. They are effectively separate AMQP servers with shared infrastructure.
You can add vhosts with rabbitmqctl as follows
rabbitmqctl add-vhost vhostname