مطالب
Postable

ارسال سورس کد برنامه‌ها در بلاگر داستان خودش را دارد که پیشتر در مورد آن بحث شد.
اما این‌کار (تبدیل کاراکترهای غیرمجاز به نمونه‌های مجاز یا به اصطلاح escape آن‌ها) پس از یک مدت تبدیل به دردسر خواهد شد. به همین جهت برنامه‌ی کوچک زیر را برای ساده‌تر کردن این وضع تهیه کرده‌ام، که از آدرس زیر قابل دریافت است:



دریافت برنامه (برای اجرا نیاز به دات نت فریم ورک 2 دارد)

این برنامه‌ی کمکی، انجام چند کار زیر را در بلاگر برای شما ساده‌تر خواهد کرد:
الف) escape خودکار کاراکترهای غیرمجاز xml هنگام ارسال سورس کدهای خود و همچنین قرار دادن آن‌ها داخل تگ‌های div و pre مناسب.

روش برنامه نویسی آن:

public static string EscapeXml(string s)
{
var xml = s;
if (!string.IsNullOrEmpty(xml))
{
// replace literal values with entities
xml = xml.Replace("&", "&");
xml = xml.Replace("<", "&lt;");
xml = xml.Replace(">", "&gt;");
xml = xml.Replace("\"", "&quot;");
xml = xml.Replace("'", "&apos;");
}
return xml;
}
ب) حذف خطوط بین تگ‌های html . این مورد هنگام ارسال یک table استاندارد html در بلاگر لازم است. برای مثال در بلاگر ارسال کد زیر
<table>
<tr>
<td>data
</td>
</tr>
سبب ایجاد فواصل عجیبی در حین نمایش ردیف‌های جدول در سایت خواهد شد، زیرا بلاگر به ازای هر خط جدید یک br را به صورت خودکار نمایش خواهد داد. برای رفع این مشکل از دکمه remove html new lines استفاده کنید. به این صورت اطلاعات فوق به صورت خودکار به شکل زیر تبدیل می‌شوند:

<table> <tr> <td>data</td> </tr>

روش برنامه نویسی آن :

private static readonly Regex REGEX_BETWEEN_TAGS = new Regex(@">\s+<", RegexOptions.Compiled);
private static readonly Regex REGEX_LINE_BREAKS = new Regex(@"\n\s+", RegexOptions.Compiled);
public static string RemoveSpaces(string html)
{
html = REGEX_BETWEEN_TAGS.Replace(html, "> <");
return REGEX_LINE_BREAKS.Replace(html, string.Empty);

}

ج) حذف کاراکتر 0xA0 . البته این مورد ارتباطی به بلاگر پیدا نمی‌کند ولی اگر با CPP کار کرده باشید، حتما به مورد کپی سورس از اینترنت به داخل ادیتور و عدم کامپایل آن، برخورده‌اید. در سورس کدهای CPP مجاز به استفاده از کاراکتر No-Break Space نیستید (0xA0) و باید حذف شود. حال فرض کنید با بیش از 200 سطر سر و کار دارید. بنابراین نیاز به یک تمیز کننده سریع وجود خواهد داشت. (این مورد در ادیتور برنامه management studio اس کیوال سرور هم صادق است)

txtMod.Text = txtOrig.Text.Replace((char)160, ' ');
ساده است ولی انجام دستی آن مشکل خواهد بود.

نظرات مطالب
صفحه بندی و مرتب سازی خودکار اطلاعات به کمک jqGrid در ASP.NET MVC
سلام و ممنون از راهنمایی‌های خوبتان
کتابخانه اصلی Lib.AspNetCore.Mvc.JqGrid مربوط به پروژه Demo.AspNetCore.JqGrid رو با نسخه ASP.NET Core 3.1 به روز رسانی کردم و بعضی خطاهای Type Load Exception هم که در سورس فریمورک اصلی بود برطرف کردم. چون کدهای سورس اصلی فریمورک پروژه رو مجبور شدم تغییر بدم دیگه نشد که به صورت nuget Package به پروژه اضافه کنم برای همین سورس فریمورک اصلی رو در یک پوشه در کنار پروژه دمو که همه را به نسخه ASP.NET Core 3.1 به روز رسانی کردم گذاشتم. البته این نسخه با همان شیوه ای هست که در این لینک توضیح داده بودم و هم میشه با انواع بانکهای اطلاعاتی NoSQL استفاده کرد و هم با بانک اطلاعاتی‌های رابطه ای از قبیل MS SQL Server.

ضمنا اون موارد خطا هم به خاطر همین exception داخلی Type Load Exception در فریمورک اصلی بود که پس از ارتقا به نسخه 3.1 برطرف شد.


دوستان از لینک زیر می‌توانید نسخه بروز رسانی شده با ASP.NET Core 3.1 را استفاده نمایید.
Demo.JqGrid.core3.1.rar  (6.25 MB) 




در لینک هم همون نسخه دمو به همراه سورسهای فریمورک اصلی رو که تغییر دادم برای نویسنده اصلی فریمورک Lib.AspNetCore.Mvc.JqGrid در کامنت بارگزاری کردم تا فریمورک اصلی رو هم بروز رسانی کنند تا بعدا بشه به صورت nuget package استفاده کرد. البته توی اون نسخه کتابخانه JqGrid.Core که در نمونه مثال اینجا آپلود کردم نیست به این دلیل که نمونه مثال خودشون رو داشته باشند و باعث پیچیدگی کد برای ارتقای package نشه


سورس پروژه رو با این تغییرات در گیتهاب هم منتشر کردم

مطالب
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
مطالب
آشنایی با NHibernate - قسمت هشتم

معرفی الگوی Repository

روش متداول کار با فناوری‌های مختلف دسترسی به داده‌ها عموما بدین شکل است:
الف) یافتن رشته اتصالی رمزنگاری شده به دیتابیس از یک فایل کانفیگ (در یک برنامه اصولی البته!)
ب) باز کردن یک اتصال به دیتابیس
ج) ایجاد اشیاء Command برای انجام عملیات مورد نظر
د) اجرا و فراخوانی اشیاء مراحل قبل
ه) بستن اتصال به دیتابیس و آزاد سازی اشیاء

اگر در برنامه‌های یک تازه کار به هر محلی از برنامه او دقت کنید این 5 مرحله را می‌توانید مشاهده کنید. همه جا! قسمت ثبت، قسمت جستجو، قسمت نمایش و ...
مشکلات این روش:
1- حجم کارهای تکراری انجام شده بالا است. اگر قسمتی از فناوری دسترسی به داده‌ها را به اشتباه درک کرده باشد، پس از مطالعه بیشتر و مشخص شدن نحوه‌ی رفع مشکل، قسمت عمده‌ای از برنامه را باید اصلاح کند (زیرا کدهای تکراری همه جای آن پراکنده‌اند).
2- برنامه نویس هر بار باید این مراحل را به درستی انجام دهد. اگر در یک برنامه بزرگ تنها قسمت آخر در یکی از مراحل کاری فراموش شود دیر یا زود برنامه تحت فشار کاری بالا از کار خواهد افتاد (و متاسفانه این مساله بسیار شایع است).
3- برنامه منحصرا برای یک نوع دیتابیس خاص تهیه خواهد شد و تغییر این رویه جهت استفاده از دیتابیسی دیگر (مثلا کوچ برنامه از اکسس به اس کیوال سرور)، نیازمند بازنویسی کل برنامه می‌باشد.
و ...

همین برنامه نویس پس از مدتی کار به این نتیجه می‌رسد که باید برای این‌کارهای متداول، یک لایه و کلاس دسترسی به داده‌ها را تشکیل دهد. اکنون هر قسمتی از برنامه برای کار با دیتابیس باید با این کلاس مرکزی که انجام کارهای متداول با دیتابیس را خلاصه می‌کند، کار کند. به این صورت کد نویسی یک نواختی با حذف کدهای تکراری از سطح برنامه و همچنین بدون فراموش شدن قسمت مهمی از مراحل کاری، حاصل می‌گردد. در اینجا اگر روزی قرار شد از یک دیتابیس دیگر استفاده شود فقط کافی است یک کلاس برنامه تغییر کند و نیازی به بازنویسی کل برنامه نخواهد بود.

این روزها تشکیل این لایه دسترسی به داده‌ها (data access layer یا DAL) نیز مزموم است! و دلایل آن در مباحث چرا به یک ORM نیازمندیم برشمرده شده است. جهت کار با ORM ها نیز نیازمند یک لایه دیگر می‌باشیم تا یک سری اعمال متداول با آن‌هارا کپسوله کرده و از حجم کارهای تکراری خود بکاهیم. برای این منظور قبل از اینکه دست به اختراع بزنیم، بهتر است به الگوهای طراحی برنامه نویسی شیء گرا رجوع کرد و از رهنمودهای آن استفاده نمود.

الگوی Repository یکی از الگوهای برنامه‌ نویسی با مقیاس سازمانی است. با کمک این الگو لایه‌ای بر روی لایه نگاشت اشیاء برنامه به دیتابیس تشکیل شده و عملا برنامه را مستقل از نوع ORM مورد استفاه می‌کند. به این صورت هم از تشکیل یک سری کدهای تکراری در سطح برنامه جلوگیری شده و هم از وابستگی بین مدل برنامه و لایه دسترسی به داده‌ها (که در اینجا همان NHibernate می‌باشد) جلوگیری می‌شود. الگوی Repository (مخزن)، کار ثبت،‌ حذف، جستجو و به روز رسانی داده‌ها را با ترجمه آن‌ها به روش‌های بومی مورد استفاده توسط ORM‌ مورد نظر، کپسوله می‌کند. به این شکل شما می‌توانید یک الگوی مخزن عمومی را برای کارهای خود تهیه کرده و به سادگی از یک ORM به ORM دیگر کوچ کنید؛ زیرا کدهای برنامه شما به هیچ ORM خاصی گره نخورده و این عملیات بومی کار با ORM توسط لایه‌ای که توسط الگوی مخزن تشکیل شده، صورت گرفته است.

طراحی کلاس مخزن باید شرایط زیر را برآورده سازد:
الف) باید یک طراحی عمومی داشته باشد و بتواند در پروژه‌های متعددی مورد استفاده مجدد قرار گیرد.
ب) باید با سیستمی از نوع اول طراحی و کد نویسی و بعد کار با دیتابیس، سازگاری داشته باشد.
ج) باید امکان انجام آزمایشات واحد را سهولت بخشد.
د) باید وابستگی کلاس‌های دومین برنامه را به زیر ساخت ORM مورد استفاده قطع کند (اگر سال بعد به این نتیجه رسیدید که ORM ایی به نام XYZ برای کار شما بهتر است، فقط پیاده سازی این کلاس باید تغییر کند و نه کل برنامه).
ه) باید استفاده از کوئری‌هایی از نوع strongly typed را ترویج کند (مثل کوئری‌هایی از نوع LINQ).


بررسی مدل برنامه

مدل این قسمت (برنامه NHSample4 از نوع کنسول با همان ارجاعات متداول ذکر شده در قسمت‌های قبل)، از نوع many-to-many می‌باشد. در اینجا یک واحد درسی توسط چندین دانشجو می‌تواند اخذ شود یا یک دانشجو می‌تواند چندین واحد درسی را اخذ نماید که برای نمونه کلاس دیاگرام و کلاس‌های متشکل آن به شکل زیر خواهند بود:



using System.Collections.Generic;

namespace NHSample4.Domain
{
public class Course
{
public virtual int Id { get; set; }
public virtual string Teacher { get; set; }
public virtual IList<Student> Students { get; set; }

public Course()
{
Students = new List<Student>();
}
}
}


using System.Collections.Generic;

namespace NHSample4.Domain
{
public class Student
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual IList<Course> Courses { get; set; }

public Student()
{
Courses = new List<Course>();
}
}
}

کلاس کانفیگ برنامه جهت ایجاد نگاشت‌ها و سپس ساخت دیتابیس متناظر

using FluentNHibernate.Automapping;
using FluentNHibernate.Cfg;
using FluentNHibernate.Cfg.Db;
using NHibernate.Tool.hbm2ddl;

namespace NHSessionManager
{
public class Config
{
public static FluentConfiguration GetConfig()
{
return
Fluently.Configure()
.Database(
MsSqlConfiguration
.MsSql2008
.ConnectionString(x => x.FromConnectionStringWithKey("DbConnectionString"))
)
.Mappings(
m => m.AutoMappings.Add(
new AutoPersistenceModel()
.Where(x => x.Namespace.EndsWith("Domain"))
.AddEntityAssembly(typeof(NHSample4.Domain.Course).Assembly))
.ExportTo(System.Environment.CurrentDirectory)
);
}

public static void CreateDb()
{
bool script = false;//آیا خروجی در کنسول هم نمایش داده شود
bool export = true;//آیا بر روی دیتابیس هم اجرا شود
bool dropTables = false;//آیا جداول موجود دراپ شوند
new SchemaExport(GetConfig().BuildConfiguration()).Execute(script, export, dropTables);
}
}
}
چند نکته در مورد این کلاس:
الف) با توجه به اینکه برنامه از نوع ویندوزی است، برای مدیریت صحیح کانکشن استرینگ، فایل App.Config را به برنامه افروده و محتویات آن‌را به شکل زیر تنظیم می‌کنیم (تا کلید DbConnectionString توسط متد GetConfig مورد استفاده قرارگیرد ):

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<connectionStrings>
<!--NHSessionManager-->
<add name="DbConnectionString"
connectionString="Data Source=(local);Initial Catalog=HelloNHibernate;Integrated Security = true"/>
</connectionStrings>
</configuration>

ب) در NHibernate سنتی (!) کار ساخت نگاشت‌ها توسط یک سری فایل xml صورت می‌گیرد که با معرفی فریم ورک Fluent NHibernate و استفاده از قابلیت‌های Auto Mapping آن، این‌کار با سهولت و دقت هر چه تمام‌تر قابل انجام است که توضیحات نحوه‌ی انجام ‌آن‌را در قسمت‌های قبل مطالعه فرمودید. اگر نیاز بود تا این فایل‌های XML نیز جهت بررسی شخصی ایجاد شوند، تنها کافی است از متد ExportTo آن همانگونه که در متد GetConfig استفاده شده، کمک گرفته شود. به این صورت پس از ایجاد خودکار نگاشت‌ها، فایل‌های XML متناظر نیز در مسیری که به عنوان آرگومان متد ExportTo مشخص گردیده است، تولید خواهند شد (دو فایل NHSample4.Domain.Course.hbm.xml و NHSample4.Domain.Student.hbm.xml را در پوشه‌ای که محل اجرای برنامه است خواهید یافت).

با فراخوانی متد CreateDb این کلاس، پس از ساخت خودکار نگاشت‌ها، database schema متناظر، در دیتابیسی که توسط کانکشن استرینگ برنامه مشخص شده، ایجاد خواهد شد که دیتابیس دیاگرام آن‌را در شکل ذیل مشاهده می‌نمائید (جداول دانشجویان و واحدها هر کدام به صورت موجودیتی مستقل ایجاد شده که ارجاعات آن‌ها در جدولی سوم نگهداری می‌شود).



پیاده سازی الگوی مخزن

اینترفیس عمومی الگوی مخزن به شکل زیر می‌تواند باشد:

using System;
using System.Linq;
using System.Linq.Expressions;

namespace NHSample4.NHRepository
{
//Repository Interface
public interface IRepository<T>
{
T Get(object key);

T Save(T entity);
T Update(T entity);
void Delete(T entity);

IQueryable<T> Find();
IQueryable<T> Find(Expression<Func<T, bool>> predicate);
}
}

سپس پیاده سازی آن با توجه به کلاس SingletonCore ایی که در قسمت قبل تهیه کردیم (جهت مدیریت صحیح سشن فکتوری)، به صورت زیر خواهد بود.
این کلاس کار آغاز و پایان تراکنش‌ها را نیز مدیریت کرده و جهت سهولت کار اینترفیس IDisposable را نیز پیاده سازی می‌کند :

using System;
using System.Linq;
using NHSessionManager;
using NHibernate;
using NHibernate.Linq;

namespace NHSample4.NHRepository
{
public class Repository<T> : IRepository<T>, IDisposable
{
private ISession _session;
private bool _disposed = false;

public Repository()
{
_session = SingletonCore.SessionFactory.OpenSession();
BeginTransaction();
}

~Repository()
{
Dispose(false);
}

public T Get(object key)
{
if (!isSessionSafe) return default(T);

return _session.Get<T>(key);
}

public T Save(T entity)
{
if (!isSessionSafe) return default(T);

_session.Save(entity);
return entity;
}

public T Update(T entity)
{
if (!isSessionSafe) return default(T);

_session.Update(entity);
return entity;
}

public void Delete(T entity)
{
if (!isSessionSafe) return;

_session.Delete(entity);
}

public IQueryable<T> Find()
{
if (!isSessionSafe) return null;

return _session.Linq<T>();
}

public IQueryable<T> Find(System.Linq.Expressions.Expression<Func<T, bool>> predicate)
{
if (!isSessionSafe) return null;

return Find().Where(predicate);
}

void Commit()
{
if (!isSessionSafe) return;

if (_session.Transaction != null &&
_session.Transaction.IsActive &&
!_session.Transaction.WasCommitted &&
!_session.Transaction.WasRolledBack)
{
_session.Transaction.Commit();
}
else
{
_session.Flush();
}
}

void Rollback()
{
if (!isSessionSafe) return;

if (_session.Transaction != null && _session.Transaction.IsActive)
{
_session.Transaction.Rollback();
}
}

private bool isSessionSafe
{
get
{
return _session != null && _session.IsOpen;
}
}

void BeginTransaction()
{
if (!isSessionSafe) return;

_session.BeginTransaction();
}


public void Dispose()
{
Dispose(true);
// tell the GC that the Finalize process no longer needs to be run for this object.
GC.SuppressFinalize(this);
}

protected virtual void Dispose(bool disposeManagedResources)
{
if (_disposed) return;
if (!disposeManagedResources) return;
if (!isSessionSafe) return;

try
{
Commit();
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
Rollback();
}
finally
{
if (isSessionSafe)
{
_session.Close();
_session.Dispose();
}
}

_disposed = true;
}
}
}
اکنون جهت استفاده از این کلاس مخزن به شکل زیر می‌توان عمل کرد:

using System;
using System.Collections.Generic;
using NHSample4.Domain;
using NHSample4.NHRepository;

namespace NHSample4
{
class Program
{
static void Main(string[] args)
{
//ایجاد دیتابیس در صورت نیاز
//NHSessionManager.Config.CreateDb();


//ابتدا یک دانشجو را اضافه می‌کنیم
Student student = null;
using (var studentRepo = new Repository<Student>())
{
student = studentRepo.Save(new Student() { Name = "Vahid" });
}

//سپس یک واحد را اضافه می‌کنیم
using (var courseRepo = new Repository<Course>())
{
var course = courseRepo.Save(new Course() { Teacher = "Shams" });
}

//اکنون یک واحد را به دانشجو انتساب می‌دهیم
using (var courseRepo = new Repository<Course>())
{
courseRepo.Save(new Course() { Students = new List<Student>() { student } });
}

//سپس شماره دروس استادی خاص را نمایش می‌دهیم
using (var courseRepo = new Repository<Course>())
{
var query = courseRepo.Find(t => t.Teacher == "Shams");

foreach (var course in query)
Console.WriteLine(course.Id);
}

Console.WriteLine("Press a key...");
Console.ReadKey();
}
}
}

همانطور که ملاحظه می‌کنید در این سطح دیگر برنامه هیچ درکی از ORM مورد استفاده ندارد و پیاده سازی نحوه‌ی تعامل با NHibernate در پس کلاس مخزن مخفی شده است. کار آغاز و پایان تراکنش‌ها به صورت خودکار مدیریت گردیده و همچنین آزاد سازی منابع را نیز توسط اینترفیس IDisposable مدیریت می‌کند. به این صورت امکان فراموش شدن یک سری از اعمال متداول به حداقل رسیده، میزان کدهای تکراری برنامه کم شده و همچنین هر زمانیکه نیاز بود، صرفا با تغییر پیاده سازی کلاس مخزن می‌توان به ORM دیگری کوچ کرد؛ بدون اینکه نیازی به بازنویسی کل برنامه وجود داشته باشد.

دریافت سورس برنامه قسمت هشتم

ادامه دارد ...


مطالب
ساخت کلیدهای امنیتی GunPG
یکی از روش‌های ارسال و رمزگذاری اطلاعات، استفاده از کلیدهای امنیتی مورد استفاده‌ی در سیستم یونیکس یا GnuPG است. استفاده از نرم افزار Gnu Privacy Guard یا گارد حفاظتی گنو، به ما این اجازه را می‌دهد که بتوانیم اطلاعاتمان را در بسترهای ارتباطی، با خیالی راحت‌تر ارسال کنیم و تا حد زیادی مطمئن باشیم که تنها فرد هدف توانایی دسترسی به اطلاعات را خواهد داشت. گارد امنیتی گنو زیر مجموعه‌ای از پروژه‌ی گنو است که دولت آلمان پایه ریز اصلی آن بوده است. این نرم افزار از یک روش رمزگذاری ترکیبی استفاده می‌کند که الگوریتم‌های کلیدهای برابر(متقارن) و کلید‌های عمومی (نامتقارن) جهت تبادل آسان کلید را شامل می‌شود. در حال حاضر که نسخه‌ی دو این برنامه ارائه شده است، برای رمزگذاری‌ها از کتابخانه‌ای به اسم libgcrypt استفاده می‌کند. یکی از مشکلات فعلی این پروژه، عدم وجود api‌های مناسبی جهت دسترسی راحت‌تر است و برای حل این مشکل، GPGME که مخفف GnuPG Made Easy ایجاد شد. بسیاری از برنامه‌ها و پلاگین‌های ارسال اطلاعات، امروزه همچون ارسال ایمیل، از این کلیدها بهره می‌برند.

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

  • OpenPGP: یک برنامه نیست و یک قانون و استانداری برای تهیه‌ی آن است؛ که رعایت اصول آن الزامی است و برنامه‌ی بالا، یک پیاده سازی از این استاندارد است.
  • PGP: یک برنامه، برای رمزگذاری اطلاعات است که مخفف Pretty Good Privacy است.
  • و GnuPG یا GPG که در بالا به آن اشاره شد.
برای ساخت کلید، ما از دستور یا برنامه‌ی GPG که که عمدتا در همه‌ی لینوکس‌ها مثل دبیان و مشتقات آن نصب است، استفاده می‌کنیم و اگر نصب نیست از طریق توزیع آن اقدام نمایید.
در صورتیکه از ویندوز استفاده می‌کنید، نیاز است ابتدا خط فرمان یونیکس را روی آن نصب کنید. برنامه‌ی Cygwin این امکان را به شما می‌دهد تا خط فرمان یونیکس و دستورات پیش فرض آن را داشته باشید. این برنامه در دو حالت ۳۲ بیتی و ۶۴ بیتی ایجاد شده است. از آنجا که گفتیم این برنامه شامل دستورات پیش فرض آن است، برای همین GPG باید به صورت یک بسته‌ی جداگانه نصب شود که در سایت آن می‌توانید بسته‌های مختلف آن‌را برای پلتفرم‌های مختلف را مشاهده کنید.

ساخت کلید


برای ساخت کلید دستور زیر را صادر کنید:
gpg --gen-key
اگر از نسخه‌های جدیدتر GPG استفاده می‌کنید، گزینه‌هایی به شکل زیر ایجاد می‌شوند؛ ولی اگر خیر، ممکن است تعداد و شماره‌ی گزینه‌ها متفاوت باشند که در این مورد دقت کنید. من در اینجا همان حالت پیش فرض، یعنی ۱ را انتخاب می‌کنم. این گزینه نحوه‌ی امضاء و یا رمزگذاری شما با استفاده از الگوریتم‌های RSA و DSA را مشخص می‌کند.
Please select what kind of key you want:
   (1) RSA and RSA (default)
   (2) DSA and Elgamal
   (3) DSA (sign only)
   (4) RSA (sign only)
در کل در هر حالتی، استفاده‌ی از RSA پیشنهاد می‌شود. بعد از آن، از شما اندازه‌ی کلید را می‌پرسد که همان مقدار پیش فرض خودش را وارد می‌کنیم:
What keysize do you want? (2048)
البته بسیاری ۱۰۲۴ بایت را نیز کافی می‌دانند.
بعد از آن مدت زمان اعتبار این کلید را از شما جویا می‌شود:
Key is valid for? (0)
هنگام این پرسش نحوه‌ی ورود زمان را به شما خواهد گفت که می‌تواند به شکل‌های زیر باشد:
دو هفته
2w
دو سال
2y
پس از آن هم یک تاییدیه از شما می‌گیرد و تاریخ انقضاء را به طور کامل برای شما می‌نویسد و سپس نیاز است که اطلاعاتی از قبیل نام و ایمیل و توضیح را وارد کنید:
You need a user ID to identify your key; the software constructs the user ID
from the Real Name, Comment and Email Address in this form:
    "Heinrich Heine (Der Dichter) <heinrichh@duesseldorf.de>"

Real name: ali yeganeh.m
Email address: yeganehaym@gmail.com
Comment: androidbreadcrumb
You selected this USER-ID:
    "ali yeganeh.m (androidbreadcrumb) <yeganehaym@gmail.com>"
بعد از آن از شما می‌خواهد که کل عملیات را تایید و یا کنسل کنید؛ یا اگر اطلاعات بالا را اشتباه وارد کرده‌اید، اصلاح کنید. با زدن کلید O عملیات را تایید کنید. در این حین از شما یک کلید برای رمزگذاری می‌پرسد که باید آن را دو بار بدهید و کارتان در اینجا به پایان می‌رسد و کلید ایجاد می‌شود.
اگر مشکلی در ساخت کلید نباشد با ارسال دستور زیر باید آن را در لیست کلیدها ببینید:
ali@alipc:~$ gpg --list-keys
/home/ali/.gnupg/pubring.gpg
----------------------------
pub   2048R/8708016A 2015-10-23 [expires: 2065-10-10]
uid                  ali yeganeh.m (androidbreadcrumb) <yeganehaym@gmail.com>
sub   2048R/533B7E96 2015-10-23 [expires: 2065-10-10]
در اینجا کلید عمومی در خط pub بعد از  / قرار دارد؛ یعنی عبارت ۸۷۰۸۰۱۶A کلید عمومی ماست که بر روی هر سیستم و هر کلیدی متفاوت است.

تبدیل کد متنی به کد دودویی
یکی از روش‌های ارسال کدهای دودویی تبدیل آنان به یک قالب متنی ASCII است که به آن قالب ASCII Armor هم می‌گویند. سایت‌های زیادی وجود دارند که این عبارت متنی را از شما می‌خواهند. چرا که مثلا این امکان وجود دارد که کلیدی که کاربر به سمت آنان می‌فرستد، آسیب دیده باشد یا اینکه KeyServer‌ها در دسترس نباشند. در مورد این سرورها در ادامه صحبت خواهیم کرد. مثلا یکی از سایت‌هایی که به این عبارت‌ها نیاز دارد ‌‌‌‌Bintray است.

برای دریافت این کلید متنی باید دستور زیر را صادر کنید:
gpg --output mykey.asc --export -a $GPGKEY
که برای مثال ما می‌شود:
gpg --output mykey.asc --export -a 8708016A
و اگر کلید را با یک ویرایشگر متنی باز کنید، محتوایی شبیه محتوای زیر را خواهید دید:
ali@alipc:~$ cat mykey.asc
-----BEGIN PGP PUBLIC KEY BLOCK-----
Version: GnuPG v1

mQENBFYqAJABCADcw5xPonh5Vj7nDk1CxDskq/VsO08XOa/i2OLOzatB4oK5x+0x
jxORxXMnIAR83PCK5/WkOBa64jnu3eiP3jKEwAykGGz/Z1bezC9TIP8y+PnsiDhT
aFArluUJx+RT5q7s27aKjqoc3fR/xuwLWopZt9uYzE/DQAPDsHdUoUg+fh4Hevm+
a8/3ncR7q6nM8gc9wk621Urb1HaRrILdmeh7ZpJcl8ZUbc+NObw357fGsjnpfHXO
rdCr7ClvNUq6I+IeGMQG/6040LeeaqhaRxPrUhbFjLA155gkSqzecxl7wQaYc71M
Zdlv+6Pt1B8nPAA3WXq0ypjU8A5bvmAQRD5LABEBAAG0OGFsaSB5ZWdhbmVoLm0g
KGFuZHJvaWRicmVhZGNydW1iKSA8eWVnYW5laGF5bUBnbWFpbC5jb20+iQE+BBMB
AgAoBQJWKgCQAhsDBQld/A8ABgsJCAcDAgYVCAIJCgsEFgIDAQIeAQIXgAAKCRDS
Lhq8hwgBanaHB/4reGxUjR6dB08ykfwQOx+raYHGqJlgawisE4qUHTkGaspyQaNy
yxh0vwKkGvg6nNy2VN1XFBc7jlHlrYqPPuPdg2B+1LvEghb30ESDbHUvk8NrJgDJ
C0257gxqWvUQTWvMC3FkSLdw3tyQ8dF7FxmSU79XcxVqGeseaDzMQrEasP0yJHsm
NJf8pvuD6qiWu3KSSoQmI/17Sj8s7eGJMh6o5YRFGHc1Bt9tCD+52bvt579Ju4vZ
tmQvxR4fNQo9sAeMqAJhIpF7IYcuyCEy+CQ847UkzE4f/OCCPxfV3samV/nnBJJ9
Ouu+68lk6Fpx4A0a3nEwqoAmMWxrbSSUFW97uQENBFYqAJABCAC4CzrUOKskE4hK
GVCjaOJKxhbuUdOrep6n3vof0fscs5Dy7h2oVh2vb12WH9X6pijJVPiUpGR4Mpu0
lO2Bu9Rwt38AQ6mRmL/hfzjEXSvKkdX7osk+1CVnnUaSdM9Ek2hWUH8JcN28z/WT
X9Bw8MCdZF7j1HvX/5ojghzMZyYM4elWJLBr1gON6xXAI6HR7DlnRkaVr8L9SYGm
FyAXZ0LzWYwG1Z1AnTyxff6v/Mn3p1/1E3aBA+LkQqBzHg2nBm4jCaFWfeCdiNBf
CHkY9r/Evo9hUPD+CtBNFwsUm1D4maZ0FFtIQ701QhVmupnub+rKoObC0AFj3abK
MCw9uo8TABEBAAGJASUEGAECAA8FAlYqAJACGwwFCV38DwAACgkQ0i4avIcIAWrz
rAf+K1IIMtBq3WlabfZQrgzFHQ62ugVJO/yI1ITkm4l08XHDf+ShqDg4urNuMDEe
oQD35MvB2BhER1jL6VR3qjLkZyZYJ+EQiSxEDWXooav3KvpWjhcqjQy79GFs8waH
E7ssGmWwaugVS/PJAmGQ+s8YWDNa6aCClmp2dJRiwBTyFdewNBLA2V32xzWCYxhI
YtEp+Kg15XuCDTRatOPWSFGSPe/paytmpGZc0XzU/W9sBpabhxVmcL4H6L07uCef
IOn/S5QXo3P9X/3ckmJ9GUb7rjdq1ivYgX53xI75jlePsmN/2f+3fNffUaZgFTTd
Uls+XCun7OVYSBBfjgRfQbTvoA==
=6j7i
-----END PGP PUBLIC KEY BLOCK-----
در صورتی که قصد دارید متن کلید خصوصی را به دست بیاورید، لازم است بعد از export- عبارت secret-key- را نیز اضافه کنی د؛ یعنی:
gpg --output mykey.asc --export-secret-key -a 8708016A


آپلود کلید به سرورهای کلید (Key Servers
)

 یکی از روش‌های به اشتراک گذاری کلید برای کاربران این است که از سرورهای کلید استفاده کنیم. یکبار آپلود روی یکی از این سرورها باعث می‌شود که به بقیه‌ی سرورها هم اضافه شود. یکی از این سرورهای کلید که خودم از آن استفاده می‌کنم، سرور ابونتو است و با استفاده از دستور زیر، همان کلید بالا را برای آن سرور ارسال می‌کنم:
gpg --send-keys --keyserver keyserver.ubuntu.com $GPGKEY

==>
gpg --send-keys --keyserver keyserver.ubuntu.com 8708016A
سپس از طریق کلید متنی، کلید آپلود شده را تایید می‌کنیم. به این آدرس رفته و محتوای کلید متنی خود را به طور کامل به همراه تگ‌های شروع و پایان کپی کنید و حتی می‌توانید کلید خود را از طریق کادر جست و جو پیدا کنید.

رمزگذاری
ابتدا در محیط یونیکس، یک فایل متنی ساده با متن hello ubuntu را ایجاد میکنم. در ادامه قصد دارم این فایل را رمزنگاری کنم:
ali@alipc:~$ cat >ali.txt
hello ubuntu
سپس همین فایل را رمزنگاری می‌کنم:
ali@alipc:~$ gpg --output myali.gpg --encrypt --recipient yeganehaym@gmail.com ali.txt
در این دستور ابتدا گفتیم که نام فایل خروجی ما myali.gpg است و می‌خواهیم آن را رمزگذاری کنیم که توسط کلیدی با ایمیل yeganehaym@gmail.com می‌باشد فایل ali.txt را رمزگذاری می‌کنیم.

رمزگشایی
برای رمزگشایی می‌توانید از طریق دستور زیر اقدام کنید:
gpg --output output.txt --decrypt myali.gpg

You need a passphrase to unlock the secret key for
user: "ali yeganeh.m (androidbreadcrumb) <yeganehaym@gmail.com>"
2048-bit RSA key, ID 533B7E96, created 2015-10-23 (main key ID 8708016A)
در اینجا دستور دادیم محتوای فایل رمزشده‌ی myali.gpg را رمزگشایی کن و محتوای آن را داخل فایلی با نام output.txt قرار بده. بعد از اجرای این دستور از شما عبارت رمزی را که در مرحله‌ی ساخت کلید دوبار از شما پرسید، درخواست می‌کند. در بعضی سیستم‌ها در همان ترمینال می‌پرسد، ولی بعضی سیستم‌ها مثل ابونتو که من از آن استفاده می‌کنم، به صورت گرافیکی یک کادر باز کرده و از شما خواهش می‌کند عبارت رمز را وارد کنید.
عبارت رمز را وارد کنید و حالا فایل output.txt را باز کنید:
ali@alipc:~$ cat output.txt 
hello ubuntu
نظرات مطالب
همه چیز در مورد CLR : قسمت اول
- به صورت خیلی خلاصه، کار DEP غیر میسر کردن اجرای داده‌ها به صورت کد است (مانند اجرای کدهای مخرب از طریق سر ریز بافر) و کار ASLR هم غیرقابل پیش بینی کردن محل قرارگرفتن بیت‌ها و داده‌های برنامه در حافظه‌است.
- ربطی به زبان برنامه نویسی ندارند؛ درست است. اما CLR یعنی Common Language Runtime . این محیط اجرایی و JIT آن ASLR را میسر می‌کنند . حتی اسمبلی‌های ngen شده هم از دات نت 3.5 به بعد دارای ASLR فعال هستند. همچنین DEP هم از طریق روشن کردن سوئیچ NXCOMPAT کامپایلر فراهم شده‌است (از زمان دات نت 2 به بعد). یعنی اگر OpenSSL را با دات نت می‌نوشتند، هیچ وقت مشکل heartbleed رخ نمی‌داد.
اشتراک‌ها
روش های مقایسه اشیاء با null

Check

Code 

Description

Is Null
if(variable is null) return true;

  • 🙂 This syntax supports static analysis such that later code will know whether variable is null or not.
  • 🙁 Doesn’t produce a warning even when comparing against a non-nullable value type making the code to check pointless.
  • 😐 Requires C# 7.0 because it leverages type pattern matching.
Is Not Null
if(variable is { }) return false

  • 🙂 This syntax supports static analysis such that later code will know whether variable is null or not.
  • 😐 Requires C# 8.0 since this is the method for checking for not null using property pattern matching.
Is Not Null
if(variable is object) return false

  • 🙂 Triggers a warning when comparing a non-nullable value type which could never be null
  • 🙂 This syntax works with C# 8.0’s static analysis so later code will know that variable has been checked for null.
  • Checks if the value not null by testing whether it is of type object.  (Relies on the fact that null values are not of type object.)
Is Null
if(variable == null) return true

  • 🙂 The only way to check for null prior to C# 7.0.
  • 🙁 However, because the equality operator can be overridden, this has the (remote) possibility of failing or introducing a performance issue.
Is Not Null
if(variable != null) return false

  • 🙂 The only way to check for not null prior to C# 7.0.
  • 😐 Since the not-equal operator can be overridden, this has the (remote) possibility of failing or introducing a performance issue. 
روش های مقایسه اشیاء با null
مطالب
برنامه نویسی پیشرفته JavaScript - قسمت 6 - تغییر صفات Property ها

برنامه نویسی شیء گرا

در این بخش میخواهیم به بررسی یکسری از ویژگی‌ها و نکات ریز برنامه نویسی شیء گرا در جاوا اسکریپت بپردازیم که یک برنامه نویس حرفه‌ای جاوا اسکریپت حتما باید بر آن‌ها واقف باشد تا بتواند کتابخانه‌ها و Framework ‌های موثرتر و بهینه‌تری را ایجاد کند. لازم به ذکر است که در این مجموعه مقالات، پیاده‌سازی اشیاء و شیوه‌ی کد نویسی، بر اساس استاندارد ECMAScript 5 یا ES5 انجام خواهد شد. بنابراین از قابلیتهای جدیدی که در ES6 اضافه شده‌است، صحبت نخواهیم کرد. پس از پایان این مجموعه مقالات و پس از آگاهی کامل از قابلیتهای جاوا اسکریپت، در مجموعه مقالاتی به بررسی قابلیتهای جدید ES6 خواهیم پرداخت که مرتبط به مقالات جاری است.

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

بر اساس تعریفی که از اشیاء در استاندارد ECMAScript صورت گرفته است، هرشیء، شامل مجموعه‌ای از ویژگی‌هاست، که هر یک از آنها می‌تواند حاوی یک مقدار پایه، شیء و یا تابع باشد. به عبارت دیگر هر شیء شامل آرایه‌ای از مقادیر است. هر ویژگی ( Property ) یا تابع (که در برنامه نویسی شیء گرا متد نیز نامیده می‌شود) توسط نام خود شناسایی می‌شوند که به یک مقدار داده‌ای نگاشت یا Map شده‌اند. به همین دلیل میتوان هر شیء را به عنوان یک Hash Table تصور کرد که داده‌ها را به صورت یک زوج کلید مقدار یا key-value pairs نگهداری می‌نماید. در اینصورت نام ویژگی‌ها و متدها به عنوان key و مقدار آنها به عنوان value در نظر گرفته می‌شوند.


مفهوم شیء

همانطور که قبلا اشاره شد، جهت تعریف اشیاء می‌توان از دو روش استفاده نمود. در روش اول، ایجاد شیء با استفاده از شیء Object و در روش دوم، با استفاده از Object Literal Notation انجام خواهد شد. روش دوم جدیدتر و بین برنامه نویسان جاوا اسکریپت محبوب‌تر است. مثال دیگری را جهت یادآوری در این مورد ذکر می‌کنم:

var person = new Object();
person.firstName = "Meysam";
person.birth = new Date(1982, 11, 8);
person.getAge = function () {
    var now = new Date();
    return now.getFullYear() - this.birth.getFullYear();
}

alert(person.firstName + ": " + person.getAge());    // Meysam: 34
در مثال فوق، شیء person شامل دو ویژگی firstName و birth و همچنین تابع getAge() می‌باشد. در تابع getAge() از روی ویژگی birth یا تاریخ تولد، سن شخص محاسبه شده‌است. همانطور که مشاهده می‌کنید، در داخل این تابع، جهت دسترسی به ویژگی birth، از شیء this استفاده نمودیم. this به شیء ای اشاره می‌کند که تابع getAge() به آن تعلق دارد و در اینجا به شیء person اشاره می‌نماید. اگر از this استفاده نکنید، برنامه خطا می‌دهد؛ زیرا قادر به شناسایی birth نمی‌باشد. مثال فوق را میتوان با استفاده از Object Literal Notation به صورت زیر نوشت:
var person = {
    firstName: "Meysam",
    birth: new Date(1982, 11, 8),
    getAge: function () {
        var now = new Date();
        return now.getFullYear() - this.birth.getFullYear();
    }
};

alert(person.firstName + ": " + person.getAge());    // Meysam: 34

انواع Property ها

در ECMAScript 5 ، صفاتی برای Property ‌ها معرفی شده است که از طریق Attribute ‌های داخلی به Property ‌ها اختصاص می‌یابد. این Attribute ‌ها توسط موتور جاوا اسکریپت بر روی Property ‌ها پیاده سازی می‌شوند و به صورت مستقیم قابل دسترسی نمی‌باشند. در طی فرآیند آموزش این مطالب، Attribute ‌های داخلی را در [[]] قرار می‌دهیم، مثل [[Enumarable]] ، تا از سایر دستورات تفکیک شوند. به صورت کلی دو نوع ویژگی داریم که شامل Data Properties و Accessor Properties می‌باشند که به شرح آنها می‌پردازیم.


Data Properties

Data Property ‌ها، 4 صفت یا Attribute را توصیف می‌کنند که عبارتند از:

[[Configurable]]

مشخص می‌کند یک Property اجازه حذف، تعریف مجدد و یا تغییر نوع را دارد یا خیر. بصورت پیش فرض، زمانی که یک شیء بصورت مستقیم ساخته می‌شود، مقدار این ویژگی True می‌باشد.

[[Enumarable]]

مشخص می‌کند که آیا امکان پیمایش یک Property توسط حلقه for-in وجود دارد یا خیر. بصورت پیش فرض، زمانیکه یک شیء بصورت مستقیم ساخته می‌شود، مقدار این ویژگی True می‌باشد.

[[Writable]]

مشخص می‌کند که آیا مقدار یک Property قابل تغییر می‌باشد یا خیر. بصورت پیش فرض، زمانیکه یک شیء بصورت مستقیم ساخته می‌شود، مقدار این ویژگی True می‌باشد.

[[Value]]

شامل مقدار واقعی یک Property و محل مقداردهی یا برگرداندن مقدار Property ‌ها می‌باشد. مقدار پیش فرض آن نیز undefined می‌باشد.


زمانیکه یک Property به صورت عادی به یک شیء اضافه می‌شود، مانند مثال‌های قبلی، سه Attribute اول به true تنظیم می‌شوند و [[Value]]  با مقدار اولیه Property تنظیم میگردد. در این حالت آن Property ، قابل بروزرسانی و پیمایش می‌باشد. جهت تغییر ساختار یک Property و تنظیم Attribute ‌های آن، باید آن Property را با استفاده از متد defineProperty() تعریف نماییم . شکل کلی تعریف Property با استفاده از این متد به صورت زیر می‌باشد:

Object.defineProperty(obj, prop, descriptor)
آرگومان obj ، شیء ای است که Property مورد نظر باید به آن اضافه شود. آرگومان prop نام Property را مشخص می‌کند که Attribute ‌های آن باید تنظیم شوند. آرگومان descriptor  یک شیء می‌باشد که  Attribute ‌های مورد نیاز را برای Property تنظیم می‌نماید. شیء descriptor شامل ویژگی‌های configurable ، enumerable ، writable و value می‌باشد که می‌توانند برای Property تنظیم شوند. خروجی این متد شیء ای است که به عنوان آرگومان اول ارسال شده‌است. به مثال‌های زیر توجه کنید:
var person = {};
Object.defineProperty(person, "name", {
    writable: false,
    value:"Meysam"
});

alert(person.name);   // Meysam
person.name = "Arash";
alert(person.name);   // Meysam
همانطور که در مثال فوق مشاهده می‌کنید، یک Property به نام name به شیء person اضافه شده‌است که صفت writable آن به false تنظیم گردیده‌است. بنابراین امکان تغییر مقدار ویژگی name وجود ندارد و با اینکه در دستور person.name = "Arash" ، ویژگی name را تغییر داده‌ایم، دستور alert نهایی، مجددا خروجی Meysam را نمایش داده‌است.
var person = {};
Object.defineProperty(person, "name", {
    configurable: false,
    value: "Meysam"
});

alert(person.name);  // Meysam
delete person.name;
alert(person.name);  // Meysam
در مثال فوق، صفت configurable را به false تنظیم نموده‌ایم و همانطور که مشاهده میکنید امکان حذف ویژگی name توسط عملگر delete وجود ندارد و دستور alert نهایی مجددا خروجی Meysam را نمایش داده‌است. توجه داشته باشید که اگر شما بخواهید در خطوط بعدی کد، مجددا صفت configurable را به مقدار true تغییر دهید، امکان پذیر نمی‌باشد. زیرا در تعریف فوق، صفت configurable را به false تنظیم نموده‌اید و امکان بروزرسانی Attribute ‌های ویژگی name را از آن گرفته‌اید. در این حالت تنها Attribute ی را که میتوانید تنظیم کنید، صفت writable می‌باشد.

لازم به ذکر است که می‌توانید متد defineProperty() را چندین بار برای یک Property فراخوانی نموده و در هر مرحله صفات متفاوتی را تنظیم و یا صفات قبلی را تغییر دهید.

علاوه بر متد فوق، متد دیگری به نام defineProperties() وجود دارد که می‌توان چند Property را بصورت همزمان تعریف و صفات آن را تنظیم نمود. شکل کلی این متد به صورت زیر است:

Object.defineProperties(obj, props)

آرگومان props یک شیء می‌باشد که ویژگی‌های آن، نام همان Property هایی هستند که باید به obj اضافه شوند. همچنین هر ویژگی خود یک شیء می‌باشد که میتوان صفات آن ویژگی را تنظیم نمود. به مثال زیر توجه کنید:

var person = {};
Object.defineProperties(person, {
    "name": {
        configurable: false,
        value: "Meysam"
    },
    "age": {
        writable:false,
        value:34
    }
});
در مثال فوق، برای آرگومان props ، دو ویژگی name و age را تعریف نمودیم که این دو ویژگی به شیء person اضافه خواهند شد. همچنین ویژگی‌های name و age خود یک شیء می‌باشند که صفات مربوط به آنها تنظیم شده است.

Accessor Properties

این صفات شامل توابع getter و setter می‌باشند که یک یا هر دوی آنها می‌توانند برای یک Property تنظیم شوند. زمانی که مقداری را از یک Property می‌خوانید، تابع getter فراخوانی می‌شود و مقدار Property مربوطه را بر میگرداند. این تابع می‌تواند قبل از برگرداندن مقدار، پردازش هایی را بر روی آن Property انجام داده و یک نتیجه‌ی معتبر را برگرداند. زمانیکه Property را مقداردهی می‌نمایید، تابع setter فراخوانی میشود و Property را با مقدار جدید تنظیم می‌نماید. این تابع می‌تواند قبل از مقداردهی به Property ، داده‌ی مورد نظر را اعتبارسنجی نماید تا از ورود مقادیر نامعتبر جلوگیری کند. Accessor Properties شامل 2 صفت زیر می‌باشد:

[[Get]]

یک تابع می‌باشد و زمانی فراخوانی می‌گردد که مقدار یک Property را بخوانیم و مقدار پیش فرض آن undefined می‌باشد.

[[Set]]

یک تابع می‌باشد و زمانی فراخوانی می‌گردد که یک Property را مقداردهی نماییم و مقدار پیش فرض آن undefined می‌باشد. این تابع شامل یک آرگومان ورودی است که حاوی مقدار ارسالی به Property است.

مثال زیر یک پیاده سازی ساده از شیء تاریخ شمسی می‌باشد که هنوز از لحاظ طراحی دارای نواقصی هست و در ادامه کارآیی و کد آن را بهبود می‌بخشیم.

var date = {
    _year: 1,
    _month: 1,
    _day: 1,
    isLeap: function () {
        switch (this.year % 33) {
            case 1: case 5: case 9: case 13:
            case 17: case 22: case 26: case 30:
                return true;
            default:
                return false;
        }
    }
};

Object.defineProperties(date, {
    "year": {
        "get": function () { return this._year; },
        "set": function (newValue) {
            if (newValue < 1 || newValue > 9999)
                throw new Error("Year must be between 1 and 9999");
            this._year = newValue;
        }
    },
    "month": {
        "get": function () { return this._month; },
        "set": function (newValue) {
            if (newValue < 1 || newValue > 12)
                throw new Error("Month must be between 1 and 12");
            this._month = newValue;
        }
    },
    "day": {
        "get": function () { return this._day; },
        "set": function (newValue) {
            if (newValue < 1 || newValue > 31)
                throw new Error("Day must be between 1 and 31");
            if (this.month === 12 && !this.isLeap() && newValue > 29)
                throw new Error("Day must be between 1 and 29");
            if (this.month > 6 && newValue > 30)
                throw new Error("Day must be between 1 and 30");
            this._day = newValue;
        }
    }
});
در مثال فوق، 3 ویژگی با نامهای _year ، _month و _day تعریف شده‌اند. پیشوند _ مشخص می‌کند که نباید به این ویژگی در خارج از شیء دسترسی داشته باشیم. البته دسترسی را محدود نمی‌کند و برنامه نویس به راحتی می‌تواند به آن دسترسی داشته باشد. در مباحث بعدی شیوه‌ی صحیح پیاده سازی اینگونه Property ‌ها را آموزش می‌دهیم. تابعی به نام isLeap() نیز تعریف شده است که تشخیص می‌دهد سال موجود کبیسه هست یا خیر. با استفاده از تابع defineProperties() ، 3 ویژگی دیگر نیز به شیء date ، با نامهای year ، month و day اضافه نموده‌ایم که دارای Accessor ‌های get و set می‌باشند. در بخش set ورودی‌های کاربران را بررسی و اعتبار سنجی نمودیم. در صورتی که ورودی نامعتبر باشد، با استفاده از throw خطایی را به صورت دستی ایجاد می‌نماییم که در console مربوط به Browser قابل مشاهده و یا با استفاده از try…catch قابل دسترسی و مدیریت می‌باشد.

دقت داشته باشید که لازم نیست حتما accessor ‌های getter و setter با هم برای یک Property تنظیم شوند و شما می‌توانید فقط یکی از آنها را برای Property به کار ببرید. اگر فقط تابع getter به یک Property اختصاص یابد، آن Property فقط خواندنی می‌شود و امکان تغییر مقدار آن وجود ندارد. در این صورت هر دستوری که اقدام به تغییر Property نماید، بی‌تاثیر خواهد بود. همچنین اگر فقط تابع setter به یک Property اختصاص یابد، آن Property فقط نوشتنی می‌شود و امکان خواندن مقدار آن وجود ندارد. در این صورت هر دستوری که اقدام به خواندن Property نماید، مقدار undefined برای آن برگردانده می‌شود.

نکته‌ی دیگری که باید به آن توجه کنید این است که اگر یک Property با استفاده از متد defineProperty() تعریف گردد، Attribute هایی که مقداردهی نشده‌اند، مثل [[Configurable]] ، [[Enumarable]] و [[Writable]] با false مقداردهی می‌گردند و [[Value]] ، [[Get]] و [[Set]] مقدار undefined را بر می‌گردانند. در مبحث بعدی، در مورد این نکته مثالی ارائه شده است.


خواندن Attribute ‌های مربوط به یک Property

با استفاده از متد getOwnPropertyDescriptor() می‌توان، Attribute ‌های اختصاص داده شده به Property ‌ها را خواند و از مقدار آنها مطلع شد. این متد شامل 2 آرگومان می‌باشد، که آرگومان اول، شیء ای است که میخواهیم Attribute آن را بخوانیم و آرگومان دوم، نام Attribute می‌باشد. خروجی متد getOwnPropertyDescriptor() یک شیء از نوع PropertyDescriptor می‌باشد که ویژگی‌های آن، همان Attribute هایی هستند که برای یک Property تنظیم شده‌اند. به مثال زیر جهت خواندن Attribute ‌های شیء تاریخ شمسی توجه کنید:

var descriptor = Object.getOwnPropertyDescriptor(date, "_year");
alert(descriptor.value);   // 1
alert(descriptor.configurable); // true
alert(typeof descriptor.get); // undefined

descriptor = Object.getOwnPropertyDescriptor(date, "year");
alert(descriptor.value);   // undefined
alert(descriptor.configurable); // false
alert(typeof descriptor.get); // function
ویژگی _year به صورت عادی تعریف شده است. بنابراین با توجه به نکاتی که قبلا ذکر شد، مقدار اختصاص داده شده به این ویژگی، به صفت [[Value]] تعلق گرفته است. همچنین سایر صفات این ویژگی به مانند [[Configurable]] ، با مقدار true تنظیم شده‌اند. Accessor ‌های getter و setter نیز، که برای این ویژگی تنظیم نشده بودند، مقدار undefined بر می‌گردانند. ویژگی year با استفاده از متد defineProperties() تعریف شده است و چون Accessor ‌های getter و setter به آن اختصاص یافته‌اند، صفت [[Value]]، مقدار undefined را بر می‌گرداند و سایر Attribute ‌ها به مانند [[Configurable]] که تنظیم نشده‌اند، مقدار false را بر می‌گردانند. همچنین برای getter و setter نوع function برگردانده شده‌است. 
مطالب دوره‌ها
نکاتی درباره برنامه نویسی دستوری(امری)
در این فصل نکاتی را درباره برنامه نویسی دستوری در #F فرا خواهیم گرفت. برای شروع از mutale خواهیم گفت.

mutable
Keyword
در فصل دوم(شناسه ها) گفته شد که برای یک شناسه امکان تغییر مقدار وجود ندارد. اما در #F راهی وجود دارد که در صورت نیاز بتوانیم مقدار یک شناسه را تغییر دهیم.در #F هرگاه بخواهیم شناسه ای تعریف کنیم که بتوان در هر زمان مقدار شناسه رو به دلخواه تغییر داد از کلمه کلیدی mutable کمک می‌گیریم و برای تغییر مقادیر شناسه‌ها کافیست از علامت (->) استفاده کنیم. به یک مثال در این زمینه دقت کنید:
#1 let mutable phrase = "Can it change? "

#2 printfn "%s" phrase

#3 phrase <- "yes, it can."

#4 printfn "%s" phrase


در خط اول یک شناسه را به صورت mutable(تغییر پذیر) تعریف کردیم و در خط سوم با استفاده از (->) مقدار شناسه رو update کردیم. خروجی مثال بالا به صورت زیر است:
Can it change?  
yes, it can.

نکته اول: در این روش هنگام update کردن مقدار شناسه حتما باید مقدار جدید از نوع مقدار قبلی باشد در غیر این صورت با خطای کامپایلری متوقف خواهید شد.
#1 let mutable phrase = "Can it change?  "

#3 phrase <- 1

اجرای کد بالا خطای زیر را به همراه خواهد داشت.(خطا کاملا واضح است و نیاز به توضیح دیده نمی‌شود)
Prog.fs(9,10): error: FS0001: This expression has type
int
but is here used with type
string
نکته دوم :ابتدا به مثال زیر توجه کنید.
let redefineX() =
let x = "One"
printfn "Redefining:\r\nx = %s" x
if true then
   let x = "Two"
printfn "x = %s" x
printfn "x = %s" x

در مثال بالا در تابع redefineX یک شناسه به نام x تعریف کردم با مقدار "One". یک بار مقدار شناسه x رو چاپ می‌کنیم و بعد دوباره بعد از شرط true یک شناسه دیگر با همون نام یعنی x تعریف شده است و در انتها هم دو دستور چاپ. ابتدا خروجی مثال بالا رو با هم مشاهده می‌کنیم.
Redefining:
x = One
x = Two
x = One
همان طور که میبینید شناسه دوم x بعد از تعریف دارای مقدار جدید Two بود و بعد از اتمام محدوده(scope) مقدار x دوباره به One تغییر کرد.(بهتر است بگوییم منظور از دستور print x سوم اشاره به شناسه x اول برنامه است). این رفتار مورد انتظار ما در هنگام استفاده از روش تعریف مجدد شناسه هاست. حال به بررسی رفتار muatable در این حالت می‌پردازیم.
let mutableX() =
let mutable x = "One"
printfn "Mutating:\r\nx = %s" x
if true then
   x <- "Two"
printfn "x = %s" x
printfn "x = %s" x
تنها تفاوت در استفاده از mutable keyword و (->) است. خروجی مثال بالا نیز به صورت زیر خواهد بود. کاملا واضح است که مقدار شناسه x بعد از تغییر و اتمام محدوده(scope) هم چنان Two خواهد بود.
Mutating:
x = One
x = Two
x = Two

Reference Cells

روشی برای استفاده از شناسه‌ها به صورت mutable است. با این روش می‌تونید شناسه هایی تعریف کنید که امکان تغییر مقدار برای اون‌ها وجود دارد. زمانی که از این روش برای مقدار دهی به شناسه‌ها استفاده کنیم یک کپی از مقدار مورد نظر به شناسه اختصاص داده می‌شود نه آدرس مقدار در حافظه.
به جدول زیر توجه کنید:

 Member Or Field
Description
 Definition
(derefence operator)!
 مقدار مشخص شده را برگشت می‌دهد
 

let (!) r = r.contents 

 (Assignment operator)=: مقدار مشخص شده را تغییر می‌دهد
 

let (:=) r x = r.contents <- x 

ref operator
 یک مقدار را در یک reference cell  جدید کپسوله می‌کند
 

let ref x = { contents = x } 

Value Property
 برای عملیات get  یا set  مقدار مشخص شده
 

member x.Value = x.contents 

 contents record field
 برای عملیات get  یا set  مقدار مشخص شده

let ref x = { contents = x } 

  یک مثال:
let refVar = ref 6

refVar := 50
printfn "%d" !refVar
خروجی مثال بالا 50 خواهد بود.
let xRef : int ref = ref 10

printfn "%d" (xRef.Value)
printfn "%d" (xRef.contents)

xRef.Value <- 11
printfn "%d" (xRef.Value)
xRef.contents <- 12
printfn "%d" (xRef.contents)
خروجی مثال بالا:
10
10
11
12 

خصیصه اختیاری در #F
در #F زمانی از خصیصه اختیاری استفاده می‌کنیم که برای یک متغیر مقدار وجود نداشته باشد. option  در #F نوعی است که می‌تواند هم مقدار داشته باشد و هم نداشته باشد.
let keepIfPositive (a : int) = if a > 0 then Some(a) else None
از None زمانی استفاده می‌کنیم که option مقدار نداشته باشد و از Some  زمانی استفاده می‌کنیم که option مقدار داشته باشد.
let exists (x : int option) = 
    match x with
    | Some(x) -> true
    | None -> false
در مثال بالا ورودی تابع exists از نوع int و به صورت اختیاری تعریف شده است.(معادل با ?int یا<Nullable<int در #C) در صورتی که x مقدار داشته باشد مقدار true در غیر این صورت مقدار false را برگشت می‌دهد.

چگونگی استفاده  از option
مثال
let rec tryFindMatch pred list =
    match list with
    | head :: tail -> if pred(head)
                        then Some(head)
                        else tryFindMatch pred tail
    | [] -> None

let result1 = tryFindMatch (fun elem -> elem = 100) [ 200; 100; 50; 25 ] //برابر با 100 است

let result2 = tryFindMatch (fun elem -> elem = 26) [ 200; 100; 50; 25 ]// برابر با None است
یک تابع به نام tryFindMatch داریم با دو پارامتر ورودی. با استفاده از الگوی Matching از عنصر ابتدا تا انتها را در لیست (پارامتر list) با مقدار پارامتر pred مقایسه می‌کنیم. اگر مقادیر برابر بودند مقدار head در غیر این صورت None(یعنی option مقدار ندارد) برگشت داده می‌شود.
یک مثال کاربردی تر
open System.IO
let openFile filename =
    try 
        let file = File.Open (filename, FileMode.Create)
        Some(file)
    with
        | ex -> eprintf "An exception occurred with message %s" ex.Message
                None
در مثال بالا از option‌ها برای بررسی وجود یا عدم وجود فایل‌های فیزیکی استفاده کردم.

Enumeration
تقریبا همه با نوع داده شمارشی یا enums آشنایی دارند. در اینجا فقط به نحوه پیاده سازی آن در #F می‌پردازیم. ساختار کلی تعریف آن به صورت زیر است:
type enum-name =
   | value1 = integer-literal1
   | value2 = integer-literal2
   ...
یک مثال از تعریف:
type Color =
   | Red = 0
   | Green = 1
   | Blue = 2
نحوه استفاده
let col1 : Color = Color.Red
enums فقط از انواع داده ای sbyte, byte, int16, uint16, int32, uint32, int64, uint16, uint64, char پشتیبانی می‌کند که البته مقدار پیش فرض آن Int32 است. در صورتی که بخواهیم صریحا نوع داده ای را ذکر کنیم به صورت زیر عمل می‌شود.
type uColor =
   | Red = 0u
   | Green = 1u
   | Blue = 2u
let col3 = Microsoft.FSharp.Core.LanguagePrimitives.EnumOfValue<uint32, uColor>(2u)

توضیح درباره use
در دات نت خیلی از اشیا هستند که اینترفیس IDisposable رو پیاده سازی کرده اند. این بدین معنی است که حتما یک متد به نام dispose برای این اشیا وجود دارد که فراخوانی آن به طور قطع باعث بازگرداندن حافظه ای که در اختیار این کلاس‌ها بود می‌شود. برای راحتی کار در #C یک عبارت به نام using وجود دارد که در انتها بلاک متد dispose شی مربوطه را فراخوانی می‌کند.
using(var writer = new StreamWriter(filePath))
{
   
}
در #F نیز امکان استفاده از این عبارت با اندکی تفاوت وجود دارد.مثال:
let writeToFile fileName =
    use sw = new System.IO.StreamWriter(fileName : string)
    sw.Write("Hello ")
Units Of Measure
در #F اعداد دارای علامت و اعداد شناور دارای وابستگی با واحد‌های اندازه گیری هستند که به نوعی معرف اندازه و حجم و مقدار و ... هستند. در #F شما مجاز به تعریف واحد‌های اندازه گیری خاص خود هستید و در این تعاریف نوع عملیات اندازه گیری را مشخص می‌کنید. مزیت اصلی استفاده از این روش جلوگیری از رخ دادن خطاهای کامپایلر در پروژه است. ساختار کلی تعریف:
[<Measure>] type unit-name [ = measure ]
یک مثال از تعریف واحد cm:
[<Measure>] type cm
مثالی از تعریف میلی لیتر:
[<Measure>] type ml = cm^3
برای استفاده از این واحد‌ها می‌تونید به روش زیر عمل کنید.
let value = 1.0<cm>
توابع تبدیل واحد ها
قدرت اصلی واحد‌های اندازه گیری  #F در توابع تبدیل است. تعریف توابع تبدیل به صورت زیر می‌باشد:
[<Measure>] type g                 تعریف واحد گرم
[<Measure>] type kg               تعریف واحد کیلوگرم
let gramsPerKilogram : float<g kg^-1> = 1000.0<g/kg>    تعریف تابع تبدیل
یک مثال دیگر :
[<Measure>] type degC // دما بر حسب سلسیوس
[<Measure>] type degF // دما بر حسب فارنهایت

let convertCtoF ( temp : float<degC> ) = 9.0<degF> / 5.0<degC> * temp + 32.0<degF> // تابع تبدیل سلسیوس به فارنهایت
let convertFtoC ( temp: float<degF> ) = 5.0<degC> / 9.0<degF> * ( temp - 32.0<degF>) // تابع تبدیل فارنهایت به سلسیوس

let degreesFahrenheit temp = temp * 1.0<degF> // درجه به فارنهایت
let degreesCelsius temp = temp * 1.0<degC> // درجه به سلسیوس

printfn "Enter a temperature in degrees Fahrenheit." 
let input = System.Console.ReadLine()
let mutable floatValue = 0.
if System.Double.TryParse(input, &floatValue)// اگر ورودی عدد بود
   then 
      printfn "That temperature in Celsius is %8.2f degrees C." ((convertFtoC (degreesFahrenheit floatValue))/(1.0<degC>))
   else
      printfn "Error parsing input."

خروجی مثال بالا :

Enter a temperature in degrees Fahrenheit.
90
That temperature in degrees Celsius is    32.22.
مطالب
Blazor 5x - قسمت چهارم - مبانی Blazor - بخش 1 - Data Binding
عنوان می‌شود که HTML over Web socket آینده‌ی توسعه‌ی برنامه‌های وب است و این آینده هم اکنون توسط Blazor Server در دسترس است. در این مدل توسعه، ابتدا یک اتصال SignalR برقرار شده و سپس تمام تعاملات بین سرور و کلاینت، از طریق همین اتصال که عموما web socket است، مدیریت می‌شود. به همین جهت در ادامه قصد داریم یک پروژه‌ی Blazor Server را تکمیل کنیم. پس از آن یک پروژه‌ی Blazor WASM را نیز بررسی خواهیم کرد. بنابراین هر دو مدل توسعه‌ی برنامه‌های Blazor را پوشش خواهیم داد. برای این منظور در ابتدا مبانی Blazor را بررسی می‌کنیم که در هر دو مدل یکی است.


تعریف مدل برنامه

در همان پروژه‌ی خالی Blazor Server که در قسمت دوم با دستور dotnet new blazorserver ایجاد کردیم، پوشه‌ی Models را افزوده و کلاس BlazorRoom را در آن تعریف می‌کنیم:
namespace BlazorServerSample.Models
{
    public class BlazorRoom
    {
        public int Id { set; get; }

        public string Name { set; get; }

        public decimal Price { set; get; }

        public bool IsActive { set; get; }
    }
}
سپس برای اینکه مدام نیاز به تعریف فضای نام آن در فایل‌های مختلف razor. برنامه نباشد، به فایل Imports.razor_ مراجعه کرده و سطر زیر را به انتهای آن اضافه می‌کنیم:
@using BlazorServerSample.Models
برنامه را نیز توسط دستور dotnet watch run اجرا می‌کنیم.


Data binding یک طرفه

در ادامه به فایل Pages\Index.razor مراجعه کرده و منهای سطر اول مسیریابی آن، مابقی محتوای آن‌را حذف می‌کنیم. در اینجا می‌خواهیم مقادیر نمونه‌ای از شیء BlazorRoom را نمایش دهیم. به همین جهت این شیء را در قسمت code@ فایل razor جاری (همانند نکات قسمت قبل)، ایجاد می‌کنیم:
@page "/"

<h2 class="bg-light border p-2">
    First Room
</h2>
Room: @Room.Name
<br/>
Price: @Room.Price

@code
{
    BlazorRoom Room = new BlazorRoom
    {
        Id = 1,
        Name = "Room 1",
        IsActive = true,
        Price = 499
    };
}
در اینجا در ابتدا شیء Room را در قسمت قطعه کد فایل razor جاری ایجاد کرده و سپس اطلاعات آن‌را با استفاده از زبان Razor نمایش داده‌ایم.


 به این روش نمایش اطلاعات، one-way data-binding نیز گفته می‌شود. اما چطور می‌توان یک طرفه بودن آن‌را متوجه شد؟ برای این منظور یک text-box را نیز در ذیل تعاریف فوق، به صورت زیر اضافه می‌کنیم که مقدارش را از Room.Price دریافت می‌کند:
<input type="number" value="@Room.Price" />
اکنون اگر این مقدار را تغییر دهیم، عدد جدید قیمت اتاق، به خاصیت Room.Price منعکس نمی‌شود و تغییری نمی‌کند:



Data binding دو طرفه

اکنون می‌خواهیم اگر مقدار ورودی Room.Price توسط text-box فوق تغییر کرد، نتیجه‌ی نهایی، به خاصیت متناظر با آن نیز اعمال شود و تغییر کند. برای این منظور فقط کافی است ویژگی value را به bind-value@ تغییر دهیم:
<input type="number" @bind-value="@Room.Price" />
ویژگی bind-value@ سبب برقراری data-binding دو طرفه می‌شود. یعنی در ابتدا مقدار اولیه‌ی خاصیت Room.Price را نمایش می‌دهد. در ادامه‌ی اگر کاربر، مقدار این text-box را تغییر داد، نتیجه‌ی نهایی را به خاصیت Room.Price نیز اعمال می‌کند و همچنین این تغییر، سبب به روز رسانی UI نیز می‌شود؛ یعنی در جائیکه پیشتر مقدار اولیه‌ی Room.Price را نمایش داده بودیم، اکنون مقدار جدید آن نمایش داده خواهد شد:


البته اگر برنامه را اجرا کنیم، با تغییر مقدار text-box، بلافاصله تغییری را مشاهده نخواهیم کرد. برای اعمال تغییرات نیاز خواهد بود تا در جائی خارج از text-box کلیک و focus را به المانی دیگر منتقل کنیم. اگر می‌خواهیم همراه با تایپ اطلاعات درون text-box، رابط کاربری نیز به روز شود، می‌توان bind-value را به یک رخداد خاص، مانند oninput متصل کرد. حالت پیش‌فرض آن onchange است:
<input type="number" @bind-value="@Room.Price" @bind-value:event="oninput" />
اکنون اگر برنامه را اجرا کرده و درون text-box اطلاعاتی را وارد کنیم، بلافاصله UI نیز به روز رسانی خواهد شد.
لیست کامل رخ‌دادها را در اینجا می‌توانید مشاهده کنید. برای مثال برای یک المان input، دو رخداد onchange و oninput قابل تعریف هستند.

یک نکته: در حین کار با bind-value@، نیازی نیست مقدار آن با @ شروع شود. یعنی ذکر "bind-value="Room.Price@ نیز کافی است.


تمرین 1 - خاصیت IsActive یک اتاق را به یک checkbox متصل کرده و همچنین وضعیت جاری آن‌را نیز در یک برچسب نمایش دهید.

در اینجا می‌خواهیم مقدار خاصیت Room.IsActive را توسط یک اتصال دو طرفه، به یک checkbox متصل کنیم:
<input type="checkbox" @bind-value="Room.IsActive"  />
<br/>
This room is @(Room.IsActive? "Active" : "Inactive").
با استفاده از bind-value@، وضعیت جاری خاصیت Room.IsActive را به یک checkbox متصل کرده‌ایم. همچنین در ادامه توسط یک عبارت شرطی، این وضعیت را نمایش داده‌ایم.


بار اولی که برنامه نمایش داده می‌شود، هر چند مقدار IsActive بر اساس مقدار دهی آن در شیء Room، مساوی true است، اما chekbox، علامت نخورده باقی می‌ماند. برای رفع این مشکل نیاز است ویژگی checked این المان را نیز به صورت زیر مقدار دهی کرد:
<input type="checkbox" @bind-value="Room.IsActive"
   checked="@(Room.IsActive? "cheked" : null)" />
در این حالت اگر اتاقی فعال باشد، مقدار ویژگی checked، به checked و در غیراینصورت به null تنظیم می‌شود. به این ترتیب مشکل عدم نمایش checkbox انتخاب شده در بار اول نمایش کامپوننت جاری، برطرف می‌شود.


اتصال خواص مدل‌ها به dropdown‌ها

اکنون می‌خواهیم مدل این مثال را کمی توسعه داده و خواص تو در تویی را به آن اضافه کنیم:
using System.Collections.Generic;

namespace BlazorServerSample.Models
{
    public class BlazorRoom
    {
        // ...

        public List<BlazorRoomProp> RoomProps { set; get; }
    }

    public class BlazorRoomProp
    {
        public int Id { set; get; }

        public string Name { set; get; }

        public string Value { set; get; }
    }
}
برای مثال یک اتاق می‌تواند ویژگی‌هایی مانند مساحت، تعداد نفرات مجاز و غیره را داشته باشد. هدف از ویژگی جدید RoomProps، تعیین لیست این نوع موارد است.
پس از این تعاریف، فیلد Room را به صورت زیر به روز رسانی می‌کنیم تا تعدادی از خواص اتاق را به همراه داشته باشد:
@code
{
    BlazorRoom Room = new BlazorRoom
    {
        Id = 1,
        Name = "Room 1",
        IsActive = true,
        Price = 499,
        RoomProps = new List<BlazorRoomProp>
        {
            new BlazorRoomProp
            {
                Id = 1, Name = "Sq Ft", Value = "100"
            },
            new BlazorRoomProp
            {
                Id = 2, Name = "Occupancy", Value = "3"
            }
        }
    };
}
در ادامه می‌خواهیم این خواص را در یک dropdown نمایش دهیم. همچنین با انتخاب یک خاصیت از دراپ‌داون، مقدار خاصیت انتخابی را در یک برچسب نیز به صورت پویا نمایش خواهیم داد:
<select @bind="SelectedRoomPropValue">
    @foreach (var prop in Room.RoomProps)
    {
        <option value="@prop.Value">@prop.Name</option>
    }
</select>
<span>The value of the selected room prop is: @SelectedRoomPropValue</span>

@code
{
    string SelectedRoomPropValue = "";
    // ...
همانطور که مشاهده می‌کنید، انجام یک چنین کاری با Blazor بسیار ساده‌است و نیازی به استفاده از جاوا اسکریپت و یا جی‌کوئری ندارد.
در اینجا یک فیلد را در قطعه کد برنامه تعریف کرده و به المان select متصل کرده‌ایم. هرگاه آیتمی در این دراپ داون انتخاب شود، این فیلد، مقدار آن آیتم انتخابی را خواهد داشت. در ادامه توسط یک حلقه‌ی foreach، تمام خواص یک اتاق را دریافت کرده و به صورت options‌های یک select استاندارد، نمایش می‌دهیم. در آخر نیز مقدار SelectedRoomPropValue را نمایش داده‌ایم که این مقدار به صورت پویا تغییر می‌کند:



تعریف لیستی از اتاق‌ها

عموما در یک برنامه‌ی واقعی، با یک تک اتاق کار نمی‌کنیم. به همین جهت در ادامه لیستی از اتاق‌ها را تعریف و مقدار دهی اولیه خواهیم کرد:
@code
{
    string SelectedRoomPropValue = "";

    List<BlazorRoom> Rooms = new List<BlazorRoom>();

    protected override void OnInitialized()
    {
        base.OnInitialized();

        Rooms.Add(new BlazorRoom
        {
            Id = 1,
            Name = "Room 1",
            IsActive = true,
            Price = 499,
            RoomProps = new List<BlazorRoomProp>
            {
                new BlazorRoomProp
                {
                    Id = 1, Name = "Sq Ft", Value = "100"
                },
                new BlazorRoomProp
                {
                    Id = 2, Name = "Occupancy", Value = "3"
                }
            }
        });

        Rooms.Add(new BlazorRoom
        {
            Id = 2,
            Name = "Room 2",
            IsActive = true,
            Price = 399,
            RoomProps = new List<BlazorRoomProp>
            {
                new BlazorRoomProp
                {
                    Id = 1, Name = "Sq Ft", Value = "250"
                },
                new BlazorRoomProp
                {
                    Id = 2, Name = "Occupancy", Value = "4"
                }
            }
        });
    }
}
در ابتدا فیلد Rooms تعریف شده که لیستی از BlazorRoomها است. در ادامه بجای مقدار دهی مستقیم آن در همان سطح قطعه کد، آن‌را در یک متد life-cycle کامپوننت جاری به نام OnInitialized که مخصوص این نوع مقدار دهی‌های اولیه است، مقدار دهی کرده‌ایم.


نمایش لیست قابل ویرایش اتاق‌ها

اکنون می‌خواهیم به عنوان تمرین 2، لیست جزئیات اتاق‌های تعریف شده را نمایش دهیم؛ با این شرط که نام و قیمت هر اتاق، قابل ویرایش باشد. همچنین خواص تعریف شده نیز به صورت ستون‌هایی مجزا، نمایش داده شوند. برای مثال اگر دو خاصیت در اینجا تعریف شده، 2 ستون اضافه‌تر نیز برای نمایش آن‌ها وجود داشته باشد. به علاوه از آنجائیکه می‌خواهیم اتصال دوطرفه را نیز آزمایش کنیم، نام و قیمت هر اتاق را نیز در پایین جدول، مجددا به صورت برچسب‌هایی نمایش خواهیم داد.


برای رسیدن به تصویر فوق می‌توان به صورت زیر عمل کرد:
<div class="border p-2 mt-3">
    <h2 class="text-info">Rooms List</h2>
    <table class="table table-dark">
        @foreach(var room in Rooms)
        {
            <tr>
                <td>
                    <input type="text" @bind-value="room.Name" @bind-value:event="oninput"/>
                </td>
                <td>
                    <input type="text" @bind-value="room.Price" @bind-value:event="oninput"/>
                </td>
                @foreach (var roomProp in room.RoomProps)
                {
                    <td>
                        @roomProp.Name, @roomProp.Value
                    </td>
                }
            </tr>
        }
    </table>

    @foreach(var room in Rooms)
    {
        <p>@room.Name's price is @room.Price.</p>
    }
</div>
در اینجا یک حلقه‌ی تو در تو را مشاهده می‌کنید. حلقه‌ی بیرونی، ردیف‌های جدول را که شامل نام و قیمت هر اتاق است، به صورت input-boxهای متصل به خواص متناظر با آن‌ها نمایش می‌دهد. سپس برای اینکه بتوانیم خواص هر ردیف را نیز نمایش دهیم، حلقه‌ی دومی را بر روی room.RoomProps تشکیل داده‌ایم.
هدف از foreach پس از جدول، نمایش تغییرات انجام شده‌ی در input-boxها است. برای مثال اگر نام یک ردیف را تغییر دادیم، چون یک اتصال دو طرفه برقرار است، خاصیت متناظر با آن به روز رسانی شده و بلافاصله در برچسب‌های ذیل جدول، منعکس می‌شود.


کدهای کامل این مطلب را از اینجا می‌توانید دریافت کنید: Blazor-5x-Part-04.zip