CORS Error Connecting to AspNet.SignalR Hub from Angular8 - signalr

I am attempting to create a service in Angular8 to connect to my SignalR Hub running in the Visual Studio debugger. My Angular8 is hosted on a different domain name, so I added the CorsOptions.AllowAll code to the SignalR startup.cs as suggested in several posts on StackOverflow.
using Microsoft.AspNet.SignalR;
using Microsoft.Owin;
using Microsoft.Owin.Cors;
using Owin;
[assembly: OwinStartup(typeof(SignalRChat.Startup))]
namespace SignalRChat
{
public class Startup
{
public void Configuration(IAppBuilder app)
{
app.Map("/signalr", map =>
{
map.UseCors(CorsOptions.AllowAll);
var hubConfiguration = new HubConfiguration { };
map.RunSignalR(hubConfiguration);
});
}
}
}
Now when my Angular8 service attempts to connect to the server, the Preflight throws a CORS Error.
Here is the snippet from my Service to communicate with SignalR. Note that in this snippet I import 'signalr', but I get the same error when I try to use '#aspnet/signalr' or '#microsoft/signalr'.
import { Component, Injectable, OnInit } from '#angular/core';
import * as signalR from 'signalr';
import { Subject } from 'rxjs';
import { ChatMessage } from './chat.model';
#Injectable()
export class ChatService implements OnInit {
private connection: signalR.HubConnection;
connectionEstablished = new Subject<Boolean>();
receiverSubscription = new Subject<String>();
constructor() { }
ngOnInit(): void {
}
connect() {
if (!this.connection) {
this.connection = new signalR.HubConnectionBuilder()
.withUrl('https://localhost:44323/signalr')
.configureLogging(signalR.LogLevel.Debug)
.build();
this.connection.start().then(() => {
console.log('Hub connection started');
this.connectionEstablished.next(true);
}).catch(err => console.log(err));
this.connection.on('SendChat', (chatMessage) => {
console.log('Received', chatMessage);
this.receiverSubscription.next(chatMessage);
});
}
Oddly, I do NOT get this error when I test using the jQuery client hosted on the different domain name. The one difference that jumps out at me is that jQuery adds some params to the negotiate url that Angular does not:
Can anyone help me with what I am missing, or how I can debug this issue?

Related

Launch ASP.NET Core API from windows service

I have a small API created from a ASP.NET core Api template in visual studio 22 that targets .NET6.
For testing\debuging I'm launching the API using console application and everything works.
But for production I need this API to be started from a windows service and I have no idea how to make it.
I could simply put the .exe in same folder as the service and call it, but far as I now if no user logged in the console app won't start.
So the idea is to pack the API together with all services and start it when windows service starts.
I've made a small test by creating a static class name "Test.cs" with a method named "Start" and copy all the code from "Program.cs" and call the "Start" method from a test application.
The API starts and stays listening to the endpoints but for some reason doesn't map the controllers from project controllers folder.
all OK when starting API from default
controllers not mapped when start API from static method
program.cs
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
//to avoid json serialize camel casing
builder.Services.AddControllers().AddJsonOptions(opts =>
{
opts.JsonSerializerOptions.PropertyNamingPolicy = null;
opts.JsonSerializerOptions.NumberHandling = System.Text.Json.Serialization.JsonNumberHandling.AllowNamedFloatingPointLiterals; //para aceitar NAN, infinitos etc no json
});
//////////
//Nedeed
builder.Services.AddCors(options =>
{
options.AddPolicy("CorsPolicy", builder => builder.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader());
});
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseCors("CorsPolicy");
//app.UseAuthorization();
app.MapControllers();
app.Run();
test.cs
namespace ProjectX.Api
{
public static class Test
{
public static void Start()
{
var builder = WebApplication.CreateBuilder();
// Add services to the container.
builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
//to avoid json serialize camel casing
builder.Services.AddControllers().AddJsonOptions(opts =>
{
opts.JsonSerializerOptions.PropertyNamingPolicy = null;
opts.JsonSerializerOptions.NumberHandling = System.Text.Json.Serialization.JsonNumberHandling.AllowNamedFloatingPointLiterals; //para aceitar NAN, infinitos etc no json
});
//////////
//Nedeed
builder.Services.AddCors(options =>
{
options.AddPolicy("CorsPolicy", builder => builder.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader());
});
var app = builder.Build();
app.UseSwagger();
app.UseSwaggerUI();
app.UseHttpsRedirection();
app.UseCors("CorsPolicy");
//app.UseAuthorization();
app.MapControllers();
app.Run();
}
}
}
tester app
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace ProjectX.Tester
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
try
{
ProjectX.Api.Test.Start();
}
catch (Exception ex )
{
}
}
}
}
You can run the exe file of Web Api directly in Start():
public static void Start()
{
ProcessStartInfo startInfo = new ProcessStartInfo();
startInfo.CreateNoWindow = false;
startInfo.UseShellExecute = false;
startInfo.FileName = "Your Path\\ProjectName.exe";
startInfo.WindowStyle = ProcessWindowStyle.Normal;
startInfo.Arguments = "";
try
{
using (Process exeProcess = Process.Start(startInfo))
{
exeProcess.WaitForExit();
}
}
catch
{
//Log error.
}
}
Then you can successfully call the API in the project:
If you want to apply Swagger, please add the following code in the production environment in Program.cs:
app.UseSwagger();
app.UseSwaggerUI();
Result:
Hope this can help you.

Angular + Web API Error Only In Production

About the App
I have an Angular 8 App that uses .Net REST APIs that I inherited from a previous employee (I am new to both frameworks). It has been under development for a few months and has been successfully published to the Production server for testing several times throughout development.
The Issue
After the last publish to the Production server, I am receiving two errors in the console stating Unexpected token < in JSON at position 0 for the API call api/MtUsers/GetLoggedInUser which is called on the backend of the home component. I did not update any code in the home component or the MTUsersController since the last time changes were published to production.
Observations
Error only appears in production
Error still exists if I checkout an older (previously working) commit and publish
Visual Studio started complaining about experimental decorators and missing modules on publish (fixed by restarting VS)
Calling the API using postman appears to return index.html in production but returns the MtUser object in localhost
What I've Tried
Clean solution and re-publish
Checkout last known working commit and publish
Recycle application pool and restart website in IIS
Try various code changes related to website configuration
Relevant Code
I'm not too sure what is most "relevant" to this issue, so I am providing the code specified in the error and the startup.cs file. Let me know if something else would be more useful.
home.component.ts
import { Component } from '#angular/core';
import { MtUser } from 'src/app/core/models/mtUser.model'
import { MtUserService } from 'src/app/core/services/mtUser.service';
#Component({
selector: 'app-home',
templateUrl: './home.component.html',
})
export class HomeComponent {
loadingLoggedInUserInfo = true;
loggedInUser: MtUser = <MtUser>{};
/** home ctor */
constructor(
private mtUserService: MtUserService){
document.getElementsByClassName('main-content')[0].scroll(0, 0);
this.mtUserService.GetLoggedInUser()
.subscribe(response => {
this.loadingLoggedInUserInfo = false;
this.loggedInUser = response;
});
}
}
MtUserService
import { Injectable } from '#angular/core';
import { MtUser } from 'src/app/core/models/mtUser.model';
import { environment } from 'src/environments/environment';
import { HttpClient } from '#angular/common/http';
#Injectable({ providedIn: 'root', })
export class MtUserService {
constructor(private http: HttpClient) { }
GetLoggedInUser() {
return this.http.get<MtUser>(environment.apiUrl + '/MtUsers/GetLoggedInUser');
}
}
MtUsersController
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using MT8.Data;
using MT8.Models;
using MT8.Utilities;
namespace MT8.Controllers
{
[Authorize]
[Route("api/[controller]")]
[ApiController]
public class MtUsersController : Mt8ControllerBase
{
private readonly Mt8Context _context;
public MtUsersController(Mt8Context context)
{
_context = context;
}
// GET: api/MtUsers/GetLoggedInUser
[HttpGet("GetLoggedInUser")]
public async Task<ActionResult<MtUser>> GetLoggedInUser()
{
var loggedInUserName = ApplicationEnvironment.GetLoggedInUserName(this.HttpContext);
var loggedInUser = await this._context.MtUsers
.SingleOrDefaultAsync(u => u.UserName == loggedInUserName);
if (loggedInUser == null)
return NotFound();
return loggedInUser;
}
}
}
Startup.cs
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.AspNetCore.SpaServices.AngularCli;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.EntityFrameworkCore;
using MT8.Data;
using Microsoft.AspNetCore.Server.IISIntegration;
using System.Threading.Tasks;
using MT8.Utilities;
namespace MT8
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
readonly string MyAllowSpecificOrigins = "_myAllowSpecificOrigins";
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
// In production, the Angular files will be served from this directory
services.AddSpaStaticFiles(configuration =>
{
configuration.RootPath = "ClientApp/dist";
});
services.AddCors(options =>
{
options.AddPolicy(MyAllowSpecificOrigins,
builder =>
{
builder.WithOrigins("http://example.com",
"http://www.contoso.com");
});
});
services.AddDbContext<Mt8Context>(options =>
options.UseSqlServer(Configuration.GetConnectionString("Mt8Context")));
services.AddAuthentication(IISDefaults.AuthenticationScheme);
services.AddControllers()
.AddJsonOptions(options =>
options.JsonSerializerOptions.Converters.Add(new StringToIntJsonConverter()))
.AddJsonOptions(options =>
options.JsonSerializerOptions.Converters.Add(new StringToNullableIntConverter()))
.AddJsonOptions(options =>
options.JsonSerializerOptions.Converters.Add(new StringToDecimalJsonConverter()))
.AddJsonOptions(options =>
options.JsonSerializerOptions.Converters.Add(new StringToDoubleJsonConverter()))
.AddJsonOptions(options =>
options.JsonSerializerOptions.Converters.Add(new StringToDateTimeJsonConverter()))
.AddJsonOptions(options =>
options.JsonSerializerOptions.Converters.Add(new StringToNullableDateTimeConverter()));
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, Mt8Context dbContext)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
if (!env.IsDevelopment())
{
app.UseSpaStaticFiles();
}
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller}/{action=Index}/{id?}");
});
app.UseSpa(spa =>
{
// To learn more about options for serving an Angular SPA from ASP.NET Core,
// see https://go.microsoft.com/fwlink/?linkid=864501
spa.Options.SourcePath = "ClientApp";
if (env.IsDevelopment())
{
spa.UseAngularCliServer(npmScript: "start");
}
});
if (!env.IsProduction())
dbContext.InitializeData();
}
}
}
After lots of trial and error, I determined the issue was with a connection string in the appsettings.Production.json file. It was originally set to Integrated Security=True
when the application was first built and something caused this to stop working all of a sudden. I updated the database to use an SQL login and provided the ID and password in the connection string which fixed the issue.

Application Insights in IHostedService console application

I am trying to enable Application Insights in a console application using IHostedService (for the moment, it's a simple console application which we run as WebJob, in future in containers).
As far as my knowledge goes, in the following code, so far we do not have any extension to register globally Application Insights as an implementation of ILogger:
public static class Program
{
public static Task Main(string[] args)
{
var hostBuilder = new HostBuilder()
.ConfigureHostConfiguration(config =>
{
config.SetBasePath(Directory.GetCurrentDirectory());
config.AddJsonFile("appsettings.json", optional: false);
config.AddEnvironmentVariables();
})
.ConfigureLogging((context, logging) =>
{
logging.AddConfiguration(context.Configuration.GetSection("Logging"));
if (context.HostingEnvironment.IsDevelopment())
{
logging.AddConsole();
}
else
{
//TODO: register ApplicationInsights
}
});
return hostBuilder.RunConsoleAsync();
}
}
So far, I found out that potentially, I should be able to set everything up using custom implementation of the logger, i.e. public class ApplicationInsightsLogger : ILogger, and then... register it in the container so that DI resolves it.
Is this the right direction?
I made an extension that I could use from either an IHost or an IWebHost:
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.ApplicationInsights;
public static class LoggingBuilderExtensions
{
public static ILoggingBuilder AddLogging(this ILoggingBuilder loggingBuilder)
{
loggingBuilder.AddFilter<ApplicationInsightsLoggerProvider>("", LogLevel.Trace);
loggingBuilder.AddAzureWebAppDiagnostics();
loggingBuilder.AddApplicationInsights();
return loggingBuilder;
}
}
Since I'm not sending in the context (HostBuilderContext or WebHostBuilderContext), I can use it in either app type like this:
new HostBuilder().ConfigureLogging(loggingBuilder => loggingBuilder.AddLogging())
or
WebHost.CreateDefaultBuilder().ConfigureLogging(loggingBuilder => loggingBuilder.AddLogging())
If you needed a specific property from the context (like environment type), you could extract that and send it in as a parameter to the extension.
Here's a reference: https://github.com/Microsoft/ApplicationInsights-dotnet-logging/blob/develop/src/ILogger/Readme.md

SignalR SSE handshake fails

Hosting a ASP.Net Core 2.1 webapi and an Angular 6 webapp on "Windows Server 2008 R2" under the same domain name but different ports (:81 and :80 respectively); not contained within the same folder.
According to this article, SSE is the (standard) fallback when Websockets are unavailable, which is expected since the server doesn't support Websockets as they were introduced in IIS 8.0 (server is IIS 7.5)
In development everything works perfectly. However, after publishing, the browser console reports the following after 15 seconds (suspiciously the default "HandshakeTimeout" time).
> Information: SSE connected to http://my.website.com:81/notify?id=kaSkcGUjcZD4ylJAC7B70A
> Error: Connection disconnected with error 'Error: Server returned handshake error: Handshake was canceled.'.
> Error: Not Found
The Startup.cs is currently setup as follows:
public void ConfigureServices(IServiceCollection services)
{
services.AddSignalR();
services.AddMemoryCache();
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
services.AddCors(o => {
o.AddPolicy("Limited", builder => builder
.WithOrigins("http://my.website.com") // .AllowAnyOrigin() also tested and worked
.AllowAnyMethod()
.AllowAnyHeader()
.AllowCredentials()
);
});
services.AddDbContext<DatabaseContext>(options => {
options.UseSqlServer(Configuration.GetConnectionString("Database"));
});
}
public void Configure(
IApplicationBuilder app,
IHostingEnvironment env,
IServiceProvider serviceProvider)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseHsts();
}
app.UseCors("Limited");
app.UseHttpsRedirection();
app.UseMvc();
app.UseSignalR(routes => routes.MapHub<NotifyHub>("/notify"));
}
With the SignalR Hub being almost empty (event pushing is handled elsewhere):
public class NotifyHub : Hub
{
public override async Task OnConnectedAsync()
{
await base.OnConnectedAsync();
}
public override async Task OnDisconnectedAsync(Exception exception)
{
await base.OnDisconnectedAsync(exception);
}
}
The Angular service is setup as follows:
import { Injectable } from '#angular/core';
import { HubConnection } from '#aspnet/signalr';
import * as signalR from '#aspnet/signalr';
import { Subject } from 'rxjs';
#Injectable({
providedIn: 'root'
})
export class RefreshService {
private connection: HubConnection;
public timestamp = new Subject<string>();
constructor(private zone: NgZone) {
this.connection = new signalR.HubConnectionBuilder()
.withUrl(http://my.website.com:81/notify)
.build();
this.connection
.start()
.then(() => {
this.connection.on('Message', data => {
// Do Work
});
})
.catch(err => console.error(err.toString()));
}
}
All NuGet packages are up to date (v2.1.1 for everything but Microsoft.NETCore.App which is v2.1.0) and using v1.0.2 (the latest) ASP.NET SignalR NPM package -- so the server- and client-side SignalRs are the same.
Using a static IP address I was able to get the app to connect to a locally published API (but running IIS 10.0).
Is there something I am missing? A lot of the technologies are pretty new, but I don't yet want to assume there's something wrong with them.
In case someone is still facing this issue, I solved it by configuring IIS 8.0 WebSocket Protocol Support on IIS.
Microsoft documentation

MassTransit fault handling configuration

I have a problem with configuring a fault consumer in my app. The problem is that consumed message is passed to an *_error_skipped queue and it doesn't disappear entirely.
Below is a very simple example. The client app receives failed message and it disappear from a test_error queue but it still exists on the test_error_skipped queue.
Service project
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
using GreenPipes;
using MassTransit;
using MassTransit.Util;
namespace MassTransitTest.Service
{
public class RequestModel
{
public DateTime RequestTime { get; set; }
}
class MassTransitService : IDisposable
{
private readonly IBusControl _busControl;
public MassTransitService()
{
_busControl = Bus.Factory.CreateUsingRabbitMq(configure =>
{
var host = configure.Host(new Uri("rabbitmq://localhost/mt_test"), h =>
{
h.Username("guest");
h.Password("guest");
});
configure.ReceiveEndpoint(host, "test", c =>
{
c.UseRetry(r => r.None());
c.Consumer<RequestConsumer>();
});
});
TaskUtil.Await(_busControl.StartAsync());
Console.WriteLine("bus started");
}
public void Dispose()
{
_busControl?.StopAsync().Wait();
}
}
class RequestConsumer : IConsumer<RequestModel>
{
public Task Consume(ConsumeContext<RequestModel> context)
{
Console.WriteLine($"request with message id {context.MessageId} received: {context.Message.RequestTime}");
throw new NotImplementedException();
}
}
}
Client project
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
using GreenPipes;
using MassTransit;
using MassTransit.Util;
using MassTransitTest.Service;
namespace MassTransitTest.Client
{
class MassTransitClient
{
private readonly IBusControl _busControl;
public MassTransitClient()
{
_busControl = Bus.Factory.CreateUsingRabbitMq(configure =>
{
var host = configure.Host(new Uri("rabbitmq://localhost/mt_test"), h =>
{
h.Username("guest");
h.Password("guest");
}); enter code here
configure.ReceiveEndpoint(host, "test_error", c =>
{
c.Consumer<ErrorConsumer>();
});
});
TaskUtil.Await(_busControl.StartAsync());
Console.WriteLine("bus started");
}
public async Task Send()
{
Console.WriteLine("sending request");
await (await _busControl.GetSendEndpoint(new Uri("rabbitmq://localhost/mt_test/test"))).Send(new RequestModel()
{
RequestTime = DateTime.Now
});
Console.WriteLine("request sent");
}
}
class ErrorConsumer : IConsumer<Fault<RequestModel>>
{
public Task Consume(ConsumeContext<Fault<RequestModel>> context)
{
Console.WriteLine($"request with message id {context.Message.FaultedMessageId} failed. requested time: {context.Message.Message.RequestTime}");
return Task.CompletedTask;
}
}
}
I'm using .net core 2.1.1 and MassTransit 5.1.3
To answer your question, which has several parts, first:
MassTransit receive endpoints move the message to the _error queue when an exception is thrown by the consumer. Creating a receive endpoint on the _error queue is not suggested, and should not be done.
If you simply want to observe if a fault occurred on the consumer, you can create a separate receive endpoint (such as fault-queue) and register your Fault<T> consumer. MassTransit will publish a message that implements Fault<T>, which the broker will route to your consumer via the receive endpoint.
However, based on your example above, you're sending a request and expecting the client to know if a fault occurred. For this, I'd recommend using the request client - which sets up the message headers to return faults back to the request originator. It also allows responses to be sent. If you don't want to wait for the response, or wait to see if the fault occurred, the above fault observer is your best option.
You can see how to use the request client in the documentation.

Resources