ASP.NET MVC - 多层面疑惑

时间:2011-07-15 14:08:45

标签: linq-to-sql asp.net-mvc-2 business-logic multi-layer

我一直在使用LinqToSql开发ASP.NET MVC项目。该应用程序有3层:UI,业务和数据。

过去几天我正在实施(我仍然是)Excel文件上传。所以我的Controller接收上传的文件,做一些事情,将信息传递给Business,然后传递给Data。但是,随着这一发展,出现了一些疑问。

以下是我的一些疑问(我认为子弹是最简单的展示方式):

  1. 必须验证Excel文件。应用程序必须验证工作表值是否正确,如果是,则插入/更新到数据库。我应该在Controller或业务中验证Excel吗?

  2. 此Excel可能会向数据库插入数据,例如,new Product();在UI层中创建新实例是否存在问题,或者在商业中更好?将对象从UI传递到Business更好还是更好地传递所有Class属性并在Business中创建对象?

  3. 在这个Excel操作中,我有一些辅助方法,比如验证工作表是否到达终点,验证单元格是否有值,为上传的文件和其他文件生成DataTable。应该放置哪些辅助方法?目前,它们位于UI层(与Controller相同)。

  4. 忘记Excel的事情,想象一个简单的产品表单页面。在POST时,Controller将收到FormCollection。这个FormCollection是应该在Controller上处理还是应该传递给Business and Business做所有的事情?

  5. 对很多问题感到抱歉。我也试图重构我的代码,“胖控制器”问题就在我家门口!

    提前致谢!

1 个答案:

答案 0 :(得分:2)

你确实应该避免使用胖控制器。但总是说起来容易做起来

因此,让我试着用一个例子回答你的问题。与往常一样,您首先要设计一个视图模型,该模型将表示用户发送到此操作的数据(不要使用任何弱类型的FormCollectionViewData

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和您要定义的新业务模型。