首页 > 解决方案 > 如何从另一个服务调用 .NET Core Web Api,两者都在单独的 docker 容器中运行

问题描述

在我的场景中,我有两个在两个单独的 docker 容器上运行的 .NET Core Web API。

第一个服务被调用Catalog.API,这是控制器的代码:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Catalog.API.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;

namespace Catalog.API.Controllers
{
    [ApiController]
    [Route("api/[controller]")]
    public class CatalogController : ControllerBase
    {
        private readonly CatalogItemContext _context;

        public CatalogController(CatalogItemContext context)
        {
            _context = context;
        }


        [Route("items")]
        [HttpGet]
        public async Task<ActionResult<List<CatalogItem>>> GetItemsAsync()
        {
            var items = await _context.CatalogItems.ToListAsync();
            if (items == null)
            {
                return NotFound();
            }
            return items;
        }

        [Route("items/{id}")]
        [HttpGet]
        public async Task<ActionResult<CatalogItem>> GetItemByIdAsync(int id)
        {
            var item = await _context.CatalogItems.SingleOrDefaultAsync(model => model.Id == id);
            if (item == null)
            {
                return NotFound();
            }
            return item;
        }

        [HttpPost]
        [Route("items")]
        public async Task<ActionResult> CreateProductAsync([FromBody] CatalogItem product)
        {
            var item = new CatalogItem(product.Name, product.Price, product.Count);
            _context.CatalogItems.Add(item);
            await _context.SaveChangesAsync();
            return CreatedAtAction(nameof(GetItemByIdAsync), new {id = item.Id}, null);
        }

        [HttpPut]
        [Route("items")]
        public async Task<ActionResult> UpdateProductAsync([FromBody] CatalogItem productToUpdate)
        {
            try
            {
                _context.Entry(productToUpdate).State = EntityState.Modified;
                await _context.SaveChangesAsync();
                return CreatedAtAction(nameof(GetItemByIdAsync), new {id = productToUpdate.Id}, null);
            }
            catch (Exception ex)
            {
                return BadRequest();
            }
        }

        [HttpPost]
        [Route("items/{id}")]
        public async Task<ActionResult> DeleteProductById(int id)
        {
            var itemToDelete = _context.CatalogItems.SingleOrDefault(model => model.Id == id);
            if (itemToDelete == null)
            {
                return NotFound();
            }

            _context.CatalogItems.Remove(itemToDelete);
            await _context.SaveChangesAsync();
            return NoContent();
        }
    }
}

正如你所看到的,我有一些基本的方法,到目前为止没有什么特别的。此服务正在运行localhost:80(例如,http://localhost:80/api/catalog/items

这是 Catalog.API 的 dockerfile,通过 docker-compose.yml 调用:

FROM mcr.microsoft.com/dotnet/core/aspnet:2.2 AS base
WORKDIR /app
EXPOSE 80

FROM mcr.microsoft.com/dotnet/core/sdk:2.2 AS build
WORKDIR /src

COPY src/Services/Catalog/Catalog.API/Catalog.API.csproj /src/csproj-files/

WORKDIR ./csproj-files
RUN dotnet restore


WORKDIR /src

COPY . .
WORKDIR /src/src/Services/Catalog/Catalog.API/
RUN dotnet publish -c Release -o /app

FROM build AS publish

FROM base as final
WORKDIR /app
COPY --from=publish /app .
ENTRYPOINT ["dotnet", "Catalog.API.dll"]

第二个服务被调用Basket.API,这是控制器:

using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Basket.API.Models;
using Basket.API.Services;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;

namespace Basket.API.Controllers
{
    [ApiController]
    [Route("api/[controller]")]
    public class BasketController : ControllerBase
    {
        private readonly BasketContext _context;
        private readonly IBasketService _basketService;

        public BasketController(BasketContext context, IBasketService basketService)
        {
            _context = context;
            _basketService = basketService;
        }

        [HttpGet]
        [Route("entries")]
        public async Task<ActionResult<List<Models.Basket>>> GetAllBasketAsync()
        {
            var basketList = await _context.UserBaskets.Include(basket => basket.Items).ToListAsync(); //include needed to load Items List
            if (basketList == null)
            {
                return NoContent();
            }

            return basketList;
        }

        [HttpGet]
        [Route("entries/{id}")]
        public async Task<ActionResult<Models.Basket>> GetBasketByIdAsync(int id)
        {
            var basket = await _context.UserBaskets.Where(b => b.UserId == id).Include(m => m.Items).SingleOrDefaultAsync();
            if (basket == null)
            {
                return NoContent();
            }

            return basket;
        }

        [HttpGet]
        [Route("test")]
        public async Task<ActionResult> TestCall()
        {
            var test1 = await _basketService.GetBasketByIdAsync(1);
            return Ok(test1);
        }
    }
}

在控制器内部,我使用了一个名为 BasketService 的类,该类被注入到Startup.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Basket.API.Models;
using Basket.API.Services;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;

namespace Basket.API
{
    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.AddDbContext<BasketContext>(builder =>
            {
                builder.UseSqlServer(Configuration.GetConnectionString("Default"));
            });

            services.AddHttpClient<IBasketService, BasketService>();
            services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
                app.UseHsts();
            }

            app.UseHttpsRedirection();
            app.UseMvc();
        }
    }
}

这是代码BasketService.cs

using System.Collections.Generic;
using System.Net.Http;
using System.Threading.Tasks;
using Newtonsoft.Json;

namespace Basket.API.Services
{
    public class BasketService : IBasketService
    {
        private readonly HttpClient _client;

        public BasketService(HttpClient client)
        {
            _client = client;
        }

        public async Task<IEnumerable<Models.Basket>> GetAllBasketAsync()
        {
            var stringContent = await _client.GetStringAsync("http://localhost:80/api/basket/entries");
            return JsonConvert.DeserializeObject<List<Models.Basket>>(stringContent);
        }

        public async Task<Models.Basket> GetBasketByIdAsync(int id)
        {
            var stringContent = await _client.GetStringAsync("http://localhost:80/api/basket/entries/" + id);
            return JsonConvert.DeserializeObject<Models.Basket>(stringContent);
        }
    }
}

Basket.API正在运行,这localhost:81是 Dockerfile,它也被 docker-compose.yml 调用:

FROM mcr.microsoft.com/dotnet/core/aspnet:2.2 AS base
WORKDIR /app
EXPOSE 81

FROM mcr.microsoft.com/dotnet/core/sdk:2.2 AS build
WORKDIR /src

COPY src/Services/Basket/Basket.API/Basket.API.csproj /src/csproj-files/

WORKDIR ./csproj-files
RUN dotnet restore

WORKDIR /src

COPY . .
WORKDIR /src/src/Services/Basket/Basket.API/
RUN dotnet publish -c Release -o /app

FROM build AS publish

FROM base as final
WORKDIR /app
COPY --from=publish /app .
ENTRYPOINT ["dotnet", "Basket.API.dll"]

码头工人-compose.yml:

version: '3.4'

services:

  sqldata:
    ports:
      - 1433:1433
    image: mcr.microsoft.com/mssql/server:2017-latest-ubuntu
    environment:
      - ACCEPT_EULA=Y
      - SA_PASSWORD=Pass@word

  catalog.api:
    ports:
      - 80:80
    build:
      context: .
      dockerfile: src/Services/Catalog/Catalog.API/Dockerfile
    depends_on:
      - sqldata

  basket.api:
    ports:
      - 81:80
    build:
      context: .
      dockerfile: src/Services/Basket/Basket.API/Dockerfile
    depends_on:
      - sqldata

我的目标是通过使用 http 客户端Catalog.API来调用课程。BasketService.cs正如您可能已经注意到的那样,我尝试使用调用 Web Apilocalhost:80但这不起作用,因为两个 API 都在不同的容器上运行,并且 localhost 仅对 Basket 容器有效(我有 BasketService 类)。我不确定如何正确调用 Catalog.API。我可以使用 docker inspect 手动查找容器的 IP,但是每次我重新启动或重建容器时,IP 都会更改。所以这不是一个好方法。使用服务名称不起作用。

解决此问题的最佳方法是什么?我应该使用哪种方式调用 Api,在另一个容器上运行?

标签: c#dockerasp.net-core-webapi

解决方案


您必须创建一个网络来通信两个容器

docker network create your-network docker network connect your-network the-service-container

然后,您可以编写 _client.GetStringAsync("http://the-service-container:80/api/basket/entries");


推荐阅读