نظرات مطالب
امن سازی برنامه‌های ASP.NET Core توسط IdentityServer 4x - قسمت ششم - کار با User Claims

تنظیمات کانفیگ :

public static class ConfigTest
{

    public static IEnumerable<IdentityResource> Resources => new List<IdentityResource>
    {
        new IdentityResources.OpenId(),
        new IdentityResources.Profile(),
        new IdentityResources.Address(),
        new IdentityResource
        {
            Name = "role",
            UserClaims = new List<string> {"role"}
        }
    };


    public static IEnumerable<ApiResource> ApiResources => new[]
        {
            new ApiResource
            {
                Name = "orderingapi",
                DisplayName = "Ordering Api",
                Description = "Allow the application to access Ordering Api on your behalf",
                Scopes = new List<string> { "api.ordering.read", "api.ordering.manager"},
                ApiSecrets = new List<Secret> {new Secret("OrderingScopeSecret".Sha256())}, // change me!
                UserClaims = new List<string> {"role"}
            }
        };


    public static IEnumerable<ApiScope> ApiScopes => new[]
        {
            new ApiScope("api.ordering.read", "Read Access to Ordering Api"),
            new ApiScope("api.ordering.manager",displayName:"manage Crud operation access to ordering api")
        };



    public static IEnumerable<Client> Clients => new List<Client>
    {
        new Client
        {
            ClientId = "oidcClient",
            ClientName = "Example Client Application",
            ClientSecrets = new List<Secret> {new Secret("VQGBtSDEK7tzIzSJyfCYqdHDTQHt7kD2VQ1hHWnY7Dw=".Sha256())}, // change me!
    
            AllowedGrantTypes = GrantTypes.Hybrid,
            RedirectUris = new List<string> {"https://localhost:6001/signin-oidc"},
            PostLogoutRedirectUris = new List<string>
            {
                "https://localhost:6001/signout-callback-oidc"
            },
            AllowedScopes = new List<string>
            {
                IdentityServerConstants.StandardScopes.OpenId,
                IdentityServerConstants.StandardScopes.Profile,
                IdentityServerConstants.StandardScopes.Address,
                "role",
                "api.ordering.manager"
            },

            RequirePkce=false,
            AllowOfflineAccess = true
        }
    };


    public static List<TestUser> Users => new List<TestUser>
    {
        new TestUser
        {
            SubjectId = "1",
            Username = "meysam",
            Password = "123",
            Claims = new List<Claim>
            {
                new Claim(JwtClaimTypes.Name,"meysanm"),
                new Claim(JwtClaimTypes.GivenName,"soleymani"),
                new Claim(JwtClaimTypes.Email, "meysam@test.com"),
                new Claim(JwtClaimTypes.Address,"amol city"),
                new Claim(JwtClaimTypes.Role, "admin")
            }
        },
        new TestUser
        {
            SubjectId = "2",
            Username = "ali",
            Password = "123",
            Claims = new List<Claim>
            {
                new Claim(JwtClaimTypes.Name,"ali"),
                new Claim(JwtClaimTypes.GivenName,"abbasi"),
                new Claim(JwtClaimTypes.Address,"tehran city"),
                new Claim(JwtClaimTypes.Role,"public")
            }
        }
    };
}


مطالب
آموزش Linq - بخش ششم : عملگرهای پرس و جو قسمت سوم
عملگر‌های تبدیل Conversion Operator

عملگر‌های پرس و جوی تبدیل، توالی‌هایی را که از جنس <IEnumerable<T هستند، به انواع دیگر مجموعه تبدیل می‌کنند.
از عملگر‌های پرس و جوی زیر می‌توان برای تبدیل توالی‌ها استفاده کرد :
  • OfType
  • Cast
  • ToArray
  • ToList
  • ToDictionary
  • ToLookup

عملگر OfType


این عملگر عناصری از توالی را که نوع آنها را مشخص می‌کنیم باز می‌گرداند.
امضاء عملگر پرس و جوی OfType  به صورت زیر است :
 public static IEnumerable<TResult> OfType<TResult>(this IEnumerable source)
همانطور که مشاهده می‌کنید توالی ورودی از یک نوع IEnumerable غیر جنریک می‌باشد. بدین معنی که عناصر توالی ورودی می‌توانند از نوع داده‌های مختلف باشند (توالی از اشیاء، از جنس Object).
در مثال زیر یک توالی IEnumerable (آرایه‌ای از اشیاء)، از عناصر با نوع داده‌های مختلفی را ایجاد کرده‌ایم. عملگر OfType در اینجا کلیه عناصر از جنس (string) را باز می‌گرداند. توالی خروجی یک نوع IEnumerable جنریک است(در این مثال <IEnumerable<List).
مثال :
IEnumerable input = new object[] { "Apple", 33, "Sugar", 44, 'a', new DateTime()};
IEnumerable<string> query = input.OfType<string>();
foreach (var item in query)
{
   Console.WriteLine(item);
}
خروجی مثال بالا :
Apple
Sugar
عملگر OfType را می‌توان به‌همراه  Strongly Type‌‌ها نیز استفاده کرد.
مثال :کد زیر یک ساختار سلسله مراتبی شیء گرا را نمایش می‌دهد:
 class Ingredient
  {
     public string Name { get; set; }
  }
  class DryIngredient : Ingredient
  {
     public int Grams { get; set; }
  }

  class WetIngredient : Ingredient
  {
     public int Millilitres { get; set; }
  }
کد زیر چگونگی استفاده از OfType را برای بدست آوردن یک زیر نوع (Subtype) مشخص، نشان می‌دهد (در این مثال، نوع WetIngredient):
IEnumerable<Ingredient> input = new Ingredient[]
{
   new DryIngredient { Name = "Flour" },
   new WetIngredient { Name = "Milk" },
   new WetIngredient { Name = "Water" }
};

IEnumerable<WetIngredient> query = input.OfType<WetIngredient>();
foreach (WetIngredient item in query)
{
   Console.WriteLine(item.Name);
}
خروجی مثال بالا :
Milk
Water

پیاده سازی توسط عبارت‌های جستجو


معادل این عملگر، کلمه‌ی کلیدی جدیدی در عبارت‌های جستجو وجود ندارد و ترکیب دو روش می‌تواند خروجی دلخواه را تولید کند.


عملگر Cast


عملگر Cast همانند عملگر OfType رفتار می‌کند. این عملگر یک توالی ورودی را دریافت و بر اساس نوع مشخص شده، توالی خروجی را تولید می‌کند. همه‌ی عناصر توالی ورودی به نوع مشخص شده Cast می‌شوند. اما بر عکس عملگر OfType که عناصری را که با نوع داده‌ی ما سازگاری نداشت، نادیده می‌گرفت، این عملگر در صورت عدم موفقیت در عملیات تغییر نقش (Cast)، یک استثناء را پرتاب می‌کند.
مثال : 
IEnumerable input = new object[]
{
   "Apple", 33, "Sugar", 44, 'a', new DateTime()
};

IEnumerable<string> query = input.Cast<string>();
foreach (string item in query)
{
   Console.WriteLine(item);
}
با اجرای برنامه‌ی فوق، خطای زیر را مشاهده خواهید کرد:
 Unhandled Exception: System.InvalidCastException: Unable to cast object of type 'System.Int32' to type 'System.String'.

پیاده سازی توسط عبارت‌های جستجو


کلمه‌ی کلیدی جایگزینی برای عملگر Cast، در عبارت‌های جستجو وجود ندارد.این عملگر با استفاده از متغیر Range که در مطالب قبلی این سری معرفی شد، قابل پیاده سازی می‌باشد.
IEnumerable input = new object[]{ "Apple", "Sugar", "Flour" };
IEnumerable<string> query =
from string i in input
select i;

foreach (var item in query)
{
   Console.WriteLine(item);
}
نکته:  در مثال فوق تعریف صریح (Explicit) نوع داده، قبل از متغیر Range انجام شده است (معادل همان نوع داده در عملیات Cast).


عملگر ToArray


عملگر ToArray یک توالی ورودی را دریافت و یک توالی خروجی را به صورت آرایه تولید می‌کند. این عملگر باعث اجرای سریع پرس و جو می‌شود و رفتار پیش فرض LINQ را که اجرای با تاخیر می‌باشد، تحریف/بازنویسی (Override) می‌کند.
مثال: در این مثال یک توالی از نوع <IEnumerable<string به یک آرایه رشته‌ای تبدیل شده است (تبدیل لیست به آرایه).
 IEnumerable<string> input = new List<string> { "Apple", "Sugar", "Flour" };
string[] array = input.ToArray();

پیاده سازی توسط عبارت‌های جستجو


معادل این عملگر، کلمه‌ی کلیدی جدیدی در عبارت‌های جستجو وجود ندارد و ترکیب دو روش می‌تواند خروجی دلخواه را تولید کند.


عملگر ToList

عملگر ToList همچون ToArray، اجرای با تاخیر را نادیده می‌گیرد. عملگر ToList همانطور که از نامش پیداست، توالی خروجی را به‌صورت لیست مهیا می‌کند.
مثال:
 IEnumerable<string> input = new[] { "Apple", "Sugar", "Flour" };
List<string> list = input.ToList();

پیاده سازی توسط عبارت‌های جستجو


معادل این عملگر، کلمه‌ی کلیدی جدیدی در عبارت‌های جستجو وجود ندارد و ترکیب دو روش می‌تواند خروجی دلخواه را تولید کند.


عملگر ToDictionary

این عملگر توالی ورودی را به یک  دیکشنری جنریک تبدیل می‌کند (<Dictinary<TKey,TValue) .
ساده‌ترین امضاء عملگر ToDictionary، یک عبارت Lambda می‌باشد. این عبارت Lambda  نشان دهنده‌ی یک تابع است که عنصر کلید(Key) را در دیکشنری، مشخص می‌کند.
مثال:
class Recipe
{
   public int Id { get; set; }
   public string Name { get; set; }
   public int Rating { get; set; }
}

IEnumerable<Recipe> recipes = new[]
{
   new Recipe { Id = 1, Name = "Apple Pie", Rating = 5 },
   new Recipe { Id = 2, Name = "Cherry Pie", Rating = 2 },
   new Recipe { Id = 3, Name = "Beef Pie", Rating = 3 }
};

Dictionary<int, Recipe> dict = recipes.ToDictionary(x => x.Id);
foreach (KeyValuePair<int, Recipe> item in dict)
{
   Console.WriteLine($"Key={item.Key}, Recipe={item.Value}");
}
در کد بالا ، کلید دیکشنری نهایی، از نوع  int می‌باشد که بر اساس Id کلاس Recipe تنظیم شده است. مقادیر (value) دیکشنری هم همان اشیاء از جنس کلاس Recipe می‌باشند.
خروجی مثال بالا:
Key=1, Recipe=Apple Pie
Key=2, Recipe=Cherry Pie
Key=3, Recipe=Beef Pie

پیاده سازی توسط عبارت‌های جستجو


معادل این عملگر، کلمه‌ی کلیدی جدیدی در عبارت‌های جستجو وجود ندارد و ترکیب دو روش می‌تواند خروجی دلخواه را تولید کند.


عملگر ToLookup


این عملگر رفتاری شبیه به عملگر ToDictionary را دارد، اما به جای تولید خروجی از نوع دیکشنری، نمونه‌ای از جنس ILookUp را ایجاد می‌کند.
در کد زیر خروجی ایجاد شده توسط lookup دستورالعمل‌ها (Recipes) را بر حسب  امتیاز آنها گروه بندی کرده است. در این مثال کلید، بر حسب Byte می‌باشد.
مثال :
class Recipe
{
   public int Id { get; set; }
   public string Name { get; set; }
   public byte Rating { get; set; }
}

IEnumerable<Recipe> recipes = new[]
{
   new Recipe { Id = 1, Name = "Apple Pie", Rating = 5 },
   new Recipe { Id = 1, Name = "Banana Pie", Rating = 5 },
   new Recipe { Id = 2, Name = "Cherry Pie", Rating = 2 },
   new Recipe { Id = 3, Name = "Beef Pie", Rating = 3 }
};

ILookup<byte, Recipe> look = recipes.ToLookup(x => x.Rating);
foreach (IGrouping<byte, Recipe> ratingGroup in look)
{
   byte rating = ratingGroup.Key;
   Console.WriteLine($"Rating {rating}");
   foreach (var recipe in ratingGroup)
   {
      Console.WriteLine($" - {recipe.Name}");
   }
}
خروجی مثال بالا:
 Rating 5
 - Apple Pie
 - Banana Pie
Rating 2
 - Cherry Pie
Rating 3
 - Beef Pie

پیاده سازی توسط عبارت‌های جستجو


معادل این عملگر، کلمه‌ی کلیدی جدیدی در عبارت‌های جستجو وجود ندارد و ترکیب دو روش می‌تواند خروجی دلخواه را تولید کند.


عملگر‌های عناصر  Element Operators

این عملگر‌ها، یک توالی ورودی را دریافت و تنها یک عنصر از توالی ورودی و یا یک عنصر را به عنوان عنصر پیش فرض باز می‌گردانند. این نوع عملگر‌ها توالی خروجی را تولید نمی‌کنند.


عملگر First

این عملگر اولین عنصر توالی را باز می‌گرداند.
مثال :
Ingredient[] ingredients =
{
   new Ingredient {Name = "Sugar", Calories = 500},
   new Ingredient {Name = "Egg", Calories = 100},
   new Ingredient {Name = "Milk", Calories = 150},
   new Ingredient {Name = "Flour", Calories = 50},
   new Ingredient {Name = "Butter", Calories = 500}
};

Ingredient element = ingredients.First();
Console.WriteLine(element.Name);
خروجی مثال بالا :
 Sugar
امضای دیگر این متد، امکان تعریف یک شرط را مهیا می‌کند. خروجی این حالت اولین عنصری است که شرط را تامین می‌کند. در کد زیر اولین عنصری که کالری آن برابر 150 باشد به خروجی ارسال می‌شود.
Ingredient[] ingredients =
{
   new Ingredient {Name = "Sugar", Calories = 500},
   new Ingredient {Name = "Egg", Calories = 100},
   new Ingredient {Name = "Milk", Calories = 150},
   new Ingredient {Name = "Flour", Calories = 50},
   new Ingredient {Name = "Butter", Calories = 500}
};

Ingredient element = ingredients.First(x=>x.Calories==150);
Console.WriteLine(element.Name);
خروجی مثال بالا:
 Milk
در زمان استفاده از عملگر First، اگر توالی ورودی هیچ عنصری نداشته باشد، یک استثناء رخ خواهد داد:
 Unhandled Exception: System.InvalidOperationException: Sequence contains no elements
کد زیر نمونه‌ای از این حالت است:
Ingredient[] ingredients = { };
Ingredient element = ingredients.First();
در زمان استفاده‌ی از امضاء دیگر عملگر First، اگر هیچ عنصری شرط معرفی شده‌ی در پارامتر را تامین نکند، باز هم یک استثناء رخ خواهد داد:
 Unhandled Exception: System.InvalidOperationException: Sequence contains no matching element
کد زیر حالت فوق را نشان می‌دهد:
 Ingredient[] ingredients =
{
   new Ingredient {Name = "Sugar", Calories = 500},
   new Ingredient {Name = "Egg", Calories = 100},
   new Ingredient {Name = "Milk", Calories = 150},
   new Ingredient {Name = "Flour", Calories = 50},
   new Ingredient {Name = "Butter", Calories = 500}
};
Ingredient element = ingredients.First(x=>x.Calories==1500);

پیاده سازی توسط عبارت‌های جستجو

معادل این عملگر، کلمه‌ی کلیدی جدیدی در عبارت‌های جستجو وجود ندارد و ترکیب دو روش می‌تواند خروجی دلخواه را تولید کند.


عملگر FirstOrDefault

عملگر FirstOrDefalt همانند عملگر First عمل می‌کند، اما با این تفاوت که به جای پرتاب یک استثناء در شرایط معرفی شده در عملگر First، یک مقدار پیش فرض را بر اساس نوع  عناصر توالی باز می‌گرداند. در صورتیکه توالی از نوع عددی باشد، مقدار 0 و اگر عناصر توالی از انواع ارجاعی باشند، مقدار Null و برای مقادیر منطقی، ارزش False به‌عنوان مقادیر پیش فرض باز گردانده می‌شوند.
مثال :
 Ingredient[] ingredients = { };
Ingredient element = ingredients.FirstOrDefault();
Console.WriteLine(element == null);
خروجی مثال بالا :
 True
پیاده سازی حالتی که هیچ یک از عناصر با شرط عملگر کطالبقت ندارند.
Ingredient[] ingredients =
{
   new Ingredient {Name = "Sugar", Calories = 500},
   new Ingredient {Name = "Egg", Calories = 100},
   new Ingredient {Name = "Milk", Calories = 150},
   new Ingredient {Name = "Flour", Calories = 50},
   new Ingredient {Name = "Butter", Calories = 500}
};

Ingredient element = ingredients.FirstOrDefault(x=>x.Calories==1500);
Console.WriteLine(element==null);
خروجی مثال بالا :
 True

پیاده سازی توسط عبارت‌های جستجو


معادل این عملگر، کلمه‌ی کلیدی جدیدی در عبارت‌های جستجو وجود ندارد و ترکیب دو روش می‌تواند خروجی دلخواه را تولید کند.


 عملگر Last

این عملگر آخرین عنصر توالی را باز می‌گرداند. همچون عملگر First، این عملگر نیز یک امضاء برای دریافت یک عبارت شرط یا پیش بینی دارد. این پیش بینی، آخرین عنصری را که شرط را تامین کند، باز می‌گرداند. باز هم مثل عملگر First، در صورتی که توالی هیچ عنصری نداشته باشد و یا عدم تامین شرط توسط عناصر توالی، استثنایی رخ خواهد داد.
مثال :
Ingredient[] ingredients =
{
   new Ingredient {Name = "Sugar", Calories = 500},
   new Ingredient {Name = "Egg", Calories = 100},
   new Ingredient {Name = "Milk", Calories = 150},
   new Ingredient {Name = "Flour", Calories = 50},
   new Ingredient {Name = "Butter", Calories = 500}
};
Ingredient element = ingredients.Last(x=>x.Calories==500);
Console.WriteLine(element.Name);
خروجی مثال بالا :
 Flour

پیاده سازی توسط عبارت‌های جستجو


معادل این عملگر، کلمه‌ی کلیدی جدیدی در عبارت‌های جستجو وجود ندارد و ترکیب دو روش می‌تواند خروجی دلخواه را تولید کند.


عملگر LastOrDefault

این عملگر همچون عملگر FirstOrDefault عمل می‌کند. از بروز استثناء جلوگیری کرده و مقدار پیش فرض را به خروجی ارسال می‌کند.

پیاده سازی توسط عبارت‌های جستجو


معادل این عملگر، کلمه‌ی کلیدی جدیدی در عبارت‌های جستجو وجود ندارد و ترکیب دو روش می‌تواند خروجی دلخواه را تولید کند.


عملگر Single

عملگر Single ، تنها عنصر توالی ورودی را باز می‌گرداند.در صورتی که توالی ما بیش از یک عنصر داشته باشد و یا توالی هیچ عنصری نداشته باشد، یک استثناء رخ خواهد داد.
Unhandled Exception: System.InvalidOperationException: Sequence contains more than one matching element
Unhandled Exception: System.InvalidOperationException: Sequence contains no matching element
مثال :
Ingredient[] ingredients =
{
   new Ingredient { Name = "Sugar", Calories = 500 }
};

Ingredient element = ingredients.Single();
Console.WriteLine(element.Name);
خروجی مثال بالا :
 Sugar
عملگر Single، یک امضاء دیگر نیز دارد که یک عبارت پیش بینی را می‌پذیرد. در صورتی که بیش از یک عنصر، با پیش بینی مطابقت داشته باشد و یا هیچ عنصری شرط پیش بینی را تامین نکند، استثنائی رخ خواهد داد.
Ingredient[] ingredients =
{
   new Ingredient { Name = "Sugar", Calories = 500 },
   new Ingredient {Name = "Butter", Calories = 150},
   new Ingredient {Name = "Milk", Calories = 500}
};
Ingredient element = ingredients.Single(x => x.Calories == 150);
Console.WriteLine(element.Name);
خروجی مثال بالا :
 Butter

پیاده سازی توسط عبارت‌های جستجو


معادل این عملگر، کلمه‌ی کلیدی جدیدی در عبارت‌های جستجو وجود ندارد و ترکیب دو روش می‌تواند خروجی دلخواه را تولید کند.


عملگر SingleOrDefault

عملگر SingleOrDefault همچون عملگر Single عمل می‌کند؛ اما با این تفاوت که اگر توالی هیچ عنصری نداشته باشد، مقدار پیش فرض نوع توالی، باز گردانده می‌شود و در صورتیکه هیچ عنصری شرط مشخص شده را تامین نکند، باز هم مقدار پیش فرض توالی، به جای رخ دادن استثناء باز گردانده می‌شود.
مثال : در این مثال هیچ عنصری با پیش بینی مشخص شده مطالبقت ندارد:
 Ingredient[] ingredients =
{
   new Ingredient { Name = "Sugar", Calories = 500 },
   new Ingredient {Name = "Egg", Calories = 100},
   new Ingredient {Name = "Milk", Calories = 50}
};
Ingredient element = ingredients.SingleOrDefault(x => x.Calories == 9999);
Console.WriteLine(element==null);
خروجی مثال بالا :
True
توجه داشته باشید که استثنائی رخ نداده است و مقدار پیش فرض انواع ارجاعی که Null می‌باشد باز گردانده شده است.

پیاده سازی توسط عبارت‌های جستجو

معادل این عملگر، کلمه‌ی کلیدی جدیدی در عبارت‌های جستجو وجود ندارد و ترکیب دو روش می‌تواند خروجی دلخواه را تولید کند.


عملگر ElementAt


عملگر ElementAt   عنصری را در یک جایگاه مشخص شده‌ی در توالی، باز می‌گرداند.
مثال: در کد زیر سومین عنصر توالی ورودی انتخاب می‌شود:
 Ingredient[] ingredients =
{
   new Ingredient { Name = "Sugar", Calories = 500 },
   new Ingredient {Name = "Egg", Calories = 100},
   new Ingredient {Name = "Milk", Calories = 50}
};

Ingredient element = ingredients.ElementAt(2);
Console.WriteLine(element.Name);
خروجی مثال بالا :
 Milk
باید دقت کرد که مقدار ارسالی به عملگر  ElementAt، اندیسی با نقطه‌ی آغاز صفر می‌باشد. بدین معنی که برای بدست آوردن اولین عنصر باید مقدار 0 را به عملگر ElementAt ارسال کرد. در صورتی که مقدار ارسالی با بازه اندیس‌های عناصر توالی مطابقت نداشته باشد (بزرگتر از شماره اندیس آخرین عنصر توالی باشد) یک استثناء رخ خواهد داد.
 System.ArgumentOutOfRangeException: Index was out of range. Must be non-negative and less than the size of the collection.

پیاده سازی توسط عبارت‌های جستجو


معادل این عملگر، کلمه‌ی کلیدی جدیدی در عبارت‌های جستجو وجود ندارد و ترکیب دو روش می‌تواند خروجی دلخواه را تولید کند.


عملگر ElementAtOrDefualt

عملگر ElementAtOrDefualt نیز همچون عملگر ElementAt کار می‌کند؛ اما در صورت وارد کردن اندیسی بزرگتر از اندیس مجاز توالی، دیگر یک استثناء رخ نخواهد داد و یک مقدار پیش فرض، بر اساس نوع عناصر توالی باز گردانده می‌شود.
مثال :
Ingredient[] ingredients =
{
   new Ingredient { Name = "Sugar", Calories = 500 },
   new Ingredient {Name = "Egg", Calories = 100},
   new Ingredient {Name = "Milk", Calories = 50}
};
Ingredient element = ingredients.ElementAtOrDefault(5);
Console.WriteLine(element==null);
خروجی مثال بالا:
 True

پیاده سازی توسط عبارت‌های جستجو


معادل این عملگر، کلمه‌ی کلیدی جدیدی در عبارت‌های جستجو وجود ندارد و ترکیب دو روش می‌تواند خروجی دلخواه را تولید کند.


عملگر DefaultIfEmpty
عملگر DefaultIfEmpty یک توالی را دریافت کرده و به دو شکل عمل می‌کند:
1- اگر توالی شامل حداقل یک عنصر باشد، این توالی بدون هیچ تغییری به خروجی ارسال می‌شود.
2- اگر توالی هیچ عنصری نداشته باشد، توالی خروجی خالی نخواهد بود. در این حالت توالی خروجی تنها یک عضو دارد و آن هم مقدار پیش فرضی بر اساس نوع توالی می‌باشد.
مثال :
Ingredient[] ingredients =
{
   new Ingredient { Name = "Sugar", Calories = 500 },
   new Ingredient {Name = "Egg", Calories = 100},
   new Ingredient {Name = "Milk", Calories = 50}
};

IEnumerable<Ingredient> query = ingredients.DefaultIfEmpty();
foreach (Ingredient item in query)
{
  Console.WriteLine(item.Name);
}
خروجی مثال بالا :
Sugar
Egg
Milk
همانطور که می‌بینید توالی خروجی دقیقا شبیه توالی ورودی می‌باشد.
کد زیر حالت دوم معرفی شده‌ی در تعریف DefaultIfEmpty را نشان می‌دهد.
Ingredient[] ingredients = { };
IEnumerable<Ingredient> query = ingredients.DefaultIfEmpty();
foreach (Ingredient item in query)
{
   Console.WriteLine(item == null);
}
خروجی کد بالا :
 True

پیاده سازی توسط عبارت‌های جستجو


معادل این عملگر، کلمه‌ی کلیدی جدیدی در عبارت‌های جستجو وجود ندارد و ترکیب دو روش می‌تواند خروجی دلخواه را تولید کند.
مطالب
بررسی مفاهیم Covariant و Contravariant در زبان سی‌شارپ
یکی از مفاهیمی که بنظر پیچیده می‌آمد و هر دفعه موقع مطالعه از آن فرار می‌کردم، همین بحث COVARIANCE و CONTRAVARIANCE بود. در اینجا قصد دارم به زبان ساده این مفاهیم را شرح دهم.

Covariance 
A را در نظر بگیرید که قابل تبدیل به B باشد. در اینصورت X، دارای پارامتر کواریانس است اگر <X<A قابل تبدیل به <X<B باشد. بدون ذکر مثال شاید این تعریف خیلی ملموس نباشد. پس بهتر است با ذکر مثال به تشریح مفاهیم بپردازیم.
نکته: در اینجا منظور از قابل تبدیل بودن، قابل تبدیل بودن به صورت ضمنی (implicit) می‌باشد. برای مثال A از B ارث بری داشته باشد و یا A، تایپ B را پیاده سازی کند (در صورتی که B یک اینترفیس باشد). تبدیلات عددی، Boxing و تبدیلات کاستوم مجاز نیستند.
برای نمونه نوع <IFoo<T پارامتر کوواریانس T دارد، اگر کد زیر معتبر باشد:
IFoo<string> s = ...;
IFoo<object> b = s;
از C# 4.0، اینترفیسها و delegateها مجاز به استفاده از پارامتر کوواریانس T هستند؛ اما در مورد کلاس‌ها اینطور نیست. آرایه‌ها نیز مجاز هستند که در ادامه تشریح خواهند شد (اگر A قابل تبدیل به B باشد در اینصورت []A قابل تبدیل به []B خواهد بود. هر چند ممکن است به run-time exception منجر گردد که ظاهرا این پشتیبانی آرایه‌ها از پارامترهای کوواریانس دلایل تاریخی دارد!).

Variance is not automatic
برای حصول اطمینان از static type safety، پارامترها به صورت پیش فرض variant نمی‌باشند:
class Animal {}
class Bear : Animal {}
class Camel : Animal {}
public class Stack<T>
{
   int position;
   T[] data = new T[100];
   public void Push (T obj) => data[position++] = obj;
   public T Pop() => data[--position];
}
کد زیر کامپایل نخواهد شد:
Stack<Bear> bears = new Stack<Bear>();
Stack<Animal> animals = bears; // Compile-time error 
دلیل اینکه کد فوق کامپایل نمی‌شود، در کد زیر آورده شده است:
animals.Push (new Camel()); // Trying to add Camel to bears
اگر کامپایل انجام می‌شد، کد بالا در زمان اجرا خطا صادر می‌کرد؛ چرا که نوع واقعی animals، در واقع <Stack<Bear بوده و نمی‌توان به آن، شیء ای از جنس Camel اضافه کرد. عدم پشتیبانی از کوواریانس، به هرحال مانع از امکان استفاده مجدد (re-usability) خواهد شد. برای مثال فرض کنید می‌خواهیم متدی بنویسیم که وظیفه آن صادر کردن دستور شستن حیوانات موجود در پشته باشد:
public class ZooCleaner
{
  public static void Wash (Stack<Animal> animals) {...}
}
فراخوانی متد Wash با پارامتری از جنس <Stack<Bear در زمان کامپایل خطا خواهد داد (اعمال این محدودیت منطقی است. برای مثال ممکن است مثلا در بدنه متد Wash با استفاده از متد Pop کلاس Stack یک Animal برداشته شده و به Camel کست گردد که با توجه به نوع اصلی آن (Bear) خطای run-time صادر خواهد شد. اما به هرحال محدودیت ایجاد شده، جلوی خطاهایی که ممکن است در run-time اتفاق بیافتد را می‌گیرد). 
یک راه حل برای این موضوع، تعریف متد Wash به صورت جنریک و با constraint است:
class ZooCleaner
{
  public static void Wash<T> (Stack<T> animals) where T : Animal { ... }
}
با کد فوق می‌توان متد Wash را به صورت زیر فراخوانی نمود:
Stack<Bear> bears = new Stack<Bear>();
ZooCleaner.Wash(bears);
کامپایلر، ورژن جنریک متد Wash را کامپایل میکند. در این حالت میتوان با چک کردن نوع واقعی T و کست کردن به آن نوع، عملیات را بدون خطا انجام داد.
نکته: اگر reusable بودن مد نظر نبود، باید برای هر sub-type از Animal یک متد جداگانه Wash مینوشتیم (یکی برای Bear، یکی برای Camel،...).

راه حل دیگر این است که کلاس <Stack<T یک اینترفیس با پارامتر covariant پیاده سازی نماید که در ادامه به این مورد بازخواهیم گشت.

Arrays 
آرایه‌ها از covariance پشتیبانی می‌کنند. برای مثال:
Bear[] bears = new Bear[3];
Animal[] animals = bears; // OK
این مورد باعث ایجاد قابلیت استفاده مجدد می‌شود؛ به قیمت اینکه ممکن است چنین خطاهایی ایجاد شوند:
animals[0] = new Camel(); // Runtime error

Declaring a covariant type parameter  
از C# 4.0 و بالاتر، پارامترهای اینترفیسها و delegateها می‌توانند با استفاده از کلمه کلیدی out از covariance پشتیبانی کنند؛ یا به زبان ساده‌تر covariant گردند. در این صورت برخلاف آرایه‌ها از type safety اطمینان کامل خواهیم داشت.
برای نشان دادن این مورد، در کلاس <Stack<T اینترفیس زیر را پیاده سازی می‌کنیم:
public interface IPoppable<out T> { T Pop(); }
کلمه کلیدی out نشان می‌دهد که T فقط در موقعیت خروجی مورد استفاده واقع می‌گردد (برای مثال نوع برگشتی یک متد). این مورد سبب می‌شود تا پارامتر covariant باشد و کد زیر کامپایل گردد:
var bears = new Stack<Bear>();
bears.Push (new Bear());
// Bears implements IPoppable<Bear>. We can convert to IPoppable<Animal>:
IPoppable<Animal> animals = bears; // Legal
Animal a = animals.Pop();
در اینجا کامپایلر اجازه تبدیل bears را به animals می‌دهد. چرا که موردی که کامپایلر از آن جلوگیری می‌کرد (Push کردن Camel به Stack با اعضایی از جنس Bear) در اینجا نمی‌تواند رخ دهد. چرا که در اینجا پارامتر T فقط می‌تواند به عنوان خروجی استفاده گردد و امکان Push کردن وجود ندارد.

نکته: پارامترهای متدی که مزین به کلمه کلیدی out شده‌اند، واجد شرایط covariant بودن نمی‌باشند (به دلیل وجود محدودیتی در CLR).

با استفاده از کد زیر قابلیت استفاده مجددی که در ابتدا بحث کردیم فراهم می‌شود:
public class ZooCleaner
{
 public static void Wash (IPoppable<Animal> animals) { ... } //cast covariantly to solve the reusability problem 
}

نکته: Covariance (و contravariance) فقط در موارد تبدیل ارجاعی کار می‌کنند (نه تبدیل boxing). بنابراین اگر متدی داشته باشیم که دارای پارامتری از جنس IPoppa
<ble<object باشد، امکان فراخوانی آن متد با ورودی از جنس <IPoppable<string وجود دارد؛ اما پاس دادن متغیر از جنس <IPoppable<int امکانپذیر نمی‌باشد.

Contravariance   
در تعریف covaraince داشتیم:  A را در نظر بگیرید که قابل تبدیل به B باشد. در اینصورت X، دارای پارامتر کواریانس است اگر <X<A قابل تبدیل به <X<B باشد.  Contravariance 
زمانی است که تبدیل در جهت عکس صورت گیرد (تبدیل از <X<B به <X<A). این مورد فقط برای پارامترهای ورودی صحیح است و با کلمه کلیدی in تعیین می‌گردد. با استفاده از پیاده سازی اینترفیس:
public interface IPushable<in T> { void Push (T obj); }
می‌توانیم کد زیر را بنویسیم:
IPushable<Animal> animals = new Stack<Animal>();
IPushable<Bear> bears = animals; // Legal
bears.Push (new Bear());
هیچ عضوی از اینترفیس IPushable خروجی T را بر نمی‌گرداند و لذا با casting اشتباه، مواجه نخواهیم شد (برای نمونه از طریق این اینترفیس راهی برای Pop کردن نداریم).
توجه: کلاس <Stack<T هر دو اینترفیس <IPushable<T و <IPoppable<T را پیاده سازی کرده است (با وجود اینکه T هم out است و هم in). اما این مورد مشکلی ایجاد نمی‌کند. زیرا قبل از تبدیل، ارجاعی فقط به یکی از اینترفیسها صورت می‌گیرد (نه همزمان به هردو!). این مورد نشان می‌دهد که چرا class‌ها از پارامترهای variant پشتیبانی نمی‌کنند. 

برای مثال اینترفیس زیر را در نظر بگیرید:
public interface IComparer<in T>
{
// Returns a value indicating the relative ordering of a and b
  int Compare (T a, T b);
}
از آنجاییکه T در اینجا contravariant است می‌توان از <IComparer<object برای مقایسه دو string استفاده نمود:
var objectComparer = Comparer<object>.Default;
// objectComparer implements IComparer<object>
IComparer<string> stringComparer = objectComparer;
int result = stringComparer.Compare ("Hashem", "hashem");


برای مطالعه‌ی بیشتر
Covariant and Contravariant  
مطالب
آموزش Linq - بخش ششم: عملگرهای پرس و جو قسمت پنجم (پایانی)
عملگرهای اتصال (Join Operators)
• Join
• GroupJoin
• Zip

عملگر Join

این عملگر همانند inner join در SQL، دو مجموعه را بر اساس کلید‌های مرتبط که از طریق پارامترها  به آن ارسال می‌شوند، با یکدیگر ترکیب می‌کند.
در عملیات Join، یک توالی ورودی که به آن توالی خارجی (Outer Sequence) گفته می‌شود با یک توالی دیگر که به آن توالی داخلی (Inner Sequence) می‌گوییم، بر اساس کلید‌های مشخص شده، ترکیب شده و یک توالی خروجی تولید می‌شود.

بررسی پارامتر‌های عملگر Join:
public static IEnumerable<TResult> Join<TOuter,TInner,TKey,TResult>
(this IEnumerable<TOuter> outer,
IEnumerable<TInner> inner,
Func<TOuter,TKey> outerKeySelector,
Func<TInner,TKey> innerKeySelector,
Func<TOuter,TInner,TResult> resultSelector)
 • <Inner IEnumerable<TInner: نشان دهنده توالی داخلی می‌باشد.
 • Func<Touter,Tkey> outerKeySelector : عنصر کلید، در توالی خارجی
 • Func<Tinner,Tkey> innerKeySelector : عنصر کلید، در توالی داخلی
 • Func<Touter,Tinner,Tresult> resultSelector : یک عبارت Lambda است که ظاهر عناصر خروجی را مشخص می‌کند.

نکته
: بطور کلی T در پارامتر‌های بالا معرف Generic Type Parameter می‌باشد؛ T==>Type  (هر نوع داده‌ای که ما مشخص کنیم).
نکته : عملگر Join یک امضاء دیگر نیز دارد که اجازه مشخص کردن IEqualityComparer را می‌دهد.

کد زیر استفاده از عملگر Join را نشان می‌دهد. توجه داشته باشید که اعلان صریح نوع داده‌ها در عبارات Lambda نوشته شده، فقط برای روشن‌تر شدن فرآیند عملیات می‌باشد.
تعریف دو آرایه از کلاس‌های Recipe و  Review:
Recipe[] recipes =
{
   new Recipe {Id = 1, Name = "Mashed Potato"},
   new Recipe {Id = 2, Name = "Crispy Duck"},
   new Recipe {Id = 3, Name = "Sachertorte"}
};

// inner sequence
Review[] reviews =
{
   new Review {RecipeId = 1, ReviewText = "Tasty!"},
   new Review {RecipeId = 1, ReviewText = "Not nice :("},
   new Review {RecipeId = 1, ReviewText = "Pretty good"},
   new Review {RecipeId = 2, ReviewText = "Too hard"},
   new Review {RecipeId = 2, ReviewText = "Loved it"}
};

  var query = recipes // recipes توالی خارجی
.Join(reviews, // reviewsتوالی داخلی
  (Recipe outerKey) => outerKey.Id, // کلید انخاب شده از توالی خارجی
  (Review innerKey) => innerKey.RecipeId, // کلید انتخاب شده از توالی داخلی
  // نحوه قالب بندی خروجی
  (recipe, review) => recipe.Name + " - " + review.ReviewText);

foreach (string item in query)
{
   Console.WriteLine(item);
}
خروجی مثال بالا:
Mashed Potato - Tasty!
Mashed Potato - Not nice :(
Mashed Potato - Pretty good
Crispy Duck - Too hard
Crispy Duck - Loved it
ساده‌تر شده‌ی کد بالا:
var query =
recipes.Join
(reviews,
outerKey => outerKey.Id,
innerKey => innerKey.RecipeId,
(recipe, review) => recipe.Name + " - " + review.ReviewText);
همانطور که مشاهده می‌کنید در خروجی مثال بالا، عبارت Sachertorte مشاهده نمی‌شود. علت آن است که عملیات انجام شده، عملیات Left Join می‌باشد. بدین معنا که عناصری که در توالی خارجی هیچ عنصر متناظری در توالی داخلی ندارند، در توالی خروجی ظاهر نخواهند شد.

پیاده سازی توسط عبارت‌های جستجو
کلمه کلیدی Join، در زمان استفاده از روش عبارت‌های پرس و جو، مورد استفاده قرار گرفت. دستور Join در قسمت چهارم از این سری آموزشی بطور کامل بررسی شده است. کد زیر نحوه اجرای دستور Join را به روش عبارت‌های پرس و جو، نشان می‌دهد.
var query = from recipe in recipes
join review in reviews
on
recipe.Id equals review.RecipeId
select new //انواع بی نام
{
    RecipeName = recipe.Name,
    RecipeReview = review.ReviewText
};

foreach (var item in query)
{
    Console.WriteLine(item.RecipeName + " - " + item.RecipeReview);
}
نکته : بررسی‌ها نشان داده است که استفاده از دستور Join، به روش عبارت‌های پرس و جو نسبت به عملگر‌های پرس و جو، خوانایی بیشتری دارد.

عملگر GroupJoin
نحوه عملکرد عملگر GroupJoin، شبیه عملگر Join می‌باشد؛ با این تفاوت که خروجی حاصل از دستور GroupJoin، یک ساختار سلسله مراتبی می‌باشد. توالی خروجی، مجموعه‌ای  از گروه‌ها می‌باشد که هر گروه، تشکیل شده‌است از عناصر توالی درونی.

بررسی پارامتر‌های عملگر GroupJoin
• <Inner IEnumerable<TInner : نشان دهنده توالی داخلی
• Func<Touter,Tkey> outerKeySelector : عنصر کلید، در توالی خارجی
• Func<Tinner,Tkey> innerKeySelector : عنصر کلید، در توالی داخلی
• Func<Touter,Ienumerable<Tinner>,Tresult> resultSelector : قالب بندی گروه‌های تولید شده خروجی را مشخص می‌کند

کد زیر استفاده از عملگر GroupJoin را نشان می‌دهد :
// outer sequence
Recipe[] recipes =
{
   new Recipe {Id = 1, Name = "Mashed Potato"},
   new Recipe {Id = 2, Name = "Crispy Duck"},
   new Recipe {Id = 3, Name = "Sachertorte"}
};

// inner sequence
Review[] reviews =
{
   new Review {RecipeId = 1, ReviewText = "Tasty!"},
   new Review {RecipeId = 1, ReviewText = "Not nice :("},
   new Review {RecipeId = 1, ReviewText = "Pretty good"},
   new Review {RecipeId = 2, ReviewText = "Too hard"},
   new Review {RecipeId = 2, ReviewText = "Loved it"}
};

var query = recipes
.GroupJoin(
reviews,
(Recipe outerKey) => outerKey.Id,//outer key
(Review innerKey) => innerKey.RecipeId,//inner key
(Recipe recipe, IEnumerable<Review> rev )=>تعریف ساختار گروه‌ها new
{
  RecipeName = recipe.Name,
  Reviews = rev
}
);

foreach (var item in query)
{
   Console.WriteLine($"Reviews for {item.RecipeName}");
   foreach (var review in item.Reviews)
   {
      Console.WriteLine($" - {review.ReviewText}");
   }
}
خروجی مثال فوق:
Reviews for Mashed Potato
 - Tasty!
 - Not nice :(
 - Pretty good
Reviews for Crispy Duck
 - Too hard
 - Loved it
Reviews for Sachertorte
همانطور که مشاهده می‌کنید گروه "Sachertorte" در خروجی اضافه شده است؛ در صورتی که هیچ عضوی ندارد.

پیاده سازی توسط عبارت‌های جستجو
var query =
from recipe in recipes
join review in reviews on recipe.Id equals review.RecipeId
into reviewGroup
select new //انواع بی نام
{
  RecipeName = recipe.Name,
  Reviews = reviewGroup//کلیه بازخورد‌ها مرتبط با یک دستور غذایی
};
خروجی مثال فوق:
Reviews for Mashed Potato
 - Tasty!
 - Not nice :(
 - Pretty good
Reviews for Crispy Duck
 - Too hard
 - Loved it
Reviews for Sachertorte

عملگر Zip

عملگر Zip، رفتاری متفاوت نسبت به عملگر GroupJoin و Join دارد و هیچ آیتمی را به عنوان کلید، از دو توالی دریافت نمی‌کند. عملگر Zip همه عناصر دو توالی را یک به یک، به ترتیب کنار هم قرار می‌دهد. مثل زیپ در دنیای واقعی که لبه‌های دو طرف زیپ را به هم می‌رساند.
 public class Ingredient
{
  public string Name { get; set; }
  public int Calories { get; set; }
}

string[] names = { "Flour", "Butter", "Sugar" };
int[] calories = { 100, 400, 500 };
IEnumerable<Ingredient> ingredients =
names.Zip(calories, (name, calorie) =>
new Ingredient
{
  Name = name,
  Calories = calorie
});
foreach (var item in ingredients)
{
Console.WriteLine($"{item.Name} has {item.Calories} calories");
}
خروجی مثال بالا :
Flour has 100 calories
Butter has 400 calories
Sugar has 500 calories
نکته: اگر تعداد اعضای مجموعه‌ها برابر نباشد، اعضای اضافی نادیده گرفته می‌شوند.

پیاده سازی توسط عبارت‌های جستجو
معادل عملگر Zip، کلمه کلیدی در عبارت‌های جستجو وجود ندارد. ترکیب دو روش می‌تواند خروجی دلخواه را تولید کند.
مطالب
الگوی نماینده (پروکسی) Proxy Pattern
همه کاربران کامپیوتر در ایران به خوبی با کلمه پروکسی آشنا هستند. پروکسی به معنی نماینده یا واسط است و پروکسی واسطی است بین ما و شیء اصلی. پروکسی در شبکه به این معنی است که سیستم شما به یک سیستم واسط متصل شده است که از طریق پروکسی محدودیت‌های دسترسی برای آن تعریف شود. در اینجا هم پروکسی در واقع به همین منظور استفاده می‌شود. در تعدادی از کامنت‌های سایت خوانده بودم دوستان در مورد اصول SOLID  و Refactoring بحث می‌کردند که آیا انجام عمل اعتبارسنجی در خود اصل متد کار درستی است یا خیر. در واقع خودم هم نمی‌دانم که این حرکت چقدر به این اصول پایبند است یا خیر، ولی می‌دانم که الگوی پروکسی کل سؤالات بالا را حذف می‌کند و با این الگو دیگر این سؤال اهمیتی ندارد. به عنوان مثال فرض کنید که شما یک برنامه ساده کار با فایل را دارید. ولی اگر بخواهید اعتبارسنجی‌هایی را برای آن تعریف کنید، بهتر است اینکار را به یک پروکسی بسپارید تا شیء گرایی بهتری را داشته باشید.

برای شروع ابتدا یک اینترفیس تعریف می‌کنیم:
   public interface IFilesService
    {
        DirectoryInfo GetDirectoryInfo(string directoryPath);
        void DeleteFile(string fileName);
        void WritePersonInFile(string fileName,string name, string lastName, byte age);
    }
این اینترفیس شامل سه متد نام نویسی، حذف فایل و دریافت اطلاعات یک دایرکتوری است. بعد از آن دو عدد کلاس را از آن مشتق می‌کنیم:
کلاس اصلی:
    class FilesServices:IFilesService
    {
        public DirectoryInfo GetDirectoryInfo(string directoryPath)
        {
            return new DirectoryInfo(directoryPath);
        }

        public void DeleteFile(string fileName)
        {
            File.Delete(fileName);
            Console.WriteLine("the file has been deleted");
        }

        public void WritePersonInFile(string fileName, string name, string lastName, byte age)
        {
            var text = $"my name is {name} {lastName} with {age} years old from dotnettips.info";
            File.WriteAllText(fileName,text);
        }    
    }
که تنها وظیفه اجرای فرامین را دارد و دیگری کلاس پروکسی است که وظیف تامین اعتبارسنجی و آماده سازی پیش شرط‌ها را دارد:
    class FilesServicesProxy:IFilesService
    {
        private readonly IFilesService _filesService;

        public FilesServicesProxy()
        {
            _filesService=new FilesServices();
        }

        public DirectoryInfo GetDirectoryInfo(string directoryPath)
        {
            var existing = Directory.Exists(directoryPath);
            if (!existing)
                Directory.CreateDirectory(directoryPath);
            return _filesService.GetDirectoryInfo(directoryPath);
        }

        public void DeleteFile(string fileName)
        {
            if(!File.Exists(fileName))
                Console.WriteLine("Please enter a valid path");
            else
                _filesService.DeleteFile(fileName);
        }

        public void WritePersonInFile(string fileName, string name, string lastName, byte age)
        {
            if (!Directory.Exists(fileName.Remove(fileName.LastIndexOf("\\"))))
            {
                Console.WriteLine("File Path is not valid");
                return;
            }
            if (name.Trim().Length == 0)
            {
                Console.WriteLine("first name must enter");
                return;
            }

            if (lastName.Trim().Length == 0)
            {
                Console.WriteLine("last name must enter");
                return;
            }

            if (age<18)
            {
                Console.WriteLine("your age is illegal");
                return;
            }


            if (name.Trim().Length < 3)
            {
                Console.WriteLine("first name must be more than 2 letters");
                return;
            }

            if (lastName.Trim().Length <5)
            {
                Console.WriteLine("last name must be more than 4 letters");
                return;
            }

            _filesService.WritePersonInFile(fileName,name,lastName,age);
            Console.WriteLine("the file has been written");
        }
    }
کلاس پروکسی، همان کلاسی است که شما باید صدا بزنید. وظیفه کلاس پروکسی هم این است که در زمان معین و صحیح، کلاس اصلی شما را بعد از اعتبارسنجی‌ها و انجام پیش شرط‌ها صدا بزند. همانطور که می‌بیند، ما در سازنده کلاس اصلی را در حافظه قرار می‌دهیم. سپس در هر متد، اعتبارسنجی‌های لازم را انجام می‌دهیم. مثلا در متد GetDirectoryInfo باید اطلاعات دایرکتوری بازگشت داده شود؛ ولی اصل عمل در واقع در کلاس اصلی است و اینجا فقط شرط گذاشته‌ایم اگر مسیر داده شده معتبر نبود، همان مسیر را ایجاد کن و سپس متد اصلی را صدا بزن. یا اگر فایل موجود است جهت حذف آن اقدام کن و ...
در نهایت در بدنه اصلی با تست چندین حالت مختلف، همه متد‌ها را داریم:
static void Main(string[] args)
        {
            IFilesService filesService=new FilesServicesProxy();
            filesService.WritePersonInFile("c:\\myfakepath\\a.txt","ali","yeganeh",26);

            var directory = filesService.GetDirectoryInfo("d:\\myrightpath\\");
            var fileName = Path.Combine(directory.FullName, "dotnettips.txt");

            filesService.WritePersonInFile(fileName, "al", "yeganeh", 26);
            filesService.WritePersonInFile(fileName, "ali", "yeganeh", 12);
            filesService.WritePersonInFile(fileName, "ali", "yeganeh", 26);

            filesService.DeleteFile("c:\\myfakefile.txt");
            filesService.DeleteFile(fileName);
        }
و نتیجه خروجی:
File Path is not valid
first name must be more than 2 letters
your age is illegal
the file has been written
Please enter a valid path
the file has been deleted
مطالب
خودکار کردن تعاریف DbSetها در EF Code first
پیشنیاز:
تعریف نوع جنریک به صورت متغیر

مطلبی را چندی قبل در مورد نحوه خودکار کردن افزودن کلاس‌های EntityTypeConfiguration به modelBuilder در این سایت مطالعه کردید. در مطلب جاری به خودکار سازی تعاریف مرتبط با DbSetها خواهیم پرداخت.
ابتدا مثال کامل زیر را درنظر بگیرید:
using System;
using System.Data.Entity;
using System.Data.Entity.Migrations;
using System.Linq;
using System.Reflection;

namespace MyNamespace
{
    public abstract class BaseEntity
    {
        public int Id { set; get; }
        public string CreatedBy { set; get; }
    }

    public class User : BaseEntity
    {
        public string Name { get; set; }
    }

    public class MyContext : DbContext
    {
        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            var asm = Assembly.GetExecutingAssembly();
            loadEntities(asm, modelBuilder, "MyNamespace");
        }

        void loadEntities(Assembly asm, DbModelBuilder modelBuilder, string nameSpace)
        {
            var entityTypes = asm.GetTypes()
                                    .Where(type => type.BaseType != null &&
                                           type.Namespace == nameSpace &&
                                           type.BaseType.IsAbstract &&
                                           type.BaseType == typeof(BaseEntity))
                                    .ToList();

            var entityMethod = typeof(DbModelBuilder).GetMethod("Entity");
            entityTypes.ForEach(type =>
            {
                entityMethod.MakeGenericMethod(type).Invoke(modelBuilder, new object[] { });
            });
        }
    }

    public class Configuration : DbMigrationsConfiguration<MyContext>
    {
        public Configuration()
        {
            AutomaticMigrationsEnabled = true;
            AutomaticMigrationDataLossAllowed = true;
        }

        protected override void Seed(MyContext context)
        {
            context.Set<User>().Add(new User { Name = "name-1" });
            context.Set<User>().Add(new User { Name = "name-2" });
            context.Set<User>().Add(new User { Name = "name-3" });
            base.Seed(context);
        }
    }

    public static class Test
    {
        public static void RunTests()
        {
            Database.SetInitializer(new MigrateDatabaseToLatestVersion<MyContext, Configuration>());
            using (var context = new MyContext())
            {
                var user1 = context.Set<User>().Find(1);
                if (user1 != null)
                    Console.WriteLine(user1.Name);
            }
        }
    }
}
توضیحات:
همانطور که ملاحظه می‌کنید در این مثال خبری از تعاریف DbSetها نیست. به کمک Reflection تمام مدل‌های برنامه که از نوع کلاس پایه BaseEntity هستند (روشی مرسوم جهت مدیریت خواص تکراری مدل‌ها) یافت شده (در متد loadEntities) و سپس نتیجه حاصل به صورت پویا به متد جنریک Entity ارسال می‌شود. حاصل، افزوده شدن خودکار کلاس‌های مورد نظر به سیستم EF است.
البته در این حالت چون دیگر کلاس‌های مدل‌ها در MyContext به صورت صریح تعریف نمی‌شوند، نحوه استفاده از آن‌ها را توسط متد Set، در متدهای RunTests و یا Seed، ملاحظه می‌کنید. 
مطالب
استفاده از FluentValidation در ASP.NET MVC
برای هماهنگی این کتابخانه با ASP.NET MVC نیاز به نصب FluentValidation.Mvc3 یا FluentValidation.Mvc4 از طریق Nuget یا دانلود کتابخانه از سایت CodePlex می‌باشد. بعد از نصب کتابخانه، نیاز به تنظیم FluentValidationModelValidatorProvider داخل متد Application_Start (فایل Global.asax) داریم: 

protected void Application_Start() {
    AreaRegistration.RegisterAllAreas();

    RegisterGlobalFilters(GlobalFilters.Filters);
    RegisterRoutes(RouteTable.Routes);

    FluentValidationModelValidatorProvider.Configure();
}
تصور کنید دو کلاس Person و PersonValidator را به صورت زیر داریم:
[Validator(typeof(PersonValidator))]
    public class Person {
    public int Id { get; set; }
    public string Name { get; set; }
    public string Email { get; set; }
    public int Age { get; set; }
}
 
public class PersonValidator : AbstractValidator<Person> {
    public PersonValidator() {
        RuleFor(x => x.Id).NotNull();
        RuleFor(x => x.Name).Length(0, 10);
        RuleFor(x => x.Email).EmailAddress();
        RuleFor(x => x.Age).InclusiveBetween(18, 60);
    }
}
همان طور که ملاحظه می‌کنید، در بالای تعریف کلاس Person با استفاده از ValidatorAttribute مشخص کرده ایم که از PersonValidator جهت اعتبارسنجی استفاده کند.
در آخر می‌توانیم Controller و View ی برنامه مان را درست کنیم:
public class PeopleController : Controller {
    public ActionResult Create() {
        return View();
    }
 
    [HttpPost]
    public ActionResult Create(Person person) {
 
        if(! ModelState.IsValid) { // re-render the view when validation failed.
            return View("Create", person);
        }
 
        TempData["notice"] = "Person successfully created";
        return RedirectToAction("Index");
    }
}
@Html.ValidationSummary()
 
@using (Html.BeginForm()) {
Id: @Html.TextBoxFor(x => x.Id) @Html.ValidationMessageFor(x => x.Id)
<br />
Name: @Html.TextBoxFor(x => x.Name) @Html.ValidationMessageFor(x => x.Name) 
<br />
Email: @Html.TextBoxFor(x => x.Email) @Html.ValidationMessageFor(x => x.Email)
<br />
Age: @Html.TextBoxFor(x => x.Age) @Html.ValidationMessageFor(x => x.Age)
 
<input type="submit" value="submit" />
}
اکنون DefaultModelBinder موجود در MVC برای اعتبارسنجی شیء Person از FluentValidationModelValidatorProvider استفاده خواهد کرد.
توجه داشته باشید که FluentValidation با اعتبارسنجی سمت کاربر ASP.NET MVC به خوبی کار خواهد کرد منتها نه برای تمامی اعتبارسنجی ها. به عنوان مثال تمام قوانینی که از شرط‌های When/Unless استفاده کرده اند، Validator‌های سفارشی، و قوانینی که در آن‌ها از Must استفاده شده باشد، اعتبارسنجی سمت کاربر نخواهند داشت. در زیر لیست Validator هایی که با اعتبارسنجی سمت کاربر به خوبی کار خواهند کرد آمده است:
  • NotNull/NotEmpty
  • Matches 
  • InclusiveBetween 
  • CreditCard 
  • Email 
  • EqualTo 
  • Length 
صفت CustomizeValidator
با استفاده از CustomizeValidatorAttribute می‌توان نحوه اجرای Validator را تنظیم کرد. به عنوان مثال اگر میخواهید که Validator تنها برای یک RuleSet مخصوص انجام شود می‌توانید مانند زیر عمل کنید: 
public ActionResult Save([CustomizeValidator(RuleSet="MyRuleset")] Customer cust) {
  // ...
}


مواردی که تا اینجا گفته شد برای استفاده در یک برنامه‌ی ساده MVC کافی به نظر می‌رسد، اما از اینجا به بعد مربوط به مواقعی است که نخواهیم از Attribute‌ها استفاده کنیم و کار را به IoC بسپاریم. 
استفاده از Validator Factory با استفاده از یک IoC Container
Validator Factory چیست؟ Validator Factory یک کلاس می‌باشد که وظیفه ساخت نمونه از Validator‌‌ها را بر عهده دارد. اینترفیس IValidatorFactory به صورت زیر می‌باشد:  
public interface IValidatorFactory {
   IValidator<T> GetValidator<T>();
   IValidator GetValidator(Type type);
}
ساخت Validator Factory سفارشی:
برای ساخت یک Validator Factory شما می‌توانید به طور مستقیم IValidatorFactory را پیاده سازی نمایید یا از کلاس ValidatorFactoryBase به عنوان کلاس پایه استفاده کنید (که مقداری از کارها را برای شما انجام داده است). در این مثال نحوه ایجاد یک Validator Factory که از StructureMap استفاده می‌کند را بررسی خواهیم کرد. ابتدا نیاز به ثبت Validator‌ها در StructureMap داریم: 
ObjectFactory.Configure(cfg => cfg.AddRegistry(new MyRegistry()));
 
public class MyRegistry : Registry {
    public MyRegistry() {
        For<IValidator<Person>>()
    .Singleton()
    .Use<PersonValidator>();
    }
}
در اینجا StructureMap را طوری تنظیم کرده ایم که از یک Registry سفارشی استفاده کند. در داخل این Registry به StructureMap میگوییم که زمانی که خواسته شد تا یک نمونه از IValidator<Person> ایجاد کند، PersonValidator را بر گرداند. متد CreateInstance نوع مناسب را نمونه سازی می‌کند (CustomerValidator) و آن را بازمی گرداند ( یا Null بر می‌گرداند اگر نوع مناسبی وجود نداشته باشد) 
استفاده از AssemblyScanner 
FluentValidation دارای یک AssemblyScanner می‌باشد که کار ثبت Validator‌ها داخل یک اسمبلی را راحت‌تر می‌سازد. با استفاده از AssemblyScanner کلاس MyRegistery ما شبیه قطعه کد زیر خواهد شد: 
public class MyRegistry : Registry {
   public MyRegistry() {
 
     AssemblyScanner.FindValidatorsInAssemblyContaining<MyValidator>()
       .ForEach(result => {
            For(result.InterfaceType)
               .Singleton()
               .Use(result.ValidatorType);
       });
   }
}
حالا زمان استفاده از factory ساخته شده در متد Application_Start برنامه می‌باشد:
protected void Application_Start() {
    RegisterRoutes(RouteTable.Routes);
 
    //Configure structuremap
    ObjectFactory.Configure(cfg => cfg.AddRegistry(new MyRegistry()));
    ControllerBuilder.Current.SetControllerFactory(new StructureMapControllerFactory());
 
    //Configure FV to use StructureMap
    var factory = new StructureMapValidatorFactory();
 
    //Tell MVC to use FV for validation
    ModelValidatorProviders.Providers.Add(new FluentValidationModelValidatorProvider(factory));        
    DataAnnotationsModelValidatorProvider.AddImplicitRequiredAttributeForValueTypes = false;
}
اکنون FluentValidation از StructureMap برای نمونه سازی Validatorها استفاده خواهد کرد و کار اعتبارسنجی مدل‌ها به FluentValidaion سپرده شده است.
مطالب
SortedSet در دات نت 4

SortedSet قرار گرفته در فضای نام System.Collections.Generic دات نت 4، لیستی از اشیاء به صورت خودکار مرتب شده را ارائه می‌دهد. SortedSet نیز همانند HashSet از اعضای منحصربفردی تشکیل خواهد شد اما اینبار به شکلی مرتب شده. برای پیاده سازی آن از red-black tree data structure استفاده شده است که مهم‌ترین مزیت آن امکان افزودن و یا حذف اشیاء به آن بدون کاهش قابل توجه کارآیی برنامه است.

مثال اول:
using System;
using System.Collections.Generic;

namespace SortedSetTest
{
class Program
{
static void sample1()
{
var setRange = new SortedSet<int> { 2, 5, 6, 2, 1, 4, 8 };

foreach (var i in setRange)
{
Console.WriteLine(i);
}
}

static void Main()
{
sample1();
}
}
}
در این مثال با نحوه‌ی ایجاد این لیست جنریک خود مرتب شونده‌ی تکراری نپذیر (!) آشنا می‌شوید. اگر این مثال را اجرا نمائید، خروجی آن مرتب شده است و همچنین تنها شامل یک عدد 2 است (اعضای تکراری را حذف می‌کند).

مثال دوم:

using System;
using System.Collections.Generic;

namespace SortedSetTest
{
class Program
{
static void sample2()
{
var setRange = new SortedSet<int>();
var random = new Random();

for (int counter = 0; counter < 100; counter++)
{
var rnd = random.Next(-180, 181);
if (!setRange.Add(rnd))
{
Console.WriteLine("Couldn't add {0}", rnd);
}
}

Console.WriteLine("Result set:");
foreach (var item in setRange)
{
Console.WriteLine(item);
}
}

static void Main()
{
sample2();
}
}
}
در این مثال نحوه‌ی افزودن اعضای مختلف به این لیست ویژه، توسط متد Add آن بیان شده است. اگر آیتمی در این لیست موجود باشد، مجددا اضافه نشده و حاصل متد Add آن، False خواهد بود.

مثال سوم:
اگر از سایر انواع سفارشی تعریف شده استفاده نمائید، باید روش مقایسه‌ی آن‌ها را نیز با پیاده سازی اینترفیس استاندارد IComparable ارائه دهید؛ در غیر اینصورت با خطای At least one object must implement IComparable متوقف خواهید شد.

using System;
using System.Collections;
using System.Collections.Generic;

namespace SortedSetTest
{
class FileInfo
{
public string Name { set; get; }
public long Size { set; get; }
}

class FileInfoComparer : IComparer<FileInfo>
{
public int Compare(FileInfo x, FileInfo y)
{
var caseiComp = new CaseInsensitiveComparer();
return caseiComp.Compare(x.Name, y.Name);
}
}


class Program
{

static void sample3()
{
var setRange = new SortedSet<FileInfo>(new FileInfoComparer())
{
new FileInfo
{
Name = "file1.txt",
Size = 100
},
new FileInfo
{
Name = "file2.txt",
Size = 10
},
new FileInfo
{
Name = "file3.txt",
Size = 300
}
};

foreach (var item in setRange)
{
Console.WriteLine(item.Name);
}
}

static void Main()
{
sample3();

Console.WriteLine("Press a key...");
Console.ReadKey();
}
}
}

در این مثال اشیایی از نوع کلاس FileInfo به لیست ویژه‌ی ما اضافه شده‌اند. برای اینکه امکان مقایسه‌ی آن‌ها فراهم باشد ، کلاس FileInfoComparer با پیاده سازی اینترفیس IComparer ، روش مقایسه دو شیء از این دست را ارائه می‌دهد.

نظرات مطالب
بررسی تفصیلی رابطه Many-to-Many در EF Code first
توضیح دادم با مثال:  «... در مثالی دیگر اگر یک برنامه ASP.NET را درنظر بگیریم  ...»
listOfActualTags از بانک اطلاعاتی دریافت شده؛ بر اساس مواردی که موجود بوده.
به این ترتیب چون این تگ‌ها به سیستم ردیابی EF وارد می‌شوند و همچنین post1.Tags.Clear در ابتدای کار فراخوانی شده، استفاده از متد (post1.Tags.Add(item سبب ثبت مورد تکراری نخواهد شد.
کلا EF هر آیتمی رو که Id آن‌را از طریق دریافت اطلاعات از بانک اطلاعاتی در سیستم ردیابی خودش داشته باشه، جدید و تکراری ثبت نمی‌کنه. برای نمونه در حالت new Tag استفاده شده، این موارد جدید ثبت می‌شوند چون Id از قبل ثبت شده‌ای ندارند.
برای توضیحات بیشتر مراجعه کنید به مطلب نحوه استفاده از کلیدهای خارجی در EF. (حتی می‌شود یک شیء را بدون واکشی از دیتابیس به سیستم ردیابی وارد کرد؛ البته اگر Id آن‌را داشته باشید)
مطالب دوره‌ها
تزریق وابستگی‌های AutoMapper در لایه سرویس برنامه
اگر مطلب «Refactoring به تزریق وابستگی‌ها» را به خاطر داشته باشید، جهت تشخیص وابستگی‌های یک کلاس، کار از بررسی کلمات new و همچنین فراخوانی‌های استاتیک، شروع می‌شود و ... متد استاتیک Mapper.Map کتابخانه‌ی AutoMapper نیز از همین دست است. در ادامه قصد داریم بجای فراخوانی مستقیم Mapper.Map از اینترفیس IMappingEngine به عنوان تامین کننده‌ی متد Map استفاده کنیم. همچنین کلاس‌های Profile نوشته شده را نیز به صورت خودکار به برنامه اضافه نمائیم.


تنظیمات IoC Container مختص به AutoMapper

در ذیل یک کلاس Registry مخصوص StructureMap را مشاهده می‌کنید که جهت کپسوله کردن اطلاعات خاص AutoMapper تهیه شده‌است. می‌توان این اطلاعات را در داخل تنظیمات new Container خود قرار داد و یا می‌توان آن‌ها را جهت شلوغ نشدن سایر تنظیمات IoC Container، به یک کلاس Registry منتقل کرد:
public class AutomapperRegistry : Registry
{
    public AutomapperRegistry()
    {
        var platformSpecificRegistry = PlatformAdapter.Resolve<IPlatformSpecificMapperRegistry>();
        platformSpecificRegistry.Initialize();
 
        For<ConfigurationStore>().Singleton().Use<ConfigurationStore>()
            .Ctor<IEnumerable<IObjectMapper>>().Is(MapperRegistry.Mappers);
 
        For<IConfigurationProvider>().Use(ctx => ctx.GetInstance<ConfigurationStore>());
 
        For<IConfiguration>().Use(ctx => ctx.GetInstance<ConfigurationStore>());
 
        For<ITypeMapFactory>().Use<TypeMapFactory>();
 
        For<IMappingEngine>().Singleton().Use<MappingEngine>()
                             .SelectConstructor(() => new MappingEngine(null));
 
        this.Scan(scanner =>
        {
            scanner.AssembliesFromApplicationBaseDirectory();
 
            scanner.ConnectImplementationsToTypesClosing(typeof(ITypeConverter<,>))
                   .OnAddedPluginTypes(t => t.HybridHttpOrThreadLocalScoped());
 
            scanner.ConnectImplementationsToTypesClosing(typeof(ValueResolver<,>))
                .OnAddedPluginTypes(t => t.HybridHttpOrThreadLocalScoped());
        });
    }
}
هدف اصلی، وهله سازی خودکار IMappingEngine است و برای رسیدن به آن، باید تمام وابستگی‌های کلاس MappingEngine را مانند IConfigurationProvider و سایر مواردی که مشاهده می‌کنید، مشخص کرد. پس از این تنظیمات، کلاس ObjectFactory سفارشی برنامه به شکل ذیل جهت معرفی AutomapperRegistry تغییر خواهد کرد:
public static class SmObjectFactory
{
    private static readonly Lazy<Container> _containerBuilder =
        new Lazy<Container>(defaultContainer, LazyThreadSafetyMode.ExecutionAndPublication);
 
    public static IContainer Container
    {
        get { return _containerBuilder.Value; }
    }
 
    private static Container defaultContainer()
    {
        var container = new Container(cfg =>
        {
            cfg.AddRegistry<AutomapperRegistry>();
            cfg.Scan(scan =>
            {
                scan.TheCallingAssembly();
                scan.WithDefaultConventions();
                scan.AddAllTypesOf<Profile>().NameBy(item => item.FullName);
            });
        });
 
        configureAutoMapper(container);
 
        return container;
    }
 
    private static void configureAutoMapper(IContainer container)
    {
        var configuration = container.TryGetInstance<IConfiguration>();
        if (configuration == null) return;
        //saying AutoMapper how to resolve services
        configuration.ConstructServicesUsing(container.GetInstance);
        foreach (var profile in container.GetAllInstances<Profile>())
        {
            configuration.AddProfile(profile);
        }
    }
}
در اینجا علاوه بر معرفی AutomapperRegistry، یک مورد دیگر نیز اضافه شده‌است: یافتن خودکار کلاس‌هایی از نوع Profile و همچنین فراخوانی متد AddProfile کتابخانه‌ی AutoMapper به صورت خودکار. به این ترتیب دیگر نیازی نخواهد بود تا در ابتدای کار برنامه، متد Mapper.Initialize را جهت معرفی کلاس‌های Profile فراخوانی کرد و این‌کار به صورت خودکار توسط متد configureAutoMapper انجام می‌شود.


تغییرات لایه سرویس برنامه جهت استفاده از IoC Container

اکنون که IoC Container ما با نحوه‌ی یافتن وابستگی‌های IMappingEngine آشنا شده‌است، تنها کافی است این اینترفیس را در سازنده‌ی کلاس سرویس خود تزریق کنیم:
public class UsersService : IUsersService
{
    private readonly IMappingEngine _mappingEngine;
 
    public UsersService(IMappingEngine mappingEngine)
    {
        _mappingEngine = mappingEngine;
    }
 
    public UserViewModel GetName(int id)
    {
        var dbUser1 = new User
        {
            Id = 1,
            Name = "Test",
            RegistrationDate = DateTime.Now.AddDays(-10)
        };
 
        var uiUser = new UserViewModel();
        _mappingEngine.Map(source: dbUser1, destination: uiUser);
        return uiUser;
    }
}
و پس از آن از متد Map این اینترفیس بجای فراخوانی مستقیم Mapper.Map می‌توان استفاده کرد. به این ترتیب وابستگی مورد نیاز این کلاس، از طریق سازنده‌ی آن به آن تزریق شده‌است و دیگر فراخوانی‌های استاتیک را در اینجا مشاهده نمی‌کنیم.


کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید:
AM_Sample03.zip