Asp.net Core API with CORS on Service Fabric 100% CPU bottleneck - asp.net-core

I have an ASP.net Core on .Net Framework 4.5.2 hosted on Service Fabric as STATELESS Service.
The API is a vanilla API, empty
[Route("Test")]
public class TestController : Controller
{
[HttpGet]
public IActionResult Get()
{
return Ok("Done");
}
}
This is my Startup Code
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", true)
.AddEnvironmentVariables();
Configuration = builder.Build();
}
public IConfigurationRoot Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddCors(options =>
{
options.AddPolicy("CorsPolicy",
builder => builder.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader()
.AllowCredentials());
});
services.AddResponseCompression();
services.AddMvc().AddJsonOptions(opts =>
{
// Force Camel Case to JSON
opts.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
});
}
// 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)
{
app.UseCors("CorsPolicy");
app.UseResponseCompression();
app.UseMvc();
}
}
This is the OpenAsync method:
Task<string> ICommunicationListener.OpenAsync(CancellationToken cancellationToken)
{
var endpoint = FabricRuntime.GetActivationContext().GetEndpoint(_endpointName);
string serverUrl = $"{endpoint.Protocol}://{FabricRuntime.GetNodeContext().IPAddressOrFQDN}:{endpoint.Port}";
//.UseWebListener()
_webHost = new WebHostBuilder().UseKestrel()
.UseContentRoot(Directory.GetCurrentDirectory())
.UseStartup<Startup>()
.UseUrls(serverUrl)
.Build();
_webHost.Start();
return Task.FromResult(serverUrl);
}
Everything plain and simple, no customizations.
The CORS call works, everything is perfect.
I did a test using Visual Studio Team Services with 15K users load, and it all worked like a charm, with 14K RPS. I think the Load test from VS do not use the CORS middleware by the way.
Now the problem is that when I put this exactly empty API in production, receiving calls from around just 100 simultaneous users, the CPU jump to 100% in 3 minutes. The calls are answered until the CPU reach 100% and then start sending errors back.
Seems that with 15000 users and NO CORS it all WORKS, and with 100 users + CORS DOES NOT WORK, CPU goes to 100% and remain like that until I reboot the VM Scale set.
If I stop sending calls, the CPU of the 5 nodes remains stable at 99% without receiving any single call.
How is this possible?
I tried everything, the project is plain and simple, the VS load test works, it's only when I put this on real CORS calls from different sites and different IP addresses that this happens.
I did a performance Trace in the server before sending traffic to it, again with a load test from Visual studio using EXACTLY the CORS headers everything is blazing fast.
With Real world calls this is what I see in the profiler:
There is nothing in it but the CORS middleware and the usual Kestrel processes.
The Stateless service eat up 99% of the CPU, and keep it even if I STOP the Traffic.
This is another 30 seconds trace with no traffic coming but CPU at 90%
I don't know what else to do, there is something wrong with CORS, I'm sure of it, even if it works, somehow something goes wrong.
This is a CORS call, correctly served.
Is there a bug in the Asp.net Core CORS middleware?
UPDATE:
I tried many combinations to isolate the problem:
New cluster, same Asp.net Core vanilla service=> Problem still there
Same cluster new project same Asp.net Core vanilla service=> Problem still there
Same Cluster WebAPI OWIN service, same code => Problem VANISHED!
The problem occurs with Asp.Net Core on Service Fabric using CORS with more than 50 concurrent requests.
This is the CPU (0.85%) with a Asp.Net Stateless service using Visual Studio template, OWIN and CORS, with around 100 concurrent connections and the same empty web API above
At this point I need help from a Microsoft official source to address the problem.
I'm almost sure this is a Asp.net Core CORS bug, that happens when you host it on Service Fabric as a stateless service and send it some minimum traffic (not just a couple of refresh in the browser).

Related

URL Rewrite exceptions for Blazor WebAssembly Hosted deployment

During development, i have used Swagger on the server side of my Blazor WebAssembly App. Always launching (debug) using kestrel instead of IIS Express.
Routing worked as expected, all my component routed properly and if i manually typed /swagger, i got to the swagger page. All good.
We have deployed under IIS on our pre-prod servers, the Server side and Blazor WebAssembly App (client) work as expected and are usable, however, my /swagger url gets rewritten (I assume) to go somewhere in my App instead of letting it go to Swagger, obviously there isn't any component that answers to /swagger.
My only guess is that, when hosted on IIS, the aspnet core app takes care of telling IIS what to rewrite and how (similar to the configs that could be provided thru a web.config for a "Standalone" deployment.)
I can't find how to specify exceptions, I've been following the doc at
https://learn.microsoft.com/en-us/aspnet/core/host-and-deploy/blazor/webassembly?view=aspnetcore-3.1#iis
Any idea how i could add an exception for /swagger ?
EDIT:
Turns out it works without issues in Chrome, only Firefox has the unwanted behavior. If i clear my cache, or use Incognito mode, the issue does not happen in Firefox. So, it seems that Firefox caches some stuff and tries to send my URL input to the Blazor Wasm instead of going thru to the server. I will debug some more with the dev tools and fiddler open to try and figure it out, will report back.
Turns out there this is part of the service-worker.js file that is published. It is different in dev than what gets published (which makes sense).
During my debugging i was able to reproduce the issue on all browsers (Edge, Chrome and Firefox), regardless of being in Incognito/Private mode or not.
Once the service-worker is running, it handles serving requests from cache/index.html of the Blazor WebAssembly app.
If you go into your Blazor WebAssembly Client "wwwroot" folder, you'll find a service-worker.js and a service-worker.published.js. In the service-worker.published.js, you will find a function that looks like this :
async function onFetch(event) {
let cachedResponse = null;
if (event.request.method === 'GET') {
// For all navigation requests, try to serve index.html from cache
// If you need some URLs to be server-rendered, edit the following check to exclude those URLs
const shouldServeIndexHtml = event.request.mode === 'navigate'
&& !event.request.url.includes('/connect/')
&& !event.request.url.includes('/Identity/');
const request = shouldServeIndexHtml ? 'index.html' : event.request;
const cache = await caches.open(cacheName);
cachedResponse = await cache.match(request);
}
return cachedResponse || fetch(event.request);
}
Simply following the instructions found in the code comments is gonna fix the issue. So we ended up adding an exclusion for "/swagger" like so :
&& !event.request.url.includes('/swagger')
Hopefully this post is useful for people who are gonna want to serve things outside of the service worker, not only Swagger.
Do you have UseSwagger first in your Startup.Configure method?
public static void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseSwagger();
app.UseSwaggerUI(c =>
c.SwaggerEndpoint("/swagger/v1/swagger.json", "YourAppName V1")
);
In Startup.ConfigureServices I have the Swagger code last.
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddSwaggerGen(c =>
c.SwaggerDoc(
name: "v1",
info: new OpenApiInfo
{
Title = "YourAppName",
Version = "V1",
}));
}
This is working just fine for us.
Note: You must navigate to https://yourdomain/swagger/index.html

ASP.NET Core 2.2 Authentication not working after deploying the app - Nginx with mutliple apps

I'm trying deploy my application to our internal server with Nginx. It's an ASP.NET Core 2.2 Razor Pages site. I was asked to include some authentication for logging purposes. Eventually everything was working fine on my computer. I used this site to add cookie based authentication:
https://www.mikesdotnetting.com/article/335/simple-authentication-in-razor-pages-without-a-database
I did some modifications to handle a few more users within the OnPost() method. Though I don't think that would be the problem.
It might be important to mention this is not the only .net core app running on the server. The setup is similar to this:
app1: our.domain.com
app2: our.domain.com/app2 (this is the one I have problems with)
everything works properly except the login. When I try to log in, if the password and username is correct it gets redirected to the proper page, however it seems like there is no identity or it cannot find it afterwards.
On my first attempt, I found the following error in the kestrel service log:
fail: Microsoft.AspNetCore.Antiforgery.Internal.DefaultAntiforgery[7]
I could make that disappear with using services.AddDataProtection(), however the problem is still the same, I get redirected - or get the error message if the login attempt is incorrect - but still can't access the authorised folder and e.g. the HttpContext.User.Claims.FirstOrDefault(x => x.Type == ClaimTypes.GivenName)?.Value returns null or empty.
I did a quick test and added the same login page and an authorised folder, and the other dependencies to the other app. And there it works. I didn't even included services.AddDataProtection() in the startup.cs. The login works perfectly. Though it's using .net core 2.1.
So it might have to do something with the rooting? Or I don't know. I'm totally lost. I'm not a full time developer, more like a hobbyist and I'm completely stuck at this moment. Maybe I messed up something within the startup.cs? Or I should add something else? Or is it something with the cookies handling? I did make a lot of searching, no luck so far.
Here is the relevant part of my startup.cs:
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.AddEntityFrameworkNpgsql()
.AddDbContext<Models.UserAccessDbContext>(options =>
options.UseNpgsql(Configuration.GetConnectionString("appConnection")))
.BuildServiceProvider();
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;
});
// https://hanselman.com/blog/DealingWithApplicationBaseURLsAndRazorLinkGenerationWhileHostingASPNETWebAppsBehindReverseProxies.aspx
services.Configure<ForwardedHeadersOptions>(options =>
{
options.ForwardedHeaders = ForwardedHeaders.All;
options.AllowedHosts = Configuration.GetValue<string>("AllowedHosts")?.Split(';').ToList<string>();
});
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme).AddCookie(cookieOptions =>
{
cookieOptions.LoginPath = "/";
});
services.AddMvc().AddRazorPagesOptions(options =>
{
options.Conventions.AuthorizeFolder("/admin");
}).SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
services.AddDataProtection()
.SetApplicationName("app")
.PersistKeysToFileSystem(new DirectoryInfo(#"/var/dpkeys/"));
}
// 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");
// 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.Use((context, next) =>
{
context.Request.PathBase = new PathString("/app");
return next.Invoke();
});
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseCookiePolicy();
app.UseAuthentication();
app.UseMvc();
}
Update:
One small detail. The cookie is created and I can see it within the Chrome's inspector. But the site/app doesn't see me as an authenticated user.
Seems I had problems with the cookie settings. One more difference between the two pages, that one if it uses font-awesome, which means I have some external cookies on my site. As a wild guess, first I set
options.MinimumSameSitePolicy = SameSiteMode.Lax;
since the other cookies had that mode.
And just like that it started to work on the hosting server as well! Now the app recognises the cookie created after the log in.

Add Session storage to ASP.NET Core 3.0

We are currently migrating an existing ASP.NET Core 2.2 web application to 3.0 So far we've got most things working, except session storage.
We had this fully working in v2.2 as we used it to hold the current logged in user's details. Now that we've upgraded to v3.0 it no longer works.
Here's the middleware code.
public void ConfigureServices(IServiceCollection services)
{
// configure Razor pages, MVC, authentication here
services.AddDistributedMemoryCache();
services.AddSession(options =>
{
//prevent session storage from being accessed from client script
//i.e. only server side code (added security)
options.Cookie.HttpOnly = true;
});
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseSession();
}
N.B. I've removed the rest of the middleware code for clarity.
I've tried moving the app.SetSession() line to the top of the method in case the order of execution was the problem but this has made no difference.
When I hover over the HttpContent.Session property in the debugger I get the following error:
HttpContext.Session threw an exception of type System.InvalidOperationException
How do I enable Session storage in ASP.NET Core 3.0?
I've just tried adding the app.UseSession() to the top of the method and it's working now. It definitely didn't work before but it's working now and that's the main thing.

.NET Core Controller API call to Azure SQL Database works in localhost but not in deployed Azure Web App

Summary:
I have a .NET Core project that uses the React web app template for the front end. This app uses Entity Framework Core to connect to an Azure SQL Database. I used the Db-Scaffold command to generate my models (just one table at the moment), and created a controller to return this table. Locally, this works fine and the table (JSON) is returned at localhost/api/Users. However when I deploy the website to Azure (CD pipeline is VS 2017 - > GitHub -> DockerHub -> Azure Web App), navigating to mysite.azurewebsites.net/api/Users just renders the login page (React) of my app.
Attempts:
I have tried:
Adding a connection string as a shared value in Azure (named DefaultConnection)
Adding all the outbound IP's of the Azure Web App to the Azure SQL Whitelist
Running the following in the consoles of the web app
fetch('api/users')
This just returns:
Failed to load resource: the server responded with a status of 500 (Internal Server Error)
I have also tried changing database values and refreshing the local version to make sure it was not just a cached page and sure enough the changes were reflected locally.
I also set ASPNETCORE_ENVIRONMENT in the Web App settings in Azure to Production. Although when I go to the error message, page (through the console) I get this:
<h1 class="text-danger">Error.</h1>
<h2 class="text-danger">An error occurred while processing your request.</h2>
<p>
<strong>Request ID:</strong> <code>0HLK3RLI8HD9Q:00000001</code>
</p>
<h3>Development Mode</h3>
<p>
Swapping to the <strong>Development</strong> environment displays detailed information about the error that occurred.
</p>
<p>
<strong>The Development environment shouldn't be enabled for deployed applications.</strong>
It can result in displaying sensitive information from exceptions to end users.
For local debugging, enable the <strong>Development</strong> environment by setting the <strong>ASPNETCORE_ENVIRONMENT</strong> environment variable to <strong>Development</strong>
and restarting the app.
</p>
Code
UsersController.cs
[Route("api/[controller]")]
public class UsersController : Controller
{
private readonly AccrubalanceDbContext _context;
public UsersController(AccrubalanceDbContext context)
{
_context = context;
}
// GET: api/values
[HttpGet]
public async Task<IEnumerable<Users>> Get()
{
return await _context.Users.ToListAsync();
}
appsettings.json
{
"ConnectionStrings": {
"DefaultConnection":<MyConnectionStringGoesHere>
},
index.js (just in case React might be the routing problem)
const baseUrl = document.getElementsByTagName('base')
[0].getAttribute('href');
const rootElement = document.getElementById('root');
ReactDOM.render(
<BrowserRouter basename={baseUrl}>
<App />
</BrowserRouter>,
rootElement);
registerServiceWorker();
Startup.cs (could be potentially problem with HTTP routing in Prod?)
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.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
// In production, the React files will be served from this directory
services.AddSpaStaticFiles(configuration =>
{
configuration.RootPath = "ClientApp/build";
});
services.AddDbContext<AccrubalanceDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
}
// 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");
// 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();
app.UseSpaStaticFiles();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller}/{action=Index}/{id?}");
});
app.UseSpa(spa =>
{
spa.Options.SourcePath = "ClientApp";
if (env.IsDevelopment())
{
spa.UseReactDevelopmentServer(npmScript: "start");
}
});
}
Conclusion
In conclusion, I need this API call to work within the hosted Azure Web App like it does on my local machine. I know I am close since I got it to work locally, but I am missing something along the way to Azure. Any help or pointers you can provide would be great :)
I am still new to SO and took my time to do my best to format this correctly. I am open to constructive formatting critiques and suggestions to help me improve.
Edit:
As I mentioned before, I am using docker for CD/CI. So I ran my docker container locally and the api does not work there either. Docker throws this warning in the command window when I navigate to the apps home page.
warn: Microsoft.AspNetCore.HttpsPolicy.HttpsRedirectionMiddleware[3]
Failed to determine the https port for redirect.
Edit 1 Determination
I also found this article which points to react routing being an issue. I have looked in Kudo in my Azure app and I do not have a web.config. Could potentially try adding on but I do not have the regular Windows UI since my app is a Linux server.
The container build acts like the Azure App does, may not be an Azure issue. Still unsure why docker is acting differently than running in VS.
Solution:
There is obviously some problem with Docker. Since it was becoming more of a headache then a help, I removed it from the deployment pipeline and just followed the instructions here. Once I did this deployment method, all the API's worked. Only downside is I had to make a new app in Azure.

AspNet Core Identity - cookie not getting set in production

I have a .NET Core 2 web app and I want to use ASP.NET Identity to authenticate my users. On .NET Core 1.x, my code was working fine.
I migrated to .NET Core 2, and authentication works when running locally in Visual Studio. But when I deploy to a live environment, authentication stops working: the authentication cookie isn't being set in production.
My Startup.cs code looks like this:
public void ConfigureServices(IServiceCollection services)
{
services.AddIdentity<AppUser, RavenDB.IdentityRole>()
.AddDefaultTokenProviders();
...
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
...
app.UseAuthentication();
}
To sign in, my code looks like this:
public async Task<ActionResult> SignIn(...)
{
var user = ...; // Load the User from the database.
await this.signInManager.SignInAsync(user, isPersistent: true);
...
}
This code works locally: the ASP.NET Identity auth cookie is set. However, when I deploy this to production enviro in Azure, the cookie never gets set.
What am I missing?
I solved the problem. It boiled down to HTTPS: it appears that signInManager.SignInAsync(...) sets a cookie that is HTTPS-only. I was publishing to a non-HTTPS site initially for testing.
Once I published to an HTTPS site, the cookie started working again.
The reason it was working locally was that I was running in HTTPS locally.
Had same problem with Chrome 60+. Cookie did not want to set on HTTP site or even HTTPS and Cordova.
options.Cookie.SameSite = SameSiteMode.None;
https://github.com/aspnet/Docs/blob/master/aspnetcore/security/authentication/cookie.md
Changing from default value (Lax) to None fixed it for me.
I had similar issue. In the startup.cs file, I had to change
app.UseCookiePolicy(new CookiePolicyOptions
{
Secure = CookieSecurePolicy.Always
});
to
app.UseCookiePolicy(new CookiePolicyOptions
{
Secure = CookieSecurePolicy.SameAsRequest
});
This allowed cookie authentication to work on both http and https.