【问题标题】:ASP.NET MVC - Multilayer doubtsASP.NET MVC - 多层疑虑
【发布时间】:2011-07-15 14:08:44
【问题描述】:

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

最近几天我正在实施(现在仍然是)Excel 文件上传。所以我的控制器接收上传的文件,做一些事情,将信息传递给业务,然后传递给数据。但是,随着它的发展,也出现了一些疑问。

以下是我的一些疑惑(我认为项目符号是最简单的表达方式):

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

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

  3. 在这个 Excel 操作中,我有一些辅助方法,例如验证工作表是否到达末尾、验证单元格是否有值、为上传的文件生成数据表等。这些辅助方法应该放在哪里?目前,它们位于 UI 层(与 Controller 相同)。

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

很抱歉有很多问题。我也在尝试重构我的代码,“胖控制器”问题就在我的门前!

提前致谢!

【问题讨论】:

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


    【解决方案1】:

    您确实应该避免使用 胖控制器。但一如既往说起来容易做起来难

    所以让我试着用一个例子来回答你的问题。与往常一样,您将首先设计一个视图模型,该模型将表示用户发送给此操作的数据(不要使用任何弱类型的 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 个依赖项:第一个负责解析输入流并返回 Products 列表,第二个负责将这些产品持久化到数据库中。

    就像这样:

    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 和您将定义的新业务模型之间进行映射。

    【讨论】:

    • 达林,感谢您的 cmets!它对我对重构代码产生了一些扎实的想法帮助很大!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-06-11
    • 1970-01-01
    • 2011-11-15
    相关资源
    最近更新 更多