1

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

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

以下是我的一些疑问(我认为项目符号是最简单的展示方式):

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

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

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

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

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

提前致谢!

4

1 回答 1

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 个依赖项:第一个负责解析输入流并返回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. 在这种情况下,可以在控制器操作中使用AutoMapperUploadViewModel在您定义的新业务模型和新业务模型之间进行映射。

于 2011-07-16T08:19:54.810 回答