.Net framework Owin Test Server does not work with WebSocket - asp.net

My project is Owin self-hosted, it provides Web API endpoints and web socket endpoints.
Here is the relevant config code in the project's startup class
Owin WebSocket is used here
using Owin;
using Owin.WebSocket.Extensions;
public void Configuration(IAppBuilder app)
{
HttpConfiguration config = new HttpConfiguration();
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "api",
routeTemplate: "api/{version}/{controller}"
);
config.EnsureInitialized();
app.MapWebSocketRoute<WebSocket>("/api/v1/socket/test");
app.UseWebApi(config);
}
Works smoothly, when the app is launched I can consume the web api via "http://{host}/api/v1/test" and use the websockets by: "ws://{host}/api/v1/socket/test"
Then I decided to add some integration tests. I use Owin Test Server here. In TestServer.Create the config is identical:
HttpConfiguration config = new HttpConfiguration();
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "api",
routeTemplate: "api/{version}/{controller}"
);
config.EnsureInitialized();
app.MapWebSocketRoute<WebSocket>("/api/v1/socket/test");
app.UseWebApi(config);
Test method for api
var url = new UriBuilder()
{
Scheme = "http",
Path = "/api/v1/test"
}.Uri;;
var result = client.GetAsync(url).Result;
Works nicely. But does not work for web socket:
var wsUri = new UriBuilder()
{
Scheme = "ws",
Path = "/api/v1/socket/test"
}.Uri;
//create websocket client, connect it and to server
var wsc = new ClientWebSocket();
Task.Run(async () =>
{
await wsc.ConnectAsync(wsUri, CancellationToken.None);
var a = wsc.State; // Here error thrown: No connection could be made because the target machine actively refused it 127.0.0.1:80
}).GetAwaiter().GetResult();
Why No connection could be made here? It seems like the testing server can only support regular http request not websocket. But this is not the case in the main app where the identical setting and framework is used. What am I missing here? I have been fiddling with this for hours to no avail...

Found the answer. The TestServer.Create<Startup>() only starts just the in-memory instance where the url is not available. The web socket client however relies on the url to work. So the solution is to user the overload WebApp.Start<Startup>(Settings.WebApiUrl) (it starts a web app on the url you provide)
WebApp.Start(new StartOptions("http://localhost:8989"), startup =>
{
startup.MapWebSocketRoute<TestConnection>();
startup.MapWebSocketRoute<TestConnection>("/ws", sResolver);
startup.MapWebSocketPattern<TestConnection>("/captures/(?<capture1>.+)/(?<capture2>.+)", sResolver);
});
Ref

Related

Sentry SDK with Asp.Net 4.7.2 Not working

I have an Asp.Net web Api 2, using .net 4.7.2 version. And Sentry documents saying for .Net 4.7.2 version I can use Sentry nuget rather than ravenClient nuget. But when I installed Sentry nuget and used Sentry SDK, nothing working, no events are being sent to Sentry.
Below is my WebApi config file where I tried this Sentry.
public static void Register(HttpConfiguration config)
{
using (SentrySdk.Init(o =>
{
o.Dsn = myDsn;
// When configuring for the first time, to see what the SDK is doing:
o.Debug = true;
// Set traces_sample_rate to 1.0 to capture 100% of transactions for performance monitoring.
// We recommend adjusting this value in production.
o.TracesSampleRate = 1.0;
}))
{
config.Routes.MapHttpRoute(
name: "DefaultApi",
//routeTemplate: "api/{controller}/{id}",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { action = RouteParameter.Optional, id = RouteParameter.Optional }
);
string date = DateTime.Now.ToString("yyyy-MM-dd hh:mm:ss");
SentrySdk.CaptureMessage("Something went wrong " + date);
}
}
Can anyone help me here?
Thanks
You have the Sentry initialization code within using block which is a common pattern for something like a CLI (so it flushes events before exiting).
With a long running process that's managed outside of your control like ASP.NET (not Core) you can call Init and then call Dispose or Close on the hook that's triggered before the process exits.
For example:
https://github.com/getsentry/examples/blob/81fb90b957ce9a8c1f6222d1603d0fa499ecba7e/dotnet/AspNetMvc5Ef6/Global.asax.cs#L21-L51

Can you run a asp.net core 3.0 gRPC CLIENT in IIS? (possibly on Azure?)

I've read a lot of conflicting information about this and it seems people are not 100% clear on what is possible and what is not. I am certain that you cannot host a gRPC server app in IIS due to the HTTP/2 limitations. The documentation is pretty clear. However, I want to use IIS as a reverse proxy, with the internal side communicating using gRPC. So the client would be in IIS, not the server. I assumed that since the communication at this point (i.e. the back end) was not funneled through IIS, there would be no issue with this. However, I keep seeing mixed answers.
I have created a dumb webapp that is hosted in IIS Express and can successfully post to my service running on Kestrel with gRPC.
Client code sample below. The SubmitButton is just a form post on the razor page.
public async void OnPostSubmitButton()
{
// The port number(5001) must match the port of the gRPC server.
using var channel = GrpcChannel.ForAddress("https://localhost:5001");
var client = new Greeter.GreeterClient(channel);
var reply = await client.SayHelloAsync(
new HelloRequest { Name = "GreeterClient" });
Console.WriteLine("Greeting: " + reply.Message);
Console.WriteLine("Press any key to exit...");
Console.ReadKey();
}
Server code is the boilerplate template for gRPC but looks like this:
namespace grpcGreeter
{
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
// Additional configuration is required to successfully run gRPC on macOS.
// For instructions on how to configure Kestrel and gRPC clients on macOS, visit https://go.microsoft.com/fwlink/?linkid=2099682
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}
}
namespace grpcGreeter
{
public class GreeterService : Greeter.GreeterBase
{
private readonly ILogger<GreeterService> _logger;
public GreeterService(ILogger<GreeterService> logger)
{
_logger = logger;
}
public override Task<HelloReply> SayHello(HelloRequest request, ServerCallContext context)
{
return Task.FromResult(new HelloReply
{
Message = "Hello " + request.Name
});
}
}
}
This works. But, because I keep seeing mixed information saying it that it won't, I am not certain that once I go to deploy the client code (i.e. the reverse proxy), if I will run into problems. I would like to use a host like Azure...but don't know if it's possible or not.
Any clarity on the subject would be greatly appreciated.
As far as I know, we could use asp.net core mvc or razor page application as the client to call the grpc server.
But gRPC client requires the service to have a trusted certificate when you hosted the application on remote server IIS.
If you don't have the permission to install the certificate, you should uses HttpClientHandler.ServerCertificateCustomValidationCallback to allow calls without a trusted certificate.
Notice: this will make the call not security.
Additional configuration is required to call insecure gRPC services with the .NET Core client. The gRPC client must set the System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport switch to true and use http in the server address.
Code as below:
AppContext.SetSwitch(
"System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport", true);
var httpClientHandler = new HttpClientHandler();
// Return `true` to allow certificates that are untrusted/invalid
httpClientHandler.ServerCertificateCustomValidationCallback =
HttpClientHandler.DangerousAcceptAnyServerCertificateValidator;
var httpClient = new HttpClient(httpClientHandler);
var channel = GrpcChannel.ForAddress("https://localhost:5001",
new GrpcChannelOptions { HttpClient = httpClient });
var client = new Greeter.GreeterClient(channel);
var response = await client.SayHelloAsync(new HelloRequest { Name = "World" });

Access-Control-Allow-Origin error when using Owin

I'm have a aurelia client and a webserver. When i use localhost and i'm running on the same machine it works fine.
But when i want to access the server from another machine the page loads but the api calls give the following error:
No Access-Control-Allow-Origin header is present on the requested resource.
I'm using owin and to my undestanding i need to enable CORS for owin.
I did the follwing in my startup class:-
UPDATE
I have updated my class with input from Nenad but is still get the same error.
Below i have added the call from the client.
public void Configuration(IAppBuilder app)
{
this.container = new Container();
// Create the container as usual.
container.Options.DefaultScopedLifestyle = new AsyncScopedLifestyle();
// Register your types, for instance using the scoped lifestyle:
container.Register<IWebDeps, WebDeps>(Lifestyle.Singleton);
// This is an extension method from the integration package.
container.RegisterWebApiControllers(GlobalConfiguration.Configuration, Assembly.GetExecutingAssembly());
container.Verify();
app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);
// Configure Web API for self-host.
var config = new HttpConfiguration()
{
DependencyResolver =
new SimpleInjectorWebApiDependencyResolver(container)
};
var cors = new EnableCorsAttribute("*", "*", "*");
config.EnableCors(cors);
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
//// Custom Middleare
app.Use(typeof(CustomMiddleware));
app.UseWebApi(config);
//New code:
app.Run(context =>
{
context.Response.ContentType = "text/plain";
return context.Response.WriteAsync("Hello, world.");
});
}
My main program is calling the startUp class:-
using (Microsoft.Owin.Hosting.WebApp.Start<Startup>("http://localhost:8080"))
{
Console.WriteLine("Press [enter] to quit...");
Console.ReadLine();
}
Client code, 192.168.178.23 is the ip from the server.
let baseUrl2 = "http://192.168.178.23:8080/api/status/getStatus";
getStatus() {
return this.client.get(baseUrl2)
.then(response => {
return this.parseJSONToObject(response.content);
});
}
The error in Chrome:
XMLHttpRequest cannot load
http://192.168.178.23:8080/api/status/getStatus. No
'Access-Control-Allow-Origin' header is present on the requested
resource. Origin 'http://localhost:9000' is therefore not allowed
access. The response had HTTP status code 400.
Cors should be enabled now right? But i still get the error when doing a api call. Am i missing any steps? Our is this approah wrong?
Any suggestions are welcome!
You have to configure WebAPI to work with CORS.
Install Nuget package:
Install-Package Microsoft.AspNet.WebApi.Cors
Enable CORS on HttpConfiguration object:
config.EnableCors();
Add [EnableCors] attribute on your controller:
using System.Net.Http;
using System.Web.Http;
using System.Web.Http.Cors;
namespace WebService.Controllers
{
[EnableCors(origins: "www.example.com", headers: "*", methods: "*")]
public class TestController : ApiController
{
// Controller methods not shown...
}
}
or register it globally via HttpConfig:
var cors = new EnableCorsAttribute("www.example.com", "*", "*");
config.EnableCors(cors);
More details at: Enabling Cross-Origin Requests in ASP.NET Web API 2
Add this line and check,
app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);
and remove,
var cors = new EnableCorsAttribute("*", "*", "*");
config.EnableCors(cors);
this worked for me.
Turns out that when starting OWIN the adres should be http://*:8080. Instead of local host.

Can you connect to a hub that is located on a different host / server?

Let's say I have a website on www.website.com. My SaaS with signalr is hosted on www.signalr.com.
Can I connect to www.signalr.com signalr server from www.website.com ?
Instead of :
var connection = $.hubConnection();
var contosoChatHubProxy = connection.createHubProxy('contosoChatHub');
Something like :
var connection = $.hubConnection();
var contosoChatHubProxy = connection.createHubProxy('www.signalr.com/contosoChatHub');
Short answer: Yes - As the SinalR documentation exemplifies.
The first step is enabling cross domain on your server. Now, you can either enable calls from all domains, or only from specified ones. (See this SO post on this matter)
public void Configuration(IAppBuilder app)
{
var policy = new CorsPolicy()
{
AllowAnyHeader = true,
AllowAnyMethod = true,
SupportsCredentials = true
};
policy.Origins.Add("domain"); //be sure to include the port:
//example: "http://localhost:8081"
app.UseCors(new CorsOptions
{
PolicyProvider = new CorsPolicyProvider
{
PolicyResolver = context => Task.FromResult(policy)
}
});
app.MapSignalR();
}
The next step is configuring the client to connect to a specific domain.
Using the generated proxy(see the documentation for more information), you would connect to a hub named TestHub in the following way:
var hub = $.connection.testHub;
//here you define the client methods (at least one of them)
$.connection.hub.start();
Now, the only thing you have to do is specify the URL where SignalR is configured on the server. (basically the server).
By default, if you don't specify it, it is assumed that it is the same domain as the client.
`var hub = $.connection.testHub;
//here you specify the domain:
$.connection.hub.url = "http://yourdomain/signalr" - with the default routing
//if you routed SignalR in other way, you enter the route you defined.
//here you define the client methods (at least one of them)
$.connection.hub.start();`
And that should be it. Hope this helps. Best of luck!

SignalR Negotiate 404

I am using SignalR 2.0. Everything works fine when running locally on my VS 2012. But when I publish the site on IIS, it breaks. The site loads but one of the scripts returns 404 Not Found. The script is something like.
https://example.com/signalr/negotiate?xxx
This path doesn't exist indeed. The correct path should be:
https://example.com/private/signalr/negotiate?xxx
Note the part in bold.
Inside the WebSite (https://example.com/) I have another Application (https://example.com/private/). This one is using SignalR.
This seems like a bug in SignalR since the signalr/hubs path is accessible from my private site.
I had a similar problem.
Here is the documentation for configuring the /signalr URL.
However, my solution differed from the docs.
Instead of changing the standard app.MapSignalR(), I changed my client code to use /MyApp/signalr. Here is the code where "MyApp" is the virtual directory of my web application.
var connection = $.hubConnection('/MyApp/signalr', {useDefaultPath: false});
var changesHub = connection.createHubProxy('changesHub');
changesHub.on('userCountChanged', function (count) {
$('#user-count').text(count);
});
connection.start().done(function () {
console.log('Hub has started');
changesHub.invoke('subscribeToChanges', user.id);
});
I tried the other way around (change the MapSignalR to the /signalr path) but this did not work and the negotiation was still routed to /MyApp/signalr/negotiate.
I had the same problem, with an application running in the IIS Default Web Site.
All the Microsoft examples show the hub url with a starting \, and I had copied those examples. But this meant that the signalr routing was from the Default Web Site rather than the application. Removing the leading \ solved it.
So I used endpoints in Startup.cs like:
endpoints.MapHub<MyHub>("myHub");
and hub connections in Javascript like:
var connection = new signalR.HubConnectionBuilder().withUrl("myHub").build();
I had the same issue when web site with signalr is not running as root site. Below solution worked for me. instead of using /signalr, use ../signalr. it will work with any site name folder. no hardcoded name 'MyApp'
var connection = $.hubConnection('../signalr', {useDefaultPath: false});
Had the same issue. web sites running as virtual directories of the root site. For some reason prefixing with ../ as in ../signalr didn't work, but ./signalr did.
My sample code:
function initSR() {
// logs signalr messages
$.connection.hub.logging = true;
// Declare a proxy to reference the hub.
var chat = $.connection.myHub;
$.connection.hub.url = "./signalr";
$.connection.hub.start();
// Create a function that the hub can call to broadcast messages.
chat.client.broadcastMessage = function (message) {
// Process Message, take action upon receipt
alert(message);
};
}
I had the same problem, it is all about CORS, you should add Host URL in CORS config in Startup.cs like this:
services.AddCors(option =>
{
option.AddPolicy("AutomationCors", builder =>
{
builder.AllowAnyMethod()
.AllowAnyHeader()
.WithOrigins("YOUR LOCALHOST URL",
"YOUR HOST URL")
.AllowCredentials();
});
});
I faced the same problem. The mistake i was doing that i was calling the wrong endpoint url like i was mapping the Signal Url in Configure service like /notification but calling [API-Host]/api/notification. Removing the api from url and calling [API-Host]/notification fixed for me.
Probably you added MapSignalR() in your Application (https://example.com/private/).
If you want it on the root, then do the configuration on your WebSite (https://example.com/)
#styfle point me in the right direction the problem can be resolve in a more flexible way injecting BASE_URL (at least in angular 4)
import { Injectable, Inject } from '#angular/core';
import { HubConnection } from '#microsoft/signalr';
import * as signalR from '#microsoft/signalr';
import { Subject, Observable } from 'rxjs';
#Injectable()
export class SignalRService {
private hubConnection: HubConnection;
private message: Subject<any>;
constructor(#Inject('BASE_URL') private baseUrl: string) {
}
public connect() {
this.message = new Subject<any>();
this.hubConnection = new signalR.HubConnectionBuilder()
.withUrl(this.baseUrl+"notification-hub")
.withAutomaticReconnect()
.build();
// ...
}
}

Resources