مطالب دوره‌ها
ویژگی StringLength و اعمال maxlength به صورت خودکار در ASP.NET MVC
خاصیت نام را که توسط ویژگی StringLength مزین شده است درنظر بگیرید:
[StringLength(10, ErrorMessage = "حداکثر 10 حرف")]
public string Name { set; get; }
قصد داریم در سمت کاربر، فیلد متناظر را طوری تنظیم کنیم که واقعا کاربر نتواند بیش از 10 حرف را وارد کند:
        $(function () {
            $("input[data-val-length-max]").each(function (i, e) {
                var input = $(e);
                var maxlength = input.attr("data-val-length-max");
                input.attr("maxlength", maxlength);
            });
        });
توضیحات این کدها به نحوه رندر ویژگی StringLength بر می‌گردد:
<input class="text-box single-line" data-val="true" data-val-length="حداکثر 10 حرف" 
data-val-length-max="10" data-val-required="(*)" id="Name" name="Name" type="text" value="" />
توسط قطعه کد جی‌کوئری نوشته شده، ابتدا کلیه inputهایی که دارای data-val-length-max هستند را می‌یابیم و در این بین مقدار data-val-length-max را یافته و سپس خاصیت maxlength آن‌ها را به صورت پویا تنظیم می‌کنیم. به این ترتیب کاربر دیگر نمی‌تواند رشته‌ای دلخواه را در ابتدای کار وارد نماید و به طول تنظیم شده محدود می‌گردد.
 
نظرات مطالب
فعال سازی و پردازش صفحات پویای افزودن، ویرایش و حذف رکوردهای jqGrid در ASP.NET MVC
- زمانیکه از یک اکشن متد، خروجی HTML دریافت می‌کنید، Content-Type آن مساوی text/html است. در حالت Web Api این مورد application/json یا حالات دیگر می‌تواند باشد (جهت دیباگ بهتر، برگه‌ی network فایرباگ را در این دو حالات با هم مقایسه کنید. بررسی کنید Response ارسالی چه محتوایی و چه Content-type ایی دارد).
- ضمنا نیازی نیست اطلاعات select را در سمت سرور تولید کنید. امکان دریافت JSON از سرور و تبدیل آن به فرمت مورد نظر در سمت کلاینت هم پیش بینی شده‌است:
editoptions: { dataUrl: '...url to get json....',
               buildSelect: function (response) {
                    var data = typeof response === "string" ?  $.parseJSON(response.responseText) : response,
                    var s = "<select>";
                    s += '<option value="0">--No Manager--</option>';
                    $.each(data, function () {
                          s += '<option value="' + this.EmployeeId + '">' + this.EmployeeName + '</option>';
                    });
                return s + "</select>";
              }
}
در این حالت dataUrl شیء JSON مدنظر را از سرور دریافت می‌کند (آرایه‌ای از EmployeeId و EmployeeName ها). در رویدادگردان سمت کاربر buildSelect، این مورد دریافت و پردازش می‌شود.
نظرات مطالب
فرم‌های مبتنی بر قالب‌ها در Angular - قسمت چهارم - اعتبارسنجی ورودی‌ها
در قسمت «بهبود اعتبارسنجی drop down» بنده قبلا در Angular 1.X برای این نوع اعتبار سنجی دایرکتیوی طراحی کرده بودم. این دایرکتیو لیست مقادیر غیر مجاز را دریافت میکرد و در صورتی که المنت یکی از این مقادیر را دریافت میکرد فرم invalid میشد. کدهای Angular 1.x به شکل زیر:
(function (app) {
    'use strict';

    function blackList() {
        return {
            require: 'ngModel',
            link: function (scope, elem, attr, ngModel) {
                var blacklist = attr.blackList.split(',');

                ngModel.$validators.blackList = function (modelValue) {
                    if (modelValue == undefined)
                        return 0;
                    var valid = blacklist.indexOf(modelValue.toString()) === -1;
                    return valid;
                };
            }
        };
    };

    app.directive("blackList", blackList);
})(angular.module('core'));
پس از برسی اینکه آیا در Angular نیز می‌توانیم دایرکتیوهایی به این صورت داشته باشیم، به این نتیجه رسیدم که بله خوشبختانه در Angular نیز میتوان اعتبار سنجی‌های سفارشی ساخت.
برای همین یک ماژول Angular برای اعتبار سنجی در Angular در صفحه گیت‌هاب خودم ایجاد کردم که فعلا تنها داری یک دایرکتیو برای اعتبار سنجی مقادیر غیر مجاز است. لطفا دوستان در صورت همکاری به این لینک مراجعه کنند.
بعد از اضافه کردن این ماژول طبق راهنما، برای قطعه کد موجود در «بهبود اعتبارسنجی drop down» از تگهای زیر می‌توان استفاده کرد.
<div [class.has-error]="blackList">
    <label>Primary Language</label>
    <select name="primaryLanguage" ngModel blackList='default' #primaryLanguage="ngModel">
          <option value="default">Select a Language...</option>
          <option *ngFor="let lang of languages">
            {{ lang }}
          </option>
      </select>
      <small [hidden]="primaryLanguage.valid">
        Not Valid
      </small>
  </div>
در این حالت بخاطر وجود دایرکتیو blackList با مقدار 'default' در صورتی که مقدار drop down با مقدار default انتخاب شده باشد فرم invalid خواهد بود و عبارت Not Valid نمایش داده خواهد شد.
پ.ن: این کد در عرض زمان خیلی کوتاهی آماده شد و منتشر شده است و احتمالا هنوز نیاز به اصلاح دارد.
مطالب
Performance در AngularJS قدم سوم
خیلی خوشحالم که تا این مرحله، این مقاله‌ها را دنبال می‌کنید. در مقالات قبل مسائل ساده و مهمی در بحث Performance مطرح شد. در این مقاله می‌خواهم قدم سوم در بهبود Performance را توضیح دهم که رعایت کردن این مسائل می‌تواند کمک زیادی در بهبود عملکرد برنامه‌های مبتنی بر AngularJS داشته باشد.

scope؟
همه‌ی برنامه نویسان و توسعه دهندگان، یکی از اولین مفاهیمی را که در AngularJS یاد می‌گیرند، scope هست. اما scope چیست؟ به صورت خیلی ساده می‌توان گفت scope مشخص کننده‌ی حوزه متغیر‌ها و توابعی هست که قرار است در View تاثیر داشته باشند. کد زیر را مشاهده کنید:
<div>نام و نام خانوادگی: {{name}}</div>
<div>معدل: {{avg()}}<div>
و کد سمت controller
scope.nums=[19,20,17,16,15,18,19];
scope.name='بهنام محمدی';
scope.avg= function(){
  return scope.nums.reduce(function(previousValue, currentValue) {
      return previousValue + currentValue;
   })/scope.nums.length;
}
و اما خروجی نهایی
نام و نام خانوادگی: بهنام محمدی
معدل:17.71
خوب چون ما در قسمت controller به صورت scope.name و scope.avg عمل کرده‌ایم، می‌توانیم در View به صورت name و avg از این متغیر‌ها استفاده کنیم. در نتیجه اگر ما در controller، به جای scope.name بنویسیم name و یا به جای scope.avg بنویسیم avg به مشکل بر می‌خوریم؛ چون قسمت View ما متغیر‌های داخل scope را در View دخیل می‌کند و متغیر‌های داخل scope توسط Watcher‌ها رصد می‌شود.

خوب سؤال، همه چیز که عالی هست پس مشکل در کجاست؟
مشکل در متغیر scope.nums هست! به کد زیر توجه کنید:
var nums=[19,20,17,16,15,18,19];
scope.name='بهنام محمدی';
scope.avg= function(){
  return nums.reduce(function(previousValue, currentValue) {
      return previousValue + currentValue;
   })/nums.length;
}
فکر کنم متوجه تفاوت این کد با کد بالایی شده‌اید. اما کدام کد درست است؟ یا بهتر بگویم کدام کد بر روی Performance تاثیر مناسبی دارد؟ کد پایینی Performance بالایی دارد. دلیل این موضوع این است وقتی ما از nums در View هیچ استفاده‌ای نمی‌کنیم، بهتر است به صورت var nums تعریف شود. در کد بالایی که این متغیر به صورت scope.nums تعریف شده بود، با اینکه در View استفاده نشده بود، ولی توسط Watcher AngularJS در هر لحظه رصد می‌شود و این کار باعث کندی و کاهش عملکرد AngularJS خواهد شد. بنابراین در کل متغیرهایی را که در View استفاده نمی‌کنید، به صورت var test استفاده نمایید تا Watcher AngularJS این متغیر‌ها را رصد نکند.
امیدوارم از این مقاله لذت برده باشید. منتظر مقاله بعدی من باشید.

مطالب
بارگزاری PartialView با استفاده از jQuery در زمان اجرا
مزیت استفاده از PartialViewها، ماژولار کردن برنامه است. برای مثال اگر صفحه جاری شما قرار است از چهار قسمت اخبار، منوی پویا، سخن روز و آمار کاربران تشکیل شود، می‌توان هر کدام را توسط یک PartialView پیاده سازی کرد و سپس صفحه اصلی را از کنار هم قرار دادن این PartialViewها تهیه نمود.(منبع).
در این پست قصد دارم به نحوه بارگزاری یک PartialView  با استفاده از ASP.NET MVC بپردازم.
ابتدا یک پروژه جدید ایجاد کنید. حال می‌خواهیم زمانیکه صفحه اصلی سایت بارگزاری می‌شود، لیست تمام محصولات را نمایش دهد. برای نمایش محصولات یک PartialView جدید را ایجاد می‌کنیم؛ همانند شکل ذیل:

حال برای ساده کردن مثال، متن ثابتی را درون این PartialView می‌نویسیم:

product  List Partial
در ادامه قصد داریم که درIndex، این View را بارگزاری کنیم. برای این کار از متد ajax مربوط به کتابخانه jQuery به صورت زیر استفاده می‌کنیم:
 $(function () {
        $.ajax({
            //مشخص کردن  اکشنی که باید فراخوانی شود
            url: '/Home/Details',
            contentType: 'application/html; charset=utf-8',
            type: 'GET',
            //نوع نتیجه بازگشتی
            dataType: 'html'
           
        })
.success(function (result) {
    //زمانی که کدهای سمت سرور بدون خطا اجرا شده اند
    //این قسمت فراخوانی می‌شود و نتیجه اکشن درون متغیر 
    //result 
    //قرار می‌گیرد
    $('#sectionContents').html(result);
})
.error(function (xhr, status) {
    alert(xhr.responseText);
});
    });
و پس از آن محلی را که قرار است PartialView در آن بارگزاری شود، ایجاد می‌کنیم:
<div id="sectionContents"></div>
حال فقط باید اکشن مورد نظر را در HomeController پیاده سازی کنیم:
 public ActionResult Details()
        {
           return PartialView("partial/_ProductList");
        }
 با استفاده از این کد مشخص کردیم که این اکشن، یک PartialView را با نام ProductList_ برگشت می‌دهد. البته در یک مثال واقعی باید لیست محصولات را هم به این PartialView پاس دهیم.
نتیجه اجرا به صورت زیر است:

مطالب
C# 6 - Index Initializers
زمان زیادی از ارائه‌ی امکان Collection Initializer برای ایجاد یک متغیر از نوع Collection می‌گذرد؛ برای نمونه به مثال زیر توجه کنید:
enum USState {...}
var AreaCodeUSState = new Dictionary<string, USState>
    {
        {"408", USState.California},
        {"701", USState.NorthDakota},
        ...
    };
در پشت صحنه، کامپایلر، Collection Initializer را می‌گیرد، با استفاده از یک <Dictionary<TKey, TValue و با فراخوانی متد Add آن بر روی لیست Collection Initializer شروع به درج آن در دیکشنری ساخته شده می‌کند. Collection Initializer فقط بر روی کلاس هایی که در آن‌ها IEnumerable پیاده سازی شده باشد امکان پذیر است چرا که کامپایلر کار اضافه کردن مقادیر اولیه را به ()IEnumerable.Add می‌سپارد.

اکنون در C# 6.0 ما می‌توانیم از Index Initializer استفاده کنیم:
enum USState {...}
var AreaCodeUSState = new Dictionary<string, USState>
    {
        ["408"] = USState.California,
        ["701"] = USState.NorthDakota,
        ...
    };
اولین تفاوتی که این دو روش با هم دارند این است که در حالت استفاده‌ی از Index Initializer پس از کامپایل، ()IEnumerable.Add فراخوانی نمی‌شود. این تفاوت بسیار مهم است و کار اضافه کردن مقادیر اولیه را با استفاده از کلید (Key) ویژه انجام می‌دهد.
شبه کد مثال بالا به صورت زیر می‌شود:

Collection Initializer
create a Dictionary<string, USState>
add to new Dictionary the following items: 
     "408", USState.California
     "701", USState.NorthDakota
Index Initializer
create a Dictionary<string, USState> then
using AreaCodeUSState's default Indexed property
    set the Value of Key "408" to USState.California
    set the Value of Key "701" to USState.NorthDakota
حال به مثال زیر توجه کنید:

Collection Initializer 
enum USState {...}
var AreaCodeUSState = new Dictionary<string, USState>
        {
            { "408", USState.Confusion},
            { "701", USState.NorthDakota },
            { "408", USState.California},
            ...
        };
Console.WriteLine( AreaCodeUSState.Where(x => x.Key == "408").FirstOrDefault().Value );
Index Initializer
enum USState {...}
var AreaCodeUSState = new Dictionary<string, USState>
    {
        ["408"] = USState.Confusion,
        ["701"] = USState.NorthDakota,
        ["408"] = USState.California,
        ...
    };
Console.WriteLine( AreaCodeUSState2.Where(x => x.Key == "408").FirstOrDefault().Value );  // output = California
هر دو کد بالا با موفقیت کامپایل و اجرا می‌شود، اما در زمان اجرای Collection Initializer هنگامیکه می‌خواهد مقدار دوم "408" را اضافه کند با استثناء ArgumentException متوقف می‌شود چرا که کلید "408" از قبل وجود دارد.
اما در زمان اجرا، Index Initializer به صورت کامل و بدون خطا این کار را انجام می‌دهد و در کلید "408" مقدار USState.Confusion قرار می‌گیرد. سپس "701" مقدار USState.NorthDakota و بعد از استفاده‌ی مجدد از کلید "408" مقدار USState.California جایگزین مقدار قبلی می‌شود.

var fibonaccis = new List<int>
    {
        [0] = 1,
        [1] = 2,
        [3] = 5,
        [5] = 13
    }
این کد هم معتبر است و هم کامپایل می‌شود. البته معتبر است، ولی صحیح نیست. <List<T اجازه‌ی تخصیص اندیسی فراتر از اندازه‌ی فعلی را نمی‌دهد.
تلاش برای تخصیص مقدار 1 با کلید 0 به <List<int، سبب بروز استثناء ArguementOutOfRangeException می شود. وقتی (List<T>.Add(item فراخوانی می‌شود اندازه‌ی لیست یک واحد افزایش می‌یابد. بنابراین باید دقت داشت که Index Initializer از ()Add. استفاده نمی‌کند؛ در عوض با استفاده از خصوصیت اندیس پیش فرض، مقداری را برای یک کلید تعیین می‌کند.
برای چنین حالتی بهتر است از همان روش قدیمی Collection Initializer استفاده کنیم:
var fibonaccis = new List<int>()
    {
        1,
        3,
        5,
        13
    };
نظرات مطالب
Defensive Programming - بازگشت نتایج قابل پیش بینی توسط متدها
باید عرض کنم بله وقتی کاربری یافت نشد چه کاری می‌توانیم انجام دهیم؟ 
مطلب پایه‌ای در مورد صدور استثناءها در اینجا هست: Dont Use Exceptions For Flow Control و قسمتی از آن به این صورت است:
 Errors should be handled via exceptions, but successes shouldn't
 مشکلی که در اینجا هست این است که بازگشت null یک کاربر در یک کوئری، یک error نیست؛ یک بازگشت و عملیات موفقیت آمیز است. به همین جهت صدور استثناء برای دریافت آن نباید رخ دهد:  
The typical meaning of the word "exception" is an event that is unexpected rather than part of normal operation; otherwise people would just say "event" 
و اینکه نتیجه‌ی یک متد باید در همان متد به صورت استثناء درنظر گرفته شود، غیرضروری است و این تصمیم گیری باید به عهده‌ی فراخوان گذاشته شود. گاهی می‌خواهیم بررسی کنیم کاربری در دیتابیس هست؟ اگر بله، آ‌‌ن‌را تکراری ثبت نکنیم. گاهی می‌خواهیم بررسی کنیم اگر در دیتابیس هست، اطلاعات او را به روز رسانی کنیم. اینکه کوئری مدنظر نال بر می‌گرداند، تصمیم گیری در مورد اهمیت آن ربطی به آن متد ندارد و صرفا به عهده‌ی فراخوان است:
Methods should have a way to indicate various kinds of results. Whether those results are treated as exceptional should depend on the caller.  
نظرات مطالب
از سرگیری مجدد، لغو درخواست و سعی مجدد دریافت فایل‌های حجیم توسط HttpClient
سلام؛ اگر قصد داشته باشیم از طریق Angular 2  دانلود رو فراخوانی کنیم ، به شکلی که یک فایل حجیم بر روی مرورگر کاربر دانلود بشه ، پیاده سازی به چه صورت خواهد بود ، الان شما در سمت سرور یک حلقه while دارید که محتوی رو می‌خونه ، این یعنی در هر اجرای حلقه من باید محتوی خوانده شده رو return کنم به کلاینت و دوباره با آفست جدید فراخوانی کنم؟
نحوه فراخوانی من به شکل زیر هست که با FileStreamResult برگشتی از سمت سرور به درستی کار میکنه ولی خب همه فایل رو یکجا برمی گردونه :
 downloadOrder(orderId: number , userId : string) {

    this._http.get(this._config.getApiURI() + '/Download/productfile/' + orderId + '/' + userId, { observe: 'blob'})
      .subscribe(
        (data) => {
          if(this._functionService.isNullOrEmpty(data.body)){
            this._snackBarService.error('فایل پیدا نشد');
            return;
          }
          var contentType = data.body.type || "application/octet-stream";

          var fileInfo = JSON.parse(data.headers.get('FileInfo'));          
          var blob = new Blob([data.body], { type: contentType });
          var url = window.URL.createObjectURL(blob);
          var anchor = document.createElement("a");
          anchor.setAttribute("href", url)
          anchor.setAttribute("download", fileInfo.fileName + fileInfo.fileType);
          anchor.setAttribute("target", "_blank");
          document.body.appendChild(anchor);
          anchor.click();
          setTimeout(function () {
            document.body.removeChild(anchor);
            window.URL.revokeObjectURL(url);
          }, 200);
        },
        error => {
          this._snackBarService.error(error);
        });
  }
ولی حالا که قراره فایل رو در چندین مرحله و با حلقه while برگردونیم ، با این سناریو یعنی باید از طرف کلاینت چندین بار فراخوانی باشه ، مگر اینکه کلا یک window جدید باز کنیم  (که احتمالا با مشکل popup blocker مواجه میشیم) و ... آیا باید در سمت سرور بایت‌های خوانده شده رو تو Response بنویسیم ...
نحوه پیاده سازی کد پایین به چه شکل میشه
             while ((bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length)) > 0)  
                    {                        
                        await Response.Body.WriteAsync(buffer);                        
                        //return File(buffer, GetContentType(fullPath), newFileName, true);                        
                        //return new FileStreamResult(stream,"application/octet-stream");
                    }
مطالب
معماری لایه بندی نرم افزار #3

Service Layer

نقش لایه‌ی سرویس این است که به عنوان یک مدخل ورودی به برنامه کاربردی عمل کند. در برخی مواقع این لایه را به عنوان لایه‌ی Facade نیز می‌شناسند. این لایه، داده‌ها را در قالب یک نوع داده ای قوی (Strongly Typed) به نام View Model، برای لایه‌ی Presentation فراهم می‌کند. کلاس View Model یک Strongly Typed محسوب می‌شود که نماهای خاصی از داده‌ها را که متفاوت از دید یا نمای تجاری آن است، بصورت بهینه ارائه می‌نماید. در مورد الگوی View Model در مباحث بعدی بیشتر صحبت خواهم کرد.

الگوی Facade یک Interface ساده را به منظور کنترل دسترسی به مجموعه ای از Interface‌ها و زیر سیستم‌های پیچیده ارائه می‌کند. در مباحث بعدی در مورد آن بیشتر صحبت خواهم کرد.

کلاسی با نام ProductViewModel را با کد زیر به پروژه SoCPatterns.Layered.Service اضافه کنید:

public class ProductViewModel
{
    Public int ProductId {get; set;}
    public string Name { get; set; }
    public string Rrp { get; set; }
    public string SellingPrice { get; set; }
    public string Discount { get; set; }
    public string Savings { get; set; }
}

برای اینکه کلاینت با لایه‌ی سرویس در تعامل باشد باید از الگوی Request/Response Message استفاده کنیم. بخش Request توسط کلاینت تغذیه می‌شود و پارامترهای مورد نیاز را فراهم می‌کند. کلاسی با نام ProductListRequest را با کد زیر به پروژه SoCPatterns.Layered.Service اضافه کنید:

using SoCPatterns.Layered.Model;

namespace SoCPatterns.Layered.Service
{
    public class ProductListRequest
    {
        public CustomerType CustomerType { get; set; }
    }
}

در شی Response نیز بررسی می‌کنیم که درخواست به درستی انجام شده باشد، داده‌های مورد نیاز را برای کلاینت فراهم می‌کنیم و همچنین در صورت عدم اجرای صحیح درخواست، پیام مناسب را به کلاینت ارسال می‌نماییم. کلاسی با نام ProductListResponse را با کد زیر به پروژه SoCPatterns.Layered.Service اضافه کنید:

public class ProductListResponse
{
    public bool Success { get; set; }
    public string Message { get; set; }
    public IList<ProductViewModel> Products { get; set; }
}

به منظور تبدیل موجودیت Product به ProductViewModel، به دو متد نیاز داریم، یکی برای تبدیل یک Product و دیگری برای تبدیل لیستی از Product. شما می‌توانید این دو متد را به کلاس Product موجود در Domain Model اضافه نمایید، اما این متدها نیاز واقعی منطق تجاری نمی‌باشند. بنابراین بهترین انتخاب، استفاده از Extension Method‌ها می‌باشد که باید برای کلاس Product و در لایه‌ی سرویس ایجاد نمایید. کلاسی با نام ProductMapperExtensionMethods را با کد زیر به پروژه SoCPatterns.Layered.Service اضافه کنید:

public static class ProductMapperExtensionMethods
{
    public static ProductViewModel ConvertToProductViewModel(this Model.Product product)
    {
        ProductViewModel productViewModel = new ProductViewModel();
        productViewModel.ProductId = product.Id;
        productViewModel.Name = product.Name;
        productViewModel.RRP = String.Format(“{0:C}”, product.Price.RRP);
        productViewModel.SellingPrice = String.Format(“{0:C}”, product.Price.SellingPrice);
        if (product.Price.Discount > 0)
            productViewModel.Discount = String.Format(“{0:C}”, product.Price.Discount);
        if (product.Price.Savings < 1 && product.Price.Savings > 0)
            productViewModel.Savings = product.Price.Savings.ToString(“#%”);
        return productViewModel;
    }
    public static IList<ProductViewModel> ConvertToProductListViewModel(
        this IList<Model.Product> products)
    {
        IList<ProductViewModel> productViewModels = new List<ProductViewModel>();
        foreach(Model.Product p in products)
        {
            productViewModels.Add(p.ConvertToProductViewModel());
        }
        return productViewModels;
    }
}

حال کلاس ProductService را جهت تعامل با کلاس سرویس موجود در Domain Model و به منظور برگرداندن لیستی از محصولات و تبدیل آن به لیستی از ProductViewModel، ایجاد می‌نماییم. کلاسی با نام ProductService را با کد زیر به پروژه SoCPatterns.Layered.Service اضافه کنید:

public class ProductService
{
    private Model.ProductService _productService;
    public ProductService(Model.ProductService ProductService)
    {
        _productService = ProductService;
    }
    public ProductListResponse GetAllProductsFor(
        ProductListRequest productListRequest)
    {
        ProductListResponse productListResponse = new ProductListResponse();
        try
        {
            IList<Model.Product> productEntities =
                _productService.GetAllProductsFor(productListRequest.CustomerType);
            productListResponse.Products = productEntities.ConvertToProductListViewModel();
            productListResponse.Success = true;
        }
        catch (Exception ex)
        {
            // Log the exception…
            productListResponse.Success = false;
            // Return a friendly error message
            productListResponse.Message = ex.Message;
        }
        return productListResponse;
    }
}

کلاس Service تمامی خطاها را دریافت نموده و پس از مدیریت خطا، پیغامی مناسب را به کلاینت ارسال می‌کند. همچنین این لایه محل مناسبی برای Log کردن خطاها می‌باشد. در اینجا کد نویسی لایه سرویس به پایان رسید و در ادامه به کدنویسی Data Layer می‌پردازیم.

Data Layer

برای ذخیره سازی محصولات، یک بانک اطلاعاتی با نام Shop01 ایجاد کنید که شامل جدولی به نام Product با ساختار زیر باشد:

برای اینکه کدهای بانک اطلاعاتی را سریعتر تولید کنیم از روش Linq to SQL در Data Layer استفاده می‌کنم. برای این منظور یک Data Context برای Linq to SQL به این لایه اضافه می‌کنیم. بر روی پروژه SoCPatterns.Layered.Repository کلیک راست نمایید و گزینه Add > New Item را انتخاب کنید. در پنجره ظاهر شده و از سمت چپ گزینه Data و سپس از سمت راست گزینه Linq to SQL Classes را انتخاب نموده و نام آن را Shop.dbml تعیین نمایید.

از طریق پنجره Server Explorer به پایگاه داده مورد نظر متصل شوید و با عمل Drag & Drop جدول Product را به بخش Design کشیده و رها نمایید.

اگر به یاد داشته باشید، در لایه Model برای برقراری ارتباط با پایگاه داده از یک Interface به نام IProductRepository استفاده نمودیم. حال باید این Interface را پیاده سازی نماییم. کلاسی با نام ProductRepository را با کد زیر به پروژه SoCPatterns.Layered.Repository اضافه کنید:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using SoCPatterns.Layered.Model;

namespace SoCPatterns.Layered.Repository
{
    public class ProductRepository : IProductRepository
    {
        public IList<Model.Product> FindAll()
        {
            var products = from p in new ShopDataContext().Products
                                select new Model.Product
                                {
                                    Id = p.ProductId,
                                    Name = p.ProductName,
                                    Price = new Model.Price(p.Rrp, p.SellingPrice)
                                };
            return products.ToList();
        }
    }
}

در متد FindAll، با استفاده از دستورات Linq to SQL، لیست تمامی محصولات را برگرداندیم. کدنویسی لایه‌ی Data هم به پایان رسید و در ادامه به کدنویسی لایه‌ی Presentation و UI می‌پردازیم.

Presentation Layer

به منظور جداسازی منطق نمایش (Presentation) از رابط کاربری (User Interface)، از الگوی Model View Presenter یا همان MVP استفاده می‌کنیم که در مباحث بعدی با جزئیات بیشتری در مورد آن صحبت خواهم کرد. یک Interface با نام IProductListView را با کد زیر به پروژه SoCPatterns.Layered.Presentation اضافه کنید:

using SoCPatterns.Layered.Service;

public interface IProductListView
{
    void Display(IList<ProductViewModel> Products);
    Model.CustomerType CustomerType { get; }
    string ErrorMessage { set; }
}

این Interface توسط Web Form‌های ASP.NET و یا Win Form‌ها باید پیاده سازی شوند. کار با Interface‌ها موجب می‌شود تا تست View‌ها به راحتی انجام شوند. کلاسی با نام ProductListPresenter را با کد زیر به پروژه SoCPatterns.Layered.Presentation اضافه کنید:

using SoCPatterns.Layered.Service;

namespace SoCPatterns.Layered.Presentation
{
    public class ProductListPresenter
    {
        private IProductListView _productListView;
        private Service.ProductService _productService;
        public ProductListPresenter(IProductListView ProductListView,
            Service.ProductService ProductService)
        {
            _productService = ProductService;
            _productListView = ProductListView;
        }
        public void Display()
        {
            ProductListRequest productListRequest = new ProductListRequest();
            productListRequest.CustomerType = _productListView.CustomerType;
            ProductListResponse productResponse =
                _productService.GetAllProductsFor(productListRequest);
            if (productResponse.Success)
            {
                _productListView.Display(productResponse.Products);
            }
            else
            {
                _productListView.ErrorMessage = productResponse.Message;
            }
        }
    }
}

کلاس Presenter وظیفه‌ی واکشی داده ها، مدیریت رویدادها و بروزرسانی UI را دارد. در اینجا کدنویسی لایه‌ی Presentation به پایان رسیده است. از مزایای وجود لایه‌ی Presentation این است که تست نویسی مربوط به نمایش داده‌ها و تعامل بین کاربر و سیستم به سهولت انجام می‌شود بدون آنکه نگران دشواری Unit Test نویسی Web Form‌ها باشید. حال می‌توانید کد نویسی مربوط به UI را انجام دهید که در ادامه به کد نویسی در Win Forms و Web Forms خواهیم پرداخت. 

اشتراک‌ها
refactor شدن ngAnimate در AngularJs 1.4
 With the 1.4 release, ngAnimate uses the new animation system to provide support for CSS-based animations as well as JavaScript-based animations via callback hooks.
refactor شدن ngAnimate در AngularJs 1.4