<script src="/Asset/Scripts/js?v="></script> <script src="/Asset/Scripts/JQuery/Zebra/js?v="></script> <script src="/Asset/Scripts/Angular/Asset/js?v="></script>
کلاسهای CSS اعتبارسنجی در Angular
زمانیکه Angular فرمی را تحت نظر قرار میدهد، کلاسهای CSS خاصی را نیز بر اساس حالات عناصر مختلف آن، به آنها متصل خواهد کرد. بر این اساس میتوان ظاهر این المانها را سفارشی سازی نمود. این کلاسها به شرح زیر هستند:
کلاس CSS اعتبارسنجی | توضیحات |
ng-untouched | زمانیکه فرمی برای بار اول رندر میشود، تمام فیلدهای آن با کلاس CSS ایی به نام ng-untouched علامتگذاری میشوند. |
ng-touched | همینقدر که کاربر با یک Tab از فیلدی عبور کند، با کلاس ng-touched مزین خواهد شد. بنابراین مهم نیست که حتما دادهای وارد شده باشد یا خیر. حتی عبور از یک فیلد نیز به معنای لمس آن نیز میباشد. |
ng-pristine | مربوط به زمانیاست که یک فیلد نه تغییر کردهاست و نه لمس شدهاست. |
ng-dirty | همینقدر که کاربر، تغییری را در فیلدی ایجاد کند، آن المان با کلاس ng-dirty مشخص خواهد شد. |
ng-valid | برای حالت موفقیت آمیز بودن اعتبارسنجی، به آن المان انتساب داده میشود. |
ng-invalid | برای حالت غیر موفقیت آمیز بودن اعتبارسنجی، به آن المان انتساب داده میشود. |
برای اینکه بتوانیم این موارد را در عمل مشاهده کنیم، به ابتدای فرم مثال این سری، تغییرات ذیل را اعمال خواهیم کرد:
<div class="form-group"> <label>First Name</label> <input #firstName required type="text" class="form-control" name="firstName" [(ngModel)]="model.firstName"> </div> <h3>Classes</h3> <h4>{{ firstName.className }}</h4>
تصویر فوق کلاسهایی را نمایش میدهد که در اولین بار نمایش فرم، به المان firstName متصل شدهاند. برای مثال در این حالت کلاس ng-pristine قابل مشاهدهاست و هنوز تغییری در آن حاصل نشدهاست.
در ادامه اگر حرفی را به آن اضافه کنیم:
هنوز هم ng-untouched آن برقرار است؛ اما ng-pristine آن به ng-dirty تبدیل شدهاست. در اینجا حتی اگر کل اطلاعات فیلد را نیز حذف کنیم و آنرا خالی کنیم یا به حالت اول بازگردانیم نیز کلاس ng-dirty قابل مشاهدهاست. بنابراین اگر حالت فیلدی dirty شد، همواره به همین حالت باقی میماند.
در این لحظه اگر با Tab به فیلد دیگری در فرم مراجعه کنیم:
در اینجا است که کلاس ng-untouched به ng-touched تبدیل میشود. بنابراین کلاسهای مختلف لمس یک فیلد، ارتباطی به افزوده شدن یا حذف کاراکتری از یک فیلد ندارند و فقط به از دست رفتن focus و مراجعهی به فیلد دیگری مرتبط میشوند.
اگر به المان تغییر یافتهی فوق دقت کنید، ویژگی required نیز به آن اضافه شدهاست (علاوه بر template reference variable ایی که تعریف کردیم). در این حالت کل فیلد را خالی کنید:
همانطور که مشاهده میکنید، اکنون کلاس ng-valid به کلاس ng-invalid تغییر یافتهاست.
ارتباط بین کلاسهای CSS اعتبارسنجی و خواص ngModel
تمام کلاسهای -ng ایی که در بالا معرفی شدند، معادلهای خواص ngModel ایی نیز دارند. فقط کافی است -ng آنها را حذف کنید، باقیماندهی آن، نام خاصیت متناظری در ngModel خواهد بود. برای مثال کلاس ng-untouched به خاصیت untouched نگاشت میشود و به همین ترتیب برای مابقی.
template reference variable ایی را که تا به اینجا به المان اضافه کردیم (firstName#) به خواص همان المان دسترسی دارد (مانند className آن). اما در ادامه میخواهیم این متغیر به ngModel و خواص آن دسترسی داشته باشد و میدان دید آن تغییر کند. به همین جهت تنها کافی است تا ngModel را به این متغیر انتساب دهیم:
<div class="form-group"> <label>First Name</label> <input #firstName="ngModel" required type="text" class="form-control" name="firstName" [(ngModel)]="model.firstName"> </div> <h4>dirty: {{ firstName.dirty }}</h4>
یک نکته: در حالت خواص valid و invalid که مرتبط با اعتبارسنجی هستند، خاصیت سومی نیز به نام errors وجود دارد که حاوی اطلاعات بیشتری در مورد خطای اعتبارسنجی رخ دادهاست. بنابراین وجود این خاصیت و نال نبودن آن نیز دلالت بر وجود یک خطای اعتبارسنجی است. از خاصیت errors در ادامهی بحث در قسمت «مدیریت چندین خطای همزمان اعتبارسنجی» استفاده خواهیم کرد.
نمایش بهتر خطاهای اعتبارسنجی با بررسی خواص ngModel
یکی از مزایای کار با خواص ngModel، امکان استفادهی از آنها در عبارات شرطیاست که نسبت به کلاسهای CSS معرفی شدهی در ابتدای بحث، انعطاف پذیری بیشتری را به همراه خواهند داشت.
<div class="form-group"> <label>First Name</label> <input #firstName="ngModel" #firstNameElement required type="text" class="form-control" name="firstName" [(ngModel)]="model.firstName"> <div *ngIf="firstName.invalid && firstName.touched" class="alert alert-danger"> First Name is required. </div> </div> <h4>className: {{ firstNameElement.className }}</h4> <h4>dirty: {{ firstName.dirty }}</h4> <h4>invalid: {{ firstName.invalid }}</h4>
نمایش بهتر خطاهای اعتبارسنجی با مزین ساختن المانهای ورودی
علاوه بر نمایش یک alert بوت استرپی متناظر با یک فیلد غیرمعتبر، میتوان خود المانهای ورودی را نیز با شیوهنامههایی مزین ساخت.
این کار را در بوت استرپ با افزودن کلاس has-error در کنار form-group انجام میدهند. همچنین label نیز باید به کلاس control-label مزین شود تا hass-error بر روی آن نیز تاثیرگذار شود. برای پیاده سازی پویای آن در Angular به روش ذیل عمل میشود:
<div class="form-group" [class.has-error]="firstName.invalid && firstName.touched"> <label class="control-label">First Name</label>
بنابراین اگر المان firstName خالی باشد و همچنین با یک Tab از روی آن عبور کرده باشیم، کلاس has-error در کنار کلاس form-group اضافه میشود.
روش دوم: همانطور که در ابتدای بحث نیز عنوان شد، Angular بر اساس حالات مختلف یک فیلد، کلاسهای CSS خاصی را به آنها انتساب میدهد. یک چنین کاری را با مقدار دهی این کلاسها در فایل src\styles.css نیز میتوان انجام داد که دقیقا معادل بررسی خواص invalid و touched با کدنویسی است:
.ng-touched.ng-invalid{ border: 1px solid red; }
سایر ویژگیهای اعتبارسنجی HTML 5
تا اینجا ویژگی استاندارد required را به المان ورودی فرم ثبت اطلاعات کاربران، اضافه کردیم.
<input #firstName="ngModel" #firstNameElement required maxlength="3" minlength="2" pattern="^V.*" type="text" class="form-control" name="firstName" [(ngModel)]="model.firstName">
برای نمونه minlength همهجا پشتیبانی نمیشود؛ اما آنرا میتوان برای مثال با الگویی مساوی "+..." جایگزین کرد.
مشکل! ذکر چند ویژگی اعتبارسنجی با هم، تداخل ایجاد میکنند!
در اینجا چون چهار ویژگی مختلف را به صورت یکجا به یک المان متصل کردهایم، اکنون div ذیل به هر کدام از این ویژگیها به صورت یکسانی واکنش نشان خواهد داد؛ زیرا خاصیت invalid را true میکنند:
<div *ngIf="firstName.invalid && firstName.touched" class="alert alert-danger"> First Name is required. </div>
<div class="form-group" [class.has-error]="firstName.invalid && firstName.touched"> <label class="control-label">First Name</label> <input #firstName="ngModel" #firstNameElement required maxlength="3" minlength="2" pattern="^V.*" type="text" class="form-control" name="firstName" [(ngModel)]="model.firstName"> <div *ngIf="firstName.invalid && firstName.touched"> <div class="alert alert-info"> errors: {{ firstName.errors | json }} </div> <div class="alert alert-danger" *ngIf="firstName.errors.required"> Name is required. </div> <div class="alert alert-danger" *ngIf="firstName.errors.minlength"> Name should be minimum {{firstName.errors.minlength.requiredLength}} characters. </div> <div class="alert alert-danger" *ngIf="firstName.errors.maxlength"> Name should be max {{firstName.errors.maxlength.requiredLength}} characters. </div> <div class="alert alert-danger" *ngIf="firstName.errors.pattern"> Name pattern: {{firstName.errors.pattern.requiredPattern}} </div> </div> </div>
همانطور که در تصویر ملاحظه میکنید، محتوای خاصیت errors به صورت JSON، جهت دیباگ نیز درج شدهاست.
بنابراین وجود خاصیت firstName.errors.minlength و یا firstName.errors.pattern به این معنا است که این خطاهای خاص وجود دارند (خاصیت firstName.errors به همراه اضافاتی است) و برعکس نال بودن آنها مؤید عدم وجود خطایی است. به همین جهت میتوان به ازای هر کدام، یک div جداگانه را تشکیل داد.
مرحلهی بعد، استخراج اطلاعات بیشتری از همین شیء و محتوای خاصیت errors است. برای مثال زمانیکه در آن خاصیت minlength ظاهر شد، این خاصیت نیز دارای خاصیتی مانند requiredLength است که از آن میتوان جهت درج عدد واقعی مورد نیاز این اعتبارسنج استفاده کرد.
بهبود اعتبارسنجی drop down
در حالت فعلی تعریف drop down مثال این سری، نیازی به اعتبارسنجی نیست؛ چون لیست مشخصی از طریق کامپوننت در اختیار این المان قرار میگیرد و همواره دقیقا یکی از این عناصر انتخاب خواهند شد. اما اگر گزینهی دیگری را مانند:
<option value="default">Select a Language...</option>
<div class="form-group" [class.has-error]="hasPrimaryLanguageError"> <label class="control-label">Primary Language</label> <select class="form-control" name="primaryLanguage" #primaryLanguage (blur)="validatePrimaryLanguage(primaryLanguage.value)" (change)="validatePrimaryLanguage(primaryLanguage.value)" [(ngModel)]="model.primaryLanguage"> <option value="default">Select a Language...</option> <option *ngFor="let lang of languages"> {{ lang }} </option> </select> </div>
export class EmployeeRegisterComponent { hasPrimaryLanguageError = false; validatePrimaryLanguage(value) { if (value === 'default'){ this.hasPrimaryLanguageError = true; } else{ this.hasPrimaryLanguageError = false; } } }
اعتبارسنجی در سطح کل فرم
تا اینجا بررسیهایی را که انجام دادیم، در سطح فیلدها بودند. اکنون اگر کاربر به طور کامل تمام این فیلدها را تغییر ندهد و بر روی دکمهی ارسال کلیک کند چطور؟
<form #form="ngForm" novalidate>
<h3> form.valid: {{ form.valid }}</h3>
<button class="btn btn-primary" type="submit" [disabled]="form.invalid">Ok</button>
در قسمت بعد نحوهی ارسال این فرم را به سرور، بررسی میکنیم.
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید: angular-template-driven-forms-lab-04.zip
برای اجرای آن فرض بر این است که پیشتر Angular CLI را نصب کردهاید. سپس از طریق خط فرمان به ریشهی پروژه وارد شده و دستور npm install را صادر کنید تا وابستگیهای آن دریافت و نصب شوند. در آخر با اجرای دستور ng serve -o برنامه ساخته شده و در مرورگر پیش فرض سیستم نمایش داده خواهد شد.
خلاصه مهاجرت داده پروفایل ها
- کلاس جدیدی بسازید که دارای خواصی برای ذخیره اطلاعات پروفایل است.
- کلاس جدیدی بسازید که از 'ProfileBase' ارث بری میکند و متدهای لازم برای دریافت پروفایل کاربران را پیاده سازی میکند.
- استفاده از تامین کنندههای پیش فرض را، در فایل web.config فعال کنید. و کلاسی که در مرحله 2 ساختید را بعنوان کلاس پیش فرض برای خواندن اطلاعات پروفایل معرفی کنید.
شروع به کار
پوشه جدیدی با نام 'Models' بسازید تا اطلاعات پروفایل را در آن قرار دهیم.
بعنوان یک مثال، بگذارید تا تاریخ تولد کاربر، شهر سکونت، قد و وزن او را در پروفایلش ذخیره کنیم. قد و وزن بصورت یک کلاس سفارشی (custom class) بنام 'PersonalStats' ذخیره میشوند. برای ذخیره و بازیابی پروفایل ها، به کلاسی احتیاج داریم که 'ProfileBase' را ارث بری میکند. پس کلاس جدیدی با نام 'AppProfile' بسازید.
public class ProfileInfo { public ProfileInfo() { UserStats = new PersonalStats(); } public DateTime? DateOfBirth { get; set; } public PersonalStats UserStats { get; set; } public string City { get; set; } } public class PersonalStats { public int? Weight { get; set; } public int? Height { get; set; } } public class AppProfile : ProfileBase { public ProfileInfo ProfileInfo { get { return (ProfileInfo)GetPropertyValue("ProfileInfo"); } } public static AppProfile GetProfile() { return (AppProfile)HttpContext.Current.Profile; } public static AppProfile GetProfile(string userName) { return (AppProfile)Create(userName); } }
پروفایل را در فایل web.config خود فعال کنید. نام کلاسی را که در مرحله قبل ساختید، بعنوان کلاس پیش فرض برای ذخیره و بازیابی پروفایلها معرفی کنید.
<profile defaultProvider="DefaultProfileProvider" enabled="true" inherits="UniversalProviders_ProfileMigrations.Models.AppProfile"> <providers> ..... </providers> </profile>
برای دریافت اطلاعات پروفایل از کاربر، فرم وب جدیدی در پوشه Account بسازید و آنرا 'AddProfileData.aspx' نامگذاری کنید.
<h2> Add Profile Data for <%# User.Identity.Name %></h2> <asp:Label Text="" ID="Result" runat="server" /> <div> Date of Birth: <asp:TextBox runat="server" ID="DateOfBirth"/> </div> <div> Weight: <asp:TextBox runat="server" ID="Weight"/> </div> <div> Height: <asp:TextBox runat="server" ID="Height"/> </div> <div> City: <asp:TextBox runat="server" ID="City"/> </div> <div> <asp:Button Text="Add Profile" ID="Add" OnClick="Add_Click" runat="server" /> </div>
کد زیر را هم به فایل code-behind اضافه کنید.
protected void Add_Click(object sender, EventArgs e) { AppProfile profile = AppProfile.GetProfile(User.Identity.Name); profile.ProfileInfo.DateOfBirth = DateTime.Parse(DateOfBirth.Text); profile.ProfileInfo.UserStats.Weight = Int32.Parse(Weight.Text); profile.ProfileInfo.UserStats.Height = Int32.Parse(Height.Text); profile.ProfileInfo.City = City.Text; profile.Save(); }
دقت کنید که فضای نامی که کلاس AppProfile در آن قرار دارد را وارد کرده باشید.
اپلیکیشن را اجرا کنید و کاربر جدیدی با نام 'olduser' بسازید. به صفحه جدید 'AddProfileData' بروید و اطلاعات پروفایل کاربر را وارد کنید.
با استفاده از پنجره Server Explorer میتوانید تایید کنید که اطلاعات پروفایل با فرمت xml در جدول 'Profiles' ذخیره میشوند.
مهاجرت الگوی دیتابیس
اسکریپت مورد نیاز را از آدرس https://raw.github.com/suhasj/UniversalProviders-Identity-Migrations/master/Migration.txt دریافت کرده و آن را اجرا کنید. اگر اتصال خود به دیتابیس را تازه کنید خواهید دید که جداول جدیدی اضافه شده اند. میتوانید دادههای این جداول را بررسی کنید تا ببینید چگونه اطلاعات منتقل شده اند.
مهاجرت اپلیکیشن برای استفاده از ASP.NET Identity
- Microsoft.AspNet.Identity.EntityFramework
- Microsoft.AspNet.Identity.Owin
- Microsoft.Owin.Host.SystemWeb
- Microsoft.Owin.Security.Facebook
- Microsoft.Owin.Security.Google
- Microsoft.Owin.Security.MicrosoftAccount
- Microsoft.Owin.Security.Twitter
using Microsoft.AspNet.Identity.EntityFramework; using System; using System.Collections.Generic; using System.Linq; using System.Web; using UniversalProviders_ProfileMigrations.Models; namespace UniversalProviders_Identity_Migrations { public class User : IdentityUser { public User() { CreateDate = DateTime.UtcNow; IsApproved = false; LastLoginDate = DateTime.UtcNow; LastActivityDate = DateTime.UtcNow; LastPasswordChangedDate = DateTime.UtcNow; Profile = new ProfileInfo(); } public System.Guid ApplicationId { get; set; } public bool IsAnonymous { get; set; } public System.DateTime? LastActivityDate { get; set; } public string Email { 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; } public ProfileInfo Profile { get; set; } } }
انتقال داده پروفایلها به جداول جدید
آخرین نسخه پکیج Entity Framework را نصب کنید. همچنین یک رفرنس به اپلیکیشن وب پروژه بدهید (کلیک راست روی پروژه و گزینه 'Add Reference').
کد زیر را در کلاس Program.cs وارد کنید. این قطعه کد پروفایل تک تک کاربران را میخواند و در قالب 'ProfileInfo' آنها را serialize میکند و در دیتابیس ذخیره میکند.
public class Program { var dbContext = new ApplicationDbContext(); foreach (var profile in dbContext.Profiles) { var stringId = profile.UserId.ToString(); var user = dbContext.Users.Where(x => x.Id == stringId).FirstOrDefault(); Console.WriteLine("Adding Profile for user:" + user.UserName); var serializer = new XmlSerializer(typeof(ProfileInfo)); var stringReader = new StringReader(profile.PropertyValueStrings); var profileData = serializer.Deserialize(stringReader) as ProfileInfo; if (profileData == null) { Console.WriteLine("Profile data deserialization error for user:" + user.UserName); } else { user.Profile = profileData; } } dbContext.SaveChanges(); }
برخی از مدلهای استفاده شده در پوشه 'IdentityModels' تعریف شده اند که در پروژه اپلیکیشن وبمان قرار دارند، بنابراین افزودن فضاهای نام مورد نیاز فراموش نشود.
کد بالا روی دیتابیسی که در پوشه App_Data وجود دارد کار میکند، این دیتابیس در مراحل قبلی در اپلیکیشن وب پروژه ایجاد شد. برای اینکه این دیتابیس را رفرنس کنیم باید رشته اتصال فایل app.config اپلیکیشن کنسول را بروز رسانی کنید. از همان رشته اتصال web.config در اپلیکیشن وب پروژه استفاده کنید. همچنین آدرس فیزیکی کامل را در خاصیت 'AttachDbFilename' وارد کنید.
یک Command Prompt باز کنید و به پوشه bin اپلیکیشن کنسول بالا بروید. فایل اجرایی را اجرا کنید و نتیجه را مانند تصویر زیر بررسی کنید.
در پنجره Server Explorer جدول 'AspNetUsers' را باز کنید. حال ستونهای این جدول باید خواص کلاس مدل را منعکس کنند.
کارایی سیستم را تایید کنید
الف) استفاده از امکانات Serialization توکار دات نت
using System.IO; using System.Xml; using System.Xml.Serialization; namespace DNTViewer.Common.Toolkit { public static class Serializer { public static string Serialize<T>(T type) { var serializer = new XmlSerializer(type.GetType()); using (var stream = new MemoryStream()) { serializer.Serialize(stream, type); stream.Seek(0, SeekOrigin.Begin); using (var reader = new StreamReader(stream)) { return reader.ReadToEnd(); } } } } }
امکانات سفارشی سازی محدودی نیز برای XmlSerializer درنظر گرفته شده است؛ برای نمونه قرار دادن ویژگیهایی مانند XmlIgnore بالای خواصی که نیازی به حضور آنها در خروجی نهایی XML نمیباشد.
ب) استفاده از امکانات LINQ to XML دات نت
روش فوق بدون مشکل کار میکند، اما اگر بخواهیم قسمت Reflection خودکار ثانویه آنرا (برای نمونه جهت استخراج مقادیر از لیست دریافتی) حذف کنیم، میتوان از LINQ to XML استفاده کرد که قابلیت سفارشی سازی بیشتری را نیز در اختیار ما قرار میدهد (کاری که در سایت جاری برای تهیه خروجی XML از بانک اطلاعاتی آن انجام میشود).
private string createXmlFile(string dir) { var xLinq = new XElement("ArrayOfPost", _blogPosts .AsNoTracking() .Include(x => x.Comments) .Include(x => x.User) .Include(x => x.Tags) .OrderBy(x => x.Id) .ToList() .Select(x => new XElement("Post", postXElement(x))) ); var xmlFile = Path.Combine(dir, "dot-net-tips-database.xml"); xLinq.Save(xmlFile); return xmlFile; } private static XElement[] postXElement(BlogPost x) { return new XElement[] { new XElement("Id", x.Id), new XElement("Title", x.Title), new XElement("Body", x.Body), new XElement("CreatedOn", x.CreatedOn), tagElement(x), new XElement("User", new XElement("Id", x.UserId.Value), new XElement("FriendlyName", x.User.FriendlyName)) }.Where(item => item != null).ToArray(); } private static XElement tagElement(BlogPost x) { var tags = x.Tags.Any() ? x.Tags.Select(y => new XElement("Tag", new XElement("Id", y.Id), new XElement("Name", y.Name))) .ToArray() : null; if (tags == null) return null; return new XElement("Tags", tags); }
1) کار با یک new XElement که دارای متد Save با فرمت XML نیز هست، شروع میشود. مقدار آنرا مساوی یک کوئری از بانک اطلاعاتی قرار میدهیم. این کوئری چون قرار است تنها اطلاعاتی را از بانک اطلاعاتی دریافت کند و نیازی به تغییر در آنها نیست، با استفاده از متد AsNoTracking، حالت فقط خواندنی پیدا کرده است.
2) اطلاعاتی را که نیاز است در فایل نهایی XML وجود داشته باشند، تنها کافی است در قسمت Select این کوئری با فرمت new XElementهای تو در تو قرار دهیم. به این ترتیب قسمت Relection خودکار XmlSerializer روش مطرح شده در ابتدای بحث دیگر وجود نداشته و عملیات نهایی بسیار سریعتر خواهد بود.
3) چون در این حالت، کار انجام شده دستی است، باید نامهای گرههای صحیحی را انتخاب کنیم تا اگر قرار است توسط همان XmlSerializer مجددا کار serializer.Deserialize صورت گیرد، عملیات با شکست مواجه نشود. بهترین کار برای کم شدن سعی و خطاها، تهیه یک لیست اطلاعات آزمایشی و سپس ارسال آن به روش ابتدای بحث است. سپس میتوان با بررسی خروجی آن مثلا دریافت که روش serializer.Deserialize به صورت پیش فرض به دنبال ریشهای به نام ArrayOfPost برای دریافت لیستی از مطالب میگردد و نه Posts یا هر نام دیگری.
4) در کوئری LINQ to Entites نوشته شده، پیش از Select، یک ToList قرار دارد. متاسفانه EF اجازه استفاده مستقیم از Select هایی از نوع XElement را نمیدهد و باید ابتدا اطلاعات را تبدیل به LINQ to Objects کرد.
5) در حین تهیه XElementها اگر قرار است عنصری نال باشد، باید آنرا در خروجی نهایی ذکر نکرد. به این ترتیب serializer.Deserialize بدون نیاز به تنظیمات اضافهتری بدون مشکل کار خواهد کرد. در غیراینصورت باید وارد مباحثی مانند تعریف یک فضای نام جدید برای خروجی XML به نام XSI رفت و سپس به کمک ویژگیها، xsi:nil را به true مقدار دهی کرد. اما همانطور که در متد postXElement ملاحظه میکنید، برای وارد نشدن به مبحث فضای نام xsi، مواردی که null بودهاند، اصلا در آرایه نهایی ظاهر نمیشوند و نهایتا در خروجی، حضور نخواهند داشت. به این ترتیب متد ذیل، بدون مشکل و بدون نیاز به تنظیمات اضافهتری قادر است فایل XML نهایی را تبدیل به معادل اشیاء دات نتی آن کند.
using System.IO; using System.Xml; using System.Xml.Serialization; namespace DNTViewer.Common.Toolkit { public static class Serializer { public static T DeserializePath<T>(string xmlAddress) { using (var xmlReader = new XmlTextReader(xmlAddress)) { var serializer = new XmlSerializer(typeof(T)); return (T)serializer.Deserialize(xmlReader); } } } }
معرفی Blazor Hybrid
Blazor Hybrid یا همون NET MAUI Blazor App راهکار Blazor هست که HTML/CSS برای UI استفاده میشه، ولی C# .NET اش دسترسی کامل به سیستم عامل داره (بر خلاف Blazor Web Assembly که محدود به Sandbox مرورگر هست)
یا Blazor Native شما دیگه UI تون HTML/CSS نیست و برای داشتن TextBox به جای input type=text، از Entry استفاده میکنید برای مثال که پشت صحنه مپ میشه به کنترلهای Native در Android / iOS / Windows
توصیه من این هست که Blazor رو به صورت Multi Mode تنظیم کنید، به صورتی که UI رو با HTML / CSS بزنید، و هم خروجی Android بگیرید و هم iOS و Web و ویندوز
برای درک بهتر این مسئله میتونید وبینارم رو در رابطه با what's new in dotnet 6 ببینید
public class User { public string UserName { get; set; } public bool IsRole { get; set; } public HashSet<string> ConnectionIds { get; set; } } [Authorize] [HubName("userActivityHub")] public class UserActivityHub : Hub { private static readonly ConcurrentDictionary<string, User> Users = new ConcurrentDictionary<string, User>(); public void AdminJoin() { Groups.Add(Context.ConnectionId, "admins"); } public void Join() { var userName = Context.User.Identity.Name; var connectionId = Context.ConnectionId; var isAdmin = Context.User.IsInRole("Admin"); var user = Users.GetOrAdd(userName, _ => new User { UserName = userName, IsRole = isAdmin, ConnectionIds = new HashSet<string>() }); if (user.IsRole == true) { Groups.Add(user.ConnectionIds.ToString(), "admins"); } else { lock (user.ConnectionIds) { user.ConnectionIds.Add(connectionId); } Clients.Group("admins").showUserCount(Users.Count(a => a.Value.IsRole != true)); } } public void GetUserCount() { Clients.Group("admins").showUserCount(Users.Count(a => a.Value.IsRole != true)); } public override System.Threading.Tasks.Task OnDisconnected(bool stopCalled) { if (stopCalled) { var userName = Context.User.Identity.Name; var connectionId = Context.ConnectionId; User user; Users.TryGetValue(userName, out user); if (user != null) { lock (user.ConnectionIds) { user.ConnectionIds.RemoveWhere(cid => cid.Equals(connectionId)); if (!user.ConnectionIds.Any()) { User removeUser; Users.TryRemove(userName, out removeUser); } } } return Clients.Group("admins").showUserCount(Users.Count(a => a.Value.IsRole != true)); } else { return base.OnDisconnected(false); } } }
<script type="text/javascript"> var userHub = $.connection.userActivityHub; $.connection.hub.logging = true; $.connection.hub.start().done(function() { userHub.server.join(); }); $(function() { window.onbeforeunload = function() { $.connection.hub.stop(); }; }); </script>
<script type="text/javascript"> var userHub = $.connection.userActivityHub; userHub.client.showUserCount = function (message) { $('#userOnlineCount').html(message); }; $.connection.hub.start().done(function() { userHub.server.adminJoin().done(function() { userHub.server.getUserCount(); }); }); </script>
<script> $('a.btn.btn-danger.btn-block').click(function(e) { e.preventDefault(); $('#logoutForm').submit(); $.connection.userActivityHub.connection.stop(); }); $(function() { window.onbeforeunload = function(e) { $.connection.hub.stop(); }; }); </script>
(function () { var itemCtx = {}; itemCtx.Templates = {}; itemCtx.Templates.Header = "<div><b title=\"اطلاعات فیلم ها\">Movie Data</b></div><ul>"; itemCtx.Templates.Item = MyOverrideTemplate; itemCtx.Templates.Footer = "</ul>"; itemCtx.BaseViewID = 1; itemCtx.ListTemplateType = 100; //For Generic List (More : http://msdn.microsoft.com/en-us/library/ms462947(v=office.12).aspx) SPClientTemplates.TemplateManager.RegisterTemplateOverrides(itemCtx); })(); function GT(val , index) { // example of val : 60 % var temp = val.split(' ')[0]; var v = Number(temp); return v > index; } function LT(val, index) { var temp = val.split(' ')[0]; var v = Number(temp); return v < index; } function EQ(val, index) { var temp = val.split(' ')[0]; var v = Number(temp); return v == index; } function MyOverrideTemplate(ctx) { if (LT(ctx.CurrentItem.PopularityPercent ,25)) { return "<li title='خیلی کم بازدید' style='color:white;background-color: red;width: 300px;height: 24px;'>" + ctx.CurrentItem.Title + " – " + ctx.CurrentItem.PopularityPercent + "</li>"; } else if (LT(ctx.CurrentItem.PopularityPercent ,50)) { return "<li title='کم بازدید' style='color:maroon;background-color: #ffcc00;width: 300px;height: 24px;'>" + ctx.CurrentItem.Title + " – " + ctx.CurrentItem.PopularityPercent + "</li>"; } else if (LT(ctx.CurrentItem.PopularityPercent ,75)) { return "<li title='بازدید معمولی' style='color:#ffcc00;background-color: maroon;width: 300px;height: 24px;'>" + ctx.CurrentItem.Title + " – " + ctx.CurrentItem.PopularityPercent + "</li>"; } else if (LT(ctx.CurrentItem.PopularityPercent ,95)) { return "<li title='پر بازدید' style='color:yellow;background-color: blue;width: 300px;height: 24px;'>" + ctx.CurrentItem.Title + " – " + ctx.CurrentItem.PopularityPercent + "</li>"; } else if (EQ(ctx.CurrentItem.PopularityPercent, 100)) { return "<li title='بالاترین بازدید' style='color:black;background-color: green;width: 300px;height: 24px;'>" + ctx.CurrentItem.Title + " – " + ctx.CurrentItem.PopularityPercent + "</li>"; } else { return "<li title='نامعلوم' style='color:navy;background-color: yellow;width: 300px;height: 24px;'>" + ctx.CurrentItem.Title + " – " + ctx.CurrentItem.PopularityPercent + "</li>"; } }
~site/_catalogs/masterpage/MyJsLinkSample.js
در SharePoint 2010 گزینه دیگری در برنامه نویسی، برای دسترسی به دادههای SharePoint تدارک دیده شده است: Client Object Model. این یک روش جدید، در برنامه نویسی شیرپوینت است. اگرچه استفاده از web services، پوشش وسیعی از امکانات شیرپوینت را به شما میدهد، اما برنامه نویسی به روش Client Object Model و API با استفاده از web services بسیار متفاوت است. استفاده از web services کار را برای شما سخت خواهد کرد و لازم است دو روش برنامه نویسی کاملا مختلف را بیاموزید. همچنین فراخوانی web services با JavaScript پیچیده است و نیازمند ساخت و دستکاری XMLهای فراوان است. Client Object Model تمام این مسائل را حل و برنامه نویسی سمت client را راحت کرده است.
در واقع Client Object Model سه Object Model جدا از هم است:
نسخه: .NET CLR برای ساخت WinForms, Windows Presentation Foundation (WPF), console applications
نسخه Silverlight : برای کا با هر دو حالت داخل in-browser و out-of-browser Silverlight applications
نسخه JavaScript : کدهای Ajax و jQuery را قادر میسازد تا دادههای شیرپوینت را فراخوانی کنند
یکی از سوالاتی که در مورد Client Object Model پیش میآید، این است که چه کارهایی را با آن میشود انجام داد؟ Client Object Model امکان دسترسی به بیشتر اشیاء رایج را مانند sites, webs, content types, lists, folders, navigations فراهم میکند. این اشیا با اسمهای مشابه در Client Object Model وجود دارند که در جدول زیر مشخص شدهاند.
در زیر یک مثال ساده از استفادههای Client Object Model را توضیح خواهم داد که لیستهای موجود در سایت را در خروجی نمایش میدهد.
1- در Visual Studio یک پروژه Console application ایجاد کنید.
2- بر روی References کلیک راست کرده Add Reference را انتخاب کنید. از مسیر زیر
C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\ISAPI
Microsoft.SharePoint.dll Microsoft.SharePoint.Client.Runtime.dll
static void Main(string[] args) { var ctx = new ClientContext(@"http://localhost"); var web = ctx.Web; var lists = web.Lists; ctx.Load(lists, l => l.Include (list => list.Title).Where (list => list.BaseType == BaseType.GenericList)); ctx.ExecuteQuery(); foreach (var list in lists) Console.WriteLine(list.Title); Console.ReadLine(); }
<embed height="400" width="500" flashvars="config=http://www.aparat.com//video/video/config/videohash/BA9Md/watchtype/embed" allowfullscreen="true" quality="high" name="aparattv_BA9Md" id="aparattv_BA9Md" src="http://host10.aparat.com/public/player/aparattv" type="application/x-shockwave-flash">
using System.Web.Mvc; namespace MvcApplication1 { public static class AparatPlayerHelper { public static MvcHtmlString AparatPlayer(this HtmlHelper helper, string mediafile, int height, int width) { var player = @"<embed height=""{0}"" width=""{1}"" flashvars=""config=http://www.aparat.com//video/video/config/videohash/{2}/watchtype/embed"" allowfullscreen=""true"" quality=""high"" name=""aparattv_{2}"" id=""aparattv_{2}"""" src=""http://host10.aparat.com/public/player/aparattv"" type=""application/x-shockwave-flash"">"; player = string.Format(player, height, width, mediafile); return new MvcHtmlString(player); } } }
@Html.AparatPlayer("BA9Md", 400, 500)
using System; using System.Drawing; using System.Web.Mvc; namespace MvcApplication1 { public static class YouTubePlayerHelper { public static MvcHtmlString YouTubePlayer(this HtmlHelper helper, string playerId, string mediaFile, YouTubePlayerOption youtubePlayerOption) { const string baseURL = "http://www.youtube.com/v/"; // YouTube Embedded Code var player = @"<div id=""YouTubePlayer_{7}""width:{1}px; height:{2}px;""> <object width=""{1}"" height=""{2}""> <param name=""movie"" value=""{6}{0}&fs=1&border={3}&color1={4}&color2={5}""></param> <param name=""allowFullScreen"" value=""true""></param> <embed src=""{6}{0}&fs=1&border={3}&color1={4}&color2={5}"" type = ""application/x-shockwave-flash"" width=""{1}"" height=""{2}"" allowfullscreen=""true""></embed> </object> </div>"; // Replace All The Value player = String.Format(player, mediaFile, youtubePlayerOption.Width, youtubePlayerOption.Height, (youtubePlayerOption.Border ? "1" : "0"), ConvertColorToHexa.ConvertColorToHexaString(youtubePlayerOption.PrimaryColor), ConvertColorToHexa.ConvertColorToHexaString(youtubePlayerOption.SecondaryColor), baseURL, playerId); //Retrun Embedded Code return new MvcHtmlString(player); } } public class YouTubePlayerOption { int _width = 425; int _height = 355; Color _color1 = Color.Black; Color _color2 = Color.Aqua; public YouTubePlayerOption() { Border = false; } public int Width { get { return _width; } set { _width = value; } } public int Height { get { return _height; } set { _height = value; } } public Color PrimaryColor { get { return _color1; } set { _color1 = value; } } public Color SecondaryColor { get { return _color2; } set { _color2 = value; } } public bool Border { get; set; } } public class ConvertColorToHexa { private static readonly char[] HexDigits = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; public static string ConvertColorToHexaString(Color color) { var bytes = new byte[3]; bytes[0] = color.R; bytes[1] = color.G; bytes[2] = color.B; var chars = new char[bytes.Length * 2]; for (int i = 0; i < bytes.Length; i++) { int b = bytes[i]; chars[i * 2] = HexDigits[b >> 4]; chars[i * 2 + 1] = HexDigits[b & 0xF]; } return new string(chars); } } }
@Html.YouTubePlayer("Casablanca", "iLdqKUkkM6w", new YouTubePlayerOption() { Border = true })