Bonjour a tous , J’ai récemment été engagé dans mon premier emploie dans le domaine en tant que full-stack dev asp ( ).
Apres avoir travaillé sur 1 ou 2 projet , j’ai décidé d’etre pro-actif dans mon entreprise en fournissant un template de model/controlleur qui abstrait ce que nous faisons 95% du temps , ce qui nous permettrais de sauver enormement de temps de developpement.
Ce que j’ai cherché a faire c’est d’isoler les operations entre vue | model n’ayant qu’a s’occuper de : vue -> quoi faire lors d’un success/fail ? model -> quoi faire lors des operatioons : save , delete , get , getlist.
Je crois avoir reussi a isoler une tres grosse partie de notre travail.
Ce que j’aimerais c’est votre avis .. je ne fait du c# que depuis 3 mois : A premiere vu j’ai louper des truc ? Voyez vous ce qui pourrait faire "planter" le fonctionnement ? Suggestions ou autre procédés a me proposer ? Je compte le présenter dans un meeting R&D la semaine prochaine.
Mise en contexte :
je travail sur des projet en petite equipe ( 4 personnes ) et nous sommes tous des full stack. Bien que nos modeles soit un minimum standardisé .. certaines personnes travaillent sur un framework interne afin de mettre un standard sur nos procédés. C’est ce que ce template tente d’apporter : un standard dans le procédé de travail. Le plus gros probleme c’est les différence entre les dev : gestion des erreurs dans le controlleur ou dans le modele ( meme certain le font dans la vue .. ). Operations dans le controlleur ou modele.
Avec ce template je contrains donc ce fonctionnement : les operations du controlleurs reste minimales et ne font qu’apeller la fonction respective du model. Tous les operations sont donc fait dans une fonction static du model.
Q : Pourquoi une struct epresult ?
Est-ce que ta question porte sur le struct vs class ou sur le pourquoi retourner une structure ? En fait c’est qu’en cas d’exception ( pas une Exception mais une différence dans le fonctionnement de notre logiciel comparé a ce que nous faisons habituellement ) , il y’a des cas ou nous voudrions faire des operations différente sur le model. Ce EpResult permet donc dans le cas d’un save de recuperer autant la liste d’erreur que le model. Nous n’avons donc qu’a overload ’Save’ dans le controlleur concret et recuperer ces resultats. ( meme si ce point est discutable du fait que ces exceptions peuvent etre geré dans le model .. j’ai quand meme voulu laisser cette posibilité d’overide les fonctions du controlleur sans cassé le workflow que j’etabli avec le model )
Q : Pourquoi expose tu a certain endroit des List<string>** ?
Afin de contreindre le fonctionnement entre model et vue. Les fonction qui peuvent produire une erreur retournent donc une liste de string qui est la liste des erreurs produits ( a part ceux du modelstate/annotations ) dans les operations du model. Par exemple un save : bien que le model puisse etre valide , identity peut retourner une erreur dans le cas d’un email deja utilisé par exemple. Ces retour contreignent donc nos futur dev a utiliser le model correctement : Si les conditions passent tu save et le controlleur s’occupe de retourner succes. Sinon tu rempli une liste d’erreur que le controlleur s’occupe de mettre dans le modelState afin que la vue les affiche.
Q : Lien entre TModel et TViewModel :
presque aucun a part le fait que certaines personne aime bien MVVM et travailler avec des ViewModel plutot que le model directement. Je leur est donc laissé la possibilité d’envoyé un ViewModel a leur model afin de procédé au ’Save’ désiré.
Q : Index et Detail
En fait , c’est question de convention interne : pour une liste de model nous utilisons toujours "Index" et pour la page de détail "Detail" , c’est donc la convention que j’ai représenté ici. Je veux que notre liste s’affiche lorsque nous allons dans /Controlleur/Index.
Flow du template :
Template :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 | using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Web; using System.Web.Mvc; namespace EpController { /// <summary> /// Enum for the different views. Each views MUST be defined /// </summary> public enum ViewName { List = 0, Detail = 1, SuccessSave = 2, FailedSave = 3 } /// <summary> /// Result from Save functions to have a list of errors and the model /// </summary> /// <typeparam name="TModel"></typeparam> public struct EpResult<TModel> { public List<string> Errors { get; set; } public TModel Model { get; set; } } /// <summary> /// Base class for any model used by any EpController /// </summary> /// <typeparam name="TModel">The model class</typeparam> /// <typeparam name="TViewModel">The modelView class</typeparam> /// <typeparam name="TListModel">The model list class</typeparam> /// <typeparam name="TDataBaseConext">The database context</typeparam> public abstract class EpModel<TModel,TViewModel, TListModel, TDataBaseConext> { public abstract TModel Get(TDataBaseConext db, string id); public abstract List<TListModel> GetList(TDataBaseConext db); public abstract EpResult<TModel> Save(TDataBaseConext db, TModel model); public abstract EpResult<TViewModel> Save(TDataBaseConext db, TViewModel viewModel); public abstract List<string> Delete(TDataBaseConext db, List<string> ids); } /// <summary> /// Ep controller managing an EpModel /// </summary> /// <typeparam name="TModel">The model class</typeparam> /// <typeparam name="TViewModel">The model view class</typeparam> /// <typeparam name="TListModel">The model list class</typeparam> /// <typeparam name="TDataBaseContext">The database context</typeparam> public abstract class EpController<TModel, TViewModel, TListModel, TDataBaseContext> : Controller where TModel : EpModel<TModel,TViewModel,TListModel, TDataBaseContext>, new() where TViewModel : new() where TListModel : new() where TDataBaseContext : IDisposable, new() { /// <summary> /// The controller views [ Detail, List, SucessSave, FailedSave] /// </summary> private Dictionary<ViewName, string> Views; /// <summary> /// Constructor /// </summary> /// <param name="views">The controller views [ Detail, List, SucessSave, FailedSave]</param> public EpController(Dictionary<ViewName,string> views) { var AllViewDefined = (views[ViewName.List] != null) && (views[ViewName.SuccessSave] != null) && (views[ViewName.FailedSave] != null); Debug.Assert(AllViewDefined, "Tous les vues ( sauf detail ) doivent etre définis [ List, SucessSave, FailedSave ]"); Views = views; } /// <summary> /// Returns the Model List /// </summary> /// <returns>The list view</returns> public ActionResult Index() { using (var db = new TDataBaseContext()) { var type = typeof(TModel); var method = type.GetMethod("GetList"); List<TListModel> listModel = (List < TListModel > )(method.Invoke(null, new object[] { db })); InitializeViewBag(); return View(Views[ViewName.List], listModel); } } /// <summary> /// Save the model from the detail page /// </summary> /// <returns>The SuccessSave or FailedSave view</returns> public ActionResult Save(TModel model, string successView, string errorView) { if (ModelState.IsValid) { ModelState.Clear(); using(var db = new TDataBaseContext()) { var result = (EpResult<TModel>)( typeof(TModel).GetMethod("Save").Invoke(null, new object[] { db, model }) ); InitializeViewBag(); if (result.Errors.Count() > 0) { foreach(var error in result.Errors) { ModelState.AddModelError("", error); } return this.Json(new { success = false, view = RenderHelper.RenderHelper.PartialViewToString(this, Views[ViewName.FailedSave], model) }); } return this.Json(new { success = true , view = RenderHelper.RenderHelper.PartialViewToString(this, Views[ViewName.SuccessSave], model) }); } } InitializeViewBag(); return this.Json(new { success = false, view = RenderHelper.RenderHelper.PartialViewToString(this, Views[ViewName.FailedSave], model) }); } /// <summary> /// Save the model /// </summary> /// <returns>The SuccessSave or FailedSave view</returns> public ActionResult Save(TViewModel viewModel , string successView , string errorView) { if (ModelState.IsValid) { ModelState.Clear(); using (var db = new TDataBaseContext()) { var result = (EpResult<TViewModel>)( typeof(TModel).GetMethod("Save").Invoke(null, new object[] { db, viewModel }) ); InitializeViewBag(); if (result.Errors.Count() > 0) { foreach (var error in result.Errors) { ModelState.AddModelError("", error); } // use result.Model to send it to the view return this.Json(new { success = false, view = RenderHelper.RenderHelper.PartialViewToString(this, Views[ViewName.FailedSave], viewModel) }); } return this.Json(new { success = true, view = RenderHelper.RenderHelper.PartialViewToString(this, Views[ViewName.SuccessSave], viewModel) }); } } InitializeViewBag(); return this.Json(new { success = false, view = RenderHelper.RenderHelper.PartialViewToString(this, Views[ViewName.FailedSave], viewModel) }); } /// <summary> /// Get the detail page /// </summary> /// <param name="id"></param> /// <returns>The detail view</returns> public ActionResult Detail(string id) { using (var db = new TDataBaseContext()) { InitializeViewBag(); if (Views[ViewName.Detail] != null) { TModel model = (TModel)typeof(TModel).GetMethod("Get").Invoke(null, new object[] { db, id }); return View(Views[ViewName.Detail], model); } else { return Index(); } } } /// <summary> /// Delete a list of models /// </summary> /// <param name="ids"></param> public void Delete(List<string> ids) { using (var db = new TDataBaseContext()) { var result = (List<string>)typeof(TModel).GetMethod("Delete").Invoke(null, new object[] { db, ids }); } } public abstract void InitializeViewBag(); } } |
Exemple model :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 | public class Client : EpController.EpModel<Client,ClientViewModel,ClientListModel,ClientContext> { public string Id { get; set; } public string Name { get; set; } public string Email { get; set; } public int RoleId { get; set; } public override List<ClientListModel> GetList(ClientContext db) { return db.GetList(); } public override EpResult<Client> Save(ClientContext db, Client model) { //do operations db.Save(model); return new EpResult<Client>() { Errors = null, Model = model }; // return the errors and model } public override EpResult<ClientViewModel> Save(ClientContext db, ClientViewModel viewModel) { // do operations db.Save(viewModel); return new EpResult<ClientViewModel>() { Errors = null, Model = viewModel }; // return the errors and model } public override List<string> Delete(ClientContext db, List<string> ids) { // do operations db.Delete(ids); return new List<string>(); // return the errors } public override Client Get(ClientContext db, string id) { throw new NotImplementedException(); } } public class ClientListModel { public string Id { get; set; } public string Name { get; set; } public string Email { get; set; } public string Role { get; set; } } public class ClientViewModel { public string Id { get; set; } public string Name { get; set; } public string Email { get; set; } public string Role { get; set; } } |
Exemple de controller :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | namespace EpControllerTemplate.Controllers { public class Views { public static Dictionary<EpController.ViewName, string> ClientViews = new Dictionary<EpController.ViewName, string>(); static Views() { ClientViews.Add(EpController.ViewName.Detail, "_ClientDetail"); ClientViews.Add(EpController.ViewName.List, "Index" ); ClientViews.Add(EpController.ViewName.SuccessSave, "Index"); ClientViews.Add(EpController.ViewName.FailedSave, "_ClientDetail"); } } public class ClientController : EpController.EpController<Client, ClientViewModel, ClientListModel, ClientContext> { public ClientController() : base(Views.ClientViews) { } public override void InitializeViewBag() { ViewBag.SomeData = "Some data"; } } } |
Merci d’avance pour vos commentaires