Avatar ·

Как обрабатывать несколько кнопок отправки в одной форме ASP.NET MVC?

Всем привет! Я новичок в ASP.NET MVC и столкнулся с проблемой при создании формы. У меня есть представление, в котором нужно реализовать несколько действий: сохранить черновик и опубликовать статью. Я хочу использовать одну форму с двумя кнопками submit, но не понимаю, как на стороне контроллера определить, какая именно кнопка была нажата. Вот мой текущий код: ```csharp // Модель public class ArticleViewModel { public int Id { get; set; } public string Title { get; set; } public string Content { get; set; } } // Контроллер [HttpPost] public ActionResult Save(ArticleViewModel model) { // Сохранить как черновик return View(model); } [HttpPost] public ActionResult Publish(ArticleViewModel model) { // Опубликовать return View(model); } ``` ```html @using (Html.BeginForm()) { @Html.HiddenFor(m => m.Id) @Html.EditorFor(m => m.Title) @Html.EditorFor(m => m.Content) } ``` Проблема в том, что обе кнопки отправляют форму на один и тот же экшен (по умолчанию), и я не могу понять, как заставить их вызывать разные методы контроллера. Пробовал использовать разные action в Html.BeginForm, но тогда теряются данные модели. Подскажите, как правильно реализовать такую функциональность? Какие есть best practices? Заранее спасибо! <<>>

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

Самый распространённый и простой способ — дать кнопкам одинаковый атрибут name, но разные value. Затем в контроллере можно проверить, какое значение пришло, и вызвать соответствующий метод. Однако в ASP.NET MVC есть более элегантное решение — использовать атрибут value как имя действия.

Вот как это работает:

  1. Дайте кнопкам атрибут name="submitButton" (или любое другое имя).
  2. Установите разные значения value, которые будут соответствовать именам методов контроллера.
  3. Создайте один метод [HttpPost], который принимает модель и строку с именем кнопки.
  4. Внутри метода используйте switch или if для вызова нужной логики.

Пример кода представления:

@using (Html.BeginForm("HandleForm", "Article"))
{
    @Html.HiddenFor(m => m.Id)
    @Html.EditorFor(m => m.Title)
    @Html.EditorFor(m => m.Content)

    <input type="submit" name="action" value="SaveDraft" />
    <input type="submit" name="action" value="Publish" />
}

Контроллер:

[HttpPost]
public ActionResult HandleForm(ArticleViewModel model, string action)
{
    if (!ModelState.IsValid)
    {
        return View(model);
    }

    switch (action)
    {
        case "SaveDraft":
            // Логика сохранения черновика
            TempData["Message"] = "Черновик сохранён";
            break;

        case "Publish":
            // Логика публикации
            TempData["Message"] = "Статья опубликована";
            break;

        default:
            return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
    }

    return RedirectToAction("Index");
}

Более продвинутый подход: кастомный ActionMethodSelectorAttribute

Если вы хотите сохранить отдельные методы для каждой кнопки (как вы изначально пытались), можно создать свой атрибут, который проверяет значение кнопки в запросе. Это потребует больше кода, но даст более чистую архитектуру.

Создайте класс атрибута:

public class SubmitButtonAttribute : ActionMethodSelectorAttribute
{
    public string ButtonName { get; set; }
    public string ButtonValue { get; set; }

    public override bool IsValidForRequest(ControllerContext controllerContext, MethodInfo methodInfo)
    {
        var value = controllerContext.HttpContext.Request[ButtonName];
        return value != null && value == ButtonValue;
    }
}

Теперь используйте его в контроллере:

[HttpPost]
[SubmitButton(ButtonName = "action", ButtonValue = "SaveDraft")]
public ActionResult SaveDraft(ArticleViewModel model)
{
    // Сохранить черновик
    return RedirectToAction("Index");
}

[HttpPost]
[SubmitButton(ButtonName = "action", ButtonValue = "Publish")]
public ActionResult Publish(ArticleViewModel model)
{
    // Опубликовать
    return RedirectToAction("Index");
}

Таким образом, ASP.NET MVC сам выберет правильный метод на основе нажатой кнопки. Этот подход более масштабируемый, если у вас много кнопок или сложная логика.

Важно: Не забывайте про валидацию модели и редирект после POST, чтобы избежать повторной отправки формы при обновлении страницы (принцип Post/Redirect/Get).

<<>>

Альтернативный подход: использование JavaScript для изменения action формы

Если вы предпочитаете не смешивать логику в одном методе контроллера, можно решить задачу на стороне клиента с помощью JavaScript. Этот подход особенно удобен, когда кнопки должны отправлять форму на разные URL или когда у вас много кнопок с разной логикой.

Пример с jQuery:

@using (Html.BeginForm("SaveDraft", "Article"))
{
    @Html.HiddenFor(m => m.Id)
    @Html.EditorFor(m => m.Title)
    @Html.EditorFor(m => m.Content)

    <input type="submit" value="Сохранить черновик" class="btn-action" data-action="SaveDraft" />
    <input type="submit" value="Опубликовать" class="btn-action" data-action="Publish" />
}

<script>
    $(document).ready(function() {
        $('.btn-action').on('click', function(e) {
            e.preventDefault();
            var action = $(this).data('action');
            var form = $(this).closest('form');
            form.attr('action', '/Article/' + action);
            form.submit();
        });
    });
</script>

В контроллере у вас будут два отдельных метода:

[HttpPost]
public ActionResult SaveDraft(ArticleViewModel model)
{
    // Логика
    return RedirectToAction("Index");
}

[HttpPost]
public ActionResult Publish(ArticleViewModel model)
{
    // Логика
    return RedirectToAction("Index");
}

Плюсы: чистая архитектура контроллера, легко тестировать, не нужно парсить имена кнопок.

Минусы: зависимость от JavaScript (если JS отключён, форма не сработает корректно). Для критически важных форм лучше использовать серверный подход из первого ответа.