مطالب
امکان تعریف قالب‌ها در Angular با دایرکتیو ng-template
معرفی دایرکتیو ng-template

همانطور که از اسم آن نیز مشخص است، ng-template به معنای قالب انگیولار است و هدف از آن، ارائه‌ی قسمتی از قالب نهایی یک کامپوننت می‌باشد. فریم ورک Angular از دایرکتیو ng-template در پشت صحنه‌ی دایرکتیوهای ساختاری مانند ngIf، ngFor و ngSwitch استفاده می‌کند. برای مثال، قسمت if‌، تبدیل به یک ng-template شده و else آن نیز تبدیل به یک ng-template ضمنی دیگر خواهد شد.


روش فعالسازی و نمایش قالب‌ها

باید دقت داشت که تعریف یک ng-template سبب رندر هیچگونه خروجی در صفحه نمی‌شود و باید به طریقی درخواست فعالسازی و رندر آن‌را ارائه داد.
<div class="lessons-list" *ngIf="lessons else loading">
... 
</div>

<ng-template #loading>
     <div>Loading...</div>
</ng-template>
یکی از روش‌های معمول نمایش قالب‌ها، استفاده از ngIf/else است. در این مثال اگر آرایه‌ی فرضی دروس دارای عضوی باشد، div مرتبط نمایش داده می‌شود؛ در غیراینصورت، قالبی که توسط یک template reference variable به نام loading مشخص شده‌است، نمایش داده خواهد شد (loading‌# در اینجا).
هرچند در پشت صحنه برای حالت ngIf نیز یک ng-template ضمنی محصور کننده‌ی div اصلی تشکیل می‌شود که از دید ما پنهان است.

استفاده از ngIf برای نمایش یک قالب، یکی از روش‌های کار با آن‌ها است. روش دیگر، استفاده از ng-container است:
<ng-container *ngTemplateOutlet="loading"></ng-container>
در اینجا دایرکتیو ساختاری ngTemplateOutlet، قالبی را که توسط loading# مشخص شده‌است، وهله سازی کرده و به درون ng-container تزریق می‌کند که در این حالت سبب نمایش آن نیز خواهد شد.


سطوح دسترسی در قالب‌ها

اکنون این سؤال مطرح است: «آیا یک قالب میدان دید متغیرهای خاص خودش را دارد؟ این قالب به چه متغیرهایی دسترسی دارد؟»
درون بدنه یک تگ ng-template، به همان متغیرهایی که در قالب خارجی آن قابل دسترسی هستند، دسترسی خواهیم داشت؛ برای نمونه در مثال فوق به همان متغیر lessons. به عبارتی تمام وهله‌های ng-templateها، به همان متغیرهای زمینه‌ی قالبی که درون آن جای‌گرفته‌اند، دسترسی دارند. به علاوه هر قالب می‌تواند متغیرهای خاص خود را نیز تعریف کند.
در ادامه قالب یک کامپوننت را به صورت ذیل فرض کنید:
<ng-template #estimateTemplate let-lessonsCounter="estimate">
     <div> Approximately {{lessonsCounter}} lessons ...</div>
</ng-template>
<ng-container *ngTemplateOutlet="estimateTemplate;context:ctx">
</ng-container>
با کدهای ذیل
export class AppComponent {
     totalEstimate = 10;
     ctx = {estimate: this.totalEstimate};
}
در اینجا قالب تعریف شده، توسط پیشوند -let دارای یک متغیر ورودی به نام lessonsCounter شده‌است (می‌تواند چندین متغیر ورودی داشته باشد). شکل کلی آن به صورت "let-{{templateVariableName}}=”contextProperty است.
 این متغیر lessonsCounter تنها داخل این قالب است که قابل مشاهده و دسترسی می‌باشد و نه خارج از آن. مقدار این متغیر نیز توسط عبارت estimate تامین می‌شود. این عبارت زمانیکه ng-container  سبب وهله سازی estimateTemplate می‌شود، توسط شیء ویژه‌ای به نام context مقدار دهی خواهد شد.
برای اینکه عبارت estimate در قالب، قابل استخراج از شیء context باشد، باین دقیقا خاصیتی به همین نام در این شیء تعریف شده باشد (و برای سایر متغیرها نیز به همین ترتیب). به همین جهت است که خاصیت عمومی ctx در کلاس AppComponent به صورت یک شیء دارای خاصیت estimate تعریف شده‌است تا بتوان نگاشتی را بین این مقدار و عبارت estimate برقرار کرد.

نکته 1: اگر در اینجا متغیری تعریف شود، اما محل تامین آن مشخص نگردد، به دنبال خاصیتی به نام implicit$ خواهد گشت. برای مثال در قالب ذیل، متغیر default تعریف شده‌است؛ اما عبارت تامین کننده‌ی آن مشخص نیست:
    <ng-container *ngTemplateOutlet="templateRef; context: exampleContext"></ng-container>
    <ng-template #templateRef let-default>
      <div>
        '{{default}}'
      </div>
    </ng-template>
در این حالت مقدار default از خاصیت implicit$ شیء منتسب به context دریافت می‌شود:
export class AppComponent {
  exampleContext = {
    $implicit: 'default context property when none specified'
  };
}

نکته 2:
نحوه‌ی تعریف شیء context را به صورت ذیل نیز می‌توان مشخص کرد:
 [ngOutletContext]="exampleContext"


دسترسی به قالب‌ها در کدهای کامپوننت‌ها

در اینجا قالبی را مشاهده می‌کنید که توسط یک template reference variable به نام defaultTabButtons مشخص شده‌است:
<ng-template #defaultTabButtons>
</ng-template>
برای دسترسی به آن در کدهای کامپوننت مرتبط، می‌توان از طریق تعریف یک ViewChild هم نام با این متغیر استفاده کرد:
export class AppComponent implements OnInit {
   @ViewChild('defaultTabButtons') private defaultTabButtonsTpl: TemplateRef<any>;
   ngOnInit() {
      console.log(this.defaultTabButtonsTpl);
   }
}
در اینجا متغیر defaultTabButtonsTpl با ویژگی ViewChild مزین شده‌است. البته این یک روش عمومی برای دسترسی به تمام عناصر DOM در کدهای یک کامپوننت می‌باشد.

یکی از کاربردهای این قابلیت، امکان تعویض پویای قالب‌های یک دربرگیرنده‌است:
<ng-container *ngTemplateOutlet="headerTemplate ? headerTemplate: defaultTabButtons">
</ng-container>
توسط دایرکتیو ساختاری ngTemplateOutlet می‌توان در زمان اجرا، قالب‌های مختلفی را توسط کدهای کامپوننت مشخص کرد.
در اینجا headerTemplate خاصیتی است عمومی از نوع TemplateRef که در کدهای کامپوننت متناظر با این قالب مقدار دهی می‌شود. اگر این مقدار دهی صورت نگیرد، از قالب از پیش موجود defaultTabButtons استفاده خواهد کرد.
همچنین اگر می‌خواهیم به selector یک کامپوننت قابلیت انتخاب قالبی را بدهیم می‌توان یک خاصیت عمومی مزین شده‌ی با Input از نوع TemplateRef را مشخص کرد:
 @Input() headerTemplate: TemplateRef<any>;
در این حالت این کامپوننت ویژه می‌تواند به صورت ذیل، قالب خودش را با انتساب به این خاصیت عمومی دریافت کند:
 <tab-container [headerTemplate]="defaultTabButtons"></tab-container>
نظرات مطالب
C# 8.0 - Async Streams
یک نکته‌ی تکمیلی: استفاده از IAsyncEnumerable در جهت ایجاد وب سرویس‌های REST با قابلیت Stream

 مقدمه
در Net Core 3. نوع‌های جدیدی با عنوان‌های <IA­syncEnumerable<T>,IAsync­Enumerator<T> در فضای نام System.Collections.Gener­ic معرفی شدند. همانطور که مشخص است این نوع‌های جدید کاملا با نوع‌های synchronous خود هم پوشانی دارند و مفاهیم قبلی را به پیاده سازی میکنند.

نوع <IAsync­Enumerable<T متد GetAsyncEnumerator را معرفی میکند تا عملیات enumeration را به صورت async انجام دهد و در خروجی این متد، نوع <IAsyncEnumerator<T را برگشت میدهد؛ به‌طوریکه این نوع disposable و دو عضو MoveNextAsync و Current را در خود دارد. اولی برای رسیدن به مقدار بعدی و دومی برای دریافت مقدار فعلی استفاده می‌شود. این در حالی است که MoveNextAsync بجای برگشت دادن یک bool یک <ValueTask<bool را برگشت می‌دهد. همچنین این متد، مقدار CancelationToken را همانند سایر فرآیندهایی که به صورت async تعریف می‌شوند، به صورت اختیاری از ورودی دریافت میکند، تا در صورت لزوم، عملیات جاری را کنسل کند. از طرفی به دلیل اینکه IAsyncEnumerator اینترفیس IAsyncDisposable را پیاده سازی میکند، متد DisposeAsync را نیز در اختیار دارد به‌طوریکه بجای void یک ValueTask را برگشت میدهد.


نحوه استفاده از IAsyncEnumerable 
static async IAsyncEnumerable<int> RangeAsync(int start, int count)
{
  for (int i = 0; i < count; i++)
  {
    await Task.Delay(i);
    yield return start + i;
  }
}
برای استفاده از این نوع در نهایت باید از عبارت yield return استفاده کرد. تا مقدار برگشتی مشخص شده در IAsyncEnumerable که در این مثال int است برگشت داده شود. در صورت استفاده نشدن از yield، خطای cannot return value from an iterator داده می‌شود.

پیاده سازی سمت سرور  

در قسمت قبل سعی بر این بود تا با این نوع جدید آشنا شویم. در این قسمت تلاش میکنیم تا با استفاده از این نوع یک وب سرویس stream را ایجاد کنیم .

ایجاد یک وب سرویس بدون خروجی IAsyncEnumerable

در مرحله اول، یک وب سرویس REST را بدون استفاده از IAsyncEnumerable ایجاد می‌کنیم تا متوجه مشکلات آن شویم و سپس در مرحله بعدی همین وب سرویس را با نوع IAsyncEnumerable  بازنویسی میکنیم.
    [ApiController]
    [Route("[controller]")]
    public class CustomerController : ControllerBase
    {
        private readonly IDictionary<int, Customer> _customers;
        private void FillCustomerFromMemory(int countOfCustomer)
        {
            for (int CustomerId = 1; CustomerId <= countOfCustomer; CustomerId++)
            {
                _customers.Add(key: CustomerId, new Customer($"name_{CustomerId}", CustomerId));
            }
        }
        public CustomerController()
        {
            _customers = new Dictionary<int, Customer>();
            FillCustomerFromMemory(countOfCustomer : 100);
        }
        [HttpGet]
        public async Task<IEnumerable<Customer>> Get()
        {
            var output = new List<Customer>();
            while (_customers.Any(_ => _.Key % 10 == 0))
            {
                var customer = _customers.First(_ => _.Key % 10 == 0);
                output.Add(new Customer(customer.Value.Name, customer.Key));
                await Task.Delay(500);
                _customers.Remove(customer);
            }
            return output;
        }

        public class Customer
        {
            public int Id { get; private set; }

            public string Name { get; private set; }
            public Customer(string name, int id)
            {
                Name = name;
                Id = id;
            }
        }
    }
در صورت اجرای این تکه کد و فراخوانی وب سرویس موجود بعد از بارگذاری کامل دیتا، خروجی به کاربر برگشت داده می‌شود. این در حالی است که ممکن است کاربر فقط به بخشی از این دیتا نیاز داشته باشد؛ برای مثال شاید صرفا به Id با مقدار ۸۰ نیاز داشته باشد، اما مجبور است تا بارگذاری کل دیتا صبر کند. برای رفع این مشکل وب سرویس موجود را مجدد باز نویسی میکنیم.

ایجاد یک وب سرویس با خروجی IAsyncEnumerable

  [HttpGet]
        public async IAsyncEnumerable<Customer> Get()
        {
            while (_customers.Any(_ => _.Key % 10 == 0))
            {
                var customer = _customers.First(_ => _.Key % 10 == 0);
                yield return new Customer(customer.Value.Name, customer.Key);
                _customers.Remove(customer);
                await Task.Delay(500);
            }
        }
این بار به محض اینکه یک دیتا ساخته شد، برگشت داده می‌شود و منتظر تمام دیتا نیستیم. این برگه برنده استفاده از IAsyncEnumerable , yield return است چرا که با ترکیب این دو میتوان وب سرویسی با قابلیت stream را ایجاد کرد. از طرفی حجم payload نیز کمتر شده‌است، چرا که هر بار صرفا یک بلاک مشخص از دیتا را به کلاینت ارسال میکنیم.

تا اینجا سمت سرور را به صورت stream پیاده سازی کردیم. در قسمت بعدی سمت کلاینت را نیز پیاده سازی میکنیم تا دیتا را همانطور که سرور، قسمت به قسمت ارسال میکند، کلاینت نیز آن را به شکل تک قسمتی دریافت کند.

پیاده سازی سمت کلاینت

در قسمت قبل تلاش کردیم تا یک وب سرویس با قابلیت stream را پیاده سازی کنیم. حال در این بخش کد کلاینت را به صورتی ایجاد میکنیم تا هر سری صرفا یک بلاک ارسال شده توسط سرور را دریافت و آن را Deserialize کند. برای این کار از کتابخانه Newtonsoft.Json استفاده میکنیم.
const int TARGET = 80;
var _httpClient = new HttpClient();
using (var response = await _httpClient.GetAsync(
    "https://localhost:7284/customer",
     HttpCompletionOption.ResponseHeadersRead))
{
    var stream = await response.Content.ReadAsStreamAsync();

    var _jsonSerializerSettings = new JsonSerializerSettings();
    var _serializer = Newtonsoft.Json.JsonSerializer.Create(_jsonSerializerSettings);

    using TextReader textReader = new StreamReader(stream);
    using JsonReader jsonReader = new JsonTextReader(textReader);

    await using (stream.ConfigureAwait(false))
    {
        await jsonReader.ReadAsync().ConfigureAwait(false);
        while (await jsonReader.ReadAsync().ConfigureAwait(false) &&
               jsonReader.TokenType != JsonToken.EndArray)
        {
            Customer customer = _serializer!.Deserialize<Customer>(jsonReader);
            if (customer.Id == TARGET)
            {
                Console.WriteLine(customer.Id + " : " + customer.Name);
                break;
            }
        }
    }
}
همانطورکه در کد بالا مشخص است، ابتدا یک درخواست Get را به آدرس وب سرویس زده و برای اینکه متجوجه شویم به انتهای لیست داده‌ها رسیدیم از jsonReader.TokenType != JsonToken.EndArray استفاده میکنیم. با این کار در صورتی که به ] نرسیده باشیم، باید عملیات خواندن از stream ادامه داشته باشد و هر سری بلاک جاری را Deserialize  میکنیم و در آخر در صورتیکه آیتم مورد نظر را دریافت کردیم، با دستور break از حلقه دریافت بلاک‌ها خارج می‌شویم.

 
استفاده از CancelationToken در جهت استفاده بهینه از منابع

تا اینجا به هدفی که انتظار داشتیم رسیدیم؛ به این شکل که یک وب سرویس را ایجاد کردیم تا اطلاعات را به صورت بخش بخش ارسال کند و کلاینتی ساختیم تا این اطلاعات را دریافت کند و در صورتیکه اطلاعات مورد نظر را دریافت کرد، به کار خواندن از وب سرویس خاتمه دهد. برای اینکه متوجه اهمیت CanclationToken  شویم دو سناریو زیر را با هم بررسی میکنیم :

سناریو اول - قطع کردن ارتباط توسط کلاینت

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

برای برطرف کردن مشکل، این سناریو کد سمت سرور را مجدد باز نویسی میکنیم : 
[HttpGet]
        public async IAsyncEnumerable<Customer> Get(CancellationToken cancellationToken)
        {
            while (!cancellationToken.IsCancellationRequested && _customers.Any(_ => _.Key % 10 == 0))
            {
                var customer = _customers.First(_ => _.Key % 10 == 0);
                yield return new Customer(customer.Value.Name, customer.Key);
                _customers.Remove(customer);
                await Task.Delay(500,cancellationToken);
            }
        }
در کد بالا صرفا یک CancelationToken به ورودی متد اضافه شده و از آن در جهت اطمینان از اتصال کلاینت استفاده شده، به طوری که در حلقه اصلی ارسال اطلاعات شرط cancellationToken.IsCancellationRequested را چک میکند تا کاربر به دلایل مختلفی از دریافت اطلاعات منصرف نشده باشد و در صورت لغو کاربر، سرور به کار خود خاتمه میدهد

سناریو دوم-دستیابی کلاینت به اطلاعات مورد نظر

کلاینت در صورتیکه به اطلاعات مورد نظر از طریق وب سرویس دسترسی پیدا کرد، دیگر تمایلی به ادامه خواندن از جریان داده یا stream را ندارد و از حلقه خواندن اطلاعات خارج می‌شود. اما سرور همچنان درگیر ارسال اطلاعات است. برای رفع این مشکل کد سمت کلاینت را بازنویسی میکنیم: 
const int TARGET = 80;
var _httpClient = new HttpClient();
var _cancelationTokenSource = new CancellationTokenSource();

using (var response = await _httpClient.GetAsync(
    "https://localhost:7284/customer",
     HttpCompletionOption.ResponseHeadersRead,
     _cancelationTokenSource.Token))
{
    var stream = await response.Content.ReadAsStreamAsync(_cancelationTokenSource.Token);

    var _jsonSerializerSettings = new JsonSerializerSettings();
    var _serializer = Newtonsoft.Json.JsonSerializer.Create(_jsonSerializerSettings);

    using TextReader textReader = new StreamReader(stream);
    using JsonReader jsonReader = new JsonTextReader(textReader);

    await using (stream.ConfigureAwait(false))
    {
        await jsonReader.ReadAsync(_cancelationTokenSource.Token).ConfigureAwait(false);
        while (await jsonReader.ReadAsync(_cancelationTokenSource.Token).ConfigureAwait(false) &&
               jsonReader.TokenType != JsonToken.EndArray)
        {
            Customer customer = _serializer!.Deserialize<Customer>(jsonReader);
            if (customer.Id == TARGET)
            {
                Console.WriteLine(customer.Id + " : " + customer.Name);
                _cancelationTokenSource.Cancel();
                break;
            }
        }
    }
}

منابع :

https://learn.microsoft.com/en-us/archive/msdn-magazine/2019/november/csharp-iterating-with-async-enumerables-in-csharp-8

https://code-maze.com/csharp-async-enumerable-yield

Github Link : https://github.com/Ershad95/Stream_REST_API
مطالب
استفاده از Web API در ASP.NET Web Forms
گرچه ASP.NET Web API بهمراه ASP.NET MVC بسته بندی شده و استفاده می‌شود، اما اضافه کردن آن به اپلیکیشن‌های ASP.NET Web Forms کار ساده ای است. در این مقاله مراحل لازم را بررسی می‌کنیم.

برای استفاده از Web API در یک اپلیکیشن ASP.NET Web Forms دو قدم اصلی باید برداشته شود:

  • اضافه کردن یک کنترلر Web API که از کلاس ApiController مشتق می‌شود.
  • اضافه کردن مسیرهای جدید به متد Application_Start.


یک پروژه Web Forms بسازید

ویژوال استودیو را اجرا کنید و پروژه جدیدی از نوع ASP.NET Web Forms Application ایجاد کنید.


کنترلر و مدل اپلیکیشن را ایجاد کنید

کلاس جدیدی با نام Product بسازید و خواص زیر را به آن اضافه کنید.

public class Product
{
    public int Id { get; set; }
    public string Name { get; set; }
    public decimal Price { get; set; }
    public string Category { get; set; }
}
همانطور که مشاهده می‌کنید مدل مثال جاری نمایانگر یک محصول است. حال یک کنترلر Web API به پروژه اضافه کنید. کنترلر‌های Web API درخواست‌های HTTP را به اکشن متدها نگاشت می‌کنند. در پنجره Solution Explorer روی نام پروژه کلیک راست کنید و گزینه Add, New Item را انتخاب کنید.

در دیالوگ باز شده گزینه Web را از پانل سمت چپ کلیک کنید و نوع آیتم جدید را Web API Controller Class انتخاب نمایید. نام این کنترلر را به "ProductsController" تغییر دهید و OK کنید.

کنترلر ایجاد شده شامل یک سری متد است که بصورت خودکار برای شما اضافه شده اند، آنها را حذف کنید و کد زیر را به کنترلر خود اضافه کنید.

namespace WebForms
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Net;
    using System.Net.Http;
    using System.Web.Http;

    public class ProductsController : ApiController
    {

        Product[] products = new Product[] 
        { 
            new Product { Id = 1, Name = "Tomato Soup", Category = "Groceries", Price = 1 }, 
            new Product { Id = 2, Name = "Yo-yo", Category = "Toys", Price = 3.75M }, 
            new Product { Id = 3, Name = "Hammer", Category = "Hardware", Price = 16.99M } 
        };

        public IEnumerable<Product> GetAllProducts()
        {
            return products;
        }

        public Product GetProductById(int id)
        {
            var product = products.FirstOrDefault((p) => p.Id == id);
            if (product == null)
            {
                throw new HttpResponseException(HttpStatusCode.NotFound);
            }
            return product;
        }

        public IEnumerable<Product> GetProductsByCategory(string category)
        {
            return products.Where(
                (p) => string.Equals(p.Category, category,
                    StringComparison.OrdinalIgnoreCase));
        }
    }
}
کنترلر جاری لیستی از محصولات را بصورت استاتیک در حافظه محلی نگهداری می‌کند. متدهایی هم برای دریافت لیست محصولات تعریف شده اند.


اطلاعات مسیریابی را اضافه کنید

مرحله بعدی اضافه کردن اطلاعات مسیریابی (routing) است. در مثال جاری می‌خواهیم آدرس هایی مانند "api/products/" به کنترلر Web API نگاشت شوند. فایل Global.asax را باز کنید و عبارت زیر را به بالای آن اضافه نمایید.

using System.Web.Http;
حال کد زیر را به متد Application_Start اضافه کنید.
RouteTable.Routes.MapHttpRoute(
    name: "DefaultApi",
    routeTemplate: "api/{controller}/{id}",
    defaults: new { id = System.Web.Http.RouteParameter.Optional }
    );

برای اطلاعات بیشتر درباره مسیریابی در Web API به این لینک مراجعه کنید.


دریافت اطلاعات بصورت آژاکسی در کلاینت

تا اینجا شما یک API دارید که کلاینت‌ها می‌توانند به آن دسترسی داشته باشند. حال یک صفحهHTML خواهیم ساخت که با استفاده از jQuery سرویس را فراخوانی می‌کند. صفحه Default.aspx را باز کنید و کدی که بصورت خودکار در قسمت Content تولید شده است را حذف کرده و کد زیر را به این قسمت اضافه کنید:

<%@ Page Title="Home Page" Language="C#" MasterPageFile="~/Site.Master" 
    AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="WebForms._Default" %>

<asp:Content ID="HeaderContent" runat="server" ContentPlaceHolderID="HeadContent">
</asp:Content>

<asp:Content ID="BodyContent" runat="server" ContentPlaceHolderID="MainContent">
    <h2>Products</h2>
    <table>
    <thead>
        <tr><th>Name</th><th>Price</th></tr>
    </thead>
    <tbody id="products">
    </tbody>
    </table>
</asp:Content>
حال در قسمت HeaderContent کتابخانه jQuery را ارجاع دهید.
<asp:Content ID="HeaderContent" runat="server" ContentPlaceHolderID="HeadContent">
    <script src="Scripts/jquery-1.7.1.min.js" type="text/javascript"></script>
</asp:Content>

همانطور که می‌بینید در مثال جاری از فایل محلی استفاده شده است اما در اپلیکیشن‌های واقعی بهتر است از CDN‌‌ها استفاده کنید.

نکته: برای ارجاع دادن اسکریپت‌ها می‌توانید بسادگی فایل مورد نظر را با drag & drop به کد خود اضافه کنید.

زیر تگ jQuery اسکریپت زیر را اضافه کنید.

<script type="text/javascript">
    function getProducts() {
        $.getJSON("api/products",
            function (data) {
                $('#products').empty(); // Clear the table body.

                // Loop through the list of products.
                $.each(data, function (key, val) {
                    // Add a table row for the product.
                    var row = '<td>' + val.Name + '</td><td>' + val.Price + '</td>';
                    $('<tr/>', { text: row })  // Append the name.
                        .appendTo($('#products'));
                });
            });
        }

        $(document).ready(getProducts);
</script>

هنگامی که سند جاری (document) بارگذاری شد این اسکریپت یک درخواست آژاکسی به آدرس "api/products/" ارسال می‌کند. سرویس ما لیستی از محصولات را با فرمت JSON بر می‌گرداند، سپس این اسکریپت لیست دریافت شده را به جدول HTML اضافه می‌کند.

اگر اپلیکیشن را اجرا کنید باید با نمایی مانند تصویر زیر مواجه شوید:

مطالب
آشنایی با قابلیت FileStream اس کیوال سرور 2008 - قسمت سوم

در انتهای قسمت قبل، نحوه‌ی ایجاد یک جدول جدید با فیلدی از نوع فایل استریم بررسی شد، حال اگر جدولی از پیش وجود داشت، نحوه‌ی افزودن فیلد ویژه مورد نظر به آن، به صورت زیر است:

alter table tbl_files set(filestream_on ='default')

go
alter table tbl_files
add

[systemfile] varbinary(max) filestream null ,
FileId uniqueidentifier not null rowguidcol unique default (newid())
go

در ادامه جدول tblFiles قسمت قبل را در نظر بگیرید:

CREATE TABLE [tblFiles](
[FileId] [uniqueidentifier] ROWGUIDCOL NOT NULL,
[Title] [nvarchar](255) NOT NULL,
[SystemFile] [varbinary](max) FILESTREAM NULL,
UNIQUE NONCLUSTERED
(
[FileId] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY] FILESTREAM_ON [fsg1]

ALTER TABLE [dbo].[tblFiles] ADD DEFAULT (newid()) FOR [FileId]
GO

نحوه‌ی افزودن رکوردی جدید به جدول tblFiles :

INSERT INTO [tblFiles]
(
[Title],
[SystemFile]
)
VALUES
(
'file-1',
CAST('data data data' AS VARBINARY(MAX))
)
در اینجا سعی کرده‌ایم یک رشته ساده را در فیلدی از نوع فایل استریم ذخیره کنیم که روش کار به صورت فوق است. از آنجائیکه مقدار پیش فرض FileId را هنگام تعریف جدول به NEWID تنظیم کرده‌ایم، نیازی به ذکر آن نیست و به صورت خودکار محاسبه و ذخیره خواهد شد.
اگر کنجکاو باشید که این فایل اکنون کجا ذخیره شده و نحوه‌ی مدیریت آن توسط اس کیوال سرور به چه صورتی است، فقط کافی است به مسیری که هنگام افزودن گروه فایل‌ها و فایل مربوطه در تنظیمات خواص دیتابیس در قسمت قبل مشخص کردیم، مراجعه کرد (شکل زیر).



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

using System;
using System.IO;
using System.Data.SqlClient;
using System.Data;

namespace FileStreamTest
{
class CFS
{
/// <summary>
/// افزودن رکورد به جدول حاوی ستونی از نوع فایل استریم
/// </summary>
/// <param name="filePath">مسیر فایل</param>
/// <param name="title">عنوانی دلخواه</param>
public static void AddNewRecord(string filePath, string title)
{
//آیا فایل وجود دارد؟
if (!File.Exists(filePath))
throw new FileNotFoundException(
"لطفا مسیر فایل معتبری را مشخص نمائید", filePath);

//خواندن اطلاعات فایل در آرایه‌ای از بایت‌ها
byte[] buffer = File.ReadAllBytes(filePath);

using (SqlConnection objSqlCon = new SqlConnection())
{
//todo: کانکشن استرینگ باید از یک فایل کانفیگ خوانده شود
objSqlCon.ConnectionString =
"Data Source=(local);Initial Catalog=testdb2009;Integrated Security = true";
objSqlCon.Open();

//شروع یک تراکنش
using (SqlTransaction objSqlTran = objSqlCon.BeginTransaction())
{
//ساخت عبارت افزودن پارامتری
using (SqlCommand objSqlCmd = new SqlCommand(
"INSERT INTO [tblFiles]([Title],[SystemFile]) VALUES(@title , @file)",
objSqlCon, objSqlTran))
{
objSqlCmd.CommandType = CommandType.Text;

//تعریف وضعیت پارامترها و مقدار دهی آن‌ها
objSqlCmd.Parameters.AddWithValue("@title", title);
objSqlCmd.Parameters.AddWithValue("@file", buffer);

//اجرای فرامین
objSqlCmd.ExecuteNonQuery();
}

//پایان تراکنش
objSqlTran.Commit();
}
}
}

/// <summary>
/// دریافت اطلاعات فایل ذخیره شده به صورت آرایه‌ای از بایت‌ها
/// </summary>
/// <param name="fileId">کلید مورد استفاده</param>
/// <returns></returns>
public static byte[] GetDataFromDb(string fileId)
{
byte[] data = null;

using (SqlConnection objConn = new SqlConnection())
{
//کوئری اس کیوال پارامتری جهت دریافت محتویات فایل
string cmdText = "SELECT SystemFile FROM tblFiles WHERE FileId=@id";
using (SqlCommand objCmd = new SqlCommand(cmdText, objConn))
{
//todo: کانکشن استرینگ باید از یک فایل کانفیگ خوانده شود
objConn.ConnectionString =
"Data Source=(local);Initial Catalog=testdb2009;Integrated Security = true";
objConn.Open();

//تنظیم کردن وضعیت و مقدار پارامتر تعریف شده در کوئری
objCmd.Parameters.AddWithValue("@id", fileId);

//اجرای فرامین و دریافت فایل
using (SqlDataReader objread = objCmd.ExecuteReader())
{
if (objread != null)
if (objread.Read())
{
if (objread["SystemFile"] != DBNull.Value)
data = (byte[])objread["SystemFile"];
}
}
}
}

return data;
}
}
}

مثالی در مورد روش استفاده از کلاس فوق :

using System.IO;

namespace FileStreamTest
{
class Program
{
static void Main(string[] args)
{
CFS.AddNewRecord(@"C:\filest05.PNG", "test1");

//آی دی رکورد ذخیره شده در دیتابیس برای مثال
byte[] data = CFS.GetDataFromDb("BB848D45-382C-4D95-BF4E-52C3509407D4");
if (data != null)
{
File.WriteAllBytes(@"C:\tst.PNG", data);
}
}
}
}
روش فوق با روش متداول افزودن یک فایل به دیتابیس اس کیوال سرور هیچ تفاوتی ندارد و این‌جا هم بدون مشکل کار می‌کند. اطلاعات نهایی به صورت فایل‌هایی بر روی سیستم که توسط اس کیوال سرور مدیریت خواهند شد و با جدول شما یکپارچه‌اند، ذخیره می‌شوند.

در روش دیگری که در اکثر مقالات مرتبط مورد استفاده است، از شیء SqlFileStream کمک گرفته شده و نحوه‌ی انجام آن نیز به صورت زیر می‌باشد.
در ابتدا دو رویه ذخیره شده زیر را ایجاد می‌کنیم:

CREATE PROCEDURE [AddFile](@Title NVARCHAR(255), @filepath VARCHAR(MAX) OUTPUT)
AS
BEGIN
SET NOCOUNT ON;

DECLARE @ID UNIQUEIDENTIFIER
SET @ID = NEWID()

INSERT INTO [tblFiles]
(
[FileId],
[title],
[SystemFile]
)
VALUES
(
@ID,
@Title,
CAST('' AS VARBINARY(MAX))
)

SELECT @filepath = SystemFile.PathName()
FROM tblFiles
WHERE FileId = @ID
END
GO

CREATE PROCEDURE [GetFilePath](@Id VARCHAR(50))
AS
BEGIN
SET NOCOUNT ON;

SELECT SystemFile.PathName()
FROM tblFiles
WHERE FileId = @ID
END
در رویه ذخیره شده AddFile ، ابتدا رکوردی بر اساس عنوان دلخواه ورودی با یک فایل خالی ایجاد می‌شود. سپس مسیر سیستمی این فایل را در آرگومان خروجی filepath قرار می‌دهیم. SystemFile.PathName از اس کیوال سرور 2008 جهت فیلدهای فایل استریم به اس کیوال سرور اضافه شده است. از این مسیر در برنامه خود جهت نوشتن بایت‌های فایل مورد نظر در آن توسط شیء SqlFileStream استفاده خواهیم کرد.
رویه ذخیره شده GetFilePath نیز تنها مسیر سیستمی فایل استریم ذخیره شده را بر می‌گرداند.
به این ترتیب کدهای برنامه به صورت زیر تغییر خواهند کرد:

using System.Data.SqlClient;
using System.Data;
using System.Data.SqlTypes;
using System.IO;

namespace FileStreamTest
{
class CFSqlFileStream
{
/// <summary>
/// افزودن رکورد به جدول حاوی ستونی از نوع فایل استریم
/// </summary>
/// <param name="filePath">مسیر فایل</param>
/// <param name="title">عنوانی دلخواه</param>
public static void AddNewRecord(string filePath, string title)
{
//آیا فایل وجود دارد؟
if (!File.Exists(filePath))
throw new FileNotFoundException(
"لطفا مسیر فایل معتبری را مشخص نمائید", filePath);

//خواندن اطلاعات فایل در آرایه‌ای از بایت‌ها
byte[] buffer = File.ReadAllBytes(filePath);

using (SqlConnection objSqlCon = new SqlConnection())
{
//todo: کانکشن استرینگ باید از یک فایل کانفیگ خوانده شود
objSqlCon.ConnectionString =
"Data Source=(local);Initial Catalog=testdb2009;Integrated Security = true";
objSqlCon.Open();

//شروع یک تراکنش
using (SqlTransaction objSqlTran = objSqlCon.BeginTransaction())
{
//استفاده از رویه ذخیره شده افزودن فایل
using (SqlCommand objSqlCmd = new SqlCommand(
"AddFile", objSqlCon, objSqlTran))
{
objSqlCmd.CommandType = CommandType.StoredProcedure;

//مشخص ساختن وضعیت و مقدار پارامتر عنوان
SqlParameter objSqlParam1 = new SqlParameter("@Title", SqlDbType.NVarChar, 255);
objSqlParam1.Value = title;

//مشخص ساختن پارامتر خروجی رویه ذخیره شده
SqlParameter objSqlParamOutput = new SqlParameter("@filepath", SqlDbType.VarChar, -1);
objSqlParamOutput.Direction = ParameterDirection.Output;

//افزودن پارامترها به شیء کامند
objSqlCmd.Parameters.Add(objSqlParam1);
objSqlCmd.Parameters.Add(objSqlParamOutput);

//اجرای رویه ذخیره شده
objSqlCmd.ExecuteNonQuery();

//و سپس دریافت خروجی آن
string Path = objSqlCmd.Parameters["@filepath"].Value.ToString();

//زمینه تراکنش فایل استریم موجود را دریافت کرده و از آن برای نوشتن محتویات فایل استفاده خواهیم کرد
//این مورد نیز یکی از تازه‌های اس کیوال سرور 2008 است
using (SqlCommand objCmd = new SqlCommand(
"SELECT GET_FILESTREAM_TRANSACTION_CONTEXT()", objSqlCon, objSqlTran))
{
byte[] objContext = (byte[])objCmd.ExecuteScalar();
using (SqlFileStream objSqlFileStream =
new SqlFileStream(Path, objContext, FileAccess.Write))
{
objSqlFileStream.Write(buffer, 0, buffer.Length);
}
}
}

objSqlTran.Commit();
}
}
}

/// <summary>
/// دریافت اطلاعات فایل ذخیره شده به صورت آرایه‌ای از بایت‌ها
/// </summary>
/// <param name="fileId">کلید مورد استفاده</param>
/// <returns></returns>
public static byte[] GetDataFromDb(string fileId)
{
byte[] buffer = null;

using (SqlConnection objSqlCon = new SqlConnection())
{
//todo: کانکشن استرینگ باید از یک فایل کانفیگ خوانده شود
objSqlCon.ConnectionString =
"Data Source=(local);Initial Catalog=testdb2009;Integrated Security = true";
objSqlCon.Open();

//شروع یک تراکنش
using (SqlTransaction objSqlTran = objSqlCon.BeginTransaction())
{
//استفاده از رویه ذخیره شده دریافت مسیر فایل
using (SqlCommand objSqlCmd =
new SqlCommand("GetFilePath", objSqlCon, objSqlTran))
{
objSqlCmd.CommandType = CommandType.StoredProcedure;

//مشخص ساختن پارامتر ورودی رویه ذخیره شده و مقدار دهی آن
SqlParameter objSqlParam1 = new SqlParameter("@ID", SqlDbType.VarChar, 50);
objSqlParam1.Value = fileId;
objSqlCmd.Parameters.Add(objSqlParam1);

//اجرای رویه ذخیره شده و دریافت مسیر سیستمی فایل استریم
string path = string.Empty;
using (SqlDataReader sdr = objSqlCmd.ExecuteReader())
{
sdr.Read();
path = sdr[0].ToString();
}

//زمینه تراکنش فایل استریم موجود را دریافت کرده و از آن برای خواندن محتویات فایل استفاده خواهیم کرد
//این مورد نیز یکی از تازه‌های اس کیوال سرور 2008 است
using (SqlCommand objCmd = new SqlCommand(
"SELECT GET_FILESTREAM_TRANSACTION_CONTEXT()", objSqlCon, objSqlTran))
{
byte[] objContext = (byte[])objCmd.ExecuteScalar();

using (SqlFileStream objSqlFileStream =
new SqlFileStream(path, objContext, FileAccess.Read))
{
buffer = new byte[(int)objSqlFileStream.Length];
objSqlFileStream.Read(buffer, 0, buffer.Length);
}
}
}

objSqlTran.Commit();
}
}

return buffer;
}
}
}
در پایان برای تکمیل بحث می‌توان به مقاله‌ی مرجع زیر مراجعه کرد:
FILESTREAM Storage in SQL Server 2008

مطالب
ASP.NET MVC #8

معرفی HTML Helpers

یک HTML Helper تنها یک متد است که رشته‌ای را بر می‌گرداند و این رشته می‌تواند حاوی هر نوع محتوای دلخواهی باشد. برای مثال می‌توان از HTML Helpers برای رندر تگ‌های HTML، مانند img و input استفاده کرد. یا به کمک HTML Helpers می‌توان ساختارهای پیچیده‌تری مانند نمایش لیستی از اطلاعات دریافت شده از بانک اطلاعاتی را پیاده سازی کرد. به این ترتیب حجم کدهای تکراری تولید رابط کاربری در Viewهای برنامه‌های ASP.NET MVC به شدت کاهش خواهد یافت، به همراه قابلیت استفاده مجدد از متدهای الحاقی HTML Helpers در برنامه‌های دیگر.
HTML Helpers در ASP.NET MVC معادل کنترل‌های ASP.NET Web forms هستند اما نسبت به آن‌ها بسیار سبک‌تر می‌باشند؛ برای مثال به همراه ViewState و همچنین Event model نیستند.
ASP.NET MVC به همراه تعدادی متد HTML Helper توکار است و برای دسترسی به آن‌ها شیء Html که وهله‌ای از کلاس توکار HtmlHelper می‌باشد، در تمام Viewها قابل استفاده است.


نحوه ایجاد یک HTML Helper سفارشی

از دات نت سه و نیم به بعد امکان توسعه اشیاء توکار فریم ورک، به کمک متدهای الحاقی (extension methods) میسر شده است. برای نوشتن یک HTML Helper نیز باید همین شیوه عمل کرد و کلاس HtmlHelper را توسعه داد. در ادامه قصد داریم یک HTML Helper را جهت رندر تگ label در صفحه ایجاد کنیم. برای این منظور پوشه‌ی جدیدی به نام Helper را به پروژه اضافه نمائید (جهت نظم بیشتر). سپس کلاس زیر را به آن اضافه کنید:

using System;
using System.Web.Mvc;

namespace MvcApplication4.Helpers
{
public static class LabelExtensions
{
public static string MyLabel(this HtmlHelper helper, string target, string text)
{
return string.Format("<label for='{0}'>{1}</label>", target, text);
}
}
}

همانطور که ملاحظه می‌کنید متد Label به شکل یک متد الحاقی توسعه دهنده کلاس HtmlHelper که تنها یک رشته را بر می‌گرداند، تعریف شده است. اکنون برای استفاده از این متد در View دلخواهی خواهیم داشت:

@using MvcApplication4.Helpers
@{
ViewBag.Title = "Index";
}

<h2>Index</h2>

@Html.MyLabel("firstName", "First Name:")

ابتدا فضای نام مرتبط با متد الحاقی باید پیوست شود و سپس از طریق شیء Html می‌توان به این متد الحاقی دسترسی پیدا کرد. اگر برنامه را اجرا کنید، این خروجی را مشاهده خواهیم کرد. چرا؟

Index
<label for='firstName'>First Name:</label>

علت این است که Razor، اطلاعات را Html encoded به مرورگر تحویل می‌دهد. برای تغییر این رویه باید اندکی متد الحاقی تعریف شده را تغییر داد:

using System.Web.Mvc;

namespace MvcApplication4.Helpers
{
public static class LabelExtensions
{
public static MvcHtmlString MyLabel(this HtmlHelper helper, string target, string text)
{
return MvcHtmlString.Create(string.Format("<label for='{0}'>{1}</label>", target, text));
}
}
}

تنها تغییر صورت گرفته، استفاده از MvcHtmlString بجای string معمولی است تا Razor آن‌را encode نکند.

تعریف HTML Helpers سفارشی به صورت عمومی:

می‌توان فضای نام MvcApplication4.Helpers این مثال را عمومی کرد. یعنی بجای اینکه بخواهیم در هر View  آن‌را ابتدا تعریف کنیم، یکبار آن‌را همانند تعاریف اصلی یک برنامه ASP.NET MVC، عمومی معرفی می‌کنیم. برای این منظور فایل web.config موجود در پوشه Views را باز کنید (و نه فایل web.config قرار گرفته در ریشه اصلی برنامه). سپس فضای نام مورد نظر را در قسمت namespaces صفحات اضافه نمائید:

<pages pageBaseType="System.Web.Mvc.WebViewPage">
<namespaces>
<add namespace="System.Web.Mvc" />
<add namespace="System.Web.Mvc.Ajax" />
<add namespace="System.Web.Mvc.Html" />
<add namespace="System.Web.Routing" />
<add namespace="MvcApplication4.Helpers"/>
</namespaces>

به این ترتیب متدهای الحاقی تعریف شده در فضای نام MvcApplication4.Helpers،‌ در تمام Viewهای برنامه در دسترس خواهند بود.

استفاده از کلاس TagBuilder برای تولید HTML Helpers سفارشی:

using System.Web.Mvc;

namespace MvcApplication4.Helpers
{
public static class LabelExtensions
{
public static MvcHtmlString MyNewLabel(this HtmlHelper helper, string target, string text)
{
var labelTag = new TagBuilder("label");
labelTag.MergeAttribute("for", target);
labelTag.InnerHtml = text;
return MvcHtmlString.Create(labelTag.ToString());
}
}
}

در فضای نام System.Web.Mvc، کلاسی وجود دارد به نام TagBuilder که کار تولید تگ‌های HTML، مقدار دهی ویژگی‌ها و خواص آن‌ها را بسیار ساده می‌کند و روش توصیه شده‌ای است برای تولید متدهای HTML Helper. یک نمونه از کاربرد آن‌را برای بازنویسی متد MyLabel ذکر شده در اینجا ملاحظه می‌کنید.
شبیه به همین کلاس، کلاس دیگری به نام HtmlTextWriter در فضای نام System.Web.UI برای انجام اینگونه کارها وجود دارد.


نوشتن HTML Helpers ویژه، به کمک امکانات Razor

نوع دیگری از این متدهای کمکی، Declarative HTML Helpers نام دارند. از این جهت هم Declarative نامیده شده‌اند که مستقیما درون فایل‌های cshtml یا vbhtml به کمک امکانات Razor قابل تعریف هستند. تولید این نوع متدهای کمکی به این شکل نسبت به مثلا روش TagBuilder ساده‌تر است،‌ چون توسط Razor به سادگی و به نحو طبیعی‌تری می‌توان تگ‌های HTML و کدهای مورد نظر را با هم ترکیب کرد (این رفتار طبیعی و روان، یکی از اهداف Razor است).
به عنوان مثال، تعاریف همان کلاس‌های Product و Products قسمت قبل (قسمت هفتم) را در نظر بگیرید. با همان کنترلر و View ایی که ذکر شد.
سپس برای تعریف این نوع خاص از HTML Helpers/Razor Helpers باید به این نحو عمل کرد:
الف) در ریشه پروژه یا سایت، پوشه‌ی جدیدی به نام App_Code ایجاد کنید (دقیقا به همین نام. این پوشه، جزو پوشه‌های ویژه ASP.NET است).
ب) بر روی این پوشه کلیک راست کرده و گزینه Add|New Item را انتخاب کنید.
ج) در صفحه باز شده، MVC 3 Partial Page/Razor را یافته و مثلا نام ProductsList.cshtml را وارد کرده و این فایل را اضافه کنید.
د) محتوای این فایل جدید را به نحو زیر تغییر دهید:

@using MvcApplication4.Models
@helper GetProductsList(List<Product> products)
{
<ul>
@foreach (var item in products)
{
<li>@item.Name ($@item.Price)</li>
}
</ul>
}

در اینجا نحوه تعریف یک helper method مخصوص Razor را مشاهده می‌کنید که با کلمه @helper شروع شده است. مابقی آن هم ترکیب آشنای code و markup هستند که به کمک امکانات Razor به این شکل روان میسر شده است.
اکنون اگر Viewایی بخواهد از این اطلاعات استفاده کند تنها کافی است به نحو زیر عمل نماید:

@model List<MvcApplication4.Models.Product>
@{
ViewBag.Title = "Index";
}

<h2>Index</h2>

@ProductsList.GetProductsList(@Model)

ابتدا نام فایل ذکر شده بعد نام متد کمکی تعریف شده در آن. Model هم در اینجا به لیستی از محصولات اشاره می‌کند.
همچنین چون در پوشه app_code قرار گرفته، تمام Viewها به اطلاعات آن دسترسی خواهند داشت. علت هم این است که ASP.NET به صورت خودکار محتوای این پوشه ویژه را همواره کامپایل می‌کند و در اختیار برنامه قرار می‌دهد.
به علاوه در این فایل ProductsList.cshtml، باز هم می‌توان متدهای helper دیگری را اضافه کرد و از این بابت محدودیتی ندارد. همچنین می‌توان این متد helper را مستقیما داخل یک View هم تعریف کرد. بدیهی است در این حالت قابلیت استفاده مجدد از آن‌را به همراه داشتن Viewهایی تمیز و کم حجم، از دست خواهیم داد.

جهت تکمیل بحث
Turn your Razor helpers into reusable libraries


مطالب
رفرنس تایپ‌ها چگونه به ورودی متدها ارسال می‌شوند
اگر شما یک تایپ از نوع reference type را در ورودی یک متد قرار دهید و در داخل متد، پراپرتی‌های این تایپ را ویرایش کنید، بعد از آنکه از متد خارج می‌شود، تغییرات خود را مشاهده خواهید کرد. به طور مثال کد زیر را در نظر بگیرید که در داخل متد EditUserName، مقدار پراپرتی Name را تغییر داده‌ایم:
public class User
{
    public string Name { get; set; }
}
class Program
{
    static void Main(string[] args)
    {
        User user = new User
        {
            Name = "Farhad"
        };
        EditUserName(user);
        Console.WriteLine(user.Name);
        //Print Zamani in  console 
    }
    public static void EditUserName(User userCopy)
    {
        userCopy.Name = "Zamani";
    }
}
اگر کد بالا را اجرا کنید، مقدار "Zamani" را در صفحه کنسول مشاهده میکنید.
 اگر یک متغیر از نوع Value type مانند int را به ورودی متدی ارسال کنید و آن را در داخل متد تغییر دهید، تغییرات خود را بعد از آنکه از متد خارج میشود، مشاهده نمیکنید.
اما اگر در داخل متد (EditUserName) بالا که کلاس User را پاس داده‌ایم، مقدار پارامتر userCopy را برابر null کنیم چه اتفاقی میافتد؟ (خودم اول فکر کردم بعد از اینکه از متد EditUserName خارج میشه و میخواد user.Name رو چاپ کنه، Null reference exception رخ میده؛ ولی نه).
اگر تغییرات زیر را اعمال کنیم و مجددا برنامه را اجرا کنیم، همان نتیجه‌ی قبلی را مشاهده میکنیم:
static void Main(string[] args)
{
    User user = new User
    {
        Name = "Farhad"
    };
    EditUserName(user);
    Console.WriteLine(user.Name);
    //Print Zamani in console
}
public static void EditUserName(User userCopy)
{
    userCopy.Name = "Zamani";
    userCopy = null;
}
اگرچه نوع userCopy از نوع reference type میباشد، اما بعد از آنکه از متد خارج میشود، مقدار قبلی خود را دارد، چرا؟ 
زیرا زمانیکه شما مقدار user را به متد EditUserName پاس میدهید، یک کپی از آبجکت user به متد ارسال میشود، یعنی یک کپی از متغیر user ایجاد میشود و به ورودی متد ارسال میشود (خود متغیر user ارسال نمیشود) . بر این اساس میتوان گفت که user و userCopy هر دو به یک مکان از حافظه اشاره دارند که userCopy به متد EditUserName ارسال شده است.

 و زمانی که مقدار userCopy را برابر null میکنید، رفرنسی که به آن اشاره دارد، از بین میرود.

به همین دلیل اگر در داخل متد، پارامتری که از نوع reference type است را برابر null کنید و یا new کنید، بعد از آنکه از متد خارج شود، تغییرات را مشاهده نمی‌کنید. همین عمل برای نمونه سازی داخل متد نیز صدق میکند.
 برای مثال در کد زیر در داخل متد EditUserName، پارامتر userCopy را new میکنیم و سپس مقدار نام آن را تغییر میدهیم و بعد از آنکه از متد خارج میشود، همان مقداری را نشان میدهد که قبل از new شدن اعمال شده بود.
static void Main(string[] args)
{
    User user = new User
    {
        Name = "Farhad"
    };
    EditUserName(user);
    Console.WriteLine(user.Name);
    //Print Zamani in console
}
public static void EditUserName(User userCopy)
{
    userCopy.Name = "Zamani";
    userCopy = new User();
    userCopy.Name = "Farhad";
}
اگر کد بالا را اجرا کنید مجددا "Zamani" را در صفحه کنسول مشاهده میکنید؛ زیرا زمانیکه userCopy را new میکنید، رفرنسی که userCopy به آن اشاره دارد، تغییر میکند و به مکانی دیگر از حافظه اشاره میکند و تغییرات بر روی user که در متد Main قرار دارد اعمال نمیشود. 
در متغیر از نوع رفرنس، آدرس آبجکت اصلی کپی می‌شود و در واقع پارامتر، یک متغیر جدید است که آدرس ابجکت اصلی را دارد؛ بنابراین هنگامیکه به اعضای آبجکت دسترسی صورت گیرد، از طریق آدرس، به همان عضو آبجکت اصلی اشاره شده و درنتیجه تغییر، ماندگار می‌ماند. اما هنگامیکه خود پارامتر کلاس مقدار دهی شود، یک فضای جدید در حافظه ایجاد شده و آدرس آن در محتوای پارامتر کپی می‌شود. اینگونه پس از پایان متد، تغییر پایا نبوده، چرا که آدرس پارامتر، با آبجکت اصلی متفاوت خواهد بود. 
مطالب دوره‌ها
محدود کردن بارگذاری اشیاء مرتبط یک ViewModel در حین کار با Entity Framework و AutoMapper
فرض کنید مدل کاربران سایت، دارای دو خاصیت راهبری (navigation properties) آدرس‌های مختلف یک کاربر و ایمیل‌های متفاوت او است:
public class SiteUser
{
    public int Id { get; set; }
    public string Name { get; set; }
 
    public virtual ICollection<Address> Addresses { get; set; }
    public virtual ICollection<Email> Emails { get; set; }
}

public class Email
{
    public int Id { get; set; }
    public string Text { get; set; }
 
    [ForeignKey("SiteUserId")]
    public virtual SiteUser SiteUser { get; set; }
    public int SiteUserId { get; set; }
}

public class Address
{
    public int Id { get; set; }
    public string Text { get; set; }
 
    [ForeignKey("SiteUserId")]
    public virtual SiteUser SiteUser { get; set; }
    public int SiteUserId { get; set; }
}
همچنین ViewModel ایی را هم که تعریف کرده‌ایم، شامل همان خواص راهبری مدل می‌شود:
public class UserViewModel
{
    public int Id { get; set; }
    public string Name { get; set; }
 
    public ICollection<Address> Addresses { get; set; }
    public ICollection<Email> Emails { get; set; }
}
در این حالت کوئری ذیل:
 var user1 = context.Users.Project().To<UserViewModel>().FirstOrDefault();
سبب خواهد شد تا تمام خواص راهبری ذکر شده‌ی در ViewModel، در طی یک کوئری از بانک اطلاعاتی دریافت شده و مقدار دهی شوند. اما ... شاید در حین استفاده‌ی از آن، صرفا به لیست ایمیل‌های شخص نیاز داشته باشیم و نیازی نباشد تا حتما آدرس‌های او نیز واکشی شوند. برای حل این بارگذاری اضافی، می‌توان از تنظیم ExplicitExpansion استفاده کرد:
public class TestProfile : Profile
{
    protected override void Configure()
    {
        this.CreateMap<SiteUser, UserViewModel>()
                .ForMember(dest => dest.Addresses, opt => opt.ExplicitExpansion())
                .ForMember(dest => dest.Emails, opt => opt.ExplicitExpansion());
    }
 
    public override string ProfileName
    {
        get { return this.GetType().Name; }
    }
}
ExplicitExpansion به این معنا است که تا در کوئری مدنظر صریحا قید نشود که قرار است کدام خاصیت راهبری بسط یابد، اطلاعات آن از بانک اطلاعاتی دریافت نخواهد شد.
پس از تنظیم فوق، اگر کوئری ذکر شده را اجرا کنید، مشاهده خواهید کرد که دو خاصیت آدرس‌ها و ایمیل‌های شخص، نال هستند.
برای ذکر صریح خواص راهبری مورد نیاز، اینبار می‌توان از پارامترهای متد Project To مانند مثال ذیل استفاده کرد:
using (var context = new MyContext())
{
    var user1 = context.Users
                       .Project()
                       .To<UserViewModel>(parameters: new { }, membersToExpand: viewModel => viewModel.Emails)
                       .FirstOrDefault(); 
 
    if (user1 != null)
    {
        foreach (var email in user1.Emails)
        {
            Console.WriteLine(email.Text);
        }
    }
}
این کوئری سبب خواهد شد تا صرفا خاصیت Emails از بانک اطلاعاتی واکشی شود و آدرس‌ها خیر. به این ترتیب می‌توان بر روی نحوه‌ی بارگذاری خواص راهبری کنترل کاملی داشت.


کدهای کامل این مطلب را از اینجا می‌توانید دریافت کنید.
نظرات مطالب
ارتقاء به ASP.NET Core 1.0 - قسمت 13 - معرفی View Components
نکته تکمیلی :
ایجاد منوهای چند سطحی با استفاده از ViewComponent تا N سطح
کلاس Entity :
public class Navbar
    {
        public int Id { get; set; }
        public string Title { get; set; }

        public int? ParentId { get; set; }
        public virtual Navbar Parent { get; set; }
        public bool IsActive { get; set; }
        public bool HasChiled { get; set; }
        public bool IsMegaMenu { get; set; }
        public PageGroup PageGroup { get; set; }
        public string Url { get; set; }
        public bool OpenNewPage { get; set; }

        public virtual ICollection<Navbar> Children { get; set; }
    }
کلاس ViewComponent :
    public class TopNavbar : ViewComponent
    {
        private readonly DbSet<Navbar> _navbars;
        private readonly AppDbContext _dbContext;

        public TopNavbar(AppDbContext dbContext)
        {
            _dbContext = dbContext;
            _navbars = _dbContext.Set<Navbar>();
        }
        public async Task<IViewComponentResult> InvokeAsync()
        {
            var navbars = await _navbars.Include(p=>p.Parent).Include(x=>x.Children).OrderBy(x=>x.ParentId).ToListAsync();
            return View(viewName: "~/Views/Shared/Components/NavbarViewComponent/_Menu.cshtml", navbars);
        }
    }
فراخوانی viewcomponent در Layout.cshtml
 <ul class="menu">
                <li>
                    <a href="Index_demo6.html"><i class="menu_icon_wrapper fal fa-home-lg-alt"></i>صفحه اصلی</a>
                </li>
                @await Component.InvokeAsync("TopNavbar");                
 </ul>
Menu.cshtml_ :
@using TR.Context.Entities
@using Microsoft.AspNetCore.Html
@model IEnumerable<TR.Context.Entities.Navbar>


@foreach (var menu in Model.Where(x => x.Parent == null))
{

    <li class="@(menu.HasChiled ? "has_sub narrow" : "")">
        <a href="#">@menu.Title</a>
        @if (menu.HasChiled)
        {
            <div class="second">
                <div class="inner">
                    <ul>
                        @foreach (var menuChild in menu.Children)
                        {
                        <partial name="~/Views/Shared/Components/NavbarViewComponent/_SubMenu.cshtml" model="menuChild" />
                        }
                    </ul>
                </div>
            </div>
        }
    </li>
}
پارشیال ویو SubMenu.cshtml_
@model TR_.Context.Entities.Navbar

<li class="@(Model.HasChiled ? "sub":"")">
    <a href="#">
        @if (Model.Children.Any())
        {<i class="q_menu_arrow fal fa-angle-left"></i>}
        @Model.Title
    </a>
    @if (Model.Children.Any())
    {
        <ul>
            @foreach (var menuChild in Model.Children)
            {
                <partial name="~/Views/Shared/Components/NavbarViewComponent/_SubMenu.cshtml" model="menuChild" />
            }
        </ul>
    }
</li>
با این خروجی :

مطالب
فیلترها در MVC
هنگامی که درخواستی به سرور ارسال می‌گردد، به کنترلر و اکشن مربوطه جهت پاسخگویی هدایت می‌شود. خب شاید مواقعی شما نیاز داشته باشید قبل یا بعد از اجرای اکشن متدی، کدی اجرا گردد. به‌همین جهت در MVC قابلیتی بنام Filter ارائه گردید.
فیلتر، یک کلاس سفارشی است که شما می‌توانید منطق برنامه را جهت اجرا، قبل یا بعد از اجرای یک اکشن متد، در آن پیاده سازی نمایید. فیلترها می‌توانند به یک اکشن متد و یا کنترلری منتسب شوند که در ادامه با این روشها آشنا خواهید شد.

در لیست زیر انواع فیلترها و اینترفیس‌هایی که باید توسط کلاس سفارشی شما پیاده سازی شوند، معرفی شده است.

 نوع توضیح
 فیلتر توکار
 اینترفیس
 Authorization
انجام عملیات احراز هویت و سطح دسترسی، قبل از اجرای کد اکشن متد  
 [Authorize] و [RequireHttps]  
 IAuthorizationFilter 
 Action
اجرای کدهایی قبل از اجرای کدهای اکشن متد 
   IActionFilter 
 Result
اجرای کدهایی قبل یا بعد از تولید ویو (View result) 
 [OutputCache]   IResultFilter 
Exception
اجرای کدهایی در صورت وجود استثنای مدیریت نشده 
[HandleError] 
IExceptionFilter
مثال: هنگامی که خطایی در حین اجرای اکشن متدی رخ می‌دهد، فیلتر توکار MVC بنام HandleError اجرا می‌شود. این فیلتر توکار فایل Error.cshtml را که در فولدر Shared قرار دارد، رندر می‌کند و نمایش می‌دهد.
در تکه کد زیر نحوه‌ی استفاده از این فیلتر را مشاهده می‌کنید:
[HandleError]
public class HomeController : Controller
{
    public ActionResult Index()
    {
        //throw exception for demo
        throw new Exception("This is unhandled exception");
            
        return View();
    }

    public ActionResult About()
    {
        return View();
    }

    public ActionResult Contact()
    {
        return View();
    }        
}

نکته: فیلترهای اعمال شده‌ی به یک کنترلر، به تمام اکشن متدهای آن نیز اعمال می‌گردند. 

در کد بالا خصیصه‌ی HandleError به HomeController اعمال شده است. بنابراین در صورت بروز خطایی در هر کدام از اکشن‌ها، صفحه‌ی Error.cshtml نمایش داده خواهد شد و در تظر داشته باشید که خطاها توسط try-catch هندل نشده‌اند.
باید جهت عملکرد صحیح فیلتر توکار HandleErrorAttribute، مقدار customErrors در قسمت System.web فایل web.config مساوی on باشد.
<customErrors mode="On" />


مهیا کنندگان فیلترها

بصورت پیش فرض MVC از سه طریق زیر فیلترها را جهت استفاده‌ی در برنامه فراهم می‌کند:

  1. خصیصه‌ی GlobalFilters.Filters برای فیلترهای سراسری
  2. کلاس FilterAttributeFilterProvider برای فیلترهای خصیصه‌ای
  3. کلاس ControllerInstanceFilterProvider جهت افزودن کنترلر به یک وهله از FilterProviderCollection

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

ترتیب اجرای فیلترها

همانطور که در ابتدا اشاره شد، در MVC چهار نوع فیلتر معرفی شده است که امکان استفاده‌ی از آنها به‌صورت همزمان در سطح کنترلر و یا اکشن متد وجود دارد. اما ترتیب  اجرای آنها متفاوت و به ترتیب زیر است:

  1. فیلترهای Authorization
  2. فیلترهای Action
  3. فیلترهای Result یا Response
  4. فیلترهای Exception

فیلترها براساس ترتیب اشاره شده‌ی در بالا اجرا خواهند شد. در صورتیکه چند فیلتر از یک نوع استفاده شود، جهت تقدم و تاخر در اجرا، از خاصیت Order استفاده خواهد شد. بعنوان مثال در کد زیر بدلیل خاصیت Order=1 ابتدا AuthorizationFilterB  و سپس AuthorizationFilterA اجرا می‌شود.

[AuthorizationFilterA(Order=2)]
[AuthorizationFilterB(Order=1)]
public ActionResult Index()
{          
    return View();
}
علاوه بر خاصیت Order، مقدار Scope نیز سطح سومی از ترتیب اجرای فیلترها می‌باشد. مقادیر Scope بشرح زیر است:
public enum FilterScope
{
    First = 0,
    Global = 10,
    Controller = 20,
    Action = 30,
    Last = 100,
}
این خصیصه‌ی فیلترها در محل بکار گیری آنها مقدار دهی می‌شود. در صورتیکه فیلتری بصورت سراسری رجیستر شود، Scope آن برابر 10 و در سطح کنترلر، برابر 20 خواهد بود و الی آخر.

نکته: مقدار Scope فیلترهای Authorization برابر 0 و فیلترهای Exception برابر 100 می‌باشد.

ایجاد فیلتر سفارشی

روش اول: پیاده سازی اینترفیس یکی از انواع فیلترها و ارث بری از کلاس FilterAttribute

در این روش متدهایی که باید پیاده سازی شوند متفاوت خواهد بود. به همین جهت متدهای هر نوع بشرح زیر معرفی می‌شود:

  • IAuthorizationFilter
// Called when authorization is required
void OnAuthorization(AuthorizationContext filterContext)
  • IActionFilter
// Called after the action method executes
void OnActionExecuted(ActionExecutedContext filterContext)

// Called before an action method executes
void OnActionExecuting(ActionExecutingContext filterContext)
  • IResultFilter
// Called after an action result executes
void OnResultExecuted(ResultExecutedContext filterContext)

// Called before an action result executes
void OnResultExecuting(ResultExecutedContext filterContext)
  • IException
// Called when an exception occurs
void OnException(ExceptionContext filterContext)

یادآوری: همانطور که در ابتدای مقاله اشاره شد، فیلترها قبل یا بعد از اجرای اکشن متدها فراخوانی خواهند شد. بنابراین به کامنت بالای متد فیلترها دقت داشته باشید.

مثال: پیاده سازی اینترفیس IExceptionFilter و ارث بری از کلاس FilterAttribute جهت تهیه‌ی فیلتری سفارشی از نوع Exception

class CustomErrorHandler : FilterAttribute, IExceptionFilter
{
    public override void IExceptionFilter.OnException(ExceptionContext filterContext)
    {
        Log(filterContext.Exception);

        base.OnException(filterContext);
    }

    private void Log(Exception exception)
    {
        //log exception here..
    }
}

روش دوم:
ارث بری از ActionFilterAttribute
کلاس abstract فوق دارای چهار متد زیر جهت تحریف است. همانطور که مشاهده می‌کنید این کلاس علاوه بر دو متد OnActionExecuted و OnActionExecuting دارای دو متد دیگر OnResultExecuting و OnResultExecuted که به‌ترتیب قبل و بعد خروجی (Result) اکشن متد اجرا می‌شوند، نیز می‌باشد. این نوع فیلترها عموما جنبه‌ی استفاده عمومی داشته و می‌توان از آنها جهت logging ،caching و یا authorization استفاده کرد.
// Called by MVC after the action method executes
void OnActionExecuted(ActionExecutedContext filterContext)

// Called by MVC before the action method executes
void OnActionExecuted(ActionExecutedContext filterContext)

// Called by MVC after the action result executes
void OnResultExecuted(ResultExecutedContext filterContext)

// Called by MVC before the action result executes
void OnResultExecuting(ResultExecutingContext filterContext)

مثال: کلاس LogAttribute که از کلاس ActionFilterAttribute ارث بری کرده است، عملیات قبل و بعد از اجرای اکشن متد را لاگ می‌کند.
public class LogAttribute : ActionFilterAttribute
{
    public override void OnActionExecuted(ActionExecutedContext filterContext)
    {
        Log("OnActionExecuted", filterContext.RouteData); 
    }

    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        Log("OnActionExecuting", filterContext.RouteData);      
    }

    public override void OnResultExecuted(ResultExecutedContext filterContext)
    {
        Log("OnResultExecuted", filterContext.RouteData);      
    }

    public override void OnResultExecuting(ResultExecutingContext filterContext)
    {
        Log("OnResultExecuting ", filterContext.RouteData);      
    }

    private void Log(string methodName, RouteData routeData)
    {
        var controllerName = routeData.Values["controller"];
        var actionName = routeData.Values["action"];
        var message = String.Format("{0}- controller:{1} action:{2}", methodName, 
                                                                    controllerName, 
                                                                    actionName);
        Debug.WriteLine(message);
    }
}

روش سوم:
پیاده سازی داخل کنترلر
کلاس Controller  می‌تواند هر یک از اینترفیس‌های فیلترها را پیاده سازی نماید. به عبارت دیگر در هر کلاس کنترلر می‌توانید متدهای زیر را تحریف نمایید.
  • OnAuthorization ^
  • OnException ^
  • OnActionExecuting ^
  • OnActionExecuted ^
  • OnResultExecuting ^
  • OnResultExecuted ^


روش چهارم: ارث بری از کلاس فیلترهای توکار و مهیای در MVC و تحریف متدهای آن 
در کد زیر با تحریف و سفارشی سازی متد OnException مخصوص فیلتر توکار HandleError، قابلیت‌های آن افزایش یافته است:

class CustomErrorHandler : HandleErrorAttribute
{
    public override void OnException(ExceptionContext filterContext)
    {
        Log(filterContext.Exception);

        base.OnException(filterContext);
    }

    private void Log(Exception exception)
    {
        //log exception here..
    }
}


رجیستر فیلترها

  • سراسری:

درصورتی که قصد داشته باشید فیلتری بصورت سراسری و در کل برنامه فعال گردد باید آن را در رویداد Application_Start فایل Global.asax.cs بوسیله‌ی متد RegisterGlobalFilters کلاس FiterConfig رجیستر نمایید. بعد از آن فیلتر به کلیه‌ی کنترلرها و اکشن متدها اعمال می‌گردد.

public class MvcApplication : System.Web.HttpApplication
{
    protected void Application_Start()
    {
          FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
    }
}

// FilterConfig.cs located in App_Start folder 
public class FilterConfig
{
    public static void RegisterGlobalFilters(GlobalFilterCollection filters)
    {
        filters.Add(new HandleErrorAttribute());

        // add your new custom filters
        filters.Add(new LogAttribute()); 
        filters.Add(new CustomErrorHandler());
     }
}

در کد بالا فیلتر توکار HandleError و البته فیلترهای سفارشی دیگری نیز به صورت سراسری به تمام اکشن متدهای کنترلرها اعمال گردیده است.

  • کنترلر: در صورتی که فقط بخواهید یک فیلتر به کل اکشن‌های یک کنترلر اعمال گردد. همانند آنچه که در مثال ابتدایی بدان اشاره شد.
[HandleError]
public class HomeController : Controller
  • اکشن متد: اعمال یک فیلتر به یک اکشن متد خاص کنترلر. در کد زیر فیلتر HandleError فقط به اکشن متد Index کنترلر Home اعمال خواهد شد.
public class HomeController : Controller
{
    [HandleError]
    public ActionResult Index()
    {
        return View();
    }
}
مطالب
معرفی Lex.Db
Lex.Db یک بانک اطلاعاتی درون پروسه‌ای (مدفون شده یا embedded) بسیار سریع نوشته شده با سی‌شارپ است. این بانک اطلاعاتی کم حجم، سورس باز بوده و مجوز استفاده از آن LGPL است. به این معنا که استفاده از اسمبلی‌های آن در هر نوع پروژه‌ای آزاد است.
نکته مهم آن سازگاری با برنامه‌های دات نت 4 به بعد، همچنین برنامه‌های ویندوز 8، سیلورلایت 5، ویندوز فون 8 و همچنین اندروید (از طریق Mono) است. به علاوه چون با دات نت تهیه شده است، دیگر نیازی نیست دو نگارش 32 بیتی و 64 بیتی آن توزیع شوند و به این ترتیب مشکلات توزیع بانک‌های اطلاعاتی native مانند SQLite را ندارد ( و مطابق ادعای نویسنده آلمانی آن، از SQLite سریعتر است).
API این بانک اطلاعاتی، هر دو نوع متدهای synchronous  و  asynchronous را شامل می‌شود؛ به همین جهت با برنامه‌های ویندوز 8 و سیلورلایت نیز سازگاری دارد.
Lex.Db از برنامه‌های چندریسمانی و همچنین استفاده از یک بانک اطلاعاتی آن توسط چندین پروسه همزمان نیز پشتیبانی می‌کند.
در ادامه مروری خواهیم داشت بر نحوه استفاده از آن در حالت طراحی رابطه‌ای؛ از این جهت که فعلا به ظاهر این بانک اطلاعاتی روابط را پشتیبانی نمی‌کند، اما در عمل پیاده سازی آن مشکل نیست.

دریافت Lex.Db

برای دریافت Lex.Db، دستور ذیل را در خط فرمان پاورشل نیوگت وارد نمائید:
 PM> Install-Package Lex.Db
بسته به نوع پروژه شما (دات نت یا WinRT یا ...)، اسمبلی متناسبی به پروژه اضافه خواهد شد.


مدل‌های برنامه

    public class Product
    {
        public int Id { get; set; }
        public string Name { get; set; }
    }

    public class Customer
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public string City { get; set; }
    }

    public class Order
    {
        public int Id { get; set; }
        public int? CustomerFK { get; set; }
        public int[] ProductsFK { get; set; }
    }
مدل‌های برنامه آزمایشی مطلب جاری را در اینجا ملاحظه می‌کنید. برای طراحی روابط یک به صفر یا یک و همچنین یک به چند، تنها کافی است کلیدهای اصلی یا آرایه‌ای از کلیدهای اصلی مرتبط را در اینجا ذخیره کنیم، که نمونه‌ای از آن‌را در کلاس Order ملاحظه می‌کنید.


آغاز بانک اطلاعاتی

    public static class Database
    {
        public static DbInstance Instance { get; private set; }

        public static DbTable<Product> Products { get; private set; }
        public static DbTable<Order> Orders { get; private set; }
        public static DbTable<Customer> Customers { get; private set; }

        /// <summary>
        /// سازنده استاتیکی که در طول عمر برنامه فقط یکبار اجرا می‌شود
        /// </summary>
        static Database()
        {
            createDb();
            getTables();
        }

        private static void getTables()
        {
            Products = Instance.Table<Product>();
            Customers = Instance.Table<Customer>();
            Orders = Instance.Table<Order>();
        }

        private static void createDb()
        {
            Instance = new DbInstance(Path.Combine(Environment.CurrentDirectory, "LexDbTests"));

            Instance.Map<Product>()
                    .WithIndex("NameIdx", x => x.Name)
                    .Automap(i => i.Id, true);

            Instance.Map<Order>()
                    .Automap(i => i.Id, true);

            Instance.Map<Customer>()
                    .WithIndex("NameIdx", x => x.Name)
                    .WithIndex("CityIdx", x => x.City)
                    .Automap(i => i.Id, true);

            Instance.Initialize();
        }
    }
کلاس دیتابیس و سازنده آن، استاتیک تعریف شده‌اند؛ تا در طول عمر برنامه تنها یکبار وهله سازی شوند. new DbInstance یک وهله جدید از بانک اطلاعاتی را آغاز می‌کند. سازنده آن، مسیر پوشه‌ای که فایل‌های این بانک اطلاعاتی در آن ذخیره خواهند شد را دریافت می‌کند. Lex.Db به ازای هر کلاس مدلی که به آن معرفی شود، دو فایل data و index را ایجاد می‌کند.
سپس توسط وهله‌ای از بانک اطلاعاتی که ایجاد کردیم، کار معرفی خواص مدل‌های برنامه توسط متد Map و Automap انجام می‌شود. متد Automap خاصیت primary key کلاس را دریافت کرده و همچنین پارامتر دوم آن مشخص می‌کند که آیا این کلید اصلی به صورت خودکار ایجاد شود یا خیر. به علاوه در همینجا می‌توان روی فیلدهای مختلف، ایندکس نیز ایجاد کرد. متد WithIndex یک نام دلخواه را دریافت کرده و سپس خاصیتی را که باید بر روی آن ایندکس ایجاد شود، دریافت می‌کند.
در نهایت متد Initialize باید فراخوانی گردد. البته اگر برنامه شما WinRT است، این متد Initialize Async خواهد بود.
جداول نیز بر اساس مدل‌های برنامه از طریق متد Instance.Table در دسترس قرار گرفته‌اند.

افزودن اطلاعات به بانک اطلاعاتی
        private static void addData()
        {
            var customer1 = new Customer { Name = "customer1", City = "City1" };
            var customer2 = new Customer { Name = "customer2", City = "City2" };
            Database.Instance.Save(customer1, customer2); // automatic Id assignment after Save

            var product1 = new Product { Name = "product1" };
            var product2 = new Product { Name = "product2" };
            Database.Instance.Save(product1, product2); // automatic Id assignment after Save

            var order1 = new Order { CustomerFK = customer1.Id, ProductsFK = new[] { product1.Id } };
            var order2 = new Order { CustomerFK = customer2.Id, ProductsFK = new[] { product1.Id, product2.Id } };
            Database.Instance.Save(order1, order2); // automatic Id assignment after Save
        }
اکنون که کار آغاز بانک اطلاعاتی صورت گرفت، برای افزودن اطلاعات از متد Database.Instance.Save می‌توان استفاده کرد (در برنامه‌های WinRT از  متد Save Async استفاده کنید).
در اینجا نیازی به ذکر Id نمونه‌های ساخته شده نیست؛ از این جهت که در حین عملیات Save، به صورت خودکار انتساب خواهند یافت.
همچنین نحوه مقدار دهی کلیدهای خارجی نیز با استفاده از همین کلیدهای اصلی آماده شده است.


واکشی تمام اطلاعات

        private static void loadAll()
        {
            var orders = Database.Orders.LoadAll();
            foreach (var order in orders)
            {
                // نحوه دریافت اطلاعات مشتری بر اساس کلید خارجی ثبت شده
                var orderCustomer = Database.Customers.LoadByKey(order.CustomerFK.Value);
                Console.WriteLine("Order Id: {0}, Customer: {1} ({2}) {3}", order.Id, orderCustomer.Name, orderCustomer.Id, orderCustomer.City);

                // نحوه بازیابی لیستی از اشیاء مرتبط از طریق آرایه‌ای از کلیدهای خارجی ثبت شده
                var orderProducts = Database.Products.LoadByKeys(order.ProductsFK);
                foreach (var product in orderProducts)
                {
                    Console.WriteLine("  Product Id: {0}, Name: {1}", product.Id, product.Name);
                }
            }
        }
بانک اطلاعاتی آغاز شد؛ تعدادی رکورد نیز در آن ثبت گردید. اکنون برای بازیابی اطلاعات می‌توان از متدهای در دسترس جداول کلاس Database استفاده کرد. برای مثال متد LoadAll تمام رکوردهای یک جدول را واکشی می‌کند (در برنامه‌های WinRT این متد LoadAll Async خواهد بود).
سپس با استفاده از متدهای LoadByKey و LoadByKeys، به سادگی می‌توان اشیاء مرتبط با هر سفارش را نیز واکشی کرد.


استفاده از ایندکس‌ها برای کوئری گرفتن

        private static void queryingByAnIndex()
        {
            var name = "customer1";
            var customersList = Database.Customers
                                        .IndexQueryByKey("NameIdx", name)
                                        .ToList();
            foreach (var person in customersList)
            {
                Console.WriteLine(person.Name);
            }
        }
در ابتدای بحث، توسط متد WithIndex، تعدادی ایندکس را نیز تعریف کردیم. اکنون توسط این ایندکس‌ها و متد IndexQueryByKey، می‌توان کوئری‌هایی بسیار سریع را تهیه کرد.
            // Using Take and Skip
            var list1 = Database.Orders.Query<int>() // primary idx
                                       .Take(1).Skip(2).ToList();

            // Querying Between Ranges 
            var list2 = Database.Customers
                                .IndexQuery<string>("NameIdx")
                                .GreaterThan("a", orEqual: true).LessThan("d").ToList();
همچنین در اینجا متدهایی مانند Take و Skip و یا جستجو در یک بازه توسط متدهای GreaterThan و LessThan نیز پشتیبانی می‌شوند.


حذف رکوردها
        private static void deletingRecords()
        {
            Database.Customers.DeleteByKey(key: 1);

            var customers = Database.Customers.LoadByKeys(new[] { 1, 2 });
            Database.Customers.Delete(customers);
        }
برای حذف رکوردها از متدهای DeleteByKey و یا Delete می‌توان استفاده کرد. متد Delete می‌تواند آرایه‌ای از اشیاء را نیز قبول کند.
و اگر خواستید کل بانک اطلاعاتی را خالی کنید، متد Database.Instance.Purge اینکار را انجام خواهد داد.


کدهای کامل این مثال را از اینجا نیز می‌توانید دریافت کنید:
Program-LexDb.cs