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

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

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

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

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

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

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

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

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

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

using System.Text.RegularExpressions;

// начальные данные
List<Person> users = new List<Person> 
{ 
    new() { Id = Guid.NewGuid().ToString(), Name = "Tom", Age = 37 },
    new() { Id = Guid.NewGuid().ToString(), Name = "Bob", Age = 41 },
    new() { Id = Guid.NewGuid().ToString(), Name = "Sam", Age = 24 }
};

var builder = WebApplication.CreateBuilder();
var app = builder.Build();

app.Run(async (context) =>
{
    var response = context.Response;
    var request = context.Request;
    var path = request.Path;
    //string expressionForNumber = "^/api/users/([0-9]+)$";   // если id представляет число

    // 2e752824-1657-4c7f-844b-6ec2e168e99c
    string expressionForGuid = @"^/api/users/\w{8}-\w{4}-\w{4}-\w{4}-\w{12}$";
    if (path == "/api/users" && request.Method=="GET")
    {
        await GetAllPeople(response); 
    }
    else if (Regex.IsMatch(path, expressionForGuid) && request.Method == "GET")
    {
        // получаем id из адреса url
        string? id = path.Value?.Split("/")[3];
        await GetPerson(id, response);
    }
    else if (path == "/api/users" && request.Method == "POST")
    {
        await CreatePerson(response, request);
    }
    else if (path == "/api/users" && request.Method == "PUT")
    {
        await UpdatePerson(response, request);
    }
    else if (Regex.IsMatch(path, expressionForGuid) && request.Method == "DELETE")
    {
        string? id = path.Value?.Split("/")[3];
        await DeletePerson(id, response);
    }
    else
    {
        response.ContentType = "text/html; charset=utf-8";
        await response.SendFileAsync("html/index.html");
    }
});

app.Run();

// получение всех пользователей
async Task GetAllPeople(HttpResponse response)
{
    await response.WriteAsJsonAsync(users);
}
// получение одного пользователя по id
async Task GetPerson(string? id, HttpResponse response)
{
    // получаем пользователя по id
    Person? user = users.FirstOrDefault((u) => u.Id == id);
    // если пользователь найден, отправляем его
    if (user != null)
        await response.WriteAsJsonAsync(user);
    // если не найден, отправляем статусный код и сообщение об ошибке
    else
    {
        response.StatusCode = 404;
        await response.WriteAsJsonAsync(new { message = "Пользователь не найден" });
    }
}

async Task DeletePerson(string? id, HttpResponse response)
{
    // получаем пользователя по id
    Person? user = users.FirstOrDefault((u) => u.Id == id);
    // если пользователь найден, удаляем его
    if (user != null)
    {
        users.Remove(user);
        await response.WriteAsJsonAsync(user);
    }
    // если не найден, отправляем статусный код и сообщение об ошибке
    else
    {
        response.StatusCode = 404;
        await response.WriteAsJsonAsync(new { message = "Пользователь не найден" });
    }
}

async Task CreatePerson(HttpResponse response, HttpRequest request)
{
    try
    {
        // получаем данные пользователя
        var user = await request.ReadFromJsonAsync<Person>();
        if (user != null)
        {
            // устанавливаем id для нового пользователя
            user.Id = Guid.NewGuid().ToString();
            // добавляем пользователя в список
            users.Add(user);
            await response.WriteAsJsonAsync(user);
        }
        else
        {
            throw new Exception("Некорректные данные");
        }
    }
    catch (Exception)
    {
        response.StatusCode = 400;
        await response.WriteAsJsonAsync(new { message = "Некорректные данные" });
    }
}

async Task UpdatePerson(HttpResponse response, HttpRequest request)
{
    try
    {
        // получаем данные пользователя
        Person? userData = await request.ReadFromJsonAsync<Person>();
        if (userData != null)
        {
            // получаем пользователя по id
            var user = users.FirstOrDefault(u => u.Id == userData.Id);
            // если пользователь найден, изменяем его данные и отправляем обратно клиенту
            if (user != null)
            {
                user.Age = userData.Age;
                user.Name = userData.Name;
                await response.WriteAsJsonAsync(user);
            }
            else
            {
                response.StatusCode = 404;
                await response.WriteAsJsonAsync(new { message = "Пользователь не найден" });
            }
        }
        else
        {
            throw new Exception("Некорректные данные");
        }
    }
    catch (Exception)
    {
        response.StatusCode = 400;
        await response.WriteAsJsonAsync(new { message = "Некорректные данные" });
    }
}
public class Person
{
    public string Id { get; set; } = "";
    public string Name { get; set; } = "";
    public int Age { get; set; }
}

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

var users = new List<Person> 
{ 
    new() { Id = Guid.NewGuid().ToString(), Name = "Tom", Age = 37 },
    new() { Id = Guid.NewGuid().ToString(), Name = "Bob", Age = 41 },
    new() { Id = Guid.NewGuid().ToString(), Name = "Sam", Age = 24 }
};

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

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

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

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

if (path == "/api/users" && request.Method=="GET")
{
	await GetAllPeople(response); 
}
//.........
// получение всех пользователей
async Task GetAllPeople(HttpResponse response)
{
    await response.WriteAsJsonAsync(users);
}

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

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

else if (Regex.IsMatch(path, expressionForGuid) && request.Method == "GET")
{
	// получаем id из адреса url
    string? id = path.Value?.Split("/")[3];
    await GetPerson(id, response);
}
//..............
// получение одного пользователя по id
async Task GetPerson(string? id, HttpResponse response)
{
    // получаем пользователя по id
    Person? user = users.FirstOrDefault((u) => u.Id == id);
    // если пользователь найден, отправляем его
    if (user != null)
        await response.WriteAsJsonAsync(user);
    // если не найден, отправляем статусный код и сообщение об ошибке
    else
    {
        response.StatusCode = 404;
        await response.WriteAsJsonAsync(new { message = "Пользователь не найден" });
    }
}

Чтобы убедиться, что в запрошенном адресе после "/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 действует аналогичная логика:

else if (Regex.IsMatch(path, expressionForGuid) && request.Method == "DELETE")
{
	// получаем id из адреса url
    string? id = path.Value?.Split("/")[3];
    await DeletePerson(id, response);
}
//..............
async Task DeletePerson(string? id, HttpResponse response)
{
    // получаем пользователя по id
    Person? user = users.FirstOrDefault((u) => u.Id == id);
    // если пользователь найден, удаляем его
    if (user != null)
    {
        users.Remove(user);
        await response.WriteAsJsonAsync(user);
    }
    // если не найден, отправляем статусный код и сообщение об ошибке
    else
    {
        response.StatusCode = 404;
        await response.WriteAsJsonAsync(new { message = "Пользователь не найден" });
    }
}

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

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

else if (path == "/api/users" && request.Method == "POST")
{
	await CreatePerson(response, request);
}
//..............
async Task CreatePerson(HttpResponse response, HttpRequest request)
{
    try
    {
        // получаем данные пользователя
        var user = await request.ReadFromJsonAsync<Person>();
        if (user != null)
        {
            // устанавливаем id для нового пользователя
            user.Id = Guid.NewGuid().ToString();
            // добавляем пользователя в список
            users.Add(user);
            await response.WriteAsJsonAsync(user);
        }
        else
        {
            throw new Exception("Некорректные данные");
        }
    }
    catch (Exception)
    {
        response.StatusCode = 400;
        await response.WriteAsJsonAsync(new { message = "Некорректные данные" });
    }
}

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

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

else if (path == "/api/users" && request.Method == "PUT")
{
    await UpdatePerson(response, request);
}
//..............
async Task UpdatePerson(HttpResponse response, HttpRequest request)
{
    try
    {
        // получаем данные пользователя
        Person? userData = await request.ReadFromJsonAsync<Person>();
        if (userData != null)
        {
            // получаем пользователя по id
            var user = users.FirstOrDefault(u => u.Id == userData.Id);
            // если пользователь найден, изменяем его данные и отправляем обратно клиенту
            if (user != null)
            {
                user.Age = userData.Age;
                user.Name = userData.Name;
                await response.WriteAsJsonAsync(user);
            }
            else
            {
                response.StatusCode = 404;
                await response.WriteAsJsonAsync(new { message = "Пользователь не найден" });
            }
        }
        else
        {
            throw new Exception("Некорректные данные");
        }
    }
    catch (Exception)
    {
        response.StatusCode = 400;
        await response.WriteAsJsonAsync(new { message = "Некорректные данные" });
    }
}

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

else
{
    response.ContentType = "text/html; charset=utf-8";
    await response.SendFileAsync("html/index.html");
}

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

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

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

Создание Web API в ASP.NET Core и C#

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

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>METANIT.COM</title>
<style>
td {padding:5px;}
button{margin: 5px;}
</style>
</head>
<body>
    <h2>Список пользователей</h2>
    <div>
        <input type="hidden" id="userId" />
        <p>
            Имя:<br/>
            <input id="userName" />
        </p>
        <p>
            Возраст:<br />
            <input id="userAge" type="number" />
        </p>
         <p>
            <button id="saveBtn">Сохранить</button>
            <button id="resetBtn">Сбросить</button>
        </p>
    </div>
    <table>
        <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 === true) {
                // получаем данные
                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 === true) {
                const user = await response.json();
                document.getElementById("userId").value = user.id;
                document.getElementById("userName").value = user.name;
                document.getElementById("userAge").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 === true) {
                const user = await response.json();
                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 === true) {
                const user = await response.json();
                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 === true) {
                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() {
            document.getElementById("userId").value = 
            document.getElementById("userName").value = 
            document.getElementById("userAge").value = "";
        }
        // создание строки для таблицы
        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("button"); 
            editLink.append("Изменить");
            editLink.addEventListener("click", async() => await getUser(user.id));
            linksTd.append(editLink);
 
            const removeLink = document.createElement("button"); 
            removeLink.append("Удалить");
            removeLink.addEventListener("click", async () => await deleteUser(user.id));
 
            linksTd.append(removeLink);
            tr.appendChild(linksTd);
 
            return tr;
        }
        // сброс значений формы
        document.getElementById("resetBtn").addEventListener("click", () =>  reset());
 
        // отправка формы
        document.getElementById("saveBtn").addEventListener("click", async () => {
            
            const id = document.getElementById("userId").value;
            const name = document.getElementById("userName").value;
            const age = document.getElementById("userAge").value;
            if (id === "")
                await createUser(name, age);
            else
                await editUser(id, name, age);
            reset();
        });
 
        // загрузка пользователей
        getUsers();
    </script>
</body>
</html>

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

async function getUsers() {
    // отправляет запрос и получаем ответ
    const response = await fetch("/api/users", {
        method: "GET",
        headers: { "Accept": "application/json" }
    });
    // если запрос прошел нормально
    if (response.ok === true) {
        // получаем данные
        const users = await response.json();
        const 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 === true) {
        const user = await response.json();
        document.getElementById("userId").value = user.id;
        document.getElementById("userName").value = user.name;
        document.getElementById("userAge").value = user.age;
    }
    else {
        // если произошла ошибка, получаем сообщение об ошибке
        const error = await response.json();
        console.log(error.message); // и выводим его на консоль
    }
}

И выделенный пользователь добавляется в форму над таблицей. Эта же форма применяется и для добавления объекта. С помощью скрытого поля, которое хранит id пользователя, мы можем узнать, какое действие выполняется - добавление или редактирование. Если id не установлен (равен пустой строке), то выполняется функция 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 === true) {
        const user = await response.json();
        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 === true) {
        const user = await response.json();
        document.querySelector(`tr[data-rowid='${user.id}']`).replaceWith(row(user));
    }
    else {
        const error = await response.json();
        console.log(error.message);
    }
}

И функция deleteUser() посылает на сервер запрос типа DELETE на удаление пользователя, и при успешном удалении на сервере удаляет объект по id из списка объектов Person.

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

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

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

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