اشتراکها
اشتراکها
NET 4.0. و یک GAC جدید
اشتراکها
دیتابیسهای شیءگرا
اشتراکها
معرفی سایت dotnet-tricks
نظرات مطالب
سفارشی کردن ASP.NET Identity در MVC 5
خیر. با دات نت 4.5 کامپایل شده.
در مقاله «استفاده از Razor در فایلهای JavaScript و CSS» با نحوهی استفاده از Razor در فایلهای Js و Css آشنا شدید. در مقالهی جاری با روش دیگری، با نحوهی استفاده از Syntax Razor در فایلهای Css آشنا خواهید شد.
در ابتدا بعد از ایجاد یک پروژهی جدید، نیاز دارید تا اسمبلی RazorEngin را توسط Package Manager Console به پروژه اضافه نماید.
در گام بعدی نیاز است در کنترلری، یک اکشن متد را تعریف نماید که خروجی آن از نوع رشته خواهد بود و دستورات زیر در آن تعریف میشوند:
در خط 21، فایل Css موجود در پوشهی Content واقع در ریشهی پروژه، خوانده شده و با متد Parse در کلاس Razor پردازش و بازگشت داده میشود. در کد زیر تمامی متدهای موجود در کلاس Razor را میتوانید ملاحظه کنید:
در این حالت میتوان از دستورات Razor در فایل Css نیز استفاده کرد:
و در انتها میبایست در Layout پروژه، آدرس فایل Css را مشخص کرد:
نکته: در صورتیکه متغیری بعد از دستورات استفاده شده تعریف گردد، با خطای زیر روبرو خواهید شد:
در خروجی نهایی تگ h1 با فونت 100 پیکسل و رنگ قرمز به نمایش در میآید:
در ابتدا بعد از ایجاد یک پروژهی جدید، نیاز دارید تا اسمبلی RazorEngin را توسط Package Manager Console به پروژه اضافه نماید.
Install-Package RazorEngine -Version 3.7.0
در گام بعدی نیاز است در کنترلری، یک اکشن متد را تعریف نماید که خروجی آن از نوع رشته خواهد بود و دستورات زیر در آن تعریف میشوند:
using System.Web.Mvc; using RazorEngine; namespace dynamicCSS.Controllers { public class StyleController : Controller { /// <summary> /// نام متد ارجاعی به فایل سی اس اس /// </summary> /// <returns></returns> public string Index() { //The ContentType property specifies the HTTP content type for the response. If no ContentType is specified, the default is text/HTML. Response.ContentType = "text/css"; //با استفاه از متد //ReadAllText //فایل رو خوانده و سپس از متد //Parse in Razor Class //به صورت رشته برگشت خواهیم داد return Razor.Parse(System.IO.File.ReadAllText(Server.MapPath("/Content/Site.css"))); } } }
#region Assembly RazorEngine.dll, v2.1.4039.23635 // Your Address\dynamicCSS\packages\RazorEngine.2.1\lib\.NetFramework 4.0\RazorEngine.dll #endregion using RazorEngine.Templating; using System; using System.Collections.Generic; namespace RazorEngine { public static class Razor { public static TemplateService DefaultTemplateService { get; } public static IDictionary<string, TemplateService> Services { get; } public static void AddResolver(Func<string, string> resolverDelegate); public static void AddResolver(ITemplateResolver resolver); public static void Compile(string template, string name); public static void Compile(string template, Type modelType, string name); public static void CompileWithAnonymous(string template, string name); public static string Parse(string template, string name = null); public static string Parse<T>(string template, T model, string name = null); public static string Run(string name); public static string Run<T>(T model, string name); public static void SetActivator(Func<Type, ITemplate> activator); public static void SetActivator(IActivator activator); public static void SetTemplateBase(Type type); } }
در این حالت میتوان از دستورات Razor در فایل Css نیز استفاده کرد:
@{ // در اینجا دو متغییر با کلمه کلیدی // var // ساخته و به صورت پیش فرض مقدار دهی نمودیم var redColor = "red"; var sizeMode = "100px"; } h1 { // روش استفاده از متغییرها color: @redColor !important; font-size : @sizeModel !impotant; }
//تغییر ادرس فایل به اکشن متد در کنترلر //Home //<link href="/Content/Site.Css" rel="stylesheet" /> //شکل صحیح آدرس دهی <link href="@Url.Action("Style", "Home")" rel="stylesheet" />
نکته: در صورتیکه متغیری بعد از دستورات استفاده شده تعریف گردد، با خطای زیر روبرو خواهید شد:
در خروجی نهایی تگ h1 با فونت 100 پیکسل و رنگ قرمز به نمایش در میآید:
مطالب
معرفی پروژه Orchard
معرفی پروژه Orchard:
در حال حاضر Orchard بیشتر به عنوان یک سکو (platform) برای ساخت وب سایتهای ایجاد محتوی استفاده میشود آنچه در Orchard حائز اهمیت است ذکر این نکته است که این سیستم به طور کامل با استفاده از ابزارهای متن باز نوشته شده است. Orchard از ASP.NET MVC 3.0 به همراه View engine جدید و فوق العاده آن یعنی Razor بهره میبرد. همچنین این پروژه وابستگی زیادی به دیگر ابزارهای متن باز نظیر NHibernate برای دسترسی به دادهها و همچنین Autofac برای dependency injection دارد شایان ذکر است که مجوز استفاده از Orchard تحت لیسانس BSD است.
طبق اعلام وب سایت رسمی این پروژه در عرض حدود یک سالی که از ارائه این CMS میگذرد بیش از یک میلیون بار دانلود و بیش از 300 ماژول و تم برای آن ساخته شده است که در گالری آن در دسترس میباشد. Orchard به صورت ریلیزهای جزئی ارائه میشود و جدیدترن نسخه آن در هنگام نوشتن این متن 1.5.1 میباشد.
تعداد زیادی سیستمهای مدیریت محتوای تجاری و یا متن باز در طول این سالها با استفاده از دات نت ارائه شده اند. (DotNetNuke (DNN بدون تردید یک از معروفترین و قدرتمندترین آنها است. این CMS در ابتدا با VB.NET نوشته شد و این رویه تا مدتها ادامه داشت تا اینکه در نسخه اخیر به #C تغییر کرد. اگرچه DNN و همچنین پروژه متن باز دیگری به نام Umbraco هر دو محبوب هستند اما با استفاده از WebFormها پیاده سازی شده اند( البته Umbraco در نسخه 5 قصد داشت که از ASP.NET MVC استفاده کند اما علی رغم در دسترس قرار گرفتن این نسخه ظاهرا تیم Umbraco برای تمرکز بیشتر روی نسخه وب فرمی, تصمیم ندارند این پروژه را ادامه دهند.) امروزه وب فرمها همانند گذشته محبوب نیستند به همین دلیل رغبت کمتری برای استفاده از این CMSها نسبت به قبل وجود دارد. با توجه به شواهد موجود بسیاری از برنامه نویسان دات نتی به سمت ASP.NET MVC مهاجرت کرده اند به همین دلیل سیستم Orchard بر مبنای این تکنولوژی نسبتا جدید دات نت پیاده شده است. با استفاده از Orchard میتوان یک وب سایت با عملکرد بسیار بالا بدون نوشتن حتی یک خط کد ایجاد نمود. اما مانند هر سیستم مدیریت محتوی دیگری اگر بخواهیم به آن قابلیت هایی را اضافه کنیم که به صورت پیش فرض در آن نیست باید با ساختار آن به خوبی آشنا شویم و همچنین بر ابزارهای مورد نیاز این کار نیز احاطه داشته باشیم. برای دریافت اطلاعات بیشتر میتوانید به وب سایت رسمی این پروژه در اینجا مراجعه کنید
سیستم مدیریت محتوای Orchard توسط مایکروسافت در ژانویه سال 2011 همراه با ASP.NET MVC 3, IIS Express, SQL CE 4 ,فریم ورک Web Farm و WebMatrix ارائه شد. هدف تمامی این پروژهها ایجاد قابلیتی برای توسعه آسان برنامههای تحت وب در محیط ویندوز بود. همانطور که PHP دارای ابزارهای مناسبی برای این منظور است. با ارائه این ابزارها مایکروسافت درخواست برنامه نویسان را برای ساده سازی تجربه توسعه وب اجابت کرد.
پروژه Orchard متعلق به Outercurve Foundation (به ندرت CodePlex Foundation نیز شناخته میشود) است که توسط مایکروسافت پشتیبانی میشود.
Outercurve Foundation یک سازمان غیر انتفاعی است که هدف آن تشویق و حمایت از پروژههای متنی بازی نظیر Orchad و یا toolkit معروف ASP.NET MVC یعنی MVC Contrib است.
مایکروسافت به صورت رسمی از Orchad پشتیبانی نمیکند اما در حال حاضر برنامه نویسانی را جهت توسعه این سیستم استخدام کرده است.
برای پروژه Orchad سه هدف تعیین شده است :
برای پروژه Orchad سه هدف تعیین شده است :
1)فراهم نمودن و به اشتراک گذاری یک مجموعه کامپوننت جهت استفاده در برنامههای ASP.NET
2)ساخت تعدادی برنامهی مرجع با استفاده از کامپوننتهای فوق
3)ساخت انجمن هایی برای پشتیبانی از این کامپوننتها و یا برنامههای مرجع
در حال حاضر Orchard بیشتر به عنوان یک سکو (platform) برای ساخت وب سایتهای ایجاد محتوی استفاده میشود آنچه در Orchard حائز اهمیت است ذکر این نکته است که این سیستم به طور کامل با استفاده از ابزارهای متن باز نوشته شده است. Orchard از ASP.NET MVC 3.0 به همراه View engine جدید و فوق العاده آن یعنی Razor بهره میبرد. همچنین این پروژه وابستگی زیادی به دیگر ابزارهای متن باز نظیر NHibernate برای دسترسی به دادهها و همچنین Autofac برای dependency injection دارد شایان ذکر است که مجوز استفاده از Orchard تحت لیسانس BSD است.
طبق اعلام وب سایت رسمی این پروژه در عرض حدود یک سالی که از ارائه این CMS میگذرد بیش از یک میلیون بار دانلود و بیش از 300 ماژول و تم برای آن ساخته شده است که در گالری آن در دسترس میباشد. Orchard به صورت ریلیزهای جزئی ارائه میشود و جدیدترن نسخه آن در هنگام نوشتن این متن 1.5.1 میباشد.
اما چرا به یک CMS دات نتی دیگر نیاز است ؟
تعداد زیادی سیستمهای مدیریت محتوای تجاری و یا متن باز در طول این سالها با استفاده از دات نت ارائه شده اند. (DotNetNuke (DNN بدون تردید یک از معروفترین و قدرتمندترین آنها است. این CMS در ابتدا با VB.NET نوشته شد و این رویه تا مدتها ادامه داشت تا اینکه در نسخه اخیر به #C تغییر کرد. اگرچه DNN و همچنین پروژه متن باز دیگری به نام Umbraco هر دو محبوب هستند اما با استفاده از WebFormها پیاده سازی شده اند( البته Umbraco در نسخه 5 قصد داشت که از ASP.NET MVC استفاده کند اما علی رغم در دسترس قرار گرفتن این نسخه ظاهرا تیم Umbraco برای تمرکز بیشتر روی نسخه وب فرمی, تصمیم ندارند این پروژه را ادامه دهند.) امروزه وب فرمها همانند گذشته محبوب نیستند به همین دلیل رغبت کمتری برای استفاده از این CMSها نسبت به قبل وجود دارد. با توجه به شواهد موجود بسیاری از برنامه نویسان دات نتی به سمت ASP.NET MVC مهاجرت کرده اند به همین دلیل سیستم Orchard بر مبنای این تکنولوژی نسبتا جدید دات نت پیاده شده است. با استفاده از Orchard میتوان یک وب سایت با عملکرد بسیار بالا بدون نوشتن حتی یک خط کد ایجاد نمود. اما مانند هر سیستم مدیریت محتوی دیگری اگر بخواهیم به آن قابلیت هایی را اضافه کنیم که به صورت پیش فرض در آن نیست باید با ساختار آن به خوبی آشنا شویم و همچنین بر ابزارهای مورد نیاز این کار نیز احاطه داشته باشیم. برای دریافت اطلاعات بیشتر میتوانید به وب سایت رسمی این پروژه در اینجا مراجعه کنید
مطالب دورهها
آشنایی با AOP Interceptors
در حین استفاده از Interceptors، کار مداخله و تحت نظر قرار دادن قسمتهای مختلف کدها، توسط کامپوننتهای خارجی صورت خواهد گرفت. این کامپوننتهای خارجی، به صورت پویا، تزئین کنندههایی را جهت محصور سازی قسمتهای مختلف کدهای شما تولید میکنند. اینها، بسته به تواناییهایی که دارند، در زمان اجرا و یا حتی در زمان کامپایل نیز قابل تنظیم میباشند.
ابزارهایی جهت تولید AOP Interceptors
متداولترین کامپوننتهای خارجی که جهت تولید AOP Interceptors مورد استفاده قرار میگیرند، همان IOC Containers معروف هستند مانند StructureMap، Ninject، MS Unity و غیره.
سایر ابزارهای تولید AOP Interceptors، از روش تولید Dynamic proxies بهره میگیرند. به این ترتیب مزین کنندههایی پویا، در زمان اجرا، کدهای شما را محصور خواهند کرد. (نمونهای از آنرا شاید در حین کار با ORMهای مختلف دیده باشید).
نگاهی به فرآیند Interception
زمانیکه از یک IOC Container در کدهای خود استفاده میکنید، مراحلی چند رخ خواهند داد:
الف) کد فراخوان، از IOC Container، یک شیء مشخص را درخواست میکند. عموما اینکار با درخواست یک اینترفیس صورت میگیرد؛ هرچند محدودیتی نیز وجود نداشته و امکان درخواست یک کلاس از نوعی مشخص نیز وجود دارد.
ب) در ادامه IOC Container به لیست اشیاء قابل ارائه توسط خود نگاه کرده و در صورت وجود، وهله سازی شیء درخواست شده را انجام و نهایتا شیء مطلوب را بازگشت خواهد داد.
ج) سپس، کد فراخوان، وهله دریافتی را مورد پردازش قرار داده و شروع به استفاده از متدها و خواص آن خواهد نمود.
اکنون با اضافه کردن Interception به این پروسه، چند مرحله دیگر نیز در این بین به آن اضافه خواهند شد:
الف) در اینجا نیز در ابتدا کد فراخوان، درخواست وهلهای را بر اساس اینترفیسی خاص به IOC Container ارائه میدهد.
ب) IOC Container نیز سعی در وهله سازی درخواست رسیده بر اساس تنظیمات اولیه خود میکند.
ج) اما در این حالت IOC Container تشخیص میدهد، نوعی که باید بازگشت دهد، علاوه بر وهله سازی، نیاز به مزین سازی توسط Aspects و پیاده سازی Interceptors را نیز دارد. بنابراین نوع مورد انتظار را در صورت وجود، به یک Dynamic Proxy، بجای بازگشت مستقیم به فراخوان ارائه میدهد.
د) در ادامه Dynamic Proxy، نوع مورد انتظار را توسط Interceptors محصور کرده و به فراخوان بازگشت میدهد.
ه) اکنون فراخوان، در حین استفاده از امکانات شیء وهله سازی شده، به صورت خودکار مراحل مختلف اجرای یک Aspect را که در قسمت قبل بررسی شدند، سبب خواهد شد.
نحوه ایجاد Interceptors
برای ایجاد یک Interceptor دو مرحله باید انجام شود:
الف) پیاده سازی یک اینترفیس
ب) اتصال آن به کدهای اصلی برنامه
در ادامه قصد داریم از یک IOC Container معروف به نام StructureMap در یک برنامه کنسول استفاده کنیم. برای دریافت آن نیاز است دستور پاورشل ذیل را در کنسول نوگت ویژوال استودیو فراخوانی کنید:
پس از آن یک برنامه کنسول جدید را ایجاد کنید. (هدف از استفاده از این نوع پروژه خاص، توضیح جزئیات یک فناوری، بدون درگیر شدن با لایه UI است)
البته باید دقت داشت که برای استفاده از StructureMap نیاز است به خواص پروژه مراجعه و سپس حالت Client profile را به Full profile تغییر داد تا برنامه قابل کامپایل باشد.
اکنون کدهای این برنامه را به نحو فوق تغییر دهید.
در اینجا یک اینترفیس نمونه و پیاده سازی آنرا ملاحظه میکنید. همچنین نحوه آغاز تنظیمات StructureMap و نحوه دریافت یک وهله متناظر با IMyType نیز بیان شدهاند.
نکتهی مهمی که در اینجا باید به آن دقت داشت، وضعیت شیء myType حین فراخوانی متد myType.DoSomething است. شیء myType در اینجا، دقیقا یک وهلهی متداول از کلاس myType است و هیچگونه دخل و تصرفی در نحوه اجرای آن صورت نگرفته است.
خوب! تا اینجای کار را احتمالا پیشتر نیز دیده بودید. در ادامه قصد داریم یک Interceptor را طراحی و مراحل چهارگانه اجرای یک Aspect را در اینجا بررسی کنیم.
در ادامه نیاز خواهیم داشت تا یک Dynamic proxy را نیز مورد استفاده قرار دهیم؛ از این جهت که StructureMap تنها دارای Interceptorهای وهله سازی اطلاعات است و نه Method Interceptor. برای دسترسی به Method Interceptors نیاز به یک Dynamic proxy نیز میباشد. در اینجا از Castle.Core استفاده خواهیم کرد:
برای دریافت آن تنها کافی است دستور پاور شل فوق را در خط فرمان کنسول پاورشل نوگت در VS.NET اجرا کنید.
سپس کلاس ذیل را به پروژه جاری اضافه کنید:
در کلاس فوق کار Method Interception توسط امکانات Castle.Core انجام شده است. این کلاس باید اینترفیس IInterceptor را پیاده سازی کند. در این متد سطر invocation.Proceed دقیقا معادل فراخوانی متد مورد نظر است. مراحل چهارگانه شروع، پایان، خطا و موفقیت نیز توسط try/catch/finally پیاده سازی شدهاند.
اکنون برای معرفی این کلاس به برنامه کافی است سطرهای ذیل را اندکی ویرایش کنیم:
در اینجا تنها سطر EnrichAllWith آن جدید است. ابتدا یک پروکسی پویا تولید شده است. سپس این پروکسی پویا کار دخالت و تحت نظر قرار دادن اجرای متدهای اینترفیس IMyType را عهده دار خواهد شد.
برای مثال اکنون با فراخوانی متد myType.DoSomething، ابتدا کنترل برنامه به پروکسی پویای تشکیل شده توسط Castle.Core منتقل میشود. در اینجا هنوز هم متد DoSomething فراخوانی نشده است. ابتدا وارد بدنه متد public void Intercept خواهیم شد. سپس سطر invocation.Proceed، فراخوانی واقعی متد DoSomething اصلی را انجام میدهد. در ادامه باز هم فرصت داریم تا مراحل موفقیت، خطا یا خروج را لاگ کنیم.
تنها زمانیکه کار متد public void Intercept به پایان میرسد، سطر پس از فراخوانی متد myType.DoSomething اجرا خواهد شد.
در این حالت اگر برنامه را اجرا کنیم، چنین خروجی را نمایش میدهد:
بنابراین در اینجا نحوه دخالت و تحت نظر قرار دادن اجرای متدهای یک کلاس عمومی خاص را ملاحظه میکنید. برای اینکه کنترل کامل را در دست بگیریم، کلاس پروکسی پویا وارد عمل شده و اینجا است که این کلاس پروکسی تصمیم میگیرد چه زمانی باید فراخوانی واقعی متد مورد نظر انجام شود.
برای اینکه فراخوانی قسمت On Error را نیز ملاحظه کنید، یک استثنای عمدی را در متد DoSomething قرار داده و مجددا برنامه را اجرا کنید.
ابزارهایی جهت تولید AOP Interceptors
متداولترین کامپوننتهای خارجی که جهت تولید AOP Interceptors مورد استفاده قرار میگیرند، همان IOC Containers معروف هستند مانند StructureMap، Ninject، MS Unity و غیره.
سایر ابزارهای تولید AOP Interceptors، از روش تولید Dynamic proxies بهره میگیرند. به این ترتیب مزین کنندههایی پویا، در زمان اجرا، کدهای شما را محصور خواهند کرد. (نمونهای از آنرا شاید در حین کار با ORMهای مختلف دیده باشید).
نگاهی به فرآیند Interception
زمانیکه از یک IOC Container در کدهای خود استفاده میکنید، مراحلی چند رخ خواهند داد:
الف) کد فراخوان، از IOC Container، یک شیء مشخص را درخواست میکند. عموما اینکار با درخواست یک اینترفیس صورت میگیرد؛ هرچند محدودیتی نیز وجود نداشته و امکان درخواست یک کلاس از نوعی مشخص نیز وجود دارد.
ب) در ادامه IOC Container به لیست اشیاء قابل ارائه توسط خود نگاه کرده و در صورت وجود، وهله سازی شیء درخواست شده را انجام و نهایتا شیء مطلوب را بازگشت خواهد داد.
ج) سپس، کد فراخوان، وهله دریافتی را مورد پردازش قرار داده و شروع به استفاده از متدها و خواص آن خواهد نمود.
اکنون با اضافه کردن Interception به این پروسه، چند مرحله دیگر نیز در این بین به آن اضافه خواهند شد:
الف) در اینجا نیز در ابتدا کد فراخوان، درخواست وهلهای را بر اساس اینترفیسی خاص به IOC Container ارائه میدهد.
ب) IOC Container نیز سعی در وهله سازی درخواست رسیده بر اساس تنظیمات اولیه خود میکند.
ج) اما در این حالت IOC Container تشخیص میدهد، نوعی که باید بازگشت دهد، علاوه بر وهله سازی، نیاز به مزین سازی توسط Aspects و پیاده سازی Interceptors را نیز دارد. بنابراین نوع مورد انتظار را در صورت وجود، به یک Dynamic Proxy، بجای بازگشت مستقیم به فراخوان ارائه میدهد.
د) در ادامه Dynamic Proxy، نوع مورد انتظار را توسط Interceptors محصور کرده و به فراخوان بازگشت میدهد.
ه) اکنون فراخوان، در حین استفاده از امکانات شیء وهله سازی شده، به صورت خودکار مراحل مختلف اجرای یک Aspect را که در قسمت قبل بررسی شدند، سبب خواهد شد.
نحوه ایجاد Interceptors
برای ایجاد یک Interceptor دو مرحله باید انجام شود:
الف) پیاده سازی یک اینترفیس
ب) اتصال آن به کدهای اصلی برنامه
در ادامه قصد داریم از یک IOC Container معروف به نام StructureMap در یک برنامه کنسول استفاده کنیم. برای دریافت آن نیاز است دستور پاورشل ذیل را در کنسول نوگت ویژوال استودیو فراخوانی کنید:
PM> Install-Package structuremap
البته باید دقت داشت که برای استفاده از StructureMap نیاز است به خواص پروژه مراجعه و سپس حالت Client profile را به Full profile تغییر داد تا برنامه قابل کامپایل باشد.
using System; using StructureMap; namespace AOP00 { public interface IMyType { void DoSomething(string data, int i); } public class MyType : IMyType { public void DoSomething(string data, int i) { Console.WriteLine("DoSomething({0}, {1});", data, i); } } class Program { static void Main(string[] args) { ObjectFactory.Initialize(x => { x.For<IMyType>().Use<MyType>(); }); var myType = ObjectFactory.GetInstance<IMyType>(); myType.DoSomething("Test", 1); } } }
در اینجا یک اینترفیس نمونه و پیاده سازی آنرا ملاحظه میکنید. همچنین نحوه آغاز تنظیمات StructureMap و نحوه دریافت یک وهله متناظر با IMyType نیز بیان شدهاند.
نکتهی مهمی که در اینجا باید به آن دقت داشت، وضعیت شیء myType حین فراخوانی متد myType.DoSomething است. شیء myType در اینجا، دقیقا یک وهلهی متداول از کلاس myType است و هیچگونه دخل و تصرفی در نحوه اجرای آن صورت نگرفته است.
خوب! تا اینجای کار را احتمالا پیشتر نیز دیده بودید. در ادامه قصد داریم یک Interceptor را طراحی و مراحل چهارگانه اجرای یک Aspect را در اینجا بررسی کنیم.
در ادامه نیاز خواهیم داشت تا یک Dynamic proxy را نیز مورد استفاده قرار دهیم؛ از این جهت که StructureMap تنها دارای Interceptorهای وهله سازی اطلاعات است و نه Method Interceptor. برای دسترسی به Method Interceptors نیاز به یک Dynamic proxy نیز میباشد. در اینجا از Castle.Core استفاده خواهیم کرد:
PM> Install-Package Castle.Core
سپس کلاس ذیل را به پروژه جاری اضافه کنید:
using System; using Castle.DynamicProxy; namespace AOP00 { public class LoggingInterceptor : IInterceptor { public void Intercept(IInvocation invocation) { try { Console.WriteLine("Logging On Start."); invocation.Proceed(); //فراخوانی متد اصلی در اینجا صورت میگیرد Console.WriteLine("Logging On Success."); } catch (Exception ex) { Console.WriteLine("Logging On Error."); throw; } finally { Console.WriteLine("Logging On Exit."); } } } }
اکنون برای معرفی این کلاس به برنامه کافی است سطرهای ذیل را اندکی ویرایش کنیم:
static void Main(string[] args) { ObjectFactory.Initialize(x => { var dynamicProxy = new ProxyGenerator(); x.For<IMyType>().Use<MyType>(); x.For<IMyType>().EnrichAllWith(myTypeInterface => dynamicProxy.CreateInterfaceProxyWithTarget(myTypeInterface, new LoggingInterceptor())); }); var myType = ObjectFactory.GetInstance<IMyType>(); myType.DoSomething("Test", 1); }
برای مثال اکنون با فراخوانی متد myType.DoSomething، ابتدا کنترل برنامه به پروکسی پویای تشکیل شده توسط Castle.Core منتقل میشود. در اینجا هنوز هم متد DoSomething فراخوانی نشده است. ابتدا وارد بدنه متد public void Intercept خواهیم شد. سپس سطر invocation.Proceed، فراخوانی واقعی متد DoSomething اصلی را انجام میدهد. در ادامه باز هم فرصت داریم تا مراحل موفقیت، خطا یا خروج را لاگ کنیم.
تنها زمانیکه کار متد public void Intercept به پایان میرسد، سطر پس از فراخوانی متد myType.DoSomething اجرا خواهد شد.
در این حالت اگر برنامه را اجرا کنیم، چنین خروجی را نمایش میدهد:
Logging On Start. DoSomething(Test, 1); Logging On Success. Logging On Exit.
برای اینکه فراخوانی قسمت On Error را نیز ملاحظه کنید، یک استثنای عمدی را در متد DoSomething قرار داده و مجددا برنامه را اجرا کنید.
بخش عمدهای از مهندسی نرم افزار، مربوط به ساخت کامپوننتهایی است که نه تنها به خوبی و مستحکم توسعه داده شدهاند، بلکه قابلیت استفاده دوباره را نیز دارند.
کامپوننتهایی که قادر هستند بر روی دادههای فعلی و همچنین دادههای آینده، کار کنند، قابلیتهای انعطاف پذیری را برای ساخت سیستمهای نرم افزاری بزرگ در اختیار شما قرار خواهند داد.
در زبان هایی نظیر جاوا و سی شارپ، یکی از ابزارهای اصلی برای ساخت کامپوننتهایی با قابلیت استفاده مجدد، "جنریکها" میباشد که امکان ساخت کامپوننتهایی را میدهند که با انواع دادههای متنوعی به جای یک نوع داده، کار میکنند.
برای شروع به تابع زیر توجه کنید:
function identity(arg: number): number { return arg; }
تابع identity هر آنچه را که به عنوان پارامتر به آرگومان آن ارسال کنیم، بازگشت خواهد داد. میتوانید آن را به مانند دستور "echo" در نظر بگیرید.
بدون استفاده از جنریک ها، باید برای هر نوع داده، یک تابع جدید و یا تابعی را به صورت کلی زیر در نظر بگیریم:
function identity(arg: any): any { return arg; }
در تابع بالا از نوع any استفاده شده است. با استفاده از any، قطعا تابع بالا به صورت عمومی خواهد بود و تمام نوع دادهها را به عنوان آرگومان خواهد پذیرفت. ولی در واقع ما اطلاعات مربوط به اینکه نوع داده بازگشتی توسط تابع چه چیزی است را از دست خواهیم داد.
برای مثال اگر یک عدد را به آن ارسال کنیم، تنها متوجه خواهیم شد که نوع آن any میباشد؛ بنابراین به روشی نیاز داریم تا بتوانیم نوع داده آرگومانهای تابع مورد نظر را کنترل کنیم.
در پیاده سازی زیر، ما از یک type variable خاصی استفاده خواهیم کرد که به جای مقادیر برای انوع دادهها مورد استفاده قرار میگیرد.
function identity<T>(arg: T): T { return arg; }
در تابع بالا با از T به عنوان یک type variable استفاده کردهایم که امکان گرفتن انواع دادههایی را (برای مثال number) که توسط کاربر مهیا میشود، به ما خواهد داد.
این پیاده سازی از تابع identity، تحت عنوان تابع جنریک مطرح میشود که برای دامنهی عظیمی از انواع دادهها میتواند مورد استفاده قرار گیرد و بر خلاف پیاده سازی قبل که از any استفاده کردهایم، در این حالت دیگر اطلاعات نوع داده را از دست نخواهیم داد.
برای استفاده از تابع فوق ما دو روش را پیش رو خواهیم داشت:
- ارسال تمام آرگومانها که شامل آرگومان نوع داده هم میباشد
let output = identity<string>("myString"); // type of output will be 'string'
در کد بالا ما به صراحت T را با نوع داده string با استفاده از < > مقدار دهی کردهایم.
- روش دوم که شاید استفاده رایج از توابع جنریک هم هست، استفاده از امکان type argument inference میباشد.
let output = identity("myString"); // type of output will be 'string'
در کد بالا اینبار به صورت صریح نوع T را مشخص نکردهایم و کامپایلر باتوجه به "myString"، نوع T را تعیین خواهد کرد. درحالیکه استفاده از امکان type argument inference خیلی مفید میباشد و کد را خیلی کم حجم و خوانا در اختیار ما قرار میدهد، ولی در مثالهای پیچیده، امکان این وجود دارد که کامپایلر در تشخیص نوع داده، با خطا مواجه شود. در این صورت استفاده از روش اول مفید خواهد بود.
در ادامه اگر قصد لاگ کردن Length مربوط به آرگومان arg را در هر بار فراخوانی تابع داشته باشیم، میبایستی به شکل زیر عمل کنیم:
function loggingIdentity<T>(arg: T): T { console.log(arg.length); // Error: T doesn't have .length return arg; }
همانطور که انتظار داشتیم، کامپایلر خطایی مبنی بر نداشتن عضوی تحت عنوان length برای آرگومان arg را نمایش خواهد داد. همانطور که قبلا نیز اشاره کردیم، T جانشینی برای تمام نوع دادهها خواهد بود؛ بنابراین در اینجا میتوانیم یک دادهی از نوع number را که عضوی بنام length ندارد، هم به این تابع پاس دهیم.
حال بیایید بگوییم که ما قصد داریم این تابع، با آرایه ای از T کار کند. در این صورت اگر با آرایهها کار کنیم، عضوی به نام length را خواهیم داشت. به پیاده سازی زیر توجه کنید:
function loggingIdentity<T>(arg: T[]): T[] { console.log(arg.length); // Array has a .length, so no more error return arg; }
کد بالا را میتوانیم به این شکل تفسیر کنیم: تابع جنریک loggingIdentity یک type parameter را تحت عنوان T و یک آرگومان را تحت عنوان arg که آرایه ای از T هست، گرفته و آرایهای از T را بازگشت خواهد داد. اگر ما آرایهای از number را به آن پاس دهیم، آرایهای از numberها را بازگشت خواهد داد.
در این حالت استفاده از T به عنوان type variable که بخشی از نوع دادههایی است که ما با آنها کار میکنیم، به جای پشتیبانی از تمام نوع دادهها، انعطاف پذیری بالایی را به ما خواهد داد.
حتی میتوانیم این مثال را به شکل زیر نیز پیاده سازی کنیم:
function loggingIdentity<T>(arg: Array<T>): Array<T> { console.log(arg.length); // Array has a .length, so no more error return arg; }
پیاده سازی بالا خیلی شبیه به پیاده سازی در سایر زبانها هم میباشد.
Generic Types
در این قسمت ما به دنبال یافتن نوع خود توابع بوده و سعی خواهیم کرد اینترفیسهای جنریک را هم پیاده سازی کنیم. نوع توابع جنریک هم بمانند توابع غیر جنریک میباشند؛ به طوری که میتوان لیستی از type parameters هایی را که در حالت function declarations موجود هستند، در ابتدا بنویسیم.
function identity<T>(arg: T): T { return arg; } let myIdentity: <T>(arg: T) => T = identity;
حتی میتوانیم نام متفاوتی را هم برای type parameter در نظر بگیرم:
function identity<T>(arg: T): T { return arg; } let myIdentity: <U>(arg: U) => U = identity;
یا حتی میتوانیم به مانند امضای یک object literal هم کد بالا را بازنویسی کنیم:
function identity<T>(arg: T): T { return arg; } let myIdentity: {<T>(arg: T): T} = identity;
حال میتوانیم این object literal را به یک اینترفیس منتقل کنیم:
interface GenericIdentityFn { <T>(arg: T): T; } function identity<T>(arg: T): T { return arg; } let myIdentity: GenericIdentityFn = identity;
کد بالا خوانایی بالاتری را نسبت به حالت قبل دارد و با تعریف یک اینترفیس به نام GenericIdentityFn و انتقال object literal به داخل آن، میتوانیم از نام اینترفیس به جای استفاده مستقیم از object literal، بهره ببریم.
حتی میتوانیم type parameter تابع جنریک خود را هم به اینترفیس منتقل کنیم.
interface GenericIdentityFn<T> { (arg: T): T; } function identity<T>(arg: T): T { return arg; } let myIdentity: GenericIdentityFn<number> = identity;
باید توجه داشت که پیاده سازی ما کمی متفاوتتر از قبل شده است.الان type parameter ما برای کل اعضای اینترفیس قابل رویت میباشد.فهم این مورد که چه زمانی type parameter را در امضای نامیدن داخل اینترفیس یا بر روی خود اینترفیس استفاده کنیم، خود میتوانید برای شرح اینکه کدام وجههای یک نوع داده جنریک هستند، مفید باشد.
نکته : امکان تعریف enumها و namespaceهای جنریک وجود ندارد.
Generic Classes
تعریف کلاسهای جنریک هم به مانند اینترفیسهای جنریک میباشد. به مثال زیر توجه کنید:
class GenericNumber<T> { zeroValue: T; add: (x: T, y: T) => T; } let myGenericNumber = new GenericNumber<number>(); myGenericNumber.zeroValue = 0; myGenericNumber.add = function(x, y) { return x + y; };
در کد بالا، استفادهای واقعی از کلاس GenericNumber قابل مشاهده است. شاید متوجه شده باشید که هیچ محدودیتی برای استفادهی نوعها برای مثال تنها از نوع number در آن نیست و میتوانید از نوع string هم به شکل زیر استفاده کنید:
let stringNumeric = new GenericNumber<string>(); stringNumeric.zeroValue = ""; stringNumeric.add = function(x, y) { return x + y; }; alert(stringNumeric.add(stringNumeric.zeroValue, "test"));
نکته : برای اعضای استاتیک کلاس نمیتوانید از type parameter کلاس استفاده کنید.
Generic Constraints
اگر مثال اخیر را به یاد داشته باشید، شاید بعضی اوقات لازم باشد که یک تابع جنریک را تعریف کنیم تا تنها با مجموعهای از نوع دادهها کار کند که اتفاقا از امکانات این مجموعه، آگاهی داریم. در همان مثال loggingIdentity، ما نیاز داشتیم تا به خصوصیت length آرگومان arg دسترسی داشته باشیم و کامپایلر در همان ابتدا، به دلیل اینکه همه نوع دادهها از این خصوصیت برخوردار نیستند، خطایی را به ما نشان میدهد.
در ادامه تابعی را پیاده سازی میکنیم که جوابگوی تمام نوع دادهها بوده، به شرطی که حداقل خصوصیت length را داشته باشند. لذا باید نیاز خود را در قالب یک محدودیت بر آنچه که T میتواند انجام دهد، فهرست کنیم.
interface Lengthwise { length: number; } function loggingIdentity<T extends Lengthwise>(arg: T): T { console.log(arg.length); // Now we know it has a .length property, so no more error return arg; }
در کد بالا برای توصیف محدودیت خود از یک اینترفیس به نام Lengthwise استفاده کردهایم که فقط یه خصوصیت length را دارد و با استفاده از آن و کلمهی کلیدی extends، محدودیت خود را اعمال کرده ایم.
استفاده از تابع بالا:
loggingIdentity(3); // Error, number doesn't have a .length property
چون تابع جنریک ما الان محدود میباشد و با تمام نوع دادهها کار نخواهد کرد، با خطای بالا روبرو خواهیم شد.
loggingIdentity({length: 10, value: 3});
در عوض مثال بالا، محدودیت ما را به همراه دارد (داشتن خصوصیت length) و بدون هیچ خطایی جواب خواهیم گرفت.
استفاده از Type Parameterها در تعریف محدودیت
در برخی از سناریوها شاید نیاز باشد که یکی از type parameterها توسط دیگری محدود شده باشد. به مثال زیر توجه کنید:
function find<T, U extends Findable<T>>(n: T, s: U) { // errors because type parameter used in constraint // ... } find (giraffe, myAnimals);
همانطور که مشخص است، کامپایلر ما را با نشان دادن خطایی متوقف خواهد کرد. چون اجازهی استفاده از type parameter را در اعمال محدودیت، نداریم. در عوض میشود به شکل زیر عمل کرد:
function find<T>(n: T, s: Findable<T>) { // ... } find(giraffe, myAnimals);
این بار آرگومان s ما باید از نوع <Findable<T باشد که باز هم توانستهایم محدودیت خود را توسط یک type parameter بر آن یکی اعمال کنیم.
نکته : دو پیاده سازی بالا اصلا یکسان نیستند؛ نوع بازگشی در تابع اول میبایستی از نوع U میبود، ولی در پیاده سازی دوم اینگونه نیست.(در صورت نبودن خطا)
استفاده از کلاسها در جنریکها
زمانی که قصد دارید با استفاده از جنریکها، factoryها را پیاده سازی کنید، باید با استفاده از سازندهی کلاسها، به آنها اشاره کنید. به مثال زیر توجه کنید:
function create<T>(c: {new(): T; }): T { return new c(); }
تابع بالا به عنوان یک object factory میتواند مورد استفاده قرار بگیرد و نکته آن در تعریف نوع آرگومان c میباشد که باز هم به صورت object literal معرفی شده است. اگر در قسمتهای بالا به یاد داشته باشید، میتوان این مورد را هم داخل یک اینترفیس گنجاند.
به عنوان یک مثال پیشرفتهتر هم میتوان به استفاده از prototype property برای استنتاج type parameterها و تحمیل کردن ارتباط بین تابع سازنده و وهله کلاسها، اشاره کرد. به مثال زیر توجه کنید:
class BeeKeeper { hasMask: boolean; } class ZooKeeper { nametag: string; } class Animal { numLegs: number; } class Bee extends Animal { keeper: BeeKeeper; } class Lion extends Animal { keeper: ZooKeeper; } function findKeeper<A extends Animal, K> (a: {new(): A; prototype: {keeper: K}}): K { return a.prototype.keeper; }
در کد بالا از دو کلاس BeeKeeper و ZooKeeper برای نوع بازگشتی متدهای موجود در کلاسهای Bee و Lion استفاده شدهاست. کلاس Animal به عنوان کلاس پایه دو کلاس Bee و Lion که یک خصوصیت numLegs دارد، تعریف شدهاست. از تابع جنریک findKeeper برای مشخص کردن نگهبان مرتبط با Animal ای که به عنوان type parameter توسط A مشخص میشود، استفاده میگردد. محدودیتی که بر روی A اعمال شده است نشان دهندهی این است که نوع دادهی مورد نظر باید حتما یک Animal باشد و همچنین با اعمال محدودیتی که در قالب object literal مشخص است، تعیین شده است که نوع مورد نظر باید یک کلاس باشد و در نهایت با استفاده از prototype مشخص کردهایم که متدی به نام Keeper آن کلاس، باید نوع برگشتی از نوع K را که به عنوان type parameter مطرح شدهی در امضای تابع است، دارا باشد. K نشان دهنده نوع داده بازگشتی این تابع جنریک نیز میباشد.
استفاده از تابع بالا:
findKeeper(Lion).nametag; // typechecks!
بله همانطور که مشخص است، type parameterهای مورد نظر به اصطلاح infer شدهاند و خصوصیت nametag نشان از این دارد که ZooKeeper به صورت خودکار به عنوان نوع داده K تشخیص داده شده است.