Self contained .net core web app - asp.net-core

I have a .net core web app that I would like to install as a regular app on Windows or OSX. If I deploy that as a self contained app, will the web server start when the .exe is run? And, how can I configure what port the app will listen on?
My long term goal would be to create a UI to let the user configure some stuff and have the app distributable from Windows Store and/or Mac AppStore, but I'm not sure if this is possible?

In order to deply as a self contained app you need to specify a runtime in your csproj file.
:
<PropertyGroup>
<RuntimeIdentifiers>win10-x64;osx.10.11-x64;ubuntu.16.10-x64</RuntimeIdentifiers>
</PropertyGroup>
Than publish the application for that runtime:
dotnet publish -c Release -r win10-x64
If you want to change the port your application is listening on you need to set that in the Program.cs file:
public static void Main(string[] args)
{
var host = new WebHostBuilder()
.UseKestrel()
.UseContentRoot(Directory.GetCurrentDirectory())
.UseIISIntegration()
.UseStartup<Startup>()
.UseApplicationInsights()
// Set url and port here.
.UseUrls("http://0.0.0.0:5005")
.Build();
host.Run();
}
You can now run your app by running the yourapp.exe file in your publish directiry.

Related

IIS ASP.NET 6 startup throws System.IO.DirectoryNotFoundException: D:\agent\_work\38\s\IdentityServer\wwwroot\

We are updating one of our applications, in this case IdentityServer, from .NET 5 to .NET 6. It is being hosted by IIS and deployed by Azure Devops Services. The issue we are seeing is that on our development environment the website fails to load but on our staging environment it runs just fine. The error we are seeing on development is
12:45:37.519|Fatal|1||Host terminated unexpectedly.||
System.IO.DirectoryNotFoundException: D:\agent\_work\38\s\IdentityServer\wwwroot\
at Microsoft.Extensions.FileProviders.PhysicalFileProvider..ctor(String root, ExclusionFilters filters)
at Microsoft.Extensions.FileProviders.PhysicalFileProvider..ctor(String root)
at Microsoft.AspNetCore.Hosting.StaticWebAssets.StaticWebAssetsLoader.<>c.<UseStaticWebAssetsCore>b__1_0(String contentRoot)
at Microsoft.AspNetCore.StaticWebAssets.ManifestStaticWebAssetFileProvider..ctor(StaticWebAssetManifest manifest, Func`2 fileProviderFactory)
at Microsoft.AspNetCore.Hosting.StaticWebAssets.StaticWebAssetsLoader.UseStaticWebAssetsCore(IWebHostEnvironment environment, Stream manifest)
at Microsoft.AspNetCore.Hosting.StaticWebAssets.StaticWebAssetsLoader.UseStaticWebAssets(IWebHostEnvironment environment, IConfiguration configuration)
at Microsoft.AspNetCore.WebHost.<>c.<ConfigureWebDefaults>b__9_0(WebHostBuilderContext ctx, IConfigurationBuilder cb)
at Microsoft.AspNetCore.Hosting.GenericWebHostBuilder.<>c__DisplayClass9_0.<ConfigureAppConfiguration>b__0(HostBuilderContext context, IConfigurationBuilder builder)
at Microsoft.Extensions.Hosting.HostBuilder.BuildAppConfiguration()
at Microsoft.Extensions.Hosting.HostBuilder.Build()
at IdentityServer.Program.Main(String[] args) in D:\agent\_work\38\s\IdentityServer\Program.cs:line 23
The path it reports, D:\agent\_work\38\s\IdentityServer\wwwroot\ is interesting because that path is the same as the path from the DevOps build machine. We don't see this error if we revert back to .NET 5 and we don't see the problem on our staging machine.
The Program.cs class is defined as
using System;
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using NewRelic.LogEnrichers.Serilog;
using Serilog;
using Serilog.Events;
namespace IdentityServer
{
public class Program
{
public static int Main(string[] args)
{
try
{
CreateLogger();
Log.Information("Starting host...");
CreateHostBuilder(args).Build().Run();
return 0;
}
catch (Exception ex)
{
Log.Fatal(ex, "Host terminated unexpectedly.");
return 1;
}
finally
{
Log.CloseAndFlush();
}
}
public static void CreateLogger()
{
var configuration = GetConfiguration();
Log.Logger = new LoggerConfiguration()
.ReadFrom.Configuration(configuration)
.Enrich.FromLogContext() // allows logging middleware to inject output values
.Enrich.WithThreadId()
.Enrich.WithNewRelicLogsInContext()
.CreateLogger();
}
public static IHostBuilder CreateHostBuilder(string[] args)
{
var configuration = GetConfiguration();
return Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(
webBuilder =>
{
webBuilder.UseConfiguration(configuration);
webBuilder.UseSerilog();
webBuilder.UseIIS();
webBuilder.CaptureStartupErrors(true);
webBuilder.UseStartup<Startup>();
});
}
private static IConfiguration GetConfiguration()
{
var environment = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT");
var builder = new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
.AddJsonFile($"appsettings.{environment}.json", true, true);
var configuration = builder.Build();
return configuration;
}
}
}
We do have other .NET 6 web applications running just fine on this instance of IIS. I was thinking that the problem might be in our release pipelines but they are identical in their task configurations between the environments. Tried looking for the directory path in the code or configuration but don't see it anywhere. Have tried manually setting the WebRoot and ContentRoot paths via .UseWebRoot("path to folder") and .UseContentRoot("path to folder") in the Program.cs but didn't see any change in the logs or the app starting.
Even updated the web.config file to have the exact path for executing the project dll in the aspNetCore element but still no change.
Update 10 Feb 2022
Added debug output to the startup to verify file and folder paths. Everything in the environment variables and execution file path look correct.
ASPNETCORE_IIS_PHYSICAL_PATH - C:\inetpub\webapps\IdentityServer\
Executable Path: C:\inetpub\webapps\IdentityServer\IdentityServer.dll
The problem ended up being how we were pushing our updates out to the servers from DevOps. Our pipelines were built to copy over files out of the Release directory of the build folder. One of the problems with this approach is that files not needed for a site to run but generated during a build are also copied to the release server. In this case, a new file which is generated in .NET 6, .staticwebassets.runtime.json, was getting copied to our servers.
The way .NET 6 seems to behave is that if the environment is set to Development then it will look for this file to figure out where the static web assets are located. If the file doesn't exist then it will assume the files are in a wwwroot sub-directory of the site. This makes sense for instances where you are running the project from your local Visual Studio. More details about this file are available in another SO post with links to the source code in GitHub. To fix our problem we changed our release pipeline to use the publish.zip file that is generated when you run the publish command on a solution. The archive only contains the files needed to run the site, so none of the extraneous files like .staticwebassets.runtime.json are included. We should have been doing this the whole time... lesson learned.
We now unzip the publish.zip file, apply any file transformations, then copy the unzipped files to the web server.

How to configure multiple ASPNETCORE_ENVIRONMENT on same machine?

I have ASP.NET core web application. I have configured the web application on our web server and set the ASPNETCORE_ENVIRONMENT variable to Development. I set this variable at machine level like shown in the picture below.
Now on the same machine i want to configured one more instance of same web application as Staging environment.
What are my options here to set ASPNETCORE_ENVIRONMENT at application level instead of machine level? so i can host multiple instances of the same application on the same machine?
You have a couple of options.
Run each app as a different user, and set the environment variable within that user profile. This gives you a nice added security bonus. You'll have to set the app pool to load the user profile.
Use IIS configuration
Start IIS manager
Choose configuration editor Pull down the section
combobox and choose system.webServer/aspNetCore
Pull down the from
combobox and choose Applicationhost.config
Click on the
environmentVariables element and click on the ... button hiding in
the second column, at the right.
Set your environment variables.
Exit out of the environment variables screen and then click Apply.
Restart the app pool/app.
Can you change the code parsing configuration running on the web server? That's what I would recommend doing. That would allow you to configure your environment more naturally in a Windows setting.
While the traditional way to configure the IHostingEnvironment.EnvironmentName variable is via the ASPNETCORE_ENVIRONMENT environment variable as you have done, you can change how ASP.NET Core parses its configuration such that you can set the variable via a command line argument.
To get into specifics...
By default, the Program.cs file emitted by the dotnet new -t web command looks something like the following:
public static void Main(string[] args) {
var host = new WebHostBuilder()
.UseKestrel()
.UseUrls("http://0.0.0.0:5000")
.UseContentRoot(Directory.GetCurrentDirectory())
.UseIISIntegration()
.UseStartup<Startup>()
.Build();
host.Run();
}
This makes ASP.NET Core use the default configuration processing (environment variables with a ASPNETCORE_ prefix) to determine the value of IHostingEnvironment.EnvironmentName, which you are using to configure how your application runs.
Fortunately, you can alter the way that ASP.NET Core parses configuration by utilizing the UseConfiguration() extension method on WebHostBuilder. Here's an example of using custom configuration with the default implementation:
public static void Main(string[] args) {
var configuration =
new ConfigurationBuilder()
.AddEnvironmentVariables("ASPNETCORE_")
.Build();
var host =
new WebHostBuilder()
.UseConfiguration(configuration)
.UseKestrel()
.UseUrls("http://0.0.0.0:5000")
.UseContentRoot(Directory.GetCurrentDirectory())
.UseIISIntegration()
.UseStartup<Startup>()
.Build();
host.Run();
}
From here, I would change it so it can use the command line in addition to the ASPNETCORE_ prefixed environment variables. This will allow you to easily run your application with whatever environment name you want, like so:
public static void Main(string[] args) {
var configuration =
new ConfigurationBuilder()
.AddEnvironmentVariables("ASPNETCORE_")
.AddCommandLine(args)
.Build();
var host =
new WebHostBuilder()
.UseConfiguration(configuration)
.UseKestrel()
.UseUrls("http://0.0.0.0:5000")
.UseContentRoot(Directory.GetCurrentDirectory())
.UseIISIntegration()
.UseStartup<Startup>()
.Build();
host.Run();
}
Then, when you start your dotnet core application with dotnet run, you can set the environment on the command line, like this:
dotnet run environment=development
dotnet run environment=staging
Now the ASPNETCORE_ENVIRONMENT environment variable will still be respected, but you can override it via the command line when you are doing local development. As a note, you will need to include the Microsoft.Extensions.Configuration.CommandLine nuget package to your project.json file if you have no already done so to get the AddCommandLine() extension method.

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\

dependency Kestrel.Https 1.0.0-rc1-final does not support framework DNXCore, Version=v5.0

I'm trying to configure https to my Kestrel server to work on Ubuntu 14 with dnxcore50.
But when I add a dependency to:
"Microsoft.AspNet.Server.Kestrel.Https": "1.0.0-rc1-final"
And I try to restore my package I get this message:
Dependency Kestrel.Https 1.0.0-rc1-final does not support framework DNXCore, Version=v5.0
If I go to windows and use dnx451 and add the same dependency things works great.
But, if I can't use Kestrel.Https on Ubuntu with dnxcore50, how can I configure Https on Ubuntu using dnxcore50?
That's because the HTTPS version of Kestrel only targets the full .NET framework on RC1: https://www.nuget.org/packages/Microsoft.AspNet.Server.Kestrel.Https/1.0.0-rc1-final.
As of RC2 Kestrel.Https will target netstandard1.3: https://github.com/aspnet/KestrelHttpServer/blob/dev/src/Microsoft.AspNetCore.Server.Kestrel.Https/project.json#L20.
So the solution is to either wait for RC2 to drop or use the bleeding RC2 bits from MyGet.
Today Kestrel already supports HTTPS:
Here's the library tha supports it since version 1.0.0:
https://www.nuget.org/packages/Microsoft.AspNetCore.Server.Kestrel.Https/
To implement it on your code, in you main to initialize your asp.net core app add UseHttps as an option
Here's a sample on how to do it!
public static void Main(string[] args)
{
var host = new WebHostBuilder()
.UseKestrel(options =>
{
// options.ThreadCount = 4;
options.NoDelay = true;
options.UseHttps("testCert.pfx", "testPassword");
options.UseConnectionLogging();
})
.UseUrls("http://localhost:5000", "https://localhost:5001")
.UseContentRoot(Directory.GetCurrentDirectory())
.UseStartup<Startup>()
.Build();
// The following section should be used to demo sockets
//var addresses = application.GetAddresses();
//addresses.Clear();
//addresses.Add("http://unix:/tmp/kestrel-test.sock");
host.Run();
}
Below there is also a link from the sample
https://github.com/aspnet/KestrelHttpServer/blob/dev/samples/SampleApp/Startup.cs#L37-L43

MVC 6 install as a Windows Service (ASP.NET Core 1.0.0)

UPDATE - 26th July 2016
I have added the solution to this in ASP.NET Core 1.0.0 in the answers below.
I have created a simple MVC 6 app and have included the Microsoft.AspNet.WebListener library so I can host outside of IIS.
From project.json:
"dependencies": {
"Microsoft.AspNet.Server.WebListener": "1.0.0-beta4",
"Microsoft.AspNet.Mvc": "6.0.0-beta4"
},
"commands": {
"web": "Microsoft.AspNet.Hosting --server Microsoft.AspNet.Server.WebListener --server.urls http://localhost:5000"
}
When I publish this I can run the web.cmd file and get the site running in a console window. Great!
But in OWIN you can use TopShelf to launch your web app from a Console Application. This can then be built as an executable and installed as a Windows Service.
Is there a way to do this with an ASP.NET 5 MVC 6 web app?
You can run a DNX app as a Windows service, however, you can't run the CMD file directly. You will get an error saying the following: 'The service did not respond to the start or control request in a timely fashion.' You can point directly to dnx.exe and pass the project folder and command as arguments.
Read this post for a lot more detail: http://taskmatics.com/blog/run-dnx-applications-windows-service/
Once you have your app set up. You can bootstrap ASP.NET from the OnStart method of the service. To do this you can use WebHostBuilder from Microsoft.AspNet.Hosting.
Lastly, you can ensure the app is still runnable in VS by passing an argument (such as 'non-service') to the Main method and check that before calling ServiceBase.Run, and if present, you can call OnStart directly instead. The project's properties gives you the option to pass arguments when running in VS.
UPDATE:
There is a follow up post which builds upon the one above. It shows how to run ASP.NET 5 with static files and MVC 6 in a Windows service. The link is here: http://taskmatics.com/blog/host-asp-net-in-a-windows-service/
As of the latest ASP.NET Core Version 1.0.0 libraries this is now somewhat simplified.
There is an open discussion on this topic on the ASP.NET GitHub page.
All ASP.NET Core applications are now Console Applications and there is a new library to host as a Windows Service that runs on the full .NET framework (which makes sense as this whole problem assumes a Windows web server).
We need to create a new ASP.NET Core Web Application (.NET Framework)
Check the project.json file to ensure that the "frameworks" section is as below:
"frameworks": {
"net461": {}
},
We need to then add the service hosting library Microsoft.AspNetCore.Hosting.WindowsServices and save the project.json to restore the package.
We then need to edit the program.cs file and add paths for running in debug and running as a service, the code for this is as follows:
public static void Main(string[] args)
{
var isDebug = Debugger.IsAttached || ((IList)args).Contains("--debug");
string runPath;
if (isDebug)
runPath = Directory.GetCurrentDirectory();
else
{
var exePath = Process.GetCurrentProcess().MainModule.FileName;
runPath = Path.GetDirectoryName(exePath);
}
var host = new WebHostBuilder()
.UseKestrel()
.UseContentRoot(runPath)
.UseStartup<Startup>()
.Build();
if (isDebug)
host.Run();
else
host.RunAsService();
}
The .RunAsService() method is an extension method provided by the Microsoft.AspNetCore.Hosting.WindowsServices lib.
To install as a service you just need to run the following command from an Administrator command prompt:
SC Create <service-name> binPath= "[PublishOutputPath]\mvc6-example.exe"
Please clone and view the working version on my GitHub repository.
I hope this helps :)
UPDATE: It seems like there is going to be a Windows Service hosting option coming in with RC2. See this GitHub comment for more info and this answer.
I am afraid the answer is no for this. I have been looking into this as well and the best way to do this is to deploy your project into a known location on disk and have a Windows Service to spin up the process which calls the cmd file. This way, the Windows Service will only act as a watchdog.
I am hoping to get some blog posts and samples on this as I have been looking into this in terms of deployment. There is also an open discussion here: https://github.com/aspnet/Home/issues/465
It is worth looking at https://github.com/aspnet/Hosting/tree/dev/src/Microsoft.AspNet.Hosting.WindowsServices
It seems that ASP.NET team is working on native support for hosting ASP.NET MVC 6 applications within Windows Services.
Here is a simple ServiceBase hosting an ASP.NET MVC 6 app:
/// <summary>
/// Provides an implementation of a Windows service that hosts ASP.NET.
/// </summary>
public class WebApplicationService : ServiceBase
{
private IWebApplication _application;
private IDisposable _applicationShutdown;
private bool _stopRequestedByWindows;
/// <summary>
/// Creates an instance of <c>WebApplicationService</c> which hosts the specified web application.
/// </summary>
/// <param name="application">The web application to host in the Windows service.</param>
public WebApplicationService(IWebApplication application)
{
_application = application;
}
protected sealed override void OnStart(string[] args)
{
OnStarting(args);
_application
.Services
.GetRequiredService<IApplicationLifetime>()
.ApplicationStopped
.Register(() =>
{
if (!_stopRequestedByWindows)
{
Stop();
}
});
_applicationShutdown = _application.Start();
OnStarted();
}
protected sealed override void OnStop()
{
_stopRequestedByWindows = true;
OnStopping();
_applicationShutdown?.Dispose();
OnStopped();
}
}