مسیرراه‌ها
ASP.NET MVC
              مطالب دوره‌ها
              استفاده از async و await در برنامه‌های ASP.NET Web forms 4.5
              سؤال: چه زمانی از متدهای async و چه زمانی از متدهای همزمان بهتر است استفاده شود؟

              از متدهای همزمان متداول برای انجام امور ذیل استفاده نمائید:
              - جهت پردازش اعمالی ساده و سریع
              - اعمال مدنظر بیشتر قرار است بر روی CPU اجرا شوند و از مرزهای IO سیستم عبور نمی‌کنند.

              و از متدهای غیرهمزمان برای پردازش موارد زیر کمک بگیرید:
              - از وب سرویس‌هایی استفاده می‌کنید که متدهای نگارش async را نیز ارائه داده‌اند.
              - عمل مدنظر network-bound و یا I/O-bound است بجای CPU-bound. یعنی از مرزهای IO سیستم عبور می‌کند.
              - نیاز است چندین عملیات را به موازات هم اجرا کرد.
              - نیاز است مکانیزمی را جهت لغو یک عملیات طولانی ارائه دهید.


              مزایای استفاده از متدهای async در ASP.NET

              استفاده از await در ASP.NET، ساختار ذاتی پروتکل HTTP را که اساسا یک synchronous protocol، تغییر نمی‌دهد. کلاینت، درخواستی را ارسال می‌کند و باید تا زمان آماده شدن نتیجه و بازگشت آن از طرف سرور، صبر کند. نحوه‌ی تهیه‌ی این نتیجه، خواه async باشد و یا حتی همزمان، از دید مصرف کننده کاملا مخفی است. اکنون سؤال اینجا است که چرا باید از متدهای async استفاده کرد؟
              - پردازش موازی: می‌توان چند Task را مثلا توسط Task.WhenAll به صورت موازی با هم پردازش کرده و در نهایت نتیجه را سریعتر به مصرف کننده بازگشت داد. اما باید دقت داشت که این Taskها اگر I/O bound باشند، ارزش پردازش موازی را دارند و اگر compute bound باشند (اعمال محاسباتی)، صرفا یک سری ترد را ایجاد و مصرف کرده‌اید که می‌توانسته‌اند به سایر درخواست‌های رسیده پاسخ دهند.
              - خالی کردن تردهای در حال انتظار: در اعمالی که disk I/O و یا network I/O دارند، پردازش موازی و اعمال async به شدت مقیاس پذیری سیستم را بالا می‌برند. به این ترتیب worker thread جاری (که تعداد آن‌ها محدود است)، سریعتر آزاد شده و به worker pool بازگشت داده می‌شود تا بتواند به یک درخواست دیگر رسیده سرویس دهد. در این حالت می‌توان با منابع کمتری، درخواست‌های بیشتری را پردازش کرد.


              ایجاد Asynchronous HTTP Handlers در ASP.Net 4.5

              در نگارش‌های پیش از دات نت 4.5، برای نوشتن فایل‌های ashx غیرهمزمان می‌بایستی اینترفیس IHttpAsynchHandler پیاده سازی می‌شد که نحوه‌ی کار با آن از مدل APM پیروی می‌کرد؛ نیاز به استفاده از یک سری callback داشت و این عملیات باید طی دو متد پردازش می‌شد. اما در دات نت 4.5 و با معرفی امکانات async و await، نگارش سازگاری با پیاده سازی کلاس پایه HttpTaskAsyncHandler فراهم شده است.
              برای آزمایش آن، یک برنامه‌ی جدید ASP.NET Web forms نگارش 4.5 یا بالاتر را ایجاد کنید. سپس از منوی پروژه، گزینه‌ی Add new item یک Generic handler به نام LogRequestHandler.ashx را به پروژه اضافه نمائید.
              زمانیکه این فایل به پروژه اضافه می‌شود، یک چنین امضایی را دارد:
               public class LogRequestHandler : IHttpHandler
              IHttpHandler آن‌را اکنون به HttpTaskAsyncHandler تغییر دهید. سپس پیاده سازی ابتدایی آن به شکل زیر خواهد بود:
              using System;
              using System.Net;
              using System.Text;
              using System.Threading.Tasks;
              using System.Web;
              
              namespace Async14
              {
                  public class LogRequestHandler : HttpTaskAsyncHandler
                  {
                      public override async Task ProcessRequestAsync(HttpContext context)
                      {
                          string url = context.Request.QueryString["rssfeedURL"];
                          if (string.IsNullOrWhiteSpace(url))
                          {
                              context.Response.Write("Rss feed URL is not provided");
                          }
              
                          using (var webClient = new WebClient {Encoding = Encoding.UTF8})
                          {
                              webClient.Headers.Add("User-Agent", "LogRequestHandler 1.0");
                              var rssfeed = await webClient.DownloadStringTaskAsync(url);
                              context.Response.Write(rssfeed);
                          }
                      }
              
                      public override bool IsReusable
                      {
                          get { return true; }
                      }
              
                      public override void ProcessRequest(HttpContext context)
                      {
                          throw new Exception("The ProcessRequest method has no implementation.");
                      }
                  }
              }
              واژه‌ی کلیدی async را نیز جهت استفاده از await به نسخه‌ی غیرهمزمان آن اضافه کرده‌ایم.
              در این مثال آدرس یک فید RSS از طریق کوئری استرینگ rssfeedURL دریافت شده و سپس محتوای آن به کمک متد DownloadStringTaskAsync دریافت و بازگشت داده می‌شود.
              برای آزمایش آن، مسیر ذیل را درخواست دهید:
               http://localhost:4207/LogRequestHandler.ashx?rssfeedURL=https://www.dntips.ir/feed/latestchanges
              کاربردهای فایل‌های ashx برای مثال ارائه فید‌های XML ایی یک سایت، ارائه منبع نمایش تصاویر پویا از بانک اطلاعاتی، ارائه JSON برای افزونه‌های auto complete جی‌کوئری و امثال آن است. مزیت آن‌ها سربار بسیار کم است؛ زیرا وارد چرخه‌ی طول عمر یک صفحه‌ی aspx معمولی نمی‌شوند.


              صفحات async در ASP.NET 4.5

              در قسمت‌های قبل مشاهده کردیم که در برنامه‌های دسکتاپ، به سادگی می‌توان امضای روال‌های رخداد گردان را به async تغییر داد و ... برنامه کار می‌کند. به علاوه از مزیت استفاده از واژه کلیدی await نیز در آن‌ها برخوردار خواهیم شد. اما ... هرچند این روش در وب فرم‌ها نیز صادق است (مثلا public void Page_Load را به  public async void Page_Load می‌توان تبدیل کرد) اما اعضای تیم ASP.NET آن‌را در مورد برنامه‌های وب فرم توصیه نمی‌کنند:
              Async void event handlers تنها در مورد تعداد کمی از روال‌های رخدادگردان ASP.NET Web forms کار می‌کنند و از آن‌ها تنها برای تدارک پردازش‌های ساده می‌توان استفاده کرد. اگر کار در حال انجام اندکی پیچیدگی دارد، «باید» از PageAsyncTask استفاده نمائید. علت اینجا است که Async void یعنی fire and forget (کاری را شروع کرده و فراموشش کنید). این روش در برنامه‌های دسکتاپ کار می‌کند، زیرا این برنامه‌ها مدل طول عمر متفاوتی داشته و تا زمانیکه برنامه از طرف OS خاتمه نیابد، مشکلی نخواهند داشت. اما برنامه‌های بدون حالت وب متفاوتند. اگر عملیات async پس از خاتمه‌ی طول عمر صفحه پایان یابد، دیگر نمی‌توان اطلاعات صحیحی را به کاربر ارائه داد. بنابراین تا حد ممکن از تعاریف async void در برنامه‌های وب خودداری کنید.

              تبدیل روال‌های رخدادگردان متداول وب فرم‌ها به نسخه‌ی async شامل دو مرحله است:
              الف) از متد جدید RegisterAsyncTask که در کلاس پایه Page قرار دارد برای تعریف یک PageAsyncTask استفاده کنید:
              using System;
              using System.Net;
              using System.Text;
              using System.Threading.Tasks;
              using System.Web.UI;
              
              namespace Async14
              {
                  public partial class _default : Page
                  {
                      protected void Page_Load(object sender, EventArgs e)
                      {
                          RegisterAsyncTask(new PageAsyncTask(LoadSomeData));
                      }
              
                      public async Task LoadSomeData()
                      {
                          using (var webClient = new WebClient { Encoding = Encoding.UTF8 })
                          {
                              webClient.Headers.Add("User-Agent", "LogRequest 1.0");
                              var rssfeed = await webClient.DownloadStringTaskAsync("url");
              
                              //listcontacts.DataSource = rssfeed;
                          }
                      }
                  }
              }
              با استفاده از System.Web.UI.PageAsyncTask می‌توان یک async Task را در روال‌های رخدادگردان ASP.NET مورد استفاده قرار داد.

              ب) سپس در کدهای فایل aspx، نیاز است خاصیت async را نیز true نمائید:
               <%@ Page Language="C#" AutoEventWireup="true"
              Async="true"
                CodeBehind="default.aspx.cs" Inherits="Async14._default" %>


              تغییر تنظیمات IIS برای بهره بردن از پردازش‌های Async

              اگر از ویندوزهای 7، ویستا و یا 8 استفاده می‌کنید، IIS آن‌ها به صورت پیش فرض به 10 درخواست همزمان محدود است.
              بنابراین تنظیمات ذیل مرتبط است به یک ویندوز سرور و نه یک work station :
              به IIS manager مراجعه کنید. سپس برگه‌ی Application Pools آن‌را باز کرده و بر روی Application pool برنامه خود کلیک راست نمائید. در اینجا گزینه‌ی Advanced Settings را انتخاب کنید. در آن Queue Length را به مثلا عدد 5000 تغییر دهید. همچنین در دات نت 4.5 عدد 5000 برای MaxConcurrentRequestsPerCPU نیز مناسب است. به علاوه عدد connectionManagement/maxconnection را نیز به 12 برابر تعداد هسته‌های موجود تغییر دهید.
              مطالب
              معرفی System.Text.Json در NET Core 3.0.
              معروفترین کتابخانه‌ی کار با JSON در دات نت، Json.NET است که این روزها، جزء جدایی ناپذیر حداقل، تمام برنامه‌های وب مبتنی بر دات نت می‌باشد. برای مثال ASP.NET Core 2x/1x و همچنین ASP.NET Web API پیش از NET Core.، به صورت پیش‌فرض از این کتابخانه برای کار با JSON استفاده می‌کنند. این کتابخانه 10 سال پیش ایجاد شد و در طول زمان، قابلیت‌های زیادی به آن اضافه شده‌است. همین حجم بالای کار صورت گرفته سبب شده‌است که برای مثال شروع به استفاده‌ی از <Span<T در آن برای بالابردن کارآیی، بسیار مشکل شده و نیاز به تغییرات اساسی در آن داشته باشد. به همین جهت خود تیم CoreFX دات نت Core گزینه‌ی دیگری را برای کار با JSON در فضای نام جدید System.Text.Json ارائه داده‌است که برای کار با آن نیاز به نصب وابستگی ثالثی نیست و همچنین کارآیی آن به علت استفاده‌ی از ویژگی‌های جدید زبان، مانند ref struct و Span، به طور میانگین دو برابر کتابخانه‌ی Json.NET است. برای مثال استفاده‌ی از string (حالت پیش‌فرض کتابخانه‌ی Json.NET) یعنی کار با رشته‌هایی از نوع UTF-16؛ اما کار با Span، امکان دسترسی مستقیم به رشته‌هایی از نوع UTF-8 را میسر می‌کند که نیازی به تبدیل به رشته‌هایی از نوع UTF-16 را ندارند.


              ASP.NET Core 3x دیگر به صورت پیش‌فرض به همراه Json.NET ارائه نمی‌شود

              در برنامه‌های ASP.NET Core 3x، وابستگی ثالث Json.NET حذف شده‌است و از این پس هر نوع خروجی JSON آن، مانند بازگشت مقادیر مختلف از اکشن متدهای کنترلرها، به صورت خودکار در پشت صحنه از امکانات ارائه شده‌ی در System.Text.Json استفاده می‌کند و دیگر Json.NET، کتابخانه‌ی پیش‌فرض کار با JSON آن نیست. بنابراین برای کار با آن نیاز به تنظیم خاصی نیست. همینقدر که یک پروژه‌ی جدید ASP.NET Core 3x را ایجاد کنید، یعنی در حال استفاده‌ی از System.Text.Json هستید.


              روش بازگشت به Json.NET در ASP.NET Core 3x

              اگر به هر دلیلی هنوز نیاز به استفاده‌ی از کتابخانه‌ی Json.NET را دارید، آداپتور ویژه‌ی آن نیز تدارک دیده شده‌است. برای اینکار:
              الف) ابتدا باید بسته‌ی نیوگت Microsoft.AspNetCore.Mvc.NewtonsoftJson را نصب کنید.
              ب) سپس در کلاس Startup، باید این کتابخانه را به صورت یک سرویس جدید، با فراخوانی متد AddNewtonsoftJson، معرفی کرد:
               public void ConfigureServices(IServiceCollection services)
               {
                   services.AddControllers()
                          .AddNewtonsoftJson()
                   // ...
              }
              یکی از دلایل بازگشت به Json.NET می‌تواند عدم پشتیبانی از OpenAPI / Swagger در حین کار با System.Text.Json باشد و این مورد قرار نیست در نگارش نهایی 3.0، حضور داشته باشد و انطباق با آن به نگارش‌های بعدی موکول شده‌است.


              روش کار مستقیم با System.Text.Json

              اگر در قسمتی از برنامه‌ی خود نیاز به کار مستقیم با اشیاء JSON را داشته باشید و یا حتی بخواهید از این قابلیت در برنامه‌های کنسول و یا کتابخانه‌ها نیز استفاده کنید، روش انتقال کدهایی که از Json.NET استفاده می‌کنند به System.Text.Json، به صورت زیر است:
              public class Person
              {
                 public string FirstName { get; set; }
                 public string LastName { get; set; }
                 public DateTime? BirthDay { get; set; }
              }
              تبدیل رشته‌ی JSON حاوی اطلاعات شخص، به شیء متناظر با آن و یا حالت عکس آن:
              using System;
              using System.Text.Json.Serialization;
              
              namespace ConsoleApp
              {
                  class Program
                  {
                      static void Main(string[] args)
                      {
                          Person person = JsonSerializer.Parse<Person>(...);
                          string json = JsonSerializer.ToString(person);
                      }
                  }
              }
              در اینجا از کلاس System.Text.Json.Serialization.JsonSerializer، روش کار با دو متد Parse را برای Deserialization و ToString را برای Serialization مشاهده می‌کنید.
              کلاس JsonSerializer دارای overloadهای زیر برای کار با متدهای Parse و ToString است:
              namespace System.Text.Json.Serialization
              {
                  public static class JsonSerializer
                  {
                      public static object Parse(ReadOnlySpan<byte> utf8Json, Type returnType, JsonSerializerOptions options = null);
                      public static object Parse(string json, Type returnType, JsonSerializerOptions options = null);
                      public static TValue Parse<TValue>(ReadOnlySpan<byte> utf8Json, JsonSerializerOptions options = null);
                      public static TValue Parse<TValue>(string json, JsonSerializerOptions options = null);
              
                      public static string ToString(object value, Type type, JsonSerializerOptions options = null);
                      public static string ToString<TValue>(TValue value, JsonSerializerOptions options = null);
                  }
              }
              یک نکته: کارآیی متد Parse با امضای ReadOnlySpan<byte> utf8Json، بیشتر است از نمونه‌ای که string json را می‌پذیرد. از این جهت که چون با آرایه‌ای از بایت‌های رشته‌ای از نوع UTF-8 کار می‌کند، نیاز به تبدیل به UTF-16 را مانند متدی که string را می‌پذیرد، ندارد. برای تولید آرایه‌ی بایت‌های utf8Json از روی یک شیء، می‌توانید از متد JsonSerializer.ToUtf8Bytes استفاده کنید و یا برای تولید آن از روی یک رشته، از متد Encoding.UTF8.GetBytes استفاده کنید.


              سفارشی سازی JsonSerializer جدید

              اگر به امضای متدهای Parse و ToString کلاس JsonSerializer دقت کنید، دارای یک پارامتر اختیاری از نوع JsonSerializerOptions نیز هستند که به صورت زیر تعریف شده‌است:
              public sealed class JsonSerializerOptions
              {
                 public bool AllowTrailingCommas { get; set; }
                 public int DefaultBufferSize { get; set; }
                 public JsonNamingPolicy DictionaryKeyPolicy { get; set; }
                 public bool IgnoreNullValues { get; set; }
                 public bool IgnoreReadOnlyProperties { get; set; }
                 public int MaxDepth { get; set; }
                 public bool PropertyNameCaseInsensitive { get; set; }
                 public JsonNamingPolicy PropertyNamingPolicy { get; set; }
                 public JsonCommentHandling ReadCommentHandling { get; set; }
                 public bool WriteIndented { get; set; }
              }
              برای نمونه معادل تنظیم NullValueHandling در Json.NET:
              // Json.NET:
              var settings = new JsonSerializerSettings
              {
                  NullValueHandling = NullValueHandling.Ignore
              };
              string json = JsonConvert.SerializeObject(person, settings);
              اینبار توسط خاصیت IgnoreNullValues صورت می‌گیرد:
              // JsonSerializer:
              var options = new JsonSerializerOptions
              {
                  IgnoreNullValues = true
              };
              string json = JsonSerializer.ToString(person, options);

              در برنامه‌های ASP.NET Core که این نوع متدها در پشت صحنه فراخوانی می‌شوند، روش تنظیم JsonSerializerOptions به صورت زیر است:
              services.AddControllers()
                 .AddJsonOptions(options => options.JsonSerializerOptions.WriteIndented = true);


              نگاشت نام ویژه‌ی خواص در حین عملیات deserialization

              در مثال فوق، فرض شده‌است که نام خاصیت BirthDay، دقیقا با اطلاعاتی که از رشته‌ی JSON دریافتی پردازش می‌شود، تطابق دارد. اگر این نام در اطلاعات دریافتی متفاوت است، می‌توان از ویژگی JsonPropertyName برای تعریف این نگاشت استفاده کرد:
              [JsonPropertyName("birthdate")]
              public DateTime? BirthDay { get; set; }
              روش دیگر اینکار، برای نمونه تنظیم PropertyNamingPolicy به حالت CamelCase است:
              var options = new JsonSerializerOptions
              {
                 PropertyNamingPolicy = JsonNamingPolicy.CamelCase
              };
              string json = JsonSerializer.ToString(person, options);
              و یا اگر می‌خواهید حساسیت به بزرگی و کوچکی حروف را ندید بگیرید (مانند حالت پیش‌فرض JSON.NET) از تنظیم JsonSerializerOptions.PropertyNameCaseInsensitive استفاده کنید.

              در این بین اگر نمی‌خواهید خاصیتی در عملیات serialization و یا برعکس آن پردازش شود، می‌توان از تعریف ویژگی [JsonIgnore] بر روی آن استفاده کرد.
              نظرات مطالب
              یافتن لیست اسمبلی‌های ارجاعی
              سلام
              نیازی نبود سورسی را Map کنید یا اصلا نباید این‌کار را می‌کردید. (اگر منظور مپ کردن سورس بوده نه فایل اجرایی برنامه)
              SVN وظیفه مدیریت و به اشتراک گذاشتن پروژه رو داره، نه شبکه ویندوزی یا لینوکسی. (حتما از visual svn server استفاده کنید تا این موارد را برای شما ساده کند)
              کلاینت‌ها هر کدام نسخه‌ی کامل و لوکال خودشون رو باید داشته باشند (از طریق check out مخزن کد این پروژه لوکال باید تشکیل شود نه کپی دستی). سپس مثل اینکه دارند لوکال کار می‌کنند (نه از روی شبکه در حالت مپ شده). کاملا حالت معمولی و قطع از شبکه. SVN برای مدیریت پروژه روی اینترنت هم بکار میره. نه چیزی map میشه و نه لازم هست کاربر همیشه به شبکه وصل باشه.
              نسخه کد شما که روی سرور هست و توسط SVN نگهداری می‌شود، مخزن اصلی است که تغییرات با آن هماهنگ می‌شود. برای انتقال کد به مخزن، باید عملیات check in صورت گیرد.
              بعد هر کدام از اعضای تیم زمانیکه check out می‌کنند ، یک نسخه‌ی محلی دریافت می‌کنند و این فولدر تحت کنترل SVN قرار میگیره، حالا مباحث update و commit و غیره کار می‌کند. فقط هر بار که می‌خواهند commit کنند باید اول update کنند ببینند کسی چیزی را تغییر داده، تصادمی هست یا نه؟ بعد commit‌ کنند به سرور. (یعنی ارتباط با شبکه فقط در همین چند لحظه کوتاه است و بسیار سریع هم خواهد بود)
              فصل دوم کتابچه‌ای را که من تهیه کردم لطفا مطالعه کنید، گردش کاری آن توضیح داده شده است.
              https://www.dntips.ir/2008/10/subversion.html
              در فصل یک هم توضیح دادم که چه پورتی را باید روی سرور باز کنید تا SVN توسط فایروال بلاک نشود.

              حتما توصیه می‌کنم اگر با VS.Net کار می‌کنید افزونه Visual SVN را نصب کنید تا راحت و بدون دردسر کار کنید .


              خطایی رو که توضیح دادید مربوط هست به اشتراک گذاشتن فایل اجرایی یک برنامه دات نتی روی شبکه که این خطا رو می‌گیرید:
              Project Location Is not Trusted
              این خطا رو با دادن full trust به برنامه می‌تونید حل کنید که اینجا قدم به قدم توضیح داده شده:
              http://msdn.microsoft.com/en-us/library/bs2bkwxc(VS.80).aspx
              همچنین کامنت‌های اون رو لطفا بخونید. مثلا ممکن هست فایل بلاک شده باشه که با کلیک راست و unblock کردن مشکل حل میشه.

              خطای زیر بیشتر مربوط به حالتی است که الف) هنوز مخزن کد ایجاد نشده و ب) عملیات initial import یا check in صورت نگرفته
              Unable to open an ra_local url. unable to open repository.
              حتما کتابچه فوق را مطالعه نمایید.
              اشتراک‌ها
              راهنمای پروژه‌های متوسط به بالا در سال 2019

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

              راهنمای پروژه‌های متوسط به بالا در سال 2019
              مطالب
              پَرباد - آموزش پیاده‌سازی پرداخت آنلاین در دات نت - تنظیمات
              در قسمت قبل یاد گرفتیم چگونه عملیات پرداخت را انجام دهیم. در این قسمت قصد داریم با تنظیمات پَرباد آشنا شویم.

              این تنظیمات در حالت کلی شامل موارد زیر است:

              • درگاه‌ها (اجباری)
              • HttpContext (اجباری)
              • پایگاه داده (اجباری)
              • پیام‌ها (اختیاری)

              روش‌های تنظیم:
              • وارد کردن تنظیمات به صورت ثابت (استاتیک)
              • تنظیم به صورت داینامیک (برای مثال استفاده از یک منبع، مانند پایگاه داده وب سایت شما)
              • تنظیم توسط اینترفیس مایکروسافت IConfiguration

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

              برای روش اول، تنظیمات در حالت کلی به صورت زیر است:
              (نمونه مثال در یک اپلیکیشن ASP.NET CORE)
              using Parbad.Builder;
              
              public void ConfigureServices(IServiceCollection services)
              {
                  services.AddMvc();
              
                  services.AddParbad()
                       // .configurations
                       // .configurations
                       // .configurations
              }
              همانطور که می‌بینید، با استفاده از سرویس موجود در اپلیکیشن، به راحتی می‌توانید تنظیمات مورد نیاز را انجام دهید.

              و برای روش دوم، تنظیمات در حالت کلی به صورت زیر است:
              (نمونه مثال در یک اپلیکیشن ASP.NET MVC)
              using Parbad.Builder;
              
              public class Startup
              {
                  public void Configuration(IAppBuilder app)
                  {
                      ParbadBuilder.CreateDefaultBuilder()
                                // .configurations
                                // .configurations
                                // .configurations
                  }
              }


              اکنون، با توجه به اینکه با روش‌های مختلف تنظیمات آشنا شدید، برای ادامه توضیحات و مثال‌ها (صرفا جهت نوشتن راحت‌تر این مطلب) از همان روش اول استفاده می‌کنیم.

              تنظیمات درگاه‌ها

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

              روش اول: وارد کردن اطلاعات ثابت

              نمونه کد‌های تنظیم درگاه بانک ملت:
              services.AddParbad()
                      .ConfigureGateways(gateways =>
                      {
                          gateways
                              .AddMellat()
                              .WithOptions(options =>
                              {
                                   options.TerminalId = 123;
                                   options.UserName = "MyId";
                                   options.UserPassword = "MyPassword";
                              });
                      });
              نکته: تنظیم سایر درگاه‌ها نیز کاملا مشابه فرمت کد‌های بالا است. 

              روش دوم: تنظیم به صورت داینامیک

              برای تنظیم به صورت داینامیک، کلاسی را تعریف کنید که اینترفیس IParbadOptionsProvider را پیاده‌سازی می‌کند. مقدار T در این اینترفیس، معادل کلاس مورد نظر جهت تنظیم است.
              مثال: تنظیم درگاه ملت توسط یک منبع:
              ما قصد داریم اطلاعات مربوط به درگاه بانک ملت را از پایگاه داده فروشگاه خود دریافت کنیم. بنابراین یک منبع را به صورت زیر تعریف می‌کنیم:
              public class MellatOptionsProvider : IParbadOptionsProvider<MellatGatewayOptions>
              {
                  private readonly IMySettingsService _settingsService;
              
                  public MellatOptionsProvider(IMySettingsService settingsService)
                  {
                      _settingsService = settingsService;
                  }
              
                  public void Provide(MellatGatewayOptions options)
                  {
                      var settings = _settingsService.GetSettings();
              
                      options.TerminalId = settings.TerminalId;
                      options.UserName = settings.UserName;
                      options.UserPassword = settings.UserPassword;
                  }
              }
              کد بالا اطلاعات مربوط به درگاه بانک ملت را از پایگاه داده (وب سایت شما) دریافت کرده و سپس در متد Provide، آنها را نسبت می‌دهد.
              نکته: همانطور که در مثال بالا می‌بینید، در تعریف یک منبع، شما همچنین قادر به تزریق وابستگی‌ها نیز هستید (در صورت نیاز). بدیهی است که در اینجا، اینترفیس IMySettingsService توسط تزریق وابستگی اپلیکیشن شما باید ثبت شده باشد، در غیر اینصورت پَرباد قادر به ساخت منبع شما نخواهد بود.
              در نهایت منبع را به پَرباد معرفی می‌کنیم:
              services.AddParbad()
                      .ConfigureGateways(gateways =>
                      {
                          gateways
                              .AddMellat()
                              .WithOptionsProvider<MellatOptionsProvider>(ServiceLifetime.Transient);
                      });
              نکته: ServiceLifetime در اینجا تعیین کننده طول عمر منبع ما است.
              نمونه مثال کامل را در اینجا می‌توانید پیدا کنید.

              روش سوم: تنظیم توسط IConfiguration

              اگر با اپلیکیشن‌های ASP.NET CORE آشنایی داشته باشید، پس قطعا IConfiguration را نیز می‌شناسید. این اینترفیس به شما کمک می‌کند تنظیمات مورد نیاز در یک اپلیکیشن را از منابع مختلفی (مانند فایل‌های JSON ) دریافت و استفاده کنید.
              کد زیر نمونه تنظیم درگاه بانک ملت، با استفاده از IConfiguration و یک فایل JSON است.
              services.AddParbad()
                      .ConfigureGateways(gateways =>
                      {
                          gateways
                              .AddMellat()
                              .WithConfiguration(IConfiguration.GetSection("Mellat");
                      });

              و محتوای فایل JSON:
              "Mellat": {
                  "TerminalId": 123,
                  "UserName": "MyUsername",
                  "UserPassword": "MyPassword"
              }



              تنظیمات HttpContext

              پَرباد برای تبادل اطلاعات با درگاه‌های بانکی، نیاز به یک HttpContext دارد.

              ASP.NET WebForms, ASP.NET MVC
              ParbadBuilder.CreateDefaultBuilder()
                           .ConfigureHttpContext(builder => builder.UseOwinFromCurrentHttpContext());


              در کد بالا، پَرباد HttpContext مورد نیاز خود را توسط Owin تامین می‌کند. متد UseOwin همچنین شامل گزینه‌های دیگری جهت تنظیمات بیشتر نیز می‌باشد.

              ASP.NET CORE
              services.AddParbad()
                      .ConfigureHttpContext(builder => builder.UseDefaultAspNetCore());
              در کد بالا، پَرباد از اینترفیس پیش فرض IHttpContextAccessor در اپلیکیشن ASP.NET CORE استفاده می‌کند.

              نکته: اگر این اینترفیس قبلا توسط شما و یا اپلیکیشن شما ثبت شده باشد، پَرباد از آن استفاده خواهد کرد؛ در غیر اینصورت، کلاس پیش فرض HttpContextAccessor را به صورت خودکار جهت استفاده ثبت می‌کند.

              تنظیمات پایگاه داده

              پایگاه داده استفاده شده در پَرباد سیستم مشهور و شناخته شده‌ی EntityFrameworkCore است. این بدان معناست که شما می‌توانید پایگاه داده مورد نیاز پَرباد را توسط منابع بسیار مختلفی از جمله SQL Server, MySql, Oracle, SQLite و غیره تامین کنید.
              SQL Server و InMemory به صورت پیش فرض با پکیج پَرباد در اپلیکیشن شما نصب خواهند شد. اما اگر نیاز به پایگاه داده‌ی دیگری دارید، می‌توانید آن را از بین تامین کننده‌های مختلف انتخاب، نصب و استفاده کنید.
              نکته: پایگاه داده، برای مصرف و عملکرد داخلی پَرباد است و نه مصرف خارجی در اپلیکیشن شما. در واقع شما نیازی به داشتن اطلاعات درونی پایگاه داده پَرباد ندارید و موارد مهمی مانند کد رهگیری، شماره تراکنش بانکی، مبلغ، نام بانک و غیره را پس از هر عمل پرداخت می‌توانید توسط پَرباد دریافت کنید و در پایگاه داده خود برای فاکتور مورد نظر ذخیره کنید.

              نمونه کد‌های تنظیم را در زیر می‌توانید مشاهده کنید:
              SQL Server
              services.AddParbad()
                      .ConfigureStorage(builder => builder.UseParbadSqlServer("ConnectionString"));
              نکته: همانطور که می‌دانید، متد اصلی دیگری به نام UseSqlServer وجود دارد. تفاوت آن با متد استفاده شده‌ی در کد بالا این است که UseParbadSqlServer ، به صورت خودکار Migration‌های مرتبط با پروژه پَرباد را نیز اعمال می‌کند. هر چند که این عمل توسط خود شما نیز امکان پذیر است.
              In-Memory Database
              services.AddParbad()
                      .ConfigureStorage(builder => builder.UseInMemoryDatabase("MyMemoryName"));
              نکته: اگر به هر دلیلی، سرور و یا وب سایت شما، ری‌استارت شود، اطلاعات موجود در این پایگاه داده ( In-Memory Database ) نیز از بین خواهند رفت. به عبارت دیگر، این پایگاه داده پایدار نیست و صرفا جهت اهداف تست از آن استفاده می‌شود.

              تنظیمات پیام‌ها (اختیاری)

              منظور از پیام‌ها، پیام‌های متنی‌ای است که پس از انجام عملیات‌های مختلف به شما بازگشت داده می‌شوند؛ برای مثال: پرداخت با موفقیت انجام شد.
              شما می‌توانید این پیام‌ها را به شکل زیر تنظیم کنید:
              services.AddParbad()
                      .ConfigureMessages(options => 
                      {
                              options.PaymentSucceed = "Payment was successful.";
                              options.PaymentFailed = "Payment was not successful.";
                              // other messages...
                      });

              بدیهی است که شما می‌توانید این تنظیمات را نادیده گرفته و خودتان مسئولیت نمایش پیام به کاربران را به عهده بگیرید.
              نکته: شما همچنین می‌توانید از اینترفیس IConfiguration که بالاتر توضیح داده شد نیز برای تنظیم پیام‌ها استفاده کنید.

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

              از اس کیوال سرور 2005 به بعد تابع HashBytes نیز به مجموعه توابع قابل استفاده در دستورات T-SQL اس کیوال سرور اضافه شده است که الگوریتم‌های MD2 | MD4 | MD5 | SHA | SHA1 را پشتیبانی می‌کند. برای مثال:
              DECLARE @str1 VARCHAR(4),
              @str2 NVARCHAR(4)

              --متن یونیکد اینجا ناقص ذخیره می‌شود
              SET @str1 = 'وحید'

              SET @str2 = N'وحید'

              SELECT hashbytes('md5', @str1) --C82A7D721AAE517AD76EF1B871BC33CE

              SELECT hashbytes('md5', @str2) --7D883091B80F3CD20B872CADBFDDACDF

              اگر این نتایج را بخواهیم با استفاده از فضای نام استاندارد System.Security.Cryptography تولید کنیم، باید به encoding رشته دریافتی حتما دقت داشت؛ در غیر اینصورت نتایج یکسان نخواهند بود.
              مهم‌ترین encoding های پشتیبانی شده در دات نت در جدول زیر برشمرده شده‌اند:

              Encoding تعداد بیت هر کاراکتر

              ASCII
              هر کاراکتر آن 7 بیت است

              UTF7
              هر کاراکتر آن 7 بیت است

              UTF8
              هر کاراکتر آن 8 بیت و یا یک بایت است

              Unicode (UTF-16)
              هر کاراکتر آن 16 بیت و یا دو بایت است

              UTF32
              هر کاراکتر آن 32 بیت و یا 4 بایت است


              نوع nvarchar در اس کیوال سرور همانند حالت Encoding.Unicode‌ دات نت است و هر کاراکتر آن 2 بایت می‌باشد.
              این نکته‌ هنگام استفاده از این توابع بسیار حائز اهمیت است. برای مثال اگر تابع HashBytes اس کیوال سرور را بخواهیم در دات نت پیاده سازی کنیم، به کلاس زیر خواهیم رسید:

              using System.Text;
              using System.Security.Cryptography;

              class CHash
              {
              public static string GetMD5Hash(string input, Encoding encoding)
              {
              byte[] bytes = new MD5CryptoServiceProvider().ComputeHash(encoding.GetBytes(input));
              StringBuilder chars = new StringBuilder();
              foreach (byte chr in bytes)
              {
              chars.Append(chr.ToString("x2"));
              }
              return chars.ToString();
              }
              }
              در اینجا تنها حالت زیر با هش تولید شده یک فیلد یا متغیر از نوع nvarchar توسط تابع HashBytes اس کیوال سرور معادل است:

              string result = CHash.GetMD5Hash("وحید", Encoding.Unicode);

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


              مطالب
              آموزش QUnit #1
              مقدمه:
              تست و آزمایش کد برنامه‌ها و وب سایت‌هایمان، بهترین راه کاهش خطا و مشکلات آنها بعد از انتشار است. از جمله روش‌های موجود، تست واحد است که ویژوال استادیو نیز از آن برای پروژه‌های دات نت پشتیبانی می‌کند. با افزایش روز افزون کتابخانه‌های جاوا اسکریپتی و جی کوئری، نیاز به تست کد‌های جاواسکریپتی نیز بیشتر به نظر می‌رسد و بهتر است تست واحد و آزمایش شوند. اما برخلاف کدهای #C و ASP.NET تست کد‌های جاوا اسکریپت، مخصوصا زمانی که به دستکاری عناصر DOM می‌پردازیم و یا رویداد‌های درون صفحه وب را با استفاده از جی کوئری می‌نویسیم، حتی اگر در فایل جداگانه‌ای نوشته شود، این بدان معنی نیست که آماده تست واحد است و ممکن است امکان نوشتن تست وجود نداشته باشد.
              بنابراین چه چیزی یک تست واحد است؟ در بهترین حالت توابعی که مقداری را برمی گردادنند، بهترین حالت برای تست واحد است. اما در بیشتر موارد شما نیاز دارید تا تاثیر کد را بر روی عناصر صفحه نیز مشاهد نمایید.
              ساخت تست واحد
              برای تست پذیری بهتر، توابع جاوا اسکریپت و هر کد دیگری، آن را می‌بایست طوری بنویسید که مقادیر تاثیر گذار در اجرای تابع به عنوان ورودی تابع در نظر گرفته شده باشند و همیشه نتیجه به عنوان خروجی تابع برگردانده شود؛ قطعه کد زیر را در نظر بگیرید:
               function prettyDate(time){
                  var date = new Date(time || ""),
                    diff = (((new Date()).getTime() - date.getTime()) / 1000),
                    day_diff = Math.floor(diff / 86400);
               
                  if ( isNaN(day_diff) || day_diff < 0 || day_diff >= 31 )
                    return;
               
                  return day_diff == 0 && (
                      diff < 60 && "just now" ||
                      diff < 120 && "1 minute ago" ||
                      diff < 3600 && Math.floor( diff / 60 ) +
                        " minutes ago" ||
                      diff < 7200 && "1 hour ago" ||
                      diff < 86400 && Math.floor( diff / 3600 ) +
                        " hours ago") ||
                    day_diff == 1 && "Yesterday" ||
                    day_diff < 7 && day_diff + " days ago" ||
                    day_diff < 31 && Math.ceil( day_diff / 7 ) +
                      " weeks ago";
                }
              تابع pertyDate اختلاف زمان حال را نسبت به زمان ورودی، بصورت یک رشته برمی گرداند. اما در اینجا مقدار زمان حال، در خط سوم، در خود تابع ایجاد شده است و در صورتی که بخواهیم برای چندین مقدار آن را تست کنیم زمان حال متفاوتی در نظر گرفته می‌شود و حداکثر، زمان 31 روز قبل را نمایش داده و در بقیه تاریخ ها undefined را بر می‌گرداند. برای تست واحد، چند تغییر می‌دهیم.
              بهینه سازی، مرحله اول:
              پارامتری به عنوان مقدار زمان جاری برای تابع در نظر می‌گیریم و تابع را جدا کرده و در یک فایل جداگانه قرار می‌دهیم. فایل prettydate.js بصورت زیر خواهد شد.
              function prettyDate(now, time){
                var date = new Date(time || ""),
                  diff = (((new Date(now)).getTime() - date.getTime()) / 1000),
                  day_diff = Math.floor(diff / 86400);
               
                if ( isNaN(day_diff) || day_diff < 0 || day_diff >= 31 )
                  return;
               
                return day_diff == 0 && (
                    diff < 60 && "just now" ||
                    diff < 120 && "1 minute ago" ||
                    diff < 3600 && Math.floor( diff / 60 ) +
                      " minutes ago" ||
                    diff < 7200 && "1 hour ago" ||
                    diff < 86400 && Math.floor( diff / 3600 ) +
                      " hours ago") ||
                  day_diff == 1 && "Yesterday" ||
                  day_diff < 7 && day_diff + " days ago" ||
                  day_diff < 31 && Math.ceil( day_diff / 7 ) +
                    " weeks ago";
              }
              حال یک تابع برای تست داریم، چند تست واحد واقعی می‌نویسیم
              <!doctype html>
              <html>
              <head>
                <meta charset="utf-8">
                <title>Refactored date examples</title>
                <script src="prettydate.js"></script>
                <script>
                function test(then, expected) {
                  results.total++;
                  var result = prettyDate("2013/01/28 22:25:00", then);
                  if (result !== expected) {
                    results.bad++;
                    console.log("Expected " + expected +
                      ", but was " + result);
                  }
                }
                var results = {
                  total: 0,
                  bad: 0
                };
                test("2013/01/28 22:24:30", "just now");
                test("2013/01/28 22:23:30", "1 minute ago");
                test("2013/01/28 21:23:30", "1 hour ago");
                test("2013/01/27 22:23:30", "Yesterday");
                test("2013/01/26 22:23:30", "2 days ago");
                test("2012/01/26 22:23:30", undefined);
                console.log("Of " + results.total + " tests, " +
                  results.bad + " failed, " +
                  (results.total - results.bad) + " passed.");
                </script>
              </head>
              <body>
               
              </body>
              </html>
              در کد بالا یک تابع بدون استفاده از Qunit برای تست واحد نوشته ایم که با آن تابع prettyDate را تست می‌کند. تابع test مقدار زمان حال و رشته خروجی را گرفته  و آن را با تابع اصلی تست می‌کند در آخر تعداد تست ها، تست‌های شکست خورده و تست  های پاس شده گزارش داده می‌شود.
              خروجی می‌تواند مانند زیر باشد:

              Of 6 tests, 0 failed, 6 passed.
              Expected 2 day ago, but was 2 days ago. 
              f 6 tests, 1 failed, 5 passed.

              مطالب دوره‌ها
              متدهای توکار استفاده از نوع داده‌ای XML - قسمت دوم
              امکان ترکیب داده‌های یک بانک اطلاعاتی رابطه‌ای و XML در SQL Server به کمک یک سری تابع کمکی خاص به نام‌های sql:variable و sql:column پیش بینی شده‌است. sql:variable امکان استفاده از یک متغیر T-SQL را داخل یک XQuery میسر می‌سازد و توسط sql:column می‌توان با یکی از ستون‌های ذکر شده در قسمت select، داخل XQuery کار کرد. در ادامه به مثال‌هایی در این مورد خواهیم پرداخت.

              ابتدا جدول xmlTest را به همراه چند رکورد ثبت شده در آن، درنظر بگیرید:
               CREATE TABLE xmlTest
              (
               id INT IDENTITY PRIMARY KEY,
               doc XML
              )
              GO
              INSERT xmlTest VALUES('<Person name="Vahid" />')
              INSERT xmlTest VALUES('<Person name="Farid" />')
              INSERT xmlTest VALUES('<Person name="Mehdi" /><Person name="Hamid" />')
              GO

              استفاده از متد sql:column

              در ادامه می‌خواهیم مقدار ویژگی name رکوردی را که نام آن Vahid است، به همراه id آن ردیف، توسط یک XQuery بازگشت دهیم:
               SELECT doc.query('
              for $p in //Person
              where $p/@name="Vahid"
              return <li>{data($p/@name)} has id = {sql:column("xmlTest.id")}</li>
              ')
              FROM xmlTest
              یک sql:column حتما نیاز به یک نام ستون دو قسمتی دارد. قسمت اول آن نام جدول است و قسمت دوم، نام ستون مورد نظر.
              در مورد متد data در قسمت قبل بیشتر بحث شد و از آن برای استخراج داده‌ی یک ویژگی در اینجا استفاده شده‌است. عبارات داخل {} نیز پویا بوده و به همراه سایر قسمت‌های ثابت return، ابتدا محاسبه و سپس بازگشت داده می‌شود.
              اگر این کوئری را اجرا کنید، ردیف اول آن مساوی عبارت زیر خواهد بود
               <li>Vahid has id = 1</li>
              به همراه دو ردیف خالی دیگر در ادامه. این ردیف‌های خالی به علت وجود دو رکورد دیگری است که با شرط where یاد شده تطابق ندارند.
              یک روش برای حذف این ردیف‌های خالی استفاده از متد exist است به شکل زیر:
               SELECT doc.query('
              for $p in //Person
              where $p/@name="Vahid"
              return <li>{data($p/@name)} has id = {sql:column("xmlTest.id")}</li>
              ')
              FROM xmlTest
              WHERE doc.exist('
              for $p in //Person
              where $p/@name="Vahid"
              return <li>{data($p/@name)} has id = {sql:column("xmlTest.id")}</li>
              ')=1
              در اینجا فقط ردیفی انتخاب خواهد شد که نام ویژگی آن Vahid است.
              روش دوم استفاده از یک derived table و بازگشت ردیف‌های غیرخالی است:
               SELECT * FROM
              (
               (SELECT doc.query('
               for $p in //Person
               where $p/@name="Vahid"
               return <li>{data($p/@name)} has id = {sql:column("xmlTest.id")}</li>
               ') AS col1
               FROM xmlTest)
              ) A
              WHERE CONVERT(VARCHAR(8000), col1)<>''


              استفاده از متد sql:variable

               DECLARE @number INT = 1
              SELECT doc.query('
              for $p in //Person
              where $p/@name="Vahid"
              return <li>{data($p/@name)} has number = {sql:variable("@number")}</li>
              ')
              FROM xmlTest
              در این مثال نحوه‌ی بکارگیری یک متغیر T-SQL را داخل یک XQuery توسط متد sql:variable ملاحظه می‌کنید.


              استفاده از For XML برای دریافت یکباره‌ی تمام ردیف‌های XML

              اگر کوئری معمولی ذیل را اجرا کنیم:
               SELECT doc.query('/Person') FROM xmlTest
              سه ردیف خروجی را مطابق سه رکوردی که ثبت کردیم، بازگشت می‌دهد.
              اما اگر بخواهیم این سه ردیف را با هم ترکیب کرده و تبدیل به یک نتیجه‌ی واحد کنیم، می‌توان از For XML به نحو ذیل استفاده کرد:
               DECLARE @doc XML
              SET @doc = (SELECT * FROM xmlTest FOR XML AUTO, ELEMENTS)
              SELECT @doc.query('/xmlTest/doc/Person')


              بررسی متد xml.nodes

              متد xml.nodes اندکی متفاوت است نسبت به تمام متدهایی که تاکنون بررسی کردیم. کار آن تجزیه‌ی محتوای XML ایی به ستون‌ها و سطرها می‌باشد. بسیار شبیه است به متد OpenXML اما کارآیی بهتری دارد.
               DECLARE @doc XML ='
              <people>
                <person><name>Vahid</name></person>
                <person><name id="2">Farid</name></person>
                <person><name>Mehdi</name></person>
                <person><name>Hooshang</name><name id="1">Hooshi</name></person>
                <person></person>
              </people>
              '
              در اینجا یک سند XML را درنظر بگیرید که از چندین نود شخص تشکیل شده‌است. اغلب آن‌ها دارای یک name هستند. چهارمین نود، دو نام دارد و آخری بدون نام است.
              در ادامه قصد داریم این اطلاعات را تبدیل به ردیف‌هایی کنیم که هر ردیف حاوی یک نام است. اولین سعی احتمالا استفاده از متد value خواهد بود:
               SELECT @doc.value('/people/person/name', 'varchar(50)')
              این روش کار نمی‌کند زیرا متد value، بیش از یک مقدار را نمی‌تواند بازگشت دهد. البته می‌توان از متد value به نحو زیر استفاده کرد:
               SELECT @doc.value('(/people/person/name)[1]', 'varchar(50)')
              اما حاصل آن دقیقا چیزی نیست که دنبالش هستیم؛ ما دقیقا نیاز به تمام نام‌ها داریم و نه تنها یکی از آن‌ها را.
              سعی بعدی استفاده از متد query است:
               SELECT @doc.query('/people/person/name')
              در این حالت تمام نام‌ها را بدست می‌آوریم:
               <name>Vahid</name>
              <name id="2">Farid</name>
              <name>Mehdi</name>
              <name>Hooshang</name>
              <name id="1">Hooshi</name>
              اما این حاصل دو مشکل را به همراه دارد:
              الف) خروجی آن XML است.
              ب) تمام این‌ها در طی یک ردیف و یک ستون بازگشت داده می‌شوند.

              و این خروجی نیز چیزی نیست که برای ما مفید باشد. ما به ازای هر شخص نیاز به یک ردیف جداگانه داریم. اینجا است که متد xml.nodes مفید واقع می‌شود:
               SELECT
              tab.col.value('text()[1]', 'varchar(50)') AS name,
              tab.col.query('.'),
              tab.col.query('..')
              from @doc.nodes('/people/person/name') AS tab(col)
              خروجی متد xml.nodes یک table valued function است؛ یک جدول را باز می‌گرداند که دقیقا حاوی یک ستون می‌باشد. به همین جهت Alias آن‌را با tab col مشخص کرده‌ایم. tab متناظر است با جدول بازگشت داده شده و col متناظر است با تک ستون این جدول حاصل. این نام‌ها در اینجا مهم نیستند؛ اما ذکر آن‌ها اجباری است.
              هر ردیف حاصل از این جدول بازگشت داده شده، یک اشاره‌گر است. به همین جهت نمی‌توان آن‌ها را مستقیما نمایش داد. هر سطر آن، به نودی که با آن مطابق XQuery وارد شده تطابق داشته است، اشاره می‌کند. در اینجا مطابق کوئری نوشته شده، هر ردیف به یک نود name اشاره می‌کند. در ادامه برای استخراج اطلاعات آن می‌توان از متد text استفاده کرد.
              اگر قصد داشتید، اطلاعات کامل نود ردیف جاری را مشاهده کنید می‌توان از
               tab.col.query('.'),
              استفاده کرد. دات در اینجا به معنای self است. دو دات (نقطه) پشت سرهم به معنای درخواست اطلاعات والد نود می‌باشد.
              روش دیگر بدست آوردن مقدار یک نود را در کوئری ذیل مشاهده می‌کنید؛ value دات و data دات. خروجی  value مقدار آن نود است و خروجی data مقدار آن نود با فرمت XML.

               SELECT
              tab.col.value('.', 'varchar(50)') AS name,
              tab.col.query('data(.)'),
              tab.col.query('.'),
              tab.col.query('..')
              from @doc.nodes('/people/person/name') AS tab(col)

              همچنین اگر بخواهیم اطلاعات تنها یک نود خاص را بدست بیاوریم، می‌توان مانند کوئری ذیل عمل کرد:
               SELECT
              tab.col.value('name[.="Farid"][1]', 'varchar(50)') AS name,
              tab.col.value('name[.="Farid"][1]/@id', 'varchar(50)') AS id,
              tab.col.query('.')
              from @doc.nodes('/people/person[name="Farid"]') AS tab(col)

              در مورد کار با جداول، بجای متغیرهای T-SQL نیز روال کار به همین نحو است:
               DECLARE @tblXML TABLE (
               id INT IDENTITY PRIMARY KEY,
               doc XML
               )
              
              INSERT @tblXML VALUES('<person name="Vahid" />')
              INSERT @tblXML VALUES('<person name="Farid" />')
              INSERT @tblXML VALUES('<person />')
              INSERT @tblXML VALUES(NULL)
              
              SELECT
              id,
              doc.value('(/person/@name)[1]', 'varchar(50)') AS name
              FROM @tblXML
              در اینجا یک جدول حاوی ستون XML ایی ایجاد شده‌است. سپس چهار ردیف در آن ثبت شده‌اند. در آخر مقدار ویژگی نام این ردیف‌ها بازگشت داده شده‌است.


              نکته : استفاده‌ی وسیع SQL Server از XML برای پردازش کارهای درونی آن

              بسیاری از ابزارهایی که در نگارش‌های جدید SQL Server اضافه شده‌اند و یا مورد استفاده قرار می‌گیرند، استفاده‌ی وسیعی از امکانات توکار XML آن دارند. مانند:
              Showplan، گراف‌های dead lock، گزارش پروسه‌های بلاک شده، اطلاعات رخدادها، SSIS Jobs، رخدادهای Trace و ...

              مثال اول: کدام کوئری‌ها در Plan cache، کارآیی پایینی داشته و table scan را انجام می‌دهند؟

               CREATE PROCEDURE LookForPhysicalOps (@op VARCHAR(30))
              AS
              SELECT sql.text, qs.EXECUTION_COUNT, qs.*, p.*
              FROM sys.dm_exec_query_stats AS qs
              CROSS APPLY sys.dm_exec_sql_text(sql_handle) sql
              CROSS APPLY sys.dm_exec_query_plan(plan_handle) p
              WHERE query_plan.exist('
              declare default element namespace "http://schemas.microsoft.com/sqlserver/2004/07/showplan";
              /ShowPlanXML/BatchSequence/Batch/Statements//RelOp/@PhysicalOp[. = sql:variable("@op")]
              ') = 1
              GO
              
              EXECUTE LookForPhysicalOps 'Table Scan'
              EXECUTE LookForPhysicalOps 'Clustered Index Scan'
              EXECUTE LookForPhysicalOps 'Hash Match'
              اطلاعات Query Plan در SQL Server با فرمت XML ارائه می‌شود. در اینجا می‌خواهیم یک سری متغیر مانند Clustered Index Scan و امثال آن‌را از ویژگی PhysicalOp آن کوئری بگیریم. بنابراین از متد  sql:variable کمک گرفته شده‌است.
              اگر علاقمند هستید که اصل این اطلاعات را با فرمت XML مشاهده کنید، کوئری نوشته شده را تا پیش از where آن یکبار مستقلا اجرا کنید. ستون آخر آن query_plan نام دارد و حاوی اطلاعات XML ایی است.

              مثال دوم:   استخراج اپراتورهای رابطه‌ای (RelOp) از یک Query Plan ذخیره شده

               WITH XMLNAMESPACES(DEFAULT N'http://schemas.microsoft.com/sqlserver/2004/07/showplan')
              SELECT RelOp.op.value(N'../../@NodeId', N'int') AS ParentOperationID,
              RelOp.op.value(N'@NodeId', N'int') AS OperationID,
              RelOp.op.value(N'@PhysicalOp', N'varchar(50)') AS PhysicalOperator,
              RelOp.op.value(N'@LogicalOp', N'varchar(50)') AS LogicalOperator,
              RelOp.op.value(N'@EstimatedTotalSubtreeCost ', N'float') AS EstimatedCost,
              RelOp.op.value(N'@EstimateIO', N'float') AS EstimatedIO,
              RelOp.op.value(N'@EstimateCPU', N'float') AS EstimatedCPU,
              RelOp.op.value(N'@EstimateRows', N'float') AS EstimatedRows,
              cp.plan_handle AS PlanHandle,
              st.TEXT AS QueryText,
              qp.query_plan AS QueryPlan,
              cp.cacheobjtype AS CacheObjectType,
              cp.objtype AS ObjectType
              FROM sys.dm_exec_cached_plans cp
              CROSS APPLY sys.dm_exec_sql_text(cp.plan_handle) st
              CROSS APPLY sys.dm_exec_query_plan(cp.plan_handle) qp
              CROSS APPLY qp.query_plan.nodes(N'//RelOp') RelOp(op)
              در اینجا کار کردن با WITH XMLNAMESPACES در حین استفاده از متد xml.nodes ساده‌تر است؛ بجای قرار دادن فضای نام در تمام کوئری‌های نوشته شده.


              بررسی متد xml.modify

              تا اینجا تمام کارهایی که صورت گرفت و نکاتی که بررسی شدند، به مباحث select اختصاص داشتند. اما insert، delete و یا update قسمتی از یک سند XML بررسی نشدند. برای این منظور باید از متد xml.modify استفاده کرد. از آن در عبارات update و یا set کمک گرفته شده و ورودی آن نباید نال باشد. در ادامه در طی مثال‌هایی این موارد را بررسی خواهیم کرد.
              ابتدا فرض کنید که سند XML ما چنین شکلی را دارا است:
              DECLARE @doc XML = '
              <Invoice>
              <InvoiceId>100</InvoiceId>
              <CustomerName>Vahid</CustomerName>
              <LineItems>
              <LineItem>
              <Sku>134</Sku>
              <Quantity>10</Quantity>
              <Description>Item 1</Description>
              <UnitPrice>9.5</UnitPrice>
              </LineItem>
              <LineItem>
              <Sku>150</Sku>
              <Quantity>5</Quantity>
              <Description>Item 2</Description>
              <UnitPrice>1.5</UnitPrice>
              </LineItem>
              </LineItems>
              </Invoice>
              '
              در ادامه قصد داریم یک نود جدید را پس از CustomerName اضافه کنیم.
               SET @doc.modify('
              insert <InvoiceInfo><InvoiceDate>2014-02-10</InvoiceDate></InvoiceInfo>
              after /Invoice[1]/CustomerName[1]
              ')
              
              SELECT @doc
              اینکار را با استفاده از دستور insert، به نحو فوق می‌توان انجام داد. از عبارت Set و متغیر doc مقدار دهی شده، کار شروع شده و سپس نود جدیدی پس از (after) اولین نود CustomerName موجود insert می‌شود. Select بعدی نتیجه را نمایش خواهد داد.
              <Invoice>
                <InvoiceId>100</InvoiceId>
                <CustomerName>Vahid</CustomerName>
                <InvoiceInfo>
                      <InvoiceDate>2014-02-10</InvoiceDate>
                </InvoiceInfo>
                <LineItems>
              ...

              در SQL Server 2008 به بعد، امکان استفاده از متغیرهای T-SQL نیز در اینجا مجاز شده‌است:
               SET @x.modify('insert sql:variable("@x") into /doc[1]')
              بنابراین اگر نیاز به تعریف متغیری در اینجا داشتید از جمع زدن رشته‌ها استفاده نکنید. حتما نیاز است متغیر تعریف شود و گرنه باخطای ذیل متوقف خواهید شد:
               The argument 1 of the XML data type method "modify" must be a string literal.


              افزودن ویژگی‌های جدید به یک سند XML توسط متد xml.modify

              اگر بخواهیم یک ویژگی (attribute) جدید را به نود خاصی اضافه کنیم می‌توان به نحو ذیل عمل کرد:
               SET @doc.modify('
              insert attribute status{"backorder"}
              into /Invoice[1]
              ')
              
              SELECT @doc
              که خروجی دو سطر ابتدایی آن پس از اضافه شدن ویژگی status با مقدار backorder به نحو ذیل است:
               <Invoice status="backorder">
                <InvoiceId>100</InvoiceId>
              ....


              حذف نودهای یک سند XML توسط متد xml.modify

              اگر بخواهیم تمام LineItemها را حذف کنیم می‌توان نوشت:
               SET @doc.modify('delete /Invoice/LineItems/LineItem')
              SELECT @doc
              با این خروجی:
               <Invoice status="backorder">
                <InvoiceId>100</InvoiceId>
                <CustomerName>Vahid</CustomerName>
                <InvoiceInfo>
                    <InvoiceDate>2014-02-10</InvoiceDate>
                </InvoiceInfo>
                <LineItems />
              </Invoice>


              به روز رسانی نودهای یک سند XML توسط متد xml.modify

              اگر نیاز باشد تا مقدار یک نود را تغییر دهیم می‌توان از replace value of استفاده کرد:
               SET @doc.modify('replace value of
                /Invoice[1]/CustomerName[1]/text()[1]
                with "Farid"
              ')
              SELECT @doc
              با خروجی ذیل که در آن نام اولین مشتری با مقدار Farid جایگزین شده است:
               <Invoice status="backorder">
                <InvoiceId>100</InvoiceId>
                <CustomerName>Farid</CustomerName>
                <InvoiceInfo>
                     <InvoiceDate>2014-02-10</InvoiceDate>
                </InvoiceInfo>
                <LineItems />
              </Invoice>
              replace value of فقط با یک نود کار می‌کند و همچنین، فقط مقدار آن نود را تغییر می‌دهد. به همین جهت از متد text استفاده شده‌است. اگر از text استفاده نشود با خطای ذیل متوقف خواهیم شد:
               The target of 'replace value of' must be a non-metadata attribute or an element with simple typed content.


              به روز رسانی نودهای خالی توسط متد xml.modify

              باید دقت داشت، نودهای خالی (بدون مقدار)، مانند LineItems پس از delete کلیه اعضای آن در مثال قبل، قابل replace نیستند و باید مقادیر جدید را در آن‌ها insert کرد. یک مثال:

               DECLARE @tblTest AS TABLE (xmlField XML)
              
              INSERT INTO @tblTest(xmlField)
              VALUES
               (
              '<Sample>
                 <Node1>Value1</Node1>
                 <Node2>Value2</Node2>
                 <Node3/>
              </Sample>'
              )
               
              DECLARE @newValue VARCHAR(50) = 'NewValue'
              
              UPDATE @tblTest
              SET xmlField.modify(
              'insert text{sql:variable("@newValue")} into
                (/Sample/Node3)[1] [not(text())]'
              )
              
              SELECT xmlField.value('(/Sample/Node3)[1]','varchar(50)') FROM @tblTest
              در این مثال اگر از replace value of برای مقدار دهی نود سوم استفاده می‌شد:
               UPDATE @tblTest
              SET xmlField.modify(
              'replace value of (/Sample/Node3/text())[1]
                with sql:variable("@newValue")'
              )
              تغییری را پس از اعمال دستورات مشاهده نمی‌کردید؛ زیرا این المان ()text ایی را برای replace شدن ندارد.
              مطالب
              استفاده از SQL-CE به کمک NHibernate

              خلاصه‌ای را در مورد SQL Server CE قبلا در این سایت مطالعه‌ کرده‌اید. در ادامه خلاصه‌ای کاربردی را از تنظیمات و نکات مرتبط به کار با SQL-CE به کمک NHibernate ملاحظه خواهید نمود:

              1) دریافت SQL-CE 4.0


              همین مقدار برای استفاده از SQL-CE 4.0 به کمک NHibernate کفایت می‌کند و حتی نیازی به نصب سرویس پک یک VS 2010 هم نیست.

              2) ابزار سازی جهت ایجاد یک بانک اطلاعاتی خالی SQL-CE

              using System;
              using System.IO;

              namespace NHibernate.Helper.DbSpecific
              {
              public class SqlCEDbHelper
              {
              const string engineTypeName = "System.Data.SqlServerCe.SqlCeEngine, System.Data.SqlServerCe";

              /// <summary>
              /// note: this method will delete existing db and then creates a new one.
              /// </summary>
              /// <param name="filename"></param>
              /// <param name="password"></param>
              public static void CreateEmptyDatabaseFile(string filename, string password = "")
              {
              if (File.Exists(filename))
              File.Delete(filename);

              var type = System.Type.GetType(engineTypeName);
              var localConnectionString = type.GetProperty("LocalConnectionString");
              var createDatabase = type.GetMethod("CreateDatabase");

              var engine = Activator.CreateInstance(type);

              string connectionStr = string.Format("Data Source='{0}';Password={1};Encrypt Database=True", filename, password);
              if (string.IsNullOrWhiteSpace(password))
              connectionStr = string.Format("Data Source='{0}'", filename);

              localConnectionString.SetValue(
              obj: engine,
              value: connectionStr,
              index: null);
              createDatabase.Invoke(engine, new object[0]);
              }

              /// <summary>
              /// use this method to compact or encrypt existing db or decrypt it to a new db with all records
              /// </summary>
              /// <param name="sourceConnection"></param>
              /// <param name="destConnection"></param>
              public static void CompactDatabase(string sourceConnection, string destConnection)
              {
              var type = System.Type.GetType(engineTypeName);
              var engine = Activator.CreateInstance(type);

              var localConnectionString = type.GetProperty("LocalConnectionString");
              localConnectionString.SetValue(
              obj: engine,
              value: sourceConnection,
              index: null);

              var compactDatabase = type.GetMethod("Compact");
              compactDatabase.Invoke(engine, new object[] { destConnection });
              }
              }
              }

              کلاس فوق، یک کلاس عمومی است و مرتبط به NHibernate نیست و در همه جا قابل استفاده است.
              متد CreateEmptyDatabaseFile یک فایل بانک اطلاعاتی خالی با فرمت مخصوص SQL-CE را برای شما تولید خواهد کرد. به این ترتیب می‌توان بدون نیاز به ابزار خاصی، سریعا یک بانک خالی را تولید و شروع به کار کرد. در این متد اگر کلمه عبوری را وارد نکنید، بانک اطلاعاتی رمزنگاری شده نخواهد بود و اگر کلمه عبور را وارد کنید، دیتابیس اولیه به همراه کلیه اعمال انجام شده بر روی آن در طول زمان، با کمک الگوریتم AES به صورت خودکار رمزنگاری خواهند شد. کل کاری را هم که باید انجام دهید ذکر این کلمه عبور در کانکشن استرینگ است.
              متد CompactDatabase، یک متد چند منظوره است. اگر بانک اطلاعاتی SQL-CE رمزنگاری نشده‌ای دارید و می‌خواهید کل آن‌را به همراه تمام اطلاعات درون آن رمزنگاری کنید، می‌توانید جهت سهولت کار از این متد استفاده نمائید. آرگومان اول آن به کانکشن استرینگ بانکی موجود و آرگومان دوم به کانکشن استرینگ بانک جدیدی که تولید خواهد شد، اشاره می‌کند.
              همچنین اگر یک بانک اطلاعاتی SQL-CE رمزنگاری شده دارید و می‌خواهید آن‌را به صورت یک بانک اطلاعاتی جدید به همراه تمام رکوردهای آن رمزگشایی کنید، باز هم می‌توان از این متد استفاده کرد. البته بدیهی است که کلمه عبور را باید داشته باشید و این کلمه عبور جایی درون فایل بانک اطلاعاتی ذخیره نمی‌شود. در این حالت در کانکشن استرینگ اول باید کلمه عبور ذکر شود و کانکشن استرینگ دوم نیازی به کلمه عبور نخواهد داشت.

              فرمت کلی کانکشن استرینگ SQL-CE هم به شکل زیر است:

              Data Source=c:\path\db.sdf;Password=1234;Encrypt Database=True

              البته این برای حالتی است که قصد داشته باشید بانک اطلاعاتی مورد استفاده را رمزنگاری کنید یا از یک بانک اطلاعاتی رمزنگاری شده استفاده نمائید. اگر بانک اطلاعاتی شما کلمه عبوری ندارد، ذکر Data Source=c:\path\db.sdf کفایت می‌کند.

              این کلاس هم از این جهت مطرح شد که NHibernate می‌تواند ساختار بانک اطلاعاتی را بر اساس تعاریف نگاشت‌ها به صورت خودکار تولید و اعمال کند، «اما» بر روی یک بانک اطلاعاتی خالی SQL-CE از قبل تهیه شده (در غیراینصورت خطای The database file cannot be found. Check the path to the database را دریافت خواهید کرد).

              نکته:
              اگر دقت کرده باشید در این کلاس engineTypeName به صورت رشته ذکر شده است. چرا؟
              علت این است که با ذکر engineTypeName به صورت رشته، می‌توان از این کلاس در یک کتابخانه عمومی هم استفاده کرد، بدون اینکه مصرف کننده نیازی داشته باشد تا ارجاع مستقیمی را به اسمبلی SQL-CE به برنامه خود اضافه کند. اگر این ارجاع وجود داشت، متدهای یاد شده کار می‌کنند، در غیراینصورت در گوشه‌ای ساکت و بدون دردسر و بدون نیاز به اسمبلی خاصی برای روز مبادا قرار خواهند گرفت.


              3) ابزار مرور اطلاعات بانک اطلاعاتی SQL-CE

              با استفاده از management studio خود SQL Server هم می‌شود با بانک‌های اطلاعاتی SQL-CE کار کرد، اما ... اینبار برخلاف نگارش کامل اس کیوال سرور، با یک نسخه‌ی بسیار بدوی، که حتی امکان rename فیلدها را هم ندارد مواجه خواهید شد. به همین جهت به شخصه برنامه SqlCe40Toolbox را ترجیح می‌دهم و اطمینان داشته باشید که امکانات آن برای کار با SQL-CE از امکانات ارائه شده توسط management studio مایکروسافت، بیشتر و پیشرفته‌تر است!



              4) تنظیمات NHibernate جهت کار با SQL-CE

              الف) پس از نصب SQL-CE ، فایل‌های آن‌را در مسیر C:\Program Files\Microsoft SQL Server Compact Edition\v4.0 می‌توان یافت. درایور ADO.NET آن هم در مسیر C:\Program Files\Microsoft SQL Server Compact Edition\v4.0\Desktop قرار دارد. بنابراین در ابتدا نیاز است تا ارجاعی را به اسمبلی System.Data.SqlServerCe.dll به برنامه خود اضافه کنید (نام پوشه desktop آن هم غلط انداز است. از این جهت که نگارش 4 آن، به راحتی در برنامه‌های ذاتا چند ریسمانی ASP.Net بدون مشکل قابل استفاده است).
              نکته مهم: در این حالت NHibernate قادر به یافتن فایل درایور یاد شده نخواهد بود و پیغام خطای «Could not create the driver from NHibernate.Driver.SqlServerCeDriver» را دریافت خواهید کرد. برای رفع آن، اسمبلی System.Data.SqlServerCe.dll را در لیست ارجاعات برنامه یافته و در برگه خواص آن، خاصیت «Copy Local» را true کنید. به این معنا که NHibernate این اسمبلی را در کنار فایل اجرایی برنامه شما جستجو خواهد کرد.

              ب) مطلب بعد، تنظیمات ابتدایی NHibernate‌ است جهت شناساندن SQL-CE . مابقی مسایل (نکات mapping، کوئری‌ها و غیره) هیچ تفاوتی با سایر بانک‌های اطلاعاتی نخواهد داشت و یکی است. به این معنا که اگر برنامه شما از ویژگی‌های خاص بانک‌های اطلاعاتی استفاده نکند (مثلا اگر از رویه‌های ذخیره شده اس کیوال سرور استفاده نکرده باشد)، فقط با تغییر کانکشن استرینگ و معرفی dialect و driver جدید، به سادگی می‌تواند به یک بانک اطلاعاتی دیگر سوئیچ کند؛ بدون اینکه حتی بخواهید یک سطر از کدهای اصلی برنامه خود را تغییر دهید.



              تنها نکته جدید آن این متد است:

              private Configuration getConfig()
              {
              var configure = new Configuration();
              configure.SessionFactoryName("BuildIt");

              configure.DataBaseIntegration(db =>
              {
              db.ConnectionProvider<DriverConnectionProvider>();
              db.Dialect<MsSqlCe40Dialect>();
              db.Driver<SqlServerCeDriver>();
              db.KeywordsAutoImport = Hbm2DDLKeyWords.AutoQuote;
              db.IsolationLevel = IsolationLevel.ReadCommitted;
              db.ConnectionString = ConnectionString;
              db.Timeout = 10;

              //for testing ...
              db.LogFormattedSql = true;
              db.LogSqlInConsole = true;
              });

              return configure;
              }

              که در آن نحوه تعریف MsSqlCe40Dialect و SqlServerCeDriver مشخص شده است.

              نکته حاشیه‌ای!
              در این مثال primary key از نوع identity تعریف شده و بدون مشکل کار کرد. همین را اگر با EF تست کنید، این خطا را دریافت می‌کنید: «Server-generated keys and server-generated values are not supported by SQL Server Compact». بله، EF نمی‌تواند با primary key از نوع identity حین کار با SQL-CE کار کند. برای رفع آن توصیه شده است که از Guid استفاده کنید!

              نکته تکمیلی:
              استفاده از Dialect سفارشی در NHibernate


              نکته پایانی!
              و در پایان باید اشاره کرد که SQL-CE یک بانک اطلاعاتی نوشته شده با دات نت نیست (با CPP نوشته شده است و نصب آن هم نیاز به ران تایم به روز VC را دارد). به این معنا که جهت سیستم‌های 64 بیتی و 32 بیتی باید نسخه مناسب آن‌را توزیع کنید. یا اینکه Target platform پروژه جاری دات نت خود را بر روی X86 قرار دهید (نه بر روی Any CPU پیش فرض) و در این حالت تنها یک نسخه X86 بانک اطلاعاتی SQL-CE و همچنین برنامه خود را برای تمام سیستم‌ها توزیع کنید.