Поиск  
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#
Автор:
Источник:
Просмотров:
1753

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

Похожее
Jul 16, 2020
Author: Kannan Eswar
In this blog, I am going to provide a walkthrough on developing REST APIs using ASP.NET Core 3.1, connecting with existing databases using Entity Framework, creating a JWT token, and securing APIs. I am going to develop a sample application...
Oct 26, 2023
Author: Matt Bentley
How to implement CQRS in ASP.NET using MediatR. A guided example using CQRS with separate Read and Write models using Enity Framework Core for Commands and Dapper for Queries. When people think about CQRS they often think about complex, event-driven,...
Nov 24, 2022
Author: Chandan Das
Blazor and Razor have a healthy following in the web UI dev community, especially among developers who primarily work within .NET Core. However, the confusion between these two can be off-putting for some developers, especially beginners. This post discusses the...
Nov 16, 2022
Author: Vipin Sharma
Need to communicate with multiple DBs in one application?? Usually we don’t face such scenarios. However if such requirement comes in picture, we will see how easy it is to communicate with multiple DBs using repository pattern in .Net Core...
Написать сообщение
Тип
Почта
Имя
*Сообщение