Создание простейшего API

Последнее обновление: 15.05.2022

Рассмотренного в прошлых темах материала достаточно для создания примитивного приложения. В этой теме попробуем реализовать простейшее приложение Web API в стиле REST. Архитектура REST предполагает применение следующих методов или типов запросов HTTP для взаимодействия с сервером, где каждый тип запроса отвечает за определенное действие:

  • GET (получение данных)

  • POST (добавление данных)

  • PUT (изменение данных)

  • DELETE (удаление данных)

Поскольку в приложении ASP.NET Core мы можем легко получить и адрес запроса и тип запроса, то реализовать подобную архитектуру не составит труда.

Создание сервера на ASP.NET Core

Вначале определим веб-приложение на ASP.NET Core, которое и будет собственно представлять Web API:

Imports Microsoft.AspNetCore.Builder
Imports Microsoft.AspNetCore.Http
Imports System.Text.RegularExpressions
Module Program

    'начальные данные
    Dim tom = New Person With {.Id = Guid.NewGuid().ToString(), .Name = "Tom", .Age = 37}
    Dim bob = New Person With {.Id = Guid.NewGuid().ToString(), .Name = "Bob", .Age = 41}
    Dim sam = New Person With {.Id = Guid.NewGuid().ToString(), .Name = "Sam", .Age = 24}
    Dim users As List(Of Person) = New List(Of Person) From {tom, bob, sam}

    Sub Main(args As String())


        Dim builder = WebApplication.CreateBuilder(args)
        Dim app = builder.Build()

        app.Run(Async Function(context As HttpContext) As Task
                    Dim response = context.Response
                    Dim request = context.Request
                    Dim path = request.Path.Value
                    'Dim expressionForNumber = "^/api/users/([0 - 9]+)$";    'если id представляет число

                    '2e752824-1657-4c7f-844b-6ec2e168e99c
                    Dim expressionForGuid = "^/api/users/\w{8}-\w{4}-\w{4}-\w{4}-\w{12}$"

                    If path = "/api/users" AndAlso request.Method = "GET" Then
                        Await GetAllPeople(response)
                    ElseIf Regex.IsMatch(path, expressionForGuid) AndAlso request.Method = "GET" Then
                        'получаем id из адреса url
                        Dim id As String = path.Split("/")(3)
                        Await GetPerson(id, response, request)
                    ElseIf path = "/api/users" AndAlso request.Method = "POST" Then
                        Await CreatePerson(response, request)
                    ElseIf path = "/api/users" AndAlso request.Method = "PUT" Then
                        Await UpdatePerson(response, request)

                    ElseIf Regex.IsMatch(path, expressionForGuid) AndAlso request.Method = "DELETE" Then
                        Dim id = path.Split("/")(3)
                        Await DeletePerson(id, response)
                    Else
                        response.ContentType = "text/html; charset=utf-8"
                        Await response.SendFileAsync("html/index.html")
                    End If
                End Function)
        app.Run()
    End Sub

    'получение всех пользователей
    Async Function GetAllPeople(response As HttpResponse) As Task
        Await response.WriteAsJsonAsync(users)
    End Function

    'получение одного пользователя по id
    Async Function GetPerson(id As String, response As HttpResponse, request As HttpRequest) As Task
        'получаем пользователя по id
        Dim user = users.FirstOrDefault(Function(u) u.Id = id)
        'если пользователь найден, отправляем его
        If user IsNot Nothing Then
            Await response.WriteAsJsonAsync(user)
            'если не найден, отправляем статусный код и сообщение об ошибке
        Else
            response.StatusCode = 404
            Await response.WriteAsJsonAsync(New With {.message = "Пользователь не найден"})
        End If
    End Function

    Async Function DeletePerson(id As String, response As HttpResponse) As Task
        'получаем пользователя по id
        Dim user = users.FirstOrDefault(Function(u) u.Id = id)
        'если пользователь найден, удаляем его
        If user IsNot Nothing Then
            users.Remove(user)
            Await response.WriteAsJsonAsync(user)
            'если не найден, отправляем статусный код и сообщение об ошибке
        Else
            response.StatusCode = 404
            Await response.WriteAsJsonAsync(New With {.message = "Пользователь не найден"})
        End If

    End Function

    Async Function CreatePerson(response As HttpResponse, request As HttpRequest) As Task
        'Отправляемый в ответ клиенту объект
        Dim objectToSent As Object = New With {.message = "Некорректные данные"}  ' значение по умолчанию
        Try
            'получаем данные пользователя
            Dim user = Await request.ReadFromJsonAsync(Of Person)()
            If user IsNot Nothing Then
                'устанавливаем id для нового пользователя
                user.Id = Guid.NewGuid().ToString()
                'добавляем пользователя в список
                users.Add(user)
                objectToSent = user
            Else
                Throw New Exception("Некорректные данные")
            End If

        Catch ex As Exception
            response.StatusCode = 400
        End Try
        Await response.WriteAsJsonAsync(objectToSent)
    End Function
    Async Function UpdatePerson(response As HttpResponse, request As HttpRequest) As Task
        'Отправляемый в ответ клиенту объект
        Dim objectToSent As Object = New With {.message = "Некорректные данные"}  ' значение по умолчанию
        Try
            'получаем данные пользователя
            Dim userData = Await request.ReadFromJsonAsync(Of Person)()
            If userData IsNot Nothing Then
                'получаем пользователя по id
                Dim user = users.FirstOrDefault(Function(u) u.Id = userData.Id)
                'если пользователь найден, изменяем его данные и отправляем обратно клиенту
                If user IsNot Nothing Then

                    user.Age = userData.Age
                    user.Name = userData.Name
                    objectToSent = user

                Else

                    response.StatusCode = 404
                    objectToSent = New With {.message = "Пользователь не найден"}
                End If
            Else
                Throw New Exception("Некорректные данные")
            End If
        Catch
            response.StatusCode = 400
        End Try
        Await response.WriteAsJsonAsync(objectToSent)
    End Function

    Public Class Person
        Public Property Id() As String
        Public Property Name() As String
        Public Property Age() As Integer
    End Class

End Module

Разберем в общих чертах этот код. Вначале идет определение данных - список объектов Person, с которыми будут работать клиенты:

Dim tom = New Person With {.Id = Guid.NewGuid().ToString(), .Name = "Tom", .Age = 37}
Dim bob = New Person With {.Id = Guid.NewGuid().ToString(), .Name = "Bob", .Age = 41}
Dim sam = New Person With {.Id = Guid.NewGuid().ToString(), .Name = "Sam", .Age = 24}
Dim users As List(Of Person) = New List(Of Person) From {tom, bob, sam}

Стоит обратить внимание, что каждый объект Person имеет свойство Id, которое в качестве значения получает Guid - уникальный идентификатор, например "2e752824-1657-4c7f-844b-6ec2e168e99c".

Для упрошения данные определены в виде обычного списка объектов, но в реальной ситуации обычно подобные данные извлекаются из какой-нибудь базы данных.

В методе app.Run() определяем компонент middleware, который в зависимости от типа запросов (GET/POST/PUT/DELETE) выполняет те или иные действия.

Так, когда приложение получает запрос типа GET по адресу "api/users", то срабатывает следующий код:

If path = "/api/users" AndAlso request.Method = "GET" Then
    Await GetAllPeople(response)
................
'получение всех пользователей
Async Function GetAllPeople(response As HttpResponse) As Task
    Await response.WriteAsJsonAsync(users)
End Function

Запрос GET предполагает получение объектов, и в данном случае отправляем выше определенный список объектов Person.

Когда клиент обращается к приложению для получения одного объекта по id в запрос типа GET по адресу "api/users/[id]", то срабатывает следующий код:

ElseIf Regex.IsMatch(path, expressionForGuid) AndAlso request.Method = "GET" Then
    'получаем id из адреса url
    Dim id As String = path.Split("/")(3)
    Await GetPerson(id, response, request)
'..............
'получение одного пользователя по id
Async Function GetPerson(id As String, response As HttpResponse, request As HttpRequest) As Task
    'получаем пользователя по id
    Dim user = users.FirstOrDefault(Function(u) u.Id = id)
    'если пользователь найден, отправляем его
    If user IsNot Nothing Then
        Await response.WriteAsJsonAsync(user)
        'если не найден, отправляем статусный код и сообщение об ошибке
    Else
        response.StatusCode = 404
        Await response.WriteAsJsonAsync(New With {.message = "Пользователь не найден"})
    End If
End Function

Чтобы убедиться, что в запрошенном адресе после "/api/users/" указан id, проверяем соответствие адреса регулярному выражению: "^/api/users/\w{8}-\w{4}-\w{4}-\w{4}-\w{12}$". Данное выражение проверяет соответствие последнего сегмента адреса значению Guid, который имеет формат xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx

В этом случае нам надо найти нужного пользователя по Id в списке и отправить клиенту. Если же пользователь по Id не был найден, то возвращаем статусный код 404 с некоторым сообщением в формате JSON.

При получении запроса DELETE действует аналогичная логика:

ElseIf Regex.IsMatch(path, expressionForGuid) AndAlso request.Method = "DELETE" Then
    Dim id = path.Split("/")(3)
    Await DeletePerson(id, response)
'..............
Async Function DeletePerson(id As String, response As HttpResponse) As Task
    'получаем пользователя по id
    Dim user = users.FirstOrDefault(Function(u) u.Id = id)
    'если пользователь найден, удаляем его
    If user IsNot Nothing Then
        users.Remove(user)
        Await response.WriteAsJsonAsync(user)
        'если не найден, отправляем статусный код и сообщение об ошибке
    Else
        response.StatusCode = 404
        Await response.WriteAsJsonAsync(New With {.message = "Пользователь не найден"})
    End If
End Function

Только в данном случае, если пользователь найден в списке, удаляем его из списка и посылаем клиенту.

При получении запроса с методом POST по адресу "/api/users" используем метод request.ReadFromJsonAsync() для извлечения данных из запроса:

ElseIf path = "/api/users" AndAlso request.Method = "POST" Then
    Await CreatePerson(response, request)
'..............
Async Function CreatePerson(response As HttpResponse, request As HttpRequest) As Task
    'Отправляемый в ответ клиенту объект
    Dim objectToSent As Object = New With {.message = "Некорректные данные"}  ' значение по умолчанию
    Try
        'получаем данные пользователя
        Dim user = Await request.ReadFromJsonAsync(Of Person)()
        If user IsNot Nothing Then
            'устанавливаем id для нового пользователя
            user.Id = Guid.NewGuid().ToString()
            'добавляем пользователя в список
            users.Add(user)
            objectToSent = user
        Else
            Throw New Exception("Некорректные данные")
        End If
    Catch ex As Exception
        response.StatusCode = 400
    End Try
    Await response.WriteAsJsonAsync(objectToSent)
End Function

Поскольку при извлечении данных из запроса может произойти исключение (например, в результате парсинга в JSON), оборачиваем весь код в Try..Catch. И в случае успешного получения данных устанавливаем у нового объекта свойство Id, добавляем его в список users и отправляем обратно клиенту.

Если приложению приходит PUT-запрос, то также с помощью метода request.ReadFromJsonAsync() получаем отправленные клиентом данные. Если объект найден в списке, то изменяем его данные и отправляем обратно клиенту, иначе отправляем статусный код 404:

ElseIf path = "/api/users" AndAlso request.Method = "PUT" Then
    Await UpdatePerson(response, request)
'..............
Async Function UpdatePerson(response As HttpResponse, request As HttpRequest) As Task
    'Отправляемый в ответ клиенту объект
    Dim objectToSent As Object = New With {.message = "Некорректные данные"}  'значение по умолчанию
    Try
        'получаем данные пользователя
        Dim userData = Await request.ReadFromJsonAsync(Of Person)()
        If userData IsNot Nothing Then
            'получаем пользователя по id
            Dim user = users.FirstOrDefault(Function(u) u.Id = userData.Id)
            'если пользователь найден, изменяем его данные и отправляем обратно клиенту
            If user IsNot Nothing Then
                user.Age = userData.Age
                user.Name = userData.Name
                objectToSent = user
            Else
                response.StatusCode = 404
                objectToSent = New With {.message = "Пользователь не найден"}
            End If
        Else
            Throw New Exception("Некорректные данные")
        End If
    Catch
        response.StatusCode = 400
    End Try
    Await response.WriteAsJsonAsync(objectToSent)
End Function

В случае, если запрос идет по другому адресу, то отправляем клиенту веб-страницу index.html, которую мы далее определим:

Else
    response.ContentType = "text/html; charset=utf-8"
    Await response.SendFileAsync("html/index.html")
End If

Таким образом, мы определили простейший API. Теперь добавим код клиента.

Определение клиента

Теперь добавим в проект папку html, в которую добавим новый файл index.html

Создание Web API в ASP.NET Core и Visual Basic .NET

Определим в файле index.html следующим код для взаимодействия с сервером ASP.NET Core:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width" />
    <title>Список пользователей</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" />
</head>
<body>
    <h2>Список пользователей</h2>
    <form name="userForm">
        <input type="hidden" name="id" value="0" />
        <div class="mb-3">
            <label class="form-label" for="name">Имя:</label>
            <input class="form-control" name="name" />
        </div>
        <div class="mb-3">
            <label for="age" class="form-label">Возраст:</label>
            <input class="form-control" name="age" />
        </div>
        <div class="mb-3">
            <button type="submit" class="btn btn-sm btn-primary">Сохранить</button>
            <a id="reset" class="btn btn-sm btn-primary">Сбросить</a>
        </div>
    </form>
    <table class="table table-condensed table-striped table-bordered">
        <thead><tr><th>Имя</th><th>возраст</th><th></th></tr></thead>
        <tbody>
        </tbody>
    </table>

    <script>
    // Получение всех пользователей
        async function getUsers() {
            // отправляет запрос и получаем ответ
            const response = await fetch("/api/users", {
                method: "GET",
                headers: { "Accept": "application/json" }
            });
            // если запрос прошел нормально
            if (response.ok) {
                // получаем данные
                const users = await response.json();
                const rows = document.querySelector("tbody");
                // добавляем полученные элементы в таблицу
                users.forEach(user => rows.append(row(user)));
            }
        }
        // Получение одного пользователя
        async function getUser(id) {
            const response = await fetch("/api/users/" + id, {
                method: "GET",
                headers: { "Accept": "application/json" }
            });
            if (response.ok) {
                const user = await response.json();
                const form = document.forms["userForm"];
                form.elements["id"].value = user.id;
                form.elements["name"].value = user.name;
                form.elements["age"].value = user.age;
            }
            else {
                // если произошла ошибка, получаем сообщение об ошибке
                const error = await response.json();
                console.log(error.message); // и выводим его на консоль
            }
        }
        // Добавление пользователя
        async function createUser(userName, userAge) {

            const response = await fetch("api/users", {
                method: "POST",
                headers: { "Accept": "application/json", "Content-Type": "application/json" },
                body: JSON.stringify({
                    name: userName,
                    age: parseInt(userAge, 10)
                })
            });
            if (response.ok) {
                const user = await response.json();
                reset();
                document.querySelector("tbody").append(row(user));
            }
            else {
                const error = await response.json();
                console.log(error.message);
            }
        }
        // Изменение пользователя
        async function editUser(userId, userName, userAge) {
            const response = await fetch("api/users", {
                method: "PUT",
                headers: { "Accept": "application/json", "Content-Type": "application/json" },
                body: JSON.stringify({
                    id: userId,
                    name: userName,
                    age: parseInt(userAge, 10)
                })
            });
            if (response.ok) {
                const user = await response.json();
                reset();
                document.querySelector("tr[data-rowid='" + user.id + "']").replaceWith(row(user));
            }
            else {
                const error = await response.json();
                console.log(error.message);
            }
        }
        // Удаление пользователя
        async function deleteUser(id) {
            const response = await fetch("/api/users/" + id, {
                method: "DELETE",
                headers: { "Accept": "application/json" }
            });
            if (response.ok) {
                const user = await response.json();
                document.querySelector("tr[data-rowid='" + user.id + "']").remove();
            }
            else {
                const error = await response.json();
                console.log(error.message);
            }
        }

        // сброс данных формы после отправки
        function reset() {
            const form = document.forms["userForm"];
            form.reset();
            form.elements["id"].value = 0;
        }
        // создание строки для таблицы
        function row(user) {

            const tr = document.createElement("tr");
            tr.setAttribute("data-rowid", user.id);

            const nameTd = document.createElement("td");
            nameTd.append(user.name);
            tr.append(nameTd);

            const ageTd = document.createElement("td");
            ageTd.append(user.age);
            tr.append(ageTd);

            const linksTd = document.createElement("td");

            const editLink = document.createElement("a");
            editLink.setAttribute("style", "cursor:pointer;padding:15px;");
            editLink.append("Изменить");
            editLink.addEventListener("click", e => {

                e.preventDefault();
                getUser(user.id);
            });
            linksTd.append(editLink);

            const removeLink = document.createElement("a");
            removeLink.setAttribute("style", "cursor:pointer;padding:15px;");
            removeLink.append("Удалить");
            removeLink.addEventListener("click", e => {

                e.preventDefault();
                deleteUser(user.id);
            });

            linksTd.append(removeLink);
            tr.appendChild(linksTd);

            return tr;
        }
        // сброс значений формы
        document.getElementById("reset").addEventListener("click", e => {

            e.preventDefault();
            reset();
        })

        // отправка формы
        document.forms["userForm"].addEventListener("submit", e => {
            e.preventDefault();
            const form = document.forms["userForm"];
            const id = form.elements["id"].value;
            const name = form.elements["name"].value;
            const age = form.elements["age"].value;
            if (id == 0)
                createUser(name, age);
            else
                editUser(id, name, age);
        });

        // загрузка пользователей
        getUsers();
    </script>
</body>
</html>

Основная логика здесь заключена в коде javascript. При загрузке страницы в браузере получаем все объекты из БД с помощью функции getUsers():

async function getUsers() {
	// отправляет запрос и получаем ответ
	const response = await fetch("/api/users", {
		method: "GET",
		headers: { "Accept": "application/json" }
	});
	// если запрос прошел нормально
	if (response.ok) {
		// получаем данные
		const users = await response.json();
		let rows = document.querySelector("tbody"); 
		users.forEach(user => {
			// добавляем полученные элементы в таблицу
			rows.append(row(user));
		});
	}
}

Для добавления строк в таблицу используется функция row(), которая возвращает строку. В этой строке будут определены ссылки для изменения и удаления пользователя.

Ссылка для изменения пользователя с помощью функции getUser() получает с сервера выделенного пользователя:

async function getUser(id) {
    const response = await fetch("/api/users/" + id, {
        method: "GET",
        headers: { "Accept": "application/json" }
    });
    if (response.ok) {
        const user = await response.json();
        const form = document.forms["userForm"];
        form.elements["id"].value = user.id;
        form.elements["name"].value = user.name;
        form.elements["age"].value = user.age;
    }
    else {
        // если произошла ошибка, получаем сообщение об ошибке
        const error = await response.json();
        console.log(error.message); // и выводим его на консоль
    }
}

И выделенный пользователь добавляется в форму над таблицей. Эта же форма применяется и для добавления объекта. С помощью скрытого поля, которое хранит id пользователя, мы можем узнать, какое действие выполняется - добавление или редактирование. Если id равен 0, то выполняется функция createUser, которая отправляет данные в POST-запросе:

async function createUser(userName, userAge) {

    const response = await fetch("api/users", {
        method: "POST",
        headers: { "Accept": "application/json", "Content-Type": "application/json" },
        body: JSON.stringify({
            name: userName,
            age: parseInt(userAge, 10)
        })
    });
    if (response.ok) {
        const user = await response.json();
        reset();
        document.querySelector("tbody").append(row(user));
    }
    else {
        const error = await response.json();
        console.log(error.message);
    }
}

Если же ранее пользователь был загружен на форму, и в скрытом поле сохранился его id, то выполняется функция editUser, которая отправляет PUT-запрос:

async function editUser(userId, userName, userAge) {
    const response = await fetch("api/users", {
        method: "PUT",
        headers: { "Accept": "application/json", "Content-Type": "application/json" },
        body: JSON.stringify({
            id: userId,
            name: userName,
            age: parseInt(userAge, 10)
        })
    });
    if (response.ok) {
        const user = await response.json();
        reset();
        document.querySelector("tr[data-rowid='" + user.id + "']").replaceWith(row(user));
    }
    else {
        const error = await response.json();
        console.log(error.message);
    }
}

И функция deleteUser() посылает приложению ASP.NET Core запрос типа DELETE на удаление пользователя, и при успешном удалении на сервере удаляет пользователя по id из таблицы пользователей.

Теперь запустим проект, и по умолчанию приложение отправит браузеру веб-страницу index.html, которая загрузит список объектов:

взаимодействие javascript с Web API в ASP.NET Core и Visual Basic .NET

После этого мы сможем выполнять все базовые операции с пользователями - получение, добавление, изменение, удаление. Например, добавим нового пользователя:

REST и Web API с помощью app.Run в ASP.NET Core и Visual Basic .NET
Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850