مسیرراه‌ها
WPF
          مطالب
          نگاشت اشیاء در AutoMapper توسط Attribute ها #1
          نگاشت اشیاء امری مفید و لذت بخش است. ولی بخاطر تنظیمات خاص آن و افزایش کدها، همیشه کمی دردسر ساز بوده است. استفاده از کلاس Profile راه کار مناسبی است؛ اما در این حالت کلاس مقصد (ViewModel) از تنظیمات نگاشت‌ها بی اطلاع می‌ماند و فقط حاوی داده خواهد بود. برای ادغام کلاس و تنظیمات نگاشت در اینجا راهکاری ارائه گردید که در ادامه و با الگو گیری از همین ایده، اقدام به ارائه‌ی روشی جدید می‌کنم که با استفاده از Attribute‌ها تنظیمات نگاشت اشیاء را در AutoMapper انجام می‌دهد.
          در نهایت می‌خواهیم نگاشت‌ها را اینچنین تنظیم کنیم:
           [MapFrom(typeof (Student), ignoreAllNonExistingProperty: true, alsoCopyMetadata: true)]
           public class AdminStudentViewModel
           {
               // [IgnoreMap]
               public int Id { set; get; }
          
               [MapForMember("Name")]
               public string FirstName { set; get; }
          
               [MapForMember("Family")]
               public string LastName { set; get; }
          
               public string Email { set; get; }
          
               [MapForMember("RegisterDateTime")]
               public string RegisterDateTimePersian { set; get; }
          
               [UseValueResolver(typeof (BookCountValueResolver))]
               public int BookCounts { set; get; }
          
               [UseValueResolver(typeof (BookPriceValueResolver))]
               public decimal BookPrice { set; get; }
           };
            این سبک تنظیم کردن نگاشت‌های اشیاء به نظر بهتر از روش‌های دیگر است؛ چون کلاس‌های ویوومدل را معنادار کرده و همچنین برای برنامه نویسان EF و ASP.NET MVC استفاده‌ی از ویژگی‌ها، یک شیوه‌ی کاری معمول به حساب می‌آید. 
          به تعریف و توضیح صفت‌های (ویژگی‌ها یا Attributes) مورد نیاز می‌پردازم:

          صفت MapFromAttribute

          [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
          public class MapFromAttribute : Attribute
          {
              public Type SourceType { get; private set; }
              public bool IgnoreAllNonExistingProperty { get; private set; }
              public bool AlsoCopyMetadata { get; private set; }    //Go to: https://www.dntips.ir/courses/topic/16/cb36bc2e-4263-431e-86a5-236322cb5576
          
              public MapFromAttribute(Type sourceType, bool ignoreAllNonExistingProperty = false,
                  bool alsoCopyMetadata = false)
              {
                  SourceType = sourceType;
                  IgnoreAllNonExistingProperty = ignoreAllNonExistingProperty;
                  AlsoCopyMetadata = alsoCopyMetadata;
              }
          };
          این صفت روی کلاس‌ها می‌نشیند و توسط آرگومان sourceType آن، نوع مبدأ را برای automapper مشخص می‌کند. در واقع همه چیز از اینجا شروع می‌شود. همچنین آرگومان ignoreAllNonExistingProperty مشخص می‌کند کلیه‌ی صفاتی که در مقصد هستند ولی معادل اسمی در مبدأ ندارند، بصورت خودکار رد (Ignore) شده و از آن‌ها صرف نظر شود تا از شکست متد AutoMapper.Mapper.AssertConfigurationIsValid جلوگیری کند (پرداخته شده در اینجا). آرگومان alsoCopyMetadata پیاده سازی نمی‌شود؛ ولی می‌تواند پرچمی باشد تا اجازه دهد  Data Annotations از مدل‌های ef به ViewModel انتقال یابند.


          صفت IgnoreMapAttribute
          [AttributeUsage(AttributeTargets.Property)]
          public class IgnoreMapAttribute : Attribute {};
          از این صفت برای رد کردن خصیصه‌‌ای در نگاشت‌ها استفاده می‌کنیم. لازم به ذکر است که صفتی مشابه در Automapper.IgnoreAttribute وجود دارد که می‌تواند به جای این صفت مورد استفاده قرار گیرد. «نگارنده جهت همخوانی با سایر صفات، اقدام به استفاده‌ی از این صفت می‌کند»


          صفت MapForMemberAttribute
          [AttributeUsage(AttributeTargets.Property)]
          public class MapForMemberAttribute : Attribute
          {
              public string MemberToMap { get; private set; }
              public MapForMemberAttribute(string memberToMap)
              {
                  MemberToMap = memberToMap;
              }
          };
          اگر نام خصیصه‌ها در مبدأ و مقصد یکی نباشند، از این صفت برای همگام سازی این دو استفاده می‌کنیم.


          صفت UseValueResolverAttribute
          [AttributeUsage(AttributeTargets.Property)]
          public class UseValueResolverAttribute : Attribute
          {
              public IValueResolver ValueResolver { get; private set; }
              public UseValueResolverAttribute(Type valueResolver)
              {
                  ValueResolver = valueResolver.GetConstructors()[0].Invoke(new object[] {}) as IValueResolver;
              }
          };
          استفاده از ValueResolver‌ها در اینجا ذکر شده است. از این صفت برای تنظیم این مقدار برای یک خصیصه استفاده می‌شود. برای مثال فیلد FullName را در مقصد درنظر بگیرد که از دو فیلد Name و Family در مبدأ تشکیل می‌شود.

          تا اینجا صفات پیش نیاز کار فراهم شدند. حال باید این صفت‌ها را به نگاشت متناسبی در automapper تبدیل کنیم.
          دریافت کدها
          ادامه دارد...
          نظرات مطالب
          معرفی System.Text.Json در NET Core 3.0.
          یک نکته‌ی تکمیلی
          در NET Core 3.0 Preview 7. امضای این کلاس‌ها و متدها به صورت زیر تغییر کرده‌است و متد Parse به Deserialize تبدیل شده‌است:
          namespace System.Text.Json
          {
              public static class JsonSerializer
              {
                  public static object Deserialize(ReadOnlySpan<byte> utf8Json, Type returnType, JsonSerializerOptions options = null);
                  public static object Deserialize(string json, Type returnType, JsonSerializerOptions options = null);
                  public static TValue Deserialize<TValue>(ReadOnlySpan<byte> utf8Json, JsonSerializerOptions options = null);
                  public static TValue Deserialize<TValue>(string json, JsonSerializerOptions options = null);
                  public static object Deserialize(ref Utf8JsonReader reader, Type returnType, JsonSerializerOptions options = null);
                  public static TValue Deserialize<TValue>(ref Utf8JsonReader reader, JsonSerializerOptions options = null);
                  public static ValueTask<object> DeserializeAsync(Stream utf8Json, Type returnType, JsonSerializerOptions options = null, CancellationToken cancellationToken = default);
                  public static ValueTask<TValue> DeserializeAsync<TValue>(Stream utf8Json, JsonSerializerOptions options = null, CancellationToken cancellationToken = default);
                  public static string Serialize(object value, Type inputType, JsonSerializerOptions options = null);
                  public static string Serialize<TValue>(TValue value, JsonSerializerOptions options = null);
                  public static void Serialize(Utf8JsonWriter writer, object value, Type inputType, JsonSerializerOptions options = null);
                  public static void Serialize<TValue>(Utf8JsonWriter writer, TValue value, JsonSerializerOptions options = null);
                  public static Task SerializeAsync(Stream utf8Json, object value, Type inputType, JsonSerializerOptions options = null, CancellationToken cancellationToken = default);
                  public static Task SerializeAsync<TValue>(Stream utf8Json, TValue value, JsonSerializerOptions options = null, CancellationToken cancellationToken = default);
                  public static byte[] SerializeToUtf8Bytes(object value, Type inputType, JsonSerializerOptions options = null);
                  public static byte[] SerializeToUtf8Bytes<TValue>(TValue value, JsonSerializerOptions options = null);
              }
          }
          همچنین JsonConverter نیز به تنظیمات آن اضافه شده‌است:
          namespace System.Text.Json
          {
              public sealed class JsonSerializerOptions
              {
                  public JsonSerializerOptions();
          
                  public bool AllowTrailingCommas { get; set; }
                  public IList<JsonConverter> Converters { get; }
                  public int DefaultBufferSize { get; set; }
                  public JsonNamingPolicy DictionaryKeyPolicy { get; set; }
                  public bool IgnoreNullValues { get; set; }
                  public bool IgnoreReadOnlyProperties { get; set; }
                  public int MaxDepth { get; set; }
                  public bool PropertyNameCaseInsensitive { get; set; }
                  public JsonNamingPolicy PropertyNamingPolicy { get; set; }
                  public JsonCommentHandling ReadCommentHandling { get; set; }
                  public bool WriteIndented { get; set; }
          
                  public JsonConverter GetConverter(Type typeToConvert);
              }
          }
          با این تعریف:
          namespace System.Text.Json.Serialization
          {
              public abstract class JsonConverter
              {
                  public abstract bool CanConvert(Type typeToConvert);
              }
          }
          مطالب
          اعتبارسنجی سرویس های WCF
          حالتی را در نظر بگیرید که سرویس‌های یک برنامه در آدرسی مشخص هاست شده اند. اگر اعتبار سنجی برای این سرویس‌ها در نظر گرفته نشود به راحتی می‌توان با در اختیار داشتن آدرس مورد نظر تمام سرویس های  برنامه را فراخوانی کرد و اگر رمزگذاری اطلاعات بر روی سرویس‌ها فعال نشده باشد می‌توان تمام اطلاعات این سرویس‌ها را به راحتی به دست آورد. کمترین تلاش در این مرحله برای پیاده سازی امنیت این است که برای فراخوانی هر سرویس حداقل یک شناسه و رمز عبور چک شود و فقط در صورتی که فراخوانی سرویس همراه با شناسه و رمز عبور درست بود اطلاعات در اختیار کلاینت قرار گیرد. قصد داریم طی یک مثال این مورد را بررسی کنیم:
          ابتدا یک پروژه با دو Console Application  با نام های  Service و Client ایجاد کنید. سپس در پروژه Service یک سرویس به نام BookService ایجاد کنید و کد‌های زیر را در آن کپی نمایید:
          Contract مربوطه به صورت زیر است:
          [ServiceContract]
          public interface IBookService
              {
                  [OperationContract]
                  int GetCountOfBook();
              }
          کد‌های مربوط به سرویس:
           [ServiceBehavior(IncludeExceptionDetailInFaults = true)]
              public class BookService : IBookService
              {
                  public int GetCountOfBook()
                  {
                      return 10;
                  }
              }
          فایل Program در پروژه Service را باز نمایید و کد‌های زیر را که مربوط به hosting سرویس مورد نظر است در آن کپی کنید:
           class Program
              {
                  static void Main(string[] args)
                  {
                      ServiceHost host = new ServiceHost(typeof(BookService));
          
                      var binding = new BasicHttpBinding();
                      
                      host.AddServiceEndpoint(typeof(IBookService), binding, "http://localhost/BookService");
                      host.Open();
          
                      Console.Write("BookService host");
          
                      Console.ReadKey();
                  }
              }
          بر اساس کد‌های بالا، سرویس BookService در آدرس http://localhost/BookService هاست می‌شود.  نوع Binding نیز BasicHttpBinding انتخاب شده است.
          حال نوبت به پیاده سازی سمت کلاینت می‌رسد. فایل Program سمت کلاینت را باز کرده و کد‌های زیر را نیز در آن کپی نمایید:
                  static void Main(string[] args)
                  {
                      Thread.Sleep(2000);
                      BasicHttpBinding binding = new BasicHttpBinding();
          
                      ChannelFactory<IBookService> channel = new ChannelFactory<IBookService>(binding, new EndpointAddress("http://localhost/BookService"));
          
                      Console.WriteLine("Count of book: {0}", channel.CreateChannel().GetCountOfBook());
          
                      Console.ReadKey();
                  }
          در کد‌های عملیات ساخت ChannelFactory برای برقراری اطلاعات با سرویس مورد نظر انجام شده است. پروژه را Build نمایید و سپس آن را اجرا کنید.
           خروجی زیر مشاهده می‌شود:

          تا اینجا هیچ گونه اعتبار سنجی انجام نشد. برای پیاده سازی اعتبار سنجی باید یک سری تنظیمات بر روی Binding و Hosting  سمت سرور و البته کلاینت بر قرار شود. فایل Program پروزه Service را باز نمایید و محتویات آن را به صورت زیر تغییر دهید:

           static void Main(string[] args)
                  {
                      ServiceHost host = new ServiceHost(typeof(BookService));
          
                      var binding = new BasicHttpBinding();
                      binding.Security = new BasicHttpSecurity();
                      binding.Security.Mode = BasicHttpSecurityMode.TransportCredentialOnly;
                      binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Basic;
          
                      host.Credentials.UserNameAuthentication.UserNamePasswordValidationMode = System.ServiceModel.Security.UserNamePasswordValidationMode.Custom;
          
                      host.Credentials.UserNameAuthentication.CustomUserNamePasswordValidator = new CustomUserNamePasswordValidator();               
          
                      host.AddServiceEndpoint(typeof(IBookService), binding, "http://localhost/BookService");
                      host.Open();
          
          
                      Console.Write("BookService host");
          
                      Console.ReadKey();
                  }
          تغییرات اعمال شده:
          ابتدا نوع Security در Binding را به حالت TransportCredentialOnly تنظیم کردیم. در یک جمله هیچ گونه تضمینی برای صحت اطلاعات انتقالی در این حالت وجود ندارد و فقط یک اعتبار سنجی اولیه انجام خواهد شد. در نتیجه هنگام استفاده از این حالت باید با دقت عمل نمود و نباید فقط به پیاده سازی این حالت اکتفا کرد.( Encryption اطلاعات سرویس‌ها مورد بحث این پست نیست)
          ClientCredentialType نیز باید به حالت Basic تنظیم شود. در WCF اعتبار سنجی به صورت پیش فرض در حالت Windows است (بعنی UserNamePasswordValidationMode برابر مقدار Windows است و اعتبار سنجی بر اساس کاربر انجام می‌شود) . این مورد باید به مقدار Custom تغییر یابد. در انتها نیز باید مدل اعتبار سنجی دلخواه خود را به صورت زیر پیاده سازی کنیم:
          در پروژه سرویس یک کلاس به نام CustomUserNamePasswordValidator بسازید و کد‌های زیر را در آن کپی کنید:
           public class CustomUserNamePasswordValidator : UserNamePasswordValidator
              {
                  public override void Validate(string userName, string password)
                  {
                      if (userName != "Masoud" || password != "Pakdel")
                          throw new SecurityException("Incorrect userName or password");
                  }
              }
          Validator مورد نظر از کلاسی abstract به نام UserNamePasswordValidator  ارث می‌برد، در نتیجه باید متد abstract به نام Validate را override نماید. در بدنه این متد شناسه و رمز عبور با یک مقدار پیش فرض چک می‌شوند و در صورت عدم درستی این پارارمترها یک استثنا پرتاب خواهد شد.

          تغییرات مورد نیاز سمت کلاینت:
          اگر در این حالت پروژه را اجرا نمایید از آن جا که از این به بعد، درخواست‌ها سمت سرور اعتبار سنجی می‌شوند در نتیجه با خطای زیر روبرو خواهید شد:

          این خطا از آن جا ناشی می‌شود که تنظیمات کلاینت و سرور از نظر امنیتی با هم تناسب ندارد. در نتیجه باید تنظیمات Binding کلاینت و سرور یکی شود. برای این کار کد زیر را به فایل Program سمت کلاینت اضافه می‌کنیم:


          static void Main(string[] args)
                  {
                      Thread.Sleep(2000);
                      BasicHttpBinding binding = new BasicHttpBinding();
          
                      binding.Security = new BasicHttpSecurity();
                      binding.Security.Mode = BasicHttpSecurityMode.TransportCredentialOnly;
                      binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Basic;            
          
                      ChannelFactory<IBookService> channel = new ChannelFactory<IBookService>(binding, new EndpointAddress("http://localhost/BookService"));
          
                      channel.Credentials.UserName.UserName = "WrongUserName";
                      channel.Credentials.UserName.Password = "WrongPassword";

           Console.WriteLine("Count of book: {0}", channel.CreateChannel().GetCountOfBook()); Console.ReadKey(); }
          توسط دستور زیر، مقدار شناسه و رمز عبور به درخواست اضافه می‌شود.
          channel.Credentials.UserName.UserName = "WrongUserName";
          channel.Credentials.UserName.Password = "WrongPassword";
           در اینجا UserName و Password اشتباه مقدار دهی شده اند تا روش کار Validator مورد بررسی قرار گیرد. حال اگر پروژه را اجرا نمایید خواهید دید که در Validator مورد نظر، عملیات اعتبار سنجی به درستی انجام می‌شود:


          دریافت سورس مثال بالا
          نظرات مطالب
          Blazor 5x - قسمت چهارم - مبانی Blazor - بخش 1 - Data Binding
          ساده شدن «روش تعریف data binding دو طرفه در کامپوننت‌ها» در دات نت 7

          در نکته‌ی فوق، روش تعریف data binding دو طرفه را در کامپوننت‌ها بررسی کردیم. برای مثال اگر بخواهیم کامپوننت سفارشی ما دارای ویژگی bind-Value@ باشد، باید حداقل توسط دو پارامتر زیر پشتیبانی شود:
          [Parameter] public string Value { set; get; }
          [Parameter] public EventCallback<string> ValueChanged { get; set; }
          که در اینجا باید قسمت setter مربوط به خاصیت Value را هم بازنویسی کنیم و توسط آن، با فراخوانی ValueChanged.InvokeAsync، به استفاده کننده اطلاع دهیم که مقدار Value تغییر کرده‌است. این قسمت بازنویسی در دات نت 7 به صورت زیر قابل حذف شدن است (نام فرضی کامپوننت زیر، MyInput است):
          <input @bind:get="Value" @bind:set="ValueChanged" />
          
          @code {
              [Parameter]
              public TValue Value { get; set; }
              
              [Parameter]
              public EventCallback<TValue> ValueChanged { get; set; }
          }
          در دات نت 7، دو binding modifier جدید bind:get و bind:set جهت تعریف ساده‌تر two-way data-binding اضافه شده‌اند که باید به همراه هم اضافه شوند. توسط bind:get، مقدار در حال تغییر و توسط bind:set، یک callback را که پس از تغییر این مقدار فراخوانی می‌شود، مشخص می‌کنیم و ... همین! پس از این تعاریف دیگر نیازی به بازنویسی قسمت set پارامتر Value مانند قبل نیست و اکنون می‌توان به bind-Value@ دو طرفه به صورت زیر دسترسی یافت:
          <MyInput @bind-Value="text" />
          
          @code {
              string text = "Type something great!";
          }
          مطالب
          امن سازی برنامه‌های ASP.NET Core توسط IdentityServer 4x - قسمت دهم- ذخیره سازی اطلاعات کاربران IDP در بانک اطلاعاتی
          تا اینجا تمام قسمت‌های این سری، برای اساس اطلاعات یک کلاس Config استاتیک تشکیل شده‌ی در حافظه ارائه شدند. این روش برای دمو و توضیح مفاهیم پایه‌ی IdentityServer بسیار مفید است؛ اما برای دنیای واقعی خیر. بنابراین در ادامه می‌خواهیم این قسمت را با اطلاعات ذخیره شده‌ی در بانک اطلاعاتی تعویض کنیم. یک روش مدیریت آن، نصب ASP.NET Core Identity دقیقا داخل همان پروژه‌ی IDP است. در این حالت کدهای ASP.NET Core Identity مایکروسافت، کار مدیریت کاربران IDP را انجام می‌دهند. روش دیگر اینکار را که در اینجا بررسی خواهیم کرد، تغییر کدهای Quick Start UI اضافه شده‌ی در «قسمت چهارم - نصب و راه اندازی IdentityServer»، جهت پذیرفتن مدیریت کاربران مبتنی بر بانک اطلاعاتی تهیه شده‌ی توسط خودمان است. مزیت آن آشنا شدن بیشتر با کدهای Quick Start UI و درک زیرساخت آن است.


          تکمیل ساختار پروژه‌ی IDP

          تا اینجا برای IDP، یک پروژه‌ی خالی وب را ایجاد و به مرور، آن‌را تکمیل کردیم. اما اکنون نیاز است پشتیبانی از بانک اطلاعاتی را نیز به آن اضافه کنیم. برای این منظور چهار پروژه‌ی Class library کمکی را نیز به Solution آن اضافه می‌کنیم:


          - DNT.IDP.DomainClasses
          در این پروژه، کلاس‌های متناظر با موجودیت‌های جداول مرتبط با اطلاعات کاربران قرار می‌گیرند.
          - DNT.IDP.DataLayer
          این پروژه Context برنامه و Migrations آن‌را تشکیل می‌دهد. همچنین به همراه تنظیمات و Seed اولیه‌ی اطلاعات بانک اطلاعاتی نیز می‌باشد.
          رشته‌ی اتصالی آن نیز در فایل DNT.IDP\appsettings.json ذخیره شده‌است.
          - DNT.IDP.Common
          الگوریتم هش کردن اطلاعات، در این پروژه‌ی مشترک بین چند پروژه‌ی دیگر قرار گرفته‌است. از آن جهت هش کردن کلمات عبور، در دو پروژه‌ی DataLayer و همچنین Services استفاده می‌کنیم.
          - DNT.IDP.Services
          کلاس سرویس کاربران که با استفاده از DataLayer با بانک اطلاعاتی ارتباط برقرار می‌کند، در این پروژه قرار گرفته‌است.


          ساختار بانک اطلاعاتی کاربران IdentityServer

          در اینجا ساختار بانک اطلاعاتی کاربران IdentityServer، بر اساس جداول کاربران و Claims آن‌ها تشکیل می‌شود:
          namespace DNT.IDP.DomainClasses
          {
              public class User
              {
                  [Key]
                  [MaxLength(50)]       
                  public string SubjectId { get; set; }
              
                  [MaxLength(100)]
                  [Required]
                  public string Username { get; set; }
          
                  [MaxLength(100)]
                  public string Password { get; set; }
          
                  [Required]
                  public bool IsActive { get; set; }
          
                  public ICollection<UserClaim> UserClaims { get; set; }
          
                  public ICollection<UserLogin> UserLogins { get; set; }
              }
          }
          در اینجا SubjectId همان Id کاربر، در سطح IDP است. این خاصیت به صورت یک کلید خارجی در جداول UserClaims و UserLogins نیز بکار می‌رود.
          ساختار Claims او نیز به صورت زیر تعریف می‌شود که با تعریف یک Claim استاندارد، سازگاری دارد:
          namespace DNT.IDP.DomainClasses
          {
              public class UserClaim
              {         
                  public int Id { get; set; }
          
                  [MaxLength(50)]
                  [Required]
                  public string SubjectId { get; set; }
                  
                  public User User { get; set; }
          
                  [Required]
                  [MaxLength(250)]
                  public string ClaimType { get; set; }
          
                  [Required]
                  [MaxLength(250)]
                  public string ClaimValue { get; set; }
              }
          }
          همچنین کاربر می‌توان تعدادی لاگین نیز داشته باشد:
          namespace DNT.IDP.DomainClasses
          {
              public class UserLogin
              {
                  public int Id { get; set; }
          
                  [MaxLength(50)]
                  [Required]
                  public string SubjectId { get; set; }
                  
                  public User User { get; set; }
          
                  [Required]
                  [MaxLength(250)]
                  public string LoginProvider { get; set; }
          
                  [Required]
                  [MaxLength(250)]
                  public string ProviderKey { get; set; }
              }
          }
          هدف از آن، یکپارچه سازی سیستم، با IDPهای ثالث مانند گوگل، توئیتر و امثال آن‌ها است.

          در پروژه‌ی DNT.IDP.DataLayer در پوشه‌ی Configurations آن، کلاس‌های UserConfiguration و UserClaimConfiguration را مشاهده می‌کنید که حاوی اطلاعات اولیه‌ای برای تشکیل User 1 و User 2 به همراه Claims آن‌ها هستند. این اطلاعات را دقیقا از فایل استاتیک ‍Config که در قسمت‌های قبل تکمیل کردیم، به این دو کلاس جدید IEntityTypeConfiguration منتقل کرده‌ایم تا به این ترتیب متد GetUsers فایل استاتیک Config را با نمونه‌ی دیتابیسی آن جایگزین کنیم.
          سرویسی که از طریق Context برنامه با بانک اطلاعاتی ارتباط برقرار می‌کند، چنین ساختاری را دارد:
              public interface IUsersService
              {
                  Task<bool> AreUserCredentialsValidAsync(string username, string password);
                  Task<User> GetUserByEmailAsync(string email);
                  Task<User> GetUserByProviderAsync(string loginProvider, string providerKey);
                  Task<User> GetUserBySubjectIdAsync(string subjectId);
                  Task<User> GetUserByUsernameAsync(string username);
                  Task<IEnumerable<UserClaim>> GetUserClaimsBySubjectIdAsync(string subjectId);
                  Task<IEnumerable<UserLogin>> GetUserLoginsBySubjectIdAsync(string subjectId);
                  Task<bool> IsUserActiveAsync(string subjectId);
                  Task AddUserAsync(User user);
                  Task AddUserLoginAsync(string subjectId, string loginProvider, string providerKey);
                  Task AddUserClaimAsync(string subjectId, string claimType, string claimValue);
              }
          که توسط آن امکان دسترسی به یک کاربر، اطلاعات Claims او و افزودن رکوردهایی جدید وجود دارد.
          تنظیمات نهایی این سرویس‌ها و Context برنامه نیز در فایل DNT.IDP\Startup.cs جهت معرفی به سیستم تزریق وابستگی‌ها، صورت گرفته‌اند. همچنین در اینجا متد initializeDb را نیز مشاهده می‌کنید که با فراخوانی متد context.Database.Migrate، تمام کلاس‌های Migrations پروژه‌ی DataLayer را به صورت خودکار به بانک اطلاعاتی اعمال می‌کند.


          غیرفعال کردن صفحه‌ی Consent در Quick Start UI

          در «قسمت چهارم - نصب و راه اندازی IdentityServer» فایل‌های Quick Start UI را به پروژه‌ی IDP اضافه کردیم. در ادامه می‌خواهیم قدم به قدم این پروژه را تغییر دهیم.
          در صفحه‌ی Consent در Quick Start UI، لیست scopes درخواستی برنامه‌ی کلاینت ذکر شده و سپس کاربر انتخاب می‌کند که کدامیک از آن‌ها، باید به برنامه‌ی کلاینت ارائه شوند. این صفحه، برای سناریوی ما که تمام برنامه‌های کلاینت توسط ما توسعه یافته‌اند، بی‌معنا است و صرفا برای کلاینت‌های ثالثی که قرار است از IDP ما استفاده کنند، معنا پیدا می‌کند. برای غیرفعال کردن آن کافی است به فایل استاتیک Config مراجعه کرده و خاصیت RequireConsent کلاینت مدنظر را به false تنظیم کرد.


          تغییر نام پوشه‌ی Quickstart و سپس اصلاح فضای نام پیش‌فرض کنترلرهای آن

          در حال حاضر کدهای کنترلرهای Quick Start UI داخل پوشه‌ی Quickstart برنامه‌ی IDP قرار گرفته‌اند. با توجه به اینکه قصد داریم این کدها را تغییر دهیم و همچنین این پوشه در اساس، همان پوشه‌ی استاندارد Controllers است، ابتدا نام این پوشه را به Controllers تغییر داده و سپس در تمام کنترلرهای ذیل آن، فضای نام پیش‌فرض IdentityServer4.Quickstart.UI را نیز به فضای نام متناسبی با پوشه بندی پروژه‌ی جاری تغییر می‌دهیم. برای مثال کنترلر Account واقع در پوشه‌ی Account، اینبار دارای فضای نام DNT.IDP.Controllers.Account خواهد شد و به همین ترتیب برای مابقی کنترل‌ها عمل می‌کنیم.
          پس از این تغییرات، عبارات using موجود در Viewها را نیز باید تغییر دهید تا برنامه در زمان اجرا به مشکلی برنخورد. البته ASP.NET Core 2.1 در زمان کامپایل برنامه، تمام Viewهای آن‌را نیز کامپایل می‌کند و اگر خطایی در آن‌ها وجود داشته باشد، امکان بررسی و رفع آن‌ها پیش از اجرای برنامه، میسر است.
          و یا می‌توان جهت سهولت کار، فایل DNT.IDP\Views\_ViewImports.cshtml را جهت معرفی این فضاهای نام جدید ویرایش کرد تا نیازی به تغییر Viewها نباشد:
          @using DNT.IDP.Controllers.Account;
          @using DNT.IDP.Controllers.Consent;
          @using DNT.IDP.Controllers.Grants;
          @using DNT.IDP.Controllers.Home;
          @using DNT.IDP.Controllers.Diagnostics;
          @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers


          تعامل با IdentityServer از طریق کدهای سفارشی

          پس از تشکیل «ساختار بانک اطلاعاتی کاربران IdentityServer» و همچنین تهیه سرویس‌های متناظری جهت کار با آن، اکنون نیاز است مطمئن شویم IdentityServer از این بانک اطلاعاتی برای دریافت اطلاعات کاربران خود استفاده می‌کند.
          در حال حاضر، با استفاده از متد الحاقی AddTestUsers معرفی شده‌ی در فایل DNT.IDP\Startup.cs، اطلاعات کاربران درون حافظه‌ای برنامه را از متد ()Config.GetUsers دریافت می‌کنیم.
          بنابراین اولین قدم، بررسی ساختار متد AddTestUsers است. برای این منظور به مخزن کد IdentityServer4 مراجعه کرده و کدهای متد الحاقی AddTestUsers را بررسی می‌کنیم:
           public static class IdentityServerBuilderExtensions
           {
                  public static IIdentityServerBuilder AddTestUsers(this IIdentityServerBuilder builder, List<TestUser> users)
                  {
                      builder.Services.AddSingleton(new TestUserStore(users));
                      builder.AddProfileService<TestUserProfileService>();
                      builder.AddResourceOwnerValidator<TestUserResourceOwnerPasswordValidator>();
          
                      return builder;
                  }
          }
          - ابتدا یک TestUserStore را به صورت Singleton ثبت کرده‌است.
          - سپس سرویس پروفایل کاربران را اضافه کرده‌است. این سرویس با پیاده سازی اینترفیس IProfileService تهیه می‌شود. کار آن اتصال یک User Store سفارشی به سرویس کاربران و دریافت اطلاعات پروفایل آن‌ها مانند Claims است.
          - در آخر TestUserResourceOwnerPasswordValidator، کار اعتبارسنجی کلمه‌ی عبور و نام کاربری را در صورت استفاده‌ی از Flow ویژه‌ای به نام ResourceOwner که استفاده‌ی از آن توصیه نمی‌شود (ROBC Flow)، انجام می‌دهد.

          برای جایگزین کردن AddTestUsers، کلاس جدید IdentityServerBuilderExtensions را در ریشه‌ی پروژه‌ی IDP با محتوای ذیل اضافه می‌کنیم:
          using DNT.IDP.Services;
          using Microsoft.Extensions.DependencyInjection;
          
          namespace DNT.IDP
          {
              public static class IdentityServerBuilderExtensions
              {
                  public static IIdentityServerBuilder AddCustomUserStore(this IIdentityServerBuilder builder)
                  {
                      // builder.Services.AddScoped<IUsersService, UsersService>();
                      builder.AddProfileService<CustomUserProfileService>();
                      return builder;
                  }
              }
          }
          در اینجا ابتدا IUsersService سفارشی برنامه معرفی شده‌است که User Store سفارشی برنامه است. البته چون UsersService ما با بانک اطلاعاتی کار می‌کند، نباید به صورت Singleton ثبت شود و باید در پایان هر درخواست به صورت خودکار Dispose گردد. به همین جهت طول عمر آن Scoped تعریف شده‌است. در کل ضرورتی به ذکر این سطر نیست؛ چون پیشتر کار ثبت IUsersService در کلاس Startup برنامه انجام شده‌است.
          سپس یک ProfileService سفارشی را ثبت کرده‌ایم. این سرویس، با پیاده سازی IProfileService به صورت زیر پیاده سازی می‌شود:
          namespace DNT.IDP.Services
          {
              public class CustomUserProfileService : IProfileService
              {
                  private readonly IUsersService _usersService;
          
                  public CustomUserProfileService(IUsersService usersService)
                  {
                      _usersService = usersService;
                  }
          
                  public async Task GetProfileDataAsync(ProfileDataRequestContext context)
                  {
                      var subjectId = context.Subject.GetSubjectId();
                      var claimsForUser = await _usersService.GetUserClaimsBySubjectIdAsync(subjectId);
                      context.IssuedClaims = claimsForUser.Select(c => new Claim(c.ClaimType, c.ClaimValue)).ToList();
                  }
          
                  public async Task IsActiveAsync(IsActiveContext context)
                  {
                      var subjectId = context.Subject.GetSubjectId();
                      context.IsActive = await _usersService.IsUserActiveAsync(subjectId);
                  }
              }
          }
          سرویس پروفایل، توسط سرویس کاربران برنامه که در ابتدای مطلب آن‌را تهیه کردیم، امکان دسترسی به اطلاعات پروفایل کاربران را مانند Claims او، پیدا می‌کند.
          در متدهای آن، ابتدا subjectId و یا همان Id منحصربفرد کاربر جاری سیستم، دریافت شده و سپس بر اساس آن می‌توان از usersService، جهت دریافت اطلاعات مختلف کاربر، کوئری گرفت و نتیجه را در خواص context جاری، برای استفاده‌های بعدی، ذخیره کرد.

          اکنون به کلاس src\IDP\DNT.IDP\Startup.cs مراجعه کرده و متد AddTestUsers را با AddCustomUserStore جایگزین می‌کنیم:
          namespace DNT.IDP
          {
              public class Startup
              {
                  public void ConfigureServices(IServiceCollection services)
                  {
                      services.AddIdentityServer()
                       .AddDeveloperSigningCredential()
                       .AddCustomUserStore()
                       .AddInMemoryIdentityResources(Config.GetIdentityResources())
                       .AddInMemoryApiResources(Config.GetApiResources())
                       .AddInMemoryClients(Config.GetClients());
          تا اینجا فقط این سرویس‌های جدید را ثبت کرده‌ایم، اما هنوز کار خاصی را انجام نمی‌دهند و باید از آن‌ها در برنامه استفاده کرد.


          اتصال IdentityServer به User Store سفارشی

          در ادامه، سازنده‌ی کنترلر DNT.IDP\Quickstart\Account\AccountController.cs را بررسی می‌کنیم:
                  public AccountController(
                      IIdentityServerInteractionService interaction,
                      IClientStore clientStore,
                      IAuthenticationSchemeProvider schemeProvider,
                      IEventService events,
                      TestUserStore users = null)
                  {
                      _users = users ?? new TestUserStore(TestUsers.Users);
          
                      _interaction = interaction;
                      _clientStore = clientStore;
                      _schemeProvider = schemeProvider;
                      _events = events;
                  }
          - سرویس توکار IIdentityServerInteractionService، کار تعامل برنامه با IdentityServer4‌  را انجام می‌دهد.
          - IClientStore پیاده سازی محل ذخیره سازی اطلاعات کلاینت‌ها را ارائه می‌دهد که در حال حاضر توسط متد استاتیک Config در اختیار آن قرار می‌گیرد.
          - IEventService رخ‌دادهایی مانند لاگین موفقیت آمیز یک کاربر را گزارش می‌دهد.
          - در آخر، TestUserStore تزریق شده‌است که می‌خواهیم آن‌را با User Store سفارشی خودمان جایگزین کنیم.  بنابراین در ابتدا TestUserStore را با UserStore سفارشی خودمان جایگزین می‌کنیم:
                  private readonly TestUserStore _users;
                  private readonly IUsersService _usersService;
                  public AccountController(
              // ...
                      IUsersService usersService)
                  {
                      _usersService = usersService;
              // ...
                  }
          فعلا فیلد TestUserStore را نیز سطح کلاس جاری باقی نگه می‌داریم. از این جهت که قسمت‌های لاگین خارجی سیستم (استفاده از گوگل، توئیتر و ...) هنوز از آن استفاده می‌کنند و آن‌را در قسمتی دیگر تغییر خواهیم داد.
          پس از معرفی فیلد usersService_، اکنون در قسمت زیر از آن استفاده می‌کنیم:
          در اکشن متد لاگین، جهت بررسی صحت نام کاربری و کلمه‌ی عبور و همچنین یافتن کاربر متناظر با آن:
                  public async Task<IActionResult> Login(LoginInputModel model, string button)
                  {
              //...
                      if (ModelState.IsValid)
                      {
                          if (await _usersService.AreUserCredentialsValidAsync(model.Username, model.Password))
                          {
                              var user = await _usersService.GetUserByUsernameAsync(model.Username);
          تا همینجا برنامه را کامپایل کرده و اجرا کنید. پس از لاگین در آدرس https://localhost:5001/Gallery/IdentityInformation، هنوز اطلاعات User Claims کاربر وارد شده‌ی به سیستم نمایش داده می‌شوند که بیانگر صحت عملکرد CustomUserProfileService است.


          افزودن امکان ثبت کاربران جدید به برنامه‌ی IDP

          پس از اتصال قسمت login برنامه‌ی IDP به بانک اطلاعاتی، اکنون می‌خواهیم امکان ثبت کاربران را نیز به آن اضافه کنیم.
          این قسمت شامل تغییرات ذیل است:
          الف) اضافه شدن RegisterUserViewModel
          این ViewModel که فیلدهای فرم ثبت‌نام را تشکیل می‌دهد، ابتدا با نام کاربری و کلمه‌ی عبور شروع می‌شود:
              public class RegisterUserViewModel
              {
                  // credentials       
                  [MaxLength(100)]
                  public string Username { get; set; }
          
                  [MaxLength(100)]
                  public string Password { get; set; }
          سپس سایر خواصی که در اینجا اضافه می‌شوند:
              public class RegisterUserViewModel
              {
             // ...
          
                  // claims 
                  [Required]
                  [MaxLength(100)]
                  public string Firstname { get; set; }
          
                  [Required]
                  [MaxLength(100)]
                  public string Lastname { get; set; }
          
                  [Required]
                  [MaxLength(150)]
                  public string Email { get; set; }
          
                  [Required]
                  [MaxLength(200)]
                  public string Address { get; set; }
          
                  [Required]
                  [MaxLength(2)]
                  public string Country { get; set; }
          در کنترلر UserRegistrationController، تبدیل به UserClaims شده و در جدول مخصوص آن ذخیره خواهند شد.
          ب) افزودن UserRegistrationController
          این کنترلر، RegisterUserViewModel را دریافت کرده و سپس بر اساس آن، شیء User ابتدای بحث را تشکیل می‌دهد. ابتدا نام کاربری و کلمه‌ی عبور را در جدول کاربران ثبت می‌کند و سپس سایر خواص این ViewModel را در جدول UserClaims:
          varuserToCreate=newUser
          {
            Password=model.Password.GetSha256Hash(),
            Username=model.Username,
            IsActive=true
          };
          userToCreate.UserClaims.Add(newUserClaim("country",model.Country));
          userToCreate.UserClaims.Add(newUserClaim("address",model.Address));
          userToCreate.UserClaims.Add(newUserClaim("given_name",model.Firstname));
          userToCreate.UserClaims.Add(newUserClaim("family_name",model.Lastname));
          userToCreate.UserClaims.Add(newUserClaim("email",model.Email));
          userToCreate.UserClaims.Add(newUserClaim("subscriptionlevel","FreeUser"));
          ج) افزودن RegisterUser.cshtml
          این فایل، view متناظر با ViewModel فوق را ارائه می‌دهد که توسط آن، کاربری می‌تواند اطلاعات خود را ثبت کرده و وارد سیستم شود.
          د) اصلاح فایل ViewImports.cshtml_ جهت تعریف فضای نام UserRegistration
          در RegisterUser.cshtml از RegisterUserViewModel استفاده می‌شود. به همین جهت بهتر است فضای نام آن‌را به ViewImports اضافه کرد.
          ه) افزودن لینک ثبت نام به صفحه‌ی لاگین در Login.cshtml
          این لینک دقیقا در ذیل چک‌باکس Remember My Login اضافه شده‌است.


          اکنون اگر برنامه را اجرا کنیم، ابتدا مشاهده می‌کنیم که صفحه‌ی لاگین به همراه لینک ثبت نام ظاهر می‌شود:


          و پس از کلیک بر روی آن، صفحه‌ی ثبت کاربر جدید به صورت زیر نمایش داده خواهد شد:


          برای آزمایش، کاربری را ثبت کنید. پس از ثبت اطلاعات، بلافاصله وارد سیستم خواهید شد. البته چون در اینجا subscriptionlevel به FreeUser تنظیم شده‌است، این کاربر یکسری از لینک‌های برنامه‌ی MVC Client را به علت نداشتن دسترسی، مشاهده نخواهد کرد.



          کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید.
          برای اجرای برنامه:
          - ابتدا به پوشه‌ی src\WebApi\ImageGallery.WebApi.WebApp وارد شده و dotnet_run.bat آن‌را اجرا کنید تا WebAPI برنامه راه اندازی شود.
          - سپس به پوشه‌ی src\IDP\DNT.IDP مراجعه کرده و و dotnet_run.bat آن‌را اجرا کنید تا برنامه‌ی IDP راه اندازی شود.
          - در آخر به پوشه‌ی src\MvcClient\ImageGallery.MvcClient.WebApp وارد شده و dotnet_run.bat آن‌را اجرا کنید تا MVC Client راه اندازی شود.
          اکنون که هر سه برنامه در حال اجرا هستند، مرورگر را گشوده و مسیر https://localhost:5001 را درخواست کنید. در صفحه‌ی login نام کاربری را User 1 و کلمه‌ی عبور آن‌را password وارد کنید.
          مطالب
          آشنایی با WPF قسمت دوم: Layouts بخش اول
          layout‌‌‌ها یکی از مهمترین قسمت‌های یک برنامه‌ی کاربردی هستند. چیدمان کنترل‌ها روی یک ناحیه با دادن مختصات پیکسلی ثابت، ممکن است در یک محیط محدود خود را خوب نشان بدهد ولی به زودی با تغییر محیط برنامه و یا تغییر وضوح تصویر صفحه نمایش، برنامه از کنترل خارج خواهد شد؛ در نتیجه استفاده از Layout‌ها یا پنل‌ها در WPF امری حیاتی و مهم هستند. layout‌‌ها که با نام container هم شناخته می‌شوند وظیفه دارند که بگویند چه کنترل‌هایی در کجا و چگونه باید در صفحه‌ی برنامه قرار بگیرند. پنل‌های توکار در WPF به دسته‌های زیر تقسیم می‌شوند:
          • Grid Panel
          • Stack Panel
          • Dock Panel
          • Wrap Panel
          • Canvas Panel

          StackPanel

          این پنل یکی از ساده‌ترین و سودمندترین پنل هاست که بر اساس جهت Orientation افقی یا عمودی که به آن تنظیم می‌شود، کنترل‌ها را کنار یکدیگر یا زیر یکدیگر قرار می‌دهد. این کنترل برای ایجاد و تهیه لیست‌های مختلف مناسب است. در ساختار داخلی کنترل‌های لیست و کامبو و منو‌ها که در WPF موجود هستند این پنل استفاده شده است. کد زیر یک نمونه کاربرد Stack Panel را نشان می‌دهد که به صورت عمودی چینش شده است.
          <StackPanel>
            <TextBlock Margin="10" FontSize="20">How do you like your coffee?</TextBlock>
            <Button Margin="10">Black</Button>
            <Button Margin="10">With milk</Button>
            <Button Margin="10">Latte machiato</Button>
            <Button Margin="10">Chappuchino</Button>
          </StackPanel>

          نکته‌ی مهم اینکه میتوانید در اینجا از یک nested layout هم استفاده کنید بدین صورت که یک layout را داخل یک layout دیگر قرار دهید. کد زیر ترکیب دو stack panel را به صورت افقی و عمودی به ما نشان می‌دهد:

          <StackPanel Orientation="Vertical"> <!-- Vertical is the default -->
            <Label Background="Red">Red 1</Label>
            <Label Background="LightGreen">Green 1</Label>
            <StackPanel Orientation="Horizontal">
              <Label Background="Red">Red 2</Label>
              <Label Background="LightGreen">Green 2</Label>
              <Label Background="LightBlue">Blue 2</Label>
              <Label Background="Yellow">Yellow 2</Label>
              <Label Background="Orange">Orange 2</Label>
            </StackPanel>
            <Label Background="LightBlue">Blue 1</Label>
            <Label Background="Yellow">Yellow 1</Label>
            <Label Background="Orange">Orange 1</Label>
          </StackPanel>


          Dock Panel

          احتمالا به خاطر نامش، نحوه کارش را حدس زده اید. این پنل، اشیاء موجود را در 4 جهت و مرکز می‌چسباند. مشخص نمودن جهت چسبیده شدن هر کنترل توسط خاصیت DockPanel.Dock صورت می‌گیرد و مقدار Left، مقدار پیش فرض است. در صورتی که بخواهید المانی را در مرکز بچسبانید باید آن را به عنوان آخرین المان معرفی کرده و در Dock Panel مقدار خاصیت LastChildFill را با True برابر کنید.

          <DockPanel LastChildFill="True">
              <Button Content="Dock=Top" DockPanel.Dock="Top"/>
              <Button Content="Dock=Bottom" DockPanel.Dock="Bottom"/>
              <Button Content="Dock=Left"/>
              <Button Content="Dock=Right" DockPanel.Dock="Right"/>
              <Button Content="LastChildFill=True"/>
          </DockPanel>


          به نحوه‌ی تعریف خاصیت DockPanel.Dock دقت کنید به این نوع خاصیت‌ها،  Attached Dependency Property (شاید در فارسی بتوانیم خاصیت‌های وابستگی متصل صدا بزنیم) می‌گویند. این خاصیت‌ها نوع خاصی از خاصیت‌های وابستگی هستند که به شما اجازه می‌دهند مقداری را به شیء‌ایی نسبت دهید که آن شیء چیزی در مورد آن نمی‌داند. بهترین مثال در مورد این ویژگی، پنل‌ها هستند که یکی از موارد استفاده‌ی از آن را در بالا می‌بینید. هر پنل می‌تواند تا بی نهایت المان فرزند داشته باشد که هر المان باید خواصش توسط پنل مشخص گردد. ولی اگر پنل ما تعداد زیادی فرزند داشته باشد، نوشتن خواص هر کدام از فرزندها داخل تگ پنل، کاری غیر ممکن است. اینجاست که این نوع خاصیت‌ها خودشان را نشان می‌دهند. پس به این نحو مقادیر، داخل کنترل هر تگ تعریف می‌شود ولی توسط پنل مورد استفاده قرار می‌گیرد. نحوه‌ی نوشتن این نوع خاصیت: ابتدا یک پیشوند از نوع تگ پنل را در ابتدا آورده و سپس بعد از .(نقطه) نام خاصیت را ذکر می‌کنیم.

          نحوه‌ی تعریف این نوع خاصیت‌ها در یک کلاس به صورت زیر است که برای شیء یا پنل canvas می‌باشد:

          public static readonly DependencyProperty TopProperty =
              DependencyProperty.RegisterAttached("Top", 
              typeof(double), typeof(Canvas),
              new FrameworkPropertyMetadata(0d,
                  FrameworkPropertyMetadataOptions.Inherits));
           
          public static void SetTop(UIElement element, double value)
          {
              element.SetValue(TopProperty, value);
          }
           
          public static double GetTop(UIElement element)
          {
              return (double)element.GetValue(TopProperty);
          }
          در مثال dockPanel بالا در هر طرف تنها یک المان قرار دادیم. برای قرار دادن المان‌های بیشتر در طرفین، تنها ذکر یک المان جدید با خاصیت Dockpanel.dock کافی است و الویت نمایش آن‌ها بر اساس ترتیب نوشتن تگها توسط شماست. مثال زیر این نکته را نشان می‌دهد:
              <Button Content="Dock=Top" DockPanel.Dock="Top"/>
                  <Button Content="Dock=Bottom" DockPanel.Dock="Bottom"/>
                  <Button Content="Dock=Left"/>
                  <Button Content="Dock=Left2"/>
                  <Button Content="Right2" DockPanel.Dock="Right"/>
                  <Button Content="Dock=Right" DockPanel.Dock="Right"/>
                  <Button Content="LastChildFill=True"/>



          Wrap Panel
          این پنل بسیار شبیه StackPanel هست ولی مثل آن اشیاء را در یک سطر یا ستون ادامه نمی‌دهد؛ بلکه با رسیدن به انتهای پنجره، سطر یا ستون جدیدی را آغاز می‌کند. در stack panel با پایان پنجره، ادامه اشیا آن قابل مشاهده نبود ولی در این شیء با اتمام و رسیدن به لبه‌ی پنجره، اشیاء در سر جدید (افقی) یا ستون جدید (عمودی) نمایش داده می‌شوند. این پنل‌ها می‌توانند در ساخت تب‌ها و نوار ابزار استفاده شوند.

          Canvas Panel

          پایه‌ای‌ترین layout موجود در WPF است. موقعیت قرارگیری المان‌های فرزندش بر اساس نقاط تعیین شده است.این پنل بیشتر برای رسم اشکال و گرافیک دو بعدی مناسب است و اصلا برای قرارگیری کنترل‌های WPF روی آن توصیه نمی‌شود و مشکل winform‌ها در آن رخ خواهد داد.

          شروع ترسیم یک شکل دو بعدی روی آن بر اساس دوتا از چهار "خاصیت‌های وابستگی متصل" صورت می‌گیرد که به شرح زیر هستند:

          • Canvas.LEFT
          • Canvas.RIGHT
          • Canvas.TOP
          • Canvas.BOTTOM

          نمونه از کد نوشته شده آن به صورت زیر است:

          <Canvas>
              <Rectangle Canvas.Left="40" Canvas.Top="31" Width="63" Height="41" Fill="Blue"  />
              <Ellipse Canvas.Left="130" Canvas.Top="79" Width="58" Height="58" Fill="Blue"  />
              <Path Canvas.Left="61" Canvas.Top="28" Width="133" Height="98" Fill="Blue" 
                    Stretch="Fill" Data="M61,125 L193,28"/>
          </Canvas>

          ترتیب قرارگیری اشکال روی هم در canvas به ترتیبی انجام می‌گیرد که در XAML نوشته اید ولی می‌توان با استفاده از خاصیت Canvas.ZIndex این ترتیب را تغییر داد.

          <Canvas>
              <Ellipse Fill="Green" Width="60" Height="60" Canvas.Left="30" Canvas.Top="20"    
                       Canvas.ZIndex="1"/>
              <Ellipse Fill="Blue"  Width="60" Height="60" Canvas.Left="60" Canvas.Top="40"/>
          </Canvas>
          شکل زیر در سمت راست، نتیجه‌ی کد بالاست ولی بدون ذکر ZIndex شکل سمت چپ نتیجه‌ی کار خواهد بود.

          ViewBox
          شاید عده‌ای به سختی آن را یک Layout بدانند و بیشتر آن را یک کنترل معمولی می‌شناسند ولی وظیفه‌ی آن بسیار شبیه Layout هاست. خصوصیتی که این شیء دارد این است که با تغییر اندازه محیط برنامه به هر نحوی، یک تغییر مقیاس روی اشیاء داخل آن رخ می‌دهد و کنترل‌ها به همراه متون و هر چیزی که در درخت منطقی و بصری است Scale آن تغییر می‌یابند.
          نمونه‌ی کد زیر را تست کنید تا تفاوت بین دو Button را ببینید:
            <StackPanel Orientation="Vertical">
                  <Button Content="Test" />
                  <Viewbox Stretch="Uniform">
                      <Button Content="Test" />
                  </Viewbox>
              </StackPanel>
          نتیجه‌ی کار:

          در بخش دوم Layout‌ها مبحث گرید و ساخت Layout اختصاصی و تعدادی از خاصیت‌ها را بررسی خواهیم کرد.

          مطالب
          اصول و قراردادهای نام‌گذاری در دات‌نت
          نامگذاری (Naming) اشیا یک برنامه شاید در نگاه اول دارای اهمیت بالایی نباشه، اما تجربه نشون داده که در پروژه‌های بزرگ که با کمک چندین مجموعه به انجام میرسه نامگذاری صحیح و اصولی که از یکسری قواعد کلی و مناسب پیروی میکنه میتونه به پیشبرد اهداف و مدیریت راحتتر برنامه کمک بسیاری بکنه.
          بیشتر موارد اشاره شده در این مطلب از کتاب جامع و مفید Framework Design Guidelines اقتباس شده که خوندن این کتاب مفید رو به خوانندگان توصیه میکنم.
          برای کمک به نوشتن اصولی و راحتتر سورسهای برنامه‌ها در ویژوال استودیو نرم افزارهای متعددی وجود داره که با توجه به تجربه شخصی خودم نرم افزار Resharper  محصول شرکت Jetbrains یکی از بهترین هاست که در مورد خاص مورد بحث در این مطلب نیز بسیار خوب عمل میکنه.
          برخی از موارد موجود در مطلب جاری نیز از قراردادهای پیشفرض موجود در نرم افزار Resharper نسخه 6.0 برگرفته شده است و قسمتی نیز از تجربه شخصی خودم و سایر دوستان و همکاران بوده است.
          اصل این مطلب حدود یکسال پیش تهیه شده و اگر نقایصی وجود داره لطفا اشاره کنین.

          اصول و قراردادهای نام‌گذاری در دات‌نت
          انواع نام‌گذاری
          نام‌گذاری اشیا در حالت کلی را می‌توان به سه روش زیر انجام داد:
          1. Pascal Casing: در این روش حرف اول هر کلمه در نام شی به صورت بزرگ نوشته می‌شود.
          FirstName
          2. camel Casing: حرف اول در اولین کلمه نام هر شی به صورت کوچک و حرف اول بقیه کلمات به صورت بزرگ نوشته می‌شود.
          firstName
          3. Hungarian: در این روش برای هر نوع شی موجود یک پیشوند درنظر گرفته می‌شود تا از روی نام شی بتوان به نوع آن پی برد. در ادامه و پس از این پیشوندها سایر کلمات بر اساس روش Pascal Casing نوشته می‌شوند.
          strFirstName
          lblFirstName
          نکته: استفاده از این روش به جز در نام‌گذاری کنترل‌های UI منسوخ شده است.

          قراردادهای کلی
          1. نباید نام اشیا تنها در بزرگ یا کوچک بودن حروف با هم فرق داشته باشند. به عنوان مثال نباید دو کلاس با نام‌های MyClass و myClass داشته باشیم. هرچند برخی از زبان‌ها case-sensitive هستند اما برخی دیگر نیز چنین قابلیتی ندارند (مثل VB.NET). بنابراین اگر بخواهیم کلاس‌های تولیدی ما در تمام محیط‌ها و زبان‌های برنامه نویسی قابل اجرا باشند باید از این قرارداد پیروی کنیم.
          2. تا آنجا که امکان دارد باید از به‌کار بردن مخفف کلمات در نام‌گذاری اشیا دوری کنیم. مثلا به جای استفاده از GetChr باید از GetCharacter استفاده کرد.
          البته در برخی موارد که مخفف واژه موردنظر کاربرد گسترده ای دارد می‌توان از عبارت مخفف نیز استفاده کرد. مثل UI به جای UserInterface و یا IO به جای InputOutput.
           
          آ. اصول نام‌گذاری فضای نام (namespace)
          1. اساس نام‌گذاری فضای نام باید از قاعده زیر پیروی کند:
          <Company>.<Technology|Produt|Project>[.<Feature>][.<SubNamespace>]
          ( < > : اجباری        [ ] : اختیاری )
          2. برای نام‌گذاری فضای نام باید از روش Pascal Casing استفاده شود.
          3. در هنگام تعریف فضاهای نام به وابستگی آنها توجه داشته باشید. به عنوان مثال اشیای درون یک فضای نام پدر نباید به اشیای درون فضای نام یکی از فرزندانش وابسته باشد. مثلا در فضای نام System نباید اشیایی وجود داشته باشند که به اشیای درون فضای نام System.UI وابسته باشند.
          4. سعی کنید از نام‌ها به صورت جمع برای عناوین فضای نام استفاده کنید. مثلا به جای استفاده از Kara.CSS.HQ.Manager.Entity از Kara.CSS.HQ.Manager.Entities استفاده کنید. البته برای مورادی که از عناوین مخفف و یا برندهای خاص استفاده کرده‌اید از این قرارداد پیروی نکنید. مثلا نباید از عنوانی شبیه به Kara.CSS.Manager.IOs استفاده کنید.
          5. از عنوانی پایدار و مستقل از نسخه محصول برای بخش دوم عنوان فضای نام استفاده کنید. بدین معنی که این عناوین با گذر زمان و تغییر و تحولات در محتوای محصول و یا تولیدکننده نباید تغییر کنند. مثال:
          Microsoft.Reporting.WebForms
          Kara.Support.Manager.Enums
          Kara.CSS.HQ.WebUI.Configuration
          6. از عناوین یکسان برای فضای نام و اشیای درون آن استفاده نکنید. مثلا نباید کلاسی با عنوان Manager در فضای نام Kara.CSS.Manager وجود داشته باشد.
          7. از عناوین یکسان برای اشیای درون فضاهای نام یک برنامه استفاده نکنید. مثلا نباید دو کلاس با نام Package در فضاهای نام Kara.CSS.Manger و Kara.CSS.Manger.Entities داشته باشید.

          ب. اصول نام‌گذاری کلاس‌ها و Structها
          1. عنوان کلاس باید اسم یا موصوف باشد.
          2. در نام‌گذاری کلاس‌ها باید از روش Pascal Casing استفاده شود.
          3. نباید از عناوین مخففی که رایج نیستند استفاده کرد.
          4. از پیشوندهای زائد مثل C یا Cls نباید استفاده شود.
          5. نباید از کاراکترهایی به غیر از حروف (و یا در برخی موارد خیلی خاص، شماره نسخه) در نام‌گذاری کلاس‌ها استفاده شود.
          مثال:
          درست:
          PackageManager , PacakgeConfigGenerator
          Circle , Utility , Package
          نادرست:
          CreateConfig , classdata
          CManager , ClsPackage , Config_Creator , Config1389
          6. در نام‌گذاری اشیای Generic از استفاده از عناوینی چون Element, Node, Log و یا Message پرهیز کنید. این کار موجب به‌وجودآمدن کانفلیکت در عناوین اشیا می‌شود. در این موارد بهتر است از عناوینی چون FormElement, XmlNode, EventLog و SoapMessage استفاده شود. درواقع بهتر است نام اشیای جنریک، برای موارد موردنیاز، کاملا اختصاصی و درعین حال مشخص‌کننده محتوا و کاربرد باشند.
          7. از عناوینی مشابه عناوین کتابخانه‌های پایه دات‌نت برای اشیای خود استفاده نکنید. مثلا نباید کلاسی با عنوان Console, Parameter, Action و یا Data را توسعه دهید. هم‌چنین از کابرد عناوینی مشابه اشیای موجود در فضاهای نام غیرپایه دات‌نت و یا غیردات‌نتی اما مورداستفاده در پروژه خود که محتوا و کاربردی مختص همان فضای نام دارند پرهیز کنید. مثلا نباید کلاسی با عنوان Page در صورت استفاده از فضای نام System.Web.UI تولید کنید.
          8. سعی کنید در مواردی که مناسب به‌نظر می‌رسد از عنوان کلاس پایه در انتهای نام کلاس‌های مشتق‌شده استفاده کنید. مثل FileStream که از کلاس Stream مشتق شده است. البته این قاعده در تمام موارد نتیجه مطلوب ندارد. مثلا کلاس Button که از کلاس Control مشتق شده است. بنابراین در به‌کاربردن این مورد بهتر است تمام جوانب و قواعد را درنظر بگیرید.

          پ. اصول نام‌گذاری مجموعه‌ها (Collections)
          یک مجموعه در واقع یک نوع کلاس خاص است که حاوی مجموعه‌ای از داده‌هاست و از همان قوانین کلاس‌ها برای نام‌گذاری آن‌ها استفاده می‌شود.
          1. بهتر است در انتهای عنوان مجموعه از کلمه Collection استفاده شود. مثال:
          CenterCollection , PackageCollection
          2. البته درصورتی‌که کلاس مورد نظر ما رابط IDictionary را پیاده‌سازی کرده باشد بهتر است از پسوند Dictionary استفاده شود.

          ت. اصول نام‌گذاری Delegateها
          1. عنوان یک delegate باید اسم یا موصوف باشد.
          2. در نام‌گذاری delegateها باید از روش Pascal Casing استفاده شود.
          3. نباید از عناوین مخففی که رایج نیستند استفاده کرد.
          4. از پیشوندهای زائد مثل D یا del نباید استفاده شود.
          5. نباید از کاراکترهایی به غیر از حروف در نام‌گذاری delegateها استفاده شود.
          6. نباید در انتهای نام یک delegate از عبارت Delegate استفاده شود.
          7. بهتر است که درصورت امکان در انتهای نام یک delegate که برای Event Handler استفاده نمی‌شود از عبارت Callback استفاده شود. البته تنها درصورتی‌که معنی و مفهوم مناسب را داشته باشد.
          مثال:
          نحوه تعریف یک delegate
          public delegate void Logger (string log);
          public delegate void LoggingCallback (object sender, string reason);

          ث. اصول نام‌گذاری رویدادها (Events)
          1. عنوان یک رویداد باید فعل یا مصدر باشد.
          2. در نام‌گذاری کلاس‌ها باید از روش Pascal Casing استفاده شود.
          3. نباید از عناوین مخففی که رایج نیستند استفاده کرد.
          4. از پیشوندهای زائد نباید استفاده شود.
          5. نباید از کاراکترهایی به غیر از حروف در نام‌گذاری رویدادها استفاده شود.
          6. بهتر است از پسوند EventHandler در عنوان هندلر رویداد استفاده شود.
          7. از به کاربردن عباراتی چون AfterXXX و یا BeforeXXX برای نمایش رویدادهای قبل و یا بعد از رخداد خاصی خودداری شود. به جای آن باید از اسم مصدر (شکل ingدار فعل) برای نام‌گذاری رویداد قبل از رخداد و هم‌چنین شکل گذشته فعل برای نام‌گذاری رویداد بعد از رخداد خاص استفاده کرد.
          مثلا اگر کلاسی دارای رویداد Open باشد باید از عنوان Openning برای رویداد قبل از Open و از عنوان Opened برای رویداد بعد از Open استفاده کرد.
          8. نباید از پیشوند On برای نام‌گذاری رویداد استفاده شود.
          9. همیشه پارامتر e و sender را برای آرگومانهای رویداد پیاده سازی کنید. sender که از نوع object است نمایش دهنده شیی است که رویداد مربوطه را به وجود آورده است و e درواقع آرگومانهای رویداد مربوطه است.
          10. عنوان کلاس‌آرگومان‌های رویداد باید دارای پسوند EventArgs باشد.
          مثال:
          عنوان کلاس‌آرگومان:
          AddEventArgs , EditEventArgs , DeleteEventArgs
          عنوان رویداد:
          Adding , Add , Added
          تعریف یک EventHandler:
          public delegate void <EventName>EventHandler  (object sender, <EventName>EventArgs e);
          نکته: نیاز به تولید یک EventHandler مختص توسعه یک برنامه به‌ندرت ایجاد می‌شود. در اکثر موارد می‌توان با استفاده از کلاس جنریک <EventHandler<TEventArgs تمام احتیاجات خود را برطرف کرد.
          تعریف یک رویداد:
          public event EventHandler <AddEventArgs> Adding;

          ج. اصول نام‌گذاری Attributeها
          1. عنوان یک attribute باید اسم یا موصوف باشد.
          2. در نام‌گذاری attributeها باید از روش Pascal Casing استفاده شود.
          3. نباید از عناوین مخففی که رایج نیستند استفاده کرد.
          4. از پیشوندهای زائد مثل A یا atr نباید استفاده شود.
          5. نباید از کاراکترهایی به غیر از حروف در نام‌گذاری attributeها استفاده شود.
          6. بهتر است در انتهای نام یک attribute از عبارت Attribute استفاده شود.
          مثال:
          DisplayNameAttribute , MessageTypeAttribute

          چ. اصول نام‌گذاری Interfaceها
          1. در ابتدای عنوان interface باید از حرف I استفاده شود.
          2. نام باید اسم، موصوف یا صفتی باشد که interface را توصیف می‌کند.
          به عنوان مثال:
          IComponent  (اسم)
          IConnectionProvider (موصوف)
          ICloneable (صفت)
          3. از روش Pascal Casing استفاده شود.
          4. خودداری از بکاربردن عبارات مخفف غیررایج.
          5. باید تنها از کاراکترهای حرفی در نام interface استفاده شود.
           
          ح. اصول نام‌گذاری Enumerationها
          1. استفاده از روش Pascal Casing
          2. خودداری از کاربرد عبارات مخفف غیررایج
          3. تنها از کاراکترهای حرفی در نام Enumretionها استفاده شود.
          4. نباید از پسوند یا پیشوند Enum یا Flag استفاده شود.
          5. اعضای یک Enum نیز باید با روش Pascal Casing نام‌گذاری شوند.
          مثال:
          public enum FileMode {
              Append,
              Read, …
          }
          6. درصورتی‌که enum موردنظر از نوع flag نیست باید عنوان آن مفرد باشد. 
          7. درصورتی‌که enum موردنظر برای کاربرد flag طراحی شده باشد نام آن باید جمع باشد. مثال:
          [Flag]
          public enum KeyModifiers {
              Alt = 1, 
              Control = 2,
              Shift = 4
          }
          8. از به‌کاربردن پسوند و یا پیشوندهای اضافه در نام‌گذاری اعضای یک enum نیز پرهیز کنید. مثلا نام‌گذاری زیر نادرست است:
          public enum OperationState {
              DoneState, 
              FaultState,
              RollbackState
          }

          خ. اصول نام‌گذاری متدها
          1. نام متد باید فعل یا ترکیبی از فعل و اسم یا موصوف باشد.
          2. باید از روش Pascal Casing استفاده شود.
          3. خودداری از بکاربردن عبارات مخفف غیررایج و یا استفاده زیاد از اختصار
          4. تنها از کاراکترهای حرفی برای نام متد استفاده شود.
          مثال:
          AddDays , Save , DeleteRow , BindData , Close , Open

          د. اصول نام‌گذاری Propertyها
          1. نام باید اسم، صفت یا موصوف باشد.
          2. باید از روش Pascal Casing استفاده شود.
          3. خودداری از بکاربردن عبارات مخفف غیررایج.
          4. تنها از کاراکترهای حرفی برای نام‌گذاری پراپرتی استفاده شود.
          مثال:
          Radius , ReportType , DataSource , Mode , CurrentCenterId
          5. از عبارت Get در ابتدای هیچ Propertyای استفاده نکنید.
          6. نام خاصیت‌هایی که یک مجموعه برمی‌گرداند باید به صورت جمع باشد. عنوان این Propertyها نباید به‌صورت مفرد به همراه پسوند Collection یا List باشد. مثال:
          public CenterCollection Centers { get; set; }
          7. خواص Boolean را با عناوینی مثبت پیاده‌سازی کنید. مثلا به‌جای استفاده از CantRead از CanRead استفاده کنید. بهتر است این Propertyها پیشوندهایی چون Is, Can یا Has داشته باشند، البته تنها درصورتی‌که استفاده از چنین پیشوندهایی ارزش افزوده داشته و مفهوم آن را بهتر برساند. مثلا عنوان CanSeek مفهوم روشن‌تری نسبت به Seekable دارد. اما استفاده از Created خیلی بهتر از IsCreated است یا Enabled کاربرد به مراتب راحت‌تری از IsEnabled دارد.
          برای تشخیص بهتر این موارد بهتر است از روش ifسنجی استفاده شود. به عنوان مثال
          if (list.Contains(item))
          if (regularExpression.Matches(text))
          if (stream.CanSeek)
          if (context.Created)
          if (form.Enabled)
          مفهوم درست‌تری نسبت به موارد زیر دارند:
          if (list.IsContains(item))
          if (regularExpression.Match(text))
          if (stream.Seekable)
          if (context.IsCreated)
          if (form.IsEnabled)
          8. بهتر است در موارد مناسب عنوان Property با نام نوعش برابر باشد. مثلا
          public Color Color { get; set; }

          ذ. اصول نام‌گذاری پارامترها
          پارامتر درحالت کلی به آرگومان وروی تعریف شده برای یک متد گفته می‌شود.
          1. حتما از یک نام توصیفی استفاده شود. از نام‌گذاری پارامترها براساس نوعشان به‌شدت پرهیز کنید.
          2. از روش camel Casing استفاده شود.
          3. تنها از کاراکترهای حرفی برای نام‌گذاری پارامترها استفاده شود.
          مثال:
          firstName , e , id , packageId , centerName , name
          4. نکاتی برای نام‌گذاری پارامترهای Operator Oveloading:
          - برای operatorهای دو پارامتری (binary operators) از عناوین left و right برای پارامترهای آن استفاده کنید:
          public static MyType operator +(MyType left, MyType right)
          public static bool operator ==(MyType left, MyType right)
          - برای operatorهای تک‌پارامتری (unary operators) اگر برای پارامتر مورد استفاده هیچ عنوان توصیفی مناسبی پیدا نکردید حتما از عبارت value استفاده کنید:
          public static MyType operator ++(MyType value)
          - درصورتی‌که استفاده از عناوین توصیفی دارای ارزش افزوده بوده و خوانایی کد را بهتر می‌کند حتما از این نوع عناوین استفاده کنید:
          public static MyType operator /(MyType dividend, MyType divisor)
          - نباید از عبارات مخفف یا عناوینی با اندیس‌های عددی استفاده کنید:
          public static MyType operator -(MyType d1, MyType d2) // incorrect!

          ر. اصول نام‌گذاری متغیر (Variable)ها
          - نام متغیر باید اسم، صفت یا موصوف باشد.
          نام‌گذاری متغیرها باید با توجه به نوع آن انجام شود.
          1. متغیرهای عمومی (public) و protected و Constantها
          - استفاده از روش Pascal Casing
          - تنها از کاراکترهای حرفی برای نام متغیر عمومی استفاده شود.
          مثال:
          Area , DataBinder , PublicCacheName
          2. متغیرهای private (در سطح کلاس یا همان field)
          - نام این نوع متغیر باید با یک "_" شروع شود.
          - از روش camel Casing استفاده شود.
          مثال:
          _centersList
          _firstName
          _currentCenter
          3. متغیرهای محلی در سطح متد
          - باید از روش camel Casing استفاده شود.
          - تنها از کاراکترهای حرفی استفاده شود.
          مثال:
          parameterType , packageOperationTypeId

          ز. اصول نام‌گذاری کنترل‌های UI
          1. نام باید اسم یا موصوف باشد.
          2. استفاده از روش Hungarian !
          3. عبارت مخفف معرفی‌کننده کنترل، باید به اندازه کافی برای تشخیص نوع آن مناسب باشد.
          مثال:
          lblName (Label)
          txtHeader (TextBox)
          btnSave (Button)

          ژ. اصول نام‌گذاری Exceptionها
          تمام موارد مربوط به نام‌گذاری کلاس‌ها باید در این مورد رعایت شود. 
          1. باید از پسوند Exception در انتهای عنوان استفاده شود.
          مثال:
          ArgumentNullException , InvalidOperaionException

          س. نام‌گذاری اسمبلی‌ها و DLLها
          1. عناوینی که برای نام‌گذاری اسمبلی‌ها استفاده می‌شوند، باید نمایش‌دهنده محتوای کلی آن باشند. مثل:
          System.Data
          2. از روش زیر برای نام‌گذاری اسمبلی‌ها استفاده شود:
          <Company>.<Component>.dll
          <Company>.<Project|Product|Technology>.<Component>.dll
          مثل:
          Microsoft.CSharp.dll , Kara.CSS.Manager.dll

          ش. نام‌گذاری پارامترهای نوع (Generic (type parameter
          1. از حرف T برای پارامترهای تک‌حرفی استفاده کنید. مثل:
          public int IComparer<T> {…}
          public delegate bool Predicate<T> (T item)
          2. تمامی پارامترهای جنریک را با عناوینی توصیفی و مناسب که مفهوم و کاربرد آنرا برساند نام‌گذاری کنید، مگر آنکه یافتن چنین عباراتی ارزش افزوده‌ای در روشن‌تر کردن کد نداشته باشد. مثال:
          public int ISessionChannel<TSession> {…}
          public delegate TOutput Converter<TInput, TOutput> (TInput from)
          public class Nullable<T> {…}
          public class List<T> {…}
          3. در ابتدای عناوین توصیفی حتما از حرف T استفاده کنید.
          4. بهتر است تا به‌صورتی روشن نوع قید قرار داده شده بر روی پارامتری خاص را در نام آن پارامتر نمایش دهید. مثلا اگر قید ISession را برای پارامتری قرار دادید بهتر است نام آن پارامتر را TSession درنظر بگیرید.
           
          ص. نام‌گذاری کلید Resourceها
          به دلیل شباهت ساختاری که میان کلیدهای resource و propertyها وجود دارد قواعد نام‌گذاری propertyها در اینجا نیز معتبر هستند.
          1. از روش نام‌گذاری Pascal Casing برای کلیدهای resource استفاده کنید.
          2. از عناوین توصیفی برای این کلیدها استفاده کنید. سعی کنید تا حد امکان به هیچ وجه از عناوین کوتاه و یا مخففی که مفهوم را به‌صورت ناکامل می‌رساند استفاده نکنید. درواقع سعی کنید که خوانایی بیشتر کد را فدای فضای بیشتر نکنید.
          3. از کلیدواژه‌های CLR و یا زبان مورداستفاده برای برنامه‌نویسی در نام‌گذاری این کلیدها استفاده نکنید.
          4. تنها از حروف و اعداد و _ در نام‌گذاری این کلیدها استفاده کنید.
          5. سعی کنید از عناوین توصیفی همانند زیر برای پیام‌های مناسب خطاها جهت نمایش به کاربر برای کلیدهای مربوطه استفاده کنید. درواقع نام کلید باید ترکیبی از نام نوع خطا و یک آی‌دی مشخص‌کننده پیغام مربوطه باشد:
          ArgumentExceptionIllegalCharacters
          ArgumentExceptionInvalidName
          ArgumentExceptionFileNotFound
           
          نکاتی درمورد کلمات مرکب
          کلمات مرکب به کلماتی گفته می‌شود که در آن از بیش از یک کلمه با مفهوم مستقل استفاده شده باشد. مثل Callback یا FileName. 
          باید توجه داشت که با تمام کلمات موجود در یک کلمه مرکب نباید همانند یک کلمه مستقل رفتار کرد و حرف اول آن را در روش‌های نام‌گذاری موجود به‌صورت بزرگ نوشت. کلمات مرکبی وجود دارند که به آن‌ها closed-form گفته می‌شود. این کلمات مرکب با اینکه از 2 یا چند کلمه دارای مفهوم مستقل تشکیل شده‌اند اما به‌خودی‌خود دارای مفهوم جداگانه و مستقلی هستند. برای تشخیص این کلمات می‌توان به فرهنگ لغت مراجعه کرد (و یا به‌سادگی از نرم‌افزار Microsoft Word استفاده کرد) و دریافت که آیا کلمه مرکب موردنظر آیا مفهوم مستقلی برای خود دارد، یعنی درواقع آیا عبارتی closed-form است. با کلمات مرکب از نوع closed-form همانند یک کلمه ساده برخورد می‌شود! 
          در جدول زیر مثال‌هایی از عبارات رایج و نحوه درست و نادرست استفاده از هریک نشان داده شده است.

          Pascal Casing

          camel Casing

          Wrong

          Callback

          callback

          CallBack

          BitFlag

          bitFlag

          Bitflag / bitflag

          Canceled

          canceled

          Cancelled

          DoNot

          doNot

          Donot / Don’t

          Email

          email

          EMail

          Endpoint

          endpoint

          EndPoint / endPoint

          FileName

          fileName

          Filename / filename

          Gridline

          gridline

          GridLine / gridLine

          Hashtable

          hashtable

          HashTable / hashTable

          Id

          id

          ID

          Indexes

          indexes

          Indices

          LogOff

          logOff

          Logoff / LogOut !

          LogOn

          logOn

          Logon / LogIn !

          SignOut

          signOut

          Signout / SignOff

          SignIn

          signIn

          Signin / SignOn

          Metadata

          metadata

          MetaData / metaData

          Multipanel

          multipanel

          MultiPanel / multiPanel

          Multiview

          multiview

          MultiView / multiView

          Namespace

          namespace

          NameSpace / nameSpace

          Ok

          ok

          OK

          Pi

          pi

          PI

          Placeholder

          placeholder

          PlaceHolder / placeHolder

          UserName

          username

          Username / username

          WhiteSpace

          whiteSpace

          Whitespace / whitespace

          Writable

          writable

          Writeable / writeable

          همان‌طور که در جدول بالا مشاهد می‌شود در استفاده از قوانین عبارات مخفف دو مورد استثنا وجود دارد که عبارتند از Id و Ok. این کلمات باید همان‌طور که در اینجا نشان داده شده‌اند استفاده شوند.
           
          نکاتی درباره عبارات مخفف
          1. عبارات مخفف (Acronym) با خلاصه‌سازی کلمات (Abbreviation) فرق دارند. یک عبارت مخفف شامل حروف اول یک عبارت طولانی یا معروف است، درصورتی‌که خلاصه‌سازی یک عبارت یا کلمه از حذف بخشی از آن به‌دست می‌آید. تا آنجاکه امکان دارد از خلاصه‌سازی عبارات نباید استفاده شود. هم‌چنین استفاده از عبارات مخفف غیررایج توصیه نمی‌شود.
          مثال: IO و UI
          2. براساس تعریف، یک مخفف حداقل باید 2 حرف داشته باشد. نحوه برخورد با مخفف‌های دارای بیشتر از 2 حرف با مخفف‌های دارای 2 حرف باهم متفاوت است. با مخفف‌های دارای 2 حرف همانند کلمه‌ای یک حرفی! برخورد می‌شود، درصورتی‌که با سایر مخفف‌ها همانند یک کلمه کامل چندحرفی برخورد می‌شود. به‌عنوان مثال IOStream برای روش PascalCasing و ioStream برای روش camelCasing استفاده می‌شود. هم‌چنین از HtmlBody و htmlBody به‌ترتیب برای روش‌های Pascal و camel استفاده می‌شود.
          3. هیچ‌کدام از حروف یک عبارت مخفف در ابتدای یک واژه نام‌گذاری‌شده به روش camelCasing به‌صورت بزرگ نوشته نمی‌شود!
          نکته: موارد زیادی را می‌توان یافت که در ابتدا به‌نظر می‌رسد برای پیاده‌سازی آنها باید قوانین فوق را نقض کرد. این موارد شامل استفاده از کتابخانه‌های سایر پلتفرم‌ها (مثل MFC, HTML و غیره)، جلوگیری از مشکلات جغرافیایی! (مثلا در مورد نام کشورها یا سایر موقعیت‌های جغرافیایی)، احترام به نام افراد درگذشته، و مواردی از این دست می‌شود. اما در بیشتر قریب به اتفاق این موارد هم می‌توان بدون تقض قوانین فوق اقدام به نام‌گذاری اشیا کرد، بدون اینکه با تغییر عبارت اصلی (که موجب تطابق با این قوانین می‌شود) لطمه‌ای به مفهوم آن بزند. البته تنها موردی که به‌نظر می‌رسد می‌تواند قوانین فوق را نقض کند نام‌های تجاری هستند. البته استفاده از نام‌های تجاری توصیه نمی‌شود، چون این نام‌ها سریع‌تر از محتوای کتابخانه‌های برنامه‌نویسان تغییر می‌کنند!
          نکته: برای درک بهتر قانون "عدم استفاده از عبارات مخففی که رایج نیستند" مثالی از زبان توسعه دهندگان دات‌نت‌فریمورک ذکر می‌شود. در کلاس Color متد زیر با Overloadهای مختلف در دسترس است:
          public class Color {
              …
              public static Color FromArgb(…)
              { … }
          }
          همان‌طور که مشاهده می‌شود برای رعایت قوانین فوق به‌جای استفاده از ARGB از عبارت Argb استفاده شده است. اما این نحوه استفاده موجب شده تا این سوال به‌ظاهر خنده‌دار اما درست پیش‌آید:
          "چطور می‌شود در این کلاس رنگی را از ARGB تبدل کرد؟ هرچه که من میبینم فقط تبدیل از طریق (Argb) آرگومان b است!"
          حال در این نقطه به‌نظر می‌رسد که در اینجا باید قوانین فوق را نقض کرد و از عنوان FromARGB که مفهوم درست را می‌رساند استفاده کرد. اما با کمی دقت متوجه می‌شویم که این قوانین در ابتدا نیز با پیاده‌سازی نشان داده شده در قطعه کد بالا نقض شده‌اند! همه می‌دانیم که عبارت RGB مخفف معروفی برای عبارت Red Green Blue است. اما استفاده از ARGB برای افزودن کلمه Alpha به ابتدای عبارت مذکور چندان رایج نیست. پس استفاده از مخفف Argb از همان ابتدا اشتباه به‌نظر می‌رسد. بنابراین راه‌حل بهتر می‌تواند استفاده از عنوان FromAlphaRgb باشد که هم قوانین فوق را نقض نکرده و هم مفهوم را بهتر می‌رساند.
           
          دیگر نکات
          1. قراردادهای اشاره شده در این سند حاصل کار شبانه روزی تعداد بسیاری از برنامه نویسان در سرتاسر جهان در پروژه‌های بزرگ بوده است. این اصول کلی تنها برای توسعه آسان‌تر و سریع‌تر پروژه‌های بزرگ تعیین شده‌اند و همان‌طور که روشن است تنها ازطریق تجربه دست‌یافتنی هستند. بنابراین چه بهتر است که در این راه از تجارب بزرگان این عرصه بیشترین بهره برده شود.
          2. هم‌چنین توجه داشته باشید که بیشتر قراردادهای اشاره شده در این سند از راهنمای نام‌گذاری تیم توسعه BCL دات‌نت‌فریمورک گرفته شده است. این تیم طبق اعتراف خودشان زمان بسیار زیادی را برای نام‌گذاری اشیا صرف کرده‌اند و توصیه کرده‌اند که دیگران نیز برای نام‌گذاری، زمان مناسب و کافی را در توسعه پروژه‌ها درنظر بگیرند.
           
          نظرات مطالب
          نحوه استفاده از ViewModel در ASP.NET MVC

          برای اتصال ViewModel به مدل می‌نویسیم: یک خاصیت viewmodel ،خودت رو به یک خاصیت model انتساب بده پدرجان! همین! (البته حالت پیشرفته‌تر استفاده از AutoMapper هست که نخوای این‌ها رو اگه سختت هست دستی بنویسی)

          خواص مدل رابطه یک به یک داره با جدول شما در بانک اطلاعاتی. اون هم اطلاعاتش نیاز به اعتبار سنجی داره. ViewModel هم نیاز داره چون یک سری خاصیت اضافی عموما داره که در بانک اطلاعاتی نیست و فقط در View تعریف شده.

          مطالب
          MEF و الگوی Singleton

          در مورد معرفی مقدماتی MEF می‌توانید به این مطلب مراجعه کنید و در مورد الگوی Singleton به اینجا.


          کاربردهای الگوی Singleton عموما به شرح زیر هستند:
          1) فراهم آوردن دسترسی ساده و عمومی به DAL (لایه دسترسی به داده‌ها)
          2) دسترسی عمومی به امکانات ثبت وقایع سیستم در برنامه logging -
          3) دسترسی عمومی به تنظیمات برنامه
          و موارد مشابهی از این دست به صورتیکه تنها یک روش دسترسی به این اطلاعات وجود داشته باشد و تنها یک وهله از این شیء در حافظه قرار گیرد.

          با استفاده از امکانات MEF دیگر نیازی به نوشتن کدهای ویژه تولید کلاس‌های Singleton نمی‌باشد زیرا این چارچوب کاری دو نوع روش وهله سازی از اشیاء (PartCreationPolicy) را پشتیبانی می‌کند: Shared و NonShared . حالت Shared دقیقا همان نام دیگر الگوی Singleton است. البته لازم به ذکر است که حالت Shared ، حالت پیش فرض تولید وهله‌ها بوده و نیازی به ذکر صریح آن همانند ویژگی زیر نیست:
          [PartCreationPolicy(CreationPolicy.Shared)]

          مثال:
          فرض کنید قرار است از کلاس زیر تنها یک وهله بین صفحات یک برنامه‌ی Silverlight توزیع شود. با استفاده از ویژگی‌ Export به MEF اعلام کرده‌ایم که قرار است سرویسی را ارائه دهیم :

          using System;
          using System.ComponentModel.Composition;

          namespace SlMefTest
          {
          [Export]
          public class WebServiceData
          {
          public int Result { set; get; }

          public WebServiceData()
          {
          var rnd = new Random();
          Result = rnd.Next();
          }
          }

          }
          اکنون برای اثبات اینکه تنها یک وهله از این کلاس در اختیار صفحات مختلف قرار خواهد گرفت، یک User control جدید را به همراه یک دکمه که مقدار Result را نمایش می‌دهد به برنامه اضافه خواهیم کرد. دکمه‌ی دیگری را نیز به همین منظور به صفحه‌ی اصلی برنامه اضافه می‌کنیم.
          کدهای صفحه اصلی برنامه (که از یک دکمه و یک Stack panel جهت نمایش محتوای یوزر کنترل تشکیل شده) به شرح بعد هستند:
          <UserControl x:Class="SlMefTest.MainPage"
          xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
          xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
          xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
          xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
          mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="400">
          <StackPanel>
          <Button Content="MainPageButton" Height="23"
          HorizontalAlignment="Left"
          Margin="10,10,0,0" Name="button1"
          VerticalAlignment="Top" Width="98" Click="button1_Click" />
          <StackPanel Name="panel1" Margin="5"/>
          </StackPanel>
          </UserControl>

          using System.ComponentModel.Composition;
          using System.Windows;

          namespace SlMefTest
          {
          public partial class MainPage
          {
          [Import]
          public WebServiceData Data { set; get; }

          public MainPage()
          {
          InitializeComponent();
          this.Loaded += mainPageLoaded;
          }

          void mainPageLoaded(object sender, RoutedEventArgs e)
          {
          CompositionInitializer.SatisfyImports(this);
          panel1.Children.Add(new SilverlightControl1());
          }

          private void button1_Click(object sender, RoutedEventArgs e)
          {
          MessageBox.Show(Data.Result.ToString());
          }
          }
          }
          با استفاده از ویژگی Import به MEF اعلام می‌کنیم که به اطلاعاتی از نوع شیء WebServiceData نیاز داریم و توسط متد CompositionInitializer.SatisfyImports کار وهله سازی و پیوند زدن export و import های همانند صورت می‌گیرد. سپس استفاده‌ی مستقیم از Data.Result مجاز بوده و مقدار آن null نخواهد بود.

          کدهای User control ساده اضافه شده به شرح زیر هستند:

          <UserControl x:Class="SlMefTest.SilverlightControl1"
          xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
          xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
          xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
          xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
          mc:Ignorable="d"
          d:DesignHeight="300" d:DesignWidth="400">

          <Grid x:Name="LayoutRoot" Background="White">
          <Button Content="UserControlButton"
          Height="23"
          HorizontalAlignment="Left"
          Margin="10,10,0,0"
          Name="button1"
          VerticalAlignment="Top"
          Width="125"
          Click="button1_Click" />
          </Grid>
          </UserControl>

          using System.ComponentModel.Composition;
          using System.Windows;

          namespace SlMefTest
          {
          public partial class SilverlightControl1
          {
          [Import]
          public WebServiceData Data { set; get; }

          public SilverlightControl1()
          {
          InitializeComponent();
          this.Loaded += silverlightControl1Loaded;
          }

          void silverlightControl1Loaded(object sender, RoutedEventArgs e)
          {
          CompositionInitializer.SatisfyImports(this);
          }

          private void button1_Click(object sender, RoutedEventArgs e)
          {
          MessageBox.Show(Data.Result.ToString());
          }
          }
          }
          اکنون قبل از شروع برنامه یک break point را در سازنده‌ی کلاس WebServiceData قرار دهید. سپس برنامه را آغاز نمائید. تنها یکبار این سازنده فراخوانی خواهد شد (هر چند در دو کلاس کار Import اطلاعات WebServiceData صورت گرفته است). همچنین با کلیک بر روی دو دکمه‌ای که اکنون در صفحه‌ی اصلی برنامه ظاهر می‌شوند، فقط یک عدد مشابه نمایش داده می‌شود (با توجه به اینکه اطلاعات هر دکمه در یک وهله‌ی جداگانه قرار دارد؛ یکی متعلق است به صفحه‌ی اصلی و دیگری متعلق است به user control اضافه شده).