DEV Community

Cover image for Working with web services through ASP.NET Core and DotVVM (A step by step guide)
Daniel Gomez for DotVVM

Posted on • Edited on

Working with web services through ASP.NET Core and DotVVM (A step by step guide)

Today there are many organizations that need to communicate and exchange data with external applications, in order to acquire web services with which they do not have and meet the needs of their potential customers.

In more technical terms, the first goal is to implement the backend of a given system that accesses the database and provides services for the manipulation of this data. From this, the second objective is to use these services, either from a website or from a mobile application, in order to worry only about the design of the frontend in this second instance.

Overall idea of the solution:

For this article, we have two objectives. The first is about building a Rest API that will make available web services for the handling of data from a database with MySQL. The second objective is to use these services in a second project through a web page implemented with DotVVM.

Note: The source code used in this article is available on GitHub in the following repositories: REST API with ASP.NET Core and MySQL and Consuming Web API in DotVVM with ASP.NET Core.

Activities:

The article will have two important parts:

  • Part 1: Developing a Web API with ASP.NET Core
  • Part 2: Consuming HTTP Web Services with DotVVM

Resources needed:

To follow this article step by step or run the included demo, it is necessary to have the following tools in operation:

  • MySQL.
  • .NET Core SDK.
  • Visual Studio 2019.
  • The Web Development and ASP.NET workload for Visual Studio 2019.
  • The DotVVM extension for Visual Studio 2019.

Part 1: Developing a Web API with ASP.NET Core

In this first part we will have three important parts:

  • 1. Set the database.
  • 2. Set database access from ASP.NET Core through Entity Framework.
  • 3. Set controllers and their functionalities. These methods will be responsible for providing web services for later use.

As a case study for this tutorial will handle user information through CRUD operations: create, read, update, and delete.

The database for the application domain

The database consists of a single table named User, with attributes: Int, FirstName, LastName, Username, Password and EnrrollmentDate.

The SQL statement for creating the User table is as follows:

CREATE TABLE `user2` (
  `Id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
  `FirstName` VARCHAR(45) NOT NULL,
  `LastName` VARCHAR(45) NOT NULL,
  `Username` VARCHAR(45) NOT NULL,
  `Password` VARCHAR(45) NOT NULL,
  `EnrollmentDate` datetime NOT NULL 
) AUTO_INCREMENT=1;

Enter fullscreen mode Exit fullscreen mode

All right, with the database ready we can start with the implementation of the first project for the development of API Rest services.

ASP.NET Core project with Web API type

In Visual Studio 2019, the first thing we'll do is create a new project of type ASP.NET Core Web Application (within the .NET Core - C# category):

After specifying the project name, we will select the type template: API, for the ASP.NET Core Web project to create:

With this project, we will create access to the database and implement the corresponding driver, in this case, for the User class.

Access to the database with Entity Framework

To establish entities through classes and the database connection, we can use the Entity Framework Database First approach, which allows you to scaffold from the database to the project, that is, generate classes automatically according to the entities established in the database and the connection in the project.

For this purpose, we need to install three NuGet packages:

  • Microsoft.EntityFrameworkCore.Design
  • Microsoft.EntityFrameworkCore.Tools
  • MySql.Data.EntityFrameworkCore

In case you are working with SQL Server, the NuGet package to install will be: Microsoft.EntityFrameworkCore.SQLServer.

Note: To find the NuGet Package Admin Center we can go to the options menu -> project -> Handle NuGet packages.

With the installation of these NuGet packages, we will now open the Package Management Console to enter a command that will allow you to scaffold from the database:

Command:

Scaffold-DbContext "server=servername;port=portnumber;user=username;password=pass;database=databasename" MySql.Data.EntityFrameworkCore -OutputDir Entities -f
Enter fullscreen mode Exit fullscreen mode

The result is as follows:

The User class is defined as follows:

public partial class User
{
    public int Id { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string Username { get; set; }
    public string Password { get; set; }
    public DateTime EnrollmentDate { get; set; }
}

Enter fullscreen mode Exit fullscreen mode

And the DBContext, which has the configuration with the database, whose main method OnConfiguring will look something like this:

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            if (!optionsBuilder.IsConfigured)
            {                optionsBuilder.UseMySQL("server=localhost;port=3306;user=root;password=;database=database");
            }
        }

Enter fullscreen mode Exit fullscreen mode

Now, it is not as appropriate as the database connection string is specified in this OnConfiguring method, to do this, we will specify the connection string in the appsettings.json file as follows:

"AllowedHosts": "*",
"ConnectionStrings": {
  "DefaultConnection": "server=servername;port=portnumber;user=username;password=pass;database=databasename;"
}
Enter fullscreen mode Exit fullscreen mode

Then, in the Startup class in ConfigureServices method we add as a service to the DBContext and reference the DefaultConnection property specified in the appsettings.json file:

public void ConfigureServices(IServiceCollection services)
{
    services.AddEntityFrameworkMySQL()
        .AddDbContext<DBContext>(options =>
        {
            options.UseMySQL(Configuration.GetConnectionString("DefaultConnection"));
        });
    services.AddControllers();
}
Enter fullscreen mode Exit fullscreen mode

In this case, by returning to the DBContext class, we clear the connection string specified in the OnConfiguring method. In the end, we would have the empty method:

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{}
Enter fullscreen mode Exit fullscreen mode

And the constructor of the DBContext would be constituted as follows:

public DBContext(DbContextOptions<DBContext> options)
    : base(options) {}
Enter fullscreen mode Exit fullscreen mode

With these steps, we already have the connection and configurations necessary to work with the database in ASP.NET Core with the help of Entity Framework.

Set DTOs - Data Transfer Objects

In order to transport the data between the processes for database management and the processes to work with the web services, it is advisable to establish the DTO classes for each entity of the project, in this case, a DTO for the User entity.

To do this we will create a new folder within the project called DTO and create a class called UserDTO, whose attributes will be the same as the User class defined in the Entities section above:

public class UserDTO
{
    public int Id { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string Username { get; set; }
    public string Password { get; set; }
    public DateTime EnrollmentDate { get; set; }
}
Enter fullscreen mode Exit fullscreen mode

Web API Controllers

Now what we'll do is add the handlers, in this case, the driver for the user, which will allow us to set methods to perform CRUD operations on the database tables and expose them through the Web API. Above the Controllers folder, we'll add a driver called User:

The definition of the class and its constructor will look like this:

[ApiController]
[Route("api/[controller]")]
public class UserController : ControllerBase
{
    private readonly DBContext DBContext;

    public UserController( DBContext DBContext)
    {
        this.DBContext = DBContext;
    }
    ...
}
Enter fullscreen mode Exit fullscreen mode

Now the goal is to implement the methods of this controller to perform CRUD operations. At this point it is important to mention the basic methods of HTTP. These methods are:

  • POST (create): This is a method for when sending information to insert a record into the database for example. The information is sent in the body of the request, that is, the data is not visible to the user.
  • GET (read): This is a method used for a read mode, for example: when you want to list all users in a database. Parameters are sent by a URL.
  • PUT (update): This is a method for when you want to update a record.
  • DELETE: This is a method for when you want to delete a record. Delete an X user from a database.

For the implementation of CRUD operations, we will use methods to access the information (Get), to insert data (Post), to modify (Put), and to delete a record (Delete).

The final code for each of the methods is shown below:

A. Get the list of all registered users

[HttpGet("GetUsers")]
public async Task<ActionResult<List<UserDTO>>> Get()
{
    var List = await DBContext.User.Select(
        s => new UserDTO
        {
            Id = s.Id,
            FirstName = s.FirstName,
            LastName = s.LastName,
            Username = s.Username,
            Password = s.Password,
            EnrollmentDate = s.EnrollmentDate
        }
    ).ToListAsync();

    if (List.Count < 0)
    {
        return NotFound();
    }
    else
    {
        return List;
    }
}
Enter fullscreen mode Exit fullscreen mode

B. Get the data of a specific user according to their ID

[HttpGet("GetUserById")]
public async Task<ActionResult<UserDTO>> GetUserById(int Id)
{
    UserDTO User = await DBContext.User.Select(
            s => new UserDTO
            {
                Id = s.Id,
                FirstName = s.FirstName,
                LastName = s.LastName,
                Username = s.Username,
                Password = s.Password,
                EnrollmentDate = s.EnrollmentDate
            })
        .FirstOrDefaultAsync(s => s.Id == Id);

    if (User == null)
    {
        return NotFound();
    }
    else
    {
        return User;
    }
}
Enter fullscreen mode Exit fullscreen mode

C. Insert a new user

[HttpPost("InsertUser")]
public async Task<HttpStatusCode> InsertUser(UserDTO User)
{
    var entity = new User()
    {
        FirstName = User.FirstName,
        LastName = User.LastName,
        Username = User.Username,
        Password = User.Password,
        EnrollmentDate = User.EnrollmentDate
    };

    DBContext.User.Add(entity);
    await DBContext.SaveChangesAsync();

    return HttpStatusCode.Created;
}
Enter fullscreen mode Exit fullscreen mode

D. Update the data of a specific user

[HttpPut ("UpdateUser")]
public async Task<HttpStatusCode> UpdateUser(UserDTO User)
{
    var entity = await DBContext.User.FirstOrDefaultAsync(s => s.Id == User.Id);

    entity.FirstName = User.FirstName;
    entity.LastName = User.LastName;
    entity.Username = User.Username;
    entity.Password = User.Password;
    entity.EnrollmentDate = User.EnrollmentDate;

    await DBContext.SaveChangesAsync();
    return HttpStatusCode.OK;
}
Enter fullscreen mode Exit fullscreen mode

E. Delete a user based on their ID

[HttpDelete("DeleteUser/{Id}")]
public async Task<HttpStatusCode> DeleteUser(int Id)
{
    var entity = new User()
    {
        Id = Id
    };
    DBContext.User.Attach(entity);
    DBContext.User.Remove(entity);
    await DBContext.SaveChangesAsync();
    return HttpStatusCode.OK;
}
Enter fullscreen mode Exit fullscreen mode

With these methods and with the steps followed up to this point, the services are ready to run.

Test the developed Web API

To test the deployed Web API we will make use of Postman, a cross-platform tool that allows you to do REST API testing. The installer can be found at the following address: https://www.postman.com/downloads/.

Build and run our application from Visual Studio 2019:

Once Postman is started, we can perform the first test. Below, we can see the use of the service to recover all registered users:

As we can see in the image, it is necessary to specify the type of operation, in this case, it is of type GET, as specified in the user driver. Also, we can see that the status of the result is 200 OK – that is, everything went well. And finally, we can see in the response the JSON with the results.

An additional example we can see the case to get a specific user:

Part 2: Consuming HTTP services with DotVVM

In this second part we will have three important parts:

  • 1. Set methods for communication with HTTP services defined above.
  • 2. Implement the views and models of these views for the development of the corresponding web page.

The goal with web pages is to consume CRUD operations: create, read, update, and delete set in HTTP services.

DotVVM project with ASP.NET Core

In DotVVM, communication between HTML (web pages) and C-code (source code) is done through the MVVM design pattern (Model, View, Viewmodel). The purpose of these elements are as follows:

  • Model. — is responsible for all application data and related business logic.

  • The view. — Representations for the end-user of the application model. The view is responsible for displaying the data to the user and allowing manipulation of the application data.

  • Model-View or View-Model. — one or more per view; the model-view is responsible for implementing view behavior to respond to user actions and for easily exposing model data.

Models and logic of the application

In this first part, we will define the models and services to consume HTTP services and set the logic of our application. In this case, what you are looking for is to have a general list of users and specific information on each of them.

To do this, as the first point we will define the models:

UserList

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

UserDetailModel

public class UserDetailModel
{
    public int Id { get; set; }
    [Required]
    public string FirstName { get; set; }
    [Required]
    public string LastName { get; set; }
    [Required]
    public string Username { get; set; }
    [Required]
    public string Password { get; set; }
    [Required]
    public DateTime EnrollmentDate { get; set; }
Enter fullscreen mode Exit fullscreen mode

And then the services of our application. In this case we have the user service that will allow us to set the CRUD operations according to the HTTP methods.

For User service initialization, we can start with a definition like this:

public class UserService
{
    private readonly string URLbase = "https://localhost:5003/api/User";
    
Enter fullscreen mode Exit fullscreen mode

From the base address for HTTP service consumption, the following are the corresponding methods:

A. Get the list of all registered users

public async Task<List<UserListModel>> GetAllUsersAsync()
{
    List<UserListModel> usersList = new List<UserListModel>();
    using (var httpClient = new HttpClient())
    {
        string URL = URLbase + "/GetUsers";
        HttpResponseMessage response = await httpClient.GetAsync(URL);
        string apiResponse = await response.Content.ReadAsStringAsync();
        usersList = JsonConvert.DeserializeObject<List<UserListModel>>(apiResponse).Select(
            s => new UserListModel
            {
                Id = s.Id,
                FirstName = s.FirstName,
                LastName = s.LastName
            }
            ).ToList();
    }
    return usersList;
}
Enter fullscreen mode Exit fullscreen mode

B. Get the data of a specific user according to their ID

public async Task<UserDetailModel> GetUserByIdAsync(int Id)
{
    string URL = URLbase + "/GetUserById?id=" + Id;
    UserDetailModel User = new UserDetailModel();

    using (var httpClient = new HttpClient())
    {
        HttpResponseMessage response = await httpClient.GetAsync(URL);
        string apiResponse = await response.Content.ReadAsStringAsync();
        User = JsonConvert.DeserializeObject<UserDetailModel>(apiResponse);
    }
    return User;
}
Enter fullscreen mode Exit fullscreen mode

C. Insert a new user

public async Task InsertUserAsync(UserDetailModel user)
{
    string URL = URLbase + "/InsertUser";

    using (var httpClient = new HttpClient())
    {
        StringContent content = new StringContent(JsonConvert.SerializeObject(user), Encoding.UTF8, "application/json");
        HttpResponseMessage response = await httpClient.PostAsync(URL, content);
        string apiResponse = await response.Content.ReadAsStringAsync();       
    }
}
Enter fullscreen mode Exit fullscreen mode

D. Update a specific user's data

public async Task UpdateUserAsync(UserDetailModel user)
{
    string URL = URLbase + "/UpdateUser";

    using (var httpClient = new HttpClient())
    {
        StringContent content = new StringContent(JsonConvert.SerializeObject(user), Encoding.UTF8, "application/json");

        HttpResponseMessage response = await httpClient.PutAsync(URL, content);
        string apiResponse = await response.Content.ReadAsStringAsync();
    }
}
Enter fullscreen mode Exit fullscreen mode

E. Delete a user based on their ID

public async Task DeleteUserAsync(int Id)
{
    string URL = URLbase + "/DeleteUser/" + Id;

    using (var httpClient = new HttpClient())
    {
        var response = await httpClient.DeleteAsync(URL);
        string apiResponse = await response.Content.ReadAsStringAsync();
    }
}
Enter fullscreen mode Exit fullscreen mode

Views and viewmodels

Now that the methods consuming HTTP services have been defined, we now only have to design the web page so that the user can interact with it and in this case, perform CRUD operations for user handling.

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 what the page will look like.
  • A model of the view, that is, a class in CTM that describes the state of the page (for example, values in form fields) and handles user interactions (for example, button clicks).

For our case we will have four views and four models associated with these views:

  • Default: it will be the main page of the application where the list of registered users will be displayed.

  • Create: A page made up of a form to create new users.

  • Detail: To see a user's information in detail.

  • Edit: to modify a user's information or delete it.

Considering the Views and Viewmodels files, in Visual Studio we'll visualize something like this:

Next, let's take a closer look at the View and Viewmodel of Default and its components.

Viewmodel del Default

public class DefaultViewModel : MasterPageViewModel
{
    private readonly UserService userService;

    public DefaultViewModel(UserService userService)
    {
        this.userService = userService;
    }

    [Bind(Direction.ServerToClient)]
    public List<UserListModel> Users { get; set; }

    public override async Task PreRender()
    {
        Users = await userService.GetAllUsersAsync();
        await base.PreRender();
    }
}
Enter fullscreen mode Exit fullscreen mode

As the first point, we have the instance of UserService that will allow us to access the methods to handle the operations defined in the User service implemented previously.

Then we have the definition List<UserListModel> Users of type UserListModel defined in the model classes, which will have the list of users (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 declaration [Bind(Direction.ServerToClient)]. This type of property allows us to specify which information 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 user list, it is often not necessary to transfer the entire view model in both directions. From the server to the view will suffice in this case.

Learn more about Binding Directions here:
https://www.dotvvm.com/docs/tutorials/basics-binding-direction/2.0.

Finally, in Default Viewmodel we have the method PreRender(), which allows us to perform certain kinds of operations that will be performed at the time of loading the View. In this case, the database will be queried through the service method call userService.GetAllUsersAsync(), then the results will be assigned to the Users collection of type UserListModel and then the page will be loaded along with the other design components.

View de Default

@viewModel DotVVM_APIConsume.ViewModels.DefaultViewModel, DotVVM_APIConsume
@masterPage Views/MasterPage.dotmaster
<dot:Content ContentPlaceHolderID="MainContent">
    <div class="page-center">
        <div class="page-grid-top">
        <div class="student-image"></div>
            <h1>User List</h1>

                <dot:RouteLink Text="New User" RouteName="CRUD_Create" class="page-button btn-add btn-long"/>

        </div>
        <dot:GridView DataSource="{value: Users}" class="page-grid">
            <Columns>
                <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 users. 
            </EmptyDataTemplate>
        </dot:GridView>
    </div>
</dot:Content>
Enter fullscreen mode Exit fullscreen mode

As we can see the View of Default, the page layout 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 tag <table>. One of its attributes is DataSource: DataSource="{value: Users}" which allows us to specify the data source, in this case we refer to the list of users: 'Users', which was defined in the Viewmodel as we saw earlier.

In addition to tables, 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 information sources defined in the Viewmodels. See more here: https://www.dotvvm.com/docs/controls/.

Continuing with our analysis, in the GridView we have the columns Id, FirstName, and LastName of the users, but additionally, we can also add columns to perform operations on some specific record. In this case, with RouteLink, we can define a hyperlink that constructs a URL from route names and parameter values to redirect us to other pages or perform additional operations, for example, view detail or modify a particular student's record based on their Id:

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

These routes and their corresponding parameters are defined in the DotvvmStartup.cs file in the ConfigureRoutes method as follows:

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

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.

Execution of solutions

Up to this point, we have implemented two solutions, the first to provide HTTP services over ASP.NET Core, and the second, to consume these services through a web application with DotVVM. To test locally, both Visual Studio solutions must run in order to enable HTTP services and consume them in turn. Below we can see some screenshots about the deployed web application:

Create a new record

Get the detail of a specific record

General list of users

What's next?

With this article, we have learned step by step how to implement HTTP services that handle database information with ASP.NET Core and how to consume them through a project with DotVVM.

The next activity is to publish these cloud services so that they can be accessed from anywhere. In Azure, for example, the process for uploading a Web API project or a project made up of web pages is the same. The steps to carry out this process can be found in the following article: Deploy DotVVM and .NET Core web applications to Azure (Everything you need to know).

The source code of the two projects used in this article is available in the following GitHub repositories: REST API with ASP.NET Core and MySQL and Consuming Web API in DotVVM with ASP.NET Core.

Thank you for reading:

I hope you liked the article. To stay on top of future contributions or if you have any concerns, you can follow me on Twitter: twitter.com/esDanielGomez.

See you soon!

Top comments (0)