مسیرراه‌ها
ASP.NET MVC
              اشتراک‌ها
              دریافت کتاب Pro ASP.NET MVC 5 Platform
              قدرت ASP.NET MVC 5 ریشه در پلت فرم زیرین آن یعنی ASP.NET دارد. برای اینکه بتوانید برنامه‌ی ASP.NET MVC خود را به بهترین حالتی که می‌تواند باشد تبدیل کنید، شما احتیاج دارید که به طور کامل با امکانات و ویژگی‌های پلتفرم مورد استفاده آشنا شوید و یاد بگیرید که چگونه می‌توانید برنامه‌های کارا و موثر با فریم ورک MVC بنویسید.
              در این کتاب شما فرا خواهید گرفت که چگونه بیشترین کار را از پلت فرم ASP.NET کشید.این کتاب برای افرادی مناسب است که می‌خواهند از ASP.NET MVC 5 در کارهای حرفه ای خود استفاده کنند و پیش زمینه ای در فریم ورک ASP.NET MVC دارند. 
              دریافت کتاب Pro ASP.NET MVC 5 Platform
              مطالب
              استایل دهی به ستون های header در WebGrid

              Webgrid  گرید توکار asp.net mvc 3 است که در سری آموزش‌های mvc جناب نصیری به خوبی بررسی شده است . WebGrid از طریق مجموعه ای از خواص امکان استایل دهی به ستون‌ها و ردیف‌ها را به توسعه دهنده می‌دهد . اما در این بخش مشکلی وجود دارد که در ادامه به آن خواهم پرداخت . کدهای زیر را در نظر بگیرید

              مدل‌ها :

                  public class Customer
                  {
                      public int Id { get; set; }
              
                      public string Name { get; set; }
              
                      public string Email { get; set; }
              
                      public string Website { get; set; }
              
                      public string Phone { get; set; }
                  }
              
                  public class Customers
                  {
                      public IList<Customer> GetList()
                      {
                          return new List<Customer>()
                          {
                              new Customer()
                              {
                                   Id=1,
                                   Name="mohsen.d",
                                   Email="email@domain.com",
                                   Website="domain.com",
                                   Phone="213214321"
                              }
                          };
                      }
              
                      public IList<Customer> GetEmptyList()
                      {
                          return new List<Customer>();
                      }
                  }
              و کنترلر :
                  public class HomeController : Controller
                  {
                      public ActionResult List()
                      {
                          var model = new Customers().GetList();
                          return View(model);
                      }
              
                      public ActionResult EmptyList()
                      {
                          var model = new Customers().GetEmptyList();
                          return View("list", model);
                      }
                  }

              تابع کمکی برای ایجاد گرید :

              @helper GenerateList(IEnumerable<object> items, List<WebGridColumn> columns)
              {
                  var grid = new WebGrid(items);
                  
                  <div>  
                      @grid.GetHtml(
                                      tableStyle: "list",
                                      headerStyle: "list-header",
                                      footerStyle: "list-footer",
                                      alternatingRowStyle: "list-alt",
                                      selectedRowStyle: "list-selected",
                                      rowStyle: "list-row",
                                      htmlAttributes: new { id = "listItems" },
                                      mode: WebGridPagerModes.All,
                                      columns: columns
                  )
              
                  </div>
              }
              view :
              @model IEnumerable<WebGridHeaderStyle.Models.Customer>
              
              @{
                  ViewBag.Title = "List";
              }
              
              <h2>List</h2>
              
              @_List.GenerateList(
                  Model,
                  new List<WebGridColumn>()
                  {
                      new WebGridColumn(){
                       ColumnName="Id",
                       Header="Id",
                       Style="list-small-field"
                      },
                      new WebGridColumn(){
                       ColumnName="Name",
                       Header="Name",
                       Style="list-long-field"
                      },
                      new WebGridColumn(){
                       ColumnName="Email",
                       Header="Email",
                       Style="list-mid-field"
                      },
                      new WebGridColumn(){
                       ColumnName="Website",
                       Header="Website",
                       Style="list-mid-field"
                      },
                      new WebGridColumn(){
                       ColumnName="Phone",
                       Header="Phone",
                       Style="list-mid-field"
                      }
                  }
              )
              ابتدا به مسیر Home/List می‌رویم

              خوب چندان بد نیست . با استفاده از استایل‌های تعریف شده برای فیلدها و ردیف‌ها ، لیست ساختار مناسبی دارد . اما حالا به Home/EmptyList  می رویم :

              همانطور که می‌بینید استایل هایی که برای هر ستون تعریف کرده بودیم اعمال نشده اند. مشکل هم همین جاست . WebGrid استایل تعریف شده را تنها به ستون‌های درون tbody اعمال میکند و thead از این تنظیمات بی نصیب می‌ماند ( WebGrid از table برای ساختن لیست استفاده می‌کند ) و در زمانی که رکوردی وجود نداشته باشد فرمت طراحی شده اعمال نمی‌شود .

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

              در زمان ساختن گرید ، استایل‌های تعریف شده را در یک فیلد hidden ذخیره و سپس با استفاده از jquery این استایل‌ها را به ستون‌های header اعمال می‌کنیم .

              تابع ساختن فیلد hidden :

              @helper SetHeaderColumnsStyle(IEnumerable<WebGridColumn> columns)
              {
                  var styles = new List<string>();
                  
                  foreach(var col in columns)
                  {
                      styles.Add(col.Style);
                  }
                  
                  <input id="styles" type="hidden" value="@string.Join("#",styles)" />
              }
              این تابع را در تابع کمکی ساخت گرید فراخوانی می‌کنیم :
              @SetHeaderColumnsStyle(columns)
              و در view کد javascript  زیر را اضافه می‌کنیم :
              <script>
              
                  $(document).ready(function () {
              
                      var styles = $("#styles").attr("value").split('#');
              
                      var $cols = $("#listItems th");
              
                      $cols.each(function (i) {
                          $(this).addClass(styles[i]);
                      });
                  });
              </script>
                حال اگر صفحه را بارگذاری کنید با اینکه رکوردی وجود ندارد اما ساختار گرید به همان شکل تعریف شده باقی مانده است .

                پروژه نمونه را می‌توانید از اینجا دانلود کنید .
              اشتراک‌ها
              دریافت کتاب Pro ASP.NET MVC 5
              فریم ورک ASP.NET MVC 5، آخرین تحول پلتفرم وب Microsoft’s ASP.NET است. این فریم ورک، یک مدل برنامه نویسی  با بهره بری بالا  ارائه می‌دهد از جمله: معماری کد تمیز تر، توسعه مبتنی بر تست (Test-Driven Development)،  توسعه پذیری قدرتمند؛ که با تمام مزایای ASP.NET ترکیب شده است، فراهم می‌کند. 
              دریافت کتاب Pro ASP.NET MVC 5
              مطالب
              ارتقاء به 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"
                }
              }
              مطالب
              Attribute Routing در ASP.NET MVC 5
              Routing مکانیزم مسیریابی ASP.NET MVC است، که یک URI را به یک اکشن متد نگاشت می‌کند. MVC 5 نوع جدیدی از مسیر یابی را پشتیبانی میکند که Attribute Routing یا مسیریابی نشانه ای نام دارد. همانطور که از نامش پیداست، مسیریابی نشانه ای از Attribute‌ها برای این امر استفاده میکند. این روش به شما کنترل بیشتری روی URI‌های اپلیکیشن تان می‌دهد.
              مدل قبلی مسیریابی (conventional-routing) هنوز کاملا پشتیبانی می‌شود. در واقع می‌توانید هر دو تکنیک را بعنوان مکمل یکدیگر در یک پروژه استفاده کنید.
              در این پست قابلیت‌ها و گزینه‌های اساسی مسیریابی نشانه ای را بررسی میکنیم.
              • چرا مسیریابی نشانه ای؟
              • فعال سازی مسیریابی نشانه ای
              • پارامتر‌های اختیاری URI و مقادیر پیش فرض
              • پیشوند مسیر ها
              • مسیر پیش فرض
              • محدودیت‌های مسیر ها
                  • محدودیت‌های سفارشی
              • نام مسیر ها
              • ناحیه‌ها (Areas)


              چرا مسیریابی نشانه ای

              برای مثال یک وب سایت تجارت آنلاین بهینه شده اجتماعی، می‌تواند مسیرهایی مانند لیست زیر داشته باشد:
              • {productId:int}/{productTitle}
              نگاشت می‌شود به: (ProductsController.Show(int id
              • {username}
              نگاشت می‌شود به: (ProfilesController.Show(string username
              • {username}/catalogs/{catalogId:int}/{catalogTitle}
              نگاشت می‌شود به: (CatalogsController.Show(string username, int catalogId
              در نسخه قبلی ASP.NET MVC، قوانین مسیریابی در فایل RouteConfig.cs تعریف می‌شدند، و اشاره به اکشن‌های کنترلرها به نحو زیر انجام می‌شد:
              routes.MapRoute(
                  name: "ProductPage",
                  url: "{productId}/{productTitle}",
                  defaults: new { controller = "Products", action = "Show" },
                  constraints: new { productId = "\\d+" }
              );
              هنگامی که قوانین مسیریابی در کنار اکشن متدها تعریف می‌شوند، یعنی در یک فایل سورس و نه در یک کلاس پیکربندی خارجی، درک و فهم نگاشت URI‌ها به اکشن‌ها واضح‌تر و راحت می‌شود. تعریف مسیر قبلی، می‌تواند توسط یک attribute ساده بدین صورت نگاشت شود:
              [Route("{productId:int}/{productTitle}")]
              public ActionResult Show(int productId) { ... }

              فعال سازی Attribute Routing

              برای فعال سازی مسیریابی نشانه ای، متد MapMvcAttributeRoutes را هنگام پیکربندی فراخوانی کنید.
              public class RouteConfig
              {
                  public static void RegisterRoutes(RouteCollection routes)
                  {
                      routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
               
                      routes.MapMvcAttributeRoutes();
                  }
              }
              همچنین می‌توانید مدل قبلی مسیریابی را با تکنیک جدید تلفیق کنید.
              public static void RegisterRoutes(RouteCollection routes)
              {
                  routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
               
                  routes.MapMvcAttributeRoutes();
               
                  routes.MapRoute(
                      name: "Default",
                      url: "{controller}/{action}/{id}",
                      defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
                  );
              }

              پارامترهای اختیاری URI و مقادیر پیش فرض

              می توانید با اضافه کردن یک علامت سوال به پارامترهای مسیریابی، آنها را optional یا اختیاری کنید. برای تعیین مقدار پیش فرض هم از فرمت parameter=value استفاده می‌کنید.
              public class BooksController : Controller
              {
                  // eg: /books
                  // eg: /books/1430210079
                  [Route("books/{isbn?}")]
                  public ActionResult View(string isbn)
                  {
                      if (!String.IsNullOrEmpty(isbn))
                      {
                          return View("OneBook", GetBook(isbn));
                      }
                      return View("AllBooks", GetBooks());
                  }
               
                  // eg: /books/lang
                  // eg: /books/lang/en
                  // eg: /books/lang/he
                  [Route("books/lang/{lang=en}")]
                  public ActionResult ViewByLanguage(string lang)
                  {
                      return View("OneBook", GetBooksByLanguage(lang));
                  }
              }
              در این مثال، هر دو مسیر books/ و books/1430210079/ به اکشن متد "View" نگاشت می‌شوند، مسیر اول تمام کتاب‌ها را لیست میکند، و مسیر دوم جزئیات کتابی مشخص را لیست می‌کند. هر دو مسیر books/lang/ و books/lang/en/ به یک شکل نگاشت می‌شوند، چرا که مقدار پیش فرض این پارامتر en تعریف شده.



              پیشوند مسیرها (Route Prefixes)

              برخی اوقات، تمام مسیرها در یک کنترلر با یک پیشوند شروع می‌شوند. بعنوان مثال:
              public class ReviewsController : Controller
              {
                  // eg: /reviews
                  [Route("reviews")]
                  public ActionResult Index() { ... }
                  // eg: /reviews/5
                  [Route("reviews/{reviewId}")]
                  public ActionResult Show(int reviewId) { ... }
                  // eg: /reviews/5/edit
                  [Route("reviews/{reviewId}/edit")]
                  public ActionResult Edit(int reviewId) { ... }
              }
              همچنین می‌توانید با استفاده از خاصیت [RoutePrefix] یک پیشوند عمومی برای کل کنترلر تعریف کنید:
              [RoutePrefix("reviews")]
              public class ReviewsController : Controller
              {
                  // eg.: /reviews
                  [Route]
                  public ActionResult Index() { ... }
                  // eg.: /reviews/5
                  [Route("{reviewId}")]
                  public ActionResult Show(int reviewId) { ... }
                  // eg.: /reviews/5/edit
                  [Route("{reviewId}/edit")]
                  public ActionResult Edit(int reviewId) { ... }
              }
              در صورت لزوم، می‌توانید برای بازنویسی (override) پیشوند مسیرها از کاراکتر ~ استفاده کنید:
              [RoutePrefix("reviews")]
              public class ReviewsController : Controller
              {
                  // eg.: /spotlight-review
                  [Route("~/spotlight-review")]
                  public ActionResult ShowSpotlight() { ... }
               
                  ...
              }

              مسیر پیش فرض

              می توانید خاصیت [Route] را روی کنترلر اعمال کنید، تا اکشن متد را بعنوان یک پارامتر بگیرید. این مسیر سپس روی تمام اکشن متدهای این کنترلر اعمال می‌شود، مگر آنکه یک [Route] بخصوص روی اکشن‌ها تعریف شده باشد.
              [RoutePrefix("promotions")]
              [Route("{action=index}")]
              public class ReviewsController : Controller
              {
                  // eg.: /promotions
                  public ActionResult Index() { ... }
               
                  // eg.: /promotions/archive
                  public ActionResult Archive() { ... }
               
                  // eg.: /promotions/new
                  public ActionResult New() { ... }
               
                  // eg.: /promotions/edit/5
                  [Route("edit/{promoId:int}")]
                  public ActionResult Edit(int promoId) { ... }
              }

              محدودیت‌های مسیر ها

              با استفاده از Route Constraints می‌توانید نحوه جفت شدن پارامتر‌ها در قالب مسیریابی را محدود و کنترل کنید. فرمت کلی {parameter:constraint} است. بعنوان مثال:
              // eg: /users/5
              [Route("users/{id:int}"]
              public ActionResult GetUserById(int id) { ... }
               
              // eg: users/ken
              [Route("users/{name}"]
              public ActionResult GetUserByName(string name) { ... }
              در اینجا، مسیر اول تنها در صورتی انتخاب می‌شود که قسمت id در URI یک مقدار integer باشد. در غیر اینصورت مسیر دوم انتخاب خواهد شد.
              جدول زیر constraint‌ها یا محدودیت هایی که پشتیبانی می‌شوند را لیست می‌کند.
               مثال  توضیحات  محدودیت
               {x:alpha}  کاراکترهای الفبای لاتین را تطبیق (match) می‌دهد (a-z, A-Z).  alpha
               {x:bool}  یک مقدار منطقی را تطبیق می‌دهد.  bool
               {x:datetime}  یک مقدار DateTime را تطبیق می‌دهد.  datetime
               {x:decimal}  یک مقدار پولی را تطبیق می‌دهد.  decimal
               {x:double}  یک مقدار اعشاری 64 بیتی را تطبیق می‌دهد.  double
               {x:float}  یک مقدار اعشاری 32 بیتی را تطبیق می‌دهد.  float
               {x:guid}  یک مقدار GUID را تطبیق می‌دهد.  guid
               {x:int}  یک مقدار 32 بیتی integer را تطبیق می‌دهد.  int
               {(x:length(6}
              {(x:length(1,20}
               رشته ای با طول تعیین شده را تطبیق می‌دهد.  length
               {x:long}  یک مقدار 64 بیتی integer را تطبیق می‌دهد.  long
               {(x:max(10}  یک مقدار integer با حداکثر مجاز را تطبیق می‌دهد.  max
               {(x:maxlength(10}  رشته ای با حداکثر طول تعیین شده را تطبیق می‌دهد.  maxlength
               {(x:min(10}  مقداری integer با حداقل مقدار تعیین شده را تطبیق می‌دهد.  min
               {(x:minlength(10}  رشته ای با حداقل طول تعیین شده را تطبیق می‌دهد.  minlength
               {(x:range(10,50}  مقداری integer در بازه تعریف شده را تطبیق می‌دهد.  range
               {(${x:regex(^\d{3}-\d{3}-\d{4}  یک عبارت با قاعده را تطبیق می‌دهد.  regex

              توجه کنید که بعضی از constraint ها، مانند "min" آرگومان‌ها را در پرانتز دریافت می‌کنند.
              می توانید محدودیت‌های متعددی روی یک پارامتر تعریف کنید، که باید با دونقطه جدا شوند. بعنوان مثال:
              // eg: /users/5
              // but not /users/10000000000 because it is larger than int.MaxValue,
              // and not /users/0 because of the min(1) constraint.
              [Route("users/{id:int:min(1)}")]
              public ActionResult GetUserById(int id) { ... }
              مشخص کردن اختیاری بودن پارامتر ها، باید در آخر لیست constraints تعریف شود:
              // eg: /greetings/bye
              // and /greetings because of the Optional modifier,
              // but not /greetings/see-you-tomorrow because of the maxlength(3) constraint.
              [Route("greetings/{message:maxlength(3)?}")]
              public ActionResult Greet(string message) { ... }

              محدودیت‌های سفارشی

              با پیاده سازی قرارداد IRouteConstraint می‌توانید محدودیت‌های سفارشی بسازید. بعنوان مثال، constraint زیر یک پارامتر را به لیستی از مقادیر قابل قبول محدود می‌کند:
              public class ValuesConstraint : IRouteConstraint
              {
                  private readonly string[] validOptions;
                  public ValuesConstraint(string options)
                  {
                      validOptions = options.Split('|');
                  }
               
                  public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
                  {
                      object value;
                      if (values.TryGetValue(parameterName, out value) && value != null)
                      {
                          return validOptions.Contains(value.ToString(), StringComparer.OrdinalIgnoreCase);
                      }
                      return false;
                  }
              }
              قطعه کد زیر نحوه رجیستر کردن این constraint را نشان می‌دهد:
              public class RouteConfig
              {
                  public static void RegisterRoutes(RouteCollection routes)
                  {
                      routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
               
                      var constraintsResolver = new DefaultInlineConstraintResolver();
               
                      constraintsResolver.ConstraintMap.Add("values", typeof(ValuesConstraint));
               
                      routes.MapMvcAttributeRoutes(constraintsResolver);
                  }
              }
              حالا می‌توانید این محدودیت سفارشی را روی مسیرها اعمال کنید:
              public class TemperatureController : Controller
              {
                  // eg: temp/celsius and /temp/fahrenheit but not /temp/kelvin
                  [Route("temp/{scale:values(celsius|fahrenheit)}")]
                  public ActionResult Show(string scale)
                  {
                      return Content("scale is " + scale);
                  }
              }

              نام مسیر ها

              می توانید به مسیرها یک نام اختصاص دهید، با این کار تولید URI‌ها هم راحت‌تر می‌شوند. بعنوان مثال برای مسیر زیر:
              [Route("menu", Name = "mainmenu")]
              public ActionResult MainMenu() { ... }
              می‌توانید لینکی با استفاده از Url.RouteUrl تولید کنید:
              <a href="@Url.RouteUrl("mainmenu")">Main menu</a>

              ناحیه‌ها (Areas)

              برای مشخص کردن ناحیه ای که کنترلر به آن تعلق دارد می‌توانید از خاصیت [RouteArea] استفاده کنید. هنگام استفاده از این خاصیت، می‌توانید با خیال راحت کلاس AreaRegistration را از ناحیه مورد نظر حذف کنید.
              [RouteArea("Admin")]
              [RoutePrefix("menu")]
              [Route("{action}")]
              public class MenuController : Controller
              {
                  // eg: /admin/menu/login
                  public ActionResult Login() { ... }
               
                  // eg: /admin/menu/show-options
                  [Route("show-options")]
                  public ActionResult Options() { ... }
               
                  // eg: /stats
                  [Route("~/stats")]
                  public ActionResult Stats() { ... }
              }
              با این کنترلر، فراخوانی تولید لینک زیر، رشته "Admin/menu/show-options/" را بدست میدهد:
              Url.Action("Options", "Menu", new { Area = "Admin" })
              به منظور تعریف یک پیشوند سفارشی برای یک ناحیه، که با نام خود ناحیه مورد نظر متفاوت است می‌توانید از پارامتر AreaPrefix استفاده کنید. بعنوان مثال:
              [RouteArea("BackOffice", AreaPrefix = "back-office")]
              اگر از ناحیه‌ها هم بصورت مسیریابی نشانه ای، و هم بصورت متداول (که با کلاس‌های AreaRegistration پیکربندی می‌شوند) استفاده می‌کنید باید مطمئن شوید که رجیستر کردن نواحی اپلیکیشن پس از مسیریابی نشانه ای پیکربندی می‌شود. به هر حال رجیستر کردن ناحیه‌ها پیش از تنظیم مسیرها بصورت متداول باید صورت گیرد. دلیل آن هم مشخص است، برای اینکه درخواست‌های ورودی بدرستی با مسیرهای تعریف شده تطبیق داده شوند، باید ابتدا attribute routes، سپس area registration و در آخر default route رجیستر شوند. بعنوان مثال:
              public static void RegisterRoutes(RouteCollection routes)
              {
                  routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
               
                  routes.MapMvcAttributeRoutes();
               
                  AreaRegistration.RegisterAllAreas();
               
                  routes.MapRoute(
                      name: "Default",
                      url: "{controller}/{action}/{id}",
                      defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
                  );
              }

              مطالب
              بررسی تغییرات ASP.NET MVC 5 beta1
              همانطور که می‌دانید، مایکروسافت در کنفرانس Build 2013  که چند روز پیش برگزار شد،  Visual Studio 2013 Preview را به همراه ASP.NET MVC 5 beta1  و Entity Framework 6 beta 1 و تعدادی محصول دیگر، معرفی کرد.
              در طی این مقاله قصد دارم تجربیات کار خودم با نسخه‌ی پیش نمایش MVC 5 را به اشتراک بزارم و نه صرفا بررسی یک change-log ساده.
              برای کار با MVC 5 شما ابتدا باید یکی از نسخه‌های Visual Studio 2013 را نصب کنید. من در مقاله از Visual Studio Express 2013 Preview For Web استفاده می‌کنم.
              ابتدا New Project را زده تا یک پروژه جدید را آغاز کنیم. از قسمت Templates، بخش Web  را که انتخاب کنید، اولین تغییر را مشاهده خواهید کرد.بله! دیگر خبر از چند ASP.NET نیست.حداقل در دسته بندی تبدیل به یک ASP.NET واحد شده اند.

              با انتخاب ok باز نیز با قالب جدیدی به شکل زیر برای انتخاب پروژه مواجه می‌شوید. 

              اینجا همه چیز تکراری است به غیر از گزینه  Configure Authentication. 

              همه‌ی گزینه‌ها تکراری اند به غیر از گزینه Individual User Accounts. البته این همان FormsAuthentication قبلی است. نکته قابل توجه، یکپارچی آن با سرویس‌های اجتماعی و شبکه‌های سرویس دهنده است. البته در نسخه‌ی قبلی نیز این سیستم وجود داشت، ولی این دفعه  با ASP.NET Identity یک پارچه است که در ادامه بیشتر آن را خواهید دید.

              البته گویا حالت دیگری به نام Organizational Accounts  نیز وجود دارد که گویا  برای فعال سازی،باید یک بسته‌ی به روز رسانی دریافت می‌کردم، که من نکردم.(اینترنت حجمی و شبانه دانلود کردن...)

              این حالت که در شکل زیر مشخص است، امکان یکپارچگی احراز هویت با Active Directory  در windows server و azure را دارد.

              پس از ایجاد پروژه یک نگاهی به Solution Explorer می‌اندازیم. 

              همان طور که می‌بینید ساختار اصلی با نسخه‌های قبلی هیچ تفاوتی نکرده و تنها کتاب خانه ای که اینجا خودنمایی می‌کند و به چشم آشنا نیست، twitter bootstrap است!

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

              با اجرا کردن پروژه bootstrap و responsive بودن آن، خودنمایی می‌کنند.

              اگر نگاهی به کنترلر Account بیندازیم، با موارد جالبی روبرو می‌شویم. 

              به لطف سیستم Identity جدید، Entity Framework 6 و .NET 4.5 ، می‌بینیم که تا حد امکان، عملیات به صورت آسنکرون(نامتقارن) انجام شده اند که برای برنامه‌های scalable بسیار مفید و ضروری به نظر می‌رسد.
              اگر نگاهی به reference‌های پروژه هم بیندازیم، حضور بسیاری از کتاب خانه‌های نام آشنا را به صورت پیش فرض، شاهد هستیم.

               Entity Framework نسخه‌ی 6 beta1 به صورت پیش فرض در پروژه وجود دارد. خوشبختانه دیگر خبری هم از System.Data.Entity  نیست. همچنین حضور پررنگ Owin و ASP.NET Identity را متوجه خواهید شد.
              خب قبلا اگر قصد افزودن کنترلر جدیدی به بروژه داشتید، به راحتی در هر جای پروژه گزینه ای به نام AddController را می‌زدید. اما اینجا سناریو کمی متفاوت است.
               

              همه چیز گویا با Scaffolding یکپارچه شده. به گفته‌ی تیم ASP.NET، Scaffold کاملا از نو نوشته شده، در بررسی‌های اولیه من، کدهای تولیدی چندان تفاوتی با نسخه‌ی قبل نداشت.احتمالا تغییرات در جای دیگری است. 
              حال اگر بخواهیم برای Controller ایجاد شده یه View ایجاد کنیم باید طبق روال سابق از شرتکات ctrl+m, ctrl+v استفاده کنیم. اما... 

              بله، لااقل در این IDE در اقدامی ناجوانمردانه! این گزینه حذف شده است. گویا باید وارد فولدر Views شده و به صورت دستی فولدر جدیدی ایجاد و از گزینه‌ی Scaffold برای افزودن View جدید اقدام کرد. 

              همین طور که می‌بینید، Intellisense ویژوال استادیو به صورت توکار، از AngularJs پشتیبانی می‌کند.
              نتیجه گیری:
              گویا مایکروسافت نیز به این نتیجه رسیده که ASP.NET MVC در نسخه‌ی سوم خود، کاملا پخته و به بلوغ رسیده است و پس از آن باید فقط آن را بهینه کرده و تغییرات اساسی در آن انجام ندهد .تیم ASP.NET تنها حواسش منعطف به همگام شدن با تکنولوژی‌های روز Web است و این را با پشتیبانی پیش فرض از bootstrap و Angularjs شاهد هستیم. گویا خط مشی تیم توسعه دهنده نیز این گونه است. از جمله تغییرات خوب بحث Identity هست که کاملا به EF Code First یک پارچه هست و دیگر مشکلات کار با سرویس Memberships وجود ندارد. در کل شما یک سری اینترفیس پیاده سازی می‌کنید و بقیه مسائل توسط این کتابخانه‌ی نو ظهور مدیریت می‌شود و فراموش نکنیم که این کتابخانه با OAuth یکپارچه است.(دریافت اطلاعات بیشتر )
              طبق change-log رسمی، تغییر آنچنانی در MVC رخ نداده است. فقط یک سری آپدیت و بهینه سازی و همگام سازی با تکنولوژی‌های جدید.
              نظر شما در مورد این تغییرات چیست؟

              مطالب
              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 ارائه میشود.