Asp.Net-Mvc-5中的精简控制器具体示例

时间:2017-10-03 12:53:18

标签: c# design-patterns model-view-controller asp.net-mvc-5

我对在Asp.net Mvc 5应用程序中创建“瘦控制器”的实现有疑问。在过去的几天里,我研究了这个话题,我相信我需要一个具体的例子,以便在理解中连接点。

所以,我想在我的应用程序中使用单元测试。我已经看过创建视图模型工厂和构建器,瘦的控制器 - 脂肪模型,但我不确定如何实现我在这个特定场景中读过的任何这些设计模式。

您可以在下方找到我的管理控制器中的5种不同操作。我担心它们闻起来需要一些清理以简化测试/单元测试。我理解这些类型的问题通常没有“正确的答案”,所以我非常感谢所有有助于简化测试应用程序的答案。

以下是我的行动:

行动#1:

[HttpPost]
[ValidateAntiForgeryToken]
[Authorize(Roles = "DM_Admin")]

public async Task<ActionResult> Users_Create([DataSourceRequest] DataSourceRequest request, ManageUsersViewModel model)

{
    if (model != null && ModelState.IsValid)
    {
        // instantiate new application user
        var user = new ApplicationUser
        {
            UserName = model.Email,
            Email = model.Email,
            FirstName = model.FirstName,
            LastName = model.LastName
        };

        // format the RolesList to type List<string> for entry  
        List<string> rolesToAssign = getRoleNameList(model);

        try
        {
            // persist user to User Db
            var createResult = await UserManager.CreateAsync(user, model.Password);
            if (createResult.Succeeded)
            {
                // persist user roles to User Db
                var rolesResult = await UserManager.AddToRolesAsync(user.Id, rolesToAssign.ToArray());
                if (rolesResult.Succeeded)
                {
                    return RedirectToAction("ManageUsers");
                }
                AddErrors(rolesResult);
            }
            else
            {
                AddErrors(createResult);
            }
        }
        catch (Exception ex)
        {
            ErrLog.LogError(ex, "ManageController.Users_Create");
        }
    }

    return Json(new[] { model }.AsQueryable().ToDataSourceResult(request, ModelState));
}

行动#2:

[Authorize(Roles = "DM_Admin")]
public async Task<ActionResult> Users_Read([DataSourceRequest] DataSourceRequest request)

{
    List<ManageUsersViewModel> users = null;
List<ApplicationUser> allUsers = null;

try
{
    // get all Data Management roles
    var dmRoles = await RoleManager.Roles.Where(r => r.Name.Contains("DM_")).ToListAsync();

    // find all the users for each Data Management role
    foreach (var id in dmRoles.Select(r => r.Id).ToList())
    {
        if(allUsers == null)
        {
            allUsers = await UserManager.Users.Where(u => u.Roles.Any(r => r.RoleId == id)).ToListAsync();
        }
        else
        {
            allUsers.AddRange(await UserManager.Users.Where(u => u.Roles.Any(r => r.RoleId == id)).ToListAsync());
        }
    }

    // list of users may have repeats, so remove repeated users in list 
    allUsers = allUsers.Distinct().ToList();
    // instantiate view model with list of users and their respective roles
    users = allUsers.Select(u => new ManageUsersViewModel
    {
        Id = u.Id,
        FirstName = u.FirstName,
        LastName = u.LastName,
        Email = u.Email,
        Password = u.PasswordHash,
        ConfirmPassword = u.PasswordHash,
        RolesList = dmRoles.Where(r => r.Users.Any(user => user.UserId == u.Id)).Select(r => new RoleModel
                {
                    Id = r.Id,
                    Name = r.Name
                }).ToList()
    }).ToList();
}
catch (Exception ex)
{
    ErrLog.LogError(ex, "ManageController.Users_Read");
}

return Json(users.ToDataSourceResult(request));
}

行动#3:

[HttpPost]
[ValidateAntiForgeryToken]
[Authorize(Roles = "DM_Admin")]
public async Task<ActionResult> Users_Edit([DataSourceRequest] DataSourceRequest request, ManageUsersViewModel model)
{
    if (model != null && ModelState.IsValid)
    {
        // format the RolesList to type List<string> with name and List<string> with Id for entry and comparison
        List<string> rolesToAssign = getRoleNameList(model);
        List<string> modelRoleIds = getRoleIdList(model);
    try
    {
        var currentUser = await UserManager.FindByIdAsync(model.Id);
        // create list of current user's roles to determine if they have been modified
        List<string> currentUserRoleIds = new List<string>();
        foreach (var role in currentUser.Roles)
        {
            currentUserRoleIds.Add(role.RoleId);
        }

        // persist user roles to User Db if changes have been made
        if (currentUserRoleIds.Except(modelRoleIds).Any() || modelRoleIds.Except(currentUserRoleIds).Any())
        {
            var updateRolesResult = await AssignRolesToUser(model.Id, rolesToAssign.ToArray());
            if (updateRolesResult.Succeeded)
            {
                return RedirectToAction("ManageUsers", new { Message = ManageMessageId.AccountUpdateSuccess });
            }
            AddErrors(updateRolesResult);
        }
        // persist user info to User Db if changes have been made
        else if(currentUser.Email != model.Email || currentUser.FirstName != model.FirstName || currentUser.LastName != model.LastName)
        {
            currentUser.UserName = model.Email;
            currentUser.Email = model.Email;
            currentUser.FirstName = model.FirstName;
            currentUser.LastName = model.LastName;

            var updateUserResult = await UserManager.UpdateAsync(currentUser);
            if (updateUserResult.Succeeded)
            {
                return RedirectToAction("ManageUsers", new { Message = ManageMessageId.AccountUpdateSuccess });
            }
            AddErrors(updateUserResult);
        }
        // persist user password to User Db if changes have been made
        else
        {
            var token = await UserManager.GeneratePasswordResetTokenAsync(currentUser.Id);
            var updatePasswordResult = await UserManager.ResetPasswordAsync(currentUser.Id, token, model.Password);
            if (updatePasswordResult.Succeeded)
            {
                return RedirectToAction("ManageUsers", new { Message = ManageMessageId.AccountUpdateSuccess });
            }
            AddErrors(updatePasswordResult);
        }
    }
    catch (Exception ex)
    {
        ErrLog.LogError(ex, "ManageController.Users_Edit");
    }
}

return Json(new[] { model }.AsQueryable().ToDataSourceResult(request, ModelState));
}

行动#4:

[HttpPost]
[ValidateAntiForgeryToken]
[Authorize(Roles = "DM_Admin")] 
public async Task<ActionResult> Users_Delete([DataSourceRequest] DataSourceRequest request, ManageUsersViewModel model)
{
    if (model != null && ModelState.IsValid)
    {
        // format the RolesList to type List<string> for removal  
        List<string> rolesToRemove = getRoleNameList(model);
    try
    {
        // get the user to be deleted by id
        var user = await UserManager.FindByIdAsync(model.Id);

        // remove roles from user in User Db
        var removeRolesResult = await UserManager.RemoveFromRolesAsync(user.Id, rolesToRemove.ToArray());
        if (removeRolesResult.Succeeded)
        {
            // remove user from User Db
            var removeUserResult = await UserManager.DeleteAsync(user);
            if (removeUserResult.Succeeded)
            {
                return RedirectToAction("ManageUsers", new { Message = ManageMessageId.AccountDeleteSuccess });
            }
            AddErrors(removeUserResult);
        }
        else
        {
            AddErrors(removeRolesResult);
        }
    }
    catch (Exception ex)
    {
        ErrLog.LogError(ex, "ManageController.Users_Delete");
    }
}

return Json(new[] { model }.AsQueryable().ToDataSourceResult(request, ModelState));
}

行动#5:

[HttpPost]
[ValidateAntiForgeryToken]
[Authorize(Roles = "DM_Admin")] 
public async Task<IdentityResult> AssignRolesToUser(string id, string[] rolesToAssign)
{
    if (rolesToAssign != null)
    {
        try
        {
            // find the user to assign roles to
            var user = await UserManager.FindByIdAsync(id);
            if (user != null)
            {
                // check if the user currently has any roles
                var currentRoles = await UserManager.GetRolesAsync(user.Id);
                var rolesNotExist = rolesToAssign.Except(RoleManager.Roles.Select(x => x.Name)).ToArray();
            if (!(rolesNotExist.Count() > 0))
            {
                // remove current roles from user, if any, in User Db
                var removeRolesResult = await UserManager.RemoveFromRolesAsync(user.Id, currentRoles.ToArray());
                if (!removeRolesResult.Succeeded)
                {
                    AddErrors(removeRolesResult);
                    return removeRolesResult;
                }

                // assign new roles to user in User Db
                var addRolesResult = await UserManager.AddToRolesAsync(user.Id, rolesToAssign);
                if (!addRolesResult.Succeeded)
                {
                    AddErrors(addRolesResult);
                }
                return addRolesResult;
            }
            else
            {
                ModelState.AddModelError("", string.Format("Roles '{0}' do not exist in the system", string.Join(",", rolesNotExist)));
            }
        }
        else
        {
            ModelState.AddModelError("", string.Format("Unable to find user"));
        }
    }
    catch (Exception ex)
    {
        ErrLog.LogError(ex, "ManageController.AssignRolesToUser");
    }
}
else
{
    ModelState.AddModelError("", string.Format("No roles specified"));
}
return null;
}

我保持Identity框架与Microsoft在创建默认Mvc应用程序时的设置方式相同,因此我不确定如何重构代码。

我知道这是一篇较长的帖子,所以非常感谢你花时间阅读它,我希望很快能收到你的回复。

**注意:我添加了5种动作方法,因为我认为可能需要显示它们以便整体控制器变薄,同时更好地理解正在发生的事情。但请不要觉得有必要为列出的所有操作提供示例。 **

非常感谢你们! Snawwz

2 个答案:

答案 0 :(得分:6)

你承担了新开发者的主要罪过:陷入模式地狱,而不是担心你的申请。

设计模式只是解决特定类型问题的推荐方法。足够多的开发人员已经做了相同的事情,以便在一组公认的最佳实践上进行凝结。然而,在此经常丢失的是存在设计模式来解决特定类型的问题。如果您的应用没有特定问题,则无需实施该特定模式。通过将存储库模式与ORM(如Entity Framework)一起使用,您经常会看到这种情况。存储库模式的存在是为了使用数据库直接将所有原始SQL代码混乱,需要所有进入整洁的地方。如果周围没有原始SQL代码,则不需要存储库模式

此外,应用程序的需求超过任何“最佳实践”。例如,很少有人会认为你不应该在你的应用程序中实现控制和依赖注入的反转。 99.999%的应用程序可显着改善您的应用程序。但是,Stack Overflow Core团队根本不使用它。为什么?因为,它增加了应用程序的重要性。 Core团队的唯一责任是使Stack Overflow尽可能高效,并且在此特定应用程序中依赖注入不起作用。

所有这一切都是为了构建你的应用程序。不要担心一切都是正确的或每一个“最佳实践”被遵循。建立它。然后,您可以返回并重构,当您重构时,您将找到以更好的方式做事的机会。此时,您可以参考各种设计模式以获得指导。在开始时试图担心所有这些东西是一个陷阱,往往会让你在你的轨道上冻结,无法前进。

还有一点需要注意:你需要让自己有时间和自由去体验。我的意思是,一旦你对这些东西变得有经验,你就会做出如何在门外完成它们的事情,因为它开始成为第二天性。但是,首先必须达到这一点。当你第一次出发时,你的代码并不完美。我不认识一个开发人员,如果他们遇到他们是新手时所写的代码,他们就不会不寒而栗。当您构建更多应用程序,制定新挑战,解决新问题等时,您将建立一个知识库,使得更容易做正确的事情。给自己一些喘息的空间。

答案 1 :(得分:0)

基本上MVC是一种表示模式。它不是设计成所有设计问题的灵丹妙药。它应该只格式化要查看的用户的数据。不多也不少。

它应该只获取数据,将其传递给渲染引擎(Razor)并创建路径(就是它)。其他一切都应该在其他层/模式中。这是“Skinny控制器”的关键,它们很瘦,因为它们包含零逻辑(表示模式记住)。这些“其他”模式应该完全取决于您的情况。这个问题没有一个解决方案。

要提供示例中的瘦控制器示例,它看起来像这样

private MyLogicClass _logic;

[HttpPost]
[ValidateAntiForgeryToken]
[Authorize(Roles = "DM_Admin")] 
public async Task<ActionResult> Users_Delete([DataSourceRequest] DataSourceRequest request, ManageUsersViewModel model)
{
    var model = _logic.DoLogic();
    return View(model);
}

示例中的所有“内容”应该位于上面示例中的一个或多个“逻辑类”MyLogicClass内。你的控制器应该非常愚蠢,只是传递数据。