مطالب
ASP.NET MVC #22

تهیه سایت‌های چند زبانه و بومی سازی نمایش اطلاعات در ASP.NET MVC

زمانیکه دات نت فریم ورک نیاز به انجام اعمال حساس به مسایل بومی را داشته باشد،‌ ابتدا به مقادیر تنظیم شده دو خاصیت زیر دقت می‌کند:
الف) System.Threading.Thread.CurrentThread.CurrentCulture
بر این اساس دات نت می‌تواند تشخیص دهد که برای مثال خروجی متد DateTime.Now.ToString در کانادا و آمریکا باید با هم تفاوت داشته باشند. مثلا در آمریکا ابتدا ماه، سپس روز و در آخر سال نمایش داده می‌شود و در کانادا ابتدا سال، بعد ماه و در آخر روز نمایش داده خواهد شد. یا نمونه‌ی دیگری از این دست می‌تواند نحوه نمایش علامت واحد پولی کشورها باشد.
ب) System.Threading.Thread.CurrentThread.CurrentUICulture
مقدار CurrentUICulture بر روی بارگذاری فایل‌های مخصوصی به نام Resource، تاثیر گذار است.

این خواص را یا به صورت دستی می‌توان تنظیم کرد و یا ASP.NET، این اطلاعات را از هدر Accept-Language دریافتی از مرورگر کاربر به صورت خودکار مقدار دهی می‌کند. البته برای این منظور نیاز است یک سطر زیر را به فایل وب کانفیگ برنامه اضافه کرد:

<system.web>
<globalization culture="auto" uiCulture="auto" />

یا اگر نیاز باشد تا برنامه را ملزم به نمایش اطلاعات Resource مرتبط با فرهنگ بومی خاصی کرد نیز می‌توان در همین قسمت مقادیر culture و uiCulture را دستی تنظیم نمود و یا اگر همانند برنامه‌هایی که چند لینک را بالای صفحه نمایش می‌دهند که برای مثال به نگارش‌های فارسی/عربی/انگلیسی اشاره می‌کند، اینکار را با کد نویسی نیز می‌توان انجام داد:

System.Threading.Thread.CurrentThread.CurrentCulture =
System.Globalization.CultureInfo.CreateSpecificCulture("fa");


جهت آزمایش این مطلب، ابتدا تنظیم globalization فوق را به فایل وب کانفیگ برنامه اضافه کنید. سپس به مسیر زیر در IE مراجعه کنید:

IE -> Tools -> Intenet options -> Genarl tab -> Languages

در اینجا می‌توان هدر Accept-Language را مقدار دهی کرد. برای نمونه اگر مقدار زبان پیش فرض را به فرانسه تنظیم کنیم (به عنوان اولین زبان تعریف شده در لیست) و سپس سعی در نمایش مقدار decimal زیر را داشته باشیم:

string.Format("{0:C}", 10.5M)

اگر زبان پیش فرض، انگلیسی آمریکایی باشد، $ نمایش داده خواهد شد و اگر زبان به فرانسه تنظیم شود، یورو در کنار عدد مبلغ نمایش داده می‌شود.
تا اینجا تنها با تنظیم culture=auto به این نتیجه رسیده‌ایم. اما سایر قسمت‌های صفحه چطور؟ برای مثال برچسب‌های نمایش داده شده را چگونه می‌توان به صورت خودکار بر اساس Accept-Language مرجح کاربر تنظیم کرد؟ خوشبختانه در دات نت، زیر ساخت مدیریت برنامه‌های چند زبانه به صورت توکار وجود دارد که در ادامه به بررسی آن خواهیم پرداخت.


آشنایی با ساختار فایل‌های Resource


فایل‌های Resource یا منبع، در حقیقت فایل‌هایی هستند مبتنی بر XML با پسوند resx و هدف آن‌ها ذخیره سازی رشته‌های متناظر با فرهنگ‌های مختلف می‌باشد و برای استفاده از آن‌ها حداقل یک فایل منبع پیش فرض باید تعریف شود. برای نمونه فایل mydata.resx را در نظر بگیرید. برای ایجاد فایل منبع اسپانیایی متناظر، باید فایلی را به نام mydata.es.resx تولید کرد. البته نوع فرهنگ مورد استفاده را کاملتر نیز می‌توان ذکر کرد برای مثال mydata.es-mex.resx جهت فرهنگ اسپانیایی مکزیکی بکارگرفته خواهد شد، یا mydata.fr-ca.resx به فرانسوی کانادایی اشاره می‌کند. سپس مدیریت منابع دات نت فریم ورک بر اساس مقدار CurrentUICulture جاری، اطلاعات فایل متناظری را بارگذاری خواهد کرد. اگر فایل متناظری وجود نداشت، از اطلاعات همان فایل پیش فرض استفاده می‌گردد.
حین تهیه برنامه‌ها نیازی نیست تا مستقیما با فایل‌های XML منابع کار کرد. زمانیکه اولین فایل منبع تولید می‌شود، به همراه آن یک فایل cs یا vb نیز ایجاد خواهد شد که امکان دسترسی به کلیدهای تعریف شده در فایل‌های XML را به صورت strongly typed میسر می‌کند. این فایل‌های خودکار، تنها برای فایل پیش فرض mydata.resx تولید می‌شوند،‌از این جهت که تعاریف اطلاعات سایر فرهنگ‌های متناظر نیز باید با همان کلیدهای فایل پیش فرض آغاز شوند. تنها «مقادیر» کلیدهای تعریف شده در کلاس‌های منبع متفاوت هستند.
اگر به خواص فایل‌های resx در VS.NET دقت کنیم، نوع Build action آن‌ها به embedded resource تنظیم شده است.


مثالی جهت بررسی استفاده از فایل‌های Resource

یک پروژه جدید خالی ASP.NET MVC را آغاز کنید. فایل وب کانفیگ آن‌را ویرایش کرده و تنظیمات globalization ابتدای بحث را به آن اضافه کنید. سپس مدل، کنترلر و View متناظر با متد Index آن‌را با محتوای زیر به پروژه اضافه نمائید:

namespace MvcApplication19.Models
{
public class Employee
{
public int Id { set; get; }
public string Name { set; get; }
}
}

using System.Web.Mvc;
using MvcApplication19.Models;

namespace MvcApplication19.Controllers
{
public class HomeController : Controller
{
public ActionResult Index()
{
var employee = new Employee { Name = "Name 1" };
return View(employee);
}
}
}

@model MvcApplication19.Models.Employee
@{
ViewBag.Title = "Index";
}
<h2>
Index</h2>
<fieldset>
<legend>Employee</legend>
<div class="display-label">
Name
</div>
<div class="display-field">
@Html.DisplayFor(model => model.Name)
</div>
</fieldset>
<fieldset>
<legend>Employee Info</legend>
@Html.DisplayForModel()
</fieldset>

قصد داریم در View فوق بر اساس uiCulture کاربر مراجعه کننده به سایت، برچسب Name را مقدار دهی کنیم. اگر کاربری از ایران مراجعه کند، «نام کارمند» نمایش داده شود و سایر کاربران، «Employee Name» را مشاهده کنند. همچنین این تغییرات باید بر روی متد Html.DisplayForModel نیز تاثیرگذار باشد.
برای این منظور بر روی پوشه Views/Home که محل قرارگیری فایل Index.cshtml فوق است کلیک راست کرده و گزینه Add|New Item را انتخاب کنید. سپس در صفحه ظاهر شده، گزینه «Resources file» را انتخاب کرده و برای مثال نام Index_cshtml.resx را وارد کنید.
به این ترتیب اولین فایل منبع مرتبط با View جاری که فایل پیش فرض نیز می‌باشد ایجاد خواهد شد. این فایل، به همراه فایل Index_cshtml.Designer.cs تولید می‌شود. سپس همین مراحل را طی کنید، اما اینبار نام Index_cshtml.fa.resx را حین افزودن فایل منبع وارد نمائید که برای تعریف اطلاعات بومی ایران مورد استفاده قرار خواهد گرفت. فایل دومی که اضافه شده است، فاقد فایل cs همراه می‌باشد.
اکنون فایل Index_cshtml.resx را در VS.NET باز کنید. از بالای صفحه، به کمک گزینه Access modifier، سطح دسترسی متدهای فایل cs همراه آن‌را به public تغییر دهید. پیش فرض آن internal است که برای کار ما مفید نیست. از این جهت که امکان دسترسی به متدهای استاتیک تعریف شده در فایل خودکار Index_cshtml.Designer.cs را در View های برنامه، نخواهیم داشت. سپس دو جفت «نام-مقدار» را در فایل resx وارد کنید. مثلا نام را Name و مقدار آن‌را «Employee Name» و سپس نام دیگر را NameIsNotRight و مقدار آن‌را «Name is required» وارد نمائید.
در ادامه فایل Index_cshtml.fa.resx را باز کنید. در اینجا نیز دو جفت «نام-مقدار» متناظر با فایل پیش فرض منبع را باید وارد کرد. کلیدها یا نام‌ها یکی است اما قسمت مقدار اینبار باید فارسی وارد شود. مثلا نام را Name و مقدار آن‌را «نام کارمند» وارد نمائید. سپس کلید یا نام NameIsNotRight و مقدار «لطفا نام را وارد نمائید» را تنظیم نمائید.
تا اینجا کار تهیه فایل‌های منبع متناظر با View جاری به پایان می‌رسد.
در ادامه با کمک فایل Index_cshtml.Designer.cs که هربار پس از تغییر فایل resx متناظر آن به صورت خودکار توسط VS.NET تولید و به روز می‌شود، می‌توان به کلیدها یا نام‌هایی که تعریف کرده‌ایم، در قسمت‌های مختلف برنامه دست یافت. برای نمونه تعریف کلید Name در این فایل به نحو زیر است:

namespace MvcApplication19.Views.Home {
public class Index_cshtml {
public static string Name {
get {
return ResourceManager.GetString("Name", resourceCulture);
}
}
}
}

بنابراین برای استفاده از آن در هر View ایی تنها کافی است بنویسیم:

@MvcApplication19.Views.Home.Index_cshtml.Name

به این ترتیب بر اساس تنظیمات محلی کاربر، اطلاعات به صورت خودکار از فایل‌های Index_cshtml.fa.resx فارسی یا فایل پیش فرض Index_cshtml.resx، دریافت می‌گردد.
علاوه بر امکان دسترسی مستقیم به کلیدهای تعریف شده در فایل‌های منبع، امکان استفاده از آن‌ها توسط data annotations نیز میسر است. در این حالت می‌توان مثلا پیغام‌های اعتبار سنجی را بومی کرد یا حین استفاده از متد Html.DisplayForModel، بر روی برچسب نمایش داده شده خودکار، تاثیر گذار بود. برای اینکار باید اندکی مدل برنامه را ویرایش کرد:

using System.ComponentModel.DataAnnotations;

namespace MvcApplication19.Models
{
public class Employee
{
[ScaffoldColumn(false)]
public int Id { set; get; }

[Display(ResourceType = typeof(MvcApplication19.Views.Home.Index_cshtml),
Name = "Name")]
[Required(ErrorMessageResourceType = typeof(MvcApplication19.Views.Home.Index_cshtml),
ErrorMessageResourceName = "NameIsNotRight")]
public string Name { set; get; }
}
}

همانطور که ملاحظه می‌کنید، حین تعریف ویژگی‌های Display یا Required، امکان تعریف نام کلاس متناظر با فایل resx خاصی وجود دارد. به علاوه ErrorMessageResourceName به نام یک کلید در این فایل و یا پارامتر Name ویژگی Display نیز به نام کلیدی در فایل منبع مشخص شده، اشاره می‌کنند. این اطلاعات توسط متدهای Html.DisplayForModel، Html.ValidationMessageFor، Html.LabelFor و امثال آن به صورت خودکار مورد استفاده قرار خواهند گرفت.


نکته‌ای در مورد کش کردن اطلاعات
در این مثال اگر فیلتر OutputCache را بر روی متد Index تعریف کنیم، حتما نیاز است به هدر Accept-Language نیز دقت داشت. در غیراینصورت تمام کاربران، صرفنظر از تنظیمات بومی آن‌ها، یک صفحه را مشاهده خواهند کرد:

[OutputCache(Duration = 60, VaryByHeader = "Accept-Language")]
public ActionResult Index()


نظرات مطالب
شروع به کار با EF Core 1.0 - قسمت 1 - برپایی تنظیمات اولیه
- Collation بانک اطلاعاتی ایجاد شده، بر اساس collation «کل سرور» تنظیم می‌شود. برای مثال بر روی سیستم من Server collation به Persian_100_CI_AS تنظیم شده‌است. بنابراین بانک اطلاعاتی ایجاد شده‌ی در اینجا هم دقیقا همین Collation را دارد (امتحان کردم).
/* Identify SQL Server Collation Settings*/
USE Master
GO
SELECT SERVERPROPERTY('collation') AS SQLServerCollation
GO
 - Collation، ارتباطی به نحوه‌ی ذخیره شدن اطلاعات یونیکد ندارد. بیشتر هدف آن sort صحیح اطلاعات است و همچنین مشخص سازی نحوه‌ی مقایسه‌ی عبارات و حساس بودن به بزرگی و کوچکی حروف. شما در Collation فارسی، هم ی فارسی و هم ی عربی را می‌توانید بدون مشکل ثبت کنید. اگر این Collation وجود نداشت، شبیه به SQLite، در مرتب سازی حروف فارسی، عملیات نهایی بر اساس کد اسکی آن‌ها انجام می‌شد و در این حالت مثلا «پ» را در انتهای لیست مشاهده می‌کردید.
 - اگر مشکل ثبت اطلاعات یونیکد را داشت، شما در بانک اطلاعاتی فقط ????? را مشاهده می‌کردید و نه هیچ چیز دیگری را و نه اینکه قسمتی درست ثبت شود و قسمتی نادرست.
 - ویژوال استودیو عموما بر مبنای «تنظیمات محلی سیستم عامل شما» فایل‌ها را ذخیره می‌کند. اگر می‌خواهید این مورد را همواره به UTF8 تغییر دهید، از افزونه‌ی ForceUTF8 (with BOM) استفاده کنید.
مسیرراه‌ها
ASP.NET MVC
              مطالب
              شروع به کار با EF Core 1.0 - قسمت 5 - استراتژهای تعیین کلید اصلی جداول و ایندکس‌ها
              پس از بررسی نحوه‌ی انجام تنظیمات اولیه‌ی کار با EF Core و همچنین آشنایی با مهاجرت‌های آن، مرحله‌ی بعد، مرحله‌ی مدلسازی داده‌ها است و اولین مرحله‌ی آن، نحوه‌ی تعیین کلید اصلی جداول است که در این زمینه، EF Core پیشرفت‌هایی قابل ملاحظه‌ای را نسبت به EF 6.x داشته‌است. در EF 6.x تنها دو حالت کلیدهای اصلی خود افزاینده که توسط بانک اطلاعاتی مدیریت می‌شوند و یا تولید کلید اصلی در سمت کلاینت و توسط برنامه، پشتیبانی می‌شوند. در EF Core، مواردی مانند Sequence و Alternate keys نیز اضافه شده‌اند.


              پیش فرض‌های تعیین کلید اصلی در EF Core

              به صورت پیش فرض هر خاصیتی که به نام Id و یا type name>Id> باشد، به عنوان primary key تفسیر خواهد شد؛ مانند:
              public class Car
              {
                  public string Id { get; set; }
              و یا
              public class Car
              {
                 public string CarId { get; set; }
              در مثال اول، نام خاصیت، Id است و در مثال دوم، جمع نام کلاس به همراه Id ذکر شده‌است. یک چنین مواردی، نیازی به تنظیم اضافه‌تری ندارند.


              نحوه‌ی تعیین کلید اصلی به صورت صریح

              اگر یکی از دو حالت فوق برقرار نباشند، باید کلید اصلی را به نحو صریحی مشخص کرد.
              الف) از طریق ویژگی‌ها
              public class Car
              {
                 [Key]
                 public string LicensePlate { get; set; }
              در اینجا چون LicensePlate نه Id نام دارد و نه جمع نام کلاس به همراه Id است، باید به نحو صریحی توسط ویژگی Key مشخص شود.
              ب) با استفاده از روش Fluent API
              public class MyContext : DbContext
              {
                  public DbSet<Car> Cars { get; set; }
              
                  protected override void OnModelCreating(ModelBuilder modelBuilder)
                  {
                       modelBuilder.Entity<Car>()
                               .HasKey(c => c.LicensePlate);
                  }
               }
              روش تنظیم کلید اصلی به صورت صریح، از طریق کدنویسی است که به آن Fluent API یا API روان هم گفته می‌شود. برای اینکار باید متد OnModelCreating کلاس Context برنامه را بازنویسی کرد و سپس از طریق متد HasKey، نام خاصیت کلید اصلی را ذکر نمود.


              پیشنیاز کار با ویژگی‌ها در EF Core

              در اسمبلی که مدل‌های موجودیت‌ها شما قرار دارند، نیاز است وابستگی System.ComponentModel.Annotations به فایل project.json پروژه اضافه شود، تا ویژگی‌هایی مانند Key، شناسایی و قابل استفاده شوند:
              {
                 "dependencies": {
                        "System.ComponentModel.Annotations": "4.1.0"
                 }
              }


              تعیین کلید ترکیبی و یا Composite key

              اگر نیاز است چندین خاصیت را به صورت کلید اصلی معرفی کرد که به آن composite key هم می‌گویند، تنها روش ممکن، استفاده از Fluent API و به صورت زیر است:
              protected override void OnModelCreating(ModelBuilder modelBuilder)
              {
                 modelBuilder.Entity<Car>()
                                     .HasKey(c => new { c.State, c.LicensePlate });
              }
              در قسمت HasKey می‌توان چندین خاصیت را نیز جهت تعیین کلید ترکیبی مشخص کرد.


              روش‌های مختلف تولید خودکار مقادیر خواص

              حالت پیش فرض تولید مقدار فیلدهای Id عددی، همان حالت خود افزاینده‌ای است که توسط بانک اطلاعاتی کنترل می‌شود و یا کلید اصلی که از نوع Guid تعیین شود نیز به صورت خودکار توسط بانک اطلاعاتی در حین عملیات Add، مقدار دهی می‌شود (با استفاده از الگوریتم Guid سری در SQL Server).
               اگر این حالات مطلوب شما نیست، حالت‌های سه گانه‌ی ذیل را می‌توان استفاده کرد:

              الف) هیچ داده‌ی خودکاری تولید نشود
              برای اینکار می‌توان با استفاده از ویژگی DatabaseGenerated و تنظیم مقدار آن به None، جلوی تولید خودکار کلید اصلی را گرفت. در این حالت باید هم در حین عملیات Add و هم در حین عملیات Update، مقادیر را خودتان مقدار دهی کنید:
              public class Blog
              {
                  [DatabaseGenerated(DatabaseGeneratedOption.None)]
                  public int BlogId { get; set; }
              
                  public string Url { get; set; }
              }
              و یا معادل این تنظیم با استفاده از Fluent API به صورت ذیل است:
              protected override void OnModelCreating(ModelBuilder modelBuilder)
              {
                  modelBuilder.Entity<Blog>()
                         .Property(b => b.BlogId)
                         .ValueGeneratedNever();
              }

              ب) تولید داده‌های خودکار فقط در حالت Add
              حالت Add به این معنا است که داده‌های خواص مشخصی، برای موجودیت‌های «جدید»، به صورت خودکار تولید خواهند شد. اینکه آیا واقعا این مقادیر به صورت خودکار تولید می‌شوند یا خیر، صرفا وابسته‌است به بانک اطلاعاتی در حال استفاده. برای مثال SQL Server برای نوع‌های Guid، به صورت خودکار با کمک الگوریتم SQL Server sequential GUID، کار مقدار دهی یک چنین فیلدهایی را انجام می‌دهد.
              این فیلدها باید توسط ویژگی DatabaseGenerated و با مقدار Identity مشخص شوند. در اینجا Identity به معنای فیلدهایی است که به صورت خودکار توسط بانک اطلاعاتی مقدار دهی می‌شوند و الزاما به کلید اصلی اشاره نمی‌کنند. برای مثال در موجودیت ذیل، خاصیت تاریخ ثبت رکورد، از نوع Identity مشخص شده‌است. به این معنا که در حین ثبت اولیه‌ی رکورد آن، نیازی نیست تا خاصیت Inserted را مقدار دهی کرد. اما اینکه آیا SQL Server یک چنین کاری را به صورت خودکار انجام می‌دهد، پاسخ آن خیر است. SQL server فقط برای فیلدهای عددی و Guid ایی که با DatabaseGeneratedOption.Identity مزین شده باشند، مقادیر متناظری را به صورت خودکار تولید می‌کند. برای حالت DateTime نیاز است، مقدار پیش فرض فیلد را صریحا مشخص کرد که توسط ویژگی‌ها میسر نیست و فقط fluent API از آن پشتیبانی می‌کند.
              public class Blog
              {
                 public int BlogId { get; set; }
                 public string Url { get; set; }
              
                 [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
                 public DateTime Inserted { get; set; }
              }
              و یا معادل این تنظیم با استفاده از Fluent API به صورت ذیل است:
              protected override void OnModelCreating(ModelBuilder modelBuilder)
              {
                  modelBuilder.Entity<Blog>()
                         .Property(b => b.Inserted)
                         .ValueGeneratedOnAdd();
              }
              برای تعیین مقدار پیش فرض خاصیت Inserted به نحوی که توسط SQL Server به صورت خودکار مقدار دهی شود، می‌توان از متد HasDefaultValueSql به نحو ذیل استفاده کرد:
              protected override void OnModelCreating(ModelBuilder modelBuilder)
              {
                  modelBuilder.Entity<Blog>()
                      .Property(b => b.Inserted)
                      .HasDefaultValueSql("getdate()");
              }
              البته باید درنظر داشت که اگر خاصیت DateTime تعریف شده در اینجا به همین نحو بکاربرده شود، اگر مقداری برای آن در حین تعریف یک وهله جدید از کلاس Blog درکدهای برنامه درنظر گرفته نشود، یک مقدار پیش فرض حداقل به آن انتساب داده خواهد شد (چون value type است). بنابراین نیاز است این خاصیت را از نوع nullable تعریف کرد (public DateTime? Inserted).

              یک نکته: در حالت DatabaseGeneratedOption.Identity و یا ValueGeneratedOnAdd فوق، اگر مقداری به این نوع فیلدها انتساب داده شده باشد که با مقدار پیش فرض آن‌ها (property.ClrType.GetDefaultValue) متفاوت باشد، از این مقدار جدید، بجای تولید مقداری خودکار، استفاده خواهد شد. برای مثال مقدار پیش فرض رشته‌ها، نال، مقادیر عددی، صفر و برای Guid مقدار Guid.Empty است. اگر هر مقدار دیگری بجای این‌ها به فیلدهای فوق انتساب داده شوند، از آن‌ها استفاده می‌شود.

              ج) تولید داده‌های خودکار در هر دو حالت Add و Update
              تولید داده‌ها در حالت‌های Add و Update به این معنا است که یک چنین خواصی، همواره با فراخوانی متد SaveChanges، دارای مقدار خودکار جدیدی خواهند شد و نیازی نیست در کدها مقدار دهی شوند. برای مشخص سازی این نوع خواص، از ویژگی DatabaseGenerated با مقدار Computed و یا متد ValueGeneratedOnAddOrUpdate در حالت Fluent API می‌توان استفاده کرد:
              public class Blog
              {
                  public int BlogId { get; set; }
                  public string Url { get; set; }
              
                  [DatabaseGenerated(DatabaseGeneratedOption.Computed)]
                  public DateTime LastUpdated { get; set; }
              }
              و یا معادل این تنظیم با استفاده از Fluent API به صورت ذیل است:
              protected override void OnModelCreating(ModelBuilder modelBuilder)
              {
                  modelBuilder.Entity<Blog>()
                     .Property(b => b.LastUpdated)
                     .ValueGeneratedOnAddOrUpdate();
              }
              همانطور که پیشتر نیز عنوان شد، تولید خودکار مقادیر فیلدها فقط در حالت‌های int و Guid انجام می‌شود (که برای مثال SQL Server از آن‌ها پشتیبانی می‌کند). در مثال فوق، خاصیت LastUpdated از نوع DateTime اینگونه تعریف شده‌است و SQL Server برای یک چنین فیلدهای خاصی، مقدار خودکاری را تولید نکرده و به دنبال مقدار پیش فرض آن می‌گردد. بنابراین در اینجا نیز باید مشخص سازی HasDefaultValueSql("getdate()") را که در قسمت قبل عنوان کردیم، صراحتا در قسمت تنظیمات Fluent API ذکر و تنظیم کرد.

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


              خواص محاسباتی (Computed Columns) و تفاوت آن‌ها با DatabaseGeneratedOption.Computed

              خواص محاسباتی (Computed Columns)، خواصی هستند که مقادیر آن‌ها در بانک اطلاعاتی محاسبه می‌شوند و کاملا متفاوت هستند با DatabaseGeneratedOption.Computed که مفهوم دیگری دارد. DatabaseGeneratedOption.Computed به این معنا است که این فیلد خاص، با هر بار فراخوانی SaveChanges باید مقدار محاسبه شده‌ی جدیدی را داشته باشد و روش تولید این مقدار خودکار، یا بر اساس Guidهای سری است، یا توسط فیلدهای خود افزاینده‌ی عددی و یا از طریق مقادیر پیش فرضی مانند getdate در حین ثبت یا به روز رسانی، مقدار دهی می‌شوند. اما خواص محاسباتی، یکی از امکانات «گزارشگیری سریع» SQL Server هستند و به نحو ذیل، تنها توسط Fluent API قابل تنظیم می‌باشند:
              public class Person
              {
                  public int PersonId { get; set; }
                  public string FirstName { get; set; }
                  public string LastName { get; set; }
                  public string DisplayName { get; set; }
              }
              
              public class MyContext : DbContext
              {
                  public DbSet<Person> People { get; set; }
              
                  protected override void OnModelCreating(ModelBuilder modelBuilder)
                  {
                        modelBuilder.Entity<Person>()
                            .Property(p => p.DisplayName)
                             .HasComputedColumnSql("[LastName] + ', ' + [FirstName]");
                   }
               }
              در اینجا فیلد DisplayName یک فیلد محاسباتی بوده و از حاصل جمع دو فیلد دیگر در سمت دیتابیس تشکیل می‌شود. این نگاشت و محاسبه چون در سمت بانک اطلاعاتی انجام می‌شود، بازدهی بیشتری دارد نسبت به حالتی که ابتدا دو فیلد به کلاینت منتقل شده و سپس در این سمت جمع زده شوند.


              امکان تعریف Sequence در EF Core 1.0

              Sequence قابلیتی است که به SQL Server 2012 اضافه شده‌است و توضیحات بیشتر آن‌را در مطلب «نحوه ایجاد Sequence و استفاده آن در Sql Server 2012» می‌توانید مطالعه کنید.
              در EF Core، امکان مدلسازی Sequence نیز پیش بینی شده‌است. آن‌ها به صورت پیش فرض در مدل‌ها ذکر نمی‌شوند و همچنین وابستگی به جدول خاصی ندارند. به همین جهت امکان تعریف آن‌ها صرفا توسط Fluent API وجود دارد:
              protected override void OnModelCreating(ModelBuilder modelBuilder)
              {
                   modelBuilder.HasSequence<int>("OrderNumbers", schema: "shared") 
                         .StartsAt(1000).IncrementsBy(5);
              
                   modelBuilder.Entity<Order>()
                       .Property(o => o.OrderNo)
                       .HasDefaultValueSql("NEXT VALUE FOR shared.OrderNumbers");
              }
              پس از اینکه یک Sequence  تعریف شد، می‌توان برای نمونه از آن جهت تولید مقادیر پیش فرض ستون‌ها استفاده کرد.
              در مثال فوق، ابتدا یک Sequence نمونه به نام OrderNumbers تعریف شده‌است که از عدد 1000 شروع شده و واحد افزایش آن 5 است. سپس از این نام در قسمت مقدار پیش فرض ستون OrderNo استفاده شده‌است.

              و یا از Sequence ‌ها می‌توان برای تعیین مقدار پیش فرض Primary key بجای حالت identity خود افزایش یابنده استفاده کرد:
              protected override void OnModelCreating(ModelBuilder modelBuilder)
              {
                  modelBuilder.HasSequence<int>("PrimaryKeyWithSequenceSequence");
                  modelBuilder.Entity<PrimaryKeyWithSequence>(entity =>
                   {
                     entity.Property(e => e.PrimaryKeyWithSequenceId).HasDefaultValueSql("NEXT VALUE FOR [PrimaryKeyWithSequenceSequence]");
                   });
              }
              در اینجا یک توالی از نوع int تعریف شده و سپس هربار که قرار است رکوردی درج شود، مقدار id آن به صورت خودکار از طریق کوئری Select NEXT VALUE FOR
              [PrimaryKeyWithSequenceSequence] دریافت و سپس بجای فیلد id درج می‌شود.

              به این روش الگوریتم Hi-Low هم می‌گویند که یکی از مهم‌ترین اهداف آن داشتن یک سری Id منحصربفرد، جهت بالابردن سرعت insertها در یک batch است. در حالت عادی insertها، ابتدا یک insert انجام می‌شود، سپس کوئری گرفته شده و آخرین Id درج شده به کلاینت بازگشت داده می‌شود. این روش، برای انجام تنها یک insert، سریع است. اما برای batch insert، به شدت کارآیی پایینی دارد. به همین جهت دسترسی به بازه‌ای از اعداد منحصربفرد، پیش از شروع به insert تعداد زیادی رکورد، سرعت نهایی کار را بالا می‌برد.


              نحوه‌ی تعریف ایندکس‌ها در EF Core 1.0

              برای افزودن ایندکس‌ها به EF Core 1.0، تنها روش میسر، استفاده از Fluent API است (و برخلاف EF 6.x از روش data annotations فعلا پشتیبانی نمی‌کند؛ هرچند API جدید آن نسبت به EF 6.x بسیار واضح‌تر است و با ابهامات کمتر).
              protected override void OnModelCreating(ModelBuilder modelBuilder)
              {
                    modelBuilder.Entity<Blog>()
                        .HasIndex(b => b.Url)
                        .HasName("Index_Url");
              اگر قسمت HasName را ذکر نکنید، نام آن <IX_<type name>_<property name درنظر گرفته می‌شود و برای اینکه ایندکس منحصربفردی را تعریف کنید، می‌توان متد IsUnique را به انتهای این زنجیره اضافه کرد:
               modelBuilder.Entity<Blog>().HasIndex(b => b.Url).HasName("Index_Url").IsUnique();
              همچنین می‌توان همانند composite keys، در اینجا نیز ترکیبی از خواص را به صورت یک ایندکس معرفی نمود:
              modelBuilder.Entity<Person>()
                 .HasIndex(idx => new { idx.FirstName, idx.LastName })
                 .IsUnique();
              در این حالت اگر HasName ذکر نشود، نام آن همانند الگویی است که پیشتر عنوان شد؛ با این تفاوت که قسمت property name آن، جمع نام تمام خواص ذکر شده و جدا شده‌ی با _ خواهد بود.

              یک نکته: اگر از پروایدر SQL Server استفاده می‌کنید، می‌توان متد الحاقی ویژه‌ای را به نام ForSqlServerIsClustered نیز برای تعریف clustered indexes، در این زنجیره ذکر کرد.


              امکان تعریف Alternate Keys در EF Core 1.0

              به Unique Constraints در EF Core، نام Alternate Keys را داده‌اند و این مورد نیز تنها از طریق Fluent API قابل تنظیم است:
              protected override void OnModelCreating(ModelBuilder modelBuilder)
              {
                 modelBuilder.Entity<Car>()
                   .HasAlternateKey(c => c.LicensePlate)
                   .HasName("AlteranteKey_LicensePlate");
              }
              برای یک Alternate Key به صورت خودکار هم ایندکس ایجاد می‌شود و هم اینکه این ایندکس منحصربفرد خواهد بود.
              اگر متد HasName در اینجا ذکر نشود، نام پیش فرض آن  <type name>_<property name> خواهد بود و اگر همانند composite keys و یا ایندکس‌های ترکیبی، چند خاصیت ذکر شوند، قسمت property name به جمع نام تمام خواص ذکر شده و جدا شده‌ی با _ تنظیم می‌شود.
              برای نمونه اگر یک Alternate Key ترکیبی را به صورت ذیل تعریف کنیم:
              modelBuilder.Entity<Person>()
                   .HasAlternateKey(x => new { x.FirstName, x.LastName });
              در قسمت مهاجرت‌هایی که قرار است به بانک اطلاعاتی اعمال شوند، به یک UniqueConstraint ترجمه می‌شود:
               table.UniqueConstraint("AK_Persons_FirstName_LastName", x => new { x.FirstName, x.LastName });


              سؤال: یک Unique Constraint با Unique Index چه تفاوتی دارد؟

              در پشت صحنه، پیاده سازی یک Unique Constraint با Unique Index تفاوتی ندارند. فقط از دیدگاه روشن‌تر شدن مقصود، استفاده‌ی از Unique Constraint ترجیح داده می‌شود.
              البته از دیدگاه بانک اطلاعاتی پیاده سازی کننده نیز برای نمونه SQL Server، این تفاوت‌ها وجود دارند:
              الف) یک Unique Constraint را نمی‌توان غیرفعال کرد؛ برخلاف Unique Indexها.
              ب) Unique Constraint‌ها موارد اضافه‌تری را مانند FILLFACTOR و IGNORE_DUP_KEY نیز می‌توانند تنظیم کنند.
              ج) امکان تعریف فیلترها برای Unique Indexها وجود دارد؛ برخلاف Unique Constraint ها.

              که البته از دیدگاه EF، این سه مورد اهمیتی ندارند و بیشتر روشن‌تر شدن مقصود، هدف اصلی آن‌ها است.
              مطالب
              آشنایی با NHibernate - قسمت دوم

              آزمون واحد کلاس نگاشت تهیه شده

              در مورد آشنایی با آزمون‌های واحد لطفا به برچسب مربوطه در سمت راست سایت مراجعه بفرمائید. همچنین در مورد اینکه چرا به این نوع API کلمه Fluent اطلاق می‌شود، می‌توان به تعریف آن جهت مطالعه بیشتر مراجعه نمود.

              در این قسمت قصد داریم برای بررسی وضعیت کلاس نگاشت تهیه شده یک آزمون واحد تهیه کنیم. برای این منظور ارجاعی را به اسمبلی nunit.framework.dll به پروژه UnitTests که در ابتدای کار به solution جاری در VS.Net افزوده بودیم، اضافه نمائید (همچنین ارجاع‌هایی به اسمبلی‌های پروژه NHSample1 ، FluentNHibernate ، System.Data.SQLite ، NHibernate.ByteCode.Castle و Nhibernate نیز نیاز هستند). تمام اسمبلی‌های این فریم ورک‌ها از پروژه FluentNHibernate قابل استخراج هستند.

              سپس سه کلاس زیر را به پروژه آزمون واحد اضافه خواهیم کرد.
              کلاس TestModel : (جهت مشخص سازی محل دریافت اطلاعات نگاشت)

              using FluentNHibernate;
              using NHSample1.Domain;

              namespace UnitTests
              {
              public class TestModel : PersistenceModel
              {
              public TestModel()
              {
              AddMappingsFromAssembly(typeof(CustomerMapping).Assembly);
              }
              }
              }

              کلاس FixtureBase : (جهت ایجاد سشن NHibernate در ابتدای آزمون واحد و سپس پاکسازی اشیاء در پایان کار)

              using NUnit.Framework;
              using NHibernate;
              using FluentNHibernate;
              using FluentNHibernate.Cfg;
              using FluentNHibernate.Cfg.Db;

              namespace UnitTests
              {
              public class FixtureBase
              {
              protected SessionSource SessionSource { get; set; }
              protected ISession Session { get; private set; }

              [SetUp]
              public void SetupContext()
              {
              var cfg = Fluently.Configure().Database(SQLiteConfiguration.Standard.InMemory);

              SessionSource = new SessionSource(
              cfg.BuildConfiguration().Properties,
              new TestModel());

              Session = SessionSource.CreateSession();
              SessionSource.BuildSchema(Session);
              }

              [TearDown]
              public void TearDownContext()
              {
              Session.Close();
              Session.Dispose();
              }
              }
              }

              و کلاس CustomerMapping_Fixture.cs : (جهت بررسی صحت نگاشت تهیه شده با کمک دو کلاس قبل)

              using NUnit.Framework;
              using FluentNHibernate.Testing;
              using NHSample1.Domain;

              namespace UnitTests
              {
              [TestFixture]
              public class CustomerMapping_Fixture : FixtureBase
              {
              [Test]
              public void can_correctly_map_customer()
              {
              new PersistenceSpecification<Customer>(Session)
              .CheckProperty(c => c.Id, 1001)
              .CheckProperty(c => c.FirstName, "Vahid")
              .CheckProperty(c => c.LastName, "Nasiri")
              .CheckProperty(c => c.AddressLine1, "Addr1")
              .CheckProperty(c => c.AddressLine2, "Addr2")
              .CheckProperty(c => c.PostalCode, "1234")
              .CheckProperty(c => c.City, "Tehran")
              .CheckProperty(c => c.CountryCode, "IR")
              .VerifyTheMappings();
              }
              }
              }

              توضیحات:
              اکنون به عنوان یک برنامه نویس متعهد نیاز است تا کار صورت گرفته در قسمت قبل را آزمایش کنیم.
              کار بررسی صحت نگاشت تعریف شده در قسمت قبل توسط کلاس استاندارد PersistenceSpecification فریم ورک FluentNHibernate انجام خواهد شد (در کلاس CustomerMapping_Fixture). این کلاس برای انجام عملیات آزمون واحد نیاز به کلاس پایه دیگری به نام FixtureBase دارد که در آن کار ایجاد سشن NHibernate (در قسمت استاندارد SetUp آزمون واحد) و سپس آزاد سازی آن را در هنگام خاتمه کار ، انجام می‌دهد (در قسمت TearDown آزمون واحد).
              این ویژگی‌ها که در مباحث آزمون واحد نیز به آن‌ها اشاره شده است، سبب اجرای متدهایی پیش از اجرا و بررسی هر آزمون واحد و سپس آزاد سازی خودکار منابع خواهند شد.
              برای ایجاد یک سشن NHibernate نیاز است تا نوع دیتابیس و همچنین رشته اتصالی به آن (کانکشن استرینگ) مشخص شوند. فریم ورک Fluent NHibernate با ایجاد کلاس‌های کمکی برای این امر، به شدت سبب ساده‌ سازی انجام آن شده است. در این مثال، نوع دیتابیس به SQLite و در حالت دیتابیس در حافظه (in memory)، تنظیم شده است (برای انجام امور آزمون واحد با سرعت بالا).
              جهت اجرای هر دستوری در NHibernate نیاز به یک سشن می‌باشد. برای تعریف شیء سشن، نه تنها نیاز به مشخص سازی نوع و حالت دیتابیس مورد استفاده داریم، بلکه نیاز است تا وهله‌ای از کلاس استاندارد PersistanceModel را نیز جهت مشخص سازی کلاس نگاشت مورد استفاده مشخص نمائیم. برای این منظور کلاس TestModel فوق تعریف شده است تا این نگاشت را از اسمبلی مربوطه بخواند و مورد استفاده قرار دهد (بر پایی اولیه این مراحل شاید در ابتدای امر کمی زمانبر باشد اما در نهایت یک پروسه استاندارد است). توسط این کلاس به سیستم اعلام خواهیم کرد که اطلاعات نگاشت را باید از کدام کلاس دریافت کند.
              تا اینجای کار شیء SessionSource را با معرفی نوع دیتابیس و همچنین محل دریافت اطلاعات نگاشت اشیاء معرفی کردیم. در دو سطر بعدی متد SetupContext کلاس FixtureBase ، ابتدا یک سشن را از این منبع سشن تهیه می‌کنیم. شیء منبع سشن در این فریم ورک در حقیقت یک factory object است (الگوهای طراحی برنامه نویسی شیءگرا) که امکان دسترسی به انواع و اقسام دیتابیس‌ها را فراهم می‌سازد. برای مثال اگر روزی نیاز بود از دیتابیس اس کیوال سرور استفاده شود، می‌توان از کلاس MsSqlConfiguration بجای SQLiteConfiguration استفاده کرد و همینطور الی آخر.
              در ادامه توسط شیء SessionSource کار ساخت database schema را نیز به صورت پویا انجام خواهیم داد. بله، همانطور که متوجه شده‌اید، کار ساخت database schema نیز به صورت پویا توسط فریم ورک NHibernate با توجه به اطلاعات کلاس‌های نگاشت، صورت خواهد گرفت.

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

              در ادامه اگر آزمون واحد را اجرا نمائیم (متد can_correctly_map_customer در کلاس CustomerMapping_Fixture)، نتیجه باید شبیه به شکل زیر باشد:



              توسط متد CheckProperty کلاس PersistenceSpecification ، امکان بررسی نگاشت تهیه شده میسر است. اولین پارامتر آن، یک lambda expression خاصیت مورد نظر جهت بررسی است و دومین آرگومان آن، مقداری است که در حین آزمون به خاصیت تعریف شده انتساب داده می‌شود.

              نکته:
              شاید سؤال بپرسید که در تابع can_correctly_map_customer عملا چه اتفاقاتی رخ داده است؟ برای بررسی آن در متد SetupContext کلاس FixtureBase ، اولین سطر آن‌را به صورت زیر تغییر دهید تا عبارات SQL نهایی تولید شده را نیز بتوانیم در حین عملیات تست مشاهده نمائیم:

              var cfg = Fluently.Configure().Database(SQLiteConfiguration.Standard.ShowSql().InMemory);




              مطابق متد تست فوق، عبارات تولید شده به شرح زیر هستند:

              NHibernate: select next_hi from hibernate_unique_key
              NHibernate: update hibernate_unique_key set next_hi = @p0 where next_hi = @p1;@p0 = 2, @p1 = 1
              NHibernate: INSERT INTO "Customer" (FirstName, LastName, AddressLine1, AddressLine2, PostalCode, City, CountryCode, Id) VALUES (@p0, @p1, @p2, @p3, @p4, @p5, @p6, @p7);@p0 = 'Vahid', @p1 = 'Nasiri', @p2 = 'Addr1', @p3 = 'Addr2', @p4 = '1234', @p5 = 'Tehran', @p6 = 'IR', @p7 = 1001
              NHibernate: SELECT customer0_.Id as Id0_0_, customer0_.FirstName as FirstName0_0_, customer0_.LastName as LastName0_0_, customer0_.AddressLine1 as AddressL4_0_0_, customer0_.AddressLine2 as AddressL5_0_0_, customer0_.PostalCode as PostalCode0_0_, customer0_.City as City0_0_, customer0_.CountryCode as CountryC8_0_0_ FROM "Customer" customer0_ WHERE customer0_.Id=@p0;@p0 = 1001

              نکته جالب این عبارات، استفاده از کوئری‌های پارامتری است به صورت پیش فرض که در نهایت سبب بالا رفتن امنیت بیشتر برنامه (یکی از راه‌های جلوگیری از تزریق اس کیوال در ADO.Net که در نهایت توسط تمامی این فریم ورک‌ها در پشت صحنه مورد استفاده قرار خواهند گرفت) و همچنین سبب بکار افتادن سیستم‌های کش دیتابیس‌های پیشرفته مانند اس کیوال سرور می‌شوند (execution plan کوئری‌های پارامتری در اس کیوال سرور جهت بالا رفتن کارآیی سیستم کش می‌شوند و اهمیتی هم ندارد که حتما رویه ذخیره شده باشند یا خیر).

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

              مطالب
              Blazor 5x - قسمت 13 - کار با فرم‌ها - بخش 1 - کار با EF Core در برنامه‌های Blazor Server
              در ادامه قصد داریم یک پروژه‌ی مدیریت هتل را پیاده سازی کنیم. این پروژه، دو قسمتی است. قسمت اول آن یک پروژه‌ی Blazor Server، برای مدیریت هتل مانند تعاریف اتاق‌ها است و پروژه‌ی دوم آن از نوع Blazor WASM، برای مراجعه‌ی کاربران عمومی و رزرو اتاق‌ها است. هدف، بررسی نحوه‌ی کار با هر دو نوع فناوری است. وگرنه می‌توان کل پروژه را با Blazor Server و یا کل آن‌را با Blazor WASM هم پیاده سازی کرد. در مورد نحوه‌ی انتخاب و مزایا و معایب هرکدام از این فناوری‌ها، در قسمت‌های اول و دوم این سری بیشتر بحث شده‌است.


              ساختار پوشه‌ها و پروژه‌های قسمت Blazor Server


              قسمت Blazor Server مدیریت هتل ما از 7 پروژه و پوشه‌ی زیر تشکیل می‌شود:
              - BlazorServer.App: پروژه‌ی اصلی Blazor Server است که با اجرای دستور dotnet new blazorserver در پوشه‌ی خالی آن آغاز می‌شود.
              - BlazorServer.Common: پروژه‌ای از نوع classlib، جهت قرارگیری کدهای مشترک بین پروژه‌ها است که با اجرای دستور dotnet new classlib در این پوشه آغاز می‌شود.
              - BlazorServer.DataAccess: پروژه‌ای از نوع classlib، برای تعریف DbContext برنامه است که با اجرای دستور dotnet new classlib در این پوشه آغاز می‌شود.
              - BlazorServer.Entities: پروژه‌ای از نوع classlib، جهت تعریف کلاس‌های متناظر با جداول بانک اطلاعاتی برنامه است که با اجرای دستور dotnet new classlib در این پوشه آغاز می‌شود.
              - BlazorServer.Models: پروژه‌ای از نوع classlib، برای تعریف کلاس‌های data transfer objects برنامه (DTO's) است که با اجرای دستور dotnet new classlib در این پوشه آغاز می‌شود.
              - BlazorServer.Models.Mappings: پروژه‌ای از نوع classlib، برای تعریف نگاشت‌های بین DTO's و مجودیت‌های برنامه و برعکس است که با اجرای دستور dotnet new classlib در این پوشه آغاز می‌شود.
              - BlazorServer.Services: پروژه‌ای از نوع classlib، جهت تعریف کدهایی که منطق تجاری تعامل با بانک اطلاعاتی را از طریق BlazorServer.DataAccess میسر می‌کند که با اجرای دستور dotnet new classlib در این پوشه آغاز می‌شود.


              اصلاح پروژه‌ی BlazorServer.App جهت استفاده از LibMan

              قالب پیش‌فرض BlazorServer.App، به همراه پوشه‌ی wwwroot\css است که در آن بوت استرپ و open-iconic به همراه فایل site.css قرار دارند. چون این پروژه به همراه هیچ نوع روشی برای مدیریت نگهداری بسته‌های سمت کلاینت خود نیست، دو پوشه‌ی بوت استرپ و open-iconic آن‌را حذف کرده و از روش مطرح شده‌ی در مطلب «Blazor 5x - قسمت یازدهم - مبانی Blazor - بخش 8 - کار با جاوا اسکریپت» استفاده خواهیم کرد:
              dotnet tool update -g Microsoft.Web.LibraryManager.Cli
              libman init
              libman install bootstrap --provider unpkg --destination wwwroot/lib/bootstrap
              libman install open-iconic --provider unpkg --destination wwwroot/lib/open-iconic
              در پوشه‌ی ریشه‌ی پروژه‌ی BlazorServer.App، دستورات فوق را اجرا می‌کنیم تا بسته‌های bootstrap و open-iconic را در پوشه‌ی wwwroot/lib نصب کند و همچنین فایل libman.json متناظری را نیز جهت اجرای دستور libman restore برای دفعات آتی، تولید کند.

              بعد از نصب بسته‌های ذکر شده، ابتدا سطر زیر را از ابتدای فایل پیش‌فرض wwwroot\css\site.css حذف می‌کنیم:
              @import url('open-iconic/font/css/open-iconic-bootstrap.min.css');
              سپس فایل Pages\_Host.cshtml را به صورت زیر به روز رسانی می‌کنیم تا به مسیرهای جدید بسته‌های CSS، اشاره کند:
              <head>
                  <meta charset="utf-8" />
                  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
                  <title>BlazorServer.App</title>
                  <base href="~/" />
                  <link href="lib/open-iconic/font/css/open-iconic-bootstrap.min.css" rel="stylesheet" />
                  <link href="lib/bootstrap/dist/css/bootstrap.min.css" rel="stylesheet" />
                  <link href="css/site.css" rel="stylesheet" />
                  <link href="BlazorServer.App.styles.css" rel="stylesheet" />
              </head>

              برای bundling & minification این فایل‌ها می‌توان از «Bundler Minifier» استفاده کرد.


              پروژه‌ی موجودیت‌های مدیریت هتل

              فایل BlazorServer.Entities.csproj وابستگی خاصی را نداشته و به صورت زیر تعریف شده‌است:
              <Project Sdk="Microsoft.NET.Sdk">
                <PropertyGroup>
                  <TargetFramework>net5.0</TargetFramework>
                </PropertyGroup>
              </Project>
              در این پروژه، کلاس جدید HotelRoom را که بیانگر ساختار جدول متناظری در بانک اطلاعاتی است، به صورت زیر تعریف می‌کنیم:
              using System;
              using System.ComponentModel.DataAnnotations;
              
              namespace BlazorServer.Entities
              {
                  public class HotelRoom
                  {
                      [Key]
                      public int Id { get; set; }
              
                      [Required]
                      public string Name { get; set; }
              
                      [Required]
                      public int Occupancy { get; set; }
              
                      [Required]
                      public decimal RegularRate { get; set; }
              
                      public string Details { get; set; }
              
                      public string SqFt { get; set; }
              
                      public string CreatedBy { get; set; }
              
                      public DateTime CreatedDate { get; set; } = DateTime.Now;
              
                      public string UpdatedBy { get; set; }
              
                      public DateTime UpdatedDate { get; set; }
                  }
              }
              که شامل فیلدهایی مانند نام، ظرفیت، مساحت و ... یک اتاق هتل است.


              پروژه‌ی تعریف DbContext برنامه‌ی مدیریت هتل

              فایل BlazorServer.DataAccess.csproj به این صورت تعریف شده‌است:
              <Project Sdk="Microsoft.NET.Sdk">
                <PropertyGroup>
                  <TargetFramework>net5.0</TargetFramework>
                </PropertyGroup>
              
                <ItemGroup>
                  <ProjectReference Include="..\BlazorServer.Entities\BlazorServer.Entities.csproj" />
                </ItemGroup>
              
                <ItemGroup>
                  <PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="5.0.3" />
                  <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="5.0.3">
                    <PrivateAssets>all</PrivateAssets>
                    <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
                  </PackageReference>
                </ItemGroup>
              </Project>
              - در اینجا چون نیاز است موجودیت HotelRoom را به صورت یک DbSet معرفی کنیم، ارجاعی را به پروژه‌ی BlazorServer.Entities.csproj تعریف کرده‌ایم.
              - همچنین دو وابستگی مورد نیاز جهت کار با EntityFrameworkCore و اجرای مهاجرت‌ها را نیز به آن افزوده‌ایم.

              پس از تامین این وابستگی‌ها، اکنون می‌توان DbContext ابتدایی برنامه را به صورت زیر تعریف کرد که کار آن، در معرض دید قرار دادن HotelRoom به صورت یک DbSet است:
              using BlazorServer.Entities;
              using Microsoft.EntityFrameworkCore;
              
              namespace BlazorServer.DataAccess
              {
                  public class ApplicationDbContext : DbContext
                  {
                      public DbSet<HotelRoom> HotelRooms { get; set; }
              
                      public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options)
                      { }
                  }
              }
              پس از این مرحله، نیاز است این DbContext را به سیستم تزریق وابستگی‌های برنامه‌ی اصلی معرفی کرد. بنابراین فایل BlazorServer.App.csproj پروژه‌ی اصلی Blazor Server را گشوده و تغییرات زیر را اعمال می‌کنیم:
              <Project Sdk="Microsoft.NET.Sdk.Web">
                <PropertyGroup>
                  <TargetFramework>net5.0</TargetFramework>
                </PropertyGroup>
              
                <ItemGroup>
                  <ProjectReference Include="..\BlazorServer.DataAccess\BlazorServer.DataAccess.csproj" />
                </ItemGroup>
              
                <ItemGroup>
                  <PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="5.0.3" />
                  <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="5.0.3">
                    <PrivateAssets>all</PrivateAssets>
                    <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
                  </PackageReference>
                </ItemGroup>
              </Project>
              - چون می‌خواهیم ApplicationDbContext را به سیستم تزریق وابستگی‌ها معرفی کنیم، بنابراین باید بتوان به کلاس آن نیز دسترسی داشت که اینکار، با تعریف ارجاعی به BlazorServer.DataAccess.csproj میسر شده‌است.
              - سپس چون می‌خواهیم از تامین کننده‌ی بانک اطلاعاتی SQL Server نیز استفاده کنیم، وابستگی‌های آن‌را نیز افزوده‌ایم.

              با این تنظیمات، به فایل BlazorServer\BlazorServer.App\Startup.cs مراجعه کرده و کار افزودن AddDbContext و UseSqlServer را انجام می‌دهیم تا DbContext برنامه از طریق تزریق وابستگی‌ها قابل دسترسی شود و همچنین رشته‌ی اتصالی مشخص شده نیز به تامین کننده‌ی SQL Server ارسال شود:
              namespace BlazorServer.App
              {
                  public class Startup
                  {
                      // ...
               
                      public void ConfigureServices(IServiceCollection services)
                      {
                          var connectionString = Configuration.GetConnectionString("DefaultConnection");
                          services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer(connectionString));
              
                          // ...
              این رشته‌ی اتصالی را به صورت زیر در فایل BlazorServer\BlazorServer.App\appsettings.json تعریف کرده‌ایم:
              {
                "ConnectionStrings": {
                  "DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=HotelManagement;Trusted_Connection=True;MultipleActiveResultSets=true"
                }
              }
              که در حقیقت یک رشته‌ی اتصالی جهت کار با LocalDB است.


              اجرای مهاجرت‌ها و تشکیل ساختار بانک اطلاعاتی

              پس از این تنظیمات، اکنون می‌توانیم به پوشه‌ی BlazorServer\BlazorServer.DataAccess مراجعه کرده و از طریق خط فرمان، دستورات زیر را صادر کنیم:
              dotnet tool update --global dotnet-ef --version 5.0.3
              dotnet build
              dotnet ef migrations --startup-project ../BlazorServer.App/ add Init --context ApplicationDbContext
              dotnet ef --startup-project ../BlazorServer.App/ database update --context ApplicationDbContext
              - در ابتدا نیاز است ابزارهای مهاجرت EF-Core را نصب کنیم که سطر اول، اینکار را انجام می‌دهد.
              - همیشه بهتر است پیش از اجرای عملیات Migration، یکبار dotnet build را اجرا کرد؛ تا اگر خطایی وجود دارد، بتوان جزئیات دقیق آن‌را مشاهده کرد. چون عموما این جزئیات در حین اجرای دستورات بعدی، با پیام مختصر «عملیات شکست خورد»، نمایش داده نمی‌شوند.
              - دستور سوم، کار تشکیل پوشه‌ی BlazorServer\BlazorServer.DataAccess\Migrations و تولید خودکار دستورات تشکیل بانک اطلاعاتی را بر اساس ساختار DbContext برنامه انجام می‌دهد.
              - دستور چهارم، بر اساس اطلاعات موجود در پوشه‌ی BlazorServer\BlazorServer.DataAccess\Migrations، بانک اطلاعاتی واقعی را تولید می‌کند.
              در این دستورات ذکر پروژه‌ی آغازین برنامه جهت یافتن وابستگی‌های پروژه ضروری است.



              تکمیل پروژه‌ی DTO‌های برنامه

              همواره توصیه شده‌است که موجودیت‌های برنامه را مستقیما در معرض دید UI قرار ندهید. حداقل مشکلی را که در اینجا ممکن است مشاهده کنید، حملات از نوع mass assignment هستند. برای مثال قرار است از کاربر، کلمه‌ی عبور جدید آن‌را دریافت کنید، ولی چون اطلاعات دریافتی، به اصل موجودیت متناظر با بانک اطلاعاتی نگاشت می‌شود، کاربر می‌تواند فیلد IsAdmin را هم خودش مقدار دهی کند! و چون سیستم Binding بسیار پیشرفته عمل می‌کند، این ورودی را معتبر یافته و در اینجا علاوه بر به روز رسانی کلمه‌ی عبور، خواص دیگری را هم که نباید به روز رسانی شوند، به روز رسانی می‌کند و یا در بسیاری از موارد نیاز است data annotations خاصی را برای فیلدها تعریف کرد که ربطی به موجودیت اصلی ندارند و یا نیاز است فیلدهایی را در UI قرار داد که باز هم تناظر یک به یکی با موجودیت اصلی ندارند (گاهی کمتر و گاهی بیشتر هستند و باید بر روی آن‌ها محاسباتی صورت گیرد تا قابلیت ذخیره سازی در بانک اطلاعاتی را پیدا کنند). به همین جهت کار مدل سازی UI و یا بازگشت اطلاعات نهایی از سرویس‌ها را توسط DTO‌ها که یک سری کلاس ساده‌ی C# 9.0 از نوع record هستند، انجام می‌دهیم:
              using System;
              using System.ComponentModel.DataAnnotations;
              
              namespace BlazorServer.Models
              {
                  public record HotelRoomDTO
                  {
                      public int Id { get; init; }
              
                      [Required(ErrorMessage = "Please enter the room's name")]
                      public string Name { get; init; }
              
                      [Required(ErrorMessage = "Please enter the occupancy")]
                      public int Occupancy { get; init; }
              
                      [Range(1, 3000, ErrorMessage = "Regular rate must be between 1 and 3000")]
                      public decimal RegularRate { get; init; }
              
                      public string Details { get; init; }
              
                      public string SqFt { get; init; }
                  }
              }
              Record‌های C# 9.0، انتخاب بسیار مناسبی برای تعریف DTO‌ها هستند. از این لحاظ که قرار نیست اطلاعات دریافتی از کاربر، در این بین و پس از مقدار دهی اولیه، تغییر کنند.
              در اینجا فیلدهای UI برنامه را که در قسمت بعد تکمیل خواهیم کرد، مشاهده می‌کنید؛ به همراه یک سری data annotation برای تعریف اجباری و یا بازه‌ی مورد قبول، به همراه پیام‌های خطای مرتبط.


              نگاشت DTO‌های برنامه به موجودیت‌ها و بر عکس

              یا می‌توان خواص DTO تعریف شده را یکی یکی به موجودیتی متناظر با آن انتساب داد و یا می‌توان از AutoMapper برای اینکار استفاده کرد. به همین جهت به BlazorServer.Models.Mappings.csproj مراجعه کرده و تغییرات زیر را اعمال می‌کنیم:
              <Project Sdk="Microsoft.NET.Sdk">
                <PropertyGroup>
                  <TargetFramework>net5.0</TargetFramework>
                </PropertyGroup>
              
                <ItemGroup>
                  <ProjectReference Include="..\BlazorServer.Entities\BlazorServer.Entities.csproj" />
                  <ProjectReference Include="..\BlazorServer.Models\BlazorServer.Models.csproj" />
                </ItemGroup>
              
                <ItemGroup>
                  <PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="8.1.1" />
                </ItemGroup>
              </Project>
              - پروژه‌ای که کار تعریف نگاشت‌ها را انجام می‌دهد، نیاز به اطلاعات موجودیت‌ها و مدل‌ها (DTO ها)ی متناظر را دارد. به همین جهت ارجاعاتی را به این دو پروژه، تعریف کرده‌ایم.
              - همچنین بسته‌ی مخصوص AutoMapper را که به همراه امکانات تزریق وابستگی‌های آن نیز هست، در اینجا افزوده‌ایم.

              پس از افزودن این ارجاعات، نگاشت دو طرفه‌ی بین مدل و موجودیت تعریف شده را به صورت زیر تعریف می‌کنیم:
              using AutoMapper;
              using BlazorServer.Entities;
              
              namespace BlazorServer.Models.Mappings
              {
                  public class MappingProfile : Profile
                  {
                      public MappingProfile()
                      {
                          CreateMap<HotelRoomDTO, HotelRoom>().ReverseMap(); // two-way mapping
                      }
                  }
              }
              اکنون برای شناسایی پروفایل فوق و معرفی آن به AutoMapper، به فایل BlazorServer\BlazorServer.App\Startup.cs مراجعه کرده و تزریق وابستگی و ردیابی خودکار آن‌را اضافه می‌کنیم که شامل اسکن تمام اسمبلی‌های موجود، جهت یافتن Profile‌های AutoMapper است:
              namespace BlazorServer.App
              {
                  public class Startup
                  {
                      // ...
              
                      public void ConfigureServices(IServiceCollection services)
                      {
                          services.AddAutoMapper(AppDomain.CurrentDomain.GetAssemblies());
              
                          // ...


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

              پس از راه اندازی برنامه و تعریف موجودیت‌ها، DbContext و غیره، اکنون می‌توانیم از آن‌ها جهت ارائه‌ی منطق مدیریتی برنامه استفاده کنیم:
              using System.Collections.Generic;
              using System.Threading.Tasks;
              using BlazorServer.Models;
              
              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);
                  }
              }

              که پیاده سازی ابتدایی آن به صورت زیر است:
              using System;
              using System.Collections.Generic;
              using System.Threading.Tasks;
              using AutoMapper;
              using AutoMapper.QueryableExtensions;
              using BlazorServer.DataAccess;
              using BlazorServer.Entities;
              using BlazorServer.Models;
              using Microsoft.EntityFrameworkCore;
              
              namespace BlazorServer.Services
              {
                  public class HotelRoomService : IHotelRoomService
                  {
                      private readonly ApplicationDbContext _dbContext;
                      private readonly IMapper _mapper;
                      private readonly IConfigurationProvider _mapperConfiguration;
              
                      public HotelRoomService(ApplicationDbContext dbContext, IMapper mapper)
                      {
                          _dbContext = dbContext ?? throw new ArgumentNullException(nameof(dbContext));
                          _mapper = mapper ?? throw new ArgumentNullException(nameof(mapper));
                          _mapperConfiguration = mapper.ConfigurationProvider;
                      }
              
                      public async Task<HotelRoomDTO> CreateHotelRoomAsync(HotelRoomDTO hotelRoomDTO)
                      {
                          var hotelRoom = _mapper.Map<HotelRoom>(hotelRoomDTO);
                          hotelRoom.CreatedDate = DateTime.Now;
                          hotelRoom.CreatedBy = "";
                          var addedHotelRoom = await _dbContext.HotelRooms.AddAsync(hotelRoom);
                          await _dbContext.SaveChangesAsync();
                          return _mapper.Map<HotelRoomDTO>(addedHotelRoom.Entity);
                      }
              
                      public async Task<int> DeleteHotelRoomAsync(int roomId)
                      {
                          var roomDetails = await _dbContext.HotelRooms.FindAsync(roomId);
                          if (roomDetails == null)
                          {
                              return 0;
                          }
              
                          _dbContext.HotelRooms.Remove(roomDetails);
                          return await _dbContext.SaveChangesAsync();
                      }
              
                      public IAsyncEnumerable<HotelRoomDTO> GetAllHotelRoomsAsync()
                      {
                          return _dbContext.HotelRooms
                                      .ProjectTo<HotelRoomDTO>(_mapperConfiguration)
                                      .AsAsyncEnumerable();
                      }
              
                      public Task<HotelRoomDTO> GetHotelRoomAsync(int roomId)
                      {
                          return _dbContext.HotelRooms
                                          .ProjectTo<HotelRoomDTO>(_mapperConfiguration)
                                          .FirstOrDefaultAsync(x => x.Id == roomId);
                      }
              
                      public Task<HotelRoomDTO> IsRoomUniqueAsync(string name)
                      {
                          return _dbContext.HotelRooms
                                          .ProjectTo<HotelRoomDTO>(_mapperConfiguration)
                                          .FirstOrDefaultAsync(x => x.Name == name);
                      }
              
                      public async Task<HotelRoomDTO> UpdateHotelRoomAsync(int roomId, HotelRoomDTO hotelRoomDTO)
                      {
                          if (roomId != hotelRoomDTO.Id)
                          {
                              return null;
                          }
              
                          var roomDetails = await _dbContext.HotelRooms.FindAsync(roomId);
                          var room = _mapper.Map(hotelRoomDTO, roomDetails);
                          room.UpdatedBy = "";
                          room.UpdatedDate = DateTime.Now;
                          var updatedRoom = _dbContext.HotelRooms.Update(room);
                          await _dbContext.SaveChangesAsync();
                          return _mapper.Map<HotelRoomDTO>(updatedRoom.Entity);
                      }
                  }
              }
              - در اینجا DbContext برنامه و همچنین نگاشت‌گر AutoMapper، به سازنده‌ی سرویس، تزریق شده و توسط آن‌ها، ابتدا اطلاعات DTOها به موجودیت‌ها تبدیل شده‌اند (و یا برعکس) و سپس با استفاده از dbContext برنامه، کوئری‌هایی را بر روی بانک اطلاعاتی اجرا کرده‌ایم.
              - در این کدها استفاده از متد ProjectTo را هم مشاهده می‌کنید. استفاده از این متد، بسیار بهینه‌تر از کار با متد Map درون حافظه‌ای است. از این جهت که بر روی SQL نهایی ارسالی به سمت سرور تاثیرگذار است و تعداد فیلدهای بازگشت داده شده را بر اساس DTO تعیین شده، کاهش می‌دهد. درغیراینصورت باید تمام ستون‌های جدول را بازگشت داد و سپس با استفاده از متد Map درون حافظه‌‌ای، کار نگاشت نهایی را انجام داد که آنچنان بهینه نیست.

              در آخر نیاز است این سرویس را نیز به سیستم تزریق وابستگی‌های برنامه معرفی کنیم. به همین جهت در فایل BlazorServer\BlazorServer.App\Startup.cs، تغییر زیر را اعمال خواهیم کرد:
              namespace BlazorServer.App
              {
                  public class Startup
                  {
                      // ...
              
                      public void ConfigureServices(IServiceCollection services)
                      {
                          services.AddScoped<IHotelRoomService, HotelRoomService>();
              
                       // ...


              کدهای کامل این مطلب را از اینجا می‌توانید دریافت کنید: Blazor-5x-Part-13.zip
              مطالب
              ASP.NET MVC #11

              بررسی نکات تکمیلی Model binder در ASP.NET MVC

              یک برنامه خالی جدید ASP.NET MVC را شروع کنید و سپس مدل زیر را به پوشه Models آن اضافه نمائید:

              using System;

              namespace MvcApplication7.Models
              {
              public class User
              {
              public int Id { set; get; }
              public string Name { set; get; }
              public string Password { set; get; }
              public DateTime AddDate { set; get; }
              public bool IsAdmin { set; get; }
              }
              }

              از این مدل چند مقصود ذیل دنبال می‌شوند:
              استفاده از Id به عنوان primary key برای edit و update رکوردها. استفاده از DateTime برای اینکه اگر کاربری اطلاعات بی ربطی را وارد کرد چگونه باید این مشکل را در حالت model binding خودکار تشخیص داد و استفاده از IsAdmin برای یادآوری یک نکته امنیتی بسیار مهم که اگر حین model binding خودکار به آن توجه نشود، سایت را با مشکلات حاد امنیتی مواجه خواهد کرد. سیستم پیشرفته است. می‌تواند به صورت خودکار ورودی‌های کاربر را تبدیل به یک شیء حاضر و آماده کند ... اما باید حین استفاده از این قابلیت دلپذیر به یک سری نکات امنیتی هم دقت داشت تا سایت ما به نحو دلپذیری هک نشود!

              در ادامه یک کنترلر جدید به نام UserController را به پوشه کنترلرهای پروژه اضافه نمائید. همچنین نام کنترلر پیش فرض تعریف شده در قسمت مسیریابی فایل Global.asax.cs را هم به User تغییر دهید تا در هربار اجرای برنامه در VS.NET، نیازی به تایپ آدرس‌های مرتبط با UserController نداشته باشیم.
              یک منبع داده تشکیل شده در حافظه را هم برای نمایش لیستی از کاربران، به نحو زیر به پروژه اضافه خواهیم کرد:

              using System;
              using System.Collections.Generic;

              namespace MvcApplication7.Models
              {
              public class Users
              {
              public IList<User> CreateInMemoryDataSource()
              {
              return new[]
              {
              new User { Id = 1, Name = "User1", Password = "123", IsAdmin = false, AddDate = DateTime.Now },
              new User { Id = 2, Name = "User2", Password = "456", IsAdmin = false, AddDate = DateTime.Now },
              new User { Id = 3, Name = "User3", Password = "789", IsAdmin = true, AddDate = DateTime.Now }
              };
              }
              }
              }

              در اینجا فعلا هدف آشنایی با زیر ساخت‌های ASP.NET MVC است و درک صحیح نحوه کارکرد آن. مهم نیست از EF استفاده می‌کنید یا NH یا حتی ADO.NET کلاسیک و یا از Micro ORMهایی که پس از ارائه دات نت 4 مرسوم شده‌اند. تهیه یک ToList یا Insert و Update با این فریم ورک‌ها خارج از بحث جاری هستند.

              سورس کامل کنترلر User به شرح زیر است:

              using System;
              using System.Linq;
              using System.Web.Mvc;
              using MvcApplication7.Models;

              namespace MvcApplication7.Controllers
              {
              public class UserController : Controller
              {
              [HttpGet]
              public ActionResult Index()
              {
              var usersList = new Users().CreateInMemoryDataSource();
              return View(usersList); // Shows the Index view.
              }

              [HttpGet]
              public ActionResult Details(int id)
              {
              var user = new Users().CreateInMemoryDataSource().FirstOrDefault(x => x.Id == id);
              if (user == null)
              return View("Error");
              return View(user); // Shows the Details view.
              }

              [HttpGet]
              public ActionResult Create()
              {
              var user = new User { AddDate = DateTime.Now };
              return View(user); // Shows the Create view.
              }

              [HttpPost]
              public ActionResult Create(User user)
              {
              if (this.ModelState.IsValid)
              {
              // todo: Add record
              return RedirectToAction("Index");
              }
              return View(user); // Shows the Create view again.
              }

              [HttpGet]
              public ActionResult Edit(int id)
              {
              var user = new Users().CreateInMemoryDataSource().FirstOrDefault(x => x.Id == id);
              if (user == null)
              return View("Error");
              return View(user); // Shows the Edit view.
              }

              [HttpPost]
              public ActionResult Edit(User user)
              {
              if (this.ModelState.IsValid)
              {
              // todo: Edit record
              return RedirectToAction("Index");
              }
              return View(user); // Shows the Edit view again.
              }

              [HttpPost]
              public ActionResult Delete(int id)
              {
              // todo: Delete record
              return RedirectToAction("Index");
              }
              }
              }

              توضیحات:

              ایجاد خودکار فرم‌های ورود اطلاعات

              در قسمت قبل برای توضیح دادن نحوه ایجاد فرم‌ها در ASP.NET MVC و همچنین نحوه نگاشت اطلاعات آن‌ها به اکشن متدهای کنترلرها، فرم‌های مورد نظر را دستی ایجاد کردیم.
              اما باید درنظر داشت که برای ایجاد Viewها می‌توان از ابزار توکار خود VS.NET نیز استفاده کرد و سپس اطلاعات و فرم‌های تولیدی را سفارشی نمود. این سریع‌ترین راه ممکن است زمانیکه مدل مورد استفاده کاملا مشخص است و می‌خواهیم Strongly typed views را ایجاد کنیم.
              برای نمونه بر روی متد Index کلیک راست کرده و گزینه Add view را انتخاب کنید. در اینجا گزینه‌ی create a strongly typed view را انتخاب کرده و سپس از لیست مدل‌ها، User را انتخاب نمائید. Scaffold template را هم بر روی حالت List قرار دهید.
              برای متد Details هم به همین نحو عمل نمائید.
              برای ایجاد View متناظر با متد Create در حالت HttpGet، تمام مراحل یکی است. فقط Scaffold template انتخابی را بر روی Create قرار دهید تا فرم ورود اطلاعات، به صورت خودکار تولید شود.
              متد Create در حالت HttpPost نیازی به View اضافی ندارد. چون صرفا قرار است اطلاعاتی را از سرور دریافت و ثبت کند.
              برای ایجاد View متناظر با متد Edit در حالت HttpGet، باز هم مراحل مانند قبل است با این تفاوت که Scaffold template انتخابی را بر روی گزینه Edit قرار دهید تا فرم ویرایش اطلاعات کاربر به صورت خودکار به پروژه اضافه شود.
              متد Edit در حالت HttpPost نیازی به View اضافی ندارد و کارش تنها دریافت اطلاعات از سرور و به روز رسانی بانک اطلاعاتی است.
              به همین ترتیب متد Delete نیز، نیازی به View خاصی ندارد. در اینجا بر اساس primary key دریافتی، می‌توان یک کاربر را یافته و حذف کرد.



              سفارشی سازی Viewهای خودکار تولیدی

              با کمک امکانات Scaffolding نامبرده شده، حجم قابل توجهی کد را در اندک زمانی می‌توان تولید کرد. بدیهی است حتما نیاز به سفارشی سازی کدهای تولیدی وجود خواهد داشت. مثلا شاید نیازی نباشد فیلد پسود کاربر، در حین نمایش لیست کاربران، نمایش داده شود. می‌شود کلا این ستون را حذف کرد و از این نوع مسایل.
              یک مورد دیگر را هم در Viewهای تولیدی حتما نیاز است که ویرایش کنیم. آن هم مرتبط است به لینک حذف اطلاعات یک کاربر در صفحه Index.cshtml:

              @Html.ActionLink("Delete", "Delete", new { id=item.Id }

              در قسمت قبل هم عنوان شد که اعمال حذف باید بر اساس HttpPost محدود شوند تا بتوان میزان امنیت برنامه را بهبود داد. متد Delete هم در کنترلر فوق تنها به حالت HttpPost محدود شده است. بنابراین ActionLink پیش فرض را حذف کرده و بجای آن فرم و دکمه زیر را قرار می‌دهیم تا اطلاعات به سرور Post شوند:

              @using (Html.BeginForm(actionName: "Delete", controllerName: "User", routeValues: new { id = item.Id }))
              {
              <input type="submit" value="Delete"
              onclick="return confirm ('Do you want to delete this record?');" />
              }

              در اینجا نحوه ایجاد یک فرم، که id رکورد متناظر را به سرور ارسال می‌کند، مشاهده می‌کنید.



              علت وجود دو متد، به ازای هر Edit یا Create

              به ازای هر کدام از متدهای Edit و Create دو متد HttpGet و HttpPost را ایجاد کرده‌ایم. کار متدهای HttpGet نمایش View‌های متناظر به کاربر هستند. بنابراین وجود آن‌ها ضروری است. در این حالت چون از دو Verb متفاوت استفاده شده، می‌توان متدهای هم نامی را بدون مشکل استفاده کرد. به هر کدام از افعال Get و Post و امثال آن، یک Http Verb گفته می‌شود.



              بررسی معتبر بودن اطلاعات دریافتی

              کلاس پایه Controller که کنترلرهای برنامه از آن مشتق می‌شوند، شامل یک سری خواص و متدهای توکار نیز هست. برای مثال توسط خاصیت this.ModelState.IsValid می‌توان بررسی کرد که آیا Model دریافتی معتبر است یا خیر. برای بررسی این مورد، یک breakpoint را بر روی سطر this.ModelState.IsValid در متد Create قرار دهید. سپس به صفحه ایجاد کاربر جدید مراجعه کرده و مثلا بجای تاریخ روز، abcd را وارد کنید. سپس فرم را به سرور ارسال نمائید. در این حالت مقدار خاصیت this.ModelState.IsValid مساوی false می‌باشد که حتما باید به آن پیش از ثبت اطلاعات دقت داشت.



              شبیه سازی عملکرد ViewState در ASP.NET MVC

              در متدهای Create و Edit در حالت Post، اگر اطلاعات Model معتبر نباشند، مجددا شیء User دریافتی، به View بازگشت داده می‌شود. چرا؟
              صفحات وب، زمانیکه به سرور ارسال می‌شوند، تمام اطلاعات کنترل‌های خود را از دست خواهد داد (صفحه پاک می‌شود، چون مجددا یک صفحه خالی از سرور دریافت خواهد شد). برای رفع این مشکل در ASP.NET Web forms، از مفهومی به نام ViewState کمک می‌گیرند. کار ViewState ذخیره موقت اطلاعات فرم جاری است برای استفاده مجدد پس از Postback. به این معنا که پس از ارسال فرم به سرور، اگر کاربری در textbox اول مقدار abc را وارد کرده بود، پس از نمایش مجدد فرم، مقدار abc را در همان textbox مشاهده خواهد کرد (شبیه سازی برنامه‌های دسکتاپ در محیط وب). بدیهی است وجود ViewState برای ذخیره سازی این نوع اطلاعات، حجم صفحه را بالا می‌برد (بسته به پیچیدگی صفحه ممکن است به چند صد کیلوبایت هم برسد).
              در ASP.NET MVC بجای استفاده از ترفندی به نام ViewState، مجددا اطلاعات همان مدل متناظر با View را بازگشت می‌دهند. در این حالت پس از ارسال صفحه به سرور و نمایش مجدد صفحه ورود اطلاعات، تمام کنترل‌ها با همان مقادیر قبلی وارد شده توسط کاربر قابل مشاهده خواهند بود (مدل مشخص است، View ما هم از نوع strongly typed می‌باشد. در این حالت فریم ورک می‌داند که اطلاعات را چگونه به کنترل‌های قرار گرفته در صفحه نگاشت کند).
              در مثال فوق، اگر اطلاعات وارد شده صحیح باشند، کاربر به صفحه Index هدایت خواهد شد. در غیراینصورت مجددا همان View جاری با همان اطلاعات model قبلی که کاربر تکمیل کرده است به او برای تصحیح، نمایش داده می‌شود. این مساله هم جهت بالا بردن سهولت کاربری برنامه بسیار مهم است. تصور کنید که یک فرم خالی با پیغام «تاریخ وارد شده معتبر نیست» مجدا به کاربر نمایش داده شود و از او درخواست کنیم که تمام اطلاعات دیگر را نیز از صفر وارد کند چون اطلاعات صفحه پس از ارسال به سرور پاک شده‌اند؛ که ... اصلا قابل قبول نیست و فوق‌العاده برنامه را غیرحرفه‌ای نمایش می‌دهد.



              خطاهای نمایش داده شده به کاربر

              به صورت پیش فرض خطایی که به کاربر نمایش داده می‌شود، استثنایی است که توسط فریم ورک صادر شده است. برای مثال نتوانسته است abcd را به یک تاریخ معتبر تبدیل کند. می‌توان توسط this.ModelState.AddModelError خطایی را نیز در اینجا اضافه کرد و پیغام بهتری را به کاربر نمایش داد. یا توسط یک سری data annotations هم کار اعتبار سنجی را سفارشی کرد که بحث آن به صورت جداگانه در یک قسمت مستقل بررسی خواهد شد.
              ولی به صورت خلاصه اگر به فرم‌های تولید شده توسط VS.NET دقت کنید، در ابتدای هر فرم داریم:

              @Html.ValidationSummary(true)

              در اینجا خطاهای عمومی در سطح مدل نمایش داده می‌شوند. برای اضافه کردن این نوع خطاها، در متد AddModelError، مقدار key را خالی وارد کنید:

              ModelState.AddModelError(string.Empty, "There is something wrong with model.");

              همچنین در این فرم‌ها داریم:
              @Html.EditorFor(model => model.AddDate)
              @Html.ValidationMessageFor(model => model.AddDate)

              EditorFor سعی می‌کند اندکی هوش به خرج دهد. یعنی اگر خاصیت دریافتی مثلا از نوع bool بود، خودش یک checkbox را در صفحه نمایش می‌دهد. همچنین بر اساس متادیتا یک خاصیت نیز می‌تواند تصمیم گیری را انجام دهد. این متادیتا منظور attributes و data annotations ایی است که به خواص یک مدل اعمال می‌شود. مثلا اگر ویژگی HiddenInput را به یک خاصیت اعمال کنیم، به شکل یک فیلد مخفی در صفحه ظاهر خواهد شد.
              یا متد Html.DisplayFor، اطلاعات را به صورت فقط خواندنی نمایش می‌دهد. اصطلاحا به این نوع متدها، Templated Helpers هم گفته می‌شود. بحث بیشتر درباره‌ای این موارد به قسمتی مجزا و مستقل موکول می‌گردد. برای نمونه کل فرم ادیت برنامه را حذف کنید و بجای آن بنویسید Html.EditorForModel و سپس برنامه را اجرا کنید. یک فرم کامل خودکار ویرایش اطلاعات را مشاهده خواهید کرد (و البته نکات سفارشی سازی آن به یک قسمت کامل نیاز دارند).
              در اینجا متد ValidationMessageFor کار نمایش خطاهای اعتبارسنجی مرتبط با یک خاصیت مشخص را انجام می‌دهد. بنابراین اگر قصد ارائه خطایی سفارشی و مخصوص یک فیلد مشخص را داشتید، در متد AddModelError، مقدار پارامتر اول یا همان key را مساوی نام خاصیت مورد نظر قرار دهید.


              مقابله با مشکل امنیتی Mass Assignment در حین کار با Model binders

              استفاده از Model binders بسیار لذت بخش است. یک شیء را به عنوان پارامتر اکشن متد خود معرفی می‌کنیم. فریم ورک هم در ادامه سعی می‌کند تا اطلاعات فرم را به خواص این شیء نگاشت کند. بدیهی است این روش نسبت به روش ASP.NET Web forms که باید به ازای تک تک کنترل‌های موجود در صفحه یکبار کار دریافت اطلاعات و مقدار دهی خواص یک شیء را انجام داد، بسیار ساده‌تر و سریعتر است.
              اما اگر همین سیستم پیشرفته جدید ناآگاهانه مورد استفاده قرار گیرد می‌تواند منشاء حملات ناگواری شود که به نام «Mass Assignment» شهرت یافته‌اند.
              همان صفحه ویرایش اطلاعات را درنظر بگیرید. چک باکس IsAdmin قرار است در قسمت مدیریتی برنامه تنظیم شود. اگر کاربری نیاز داشته باشد اطلاعات خودش را ویرایش کند، مثلا پسوردش را تغییر دهد، با یک صفحه ساده کلمه عبور قبلی را وارد کنید و دوبار کلمه عبور جدید را نیز وارد نمائید، مواجه خواهد شد. خوب ... اگر همین کاربر صفحه را جعل کند و فیلد چک باکس IsAdmin را به صفحه اضافه کند چه اتفاقی خواهد افتاد؟ بله ... مشکل هم همینجا است. در اینصورت کاربر عادی می‌تواند دسترسی خودش را تا سطح ادمین بالا ببرد، چون model binder اطلاعات IsAdmin را از کاربر دریافت کرده و به صورت خودکار به model ارائه شده، نگاشت کرده است.
              برای مقابله با این نوع حملات چندین روش وجود دارند:
              الف) ایجاد لیست سفید
              به کمک ویژگی Bind می‌توان لیستی از خواص را جهت به روز رسانی به model binder معرفی کرد. مابقی ندید گرفته خواهند شد:

              public ActionResult Edit([Bind(Include = "Name, Password")] User user)

              در اینجا تنها خواص Name و Password توسط model binder به خواص شیء User نگاشت می‌شوند.
              به علاوه همانطور که در قسمت قبل نیز ذکر شد، متد edit را به شکل زیر نیز می‌توان بازنویسی کرد. در اینجا متدهای توکار UpdateModel و TryUpdateModel نیز لیست سفید خواص مورد نظر را می‌پذیرند (اعمال دستی model binding):

              [HttpPost]
              public ActionResult Edit()
              {
              var user = new User();
              if(TryUpdateModel(user, includeProperties: new[] { "Name", "Password" }))
              {
              // todo: Edit record
              return RedirectToAction("Index");
              }
              return View(user); // Shows the Edit view again.
              }


              ب) ایجاد لیست سیاه
              به همین ترتیب می‌توان تنها خواصی را معرفی کرد که باید صرفنظر شوند:
              public ActionResult Edit([Bind(Exclude = "IsAdmin")] User user)

              در اینجا از خاصیت IsAdmin صرف نظر گردیده و از مقدار ارسالی آن توسط کاربر استفاده نخواهد شد.
              و یا می‌توان پارامتر excludeProperties متد TryUpdateModel را نیز مقدار دهی کرد.

              لازم به ذکر است که ویژگی Bind را به کل یک کلاس هم می‌توان اعمال کرد. برای مثال:

              using System;
              using System.Web.Mvc;

              namespace MvcApplication7.Models
              {
              [Bind(Exclude = "IsAdmin")]
              public class User
              {
              public int Id { set; get; }
              public string Name { set; get; }
              public string Password { set; get; }
              public DateTime AddDate { set; get; }
              public bool IsAdmin { set; get; }
              }
              }

              این مورد اثر سراسری داشته و قابل بازنویسی نیست. به عبارتی حتی اگر در متدی خاصیت IsAdmin را مجددا الحاق کنیم، تاثیری نخواهد داشت.
              یا می‌توان از ویژگی ReadOnly هم استفاده کرد:
              using System;
              using System.ComponentModel;

              namespace MvcApplication7.Models
              {
              public class User
              {
              public int Id { set; get; }
              public string Name { set; get; }
              public string Password { set; get; }
              public DateTime AddDate { set; get; }

              [ReadOnly(true)]
              public bool IsAdmin { set; get; }
              }
              }

              در این حالت هم خاصیت IsAdmin هیچگاه توسط model binder به روز و مقدار دهی نخواهد شد.

              ج) استفاده از ViewModels
              این راه حلی است که بیشتر مورد توجه معماران نرم افزار است و البته کسانی که پیشتر با الگوی MVVM کار کرده باشند این نام برایشان آشنا است؛ اما در اینجا مفهوم متفاوتی دارد. در الگوی MVVM، کلاس‌های ViewModel شبیه به کنترلرها در MVC هستند یا به عبارتی همانند رهبر یک اکستر عمل می‌کنند. اما در الگوی MVC خیر. در اینجا فقط مدل یک View هستند و نه بیشتر. هدف هم این است که بین Domain Model و View Model تفاوت قائل شد.
              کار View model در الگوی MVC، شکل دادن به چندین domain model و همچنین اطلاعات اضافی دیگری که نیاز هستند، جهت استفاده نهایی توسط یک View می‌باشد. به این ترتیب View با یک شیء سر و کار خواهد داشت و همچنین منطق شکل دهی به اطلاعات مورد نیازش هم از داخل View حذف شده و به خواص View model در زمان تشکیل آن منتقل می‌شود.
              مشخصات یک View model خوب به شرح زیر است:
              الف) رابطه بین یک View و View model آن، رابطه‌ای یک به یک است. به ازای هر View، بهتر است یک کلاس View model وجود داشته باشد.
              ب) View ساختار View model را دیکته می‌کند و نه کنترلر.
              ج) View modelها صرفا یک سری کلاس POCO (کلاس‌هایی تشکیل شده از خاصیت، خاصیت، خاصیت ....) هستند که هیچ منطقی در آن‌ها قرار نمی‌گیرد.
              د) View model باید حاوی تمام اطلاعاتی باشد که View جهت رندر نیاز دارد و نه بیشتر و الزامی هم ندارد که این اطلاعات مستقیما به domain models مرتبط شوند. برای مثال اگر قرار است firstName+LastName در View نمایش داده شود، کار این جمع زدن باید حین تهیه View Model انجام شود و نه داخل View. یا اگر قرار است اطلاعات عددی با سه رقم جدا کننده به کاربر نمایش داده شوند، وظیفه View Model است که یک خاصیت اضافی را برای تهیه این مورد تدارک ببیند. یا مثلا اگر یک فرم ثبت نام داریم و در این فرم لیستی وجود دارد که تنها Id عنصر انتخابی آن در Model اصلی مورد استفاده قرار می‌گیرد، تهیه اطلاعات این لیست هم کار ViewModel است و نه اینکه مدام به Model اصلی بخواهیم خاصیت اضافه کنیم.

              ViewModel چگونه پیاده سازی می‌شود؟
              اکثر مقالات را که مطالعه کنید، این روش را توصیه می‌کنند:

              public class MyViewModel
              {
                  public SomeDomainModel1 Model1 { get; set; }
                  public SomeDomainModel2 Model2 { get; set; }
                  ...
              }

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

              public class MyViewModel
              {
                  public string SomeExtraField1 { get; set; }
                  public string SomeExtraField2 { get; set; }
              public IEnumerable<SelectListItem> StateSelectList { get; set; }
              // ...
                  public string PersonFullName { set; set; }
              }

              در اینجا، View متناظری، قرار است نام کامل یک شخص را به علاوه یک سری اطلاعات اضافی که در domain model نیست، نمایش دهد. مثلا نمایش نام استان‌ها که نهایتا Id انتخابی آن قرار است در برنامه استفاده شود.
              خلاصه علت وجودی ViewModel این موارد است:
              الف) Model برنامه را مستقیما در معرض استفاده قرار ندهیم (عدم رعایت این نکته به مشکلات امنیتی حادی هم حین به روز رسانی اطلاعات ممکن است ختم ‌شود که پیشتر توضیح داده شد).
              ب) فیلدهای نمایشی اضافی مورد نیاز یک View را داخل Model برنامه تعریف نکنیم (مثلا تعاریف عناصر یک دراپ داون لیست، جایش اینجا نیست. مدل فقط نیاز به Id عنصر انتخابی آن دارد).

              با این توضیحات، اگر View به روز رسانی اطلاعات کلمه عبور کاربر، تنها به اطلاعات id آن کاربر و کلمه عبور او نیاز دارد، فقط باید همین اطلاعات را در اختیار View قرار داد و نه بیشتر:

              namespace MvcApplication7.Models
              {
              public class UserViewModel
              {
              public int Id { set; get; }
              public string Password { set; get; }
              }
              }

              به این ترتیب دیگر خاصیت IsAdming اضافه‌ای وجود ندارد که بخواهد مورد حمله واقع شود.



              استفاده از model binding برای آپلود فایل به سرور

              برای آپلود فایل به سرور تنها کافی است یک اکشن متد به شکل زیر را تعریف کنیم. HttpPostedFileBase نیز یکی دیگر از model binderهای توکار ASP.NET MVC است:

              [HttpGet]
              public ActionResult Upload()
              {
              return View(); // Shows the upload page
              }

              [HttpPost]
              public ActionResult Upload(System.Web.HttpPostedFileBase file)
              {
              string filename = Server.MapPath("~/files/somename.ext");
              file.SaveAs(filename);
              return RedirectToAction("Index");
              }

              View متناظر هم می‌تواند به شکل زیر باشد:

              @{
              ViewBag.Title = "Upload";
              }
              <h2>
              Upload</h2>
              @using (Html.BeginForm(actionName: "Upload", controllerName: "User",
              method: FormMethod.Post,
              htmlAttributes: new { enctype = "multipart/form-data" }))
              {
              <text>Upload a photo:</text> <input type="file" name="photo" />
              <input type="submit" value="Upload" />
              }

              اگر دقت کرده باشید در طراحی ASP.NET MVC از anonymously typed objects زیاد استفاده می‌شود. در اینجا هم برای معرفی enctype فرم آپلود، مورد استفاده قرار گرفته است. به عبارتی هر جایی که مشخص نبوده چه تعداد ویژگی یا کلا چه ویژگی‌ها و خاصیت‌هایی را می‌توان تنظیم کرد، اجازه تعریف آن‌ها را به صورت anonymously typed objects میسر کرده‌اند. یک نمونه دیگر آن در متد routes.MapRoute فایل Global.asax.cs است که پارامتر سوم دریافت مقدار پیش فرض‌ها نیز anonymously typed object است. یا نمونه دیگر آن‌را در همین قسمت در جایی که لینک delete را به فرم تبدیل کردیم مشاهده نمودید. مقدار routeValues هم یک anonymously typed object معرفی شد.



              سفارشی سازی model binder پیش فرض ASP.NET MVC

              در همین مثال فرض کنید تاریخ را به صورت شمسی از کاربر دریافت می‌کنیم. خاصیت تعریف شده هم DateTime میلادی است. به عبارتی model binder حین تبدیل رشته تاریخ شمسی دریافتی به تاریخ میلادی با شکست مواجه شده و نهایتا خاصیت this.ModelState.IsValid مقدارش false خواهد بود. برای حل این مشکل چکار باید کرد؟
              برای این منظور باید نحوه پردازش یک نوع خاص را سفارشی کرد. ابتدا با پیاده سازی اینترفیس IModelBinder شروع می‌کنیم. توسط bindingContext.ValueProvider می‌توان به مقداری که کاربر وارد کرده در میانه راه دسترسی یافت. آن‌را تبدیل کرده و نمونه صحیح را بازگشت داد.
              نمونه‌ای از این پیاده سازی را در ادامه ملاحظه می‌کنید:

              using System;
              using System.Globalization;
              using System.Web.Mvc;

              namespace MvcApplication7.Binders
              {
              public class PersianDateModelBinder : IModelBinder
              {

              public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
              {
              var valueResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
              var modelState = new ModelState { Value = valueResult };
              object actualValue = null;
              try
              {
              var parts = valueResult.AttemptedValue.Split('/'); //ex. 1391/1/19
              if (parts.Length != 3) return null;
              int year = int.Parse(parts[0]);
              int month = int.Parse(parts[1]);
              int day = int.Parse(parts[2]);
              actualValue = new DateTime(year, month, day, new PersianCalendar());
              }
              catch (FormatException e)
              {
              modelState.Errors.Add(e);
              }

              bindingContext.ModelState.Add(bindingContext.ModelName, modelState);
              return actualValue;
              }
              }
              }

              سپس برای معرفی PersianDateModelBinder جدید تنها کافی است سطر زیر را

              ModelBinders.Binders.Add(typeof(DateTime), new PersianDateModelBinder());

              به متد Application_Start قرار گرفته در فایل Global.asax.cs برنامه اضافه کرد. از این پس کاربران می‌توانند تاریخ‌ها را در برنامه شمسی وارد کنند و model binder بدون مشکل خواهد توانست اطلاعات ورودی را به معادل DateTime میلادی آن تبدیل کند و استفاده نماید.
              تعریف مدل بایندر سفارشی در فایل Global.asax.cs آن‌را به صورت سراسری در تمام مدل‌ها و اکشن‌متدها فعال خواهد کرد. اگر نیاز بود تنها یک اکشن متد خاص از این مدل بایندر سفارشی استفاده کند می‌توان به روش زیر عمل کرد:

              public ActionResult Create([ModelBinder(typeof(PersianDateModelBinder))] User user)

              همچنین ویژگی ModelBinder را به یک کلاس هم می‌توان اعمال کرد:

              [ModelBinder(typeof(PersianDateModelBinder))]
              public class User
              {


              مطالب
              آموزش VC++ از مقدماتی تا پیشرفته
              بحثی که بنده قصد آموزش آن را دارم آموزش  ++ C  در IDE  مایکروسافت  visual studio  می‌باشد . آموزش از پروژه‌های Win32 Console Application  شروع شده و قسمت پیشرفته آموزش در پروژه‌های Win32 Project ادامه می‌یابد .
              ...

              اولین پروژه

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

              ایجاد اولین پروژه  Win32 Console Application

              ویژوال استادیو را اجرا نمایید و از گزینه  File -> New -> Project و سپس طبق عکس زیر پروژه Win32 Console Application را انتخاب نمایید ، دقت کنید که زبان انتخاب شده ++Visual C باشد.

              در این مرحله میتوانید محل ذخیره شده پروژه را در قسمت Location  تنظیم نمایید و از قسمت  Name  میتوانید نام دلخواه را وارد کنید در حالت پیش فرض اگر اولین پروژه Win32 Console  در مسیر تعین شده‌ی قسمت  Location  باشد ، نام  ConsoleApplication1  قرار گرفته است . پس از تنظیمات Ok کنید .

              در این مرحله Next  را بزنید .

              در این مرحله در قسمت Additional options  تیک Empty project را بزنید ، همانند عکس فوق تنظیمات را انجام دهید .

              پس از انجام مراحل فوق پروژه بصورت شکل زیر ظاهر میشود .

              برای کد نویسی روی نام پروژه که در اینجا ConsoleApplication1 می‌باشد ، راست کلیک میکنیم و گزینه Add و سپس New Item  را انتخاب میکنیم .

              طبق عکس زیر فایل با پسوند cpp  را انتخاب و Add  میکنیم .

              فایلی که اکنون به پروژه اضافه کردیم خالی و با نام پیش فرض Source.cpp  می‌باشد ، دستورات زیر را در آن تایپ کنید .حال پروژه به شکل زیر خواهد بود .

              #include<iostream>
              
              int  main()
              {
                 std::cout<<"Hello world ...\n";
                 return 0;
              }

              برای اجرای پروژه کلید F5 را فشار دهید و اگر میخواهید نتیجه کار را مشاهده کنید کلید Ctrl + F5  را امتحان کنید .

              شما اولین پروژه VC++  را اجرا نمودید ( آفرین ) .

              اما توضیحات :

              خط اول برنامه یک راهنمای پیش پردازنده است ، کاراکتر # که نشان میدهد این خط یک راهنمای پیش پردازنده است و بعد عبارت include  و نام یک فایل کتابخانه ای که بین علامت <> قرار داده شده ،  فایل سرآیند استفاده شده در اینجا  iostream  میباشد . (به فایل‌های کتابخانه ای ، فایلهای سرآیند (Header Files) نیز گفته میشود. ) راهنمای پیش پردازنده خطی است که به کامپایلر اطلاع میدهد در برنامه موجودیتی است که تعریف آن را در فایل سرآیند مذکور جستجو کند . در این برنامه از std::cout  استفاده شده ، که کامپایلر در مورد آن چیزی نمیداند لذا به فایل iostream  مراجعه نموده ، تعریف آن را می‌یابد و آن را اجرا میکند . 

              خط 3 :

              بخشی از هر برنامه تابع می‌باشد . پرانتزهای واقع پس از  آن  main نشان می‌دهند که main یک بلوک برنامه بنام تابع است. برنامه‌ها می‌توانند حاوی یک یا چندین تابع  باشند، اما main  تابع اصلی برنامه است که وجود آن الزامی میباشد . کلمه کلیدی  int  که در سمت چپ main   قرار گرفته، بر این نکته دلالت دارد که main  یک مقدار صحیح برمی‌گرداند.

              خط 5 :

              با استفاده از این دستور رشته ای را به خروجی استاندارد که معمولا صفحه نمایش باشد ارسال میکنیم .

              خط 6 :

              که ;0 return میباشد مقدار برگشتی تابع را مشخص میکند در حقیقت این خط که مقدار 0 را برمیگرادند نشان دهنده اتمام موفقیت آمیز برنامه می‌باشد .

              به مرور زمان نسبت به موارد بالا بیشتر و مفصل صحبت خواهیم نمود .

              نظرات مطالب
              آشنایی با TransactionScope
              با سلام
              من روی پروژه‌ای کار می‌کنم که در اون سرور داده‌ها از سرور وب جداست. روی هردو سرور فایروال خاموش بوده و تنظیمات مربوط به Network DTC نیز روی هردو سرور انجام شده است(منظور تنظیمات زیر است)
              1-Notwork DTC Access
              2- Allow Remote Client
              3- Allow Remote Administration
              4-Allow Inbound
              5-Allow OutBound
              6 -No Authentication Required. 

              اما با وجود همه این تنظیمات در هنگام کارکردن با دیتابیس با خطای زیر مواجه میشم
              System.Transactions.TransactionManagerCommunicationException: Communication with the underlying transaction manager has failed 
              توی اینترنت خیلی سرچ کردم ولی همه معتقد هستند که مشکل فایروال هست در حالی که فایروال سرورها کلا خاموشه.
              نکته اینکه روی هردو سرور ویندوز سرور 2003 نصب هست. 
              آیا کسی هست که بتونه در این مورد منو راهنمایی کنه. ممنون
              مطالب
              آزمایش Web APIs توسط Postman - قسمت سوم - نکات تکمیلی ایجاد درخواست‌ها
              تا اینجا، یک دید کلی را نسبت به postman و توانمندی‌های آن پیدا کرده‌ایم. در ادامه قصد داریم، تعدادی نکته‌ی تکمیلی مهم را که در حین ساخت درخواست‌ها در postman، به آن‌ها نیاز پیدا خواهیم کرد، بررسی کنیم.


              ایجاد یک request bin جدید

              برای مشاهده‌ی محتوای ارسالی توسط postman بدون برپایی وب سرویس خاصی، از سایت requestbin استفاده خواهیم کرد. در اینجا با کلیک بر روی دکمه‌ی create a request bin، یک آدرس موقتی را مانند http://requestbin.fullcontact.com/1gdduy21 برای شما تولید می‌کند. می‌توان انواع و اقسام درخواست‌ها را به این آدرس ارسال کرد و سپس با ریفرش کردن آن در مرورگر، دقیقا محتوای ارسالی به سمت سرور را بررسی نمود.
              برای مثال اگر همین آدرس را در postman وارد کرده و سپس بر روی دکمه‌ی send آن کلیک کنیم، پس از ریفرش صفحه، چنین تصویری حاصل می‌شود:



              Encoding کوئری استرینگ‌ها در postman

              مثال زیر را درنظر بگیرید:


              در اینجا با استفاده از گرید ساخت Query Params، دو کوئری استرینگ جدید را ایجاد کرده‌ایم که در دومی، مقدار وارد شده، دارای فاصله است. اگر این درخواست را ارسال کنیم، مشاهده خواهیم کرد که مقدار ارسالی توسط آن encoded نیست:


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


              البته برای اینکه این گزینه درست عمل کند، نیاز است یکبار کل متن را انتخاب کرد و سپس بر روی آن کلیک راست نمود، تا انتخاب گزینه‌ی encode URI component، به درستی اعمال شود:



              امکان تعریف متغیرها در آدرس‌های HTTP

              Postman از امکان تعریف path variables پشتیبانی می‌کند. برای مثال مسیر api.example.com/users/5/contracts/2 را در نظر بگیرید که می‌تواند سبب نمایش اطلاعات قرارداد دوم کاربر پنجم شود. برای پویا کردن یک چنین آدرسی در postman می‌توان از مسیری مانند api.example.com/users/:userid/contracts/:contractid استفاده کرد:


              اگر به تصویر فوق دقت کنیم، متغیرهای شروع شده‌ی با :، در قسمت path variables ذکر شده‌اند و به سادگی قابل تغییر و مقدار دهی می‌باشند (در گریدی همانند گرید کوئری استرینگ‌ها) که برای آزمایش دستی، بسیار مفید هستند.


              امکان ارسال فایل‌ها به سمت سرور

              زمانیکه برای مثال، نوع درخواست به Post تغییر می‌کند، امکان تنظیم بدنه‌ی آن نیز مسیر می‌شود. در این حالت اگر گزینه‌ی form-data را انتخاب کنیم، با نزدیک کردن اشاره‌گر ماوس به هر ردیف جدید (پیش از ورود کلید آن ردیف)، می‌توان نوع Text و یا File را انتخاب کرد:


              در اینجا پس از انتخاب گزینه‌ی File، می‌توان علاوه بر تعیین کلید این ردیف، با استفاده از دکمه‌ی select files، چندین فایل را نیز برای ارسال انتخاب کرد:



              روش انتقال درخواست‌های پیچیده به Postman

              تا اینجا روش ساخت درخواست‌های متداولی را بررسی کردیم که آنچنان پیچیده، طولانی و به همراه جزئیات زیادی (مانند کوکی‌ها،انواع و اقسام هدرها و ...) نبودند. فرض کنید می‌خواهید درخواست ارسال یک امتیاز جدید را به مطلبی در سایت جاری، توسط Postman شبیه سازی کنیم. برای اینکار، توسط مرورگر کروم به سایت وارد شده و پس از لاگین و تنظیم خودکار کوکی‌های اعتبارسنجی، برگه‌ی developer tools مرورگر را باز کرده و در آن، قسمت network را انتخاب کنید:


              در اینجا گزینه‌ی preserve log را نیز انتخاب کنید تا پس از ارسال درخواستی، سابقه‌ی عملیات، پاک نشود. سپس به صورت معمولی به مطلبی امتیاز دهید. اکنون بر روی مدخل درخواست آن کلیک راست کرده و از منوی ظاهر شده، گزینه‌ی Copy->Copy as cURL را انتخاب کنید تا این عملیات و تمام جزئیات مرتبط با آن‌را تبدیل به یک دستور cURL کرده و به حافظه کپی کند.
              سپس در postman، از منوی بالای صفحه، سمت چپ آن، گزینه‌ی Import را انتخاب کنید:


              در ادامه این دستور کپی شده‌ی در حافظه را در قسمت Paste Raw Text، قرار دهید و بر روی دکمه‌ی import کلیک کنید:


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

              یک نکته‌ی مهم: در حالت انتخاب Copy->Copy as cURL در مرورگر کروم، دو گزینه‌ی cmd و bash موجود هستند. حالت bash را باید انتخاب کنید تا توسط postman به دسترسی parse شود. حالت cmd یک چنین خروجی مشکل داری را در postman تولید می‌کند که قابل ارسال به سمت سرور نیست:


              اما جزئیات حالت bash آن، به درستی parse شده‌است و قابلیت send مجدد را دارد:



              کار با کوکی‌ها در Postman

              کوکی‌ها نیز در اصل به صورت یک هدر HTTP به صورت خودکار توسط مرورگرها به سمت سرور ارسال می‌شوند؛ اما Postman روش ساده‌تری را برای تعریف و کار با آن‌ها ارائه می‌دهد (و ترجیح می‌دهد که بین کوکی‌ها و هدرها تفاوت قائل شود؛ هم در زمان ارسال و هم در زمان نمایش response که در کنار قسمت هدرهای دریافتی از سرور، لیست کوکی‌های دریافتی نیز به صورت مجزایی نمایش داده می‌شوند).


              با کلیک بر روی لینک Cookies، در ذیل قسمتی که آدرس یک درخواست تنظیم می‌شود، قسمت مدیریت کوکی‌ها نیز ظاهر خواهد شد که با انتخاب هر نامی در اینجا، می‌توان مقدار آن‌را ویرایش و یا حتی حذف کرد. در این لیست، تمام کوکی‌هایی را که تاکنون تنظیم کرده باشید، می‌توانید مشاهده کنید (و مختص به برگه‌ی خاصی نیست) و همانند قسمت مدیریت کوکی‌های یک مرورگر رفتار می‌کند.
              روش کار با آن نیز به این صورت است: ابتدا باید یک دومین را اضافه کنید. سپس ذیل دومین اضافه شده، دکمه‌ی Add cookie ظاهر می‌شود و به هر تعدادی که لازم باشد، می‌توان برای آن دومین کوکی تعریف کرد:


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


              به اشتراک گذاری سابقه‌ی درخواست‌ها

              در قسمت اول مشاهده کردیم که برای ذخیره سازی درخواست‌ها، باید آن‌ها را در مجموعه‌های Postman، ذخیره و ساماندهی کرد. برای گرفتن خروجی از این مجموعه‌ها و به اشتراک گذاشتن آن‌ها، اگر اشاره‌گر ماوس را بر روی نام یک مجموعه حرکت دهیم، یک دکمه‌ی جدید با برچسب ... ظاهر می‌شود:


              با کلیک بر روی آن، یکی از گزینه‌های آن، export نام دارد که جزئیات تمام درخواست‌های یک مجموعه را به صورت یک فایل JSON ذخیره می‌کند. برای نمونه فایل JSON خروجی دو قسمت قبل این سری را می‌توانید از اینجا دریافت کنید: httpbin.postman_collection.json
              پس از تولید این فایل JSON، برای بازیابی آن می‌توان از دکمه‌ی Import که در منوی سمت چپ، بالای postman قرار دارد، استفاده کرد که نمونه‌ای از آن‌را برای Import جزئیات درخواست‌های cURL پیشتر مشاهده کردید. در اینجا، همان اولین گزینه‌ی دیالوگ Import که Import file نام دارد، دقیقا برای همین منظور تدارک دیده شده‌است.