مطالب دورهها
نکاتی درباره برنامه نویسی دستوری(امری)
در این فصل نکاتی را درباره برنامه نویسی دستوری در #F فرا خواهیم گرفت. برای شروع از mutale خواهیم گفت.
mutable Keyword
در فصل دوم(شناسه ها) گفته شد که برای یک شناسه امکان تغییر مقدار وجود ندارد. اما در #F راهی وجود دارد که در صورت نیاز بتوانیم مقدار یک شناسه را تغییر دهیم.در #F هرگاه بخواهیم شناسه ای تعریف کنیم که بتوان در هر زمان مقدار شناسه رو به دلخواه تغییر داد از کلمه کلیدی mutable کمک میگیریم و برای تغییر مقادیر شناسهها کافیست از علامت (->) استفاده کنیم. به یک مثال در این زمینه دقت کنید:
در خط اول یک شناسه را به صورت mutable(تغییر پذیر) تعریف کردیم و در خط سوم با استفاده از (->) مقدار شناسه رو update کردیم. خروجی مثال بالا به صورت زیر است:
نکته اول: در این روش هنگام update کردن مقدار شناسه حتما باید مقدار جدید از نوع مقدار قبلی باشد در غیر این صورت با خطای کامپایلری متوقف خواهید شد.
اجرای کد بالا خطای زیر را به همراه خواهد داشت.(خطا کاملا واضح است و نیاز به توضیح دیده نمیشود)
نکته دوم :ابتدا به مثال زیر توجه کنید.
در مثال بالا در تابع redefineX یک شناسه به نام x تعریف کردم با مقدار "One". یک بار مقدار شناسه x رو چاپ میکنیم و بعد دوباره بعد از شرط true یک شناسه دیگر با همون نام یعنی x تعریف شده است و در انتها هم دو دستور چاپ. ابتدا خروجی مثال بالا رو با هم مشاهده میکنیم.
همان
طور که میبینید شناسه دوم x بعد از تعریف دارای مقدار جدید Two بود و بعد
از اتمام محدوده(scope) مقدار x دوباره به One تغییر کرد.(بهتر است بگوییم
منظور از دستور print x سوم اشاره به شناسه x اول برنامه است). این رفتار
مورد انتظار ما در هنگام استفاده از روش تعریف مجدد شناسه هاست. حال به
بررسی رفتار muatable در این حالت میپردازیم.
تنها تفاوت در استفاده از mutable keyword و (->) است. خروجی مثال بالا نیز به صورت زیر خواهد بود. کاملا واضح است که مقدار شناسه x بعد از تغییر و اتمام محدوده(scope) هم چنان Two خواهد بود.
Reference Cells
روشی برای استفاده از شناسهها به صورت mutable است. با این روش میتونید شناسه هایی تعریف کنید که امکان تغییر مقدار برای اونها وجود دارد. زمانی که از این روش برای مقدار دهی به شناسهها استفاده کنیم یک کپی از مقدار مورد نظر به شناسه اختصاص داده میشود نه آدرس مقدار در حافظه.
به جدول زیر توجه کنید:
یک مثال:
خروجی مثال بالا 50 خواهد بود.
خروجی مثال بالا:
خصیصه اختیاری در #F
در #F زمانی از خصیصه اختیاری استفاده میکنیم که برای یک متغیر مقدار وجود نداشته باشد. option در #F نوعی است که میتواند هم مقدار داشته باشد و هم نداشته باشد.
از None زمانی استفاده میکنیم که option مقدار نداشته باشد و از Some زمانی استفاده میکنیم که option مقدار داشته باشد.
در مثال بالا ورودی تابع exists از نوع int و به صورت اختیاری تعریف شده است.(معادل با ?int یا<Nullable<int در #C) در صورتی که x مقدار داشته باشد مقدار true در غیر این صورت مقدار false را برگشت میدهد.
چگونگی استفاده از option
مثال
یک تابع به نام tryFindMatch داریم با دو پارامتر ورودی. با استفاده از الگوی Matching از عنصر ابتدا تا انتها را در لیست (پارامتر list) با مقدار پارامتر pred مقایسه میکنیم. اگر مقادیر برابر بودند مقدار head در غیر این صورت None(یعنی option مقدار ندارد) برگشت داده میشود.
یک مثال کاربردی تر
در مثال بالا از optionها برای بررسی وجود یا عدم وجود فایلهای فیزیکی استفاده کردم.
Enumeration
تقریبا همه با نوع داده شمارشی یا enums آشنایی دارند. در اینجا فقط به نحوه پیاده سازی آن در #F میپردازیم. ساختار کلی تعریف آن به صورت زیر است:
یک مثال از تعریف:
نحوه استفاده
enums فقط از انواع داده ای sbyte, byte, int16, uint16, int32, uint32, int64, uint16, uint64, char پشتیبانی میکند که البته مقدار پیش فرض آن Int32 است. در صورتی که بخواهیم صریحا نوع داده ای را ذکر کنیم به صورت زیر عمل میشود.
توضیح درباره use
در دات نت خیلی از اشیا هستند که اینترفیس IDisposable رو پیاده سازی کرده اند. این بدین معنی است که حتما یک متد به نام dispose برای این اشیا وجود دارد که فراخوانی آن به طور قطع باعث بازگرداندن حافظه ای که در اختیار این کلاسها بود میشود. برای راحتی کار در #C یک عبارت به نام using وجود دارد که در انتها بلاک متد dispose شی مربوطه را فراخوانی میکند.
در #F نیز امکان استفاده از این عبارت با اندکی تفاوت وجود دارد.مثال:
Units Of Measure
در #F اعداد دارای علامت و اعداد شناور دارای وابستگی با واحدهای اندازه گیری هستند که به نوعی معرف اندازه و حجم و مقدار و ... هستند. در #F شما مجاز به تعریف واحدهای اندازه گیری خاص خود هستید و در این تعاریف نوع عملیات اندازه گیری را مشخص میکنید. مزیت اصلی استفاده از این روش جلوگیری از رخ دادن خطاهای کامپایلر در پروژه است. ساختار کلی تعریف:
یک مثال از تعریف واحد cm:
مثالی از تعریف میلی لیتر:
برای استفاده از این واحدها میتونید به روش زیر عمل کنید.
توابع تبدیل واحد ها
قدرت اصلی واحدهای اندازه گیری #F در توابع تبدیل است. تعریف توابع تبدیل به صورت زیر میباشد:
یک مثال دیگر :
خروجی مثال بالا :
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
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
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
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
let exists (x : int option) = match x with | Some(x) -> true | None -> 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 است
یک مثال کاربردی تر
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
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
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)) { }
let writeToFile fileName = use sw = new System.IO.StreamWriter(fileName : string) sw.Write("Hello ")
در #F اعداد دارای علامت و اعداد شناور دارای وابستگی با واحدهای اندازه گیری هستند که به نوعی معرف اندازه و حجم و مقدار و ... هستند. در #F شما مجاز به تعریف واحدهای اندازه گیری خاص خود هستید و در این تعاریف نوع عملیات اندازه گیری را مشخص میکنید. مزیت اصلی استفاده از این روش جلوگیری از رخ دادن خطاهای کامپایلر در پروژه است. ساختار کلی تعریف:
[<Measure>] type unit-name [ = measure ]
[<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.
پیشنیاز بحث
- «فرمت کردن اطلاعات نمایش داده شده به کمک Kendo UI Grid»
Kendo UI Grid دارای امکانات ثبت، ویرایش و حذف توکاری است که در ادامه نحوهی فعال سازی آنها را بررسی خواهیم کرد. مثالی که در ادامه بررسی خواهد شد، در تکمیل مطلب «فرمت کردن اطلاعات نمایش داده شده به کمک Kendo UI Grid» است.
تنظیمات Data Source سمت کاربر
برای فعال سازی صفحه بندی سمت سرور، با قسمت read منبع داده Kendo UI پیشتر آشنا شده بودیم. جهت فعال سازی قسمتهای ثبت اطلاعات جدید (create)، به روز رسانی رکوردهای موجود (update) و حذف ردیفی مشخص (destroy) نیاز است تعاریف قسمتهای متناظر را که هر کدام به آدرس مشخصی در سمت سرور اشاره میکنند، اضافه کنیم:
- همانطور که ملاحظه میکنید، حالتهای update و destroy بر اساس Id ردیف انتخابی کار میکنند. این Id را باید در قسمت model مربوط به اسکیمای تعریف شده، دقیقا مشخص کرد. عدم تعریف فیلد id، سبب خواهد شد تا عملیات update نیز در حالت create تفسیر شود.
- به علاوه در اینجا به ازای هر فیلد، مباحث اعتبارسنجی نیز اضافه شدهاند؛ برای مثال فیلدهای اجباری با required: true مشخص گردیدهاند.
- اگر فیلدی نباید ویرایش شود (مانند فیلد Id)، خاصیت editable آنرا false کنید.
- در data source امکان تعریف خاصیتی به نام batch نیز وجود دارد. حالت پیش فرض آن false است. به این معنا که در حالت ویرایش، تغییرات هر ردیفی، یک درخواست مجزا را به سمت سرور سبب خواهد شد. اگر آنرا true کنید، تغییرات تمام ردیفها در طی یک درخواست به سمت سرور ارسال میشوند. در این حالت باید به خاطر داشت که پارامترهای سمت سرور، از حالت یک شیء مشخص باید به لیستی از آنها تغییر یابند.
مدیریت سمت سرور ثبت، ویرایش و حذف اطلاعات
در حالت ثبت، متد Post، توسط آدرس مشخص شده در قسمت create منبع داده گرید، فراخوانی میگردد:
نکتهی مهمی که در اینجا باید به آن دقت داشت، نحوهی بازگشت Id رکورد جدید ثبت شدهاست. در این مثال، قسمت schema منبع داده سمت کاربر به نحو ذیل تعریف شدهاست:
از این جهت که خروجی متد Get بازگردانندهی اطلاعات صفحه بندی شده، از نوع DataSourceResult است و این نوع، دارای خواصی مانند Data، Total و Aggergate است:
بنابراین در متد Post نیز باید بر این اساس، response.Content را از نوع لیستی از DataSourceResult تعریف کرد تا Kendo UI Grid بداند که Id رکورد جدید را باید از فیلد Data، همانند تنظیمات schema منبع داده خود، دریافت کند.
اگر این تنظیم صورت نگیرد، Id رکورد جدید را در گرید، مساوی صفر مشاهده خواهید کرد و عملا بدون استفاده خواهد شد؛ زیرا قابلیت ویرایش و حذف خود را از دست میدهد.
متدهای حذف و به روز رسانی سمت سرور نیز چنین امضایی را خواهند داشت:
حالت Update از HTTP Verb خاصی به نام Put استفاده میکند و ممکن است در این بین خطای The requested resource does not support http method 'PUT' را دریافت کنید. برای رفع آن ابتدا بررسی کنید که آیا Web.config برنامه دارای تعاریف ExtensionlessUrlHandler هست یا خیر. همچنین مزین کردن این متد با ویژگی HttpPut، مشکل را برطرف میکند.
تنظیمات Kendo UI Grid جهت فعال سازی CRUD
در ادامه کلیه تغییرات مورد نیاز جهت فعال سازی CRUD را در Kendo UI، به همراه مباحث بومی سازی عبارات متناظر با دکمهها و صفحات خودکار مرتبط، مشاهده میکنید:
- سادهترین حالت 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
- «فرمت کردن اطلاعات نمایش داده شده به کمک 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 //... });
- به علاوه در اینجا به ازای هر فیلد، مباحث اعتبارسنجی نیز اضافه شدهاند؛ برای مثال فیلدهای اجباری با 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; } } }
var productsDataSource = new kendo.data.DataSource({ //... schema: { data: "Data", total: "Total", } //... });
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); } } }
response.Content = new ObjectContent<DataSourceResult>( new DataSourceResult { Data = new[] { product } }, new JsonMediaTypeFormatter());
متدهای حذف و به روز رسانی سمت سرور نیز چنین امضایی را خواهند داشت:
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); } } }
تنظیمات 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: " ", 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: "لغو" } } });
- برای فعال سازی تولید صفحات خودکار ویرایش و افزودن ردیفها، نیاز است خاصیت 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 Kubernetes project plans to deprecate Docker Engine support in the kubelet and support for dockershim will be removed in a future release, probably late next year.
فایلهای پروژهها
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.
در مطلب «فرمهای مبتنی بر قالبها در Angular - قسمت چهارم - اعتبارسنجی ورودیها» با نحوهی تنظیمات اعتبارسنجی سمت کلاینت برنامههای Angular آشنا شدیم. اما اگر مدل سمت سرور ما یک چنین شکلی را داشته باشد که به همراه خطاهای اعتبارسنجی سفارشی نیز هست:
و همچنین کنترلر و اکشن متد دریافت کنندهی آن نیز به صورت ذیل تعریف شده باشد:
دو نوع خطای اعتبارسنجی سمت سرور را به سمت کلاینت ارسال خواهیم کرد:
الف) خطاهای اعتبارسنجی در سطح فیلدها
زمانیکه return BadRequest(ModelState) صورت میگیرد، محتویات شیء ModelState به همراه status code مساوی 400 به سمت کلاینت ارسال خواهد شد. در شیء ModelState یک دیکشنری که کلیدهای آن، نام خواص و مقادیر متناظر با آنها، خطاهای اعتبارسنجی تنظیم شدهی در مدل است، قرار دارند.
ب) خطاهای اعتبارسنجی عمومی
در این بین میتوان دیکشنری ModelState را توسط متد AddModelError نیز تغییر داد و برای مثال کلید آنرا مساوی "" تعریف کرد. در این حالت یک چنین خطایی به کل فرم اشاره میکند و نه به یک خاصیت خاص.
نمونهای از خروجی نهایی ارسالی به سمت کاربر:
به همین جهت نیاز است بتوان خطاهای حالت (الف) را دقیقا در ذیل هر فیلد و خطاهای حالت (ب) را در بالای فرم به صورت عمومی به کاربر نمایش داد:
پردازش و دریافت خطاهای اعتبارسنجی سمت سرور در یک برنامهی Angular
با توجه به اینکه سرور، شیء ModelState را توسط return BadRequest به سمت کلاینت ارسال میکند، برای پردازش دیکشنری دریافتی از سمت آن، تنها کافی است قسمت بروز خطای عملیات ارسال اطلاعات را بررسی کنیم:
در این HttpErrorResponse دریافتی، دو خاصیت error که همان آرایهی دیکشنری نام خواص و پیامهای خطای مرتبط با هر کدام و status code دریافتی مهم هستند:
توضیحات:
در اینجا از آرایهی errors برای نمایش خطاهای عمومی در سطح فرم استفاده میکنیم. این خطاها در ModelState، دارای کلید مساوی "" هستند. به همین جهت حلقهای را بر روی شیء responseError.error تشکیل میدهیم. به این ترتیب میتوان به نام خواص و همچنین خطاهای متناظر با آنها رسید.
از نام خاصیت یا فیلد، جهت یافتن کنترل متناظر با آن، در فرم جاری استفاده میکنیم. ممکن است کنترل تعریف شده camel case و یا pascal case باشد. به همین جهت دو حالت بررسی را در اینجا مشاهده میکنید.
در ادامه اگر control ایی یافت شد، توسط متد setErrors، کلید جدید modelStateError را که دارای خاصیت سفارشی error است، تنظیم میکنیم. با اینکار سبب خواهیم شد تا خطای اعتبارسنجی دریافتی از سمت سرور، با سیستم اعتبارسنجی Angular یکی شود. به این ترتیب میتوان این خطا را دقیقا ذیل همین کنترل در فرم نمایش داد. اگر کنترلی یافت نشد (کلید آن "" بود و یا جزو نام کنترلهای موجود در آرایهی form.controls نبود)، این خطا را به آرایهی errors اضافه میکنیم تا در بالاترین سطح فرم قابل نمایش شود.
نحوهی استفادهی از متد processModelStateErrors فوق را در متد submitForm، در قسمت شکست عملیات ارسال اطلاعات، مشاهده میکنید:
نمایش خطاهای اعتبارسنجی عمومی فرم
اکنون که کار مقدار دهی آرایهی errors انجام شدهاست، میتوان حلقهای را بر روی آن تشکیل داد و عناصر آنرا در بالای فرم، به صورت عمومی و مستقل از تمام فیلدهای آن نمایش داد:
نمایش خطاهای اعتبارسنجی در سطح فیلدهای فرم
با توجه به تنظیم خطاهای اعتبارسنجی کنترلهای Angular در متد processModelStateErrors و داشتن کلید جدید modelStateError
اکنون میتوان از این کلید جدید (ctrl.errors.modelStateError)، به صورت ذیل جهت نمایش خطای متناظر با آن (ctrl.errors.modelStateError.error) استفاده کرد:
چون تکرار خطاهای اعتبارسنجی در ذیل هر فیلد، فرم را بیش از اندازه شلوغ میکند، میتوان توسط یک ng-template این کدهای تکراری را تبدیل به یک قالب کرد و اکنون استفادهی از این قالب، به سادگی فراخوانی یک ng-container است:
در اینجا در ngTemplateOutlet، ابتدا نام قالب متناظر ذکر میشود و سپس در context آن، نام خاصیت control را که توسط قالب دریافت میشود، به template reference variable متناظری تنظیم میکنیم، تا به کنترل جاری اشاره کند. به این ترتیب میتوان به فرمهایی خلوتتر و با قابلیت مدیریت بهتری رسید.
کدهای کامل این مطلب را از اینجا میتوانید دریافت کنید.
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)];
در ادامه اگر 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 } });
<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>
<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>
کدهای کامل این مطلب را از اینجا میتوانید دریافت کنید.
نظرات مطالب
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.