I have created a React Project using dotnetcore 3.1 Version. This is hosted on an IIS Server on AWS Lightsail. We use AWS Lightsail Loadbalancers. The Web Service communicates to an Microsoft SQL Server Express (64-bit) Database, version 14.0.3281.6 using Entity Framework Core.
The problem we are facing is:
We make a call to the webservice via a POST request. This runs a query on the database. This query fetches data from many related tables using Include()
For large data we have noticed that the web service return a 504 Gateway Timeout.
We have tried setting the CommandTimeout to 900 seconds as below
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
if (!optionsBuilder.IsConfigured)
{
IConfigurationRoot configuration = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json")
.Build();
var connectionString = configuration.GetConnectionString("DefaultConnection");
optionsBuilder.UseSqlServer(connectionString, sqlServerOptions => sqlServerOptions.CommandTimeout(900));
}
}
Our Connection string
"DefaultConnection": "Data Source=server_name_here,port_number_here;Initial Catalog=db_name_here;Integrated Security=False;Persist Security Info=False;User ID=user_name_here;Password=password_here
Other things we have tried:
Setting the requestTimeout="00:20:00" in the web.config
Application Pool settings screenshot
Are we missing something?
I suspect that this has something to do with the AWS loadbalancer configuration. The 504 Gateway timeout probably comes from the loadbalancer because it expects your application to answer within a certain amount of time (which it doesn't for large data).
You could try to (temporarily) disable the loadbalancer and see if you still hit the 504 Gateway Timeout error.
You could also add a new controller to test this issue:
[ApiController]
[Route("[controller]")]
public class TimeoutController : ControllerBase
{
[HttpGet]
public IActionResult Get(int timeoutInSeconds)
{
Thread.Sleep(TimeSpan.FromSeconds(timeoutInSeconds));
return Ok($"Waited for {timeoutInSeconds} seconds.");
}
}
Then you can call it with your browser on http://[server:port]/timeout?timeoutInSeconds=10
You can then gradually increase the timeout. This helps you to identify the threshold and to prove that it does not have something to do with your application code (because the controller does not do anything but wait).
Related
I've written my first API, which is a basic validation API. A client (.NET Core Desktop) application logs in with a username and password. I then check valid licences against a database and return licence details. If the (corporate) user has never used the software on this PC before, some shared initial JSON settings are downloaded.
The thing is, there is a different set of JSON settings downloaded depending on the client's site. When I update these settings in the SQL database, this change does not reflect in the returned data. Something, somewhere is caching.
This morning, I attempted a test:
You can see that I have replaced the JSON settings with letters a-f. When I call the API, I still get the full JSON settings field returned, however!
From the Client side, I'm performing the request like this:
public async Task<IEnumerable<ISetting>> GetSettingsAsync(Guid licenceId)
{
SettingsRequest request = new() { LicenceId = licenceId };
IEnumerable<SettingsResponse> settingsResponse = null;
using (HttpClient client = Client)
{
var responseMessage = await client.PostAsJsonAsync<SettingsRequest>("api/Settings", request).ConfigureAwait(false);
...
I understand a little about server-side caching and have attempted do disable this with both:
[Authorize]
[Route("api/[controller]")]
[ApiController]
[ResponseCache(NoStore = true, Location = ResponseCacheLocation.None)]
public class SettingsController : ControllerBase
...
at the start of my controllers and in my ConfigureServices method in the API, have called:
services.AddMvc(o =>
{
o.Filters.Add(new ResponseCacheAttribute { NoStore = true, Location = ResponseCacheLocation.None });
});
But I still end up with the same result.
Another important factor is that if I publish to IISExpress server or Azure, I get the expected results for the first run.
But the second run onwards on IISExpress or Azure App Service after publishing, I receive quite old, cached results.
Something, somewhere is caching my results but I'm at a loss to know what it is. Any help much appreciated.
EDITS based on discussions below:
Response headers in Postman contain no-store,no-cache on both the first and subsequent requests. This applies when my API runs on both Azure and IISExpress.
I am using EF Core with an Azure SQL Database with the repository pattern. I have tried adding "AsNoTracking()" to my database requests to eliminate the possibility of EF doing the caching.
A quick and dirty desktop app put together to query the SQL database directly returns expected data every time.
I am trying to configure my .net core API in order to limit the requests.
To achieve this i modify the program.cs class like
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.ConfigureKestrel(serverOptions =>
{
serverOptions.Limits.MaxConcurrentConnections = 2;
})
//.UseContentRoot(Directory.GetCurrentDirectory())
//.UseIISIntegration()
.UseStartup<Startup>();
});
}
but the problem is when I call my API with a console app using more threads than 2 I get the responses from all threads-calls. The API I have deployed-published it in my local IIS pc.
I consider that I must get only 2 responses and then for the other calls I will get 503 services unavailable.
what is wrong with my code?
EDIT
I have read this article How to configure concurrency in .NET Core Web API?, the problem is when I add the web.config
<configuration>
<system.web>
<applicationPool
maxConcurrentRequestsPerCPU="5000"
maxConcurrentThreadsPerCPU="0"
requestQueueLimit="5000" />
</system.web>
</configuration>
i have the warning
the element system. web has invalid child element applicationpool
and i cannot publish the api on iis or run it in iis express
Since the API will host in the IIS, so, the configuration for the Kestrel will not be used.
To set the max concurrency connections in IIS, you could Open IIS manager window. Select the site from the server node. Then, select Advance setting from the action pane. You can see the Limits under the Behavior section. Set Maximum Concurrent Connection value based on your requirement. Like this:
[Note] This setting is for all Sites.
Besides, you could also check this sample and create a custom middleware to limit the request.
Option serverOptions.Limits.MaxConcurrentConnections limits maximum number of tcp connections to Kestrel, not the number of concurrent http requests. Through each tcp connection there still might be multiple concurrent http requests. That's why in your test you can see no errors, even though there are more than 2 concurrent http requests.
In order to limit the number of concurrent http requests in .NET Core (or .NET
5+), you can use ConcurrencyLimiterMiddleware (package Microsoft.AspNetCore.ConcurrencyLimiter). Here is an example:
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddStackPolicy(options =>
{
options.MaxConcurrentRequests = 2;
options.RequestQueueLimit = 25;
});
}
public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory)
{
app.UseConcurrencyLimiter();
}
}
I have a client app in blazor that connects to a server. When I'm running it on my local machine in dev, the server's hostname is dev.someurl.com
When it's live it's live.someurl.com
I've set up injection of IConfiguration which loads appsettings.Development.json automatically, however I can't change the environment. The docs say that when published it will use appsettings.Production.json however this is not the case as I've published it under Release build and seen that.
The environment appears to be readonly. With a signalr .net core app I can do this:
return Host.CreateDefaultBuilder(args).UseEnvironment(environmentName)
However with a blazor client application you have this:
var builder = WebAssemblyHostBuilder.CreateDefault(args);
The builder lets you access HostEnvironment but you cannot modify it.
The reading of the appsettings config file is all automatic, built in with this code:
builder.Services.AddSingleton(provider =>
{
var config = provider.GetService<IConfiguration>();
return config;
});
Any ideas how I can get around this?
Being a blazor client app you can apparently set the environment in a header, but I can't figure out how to do that programatically. Ideas welcome.
I have a self-hosted Web API application (the server application) that uses Windows authentication.
Windows Auth is enabled in Startup.cs/Configuration by setting the following AuthenticationSchemes on System.Net.HttpListener
System.Net.HttpListener listener = (System.Net.HttpListener)appBuilder.Properties["System.Net.HttpListener"];
listener.AuthenticationSchemes = System.Net.AuthenticationSchemes.IntegratedWindowsAuthentication
| System.Net.AuthenticationSchemes.Anonymous;
And Controllers then use the [Authorize] tag. I can then extract the Principal.Identity from the HttpRequestContext for every controller method to see who’s making the call.
It appears this is only working if the caller and server are on the same host. As soon as the calling application is on another host, all requests are blocked with a 401 unauthorized and no controller method is ever hit on the server. This is even if the calling application is executed under the same user account that the server. So is there a special config required so Windows authentication on web.api works across different machines?
Regards meisterd
In the Startup class of your WebAPI, add a call to use CORS. You may need to add Microsoft.Owin to you packages if you don't already have it. This should allow access to your api from other hosts.
It should look something like this:
public class Startup
{
public void Configuration(IAppBuilder appBuilder)
{
HttpConfiguration config = new HttpConfiguration();
WebApiConfig.Register(config);
//... Your other startup code
appBuilder.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);
I have constructed an API using webapi2.2.
When I deploy the API to Azure I get the Service Unhealthy Message...when I check the logs of my API the log gives the error message
"Boot strapping failed: executing 'WebApiConfig.Register' caused an
exception: 'Parameter count mismatch.'.
The Application Start function is below
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
WebApiConfig.Register(GlobalConfiguration.Configuration);
And my WebApiConfig.cs has the following:
public static void Register(HttpConfiguration config)
{
config.EnableCors();
config.Formatters.JsonFormatter.
SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/html"));.......
Another question here: The api implements its own Security mechanism (I used the following as a reference http://bitoftech.net/2014/12/15/secure-asp-net-web-api-using-api-key-authentication-hmac-authentication/). Would this implementation work in Azure or would you have to make use of the x-zumo header authorisation mechanism?
I found the resolution to this - I believe that the problem is caused by the fact that I have another mobile services app running in my Azure account. That app was built awhile ago - early 2015 and used the register procedure with no parameters
public static void Register(){.....}
I think that this may have confused the service operation (the fact that one app has a register without parameters and the other has a register with one parameter). To resolve the issue with my new app I removed the config parameter, and build the config settings in the register function see below
public static void Register()
{
ConfigOptions options = new ConfigOptions();
HttpConfiguration config = ServiceConfig.Initialize(new ConfigBuilder(options));
config.EnableCors();.....
Remember though you will need access to the using Microsoft.WindowsAzure.Mobile.Service namespace...this can be obtained by installing the nuget package WindowsAzure.MobileServices.Backend
Hope this helps someone who has similar problems