How to configure Kestrel urls in startup class - asp.net-core

I am trying to figure out the proper way to modify the URL(s) that kestrel listens on from the Startup class constructor.
Update: In addition to the answer below, I've come to understand that Startup class isn't used to configure Kestrel in the way I had thought. I had thought that Startup would create a single application-wide configuration object that would be located through convention, it is not. As #Tseng points out, the configuration of the Application and Hosting are separate concerns. The linked answer and accepted answer provide working examples.
I spun up a brand new Ubuntu 14 box in Vagrant and installed ASP.NET Core 1.1 according to the current instructions from Microsoft: https://www.microsoft.com/net/core#linuxubuntu
I ran:
dotnet new -t web
dotnet restore
dotnet run
This listens on http://localhost:5000 by default. In Program.cs I can call app.UseUrls("http://*:5001) and this works to change the URL and port.
What I really want is to change the URLs by convention through adding a new JSON setting file in the Startup class, so I followed examples and created a hosting.JSON file with this (note: I've tried "server.urls" and "urls" as the key).
{
urls: "http://*:5001"
}
In Startup.cs below the default lines for adding the appsettings.json files I added .AddJsonFile("hosting.json", optional: false); (optional: false to make sure it was picking up the file)
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)
.AddJsonFile("hosting.json", optional: false);
if (env.IsDevelopment())
{
// For more details on using the user secret store see https://go.microsoft.com/fwlink/?LinkID=532709
builder.AddUserSecrets();
}
builder.AddEnvironmentVariables();
Configuration = builder.Build();
}
I have verified that the setting is there after the configuration builds, but it is not picked up or used when the host is built in the Program.cs
public class Program
{
public static void Main(string[] args)
{
var host = new WebHostBuilder()
.UseKestrel()
.UseContentRoot(Directory.GetCurrentDirectory())
.UseIISIntegration()
.UseStartup<Startup>()
.Build();
host.Run();
}
}
and the application still starts listening on localhost:5000. It is my (likely incorrect) understanding that by having a setting properly named with the correct key the WebHostBuidler should pick it up and use it.
I've seen other examples that basically get rid of the startup class and create the configuration in Program.cs where it can then be passed into a call to UseConfiguration but again my understanding is that using a Startup class should do this by convention.
Essentially I'd like to keep Startup and Program separate, add a hosting.JSON file to the configuration with URLs, and have it picked up and used without needing to call UseUrls, UseConfiguration, etc.
Am I missing something obvious, or trying to do something that is not actually correct?
To explain why this is not a duplicate as per my comment:
That post specifically says "launcherSettings is for Visual Studio F5". I am using the command line on a Linux box. Nothing to do with VS.
The solution provided in that post moved the configuration build into the main method. I specifically state that I want to build my configuration in the Startup class just like the default "dotnet new -t web" project.
I do not feel this is a duplicate, and am still reviewing the ASP.NET Core source to see if this is possible.
In regards to the correct key:
https://github.com/aspnet/Hosting/blob/b6da89f54cff11474f17486cdc55c2f21f2bbd6b/src/Microsoft.AspNetCore.Hosting.Abstractions/WebHostDefaults.cs
namespace Microsoft.AspNetCore.Hosting
{
public static class WebHostDefaults
{
public static readonly string ApplicationKey = "applicationName";
public static readonly string StartupAssemblyKey = "startupAssembly";
public static readonly string DetailedErrorsKey = "detailedErrors";
public static readonly string EnvironmentKey = "environment";
public static readonly string WebRootKey = "webroot";
public static readonly string CaptureStartupErrorsKey = "captureStartupErrors";
public static readonly string ServerUrlsKey = "urls";
public static readonly string ContentRootKey = "contentRoot";
}
}
https://github.com/aspnet/Hosting/blob/80ae7f056c08b740820ee42a7df9eae34541e49e/src/Microsoft.AspNetCore.Hosting/Internal/WebHost.cs
public class WebHost : IWebHost
{
private static readonly string DeprecatedServerUrlsKey = "server.urls";

In your hosting.json file you should use server.urls instead of urls. And hosting.json file need to added in the program.cs (in the main method) not in startup.
Here is my hosting.json file.
{
"server.urls": "http://localhost:5010;http://localhost:5012"
}
Here is the Main method.
public static void Main(string[] args)
{
var config = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddCommandLine(args)
.AddEnvironmentVariables(prefix: "ASPNETCORE_")
.AddJsonFile("hosting.json", optional: true)
.Build();
var host = new WebHostBuilder()
.UseConfiguration(config)
.UseKestrel()
.UseContentRoot(Directory.GetCurrentDirectory())
.UseIISIntegration()
.UseStartup<Startup>()
.Build();
host.Run();
}
And here is the screenshot.

Related

aspnet core dependency injection with IOptions in startup

What is going on gang?
I am looking for a proper and clean way of injecting configuration into my classes using the IOptions package. Currently, I am registering all the configuration object in one place and everything is just fine. The problem is with some classes that I need to register in the startup method.
The initial method implementation looks like this: (extension method)
public static void RegisterHttpClients(this IServiceCollection services, AppSettings appSettings)
{
services.AddHttpClient<IPdfService, PdfService>(
httpClient => httpClient.DefaultRequestHeaders.Add("PdfApiKey", appSettings.PdfApiKey));
}
This AppSettings object I am also injecting into other classes using IOptions so would like to somehow reuse the object from there instead of having to get same object from json again like that:
var appSettings = Configuration.GetSection("AppSettings").Get<AppSettings>();
services.RegisterHttpClients(appSettings);
In other words - I already have the thing registered with services.Configure<AppSettings>(configuration.GetSection("AppSettings")); so would kind of like to piggy-back on this if possible.
Any thoughts/suggestions for possible directions on how to configure this?
EDIT: (added JSON and AppSettings files)
public class AppSettings
{
public string PdfApiKey {get; set;}
public string AnotherProperty {get; set;}
}
{
"AppSettings": {
"PdfApiKey": "SomeKey",
"AnotherProperty": "HollyTheCow"
}
}
Thanks in advance!
You can accomplish this by registering your options with Configure (which you've done), then using the second overload to AddHttpClient() to get the registered options from the DI container (Service Locator pattern). Note I've used IServiceProvider.GetRequiredService, but you can use IServiceProvider.GetService()?.Value if it's an optional setting.
services.Configure<AppSettings>(_configuration.GetSection("AppSettings"));
services.AddHttpClient<YourService>((serviceProvider, config) => {
var settings = serviceProvider.GetRequiredService<IOptions<AppSettings>>().Value;
config.BaseAddress = new Uri(settings.BaseUri);
});
Your code has no any error and will run successfully
There may be many appSetting.json files on your project.
Check the ASPNETCORE_ENVIRONMENTvariable on your project.
Right click on your project > Properties > Debug > Environment variables
if ASPNETCORE_ENVIRONMENT is set on Development IConfiguration read data from
appSettings.development.json and you should add AppSettigs json in appSettings.development.json
if ASPNETCORE_ENVIRONMENT is set on Production IConfiguration read data from
appSettings.production.json and you should add AppSettigs json in appSettings.production.json
if you want IConfiguration read data from appSetting.json delete both appSettings.development.json and appSettings.production.json file.
in this case IConfiguration by default read and bind data from appSetting.json
Another options
You can config your IConfiguration to load data dynamically from
appSettings.json like this
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();
this.configuration = builder.Build();
}

appsettings.json file not in .net core console project

I understand that .net core has replaced the app.config file with appsetting.json. However this file seems to be added for ASP.net projects only. In fact it is not even available in the add items list.
I found this post that list packages needed to be added:
Microsoft.Extensions.Configuration
Microsoft.Extensions.Configuration.FileExtensions
Microsoft.Extensions.Configuration.Json
I added all these and it does give me the option of adding a json configuration file but still not the App Settings File which is only available under ASP.Net Core.
I am trying to understand why, doesn't a non web project need configuration and what is the recommended way to configure a .net core console application.
Thanks in advance.
Non-web project may or may not need configuration. But, as you noticed, Visual Studio doesn't scaffold console projects with appsettings.json. Obviously, you can add it to the project as json file. Once you have it, the challenge is to make use of it. I frequently use Configuration object and dependency injection in Entity Framework utilities.
For example,
public static class Program
{
private static IConfigurationRoot Configuration { get; set; }
public static void Main()
{
IConfigurationBuilder builder = new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
.AddEnvironmentVariables();
Configuration = builder.Build();
IServiceCollection services = new ServiceCollection();
services.AddDbContext<MyDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddScoped<IMyService, MyService>();
IServiceProvider provider = services.BuildServiceProvider();
IMyService myService = provider.GetService<IMyService>();
myService.SomeMethod();
}
public class TemporaryDbContextFactory : IDesignTimeDbContextFactory<MyDbContext>
{
public MyDbContext CreateDbContext(string[] args)
{
IConfigurationBuilder configBuilder = new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
.AddEnvironmentVariables();
IConfigurationRoot configuration = configBuilder.Build();
DbContextOptionsBuilder<MyDbContext> builder = new DbContextOptionsBuilder<MyDbContext>();
builder.UseSqlServer(configuration.GetConnectionString("DefaultConnection"));
return new MyDbContext(builder.Options);
}
}
}
This allows me to both run migrations and console-based utilities against DbContext. You don't specify what kind of configuration you are going to need - so this is just one example. But hopefully, you can adjust it to your needs.

ASP.NET Core 2: In `Startup()`, how do I specify additional arguments to choose the right configuration?

We are building a new ASP.NET Core app to deploy to a few different environments that require different Configuration options. For example, Azure, AWS, and local for development. Each needs different configuration settings. We already use the common:
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
which handles debug vs staging vs prod, but we have a second dimension to it, so we also want a:
.AddJsonFile($"appsettings.{DeploySite}.json", optional: true)
or something like that. We could pass in the DeploySite on commandline or environment variable or whatever is convenient, but nothing like that seems exposed in the IHostingEnvironment offered in the Startup method.
How is this best accomplished?
In ASP.NET Core 2+ this is done in Program.CreateWebHostBuilder instead of Startup. We do something almost identical in our deployments, requiring an appsettings.<name>.json file for configurations. We do it by passing in a command line parameter in the form of --App:InstanceName <name>. Then set it on launch:
public static void Main(string[] args)
{
IWebHost host = CreateWebHostBuilder(args).Build();
host.Run();
}
public static IWebHostBuilder CreateWebHostBuilder(string[] args)
{
return WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>()
.ConfigureAppConfiguration((hostContext, config) =>
{
IConfigurationRoot commandConfig = config.Build();
config.AddJsonFile($"appsettings.{commandConfig["App:InstanceName"]}.json", optional: true, reloadOnChange: true);
});
}

Json configuration source ignored in Kestrel setup

I'm working with an ASP.NET Core 1 RTM web app and I'm updating Kestrel setup to latest conventions. Setup aims at having the following sources for server.urls, from lowest to highest priority:
URLs set in Program.Main() code (default, e.g. for production)
URLs set in hosting.Development.json (e.g. to override default while developing)
URLs set in environment variables (e.g. to override default for staging or other production env.)
As per latest references (e.g. here on SO and here on Github), this is what I got now:
ProjDir\Program.cs:
public class Program
{
// Entry point for the application
public static void Main(string[] args)
{
const string hostingDevFilepath = "hosting.Development.json";
const string environmentVariablesPrefix = "ASPNETCORE_";
string currentPath = Directory.GetCurrentDirectory();
var hostingConfig = new ConfigurationBuilder()
.SetBasePath(currentPath)
.AddJsonFile(hostingDevFilepath, optional: true)
.AddEnvironmentVariables(environmentVariablesPrefix)
.Build();
System.Console.WriteLine("From hostingConfig: " +
hostingConfig.GetSection("server.urls").Value);
var host = new WebHostBuilder()
.UseUrls("https://0.0.0.0")
.UseConfiguration(hostingConfig)
.UseKestrel()
.UseContentRoot(currentPath)
.UseIISIntegration()
.UseStartup<Startup>()
.Build();
host.Run();
}
}
ProjDir\hosting.Development.json:
{
"server.urls": "http://localhost:51254"
}
From command line, having set ASPNETCORE_ENVIRONMENT=Development, this is the output:
> dotnet run
Project Root (.NETCoreApp,Version=v1.0) was previously compiled. Skipping compilation.
From hostingConfig: http://localhost:51254
info: AspNet.Security.OpenIdConnect.Server.OpenIdConnectServerMiddleware[0]
An existing key was automatically added to the signing credentials list: <<yadda yadda yadda>>
Hosting environment: Development
Content root path: <<my project root dir>>
Now listening on: https://0.0.0.0:443
Application started. Press Ctrl+C to shut down.
My expected output would be instead Now listening on: http://localhost:51254.
URLs value is correctly picked up from JSON source (as per console log), but then Kestrel configuration ignores that, even if UseConfiguration comes after UseUrls.
What am I missing? Thanks for your suggestions.
Try using urls instead of server.urls. The name of the setting changed post RC2.
Did some more tests. It seems to me that as soon as UseUrls() is present, no matter in which order, all Json config sources are ignored.
So I tried to come up with a solution supporting more than one hosting.json file, e.g. a default one and then one per environment. Basically I tried to replicate in Program.Main() a behavior similar to Startup.Startup(IHostingEnvironment env), where one can use both "appsettings.json" and $"appsettings.{hostingEnv.EnvironmentName}.json" as source. The only issue is that in Program.Main() there's no IHostingEnvironment available, but this GH issue reminded me that we still have Environment.GetEnvironmentVariable("some-variable") in our toolbelt.
Here's the full solution, please feel free to suggest improvements or (even better) some semplification:
public class Program
{
// Entry point for the application
public static void Main(string[] args)
{
const string environmentVariablesPrefix = "ASPNETCORE_";
string hostingEnvironmentKey = $"{environmentVariablesPrefix}ENVIRONMENT";
string hostingEnvironmentValue;
try
{
hostingEnvironmentValue = Environment
.GetEnvironmentVariable(hostingEnvironmentKey);
}
catch
{
hostingEnvironmentValue = "Development";
}
const string hostingFilepath = "hosting.json";
string envHostingFilepath = $"hosting.{hostingEnvironmentValue}.json";
string currentPath = Directory.GetCurrentDirectory();
var hostingConfig = new ConfigurationBuilder()
.SetBasePath(currentPath)
.AddJsonFile(hostingFilepath, optional: true)
.AddJsonFile(envHostingFilepath, optional: true)
.AddEnvironmentVariables(environmentVariablesPrefix)
.Build();
var host = new WebHostBuilder()
.UseConfiguration(hostingConfig)
.UseKestrel()
.UseContentRoot(currentPath)
.UseIISIntegration()
.UseStartup<Startup>()
.Build();
host.Run();
}
}
// in hosting.json
{
"server.urls": "https://0.0.0.0"
}
// in hosting.Development.json
{
"server.urls": "http://localhost:51254"
}

How do we set ContentRootPath and WebRootPath?

We're ending up with the following ContentRoot and WebRoot when we run our app from IIS.
ContentRoot: C:\MyApp\wwwroot
WebRoot: C:\MyApp\wwwroot\wwwroot
Here is how we are setting ContentRoot and WebRoot.
public class Startup
{
private readonly IHostingEnvironment _hostingEnv;
public Startup(IHostingEnvironment hostingEnv)
{
_hostingEnv = hostingEnv;
}
public void Configure(IApplicationBuilder app)
{
app.Run(context =>
{
// test output
context.Response.WriteAsync(_hostingEnv.ContentRootPath + "\r\n");
return context.Response.WriteAsync(_hostingEnv.WebRootPath + "\r\n");
});
}
public static void Main(string[] args)
{
var contentRoot = Directory.GetCurrentDirectory();
var webRoot = Path.Combine(contentRoot, "wwwroot");
var host = new WebHostBuilder()
.UseKestrel()
.UseIISPlatformHandlerUrl()
.UseContentRoot(contentRoot) // set content root
.UseWebRoot(webRoot) // set web root
.UseStartup<Startup>()
.Build();
host.Run();
}
}
From intellisense I see that...
ContentRootPath contains the application content files.
WebRootPath contains the web-servable content files.
How do we make the test output look instead like this:
ContentRoot: C:\MyApp\
WebRoot: C:\MyApp\wwwroot\
While RC2 documentation is still being prepared, here is what I learned while trying to deploy pre-RC2 app as Azure Web App:
There is no Visual Studio tooling yet, so the app must be published and deployed manually over FTP. For publishing, use: dotnet publish --configuration Release --output ./approot
If connected to Azure over FTP, you will probably see something similar to:
The "approot" folder can be replaced with the published one (the web.config is left in the approot).
The "approot" must be configured as a virtual application in Azure Portal (the default was site\wwwroot):
An the last thing to get static files served from the wwwroot folder, the Startup.cs file should be modified to include custom UseWebRoot call:
var currentDirectory = Directory.GetCurrentDirectory();
var host = new WebHostBuilder()
.UseKestrel()
.UseWebRoot(Path.Combine(currentDirectory, "..", "wwwroot"))
.UseDefaultHostingConfiguration(args)
.UseIISIntegration()
.UseStartup<Startup>()
.Build();
After these steps you should have ASPNET Core pre-RC2 web app running on Azure.
In RC2, if we put the web.config beside wwwroot and point IIS at the MyApp directory like this...
MyApp
web.config
wwwroot
...the code from the original question outputs this...
ContentRoot: C:\MyApp\
WebRoot: C:\MyApp\wwwroot\