将MongoDB.NET驱动程序与.NET Core WebAPI一起使用

目录

您可能也有兴趣

技术栈

安装

创建ASP.NET WebApi项目

配置

依赖注入和选项模型

MongoDB配置

MongoDB .NET驱动程序

模型

定义数据库上下文

添加存储库

添加主控制器

添加管理员控制器

启动设定

运行项目

使用Robo 3T

在GitHub上运行项目

允许跨域调用(CORS)

完全更新MongoDB文档

测试更新

异常管理

.NET Core中的JSON POST的模型绑定

查询嵌入/嵌套文档


项目已经房子啊GitHub上,您可以直接下载源代码或在本地克隆项目。

您可能也有兴趣

  • 1部分使用MongoDB运行LINQ查询– 如何搜索旅行的好地方(MongoDb LINQ和.NET Core)
  • 第2部分– MongoDB中的分页–如何真正避免性能下降?
  • 第3部分– MongoDb和LINQ:如何汇总和加入集合

技术栈

ASP.NET Core Web API具有很大的优势,它可以用作HTTP服务,并且可以由任何客户端应用程序(从台式机到移动设备)进行订阅,也可以安装在WindowsmacOSLinux上。

MongoDB是一种流行的NoSQL数据库,是Web API的出色后端。它们更适合于文档存储类型,而不是关系数据库。本文将介绍如何构建与MongoDB异步连接的.NET Core Web API,并全面支持HTTP GETPUTPOSTDELETE

安装

这里是所有需要安装的东西:

  • Visual Studio Community 2017,包括.NET Core选项
  • MongoDBRobo 3T

创建ASP.NET WebApi项目

启动Visual Studio,然后访问文件”>“新建项目”>“.Net Core”>“ ASP.NET Core Web应用程序
将MongoDB.NET驱动程序与.NET Core WebAPI一起使用_第1张图片
然后

将MongoDB.NET驱动程序与.NET Core WebAPI一起使用_第2张图片

配置

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

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

  "Logging": {
    "IncludeScopes": false,
    "Debug": {
      "LogLevel": {
        "Default": "Warning"
      }
    },
    "Console": {
      "LogLevel": {
        "Default": "Warning"
      }
    }
  }
}

依赖注入和选项模型

构造函数注入是实现依赖注入(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以便在Options访问器模型中注入设置的方法:

public void ConfigureServices(IServiceCollection services)
{
    // Add framework services.
    services.AddMvc();
    services.Configure(options =>
    {
        options.ConnectionString 
			= Configuration.GetSection("MongoConnection:ConnectionString").Value;
        options.Database 
			= Configuration.GetSection("MongoConnection:Database").Value;
    });
}

此外,在项目中,可以通过IOptions接口访问设置:

IOptions

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"

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

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

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

将MongoDB.NET驱动程序与.NET Core WebAPI一起使用_第3张图片

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

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

然后停止服务器并更新配置文件,包括安全选项。

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

从现在开始,我们将使用管理员用户连接到MongoDb 。有一个很好的做法,就是不要在正常操作中使用超级用户角色(在我们的案例中为Administrator),但是为了使事情变得简单,我们将继续只有一个用户。

MongoDB .NET驱动程序

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

将MongoDB.NET驱动程序与.NET Core WebAPI一起使用_第4张图片

模型

与笔记本中每个条目关联的模型类(POCO)包括以下内容:

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

namespace NotebookAppApi.Model
{
	public class Note
	{
		[BsonId]
		// standard BSonId generated by MongoDb
		public ObjectId InternalId { get; set; }

		// external Id, easier to reference: 1,2,3 or A, B, C etc.
		public string Id { get; set; }                          

		public string Body { get; set; } = string.Empty;

		[BsonDateTimeOptions]
                // attribute to gain control on datetime serialization
		public DateTime UpdatedOn { get; set; } = DateTime.Now;

		public NoteImage HeaderImage { get; set; }

		public int UserId { get; set; } = 0;
	}
}

注意:默认情况下,使用参数 BsonDateTimeOptionsBson序列化程序尝试将序列化为DateTimeUTC。如下添加特性[BsonDateTimeOptions(Kind = DateTimeKind.Local)],我们允许保存本地时间:

假设Note带有标题图像,下面是一个示例嵌入式类:

public class NoteImage
{
	public string Url { get; set; } = string.Empty;
	public string ThumbnailUrl { get; set; } = string.Empty;
	public long ImageSize { get; set; } = 0L;
}

定义数据库上下文

为了将访问数据库的功能保留在不同的位置,我们将添加一个NoteContext类。这将使用上面定义的设置

public class NoteContext
{
    private readonly IMongoDatabase _database = null;

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

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

添加存储库

使用存储库接口,我们将实现管理Notes所需的功能。这些还将使用依赖注入(DI)来从应用程序(例如,控制器部分)轻松访问:

public interface INoteRepository
{
	Task> GetAllNotes();
	Task GetNote(string id);

	// query after multiple parameters
	Task> GetNote(string bodyText, DateTime updatedFrom, long headerSizeLimit);

	// add new note document
	Task AddNote(Note item);

	// remove a single document / note
	Task RemoveNote(string id);

	// update just a single document / note
	Task UpdateNote(string id, string body);

	// demo interface - full document update
	Task UpdateNoteDocument(string id, string body);

	// should be used with high cautious, only in relation with demo setup
	Task RemoveAllNotes();
}

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

举个例子:要获取所有Notes,我们提出一个异步请求:

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

这是所有基本CRUD操作的完整实现:

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

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

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

	// query after Id or InternalId (BSonId value)
	//
	public async Task GetNote(string id)
	{
		try
		{
			ObjectId internalId = GetInternalId(id);
			return await _context.Notes
							.Find(note => note.Id == id 
									|| note.InternalId == internalId)
							.FirstOrDefaultAsync();
		}
		catch (Exception ex)
		{
			// log or manage the exception
			throw ex;
		}
	}

	// query after body text, updated time, and header image size
	//
	public async Task> GetNote(string bodyText, DateTime updatedFrom, long headerSizeLimit)
	{
		try
		{
			var query = _context.Notes.Find(note => note.Body.Contains(bodyText) &&
								   note.UpdatedOn >= updatedFrom &&
								   note.HeaderImage.ImageSize <= headerSizeLimit);

			return await query.ToListAsync();
		}
		catch (Exception ex)
		{
			// log or manage the exception
			throw ex;
		}
	}

	private ObjectId GetInternalId(string id)
	{
		ObjectId internalId;
		if (!ObjectId.TryParse(id, out internalId))
			internalId = ObjectId.Empty;

		return internalId;
	}
	
	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 RemoveNote(string id)
	{
		try
		{
			DeleteResult actionResult 
				= await _context.Notes.DeleteOneAsync(
					Builders.Filter.Eq("Id", id));

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

	public async Task UpdateNote(string id, string body)
	{
		var filter = Builders.Filter.Eq(s => s.Id, id);
		var update = Builders.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 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;
		}
	}

	// Demo function - full document update
	public async Task UpdateNoteDocument(string id, string body)
	{
		var item = await GetNote(id) ?? new Note();
		item.Body = body;
		item.UpdatedOn = DateTime.Now;

		return await UpdateNote(id, item);
	}

	public async Task 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();

其中:

  • 瞬态(Transient):每次创建。

  • 范围(Scoped):每个请求仅创建一次。

  • 单例(Singleton):在首次请求时创建。每个后续请求都使用第一次创建的实例。

添加主控制器

首先,我们介绍主控制器。它提供了所有CRUD接口,可供外部应用程序使用。
获取的行为有NoCache指令,以确保Web客户端使经常对服务器的请求。

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

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

	[NoCache]
	[HttpGet]
	public async Task> Get()
	{
		return await _noteRepository.GetAllNotes();
	}

	// GET api/notes/5 - retrieves a specific note using either Id or InternalId (BSonId)
	[HttpGet("{id}")]
	public async Task Get(string id)
	{
		return await _noteRepository.GetNote(id) ?? new Note();
	}

	// GET api/notes/text/date/size
	// ex: http://localhost:53617/api/notes/Test/2018-01-01/10000
	[NoCache]
	[HttpGet(template: "{bodyText}/{updatedFrom}/{headerSizeLimit}")]
	public async Task> Get(string bodyText, 
											 DateTime updatedFrom, 
											 long headerSizeLimit)
	{
		return await _noteRepository.GetNote(bodyText, updatedFrom, headerSizeLimit) 
					?? new List();
	}

	// POST api/notes - creates a new note
	[HttpPost]
	public void Post([FromBody] NoteParam newNote)
	{
		_noteRepository.AddNote(new Note
									{
										Id = newNote.Id,
										Body = newNote.Body,
										CreatedOn = DateTime.Now,
										UpdatedOn = DateTime.Now,
										UserId = newNote.UserId
									});
	}

	// PUT api/notes/5 - updates a specific note
	[HttpPut("{id}")]
	public void Put(string id, [FromBody]string value)
	{
		_noteRepository.UpdateNoteDocument(id, value);
	}

	// DELETE api/notes/5 - deletes a specific note
	[HttpDelete("{id}")]
	public void Delete(string id)
	{
		_noteRepository.RemoveNote(id);
	}
}

添加管理员控制器

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

要使用它,我们只需将URL添加到浏览器中。运行下面的代码,将自动创建完整的设置(例如,新数据库,新集合,样本记录)。我们可以使用http://localhost:5000/api/system/init(使用IIS时)或 http://localhost:53617/api/system/init (使用IIS Express时,在此示例项目中默认启用) 。我们甚至可以扩展想法,添加更多命令。但是,如上所述,这类方案应仅用于开发,决不能部署到生产环境中。

[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();
			var name = _noteRepository.CreateIndex();

			_noteRepository.AddNote(new Note()
			{
				Id = "1",
				Body = "Test note 1",
				UpdatedOn = DateTime.Now,
				UserId = 1,
				HeaderImage = new NoteImage
				{
					ImageSize = 10,
					Url = "http://localhost/image1.png",
					ThumbnailUrl = "http://localhost/image1_small.png"
				}
			});

			_noteRepository.AddNote(new Note()
			{
				Id = "2",
				Body = "Test note 2",
				UpdatedOn = DateTime.Now,
				UserId = 1,
				HeaderImage = new NoteImage
				{
					ImageSize = 13,
					Url = "http://localhost/image2.png",
					ThumbnailUrl = "http://localhost/image2_small.png"
				}
			});

			_noteRepository.AddNote(new Note()
			{
				Id = "3",
				Body = "Test note 3",
				UpdatedOn = DateTime.Now,
				UserId = 1,
				HeaderImage = new NoteImage
				{
					ImageSize = 14,
					Url = "http://localhost/image3.png",
					ThumbnailUrl = "http://localhost/image3_small.png"
				}
			});

			_noteRepository.AddNote(new Note()
			{
				Id = "4",
				Body = "Test note 4",
				UpdatedOn = DateTime.Now,
				UserId = 1,
				HeaderImage = new NoteImage
				{
					ImageSize = 15,
					Url = "http://localhost/image4.png",
					ThumbnailUrl = "http://localhost/image4_small.png"
				}
			});

			return "Database NotesDb was created, and collection 'Notes' was filled with 4 sample items";
		}

		return "Unknown";
	}
}

启动设定

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

将MongoDB.NET驱动程序与.NET Core WebAPI一起使用_第5张图片

这是完整的文件内容,默认情况下指向api/notes网址。

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

运行项目

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

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

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

将MongoDB.NET驱动程序与.NET Core WebAPI一起使用_第6张图片

使用Robo 3T

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

即使唯一ID的名称为_idMongoDb .NET Driver也会使用标签[BsonId]将其映射到我们的变量InternalId

将MongoDB.NET驱动程序与.NET Core WebAPI一起使用_第7张图片

GitHub上运行项目

该示例的完整资源可在GitHub-> https://github.com/fpetru/WebApiMongoDB上找到。

允许跨域调用(CORS

作为不同的应用程序,它们运行在单独的域上,所有对ASP.NET WebAPI站点的调用实际上都是跨域调用。对于Angular 2,首先有一个预请求,然后是实际请求(OPTIONS请求)。在进行此预检查之前,我们首先确认允许跨域调用(CORS)。

我通过应用两个更改启用了CORS

  • 首先在Startup.csConfigureServices()方法中注册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();
}
  • 然后在UseMVC之前,通过在StartupConfigure()方法中调用app.useCors()在应用程序中的每个请求上全局启用策略。

public void Configure(IApplicationBuilder app)
{
   // ...

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

   // ...

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

即使可以更进一步,更有选择性地应用它,本文其余部分也保持不变。

完全更新MongoDB文档

最初,示例项目仅包含属性的选择性更新。使用ReplaceOneAsync我们可以更新整个文档。如果尚不存在,Upsert创建该文档。

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

测试更新

为了能够测试更新,我使用了Postman。它是测试API的出色工具。

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

将MongoDB.NET驱动程序与.NET Core WebAPI一起使用_第8张图片

然后将主体设置为原始并更新虚拟值。将MongoDB.NET驱动程序与.NET Core WebAPI一起使用_第9张图片

使用RoboMongo,我们可以看到更新的值。将MongoDB.NET驱动程序与.NET Core WebAPI一起使用_第10张图片

异常管理

C5.0开始, 该语言引入了async    await来简化任务并行库的使用。我们可以简单地使用try/catch块来捕获异常,如下所示:

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

通过这种方式,我们通过使用await异步地等待有故障的任务来完成它。这将重新引发原始存储的异常。

最初,我使用void作为返回值。更改返回类型后,异步方法中引发的异常将安全地保存在返回的Task实例中。当我们等待错误的方法时,保存在Task中的异常将被重新抛出,并保留其完整的堆栈跟踪。

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

.NET Core中的JSON POST的模型绑定

模型绑定是将原始HTTP请求转换为控制器上的操作方法调用的参数。
[FromBody]参数告诉.net core框架使用请求的content-type标头,以决定使用哪个已配置的IInputFormatters进行模型绑定。

默认情况下,当您在Startup.cs中调用AddMvc()时,将自动配置JSON格式(JsonInputFormatter)。如果需要,可以添加其他格式化程序,例如将XML绑定到对象。

[HttpPost]
public void Post([FromBody] NoteParam newNote)

要添加新的Note,我们首先需要将Content-Type设置为application/json

然后,我们发送一个JSON对象,并成功添加了一个新的Note。由于未设置UserId,因此该对象将采用默认值。将MongoDB.NET驱动程序与.NET Core WebAPI一起使用_第11张图片

查询嵌入/嵌套文档

MongoDBCSharp驱动程序使对嵌入式文档的查询变得容易。在下面的示例中,我们混合使用了两个过滤器,一个过滤器比较主文档中的日期,另一个过滤器比较嵌套类的long 成员。

note.UpdatedOn >= updatedFrom && note.HeaderImage.ImageSize <= headerSizeLimit

使用IIS Express访问应用程序,我们可以使用Get函数,该函数包含所有带有Test的注释,该注释在2018-01-01之后创建,并且大小小于10000。项目启动后,可以使用浏览器中的下一个URL调用此函数:http://localhost:53617/api/notes/Test/2018-01-01/10000

你可能感兴趣的:(ASP.NET,CORE,架构及框架)