In my ASP.NET API im having an issue for a while where i cannot host my web app locally using VS web server on ports other than the default ones (i.e. http: 5000, https: 5001). VS keeps on waiting for web server to listen on the ports specified in my LaunchProfile.json indefinitely while the app is being hosted on the default ports.
{
"profiles": {
"HHT_API": {
"commandName": "Project",
"launchBrowser": true,
"launchUrl": "swagger",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"applicationUrl": "https://localhost:7291;http://localhost:7290"
}
}
}
What does this have to do with ConfigurationBuilder?
Well, I was having an argument with my colleague regarding how Configuration should be provided in DI. He relies on DI to auto-provide the configuration, while I do it explicitly
through creating an IConfiguration object from ConfigurationBuilder
IConfiguration configuration = new ConfigurationBuilder()
.AddJsonFile("appsettings.json", false, true)
.Build();
builder.Services.AddSingleton(configuration);
after deciding to try it his way, i realized that the issue is gone, i can now host the app using any set of ports unlike before.
What am i missing here?
EDIT 1: to clarify, below is program.cs for both cases:
Case 1: Creating an IConfiguration object with Configuration builder and passing it as a DI service (app is hosted on default ports only)
using HHT_API.Databases.HHTCustom;
using HHT_API.Modules.HHT.Models.ResponseModels;
using HHT_API.Modules.Logger.Models;
using HHT_API.Modules.Middleware.API;
using HHT_API.WrapperManagement;
using Microsoft.AspNetCore.HttpOverrides;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.EntityFrameworkCore;
using System.Text;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllersWithViews()
.AddJsonOptions(options =>
{
options.JsonSerializerOptions.PropertyNameCaseInsensitive = true;
options.JsonSerializerOptions.PropertyNamingPolicy = null;
})
.ConfigureApiBehaviorOptions(options =>
{
//configure a special response for invalid model state
options.InvalidModelStateResponseFactory = context =>{
ModelStateDictionary modelStatePtr = context.ModelState;
modelStatePtr.Remove(modelStatePtr.ElementAt(0).Key); //remove the key of the class model itself
List<string> invalidFields = modelStatePtr.Keys.Select(x => x.Replace("$", "").Replace(".", "")).ToList();
var result = new OkObjectResult(new MainResponseModel<object>() {
MessageCode = (int)StatusCode.ModelValidationError,
MessageText = $"Invalid entry for ({string.Join(", ", invalidFields)}). Please check your input and try again!",
Data = null
});
return result;
};
});
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddHsts(options =>
{
options.MaxAge = TimeSpan.FromSeconds(31536000);
options.IncludeSubDomains = true;
options.Preload = true;
});
builder.Services.AddHttpsRedirection(options =>
{
options.HttpsPort = 443;
options.RedirectStatusCode = 308;
});
builder.Services.AddAntiforgery(options =>{
options.SuppressXFrameOptionsHeader = false;
});
// [1]: creating an IConfiguration object with Configuration builder and passing it as a DI service
IConfiguration configuration = new ConfigurationBuilder()
.AddJsonFile("appsettings.json", false, true)
.Build();
builder.Services.AddSingleton(configuration);
byte[] bConnectionString = Convert.FromBase64String(configuration["HHTCustom"]);
builder.Services.AddDbContext<HHTCustomContext>(options =>
{
options.UseSqlServer(Encoding.UTF8.GetString(bConnectionString));
//options.EnableSensitiveDataLogging();
});
builder.Services.AddScoped<ComponentsWrapperManager>();
builder.Services.AddHttpContextAccessor();
var app = builder.Build();
/// Configure the HTTP request pipeline.
// configure swagger behavior for each envirnonment
if (app.Environment.IsProduction() || app.Environment.IsStaging()) {
app.UseHsts();
//app.UseHttpsRedirection();
app.UseSwagger();
string sVirtualPath = builder.Configuration.GetSection("VirtualPath").Value;
app.UseSwaggerUI(c => c.SwaggerEndpoint(sVirtualPath + "/swagger/v1/swagger.json","HHT v1"));
}
else { //development
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseForwardedHeaders(new ForwardedHeadersOptions {
ForwardedHeaders = ForwardedHeaders.XForwardedFor |
ForwardedHeaders.XForwardedProto
});
app.UseAuthorization();
app.MapControllers();
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
//use custom middleware for logging requests and responses
app.UseMiddleware<APIMiddleware>();
app.Run();
Case 2: Configuration is provided as a default application configuration source by ASP.NET (Works fine with the desired unique ports specified in LaunchProfile.json)
using HHT_API.Databases.HHTCustom;
using HHT_API.Modules.HHT.Models.ResponseModels;
using HHT_API.Modules.Logger.Models;
using HHT_API.Modules.Middleware.API;
using HHT_API.WrapperManagement;
using Microsoft.AspNetCore.HttpOverrides;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.EntityFrameworkCore;
using System.Text;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllersWithViews()
.AddJsonOptions(options =>
{
options.JsonSerializerOptions.PropertyNameCaseInsensitive = true;
options.JsonSerializerOptions.PropertyNamingPolicy = null;
})
.ConfigureApiBehaviorOptions(options =>
{
//configure a special response for invalid model state
options.InvalidModelStateResponseFactory = context =>{
ModelStateDictionary modelStatePtr = context.ModelState;
modelStatePtr.Remove(modelStatePtr.ElementAt(0).Key); //remove the key of the class model itself
List<string> invalidFields = modelStatePtr.Keys.Select(x => x.Replace("$", "").Replace(".", "")).ToList();
var result = new OkObjectResult(new MainResponseModel<object>() {
MessageCode = (int)StatusCode.ModelValidationError,
MessageText = $"Invalid entry for ({string.Join(", ", invalidFields)}). Please check your input and try again!",
Data = null
});
return result;
};
});
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddHsts(options =>
{
options.MaxAge = TimeSpan.FromSeconds(31536000);
options.IncludeSubDomains = true;
options.Preload = true;
});
builder.Services.AddHttpsRedirection(options =>
{
options.HttpsPort = 443;
options.RedirectStatusCode = 308;
});
builder.Services.AddAntiforgery(options =>{
options.SuppressXFrameOptionsHeader = false;
});
// [2] configuration is provided as a default application configuration source by ASP.NET
byte[] bConnectionString = Convert.FromBase64String(builder.Configuration["HHTCustom"]);
builder.Services.AddDbContext<HHTCustomContext>(options =>
{
options.UseSqlServer(Encoding.UTF8.GetString(bConnectionString));
//options.EnableSensitiveDataLogging();
});
builder.Services.AddScoped<ComponentsWrapperManager>();
builder.Services.AddHttpContextAccessor();
var app = builder.Build();
/// Configure the HTTP request pipeline.
// configure swagger behavior for each envirnonment
if (app.Environment.IsProduction() || app.Environment.IsStaging()) {
app.UseHsts();
//app.UseHttpsRedirection();
app.UseSwagger();
string sVirtualPath = builder.Configuration.GetSection("VirtualPath").Value;
app.UseSwaggerUI(c => c.SwaggerEndpoint(sVirtualPath + "/swagger/v1/swagger.json","HHT v1"));
}
else { //development
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseForwardedHeaders(new ForwardedHeadersOptions {
ForwardedHeaders = ForwardedHeaders.XForwardedFor |
ForwardedHeaders.XForwardedProto
});
app.UseAuthorization();
app.MapControllers();
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
//use custom middleware for logging requests and responses
app.UseMiddleware<APIMiddleware>();
app.Run();