ASP.NET MVC - многослойные сомнения
Я работал над проектом ASP.NET MVC с LinqToSql. Приложение имеет 3 уровня: пользовательский интерфейс, бизнес и данные.
Последние несколько дней я выполнял (я все еще) загрузку файла Excel. Таким образом, мой Контроллер получает загруженный файл, делает некоторые вещи, передает информацию в Бизнес, а затем в Данные. Но вместе с этим возникли некоторые сомнения.
Вот некоторые из моих сомнений (я думаю, что пуля - самый простой способ показать):
Файл Excel должен быть проверен. Приложение должно проверить правильность значений листа и, если они есть, вставить / обновить базу данных. Должен ли я проверить Excel в контроллере или в бизнесе?
Этот Excel может вставлять данные в БД, например,
new Product();
Есть ли проблема в создании новых экземпляров на уровне пользовательского интерфейса или это лучше делать в бизнесе? Лучше передать объект из пользовательского интерфейса в Business или лучше передать все свойства Class и создать объект в Business?В этом действии Excel у меня есть несколько вспомогательных методов, таких как проверка того, подошел ли лист к концу, проверка наличия значения в ячейке, создание таблицы данных для загруженного файла и некоторые другие. Где эти вспомогательные методы должны быть размещены? На данный момент они находятся на уровне пользовательского интерфейса (так же, как контроллер).
Забыв о вещах Excel, представьте простую страницу формы Product. На POST Контроллер получит FormCollection. Должна ли эта FormCollection обрабатываться на контроллере или она должна быть передана бизнесу, и бизнес все это делает?
Извините за несколько вопросов. Я также пытаюсь реорганизовать свой код, и проблема "Fat Controller" прямо у меня на пороге!
Заранее спасибо!
1 ответ
Вы действительно должны избегать жирных контроллеров. Но как всегда легче сказать, чем сделать.
Итак, позвольте мне попытаться ответить на ваши вопросы на примере. Как всегда, вы начнете с разработки модели представления, которая будет представлять данные, которые пользователь отправляет на это действие (не используйте слабо типизированные 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
s, а второй позаботится о сохранении этих продуктов в базе данных.
Именно так:
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
и эта новая бизнес-модель, которую вы бы определили.