ASP.NET MVC - travailler avec MySQL via ADO.NET

Technologie héritée
Avertissement: ASP.NET MVC est obsolète et ADO.NET aussi. Il est recommandé d'utiliser ASP.NET Core avec l'ORM moderne. Mais si vous êtes intéressé, alors lisez.

Déjà, probablement, trois fois j'arrive à ASP.NET MVC. Après dix ans avec ASP.NET WebForms, il est un peu difficile de passer à la technologie MVC, car il y a tellement de différences qu'il est plutôt plus facile de répertorier ce que ces technologies ont en commun - à l'exception des bibliothèques .NET Framework. Je n'écrirai pas ici - MVC est meilleur ou pire que WebForms, ils sont tout simplement bons et vous pouvez créer une bonne application sur les deux technologies. Je laisse également mes réflexions sur le besoin de TDD avec moi, bien que je les ai.

Et maintenant, je vais parler de la tâche la plus standard - le travail habituel avec les données: afficher sous forme de tableau une liste d'enregistrements, ajouter, modifier et supprimer des données (opérations CRUD). Cependant, dans presque tous les livres et de nombreuses solutions Internet pour ASP.NET MVC, pour une raison quelconque, l'option est exclusivement envisagée via ORM (Object Relation Mapping): Entity Framework (EF) ou LINQ for SQL. Les technologies sont excellentes, sans doute, enfin, le programmeur peut ne pas comprendre - mais comment fonctionne généralement ce SGBD très relationnel (qu'il utilise le plus probablement), et même SQL, en théorie, n'est plus nécessaire: la mise en forme d'EF et les connecteurs pour le SGBD se comprendront. "Ici, c'est le bonheur - il n'y a plus de beauté pour lui." Mais pour les programmeurs qui n'ont pas peur de travailler directement avec la base de données via le mécanisme ADO.NET, il est souvent difficile de savoir - et par où commencer avec ASP.NET MVC en général et s'il est nécessaire.

De plus, pour moi, par exemple, au début, cela a provoqué une rupture sauvage en l'absence d'un composant pratique pour afficher les données dans une table de grille. Il est entendu que le développeur lui-même doit implémenter tout cela ou prendre quelque chose de convenable du gestionnaire de packages. Si vous, comme moi, étiez plus que satisfait du composant GridView dans ASP.NET WebForms, alors pour MVC, il est difficile de trouver quelque chose de plus ou moins comparable, à l'exception de Grid.mvc. Mais ma confiance dans ces composants ne suffit pas à les utiliser pour un projet suffisamment important. S'ils sont utilisés, le programmeur commence à dépendre d'un autre développeur qui, d'une part, ne sait pas écrire ce composant (est-ce qualitatif?), Et d'autre part, on ne sait pas quand et comment il sera finalisé. Il semble parfois même possible d'étendre le composant et de le voir plus loin, mais, en cas de développement par le développeur, nous sommes obligés soit de rechiffrer à nouveau notre code soit de geler la mise à jour du composant sur une certaine version. Et si le développeur corrige une vulnérabilité, vous devez toujours mettre à niveau vers la nouvelle version. Même la transition vers de nouvelles versions du connecteur MySQL pose certains problèmes, bien qu'il soit toujours développé par une grande entreprise, mais qu'en est-il des nombreux «vélos» dans le gestionnaire de paquets Nuget?

Essayons donc d'apprendre à écrire sous ASP.NET MVC, tout en travaillant avec des données traitées par le SGBD MySQL. Tout le code peut être pris ici à cette adresse , ci-dessous ce code sera partiellement présenté avec de petits liens et explications. Et ici, vous pouvez voir l'application.

Créer une base de données dans MySQL


CREATE SCHEMA `example`; USE `example`; CREATE TABLE `users` ( `UserID` int(4) unsigned NOT NULL AUTO_INCREMENT, `SupporterTier` enum('None','Silver','Gold','Platinum') NOT NULL DEFAULT 'None', `Loginname` varchar(255) NOT NULL, `LanguageID` int(4) unsigned NOT NULL DEFAULT '2', `Email` varchar(255) DEFAULT NULL, `LastLoginDate` datetime DEFAULT NULL, PRIMARY KEY (`UserID`), KEY `i_Loginname` (`Loginname`), KEY `i_Language_idx` (`LanguageID`), CONSTRAINT `i_Language` FOREIGN KEY (`LanguageID`) REFERENCES `languages` (`LanguageID`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8; CREATE TABLE `languages` ( `LanguageID` int(4) unsigned NOT NULL AUTO_INCREMENT, `LanguageName` varchar(50) NOT NULL DEFAULT '', `Culture` varchar(10) DEFAULT NULL, PRIMARY KEY (`LanguageID`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8; 

La base de données (DB) sera simple et représentée par seulement deux tables, avec une relation un-à-plusieurs entre elles dans le champ LanguageID . De cette façon, je vais compliquer la situation pour la nécessité d'utiliser la liste déroulante pour choisir l'une des langues pour l'utilisateur. De plus, pour complication, pour l'utilisateur, nous introduirons également le champ SupporterTier , qui déterminera le niveau de support utilisateur via l'énumération. Et afin d'utiliser presque tous les types de données dans notre projet, nous ajoutons le champ LastLoginDate du type «date / heure», qui sera rempli par l'application elle-même lorsque l'utilisateur se connectera (non reflété dans ce projet).

Créer un projet




Choisissez «MVC». Il est possible d'utiliser «Empty», mais nous avons une formation, pas une vraie application, donc cela nous aidera immédiatement, sans gestes inutiles, à intégrer Bootstrap et JQuery dans notre application.



Nous obtenons les dossiers Content , Fonts , Scripts déjà remplis, ainsi que les fichiers BundleConfig.cs et FilterConfig.cs dans le répertoire App_Start avec enregistrement des bundles et des filtres ASP.NET MVC. Dans un projet vide, il n'y a que l'enregistrement des routes dans le fichier RouteConfig.cs . Dans Global.asax.cs, les appels de méthode décrits dans ces fichiers seront également ajoutés:

 FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters); RouteConfig.RegisterRoutes(RouteTable.Routes); BundleConfig.RegisterBundles(BundleTable.Bundles); 

Nous mettons en place l'infrastructure et l'ensemble du bundle


Pour travailler avec le SGBD MySQL, ajoutez la bibliothèque MySql.Data : soit manuellement, si le connecteur mysql-connector-net-8.0.18 est déjà installé sur l'ordinateur, soit à partir du gestionnaire de paquets Nuget:



Ajoutez la configuration de la chaîne de connexion au SGBD MySQL au fichier Web.config :

 <connectionStrings> <add name="example" providerName="MySql.Data.MySqlClient" connectionString="server=localhost;Port=3306;user id=develop;Password=develop;persistsecurityinfo=True;database=example;CharSet=utf8;SslMode=none" /> </connectionStrings> 

Ajoutez une ligne dans la <appSettings> avec un lien vers la chaîne de connexion ajoutée: <add key="ConnectionString" value="example" />
Nous ajoutons un nouveau répertoire de domaine à l'application, nous y créons une nouvelle classe de Base statique (dans le fichier Base.cs ), dans laquelle nous accédons à ces paramètres:

 public static class Base { private static string ConnectionString { get { return System.Configuration.ConfigurationManager.AppSettings["ConnectionString"]; } } public static string strConnect { get { return System.Configuration.ConfigurationManager.ConnectionStrings[ConnectionString].ConnectionString; } } } 

J'aime avoir une certaine classe de base dans l'application avec des liens vers les paramètres de l'application et certaines fonctions standard qui pourraient être appelées à partir de l'application entière.
Le nom de la chaîne de connexion est défini dans les paramètres de l'application, de sorte qu'à l'avenir, il sera plus facile de travailler avec la chaîne de connexion au SGBD: basculez rapidement entre les différentes bases de données et modifiez les paramètres de connexion. De plus, l'utilisation du nom de la chaîne de connexion dans le paramètre d'application est pratique pour publier l'application dans Microsoft Azure - vous pouvez y définir le paramètre du service d'application utilisé pour la publication et déterminer la chaîne de connexion souhaitée, qui est prédéfinie dans <connectionStrings> . Ensuite, lors de la publication, vous ne pouvez pas utiliser la transformation du fichier web.config .

J'aime également utiliser les valeurs du fichier de ressources global dans le texte, afin de ne pas les écraser à plusieurs endroits, si vous en avez soudainement besoin. Par exemple:



Dans le fichier de mise en page _Layout.cshtml (il est généralement situé dans le répertoire Views \ Shared et utilisé ultérieurement pour toutes les pages de ce projet), vous pouvez désormais utiliser ces variables (voir, par exemple, Example_Users.Properties.Resources.Title ):

 <!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>@ViewBag.Title – @Example_Users.Properties.Resources.Title</title> @Styles.Render("~/Content/css") @Scripts.Render("~/bundles/modernizr") </head> <body> <div class="container body-content"> <h1 class="page-header"><a href="/">@Example_Users.Properties.Resources.Title</a></h1> @RenderBody() <hr /> <footer> <p> @DateTime.Now.Year – @Example_Users.Properties.Resources.Author</p> </footer> </div> @Scripts.Render("~/bundles/jquery") @Scripts.Render("~/bundles/bootstrap") @RenderSection("scripts", required: false) </body> </html> 

Dans ce fichier, nous voyons également la pièce jointe de la feuille de style en cascade du Bootstrap connecté et de la bibliothèque de scripts JQuery. Le contenu de toutes les vues sera généré à l'emplacement de l'appel de fonction RenderBody() .

M signifie modèle


Ajoutez le fichier UserClass.cs au répertoire du domaine :

 [DisplayName("User")] public class UserClass { [Key] [HiddenInput(DisplayValue=false)] public int UserID { get; set; } [Required(ErrorMessage="Please enter a login name")] [Display(Name = "Login")] public string Loginname { get; set; } public virtual LanguageClass Language { get; set; } [EmailAddress(ErrorMessage = "Please enter a valid email")] public string Email { get; set; } [UIHint("Enum")] [EnumDataType(typeof(Supporter))] [Required(ErrorMessage = "Please select supporter tier")] [Display(Name = "Supporter")] public Supporter SupporterTier { get; set; } [HiddenInput(DisplayValue = true)] [ScaffoldColumn(false)] [Display(Name = "Last login")] public DateTime? LastLoginDate { get; set; } [HiddenInput(DisplayValue = false)] public bool IsLastLogin { get { return LastLoginDate != null && LastLoginDate > DateTime.MinValue; } } public UserClass() {} public UserClass(int UserID, string Loginname, LanguageClass Language, string Email, DateTime? LastLoginDate, Supporter SupporterTier) { this.UserID = UserID; this.Loginname = Loginname; this.Language = Language; this.Email = Email; this.LastLoginDate = LastLoginDate; this.SupporterTier = SupporterTier; } } public enum Supporter { [Display(Name="")] None = 1, Silver = 2, Gold = 3, Platinum = 4 } 

Et aussi le fichier LanguageClass.cs dans le même répertoire:
 [DisplayName("Language")] public class LanguageClass { [Key] [HiddenInput(DisplayValue = false)] [Required(ErrorMessage = "Please select a language")] [Range(1, int.MaxValue, ErrorMessage = "Please select a language")] public int LanguageID { get; set; } [Display(Name = "Language")] public string LanguageName { get; set; } public LanguageClass() {} public LanguageClass(int LanguageID, string LanguageName) { this.LanguageID = LanguageID; this.LanguageName = LanguageName; } } 

Ici, vous pouvez voir que les propriétés des classes répètent la structure de la table des Users et des Languages dans le SGBD. Un enum Supporter créé pour le type d'énumération afin qu'il puisse être utilisé pour la propriété de la classe SupporterTier d'un champ similaire dans la table de base de données. Pour les champs UserID , LanguageID , vous pouvez voir qu'ils sont spécifiés comme clé primaire, tout comme dans la base de données. L'attribut [Key] est utilisé pour cela.

Tous les autres attributs sont plus probablement liés aux vues utilisant cette classe. Et si nous allons utiliser des assistants pour former des balises HTML pour ces propriétés (que je recommanderais certainement), nous devrons définir ces attributs très soigneusement pour obtenir ce dont nous avons besoin. En particulier, voici ce qui était nécessaire pour ce projet:

  • [DisplayName] - Utilisé comme nom d'affichage pour la classe. Parfois, cela peut être utile, dans ce projet, j'ai spécialement ajouté l'utilisation de l'assistant Html.DisplayNameForModel pour la démonstration.
  • [Display] avec la propriété Name - utilisé comme nom de la propriété de classe affichée à l'écran. Il existe également une propriété Order utile qui vous permet de commander la séquence d'affichage des propriétés de classe dans un formulaire à l'aide d'aides (par défaut, le tri par ordre dans lequel les propriétés de classe sont définies, donc la propriété Order n'a pas été utilisée dans ce projet).
  • [HiddenInput] avec la propriété DisplayValue . Il est utilisé pour les propriétés qui n'ont pas du tout besoin d'être affichées dans les formulaires et les listes ( DisplayValue=false , sont dessinées comme balises d' input avec le type hidden ), ou pour les propriétés qui doivent encore être affichées, mais sous la forme d'un texte immuable ( DisplayValue=true , est dessiné comme du texte clair, sans balises)
  • [ScaffoldColumn] - indique s'il faut afficher le champ dans les Html.EditorForModel édition (par exemple, Html.EditorForModel ). S'il est false , ni la description de la propriété de classe ni sa valeur ne seront affichées dans le formulaire. [HiddenInput(DisplayValue = false)] ne peut pas être utilisé [HiddenInput(DisplayValue = false)] , car dans ce cas, les valeurs de cette propriété de classe ne seront pas du tout affichées non seulement dans les formulaires de saisie d'informations, mais également dans les affichages de tableau. Dans ce cas, cela était requis pour la propriété LastLoginDate , qui n'est pas entrée manuellement, mais remplie quelque part automatiquement, mais nous devons toujours la voir.
  • [Required] - pour vérifier si une valeur a été entrée pour une propriété de classe, avec le texte du message d'erreur dans la propriété ErrorMessage et la propriété AllowEmptyStrings vous permet d'entrer des lignes vides.
  • [EmailAddress] - essentiellement le même attribut pour vérifier l'exactitude de l'adresse postale.

Les classes du modèle de base de données sont prêtes, nous passons aux représentations (les classes de modèles de représentations seront décrites ci-dessous).

V - signifie performance de vendetta


Dans le répertoire Vues , créez le répertoire Utilisateurs pour nos vues. Toutes nos vues utilisent la disposition standard (définie dans le fichier _ViewStart.cshtml du répertoire Views ) _Layout.cshtml située dans le répertoire Views \ Shared . Créez une vue Index (fichier Index.cshtml dans le répertoire Users ):



Et écrivez le code:

 @model Example_Users.Models.UsersGrid @{ ViewBag.Title = "Users page"; } @using (@Html.BeginForm()) { <div> <h3>Users list:</h3> @if (TempData["message"] != null) {<div class="text-success">@TempData["message"]</div>} @if (TempData["error"] != null) {<div class="text-warning"><span class="alert">ERROR:</span> @TempData["error"]</div>} @Html.Partial("List") <p> <input type="submit" name="onNewUser" value="New user" /> @*@Html.ActionLink("New user", "New", "Users")*@ </p> </div> } 

Si nous voulons que cette vue démarre par défaut, modifiez la valeur Default dans le fichier RouteConfig.cs :

 routes.MapRoute(name: "Default", url: "{controller}/{action}/{id}", defaults: new { controller = "Users", action = "Index", id = UrlParameter.Optional }); 

Dans la vue elle-même, vous devez faire attention à la ligne avec Html.Partial("List") . Cela est nécessaire pour dessiner à cet endroit une vue partielle générale séparée spéciale située dans le fichier List.cshtml dans le répertoire Views \ Shared . En fait, c'est une table de grille pour afficher les données de notre table de base de données d' users :

 @model Example_Users.Models.UsersGrid @using Example_Users.Domain <div class="table-responsive"> <table class="table table-bordered table-hover"> <thead> <tr> <th>@Html.ActionLink(Html.DisplayNameFor(m => Model.Users.First().Loginname).ToString(), "Index", Request.QueryString.ToRouteValueDictionary("sortOrder", Model.SortingInfo.NewOrder(Html.NameFor(m => Model.Users.First().Loginname).ToString()))) @Html.SortIdentifier(Model.SortingInfo.currentSort, Html.NameFor(m => Model.Users.First().Loginname).ToString()) </th> <th>@Html.ActionLink(Html.DisplayNameFor(m => Model.Users.First().Language.LanguageName).ToString(), "Index", Request.QueryString.ToRouteValueDictionary("sortOrder", Model.SortingInfo.NewOrder(Html.NameFor(m => Model.Users.First().Language).ToString()))) @Html.SortIdentifier(Model.SortingInfo.currentSort, Html.NameFor(m => Model.Users.First().Language).ToString()) </th> <th>@Html.ActionLink(Html.DisplayNameFor(m => Model.Users.First().Email).ToString(), "Index", Request.QueryString.ToRouteValueDictionary("sortOrder", Model.SortingInfo.NewOrder(Html.NameFor(m => Model.Users.First().Email).ToString()))) @Html.SortIdentifier(Model.SortingInfo.currentSort, Html.NameFor(m => Model.Users.First().Email).ToString()) </th> <th>@Html.ActionLink(Html.DisplayNameFor(m => Model.Users.First().SupporterTier).ToString(), "Index", Request.QueryString.ToRouteValueDictionary("sortOrder", Model.SortingInfo.NewOrder(Html.NameFor(m => Model.Users.First().SupporterTier).ToString()))) @Html.SortIdentifier(Model.SortingInfo.currentSort, Html.NameFor(m => Model.Users.First().SupporterTier).ToString()) </th> <th>@Html.ActionLink(Html.DisplayNameFor(m => Model.Users.First().LastLoginDate).ToString(), "Index", Request.QueryString.ToRouteValueDictionary("sortOrder", Model.SortingInfo.NewOrder(Html.NameFor(m => Model.Users.First().LastLoginDate).ToString()))) @Html.SortIdentifier(Model.SortingInfo.currentSort, Html.NameFor(m => Model.Users.First().LastLoginDate).ToString()) </th> </tr> </thead> <tbody> @foreach (var item in Model.Users) { <tr> <td>@Html.ActionLink(item.Loginname, "Edit", "Users", new { UserID = item.UserID }, null)</td> <td>@Html.DisplayFor(modelitem => item.Language.LanguageName)</td> <td>@Html.DisplayFor(modelitem => item.Email)</td> <td class="@Html.DisplayFor(modelitem => item.SupporterTier)">@if (item.SupporterTier != Supporter.None) {@Html.DisplayFor(modelitem => item.SupporterTier);}</td> <td>@if (item.IsLastLogin) {@Html.DisplayFor(modelitem => item.LastLoginDate)}</td> </tr> } </tbody> </table> </div> @if (Model.PagingInfo.totalPages > 1) { <ul class="pagination"> @Html.PageLinks(Model.PagingInfo, x => Url.Action("Index", new { page = x, sortOrder = Model.SortingInfo.currentSort })) </ul> } 

On peut voir que dans l'en-tête de la table de données, les assistants Html.DisplayNameFor sont Html.DisplayNameFor pour afficher les noms des colonnes et pour cela vous devez spécifier une référence à la propriété de l'objet classe. Puisque lors de la formation du titre du tableau, nous n'avons qu'un objet Model.Users , qui est une liste d'objets de type UserClass , nous devons utiliser la méthode suivante: sélectionnez la première ligne de cette liste en tant qu'objet de la classe UserClass . Par exemple, pour le nom d'utilisateur: Model.Users.First().Loginname . Étant donné que l'attribut Loginname de la classe Users possède l'attribut [Display(Name = "Login")] , il sera affiché "Login" dans le nom de la colonne:



Quoi d'autre est intéressant dans la vue List ? Le bloc foreach , bien sûr, rend les objets de la classe UserClass qui se trouvent dans la liste des Users reçus du contrôleur. Et les PagingInfo SortingInfo et PagingInfo de notre modèle UsersGrid sont intéressants ici. Et nous avons besoin de ces objets pour organiser le tri des données (utilisé dans l'en-tête du tableau dans les balises <th> ) et organiser la pagination des informations (utilisé au bas de la page, sous le tableau). C'est pourquoi nous n'utilisons pas comme modèle une pure liste d'objets de type IEnumerable<UserClass> . Et en tant que modèle, nous utilisons la classe UsersGrid , qui se trouve dans le fichier UsersGrid.cs du répertoire Model .

 public class UsersGrid { public IEnumerable<UserClass> Users { get; set; } public PagingInfo PagingInfo { get; set; } public SortingInfo SortingInfo { get; set; } } 

Et les classes PagingInfo et SortingInfo elles-mêmes dans le fichier GridInfo.cs au même endroit.

 public class PagingInfo { //     public int totalItems { get; set; } //     public int itemsPerPage { get; set; } //   public int currentPage { get; set; } //         public int showPages { get; set; } //   public int totalPages { get { return (int)Math.Ceiling((decimal)totalItems / itemsPerPage); } } //           public int pagesSide { get { return (int)Math.Truncate((decimal)showPages / 2); } } } public class SortingInfo { //  ,     public string currentOrder { get; set; } //   public SortDirection currentDirection { get; set; } //      public string currentSort { get { return currentDirection != SortDirection.Descending ? currentOrder : currentOrder + "_desc"; } } //       (      -   ) public string NewOrder(string columnName) { return columnName == currentOrder && currentDirection != SortDirection.Descending ? columnName + "_desc" : columnName; } } 

Et pour une utilisation dans les vues, des assistants spéciaux ont été ajoutés au fichier GridHelpers.cs (répertoire HtmlHelpers ):

 public static class GridHelpers { //     1 ... 3 4 5 ... Last public static MvcHtmlString PageLinks(this HtmlHelper html, PagingInfo pagingInfo, Func<int, string> pageUrl) { StringBuilder result = new StringBuilder(); if (pagingInfo.currentPage > pagingInfo.pagesSide + 1) {//   TagBuilder li = new TagBuilder("li"); li.AddCssClass("page-item"); TagBuilder tag = new TagBuilder("a"); tag.MergeAttribute("href", pageUrl(1)); tag.InnerHtml = "1"; li.InnerHtml = tag.ToString(); result.Append(li.ToString()); } int page1 = pagingInfo.currentPage - pagingInfo.pagesSide; int page2 = pagingInfo.currentPage + pagingInfo.pagesSide; if (page1 < 1) { page2 = page2 - page1 + 1; page1 = 1; } if (page2 > pagingInfo.totalPages) page2 = pagingInfo.totalPages; if (page1 > 2) {// ... TagBuilder li = new TagBuilder("li"); li.AddCssClass("page-item"); TagBuilder tag = new TagBuilder("span"); tag.InnerHtml = "..."; tag.AddCssClass("page-item"); tag.AddCssClass("disabled"); li.InnerHtml = tag.ToString(); result.Append(li.ToString()); } for (int i = page1; i <= page2; i++) {//  TagBuilder li = new TagBuilder("li"); li.AddCssClass("page-item"); if (i == pagingInfo.currentPage) li.AddCssClass("active"); TagBuilder tag = new TagBuilder("a"); tag.AddCssClass("page-link"); tag.MergeAttribute("href", pageUrl(i)); tag.InnerHtml = i.ToString(); li.InnerHtml = tag.ToString(); result.Append(li.ToString()); } if (page2 < pagingInfo.totalPages) {// ...    TagBuilder li = new TagBuilder("li"); li.AddCssClass("page-item"); TagBuilder tag = new TagBuilder("span"); tag.InnerHtml = "..."; tag.AddCssClass("page-item"); tag.AddCssClass("disabled"); li.InnerHtml = tag.ToString(); result.Append(li.ToString()); li = new TagBuilder("li"); li.AddCssClass("page-item"); tag = new TagBuilder("a"); tag.MergeAttribute("href", pageUrl(pagingInfo.totalPages)); tag.InnerHtml = pagingInfo.totalPages.ToString(); li.InnerHtml = tag.ToString(); result.Append(li.ToString()); } return MvcHtmlString.Create(result.ToString()); } public static IHtmlString SortIdentifier(this HtmlHelper htmlHelper, string sortOrder, string field) { if (string.IsNullOrEmpty(sortOrder) || (sortOrder.Trim() != field && sortOrder.Replace("_desc", "").Trim() != field)) return null; string glyph = "glyphicon glyphicon-chevron-up"; if (sortOrder.ToLower().Contains("desc")) { glyph = "glyphicon glyphicon-chevron-down"; } var span = new TagBuilder("span"); span.Attributes["class"] = glyph; return MvcHtmlString.Create(span.ToString()); } public static RouteValueDictionary ToRouteValueDictionary(this NameValueCollection collection, string newKey, string newValue) { var routeValueDictionary = new RouteValueDictionary(); foreach (var key in collection.AllKeys) { if (key == null) continue; if (routeValueDictionary.ContainsKey(key)) routeValueDictionary.Remove(key); routeValueDictionary.Add(key, collection[key]); } if (string.IsNullOrEmpty(newValue)) { routeValueDictionary.Remove(newKey); } else { if (routeValueDictionary.ContainsKey(newKey)) routeValueDictionary.Remove(newKey); routeValueDictionary.Add(newKey, newValue); } return routeValueDictionary; } } 

Puisqu'une grille avec des données sans tri et sans informations de pagination est une chose plutôt inutile, et qu'il n'y a pas d'aide standard pour une table entière de données dans ASP.NET MVC, vous devez la créer vous-même (ou en prendre une créée par quelqu'un d'autre). Dans ce cas, j'ai espionné plusieurs implémentations dans les livres et solutions ASP.NET MVC présentés sur Internet. De plus, pour une raison quelconque, les solutions qui combinent au moins le tri et la pagination des données, soit ne le sont pas du tout, soit je ne les ai pas trouvées. J'ai dû comprendre tout cela, le combiner et le modifier à un état normal. Par exemple, la pagination dans ces implémentations ne fournit souvent pas la sortie d'une liste plus ou moins longue de pages - eh bien, que faire s'il y a plusieurs milliers de pages? Je donne un exemple d'affichage pour la solution présentée ci-dessus:



Nous avons également besoin de vues pour créer et modifier des données. Vue de création d'un objet de type UserClass :



Et le code d'affichage ressemblera à ceci:

 @model Example_Users.Models.UserModel @{ ViewBag.Title = "New " + Html.DisplayNameForModel().ToString().ToLower(); } <h2>@ViewBag.Title</h2> @using (@Html.BeginForm("New", "Users", FormMethod.Post)) { @Html.EditorFor(m => m.User); @Html.LabelFor(m => Model.User.Language)<br /> @Html.DropDownListFor(m => Model.User.Language.LanguageID, Model.SelectLanguages()) <span/>@Html.ValidationMessageFor(m => Model.User.Language.LanguageID)<br /> <br /> <p><input type="submit" name="action" value="Add" /> <input type="button" onclick="history.go(-1)" value="Cancel" /></p> @*<p>@Html.ActionLink("Back to list", "Index")</p>*@ } 

Cette vue montre, par exemple, l'utilisation de l'assistant Html.EditorFor comme moyen de générer des balises pour éditer toutes les propriétés des objets de la classe UserClass . Il s'affiche comme suit:



Cette vue utilise la classe UserModel comme modèle, pas directement la classe utilisateur. La classe UserModel elle UserModel même UserModel trouve dans le fichier UserModel.cs du répertoire Models :

 public class UserModel { public UserClass User { get; set; } private IList<LanguageClass> Languages { get; set; } public UserModel() {} public UserModel(UserClass user, IList<LanguageClass> languages) { this.User = user; this.Languages = languages; } public IEnumerable<SelectListItem> SelectLanguages() { if (Languages != null) { return new SelectList(Languages, "LanguageID", "LanguageName"); } return null; } } 

L'objet UserClass lui-même et une liste supplémentaire d'objets de type LanguageClass inclus dans cette classe. Nous avions besoin de cette liste pour créer une liste déroulante de langues, avec la langue utilisateur actuelle sélectionnée: @Html.DropDownListFor(m => Model.User.Language.LanguageID, Model.SelectLanguages(), "")

Cet assistant utilise l'appel à la fonction SelectLanguages() , qui convertit la liste des langues en un objet de type SelectList avec les paramètres d'identifiant et de nom de ligne déjà définis. Mettre la génération de cet objet dans la vue serait une erreur, car l'idée n'était pas de connaître ces liaisons avec les noms de champ. SelectList sûr, il serait possible de générer une SelectList prête à l' SelectList directement dans le contrôleur, mais j'aime l'option avec une liste privée d'objets de classe de domaine et une fonction de plus.

Pour générer une liste déroulante, nous devons utiliser des assistants distincts, car l' Html.EditorFor(m => m.User) ne générera pas de balisage d'édition pour un objet incorporé de type LanguageClass (cela pourrait être contourné en écrivant un modèle général pour les listes déroulantes, mais ici nous nous ne le ferons pas ...).

Et comme nous utilisons un objet de la classe UserModel à notre avis, qui inclut un autre objet de la classe UserClass , nous ne pourrons pas utiliser l' Html.EditorForModel() , car les aides ne sont pas récursives et ne fonctionneront pas dans cette situation, donc l'aide Html.EditorFor() est utilisée Html.EditorFor() pour l'objet User .

Je veux également faire attention à la balise commentée: Html.ActionLink("Back to list", "Index")

Habituellement, un retour de la vue pour la modification de la liste de données est implémenté de la même manière. En fait, tout d'abord, à mon avis, cela semble étrange - lorsque vous utilisez des boutons de saisie dans un formulaire buttonet que le bouton de retour est pour une raison quelconque implémenté par un lien. Deuxièmement, si nous utilisons le tri et la pagination - nous devrons être prudents en revenant à la même page dans la même vue - et en passant non seulement l'objet à la vue UserClass, mais aussi les paramètres de retour à la page. Il existe un moyen beaucoup plus simple - utilisez l'option avec un bouton comme:/>, qui renverra l'utilisateur vers la page d'historique du navigateur. Ici, bien sûr, il y a des nuances (par exemple, vous avez déjà essayé d'enregistrer l'objet sur cette page, cela n'a pas fonctionné, et maintenant - vous devez déjà double-cliquer sur le bouton Annuler), mais cette option dans son ensemble fonctionne bien.

Et la vue pour éditer un objet de type UserClass:



Et son code:

 @model Example_Users.Models.UserModel @{ ViewBag.Title = "Edit " + Html.DisplayNameForModel().ToString().ToLower(); } <h2>@ViewBag.Title @Model.User.Loginname</h2> @using (@Html.BeginForm("Edit", "Users", FormMethod.Post)) { @Html.HiddenFor(m => Model.User.UserID) <div> @Html.LabelFor(m => Model.User.Loginname) @Html.EditorFor(m => Model.User.Loginname) @Html.ValidationMessageFor(m => Model.User.Loginname) </div> <div> @Html.LabelFor(m => Model.User.Language) @Html.DropDownListFor(m => Model.User.Language.LanguageID, Model.SelectLanguages()) @Html.ValidationMessageFor(m => Model.User.Language.LanguageID) </div> <div> @Html.LabelFor(m => Model.User.Email) @Html.EditorFor(m => Model.User.Email) @Html.ValidationMessageFor(m => Model.User.Email) </div> <div> @Html.LabelFor(m => Model.User.SupporterTier) @Html.EditorFor(m => Model.User.SupporterTier) @*@Html.EnumDropDownListFor(m => Model.User.SupporterTier)*@ @*@Html.DropDownListFor(m => m.Model.User.SupporterTier, new SelectList(Enum.GetNames(typeof(Example_Users.Domain.Supporter))))*@ @Html.ValidationMessageFor(m => Model.User.SupporterTier) </div> <br /> <p><input type="submit" name="action" value="Save" /> <input type="submit" name="action" value="Remove" onclick="javascript:return confirm('Are you sure?');" /> <input type="submit" name="action" value="Cancel" /></p> } 

Et dans cette vue, l'option d'utiliser différents assistants pour générer des balises séparément pour chaque propriété souhaitée d'un objet de classe est présentée. Dans ce cas, vous pouvez déjà rendre l'affichage d'une manière légèrement différente et plus jolie:



Et dans cette vue, une autre façon est utilisée pour revenir à la liste. Au lieu d'utiliser un bouton comme: />le même bouton est utilisé comme les autres boutons d'action ( Enregistrer , Supprimer ): />et le retour s'effectue à l'intérieur de la méthode du contrôleur qui traite les actions de ces boutons (voir l'implémentation de la méthode ci-dessous).

Enum


Et puis il y avait une nuance associée à l'utilisation d'une propriété de classe de type enum . Le fait est que si nous utilisons simplement l'aide Html.EditorFor(), le champ de saisie des informations textuelles (type tag <input type=”text”/>) sera affiché sur le formulaire , mais nous avons en fait besoin d'un champ avec un choix parmi un ensemble de valeurs (c'est-à-dire une balise <select>avec un ensemble d'options <option>).

1. est résolu en utilisant le type d'rectilignement aide Html.DropDownListFor()ou Html.ListBoxFor(), par exemple, dans notre cas @Html.DropDownListFor(m => m.Model.User.SupporterTier, new SelectList(Enum.GetNames(typeof(Example_Users.Domain.Supporter)))). Il y a deux inconvénients - à chaque fois, il devra être prescrit individuellement pour l'aide Html.EditorForModel()ou Html.EditorFor()cela ne fonctionnera pas.

2. Vous pouvez créer un modèle de type personnalisé Editor. Créez le fichier Supporter.cshtml dans le dossier Views \ Shared \ EditorTemplates :

 @model Example_Users.Domain.Supporter @Html.DropDownListFor(m => m, new SelectList(Enum.GetNames(Model.GetType()), Model.ToString())) 

, Html.EditorFor(m => Model.SupporterTier) , Html.EditorFor(m => Model.SupporterTier, "Supporter") . -, [UIHint("Supporter")] . , Html.EditorForModel()Html.EditorFor(m => Model.SupporterTier, "Supporter") .

- , ( New ) Html.EditorFor() Html.EditorForModel() : « , , NULL, «Example_Users.Domain.Supporter», NULL. » – , . .

3. Html.EnumDropDownListFor() , . , , , . «»: Html.EditorForModel() Html.EditorFor() , , Html.EnumDropDownListFor() . , , – [UIHint] , [DataType] [EnumDataType] . , None , , , Supporter.

4. En conséquence, j'ai trouvé une option de solution trouvée sur Internet : créer un modèle commun pour les transferts. Créez le fichier Enum.cshtml dans le dossier Views \ Shared \ EditorTemplates :

 @using System.ComponentModel.DataAnnotations @model Enum @{ Func<object, string> GetDisplayName = o => { var result = null as string; var display = o.GetType() .GetMember(o.ToString()).First() .GetCustomAttributes(false) .OfType<DisplayAttribute>() .LastOrDefault(); if (display != null) result = display.GetName(); return result ?? o.ToString(); }; var values = Enum.GetValues(ViewData.ModelMetadata.ModelType).Cast<object>() .Select(v => new SelectListItem { Selected = v.Equals(Model), Text = GetDisplayName(v), // v.ToString(), Value = v.ToString() }); } @Html.DropDownList("", values) 

Ici, en général, tout s'est bien passé: le modèle fonctionne très bien dans la mesure du possible. Vous [UIHint("Enum")]ne pouvez même pas ajouter. De plus, ce modèle général lit l'attribut [Display(Name)]des valeurs d'énumération à l'aide d'une fonction spéciale.

C signifie contrôleur


Ajoutez le fichier UsersController.cs au répertoire Controllers .

 public class UsersController : Controller { public int pageSize = 10; public int showPages = 15; public int count = 0; //    public ViewResult Index(string sortOrder, int page = 1) { string sortName = null; System.Web.Helpers.SortDirection sortDir = System.Web.Helpers.SortDirection.Ascending; sortOrder = Base.parseSortForDB(sortOrder, out sortName, out sortDir); UsersRepository rep = new UsersRepository(); UsersGrid users = new UsersGrid { Users = rep.List(sortName, sortDir, page, pageSize, out count), PagingInfo = new PagingInfo { currentPage = page, itemsPerPage = pageSize, totalItems = count, showPages = showPages }, SortingInfo = new SortingInfo { currentOrder = sortName, currentDirection = sortDir } }; return View(users); } [ReferrerHold] [HttpPost] public ActionResult Index(string onNewUser) { if (onNewUser != null) { TempData["referrer"] = ControllerContext.RouteData.Values["referrer"]; return View("New", new UserModel(new UserClass(), Languages())); } return View(); } [ReferrerHold] public ActionResult New() { TempData["referrer"] = ControllerContext.RouteData.Values["referrer"]; return View("New", new UserModel(new UserClass(), Languages())); } [HttpPost] public ActionResult New(UserModel model) { if (ModelState.IsValid) { if (model.User == null || model.User.Language == null || model.User.Language.LanguageID == 0) RedirectToAction("Index"); UsersRepository rep = new UsersRepository(); if (rep.AddUser(model.User)) TempData["message"] = string.Format("{0} has been added", model.User.Loginname); else TempData["error"] = string.Format("{0} has not been added!", model.User.Loginname); if (TempData["referrer"] != null) return Redirect(TempData["referrer"].ToString()); return RedirectToAction("Index"); } else { model = new UserModel(model.User, Languages()); // -          model.Languages,   return View(model); } } [ReferrerHold] public ActionResult Edit(int UserID) { UsersRepository rep = new UsersRepository(); UserClass user = rep.FetchByID(UserID); if (user == null) return HttpNotFound(); TempData["referrer"] = ControllerContext.RouteData.Values["referrer"]; return View(new UserModel(user, Languages())); } [HttpPost] public ActionResult Edit(UserModel model, string action) { if (action == "Cancel") { if (TempData["referrer"] != null) return Redirect(TempData["referrer"].ToString()); return RedirectToAction("Index"); } if (ModelState.IsValid) { if (model.User == null || model.User.Language == null || model.User.Language.LanguageID == 0) RedirectToAction("Index"); UsersRepository rep = new UsersRepository(); if (action == "Save") { if (rep.ChangeUser(model.User)) TempData["message"] = string.Format("{0} has been saved", model.User.Loginname); else TempData["error"] = string.Format("{0} has not been saved!", model.User.Loginname); } if (action == "Remove") { if (rep.RemoveUser(model.User)) TempData["message"] = string.Format("{0} has been removed", model.User.Loginname); else TempData["error"] = string.Format("{0} has not been removed!", model.User.Loginname); } if (TempData["referrer"] != null) return Redirect(TempData["referrer"].ToString()); return RedirectToAction("Index"); } else { model = new UserModel(model.User, Languages()); return View(model); } } public IList<LanguageClass> Languages() { IList<LanguageClass> languages = new List<LanguageClass>(); LanguagesRepository rep = new LanguagesRepository(); languages = rep.List(); return languages; } } 

:

1) Index

: public ViewResult Index(string sortOrder, int page = 1)

sortOrder page . page - , sortOrder , SQL- . ( , Base.cs ): sortOrder = Base.parseSortForDB(sortOrder, out sortName, out sortDir);

, UsersRepository ( , ), List UsersGridà partir de la liste réelle des utilisateurs et des informations nécessaires à l'organisation de la pagination des informations et du tri. De plus, la valeur de totalItem est obtenue lors de l'appel de la méthode List, simultanément à la création de l'objet classe UsersGrid. En outre, cet objet est transféré pour l'affichage à la représentation.

Il existe également une autre méthode Index avec l'attribut [HttpPost]dont nous avions besoin pour calculer un clic sur un bouton Newdans la vue Index: public ActionResult Index(string onNewUser)

le paramètre d'entrée a onNewUserquelque chose en commun avec l'élément />dans la vue Indexet lorsque nous cliquons sur ce bouton, il transmet la valeur à la fonction que nous vérifions par rapport à null . Si les boutons de type submitétaient dans la vueIndexplusieurs, vous devrez vérifier la valeur elle-même (dans ce cas, la valeur serait "Nouvel utilisateur").

Après vérification, nous formons un objet de classe UserModel, composé d'un nouvel objet UserClasset d'une liste de langues pour les sélectionner dans la liste déroulante et les transmettre à la présentation New. La liste des langues est obtenue à partir du référentiel en LanguageRepositoryappelant la méthode Listcomme suit:

 public IList<LanguageClass> Languages() { LanguagesRepository rep = new LanguagesRepository(); return rep.List(); } 

2) Méthodes New

Une méthode Newsans paramètre est conçue pour établir un clic sur le lien Nouvel utilisateur pour une ligne commentée Html.ActionLink("New user", "New", "Users")dans la vue Index. L'action (ouverture de la vue New) est effectuée de la même manière que la précédente, seulement il n'y a pas de contrôle sur le bouton enfoncé, car un clic sur le lien est en cours d'élaboration.

La méthode Newavec le paramètre modèle du type UserModelpour traiter l'événement d'envoi de données de formulaire afin d'enregistrer le nouvel utilisateur dans la base de données: public ActionResult New(UserModel model)

récupère l'objet de classe rempli de la nouvelle vue UserModel, vérifie l'exactitude des données (selon les exigences décrites dans UserClasset LanguageClass), crée l'objet de référentiel UsersRepositoryet essaie d'ajouter une nouvelle ligne à la base de données, appeler une fonctionAddUser(model.User). Il revient ensuite à la page précédente (d'où la vue a été appelée New) et un message y est affiché indiquant le succès de l'opération ou l'échec.

3) Méthodes Edit

Une méthode Editavec un paramètre d'entrée UserID(ID utilisateur) clique sur le nom d'utilisateur dans la liste pour ouvrir la vue d'édition des données utilisateur: public ActionResult Edit(int UserID)

Dans la méthode, un référentiel est créé à nouveau UsersRepositoryet une fonction est appelée à partir de celui-ci FetchByID(UserID)pour obtenir un objet de type UserClass. En cas de succès, un modèle est créé - un objet UserModelde classe à partir de l'objet reçu et une liste de langues et transmis à la vue Editpour affichage.

Méthode avec un paramètre d'entrée du type UserModelet du modèle de ligne action:public ActionResult Edit(UserModel model, string action)

Un objet type est transmis à cette méthode UserModelet il traite les actions lorsque les boutons sont cliqués (traitement des événements pour l'envoi des données de formulaire) de la vue Edit. Afin de comprendre quel bouton est enfoncé, le paramètre d'entrée est utilisé action, ce qui est indiqué dans le nom des balises de type HTML input. Par conséquent, la méthode compare les valeurs de ce paramètre avec les valeurs de paramètre de valueces boutons. Avant de vérifier la validité du modèle, la valeur est vérifiée Cancelpour effectuer des actions pour annuler la modification de l'utilisateur. Cette option est utilisée au lieu de revenir à l'historique du navigateur, qui est utilisé dans la vue Newet utilise le stockage de l'adresse de la page à partir de laquelle la vue a été redirigée.Edit. Pour ce faire, le contrôleur a démontré la technologie d'utilisation de son attribut ActionFilter: la classe ReferrerHoldAttributedu fichier ReferrerHoldAttribute.cs (répertoire HtmlAttribute ):

 public class ReferrerHoldAttribute : ActionFilterAttribute { public override void OnActionExecuting(ActionExecutingContext filterContext) { var referrer = filterContext.RequestContext.HttpContext.Request.UrlReferrer; if (referrer != null) filterContext.RouteData.Values.Add("referrer", referrer); base.OnActionExecuting(filterContext); } } 

Il est utilisé pour stocker des informations sur la page où vous devez retourner après avoir cliqué sur le bouton " Nouvel utilisateur " ou sur un lien pour changer l'utilisateur: TempData["referrer"] = ControllerContext.RouteData.Values["referrer"];

Nous en avons besoin pour qu'à chaque fois nous n'écrivions pas le même code dans différentes méthodes. Il serait possible d'écrire une fonction distincte dans le contrôleur, mais si plusieurs contrôleurs sont utilisés, il est plus pratique d'utiliser un type d'attribut spécial.

Par la suite, dans le corps de la méthode qui traite les actions des vues Newet Edit, les informations stockées sont extraites et utilisées pour rediriger vers la page d'où ces vues ont été appelées: if (TempData["referrer"] != null) return Redirect(TempData["referrer"].ToString());

Pour déterminer si l'utilisateur doit être enregistré ou supprimé, le paramètre est comparé actionà Enregistrer etSupprimez respectivement les UsersRepositoryfonctions ChangeUser(model.User)et sont appelées à partir du référentiel RemoveUser(model.User). Ensuite, il y a un retour à la page précédente (d'où l'appel de vue est venu Edit) et un message y est affiché indiquant le succès de l'opération ou l'échec.

ADO.NET et MySQL - référentiel


Ici, nous avons finalement pu travailler avec le SGBD. Nous devons implémenter les fonctions d'ajout d'un utilisateur à une table Users, d'apporter des modifications aux données utilisateur, de supprimer un utilisateur de la table, d'obtenir l'utilisateur par ID, d'obtenir une liste d'utilisateurs et d'obtenir une liste de langues de la table Languages.

La plupart du temps, des constructions standard sont utilisées pour la classe MySQL.Data en utilisant MySqlCommand:

 using (MySqlConnection connect = new MySqlConnection( )) { string sql = " "; using (MySqlCommand cmd = new MySqlCommand(sql, connect)) { cmd.Parameters.Add(" ",  ).Value =  ; connect.Open(); result = cmd.ExecuteNonQuery() >= 0; //        (INSERT, UPDATE, DELETE)   cmd.ExecuteScalar()         SELECT } } 

ou MySqlDataReaderpour lire les lignes du tableau à la suite d'une requête:
 using (MySqlConnection connect = new MySqlConnection( )) { string sql = " "; using (MySqlDataReader dr = cmd.ExecuteReader()) { cmd.Parameters.Add(" ",  ).Value =  ; objConnect.Open(); while (dr.Read()) {//       } } } 

Créez le fichier UserRepository.cs dans le répertoire Models :

 public class UsersRepository { public bool AddUser(UserClass user) { user.UserID = AddUser(Name: user.Loginname, LanguageID: user.Language.LanguageID, Email: user.Email, SupporterTier: user.SupporterTier); return user.UserID > 0; } public int AddUser(string Name, int LanguageID, string Email, Supporter SupporterTier) { int ID = 0; using (MySqlConnection connect = new MySqlConnection(Base.strConnect)) { string sql = "INSERT INTO `Users` (`Loginname`, `LanguageID`, `Email`, `SupporterTier`) VALUES (@Loginname, @LanguageID, @Email, @SupporterTier)"; using (MySqlCommand cmd = new MySqlCommand(sql, connect)) { cmd.Parameters.Add("Loginname", MySqlDbType.String).Value = Name; cmd.Parameters.Add("LanguageID", MySqlDbType.Int32).Value = LanguageID; cmd.Parameters.Add("Email", MySqlDbType.String).Value = Email; cmd.Parameters.Add("SupporterTier", MySqlDbType.Int32).Value = SupporterTier; connect.Open(); if (cmd.ExecuteNonQuery() >= 0) { sql = "SELECT LAST_INSERT_ID() AS ID"; cmd.CommandText = sql; int.TryParse(cmd.ExecuteScalar().ToString(), out ID); } } } return ID; } public bool ChangeUser(UserClass user) { return ChangeUser(ID: user.UserID, Name: user.Loginname, LanguageID: user.Language.LanguageID, Email: user.Email, SupporterTier: user.SupporterTier); } public bool ChangeUser(int ID, string Name, int LanguageID, string Email, Supporter SupporterTier) { bool result = false; if (ID > 0) { using (MySqlConnection connect = new MySqlConnection(Base.strConnect)) { string sql = "UPDATE `Users` SET `Loginname`=@Loginname, `LanguageID`=@LanguageID, `Email`=@Email, `SupporterTier`=@SupporterTier WHERE UserID=@UserID"; using (MySqlCommand cmd = new MySqlCommand(sql, connect)) { cmd.Parameters.Add("UserID", MySqlDbType.Int32).Value = ID; cmd.Parameters.Add("Loginname", MySqlDbType.String).Value = Name; cmd.Parameters.Add("LanguageID", MySqlDbType.Int32).Value = LanguageID; cmd.Parameters.Add("Email", MySqlDbType.String).Value = Email; cmd.Parameters.Add("SupporterTier", MySqlDbType.Int32).Value = SupporterTier; connect.Open(); result = cmd.ExecuteNonQuery() >= 0; } } } return result; } public bool RemoveUser(UserClass user) { return RemoveUser(user.UserID); } public bool RemoveUser(int ID) { using (MySqlConnection connect = new MySqlConnection(Base.strConnect)) { string sql = "DELETE FROM `Users` WHERE `UserID`=@UserID"; using (MySqlCommand cmd = new MySqlCommand(sql, connect)) { cmd.Parameters.Add("UserID", MySqlDbType.Int32).Value = ID; connect.Open(); return cmd.ExecuteNonQuery() >= 0; } } } public UserClass FetchByID(int ID) { UserClass user = null; using (MySqlConnection objConnect = new MySqlConnection(Base.strConnect)) { string strSQL = "SELECT u.`UserID`, u.`Loginname`, l.`LanguageID`, l.`LanguageName`, u.`Email`, u.`LastLoginDate`, CAST(u.`SupporterTier` AS UNSIGNED) as `SupporterTier` FROM `Users` u LEFT JOIN `Languages` l ON l.LanguageID=u.LanguageID WHERE `UserID`=@UserID"; using (MySqlCommand cmd = new MySqlCommand(strSQL, objConnect)) { objConnect.Open(); int UserID = 0, LanguageID = 0; string Loginname = null, LanguageName= null, Email = String.Empty; Supporter SupporterTier = Supporter.None; DateTime? LastLoginDate = null; cmd.Parameters.Add("UserID", MySqlDbType.Int32).Value = ID; using (MySqlDataReader dr = cmd.ExecuteReader()) { if (dr.Read()) { UserID = dr.GetInt32("UserID"); Loginname = dr.GetString("Loginname").ToString(); LanguageID = dr.GetInt32("LanguageID"); LanguageName = dr.GetString("LanguageName").ToString(); if (!dr.IsDBNull(dr.GetOrdinal("Email"))) Email = dr.GetString("Email").ToString(); if (!dr.IsDBNull(dr.GetOrdinal("LastLoginDate"))) LastLoginDate = dr.GetDateTime("LastLoginDate"); if (!dr.IsDBNull(dr.GetOrdinal("SupporterTier"))) SupporterTier = (Supporter)dr.GetInt32("SupporterTier"); } LanguageClass language = null; if (LanguageID > 0) language = new LanguageClass(LanguageID: LanguageID, LanguageName: LanguageName); if (UserID > 0 && language != null && language.LanguageID > 0) user = new UserClass(UserID: UserID, Loginname: Loginname, Language: language, Email: Email, LastLoginDate: LastLoginDate, SupporterTier: (Supporter)SupporterTier); } } } return user; } //       ASP.NET WebForms,             ObjectDataSource      //     ,         " "         //public IEnumerable<DataRow> List() //{ // using (MySqlConnection objConnect = new MySqlConnection(Base.strConnect)) // { // string strSQL = "select * from users"; // using (MySqlCommand objCommand = new MySqlCommand(strSQL, objConnect)) // { // objConnect.Open(); // using (MySqlDataAdapter da = new MySqlDataAdapter(objCommand)) // { // DataTable dt = new DataTable(); // da.Fill(dt); // return dt.AsEnumerable(); // } // } // } //} public IList<UserClass> List(string sortOrder, System.Web.Helpers.SortDirection sortDir, int page, int pagesize, out int count) { List<UserClass> users = new List<UserClass>(); using (MySqlConnection objConnect = new MySqlConnection(Base.strConnect)) { //     string sort = " ORDER BY "; //   ,               (inject) // ,  , MySQL        //    ,         (       ) //        ,    if (sortOrder != null && sortOrder != String.Empty) { sort += "`" + sortOrder + "`"; if (sortDir == System.Web.Helpers.SortDirection.Descending) sort += " DESC"; sort += ","; } sort += "`UserID`"; //   //        ( ) string limit = ""; if (pagesize > 0) { int start = (page - 1) * pagesize; limit = string.Concat(" LIMIT ", start.ToString(), ", ", pagesize.ToString()); } string strSQL = "SELECT SQL_CALC_FOUND_ROWS u.`UserID`, u.`Loginname`, l.`LanguageID`, l.`LanguageName` as `Language`, u.`Email`, u.`LastLoginDate`, CAST(u.`SupporterTier` AS UNSIGNED) as `SupporterTier` FROM `Users` u LEFT JOIN `Languages` l ON l.LanguageID=u.LanguageID" + sort + limit; using (MySqlCommand cmd = new MySqlCommand(strSQL, objConnect)) { objConnect.Open(); cmd.Parameters.Add("page", MySqlDbType.Int32).Value = page; cmd.Parameters.Add("pagesize", MySqlDbType.Int32).Value = pagesize; using (MySqlDataReader dr = cmd.ExecuteReader()) { while (dr.Read()) { LanguageClass language = new LanguageClass(LanguageID: dr.GetInt32("LanguageID"), LanguageName: dr.GetString("Language").ToString()); users.Add(new UserClass( UserID: dr.GetInt32("UserID"), Loginname: dr.GetString("Loginname"), Language: language, Email: dr.IsDBNull(dr.GetOrdinal("Email")) ? String.Empty : dr.GetString("Email"), LastLoginDate: dr.IsDBNull(dr.GetOrdinal("LastLoginDate")) ? (DateTime?) null : dr.GetDateTime("LastLoginDate"), SupporterTier: dr.IsDBNull(dr.GetOrdinal("SupporterTier")) ? (Supporter) Supporter.None : (Supporter)dr.GetInt32("SupporterTier"))); } } } using (MySqlCommand cmdrows = new MySqlCommand("SELECT FOUND_ROWS()", objConnect)) { int.TryParse(cmdrows.ExecuteScalar().ToString(), out count); } } return users; } } 

Il contient une méthode AddUserpour ajouter un utilisateur, mettre à jour les données utilisateur ( ChangeUser), supprimer ( RemoveUser), rechercher l'utilisateur ( FetchByID) par identifiant et la méthode la plus intéressante Listpour afficher une liste paginée d'utilisateurs avec tri. Je voudrais en outre commenter la fonction List:

  1. Cela ne vaut pas la peine de renvoyer la table ( DataTable) en tant que telle - dans ce cas, nous perdrons la possibilité d'accéder aux éléments de la classe dans la vue et serons obligés d'utiliser des constantes de chaîne pour faire référence aux valeurs de la ligne de table. C'est-à-dire SQL-. UserClass .
  2. LIMIT SQL- SELECT . , MySQL , LIMIT . , , ORDER BY . , SQL-, SQL , , . , , : .
  3. SQL SELECT FOUND_ROWS() , , SELECT SQL_CALC_FOUND_ROWS LIMIT .

Toutes les autres méthodes sont complètement ordinaires, conformes au canon ci-dessus.

Et le fichier LanguageRepository.cs dans le répertoire Models :

 public class LanguagesRepository { public IList<LanguageClass> List() { List<LanguageClass> languages = new List<LanguageClass>(); using (MySqlConnection objConnect = new MySqlConnection(Base.strConnect)) { string strSQL = "SELECT `LanguageID`, `LanguageName` as `Language` FROM `Languages` ORDER BY `LanguageName`"; using (MySqlCommand cmd = new MySqlCommand(strSQL, objConnect)) { objConnect.Open(); using (MySqlDataReader dr = cmd.ExecuteReader()) { while (dr.Read()) { LanguageClass language = new LanguageClass(LanguageID: dr.GetInt32("LanguageID"), LanguageName: dr.GetString("Language").ToString()); languages.Add(language); } } } } return languages; } } 

Il n'a qu'une seule fonction pour obtenir une liste de langues à partir de la base de données, créant une liste d'éléments de classe LanguageClass.

Total


C'est tout - la tâche est résolue. Il est clair que de nombreux problèmes n'ont pas été abordés: conteneur d'injection de dépendance, tests unitaires, outils de moking, localisation, etc. etc.Certaines choses pourraient être faites différemment ou simplement mieux. Mais la «brique» qui s'est avérée est suffisamment complète pour comprendre comment ASP.NET MVC fonctionne et qu'il n'est pas si effrayant de travailler avec ADO.NET et des SGBD qui ne sont pas MS SQL.

PS Et enfin: vous pouvez voir comment ce projet fonctionne à cette adresse . Et ici, vous pouvez télécharger l'intégralité du projet. Eh bien, au tas - un lien vers mon blog .

Source: https://habr.com/ru/post/fr482346/


All Articles