HTTP Error 500.21 - Internal Server Error Handler "aspNetCore" has a bad module "AspNetCoreModule" in its module list
پیشنیازهای کار با IIS Express توسط Rider
- نصب IIS Express به صورت جداگانه
- نصب بستهی هاستینگ ASP.NET Core برای IIS
این مورد برای اضافه شدن AspNetCoreModuleV2 به IIS Express خام فوق، الزامی است.
معرفی بستهی هاستینگ ASP.NET Core به IIS Express
پس از نصب این بستهها، فایل واقع در مسیر زیر را برای یافتن واژهی AspNetCoreModule جستجو کنید (یک چنین فایلی در مسیر 64 بیتی C:\Program Files\IIS Express\config\templates\PersonalWebServer نیز وجود دارد):
%PROGRAMFILES(x86)%\IIS Express\config\templates\PersonalWebServer\applicationhost.config
%PROGRAMFILES(x86)%\IIS Express\Asp.Net Core Module\V2
- ابتدا پوشهی C:\Program Files (x86)\IIS\Asp.Net Core Module را به درون پوشهی C:\Program Files (x86)\IIS Express کپی کنید.
- سپس پوشهی C:\Program Files\IIS\Asp.Net Core Module را به درون پوشهی C:\Program Files\IIS Express کپی کنید.
در فایل C:\Program Files\IIS Express\config\templates\PersonalWebServer\applicationhost.config
<add name="AspNetCoreModule" image="C:\Program Files\IIS Express\aspnetcore.dll" /> <add name="AspNetCoreModuleV2" image="C:\Program Files\IIS Express\Asp.Net Core Module\V2\aspnetcorev2.dll" />
<add name="AspNetCoreModule" image="C:\Program Files (x86)\IIS Express\aspnetcore.dll" /> <add name="AspNetCoreModuleV2" image="C:\Program Files (x86)\IIS Express\Asp.Net Core Module\V2\aspnetcorev2.dll" />
- ذیل تگ <sectionGroup name="system.webServer">، سطر زیر را اضافه کنید:
<section name="aspNetCore" overrideModeDefault="Allow" />
- ذیل تگ <system.webServer><modules>، دو سطر زیر را اضافه کنید:
<add name="AspNetCoreModule" lockItem="true" /> <add name="AspNetCoreModuleV2" lockItem="true" />
البته برای ذخیره سازی فایلهای موجود در Program Files، باید آنها را با دسترسی ادمین باز کنید. برای مثال اگر از ++nodepad استفاده کنید، به صورت خودکار این مساله را تشخیص داده و دسترسی صحیح را درخواست میکند.
تنظیم Rider برای یافتن مسیر صحیح AspNetCoreModuleV2 نصب شده
در برنامهی Rider، از منوی File، قسمت settings آن، گزینهی Build, Execution, Deployment | IIS Express را انتخاب و سپس مسیرهای x86 و x64 را به صورت زیر تنظیم کنید:
البته دراپ داونهای این صفحه، به صورت خودکار این مسیرها را پر میکنند. فقط کافی است، مسیر صحیح را از طریق آنها انتخاب کنید.
- اکنون به ریشهی پروژهی خود مراجعه کرده و فایل idea\config\applicationhost.config. را در صورت وجود حذف کنید (البته بهتر است کل پوشهی idea. و همچنین vs. را (در صورت وجود) حذف کنید؛ هر دو را با هم. مهم!). برنامهی Rider، این فایل تنظیمات موقتی IIS Express را بر اساس دو فایل config\templates\PersonalWebServer\applicationhost.config ای که اصلاح کردیم، به صورت خودکار تولید میکند و حاوی تمام تغییرات فوق خواهد بود.
- فایل web.config واقع در ریشهی پروژه وب نیز بهتر است یک چنین محتوایی را داشته باشد:
<?xml version="1.0" encoding="utf-8"?> <configuration> <system.webServer> <handlers> <add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModuleV2" resourceType="Unspecified"/> </handlers> <aspNetCore processPath="%LAUNCHER_PATH%" arguments="%LAUNCHER_ARGS%" stdoutLogEnabled="false" stdoutLogFile=".\logs\stdout" forwardWindowsAuthToken="false"/> </system.webServer> </configuration>
تنظیمات IIS Express در Rider
تنظیمات پورت IIS Express، در فایل Properties\launchSettings.json پروژههای وب، قابل مشاهده و تغییر است. اگر نیاز به کار با HTTPS باشد، برنامهی Rider، پیام کوچکی را که در آن لینک setup certificate قرار دارد، نمایش میدهد و با کلیک بر روی آن، یک مجوز موقتی self-signed certificate تولید و نصب خواهد شد.
و یا در Rider، از منوی بالای صفحه که تنظیمات Build را نمایش میدهد، میتوان IIS Express را به عنوان اجرا کنندهی پروژه، انتخاب کرد. پس از انتخاب آن، یکبار دیگر از همان dropdown میتوان گزینهی edit configuration را انتخاب کرد تا تنظیمات مخصوص IIS Express، ظاهر شود.
EF Code First #12
پیاده سازی الگوی Context Per Request در برنامههای مبتنی بر EF Code first
در طراحی برنامههای چند لایه مبتنی بر EF مرسوم نیست که در هر کلاس و متدی که قرار است از امکانات آن استفاده کند، یکبار DbContext و کلاس مشتق شده از آن وهله سازی شوند؛ به این ترتیب امکان انجام امور مختلف در طی یک تراکنش از بین میرود. برای حل این مشکل الگویی مطرح شده است به نام Session/Context Per Request و یا به اشتراک گذاری یک Unit of work در لایههای مختلف برنامه در طی یک درخواست، که در ادامه یک پیاده سازی آنرا با هم مرور خواهیم کرد.
البته این سشن با سشن ASP.NET یکی نیست. در NHibernate معادل DbContextایی که در اینجا ملاحظه میکنید، Session نام دارد.
اهمیت بکارگیری الگوی Unit of work و به اشتراک گذاری آن در طی یک درخواست
در الگوی واحد کار یا همان DbContext در اینجا، تمام درخواستهای رسیده به آن، در صف قرار گرفته و تمام آنها در پایان کار، به بانک اطلاعاتی اعمال میشوند. برای مثال زمانیکه شیءایی را به یک وهله از DbContext اضافه/حذف میکنیم، یا در ادامه مقدار خاصیتی را تغییر میدهیم، هیچکدام از این تغییرات تا زمانیکه متد SaveChanges فراخوانی نشود، به بانک اطلاعاتی اعمال نخواهند شد. این مساله مزایای زیر را به همراه خواهد داشت:
الف) کارآیی بهتر
در اینجا از یک کانکشن باز شده، حداکثر استفاده صورت میگیرد. چندین و چند عملیات در طی یک batch به بانک اطلاعاتی اعمال میگردند؛ بجای اینکه برای اعمال هرکدام، یکبار اتصال جداگانهای به بانک اطلاعاتی باز شود.
ب) بررسی مسایل همزمانی
استفاده از یک الگوی واحد کار، امکان بررسی خودکار تمام تغییرات انجام شده بر روی یک موجودیت را در متدها و لایههای مختلف میسر کرده و به این ترتیب مسایل مرتبط با ConcurrencyMode عنوان شده در قسمتهای قبل به نحو بهتری قابل مدیریت خواهند بود.
ج) استفاده صحیح از تراکنشها
الگوی واحد کار به صورت خودکار از تراکنشها استفاده میکند. اگر در حین فراخوانی متد SaveChanges مشکلی رخ دهد، کل عملیات Rollback خواهد شد و تغییری در بانک اطلاعاتی رخ نخواهد داد. بنابراین استفاده از یک تراکنش در حین چند عملیات ناشی از لایههای مختلف برنامه، منطقیتر است تا اینکه هر کدام، در تراکنشی جدا مشغول به کار باشند.
کلاسهای مدل مثال جاری
در مثالی که در این قسمت بررسی خواهیم کرد، از کلاسهای مدل گروه محصولات کمک گرفته شده است:
using System.Collections.Generic;
namespace EF_Sample07.DomainClasses { public class Category { public int Id { get; set; } public virtual string Name { get; set; } public virtual string Title { get; set; } public virtual ICollection<Product> Products { get; set; } } }
using System.ComponentModel.DataAnnotations;
namespace EF_Sample07.DomainClasses { public class Product { public int Id { get; set; } public string Name { get; set; } public decimal Price { get; set; }
[ForeignKey("CategoryId")] public virtual Category Category { get; set; } public int CategoryId { get; set; } } }
در کلاس Product، یک خاصیت اضافی به نام CategoryId اضافه شده است که توسط ویژگی ForeignKey، به عنوان کلید خارجی جدول معرفی خواهد شد. از این خاصیت در برنامههای ASP.NET برای مقدار دهی یک کلید خارجی توسط یک DropDownList پر شده با لیست گروهها، استفاده خواهیم کرد.
پیاده سازی الگوی واحد کار
همانطور که در قسمت قبل نیز ذکر شد، DbContext در EF Code first بر اساس الگوی واحد کار تهیه شده است، اما برای به اشتراک گذاشتن آن بین لایههای مختلف برنامه نیاز است یک لایه انتزاعی را برای آن تهیه کنیم، تا بتوان آنرا به صورت خودکار توسط کتابخانههای Dependency Injection یا به اختصار DI در زمان نیاز به استفاده از آن، به کلاسهای استفاده کننده تزریق کنیم. کتابخانهی DI ایی که در این قسمت مورد استفاده قرار میگیرد، کتابخانه معروف StructureMap است. برای دریافت آن میتوانید از Nuget استفاده کنید؛ یا از صفحه اصلی آن در Github : (^).
اینترفیس پایه الگوی واحد کار ما به شرح زیر است:
using System.Data.Entity; using System;
namespace EF_Sample07.DataLayer.Context { public interface IUnitOfWork { IDbSet<TEntity> Set<TEntity>() where TEntity : class; int SaveChanges(); } }
برای استفاده اولیه آن، تنها تغییری که در برنامه حاصل میشود به نحو زیر است:
using System.Data.Entity; using EF_Sample07.DomainClasses;
namespace EF_Sample07.DataLayer.Context { public class Sample07Context : DbContext, IUnitOfWork { public DbSet<Category> Categories { set; get; } public DbSet<Product> Products { set; get; }
#region IUnitOfWork Members public new IDbSet<TEntity> Set<TEntity>() where TEntity : class { return base.Set<TEntity>(); } #endregion } }
توضیحات:
با کلاس Context در قسمتهای قبل آشنا شدهایم. در اینجا به معرفی کلاسهایی خواهیم پرداخت که در معرض دید EF Code first قرار خواهند گرفت.
DbSetها هم معرف الگوی Repository هستند. کلاس Sample07Context، معرفی الگوی واحد کار یا Unit of work برنامه است.
برای اینکه بتوانیم تعاریف کلاسهای سرویس برنامه را مستقل از تعریف کلاس Sample07Context کنیم، یک اینترفیس جدید را به نام IUnitOfWork به برنامه اضافه کردهایم.
در اینجا کلاس Sample07Context پیاده سازی کننده اینترفیس IUnitOfWork خواهد بود (اولین تغییر).
دومین تغییر هم استفاده از متد base.Set میباشد. به این ترتیب به سادگی میتوان به DbSetهای مختلف در حین کار با IUnitOfWork دسترسی پیدا کرد. به عبارتی ضرورتی ندارد به ازای تک تک DbSetها یکبار خاصیت جدیدی را به اینترفیس IUnitOfWork اضافه کرد. به کمک استفاده از امکانات Generics مهیا، اینبار
uow.Set<Product>
معادل همان db.Products سابق است؛ در حالتیکه از Sample07Context به صورت مستقیم استفاده شود.
همچنین نیازی به پیاده سازی متد SaveChanges نیست؛ زیرا پیاده سازی آن در کلاس DbContext قرار دارد.
استفاده از الگوی واحد کار در کلاسهای لایه سرویس برنامه
using EF_Sample07.DomainClasses; using System.Collections.Generic;
namespace EF_Sample07.ServiceLayer { public interface ICategoryService { void AddNewCategory(Category category); IList<Category> GetAllCategories(); } }
using EF_Sample07.DomainClasses; using System.Collections.Generic;
namespace EF_Sample07.ServiceLayer { public interface IProductService { void AddNewProduct(Product product); IList<Product> GetAllProducts(); } }
لایه سرویس برنامه را با دو اینترفیس جدید شروع میکنیم. هدف از این اینترفیسها، ارائه پیاده سازیهای متفاوت، به ازای ORMهای مختلف است. برای مثال در کلاسهای زیر که نام آنها با Ef شروع شده است، پیاده سازی خاص Ef Code first را تدارک خواهیم دید. این پیاده سازی، قابل انتقال به سایر ORMها نیست چون نه پیاده سازی یکسانی را از مباحث LINQ ارائه میدهند و نه متدهای الحاقی همانندی را به همراه دارند و نه اینکه مباحث نگاشت کلاسهای آنها به جداول مختلف یکی است:
using System.Collections.Generic; using System.Data.Entity; using System.Linq; using EF_Sample07.DataLayer.Context; using EF_Sample07.DomainClasses;
namespace EF_Sample07.ServiceLayer { public class EfCategoryService : ICategoryService { IUnitOfWork _uow; IDbSet<Category> _categories; public EfCategoryService(IUnitOfWork uow) { _uow = uow; _categories = _uow.Set<Category>(); }
public void AddNewCategory(Category category) { _categories.Add(category); }
public IList<Category> GetAllCategories() { return _categories.ToList(); } } }
using System.Collections.Generic; using System.Data.Entity; using System.Linq; using EF_Sample07.DataLayer.Context; using EF_Sample07.DomainClasses;
namespace EF_Sample07.ServiceLayer { public class EfProductService : IProductService { IUnitOfWork _uow; IDbSet<Product> _products; public EfProductService(IUnitOfWork uow) { _uow = uow; _products = _uow.Set<Product>(); }
public void AddNewProduct(Product product) { _products.Add(product); }
public IList<Product> GetAllProducts() { return _products.Include(x => x.Category).ToList(); } } }
توضیحات:
همانطور که ملاحظه میکنید در هیچکدام از کلاسهای سرویس برنامه، وهله سازی مستقیمی از الگوی واحد کار وجود ندارد. این لایه از برنامه اصلا نمیداند که کلاسی به نام Sample07Context وجود خارجی دارد یا خیر.
همچنین لایه اضافی دیگری را به نام Repository جهت مخفی سازی سازوکار EF به برنامه اضافه نکردهایم. این لایه شاید در نگاه اول برنامه را مستقل از ORM جلوه دهد اما در عمل قابل انتقال نیست و سبب تحمیل سربار اضافی بی موردی به برنامه میشود؛ ORMها ویژگیهای یکسانی را ارائه نمیدهند. حتی در حالت استفاده از LINQ، پیاده سازیهای یکسانی را به همراه ندارند.
بنابراین اگر قرار است برنامه مستقل از ORM کار کند، نیاز است لایه استفاده کننده از سرویس برنامه، با دو اینترفیس IProductService و ICategoryService کار کند و نه به صورت مستقیم با پیاده سازی آنها. به این ترتیب هر زمان که لازم شد، فقط باید پیاده سازیهای کلاسهای سرویس را تغییر داد؛ باز هم برنامه نهایی بدون نیاز به تغییری کار خواهد کرد.
تا اینجا به معماری پیچیدهای نرسیدهایم و اصطلاحا over-engineering صورت نگرفته است. یک اینترفیس بسیار ساده IUnitOfWork به برنامه اضافه شده؛ در ادامه این اینترفیس به کلاسهای سرویس برنامه تزریق شده است (تزریق وابستگی در سازنده کلاس). کلاسهای سرویس ما «میدانند» که EF وجود خارجی دارد و سعی نکردهایم توسط لایه اضافی دیگری آنرا مخفی کنیم. شیوه کار با IDbSet تعریف شده دقیقا همانند روال متداولی است که با EF Code first کار میشود و بسیار طبیعی جلوه میکند.
استفاده از الگوی واحد کار و کلاسهای سرویس تهیه شده در یک برنامه کنسول ویندوزی
در ادامه برای وهله سازی اینترفیسهای سرویس و واحد کار برنامه، از کتابخانه StructureMap که یاد شد، استفاده خواهیم کرد. بنابراین، تمام برنامههای نهایی ارائه شده در این قسمت، ارجاعی را به اسمبلی StructureMap.dll نیاز خواهند داشت.
کدهای برنامه کنسول مثال جاری را در ادامه ملاحظه خواهید کرد:
using System.Collections.Generic; using System.Data.Entity; using EF_Sample07.DataLayer.Context; using EF_Sample07.DomainClasses; using EF_Sample07.ServiceLayer; using StructureMap;
namespace EF_Sample07 { class Program { static void Main(string[] args) { Database.SetInitializer(new MigrateDatabaseToLatestVersion<Sample07Context, Configuration>());
HibernatingRhinos.Profiler.Appender.EntityFramework.EntityFrameworkProfiler.Initialize(); ObjectFactory.Initialize(x => { x.For<IUnitOfWork>().CacheBy(InstanceScope.Hybrid).Use<Sample07Context>(); x.For<ICategoryService>().Use<EfCategoryService>(); });
var uow = ObjectFactory.GetInstance<IUnitOfWork>(); var categoryService = ObjectFactory.GetInstance<ICategoryService>();
var product1 = new Product { Name = "P100", Price = 100 }; var product2 = new Product { Name = "P200", Price = 200 }; var category1 = new Category { Name = "Cat100", Title = "Title100", Products = new List<Product> { product1, product2 } }; categoryService.AddNewCategory(category1); uow.SaveChanges(); } } }
در اینجا بیشتر هدف، معرفی نحوه استفاده از StructureMap است.
ابتدا توسط متد ObjectFactory.Initialize مشخص میکنیم که اگر برنامه نیاز به اینترفیس IUnitOfWork داشت، لطفا کلاس Sample07Context را وهله سازی کرده و مورد استفاده قرار بده. اگر ICategoryService مورد استفاده قرار گرفت، وهله مورد نظر باید از کلاس EfCategoryService تامین شود.
توسط ObjectFactory.GetInstance نیز میتوان به وهلهای از این کلاسها دست یافت و نهایتا با فراخوانی uow.SaveChanges میتوان اطلاعات را ذخیره کرد.
چند نکته:
- به کمک کتابخانه StructureMap، تزریق IUnitOfWork به سازنده کلاس EfCategoryService به صورت خودکار انجام میشود. اگر به کدهای فوق دقت کنید ما فقط با اینترفیسها مشغول به کار هستیم، اما وهلهسازیها در پشت صحنه انجام میشود.
- حین معرفی IUnitOfWork از متد CacheBy با پارامتر InstanceScope.Hybrid استفاده شده است. این enum مقادیر زیر را میتواند بپذیرد:
public enum InstanceScope { PerRequest = 0, Singleton = 1, ThreadLocal = 2, HttpContext = 3, Hybrid = 4, HttpSession = 5, HybridHttpSession = 6, Unique = 7, Transient = 8, }
برای مثال اگر در برنامهای نیاز داشتید یک کلاس به صورت Singleton عمل کند، فقط کافی است نحوه کش شدن آنرا تغییر دهید.
حالت PerRequest در برنامههای وب کاربرد دارد (و حالت پیش فرض است). با انتخاب آن وهله سازی کلاس مورد نظر به ازای هر درخواست رسیده انجام خواهد شد.
در حالت ThreadLocal، به ازای هر Thread، وهلهای متفاوت در اختیار مصرف کننده قرار میگیرد.
با انتخاب حالت HttpContext، به ازای هر HttpContext ایجاد شده، کلاس معرفی شده یکبار وهله سازی میگردد.
حالت Hybrid ترکیبی است از حالتهای HttpContext و ThreadLocal. اگر برنامه وب بود، از HttpContext استفاده خواهد کرد در غیراینصورت به ThreadLocal سوئیچ میکند.
استفاده از الگوی واحد کار و کلاسهای سرویس تهیه شده در یک برنامه ASP.NET MVC
یک برنامه خالی ASP.NET MVC را آغاز کنید. سپس یک HomeController جدید را نیز به آن اضافه نمائید و کدهای آنرا مطابق اطلاعات زیر تغییر دهید:
using System.Web.Mvc; using EF_Sample07.DomainClasses; using EF_Sample07.ServiceLayer; using EF_Sample07.DataLayer.Context; using System.Collections.Generic;
namespace EF_Sample07.MvcAppSample.Controllers { public class HomeController : Controller { IProductService _productService; ICategoryService _categoryService; IUnitOfWork _uow; public HomeController(IUnitOfWork uow, IProductService productService, ICategoryService categoryService) { _productService = productService; _categoryService = categoryService; _uow = uow; }
[HttpGet] public ActionResult Index() { var list = _productService.GetAllProducts(); return View(list); }
[HttpGet] public ActionResult Create() { ViewBag.CategoriesList = new SelectList(_categoryService.GetAllCategories(), "Id", "Name"); return View(); }
[HttpPost] public ActionResult Create(Product product) { if (this.ModelState.IsValid) { _productService.AddNewProduct(product); _uow.SaveChanges(); }
return RedirectToAction("Index"); }
[HttpGet] public ActionResult CreateCategory() { return View(); }
[HttpPost] public ActionResult CreateCategory(Category category) { if (this.ModelState.IsValid) { _categoryService.AddNewCategory(category); _uow.SaveChanges(); }
return RedirectToAction("Index"); } } }
نکته مهم این کنترلر، تزریق وابستگیها در سازنده کلاس کنترلر است؛ به این ترتیب کنترلر جاری نمیداند که با کدام پیاده سازی خاصی از این اینترفیسها قرار است کار کند.
اگر برنامه را به همین نحو اجرا کنیم، موتور ASP.NET MVC ایراد خواهد گرفت که یک کنترلر باید دارای سازندهای بدون پارامتر باشد تا من بتوانم به صورت خودکار وهلهای از آنرا ایجاد کنم. برای رفع این مشکل از کتابخانه StructureMap برای تزریق خودکار وابستگیها کمک خواهیم گرفت:
using System; using System.Data.Entity; using System.Web.Mvc; using System.Web.Routing; using EF_Sample07.DataLayer.Context; using EF_Sample07.ServiceLayer; using StructureMap;
namespace EF_Sample07.MvcAppSample
{ // Note: For instructions on enabling IIS6 or IIS7 classic mode, // visit http://go.microsoft.com/?LinkId=9394801
public class MvcApplication : System.Web.HttpApplication { public static void RegisterGlobalFilters(GlobalFilterCollection filters) { filters.Add(new HandleErrorAttribute()); }
public static void RegisterRoutes(RouteCollection routes) { routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute( "Default", // Route name "{controller}/{action}/{id}", // URL with parameters new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults ); }
protected void Application_Start() { Database.SetInitializer(new MigrateDatabaseToLatestVersion<Sample07Context, Configuration>()); HibernatingRhinos.Profiler.Appender.EntityFramework.EntityFrameworkProfiler.Initialize(); AreaRegistration.RegisterAllAreas(); RegisterGlobalFilters(GlobalFilters.Filters); RegisterRoutes(RouteTable.Routes); initStructureMap(); }
private static void initStructureMap() { ObjectFactory.Initialize(x => { x.For<IUnitOfWork>().HttpContextScoped().Use(() => new Sample07Context()); x.ForRequestedType<ICategoryService>().TheDefaultIsConcreteType<EfCategoryService>(); x.ForRequestedType<IProductService>().TheDefaultIsConcreteType<EfProductService>(); });
//Set current Controller factory as StructureMapControllerFactory ControllerBuilder.Current.SetControllerFactory(new StructureMapControllerFactory()); }
protected void Application_EndRequest(object sender, EventArgs e) { ObjectFactory.ReleaseAndDisposeAllHttpScopedObjects(); } }
public class StructureMapControllerFactory : DefaultControllerFactory { protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType) { return ObjectFactory.GetInstance(controllerType) as Controller; } } }
توضیحات:
کدهای فوق متعلق به کلاس Global.asax.cs هستند. در اینجا در متد Application_Start، متد initStructureMap فراخوانی شده است.
با پیاده سازی ObjectFactory.Initialize در کدهای برنامه کنسول معرفی شده آشنا شدیم. اینبار فقط حالت کش شدن کلاس Context برنامه را HttpContextScoped قرار دادهایم تا به ازای هر درخواست رسیده یک بار الگوی واحد کار وهله سازی شود.
نکته مهمی که در اینجا اضافه شدهاست، استفاده از متد ControllerBuilder.Current.SetControllerFactory میباشد. این متد نیاز به وهلهای از نوع DefaultControllerFactory دارد که نمونهای از آنرا در کلاس StructureMapControllerFactory مشاهده میکنید. به این ترتیب در زمان وهله سازی خودکار یک کنترلر، اینبار StructureMap وارد عمل شده و وابستگیهای برنامه را مطابق تعاریف ObjectFactory.Initialize ذکر شده، به سازنده کلاس کنترلر تزریق میکند.
همچنین در متد Application_EndRequest با فراخوانی ObjectFactory.ReleaseAndDisposeAllHttpScopedObjects از نشتی اتصالات به بانک اطلاعاتی جلوگیری خواهیم کرد. چون وهله الگوی کار برنامه HttpScoped تعریف شده، در پایان یک درخواست به صورت خودکار توسط StructureMap پاکسازی میشود و به نشتی منابع نخواهیم رسید.
استفاده از الگوی واحد کار و کلاسهای سرویس تهیه شده در یک برنامه ASP.NET Web forms
در یک برنامه ASP.NET Web forms نیز میتوان این مباحث را پیاده سازی کرد:
using System; using System.Data.Entity; using EF_Sample07.DataLayer.Context; using EF_Sample07.ServiceLayer; using StructureMap;
namespace EF_Sample07.WebFormsAppSample { public class Global : System.Web.HttpApplication { private static void initStructureMap() { ObjectFactory.Initialize(x => { x.For<IUnitOfWork>().HttpContextScoped().Use(() => new Sample07Context()); x.ForRequestedType<ICategoryService>().TheDefaultIsConcreteType<EfCategoryService>(); x.ForRequestedType<IProductService>().TheDefaultIsConcreteType<EfProductService>();
x.SetAllProperties(y=> { y.OfType<IUnitOfWork>(); y.OfType<ICategoryService>(); y.OfType<IProductService>(); }); }); }
void Application_Start(object sender, EventArgs e) { Database.SetInitializer(new MigrateDatabaseToLatestVersion<Sample07Context, Configuration>()); HibernatingRhinos.Profiler.Appender.EntityFramework.EntityFrameworkProfiler.Initialize(); initStructureMap(); }
void Application_EndRequest(object sender, EventArgs e) { ObjectFactory.ReleaseAndDisposeAllHttpScopedObjects(); }
در اینجا کدهای کلاس Global.asax.cs را ملاحظه میکنید. توضیحات آن با قسمت ASP.NET MVC آنچنان تفاوتی ندارد و یکی است. البته منهای تعاریف SetAllProperties که جدید است و در ادامه به علت اضافه کردن آنها خواهیم رسید.
در ASP.NET Web forms برخلاف ASP.NET MVC نیاز است کار وهله سازی اینترفیسها را به صورت دستی انجام دهیم. برای این منظور و کاهش کدهای تکراری برنامه میتوان یک کلاس پایه را به نحو زیر تعریف کرد:
using System.Web.UI; using StructureMap;
namespace EF_Sample07.WebFormsAppSample { public class BasePage : Page { public BasePage() { ObjectFactory.BuildUp(this); } } }
سپس برای استفاده از آن خواهیم داشت:
using System; using EF_Sample07.DataLayer.Context; using EF_Sample07.DomainClasses; using EF_Sample07.ServiceLayer;
namespace EF_Sample07.WebFormsAppSample { public partial class AddProduct : BasePage { public IUnitOfWork UoW { set; get; } public IProductService ProductService { set; get; } public ICategoryService CategoryService { set; get; }
protected void Page_Load(object sender, EventArgs e) { if (!IsPostBack) { bindToCategories(); } }
private void bindToCategories() { ddlCategories.DataTextField = "Name"; ddlCategories.DataValueField = "Id"; ddlCategories.DataSource = CategoryService.GetAllCategories(); ddlCategories.DataBind(); }
protected void btnAdd_Click(object sender, EventArgs e) { var product = new Product { Name = txtName.Text, Price = int.Parse(txtPrice.Text), CategoryId = int.Parse(ddlCategories.SelectedItem.Value) }; ProductService.AddNewProduct(product); UoW.SaveChanges(); Response.Redirect("~/Default.aspx"); } } }
اینبار وابستگیهای کلاس افزودن محصولات، به صورت خواصی عمومی تعریف شدهاند. این خواص عمومی توسط متد SetAllProperties که در فایل global.asax.cs معرفی شدند، باید یکبار تعریف شوند (مهم!).
سپس اگر دقت کرده باشید، اینبار کلاس AddProduct از BasePage ما ارث بری کرده است. در سازند کلاس BasePage، با فراخوانی متد ObjectFactory.BuildUp، تزریق وابستگیها به خواص عمومی کلاس جاری صورت میگیرد.
در ادامه نحوه استفاده از این اینترفیسها را جهت مقدار دهی یک DropDownList یا ذخیره سازی اطلاعات یک محصول مشاهده میکنید. در اینجا نیز کار با اینترفیسها انجام شده و کلاس جاری دقیقا نمیداند که با چه وهلهای مشغول به کار است. تنها در زمان اجرا است که توسط StructureMap ، به ازای هر اینترفیس معرفی شده، وهلهای مناسب بر اساس تعاریف فایل Global.asax.cs در اختیار برنامه قرار میگیرد.
کدهای کامل مثالهای این سری را از آدرس زیر هم میتوانید دریافت کنید: (^)
به روز رسانی
کدهای قسمت جاری را به روز شده جهت استفاده از EF 6 و StructureMap 3 در VS 2013، از اینجا میتوانید دریافت کنید:
EF_Sample07
دمویی نیم ساعته در مورد مباحث مختلف امنیت و سطوح دسترسی در SharePoint
پس از دریافت پسوند فایل را به mp4 تغییر دهید.
services.AddServerSideBlazor () //............ endpoints.MapBlzorHub()
<script script src= _framework/blazor.server.js></script>
script src= admin/_framework/blazor.server.js
1) فراموش میکنیم تا اسکریپت اصلی jQuery را به درستی پیوست و مسیردهی کنیم.
2) مسیر Generic handler دیگری را ذکر میکنیم.
3) مسیرهای تصاویری را که Image slider باید نمایش دهد، کاملا بیربط ذکر میکنیم.
4) خروجی JSON نامربوطی را بازگشت میدهیم.
5) یکبار هم یک استثنای عمدی دستی را در بین کدها قرار خواهیم داد.
و ... بعد سعی میکنیم با استفاده از Firebug عیوب فوق را یافته و اصلاح کنیم؛ تا به یک برنامه قابل اجرا برسیم.
معرفی برنامهای که کار نمیکند!
یک برنامه ASP.NET Empty web application را آغاز کنید. سپس سه پوشه Scripts، Content و Images را به آن اضافه نمائید. در این پوشهها، اسکریپتهای نمایش دهنده تصاویر، Css آن و تصاویری که قرار است نمایش داده شوند، قرار میگیرند:
سپس یک فایل default.aspx و یک فایل OrbitHandler.ashx را نیز به پروژه با محتویات ذیل اضافه کنید: (در این دو فایل، 5 مورد مشکل ساز یاد شده لحاظ شدهاند)
محتویات فایل OrbitHandler.ashx.cs مطابق کدهای ذیل است:
using System.Collections.Generic; using System.IO; using System.Web; using System.Web.Script.Serialization; namespace OrbitWebformsTest { public class Picture { public string Title { set; get; } public string Path { set; get; } } public class OrbitHandler : IHttpHandler { IList<Picture> PicturesDataSource() { var results = new List<Picture>(); var path = HttpContext.Current.Server.MapPath("~/Images"); foreach (var item in Directory.GetFiles(path, "*.*")) { var name = Path.GetFileName(item); results.Add(new Picture { Path = /*"Images/" + name*/ name, Title = name }); } return results; } public void ProcessRequest(HttpContext context) { var items = PicturesDataSource(); var json = /*new JavaScriptSerializer().Serialize(items)*/ string.Empty; throw new InvalidDataException("همینطوری"); context.Response.ContentType = "text/plain"; context.Response.Write(json); } public bool IsReusable { get { return false; } } } }
همچنین کدهای صفحه ASPX ایی که قرار است (به ظاهر البته) از این Generic handler استفاده کند به نحو ذیل است:
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="default.aspx.cs" Inherits="OrbitWebformsTest._default" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head runat="server"> <title></title> <link href="Content/orbit-1.2.3.css" rel="stylesheet" type="text/css" /> <script src="Script/jquery-1.5.1.min.js" type="text/javascript"></script> <script src="Scripts/jquery.orbit-1.2.3.min.js" type="text/javascript"></script> </head> <body> <form id="form1" runat="server"> <div id="featured"> </div> </form> <script type="text/javascript"> $(function () { $.ajax({ url: "Handler.ashx", contentType: "application/json; charset=utf-8", success: function (data) { $.each(data, function (i, b) { var str = '<img src="' + b.Path + '" alt="' + b.Title + '"/>'; $("#featured").append(str); }); $('#featured').orbit(); }, dataType: "json" }); }); </script> </body> </html>
مراحل عیب یابی برنامهای که کار نمیکند!
ابتدا برنامه را در فایرفاکس باز کرده و سپس افزونه Firebug را با کلیک بر روی آیکن آن، بر روی سایت فعال میکنیم. سپس یکبار بر روی دکمه F5 کلیک کنید تا مجددا مراحل بارگذاری سایت تحت نظر افزونه Firebug فعال شده، طی شود.
اولین موردی که مشهود است، نمایش عدد 3، کنار آیکن فایرباگ میباشد. این عدد به معنای وجود خطاهای اسکریپتی در کدهای ما است.
برای مشاهده این خطاها، بر روی برگه Console آن کلیک کنید:
بله. مشخص است که مسیر دهی فایل jquery-1.5.1.min.js صحیح نبوده و همین مساله سبب بروز خطاهای اسکریپتی گردیده است. برای اصلاح آن سطر زیر را در برنامه تغییر دهید:
<script src="Scripts/jquery-1.5.1.min.js" type="text/javascript"></script>
مجددا دکمه F5 را فشرده و سایت را با تنظیمات جدید اجرا کنید. اینبار در برگه Console و یا در برگه شبکه فایرباگ، خطای یافت نشدن Generic handler نمایان میشوند:
برای رفع آن به فایل default.aspx مراجعه و بجای معرفی Handler.ashx، نام OrbitHandler.ashx را وارد کنید.
مجددا دکمه F5 را فشرده و سایت را با تنظیمات جدید اجرا کنید.
اگر به برگه کنسول دقت کنیم، بروز استثناء در کدها تشخیص داده شده و همچنین در برگه Response پاسخ دریافتی از سرور، جزئیات صفحه خطای بازگشتی از آن نیز قابل بررسی و مشاهده است.
اینبار به فایل OrbitHandler.ashx.cs مراجعه کرده و سطر throw new InvalidDataException را حذف میکنیم. در ادامه برنامه را کامپایل و مجددا اجرا خواهیم کرد.
با اجرای مجدد سایت، تبادل اطلاعات صحیحی با فایل OrbitHandler.ashx برقرار شده است، اما خروجی خاصی قابل مشاهده نیست. بنابراین بازهم سایت کار نمیکند.
برای رفع این مشکل، متد ProcessRequest را به نحو ذیل تغییر خواهیم داد:
public void ProcessRequest(HttpContext context) { var items = PicturesDataSource(); var json = new JavaScriptSerializer().Serialize(items); context.Response.ContentType = "text/plain"; context.Response.Write(json); }
بله. تمام تنظیمات به نظر درست هستند، اما در برگه شبکه فایرباگ تعدادی خطای 404 و یا «یافت نشد»، مشاهده میشوند. مشکل اینجا است که مسیرهای بازگشت داده شده توسط متد Directory.GetFiles، مسیرهای مطلقی هستند؛ مانند c:\path\images\01.jpg و جهت نمایش در یک وب سایت مناسب نمیباشند. برای تبدیل آنها به مسیرهای نسبی، اینبار کدهای متد تهیه منبع داده را به نحو ذیل ویرایش میکنیم:
IList<Picture> PicturesDataSource() { var results = new List<Picture>(); var path = HttpContext.Current.Server.MapPath("~/Images"); foreach (var item in Directory.GetFiles(path, "*.*")) { var name = Path.GetFileName(item); results.Add(new Picture { Path = "Images/" + name, Title = name }); } return results; }
اینبار اگر برنامه را اجرا کنیم، بدون مشکل کار خواهد کرد.
بنابراین در اینجا مشاهده کردیم که اگر «برنامهای مبتنی بر jQuery کار نمیکند»، چگونه باید قدم به قدم با استفاده از فایرباگ و امکانات آن، به خطاهایی که گزارش میدهد و یا مسیرهایی را که یافت نشد بیان میکند، دقت کرد تا بتوان برنامه را عیب یابی نمود.
سؤال مهم: اجرای کدهای jQuery Ajax فوق، چه تغییری را در صفحه سبب میشوند؟
اگر به برگه اسکریپتها در کنسول فایرباگ مراجعه کنیم، امکان قرار دادن breakpoint بر روی سطرهای کدهای جاوا اسکریپتی نمایش داده شده نیز وجود دارد:
در اینجا همانند VS.NET میتوان برنامه را در مرورگر اجرا کرده و تگهای تصویر پویای تولید شده را پیش از اضافه شدن به صفحه، مرحله به مرحله بررسی کرد. به این ترتیب بهتر میتوان دریافت که آیا src بازگشت داده شده از سرور فرمت صحیحی دارد یا خیر و آیا به محل مناسبی اشاره میکند یا نه. همچنین در برگه HTML آن، عناصر پویای اضافه شده به صفحه نیز بهتر مشخص هستند:
docker pull node
docker images
در ادامه نیاز داریم یک دایرکتوری را ایجاد کرده و فایل index.js را درون آن بسازیم:
mkdir testapp cd testapp touch index.js npm init npm i express --save
یک دایرکتوری را ساختیم و همچنین express را نیز نصب نمودیم.
اکنون package.json را باز کرده و این قسمت را جایگزین کنید؛ تا با استفاده از npm start، برنامه اجرا شود:
"scripts": { "start": "node index" }
index.js را باز کرده و کدهای زیر را وارد کنید:
const express = require('express') const app = express() const PORT = 3000; app.get('/', function (req, res) { res.send('Hello World') }) app.listen(PORT, function () { console.log(`listening on port ${PORT}!`) })
همه چیز خیلی ساده در نظر گرفته شده است. از فریمورک express استفاده کردیم و یک سرور را بر روی پورت 3000 اجرا کرده و همچنین بر روی آدرس "/" یک سطر کد Hello World اجرا میشود.
خوب، فرض کنید قصد داریم با استفاده از volume، کد فوق را بر روی container اجرا کنیم.
برای اجرا شدن این کدها بر روی Volume، دستور زیر را درون ترمینال خود وارد کنید:
docker run -d -p 3030:3000 -v $(pwd):/var/www -w "/var/www" node npm start
شرح دستور فوق:
دستور ساخت container با استفاده از ارگومان run
d- برای اجرا شدن container در حالت detached، باعث میشود اجرا شدن آن در حالت بکگراند بوده و بتوانید بر روی ترمینال مربوطه، دستورات دیگری را وارد نمایید.
p- پورت داخلی و خارجی را مشخص میکند. در اینجا پورت داخلی، 3000 و خارجی، 3030 میباشد.
آرگومان v- برای ساخت volume و (pwd)$ دایرکتوری جاری را بر روی سیستم شما، نشان خواهد داد. بعد از آن /var/www یک دایرکتوری فرضی است (هر آدرس دلخواهی را میتوانیم داشته باشیم) که قرار است بوسیلهیdocker ساخته شود و از آن اشارهگری به دایرکتوری جاری بر روی ماشین محلی زده شود.
w- همان WorkingDirectory میباشد. بدلیل اینکه میخواهیم بر روی container به دایرکتوری که کدهای ما وجود دارد، وارد شود.
بعد از آن اسم image ای را که قرار است از آن استفاده شود، آورده تا container ایجاد شود.
و بعد از npm start برنامه را اجرا خواهد کرد.
پس از اجرا کردن دستور فوق، container ایجاد میشود و قابلیت اجرایی دارد (با استفاده از ip و port خارجی بر روی browser میتوانیم برنامه را مشاهده کنیم).
حال با استفاده از دستور زیر لیست containerهای اجرایی را مشاهده خواهیم کرد:
docker ps
میبینید که container، اجرا شده و پورت آن مشخص شدهاست. تصویر و کلید هش شدهی منحصر به فرد آن را نیز مشاهده میفرمایید.
حتی میتوانید به راحتی درون container را با استفاده از دستور زیر مشاهده کنید:
docker exec -it 6003 bash
6003 ابتدای کلید container ایجاد شدهاست و با استفاده از bash وارد محیط command line در container ایجاد شده خواهیم شد و دسترسی کاملی خواهیم داشت.
بطور مثال برای دیدن کدهای index.js بر روی container ایجاد شده، بعد از دستور فوق، command زیر را وارد نمایید:
cat index.js
جالب است بدانید از آنجائیکه container از طریق volume به دایرکتوری محلی شما لینک شدهاست، به محض اینکه بر روی سیستم خود کدی را تغییر داده و دوباره دستور فوق را اجرا کنید، تغییرات را مشاهده خواهید کرد.
برای متوقف کردن container از دستور زیر باید استفاده کرد:
docker stop 6003
نکته: 6003 آی دی container است و برای اجرای مجدد آن docker start 6003
بعد از متوقف کردن container و اجرای دستور docker ps متوجه خواهید شد که دیگر Container در لیست containerهای باز نیست.
با استفاده از دستور زیر به لیست تمامی Containerها چه در حال اجرا و چه متوقف شده، دسترسی خواهیم داشت:
docker ps -a
برای حذف container نیز از دستور زیر استفاده میکنیم:
docker rm -v 6003
rm برای حذف container و همچنین v- برای حذف volume میباشد.
بررسی روش آپلود فایلها در ASP.NET Core
معرفی ES 6
چرا باید ES 6 را آموخت؟
در طی 2 سال آینده، تمام فریم ورکهای جدید جاوا اسکریپتی، از بوت استرپ 4 تا AngularJS 2 تا Aurelia و غیره، همگی به ES 6 کوچ خواهند کرد (و این اتفاق هم اکنون در حال رخ دادن است). بنابراین به زودی بدون فراگیری و تسلط بر ES 6، در حوزهی وب، «بیسواد» محسوب خواهید شد و فراگیری آن یک «باید» است.
وضعیت پشتیبانی از ES 6 در مرورگرهای مختلف
برای مشاهدهی پیشرفتهای مرورگرهای کنونی در زمینهی پشتیبانی از ES 6، میتوان به صفحهی ES 6 compatibility table مراجعه کرد.
برای نمونه در حال حاضر، فایرفاکس بهترین پشتیبانی از ES 6 را ارائه میدهد (با پیاده سازی 85 درصد از قابلیتها) و بعد از آن مرورگر جدید مایکروسافت قرار دارد.
وضعیت IE 10,11 در این بین تغییری نخواهند کرد؛ زیرا پشتیبانی رسمی از تمام آنها به زودی خاتمه مییابد (در سه شنبه، ۲۲ دی ۱۳۹۴).
در همین صفحه، در ابتدای چارت، ستون current browser نیز قرار دارد. این ستون، وضعیت مرورگر جاری شما را از لحاظ درصد پیاده سازی قابلیتهای ES 6 نمایش میدهد.
اهمیت دریافت آخرین نگارشهای مرورگرها
با توجه به ES 6 compatibility table، اکثر مرورگرها در نسخههای شبانه و همچنین آزمایشی آنها، به مرور در حال افزودن قابلیتهای باقیماندهی ES 6 هستند. بنابراین اگر با فایرفاکس کار میکنید، نیاز است Firefox nightly builds را نصب کنید. اگر از مرورگرهای مایکروسافت استفاده میکنید، آخرین نگارش MS Edge بهترین پشتیبانی از ES 6 را ارائه میدهد و اگر از کروم استفاده میکنید، نگارشهای بتا و Dev آن را میتوانید دریافت کنید.
علاوه بر اینها، نگارشهای فعلی این مرورگرها نیز دارای امکانات آزمایشی هستند که میتوان آنها را به صورت دستی فعال کرد. برای مثال در مرورگر کروم، به آدرس chrome://flags مراجعه کنید و در صفحهی باز شده، کلمهی JavaScript را جستجو کنید. در اینجا نیاز است گزینهی «Enable Experimental JavaScript» را فعال کنید (بر روی لینک enable ذیل آن کلیک نمائید).
به این ترتیب قادر خواهید بود آخرین افزونههای ES 6 را در developer tools console آن اجرا کنید.
چنین تنظیمی به MS Edge نیز اضافه شدهاست. پس از اجرای آن، به آدرس about:flags مراجعه کنید:
در اینجا نیز میتوانید گزینهی «Enable experimental JavaScript features» را انتخاب کنید.
معرفی traceur-compiler
هرچند قابلیتهای فعلی آخرین نگارشهای مرورگرها برای اجرای بسیاری از امکانات ES 6 کفایت میکنند، اما اگر علاقمند به اجرای تمامی آنها هستید، میتوان از traceur-compiler گوگل نیز کمک گرفت (با تلفظ تریسر). این کامپایلر، قابلیتهای جدید ES 6 را تبدیل به نگارشهای فعلی قابل درک برای مرورگرهای قدیمیتر میکند. به این ترتیب امکان اجرای آزمایشات مرتبط با ES 6 را خواهید یافت.
روش استفادهی از آن هم به صورت ذیل است:
<script src="https://google.github.io/traceur-compiler/bin/traceur.js"></script> <script src="https://google.github.io/traceur-compiler/src/bootstrap.js"></script> <script type="module"> // ES 6 </script>
معرفی ELMAH
یا این تنظیمات رو مطابق مقالهای که ذکر کردم میتونید به web.config انتقال بدید (قسمت system.net -- mailSettings -- smtp -- network) و نیازی به تعریف مجدد آنها در code behind نیست و به صورت سراسری به برنامه اعمال میشود. یکی از این دو حالت رو انتخاب کنید.
در elmah مطابق مقاله https://www.dntips.ir/2010/08/elmah-wcf-ria-services.html باید قسمت errorMail اضافه شود که در آن مقاله البته comment شده که باید comment آن برداشته شود.
سپس elmah هم چون از همان کدهای متداول دات نتی استفاده میکند تنظیمات smtp authentication را که بحث مجزایی است میتواند از web.config هم دریافت کند. راه دیگر هم این است که این یوزر نیم و پسورد را در همان تگ errorMail هم مطابق مثال web.config موجود در پوشه samples آن هم میشود تعریف کرد. تمام اینها یک اثر را دارند و فقط یکی باید اعمال شود.
ضمنا یک مطلب رو هم به صراحت خدمت شما عرض کنم. تابحال هاستی رو ندیدم که با همان تنظیمات local شما بدون مشکل کار کند. علت هم این است که کسی نتواند از امکانات ارسال ایمیل آنها سوء استفاده کند. بنابراین بر روی هاست حتما نیاز به smtp authentication هست. یا این اطلاعات را باید از هاست بگیرید. یا اگر کنترل پنل ارسال ایمیل هم سایت دارد یک یوزر جدید در آن تعریف کنید و مشخصات آنرا در برنامه استفاده کنید. در غیر اینصورت آن هاست از لحاظ امنیتی به شدت مشکل دارد و دیر یا زود زیر بار ارسال میلیونها اسپم از پا در خواهد آمد.