{ "name": "Appscope", "display": "standalone", "icons": [{ "src": "icons/icon-192.png", "sizes": "192x192"}, { "src": "icons/icon-512.png", "sizes": "512x512"} ] }
<meta name="apple-mobile-web-app-capable" content="yes">
{ "name": "Appscope", "display": "standalone", "icons": [{ "src": "icons/icon-192.png", "sizes": "192x192"}, { "src": "icons/icon-512.png", "sizes": "512x512"} ] }
<meta name="apple-mobile-web-app-capable" content="yes">
نکته: دقت کنید که تنها یک فولدر App_GlobalResources به هر پروزه میتوان افزود. همچنین در ریشه هر مسیر موجود در پروژه تنها میتوان یک فولدر Appp_LocalResources داشت. پس از افزودن هر یک از این فولدرهای مخصوص، منوی فوق به صورت زیر در خواهد آمد:
protected object GetLocalResourceObject(string resourceKey) protected object GetLocalResourceObject(string resourceKey, Type objType, string propName)
txtTest.Text = GetLocalResourceObject("txtTest.Text") as string;
protected object GetGlobalResourceObject(string className, string resourceKey) protected object GetGlobalResourceObject(string className, string resourceKey, Type objType, string propName)
TextBox1.Text = GetGlobalResourceObject("Resource1", "String1") as string;
public static object GetLocalResourceObject(string virtualPath, string resourceKey) public static object GetLocalResourceObject(string virtualPath, string resourceKey, CultureInfo culture)
txtTest.Text = HttpContext.GetLocalResourceObject("~/Default.aspx", "txtTest.Text") as string;
public static object GetGlobalResourceObject(string classKey, string resourceKey) public static object GetGlobalResourceObject(string classKey, string resourceKey, CultureInfo culture)
TextBox1.Text = HttpContext.GetGlobalResourceObject("Resource1", "String1") as string;
//------------------------------------------------------------------------------ // <auto-generated> // This code was generated by a tool. // Runtime Version:4.0.30319.17626 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. // </auto-generated> //------------------------------------------------------------------------------ namespace Resources { using System; /// <summary> /// A strongly-typed resource class, for looking up localized strings, etc. /// </summary> // This class was auto-generated by the StronglyTypedResourceBuilder // class via a tool like ResGen or Visual Studio. // To add or remove a member, edit your .ResX file then rerun ResGen // with the /str option or rebuild the Visual Studio project. [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Web.Application.StronglyTypedResourceProxyBuilder", "10.0.0.0")] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] internal class Resource1 { private static global::System.Resources.ResourceManager resourceMan; private static global::System.Globalization.CultureInfo resourceCulture; [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] internal Resource1() { } /// <summary> /// Returns the cached ResourceManager instance used by this class. /// </summary> [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] internal static global::System.Resources.ResourceManager ResourceManager { get { if (object.ReferenceEquals(resourceMan, null)) { global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Resources.Resource1", global::System.Reflection.Assembly.Load("App_GlobalResources")); resourceMan = temp; } return resourceMan; } } /// <summary> /// Overrides the current thread's CurrentUICulture property for all /// resource lookups using this strongly typed resource class. /// </summary> [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] internal static global::System.Globalization.CultureInfo Culture { get { return resourceCulture; } set { resourceCulture = value; } } /// <summary> /// Looks up a localized string similar to String1. /// </summary> internal static string String1 { get { return ResourceManager.GetString("String1", resourceCulture); } } } }
TextBox1.Text = Resources.Resource1.String1;
%GIT_HOME%\cmd;C:\Program Files (x86)\nodejs\;%JAVA_HOME%\bin;%ANT_HOME%\bin; %ANDROID_HOME%\tools;%ANDROID_HOME%\platform-tools; C:\ProgramData\Oracle\Java\javapath;
customElements.define("x-component", class extends HTMLElement { constructor() { super(); console.log('constructed!'); } connectedCallback() { console.log('connected!'); } disconnectedCallback() { console.log('disconnected!'); } adoptedCallback() { console.log('adopted!'); } attributeChangedCallback(name, oldValue, newValue) { console.log('attirbuteChanged!', name, oldValue, newValue); } static get observedAttributes() { return ['checked','demo','label']; } });
// "tabs" is not a valid custom element name document.createElement('tabs') instanceof HTMLUnknownElement === true //true // "x-tabs" is a valid custom element name document.createElement('x-tabs') instanceof HTMLElement === true //true
customElements.whenDefined('x-component').then(() => { console.log('x-component defined'); });
<share-buttons> <social-button type="twitter"><a href="...">Twitter</a></social-button> <social-button type="fb"><a href="...">Facebook</a></social-button> <social-button type="plus"><a href="...">G+</a></social-button> </share-buttons> // Fetch all the children of <share-buttons> that are not defined yet. let undefinedButtons = buttons.querySelectorAll(':not(:defined)'); let promises = [...undefinedButtons].map(socialButton => { return customElements.whenDefined(socialButton.localName); )); // Wait for all the social-buttons to be upgraded. Promise.all(promises).then(() => { // All social-button children are ready. });
customeElements.define('x-component',...) let XComponent = customElements.get('x-component'); document.body.appendChild(new XComponent())
const el = document.createElement('x-component'); document.body.appendChild(el); // connectedCallback() called el.remove(); // disconnectedCallback() document.body.appendChild(el); // connectedCallback() called again
const iframe = document.querySelector('iframe'); const iframeImages = iframe.contentDocument.querySelectorAll('img'); const newParent = document.getElementById('images'); iframeImages.forEach(function(imgEl) { newParent.appendChild(document.adoptNode(imgEl)); });
class XComponent extends HTMLElement { constructor() { super(); } doSomething(){ console.log('doSomething'); } }
let component = document.querySelector('x-component'); component.doSomething();
div.id = 'id-value'; div.hidden = true;
<div id="id-value" hidden>
class XComponent extends HTMLElement { constructor() { super(); } connectedCallback() { this._render(); } get disabled() { return this.hasAttribute('disabled'); } set disabled(val) { // Reflect the value of `disabled` as an attribute. if (val) { this.setAttribute('disabled', ''); } else { this.removeAttribute('disabled'); } this._render(); } _render() { //... } }
attributeChangedCallback(name, oldValue, newValue) { switch (name) { case 'checked': // Note the attributeChangedCallback is only handling the *side effects* // of setting the attribute. this.setAttribute('aria-disabled', !!newValue); break; ... }
attributeChangedCallback(name, oldValue, newValue) { // When the component is disabled, update keyboard/screen reader behavior. if (this.disabled) { this.setAttribute('tabindex', '-1'); this.setAttribute('aria-disabled', 'true'); } else { this.setAttribute('tabindex', '0'); this.setAttribute('aria-disabled', 'false'); } // TODO: also react to the other attribute changing. }
constructor() { super(); this._data = []; } get data() { return _data; } set data(value) { if (this_data === value) return; this._data = value; this._render(); }
<x-component [disabled]="model.disabled"></x-component>
let el = document.querySelector('x-component'); el.disabled = true; customElements.define("x-component", class extends HTMLElement { constructor() { super(); } get disabled() { return this.hasAttribute('disabled'); } set disabled(val) { // Reflect the value of `disabled` as an attribute. if (val) { this.setAttribute('disabled', ''); } else { this.removeAttribute('disabled'); } this._render(); } });
let el = document.querySelector('x-component'); el.disabled = true; customElements.define("x-component", class extends HTMLElement { constructor() { super(); } connectedCallback() { this._upgradeProp('disabled'); } get disabled() { return this.hasAttribute('disabled'); } set disabled(val) { // Reflect the value of `disabled` as an attribute. if (val) { this.setAttribute('disabled', ''); } else { this.removeAttribute('disabled'); } this._render(); } _upgradeProp(prop) { if (this.hasOwnProperty(prop)) { let value = this[prop]; delete this[prop]; //delete instance property this[prop] = value; // set prototype property } } });
connectedCallback() { if (!this.hasAttribute('role')) this.setAttribute('role', 'checkbox'); if (!this.hasAttribute('tabindex')) this.setAttribute('tabindex', -1); //element is not reachable via sequential keyboard navigation, but could be focused }
حال با استفاده از ابزار 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, …};