در انتهای قسمت قبل، نحوهی ایجاد یک جدول جدید با فیلدی از نوع فایل استریم بررسی شد، حال اگر جدولی از پیش وجود داشت، نحوهی افزودن فیلد ویژه مورد نظر به آن، به صورت زیر است:
alter table tbl_files set(filestream_on ='default')
go
alter table tbl_files
add
[systemfile] varbinary(max) filestream null ,
FileId uniqueidentifier not null rowguidcol unique default (newid())
go
در ادامه جدول tblFiles قسمت قبل را در نظر بگیرید:
CREATE TABLE [tblFiles](
[FileId] [uniqueidentifier] ROWGUIDCOL NOT NULL,
[Title] [nvarchar](255) NOT NULL,
[SystemFile] [varbinary](max) FILESTREAM NULL,
UNIQUE NONCLUSTERED
(
[FileId] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY] FILESTREAM_ON [fsg1]
ALTER TABLE [dbo].[tblFiles] ADD DEFAULT (newid()) FOR [FileId]
GO
نحوهی افزودن رکوردی جدید به جدول tblFiles :
INSERT INTO [tblFiles]
(
[Title],
[SystemFile]
)
VALUES
(
'file-1',
CAST('data data data' AS VARBINARY(MAX))
)
اگر کنجکاو باشید که این فایل اکنون کجا ذخیره شده و نحوهی مدیریت آن توسط اس کیوال سرور به چه صورتی است، فقط کافی است به مسیری که هنگام افزودن گروه فایلها و فایل مربوطه در تنظیمات خواص دیتابیس در قسمت قبل مشخص کردیم، مراجعه کرد (شکل زیر).
بدیهی است افزودن یک رشته به این صورت کاربرد عملی ندارد و صرفا جهت یک مثال ارائه شد. در ادامه، نحوهی ثبت محتویات یک فایل را در فیلدی از نوع فایل استریم و سپس خواندن اطلاعات آنرا از طریق برنامه نویسی بررسی خواهیم کرد:
using System;
using System.IO;
using System.Data.SqlClient;
using System.Data;
namespace FileStreamTest
{
class CFS
{
/// <summary>
/// افزودن رکورد به جدول حاوی ستونی از نوع فایل استریم
/// </summary>
/// <param name="filePath">مسیر فایل</param>
/// <param name="title">عنوانی دلخواه</param>
public static void AddNewRecord(string filePath, string title)
{
//آیا فایل وجود دارد؟
if (!File.Exists(filePath))
throw new FileNotFoundException(
"لطفا مسیر فایل معتبری را مشخص نمائید", filePath);
//خواندن اطلاعات فایل در آرایهای از بایتها
byte[] buffer = File.ReadAllBytes(filePath);
using (SqlConnection objSqlCon = new SqlConnection())
{
//todo: کانکشن استرینگ باید از یک فایل کانفیگ خوانده شود
objSqlCon.ConnectionString =
"Data Source=(local);Initial Catalog=testdb2009;Integrated Security = true";
objSqlCon.Open();
//شروع یک تراکنش
using (SqlTransaction objSqlTran = objSqlCon.BeginTransaction())
{
//ساخت عبارت افزودن پارامتری
using (SqlCommand objSqlCmd = new SqlCommand(
"INSERT INTO [tblFiles]([Title],[SystemFile]) VALUES(@title , @file)",
objSqlCon, objSqlTran))
{
objSqlCmd.CommandType = CommandType.Text;
//تعریف وضعیت پارامترها و مقدار دهی آنها
objSqlCmd.Parameters.AddWithValue("@title", title);
objSqlCmd.Parameters.AddWithValue("@file", buffer);
//اجرای فرامین
objSqlCmd.ExecuteNonQuery();
}
//پایان تراکنش
objSqlTran.Commit();
}
}
}
/// <summary>
/// دریافت اطلاعات فایل ذخیره شده به صورت آرایهای از بایتها
/// </summary>
/// <param name="fileId">کلید مورد استفاده</param>
/// <returns></returns>
public static byte[] GetDataFromDb(string fileId)
{
byte[] data = null;
using (SqlConnection objConn = new SqlConnection())
{
//کوئری اس کیوال پارامتری جهت دریافت محتویات فایل
string cmdText = "SELECT SystemFile FROM tblFiles WHERE FileId=@id";
using (SqlCommand objCmd = new SqlCommand(cmdText, objConn))
{
//todo: کانکشن استرینگ باید از یک فایل کانفیگ خوانده شود
objConn.ConnectionString =
"Data Source=(local);Initial Catalog=testdb2009;Integrated Security = true";
objConn.Open();
//تنظیم کردن وضعیت و مقدار پارامتر تعریف شده در کوئری
objCmd.Parameters.AddWithValue("@id", fileId);
//اجرای فرامین و دریافت فایل
using (SqlDataReader objread = objCmd.ExecuteReader())
{
if (objread != null)
if (objread.Read())
{
if (objread["SystemFile"] != DBNull.Value)
data = (byte[])objread["SystemFile"];
}
}
}
}
return data;
}
}
}
مثالی در مورد روش استفاده از کلاس فوق :
using System.IO;
namespace FileStreamTest
{
class Program
{
static void Main(string[] args)
{
CFS.AddNewRecord(@"C:\filest05.PNG", "test1");
//آی دی رکورد ذخیره شده در دیتابیس برای مثال
byte[] data = CFS.GetDataFromDb("BB848D45-382C-4D95-BF4E-52C3509407D4");
if (data != null)
{
File.WriteAllBytes(@"C:\tst.PNG", data);
}
}
}
}
در روش دیگری که در اکثر مقالات مرتبط مورد استفاده است، از شیء SqlFileStream کمک گرفته شده و نحوهی انجام آن نیز به صورت زیر میباشد.
در ابتدا دو رویه ذخیره شده زیر را ایجاد میکنیم:
CREATE PROCEDURE [AddFile](@Title NVARCHAR(255), @filepath VARCHAR(MAX) OUTPUT)
AS
BEGIN
SET NOCOUNT ON;
DECLARE @ID UNIQUEIDENTIFIER
SET @ID = NEWID()
INSERT INTO [tblFiles]
(
[FileId],
[title],
[SystemFile]
)
VALUES
(
@ID,
@Title,
CAST('' AS VARBINARY(MAX))
)
SELECT @filepath = SystemFile.PathName()
FROM tblFiles
WHERE FileId = @ID
END
GO
CREATE PROCEDURE [GetFilePath](@Id VARCHAR(50))
AS
BEGIN
SET NOCOUNT ON;
SELECT SystemFile.PathName()
FROM tblFiles
WHERE FileId = @ID
END
رویه ذخیره شده GetFilePath نیز تنها مسیر سیستمی فایل استریم ذخیره شده را بر میگرداند.
به این ترتیب کدهای برنامه به صورت زیر تغییر خواهند کرد:
using System.Data.SqlClient;
using System.Data;
using System.Data.SqlTypes;
using System.IO;
namespace FileStreamTest
{
class CFSqlFileStream
{
/// <summary>
/// افزودن رکورد به جدول حاوی ستونی از نوع فایل استریم
/// </summary>
/// <param name="filePath">مسیر فایل</param>
/// <param name="title">عنوانی دلخواه</param>
public static void AddNewRecord(string filePath, string title)
{
//آیا فایل وجود دارد؟
if (!File.Exists(filePath))
throw new FileNotFoundException(
"لطفا مسیر فایل معتبری را مشخص نمائید", filePath);
//خواندن اطلاعات فایل در آرایهای از بایتها
byte[] buffer = File.ReadAllBytes(filePath);
using (SqlConnection objSqlCon = new SqlConnection())
{
//todo: کانکشن استرینگ باید از یک فایل کانفیگ خوانده شود
objSqlCon.ConnectionString =
"Data Source=(local);Initial Catalog=testdb2009;Integrated Security = true";
objSqlCon.Open();
//شروع یک تراکنش
using (SqlTransaction objSqlTran = objSqlCon.BeginTransaction())
{
//استفاده از رویه ذخیره شده افزودن فایل
using (SqlCommand objSqlCmd = new SqlCommand(
"AddFile", objSqlCon, objSqlTran))
{
objSqlCmd.CommandType = CommandType.StoredProcedure;
//مشخص ساختن وضعیت و مقدار پارامتر عنوان
SqlParameter objSqlParam1 = new SqlParameter("@Title", SqlDbType.NVarChar, 255);
objSqlParam1.Value = title;
//مشخص ساختن پارامتر خروجی رویه ذخیره شده
SqlParameter objSqlParamOutput = new SqlParameter("@filepath", SqlDbType.VarChar, -1);
objSqlParamOutput.Direction = ParameterDirection.Output;
//افزودن پارامترها به شیء کامند
objSqlCmd.Parameters.Add(objSqlParam1);
objSqlCmd.Parameters.Add(objSqlParamOutput);
//اجرای رویه ذخیره شده
objSqlCmd.ExecuteNonQuery();
//و سپس دریافت خروجی آن
string Path = objSqlCmd.Parameters["@filepath"].Value.ToString();
//زمینه تراکنش فایل استریم موجود را دریافت کرده و از آن برای نوشتن محتویات فایل استفاده خواهیم کرد
//این مورد نیز یکی از تازههای اس کیوال سرور 2008 است
using (SqlCommand objCmd = new SqlCommand(
"SELECT GET_FILESTREAM_TRANSACTION_CONTEXT()", objSqlCon, objSqlTran))
{
byte[] objContext = (byte[])objCmd.ExecuteScalar();
using (SqlFileStream objSqlFileStream =
new SqlFileStream(Path, objContext, FileAccess.Write))
{
objSqlFileStream.Write(buffer, 0, buffer.Length);
}
}
}
objSqlTran.Commit();
}
}
}
/// <summary>
/// دریافت اطلاعات فایل ذخیره شده به صورت آرایهای از بایتها
/// </summary>
/// <param name="fileId">کلید مورد استفاده</param>
/// <returns></returns>
public static byte[] GetDataFromDb(string fileId)
{
byte[] buffer = null;
using (SqlConnection objSqlCon = new SqlConnection())
{
//todo: کانکشن استرینگ باید از یک فایل کانفیگ خوانده شود
objSqlCon.ConnectionString =
"Data Source=(local);Initial Catalog=testdb2009;Integrated Security = true";
objSqlCon.Open();
//شروع یک تراکنش
using (SqlTransaction objSqlTran = objSqlCon.BeginTransaction())
{
//استفاده از رویه ذخیره شده دریافت مسیر فایل
using (SqlCommand objSqlCmd =
new SqlCommand("GetFilePath", objSqlCon, objSqlTran))
{
objSqlCmd.CommandType = CommandType.StoredProcedure;
//مشخص ساختن پارامتر ورودی رویه ذخیره شده و مقدار دهی آن
SqlParameter objSqlParam1 = new SqlParameter("@ID", SqlDbType.VarChar, 50);
objSqlParam1.Value = fileId;
objSqlCmd.Parameters.Add(objSqlParam1);
//اجرای رویه ذخیره شده و دریافت مسیر سیستمی فایل استریم
string path = string.Empty;
using (SqlDataReader sdr = objSqlCmd.ExecuteReader())
{
sdr.Read();
path = sdr[0].ToString();
}
//زمینه تراکنش فایل استریم موجود را دریافت کرده و از آن برای خواندن محتویات فایل استفاده خواهیم کرد
//این مورد نیز یکی از تازههای اس کیوال سرور 2008 است
using (SqlCommand objCmd = new SqlCommand(
"SELECT GET_FILESTREAM_TRANSACTION_CONTEXT()", objSqlCon, objSqlTran))
{
byte[] objContext = (byte[])objCmd.ExecuteScalar();
using (SqlFileStream objSqlFileStream =
new SqlFileStream(path, objContext, FileAccess.Read))
{
buffer = new byte[(int)objSqlFileStream.Length];
objSqlFileStream.Read(buffer, 0, buffer.Length);
}
}
}
objSqlTran.Commit();
}
}
return buffer;
}
}
}
FILESTREAM Storage in SQL Server 2008
- آدرسهای مجازی به صورت پیوسته و پشت سر هم هستند و آدرس دهی بسیار راحت است ولی دادهها بر روی یک حافظه به صورت متصل به هم یا پیوسته ذخیره یا خوانده نمیشوند و کار آدرس دهی مشکل است. پس یکی از مزایای داشتن آدرس دهی مجازی پشت سر هم قرار گرفتن آدرس هاست.
- برنامه از آدرسهای مجازی برای دسترسی به بافر حافظه استفاده میکند که بزرگتر از حافظه فیزیکی موجود هست. موقعی که نیاز به حافظه بیشتر باشد و حافظه سیستم کوچکتر یا کمتر از تقاضا باشد، مدیر حافظه، صفحات حافظه فیزیکی را به صورت یک فایل (عموما 4 کیلیویی) بر روی دیسک سخت ذخیره میکند و صفحات دادهها در موقع نیاز بین حافظه فیزیکی و دیسک سخت جابجا میشود.
- هر پردازشی که بر روی آدرسهای مجازی کار میکند ایزوله شده است. یعنی یک پروسه هیچ گاه نمیتواند به آدرسهای یک پروسه دیگر دسترسی داشته باشد و باعث تخریب دادههای آن شود.
در شکل بالا دو پروسه 64 بیتی به نامهای notepad.exe و myapp.exe قرار دارند که هر کدام فضای آدرسهای مجازی خودشان را دارند و از آدرس 0x000'0000000 شروع و تا آدرس 0x7FF'FFFFFFFF ادامه میابند. هر قسمت شامل یک صفحه 4 کیلویی از حافظه مجازی یا فیزیکی است. به برنامه نوتپد دقت کنید که از سه صفحه پشت سر هم یا پیوسته تشکیل شده که آدرس شروع آن 0x7F7'93950000 می باشد ولی در حافظه فیزیکی خبری از پیوسته بودن دیده نمیشود و حتما این نکته را متوجه شدید که هر دو پروسه از یک آدرس شروع استفاده کردهاند، ولی به آدرسی متفاوت از حافظه فیزیکی نگاشت شده اند.
فضای کاربری و فضای سیستمی User space and system space
گفتیم بسیاری از پروسهها در حالت user mode و پروسههای هسته سیستم عامل و درایورها در حالت kernel mode اجرا میشوند. هر پروسه مد کاربر از فضای آدرس دهی مجازی خودش استفاده میکند ولی در حالت کرنل همه از یک فضای آدرس دهی استفاده میکنند که به آن فضای سیستمی میگویند و برای مد کاربری میگویند فضای کاربری.
در سیستمهای 32 بیتی نهایتا تا 4 گیگ حافظه میتوان به اینها تخصیص داد؛ 2 گیگ ابتدایی به user space و دو گیگ بعدی به system space :
در ویندوزهای 32 بیتی شما امکان تغییر این مقدار حافظه را در میان بوت دارید و میتوانید حافظه کاربری را تا 3 گیگ مشخص کنید و یک گیگ را برای فضای سیستمی. برای اینکار میتوانید از برنامه bcedit استفاده کنید.
در سیستمهای 64 بیتی میزان حافظههای مجازی به صورت تئوری تا 16 اگزابایت مشخص شده است؛ ولی در عمل تنها بخش کوچکی از آن یعنی 8 ترابایت استفاده میشود.
- برنامه جهت اجرا در مد کاربر یک درخواست را برای خواندن دادههای یک device را آماده میکند. سپس برنامه آدرس شروع یک بافر را برای دریافت داده، مشخص میکند.
- وظیفه این درایور یک قطعه در مد کرنل این است که عملیات خواندن را شروع کرده و کنترل را به درخواست کننده ارسال میکند.
- بعد device یک وقفه را به هر تردی thread که در حال اجراست ارسال میکند تا بگوید، عملیات خواندن پایان یافته است. این وقفه توسط ترد درایور مربوطه دریافت میشود.
- حالا دیگر درایور نباید دادهها را در همان جایی که گام اول برنامه مشخص کرده است ذخیره کند. چون این آدرس که برنامه در مد کاربری مشخص کرده است، با نمونهای که این فرآیند محاسبه میکند متفاوت است.
فرض کنید میخواهیم وضعیت یک سایت را از لحاظ قابلیت دسترسی مونیتور کنیم، آیا Up است، Down است و امثال آن. یک سری از وب سرورها ping را بستهاند (ICMP Replies). بنابراین الزاما با استفاده از این روش ساده نمیتوان به مقصود رسید.
خوشبختانه انجام اینکار با استفاده از فضای نام استاندارد System.Net و کلاس HttpWebRequest ، بدون نیاز به هیچگونه کلاس یا کامپوننت خارجی، به سادگی قابل انجام است. کلاس زیر به همین منظور تهیه شده است:
using System;
using System.Net;
public class CSiteMonitor
{
public struct UrlHeaderInfo
{
public DateTime _lastModified;
public string _statusCode;
public string _errorMessage;
}
/// <summary>
/// آیا آدرس اینترنتی وارد شده معتبر است؟
/// </summary>
/// <param name="url">آدرس مورد نظر جهت بررسی</param>
/// <returns></returns>
public static bool IsValidURL(string url)
{
try
{
Uri uri = new Uri(url);
return (uri.Scheme == Uri.UriSchemeHttp) || (uri.Scheme == Uri.UriSchemeHttps);
}
catch { return false; }
}
/// <summary>
/// آدرس اینترنتی جهت بررسی
/// </summary>
/// <param name="url"></param>
/// <returns></returns>
/// <exception cref="ArgumentException">آدرس اینترنتی وارد شده معتبر نیست</exception>
public static UrlHeaderInfo GetSiteHeaderInfo(string url)
{
if (!IsValidURL(url))
throw new ArgumentException("آدرس اینترنتی وارد شده معتبر نیست", "url");
UrlHeaderInfo hhi = new UrlHeaderInfo { _lastModified = DateTime.Now, _statusCode = "NOK!", _errorMessage = string.Empty };
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
//request.Proxy
request.Method = "HEAD";
request.AllowAutoRedirect = true;
request.UserAgent = "Mozilla/5.0 (Windows; U; Windows NT 5.0; ; rv:1.8.0.7) Gecko/20060917 Firefox/1.9.0.1";
request.Timeout = 1000 * 300;
request.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate;
try
{
using (HttpWebResponse response = (HttpWebResponse) request.GetResponse())
{
hhi._statusCode = response.StatusCode.ToString();
hhi._lastModified = response.LastModified;
}
}
catch (Exception ex)
{
hhi._errorMessage = ex.Message;
}
return hhi;
}
}
- در متد GetSiteHeaderInfo نیاز بود تا از یک تابع بیش از یک خروجی داشته باشیم. راههای زیاد برای انجام اینکار هست.برای مثال:
الف)ارائه خروجیها به صورت یک آرایه. زیاد جالب نیست، چون اگر شخصی دقیقا مستندات متد شما را مطالعه نکند نمیداند که ترتیب خروجیها چگونه است و هر کدام چه معنایی دارند.
ب)ارائه خروجیها با استفاده از آرگومانهایی از نوع out یا ref . در دنیای شیء گرایی این نوع روشها را باید منسوخ شده در نظر گرفت و صرف سازگاری با زبانهایی مانند C که این روش در آنها رواج دارد (استفاده از آرگومانهایی از نوع اشارهگر) باید به آن نگاه کرد و نه بیشتر.
ج)خروجیها را به صورت یک کلاس یا struct درنظر گرفت تا استفاده کننده دقیقا بداند که فیلد خروجی چه معنایی دارد و هم چنین دقیقا چه تعداد خروجی مد نظر است.
- حتما باید از try/finally جهت اطمینان حاصل نمودن از بسته شدن response استفاده شود، در غیر اینصورت پس از دو خطای متوالی حاصل شده عملا دیگر نمیتوان از شیء response استفاده کرد. البته همانطور که پیش تر نیز ذکر شد، عبارت using توسط کامپایلر به try/finally بست داده میشود، بنابراین جهت خوانایی بیشتر کد بهتر است از این روش استفاده شود.
- جهت بلاک نشدن درخواست بهتر است از یک UserAgent کمک گرفته شود.
- جهت بررسی اعتبار یک آدرس اینترنتی یا میتوان از Regular expressions استفاده کرد یا از شیء Uri که روش آنرا ملاحظه میکنید.
- اگر در شبکه داخلی خود از پروکسی استفاده میشود، میتوان قسمت request.Proxy را با شیء پروکسی تنظیم شده مطابق مشخصات پروکسی سرور خود، بکار برد.
- در این مثال بیشتر هدف پیاده سازی کلاس دریافت اطلاعات هدر سایت بود و از ارائه کدهای مربوط به تایمر یا یک ترد جهت بررسی متوالی وضعیت سایت صرفنظر شد.
مثالی در مورد نحوهی استفاده از کلاس فوق:
CSiteMonitor.UrlHeaderInfo info = CSiteMonitor.GetSiteHeaderInfo("http://www.google.com");
MessageBox.Show(info._statusCode);
AngularJS #2
بنده طبق فرمایشات شما به روش زیر عمل کردم ولی بهم کار نمیده و پارشال مورد نظر را به من نشان نمیده. میشه بگید مشکل از کجاست؟
public class PostController : Controller { ApplicationDbContext db; // GET: Post public ActionResult List() { using (db=new ApplicationDbContext()) { var query = db.Posts.ToList(); return PartialView("List",query); } } }
و کدهای ویو:
<div ng-app="postmodule"> <div ng-controller="PostController"> <ul> <li ng-repeat="item in ListOfItems"> {{item.Title}} <hr /> {{item.Text}} </li> </ul> </div> </div>
و کدهایی که در فایل _Layout.cshtml نوشته ام:
<div class="container body-content" > <script src="~/Scripts/angular.js"></script> <script src="~/Scripts/angular-route.js"></script> <script> var PostApp = angular.module('postmodule', []).config(['$routeProvider', function ($routeProvider) { $routeProvider. when('/list', { templateUrl: '/Post/List', controller: 'PostController' }); }]); PostApp.controller('PostController', function ($scope, $http, postServices) { //... $scope.ListOfItems = postServices.GetPosts() .success(function (data) { $scope.ListOfItems = data; }); //... }); PostApp.service('postServices', function ($http) { this.GetPosts = function () { return $http.get('/Post/List'); }; }); </script> <a href="#list">list post</a> <div ng-view=""></div> @RenderBody() <hr /> <footer> <p>© @DateTime.Now.Year - My ASP.NET Application</p> </footer> </div>
fields: { "Price": { type: "number", validation: { required: true, min: 1 } } }
Kendo UI Validation و HTML 5
در HTML 5 امکان تعریف نوعهای خاص کنترلهای ورودی کاربر مانند email، url، number، range، date، search و color وجود دارد. برای مثال در اینجا اگر کاربر تاریخ غیرمعتبری را وارد کند، مرورگر پیام اعتبارسنجی متناظری را به او نمایش خواهد داد. همچنین در HTML 5 امکان افزودن ویژگی required نیز به کنترلهای ورودی پیش بینی شدهاست. اما باید درنظر داشت که مرورگرهای قدیمی از این امکانات پشتیبانی نمیکنند. در این حالت Kendo UI با تشویق استفاده از روش معرفی شده در HTML 5، با آن یکپارچه شده و همچنین این قابلیتهای اعتبارسنجی HTML 5 را در مرورگرهای قدیمی نیز میسر میکند. Kendo UI Validation جزو نسخهی سورس باز Kendo UI با مجوز Apache نیز میباشد.
نمونهای از امکانات اعتبارسنجی توکار HTML 5 را در اینجا مشاهده میکنید:
<input type="text" name="firstName" required /> <input type="text" name="twitter" pattern="https?://(?:www\.)?twitter\.com/.+i" /> <input type="number" name="age" min="1" max="42" /> <input type="number" name="age" min="1" max="100" step="2" /> <input type="url" name="url" /> <input type="email" name="email" />
یکپارچه سازی اعتبارسنجی Kendo UI با اعتبارسنجی HTML 5
در اینجا یک فرم تشکیل شده با ساختار HTML 5 را ملاحظه میکنید. هر دو فیلد ورودی، با ویژگی استاندارد required مزین شدهاند. همچنین توسط ویژگی type، ورودی دوم جهت دریافت آدرس ایمیل معرفی شدهاست.
چون فیلد دوم دارای دو اعتبارسنجی تعریف شده است، دارای دو ویژگی *-data برای تعریف پیامهای اعتبارسنجی متناظر نیز میباشد. الگوی تعریف آنها data-[rule]-msg است.
<div class="k-rtl"> <form id="testView"> <label for="firstName">نام</label> <input id="firstName" name="firstName" type="text" class="k-textbox" required validationmessage="لطفا نامی را وارد کنید"> <br> <label for="emailId">آدرس پست الکترونیک</label> <input id="emailId" name="emailId" type="email" dir="ltr" required class="k-textbox" data-required-msg="لطفا ایمیلی را وارد کنید." data-email-msg="ایمیل وارد شده معتبر نیست."> <br> <input type="submit" class="k-button" value="ارسال"> </form> </div> <script type="text/javascript"> $(function () { $("form#testView").kendoValidator(); }); </script>
تعیین محل نمایش پیامهای اعتبارسنجی
پیامهای اعتبارسنجی Kendo UI به صورت خودکار در کنار فیلد متناظر با آن نمایش داده میشوند. اما اگر نیاز به تعیین مکان دستی آنها وجود داشت (جهت خوانایی بهتر) باید به نحو ذیل عمل کرد:
<input type="text" id="name" name="name" required> <span class="k-invalid-msg" data-for="name"></span>
تعریف سراسری پیامهای اعتبارسنجی
در مثال فوق، به ازای تک تک فیلدهای ورودی، پیام اعتبارسنجی متناظر با required وارد شد. میتوان این پیامها را حذف کرد و در قسمت messages متد kendoValidator قرار داد:
<script type="text/javascript"> $(function () { $("form#testView").kendoValidator({ messages: { // {0} would be replaced with the input element's name required: '{0} را تکمیل کنید.', email: 'ایمیل وارد شده معتبر نیست.' } }); }); </script>
- در این پیامها {0} با مقدار ویژگی name فیلد ورودی متناظر جایگزین میشود.
- اگر هم در markup و هم در تعاریف kendoValidator، پیامهای اعتبارسنجی تعریف شوند، حق تقدم با تعاریف markup خواهد بود.
اعتبارسنجی سفارشی سمت کاربر
علاوه بر امکانات استاندارد HTML 5، امکان تعریف دستورهای اعتبارسنجی سفارشی نیز وجود دارد:
<script type="text/javascript"> $(function () { $("form#testView").kendoValidator({ rules: { customRule1: function (input) { if (!input.is("[id=firstName]")) return true; var re = /^[A-Za-z]+$/; return re.test(input.val()); } //, customRule1: …. }, messages: { // {0} would be replaced with the input element's name required: '{0} را تکمیل کنید.', email: 'ایمیل وارد شده معتبر نیست.', customRule1: 'اعداد مجاز نیستند.' } }); }); </script>
- همانطور که ملاحظه میکنید، برای تعریف منطق اعتبارسنجی سفارشی، باید از خاصیت rules ورودی متد kendoValidator شروع کرد. در اینجا نام یک متد callback دلخواهی را وارد کرده و سپس بر اساس منطق اعتبارسنجی مورد نظر، باید true/false را بازگشت داد. برای نمونه در این مثال اگر کاربر در فیلد نام، عدد وارد کند، ورودی او مورد قبول واقع نخواهد شد.
- باید دقت داشت که اگر بررسی input.is صورت نگیرد، منطق تعریف شده به تمام کنترلهای صفحه اعمال میشود.
- پیام متناظر با این دستور سفارشی جدید، در قسمت messages، دقیقا بر اساس نام callback method تعریف شده در قسمت rules باید تعریف شود.
فراخوانی دستی اعتبارسنجی یک فرم
در حالت پیش فرض، با کلیک بر روی دکمهی ارسال، اعتبارسنجی کلیه عناصر فرم به صورت خودکار انجام میشود. اگر بخواهیم در این بین یک پیام سفارشی را نیز نمایش دهیم میتوان به صورت زیر عمل کرد:
<script type="text/javascript"> $(function () { $("form#testView").submit(function (event) { event.preventDefault(); var validator = $("form#testView").data("kendoValidator"); if (validator.validate()) { alert("validated!"); } else { alert("There is invalid data in the form."); } }); $("form#testView").kendoValidator(); }); </script>
اعتبارسنجی سفارشی در DataSource
در تعریف فیلدهای مدل DataSource، امکان تعریف اعتبارسنجیهای پیش فرضی مانند rquired، min، max و امثال آن وجود دارد که نمونهای از آنرا در بحث فعال سازی CRUD در Kendo UI Grid مشاهده کردید:
fields: { "serviceName": { type: "string", defaultValue: "Inspection", editable: true, nullable: false, validation: { /*...*/ } }, // ... }
schema: { model: { id: "ProductID", fields: { ProductID: { editable: false, nullable: true }, ProductName: { validation: { required: true, custom1: function (input) { if (input.is("[name='ProductName']") && input.val() != "") { input.attr("data-custom1-msg", "نام محصول باید با حرف بزرگ انگلیسی شروع شود"); return /^[A-Z]/.test(input.val()); } return true; } // ,custom2: ... } }, UnitPrice: { type: "number", validation: { required: true, min: 1} }, Discontinued: { type: "boolean" }, UnitsInStock: { type: "number", validation: { min: 0, required: true} } } } }
متد ()input.val مقدار کنترل جاری را بر میگرداند. برای دسترسی به مقدار سایر کنترلها میتوان از روش ()fieldName").val#")$ استفاده کرد.
همان طور که ممکن است بدانید، اکشن متدها در کنترلرهای MVC میتوانند انواع مختلفی را برگشت دهند که شرح آن در مطالب این سایت به مفصل گذشته است. یکی از این انواع، نوع ActionResult میباشد. این یک کلاس پایه برای انواع برگشتی توسط اکشن متدها مثل JsonResult، FileResult میباشد. (اطلاعات بیشتر را اینجا بخوانید) اما ممکن است مواقعی پیش بیاید که بخواهید نوعی را توسط یک اکشن متد برگشت دهید که به صورت توکار تعریف نشده باشد. مثلا زمانی را در نظر بگیرید که بخواهید یک تصویر امنیتی را برگشت دهید. یکی از راه حلهای ممکن به این صورت است که کلاسی ایجاد شود که از کلاس پایه ActionResult ارث بری کرده باشد. بدین صورت:
using System; using System.Web.Mvc; namespace MVCPersianCaptcha.Models { public class CaptchaImageResult : ActionResult { public override void ExecuteResult(ControllerContext context) { throw new NotImplementedException(); } } }
کدهای اولیه برای ایجاد یک تصویر امنیتی به صورت خیلی ساده از کلاسهای فراهم شده توسط +GDI ، که در دات نت فریمورک وجود دارند استفاده خواهند کرد. برای این کار ابتدا یک شیء از کلاس Bitmap با دستور زیر ایجاد خواهیم کرد:
// Create a new 32-bit bitmap image. Bitmap bitmap = new Bitmap(width, height, PixelFormat.Format32bppArgb);
سپس شیئی از نوع Graphics برای انجام عملیات ترسیم نوشتههای فارسی روی شیء bitmap ساخته میشود:
// Create a graphics object for drawing. Graphics gfxCaptchaImage = Graphics.FromImage(bitmap);
gfxCaptchaImage.PageUnit = GraphicsUnit.Pixel; gfxCaptchaImage.SmoothingMode = SmoothingMode.HighQuality; gfxCaptchaImage.Clear(Color.White);
سپس یک عدد اتفاقی بین 1000 و 9999 با دستور زیر تولید میشود:
// Create a Random Number from 1000 to 9999 int salt = CaptchaHelpers.CreateSalt();
public int CreateSalt() { Random random = new Random(); return random.Next(1000, 9999); }
HttpContext.Current.Session["captchastring"] = salt;
string randomString = (salt).NumberToText(Language.Persian);
به صورت پیش فرض نوشتههای تصویر امنیتی به صورت چپ چین نوشته خواهند شد، و با توجه به این که نوشته ای که باید در تصویر امنیتی قرار بگیرد فارسی است، پس بهتر است آنرا به صورت راست به چپ در تصویر بنویسیم، بدین صورت:
// Set up the text format. var format = new StringFormat(); int faLCID = new System.Globalization.CultureInfo("fa-IR").LCID; format.SetDigitSubstitution(faLCID, StringDigitSubstitute.National); format.Alignment = StringAlignment.Near; format.LineAlignment = StringAlignment.Near; format.FormatFlags = StringFormatFlags.DirectionRightToLeft;
// Font of Captcha and its size Font font = new Font("Tahoma", 10);
// Create a path for text GraphicsPath path = new GraphicsPath();
path.AddString(randomString, font.FontFamily, (int)font.Style, (gfxCaptchaImage.DpiY * font.SizeInPoints / 72), new Rectangle(0, 0, width, height), format);
gfxCaptchaImage.DrawPath(Pens.Navy, path);
//-- using a sin ware distort the image int distortion = random.Next(-10, 10); using (Bitmap copy = (Bitmap)bitmap.Clone()) { for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { int newX = (int)(x + (distortion * Math.Sin(Math.PI * y / 64.0))); int newY = (int)(y + (distortion * Math.Cos(Math.PI * x / 64.0))); if (newX < 0 || newX >= width) newX = 0; if (newY < 0 || newY >= height) newY = 0; bitmap.SetPixel(x, y, copy.GetPixel(newX, newY)); } } }
//-- Draw the graphic to the bitmap gfxCaptchaImage.DrawImage(bitmap, new Point(0, 0)); gfxCaptchaImage.Flush();
HttpResponseBase response = context.HttpContext.Response; response.ContentType = "image/jpeg"; bitmap.Save(response.OutputStream, ImageFormat.Jpeg);
// Clean up. font.Dispose(); gfxCaptchaImage.Dispose(); bitmap.Dispose();
public CaptchaImageResult CaptchaImage() { return new CaptchaImageResult(); }
<img src="@Url.Action("CaptchaImage")"/>
[HttpPost] public ActionResult Index(LogOnModel model) { if (!ModelState.IsValid) return View(model); if (model.CaptchaInputText == Session["captchastring"].ToString()) TempData["message"] = "تصویر امنتی را صحیح وارد کرده اید"; else TempData["message"] = "تصویر امنیتی را اشتباه وارد کرده اید"; return View(); }
کدهای کامل مربوط به این مطلب را به همراه یک مثال از لینک زیر دریافت نمائید:
MVC-Persian-Captcha
پایان پروژه ASP.NET Ajax Control Toolkit !
برای کلا جاوا اسکریپت هم کتابخانههای Unit test وجود دارد تا از این دلچرکین بودن خارج شوید:
QUnit
@محمد امین شریفی
اینبار انتخاب مایکروسافت درست بوده:
1- هزینه توسعه رو کاهش دادند. برای مثال هدف از Ajax Control Toolkit چیزی بوده شبیه به پلاگینهای jQUery اما با هزینه بیشتر. اینبار شما هزاران پلاگین جی کوئری در اختیارتون هست و نه صرفا مجموعهی Ajax Control Toolkit .
2- موردی رو که در مقاله بالا بهش اشاره شده و دلیل اصلی کنار گذاشتن ASP.NET Ajax بوده پایین بودن کارآیی ASP.NET Ajax است. هم حجم بالایی دارد و هم تبادلات شبکهای حجیمی را به همراه دارد که در هر دو مورد جی کوئری بهینهتر است.
و یک سری موارد دیگر که قبلا بهش اشاره کرده بودم: (+)
3- انتخاب اصلی کتابخانه Ajax در ASP.NET MVC الان همین جیکوئری است و در مقاله فوق به یک سری افزونه و کتابخانه کمکی برای آن اشاره شده (که در حال توسعه و تکمیل است).
ویجتهای وب Kendo UI مجموعهای از کنترلهای سفارشی HTML 5 هستند که برفراز jQuery تهیه شدهاند. این کنترلها برای برنامههای وب و همچنین برنامههای دسکتاپ لمسی طراحی شدهاند.
بهترین روش برای مشاهدهی این مجموعه، مراجعه به فایل examples\index.html پوشهی اصلی Kendo UI است که لیست کاملی از این ویجتها را به همراه مثالهای مرتبط ارائه میدهد. تعدادی از اعضای این مجموعه شامل کنترلهای ذیل هستند:
Window, TreeView, Tooltip, ToolBar, TimePicker, TabStrip, Splitter, Sortable, Slider, Gantt, Scheduler, ProgressBar, PanelBar, NumericTextBox, Notification, MultiSelect, Menu, MaskedTextBox, ListView, PivotGrid, Grid, Editor, DropDownList, DateTimePicker, DatePicker, ComboBox, ColorPicker, Calendar, Button, AutoComplete
نحوهی استفاده کلی از ویجتهای وب Kendo UI
با توجه به اینکه کنترلهای Kendo UI مبتنی بر jQuery هستند، نحوهی استفاده از آنها، مشابه سایر افزونههای جیکوئری است. ابتدا المانی به صفحه اضافه میشود:
<input id="pickDate" type="text"/>
<script type="text/javascript"> $(function() { $("#pickDate").kendoDatePicker(); }); </script>
<input id="dateOfBirth" type="text" data-role="datepicker" />
برای فعال سازی حالت declarative initialization باید به دو نکتهی مهم دقت داشت:
الف) در مطلب معرفی Kendo UI اسکریپتهای ذیل برای آماده سازی Kendo Ui معرفی شدند:
<!--KendoUI: Web--> <link href="styles/kendo.common.min.css" rel="stylesheet" type="text/css" /> <link href="styles/kendo.default.min.css" rel="stylesheet" type="text/css" /> <script src="js/jquery.min.js" type="text/javascript"></script> <script src="js/kendo.web.min.js" type="text/javascript"></script> <!--KendoUI: DataViz--> <link href="styles/kendo.dataviz.min.css" rel="stylesheet" type="text/css" /> <script src="js/kendo.dataviz.min.js" type="text/javascript"></script> <!--KendoUI: Mobile--> <link href="styles/kendo.mobile.all.min.css" rel="stylesheet" type="text/css" /> <script src="js/kendo.mobile.min.js" type="text/javascript"></script>
<link href="styles/kendo.common.min.css" rel="stylesheet" type="text/css" /> <link href="styles/kendo.default.min.css" rel="stylesheet" type="text/css" /> <script src="js/jquery.min.js" type="text/javascript"></script> <script src="js/kendo.all.min.js" type="text/javascript"></script>
یک مثال کامل:
<!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <title></title> <link href="styles/kendo.common.min.css" rel="stylesheet" type="text/css" /> <link href="styles/kendo.default.min.css" rel="stylesheet" type="text/css" /> <script src="js/jquery.min.js" type="text/javascript"></script> <script src="js/kendo.all.min.js" type="text/javascript"></script> <script type="text/javascript"> $(function () { $("#pickDate").kendoDatePicker(); }); $(function () { // initialize any widgets in the #container div kendo.init($("#container")); }); </script> </head> <body> <span> Pick a date: <input id="pickDate" type="text" /> </span> <div id="container"> <input id="dateOfBirth" type="text" data-role="datepicker" /> <div id="colors" data-role="colorpalette" data-columns="4" data-tile-size="{ width: 34, height: 19 }"></div> </div> </body> </html>
- در اینجا pickDate به صورت معمولی فعال شدهاست.
- اما در قسمت kendo.init نام یک ناحیه یا نام یک کنترل را میتوان ذکر کرد. برای مثال در اینجا کل ناحیهی مشخص شده توسط یک div با id مساوی container به صورت یکجا با تمام کنترلهای داخل آن فعال گردیدهاست.
بنابراین برای اعمال declarative initialization، یک ناحیه را توسط kendo.init مشخص کرده و سپس توسط data-roleها، نام ویجت وب مورد نظر را به صورت lower case مشخص میکنیم. همچنین فایلهای اسکریپت مورد استفاده نیز نباید تداخلی داشته باشند.
تنظیمات ویجتهای وب Kendo UI
تاکنون نمونهی سادهای از بکارگیری ویجتهای وب Kendo UI را بررسی کردیم؛ اما این ویجتها توسط تنظیمات پیش بینی شده برای آنها بسیار قابل تنظیم و تغییر هستند. تنظیمات آنها نیز بستگی به روش استفاده و آغاز آنها دارد. برای مثال اگر این ویجتها را توسط کدهای جاوا اسکریپتی آغاز کردهاید، در همانجا توسط پارامترهای افزونهی جیکوئری میتوان تنظیمات مرتبط را اعمال کرد:
<script type="text/javascript"> $(function () { $("#pickDate").kendoDatePicker({ format: "yyyy/MM/dd" }); }); </script>
در حالت declarative initialization، پارامتر format تبدیل به ویژگی data-format خواهد شد:
<input id="dateOfBirth" type="text" data-role="datepicker" data-format="yyyy/MM/dd" />
تنظیمات DataSource ویجتهای وب
بسیاری از ویجتهای وب Kendo UI با دادهها سر و کار دارند مانند Grid، Auto Complete، Combo box و غیره. این کنترلها دادههای خود را از طریق خاصیت DataSource دریافت میکنند. برای نمونه در اینجا یک combo box را در نظر بگیرید. در مثال اول، خاصیت dataSource کنترل ComboBox در همان افزونهی جیکوئری تنظیم شدهاست:
<input id="colorPicker1" /> <script type="text/javascript"> $(document).ready(function () { $("#colorPicker1").kendoComboBox({ dataSource: ["Blue", "Green", "Red", "Yellow"] }); }); </script>
<input id="colorPicker2" data-role="combobox" data-source='["Blue", "Green", "Red", "Yellow"]' /> <script type="text/javascript"> $(document).ready(function () { kendo.init($("#colorPicker2")); }); </script>
کار با رویدادهای ویجتهای وب
نحوهی کار با رویدادهای ویجتهای وب نیز بر اساس نحوهی آغاز آنها متفاوت است. در مثالهای ذیل، دو حالت متفاوت تنظیم رویداد change را توسط خواص افزونهی جیکوئری:
<input id="colorPicker3" /> <script type="text/javascript"> function onColorChange(e) { alert('Color Change!'); } $(document).ready(function () { $("#colorPicker3").kendoComboBox({ dataSource: ["Blue", "Green", "Red", "Yellow"], change: onColorChange }); }); </script>
<input id="colorPicker4" data-role="combobox" data-source='["Blue", "Green", "Red", "Yellow"]' data-change="onColorChange" /> <script type="text/javascript"> function onColorChange(e) { alert('Color Change!'); } $(document).ready(function () { kendo.init($("#colorPicker4")); }); </script>
تغییر قالب ویجتهای وب
Kendo UI همیشه یک جفت CSS را جهت تعیین قالبهای ویجتهای خود، مورد استفاده قرار میدهد. برای نمونه در مثالهای فوق، kendo.common.min.css حاوی اطلاعات محل قرارگیری و اندازهی ویجتها است. شیوه نامهی دوم همیشه به شکل kendo.[skin].min.css تعریف میشود که دارای اطلاعات رنگ و پس زمینهی ویجتها خواهد بود؛ مانند kendo.black.min.css، kendo.blueopal.min.css و امثال آن که در پوشهی styles قابل مشاهده هستند.
همچنین باید دقت داشت که همیشه common باید پیش از skin ذکر شود؛ زیرا در تعدادی از حالات، شیوه نامهی skin، اطلاعات common را بازنویسی میکند.
علاوه بر skinهای پیش فرض موجود در پوشهی styles، امکان استفاده از یک theme builder آنلاین نیز وجود دارد: kendo-ui-themebuilder