4. فشرده سازی HTTP را فعال کنید
5.تنظیم CacheControlMaxAge
6. استفاده از OutputCache
7. بهره برداری از ORM Profiler
4. فشرده سازی HTTP را فعال کنید
5.تنظیم CacheControlMaxAge
6. استفاده از OutputCache
7. بهره برداری از ORM Profiler
From: Atif Aziz <notifications@github.com> Reply-To: elmah/Elmah <reply+000bb03ad52eb40a4ec2d49bf78c53c3eba42efc401701a592cf00000001143e18c892a169ce0ae0bf4c@reply.github.com> To: elmah/Elmah <Elmah@noreply.github.com> Message-ID: <elmah/Elmah/issues/407/260080923@github.com> In-Reply-To: <elmah/Elmah/issues/407@github.com> References: <elmah/Elmah/issues/407@github.com> Subject: Re: [elmah/Elmah] Will ELMAH be ported to ASP.NET Core? (#407)
References: <11111@yoursite.com> <22222@yoursite.com> <33333@yoursite.com>
var smtpClient = new SmtpClient("….",587); using (MailMessage message = new MailMessage("USERNAME@gmail.com","USERNAME@gmail.com")) { message.Subject = "test"; message.Headers.Add("Message-ID", "<MESSAGEID@site.com>"); smtpClient.Send(message); } using (MailMessage message = new MailMessage("USERNAME@gmail.com","USERNAME@gmail.com")) { message.Subject = "Re: test"; message.Headers.Add("In-Reply-To", "<MESSAGEID@site.com>"); message.Headers.Add("References", "<MESSAGEID@site.com>"); smtpClient.Send(message); }
message.Headers.Add("Message-ID", $"<post/{post.id}@your-app-name.example>");
message.Headers.Add("Message-ID", $"<comments/{comment.id}@your-app-name.example>"); message.Headers.Add("In-Reply-To", $"<post/{post.id}@your-app-name.example>"); message.Headers.Add("References", $"<post/{post.id}@your-app-name.example>");
در ابتدای بحث، برای آشنایی بیشتر با HTML Helperها به مطالعه این مقاله بپردازین.
در این مقاله قرار است برای یک HTML Helper خاص، قالب نمایشی اختصاصی خودمان را طراحی کنیم و به نحوی HTML Helper موجود را سفارشی سازی کنیم. به عنوان مثال میخواهیم خروجی یک EditorFor() برای یک نوع خاص، به حالت دلخواهی باشد که ما خودمان آن را تولیدش کردیم؛ یا اصلا نه. حتی میشود برای خروجی یک EditorFor() که خصوصیتی از جنس string را میخواهیم به آن انتساب دهیم، به جای تولید input، یک مقدار متنی را برگردانیم. به این حالت:
<div> @Html.LabelFor(model => model.Name, htmlAttributes: new { @class = "control-label col-md-2" }) <div> @Html.EditorFor(model => model.Name, new { htmlAttributes = new { @class = "form-control" } }) @Html.ValidationMessageFor(model => model.Name, "", new { @class = "text-danger" }) </div> </div> <div> @Html.LabelFor(model => model.Genre, htmlAttributes: new { @class = "control-label col-md-2" }) <div> @Html.EditorFor(model => model.Genre, new { htmlAttributes = new { @class = "form-control" } }) @Html.ValidationMessageFor(model => model.Genre, "", new { @class = "text-danger" }) </div> </div>
در ادامه یک پروژهی عملی را شروع کرده و در آن کاری را که میخواهیم، انجام میدهیم. پروژهی ما به این شکل میباشد که قرار است در آن به ثبت کتاب بپردازیم و برای هر کتاب هم یک سبک داریم و قسمت سبک کتابهای ما یک Enum است که از قبل میخواهیم مقدارهایش را تعریف کنیم.
مدل برنامه
public class Books { public int Id { get; set; } [Required] [StringLength(255)] public string Name { get; set; } public Genre Genre { get; set; } }
public enum Genre { [Display(Name = "Non Fiction")] NonFiction, Romance, Action, [Display(Name = "Science Fiction")] ScienceFiction }
در داخل کلاس Books یک خصوصیت از جنس Genre برای سبک کتابها داریم و در داخل نوع شمارشی Genre، سبکهای ما تعریف شدهاند. همچنین هر کدام از سبکها هم به ویژگی Display مزین شدهاند تا بتونیم بعدا از مقدار آنها استفاده کنیم.
کنترلر برنامه
public class BookController : Controller { // GET: Book public ActionResult Index() { return View(DataAccess.DataContext.Book.ToList()); } public ActionResult Create() { return View(); } [HttpPost] [ValidateAntiForgeryToken] public ActionResult Create(Books model) { if (!ModelState.IsValid) return View(model); try { DataAccess.DataContext.Book.Add(model); DataAccess.DataContext.SaveChanges(); return RedirectToAction("Index"); } catch (Exception ex) { ModelState.AddModelError("", ex.Message); return View(model); } } public ActionResult Edit(int id) { try { var book = DataAccess.DataContext.Book.Find(id); return View(book); } catch (Exception ex) { return View("Error"); } } [HttpPost] [ValidateAntiForgeryToken] public ActionResult Edit(Books model) { if (!ModelState.IsValid) return View(model); try { DataAccess.DataContext.Book.AddOrUpdate(model); DataAccess.DataContext.SaveChanges(); return RedirectToAction("Index"); } catch (Exception ex) { ModelState.AddModelError("", ex.Message); return View(model); } } public ActionResult Details(int id) { try { var book = DataAccess.DataContext.Book.Find(id); return View(book); } catch (Exception ex) { return View("Error"); } } }
در قسمت کنترلر هم کار خاصی جز عملیات اصلی نوشته نشدهاست. لیست کتابها را از پایگاه داده بیرون آوردیم و از طریق اکشن Index به نمایش گذاشتیم. با اکشنهای Create، Edit و Details هم کارهای روتین مربوط به خودشان را انجام دادیم. نکتهی قابل تذکر، DataAccess میباشد که کلاسی است که با آن ارتباط برقرار شده با EF و سپس اطلاعات واکشی و تزریق میشوند.
View مربوط به اکشن Create برنامه
@using Book.Entities @model Book.Entities.Books @{ ViewBag.Title = "Create"; } <h2>New Book</h2> @using (Html.BeginForm()) { @Html.AntiForgeryToken() <div> <h4>Books</h4> <hr /> @Html.ValidationSummary(true, "", new { @class = "text-danger" }) <div> @Html.LabelFor(model => model.Name, htmlAttributes: new { @class = "control-label col-md-2" }) <div> @Html.EditorFor(model => model.Name, new { htmlAttributes = new { @class = "form-control" } }) @Html.ValidationMessageFor(model => model.Name, "", new { @class = "text-danger" }) </div> </div> <div> @Html.LabelFor(model => model.Genre, htmlAttributes: new { @class = "control-label col-md-2" }) <div> @Html.EditorFor(model => model.Genre, new { htmlAttributes = new { @class = "form-control" } }) @Html.ValidationMessageFor(model => model.Genre, "", new { @class = "text-danger" }) </div> </div> <div> <div> <input type="submit" value="Create" /> <input type="reset" value="Reset" /> @Html.ActionLink("Back to List", "Index", null, new {@class="btn btn-default"}) </div> </div> </div> } @section Scripts { @Scripts.Render("~/bundles/jqueryval") }
View برنامه هم همان ویویی است که خود Visual Studio برای ما ساختهاست. به جز یک سری دستکاریهایی داخل سیاساس، هدف از گذاشتن View مربوط به Create این بود که قرار است بر روی این قسمت کار کنیم. اگر پروژه رو اجرا کنید و به قسمت Create بروید، مشاهده خواهید کرد که برای Genre یک input ساخته شدهاست که کاربر باید در آن مقدار وارد کند. ولی اگر یادتان باشد، ما سبکهای نگارشی خودمان را در نوع شمارشی Genre ایجاد کرده بودیم. پس عملا باید یک لیست به کاربر نشان داده شود که تا از آن لیست، نوع را انتخاب کند. میتوانیم بیایم همینجا در داخل View مربوطه، بهجای استفاده از HTML Helper پیشفرض، از DropDownList یا EnumFor استفاده کنیم و به طریقی این لیست را ایجاد کنیم. ولی چون قرار است در این مثال به شرح موضوع مقاله خودمان بپردازیم، این کار را انجام نمیدهیم.
در حقیقیت میخوایم متد EditorFor را طوری سفارشی سازی کنیم که برای نوع شمارشی Genre، به صورت خودکار یک لیست ایجاد کرده و برگرداند. از نسخهی سوم ASP.NET MVC به بعد این امکان برای توسعه دهندهها فراهم شدهاست. شما میتوانید در پوشهی Shared داخل پوشه Views برنامه، پوشهای را به اسم EditorTemplates ایجاد کنید؛ همینطور DisplayTemplates و برای نوع خاصی که میخواهید سفارشیسازی را برای آن انجام دهید، یک PartialView بسازید.
Views/Shared/DisplayTemplates/<type>.cshtml
کاری که الان میخواهیم انجام دهیم این است که یک SelectListItem ایجاد کرده تا مقدارهای نوع Genreمان داخلش باشد و بتوانیم به راحتی برای ساختن DropDownList از آن استفاده کنیم. برای این کار Helper مخصوص خودمان را ایجاد میکنیم. پوشهای به اسم Helpers در کنار پوشههای Controllers، Models ایجاد میکنیم و در داخل آن کلاسی به اسم EnumHelpers میسازیم.
public static class EnumHelpers { public static IEnumerable<SelectListItem> GetItems( this Type enumType, int? selectedValue) { if (!typeof(Enum).IsAssignableFrom(enumType)) { throw new ArgumentException("Type must be an enum"); } var names = Enum.GetNames(enumType); var values = Enum.GetValues(enumType).Cast<int>(); var items = names.Zip(values, (name, value) => new SelectListItem { Text = GetName(enumType, name), Value = value.ToString(), Selected = value == selectedValue } ); return items; } static string GetName(Type enumType, string name) { var result = name; var attribute = enumType .GetField(name) .GetCustomAttributes(inherit: false) .OfType<DisplayAttribute>() .FirstOrDefault(); if (attribute != null) { result = attribute.GetName(); } return result; } }
در توضیح کد بالا عنوان کرد که متدها بهصورت متدهای الحاقی به نوع Type نوشته شدند. کار خاصی در بدنهی متدها انجام نشدهاست. در بدنهی متد اول لیست آیتمها را تولید کردیم. در هنگام ساخت SelectListItem برای گرفتن Text، متد GetName را صدا زدیم. برای اینکه بتوانیم مقدار ویژگی Display که در هنگام تعریف نوع شمارشی استفاده کردیم را بدست بیاریم، باید چک کنیم ببینیم که آیا این آیتم به این ویژگی مزین شدهاست یا نه. اگر شده بود مقدار را میگیریم و به خصوصیت Text متد اول انتساب میدهیم.
@using Book.Entities @using Book.Web.Helpers @{ var items = typeof(Genre).GetItems((int?)Model); } @Html.DropDownList("", items, new {@class="form-control"})
کدهایی که در بالا مشاهده میکنید کدهایی میباشند که قرار است داخل PartialViewی Genre قرار دهیم که در پوشهی EditorTemplates ساختیم. ابتدا آمدیم آیتمها را گرفتیم و بعد به DropDownList دادیم تا لیست نوع را برای ما بسازد. حالا اگه برنامه را اجرا کنید میبینید که EditorFor برای شما یه لیست از نوع شمارشی ساخته و حالا قابل استفاده هست.
کدهای کامل این مثال را از اینجا میتوانید دریافت کنید
Cannot shrink log file 2 because of minimum log space required.
DBCC OPENTRAN
Replicated Transaction Information:
Oldest distributed LSN : (0:0:0)
Oldest non-distributed LSN : (5291:25:1)
exec sp_removedbreplication 'dbName', 'both';
DBCC OPENTRAN
حال با استفاده از ابزار ASP.NET Configuration دو کاربر جدید بسازید: oldAdminUser و oldUser.
نقش جدیدی با نام Admin بسازید و کاربر oldAdminUser را به آن اضافه کنید.
بخش جدیدی با نام Admin در سایت خود بسازید و فرمی بنام Default.aspx به آن اضافه کنید. همچنین فایل web.config این قسمت را طوری پیکربندی کنید تا تنها کاربرانی که در نقش Admin هستند به آن دسترسی داشته باشند. برای اطلاعات بیشتر به این لینک مراجعه کنید.
پنجره Server Explorer را باز کنید و جداول ساخته شده توسط SQL Membership را بررسی کنید. اطلاعات اصلی کاربران که برای ورود به سایت استفاده میشوند، در جداول aspnet_Users و aspnet_Membership ذخیره میشوند. دادههای مربوط به نقشها نیز در جدول aspnet_Roles ذخیره خواهند شد. رابطه بین کاربران و نقشها نیز در جدول aspnet_UsersInRoles ذخیره میشود، یعنی اینکه هر کاربری به چه نقش هایی تعلق دارد.
برای مدیریت اساسی سیستم عضویت، مهاجرت جداول ذکر شده به سیستم جدید ASP.NET Identity کفایت میکند.
پروژه را Build کنید تا مطمئن شوید هیچ خطایی وجود ندارد.
در پنجره کوئری باز شده، تمام محتویات فایل Migrations.sql را کپی کنید. سپس اسکریپت را با کلیک کردن دکمه Execute اجرا کنید.
ممکن است با اخطاری مواجه شوید مبنی بر آنکه امکان حذف (drop) بعضی از جداول وجود نداشت. دلیلش آن است که چهار عبارت اولیه در این اسکریپت، تمام جداول مربوط به Identity را در صورت وجود حذف میکنند. از آنجا که با اجرای اولیه این اسکریپت چنین جداولی وجود ندارند، میتوانیم این خطاها را نادیده بگیریم. حال پنجره Server Explorer را تازه (refresh) کنید و خواهید دید که پنج جدول جدید ساخته شده اند.
لیست زیر نحوه Map کردن اطلاعات از جداول SQL Membership به سیستم Identity را نشان میدهد.
کلاس User باید کلاس IdentityUser را که در اسمبلی Microsoft.AspNet.Identity.EntityFramework وجود دارد گسترش دهد. خاصیت هایی را تعریف کنید که نماینده الگوی جدول AspNetUser هستند. خواص ID, Username, PasswordHash و SecurityStamp در کلاس IdentityUser تعریف شده اند، بنابراین این خواص را در لیست زیر نمیبینید.
public class User : IdentityUser { public User() { CreateDate = DateTime.Now; IsApproved = false; LastLoginDate = DateTime.Now; LastActivityDate = DateTime.Now; LastPasswordChangedDate = DateTime.Now; LastLockoutDate = DateTime.Parse("1/1/1754"); FailedPasswordAnswerAttemptWindowStart = DateTime.Parse("1/1/1754"); FailedPasswordAttemptWindowStart = DateTime.Parse("1/1/1754"); } public System.Guid ApplicationId { get; set; } public string MobileAlias { get; set; } public bool IsAnonymous { get; set; } public System.DateTime LastActivityDate { get; set; } public string MobilePIN { get; set; } public string Email { get; set; } public string LoweredEmail { get; set; } public string LoweredUserName { get; set; } public string PasswordQuestion { get; set; } public string PasswordAnswer { get; set; } public bool IsApproved { get; set; } public bool IsLockedOut { get; set; } public System.DateTime CreateDate { get; set; } public System.DateTime LastLoginDate { get; set; } public System.DateTime LastPasswordChangedDate { get; set; } public System.DateTime LastLockoutDate { get; set; } public int FailedPasswordAttemptCount { get; set; } public System.DateTime FailedPasswordAttemptWindowStart { get; set; } public int FailedPasswordAnswerAttemptCount { get; set; } public System.DateTime FailedPasswordAnswerAttemptWindowStart { get; set; } public string Comment { get; set; } }
حال برای دسترسی به دیتابیس مورد نظر، نیاز به یک DbContext داریم. اسمبلی Microsoft.AspNet.Identity.EntityFramework کلاسی با نام IdentityDbContext دارد که پیاده سازی پیش فرض برای دسترسی به دیتابیس ASP.NET Identity است. نکته قابل توجه این است که IdentityDbContext آبجکتی از نوع TUser را میپذیرد. TUser میتواند هر کلاسی باشد که از IdentityUser ارث بری کرده و آن را گسترش میدهد.
در پوشه Models کلاس جدیدی با نام ApplicationDbContext بسازید که از IdentityDbContext ارث بری کرده و از کلاس User استفاده میکند.
public class ApplicationDbContext : IdentityDbContext<User> { }
مدیریت کاربران در ASP.NET Identity توسط کلاسی با نام UserManager انجام میشود که در اسمبلی Microsoft.AspNet.Identity.EntityFramework قرار دارد. چیزی که ما در این مرحله نیاز داریم، کلاسی است که از UserManager ارث بری میکند و آن را طوری توسعه میدهد که از کلاس User استفاده کند.
در پوشه Models کلاس جدیدی با نام UserManager بسازید.
public class UserManager : UserManager<User> { }
کلمه عبور کاربران بصورت رمز نگاری شده در دیتابیس ذخیره میشوند. الگوریتم رمز نگاری SQL Membership با سیستم ASP.NET Identity تفاوت دارد. هنگامی که کاربران قدیمی به سایت وارد میشوند، کلمه عبورشان را توسط الگوریتمهای قدیمی SQL Membership رمزگشایی میکنیم، اما کاربران جدید از الگوریتمهای ASP.NET Identity استفاده خواهند کرد.
کلاس UserManager خاصیتی با نام PasswordHasher دارد. این خاصیت نمونه ای از یک کلاس را ذخیره میکند، که اینترفیس IPasswordHasher را پیاده سازی کرده است. این کلاس هنگام تراکنشهای احراز هویت کاربران استفاده میشود تا کلمههای عبور را رمزنگاری/رمزگشایی شوند. در کلاس UserManager کلاس جدیدی بنام SQLPasswordHasher بسازید. کد کامل را در لیست زیر مشاهده میکنید.
public class SQLPasswordHasher : PasswordHasher { public override string HashPassword(string password) { return base.HashPassword(password); } public override PasswordVerificationResult VerifyHashedPassword(string hashedPassword, string providedPassword) { string[] passwordProperties = hashedPassword.Split('|'); if (passwordProperties.Length != 3) { return base.VerifyHashedPassword(hashedPassword, providedPassword); } else { string passwordHash = passwordProperties[0]; int passwordformat = 1; string salt = passwordProperties[2]; if (String.Equals(EncryptPassword(providedPassword, passwordformat, salt), passwordHash, StringComparison.CurrentCultureIgnoreCase)) { return PasswordVerificationResult.SuccessRehashNeeded; } else { return PasswordVerificationResult.Failed; } } } //This is copied from the existing SQL providers and is provided only for back-compat. private string EncryptPassword(string pass, int passwordFormat, string salt) { if (passwordFormat == 0) // MembershipPasswordFormat.Clear return pass; byte[] bIn = Encoding.Unicode.GetBytes(pass); byte[] bSalt = Convert.FromBase64String(salt); byte[] bRet = null; if (passwordFormat == 1) { // MembershipPasswordFormat.Hashed HashAlgorithm hm = HashAlgorithm.Create("SHA1"); if (hm is KeyedHashAlgorithm) { KeyedHashAlgorithm kha = (KeyedHashAlgorithm)hm; if (kha.Key.Length == bSalt.Length) { kha.Key = bSalt; } else if (kha.Key.Length < bSalt.Length) { byte[] bKey = new byte[kha.Key.Length]; Buffer.BlockCopy(bSalt, 0, bKey, 0, bKey.Length); kha.Key = bKey; } else { byte[] bKey = new byte[kha.Key.Length]; for (int iter = 0; iter < bKey.Length; ) { int len = Math.Min(bSalt.Length, bKey.Length - iter); Buffer.BlockCopy(bSalt, 0, bKey, iter, len); iter += len; } kha.Key = bKey; } bRet = kha.ComputeHash(bIn); } else { byte[] bAll = new byte[bSalt.Length + bIn.Length]; Buffer.BlockCopy(bSalt, 0, bAll, 0, bSalt.Length); Buffer.BlockCopy(bIn, 0, bAll, bSalt.Length, bIn.Length); bRet = hm.ComputeHash(bAll); } } return Convert.ToBase64String(bRet); } }
دقت کنید تا فضاهای نام System.Text و System.Security.Cryptography را وارد کرده باشید.
متد EncodePassword کلمه عبور را بر اساس پیاده سازی پیش فرض SQL Membership رمزنگاری میکند. این الگوریتم از System.Web گرفته میشود. اگر اپلیکیشن قدیمی شما از الگوریتم خاصی استفاده میکرده است، همینجا باید آن را منعکس کنید. دو متد دیگر نیز بنامهای HashPassword و VerifyHashedPassword نیاز داریم. این متدها از EncodePassword برای رمزنگاری کلمههای عبور و تایید آنها در دیتابیس استفاده میکنند.
سیستم SQL Membership برای رمزنگاری (Hash) کلمههای عبور هنگام ثبت نام و تغییر آنها توسط کاربران، از PasswordHash, PasswordSalt و PasswordFormat استفاده میکرد. در روند مهاجرت، این سه فیلد در ستون PasswordHash جدول AspNetUsers ذخیره شده و با کاراکتر '|' جدا شده اند. هنگام ورود کاربری به سایت، اگر کله عبور شامل این فیلدها باشد از الگوریتم SQL Membership برای بررسی آن استفاده میکنیم. در غیر اینصورت از پیاده سازی پیش فرض ASP.NET Identity استفاده خواهد شد. با این روش، کاربران قدیمی لازم نیست کلمههای عبور خود را صرفا بدلیل مهاجرت اپلیکیشن ما تغییر دهند.
کلاس UserManager را مانند قطعه کد زیر بروز رسانی کنید.
public UserManager() : base(new UserStore<User>(new ApplicationDbContext())) { this.PasswordHasher = new SQLPasswordHasher(); }
private Guid GetApplicationID() { using (SqlConnection connection = new SqlConnection(ConfigurationManager.ConnectionStrings["ApplicationServices"].ConnectionString)) { string queryString = "SELECT ApplicationId from aspnet_Applications WHERE ApplicationName = '/'"; //Set application name as in database SqlCommand command = new SqlCommand(queryString, connection); command.Connection.Open(); var reader = command.ExecuteReader(); while (reader.Read()) { return reader.GetGuid(0); } return Guid.NewGuid(); } }
var currentApplicationId = GetApplicationID(); User user = new User() { UserName = Username.Text, ApplicationId=currentApplicationId, …};
let name: string; name = "Vahid"; // OK name = null; // OK name = undefined; // OK
let age: number; age = 24; // OK age = null; // OK age = undefined; // OK
{ "compilerOptions": { "strictNullChecks": true } }
let foo: string = null; // Error! Type 'null' is not assignable to type 'string'.
Uncaught ReferenceError: foo is not defined Uncaught TypeError: window.foo is not a function
// With strictNullChecks set to false let d: Array<number> = [null, undefined, 10, 15]; //OK let e: Array<string> = ["pie", null, ""]; //OK // With strictNullChecks set to true let d: Array<number> = [null, undefined, 10, 15]; // Error let e: Array<string> = ["pie", null, ""]; // Error
{ "compilerOptions": { "strict": true /* Enable all strict type-checking options. */ } }
{ "compilerOptions": { "strict": true, "noImplicitThis": false } }
let foo: string | null = null; // Okay!
let name: string | null; name = "Vahid"; // OK name = null; // OK name = undefined; // Error
let z = null;
testAssignedBeforeUseChecking() { let x: number; console.log(x); }
[ts] Variable 'x' is used before being assigned.
let z: number | undefined; console.log(z);
method1(x?: number) { }
method1(x?: number | undefined) { }
f(x: number): string { return x.toString(); } testTypeGuards() { let x: number | null | undefined; if (x) { this.f(x); // Ok, type of x is number here } else { this.f(x); // Error, type of x is number? here } }
Argument of type 'number | null | undefined' is not assignable to parameter of type 'number'. Type 'undefined' is not assignable to type 'number'.
getLength(s: string | null) { return s ? s.length : 0; }
isNumber(n: any): n is number { // type guard return typeof n === "number"; }
usedMb(usedBytes?: number): number | undefined { return this.isNumber(usedBytes) ? (usedBytes / (1024 * 1024)) : undefined; }
usedMb2(usedBytes?: number): number | undefined { return usedBytes ? (usedBytes / (1024 * 1024)) : undefined; }
formatUsedMb(): string { //ERROR: TS2531: Object is possibly undefined return this.usedMb(123).toFixed(0).toString(); }
formatUsed(): string { const usedMb = this.usedMb(123); return usedMb ? usedMb.toFixed(0).toString() : ""; }
export interface User { name: string; age?: number; }
printUserInfo(user: User) { if (user.age != null) { console.log(`${user.name}, ${user.age.toString()}`); } }
printUserInfo(user: User) { console.log(`${user.name}, ${user.age.toString()}`); }
printUserInfo(user: User) { console.log(`${user.name}, ${user.age!.toString()}`); }
[tslint] Forbidden non null assertion (no-non-null-assertion)
using System.ComponentModel; using System.ComponentModel.DataAnnotations; namespace jQueryMvcSample01.Models { public class User { [Required(ErrorMessage = "(*)"), DisplayName("نام")] public string Name { set; get; } public PhoneInfo PhoneInfo { set; get; } } public class PhoneInfo { [Required(ErrorMessage = "(*)"), DisplayName("تلفن")] public string Phone { get; set; } [Required(ErrorMessage = "(*)"), DisplayName("پیش شماره")] public string Ext { get; set; } } }
using System.Web.Mvc; using jQueryMvcSample01.Models; using jQueryMvcSample01.Security; namespace jQueryMvcSample01.Controllers { public class HomeController : Controller { [HttpGet] public ActionResult Index() { return View(); //نمایش فرم } [HttpPost] [AjaxOnly] //فقط در حالت ایجکس قابل دسترسی باشد [ValidateAntiForgeryToken] public ActionResult Index(User user) { if (this.ModelState.IsValid) { // ذخیره سازی در بانک اطلاعاتی ... System.Threading.Thread.Sleep(3000); return Content("ok");//اعلام موفقیت آمیز بودن کار } return Content(null);//ارسال خطا } } }
// <![CDATA[ (function ($) { $.fn.PostMvcFormAjax = function (options) { var defaults = { postUrl: '/', loginUrl: '/login', beforePostHandler: null, completeHandler: null, errorHandler: null }; var options = $.extend(defaults, options); var validateForm = function (form) { //فعال سازی دستی اعتبار سنجی جیکوئری var val = form.validate(); val.form(); return val.valid(); }; return this.each(function () { var form = $(this); //اگر فرم اعتبار سنجی نشده، اطلاعات آن ارسال نشود if (!validateForm(form)) return; //در اینجا میتوان مثلا دکمهای را غیرفعال کرد if (options.beforePostHandler) options.beforePostHandler(this); //اطلاعات نباید کش شوند $.ajaxSetup({ cache: false }); $.ajax({ type: "POST", url: options.postUrl, data: form.serialize(), //تمام فیلدهای فرم منجمله آنتی فرجری توکن آنرا ارسال میکند complete: function (xhr, status) { var data = xhr.responseText; if (xhr.status == 403) { window.location = options.loginUrl; //در حالت لاگین نبودن شخص اجرا میشود } else if (status === 'error' || !data) { if (options.errorHandler) options.errorHandler(this); } else { if (options.completeHandler) options.completeHandler(this); } } }); }); }; })(jQuery); // ]]>
@model jQueryMvcSample01.Models.User @{ ViewBag.Title = "تعریف کاربر"; var postUrl = Url.Action(actionName: "Index", controllerName: "Home"); } @using (Html.BeginForm(actionName: "Index", controllerName: "Home", method: FormMethod.Post, htmlAttributes: new { id = "UserForm" })) { @Html.ValidationSummary(true) @Html.AntiForgeryToken() <fieldset> <legend>تعریف کاربر</legend> <div class="editor-label"> @Html.LabelFor(model => model.Name) </div> <div class="editor-field"> @Html.EditorFor(model => model.Name) @Html.ValidationMessageFor(model => model.Name) </div> <div class="editor-label"> @Html.LabelFor(model => model.PhoneInfo.Ext) </div> <div class="editor-field"> @Html.EditorFor(model => model.PhoneInfo.Ext) @Html.ValidationMessageFor(model => model.PhoneInfo.Ext) </div> <div class="editor-label"> @Html.LabelFor(model => model.PhoneInfo.Phone) </div> <div class="editor-field"> @Html.EditorFor(model => model.PhoneInfo.Phone) @Html.ValidationMessageFor(model => model.PhoneInfo.Phone) </div> <p> <input type="submit" id="btnSave" value="ارسال" /> </p> </fieldset> } @section JavaScript { <script type="text/javascript"> $(document).ready(function () { $("#btnSave").click(function (event) { //جلوگیری از پست بک به سرور event.preventDefault(); var button = $(this); $("#UserForm").PostMvcFormAjax({ postUrl: '@postUrl', loginUrl: '/login', beforePostHandler: function () { //غیرفعال سازی دکمه ارسال button.attr('disabled', 'disabled'); button.val("..."); }, completeHandler: function () { //فعال سازی مجدد دکمه ارسال alert('انجام شد'); button.removeAttr('disabled'); button.val("ارسال"); }, errorHandler: function () { alert('خطایی رخ داده است'); } }); }); }); </script> }
@Html.EditorFor(model => model.PhoneInfo.Phone)
<head> <title>@ViewBag.Title</title> <link href="@Url.Content("Content/Site.css")" rel="stylesheet" type="text/css" /> <script src="@Url.Content("~/Scripts/jquery-1.9.1.min.js")" type="text/javascript"></script> <script src="@Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script> <script src="@Url.Content("~/Scripts/jquery.unobtrusive-ajax.min.js")" type="text/javascript"></script> <script src="@Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script> <script src="@Url.Content("~/Scripts/jquery.PostMvcFormAjax.js")" type="text/javascript"></script> @RenderSection("JavaScript", required: false) </head>
همان طور که میدانید کاربرد پذیری در خیلی از
پروژهها حرف اول رو میزند و کاربر دوست دارد کارهایی که انجام میدهد خیلی راحت
و با استفاده از موس باشد.یکی از کار هایی که در اکثر پروژهها نیاز است ، چیدمان
ترتیب رکوردها است. ما میخواهیم در این پست ترتیبی اتخاذ کنیم که کاربر بتواند رکوردها را به هر ترتیبی که دوست دارد نمایش دهد.
از توضیحاتی که قبلا دادم مشخص است که این کار احتمالا در ASP.NET WebForm کار سختی نیست ولی این کار باید در MVC از ابتدا طراحی شود.
طرح سوال : یک سری رکورد از یک Table داریم که میخواهیم به ترتیب وارد شدن رکوردها نباشد و ترتیبی که ما میخواهیم نمایش داده شود.
پاسخ کوتاه : خب باید ابتدا یک فیلد (برای اولویت بندی) به Table اضافه کنیم بعد اون فیلد رو بنا به ترتیبی که دوست داریم رکوردها نمایش داده شود پر کنیم (Sort می کنیم ) و در آخر هم هنگام نمایش در View رکوردها را بر اساس این فیلد نمایش میدهیم.
(این پست هم در ادامه پست قبلی در همان پروژه است و از همان Table ها استفاده شده است)
اضافه کردن فیلد :
ابتدا یک فیلد به Table مورد نظر اضافه میکنیم. من اسم این فیلد رو Priority گذاشتم. Table من چنین وضعیتی دارد.
افزودن فایلهای jQuery UI :
در این مرحله شما نیاز دارید فایلهای مورد نیاز برای Sort کردن رکوردها را اضافه کنید. شما میتوانید فقط فایلهای مربوط به Sortable را به صفحه خودتان اضافه کنید و یا مثل من فایل هایی که حاوی تمام قسمتهای jQuery UI هست را اضافه کنید.
من برای این کار از Section استفاده کردم ، ابتدا در Head فایل Layout دو Section تعریف کردم برای CSS و JavaScript . و فایلهای مربوط به Sort کردن را در صفحه ای که باید عمل Sort انجام بشود در این Section ها قرار دادم.
فایل Layout
<head> <meta charset="utf-8" /> @RenderSection("meta", false) <title>@ViewBag.Title</title> <link href="@Url.Content("~/Content/~Site.css")" rel="stylesheet" type="text/css" /> <link href="@Url.Content("~/Content/redactor/css/redactor.css")" rel="stylesheet" type="text/css" /> <link href="@Url.Content("~/Content/css/bootstrap-rtl.min.css")" rel="stylesheet" type="text/css" /> @RenderSection("css", false) <script src="@Url.Content("~/Scripts/jquery-1.8.2.min.js")" type="text/javascript"></script> <script src="@Url.Content("~/Scripts/modernizr-1.7.min.js")" type="text/javascript"></script> <script src="@Url.Content("~/Content/js/bootstrap-rtl.js")" type="text/javascript"></script> <script src="@Url.Content("~/Content/redactor/redactor.min.js")" type="text/javascript"></script> <script src="@Url.Content("~/Scripts/jquery.unobtrusive-ajax.min.js")" type="text/javascript"></script> @RenderSection("js", false) </head>
فایل Index.chtml در پوشه کنترلر Type
@model IEnumerable<KhazarCo.Models.Type> @{ ViewBag.Title = "Index"; Layout = "~/Areas/Administrator/Views/Shared/_Layout.cshtml"; } @section css {<link href="@Url.Content("~/Content/themes/base/jquery-ui.css")" rel="stylesheet" type="text/css" /> } @section js { <script src="@Url.Content("~/Scripts/jquery-ui-1.9.0.min.js")" type="text/javascript"></script> }
در آخر فایل Index.chtml به اینصورت شده است:
<h2> نوع ها</h2> <p> @Html.ActionLink("ایجاد یک مورد جدید", "Create", null, new { @class = "btn btn-info" }) </p> <table> <thead> <tr> <th> عنوان </th> <th> توضیحات </th> <th> فعال </th> <th> </th> </tr> </thead> <tbody> @foreach (var item in Model.OrderBy(m => m.Priority)) { <tr id="@item.Id"> <td> @Html.DisplayFor(modelItem => item.Title) </td> <td> @(new HtmlString(item.Description)) </td> <td> @Html.DisplayFor(modelItem => item.IsActive) </td> <td> @Html.ActionLink("ویرایش", "Edit", new { id = item.Id }, new { @class = "btnEdit label label-warning" }) | @Html.ActionLink("مشاهده", "Details", new { id = item.Id }, new { @class = "btnDetails label label-info" }) | @Html.ActionLink("حذف", "Delete", new { id = item.Id }, new { @class = "btnDelete label label-important" }) </td> </tr> } </tbody> </table>
** توجه داشته باشید که من به هر tr یک id اختصاص داده ام که این مقدار id همان مقدار فیلد Id همان رکورد هست ، ما برای مرتب کردن به این Id نیاز داریم (خط 25).
افزودن کدهای کلاینت:
حالا باید کدی بنویسم که دو کار را برای ما انجام دهد : اول حالت Sort پذیری را به سطرهای Table بدهد و دوم اینکه هنگامی که ترتیب سطرهای تغییر کرد ما را با خبر کند:
<script type="text/javascript"> $(function () { $("table tbody").sortable({ helper: fixHelper, update: function (event, ui) { jQuery.ajax('@Url.Action("Sort", "Type", new { area = "Administrator" })', { data: { s: $(this).sortable('toArray').toString() } }); } }).disableSelection(); }); var fixHelper = function (e, ui) { ui.children().each(function () { $(this).width($(this).width()); }); return ui; }; </script>
توضیح کد :
در این کد ما حالت ترتیب پذیری را به Table می دهیم و هنگامی که عمل Update در Table انجام شد تابع مربوطه اجرا میشود. ما در این تایع، ترتیب جدید سطرها را میگیریم ( ** به کمک مقدار Id که به هر سطر دادیم ، این مقدار Id برابر بود با Id خود رکورد در Database ) و به کمکjQuery.ajax به تابع Sort از کنترلر Type در منطقه (area ) Administrator ارسال میکنیم و در آنجا ادامه کار را انجام میدهیم.
تابع fixHelper هم به ما کمک میکند که هنگامی که سطرها از جای خود جدا میشوند ، دارای عرض یکسانی باشند و عرض آنها تغییری نکند.
افزودن کد Server:
حالا باید تابع Sort که مقادیر را به آن ارسال کردیم بنویسم. من این تابع را بر اساس مقداری که از کلاینت ارسال میشود اینگونه طراحی کردم.
public EmptyResult Sort(string s) { if (s != null) { var ids = new List<int>(); foreach (var item in s.Split(',')) { ids.Add(int.Parse(item)); } int intpriority = 0; foreach (var item in ids) { intpriority++; db.Types.Single(m => m.Id == item).Priority = intpriority; } db.SaveChanges(); } return new EmptyResult(); }
در ایتدا مقادیر Id که از کلاینت به صورت String ارسال شده است را میگیریم و بعد به همان ترتیب ارسال در لیستی از int قرار میدهیم ids.
سپس به اضای هر رکورد Type مقدار اولویت را به فیلدی که برای همین مورد اضافه کردیم Priority اختصاص میدهیم. و در آخر هم تغییرات را ذخیره میکنیم. (خود کد کاملا واضح است و نیاری به توضیح بیشتر نیست )
حالا باید هنگامی که لیست Type ها نمایش داده میشود به ترتیب (OrderBy) فیلد Priority نمایش داده شود پس تابع Index را اینطور تغییر میدهیم.
public ViewResult Index() { return View(db.Types.Where(m => m.IsDeleted == false).OrderBy(m => m.Priority)); }
این هم خروجی کار من:
این عکس مربوط به است به قسمت مدیریت پروژه شیرآلات مرجان خزر.
npm update -g
ng new MaterialAngularClient --routing
cd MaterialAngularClient ng serve -o
ng add @angular/material
npm install --save @angular/material @angular/cdk npm install --save @angular/animations npm install --save hammerjs
import "hammerjs";
import { BrowserAnimationsModule } from "@angular/platform-browser/animations"; @NgModule({ imports: [ BrowserModule, BrowserAnimationsModule, AppRoutingModule ] }) export class AppModule { }
import { CommonModule } from "@angular/common"; import { NgModule, Optional, SkipSelf } from "@angular/core"; import { RouterModule } from "@angular/router"; @NgModule({ imports: [CommonModule, RouterModule], exports: [ // components that are used in app.component.ts will be listed here. ], declarations: [ // components that are used in app.component.ts will be listed here. ], providers: [ /* ``No`` global singleton services of the whole app should be listed here anymore! Since they'll be already provided in AppModule using the `tree-shakable providers` of Angular 6.x+ (providedIn: 'root'). This new feature allows cleaning up the providers section from the CoreModule. But if you want to provide something with an InjectionToken other that its class, you still have to use this section. */ ] }) export class CoreModule { constructor(@Optional() @SkipSelf() core: CoreModule) { if (core) { throw new Error("CoreModule should be imported ONLY in AppModule."); } } }
import { CommonModule } from "@angular/common"; import { HttpClientModule } from "@angular/common/http"; import { ModuleWithProviders, NgModule } from "@angular/core"; import { FormsModule } from "@angular/forms"; @NgModule({ imports: [ CommonModule, FormsModule, HttpClientModule ], entryComponents: [ // All components about to be loaded "dynamically" need to be declared in the entryComponents section. ], declarations: [ // common and shared components/directives/pipes between more than one module and components will be listed here. ], exports: [ // common and shared components/directives/pipes between more than one module and components will be listed here. CommonModule, FormsModule, HttpClientModule, ] /* No providers here! Since they’ll be already provided in AppModule. */ }) export class SharedModule { static forRoot(): ModuleWithProviders { // Forcing the whole app to use the returned providers from the AppModule only. return { ngModule: SharedModule, providers: [ /* All of your services here. It will hold the services needed by `itself`. */] }; } }
import { CoreModule } from "./core/core.module"; import { SharedModule } from "./shared/shared.module"; @NgModule({ imports: [ BrowserModule, BrowserAnimationsModule, CoreModule, SharedModule.forRoot(), AppRoutingModule ] }) export class AppModule { }
import { CdkTableModule } from "@angular/cdk/table"; import { NgModule } from "@angular/core"; import { MatAutocompleteModule, MatButtonModule, MatButtonToggleModule, MatCardModule, MatCheckboxModule, MatChipsModule, MatDatepickerModule, MatDialogModule, MatExpansionModule, MatFormFieldModule, MatGridListModule, MatIconModule, MatInputModule, MatListModule, MatMenuModule, MatNativeDateModule, MatPaginatorModule, MatProgressBarModule, MatProgressSpinnerModule, MatRadioModule, MatRippleModule, MatSelectModule, MatSidenavModule, MatSliderModule, MatSlideToggleModule, MatSnackBarModule, MatSortModule, MatStepperModule, MatTableModule, MatTabsModule, MatToolbarModule, MatTooltipModule, } from "@angular/material"; @NgModule({ imports: [ MatAutocompleteModule, MatButtonModule, MatButtonToggleModule, MatCardModule, MatCheckboxModule, MatChipsModule, MatDatepickerModule, MatDialogModule, MatExpansionModule, MatFormFieldModule, MatGridListModule, MatIconModule, MatInputModule, MatListModule, MatMenuModule, MatNativeDateModule, MatPaginatorModule, MatProgressBarModule, MatProgressSpinnerModule, MatRadioModule, MatRippleModule, MatSelectModule, MatSidenavModule, MatSliderModule, MatSlideToggleModule, MatSnackBarModule, MatStepperModule, MatSortModule, MatTableModule, MatTabsModule, MatToolbarModule, MatTooltipModule, CdkTableModule ], exports: [ MatAutocompleteModule, MatButtonModule, MatButtonToggleModule, MatCardModule, MatCheckboxModule, MatChipsModule, MatDatepickerModule, MatDialogModule, MatExpansionModule, MatGridListModule, MatIconModule, MatInputModule, MatListModule, MatMenuModule, MatNativeDateModule, MatPaginatorModule, MatProgressBarModule, MatProgressSpinnerModule, MatRadioModule, MatRippleModule, MatSelectModule, MatSidenavModule, MatSliderModule, MatSlideToggleModule, MatSnackBarModule, MatStepperModule, MatSortModule, MatTableModule, MatTabsModule, MatToolbarModule, MatTooltipModule, CdkTableModule ] }) export class MaterialModule { }
import { MaterialModule } from "./material.module"; @NgModule({ imports: [ CommonModule, FormsModule, HttpClientModule, MaterialModule ], exports: [ // common and shared components/directives/pipes between more than one module and components will be listed here. CommonModule, FormsModule, HttpClientModule, MaterialModule ] }) export class SharedModule { }
<button mat-button>Click me!</button>
<mat-checkbox>Check me!</mat-checkbox>
@import "~@angular/material/prebuilt-themes/indigo-pink.css";
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
<button mat-button> <mat-icon>face</mat-icon> Click me! </button> <mat-checkbox>Check me!</mat-checkbox>
npm install material-design-icons --save
"styles": [ "node_modules/material-design-icons/iconfont/material-icons.css", "src/styles.css" ],
<Employees>
<Employee>
<Name>Vahid</Name>
<Phone>11111111</Phone>
<Department>IT</Department>
<Age>52</Age>
</Employee>
<Employee>
<Name>Farid</Name>
<Phone>124578963</Phone>
<Department>Civil</Department>
<Age>35</Age>
</Employee>
<Employee>
<Name>Mehdi</Name>
<Phone>1245788754</Phone>
<Department>HR</Department>
<Age>30</Age>
</Employee>
</Employees>
using System.Xml.Linq;
XDocument xDoc = XDocument.Load("Employee.xml");
// loading XML from string
StringReader sr = new StringReader(stringXML);
XDocument xDoc = XDocument.Load(sr);
using System.Collections;
IEnumerable<XElement> empList = from e in xDoc.Root.Elements("Employee") select e;
foreach (XElement employee in empList)
{
foreach (XElement e in employee.Elements())
{
Console.WriteLine(e.Name + " = " + e.Value);
}
}
IEnumerable<XElement> hrList = from e in xDoc.Root.Elements("Employee")
where e.Element("Department").Value == "HR"
select e;
IEnumerable<XElement> tList = from e in xDoc.Root.Elements("Employee")
where int.Parse(e.Element("Age").Value) > 30
select e;
IEnumerable<XElement> tList = from e in xDoc.Root.Elements("Employee")
where int.Parse(e.Element("Age").Value) > 30
orderby e.Element("Name").Value
select e;
var empList = from e in xDoc.Root.Elements("Employee")
orderby e.Element("Name").Value
select new
{
Name = e.Element("Name").Value,
Phone = e.Element("Phone").Value,
Department = e.Element("Department").Value,
Age = int.Parse(e.Element("Age").Value)
};
foreach (var employee in empList)
{
Console.WriteLine("Name = " + employee.Name);
Console.WriteLine("Dep = " + employee.Department);
Console.WriteLine("Phone = " + employee.Phone);
Console.WriteLine("Age = " + employee.Age);
}
public class Employee
{
public string Name { get; set; }
public string Phone { get; set; }
public string Department { get; set; }
public int Age { get; set; }
}
public class Employee
{
public string Name { get; set; }
public string Phone { get; set; }
public string Department { get; set; }
public int Age { get; set; }
}
List<Employee> Get()
{
XDocument xDoc = XDocument.Load("Employee.xml");
var items =
from e in xDoc.Root.Elements("Employee")
orderby e.Element("Name").Value
select new Employee
{
Name = e.Element("Name").Value,
Phone = e.Element("Phone").Value,
Department = e.Element("Department").Value,
Age = int.Parse(e.Element("Age").Value)
};
return items.ToList();
}