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:
1417

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
Jan 2, 2023
Author: Jaydeep Patil
In this article, we are going to discuss the working of CQRS and MediatR patterns and step-by-step implementation using .NET Core 6 Web API. Agenda Introduction of CQRS Pattern When to use CQRS MediatR Step-by-step Implementation Prerequisites Visual Studio 2022...
Aug 14
Author: Yasith Wimukthi
In today’s fast-paced development environment, Docker has become a vital tool for developers, simplifying the process of creating, deploying, and running applications in containers. However, to truly harness the power of Docker, it’s crucial to follow best practices that ensure...
Jul 8
In this article, let’s learn about how to perform file upload in ASP.NET Core 6. The file for upload can be of any format like image (jpg, BMP, gif, etc), text file, XML file, CSV file, PDF file, etc. We...
Aug 12
Author: Crafting-Code
Containerizing and deploying ASP.NET Core applications Containerization has transformed the way applications are developed, deployed, and managed in the modern software industry. Docker, in particular, has become a pivotal tool, simplifying the packaging and deployment of applications, including ASP.NET Core...
Send message
Type
Email
Your name
*Message