مطالب
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


مطالب
Value Types ارجاعی در C# 7.2
در C# 7.2 می‌توان با value types (مانند structs) همانند reference types (مانند کلاس‌ها) رفتار کرد. جائیکه کارآیی برنامه بسیار حائز اهمیت باشد (مانند بازی‌ها)، استفاده از structs و value types بسیار مرسوم است؛ از این جهت که این نوع‌ها بر روی heap تخصیص داده نمی‌شوند. اما مشکل آن‌ها این است که زمانیکه به متدها ارسال می‌شوند، مقدار آن‌ها ارسال خواهد شد و برای این منظور نیاز به ایجاد یک کپی جدید از آن‌ها می‌باشد. برای رفع این مشکل و کاهش سربار کپی کردن اشیاء، اکنون در C# 7.2 می‌توان value types را همانند reference types به متدها ارسال کرد.


واژه‌ی کلیدی جدید in

C# 7.2‌، واژه‌ی کلیدی جدیدی را به نام in جهت تعریف پارامترها، معرفی کرده‌است. زمانیکه از آن استفاده می‌شود به این معنا است که value type ارسالی به آن، توسط ارجاعی از آن، در اختیار متد قرار می‌گیرد و نه توسط مقدار کپی شده‌ی آن (حالت پیش‌فرض) و همچنین متد استفاده کننده‌ی از آن، مقدار این شیء را تغییر نمی‌دهد.
واژه‌ی کلیدی in مکمل واژه‌های کلیدی ref و out است که پیشتر به همراه زبان #C ارائه شده بودند:
- واژه‌ی کلیدی out: مقدار آرگومان مزین شده‌ی توسط آن، باید درون متد تنظیم شود و صرفا کاربرد ارائه‌ی یک خروجی اضافه‌تر توسط آن متد را دارد.
- واژه‌ی کلیدی ref: مقدار آرگومان مزین شده‌ی توسط آن، ممکن است درون متد تنظیم شود، یا خیر و همچنین توسط ارجاع به آن منتقل می‌شود.
- واژه‌ی کلیدی in: مقدار آرگومان مزین شده‌ی توسط آن، درون متد تغییر نخواهد کرد و همچنین توسط ارجاع به آن منتقل می‌شود.

برای مثال اگر پارامترهای value type متد زیر را از نوع in معرفی کنیم، امکان تغییر مقدار آن‌ها درون متد وجود نخواهد داشت:
public static int Add(in int number1, in int number2)
{
   number1 = 5; // Cannot assign to variable 'in int' because it is a readonly variable
   return number1 + number2;
}
و کامپایلر با صدور خطای readonly بودن پارامتر number1، از انجام اینکار جلوگیری می‌کند


واژه‌ی کلیدی جدید in تا چه اندازه‌ای بر روی کارآیی برنامه تاثیر دارد؟

زمانیکه یک value type را به متدی ارسال می‌کنیم، ابتدا به مکان جدیدی از حافظه کپی شده و سپس مقدار clone شده‌ی آن، به متد ارسال می‌شود. با استفاده از واژه‌ی کلیدی in، دقیقا همان ارجاع به مقدار اولیه، به متد ارسال خواهد شد؛ بدون ایجاد کپی اضافه‌تری از آن. برای بررسی تاثیر این عملیات بر روی کارآیی برنامه، می‌توان از BenchmarkDotNet استفاده کرد. برای این منظور ابتدا ارجاعی را به BenchmarkDotNet اضافه می‌کنیم:
<ItemGroup>
     <PackageReference Include="BenchmarkDotNet" Version="0.10.12" />
</ItemGroup>
سپس متدهایی را که قرار است کارآیی آن‌ها بررسی شوند، با ویژگی Benchmark مزین خواهیم کرد:
using BenchmarkDotNet.Attributes;

namespace CS72Tests
{
    public struct Input
    {
        public decimal Number1;
        public decimal Number2;
    }

    [MemoryDiagnoser]
    public class InBenchmarking
    {
        const int loops = 50000000;
        Input inputInstance = new Input();

        [Benchmark(Baseline = true)]
        public decimal RunNormalLoop_Pass_By_Value()
        {
            decimal result = 0M;
            for (int i = 0; i < loops; i++)
            {
                result = Run(inputInstance);
            }
            return result;
        }

        [Benchmark]
        public decimal RunInLoop_Pass_By_Reference()
        {
            decimal result = 0M;
            for (int i = 0; i < loops; i++)
            {
                result = RunIn(in inputInstance);
            }
            return result;
        }

        public decimal Run(Input input)
        {
            return input.Number1;
        }

        public decimal RunIn(in Input input)
        {
            return input.Number1;
        }
    }
}
در آخر برای اجرای آن خواهیم داشت:
static void Main(string[] args)
{
    var summary = BenchmarkRunner.Run<InBenchmarking>();
و در این حالت برنامه را باید توسط دستور «dotnet run -c release» اجرا کرد (اندازه گیری کارآیی در حالت release و نه دیباگ پیش‌فرض)
با این خروجی نهایی:
                      Method |      Mean |    Error |   StdDev | Scaled | Allocated |
---------------------------- |----------:|---------:|---------:|-------:|----------:|
 RunNormalLoop_Pass_By_Value | 280.04 ms | 2.219 ms | 1.733 ms |   1.00 |       0 B |
 RunInLoop_Pass_By_Reference |  91.75 ms | 1.733 ms | 1.780 ms |   0.33 |       0 B |
همانطور که ملاحظه می‌کنید، کارآیی برنامه در حالت استفاده‌ی از پارامترهای in، حداقل 3 برابر شده‌است.


امکان استفاده‌ی از واژه‌ی کلیدی in در حین تعریف متدهای الحاقی

در حین تعریف متدهای الحاقی، واژه‌ی کلیدی in باید پیش از واژه‌ی کلیدی this ذکر شود:
    public static class Factorial
    {
        public static int Calculate(in this int num)
        {
            int result = 1;
            for (int i = num; i > 1; i--)
                result *= i;

            return result;
        }
    }
در این حالت اگر برنامه را به صورت زیر اجرا کنیم (یکبار با ذکر صریح in، بار دیگر بدون in و یکبار هم به صورت فراخوانی متد الحاقی بر روی عدد):
int num = 3;
Console.WriteLine($"(in num) -> {Factorial.Calculate(in num)}");
Console.WriteLine($"(num) -> {Factorial.Calculate(num)}");
Console.WriteLine($"num. -> {num.Calculate()}");
خروجی‌های ذیل را دریافت خواهیم کرد:
(in num) -> 6
(num) -> 6
num. -> 6
به عبارتی حین فراخوانی و استفاده‌ی از متدی که پارامتر آن به صورت in تعریف شده‌است، ذکر in ضروری نیست.

و به طور کلی استفاده‌ی از in در مکان‌های ذیل مجاز است:
• methods
• delegates
• lambdas
• local functions
• indexers
• operators
 

محدودیت‌های استفاده‌ی از پارامترهای in

الف) محدودیت استفاده از پارامترهای in در تعریف overloads
مثال زیر را در نظر بگیرید:
    public class CX
    {
        public void A(Input a)
        {
            Console.WriteLine("int a");
        }

        public void A(in Input a)
        {
            Console.WriteLine("in int a");
        }
    }
در اینجا overloadهای تعریف شده‌ی متد A تنها در ذکر واژه‌ی کلیدی in یا modifier متفاوت هستند.
اگر سعی کنیم وهله‌ای از این کلاس را ایجاد کرده و از متدهای A آن استفاده کنیم:
    public class Y
    {
        public void Test()
        {
            var inputInstance = new Input();
            var cx = new CX();
            cx.A(inputInstance); // The call is ambiguous between the following methods or properties: 'CX.A(Input)' and 'CX.A(in Input)'
        }
    }
خطای کامپایلر مبهم بودن متد A مورد استفاده صادر خواهد شد. یعنی نمی‌توان overload ایی را تعریف کرد که تنها در modifier از نوع in با دیگری متفاوت باشد؛ چون ذکر in در حین فراخوانی متد، اختیاری است.

ب) پارامترهای از نوع in را در متدهای iterator نمی‌توان استفاده کرد:
public IEnumerable<int> B(in int a) // Iterators cannot have ref or out parameters
{
   Console.WriteLine("in int a");
   yield return 1;
}

ج) پارامترهای از نوع in را در متدهای async نمی‌توان استفاده کرد:
public async Task C(in int a) // Async methods cannot have ref or out parameters
{
   await Task.Delay(1000);
}


تاثیر کار با متدهای داخلی تغییر دهنده‌ی وضعیت یک struct

مثال زیر را درنظر بگیرید. به نظر شما خروجی آن چیست؟
using System;

namespace CS72Tests
{
    struct MyStruct
    {
        public int MyValue { get; set; }

        public void UpdateMyValue(int value)
        {
            MyValue = value;
        }
    }

    public static class TestInStructs
    {
        public static void Run()
        {
            var myStruct = new MyStruct();
            myStruct.UpdateMyValue(1);

            UpdateMyValue(myStruct);

            Console.WriteLine(myStruct.MyValue);
        }

        static void UpdateMyValue(in MyStruct myStruct)
        {
            myStruct.UpdateMyValue(5);
        }
    }
}
در اینجا اگر متد TestInStructs.Run را اجرا کنیم، خروجی آن، نمایش عدد 1 خواهد بود.
در ابتدا مقدار struct را به 1 تنظیم و سپس ارجاع آن‌را به متدی دیگر که مقدار آن‌را به 5 تنظیم می‌کند، ارسال کردیم. در این حالت برنامه بدون مشکل کامپایل و اجرا می‌شود.  علت اینجا است که کامپایلر #C زمانیکه متدی را در داخل یک struct فراخوانی می‌کند، یک clone از آن struct را ایجاد کرده و متد را بر روی آن clone اجرا می‌کند؛ چون نمی‌داند که آیا این متد وضعیت و مقدار این struct را تغییر می‌دهد یا خیر. در این حالت کپی اصلی بدون تغییر باقی می‌ماند (در نهایت عدد 1 را مشاهده خواهیم کرد)، اما در آخر فراخوان، ارجاعی از struct را دریافت نکرده و بر روی کپی آن کار می‌کند. بنابراین مزیت بهبود کارآیی، از دست خواهد رفت.

البته در اینجا اگر می‌خواستیم مقدار MyValue را مستقیما تغییر دهیم، کامپایلر از آن جلوگیری می‌کرد و این کد هیچگاه کامپایل نمی‌شد:
static void UpdateMyValue(in MyStruct myStruct)
{
   myStruct.MyValue = 5; // Cannot assign to a member of variable 'in MyStruct' because it is a readonly variable
   myStruct.UpdateMyValue(5);
}
مطالب
کار با Razor در ASP.NET Core 2.0
پیش نویس: این مقاله ترجمه شده فصل 5 کتاب Pro Asp.Net Core MVC2 می‌باشد.


ایجاد یک پروژه با استفاده Razor

در ادامه با هم یک مثال را با استفاده از Razor ایجاد می‌کنیم. یک پروژه جدید را با قالب Empty و با نام Razor ایجاد می‌کنیم.

مراحل:

1- ابتدا در کلاس startup قابلیت MVC را فعال می‌کنیم؛ با قرار دادن کد زیر در متد ConfigureServices:
 services.AddMvc();
و بعد کد زیر را که مربوط به اجرای پروژه‌ی hello Word است ، از متد Configure حذف می‌کنیم:
app.Run(async (context) =>
{
   await context.Response.WriteAsync("Hello World!");
});
در نهایت محتویات  فایل StartUp به صورت زیر می‌باشد:

namespace Razor
{
    public class Startup
    {
        // This method gets called by the runtime. Use this method to add services to the container.
        // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc();
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            //app.Run(async (context) =>
            //{
            //    await context.Response.WriteAsync("Hello World!");
            //});
        }
    }
}


ایجاد یک Model
 یک پوشه جدید را به نام Models ایجاد و بعد در این پوشه یک کلاس را به نام Product ایجاد می‌کنیم و کدهای زیر را در آن قرار میدهیم:
namespace Razor.Models
{
    public class Product
    {
        public int ProductID { get; set; }
        public string Name { get; set; }
        public string Description { get; set; }
        public decimal Price { get; set; }
        public string Category { set; get; }
    }
}

ایجاد Controller
تنظیمات پیشفرض را در فایل Startup انجام داده‌ایم. درخواست‌هایی را که توسط کاربر ارسال میشوند، به controller پیشفرضی که نامش در اینجا Home است، ارسال می‌کند. حالا ما یک پوشه جدید را به نام Controllers ایجاد می‌کنیم و در آن یک کنترلر جدید را به نام HomeController ایجاد می‌کنیم و کدهای زیر را در آن قرار میدهیم:
namespace Razor.Controllers
{
    public class HomeController : Controller
    {
        // GET: /<controller>/
        public ViewResult Index()
        {
            Product myProduct = new Product
            {
                ProductID = 1,
                Name = "Kayak",
                Description = "A boat for one person",
                Category = "Watersports",
                Price = 275M
            };
            return View(myProduct);
        }
    }
}
در این کلاس یک Action Method را به نام index ایجاد می‌کنیم. سپس در آن یک شیء را از مدل ایجاد و مقدار دهی و آن‌را به View ارسال می‌کنیم تا در زمان بارگذاری View از این شیء استفاده نماییم. نیاز نیست نام View را مشخص کنید. به صورت پیشفرض نام View با نام اکشن متد یکسان می‌باشد.

 
ایجاد View
 برای ایجاد یک View پیشفرض برای Action Method فوق در پوشه Views/Home یک MVC View Page (Razor View Page) را به نام Index.schtml ایجاد می‌کنیم.
- نکته1: پوشه View و داخل آن Home را ایجاد کنید.
- نکته2: معادل MVC View Page در نسخه جدید، Razor View می‌باشد. اگر در لیست این آیتم را انتخاب کنید، در توضیحات پنل سمت راست میتوانید این مطلب را مشاهده کنید.
- نکته3: دقت نمایید برای اینکه پروژه net Core2. باشد و تمام مشخصات موردنظر را داشته باشد، باید نگارش ویژوال استودیو VS 2017.15.6.6 و یا بیشتر باشد.
 
@model Razor.Models.Product
@{
    Layout = null;
}

<!DOCTYPE html>
<html>
<head>
    <meta name="viewport" content="width=device-width"/>
    <title>Index</title>
</head>
<body>
    Content will go here
</body>
</html>

تا اینجا ما یک پروژه ساده را ایجاد نموده‌ایم که قابلیت استفاده‌ی از Razor را هم دارد. در ادامه نحوه‌ی استفاده از امکانات Razor شرح داده میشوند.


استفاده از Model در یک View
برای استفاده از شیء مدل در View، باید در View به آن شیء و مشخصات آن دسترسی داشته باشیم که این دسترسی را Razor با استفاده از کاراکتر @ برای ما ایجاد می‌کند. برای اتصال به Model از عبارت model@ (حتما باید حروف کوچک باشد) استفاده می‌کنیم و برای دسترسی به مشخصات مدل از عبارت Model@ (حتما باید حرف اول آن بزرگ باشد) استفاده می‌کنیم. به کد زیر دقت کنید:

@model Razor.Models.Product
@{
    Layout = null;
}

<!DOCTYPE html>
<html>
<head>
    <meta name="viewport" content="width=device-width"/>
    <title>Index</title>
</head>
<body>
  @Model.Name
</body>
</html>
خط اولی که در View تعریف شده است، با استفاده از عبارت model@ مانند تعریف نوع مدل می‌باشد و کار اتصال مدل به View را انجام میدهد و همین خط باعث میشود زمانی که شما در تگ body عبارت Model@ وبعد دات (.) را میزنید، لیست خصوصیات آن مدل ظاهر میشوند. لیست شدن خصوصیات بعد از دات(.) یکی از کارهای پیشفرض ویژوال استودیو می‌باشد؛ برای اینکه از خطاهای احتمالی کاربر جلوگیری کند.

نتیجه خروجی بالا مانند زیر می‌باشد:

 



معرفی View Imports

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

در پوشه View راست کلیک کرده و گزینه Add و بعد New Item را انتخاب می‌کنیم و در کادر باز شده، آیتم MVC View Import Page (در نسخه جدید نام آن  Razor View Imports است) انتخاب می‌کنیم. ویژوال استودیو به صورت پیش فرض نام ViewImports.cshtml_ را برای آن قرار میدهد.


نکته: استاندارد نام گذاری این View این می‌باشد که ابتدای آن کاراکتر (_) حتما وجود داشته باشد.
 
در کلاس تعریف شده با استفاده از عبارت using@ فضای نام‌های خود را قرار میدهیم؛ مانند زیر:
 @using Razor.Models
در این کلاس شما فقط میتوانید فضاهای نام را مانند بالا قرار دهید. پس از آم قسمت فضاهای نام اضافی در Viewها قابل حذف میشوند و در این حالت فقط نام کلاس مدل را در بالای فرم قرار میدهیم مانند زیر:
@model Product
@{
    Layout = null;
}

<!DOCTYPE html>
<html>
<head>
    <meta name="viewport" content="width=device-width"/>
    <title>Index</title>
</head>
<body>
  @Model.Name
</body>
</html>


Layout ها

یکی دیگر از عبارت‌های مهم Razor که در فایل Index وجود دارد، عبارت زیر است:
@{
    Layout = null;
}
شما می‌توانید در بین {} کدهای سی شارپ را قرار دهید. حالا مقدار Layout را مساوی نال قرار داده‌ایم که بگوییم View مستقلی است و از قالب مشخصی استفاده نمی‌کند.

از Layout برای طراحی الگوی Viewها استفاده می‌کنیم. اگر بخواهیم برای View ها یک قالب طراحی کنیم و این الگو بین تمام یا چندتای از آن‌ها مشترک باشد، کدهای مربوط به الگو را با استفاده از Layout ایجاد می‌کنیم و از آن در View ها استفاده می‌کنیم. اینکار برای جلوگیری از درج کدهای تکراری قالب در برنامه انجام میشود. با اینکار اگر بخواهیم در الگو تغییری را انجام دهیم، این تغییر را در یک قسمت انجام میدهم و سپس به تمام Viewها اعمال میشود.
 
Layout
طرحبندی  Viewهای برنامه بطور معمول بین چند View مشترک است و طبق استاندارد ویژوال استودیو در پوشه‌ی Views/Shared قرار میگیرد. برای ایجاد Layout، روی پوشه Views/shared راست کلیک کرده و بعد گزینه Add وبعد NewItem و سپس گزینه MVC View Layout Page (نام آن در نسخه جدید Razor Layout است) را انتخاب می‌کنیم و ابتدای نام آن را به صورت پیشفرض کاراکتر (_) قرار میدهیم.
 


هنگام ایجاد این فایل توسط ویژوال استودیو، کدهای زیر به صورت پیش فرض در فایل ایجاد شده وجود دارند: 
<!DOCTYPE html>

<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>@ViewBag.Title</title>
</head>
<body>
    <div>
        @RenderBody()
    </div>
</body>
</html>
طرحبندی‌ها فرم خاصی از View هستند و دو عبارت @ در کدهای آن وجود دارد. در اینجا فراخوانی RenderBody@ سبب درج محتویات View مشخص شده توسط Action Method در این مکان می‌شود. عبارت دیگری که در اینجا وجود دارد، ViewBag است که برای مشخص کردن عنوان در اینجا استفاده شده‌است.
ViewBag ویژگی مفیدی است که اجازه می‌دهد تا مقادیر و داده‌ها در برنامه گردش داشته باشند و در این مورد بین یک View و Layout منتقل شوند. در ادامه خواهید دید وقتی Layout را به یک نمایه اعمال می‌کنیم، این مورد چگونه کار می‌کند.

عناصر HTML در یک Layout به هر View که از آن استفاده می‌کند، اعمال و توسط آن یک الگو برای تعریف محتوای معمولی ارائه می‌شود؛ مانند کدهای زیر. من برخی از نشانه گذاری‌های ساده را به Layout اضافه کردم تا اثر قالب آن آشکارتر شود:
<!DOCTYPE html>

<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>@ViewBag.Title</title>
    <style>
        #mainDiv {
            padding: 20px;
            border: solid medium black;
            font-size: 20pt
        }
    </style>
</head>
<body>
    <h1>Product Information</h1>
    <div id="mainDiv">
        @RenderBody()
    </div>
</body>
</html>
در اینجا یک عنصر عنوان و همچنین بعضی از CSS‌ها را به عنصر div که حاوی عبارت RenderBody@ است، اضافه کرده‌ام؛ فقط برای اینکه مشخص شود، چه محتوایی از طرحبندی سایت می‌آید و چه چیزی از View.
 

اعمال Layout

برای اعمال کردن Layout به یک View، نیاز است مشخصه Layout آن‌را مقدار دهی و سپس Htmlهای اضافی موجود در آن‌را مانند المنت‌های head و Body حذف کنید؛ همانند کدهای زیر:
@model Product
@{
    Layout = "_BasicLayout";
    ViewBag.Title = "Product";
}
در خاصیت Layout، مقدار را برابر نام فایل Layout، بدون پسوند cshtml آن قرار میدهیم. Razor در مسیر پوشه Views/shared و پوشه Views/Home فایل Layout را جستجو می‌کند.
در اینجا عبارت ViewBag.Title را نیز مقدار دهی می‌کنیم. زمانیکه فایل فراخوانی میشود، عنوان آن صفحه با این مقدار، جایگزین خواهد شد.
تغییرات این View بسیار چشمگیر است؛ حتی برای چنین برنامه ساده‌ای. طرحبندی شامل تمام ساختار مورد نیاز برای هر پاسخ HTML است که View را به صورت یک محتوای پویا ارائه می‌دهد و داده‌ها را به کاربر منتقل می‌کند. هنگامیکه MVC فایل Index.cshtmal را پردازش می‌کند، این طرحبندی برای ایجاد پاسخ HTML نهایی یکپارچه می‌شود؛ مانند عکس زیر:
 


 
View Start

بعضی موارد هنوز در برنامه وجود دارند که می‌توان کنترل بیشتری بر روی آن‌ها داشته باشید. مثلا اگر بخواهیم نام یک فایل layout را تغییر دهیم، مجبور هستیم تمام Viewهایی را که از آن Layout استفاده می‌کنند، پیدا کنید و نام Layout استفاده شده در آن‌ها را تغییر دهیم. اینکار احتمال خطای بالایی دارد و امکان دارد بعضی View ها از قلم بیفتند و برنامه دچار خطا شود. بنابراین با استفاده از View Start می‌توانیم این مشکل را برطرف کنیم. وقتی نام Layout تغییر کرد، تنها کافی است نام آن‌را در View Start تغییر دهیم. اکنون زمانیکه برنامه را اجرا می‌کنیم، MVC به دنبال فایل View Start می‌گردد و اگر اطلاعاتی داشته باشد، آن را اجرا می‌کند و الویت این فایل از تمام فایل‌های دیگر بیشتر است و ابتدا تمام آنها اجرا میشوند.

برای ایجاد یک فایل شروع مشاهده، روی پوشه‌ی Views کلیک راست کرده و گزینه add->New Items را انتخاب می‌کنیم و از پنجره باز شده گزینه ( Razor View Start ) Mvc View Start Page را انتخاب می‌کنیم؛ مانند تصویر زیر:


ویژوال استودیو به صورت پیش فرض نام ViewStart.cshtml_ را به عنوان نام آن قرار میدهد؛ شما گزینه‌ی Create را در این حالت انتخاب کنید. محتویات فایل ایجاد شده به صورت زیر می‌باشد:
@{
    Layout = "_Layout";
}
برای اعمال Layout جدید به تمام Viewها، مقدار Layout را معادل طرحبندی خود تغییر میدهیم؛ مانند کد زیر: 
@{
    Layout = "_BasicLayout";
}
از آنجا که فایل View Start دارای مقداری برای Layout می‌باشد، می‌توانیم عبارت‌های مربوطه را در Index.cshtml‌ها حذف کنیم:
@model Product
@{
    ViewBag.Title = "Product";
}
در اینجا لازم نیست مشخص کنیم که من می‌خواهم از فایل View Start استفاده کنم. MVC این فایل را پیدا خواهد کرد و از محتویات آن به طور خودکار استفاده می‌کند. البته باید دقت داشت که مقادیر تعریف شده‌ی در فایل View اولویت دارند و باعث میشوند با معادل‌های فایل View Start جایگزین شوند.

شما همچنین می‌توانید چندین فایل View Start را برای تنظیم مقادیر پیش فرض قسمت‌های مختلف برنامه، استفاده کنید. یک فایل Razor همواره توسط نزدیک‌ترین فایل View start، پردازش می‌شود. به این معنا که شما می‌توانید تنظیمات پیش فرض را با افزودن یک فایل View Start به پوشه Views / Home و یا Views / Shared لغو کنید.

نکته: درک تفاوت میان حذف محتویات فایل View Start یا مساوی Null قرار دادن آن مهم است. اگر View شما مستقل است و شما نمی‌خواهید از آن استفاده کنید، بنابراین مقدار Layout آن‌را صریحا برابر Null قرار دهید. اگر مقدار دهی صریح شما مشخصه Layout را نادیده بگیرید، Mvc فرض می‌کند که میخواهید layout را داشته باشید و مقدار آن را از فایل View Start تامین می‌کند.
 

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

کامپوننت 
انجام میشود 
انجام نمیشود 
  Action Method    یک شیء ViewModel را به View ارسال می‌کند.
  یک فرمت داده را به View ارسال می‌کند.
  View    از شیء ViewModel برای ارائه محتوا به کاربر استفاده می‌کند.
  هر جنبه‌ای از شیء View Model مشخصات را تغییر می‌دهد.
 
برای به دست آوردن بهترین نتیجه از MVC، نیاز به تفکیک و جداسازی بین قسمت‌های مختلف برنامه را دارید. همانطور که می‌بینید، می‌توانید کاملا با Razor کار کنید و این نوع فایل‌ها شامل دستورالعمل‌های سی شارپ نیز هستند. اما شما نباید از Razor برای انجام منطق کسب و کار استفاده کنید و یا هر گونه اشیاء Domain Model خود را دستکاری کنید. کد زیر نشان میدهد که یک عبارت جدید به View اضافه میشود:
*@
@model Product
@{

    ViewBag.Title = "Product";
}
<p>Product Name: @Model.Name</p> <p>Product Price: @($"{Model.Price:C2}")</p>
می‌توان برای خصوصیت price، در اکشن متد فرمتی را تعریف و بعد آن را به View ارسال کنیم. این روش کار می‌کند، اما استفاده از این رویکرد منافع الگوی MVC را تضعیف می‌کند و توانایی من برای پاسخ دادن به تغییرات در آینده را کاهش می‌دهد. باید به یاد داشته باشید که در ASP NET Core MVC، استفاده مناسب از الگوی MVC اجتناب ناپذیر است و شما باید از تاثیر تصمیمات طراحی و کدگذاری که انجام می‌دهید مطلع باشید.
 

پردازش داده‌ها در مقابل فرمت

تفاوت بین پردازش داده و قالب بندی داده مهم است.
- نمایش فرمت داده‌ها: به همین دلیل در آموزش قبل من یک نمونه از شیء کلاس Product را برای View ارسال کرده‌ام و نه فرمت خاص یک شیء را به صورت یک رشته نمایشی.
- پردازش داده: انتخاب اشیاء داده‌‌ای برای نمایش، مسئولیت کنترلر است و در این حالت مدلی را برای دریافت و تغییر داده مورد نیاز، فراخوانی می‌کند.
گاهی سخت است که متوجه شویم کدی جهت پردازش داده است و یا فرمت آن.


اضافه نمودن مقدار داده ای

ساده‌ترین کاری را که می‌توانید با یک عبارت Razor انجام دهید این است که یک مقدار داده را در نمایش دهید. رایج‌ترین کار برای انجام آن، استفاده از عبارت Model@ است. ویوو Index یک مثال از این مورد است؛ شبیه به این مورد:
 <p>Product Name: @Model.Name</p>
شما همچنین می‌توانید یک مقدار را با استفاده قابلیت ViewBag نیز به View ارسال نمایید که از این قابلیت در Layout برای تنظیم کردن محتوای عنوان استفاده کردیم. اما در حالت زیر یک مدل نوع دار را به سمت View ارسال کرده‌ایم:
using Microsoft.AspNetCore.Mvc;
using Razor.Models;


namespace Razor.Controllers
{
    public class HomeController : Controller
    {
        // GET: /<controller>/
        public ViewResult Index()
        {
            Product myProduct = new Product
            {
                ProductID = 1,
                Name = "Kayak",
                Description = "A boat for one person",
                Category = "Watersports",
                Price = 275M
            };
            return View(myProduct);
        }
    }
}

خصوصیت ViewBag یک شیء پویا را باز می‌گرداند که می‌تواند برای تعیین خواص دلخواهی مورد استفاده قرار گیرد. از آنجا که ویژگی ViewBag پویا است، لازم نیست که نام خصوصیات را پیش از آن اعلام کنم. اما این بدان معنا است که ویژوال استودیو قادر به ارائه پیشنهادهای تکمیل کننده برای ViewBag نیست.
در مثال زیر از یک مدل نوع دار و مزایای به همراه آن استفاده شده‌است: 
 <p>Product Name: @Model.Name</p> <p>Product Price: @($"{Model.Price:C2}")</p> <p>Stock Level: @ViewBag.StockLevel</p>
نتیجه آن‌را در زیر می‌توانید مشاهده کنید:



تنظیم مقادیر مشخص

شما همچنین می‌توانید از عبارات Razor برای تعیین مقدار عناصر، استفاده کنید:
@model Product
@{

    ViewBag.Title = "Product";
}
p>Product Name: @Model.Name</p> <p>Product Price: @($"{Model.Price:C2}")</p> 
<p>Stock Level: @ViewBag.StockLevel</p>
<div data-productid="@Model.ProductID" data-stocklevel="@ViewBag.StockLevel">    
<p>Product Name: @Model.Name</p>    
<p>Product Price: @($"{Model.Price:C2}")</p>   
 <p>Stock Level: @ViewBag.StockLevel</p> 
</div>
در اینجا از عبارات Razor، برای تعیین مقدار برای برخی از ویژگی‌های داده در عنصر div استفاده کرده‌ام.

نکته: ویژگی‌های داده‌ها که نام آنها *-data است، روشی برای ایجاد ویژگی‌های سفارشی برای سال‌ها بوده است و بعنوان بخشی از استاندارد HTML5 است. عموما کدهای جاوا اسکریپت از آن‌ها برای یافتن اطلاعات استفاده می‌کنند.

اگر برنامه را اجرا کنید و به منبع HTML که به مرورگر فرستاده شده نگاهی بیندازید، خواهید دید که Razor مقادیر صفات را تعیین کرده است؛ مانند این:
<div data-productid="1" data-stocklevel="2">    <p>Product Name: Kayak</p>    <p>Product Price: £275.00</p>    <p>Stock Level: 2</p> </div>


استفاده از عبارت‌های شرطی

Razor قادر به پردازش عبارات شرطی است. در ادامه کدهای Index View را که در آن دستورات شرطی اضافه شده‌اند می‌بینید:

@model Product
@{ ViewBag.Title = "Product Name"; }
<div data-productid="@Model.ProductID" data-stocklevel="@ViewBag.StockLevel">  
  <p>Product Name: @Model.Name</p>   
 <p>Product Price: @($"{Model.Price:C2}")</p> 
   <p>Stock Level:       
 @switch (ViewBag.StockLevel)
{
    case 0:@:Out of Stock                break;           
    case 1:          
    case 2:        
    case 3:            
    <b>Low Stock (@ViewBag.StockLevel)</b>         
       break;      
    default:            
    @: @ViewBag.StockLevel in Stock          
      break;      
  }    
</p>
</div>


برای شروع یک عبارت شرطی، یک علامت @ را در مقابل کلمه کلیدی if یا swicth سی شارپ قرار دهید. سپس بخش کد را داخل } قرار می‌دهیم. درون قطعه کد Razor، می‌توانید عناصر HTML و مقادیر داده را در خروجی نمایش دهید؛ مانند:
 <b>Low Stock (@ViewBag.StockLevel)</b>
در اینجا لازم نیست عناصر یا عبارات را در نقل قول قرار دهیم و یا آنها را به روش خاصی تعریف کنیم. موتور Razor این را به عنوان خروجی برای پردازش تفسیر خواهد کرد.
با این حال، اگر می‌خواهید متن واقعی را در نظر بگیرید و دستورات Razor را لغو کنید،‌می‌توانید از :@ استفاده کنید تا عین آن عبارت درج شود.
مطالب
React 16x - قسمت 18 - کار با فرم‌ها - بخش 1 - دریافت ورودی‌ها از کاربر
تقریبا تمام برنامه‌ها نیاز دارند فرم‌های مخصوصی را داشته باشند. به همین جهت در این قسمت، برنامه‌ی نمایش لیست فیلم‌ها را که تا این مرحله تکمیل کردیم، با افزودن تعدادی فرم بهبود می‌بخشیم؛ مانند فرم لاگین، فرم ثبت نام، فرمی برای ثبت و ویرایش فیلم‌ها و یک فرم جستجوی سریع در لیست فیلم‌های موجود.


ایجاد فرم لاگین

فرم لاگینی را که به برنامه‌ی نمایش لیست فیلم‌های تکمیل شده‌ی تا قسمت 17، اضافه خواهیم کرد، یک فرم بوت استرپی است و می‌توانید جزئیات بیشتر مزین سازی المان‌های این نوع فرم‌ها را با کلاس‌های بوت استرپ، در مطلب «کار با شیوه‌نامه‌های فرم‌ها در بوت استرپ 4» مطالعه کنید.
در ابتدا فایل جدید src\components\loginForm.jsx را ایجاد کرده و سپس توسط میان‌برهای imrc و cc در VSCode، ساختار ابتدایی کامپوننت جدید LoginForm را ایجاد می‌کنیم:
import React, { Component } from "react";


class LoginForm extends Component {
  render() {
    return <h1>Login</h1>;
  }
}

export default LoginForm;
در ادامه یک Route جدید را در فایل app.js برای این فرم، با مسیر login/ و کامپوننت LoginForm، در ابتدای Switch موجود، تعریف می‌کنیم:
import LoginForm from "./components/loginForm";
//...

function App() {
  return (
    <React.Fragment>
      <NavBar />
      <main className="container">
        <Switch>
          <Route path="/login" component={LoginForm} />
          <Route path="/movies/:id" component={MovieForm} />
          // ...
        </Switch>
      </main>
    </React.Fragment>
  );
}
پس از تعریف این مسیریابی، نیاز است لینک آن‌را نیز به منوی راهبری سایت اضافه کنیم. به همین جهت در فایل navBar.jsx که آن‌را در قسمت قبل تکمیل کردیم، در انتهای لیست موجود و پس از Rentals، لینک لاگین را نیز قرار می‌دهیم:
<NavLink className="nav-item nav-link" to="/login">
   Login
</NavLink>
که در نهایت حاصل این تغییرات، به صورت زیر در مرورگر ظاهر می‌شود:


اکنون نوبت به افزودن فرم بوت استرپی لاگین به فایل loginForm.jsx رسیده‌است:
import React, { Component } from "react";


class LoginForm extends Component {
  render() {
    return (
      <form>
        <div className="form-group">
          <label htmlFor="username">Username</label>
          <input id="username" type="text" className="form-control" />
        </div>
        <div className="form-group">
          <label htmlFor="password">Password</label>
          <input id="password" type="password" className="form-control" />
        </div>
        <button className="btn btn-primary">Login</button>
      </form>
    );
  }
}

export default LoginForm;
توضیحات:
- ابتدا المان form به صفحه اضافه می‌شود.
- سپس هر ورودی، داخل یک div با کلاس form-group، محصور می‌شود. کار آن تبدیل یک برچسب و فیلد ورودی، به یک گروه از ورودی‌های بوت استرپ است.
- در اینجا هر برچسب دارای یک ویژگی for است. اما چون قرار است عبارات jsx، به معادل‌های جاوا اسکریپتی ترجمه شوند، نمی‌توان از واژه‌ی کلیدی for در اینجا استفاده کرد. به همین جهت از معادل react ای آن که htmlFor است، در کدهای فوق استفاده کرده‌ایم؛ شبیه به نکته‌ای که در مورد تبدیل ویژگی class به className وجود دارد. مقدار هر ویژگی htmlFor نیز به id فیلد ورودی متناظر با آن تنظیم می‌شود. به این ترتیب اگر کاربر بر روی این برچسب کلیک کرده و آن‌را انتخاب کند، فیلد متناظر با آن، دارای focus می‌شود.
- فیلدهای ورودی نیز دارای کلاس form-control هستند.

با این خروجی نهایی در مرورگر:



مدیریت ارسال فرم‌ها

به صورت پیش فرض و استاندارد، دکمه‌ی افزوده شده‌ی به المان form، سبب ارسال اطلاعات آن به سرور و سپس بارگذاری کامل صفحه می‌شود. این رفتاری نیست که در یک برنامه‌ی SPA مدنظر باشد. برای مدیریت این حالت، می‌توان از رخ‌داد onSubmit هر المان فرم، استفاده کرد:
class LoginForm extends Component {
  handleSubmit = e => {
    console.log("handleSubmit", e);
    e.preventDefault();

    // call the server
  };

  render() {
    return (
      <form onSubmit={this.handleSubmit}>
      //...
در اینجا یک متد رویدادگردان را برای رخ‌داد onSubmit تعریف کرده‌ایم که توسط آن رخ‌داد جاری، دریافت و متد preventDefault آن فراخوانی می‌شود تا دیگر پس از کلیک بر روی دکمه‌ی submit، حالت پیش‌فرض و استاندارد full page reload و post back به سمت سرور، رخ ندهد.


دسترسی مستقیم به المان‌های فرم‌ها

پس از فراخوانی متد preventDefault، کار مدیریت ارسال فرم به سرور را باید خودمان مدیریت کنیم و دیگر رخ‌داد full post back استاندارد به سمت سرور را نخواهیم داشت. در جاوا اسکریپت خالص برای دریافت مقادیر وارد شده‌ی توسط کاربر می‌توان نوشت:
const username = document.getElementById("username").value;
اما در React و کدهای یک کامپوننت، نباید ارجاع مستقیمی را به شیء document و DOM اصلی مرورگر داشته باشیم. در برنامه‌های React هیچگاه نباید با شیء document کار کرد؛ چون کل فلسفه‌ی آن ایجاد یک abstraction بر فراز DOM اصلی مرورگر است که به آن DOM مجازی گفته می‌شود. به این ترتیب مدیریت برنامه و همچنین آزمون نویسی برای آن نیز ساده‌تر می‌شود. اما اگر واقعا نیاز به دسترسی به یک المان DOM در React وجود داشت، چه باید کرد؟
برای دسترسی به یک المان DOM در React، باید یک reference را به آن نسبت داد. برای این منظور یک خاصیت جدید را در سطح کلاس کامپوننت، ایجاد کرده و آن‌را با React.RefObject، مقدار دهی اولیه می‌کنیم:
class LoginForm extends Component {
  username = React.createRef();
سپس ویژگی ref المان مدنظر را به این RefObject تنظیم می‌کنیم:
<input
  ref={this.username}
  id="username"
  type="text"
  className="form-control"
/>
اکنون زمان submit فرم، اگر نیاز به مقدار username وجود داشت، می‌توان توسط خاصیت ارجاعی username تعریف شده، به خاصیت current آن که DOM element مدنظر را بازگشت می‌دهد، دسترسی یافت و مانند مثال زیر، مقدار آن‌را مورد استفاده قرار داد:
  handleSubmit = e => {
    e.preventDefault();

    // call the server
    const username = this.username.current.value;
    console.log("handleSubmit", username);
  };

البته در حالت کلی باید استفاده‌ی از RefObjectها را به حداقل رساند (راه حل بهتری برای دریافت ورودی‌ها وجود دارد) و جاهائی از آن‌ها استفاده کرد که واقعا راه حل دیگری وجود ندارد؛ مانند تنظیم focus بر روی یک المان DOM. در این حالت حتما باید ارجاعی را از آن المان DOM در دسترس داشت و یا برای پویانمایی (animation) نیز مجبور به استفاده‌ی از RefObjectها هستیم.
برای نمونه روش تنظیم focus بر روی یک فیلد ورودی توسط RefObjectها به صورت زیر است:
class LoginForm extends Component {
  username = React.createRef();

  componentDidMount = () => {
    this.username.current.focus();
  };
در life-cycle hook ای به نام componentDidMount که پس از رندر کامپوننت در DOM فراخوانی می‌شود، می‌‌توان توسط RefObject تعریف شده، به شیء current که معادل DOM Element متناظر است، دسترسی یافت و سپس متد focus آن‌را فراخوانی کرد. در این حالت در اولین بار نمایش فرم، یک چنین تصویری حاصل می‌شود:


البته روش بهتری نیز برای انجام اینکار وجود دارد. المان‌های JSX دارای ویژگی autoFocus نیز هستند که دقیقا همین کار را انجام می‌دهد:
<input
  autoFocus
  ref={this.username}
  id="username"
  type="text"
  className="form-control"
/>
برای آزمایش آن، قطعه کد componentDidMount را کامنت کرده و برنامه را اجرا کنید.


تبدیل المان‌های فرم‌ها به Controlled elements

در بسیاری از اوقات، فرم‌های ما state خود را از سرور دریافت می‌کنند. فرض کنید که در حال ایجاد یک فرم ثبت اطلاعات فیلم‌ها هستیم. در این حالت باید بر اساس id فیلم، اطلاعات آن را از سرور دریافت و در state ذخیره کرد؛ سپس فیلدهای فرم را بر اساس آن مقدار دهی اولیه کرد. برای نمونه در فرم لاگین می‌توان state را با شیء account، به صورت زیر مقدار دهی اولیه کرد:
class LoginForm extends Component {
  state = {
    account: { username: "", password: "" }
  };
تا اینجا فیلدهای فرم لاگین، از این state مطلع نبوده و تغییرات داده‌های ورودی در آن‌ها، به شیء account منعکس نمی‌شوند. علت اصلی هم اینجا است که هر کدام از فیلدهای ورودی در React، دارای state خاص خود بوده و مستقل از state کامپوننت جاری هستند. برای رفع این مشکل باید آن‌ها را تبدیل به controlled element هایی کرد که دارای state خاص خود نبوده، تمام اطلاعات مورد نیاز خود را از طریق props دریافت می‌کنند و تغییرات در داده‌های خود را از طریق صدور رخ‌دادهایی اطلاع رسانی می‌کنند. برای اینکار باید مراحل زیر طی شوند:
ابتدا ویژگی value فیلد برای مثال username را به خاصیت username شیء account موجود در state متصل می‌کنیم:
<input 
  value={this.state.account.username}
به این ترتیب دیگر این المان، state خاص خود را نداشته و از طریق props، مقادیر خود را دریافت می‌کند. تا اینجا username، به رشته‌ی خالی دریافتی از شیء state و خاصیت account آن، به صورت یک طرفه متصل شده‌است. یعنی زمانیکه فرم نمایش داده می‌شود، دارای یک مقدار خالی است. برای اینکه تغییرات رخ‌داده‌ی در این المان را به state منعکس کرد، باید رخ‌داد change آن‌را مدیریت نمود. به این ترتیب زمانیکه کاربری اطلاعاتی را در اینجا وارد می‌کند، رخ‌داد change صادر شده و پس از آن می‌توان اطلاعات وارد شده را دریافت و state را به روز رسانی کرد. به روز رسانی state نیز سبب رندر مجدد فرم می‌شود. بنابراین فیلدهای ورودی، با اطلاعات state جدید، به روز رسانی و رندر می‌شوند. به همین جهت ابتدا رویداد onChange را به فیلد username اضافه کرده:
<input 
  value={this.state.account.username}
  onChange={this.handleChange}
و متد مدیریت کننده‌ی آن‌را به صورت زیر تعریف می‌کنیم:
  handleChange = e => {
    const account = { ...this.state.account }; //cloning an object
    account.username = e.currentTarget.value;
    this.setState({ account });
  };
در اینجا، هدف به روز رسانی this.state.account، بر اساس رخ‌داد رسیده (پارامتر e) است و چون نمی‌توان state را مستقیما به روز رسانی کرد، ابتدا یک clone از آن را تهیه می‌کنیم. سپس توسط e.currentTarget به المان در حال به روز رسانی دسترسی یافته و مقدار آن‌را به مقدار خاصیت username انتساب می‌دهیم. در آخر state را بر اساس این تغییرات، به روز رسانی می‌کنیم. این انعکاس در state را توسط افزونه‌ی react developer tools هم می‌توان مشاهده کرد:



مدیریت دریافت اطلاعات چندین فیلد ورودی

تا اینجا موفق شدیم اطلاعات state را به تغییرات فیلد username در فرم لاگین متصل کنیم؛ اما فیلد password را چگونه باید مدیریت کرد؟ برای اینکه تمام این مراحل را مجددا تکرار نکنیم، می‌توان از مقدار دهی پویای خواص در جاوا اسکریپت که توسط [] انجام می‌شود استفاده کرد:
  handleChange = e => {
    const account = { ...this.state.account }; //cloning an object
    account[e.currentTarget.name] = e.currentTarget.value;
    this.setState({ account });
  };
البته برای اینکه این قطعه کد کار کند، نیاز است ویژگی name فیلدهای ورودی را نیز تنظیم کرد تا e.currentTarget.name، به نام یکی از خواص شیء account تعریف شده‌ی در state اشاره کند. برای نمونه فیلد کلمه‌ی عبور، ابتدا دارای ویژگی value متصل به خاصیت password شیء account موجود در state می‌شود. سپس تغییرات آن توسط رویداد onChange، به متد handleChange منتقل شده و خاصیت name آن نیز مقدار دهی شده‌است تا مقدار دهی پویای خواص، در این متد میسر شود:
<input
  id="password"
  name="password"
  value={this.state.account.password}
  onChange={this.handleChange}
  type="password"
  className="form-control"
/>
که در نهایت سبب مقدار دهی صحیح state، با هر دو فیلد تغییر یافته می‌شود:


یک نکته: می‌توان توسط Object Destructuring، تکرار e.currentTarget را حذف کرد:
  handleChange = ({ currentTarget: input }) => {
    const account = { ...this.state.account }; //cloning an object
    account[input.name] = input.value;
    this.setState({ account });
  };
ما از شیء e دریافتی، تنها به خاصیت currentTarget آن نیاز داریم. بنابراین آن‌را از طریق Object Destructuring در همان پارامتر ورودی متد جاری دریافت کرده و سپس آن‌را به نام input، تغییر نام می‌دهیم.


آشنایی با خطاهای متداول دریافتی در حین کار با فرم‌ها

فرض کنید خاصیت username را از شیء account موجود در state حذف کرده‌ایم. در زمان نمایش ابتدایی فرم، خطایی را دریافت نخواهیم کرد، اما اگر اطلاعاتی را در آن وارد کنیم، بلافاصله در کنسول توسعه دهندگان مرورگر چنین اخطاری ظاهر می‌شود:
Warning: A component is changing an uncontrolled input of type text to be controlled.
Input elements should not switch from uncontrolled to controlled (or vice versa).
Decide between using a controlled or uncontrolled input element for the lifetime of the component.
More info: https://fb.me/react-controlled-components
چون خاصیت username را حذف کرده‌ایم، اینبار که در textbox مقداری را وارد می‌کنیم، سبب انتساب undefined و یا null به مقدار المان خواهد شد. در این حالت React چنین المانی را به صورت controlled element درنظر نمی‌گیرد و دارای state خاص خودش خواهد بود. به همین جهت عنوان می‌کند که بین یک المان کنترل شده و نشده، یکی را انتخاب کنید.
دقیقا چنین اخطاری را با ورود null/undefined بجای "" در حین مقدار دهی اولیه‌ی username در شیء account نیز دریافت خواهیم کرد:
Warning: `value` prop on `input` should not be null.
Consider using an empty string to clear the component or `undefined` for uncontrolled components.
بنابراین به عنوان یک قاعده در فرم‌های React، المان‌های یک فرم را باید توسط یک "" مقدار دهی اولیه کرد و یا با مقداری که از سمت سرور دریافت می‌شود.


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

هر چند در پیاده سازی فعلی سعی کردیم با بکارگیری مقداردهی پویای خواص اشیاء، تکرار کدها را کاهش دهیم، اما باز هم به ازای هر فیلد ورودی باید این مسایل تکرار شوند:
- ایجاد یک div با کلاس‌های بوت استرپی.
- ایجاد label و همچنین فیلد ورودی.
- در اینجا مقدار htmlFor باید با مقدار id فیلد ورودی یکی باشد.
- مقدار دهی ویژگی‌های value و onChange نیز باید تکرار شوند.

بنابراین بهتر است این تعاریف را استخراج و به یک کامپوننت با قابلیت استفاده‌ی مجدد منتقل کرد. به همین جهت فایل جدید src\components\common\input.jsx را در پوشه‌ی common ایجاد کرده و سپس توسط میانبرهای imrc و sfc، این کامپوننت تابعی بدون حالت را تکمیل می‌کنیم:
import React from "react";

const Input = ({ name, label, value, onChange }) => {
  return (
    <div className="form-group">
      <label htmlFor={name}>{label}</label>
      <input
        value={value}
        onChange={onChange}
        id={name}
        name={name}
        type="text"
        className="form-control"
      />
    </div>
  );
};

export default Input;
در اینجا کل تگ div مرتبط با username را از کامپوننت فرم لاگین cut کرده و در اینجا در قسمت return، قرار داده‌ایم. سپس شروع به تبدیل مقادیر قبلی به مقادیری که قرار است از props تامین شوند، کرده‌ایم. یا می‌توان props را به عنوان آرگومان این متد تعریف کرد و یا می‌توان توسط Object Destructuring، خواصی را که از props نیاز داریم، در پارامتر متد Input ذکر کنیم که این روش چون به نوعی اینترفیس کامپوننت را نیز مشخص می‌کند و همچنین کدهای تکراری دسترسی به props را به حداقل می‌رساند، تمیزتر و با قابلیت نگهداری بالاتری است. برای مثال هر جائیکه نام username استفاده شده بود، با خاصیت name جایگزین شده و بجای برچسب از label، بجای مقدار username از متغیر value و بجای رخ‌داد تعریف شده نیز onChange قرار گرفته‌است.

سپس به کامپوننت فرم لاگین بازگشته و ابتدا آن‌را import می‌کنیم:
import Input from "./common/input";
اکنون متد رندر ماژول src\components\loginForm.jsx، به صورت زیر با درج دو Input، خلاصه می‌شود که دیگر در آن خبری از تگ‌ها و کدهای تکراری نیست:
  render() {
    const { account } = this.state;
    return (
      <form onSubmit={this.handleSubmit}>
        <Input
          name="username"
          label="Username"
          value={account.username}
          onChange={this.handleChange}
        />
        <Input
          name="password"
          label="Password"
          value={account.password}
          onChange={this.handleChange}
        />
        <button className="btn btn-primary">Login</button>
      </form>
    );


کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید:  sample-18.zip
مطالب
مروری بر کاربردهای Action و Func - قسمت اول
delegate‌ها، نوع‌هایی هستند که ارجاعی را به یک متد دارند؛ بسیار شبیه به function pointers در C و CPP هستند، اما برخلاف آن‌ها، delegates شی‌ء‌گرا بوده، به امضای متد اهمیت داده و همچنین کد مدیریت شده و امن به شمار می‌روند.
سیر تکاملی delegates را در مثال ساده زیر می‌توان ملاحظه کرد:
using System;

namespace ActionFuncSamples
{
    public delegate int AddMethodDelegate(int a);
    public class DelegateSample
    {
        public void UseDelegate(AddMethodDelegate addMethod)
        {
            Console.WriteLine(addMethod(5));
        }
    }

    public class Helper
    {
        public int CustomAdd(int a)
        {
            return ++a;
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            Helper helper = new Helper();

            // .NET 1            
            AddMethodDelegate addMethod = new AddMethodDelegate(helper.CustomAdd);
            new DelegateSample().UseDelegate(addMethod);

            // .NET 2, anonymous delegates
            new DelegateSample().UseDelegate(delegate(int a) { return helper.CustomAdd(a); });

            // .NET 3.5
            new DelegateSample().UseDelegate(a => helper.CustomAdd(a));
        }
    }
}
معنای کلمه delegate، واگذاری مسئولیت است. به این معنا که ما در متد UseDelegate، نمی‌دانیم addMethod به چه نحوی تعریف خواهد شد. فقط می‌دانیم که امضای آن چیست.
در دات نت یک، یک وهله از شیء AddMethodDelegate ساخته شده و سپس متدی که امضایی متناسب و متناظر با آن را داشت، به عنوان متد انجام دهنده مسئولیت معرفی می‌شد. در دات نت دو، اندکی نحوه تعریف delegates با ارائه delegates بی‌نام، ساده‌تر شد و در دات نت سه و نیم با ارائه lambda expressions ، تعریف و استفاده از delegates باز هم ساده‌تر و زیباتر گردید.
به علاوه در دات نت 3 و نیم، دو Generic delegate به نام‌های Action و Func نیز ارائه گردیده‌اند که به طور کامل جایگزین تعریف طولانی delegates در کدهای پس از دات نت سه و نیم شده‌اند. تفاوت‌های این دو نیز بسیار ساده است:
اگر قرار است واگذاری قسمتی از کد را به متدی محول کنید که مقداری را بازگشت می‌دهد، از Func و اگر این متد خروجی ندارد از Action استفاده نمائید:
Action<int> example1 = x => Console.WriteLine("Write {0}", x);
example1(5);

Func<int, string> example2 = x => string.Format("{0:n0}", x);
Console.WriteLine(example2(5000));
در دو مثال فوق، نحوه تعریف inline یک Action و یا Func را ملاحظه می‌کنید. Action به متدی اشاره می‌کند که خروجی ندارد و در اینجا تنها یک ورودی int را می‌پذیرد. Func در اینجا به تابعی اشاره می‌کند که یک ورودی int را دریافت کرده و یک خروجی string را باز می‌گرداند.

پس از این مقدمه، در ادامه قصد داریم مثال‌های دنیای واقعی Action و Func را که در سال‌های اخیر بسیار متداول شده‌اند، بررسی کنیم.


مثال یک) ساده سازی تعاریف API ارائه شده به استفاده کنندگان از کتابخانه‌های ما
عنوان شد که کار delegates، واگذاری مسئولیت انجام کاری به کلاس‌های دیگر است. این مورد شما را به یاد کاربردهای interfaceها نمی‌اندازد؟
در interfaceها نیز یک قرارداد کلی تعریف شده و سپس کدهای یک کتابخانه، تنها با امضای متدها و خواص تعریف شده در آن کار می‌کنند و کتابخانه ما نمی‌داند که این متدها قرار است چه پیاده سازی خاصی را داشته باشند.
برای نمونه طراحی API زیر را درنظر بگیرید که در آن یک interface جدید تعریف شده که تنها حاوی یک متد است. سپس کلاس Runner از این interface استفاده می‌کند:
using System;

namespace ActionFuncSamples
{
    public interface ISchedule
    {
        void Run();
    }

    public class Runner
    {
        public void Exceute(ISchedule schedule)
        {
            schedule.Run();
        }
    }

    public class HelloSchedule : ISchedule
    {
        public void Run()
        {
            Console.WriteLine("Just Run!");
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            new Runner().Exceute(new HelloSchedule());
        }
    }
}
در اینجا ابتدا باید این interface را در طی یک کلاس جدید (مثلا HelloSchedule) پیاده سازی کرد و سپس حاصل را در کلاس Runner استفاده نمود.
نظر شما در مورد این طراحی ساده شده چیست؟
using System;

namespace ActionFuncSamples
{
    public class Schedule
    {
        public void Exceute(Action run)
        {
            run();
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            new Schedule().Exceute(() => Console.WriteLine("Just Run!"));
        }
    }
}
با توجه به اینکه هدف از معرفی interface در طراحی اول، واگذاری مسئولیت نحوه تعریف متد Run به کلاسی دیگر است، به همین طراحی با استفاده از یک Action delegate نیز می‌توان رسید. مهم‌ترین مزیت آن، حجم بسیار کمتر کدنویسی استفاده کننده نهایی از API تعریف شده ما است. به علاوه امکان inline coding نیز فراهم گردیده است و در همان محل تعریف Action، بدنه آن‌را نیز می‌توان تعریف کرد.
بدیهی است delegates نمی‌توانند به طور کامل جای interfaceها را پر کنند. اگر نیاز است قرارداد تهیه شده بین ما و استفاده کنندگان از کتابخانه، حاوی بیش از یک متد باشد، استفاده از interfaceها بهتر هستند.
از دیدگاه بسیاری از طراحان API، اشیاء delegate معادل interface ایی با یک متد هستند و یک وهله از delegate معادل وهله‌ای از کلاسی است که یک interface را پیاده سازی کرده‌است.
علت استفاده بیش از حد interfaceها در سایر زبان‌ها برای ابتدایی‌ترین کارها، کمبود امکانات پایه‌ای آن زبان‌ها مانند نداشتن lambda expressions، anonymous methods و anonymous delegates هستند. به همین دلیل مجبورند همیشه و در همه‌جا از interfaceها استفاده کنند.

ادامه دارد ...

 
مطالب
آشنایی با NHibernate - قسمت دوم

آزمون واحد کلاس نگاشت تهیه شده

در مورد آشنایی با آزمون‌های واحد لطفا به برچسب مربوطه در سمت راست سایت مراجعه بفرمائید. همچنین در مورد اینکه چرا به این نوع API کلمه Fluent اطلاق می‌شود، می‌توان به تعریف آن جهت مطالعه بیشتر مراجعه نمود.

در این قسمت قصد داریم برای بررسی وضعیت کلاس نگاشت تهیه شده یک آزمون واحد تهیه کنیم. برای این منظور ارجاعی را به اسمبلی nunit.framework.dll به پروژه UnitTests که در ابتدای کار به solution جاری در VS.Net افزوده بودیم، اضافه نمائید (همچنین ارجاع‌هایی به اسمبلی‌های پروژه NHSample1 ، FluentNHibernate ، System.Data.SQLite ، NHibernate.ByteCode.Castle و Nhibernate نیز نیاز هستند). تمام اسمبلی‌های این فریم ورک‌ها از پروژه FluentNHibernate قابل استخراج هستند.

سپس سه کلاس زیر را به پروژه آزمون واحد اضافه خواهیم کرد.
کلاس TestModel : (جهت مشخص سازی محل دریافت اطلاعات نگاشت)

using FluentNHibernate;
using NHSample1.Domain;

namespace UnitTests
{
public class TestModel : PersistenceModel
{
public TestModel()
{
AddMappingsFromAssembly(typeof(CustomerMapping).Assembly);
}
}
}

کلاس FixtureBase : (جهت ایجاد سشن NHibernate در ابتدای آزمون واحد و سپس پاکسازی اشیاء در پایان کار)

using NUnit.Framework;
using NHibernate;
using FluentNHibernate;
using FluentNHibernate.Cfg;
using FluentNHibernate.Cfg.Db;

namespace UnitTests
{
public class FixtureBase
{
protected SessionSource SessionSource { get; set; }
protected ISession Session { get; private set; }

[SetUp]
public void SetupContext()
{
var cfg = Fluently.Configure().Database(SQLiteConfiguration.Standard.InMemory);

SessionSource = new SessionSource(
cfg.BuildConfiguration().Properties,
new TestModel());

Session = SessionSource.CreateSession();
SessionSource.BuildSchema(Session);
}

[TearDown]
public void TearDownContext()
{
Session.Close();
Session.Dispose();
}
}
}

و کلاس CustomerMapping_Fixture.cs : (جهت بررسی صحت نگاشت تهیه شده با کمک دو کلاس قبل)

using NUnit.Framework;
using FluentNHibernate.Testing;
using NHSample1.Domain;

namespace UnitTests
{
[TestFixture]
public class CustomerMapping_Fixture : FixtureBase
{
[Test]
public void can_correctly_map_customer()
{
new PersistenceSpecification<Customer>(Session)
.CheckProperty(c => c.Id, 1001)
.CheckProperty(c => c.FirstName, "Vahid")
.CheckProperty(c => c.LastName, "Nasiri")
.CheckProperty(c => c.AddressLine1, "Addr1")
.CheckProperty(c => c.AddressLine2, "Addr2")
.CheckProperty(c => c.PostalCode, "1234")
.CheckProperty(c => c.City, "Tehran")
.CheckProperty(c => c.CountryCode, "IR")
.VerifyTheMappings();
}
}
}

توضیحات:
اکنون به عنوان یک برنامه نویس متعهد نیاز است تا کار صورت گرفته در قسمت قبل را آزمایش کنیم.
کار بررسی صحت نگاشت تعریف شده در قسمت قبل توسط کلاس استاندارد PersistenceSpecification فریم ورک FluentNHibernate انجام خواهد شد (در کلاس CustomerMapping_Fixture). این کلاس برای انجام عملیات آزمون واحد نیاز به کلاس پایه دیگری به نام FixtureBase دارد که در آن کار ایجاد سشن NHibernate (در قسمت استاندارد SetUp آزمون واحد) و سپس آزاد سازی آن را در هنگام خاتمه کار ، انجام می‌دهد (در قسمت TearDown آزمون واحد).
این ویژگی‌ها که در مباحث آزمون واحد نیز به آن‌ها اشاره شده است، سبب اجرای متدهایی پیش از اجرا و بررسی هر آزمون واحد و سپس آزاد سازی خودکار منابع خواهند شد.
برای ایجاد یک سشن NHibernate نیاز است تا نوع دیتابیس و همچنین رشته اتصالی به آن (کانکشن استرینگ) مشخص شوند. فریم ورک Fluent NHibernate با ایجاد کلاس‌های کمکی برای این امر، به شدت سبب ساده‌ سازی انجام آن شده است. در این مثال، نوع دیتابیس به SQLite و در حالت دیتابیس در حافظه (in memory)، تنظیم شده است (برای انجام امور آزمون واحد با سرعت بالا).
جهت اجرای هر دستوری در NHibernate نیاز به یک سشن می‌باشد. برای تعریف شیء سشن، نه تنها نیاز به مشخص سازی نوع و حالت دیتابیس مورد استفاده داریم، بلکه نیاز است تا وهله‌ای از کلاس استاندارد PersistanceModel را نیز جهت مشخص سازی کلاس نگاشت مورد استفاده مشخص نمائیم. برای این منظور کلاس TestModel فوق تعریف شده است تا این نگاشت را از اسمبلی مربوطه بخواند و مورد استفاده قرار دهد (بر پایی اولیه این مراحل شاید در ابتدای امر کمی زمانبر باشد اما در نهایت یک پروسه استاندارد است). توسط این کلاس به سیستم اعلام خواهیم کرد که اطلاعات نگاشت را باید از کدام کلاس دریافت کند.
تا اینجای کار شیء SessionSource را با معرفی نوع دیتابیس و همچنین محل دریافت اطلاعات نگاشت اشیاء معرفی کردیم. در دو سطر بعدی متد SetupContext کلاس FixtureBase ، ابتدا یک سشن را از این منبع سشن تهیه می‌کنیم. شیء منبع سشن در این فریم ورک در حقیقت یک factory object است (الگوهای طراحی برنامه نویسی شیءگرا) که امکان دسترسی به انواع و اقسام دیتابیس‌ها را فراهم می‌سازد. برای مثال اگر روزی نیاز بود از دیتابیس اس کیوال سرور استفاده شود، می‌توان از کلاس MsSqlConfiguration بجای SQLiteConfiguration استفاده کرد و همینطور الی آخر.
در ادامه توسط شیء SessionSource کار ساخت database schema را نیز به صورت پویا انجام خواهیم داد. بله، همانطور که متوجه شده‌اید، کار ساخت database schema نیز به صورت پویا توسط فریم ورک NHibernate با توجه به اطلاعات کلاس‌های نگاشت، صورت خواهد گرفت.

این مراحل، نحوه ایجاد و بر پایی یک آزمایشگاه آزمون واحد فریم ورک Fluent NHibernate را مشخص ساخته و در پروژه‌های شما می‌توانند به کرات مورد استفاده قرار گیرند.

در ادامه اگر آزمون واحد را اجرا نمائیم (متد can_correctly_map_customer در کلاس CustomerMapping_Fixture)، نتیجه باید شبیه به شکل زیر باشد:



توسط متد CheckProperty کلاس PersistenceSpecification ، امکان بررسی نگاشت تهیه شده میسر است. اولین پارامتر آن، یک lambda expression خاصیت مورد نظر جهت بررسی است و دومین آرگومان آن، مقداری است که در حین آزمون به خاصیت تعریف شده انتساب داده می‌شود.

نکته:
شاید سؤال بپرسید که در تابع can_correctly_map_customer عملا چه اتفاقاتی رخ داده است؟ برای بررسی آن در متد SetupContext کلاس FixtureBase ، اولین سطر آن‌را به صورت زیر تغییر دهید تا عبارات SQL نهایی تولید شده را نیز بتوانیم در حین عملیات تست مشاهده نمائیم:

var cfg = Fluently.Configure().Database(SQLiteConfiguration.Standard.ShowSql().InMemory);




مطابق متد تست فوق، عبارات تولید شده به شرح زیر هستند:

NHibernate: select next_hi from hibernate_unique_key
NHibernate: update hibernate_unique_key set next_hi = @p0 where next_hi = @p1;@p0 = 2, @p1 = 1
NHibernate: INSERT INTO "Customer" (FirstName, LastName, AddressLine1, AddressLine2, PostalCode, City, CountryCode, Id) VALUES (@p0, @p1, @p2, @p3, @p4, @p5, @p6, @p7);@p0 = 'Vahid', @p1 = 'Nasiri', @p2 = 'Addr1', @p3 = 'Addr2', @p4 = '1234', @p5 = 'Tehran', @p6 = 'IR', @p7 = 1001
NHibernate: SELECT customer0_.Id as Id0_0_, customer0_.FirstName as FirstName0_0_, customer0_.LastName as LastName0_0_, customer0_.AddressLine1 as AddressL4_0_0_, customer0_.AddressLine2 as AddressL5_0_0_, customer0_.PostalCode as PostalCode0_0_, customer0_.City as City0_0_, customer0_.CountryCode as CountryC8_0_0_ FROM "Customer" customer0_ WHERE customer0_.Id=@p0;@p0 = 1001

نکته جالب این عبارات، استفاده از کوئری‌های پارامتری است به صورت پیش فرض که در نهایت سبب بالا رفتن امنیت بیشتر برنامه (یکی از راه‌های جلوگیری از تزریق اس کیوال در ADO.Net که در نهایت توسط تمامی این فریم ورک‌ها در پشت صحنه مورد استفاده قرار خواهند گرفت) و همچنین سبب بکار افتادن سیستم‌های کش دیتابیس‌های پیشرفته مانند اس کیوال سرور می‌شوند (execution plan کوئری‌های پارامتری در اس کیوال سرور جهت بالا رفتن کارآیی سیستم کش می‌شوند و اهمیتی هم ندارد که حتما رویه ذخیره شده باشند یا خیر).

ادامه دارد ...

مطالب
آشنایی با Refactoring - قسمت 14

در بسیاری از زبان‌های برنامه نویسی امکان null بودن Reference types وجود دارد. به همین جهت مرسوم است پیش از استفاده از آن‌ها، بررسی شود آیا شیء مورد استفاده نال است یا خیر و سپس برای مثال متد یا خاصیت مرتبط با آن فراخوانی گردد؛ در غیر اینصورت برنامه با یک استثناء خاتمه خواهد یافت.
مشکلی هم که با این نوع بررسی‌ها وجود دارد این است که پس از مدتی کد موجود را تبدیل به مخزنی از انبوهی از if و else ها خواهند کرد که هم درجه‌ی پیچیدگی متدها را افزایش می‌دهند و هم نگهداری ‌آن‌ها را در طول زمان مشکل می‌سازند. برای حل این مساله، الگوی استانداردی وجود دارد به نام null object pattern؛ به این معنا که بجای بازگشت دادن null و یا سبب بروز یک exception شدن، بهتر است واقعا مطابق شرایط آن متد یا خاصیت، «هیچ‌کاری» را انجام نداد. در ادامه، توضیحاتی در مورد نحوه‌ی پیاده سازی این «هیچ‌کاری» را انجام ندادن، ارائه خواهد شد.


الف) حین معرفی خاصیت‌ها از محافظ استفاده کنید.

برای مثال اگر قرار است خاصیتی به نام Name را تعریف کنید که از نوع رشته‌ است، حالت امن آن رشته بجای null بودن، «خالی» بودن است. به این ترتیب مصرف کننده مدام نگران این نخواهد بود که آیا الان این Name نال است یا خیر. مدام نیاز نخواهد داشت تا if و else بنویسد تا این مساله را چک کند. نحوه پیاده سازی آن هم ساده است و در ادامه بیان شده است:

private string name = string.Empty;
public string Name
{
    get { return this.name; }
    set
    {
        if (value == null)
        {
            this.name = "";
            return;
        }
        this.name = value;
    }
}

دقیقا در زمان انتساب مقداری به این خاصیت، بررسی می‌شود که آیا مثلا null است یا خیر. اگر بود، همینجا و نه در کل برنامه، مقدار آن «خالی» قرار داده می‌شود.

ب) سعی کنید در متدها تا حد امکان null بازگشت ندهید.

برای نمونه اگر متدی قرار است لیستی را بازگشت دهد:

public IList<string> GetCultures()
{
//...
}

و حین تهیه این لیست، عضوی مطابق منطق پیاده سازی آن یافت نشد، null را بازگشت ندهید؛ یک new List خالی را بازگشت دهید. به این ترتیب مصرف کننده دیگری نیازی به بررسی نال بودن خروجی این متد نخواهد داشت.


ج) از متدهای الحاقی بجای if و else استفاده کنید.

پیاده سازی حالت الف زمانی میسر خواهد بود که طراح اصلی ما باشیم و کدهای برنامه کاملا در اختیار ما باشند. اما در شرایطی که امکان دستکاری کدهای یک کتابخانه پایه را نداریم چه باید کرد؟ مثلا دسترسی به تعاریف کلاس XElement دات نت فریم ورک را نداریم (یا حتی اگر هم داریم، تغییر آن تا زمانیکه در کدهای پایه اعمال نشده باشد، منطقی نیست). در این حالت می‌شود یک یا چند extension method را طراحی کرد:

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

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

به این ترتیب می‌توان امکانات کلاس پایه‌‌ای را بدون نیاز به دسترسی به کدهای اصلی آن مطابق نیاز‌های خود تغییر و توسعه داد.


مطالب
مبانی TypeScript؛ کلاس‌ها
تا قبل از ES 6 در جاوا اسکریپت از توابع جهت ایجاد کامپوننت‌هایی با قابلیت استفاده مجدد استفاده می‌شد. این امر برای برنامه‌نویسانی که با زبان‌های OOP آشنایی دارند، شاید چندان خوشایند نباشد. در TypeScript نیز همانند ES 6 امکان استفاده از کلاس‌ها مهیا است.
در حالت کلی یک کلاس قالبی برای ایجاد اشیاء است. تمامی اشیاء ایجاد شده از این الگو دارای یکسری پراپرتی و متد می‌باشند. از پراپرتی‌ها جهت تعریف وضعیت‌ها و از متدها جهت تعریف رفتارها استفاده خواهد شد. همچنین مزیت اصلی یک کلاس، کپسوله‌سازی قابلیت‌های یک موجودیت خاص است. همانند دیگر زبان‌های شیءگرا، در TypeScript نیز یک کلاس می‌تواند ویژگی‌های زیر را داشته باشد:
  • سازنده (constructor)
  • پراپرتی، متد
  • Access Modifiers
  • ارث‌بری
  • کلاس‌های Abstract
در ادامه هر کدام از موارد فوق را بررسی خواهیم کرد.

سازنده (Constructor)
از سازنده‌ها جهت مقداردهی وهله‌های یک کلاس استفاده می‌شود. در ادامه یک کلاس جدید را با استفاده از کلمه‌ی کلیدی class ایجاد کرده‌ایم. این کلاس دارای یک سازنده است:
class ReferenceItem {
    constructor(title: string, publisher?: string) {
        // perform initialization here
    }
}
همانطور که مشاهده می‌کنید یک سازنده شبیه به یک متد است؛ با این تفاوت که برای نام آن از کلمه کلیدی constructor استفاده می‌شود. در TypeScript برای یک کلاس تنها یک سازنده را می‌توانیم داشته باشیم. البته در دیگر زبان‌های برنامه‌نویسی امکان تعریف چندین سازنده را با پارامترهای مختلف برای یک کلاس می‌توانید داشته باشید. برای رسیدن به این هدف در TypeScript می‌توان از Optional Parameters استفاده کرد. برای ایجاد یک وهله از کلاس فوق می‌توانیم به این صورت عمل کنیم:
let encyclopedia = new ReferenceItem('WorldPedia', 'WorldPub');
در کد فوق با استفاده از کلمه‌ی کلیدی new یک وهله از کلاس ReferenceItem را ایجاد کرده‌ایم و در نهایت آن را به متغیری با نام encyclopedia انتساب داده‌ایم. یعنی در واقع با استفاده از new توانسته‌ایم سازنده‌ی کلاس را فراخوانی کرده و سپس وهله‌ایی از آن را به متغیر ذکر شده انتساب دهیم.

پراپرتی، متد 
همانند اینترفیس‌ها، کلاس‌ها نیز می‌توانند پراپرتی و متد داشته باشند. با این تفاوت که در کلاس‌ها جزئیات پیاده‌سازی نیز ذکر خواهد شد. در یک کلاس به دو روش متفاوت می‌توانیم پراپرتی را تعریف کنیم. روش اول همانند تعریف یک متغیر است. به عنوان مثال در کلاس زیر یک پراپرتی با نام numberOfPages را از نوع عددی تعریف کرده‌ایم:
class ReferenceItem {
    numberOfPages: number;
}
برای دسترسی به این پراپرتی می‌توانیم از سینتکس نقطه (.) استفاده کنیم. روش دوم برای تعریف یک پراپرتی، ایجاد accessor‌های سفارشی است. accessors در واقع توابع getter و setter هستند که به شما در نحوه‌ی get و set کردن یک پراپرتی کمک خواهند کرد:
class ReferenceItem {
    numberOfPages: number;
    
    get editor(): string {
        // custom getter logic goes here, should return a value
    }
    
    set editor(newEditor: string) {
        // custom setter logic goes here
    }
}
همانطور که مشاهده می‌کنید، accessorهایی را برای پراپرتی editor با استفاده از کلمات کلیدی get و set ایجاد کرده‌ایم. این accessorها در واقع توابعی همنام هستند. تابع get همیشه فاقد پارامتر است. می‌توانیم برای تابع get نوع برگشتی را نیز تعیین کنیم (به عنوان مثال در کد فوق نوع برگشتی string است). setter نیز باید تنها یک پارامتر از ورودی دریافت کند. همچنین نمی‌توانیم برای آن نوع برگشتی را تعیین کنیم. درون بدنه‌ی این accessorها می‌توانیم هر نوع کنترلی را بر روی پراپرتی داشته باشیم. برای دسترسی این accessorها نیز باید از سینتکس نقطه (.) استفاده کنیم.
متدها نیز توابعی هستند که درون یک کلاس تعریف می‌شوند. برای نمونه در کد زیر یک تابع با نام printChapterTitle را تعریف کرده‌ایم که یک پارامتر را از ورودی دریافت کرده و هیچ مقداری را در خروجی بر نمی‌گرداند:
class ReferenceItem {
    numberOfPages: number;
    
    get editor(): string {
        // custom getter logic goes here, should return a value
    }
    
    set editor(newEditor: string) {
        // custom setter logic goes here
    }
    
    printChapterTitle(chapterNum: number): void {
        // print title here
    }
}

Parameter properties
در حالت عادی برای مقداردهی اولیه‌ی پراپرتی‌ها یک شیء می‌توانیم یکسری پارامتر را برای سازنده کلاس تعریف کرده و درون سازنده، پراپرتی‌های موردنیازمان را مقداردهی کنیم:
class Author {
    name: string;
    
    constructor(authorName: string) {
        name = authorName;
    }
}
با کمک Parameter properties می‌توانیم به صورت خلاصه‌تری اینکار را انجام دهیم:
class Author {
    constructor(public name: string){}
}
همانطور که مشاهده می‌کنید اینکار را با افزودن کلمه‌ی کلیدی public به ابتدای پارامتر name انجام داده‌ایم. در این‌حالت دیگر نیازی به تعریف یک پراپرتی اضافی درون کلاس نخواهیم داشت. کامپایلر TypeScript خودش یک پراپرتی را با همین نام ایجاد کرده و مقدار دریافتی از سازنده را برای آن ست خواهد کرد.

Static Properties
تاکنون درباره‌ی اعضای مربوط به هر وهله از کلاس‌ها صحبت کردیم؛ یعنی اعضایی که در زمان وهله‌سازی در دسترس خواهند بود. در واقع می‌توانیم اعضای استاتیک را نیز برای کلاس‌ها داشته باشیم. منظور از استاتیک این است که مقادیر یک عضوء استاتیک در وهله‌های مختلف یک شیء، متفاوت نیست. بلکه یک مقدار آن برای تمامی وهله‌ها به اشتراک گذاشته خواهد شد:
class Library {
    constructor(public name: string) {}
    
    static description: string = 'A source of knowledge';
}

let lib = new Library('New York Public Library');
console.log(lib.name); // available on instances of the class

console.log(Library.description);

Access Modifiers
با استفاده از Access Modifier می‌توانیم میدان دید یک پراپرتی و یا یک متد را برای مصرف کننده‌ی کلاس کنترل کنیم. TypeScript دارای سه Access Modifier است:
public: در حالت پیش‌فرض تمامی اعضای یک کلاس عمومی (public) هستند. در نتیجه لزومی به ذکر آن برای پراپرتی‌ها و متدها نیست. یک حالت استثناء، استفاده از Parameter properties است. در این حالت باید کلمه‌ی کلیدی public حتماً ذکر شود. 
private: برای محدود کردن دسترسی اعضای یک کلاس می‌توانید از کلمه‌ی کلیدی private استفاده کنید. در این‌حالت مصرف کننده‌ی کلاس به اعضای خصوصی (private) دسترسی نخواهد داشت. 
protected: این modifier نیز شبیه به private عمل می‌کند، با این تفاوت که توسط subclassهای مربوط به کلاس تعریف شده در آن نیز قابل دسترس است.


Inheritance
منظور از Inheritance یا ارث‌بری، اشتراک‌گذاری تعاریف یک کلاس برای یک یا چند sub-class است. فرض کنید یک کلاس با نام ReferenceItem با یکسری اعضای تعریف شده درون آن داریم و می‌خواهیم دو کلاس مشتق شده را از این کلاس تهیه کنیم. در این‌حالت کلاس ReferenceItem کلاس پایه (base class) و کلاس‌های مشتق شده از آن sub-class نامیده می‌شوند. بنابراین وهله‌های ایجاد شده از کلاس‌های مشتق شده دارای پراپرتی‌های کلاس پایه نیز خواهند بود. برای داشتن قابلیت ارث‌بری در TypeScript می‌توانیم به اینصورت عمل کنیم:
class ReferenceItem {
    title: string;
    printItem(): void { 
        // print something here 
    }
}

class Journal extends ReferenceItem {
    constructor() {
        super();
    }
    
    contributors: string[];
}
همانطور که مشاهده می‌کنید با استفاده از کلمه‌ی کلیدی extends توانسته‌ایم یک sub-class ایجاد کنیم. بنابراین وهله‌های کلاس Journal علاوه بر پراپرتی‌های خود (در اینجا contributors ) دارای پراپرتی title و همچنین متد printItem نیز هستند. نکته‌ایی که در اینجا وجود دارد این است که تمامی sub-classها یا کلاس‌های مشتق شده باید درون سازنده‌ی خود، تابع super را فراخوانی کنند؛ با اینکار سازنده‌ی کلاس پایه فراخوانی خواهد شد.
لازم به ذکر است که می‌توان متدهای کلاس پایه را درون کلاس‌های مشتق شده، override کرد. برای اینکار کافی است متد موردنظر در کلاس پایه را درون کلاس مشتق شده مجدداً تعریف کرده و منطق موردنظر را درون آن نوشت:
class Journal extends ReferenceItem {
    constructor() {
        super();
    }
    
    printItem(): void { 
        super.printItem();
        console.log('message from Journal');
    }
    
    contributors: string[];
}
با استفاده از super.printItem به کامپایلر TypeScript گفته‌ایم که تمامی کدهای درون متد printItem در کلاس پایه نیز اجرا شوند. اگر مایل بودید می‌توانید از آن صرفنظر کنید.

Abstract Classes 
کلاس‌های Abstract یک نوع خاص از کلاس‌ها هستند که نمی‌توان آنها را وهله‌سازی کرد. یعنی تنها برای تعریف کلاس‌های پایه از آنها استفاده خواهد شد. این نوع کلاس‌ها شبیه به اینترفیس‌ها هستند؛ اما ممکن است دارای پیاده‌سازی نیز باشند. در ادامه یک نمونه از abstract class را مشاهده می‌کنید:
abstract class ReferenceItem {
    private _publisher: string;
    static departement: string = 'Research';
    
    constructor(public title: string, protected year: number) {
        
    }
    
    printItem(): void {
        console.log('message from abstract class');
    } 
    
    get publisher(): string {
        return this._publisher.toUpperCase();
    }
    
    set publisher(newPublisher: string) {
        this._publisher = newPublisher;
    }
    
    abstract printCitation(): void;
}

class Encyclopedia extends ReferenceItem {
    
    constructor(newTitle: string, newYear, public edition: number) {
        super(newTitle, newYear);
    }
    
    printCitation(): void {
        console.log('message');
    }
}

let test = new Encyclopedia('WorldPerdia', 1900, 10);
test.printItem();
همانطور که مشاهده می‌کنید درون یک کلاس abstract می‌توانیم متدهای abstract را نیز داشته باشیم؛ یعنی تنها امضای متد را تعیین کرده و پیاده‌سازی آن را به کلاس‌های مشتق شده واگذار کنیم. 
نظرات مطالب
اصول طراحی شی گرا SOLID - #بخش سوم اصل LSP
ممنون.
کلاس های Rectangle و Square  هر دو به همون شکل باقی میمونند با این تفاوت که هر دو از کلاس Shape مشتق شده اند و میتوانند خاصیت‌های Width و Height را طبق نیاز خود دوباره نویسی کنند (override).
کلاس Restangle:
public class Rectangle : Shape
{
    //شما میتوانید خاصیت‌ها طول و عرض در کلاس پایه را در صورت نیاز دوباره نویسی کنید
}
کلاس Square :
public class Square : Shape
{
    //دوباره نویسی کردن خاصیت‌های طول و عرض در کلاس پایه جهت برابر کردن طول و عرض مربع
    public override int Width
    {
        get{return base.Width;}
        set
        {
            base.Height = value;
            base.Width = value;
        }
    }
    public override int Height
    {
        get{return base.Height;}
        set
        {
            base.Height = value;
            base.Width = value;
        }
    }        
}
که با توجه به کدهای بالا ، کلاسهای مشتق شده‌ی Square و Restangle میتوانند جایگزین کلاس پایه خود یعنی Shape شوند :
Shape o = new Rectangle();
o.Width = 5;
o.Height = 6;
 
Shape o = new Square();
o.Width = 5; //طول و عرض هر دو برابر 5 میشوند
o.Height = 6; //عرض و طول هر دو برابر 6 میشوند

مطالب
مقایسه value type و reference type
در سی شارپ دو نوع class و struct وجود دارد که تقریباً مشابه یکدیگرند در حالیکه یکی از آنها-value type و دیگری reference-type است.

struct چیست؟
structها مشابه classها هستند با این تفاوت که structها finalizer ندارند و از ارث بری پشتیبانی نمی‌کنند. structها کاملا مشابه classها تعریف می‌شوند و در تعریف آنها از کلمه کلیدی struct استفاده می‌شود. آنها شامل فیلدها، متدها، خصوصیت‌ها نیز می‌شوند. در زیر نحوه تعریف آن را مشاهده می‌کنید: 

struct Point
{
   private int x, y;             // private fields
 
   public Point (int x, int y)   // constructor
   {
         this.x = x;
         this.y = y;
   }

   public int X                  // property
   {
         get {return x;}
         set {x = value;}
   }

   public int Y
   {
         get {return y;}
         set {y = value;}
   }
}

value type و reference type
تفاوت دیگری که بین class و struct، از اهمیت ویژه‌ای برخوردار است  آن است که classها reference-type و structها value-type هستند و در زمان اجرا با آنها متفاوت رفتار می‌شود و در ادامه به تشریح آن می‌پردازیم.
وقتی یک وهله از value-type ایجاد شود، یک فضای خالی از حافظه‌ی اصلی (RAM) برای ذخیره سازی مقدار آن تخصیص داده می‌شود. نوع‌های اصلی مانند int, float, bool و char از نوع value type هستند. در ضمن سرعت دسترسی به آنها بسیار بالاست.
ولی وقتی یک وهله از reference-type ایجاد شود، یک فضا برای object و فضایی دیگر برای اشاره‌گر به آن شیء در حافظه اصلی ذخیره می‌شود. در واقع دو فضا از حافظه برای ذخیره سازی آنها اشغال می‌شود. برای درک بهتر به مثال زیر توجه کنید:
Point p1 = new Point();         // Point is a *struct*
Form f1 = new Form();           // Form is a *class*
نکته: Point از نوع struct و Form از نوع reference است. در مورد اول، یک فضا از حافظه برای p1 تخصیص داده می‌شود و در مورد دوم، دو فضا از حافظه اصلی یکی برای ذخیره کردن اشاره‌گر f1 برای اشاره به Form object و دیگری برای ذخیره کردن Form object تخصیص داده می‌شود.
Form f1;                        // Allocate the reference
f1 = new Form();                // Allocate the object
به قطعه کد زیر دقت کنید:
Point p2 = p1;
Form f2 = f1;
همانطور که قبلاً گفته شد p2، یک نوع struct است بنابراین در مورد اول مقدار p2 یک کپی از مقدار p1 خواهد بود ولی در مورد دوم، آدرس f1 را درون f2 کپی می‌کنیم در واقع f1 و f2 به یک شیء اشاره خواهند کرد. (یک شیء با 2 اشاره گر)
در سی شارپ، پارامترها (بصورت پیش فرض) بصورت یک کپی از آنها به متدها ارسال می‌شوند، یعنی اگر پارامتر از نوع value-type باشد یک کپی از آن وهله و اگر پارامتر reference-type یک کپی از آدرس ارسال خواهد شد. برای توضیح بهتر به مثال زیر توجه کنید:
Point myPoint = new Point (0, 0);      // a new value-type variable
Form myForm = new Form();              // a new reference-type variable
Test (myPoint, myForm);                // Test is a method defined below
 
void Test (Point p, Form f)
{
      p.X = 100;                       // No effect on MyPoint since p is a copy
      f.Text = "Hello, World!";        // This will change myForm’s caption since
                                       // myForm and f point to the same object
      f = null;                        // No effect on myForm
}
انتساب null به f درون متد Test هیچی اثری بر روی آدرس myForm ندارد چون f، یک کپی از آدرس myForm است.
حال می‌توانیم روش پیش فرض را با افزودن کلمه کلید ref تغییر دهیم.  وقتی از ref استفاده کنیم متد با پارامترهای فراخوانی کننده (caller's arguments) بصورت مستقیم در تعامل است در کد زیر می‌توانیم تصور کنیم که پارامترهای p و f متد Test همان متغیرهای myPoint و myForm است.
Point myPoint = new Point (0, 0);      // a new value-type variable
Form myForm = new Form();              // a new reference-type variable
Test (ref myPoint, ref myForm);        // pass myPoint and myForm by reference
 
void Test (ref Point p, ref Form f)
{
      p.X = 100;                       // This will change myPoint’s position
      f.Text = “Hello, World!”;        // This will change MyForm’s caption
      f = null;                        // This will nuke the myForm variable!
}
در کد بالا انتساب null به f باعث تهی شدن myForm می‌شود بدلیل اینکه متد مستقیماً به آن دسترسی داشته است.

تخصیص حافظه
CLR اشیاء را در دو قسمت ذخیره می‌کند:
  1. stack یا پشته
  2. heap
ساختار stack یا پشته first-in last-out است که دسترسی به آن سریع است. زمانی که متدی فراخوانی می‌شود، CLR پشته را نشانه گذاری می‌کند. سپس متد data را به پشته جهت اجرا push می‌کند و زمانی که اجرایش به اتمام رسید، CLR پشته را تا محل نشانه گذاری شده مرحله قبل، پاک می‌کند (pop).
ولی ساختار heap بصورت تصادفی است. یعنی اشیاء در محل‌های تصادفی قرار داده می‌شوند بهمین دلیل آنها دارای 2 سربار memory manager و garbage-collector هستند.
برای آشنایی با نحوه استفاده پشته و heap به کد زیر توجه کنید:
void CreateNewTextBox()
{
      TextBox myTextBox = new TextBox();             // TextBox is a class
}
در این متد، ما یک متغیر محلی ایجاد کرده ایم که به یک شیء اشاره می‌کند.

پشته همیشه برای ذخیره سازی موارد زیر استفاده می‌شود:
  • قسمت reference متغیرهای محلی و پارامترهای از نوع reference-typed (مانند myTextBox)
  • متغیرهای محلی و پارامترهای متد از نوع value-typed (مانند integer, bool, char, DateTime و ...)
همچنین از heap برای ذخیره سازی موارد زیر استفاده می‌شود:
  • محتویات شیء از نوع reference-typed
  • هر چیزی که قرار است در شیء از نوع reference-typed ذخیره شود.

آزادسازی حافظه در heap

در کد بالا وقتی اجرای متد CreateNewTextBox به اتمام برسد متغیر myTextBox از دید (Scope) خارج می‌شود. بنابراین از پشته نیز خارج می‌شود ولی با خارج شدن myTextBox از پشته چه اتفاقی برای TextBox object رخ خواهد داد؟! پاسخ در garbage-collector نهفته است. garbage-collector بصورت خودکار عملیات پاکسازی heap را انجام می‌دهد و اشیائی که اشاره گر معتبر ندارند را حذف می‌نماید. در حالت کلی اگر شیء از حافظه خارج شد باید منابع سایر قسمت‌های اشغال شده توسط آن هم آزاد شود، که این آزاد سازی بعهده garbage-collector است.

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

  1. دستی: با فراخوانی متد Dispose میسر است.
  2. خودکار: افزودن شیء به Net Container. مانند Form, Panel, TabPage یا UserControl. این نگهدارندها این اطمینان را به ما می‌دهند در صورتیکه آنها از حافظه خارج شدند کلیه عضوهای آن هم از حافظه خارج شوند.

برای آزادسازی دستی می‌توانیم مانند کدهای زیر عمل کنیم:

using (Stream s = File.Create ("myfile.txt"))

{
   ...
}
یا
Stream s = File.Create ("myfile.txt");

try
{
   ...
}

finally
{
   if (s != null) s.Dispose();
}


مثالی از Windows Forms
فرض کنید قصد داریم فونت و اندازه یک ویندوز فرم را تغییر دهیم.

Size s = new Size (100, 100);          // struct = value type
Font f = new Font (“Arial”,10);        // class = reference type

Form myForm = new Form();

myForm.Size = s;
myForm.Font = f;
توجه کنید که ما در کد بالا از اعضای myForm استفاده کردیم نه از کلاسهای Font و Size که این دو گانگی قابل قبول است. حال به تصویر زیر که به پیاده سازی کد بالا اشاره دارد توجه کنید.

همانطور که مشاهد می‌کنید محتویات s و آدرس f را در Form object ذخیره کرده ایم که نشان می‌دهد تغییر در s برروی فرم تغییر ایجاد نمی‌کند ولی تغییر در f باعث ایجاد تغییر فرم می‌شود. Form object دو اشاره گر به Font object دارد.

In-Line Allocation (تخصیص درجا)
در قبل گفته شد برای ذخیره متغیرهای محلی از نوع value-typed از پشته استفاده می‌شود آیا شیء Size جدید هم در پشته ذخیره می‌شود؟ خیر، بدلیل اینکه آن متغیر محلی نیست و در شیء دیگر ذخیره می‌شود (در مثال بالا در یک فرم ذخیره شده است) که آن شیء هم در heap ذخیره شده است پس شیء جدید Size هم در heap ذخیر می‌شود که به این نوع ذخیره سازی In-Line گفته می‌شود.

تله (Trap)
فرض کنید کلاس Form بشکل زیر تعریف شده است:
class Form
{
      // Private field members
      Size size;
      Font font;

      // Public property definitions
      public Size Size
      {
            get    { return size; }
            set    { size = value; fire resizing events }
      }

      public Font Font
      {
            get    { return font;  }
            set    { font = value; }
      }
}
حال ما قصد داریم ارتفاع آن را دو برابر کنیم، بنابراین از کد زیر استفاده می‌کنیم:
myForm.ClientSize.Height = myForm.ClientSize.Height * 2;
ولی با خطای کامپایلر زیر روبرو می‌شویم:
Cannot modify the return value of 'System.Windows.Forms.Form.ClientSize' because it is not a variable
علت چیست؟ بدلیل اینکه myForm.ClientSize شیء Size که از نوع Struct است را بر می‌گرداند و این Struct از نوع value-typed است و این شیء یک کپی از اندازه فرم است و ما همزمان قصد دو برابر نمودن آن کپی را داریم که کامپایلر خطای بالا را نمایش می‌دهد.

برای توضیح بیشتر می‌توانید به این سوال مراجعه کنید و در تکمیل آن این لینک را هم بررسی کنید.

پس بنابراین کد بالا را به کد زیر اصلاح می‌کنیم:
myForm.ClientSize = new Size (myForm.ClientSize.Width, myForm.ClientSize.Height * 2);
برای اصلاح خطای کامپایلر، ما باید یک شیء جدیدی را برای اندازه فرم تخصیص بدهیم.