مسیرراهها
باسلام و خسته نباشید.
دقیقا کجای Index باید قرارداده بشه؟ سورس پروژه را هم ارسال میکردید خیلی خوب میشد .
من مثال فوق را خط به خط اجرا کردم ولی partial view نمایش داده نمیشه. فکر کنم مکان قطعه کد Ajax را اشتباه جایگذاری کردم اگه ممکنه راهنمائی میکنید که قطعه:
$( function () { $.ajax({ //مشخص کردن اکشنی که باید فراخوانی شود url: '/Home/Details' , contentType: 'application/html; charset=utf-8' , type: 'GET' , //نوع نتیجه بازگشتی dataType: 'html' }) .success( function (result) { //زمانی که کدهای سمت سرور بدون خطا اجرا شده اند //این قسمت فراخوانی میشود و نتیجه اکشن درون متغیر //result //قرار میگیرد $( '#sectionContents' ).html(result); }) .error( function (xhr, status) { alert(xhr.responseText); }); });
ممنون.
در ادامه قصد داریم از سرویس زیر که در قسمت قبل تکمیل شد، در یک برنامهی Blazor Server استفاده کنیم:
تعریف کامپوننتهای ابتدایی نمایش لیست اتاقها و ثبت و ویرایش آنها
در ابتدا کامپوننتهای خالی نمایش لیست اتاقها و همچنین فرم خالی ثبت و ویرایش آنها را به همراه مسیریابیهای مرتبط، ایجاد میکنیم. به همین جهت ابتدا داخل پوشهی Pages، پوشهی جدید HotelRoom را ایجاد کرده و فایل جدید HotelRoomList.razor را با محتوای ابتدایی زیر، به آن اضافه میکنیم.
این کامپوننت در مسیر hotel-room/ قابل دسترسی خواهد بود. بر این اساس، به کامپوننت Shared\NavMenu.razor مراجعه کرده و مدخل منوی آنرا تعریف میکنیم:
تا اینجا صفحهی ابتدایی نمایش لیست اتاقها، به همراه یک دکمهی افزودن اتاق جدید نیز هست. به همین جهت فایل جدید Pages\HotelRoom\HotelRoomUpsert.razor را به همراه مسیریابی hotel-room/create/ برای تعریف کامپوننت ابتدایی ثبت و ویرایش اطلاعات اتاقها، اضافه میکنیم:
- واژهی Upsert در مورد فرمی بکاربرده میشود که هم برای ثبت اطلاعات و هم برای ویرایش اطلاعات از آن استفاده میشود.
- NavLink تعریف شدهی در کامپوننت نمایش لیست اتاقها، به مسیریابی کامپوننت فوق اشاره میکند.
ایجاد فرم ثبت یک اتاق جدید
برای ثبت یک اتاق جدید نیاز است به مدل UI آن که همان HotelRoomDTO تعریف شدهی در قسمت قبل است، دسترسی داشت. به همین جهت در پروژهی BlazorServer.App، ارجاعی را به پروژهی BlazorServer.Models.csproj اضافه میکنیم:
سپس جهت سراسری اعلام کردن فضای نام آن، یک سطر زیر را به انتهای فایل BlazorServer.App\_Imports.razor اضافه میکنیم:
اکنون میتوانیم کامپوننت Pages\HotelRoom\HotelRoomUpsert.razor را به صورت زیر تکمیل کنیم:
توضیحات:
- در برنامههای 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، برای دریافت اطلاعات متنی چندسطری استفاده شدهاست:
با این خروجی:
تعریف اعتبارسنجیهای فیلدهای یک فرم Blazor
در حین تعریف یک فرم، برای واکنش نشان دادن به دکمهی submit، میتوان رویداد OnSubmit را به کامپوننت EditForm اضافه کرد که سبب فراخوانی متدی در قسمت کدهای کامپوننت جاری خواهد شد؛ مانند فراخوانی متد HandleHotelRoomUpsert در مثال زیر:
هرچند HotelRoomDTO تعریف شده به همراه تعریف اعتبارسنجیهایی مانند Required است، اما اگر بر روی دکمهی submit کلیک کنیم، متد HandleHotelRoomUpsert فراخوانی میشود. یعنی روال رویدادگردان OnSubmit، صرفنظر از وضعیت اعتبارسنجی مدل فرم، همواره با submit فرم، اجرا میشود.
اگر این مورد، مدنظر نیست، میتوان بجای OnSubmit، از رویداد OnValidSubmit استفاده کرد. در این حالت اگر اعتبارسنجی مدل فرم با شکست مواجه شود، دیگر متد HandleHotelRoomUpsert فراخوانی نخواهد شد. همچنین در این حالت میتوان خطاهای اعتبارسنجی را نیز در فرم نمایش داد:
- در اینجا قسمتهای تغییر کرده را مشاهده میکنید که به همراه درج DataAnnotationsValidator و ValidationMessageها است.
- کامپوننت DataAnnotationsValidator، اعتبارسنجی مبتنی بر data annotations را مانند [Required]، در دامنهی دید یک EditForm فعال میکند.
- اگر خواستیم تمام خطاهای اعتبارسنجی را به صورت خلاصهای در بالای فرم نمایش دهیم، میتوان از کامپوننت ValidationSummary استفاده کرد.
- و یا اگر خواستیم خطاها را به صورت اختصاصیتری ذیل هر تکستباکس نمایش دهیم، میتوان از کامپوننت ValidationMessage کمک گرفت. خاصیت For آن از نوع <Expression<System.Func تعریف شدهاست که اجازهی تعریف strongly typed نام خاصیت در حال اعتبارسنجی را به صورتی که مشاهده میکنید، میسر میکند.
ثبت اولین اتاق هتل
در ادامه میخواهیم روال رویدادگردان HandleHotelRoomUpsert را مدیریت کنیم. به همین جهت نیاز به کار با سرویس IHotelRoomService ابتدای بحث خواهد بود. بنابراین در ابتدا به فایل BlazorServer.App\_Imports.razor مراجعه کرده و فضای نام سرویسهای برنامه را اضافه میکنیم:
اکنون امکان تزریق IHotelRoomService را که در قسمت قبل پیاده سازی و به سیستم تزریق وابستگیهای برنامه معرفی کردیم، پیدا میکنیم:
در اینجا در ابتدا، سرویس IHotelRoomService به کامپوننت جاری تزریق شده و سپس از متدهای IsRoomUniqueAsync و CreateHotelRoomAsync آن، جهت بررسی منحصربفرد بودن نام اتاق و ثبت نهایی اطلاعات مدل برنامه که به فرم جاری به صورت دو طرفهای متصل است، استفاده کردهایم. در نهایت پس از ثبت اطلاعات، کاربر به صفحهی نمایش لیست اتاقها، توسط سرویس توکار NavigationManager، هدایت میشود.
اگر پیشتر با ASP.NET Web Forms کار کرده باشید (اولین روش توسعهی برنامههای وب در دنیای دات نت)، مدل برنامه نویسی Blazor Server، بسیار شبیه به کار با وب فرمها است؛ البته بر اساس آخرین تغییرات دنیای دانت نت مانند برنامه نویسی async، کار با سرویسها، تزریق وابستگیهای توکار و غیره.
نمایش لیست اتاقهای ثبت شده
تا اینجا موفق شدیم اطلاعات یک مدل اعتبارسنجی شده را در بانک اطلاعاتی ثبت کنیم. مرحلهی بعد، نمایش لیست اطلاعات ثبت شدهی در بانک اطلاعاتی است. بنابراین به کامپوننت HotelRoomList.razor مراجعه کرده و آنرا به صورت زیر تکمیل میکنیم:
توضیحات:
- متد GetAllHotelRoomsAsync، لیست اتاقهای ثبت شده را بازگشت میدهد. البته خروجی آن از نوع <IAsyncEnumerable<HotelRoomDTO است که از زمان C# 8.0 ارائه شد و روش کار با آن اندکی متفاوت است. IAsyncEnumerableها را باید توسط await foreach پردازش کرد.
- همانطور که در مطلب بررسی چرخهی حیات کامپوننتها نیز عنوان شد، متدهای رویدادگران OnInitialized و نمونهی async آن برای دریافت اطلاعات از سرویسها طراحی شدهاند که در اینجا نمونهای از آنرا مشاهده میکنید.
- پس از تشکیل لیست اتاقها، حلقهی foreach (var room in HotelRooms) تعریف شده، ردیفهای آنرا در UI نمایش میدهد.
کدهای کامل این مطلب را از اینجا میتوانید دریافت کنید: Blazor-5x-Part-14.zip
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 { }
<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 { }
- NavLink تعریف شدهی در کامپوننت نمایش لیست اتاقها، به مسیریابی کامپوننت فوق اشاره میکند.
ایجاد فرم ثبت یک اتاق جدید
برای ثبت یک اتاق جدید نیاز است به مدل UI آن که همان HotelRoomDTO تعریف شدهی در قسمت قبل است، دسترسی داشت. به همین جهت در پروژهی BlazorServer.App، ارجاعی را به پروژهی BlazorServer.Models.csproj اضافه میکنیم:
<Project Sdk="Microsoft.NET.Sdk.Web"> <ItemGroup> <ProjectReference Include="..\BlazorServer.Models\BlazorServer.Models.csproj" /> </ItemGroup> </Project>
@using BlazorServer.Models
@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() { } }
اگر این مورد، مدنظر نیست، میتوان بجای 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، اعتبارسنجی مبتنی بر data annotations را مانند [Required]، در دامنهی دید یک EditForm فعال میکند.
- اگر خواستیم تمام خطاهای اعتبارسنجی را به صورت خلاصهای در بالای فرم نمایش دهیم، میتوان از کامپوننت ValidationSummary استفاده کرد.
- و یا اگر خواستیم خطاها را به صورت اختصاصیتری ذیل هر تکستباکس نمایش دهیم، میتوان از کامپوننت ValidationMessage کمک گرفت. خاصیت For آن از نوع <Expression<System.Func تعریف شدهاست که اجازهی تعریف strongly typed نام خاصیت در حال اعتبارسنجی را به صورتی که مشاهده میکنید، میسر میکند.
ثبت اولین اتاق هتل
در ادامه میخواهیم روال رویدادگردان HandleHotelRoomUpsert را مدیریت کنیم. به همین جهت نیاز به کار با سرویس IHotelRoomService ابتدای بحث خواهد بود. بنابراین در ابتدا به فایل BlazorServer.App\_Imports.razor مراجعه کرده و فضای نام سرویسهای برنامه را اضافه میکنیم:
@using BlazorServer.Services
@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"); } }
اگر پیشتر با 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
مطالب دورهها
معرفی Aspect oriented programming
AOP یا Aspect oriented programming چیست؟
AOP یکی از فناوریهای مرتبط با توسعه نرم افزار محسوب میشود که توسط آن میتوان اعمال مشترک و متداول موجود در برنامه را در یک یا چند ماژول مختلف قرار داد (که به آنها Aspects نیز گفته میشود) و سپس آنها را به مکانهای مختلفی در برنامه متصل ساخت. عموما Aspects، قابلیتهایی را که قسمت عمدهای از برنامه را تحت پوشش قرار میدهند، کپسوله میکنند. اصطلاحا به این نوع قابلیتهای مشترک، تکراری و پراکنده مورد نیاز در قسمتهای مختلف برنامه، Cross cutting concerns نیز گفته میشود؛ مانند اعمال ثبت وقایع سیستم، امنیت، مدیریت تراکنشها و امثال آن. با قرار دادن این نیازها در Aspects مجزا، میتوان برنامهای را تشکیل داد که از کدهای تکراری عاری است.
مثالی از کدهای تکراری پراکنده در برنامه
به برنامه ذیل و قسمتهای مختلف ثبت وقایع آن دقت کنید:
همانطور که ملاحظه میکنید، حجم بالایی از کدهای تکراری ثبت وقایع، تنها در قسمت کوچکی از برنامه تدارک دیده شدهاند. این مساله نقض اصل DRY یا Don't repeat yourself است. کاری که برای رفع این مشکل قرار است انجام دهیم، استفاده از AOP و کپسوله سازی اعمال تکراری و سپس اتصال آن به قسمتهای مختلف برنامه است.
معرفی Aspects و مزایای استفاده از آنها
همانطور که عنوان شد اولین گام در AOP، کپسوله سازی کدهای تکراری است که اصطلاحا یک Aspect را تشکیل میدهند. بنابراین هر Aspect صرفا یک محصور کننده قابلیتی خاص و تکراری در برنامه است. این Aspect باید اصل SRP یا Single responsibility principle (تک مسئولیتی) را رعایت کند. برای اتصال یک Aspect به قطعههای مختلف کدهای برنامه از الگوی طراحی تزئین کننده یا Decorator pattern استفاده میشود. به این ترتیب که این Aspect خاص قرار است قسمتی از کدهای برنامه را تزئین کند. همچنین در این حالت، open closed principle نیز بهتر رعایت خواهد گردید. از این جهت که کدهای تکراری برنامه، به Aspects منتقل شدهاند و دیگر نیازی نیست برای تغییر آنها، کدهای قسمتهای مختلف را تغییر داد (کدهای برنامه باز خواهند بود برای توسعه و بسته برای تغییر). بنابراین با استفاده از Aspects، به یک طراحی شیءگرای بهتر نیز دست خواهیم یافت.
مراحل اجرای یک Aspect
هر Aspect برای تزئین یا اتصال به قسمتهای مختلف برنامه، یک طول عمر کاری مشخص را طی میکند:
الف) مرحله OnStart
مرحله اول اجرای یک Aspect، در آغاز کار قطعهای است که قرار است آنرا مزین کند. بنابراین بلافاصله قبل از اجرای کدی، برای مثال در یک متد، قادر خواهیم بود تا قطعه کد موجود در Aspect ایی را فراخوانی و اجرا کنیم.
برای مثال در متد GetUserById، پیش از اینکه کار به مراجعه به بانک اطلاعاتی برسد، ابتدا وضعیت کش سیستم بررسی میشود. بنابراین در این مثال میتوان قسمت بررسی کش را به یک Aspect مجزا منتقل ساخته و در صورتیکه اطلاعاتی موجود بود، بازگشت داده شود؛ در غیر اینصورت مجوز اجرای ادامه کدها صادر گردد.
ب) مرحله OnSuccess
مرحله OnSuccess زمانی اجرا میشود که اجرای یک متد بدون بروز استثنایی خاتمه یافته است.
ج) مرحله OnExit
مرحله OnExit همانند مرحله OnSuccess است؛ با این تفاوت که مرحله OnSuccess در صورت بروز استثنایی در کدها اجرا نخواهد شد اما مرحله OnExit همواره در پایان کار یک متد فراخوانی میگردد.
د) مرحله OnError
مرحله OnError در طول عمر یک Aspect، در زمان بروز استثنایی رخ میدهد. برای مثال به این ترتیب میتوان قسمت ثبت وقایع بروز استثناهای سیستم را کلا به یک Aspect مشخص انتقال داده و حجم کدهای تکراری را به این ترتیب به شدت کاهش داد.
انواع مختلف AOP
تا اینجا شاید این سؤال برای شما پیش آمده باشد که خوب! جالب است! اما چطور میخواهید در مراحلی که یاد شد، دخالت کرده و قطعه کدی را تزریق کنید؟
در AOP دو روش متداول کلی برای انجام اعمال تزریق کد وجود دارند:
1) استفاده از Interceptors
به کمک Interceptors، فرآیند فراخوانی متدها و خواص یک کلاس، تحت کنترل و نظارت قرار خواهند گرفت. برای انجام این امر، عموما از IOC Containers استفاده میشود (Inversion of control). احتمالا تا کنون از این کتابخانهها تنها برای تزریق وابستگیهای برنامه خود کمک گرفتهاید و از سایر توانمندیهای آنها آنچنان استفادهای نکردهاید. در این حالت، زمانیکه یک IOC Container کار وهله سازی کلاس خاصی را انجام میدهد، در همین حین میتواند مراحل یاد شده شروع، پایان و خطای متدها یا فراخوانیهای خواص را نیز تحت نظر قرار داده و به این ترتیب مصرف کننده امکان تزریق کدهایی را در این مکانها خواهد یافت.
مزیت مهم استفاده از Interceptors، عدم نیاز به کامپایل و یا تغییر ثانویه اسمبلیهای موجود برای تغییری در کدهای آنها است (برای تزریق نواحی تحت کنترل قرار دادن اعمال) و تمام کارها به صورت خودکار در زمان اجرای برنامه مدیریت میگردند.
2) بهره گیری از فناوری IL Code Weaving
در فناوی IL Code Weaving، ابتدا برنامه و ماژولهای آن به نحو متداولی کامپایل و تبدیل به dll یا exe خواهند شد. سپس این dllها و فایلهای اجرایی به پردازشگر ثانویه یک فریم ورک AOP برای تغییر و تزریق کدها سپرده خواهند شد. برای مثال در این حالت، کدهای سطح پایین IL مرتبط با مراحل مختلف اجرای یک Aspect، تولید و به اسمبلیهای نهایی برنامه تزریق میشوند. اکنون به dll یا فایل اجرایی جدیدی خواهیم رسید که علاوه بر کدهای اصلی برنامه، حاوی کدهای تزریق شده تمام Aspects تعریف شده نیز هستند.
AOP یکی از فناوریهای مرتبط با توسعه نرم افزار محسوب میشود که توسط آن میتوان اعمال مشترک و متداول موجود در برنامه را در یک یا چند ماژول مختلف قرار داد (که به آنها Aspects نیز گفته میشود) و سپس آنها را به مکانهای مختلفی در برنامه متصل ساخت. عموما Aspects، قابلیتهایی را که قسمت عمدهای از برنامه را تحت پوشش قرار میدهند، کپسوله میکنند. اصطلاحا به این نوع قابلیتهای مشترک، تکراری و پراکنده مورد نیاز در قسمتهای مختلف برنامه، Cross cutting concerns نیز گفته میشود؛ مانند اعمال ثبت وقایع سیستم، امنیت، مدیریت تراکنشها و امثال آن. با قرار دادن این نیازها در Aspects مجزا، میتوان برنامهای را تشکیل داد که از کدهای تکراری عاری است.
مثالی از کدهای تکراری پراکنده در برنامه
به برنامه ذیل و قسمتهای مختلف ثبت وقایع آن دقت کنید:
using System; namespace AOP00 { class Program { static void Main(string[] args) { Log.Debug("Program has started."); //..... try { } catch (Exception ex) { Log.Error(ex); throw; } finally { //..... Log.Debug("Program has ended."); } } } }
معرفی Aspects و مزایای استفاده از آنها
همانطور که عنوان شد اولین گام در AOP، کپسوله سازی کدهای تکراری است که اصطلاحا یک Aspect را تشکیل میدهند. بنابراین هر Aspect صرفا یک محصور کننده قابلیتی خاص و تکراری در برنامه است. این Aspect باید اصل SRP یا Single responsibility principle (تک مسئولیتی) را رعایت کند. برای اتصال یک Aspect به قطعههای مختلف کدهای برنامه از الگوی طراحی تزئین کننده یا Decorator pattern استفاده میشود. به این ترتیب که این Aspect خاص قرار است قسمتی از کدهای برنامه را تزئین کند. همچنین در این حالت، open closed principle نیز بهتر رعایت خواهد گردید. از این جهت که کدهای تکراری برنامه، به Aspects منتقل شدهاند و دیگر نیازی نیست برای تغییر آنها، کدهای قسمتهای مختلف را تغییر داد (کدهای برنامه باز خواهند بود برای توسعه و بسته برای تغییر). بنابراین با استفاده از Aspects، به یک طراحی شیءگرای بهتر نیز دست خواهیم یافت.
مراحل اجرای یک Aspect
هر Aspect برای تزئین یا اتصال به قسمتهای مختلف برنامه، یک طول عمر کاری مشخص را طی میکند:
الف) مرحله OnStart
public User GetUserById(int id) { if (Cache.ExistsFor(id)) { return Cache[id]; } else { var user = LoadFromDb(id); Cache.AddFor("User", id, user); return user; } }
برای مثال در متد GetUserById، پیش از اینکه کار به مراجعه به بانک اطلاعاتی برسد، ابتدا وضعیت کش سیستم بررسی میشود. بنابراین در این مثال میتوان قسمت بررسی کش را به یک Aspect مجزا منتقل ساخته و در صورتیکه اطلاعاتی موجود بود، بازگشت داده شود؛ در غیر اینصورت مجوز اجرای ادامه کدها صادر گردد.
ب) مرحله OnSuccess
مرحله OnSuccess زمانی اجرا میشود که اجرای یک متد بدون بروز استثنایی خاتمه یافته است.
ج) مرحله OnExit
مرحله OnExit همانند مرحله OnSuccess است؛ با این تفاوت که مرحله OnSuccess در صورت بروز استثنایی در کدها اجرا نخواهد شد اما مرحله OnExit همواره در پایان کار یک متد فراخوانی میگردد.
د) مرحله OnError
مرحله OnError در طول عمر یک Aspect، در زمان بروز استثنایی رخ میدهد. برای مثال به این ترتیب میتوان قسمت ثبت وقایع بروز استثناهای سیستم را کلا به یک Aspect مشخص انتقال داده و حجم کدهای تکراری را به این ترتیب به شدت کاهش داد.
انواع مختلف AOP
تا اینجا شاید این سؤال برای شما پیش آمده باشد که خوب! جالب است! اما چطور میخواهید در مراحلی که یاد شد، دخالت کرده و قطعه کدی را تزریق کنید؟
در AOP دو روش متداول کلی برای انجام اعمال تزریق کد وجود دارند:
1) استفاده از Interceptors
به کمک Interceptors، فرآیند فراخوانی متدها و خواص یک کلاس، تحت کنترل و نظارت قرار خواهند گرفت. برای انجام این امر، عموما از IOC Containers استفاده میشود (Inversion of control). احتمالا تا کنون از این کتابخانهها تنها برای تزریق وابستگیهای برنامه خود کمک گرفتهاید و از سایر توانمندیهای آنها آنچنان استفادهای نکردهاید. در این حالت، زمانیکه یک IOC Container کار وهله سازی کلاس خاصی را انجام میدهد، در همین حین میتواند مراحل یاد شده شروع، پایان و خطای متدها یا فراخوانیهای خواص را نیز تحت نظر قرار داده و به این ترتیب مصرف کننده امکان تزریق کدهایی را در این مکانها خواهد یافت.
مزیت مهم استفاده از Interceptors، عدم نیاز به کامپایل و یا تغییر ثانویه اسمبلیهای موجود برای تغییری در کدهای آنها است (برای تزریق نواحی تحت کنترل قرار دادن اعمال) و تمام کارها به صورت خودکار در زمان اجرای برنامه مدیریت میگردند.
2) بهره گیری از فناوری IL Code Weaving
در فناوی IL Code Weaving، ابتدا برنامه و ماژولهای آن به نحو متداولی کامپایل و تبدیل به dll یا exe خواهند شد. سپس این dllها و فایلهای اجرایی به پردازشگر ثانویه یک فریم ورک AOP برای تغییر و تزریق کدها سپرده خواهند شد. برای مثال در این حالت، کدهای سطح پایین IL مرتبط با مراحل مختلف اجرای یک Aspect، تولید و به اسمبلیهای نهایی برنامه تزریق میشوند. اکنون به dll یا فایل اجرایی جدیدی خواهیم رسید که علاوه بر کدهای اصلی برنامه، حاوی کدهای تزریق شده تمام Aspects تعریف شده نیز هستند.
تا قبل از ES 6 در جاوا اسکریپت از توابع جهت ایجاد کامپوننتهایی با قابلیت استفاده مجدد استفاده میشد. این امر برای برنامهنویسانی که با زبانهای OOP آشنایی دارند، شاید چندان خوشایند نباشد. در TypeScript نیز همانند ES 6 امکان استفاده از کلاسها مهیا است.
سازنده (Constructor)
همانطور که مشاهده میکنید یک سازنده شبیه به یک متد است؛ با این تفاوت که برای نام آن از کلمه کلیدی constructor استفاده میشود. در TypeScript برای یک کلاس تنها یک سازنده را میتوانیم داشته باشیم. البته در دیگر زبانهای برنامهنویسی امکان تعریف چندین سازنده را با پارامترهای مختلف برای یک کلاس میتوانید داشته باشید. برای رسیدن به این هدف در TypeScript میتوان از Optional Parameters استفاده کرد. برای ایجاد یک وهله از کلاس فوق میتوانیم به این صورت عمل کنیم:
در کد فوق با استفاده از کلمهی کلیدی new یک وهله از کلاس ReferenceItem را ایجاد کردهایم و در نهایت آن را به متغیری با نام encyclopedia انتساب دادهایم. یعنی در واقع با استفاده از new توانستهایم سازندهی کلاس را فراخوانی کرده و سپس وهلهایی از آن را به متغیر ذکر شده انتساب دهیم.
پراپرتی، متد
برای دسترسی به این پراپرتی میتوانیم از سینتکس نقطه (.) استفاده کنیم. روش دوم برای تعریف یک پراپرتی، ایجاد accessorهای سفارشی است. accessors در واقع توابع getter و setter هستند که به شما در نحوهی get و set کردن یک پراپرتی کمک خواهند کرد:
همانطور که مشاهده میکنید، accessorهایی را برای پراپرتی editor با استفاده از کلمات کلیدی get و set ایجاد کردهایم. این accessorها در واقع توابعی همنام هستند. تابع get همیشه فاقد پارامتر است. میتوانیم برای تابع get نوع برگشتی را نیز تعیین کنیم (به عنوان مثال در کد فوق نوع برگشتی string است). setter نیز باید تنها یک پارامتر از ورودی دریافت کند. همچنین نمیتوانیم برای آن نوع برگشتی را تعیین کنیم. درون بدنهی این accessorها میتوانیم هر نوع کنترلی را بر روی پراپرتی داشته باشیم. برای دسترسی این accessorها نیز باید از سینتکس نقطه (.) استفاده کنیم.
Parameter properties
با کمک Parameter properties میتوانیم به صورت خلاصهتری اینکار را انجام دهیم:
همانطور که مشاهده میکنید اینکار را با افزودن کلمهی کلیدی public به ابتدای پارامتر name انجام دادهایم. در اینحالت دیگر نیازی به تعریف یک پراپرتی اضافی درون کلاس نخواهیم داشت. کامپایلر TypeScript خودش یک پراپرتی را با همین نام ایجاد کرده و مقدار دریافتی از سازنده را برای آن ست خواهد کرد.
Access Modifiers
همانطور که مشاهده میکنید با استفاده از کلمهی کلیدی extends توانستهایم یک sub-class ایجاد کنیم. بنابراین وهلههای کلاس Journal علاوه بر پراپرتیهای خود (در اینجا contributors ) دارای پراپرتی title و همچنین متد printItem نیز هستند. نکتهایی که در اینجا وجود دارد این است که تمامی sub-classها یا کلاسهای مشتق شده باید درون سازندهی خود، تابع super را فراخوانی کنند؛ با اینکار سازندهی کلاس پایه فراخوانی خواهد شد.
با استفاده از super.printItem به کامپایلر TypeScript گفتهایم که تمامی کدهای درون متد printItem در کلاس پایه نیز اجرا شوند. اگر مایل بودید میتوانید از آن صرفنظر کنید.
همانطور که مشاهده میکنید درون یک کلاس abstract میتوانیم متدهای abstract را نیز داشته باشیم؛ یعنی تنها امضای متد را تعیین کرده و پیادهسازی آن را به کلاسهای مشتق شده واگذار کنیم.
در حالت کلی یک کلاس قالبی برای ایجاد اشیاء است. تمامی اشیاء ایجاد شده از این الگو دارای یکسری پراپرتی و متد میباشند. از پراپرتیها جهت تعریف وضعیتها و از متدها جهت تعریف رفتارها استفاده خواهد شد. همچنین مزیت اصلی یک کلاس، کپسولهسازی قابلیتهای یک موجودیت خاص است. همانند دیگر زبانهای شیءگرا، در TypeScript نیز یک کلاس میتواند ویژگیهای زیر را داشته باشد:
- سازنده (constructor)
- پراپرتی، متد
- Access Modifiers
- ارثبری
- کلاسهای Abstract
در ادامه هر کدام از موارد فوق را بررسی خواهیم کرد.
سازنده (Constructor)
از سازندهها جهت مقداردهی وهلههای یک کلاس استفاده میشود. در ادامه یک کلاس جدید را با استفاده از کلمهی کلیدی class ایجاد کردهایم. این کلاس دارای یک سازنده است:
class ReferenceItem { constructor(title: string, publisher?: string) { // perform initialization here } }
let encyclopedia = new ReferenceItem('WorldPedia', 'WorldPub');
پراپرتی، متد
همانند اینترفیسها، کلاسها نیز میتوانند پراپرتی و متد داشته باشند. با این تفاوت که در کلاسها جزئیات پیادهسازی نیز ذکر خواهد شد. در یک کلاس به دو روش متفاوت میتوانیم پراپرتی را تعریف کنیم. روش اول همانند تعریف یک متغیر است. به عنوان مثال در کلاس زیر یک پراپرتی با نام numberOfPages را از نوع عددی تعریف کردهایم:
class ReferenceItem { numberOfPages: number; }
class ReferenceItem { numberOfPages: number; get editor(): string { // custom getter logic goes here, should return a value } set editor(newEditor: string) { // custom setter logic goes here } }
متدها نیز توابعی هستند که درون یک کلاس تعریف میشوند. برای نمونه در کد زیر یک تابع با نام printChapterTitle را تعریف کردهایم که یک پارامتر را از ورودی دریافت کرده و هیچ مقداری را در خروجی بر نمیگرداند:
class ReferenceItem { numberOfPages: number; get editor(): string { // custom getter logic goes here, should return a value } set editor(newEditor: string) { // custom setter logic goes here } printChapterTitle(chapterNum: number): void { // print title here } }
در حالت عادی برای مقداردهی اولیهی پراپرتیها یک شیء میتوانیم یکسری پارامتر را برای سازنده کلاس تعریف کرده و درون سازنده، پراپرتیهای موردنیازمان را مقداردهی کنیم:
class Author { name: string; constructor(authorName: string) { name = authorName; } }
class Author { constructor(public name: string){} }
Static Properties
تاکنون دربارهی اعضای مربوط به هر وهله از کلاسها صحبت کردیم؛ یعنی اعضایی که در زمان وهلهسازی در دسترس خواهند بود. در واقع میتوانیم اعضای استاتیک را نیز برای کلاسها داشته باشیم. منظور از استاتیک این است که مقادیر یک عضوء استاتیک در وهلههای مختلف یک شیء، متفاوت نیست. بلکه یک مقدار آن برای تمامی وهلهها به اشتراک گذاشته خواهد شد:
class Library { constructor(public name: string) {} static description: string = 'A source of knowledge'; } let lib = new Library('New York Public Library'); console.log(lib.name); // available on instances of the class console.log(Library.description);
با استفاده از Access Modifier میتوانیم میدان دید یک پراپرتی و یا یک متد را برای مصرف کنندهی کلاس کنترل کنیم. TypeScript دارای سه Access Modifier است:
public: در حالت پیشفرض تمامی اعضای یک کلاس عمومی (public) هستند. در نتیجه لزومی به ذکر آن برای پراپرتیها و متدها نیست. یک حالت استثناء، استفاده از Parameter properties است. در این حالت باید کلمهی کلیدی public حتماً ذکر شود.
private: برای محدود کردن دسترسی اعضای یک کلاس میتوانید از کلمهی کلیدی private استفاده کنید. در اینحالت مصرف کنندهی کلاس به اعضای خصوصی (private) دسترسی نخواهد داشت.
protected: این modifier نیز شبیه به private عمل میکند، با این تفاوت که توسط subclassهای مربوط به کلاس تعریف شده در آن نیز قابل دسترس است.
Inheritance
منظور از Inheritance یا ارثبری، اشتراکگذاری تعاریف یک کلاس برای یک یا چند sub-class است. فرض کنید یک کلاس با نام ReferenceItem با یکسری اعضای تعریف شده درون آن داریم و میخواهیم دو کلاس مشتق شده را از این کلاس تهیه کنیم. در اینحالت کلاس ReferenceItem کلاس پایه (base class) و کلاسهای مشتق شده از آن sub-class نامیده میشوند. بنابراین وهلههای ایجاد شده از کلاسهای مشتق شده دارای پراپرتیهای کلاس پایه نیز خواهند بود. برای داشتن قابلیت ارثبری در TypeScript میتوانیم به اینصورت عمل کنیم:
class ReferenceItem { title: string; printItem(): void { // print something here } } class Journal extends ReferenceItem { constructor() { super(); } contributors: string[]; }
لازم به ذکر است که میتوان متدهای کلاس پایه را درون کلاسهای مشتق شده، override کرد. برای اینکار کافی است متد موردنظر در کلاس پایه را درون کلاس مشتق شده مجدداً تعریف کرده و منطق موردنظر را درون آن نوشت:
class Journal extends ReferenceItem { constructor() { super(); } printItem(): void { super.printItem(); console.log('message from Journal'); } contributors: string[]; }
Abstract Classes
کلاسهای Abstract یک نوع خاص از کلاسها هستند که نمیتوان آنها را وهلهسازی کرد. یعنی تنها برای تعریف کلاسهای پایه از آنها استفاده خواهد شد. این نوع کلاسها شبیه به اینترفیسها هستند؛ اما ممکن است دارای پیادهسازی نیز باشند. در ادامه یک نمونه از abstract class را مشاهده میکنید:
abstract class ReferenceItem { private _publisher: string; static departement: string = 'Research'; constructor(public title: string, protected year: number) { } printItem(): void { console.log('message from abstract class'); } get publisher(): string { return this._publisher.toUpperCase(); } set publisher(newPublisher: string) { this._publisher = newPublisher; } abstract printCitation(): void; } class Encyclopedia extends ReferenceItem { constructor(newTitle: string, newYear, public edition: number) { super(newTitle, newYear); } printCitation(): void { console.log('message'); } } let test = new Encyclopedia('WorldPerdia', 1900, 10); test.printItem();
در حالت استفادهی از MVC، قسمت رندر خود pager یک کد سمت سرور هست. جائیکه لینک به صفحات مختلف رندر میشود، هشتگ مربوط به افزونهی pathjs را هم خودتان اضافه کنید:
var path = "#/page/" + (page + 1) + "/" + $(options.pagerSortById).val() + "/" + $(options.pagerSortOrderId).val();
قسمت بعدی آن، پردازش این هشتگها است (زمانیکه به صورت مستقیم در مرورگر وارد شد) که نیاز به کد جاوا اسکریپتی زیر را دارد:
Path.map("#/page(/:page)(/:sortby)(/:order)").to(function () {
نظرات اشتراکها
FireFox 18 و مصرف بالای CPU
این مشکل بیشتر در سایتهایی که استفاده بالایی از جاوا اسکریپت دارند هست مثل ایمیل یاهو یا گوگل ریدر.
اشتراکها
دوره آموزشی جاوا اسکریپت
اشتراکها