jQuery Ajax CRUD в ASP.NET Core MVC с модальным всплывающим окном

  • Михаил
  • 12 мин. на прочтение
  • 158
  • 09 Dec 2022
  • 09 Dec 2022

В этой статье мы обсудим, как использовать  jQuery Ajax для операций ASP.NET Core MVC CRUD  с использованием Bootstrap Modal. Когда вы реализуете операции CRUD без jQuery Ajax , для каждого запроса пользователя вся веб-страница перезагружается еще раз. С помощью jQuery Ajax мы можем сделать HTTP-запрос к методам действия контроллера без перезагрузки всей страницы. Это позволяет нам повторно отображать только часть приложения, что обеспечивает большую производительность и меньшее использование полосы пропускания, аналогично одностраничному приложению, созданному с помощью интерфейсных фреймворков, таких как Angular или React .

Чтобы продемонстрировать операции CRUD — вставка, обновление, удаление и извлечение, проект будет иметь дело с деталями обычной банковской транзакции. Репозиторий GitHub для этого демонстрационного проекта:  https://bit.ly/33KTJAu .

Обсуждаемые подтемы:

  • Дизайн формы для операций вставки и обновления.
  • Отображать формы в модальном всплывающем диалоговом окне.
  • Формируйте сообщение с помощью jQuery Ajax.
  • Реализуйте операции MVC CRUD с помощью jQuery Ajax.
  • Загрузка счетчика в .NET Core MVC.
  • Запретить прямой доступ к методу действия MVC.

Создать проект ASP.NET Core MVC

В Visual Studio 2019 выберите  «Файл» > «Создать» > «Проект» (Ctrl + Shift + N) .
В новом окне проекта выберите Asp.Net Core Web Application .

После того, как вы укажете название проекта и местоположение. Выберите Веб-приложение (модель-представление-контроллер) и снимите флажок  Конфигурация HTTPS . Вышеупомянутые шаги создадут новый проект ASP.NET Core MVC.

Настройка базы данных

Давайте создадим базу данных для этого приложения, используя Entity Framework Core. Для этого нам нужно установить соответствующие пакеты NuGet. Щелкните правой кнопкой мыши проект в обозревателе решений, выберите «Управление пакетами NuGet» ,  на вкладке «Обзор» установите следующие 3 пакета.

Теперь давайте определим файл класса модели БД —  /Models/TransactionModel.cs .

public class TransactionModel
{
    [Key]
    public int TransactionId { get; set; }

    [Column(TypeName ="nvarchar(12)")]
    [DisplayName("Account Number")]
    [Required(ErrorMessage ="This Field is required.")]
    [MaxLength(12,ErrorMessage ="Maximum 12 characters only")]
    public string AccountNumber { get; set; }

    [Column(TypeName ="nvarchar(100)")]
    [DisplayName("Beneficiary Name")]
    [Required(ErrorMessage = "This Field is required.")]
    public string BeneficiaryName { get; set; }

    [Column(TypeName ="nvarchar(100)")]
    [DisplayName("Bank Name")]
    [Required(ErrorMessage = "This Field is required.")]
    public string BankName { get; set; }

    [Column(TypeName ="nvarchar(11)")]
    [DisplayName("SWIFT Code")]
    [Required(ErrorMessage = "This Field is required.")]
    [MaxLength(11)]
    public string SWIFTCode { get; set; }

    [DisplayName("Amount")]
    [Required(ErrorMessage = "This Field is required.")]
    public int Amount { get; set; }

    [DisplayFormat(DataFormatString = "{0:MM/dd/yyyy}")]
    public DateTime Date { get; set; }
}

Здесь мы определили свойства модели для транзакции с надлежащей проверкой. Теперь давайте определим  DbContext класс для EF Core.

public class TransactionDbContext:DbContext
{
    public TransactionDbContext(DbContextOptions<TransactionDbContext> options):base(options)
    { }

    public DbSet<TransactionModel> Transactions { get; set; }
}

Этот класс решает, что должно быть в фактической физической базе данных после миграции базы данных. Для создания таблицы —  Транзакции  для вышеуказанной модели мы добавили соответствующее  DbSet свойство —  Transactions. С помощью внедрения зависимостей ASP.NET Core мы создадим новый экземпляр   DbContext класса, который мы можем сделать в  файле Startup.cs  , как показано ниже.

public void ConfigureServices(IServiceCollection services)
{
    ...

    services.AddDbContext<TransactionDbContext>(options =>
    options.UseSqlServer(Configuration.GetConnectionString("DevConnection")));
}

При создании экземпляра класса необходимо значение его параметра конструктора. Параметру конструктора класса контекста  DbContextOptionsтребуется информация, такая как поставщик базы данных (SQL Server, MySQL, Oracle и т. д.) и его строка подключения. Теперь давайте добавим строку подключения –  DevConnectionв  appsettings.json.

...
"ConnectionStrings": {
  "DevConnection": "Server=(local)\\sqlexpress;Database=jQueryAjaxMVCDB;Trusted_Connection=True;MultipleActiveResultSets=True;"
}

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

Add-Migration "InitialCreate"
Update-Database

После завершения миграции вы можете проверить, создана ли новая база данных в соответствии с нашей моделью БД или нет.

Создайте контроллер MVC с методами действий CRUD

Теперь давайте добавим контроллер MVC с методами действий для операций CRUD. Для этого щелкните правой кнопкой мыши  папку «Контроллеры»  ,  «Добавить»> «Контроллер». Затем выберите «Контроллер MVC с представлениями, используя Entity Framework» Заполните окно соответствующими данными.

Если вы столкнулись со следующей ошибкой при создании файла controller. вам следует повторить попытку после установки пакета NuGet —  Microsoft.VisualStudio.Web.CodeGeneration.Utils.

Внутри контроллера вы могли видеть методы действий для грубых операций как для запроса GET, так и для запроса POST. Как вы знаете, экземпляр этого контроллера автоматически создается платформой ASP.NET Core при запросе к нему. В то же время значение параметра конструктора  TransactionDbContext передается из внедрения зависимости.

Приступим к разработке приложения

Прежде всего, мы хотим добавить ссылку на таблицу стилей для Google Font Roboto и Font Awesome Icon в основной макет —  _Layout.cshtml .

<!--  Add following stylesheets to <head> tag  -->
<link rel="preload" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.12.1/css/all.min.css" data-rocket-async="style" as="style" onload="this.onload=null;this.rel='stylesheet'" onerror="this.removeAttribute('data-rocket-async')"  />

Теперь позвольте мне обновить глобальную таблицу стилей ( wwwroot/css/site.css ) всеми необходимыми правилами css для всего приложения.

body{
 font-family: 'Roboto', sans-serif;
}

/* Change input control border color */
.form-control, .input-group-text {
    border: 1px solid #0080ff;
}

a.btn:hover{
    cursor: pointer !important;
}

/*loader*/
.loaderbody {
    width: 100%;
    height: 100%;
    left: 0px;
    top: 0px;
    position: absolute;
    background-color: rgba(128,128,128,0.2);
    z-index: 2147483647;
}

.loader {
    border: 16px solid #f3f3f3; /* Light grey */
    border-top: 16px solid #3498db; /* Blue */
    border-radius: 50%;
    width: 80px;
    height: 80px;
    animation: spin 2s linear infinite;
    position: fixed;
    top: 45%;
    left: 40%;
}

@keyframes spin {
    0% {
        transform: rotate(0deg);
    }

    100% {
        transform: rotate(360deg);
    }
}

.hide{
    display:none;
}

Вы можете получить доступ к вновь созданным методам действия контроллера с помощью URL-адреса в формате — /Transaction/[action_name]. Вы можете настроить этот контроллер как маршрут по умолчанию в  Startup.cs.

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    ...
    app.UseEndpoints(endpoints =>
    {
        endpoints.MapControllerRoute(
            name: "default",
            pattern: "{controller=Transaction}/{action=Index}/{id?}");
    });
}

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

<!-- File : /Views/Transaction/Index.cshml !-->
@model IEnumerable<jQuery_Ajax_CRUD.Models.TransactionModel>

@{ ViewData["Title"] = "Index"; }

<div id="view-all">
    @await Html.PartialAsync("_ViewAll", Model)
</div>

@section Scripts{
    @{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}

Внутри этого представления индекса мы внесли несколько серьезных изменений. В первую очередь добавили скрипты проверки в разделе Scripts. По умолчанию в index.html у нас была таблица для перечисления записей транзакций. мы должны переместить это в новое частичное представление  _ViewAll.  Мы должны повторно отображать таблицу после каждой операции, такой как вставка, обновление и удаление. Поэтому лучше переместить таблицу в отдельное частичное представление для запроса JQuery Ajax Get позже. Создайте файл _ViewAll.cshtml в  папке /Views/Transaction  , как показано ниже.

@model IEnumerable<jQuery_Ajax_CRUD.Models.TransactionModel>

<h1 class="text-center"><i class="fas fa-comments-dollar text-success"></i> Transaction History</h1>


<table class="table">
    <thead class="thead-light">
        <tr>
            <th>
                @Html.DisplayNameFor(model => model.AccountNumber)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.BeneficiaryName)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.Date)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.Amount)
            </th>
            <th>
                <a onclick="showInPopup('@Url.Action("AddOrEdit","Transaction",null,Context.Request.Scheme)','New Transaction')" class="btn btn-success text-white"><i class="fas fa-random"></i> New Transaction</a>
            </th>
        </tr>
    </thead>
    <tbody>
        @foreach (var item in Model)
        {
            <tr>
                <td>
                    @Html.DisplayFor(modelItem => item.AccountNumber)
                </td>
                <td>
                    @Html.DisplayFor(modelItem => item.BeneficiaryName)
                </td>
                <td>
                    @Html.DisplayFor(modelItem => item.Date)
                </td>
                <td>
                    @Html.DisplayFor(modelItem => item.Amount)
                </td>
                <td>
                    <div>
                        <a onclick="showInPopup('@Url.Action("AddOrEdit","Transaction",new {id=item.TransactionId},Context.Request.Scheme)','Update Transaction')" class="btn btn-info text-white"><i class="fas fa-pencil-alt"></i> Edit</a>
                        <form asp-action="Delete" asp-route-id="@item.TransactionId" onsubmit="return jQueryAjaxDelete(this)" class="d-inline">
                            <input type="submit" value="Delete" class="btn btn-danger" />
                        </form>
                    </div>
                </td>
            </tr>
        }
    </tbody>
</table>

Внутри этого частичного представления у нас есть кнопка для операции вставки в заголовок таблицы. Каждая запись имеет кнопки для обновления и удаления. Все эти события нажатия кнопки привязаны к их функциям JavaScript. мы можем обсудить это позже. Теперь вы могли видеть пустую таблицу, если вы перейдете к действию индекса — /Transaction/Index.

Реализовать операцию вставки и обновления

В контроллере транзакций вы могли видеть  Addи  Edit методы действий с их бритвенными представлениями. В этом проекте мы объединим эти два метода действий и их представления в один —  AddOrEdit. Для представления AddOrEdit вам просто нужно внести несколько изменений в существующее представление  Edit.cshtml . Прежде всего, переименуйте файл в  AddOrEdit.cshtml  и обновите файл, как показано ниже.

@model PopUpCRUD.Models.TransactionModel

@{ Layout = null; }

<div class="row">
    <div class="col-md-12">
        <form asp-action="AddOrEdit" asp-route-id="@Model.TransactionId" onsubmit="return jQueryAjaxPost(this);">
            <div asp-validation-summary="ModelOnly" class="text-danger"></div>
            <input type="hidden" asp-for="TransactionId" />
            <input type="hidden" asp-for="Date" />
            <div class="form-group">
                <label asp-for="AccountNumber" class="control-label"></label>
                <div class="input-group">
                    <div class="input-group-prepend">
                        <div class="input-group-text">#</div>
                    </div>
                    <input asp-for="AccountNumber" class="form-control" />
                </div>
                <span asp-validation-for="AccountNumber" class="text-danger"></span>
            </div>
            <div class="row">
                <div class="col-md-6">
                    <div class="form-group">
                        <label asp-for="BeneficiaryName" class="control-label"></label>
                        <input asp-for="BeneficiaryName" class="form-control" />
                        <span asp-validation-for="BeneficiaryName" class="text-danger"></span>
                    </div>
                </div>
                <div class="col-md-6">
                    <div class="form-group">
                        <label asp-for="BankName" class="control-label"></label>
                        <input asp-for="BankName" class="form-control" />
                        <span asp-validation-for="BankName" class="text-danger"></span>
                    </div>
                </div>
            </div>
            <div class="row">
                <div class="col-md-6">
                    <div class="form-group">
                        <label asp-for="SWIFTCode" class="control-label"></label>
                        <input asp-for="SWIFTCode" class="form-control" />
                        <span asp-validation-for="SWIFTCode" class="text-danger"></span>
                    </div>
                </div>
                <div class="col-md-6">
                    <div class="form-group">
                        <label asp-for="Amount" class="control-label"></label>
                        <div class="input-group">
                            <div class="input-group-prepend">
                                <div class="input-group-text">
                                    <i class="fas fa-dollar-sign"></i>
                                </div>
                            </div>
                            <input asp-for="Amount" class="form-control" />
                        </div>
                        <span asp-validation-for="Amount" class="text-danger"></span>
                    </div>
                </div>
            </div>
            <div class="form-group">
                <div class="col-md-6 offset-md-3">
                    <input type="submit" value="Submit" class="btn btn-primary btn-block" />
                </div>
            </div>
        </form>
    </div>
</div>

Есть некоторые перестановки для элементов управления вводом, кроме этого, мы изменили метод действия отправки формы на  AddOrEdit и asp-route-id установлен в свойство PK –  TransactionId. В этом представлении вы могли видеть дополнительное скрытое свойство — Дата. Какие данные транзакции.

Теперь давайте определим соответствующие методы действий GET и POST  AddOrEdit в  TransactionController.

// GET: Transaction/AddOrEdit(Insert)
// GET: Transaction/AddOrEdit/5(Update)
public async Task<IActionResult> AddOrEdit(int id = 0)
{
    if (id == 0)
        return View(new TransactionModel());
    else
    {
        var transactionModel = await _context.Transactions.FindAsync(id);
        if (transactionModel == null)
        {
            return NotFound();
        }
        return View(transactionModel);
    }
}

[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> AddOrEdit(int id, [Bind("TransactionId,AccountNumber,BeneficiaryName,BankName,SWIFTCode,Amount,Date")] TransactionModel transactionModel)
{
    if (ModelState.IsValid)
    {
        //Insert
        if (id == 0)
        {
            transactionModel.Date = DateTime.Now;
            _context.Add(transactionModel);
            await _context.SaveChangesAsync();

        }
        //Update
        else
        {
            try
            {
                _context.Update(transactionModel);
                await _context.SaveChangesAsync();
            }
            catch (DbUpdateConcurrencyException)
            {
                if (!TransactionModelExists(transactionModel.TransactionId))
                { return NotFound(); }
                else
                { throw; }
            }
        }
        return Json(new { isValid = true, html = Helper.RenderRazorViewToString(this, "_ViewAll", _context.Transactions.ToList()) });
    }
    return Json(new { isValid = false, html = Helper.RenderRazorViewToString(this, "AddOrEdit", transactionModel) });
}

Внутри этих методов действия мы просто объединили существующие  методы Create и  Edit методы действия. Чтобы узнать, есть ли у нас операция вставки или обновления, мы можем проверить  idпараметр (TransactionId). если его значение равно нулю, то у нас есть операция вставки, иначе это операция обновления.

Из метода действия GET, если  id он равен нулю, будет возвращена новая форма транзакции, в противном случае соответствующие сведения о транзакции будут заполнены в возвращаемой форме. Внутри метода действия POST операция вставки/обновления будет выполняться на основе файла  id. Запрос в этот метод действия POST будет выполнен с использованием jQuery Ajax. поэтому метод действия должен возвращать  объект JSON  . При этом список транзакций объекта может быть заменен, в свойстве передается метод действия _ViewAll  html . Чтобы преобразовать представление в строку HTML, мы определили функцию в новом файле C# —  Helper.cs  , как показано ниже.

public class Helper {

  public static string RenderRazorViewToString(Controller controller, string viewName, object model = null)
  {
    controller.ViewData.Model = model;
    using (var sw = new StringWriter())
    {
        IViewEngine viewEngine = controller.HttpContext.RequestServices.GetService(typeof(ICompositeViewEngine)) as ICompositeViewEngine;
        ViewEngineResult viewResult = viewEngine.FindView(controller.ControllerContext, viewName, false);

        ViewContext viewContext = new ViewContext(
            controller.ControllerContext,
            viewResult.View,
            controller.ViewData,
            controller.TempData,
            sw,
            new HtmlHelperOptions()
        );
        viewResult.View.RenderAsync(viewContext);
        return sw.GetStringBuilder().ToString();
    }
  }

}

Как сделать сообщение формы jQuery Ajax в ASP.NET MVC

Что касается  AddOrEdit метода действия, мы связали две функции JavaScript.

  • showInPopup(), который открывает ответ на запрос GET в модальном всплывающем окне Bootstrap.
  • jQueryAjaxPost() отправляет форму с помощью jQuery Ajax.

Теперь давайте определим эти функции в  wwwroot/js/site.js.

showInPopup = (url, title) => {
    $.ajax({
        type: 'GET',
        url: url,
        success: function (res) {
            $('#form-modal .modal-body').html(res);
            $('#form-modal .modal-title').html(title);
            $('#form-modal').modal('show');
        }
    })
}

jQueryAjaxPost = form => {
    try {
        $.ajax({
            type: 'POST',
            url: form.action,
            data: new FormData(form),
            contentType: false,
            processData: false,
            success: function (res) {
                if (res.isValid) {
                    $('#view-all').html(res.html)
                    $('#form-modal .modal-body').html('');
                    $('#form-modal .modal-title').html('');
                    $('#form-modal').modal('hide');
                }
                else
                    $('#form-modal .modal-body').html(res.html);
            },
            error: function (err) {
                console.log(err)
            }
        })
        //to prevent default form submit event
        return false;
    } catch (ex) {
        console.log(ex)
    }
}

Чтобы открыть запрос-ответ с помощью  showInPopup функции, мы использовали модальное окно начальной загрузки, поэтому давайте добавим его HTML-элементы в  _Layout.cshtml  , как показано ниже, прямо перед нижним колонтитулом.

...

<div class="modal" tabindex="-1" role="dialog" id="form-modal">
    <div class="modal-dialog" role="document">
        <div class="modal-content">
            <div class="modal-header">
                <h5 class="modal-title"></h5>
                <button type="button" class="close" data-dismiss="modal" aria-label="Close">
                    <span aria-hidden="true">×</span>
                </button>
            </div>
            <div class="modal-body">

            </div>
        </div>
    </div>
</div>
<footer>
...
</footer>

Внутри  showInPopup метода мы сделаем запрос GET к данному URL-адресу, а запрос-ответ будет сохранен внутри modal-bodydiv. мы открыли модель Bootstrap с помощью  modal функции. Эта  showInPopup функция вызывается для добавления новых транзакций с помощью кнопки заголовка таблицы, а также с помощью кнопки редактирования/обновления, чтобы отобразить детали конкретной транзакции для операции обновления.

В  jQueryAjaxPost функции мы должны выполнить следующие операции

  • Проверка формы  : перед отправкой формы мы должны проверить элементы управления формы. Если проверка не удалась, будет показана та же форма с сообщениями/указаниями об ошибках проверки.
  • Отправка формы  : успешная проверка гарантирует целостность данных. Теперь мы можем сделать почтовый запрос jQuery Ajax для формирования URL-адреса действия. Если запрос выполнен успешно, возвращаемая строка ответа html для таблицы заменяется на место текущего div таблицы. наконец, чтобы предотвратить отправку формы по умолчанию, мы вернули  false.

Операция вставки и обновления выполняется с помощью этой функции. Ведь модальное всплывающее окно будет выглядеть вот так.

Удалить запись с помощью jQuery Ajax

Теперь давайте удалим запись с помощью jQuery Ajax, мы уже добавили кнопку удаления для каждой строки таблицы. На самом деле это форма с кнопкой отправки. Всегда рекомендуется выполнять операцию удаления с почтовым запросом. Здесь мы вызвали  DeleteConfirmedactionметод (URL —  Transaction/Delete ). Теперь нам нужно обновить соответствующий метод удаления сообщения, как показано ниже.

// POST: Transaction/Delete/5
[HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> DeleteConfirmed(int id)
{
    var transactionModel = await _context.Transactions.FindAsync(id);
    _context.Transactions.Remove(transactionModel);
    await _context.SaveChangesAsync();
    return Json(new { html = Helper.RenderRazorViewToString(this, "_ViewAll", _context.Transactions.ToList()) });
}

Теперь давайте добавим соответствующий метод публикации jQuery Ajax —  jQueryAjaxDelete в  wwwroot/js/site.js .

jQueryAjaxDelete = form => {
    if (confirm('Are you sure to delete this record ?')) {
        try {
            $.ajax({
                type: 'POST',
                url: form.action,
                data: new FormData(form),
                contentType: false,
                processData: false,
                success: function (res) {
                    $('#view-all').html(res.html);
                },
                error: function (err) {
                    console.log(err)
                }
            })
        } catch (ex) {
            console.log(ex)
        }
    }

    //prevent default form submit event
    return false;
}

После операции удаления HTML-строка из _ViewAll заменяется на место таблицы. Вот и все, мы завершили реализацию операций jQuery Ajax CRUD в ASP.NET Core MVC. На данный момент наше приложение будет выглядеть так.

Добавить загрузчик Spinner

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

  1. пользователь знает, что в серверной части выполняется текущий запрос. Нужно подождать еще несколько секунд, пока он не завершит свое выполнение.
  2. препятствует дальнейшему взаимодействию пользователя с интерфейсом. следовательно, предотвратите зависание браузера.

Теперь давайте посмотрим, как добавить счетчик загрузки в ASP.NET MVC во время запроса jQuery Ajax. Прежде всего, добавьте следующее  div в основной макет  _Layout.cshtml , прямо над нижним колонтитулом.

<div class="loaderbody" id="loaderbody">
   <div class="loader"></div>
</div>

Мы уже добавили соответствующие стили CSS с анимацией в  site.css . Используя jQuery, мы должны показывать и скрывать этот счетчик во время запроса-ответа Ajax с помощью события загрузки jQuery в  wwwroot/js/site.js.

$(function () {
    $("#loaderbody").addClass('hide');

    $(document).bind('ajaxStart', function () {
        $("#loaderbody").removeClass('hide');
    }).bind('ajaxStop', function () {
        $("#loaderbody").addClass('hide');
    });
});

Запретить прямой доступ к методам действий

В этом приложении мы получили доступ к  AddOrEdit методу действия через jQuery Ajax GET-запрос. вы также можете получить доступ к тому же методу действия GET с URL-адресом —  /Transaction/AddOrEdit . Но он будет содержать только форму без макета, поэтому он не будет работать так, как мы ожидаем. Если есть какие-либо такие методы действия, определенные только для запроса jQuery Ajax, мы должны заблокировать прямой доступ к ним. Для этого мы можем создать  NoDirectAccess атрибут в  файле Helper.cs  .

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class NoDirectAccessAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        if (filterContext.HttpContext.Request.GetTypedHeaders().Referer == null ||
 filterContext.HttpContext.Request.GetTypedHeaders().Host.Host.ToString() != filterContext.HttpContext.Request.GetTypedHeaders().Referer.Host.ToString())
        {
            filterContext.HttpContext.Response.Redirect("/");
        }
    }
}

Теперь вам просто нужно добавить этот атрибут в метод действия GET, AddOrEdit как показано ниже.

[NoDirectAccess]
public async Task<IActionResult> AddOrEdit(int id = 0)
{ ... }

На данный момент это все.