ASP.NET MVC - многослойные сомнения - PullRequest
1 голос
/ 15 июля 2011

Я работал над проектом ASP.NET MVC с LinqToSql. Приложение имеет 3 уровня: пользовательский интерфейс, бизнес и данные.

Последние несколько дней я выполнял (я все еще использую) загрузку файла Excel. Таким образом, мой Контроллер получает загруженный файл, делает некоторые вещи, передает информацию в Бизнес, а затем в Данные. Но вместе с этим возникли некоторые сомнения.

Вот некоторые из моих сомнений (я думаю, что пуля - самый простой способ показать):

  1. Файл Excel должен быть проверен. Приложение должно проверить правильность значений листа и, если они есть, вставить / обновить базу данных. Должен ли я проверить Excel в контроллере или в бизнесе?

  2. Этот Excel может вставлять данные в БД, например, new Product(); Есть ли проблема при создании новых экземпляров на уровне пользовательского интерфейса или это лучше делать в Business? Лучше передать объект из пользовательского интерфейса в Business или лучше передать все свойства Class и создать объект в Business?

  3. В этом действии Excel у меня есть несколько вспомогательных методов, таких как проверка того, достиг ли рабочий лист своего конца, проверка наличия значения в ячейке, создание DataTable для загруженного файла и некоторые другие. Где эти вспомогательные методы должны быть размещены? На данный момент они находятся на уровне пользовательского интерфейса (так же, как контроллер).

  4. Забыв о вещах Excel, представьте себе простую страницу формы Product. На POST Контроллер получит FormCollection. Должна ли эта FormCollection обрабатываться на контроллере, или она должна быть передана бизнесу, и бизнес все сделает?

Извините за несколько вопросов. Я также пытаюсь реорганизовать свой код, и проблема "Fat Controller" находится прямо у меня на пороге!

Заранее спасибо!

1 Ответ

2 голосов
/ 16 июля 2011

Вы действительно должны избегать жировых контроллеров . Но, как всегда, легче сказать, чем сделать .

Итак, позвольте мне попытаться ответить на ваши вопросы на примере. Как всегда, вы начнете с разработки модели представления, которая будет представлять данные, которые пользователь отправляет на это действие (не используйте слабо типизированные FormCollection или ViewData)

public class UploadViewModel
{
    [Required]
    public HttpPostedFileBase File { get; set; }
}

затем мы переходим к контроллеру:

public ProductsController: Controller
{
    private readonly IProductsService _service;
    public ProductsController(IProductsService service)
    {
        _service = service;
    }

    public ActionResult Upload()
    {
        var model = new UploadViewModel();
        return View(model);
    }

    [HttpPost]
    public ActionResult Upload(UploadViewModel model)
    {
        if (!ModelState.IsValid)
        {
            // The model was not valid => redisplay the form 
            // so that the user can fix his errors
            return View(model);
        }

        // at this stage we know that the model passed UI validation
        // so let's hand it to the service layer by constructing a
        // business model
        string error;
        if (!_service.TryProcessFile(model.File.InputStream, out error))
        {
            // there was an error while processing the file =>
            // redisplay the view and inform the user
            ModelState.AddModelError("file", error);
            return View(model);
        }

        return Content("thanks for submitting", "text/plain");
    }
}

и последний бит - уровень обслуживания. Он будет иметь 2 зависимости: первая будет заботиться о разборе входного потока и возвращать список Product с, а вторая будет заботиться о сохранении этих продуктов в базе данных.

Так же, как это:

public class ProductsService: IProductsService
{
    private readonly IProductsParser _productsParser;
    private readonly IProductsRepository _productsRepository;
    public ProductsService(IProductsParser productsParser, IProductsRepository productsRepository)
    {
        _productsParser = productsParser;
        _productsRepository = productsRepository;
    }

    public bool TryProcessFile(Stream input, out string error)
    {
        error = "";
        try
        {
            // Parse the Excel file to extract products
            IEnumerable<Product> products = _productsParser.Parse(input);

            // TODO: Here you may validate whether the products that were
            // extracted from the Excel file correspond to your business
            // requirements and return false if not

            // At this stage we have validated the products => let's persist them
            _productsRepository.Save(products);
            return true;
        }
        catch (Exception ex)
        {
            error = ex.Message;
        }
        return false;
    }
}

Тогда, конечно, у вас будет две реализации этих зависимостей:

public class ExcelProductsParser: IProductsParser
{
    public IEnumerable<Product> Parse(Stream input)
    {
        // parse the Excel file and return a list of products
        // that you might have extracted from it
        ...
    }
}

и хранилище:

public class Linq2SqlProductsRepository: IProductsRepository
{
    public void Save(IEnumerable<Product> products)
    {
        // save the products to the database
        ...
    }
}

Примечание: Вы можете обогатить модель представления дополнительными свойствами, которые будут представлять некоторые метаданные, которые мы могли бы связать с этой загрузкой файла, и которые могли бы иметь некоторые соответствующие поля ввода в форме. Затем вы можете определить бизнес-модель для перехода к методу TryProcessFile вместо простого Stream. В этом случае AutoMapper может использоваться в действии контроллера для сопоставления между UploadViewModel и этой новой бизнес-моделью, которую вы определите.

...