مطالب دوره‌ها
نکاتی درباره برنامه نویسی دستوری(امری)
در این فصل نکاتی را درباره برنامه نویسی دستوری در #F فرا خواهیم گرفت. برای شروع از mutale خواهیم گفت.

mutable
Keyword
در فصل دوم(شناسه ها) گفته شد که برای یک شناسه امکان تغییر مقدار وجود ندارد. اما در #F راهی وجود دارد که در صورت نیاز بتوانیم مقدار یک شناسه را تغییر دهیم.در #F هرگاه بخواهیم شناسه ای تعریف کنیم که بتوان در هر زمان مقدار شناسه رو به دلخواه تغییر داد از کلمه کلیدی mutable کمک می‌گیریم و برای تغییر مقادیر شناسه‌ها کافیست از علامت (->) استفاده کنیم. به یک مثال در این زمینه دقت کنید:
#1 let mutable phrase = "Can it change? "

#2 printfn "%s" phrase

#3 phrase <- "yes, it can."

#4 printfn "%s" phrase


در خط اول یک شناسه را به صورت mutable(تغییر پذیر) تعریف کردیم و در خط سوم با استفاده از (->) مقدار شناسه رو update کردیم. خروجی مثال بالا به صورت زیر است:
Can it change?  
yes, it can.

نکته اول: در این روش هنگام update کردن مقدار شناسه حتما باید مقدار جدید از نوع مقدار قبلی باشد در غیر این صورت با خطای کامپایلری متوقف خواهید شد.
#1 let mutable phrase = "Can it change?  "

#3 phrase <- 1

اجرای کد بالا خطای زیر را به همراه خواهد داشت.(خطا کاملا واضح است و نیاز به توضیح دیده نمی‌شود)
Prog.fs(9,10): error: FS0001: This expression has type
int
but is here used with type
string
نکته دوم :ابتدا به مثال زیر توجه کنید.
let redefineX() =
let x = "One"
printfn "Redefining:\r\nx = %s" x
if true then
   let x = "Two"
printfn "x = %s" x
printfn "x = %s" x

در مثال بالا در تابع redefineX یک شناسه به نام x تعریف کردم با مقدار "One". یک بار مقدار شناسه x رو چاپ می‌کنیم و بعد دوباره بعد از شرط true یک شناسه دیگر با همون نام یعنی x تعریف شده است و در انتها هم دو دستور چاپ. ابتدا خروجی مثال بالا رو با هم مشاهده می‌کنیم.
Redefining:
x = One
x = Two
x = One
همان طور که میبینید شناسه دوم x بعد از تعریف دارای مقدار جدید Two بود و بعد از اتمام محدوده(scope) مقدار x دوباره به One تغییر کرد.(بهتر است بگوییم منظور از دستور print x سوم اشاره به شناسه x اول برنامه است). این رفتار مورد انتظار ما در هنگام استفاده از روش تعریف مجدد شناسه هاست. حال به بررسی رفتار muatable در این حالت می‌پردازیم.
let mutableX() =
let mutable x = "One"
printfn "Mutating:\r\nx = %s" x
if true then
   x <- "Two"
printfn "x = %s" x
printfn "x = %s" x
تنها تفاوت در استفاده از mutable keyword و (->) است. خروجی مثال بالا نیز به صورت زیر خواهد بود. کاملا واضح است که مقدار شناسه x بعد از تغییر و اتمام محدوده(scope) هم چنان Two خواهد بود.
Mutating:
x = One
x = Two
x = Two

Reference Cells

روشی برای استفاده از شناسه‌ها به صورت mutable است. با این روش می‌تونید شناسه هایی تعریف کنید که امکان تغییر مقدار برای اون‌ها وجود دارد. زمانی که از این روش برای مقدار دهی به شناسه‌ها استفاده کنیم یک کپی از مقدار مورد نظر به شناسه اختصاص داده می‌شود نه آدرس مقدار در حافظه.
به جدول زیر توجه کنید:

 Member Or Field
Description
 Definition
(derefence operator)!
 مقدار مشخص شده را برگشت می‌دهد
 

let (!) r = r.contents 

 (Assignment operator)=: مقدار مشخص شده را تغییر می‌دهد
 

let (:=) r x = r.contents <- x 

ref operator
 یک مقدار را در یک reference cell  جدید کپسوله می‌کند
 

let ref x = { contents = x } 

Value Property
 برای عملیات get  یا set  مقدار مشخص شده
 

member x.Value = x.contents 

 contents record field
 برای عملیات get  یا set  مقدار مشخص شده

let ref x = { contents = x } 

  یک مثال:
let refVar = ref 6

refVar := 50
printfn "%d" !refVar
خروجی مثال بالا 50 خواهد بود.
let xRef : int ref = ref 10

printfn "%d" (xRef.Value)
printfn "%d" (xRef.contents)

xRef.Value <- 11
printfn "%d" (xRef.Value)
xRef.contents <- 12
printfn "%d" (xRef.contents)
خروجی مثال بالا:
10
10
11
12 

خصیصه اختیاری در #F
در #F زمانی از خصیصه اختیاری استفاده می‌کنیم که برای یک متغیر مقدار وجود نداشته باشد. option  در #F نوعی است که می‌تواند هم مقدار داشته باشد و هم نداشته باشد.
let keepIfPositive (a : int) = if a > 0 then Some(a) else None
از None زمانی استفاده می‌کنیم که option مقدار نداشته باشد و از Some  زمانی استفاده می‌کنیم که option مقدار داشته باشد.
let exists (x : int option) = 
    match x with
    | Some(x) -> true
    | None -> false
در مثال بالا ورودی تابع exists از نوع int و به صورت اختیاری تعریف شده است.(معادل با ?int یا<Nullable<int در #C) در صورتی که x مقدار داشته باشد مقدار true در غیر این صورت مقدار false را برگشت می‌دهد.

چگونگی استفاده  از option
مثال
let rec tryFindMatch pred list =
    match list with
    | head :: tail -> if pred(head)
                        then Some(head)
                        else tryFindMatch pred tail
    | [] -> None

let result1 = tryFindMatch (fun elem -> elem = 100) [ 200; 100; 50; 25 ] //برابر با 100 است

let result2 = tryFindMatch (fun elem -> elem = 26) [ 200; 100; 50; 25 ]// برابر با None است
یک تابع به نام tryFindMatch داریم با دو پارامتر ورودی. با استفاده از الگوی Matching از عنصر ابتدا تا انتها را در لیست (پارامتر list) با مقدار پارامتر pred مقایسه می‌کنیم. اگر مقادیر برابر بودند مقدار head در غیر این صورت None(یعنی option مقدار ندارد) برگشت داده می‌شود.
یک مثال کاربردی تر
open System.IO
let openFile filename =
    try 
        let file = File.Open (filename, FileMode.Create)
        Some(file)
    with
        | ex -> eprintf "An exception occurred with message %s" ex.Message
                None
در مثال بالا از option‌ها برای بررسی وجود یا عدم وجود فایل‌های فیزیکی استفاده کردم.

Enumeration
تقریبا همه با نوع داده شمارشی یا enums آشنایی دارند. در اینجا فقط به نحوه پیاده سازی آن در #F می‌پردازیم. ساختار کلی تعریف آن به صورت زیر است:
type enum-name =
   | value1 = integer-literal1
   | value2 = integer-literal2
   ...
یک مثال از تعریف:
type Color =
   | Red = 0
   | Green = 1
   | Blue = 2
نحوه استفاده
let col1 : Color = Color.Red
enums فقط از انواع داده ای sbyte, byte, int16, uint16, int32, uint32, int64, uint16, uint64, char پشتیبانی می‌کند که البته مقدار پیش فرض آن Int32 است. در صورتی که بخواهیم صریحا نوع داده ای را ذکر کنیم به صورت زیر عمل می‌شود.
type uColor =
   | Red = 0u
   | Green = 1u
   | Blue = 2u
let col3 = Microsoft.FSharp.Core.LanguagePrimitives.EnumOfValue<uint32, uColor>(2u)

توضیح درباره use
در دات نت خیلی از اشیا هستند که اینترفیس IDisposable رو پیاده سازی کرده اند. این بدین معنی است که حتما یک متد به نام dispose برای این اشیا وجود دارد که فراخوانی آن به طور قطع باعث بازگرداندن حافظه ای که در اختیار این کلاس‌ها بود می‌شود. برای راحتی کار در #C یک عبارت به نام using وجود دارد که در انتها بلاک متد dispose شی مربوطه را فراخوانی می‌کند.
using(var writer = new StreamWriter(filePath))
{
   
}
در #F نیز امکان استفاده از این عبارت با اندکی تفاوت وجود دارد.مثال:
let writeToFile fileName =
    use sw = new System.IO.StreamWriter(fileName : string)
    sw.Write("Hello ")
Units Of Measure
در #F اعداد دارای علامت و اعداد شناور دارای وابستگی با واحد‌های اندازه گیری هستند که به نوعی معرف اندازه و حجم و مقدار و ... هستند. در #F شما مجاز به تعریف واحد‌های اندازه گیری خاص خود هستید و در این تعاریف نوع عملیات اندازه گیری را مشخص می‌کنید. مزیت اصلی استفاده از این روش جلوگیری از رخ دادن خطاهای کامپایلر در پروژه است. ساختار کلی تعریف:
[<Measure>] type unit-name [ = measure ]
یک مثال از تعریف واحد cm:
[<Measure>] type cm
مثالی از تعریف میلی لیتر:
[<Measure>] type ml = cm^3
برای استفاده از این واحد‌ها می‌تونید به روش زیر عمل کنید.
let value = 1.0<cm>
توابع تبدیل واحد ها
قدرت اصلی واحد‌های اندازه گیری  #F در توابع تبدیل است. تعریف توابع تبدیل به صورت زیر می‌باشد:
[<Measure>] type g                 تعریف واحد گرم
[<Measure>] type kg               تعریف واحد کیلوگرم
let gramsPerKilogram : float<g kg^-1> = 1000.0<g/kg>    تعریف تابع تبدیل
یک مثال دیگر :
[<Measure>] type degC // دما بر حسب سلسیوس
[<Measure>] type degF // دما بر حسب فارنهایت

let convertCtoF ( temp : float<degC> ) = 9.0<degF> / 5.0<degC> * temp + 32.0<degF> // تابع تبدیل سلسیوس به فارنهایت
let convertFtoC ( temp: float<degF> ) = 5.0<degC> / 9.0<degF> * ( temp - 32.0<degF>) // تابع تبدیل فارنهایت به سلسیوس

let degreesFahrenheit temp = temp * 1.0<degF> // درجه به فارنهایت
let degreesCelsius temp = temp * 1.0<degC> // درجه به سلسیوس

printfn "Enter a temperature in degrees Fahrenheit." 
let input = System.Console.ReadLine()
let mutable floatValue = 0.
if System.Double.TryParse(input, &floatValue)// اگر ورودی عدد بود
   then 
      printfn "That temperature in Celsius is %8.2f degrees C." ((convertFtoC (degreesFahrenheit floatValue))/(1.0<degC>))
   else
      printfn "Error parsing input."

خروجی مثال بالا :

Enter a temperature in degrees Fahrenheit.
90
That temperature in degrees Celsius is    32.22.
مطالب
فعال سازی عملیات CRUD در Kendo UI Grid
پیشنیاز بحث
- «فرمت کردن اطلاعات نمایش داده شده به کمک Kendo UI Grid»

Kendo UI Grid دارای امکانات ثبت، ویرایش و حذف توکاری است که در ادامه نحوه‌ی فعال سازی آن‌‌ها را بررسی خواهیم کرد. مثالی که در ادامه بررسی خواهد شد، در تکمیل مطلب «فرمت کردن اطلاعات نمایش داده شده به کمک Kendo UI Grid» است.



تنظیمات Data Source سمت کاربر

برای فعال سازی صفحه بندی سمت سرور، با قسمت read منبع داده Kendo UI پیشتر آشنا شده بودیم. جهت فعال سازی قسمت‌های ثبت اطلاعات جدید (create)، به روز رسانی رکوردهای موجود (update) و حذف ردیفی مشخص (destroy) نیاز است تعاریف قسمت‌های متناظر را که هر کدام به آدرس مشخصی در سمت سرور اشاره می‌کنند، اضافه کنیم:
            var productsDataSource = new kendo.data.DataSource({
                transport: {
                    read: {
                        url: "api/products",
                        dataType: "json",
                        contentType: 'application/json; charset=utf-8',
                        type: 'GET'
                    },
                    create: {
                        url: "api/products",
                        contentType: 'application/json; charset=utf-8',
                        type: "POST"
                    },
                    update: {
                        url: function (product) {
                            return "api/products/" + product.Id;
                        },
                        contentType: 'application/json; charset=utf-8',
                        type: "PUT"
                    },
                    destroy: {
                        url: function (product) {
                            return "api/products/" + product.Id;
                        },
                        contentType: 'application/json; charset=utf-8',
                        type: "DELETE"
                    },
                    //...
                },
                schema: {
                    //...
                    model: {
                        id: "Id", // define the model of the data source. Required for validation and property types.
                        fields: {
                            "Id": { type: "number", editable: false }, //تعیین نوع فیلد برای جستجوی پویا مهم است
                            "Name": { type: "string", validation: { required: true } },
                            "IsAvailable": { type: "boolean" },
                            "Price": { type: "number", validation: { required: true, min: 1 } },
                            "AddDate": { type: "date", validation: { required: true } }
                        }
                    }
                },
                batch: false, // enable batch editing - changes will be saved when the user clicks the "Save changes" button
                //...
            });
- همانطور که ملاحظه می‌کنید، حالت‌های update و destroy بر اساس Id ردیف انتخابی کار می‌کنند. این Id را باید در قسمت model مربوط به اسکیمای تعریف شده، دقیقا مشخص کرد. عدم تعریف فیلد id، سبب خواهد شد تا عملیات update نیز در حالت create تفسیر شود.
- به علاوه در اینجا به ازای هر فیلد، مباحث اعتبارسنجی نیز اضافه شده‌اند؛ برای مثال فیلدهای اجباری با required: true مشخص گردیده‌اند.
- اگر فیلدی نباید ویرایش شود (مانند فیلد Id)، خاصیت editable آن‌را false کنید.
- در data source امکان تعریف خاصیتی به نام batch نیز وجود دارد. حالت پیش فرض آن false است. به این معنا که در حالت ویرایش، تغییرات هر ردیفی، یک درخواست مجزا را به سمت سرور سبب خواهد شد. اگر آن‌را true کنید، تغییرات تمام ردیف‌ها در طی یک درخواست به سمت سرور ارسال می‌شوند. در این حالت باید به خاطر داشت که پارامترهای سمت سرور، از حالت یک شیء مشخص باید به لیستی از آن‌ها تغییر یابند.


مدیریت سمت سرور ثبت، ویرایش و حذف اطلاعات

در حالت ثبت، متد Post، توسط آدرس مشخص شده در قسمت create منبع داده گرید، فراخوانی می‌گردد:
namespace KendoUI06.Controllers
{
    public class ProductsController : ApiController
    {
        public HttpResponseMessage Post(Product product)
        {
            if (!ModelState.IsValid)
                return Request.CreateResponse(HttpStatusCode.BadRequest);

            var id = 1;
            var lastItem = ProductDataSource.LatestProducts.LastOrDefault();
            if (lastItem != null)
            {
                id = lastItem.Id + 1;
            }
            product.Id = id;
            ProductDataSource.LatestProducts.Add(product);

            var response = Request.CreateResponse(HttpStatusCode.Created, product);
            response.Headers.Location = new Uri(Url.Link("DefaultApi", new { id = product.Id }));
            // گرید آی دی جدید را به این صورت دریافت می‌کند
            response.Content = new ObjectContent<DataSourceResult>(
                new DataSourceResult { Data = new[] { product } }, new JsonMediaTypeFormatter());
            return response;
        }
    }
}
نکته‌ی مهمی که در اینجا باید به آن دقت داشت، نحوه‌ی بازگشت Id رکورد جدید ثبت شده‌است. در این مثال، قسمت schema منبع داده سمت کاربر به نحو ذیل تعریف شده‌است:
            var productsDataSource = new kendo.data.DataSource({
                //...
                schema: {
                    data: "Data",
                    total: "Total",
                }
                //...
            });
از این جهت که خروجی متد Get بازگرداننده‌ی اطلاعات صفحه بندی شده، از نوع DataSourceResult است و این نوع، دارای خواصی مانند Data، Total و Aggergate است:
namespace KendoUI06.Controllers
{
    public class ProductsController : ApiController
    {
        public DataSourceResult Get(HttpRequestMessage requestMessage)
        {
            var request = JsonConvert.DeserializeObject<DataSourceRequest>(
                requestMessage.RequestUri.ParseQueryString().GetKey(0)
            );

            var list = ProductDataSource.LatestProducts;
            return list.AsQueryable()
                       .ToDataSourceResult(request.Take, request.Skip, request.Sort, request.Filter);
        }
    }
}
بنابراین در متد Post نیز باید بر این اساس، response.Content را از نوع لیستی از DataSourceResult تعریف کرد تا Kendo UI Grid بداند که Id رکورد جدید را باید از فیلد Data، همانند تنظیمات schema منبع داده خود، دریافت کند.
response.Content = new ObjectContent<DataSourceResult>(
                              new DataSourceResult { Data = new[] { product } }, new JsonMediaTypeFormatter());
اگر این تنظیم صورت نگیرد، Id رکورد جدید را در گرید، مساوی صفر مشاهده خواهید کرد و عملا بدون استفاده خواهد شد؛ زیرا قابلیت ویرایش و حذف خود را از دست می‌دهد.

متدهای حذف و به روز رسانی سمت سرور نیز چنین امضایی را خواهند داشت:
namespace KendoUI06.Controllers
{
    public class ProductsController : ApiController
    {
        public HttpResponseMessage Delete(int id)
        {
            var item = ProductDataSource.LatestProducts.FirstOrDefault(x => x.Id == id);
            if (item == null)
                return Request.CreateResponse(HttpStatusCode.NotFound);

            ProductDataSource.LatestProducts.Remove(item);

            return Request.CreateResponse(HttpStatusCode.OK, item);
        }

        [HttpPut] // Add it to fix this error: The requested resource does not support http method 'PUT'
        public HttpResponseMessage Update(int id, Product product)
        {
            var item = ProductDataSource.LatestProducts
                                        .Select(
                                            (prod, index) =>
                                                new
                                                {
                                                    Item = prod,
                                                    Index = index
                                                })
                                        .FirstOrDefault(x => x.Item.Id == id);
            if (item == null)
                return Request.CreateResponse(HttpStatusCode.NotFound);


            if (!ModelState.IsValid || id != product.Id)
                return Request.CreateResponse(HttpStatusCode.BadRequest);

            ProductDataSource.LatestProducts[item.Index] = product;
            return Request.CreateResponse(HttpStatusCode.OK);
        }
    }
}
حالت Update از HTTP Verb خاصی به نام Put استفاده می‌کند و ممکن است در این بین خطای The requested resource does not support http method 'PUT' را دریافت کنید. برای رفع آن ابتدا بررسی کنید که آیا Web.config برنامه دارای تعاریف ExtensionlessUrlHandler هست یا خیر. همچنین مزین کردن این متد با ویژگی HttpPut، مشکل را برطرف می‌کند.


تنظیمات Kendo UI Grid جهت فعال سازی CRUD

در ادامه کلیه تغییرات مورد نیاز جهت فعال سازی CRUD را در Kendo UI، به همراه مباحث بومی سازی عبارات متناظر با دکمه‌ها و صفحات خودکار مرتبط، مشاهده می‌کنید:
            $("#report-grid").kendoGrid({
                //....
                editable: {
                    confirmation: "آیا مایل به حذف ردیف انتخابی هستید؟",
                    destroy: true, // whether or not to delete item when button is clicked
                    mode: "popup", // options are "incell", "inline", and "popup"
                    //template: kendo.template($("#popupEditorTemplate").html()), // template to use for pop-up editing
                    update: true, // switch item to edit mode when clicked?
                    window: {
                        title: "مشخصات محصول"   // Localization for Edit in the popup window
                    }
                },
                columns: [
                //....
                    {
                        command: [
                            { name: "edit", text: "ویرایش" },
                            { name: "destroy", text: "حذف" }
                        ],
                        title: "&nbsp;", width: "160px"
                    }
                ],
                toolbar: [
                    { name: "create", text: "افزودن ردیف جدید" },
                    { name: "save", text: "ذخیره‌ی تمامی تغییرات" },
                    { name: "cancel", text: "لغو کلیه‌ی تغییرات" },
                    { template: kendo.template($("#toolbarTemplate").html()) }
                ],
                messages: {
                    editable: {
                        cancelDelete: "لغو",
                        confirmation: "آیا مایل به حذف این رکورد هستید؟",
                        confirmDelete: "حذف"
                    },
                    commands: {
                        create: "افزودن ردیف جدید",
                        cancel: "لغو کلیه‌ی تغییرات",
                        save: "ذخیره‌ی تمامی تغییرات",
                        destroy: "حذف",
                        edit: "ویرایش",
                        update: "ثبت",
                        canceledit: "لغو"
                    }
                }
            });
- ساده‌ترین حالت CRUD در Kendo UI با مقدار دهی خاصیت editable آن به true آغاز می‌شود. در این حالت، ویرایش درون سلولی یا incell فعال خواهد شد که مباحث batching ابتدای بحث، فقط در این حالت کار می‌کند. زمانیکه incell editing فعال است، کاربر می‌تواند تمام ردیف‌ها را ویرایش کرده و در آخر کار بر روی دکمه‌ی «ذخیره‌ی تمامی تغییرات» موجود در نوار ابزار، کلیک کند. در سایر حالات، هر بار تنها یک ردیف را می‌توان ویرایش کرد.
- برای فعال سازی تولید صفحات خودکار ویرایش و افزودن ردیف‌ها، نیاز است خاصیت editable را به نحوی که ملاحظه می‌کنید، مقدار دهی کرد. خاصیت mode آن سه حالت incell (پیش فرض)، inline و popup را پشتیبانی می‌کند.
- اگر حالت‌های inline و یا popup را فعال کردید، در انتهای ستون‌های تعریف شده، نیاز است ستون ویژه‌ای به نام command را مطابق تعاریف فوق، تعریف کنید. در این حالت دو دکمه‌ی ویرایش و ثبت، فعال می‌شوند و اطلاعات خود را از تنظیمات data source گرید دریافت می‌کنند. دکمه‌ی ویرایش در حالت incell کاربردی ندارد (چون در این حالت کاربر با کلیک درون یک سلول می‌تواند آن‌را مانند برنامه‌ی اکسل ویرایش کند). اما دکمه‌ی حذف در هر سه حالت قابل استفاده است.
- به نوار ابزار گرید، سه دکمه‌ی افزودن ردیف‌های جدید، ذخیره‌ی تمامی تغییرات و لغو تغییرات صورت گرفته، اضافه شده‌اند. این دکمه‌ها استاندارد بوده و در اینجا نحوه‌ی بومی سازی پیام‌های مرتبط را نیز مشاهده می‌کنید. همانطور که عنوان شد، دکمه‌های «تمامی تغییرات» در حالت فعال سازی batching در منبع داده و استفاده از incell editing معنا پیدا می‌کند. در سایر حالات این دو دکمه کاربردی ندارند. اما دکمه‌ی افزودن ردیف‌های جدید در هر سه حالت کاربرد دارد و یکسان است.


کدهای کامل این مثال را از اینجا می‌توانید دریافت کنید
KendoUI06.zip
اشتراک‌ها
اعمال فیلترهای اینستاگرام بر روی تصاویر تحت وب
 Many love using Instagram and the filters that come with the app, to make their photos more interesting and beautiful. So far though, the use of these filters are restricted to use inside the app. What if you want to use Instagram filters on web images, outside of the app, like photos you want to put up in your personal blog or website ?
اعمال فیلترهای اینستاگرام بر روی تصاویر تحت وب
اشتراک‌ها
Angular 6 منتشر شد

The 6.0.0 release of Angular is here! This is a major release focused less on the underlying framework, and more on the toolchain and on making it easier to move quickly with Angular in the future. 

Angular 6 منتشر شد
اشتراک‌ها
مزیت ارجاع به CDN ها برای کتابخانه های اسکریپتی

مطلب قدیمی می‌باشد ولی خالی از لطف نیست .

Use CDN's for scripts and media content to improve loading on the client side (i.e. Google CDN)

If everyone is using the same CDNs, odds are the client already has that script cached from some other site and the load time is zero   
مزیت ارجاع به CDN ها برای کتابخانه های اسکریپتی
فایل‌های پروژه‌ها
PdfRpt-1.9.zip
- Updated the project to use iTextSharp 5.4.1.0.
- Updated the project to use EPPlus 3.1.3. EPPlus 3.1.3 has a reference to System.Web for Uri decoding. So to use PdfReport from now on you need to change your project's target framework to full profile instead of the client profile.
- Added FlushType parameter to FlushInBrowser method. FlushType.Inline displays PDF in the browser instead of showing the download popup.
- Added EFCodeFirstMvc4Sample.
- Added PdfFilePrinter sample.
- Fixed `StartIndex cannot be less than zero` exception when parameter values are defined without defining the actual parameters in SQL data sources.
مطالب
نمایش خطاهای اعتبارسنجی سمت سرور ASP.NET Core در برنامه‌های Angular
در مطلب «فرم‌های مبتنی بر قالب‌ها در Angular - قسمت چهارم - اعتبارسنجی ورودی‌ها» با نحوه‌ی تنظیمات اعتبارسنجی سمت کلاینت برنامه‌های Angular آشنا شدیم. اما اگر مدل سمت سرور ما یک چنین شکلی را داشته باشد که به همراه خطاهای اعتبارسنجی سفارشی نیز هست:
using System;
using System.ComponentModel.DataAnnotations;

namespace AngularTemplateDrivenFormsLab.Models
{
    public class Movie
    {
        public int Id { get; set; }

        [Required(ErrorMessage = "Movie Title is Required")]
        [MinLength(3, ErrorMessage = "Movie Title must be at least 3 characters")]
        public string Title { get; set; }

        [Required(ErrorMessage = "Movie Director is Required.")]
        public string Director { get; set; }

        [Range(0, 100, ErrorMessage = "Ticket price must be between 0 and 100.")]
        public decimal TicketPrice { get; set; }

        [Required(ErrorMessage = "Movie Release Date is required")]
        public DateTime ReleaseDate { get; set; }
    }
}
و همچنین کنترلر و اکشن متد دریافت کننده‌ی آن نیز به صورت ذیل تعریف شده باشد:
using AngularTemplateDrivenFormsLab.Models;
using Microsoft.AspNetCore.Mvc;

namespace AngularTemplateDrivenFormsLab.Controllers
{
    [Route("api/[controller]")]
    public class MoviesController : Controller
    {
        [HttpPost]
        public IActionResult Post([FromBody]Movie movie)
        {
            if (ModelState.IsValid)
            {
                // TODO: save ...
                return Ok(movie);
            }

            ModelState.AddModelError("", "This record already exists."); // a cross field validation
            return BadRequest(ModelState);
        }
    }
}
دو نوع خطای اعتبارسنجی سمت سرور را به سمت کلاینت ارسال خواهیم کرد:
الف) خطاهای اعتبارسنجی در سطح فیلدها
زمانیکه return BadRequest(ModelState) صورت می‌گیرد، محتویات شیء ModelState به همراه status code مساوی 400 به سمت کلاینت ارسال خواهد شد. در شیء ModelState یک دیکشنری که کلیدهای آن، نام خواص و مقادیر متناظر با آن‌ها، خطاهای اعتبارسنجی تنظیم شده‌ی در مدل است، قرار دارند.
ب) خطاهای اعتبارسنجی عمومی
در این بین می‌توان دیکشنری ModelState را توسط متد AddModelError نیز تغییر داد و برای مثال کلید آن‌را مساوی "" تعریف کرد. در این حالت یک چنین خطایی به کل فرم اشاره می‌کند و نه به یک خاصیت خاص.

نمونه‌ای از خروجی نهایی ارسالی به سمت کاربر:
 {"":["This record already exists."],"TicketPrice":["Ticket price must be between 0 and 100."]}

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



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

با توجه به اینکه سرور، شیء ModelState را توسط return BadRequest به سمت کلاینت ارسال می‌کند، برای پردازش دیکشنری دریافتی از سمت آن، تنها کافی است قسمت بروز خطای عملیات ارسال اطلاعات را بررسی کنیم:


در این HttpErrorResponse دریافتی، دو خاصیت error که همان آرایه‌ی دیکشنری نام خواص و پیام‌های خطای مرتبط با هر کدام و status code دریافتی مهم هستند:
  errors: string[] = [];

  processModelStateErrors(form: NgForm, responseError: HttpErrorResponse) {
    if (responseError.status === 400) {
      const modelStateErrors = responseError.error;
      for (const fieldName in modelStateErrors) {
        if (modelStateErrors.hasOwnProperty(fieldName)) {
          const modelStateError = modelStateErrors[fieldName];
          const control = form.controls[fieldName] || form.controls[this.lowerCaseFirstLetter(fieldName)];
          if (control) {
            // integrate into Angular's validation
            control.setErrors({
              modelStateError: { error: modelStateError }
            });
          } else {
            // for cross field validations -> show the validation error at the top of the screen
            this.errors.push(modelStateError);
          }
        }
      }
    } else {
      this.errors.push("something went wrong!");
    }
  }

  lowerCaseFirstLetter(data: string): string {
    return data.charAt(0).toLowerCase() + data.slice(1);
  }
توضیحات:
در اینجا از آرایه‌ی errors برای نمایش خطاهای عمومی در سطح فرم استفاده می‌کنیم. این خطاها در ModelState، دارای کلید مساوی "" هستند. به همین جهت حلقه‌ای را بر روی شیء responseError.error تشکیل می‌دهیم. به این ترتیب می‌توان به نام خواص و همچنین خطاهای متناظر با آن‌ها رسید.
 const control = form.controls[fieldName] || form.controls[this.lowerCaseFirstLetter(fieldName)];
از نام خاصیت یا فیلد، جهت یافتن کنترل متناظر با آن، در فرم جاری استفاده می‌کنیم. ممکن است کنترل تعریف شده camel case و یا pascal case باشد. به همین جهت دو حالت بررسی را در اینجا مشاهده می‌کنید.
در ادامه اگر control ایی یافت شد، توسط متد setErrors، کلید جدید modelStateError را که دارای خاصیت سفارشی error است، تنظیم می‌کنیم. با اینکار سبب خواهیم شد تا خطای اعتبارسنجی دریافتی از سمت سرور، با سیستم اعتبارسنجی Angular یکی شود. به این ترتیب می‌توان این خطا را دقیقا ذیل همین کنترل در فرم نمایش داد. اگر کنترلی یافت نشد (کلید آن "" بود و یا جزو نام کنترل‌های موجود در آرایه‌ی form.controls نبود)، این خطا را به آرایه‌ی errors اضافه می‌کنیم تا در بالاترین سطح فرم قابل نمایش شود.

نحوه‌ی استفاده‌ی از متد processModelStateErrors فوق را در متد submitForm، در قسمت شکست عملیات ارسال اطلاعات، مشاهده می‌کنید:
  model = new Movie("", "", 0, "");
  successfulSave: boolean;
  errors: string[] = [];

  constructor(private movieService: MovieService) { }

  ngOnInit() {
  }

  submitForm(form: NgForm) {
    console.log(form);

    this.errors = [];
    this.movieService.postMovieForm(this.model).subscribe(
      (data: Movie) => {
        console.log("Saved data", data);
        this.successfulSave = true;
      },
      (responseError: HttpErrorResponse) => {
        this.successfulSave = false;
        console.log("Response Error", responseError);
        this.processModelStateErrors(form, responseError);
      });
  }


نمایش خطاهای اعتبارسنجی عمومی فرم

اکنون که کار مقدار دهی آرایه‌ی errors انجام شده‌است، می‌توان حلقه‌ای را بر روی آن تشکیل داد و عناصر آن‌را در بالای فرم، به صورت عمومی و مستقل از تمام فیلدهای آن نمایش داد:
<form #form="ngForm" (submit)="submitForm(form)" novalidate>
  <div class="alert alert-danger" role="alert" *ngIf="errors.length > 0">
    <ul>
      <li *ngFor="let error of errors">
        {{ error }}
      </li>
    </ul>
  </div>
  <div class="alert alert-success" role="alert" *ngIf="successfulSave">
    Movie saved successfully!
  </div>



نمایش خطاهای اعتبارسنجی در سطح فیلدهای فرم

با توجه به تنظیم خطاهای اعتبارسنجی کنترل‌های Angular در متد processModelStateErrors و داشتن کلید جدید modelStateError
control.setErrors({
  modelStateError: { error: modelStateError }
});
اکنون می‌توان از این کلید جدید (ctrl.errors.modelStateError)، به صورت ذیل جهت نمایش خطای متناظر با آن (ctrl.errors.modelStateError.error) استفاده کرد:


<ng-template #validationErrorsTemplate let-ctrl="control">
  <div *ngIf="ctrl.invalid && ctrl.touched">
    <div class="alert alert-danger"  *ngIf="ctrl.errors.required">
      This field is required.
    </div>
    <div class="alert alert-danger"  *ngIf="ctrl.errors.minlength">
      This field should be minimum {{ctrl.errors.minlength.requiredLength}} characters.
    </div>
    <div class="alert alert-danger"  *ngIf="ctrl.errors.maxlength">
      This field should be max {{ctrl.errors.maxlength.requiredLength}} characters.
    </div>
    <div class="alert alert-danger"  *ngIf="ctrl.errors.pattern">
      This field's pattern: {{ctrl.errors.pattern.requiredPattern}}
    </div>
    <div class="alert alert-danger"  *ngIf="ctrl.errors.modelStateError">
      {{ctrl.errors.modelStateError.error}}
    </div>
  </div>
</ng-template>
چون تکرار خطاهای اعتبارسنجی در ذیل هر فیلد، فرم را بیش از اندازه شلوغ می‌کند، می‌توان توسط یک ng-template این کدهای تکراری را تبدیل به یک قالب کرد و اکنون استفاده‌ی از این قالب، به سادگی فراخوانی یک ng-container است:
  <div class="form-group" [class.has-error]="releaseDate.invalid && releaseDate.touched">
    <label class="control-label" for="releaseDate">Release Date</label>
    <input type="text" name="releaseDate" #releaseDate="ngModel"  class="form-control"
      required [(ngModel)]="model.releaseDate" />
    <ng-container *ngTemplateOutlet="validationErrorsTemplate; context:{ control: releaseDate }"></ng-container>
  </div>
در اینجا در ngTemplateOutlet، ابتدا نام قالب متناظر ذکر می‌شود و سپس در context آن، نام خاصیت control را که توسط قالب دریافت می‌شود، به template reference variable متناظری تنظیم می‌کنیم، تا به کنترل جاری اشاره کند. به این ترتیب می‌توان به فرم‌هایی خلوت‌تر و با قابلیت مدیریت بهتری رسید.


کدهای کامل این مطلب را از اینجا می‌توانید دریافت کنید.
نظرات مطالب
NuGet 2.0 منتشر شد
درهر حالتی اگه شما بخواین نسخه ای از نوگت رو به روز رسانی بکنین یکسری خطاهایی ممکنه به وجود بیاد. البته ظاهرا این مشکل نوگت نیست (^ و ^) بلکه به یه ایرادهای خاص در Visual Studio Extension manager بر میگرده (^ و ^) : Hotfix و یا این مورد:

Attempting to install or uninstall results in the error "Cannot create a file when that file already exists.”
For some reason, Visual Studio extensions can get in a weird state where you've uninstalled the VSIX extension, but some files were left behind. To work around this issue:

1. Exit Visual Studio
2. Open the following folder (it might be on a different drive on your machine)
    C:\Program Files (x86)\Microsoft Visual Studio 10.0\Common7\IDE\Extensions\Microsoft Corporation\NuGet Package Manager\<version>\
3. Delete all the files with the .deleteme extensions.
4. Re-open Visual Studio
After following these steps, you should be able to continue.