مطالب
آشنایی با کلاس JavaScriptSerializer

برای استفاده از jQuery Ajax یکی از روش‌های ارسال دیتا به برنامه، تبدیل داده‌ها به فرمت JSON می‌باشد. برای داده‌های ساده، تشخیص این فرمت ساده است. مثلا اگر امضای تابع وب سرویس اجکس ما به صورت زیر باشد:
public static bool IsUserAvailable(string username)
اطلاعات جی‌سونی را که قرار است ارسال کنیم، فرمت زیر را باید داشته باشد:
{'username':'value'}
حال اگر آرگومان‌های ما پیچیده‌تر بودند چطور؟ مثلا بجای یک رشته ساده، یک لیست جنریک داشتیم، فرمت ورودی را چگونه باید تشخیص داد؟
برای این منظور در دات نت 3 و نیم، کلاسی جهت انجام اینگونه تبدیلات پیش بینی شده است که شرح مختصر آن به صورت زیر است:
ابتدا باید ارجاعی را به اسمبلی system.web.extensions به برنامه افزود و سپس جهت سهولت کار می‌توان یک extension method از کلاس JavaScriptSerializer مهیا در فضای نام System.Web.Script.Serialization ایجاد کرد:

public static string ToJson(this object data)
{
return new JavaScriptSerializer().Serialize(data);
}
اکنون چند مثال زیر را در نظر بگیرید:
        public static string GetJsonTest0()
{
var data = "a1";
return data.ToJson();
}

public static string GetJsonTest1()
{
var data = new List<string> { "a1", "a2", "a3" };
return data.ToJson();
}

public static string GetJsonTest2()
{

var lst =
new List<KeyValuePair<string, object>>
{
new KeyValuePair<string, object>("Name", "علی"),
new KeyValuePair<string, object>("Number", 10),
new KeyValuePair<string, object>("Desc", "منابع مورد نیاز")
};

return lst.ToJson();

}
خروجی‌های آن‌ها به ترتیب به صورت زیر خواهند بود:

"a1"
["a1","a2","a3"]
[{"Key":"Name","Value":"علی"},{"Key":"Number","Value":10},{"Key":"Desc","Value":"منابع مورد نیاز"}]

این کلاس همچنین قابلیت Deserialize و تبدیل داده‌هایی به فرمت JSON به اشیاء مورد نظر ما را نیز دارا است.

مطالب
آموزش فریم ورک Vuetify قسمت دوم - UI Components بخش اول

 کاربران امروزه با عناصری که به نحوی خاص درون صفحه عمل می‌کنند، آشنا شده‌اند. بنابراین انتخاب مناسب برای اتخاذ این عناصر زمانی مناسب است که به تکمیل کارآیی و رضایت کاربر کمک کند.

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

1 - اول اینکه چگونه به نظر می‌رسد ( UI ).

2 – دوم اینکه چگونه کار میکند ( UX ).

این عناصر رابط ( component ) شامل :

Input Controls : check boxes, radio buttons, drop down lists, list boxes, buttons, toggles, text fields, date field

Navigational Components : breadcrumb, slider, search field, pagination, slider, tags, icons

Informational Components : tool tips, icons, progress bar, notifications, message boxes, modal windows

Containers : accordion 

اما باید توجه داشت که فقط به این موارد محدود نمی‌شوند.

در این قسمت به طور مختصر با این دست از کامپوننت‌ها ( UI Component ) آشنا می‌شویم. 


کامپوننت v-alert 

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

هشدارها می‌توانند یک رنگ خاص را داشته باشند که به طور پیش فرض نمایش داده نمی‌شوند. 

در مثال پایین، کامپوننت v-alert شامل دو مقدار است که برای آن تنظیم شده‌است. مقدار (value) که شامل یک مقدار Boolean است و مقدار (type) که مشخص کننده نوع هشدار است (موفقیت ، اطلاعات ، هشدار و خطا).

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

<div id="app">
  <v-app id="inspire">
    <div>
      <v-alert :value="true" type="success">
        This is a success alert.
      </v-alert>
  
      <v-alert :value="true" type="info">
        This is a info alert.
      </v-alert>
  
      <v-alert :value="true" type="warning">
        This is a warning alert.
      </v-alert>
  
      <v-alert :value="true" type="error">
        This is a error alert.
      </v-alert>
    </div>
  </v-app>
</div>



برای کامپوننت V-Alert می‌توان properties‌های مختلفی را مشخص نمود که از جمله آنها می‌توان به موارد زیر اشاره کرد:

Color : به وسیله این property می‌توان رنگ پیغام را مشخص نمود. هم به وسیله نام رنگ میتوان رنگ مورد نظر را مشخص کرد و هم به وسیله‌ی کد RGB این کار را انجام داد. dismissible : این تنظیم مشخص می‌کند که پیغام، قابلیت بسته شدن را دارد یا خیر که حاوی یک مقدار Boolean است.

icon : مشخص کننده یک نماد خاص است که درون جعبه پیغام قرار می‌گیرد.

type :  مشخص کننده نوع پیام است که پیشتر در مورد آن توضیح داده شد. 


کامپوننت v-avatar 

کامپوننت v-avatar برای تغییر اندازه تصاویر مورد استفاده قرار می‌گیرد که معمولا جهت نمایش عکس پروفایل استفاده می‌شود.

طریقه استفاده :

avatar، دارای یک اندازه‌ی پویا است که می‌تواند برای هر وضعیتی تغییر کند.

برای این کامپوننت سه properties قابل تنظیم است:

color : به وسیله این property می‌توان رنگ دلخواهی را برای آواتار مشخص نمود. هم به وسیله نام رنگ می‌توان رنگ مورد نظر را مشخص کرد و هم به وسیله کد RGB این کار را صورت داد.

size : به طور پیش‌فرض برای avatar، سایز 48 تنظیم شده‌است که می‌توان این میزان را کم و یا زیاد کرد.

tile : همانند border radius در css عمل می‌کند. با تنظیم این گزینه میتوانیم یک آواتار گرد داشته باشیم.


کامپوننت v-badge

به وسیله این کامپوننت می‌توان نمادهایی را برای نمایش اطلاعاتی به کاربر یا جلب توجه کاربر به یک عنصر خاص، ایجاد نمود.

این کامپوننت نیز properties خاص خود را دارد که از جمله آن می‌توان به color , left , mode , overlab  و غیره اشاره کرد.

قطعه کد پایین نشان دهنده چگونگی عملکرد این کامپوننت است:

<div id="app">
  <v-app id="inspire">
    <div>
      <v-badge color="purple" left overlap>
        <template v-slot:badge>
          <v-icon dark small>
            done
          </v-icon>
        </template>
        <v-icon color="grey lighten-1" large>
          account_circle
        </v-icon>
      </v-badge>
        <v-badge overlap color="orange">
        <template v-slot:badge>
          <v-icon dark small>
            notifications
          </v-icon>
        </template>
        <v-icon large color="grey darken-1">
          account_box
        </v-icon>
      </v-badge>
    </div>
  </v-app>
</div>

نتیجه قطعه کد بالا بدین ترتیب است:


کامپوننت v-bottom-nav 

این کامپوننت را می‌توان جایگزین sidebar‌ها نمود. این کامپوننت در درجه اول در موبایل مورد استفاده قرار میگیرد که می‌تواند شامل متن و یا آیکن باشد. 

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

این کامپوننت نیز properties خاص خود را دارد که از جمله آن می‌توان به active-sync (برای نشان دادن فعال یا غیر فعال بودن گزینه انتخاب شده)، fixed ( برای مشخص کردن موقعیت کامپوننت در صفحه) و موارد دیگر اشاره کرد.

تقسیم بندی اجزاء این کامپوننت به شرح زیر است:

1 - محل قرار گیری کامپوننت

2- آیکن غیر فعال

3- برچسب غیر فعال

4 - آیکن فعال

5- برچسب فعال

قطعه کد پایین نشان دهنده چگونگی یک bottom navbar است:

<div id="app">
  <v-app id="inspire">
    <v-card height="200px" flat>
      <div>
        Active: {{ bottomNav }}   // 
      </div>
      <v-bottom-nav :active.sync="bottomNav" :value="true" absolute color="transparent">
        <v-btn color="teal" flat value="recent">
          <span>Recent</span>
          <v-icon>history</v-icon>
        </v-btn>
  
        <v-btn color="teal" flat value="favorites">
          <span>Favorites</span>
          <v-icon>favorite</v-icon>
        </v-btn>
  
        <v-btn color="teal" flat value="nearby">
          <span>Nearby</span>
          <v-icon>place</v-icon>
        </v-btn>
      </v-bottom-nav>
    </v-card>
  </v-app>
</div>



کامپوننت v-btn  

این کامپوننت برای ایجاد یک دکمه چه به صورت متن و یا آیکن مورد استفاده قرار میگیرد. دکمه‌ها به کاربران این امکان را می‌دهند تا اقداماتی را انجام دهند و انتخاب‌های خود را تنها با یک کلیک انجام دهند.

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

کامپوننت v-btn نیز مانند سایر کامپوننت‌ها تنظیمات خاص خود را دارد که از جمله آن می‌توان به کوچکی و بزرگی دکمه، فعال یا غیر فعال بودن دکمه، نوع متن یا آیکن بودن دکمه اشاره نمود. 

حالت‌های مختلفی از دکمه‌ها وجود دارند که میتوانند به بهتر شدن UI برنامه ما کمک کنند. برای مثال می‌توان به موارد زیر اشاره کرد:

button drop-down variants : دکمه‌های کرکره‌ای که معمولا برای نظم و کم جا بودن در صفحه مورد استفاده قرار می‌گیرند.

icons : آیکن‌ها می‌توانند برای محتوای اصلی یک دکمه مورد استفاده قرار بگیرند تا ظاهر زیباتری را به دکمه ما بدهند.

floating : این دکمه‌ها حالت آیکن را دارند؛ با این تفاوت که آیکن مورد نظر، درون یک محتوا قرار می‌گیرد.

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

round : این نوع دکمه‌ها دقیقا کارآیی دکمه‌های معمولی را دارند؛ با این تفاوت که این دکمه‌ها دارای لبه‌هایی گرد هستند.

یک نمونه از ایجاد انواع دکمه‌ها در زیر آمده است:

<div id="app">
  <v-app id="inspire">
    <div>
      <v-btn color="success">Success</v-btn>
      <v-btn color="error">Error</v-btn>
      <v-btn color="warning">Warning</v-btn>
      <v-btn color="info">Info</v-btn>
    </div>
  </v-app>
</div>




کامپوننت v-calendar

یکی از کامپوننت‌هایی که به تازگی به vuetify اضافه شده است، کامپوننت تقویم یا v-calendar است. از این کامپوننت برای نمایش تاریخ، روز، هفته، ماه و سال استفاده می‌شود. یک تقویم دارای یک نوع و یک مقدار است که تعیین می‌کند چه نوع تقویمی، در طول چه مدت زمانی نمایش داده شود.

حالت‌های مختلفی برای نمایش تقویم در صفحه وجود دارد که برای مثال می‌توان به موارد زیر اشاره کرد: 

events : به وسیله این گزینه میتوان برای هر روز یک رخداد خاص را مشخص نمود که به وسیله کلیک بر روی آن، اطلاعات آن رخداد نمایش داده شود.

weekly : می‌توان یک تقویم هفتگی را ایجاد نمود و رخدادهای هفتگی را برای آن تنظیم کرد.

نمونه ایجاد یک تقویم در پایین آمده است:

<div id="app">
  <v-app id="inspire">
    <v-layout wrap>
      <v-flex xs12>
        <v-sheet height="500">
          <v-calendar ref="calendar" v-model="start" :type="type" :end="end" color="primary">
          </v-calendar>
        </v-sheet>
      </v-flex>
  
      <v-flex sm4 xs12>
        <v-btn @click="$refs.calendar.prev()">
          <v-icon dark left>
            keyboard_arrow_left
          </v-icon>
          Prev
        </v-btn>
      </v-flex>
      <v-flex sm4 xs12>
        <v-select v-model="type" :items="typeOptions" label="Type">
        </v-select>
      </v-flex>
      <v-flex sm4 xs12>
        <v-btn @click="$refs.calendar.next()">
          Next
          <v-icon right dark>
            keyboard_arrow_right
          </v-icon>
        </v-btn>
      </v-flex>
    </v-layout>
  </v-app>
</div>
js قطعه کد
new Vue({
  el: '#app',
  data: () => ({
    type: 'month', //مشخص کننده نوع تقویم که در اینجا تقویم به صورت ماهانه است
    start: '2019-01-01',
    end: '2019-01-06',
    typeOptions: [
      { text: 'Day', value: 'day' },
      { text: '4 Day', value: '4day' },
      { text: 'Week', value: 'week' },
      { text: 'Month', value: 'month' },
      { text: 'Custom Daily', value: 'custom-daily' },
      { text: 'Custom Weekly', value: 'custom-weekly' }
    ]
  })
})





نظرات مطالب
Blazor 5x - قسمت ششم - مبانی Blazor - بخش 3 - چرخه‌های حیات کامپوننت‌ها
یک نکته‌ی تکمیلی: تنظیم پویای عنوان صفحات در برنامه‌های Blazor

برای تنظیم پویای عنوان یک صفحه‌ی وب، نیاز است با DOM API مرورگر به صورت مستقیم کار کرد. برای مثال فایل wwwroot\main.js را که مدخل آن به کامپوننت Host_ و یا صفحه‌ی index.html اضافه می‌شود، به صورت زیر تکمیل می‌کنیم:
window.JsFunctionHelper = {
  blazorSetTitle: function (title) {
    document.title = title;
  }
};
اکنون می‌خواهیم این متد جاوااسکریپتی را که مستقیما با شیء document کار می‌کند، در کامپوننت جدید Client\Shared\PageTitle.razor استفاده کنیم:
@inject IJSRuntime JSRuntime

@code
{
   [Parameter]
   public string Title { get; set; }

   protected override async Task OnParametersSetAsync()
   {
      await JSRuntime.InvokeVoidAsync("JsFunctionHelper.blazorSetTitle", Title);
   }
}
در اینجا کامپوننت جدیدی تعریف شده‌است که به محض تنظیم مقدار پارامتر عنوان آن، سبب فراخوانی متد جاوا اسکریپتی blazorSetTitle می‌شود. برای نمونه روش استفاده‌ی از آن در کامپوننت Counter، جهت نمایش عنوانی پویا، به محض تغییر مقدار شمارشگر، به صورت زیر می‌تواند باشد:
@page "/counter"

<PageTitle Title="@GetPageTitle()" />

<h1>Counter</h1>

<p>Current count: @currentCount</p>

<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>

@code {
    private int currentCount = 0;

    private void IncrementCount()
    {
        currentCount++;
    }

    private string GetPageTitle() => $"Counter ({currentCount})";
}
این روش در برنامه‌های Blazor Server کار نخواهد کرد و در حین فراخوانی متد InvokeVoidAsync یک NullReferenceException مشاهده می‌شود؛ چون این نوع برنامه‌های Blazor Server به همراه یک مرحله‌ی pre-render در سمت سرور هستند که ابتدا، کار تهیه‌ی HTML ای را که باید به سمت مرورگر ارسال کنند، به پایان می‌رسانند. در این مرحله خبری از DOM نیست که بتوان به آن دسترسی یافت و تغییری را در آن ایجاد کرد.
برای رفع این مشکل همانطور که در مطلب جاری نیز عنوان شد، باید از روال رویدادگردان OnAfterRenderAsync استفاده کرد. در این حالت کدهای کامپوننت PageTitle.razor به صورت زیر تغییر می‌کنند:
@inject IJSRuntime JSRuntime

@code
{
   [Parameter]
   public string Title { get; set; }

   protected override async Task OnAfterRenderAsync(bool firstRender)
   {
      await JSRuntime.InvokeVoidAsync("JsFunctionHelper.blazorSetTitle", Title);
   }
}
روال رویدادگردان OnAfterRenderAsync پس از اینکه کار بارگذاری و تشکیل کامل DOM در مرورگر انجام شد، فراخوانی می‌شود. به همین جهت دیگر دسترسی به شیء document.title، سبب بروز یک NullReferenceException نخواهد شد.


یک نکته: قرار است در Blazor 6x، کامپوننت‌های جدید Title، Link و Meta جهت تنظیم اطلاعات تگ head صفحه، به صورت استاندارد اضافه شوند:
<Title Value="@title" />
<Meta name="description" content="Modifying the head from a Blazor component." />
<Link href="main.css" rel="stylesheet" />
مطالب
نصب خودکار اطلاعات فایل‌های PFX در سیستم

در مورد نحوه رمزنگاری فایل‌های PDF به کمک روش Public-key encryption توسط iTextSharp مطلبی را پیشتر در این سایت مطالعه کرده‌اید.
این روش یک مشکل مهم دارد: «ارائه فایل PFX و همچنین کلمه عبور آن به کاربر نهایی»
خوب، این یعنی اینکه شما به راحتی می‌تونید اطلاعات را رمزگشایی کنید؛ چون همه چیز سخاوتمندانه در اختیارتان است. بنابراین ضرورت رمزنگاری آن در ابتدای امر زیر سؤال می‌رود.
اکنون این سؤال مطرح می‌شود که آیا می‌توان این اطلاعات را تا حد قابل قبولی مخفی کرد؟ مثلا یک برنامه را در اختیار کاربر قرار داد که اطلاعات فایل PFX را به همراه کلمه عبور آن در سیستم نصب کند.
پاسخ:
دات نت به صورت توکار از این نوع فایل‌های مجوز پشتیبانی می‌کند:

using System.Security.Cryptography.X509Certificates;

namespace InstallPfx
{
class Program
{
private static void InstallCertificate(string cerFileName, string password)
{
var certificate = new X509Certificate2(cerFileName, password, X509KeyStorageFlags.PersistKeySet);
var store = new X509Store(StoreName.My);
store.Open(OpenFlags.ReadWrite);
store.Add(certificate);
store.Close();
}

static void Main(string[] args)
{
InstallCertificate(@"D:\forTest\file.pfx", "123456");
}
}
}

پس از اجرای کد فوق، امکان مشاهده فایل‌های PDF رمزنگاری شده به کمک اطلاعات فایل file.pfx، میسر می‌شود.
برای مشاهده این مجوز نصب شده هم می‌توان در دیالوگ Run ویندوز نوشت : certmgr.msc تا کنسول مدیریتی مجوز‌های ویندوز ظاهر شود. سپس به قسمت personal certificates باید مراجعه کرد.

مطالب
نوشتن آزمون‌های واحد به کمک کتابخانه‌ی Moq - قسمت اول - معرفی
گاهی از اوقات، برای نوشتن آزمون‌های واحد، ایزوله سازی قسمتی که می‌خواهیم آن‌را بررسی کنیم، از سایر قسمت‌های سیستم مشکل می‌شود. برای مثال اگر در کلاسی کار اتصال به بانک اطلاعاتی صورت می‌گیرد و قصد داریم برای آن آزمون واحد بنویسیم، اما قرار نیست که الزاما با بانک اطلاعاتی کار کنیم، در این حالت نیاز به یک نمونه‌ی تقلیدی یا Mock از بانک اطلاعاتی را خواهیم داشت، تا کار دسترسی به بانک اطلاعاتی را شبیه سازی کند. در این سری با استفاده از کتابخانه‌ی بسیار معروف Moq (ماک‌یو تلفظ می‌شود؛ گاهی از اوقات هم ماک)، کار ایزوله سازی کلاس‌ها را انجام خواهیم داد، تا بتوانیم آن‌ها را مستقل از هم آزمایش کنیم.


Mocking چیست؟

فرض کنید برنامه‌ای را داریم که از تعدادی کلاس تشکیل شده‌است. در این بین می‌خواهیم تعدادی از آن‌ها را به صورت ایزوله‌ی از کل سیستم آزمایش کنیم. البته باید درنظر داشت که این کلاس‌ها در حین اجرای واقعی برنامه، از تعدادی وابستگی خاص در همان سیستم استفاده می‌کنند. برای مثال کلاسی در این بین برای بررسی میزان اعتبار مالی یک کاربر، نیاز دارد تا با یک وب سرویس خارجی کار کند. اما چون می‌خواهیم این کلاس را به صورت ایزوله‌ی از کل سیستم آزمایش کنیم، اینبار بجای استفاده‌ی از وابستگی واقعی این کلاس، آن وابستگی را با یک نمونه‌ی تقلیدی یا Mock object در اینجا، جایگزین می‌کنیم.
بنابراین Mocking به معنای جایگزین کردن یک وابستگی واقعی سیستم که در زمان اجرای آن مورد استفاده قرار می‌گیرد، با نمونه‌ی تقلیدی مختص زمان آزمایش برنامه، جهت بالابردن سهولت نوشتن آزمون‌های واحد است.


دلایل و مزایای استفاده‌ی از Mocking

- یکی از مهم‌ترین دلایل استفاده‌ی از Mocking، کاهش پیچیدگی تنظیمات اولیه‌ی نوشتن آزمون‌های واحد است. برای مثال اگر در برنامه‌ی خود از تزریق وابستگی‌ها استفاده می‌کنید و کلاسی دارای چندین وابستگی تزریق شده‌ی به آن است، برای آزمایش این کلاس نیاز به تدارک تمام این وابستگی‌ها را خواهید داشت تا بتوان این کلاس را وهله سازی کرد و همچنین برنامه را نیز کامپایل نمود. اما در این بین ممکن است آزمایش متدی در همان کلاس، الزاما از تمام وابستگی‌های تزریق شده‌ی در یک کلاس استفاده نکند. در این حالت، Mocking می‌تواند تنظیمات پیچیده‌ی وهله سازی این کلاس را به حداقل برساند.
- Mocking می‌تواند سبب افزایش سرعت اجرای آزمون‌های واحد نیز شود. برای مثال با تقلید سرویس‌های خارجی مورد استفاده‌ی در برنامه (هر عملی که از مرزهای سیستم رد شود مانند کار با شبکه، بانک اطلاعاتی، فایل سیستم و غیره)، می‌توان میزان I/O و همچنین زمان صرف شده‌ی به آن‌را به حداقل رساند.
- از mock objects می‌توان برای رهایی از مشکلات کار با مقادیر غیرمشخص استفاده کرد. برای مثال اگر در کدهای خود از DateTime.Now استفاده می‌کنید یا اعداد اتفاقی و امثال آن، هربار که آزمون‌های واحد را اجرا می‌کنیم، خروجی متفاوتی را دریافت کرده و بسیاری از آزمون‌های نوشته شده با مشکل مواجه می‌شوند. به کمک mocking می‌توان بجای این مقادیر غیرمشخص، یک مقدار ثابت و مشخص را بازگشت دهد.
- چون به سادگی می‌توان mock objects را تهیه کرد، می‌توان کار توسعه و آزمایش برنامه را پیش از به پایان رسیدن پیاده سازی اصلی سرویس‌های مدنظر، همینقدر که اینترفیس آن سرویس مشخص باشد، شروع کرد که می‌تواند برای کارهای تیمی بسیار مفید باشد.
- اگر وابستگی مورد استفاده ناپایدار و یا غیرقابل پیش بینی است، می‌توان توسط mocking به یک نمونه‌ی قابل پیش بینی و پایدار مخصوص آزمون‌های برنامه رسید.
- اگر وابستگی خارجی مورد استفاده به ازای هر بار استفاده، هزینه‌ای را شارژ می‌کند، می‌توان توسط mocking، هزینه‌ی آزمون‌های برنامه را کاهش داد.


Unit test چیست؟

بدیهی است در کنار آزمایش ایزوله‌ی قسمت‌های مختلف برنامه توسط mocking، باید کل برنامه را جهت بررسی دستیابی به نتایج واقعی نیز آزمایش کرد که به این نوع آزمون‌ها، آزمون یکپارچگی (Integration Tests)، API Tests ،UI Tests و غیره می‌گویند که در کنار Unit tests ما حضور خواهند داشت. بنابراین اکنون این سؤال مطرح می‌شود که یک Unit چیست؟
در برنامه‌ای که از چندین کلاس تشکیل می‌شود، به یک کلاس، یک Unit گفته می‌شود. همچنین اگر در این سیستم، دو یا چند کلاس با هم کار می‌کنند (کلاسی که از چندین وابستگی استفاده می‌کند)، این‌ها با هم نیز یک Unit را تشکیل دهند. بنابراین تعریف Unit بستگی به نحوه‌ی درک عملکرد یک سیستم و تعامل اجزای آن با هم دارد.


واژه‌های متناظر با Mock objects

در حین مطالعه‌ی منابع مرتبط با آزمون‌های واحد ممکن است با این واژه‌های تقریبا مشابه مواجه شوید: fakes ،stubs ،dummies و mocks. اما تفاوت آن‌ها در چیست؟
- Fakes در حقیقت یک نمونه پیاده سازی واقعی، اما غیرمناسب محیط واقعی و اصلی پروژه‌است. برای نمونه EF Core به همراه یک نمونه in-memory database هم هست که دقیقا با مفهوم Fakes تطابق دارد.
- از Dummies صرفا جهت تهیه‌ی پارامترهای مورد نیاز برای اجرای یک آزمایش استفاده می‌شوند. این پارامترها، هیچگاه در آزمایش‌های انجام شده مورد استفاده قرار نمی‌گیرند.
- از Stubs برای ارائه‌ی پاسخ‌هایی مشخص به فراخوان‌ها استفاده می‌شود. برای مثال یک متد یا خاصیت، دقیقا چه چیزی را باید بازگشت دهند.
- از Mocks برای بررسی تعامل اجزای مختلف در حال آزمایش استفاده می‌شود. آیا متدی یا خاصیتی مورد استفاده قرار گرفته‌است یا خیر؟

باید درنظر داشت که زمانیکه یک شیء Mock را توسط کتابخانه‌ی Moq تهیه می‌کنیم، هر سه مفهوم stubs ،dummies و mocks را با هم به همراه دارد. به همین جهت در این سری زمانیکه به یک mock object اشاره می‌شود، هر سه مفهوم مدنظر هستند.

واژه‌ی دیگری که ممکن است در این گروه زیاد مشاهده شود، «Test double» نام دارد که ترکیب هر 4 مورد fakes ،stubs ،dummies و mocks می‌باشد. در کل هر زمانیکه یک شیء مورد استفاده‌ی در زمان اجرای برنامه را جهت آزمایش ساده‌تر آن جایگزین می‌کنید، یک Test double را ایجاد کرده‌اید.


بررسی ساختار برنامه‌ای که می‌خواهیم آن‌را آزمایش کنیم

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


موجودیت‌های برنامه‌ی وام دهی
namespace Loans.Entities
{
    public class Applicant
    {
        public int Id { set; get; }

        public string Name { set; get; }

        public int Age { set; get; }

        public string Address { set; get; }

        public decimal Salary { set; get; }
    }
}

namespace Loans.Entities
{
    public class LoanProduct
    {
        public int Id { set; get; }

        public string ProductName { set; get; }

        public decimal InterestRate { set; get; }
    }
}

namespace Loans.Entities
{
    public class LoanApplication
    {
        public int Id { set; get; }

        public LoanProduct Product { set; get; }

        public LoanAmount Amount { set; get; }

        public Applicant Applicant { set; get; }

        public bool IsAccepted { set; get; }
    }

    public class LoanAmount
    {
        public string CurrencyCode { get; set; }

        public decimal Principal { get; set; }
    }
}

مدل‌های برنامه‌ی وام دهی

namespace Loans.Models
{
    public class IdentityVerificationStatus
    {
        public bool Passed { get; set; }
    }
}

سرویس‌های برنامه‌ی وام دهی

using Loans.Models;

namespace Loans.Services.Contracts
{
    public interface IIdentityVerifier
    {
        void Initialize();

        bool Validate(string applicantName, int applicantAge, string applicantAddress);

        void Validate(string applicantName, int applicantAge, string applicantAddress, out bool isValid);

        void Validate(string applicantName, int applicantAge, string applicantAddress,
            ref IdentityVerificationStatus status);
    }
}

namespace Loans.Services.Contracts
{
    public interface ICreditScorer
    {
        int Score { get; }

        void CalculateScore(string applicantName, string applicantAddress);
    }
}

using System;
using Loans.Entities;
using Loans.Services.Contracts;

namespace Loans.Services
{
    public class LoanApplicationProcessor
    {
        private const decimal MinimumSalary = 1_500_000_0;
        private const int MinimumAge = 18;
        private const int MinimumCreditScore = 100_000;

        private readonly IIdentityVerifier _identityVerifier;
        private readonly ICreditScorer _creditScorer;

        public LoanApplicationProcessor(
            IIdentityVerifier identityVerifier,
            ICreditScorer creditScorer)
        {
            _identityVerifier = identityVerifier ?? throw new ArgumentNullException(nameof(identityVerifier));
            _creditScorer = creditScorer ?? throw new ArgumentNullException(nameof(creditScorer));
        }

        public bool Process(LoanApplication application)
        {
            application.IsAccepted = false;

            if (application.Applicant.Salary < MinimumSalary)
            {
                return application.IsAccepted;
            }

            if (application.Applicant.Age < MinimumAge)
            {
                return application.IsAccepted;
            }

            _identityVerifier.Initialize();

            var isValidIdentity = _identityVerifier.Validate(
                application.Applicant.Name, application.Applicant.Age, application.Applicant.Address);

            if (!isValidIdentity)
            {
                return application.IsAccepted;
            }

            _creditScorer.CalculateScore(application.Applicant.Name, application.Applicant.Address);
            if (_creditScorer.Score < MinimumCreditScore)
            {
                return application.IsAccepted;
            }

            application.IsAccepted = true;
            return application.IsAccepted;
        }
    }
}

using System;
using Loans.Models;
using Loans.Services.Contracts;

namespace Loans.Services
{
    public class IdentityVerifierServiceGateway : IIdentityVerifier
    {
        public DateTime LastCheckTime { get; private set; }

        public void Initialize()
        {
            // Initialize connection to external service
        }

        public bool Validate(string applicantName, int applicantAge, string applicantAddress)
        {
            Connect();
            var isValidIdentity = CallService(applicantName, applicantAge, applicantAddress);
            LastCheckTime = DateTime.Now;
            Disconnect();

            return isValidIdentity;
        }

        private void Connect()
        {
            // Open connection to external service
        }

        private bool CallService(string applicantName, int applicantAge, string applicantAddress)
        {
            // Make call to external service, interpret the response, and return result

            return false; // Simulate result for demo purposes
        }

        private void Disconnect()
        {
            // Close connection to external service
        }

        public void Validate(string applicantName, int applicantAge, string applicantAddress, out bool isValid)
        {
            throw new NotImplementedException();
        }

        public void Validate(string applicantName, int applicantAge, string applicantAddress,
            ref IdentityVerificationStatus status)
        {
            throw new NotImplementedException();
        }
    }
}
توضیحات:
هدف از این برنامه، درخواست یک وام جدید است. Application در اینجا به معنای درخواست یا فرم جدید است و Applicant نیز شخصی است که این درخواست را داده‌است.
در اینجا بیشتر تمرکز ما بر روی کلاس LoanApplicationProcessor است که دارای دو وابستگی تزریق شده‌ی به آن نیز می‌باشد:
        public LoanApplicationProcessor(
            IIdentityVerifier identityVerifier,
            ICreditScorer creditScorer)
        {
            _identityVerifier = identityVerifier ?? throw new ArgumentNullException(nameof(identityVerifier));
            _creditScorer = creditScorer ?? throw new ArgumentNullException(nameof(creditScorer));
        }
از این وابستگی‌ها برای تصدیق هویت درخواست کننده و همچنین بررسی میزان اعتبار او استفاده می‌شود.
تمام این منطق نیز در متد Process آن قابل مشاهده‌است که هدف اصلی آن، بررسی قابل پذیرش بودن درخواست یک وام جدید است.


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

در اولین تصویر این قسمت، پروژه‌ی class library دومی را نیز به نام Loans.Tests مشاهده می‌کنید. فایل csproj آن به صورت زیر برای کار با MSTest تنظیم شده‌است:
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>netcoreapp2.2</TargetFramework>
  </PropertyGroup>
  <ItemGroup>
    <ProjectReference Include="..\Loans\Loans.csproj" />
  </ItemGroup>
  <ItemGroup>
    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.3.0" />
    <PackageReference Include="MSTest.TestAdapter" Version="2.0.0" />
    <PackageReference Include="MSTest.TestFramework" Version="2.0.0" />    
  </ItemGroup>
</Project>
که در آن ارجاعی به پروژه‌ی Loans.csproj و همچنین وابستگی‌های MSTest، تنظیم شده‌اند.

اکنون اولین آزمون واحد ما در کلاس جدید LoanApplicationProcessorShould چنین شکلی را پیدا می‌کند:
using Loans.Entities;
using Loans.Services;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace Loans.Tests
{
    [TestClass]
    public class LoanApplicationProcessorShould
    {
        [TestMethod]
        public void DeclineLowSalary()
        {
            var product = new LoanProduct {Id = 99, ProductName = "Loan", InterestRate = 5.25m};
            var amount = new LoanAmount {CurrencyCode = "Rial", Principal = 2_000_000_0};
            var applicant =
                new Applicant {Id = 1, Name = "User 1", Age = 25, Address = "This place", Salary = 1_100_000_0};
            var application = new LoanApplication {Id = 42, Product = product, Amount = amount, Applicant = applicant};
            var processor = new LoanApplicationProcessor(null, null);
            processor.Process(application);

            Assert.IsFalse(application.IsAccepted);
        }
    }
}
در حین کار با MSTest، کلاس آزمون واحد باید به ویژگی TestClass و متدهای public void آن به ویژگی TestMethod مزین شوند تا توسط این فریم‌ورک آزمون واحد شناسایی شده و مورد آزمایش قرار گیرند.
در این آزمایش، شخص درخواست کننده، حقوق کمی دارد و می‌خواهیم بررسی کنیم که آیا LoanApplicationProcessor می‌تواند آن‌را بر اساس مقدار MinimumSalary، رد کند یا خیر؟
public class LoanApplicationProcessor
{
    private const decimal MinimumSalary = 1_500_000_0;

در حین وهله سازی LoanApplicationProcessor، دو وابستگی آن به null تنظیم شده‌اند؛ چون می‌دانیم که بررسی MinimumSalary پیش از سایر بررسی‌ها صورت می‌گیرد و اساسا در این آزمایش، نیازی به این وابستگی‌ها نداریم.
اما اگر سعی در اجرای این آزمایش کنیم (برای مثال با اجرای دستور dotnet test در خط فرمان)، آزمایش اجرا نشده و با استثنای زیر مواجه می‌شویم:
Test method Loans.Tests.LoanApplicationProcessorShould.DeclineLowSalary threw exception:
System.ArgumentNullException: Value cannot be null.
Parameter name: identityVerifier
چون در سازنده‌ی کلاس LoanApplicationProcessor، در صورت نال بودن وابستگی‌های دریافتی، یک استثناء صادر می‌شود. بنابراین ذکر آن‌ها الزامی است:
        public LoanApplicationProcessor(
            IIdentityVerifier identityVerifier,
            ICreditScorer creditScorer)
        {
            _identityVerifier = identityVerifier ?? throw new ArgumentNullException(nameof(identityVerifier));
            _creditScorer = creditScorer ?? throw new ArgumentNullException(nameof(creditScorer));
        }


نصب کتابخانه‌ی Moq جهت برآورده کردن وابستگی‌های کلاس LoanApplicationProcessor

در این آزمایش چون وجود وابستگی‌های در سازنده‌ی کلاس، برای ما اهمیتی ندارند و همچنین ذکر آن‌ها نیز الزامی است، می‌خواهیم توسط کتابخانه‌ی Moq، دو نمونه‌ی تقلیدی از آن‌ها را تهیه کرده (همان dummies که پیشتر معرفی شدند) و جهت برآورده کردن بررسی صورت گرفته‌ی در سازنده‌ی کلاس LoanApplicationProcessor، آن‌ها را ارائه کنیم.
کتابخانه‌ی بسیار معروف Moq، با پروژه‌های مبتنی بر NETFramework 4.5. و همچنین NETStandard 2.0. به بعد سازگار است و برای نصب آن، می‌توان یکی از دو دستور زیر را صادر کرد:
> dotnet add package Moq
> Install-Package Moq

اما چرا کتابخانه‌ی Moq؟
کتابخانه‌ی Moq این اهداف را دنبال می‌کند: ساده‌است، به شدت کاربردی‌است و همچنین strongly typed است. این کتابخانه سورس باز بوده و تعداد بار دانلود بسته‌ی نیوگت آن میلیونی است.


پس از نصب آن، اولین آزمایشی را که نوشتیم، به صورت زیر اصلاح می‌کنیم:
using Loans.Entities;
using Loans.Services;
using Loans.Services.Contracts;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;

namespace Loans.Tests
{
    [TestClass]
    public class LoanApplicationProcessorShould
    {
        [TestMethod]
        public void DeclineLowSalary()
        {
            var product = new LoanProduct {Id = 99, ProductName = "Loan", InterestRate = 5.25m};
            var amount = new LoanAmount {CurrencyCode = "Rial", Principal = 2_000_000_0};
            var applicant =
                new Applicant {Id = 1, Name = "User 1", Age = 25, Address = "This place", Salary = 1_100_000_0};
            var application = new LoanApplication {Id = 42, Product = product, Amount = amount, Applicant = applicant};

            var mockIdentityVerifier = new Mock<IIdentityVerifier>();
            var mockCreditScorer = new Mock<ICreditScorer>();

            var processor = new LoanApplicationProcessor(mockIdentityVerifier.Object, mockCreditScorer.Object);
            processor.Process(application);

            Assert.IsFalse(application.IsAccepted);
        }
    }
}
در اینجا بجای ارسال null به سازنده‌ی کلاس LoanApplicationProcessor، جهت برآورده کردن مقدار پیش‌فرض پارامترهای آن و کامپایل شدن برنامه، نمونه‌های تقلیدی دو وابستگی مورد نیاز آن‌را تهیه و به آن ارسال کرده‌ایم.
کار با ذکر new Mock شروع شده و آرگومان جنریک آن‌را از نوع وابستگی‌هایی که نیاز داریم، مقدار دهی می‌کنیم. سپس خاصیت Object آن، امکان دسترسی به این شیء تقلید شده را میسر می‌کند.
اکنون اگر مجددا این آزمون واحد را اجرا کنیم، مشاهده خواهیم کرد که بجای صدور استثناء، با موفقیت به پایان رسیده‌است:



گاهی از اوقات جایگزین کردن یک وابستگی null با نمونه‌ی Mock آن کافی نیست

در مثالی که بررسی کردیم، اشیاء mock، کار برآورده کردن نیازهای ابتدایی آزمایش را انجام داده و سبب اجرای موفقیت آمیز آن شدند؛ اما همیشه اینطور نیست:
using Loans.Entities;
using Loans.Services;
using Loans.Services.Contracts;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;

namespace Loans.Tests
{
    [TestClass]
    public class LoanApplicationProcessorShould
    {        
        [TestMethod]
        public void Accept()
        {
            var product = new LoanProduct {Id = 99, ProductName = "Loan", InterestRate = 5.25m};
            var amount = new LoanAmount {CurrencyCode = "Rial", Principal = 2_000_000_0};
            var applicant =
                new Applicant {Id = 1, Name = "User 1", Age = 25, Address = "This place", Salary = 1_500_000_0};
            var application = new LoanApplication {Id = 42, Product = product, Amount = amount, Applicant = applicant};

            var mockIdentityVerifier = new Mock<IIdentityVerifier>();
            var mockCreditScorer = new Mock<ICreditScorer>();

            var processor = new LoanApplicationProcessor(mockIdentityVerifier.Object, mockCreditScorer.Object);
            processor.Process(application);

            Assert.IsTrue(application.IsAccepted);
        }
    }
}
تفاوت این آزمایش جدید با قبلی، در دو مورد است: مقدار Salary به MinimumSalary تنظیم شده‌است و در آخر Assert.IsTrue را داریم.
اگر این آزمایش را اجرا کنیم، با شکست مواجه خواهد شد. علت اینجا است که هرچند در حال استفاده‌ی از دو mock object به عنوان وابستگی‌های مورد نیاز هستیم، اما تنظیمات خاصی را بر روی آن‌ها انجام نداده‌ایم و به همین جهت خروجی مناسبی را در اختیار LoanApplicationProcessor قرار نمی‌دهند. برای مثال مرحله‌ی بعدی بررسی اعتبار شخص در کلاس LoanApplicationProcessor، فراخوانی سرویس identityVerifier و متد Validate آن است که خروجی آن بر اساس کدهای فعلی، همیشه false است:
_identityVerifier.Initialize();
var isValidIdentity = _identityVerifier.Validate(
    application.Applicant.Name, application.Applicant.Age, application.Applicant.Address);
در قسمت بعدی، کار تنظیم اشیاء mock را انجام خواهیم داد.

کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید: MoqSeries-01.zip
مطالب
کار با اسناد در RavenDb 4، ثبت و ویرایش
اگر تا بحال با بانک‌های NoSql کار کرده و لذت برده‌اید، به شما پیشنهاد میکنم حتما RavenDb را هم امتحان کنید، تا لذت استفاده از NoSql را چندین برابر حس کنید! RavenDb یک بانک اطلاعاتی NoSql از نوع DocumentStore است که به‌صورت متن باز توسعه داده می‌شود و مخزن کد آن در Github موجود است. از ویژگی‌های بارز RavenDb نسبت به سایر DocumentStoreها، Transactional بودن میباشد و در نسخه‌ی 4 بصورت کامل از Net Core. پشتیبانی میکند. برای آشنایی بیشتر با NoSql میتوانید از مقالات موجود در گروه NoSql استفاده کنید و برای آشنایی با RavenDb از دوره ای که در سایت وجود دارد استفاده نمایید(دوره مربوط به نسخه‌ی 3.5 می‌باشد).
از بارز‌ترین ویژگی‌های NoSqlها توانایی آن‌ها در ذخیره‌ی اطلاعات، بدون توجه به اسکیمای آن هاست؛ پس هر نوع مدلی که ما برای ذخیره اطلاعات نرم افزار تعریف میکنیم، فقط برای درک بهتر ما هست و بس!

با این مقدمه مدل‌های زیر را برای شروع کار داریم:
Public Class User
{
        public string Id { get; set; }
        public string PhoneNumber { get; set; }
        public Dictionary<string, App> Apps { get; set; }
}
public class App
{
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public string UserName { get; set; }
        public List<string> Roles { get; set; }
        public List<String> Messages { get; set; }
        public String AdressId { get; set; }
        public bool IsActive { get; set; } = true;
        [JsonIgnore]
        public string DisplayName => $"{FirstName} {LastName}";
}

در این مدل، هر کاربر با یک شماره تماس میتواند در چندین برنامه ثبت شود و اطلاعات او در هر برنامه هم میتواند متفاوت باشد.
برای اتصال به RavenDb، به DocumentStore و برای ارسال درخواست‌ها به سمت سرور، به DocumentSession نیاز داریم. نمونه سازی DocumentStore هزینه‌بر بوده و باید در طی اجرای نرم افزار فقط یکبار(Singleton) نمونه سازی شود. DocumentSession بسیار سبک بوده و باید به ازای هر درخواست که به سمت سرور RavenDb ارسال میگردد یک بار نمونه سازی شده و بعد از آن نابود شود. پس برای استفاده در ASP.NET Core به این پیاده سازی در Startup میرسیم:
services.AddSingleton<IDocumentStore>(serviceProvider =>
{
      var store = new DocumentStore()
      {
            Urls = new[] { "http://192.168.1.10:8080" },
            Database = "AccountingSystem",
      }.Initialize();
      return store;
});

services.AddScoped<IAsyncDocumentSession>(serviceProvider =>
{
      var store = serviceProvider.GetRequiredService<IDocumentStore>();
      return store.OpenAsyncSession();
});

حال در تمام بخش‌های نرم افزار می‌توانیم DocumentSession استفاده کنیم.
برای ذخیره سازی مدل در RavenDb از کد زیر استفاده می‌کنیم:
var user = new User
{
      PhoneNumber = user.PhoneNumber
};
user.Apps.Add(appCode, new ActiveApp
{
       FirstName = "عبدالصالح",
       LastName = "کاشانی",
       UserName = abdossaleh,
       IsActive = true,
       RolesId = new List<string>{"Admin"}
});
await _documentSession.StoreAsync(user);
await _documentSession.SaveChangesAsync()

این ساده‌ترین کاری هست که میتوانیم انجام دهیم. بلافاصله بعد از استفاده از متد StoreAsync و بدون رفت و برگشتی به سرور، ویژگی Id برای user مقداردهی می‌شود و توضیح این رفتار هم پیشتر گفته شده است. با فراخوانی متد SaveChangesAsync تغییرات اتفاق افتاده در DocumentSession برای ذخیره سازی به سمت سرور ارسال می‌شوند. بله! الگوی Repository و UnitOfWork.
حال برای دریافت همین مدل، در صورتیکه Id آن را در اختیار داشته باشیم، از متد LoadAsync استفاده میکنیم.
var user = await _documentSession.LoadAsync<User>("Users/131-A");
با لود شدن کاربر، این Entity تحت نظر قرار میگیرد و اگر تغییری در هر کدام از ویژگی‌های آن صورت گیرد و متد SaveChangesAsync فراخوانی شود، کل مدل برای به‌روزرسانی به سمت سرور ارسال میشود. کل مدل و این به معنای بار اضافی در شبکه هست. البته در مدل‌های کوچک بهتر است که همین کار را انجام دهیم. ولی در اینجا به عمد مدلی را انتخاب کرده‌ایم که اطلاعات زیادی را در خود نگهداری میکند و ارسال تمام آن به ازای یک تغییر کوچک به صرفه نیست! خوشبختانه RavenDb برای حل این مشکل امکانات جالبی را در اختیار ما قرار داده که در ادامه آن‌ها را بررسی می‌کنیم.

Patching
به معنای تغییر دادن قسمتی از سند که شامل تغییر مقادیر، اضافه یا حذف یک ویژگی، ایجاد تغییرات در لیست و ... می‌باشد. با استفاده از متدهای Patch سند، میتوانیم بدون نیاز به لود سند و تغییر و ذخیره آن، قسمتی از سند را ویرایش کنیم. عملیات Patch، سمت سرور اجرا می‌شوند. برای مثال برای تغییر شماره تماس، از متد زیر استفاده می‌کنیم:
_documentSession.Advanced.Patch<User, string>("Users/131-A",
      u => u.PhoneNumber
      , "09131110000");
که مدلی را که میخواهیم تغییر دهیم، به همراه نوع ویژگی مورد نظر برای تغییر، دریافت میکند و بعد از آن، به ترتیب Id سند مورد نظر، ویژگی مورد نظر برای اعمال تغییر و مقدار را میگیرد و با فراخوانی SaveChangesAsync این تغییرات اعمال می‌شوند. نکته‌ای که باید توجه کنید این است که اگر مدلی را لود کردید و در فیلدهای آن تغییری ایجاد نموده‌اید، دیگر نمیتوانید از Patch یا Defer (توضیح داده میشود) استفاده کنید. به عبارت دیگر در هر درخواست یا باید از سیستم Tracking خود RavenDb استفاده کنید و یا از Patching!
برای اضافه کردن یک آیتم به لیست،  از Patch بصورت زیر استفاده میکنیم:
_documentSession.Advanced.Patch<User, string>("Users/131-A",
      u => u.Apps["59"].RolesId
      , r => r.Add("Admin"));

برای اضافه کردن مقداری به یک مقدار عددی در RavenDb، از متد Increment بصورت زیر استفاده میکنیم:
 _documentSession.Advanced.Increment<User, int>("Users/131-A", x => x.TestProp, 10);
متد Patch برای کارهای ساده‌ی اینچنین بسیار کاربردی می‌باشد؛ ولی برای کارهای پیشرفته‌تر کارآیی ندارد. به همین دلیل متد Defer در کنار آن معرفی شده‌است که فوق العاده کاربردی ولی اصطلاحا non-typed است و تحت نظارت Compiler نیست. برای مثال اضافه کردن یک مقدار به Dictionary ما، از طریق Patch امکان ندارد. اما اینکار با استفاده از متد Defer و کدهای JavaScript به‌سادگی زیر می‌باشد:
_documentSession.Advanced.Defer(new PatchCommandData("Users/131-A", null,
                              new PatchRequest()
                              {
                                    Script = $@"this.Apps[args.appCode] = args.app",
                                    Values =
                                         {
                                              {"appCode", appCode},
                                              {"app", new ActiveApp
                                                   {
                                                        FirstName = "عبدالصالح",
                                                        LastName = "کاشانی",
                                                        UserName = abdossaleh,
                                                        RolesId = new List<string>{"Admin"}
                                                    }
                                              }
                                          }
                              }, null));
متد Defer شناسه‌ی سند مورد نظر را گرفته و اسکریپت ما را با آرگومان‌های ارسالی، بر روی سند اعمال میکند. Defer دسترسی کاملی را به ما برای تغییر در سند میدهد. برای نمونه میتوانیم آیتمی را به مکان خاصی از لیست اضافه کنیم (برای کوتاه‌تر شدن اسکریپت‌ها فقط بخش Script و Value را ذکر میکنم):
Script = "this.Apps[args.app].Roles.splice(args.index,0,args.role)",
Values =
        {
            {
                "index": 1 // مکانی که میخواهیم عملیات انجام شود
                "app", 59
                "role", "User"
            }
        }
this در اینجا به سند جاری اشاره میکند.
از همین روش میتوانیم برای ویرایش کردن یک آیتم هم استفاده کنیم. برای مثال اگر مقدار 0 را در متد splice به یک تغییر دهیم، عملیات ویرایش صورت می‌گیرد (در واقع حذف آیتم در مکان index و درج آیتم جدید در همان مکان):
splice(args.index,1,args.role)
و برای حذف تمام آیتم‌های لیست جز یک آیتم خاص، از کد زیر استفاده میکنیم:
Script = @"this.Roles= this.Apps[args.app].Roles.filter(role=> role != args.role);",
        Values =
        {
            {"app", 59}
            {"role", "User"}
        }
همانطور که مشاهده می‌کنید به راحتی می‌توانیم کدهای جاوا اسکریپتی خود را در Defer استفاده کنیم. اما این قدرت زیاد، امکان اشتباه در کدهای ما را زیاد میکند چرا که تحت کنترل کامپایلر نیست.
نظرات مطالب
معرفی واژه‌ی کلیدی جدید required در C# 11
یک نکته‌ی تکمیلی: بررسی صحت ساختار JSON دریافتی توسط واژه‌ی کلیدی required در زمان اجرای برنامه

فرض کنید یک Dto را به صورت زیر تعریف کرده‌اید و توسط یک API قرار است این اطلاعات را دریافت کنید:
public class OldCarDto
{
    public string Brand { get; set; }
    public string Model { get; set; }
    public uint Horsepower { get; set; }
}
در این حالت هیچ کنترلی بر روی الزام به تکمیل تمام فیلدهای مورد نیاز وجود ندارد. برای مثال اگر JSON دریافتی به صورت زیر باشد:
var json = """
[
  {
    "brand": "Ferrari",
    "horsePower": 651
  },
  {
    "model": "F50",
    "horsePower": 512
  }
]
""";
که در لیست اشیاء آن، در یکی Model و در دیگری Brand وجود ندارد، عملیات Deserialize آن به صورت زیر، بدون هیچ خطایی به پایان می‌رسد:
var options = new JsonSerializerOptions
              {
                  PropertyNameCaseInsensitive = true,
                  PropertyNamingPolicy = JsonNamingPolicy.CamelCase
              };

var oldResults =  JsonSerializer.Deserialize<List<OldCarDto>>(json, options);
که البته در آن خواصی که وجود نداشته‌اند، با null مقدار دهی خواهند شد.

اگر اینبار تعریف Dto را به صورت زیر و بر اساس واژه‌ی کلیدی جدید required اصلاح کنیم:
public class NewCarDto
{
    public required string Brand { get; init; }
    public required string Model { get; init; }
    public required uint Horsepower { get; init; }
}
اینبار همان عملیات Deserialize زیر:
var newResults =  JsonSerializer.Deserialize<List<NewCarDto>>(json, options);
با این استثنای در زمان اجرا خاتمه خواهد یافت:
System.Text.Json.JsonException: JSON deserialization for type 'NewCarDto'
was missing required properties, including the following: model
که عنوان می‌کند به علت عدم قید خاصیت الزامی model، امکان deserialization وجود ندارد.
نظرات مطالب
ASP.NET MVC #13
سلام آقای نصیری خسته نباشید
من الان واسه ثبت کاربر توی دیتابیس یه کلاس دارم به اسم CustomProfile که تمامی خصوصیت‌های یک کاربر رو توی یه شیئ از این کلاس میریزم و بعد توی کدهای لایه مدل، این شیئ رو توسط کلاس Membership و ProfileBase خود دات نت توی پایگاه داده میریزیم. الان منظور شما اینه که من باید برای ویرایش یک کلاس CustomProfile دیگه با همون مشخصات بسازم و متد اعتبار سنجی برای Username اون رو یه متد دیگه قرار بدم ؟ ببینید کدهای من اینه:
public class CustomProfile
    {
        [Required(ErrorMessage="*")]
        [StringLength(50)]
        [System.Web.Mvc.Remote(action: "CheckUserName",
                                       controller: "User",
                                       HttpMethod = "POST",
                                       ErrorMessage = "این نام کاربری وجود دارد")]
        public string Username{ get; set; }
        [Required(ErrorMessage = "*")]
        public string Password{ get; set; }
        [Required(ErrorMessage = "*")]
        public string FirstName{ get; set; }
        [Required(ErrorMessage = "*")]
        public string LastName{ get; set; }
        
        [Required(ErrorMessage = "*")]
        [StringLength(10, ErrorMessage = "کدملی باید 10 رقم باشد")]
        public string NationalCode { get; set; }
        [Required(ErrorMessage = "*")]
        public string Address { get; set; }

        [RegularExpression(@"\w+([-+.']\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*",
                           ErrorMessage = "آدرس ایمیل معتبر نیست")]
        [System.Web.Mvc.Remote(action: "CheckEmail",
                                       controller: "User",
                                       AdditionalFields = "Username",
                                       HttpMethod = "POST",
                                       ErrorMessage = "این ایمیل وجود دارد")]
        public string Email{ get; set; }

        [StringLength(11, ErrorMessage = "تلفن باید 11 رقم باشد")]
        public string PhoneNo{ get; set; }

        [StringLength(11, ErrorMessage = "موبایل باید 11 رقم باشد")]
        public string MobileNo{ get; set; }
        
        
        public string[] Roles{ get; set; }
        
        public string LastActivityDate{ get; set; }
        public string LastLoginDate { get; set; }
        public string CreationDate { get; set; }
        public bool IsLockedOut{ get; set; }
    }
این کد کلاسم بود. من توی View هام اومدم و این کلاس را به عنوان ViewModel خودم قرار دادم. و وقتی فرم Create و یا Edit رو Submit میکنم متد اعتبار سنجی من وارد عمل میشه
اینم کدViewها:
@model Sama.Models.CustomProfile
@{
    ViewBag.Title = "Create";
    Layout = "~/Views/Shared/_Layout.cshtml";
    
}
<script src="@Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script>
@using (Html.BeginForm(actionName: "Create", controllerName: "User"))
    {
        @Html.ValidationSummary(true)
     
        <fieldset>
            <legend>کاربر جدید</legend>
            <div>
                <table>
                    <tr>
                        <td>
                            <div>
                                @Html.LabelFor(model => model.Username, "نام کاربری")
                            </div>
                        </td>
                        <td>
                            <div>
                                @Html.EditorFor(model => model.Username)
                                @Html.ValidationMessageFor(model => model.Username)
                            </div>
                        </td>
                    </tr>
                    <tr>
                        <td>
                            <div>
                                @Html.LabelFor(model => model.Password, "کلمه عبور")
                            </div>
                        </td>
                        <td>
                            <div>
                                @Html.PasswordFor(model => model.Password)
                                @Html.ValidationMessageFor(model => model.Password)
                            </div>
                        </td>
                    </tr>
                    <tr>
                        <td>
                            <div>
                                @Html.LabelFor(model => model.FirstName, "نام")
                            </div>
                        </td>
                        <td>
                            <div>
                                @Html.EditorFor(model => model.FirstName)
                                @Html.ValidationMessageFor(model => model.FirstName)
                            </div>
                        </td>
                    </tr>
                    <tr>
                        <td>
                            <div>
                                @Html.LabelFor(model => model.LastName, "نام خانوادگی")
                            </div>
                        </td>
                        <td>
                            <div>
                                @Html.EditorFor(model => model.LastName)
                                @Html.ValidationMessageFor(model => model.LastName)
                            </div>
                        </td>
                    </tr>
                    <tr>
                        <td>
                            <div>
                                @Html.LabelFor(model => model.NationalCode, "کد ملی")
                            </div>
                        </td>
                        <td>
                            <div>
                                @Html.EditorFor(model => model.NationalCode)
                                @Html.ValidationMessageFor(model => model.NationalCode)
                            </div>
                        </td>
                    </tr>
                    <tr>
                        <td>
                            <div>
                                @Html.LabelFor(model => model.Email, "ایمیل")
                            </div>
                        </td>
                        <td>
                            <div>
                                @Html.EditorFor(model => model.Email)
                                @Html.ValidationMessageFor(model => model.Email)
                            </div>
                        </td>
                    </tr>
                    <tr>
                        <td>
                            <div>
                                @Html.LabelFor(model => model.PhoneNo, "تلفن")
                            </div>
                        </td>
                        <td>
                            <div>
                                @Html.EditorFor(model => model.PhoneNo)
                                @Html.ValidationMessageFor(model => model.PhoneNo)
                            </div>
                        </td>
                    </tr>
                    <tr>
                        <td>
                            <div>
                                @Html.LabelFor(model => model.MobileNo, "موبایل")
                            </div>
                        </td>
                        <td>
                            <div>
                                @Html.EditorFor(model => model.MobileNo)
                                @Html.ValidationMessageFor(model => model.MobileNo)
                            </div>
                        </td>
                    </tr>
                    <tr>
                        <td>
                            <div>
                                @Html.LabelFor(model => model.Address, "آدرس")
                            </div>
                        </td>
                        <td>
                            <div>
                                @Html.TextAreaFor(model => model.Address, 5, 30, null)
                                @Html.ValidationMessageFor(model => model.Address)
                            </div>
                        </td>
                    </tr>
                    <tr>
                        <td>
                            نقش‌ها </td>
                        <td>
                            @Helper.CheckBoxList("Roles", (List<SelectListItem>)ViewBag.Roles)
                        </td>
                    </tr>
                    <tr>
                        <td>
                        </td>
                        <td>
                            <input type="submit" value="ذخیره" class="btn btn-primary" />
                        </td>
                    </tr>
                </table>
            </div>
        </fieldset>
    
    }
این View برای Create بود و برای Edit هم مشابه همینه
حالا اگر من درست متوجه شده باشم باید برای Edit ، از یک ViewModel دیگه مثلا CustomProfileEdit استفاده کنم و اونجا متد اعتبار سنجی دیگه ای تعریف کنم. منظور شما همین بود ؟
مطالب
پیاده سازی یک MediaTypeFormatter برای پشتیبانی از MultiPart/form-data در Web API

Media Type یا MIME Type نشان دهنده فرمت یک مجموعه داده است. در HTTP، مدیا تایپ بیان کننده فرمت message body یک درخواست / پاسخ است و به دریافت کننده اعلام می‌کند که چطور باید پیام را بخواند. محل استاندارد تعیین Mime Type در هدر Content-Type است. درخواست کننده می‌تواند با استفاده از هدر Accept لیستی از MimeType‌های قابل قبول را به عنوان پاسخ، به سرور اعلام کند.

Asp.net Web API  از MimeType برای تعیین نحوه serialize یا deserialize کردن محتوای دریافتی / ارسالی استفاده می‌کند


MediaTypeFormatter

Web API برای خواندن/درج پیام در بدنه درخواست/پاسخ از MediaTypeFormmater‌‌ها استفاده می‌کند. اینها کلاس‌هایی هستند که نحوه‌ی Serialize کردن و deserialize کردن اطلاعات به فرمت‌های خاص را تعیین می‌کنند. Web API به صورت توکار دارای formatter هایی برای نوع‌های XML ، JSON، BSON و Form-UrlEncoded می‌باشد. همه این‌ها کلاس پایه MediaTypeFormatter را پیاده سازی می‌کنند. 


مسئله

یک پروژه Web API بسازید و view model زیر را در آن تعریف کنید:

public class NewProduct
    {
        [Required]
        public string Name { get; set; }

        public double Price { get; set; }

        public byte[] Pic { get; set; }
    }

همانطور که می‌بینید یک فیلد از نوع byte[] برای تصویر محصول در نظر گرفته شده است.

حالا یک کنترلر API  ساخته و اکشنی برای دریافت اطلاعات محصول جدید از کاربر می‌نویسیم :
public class ProductsController : ApiController
    {
        [HttpPost]
        public HttpResponseMessage PostProduct(NewProduct model)
        {
            if (ModelState.IsValid)
            {
                //  ثبت محصول 

                return new HttpResponseMessage(HttpStatusCode.Created);
            }

            return Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState);
        }

    }

و یک صفحه html به نام index.html که حاوی یک فرم برای ارسال اطلاعات باشد :

<!DOCTYPE html>
<html>
<head>
    <title></title>
</head>
<body>
    <h1>ساخت MediaTypeFormatter برای Multipart/form-data</h1>
    <h2>محصول جدید</h2>

    <form id="newProduct" method="post" action="/api/products" enctype="multipart/form-data">
        <div>
            <label for="name">نام محصول : </label>
            <input type="text" id="name" name="name" />
        </div>
        <div>
            <label for="price">قیمت : </label>
            <input type="number" id="price"  name="price" />
        </div>
        <div>
            <label for="pic">تصویر : </label>
            <input type="file" id="pic" name="pic" />
        </div>
        <div>
            <button type="submit">ثبت</button>
        </div>
    </form>
</body>
</html>

زمانی که فرم حاوی فایلی برای آپلود باشد مشخصه encType باید برابر با Multipart/form-data مقداردهی شود تا اطلاعات فایل به درستی کد شوند. در زمان ارسال فرم Content-type درخواست برابر با Multipart/form-data و فرمت اطلاعات درخواست ارسالی به شکل زیر خواهد بود :


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

- Content-Disposition که نام فیلد و نام فایل را شامل می‌شود .

- content-type که mime type مخصوص آن بخش از داده‌ها را مشخص می‌کند.

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

خطای روی داده اعلام می‌کند که Web API فاقد MediaTypeFormatter برای خواندن اطلاعات ارسال شده با فرمتMultiPart/Form-data  است.  Web API برای خواندن و بایند کردن پارامترهای complex Type از درون بدنه پیام یک درخواست از MediaTypeFormatter استفاده می‌کند و همانطور که گفته شد Web API فاقد Formatter توکار برای deserialize کردن داده‌های با فرمت Multipart/form-data است.

راه حل‌ها :

روشی که در سایت asp.net برای آپلود فایل در web api استفاده شده، عدم استفاده از پارامترها و خواندن محتوای Request در درون کنترلر است. که به طبع در صورتی که بخواهیم کنترلرهای تمیز و کوچکی داشته باشیم روش مناسبی نیست. از طرفی امتیاز parameter binding و modelstate را هم از دست خواهیم داد.

روش دیگری که می‌خواهیم در اینجا پیاده سازی کنیم ساختن یک MediaTypeFormatter برای خواندن فرمت Multipart/form-data است. با این روش کد موردنیاز کپسوله شده و امکان استفاده از binding و modelstate را خواهیم داشت.

برای ساختن یک MediaTypeFormatter یکی از 2 کلاس MediaTypeFormatter یا  BufferedMediaTypeFormatter  را باید پیاده سازی کنیم . تفاوت این دو در این است که BufferedMediaTypeFormatter برخلاف MediaTypeFormatter از متدهای synchronous استفاده می‌کند.

 
پیاده سازی :

یک کلاس به نام MultiPartMediaTypeFormatter می‌سازیم و کلاس MediaTypeFormatter را به عنوان کلاس پایه آن قرار می‌دهیم .

public class MultiPartMediaTypeFormatter : MediaTypeFormatter
{
    ...
}

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

        public MultiPartMediaTypeFormatter()
        {
            SupportedMediaTypes.Add(new MediaTypeHeaderValue("multipart/form-data"));
        }
در اینجا Multipart/form-data را به عنوان تنها نوع مجاز تعریف کرده ایم.

سپس با پیاده سازی توابع CanReadType و CanWriteType مربوط به کلاس MediaTypeFormatter مشخص می‌کنیم که چه مدل‌هایی را می‌توان توسط این کلاس  serialize / deserialize کرد. در اینجا چون می‌خواهیم این کلاس محدود به یک مدل خاص نباشد، از یک اینترفیس برای شناسایی کلاس‌های مجاز استفاده می‌کنیم .

    public interface INeedMultiPartMediaTypeFormatter
    {
    }

و آنرا به کلاس newProduct اضافه می‌کنیم :

public class NewProduct : INeedMultiPartMediaTypeFormatter
{
 ...
}
از آنجا که تنها نیاز به خواندن اطلاعات داریم و قصد نوشتن نداریم، در متد CanWriteType مقدار false را برمی گردانیم .
        public override bool CanReadType(Type type)
        {
            return typeof(INeedMultiPartMediaTypeFormatter).IsAssignableFrom(type);
        }

        public override bool CanWriteType(Type type)
        {
            return false;
        }

و اما تابع ReadFromStreamAsync که کار خواندن محتوای ارسال شده و بایند کردن آنها به پارامترها را برعهده دارد 

public async override Task<object> ReadFromStreamAsync(Type type, Stream stream, HttpContent content, IFormatterLogger formatterLogger)
که در آن پارامتر type مربوط به مدل مشخص شده به عنوان پارامتر اکشن (NewProduct)است و پارامتر content محتوای درخواست را در خود دارد.

ابتدا محتوای ارسال شده را خوانده و اطلاعات فرم را استخراج می‌کنیم و از طرف دیگر با استفاده از کلاس Activator یک نمونه از مدل جاری را ساخته و لیست property‌های آنرا استخراج می‌کنیم.

            MultipartMemoryStreamProvider provider = await content.ReadAsMultipartAsync();
            IEnumerable<HttpContent> formData = provider.Contents.AsEnumerable();

            var modelInstance = Activator.CreateInstance(type);
            IEnumerable<PropertyInfo> properties = type.GetProperties();

سپس در یک حلقه به ترتیب برای هر property متعلق به مدل، در میان اطلاعات فرم جستجو می‌کنیم. برای پیدا کردن اطلاعات متناظر با هر property در هدر Content-Disposition که در بالا توضیح داده شد، به دنبال فیلد همنام با property می‌گردیم .

            foreach (PropertyInfo prop in properties)
            {
                var propName = prop.Name.ToLower();
                var propType = prop.PropertyType;

                var data = formData.FirstOrDefault(d => d.Headers.ContentDisposition.Name.ToLower().Contains(propName));
در صورتی که فیلدی وجود داشته باشد کار را ادامه می‌دهیم .

 گفتیم که هر فیلد یک هدر، Content-Type هم می‌تواند داشته باشد. این هدر به صورت پیش فرض معادل text/plain است و برای فیلدهای عادی قرار داده نمی‌شود . در این مثال چون فقط یک فیلد غیر رشته ای داریم فرض را بر این گرفته ایم که در صورت وجود Content-Type، فیلد مربوط به تصویر است. در صورتیکهContentType  وجود داشته باشد، محتوای فیلد را به شکل Stream خوانده به byte[] تبدیل و با استفاده از متد SetValue در property مربوطه قرار می‌دهیم.

                if (data != null)
                {
                    if (data.Headers.ContentType != null)
                    {
                        using (var fileStream = await data.ReadAsStreamAsync())
                        {
                            using (MemoryStream ms = new MemoryStream())
                            {
                                fileStream.CopyTo(ms);
                                prop.SetValue(modelInstance, ms.ToArray());
                            }                            
                        }
                    }

در صورتی که Content-Type غایب باشد بدین معنی است که محتوای فیلد از نوع رشته است ( عدد ، تاریخ ، guid ، رشته ) و باید به نوع مناسب تبدیل شود. ابتدا آن را به صورت یک رشته می‌خوانیم و با استفاده از Convert.ChangeType آنرا به نوع مناسب تبدیل می‌کنیم و در property متناظر قرار می‌دهیم .

                if (data != null)
                {
                    if (data.Headers.ContentType != null)
                    {
                        //...
                    }
                    else
                    {
                        string rawVal = await data.ReadAsStringAsync();
                        object val = Convert.ChangeType(rawVal, propType);

                        prop.SetValue(modelInstance, val);
                    }
                }
و در نهایت نمونه ساخته شده از مدل را برگشت می‌دهیم.
return modelInstance;
برای فعال کردن این Formatter باید آنرا به لیست formmater‌های web api اضافه بکنیم. فایل WebApiConfig در App_Start را باز کرده و خط زیر را به آن اضافه می‌کنیم:
config.Formatters.Add(new MultiPartMediaTypeFormatter());
حال اگر مجددا فرم را به سرور ارسال کنیم، با پیام خطایی، مواجه نشده و عمل binding با موفقیت انجام می‌گیرد.