نحوه افزودن Nuget Package ها به Template؟
بازسازی کامل پوشه packages بستههای NuGet به صورت خودکار
فقط فایل config رو در پروژه داشته باشید کافی هست.
تولید برگه امتحان تستی با استفاده از pdfReport
.Export(export => { export.ToExcel(); })
بله. همانطور که مشاهده میکنید، چون تعداد فونتهای نصب شدهی بر روی سیستم من بیش از اندازه است (1800 فونت)، این مشکل رخ میدهد. هر بار آغاز برنامههای دات نت، به همراه بررسی تمام فونتهای نصب شدهی بر روی سیستم هم هست و اگر تعداد آنها زیاد باشد، شاید چند دقیقهای این بررسی طول بکشد.
راه حلها
الف) حذف فونتهای اضافی سیستم
این مورد به طور قطع بر روی سایر برنامههای غیردات نتی هم تاثیر مثبت خواهد گذاشت. برای نمونه، این مورد بارگذاری فونتها، در مرورگرها هم صادق است. به علاوه مصرف RAM سیستم را هم کاهش خواهد داد.
برای حذف فونتهای اضافی:
- ابتدا به مسیر C:\Windows\Fonts مراجعه کنید. در لیست فونتها، ابتدا ctrl+a و سپس delete. بله! حذف تمام فونتها، تا جایی که ممکن است.
- در ادامه ویندوز به صورت توکار، قابلیت بازگشت به لیست ابتدایی سیستمی خود را دارد (جهت ترمیم مواردی که نباید حذف میشدند). برای این منظور باید مراحل ذیل را طی کنید:
Start > Control Panel -> Appearance and Personalization -> Fonts -> Font Settings -> Restore Default Font Settings
با کلیک بر روی دکمهی «Restore Default Font Settings» قلمهای اصلی ویندوز مجددا نصب خواهند شد و سیستم به حالت اول باز میگردد.
ب) تنظیم سرویس Font Cache ویندوز
سرویس ویژهای به نام «Windows Presentation Foundation Font Cache 3.0.0.0» در ویندوزهایی که دات نت فریم ورک بر روی آنها نصب است، وجود دارد:
کار آن کش کردن و به اشتراک گذاشتن اطلاعات قلمهای نصب شدهی بر روی سیستم، بین تمام برنامههای WPF در حال اجرا است.
حالت آغاز این سرویس بر روی manual است. به این معنا که تا یک برنامهی WPF ایی بر روی سیستم اجرا نشود، این سرویس فعال نخواهد شد. میتوان این حالت آغاز را بر روی automatic قرار داد تا به تمام برنامههای WPF سیستم به صورت یکسانی، پیش از اجرای آنها اعمال شود.
این تغییر توسط مایکروسافت هم توصیه شدهاست: «12. Understand the PresentationFontCache service »
نتیجه گیری
اگر آغاز برنامهی دات نتی شما آنچنان سریع نیست، الزاما مشکل از Entity framework نیست. چه تعدادی فونت را نصب کردهاید؟!
<Button Command="{Binding IncreaseStepsCountCommand}" Text="Button with command binding" /> <Button Text="Button with event to command"> <Button.Behaviors> <prismBehaviors:EventToCommandBehavior Command="{Binding IncreaseStepsCountCommand}" EventName="Clicked" /> </Button.Behaviors> </Button>
xmlns:prismBehaviors="clr-namespace:Prism.Behaviors;assembly=Prism.Forms"
<Label Text="{Binding StepsCount, StringFormat='{}Button tapped {0} times!'}"> <Label.GestureRecognizers> <SwipeGestureRecognizer Command="{Binding IncreaseStepsCountCommand}" Direction="Left" /> </Label.GestureRecognizers> </Label>
<Label Text="{Binding StepsCount, StringFormat='{}Button tapped {0} times!'}" />
<Entry x:Name="MyEntry" /> <Label Text="{Binding Text, Source={x:Reference MyEntry}}" />
<ContentPage ... xmlns:vm="clr-namespace:XamApp.ViewModels" x:DataType="vm:HelloWorldViewModel">
private int _StepsCount; public int StepsCount { get => _StepsCount; set => SetProperty(ref _StepsCount, value); }
public int StepsCount { get; set; }
اگر به برنامههای جدید نوشته شده برای ویندوز 7 دقت کنیم، از یک سری امکانات مخصوص آن جهت بهبود دسترسی پذیری به قابلیتهایی که ارائه میدهند، استفاده شده است. برای مثال برنامهی OneNote مجموعهی آفیس را در نظر بگیرید. اگر بر روی آیکون آن در نوار وظیفهی ویندوز کلیک راست کنیم، لیست آخرین فایلهای گشوده شده توسط آن مشخص است و با کلیک بر روی هر کدام، به سادگی میتوان این فایل را گشود. یک چنین قابلیتی در منوی آغازین ویندوز نیز تعبیه شده است (شکلهای زیر):
خبر خوب اینکه برای اضافه کردن این قابلیت به برنامههای WPF4 نیازی به کد نویسی نیست و این موارد که تحت عنوان استفاده از Jump list ویندوز 7 تعریف شدهاند، با کمی دستکاری فایل App.Xaml برنامه، فعال میگردند:
<Application x:Class="WpfApplication1.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
StartupUri="MainWindow.xaml">
<Application.Resources>
</Application.Resources>
<JumpList.JumpList>
<JumpList ShowRecentCategory="True" />
</JumpList.JumpList>
</Application>
سؤال: من اینکار را انجام دادم ولی کار نمیکنه!؟
پاسخ: بله. کار نمیکنه! این قابلیت تنها زمانی فعال خواهد شد که علاوه بر نکتهی فوق، پسوند فایل یا فایلهایی نیز به برنامهی شما منتسب شده باشد. این انتسابها مطلب جدیدی نیست و در تمام برنامههای ویندوزی باید توسط بکارگیری API ویندوز مدیریت شود. قطعه کد زیر اینکار را انجام خواهد داد:
using System;
using System.Runtime.InteropServices;
using Microsoft.Win32;
namespace Common.Files
{
//from : http://www.devx.com/vb2themax/Tip/19554?type=kbArticle&trk=MSCP
public class FileAssociation
{
const int ShcneAssocchanged = 0x8000000;
const int ShcnfIdlist = 0;
public static void CreateFileAssociation(
string extension,
string className,
string description,
string exeProgram)
{
// ensure that there is a leading dot
if (extension.Substring(0, 1) != ".")
extension = string.Format(".{0}", extension);
try
{
if (IsAssociated(extension)) return;
// create a value for this key that contains the classname
using (var key1 = Registry.ClassesRoot.CreateSubKey(extension))
{
if (key1 != null)
{
key1.SetValue("", className);
// create a new key for the Class name
using (var key2 = Registry.ClassesRoot.CreateSubKey(className))
{
if (key2 != null)
{
key2.SetValue("", description);
// associate the program to open the files with this extension
using (var key3 = Registry.ClassesRoot.CreateSubKey(string.Format(@"{0}\Shell\Open\Command", className)))
{
if (key3 != null) key3.SetValue("", string.Format(@"{0} ""%1""", exeProgram));
}
}
}
}
}
// notify Windows that file associations have changed
SHChangeNotify(ShcneAssocchanged, ShcnfIdlist, 0, 0);
}
catch (Exception ex)
{
//todo: log ...
}
}
// Return true if extension already associated in registry
public static bool IsAssociated(string extension)
{
return (Registry.ClassesRoot.OpenSubKey(extension, false) != null);
}
[DllImport("shell32.dll")]
public static extern void SHChangeNotify(int wEventId, int uFlags, int dwItem1, int dwItem2);
}
}
private static void createFileAssociation()
{
var appPath = Assembly.GetExecutingAssembly().Location;
FileAssociation.CreateFileAssociation(".xyz", "xyz", "xyz File",
appPath
);
}
سؤال: من این کارها را هم انجام دادم. الان به چه صورت از آن استفاده کنم؟
زمانیکه کاربری بر روی یکی از این فایلهای ذکر شده در لیست آخرین فایلهای گشوده شده توسط برنامه کلیک کند، آدرس این فایل به صورت یک آرگومان به برنامه ارسال خواهد شد. برای مدیریت آن در WPF باید به فایل App.Xaml.cs مراجعه کرده و چند سطر زیر را به آن افزود:
public partial class App
{
public App()
{
this.Startup += appStartup;
}
void appStartup(object sender, StartupEventArgs e)
{
if (e.Args.Any())
{
this.Properties["StartupFileName"] = e.Args[0];
}
}
//...
در این کد، e.Args حاوی مسیر فایل انتخابی است. برای مثال در اینجا مقدار آن به خاصیت StartupFileName انتساب داده شده است. این خاصیت در برنامههای WPF به صورت یک خاصیت عمومی تعریف شده است و در سراسر برنامه (مثلا در رخداد آغاز فرم اصلی آن یا هر جای دیگری) به صورت زیر قابل دسترسی است:
var startupFileName = Application.Current.Properties["StartupFileName"];
سؤال: برنامهی من از OpenFileDialog برای گشودن فایلها استفاده نمیکند. آیا راه دیگری برای افزودن مسیرهای باز شده به Jump lists ویندوز 7 وجود دارد؟
پاسخ: بله. همانطور که میدانید عناصر XAML با اشیاء دات نت تناظر یک به یک دارند. به این معنا که JumpList تعریف شده در ابتدای این مطلب در فایل App.XAML ، دقیقا معادل کلاسی به همین نام در دات نت فریم ورک است (تعریف شده در فضای نام System.Windows.Shell) و با کد نویسی نیز قابل دسترسی و مدیریت است. برای مثال:
var jumpList = JumpList.GetJumpList(App.Current);
var jumpPath = new JumpPath();
jumpPath.Path = "some path goes here....";
// If the CustomCategory property is null
// or Empty, the item is added to the Tasks category
jumpPath.CustomCategory = "Files";
JumpList.AddToRecentCategory(jumpPath);
jumpList.Apply();
<Application x:Class="Win7Wpf4.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
StartupUri="MainWindow.xaml">
<Application.Resources>
</Application.Resources>
<JumpList.JumpList>
<JumpList ShowRecentCategory="True">
<JumpPath
CustomCategory="Files"
Path="Some path goes here..."
/>
</JumpList>
</JumpList.JumpList>
</Application>
prototype: { startRequest: function( element ) { //... }, stopRequest: function( element, valid ) { //... },
var originalStartRequest = $.validator.prototype.startRequest; $.validator.prototype.startRequest = function (element) { // یافتن عنصر در حال بررسی var container = $('form').find("[data-valmsg-for='" + element.name + "']"); // افزودن کلاس نمایش منتظر بمانید container.addClass('loading'); // فراخوانی متد اصلی برای انجام کارهای درونی افزونه originalStartRequest.apply(this, arguments); }; var originalStopRequest = $.validator.prototype.stopRequest; $.validator.prototype.stopRequest = function (element) { // یافتن عنصر در حال بررسی var container = $('form').find("[data-valmsg-for='" + element.name + "']"); // حذف کلاس نمایش منتظر بمانید container.removeClass('loading'); // فراخوانی متد اصلی برای انجام کارهای درونی افزونه originalStopRequest.apply(this, arguments); };
<span class="field-validation-valid" data-valmsg-replace="true" data-valmsg-for="Url"></span>
نمونهی این بازنویسی در مطلب « اعتبار سنجی سمت کاربر wysiwyg-editorها در ASP.NET MVC » هم انجام شدهاست.
Count یا Any
با وجود امکانات مهیای توسط LINQ ، یک سری از عادات متداول حین کار با گروهی از اشیاء باید کنار گذاشته شوند؛ برای مثال چگونگی بررسی این مطلب که آیا شیء IEnumerable ما حاوی عنصری هست یا خیر.
روش متداول انجام اینکار استفاده از متد Count است. چون این متد پیش از تدارک امکانات LINQ نیز وجود داشته، بنابراین اولین موردی که جهت بررسی آن به ذهن خطور میکند، استفاده از متد Count میباشد؛ برای مثال:
void Method(IEnumerable<Status> statuses)
{
if (statuses != null && statuses.Count() > 0)
// do something...
}
ولی در اینجا هدف ما این است که آیا شیء IEnumerable دارای حداقل یک عنصر است یا خیر؟ بنابراین بجای استفاده از متد Count بهتر است از یکی از extension methods فراهم شده توسط LINQ به نام Any استفاده شود.
کار متد Any ، پس از بررسی اولین عنصر یک مجموعه، خاتمه خواهد یافت و بدیهی است که نسبت به متد Count بسیار سریعتر و کم هزینهتر خواهد بود. علاوه بر آن حین کار با بانکهای اطلاعاتی برای مثال توسط LINQ to Entities ، در SQL نهایی تولیدی به EXISTS ترجمه خواهد شد.
void Method(IEnumerable<Status> statuses)
{
if (statuses != null && statuses.Any())
// do something...
}
از این پس حین استفاده از انواع و اقسام لیستها، آرایهها، IEnumerable ها و امثال آنها، جهت بررسی خالی بودن یا نبودن آنها تنها از متد Any فراهم شده توسط LINQ استفاده نمائید.
if (myArray != null && myArray.Any())
// do something...
ASP.NET Web API فریم ورکی برای ساختن APIهای وب بر روی فریم ورک دات نت است. در این مقاله با استفاده از این فریم ورک، API وبی خواهیم ساخت که لیستی از محصولات را بر میگرداند. صفحه وب کلاینت، با استفاده از jQuery نتایج را نمایش خواهد داد.
یک پروژه Web API بسازید
در ویژوال استودیو 2013 پروژه جدیدی از نوع ASP.NET Web Application بسازید و نام آن را "ProductsApp" انتخاب کنید.
در دیالوگ New ASP.NET Project قالب Empty را انتخاب کنید و در قسمت "Add folders and core references for" گزینه Web API را انتخاب نمایید.
می توانید از قالب Web API هم استفاده کنید. این قالب با استفاده از ASP.NET MVC صفحات راهنمای API را خواهد ساخت. در این مقاله از قالب Empty استفاده میکنیم تا تمرکز اصلی، روی خود فریم ورک Web API باشد. بطور کلی برای استفاده از این فریم ورک لازم نیست با ASP.NET MVC آشنایی داشته باشید.
افزودن یک مدل
یک مدل (model) آبجکتی است که داده اپلیکیشن شما را معرفی میکند. ASP.NET Web API میتواند بصورت خودکار مدل شما را به JSON, XML و برخی فرمتهای دیگر مرتب (serialize) کند، و سپس داده مرتب شده را در بدنه پیام HTTP Response بنویسد. تا وقتی که یک کلاینت بتواند فرمت مرتب سازی دادهها را بخواند، میتواند آبجکت شما را deserialize کند. اکثر کلاینتها میتوانند XML یا JSON را تفسیر کنند. بعلاوه کلاینتها میتوانند فرمت مورد نظرشان را با تنظیم Accept header در پیام HTTP Request مشخص کنند.
بگذارید تا با ساختن مدلی ساده که یک محصول (product) را معرفی میکند شروع کنیم.
کلاس جدیدی در پوشه Models ایجاد کنید.
نام کلاس را به "Product" تغییر دهید، و خواص زیر را به آن اضافه کنید.
namespace ProductsApp.Models { public class Product { public int Id { get; set; } public string Name { get; set; } public string Category { get; set; } public decimal Price { get; set; } } }
افزودن یک کنترلر
در Web API کنترلرها آبجکت هایی هستند که درخواستهای HTTP را مدیریت کرده و آنها را به اکشن متدها نگاشت میکنند. ما کنترلری خواهیم ساخت که میتواند لیستی از محصولات، یا محصولی بخصوص را بر اساس شناسه برگرداند. اگر از ASP.NET MVC استفاده کرده اید، با کنترلرها آشنا هستید. کنترلرهای Web API مشابه کنترلرهای MVC هستند، با این تفاوت که بجای ارث بری از کلاس Controller از کلاس ApiController مشتق میشوند.
کنترلر جدیدی در پوشه Controllers ایجاد کنید.
در دیالوگ Add Scaffold گزینه Web API Controller - Empty را انتخاب کرده و روی Add کلیک کنید.
در دیالوگ Add Controller نام کنترلر را به "ProductsController" تغییر دهید و روی Add کلیک کنید.
توجه کنید که ملزم به ساختن کنترلرهای خود در پوشه Controllers نیستید، و این روش صرفا قراردادی برای مرتب نگاه داشتن ساختار پروژهها است. کنترلر ساخته شده را باز کنید و کد زیر را به آن اضافه نمایید.
using ProductsApp.Models; using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Web.Http; namespace ProductsApp.Controllers { public class ProductsController : ApiController { Product[] products = new Product[] { new Product { Id = 1, Name = "Tomato Soup", Category = "Groceries", Price = 1 }, new Product { Id = 2, Name = "Yo-yo", Category = "Toys", Price = 3.75M }, new Product { Id = 3, Name = "Hammer", Category = "Hardware", Price = 16.99M } }; public IEnumerable<Product> GetAllProducts() { return products; } public IHttpActionResult GetProduct(int id) { var product = products.FirstOrDefault((p) => p.Id == id); if (product == null) { return NotFound(); } return Ok(product); } }
کنترلر ما دو متد برای دریافت محصولات تعریف میکند:
- متد GetAllProducts لیست تمام محصولات را در قالب یک <IEnumerable<Product بر میگرداند.
- متد GetProductById سعی میکند محصولی را بر اساس شناسه تعیین شده پیدا کند.
همین! حالا یک Web API ساده دارید. هر یک از متدهای این کنترلر، به یک یا چند URI پاسخ میدهند:
URI | Controller Method |
api/products/ | GetAllProducts |
api/products/id/ | GetProductById |
برای اطلاعات بیشتر درباره نحوه نگاشت درخواستهای HTTP به اکشن متدها توسط Web API به این لینک مراجعه کنید.
فراخوانی Web API با جاوا اسکریپت و jQuery
در این قسمت یک صفحه HTML خواهیم ساخت که با استفاده از AJAX متدهای Web API را فراخوانی میکند. برای ارسال درخواستهای آژاکسی و بروز رسانی صفحه بمنظور نمایش نتایج دریافتی از jQuery استفاده میکنیم.
در پنجره Solution Explorer روی نام پروژه کلیک راست کرده و گزینه Add, New Item را انتخاب کنید.
در دیالوگ Add New Item قالب HTML Page را انتخاب کنید و نام فایل را به "index.html" تغییر دهید.
حال محتوای این فایل را با لیست زیر جایگزین کنید.
<!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <title>Product App</title> </head> <body> <div> <h2>All Products</h2> <ul id="products" /> </div> <div> <h2>Search by ID</h2> <input type="text" id="prodId" size="5" /> <input type="button" value="Search" onclick="find();" /> <p id="product" /> </div> <script src="http://ajax.aspnetcdn.com/ajax/jQuery/jquery-2.0.3.min.js"></script> <script> var uri = 'api/products'; $(document).ready(function () { // Send an AJAX request $.getJSON(uri) .done(function (data) { // On success, 'data' contains a list of products. $.each(data, function (key, item) { // Add a list item for the product. $('<li>', { text: formatItem(item) }).appendTo($('#products')); }); }); }); function formatItem(item) { return item.Name + ': $' + item.Price; } function find() { var id = $('#prodId').val(); $.getJSON(uri + '/' + id) .done(function (data) { $('#product').text(formatItem(data)); }) .fail(function (jqXHR, textStatus, err) { $('#product').text('Error: ' + err); }); } </script> </body> </html>
گرفتن لیستی از محصولات
برای گرفتن لیستی از محصولات، یک درخواست HTTP GET به آدرس "api/products/" ارسال کنید.
تابع getJSON یک درخواست آژاکسی ارسال میکند. پاسخ دریافتی هم آرایه ای از آبجکتهای JSON خواهد بود. تابع done در صورت موفقیت آمیز بودن درخواست، اجرا میشود. که در این صورت ما DOM را با اطلاعات محصولات بروز رسانی میکنیم.
$(document).ready(function () { // Send an AJAX request $.getJSON(apiUrl) .done(function (data) { // On success, 'data' contains a list of products. $.each(data, function (key, item) { // Add a list item for the product. $('<li>', { text: formatItem(item) }).appendTo($('#products')); }); }); });
گرفتن محصولی مشخص
برای گرفتن یک محصول توسط شناسه (ID) آن کافی است یک درخواست HTTP GET به آدرس "api/products/id/" ارسال کنید.
function find() { var id = $('#prodId').val(); $.getJSON(apiUrl + '/' + id) .done(function (data) { $('#product').text(formatItem(data)); }) .fail(function (jqXHR, textStatus, err) { $('#product').text('Error: ' + err); }); }
اجرای اپلیکیشن
اپلیکیشن را با F5 اجرا کنید. صفحه وب باز شده باید چیزی مشابه تصویر زیر باشد.
برای گرفتن محصولی مشخص، شناسه آن را وارد کنید و روی Search کلیک کنید.
اگر شناسه نامعتبری وارد کنید، سرور یک خطای HTTP بر میگرداند.
استفاده از F12 برای مشاهده درخواستها و پاسخ ها
هنگام کار با سرویسهای HTTP، مشاهدهی درخواستهای ارسال شده و پاسخهای دریافتی بسیار مفید است. برای اینکار میتوانید از ابزار توسعه دهندگان وب استفاده کنید، که اکثر مرورگرهای مدرن، پیاده سازی خودشان را دارند. در اینترنت اکسپلورر میتوانید با F12 به این ابزار دسترسی پیدا کنید. به برگه Network بروید و روی Start Capturing کلیک کنید. حالا صفحه وب را مجددا بارگذاری (reload) کنید. در این مرحله اینترنت اکسپلورر ترافیک HTTP بین مرورگر و سرور را تسخیر میکند. میتوانید تمام ترافیک HTTP روی صفحه جاری را مشاهده کنید.
به دنبال آدرس نسبی "api/products/" بگردید و آن را انتخاب کنید. سپس روی Go to detailed view کلیک کنید تا جزئیات ترافیک را مشاهده کنید. در نمای جزئیات، میتوانید headerها و بدنه درخواستها و پاسخها را ببینید. مثلا اگر روی برگه Request headers کلیک کنید، خواهید دید که اپلیکیشن ما در Accept header دادهها را با فرمت "application/json" درخواست کرده است.
اگر روی برگه Response body کلیک کنید، میتوانید ببینید چگونه لیست محصولات با فرمت JSON سریال شده است. همانطور که گفته شده مرورگرهای دیگر هم قابلیتهای مشابهی دارند. یک ابزار مفید دیگر Fiddler است. با استفاده از این ابزار میتوانید تمام ترافیک HTTP خود را مانیتور کرده، و همچنین درخواستهای جدیدی بسازید که این امر کنترل کاملی روی HTTP headers به شما میدهد.
قدمهای بعدی
در این مثال به کمک MVC5، یک کپچای ساده و قابل فهم را تولید و استفاده خواهیم کرد. این نوشته بر اساس این مقاله ایجاد شده و جزئیات زیادی برای درک افراد مبتدی به آن افزوده شده است که امیدوارم راهنمای مفیدی برای علاقمندان باشد.
با کلیک راست بر روی پوشه کنترلر، یک کنترلر به منظور ایجاد کپچا بسازید و اکشن متد زیر را در آن کنترلر ایجاد کنید:
public class CaptchaController : Controller { public ActionResult CaptchaImage(string prefix, bool noisy = true) { var rand = new Random((int)DateTime.Now.Ticks); //generate new question int a = rand.Next(10, 99); int b = rand.Next(0, 9); var captcha = string.Format("{0} + {1} = ?", a, b); //store answer Session["Captcha" + prefix] = a + b; //image stream FileContentResult img = null; using (var mem = new MemoryStream()) using (var bmp = new Bitmap(130, 30)) using (var gfx = Graphics.FromImage((Image)bmp)) { gfx.TextRenderingHint = TextRenderingHint.ClearTypeGridFit; gfx.SmoothingMode = SmoothingMode.AntiAlias; gfx.FillRectangle(Brushes.White, new Rectangle(0, 0, bmp.Width, bmp.Height)); //add noise if (noisy) { int i, r, x, y; var pen = new Pen(Color.Yellow); for (i = 1; i < 10; i++) { pen.Color = Color.FromArgb( (rand.Next(0, 255)), (rand.Next(0, 255)), (rand.Next(0, 255))); r = rand.Next(0, (130 / 3)); x = rand.Next(0, 130); y = rand.Next(0, 30); gfx.DrawEllipse(pen, x - r, y - r, r, r); } } //add question gfx.DrawString(captcha, new Font("Tahoma", 15), Brushes.Gray, 2, 3); //render as Jpeg bmp.Save(mem, System.Drawing.Imaging.ImageFormat.Jpeg); img = this.File(mem.GetBuffer(), "image/Jpeg"); } return img; }
همانطور که از کد فوق پیداست، دو مقدار a و b، به شکل اتفاقی ایجاد میشوند و حاصل جمع آنها در یک Session نگهداری خواهد شد. سپس تصویری بر اساس تصویر a+b ایجاد میشود (مثل 3+4). این تصویر خروجی این اکشن متد است. به سادگی میتوانید این اکشن را بر اساس خواسته خود اصلاح کنید؛ مثلا به جای حاصل جمع دو عدد، از کاربرد چند حرف یا عدد که بصورت اتفاقی تولید کردهاید، استفاده نمائید.
فرض کنید میخواهیم کپچا را هنگام ثبت نام استفاده کنیم.
در فایل AccountViewModels.cs در پوشه مدلها در کلاس RegisterViewModel خاصیت زیر را اضافه کنید:
[Required(ErrorMessage = "لطفا {0} را وارد کنید")] [Display(Name = "حاصل جمع")] public string Captcha { get; set; }
حالا در پوشه View/Account به فایل Register.Cshtml خاصیت فوق را اضافه کنید:
<div class="form-group"> <input type="button" value="" id="refresh" /> @Html.LabelFor(model => model.Captcha) <img alt="Captcha" id="imgcpatcha" src="@Url.Action("CaptchaImage","Captcha")" style="" /> </div>
وظیفه این بخش، نمایش کپچاست. تگ img دارای آدرسی است که توسط اکشن متدی که در ابتدای این مقاله ایجاد نمودهایم تولید میشود. این آدرس تصویر کپچاست. یک دکمه هم با شناسه refresh برای به روز رسانی مجدد تصویر در نظر گرفتهایم.
حالا کد ایجکسی برای آپدیت کپچا توسط دکمه refresh را به شکل زیر بنویسید (من در پایین ویوی Register، اسکریپت زیر را قرار دادم):
<script type="text/javascript"> $(function () { $('#refresh').click(function () { $.ajax({ url: '@Url.Action("CaptchaImage","Captcha")', type: "GET", data: null }) .done(function (functionResult) { $("#imgcpatcha").attr("src", "/Captcha/CaptchaImage?" + functionResult); }); }); }); </script>
آنچه در url نوشته شده است، شاید اصولیترین شکل فراخوانی یک اکشن متد باشد. این اکشن در ابتدای مقاله تحت کنترلری به نام Captcha معرفی شده بود و خروجی آن آدرس یک فایل تصویری است. نوع ارتباط، Get است و هیچ اطلاعاتی به اکشن متد فرستاده نمیشود، اما اکشن متد ما آدرسی را به ما برمیگرداند که تحت نام FunctionResult آن را دریافت کرده و به کمک کد جی کوئری، مقدارش را در ویژگی src تصویر موجود در صفحه جاری جایگزین میکنیم. دقت کنید که برای دسترسی به تصویر، لازم است جایگزینی آدرس، در ویژگی src به شکل فوق صورت پذیرد.*
تنها کار باقیمانده اضافه کردن کد زیر به ابتدای اکشن متد Register درون کنترلر Account است.
if (Session["Captcha"] == null || Session["Captcha"].ToString() != model.Captcha) { ModelState.AddModelError("Captcha", "مجموع اشتباه است"); }
واضح است که اینکار پیش از شرط if(ModelState.IsValidate) صورت میگیرد و وظیفه شرط فوق، بررسی ِ برابریِ مقدار Session تولید شده در اکشن CaptchaImage (ابتدای این مقاله) با مقدار ورودی کاربر است. (مقداری که از طریق خاصیت تولیدی خودمان به آن دسترسی داریم) . بدیهیاست اگر این دو مقدار نابرابر باشند، یک خطا به ModelState اضافه میشود و شرط ModelState.IsValid که در اولین خط بعد از کد فوق وجود دارد، برقرار نخواهد بود و پیغام خطا در صفحه ثبت نام نمایش داده خواهد شد.
تصویر زیر نمونهی نتیجهای است که حاصل خواهد شد :
* اصلاح : دقت کنید بدون استفاده از ایجکس هم میتوانید تصویر فوق را آپدیت کنید:
$('#refresh').click(function () { var d = new Date(); $("#imgcpatcha").attr("src", "Captcha/CaptchaImage?" + d.getTime()); });
رویداد کلیک را با کد فوق جایگزین کنید؛ دو نکته در اینجا وجود دارد :
یک. استفاده از زمان در انتهای آدرس به خاطر مشکلاتیست که فایرفاکس یا IE با اینگونه آپدیتهای تصویری دارند. این دو مرورگر (بر خلاف کروم) تصاویر را نگهداری میکنند و آپدیت به روش فوق به مشکل برخورد میکند مگر آنکه آدرس را به کمک اضافه کردن زمان آپدیت کنید تا مرورگر متوجه داستان شود
دو. همانطور که میبینید آدرس تصویر در حقیقت خروجی یک اکشن است. پس نیازی نیست هر بار این اکشن را به کمک ایجکس صدا بزنیم و روش فوق در مرورگرهای مختلف جواب خواهد داد.