带有ASP.NET Core 7和MongoDB的REST API
#api #休息 #csharp #mongodb

这是较早的REST API with ASP.NET Core 7 and InMemory Store后期的延续。在本教程中,我将把服务扩展到在MongoDB中存储数据,我将使用MongoDB Community Server Docker Image进行此示例。我将使用Docker运行mongodb。

设置数据库服务器

我将使用Docker-Compose在Docker容器中运行MongoDB。这将使我们添加更多的服务,例如,我们的REST API在例如REDIS服务器用于分布式缓存。

让我们从右键单击Visual Studio中的解决方案名称并添加新文件开始添加新文件。我喜欢将文件命名为docker-compose.dev-env.yml,请随时按照您的要求命名。添加以下内容以添加电影REST API的数据库实例。

version: '3.7'

services:
  movies.db:
    image: mongodb/mongodb-community-server:6.0-ubi8
    environment:
      - MONGODB_INITDB_ROOT_USERNAME=root
      - MONGODB_INITDB_ROOT_PASSWORD=Password123
      - MONGO_INITDB_DATABASE=Movies
    volumes:
      - moviesdbdata:/data/db
    ports:
      - "27017:27017"

volumes:
  moviesdbdata:

在docker-compose文件为位置并执行以下命令以启动数据库服务器的解决方案的根部打开一个终端。

docker-compose -f docker-compose.dev-env.yml up -d

数据库迁移

我们不会对MongoDB进行任何数据库/模式迁移,因为它的NOSQL数据库,here是关于stackoverflow在此主题上的一个很好的讨论。我们不需要此示例的任何迁移,但是,如果需要出现,并且没有架构迁移脚本的强用例,我希望选择支持多个模式相互矛盾的途径并在需要时进行更新。

MongoDB电影商店

设置

  • 让我们首先添加Nuget软件包
dotnet add package MongoDB.Driver --version 2.19.2
dotnet add package MongoDB.Bson --version 2.19.2
  • 更新IMovieStore并制作所有方法async
  • 更新Controller以使方法asyncawait调用存储方法
  • 更新InMemoryMoviesStore以制作方法async

配置

添加一个新文件夹Configuration并添加MoviesStoreConfiguration.cs文件。

public class MoviesStoreConfiguration
{
    public string ConnectionString { get; set; } = null!;
    public string DatabaseName { get; set; } = null!;
    public string MoviesCollectionName { get; set; } = null!;
}

在appsettings.json
中添加以下

"MoviesStoreConfiguration": {
    "ConnectionString": "mongodb://localhost:27017",
    "DatabaseName": "MoviesStore",
    "BooksCollectionName": "Movies"
  }

Program.cs中的依赖项注入容器中注册配置

// Add services to the container.
builder.Services.Configure<MoviesStoreConfiguration>(
    builder.Configuration.GetSection(nameof(MoviesStoreConfiguration)));

班级和构造函数

Store下添加一个新文件夹,我将其命名为Mongo,并添加一个名为MongoMoviesStore.cs的文件。该类将接受IOptions<MoviesStoreConfiguration>作为参数,我们将使用该参数连接到MongoDB,获取数据库和Mongocollection。

private readonly IMongoCollection<Movie> moviesCollection;

public MongoMoviesStore(IOptions<MoviesStoreConfiguration> moviesStoreConfiguration)
{
    var mongoClient = new MongoClient(moviesStoreConfiguration.Value.ConnectionString);
    var mongoDatabase = mongoClient.GetDatabase(moviesStoreConfiguration.Value.DatabaseName);

    moviesCollection = mongoDatabase.GetCollection<Movie>(moviesStoreConfiguration.Value.MoviesCollectionName);
}

我已经在appsettings.json配置文件中指定了这一点。这对于开发是可以接受的,但切勿将生产/雄鹿连接字符串放在配置文件中。这可以放在安全的金库中,例如AWS参数存储或Azure KeyVault,可以从应用程序访问。 CD管道也可以配置为从安全位置加载此值,并将运行应用程序的容器设置为环境变量。

创造

我们创建了一个Movie的新实例,然后我们使用moviesCollection插入新唱片,我们正在处理MongoWriteException并抛出我们的自定义DuplicateKeyException,如果exception的WriteError.Code11000

创建函数看起来像

public async Task Create(CreateMovieParams createMovieParams)
{
    var movie = new Movie(
        createMovieParams.Id,
        createMovieParams.Title,
        createMovieParams.Director,
        createMovieParams.TicketPrice,
        createMovieParams.ReleaseDate,
        DateTime.UtcNow,
        DateTime.UtcNow);
    try
    {
        await moviesCollection.InsertOneAsync(movie);
    }
    catch (MongoWriteException ex)
    {
        if (ex.WriteError.Category == ServerErrorCategory.DuplicateKey &&
            ex.WriteError.Code == 11000)
        {
            throw new DuplicateKeyException();
        }

        throw;
    }
}

得到所有

我们使用MovieCollection查找所有记录。

public async Task<IEnumerable<Movie>> GetAll()
{
    return await moviesCollection.Find(_ => true).ToListAsync();
}

GetByid

我们在文档的Id属性上使用MovieCollection并过滤到与传递参数匹配的第一个或默认实例。

public async Task<Movie?> GetById(Guid id)
{
    return await moviesCollection.Find(x => x.Id == id).FirstOrDefaultAsync();
}

更新

我们使用MovieCollection并使用UpdateOneAsync使用id参数的方法过滤记录,并将所有可更新属性传递为UpdateDefinition

更新功能看起来像

public async Task Update(Guid id, UpdateMovieParams updateMovieParams)
{
    await moviesCollection.UpdateOneAsync(
        x => x.Id == id,
        Builders<Movie>.Update.Combine(
            Builders<Movie>.Update.Set(x => x.Title, updateMovieParams.Title),
            Builders<Movie>.Update.Set(x => x.Director, updateMovieParams.Director),
            Builders<Movie>.Update.Set(x => x.TicketPrice, updateMovieParams.TicketPrice),
            Builders<Movie>.Update.Set(x => x.ReleaseDate, updateMovieParams.ReleaseDate),
            Builders<Movie>.Update.Set(x => x.UpdatedAt, DateTime.UtcNow)
        ));
}

删除

我们使用MovieCollection并使用DeleteOneAsync收集方法使用id删除记录。

public async Task Delete(Guid id)
{
    await moviesCollection.DeleteOneAsync(x => x.Id == id);
}

请注意,我们不会像在InMemoryMoviesStore中那样抛出RecordNotFoundException异常,原因是试图删除具有不存在密钥的记录不是错误的。

设置依赖注入

最后一步是设置依赖项注入容器以连接新创建的商店。更新Program.cs,如下所示

// builder.Services.AddSingleton<IMoviesStore, InMemoryMoviesStore>();
builder.Services.AddSingleton<IMoviesStore, MongoMoviesStore>();

为了简单起见,我已禁用了InMemoryMoviesStore,我们可以添加一个配置,并根据该决定在运行时使用哪种服务。这可能是一个很好的练习,但是我们实际上不这样做。但是,对于流量重型服务,使用中音或分布式缓存用于缓存结果以提高性能。

测试

我没有为本教程添加任何单元或集成测试,也许是以下教程。但是所有端点可以通过运行应用程序或使用Postman进行测试。

来源

演示应用程序的源代码托管在blog-code-samples存储库中的GitHub上。

参考

没有特定顺序