2017-10-03 4 views
-1

J'ai une question concernant l'implémentation de la création de "thin controllers" dans une application Asp.net Mvc 5. J'ai fait des recherches sur ce sujet au cours des derniers jours, et je crois que j'en suis au point où j'ai besoin d'un exemple concret pour relier les points à ma compréhension. Donc, je voudrais utiliser les tests unitaires dans mon application. J'ai regardé la création d'usines de modèles de vue, et de constructeurs, modèle maigre de contrôleur-graisse, mais je ne suis pas sûr de savoir comment mettre en application l'un de ces modèles de conception que j'ai lus dans ce scénario particulier.Thin Out Controller dans Asp.Net-Mvc-5 Exemple spécifique

Ci-dessous vous trouverez 5 actions différentes qui se trouvent dans mon contrôleur de gestion. Je crains qu'ils sentent et exigent un peu de nettoyage pour simplifier les tests/tests unitaires. Je comprends qu'il n'y a généralement pas de «bonne réponse» à ce type de questions, alors j'apprécierais grandement toutes les réponses qui aident à simplifier le test de mon application.

Voici mes actions:

Action # 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)); 
} 

Action # 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)); 
} 

Action # 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)); 
} 

Action # 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)); 
} 

Action # 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; 
} 

je gardais le cadre d'identité même que la façon dont Microsoft il met en place lors de la création d'une application par défaut Mvc, et à cause de cela, je ne suis pas vraiment sûr de savoir comment aller à propos de refactoriser le code. Je comprends que c'est un poste plus long, alors merci beaucoup d'avoir pris le temps de le lire et j'espère avoir de vos nouvelles bientôt.

** Note: J'ai ajouté 5 méthodes d'action parce que je crois qu'ils peuvent être nécessaires pour montrer afin d'éclaircir le contrôleur dans son ensemble, avec une meilleure compréhension de ce qui se passe. Mais n'éprouvez pas le besoin de fournir des exemples pour toutes les actions énumérées. **

Merci beaucoup les gars! Snawwz

+3

Je vote pour clore cette question hors-sujet parce qu'elle est mieux adaptée à la révision de code car c'est un code qui fonctionne et qui n'a pas de problème spécifique à résoudre. https://codereview.stackexchange.com/ –

+0

L'utilisation d'une approche «handler» avec Mediatr peut bien fonctionner: https://jonhilton.net/2016/06/06/simplify-your-controllers-with-the-command-pattern -and-mediatr/ –

Répondre

0

Fondamentalement MVC est un modèle de présentation. Il a été/n'est pas conçu pour être une panacée à tous les problèmes de conception. Il ne devrait formater que les données pour le (s) utilisateur (s) à visualiser. Ni plus ni moins.

Il devrait seulement prendre des données, le passer à un moteur de rendu (Razor) et créer des routes (c'est tout). Tout le reste devrait être dans d'autres couches/modèles. C'est le point crucial des "skinny controllers", ils sont maigres car ils contiennent une logique nulle (rappelez-vous le motif de présentation). Qu'est-ce que ces "autres" modèles devraient être totalement à votre situation. Il n'y a pas de solution unique à ce problème.

Pour donner un exemple d'un contrôleur maigre de vos exemples, il avait l'air quelque chose comme ceci:

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

tous les « trucs » dans vos exemples devraient résider à l'intérieur d'un ou plusieurs « classes logiques » , MyLogicClass dans l'exemple ci-dessus. Vos contrôleurs devraient être très stupides et simplement transmettre des données.

+0

Merci pour l'entrée Liam! – Snawwz

2

Vous êtes en train de commettre le péché cardinal du nouveau développeur: rester bloqué dans l'enfer du modèle, au lieu de simplement vous inquiéter de votre application.

Les modèles de conception sont simplement recommandés pour résoudre certains types de problèmes. Assez de développeurs ont fait assez souvent la même chose pour se figer sur un ensemble de bonnes pratiques. Cependant, ce qui souvent se perd dans ceci est que les modèles de conception existent pour résoudre des types spécifiques de problèmes. Si votre application n'a pas ce problème particulier, il n'est pas nécessaire d'implémenter ce modèle particulier. Vous voyez cela trop souvent avec l'utilisation du modèle de référentiel avec un ORM comme Entity Framework. Le modèle de référentiel existe pour mélanger tout le code SQL brut en utilisant une base de données nécessite directement dans un endroit bien rangé. Si vous n'avez pas de code SQL brut autour de , vous n'avez pas besoin du modèle de référentiel.

De plus, les besoins de l'application l'emportent sur les «meilleures pratiques». Par exemple, très peu avancent que ne devrait pas implémenter l'inversion de contrôle et l'injection de dépendance dans votre application. Dans 99,999% des applications, cela améliore considérablement votre application. Cependant, l'équipe Stack Overflow Core ne l'utilise pas du tout. Pourquoi? Parce que, il ajoute du poids à l'application. La seule responsabilité de l'équipe de base est de rendre Stack Overflow aussi performant que possible, et dans cette injection de dépendance d'application particulière ne fonctionne pas.

Le but de tout cela est de construire votre application. Ne vous inquiétez pas si tout est correct ou si toutes les «meilleures pratiques» sont suivies. Juste le construire. Ensuite, vous pouvez revenir en arrière et refactoriser, et en tant que refactoriser, vous trouverez des opportunités de faire les choses d'une meilleure façon. À ce stade, vous pouvez vous référer aux différents modèles de conception pour vous guider. Essayer de s'inquiéter de tout cela dès le début est un piège qui vous laissera trop souvent dans l'impossibilité d'avancer.

Une autre remarque: vous devez vous laisser du temps et de la liberté pour acquérir de l'expérience. Ce que je veux dire par là, c'est que, une fois que vous devenez expérimenté à faire ce genre de choses, vous ferez des choses comme elles devraient être faites dès le début, parce que cela commence à devenir une seconde nature. Cependant, il faut d'abord arriver à ce point. Il n'y a pas de honte à ce que votre code ne soit pas parfait quand vous débutez. Je ne connais pas un seul développeur qui ne frémirait pas s'ils étaient confrontés au code qu'ils avaient écrit quand ils étaient nouveaux. Au fur et à mesure que vous construisez plus d'applications, que vous contrastez de nouveaux défis, résolvez de nouveaux problèmes, etc., vous construisez une base de connaissances qui vous aidera à faire le bon choix. Donnez-vous un peu de répit.

+1

Je ne peux pas vous remercier assez pour votre temps Chris. Comme je commence tout juste ma carrière, ce type de réponse est à la fois perspicace et rassurant. Je t'apprécie! – Snawwz

+0

Oui, * [l'optimisation prématurée est la racine de tout mal (ou du moins de la majeure partie) dans la programmation.] (Https://en.wikiquote.org/wiki/Donald_Knuth) * – Liam