What is the purpose of IWebHostBuilder.PreferHostingUrls - asp.net-core

I know what the documentation says, but I do not know where the IServer gets introduced or how it is configured.
My specific case is that I am calling IHostBuilder.ConfigureWebHost (not ConfigureWebHostDefaults), which as best I can determine does not automatically include Kestrel. I am using HttpSys via a UseHttpSys instead of using Kestrel.
I ran into an issue when I ran two local development websites at the same time. Even though the lauchSettings file had different ports for each, they both did register port 5000. Of course the 2nd site received an error indicating that 5000 was already in use. After much poking around, I found documentation indicating that port 5000 was the default for everything not just Kestrel. (I really believed that prior to 5.0, only Kestrel defaulted to 5000.) I proved the defaults by explicitly setting a URL in my code and it was honored and 5000 was not accessed. I then removed the code and set "urls": "http://localhost:6000" in the appSettings file and it to was honored. At this point I tried both true and false as the parameter to PreferHostingUrls and they both worked with the url configured in the appSettings file and both failed without an explicit url in either the appSettings or code.
So part of the question becomes what is IServer and how is it introduced and configured.

Both the HostBuilder and IWebHostBuilder containing the UseUrls method, it is semicolon-delimited list of IP addresses or host addresses with ports and protocols that the server should listen on for requests. By using this method, we could set the URL that the server should listen on for requests,
Besides, when we configure the Asp.net core application to use Server (such as Http.sys or Kestrel), in the server options, we could also set the URL that the server should listen on for requests, such as using the HttpSysOptions.UrlPrefixes Property or the KestrelServerOptions.Listen() method.
Then, using the PreferHostingUrls property, we could indicate whether the host should listen on the URLs configured on the IWebHostBuilder or those configured on the IServer.
Sample code as below:
public static IWebHostBuilder CreateWebHostBuilder(string[] args)
{
var config = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("hostsettings.json", optional: true)
.AddCommandLine(args)
.Build();
return WebHost.CreateDefaultBuilder(args)
.UseUrls("http://*:5000")
.UseConfiguration(config)
.UseHttpSys(serveroptions =>
{
serveroptions.Authentication.Schemes = Microsoft.AspNetCore.Server.HttpSys.AuthenticationSchemes.None;
serveroptions.Authentication.AllowAnonymous = true;
serveroptions.MaxConnections = 100;
serveroptions.MaxRequestBodySize = 30000000;
serveroptions.UrlPrefixes.Add("http://localhost:5001");
})
//.ConfigureKestrel(serverOptions =>
//{
// serverOptions.Limits.MaxConcurrentConnections = 100;
// serverOptions.Limits.MaxConcurrentUpgradedConnections = 100;
// serverOptions.Limits.MaxRequestBodySize = 10 * 1024;
// serverOptions.Limits.MinRequestBodyDataRate =
// new MinDataRate(bytesPerSecond: 100,
// gracePeriod: TimeSpan.FromSeconds(10));
// serverOptions.Limits.MinResponseDataRate =
// new MinDataRate(bytesPerSecond: 100,
// gracePeriod: TimeSpan.FromSeconds(10));
// serverOptions.Listen(IPAddress.Loopback, 5001);
// serverOptions.Limits.KeepAliveTimeout =
// TimeSpan.FromMinutes(2);
// serverOptions.Limits.RequestHeadersTimeout =
// TimeSpan.FromMinutes(1);
//})
.PreferHostingUrls(false)
.Configure(app =>
{
app.Run(context =>
context.Response.WriteAsync("Hello, World!"));
});
}
If the PreferHostingUrls is false, it listening the 5001 port:
If the PreferHostingUrls is true, it will listen the 5000 port:
Reference:
How to use HTTP.sys
ASP.NET Core Web Host: Host configuration values

Related

Swashbuckle using port 80 for https when run behind reverse proxy

I have a .net core api documented with swagger/swashbuckle.
When running the swagger ui on localhost on url https://localhost:44390/ the "Try it out" works fine.
We have the same solution in an App service in Azure with an Azure Front Door acting as reverse proxy. Front Door only accepts https traffic and only forwards https traffic. Front door domain is widget.example.com and App service is widget-test-app.azurewebsites.net. When running the swagger ui in Azure using the url https://widget.example.com/api/index.html there are two differences compared to running in localhost:
The swagger ui is showing a Servers -heading and a dropdown
The swagger ui is showing the server url as https://widget.example.com:80
I added an endpoint in the api with the following code
return $"Host {HttpContext.Request.Host.Host} Port {HttpContext.Request.Host.Port} Https {HttpContext.Request.IsHttps}";
When requesting https://widget.example.com/api/v1/test/url it returns
Host widget-test-app.azurewebsites.net Port Https True
This is completely ok since Front door is changing the host header. Port is empty, though.
Summary: Swagger ui is showing the correct domain in the Servers -dropdown but the port number is wrong. How can I get it to either omit the port number if it's 80 or 443, or add it correctly?
Update: The problem is in the swagger.json file which behind the reverse proxy includes a servers element
"servers": [{
"url": "https://widget.example.com:80"
}]
Startup.ConfigureServices
services.AddApiVersioning(options => {
options.Conventions.Add(new VersionByNamespaceConvention());
});
services.AddVersionedApiExplorer(o => {
o.GroupNameFormat = "'v'VVV";
o.SubstituteApiVersionInUrl = true;
});
services.AddSwaggerGen(c => {
c.SwaggerDoc("v1", new OpenApiInfo {
Title = "Widget backend v1", Version = "v1"
});
c.SwaggerDoc("v2", new OpenApiInfo {
Title = "Widget backend v2", Version = "v2"
});
c.EnableAnnotations();
c.AddEnumsWithValuesFixFilters();
var xmlFile = $ "{Assembly.GetExecutingAssembly().GetName().Name}.xml";
var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
c.IncludeXmlComments(xmlPath);
});
Startup.Configure
app.UseSwagger(options => {
options.RouteTemplate = "/api/swagger/{documentname}/swagger.json";
});
app.UseSwaggerUI(options => {
foreach(var description in provider.ApiVersionDescriptions) {
options.SwaggerEndpoint($ "/api/swagger/{description.GroupName}/swagger.json", "widget backend " + description.GroupName);
}
options.RoutePrefix = "api";
});
To fix this I cleared the Servers -list. Here is my code:
app.UseSwagger(options =>
{
options.RouteTemplate = "/api/swagger/{documentname}/swagger.json";
options.PreSerializeFilters.Add((swagger, httpReq) =>
{
//Clear servers -element in swagger.json because it got the wrong port when hosted behind reverse proxy
swagger.Servers.Clear();
});
});
The solution (ok, a - mine - solution :)) is to configure forward headers in Startup.
services.Configure<ForwardHeadersOptions>(options =>
{
options.ForwardHeaders = ForwardHeaders.All; // For, Proto and Host
options.KnownNetworks.Clear();
options.KnownProxies.Clear();
});
Doing this, any URL generation in the app (behind reverse proxy) should respect the port-forwarding value. According to documentation known networks should be specified (taken from docs):
Only allow trusted proxies and networks to forward headers. Otherwise, IP spoofing attacks are possible.
See ASP.NET documentation for more details.

Kestrel address binding errors in azure app service

I have an aspnet core 2.2 app which looks like this:
return WebHost.CreateDefaultBuilder(args)
.ConfigureAppConfiguration((builderContext, config) =>
{
var env = builderContext.HostingEnvironment;
config.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true);
})
.UseStartup<Startup>()
.UseKestrel(options =>
{
options.ConfigureHttpsDefaults(opts =>
{
opts.ServerCertificate = GetCertificate();
opts.ClientCertificateMode = ClientCertificateMode.RequireCertificate;
opts.ClientCertificateValidation = CertificateValidator.DisableChannelValidation;
});
});
Everything works great locally. However, when deployed to an azure app service I get the following:
Unhandled Exception: System.IO.IOException: Failed to bind to address http://127.0.0.1:5000: address already in use
Is there anything special I need to do differently here? I don't particularly care about using Kestrel vs anything else as long as I can perform client certificate authentication (which works locally in my current implementation).
The issue turned out to be that the application was already running but was not taking requests due to trying to use kestrel in the azure app service. I had to use .UseIIS() to get it to work. Perhaps I was doing something wrong.
If you already know what port your app. will consistenly use, it may make sense to just code this into your app with UseUrls:
.UseUrls(urls: "http://localhost:10000")
Or running dotnet run --urls="http://localhost:10000" like this issue.

Enable cross domain not a member of hubConfiguration

i have been developing a signalR chat application in vb.net where i'm using the below code:
Public Sub Configuration(ByVal app As IAppBuilder)
Dim config = New HubConfiguration With {.EnableCrossDomain = True}
app.MapHubs(config)
End Sub
it throws the error
Enable cross domain not a member of hubConfiguration
Could anyone please suggest what could be the alternative to enable Cross Domain
The tutorial is explaining this very good:
(http://www.asp.net/signalr/overview/guide-to-the-api/hubs-api-guide-javascript-client)
public class Startup
{
public void Configuration(IAppBuilder app)
{
// Branch the pipeline here for requests that start with "/signalr"
app.Map("/signalr", map =>
{
// Setup the CORS middleware to run before SignalR.
// By default this will allow all origins. You can
// configure the set of origins and/or http verbs by
// providing a cors options with a different policy.
map.UseCors(CorsOptions.AllowAll);
var hubConfiguration = new HubConfiguration
{
// You can enable JSONP by uncommenting line below.
// JSONP requests are insecure but some older browsers (and some
// versions of IE) require JSONP to work cross domain
// EnableJSONP = true
};
// Run the SignalR pipeline. We're not using MapSignalR
// since this branch already runs under the "/signalr"
// path.
map.RunSignalR(hubConfiguration);
});
}
}
}
By the way: In the case you post your code (all) it is easier to search.

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 to define configs multiple endpoints for a WCF self-hosted service?

I have two WCF Web API Contracts. Before this, I was happy that I could use TestClient. But after I implemented the second one I had to define endpoints (and could not use the default one) and after that, either I see nothing in browser or this message saying that "This XML file does not appear to have any style information associated with it." when I try to go to the endpoint address. It is the same when I try the config file (although I do not know how to set "EnableTestClient = true"). I really appreciate any help.
var baseurl = new Uri("http://localhost:7000/api/v1.0");
var config = new HttpConfiguration() { EnableTestClient = true };
config.CreateInstance = (type, context, request) => container.Resolve(type);
var host = new HttpServiceHost(typeof(ServiceAPI), config, baseurl);
host.Description.Behaviors.Add(
new ServiceMetadataBehavior() { HttpGetEnabled = true, HttpGetUrl = baseurl });
// Add MEX endpoint
//host.AddServiceEndpoint(
// ServiceMetadataBehavior.MexContractName,
// MetadataExchangeBindings.CreateMexHttpBinding(),
// "mex"
//);
//host.AddServiceEndpoint(typeof(IStatAPI), new WebHttpBinding(), "/stat");
//host.AddServiceEndpoint(typeof(IAlarmAPI), new WebHttpBinding(), "/alarm");
host.Faulted += (s, e) => Debug.WriteLine(e);
host.Open();
I don't believe that multiple endpoints should be used to expose different APIs. They are for exposing the same contract with a different binding.
You should create a new host for each API. You can share the config between them though.