In the previous article in this series on MongoDB, I explained how to get started with the C# driver in a simple Windows console application. I analyzed several options for generating your own domain classes and mapping them to their underlying MongoDB document-oriented schemata. In this third and final installment in the series, I dive into using the MongoDB C# driver in an ASP.NET MVC Web application. To follow along, you need to understand the concepts introduced in the previous article.
Matching Document Field Names to Domain Class Requirements
Up to this point in the series, I've shown examples of the MongoDB mapping configurations and the operations to apply when you use your own C# domain classes. The documents stored in the collections don't follow the name conventions that are appropriate for the fields in the domain classes, and therefore, it's necessary to tweak mappings. However, one of the great advantages of MongoDB and its C# driver is that you can reduce the required number of mapping configurations to a minimum by following the most appropriate conventions according to the needs of your domain classes.
I want to develop an ASP.NET MVC Web application in C# and I need the properties in each POCO (or Plain Old C# Object) to use Pascal case. Thus, for example, instead of using relase_date
as the field name for a document in the games
collection, I can just use the same name that I require for the property, ReleaseDate
. This way, I can remove the specific mapping configuration for the ReleaseDate
property in my domain class and reduce maintenance.
Removing the mapping information with field names for the documents in the collections has a price. If someone changes the name of a property in a domain class while refactoring code, there are going to be undesired side effects in the operations with the related collection. For example, if someone renames the ReleaseDate
property to FirstReleaseDate
for the Game
class, the new insert operations to the games
collection performed through the refactored C# application will add a different field to each new document: FirstReleaseDate
instead of ReleaseDate
. No exceptions will occur during the inserts because each document might have a completely different schema without problems in any MongoDB collection. In addition, when retrieving old documents in the games
collection through the refactored C# application, FirstReleaseDate
won't be filled up with values from the document. The old documents have the FirstRelease
field, which won't be mapped to any property in the refactored domain class.
Another option is to override the conventions that determine the decisions that the MongoDB C# drivers makes when a class is automatically mapped. For example, you can create a new MongoDB.Bson.Serialization.Conventions.ConventionProfile
instance, and call its SetIdGeneratorConvention
to specify the default mechanism to use for ID generation. Then, you can apply the ConventionProfile
to all the classes that belong to a specific namespace. However, overriding the conventions requires you to manually address low-level serialization configuration issues.
So, whenever you decide to reduce mapping information to the minimum by following conventions or overriding them, you must be careful with refactorings that would be safe when not working with automatically mapped domain classes.
Creating a Data Layer for an ASP.NET MVC 4 Web Application
Let's work with a complete example of an ASP.NET MVC 4 Web application that interacts with a MongoDB database and reduces mapping information to the minimum. To keep the example simple, I'll focus on the data layer. I've reduced the necessary code by using the data domain classes as ViewModels. If you already worked with ASP.NET MVC 3 or 4, you know that it is a good idea to have your ViewModels completely decoupled from the data domain classes and types. Thus, we don't consider the Views and ViewModels as pieces of code that follow best practices. In addition, to keep code simple and easy to understand, I haven't included all the necessary exception handling blocks. My main goal here is simply to explain the features of the MongoDB C# driver.
The sample ASP.NET MVC Web application has the following requirements:
- List the existing games.
- Create a new game.
- List the existing players.
- View the details for a player and his/her registered scores.
- Create a new player.
- Delete an existing player.
- Edit an existing player.
- Allow a player to see the available games and play one of them.
- Register a random score when the player decides to play a game.
First, let's create a new ASP.NET MVC 4 Web Application in Visual Studio 2012. I've used RetrogamesWeb
for the solution name (see Figure 1).
Figure 1: Creating a new ASP.NET MVC 4 Web application in Visual Studio 2012.
Select the Basic
project template in the New ASP.NET MVC 4 Project dialog box, make sure Razor
is the value for the View engine dropdown (see Figure 2).
Figure 2: Selecting the Basic template and the Razor view for a new ASP.NET MVC 4 Web Application in Visual Studio 2012.
Now, add a new project to the solution. Select Visual C# | Windows | Class Library in the Add New Project dialog box. I've used RetrogamesWeb.Data
for the solution name (see Figure 3).
Figure 3: Adding a C# Class Library to the solution in Visual Studio 2012.
Next, add the following two references to the new class library, as I explained in the previous article:
- MongoDB.Bson.dll.
- MongoDB.Driver.dll.
The RetrogamesWeb.Data
project includes interfaces and classes for the entities within the Entities
folder. The classes that define the entities include the minimum possible annotations for mappings. As mentioned, the entities will also be used as ViewModels, but the code doesn't include data annotations to avoid confusion. (However, you can create the required ViewModels, add the mapping code, and include the data annotations there.) I explained the IMongoEntity
interface and the entities in "MongoDB with C#."
The project includes interfaces and classes that provide repository services for the entities within the Serivces
folder. I'll explore these interfaces and classes as I provide their source code. In addition, there is a MongoConnectionHandler
class that performs the steps necessary to get a reference to the collection object from the Mongo database object, but in a generic way that all the services can use by following conventions.
The project includes the following folders and files (see Figure 4):
Entities
folder- Entities\Game.cs
- Entities\Gender.cs
- Entities\IMongoEntity.cs
- Entities\MongoEntity.cs
- Entities\Player.cs
- Entities\Score.cs
Services
folder- Services\EntityService.cs
- Services\GameService.cs
- Services\IEntityService.cs
- Services\PlayerService.cs
- MongoConnectionHandler.cs
Figure 4: The RetrogamesWeb.Data class library project structure in Solution Explorer.
Create the Entities
folder and add a new interface, IMongoEntity
:
namespace RetrogamesWeb.Data.Entities { using System; using MongoDB.Bson; public interface IMongoEntity { ObjectId Id { get; set; } } }
Now, add the MongoEntity
class that implements the previously created IMongoEntity
interface within the Entities
folder:
namespace RetrogamesWeb.Data.Entities { using MongoDB.Bson; using MongoDB.Bson.Serialization.Attributes; public class MongoEntity: IMongoEntity { [BsonId] public ObjectId Id { get; set; } } }
Add a new class, Game
(that inherits from MongoEntity
), within the Entities
folder:
namespace RetrogamesWeb.Data.Entities { using System; using System.Collections.Generic; using MongoDB.Bson.Serialization.Attributes; [BsonIgnoreExtraElements] public class Game : MongoEntity { public Game() { Categories = new List<string>(); } public string Name { get; set; } [BsonDateTimeOptions(DateOnly = true)] public DateTime ReleaseDate { get; set; } public List<string> Categories { get; set; } public bool Played { get; set; } } }
Notice that the only attribute specified for the properties is [BsonDateTimeOptions(DateOnly = true)]
. This way, the ReleaseDate
consists of a date only (the time is always going to be 12:00:00 AM in the field value).
Add a new class, Gender
, within the Entities
folder:
namespace RetrogamesWeb.Data.Entities { public enum Gender { Female, Male } }
Add a new class, Score
, within the Entities
folder:
namespace RetrogamesWeb.Data.Entities { using System; using MongoDB.Bson; using MongoDB.Bson.Serialization.Attributes; [BsonIgnoreExtraElements] public class Score { public ObjectId GameId { get; set; } public string GameName { get; set; } public int ScoreValue { get; set; } public DateTime ScoreDateTime { get; set; } } }