نظرات مطالب
شروع به کار با EF Core 1.0 - قسمت 3 - انتقال مهاجرت‌ها به یک اسمبلی دیگر
ایجاد نام جداول به صورت جمع (Pluralized) و داینامیک :
اگه از روش خودکار کردن تعاریف DbSet ها استفاده کرده باشیم چون دیگر در داخل کلاس Context برنامه، Dbset تعریف نمی‌شود معمولا برای نام گذاری جداول که به صورت جمع باشند از اتریبیوت Table استفاده می‌کنیم.
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

[Table("Users")]
public class User : BaseEntity
{
    public long Id { get; set; }

    [Required]
    public string FullName { get; set; } = null!;
}
برای اینکه نام جداول به صورت خودکار به صورت جمع ایجاد شوند میتوان از این متد استفاده کرد:
using Microsoft.EntityFrameworkCore;
using System.ComponentModel.DataAnnotations.Schema;
using System.Reflection;

namespace ProjectName.Common.EfHelpers;

public static class EfToolkit
{
    /// <summary>
    /// ایجاد نام موجودیت‌ها
    /// نام موجودیت اگر اتریبیوت تیبل داشته باشد
    /// از همان نام استفاده میشود و اگر نداشته باشد
    /// نامش جمع بسته میشود
    /// </summary>
    /// <param name="builder"></param>
    public static void MakeTableNamesPluralized(this ModelBuilder builder)
    {
        var entityTypes = builder.Model.GetEntityTypes();

        foreach (var entityType in entityTypes)
        {
            // Get the CLR type of the entity
            var entityClrType = entityType.ClrType;
            var hasTableAttribute = entityClrType.GetCustomAttribute<TableAttribute>();

            // Apply the pluralized table name for the entity
            if (hasTableAttribute is null)
            {
                // Get the pluralized table name
                var pluralizedTableName = GetPluralizedTableName(entityClrType);
                builder.Entity(entityClrType).ToTable(pluralizedTableName);
            }
        }
    }

    /// <summary>
    /// گرفتن نام تایپ و عوض کردن نام آن از مفرد به جمع
    /// Singular to plural
    /// Category => Categories
    /// Box => Boxes
    /// Bus => Buses
    /// Computer => Computers
    /// </summary>
    /// <param name="entityClrType"></param>
    /// <returns></returns>
    private static string GetPluralizedTableName(Type entityClrType)
    {
        // Example implementation (Note: This is a simple pluralization logic and might not cover all cases)
        var typeName = entityClrType.Name;
        if (typeName.EndsWith("y"))
        {
            // Substring(0, typeName.Length - 1)
            // Range indexer
            var typeNameWithoutY = typeName[..^1];
            return typeNameWithoutY + "ies";
        }

        if (typeName.EndsWith("s") || typeName.EndsWith("x"))
        {
            return typeName + "es";
        }
        return typeName + "s";
    }
}
نحوه فراخوانی در متد OnModelCreating:
protected override void OnModelCreating(ModelBuilder builder)
{
    // it should be placed here, otherwise it will rewrite the following settings!
    base.OnModelCreating(builder);
    builder.RegisterAllEntities(typeof(BaseEntity));
    builder.MakeTableNamesPluralized();
    builder.ApplyConfigurationsFromAssembly(typeof(ApplicationDbContext).Assembly);
}
هر موجودیتی که اتریبیوت Table داشته باشد از همان نام برای جدول استفاده می‌شود در غیر اینصورت نام انتیتی به صورت جمع ایجاد می‌گردد.
قبل از اینکه نام جداول نیز جمع بسته شود تمامی موجودیت‌ها به صورت خودکار اضافه می‌شوند.
public static class EfToolkit
{
    /// <summary>
    /// ثبت تمامی انتیتی‌ها
    /// <param name="builder"></param>
    /// <param name="type"></param>
    /// </summary>
    public static void RegisterAllEntities(this ModelBuilder builder, Type type)
    {
        var entities = type.Assembly.GetTypes()
            .Where(x => x.BaseType == type);
        foreach (var entity in entities)
            builder.Entity(entity);
    }
}  
هر کلاسی در لایه موجودیت‌ها از BaseEntity ارث بری کرده باشد به عنوان یک Entity شناخته می‌شود.
مطالب
C# 12.0 - Collection Expressions & Spread Operator
C# 12 به همراه روش جدیدی برای آغاز مجموعه‌ها است که با آرایه‌ها، Spanها و هر نوعی که آغازگرهای مجموعه‌ها را بپذیرد، کار می‌کند. همچنین اپراتور جدیدی را هم به نام spread operator به صورت .. به زبان #C اضافه کرده‌است که امکان ساده‌تر ترکیب مجموعه‌ها را میسر می‌کند.


آغاز ساده‌تر مجموعه‌ها با کمک Collection Expressions

تا پیش از C# 12 برای آغاز یک آرایه می‌توان از روش زیر استفاده کرد که در آن نوع آرایه از طریق نوع اعضای آن حدس زده می‌شود:
var numbers1_CS11 = new[] { 1, 2, 3 };
که در حقیقت ساده شده‌ی تعریف اصلی زیر است:
var numbers1_CS_11 = new int[] { 1, 2, 3 };
در C# 12، می‌توان این تعاریف را به کمک collection expressions، خلاصه‌تر هم کرد:
int[] numbers1_CS12 = [ 1, 2, 3 ];
که در اینجا، {}‌ها به [] تبدیل شده‌اند و ذکر نوع آرایه، ضروری است (یعنی نمی‌توان از var جهت تعریف آن‌ها استفاده کرد)؛ در غیراینصورت با خطای زیر متوقف می‌شویم:
error CS9176: There is no target type for the collection expression.

یک collection expression و یا collection literals، به مجموعه‌ای از عناصر گفته می‌شود که بین دو براکت [] قرار می‌گیرند.

نمونه‌ی دیگر آن کار با Spanها است که نمونه کد C# 11 آن:
Span<string> span1_CS11 = new string[] { "AC", "AL" };
در C# 12 به صورت زیر خلاصه می‌شود:
Span<string> span1_CS12 = [ "AC", "AL" ];
و در اینجا امکان کار با ReadOnlySpan‌ها هم وجود دارد:
ReadOnlySpan<string> readOnlySpan_CS12 = [ "Africa",  "Asia", "Europa"];

مثال دیگر، نحوه‌ی آغاز آرایه‌های چندبعدی است:
int[][] array2D_CS11 =
  {
    new int[] { 2002, 2006, 2010},
    new int[] { 2014, 2018},
    new int[] { 2022, 2026, 2030}
  };
که در C# 12 به صورت خلاصه‌ی زیر قابل بیان است:
int[][] array2D_CS12 =
  [
     [2002, 2006, 2010],
     [2014, 2018],
     [2022, 2026, 2030]
  ];

و یا حتی این مورد را در مورد نحوه‌ی آغاز Listهای پیش از C#12
List<string> list_CS11 = new List<string> { "Item 1", "Item 2" };
نیز می‌توان بکار گرفت:
List<string> list_CS12 = [ "Item 1", "Item 2" ];

در کل همانطور که مشاهده می‌کنید، این تغییر، تغییر مثبتی است و حجم قابل ملاحظه‌ای از کدها را کاهش داده و خواندن آن‌ها را نیز ساده‌تر می‌کند.

یک نکته: روش ساده شده‌ی آغاز یک لیست با مجموعه‌ای خالی در C# 12 به صورت زیر است:
// Before C#12
List<User> users = new List<User>();
// or
var users = new List<User>();
// or
List<User> user = new();

// C#12
List<User> users = [];


اضافه شدن spread operator به زبان #C

اگر پیشتر با زبان JavaScript کار کرده باشید، با spread operator هم آشنایی دارید. کار آن ساده سازی یکی کردن مجموعه‌ها و یا افزودن ساده‌تر عناصری به آن‌ها است و .. بالاخره به زبان #C هم راه پیدا کرده‌است! برای مثال دو آرایه‌ی زیر را درنظر بگیرید:
int[] numbers1_CS12 = [ 1, 2, 3 ];
int[] numbers2_CS12 = [ 4, 5, 6 ];
در C# 12 برای یکی کردن آن‌ها می‌توان از spread operator به صورت زیر استفاده کرد:
int[] allItems = [ ..numbers1_CS12, ..numbers2_CS12 ];
Spread به معنای «پخش کردن»/«گسترده کردن»/«باز کردن» هست. برای مثال در اینجا، اعضای دو آرایه را داخل یک آرایه‌ی جدید، پخش کرده‌ایم!

اگر در نگارش‌های قبلی #C بخواهیم چنین کاری را انجام دهیم، یک روش آن به صورت زیر است:
int[] allItems_CS11 = numbers1_CS12.Concat(numbers2_CS12).ToArray();
که ... نگارش C# 12 آن کارآیی بیشتری دارد؛ چون تعداد بار اختصاص حافظه‌ی آن کمتر است. در C# 12، هنگام استفاده از spread operator، کار کپی کردن اطلاعات صورت نمی‌گیرد و همچنین طول نهایی مجموعه‌ی حاصل دقیقا مشخص می‌شود که این مورد از چندین بار تخصیص حافظه برای چسباندن آرایه‌های مختلف به هم جلوگیری می‌کند.

همچنین اپراتور پخش کردن، قابلیت قرارگرفتن در کنار سایر اعضای یک آرایه را هم به سادگی و با خوانایی بیشتری به همراه دارد:
int[] join = [..a, ..b, ..c, 6, 5];

به علاوه محدودیتی در مورد نوع مجموعه‌ی بکار گرفته شده نیز در اینجا وجود ندارد. برای نمونه در مثال زیر، یک آرایه، یک Span و یک لیست، با هم یکی شده‌اند:
int[] a =[1, 2, 3];
Span<int> b = [2, 4, 5, 4, 4];
List<int> c = [4, 6, 6, 5];

List<int> join = [..a, ..b, ..c, 6, 5];

و مثالی دیگر، نحوه‌ی ساده‌ی تعریف لیستی از tuples است:
List<(string, int)> otherScores = [("Dave", 90), ("Bob", 80)];
و سپس باز کردن آن داخل آرایه‌ای از tuples:
(string name, int score)[] scores = [("Alice", 90), ..otherScores, ("Charlie", 70)];
مطالب
ASP.NET MVC #5

بررسی نحوه انتقال اطلاعات از یک کنترلر به View‌های مرتبط با آن

در ASP.NET Web forms در فایل code behind یک فرم مثلا می‌توان نوشت Label1.Text و سپس مقداری را به آن انتساب داد. اما اینجا به چه ترتیبی می‌توان شبیه به این نوع عملیات را انجام داد؟ با توجه به اینکه در کنترلر‌ها هیچ نوع ارجاع مستقیمی به اشیاء رابط کاربری وجود ندارد و این دو از هم مجزا شده‌اند.
در پاسخ به این سؤال، همان مثال ساده قسمت قبل را ادامه می‌دهیم. یک پروژه جدید خالی ایجاد شده است به همراه HomeController ایی که به آن اضافه کرده‌ایم. همچنین مطابق روشی که ذکر شد، View ایی به نام Index را نیز به آن اضافه کرده‌ایم. سپس برای ارسال اطلاعات از یک کنترلر به View از یکی از روش‌های زیر می‌توان استفاده کرد:

الف) استفاده از اشیاء پویا

ViewBag یک شیء dynamic است که در دات نت 4 امکان تعریف آن میسر شده است. به این معنا که هر نوع خاصیت دلخواهی را می‌توان به این شیء انتساب داد و سپس این اطلاعات در View نیز قابل دسترسی و استخراج خواهد بود. مثلا اگر در اینجا به شیء ViewBag، خاصیت دلخواه Country را اضافه کنیم و سپس مقداری را نیز به آن انتساب دهیم:

using System.Web.Mvc;

namespace MvcApplication1.Controllers
{
public class HomeController : Controller
{
public ActionResult Index()
{
ViewBag.Country = "Iran";
return View();
}
}
}

این اطلاعات در View مرتبط با اکشنی به نام Index به نحو زیر قابل بازیابی خواهد بود (نحوه اضافه کردن View متناظر با یک اکشن یا متد را هم در قسمت قبل با تصویر مرور کردیم):

@{
ViewBag.Title = "Index";
}
<h2>
Index</h2>
<p>
Country : @ViewBag.Country
</p>

در این مثال، @ در View engine جاری که Razor نام دارد، به این معنا می‌باشد که این مقداری است که می‌خواهم دریافت کنی (ViewBag.Country) و سپس آن‌را در حین پردازش صفحه نمایش دهی.


ب) انتقال اطلاعات یک شیء کامل و غیر پویا به View

هر پروژه جدید MVC به همراه پوشه‌ای به نام Models است که در آن می‌توان تعاریف اشیاء تجاری برنامه را قرار داد. در پروژه جاری، یک کلاس ساده را به نام Employee به این پوشه اضافه می‌کنیم:

namespace MvcApplication1.Models
{
public class Employee
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
}
}

اکنون برای نمونه یک وهله از این شیء را در متد Index ایجاد کرده و سپس به view متناظر با آن ارسال می‌کنیم (در قسمت return View کد زیر مشخص است). بدیهی است این وهله سازی در عمل می‌تواند از طریق دسترسی به یک بانک اطلاعاتی یا یک وب سرویس و غیره باشد.

using System.Web.Mvc;
using MvcApplication1.Models;

namespace MvcApplication1.Controllers
{
public class HomeController : Controller
{
public ActionResult Index()
{
ViewBag.Country = "Iran";

var employee = new Employee
{
Email = "name@site.com",
FirstName = "Vahid",
LastName = "N."
};

return View(employee);
}
}
}

امضاهای متفاوت (overloads) متد کمکی View هم به شرح زیر هستند:

ViewResult View(Object model)
ViewResult View(string viewName, Object model)
ViewResult View(string viewName, string masterName, Object model)


اکنون برای دسترسی به اطلاعات این شیء employee در View متناظر با این متد، چندین روش وجود دارد:

@{
ViewBag.Title = "Index";
}
<h2>
Index</h2>
<div>
Country: @ViewBag.Country <‪br />
FirstName: @Model.FirstName
</div>

می‌توان از طریق شیء استاندارد دیگری به نام Model (که این هم یک شیء dynamic است مانند ViewBag قسمت قبل)، به خواص شیء یا مدل ارسالی به View جاری دسترسی پیدا کرد که یک نمونه از آن‌را در اینجا ملاحظه می‌کنید.
روش دوم، بر اساس تعریف صریح نوع مدل است به نحو زیر:

@model MvcApplication1.Models.Employee
@{
ViewBag.Title = "Index";
}
<h2>
Index</h2>
<div>
Country: @ViewBag.Country
<‪br />
FirstName: @Model.FirstName
</div>

در اینجا در مقایسه با قبل، تنها یک سطر به اول فایل View اضافه شده است که در آن نوع شیء Model تعیین می‌گردد (کلمه model هم در اینجا با حروف کوچک شروع شده است). به این ترتیب اینبار اگر سعی کنیم به خواص این شیء دسترسی پیدا کنیم، Intellisense ویژوال استودیو ظاهر می‌شود. به این معنا که شیء Model بکارگرفته شده اینبار دیگر dynamic نیست و دقیقا می‌داند که چه خواصی را باید پیش از اجرای برنامه در اختیار استفاده کننده قرار دهد.
به این روش، روش Strongly typed view هم گفته می‌شود؛ چون View دقیقا می‌داند که چون نوعی را باید انتظار داشته باشد؛ تحت نظر کامپایلر قرار گرفته و همچنین Intellisense نیز برای آن مهیا خواهد بود.
به همین جهت این روش Strongly typed view، در بین تمام روش‌های مهیا، به عنوان روش توصیه شده و مرجح مطرح است.
به علاوه استفاده از Strongly typed views یک مزیت دیگر را هم به همراه دارد: فعال شدن یک code generator توکار در VS.NET به نام scaffolding. یک مثال ساده:
تا اینجا ما اطلاعات یک کارمند را نمایش دادیم. اگر بخواهیم یک لیست از کارمندها را نمایش دهیم چه باید کرد؟
روش کار با قبل تفاوتی نمی‌کند. اینبار در return View ما، یک شیء لیستی ارائه خواهد شد. در سمت View هم با یک حلقه foreach کار نمایش این اطلاعات صورت خواهد گرفت. راه ساده‌تری هم هست. اجازه دهیم تا خود VS.NET، کدهای مرتبط را برای ما تولید کند.
یک کلاس دیگر به پوشه مدل‌های برنامه اضافه کنید به نام Employees با محتوای زیر:

using System.Collections.Generic;

namespace MvcApplication1.Models
{
public class Employees
{
public IList<Employee> CreateEmployees()
{
return new[]
{
new Employee { Email = "name1@site.com", FirstName = "name1", LastName = "LastName1" },
new Employee { Email = "name2@site.com", FirstName = "name2", LastName = "LastName2" },
new Employee { Email = "name3@site.com", FirstName = "name3", LastName = "LastName3" }
};
}
}
}

سپس متد جدید زیر را به کنترلر Home اضافه کنید.

public ActionResult List()
{
var employeesList = new Employees().CreateEmployees();
return View(employeesList);
}

برای اضافه کردن View متناظر با آن، روی نام متد کلیک راست کرده و گزینه Add view را انتخاب کنید. در صفحه ظاهر شده:


تیک مربوط به Create a strongly typed view را قرار دهید. سپس در قسمت Model class، کلاس Employee را انتخاب کنید (نه Employees جدید را، چون از آن می‌خواهیم به عنوان منبع داده لیست تولیدی استفاده کنیم). اگر این کلاس را مشاهده نمی‌کنید، به این معنا است که هنوز برنامه را یکبار کامپایل نکرده‌اید تا VS.NET بتواند با اعمال Reflection بر روی اسمبلی برنامه آن‌را پیدا کند. سپس در قسمت Scaffold template گزینه List را انتخاب کنید تا Code generator توکار VS.NET فعال شود. اکنون بر روی دکمه Add کلیک نمائید تا View نهایی تولید شود. برای مشاهده نتیجه نهایی مسیر http://localhost/Home/List باید بررسی گردد.


ج) استفاده از ViewDataDictionary

ViewDataDictionary از نوع IDictionary با کلیدی رشته‌ای و مقداری از نوع object است. توسط آن شیء‌ایی به نام ViewData در ASP.NET MVC به نحو زیر تعریف شده است:

public ViewDataDictionary ViewData { get; set; }

این روش در نگارش‌های اولیه ASP.NET MVC بیشتر مرسوم بود. برای مثال:

using System;
using System.Web.Mvc;

namespace MvcApplication1.Controllers
{
public class HomeController : Controller
{
public ActionResult Index()
{
ViewData["DateTime"] = "<‪br/>" + DateTime.Now;
return View();
}
}
}

و سپس جهت استفاده از این ViewData تعریف شده با کلید دلخواهی به نام DateTime در View متناظر با اکشن Index خواهیم داشت:

@{
ViewBag.Title = "Index";
}
<h2>
Index</h2>
<div>
DateTime: @ViewData["DateTime"]
</div>

یک نکته امنیتی:
اگر به مقدار انتساب داده شده به شیء ViewDataDictionary دقت کنید، یک تگ br هم به آن اضافه شده است. برنامه را یکبار اجرا کنید. مشاهده خواهید کرد که این تگ به همین نحو نمایش داده می‌شود و نه به صورت یک سطر جدید HTML . چرا؟ چون Razor به صورت پیش فرض اطلاعات را encode شده (فراخوانی متد Html.Encode در پشت صحنه به صورت خودکار) در صفحه نمایش می‌دهد و این مساله از لحاظ امنیتی بسیار عالی است؛ زیرا جلوی بسیاری از حملات cross site scripting یا XSS را خواهد گرفت.
احتمالا الان این سؤال پیش خواهد آمد که اگر «عالمانه» بخواهیم این رفتار نیکوی پیش فرض را غیرفعال کنیم چه باید کرد؟
برای این منظور می‌توان نوشت:
@Html.Raw(myString)

و یا:
<div>@MvcHtmlString.Create("<h1>HTML</h1>")</div>

به این ترتیب خروجی Razor دیگر encode شده نخواهد بود.


د) استفاده از TempData

TempData نیز یک dictionary دیگر برای ذخیره سازی اطلاعات است و به نحو زیر در فریم ورک تعریف شده است:

public TempDataDictionary TempData { get; set; }

TempData در پشت صحنه از سشن‌های ASP.NET جهت ذخیره سازی اطلاعات استفاده می‌کند. بنابراین اطلاعات آن در سایر کنترلرها و View ها نیز در دسترس خواهد بود. البته TempData یک سری تفاوت هم با سشن معمولی ASP.NET دارد:
- بلافاصله پس از خوانده شدن، حذف خواهد شد.
- پس از پایان درخواست از بین خواهد رفت.
هر دو مورد هم به جهت بالابردن کارآیی برنامه‌های ASP.NET MVC و مصرف کمتر حافظه سرور درنظر گرفته‌ شده‌اند.
البته کسانی که برای بار اول هست با ASP.NET مواجه می‌شوند، شاید سؤال بپرسند این مسایل چه اهمیتی دارد؟ پروتکل HTTP، ذاتا یک پروتکل «بدون حالت» است یا Stateless هم به آن گفته می‌شود. به این معنا که پس از ارائه یک صفحه وب توسط سرور، تمام اشیاء مرتبط با آن در سمت سرور تخریب خواهند شد. این مورد متفاوت‌ است با برنامه‌های معمولی دسکتاپ که طول عمر یک شیء معمولی تعریف شده در سطح فرم به صورت یک فیلد، تا زمان باز بودن آن فرم، تعیین می‌گردد و به صورت خودکار از حافظه حذف نمی‌شود. این مساله دقیقا مشکل تمام تازه واردها به دنیای وب است که چرا اشیاء ما نیست و نابود شدند. در اینجا وب سرور قرار است به هزاران درخواست رسیده پاسخ دهد. اگر قرار باشد تمام این اشیاء را در سمت سرور نگهداری کند، خیلی زود با اتمام منابع مواجه می‌گردد. اما واقعیت این است که نیاز است یک سری از اطلاعات را در حافظه نگه داشت. به همین منظور یکی از چندین روش مدیریت حالت در ASP.NET استفاده از سشن‌ها است که در اینجا به نحو بسیار مطلوبی، با سربار حداقل توسط TempData مدیریت شده است.
یک مثال کاربردی در این زمینه:
فرض کنید در متد جاری کنترلر، ابتدا بررسی می‌کنیم که آیا ورودی دریافتی معتبر است یا خیر. در غیراینصورت، کاربر را به یک View دیگر از طریق کنترلری دیگر جهت نمایش خطاها هدایت خواهیم کرد.
همین «هدایت مرورگر به یک View دیگر» یعنی پاک شدن و تخریب اطلاعات کنترلر قبلی به صورت خودکار. بنابراین نیاز است این اطلاعات را در TempData قرار دهیم تا در کنترلری دیگر قابل استفاده باشد:

using System;
using System.Web.Mvc;

namespace MvcApplication1.Controllers
{
public class HomeController : Controller
{
public ActionResult InsertData(string name)
{
// Check for input errors.
if (string.IsNullOrWhiteSpace(name))
{
TempData["error"] = "name is required.";
return RedirectToAction("ShowError");
}
// No errors
// ...
return View();
}

public ActionResult ShowError()
{
var error = TempData["error"] as string;
if (!string.IsNullOrWhiteSpace(error))
{
ViewBag.Error = error;
}
return View();
}
}
}

در همان HomeController دو متد جدید به نام‌های InsertData و ShowError اضافه شده‌اند. در متد InsertData ابتدا بررسی می‌شود که آیا نامی وارد شده است یا خیر. اگر خیر توسط متد RedirectToAction، کاربر به اکشن یا متد ShowError هدایت خواهد شد.
برای انتقال اطلاعات خطایی که می‌خواهیم در حین این Redirect نمایش دهیم نیز از TempData استفاده شده است.
بدیهی است برای اجرا این مثال نیاز است دو View جدید برای متدهای InsertData و ShowError ایجاد شوند (کلیک راست روی نام متد و انتخاب گزینه Add view برای اضافه کردن View مرتبط با آن اکشن).
محتوای View مرتبط با متد افزودن اطلاعات فعلا مهم نیست، ولی View نمایش خطاها در ساده‌ترین حالت مثلا می‌تواند به صورت زیر باشد:

@{
ViewBag.Title = "ShowError";
}

<h2>Error</h2>

@ViewBag.Error

برای آزمایش برنامه هم مطابق مسیریابی پیش فرض و با توجه به قرار داشتن در کنترلری به نام Home، مسیر http://localhost/Home/InsertData ابتدا باید بررسی شود. چون آرگومانی وارد نشده، بلافاصله صفحه به آدرس http://localhost/Home/ShowError به صورت خودکار هدایت خواهد شد.


نکته‌ای تکمیلی در مورد Strongly typed viewها:
عنوان شد که Strongly typed view روش مرجح بوده و بهتر است از آن استفاده شود، زیرا اطلاعات اشیاء و خواص تعریف شده در یک View تحت نظر کامپایلر قرار می‌گیرند که بسیار عالی است. یعنی اگر در View بنویسم FirstName: @Model.FirstName1 چون FirstName1 وجود خارجی ندارد، برنامه نباید کامپایل شود. یکبار این را بررسی کنید. برنامه بدون مشکل کامپایل می‌شود! اما تنها در زمان اجرا است که صفحه زرد رنگ معروف خطاهای ASP.NET ظاهر می‌شود که چنین خاصیتی وجود ندارد (این حالت پیش فرض است؛ یعنی کامپایل یک View‌ در زمان اجرا). البته این باز هم خیلی بهتر است از ViewBag، چون اگر مثلا ViewBag.Country1 را وارد کنیم، در زمان اجرا تنها چیزی نمایش داده نخواهد شد؛‌ اما با روش Strongly typed view، حتما خطای Compilation Error به همراه نمایش محل مشکل نهایی، در صفحه ظاهر خواهد شد.
سؤال: آیا می‌شود پیش از اجرای برنامه هم این بررسی را انجام داد؟
پاسخ: بله. باید فایل پروژه را اندکی ویرایش کرده و مقدار MvcBuildViews را که به صورت پیش فرض false هست، true نمود. یا خارج از ویژوال استودیو با یک ادیتور متنی ساده مثلا فایل csproj را گشوده و این تغییر را انجام دهید. یا داخل ویژوال استودیو، بر روی نام پروژه کلیک راست کرده و سپس گزینه Unload Project را انتخاب کنید. مجددا بر روی این پروژه Unload شده کلیک راست نموده و گزینه edit را انتخاب نمائید. در صفحه باز شده، MvcBuildViews را یافته و آن‌را true کنید. سپس پروژه را Reload کنید.
اکنون اگر پروژه را کامپایل کنید، پیغام خطای زیر پیش از اجرای برنامه قابل مشاهده خواهد بود:

'MvcApplication1.Models.Employee' does not contain a definition for 'FirstName1' 
and no extension method 'FirstName1' accepting a first argument of type 'MvcApplication1.Models.Employee'
could be found (are you missing a using directive or an assembly reference?)
d:\Prog\MvcApplication1\MvcApplication1\Views\Home\Index.cshtml 10 MvcApplication1

البته بدیهی است این تغییر، زمان Build پروژه را مقداری افزایش خواهد داد؛ اما امن‌ترین حالت ممکن برای جلوگیری از این نوع خطاهای تایپی است.
یا حداقل بهتر است یکبار پیش از ارائه نهایی برنامه این مورد فعال و بررسی شود.

و یک خبر خوب!
مجوز سورس کد ASP.NET MVC از MS-PL به Apache تغییر کرده و همچنین Razor و یک سری موارد دیگر هم سورس باز شده‌اند. این تغییرات به این معنا خواهند بود که پروژه از حالت فقط خواندنی MS-PL به حالت متداول یک پروژه سورس باز که شامل دریافت تغییرات و وصله‌ها از جامعه برنامه نویس‌ها است، تغییر کرده است (^ و ^).

نظرات مطالب
نحوه ایجاد Sequence و استفاده آن در Sql Server 2012
سلام
شما قادر نیستید یک فیلد Identity را بروز رسانی نمایید، دستور set insert_identity Tablename on  به شما اجازه Insert به جدول بدون Identity را می‌دهد، برای اینکه بتوانید Gap مرتبط به فیلد Identity را در جدول برطرف کنید، در ابتدا از جدول مورد نظر خود یک کپی تهیه و جدول اصلی را Truncate کنید، سپس یک Sequencer ایجاد و محتویات جدول کپی را بوسیله Sequencer در جدول اصلی کپی نمایید.
فرض کنیم جدول اصلی Table_3 باشد، ابتدا آن را کپی می‌کنیم در جدولی به نام T
Select * into T from table_3
سپس دستور Truncate را روی جدول Table_3 اجرا کنید:
truncate table dbo.table_3
حال یک Sequence ایجاد کنید:
CREATE SEQUENCE testEventCounter
    AS int
    START WITH 1
    INCREMENT BY 1 ;
در ادامه محتویات جدول کپی را به جدول اصلی منتقل نمایید:
SET IDENTITY_INSERT table_3 on
INSERT INTO table_3 (ID, Descritp)
SELECT 
      NEXT VALUE FOR testEventCounter AS id
    , Descritp
FROM T

راه دیگر این است که به جای استفاده از Identity از Sequence در فیلد خود استفاده نمایید، بصورت زیر :
CREATE TABLE Table3
(
    ID int PRIMARY KEY CLUSTERED 
        DEFAULT (NEXT VALUE FOR SequenceTest),
    De nvarchar(300) NULL
) ;
GO

در هنگام ایجاد جدول Sequence را به فیلد ID ست کردیم.
حال هر زمانی که بخواهید می‌توانید فیلد ID را مطابق Sequence خود بروز رسانی کنید:
Update table3 set id=(NEXT VALUE FOR testEventCounter )

موفق باشید و امیدوارم مفید واقع شده باشد
مطالب
نوشتن آزمون‌های واحد به کمک کتابخانه‌ی Moq - قسمت چهارم - بررسی تعامل بین سیستم در حال آزمایش و وابستگی‌های آن
علاوه بر امکان تنظیم مقدار خروجی متدها، مقدار خواص و ردیابی خواص تغییر کرده، یکی دیگر از قابلیت‌های کتابخانه‌ی Moq، بررسی مورد استفاده قرار گرفتن خواص و متدهای اشیاء Mock شده‌است، که عموما به آن Behavior based testing هم می‌گویند.


Behavior Based Testing چیست؟

آزمون‌هایی را که تاکنون بررسی کردیم از نوع state based testing بودند. در این حالت ابتدا یک Mock object را ایجاد و سپس وهله‌ای از سرویس مدنظر را توسط آن تهیه می‌کنیم. در ادامه تعدادی از متدهای این سرویس را مانند متد Process کلاس LoanApplicationProcessor، فراخوانی می‌کنیم. اینکار سبب اجرای فعالیتی در این سیستم شده و به همراه آن تعاملی با اشیاء Mock شده نیز صورت می‌گیرد. در نهایت، حالت و یا نتیجه‌ای را دریافت می‌کنیم و آن‌را با حالت یا نتیجه‌ای که انتظار داریم، مقایسه خواهیم کرد. بنابراین در این روش پس از پایان اجرای سیستم در حال اجرا، حالت و نتیجه‌ی نهایی حاصل از عملکرد آن، مورد بررسی قرار می‌گیرد.
در Behavior based testing نیز در ابتدا Mock objects مورد نیاز تهیه می‌شوند و سپس وهله‌ای از سرویس مدنظر را توسط آن‌ها تهیه می‌کنیم. همانند قبل، سیستم در حال بررسی را اجرا می‌کنیم (برای مثال با فراخوانی متدی در یک سرویس) تا سیستم، با اشیاء Mock شده کار کند. در این حالت دسترسی به متدی و یا خاصیتی بر روی Mock object صورت می‌گیرد. اکنون همانند روش state based testing که نتیجه‌ی عملیات را مورد بررسی قرار می‌دهد، در اینجا بررسی می‌کنیم که آیا خاصیت یا متد خاصی در Mock objectهای تنظیم شده، استفاده شده‌اند یا خیر؟ بنابراین هدف از این نوع آزمایش، بررسی تعامل بین یک سیستم و وابستگی‌های آن است.
برای مثال فرض کنید که می‌خواهیم کلاس ProductCache را بررسی و آزمایش کنیم. این کلاس از یک DB Provider واقعی برای دسترسی به اطلاعات استفاده می‌کند. برای مثال اگر محصول شماره‌ی 42 را از آن درخواست دهیم، اگر این محصول در کش موجود نباشد، ابتدا یک کوئری را به بانک اطلاعاتی صادر کرده و مقدار متناظری را دریافت می‌کند. سپس نتیجه را کش کرده و به فراخوان بازگشت می‌دهد. در اینجا می‌توان بررسی کرد که آیا محصول صحیحی از کش دریافت شده‌است یا خیر؟ (یا همان state based testing). اما اگر بخواهیم منطق کش کردن را بررسی کنیم، چطور می‌توان متوجه شد که برای مثال محصول دریافت شده مستقیما از کش دریافت شده و یا خیر از همان ابتدا از بانک اطلاعاتی واکشی شده، کش شده و سپس بازگشت داده شده‌است؟ برای این منظور می‌توان توسط کتابخانه‌ی Moq، یک نمونه‌ی mock شده‌ی DB Provider را تهیه و سپس از آن به عنوان وابستگی شیء Product Cache استفاده کرد. اکنون زمانیکه اطلاعاتی از Product Cache درخواست می‌شود، می‌توان Mock object تهیه شده را طوری تنظیم کرد تا اطلاعات مدنظر ما را بازگشت دهد. در این بین مزیت کار کردن با یک Mock object، امکان بررسی این است که آیا متدی بر روی آن فراخوانی شده‌است یا خیر؟ به این ترتیب می‌توان تعامل و رفتار Product Cache را با وابستگی آن، تحت نظر قرار داد (Behavior based testing).


بررسی فراخوانی شدن یک متد بدون پارامتر بر روی یک Mock object

در مثال این سری و در کلاس LoanApplicationProcessor و متد Process آن، فراخوانی سطر زیر را مشاهده می‌کنید:
_identityVerifier.Initialize();
اکنون می‌خواهیم آزمایشی را بنویسیم تا نشان دهد متد Initialize فوق، در صورت فراخوانی متد Process کلاس LoanApplicationProcessor، حتما فراخوانی شده‌است:
namespace Loans.Tests
{
    [TestClass]
    public class LoanApplicationProcessorShould
    {
        [TestMethod]
        public void InitializeIdentityVerifier()
        {
            var product = new LoanProduct {Id = 99, ProductName = "Loan", InterestRate = 5.25m};
            var amount = new LoanAmount {CurrencyCode = "Rial", Principal = 2_000_000_0};
            var applicant =
                new Applicant {Id = 1, Name = "User 1", Age = 25, Address = "This place", Salary = 1_500_000_0};
            var application = new LoanApplication {Id = 42, Product = product, Amount = amount, Applicant = applicant};

            var mockIdentityVerifier = new Mock<IIdentityVerifier>();
            mockIdentityVerifier.Setup(x => x.Validate(applicant.Name, applicant.Age, applicant.Address))
                .Returns(true);

            var mockCreditScorer = new Mock<ICreditScorer>();
            mockCreditScorer.Setup(x => x.ScoreResult.ScoreValue.Score).Returns(110_000);

            var processor = new LoanApplicationProcessor(mockIdentityVerifier.Object, mockCreditScorer.Object);
            processor.Process(application);

            mockIdentityVerifier.Verify(x => x.Initialize());
        }
    }
}
تنظیم mockIdentityVerifier.Setup را در قسمت دوم این سری «تنظیم مقادیر بازگشتی متدها» بررسی کردیم.
تنظیم mockCreditScorer.Setup را نیز در قسمت سوم این سری «تنظیم مقادیر خواص اشیاء» بررسی کردیم.

در ادامه، متد Process کلاس LoanApplicationProcessor فراخوانی شده‌است. اکنون با استفاده از متد Verify کتابخانه‌ی Moq، می‌توان بررسی کرد که آیا در سیستم در حال آزمایش، متدی که توسط آن به صورت strongly typed مشخص می‌شود، فراخوانی شده‌است یا خیر؟

پس از این تنظیمات اگر متد آزمایش واحد InitializeIdentityVerifier را بررسی کنیم با موفقیت به پایان خواهد رسید. برای نمونه یکبار هم سطر فراخوانی متد Initialize را کامنت کنید و سپس این آزمایش را اجرا نمائید تا بتوان شکست آن‌را نیز مشاهده کرد.


بررسی فراخوانی شدن یک متد پارامتر دار بر روی یک Mock object

همان متد آزمون واحد InitializeIdentityVerifier را درنظر بگیرید، در انتهای آن یک سطر زیر را نیز اضافه می‌کنیم:
mockCreditScorer.Verify(x => x.CalculateScore(applicant.Name, applicant.Address));
به این ترتیب می‌توان دقیقا بررسی کرد که آیا در حین پردازش LoanApplicationProcessor، متد CalculateScore وابستگی creditScorer آن، با پارامترهایی که در آزمون فوق مشخص شده، فراخوانی شده‌است یا خیر؟
بدیهی است اگر در این بین، متد CalculateScore با هر مقدار دیگری در کلاس LoanApplicationProcessor فراخوانی شود، آزمون فوق با شکست مواجه خواهد شد. اگر در اینجا مقدار پارامترها اهمیتی نداشتند، همانند قسمت دوم می‌توان از ()<It.IsAny<string استفاده کرد.


بررسی تعداد بار فراخوانی یک متد بر روی یک Mock object

برای بررسی تعداد بار فراخوانی یک متد بر روی یک شیء Mock شده، می‌توان از پارامتر دوم متد Verify استفاده کرد:
mockCreditScorer.Verify(x => 
        x.CalculateScore(It.IsAny<string>(), applicant.Address), 
        Times.Once);
ساختار Times، دارای متدهایی مانند AtLeast ،AtMost ،Exactly و امثال آن است که انعطاف پذیری بیشتری را به آن می‌دهند.


بررسی فراخوانی Getter و Setter خواص یک شیء Mock شده

علاوه بر امکان دریافتن وقوع فراخوانی یک متد، می‌توان از خوانده شدن و یا تغییر مقدار یک خاصیت نیز توسط کتابخانه‌ی Moq مطلع شد. برای مثال در قسمتی از کدهای متد Process داریم:
if (_creditScorer.ScoreResult.ScoreValue.Score < MinimumCreditScore)
اکنون می‌خواهیم بررسی کنیم که آیا Getter خاصیت Score فراخوانی شده‌است یا خیر؟
mockCreditScorer.VerifyGet(x => x.ScoreResult.ScoreValue.Score, Times.Once);
در اینجا بجای استفاده از متد Verify از متد VerifyGet برای بررسی وقوع خوانده شدن مقدار یک خاصیت می‌توان استفاده کرد.
جهت بررسی تغییر مقدار یک متغیر بر روی یک شیء Mock شده، می‌توان از متد VerifySet کمک گرفت:
mockCreditScorer.VerifySet(x => x.Count = It.IsAny<int>(), Times.Once);
به این ترتیب می‌توان دقیقا مقداری را که انتظار داریم مشخص کنیم و یا می‌توان هر مقداری را نیز توسط کلاس It، پذیرفت. البته در این مورد روش زیر برای بررسی تغییر مقدار یک خاصیت که در قسمت قبل بررسی شد، شاید روش بهتر و متداول‌تری باشد:
mockCreditScorer.SetupProperty(x => x.Count, 10);
Assert.AreEqual(11, mockCreditScorer.Object.Count);


روش بررسی فراخوانی تمام متدها و تمام خواص یک شیء Mock شده

با استفاده از متد زیر می‌توان از «نوشتن شده بودن» آزمایش مورد استفاده قرار گرفتن تمام متدها و خواص یک شیء Mock شده، مطمئن شد:
mockIdentityVerifier.VerifyNoOtherCalls();
اگر برای مثال این سطر را به انتهای متد InitializeIdentityVerifier اضافه کنیم، با شکست مواجه می‌شود و در پیام استثنای آن دقیقا عنوان می‌کند که چه مواردی هنوز فاقد آزمون واحد هستند و باید اضافه شوند:
 mockIdentityVerifier.Verify(x => x.Validate(It.IsAny<string>(),
                                                        It.IsAny<int>(),
                                                        It.IsAny<string>()));

کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید: MoqSeries-4.zip
نظرات مطالب
پلاگین DataTables کتابخانه jQuery - قسمت سوم
سلام
رندر کردن جدول حاوی داده‌ها باید به data tables سپرده بشه. بدین صورت که داده‌های دریافتی از سرور به فرمت مناسبی تبدیل بشن و بعد به خصوصیت aaData نسبت داده بشن، البته به تبع اون و حتما باید خصوصیت aoColumns هم مقدار دهی بشه.
$(document).ready(function () {
      $.ajax({
          url: "ِDefault.aspx/GetBrowsers",
          contentType: "application/json; charset=utf-8",
          dataType: "json",
          type: "POST",
          success: function (response) {
          if (response != "") {
                    var data = eval("(" + response.d + ")");                        
                    $('#browsers-grid').dataTable({
                            "aaData": data,
                            "bProcessing" : true,
                            "aoColumns": [
                                { "mData": "Engine" },
                                { "mData": "Name" },
                                { "mData": "Platform" },
                                { "mData": "Version", "sClass": "center" },
                                { "mData": "Grade", "sClass": "center" }
                            ]
                  });
              }
          },
      });
});

کدهای سمت سرور:
مثلا فرض کنید ذر سمت سرور بخواهید لیستی از مرورگرها رو برگشت بدین. کلاس زیر رو در نظر بگیرید:
public class Browser
{
    public int Id { get; set; }
    public string Engine { get; set; }
    public string Name { get; set; }
    public string Platform { get; set; }
    public float Version { get; set; }
    public string Grade { get; set; }
}

برای برگشت دادن لیستی از مرورگر‌ها به طرف کلاینت، متدی مثل زیر خواهید داشت:
[WebMethod]
public static string GetBrowsers()
{
    List<Browser> browsers = new List<Browser>()
        {
            new Browser
                {
                    Id = 1,
                    Engine = "Trident", 
                    Name = "Internet Explorer 4.0", 
                    Platform = "Win95+", 
                    Version = 4,
                    Grade = "X"
                },
            new Browser
                {
                    Id = 2,
                    Engine = "Trident", 
                    Name = "Internet Explorer 5.0", 
                    Platform = "Win95+", 
                    Version = 5,
                    Grade = "C"
                },               
        };
    return browsers.ToJson();
}

در متد بالا، لیستی از مرورگرها با استفاده از یک متد الحاقی تبدیل به فرمت json میشه و به طرف کاربر فرستاده میشه. 
نظرات مطالب
اعتبارسنجی مبتنی بر کوکی‌ها در ASP.NET Core 2.0 بدون استفاده از سیستم Identity
مشکلی در پیاده سازی BlazorServerCookieAuthentication  پیدا کرده ام.
برای اینکه بتوانم در سرویسی از برنامه که اطلاعات کاربری لاگ می‌شود به آی دی کاربر دسترسی داشته باشم از کد زیر استفاده کردم که قبلا در SO راهنمایی کرده بودید بنده را. 
using System.Security.Claims;
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.AspNetCore.Components.Server;

namespace BlazorServerTestDynamicAccess.Services;

public class CustomAuthenticationStateProvider : RevalidatingServerAuthenticationStateProvider
{
    private readonly IServiceScopeFactory _scopeFactory;

    public CustomAuthenticationStateProvider(ILoggerFactory loggerFactory, IServiceScopeFactory scopeFactory)
        : base(loggerFactory) =>
        _scopeFactory = scopeFactory ?? throw new ArgumentNullException(nameof(scopeFactory));

    protected override TimeSpan RevalidationInterval { get; } = TimeSpan.FromMinutes(30);

    protected override async Task<bool> ValidateAuthenticationStateAsync(
        AuthenticationState authenticationState, CancellationToken cancellationToken)
    {
        // Get the user from a new scope to ensure it fetches fresh data
        var scope = _scopeFactory.CreateScope();
        try
        {
            var userManager = scope.ServiceProvider.GetRequiredService<IUsersService>();
            return await ValidateUserAsync(userManager, authenticationState?.User);
        }
        finally
        {
            if (scope is IAsyncDisposable asyncDisposable)
            {
                await asyncDisposable.DisposeAsync();
            }
            else
            {
                scope.Dispose();
            }
        }
    }

    private async Task<bool> ValidateUserAsync(IUsersService userManager, ClaimsPrincipal? principal)
    {
        if (principal is null)
        {
            return false;
        }

        var userIdString = principal.FindFirst(ClaimTypes.UserData)?.Value;
        if (!int.TryParse(userIdString, out var userId))
        {
            return false;
        }

        var user = await userManager.FindUserAsync(userId);
        return user is not null;
    }
}
حال مشکل اینجاست که اگر CustomAuthenticationStateProvider را به صورت AddSingleton ثبت کنم با این مشکل روبرو می‌شوم. اگر هم که به صورت AddScoped ثبت کنم با مشکل دیگری روبرو می‌شوم. اگر ممکن است لطفا راهنمایی فرمایید.
مطالب
کار با نوع داده‌ی HierarchyID توسط Entity framework
نوع داده‌ی HierarchyID به همراه SQL Server 2008 برای کار با داده‌هایی با ساختار درختی ارائه شد. در حال حاضر هیچکدام از ORMهای موجود، پشتیبانی رسمی را از این نوع داده به عمل نمی‌آورند؛ اما با توجه به سورس باز بودن Entity framework، یک Fork مستقل از آن تهیه شده‌است و این نوع داده‌ی جدید به همراه متدهای مرتبط با آن، به این Fork اضافه شده‌اند.
- اصل Fork در اینجا
- تاریخچه‌ی این Fork غیر رسمی در اینجا
- بسته‌ی نیوگت آن در اینجا

چون تیم EF در نگارش فعلی این کتابخانه حاضر به افزودن این نوع جدید نشده‌است، بنابراین بجای بسته‌ی اصلی Entity framework نیاز است بسته‌ی EntityFrameworkWithHierarchyId را نصب کنید.
 PM> install-package EntityFrameworkWithHierarchyId

یک تذکر مهم:
چون امضای دیجیتال این بسته، با امضای دیجیتال بسته‌ی اصلی EF یکی نیست، اگر پروژه‌ی شما صرفا از EF استفاده می‌کند، مشکلی نخواهید داشت. اما اگر برای مثال از ASP.NET Identity کامپایل شده‌ی برای کار با EF اصلی استفاده کنید، پیام یافت نشدن DLL مرتبط را دریافت خواهید کرد.


تعریفی مدلی با خاصیتی از نوع جدید HierarchyId

public class Employee
{
    public int Id { get; set; }
 
    [Required, MaxLength(100)]
    public string Name { get; set; }
 
    [Required]
    public HierarchyId Node { get; set; } // نوع داده جدید
}
در اینجا مدلی را ملاحظه می‌کنید که از نوع داده‌ی جدید HierarchyId استفاده می‌کند. همانطور که عنوان شد این نوع در بسته‌ی EntityFrameworkWithHierarchyId موجود است.


تعریف Context و مقدار دهی اولیه‌ی آن

در این حالت Context برنامه به همراه تنظیمات اولیه‌ی Migrations آن یک چنین شکلی را پیدا خواهد کرد:
public class MyContext : DbContext
{
    public DbSet<Employee> Employees { get; set; }
 
    public MyContext()
        : base("Connection1")
    {
        this.Database.Log = log => Console.WriteLine(log);
    }
}
 
public class Configuration : DbMigrationsConfiguration<MyContext>
{
    public Configuration()
    {
        AutomaticMigrationsEnabled = true;
        AutomaticMigrationDataLossAllowed = true;
    }
 
    protected override void Seed(MyContext context)
    {
        if (context.Employees.Any())
            return;
 
        context.Database.ExecuteSqlCommand(
            "ALTER TABLE [dbo].[Employees] ADD NodePath as Node.ToString() persisted");
        context.Database.ExecuteSqlCommand(
            "ALTER TABLE [dbo].[Employees] ADD Level AS Node.GetLevel() persisted");
        context.Database.ExecuteSqlCommand(
            "ALTER TABLE [dbo].[Employees] ADD ManagerNode as Node.GetAncestor(1) persisted");
        context.Database.ExecuteSqlCommand(
            "ALTER TABLE [dbo].[Employees] ADD ManagerNodePath as Node.GetAncestor(1).ToString() persisted");
 
        context.Database.ExecuteSqlCommand(
            "ALTER TABLE [dbo].[Employees] ADD CONSTRAINT [UK_EmployeeNode] UNIQUE NONCLUSTERED (Node)");
        context.Database.ExecuteSqlCommand(
            "ALTER TABLE [dbo].[Employees]  WITH CHECK ADD CONSTRAINT [EmployeeManagerNodeNodeFK] " +
            "FOREIGN KEY([ManagerNode]) REFERENCES [dbo].[Employees] ([Node])");
 
        context.Employees.Add(new Employee { Name = "Root", Node = new HierarchyId("/") });
        context.Employees.Add(new Employee { Name = "Emp1", Node = new HierarchyId("/1/") });
        context.Employees.Add(new Employee { Name = "Emp2", Node = new HierarchyId("/2/") });
        context.Employees.Add(new Employee { Name = "Emp3", Node = new HierarchyId("/1/1/") });
        context.Employees.Add(new Employee { Name = "Emp4", Node = new HierarchyId("/1/1/1/") });
        context.Employees.Add(new Employee { Name = "Emp5", Node = new HierarchyId("/2/1/") });
        context.Employees.Add(new Employee { Name = "Emp6", Node = new HierarchyId("/1/2/") });
 
        base.Seed(context);
    }
}
در اینجا نحوه‌ی تعریف رکوردهای جدید مبتنی بر HierarchyId را مشاهده می‌کنید که توسط آن‌ها تعدادی کارمند، در یک سازمان فرضی ثبت شده‌اند.
همچنین چند فیلد محاسباتی نیز بر اساس امکانات توکار SQL Server اضافه شده‌اند. متدهایی مانند ToString، GetLevel، GetAncestor و امثال آن جزئی از پیاده سازی توکار SQL Server هستند. همچنین این متدها توسط کتابخانه‌ی EntityFrameworkWithHierarchyId نیز ارائه شده‌اند.


کوئری نویسی

مرتب سازی رکوردها بر اساس HierarchyId آن‌ها

using (var context = new MyContext())
{
    Console.WriteLine("\ngetItems OrderByDescending(employee => employee.Node)");
 
    var employees = context.Employees.OrderByDescending(employee => employee.Node).ToList();
    foreach (var employee in employees)
    {
        Console.WriteLine("{0} {1}", employee.Id, employee.Node);
    }
 }
با این خروجی
SELECT
    [Extent1].[Id] AS [Id],
    [Extent1].[Name] AS [Name],
    [Extent1].[Node] AS [Node]
    FROM [dbo].[Employees] AS [Extent1]
    ORDER BY [Extent1].[Node] DESC


6 /2/1/
3 /2/
7 /1/2/
5 /1/1/1/
4 /1/1/
2 /1/
1 /


یافتن یک HierarchyId خاص و سپس یافتن کلیه‌ی فرزندان آن در یک سطح پایین‌تر

using (var context = new MyContext())
{
    Console.WriteLine("\nGetAncestor(1) of /1/");
 
    var firstItem = context.Employees.Single(employee => employee.Node == new HierarchyId("/1/"));
    foreach (var item in context.Employees.Where(employee => firstItem.Node == employee.Node.GetAncestor(1)))
    {
        Console.WriteLine("{0} {1}", item.Id, item.Name);
    }
}
این کوئری را به این شکل نیز می‌توان عنوان کرد: یافتن یک HierarchyId و سپس یافتن کلیه نودهایی که والدشان (GetAncestor) این HierarchyId است. عدد یک در اینجا مشخص کننده‌ی Level یا سطح است.
با این خروجی:
SELECT TOP (2)
    [Extent1].[Id] AS [Id],
    [Extent1].[Name] AS [Name],
    [Extent1].[Node] AS [Node]
    FROM [dbo].[Employees] AS [Extent1]
    WHERE cast('/1/' as hierarchyid) = [Extent1].[Node]

SELECT
    [Extent1].[Id] AS [Id],
    [Extent1].[Name] AS [Name],
    [Extent1].[Node] AS [Node]
    FROM [dbo].[Employees] AS [Extent1]
    WHERE (@p__linq__0 = ([Extent1].[Node].GetAncestor(1))) OR ((@p__linq__0 IS
NULL) AND ([Extent1].[Node].GetAncestor(1) IS NULL))
-- p__linq__0: '/1/' (Type = Object)

4 Emp3
7 Emp6

کوئری‌های فوق را می‌توان بجای استفاده از متد GetAncestor، با استفاده از متد IsDescendantOf به شکل زیر نیز نوشت:
var list = context.Employees.Where(
          employee => employee.Node.IsDescendantOf(new HierarchyId("/1/")) &&
                              employee.Node.GetLevel() == 2).ToList();
با این خروجی SQL (یک کوئری بجای دو کوئری):
SELECT
    [Extent1].[Id] AS [Id],
    [Extent1].[Name] AS [Name],
    [Extent1].[Node] AS [Node]
    FROM [dbo].[Employees] AS [Extent1]
    WHERE (([Extent1].[Node].IsDescendantOf(cast('/1/' as hierarchyid))) = 1) 
    AND (2 = ([Extent1].[Node].GetLevel()))


جابجا کردن نودها توسط متد GetReparentedValue

در کوئری ذیل، تمامی فرزندان ریشه‌ی /1/ یافت شده و سپس والد آن‌ها به صورت پویا تغییر داده می‌شود:
var items = context.Employees.Where(employee => employee.Node.IsDescendantOf(new HierarchyId("/1/")))
    .Select(employee => new
    {
        Id = employee.Id,
        OrigPath = employee.Node,
        ReparentedValue = employee.Node.GetReparentedValue(new HierarchyId("/1/"), HierarchyId.GetRoot()),
        Level = employee.Node.GetLevel()
    }).ToList();
 
foreach (var item in items)
{
    Console.WriteLine("Id:{0}; OrigPath:{1}; ReparentedValue:{2}; Level:{3}", item.Id, item.OrigPath, item.ReparentedValue, item.Level);
}
با این خروجی
SELECT
    [Extent1].[Id] AS [Id],
    [Extent1].[Node] AS [Node],
    [Extent1].[Node].GetReparentedValue(cast('/1/' as hierarchyid), hierarchyid::GetRoot()) AS [C1],
    [Extent1].[Node].GetLevel() AS [C2]
    FROM [dbo].[Employees] AS [Extent1]
    WHERE ([Extent1].[Node].IsDescendantOf(cast('/1/' as hierarchyid))) = 1


Id:2; OrigPath:/1/; ReparentedValue:/; Level:1
Id:4; OrigPath:/1/1/; ReparentedValue:/1/; Level:2
Id:5; OrigPath:/1/1/1/; ReparentedValue:/1/1/; Level:3
Id:7; OrigPath:/1/2/; ReparentedValue:/2/; Level:2

کدهای کامل این مثال را از اینجا می‌توانید دریافت کنید
HierarcyIdTests.zip
مطالب
استفاده از LINQ to XML جهت خواندن فیدهای RSS

مثال زیر را به عنوانی نمونه‌ای از کاربرد LINQ to XML برای خواندن فیدهای RSS که اساسا به فرمت XML هستند می‌توان ارائه داد.
ابتدا کد کامل مثال را در نظر بگیرید:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;

namespace LinqToRSS
{
public static class LanguageExtender
{
public static string SafeValue(this XElement input)
{
return (input == null) ? string.Empty : input.Value;
}

public static DateTime SafeDateValue(this XElement input)
{
return (input == null) ? DateTime.MinValue : DateTime.Parse(input.Value);
}
}

public class RssEntry
{
public string Title { set; get; }
public string Description { set; get; }
public string Link { set; get; }
public DateTime PublicationDate { set; get; }
public string Author { set; get; }
public string BlogName { set; get; }
public string BlogAddress { set; get; }
}

public class Rss
{
static XElement selectDate(XElement date1, XElement date2)
{
return date1 ?? date2;
}

public static List<RssEntry> GetEntries(string feedUrl)
{
//applying namespace in an XElement
XName xn = XName.Get("{http://purl.org/dc/elements/1.1/}creator");//{namespace}root
XName xn2 = XName.Get("{http://purl.org/dc/elements/1.1/}date");

var feed = XDocument.Load(feedUrl);
if (feed.Root == null) return null;

var items = feed.Root.Element("channel").Elements("item");
var feedQuery =
from item in items
select new RssEntry
{
Title = item.Element("title").SafeValue(),
Description = item.Element("description").SafeValue(),
Link = item.Element("link").SafeValue(),
PublicationDate =
selectDate(item.Element(xn2), item.Element("pubDate")).SafeDateValue(),
Author = item.Element(xn).SafeValue(),
BlogName = item.Parent.Element("title").SafeValue(),
BlogAddress = item.Parent.Element("link").SafeValue()
};

return feedQuery.ToList();
}
}

class Program
{
static void Main(string[] args)
{
List<RssEntry> entries = Rss.GetEntries("http://weblogs.asp.net/aspnet-team/rss.aspx");
if (entries != null)
foreach (var item in entries)
Console.WriteLine(item.Title);

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

توضیحات:
1- در این مثال فقط جهت سهولت بیان آن در یک صفحه، تمامی کلاس‌های تعریف شده در یک فایل آورده شدند. این روش صحیح نیست و باید به ازای هر کلاس یک فایل جدا در نظر گرفته شود.
2- کلاس LanguageExtender از قابلیت extension methods سی شارپ 3 استفاده می‌کند. به این صورت کلاس XElement دات نت بسط یافته و دو متد به آن اضافه می‌شود که به سادگی در کدهای خود می‌توان از آن‌ها استفاده کرد. هدف آن هم بررسی نال بودن یک آیتم دریافتی و ارائه‌ی حاصلی امن برای این مورد است.
3- کلاس RssEntry به جهت استفاده در خروجی کوئری LINQ تعریف شد. می‌خواهیم خروجی نهایی، یک لیست جنریک از نوع RssEntry باشد.
4- متد اصلی برنامه، GetEntries است. این متد آدرس اینترنتی یک فید را دریافت کرده و پس از آنالیز، آن‌را به صورت یک لیست بر می‌گرداند.

<?xml version="1.0" encoding="UTF-8" ?>
<?xml-stylesheet type="text/xsl" href="http://weblogs.asp.net/utility/FeedStylesheets/rss.xsl" media="screen"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/" xmlns:wfw="http://wellformedweb.org/CommentAPI/">
<channel>
<title>Latest Microsoft Blogs</title>
<link>http://weblogs.asp.net/aspnet-team/default.aspx</link>
<description />
<dc:language>en</dc:language>
<generator>CommunityServer 2007 SP1 (Build: 20510.895)</generator>
<item>
<title>Comments on my recent benchmarks.</title>
<link>http://misfitgeek.com/blog/aspnet/comments-on-my-recent-benchmarks/</link>
<pubDate>Mon, 10 Aug 2009 23:33:59 GMT</pubDate>
<guid isPermaLink="false">c06e2b9d-981a-45b4-a55f-ab0d8bbfdc1c:7166225</guid>
<dc:creator>Misfit Geek: msft</dc:creator>
<slash:comments>0</slash:comments>
<wfw:commentRss xmlns:wfw="http://wellformedweb.org/CommentAPI/">http://weblogs.asp.net/aspnet-team/rsscomments.aspx?PostID=7166225</wfw:commentRss>
<comments>http://misfitgeek.com/blog/aspnet/comments-on-my-recent-benchmarks/#comments</comments>
<description>Overall I’ve been pretty impressed ...</description>
<category domain="http://weblogs.asp.net/aspnet-team/archive/tags/ASP.NET/default.aspx">ASP.NET</category>
</item>
</channel>
</rss>
برای نمونه خروجی یک فید می‌تواند به صورت فوق باشد. آیتم‌های آن به صورت قابل بیان است:
var items = feed.Root.Element("channel").Elements("item");
و نکته مهمی که اینجا وجود دارد، اعمال فضاهای نام بکار رفته در این فایل xml پیشرفته می‌باشد. برای اعمال فضاهای نام به یکی از دو روش زیر می‌توان عمل کرد:

XName.Get("{mynamespace}root");
//or
XName.Get("root", "mynamespace");

نظرات مطالب
استفاده از افزونه Typeahead مجموعه Twitter Bootstrap در ASP.NET MVC
matcher یک callback جاوا اسکریپتی است. بنابراین در سمت کلاینت باید پیاده سازی شود (چیزی شبیه به مقدار دهی source پویای مثال آخر بحث).
$('.typeahead').typeahead({
    matcher: function(item) {
        // آیتم مقداری است که باید برای تطابق بررسی شود
        //  this.query کوئری جاری را بر می‌گرداند.
        return true // اگر آیتم تطابق داشته است
    }
})
مثلا:
matcher: function (item) {
    if (item.toLowerCase().indexOf(this.query.trim().toLowerCase()) != -1) {
        return true;
    }
}
از matcher برای سفارشی سازی و بررسی اینکه آیا عبارت تایپ شده توسط کاربر با list دریافتی تطابق دارد یا خیر، استفاده می‌شود. حالت پیش فرض، تطابق دقیق لیست دریافتی با متن ورودی کاربر است. با استفاده از matcher در سمت کلاینت می‌توانید نحوه نمایش لیست دریافتی از سرور را سفارشی سازی کنید.