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






4.89/5 (52投票s)
如何逐步构建一个带有最新 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)
您可能也对此感兴趣
- 将此项目链接到 Angular 2:ASP.NET Core Web API – 构建一个简单的笔记本应用程序 – 第 1 部分。
- 在大型 MongoDb 集合上运行 LINQ 查询:如何搜索好的旅游地点 (MongoDb LINQ & .NET Core)
- MongoDB 中的分页 – 如何有效避免性能不佳?
技术栈
ASP.NET Core Web API 最大的优势在于它可以作为 HTTP 服务使用,并且可以被任何客户端应用程序订阅,从桌面到移动设备,并且可以安装在 Windows、macOS 或 Linux 上。
MongoDB 是一个流行的 NoSQL 数据库,是 Web API 的出色后端。这些更适合文档存储类型,而不是关系数据库。本博客将介绍如何构建一个异步连接到 MongoDB 的 .NET Core Web API,并全面支持 HTTP GET、PUT、POST 和 DELETE。
安装
以下是所有需要安装的东西
- Visual Studio Community 2017 with .NET Core。
- MongoDB 和 Robomongo
创建 ASP.NET WebApi 项目
启动 Visual Studio,然后访问 文件 > 新建项目 > .Net Core > ASP.NET Core Web 应用程序。
然后选择带有 ASP.NET Core 2.0 的 Web 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/init 或 https://: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-Type 为 application/json)。
然后将正文设置为 raw 并更新了一个虚拟值。
使用 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 文章的引用
- 在大数据集上运行 LINQ 查询:如何搜索好的旅游地点 (MongoDb LINQ & .NET Core)
- 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’头。”我扩展了文章以解决此问题。