Technologie héritéeAvertissement: 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 {
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 {
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 button
et 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;
:
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 New
dans la vue Index
: public ActionResult Index(string onNewUser)
le paramètre d'entrée a onNewUser
quelque chose en commun avec l'élément />
dans la vue Index
et 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 vueIndex
plusieurs, 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 UserClass
et 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 LanguageRepository
appelant la méthode List
comme suit: public IList<LanguageClass> Languages() { LanguagesRepository rep = new LanguagesRepository(); return rep.List(); }
2) Méthodes New
Une méthode New
sans 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 New
avec le paramètre modèle du type UserModel
pour 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 UserClass
et LanguageClass
), crée l'objet de référentiel UsersRepository
et 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 Edit
avec 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 UsersRepository
et 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 UserModel
de classe à partir de l'objet reçu et une liste de langues et transmis à la vue Edit
pour affichage.Méthode avec un paramètre d'entrée du type UserModel
et du modèle de ligne action
:public ActionResult Edit(UserModel model, string action)
Un objet type est transmis à cette méthode UserModel
et 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 value
ces boutons. Avant de vérifier la validité du modèle, la valeur est vérifiée Cancel
pour 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 New
et 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 ReferrerHoldAttribute
du 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 New
et 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 UsersRepository
fonctions 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;
ou MySqlDataReader
pour 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; }
Il contient une méthode AddUser
pour 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 List
pour afficher une liste paginée d'utilisateurs avec tri. Je voudrais en outre commenter la fonction List
:- 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
. LIMIT
SQL- SELECT
. , MySQL , LIMIT
. , , ORDER BY
. , SQL-, SQL , , . , , : .- 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 .