مسیرراه‌ها
ASP.NET MVC
              مطالب
              فعال سازی قسمت ارسال فایل و تصویر ویرایشگر آنلاین RedActor در ASP.NET MVC
              در سایت جاری از ویرایشگر آنلاین RedActor استفاده شده و کار کردن با آن هم بسیار ساده است:
              یک TextArea ساده را به صفحه اضافه کرده و این افزونه جی‌کوئری را بر روی آن اجرا می‌کنید. به این ترتیب TextArea به صورت خودکار تبدیل به یک ویرایشگر مطلوب خواهد شد. برای مثال:
              @Html.TextAreaFor(model => model.ArticleBody, htmlAttributes: new { style = "width:98%; height:500px" })
              
              <script type="text/javascript">
              $('#ArticleBody').redactor({
                              autoformat: false,
                              convertDivs: false
                          });
              </script>
              اما فعال سازی قسمت ارسال فایل و تصویر همراه با آن چطور؟
              یک سری مثال نوشته شده با PHP به همراه این ویرایشگر آنلاین هستند که برای ایده گرفتن بد نیستند (البته به این معنا نیست که این ویرایشگر نیازی به PHP دارد. تنها قسمت سمت سرور مثال‌های آن با PHP است). برای مثال اگر در PHP از دستور echo برای ارائه یک نتیجه نهایی به ویرایشگر RedActor استفاده شده، معادل آن در ASP.NET MVC مساوی return Content است.
              <script type="text/javascript">
              $('#ArticleBody').redactor({
                              imageUpload: "@Url.Action(result: MVC.RedactorUpload.ImageUpload())",
                              fileUpload: "@Url.Action(result: MVC.RedactorUpload.FileUpload())",
                              linkFileUpload: "@Url.Action(result: MVC.RedactorUpload.FileLinkUpload())"
                              , autoformat: false
                              , convertDivs: false
                          });
              </script>
              برای فعال سازی قسمت آپلود این ادیتور نیاز است پارامترهای imageUpload، fileUpload و linkFileUpload مقدار دهی شوند.
              همانطور که ملاحظه می‌کنید از T4MVC برای مشخص سازی مسیرها استفاده شده. برای مثال MVC.RedactorUpload.ImageUpload به این معنا است که در کنترلری به نام RedactorUpload، اکشن متدی به نام ImageUpload پذیرای ارسال فایل ادیتور خواهد بود و به همین ترتیب در مورد سایر پارامترها.
              RedactorUploadController هم ساختار بسیار ساده‌ای دارد. برای مثال هر کدام از متدهای آپلود یاد شده یک چنین امضایی دارند:
              [HttpPost]
              public virtual ActionResult ImageUpload(HttpPostedFileBase file)
              {
              }
              البته در مورد مسایل امنیتی آپلود هم پیشتر در سایت بحث شده است. برای مثال در اینجا استفاده از فیلتر زیر را فراموش نکنید:
               [AllowUploadSpecialFilesOnly(".jpg,.gif,.png")]
              در هر کدام از متدهای آپلود (به سه متد برای سه پارامتر یاد شده نیاز است)، ابتدا HttpPostedFileBase را در پوشه‌ایی که مدنظر دارید ذخیره کنید. سپس باید محتوایی را به RedActor بازگشت دهید و اصل کار یکپارچگی با ASP.NET MVC نیز در همینجا است:
              در حالت imageUpload، محتوایی به شکل زیر باید بازگشت داده شود:
               return Content("<img src='" + path + "' />");
              در حالت fileUpload، پس از ذخیره سازی فایل در سرور، مسیر آن باید به نحو زیر بازگشت داده شود:
               return Content("<a href=" + path + ">" + someName + "</a>");
              و در حالت linkFileUpload فقط باید مسیر نهایی فایل ذخیره شده بر روی سرور را بازگشت دهید:
               return Content(path);

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

              پ.ن.
              قسمتی از مطالب متن فوق در نگارش جدید این ویرایشگر به نحو زیر تغییر کرده است.
              مطالب
              اعتبارسنجی سایتهای چند زبانه در ASP.NET MVC - قسمت اول

              اگر در حال تهیه یک سایت چند زبانه هستید و همچنین سری مقالات Globalization در ASP.NET MVC رو دنبال کرده باشید میدانید که با تغییر Culture فایلهای Resource مورد نظر بارگذاری و نوشته‌های سایت تغییر میابند ولی با تغییر Culture رفتار اعتبارسنجی در سمت سرور نیز تغییر و اعتبارسنجی بر اساس Culture فعلی سایت انجام میگیرد. بررسی این موضوع را با یک مثال شروع میکنیم.

              یک پروژه وب بسازید سپس به پوشه Models یک کلاس با نام ValueModel اضافه کنید. تعریف کلاس به شکل زیر هست: 

              public class ValueModel
              {
                  [Required]
                  [Display(Name = "Decimal Value")]
                  public decimal DecimalValue { get; set; }
              
                  [Required]
                  [Display(Name = "Double Value")]
                  public double DoubleValue { get; set; }
              
                  [Required]
                  [Display(Name = "Integer Value")]
                  public int IntegerValue { get; set; }
              
                  [Required]
                  [Display(Name = "Date Value")]
                  public DateTime DateValue { get; set; }
              }

              به سراغ کلاس HomeController بروید و کدهای زیر را اضافه کنید: 

              [HttpPost]
              public ActionResult Index(ValueModel valueModel)
              {
                  if (ModelState.IsValid)
                  {
                      return Redirect("Index");
                  }
              
                  return View(valueModel);
              }

              Culture را به fa-IR تغییر میدهیم، برای اینکار در فایل web.config در بخش system.web کد زیر اضافه نمایید: 

              <globalization culture="fa-IR" uiCulture="fa-IR" />

              و در نهایت به سراغ فایل Index.cshtml بروید کدهای زیر رو اضافه کنید:

              @using (Html.BeginForm())
              {
                  <ol>
                      <li>
                          @Html.LabelFor(m => m.DecimalValue)
                          @Html.TextBoxFor(m => m.DecimalValue)
                          @Html.ValidationMessageFor(m => m.DecimalValue)
                      </li>
                      <li>
                          @Html.LabelFor(m => m.DoubleValue)
                          @Html.TextBoxFor(m => m.DoubleValue)
                          @Html.ValidationMessageFor(m => m.DoubleValue)
                      </li>
                      <li>
                          @Html.LabelFor(m => m.IntegerValue)
                          @Html.TextBoxFor(m => m.IntegerValue)
                          @Html.ValidationMessageFor(m => m.IntegerValue)
                      </li>
                      <li>
                          @Html.LabelFor(m => m.DateValue)
                          @Html.TextBoxFor(m => m.DateValue)
                          @Html.ValidationMessageFor(m => m.DateValue)
                      </li>
                      <li>
                          <input type="submit" value="Submit"/>
                      </li>
                  </ol>
              }

              پرژه را اجرا نمایید و در ٢ تکست باکس اول ٢ عدد اعشاری را و در ٢ تکست باکس آخر یک عدد صحیح و یک تاریخ وارد نمایید و سپس دکمه Submit را بزنید. پس از بازگشت صفحه از سمت سرور در در ٢ تکست باکس اول با این پیامها روبرو میشوید که مقادیر وارد شده نامعتبر میباشند. 

              اگر پروژه رو در حالت دیباگ اجرا کنیم و نگاهی به داخل ModelState بیاندازیم، میبینیم که کاراکتر جدا کننده قسمت اعشاری برای fa-IR '/' میباشد که در اینجا برای اعداد مورد نظر کاراکتر '.' وارد شده است. 

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

              در سمت کلاینت برای اینکه کاربر را مجبور به وارد کردن کاراکترهای مربوط به Culture فعلی سایت نماییم باید مقادیر وارد شده را اعتبارسنجی و در صورت معتبر نبودن مقادیر پیام مناسب نشان داده شود. برای اینکار از کتابخانه jQuery Globalize استفاده میکنیم. برای اضافه کردن jQuery Globalize از طریق کنسول nuget فرمان زیر اجرا نمایید: 

              PM> Install-Package jquery-globalize

               پس از نصب کتابخانه  اگر به پوشه Scripts نگاهی بیاندازید میبینید که پوشەای با نام jquery.globalize اضافه شده است. درداخل پوشه زیر پوشەی دیگری با نام cultures وجود دارد که در آن Cultureهای مختلف وجود دارد و بسته به نیاز میتوان از آنها استفاده کرد. دوباره به سراغ فایل Index.cshtm بروید و فایلهای جاوا اسکریپتی زیر را به صفحه اضافه کنید:

              <script src="~/Scripts/jquery.validate.js"> </script>
              <script src="~/Scripts/jquery.validate.unobtrusive.js"> </script>
              <script src="~/Scripts/jquery.globalize/globalize.js"> </script>
              <script src="~/Scripts/jquery.globalize/cultures/globalize.culture.fa-IR.js"> </script>

              در فایل globalize.culture.fa-IR.js کاراکتر جدا کننده اعشاری '.' در نظر گرفته شده است که مجبور به تغییر آن هسیتم. برای اینکار فایل را باز کرده و numberFormat را پیدا کنید و آن را به شکل زیر تغییر دهید: 

              numberFormat: {
                  pattern: ["n-"],
                  ".": "/",
                  currency: {
                      pattern: ["$n-", "$ n"],
                      ".": "/",
                      symbol: "ریال"
                  }
              },

              و در نهایت کدهای زیر را به فایل Index.cshtml اضافه کنید و برنامه را دوباره اجرا نمایید:

              Globalize.culture('fa-IR');
              $.validator.methods.number = function(value, element) {
                  if (value.indexOf('.') > 0) {
                      return false;
                  }
                  var splitedValue = value.split('/');
                  if (splitedValue.length === 1) {
                      return !isNaN(Globalize.parseInt(value));
                  } else if (splitedValue.length === 2 && $.trim(splitedValue[1]).length === 0) {
                      return false;
                  }
                  return !isNaN(Globalize.parseFloat(value));
              };
              };

              در خط اول Culture را ست مینمایم و در ادامه نحوه اعتبارسنجی را در unobtrusive validation تغییر میدهیم. از آنجایی که برای اعتبارسنجی عدد وارد شده از تابع parseFloat استفاده میشود، کاراکتر جدا کننده قسمت اعشاری قابل قبول برای این تابع '.' است پس در داخل تابع دوباره '/' به '.' تبدیل میشود و سپس اعتبارسنجی انجام میشود از اینرو اگر کاربر '.' را نیز وارد نماید قابل قبول است به همین دلیل با این خط کد if (value.indexOf('.') > 0) وجود نقطه را بررسی میکنیم تا در صورت وجود '.' پیغام خطا نشان داده شود.در خط بعدی بررسی مینماییم که اگر عدد وارد شده اعشاری نباشد از تابع parseInt  استفاده نماییم. در خط بعدی این حالت را بررسی مینماییم که اگر کاربر عددی همچون /١٢ وارد کرد پیغام خطا صادر شود. 

              برای اعتبارسنجی تاریخ شمسی متاسفانه توابع کمکی برای تبدیل تاریخ در فایل globalize.culture.fa-IR.js وجود ندارد ولی اگر نگاهی به فایلهای Culture عربی بیاندازید همه دارای توابع کمکی برای تبدیل تاریج هجری به میلادی هستند به همین دلیل امکان اعتبارسنجی تاریخ شمسی با استفاده از jQuery Globalize میسر نمیباشد. من خودم تعدادی توابع کمکی را به globalize.culture.fa-IR.js اضافه کردەام که از تقویم فارسی آقای علی فرهادی برداشت شده است و با آنها کار اعتبارسنجی را انجام میدهیم. لازم به ذکر است این روش ١٠٠% تست نشده است و شاید راه کاملا اصولی نباشد ولی به هر حال در اینجا توضیح میدهم. در فایل globalize.culture.fa-IR.js قسمت Gregorian_Localized را پیدا کنید و آن را با کدهای زیر جایگزین کنید: 

              Gregorian_Localized: {
                  firstDay: 6,
                  days: {
                      names: ["یکشنبه", "دوشنبه", "سه شنبه", "چهارشنبه", "پنجشنبه", "جمعه", "شنبه"],
                      namesAbbr: ["یکشنبه", "دوشنبه", "سه شنبه", "چهارشنبه", "پنجشنبه", "جمعه", "شنبه"],
                      namesShort: ["ی", "د", "س", "چ", "پ", "ج", "ش"]
                  },
                  months: {
                      names: ["ژانویه", "فوریه", "مارس", "آوریل", "می", "ژوئن", "ژوئیه", "اوت", "سپتامبر", "اُکتبر", "نوامبر", "دسامبر", ""],
                      namesAbbr: ["ژانویه", "فوریه", "مارس", "آوریل", "می", "ژوئن", "ژوئیه", "اوت", "سپتامبر", "اُکتبر", "نوامبر", "دسامبر", ""]
                  },
                  AM: ["ق.ظ", "ق.ظ", "ق.ظ"],
                  PM: ["ب.ظ", "ب.ظ", "ب.ظ"],
                  patterns: {
                      d: "yyyy/MM/dd",
                      D: "yyyy/MM/dd",
                      t: "hh:mm tt",
                      T: "hh:mm:ss tt",
                      f: "yyyy/MM/dd hh:mm tt",
                      F: "yyyy/MM/dd hh:mm:ss tt",
                      M: "dd MMMM"
                  },
                  JalaliDate: {
                      g_days_in_month: [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31],
                      j_days_in_month: [31, 31, 31, 31, 31, 31, 30, 30, 30, 30, 30, 29]
                  },
                  gregorianToJalali: function (gY, gM, gD) {
                      gY = parseInt(gY);
                      gM = parseInt(gM);
                      gD = parseInt(gD);
                      var gy = gY - 1600;
                      var gm = gM - 1;
                      var gd = gD - 1;
              
                      var gDayNo = 365 * gy + parseInt((gy + 3) / 4) - parseInt((gy + 99) / 100) + parseInt((gy + 399) / 400);
              
                      for (var i = 0; i < gm; ++i)
                          gDayNo += Globalize.culture().calendars.Gregorian_Localized.JalaliDate.g_days_in_month[i];
                      if (gm > 1 && ((gy % 4 == 0 && gy % 100 != 0) || (gy % 400 == 0)))
                          /* leap and after Feb */
                          ++gDayNo;
                      gDayNo += gd;
              
                      var jDayNo = gDayNo - 79;
              
                      var jNp = parseInt(jDayNo / 12053);
                      jDayNo %= 12053;
              
                      var jy = 979 + 33 * jNp + 4 * parseInt(jDayNo / 1461);
              
                      jDayNo %= 1461;
              
                      if (jDayNo >= 366) {
                          jy += parseInt((jDayNo - 1) / 365);
                          jDayNo = (jDayNo - 1) % 365;
                      }
              
                      for (var i = 0; i < 11 && jDayNo >= Globalize.culture().calendars.Gregorian_Localized.JalaliDate.j_days_in_month[i]; ++i) {
                          jDayNo -= Globalize.culture().calendars.Gregorian_Localized.JalaliDate.j_days_in_month[i];
                      }
                      var jm = i + 1;
                      var jd = jDayNo + 1;
              
                      return [jy, jm, jd];
                  },
                  jalaliToGregorian: function (jY, jM, jD) {
                      jY = parseInt(jY);
                      jM = parseInt(jM);
                      jD = parseInt(jD);
                      var jy = jY - 979;
                      var jm = jM - 1;
                      var jd = jD - 1;
              
                      var jDayNo = 365 * jy + parseInt(jy / 33) * 8 + parseInt((jy % 33 + 3) / 4);
                      for (var i = 0; i < jm; ++i) jDayNo += Globalize.culture().calendars.Gregorian_Localized.JalaliDate.j_days_in_month[i];
              
                      jDayNo += jd;
              
                      var gDayNo = jDayNo + 79;
              
                      var gy = 1600 + 400 * parseInt(gDayNo / 146097); /* 146097 = 365*400 + 400/4 - 400/100 + 400/400 */
                      gDayNo = gDayNo % 146097;
              
                      var leap = true;
                      if (gDayNo >= 36525) /* 36525 = 365*100 + 100/4 */ {
                          gDayNo--;
                          gy += 100 * parseInt(gDayNo / 36524); /* 36524 = 365*100 + 100/4 - 100/100 */
                          gDayNo = gDayNo % 36524;
              
                          if (gDayNo >= 365)
                              gDayNo++;
                          else
                              leap = false;
                      }
              
                      gy += 4 * parseInt(gDayNo / 1461); /* 1461 = 365*4 + 4/4 */
                      gDayNo %= 1461;
              
                      if (gDayNo >= 366) {
                          leap = false;
              
                          gDayNo--;
                          gy += parseInt(gDayNo / 365);
                          gDayNo = gDayNo % 365;
                      }
              
                      for (var i = 0; gDayNo >= Globalize.culture().calendars.Gregorian_Localized.JalaliDate.g_days_in_month[i] + (i == 1 && leap) ; i++)
                          gDayNo -= Globalize.culture().calendars.Gregorian_Localized.JalaliDate.g_days_in_month[i] + (i == 1 && leap);
                      var gm = i + 1;
                      var gd = gDayNo + 1;
              
                      return [gy, gm, gd];
                  },
                  checkDate: function (jY, jM, jD) {
                      return !(jY < 0 || jY > 32767 || jM < 1 || jM > 12 || jD < 1 || jD >
                          (Globalize.culture().calendars.Gregorian_Localized.JalaliDate.j_days_in_month[jM - 1] + (jM == 12 && !((jY - 979) % 33 % 4))));
                  },
                  convert: function (value, format) {
                      var day, month, year;
              
                      var formatParts = format.split('/');
                      var dateParts = value.split('/');
                      if (formatParts.length !== 3 || dateParts.length !== 3) {
                          return false;
                      }
              
                      for (var j = 0; j < formatParts.length; j++) {
                          var currentFormat = formatParts[j];
                          var currentDate = dateParts[j];
                          switch (currentFormat) {
                              case 'dd':
                                  if (currentDate.length === 2 || currentDate.length === 1) {
                                      day = currentDate;
                                  } else {
                                      year = currentDate;
                                  }
                                  break;
                              case 'MM':
                                  month = currentDate;
                                  break;
                              case 'yyyy':
                                  if (currentDate.length === 4) {
                                      year = currentDate;
                                  } else {
                                      day = currentDate;
                                  }
                                  break;
                              default:
                                  return false;
                          }
                      }
              
                      year = parseInt(year);
                      month = parseInt(month);
                      day = parseInt(day);
                      var isValidDate = Globalize.culture().calendars.Gregorian_Localized.checkDate(year, month, day);
                      if (!isValidDate) {
                          return false;
                      }
              
                      var grDate = Globalize.culture().calendars.Gregorian_Localized.jalaliToGregorian(year, month, day);
                      var shDate = Globalize.culture().calendars.Gregorian_Localized.gregorianToJalali(grDate[0], grDate[1], grDate[2]);
              
                      if (year === shDate[0] && month === shDate[1] && day === shDate[2]) {
                          return true;
                      }
              
                      return false;
                  }
              },

              روال کار در تابع convert به اینصورت است که ابتدا تاریخ وارد شده را بررسی مینماید تا معتبر بودن آن معلوم شود به عنوان مثال اگر تاریخی مثل 1392/12/31 وارد شده باشد و در ادامه برای بررسی بیشتر تاریخ یک بار به میلادی و تاریخ میلادی دوباره به شمسی تبدیل میشود و با تاریخ وارد شده مقایسه میشود و در صورت برابری تاریخ معتبر اعلام میشود. در فایل Index.cshtml کدهای زیر اضافی نمایید:

              $.validator.methods.date = function (value, element) {
                  return Globalize.culture().calendars.Gregorian_Localized.convert(value, 'yyyy/MM/dd');
              };

              برای اعتبارسنجی تاریخ میتوانید از ٢ فرمت استفاده کنید:

              ١ – yyyy/MM/dd

              ٢ – dd/MM/yyyy

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

              در کل استفاده از jQuery Globalize برای اعتبارسنجی در سایتهای چند زبانه به نسبت خوب میباشد و برای هر زبان میتوانید از culture مورد نظر استفاده نمایید. در قسمت دوم این مطلب به بررسی بخش سمت سرور میپردازیم.

              اشتراک‌ها
              خلاصه‌ای از EF7
              - بر مبنای code base بیش از یک میلیون سطری EF 6 نیست.
              - سورس باز خواهد بود (^).
              - کلیه مباحث قدیمی database first آن مانند ObjectContext از آن حذف شده‌اند و فقط مباحث Code first مانند DbContext باقی مانده‌اند.
              - روش کار آن با منابع داده خارجی طوری است که اجازه استفاده از بانک‌های اطلاعاتی NoSQL را هم می‌دهد.
              - از برنامه‌های دسکتاپ تا ویندوز فون را پشتیبانی می‌کند.
              خلاصه‌ای از EF7
              مطالب
              ارتقاء به ASP.NET Core 1.0 - قسمت 1 - NET Core. چیست؟
              NET Core. چیست؟

              برای اغلب توسعه دهنده‌های دات نت (برنامه‌های وب و دسکتاپ) تنها یک دات نت فریم ورک شناخته شده وجود دارد: The `Full` .NET Framework
              که تنها بر روی ویندوز قابل اجرا است و آخرین نگارش پایدار آن در زمان نگارش این مطلب، 4.6.1 است. این فریم ورک بزرگ، از اجزایی تشکیل شده‌است که در تصویر ذیل قابل مشاهده‌اند:


              مهم‌ترین قسمت‌های این فریم ورک «بزرگ» شامل مواردی مانند CLR که کار تبدیل کدهای IL را به کدهای ماشین انجام می‌دهد، BCL که کلاس‌های پایه‌ای را جهت کار با  IO، Text و غیره، فراهم می‌کنند، هستند؛ به علاوه کتابخانه‌هایی مانند Windows Forms، WPF و ASP.NET که برفراز BCL و CLR کار می‌کنند.
              هرچند تعدادی از توسعه دهنده‌های دات نت تنها با Full framework کار می‌کنند، اما در طی سال‌های اخیر انشعابات بسیار دیگری از آن به وجود آمده‌اند؛ مانند دات نت‌های ویژه‌ی ویندوزهای 8 و Universal Windows Platform، دات نت مخصوص ویندوز فون 8 و ویندوز فون مبتنی بر پلتفرم سیلورلایت، به علاوه دات نت پلتفرم زامارین برای توسعه‌ی برنامه‌های iOS و Android نیز هم اکنون وجود دارند (البته در اینجا Mono، دات نت میکرو و غیره را هم باید ذکر کرد). این فریم ورک‌ها و انشعابات، به همراه پیاده سازی یک سری موارد مشترک و مواردی کاملا اختصاصی هستند که به سایر پلتفرم‌های دیگر قابل انتقال نیستند.

              با زیاد شدن تعداد انشعابات دات نت «بزرگ»، نوشتن کدی که قابل اجرای بر روی تمام پلتفرم‌های یاد شده باشد، مشکل شد. اینجا بود که مفهومی را به نام PCL یا Portable class libraries معرفی کردند:


              هدف از PCLها، ساده سازی کامپایل و به اشتراک گذاری کد بین پلتفرم‌های مختلف بود و پشتیبانی قابل توجهی هم از آن در VS.NET وجود دارد. هرچند این روش نسبتا موفق بود اما مشکلاتی را هم به همراه داشت. برای مثال با ارائه‌ی یک انشعاب و پلتفرم دیگری از دات نت «بزرگ»، کتابخانه‌ی PCL موجود، باید برای این انشعاب جدید مجددا کامپایل شود. به علاوه در اینجا تنها محدود به انتخاب امکانات مشترک بین پلتفرم‌های مختلف هستید.

              برای رفع این مشکلات در پایان سال 2014، یک «دات نت فریم ورک جدید» به نام NET Core. معرفی شد که سورس باز است و همچنین چندسکویی (از ویندوز، لینوکس و OSX پشتیبانی می‌کند).


              هرچند پیشتر Windows Store و ASP.NET Core app به صورت پلتفرم‌هایی مجزا ارائه شده بودند، اما اکنون از یک BCL مشترک به نام CoreFX استفاده می‌کنند و نحوه‌ی توزیع آن‌ها صرفا از طریق نیوگت است. به عبارتی اینبار بجای دریافت یک فریم ورک «بزرگ»، تنها اجزایی را دریافت می‌کنید که از طریق نیوگت سفارش داده‌اید.
              به این ترتیب نه تنها کار توزیع برنامه‌های مبتنی بر NET Core. با سهولت بیشتری انجام خواهد شد، بلکه به روز رسانی اجزای یک برنامه، تاثیری بر روی سایر برنامه‌ها نخواهد داشت و مشکلات جانبی را به وجود نمی‌آورد. به علاوه دیگر نیازی نیست تا منتظر یک نگارش «بزرگ» دیگر باشید تا بتوانید آخرین به روز رسانی‌ها را دریافت کنید. اینبار به روز رسانی بسته‌های نیوگت برنامه معادل هستند با به روز رسانی کل فریم ورک در نگارش‌های قبلی «بزرگ» آن. در اینجا حتی CoreCLR و NET Native runtime. که مربوط به Windows runtime است هم از طریق نیوگت به روز رسانی می‌شود.

              البته NET Core. انتهای مسیر نیست و هم اکنون NETStandard نیز جهت رفع مشکلات کامپایل مجدد PCLها در حال توسعه است و پس از ارائه‌ی آن، PCLها منسوخ شده درنظر گرفته می‌شوند. در این حالت با انتخاب target platform ایی به نام NETStandard (بجای مثلا انتخاب دات نت 4.5 و ویندوز فون 8)، اینبار دات نت 4.5 و ویندوز فون 8 و تمام پلتفرم‌های دیگر، به صورت یکجا انتخاب می‌شوند و اگر پلتفرم جدیدی برای مثال از NETStandard نگارش 1.1 پشتیبانی کند، به این معنا است که کتابخانه‌ی شما هم اکنون با آن سازگار است و دیگر نیازی به کامپایل مجدد آن نخواهد بود.
              به علاوه هر برنامه‌ای که بر اساس NETStandard تهیه شود، قابلیت اجرای بر روی NET Core. را نیز خواهد داشت. به عبارتی برنامه‌های NETStandard همان برنامه‌های مبتنی بر NET Core. هستند.


              ASP.NET Core چیست؟

              در زمان نگارش این مطلب، دو گزینه‌ی برنامه‌های وب ASP.NET Core 1.0 و همچنین Windows Store apps (مبتنی بر NET Native Runtime.) قابلیت استفاده‌ی از این پلتفرم جدید NET Core. دارند.
              ASP.NET Core 1.0، که پیشتر با نام ASP.NET 5 معرفی شده بود، بازنویسی کامل ASP.NET است که با ایده‌ی کاملا ماژولار بودن، تهیه شده‌است و از طریق آن، قابلیت به روز رسانی منظم و توزیع آسان از طریق نیوگت، میسر خواهد شد. به علاوه در آن، بسیاری از الگوهای برنامه نویسی شیء‌گرا مانند تزریق وابستگی‌ها، به صورت توکار و از ابتدا پشتیبانی می‌شوند.
              ASP.NET Core 1.0 از WebForms ، VB ، WebPages و SignalR پشتیبانی نمی‌کند. البته در این بین عدم پشتیبانی از «وب فرم‌ها» قطعی است؛ اما افزودن سه مورد دیگر یاد شده، جزو لیست کارهای پس از ارائه‌ی نگارش 1 این فریم ورک قرار دارند و به زودی ارائه خواهند شد.


              اکنون وضعیت  ASP.NET MVC 5 و ASP.NET Web API 2 چگونه است؟

              ASP.NET Core 1.0 مدل برنامه نویسی ASP.NET MVC و Web API را به صورت یکپارچه ارائه می‌دهد و دیگر خبری از ارائه‌ی مجزای این‌ها نخواهد بود و دقیقا بر مبنای مفاهیم برنامه نویسی این دو بنا شده‌است. به صورت خلاصه MVC + Web API + Web Pages = Core MVC 1.0
              پیشتر فضای نام System.Web.MVC مخصوص ASP.NET MVC بود و فضای نام مجزای دیگری به نام System.Web.Http مخصوص ASP.NET Web API. اما اکنون تنها یک فضای نام مشترک و یکپارچه به نام Microsoft.AspNet.Mvc هر دوی این‌ها را پوشش می‌دهد.

              در این نگارش جدید وابستگی از system.web مبتنی بر IIS حذف شده‌است و با استفاده از هاست جدید چندسکویی به نام Kesterl، به سرعتی 5 برابر سرعت NodeJS دست یافته‌اند.


              آخرین تاریخ به روز رسانی ASP.NET MVC 5.x دوشنبه، 20 بهمن 1393 است (با ارائه نگارش 5.2.3 که آخرین نگارش رسمی و پایدار آن است) و آخرین تاریخ به روز رسانی ASP.NET Web API 2.x نیز همان روز است.
              هرچند مایکروسافت عادت به اعلام رسمی پایان پشتیبانی از بسیاری از محصولات خود را ندارد اما تمام فناوری‌های «قدیمی» خودش را بر روی CodePlex نگهداری می‌کند و تمام فناوری‌های «جدید» را به GitHub منتقل کرده‌است. بنابراین اگر در مورد فناوری خاصی به Codeplex رسیدید، یعنی «دیگر ادامه‌ی رسمی نخواهد یافت» و حداکثر در حد رفع یک سری باگ‌ها و مشکلات گزارش شده باقی می‌مانند.
              مثال 1: هم اکنون نگارش دوم ASP.NET Identity را بر روی Codeplex می‌توانید مشاهده کنید. نگارش سوم آن به GitHub منتقل شد‌ه‌است که این نگارش صرفا با ASP.NET Core 1.0 سازگار است. در مورد ASP.NET MVC و Web API نیز چنین حالتی رخ داده‌است. نگارش‌های 5 و 2 آن‌ها بر روی Codeplex موجود هستند و نگارش ششم که به ASP.NET Core 1.0 تغییر نام یافت و ترکیبی است از MVC و Web API، در GitHub توسعه می‌یابد.
              مثال 2: WCF به علت پیچیدگی بیش از حد و مدرن نبودن طراحی آن، رقابت را به ASP.NET Web API 2.x واگذار کرد و مدل برنامه نویسی ASP.NET Web API 2.x نیز هم اکنون جزئی از ASP.NET Core 1.0 است. بنابراین اگر قصد ایجاد پروژه‌ی جدیدی را بر این مبنا دارید، بهتر است با APS.NET Core 1.0 کار را شروع کنید.


              اما هنوز تعداد زیادی از کتابخانه‌های Full framework به NET Core. انتقال پیدا نکرده‌اند

              برای نمونه هنوز EF Core 1.0 که پیشتر نام EF 7.x به آن داده شده بود، به مرحله‌ی نهایی تکمیل قابلیت‌های آن نرسیده‌است. اما باید دانست که ASP.NET Core 1.0 صرفا بر فراز NET Core. قابل اجرا نیست؛ بلکه قابلیت اجرای بر فراز NET 4.6. و یا همان دات نت «بزرگ و کامل» را نیز دارد. بنابراین به سادگی قابلیت اجرای EF 6.x و یا NHibernate را نیز دارا است. تنها مزیتی را که در اینجا از دست خواهید، قابلیت چندسکویی بودن ASP.NET Core 1.0 است؛ زیرا EF 6.x با چنین دیدی طراحی نشده‌است.



              همانطور که ملاحظه می‌کنید، ASP.NET Core 1.0 قابلیت اجرای بر روی هر دوی NET Core 1.0. و NET 4.6. را دارا است. اما یکی، چندسکویی است و دیگری صرفا مختص به ویندوز.


              فناورهای منسوخ شده‌ی در NET Core.

              یکسری از فناوری‌ها و کتابخانه‌ها احتمالا هیچگاه قابلیت انتقال به NET Core. را نخواهند یافت و یا حداقل باید تا چندنگارش بعدی آن صبر کنند. فناوری‌های خاتمه یافته‌ی با NET Core. به شرح زیر هستند:
              - Reflection: همانطور که عنوان شد، NET Core. بر فراز CoreCLR و همچنین NET Native runtime. اجرا می‌شود و تولید برنامه‌های native و static linking آن‌ها مانند برنامه‌های ++C، نیاز به دانستن اجزایی دارد که به صورت پویا فراخوانی نمی‌شوند و بلافاصله و در زمان کامپایل، توسط کامپایلر قابل تشخیص هستند. همین محدودیت سبب شده‌است که استفاده‌ی از Reflection در NET Core. به حداقل ممکن آن برسد. برای مثال در System.Object متد GetType آن تنها نام نوع را باز می‌گرداند و نه اطلاعات بیشتری را مانند  GetMembers سابق.
              - App Domains: هرچند CoreCLR از App Domains پشتیبانی می‌کند اما NET Native runtime. خیر. به همین جهت برای ایزوله سازی برنامه‌ها توصیه شده‌است که از containerهایی مانند docker استفاده شود.
              - Remoting: پیش از WCF جهت برقراری ارتباط بین برنامه‌ها مطرح شده بود و هم اکنون در دات نت کامل هم آنچنان استفاده‌ای از آن نمی‌شود.
              - binary serialization: البته کتابخانه‌هایی مانند JSON.NET و امثال آن، نگارش NET Core. هم دارند؛ اما چون binary serialization نیاز به اطلاعات reflection قابل توجهی دارد دیگر پشتیبانی نخواهد شد.


              فناور‌هایی که به زودی به NET Core. منتقل می‌شوند

              یکسری از فناوری‌ها مانند XAML هنوز معادل NET Core. ندارند و لیست زیر قرار است از طرف مایکروسافت سورس باز شده و همچنین به NET Core. منتقل شود:
              System.Data
              System.DirectoryServices
              System.Drawing
              System.Transactions
              System.Xml.Xsl and System.Xml.Schema
              System.Net.Mail
              System.IO.Ports
              System.Workflow
              System.Xaml


              مراحل نصب ASP.NET Core 1.0

              پیش از نصب نگارش 1.0 RTM باید به این نکته دقت داشت که نصاب آن، نگارش‌های آزمایشی قبلی را حذف و یا بازنویسی نمی‌کند و همین مساله ممکن است سبب بروز تداخل‌هایی و یا حتی از کار افتادن VS.NET شما شود. بنابراین اگر نگارش‌های RC یا بتا را پیشتر نصب کرده‌اید، به Add remove programs ویندوز مراجعه کرده و سه مورد ذیل را حتما حذف کنید (خیلی مهم):
              - Preview Tooling (all versions)
              - NET Core Runtime SDK (all versions).
              - NET Core Runtime (all Versions).
              پس از حذف بسته‌های قدیمی، برای نصب نگارش 1.0 RTM، ابتدا نیاز است Visual Studio 2015 Update 3 را نصب کنید و پس از آن با استفاده از NET Core for Visual Studio Official MSI Installer. کار نصب اجزای مورد نیاز آن انجام خواهد شد.


              بررسی شماره نگارش 1.0 RTM

              پس از نصب اجزای عنوان شده، خط فرمان را گشوده و دستور ذیل را صادر کنید:
               C:\Users\Vahid>dotnet --version
              1.0.0-preview2-003121
              همانطور که مشاهده می‌کنید، نگارش ذکر شده هنوز در مرحله‌ی preview است و صرفا مرتبط است به tooling و یا ابزارهای مرتبط با آن.
              اگر یک پروژه‌ی خالی ASP.NET Core Web Application را نیز شروع کنید (با طی مراحل زیر جهت ایجاد یک پروژه‌ی جدید):
               .NET Core -> ASP.NET Core Web Application (.NET Core) -> Select `Empty` Template


              در اینجا فایل جدیدی را به نام global.json مشاهده می‌کنید که محتوایات آن شامل دقیقا همین شماره نگارش است؛ به همراه معرفی پوشه‌های اصلی پروژه:
              {
                "projects": [ "src", "test" ],
                "sdk": {
                  "version": "1.0.0-preview2-003121"
                }
              }
              مطالب
              Globalization در ASP.NET MVC - قسمت دوم

              به‌روزرسانی فایلهای Resource در زمان اجرا

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

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

              یکی از روشهای نسبتا من‌درآوردی که برای ویرایش و به روزرسانی کلیدهای Resource وجود دارد بدین صورت است:
              - ابتدا باید اصل فایلهای Resource به همراه پروژه پابلیش شود. بهترین مکان برای نگهداری این فایلها فولدر App_Data است. زیرا محتویات این فولدر توسط سیستم FCN (همان File Change Notification) در ASP.NET رصد نمیشود.
              نکته: علت این حساسیت این است که FCN در ASP.NET تقریبا تمام محتویات فولدر سایت در سرور (فولدر App_Data یکی از معدود استثناهاست) را تحت نظر دارد و رفتار پیشفرض این است که با هر تغییری در این محتویات، AppDomain سایت Unload میشود که پس از اولین درخواست دوباره Load میشود. این اتفاق موجب از دست دادن تمام سشن‌ها و محتوای کش‌ها و ... میشود (اطلاعات بیشتر و کاملتر درباره نحوه رفتار FCN در اینجا).
              - سپس با استفاده یک مقدار کدنویسی امکاناتی برای ویرایش محتوای این فایلها فراهم شود. ازآنجا که محتوای این فایلها به صورت XML ذخیره میشود بنابراین براحتی میتوان با امکانات موجود این ویژگی را پیاده سازی کرد. اما در فضای نام System.Windows.Forms کلاسهایی وجود دارد که مخصوص کار با این فایلها طراحی شده اند که کار نمایش و ویرایش محتوای فایلهای Resource را ساده‌تر میکند. به این کلاسها در قسمت قبلی اشاره کوتاهی شده بود.
              - پس از ویرایش و به روزرسانی محتوای این فایلها باید کاری کنیم تا برنامه از این محتوای تغییر یافته به عنوان منبع جدید بهره بگیرد. اگر از این فایلهای Rsource به صورت embed استفاده شده باشد در هنگام build پروژه محتوای این فایلها به صورت Satellite Assembly در کنار کتابخانه‌های دیگر تولید میشود. اسمبلی مربوط به هر زبان هم در فولدری با عنوان زبان مربوطه ذخیره میشود. مسیر و نام فایل این اسمبلی‌ها مثلا به صورت زیر است:
              bin\fa\Resources.resources.dll
              بنابراین در این روش برای استفاده از محتوای به روز رسانی شده باید عملیات Build این کتابخانه دوباره انجام شود و کتابخانه‌های جدیدی تولید شود. راه حل اولی که به ذهن میرسد این است که از ابزارهای پایه و اصلی برای تولید این کتابخانه‌ها استفاده شود. این ابزارها (همانطور که در قسمت قبل نیز توضیح داده شد) عبارتند از Resource Generator و Assembly Linker. اما استفاده از این ابزارها و پیاده سازی روش مربوطه سختتر از آن است که به نظر می‌آید. خوشبختانه درون مجموعه عظیم دات نت ابزار مناسبتری برای این کار نیز وجود دارد که کار تولید کتابخانه‌های موردنظر را به سادگی انجام میدهد. این ابزار با عنوان Microsoft Build شناخته میشود که در اینجا توضیح داده شده است. 

              خواندن محتویات یک فایل resx.
              همانطور که در بالا توضیح داده شد برای راحتی کار میتوان از کلاس زیر که در فایل System.Windows.Forms.dll قرار دارد استفاده کرد:
              System.Resources.ResXResourceReader
              این کلاس چندین کانستراکتور دارد که مسیر فایل resx. یا استریم مربوطه به همراه چند گزینه دیگر را به عنوان ورودی میگیرد. این کلاس یک Enumator دارد که یک شی از نوع IDictionaryEnumerator برمیگرداند. هر عضو این enumerator از نوع object است. برای استفاده از این اعضا ابتدا باید آنرا به نوع DictionaryEntry تبدیل کرد. مثلا بصورت زیر:
              private void TestResXResourceReader()
              {
                using (var reader = new ResXResourceReader("Resource1.fa.resx"))
                {
                  foreach (var item in reader)
                  {
                    var resource = (DictionaryEntry)item;
                    Console.WriteLine("{0}: {1}", resource.Key, resource.Value);
                  }
                }
              }
              همانطور که ملاحظه میکنید استفاده از این کلاس بسیار ساده است. ازآنجاکه DictionaryEntry یک struct است، به عنوان یک راه حل مناسبتر بهتر است ابتدا کلاسی به صورت زیر تعریف شود:
              public class ResXResourceEntry
              {
                public string Key { get; set; }
                public string Value { get; set; }
                public ResXResourceEntry() { }
                public ResXResourceEntry(object key, object value)
                {
                  Key = key.ToString();
                  Value = value.ToString();
                }
                public ResXResourceEntry(DictionaryEntry dictionaryEntry)
                {
                  Key = dictionaryEntry.Key.ToString();
                  Value = dictionaryEntry.Value != null ? dictionaryEntry.Value.ToString() : string.Empty;
                }
                public DictionaryEntry ToDictionaryEntry()
                {
                  return new DictionaryEntry(Key, Value);
                }
              }
              سپس با استفاده از این کلاس خواهیم داشت:
              private static List<ResXResourceEntry> Read(string filePath)
              {
                using (var reader = new ResXResourceReader(filePath))
                {
                  return reader.Cast<object>().Cast<DictionaryEntry>().Select(de => new ResXResourceEntry(de)).ToList();
                }
              }
              حال این متد برای استفاده‌های آتی آماده است.

              نوشتن در فایل resx.
              برای نوشتن در یک فایل resx. میتوان از کلاس ResXResourceWriter استفاده کرد. این کلاس نیز در کتابخانه System.Windows.Forms در فایل System.Windows.Forms.dll قرار دارد:
              System.Resources.ResXResourceWriter
              متاسفانه در این کلاس امکان افزودن یا ویرایش یک کلید به تنهایی وجود ندارد. بنابراین برای ویرایش یا اضافه کردن حتی یک کلید کل فایل باید دوباره تولید شود. برای استفاده از این کلاس نیز میتوان به شکل زیر عمل کرد:
              private static void Write(IEnumerable<ResXResourceEntry> resources, string filePath)
              {
                using (var writer = new ResXResourceWriter(filePath))
                {
                  foreach (var resource in resources)
                  {
                    writer.AddResource(resource.Key, resource.Value);
                  }
                }
              }
              در متد فوق از همان کلاس ResXResourceEntry که در قسمت قبل معرفی شد، استفاده شده است. از متد زیر نیز میتوان برای حالت کلی حذف یا ویرایش استفاده کرد:
              private static void AddOrUpdate(ResXResourceEntry resource, string filePath)
              {
                var list = Read(filePath);
                var entry = list.SingleOrDefault(l => l.Key == resource.Key);
                if (entry == null)
                {
                  list.Add(resource);
                }
                else
                {
                  entry.Value = resource.Value;
                }
                Write(list, filePath);
              }
              در این متد از متدهای Read و Write که در بالا نشان داده شده‌اند استفاده شده است.

              حذف یک کلید در فایل resx.
              برای اینکار میتوان از متد زیر استفاده کرد:
              private static void Remove(string key, string filePath)
              {
                var list = Read(filePath);
                list.RemoveAll(l => l.Key == key); 
                Write(list, filePath);
              }
              در این متد، از متد Write که در قسمت معرفی شد، استفاده شده است.

              راه حل نهایی
              قبل از بکارگیری روشهای معرفی شده در این مطلب بهتر است ابتدا یکسری قرارداد بصورت زیر تعریف شوند:
              - طبق راهنماییهای موجود در قسمت قبل یک پروژه جداگانه با عنوان Resources برای نگهداری فایلهای resx. ایجاد شود.
              - همواره آخرین نسخه از محتویات موردنیاز از پروژه Resources باید درون فولدری با عنوان Resources در پوشه App_Data قرار داشته باشد.
              - آخرین نسخه تولیدی از محتویات موردنیاز پروژه Resource در فولدری با عنوان Defaults در مسیر App_Data\Resources برای فراهم کردن امکان "بازگرداندن به تنظیمات اولیه" وجود داشته باشد.
              برای فراهم کردن این موارد بهترین راه حل استفاده از تنظیمات Post-build event command line است. اطلاعات بیشتر درباره Build Eventها در اینجا.

              برای اینکار من از دستور xcopy استفاده کردم که نسخه توسعه یافته دستور copy است. دستورات استفاده شده در این قسمت عبارتند از:
              xcopy $(ProjectDir)*.* $(SolutionDir)MvcApplication1\App_Data\Resources /e /y /i /exclude:$(ProjectDir)excludes.txt
              xcopy $(ProjectDir)*.* $(SolutionDir)MvcApplication1\App_Data\Resources\Defaults /e /y /i /exclude:$(ProjectDir)excludes.txt
              xcopy $(ProjectDir)$(OutDir)*.* $(SolutionDir)MvcApplication1\App_Data\Resources\Defaults\bin /e /y /i 
              در دستورات فوق آرگومان e/ برای کپی تمام فولدرها و زیرفولدرها، y/ برای تایید تمام کانفیرم ها، و i/ برای ایجاد خودکار فولدرهای موردنیاز استفاده میشود. آرگومان exclude/ نیز همانطور که از نامش پیداست برای خارج کردن فایلها و فولدرهای موردنظر از لیست کپی استفاده میشود. این آرگومان مسیر یک فایل متنی حاوی لیست این فایلها را دریافت میکند. در تصویر زیر یک نمونه از این فایل و مسیر و محتوای مناسب آن را مشاهده میکنید:

              با استفاده از این فایل excludes.txt فولدرهای bin و obj و نیز فایلهای با پسوند user. و vspscc. (مربوط به TFS) و نیز خود فایل excludes.txt از لیست کپی دستور xcopy حذف میشوند و بنابراین کپی نمیشوند. درصورت نیاز میتوانید گزینه‌های دیگری نیز به این فایل اضافه کنید.
              همانطور که در اینجا اشاره شده است، در تنظیمات Post-build event command line یکسری متغیرهای ازپیش تعریف شده (Macro) وجود دارند که از برخی از آنها در دستوارت فوق استفاده شده است:
              (ProjectDir)$ : مسیر کامل و مطلق پروژه جاری به همراه یک کاراکتر \ در انتها
              (SolutionDir)$ : مسیر کامل و مطلق سولوشن به همراه یک کاراکتر \ در انتها
              (OutDir)$ : مسیر نسبی فولدر Output پروژه جاری به همراه یک کاراکتر \ در انتها

              نکته: این دستورات باید در Post-Build Event پروژه Resources افزوده شوند.

              با استفاده از این تنظیمات مطمئن میشویم که پس از هر Build آخرین نسخه از فایلهای موردنیاز در مسیرهای تعیین شده کپی میشوند. درنهایت با استفاده از کلاس ResXResourceManager که در زیر آورده شده است، کل عملیات را ساماندهی میکنیم:
              public class ResXResourceManager
              {
                private static readonly object Lock = new object();
                public string ResourcesPath { get; private set; }
                public ResXResourceManager(string resourcesPath)
                {
                  ResourcesPath = resourcesPath;
                }
                public IEnumerable<ResXResourceEntry> GetAllResources(string resourceCategory)
                {
                  var resourceFilePath = GetResourceFilePath(resourceCategory);
                  return Read(resourceFilePath);
                }
                public void AddOrUpdateResource(ResXResourceEntry resource, string resourceCategory)
                {
                  var resourceFilePath = GetResourceFilePath(resourceCategory);
                  AddOrUpdate(resource, resourceFilePath);
                }
                public void DeleteResource(string key, string resourceCategory)
                {
                  var resourceFilePath = GetResourceFilePath(resourceCategory);
                  Remove(key, resourceFilePath);
                }
                private string GetResourceFilePath(string resourceCategory)
                {
                  var extension = Thread.CurrentThread.CurrentUICulture.TwoLetterISOLanguageName == "en" ? ".resx" : ".fa.resx";
                  var resourceFilePath = Path.Combine(ResourcesPath, resourceCategory.Replace(".", "\\") + extension);
                  return resourceFilePath;
                }
                private static void AddOrUpdate(ResXResourceEntry resource, string filePath)
                {
                  var list = Read(filePath);
                  var entry = list.SingleOrDefault(l => l.Key == resource.Key);
                  if (entry == null)
                  {
                    list.Add(resource);
                  }
                  else
                  {
                    entry.Value = resource.Value;
                  }
                  Write(list, filePath);
                }
                private static void Remove(string key, string filePath)
                {
                  var list = Read(filePath);
                  list.RemoveAll(l => l.Key == key); 
                  Write(list, filePath);
                }
                private static List<ResXResourceEntry> Read(string filePath)
                {
                  lock (Lock)
                  {
                    using (var reader = new ResXResourceReader(filePath))
                    {
                      var list = reader.Cast<object>().Cast<DictionaryEntry>().ToList();
                      return list.Select(l => new ResXResourceEntry(l)).ToList();
                    }
                  }
                }
                private static void Write(IEnumerable<ResXResourceEntry> resources, string filePath)
                {
                  lock (Lock)
                  {
                    using (var writer = new ResXResourceWriter(filePath))
                    {
                      foreach (var resource in resources)
                      {
                        writer.AddResource(resource.Key, resource.Value);
                      }
                    }
                  }
                }
              }
              در این کلاس تغییراتی در متدهای معرفی شده در قسمتهای بالا برای مدیریت دسترسی همزمان با استفاده از بلاک lock ایجاد شده است.
              با استفاده از کلاس BuildManager عملیات تولید کتابخانه‌ها مدیریت میشود. (در مورد نحوه استفاده از MSBuild در اینجا توضیحات کافی آورده شده است):
              public class BuildManager
              {
                public string ProjectPath { get; private set; }
                public BuildManager(string projectPath)
                {
                  ProjectPath = projectPath;
                }
                public void Build()
                {
                  var regKey = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\MSBuild\ToolsVersions\4.0");
                  if (regKey == null) return;
                  var msBuildExeFilePath = Path.Combine(regKey.GetValue("MSBuildToolsPath").ToString(), "MSBuild.exe");
                  var startInfo = new ProcessStartInfo
                  {
                    FileName = msBuildExeFilePath,
                    Arguments = ProjectPath,
                    WindowStyle = ProcessWindowStyle.Hidden
                  };
                  var process = Process.Start(startInfo);
                  process.WaitForExit();
                }
              }
              درنهایت مثلا با استفاده از کلاس ResXResourceFileManager مدیریت فایلهای این کتابخانه‌ها صورت میپذیرد:
              public class ResXResourceFileManager
              {
                public static readonly string BinPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().GetName().CodeBase.Replace("file:///", ""));
                public static readonly string ResourcesPath = Path.Combine(BinPath, @"..\App_Data\Resources");
                public static readonly string ResourceProjectPath = Path.Combine(ResourcesPath, "Resources.csproj");
                public static readonly string DefaultsPath = Path.Combine(ResourcesPath, "Defaults");
                public static void CopyDlls()
                {
                  File.Copy(Path.Combine(ResourcesPath, @"bin\debug\Resources.dll"), Path.Combine(BinPath, "Resources.dll"), true);
                  File.Copy(Path.Combine(ResourcesPath, @"bin\debug\fa\Resources.resources.dll"), Path.Combine(BinPath, @"fa\Resources.resources.dll"), true);
                  Directory.Delete(Path.Combine(ResourcesPath, "bin"), true);
                  Directory.Delete(Path.Combine(ResourcesPath, "obj"), true);
                }
                public static void RestoreAll()
                {
                  RestoreDlls();
                  RestoreResourceFiles();
                }
                public static void RestoreDlls()
                {
                  File.Copy(Path.Combine(DefaultsPath, @"bin\Resources.dll"), Path.Combine(BinPath, "Resources.dll"), true);
                  File.Copy(Path.Combine(DefaultsPath, @"bin\fa\Resources.resources.dll"), Path.Combine(BinPath, @"fa\Resources.resources.dll"), true);
                }
                public static void RestoreResourceFiles(string resourceCategory)
                {
                  RestoreFile(resourceCategory.Replace(".", "\\"));
                }
                public static void RestoreResourceFiles()
                {
                  RestoreFile(@"Global\Configs");
                  RestoreFile(@"Global\Exceptions");
                  RestoreFile(@"Global\Paths");
                  RestoreFile(@"Global\Texts");
              
                  RestoreFile(@"ViewModels\Employees");
                  RestoreFile(@"ViewModels\LogOn");
                  RestoreFile(@"ViewModels\Settings");
              
                  RestoreFile(@"Views\Employees");
                  RestoreFile(@"Views\LogOn");
                  RestoreFile(@"Views\Settings");
                }
              
                private static void RestoreFile(string subPath)
                {
                  File.Copy(Path.Combine(DefaultsPath, subPath + ".resx"), Path.Combine(ResourcesPath, subPath + ".resx"), true);
                  File.Copy(Path.Combine(DefaultsPath, subPath + ".fa.resx"), Path.Combine(ResourcesPath, subPath + ".fa.resx"), true);
                }
              }
              در این کلاس از مفهومی با عنوان resourceCategory برای استفاده راحتتر در ویوها استفاده شده است که بیانگر فضای نام نسبی فایلهای Resource و کلاسهای متناظر با آنهاست که براساس استانداردها باید برطبق مسیر فیزیکی آنها در پروژه باشد مثل Global.Texts یا Views.LogOn. همچنین در متد RestoreResourceFiles نمونه هایی از مسیرهای این فایلها آورده شده است.
              پس از اجرای متد Build از کلاس BuildManager، یعنی پس از build پروژه Resource در زمان اجرا، باید ابتدا فایلهای تولیدی به مسیرهای مربوطه در فولدر bin برنامه کپی شده سپس فولدرهای تولیدشده توسط msbuild، حذف شوند. این کار در متد CopyDlls از کلاسResXResourceFileManager انجام میشود. هرچند در این قسمت فرض شده است که فایل csprj. موجود برای حالت debug تنظیم شده است.
              نکته: دقت کنید که در این قسمت بلافاصله پس از کپی فایلها در مقصد با توجه به توضیحات ابتدای این مطلب سایت Restart خواهد شد که یکی از ضعفهای عمده این روش به شمار میرود.
              سایر متدهای موجود نیز برای برگرداندن تنظیمات اولیه بکار میروند. در این متدها از محتویات فولدر Defaults استفاده میشود.
              نکته: درصورت ساخت دوباره اسمبلی و یا بازگرداندن اسمبلی‌های اولیه، از آنجاکه وب‌سایت Restart خواهد شد، بنابراین بهتر است تا صفحه جاری بلافاصله پس از اتمام عملیات،دوباره بارگذاری شود. مثلا اگر از ajax برای اعمال این دستورات استفاده شده باشد میتوان با استفاده از کدی مشابه زیر در پایان فرایند صفحه را دوباره بارگذاری کرد:
              window.location.reload();

              در قسمت بعدی راه حل بهتری با استفاده از فراهم کردن پرووایدر سفارشی برای مدیریت فایلهای Resource ارائه میشود.