ASP.NET MVC - العمل مع MySQL من خلال ADO.NET

التكنولوجيا القديمة
تحذير: تم إهمال 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 ، لسبب ما ، يتم النظر في الخيار حصريًا من خلال ORM (تعيين ارتباط الكائنات): إما Entity Framework (EF) أو LINQ for SQL. التقنيات ممتازة ، لا شك ، أخيرًا ، قد لا يفهم المبرمج - لكن كيف يعمل نظام إدارة قواعد البيانات الترابطية (الذي يستخدمه على الأرجح) بشكل عام ، وحتى SQL ، من الناحية النظرية ، لم تعد ضرورية: وضع في شكل EF و سوف الموصلات ل DBMS فهم بعضهم البعض. "هنا هي السعادة - لم يعد هناك جمال". لكن بالنسبة للمبرمجين الذين لا يخشون العمل مباشرة مع قاعدة البيانات من خلال آلية ADO.NET ، فغالبًا ما يكون الأمر غير واضح - ومن أين تبدأ بـ ASP.NET MVC بشكل عام وما إذا كان ذلك ضروريًا.

بالإضافة إلى ذلك ، على سبيل المثال ، في البداية كنت منزعجًا بشكل كبير بسبب عدم وجود مكون مناسب لعرض البيانات في جدول الشبكة. من المعلوم أنه يجب على المطور نفسه تنفيذ كل هذا أو أخذ شيء مناسب من مدير الحزمة. إذا كنت مثلي أكثر من سعيد بمكون GridView في ASP.NET WebForms ، فمن الصعب على MVC العثور على شيء أكثر أو أقل قابلية للمقارنة ، باستثناء Grid.mvc. لكن ثقتي في هذه المكونات ليست كافية لاستخدامها في مشروع كبير بما فيه الكفاية. إذا تم استخدامها ، يبدأ المبرمج في الاعتماد على مطور آخر ، أولاً ، لا يعرف كيفية كتابة هذا المكون (هل هو نوعي؟) ، وثانياً ، من غير المعروف متى وكيف سيتم الانتهاء منه. قد يبدو من الممكن في بعض الأحيان توسيع المكون ورؤيته بشكل أكبر ، ولكن إذا أكمله المطور ، فنحن مضطرون إما لإعادة حفر الكود مرة أخرى أو تجميد تحديث المكون في إصدار معين. وإذا كان المطور يعمل على إصلاح ثغرة أمنية ، فلا يزال يتعين عليك الترقية إلى الإصدار الجديد. حتى الانتقال إلى إصدارات جديدة من موصل MySQL يسبب بعض المشكلات ، على الرغم من أنه لا يزال قيد التطوير من قبل شركة كبيرة ، ولكن ماذا عن "الدراجات" العديدة في مدير حزمة Nuget؟

لذلك ، دعونا نحاول معرفة كيفية الكتابة تحت ASP.NET MVC ، أثناء العمل مع البيانات التي تتم معالجتها بواسطة MySQL DBMS. يمكن أخذ جميع الشفرات هنا على هذا العنوان ، أسفل هذا الرمز سيتم تقديمه جزئيًا مع روابط وتفسيرات صغيرة. وهنا يمكنك رؤية التطبيق.

إنشاء قاعدة بيانات في 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". من الممكن استخدام "Empty" ، لكن لدينا تدريب ، وليس تطبيقًا حقيقيًا ، لذلك سيساعدنا ذلك فورًا ، دون إيماءات غير ضرورية ، على دمج Bootstrap و JQuery في تطبيقنا.



نحصل بالفعل على مجلدات المحتوى والخطوط والمخطوطات ، وكذلك ملفات BundleConfig.cs و FilterConfig.cs في دليل App_Start مع تسجيل الحزم ومرشحات ASP.NET MVC. في مشروع فارغ ، يوجد فقط تسجيل للطرق في ملف 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" />
نضيف دليل مجال جديد إلى التطبيق ، حيث نقوم بإنشاء فئة أساسية ثابتة جديدة (في ملف 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 إلى دليل المجال :

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

هنا يمكنك أن ترى أن خصائص الفئات تكرر بنية جدول Users Languages في DBMS. enum Supporter إنشاء enum Supporter التعداد لنوع التعداد بحيث يمكن استخدامه لخاصية فئة SupporterTier لحقل مماثل في جدول قاعدة البيانات. بالنسبة لحقول UserID ، LanguageID ، يمكنك أن ترى أنه تم تحديدها كمفتاح أساسي ، كما هو الحال في قاعدة البيانات. يتم استخدام السمة [Key] لهذا الغرض.

ترتبط جميع السمات الأخرى على الأرجح باستخدام المشاهدات باستخدام هذه الفئة. وإذا كنا سنستخدم المساعدين لتكوين علامات HTML لهذه الخصائص (والتي أوصي بها شخصيًا بالتأكيد) ، فسوف يتعين علينا تعيين هذه السمات بعناية فائقة للحصول على ما نحتاج إليه. على وجه الخصوص ، إليك ما هو مطلوب لهذا المشروع:

  • [DisplayName] - يستخدم كاسم عرض للفئة. في بعض الأحيان قد يكون ذلك مفيدًا ، في هذا المشروع أضفت بشكل خاص استخدام Html.DisplayNameForModel المساعد للتظاهر.
  • [Display] مع خاصية Name - تستخدم كاسم خاصية الفئة المعروضة على الشاشة. هناك أيضًا خاصية ترتيب مفيدة تتيح لك ترتيب تسلسل عرض خصائص الفصل في نموذج باستخدام المساعدين (بشكل افتراضي ، الترتيب حسب الترتيب الذي يتم تعريف خصائص الفئة به ، لذلك لم يتم استخدام خاصية الطلب في هذا المشروع).
  • [HiddenInput] مع خاصية DisplayValue . يتم استخدامه للخصائص التي لا تحتاج إلى إظهارها في النماذج والقوائم على الإطلاق ( DisplayValue=false ، يتم رسمها كعلامات input بنوع hidden ) ، أو للخصائص التي لا تزال بحاجة إلى عرضها ، ولكن في شكل نص غير قابل للتغيير ( DisplayValue=true ، يتم رسمه مثل النص النظيف ، دون علامات)
  • [ScaffoldColumn] - يشير إلى ما إذا كان سيتم عرض الحقل في تحرير Html.EditorForModel (على سبيل المثال ، Html.EditorForModel ). إذا كانت false ، فلن يتم عرض وصف خاصية الفئة ولا قيمتها في النموذج. لا يمكن استخدام [HiddenInput(DisplayValue = false)] ، لأنه في هذه الحالة لن يتم عرض قيم خاصية هذه الفئة على الإطلاق ليس فقط في نماذج إدخال المعلومات ، ولكن أيضًا في شاشات الجدول. في هذه الحالة ، كان هذا مطلوبًا لخاصية LastLoginDate ، التي لم يتم إدخالها يدويًا ، ولكن يتم ملؤها في مكان ما تلقائيًا ، ولكن لا نزال بحاجة إلى رؤيته.
  • [Required] - للتحقق من إدخال قيمة لخاصية فئة ، مع نص رسالة الخطأ في خاصية ErrorMessage وخاصية AllowEmptyStrings تتيح لك إدخال سطور فارغة.
  • [EmailAddress] - نفس السمة نفسها للتحقق من صحة العنوان البريدي.

فئات نموذج قاعدة البيانات جاهزة ، ننتقل إلى التمثيل (سيتم وصف فئات نماذج التمثيل أدناه).

الخامس - يعني أداء الثأر


في دليل طرق العرض ، قم بإنشاء دليل المستخدمين لطرق العرض الخاصة بنا. تستخدم جميع طرق العرض الخاصة بنا المعيار (المحدد في ملف _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> } 

إذا كنا نريد أن تبدأ طريقة العرض هذه افتراضيًا ، فقم بإجراء تغيير على Default في ملف RouteConfig.cs :

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

في العرض نفسه ، تحتاج إلى الانتباه إلى السطر مع Html.Partial("List") . هذا ضروري لرسم في هذا المكان طريقة عرض جزئية عامة منفصلة خاصة موجودة في ملف List.cshtml في الدليل Views \ Shared . في الواقع ، إنه جدول شبكة لعرض البيانات من جدول قاعدة بيانات 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 مساعدين Html.DisplayNameFor لعرض أسماء الأعمدة ولهذا عليك تحديد مرجع لخاصية كائن الفئة. Model.Users عند تكوين عنوان الجدول ، لا يوجد لدينا سوى كائن Model.Users ، وهو عبارة عن قائمة بالكائنات من نوع UserClass ، وعلينا استخدام الطريقة التالية: حدد الصف الأول من هذه القائمة ككائن من فئة UserClass . على سبيل المثال ، لاسم المستخدم: Model.Users.First().Loginname . نظرًا لأن سمة Loginname لفئة Users تحتوي على السمة [Display(Name = "Login")] ، فسيتم عرضها "تسجيل الدخول" في اسم العمود:



ما هو الآخر المثير للاهتمام في عرض List ؟ كتلة foreach ، بالطبع ، يعرض كائنات فئة UserClass الموجودة في قائمة Users المستلمة من وحدة التحكم. SortingInfo و PagingInfo في نموذج UsersGrid لدينا مثيرة للاهتمام هنا. ونحن بحاجة إلى هذه الكائنات لتنظيم فرز البيانات (المستخدم في رأس الجدول في العلامات <th> ) وتنظيم ترقيم الصفحات من المعلومات (المستخدمة في أسفل الصفحة ، أسفل الجدول). هذا هو السبب في أننا لا نستخدم كنموذج قائمة خالصة بالكائنات من النوع IEnumerable<UserClass> . UsersGrid ، UsersGrid فئة UsersGrid ، الموجود في ملف UsersGrid.cs في الدليل Model .

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

PagingInfo و SortingInfo أنفسهم في ملف 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 ، عليك إنشاء ذلك بنفسك (أو التقاط واحدة أنشأها شخص آخر). في هذه الحالة ، قمت بالتجسس على العديد من التطبيقات في كتب 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 . يتم عرضه على النحو التالي:



يستخدم هذا العرض فئة UserClass كنموذج ، وليس UserClass مباشرة. توجد فئة UserModel نفسها في ملف 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; } } 

يتم UserClass كائن UserClass نفسه وقائمة إضافية من الكائنات من نوع LanguageClass في هذه الفئة. كنا بحاجة إلى هذه القائمة لإنشاء قائمة منسدلة من اللغات ، مع تحديد لغة المستخدم الحالية فيها: @Html.DropDownListFor(m => Model.User.Language.LanguageID, Model.SelectLanguages(), "")

يستخدم هذا المساعد استدعاء SelectLanguages() ، والتي تحول قائمة اللغات إلى كائن من النوع SelectList باستخدام معلمات المعرف واسم الخط SelectLanguages() بالفعل. سيكون وضع جيل من هذا الكائن في العرض خاطئًا ، لأن الفكرة لم تكن لمعرفة هذه الروابط بأسماء الحقول. بالطبع ، سيكون من الممكن إنشاء SelectList الجاهزة مباشرة في وحدة التحكم ، لكني أحب الخيار مع قائمة خاصة بكائنات فئة المجال ووظيفة أكثر.

لإنشاء قائمة منسدلة ، يتعين علينا استخدام مساعدين منفصلين ، لأن Html.EditorFor(m => m.User) لن يولد علامة تحرير لكائن مضمن من نوع LanguageClass (يمكن تجاوز هذا عن طريق كتابة قالب عام للقوائم المنسدلة ، ولكننا هنا لن نفعل هذا ...).

ونظرًا لأننا نستخدم كائنًا من فئة UserModel في طريقة العرض الخاصة بنا ، والذي يتضمن كائنًا آخر من فئة UserClass ، فلن نتمكن من استخدام مساعد Html.EditorForModel() ، نظرًا لأن المساعدين لا Html.EditorForModel() ولن يعملوا في هذا الموقف ، وبالتالي يتم استخدام مساعد Html.EditorFor() لكائن User .

أريد أيضًا الانتباه إلى علامة التعليق: في Html.ActionLink("Back to list", "Index")

العادة ، يتم تنفيذ عائد من طريقة العرض للتحرير مرة أخرى إلى قائمة البيانات بطريقة مماثلة. في الحقيقة ، أولاً ، في رأيي ، يبدو الأمر غريبًا - عند استخدام أزرار الكتابة في نموذج 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> } 

وفي طريقة العرض هذه ، يتم تقديم خيار استخدام مساعدين مختلفين لإنشاء علامات بشكل منفصل لكل خاصية مطلوبة لكائن فئة. في هذه الحالة ، يمكنك بالفعل عرض الشاشة بطريقة مختلفة وجذابة:



وفي طريقة العرض هذه ، يتم استخدام طريقة أخرى للعودة إلى القائمة. بدلاً من استخدام زر مثل: />يتم استخدام نفس الزر كأزرار إجراء أخرى ( حفظ ، إزالة ): />ويتم تنفيذ الإرجاع داخل طريقة التحكم التي تعالج إجراءات هذه الأزرار (انظر تنفيذ الطريقة أدناه).

التعداد


ثم كان هناك فارق بسيط يرتبط باستخدام خاصية فئة من نوع التعداد . الحقيقة هي أننا إذا استخدمنا المساعد فقط Html.EditorFor()، فسيتم عرض حقل إدخال معلومات النص (علامة الكتابة <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. يمكنك إنشاء قالب نوع مخصص Editor. إنشاء ملف Supporter.cshtml في المجلد طرق العرض \ 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")فلن ينجح حل النموذج .

I مثل هذا الخيار لم يأت من حقيقة أنه، عند محاولة إنشاء مستخدم (عرض بدء Newالبرنامج) لالمساعدين Html.EditorFor()و Html.EditorForModel()اختلف مع الخطأ: " النموذج عنصر تمريرها إلى القاموس، هو NULL، ولكن مطلوب هذه المفردات عنصر نوع« Example_Users نموذج. Domain.Supporter "، غير فارغ.»السبب واضح - لا يمكن أن تكون قيمة القائمة فارغة ، لكن تعذر حل المشكلة. لذلك ، بدأ يفهم أكثر.

3. يمكنك استخدام المساعد Html.EnumDropDownListFor()المصنوع خصيصًا لعمليات النقل. كل شيء على ما يرام هنا ، لا شيء يحتاج إلى أن يكتب ، كل شيء معروض بشكل صحيح ، إنه يعمل عند التحرير وعند إنشاء كائن. باستثناء واحد "لكن": Html.EditorForModel()يستخدم المساعد مساعدين لعرض جميع الخصائص Html.EditorFor()، وبالتالي لا يستخدمها Html.EnumDropDownListFor(). و، كما أفهمها، فإنه لا يمكن تجنبها مع مساعدة من سمات لفئة من خصائص - [UIHint]، [DataType]و [EnumDataType]بعد ذلك لا تفعل العمل. لن تعمل سمات قيم التعداد أيضًا ، أي أنها لن تعمل ، بدلاً من لا شيء ، على سبيل المثال ، عرض سلسلة فارغة على النحو المحدد في وصف التعدادSupporter.

4. كنتيجة لذلك ، توصلت إلى خيار حل موجود على الإنترنت : إنشاء قالب مشترك لعمليات النقل. قم بإنشاء ملف Enum.cshtml في المجلد 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) 

هنا ، بشكل عام ، تحول كل شيء بشكل جيد: يعمل القالب بشكل رائع كلما كان ذلك ممكنًا. لا [UIHint("Enum")]يمكنك حتى إضافة. علاوة على ذلك ، يقرأ هذا القالب العام السمة [Display(Name)]لقيم التعداد باستخدام دالة خاصة.

C يعني تحكم


أضف الملف UsersController.cs إلى دليل التحكم .

 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(للعمل مع DBMS ، تمت مناقشته في الفصل التالي) ، استدعاء الطريقة Listللحصول على قائمة المستخدمين و شكل كائن فئةUsersGridمن القائمة الفعلية للمستخدمين والمعلومات اللازمة لتنظيم ترقيم الصفحات من المعلومات والفرز. علاوة على ذلك ، يتم الحصول على قيمة totalItem عندما يتم استدعاء الطريقة List، بالتزامن مع إنشاء كائن الفئة UsersGrid. كذلك يتم نقل هذا الكائن لعرضه على التمثيل.

هناك أيضًا طريقة فهرس أخرى لها السمة [HttpPost]التي نحتاجها لاستكشاف الزر ، انقر فوق Newطريقة العرض Index: public ActionResult Index(string onNewUser)

تحتوي معلمة الإدخال على onNewUserشيء مشترك مع العنصر />في العرض Index، وعندما نضغط على هذا الزر ، تقوم بتمرير القيمة إلى الوظيفة التي نتحقق منها في مقابل القيمة الخالية . إذا كانت أزرار الكتابة submitستكون في العرضIndexعدة ، يجب عليك التحقق من القيمة نفسها (في هذه الحالة ، ستكون القيمة "مستخدم جديد").

بعد التدقيق ، نقوم بتكوين كائن فئة UserModel، يتكون من كائن جديد UserClassوقائمة من اللغات لتحديدها من القائمة المنسدلة ونقلها إلى العرض التقديمي New. يتم الحصول على قائمة اللغات من المستودع عن طريق LanguageRepositoryاستدعاء الطريقة Listكما يلي:

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

2) الطرق: يتم New

إجراء طريقة Newبدون معلمة لإيجاد نقرة على رابط مستخدم جديد لسطر التعليق Html.ActionLink("New user", "New", "Users")في العرض Index. يتم تنفيذ الإجراء (فتح العرض New) بشكل مشابه للإجراء السابق ، فقط لا يوجد فحص للزر الذي تم الضغط عليه ، لأن النقر على الرابط يتم تنفيذه.

الطريقة Newمع المعلمة طراز من نوع UserModelلمعالجة حدث إرسال بيانات النموذج من أجل حفظ المستخدم الجديد في قاعدة البيانات: public ActionResult New(UserModel model)

استرداد كائن فئة معبأ من طريقة العرض الجديدة 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 input. لذلك ، يقارن الأسلوب قيم هذه المعلمة مع قيم المعلمات من valueهذه الأزرار. قبل التحقق من صحة النموذج ، يتم التحقق من القيمة Cancelلأداء إجراءات لإلغاء تحرير المستخدم. يُستخدم هذا الخيار بدلاً من الرجوع إلى محفوظات المستعرض ، والذي يُستخدم في طريقة العرض Newويستخدم تخزين عنوان الصفحة التي تمت إعادة توجيه العرض منها.Edit. للقيام بذلك ، أوضحت وحدة التحكم تقنية استخدام السمة الخاصة بها ActionFilter: الفئة ReferrerHoldAttributeفي ملف ReferrerHoldAttribute.cs (دليل 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 - مستودع


هنا وصلنا أخيرًا إلى العمل مع نظام إدارة قواعد البيانات. نحتاج إلى تنفيذ وظائف إضافة مستخدم إلى جدول Users، وإجراء تغييرات على بيانات المستخدم ، وإزالة مستخدم من الجدول ، والحصول على المستخدم بمعرف ، والحصول على قائمة المستخدمين ، والحصول على قائمة باللغات من الجدول 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()) {//       } } } 

قم بإنشاء ملف 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) بمعرف ، والطريقة الأكثر إثارة للاهتمام 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 .

جميع الطرق الأخرى عادية تمامًا ، بما يتوافق مع الشريعة أعلاه.

وملف 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.

في المجموع


هذا كل شيء - تم حل المهمة. من الواضح أنه لم تتم معالجة العديد من المشكلات: حاوية حقن التبعية ، واختبار الوحدة ، وأدوات moking ، والتعريب ، إلخ. إلخيمكن أن يتم بعض الأشياء بشكل مختلف أو ببساطة أفضل. لكن "الطوب" الذي تم إفتتاحه جاء كاملاً بما فيه الكفاية لفهم كيفية عمل ASP.NET MVC وأنه ليس مخيفًا جدًا التعامل مع ADO.NET و DBMSs التي ليست MS SQL.

ملاحظة: وأخيرا ، يمكنك أن ترى كيف يعمل هذا المشروع على هذا العنوان . و هنا يمكنك تحميل المشروع بأكمله. حسنا ، إلى الكومة - رابط لبلدي بلوق .

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


All Articles