c# - asp.net mvc发布请求+服务层-最好的方法

标签 c# linq-to-sql asp.net-mvc-2 design-patterns domain-model

假设我想发布请求以更新房屋的状态,理想情况下,此数据应位于某种服务层中,通常这涉及


验证用户-他们仍然处于活动状态或被管理员踢出?
检查房屋编号-房屋编号/记录是否有效?
用户可以看到房屋详细信息吗?
将状态更新为“打开”或“关闭”


在现实世界/复杂的领域中-大多数视图都非常复杂,我们可能不得不抛弃该地区的房屋数量,房屋评论数,房屋详细信息等等,或者房屋中尚待完成的任务数...

简而言之-以上所有代码都可能位于服务层中,但是可以说引发了异常,用户无法更新房屋的状态-现在要填充视图,您必须首先获取房屋详细信息(再次)将您刚刚在服务层中加载的所有其他内容存储在控制器内部,或者将其他内容添加到加载该数据的服务层中...

我如何通过运行验证和所有种类来确保我的域模型受到保护,而不必多次重写同一代码...

这段代码在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()
        });
    }


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

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

现在,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()
    });
}


我知道上面的代码结构不正确,但是希望您提出一些有关纠正它的建议。


我将所有只读代码保留在操作中,因为每个视图都可能不同,所以我不希望在这里干扰服务层
我想“保护”我的更新/编辑并将其保存在我的服务层或核心项目(独立的c#类库)甚至是域层中,我将如何编写代码处理验证(这就是我在服务内部所做的工作)调用),执行实际保存?


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

最佳答案

也许您对此有点过分考虑

据我了解您的流程,这是应该完成的方法


用户验证(是否仍然是有效用户)是在用户登录时完成的,以后不会进行处理,因为未注册(或踢出用户)的用户将无法访问您的应用。如果您的用户可能会在工作中被踢出局,则可以通过创建操作过滤器并将其放在以下任一方法上来解决此问题:


您所有的控制器类(不是动作)
有一个基本的控制器类,并在上面放上过滤器

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


首先,您必须获得房屋数据(包括许可)
如果房屋ID无效,您将无法从DB取回任何东西
如果确实获取了数据,请检查权限
允许时相应地设置状态



附加说明

对于复杂的域模型,通常不必具有复杂的视图。您可能只拥有更多的组件,并且导航更加复杂以提高可用性。用户也很乐意使用简单视图而不是复杂视图。

模型验证

我将在Asp.net MVC实施时保持静态验证-在POCO对象层上使用数据注释-因为它是内置的,并且可以减少代码(更少的行=较小的错误表面),并且它将变得更健壮,因为您不会偶然忘记验证一些东西。

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

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

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

用户获取

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

模型声明/权限验证

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

[RequirePermission(Permission.UpdateTaskDueDate)]
public ActionResult TaskDueDate(...) { ... }


如果无法简单地完成动作过滤器,则还可以为其提供一个自定义验证器类,该类实现某个接口(因此过滤器可以调用任何接口)和应验证的参数名称。这样就可以根据用户/公司数据验证自定义类。它还将允许您使用不同的验证器来验证多个权限,甚至是相同/不同的权限。

[RequirePermission(Permission.UpdateTaskDueDate, typeof(TaskValidator), "paramName")]
public ActionResult TaskDueDate(...) { ... }


该验证器的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);
}


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");
}


因为您在两个动作中都做相同的事情。没有人说您不能在动作中调用其他动作。无论如何,它们都返回ActionResult

我认为我们都同意这一行动要简化得多。

动作方法代码

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

结语

我希望我至少回答了您的一些问题/问题/问题,因为对于我来说很难弄清实际问题是什么(或者最好说出您到底在问什么)。您以一种相当混乱的方式写了问题,因此没有得到问题的答案。人们不明白您的问题是什么。

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

关于c# - asp.net mvc发布请求+服务层-最好的方法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/6942292/

相关文章:

c# - 扩展方法、T的Func和T的List

c# - 公开可用操作的正确模式

c# - Visual Basic 中的“Friend WithEvents”与 C# 中的 'private'

c# - LINQ 查询除了不起作用,List<long?> 与 List<long>

c# - 从另一个 DataContext 加载?

ASP.NET MVC 经验/成功案例 [2010]

c# - C#创建一个对象时,会在内存中创建一个类中的多少个方法?

c# - 使用 LINQ to SQL 进行分页

c# - 如何在 Html.TextBoxFor 中使用 ShortDate 字符串格式

c# - 绑定(bind)请求参数到具体的方法参数