DEV Community

Cover image for Desarrollo de aplicaciones web con ASP.NET Core, DotVVM y MongoDB
Daniel Gomez Jaramillo for DotVVM

Posted on

Desarrollo de aplicaciones web con ASP.NET Core, DotVVM y MongoDB

Hoy en día seguramente hayas oído hablar de las bases de datos NoSQL. Es algo que últimamente está muy de moda en el mundo del desarrollo.

En la actualidad estamos acostumbrados a utilizar bases de datos SQL como MySQL, Oracle o SQL Server, pero desde hace ya algún tiempo han aparecido otras que reciben el nombre de NoSQL (Not only SQL – No sólo SQL) y que han llegado con la intención de hacer frente a las bases relacionales utilizadas por la mayoría de los usuarios.

Resumiendo, las bases de datos NoSQL, difieren en varios aspectos de las bases de datos relacionales de toda la vida como son: pueden manejar gran cantidad de datos, no se necesitan estructuras fijas (tablas, columnas etc.) para el almacenamiento de datos, no suelen permitir operaciones JOIN, entre otros aspectos.

Existen varios tipos de bases de datos NoSQL que podríamos agrupar distintos tipos: documentales, de grafo, de clave/valor, multivalor, orientadas a objetos o tabulares.

Aprovechando el aumento de la popularidad del ecosistema de .NET, en nuestro caso aprenderemos como realizar aplicaciones web en ASP.NET Core con el patrón de diseño MVVM (Modelo Vista Vista-Modelo) gracias a DotVVM y a través bases de datos documentales con MongoDB.

Recursos y herramientas necesarias:

El entorno de la solución

Para nuestro caso de estudio, tomaremos como documento de ejemplo la información de un estudiante para la realización de operaciones CRUD y manejaremos la aplicación en tres partes:

  1. Implementación del DAL (Data Access Layer): para manejar la conexión y el acceso a la base de datos documental en MongoDB.
  2. Implementación del BL (Business Layer): para el manejo de los servicios y la lógica del dominio de la aplicación.
  3. Implementación de la capa de presentación de la aplicación. En esta sección es donde DotVVM entra en acción.

Parte 1: Data Access Layer - DAL

Para trabajar con MongoDB es necesario instalar el siguiente paquete NuGet:

MongoDB.Driver

Como primer punto para crear nuestra aplicación, en el Data Access Layer debemos definir las colecciones que tendrá el dominio de la aplicación y definir un módulo de configuración para hacer referencia a las propiedades de la base de datos (cadena de conexión, nombre de la base de datos, y las colecciones de esta).

En la aplicación manejaremos la colección: Student. Si tuviéramos otras colecciones, por ejemplo: Profesor, Asignatura, etc; estas serán ubicadas en la carpeta Collections. Para nuestro caso de estudio, la colección Student estará definida de la siguiente manera:

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; }
}
Enter fullscreen mode Exit fullscreen mode

Para el almacenamiento y la transferencia de datos en MongoDB se emplea el formato BSON, el cual nos permite trabajar con documentos en MongoDB. En este caso, en la clase Student podemos especificar el nombre del atributo de la colección al que hacemos referencia (normalmente se emplean cuando los nombres son diferentes), el tipo de dato BSON y otras directivas. Por ejemplo, en MongoDB, la clave primaria de una colección esta identificada por el atributo: _id, lo cual nos permitirá identificar a un documento, en este caso, será el Id del estudiante el identificador principal. Los tipos de datos BSON los podemos encontrar en la documentación de MongoDB: https://docs.mongodb.com/manual/reference/bson-types/.

Por otro lado tenemos la interfaz y su implementación: DatabaseSettings para el manejo de las propiedades especificas de la base de datos con 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; }
}
Enter fullscreen mode Exit fullscreen mode

Los valores de cada uno de los atributos en DatabaseSettings estarán definidos en el archivo appsettings.json. Los nombres de las propiedades de JSON y C# son iguales para facilitar el proceso de asignación.

Archivo appsettings.json
{
  "DatabaseSettings": {
    "CollectionName": "Student",
    "ConnectionString": "mongodb://...",
    "DatabaseName": "StudentDb"
  }
}
Enter fullscreen mode Exit fullscreen mode

Para establecer la relación entre la clase DatabaseSettings y el archivo de configuración appsettings.json, es necesario definir este ajuste en el constructor y en el método ConfigureServices de la clase Startup.cs como se muestra a continuación:

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();
}
Enter fullscreen mode Exit fullscreen mode
Public void ConfigureServices(IServiceCollection services)
{
 // requires using Microsoft.Extensions.Options
 services.Configure<BookstoreDatabaseSettings>( Configuration.GetSection(nameof(DatabaseSettings)));
}
Enter fullscreen mode Exit fullscreen mode

En este código hay dos aspectos importantes por mencionar:

  • La instancia de configuración a la que la sección DatabaseSettings del archivo appsettings.json enlaza está registrada en el contenedor de inserción de dependencias. Por ejemplo, una propiedad ConnectionString del objeto DatabaseSettings se rellena con la propiedad DatabaseSettings:ConnectionString en appsettings.json.

  • La interfaz DatabaseSettings se registra en la inserción de dependencias con una duración de servicio de tipo singleton (patrón de diseño). Cuando se inserta, la instancia de la interfaz se resuelve en un objeto DatabaseSettings.

Parte 2: Business Layer - BL

Ahora debemos definir los modelos y crear los servicios para manejar la lógica de nuestra aplicación. En este caso, lo que se busca es tener un listado general de los estudiantes y la información específica de cada uno de ellos.

Modelos

Para ello, como primer punto definiremos nuestros modelos:

StudentList:

public class StudentListModel
{
    public int Id {get; set;}
    public string FirstName {get; set;}
    public string LastName {get; set;}
}
Enter fullscreen mode Exit fullscreen mode

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; }
}
Enter fullscreen mode Exit fullscreen mode

Y luego los servicios de nuestra aplicación.

Servicios

En este caso tenemos el servicio de Estudiantes que nos permitirá realizar operaciones CRUD: Crear, Leer, Actualizar y Borrar documentos en MongoDB. A continuación, vamos a ver cada uno de ellos.

A. Inicialización del servicio
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);
}
Enter fullscreen mode Exit fullscreen mode

De forma resumida, en esta parte el trabajo realizado consiste en establecer la conexión con MongoDB, seleccionando la base de datos con la cual trabajaremos y la colección correspondiente.

B. Leer documentos
public List<StudentListModel> GetAllStudents()
{
    return _students.Find(Student => true).ToList().Select(
        s => new StudentListModel
        {
            Id = s.Id,
            FirstName = s.FirstName,
            LastName = s.LastName
        }
    ).ToList();
}
Enter fullscreen mode Exit fullscreen mode

La primera opción que tenemos es la de obtener todos los documentos de la colección con la cual estamos trabajando. A través del atributo _students que hace referencia a la colección, podemos emplear los métodos que brinda MongoDB para la realización de operaciones CRUD, de consulta, entre otras.

En este caso se hace uso del método Find() que permite realizar consultas sobre los documentos de una colección. Con el resultado de esta consulta, podemos emplear LINQ (Language Integrated Query), un componente en el cual podemos realizar consultas a través de objetos (muy parecidas a SQL). LINQ nos servirá para establecer un listado de objetos de tipo StudentListModel con los documentos resultado de la consulta en MongoDB.

También podemos realizar consultas con condicionales, por ejemplo, la consulta que se muestra a continuación:

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;
}
Enter fullscreen mode Exit fullscreen mode
C. Crear documentos

Para la creación de documentos en una colección disponemos los métodos: InsertOne, para la inserción de un documento; e InsertMany, para la inserción de varios de ellos. Para este caso, el método que hemos destinado para este objetivo InsertStudentAsync recibe un modelo StudentDetailModel con los datos a ser insertados, el proceso consta en construir una clase Student (la que hace referencia a la base de datos) e insertarla posteriormente a través de la referencia _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);
}
Enter fullscreen mode Exit fullscreen mode
D. Actualizar documentos

Para la actualización de documentos se sigue la misma lógica de los métodos de creación e inserción. El documento referencia será actualizado según el identificador de este.

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);
}
Enter fullscreen mode Exit fullscreen mode
E. Eliminar documentos

Para eliminar un documento, se utiliza el método DeleteOne, el cual permite eliminar un documento de acuerdo al identificador del mismo.

public async Task DeleteStudentAsync(int IdStudent)
{
    await _students.DeleteOneAsync(Student => Student.Id == IdStudent);
}
Enter fullscreen mode Exit fullscreen mode

Hasta este momento hemos definido los servicios de nuestro modelo de dominio de la aplicación. Lo que resta es utilizar estos servicios desde una aplicación, la cual será diseñada en ASP.NET Core con DotVVM.

Parte 3: Capa de presentación de la aplicación

Ahora que ya tenemos definido el DAL y el BL, ahora debemos realizar el diseño de la pagina web para que el usuario pueda interactuar con ella y en este caso, realizar las operaciones CRUD para el manejo de Estudiantes.

Antes de continuar, es necesario asociar a nuestra aplicación (registrar) los servicios que se utilizaran. Para esto, debemos dirigirnos al método ConfigureServices en la clase Startup y tener algo como esto:

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>();
}
Enter fullscreen mode Exit fullscreen mode

Ahora bien, esta es la parte en la que DotVVM entra en acción. Cada página en DotVVM consta de dos archivos:

  • Una vista, que se basa en la sintaxis HTML y describe cómo se verá la página.
  • Un modelo de la vista, que es una clase en C# que describe el estado de la página (por ejemplo, valores en los campos del formulario) y maneja las interacciones del usuario (por ejemplo, clics de botones).

Para nuestro caso tendremos cuatro Vistas y cuatro modelos asociados a estas vistas:

  • Default: será la pagina principal de la aplicación en donde se visualizará el listado de los estudiantes registrados.
  • Create: una página conformada por un formulario para crear nuevos estudiantes.
  • Detail: para ver a detalle la información de un estudiante.
  • Edit: para modificar la información de un estudiante o eliminarlo.

Teniendo en cuenta los archivos Views y Viewmodels, en Visual Studio visualizaremos algo como esto:

A continuación, veamos a detalle el View y Viewmodel de Default y sus componentes.

Viewmodel del Default

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();
        }
    }
Enter fullscreen mode Exit fullscreen mode

Como primer punto tenemos la instancia de StudentService que nos permitirá acceder a los métodos para manejar las operaciones definidas en el servicio de Student implementado en el BL.

Luego tenemos la definición List<StudentListModel> Students de tipo StudentListModel definido en las clases de los modelos en el BL, que tendrá el listado de los estudiantes (Id, FirstName y LastName) para cargarlos en una tabla en la página principal de la aplicación web.

Una característica muy importante por mencionar es la declaración [Bind(Direction.ServerToClient)]. Este tipo de propiedades permiten especificar que información va a ser transferida del servidor al cliente o del cliente al servidor al usar los Binding Directions. Considerando el caso del listado de estudiantes, en muchas ocasiones no es necesario transferir todo el modelo de vista en ambas direcciones. Del servidor a la vista será suficiente en este caso.

Mas información sobre Binding Directions aquí:
https://www.dotvvm.com/docs/tutorials/basics-binding-direction/2.0.

Finalmente en el Viewmodel de Default tenemos el método PreRender(), que permite realizar cierto tipo de operaciones que serán realizadas al momento de cargar la Vista. En este caso, se realizará una consulta a la base de datos a través de la llamada al método del servicio studentService.GetAllStudentsAsync(), luego los resultados serán asignados en la colección Students de tipo StudentListModel y luego la página será cargada junto con los demás componentes de diseño.

View de Default

<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>
Enter fullscreen mode Exit fullscreen mode

Como Podemos ver el View de Default, el diseño de la página se torna en el manejo de sentencias HTML y CSS. Para nuestro caso de estudio, hay algunas sentencias y características interesantes que podemos analizar:

GridView: <dot:GridView … >, un control de DotVVM que nos permite crear una tabla o cuadrilla para visualizar un determinado listado de información. En HTML estaríamos hablando de la etiqueta <table>. Uno de sus atributos es DataSource: DataSource="{value: Students}", el cual permite especificar la fuente de datos, en este caso hacemos referencia al listado de estudiantes: Students, el cual fue definido en el Viewmodel como vimos anteriormente.

Además de las cuadriculas, DotVVM también tiene otros componentes de control personalizados, por ejemplo, para cajas de texto, ComboBox, manejo de archivos, entre otros más que nos permiten mantener una comunicación entre la Vista y las fuentes de información definidas en los Viewmodels. Ver más aquí: https://www.dotvvm.com/docs/controls/.

Siguiendo con nuestro análisis, en el GridView tenemos las columnas Id, FirstName y LastName de los estudiantes, pero adicionalmente, también podemos adicionar columnas para realizar operaciones sobre algún registro en específico. En este caso, con RouteLink, podemos definir un hipervínculo que construye una URL a partir de nombres de rutas y valores de parámetros para redirigirnos a otras paginas o realizar operaciones adicionales, por ejemplo, ver detalle o modificar el registro de un estudiante en particular según su Id:

<dot:RouteLink RouteName="Edit" Param-Id="{{value: Id}}" />
Enter fullscreen mode Exit fullscreen mode

Estas rutas y sus parámetros correspondientes las debemos definir en el archivo DotvvmStartup.cs en el método ConfigureRoutes de la siguiente manera:

config.RouteTable.Add("Edit", "edit/{Id}", "Views/Edit.dothtml");
Enter fullscreen mode Exit fullscreen mode

Para aprender más sobre el Routing en DotVVM puedes ir aquí:
https://www.dotvvm.com/docs/tutorials/basics-routing/2.0.

Para las paginas Crear, Ver Detalle y Modificar se sigue la misma lógica en cuanto a los componentes View y Viewmodel. Al agregar algunos registros de estudiantes en nuestra aplicación y cargar la página principal con el listado de los mismos, tendremos algo como esto:

Plus: Recursos para el alojamiento de datos de MongoDB y bases de datos NoSQL en la nube

Hoy en día la tendencia esta en publicar las paginas web en la nube, para ello existen diversos servicios que nos permiten cumplir con estos objetivos, sea cual sea el gestor de base de datos que se este utilizando, ya sea SQL o NoSQL. Para las bases de datos NoSQL tenemos estas opciones recomendadas:

A. mLab

mLab es un servicio gratuito de base de datos en la nube administrado que aloja bases de datos MongoDB. Tiene el mismo funcionamiento de MongoDB local, lo único que cambia es la cadena de conexión. Para conocer más de mLab podemos ir a: https://mlab.com/.

B. Azure CosmosDB - La API para MongoDB

Azure Cosmos DB es un servicio de base de datos con varios modelos y de distribución global de Microsoft. Para este caso, Azure proporciona una API de Cosmos DB para MongoDB, la cual, además de trabajar con todas las funcionalidades de MongoDB, también podemos hacer uso de todo el poder de Azure para el manejo de bases de datos NoSQL, por ejemplo: distribución global, particionamiento automático, garantías de disponibilidad y latencia, cifrado en reposo, copias de seguridad, entre otras. Para conocer más de la API de Cosmos DB para MongoDB podemos visitar este documento de Microsoft: https://docs.microsoft.com/en-us/azure/cosmos-db/mongodb-introduction.

¿Qué sigue?

Con este articulo tutorial hemos aprendido como trabajar con MongoDB en aplicaciones con ASP.NET Core y DotVVM a través de Visual Studio 2019 para implementar operaciones CRUD sobre una colección de Estudiantes.

El código de este tutorial lo podemos encontrar en el siguiente repositorio en GitHub: DotVVM + MongoDB.

Recursos adicionales:

Aquí se encuentran otros artículos para que puedas seguir adquiriendo nuevos conocimientos sobre .NET:

¡Gracias!
Nos vemos en Twitter!! :)

Discussion (0)