asp.net mvc post request + service layer-最好的方法

Har*_*oon 3 c# design-patterns domain-model linq-to-sql asp.net-mvc-2

假设我想发布一个更新房屋状态的帖子请求,理想情况下这些数据应该在某种服务层,通常这涉及到

  1. 验证用户 - 他们仍处于活动状态或被管理员踢出?
  2. 检查houseid - 家庭/记录是否有效?
  3. 用户可以看到房子的详细信息吗?
  4. 将状态更新为"打开"或"关闭"

在现实世界/复杂的领域 - 大多数观点都非常复杂,我们不得不抛弃该地区的房屋数量,房屋的评论数量,房屋的详细信息等等,也许是房子里的未完成任务数量...

简而言之 - 以上所有代码都可能位于服务层内,但是假设抛出异常,用户无法更新房屋状态 - 现在要填充视图,首先要获取房屋详细信息(再次),加载你刚刚在服务层内加载的所有其他东西控制器内的所有内容或另一个装载到加载这些数据的服务层......

如何通过运行验证和所有排序来确保我的域模型受到保护,而无需多次重写相同的代码...

这段代码在action方法内部,很容易在服务层内...

//注意:_repo是一个简单的抽象,超过linq到sql ...

    [HttpGet]
    public ActionResult TaskDetail(int houseid, int taskid)
    {
        var loggedonuser = _repo.GetCurrentUser();

        var _house = _repo.Single<House>(x => x.HouseID == houseid && x.Handler == loggedonuser.CompanyID);

        if (_house == null)
            throw new NoAccessException();

        var summary = _house.ToSummaryDTO();

        var companies = _repo.All<Company>();
        var users = _repo.All<User>();

        var task = _repo.Single<HouseTask>
            (x => x.HouseID == _house.HouseID && x.TaskID == taskid && (x.CompanyID == loggedonuser.CompanyID));

        var dto = new TaskDTO
        {
            TaskID = task.TaskID,
            Title = task.Title,
            Description = task.Description,
            DateCreated = task.DateCreated,
            IsClosed = task.IsClosed,
            CompanyID = companies.Where(y => task.CompanyID == y.CompanyID).SingleOrDefault().Identifier,
        };

        if (task.DueDate.HasValue)
            dto.DueDate = task.DueDate.Value;

        var comments = _repo.All<HouseTaskComment>()
            .Where(x => x.TaskID == task.TaskID)
            .OrderByDescending(x => x.Timestamp)
            .Select(x => new TaskCommentDTO
            {
                Comment = x.Comment,
                Timestamp = x.Timestamp,
                CompanyID = companies.Where(y => x.CompanyID == y.CompanyID).SingleOrDefault().Identifier,
                UserID = users.Where(y => x.UserID == y.UserID).SingleOrDefault().Login,
                Type = EnumHelper.Convert<TaskCommentType>(x.Type)
            });

        dto.AllComments = comments;

        return View(new TaskViewModel
        {
            Summary = summary,
            TaskDetail = dto,
            NewComment = new TaskCommentDTO()
        });
    }
Run Code Online (Sandbox Code Playgroud)

简而言之 - 获取摘要的房屋详细信息,获取任务详细信息(来自多个可用任务)并获取任务注释.这是一个简单的观点恕我直言,没有什么太复杂.

此时,用户可以:添加注释,关闭/打开任务 - 如果他们有权这样做(为简单起见省略了代码),设置任务到期日,甚至清除任务的截止日期.

现在UpdateTaskStatus - 如果无法更新状态必须返回上面的视图,类似于评论,如果你不能评论请返回详细视图 - 评论可能会被关闭.

[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult TaskDueDate(int houseid, int taskid)
{

    var duedate = Request.Form["duedate"];
    var duetime = Request.Form["duetime"];
    try
    {
        if (ModelState.IsValid)
        {

            var newduedate = DateHelper.GoodDate(duedate, duetime);
            _service.SetTaskDueDate(houseid, taskid, newduedate);

            return RedirectToAction("TaskDetail");
        }
    }
    catch (RulesException ex)
    {
        ex.CopyTo(ModelState);
    }

    var loggedonuser = _repo.GetCurrentUser();

    var _house = _repo.Single<House>(x => x.InstructionID == houseid && x.HandlerID == loggedonuser.CompanyID);

    if (_house == null)
        throw new NoAccessException();

    var summary = _house.ToSummaryDTO();

    var companies = _repo.All<Company>();
    var users = _repo.All<User>();

    var task = _repo.Single<HouseTask>
        (x => x.InstructionID == _house.HouseID && x.CompanyID == loggedonuser.CompanyID && x.TaskID == taskid);

    var dto = new TaskDTO
    {
        TaskID = task.TaskID,
        Title = task.Title,
        Description = task.Description,
        DateCreated = task.DateCreated,
        IsClosed = task.IsClosed,
        CompanyID = companies.Where(y => task.CompanyID == y.CompanyID).SingleOrDefault().Identifier
    };

    if (task.DueDate.HasValue)
        dto.DueDate = task.DueDate.Value;

    var comments = _repo.All<HouseTaskComment>()
        .Where(x => x.TaskID == task.TaskID)
        .OrderByDescending(x => x.Timestamp)
        .Select(x => new TaskCommentDTO
        {
            Comment = x.Comment,
            Timestamp = x.Timestamp,
            CompanyID = companies.Where(y => x.CompanyID == y.CompanyID).SingleOrDefault().Identifier,
            UserID = users.Where(y => x.UserID == y.UserID).SingleOrDefault().Login
        });

    dto.AllComments = comments;

    return View("TaskDetail", new TaskViewModel
    {
        Summary = summary,
        TaskDetail = dto,
        NewComment = new TaskCommentDTO()
    });
}
Run Code Online (Sandbox Code Playgroud)

我知道上面的代码结构很糟糕,但有关如何纠正它的一些建议将不胜感激.

  1. 我在动作中留下所有只读代码,因为每个视图可能不同,我不希望服务层干扰这里
  2. 我想"保护"我的更新/编辑并将其保存在我的服务层或核心项目(sepeparate c#class lib)甚至Domain层中,我将如何编写代码句柄验证,(这是我在服务中所做的)打电话),执行实际保存?

我听说过CommandHandler方法,这是一个好方法吗?理想情况下,我希望在我的域内使用一种简单的方法而不是控制器操作来保持我的验证+持久性....

Rob*_*nik 7

也许你过分思考这一点

据我了解你的过程,这是应该做的

  1. 用户验证(无论它是否仍然是有效用户)是在用户登录时完成的,而不是在以后进行,因为未注册(或被踢出的用户)应该无法访问您的应用程序.如果您的用户可以在工作中被踢出,您可以通过创建操作过滤器并将其置于以下任一位置来解决此问题:

    • 所有控制器类(不是动作)

    • 有一个基本控制器类,并在其上放置过滤器

  2. 您的步骤2,3和4实际上是相关的,因为

    • 首先你必须得到房屋数据(包括许可)

    • 如果房屋ID无效,您将无法从DB获得任何回复

    • 如果你确实获得了数据,那么检查权限

    • 允许时相应地设置状态

附加说明

对于复杂的域模型,通常不需要具有复杂的视图.您可能只有更多它们并具有更复杂的导航以帮助实现.用户也会很高兴(呃)使用简单的视图而不是复杂的视图.

模型验证

我将保持静态验证,因为Asp.net MVC实现 - 在POCO对象层上使用数据注释 - 因为它是内置的并且会减少代码(减少行数=较小的bug表面)并且它会使它更加健壮,因为你不会忘记忘记验证一些东西.

但是,您的动态验证(与实体实例上的特定用户/公司权限相关)最好保留在服务层中.如果发生任何违规,我会抛出自定义异常并将它们传播到模型状态(类似于您正在做的 - 基于我对您的代码的理解 - 使用RulesException类).

为了避免繁琐(和重复代码)的try/catch代码块,您可以编写一个自定义异常过滤器,将值传播到模型状态并返回显示模型状态错误所需的任何视图.这种异常过滤器的实现完全在你身上.然后把它放在你的父控制器上(因为你没有使用具有全局过滤器的MVC 3)而忘了它.这也将减少重复的代码.

如果将动态模型验证放入服务层,则必须将用户所需的详细信息传递给它,因为它依赖于它们.

用户提取

我可以看到你每次都从数据存储中读取用户数据,这是不可取的.大多数情况下,您可能只需要用户ID(显然还有公司ID).这些小数据可以以不同的方式保存(通常在安全的cookie中,但您可以决定自己的策略),这样可以节省一些时间.使此缓存数据无效是相当简单的,因为用户可能只能更改自己的数据,因此您可以在此时无效并重新读取.您可以将其与您经常需要的其他数据(可能是用户显示名称或登录名)捆绑在一起.但就是这样.大多数情况下,您的代码将使用用户ID.

模型声明/许可验证

您的模型不仅要验证模型实体数据是否正确,还需要验证声明(或权限),即特定用户是否具有更改实体权限(取决于其公司).你说你不能把它放在过滤级别,但尽管我可以看到你的问题.如果您保留用户数据(如前所述),则可以获得所需的所有信息.您拥有用户数据,拥有自己的模型,并且根据您还知道与该操作相关的静态声明的操作.

[RequirePermission(Permission.UpdateTaskDueDate)]
public ActionResult TaskDueDate(...) { ... }
Run Code Online (Sandbox Code Playgroud)

如果动作过滤器不能那么简单,你也可以为它提供一个自定义验证器类,它实现一个特定的接口(所以过滤器可以调用any)和应该验证的参数名.这样就可以根据用户/公司数据验证自定义类.它还允许您使用不同的验证器验证多个权限或甚至相同/不同的权限.

[RequirePermission(Permission.UpdateTaskDueDate, typeof(TaskValidator), "paramName")]
public ActionResult TaskDueDate(...) { ... }
Run Code Online (Sandbox Code Playgroud)

此验证器的Validate方法将获取权限枚举值并验证其所需的内容.

还有另一种可能性.您可以在定义其自定义验证器的实体类上具有自定义属性(或更多属性),因此过滤器仍将仅采用应验证的权限类型.这样还可以轻松地在单个操作上验证多个对象.只需提供权限,过滤器就会检查操作参数,类型和声明的自定义验证器.

使用过滤器几乎不调用的自定义验证程序也可以在服务层上进行验证,而不是在演示文稿中.因此,您的域逻辑将如何验证这些逻辑,从而使UI独立于底层代码.如果有任何改变,只有你的输入和输出类型应该匹配,但所有的UI层代码应该保持原样.

读取请求数据

Request.Form["duedate"]您应该将这些作为操作参数,而不是使用您的数据来读取数据.它们将由MVC为您填充.因为在你的代码中:

try
{
    if (ModelState.IsValid)
    {

        var newduedate = DateHelper.GoodDate(duedate, duetime);
        _service.SetTaskDueDate(houseid, taskid, newduedate);

        return RedirectToAction("TaskDetail");
    }
}
catch (RulesException ex)
{
    ex.CopyTo(ModelState);
}
Run Code Online (Sandbox Code Playgroud)

if声明完全是多余的.您的整数参数在操作体内始终有效(它们未设置为可为空).无论您DateHelper.GoodDate是静态地检查另一对,它们都可以更好地包含在自定义类中(TaskId也可以),并且可以在其上放置数据注释.并且模型验证实际上具有无效的能力(因此if声明是有意义的).

但是,不要在TaskDueDate动作中重复相同的代码,而是可以通过这种方式完成:

[HttpPost]
[ValidateAntiForgeryToken]
[RequirePermission(Permission.UpdateTaskDueDate)]
public ActionResult TaskDueDate(int houseid, TaskDue taskDueData)
{
    if (!this.ModelState.IsValid)
    {
        // don't repeat code and just call another action within this controller
        return this.TaskDetail(houseid, taskDueData.TaskId);
    }
    _service.SetTaskDueDate(houseid, taskid, newduedate);
    return RedirectToAction("TaskDetail");
}
Run Code Online (Sandbox Code Playgroud)

因为你在两个动作中做同样的事情.没有人说你不能在行动中召唤其他行动.ActionResult无论如何他们都会回来.

我认为我们都同意这一行动更加简化.

动作方法代码

我不会将所有这些_repo调用放在控制器操作中,因为这是服务域代码.您所要做的就是提供参数并调用服务层以提供视图消耗的对象.

结语

我希望我至少回答了你的一些问题/问题/问题,因为我很难解读实际问题是什么(或者更好地说明你究竟在问什么).你以相当混乱的方式写了你的问题,因此没有回答你的问题.人们没有得到你的问题.

无论如何.我试图重构你的代码并解释如果我必须实现动态验证我该怎么做.