c# - 如何从另一个服务调用 .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,在另一个容器上运行?
解决方案
您必须创建一个网络来通信两个容器
docker network create your-network docker network connect your-network the-service-container
然后,您可以编写 _client.GetStringAsync("http://the-service-container:80/api/basket/entries");
推荐阅读
- c++ - 大高度图插值
- javascript - 如何通过 Google Script 获得每个单独列的单独总和?
- c# - 如何在不同的线程上进行同步调用
- python-3.x - 使用函数拆分一个单词,然后对单词进行上下排列
- javascript - 从我的谷歌电子表格设置电子邮件系统。除了在正文中添加图像之外,我还有其他一切工作
- spring-boot - Spring Cloud Gateway 可以与非异步的微服务一起使用吗?
- c# - 更新数据库并使用数据表和数据适配器
- http - 密码重置 (Parse-server/Heroku) -> {"error":"unauthorized"}
- php - 即使条件为假,PHP Laravel save() 也会保存
- r - 根据 R 中的条件为数据框的单元格着色