我一直在使用LinqToSql开发ASP.NET MVC项目。该应用程序有3层:UI,业务和数据。
过去几天我正在实施(我仍然是)Excel文件上传。所以我的Controller接收上传的文件,做一些事情,将信息传递给Business,然后传递给Data。但是,随着这一发展,出现了一些疑问。
以下是我的一些疑问(我认为子弹是最简单的展示方式):
必须验证Excel文件。应用程序必须验证工作表值是否正确,如果是,则插入/更新到数据库。我应该在Controller或业务中验证Excel吗?
此Excel可能会向数据库插入数据,例如,new Product();
在UI层中创建新实例是否存在问题,或者在商业中更好?将对象从UI传递到Business更好还是更好地传递所有Class属性并在Business中创建对象?
在这个Excel操作中,我有一些辅助方法,比如验证工作表是否到达终点,验证单元格是否有值,为上传的文件和其他文件生成DataTable。应该放置哪些辅助方法?目前,它们位于UI层(与Controller相同)。
忘记Excel的事情,想象一个简单的产品表单页面。在POST时,Controller将收到FormCollection。这个FormCollection是应该在Controller上处理还是应该传递给Business and Business做所有的事情?
对很多问题感到抱歉。我也试图重构我的代码,“胖控制器”问题就在我家门口!
提前致谢!
答案 0 :(得分:2)
你确实应该避免使用胖控制器。但总是说起来容易做起来。
因此,让我试着用一个例子回答你的问题。与往常一样,您首先要设计一个视图模型,该模型将表示用户发送到此操作的数据(不要使用任何弱类型的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
和您要定义的新业务模型。