My road for the series is as follows,
RESTful Day #1: Enterprise level application architecture with Web APIs using Entity Framework, Generic Repository pattern and Unit of Work.
RESTful Day #2: Inversion of control using dependency injection in Web APIs using Unity Container and Bootstrapper.
RESTful Day #3: Resolve dependency of dependencies using Inversion of Control and dependency injection in Asp.net Web APIs with Unity Container and Managed Extensibility Framework (MEF).
RESTful Day #4: Custom URL Re-Writing/Routing using Attribute Routes in MVC 4 Web APIs.
RESTful Day #5: Basic Authentication and Token based custom Authorization in Web APIs using Action Filters.
RESTful Day #6: Request logging and Exception handing/logging in Web APIs using Action Filters, Exception Filters and nLog.
RESTful Day #7: Unit testing ASP.NET Web APIs controllers using nUnit.
RESTful Day #8: Extending OData support in ASP.NET Web APIs.
Controller Factory و Action Invoker وظیفهای مطابق نامشان را عهده دار هستند. اولی برای وهله سازی کنترلرهای مرتبط با درخواست و دومی برای پیدا کردن و تریگر نمودن یک اکشن متد به کار گرفته میشوند. فریم ورک MVC پیاده سازی پیش فرضی را از این دو کامپوننت، به صورت توکار دارد. در طی مقالاتی نحوهی کنترل کردن رفتار پیش فرض این Controller Factory و هم نحوهی جایگزین کرن کامل این کامپوننت را بررسی میکنیم.
ابتدا پروژهی جدیدی را از نوع MVC و با الگوی Empty به نام ControllerExtensibility ایجاد میکنیم. در پوشهی Models یک فایل را به نام Result.cs ساخته و از آن برای معرفی کلاس Result مطابق کدهای ذیل استفاده میکنیم:
namespace ControllerExtensibility.Models { public class Result { public string ControllerName { get; set; } public string ActionName { get; set; } } }
@model ControllerExtensibility.Models.Result @{ Layout = null; } <!DOCTYPE html> <html> <head> <meta name="viewport" content="width=device-width" /> <title>Result</title> </head> <body> <div>Controller: @Model.ControllerName</div> <div>Action: @Model.ActionName</div> </body> </html>
دو کنترلر را نیز حاوی کدهای زیر ایجاد میکنیم:
کنترلر product
using ControllerExtensibility.Models; using System.Web.Mvc; namespace ControllerExtensibility.Controllers { public class ProductController : Controller { public ViewResult Index() { return View("Result", new Result { ControllerName = "Product", ActionName = "Index" }); } public ViewResult List() { return View("Result", new Result { ControllerName = "Product", ActionName = "List" }); } } }
کنترلر customer
using System.Web.Mvc; namespace ControllerExtensibility.Controllers { public class CustomerController : Controller { public ViewResult Index() { return View("Result", new Result { ControllerName = "Customer", ActionName = "Index" }); } public ViewResult List() { return View("Result", new Result { ControllerName = "Customer", ActionName = "List" }); } } }
ایجاد یک Controller Factory سفارشی بهترین راه برای درک نحوهی وهله سازی کنترلرها توسط MVC است. ولی این کار صرفا جنبهی آموزشی داشته و در یک پروژهی واقعی این نوع پیاده سازیها پیشنهاد نمیشود؛ زیرا راههای مفیدتر و سادهتری با پیاده سازی توکار Controller Factory وجود دارند.
Controller Factoryها با پیاده سازی اینترفیس IControllerFactory معرفی میشوند. کدهای این اینترفیس را در ذیل میبینید:
using System.Web.Routing; using System.Web.SessionState; namespace System.Web.Mvc { public interface IControllerFactory { IController CreateController(RequestContext requestContext, string controllerName); SessionStateBehavior GetControllerSessionBehavior(RequestContext requestContext, string controllerName); void ReleaseController(IController controller); } }
using System; using System.Web.Mvc; using System.Web.Routing; using System.Web.SessionState; using ControllerExtensibility.Controllers; namespace ControllerExtensibility.Infrastructure { public class CustomControllerFactory : IControllerFactory { public IController CreateController(RequestContext requestContext, string controllerName) { Type targetType = null; switch (controllerName) { case "Product": targetType = typeof (ProductController); break; case "Customer": targetType = typeof (CustomerController); break; default: requestContext.RouteData.Values["controller"] = "Product"; targetType = typeof (ProductController); break; } return targetType == null ? null : (IController) DependencyResolver.Current.GetService(targetType); } public SessionStateBehavior GetControllerSessionBehavior(RequestContext requestContext, string controllerName) { return SessionStateBehavior.Default; } public void ReleaseController(IController controller) { IDisposable disposable = controller as IDisposable; if (disposable != null) { disposable.Dispose(); } } } }
نام | نوع | توضیحات |
HttpContext | HttpContextBase | حاوی اطلاعاتی در خصوص درخواست است. |
RouteData | RouteData | حاوی اطلاعاتی در خصوص Rout است که با درخواست رسیده همخوانی دارد. |
یکی از دلایلی که عنوان شد Controller factory سفارشی بدین روش در یک پروژهی عملی به کار گرفته نشود این است که یافتن کلاسهایی از نوع Controller در سراسر برنامه و وهله سازی آنها کار دشواری است. چرا که لازم خواهد بود بتوانید به صورت پویا کنترلر را مکان یابی کرده و بین کلاسهای هم نام در دیگر فضاهای نام تمییز قائل شوید و خطاهای محتمل در حین وهله سازی را کنترل کنید.
در این مثال تنها دو کنترلر داریم و آنها را به صورت مستقیم در Controller Factory وهله سازی میکنیم که در یک پروژهی واقعی مطلوب نیست. ولی آنچه را که این روش آشکارتر میسازد، انعطاف پذیری بالای فریم ورک MVC است که دست ما را برای نفوذ و دخل و تصرف در اعمال و رفتاریهای پیش فرض خود باز گذاشته است و برای مثال در مباحث تزریق وابستگیها و تنظیمات ابتدایی IoC Containers کاربرد دارد.
متد CreateController لازم است وهلهای از کلاسی که اینترفیس IController را پیاده سازی کرده برگرداند؛ در غیر اینصورت کار با خطا متوقف خواهد شد. لذا برای زمانی که درخواست کاربر، هیچ کدام از کنترلرها را مشمول عنایت قرار نمیدهد، باید چارهای اندیشیده شود.
میتوان آن را به کنترلر خاصی که پیغام خطایی را رندر میکند، هدایت کنیم. به عبارت بهتر باید درخواست را به کنترلری که مطمئن هستیم وجود دارد (اصطلاحا کنترلر جانشین) هدایت نماییم. همان طور که در کد فوق در قسمت default میبینید:
default: requestContext.RouteData.Values["controller"] = "Product"; targetType = typeof(ProductController); break;
در MVC انتخاب ویوی مناسب، بر حسب مقدار RouteData.Values صورت میگیرد؛ نه نام کلاس Controller و این سبب خواهد شد فریم ورک، ویوهای مرتبط با کنترلر جانشین شدهی توسط ما را جستجو کند و نه کنترلری که کاربر از طریق URL ورودی آن را درخواست کرده است.
لذا Controller Factory صرفا وظیفه مپ کردن درخواستهای واصله به کنترلرها را ندارد، بلکه توانایی دخل و تصرف در درخواست واصله بر حسب مورد را نیز خواهد داشت.
در نهایت هم نحوهی استفاده از DependencyResolver را برای وهله سازی کلاسهای کنترلر میبینید. متد استاتیک Current یک پیاده سازی از اینترفیس IDependencyResolver را که حاوی متد GetService است، برگشت داده و سپس یک شیء System.Type را به عنوان ورودی گرفته و یک وهلهی ساخته شدهی از آن را به عنوان خروجی برمیگرداند.
متد GetControllerSessionBehavior نیز توسط MVC جهت تعیین اینکه Session data برای کنترلر نیاز است یا خیر به کار گرفته میشود.
متد ReleaseController نیز هر گاه به شیء کنترلر ساخته شده در متد CreateController دیگر نیازی نبود، صدا زده خواهد شد. در کدهای ما ابتدا بررسی میشود آیا اینترفیس IDisposable توسط کلاس، پیاده سازی شده است یا خیر؟ اگر بلی متد Dispose آن جهت آزاد سازی منابعی که میتوانند آزاد شوند، صدا زده میشود.
جهت ثبت Controller Factory ساخته شده در متد Application_Start موجود در فایل global.asax.cs بوسیله کلاس ControllerBuilder و مطابق کدهای ذیل عمل مینماییم:
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Http; using System.Web.Mvc; using System.Web.Routing; using ControllerExtensibility.Infrastructure; namespace ControllerExtensibility { public class MvcApplication : System.Web.HttpApplication { protected void Application_Start() { AreaRegistration.RegisterAllAreas(); WebApiConfig.Register(GlobalConfiguration.Configuration); FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters); RouteConfig.RegisterRoutes(RouteTable.Routes); ControllerBuilder.Current.SetControllerFactory(new CustomControllerFactory()); } } }
1) پیش فرضهای EF Code first در تشخیص روابط چند به چند
تشخیص اولیه روابط چند به چند، مانند یک مطلب موجود در سایت و برچسبهای آن؛ که در این حالت یک برچسب میتواند به چندین مطلب مختلف اشاره کند و یا برعکس، هر مطلب میتواند چندین برچسب داشته باشد، نیازی به تنظیمات خاصی ندارد. همینقدر که دو طرف رابطه توسط یک ICollection به یکدیگر اشاره کنند، مابقی مسایل توسط EF Code first به صورت خودکار حل و فصل خواهند شد:
using System; using System.Linq; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Data.Entity; using System.Data.Entity.Migrations; using System.Data.Entity.ModelConfiguration; namespace Sample { public class BlogPost { public int Id { set; get; } [StringLength(maximumLength: 450, MinimumLength = 1), Required] public string Title { set; get; } [MaxLength] public string Body { set; get; } public virtual ICollection<Tag> Tags { set; get; } // many-to-many public BlogPost() { Tags = new List<Tag>(); } } public class Tag { public int Id { set; get; } [StringLength(maximumLength: 450), Required] public string Name { set; get; } public virtual ICollection<BlogPost> BlogPosts { set; get; } // many-to-many public Tag() { BlogPosts = new List<BlogPost>(); } } public class MyContext : DbContext { public DbSet<BlogPost> BlogPosts { get; set; } public DbSet<Tag> Tags { get; set; } } public class Configuration : DbMigrationsConfiguration<MyContext> { public Configuration() { AutomaticMigrationsEnabled = true; AutomaticMigrationDataLossAllowed = true; } protected override void Seed(MyContext context) { var tag1 = new Tag { Name = "Tag1" }; context.Tags.Add(tag1); var post1 = new BlogPost { Title = "Title...1", Body = "Body...1" }; context.BlogPosts.Add(post1); post1.Tags.Add(tag1); base.Seed(context); } } public static class Test { public static void RunTests() { Database.SetInitializer(new MigrateDatabaseToLatestVersion<MyContext, Configuration>()); using (var ctx = new MyContext()) { var post1 = ctx.BlogPosts.Find(1); if (post1 != null) { Console.WriteLine(post1.Title); } } } } }
در اینجا تمام تنظیمات صورت گرفته بر اساس یک سری از پیش فرضها است. برای مثال نام جدول واسط تشکیل شده، بر اساس تنظیم پیش فرض کنار هم قرار دادن نام دو جدول مرتبط تهیه شده است.
همچنین بهتر است بر روی نام برچسبها، یک ایندکس منحصربفرد نیز تعیین شود: (^ و ^)
2) تنظیم ریز جزئیات روابط چند به چند در EF Code first
تنظیمات پیش فرض انجام شده آنچنان نیازی به تغییر ندارند و منطقی به نظر میرسند. اما اگر به هر دلیلی نیاز داشتید کنترل بیشتری بر روی جزئیات این مسایل داشته باشید، باید از Fluent API جهت اعمال آنها استفاده کرد:
public class TagMap : EntityTypeConfiguration<Tag> { public TagMap() { this.HasMany(x => x.BlogPosts) .WithMany(x => x.Tags) .Map(map => { map.MapLeftKey("TagId"); map.MapRightKey("BlogPostId"); map.ToTable("BlogPostsJoinTags"); }); } } public class MyContext : DbContext { public DbSet<BlogPost> BlogPosts { get; set; } public DbSet<Tag> Tags { get; set; } protected override void OnModelCreating(DbModelBuilder modelBuilder) { modelBuilder.Configurations.Add(new TagMap()); base.OnModelCreating(modelBuilder); } }
3) حذف اطلاعات چند به چند
برای حذف تگهای یک مطلب، کافی است تک تک آنها را یافته و توسط متد Remove جهت حذف علامتگذاری کنیم. نهایتا با فراخوانی متد SaveChanges، حذف نهایی انجام و اعمال خواهد شد.
using (var ctx = new MyContext()) { var post1 = ctx.BlogPosts.Find(1); if (post1 != null) { Console.WriteLine(post1.Title); foreach (var tag in post1.Tags.ToList()) post1.Tags.Remove(tag); ctx.SaveChanges(); } }
در ادامه اینبار اگر خود post1 را حذف کنیم:
var post1 = ctx.BlogPosts.Find(1); if (post1 != null) { ctx.BlogPosts.Remove(post1); ctx.SaveChanges(); }
بنابراین دراینجا cascade delete ایی که به صورت پیش فرض وجود دارد، تنها به معنای حذف تمامی ارتباطات موجود در جدول میانی است و نه حذف کامل طرف دوم رابطه. اگر مطلبی حذف شد، فقط آن مطلب و روابط برچسبهای متعلق به آن از جدول میانی حذف میشوند و نه برچسبهای تعریف شده برای آن.
البته این تصمیم هم منطقی است. از این لحاظ که اگر قرار بود دو طرف یک رابطه چند به چند با هم حذف شوند، ممکن بود با حذف یک مطلب، کل بانک اطلاعاتی خالی شود! فرض کنید یک مطلب دارای سه برچسب است. این سه برچسب با 20 مطلب دیگر هم رابطه دارند. اکنون مطلب اول را حذف میکنیم. برچسبهای متناظر آن نیز باید حذف شوند. با حذف این برچسبها طرف دوم رابطه آنها که چندین مطلب دیگر است نیز باید حذف شوند!
4) ویرایش و یا افزودن اطلاعات چند به چند
در مثال فوق فرض کنید که میخواهیم به اولین مطلب ثبت شده، تعدادی تگ جدید را اضافه کنیم:
var post1 = ctx.BlogPosts.Find(1); if (post1 != null) { var tag2 = new Tag { Name = "Tag2" }; post1.Tags.Add(tag2); ctx.SaveChanges(); }
در مثالی دیگر اگر یک برنامه ASP.NET را درنظر بگیریم، در هربار ویرایش یک مطلب، تعدادی Tag به سرور ارسال میشوند. در ابتدای امر هم مشخص نیست کدامیک جدید هستند، چه تعدادی در لیست تگهای قبلی مطلب وجود دارند، یا اینکه کلا از لیست برچسبها حذف شدهاند:
//نام تگهای دریافتی از کاربر var tagsList = new[] { "Tag1", "Tag2", "Tag3" }; //بارگذاری یک مطلب به همراه تگهای آن var post1 = ctx.BlogPosts.Include(x => x.Tags).FirstOrDefault(x => x.Id == 1); if (post1 != null) { //ابتدا کلیه تگهای موجود را حذف خواهیم کرد if (post1.Tags != null && post1.Tags.Any()) post1.Tags.Clear(); //سپس در طی فقط یک کوئری بررسی میکنیم کدامیک از موارد ارسالی موجود هستند var listOfActualTags = ctx.Tags.Where(x => tagsList.Contains(x.Name)).ToList(); var listOfActualTagNames = listOfActualTags.Select(x => x.Name.ToLower()).ToList(); //فقط موارد جدید به تگها و ارتباطات موجود اضافه میشوند foreach (var tag in tagsList) { if (!listOfActualTagNames.Contains(tag.ToLowerInvariant().Trim())) { ctx.Tags.Add(new Tag { Name = tag.Trim() }); } } ctx.SaveChanges(); // ثبت موارد جدید //موارد قبلی هم حفظ میشوند foreach (var item in listOfActualTags) { post1.Tags.Add(item); } ctx.SaveChanges(); }
SELECT [Extent1].[Id] AS [Id], [Extent1].[Name] AS [Name] FROM [dbo].[Tags] AS [Extent1] WHERE [Extent1].[Name] IN (N'Tag1',N'Tag2',N'Tag3')
لازم است لیست موارد موجود را (listOfActualTags) از بانک اطلاعاتی دریافت کنیم، زیرا به این ترتیب سیستم ردیابی EF آنها را به عنوان رکوردی جدید و تکراری ثبت نخواهد کرد.
5) تهیه کوئریهای LINQ بر روی روابط چند به چند
الف) دریافت یک مطلب خاص به همراه تمام تگهای آن:
ctx.BlogPosts.Where(p => p.Id == 1).Include(p => p.Tags).FirstOrDefault()
var posts = from p in ctx.BlogPosts from t in p.Tags where t.Name == "Tag1" select p;
var posts = ctx.Tags.Where(x => x.Name == "Tag1").SelectMany(x => x.BlogPosts);
<?xml version="1.0" encoding="utf-8"?> <packages> <package id="Microsoft.AspNet.OData" version="6.0.0" targetFramework="net462" /> <package id="Microsoft.AspNet.SignalR.Core" version="2.2.1" targetFramework="net462" /> <package id="Microsoft.AspNet.SignalR.Owin" version="1.2.2" targetFramework="net462" /> <package id="Microsoft.AspNet.WebApi.Client" version="5.2.3" targetFramework="net462" /> <package id="Microsoft.AspNet.WebApi.Core" version="5.2.3" targetFramework="net462" /> <package id="Microsoft.AspNet.WebApi.Owin" version="5.2.3" targetFramework="net462" /> <package id="Microsoft.CodeDom.Providers.DotNetCompilerPlatform" version="1.0.2" targetFramework="net462" /> <package id="Microsoft.Extensions.DependencyInjection" version="1.0.0" targetFramework="net462" /> <package id="Microsoft.Extensions.DependencyInjection.Abstractions" version="1.0.0" targetFramework="net462" /> <package id="Microsoft.Net.Compilers" version="1.3.2" targetFramework="net462" developmentDependency="true" /> <package id="Microsoft.OData.Core" version="7.0.0" targetFramework="net462" /> <package id="Microsoft.OData.Edm" version="7.0.0" targetFramework="net462" /> <package id="Microsoft.Owin" version="3.0.1" targetFramework="net462" /> <package id="Microsoft.Owin.FileSystems" version="3.0.1" targetFramework="net462" /> <package id="Microsoft.Owin.Host.SystemWeb" version="3.0.1" targetFramework="net462" /> <package id="Microsoft.Owin.Security" version="3.0.1" targetFramework="net462" /> <package id="Microsoft.Owin.StaticFiles" version="3.0.1" targetFramework="net462" /> <package id="Microsoft.Spatial" version="7.0.0" targetFramework="net462" /> <package id="Newtonsoft.Json" version="9.0.1" targetFramework="net462" /> <package id="Owin" version="1.0" targetFramework="net462" /> <package id="System.Collections" version="4.0.11" targetFramework="net462" /> <package id="System.Collections.Concurrent" version="4.0.12" targetFramework="net462" /> <package id="System.ComponentModel" version="4.0.1" targetFramework="net462" /> <package id="System.Diagnostics.Debug" version="4.0.11" targetFramework="net462" /> <package id="System.Globalization" version="4.0.11" targetFramework="net462" /> <package id="System.Linq" version="4.1.0" targetFramework="net462" /> <package id="System.Linq.Expressions" version="4.1.0" targetFramework="net462" /> <package id="System.Reflection" version="4.1.0" targetFramework="net462" /> <package id="System.Resources.ResourceManager" version="4.0.1" targetFramework="net462" /> <package id="System.Runtime.Extensions" version="4.1.0" targetFramework="net462" /> <package id="System.Threading" version="4.0.11" targetFramework="net462" /> <package id="System.Threading.Tasks" version="4.0.11" targetFramework="net462" /> </packages>
PM>Update-Package -reinstall -Project YourProjectName
<?xml version="1.0" encoding="utf-8"?> <configuration> <appSettings> <add key="owin:AppStartup" value="OwinKatanaTest.OwinAppStartup, OwinKatanaTest" /> <!-- Owin App Startup Class --> <add key="webpages:Enabled" value="false" /> <!-- Disable asp.net web pages. Note that based on our current configuration, asp.net web forms, mvc and web pages won't work. This configuration is for owin stuffs only, for example asp.net web api & odata, signalr, etc. --> </appSettings> <system.web> <compilation debug="true" defaultLanguage="c#" enablePrefetchOptimization="true" optimizeCompilations="true" targetFramework="4.6.2"> <assemblies> <remove assembly="*" /> <!-- To improve app startup performance, our app will continue its work without this compilations, these are required for asp.net web forms, mvc and web pages. --> <add assembly="OwinKatanaTest" /> </assemblies> </compilation> <httpRuntime targetFramework="4.6.2" /> <httpModules> <!-- No need to these modules and handlers, owin handler itself will do everything for us --> <clear /> </httpModules> <httpHandlers> <clear /> </httpHandlers> <sessionState mode="Off" /> </system.web> <system.codedom> <compilers> <compiler language="c#;cs;csharp" extension=".cs" type="Microsoft.CodeDom.Providers.DotNetCompilerPlatform.CSharpCodeProvider, Microsoft.CodeDom.Providers.DotNetCompilerPlatform, Version=1.0.2.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" warningLevel="4" compilerOptions="/langversion:6 /nowarn:1659;1699;1701" /> </compilers> </system.codedom> <runtime> <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1"> <dependentAssembly> <assemblyIdentity name="Microsoft.Owin" publicKeyToken="31bf3856ad364e35" culture="neutral" /> <bindingRedirect oldVersion="0.0.0.0-3.0.1.0" newVersion="3.0.1.0" /> </dependentAssembly> <dependentAssembly> <assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral" /> <bindingRedirect oldVersion="0.0.0.0-9.0.0.0" newVersion="9.0.0.0" /> </dependentAssembly> <dependentAssembly> <assemblyIdentity name="Microsoft.Owin.Security" publicKeyToken="31bf3856ad364e35" culture="neutral" /> <bindingRedirect oldVersion="0.0.0.0-3.0.1.0" newVersion="3.0.1.0" /> </dependentAssembly> <dependentAssembly> <assemblyIdentity name="Microsoft.AspNet.SignalR.Core" publicKeyToken="31bf3856ad364e35" culture="neutral" /> <bindingRedirect oldVersion="0.0.0.0-2.2.1.0" newVersion="2.2.1.0" /> </dependentAssembly> <dependentAssembly> <assemblyIdentity name="System.Reflection" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" /> <bindingRedirect oldVersion="0.0.0.0-4.1.0.0" newVersion="4.1.0.0" /> </dependentAssembly> <dependentAssembly> <assemblyIdentity name="System.Runtime.Extensions" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" /> <bindingRedirect oldVersion="0.0.0.0-4.1.0.0" newVersion="4.1.0.0" /> </dependentAssembly> <dependentAssembly> <assemblyIdentity name="System.Web.Http" publicKeyToken="31bf3856ad364e35" culture="neutral" /> <bindingRedirect oldVersion="0.0.0.0-5.2.3.0" newVersion="5.2.3.0" /> </dependentAssembly> <dependentAssembly> <assemblyIdentity name="System.Net.Http.Formatting" publicKeyToken="31bf3856ad364e35" culture="neutral" /> <bindingRedirect oldVersion="0.0.0.0-5.2.3.0" newVersion="5.2.3.0" /> </dependentAssembly> </assemblyBinding> </runtime> <system.webServer> <validation validateIntegratedModeConfiguration="false" /> <modules runAllManagedModulesForAllRequests="false"> <!-- We're not going to remove all modules, some modules such as static & dynamic compression modules are really cool (-: --> <remove name="RewriteModule" /> <remove name="OutputCache" /> <remove name="Session" /> <remove name="WindowsAuthentication" /> <remove name="FormsAuthentication" /> <remove name="DefaultAuthentication" /> <remove name="RoleManager" /> <remove name="FileAuthorization" /> <remove name="UrlAuthorization" /> <remove name="AnonymousIdentification" /> <remove name="Profile" /> <remove name="UrlMappingsModule" /> <remove name="ServiceModel-4.0" /> <remove name="UrlRoutingModule-4.0" /> <remove name="ScriptModule-4.0" /> <remove name="Isapi" /> <remove name="IsapiFilter" /> <remove name="DigestAuthentication" /> <remove name="WindowsAuthentication" /> <remove name="ServerSideInclude" /> <remove name="DirectoryListing" /> <remove name="DefaultDocument" /> <remove name="CustomError" /> <remove name="Cgi" /> </modules> <defaultDocument> <!-- Default docs will be configured using owin static files middleware --> <files> <clear /> </files> </defaultDocument> <handlers> <!-- Only use this handler for all requests --> <clear /> <add name="Owin" verb="*" path="*" type="Microsoft.Owin.Host.SystemWeb.OwinHttpHandler, Microsoft.Owin.Host.SystemWeb" /> </handlers> <httpProtocol> <customHeaders> <clear /> </customHeaders> </httpProtocol> </system.webServer> </configuration>
بعد از build کردن پروژه، در صورت خطا داشتن از Referencesها، System.Reflection و System.Runtime.Extensions را حذف کنید.
using Microsoft.AspNet.SignalR; using Microsoft.OData; using Microsoft.OData.Edm; using Owin; using OwinKatanaTest.Model; using OwinKatanaTest.ODataControllers; using System.Collections.Generic; using System.Web.Http; using System.Web.OData.Builder; using System.Web.OData.Extensions; using System.Web.OData.Routing.Conventions; namespace OwinKatanaTest { public class OwinAppStartup { public void Configuration(IAppBuilder owinApp) { owinApp.Map("/odata", innerOwinAppForOData => { HttpConfiguration webApiODataConfig = new HttpConfiguration(); webApiODataConfig.IncludeErrorDetailPolicy = IncludeErrorDetailPolicy.Always; webApiODataConfig.Formatters.Clear(); IEnumerable<IODataRoutingConvention> conventions = ODataRoutingConventions.CreateDefault(); ODataModelBuilder modelBuilder = new ODataConventionModelBuilder(webApiODataConfig); modelBuilder.Namespace = modelBuilder.ContainerName = "Test"; var categoriesSetConfig = modelBuilder.EntitySet<Category>("Categories"); var getBestCategoryFunctionConfig = categoriesSetConfig.EntityType.Collection.Function(nameof(CategoriesController.GetBestCategory)); getBestCategoryFunctionConfig.ReturnsFromEntitySet<Category>("Categories"); IEdmModel edmModel = modelBuilder.GetEdmModel(); webApiODataConfig.MapODataServiceRoute("default", "", builder => { builder.AddService(ServiceLifetime.Singleton, sp => conventions); builder.AddService(ServiceLifetime.Singleton, sp => edmModel); }); innerOwinAppForOData.UseWebApi(webApiODataConfig); }); owinApp.Map("/api", innerOwinAppForWebApi => { HttpConfiguration webApiConfig = new HttpConfiguration(); webApiConfig.IncludeErrorDetailPolicy = IncludeErrorDetailPolicy.Always; webApiConfig.MapHttpAttributeRoutes(); webApiConfig.Routes.MapHttpRoute(name: "default", routeTemplate: "{controller}/{action}", defaults: new { action = RouteParameter.Optional }); innerOwinAppForWebApi.UseWebApi(webApiConfig); }); owinApp.Map("/signalr", innerOwinAppForSignalR => { innerOwinAppForSignalR.RunSignalR(new HubConfiguration { EnableDetailedErrors = true }); }); owinApp.UseStaticFiles(); owinApp.Run(async context => { await context.Response.WriteAsync("owin katana"); }); } } }
using OwinKatanaTest.Model; using System.Web.Http; using System.Web.OData; namespace OwinKatanaTest.ODataControllers { public class CategoriesController : ODataController { [HttpGet] public Category GetBestCategory() { return new Category { Id = 1, Name = "Test" }; } } }
using OwinKatanaTest.Model; using System.Collections.Generic; using System.Web.Http; namespace OwinKatanaTest.ApiControllers { public class ProductsController : ApiController { [HttpGet] [Route("products/{categoryId}")] public List<Product> GetProductsByCategoryId(int categoryId) { return new List<Product> { new Product { Id = 1 , Name = "Test" } }; } } }
<package id="Microsoft.Owin.Testing" version="3.0.1" targetFramework="net462" />
در ادامه تست خود را اینگونه مینویسیم
using Microsoft.Owin.Testing; using Microsoft.VisualStudio.TestTools.UnitTesting; using OwinKatanaTest; using System.Net; using System.Net.Http; using System.Threading.Tasks; namespace Test { [TestClass] public class Test { [TestMethod] public async Task TestWebApi() { using (TestServer server = TestServer.Create<OwinAppStartup>()) { HttpResponseMessage apiResponse = await server.HttpClient.GetAsync("/api/products/1"); apiResponse.EnsureSuccessStatusCode(); Assert.AreEqual(HttpStatusCode.OK, apiResponse.StatusCode); } } [TestMethod] public async Task TestOData() { using (TestServer server = TestServer.Create<OwinAppStartup>()) { HttpResponseMessage odataResponse = await server.HttpClient.GetAsync("odata/Categories/Test.GetBestCategory"); odataResponse.EnsureSuccessStatusCode(); Assert.AreEqual(HttpStatusCode.OK, odataResponse.StatusCode); } } } }
برای Update عمل نمی کنه!!!
اطلاعات daylight saving time یا بازه صرفه جویی زمانی ویندوز در دو مدخل رجیستری زیر ثبت میشوند:
[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\TimeZoneInformation] [HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Time Zones\Iran Standard Time]
تصویر سوم مرتبط است به ویندوزهای ویستا به بعد که مفهوم dynamic daylight saving time در آنها معرفی شده است.
در اینجا یک نمونه اطلاعات زمانی ثبت شده مرتبط با ایران را مشاهده میکنید:
Windows Registry Editor Version 5.00 [HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Time Zones\Iran Standard Time] "Display"="(GMT+03:30) Tehran" "Dlt"="Iran Daylight Time" "Std"="Iran Standard Time" "MapID"="-1,72" "Index"=dword:000000a0 "TZI"=hex:2e,ff,ff,ff,00,00,00,00,c4,ff,ff,ff,00,00,09,00,04,00,03,00,17,00,3b,\ 00,3b,00,00,00,00,00,03,00,02,00,03,00,17,00,3b,00,3b,00,00,00
using System.Runtime.InteropServices; namespace TimeZoneInfo.Core { [StructLayout(LayoutKind.Sequential)] public struct TZI { public int Bias; public int StandardBias; public int DaylightBias; public SystemTime StandardDate; public SystemTime DaylightDate; } }
using System; using System.Runtime.InteropServices; namespace TimeZoneInfo.Core { [StructLayoutAttribute(LayoutKind.Sequential)] public struct SystemTime { public short Year; public short Month; public short DayOfWeek; public short Day; public short Hour; public short Minute; public short Second; public short Milliseconds; } }
2C 01 00 00 00 00 00 00 C4 FF FF FF 00 00 0A 00 00 00 05 00 02 00 00 00 00 00 00 00 00 00 04 00 00 00 01 00 02 00 00 00 00 00 00 00
(little-endian) => (big-endian) 2C 01 00 00 => 00 00 01 2C = 300 Bias 00 00 00 00 => 00 00 00 00 = 0 Std Bias C4 FF FF FF => FF FF FF C4 = 4294967236 Dlt Bias ( SYSTEM TIME ) StandardDate 00 00 => 00 00 = Year 0A 00 => 00 0A = Month 00 00 => 00 00 = Day of Week 05 00 => 00 05 = Day 02 00 => 00 02 = Hour 00 00 => 00 00 = Minutes 00 00 => 00 00 = Seconds 00 00 => 00 00 = Milliseconds ( SYSTEM TIME ) DaylightDate 00 00 => 00 00 = Year 04 00 => 00 04 = Month 00 00 => 00 00 = Day of Week 01 00 => 00 01 = Day 02 00 => 00 02 = Hour 00 00 => 00 00 = Minutes 00 00 => 00 00 = Seconds 00 00 => 00 00 = Milliseconds
همچنین Day از یک شروع میشود و DayOfWeek از صفر. Year اگر صفر وارد شود به معنای زمان نسبی است و برای سال بعد نیز میتواند کاربرد داشته باشد (و عموما صفر تعریف شده است).
بنابراین برای تبدیل DateTime به SystemTime سازگار با TZI به فرمول زیر خواهیم رسید:
public static SystemTime ToSystemTime(DateTime time) { var result = new SystemTime { Year = 0, // سال نسبی وارد میشود نه مطلق Month = (short)time.Month, DayOfWeek = (short)time.DayOfWeek, Hour = (short)time.Hour, Minute = (short)time.Minute, Second = (short)time.Second, Milliseconds = (short)time.Millisecond }; int weekdayOfMonth = 1; // شماره هفته است نه شماره روز for (int dd = time.Day; dd > 7; dd -= 7) weekdayOfMonth++; result.Day = (short)weekdayOfMonth; return result; }
using System; using System.Runtime.InteropServices; namespace TimeZoneInfo.Core { public static class ByteUtils { public static Byte[] SerializeByteArray<T>(T msg) where T : struct { int objsize = Marshal.SizeOf(typeof(T)); Byte[] ret = new Byte[objsize]; IntPtr buff = Marshal.AllocHGlobal(objsize); Marshal.StructureToPtr(msg, buff, true); Marshal.Copy(buff, ret, 0, objsize); Marshal.FreeHGlobal(buff); return ret; } } }
پس از اعمال تغییرات فوق، نیاز است یکبار ویندوز را ری استارت کنید.
کش کردن حاصل عملیات در EF Core
Entity Framework (EF) Core is the rearchitected and rewritten version of the Entity Framework object relational mapping engine for .NET Core applications. It is very light-weight, extensible, and cross platform.
However, high transaction .NET Core applications using EF Core face performance and scalability bottlenecks in the database-tier under peak loads. This is because, although you can linearly scale the application tier by adding more application servers, you cannot add more database servers to scale it.
But, if you use a distributed cache like NCache in your .NET Core applications, you can quickly remove these performance and scalability bottlenecks and handle extreme transaction loads.