65.9K
CodeProject 正在变化。 阅读更多。
Home

在 .NET Core WebAPI 中使用 MongoDB .NET 驱动程序

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.89/5 (52投票s)

2016 年 11 月 2 日

CPOL

9分钟阅读

viewsIcon

174075

downloadIcon

2134

如何逐步构建一个带有最新 MongoDB 驱动程序的 ASP.NET Core WebAPI (.NET Core 2.0)。该项目支持对 MongoDB 的所有异步请求。

源代码也可以从 GitHub 访问 -> https://github.com/fpetru/WebApiMongoDB

更新于:2017 年 11 月 15 日

根据 Matthew 的评论,我已更新 INoteRepository 接口,使其不与 MongoDB 库耦合。

请在文章末尾找到完整的更新列表

我们试图实现什么

问题/解决方案格式使人们更容易理解如何构建事物,并提供即时反馈。基于这个想法,这篇博客文章将逐步介绍如何构建

“一个网络应用程序,以简单的方式存储您的想法,添加文本笔记,无论是来自桌面还是移动设备,具有以下几个特点:运行速度快,随写随存,并且合理可靠和安全。”

这篇博客文章将以最简单的方式实现后端、WebApi 和数据库访问。下一篇博客文章将介绍前端,使用 Angular 2:Angular 2 与 ASP.NET Core Web API – 构建一个简单的笔记本应用程序 – 第 1 部分

涵盖的主题

  • 技术栈
  • 配置模型
  • 选项模型
  • 依赖注入
  • MongoDB – 使用 MongoDB C# 驱动程序 v.2 进行安装和配置
  • 构建一个完整的 ASP.NET WebApi 项目,异步连接到 MongoDB
  • 启用跨域调用 (CORS)

您可能也对此感兴趣

技术栈

ASP.NET Core Web API 最大的优势在于它可以作为 HTTP 服务使用,并且可以被任何客户端应用程序订阅,从桌面到移动设备,并且可以安装在 Windows、macOS 或 Linux 上。

MongoDB 是一个流行的 NoSQL 数据库,是 Web API 的出色后端。这些更适合文档存储类型,而不是关系数据库。本博客将介绍如何构建一个异步连接到 MongoDB 的 .NET Core Web API,并全面支持 HTTP GET、PUT、POST 和 DELETE。

安装

以下是所有需要安装的东西

创建 ASP.NET WebApi 项目

启动 Visual Studio,然后访问 文件 > 新建项目 > .Net Core > ASP.NET Core Web 应用程序。

然后选择带有 ASP.NET Core 2.0Web API

配置

配置支持多种文件格式(JSON、XML 或 INI)。默认情况下,WebApi 项目模板启用 JSON 格式。在设置文件中,顺序很重要,并包含复杂的结构。这是一个带有两级数据库连接设置结构的示例。
AppSettings.json – 更新文件

{
  "MongoConnection": {
    "ConnectionString": "mongodb://admin:abc123!@localhost",
    "Database": "NotesDb"
  },

  "Logging": {
    "IncludeScopes": false,
    "LogLevel": {
      "Default": "Debug",
      "System": "Information",
      "Microsoft": "Information"
    }
  }
}

依赖注入和选项模型

构造函数注入是实现依赖注入 (DI) 最常见的方法之一,尽管不是唯一的方法。ASP.NET Core 在其解决方案中使用构造函数注入,因此我们也将使用它。ASP.NET Core 项目有一个 Startup.cs 文件,它配置了我们的应用程序将运行的环境。Startup.cs 文件还将服务放入 ASP.NET Core 的服务层,这使得依赖注入成为可能。

为了映射自定义数据库连接设置,我们将添加一个新的 Settings 类。

namespace NotebookAppApi.Model
{
    public class Settings
    {
        public string ConnectionString;
        public string Database;
    }

我们这样修改 Startup.cs 以将设置注入到选项访问器模型中

public void ConfigureServices(IServiceCollection services)
{
    // Add framework services.
    services.AddMvc();

    services.Configure<Settings>(options =>
    {
        options.ConnectionString = Configuration.GetSection("MongoConnection:ConnectionString").Value;
        options.Database = Configuration.GetSection("MongoConnection:Database").Value;
    });

在项目后面,设置将通过 IOptions 接口访问

IOptions<Settings>

MongoDB 配置

安装 MongoDB 后,您需要配置访问权限以及数据所在位置。

为此,请在本地创建一个名为 mongod.cfg 的文件。这将包含 MongoDB 服务器数据文件夹的路径设置,以及 MongoDB 日志文件的路径。请使用您自己的设置更新这些本地路径

systemLog:
  destination: file
  path: "C:\\tools\\mongodb\\db\\log\\mongo.log"
  logAppend: true
storage:
  dbPath: "C:\\tools\\mongodb\\db\\data"
security:
  authorization: enable

命令提示符中运行下一行。这将启动 MongoDB 服务器,指向已创建的配置文件(如果服务器安装在自定义文件夹中,请先更新该命令)

"C:\Program Files\MongoDB\Server\3.2\bin\mongod.exe" --config C:\Dev\Data.Config\mongod.cf

服务器启动后(您可以在日志文件中看到详细信息),在命令提示符中运行 mongo.exe。下一步是将 administrator 用户添加到数据库中。运行带完整路径的 mongodb(例如:“C:\Program Files\MongoDB\Server\3.2\bin\mongo.exe”)。

然后将以下代码复制粘贴到控制台中

use admin
db.createUser(
  {
	user: "admin",
	pwd: "abc123!",
	roles: [ { role: "root", db: "admin" } ]
  }
);
exit;

MongoDB .NET 驱动程序

要连接到 MongoDB,请通过 Nuget 添加名为 MongoDB.Driver 的包。这是适用于 .NET 的新官方驱动程序,完全支持 ASP.NET Core 应用程序。

模型

与笔记本中每个条目关联的模型类 (POCO) 如下所示

using System;
using MongoDB.Bson.Serialization.Attributes;

namespace NotebookAppApi.Model
{
    public class Note
    {
        [BsonId]
        public string Id { get; set; }
        public string Body { get; set; } = string.Empty;
        public DateTime UpdatedOn { get; set; } = DateTime.Now;
        public DateTime CreatedOn { get; set; } = DateTime.Now;
        public int UserId { get; set; } = 0;
    }
}

定义数据库上下文

为了将访问数据库的函数放在一个不同的位置,我们将添加一个 NoteContext 类。这将使用上面定义的 Settings

public class NoteContext
{
    private readonly IMongoDatabase _database = null;

    public NoteContext(IOptions<Settings> settings)
    {
        var client = new MongoClient(settings.Value.ConnectionString);
        if (client != null)
            _database = client.GetDatabase(settings.Value.Database);
    }

    public IMongoCollection<Note> Notes
    {
        get
        {
            return _database.GetCollection<Note>("Note");
        }
    }
}

添加存储库

使用存储库接口,我们将实现管理 Note 所需的函数。这些函数也将使用依赖注入 (DI) 以便从应用程序(例如控制器部分)轻松访问

public interface INoteRepository
{
    Task<IEnumerable<Note>> GetAllNotes();
    Task<Note> GetNote(string id);
    Task AddNote(Note item);
    Task<bool> RemoveNote(string id);
    Task<bool> UpdateNote(string id, string body);
    Task<bool> UpdateNoteDocument(string id, string body);
    Task<bool> RemoveAllNotes();
}

数据库访问将是异步的。我们在此使用新驱动程序,它提供完整的异步堆栈。

举个例子:要获取所有笔记,我们发出一个异步请求

public async Task<IEnumerable<Note>> GetAllNotes()
{
    return await _context.Notes.Find(_ => true).ToListAsync();
}

以下是所有基本 CRUD 操作的完整实现

public class NoteRepository : INoteRepository
{
    private readonly NoteContext _context = null;

    public NoteRepository(IOptions<Settings> settings)
    {
        _context = new NoteContext(settings);
    }

    public async Task<IEnumerable<Note>> GetAllNotes()
    {
        try
        {
            return await _context.Notes
                    .Find(_ => true).ToListAsync();
        }
        catch (Exception ex)
        {
            // log or manage the exception
            throw ex;
        }
    }

    public async Task<Note> GetNote(string id)
    {
        var filter = Builders<Note>.Filter.Eq("Id", id);

        try
        {
            return await _context.Notes
                            .Find(filter)
                            .FirstOrDefaultAsync();
        }
        catch (Exception ex)
        {
            // log or manage the exception
            throw ex;
        }
    }

    public async Task AddNote(Note item)
    {
        try
        {
            await _context.Notes.InsertOneAsync(item);
        }
        catch (Exception ex)
        {
            // log or manage the exception
            throw ex;
        }
    }

    public async Task<bool> RemoveNote(string id)
    {
        try
        {
            DeleteResult actionResult = await _context.Notes.DeleteOneAsync(
                    Builders<Note>.Filter.Eq("Id", id));

            return actionResult.IsAcknowledged 
                && actionResult.DeletedCount > 0;
        }
        catch (Exception ex)
        {
            // log or manage the exception
            throw ex;
        }
    }

    public async Task<bool> UpdateNote(string id, string body)
    {
        var filter = Builders<Note>.Filter.Eq(s => s.Id, id);
        var update = Builders<Note>.Update
                        .Set(s => s.Body, body)
                        .CurrentDate(s => s.UpdatedOn);

        try
        {
            UpdateResult actionResult
                 = await _context.Notes.UpdateOneAsync(filter, update);

            return actionResult.IsAcknowledged
                && actionResult.ModifiedCount > 0;
        }
        catch (Exception ex)
        {
            // log or manage the exception
            throw ex;
        }
    }

    public async Task<bool> UpdateNote(string id, Note item)
    {
        try
        {
            ReplaceOneResult actionResult 
                = await _context.Notes
                                .ReplaceOneAsync(n => n.Id.Equals(id)
                                        , item
                                        , new UpdateOptions { IsUpsert = true });
            return actionResult.IsAcknowledged
                && actionResult.ModifiedCount > 0;
        }
        catch (Exception ex)
        {
            // log or manage the exception
            throw ex;
        }
    }

    public async Task<bool> RemoveAllNotes()
    {
        try
        {
            DeleteResult actionResult 
                = await _context.Notes.DeleteManyAsync(new BsonDocument());

            return actionResult.IsAcknowledged
                && actionResult.DeletedCount > 0;
        }
        catch (Exception ex)
        {
            // log or manage the exception
            throw ex;
        }
    }
}

为了使用 DI 模型访问 NoteRepository,我们在 ConfigureServices 中添加一行

services.AddTransient<INoteRepository, NoteRepository>()

其中

  • 瞬态:每次创建。
  • 作用域:每个请求仅创建一次。
  • 单例:首次请求时创建。每次后续请求都使用首次创建的实例。

添加主控制器

首先,我们介绍主控制器。它提供了所有可供外部应用程序使用的 CRUD 接口。

[Route("api/[controller]")]
public class NotesController : Controller
{
    private readonly INoteRepository _noteRepository;

    public NotesController(INoteRepository noteRepository)
    {
        _noteRepository = noteRepository;
    }

    // GET: notes/notes
    [HttpGet]
    public Task<string> Get()
    {
        return GetNoteInternal();
    }

    private async Task<string> GetNoteInternal()
    {
        var notes = await _noteRepository.GetAllNotes();
        return JsonConvert.SerializeObject(notes);
    }

    // GET api/notes/5
    [HttpGet("{id}")]
    public Task<string> Get(string id)
    {
        return GetNoteByIdInternal(id);
    }

    private async Task<string> GetNoteByIdInternal(string id)
    {
        var note = await _noteRepository.GetNote(id) ?? new Note();
        return JsonConvert.SerializeObject(note);
    }

    // POST api/notes
    [HttpPost]
    public void Post([FromBody]string value)
    {
        _noteRepository.AddNote(new Note() 
           { Body = value, CreatedOn = DateTime.Now, UpdatedOn = DateTime.Now });
    }

    // PUT api/notes/5
    [HttpPut("{id}")]
    public void Put(string id, [FromBody]string value)
    {
        _noteRepository.UpdateNote(id, value);
    }

    // DELETE api/notes/5
    public void Delete(string id)
    {
        _noteRepository.RemoveNote(id);
    }

添加管理控制器

这将是一个专门用于管理任务的控制器(我们用来使用一些虚拟数据初始化数据库)。在实际项目中,我们应该非常谨慎地使用这种接口。对于仅用于开发和快速测试的目的,这种方法可能很方便。

要使用它,我们只需在浏览器中添加 URL。运行下面的代码,将自动创建完整的设置(例如,新数据库,新集合,示例记录)。我们可以使用 https://:5000/system/inithttps://:53617/api/system/init(当使用 IIS 时)。我们甚至可以扩展这个想法,添加更多命令。然而,如上所述,这类场景应仅用于开发,绝不能部署到生产环境。

[Route("api/[controller]")]
public class SystemController : Controller
{
    private readonly INoteRepository _noteRepository;

    public SystemController(INoteRepository noteRepository)
    {
        _noteRepository = noteRepository;
    }

    // Call an initialization - api/system/init
    [HttpGet("{setting}")]
    public string Get(string setting)
    {
        if (setting == "init")
        {
            _noteRepository.RemoveAllNotes();
            _noteRepository.AddNote(new Note() { Id = "1", Body = "Test note 1", 
                          CreatedOn = DateTime.Now, 
                          UpdatedOn = DateTime.Now, UserId = 1 });
            _noteRepository.AddNote(new Note() { Id = "2", Body = "Test note 2", 
                          CreatedOn = DateTime.Now, 
                          UpdatedOn = DateTime.Now, UserId = 1 });
            _noteRepository.AddNote(new Note() { Id = "3", Body = "Test note 3", 
                          CreatedOn = DateTime.Now, 
                          UpdatedOn = DateTime.Now, UserId = 2 });
            _noteRepository.AddNote(new Note() { Id = "4", Body = "Test note 4", 
                          CreatedOn = DateTime.Now, 
                          UpdatedOn = DateTime.Now, UserId = 2 });

            return "Done";
        }

        return "Unknown";
    }
}

启动设置

为了在项目运行后快速显示值,请更新 launchSettings.json 文件。

这是完整的文件内容,默认指向 api/notes url。

{
  "iisSettings": {
    "windowsAuthentication": false,
    "anonymousAuthentication": true,
    "iisExpress": {
      "applicationUrl": "https://:53617/",
      "sslPort": 0
    }
  },
  "profiles": {
    "IIS Express": {
      "commandName": "IISExpress",
      "launchBrowser": true,
      "launchUrl": "api/notes",
      "environmentVariables": {
        "ASPNETCORE_ENVIRONMENT": "Development"
      }
    },
    "NotebookAppApi": {
      "commandName": "Project",
      "launchBrowser": true,
      "launchUrl": "https://:5000/api/notes",
      "environmentVariables": {
        "ASPNETCORE_ENVIRONMENT": "Development"
      }
    }
  }
}

运行项目

在运行项目之前,请确保 MongoDB 正在运行(作为 Windows 服务,或通过控制台应用程序,如上所述)。

首先运行初始化链接
https://:53617/api/system/init

然后运行默认应用程序链接
https://:53617/api/notes

使用 Robomongo

使用 Robomongo 我们可以查看数据库中的实际条目。使用凭据连接到数据库,我们可以看到所有 4 条记录。

完全更新 MongoDB 文档

最初的示例项目只包含属性的选择性更新。使用 ReplaceOneAsync 我们可以更新整个文档。Upsert 在文档不存在时创建它。

public async Task<replaceoneresult> UpdateNote(string id, Note item)
{
     return await _context.Notes
                          .ReplaceOneAsync(n => n.Id.Equals(id)
                                            , item
                                            , new UpdateOptions { IsUpsert = true });
} 
</replaceoneresult>

测试更新

为了测试更新,我使用了 Postman。它是一个很棒的 API 测试工具。

我选择了命令类型 POST,然后输入了本地 URL,并添加了一个新的 Header (Content-Typeapplication/json)。

ASP.NET Core WebAPI Set-header

然后将正文设置为 raw 并更新了一个虚拟值。

ASP.NET Core WebAPI Make the request

使用 RoboMongo 我们可以看到更新的值。

MongoDB .NET Driver Updated document in Robomongo

异常管理

从 C# 5.0 开始,语言中引入了 async 和 await 来简化 Task Parallel Library 的使用。我们可以简单地使用 try/catch 块来捕获异常,如下所示

public async Task<ienumerable<note>> GetAllNotes()
{
    try
    {
        return await _context.Notes.Find(_ => true).ToListAsync();
    }
    catch (Exception ex)
    {
        // log or manage the exception
        throw ex;
    }
}
</ienumerable<note>

通过这种方式,我们通过异步等待任务完成来处理失败的任务,使用 await。这将重新抛出原始存储的异常。最初我使用 void 作为返回类型。更改返回类型后,异步方法中引发的异常将安全地保存到返回的 Task 实例中。当我们 await 失败的方法时,保存到 Task 中的异常将连同其完整的堆栈跟踪一起重新抛出。

public async Task AddNote(Note item)
{
    try
    {
        await _context.Notes.InsertOneAsync(item);
    }
    catch (Exception ex)
    {
        // log or manage the exception
        throw ex;
    }
}

“请求的资源上不存在‘Access-Control-Allow-Origin’头。”

错误

尝试从 Angular 2.0 消费服务,请参阅此处的 CodeProject 文章,我遇到了这个错误。

为什么会出现这个错误?

作为在不同域上运行的不同应用程序,所有对 ASP.NET WebAPI 站点的调用都有效地跨域调用。对于 Angular 2,在实际请求之前,会有一个预检请求,这是一个 OPTIONS 请求。通过执行此预检,我们首先验证是否允许跨域调用 (CORS)。

我是如何启用它们的?

为此,我进行了两项更改

  • 在 Startup.cs 的 ConfigureServices() 中注册了 CORS 功能
 public void ConfigureServices(IServiceCollection services) 
 {
      // Add service and create Policy with options 
      services.AddCors(options => { options.AddPolicy("CorsPolicy", 
                                      builder => builder.AllowAnyOrigin() 
                                                        .AllowAnyMethod() 
                                                        .AllowAnyHeader() 
                                                        .AllowCredentials()); 
                                  }); 
      // .... 

      services.AddMvc(); 
 }
  • 然后在 Startup 的 Configure() 方法中,在 UseMVC 之前调用 app.useCors(),全局启用策略以应用于应用程序中的每个请求。
 public void Configure(IApplicationBuilder app) 
 { 
    // ... 

    // global policy, if assigned here (it could be defined indvidually for each controller) 
    app.UseCors("CorsPolicy"); 

    // ... 

    // We define UseCors() BEFORE UseMvc, below just a partial call
    app.UseMvc(routes => {
 }

这是否对解决方案的其他部分有任何影响?

不,这仅指此安全策略。即使可以进一步更具选择性地应用此策略,项目的其余部分也保持不变。

原始文章的更改

更新于:2017 年 11 月 15 日

根据 Matthew 的评论,我已更新 INoteRepository 接口,使其不与 MongoDB 库耦合。

更新于:2017 年 9 月 23 日

该项目现在运行 .NET Core 2.0。

更新于 2017 年 8 月 16 日

添加了对另外两篇关于 MongoDb 文章的引用

更新于 2017 年 4 月 13 日

将项目转换为 Visual Studio 2017。

更新于 2016 年 12 月 14 日

根据 Marcello 的评论,我添加了基本级别的异常管理。

更新于 2016 年 12 月 7 日

  • 我扩展了更新功能,使其可以一次修改完整的 MongoDB 文档,而不仅仅是修改部分属性。文章末尾有一个新部分描述了此更改。
  • 我已将项目更新到 .NET Core 1.1 以及 MongoDB .NET Driver 2.4

更新于 2016 年 11 月 25 日

尝试从 Angular 2.0 消费服务,请参阅此处的 CodeProject 文章,我遇到了 CORS 问题:“请求的资源上不存在‘Access-Control-Allow-Origin’头。”我扩展了文章以解决此问题。

 

© . All rights reserved.