ASP.NET MVC-通过ADO.NET使用MySQL

传统技术
警告:ASP.NET MVC和ADO.NET均已弃用。 建议将ASP.NET Core与现代ORM结合使用。 但是,如果您有兴趣,请阅读。

我大概已经三次使用ASP.NET MVC。 在使用ASP.NET WebForms十年后,切换到MVC技术有点困难,因为存在许多差异,因此列出这些技术的共同点比较容易,但.NET Framework库除外。 我不会在这里写-MVC比WebForms更好或更差,它们都很好,并且您可以在这两种技术上构建良好的应用程序。 尽管我有想法,但我也把对TDD的需求留给了我。

现在,我将讨论最标准的任务-数据的常规工作:以表格形式查看记录列表,添加,更改和删除数据(CRUD操作)。 但是,由于某种原因,在几乎所有书籍和许多用于ASP.NET MVC的Internet解决方案中,仅通过ORM(对象关系映射)考虑该选项:实体框架(EF)或LINQ for SQL。 毫无疑问,这些技术都是极好的,最后,程序员可能不会理解-但是这种非常相关的DBMS(他最有可能使用的)通常是如何工作的,从理论上讲,甚至不再需要SQL:以EF和DBMS的连接器将相互理解。 “这里是幸福-不再有美丽。” 但是,对于那些不害怕通过ADO.NET机制直接与数据库一起工作的程序员,通常并不清楚-通常从何处开始使用ASP.NET MVC以及是否有必要。

另外,例如,对我而言,一开始它由于缺乏在网格表中显示数据的便捷组件而造成了混乱。 可以理解的是,开发人员本人必须实施所有这一切,或者从包管理器中获取适当的东西。 如果像我一样,您对ASP.NET WebForms中的GridView组件感到满意,那么对于MVC,除了Grid.mvc之外,很难找到可比的东西。 但是,我对这些组件的信任不足以将它们用于足够大的项目。 如果使用它们,则程序员开始依赖于另一个开发人员,该开发人员首先不知道如何编写此组件(是否定性?),其次,不知道何时以及如何完成该组件。 有时似乎甚至可以扩展该组件并进行进一步的观察,但是,如果开发人员完成了该组件,我们将不得不重新挖掘代码或将组件更新冻结在某个版本上。 而且,如果开发人员修复了漏洞,则仍然必须升级到新版本。 即使过渡到新版本的MySQL连接器,也会引起某些问题,尽管它仍由一家大公司开发,但是Nuget软件包管理器中的众多“自行车”又如何呢?

因此,在处理由MySQL DBMS处理的数据时,让我们尝试学习如何在ASP.NET MVC下编写。 所有代码都可以在此地址获得 ,下面将通过小链接和解释部分呈现此代码。 在这里您可以看到该应用程序。

在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; 

数据库(DB)将很简单,并且仅由两个表表示,在LanguageID字段中,它们之间具有一对多的关系。 这样,我将使需要使用下拉列表为用户选择一种语言的情况复杂化。 另外,为方便起见,对于用户,我们还将引入SupporterTier字段,该字段将确定通过枚举提供的用户支持级别。 为了在我们的项目中使用几乎所有数据类型,我们添加了“日期/时间”类型的LastLoginDate字段,该字段将在用户登录时由应用程序本身填充(未反映在该项目中)。

建立专案




选择“ MVC”。 可以使用“空”,但是我们经过培训,而不是实际的应用程序,因此这将立即为我们提供帮助,而无需不必要的手势,将Bootstrap和JQuery集成到我们的应用程序中。



我们已经使用ASP.NET MVC包和过滤器注册在App_Start目录中填充了ContentFontsScripts文件夹以及BundleConfig.csFilterConfig.cs文件 。 在一个空项目中,仅在RouteConfig.cs文件中注册了路由。 在Global.asax.cs中,还将添加这些文件中描述方法调用:

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

我们建立了基础架构和整个捆绑


要使用MySQL DBMS,请添加MySql.Data库:如果已经在计算机上安装了mysql-connector-net-8.0.18连接器 ,则手动添加,或者从Nuget包管理器添加:



将连接字符串的配置添加到MySQL DBMS的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> 

<appSettings>部分中添加一行,并带有指向所添加的连接字符串的链接: <add key="ConnectionString" value="example" />
我们向应用程序添加一个新的Domain目录,在其中创建一个新的静态Base类(在Base.cs文件中),在其中访问以下参数:

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

我喜欢在应用程序中有一个特定的基类,该类具有指向应用程序参数的链接和一些可以从整个应用程序中调用的标准函数。
连接字符串的名称是在应用程序设置中定义的,因此将来可以更轻松地将连接字符串与DBMS一起使用:在不同的数据库之间快速切换并更改连接设置。 此外,在应用程序参数中使用连接字符串名称将应用程序发布到Microsoft Azure是很方便的-您可以在其中设置用于发布的应用程序服务的参数,并在其中确定所需的连接字符串,该连接字符串在<connectionStrings>预定义。 然后,在发布时,您将无法使用web.config文件的转换。

我还喜欢在文本中使用全局资源文件中的值,以便在突然需要时不在多个位置覆盖它们。 例如:



在页面布局文件_Layout.cshtml (通常位于Views \ Shared目录中,以后将在该项目的所有页面中使用)中,您现在可以使用这些变量(例如,参见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> 

同样在此文件中,我们看到连接的Bootstrap和JQuery脚本库的级联样式表的附件。 所有视图的内容将在调用RenderBody()函数的位置生成。

M表示型号


将文件UserClass.cs添加到Domain目录中:

 [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 } 

以及同一个目录中的LanguageClass.cs文件:
 [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; } } 

在这里,您可以看到类的属性重复了DBMS中“ UsersLanguages表的结构。 enum Supporter为该枚举类型创建了一个enum Supporter ,以便可以将其用于数据库表中类似字段的SupporterTier类的属性。 对于UserIDLanguageID字段,您可以看到它们被指定为主键,就像在数据库中一样。 [Key]属性用于此目的。

所有其他属性很可能与使用此类的视图相关。 而且,如果我们要使用助手为这些属性形成HTML标记(我个人肯定会建议这样做),那么我们将必须非常仔细地设置这些属性才能获得所需的内容。 特别是,此项目需要以下内容:

  • [DisplayName] -用作类的显示名称。 有时它很有用,在这个项目中,我特别添加了Html.DisplayNameForModel帮助器的Html.DisplayNameForModel进行演示。
  • 具有Name属性的[Display] -用作屏幕上显示的class属性的名称。 还有一个有用的Order属性,使您可以使用助手对显示类属性的顺序进行排序(默认情况下,按定义类属性的顺序排序,因此该项目中未使用Order属性)。
  • 具有DisplayValue属性的[HiddenInput] 。 它用于根本不需要在表单和列表中显示的属性( DisplayValue=false ,被绘制为hidden类型的input标签),或仍需要显示但以不可变文本形式显示的属性( DisplayValue=true ,已绘制)像纯文字,没有标签)
  • [ScaffoldColumn] -指示是否在编辑Html.EditorForModel (例如Html.EditorForModel )中显示该字段。 如果为false ,则不会在表格中显示对类属性的描述或其值。 [HiddenInput(DisplayValue = false)]不能在[HiddenInput(DisplayValue = false)]使用,因为在这种情况下,此类类属性的值不仅不会在信息输入表单中而且不会在表格显示中显示。 在这种情况下, LastLoginDate属性是必需的,该属性不是手动输入的,而是自动填充的,但是我们仍然需要查看它。
  • [Required] -检查是否已为class属性输入值,并在ErrorMessage属性中显示错误消息文本,并使用AllowEmptyStrings属性允许您输入空白行。
  • [EmailAddress] -本质上相同的属性,用于检查邮件地址的正确性。

数据库模型的类已准备就绪,我们转向表示形式(表示模型的类别将在下面描述)。

V-表示仇杀表演


Views目录中,为我们的视图创建Users目录。 我们所有的视图都使用位于Views \ Shared目录中的标准(在Views目录的_ViewStart.cshtml文件中定义) _Layout.cshtml布局。 创建一个Index视图(“ 用户”目录中的Index.cshtml文件):



并编写代码:

 @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> } 

如果我们希望默认情况下启动此视图,请在RouteConfig.cs文件中对Default进行更改:

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

在视图本身中,您需要注意Html.Partial("List") 。 必须在此位置在Views \ Shared目录的List.cshtml文件中绘制一个特殊的单独的常规局部视图。 实际上,这是一个网格表,用于显示我们的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> } 

可以看出,在数据表的标题中, Html.DisplayNameFor helper Html.DisplayNameFor显示列的名称,为此,您必须指定对类对象的属性的引用。 由于形成表格标题时,我们只有一个Model.Users对象,它是UserClass类型的对象列表,因此我们必须使用以下方法:选择此列表的第一行作为UserClass类的对象。 例如,对于用户名: Model.Users.First().Loginname 。 由于Users类的Loginname属性具有[Display(Name = "Login")]属性,因此它将在列名称中显示为“ Login”:



List视图中还有什么有趣的东西? 当然, foreach块将呈现从控制器接收到的“ Users列表中的UserClass类的对象。 这里的UsersGrid模型中的SortingInfoPagingInfo很有趣。 而且,我们需要这些对象来组织数据排序(在<th>标记的表头中使用)和组织信息的分页(在页面底部的表下方)。 这就是为什么我们不将IEnumerable<UserClass>类型的对象的纯列表用作模型的原因。 作为模型,我们使用UsersGrid类,该类位于Model目录的UsersGrid.cs文件中。

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

PagingInfoSortingInfo类本身在GridInfo.cs文件中的同一位置。

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

为了在视图中使用,特殊的帮助器已添加到GridHelpers.cs文件( 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; } } 

由于具有数据的网格没有排序而没有分页信息是一件相当没用的事情,并且ASP.NET MVC中没有用于整个数据表的标准帮助程序,因此您必须自己创建(或采用其他人创建的)。 在这种情况下,我监视了Internet上ASP.NET MVC书籍和解决方案中的几种实现。 而且,由于某种原因,至少结合了数据排序和分页的解决方案根本没有,或者我没有找到。 我必须理解这件事,将其组合并修改为正常状态。 例如,在那些实现中的分页通常不能提供或多或少的长页面列表的输出-那么,如果会有几千个页面怎么办? 我给出了上述解决方案的显示示例:



我们还需要视图来创建和修改数据。 用于创建UserClass类型的对象的视图:



并且视图代码将如下所示:

 @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>*@ } 

例如,此视图显示了Html.EditorFor辅助程序作为生成用于编辑UserClass类中对象的所有属性的标记的一种方式。 显示如下:



该视图使用UserModel类作为模型,而不是直接使用UserClassUserModel类本身位于Models目录的UserModel.cs文件中:

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

该类LanguageClass包含UserClass对象本身以及LanguageClass类型的其他对象列表。 我们需要此列表来创建语言的下拉列表,并在其中选择当前用户语言: @Html.DropDownListFor(m => Model.User.Language.LanguageID, Model.SelectLanguages(), "")

该助手使用对SelectLanguages()函数的调用,该函数将语言列表转换为SelectList类型的对象,该对象的标识符和行名参数已设置。 将这个对象的生成放入视图中将是错误的,因为这种想法是不知道这些与字段名称的绑定。 当然,有可能直接在控制器中生成一个现成的SelectList ,但是我喜欢带有域类对象的私有列表和函数的选项。

要生成下拉列表,我们必须使用单独的助手,因为Html.EditorFor(m => m.User)助手将不会为LanguageClass类型的嵌入式对象生成编辑标记(可以通过为下拉列表编写通用模板来绕过它,但是在这里,我们我们不会这么做的...)。

并且由于我们在视图中使用的是UserModel类的对象,其中包括UserClass类的另一个对象,因此我们将无法使用Html.EditorForModel()帮助器,因为这些帮助器不是递归的,并且在这种情况下将无法使用,因此使用了Html.EditorFor()帮助器Html.EditorFor()用于User对象。

我还想注意注释掉的标记:Html.ActionLink("Back to list", "Index")

通常,以类似的方式实现从视图返回以编辑回数据列表的操作。实际上,首先,在我看来,它看起来很奇怪-当您在form中使用类型按钮时button,由于某种原因,返回按钮是由链接实现的。其次,如果我们使用排序和逐页输出-我们必须明智的做法是在同一视图中返回同一页面-并且不仅要在视图中传递对象UserClass,而且还要传递返回页面的参数。有一种简单得多的方法-将选项与按钮一起使用:/>,这会将用户带回到浏览器历史记录页面。当然,这里有一些细微差别(例如,您已经尝试将对象保存在此页面上,它不起作用,现在-您已经需要双击“取消”按钮了),但是此选项作为一个整体来说效果很好。

以及用于编辑类型的对象的视图UserClass



及其代码:

 @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> } 

并且在此视图中,提供了使用不同的助手为类对象的每个所需属性分别生成标签的选项。在这种情况下,您已经可以以稍微更漂亮的方式进行显示:



在此视图中,还可以使用另一种方式返回列表。而不是使用类似的按钮:/>相同的按钮与其他操作按钮(SaveRemove)一起使用:/>并且返回是在处理这些按钮的操作的controller方法内进行的(请参见下面方法的实现)。

枚举


然后就产生了与enum类型的class属性有关的细微差别事实是,如果仅使用帮助器Html.EditorFor(),则文本信息输入字段(type tag <input type=”text”/>显示在表单上,但实际上我们需要一个从一组值中进行选择的字段(即<select>带有一组选项的标签<option>)。

1.直线使用的辅助型解决Html.DropDownListFor()或者Html.ListBoxFor(),例如,在我们的例子@Html.DropDownListFor(m => m.Model.User.SupporterTier, new SelectList(Enum.GetNames(typeof(Example_Users.Domain.Supporter))))有两个缺点-每次都必须单独为助手指定处方,Html.EditorForModel()否则Html.EditorFor()它将不起作用。

2.您可以创建一个自定义类型模板EditorViews \ Shared \ EditorTemplates文件夹中创建Supporter.cshtml文件

 @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.结果,我想出了一个在Internet上找到的解决方案选项:创建用于传输的通用模板。Views \ Shared \ EditorTemplates文件夹中创建Enum.cshtml文件

 @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) 

总的来说,这里一切都很好:模板在任何可能的地方都很好用。您甚至[UIHint("Enum")]无法添加。此外,此通用模板[Display(Name)]使用特殊功能读取枚举值的属性

C表示控制器


UsersController.cs文件添加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从用户的实际列表以及组织信息分页和排序所必需的信息。此外,在调用该方法的List同时与创建类object一起获得totalItem的值UsersGrid。此外,该对象被转移以显示到表示。

还有另一个具有该属性的Index方法,[HttpPost]需要New在视图中进行按钮单击Indexpublic ActionResult Index(string onNewUser)

输入参数onNewUser/>视图中的元素一些共同点,Index当我们单击此按钮时,它将参数值传递给与null比较的函数。如果类型按钮submit在视图中Index几个,则必须检查值本身(在这种情况下,该值将是“ New user”)。

检查之后,我们形成一个类对象UserModel该类对象由一个新对象UserClass和一系列语言组成,以从下拉列表中选择它们并将其传递给presentation New。通过如下LanguageRepository方法从存储库中获取语言列表List

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

2) New

New New User Html.ActionLink("New user", "New", "Users") Index . ( New ) , , .

New model UserModel : public ActionResult New(UserModel model)

New UserModel , ( , UserClass LanguageClass ), UsersRepository , AddUser(model.User) . ( New ) .

3) Edit

Edit UserID ( ) : public ActionResult Edit(int UserID)

UsersRepository FetchByID(UserID) UserClass . – UserModel Edit .

UserModel action : public ActionResult Edit(UserModel model, string action)

将类型对象传递给此方法UserModel,当单击视图的按钮(处理用于发送表单数据的事件)时,它会处理操作Edit。为了了解按下了哪个按钮,使用了输入参数,该输入参数action在HTML type标签的名称中指示input。因此,该方法将该参数的值与value这些按钮的参数值进行比较。在检查模型的有效性之前,请检查该值Cancel以执行取消用户编辑的操作。使用此选项,而不是返回到视图中New使用的浏览器历史记录,并用于存储从其传递视图的页面的地址Edit。为此,控制器演示了使用其属性的技术ActionFilterReferrerHoldAttribute.csReferrerHoldAttribute文件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); } } 

它用于存储有关在单击“ 新用户按钮或更改用户链接后需要返回的页面的信息TempData["referrer"] = ControllerContext.RouteData.Values["referrer"];

我们需要它,以便每次我们都不会以不同的方法编写相同的代码。可以在控制器中编写一个单独的函数,但是如果使用多个控制器,则使用特殊的属性类型更为实用。

随后,在处理视图New的操作的方法的主体中Edit,提取存储的信息,并将其用于重定向回从中调用这些视图的页面:if (TempData["referrer"] != null) return Redirect(TempData["referrer"].ToString());

为了确定是否需要保存或删除用户,将参数action保存比较从存储库中分别删除UsersRepository函数ChangeUser(model.User)RemoveUser(model.User)然后返回上一页(视图调用来自Edit),并在此处显示一条消息,指示操作成功或失败。

ADO.NET和MySQL-存储库


在这里,我们终于开始使用DBMS。我们需要实现以下功能:将用户添加到表中Users,更改用户数据,从表中删除用户,通过ID获取用户,获取用户列表以及从表中获取语言列表Languages

通常,标准结构用于MySQL.Data类,使用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 } } 

MySqlDataReader由于查询而读取表行:
 using (MySqlConnection connect = new MySqlConnection( )) { string sql = " "; using (MySqlDataReader dr = cmd.ExecuteReader()) { cmd.Parameters.Add(" ",  ).Value =  ; objConnect.Open(); while (dr.Read()) {//       } } } 

Models目录中创建UserRepository.cs文件

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

它包含一种AddUser用于添加用户,更新用户数据(ChangeUser),删除(RemoveUser),FetchByID通过ID 搜索用户()的方法List以及用于显示用户分页列表进行排序的最有趣的方法我还要对功能进行评论List

  1. 如此返回表(DataTable是不值得的-在这种情况下,我们将失去访问视图中类的元素的能力,并且将被迫使用字符串常量来引用表行中的值。即 SQL-. UserClass .
  2. LIMIT SQL- SELECT . , MySQL , LIMIT . , , ORDER BY . , SQL-, SQL , , . , , : .
  3. SQL SELECT FOUND_ROWS() , , SELECT SQL_CALC_FOUND_ROWS LIMIT .

所有其他方法都是完全普通的,与上面的标准一致。

以及Models目录中LanguageRepository.cs文件

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

它只有一个功能,可以从数据库中获取语言列表,并创建类元素列表LanguageClass

合计


就这样-任务解决了。显然,许多问题没有解决:依赖注入容器,单元测试,修改工具,本地化等。有些事情可以做的不同,也可以做得更好。但是结果出来的“砖块”足够完整,足以理解ASP.NET MVC的工作原理,并且与非MS SQL的ADO.NET和DBMS一起使用也不是那么令人恐惧。

PS最后:您可以在此地址查看该项目的工作方式在这里,你可以下载整个项目。好了,到堆- 我的博客的链接

Source: https://habr.com/ru/post/zh-CN482346/


All Articles