31

I have a ASP.NET Core application that will be used as a client by multiple users. In other words, it will not be hosted on a central server and they will run the published executable anytime they need to use the application.

In the Program.cs file there's the following:

var host = new WebHostBuilder()
    .UseKestrel()
    .UseContentRoot(Directory.GetCurrentDirectory())
    .UseIISIntegration()
    .UseStartup<Startup>()
    .Build();

host.Run();

I would like the default web browser to automatically open to avoid a redundant step of the user having to open the browser and manually put in the http://localhost:5000 address.

What would be the best way to achieve this? Calling Program.Start after the call to Run() won't work because Run blocks the thread.

Gerardo Grignoli
  • 14,058
  • 7
  • 57
  • 68
Justin Helgerson
  • 24,900
  • 17
  • 97
  • 124

4 Answers4

45

You have two different problems here:

Thread Blocking

host.Run() indeed blocks the main thread. So, use host.Start() (or await StartAsync on 2.x) instead of host.Run().

How to start the web browser

If you are using ASP.NET Core over .NET Framework 4.x, Microsoft says you can just use:

Process.Start("http://localhost:5000");

But if you are targeting multiplatform .NET Core, the above line will fail. There is no single solution using .NET Standard that works on every platform. The Windows-only solution is:

System.Diagnostics.Process.Start("cmd", "/C start http://google.com");

Edit: I created a ticket and a MS dev answered that as-of-today, if you want a multi platform version you should do it manually, like:

public static void OpenBrowser(string url)
{
    if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
    {
        Process.Start(new ProcessStartInfo(url) { UseShellExecute = true }); // Works ok on windows
    }
    else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
    {
        Process.Start("xdg-open", url);  // Works ok on linux
    }
    else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
    {
        Process.Start("open", url); // Not tested
    }
    else
    {
        ...
    }
}

All together now:

using System.Threading;

public class Program
{
    public static void Main(string[] args)
    {
        var host = new WebHostBuilder()
            .UseKestrel()
            .UseContentRoot(Directory.GetCurrentDirectory())
            .UseIISIntegration()
            .UseStartup<Startup>()
            .Build();

        host.Start();
        OpenBrowser("http://localhost:5000/");
    }

    public static void OpenBrowser(string url)
    {
        if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
        {
        Process.Start(new ProcessStartInfo(url) { UseShellExecute = true });
        }
        else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
        {
            Process.Start("xdg-open", url);
        }
        else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
        {
            Process.Start("open", url);
        }
        else
        {
            // throw 
        }
    }
}
Grzegorz Smulko
  • 2,525
  • 1
  • 29
  • 42
Gerardo Grignoli
  • 14,058
  • 7
  • 57
  • 68
  • For me, if I try to use a url with a query string then somewhy it just takes the first query parameter but discards the rest. It seems it has problems with the ampersand & character. Have you tried this? Does it need to be escaped somehow? If I do this instead: Process.Start(@"C:\Program Files (x86)\Google\Chrome\Application\chrome.exe", url); Then it works as expected. – UmaN Jan 06 '19 at 13:19
  • 1
    Yes @UmaN, `&` is escaped like this: `^&` accoring to https://stackoverflow.com/a/27960888/97471 So, try `Process.Start(new ProcessStartInfo("cmd", $"/c start {url.Replace("&", "^&")}"));` – Gerardo Grignoli Jan 06 '19 at 16:24
  • 4
    According to the ticket you linked, this is now possible with `new ProcessStartInfo(url) { UseShellExecute = true }` – Kyle Delaney Aug 13 '19 at 05:34
  • You also need to add a `host.WaitForShutdown();` at the end of the `Main` method to prevent the server from shutting down immediately. – mrmashal Sep 23 '19 at 03:34
7

Yet another option here is to resolve an IApplicationLifetime object in Startup.Configure and register a callback on ApplicationStarted. That event is triggered when a host has spun up and is listening.

public void Configure(IApplicationBuilder app, IApplicationLifetime appLifetime)
{
    appLifetime.ApplicationStarted.Register(() => OpenBrowser(
        app.ServerFeatures.Get<IServerAddressesFeature>().Addresses.First()));
}

private static void OpenBrowser(string url)
{
    Process.Start(
        new ProcessStartInfo("cmd", $"/c start {url}") 
        {
            CreateNoWindow = true 
        });
}
Ivan Zaruba
  • 4,236
  • 5
  • 20
  • 29
5

The accepted answer is good, but because there's no blocking the program will end immediately, stopping the server. Here's a version adapted from Gerardo's and Ivan's answers.

It will create the server, launch the browser when the server starts listening, and block until the server ends:

using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;
using System.Diagnostics;
using Microsoft.AspNetCore.Builder;
using static System.Runtime.InteropServices.RuntimeInformation;
using static System.Runtime.InteropServices.OSPlatform;

class Program
{
    static void Main(string[] args)
    {
        string url = "http://localhost:54321/";

        using (var server = CreateServer(args, url))
        {
            StartBrowserWhenServerStarts(server, url);
            server.Run(); //blocks
        }
    }

    /// <summary>
    /// Create the kestrel server, but don't start it
    /// </summary>
    private static IWebHost CreateServer(string[] args, string url) => WebHost
        .CreateDefaultBuilder(args)
        .UseStartup<Startup>()
        .UseUrls(url)
        .Build();

    /// <summary>
    /// Register a browser to launch when the server is listening
    /// </summary>
    private static void StartBrowserWhenServerStarts(IWebHost server, string url)
    {
        var serverLifetime = server.Services.GetService(typeof(IApplicationLifetime)) as IApplicationLifetime;
        serverLifetime.ApplicationStarted.Register(() =>
        {
            var browser =
                IsOSPlatform(Windows) ? new ProcessStartInfo("cmd", $"/c start {url}") :
                IsOSPlatform(OSX) ? new ProcessStartInfo("open", url) :
                new ProcessStartInfo("xdg-open", url); //linux, unix-like

            Process.Start(browser);
        });
    }
}
Will
  • 2,086
  • 23
  • 30
2

You can spawn the web browser process right before host.Run(). This works with Chrome, and possibly other browsers:

public static void Main(string[] args)
{
    var host = new WebHostBuilder()
        .UseKestrel()
        .UseContentRoot(Directory.GetCurrentDirectory())
        .UseIISIntegration()
        .UseStartup<Startup>()
        .Build();

    var browserExecutable = "C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe";
    Process.Start(browserExecutable, "http://localhost:5000");

    host.Run();
}

In the case of Chrome, the new window will wait until the server spins up and then connect and display the application.

Depending on how your system is configured, you may be able to do Process.Start("http://localhost:5000") to launch the default browser instead of hardcoding the path to the executable. This didn't work for me for some reason. You could also pull the browser path out of the registry or out of a configuration file.

Nate Barbettini
  • 51,256
  • 26
  • 134
  • 147