مطالب
بررسی بهبودهای ProblemDetails در ASP.NET Core 7x
در زمان ارائه‌ی ASP.NET Core 2.1، ویژگی جدیدی به نام [ApiController] ارائه شد که با استفاده از آن، یکسری اعمال توکار جهت سهولت کار با Web API توسط خود فریم‌ورک انجام می‌شوند؛ برای مثال عدم نیاز به بررسی وضعیت ModelState و بررسی خودکار آن با علامتگذاری یک کنترلر به صورت ApiController. یکی دیگر از این ویژگی‌های توکار، تبدیل خروجی تمام status codeهای بزرگتر و یا مساوی 400 یا همان Bad Request، به شیء جدید و استاندارد ProblemDetails است:
{
    "type": "https://example.com/probs/out-of-credit",
    "title": "You do not have enough credit.",
    "detail": "Your current balance is 30, but that costs 50.",
    "instance": "/account/12345/msgs/abc",
    "status": 403,
}
 بازگشت یک چنین خروجی یک‌دست و استانداردی، استفاده‌ی از آن‌را توسط کلاینت‌ها، ساده و قابل پیش‌بینی می‌کند. البته باید درنظر داشت که اگر در این‌حالت، برنامه یک استثنای معمولی را سبب شود، ProblemDetails ای بازگشت داده نمی‌شود. اگر برنامه در حالت توسعه اجرا شود، با استفاده از میان‌افزار app.UseDeveloperExceptionPage، یک صفحه‌ی نمایش جزئیات خطا ظاهر می‌شود و اگر برنامه در حالت تولید و ارائه‌ی نهایی اجرا شود، یک صفحه‌ی خالی (بدون داشتن response body) با status code مساوی 500 بازگشت داده می‌شود. این کمبود ویژه و امکانات سفارشی سازی بیشتر آن، به صورت توکار به ASP.NET Core 7x اضافه شده‌اند و دیگر نیازی به استفاده از کتابخانه‌های ثالث دیگری برای انجام آن نیست.


ProblemDetails بر اساس RFC7807 طراحی شده‌است

RFC7807، قالب استانداردی را برای ارائه‌ی خطاهای HTTP APIها تعریف می‌کند تا نیازی به وجود تعاریف متعددی در این زمینه نباشد و خروجی آن قابل پیش‌بینی و قابل بررسی توسط تمام کلاینت‌های یک API باشد. کلاس ProblemDetails در ASP.NET Core نیز بر همین اساس طراحی شده‌است.
این RFC دو فرمت خروجی را بر اساس مقدار مشخص شده‌ی در هدر Content-Type بازگشت داده شده، مجاز می‌داند:
  • JSON: “application/problem+json” media type
  • XML: “application/problem+xml” media type

که با توجه به این هدر ارسالی، اگر از یک کلاینت از نوع HttpClient استفاده کنیم، می‌توان بر اساس مقدار ویژه‌ی «application/problem+json» تشخیص داد که خروجی API دریافتی، به همراه خطا است و نحوه‌ی پردازش آن به صورت زیر خواهد بود:
var mediaType = response.Content.Headers.ContentType?.MediaType;
if (mediaType != null && mediaType.Equals("application/problem+json", StringComparison.InvariantCultureIgnoreCase))
{
   var problemDetails = await response.Content.ReadFromJsonAsync<ProblemDetails>(null, ct) ?? new ProblemDetails();
   // ...
}
در اینجا بدنه‌ی اصلی شیء ProblemDetails بازگشت داده شده، می‌تواند به همراه اعضای زیر باشد:
- type: یک رشته‌است که به آدرس مستندات HTML ای مرتبط با خطای بازگشت داده شده، اشاره می‌کند.
- title: رشته‌ای است که خلاصه‌ی خطای رخ‌داده را بیان می‌کند.
- detail: رشته‌ای است که توضیحات بیشتری را در مورد خطای رخ‌داده، بیان می‌کند.
- instance: رشته‌ای است که به آدرس محل بروز خطا اشاره می‌کند.
- status: عددی است که بیانگر HTTP status code بازگشتی از سمت سرور است.


البته اگر ویژگی ApiController بر روی کنترلرهای خود استفاده نمی‌کنید، می‌توانید این خروجی را به صورت زیر هم با استفاده از return Problem، تولید کنید:
[HttpPost("/sales/products/{sku}/availableForSale")]
public async Task<IActionResult> AvailableForSale([FromRoute] string sku)
{
   return Problem(
            "Product is already Available For Sale.",
            "/sales/products/1/availableForSale",
            400,
            "Cannot set product as available.",
            "http://example.com/problems/already-available");
}


امکان افزودن اعضای سفارشی به شیء ProblemDetails

امکان بسط این خروجی، با افزودن اعضای سفارشی نیز پیش‌بینی شده‌است. یک نمونه‌ی متداول و پرکاربرد آن، بازگشت خطاهای مرتبط با اعتبارسنجی اطلاعات رسیده‌است:
HTTP/1.1 400 Bad Request
Content-Type: application/problem+json
Content-Language: en
{
    "type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
    "title": "One or more validation errors occurred.",
    "status": 400,
    "errors": {
        "User": [
            "The user name is not verified."
        ]
    }
}
در اینجا عضو جدید errors را بنابر نیاز این مساله‌ی خاص، مشاهده می‌کنید که در صورت استفاده از ویژگی ApiController بر روی کنترلرهای Web API، به صورت خودکار توسط ASP.NET Core تولید می‌شود و نیازی به تنظیم خاصی و یا کدنویسی اضافه‌تری ندارد. کلاس مخصوص آن نیز ValidationProblemDetails‌ است.


جهت افزودن اعضای سفارشی دیگری به شیء ProblemDetails می‌توان به صورت زیر عمل کرد:
namespace WebApplication.Controllers
{
    [ApiController]
    [Route("[controller]")]
    public class DemoController : ControllerBase
    {
        [HttpPost]
        public ActionResult Post()
        {
            var problemDetails = new ProblemDetails
            {
                Detail = "The request parameters failed to validate.",
                Instance = null,
                Status = 400,
                Title = "Validation Error",
                Type = "https://example.net/validation-error",
            };

            problemDetails.Extensions.Add("invalidParams", new List<ValidationProblemDetailsParam>()
            {
                new("name", "Cannot be blank."),
                new("age", "Must be great or equals to 18.")
            });

            return new ObjectResult(problemDetails)
            {
                StatusCode = 400
            };
        }
    }

    public class ValidationProblemDetailsParam
    {
        public ValidationProblemDetailsParam(string name, string reason)
        {
            Name = name;
            Reason = reason;
        }

        public string Name { get; set; }
        public string Reason { get; set; }
    }
}
شیء ProblemDetails، به همراه خاصیت Extensions است که می‌توان به آن یک <Dictionary<string, object را انتساب داد و نمونه‌ای از آن‌را در مثال فوق مشاهده می‌کنید. این مثال سبب می‌شود تا عضو جدیدی با کلید دلخواه invalidParams، به همراه لیستی از name و reasonها به خروجی نهایی اضافه شود. مقدار این کلید، از نوع object است؛ یعنی هر شیء دلخواهی را در اینجا می‌توان تعریف و استفاده کرد.


معرفی سرویس جدید ProblemDetails در دات نت 7

در دات نت 7 می‌توان سرویس‌های جدید ProblemDetails را به نحو زیر به برنامه اضافه کرد:
services.AddProblemDetails();
پس از آن به 3 روش مختلف می‌توان از امکانات این سرویس‌ها استفاده کرد:
الف) با اضافه کردن میان‌افزار مدیریت خطاها
app.UseExceptionHandler();
پس از آن، هر استثنای مدیریت نشده‌ای نیز به صورت یک ProblemDetails ظاهر می‌شود و دیگر همانند قبل، سبب نمایش یک صفحه‌ی خالی نخواهد شد.

ب) با افزودن میان‌افزار StatusCodePages
app.UseStatusCodePages();
در این حالت مواردی که استثناء شمرده نمی‌شوند مانند 404، در صورت بروز رسیدن به یک مسیریابی یافت نشده و یا 405، در صورت درخواست یک HTTP method غیرمعتبر نیز توسط یک ProblemDetails استاندارد مدیریت می‌شوند.

ج) با افزودن میان‌افزار صفحه‌ی استثناءهای توسعه دهنده‌ها
app.UseDeveloperExceptionPage();
به این ترتیب در خروجی ProblemDetails، اطلاعات بیشتری از استثناء رخ‌داده، مانند استک‌تریس آن ظاهر خواهد شد.


امکان بازگشت ساده‌تر یک ProblemDetails سفارشی در دات نت 7

برای سفارشی سازی خروجی ProblemDetails، علاوه بر راه‌حلی که پیشتر در این مطلب مطرح شد، می‌توان در دات نت 7 از روش تکمیلی ذیل نیز استفاده کرد:
builder.Services.AddProblemDetails(options =>
    options.CustomizeProblemDetails = ctx =>
            ctx.ProblemDetails.Extensions.Add("MachineName", Environment.MachineName));
به این ترتیب در صورت لزوم می‌توان یک عضو سفارشی سراسری را به تمام اشیاء ProblemDetails برنامه به صورت خودکار اضافه کرد و یا اگر می‌خواهیم این مورد را کمی اختصاصی‌تر کنیم، می‌توان به صورت زیر عمل کرد:

الف) تعریف یک ErrorFeature سفارشی
public class MyErrorFeature
{
    public ErrorType Error  { get; set; }
}
​
public enum ErrorType
{
    ArgumentException
}
در ASP.NET Core می‌توان به شیء HttpContext.Features قابل تنظیم در هر اکشن متدی، اشیاء دلخواهی را مانند شیء سفارشی فوق، اضافه کرد و سپس در قسمت options.CustomizeProblemDetails تنظیماتی که ذکر شد، به دریافت و تنظیم آن، واکنش نشان داد.

ب) تنظیم مقدار ErrorFeature سفارشی در اکشن متدها
    [HttpGet("{value}")]
    public IActionResult MyErrorTest(int value)
    {
        if (value <= 0)
        {
            var errorType = new MyErrorFeature
            {
                Error = ErrorType.ArgumentException
            };
            HttpContext.Features.Set(errorType);
            return BadRequest();
        }
​
        return Ok(value);
    }
پس از تعریف شیءایی که قرار است به HttpContext.Features اضافه شود، اکنون روش تنظیم و مقدار دهی آن‌را در یک اکشن متد، در مثال فوق مشاهده می‌کنید.

ج) واکنش نشان دادن به دریافت ErrorFeature سفارشی
services.AddProblemDetails(options =>
    options.CustomizeProblemDetails = ctx =>
    {
        var MyErrorFeature = ctx.HttpContext.Features.Get<MyErrorFeature>();
​
        if (MyErrorFeature is not null)
        {
            (string Title, string Detail, string Type) details = MyErrorFeature.Error switch
            {
                ErrorType.ArgumentException =>
                (
                    nameof(ArgumentException),
                    "This is an argument-exception.",
                    "https://www.rfc-editor.org/rfc/rfc7231#section-6.5.1"
                ),
                _ =>
                (
                    nameof(Exception),
                    "default-exception",
                    "https://www.rfc-editor.org/rfc/rfc7231#section-6.6.1"
                )
            };
​
            ctx.ProblemDetails.Title = details.Title;
            ctx.ProblemDetails.Detail = details.Detail;
            ctx.ProblemDetails.Type = details.Type;
        }
    }
);
پس از تنظیم HttpContext.Features در اکشن متدی، می‌توان در options.CustomizeProblemDetails فوق، توسط متد ctx.HttpContext.Features.Get به آن شیء خاص تنظیم شده، در صورت وجود دسترسی یافت و سپس جزئیات بیشتری را از آن استخراج و مقادیر ctx.ProblemDetails جاری را که قرار است به کاربر بازگشت داده شوند، بازنویسی کرد و یا تغییر داد.
 

امکان تبدیل ساده‌تر اطلاعات استثناءهای سفارشی به یک ProblemDetails سفارشی در دات نت 7

بجای استفاده از تنظیمات services.AddProblemDetails جهت بازنویسی مقدار شیء ProblemDetails بازگشتی، می‌توان جزئیات میان‌افزار app.UseExceptionHandler را نیز سفارشی سازی کرد و به بروز استثناءهای خاصی واکنش نشان داد. برای مثال فرض کنید یک استثنای سفارشی را به صورت زیر طراحی کرده‌اید:
public class MyCustomException : Exception
{
    public MyCustomException(
        string message,
        HttpStatusCode statusCode = HttpStatusCode.BadRequest
    ) : base(message)
    {
        StatusCode = statusCode;
    }
​
    public HttpStatusCode StatusCode { get; }
}
و سپس در اکشن متدی، سبب بروز آن شده‌اید:
    [HttpGet("{value}")]
    public IActionResult MyErrorTest(int value)
    {
        if (value <= 0)
        {
            throw new MyCustomException("The value should be positive!");
        }
​
        return Ok(value);
    }
اکنون می‌توان در میان‌افزار مدیریت استثناءهای برنامه، نسبت به مدیریت این استثناء خاص، واکشن نشان داد و ProblemDetails متناظری را تولید و بازگشت داد:
app.UseExceptionHandler(exceptionHandlerApp =>
{
    exceptionHandlerApp.Run(async context =>
    {
        context.Response.ContentType = "application/problem+json";
​
        if (context.RequestServices.GetService<IProblemDetailsService>() is { } problemDetailsService)
        {
            var exceptionHandlerFeature = context.Features.Get<IExceptionHandlerFeature>();
            var exceptionType = exceptionHandlerFeature?.Error;
​
            if (exceptionType is not null)
            {
                (string Title, string Detail, string Type, int StatusCode) details = exceptionType switch
                {
                    MyCustomException MyCustomException =>
                    (
                        exceptionType.GetType().Name,
                        exceptionType.Message,
                        "https://www.rfc-editor.org/rfc/rfc7231#section-6.5.1",
                        context.Response.StatusCode = (int)MyCustomException.StatusCode
                    ),
                    _ =>
                    (
                        exceptionType.GetType().Name,
                        exceptionType.Message,
                        "https://www.rfc-editor.org/rfc/rfc7231#section-6.6.1",
                        context.Response.StatusCode = StatusCodes.Status500InternalServerError
                    )
                };
​
                await problemDetailsService.WriteAsync(new ProblemDetailsContext
                {
                    HttpContext = context,
                    ProblemDetails =
                    {
                        Title = details.Title,
                        Detail = details.Detail,
                        Type = details.Type,
                        Status = details.StatusCode
                    }
                });
            }
        }
    });
});
​
در اینجا نحوه‌ی کار با سرویس توکار IProblemDetailsService و سپس دسترسی به IExceptionHandlerFeature و استثنای صادر شده را مشاهده می‌کنید. پس از آن بر اساس نوع و اطلاعات این استثناء، می‌توان یک ProblemDetails مخصوص را تولید و در خروجی ثبت کرد.
مطالب
آشنایی با CLR: قسمت بیست و پنجم
یکی از مهمترین خصوصیات CLR این است که نوع‌ها، ایمن هستند و همواره می‌داند که هر شیء از چه نوعی است. برای اثبات این ادعا می‌توانید متد GetType را صدا بزنید تا به شما بگوید این شیء از چه نوعی است. متد GetType قابلیت رونویسی ندارد و به همین علت می‌توانید مطمئن باشید که خروجی برگشتی دستکاری نشده است.
یکی از نیازهای طراحان این است که مرتبا نیاز به تبدیل نوع‌ها را به یکدیگر دارند. CLR به شما اجازه می‌دهد که هر آبجکتی را به نوع مربوط به خودش یا والدینش تبدیل کنید. بسته به زبانی که انتخاب می‌کنید، این تبدیل شکل متفاوتی دارد و در سی شارپ نیاز به سینتکس خاصی نیست.
سی شارپ برای تبدیل یک شیء به نوع‌های والدش، نیازی به ذکر نوع ندارد ولی اگر قرار است از سمت والد به سمت فرزند Cast شود نیاز است که صریحا نوع آن را اعلام کنید. در این روش اگر نوع تبدیلات با شیء ما سازگاری نداشته باشد، در زمان اجرا، با خطای
 InvalidCastExceptio
 روبرو خواهید شد. کد زیر نمونه‌ای از این تبدیلات است:
internal class Employee {
...
}    
public sealed class Program {
     public static void Main() {
        // بدون ذکر نام والد تبدیل صورت میگیرد
        Object o = new Employee();

      // برای تبدیل والد به یکی از مشتقات آن نیاز است
        // نوع آن به طور صریح ذکر گردد
         // در بعضی زبان‌های مثل ویژوال بیسیک نیازی به ذکر آن نیست
        Employee e = (Employee) o;
     }
  }

استفاده از کلمات as و is در تبدیلات
یکی دیگر از روش‌های امن برای cast کردن اشیاء، استفاده از کلمه‌ی کلیدی is هست. این عبارت چک می‌کند که آیا شیء مورد نظر، از نوع تبدیلی ما پشتیبانی می‌کند یا خیر؛ اگر true بازگرداند به این معنی است که پشتیبانی می‌شود و در حین cast کردن با خطایی روبرو نمی‌شویم.
Object o = new Object();
  Boolean b1 = (o is Object);   // b1 is true.
  Boolean b2 = (o is Employee); // b2 is false.
پی نوشت :در این بررسی اگر شیء نال باشد، مقدار برگشتی همیشه false است. چون به هیچ نوعی قابل تبدیل نیست.
نحوه‌ی استفاده‌ی از کلمه کلیدی is در این تبدیل به شکل زیر است:
if (o is Employee) 
{     
         Employee e = (Employee) o;
}
کد بالا با اینکه ایمنی بیشتری دارد، ولی از نظر کارآیی هزینه بر است. دلیل آن هم این است که عمل تاییدیه، در دو مرتبه انجام می‌شود: اولین مرحله‌ی تایید، استفاده از عبارت is است تا بررسی کند آیا این شیء قابل تبدیل به نوع مورد نظر است یا خیر. دومین بررسی هم در حین تبدیل یا Cast کردن اتفاق می‌افتد که خود این تبدیل هم، همانطور که در بالا اشاره کردیم، بررسی‌هایی برای تبدیل دارد.

برای بهبود کد بالا، سی شارپ کلمه‌ی کلیدی as را ارائه می‌کند. کلمه کلیدی as باعث می‌شود اگر شیء به آن نوع قابل تبدیل باشد، ارجاعی صورت بگیرد؛ در غیر این صورت مقدار نال بازگشت داده می‌شود. شاید شما بگویید که در خط بعدی ما نیز دوباره مجددا یک عبارت شرطی داریم و دوباره داریم عمل تاییدیه را انجام می‌دهیم. ولی باید گفت این if به مراتب هزینه‌ی کمتری نسبت به بررسی‌های تبدیل یا Cast به شیوه‌ی بالاست.
Employee e = o as Employee;
  if (e != null) {
     .....
  }

فضاهای نام و اسمبلی ها
همانطور که مطلع هستید، فضاهای نام به ما این اجازه را می‌دهند تا نوع‌ها را به صورت منطقی گروه بندی کنیم تا دسترسی به آنان راحت‌تر باشد. برای مثال مطمئنا با نگاه به اسم فضای نام
System.Text
متوجه می‌شویم که داخل آن، نوع‌های متفاوتی برای کار با رشته‌ها وجود دارد. برای دسترسی به یک نوع، ابتدا باید از فضاهای نام آن شروع کرد و به شیوه‌ی زیر، به نوع‌ها دسترسی پیدا کرد:
public sealed class Program {
     public static void Main() {
        System.IO.FileStream fs = new System.IO.FileStream(...);
        System.Text.StringBuilder sb = new System.Text.StringBuilder();
     }
  }
احتمالا متوجه‌ی شلوغی و طولانی شدن بی جهت کدها شده‌اید. برای رفع این مشکل، هر زبان شیوه‌ای را می‌تواند بکار بگیرد که سی شارپ از کلمه‌ی کلیدی Using و مثلا ویژوال بیسیک از کلمه‌ی کلیدی Import و ... استفاده می‌کنند و حال می‌توانیم کد بالا را خلاصه‌تر و منظم‌تر بنویسیم:
using System.IO;    // Try prepending "System.IO." 
 using System.Text;  // Try prepending "System.Text." 
  
public sealed class Program {
     public static void Main() {
        FileStream fs = new FileStream(...); 
       StringBuilder sb = new StringBuilder();
     }
  }

موقعیکه شما نوعی را در یک فضای نام استفاده می‌کنید، این نوع به ترتیب بررسی می‌کند که نوع، در کدام فضای نام و کدام اسمبلی مورد استفاده قرار گرفته است. این اسمبلی‌ها شامل FCL و اسمبلی‌های خارجی است که به آن لینک کرده‌اید. حال ممکن است این سؤال پیش بیاید که ممکن است نام دو نوع، در دو فضای نام متفاوت، یکی باشد و در یک جا مورد استفاده قرار گرفته‌اند. چگونه می‌توان تشخیص داد که کدام نوع، متعلق به دیگری است؟ نظر مایکروسافت این است که تا می‌توانید سعی کنید از اسامی متفاوت استفاده کنید. ولی در بعضی شرایط این مورد ممکن نیست. به همین علت باید هر دو کلاس یا به طور کامل، به همراه فضای نام نوشته شوند؛ یا اینکه یکی از آن‌ها بدین شکل باشد و فضای نام نوع دیگر، با using صدا زده شود.
در مثال زیر ما دو نوع را با نام Widget داریم که در دو فضای نام Microsoft و Dotnettips وجود دارند:
using Microsoft; 
using Dotnettips ;
public sealed class Program {
     public static void Main() {
        Widget w = new Widget();// An ambiguous reference
     } 
 }
در کد بالا به دلیل اینکه مشخص نیست نوعی که مدنظر شماست، کدام است، با خطای زیر روبرو می‌شوید:
 'Widget' is an ambiguous reference between 'Microsoft.Widget' and 'Dotnettips.Widget
به همین علت کد را به شکل زیر تغییر می‌دهیم:
using Microsoft; 
using Dotnettips;
   
public sealed class Program {
     public static void Main() {
        Dotnettips.Widget w = new Dotnettips.Widget(); // Not ambiguous
     }
  }
یا بدین صورت:
using Microsoft; 
using Dotnettips;
using DotnettipsWidget = Dotnettips.Widget;   

public sealed class Program {
     public static void Main() {
        DotnettipsWidget w = new DotnettipsWidget (); // No error now
     }
  }
حال بیایید تصور کنیم که فضای‌های نام هم یکسان شده‌اند. مثلا شرکتی به اسم Australian Boomerang Company و شرکت دیگری به اسم Alaskan Boat Corporation یک اسمبلی با نام Widget را تولید کرده اند و تحت فضای نام ABC منتشر کرده‌اند.با اینکه مایکروسافت سفارش زیادی کرده است که از نام کامل استفاده شود و مخفف‌ها را مورد استفاده قرار ندهید ولی از اتفاقاتی است که ممکن است رخ بدهد. در این حالت خوشبختانه کمپایلر سی شارپ قابلیتی به نام Extern را معرفی کرده است.
مطالب
React 16x - قسمت 3 - بررسی پیشنیازهای جاوا اسکریپتی - بخش 2
در قسمت قبل، بخشی از تازه‌های ES6 را که بیشتر در برنامه‌های مبتنی بر React مورد استفاده قرار می‌گیرند، بررسی کردیم. در این قسمت نیز سایر موارد مهم باقیمانده را بررسی می‌کنیم.

در اینجا نیز برای بررسی ویژگی‌های جاوا اسکریپت مدرن، یک پروژه‌ی جدید React را ایجاد می‌کنیم.
> create-react-app sample-03
> cd sample-03
> npm start
سپس تمام کدهای داخل index.js را نیز حذف می‌کنیم. اکنون تمام کدهای خالص جاوا اسکریپتی خود را داخل این فایل خواهیم نوشت.
همچنین چون در این قسمت خروجی UI نخواهیم داشت، تمام خروجی را در کنسول developer tools مرورگر خود می‌توانید مشاهده کنید (فشردن دکمه‌ی F12).


متد Array.map

در برنامه‌های مبتنی بر React، از متد Array.map برای رندر لیست‌ها استفاده می‌شود و نمونه‌های بیشتری از آن‌را در قسمت‌های بعدی مشاهده خواهید کرد.
فرض کنید آرایه‌ای از رنگ‌ها را داریم. اکنون می‌خواهیم لیستی را به صورت <li>color</li> به ازای هر آیتم آن، تشکیل دهیم:
const colors = ["red", "green", "blue"];
برای این منظور می‌توان از متد map بر روی این آرایه به نحو زیر استفاده کرد:
const items = colors.map(function(color) {
  return "<li>" + color + "</li>";
});
console.log(items);
متد map یک callback function را دریافت می‌کند که با هر بار فراخوانی آن، یک عنصر از عناصر آرایه را دریافت کرده، آن‌را تغییر شکل داده و بازگشت می‌دهد (چیزی شبیه به متد Select در LINQ).
این مثال را توسط arrow functions نیز می‌توان بازنویسی کرد:
const items2 = colors.map(color => "<li>" + color + "</li>");
console.log(items2);
ابتدا function را حذف می‌کنیم. سپس { return } را تبدیل به یک <= خواهیم کرد. چون تک پارامتری است، نیازی به ذکر پرانتز color وجود ندارد. همچنین نیازی به ذکر سمی‌کالن انتهای return هم نیست؛ چون کل بدنه‌ی این تابع، یک سطر return بیشتر نیست.

یک مرحله‌ی دیگر هم می‌توانیم این قطعه کد را زیباتر کنیم؛ جمع زدن رشته‌ها در ES6 معادل بهتری پیدا کرده‌است که template literals نام دارد:
const items3 = colors.map(color => `<li>${color}</li>`);
console.log(items3);
در اینجا بجای ' و یا " از حرف back-tick استفاده می‌شود. سپس قالب کلی رشته‌ی خود را مشخص می‌کنیم و جائیکه قرار است متغیری را درج کنیم، از {}$ استفاده می‌کنیم که بسیار شبیه به ویژگی string interpolation در #C است. فقط برخلاف آن، حرف $ در ابتدای رشته قرار نمی‌گیرد و باید دقیقا پیش از متغیر مدنظر تعریف شود.


Object Destructuring

فرض کنید شیء آدرس را به صورت زیر تعریف کرده‌ایم:
const address = {
  street: "street 1",
  city: "city 1",
  country: "country 1"
};
اکنون می‌خواهیم خواص آن‌را به متغیرهایی نسبت دهیم. یک روش متداول آن به صورت زیر است:
const street1 = address.street;
const city1 = address.city;
const country1 = address.country;
برای کاهش این حجم کد تکراری که با .address شروع می‌شود، می‌توان از ویژگی Object Destructuring استفاده کرد:
const { street, city, country } = address;
این تک سطر، دقیقا با سه سطر قبلی که نوشتیم، عملکرد یکسانی دارد. ابتدا متغیرهای مدنظر، داخل {} قرار می‌گیرند و سپس کل شیء آدرس به آن‌ها نسبت داده خواهد شد.
در اینجا باید نام متغیرهای تعریف شده با نام خواص شیء آدرس یکی باشند. همچنین ذکر تمامی این متغیرها نیز ضرورتی ندارد و برای مثال اگر فقط نیاز به street بود، می‌توان تنها آن‌را ذکر کرد.
اگر خواستیم نام متغیر دیگری را بجای نام خواص شیء آدرس انتخاب کنیم، می‌توان از یک نام مستعار ذکر شده‌ی پس از : استفاده کرد:
const { street: st } = address;
console.log(st);


Spread Operator

فرض کنید دو آرایه‌ی زیر را داریم:
const first = [1, 2, 3];
const second = [4, 5, 6];
و می‌خواهیم آن‌ها را با هم ترکیب کنیم. یک روش انجام اینکار توسط متد concat آرایه‌ها است:
const combined = first.concat(second);
console.log(combined);

در ES6 با استفاده از عملگر ... که spread نیز نام دارد، می‌توان قطعه کد فوق را به صورت زیر بازنویسی کرد:
 const combined2 = [...first, ...second];
console.log(combined2);
ابتدا یک آرایه‌ی جدید را ایجاد می‌کنیم. سپس تمام عناصر اولین آرایه را در آن گسترده می‌کنیم و بعد از آن، تمام عناصر دومین آرایه را.

شاید اینطور به نظر برسد که بین دو راه حل ارائه شده آنچنانی تفاوتی نیست. اما مزیت قطعه کد دوم، سهولت افزودن المان‌های جدید، به هر قسمتی از آرایه است:
 const combined2 = [...first, "a", ...second, "b"];
console.log(combined2);

کاربرد دیگر عملگر spread امکان clone ساده‌ی یک آرایه‌است:
const clone = [...first];
console.log(clone);

به علاوه امکان اعمال آن به اشیاء نیز وجود دارد:
const firstObject = { name: "User 1" };
const secondObject = { job: "Job 1" };
const combinedObject = { ...firstObject, ...secondObject, location: "Here" };
console.log(combinedObject);
در اینجا تمام خواص شیء اول و دوم با هم ترکیب و همچنین یک خاصیت اختیاری نیز ذکر شده‌است. خروجی نهایی آن چنین شیءای خواهد بود:
 {name: "User 1", job: "Job 1", location: "Here"}

و امکان clone اشیاء توسط آن هم وجود دارد:
const clonedObject = { ...firstObject };
console.log(clonedObject);


کلاس‌ها در ES 6

قطعه کد کلاسیک زیر را که کار ایجاد اشیاء را در جاوا اسکریپت انجام می‌دهد، در نظر بگیرید:
const person = {
  name: "User 1",
  walk() {
    console.log("walk");
  }
};

const person2 = {
  name: "User 2",
  walk() {
    console.log("walk");
  }
};
ابتدا یک شیء person را با دو عضو، ایجاد کرده‌ایم. اکنون برای ایجاد یک شیء person دیگر، باید دقیقا همان قطعه کد را تکرار کنیم. به همین جهت برای حذف کدهای تکراری، نیاز به قالبی برای ایجاد اشیاء داریم و اینجا است که از کلاس‌ها استفاده می‌شود:
class Person {
  constructor(name) {
    this.name = name;
  }

  walk() {
    console.log("walk");
  }
}
برای تعریف یک کلاس ES6، با واژه‌ی کلیدی class شروع می‌کنیم. نام یک کلاس با حروف بزرگ شروع می‌شود (pascal case) و اگر برای نمونه این نام قرار است دو قسمتی باشد، به مانند CoolPerson عمل می‌کنیم. در مرحله‌ی بعد، متد walk را از تعریف شیء شخص، به کلاس شخص انتقال داده‌ایم. سپس متد ویژه‌ی constructor را در اینجا تعریف کرده‌ایم. توسط آن زمانیکه یک نمونه از این کلاس ساخته می‌شود، پارامتری را دریافت و به یک خاصیت جدید در آن کلاس که توسط this.name تعریف شده‌است، انتساب می‌دهیم.
باید دقت داشت که  class Person تنها یک قالب است و const person ای که پیشتر تعریف شد، یک شیء. برای اینکه از روی قالب تعریف شده‌ی Person، یک شیء را ایجاد کنیم، به صورت زیر توسط واژه‌ی کلیدی new عمل می‌شود:
const person3 = new Person("User 3");
console.log(person3.name);
person3.walk();
در اینجا اگر دقت کنید، عبارت Person("User 3") شبیه به فراخوانی یک متد است. این متد دقیقا همان متد ویژه‌ی constructor ای است که تعریف کردیم. اکنون توسط شیء person3، می‌توان به خاصیت name و یا متد walk آن دسترسی یافت.

یک نکته: در جاوا اسکریپت، کلاس‌ها نیز شیء هستند! از این جهت که کلاس‌ها در جاوا اسکریپت صرفا یک بیان نحوی زیبای تابع constructor هستند و توابع در جاوا اسکریپت نیز شیء می‌باشند!


ارث بری کلاس‌ها در ES6

فرض کنید می‌خواهیم کلاس Teacher را به نحو زیر تعریف کنیم:
class Teacher {
  teach() {
    console.log("teach");
  }
}
این کلاس دارای متد teach است؛ اما تمام معلم‌ها باید بتوانند راه هم بروند. همچنین قصد نداریم متد walk کلاس Person را هم با توجه به اینکه Teacher یک Person نیز هست، در اینجا تکرار کنیم. یک روش حل این مشکل، استفاده از ارث‌بری کلاس‌ها است که با افزودن extends Person به نحو زیر میسر می‌شود:
class Teacher extends Person {
  teach() {
    console.log("teach");
  }
}
پس از این تعریف، اگر بخواهیم توسط واژه‌ی کلیدی new، یک شیء را بر اساس این کلاس تهیه کنیم، در VSCode، تقاضای ثبت یک سازنده نیز می‌شود:


علت اینجا است که کلاس Teacher، نه فقط متد walk کلاس Person را به ارث برده‌است، بلکه سازنده‌ی آن‌را نیز به ارث می‌برد:
const teacher = new Teacher("User 4");
اکنون می‌توان با استفاده از شیء معلم ایجاد شده، نه فقط به متدهای کلاس Teacher دسترسی یافت، بلکه امکان دسترسی به خواص و متدهای کلاس پایه‌ی Person نیز در اینجا وجود دارد:
console.log(teacher.name);
teacher.teach();
teacher.walk();

در ادامه فرض کنید علاوه بر ذکر نام، نیاز به ذکر مدرک معلم نیز در سازنده‌ی کلاس وجود دارد:
class Teacher extends Person {
  constructor(name, degree) {}
در این حالت اگر به کنسول توسعه دهنده‌های مرورگر مراجعه کنید، خطای زیر را مشاهده خواهید کرد:
 Uncaught ReferenceError: Must call super constructor in derived class before accessing 'this' or returning from derived constructor
عنوان می‌کند که نیاز است متد ویژه‌ی super را در سازنده‌ی سفارشی کلاس Teacher فراخوانی کنیم. در ES6، فراخوانی سازنده‌ی کلاس پایه، در سازنده‌های سفارشی کلاس‌های مشتق شده‌ی از آن، اجباری است:
class Teacher extends Person {
  constructor(name, degree) {
    super(name);
    this.degree = degree;
  }

  teach() {
    console.log("teach");
  }
}
با اینکار، مقدار دهی خاصیت name کلاس پایه نیز صورت خواهد گرفت. در اینجا همچنین تعریف خاصیت جدید degree و مقدار دهی آن‌را نیز مشاهده می‌کنید. در ادامه باید این پارامتر دوم سازنده را نیز در حین نمونه سازی از کلاس Teacher تعریف کنیم:
const teacher = new Teacher("User 4", "MSc");

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


ماژول‌ها در ES 6

تا اینجا اگر مثال‌ها را دنبال کرده باشید، تمام آن‌ها را داخل همان فایل index.js درج کرده‌ایم. به این ترتیب کم کم دارد مدیریت این فایل از دست خارج می‌شود. امکان تقسیم کدهای index.js به چندین فایل، مفهوم ماژول‌ها را در ES6 تشکیل می‌دهد. برای این منظور قصد داریم هر کلاس تعریف شده را به یک فایل جداگانه که ماژول نامیده می‌شود، منتقل کنیم. از کلاس Person شروع می‌کنیم و آن‌را به فایل جدید person.js و کلاس Teacher را به فایل جدید teacher.js منتقل می‌کنیم.
البته اگر از افزونه‌های VSCode استفاده می‌کنید، اگر کرسر را بر روی نام کلاس قرار دهید، یک آیکن لامپ مانند ظاهر می‌شود. با کلیک بر روی آن، منویی که شامل گزینه‌ی move to a new file هست، برای انجام ساده‌تر این عملیات (ایجاد یک فایل جدید js، سپس انتخاب و cut کردن کل کلاس و در آخر کپی کردن آن در این فایل جدید) پیش‌بینی شده‌است.

هرچند این عملیات تا به اینجا خاتمه یافته به نظر می‌رسد، اما نیاز به اصلاحات زیر را نیز دارد:
- هنگام کار با ماژول‌ها، اشیاء تعریف شده‌ی در آن به صورت پیش‌فرض، خصوصی و private هستند و خارج از آن‌ها قابل دسترسی نمی‌باشند. به این معنا که class Teacher ما که اکنون در یک ماژول جدید قرار گرفته‌است، توسط سایر قسمت‌های برنامه قابل مشاهده و دسترسی نیست.
- برای public تعریف کردن یک کلاس تعریف شده‌ی در یک ماژول، نیاز است آن‌را export کنیم. انجام این کار نیز ساده‌است. فقط کافی است واژه‌ی کلیدی export را به پیش از class اضافه کنیم:
 export class Teacher extends Person {
- اگر افزونه‌ی eslint را نصب کرده باشید، اکنون در فایل یا ماژول جدید teacher.js، زیر کلمه‌ی Person خط قرمز کشیده‌است و عنوان می‌کند که کلاس Person را نمی‌شناسد:


برای رفع این مشکل، باید این وابستگی را import کرد:
import { Person } from "./Person";

export class Teacher extends Person {
در اینجا شیء Person، از فایل محلی واقع شده‌ی در پوشه‌ی جاری Person.js تامین می‌شود. نیازی به ذکر پسوند فایل در اینجا نیست.

- مرحله‌ی آخر، اصلاح فایل index.js است؛ چون اکنون تعاریف Person و Teacher را نمی‌شناسد.
import { Person } from "./Person";
import { Teacher } from "./Teacher";
دو سطر فوق را نیز به ابتدای فایل index.js اضافه می‌کنیم تا بتوان new Person و new Teacher نوشته شده‌ی در آن‌را کامپایل کرد.


Exportهای پیش‌فرض و نامدار در ES6

اشیاء تعریف شده‌ی در یک ماژول، به صورت پیش‌فرض private هستند؛ مگر اینکه export شوند. برای مثال export class Teacher و یا export function xyz. به این‌ها named exports گویند. حال اگر ماژول ما تنها یک شیء عمومی شده را داشت (کلاس‌ها هم شیء هستند!)، می‌توان از واژه‌ی کلیدی default نیز در اینجا استفاده کرد:
 export default class Teacher extends Person {
پس از این دیگر نیازی به ذکر {} در حین import چنین شیءای نخواهد بود:
 import Teacher from "./Teacher";

در ادامه اگر یک export نامدار دیگر را به این ماژول اضافه کنیم (مانند تابع testTeacher):
import { Person } from "./Person";

export function testTeacher() {
  console.log("Test Teacher");
}

export default class Teacher extends Person {
نحوه‌ی import آن به صورت زیر تغییر می‌کند:
 import Teacher, { testTeacher } from "./Teacher";
یک default export و یک named export را در اینجا داریم که اولی بدون {} و دومی با {} تعریف شده‌است. این الگویی است که در برنامه‌های React زیاد دیده می‌شود؛ مانند:
import React, { Component } from 'react';

یک نکته: اگر در VSCode داخل {}، دکمه‌های ctrl+space را فشار دهید، می‌توانید منوی exportهای ماژول تعریف شده را مشاهده کنید.


کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید: sample-03.zip
مطالب
بازسازی کد: جایگزینی متغیر موقتی با پرس و جو (Replace temp with query)
زمانیکه متغیری برای نگهداری موقت نتیجه‌ی یک expression تعریف شده‌است، بهتر است expression مربوطه به متدی انتقال پیدا کرده و تمامی استفاده‌ها از متغیر موقتی با فراخوانی متد ایجاد شده جایگزین شوند. 
مشکل اصلی در ارتباط با متغیرهای محلی، ترویج ایجاد متدهای بلند توسط آنها است. مشخص است که این متغیرها در بدنه متد خود قابل استفاده هستند و تنها راه اشتراک مقدار آنها طولانی‌تر شدن متد است. اما زمانیکه این متغیرها با متد پرس و جوی مرتبط با آن جایگزین شوند، این مقدار توسط دیگر متدهای کلاس قابل دسترسی خواهد بود. این کار ایجاد متدهایی با اندازه مناسب را آسان‌تر می‌کند. 
این بازسازی کد بیشتر اوقات در کنار بازسازی استخراج متد استفاده می‌شود؛ به طوری که قبل از انجام استخراج متد، ابتدا تکلیف متغیرهای محلی مشخص می‌شود. یک نوع از متغیرهای محلی که نیاز به بررسی و تغییر خواهند داشت این دسته از متغیرهای محلی هستند.

مراحل انجام این بازسازی کد  

  • متغیر موقتی ای را که تنها یک بار مقداردهی شده است، پیدا کنید (در صورتیکه متغیر چندین بار مقداردهی شده باشد، باید بازسازی جداسازی متغیرهای موقتی را اعمال نمایید).
  • متغیر را به readonly تغییر دهید.
  • کد را کامپایل نمایید تا اطمینان حاصل کنید متغیر تنها یک بار مقداردهی شده‌است.
  • Expression سمت راست مقداردهی متغیر را به متد، منتقل نمایید. دو نکته در مورد متد تازه ایجاد شده:  
    • این متد را به صورت private تعریف نمایید. در صورتی در آینده مصرف دیگری برای آن پیدا کردید می‌توانید سطح دسترسی آن‌را آزادتر نمایید. 
    • اطمینان حاصل نمایید متد مورد نظر، شیء یا خصوصیتی را ویرایش نمی‌کند. در صورتیکه این کار را انجام می‌دهد، باید بخش ویرایش آن را از بخش پرس و جوی آن جدا نمایید (Separate query from modifier). 
  • کد را مجددا کامپایل نمایید.  
  • تمامی استفاده‌های متغیر را با فراخوانی متد ایجاد شده، تغییر دهید. 
مثال: تکه کد زیر را در نظر بگیرید  
public class OrderItem 
{ 
    private double quantity; 
    private double itemPrice; 
    public double CalculateTotal() 
    { 
        double basePrice = quantity * itemPrice; 

        if (basePrice > 1000) 
        { 
            return basePrice * 0.95; 
        } 
        else 
        { 
            return basePrice * 0.98; 
        } 
    } 
}

در این کلاس متدی برای محاسبه قیمت نهایی یک آیتم سفارش ایجاد شده‌است. با دقت در کد می‌توان تشخیص داد که متغیر basePrice یک متغیر محلی موقتی است. این تکه کد را می‌توان به صورت زیر بازسازی کرد   
public class OrderItem 
{ 
    private double quantity; 
    private double itemPrice; 
    public double CalculateTotal() 
    { 
        if (BasePrice() > 1000) 
        { 
            return BasePrice() * 0.95; 
        } 
        else 
        { 
            return BasePrice() * 0.98; 
        } 
    } 
    private double BasePrice() 
    { 
        return quantity * itemPrice; 
    } 
}
کد حاصل از بازسازی انجام شده، شامل یک متد به نام BasePrice است که مقدار قیمت پایه را بر می‌گرداند. این متد را به صورت private تعریف کردیم. اما می‌توان در صورت نیاز به override کردن آن در کلاس‌های مشتق شده‌ی احتمالی، سطح دستری آن را به مقدار مناسبی تغییر داد.  به نظر شما چه بازسازی کد دیگری را می‌توان بر روی متد CalculateTotal انجام داد؟ 

آیا این بازسازی کد تاثیر منفی بر کارآیی خواهد داشت؟ 

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

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

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

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

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

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

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

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

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

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


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

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

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

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

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

            filesService.DeleteFile("c:\\myfakefile.txt");
            filesService.DeleteFile(fileName);
        }
و نتیجه خروجی:
File Path is not valid
first name must be more than 2 letters
your age is illegal
the file has been written
Please enter a valid path
the file has been deleted
مطالب
انقیاد داده‌ها در WPF (بخش دوم)
در ادامه‌ی بخش اول از سری انقیاد داده‌ها در WPF، نحوه‌ی انقیاد داده‌ها در لیست را بررسی می‌کنیم.
• One Way Binding بخش اول
• INPC  بخش اول
• Tow Way Binding بخش اول
• List Binding  بخش دوم
• Element Binding بخش دوم
• Data Conversion بخش دوم

انقیاد در لیست List Binding
در ابتدا متدی با نام GetEmployees را با ساختار زیر، به کلاس Employee ایجاد شده‌ی در بخش اول این سری آموزشی، اضافه می‌کنیم: 
public static ObservableCollection<Employee> GetEmployees()
        {
            var employees = new ObservableCollection<Employee>();
            employees.Add(new Employee() { Name = "Mahdi", Title = "Manager" });
            employees.Add(new Employee() { Name = "Nima", Title = "Teacher" });
            employees.Add(new Employee() { Name = "Rahim", Title = "Assistant" });
            employees.Add(new Employee() { Name = "Saeed", Title = "Administrator" });
            return employees;
        }
در کد بالا از نوع داده‌ی جنریک ObservableCollection برای برگردان لیست کارمندان استفاده کردیم.
توجه ObservableCollection در فضای نام System.Collections.ObjectModel قرار دارد.

ObservableCollection  چیست؟
یک مجموعه‌ی پویا (Dynamic Collection) است که بر اثر عملیات‌هایی همچون به‌روز رسانی، حذف و ایجاد اشیاء در آن، یک اعلان صادر می‌شود و View از این اعلان مطلع می‌شود؛ شبیه به کاری که INotifyPropertyChanged انجام می‌دهد. 
در اینجا کلاس Employee با پیاده سازی اینترفیس INPC (معرفی شده در قسمت قبل) به یک کلاس Observable تبدیل شده است. بدین معنی که هر تغییری در وضعیت خصوصیات خود را اعلان می‌کند. با قرار دادن این شیء Observable در یک مجموعه که خود نیز از نوع Observable می‌باشد، از این پس هر تغییری در مجموعه نیز مستقیما View ما را تحت تاثیر قرار می‌دهد.
برای درک بهتر این موضوع در بخش markup، یک Combobox را قرار می‌دهیم:
<ComboBox Name="President" ItemsSource="{Binding}" FontSize="30"
                  Height="50"
                  Width="550">
            <ComboBox.ItemTemplate>
                <DataTemplate>
                    <StackPanel Orientation="Horizontal">
                        <TextBlock Text="{Binding Name}"/>
                        <TextBlock Text="{Binding Title}" Margin="5,0,0,0"/>
                    </StackPanel>
                </DataTemplate>
            </ComboBox.ItemTemplate>
 </ComboBox>
در کد xaml بالا چند نکته اهمیت دارد:
• در Tag مربوط به تعریف Combobox از طریق خصوصیت ItemSource اعلام کردیم که عملیات DataBinding اتفاق خواهد افتاد. پس Combobox ما به منبع داده‌ی پیش فرض در WPF که همان DataContext است مرتبط می‌شود. برای پر کردن منبع داده نیز همانند بخش قبل از طریق سازنده اقدام می‌کنیم.
 private ObservableCollection<Employee> employees;
        public MainWindow()
        {
            InitializeComponent();
            employees = Employee.GetEmployees();
            DataContext = employees;
        }
• برای نمایش عناصر در ComboBox، کار کمی متفاوت‌تر از عملیات انقیاد در یک TextBlock است. در کنترل‌هایی که قرار است لیستی از داده‌ها را پس از عملیات انقیاد نمایش دهند، می‌بایستی قالب نمایش داده‌ها مشخص شود.
حال باید نحوه‌ی نمایش و اعضایی از مجموعه که قرار است، در کنترل به نمایش در آیند مشخص شوند. در اینجا از Combobox.ItemTemplate برای مشخص کردن قالب و همچنین از DataTemplate، برای مشخص کردن داده‌های منتخب ما از مجموعه، استفاده شده است.
بعد از اجرای برنامه اطلاعات نام و عنوان، در Combobox نمایش داده می‌شوند. برای تست ویژگی Observable بودن مجموعه هم کافی است یک دکمه را بر روی صفحه قرار دهید و یک آیتم جدید را به لیست employees واقع در بخش CodeBehind اضافه کنید. مشاهده خواهید کرد که View ما با تغییر منبع داده، بطور اتوماتیک به‌روز می‌شود.

 انقیاد عناصر Element Binding 
در این بخش قصد داریم از یک کنترل به‌عنوان منبع داده استفاده کنیم. کنترل منتخب ما در این مثال یک Slider می‌باشد. در بخش markup کد زیر را اضافه می‌کنیم:
<Grid>
        <StackPanel Orientation="Horizontal">
            <Slider Name="mySlider" Minimum="0" Maximum="100"
                    Width="300"/>
            <TextBlock Margin="5" Text="{Binding Value,ElementName=mySlider}"/>
        </StackPanel>
    </Grid>
Value خصوصیتی است که مقدار Slider را برمی گرداند. پس از اجرای برنامه و حرکت ولوم اسلایدر مشاهده می‌کنید که مقدار جاری اسلایدر در textblock نمایش داده می‌شود.

Data Conversion 
برای درک بهتر مفهوم تبدیل داده در Data Binding با یک مثال کار را شروع می‌کنیم. به کلاس Employee موجود در بخش قبل، یک خصوصیت جدید را به نام تاریخ تولد، اضافه می‌کنیم:
public DateTime BornDate
        {
            get { return _bornDate; }
            set
            {
                _bornDate = value;
                OnPropertyChanged();
            }
        }
و متدی که لیستی از پرسنل را برای ما تولید می‌کرد، به شکل زیر بازنویسی می‌کنیم:
public static ObservableCollection<Employee> GetEmployees()
        {
            var employees = new ObservableCollection<Employee>();
            employees.Add(new Employee()
            { Name = "Mahdi", Title = "Manager", BornDate = DateTime.Parse("2008/8/8") });
            employees.Add(new Employee()
            { Name = "Nima", Title = "Teacher", BornDate = DateTime.Parse("2012/3/14") });
            employees.Add(new Employee()
            { Name = "Rahim", Title = "Assistant", BornDate = DateTime.Parse("2009/11/18") });
            employees.Add(new Employee()
            { Name = "Saeed", Title = "Administrator", BornDate = DateTime.Parse("2014/7/28") });
            return employees;
        }
حال قصد داریم اطلاعات را در یک ListBox، در بخش Markup نمایش دهیم:
<ListBox ItemsSource="{Binding}"
                 BorderThickness="1" >
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <StackPanel Orientation="Horizontal">
                        <TextBlock Text="{Binding Name}" Width="100"/>
                        <TextBlock Text="{Binding Title}" Width="100" Margin="5,0,0,0"/>
                        <TextBlock Text="{Binding BornDate}"  Margin="5,0,0,0"/>
                    </StackPanel>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>
 همانطور که قبلا اشاره شد، در نمایش لیست‌ها باید قالب و داده‌های مورد نظر برای نمایش، مشخص شوند.
پس از اجرا، خروجی برنامه به شکل زیر است:
 

 همه چیز همانطور که انتظار داشتیم به نمایش درآمد؛ اما اگر بخواهیم تاریخ میلادی، در زمان نمایش در View، بصورت شمسی نمایش داده شود، چطور؟
عملیات تبدیل داده یا همان Data Conversion در اینجا به کمک ما می‌آید.
به شکل زیر دقت کنید:

در زمانیکه در عملیات Data Binding نوع داده‌ی خصوصیت ما در Source (منبع داده) با نوع داده‌ی خصوصیت ما در target  (کنترل یا View) متفاوت است، به یک مبدل در حین Binding نیاز داریم. این کار را از طریق یک کلاس که اینترفیس IValueConvertor را پیاده سازی کرده است، انجام می‌دهیم.

  Converter به معنای مبدل است و اینجا وظیفه‌ی تبدیل مقدار گرفته شده‌ی از منبع داده را به نوع مورد نظر ما در View، دارد.
در مثال ذکر شده می‌خواهیم تاریخ میلادی موجود در منبع داده را به یک رشته (تاریخ شمسی) تبدیل کنیم و در View نمایش دهیم. (تبدیل نوع داده‌ی میلادی به رشته)
کلاسی را با نام DateConverter ایجاد می‌کنیم و اینترفیس IValueConverter را به شکل زیر پیاده سازی می‌کنیم:
public class DateConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            DateTime date = (DateTime)value;
            PersianCalendar pc = new PersianCalendar();
            var persianDate = string.Format
                ($"{pc.GetYear(date)}/{pc.GetMonth(date)}/{pc.GetDayOfMonth(date)}");
            return persianDate;
        }
        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }
اینترفیس IValueConverter دو متد دارد:
• Convert جهت تبدیل اطلاعات از مبداء به مقصد 
• ConvertBack برای تبدیل اطلاعات از مقصد به مبداء
در متد Convert همانطور که می‌بینید مقدار تاریخ میلادی ارسال شده، به یک رشته تبدیل شده و بازگردانده می‌شود.

حال برای استفاده از این مبدل باید مراحل زیر انجام شود: 
• اضافه کردن فضای نام مبدل به بخش فضاهای نام در View؛ که در اینجا همان آدرس پروژه‌ی جاری است:
 xmlns:local="clr-namespace:DataConversion"
• ایجاد یک شیء از مبدل و اضافه کردن آن به بخش Resource‌ها در view جاری:
<Window.Resources>
        <local:DateConverter x:Key="MyConverter"/>
    </Window.Resources>
markup مربوط به Resource یک پارامتر می‌گیرد و آن هم Key  است. Key کلید آیتم موجود در دیکشنری Resource است. اینجا کلید را MyConverter تعریف کردیم تا در بخش بعدی به‌راحتی بتوانیم از طریق آن اطلاعات را از Resource بازیابی کنیم.
• ارجاع به Static Resource که در مرحله‌ی قبل مقدار دهی کردیم:
<TextBlock Text="{Binding BornDate,Converter={StaticResource MyConverter}}"  Margin="5,0,0,0"/>
پس از طی مراحل بالا، برنامه را مجددا اجرا کنید. اینبار خروجی به شکل زیر است:

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

مدیریت روابط در RavenDB

یکی از اصول طراحی مدل‌ها در RavenDB، مستقل بودن اسناد یا documents است. به این ترتیب کلیه اطلاعاتی که یک سند نیاز دارد، داخل همان سند ذخیره می‌شوند (به این نوع شیء،  Root Aggregate هم گفته می‌شود). اما این اصل سبب نخواهد شد تا نتوان یا نباید ارتباطی را بین اسناد تعریف کرد. بنابراین سؤال مهم اینجا است که چه اطلاعات مرتبطی باید داخل یک سند ذخیره شوند و چه اطلاعاتی باید به سند دیگری ارجاع داده شوند. برای پاسخ به این سؤال سه روش ذیل را باید مدنظر داشت:

الف) Denormalized references
فرض کنید در دنیای رابطه‌ای دو جدول سفارش و مشتری را دارید. در این حالت، جدول سفارش تنها شماره آی دی اطلاعات مشتری را از جدول مشتری یا کاربران سیستم، در خود ذخیره خواهد کرد. به این ترتیب از تکرار اطلاعات مشتری در جدول سفارشات جلوگیری می‌گردد. اما اگر اطلاعات پرکاربرد مشتری را در داخل جدول سفارش قرار دهیم به آن denormalized reference گفته می‌شود.
ایجاد denormalized reference یکی از روش‌های مرسوم در دنیای NoSQL و RavenDB است؛ خصوصا جهت سهولت نمایش اطلاعات. به این ترتیب ارجاع به سندهای دیگر کمتر شده و ترافیک شبکه نیز کاهش می‌یابد. برای مثال در اینجا نام و آدرس مشتری را داخل سند ثبت شده قرار می‌دهیم و از سایر اطلاعات او (که اهمیت نمایشی ندارند) مانند کلمه عبور و امثال آن صرفنظر خواهیم کرد.
اینجا است که یک سری از سؤالات مطرح خواهند شد مانند : «اگر آدرس مشتری تغییر کرد، چطور؟»
بنابراین بهترین حالت استفاده از روش denormalized references محدود خواهد شد به موارد ذیل:
الف) قید اطلاعاتی که به ندرت تغییر می‌کنند. برای مثال نام یک شخص یا نام یک کشور، استان یا شهر.
ب) ثبت اطلاعات تکراری که در طول زمان تغییر می‌کنند، اما باید تاریخچه‌ی آن‌ها حفظ شوند. برای مثال اگر آدرس مشتری تغییر کرده است، واقعا اجناس سندهای قبلی او، صرفنظر از آدرس جدیدی که اعلام کرده است، به آدرس قبلی او ارسال شده‌اند و این تاریخچه باید در سیستم حفظ شوند.
ج) اطلاعاتی که ممکن است بعدها حذف شوند؛ اما نیاز است سابقه اسناد قبلی تخریب نشوند. برای مثال کارخانه‌ای را درنظر بگیرید که امسال یک سری چینی خاص را تولید می‌کند و می‌فروشد. سال بعد خط تولید خود را عوض کرده و سری اجناس دیگری را شروع به تولید و فروش خواهد کرد. در بانک‌های اطلاعاتی رابطه‌ای نمی‌توان اجناسی را که در جداول دیگر ارجاع دارند، به این سادگی‌ها حذف کرد. در اینجا باید از روش‌هایی مانند تعریف فیلد بیتی IsDeleted برای مخفی کردن ظاهری رکوردهای موجود کمک گرفت. اما در دنیای رابطه‌ای، اطلاعات مهم محصول را در سند اصلی ثبت کنید. بعد هر زمانیکه نیازی به محصول نبود، کلا تعریف آن‌را حذف نمائید.


ب) Includes
Includes در RavenDB برای پوشش مشکلات denormalization ارائه شده است. در اینجا بجای اینکه یک شیء کپی اطلاعات پرکاربرد شیء‌ایی دیگر را در خود ذخیره کند، تنها ارجاعی (یک Id رشته‌ای) از آن شیء را در سند مرتبط ذخیره خواهد کرد.
public class Order
{
    public string CustomerId { get; set; }
    public LineItem[] LineItems { get; set; }
    public double TotalPrice { get; set; }
}
 
public class Customer
{
    public string Name { get; set; }
    public string Address { get; set; }
    public short Age { get; set; }
    public string HashedPassword { get; set; }
}
برای نمونه در کلاس Order شاهد یک Id رشته‌ای ارجاع دهنده به کلاس Customer هستیم. هرگاه که نیاز به بارگذاری اطلاعات شیء Order به همراه کل اطلاعات مشتری او تنها در یک رفت و برگشت به بانک اطلاعاتی باشد، می‌توان از متد الحاقی Include مختص RavenDB استفاده کرد:
var order = session.Include<Order>(x => x.CustomerId)
                   .Load("orders/1234");
 
// این کوئری از کش سشن خوانده می‌شود و کاری به سرور ندارد
var cust = session.Load<Customer>(order.CustomerId);
همانطور که مشاهده می‌کنید، با ذکر متد Include، اعلام کرده‌ایم که مایل هستیم تا اطلاعات سند مشتری متناظر را نیز داشته باشیم. در این حالت در Load بعدی که بر اساس Id مشتری انجام شده، دیگر رفت و برگشتی به سرور انجام نشده و اطلاعات مشتری از کش سشن جاری که پیشتر با فراخوانی Include مقدار دهی شده است، دریافت می‌گردد.
حتی می‌توان چند سند مرتبط را با هم بارگذاری کرد؛ با حداقل رفت و برگشت به سرور:
var orders = session.Include<Order>(x => x.CustomerId)
    .Load("orders/1234", "orders/4321");
 
foreach (var order in orders)
{
    // این کوئری‌ها سمت کلاینت هستند و به سرور ارسال نمی‌شوند
    var cust = session.Load<Customer>(order.CustomerId);
}
همچنین امکان استفاده از متد Include در LINQ API نیز پیش بینی شده است. برای این منظور باید از متد Customize استفاده کرد:
var orders = session.Query<Order>()
    .Customize(x => x.Include<Order>(o => o.CustomerId))
    .Where(x => x.TotalPrice > 100)
    .ToList();
 
foreach (var order in orders)
{
    // این کوئری‌ها سمت کلاینت اجرا می‌شوند
    var cust = session.Load<Customer>(order.CustomerId);
}


Includeهای یک به چند

اکنون فرض کنید به کلاس سفارش، آرایه تامین کننده‌ها نیز افزوده شده است (رابطه یک به چند):
public class Order
{
    public string CustomerId { get; set; }
    public string[] SupplierIds { get; set; }
    public LineItem[] LineItems { get; set; }
    public double TotalPrice { get; set; }
}
بارگذاری یکباره روابط یک به چند نیز با Include میسر است:
var orders = session.Include<Order>(x => x.SupplierIds)
    .Load("orders/1234", "orders/4321");
 
foreach (var order in orders)
{
    foreach (var supplierId in order.SupplierIds)
    {
        // از کش سشن خوانده می‌شود
        var supp = session.Load<Supplier>(supplierId);
    }
}



Includeهای چند سطحی

در اینجا کلاس سفارشی را در نظر بگیرید که دارای خاصیت ارجاع دهنده نیز هست. این خاصیت به شکل یک کلاس تعریف شده است و نه به شکل  یک آی دی رشته‌ای:
public class Order
{
    public string CustomerId { get; set; }
    public string[] SupplierIds { get; set; }
    public Referral Refferal { get; set; }
    public LineItem[] LineItems { get; set; }
    public double TotalPrice { get; set; }
}

public class Referral
{
    public string CustomerId { get; set; }
    public double CommissionPercentage { get; set; }
}
متد Include امکان ارجاع به خواص تو در تو را نیز دارد:
var order = session.Include<Order>(x => x.Refferal.CustomerId)
    .Load("orders/1234");
 
// از کش سشن خوانده می‌شود
var referrer = session.Load<Customer>(order.Refferal.CustomerId);
همچنین این متد با مجموعه‌ها نیز کار می‌کند. برای مثال اگر تعریف متد LineItem به صورت زیر باشد:
public class LineItem
{
    public string ProductId { get; set; }
    public string Name { get; set; }
    public int Quantity { get; set; }
    public double Price { get; set; }
}
برای بارگذاری یکباره اسناد مرتبط می‌توان به روش ذیل عمل کرد:
var order = session.Include<Order>(x => x.LineItems.Select(li => li.ProductId))
    .Load("orders/1234");
 
foreach (var lineItem in order.LineItems)
{
    // از کش سمت کلاینت خوانده می‌شود
    var product = session.Load<Product>(lineItem.ProductId);
}

و به صورت خلاصه برای باگذاری اسناد مرتبط، دیگر از دو کوئری پشت سر هم ذیل استفاده نکنید:
var order = session.Load<Order>("orders/1");
var customer = session.Load<Customer>(order.CustomerId);
این دو کوئری یعنی دوبار رفت و برگشت به سرور. با استفاده از Include می‌توان تعداد رفت و برگشت‌ها و همچنین ترافیک شبکه را کاهش داد. به علاوه سرعت کار نیز افزایش خواهد یافت.


ج) تفاوت بین Reference و Relationship

برای درک اینکه آیا اطلاعات یک شیء مرتبط را بهتر است داخل شیء اصلی (Aggregate rooe) ذخیره کرد یا خیر، باید مفاهیم ارجاع و ارتباط را بررسی کنیم.
اگر به مثال سفارش و مشتری دقت کنیم، یک سفارش را بدون مشتری نیز می‌توان تکمیل کرد. برای مثال بسیاری از فروشگاه‌ها به همین نحو عمل می‌کنند و اگر شماره Id مشتری را به سندی اضافه می‌کنیم، صرفا جهت این است که بدانیم این سند متعلق به شخص دیگری نیست. بنابراین «ارجاعی» به کاربر در جدول سفارش می‌تواند وجود داشته باشد.
اکنون اقلام سفارش را درنظر بگیرید. هر آیتم سفارش تنها با بودن آن سفارش خاص است که معنا پیدا می‌کنند و نه بدون آن. این آیتم می‌تواند ارجاعی به محصول مرتبط داشته باشد. اینجا است که می‌گوییم اقلام سند با سفارش «در ارتباط» هستند؛ اما یک سند ارجاعی دارد به مشتری.
از این دو مفهوم برای تشخیص تشکیل Root Aggregate استفاده می‌شود. به این ترتیب تشخیص داده‌ایم اقلام سند، Root Aggregate را تشکیل می‌دهند؛ بنابراین ذخیره سازی تمام آن‌ها داخل یک سند RavenDB معنا پیدا می‌کند.


چند مثال برای درک بهتر نحوه طراحی اسناد در RavenDB

الف) Stackoverflow
صفحه نمایش یک سؤال و پاسخ‌های آن و همچنین رای‌های هر آیتم را درنظر بگیرید. در اینجا کاربران همزمانی ممکن است به یک سؤال رای بدهند، پاسخ‌هایی را ارائه دهند و یا کاربر اصلی، سؤال خویش را ویرایش کند. به این ترتیب با قرار دادن کلیه آیتم‌های این سند داخل آن، به مشکلات همزمانی برخواهیم خورد. برای مثال واقعا نمی‌خواهیم که به علت افزوده شدن یک پاسخ، کل سند قفل شود.
بنابراین ذخیره سازی سؤال در یک سند و ذخیره سازی لیست پاسخ‌ها در سندی دیگر، طراحی بهتری خواهد بود.

ب) سبد خرید و آیتم‌های آن
زمانیکه کاربری مشغول به خرید آنلاین از سایتی می‌شود، لیست اقلام انتخابی او یک سفارش را تشکیل داده و به تنهایی معنا پیدا نمی‌کنند. به همین جهت ذخیره سازی اقلام سفارش به صورت یک Root aggregate در اینجا مفهوم داشته و متداول است.

ج) یک بلاگ و کامنت‌های آن
در اینجا نیز کاربران، مجزای از مطلب اصلی ارسال شده ممکن است نظرات خود را ویرایش کنند یا اینکه بخواهیم نظرات را جداگانه لیست کنیم. بنابراین این دو (مطالب و نظرات) موضوعاتی جداگانه بوده و نیازی نیست به صورت یک Root aggregate تعریف شوند.

بنابراین در حین طراحی اسناد NoSQL باید به اعمال و «محدوده‌های تراکنشی» انجام شده دقت داشت تا اینکه صرفا عنوان شود این یک رابطه یک به چند یا چند به چند است.
مطالب
آشنایی با مفهوم Indexer در C#.NET
زمانی که صحبت از Indexer می‌شود، بطور ناخوداگاه ذهنمان به سمت آرایه‌ها می‌رود. آرایه‌ها در واقع ساده‌ترین اشیاء ی هستند که مفهوم Index در آنها معنا دار است.
اگر با آرایه‌ها کار کرده باشید با عملگر [] در سی شارپ آشنایی دارید. یک Indexer در واقع نوع خاصی از خاصیت (property) است که در بدنه کلاس تعریف می‌شود و به ما امکان استفاده از عملگر [] را برای نمونه کلاس فراهم می‌کند.
همانطور که به شباهت Indexer و Property اشاره شد نحوه تعریف Indexer بصورت زیر می‌باشد:
public type this [type identifier]
{
     get{ ... }
     set{ ... }
}
در پیاده سازی Indexer به نکات زیر دقت کنید:
  • از کلمه کلیدی this برای نعریف Indexer استفاده می‌شود و یک Indexer نام ندارد.
  • از دستیاب get برای برگرداندن مقدار و از دستیاب set جهت مقدار دهی و انتساب استفاده می‌شود.
  • الزامی به پیاده سازی Indexer با مقادیر صحیح عددی نیست و شما می‌توانید Indexer ی با پارامترهایی از نوع string یا double داشته باشید.
  • Indexer‌ها قابلیت سربارگذاری (overloaded) دارند.
  • Indexer‌ها می‌توانند چندین پارامتری باشند مانند آرایه‌های دو بعدی که دو پارامتری هستند.
به مثالی جهت پیاده سازی کلاس ماتریس توجه کنید:
public class Matrix
{
    // فیلدها
    private int _row, _col;
    private readonly double[,] _values;

    // تعداد ردیف‌های ماتریس
    public int Row
    {
        get { return _row; }
        set
        {
            _row = value > 0 ? value : 3;
        }
    }

    // تعداد ستون‌های ماتریس
    public int Col
    {
        get { return _col; }
        set
        {
            _col = value > 0 ? value : 3;
        }
    }

    // نعریف یک ایندکسر
    public double this[int r, int c]
    {
        get { return Math.Round(_values[r, c], 3); }
        set { _values[r, c] = value; }
    }

    // سازنده 1
    public Matrix()
    {
        _values = new double[_row,_col];
    }

    // سازنده 2
    public Matrix(int row, int col)
    {
        _row = row;
        _col = col;
        _values = new double[_row,_col];
    }

}
نحوه استفاده از کلاس ایجاد شده:
public class UseMatrixIndexer
{
    // ایجاد نمونه از شیء ماتریس
    private readonly Matrix m = new Matrix(5, 5);

    private double item;

    public UseMatrixIndexer()
    {
        // دسترسی به عنصر واقع در ردیف چهار و ستون سه
        item = m[4, 3];
    }
}
مطالب
آشنایی با ساختار IIS قسمت نهم
در قسمت قبلی  ما یک هندلر ایجاد کردیم و درخواست‌هایی را که برای فایل jpg و به صورت GET ارسال میشد، هندل می‌کردیم و تگی را در گوشه‌ی تصویر درج و آن را در خروجی نمایش میدادیم. در این مقاله قصد داریم که کمی هندلر مورد نظر را توسعه دهیم و برای آن یک UI یا یک رابط کاربری ایجاد نماییم. برای توسعه دادن ماژولها و هندلر‌ها ما یک dll نوشته و باید آن را در GAC که مخفف عبارت Global Assembly Cache ریجستر کنیم.


جهت اینکار یک پروژه از نوع class library ایجاد کنید. فایل class1.cs را که به طور پیش فرض ایجاد می‌شود، حذف کنید و رفرنس‌های Microsoft.Web.Management.dll و Microsoft.Web.Administration.dll را از مسیر زیر اضافه کنید:
\Windows\system32\inetsrv
اولین رفرنس شامل کلاس‌هایی است که جهت ساخت ماژول‌ها برای کنسول IIS مورد نیاز است و دومی هم برای خواندن پیکربندی‌های نوشته شده مورد استفاده قرار می‌گیرد.
برای طراحی UI  بر پایه winform باید رفرنس‌های System.Windows.Forms.dll و System.Web.dll را از سری اسمبلی‌های دات نت نیز اضافه کنیم و در مرحله‌ی بعدی جهت ایجاد امضاء یا strong name (^  و  ^  ) به خاطر ثبت در GAC پروژه را انتخاب و وارد Properties پروژه شوید. در تب signing گزینه sign the assembly را تیک زده و در لیست باز شده گزینه new را انتخاب نمایید و نام  imageCopyrightUI را به آن نسبت داده و گزینه تعیین کلمه عبور را غیرفعال کنید و تایید و تمام. الان باید یک فایل snk مخفف strong name key ایجاد شده باشد تا بعدا با استفاده از این کلید dll ایجاد شده را در GAC ریجستر کنیم.

در مرحله بعدی در تب Build Events کد زیر را در بخش Post-build event command line اضافه کنید. این کد باعث می‌شود بعد از هر بار کامپایل پروژه، به طور خودکار در GAC ثبت شود:

call "%VS80COMNTOOLS%\vsvars32.bat" > NULL 
gacutil.exe /if "$(TargetPath)"

نکته:در صورتی که از VS2005 استفاده می‌کنید در تب Debug در قسمت Start External Program مسیر زیر را قرار بدهید. اینکار برای تست و دیباگینگ پروژه به شما کمک خواهد کرد. این تنظیم شامل نسخه‌های اکسپرس نمی‌شود.
 \windows\system32\inetsrv\inetmgr.exe

بعد از پایان اینکار پروژه را Rebuild کنید. با اینکار dll در GAC ثبت می‌شود. استفاده از سوییچ‌های if به طور همزمان در درستور gacutil به معنی این هست که اگر اولین بار است نصب می‌شود، پس با سوییچ i نصب کن. ولی اگر قبلا نصب شده است نسخه جدید را به هر صورتی هست جایگزین قبلی کن یا همان reinstall کن.
 
ساخت یک Module Provider
رابط‌های کاربری IIS همانند هسته و کل سیستمش، ماژولار و قابل خصوصی سازی است. رابط کاربری، مجموعه‌ای از ماژول هایی است که میتوان آن‌ها را حذف یا جایگزین کرد. تگ ورودی یا معرفی برای هر UI یک module provider است. خیلی خودمانی، تگ ماژول پروایدر به معرفی یک UI در IIS می‌پردازد. لیستی از module provider‌ها را می‌توان در فایل زیر در تگ بخش <modules> پیدا کرد.
%windir%\system32\inetsrv\Administration.config

در اولین گام یک کلاس را به اسم imageCopyrightUIModuleProvider.cs ایجاد کرده و سپس آن‌را به کد زیر، تغییر می‌دهیم. کد زیر با استفاده از ModuleDefinition  یک نام به تگ Module Provider داده و کلاس imageCopyrightUI را که بعدا تعریف می‌کنیم، به عنوان مدخل entry رابط کاربری معرفی کرده:

using System;
using System.Security;
using Microsoft.Web.Management.Server;
    
namespace IIS7Demos           
{
    class imageCopyrightUIProvider : ModuleProvider
    {
        public override Type ServiceType              
        {
            get { return null; }
        }

        public override ModuleDefinition GetModuleDefinition(IManagementContext context)
        {
            return new ModuleDefinition(Name, typeof(imageCopyrightUI).AssemblyQualifiedName);
        }

        public override bool SupportsScope(ManagementScope scope)
        {
            return true;
        }
    }            
}

با ارث بری از کلاس module provider، سه متد بازنویسی می‌شوند که یکی از آن ها SupportsScope هست که میدان عمل پروایدر را مشخص می‌کند، مانند اینکه این پرواید در چه میدانی باید کار کند که می‌تواند سه گزینه‌ی server,site,application باشد. در کد زیر مثلا میدان عمل application انتخاب شده است ولی در کد بالا با برگشت مستقیم true، همه‌ی میدان را جهت پشتیبانی از این پروایدر اعلام کردیم.

 public override bool SupportsScope(ManagementScope scope)
        {
            return (scope == ManagementScope.Application) ;
        }

حالا که پروایدر (معرف رابط کاربری به IIS) تامین شده، نیاز است قلب کار یعنی ماژول معرفی گردد. اصلی‌ترین متدی که باید از اینترفیس ماژول پیاده سازی شود متد initialize است. این متد جایی است که تمام عملیات در آن رخ می‌دهد. در کلاس زیر imageCopyrightUI ما به معرفی مدخل entry رابط کاربری می‌پردازیم. در سازنده‌های این متد، پارامترهای نام، صفحه رابط کاربری وتوضیحی در مورد آن است. تصویر کوچک و بزرگ جهت آیکن سازی (در صورت عدم تعریف آیکن، چرخ دنده نمایش داده می‌شود) و توصیف‌های بلندتر را نیز شامل می‌شود.

 internal class imageCopyrightUI : Module
    {
        protected override void Initialize(IServiceProvider serviceProvider, ModuleInfo moduleInfo)
        {            
            base.Initialize(serviceProvider, moduleInfo);
            IControlPanel controlPanel = (IControlPanel)GetService(typeof(IControlPanel));
            ModulePageInfo modulePageInfo = new ModulePageInfo(this, typeof(imageCopyrightUIPage), "Image Copyright", "Image Copyright",Resource1.Visual_Studio_2012,Resource1.Visual_Studio_2012);
            controlPanel.RegisterPage(modulePageInfo);
        }
    }

شیء ControlPanel مکانی است که قرار است آیکن ماژول نمایش داده شود. شکل زیر به خوبی نام همه قسمت‌ها را بر اساس نام کلاس و اینترفیس آن‌ها دسته بندی کرده است:

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

 ModulePage   شامل اساسی‌ترین متدها و سورس‌ها شده و هیچگونه رابط کاری ویژه‌ای را در اختیار شما قرار نمی‌دهد. تنها یک صفحه‌ی خام به شما می‌دهد که می‌توانید از آن استفاده کرده یا حتی با ارث بری از آن، کلاس‌های جدیدتری را برای ساخت صفحات مختلف و ویژه‌تر بسازید. در حال حاضر که هیچ کدام از ویژگی‌های IIS فعلی از این کلاس برای ساخت رابط کاربری استفاده نکرده‌اند.
 ModuleDialogPage   یک صفحه شبیه به دیالوگ را ایجاد می‌کند و شامل دکمه‌های Apply و Cancel میشود به همراه یک سری متدهای اضافی‌تر که اجازه‌ی override کردن آنها را دارید. همچنین یک سری از کارهایی چون refresh  و از این دست عملیات خودکار را نیز انجام میدهد. از نمونه رابط‌هایی که از این صفحات استفاده می‌کنند میتوان  machine key و management service را اسم برد.
 ModulePropertiesPage   این صفحه یک رابط کاربری را شبیه پنجره property که در ویژوال استادیو وجود دارد، در دسترس شما قرار میدهد. تمام عناصر آن در یک حالت گرید grid لیست می‌شوند. از نمونه‌های موجود میتوان به CGI,ASP.Net Compilation اشاره کرد.
 ModuleListPage   این کلاس برای مواقعی کاربرد دارد که شما قرار است لیستی از آیتم‌ها را نشان دهید. در این صفحه شما یک ListView دارید که میتوانید عملیات جست و جو، گروه بندی و نحوه‌ی نمایش لیست را روی آن اعمال کنید.
در این مثال ما از اولین کلاس نامبرده که پایه‌ی همه کلاس هاست استفاده می‌کنیم. کد زیر را در کلاسی به اسم imageCopyrightUIPage  می‌نویسیم:
    public sealed class imageCopyrightUIPage : ModulePage
    {
        public string message;
        public bool featureenabled;
        public string color;

        ComboBox _colCombo = new ComboBox();
        TextBox _msgTB = new TextBox();
        CheckBox _enabledCB = new CheckBox();

        public imageCopyrightUIPage()
        {
            this.Initialize();
        }


        void Initialize()
        {
            Label crlabel = new Label();
            crlabel.Left = 50;
            crlabel.Top = 100;
            crlabel.AutoSize = true;
            crlabel.Text = "Enable Image Copyright:";
            _enabledCB.Text = "";
            _enabledCB.Left = 200;
            _enabledCB.Top = 100;
            _enabledCB.AutoSize = true;

            Label msglabel = new Label();
            msglabel.Left = 150;
            msglabel.Top = 130;
            msglabel.AutoSize = true;
            msglabel.Text = "Message:";
            _msgTB.Left = 200;
            _msgTB.Top = 130;
            _msgTB.Width = 200;
            _msgTB.Height = 50;

            Label collabel = new Label();
            collabel.Left = 160;
            collabel.Top = 160;
            collabel.AutoSize = true;
            collabel.Text = "Color:";
            _colCombo.Left = 200;
            _colCombo.Top = 160;
            _colCombo.Width = 50;
            _colCombo.Height = 90;
            _colCombo.Items.Add((object)"Yellow");
            _colCombo.Items.Add((object)"Blue");
            _colCombo.Items.Add((object)"Red");
            _colCombo.Items.Add((object)"White");

            Button apply = new Button();
            apply.Text = "Apply";
            apply.Click += new EventHandler(this.applyClick);
            apply.Left = 200;
            apply.AutoSize = true;
            apply.Top = 250;

            Controls.Add(crlabel);
            Controls.Add(_enabledCB);
            Controls.Add(collabel);
            Controls.Add(_colCombo);
            Controls.Add(msglabel);
            Controls.Add(_msgTB);
            Controls.Add(apply);
        }

        public void ReadConfig()
        {
            try
            {
                ServerManager mgr;
                ConfigurationSection section;
                mgr = new ServerManager();
                Configuration config =
                mgr.GetWebConfiguration(
                       Connection.ConfigurationPath.SiteName,
                       Connection.ConfigurationPath.ApplicationPath +
                       Connection.ConfigurationPath.FolderPath);

                section = config.GetSection("system.webServer/imageCopyright");
                color = (string)section.GetAttribute("color").Value;
                message = (string)section.GetAttribute("message").Value;
                featureenabled = (bool)section.GetAttribute("enabled").Value;

            }

            catch
            { }

        }
      
        void UpdateUI()
        {
            _enabledCB.Checked = featureenabled;
            int n = _colCombo.FindString(color, 0);
            _colCombo.SelectedIndex = n;
            _msgTB.Text = message;
        }


        protected override void OnActivated(bool initialActivation)
        {
            base.OnActivated(initialActivation);
            if (initialActivation)
            {
                ReadConfig();
                UpdateUI();
            }
        }



        private void applyClick(Object sender, EventArgs e)
        {
            try
            {
                UpdateVariables();
                ServerManager mgr;
                ConfigurationSection section;
                mgr = new ServerManager();
                Configuration config =
                mgr.GetWebConfiguration
                (
                       Connection.ConfigurationPath.SiteName,
                       Connection.ConfigurationPath.ApplicationPath +
                       Connection.ConfigurationPath.FolderPath
                );

                section = config.GetSection("system.webServer/imageCopyright");
                section.GetAttribute("color").Value = (object)color;
                section.GetAttribute("message").Value = (object)message;
                section.GetAttribute("enabled").Value = (object)featureenabled;

                mgr.CommitChanges();

            }

            catch
            { }

        }

        public void UpdateVariables()
        {
            featureenabled = _enabledCB.Checked;
            color = _colCombo.Text;
            message = _msgTB.Text;
        }
    }
اولین چیزی که در کلاس بالا صدا زده می‌شود، سازنده‌ی کلاس هست که ما در آن یک تابع تعریف کردیم به اسم initialize که به آماده سازی اینترفیس یا رابط کاربری می‌پردازد و کنترل‌ها را روی صفحه می‌چیند. این سه کنترل، یکی Combox برای تعیین رنگ، یک Checkbox برای فعال بودن ماژول و دیگری هم یک textbox جهت نوشتن متن است. مابقی هم که سه label برای نامگذاری اشیاست. بعد از اینکه کنترل‌ها روی صفحه درج شدند، لازم است که تنظیمات پیش فرض یا قبلی روی کنترل‌ها نمایش یابند که اینکار را به وسیله تابع readConfig انجام می‌دهیم و تنظیمات خوانده شده را در متغیر‌های عمومی قرار داده و با استفاده از تابع UpdateUI این اطلاعات را روی کنترل‌ها ست می‌کنیم و به این ترتیب UI به روز می‌شود. این دو تابع را به ترتیب پشت سر هم در یک متد به اسم OnActivated  که override کرده‌ایم صدا میزنیم. در واقع این متد یک جورایی همانند رویداد Load می‌باشد؛ اگر true برگرداند اولین فعال سازی رابط کاربری بعد از باز شدن IIS است و در غیر این صورت false بر میگرداند.

در صورتی که کاربر مقادیر را تغییر دهد و روی گزینه apply کلیک کند تابع applyClick اجرا شده و ابتدا به تابع UpdateVariables ارجاع داده میشود که در آن مقادیر خوانده شده و در متغیرهای Global قرار می‌گیرند و سپس با استفاده از دو شیء از نوع serverManger و ConfigSection جایگذاری یا ذخیره می‌شوند.
استفاده از دو کلاس Servermanager و Configsection در دو قسمت خواندن و نوشتن مقادیر به کار رفته‌اند. کلاس servermanager به ما اجازه دسترسی به تنظیمات IIS و قابلیت‌های آن را میدهد. در تابع ReadConfig مسیر وب سایتی را که در لیست IIS انتخاب شده است، دریافت کرده و به وب کانفیگ آن وب سایت رجوع نموده و تگ imageCopyright آن را که در تگ system.webserver قرار گرفته است، میخواند (در صورتی که این تگ در آن وب کانفیگ موجود نباشد، خواندن و سپس ذخیره مجدد آن روی تگ داخل فایل applicationHost.config اتفاق میفتد که نتیجتا برای همه‌ی وب سایت هایی که این تگ را ندارند یا مقدارهای پیش فرض آن را تغییر نداده‌اند رخ میدهد) عملیات نوشتن هم مشابه خواندن است. تنها باید خط زیر را در آخر برای اعمال تغییرات نوشت؛ مثل EF با گزینه Context.SaveChanges:
mgr.CommitChanges();
وقت آن است که رابط کاربری را به IIS اضافه کنیم: پروژه را Rebuild کنید. بعد از آن با خطوطی که قبلا در Post-Build Command نوشتیم باید dll ما در GAC ریجستر شود. برای همین آدرس زیر را در cmd تایپ کنید:
%vs110comntools%\vsvars32.bat
عبارت اول که مسیر ویژوال استودیوی  شماست و عدد 110 یعنی نسخه‌ی 11. هر نسخه‌ای را که استفاده میکنید، یک صفر جلویش بگذارید و جایگزین عدد بالا کنید. مثلا نسخه 8 می‌شود 80 و فایل بچ بالا هم دستورات visual studio را برای شما آزاد می‌کند.
سپس دستور زیر را وارد کنید:
GACUTIL /l ClassLibrary1
کلمه classLibrary1 نام پروژه‌ی ما بود که در GAC ریجستر شده است. با سوییچ l تمامی اطلاعات اسمبلی‌هایی که در GAC ریجستر شده‌اند، نمایش می‌یابند. ولی اگر اسم آن اسمبلی را جلویش بنویسید، فقط اطلاعات آن اسمبلی نمایش میابد. با اجرای خط فوق میتوانیم کلید عمومی public key اسمبلی خود را بدانیم که در شکل زیر مشخص شده است:

پس اگر کلید را دریافت کرده‌اید، خط زیر را به فایل administration.config در تگ <ModuleProviders> اضافه کنید:
<add name="imageCopyrightUI" type="ClassLibrary1.imageCopyrightUIProvider,   ClassLibrary1, Version=1.0.0.0, Culture=neutral, PublicKeyToken=d0b3b3b2aa8ea14b"/>
عبارت ClassLibrary1.imageCopyrightUIProvider به کلاس imageCopyrightUIProvider اشاره می‌کند که در این کلاس UI معرفی می‌شود. مابقی عبارت هم کاملا مشخص است و در لینک‌های بالا در مورد Strong name توضیح داده شده اند. 
فایل administration.config  در مسیر زیر قرار دارد:
%windir%\system32\inetsrv\config\administration.config
حالا تنها کاری که نیاز است، باز کردن IIS است. به بخش وب سایت‌ها رفته و اپلیکیشنی که قبلا با نام mypictures را ایجاد کرده بودیم، انتخاب کنید. در سمت راست، آخر لیست، بخش others باید ماژول ما دیده شود. بازش کنید و تنظمیات آن را تغییر دهید و حالا یک تصویر را از اپلیکیشن mypictures، روی مرورگر درخواست کنید تا تغییرات را روی تگ مشاهده کنید:

 
حالا دیگر باید ماژول نویسی برای IIS را فراگرفته باشیم. این ماژول‌ها می‌توانند از یک مورد ساده تا یک کلاس مهم و امنیتی باشند که روی سرور شما برای همه یا بعضی از وب سایت‌ها در حال اجرا هستند و در صورت لزوم و اجازه شما، برنامه نویس‌ها میتوانند مثل همه‌ی تگ‌های موجود در وب کانفیگ سایتی را که  مینویسند، تگ ماژول شما و  تنظیمات آن را با استفاده از attribute یا خصوصیت‌های تعریف شده، بر اساس سلایق و نیازهایشان تغییر دهند و روی سرور شما آپلود کنند. الان شما یک سرور خصوصی سازی شده دارید.
از آنجا که این مقاله طولانی شده است، باقی موارد ویرایشی روی این UI را در مقاله بعدی بررسی خواهیم کرد. 
مطالب
آشنایی با الگوی MVP

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

یکی از الگوهایی که شیوه‌ی صحیح این جدا سازی را ترویج می‌کند، الگوی MVP یا Model-View-Presenter می‌باشد. خلاصه‌ی این الگو به صورت زیر است:


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

View :
من می‌دانم که چگونه باید اطلاعاتی را به کاربر به شکلی بصری ارائه داد.
من می‌دانم که چگونه باید اعمالی مانند data binding و امثال آن را انجام داد.
من نمی‌دانم که چگونه باید منطق پردازشی موارد ذکر شده را فراهم آورم.

Presenter :
من می‌دانم که چگونه باید درخواست‌های رسیده کاربر به View را دریافت کرده و آن‌ها را به Model‌ انتقال دهم.
من می‌دانم که چگونه باید اطلاعات را به Model ارسال کرده و سپس نتیجه‌ی پردازش آن‌ها را جهت نمایش در اختیار View قرار دهم.
من نمی‌دانم که چگونه باید اطلاعاتی را ترسیم کرد (مشکل View است نه من) و نمی‌دانم که چگونه باید پردازشی را بر روی اطلاعات انجام دهم. (مشکل Model است و اصلا ربطی به اینجانب ندارد!)


یک مثال ساده از پیاده سازی این روش
برنامه‌ای وبی را بنویسید که پس از دریافت شعاع یک دایره از کاربر، مساحت ‌آن‌را محاسبه کرده و نمایش دهد.
یک تکست باکس در صفحه قرار خواهیم داد (txtRadius) و یک دکمه جهت دریافت درخواست کاربر برای نمایش نتیجه حاصل در یک برچسب به نام lblResult

الف) پیاده سازی به روش متداول (اسپاگتی کد)

protected void btnGetData_Click(object sender, EventArgs e)
{
lblResult.Text = (Math.PI * double.Parse(txtRadius.Text) * double.Parse(txtRadius.Text)).ToString();
}
بله! کار می‌کنه!
اما این مشکلات را هم دارد:
- منطق برنامه (روش محاسبه مساحت دایره) با رابط کاربر گره خورده.
- کدهای برنامه در پروژه‌ی دیگری قابل استفاده نیست. (شما متد یا کلاسی را این‌جا با قابلیت استفاده مجدد می‌توانید پیدا می‌کنید؟ آیا یکی از اهداف برنامه نویسی شیءگرا تولید کدهایی با قابلیت استفاده مجدد نبود؟)
- چگونه باید برای آن آزمون واحد نوشت؟

ب) بهبود کد و جدا سازی لایه‌ها از یکدیگر

در روش MVP متداول است که به ازای هر یک از اجزاء ابتدا یک interface نوشته شود و سپس این اینترفیس‌ها پیاده سازی گردد.

پیاده سازی منطق برنامه:

1- ایجاد Model :
یک فایل جدید را به نام CModel.cs به پروژه اضافه کرده و کد زیر را به آن خواهیم افزود:

using System;

namespace MVPTest
{
public interface ICircleModel
{
double GetArea(double radius);
}

public class CModel : ICircleModel
{
public double GetArea(double radius)
{
return Math.PI * radius * radius;
}
}
}
همانطور که ملاحظه می‌کنید اکنون منطق برنامه از موارد زیر اطلاعی ندارد:
- خبری از textbox و برچسب و غیره نیست. اصلا نمی‌داند که رابط کاربری وجود دارد یا نه.
- خبری از رخ‌دادهای برنامه و پاسخ دادن به آن‌ها نیست.
- از این کد می‌توان مستقیما و بدون هیچ تغییری در برنامه‌های دیگر هم استفاده کرد.
- اگر باگی در این قسمت وجود دارد، تنها این کلاس است که باید تغییر کند و بلافاصله کل برنامه از این بهبود حاصل شده می‌تواند بدون هیچگونه تغییری و یا به هم ریختگی استفاده کند.
- نوشتن آزمون واحد برای این کلاس که هیچگونه وابستگی به UI ندارد ساده است.


2- ایجاد View :
فایل دیگری را به نام CView.cs را به همراه اینترفیس زیر به پروژه اضافه می‌کنیم:

namespace MVPTest
{
public interface IView
{
string RadiusText { get; set; }
string ResultText { get; set; }
}
}

کار View دریافت ابتدایی مقادیر از کاربر توسط RadiusText و نمایش نهایی نتیجه توسط ResultText است البته با یک اما.
View نمی‌داند که چگونه باید این پردازش صورت گیرد. حتی نمی‌داند که چگونه باید این مقادیر را به Model جهت پردازش برساند یا چگونه آن‌ها را دریافت کند (به همین جهت از اینترفیس برای تعریف آن استفاده شده).

3- ایجاد Presenter :
در ادامه فایل جدیدی را به نام CPresenter.cs‌ با محتویات زیر به پروژه خواهیم افزود:

namespace MVPTest
{
public class CPresenter
{
IView _view;

public CPresenter(IView view)
{
_view = view;
}

public void CalculateCircleArea()
{
CModel model = new CModel();
_view.ResultText = model.GetArea(double.Parse(_view.RadiusText)).ToString();
}
}
}

کار این کلاس برقراری ارتباط با Model است.
می‌داند که چگونه اطلاعات را به Model ارسال کند (از طریق _view.RadiusText) و می‌داند که چگونه نتیجه‌ی پردازش را در اختیار View قرار دهد. (با انتساب آن به _view.ResultText)
نمی‌داند که چگونه باید این پردازش صورت گیرد (کار مدل است نه او). نمی‌داند که نتیجه‌ی نهایی را چگونه نمایش دهد (کار View است نه او).
روش معرفی View به این کلاس به constructor dependency injection معروف است.

اکنون کد وب فرم ما که در قسمت (الف) معرفی شده به صورت زیر تغییر می‌کند:

using System;

namespace MVPTest
{
public partial class _Default : System.Web.UI.Page, IView
{
protected void Page_Load(object sender, EventArgs e)
{
}

public string RadiusText
{
get { return txtRadius.Text; }
set { txtRadius.Text = value; }
}
public string ResultText
{
get { return lblResult.Text; }
set { lblResult.Text = value; }
}

protected void btnGetData_Click(object sender, EventArgs e)
{
CPresenter presenter = new CPresenter(this);
presenter.CalculateCircleArea();
}
}
}

در این‌جا یک وهله از Presenter برای برقراری ارتباط با Model ایجاد می‌شود. همچنین کلاس وب فرم ما اینترفیس View را نیز پیاده سازی خواهد کرد.