What’s about
Problem / solution format brings an easier understanding on how to build things, giving an immediate feedback. Starting from this idea, the blog post I will present step by step how to build
a web application to store your ideas in an easy way, adding text notes, either from desktop or mobile, with few characteristics: run fast, save on the fly whatever you write, and be reasonably reliable and secure.
This article will implement just the backend, WebApi and the database access, in the most simple way.
A couple of updates done to the original article
- Following Peter’s comment, I have simplified the documents returned, see HttpGet requests
- Following Luciano’s comment, I have extend the update function, making update of the full MongoDB documents at once, not just to some of the properties. There is a new section below, describing this change
- Trying to read from Angular 2, find the article here, I have ran into CORS problems. An error message was displayed “No ‘Access-Control-Allow-Origin’ header is present on the requested resource”. I have added a new section to describe the solution.
- I have updated the project to .NET Core 1.1 as well to MongoDB .NET Driver 2.4
- Added a basic level of exception management
- Following Peter’s comment I have converted the solution to Visual Studio 2017
- Updated to .NET Core 2.0
- Following Matthew’s comment, I have updated the interface INoteRepository to not be coupled to MongoDB libraries
- added a compound MongoDb index
- Following the comments from Kirk and Andrea, I have added to the structure the MongoDb BSonId and added a section of model binding of JSON Posts
- Following comments from Manish and Zahn, I have extended the example with a nested class; Updated to MongoDb.Driver 2.7, which add support for new features of the MongoDB 4.0 Server.
The GitHub project is updated and includes all these changes. You could directly download the sources or clone the project locally.
Topics covered
- Technology stack
- Configuration model
- Options model
- Dependency injection
- MongoDb – Installation and configuration using MongoDB C# Driver v.2
- Make a full ASP.NET WebApi project, connected async to MongoDB
- Allowing Cross Domain Calls (CORS)
- Update entire MongoDB documents
- Exception management
- Model binding of HTTP Post command (newly added)
- Nested classes in MongoDb
You might be interested also
- Part 1 – Run LINQ queries with MongoDB – How to search good places to travel (MongoDb LINQ & .NET Core)
- Part 2 – Paging in MongoDB – How to actually avoid poor performance ?
- Part 3 – MongoDb and LINQ: How to aggregate and join collections
- Using MongoDB .NET Driver with ASP.NET Core MVC
Technology stack
The ASP.NET Core Web API has the big advantage that it can be used as HTTP service and it can be subscribed by any client application, ranging from desktop to mobiles, and also be installed on Windows, macOS or Linux.
MongoDB is a popular NoSQL database that makes a great backend for Web APIs. These lend themselves more to document store type, rather than to relational databases. This blog will present how to build a .NET Core Web API connected asynchronously to MongoDB, with full support for HTTP GET, PUT, POST, and DELETE.
To install
Here are all the things needed to be installed:
- Visual Studio Community 2017, including .NET Core option
- MongoDB and Robo 3T
Creating the ASP.NET WebApi project
Launch Visual Studio and then access File > New Project > .Net Core > ASP.NET Core Web Application.
and then
Configuration
There are multiple file formats, supported out of the box for the configuration (JSON, XML, or INI). By default, the WebApi project template comes with JSON format enabled. Inside the setting file, order matters, and include complex structures. Here is an example with a 2 level settings structure for database connection.
AppSettings.json – update the file:
{ "MongoConnection": { "ConnectionString": "mongodb://admin:abc123!@localhost", "Database": "NotesDb" }, "Logging": { "IncludeScopes": false, "Debug": { "LogLevel": { "Default": "Warning" } }, "Console": { "LogLevel": { "Default": "Warning" } } } }
Dependency injection and Options model
Constructor injection is one of the most common approach to implementing Dependency Injection (DI), though not the only one. ASP.NET Core uses constructor injection in its solution, so we will also use it. ASP.NET Core project has a Startup.cs file, which configures the environment in which our application will run. The Startup.cs file also places services into ASP.NET Core’s Services layer, which is what enables dependency injection.
To map the custom database connection settings, we will add a new Settings class.
namespace NotebookAppApi.Model { public class Settings { public string ConnectionString; public string Database; } }
Here is how we modify Startup.cs to inject Settings in the Options accessor model:
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; }); }
Further in the project, settings will be access via IOptions interface:
IOptions<Settings>
MongoDB configuration
Once you have installed MongoDB, you would need to configure the access, as well as where the data is located.
To do this, create a file locally, named mongod.cfg. This will include setting path to the data folder for MongoDB server, as well as to the MongoDB log file, initially without any authentication. Please update these local paths, with your own settings:
systemLog: destination: file path: "C:\\tools\\mongodb\\db\\log\\mongo.log" logAppend: true storage: dbPath: "C:\\tools\\mongodb\\db\\data"
Run in command prompt next line. This will start the MongoDB server, pointing to the configuration file already created (in case the server is installed in a custom folder, please update first the command)
"C:\Program Files\MongoDB\Server\3.2\bin\mongod.exe" --config C:\Dev\Data.Config\mongod.cfg
Once the server is started (and you could see the details in the log file), run mongo.exe in command prompt. The next step is to add the administrator user to the database. Run mongodb with the full path (ex: “C:\Program Files\MongoDB\Server\3.2\bin\mongo.exe”).
and then copy paste the next code in the console:
use admin db.createUser( { user: "admin", pwd: "abc123!", roles: [ { role: "root", db: "admin" } ] } ); exit;
Then stop the server and update the configuration file, including the security option.
systemLog: destination: file path: "C:\\tools\\mongodb\\db\\log\\mongo.log" logAppend: true storage: dbPath: "C:\\tools\\mongodb\\db\\data" security: authorization: enabled
From now on, we’ll connect to MongoDb using admin user. There is a good practice to not use the superuser role (in our case administrator) for normal operations, but in order to keep the things simple, we will continue to have just a single user.
MongoDB .NET Driver
To connect to MongoDB, add via Nuget the package named MongoDB.Driver. This is the new official driver for .NET, fully supporting the ASP.NET Core applications.
Model
The model class (POCO) associated with each entry in the notebook is included below:
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; } }
Note: By default, using the parameter BsonDateTimeOptions, Bson serializer tries to serialize as a DateTime, as UTC. Adding the attribute as follows, we allow saving in local time instead: [BsonDateTimeOptions(Kind = DateTimeKind.Local)]
Assuming the Note would have a header image, here would be a sample embedded class:
public class NoteImage { public string Url { get; set; } = string.Empty; public string ThumbnailUrl { get; set; } = string.Empty; public long ImageSize { get; set; } = 0L; }
Defining the database context
In order to keep the functions for accessing the database in a distinct place, we will add a NoteContext class. This will use the Settings defined above.
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"); } } }
Adding the repository
Using a repository interface, we will implement the functions needed to manage the Notes. These will also use Dependency Injection (DI) to be easily access from the application (e.g. controller section):
public interface INoteRepository { Task<IEnumerable<Note>> GetAllNotes(); Task<Note> GetNote(string id); // query after multiple parameters Task<IEnumerable<Note>> GetNote(string bodyText, DateTime updatedFrom, long headerSizeLimit); // add new note document Task AddNote(Note item); // remove a single document / note Task<bool> RemoveNote(string id); // update just a single document / note Task<bool> UpdateNote(string id, string body); // demo interface - full document update Task<bool> UpdateNoteDocument(string id, string body); // should be used with high cautious, only in relation with demo setup Task<bool> RemoveAllNotes(); }
The access to database will be asynchronous. We are using here the new driver, which offers a full async stack.
Just as an example: to get all the Notes, we make an async request:
public async Task<IEnumerable<Note>> GetAllNotes() { var documents = await _context.Notes.Find(_ => true).ToListAsync(); return documents; }
Here is the full implementation, for all basic CRUD operations:
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; } } // query after Id or InternalId (BSonId value) // public async Task<Note> 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<IEnumerable<Note>> 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<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; } } // Demo function - full document update public async Task<bool> 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<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; } } }
In order to access NoteRepository using DI model, we add a new line in ConfigureServices
services.AddTransient<INoteRepository, NoteRepository>();
where:
- Transient: Created each time.
- Scoped: Created only once per request.
- Singleton: Created the first time they are requested. Each subsequent request uses the instance that was created the first time.
Adding the main controller
First we present the main controller. It provides all the CRUD interfaces, available to external applications.
The Get actions have NoCache directive, to ensure web clients make always requests to the server.
[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<IEnumerable<Note>> Get() { return await _noteRepository.GetAllNotes(); } // GET api/notes/5 - retrieves a specific note using either Id or InternalId (BSonId) [HttpGet("{id}")] public async Task<Note> 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<IEnumerable<Note>> Get(string bodyText, DateTime updatedFrom, long headerSizeLimit) { return await _noteRepository.GetNote(bodyText, updatedFrom, headerSizeLimit) ?? new List<Note>(); } // 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); } }
Adding the admin controller
This will be a controller dedicated to administrative tasks (we use to initialize the database with some dummy data). In real projects, we should very cautiously use such interface. For development only and quick testing purpose, this approach may be convenient.
To use it, we will just add the url in the browser. Running the code below, the full setup will be automatically created (e.g. new database, new collection, sample records). We can use either http://localhost:5000/api/system/init (when using IIS) or http://localhost:53617/api/system/init (when using IIS Express, enabled as default on this sample project). We could even extend the idea, adding more commands. However, as mentioned above, these kind of scenarios should be used just for development, and be never deployed to a production environment.
[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"; } }
Launch settings
In order to have a quick display of the values, once the project will run, please update the file launchSettings.json.
Here is the full file content, pointing by default to api/notes url.
{ "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" } } } }
Running the project
Before running the project, please make sure the MongoDB is running (either as an Windows Service, or via console application, as presented above).
Run first the initialization link:
http://localhost:53617/api/system/init
and then run the default application link
http://localhost:53617/api/notes
Use Robo 3T
Using Robo 3T we could check the actual entries inside the database. Connecting to the database, using the credentials, we could see all records.
Even if the unique id has the name _id, the MongoDb .NET Driver maps it to our variable InternalId using the tag [BsonId].
Running project on GitHub
Full source for this example is available on GitHub -> https://github.com/fpetru/WebApiMongoDB
Allowing Cross Domain Calls (CORS)
Being different applications, running on separate domains, all calls back to ASP.NET WebAPI site are effectively cross domain calls. With Angular 2, there is first a pre-flight request, before the actual request, (an OPTIONS request). Doing this pre-check, we verify first that cross domain calls are allowed (CORS).
I have enabled CORS by applying two changes:
- First register CORS functionality in ConfigureServices() of Startup.cs:
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(); }
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 => { }
Even if this could be further and more selective applied, the rest of the article remains unchanged.
Fully update the MongoDB documents
Initially the sample project included only selective update of the properties. Using ReplaceOneAsync we could update the full document. Upsert creates the document, in case it doesn’t already exist.
public async Task<ReplaceOneResult> UpdateNote(string id, Note item) { return await _context.Notes .ReplaceOneAsync(n => n.Id.Equals(id) , item , new UpdateOptions { IsUpsert = true }); }
Test the update
To be able to test the update, I have used Postman. It is an excellent tool to test APIs.
I have selected the command type POST, then entered the local URL, and added a new Header (Content-Type as application/json).
And then set the Body as raw and updated a dummy value.
Using RoboMongo we can see the value updated.
Exception management
Starting with C# 5.0 async and await were introduced into the language to simplify using the Task Parallel Library. We can simply use a try/catch block to catch an exception, like so:
public async Task<IEnumerable<Note>> GetAllNotes() { try { return await _context.Notes.Find(_ => true).ToListAsync(); } catch (Exception ex) { // log or manage the exception throw ex; } }
In this way we handle a faulted task by asynchronously wait for it to complete, using await. This will rethrow the original stored exception.
Initially I have used void as return. Changing the return type, the exception raised in the async method will get safely saved in the returning Task instance. When we await the faulty method, the exception saved in the Task will get rethrown with its full stack trace preserved.
public async Task AddNote(Note item) { try { await _context.Notes.InsertOneAsync(item); } catch (Exception ex) { // log or manage the exception throw ex; } }
Model binding of JSON POSTs in .NET Core
Model binding is the conversion of the raw HTTP request into the arguments for an action method invocation on a controller.
[FromBody] parameter tells the .net core framework to use the content-type header of the request, to decide which of the configured IInputFormatters to use for model binding.
By default, when you call AddMvc() in Startup.cs, a JSON formatte (JsonInputFormatter) is automatically configured. You could add additional formatters if you need to, for example to bind XML to an object.
[HttpPost] public void Post([FromBody] NoteParam newNote)
To add a new Note, we need first to set Content-Type, to be application/json.
Then we send a JSON object, and we successfully add a new Note. Since UserId is not set, the object will take the default value.
Query on Embedded / Nested Documents
CSharp driver of MongoDB makes the query on the embedded documents easy. In the example below we mix two filters, one comparing the date from the main document, and one comparing a long member of the nested class.
note.UpdatedOn >= updatedFrom && note.HeaderImage.ImageSize <= headerSizeLimit
Accessing the application using IIS Express, we could use the Get function that contain all the notes with Test, created after 2018-01-01 and size smaller than 10000. Once the project is started, this function could be called by using the next URL in the browser: http://localhost:53617/api/notes/Test/2018-01-01/10000.
At the end
Hope this helped ! Let me know if you have questions or some things needs to be updated.
94 comments On Using MongoDB .NET Driver with .NET Core WebAPI
I am working on asp.net core Web application MVC and I used MongoDB database and its shows information about collection in local browser but How can I add search box that works on local browser information?
Hi Yogesh,
Unfortunately, I cannot help on this. The search box action would be specific to your MVC web app you develop.
Dear Petru,
I am building an application to store information about user, however any can search for other users by their criteria, similar to linked in.
Is it enough to use this structure for project that will serve billions of request? or do you suggest an advance one to learn. moreover, .net core and mongodb is a good choice or do you suggest something else?
Thanks in advance,
When there is such a large project, there would be needed more data systems, besides a document database. You mentioned various criteria to search. To do this, an excellent option would be ElasticSearch. Additionally, a system to cache the requests (e.g. Memcached or Redis). StackExchange is serving 1.3 billion pages per month, and a description is included here: https://stackexchange.com/performance.
My article would not be enough. It focuses to provide a start, but for such a large system much more would be needed.
Best regards,
Petru
Hello, thank you for this tutorial.
Question: I am trying to build a chat app and I was wondering if it is possible to add a event listing on collections and documents and may be use signalR to listen on client side.
Thank you
Hi Ateyib,
If the changes are done only using the API, then you could use the Publish Subscribe (Pub/Sunb) pattern. StackExchange.Redis is one of the implementation. Microsoft has used this package and apply it for SignalR apps –
Microsoft.AspNetCore.SignalR.StackExchangeRedis
. Here is an example: ASP.NET Core 5.0 SignalR app with Redis.Best regards,
Petru
hi petru ,
i am having ubuntu and was wondering how to configure mongodb on it . I mean to get the data i need to add some data to it before with the same model as specified by you ; do i need to change anything else for this api to work . Thank you in advance .
Hi,
If you have already available MongoDb, then the installation should be very similar. Let me know if you have any issues.
Kind regards,
Petru
Hi , Thanks for the article .. but i have faced some issue while connecting the MOngoDB which i have using , how can i pass the credentials for the MongoDB , ex : if its secured username : xxx and Password :yyy inside the asspsetting.josn which you are using its not considering the Credentials ,
Please help me to reach out this scenario .
Hello Petru,
What is the best way to get the Addres of Customer on following:
public class City
{
public string _id { get; set; }
public string Desc{ get; set; }
}
public class Address
{
public string Street{ get; set; }
public string PostalCode { get; set;}
public string CityId { get; set; }
}
public class Customer
{
public string _id { get; set; }
public string Name { get; set; }
public Address Address
}
Having a ‘public virtual City’ in Address?
Thanks a lot!
Hello Alexandre,
I am not quite sure on your design of MongoDB collections, and I would like to have more things clear before providing my answer. Since this is not specific to the article, let me send a couple of details via email.
Kind regards,
Petru
You are using MongoClient incorrectly. As indicated in the docs
It is recommended to store a MongoClient instance in a global place, either as a static variable or in an IoC container with a singleton lifetime.
So you should rework your code to create a singleton MongoClient instance and then use that throughout your program.
Hi JRT,
I have used Dependency Injection pattern. This passes the dependencies to objects using them, allowing to easier run unit tests. It would be difficult to test Singleton, because each time you would run an unit test method, the application will call NodeContext.
Aside from greater testability, this practice helps the classes explicitly specify everything they need in order to perform properly.
On the other hand, if you tend to use the access to database throughout your entire code base, it’s just not practical to pass the NodeContext instance to every class that needs it as a dependency. Indeed, in this case, a better way is to introduce a Singleton.
It is more the way how the system is designed and if you need to run tests.
Best regards,
Petru
Missing CRUD for the -> Task UpdateNoteDocument(string id, string body)
Hi Tadas,
Sure, thanks for reading the article and your message. I have updated it now also the text.
The function was already included in project source.
Kind regards,
Petru
thanks for your article. i have little confuse , if the db has many collections, is that i have to create context and repository for every collection, and add them in ConfigureServices?
Hi Zhang,
Following on the initial question from Alexandre, you may have a single Context class and a single Repository. Here is the suggested example.
Now, if the logic is more complex, with a possibility for the future to have different data sources, you may have different repositories. I would recommend to start simple (with one single repository and one context class), and extend the structure later on, based on the updated requirements.
Best regards,
Petru
Hi Petru, thanks for the great aritcle,Wors fine. I am trying to display the data retrieved from the MongoDb in a view. However when binding the model i get an error: InvalidOperationException: A suitable constructor for type ‘System.Collections.Generic.IEnumerable`1[Guideliner_2.Model.Note]’ could not be located. Ensure the type is concrete and services are registered for all parameters of a public constructor.
Any idea what might cause this error?
Hi Petru, thank you for the comprehensive article.Just learning how to use ASP.Net with Mongo and this helps. However, i am getting stuck. The result of a get request to notes results in an empty array, although the application connects to the mongodatabase (as i can see in the command window). Any idea what might be causing this?
Hi Mihaly,
Maybe you would just need to fill in the database with some sample data. To do this, please run the next url in the browser http://localhost:53617/api/system/init. It will automatically setup the data you need (e.g. database, new collection and sample records).
If instead, you already have the data, and the WebApi project returns successfully the data, but you would like to use the connection to MongoDB within an ASP.NET project, then check the next article Using MongoDB .NET Driver with ASP.NET Core MVC
Please let me know if the issue still persists.
Kind regards,
Petru
Hello Petru,
Thank you for this article. I followed all the steps and able to successfully make it work on my local. I do have a question. thought. I am trying to deploy it to IIS 8 server. But getting no success. Can you please point me in right direction?
I changed database setting with Server name where MongoDB database is hosted.
Hello Agni,
Please tell me more about the error you receive.
Thanks,
Petru
Hello Petru, nice article! Thanks a lot!!
I have any question:
In NoteContext.cs i need to reference any Document on DB?
Like:
public IMongoCollection Notes
{
get
{
return _database.GetCollection(“Note”);
}
}
public IMongoCollection Computers
{
get
{
return _database.GetCollection(“Computer”);
}
}
public IMongoCollection Phones
{
get
{
return _database.GetCollection(“Phone”);
}
}
Thus?
Or should a Context be created for each DB document?
ComputerContext,
PhoneContext …
Thank you!!
Hello,
Thanks for your comment. Having just simple get functions, I would define them in a single Context class.
Kind regards,
Petru
Hi Petru, how can I implement nested class in mongo DB?
Save the instance of the class to the collection like any other object. MongoDB will take care of the rest. If it doesn’t handle your data the right way (for instance, DateTime precision) then you will need to explicitly map out the settings for individual fields in your class and any nested classes. More information on this topic can be found here: http://mongodb.github.io/mongo-csharp-driver/2.2/reference/bson/mapping/
Hi Zahn and Manish,
Yes, this is true. MongoDB carries out most of the work on this. I have expanded the sample with a nested class NoteImage, and updated in GitHub the full sample (covering also DateTime comparison).
I will update the article later today to reflect all these changes.
Thank you,
Petru
Hello, I can’t understand where this IOptions comes from. Could you please clarify? Thanks
I was running into this issue too. You must put a ‘using Microsoft.Extensions.Options;’ on top.
Great intro to Mongo – thank you!
I am glad it is helpful. Kind regards, Petru
Hi, really great article. I’m looking to setup this in a mac Dev environment, with visual studio community edition. Will this all, including mongodb driver work the same as you’ve shown with asp core on osx?
Hi Jeremy,
Yes, it should work with Visual Studio Code and .NET Core – https://www.microsoft.com/net/download/macos/build. I am not able to test on macOS, but if you have any issue please let me know, and we’ll find a solution.
Kind regards,
Petru
Jeremy, I used https://treehouse.github.io/installation-guides/mac/mongo-mac.html to get MongoDB set up. Once that’s set up, I can vouch that Petru’s tutorial works great for macOS as well.
Awesome, thanks Kirk and Petru!
Very great article but one question. Getting a single note (get request with an id on the NotesController) works with the initialized id’s (1, 2, 3, 4). When I create a document in Mongo and its given its own id (ex. ObjectId(“5a8238a527bc7ad3f702316b”)) I’m unable to retrieve it using the Get method. I’m trying to hit:
http://localhost:{port}/api/notes/5a8238a527bc7ad3f702316b and it retrieves a document with null attributes. Any advice for someone new to MongoDB? Thanks!
Hi Kirk,
Thank you for reading the article and for your comment. Starting from your question, I have updated the article and the code to be able to query Notes either using the initialized Id (ex: 1,2,3 etc.) or MongoDB BSonId (ex: “5a8238a527bc7ad3f702316b”). Now, you could either query using an URL like this: …/api/notes/5 or …/api/notes/5a8238a527bc7ad3f702316b.
Best regards,
Petru
How to use dynamic mongodb database run-time using this structure.
I am not quite sure to which structure are you referring to. Could you include few more details ?
Thanks,
Petru
Hi, i’ve downloaded the project from Github, everything seems to works perfectly but when i try to create a new Note with postman, the values passed from the body are always null, so in my database a new note get created but both the id and the body are null.
Hi Andrea,
Let me check and see why this happens. I will reply to you soon.
Best regards,
Petru
Hi, i’ve noticed that if instead of a single string value i use a new NoteParams class as a parameter, the data from the body get parsed and everything works. So maybe is a bug with the formatter?
Hi Andrea,
I’ve updated the article and the code, and extended the sample project with a NoteParam class. The default formatter is JSON and it expects an object that matches its definition. The way you did it is correct, and it now reflected in the article.
Kind regards,
Petru
I finally made MongoDB and .Net talking to each other. I was about to cry. I only read the beginning but I could not find any other resources online to get me that far. Thanks ! Will definitely read the whole thing! fyi I got here from that post https://stackoverflow.com/questions/47875412/how-can-i-use-the-mongodb-driver-with-c-sharp-asp-net-core-api-framework
Hi Kevin,
Thanks for your message and I hope the article would help. There are few other articles on MongoDb with .NET, for optimization, joins etc.
Kind regards,
Petru
Where should I write index creation logic?
Hi Irshad,
Personally, the database creation, user and index creation I would keep it in a distinct script – see the other article How to search good places to travel (MongoDb LINQ & .NET Core), or go directly to GitHub – https://github.com/fpetru/WebApiQueryMongoDb – Data folder.
You could add the index creation in a js file:
On the other hand, you also have the option to add directly the index creation inside the project. Here is a compound index.
Depending on the way you query data, you would also need to check if the index is actually used. See Paging in MongoDB – How to actually avoid poor performance ?
Thanks a lot, great article,
I have one question : shouldn’t the database context be defined in a more global place such as startup.cs ? It would be shared with other classes that might require to access it.
Thanks
Hi Guillaume,
Thanks for reading the article and for your comment. Yes, this would be possible. If you do this, then the Repository pattern and Dependency injection would not be needed.
I prefer the repository pattern for the way it structures the code and for enabling the code to be unit-tested:
Best regards,
Petru
Hello Petru,
Many thanks for taking the time to answer my question.
You made a very valid point.
I have a follow up question.
Let me explain a little my situation, I am used to Entity Framework and relationnal database : for performing basic CRUD operations I need very few lines of code.
When I look at Repository I find that it requires quite a lot of lines code, and if I get it right alsmot exactly the same lines will be needed for each document to be stored in MongoDB.
Would you have some advise on how to be more efficient in terms lines of code ?
Many thanks
Best regards
Guillaume
Hello Guillaume,
True, it would be an overhead to add a generic repository, while using Entity Framework. Specific to EF, here there is a full explanation on a possible approach (Part1, Part2).
Best regards,
Petru
Does this work with .NET Core 2.0? Would I need to change anything?
Let me check later this week. If all is ok, I will update also the project in GitHub.
Kind regards,
Petru
I have converted the project to .NET Core 2.0 and commited the changes to GitHub (https://github.com/fpetru/WebApiMongoDB). I will soon update also the article to reflect all the changes. Please clone or download the latest changes.
Thanks,
Petru
Hello Petru,
I have followed all the above steps but The only output i get on the browser is the empty braces ” [ ]” and I don’t find the database and collection in MongoDB. Any help would be greatly appreciated.
Thanks
Hello Harika,
Connecting with RoboMongo to your local MongoDb installation, do you see the database “NotesDb” and collection “Note” ? If not, return to the solution, and check if SystemController is used and it doesn’t raise any exception (using the url: http://localhost:53617/system/init).
Kind regards
Petru
It’s http://localhost:(port)/api/system/init. port 5000 for iisexpress and 53617 for iis. Without the /api/ in there, it won’t populate the db
Yes, agree, and thanks for pointing out. The article shows the link correctly, but not my comment.
Youre awesome!
Hello,
I am trying to setup a similar structure but within a .NET Core web application instead of an API. I have everything setup in a similar way, but now I am trying to call the equivalent to your NotesController. I am unable to create INotesRepository to be sent into the constructor function, I am not familiar with how web API’s work therefore I don’t see how that is being sent into your NotesController function as well. Any information is much appreciated! Thanks, Al
Hello Al,
I have made earlier an example with a .NET Core web application (MVC) – here is the GitHub repository. https://github.com/fpetru/mongodb-aspnetmvc
Please let me know if it helps you.
Kind regards
Petru
Hi,
I want to connect to remote mongo db which is located in different server. Where i have to specify the mongo db server name
Hello
To change the connection settings please go to appsettings.json file, and change from localhost to other IP or server name. Please be very careful to not open MongoDb directly to the internet. It must be within a secured network.
https://github.com/fpetru/WebApiMongoDB/blob/master/appsettings.json
Best regards
Petru
Why create all files under one structure. why not add different class libraries and import them?
I have tried to create the project as simple as possible. When you make something bigger than this concise example, yes, it would be a good idea to structure it and make it modular, and easier to maintain.
Best regards
Petru
Why Transient and not scoped?
Hi Nicolas,
Thanks for your comment. I had an issue with the syntax highlighter. The code is now displayed correctly.
Kind regards
Petru
Hi Nicolas,
I usually choose Transient, then scaling up to Scoped or Singleton, when the situation calls for it. There is usually a connection pooling, with an implicit re-using the actual database connections, behind the hood, and for such a simple example, Transient was a better fit.
Scoped services are indeed good, since they can be used to maintain some context during the given request, and they are isolated from other requests happening at the same time. However, the logic of the app would need also to be more extended.
Kind regards
Petru
Thank you Petru
Best Regards
Hello. This article have a mistake. Instead
services.Configure(options =>
Should be
services.Configure(options =>
Sorry, instead of
services.Configure(options =>
Should be
services.Configure(options =>
I can’t write statement witn 🙁
Should be
services.Configure (options =>
Hi Artholy,
I am not sure I understand where the error is. Could you please add few more details ?
Thanks
Petru
this statement and comments have a problem with an angle brackets. The word between angle brackets dissapears.
Instead of
services.Configure(options =>
Should be
services.Configure OPEN ANGLE BRACKET Settings CLOSE ANGLE BRACKET (options =>
Thanks a lot for your comment. I checked, and the issues came from the syntax highlighter being not compatible with latest WordPress version. I fixed the code and it is now displayed correctly.
Kind regards
Petru
Thanks for the post, Peter. I got one question: I was stuck at the creating user part for mongodb. How could you db.createUser after setting “security authentication” to “enabled”? It throws and error as “not authorized on admin to execute command” every time when I try to creatUser, but if I comment the security line in mongod.conf file, it allows me to create a user. I looked it up but none of the solutions work for me. Do you know why this is happening?
Hi Claire,
Yes, it is true, and thanks for the hint. I have missed to present this step initially and now I have updated the article.
At first we need to disable the authentication, and create a single user with a superuser role. Then we would stop the server, and enable the authentication. From there, we would connect with this user (“admin” in our case) and we would run any other tasks (query, insert, new user creation etc.).
Kind regards
Petru
Oops sorry I spelled it wrong Petru.. Thank you for the quick reply! It works now
No problem. It is great, and if you have any other issues or suggestions, please let me know.
Kind regards
Petru
Salve Petru,
Felicitari pentru articol! Long time no see! Scrie-mi te rog pe email, chiar as vrea sa mai povestim!
Best,
John
Thank you for a really good and simple project. I greatly enjoyed building it in Visual Studio 15.
However, there seems to be some problems with both building and running the finished project in Visual Studio 17.
I get a lot of errors with references. I know Microsoft has decided to use *.csproj instead of project.json and all references are stored here from now on. But even when I follow your tutorial and add them manually via NuGet I still get errors. I also cannot use your completed version as it does not work in Visual Studio 17, so I was wondering if you could update your project to be compatable with Visual Studio 17 or maybe add a small section outlining how to do this. It is a really good tutorial and I would very much like to make it work with the latest version Visual Studio Community.
Hi Peter,
Thanks for your comments. Yes, I will update the article.
Kind regards
Petru
I have updated the project to Visual Studio 2017, and you could download it from GitHub.
Great work , Thanks for the article
Thanks a lot
Petru
great job, thank you very much!
Run first the initialization link:
http://localhost:53617/system/init
should be
http://localhost:53617/api/system/init
Thanks for your message. Yes, you are right, and I have updated the article 😉
How can I update the whole document (all properties) without set each property manually ?
I have updated the article, and included the code necessarily to make the updates of the MongoDB documents. Basically use ReplaceOneAsync. Please let me know if this worked for you.
should be
[HttpGet]
public Task<IEnumerable> Get()
{
return GetNoteInternal();
}
Thanks for your suggestion. It works just fine, and I have updated the article and the code in GitHub.
by the way, why return the notes as a string from the controller. Why not return something strongly typed and let the framework do the serialization to JSON like so:
[HttpGet]
public Task<IEnumerable> Get()
{
return GetNoteInternal();
}
private async Task<IEnumerable> GetNoteInternal()
{
var notes = await _noteRepository.GetAllNotes();
return notes;
}
great job, keep it coming!
Thanks a lot !