Using aspnetcore 3.1 and the Grpc.AspNetCore nuget package, I have managed to get gRPC services running successfully alongside standard asp.net controllers as described in this tutorial.
However I would like to bind the gRPC services to a specific port (e.g. 5001), preferably through configuration instead of code if possible. This is because I would like to limit how my gRPC services are exposed.
The closest I have come has been using RequireHost when mapping the endpoints:
// Startup.cs
public void Configure(IApplicationBuilder app)
{
// ...
app.useEndpoints(endpoints =>
{
endpoints.MapGrpcService<MyService>()
.RequireHost("0.0.0.0:5001");
});
}
This seems to do what I want but I can't find any documentation about it, and it requires configuration in code per service. Perhaps there is a better way?
This works (server side) with Kestrel:
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.ConfigureKestrel(options =>
{
options.Listen(IPAddress.Loopback, 5000);
options.Listen(IPAddress.Loopback, 5005, configure => configure.UseHttps());
});
webBuilder.UseStartup<Startup>();
});
client side:
var httpHandler = new HttpClientHandler
{
ServerCertificateCustomValidationCallback =
HttpClientHandler.DangerousAcceptAnyServerCertificateValidator
};
AppContext.SetSwitch("System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport", true);
using var channel = GrpcChannel.ForAddress("https://localhost:5005", new GrpcChannelOptions { HttpHandler = httpHandler } );
var client = new Greeter.GreeterClient(channel);
Note:
var httpHandler = new HttpClientHandler
{
ServerCertificateCustomValidationCallback =
HttpClientHandler.DangerousAcceptAnyServerCertificateValidator
};
when you have a self-signed certificate without a trust chain (mostly when developing).
AppContext.SetSwitch("System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport", true);
Is for support of http.
You need to configure the middleware
app.UseRouting();
app.MapWhen(context => {
return context.Connection.LocalPort == 1000
}, newApp => {
newApp.UseRouting();
newApp.UseEndpoints(endpoints =>
{
endpoints.MapGrpcService<Service1>();
}
});
app.MapWhen(context => {
return context.Connection.LocalPort == 2000
}, newApp => {
newApp.UseRouting();
newApp.UseEndpoints(endpoints =>
{
endpoints.MapGrpcService<Service2>();
}
});
In the ASP.NET Core 6.0 ports can be changed in the Properties > launchSettings.json file. But this file is considered only if you run the server from the Visual Studio or VS Code.
I was trying to run the server directly using the .exe file for testing. The server was running with the default ports: "http://localhost:5000;https://localhost:5001".
Finally, I changed it from the appsettings.json for the .exe file:
"AllowedHosts": "*",
"Kestrel": {
"Endpoints": {
"Http": {
"Url": "https://localhost:7005",
"Protocols": "Http1AndHttp2"
},
"gRPC": {
"Url": "http://localhost:5005",
"Protocols": "Http2"
}
}
As far as I know, there is no other way to set a specific port for the GRPC service.
The grpc service is also running on the asp.net core kestrel server, the server will listen the port not the service.
If your asp.net core application just has GRPC service, you could just set the kestrel server's listen port to 5001.
If you have multiple service like MVC web api or else, RequireHost is the best workaround to allow only specific port access the grpc service.
If you want to prompt the routing system for GRPC service to require the specified port, you could use below port:
routes.MapGrpcService<MyService>().RequireHost("*:5001");
You can try to use the UseWhen method to use the MapGrpcService endpoints only when the request uses the port you defined.
var grpcPort = 5001;
app.UseWhen(context => context.Connection.LocalPort == grpcPort,
builder =>
{
builder.UseRouting();
builder.UseEndpoints(endpoints =>
{
endpoints.MapGrpcService<MyService>();
});
});
This has the benefit of not repeating .RequireHost("*:5001"); for every single service, although repeating UseRouting twice may induce weird behaviour: for instance, authentication may not be working unless you put in builder.UseAuthentication() after builder.UseRouting().
However, this behaviour be useful if you want to have a distinct request pipeline for REST and gRPC.
Related
I have a C# / ASP.NET Core Web API project running on https://localhost:7001 and a next js app running on http://localhost:3000.
I can run the C# API from swagger and directly in the browser (https://localhost:7001/api/SourceSystems), but when I try to call it from the next js page using GetStaticProps, I get a 500 error.
Next.js code:
export default function Sourcesystem({systems}) {
return (
<ul>
{systems.map((system) => (
<li key={system.systemName}>{system.systemName} </li>
))}
</ul>
)
};
// This function gets called at build time
export async function getStaticProps() {
// Call an external API endpoint to get posts
const res = await fetch(
'https://localhost:7001/api/Schedules',
{
method:'GET',
}
)
const systems = await res.json()
console.log(systems);
return {
props: {
systems
},
}
}
I have added CORS to the c# code (I think) in
program.cs
var MyAllowSpecificOrigins = "_myAllowSpecificOrigins";
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddCors(options =>
{
options.AddPolicy(name: MyAllowSpecificOrigins,
builder =>
{
builder.WithOrigins(
"http://example.com",
"http://www.contoso.com",
"http://localhost:3000"
);
});
});
// services.AddResponseCaching();
builder.Services.AddControllers();
// add dbContext
builder.Services.AddDbContext<GdqcDevContext>(options => options.UseSqlServer("Data Source = RAZERPRO17; Initial Catalog = GDQC_dev; Integrated Security = True; Connect Timeout = 30; Encrypt = False; TrustServerCertificate = False; ApplicationIntent = ReadWrite; MultiSubnetFailover = False"));
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseCors(MyAllowSpecificOrigins);
app.UseAuthorization();
app.MapControllers();
app.Run();
Next is saying the the fetch failed:
I am suspecting something still not set correctly for CORS but I have copied the CORS configuration and the middleware assignment order from documentation and other stackoverflow answers. NOTE: I am running the c# API code using the debug browser rather than directly from IIS. I have read somewhere about the OPTIONS but this is only for the full blown IIS
I have also added a CORS guard annotation to the c# controller with no success
namespace Overwatch_API.Controllers
{
[EnableCors("MyAllowSpecificOrigins")]
[Route("api/[controller]")]
[ApiController]
public class SourceSystemsController : ControllerBase
{
private readonly GdqcDevContext _context;
public SourceSystemsController(GdqcDevContext context)
{
_context = context;
}
// GET: api/SourceSystems
[HttpGet]
public async Task<ActionResult<IEnumerable<SourceSystem>>> GetSourceSystems()
...
UPDATE: It looks like Next is returning the following error message in the logging:
cause: Error: self-signed certificate
at TLSSocket.onConnectSecure (node:_tls_wrap:1538:34)
at TLSSocket.emit (node:events:513:28)
at TLSSocket._finishInit (node:_tls_wrap:952:8)
at ssl.onhandshakedone (node:_tls_wrap:733:12) {
code: 'DEPTH_ZERO_SELF_SIGNED_CERT'
I presume this is related to the SSL cert on the .net core 6 api code, as this is being called with https. How do I get next to accept a self signed cert, or build a propertly signed cert for the dev environment
This issue is common in nextjs.
I found two ways to fixed it.
1. If the issue occurs in dev environment. we can use NODE_TLS_REJECT_UNAUTHORIZED=0 to solve it.
① Overcome the DEPTH_ZERO_SELF_SIGNED_CERT on Node.js
② Self Signed SSL Certificate in NextJS
2. We also can use NODE_EXTRA_CA_CERTS to solve it. It can be used in Dev or Prod Environment.
Node TLS socket : DEPTH_ZERO_SELF_SIGNED_CERT error
I have an ASP.NET Core Web API running in a Linux Docker container orchestrated using Docker compose. When I try to access the Swagger endpoint of the Web API on HTTPS, the browser cannot reach the endpoint, and I see the following error in the Web API logs.
Detected a TLS handshake to an endpoint that does not have TLS enabled
After reading this topic, I "fixed" the issue by adding the following to my configuration. However, I am not sure why this is needed. It seems overkill and has too much complexity to enable HTTPS, so I am wondering if I am missing something.
builder.WebHost.ConfigureKestrel(options =>
{
options.Listen(
address: IPAddress.Any,
port: 443,
configure: listenOptions =>
{
listenOptions.Protocols = HttpProtocols.Http1AndHttp2;
listenOptions.UseHttps(new TlsHandshakeCallbackOptions
{
OnConnection = context =>
{
if (string.Equals(context.ClientHelloInfo.ServerName, "localhost", StringComparison.OrdinalIgnoreCase) ||
string.Equals(context.ClientHelloInfo.ServerName, "host.docker.internal", StringComparison.OrdinalIgnoreCase))
{
// Different TLS requirements for this host
context.AllowDelayedClientCertificateNegotation = true;
return new ValueTask<SslServerAuthenticationOptions>(
new SslServerAuthenticationOptions
{
ServerCertificate = new X509Certificate2("/root/.aspnet/https/webapi.pfx", "password")
});
}
return new ValueTask<SslServerAuthenticationOptions>(
new SslServerAuthenticationOptions
{
ServerCertificate = new X509Certificate2("/root/.aspnet/https/webapi.pfx", "password")
});
}
});
});
});
I need to start a webserver without a specific IP and Port with HTTP 1.1 and HTTP 2.
After start, i need to provide the webservers IP, Port and HTTP version of each url.
var builder = WebApplication.CreateBuilder();
...
builder_.WebHost.UseKestrel(options =>
{
options.ListenAnyIP(0, (listenOptions) =>
{
listenOptions.Protocols = HttpProtocols.Http1;
});
options.ListenAnyIP(0, (listenOptions) =>
{
listenOptions.Protocols = HttpProtocols.Http2;
});
});
...
var app = builder.Build();
// Start application
_ = app.RunAsync();
// Does only provide IP and Port but not HTTP version
var urls = app.Urls;
ASP.NET Core .net6.0
So far i was unable to retrieve the HTTP Version of the webservers endpoints from the "WebApplication" class.
So, I am trying to do a SIMPLE task with SignalR right now. Basically, I have a console app that will have .NET SignalR client, and a .NET 6 Web API with SignalR server.
I am just trying to do a simple flow:
Client sends request to SignalR Hub
Hub processes request, and returns data back to .NET client
I have no user interaction at all, that's why I'm not using JS, as this will not be in the browser.
Here is my current setup for the SignalR Server
OnPremAgentClientHub in Web API:
using Microsoft.AspNetCore.SignalR;
namespace HttpLongPollingServer.Hubs
{
public sealed class OnPremClientHub : Hub
{
public async Task GetOnPremAgentStatus(string clientIp)
{
bool isReachable = OnPremAgentData.IsAgentPingable(clientIp)
await Clients.Caller.SendAsync("OnPremAgentStatusReceived", isReachable);
}
}
}
The relevant code from Program.cs in Web API for SignalR setup:
app.MapHub<OnPremClientHub>("/clienthub", options =>
{
options.Transports = HttpTransportType.LongPolling;
});
Yes, I want to force HTTP Long polling.
Here is my setup in the console app for SignalR client
Program.cs
var hubConnection = new HubConnectionBuilder()
.WithUrl("https://localhost:7184/clienthub")
.WithAutomaticReconnect()
.ConfigureLogging(logging =>
{
logging.AddConsole();
// This will set ALL logging to Debug level
logging.SetMinimumLevel(LogLevel.Debug);
})
.Build();
await hubConnection.StartAsync();
await LongPollingTest.TestHttpSignalRLongPolling(hubConnection);
The TestHttpSignalRLongPolling that calls the SignalR Hub methods:
public static async Task TestHttpSignalRLongPolling(HubConnection hubConnection)
{
await hubConnection.InvokeAsync("GetOnPremAgentStatus", arg1: "192.168.19.128");
hubConnection.On("OnPremAgentStatusReceived", (bool isReachable) => {
if (isReachable)
Console.WriteLine("Agent is reachable");
else
Console.WriteLine("Agent is not reachable");
});
}
Now, I can get the client to invoke the GetOnPremAgentStatus. However, when the client goes to do the hubConnection.On()... I get the following Warning and Error
Microsoft.AspNetCore.SignalR.Client.HubConnection[14]
Failed to find handler for 'OnPremAgentStatusReceived' method.
fail: Microsoft.AspNetCore.SignalR.Client.HubConnection[57]
Failed to bind arguments received in invocation '(null)' of 'OnPremAgentStatusReceived'.
System.IO.InvalidDataException: Invocation provides 1 argument(s) but target expects 0.
at Microsoft.AspNetCore.SignalR.Protocol.JsonHubProtocol.BindTypes(Utf8JsonReader& reader, IReadOnlyList`1 paramTypes)
I'm banging my head against a wall. I feel it's something simple I'm missing, I just don't know what it is.
Any tips or help is appreciated!
Alright, so the answer was staring me straight in the face in the Microsoft docs. The problem was that I was trying to call a callback on an unregistred handler that is located in my Server Hub in my client code. The fix was easy. I simply moved my connection.On to Program.cs after making my Hub Connection, but before starting the connection.
Updated Program.cs looks like this:
using Microsoft.AspNetCore.SignalR.Client;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using HttpLongPollingClientConsole;
ServiceProvider service = new ServiceCollection()
.AddLogging((loggingBuilder) => loggingBuilder
.SetMinimumLevel(LogLevel.Debug)
.AddConsole()
.AddDebug())
.BuildServiceProvider();
var hubConnection = new HubConnectionBuilder()
.WithUrl("https://localhost:7184/clienthub", options =>
{
options.Transports = Microsoft.AspNetCore.Http.Connections.HttpTransportType.LongPolling;
})
.WithAutomaticReconnect()
.ConfigureLogging(logging =>
{
logging.AddConsole();
// This will set ALL logging to Debug level
logging.SetMinimumLevel(LogLevel.Debug);
})
.Build();
// Register the handler here!!
hubConnection.On<bool>("OnPremAgentStatusReceived", (isReachable) => {
if (isReachable)
Console.WriteLine("Agent is reachable");
else
Console.WriteLine("Agent is not reachable");
});
hubConnection.StartAsync().Wait();
await LongPollingTest.TestHttpSignalRLongPolling(hubConnection);
Console.ReadKey();
This is explained in the Microsoft Docs very well.
Now, from my Debug output of the SignalR client I can see the Handler being registered properly.
dbug: Microsoft.AspNetCore.SignalR.Client.HubConnection[40]
Registering handler for client method 'OnPremAgentStatusReceived'.
I have tried every other ways to set Access-Control-Allow-Origin : * in my Blazor Static Web App.
I follow this documentation Configure Azure Static Web Apps first to set globalHeaders. It isn't working.
And I try to add Cors in builder services. It isn't working too.
builder.Services.AddScoped (sp => new HttpClient
{ BaseAddress = new Uri(builder.Configuration["API_Prefix"] ??
builder.HostEnvironment.BaseAddress) });
builder.Services.AddCors(options =>
{ options.AddPolicy(name: policyName,
builder =>
{ builder.WithOrigins("https://localhost:5000") // specifying the allowed origin
.WithMethods("GET") // defining the allowed HTTP method
.AllowAnyHeader(); // allowing any header to be sent
});
});
await builder.Build().RunAsync();
And I tried it also in individual HttpClient request in the following.
// create request object
var request = new HttpRequestMessage(HttpMethod.Get, uri);
// add custom http header
request.Headers.Add("Access-Control-Allow-Origin", "*");
request.Headers.Add("Access-Control-Allow-Methods", "GET");
// send request
var httpResponse = await Http.SendAsync(request);
I had used this tutorial to create [Blazor Static Web App].2
This is the error I got in the browser's console. ].3
What am I missing to set the correct configuration?
Restrict Domain consumption of services by CORS browser restriction. But when you hit the service service will get executed but the response wont captured in browser side. By adding following code in API program.cs will allow specific Domains
builder.Services.AddCors(options =>
{
options.AddDefaultPolicy(policy =>
{
policy.WithOrigins("http://192.168.10.127",
"https://localhost:5000",
"https://localhost:5001")
.AllowAnyHeader()
.AllowAnyMethod();
});
});
app.UseCors();
To allow from any Domain follow below code
app.UseCors(options => options.SetIsOriginAllowed(x => _ = true).AllowAnyMethod().AllowAnyHeader().AllowCredentials());