Аутентификация и авторизация в ASP.NET Core MVC. Управление ролями пользователей

В предыдущей части мы разработали своб систему аутентификации пользователей с использованием библиотеки ASP.NET Core Identity. При этом, мы не затрагивали вопросов авторизации пользователя. В этой части мы продолжим работу над приложением и научимся авторизовывать пользователей, а также рассмотрим один из вариантов авторизации — авторизацию по ролям.

Авторизация пользователей в ASP.NET Core MVC

Для авторизации пользователя в ASP.NET Core MVC мы можем использовать атрибут [Authorize], который применяется к действиям контроллера или к контроллеру целиком. Например, в контроллере HomeController у нас имеется действие Privacy, которое отправляет нам одноименное представление. Применим к этому действию атрибут [Authorize]

[Authorize]
public IActionResult Privacy()
{
    return View();
}

Теперь запустим наше приложение и попробуем перейти по ссылке в приложении

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

Здесь стоит сделать несколько пояснений. Во-первых, переход на эту страницу говорит нам о том, что атрибут [Authorize] действительно защитил доступ к представлению (иначе, мы бы просто перешли по адресу). Во-вторых, в нашем приложении появился путь, которого мы нигде не задавали Identity/Account/Login?ReturnUrl=%2FHome%2FPrivacy, параметр ReturnUrl указывает ровно на тот путь по которому мы пытались перейти. Сам же путь Identity/Account/Login— это путь по умолчанию, который используется ASP.NET Core Identity в качестве пути по которому пользователь должен осуществить вход — ввести логин и пароль. Но, так как у нас путь для входа совсем иной, а именно /account/signin, то ASP.NET Core MVC справедливо выдает нам ошибку 404 Not Found.

Чтобы изменить путь по умолчанию для входа пользователя, перейдем в Program.cs и настроим куки для нашего приложения следующим образом:

builder.Services.ConfigureApplicationCookie(opts => opts.LoginPath = "/account/signin");

Теперь можно снова запустить приложение и убедиться, что вместо ошибки 404 при переходе на страницу Policy, мы попадает на страницу ввода логина и пароля.

Кроме атрибута [Authorize] для защиты данных, мы также можем использовать противоположный по действию атрибут — [AllowAnonymous], который прямо указывает на то, что доступ к действию/контроллеру имеют все пользователи, включая и не аутентифицированных. Например, мы можем применить этот атрибут к действию в контроллере AccountController при входе пользователя:

[Route("{area}/{action}")]
[HttpPost]
[AllowAnonymous]
public async Task<IActionResult> SignIn(LoginModel model)
{
   var result = await _signInManager.PasswordSignInAsync(model.Login, model.Password, false, lockoutOnFailure: false);
   if (result.Succeeded)
    {
        return LocalRedirect("/");
    }
   else
     {
        ModelState.AddModelError("", $"Задан неверный логин или пароль");
     }
    return View("Login");
}

Теперь, когда у нас заработала авторизация, можно переходить непосредственно, к управлению ролями пользователей и авторизации по ролям.

Роли пользователей в ASP.NET Core MVC

Добавление служб ролей в Identity

За работу с ролями пользователей в Identity отвечает сервис под названием RoleManager. Для того, чтобы зарегистрировать этот сервис необходимо внести небольшие изменения в наш файл Program.cs:

builder.Services.AddDefaultIdentity<ApplicationUser>(options =>
{
    options.Password.RequireNonAlphanumeric = true;
    options.Password.RequireDigit = true;
    options.Password.RequiredLength = 10;
})
    .AddRoles<ApplicationRole>() //включаем поддержку ролей
    .AddEntityFrameworkStores<ApplicationContext>();

здесь мы добавили метод AddRoles(), указав в качестве класса роли, созданный нами ранее класс ApplicationRole, который выглядит следующим образом:

using Microsoft.AspNetCore.Identity;

namespace AspMvcAuth.Models
{
    public class ApplicationRole: IdentityRole<int>
    {
    }
}

Добавление контроллера и представлений для управления ролями пользователей

По умолчанию, у нас нет никаких ролей в нашей системе аутентификации — мы их должны добавить самостоятельно. Для этого, создадим новый контроллер в области Account с названием RoleController:

Конструктор контроллера будет следующим:

RoleManager<ApplicationRole> _manager;
UserManager<ApplicationUser> _users;

public RoleController(RoleManager<ApplicationRole> manager, UserManager<ApplicationUser> users) 
{ 
    _manager = manager;
    _users = users;
}

здесь мы запрашиваем сервис работы с ролями пользователей RoleManager<ApplicationRole> и, для дальнейшей работы, сервис пользователей с которым мы уже немного знакомы. Сервис RoleManager предоставляет нам следующие методы и свойства для работы:

CreateAsync () Создает новую роль
DeleteAsync () Удаляет указанную роль
FindByIdAsync () Находит роль по ее идентификатору
FindByNameAsync() Находит роль по ее названию
RoleExistsAsync() Используется для проверки, существует ли роль с указанным именем или нет
UpdateAsync() Обновляет роль
Roles Свойство возвращающее все роли в Identity

Каждый пользователь может иметь одну, несколько или вообще не иметь никаких ролей. Управление ролями конкретного пользователя осуществляется с использованием сервиса UserManager у которого определены следующие методы:

AddToRoleAsync(TUser, String) Добавляет пользователя в указанную роль
AddToRolesAsync(TUser, IEnumerable<String>) Добавляет пользователя в указанный список ролей
GetRolesAsync(TUser) Возвращает список имен ролей, к которому принадлежит указанный пользователь
GetUsersInRoleAsync(String) Возвращает список пользователей, которые добавлены в указанную роль
IsInRoleAsync(TUser, String) Возвращает флаг, указывающий, является ли указанный пользователь членом заданной роли.
RemoveFromRoleAsync(TUser, String) Удаляет пользователя из роли.
RemoveFromRolesAsync(TUser, IEnumerable<String>) Удаляет пользователя из заданного списка ролей.

Для демонстрации работы с этими методами, нам потребуется создать два представления:

  1. для вывода списка ролей. В этом представлении необходимо предусмотреть переход к редактированию роли, а также выполнение такого действия, как удаление роли из хранилища;
  2. для редактирования роли — изменения названия роли, добавления/удаления пользователя из роли

В начале рассмотрим представление для вывода списка ролей и напишем необходимые действия контроллера.

Представление для вывода списка ролей

Добавим новое представление в папку Areas/Account/Views и назовем его Role.cshtml:

Представление будет иметь следующий код:

@using AspMvcAuth.Models
@model IEnumerable<ApplicationRole>
@addTagHelper AspMvcAuth.TagHelpers.*, AspMvcAuth

<h2>Роли пользователей</h2>
<form class="row g-3" method="post">
    <div class="col-auto">
        <input type="text" readonly class="form-control-plaintext" id="roleLabel" value="Роль">
    </div>
    <div class="col-auto">
        <input type="text" class="form-control" name="roleName" id="roleName" placeholder="Название роли...">
    </div>
    <div class="col-auto">
        <button type="submit" class="btn btn-primary mb-3">Добавить</button>
    </div>
</form>


<table class="table">
    <thead>
        <tr>
            <th scope="col">ID</th>
            <th scope="col">Имя</th>
            <th scope="col">Пользователи</th>
            <th scope="col">Редактировать</th>
            <th scope="col">Удалить</th>
        </tr>
    </thead>
    <tbody>
        @foreach (var item in Model)
        {
            <tr>
                <td>@item.Id</td>
                <td>@item.Name</td>
                <td u-role="@item.Id"></td>
                <td>
                    <a class="btn btn-sm btn-secondary" asp-action="Update" asp-route-id="@item.Id">Update</a>
                </td>
                <td>
                    <form asp-action="Delete" asp-route-id="@item.Id" method="post">
                        <button type="submit" class="btn btn-sm btn-danger">
                            Delete
                        </button>
                    </form>
                </td>
            </tr>
        }
    </tbody>
</table>

Рассмотрим код представление более подробно. В качестве модели представления выступает список ролей:

@model IEnumerable<ApplicationRole>

В представлении создана форма для добавления новой роли в хранилище:

<h2>Роли пользователей</h2>
<form class="row g-3" method="post">
    <div class="col-auto">
        <input type="text" readonly class="form-control-plaintext" id="roleLabel" value="Роль">
    </div>
    <div class="col-auto">
        <input type="text" class="form-control" name="roleName" id="roleName" placeholder="Название роли...">
    </div>
    <div class="col-auto">
        <button type="submit" class="btn btn-primary mb-3">Добавить</button>
    </div>
</form>

а также таблица, в которой выводится список всех ролей, а также кнопки для управления конкретной ролью:

<table class="table">
    <thead>
        <tr>
            <th scope="col">ID</th>
            <th scope="col">Имя</th>
            <th scope="col">Пользователи</th>
            <th scope="col">Редактировать</th>
            <th scope="col">Удалить</th>
        </tr>
    </thead>
    <tbody>
        @foreach (var item in Model)
        {
            <tr>
                <td>@item.Id</td>
                <td>@item.Name</td>
                <td u-role="@item.Id"></td>
                <td>
                    <a class="btn btn-sm btn-secondary" asp-action="Update" asp-route-id="@item.Id">Update</a>
                </td>
                <td>
                    <form asp-action="Delete" asp-route-id="@item.Id" method="post">
                        <button type="submit" class="btn btn-sm btn-danger">
                            Delete
                        </button>
                    </form>
                </td>
            </tr>
        }
    </tbody>
</table>

В этой таблице стоил обратить внимание на атрибут u-role, который используется для элементов <td> таблицы:

<td u-role="@item.Id"></td>

Для использования этого атрибута был создан tag-хэлпер RoleTagHelper, который разместили в папке TagHelpers:

Этот tag-хэлпер используется для вывода списка пользователей, которые были добавлены в определенную роль и выглядит следующим образом:

using AspMvcAuth.Models;

using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Razor.TagHelpers;

namespace AspMvcAuth.TagHelpers
{
    [HtmlTargetElement("td", Attributes = "u-role")]
    public class RoleTagHelper: TagHelper
    {
        private readonly UserManager<ApplicationUser> userManager;
        private readonly RoleManager<ApplicationRole> roleManager;

        [HtmlAttributeName("u-role")]
        public string Role { get; set; }

        public RoleTagHelper(UserManager<ApplicationUser> userManager, RoleManager<ApplicationRole> roleManager)
        {
            this.userManager = userManager;
            this.roleManager = roleManager;
        }

        public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
        {
            List<string?> users = [];
            ApplicationRole role = await roleManager.FindByIdAsync(Role);
            if (role != null)
            {
                users = (await userManager.GetUsersInRoleAsync(role.Name))
                    .Select((user) => user.UserName)
                    .ToList();
            }
            output.Content.SetContent(users.Count == 0 ? "Нет пользователей" : string.Join(", ", users));
        }
    }
}

Для формирования списка пользователей, входящих в определенную роль, в tag-хэлпере передается идентификатор роли:

[HtmlAttributeName("u-role")] 
public string Role { get; set; }

по id роли мы ищем роль в хранилище:

ApplicationRole role = await roleManager.FindByIdAsync(Role);

и, затем, обращаемся к менеджеру пользователей, чтобы получить список имен пользователей, которые были добавлены в определенную роль:

users = (await userManager.GetUsersInRoleAsync(role.Name))
    .Select((user) => user.UserName)
    .ToList();

Что касается редактирования и удаления роли, в представлении Role.cshtml предусмотрены две кнопки:

<td>
    <a class="btn btn-sm btn-secondary" asp-action="Update" asp-route-id="@item.Id">Update</a>
</td>
<td>
    <form asp-action="Delete" asp-route-id="@item.Id" method="post">
        <button type="submit" class="btn btn-sm btn-danger">
            Delete
        </button>
    </form>
</td>

Действия контроллера для работы с ролями пользователей

Действия чтения и создания роли

Для чтения списка ролей и создания новой роли в контроллере предусмотрено два действия:

[Route("{area}/{action}")]
[HttpGet]
public IActionResult Role()
{
    return View(_manager.Roles);
}

[Route("{area}/{action}")]
[HttpPost]
public IActionResult Role([FromForm]string roleName)
{
    _manager.CreateAsync(new ApplicationRole() 
    { 
        Name = roleName 
    });
    return RedirectToAction("Role");
}

Первый метод Role() обрабатывает GET-запрос и отправляет в представление список ролей, содержащихся в хранилище. Второй метод Role() обрабатывает POST-запросы и предназначен для создания новой роли в хранилище. Для создания роли в метод из формы представления передается название новой роли. После того, как новая роль добавлены мы осуществляем редирект на первый метод Role():

return RedirectToAction("Role");

Чтобы загрузить обновленный список ролей.

Действие для удаления роли из хранилища

Для удаления роли из хранилища используется следующее действие контроллера:

[Route("{area}/{action}/{id}")]
[HttpPost]
public async Task<IActionResult> Delete(string id)
{
    var role = await _manager.FindByIdAsync(id);
    if (role != null)
       await _manager.DeleteAsync(role);
    return RedirectToAction("Role");
        }

в представлении это действие вызывается кликом по кнопке «Delete»:

<td>
    <form asp-action="Delete" asp-route-id="@item.Id" method="post">
        <button type="submit" class="btn btn-sm btn-danger">
            Delete
        </button>
    </form>
</td>

Для редактирования роли, а также для добавления/удаления пользователей из роли используется отдельное представление. Рассмотрим его, а также соответствующие действия контроллера для работы с ролями пользователей.

Представление для управления ролями пользователей

Представление для управления ролями пользователей необходимо размещать также в папке Areas/Account/Views/Role. В нашем приложении это представление будет называться Update.cshtml:

Код представления следующий:

@using AspMvcAuth.Models
@model RoleEdit
<h2>Обновление роли</h2>

<form method="post">
    <input type="hidden" name="roleId" value="@Model.Role.Id" />
    <h2 class="bg-info p-1 text-white">Изменить название</h2>
    <input asp-for="@Model.Role.Name" type="text" class="form-control p-1" name="roleName" id="roleName" placeholder="Название роли...">
    
    <h2 class="bg-info p-1 text-white">Добавить в @Model.Role.Name</h2>
    <table class="table table-bordered table-sm">
        @if (Model.NonMembers.Count() == 0)
        {
            <tr><td colspan="2">Некого добавлять</td></tr>
        }
        else
        {
            @foreach (ApplicationUser user in Model.NonMembers)
            {
                <tr>
                    <td>@user.UserName</td>
                    <td>
                        <input type="checkbox" name="AddIds" value="@user.Id">
                    </td>
                </tr>
            }
        }
    </table>

    <h2 class="bg-info p-1 text-white">Удалить из @Model.Role.Name</h2>
    <table class="table table-bordered table-sm">
        @if (Model.Members.Count() == 0)
        {
            <tr><td colspan="2">Некого удалять</td></tr>
        }
        else
        {
            @foreach (ApplicationUser user in Model.Members)
            {
                <tr>
                    <td>@user.UserName</td>
                    <td>
                        <input type="checkbox" name="DeleteIds" value="@user.Id">
                    </td>
                </tr>
            }
        }
    </table>
    <button type="submit" class="btn btn-primary">Сохранить</button>
</form>

Как можно увидеть, это представление использует модель, которую мы ранее не создавали:

@model RoleEdit

Эта модель содержит данные, необходимые как для редактирования самой роли, так и информацию о пользователях, включенных и не включенных в эту роль. Модель выглядит следующим образом:

namespace AspMvcAuth.Models
{
    public class RoleEdit
    {
        public ApplicationRole Role { get; set; }
        public IEnumerable<ApplicationUser> Members { get; set; }
        public IEnumerable<ApplicationUser> NonMembers { get; set; }
    }
}

и размещается в папке Models приложения. В представлении для управления ролью используется форма, в которой, на основании данных модели, формируется два списка:

1. Список пользователей, которых необходимо включить в роль:

<table class="table table-bordered table-sm">
    @if (Model.NonMembers.Count() == 0)
    {
        <tr><td colspan="2">Некого добавлять</td></tr>
    }
    else
    {
        @foreach (ApplicationUser user in Model.NonMembers)
        {
            <tr>
                <td>@user.UserName</td>
                <td>
                    <input type="checkbox" name="AddIds" value="@user.Id">
                </td>
            </tr>
        }
    }
</table>

2. Список пользователей, которых необходимо исключить из роли:

<table class="table table-bordered table-sm">
    @if (Model.Members.Count() == 0)
    {
        <tr><td colspan="2">Некого удалять</td></tr>
    }
    else
    {
        @foreach (ApplicationUser user in Model.Members)
        {
            <tr>
                <td>@user.UserName</td>
                <td>
                    <input type="checkbox" name="DeleteIds" value="@user.Id">
                </td>
            </tr>
        }
    }
</table>

Чтобы при отправке формы сохранить информацию о том, какая из ролей подвергается изменению, в форме предусмотрены два текстовых поля:

<input type="hidden" name="roleId" value="@Model.Role.Id" />
   
<input asp-for="@Model.Role.Name" type="text" class="form-control p-1" name="roleName" id="roleName" placeholder="Название роли...">

при этом, поле roleId является скрытым. Для того, чтобы передавать данные формы в удобном для использования виде, в приложении предусмотрена следующая модель:

using System.ComponentModel.DataAnnotations;
namespace AspMvcAuth.Models
{
    public class RoleModification
    {
        [Required]
        public string RoleName { get; set; }

        public string RoleId { get; set; }

        public string[]? AddIds { get; set; }

        public string[]? DeleteIds { get; set; }
    }
}

Теперь рассмотрим действия контроллера для редактирования роли.

Действия контроллера для редактирования роли

Как и в случае с чтением списка ролей, в контроллере предусмотрена пара методов Update(), один из которых обрабатывает GET-запрос и предназначен, фактически, для загрузки представления, а второй — обрабатывает POST-запрос для непосредственного редактирования роли:

[Route("{area}/{action}/{id}")]
[HttpGet]
public async Task<IActionResult> Update(string id) 
{
    ApplicationRole role = await _manager.FindByIdAsync(id);
    List<ApplicationUser> members = new List<ApplicationUser>();
    List<ApplicationUser> nonMembers = new List<ApplicationUser>();
    foreach (ApplicationUser user in _users.Users)
    {
        var list = await _users.IsInRoleAsync(user, role.Name) ? members : nonMembers;
        list.Add(user);
    }
    return View(new RoleEdit
    {
        Role = role,
        Members = members,
        NonMembers = nonMembers
    });
}

[Route("{area}/{action}/{id}")]
[HttpPost]
public async Task<IActionResult> Update([FromForm] RoleModification model)
{
    var role = await _manager.FindByIdAsync(model.RoleId);
    role.Name = model.RoleName;
    await _manager.UpdateAsync(role);

    IdentityResult result;
    if (ModelState.IsValid)
    {
        foreach (string userId in model.AddIds ?? new string[] { })
        {
            ApplicationUser user = await _users.FindByIdAsync(userId);
            if (user != null)
            {
                result = await _users.AddToRoleAsync(user, model.RoleName);
                if (!result.Succeeded)
                   return BadRequest(result);
            }
        }
        foreach (string userId in model.DeleteIds ?? new string[] { })
        {
            ApplicationUser user = await _users.FindByIdAsync(userId);
            if (user != null)
            {
                result = await _users.RemoveFromRoleAsync(user, model.RoleName);
                if (!result.Succeeded)
                    return BadRequest(result);
            }
        }
    }

    return RedirectToAction("Role");
}

В методе, обрабатывающем GET-запрос, формируется объект модели RoleEdit, для чего загружается список пользователей и каждый пользователь проверяется на вхождение в заданную роль:

ApplicationRole role = await _manager.FindByIdAsync(id); //ищем роль по её ID
List<ApplicationUser> members = new List<ApplicationUser>(); 
List<ApplicationUser> nonMembers = new List<ApplicationUser>();
foreach (ApplicationUser user in _users.Users) //перебираем всех пользователей
{
    //выбираем список в который необходимо поместить пользователя
    var list = await _users.IsInRoleAsync(user, role.Name) ? members : nonMembers;
    //добавляем пользователя в список
    list.Add(user);
}

после того, как списки сформированы, отправляем модель в представление:

return View(new RoleEdit
{
    Role = role,
    Members = members,
    NonMembers = nonMembers
});

Второй метод немногим сложнее. В этом методе мы получаем в качестве параметра объект модели RoleModification из формы и выполняем два цикла — первый цикл ищет в хранилище конкретного пользователя и добавляет его к роли:

foreach (string userId in model.AddIds ?? new string[] { })
{
    ApplicationUser user = await _users.FindByIdAsync(userId);
    if (user != null)
    {
        result = await _users.AddToRoleAsync(user, model.RoleName);
        if (!result.Succeeded)
           return BadRequest(result);
    }
}

второй цикл аналогичный, но действует в обратном ключе — удаляет найденного пользователя из роли. После редактирования роли мы возвращаемся к списку ролей:

return RedirectToAction("Role");

Осталось только добавить ссылку для доступа к списку ролей из нашего приложения. Для этого воспользуемся макетом приложения (Файл _Layout.cshtml) в которй добавим следующий tag-хэлпер ссылки:

<a class="nav-link text-dark" asp-area="Account" asp-controller="Role" asp-action="Role">Управление ролями</a>

Проверим работу приложения.

Проверка работы приложения с ролями пользователей

Для примера, добавим в приложение новые роли. Для этого перейдем по новой ссылке:

и воспользуемся формой для добавления ролей в хранилище. На рисунке, для примера показаны три роли, добавленные в хранилище:

Обновим роль ADMIN. Нажав на кнопку Update мы перейдем к одноименному представлению:

Так как пока нет никаких пользователей с этой ролью, то второй список «Удалить из ADMIN» пустой, а в первом — содержится единственный пользователь системы, которого мы зарегистрировали в предыдущей части. Выберем пользователя и нажмем кнопку «Сохранить»:

В результате, список ролей пользователей будет выглядеть следующим образом:

Можно добавить пользователя во все три роли. Или зарегистрировать ещё несколько пользователей и назначить им роли и т.д. Мы этого делать не будем, а перейдем к следующей части работы — настроим и проверим авторизацию пользователей по ролям.

Авторизация пользователей по ролям

Для того, чтобы дать доступ к какому-то ресурсу вашего приложения пользователю с определенной ролью, также используется атрибут [Authorize] в параметрах которого указываются необходимые роли. Например, вернемся к нашему «домашнему» контроллеру HomeController и применим к действию Privacy() следующий атрибут:

[Authorize(Roles ="MANAGER")]
public IActionResult Privacy()
{
    return View();
}

Теперь доступ к представлению Privacy будут иметь только пользователи, включенные в роль MANAGER. Попробуем зайти в приложение и получить доступ к этому приложению. Напомню, что единственный пользователь системы у нас входит в роль ADMIN, а не MANAGER.

При попытке перейти на страницу Privacy() мы получим ошибку:

Здесь, опять де, как и в самом начале, ASP.NET Core MVC пытается нас перенаправить на адрес по умолчанию, который используется для указания пользователю того, что ему запрещен доступ к этой странице. Чтобы переопределить этот путь и сделать наше приложение чуть более информативным для пользователя, давайте настроим новый путь к странице с информацией о запрете доступа. Делается это также в настройках куки приложения следующим образом:

builder.Services.ConfigureApplicationCookie(opts => 
{ 
    opts.LoginPath = "/account/signin";
    opts.AccessDeniedPath = "/AccessDanied"; //путь к странице с информацией о запрете доступа
});

Теперь добавим в приложение новое представление с названием AccessDanied.cshtml, разместив его в папке Views/Shared. Код представления:

<h2 class="bg-danger p-1 text-white">Вам запрещен доступ к этой странице</h2>

В контроллер HomeController добавим действие, возвращающее это представление:

[Authorize]
[Route("/AccessDanied")]
public IActionResult AccessDanied()
{
    return View();
}

Теперь, если пользователь аутентифицирован (вошел в систему), но не авторизован (не имеет необходимой роли), то ему будет загружаться представление AccessDanied, а если пользователь не аутентифицирован, то ему будет предложено войти в систему. Вот как будет выглядеть запрет доступа для пользователя:

Итого

В этой части мы разобрались с механизмом авторизации пользователей по ролям с использованием ASP.NET Core Identity и разработали приложение в котором научились работать в ролями пользователей, назначать и удалять пользователей из ролей. Для выполнения этих задач мы разработали новый контроллер, а также вспомогательный tag-хэлпер и необходимые представления, включая и представление, информирующее аутентифицированного пользователя о запрете доступа к определенному ресурсу.

P.S.

Эта статья в части изложения далась мне довольно не просто, так как оказалось довольно трудно выстроить логику изложения материала, когда надо постоянно переключаться с описания контроллера, на представление и наоборот и, дополнительно, не забывать про используемые модели. Поэтому, уважаемые читатели, если вдруг изложенный материал «не зайдет» или вы в чем-то запутаетесь, то, на всякий случай, весь код этого проекта я буду отправлять в репозиторий на GitHub:

Скачать код проекта из Github

только при использовании кода проекта, пожалуйста, не забывайте, что для строки подключения к БД используются секреты пользователей. Поэтому, прежде, чем запустить проект либо создайте необходимый секрет, либо измените файл Program.cs, чтобы получать настройки подключения к БД из appsettings.json.

Подписаться
Уведомить о
guest
3 Комментарий
Старые
Новые Популярные
Межтекстовые Отзывы
Посмотреть все комментарии