Skip to content

ASP.NET Core 2.2 Microservices Basic Tutorial (API GATEWAY + Simple Microservice + Swagger)

Notifications You must be signed in to change notification settings

Arsslensoft/aspnetcore-microservices-tutorial

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

9 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Introduction

ASP.NET Core 2.2 Microservices Basic Tutorial (API GATEWAY + Simple Microservice + Swagger)

In this tutorial i will show you how to create your first microservice like application, in which we will use an API gateway, 2 microservices that handles the CRUD of products and categories.

We will not talk about the microservices communication because its another subject that needs a special tutorial.

Requirements

  • .NET Core 2.2
  • Visual Studio or Visual Studio Code (In this tutorial i will use Visual Studio Code because it is easily accessible by everyone)
  • Kubernetes Cluster with dashboard
  • Private docker registry

Creating the microservices

Lets start by making the microservices

The products microservice

We will start by creating the project

mkdir products 
cd products
dotnet new webapi

Go to the file Controllers\ValuesController.cs rename it to ProductsController and changes it like this

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;

namespace products.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class ProductsController : ControllerBase
    {
        static List<string> products_data = new List<string>();
        // GET api/products
        [HttpGet]
        public ActionResult<IEnumerable<string>> Get()
        {
            return products_data;
        }

        // GET api/products/5
        [HttpGet("{id}")]
        public ActionResult<string> Get(int id)
        {
            return products_data[id];
        }

        // POST api/products
        [HttpPost]
        public void Post([FromBody] string value)
        {
            products_data.Add(value);
        }

        // PUT api/products/5
        [HttpPut("{id}")]
        public void Put(int id, [FromBody] string value)
        {      
             products_data.RemoveAt(id);
             products_data.Insert(id, value);
        }

        // DELETE api/products/5
        [HttpDelete("{id}")]
        public void Delete(int id)
        {
            products_data.RemoveAt(id);
        }
    }
}

After testing with the dotnet run command we will notice that our application will be running under ports 5000 for http and 5001 for https.

The categories microservice

We shall start by going back to the root project folder then we will create another project

mkdir categories 
cd categories
dotnet new webapi

Go to the file Controllers\ValuesController.cs rename it to CategoriesController and changes it like this

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;

namespace categories.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class CategoriesController : ControllerBase
    {
        static List<string> categories_data = new List<string>();
        // GET api/categories
        [HttpGet]
        public ActionResult<IEnumerable<string>> Get()
        {
            return categories_data;
        }

        // GET api/categories/5
        [HttpGet("{id}")]
        public ActionResult<string> Get(int id)
        {
            return categories_data[id];
        }

        // POST api/categories
        [HttpPost]
        public void Post([FromBody] string value)
        {
            categories_data.Add(value);
        }

        // PUT api/categories/5
        [HttpPut("{id}")]
        public void Put(int id, [FromBody] string value)
        {       
             categories_data.RemoveAt(id);
             categories_data.Insert(id, value);
      

        }

        // DELETE api/categories/5
        [HttpDelete("{id}")]
        public void Delete(int id)
        {
            categories_data.RemoveAt(id);
        }
    }
}

After testing with the dotnet run command we will notice that our application will be running under ports 5000 for http and 5001 for https.

Adding swagger support

For both porjects we will add the Swagger support, in this section i am going to do this step for one project then i will replicate it in the other one.

I selected the products project

Adding swagger support for products microservice

Go to products/products.csproj and edit its code

We will add the swagger package reference

    <PackageReference Include="Swashbuckle.AspNetCore" Version="4.0.1" />

under

 <ItemGroup>
    <PackageReference Include="Microsoft.AspNetCore.App" />

then we will execute dotnet restore

the final csproj file will look like this

<Project Sdk="Microsoft.NET.Sdk.Web">
  <PropertyGroup>
    <TargetFramework>netcoreapp2.2</TargetFramework>
    <AspNetCoreHostingModel>InProcess</AspNetCoreHostingModel>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="Microsoft.AspNetCore.App" />
    <PackageReference Include="Swashbuckle.AspNetCore" Version="4.0.1" />
    <PackageReference Include="Microsoft.AspNetCore.Razor.Design" Version="2.2.0" PrivateAssets="All" />
  </ItemGroup>
</Project>

We edit the Startup.cs file, by changing the ConfigureServices and Configure methods.

  • We will disable the https redirection
  • We will use the default developer exception page
  • We will add swagger middleware
        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
            // Swagger
            services.AddSwaggerGen(c =>
            {
                c.SwaggerDoc("v1", new Info { Title = "Products API", Version = "v1" });
            });
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            app.UseDeveloperExceptionPage();
            app.UseMvc();
            // Swagger
            app.UseSwagger();
            app.UseSwaggerUI(c =>
            {
                c.SwaggerEndpoint("/swagger/v1/swagger.json", "Products V1");
            });
        }

and we will add a using reference

using Swashbuckle.AspNetCore.Swagger;

We will finish by editing Program.cs to enable kestrel and specify the listen url. In order to do that we shall replace the CreateWebHostBuilder method by this

      public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
            WebHost.CreateDefaultBuilder(args)
                .UseKestrel()
                .UseUrls("http://0.0.0.0:5000")
                .UseStartup<Startup>();

Testing swagger support

Openning http://localhost:5000/swagger/index.html after a dotnet run will give the results below

Products

Products Swagger Doc

Categories

Categories Swagger Doc

Creating the api gateway

We will start by creating the project

mkdir gateway 
cd gateway
dotnet new webapi

we shall remove the folder ``Controllers`

We will add a package called Ocelot

We will run

dotnet add package Ocelot
dotnet add package MMLib.SwaggerForOcelot

Configuring the gateway

For more information go to https://ocelot.readthedocs.io/en/latest/features/configuration.html

We will start by creating a json file called ocelot.json

knowing that we will be deploying the application to a kubernetes cluster under a namespace called devops-workshop the result dns names for services will be:

  • products.devops-workshop.svc.cluster.local for the products service
  • categories.devops-workshop.svc.cluster.local for the categories service
  • gateway.devops-workshop.svc.cluster.local for the gateway service

the content of ocelot.json will be

{
    "ReRoutes": [
        {
            "DownstreamPathTemplate": "/api/categories/{everything}",
            "DownstreamScheme": "http",
            "DownstreamHostAndPorts": [
                    {
                        "Host": "categories.devops-workshop.svc.cluster.local",
                        "Port": 5000
                    }
                ],
            "UpstreamPathTemplate": "/categories/{everything}",
            "SwaggerKey": "categories"
        },
        {
            "DownstreamPathTemplate": "/api/products/{everything}",
            "DownstreamScheme": "http",
            "DownstreamHostAndPorts": [
                    {
                        "Host": "products.devops-workshop.svc.cluster.local",
                        "Port": 5000
                    }
                ],
            "UpstreamPathTemplate": "/products/{everything}",
            "SwaggerKey": "products"
        }
    ],
    "SwaggerEndPoints": [
        {
        "Key": "products",
        "Config": [
                {
                "Name": "Products API",
                "Version": "v1",
                "Url": "http://products.devops-workshop.svc.cluster.local:5000/swagger/v1/swagger.json"
                }
            ]
        },
        {
        "Key": "categories",
        "Config": [
                {
                "Name": "Categories API",
                "Version": "v1",
                "Url": "http://categories.devops-workshop.svc.cluster.local:5000/swagger/v1/swagger.json"
                }
            ]
        }
    ],
    "GlobalConfiguration": {

    }
}

Then we change the Program.cs file

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;

namespace gateway
{
    public class Program
    {
        public static void Main(string[] args)
        {
            CreateWebHostBuilder(args).Build().Run();
        }

        public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
            WebHost.CreateDefaultBuilder(args)
                .ConfigureAppConfiguration((hostingContext, config) =>
                {
                    config
                        .SetBasePath(hostingContext.HostingEnvironment.ContentRootPath)
                        .AddJsonFile("appsettings.json", true, true)
                        .AddJsonFile($"appsettings.{hostingContext.HostingEnvironment.EnvironmentName}.json", true, true)
                        .AddJsonFile("ocelot.json")
                        .AddEnvironmentVariables();
                })
                .UseKestrel()
                .UseUrls("http://0.0.0.0:5000")
                .UseStartup<Startup>();
    }
}

We open the Startup.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using MMLib.SwaggerForOcelot.Configuration;
using Ocelot.Configuration;
using Ocelot.DependencyInjection;
using Ocelot.Middleware;

namespace gateway
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
            services.AddOcelot();
            services.AddSwaggerForOcelot(Configuration);
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            app.UseDeveloperExceptionPage();
            app.UseMvc();
            app.UseSwaggerForOcelotUI(Configuration,  opt => { opt.EndPointBasePath = "/swagger/docs"; }).UseOcelot().Wait(); 
        }
    }
}

Testing api gateway

Openning http://localhost:5000/swagger/index.html after a dotnet run will give the results below

Gateway Swagger Doc

Dockerizing the application

Creating docker files

for each project create a Dockerfile the base file content will include (in this case we have the products docker file, just replace products by categories or gateway)

FROM microsoft/dotnet:2.2-aspnetcore-runtime-stretch-slim AS base
WORKDIR /app
EXPOSE 5000

FROM microsoft/dotnet:2.2-sdk-stretch AS build
WORKDIR /src
COPY ["gateway.csproj", "gateway/"]
RUN dotnet restore "gateway/gateway.csproj"
COPY . .
WORKDIR "/src/gateway"
RUN dotnet build "gateway.csproj" -c Release -o /app

FROM build AS publish
RUN dotnet publish "gateway.csproj" -c Release -o /app

FROM base AS final
WORKDIR /app
COPY --from=publish /app .
ENTRYPOINT ["dotnet", "gateway.dll"]

Docker compose build file

This file will be used by the Azure DevOps Pipelines

version: '3'
services:
  gateway:
    build:
      context: ./gateway
      dockerfile: Dockerfile
  categories:
    build:
      context: ./categories
      dockerfile: Dockerfile   
  products:
    build:
      context: ./products
      dockerfile: Dockerfile         

Azure DevOps Pipeline

Go to https://azure.microsoft.com/en-us/services/devops/

Create an azure devops organisation, then create a project Azure

Creating YAML Files for kubernetes

We will work under a namespace called devops-workshop

The yaml file below will define the specs of the deployments and services

apiVersion: apps/v1 # for versions before 1.9.0 use apps/v1beta2
kind: Deployment
metadata:
  name: products
  namespace: devops-workshop
spec:
  strategy:
    type: Recreate
  selector:
    matchLabels:
      app: products
  replicas: 3 # tells deployment to run 1 pods matching the template
  template: # create pods using pod definition in this template
    metadata:
      labels:
        app: products
    spec:
      containers:
        - name: products
          image: "docker.gomycode.tn:443/devopsworkshop_products:latest"
          imagePullPolicy: Always
          ports:
            - containerPort: 5000
      imagePullSecrets:
        - name: mysecret

---
apiVersion: v1
kind: Service
metadata:
  name: products
  namespace: devops-workshop
  labels:
    app: products
spec:
  ports:
    - name: http
      port: 5000
      protocol: TCP
      targetPort: 5000
  selector:
    app: products
  type: ClusterIP

---
apiVersion: apps/v1 # for versions before 1.9.0 use apps/v1beta2
kind: Deployment
metadata:
  name: categories
  namespace: devops-workshop
spec:
  strategy:
    type: Recreate
  selector:
    matchLabels:
      app: categories
  replicas: 3 # tells deployment to run 1 pods matching the template
  template: # create pods using pod definition in this template
    metadata:
      labels:
        app: categories
    spec:
      containers:
        - name: categories
          image: "docker.gomycode.tn:443/devopsworkshop_categories:latest"
          imagePullPolicy: Always
          ports:
            - containerPort: 5000
      imagePullSecrets:
        - name: mysecret
---
apiVersion: v1
kind: Service
metadata:
  name: categories
  namespace: devops-workshop
  labels:
    app: categories
spec:
  ports:
    - name: http
      port: 5000
      protocol: TCP
      targetPort: 5000
  selector:
    app: categories
  type: ClusterIP

---
apiVersion: apps/v1 # for versions before 1.9.0 use apps/v1beta2
kind: Deployment
metadata:
  name: gateway
  namespace: devops-workshop
spec:
  strategy:
    type: Recreate
  selector:
    matchLabels:
      app: gateway
  replicas: 3 # tells deployment to run 1 pods matching the template
  template: # create pods using pod definition in this template
    metadata:
      labels:
        app: gateway
    spec:
      containers:
        - name: gateway
          image: "docker.gomycode.tn:443/devopsworkshop_gateway:latest"
          imagePullPolicy: Always
          ports:
            - containerPort: 5000
      imagePullSecrets:
        - name: mysecret
---
apiVersion: v1
kind: Service
metadata:
  name: gateway
  namespace: devops-workshop
  labels:
    app: gateway
spec:
  ports:
    - name: http
      port: 5000
      protocol: TCP
      targetPort: 5000
  selector:
    app: gateway
  type: ClusterIP

Azure Pipeline

Video

https://www.youtube.com/watch?v=YNvy8AqTzEI

About

ASP.NET Core 2.2 Microservices Basic Tutorial (API GATEWAY + Simple Microservice + Swagger)

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages