Today you've probably heard about NoSQL databases. It's something that's been very fashionable lately in the development world.
At present we are used to SQL databases like MySQL, Oracle or SQL Server, but for some time have appeared others that are called NoSQL (Not only SQL) and that have come with the intention of dealing with the bases related used by most users.
In short, NoSQL databases differ in several aspects of lifelong relational databases such as: they can handle a large amount of data, no fixed structures (tables, columns, etc.) are needed for data storage, they do not usually allow JOIN operations, among other aspects.
There are several types of NoSQL databases that we could group together different types: documentaries, graphs, key/value, multivalue, object-oriented, or tabular.
Taking advantage of the growing popularity of the .NET ecosystem, in our case, we will learn how to make web applications in ASP.NET Core with the MVVM design pattern (Model-View-Viewmodel) thanks to DotVVM and through document databases with MongoDB.
Resources and tools needed:
- Visual Studio 2019: http://visualstudio.microsoft.com/en/downloads/.
The workload in Visual Studio 2019: ASP.NET and web development.
DotVVM extension for Visual Studio 2019: www.dotvvm.com/landing/dotvvm-for-visual-studio-extension.
The solution environment
For our case study, we will take as an example document the information of a student to develop CRUD operations and manage the application in three parts:
- Data Access Layer Implementation: to manage connection and access to the document database in MongoDB.
- Implementation of the BL (Business Layer): for the management of services and logic of the application domain.
- Implementation of the application presentation layer. This section is where DotVVM comes into action.
Part 1: Data Access Layer - DAL
To work with MongoDB you need to install the following NuGet package:
MongoDB.Driver
As the first point to create our application, in the 'Data Access Layer' we must define the collections that the application domain will have and define a configuration module to reference the properties of the database (connection string, name of the database, and database collections).
In the application we will handle the collection: Student
. If we had other collections, for example: Professor
, Subject
, etc; these will be located in the Collections
folder. For our case study, the Student
collection will be defined as follows:
public class Student
{
[BsonId]
[BsonRepresentation(BsonType.Int32)]
[BsonElement("_id")]
public int Id { get; set; }
[BsonElement("FirstName")]
public string FirstName { get; set; }
[BsonElement("LastName")]
public string LastName { get; set; }
[BsonElement("About")]
public string About { get; set; }
[BsonElement("EnrollmentDate")]
[BsonRepresentation(BsonType.DateTime)]
public DateTime EnrollmentDate { get; set; }
}
BSON format is used for storage and data transfer in MongoDB, which allows us to work with documents in MongoDB. In this case, in the Student
class we can specify the name of the attribute of the collection we refer to (usually used when the names are different), the BSON data type, and other directives. For example, in MongoDB, the primary key of a collection is identified by the attribute: _id
, which will allow us to identify a document, in this case, the Id
of the student will be the primary identifier. The BSON data types can be found in the MongoDB documentation: https://docs.mongodb.com/manual/reference/bson-types/.
On the other hand we have the interface and its implementation: DatabaseSettings
for handling the specific properties of the database with MongoDB:
public class DatabaseSettings : IDatabaseSettings
{
public string CollectionName { get; set; }
public string ConnectionString { get; set; }
public string DatabaseName { get; set; }
}
public interface IDatabaseSettings
{
string CollectionName { get; set; }
string ConnectionString { get; set; }
string DatabaseName { get; set; }
}
The values of each of the attributes in DatabaseSettings
will be defined in the appsettings.json
file. The names of the JSON and C-properties are the same to make the mapping process easier.
appsettings.json file
{
"DatabaseSettings": {
"CollectionName": "Student",
"ConnectionString": "mongodb://...",
"DatabaseName": "StudentDb"
}
}
To establish the relation between the DatabaseSettings
class and the appsettings.json
configuration file, we need to define this setting in the constructor and in the ConfigureServices
method of the Startup.cs
class as shown below:
public IConfiguration Configuration { get; private set; }
public Startup(IHostingEnvironment env)
{
// Set up configuration sources.
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json");
builder.AddEnvironmentVariables();
Configuration = builder.Build();
}
Public void ConfigureServices(IServiceCollection services)
{
// requires using Microsoft.Extensions.Options
services.Configure<BookstoreDatabaseSettings>( Configuration.GetSection(nameof(DatabaseSettings)));
}
There are two important aspects to say this code:
The configuration instance to which the
DatabaseSettings
section of theappsettings.json
file links is registered in the dependency injection container. For example, aConnectionString
property of theDatabaseSettings
object is populated with theDatabaseSettings:ConnectionString
property inappsettings.json
.The
DatabaseSettings
interface is registered in dependency injection singleton service life (design pattern). When inserted, the interface instance resolves to aDatabaseSettings
object.
Part 2: Business Layer - BL
Now we need to define the models and create the services to handle the logic of our application. In this case, what we are looking for is to have a general list of students and the specific information of each of them.
Models
To do this, as a first point we will define our models:
StudentList:
public class StudentListModel
{
public int Id {get; set;}
public string FirstName {get; set;}
public string LastName {get; set;}
}
StudentDetailModel:
public class StudentDetailModel
{
public int Id { get; set; }
[Required]
public string FirstName { get; set; }
[Required]
public string LastName { get; set; }
[Required]
public DateTime EnrollmentDate {get; set;}
public string About { get; set; }
}
And then the services of our application.
Services
In this case, we have the 'Students' service that will allow us to implement CRUD operations: Create, Read, Update and Delete documents in MongoDB. Next, let's look at each of them.
A. Service initialization
private readonly IMongoCollection<Student> _students;
public StudentService(IDatabaseSettings settings)
{
var client = new MongoClient(settings.ConnectionString);
var database = client.GetDatabase(settings.DatabaseName);
_students = database.GetCollection<Student>(settings.CollectionName);
}
In short, in this part the work done is to establish the connection with MongoDB, selecting the database with which we will work and the corresponding collection.
B. Read documents
public List<StudentListModel> GetAllStudents()
{
return _students.Find(Student => true).ToList().Select(
s => new StudentListModel
{
Id = s.Id,
FirstName = s.FirstName,
LastName = s.LastName
}
).ToList();
}
The first option we have is to get all the documents from the collection we are working with. Through the _students
attribute that refers to the collection, we can use the methods provided by MongoDB to perform CRUD, query, and other operations.
In this case, the Find()
method is used to query documents in a collection. With the result of this query, we can use LINQ (Language Integrated Query), a component in which we can query through objects (very similar to SQL). LINQ will help us to set a list of objects of type StudentListModel
with the query result documents in MongoDB.
We can also query with conditionals, for example, the following query:
public async Task<StudentDetailModel> GetStudentByIdAsync(int studentId)
{
Student document = await _students.FindAsync<Student>(Student => Student.Id == studentId).Result.FirstOrDefaultAsync();
StudentDetailModel student = new StudentDetailModel();
student.Id = document.Id;
student.FirstName = document.FirstName;
student.LastName = document.LastName;
student.About = document.About;
student.EnrollmentDate = document.EnrollmentDate;
return student;
}
C. Create documents
For the creation of documents in a collection we have the methods: InsertOne
, for inserting a document; and InsertMany
, for the insertion of several of them. For this case, the method that we have intended for this purpose InsertStudentAsync
receives a model StudentDetailModel
with the data to be inserted, the process consists in constructing a Student
class (the one that references the database) and inserting it then through the reference _students
.
public async Task InsertStudentAsync(StudentDetailModel student)
{
var document = new Student()
{
Id = student.Id,
FirstName = student.FirstName,
LastName = student.LastName,
About = student.About,
EnrollmentDate = student.EnrollmentDate
};
await _students.InsertOneAsync(document);
}
D. Update documents
The same logic of the creation and insert methods is followed for document update. The reference document will be updated according to the id of the reference document.
public async Task UpdateStudentAsync(StudentDetailModel student)
{
var document = new Student()
{
Id = student.Id,
FirstName = student.FirstName,
LastName = student.LastName,
About = student.About,
EnrollmentDate = student.EnrollmentDate
};
await _students.ReplaceOneAsync(Student => Student.Id == student.Id, document);
}
E. Delete documents
To delete a document, the DeleteOne
method is used, which allows you to delete a document according to the document ID.
public async Task DeleteStudentAsync(int IdStudent)
{
await _students.DeleteOneAsync(Student => Student.Id == IdStudent);
}
So far we have defined the services of our application domain model. What remains is to use these services from an application, which will be designed in ASP.NET Core with DotVVM.
Part 3: Application Presentation Layer
Now that we have defined the DAL
and the BL
, we must now perform the design of the website so that the user can interact with it and in this case, perform CRUD operations for the management of Students.
Before proceeding, it is necessary to associate with our application (register) the services that will be used. For this, we should look at the ConfigureServices
method in the Startup
class and have something like this:
public void ConfigureServices(IServiceCollection services)
{
services.AddDataProtection();
services.AddAuthorization();
services.AddWebEncoders();
services.AddTransient(typeof(StudentService));
services.Configure<DatabaseSettings>(Configuration.GetSection(nameof(DatabaseSettings)));
services.AddSingleton<IDatabaseSettings>(sp => sp.GetRequiredService<IOptions<DatabaseSettings>>().Value);
services.AddDotVVM<DotvvmStartup>();
}
Now, this is the part where DotVVM comes into action. Each page in DotVVM consists of two files:
- A view, which is based on HTML syntax and describes how the page will look.
- A view model, which is a class in CTM that describes the state of the page (for example, values in the form fields) and handles user interactions (for example, button clicks).
In our application we will have four Views and four models associated with these views:
- Default: will be the main page of the application where the list of registered students will be displayed.
- Create: A page made up of a form to create new students.
- Detail: to view a student's information in detail.
- Edit: to modify a student's information or delete it.
Considering the Views and Viewmodels files, in Visual Studio we'll see something like this:
Next, let's take a closer look at the View and Viewmodel of Default
and its components.
Default viewmodel
public class DefaultViewModel : MasterPageViewModel
{
private readonly StudentService studentService;
public DefaultViewModel(StudentService studentService)
{
this.studentService = studentService;
}
[Bind(Direction.ServerToClient)]
public List<StudentListModel> Students { get; set; }
public override async Task PreRender()
{
Students = await studentService.GetAllStudentsAsync();
await base.PreRender();
}
}
As a first point, we have the StudentService
instance that will allow us to access the methods to handle the operations defined in the Student
service implemented in the BL.
Then we have the definition List<StudentListModel> Students
of type StudentListModel
defined in the model classes in the BL
, which will have the list of students (Id
, FirstName
and LastName
) to load them into a table on the main page of the web application.
A very important feature to mention is the [Bind(Direction.ServerToClient)]
declaration. These types of properties allow you to specify which information is to be transferred from the server to the client or from the client to the server when using Binding Directions. Considering the case of the student list, in many cases, it is not necessary to transfer the entire view model in both directions. From the server to the eye will suffice in this case.
Learn more about Binding Directions here: https://www.dotvvm.com/docs/tutorials/basics-binding-direction/2.0.
Finally, in the Viewmodel
of Default
we have the PreRender()
method, which allows you to perform certain types of operations that will be performed when loading the View. In this case, a query will be made to the database by calling the service method studentService.GetAllStudentsAsync()
, then the results will be assigned in the Students
collection of type StudentListModel
and then the page will be loaded along with the other design components.
Default view
<dot:Content ContentPlaceHolderID="MainContent">
<div class="page-center">
<div class="page-grid-top">
<div class="student-image"></div>
<h1>Student List</h1>
<dot:RouteLink Text="New Student" RouteName="CRUD_Create" class="page-button btn-add btn-long"/>
</div>
<dot:GridView DataSource="{value: Students}" class="page-grid">
<Columns>
<dot:GridViewTextColumn ValueBinding="{value: Id}" HeaderText="Id" />
<dot:GridViewTextColumn ValueBinding="{value: FirstName}" HeaderText="Firstname" />
<dot:GridViewTextColumn ValueBinding="{value: LastName}" HeaderText="Lastname" />
<dot:GridViewTemplateColumn>
<dot:RouteLink Text="Detail" RouteName="CRUD_Detail" Param-Id="{{value: Id}}" />
</dot:GridViewTemplateColumn>
<dot:GridViewTemplateColumn>
<dot:RouteLink Text="Edit" RouteName="CRUD_Edit" Param-Id="{{value: Id}}" />
</dot:GridViewTemplateColumn>
</Columns>
<EmptyDataTemplate>
There are no registered students. First sign in or sign up and add some students.
</EmptyDataTemplate>
</dot:GridView>
</div>
</dot:Content>
As we can see the Default View, the layout of the page becomes the handling of HTML and CSS statements. For our case study, there are some interesting statements and features that we can analyze:
GridView: <dot:GridView ... >
, a DotVVM control that allows us to create a table or grid to display a certain list of information. In HTML we would be talking about the <table>
tag. One of its attributes is DataSource: DataSource="{value: Students}"
, which allows you to specify the data source, in this case, we refer to the list of students: Students
, which was defined in the Viewmodel
as we saw earlier.
In addition to grids, DotVVM also has other custom control components, for example, for text boxes, ComboBox
, file handling, among others that allow us to maintain communication between the View and the sources of information defined in Viewmodels
. See more here: https://www.dotvvm.com/docs/controls/.
Continuing our analysis, in the GridView we have the columns Id
, FirstName
and LastName
of the students, but additionally, we can also add columns to perform operations on a specific record. In this case, with RouteLink
, we can define a hyperlink that constructs a URL from path names and parameter values to redirect us to other pages or perform additional operations, for example, view detail or modify the record of a student in particular according to their Id
:
<dot:RouteLink RouteName="Edit" Param-Id="{{value: Id}}" />
These paths and their corresponding parameters must be defined in the file DotvvmStartup.cs
in the ConfigureRoutes
method as follows:
config.RouteTable.Add("Edit", "edit/{Id}", "Views/Edit.dothtml");
To learn more about routing in DotVVM you can go here: https://www.dotvvm.com/docs/tutorials/basics-routing/2.0.
The Create, View Detail, and Modify pages follow the same logic for the View
and Viewmodel
components. When you add some student records in our app and load the homepage with the list of them, we'll have something like this:
Plus: Resources for hosting MongoDB data and NoSQL databases in the cloud
Today the trend is in publishing web pages in the cloud, for this, there are several services that allow us to meet these objectives, whatever database manager is being used, whether SQL or NoSQL. For NoSQL databases we have these recommended options:
A. mLab
mLab is a free managed cloud database service that hosts MongoDB databases. It has the same local MongoDB operation, the only thing that changes is the connection string. To learn more about mLab you can go to: https://mlab.com/.
B. Azure CosmosDB - The API for MongoDB
Azure Cosmos DB is a multi-model, global Microsoft distribution database service. For this case, Azure provides a Cosmos DB API for MongoDB, which, in addition to working with all the capabilities of MongoDB, we can also use the full power of Azure for handling NoSQL databases, for example: global distribution, automatic partitioning, availability, and latency guarantees, encryption at rest, backups, among others. To learn more about the Cosmos DB API for MongoDB we can visit this Microsoft document: https://docs.microsoft.com/en-us/azure/cosmos-db/mongodb-introduction.
What's next?
With this tutorial, we learned how to work with MongoDB in applications with ASP.NET Core and DotVVM through Visual Studio 2019 to implement CRUD operations on a collection of Students.
The code in this tutorial can be found in the following repository on GitHub: DotVVM + MongoDB.
Thank you!
See you on Twitter!! :)
Top comments (0)