Админ панель: добавление и удаление товаров

117 Исходный код проекта

Итак, мы уже добавили в админ-панель приложения GameStore некоторую функциональность CRUD - возможность просматривать список товаров (read) и редактировать товары (Update). Теперь осталось добавить возможность удаления (delete) и добавления новых товаров (create), что мы и реализуем в этой статье.

Создание новых товаров

Мы реализуем метод действия Create(), который указан в ссылке "Добавить игру" на странице со списком товаров. Он позволит администратору добавлять новые элементы в каталог товаров. Добавление возможности создания новых товаров требует только одного небольшого дополнения и одного изменения в приложении. Это является великолепной демонстрацией мощи и гибкости хорошо структурированного приложения MVC. Для начала добавьте в контроллер Admin метод Create(), как показано в примере ниже:

using System.Web.Mvc;
using GameStore.Domain.Abstract;
using GameStore.Domain.Entities;
using System.Linq;

namespace GameStore.WebUI.Controllers
{
    public class AdminController : Controller
    {
        // ...

        public ViewResult Create()
        {
            return View("Edit", new Game());
        }
	}
}

Метод Create() не визуализирует свое стандартное представление. Вместо этого в нем указано, что должно применяться представление Edit. Использование в методе действия представления, которое обычно связано с другим методом действия, вполне допустимо. В рассматриваемом случае мы внедряем новый объект Game в качестве модели представления, так что представление Edit заполняется пустыми полями.

Модульный тест для метода действия Create() не предусмотрен. Это позволило бы протестировать лишь возможность инфраструктуры MVC Framework обработать объект ViewResult, возвращаемый в качестве результата метода действия, корректность которой не вызывает сомнений. (Обычно тесты для лежащих в основе инфраструктур не пишутся, если только нет подозрения о наличии проблемы.)

Это приводит к необходимости во внесении ряда изменений. Обычно ожидается, что форма осуществляет обратную отправку визуализировавшему ее действию, и когда метод Html.BeginForm() генерирует HTML-форму, такое предполагается по умолчанию. Однако это не подходит для метода Create(), т.к. мы хотим, чтобы форма выполняла обратную отправку действию Edit, что позволит сохранить вновь созданные данные о товаре.

Для решения проблемы мы можем воспользоваться перегруженной версией вспомогательного метода Html.BeginForm(), которая дает возможность указать, что целью формы, сгенерированной в представлении Edit, является метод действия Edit() контроллера Admin. В примере ниже показаны изменения, внесенные в файл представления Views/Admin/Edit.cshtml:

...

<div class="panel">
    ...

    @using (Html.BeginForm("Edit", "Admin"))
    {
        ...
    }
</div>

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

Добавление нового товара в каталог

Удаление товаров

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

using System.Collections.Generic;
using GameStore.Domain.Entities;

namespace GameStore.Domain.Abstract
{
    public interface IGameRepository
    {
        IEnumerable<Game> Games { get; }
        void SaveGame(Game game);
        Game DeleteGame(int gameId);
    }
}

Затем этот метод реализуется в классе хранилища Entity Framework по имени EFGameRepository:

using System.Collections.Generic;
using GameStore.Domain.Entities;
using GameStore.Domain.Abstract;

namespace GameStore.Domain.Concrete
{
    public class EFGameRepository : IGameRepository
    {
        // ...

        public Game DeleteGame(int gameId)
        {
            Game dbEntry = context.Games.Find(gameId);
            if (dbEntry != null)
            {
                context.Games.Remove(dbEntry);
                context.SaveChanges();
            }
            return dbEntry;
        }
    }
}

Завершающий шаг предусматривает реализацию метода действия Delete() в контроллере Admin. Этот метод действия должен поддерживать только запросы POST, поскольку удаление объектов является изменяющей операцией. Браузеры и кеши вольны выдавать запросы GET без явного согласия пользователя, поэтому следует проявлять осторожность, чтобы избежать внесения изменений в результате обработки запросов GET. Новый метод действия приведен в примере ниже:

using System.Web.Mvc;
using GameStore.Domain.Abstract;
using GameStore.Domain.Entities;
using System.Linq;

namespace GameStore.WebUI.Controllers
{
    public class AdminController : Controller
    {
        // ...

        [HttpPost]
        public ActionResult Delete(int gameId)
        {
            Game deletedGame = repository.DeleteGame(gameId);
            if (deletedGame != null)
            {
                TempData["message"] = string.Format("Игра \"{0}\" была удалена",
                    deletedGame.Name);
            }
            return RedirectToAction("Index");
        }
	}
}

Модульное тестирование: удаление товаров

Тестированию подлежит основное поведение метода действия Delete(), которое заключается в том, что при передаче в качестве параметра допустимого идентификатора GameId метод действия должен вызвать метод DeleteGame() хранилища и передать ему корректное значение GameId удаляемого товара. Ниже приведен тестовый метод:

// ...

[TestMethod]
public void Can_Delete_Valid_Games()
{
    // Организация - создание объекта Game
    Game game = new Game { GameId = 2, Name = "Игра2" };

    // Организация - создание имитированного хранилища данных
    Mock<IGameRepository> mock = new Mock<IGameRepository>();
    mock.Setup(m => m.Games).Returns(new List<Game>
    {
        new Game { GameId = 1, Name = "Игра1"},
        new Game { GameId = 2, Name = "Игра2"},
        new Game { GameId = 3, Name = "Игра3"},
        new Game { GameId = 4, Name = "Игра4"},
        new Game { GameId = 5, Name = "Игра5"}
    });

    // Организация - создание контроллера
    AdminController controller = new AdminController(mock.Object);

    // Действие - удаление игры
    controller.Delete(game.GameId);

    // Утверждение - проверка того, что метод удаления в хранилище
    // вызывается для корректного объекта Game
    mock.Verify(m => m.DeleteGame(game.GameId));
}

Чтобы увидеть средство удаления в работе, просто щелкните на одной из кнопок "Удалить", на странице со списком товаров, показанной на рисунке ниже. На этом рисунке легко заметить, что с помощью переменной TempData отображается сообщение об удалении товара из каталога:

Удаление товара из каталога
Пройди тесты
Лучший чат для C# программистов