Search  
Always will be ready notify the world about expectations as easy as possible: job change page
Jul 17

Quickly convert a console app into a single-point web app in C#

Quickly convert a console app into a single-point web app in C#
Author:
Source:
Views:
1752

I found myself in need of quickly converting my C# console application into a web application, primarily, I was asked to expose our console app’s logic through an API with very little time to manage it.

This article will cover on how I eventually exposed our console app on port 5005 of the localhost, our internal network, for bonus, I’ll throw in the containerised Docker version of the app, too!

Let’s start with a simple console app:

mkdir ConsoleToWeb
cd ConsoleToWeb
dotnet new console
dotnet run

Create console app

The source of this app will look something like this:

using System;

namespace ConsoleToWeb
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Hello World!"); // => This needs to be converted into a web app
        }
    }
}

To convert this into a ASP.NET Web App, we need to do 3 things:

  • Convert the console project into a web project (csproj)
  • Introduce a Generic Host to host our Web App
  • Rewrite the Main method to run our WebHost

Converting a console csproj into a web csproj

Our original console csproj looks something like the following:

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net5.0</TargetFramework>
  </PropertyGroup>

</Project>

For a web project, we need to target a different SDK; Microsoft.NET.Sdk.Web

Also, the OutputType does not need to be an Exe.

Modify the csproj file to look like this:

<Project Sdk="Microsoft.NET.Sdk.Web">

  <PropertyGroup>
    <TargetFramework>net5.0</TargetFramework>
  </PropertyGroup>
 
</Project>

Introduce a Generic Host to host our web app

The IHost class is a class designed for the new app hosting system of .NET. It handles core functionality like Dependency Injection, Configuration, Logging, …

We will use the HostBuilder to create a vanilla Web Host that will have 1 Run method which will call the code from our Console app.

Add the following code to your Program.cs file:

public static IHostBuilder CreateHostBuilder(string[] args) =>
  Host.CreateDefaultBuilder(args)
      .ConfigureWebHost(
          webHost => webHost
              .UseKestrel(kestrelOptions => { kestrelOptions.ListenAnyIP(5005); })
              .Configure(app => app
                  .Run(
                      async context =>
                      {
                          await context.Response.WriteAsync("Hello World!");
                      }
                  )));

Don’t forget about the necessary using statements:

using Microsoft.Extensions.Hosting;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;

Rewrite the Main method to run our WebHost

We’re almost there, we just need to tie it all up in the Main method of the Program.cs:

static void Main(string[] args)
{
    CreateHostBuilder(args).Build().Run();
}

Let’s examine the full working example:

using System;
using Microsoft.Extensions.Hosting;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;

namespace ConsoleToWeb
{
    class Program
    {
        static void Main(string[] args)
        {
            CreateHostBuilder(args).Build().Run();
        }

        public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                .ConfigureWebHost(
                    webHost => webHost
                        .UseKestrel(kestrelOptions => { kestrelOptions.ListenAnyIP(5005); })
                        .Configure(app => app
                            .Run(
                                async context =>
                                {
                                    await context.Response.WriteAsync("Hello World!");
                                }
                            )));
    }
}

That is it! Now you can run the app using dotnet run and see the result in your browser at http://localhost:5005

ConsoleToWeb

Web localhost:5005

Dockerize it!

Since we’re on a roll here, let’s quickly Dockerize this app so that we can package and ship it.

Add a Dockerfile to the root of the ConsoleToWeb directory.

touch Dockerfile

The contents of the Dockerfile should be something like:

FROM mcr.microsoft.com/dotnet/sdk:5.0 AS build-env
WORKDIR /app

COPY *.csproj ./
RUN dotnet restore

COPY . ./
RUN dotnet publish -c Release -o out

FROM mcr.microsoft.com/dotnet/aspnet:5.0
WORKDIR /app
COPY --from=build-env /app/out .
EXPOSE 5005
ENTRYPOINT ["dotnet", "ConsoleToWeb.dll"]

Now that is done, we can build our image locally and tag it:

docker build . -t consoletoweb

docker build consoletoweb

After the Docker image is built, we can run the image in a container using docker run -p 5005:5005 consoletoweb

Run the Docker image

And the effect should be the same on http://localhost:5005

Web localhost:5005

Quickly adding services using FuncR

Still going strong now, let’s introduce a service, add some small implementation and have that service injected into our Web App using Dependency Injection!

Introduce the IFooService:

interface IFooService
{
    string Foo(int numberOfFoos);
}

Now, we could implement this service using a new class, or we could write just the code we need for string Foo(int numberOfFoos) method to work using a function with FuncR.

FuncR logo

FuncR is a small .NET standard library that enables you to register functions against interfaces in C#.

First we need to add FuncR to the project

dotnet add package FuncR

To use FuncR, add a ConfigureServices method to your Program.cs file:

 1: private static void ConfigureServices(IServiceCollection services)
 2: {
 3:     services.AddScopedFunction<IFooService>
 4:         (nameof(IFooService.Foo))
 5:         .Runs<int, string>(numberOfFoos =>
 6:         {
 7:             var foos = Enumerable.Range(1, numberOfFoos).Select(n => "Foo");
 8:             return $"{String.Join(", ", foos)}";
 9:         });
10: }

In this ConfigureServices method, you can register any service agains the DI container of ASP.NET, more concretely, the code above registers a Proxy for IFooService and hooks up the string Foo(int numberOfFoos) method to the function that is provided on lines 7 and 8, cool huh!?

Now we just need to tell the HostBuilder to use this method to configure its services and we’re ready to have that service injected:

using System;
using Microsoft.Extensions.Hosting;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using FuncR;
using System.Linq;

namespace ConsoleToWeb
{
    interface IFooService
    {
        string Foo(int numberOfFoos);
    }

    class Program
    {
        static void Main(string[] args)
        {
            CreateHostBuilder(args).Build().Run();
        }

        private static void ConfigureServices(IServiceCollection services)
        {
            services.AddScopedFunction<IFooService>
                (nameof(IFooService.Foo))
                .Runs<int, string>(numberOfFoos =>
                {
                    var foos = Enumerable.Range(1, numberOfFoos).Select(n => "Foo");
                    return $"{String.Join(", ", foos)}";
                });
        }

        public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                .ConfigureWebHost(
                    webHost => webHost
                        .UseKestrel(kestrelOptions => { kestrelOptions.ListenAnyIP(5005); })
                        .ConfigureServices(ConfigureServices)
                        .Configure(app => app
                            .Run(
                                async context =>
                                {
                                    var numberOfFoos = 5;
                                    // Resolve IFooService here
                                    var fooService = context.RequestServices.GetRequiredService<IFooService>();
                                    await context.Response.WriteAsync(fooService.Foo(numberOfFoos));
                                }
                            )));
    }
}

If we now dotnet run and navigate to http://localhost:5005 we finally see some different output:

Web app

That’s a lot of Foo’s.

And naturally, the Docker example also still works after a rebuild and run:

docker build . --no-cache -t consoletoweb
docker run -p 5005:5005 consoletoweb

Wrapping up

In this article we saw how easy and quickly a console app can be converted into a ASP.NET web app, and we got an introduction to FuncR to speed up our service implementations. The full code, and more is available through GitHub:

Source: https://github.com/merken/ConsoleToWeb

Similar
Dec 25, 2023
Author: Binod Mahto
Testing coverage is the key part of any project development and Testing coverage is not only about the unit testing instead Integration Testing is extremely important to make sure entire system is unbroken & bug free with any change or...
May 23, 2022
Author: Nitesh Singhal
A step by step guide to integrate OpenTelemetry with ASP.Net core and visualize in Jaeger. OpenTelemetry is a collection of tools, APIs, and SDKs. Use it to instrument, generate, collect, and export telemetry data (metrics, logs, and traces) to help...
Jun 26, 2021
We are going to have a look at the steps you need to take to publish an ASP.NET Core application. Then, we are going to have a look on how to set this website up in IIS. Locating the SPA...
Aug 25, 2020
In my 2018 series, we covered EF Core Migrations to explain how to add, remove and apply Entity Framework Core Migrations in an ASP .NET Core web application project. In this article, we’ll continue to look at the newer 2020...
Send message
Type
Email
Your name
*Message