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.
Related
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.
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?
I'm trying to use SignalR to broadcast a message from the server to the client without the client triggering the message. From tutorials that I've seen, defining a method in the client, like so:
signalRConnection.client.addNewMessage = function(message) {
console.log(message);
};
should allow the following hub code to be used on the server:
public async Task SendMessage(string message)
{
await Clients.All.addNewMessage("Hey from the server!");
}
However, the Clients.All.addNewMessage call causes an error in the C# compiler:
'IClientProxy' does not contain a definition for 'addNewMessage' and no accessible extension method 'addNewMessage' accepting a first argument of type 'IClientProxy' could be found (are you missing a using directive or an assembly reference?)
How do I fix this? The server code is contained within the hub.
This is because you are using ASP.NET Core SignalR but you are calling client method following ASP.NET MVC SignalR. In ASP.NET Core SignalR you have to call the client method as follows:
public async Task SendMessage(string message)
{
await Clients.All.SendAsync("AddNewMessage", message); // here `AddNewMessage` is the method name in the client side.
}
It showing your client side code is also for ASP.NET MVC SignalR. For ASP.NET Core SignalR it should be as follows:
"use strict";
var connection = new signalR.HubConnectionBuilder().withUrl("/chatHub").build();
connection.on("AddNewMessage", function (message) {
// do whatever you want to do with `message`
});
connection.start().catch(function (err) {
return console.error(err.toString());
});
And In the Startup class SignalR setup should be as follows:
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.Configure<CookiePolicyOptions>(options =>
{
// This lambda determines whether user consent for non-essential cookies is needed for a given request.
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
services.AddMvc()
.SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
services.AddSignalR(); // Must add this
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseCookiePolicy();
app.UseSignalR(routes =>
{
routes.MapHub<ChatHub>("/chatHub"); // Here is configuring for `ChatHub`
});
app.UseMvc();
}
}
Please follow Get started with ASP.NET Core SignalR this tutorial if you face further problem.
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
I am building my first .NETCoreApp using 1.0.0-rc2-final. I am trying to insert a copy of Model into TempData so that it is accessible after postback.
I added Microsoft.AspNetCore.Session to my project.
I altered my Startup.cs to look like...
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using System;
namespace GamesCore
{
public class Startup
{
public Startup(IHostingEnvironment env)
{
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true);
if (env.IsDevelopment())
{
// For more details on using the user secret store see http://go.microsoft.com/fwlink/?LinkID=532709
builder.AddUserSecrets();
}
builder.AddEnvironmentVariables();
Configuration = builder.Build();
}
public static IConfigurationRoot Configuration { get; private set; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
// Add framework services.
services.AddMvc();
services.AddSession();
services.AddAuthentication(
SharedOptions => SharedOptions.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme);
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
loggerFactory.AddDebug();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseBrowserLink();
}
else
{
app.UseExceptionHandler("/Home/Error");
}
app.UseStaticFiles();
app.UseCookieAuthentication();
app.UseOpenIdConnectAuthentication(new OpenIdConnectOptions
{
ClientId = Configuration["Authentication:AzureAd:ClientId"],
Authority = Configuration["Authentication:AzureAd:AADInstance"] + Configuration["Authentication:AzureAd:TenantId"],
CallbackPath = Configuration["Authentication:AzureAd:CallbackPath"]
});
app.UseSession(new SessionOptions { IdleTimeout = TimeSpan.FromMinutes(60) });
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
}
}
I have the following in one of my Controllers:
public IActionResult Index(Models.ScoreModel scoreModel)
{
if (string.IsNullOrWhiteSpace(scoreModel.Username))
{
scoreModel.GameID = new System.Guid("b90ae557-7e03-4efa-9da1-1a4e89c1f629");
scoreModel.Username = User.Identity.Name;
scoreModel.Score = 0;
scoreModel.ScoringStep = 1;
TempData.Add("scoreModel", scoreModel);
}
return View(scoreModel);
}
When I have the line with TempData in there, the page loads completely blank -- no error, no Shared Layout, etc. If I remove that line, the View loads fine within the Shared Layout. If I look at the debugger, the scoreModel is getting successfully added to TempData so that doesn't seem to be a problem.
I figured this out.
I moved to storing it in Session instead of TempData, using this page as an example:
http://benjii.me/2015/07/using-sessions-and-httpcontext-in-aspnet5-and-mvc6/
I serialized the Model using the extension class outlined on the above page.
One note, is that since this page was written, services.AddCaching(); has changed to services.AddDistributedMemoryCache();
See this link for another sample: https://github.com/aspnet/Session/blob/dev/samples/SessionSample/Startup.cs