نظرات مطالب
روش‌هایی برای بهبود سرعت برنامه‌های مبتنی بر Entity framework
سلام؛
خروجی IEnumerable، یعنی این عبارت را محاسبه کن 
وقتی خروجی query مثلا در نوع  IEnumerable ذخیره میشه تا وقتی مورد استفاده قرار نگرفته رفت و برگشتی به بانک صورت نگرفته مثل  IQueryable   :
        private IEnumerable<Entity1> ienumerableEntites;
        private IQueryable<Entity1> queryablelEntities; 

            ienumerableEntites = context.ٍEntity1.Where(x=>x.EntityID>50);
            queryablelEntities = context.Entity1.Where(x => x.EntityID > 50);
منظورتون از جمله بالا چیست؟
مطالب
Blazor 5x - قسمت 14 - کار با فرم‌ها - بخش 2 - تعریف فرم‌ها و اعتبارسنجی آن‌ها
در ادامه قصد داریم از سرویس زیر که در قسمت قبل تکمیل شد، در یک برنامه‌ی Blazor Server استفاده کنیم:
namespace BlazorServer.Services
{
    public interface IHotelRoomService
    {
        Task<HotelRoomDTO> CreateHotelRoomAsync(HotelRoomDTO hotelRoomDTO);

        Task<int> DeleteHotelRoomAsync(int roomId);

        IAsyncEnumerable<HotelRoomDTO> GetAllHotelRoomsAsync();

        Task<HotelRoomDTO> GetHotelRoomAsync(int roomId);

        Task<HotelRoomDTO> IsRoomUniqueAsync(string name);

        Task<HotelRoomDTO> UpdateHotelRoomAsync(int roomId, HotelRoomDTO hotelRoomDTO);
    }
}


تعریف کامپوننت‌های ابتدایی نمایش لیست اتاق‌ها و ثبت و ویرایش آن‌ها


در ابتدا کامپوننت‌های خالی نمایش لیست اتاق‌ها و همچنین فرم خالی ثبت و ویرایش آن‌ها را به همراه مسیریابی‌های مرتبط، ایجاد می‌کنیم. به همین جهت ابتدا داخل پوشه‌ی Pages، پوشه‌ی جدید HotelRoom را ایجاد کرده و فایل جدید HotelRoomList.razor را با محتوای ابتدایی زیر، به آن اضافه می‌کنیم.
@page "/hotel-room"

<div class="row mt-4">
    <div class="col-8">
        <h4 class="card-title text-info">Hotel Rooms</h4>
    </div>
    <div class="col-3 offset-1">
        <NavLink href="hotel-room/create" class="btn btn-info">Add New Room</NavLink>
    </div>
</div>

@code {

}
این کامپوننت در مسیر hotel-room/ قابل دسترسی خواهد بود. بر این اساس، به کامپوننت Shared\NavMenu.razor مراجعه کرده و مدخل منوی آن‌را تعریف می‌کنیم:
<li class="nav-item px-3">
    <NavLink class="nav-link" href="hotel-room">
        <span class="oi oi-list-rich" aria-hidden="true"></span> Hotel Rooms
    </NavLink>
</li>

تا اینجا صفحه‌ی ابتدایی نمایش لیست اتاق‌ها، به همراه یک دکمه‌ی افزودن اتاق جدید نیز هست. به همین جهت فایل جدید Pages\HotelRoom\HotelRoomUpsert.razor را به همراه مسیریابی hotel-room/create/ برای تعریف کامپوننت ابتدایی ثبت و ویرایش اطلاعات اتاق‌ها، اضافه می‌کنیم:
@page "/hotel-room/create"

<h3>HotelRoomUpsert</h3>

@code {

}
- واژه‌ی Upsert در مورد فرمی بکاربرده می‌شود که هم برای ثبت اطلاعات و هم برای ویرایش اطلاعات از آن استفاده می‌شود.
- NavLink تعریف شده‌ی در کامپوننت نمایش لیست اتاق‌ها، به مسیریابی کامپوننت فوق اشاره می‌کند.


ایجاد فرم ثبت یک اتاق جدید

برای ثبت یک اتاق جدید نیاز است به مدل UI آن که همان HotelRoomDTO تعریف شده‌ی در قسمت قبل است، دسترسی داشت. به همین جهت در پروژه‌ی BlazorServer.App، ارجاعی را به پروژه‌ی BlazorServer.Models.csproj اضافه می‌کنیم:
<Project Sdk="Microsoft.NET.Sdk.Web">
  <ItemGroup>
    <ProjectReference Include="..\BlazorServer.Models\BlazorServer.Models.csproj" />
  </ItemGroup>
</Project>
سپس جهت سراسری اعلام کردن فضای نام آن، یک سطر زیر را به انتهای فایل BlazorServer.App\_Imports.razor اضافه می‌کنیم:
@using BlazorServer.Models
اکنون می‌توانیم کامپوننت Pages\HotelRoom\HotelRoomUpsert.razor را به صورت زیر تکمیل کنیم:
@page "/hotel-room/create"

<div class="row mt-2 mb-5">
    <h3 class="card-title text-info mb-3 ml-3">@Title Hotel Room</h3>
    <div class="col-md-12">
        <div class="card">
            <div class="card-body">
                <EditForm Model="HotelRoomModel">
                    <div class="form-group">
                        <label>Name</label>
                        <InputText @bind-Value="HotelRoomModel.Name" class="form-control"></InputText>
                    </div>
                </EditForm>
            </div>
        </div>
    </div>
</div>

@code
{
    private HotelRoomDTO HotelRoomModel = new HotelRoomDTO();
    private string Title = "Create";
}
توضیحات:
- در برنامه‌های Blazor، کامپوننت ویژه‌ی EditForm را بجای تگ استاندارد form، مورد استفاده قرار می‌دهیم.
- این کامپوننت، مدل فرم را از فیلد HotelRoomModel که در قسمت کدها تعریف کردیم، دریافت می‌کند. کار آن تامین اطلاعات فیلدهای فرم است.
- سپس در EditForm تعریف شده، بجای المان استاندارد input، از کامپوننت InputText برای دریافت اطلاعات متنی استفاده می‌شود. با bind-value@ در قسمت چهارم این سری بیشتر آشنا شدیم و کار آن two-way data binding است. در اینجا هر اطلاعاتی که وارد می‌شود، سبب به روز رسانی خودکار مقدار خاصیت HotelRoomModel.Name می‌شود و برعکس.

یک نکته: در قسمت قبل، مدل UI را از نوع رکورد C# 9.0 و init only تعریف کردیم. رکوردها، با EditForm و two-way databinding آن سازگاری ندارند (bind-value@ در اینجا) و بیشتر برای کنترلرهای برنامه‌های Web API که یکبار قرار است کار وهله سازی آن‌ها در زمان دریافت اطلاعات از کاربر صورت گیرد، مناسب هستند و نه با فرم‌های پویای Blazor. به همین جهت به پروژه‌ی BlazorServer.Models مراجعه کرده و نوع آن‌ها را به کلاس و init‌ها را به set معمولی تغییر می‌دهیم تا در فرم‌های Blazor هم قابل استفاده شوند.

تا اینجا کامپوننت ثبت اطلاعات یک اتاق جدید، چنین شکلی را پیدا کرده‌است:



تکمیل سایر فیلدهای فرم ورود اطلاعات اتاق

پس از تعریف فیلد ورود اطلاعات نام اتاق، سایر فیلدهای متناظر با HotelRoomDTO را نیز به صورت زیر به EditForm تعریف شده اضافه می‌کنیم که در اینجا از InputNumber برای دریافت اطلاعات عددی و از InputTextArea، برای دریافت اطلاعات متنی چندسطری استفاده شده‌است:
<EditForm Model="HotelRoomModel">
    <div class="form-group">
        <label>Name</label>
        <InputText @bind-Value="HotelRoomModel.Name" class="form-control"></InputText>
    </div>
    <div class="form-group">
        <label>Occupancy</label>
        <InputNumber @bind-Value="HotelRoomModel.Occupancy" class="form-control"></InputNumber>
    </div>
    <div class="form-group">
        <label>Rate</label>
        <InputNumber @bind-Value="HotelRoomModel.RegularRate" class="form-control"></InputNumber>
    </div>
    <div class="form-group">
        <label>Sq ft.</label>
        <InputText @bind-Value="HotelRoomModel.SqFt" class="form-control"></InputText>
    </div>
    <div class="form-group">
        <label>Details</label>
        <InputTextArea @bind-Value="HotelRoomModel.Details" class="form-control"></InputTextArea>
    </div>
    <div class="form-group">
        <button class="btn btn-primary">@Title Room</button>
        <NavLink href="hotel-room" class="btn btn-secondary">Back to Index</NavLink>
    </div>
</EditForm>
با این خروجی:



تعریف اعتبارسنجی‌های فیلدهای یک فرم Blazor

در حین تعریف یک فرم، برای واکنش نشان دادن به دکمه‌ی submit، می‌توان رویداد OnSubmit را به کامپوننت EditForm اضافه کرد که سبب فراخوانی متدی در قسمت کدهای کامپوننت جاری خواهد شد؛ مانند فراخوانی متد HandleHotelRoomUpsert در مثال زیر:
<EditForm Model="HotelRoomModel" OnSubmit="HandleHotelRoomUpsert">
</EditForm>

@code
{
    private HotelRoomDTO HotelRoomModel = new HotelRoomDTO();

    private async Task HandleHotelRoomUpsert()
    {

    }
}
هرچند HotelRoomDTO تعریف شده به همراه تعریف اعتبارسنجی‌هایی مانند Required است، اما اگر بر روی دکمه‌ی submit کلیک کنیم، متد HandleHotelRoomUpsert فراخوانی می‌شود. یعنی روال رویدادگردان OnSubmit، صرفنظر از وضعیت اعتبارسنجی مدل فرم، همواره با submit فرم، اجرا می‌شود.
اگر این مورد، مدنظر نیست، می‌توان بجای OnSubmit، از رویداد OnValidSubmit استفاده کرد. در این حالت اگر اعتبارسنجی مدل فرم با شکست مواجه شود، دیگر متد HandleHotelRoomUpsert فراخوانی نخواهد شد. همچنین در این حالت می‌توان خطاهای اعتبارسنجی را نیز در فرم نمایش داد:
<EditForm Model="HotelRoomModel" OnValidSubmit="HandleHotelRoomUpsert">
    <DataAnnotationsValidator />
    @*<ValidationSummary />*@
    <div class="form-group">
        <label>Name</label>
        <InputText @bind-Value="HotelRoomModel.Name" class="form-control"></InputText>
        <ValidationMessage For="()=>HotelRoomModel.Name"></ValidationMessage>
    </div>
    <div class="form-group">
        <label>Occupancy</label>
        <InputNumber @bind-Value="HotelRoomModel.Occupancy" class="form-control"></InputNumber>
        <ValidationMessage For="()=>HotelRoomModel.Occupancy"></ValidationMessage>
    </div>
    <div class="form-group">
        <label>Rate</label>
        <InputNumber @bind-Value="HotelRoomModel.RegularRate" class="form-control"></InputNumber>
        <ValidationMessage For="()=>HotelRoomModel.RegularRate"></ValidationMessage>
    </div>
- در اینجا قسمت‌های تغییر کرده را مشاهده می‌کنید که به همراه درج DataAnnotationsValidator و ValidationMessage‌ها است.
- کامپوننت DataAnnotationsValidator، اعتبارسنجی مبتنی بر data annotations را مانند [Required]، در دامنه‌ی دید یک EditForm فعال می‌کند.
- اگر خواستیم تمام خطاهای اعتبارسنجی را به صورت خلاصه‌ای در بالای فرم نمایش دهیم، می‌توان از کامپوننت ValidationSummary استفاده کرد.
- و یا اگر خواستیم خطاها را به صورت اختصاصی‌تری ذیل هر تکست‌باکس نمایش دهیم، می‌توان از کامپوننت ValidationMessage کمک گرفت. خاصیت For آن از نوع <Expression<System.Func تعریف شده‌است که اجازه‌ی تعریف strongly typed نام خاصیت در حال اعتبارسنجی را به صورتی که مشاهده می‌کنید، میسر می‌کند.



ثبت اولین اتاق هتل

در ادامه می‌خواهیم روال رویدادگردان HandleHotelRoomUpsert را مدیریت کنیم. به همین جهت نیاز به کار با سرویس IHotelRoomService ابتدای بحث خواهد بود. بنابراین در ابتدا به فایل BlazorServer.App\_Imports.razor مراجعه کرده و فضای نام سرویس‌های برنامه را اضافه می‌کنیم:
@using BlazorServer.Services
اکنون امکان تزریق IHotelRoomService را که در قسمت قبل پیاده سازی و به سیستم تزریق وابستگی‌های برنامه معرفی کردیم، پیدا می‌کنیم:
@page "/hotel-room/create"

@inject IHotelRoomService HotelRoomService
@inject NavigationManager NavigationManager


@code
{
    private HotelRoomDTO HotelRoomModel = new HotelRoomDTO();
    private string Title = "Create";

    private async Task HandleHotelRoomUpsert()
    {
        var roomDetailsByName = await HotelRoomService.IsRoomUniqueAsync(HotelRoomModel.Name);
        if (roomDetailsByName != null)
        {
            //there is a duplicate room. show an error msg.
            return;
        }

        var createdResult = await HotelRoomService.CreateHotelRoomAsync(HotelRoomModel);
        NavigationManager.NavigateTo("hotel-room");
    }
}
در اینجا در ابتدا، سرویس IHotelRoomService به کامپوننت جاری تزریق شده و سپس از متدهای IsRoomUniqueAsync و CreateHotelRoomAsync آن، جهت بررسی منحصربفرد بودن نام اتاق و ثبت نهایی اطلاعات مدل برنامه که به فرم جاری به صورت دو طرفه‌ای متصل است، استفاده کرده‌ایم. در نهایت پس از ثبت اطلاعات، کاربر به صفحه‌ی نمایش لیست اتاق‌ها، توسط سرویس توکار NavigationManager، هدایت می‌شود.

اگر پیشتر با ASP.NET Web Forms کار کرده باشید (اولین روش توسعه‌ی برنامه‌های وب در دنیای دات نت)، مدل برنامه نویسی Blazor Server، بسیار شبیه به کار با وب فرم‌ها است؛ البته بر اساس آخرین تغییرات دنیای دانت نت مانند برنامه نویسی async، کار با سرویس‌ها، تزریق وابستگی‌های توکار و غیره.


نمایش لیست اتاق‌های ثبت شده


تا اینجا موفق شدیم اطلاعات یک مدل اعتبارسنجی شده را در بانک اطلاعاتی ثبت کنیم. مرحله‌ی بعد، نمایش لیست اطلاعات ثبت شده‌ی در بانک اطلاعاتی است. بنابراین به کامپوننت HotelRoomList.razor مراجعه کرده و آن‌را به صورت زیر تکمیل می‌کنیم:
@page "/hotel-room"

@inject IHotelRoomService HotelRoomService

<div class="row mt-4">
    <div class="col-8">
        <h4 class="card-title text-info">Hotel Rooms</h4>
    </div>
    <div class="col-3 offset-1">
        <NavLink href="hotel-room/create" class="btn btn-info">Add New Room</NavLink>
    </div>
</div>

<div class="row mt-4">
    <div class="col-12">
        <table class="table table-bordered table-hover">
            <thead>
                <tr>
                    <th>Name</th>
                    <th>Occupancy</th>
                    <th>Rate</th>
                    <th>
                        Sqft
                    </th>
                    <th>

                    </th>
                </tr>
            </thead>
            <tbody>
                @if (HotelRooms.Any())
                {
                    foreach (var room in HotelRooms)
                    {
                        <tr>
                            <td>@room.Name</td>
                            <td>@room.Occupancy</td>
                            <td>@room.RegularRate.ToString("c")</td>
                            <td>@room.SqFt</td>
                            <td></td>
                        </tr>
                    }
                }
                else
                {
                    <tr>
                        <td colspan="5">No records found</td>
                    </tr>
                }
            </tbody>
        </table>
    </div>
</div>

@code
{
    private List<HotelRoomDTO> HotelRooms = new List<HotelRoomDTO>();

    protected override async Task OnInitializedAsync()
    {
        await foreach(var room in HotelRoomService.GetAllHotelRoomsAsync())
        {
            HotelRooms.Add(room);
        }
    }
}
توضیحات:
- متد GetAllHotelRoomsAsync، لیست اتاق‌های ثبت شده را بازگشت می‌دهد. البته خروجی آن از نوع <IAsyncEnumerable<HotelRoomDTO است که از زمان C# 8.0 ارائه شد و روش کار با آن اندکی متفاوت است. IAsyncEnumerable‌ها را باید توسط await foreach پردازش کرد.
- همانطور که در مطلب بررسی چرخه‌ی حیات کامپوننت‌ها نیز عنوان شد، متدهای رویدادگران OnInitialized و نمونه‌ی async آن برای دریافت اطلاعات از سرویس‌ها طراحی شده‌اند که در اینجا نمونه‌ای از آن‌را مشاهده می‌کنید.
- پس از تشکیل لیست اتاق‌ها، حلقه‌ی foreach (var room in HotelRooms) تعریف شده، ردیف‌های آن‌را در UI نمایش می‌دهد.


کدهای کامل این مطلب را از اینجا می‌توانید دریافت کنید: Blazor-5x-Part-14.zip
نظرات مطالب
EF Code First #12
در صورتی که بخواهیم Context را به ازای هر ویندوز فرم زنده نگه داریم در WinForm، پیاده سازی زیر صحیح است:
static class Program
    {
        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        [STAThread]
        static void Main()
        {
            Database.SetInitializer<LoanContext>(null);

            // تنظیمات اولیه برنامه که فقط یکبار باید در طول عمر برنامه انجام شود
            ObjectFactory.Initialize(x =>
            {
                x.For<IUnitOfWork>().Use<LoanContext>();
                x.For<IEmployeeService>().Use<EmployeeService>();
            });

            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new frmMain());
        }
    }
و در frmMain به شکل زیر پیاده سازی را انجام دهیم:
 public partial class frmMain : Form
    {
        private IContainer _container;
        private IUnitOfWork _uow;

        public frmMain()
        {
            InitializeComponent();

            _container = ObjectFactory.Container.GetNestedContainer();
            _uow = _container.GetInstance<IUnitOfWork>();
        }

        private void btnHomeLoanRequest_Click(object sender, System.EventArgs e)
        {
            var employeeService = _container.GetInstance<IEmployeeService>();
            .
            .
            .
            _uow.SaveChanges();
        }
        private void btnEditLoan_Click(object sender, System.EventArgs e)
        {
            var categoryService = container.GetInstance<ICategoryService>();
            .
            .
            .
            _uow.SaveChanges();
        }
مطالب
کار با اسکنر در برنامه های تحت وب (قسمت دوم و آخر)

در قسمت قبل « کار با اسکنر در برنامه‌های تحت وب (قسمت اول) » دیدی از کاری که قرار است انجام دهیم، رسیدیم. حالا سراغ یک پروژه‌ی عملی و پیاده سازی مطالب مطرح شده می‌رویم.

ابتدا پروژه‌ی   WCF را شروع می‌کنیم. ویژوال استودیو را باز کرده و از قسمت New Project > Visual C# > WCF یک پروژه‌ی WCF Service Application جدید را مثلا با نام "WcfServiceScanner" ایجاد نمایید. پس از ایجاد، دو فایل IService1.cs و Service1.scv موجود را به IScannerService و ScannerService تغییر نام دهید. سپس ابتدا محتویات کلاس اینترفیس IScannerService را به صورت زیر تعریف نمایید :

    [ServiceContract]
    public interface IScannerService
    {
        [OperationContract]
        [WebInvoke(Method = "GET",
           BodyStyle = WebMessageBodyStyle.Wrapped,
           RequestFormat = WebMessageFormat.Json,
           ResponseFormat = WebMessageFormat.Json,
           UriTemplate = "GetScan")]
        string GetScan();
    }
در اینجا ما فقط اعلان متدهای مورد نیاز خود را ایجاد کرده‌ایم. علت استفاده از Attribute ایی با نام WebInvoke ، مشخص نمودن نوع خروجی به صورت Json است و همچنین عنوان آدرس مناسبی برای صدا زدن متد. پس از آن کلاس ScannerService را مطابق کدهای زیر تغییر دهید:
    public class ScannerService : IScannerService
    {
        public string GetScan()
        {
            // TODO Add code here
        }
    }
تا اینجا فقط یک WCF Service معمولی ساخته‌ایم .در ادامه به سراغ کلاس WIA برای ارتباط با اسکنر می‌رویم.
بر روی پروژه‌ی خود راست کلیک کرده و Add Reference را انتخاب نموده و سپس در قسمت COM، گزینه‌ی Microsoft Windows Image Acquisition Library v2.0 را به پروژه‌ی خود اضافه نمایید.
با اضافه شدن این ارجاع به پروژه، دسترسی به فضای نام WIA برای ما امکان پذیر می‌شود که ارجاعی از آن را در کلاس ScannerService قرار می‌دهیم.
using WIA;
اکنون متد GetScan را مطابق زیر اصلاح می‌نماییم:
        public string GetScan()
        {
            var imgResult = String.Empty;
            var dialog = new CommonDialogClass();
            try
            {
                // نمایش فرم پیشفرض اسکنر
                var image = dialog.ShowAcquireImage(WiaDeviceType.ScannerDeviceType);
                
                // ذخیره تصویر در یک فایل موقت
                var filename = Path.GetTempFileName();
                image.SaveFile(filename);
                var img = Image.FromFile(filename);

                // img جهت ارسال سمت کاربر و نمایش در تگ Base64 تبدیل تصویر به 
                imgResult = ImageHelper.ImageToBase64(img, ImageFormat.Jpeg);
            }
            catch
            {
                // از آنجاییه که امکان نمایش خطا وجود ندارد در صورت بروز خطا رشته خالی 
                // بازگردانده می‌شود که به معنای نبود تصویر می‌باشد
            }

            return imgResult;
        }
دقت داشته باشید که کدها را در زمان توسعه بین Try..Catch قرار ندهید چون ممکن‌است در این زمان به خطاهایی برخورد کنید که نیاز باشد در مرورگر آنها را دیده و رفع خطا نمایید.
CommonDialogClass  کلاس اصلی در اینجا جهت نمایش فرم کار با اسکنر می‌باشد و متد‌های مختلفی را جهت ارتباط با اسکنر در اختیار ما قرار می‌دهد که بسته به نیاز خود می‌توانید از آنها استفاده کنید. برای نمونه در مثال ما نیز متد اصلی که مورد استفاده قرار گرفته، ShowAcquireImage می‌باشد که این متد، فرم پیش فرض دریافت اسکنر را به کاربر نمایش می‌دهد و کاربر از طریق آن می‌تواند قبل از شروع اسکن، یکسری تنظیمات را انجام دهد.
این متد ابتدا به صورت خودکار فرم تعیین دستگاه اسکنر ورودی را نمایش داده :


و سپس فرم پیش فرض اسکنر‌های TWAIN را جهت تعیین تنظیمات اسکن نمایش می‌دهد که این امکان نیز در این فرم فراهم است تا دستگاه‌های Feeder یا Flated انتخاب گردند.

خروجی این متد همان عکس اسکن شده است که از نوع WIA.ImageFile می‌باشد و ما پس از دریافتش، ابتدا آن را در یک فایل موقت ذخیره نموده و سپس با استفاده از یک متد کمکی آن را به فرمت Base64 برای درخواست کننده اسکن ارسال می‌نماییم.

کدهای کلاس کمکی ImageHelper:

        public static string ImageToBase64(Image image, System.Drawing.Imaging.ImageFormat format)
        {
            if (image != null)
            {
                using (MemoryStream ms = new MemoryStream())
                {
                    // Convert Image to byte[]
                    image.Save(ms, format);
                    byte[] imageBytes = ms.ToArray();

                    // Convert byte[] to Base64 String
                    string base64String = Convert.ToBase64String(imageBytes);
                    return base64String;
                }
            }
            return String.Empty;
        }
توجه داشته باشید که خروجی این متد قرار است توسط callBack یک متد جاوا اسکریپتی مورد استفاده قرار گرفته و احیانا عکس مورد نظر در صفحه نمایش داده شود. پس بهتر است که از قالب تصویر به شکل Base64 استفاده گردد. ضمن اینکه پلاگین‌های Jquery مرتبط با ویرایش تصویر هم از این قالب پشتیبانی می‌کنند. (اینجا )

این مثال به ساده‌ترین شکل نوشته شد. کلاس دیگری هم در اینجا وجود دارد و در صورتیکه از اسکنر نوع Feeder استفاده می‌کنید، می‌توانید از کدهای آن استفاده کنید.

کار ما تا اینجا در پروژه‌ی WCF Service تقریبا تمام است. اگر پروژه را یکبار Build نمایید برای اولین بار احتمالا پیغام خطاهای زیر ظاهر خواهند شد:


جهت رفع این خطا، در قسمت Reference‌های پروژه خود، WIA را انتخاب نموده و از Properties‌های آن خصوصیت Embed Interop Types را به False تغییر دهید؛ مشکل حل می‌شود.

به سراغ پروژه‌ی ویندوز فرم جهت هاست کردن این WCF سرویس می‌رویم. می‌توانید این سرویس را بر روی یک Console App یا Windows Service هم هاست کنید که در اینجا برای سادگی مثال، از WinForm استفاده می‌کنیم.
یک پروژه‌ی WinForm جدید را ایجاد کنید و سپس از قسمت Add Reference > Solution به مسیر پروژه‌ی قبلی رفته و dll‌‌های آن را به پروژه خود اضافه نمایید.
Form1.cs  را باز کرده و ابتدا دو متغیر زیر را در آن به صورت عمومی تعریف نمایید:
        private readonly Uri _baseAddress = new Uri("http://localhost:6019");
        private ServiceHost _host;
برای استفاده از کلاس ServiceHost لازم است تا ارجاعی به فضای نام System.ServiceModel داده شود. متغیر baseAddress_ نگه دارنده‌ی آدرس ثابت سرویس اسکنر در سمت کلاینت می‌باشد و به این ترتیب ما دقیقا می‌دانیم باید سرویس را با کدام آدرس در کدهای جاوا اسکریپتی خود فراخوانی نماییم.
حال در رویداد Form_Load برنامه، کدهای زیر را جهت هاست کردن سرویس اضافه می‌نماییم:
        private void Form1_Load(object sender, EventArgs e)
        {
            _host = new ServiceHost(typeof(WcfServiceScanner.ScannerService), _baseAddress);
            _host.Open();
        }

        private void Form1_FormClosing(object sender, FormClosingEventArgs e)
        {
            _host.Close();
        }
همین چند خط برای هاست کردن سرویس روی آدرس localhost و پورت 8010 کامپیوتر کلاینت کافی است. اما یکسری تنظیمات مربوط به خود سرویس هم وجود دارد که باید در زمان پیاده سازی سرویس، در خود پروژه‌ی سرویس، ایجاد می‌گردید. اما از آنجا که ما قرار است سرویس را در یک پروژه‌ی دیگر هاست کنیم، بنابراین این تنظیمات را باید در همین پروژه‌ی WinForm قرار دهیم.
فایل App.Config پروژه‌ی WinForm را باز کرده و کدهای آنرا مطابق زیر تغییر دهید:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <startup> 
        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
    </startup>

  <system.serviceModel>
    <behaviors>
      <serviceBehaviors>
        <behavior name="BehaviourMetaData">
          <serviceMetadata httpGetEnabled="true" />
        </behavior>
      </serviceBehaviors>
    </behaviors>
    <services>
      <service name="WcfServiceScanner.ScannerService"
               behaviorConfiguration="BehaviourMetaData">
        <endpoint address=""
                  binding="basicHttpBinding"
                  contract="WcfServiceScanner.IScannerService" />
      </service>
    </services>
  </system.serviceModel>

</configuration>
اکنون پروژه‌ی هاست آماده اجرا می‌باشد. اگر آنرا اجرا کنید و در مرورگر خود آدرس ذکر شده را وارد کنید، صفحه‌ی زیر را مشاهده خواهد کرد که به معنی صحت اجرای سرویس اسکنر می‌باشد.

اگر موفق به اجرا نشدید و احیانا با خطای زیر مواجه شدید، اطمینان حاصل کنید که ویژوال استودیو Run as Administrator باشد. مشکل حل خواهد شد.


به سراغ پروژه‌ی بعدی، یعنی وب سایت خود می‌رویم. یک پروژه‌ی MVC جدید ایجاد نمایید و در View مورد نظر خود، کدهای زیر را جهت صدا زدن متد GetScan اضافه می‌کنیم.
( از آنجا که کدها به صورت جاوا اسکریپت می‌باشد، پس مهم نیست که حتما پروژه MVC باشد؛ یک صفحه‌ی HTML ساده هم کافی است).
<a href="#" id="get-scan">Get Scan</a>
<img src="" id="img-scanned" />
<script>
    $("#get-scan").click(function () {
        var url = 'http://localhost:6019/';
        $.get(url, function (data) {
            $("#img-scanned").attr("src","data:image/Jpeg;base64,  "+ data.GetScanResult);
        });
    });
</script>
دقت کنید در هنگام دریافت اطلاعات از سرویس، نتیجه به شکل GetScanResult خواهد بود. الان اگر پروژه را اجرا نمایید و روی لینک کلیک کنید، اسکنر شروع به دریافت اسکن خواهد کرد اما نتیجه‌ای بازگشت داده نخواهد شد و علت هم مشکل امنیتی CORS می‌باشد که به دلیل دریافت اطلاعات از یک دامین دیگر رخ می‌دهد و اگر با Firebug درخواست را بررسی کنید متوجه خطا به شکل زیر خواهید شد.


راه حل‌های زیادی برای این مشکل ارائه شده است، و متاسفانه بسیاری از آنها در شرایط پروژه‌ی ما جوابگو نمی‌باشد (به دلیل هاست روی یک پروژه ویندوزی). تنها راه حل مطمئن (تست شده) استفاده از یک کلاس سفارشی در پروژه‌ی WCF Service  می‌باشد که مثال آن در اینجا آورده شده است.
برای رفع مشکل به پروژه WcfServiceScanner بازگشته و کلاس جدیدی را به نام CORSSupport ایجاد کرده و کدهای زیر را به آن اضافه کنید:
    public class CORSSupport : IDispatchMessageInspector
    {
        Dictionary<string, string> requiredHeaders;
        public CORSSupport(Dictionary<string, string> headers)
        {
            requiredHeaders = headers ?? new Dictionary<string, string>();
        }

        public object AfterReceiveRequest(ref System.ServiceModel.Channels.Message request, System.ServiceModel.IClientChannel channel, System.ServiceModel.InstanceContext instanceContext)
        {
            var httpRequest = request.Properties["httpRequest"] as HttpRequestMessageProperty;
            if (httpRequest.Method.ToLower() == "options")
                instanceContext.Abort();
            return httpRequest;
        }

        public void BeforeSendReply(ref System.ServiceModel.Channels.Message reply, object correlationState)
        {
            var httpResponse = reply.Properties["httpResponse"] as HttpResponseMessageProperty;
            var httpRequest = correlationState as HttpRequestMessageProperty;

            foreach (var item in requiredHeaders)
            {
                httpResponse.Headers.Add(item.Key, item.Value);
            }
            var origin = httpRequest.Headers["origin"];
            if (origin != null)
                httpResponse.Headers.Add("Access-Control-Allow-Origin", origin);

            var method = httpRequest.Method;
            if (method.ToLower() == "options")
                httpResponse.StatusCode = System.Net.HttpStatusCode.NoContent;

        }
    }

    // Simply apply this attribute to a DataService-derived class to get
    // CORS support in that service
    [AttributeUsage(AttributeTargets.Class)]
    public class CORSSupportBehaviorAttribute : Attribute, IServiceBehavior
    {
        #region IServiceBehavior Members

        void IServiceBehavior.AddBindingParameters(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase, System.Collections.ObjectModel.Collection<ServiceEndpoint> endpoints, BindingParameterCollection bindingParameters)
        {
        }

        void IServiceBehavior.ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
        {
            var requiredHeaders = new Dictionary<string, string>();

            //Chrome doesn't accept wildcards when authorization flag is true
            //requiredHeaders.Add("Access-Control-Allow-Origin", "*");
            requiredHeaders.Add("Access-Control-Request-Method", "POST,GET,PUT,DELETE,OPTIONS");
            requiredHeaders.Add("Access-Control-Allow-Headers", "Accept, Origin, Authorization, X-Requested-With,Content-Type");
            requiredHeaders.Add("Access-Control-Allow-Credentials", "true");
            foreach (ChannelDispatcher cd in serviceHostBase.ChannelDispatchers)
            {
                foreach (EndpointDispatcher ed in cd.Endpoints)
                {
                    ed.DispatchRuntime.MessageInspectors.Add(new CORSSupport(requiredHeaders));
                }
            }
        }

        void IServiceBehavior.Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
        {
        }

        #endregion
    }
فضاهای نام لازم برای این کلاس به شرح زیر می‌باشد:
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Description;
using System.ServiceModel.Dispatcher;
کلاس ScannerService را باز کرده و آنرا به ویژگی
    [CORSSupportBehavior]
    public class ScannerService : IScannerService
    {
مزین نمایید.

کار تمام است، یکبار دیگر ابتدا پروژه‌ی WcfServiecScanner و سپس پروژه هاست را Build کرده و برنامه‌ی هاست را اجرا کنید. اکنون مشاهده می‌کنید که با زدن دکمه‌ی اسکن، اسکنر فرم تنظیمات اسکن را نمایش می‌دهد که پس از زدن دکمه‌ی Scan، پروسه آغاز شده و پس از اتمام، تصویر اسکن شده در صفحه‌ی وب سایت نمایش داده می‌شود.
نظرات مطالب
شروع به کار با EF Core 1.0 - قسمت 7 - بررسی رابطه‌ی One-to-Many
در جهت تکمیل بحث بارگذاری اطلاعات وابسته: اضافه شدن Lazy Loading به نگارش 2.1

برخلاف نگارش‌های پیشین EF، اینبار Lazy loading به صورت پیش‌فرض فعال نیست که در بسیاری از موارد یک مزیت مهم، در جهت بهبود کارآیی برنامه به حساب می‌آید؛ چون پیشتر مدام می‌بایستی توسط ابزارهای profiler، برنامه را بررسی می‌کردیم تا از وجود مشکلی به نام select n+1 مطلع می‌شدیم (lazy loading اشتباه، در جائی که نیازی به آن نبوده و رفت و برگشت بیش از اندازه‌ا‌ی را به بانک اطلاعاتی سبب شده‌است).
برای فعالسازی lazy loading در EF Core 2.1 (اگر واقعا به آن نیاز دارید البته) دو روش وجود دارد:
الف) فعالسازی Lazy loading توسط Proxyها
در این حالت ابتدا نیاز است بسته‌ی نیوگت Microsoft.EntityFrameworkCore.Proxies را نصب کنید. سپس در متد OnConfiguring مربوط به Context برنامه، متد UseLazyLoadingProxies را فراخوانی نمائید:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    => optionsBuilder
        .UseLazyLoadingProxies()
        .UseSqlServer(myConnectionString);
و یا اینکار در فایل آغازین برنامه نیز میسر است:
    .AddDbContext<BloggingContext>(
        b => b.UseLazyLoadingProxies()
              .UseSqlServer(myConnectionString));
اکنون EF Core 2.1 خواص راهبری (navigation properties) را که قابل بازنویسی باشند (همان مباحث AOP و تشکیل پروکسی‌ها)، lazy load می‌کند.
این خواص نیز حتما باید به صورت virtual معرفی شوند تا قابلیت بازنویسی را داشته باشند؛ مانند:
public class Blog
{
    public int Id { get; set; }
    public string Name { get; set; }
    public virtual ICollection<Post> Posts { get; set; }
}
public class Post
{
    public int Id { get; set; }
    public string Title { get; set; }
    public string Content { get; set; }
    public virtual Blog Blog { get; set; }
}
در این مثال با فعال بودن lazy loading، به محض لمس خاصیت Blog، اطلاعات مرتبط با آن از بانک اطلاعاتی واکشی خواهند شد و نه پیش از آن مانند eager loading که تمام اطلاعات وابسته‌ی به یک موجودیت را نیز واکشی می‌کند.
هرچند این قابلیت بارگذاری اطلاعات وابسته در آینده، جذاب به نظر می‌رسد اما در عمل در حین رندر یک گرید و یا بکارگیری حلقه‌ها، چون سبب رفت و برگشت بیش از اندازه‌ای به بانک اطلاعاتی خواهد شد، باید با دقت مورد استفاده قرار گیرد و اساسا استفاده‌ی از آن در برنامه‌های وب توصیه نمی‌شود (با بررسی‌های پروژه‌های بسیاری مشخص شده‌است که این قابلیت ضررش بیشتر از نفعش است).


ب) فعالسازی Lazy loading بدون استفاده از Proxyها
در این حالت نیازی به نصب بسته‌ی AOP جدید تشکیل پروکسی‌ها نیست. در اینجا در کلاس موجودیت خود باید سرویس ILazyLoader را تزریق کنید:
public class Blog
{
    private ICollection<Post> _posts;
public Blog()
    {
    }
private Blog(ILazyLoader lazyLoader)
    {
        LazyLoader = lazyLoader;
    }
private ILazyLoader LazyLoader { get; set; }
public int Id { get; set; }
    public string Name { get; set; }
public ICollection<Post> Posts
    {
        get => LazyLoader?.Load(this, ref _posts);
        set => _posts = value;
    }
}
public class Post
{
    private Blog _blog;
public Post()
    {
    }
private Post(ILazyLoader lazyLoader)
    {
        LazyLoader = lazyLoader;
    }
private ILazyLoader LazyLoader { get; set; }
public int Id { get; set; }
    public string Title { get; set; }
    public string Content { get; set; }
public Blog Blog
    {
        get => LazyLoader?.Load(this, ref _blog);
        set => _blog = value;
    }
}
در این روش نیازی به virtual معرفی کردن خواص راهبری نیست. اما در این حالت به علت استفاده‌ی از سرویس ILazyLoader، نیاز خواهید داشت تا بسته‌ی نیوگت Microsoft.EntityFrameworkCore.Abstractions را نیز نصب کنید.
نظرات مطالب
مقاومت اتصال و اتصالات بهبودپذیر در Entity framework 6
نکته: این روش محدودیت هایی هم دارد:
using (var db = new BloggingContext()) 
{ 
    using (var trn = db.Database.BeginTransaction()) 
    { 
        db.Blogs.Add(new Site { Url = "http://msdn.com/data/ef" }); 
        db.Blogs.Add(new Site { Url = "http://blogs.msdn.com/adonet" }); 
        db.SaveChanges(); 
 
        db.Blogs.Add(new Site { Url = "http://twitter.com/efmagicunicorns" }); 
        db.SaveChanges(); 
 
        trn.Commit(); 
    } 
}
امکان اجرای تراکنش هایی به صورت بالا وجود ندارد.
مطالب
تاریخ شمسی با Extension Method برای DateTime
برای تبدیل تاریخ میلادی به شمسی راه‌های زیادی وجود داره که خوب این هم یک نوع از اونها هست.
برای این کار با استفاده از Extension Method برای کلاس DateTime تاریخ رو تبدیل می‌کنیم.
برای فرمت‌های‌های نمایشی تاریخ هم می‌بایست به صورت متن به Method بدهید تا تاریخ رو به اون صورتی که می‌خواهید نمایش بدهد، برای نوشتن فرمت‌ها هم می‌توانید از این لینک استفاده کنید.
خوب برای این کار ابتدا یک کلاس با نام PersianDateExtensionMethods  بسازید و تمام کد زیر رو جایگزین کد‌های کلاس کنید:
using System;
using System.Globalization;
using System.Reflection;

namespace System 
{
    public static class PersianDateExtensionMethods
    {
        private static CultureInfo _Culture;
        public static CultureInfo GetPersianCulture()
        {
            if (_Culture == null)
            {
                _Culture = new CultureInfo("fa-IR");
                DateTimeFormatInfo formatInfo = _Culture.DateTimeFormat;
                formatInfo.AbbreviatedDayNames = new[] { "ی", "د", "س", "چ", "پ", "ج", "ش" };
                formatInfo.DayNames = new[] { "یکشنبه", "دوشنبه", "سه شنبه", "چهار شنبه", "پنجشنبه", "جمعه", "شنبه" };
                var monthNames = new[]
                {
                    "فروردین", "اردیبهشت", "خرداد", "تیر", "مرداد", "شهریور", "مهر", "آبان", "آذر", "دی", "بهمن",
                    "اسفند",
                    ""
                };
                formatInfo.AbbreviatedMonthNames =
                    formatInfo.MonthNames =
                    formatInfo.MonthGenitiveNames = formatInfo.AbbreviatedMonthGenitiveNames = monthNames;
                formatInfo.AMDesignator = "ق.ظ";
                formatInfo.PMDesignator = "ب.ظ";
                formatInfo.ShortDatePattern = "yyyy/MM/dd";
                formatInfo.LongDatePattern = "dddd, dd MMMM,yyyy";
                formatInfo.FirstDayOfWeek = DayOfWeek.Saturday;
                System.Globalization.Calendar cal = new PersianCalendar();

                FieldInfo fieldInfo = _Culture.GetType().GetField("calendar", BindingFlags.NonPublic | BindingFlags.Instance);
                if (fieldInfo != null)
                    fieldInfo.SetValue(_Culture, cal);

                FieldInfo info = formatInfo.GetType().GetField("calendar", BindingFlags.NonPublic | BindingFlags.Instance);
                if (info != null)
                    info.SetValue(formatInfo, cal);

                _Culture.NumberFormat.NumberDecimalSeparator = "/";
                _Culture.NumberFormat.DigitSubstitution = DigitShapes.NativeNational;
                _Culture.NumberFormat.NumberNegativePattern = 0;
            }
            return _Culture;
        }

        public static string ToPeString(this DateTime date,string format = "yyyy/MM/dd")
        {
            return date.ToString(format,GetPersianCulture());
        }
    }
}

در کد بالا ما بااستفاده از متد GetPersianCulture یک کالچر برای تاریخ شمسی و فرمت‌های اون می‌سازیم و در متد ToPeString از اون استفاده و به متد ToString از کلاس DateTime پاس می‌دهیم.
چند مثال هم برای استفاده از متد ToPeString :
var date1 = DateTime.Now.ToPeString("yyyy/MM/dd");
var date2 = DateTime.Now.ToPeString("dddd, dd MMMM,yyyy");

//Output:
//1391/12/13
//یکشنبه, 13 اسفند,1391

نکته : با استفاده از Culture ای که در کلاس بالا در متد GetPersianCulture ساخته می‌شود امکانش هست که خود کلاس DateTime رو به شمسی تبدیل کرد، برای این کار باید به صورت زیر عمل کرد:
Thread.CurrentThread.CurrentCulture = Thread.CurrentThread.CurrentUICulture =  PersianDateExtensionMethods.GetPersianCulture();

var d1 = DateTime.Now.ToString();
//Output : 1391/12/13 11:25:44 ب.ظ

یک نکته‌ی تکمیلی: کتابخانه‌ی «DNTPersianUtils.Core» به همراه نکات این مطلب است؛ مانند:
Thread.CurrentThread.CurrentCulture = PersianCulture.Instance;
نظرات مطالب
مدیریت سفارشی سطوح دسترسی کاربران در MVC

سلام؛ من هم مشکل ایشون دارم و رول‌ها کش نمیشه و هر بار متد GetRolesForUser اجرا میشود. اون هم نه یکبار بلکه به تعداد زیاد و بار زیادی به دیتابیس وارد می‌کنه. لینک شما هم پیغام زیر و میدهد:

An update is available for the .NET Framework 4.5 in Windows 7 SP1, Windows Server 2008 R2 SP1, Windows Server 2008 SP2, and Windows Vista SP2: January 2013

اما ویندوز من 8 هست و پیغام سایت برای ویندوزهای 2008و7وvista هستش. با گشتن هم دیدم بعضی‌ها خودشون متد را دستی کش کردن

        public override string[] GetRolesForUser(string username)
        {
            var cacheKey = string.Format("{0}:{1}", username, ApplicationName);
            var cache = HttpContext.Current.Cache;
            var roles = cache[cacheKey] as string[];
            if (null == roles)
            {              
                using (var db = new Uas3Context())
                {
                    var u = new EfStudentService(db);
                    roles=u.GetUserRoles(username);
                    cache.Insert(cacheKey, roles, null, Cache.NoAbsoluteExpiration, Cache.NoSlidingExpiration);                    
                }
            }
            return roles;
        }

اما این متد و من کش کردم باقی متدها چی؟ کل متدها را همین طوری کش کنم؟ این کد هم به نظرم ناقصه . چون با تغییر دیتابیس به روز نمیشه و صرفا درصورت وجود کش اطلاعات می‌خونه. اصلا چرا در دات نت 4.5 این مشکل هست و چرا بر روی ویندوز 8 این پیغام داده میشود؟

مطالب
Count یا Any

با وجود امکانات مهیای توسط LINQ ، یک سری از عادات متداول حین کار با گروهی از اشیاء باید کنار گذاشته شوند؛ برای مثال چگونگی بررسی این مطلب که آیا شیء IEnumerable ما حاوی عنصری هست یا خیر.
روش متداول انجام اینکار استفاده از متد Count است. چون این متد پیش از تدارک امکانات LINQ نیز وجود داشته، بنابراین اولین موردی که جهت بررسی آن به ذهن خطور می‌کند، استفاده از متد Count می‌باشد؛ برای مثال:
void Method(IEnumerable<Status> statuses)
{
if (statuses != null && statuses.Count() > 0)
// do something...
}
این روش بهینه نیست زیرا کار متد Count بررسی تک تک عناصر شیء IEnumerable و سپس بازگرداندن تعداد آن‌ها است. این مورد خصوصا در حالت‌های کار با بانک اطلاعاتی و تنظیمات lazy-loading آن و یا تعداد بالای عناصر یک لیست، بسیار هزینه‌بر خواهد شد.
ولی در اینجا هدف ما این است که آیا شیء 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...

مطالب
نحوه استفاده از افزونه Firebug برای دیباگ برنامه‌های ASP.NET مبتنی بر jQuery
هر از چندگاهی سؤال «این مثال jQuery رو نمی‌تونم اجرا یا باز سازی کنم» در این سایت یا سایت‌های مشابه تکرار می‌شوند. بنابراین بهتر است نحوه عیب یابی برنامه‌های ASP.NET مبتنی بر jQuery را یکبار با هم مرور کنیم. در اینجا، مثال تهیه یک Image Slider را که پیشتر در سایت مطرح شده است، به نحوی دیگر بررسی خواهیم کرد:
1) فراموش می‌کنیم تا اسکریپت اصلی jQuery را به درستی پیوست و مسیردهی کنیم.
2) مسیر Generic handler دیگری را ذکر می‌کنیم.
3) مسیرهای تصاویری را که Image slider باید نمایش دهد، کاملا بی‌ربط ذکر می‌کنیم.
4) خروجی JSON نامربوطی را بازگشت می‌دهیم.
5) یکبار هم یک استثنای عمدی دستی را در بین کدها قرار خواهیم داد.

و ... بعد سعی می‌کنیم با استفاده از Firebug عیوب فوق را یافته و اصلاح کنیم؛ تا به یک برنامه قابل اجرا برسیم.


معرفی برنامه‌ای که کار نمی‌کند!

یک برنامه ASP.NET Empty web application را آغاز کنید. سپس سه پوشه Scripts، Content و Images را به آن اضافه نمائید. در این پوشه‌ها، اسکریپت‌های نمایش دهنده تصاویر، Css آن و تصاویری که قرار است نمایش داده شوند، قرار می‌گیرند:


سپس یک فایل default.aspx و یک فایل OrbitHandler.ashx را نیز به پروژه با محتویات ذیل اضافه کنید: (در این دو فایل، 5 مورد مشکل ساز یاد شده لحاظ شده‌اند)
محتویات فایل OrbitHandler.ashx.cs مطابق کدهای ذیل است:
using System.Collections.Generic;
using System.IO;
using System.Web;
using System.Web.Script.Serialization;

namespace OrbitWebformsTest
{
    public class Picture
    {
        public string Title { set; get; }
        public string Path { set; get; }
    }

    public class OrbitHandler : IHttpHandler
    {
        IList<Picture> PicturesDataSource()
        {
            var results = new List<Picture>();
            var path = HttpContext.Current.Server.MapPath("~/Images");

            foreach (var item in Directory.GetFiles(path, "*.*"))
            {
                var name = Path.GetFileName(item);
                results.Add(new Picture
                {
                    Path = /*"Images/" + name*/ name,
                    Title = name
                });
            }

            return results;
        }

        public void ProcessRequest(HttpContext context)
        {
            var items = PicturesDataSource();
            var json = /*new JavaScriptSerializer().Serialize(items)*/ string.Empty;
            throw new InvalidDataException("همینطوری");
            context.Response.ContentType = "text/plain";
            context.Response.Write(json);
        }

        public bool IsReusable
        { 
            get { return false; } 
        }
    }
}
در اینجا جهت سهولت دموی برنامه (و همچنین امکان باز تولید آن توسط خوانندگان)، از بانک اطلاعاتی استفاده نشده و عمدا از یک لیست جنریک تشکیل شده در حافظه کمک گرفته شده است. تصاویر برنامه در پوشه Images واقع در ریشه سایت، قرار دارند. بنابراین توسط متد PicturesDataSource، فایل‌های این پوشه را یافته و مطابق ساختار کلاس Picture بازگشت می‌دهیم. نهایتا این اطلاعات به ظاهر قرار است با فرمت JSON بازگشت داده شوند تا بتوان نتیجه را توسط افزونه Orbit استفاده کرد.

همچنین کدهای صفحه ASPX ایی که قرار است (به ظاهر البته) از این Generic handler استفاده کند به نحو ذیل است:
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="default.aspx.cs" Inherits="OrbitWebformsTest._default" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title></title>
    <link href="Content/orbit-1.2.3.css" rel="stylesheet" type="text/css" />
    <script src="Script/jquery-1.5.1.min.js" type="text/javascript"></script>
    <script src="Scripts/jquery.orbit-1.2.3.min.js" type="text/javascript"></script>
</head>
<body>
    <form id="form1" runat="server">
    <div id="featured">
    </div>
    </form>
    <script type="text/javascript">
        $(function () {
            $.ajax({
                url: "Handler.ashx",
                contentType: "application/json; charset=utf-8",
                success: function (data) {
                    $.each(data, function (i, b) {
                        var str = '<img src="' + b.Path + '" alt="' + b.Title + '"/>';
                        $("#featured").append(str);
                    });
                    $('#featured').orbit();
                },
                dataType: "json"
            });
        });
    </script>
</body>
</html>
خوب! اگر پروژه را اجرا کنیم، کار نمی‌کند. یک مستطیل مشکی رنگ در کنار صفحه ظاهر شده و همین! حالا چکار باید کرد؟


مراحل عیب یابی برنامه‌ای که کار نمی‌کند!

ابتدا برنامه را در فایرفاکس باز کرده و سپس افزونه Firebug را با کلیک بر روی آیکن آن، بر روی سایت فعال می‌کنیم. سپس یکبار بر روی دکمه F5 کلیک کنید تا مجددا مراحل بارگذاری سایت تحت نظر افزونه Firebug فعال شده، طی شود.


اولین موردی که مشهود است، نمایش عدد 3، کنار آیکن فایرباگ می‌باشد. این عدد به معنای وجود خطاهای اسکریپتی در کدهای ما است.
برای مشاهده این خطاها، بر روی برگه Console آن کلیک کنید: 


بله. مشخص است که مسیر دهی فایل jquery-1.5.1.min.js صحیح نبوده و همین مساله سبب بروز خطاهای اسکریپتی گردیده است. برای اصلاح آن سطر زیر را در برنامه تغییر دهید:
 <script src="Scripts/jquery-1.5.1.min.js" type="text/javascript"></script>
پیشتر پوشه Script ذکر شده بود که باید تبدیل به Scripts شود.

مجددا دکمه F5 را فشرده و سایت را با تنظیمات جدید اجرا کنید. اینبار در برگه Console و یا در برگه شبکه فایرباگ، خطای یافت نشدن Generic handler نمایان می‌شوند:


برای رفع آن به فایل default.aspx مراجعه و بجای معرفی Handler.ashx، نام OrbitHandler.ashx را وارد کنید.
مجددا دکمه F5 را فشرده و سایت را با تنظیمات جدید اجرا کنید.


اگر به برگه کنسول دقت کنیم، بروز استثناء در کدها تشخیص داده شده و همچنین در برگه Response پاسخ دریافتی از سرور، جزئیات صفحه خطای بازگشتی از آن نیز قابل بررسی و مشاهده است.
اینبار به فایل OrbitHandler.ashx.cs مراجعه کرده و سطر throw new InvalidDataException را حذف می‌کنیم. در ادامه برنامه را کامپایل و مجددا اجرا خواهیم کرد.



با اجرای مجدد سایت، تبادل اطلاعات صحیحی با فایل OrbitHandler.ashx برقرار شده است، اما خروجی خاصی قابل مشاهده نیست. بنابراین بازهم سایت کار نمی‌کند.
برای رفع این مشکل، متد ProcessRequest را به نحو ذیل تغییر خواهیم داد:
        public void ProcessRequest(HttpContext context)
        {
            var items = PicturesDataSource();
            var json = new JavaScriptSerializer().Serialize(items);            
            context.Response.ContentType = "text/plain";
            context.Response.Write(json);
        }
برنامه را کامپایل کرده و اجرا می‌کنیم. برنامه اجرا می‌شود، اما باز هم کار نمی‌کند. مشکل از کجاست؟


بله. تمام تنظیمات به نظر درست هستند، اما در برگه شبکه فایرباگ تعدادی خطای 404 و یا «یافت نشد»، مشاهده می‌شوند. مشکل اینجا است که مسیرهای بازگشت داده شده توسط متد Directory.GetFiles، مسیرهای مطلقی هستند؛ مانند c:\path\images\01.jpg و جهت نمایش در یک وب سایت مناسب نمی‌باشند. برای تبدیل آن‌ها به مسیرهای نسبی، اینبار کدهای متد تهیه منبع داده را به نحو ذیل ویرایش می‌کنیم:
        IList<Picture> PicturesDataSource()
        {
            var results = new List<Picture>();
            var path = HttpContext.Current.Server.MapPath("~/Images");

            foreach (var item in Directory.GetFiles(path, "*.*"))
            {
                var name = Path.GetFileName(item);
                results.Add(new Picture
                {
                    Path = "Images/" + name,
                    Title = name
                });
            }

            return results;
        }
در این کدها فقط قسمت Path ویرایش شده است تا به مسیر پوشه Images واقع در ریشه سایت اشاره کند.
اینبار اگر برنامه را اجرا کنیم، بدون مشکل کار خواهد کرد.

بنابراین در اینجا مشاهده کردیم که اگر «برنامه‌ای مبتنی بر jQuery کار نمی‌کند»، چگونه باید قدم به قدم با استفاده از فایرباگ و امکانات آن، به خطاهایی که گزارش می‌دهد و یا مسیرهایی را که یافت نشد بیان می‌کند، دقت کرد تا بتوان برنامه را عیب یابی نمود.


سؤال مهم: اجرای کدهای jQuery Ajax فوق، چه تغییری را در صفحه سبب می‌شوند؟

اگر به برگه اسکریپت‌ها در کنسول فایرباگ مراجعه کنیم، امکان قرار دادن breakpoint بر روی سطرهای کدهای جاوا اسکریپتی نمایش داده شده نیز وجود دارد:


در اینجا همانند VS.NET می‌توان برنامه را در مرورگر اجرا کرده و تگ‌های تصویر پویای تولید شده را پیش از اضافه شدن به صفحه، مرحله به مرحله بررسی کرد. به این ترتیب بهتر می‌توان دریافت که آیا src بازگشت داده شده از سرور فرمت صحیحی دارد یا خیر و آیا به محل مناسبی اشاره می‌کند یا نه. همچنین در برگه HTML آن، عناصر پویای اضافه شده به صفحه نیز بهتر مشخص هستند: