I have an ASP.NET Core 2.1 REST API app. I want to be able to run it as a windows service as well as on IIS so I have created a startup project for each and they use a shared Core library for the "guts" of the app. I am using CORS which I think may be coming into play.
The Service project runs normally under dotnet.exe from visual studio and the IIS project runs using IIS Express from visual studio. Their startup code differs very little and is at the end of this post.
Like most rest API's, I want to rely on Http status codes to convey information. On one endpoint, I want to use the RequestSizeLimitAttribute to return a 413 status if the request exceeds a certain limit.
The windows service project behaves correctly, returning the correct status codes to the client. But when I run as IIS Express, the client first sends an OPTIONS request and gets a 204 back (as expected), but then the response after that is always a 502-Bad Gateway.
Does IIS mess with things if an exception happens because of the RequestSizeLimitAttribute?
//IIS Express Project Startup Code
public static void Main(string[] args)
{
BuildWebHost(args).Run();
}
public static IConfiguration config => new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddJsonFile("appsettings.Development.json", optional: true)
.Build();
public static IWebHost BuildWebHost(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseConfiguration(config)
.UseStartup<Startup>()
.Build();
AND
//Windows Service startup code
public static IConfiguration config => new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddJsonFile("appsettings.Development.json", optional: true)
.Build();
public static void Main(string[] args)
{
RunWebHost(args, logger);
}
public static void RunWebHost(string[] args)
{
var isService = true;
if (Debugger.IsAttached || args.Contains("--console"))
{
isService = false;
}
var pathToContentRoot = Directory.GetCurrentDirectory();
if (isService)
{
var pathToExe = Process.GetCurrentProcess().MainModule.FileName;
pathToContentRoot = Path.GetDirectoryName(pathToExe);
}
var webHostArgs = args.Where(arg => arg != "--console").ToArray();
var host = WebHost.CreateDefaultBuilder(webHostArgs)
.UseContentRoot(pathToContentRoot)
.UseConfiguration(config)
.UseStartup<Startup>()
.Build();
if (isService)
{
host.RunAsService();
}
else
{
host.Run();
}
}
Related
I have written custom ConfigurationProvider so that the application ( .net Core 6 ) is able to read configuration from the database. This works as expected when running from Visual Studio or running the output .exe file from command line, however when I try to run it as a Windows Service the config from database is not added. See code below with the appropriate comments
private static IHost CreateHost(string[] args)
{
return Host.CreateDefaultBuilder(args)
.ConfigureHostConfiguration(configBuilder =>
{
var builder = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appSettings.json", optional: true, reloadOnChange: true);
IConfigurationRoot configuration = builder.Build();
var dbConfig = configuration.GetSection("DatabaseConfig");
// Get connection string from appSettings.json and add SqlConfigurationProvider
configBuilder.AddSqlDatabase(config =>
{
config.ConnectionString = dbConfig["ConnectionString"];
config.RefreshInterval = TimeSpan.FromMinutes(1);
});
})
.ConfigureServices((host, services) =>
{
var myConfigSection = host.Configuration.GetSection("MyConfigSection");
var myConfigValue = myConfigSection["MyConfig"];
// When running from VS or CMD myConfigValue is properly taken from the database
// When running from Windows Service myConfigValue will be null suggesting SqlConfigurationProvider has not been added
if (string.IsNullOrEmpty(myConfigValue))
throw new ArgumentException("Config is missing");
services.AddHostedService<WorkerProcess>();
})
.UseWindowsService(options =>
{
options.ServiceName = "MyService";
})
.Build();
}
I suppose that when we call UseWindowsService the host configuration is ignored and that's the reason but that's just the assumption.
What is the proper way of registering Custom Configuration Provider when running the app as Windows Service ?
Edit
I have tried using ConfigureAppConfiguration instead of ConfigureHostConfiguration but the result is unfortunately the same
I am working on .NET Core 5 Console App and I was trying load appsettings.[environment].json at runtime based on the "ASPNETCORE_ENVIRONMENT = Development" which I setup in debug under project properties.
In the BuildConfig method I can see that
Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT")
is returning the "Development" text correctly and also loading the 2 file (appsettings.json, appsettings.development.json).
but when I pass the config to different class via constructor and then inspecting config in that class I seeing the (appsettings.json, appsettings.production.json) file not the development file why?
I don't have appsettings.production.json file in my project yet.
static void Main(string[] args)
{
try
{
//setting for SeriLog
var builder = new ConfigurationBuilder();
BuildConfig(builder);
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Debug()
.ReadFrom.Configuration(builder.Build())
.Enrich.FromLogContext()
.WriteTo.Console()
.WriteTo.File(#"Log\Log.txt")
.CreateLogger();
Log.Logger.Information("Application Starting");
var host = Host.CreateDefaultBuilder()
.ConfigureServices((context, services) =>
{
services.AddTransient<IRIUProcess, RIUProcess>();
})
.UseSerilog()
.Build();
var svc = ActivatorUtilities.CreateInstance<RIUProcess>(host.Services);
svc.Run(args);
}
catch (Exception ex)
{
Log.Logger.Error(ex.ToString());
}
}
static void BuildConfig(IConfigurationBuilder builder)
{
builder.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddJsonFile($"appsettings.{Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT")}", optional: true, reloadOnChange: true)
.AddEnvironmentVariables();
}
Configuration builder return self instance configurated like entityframework core
//setting for SeriLog
var builder = new ConfigurationBuilder();
BuildConfig(builder);
should be
//setting for SeriLog
var builder = new ConfigurationBuilder();
builder = BuildConfig(builder);
Without that, your configuration builder remain unchanged.
Adapt also the BuildConfig method for that.
Then you can remove the CreateDefaultBuilder (with use default AspNetCore Configuration) and directly use your instance of configuration :
new HostBuilder().ConfigureHostConfiguration(a => a.AddConfiguration(builder.Build()))
https://learn.microsoft.com/en-us/dotnet/api/microsoft.extensions.hosting.hostbuilder.configurehostconfiguration?view=dotnet-plat-ext-5.0
You will have then your host with the same configuration for SeriLog and your host
I am having problems with running my dotnet core 2.0 API (on Ubuntu 16.04 using .NET Core CLI) using the correct Environment settings.
I added into the "~/.bash_profile" the following:
ASPNETCORE_ENVIRONMENT=staging
When i run the project (using dotnet run) it seems to work correctly, It detects that it should be on staging.
However, when i compile using "dotnet publish"
and try to run it using:
nohup dotnet API.dll --urls "http://*:1337" &> /var/log/dotnetlogs
it does not seem to detect the environment variable and always defaults to using the "Production" environment.
Startup.cs:
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)
.AddEnvironmentVariables();
Configuration = builder.Build();
}
Program.cs
public static void Main(string[] args)
{
var config = new ConfigurationBuilder()
.AddCommandLine(args)
.Build();
var host = new WebHostBuilder()
.UseKestrel()
.UseContentRoot(Directory.GetCurrentDirectory())
.UseConfiguration(config)
.UseStartup<Startup>()
.Build();
host.Run();
}
public static IWebHost BuildWebHost(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>()
.Build();
Any idea how i can get this working?
Are you missing the export? The docs also say that Linux is case-sensitive, so you'll probably want to use Staging.
export ASPNETCORE_ENVIRONMENT=Staging
The scenario:
I have a .NET Core 2.0 Web API app configure to use only secure connections. I'm using Postman to test the requests.
If I try to POST, PUT or DELETE using non-secure URL (HTTP), it returns status 403 (as it should be). However, it accepts GET requests via HTTP.
I'm really an amateur regarding SSL usage, so I don't know if it should be the common behavior (although, In my head, it doesn't make any sense).
The SSL configuration in Web API is done as the following:
certificate.json:
{
"certificateSettings": {
"fileName": "filename.pfx",
"password": "password"
}
}
Program.cs:
public static IWebHost BuildWebHost(string[] args)
{
var config = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddEnvironmentVariables()
.AddJsonFile("certificate.json", optional: true, reloadOnChange: true)
.AddJsonFile($"certificate.{Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT")}.json", optional: true, reloadOnChange: true)
.Build();
var certificateSettings = config.GetSection("certificateSettings");
string certificateFileName = certificateSettings.GetValue<string>("filename");
string certificatePassword = certificateSettings.GetValue<string>("password");
var certificate = new X509Certificate2(certificateFileName, certificatePassword);
return WebHost.CreateDefaultBuilder(args)
.UseKestrel(
options =>
{
options.AddServerHeader = false;
options.Listen(IPAddress.Loopback, 44312, listenOptions =>
{
listenOptions.UseHttps(certificate);
});
}
)
.UseConfiguration(config)
.UseStartup<Startup>()
.Build();
}
Startup.cs:
public void ConfigureServices(IServiceCollection services)
{
services.Configure<MvcOptions>(options =>
{
options.Filters.Add(new RequireHttpsAttribute());
});
services.AddAntiforgery(
options =>
{
options.Cookie.Name = "_af";
options.Cookie.HttpOnly = true;
options.Cookie.SecurePolicy = CookieSecurePolicy.Always;
options.HeaderName = "X-XSRF-TOKEN";
}
);
The app is hosted on IIS 10. It's a sub-Application, inside a Website, and this Website has the certificate I'm using bound in port 443. There's a copy of this certificate in the root of the application (although I don't know if this is needed).
All HTTPS requests work like a charm.
I believe it's something silly, I just couldn't figure it out. My web searches came to nothing.
Thanks in advance.
When I'm developing locally using VS Code, I'm going to use port 3000 because I'm a hipster. The non-hipsters want it on port 8080 on the server. That's cool, we got this. Microsoft docs give me the following example:
public static void Main(string[] args)
{
var config = new ConfigurationBuilder()
.AddJsonFile("hosting.json", optional: true)
.AddCommandLine(args)
.Build();
var host = new WebHostBuilder()
.UseConfiguration(config)
.UseKestrel()
.Configure(app =>
{
app.Run(async (context) => await context.Response.WriteAsync("Hi!"));
})
.Build();
host.Run();
}
I don't want to use hosting.json. Why would I want that? I have this appsettings.{environment}.json file for exactly this situation. Sweet, I'll just paste that bad boy in
public static void Main(string[] args)
{
var config = new ConfigurationBuilder()
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
.AddCommandLine(args)
.Build();
What the compiler error? env does not exist in the current context. It only exists in the Startup.cs file--which is not called on startup, but called from the startup file, Program.cs, with sorcery.
So, how do I solve this problem? How can I store my environment-specific hosting settings in my environment-specific appsettings.json, and subsequently use it in while building my environment-specific web host via the WebHostBuilder in Program.cs?
This is possible. Expanding on the answer given here, by creating the WebHostBuilder and ConfigurationBuilder in the Program.cs, it is possible to have access to the host environment and then configure the host URL and port in environment-specific appsettings files.
Assuming an appsettings.json and an apppsettings.Development.json file each with the following:
"hostUrl": "http://*:<port number here>"
Modify the Main with the following:
public static void Main(string[] args)
{
var host = new WebHostBuilder();
var env = host.GetSetting("environment");
var builder = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile($"appsettings.{env}.json", optional: true)
.AddEnvironmentVariables();
var configuration = builder.Build();
host.UseKestrel()
.UseUrls(configuration["hostUrl"])
.UseContentRoot(Directory.GetCurrentDirectory())
.UseStartup<Startup>()
.Build()
.Run();
}
Using this code, the Startup.cs will still need to declare its own ConfigurationBuilder in order to publicly expose its Configuration property.
Or, starting from .NET Core 2.0, when creating the default IWebHostBuilder that will build the IWebHost implementation, you can use
return WebHost.CreateDefaultBuilder(args)
.UseConfiguration(configuration)
.ConfigureAppConfiguration((builderContext, config) =>
{
// nb: here you may clear configuration(s) loaded so far
var env = builderContext.HostingEnvironment;
config.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: false, reloadOnChange: true);
})
.UseStartup<Startup>();