name: ASP.NET Core CI on: [push] jobs: build_and_test: runs-on: $ strategy: matrix: os: [macOS-latest, ubuntu-latest, windows-latest] steps: - name: Setup .NET Core uses: actions/setup-dotnet@v1.2.0 with: dotnet-version: 3.0.100 if: matrix.os == 'macOS-latest' || matrix.os == 'ubuntu-latest' - uses: actions/checkout@v1 - name: Build with dotnet run: dotnet build ./src/Solution.sln --configuration Release - name: Test with dotnet run: dotnet test ./src/Solution.sln --configuration Release
در نگارش 2.1 میتوان فایلهای razor (هم صفحات Razor و هم Viewهای Razor) را به همراه کنترلرها و مدلهای آنها داخل class libraries مجزا قرار داد و استفاده کرد. استفاده کننده فقط کافی است ارجاعی را به این کتابخانهها اضافه کند تا امکانات آنها قابل استفاده شوند.
فعالسازی این قابلیت در یک class library نیاز به تغییرات ذیل را در یک فایل csproj دارد (مشخص کردن sdk، تعیین کامپایل شدن viewها و صفحاتی که باید الحاق شوند):
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <TargetFramework>netstandard2.0</TargetFramework> <ResolvedRazorCompileToolset>RazorSdk</ResolvedRazorCompileToolset> <RazorCompileOnBuild>true</RazorCompileOnBuild> <IncludeContentInPack>false</IncludeContentInPack> </PropertyGroup> <ItemGroup> <Content Include="Pages\**\*.cshtml" /> </ItemGroup> <ItemGroup> <PackageReference Include="Microsoft.AspNetCore.Mvc" Version="2.1.0-preview1-final" /> </ItemGroup> </Project>
یک نکتهی تکمیلی
اگر برنامههای هاست کنندهی این پلاگینها، دقیقا در مسیرهای متناظری صفحات و یا Viewهای Razor را قرار دهد، میتواند این صفحات را بازنویسی کند.
انواع ارجاعی
قبلا در مورد مقادیر ارجاعی صحبت کردیم. در اینجا نیز به این موضوع اشاره میکنیم که هر مقدار ارجاعی، نمونهای ایجاد شده از یک نوع ارجاعی میباشد. انواع ارجاعی در واقع ساختارهایی هستند که جهت گروه بندی دادهها و عملکرد بین آنها استفاده میشوند. در سایر زبانهای برنامه نویسی شیء گرا، به انواع ارجاعی، کلاس و به مقادیر ارجاعی، شیء میگویند. در جاوا اسکریپت نیز، به مقادیر ارجاعی و یا نمونههای ایجاد شده از انواع ارجاعی، شیء میگویند. به انواع ارجاعی، توصیف گر شیء نیز میگویند؛ زیرا ویژگیها و متدهای آن شیء را معرفی و توصیف مینماید.
نحوهی ایجاد شیء از نوع ارجاعی Object
از آنجاییکه نوع ارجاعی Object هیچ ویژگی و متد خاصی ندارد، متداولترین نوع ارجاعی جهت ایجاد اشیاء سفارشی میباشد. به دو روش میتوان نمونهای را از یک Object ایجاد نمود. روش اول استفاده از عملگر new و بصورت زیر میباشد:
var person = new Object (); person . name = "Meysam" ; person . age = 32 ;
با استفاده از عملگر new، شیء person را
از نوع Object
ایجاد نمودیم که شامل دو ویژگی (Property) به
نامهای name و age میباشد. در واقع شیء person
ساختاری را جهت نگهداری اطلاعات یک شخص معرفی میکند. این عمل موجب جلوگیری از
پراکندگی تعریف متغیرها و گروه بندی آنها در قالب یک شیء میشود. روش دوم استفاده
از Object Literal Notation یا
نماد تحت الفظی شیء و بصورت زیر میباشد:
var person = { name : "Meysam" , age : 32 };
Object Literal Notation ،
دستور میانبری برای ایجاد یک شیء از نوع Object میباشد. در مثال فوق هم، همانند
روش اول، شیء person را
با دو ویژگی name و age ایجاد
نمودهایم. در این روش، نام ویژگیها میتوانند به
صورت رشتهای و عددی نیز به یک شیء اختصاص یابد.
var person = { "name" : "Meysam" , "age" : 32 };
معمولا از دو روش فوق زمانی استفاده میشود که میخواهیم اشیایی را ایجاد نماییم که ویژگیهای آنها فقط خواندنی باشند. با استفاده
از روش دوم، حتی میتوان یک شیء خالی را ایجاد نمود که در ابتدا هیچ ویژگی ای
ندارد و در مراحل بعد، ویژگیهایی را به آن
اضافه نمود.
var person = {}; // var person = new Object(); person . name = "Meysam" ; person . age = 32 ;
در مثال فوق شیء person بدون ویژگیها تعریف شده است؛ سپس به آن ویژگیهایی را اضافه نمودهایم.
استفاده از روش Object Literal Notation ، یکی از روشهای محبوب برنامه نویسان جاوا اسکریپت میباشد. زیرا با کمترین کد و بصورت بصری، شیء ای را ایجاد نموده و مثلا به یک متد ارسال مینمایند. به مثال زیر توجه کنید:
function displayInfo ( arg ) { var output = "" ; if ( arg . name != undefined ) output += "Name: " + arg . name + "\n" ; if ( arg . age != undefined ) output += "Age: " + arg . age + "\n" ; return output ; } alert (displayInfo ({ name : "Meysam" , age : 32 })); alert (displayInfo ({ name : "Sohrab" }));
در این مثال یک تابع تعریف شده است که آرگومان
ورودی آن یک شیء میباشد. در تابع بررسی میشود که اگر ویژگی name و یا age
برای این آرگومان تعریف شده بود، خروجی تابع را تولید نماید. در واقع این ویژگیها اختیاری میباشند و میتوانند ارسال نگردند. در زمان فراخوانی تابع نیز شیء ای را
بصورت Object Literal Notation
ایجاد نموده و به تابع ارسال کردیم.
این الگو برای ارسال آرگومان، زمانی استفاده میشود که تعداد زیادی آرگومان اختیاری داریم و میخواهیم به تابع ارسال نماییم. معمولا کار با آرگومانهای نامی (Named Arguments) راحتتر است ولی زمانیکه تعداد آرگومانهای اختیاری زیاد باشند، مدیریت و نگهداری آنها سخت و طاقت فرسا میگردد و ظاهر زشتی را به تابع میدهد. بهترین راهکار جهت مدیریت چنین شرایطی این است که آرگومانهای اجباری را به صورت آرگومانهای نامی تعریف کنیم و آرگومانهای اختیاری را به صورت یک شیء به تابع ارسال کنیم.
نکتهی دیگری که باید به آن توجه نمود این است که جهت دسترسی به ویژگیهای یک شیء از (.) استفاده میشود. همچنین میتوان به یک ویژگی با استفاده از [] و بصورت یک آرایه دسترسی داشت که در این صورت نام ویژگی بصورت رشتهای در [] ذکر خواهد شد.
alert ( person . name ); alert ( person [ "name" ]);
در عمل تفاوتی بین دو مورد فوق وجود ندارد. مهمترین
مزیت استفاده از [] این است که میتوانید توسط یک متغیر به ویژگیهای یک شیء دسترسی
داشته باشید. همچنین اگر نام ویژگی شامل کاراکترهایی باشد که خطای گرامری رخ میدهد یا از اسامی رزرو شده استفاده کرده باشید، میتوانید از [] جهت دسترسی به
ویژگی استفاده نمایید.
var propertyName = "name" ; alert ( person [ propertyName ]); alert ( person [ "first name" ]);
در دستور alert اول، با استفاده از یک متغیر به ویژگی name از شیء person دسترسی پیدا نمودیم. در دستور آخر نیز، به دلیل وجود space در نام ویژگی، مجبور هستیم جهت دسترسی به ویژگی first name از [] استفاده نماییم.
بررسی نوع ارجاعی Function
توابع در واقع یک شیء و نمونهای از نوع ارجاعی Function میباشند که میتوانند همانند سایر اشیاء ویژگیها و متدهای خاص خود را داشته باشند. بنابراین میتوان در یک عبارت، تابعی را به یک شیء نسبت داد. به مثال زیر توجه کنید:
var sum = function ( a , b ) { return a + b ; }; alert ( sum ( 10 , 20 ));
خروجی :
30
شیء sum را تعریف نموده و یک تابع را به آن اختصاص دادیم که شامل دو آرگومان ورودی میباشد. حال میتوان با شیء sum همانند یک تابع رفتار نمود و تابع مورد نظر را فراخوانی کرد. توجه داشته باشید که هیچ نامی را در زمان تعریف تابع به آن اختصاص ندادهایم. به این شکل تعریف تابع، Function Expression میگویند.
همانند سایر اشیاء، نام تابع نیز اشارهگری به تابع میباشد. بنابراین میتوان توابع را نیز به یکدیگر نسبت داد. به مثال زیر توجه کنید:
function sum ( a , b ) { return a + b ; } alert ( sum ( 10 , 10 )); var anotherSum = sum ; alert ( anotherSum ( 10 , 10 )); sum = null ; alert ( anotherSum ( 10 , 10 )); alert ( sum ( 10 , 10 )); // Error: Object is not a function;
خروجی :
20
20
20
Error: Object is not a function
ابتدا تابعی را به نام sum ایجاد نمودیم که دو عدد را با هم جمع میکند. دقت داشته باشید که به این شکل تعریف تابع sum ، اعلان تابع یا Function Declaration میگویند. سپس متغیری را به نام anotherSum ، تعریف نموده و sum را به آن نسبت دادیم. توجه داشته باشید که در زمان انتساب یک تابع به یک متغیر نباید () را ذکر کنیم، زیرا ذکر () به منزلهی فراخوانی تابع و اختصاص خروجی آن به متغیر میباشد و نه انتساب اشاره گر تابع به آن متغیر. فراخوانی sum و anotherSum خروجی یکسانی را دارند؛ زیرا هر دو به یک تابع اشاره میکنند. در خطوط بعدی، شیء sum را با مقدار null تنظیم نمودیم. در واقع با این کار اشارهگر sum برابر null شده است؛ یعنی دیگر به هیچ تابعی اشاره نمیکند. ولی تابع همچنان در حافظه وجود دارد؛ زیرا اشارهگر دیگری به نام anotherSum در حال اشاره نمودن به آن میباشد. در مرحلهی بعدی که sum را فراخوانی نمودیم، به دلیل null بودن آن، خطا رخ خواهد داد.
بازنگری مجدد به مبحث Overloading
در اینجا فقط میخواهیم اشارهای کنیم به مبحث Overloading که قبلا در مورد آن بحث کردیم تا دلیل فنی عدم وجود Overloading را در جاوا اسکریپت درک کنیم. همانطور که قبلا بیان شد، نام تابع در واقع اشاره گری به تابع میباشد؛ بنابراین تعریف دو تابع هم نام، همانند اختصاص مجدد مقدار یا تغییر مقدار یک متغیر میباشد. به مثال زیر توجه کنید:
function calc ( num1 , num2 ) { return num1 + num2 ; } function calc ( num1 , num2 ) { return num1 - num2 ; }
همانطور که پیشتر نیز عنوان شد، تابع دوم جایگزین تابع اول میگردد. در واقع تعریف فوق همانند تعریف زیر میباشد:
var calc = function ( num1 , num2 ) { return num1 + num2 ; }; calc = function ( num1 , num2 ) { return num1 - num2 ; };
همانطور که میبینید، ابتدا متغیری به نام calc تعریف شدهاست و با یک تابع مقداردهی اولیه شده است. سپس با تابع دوم مقداردهی مجدد گردیده است و دیگر به تابع قبلی اشاره نمیکند. بنابراین همیشه تابع آخر جایگزین توابع قبلی میگردد.
Function Declaration در مقابل Function Expression
این دو روش تعریف و استفاده از توابع تقریبا مشابه هم میباشند و فقط یک تفاوت اصلی بین آنها وجود دارد و آن به نحوهی رفتار موتور پردازشی جاوا اسکریپت بر میگردد. Function Declaration قبل از اینکه کدهای جاوا اسکریپت خوانده و اجرا شوند، خوانده شده و در دسترس خواهند بود؛ اما Function Expression تا زمانی که روال اجرای کد به این خط از کد نرسد، اجرا نخواهد شد و در دسترس نخواهد بود. به مثال Function Declaration زیر توجه کنید:
alert ( sum ( 10 , 20 )); function sum ( a , b ) { return a + b ; }
خروجی :
30
قبل از اعلان تابع sum ، این تابع فراخوانی شده است؛ ولی هیچ خطایی رخ نمیدهد. زیرا قبل از اجرای دستورات، تابع sum خوانده و در دسترس خواهد بود. اما اگر تابع فوق بصورت Function Expression تعریف شده بود، خطا رخ میداد و برنامه اجرا نمیشد.
alert ( sum ( 10 , 20 )); // Error: undefined is not a function var sum = function ( a , b ) { return a + b ; };
خروجی :
Error: undefined is not a function
همانطور که میبینید، در خط اول برنامه، خطای اجرایی رخ داده است. زیرا هنوز روال اجرایی برنامه به خط تعریف تابع sum نرسیدهاست. بنابراین تابع sum در دسترس نخواهد بود و فراخوانی آن موجب بروز خطا میگردد.
ارسال تابع به عنوان ورودی یا خروجی توابع دیگر
همانطور که قبلا بیان شد، نام تابع در واقع یک متغیر میباشد که به تابع مورد نظر اشاره میکند. بنابراین میتوان همانند یک متغیر با آن رفتار نموده و به عنوان آرگومان ورودی و یا مقدار خروجی یک تابع آن را ارسال نمود. به مثال زیر توجه کنید:
function add ( a , b ) { return a + b ; } function mult ( a , b ) { return a * b ; } function calc ( a , b , fn ) { return a * b + fn ( a , b ); } alert ( calc ( 10 , 20 , add )); alert ( calc ( 10 , 20 , mult ));
خروجی :
230
400
دو تابع به نامهای add و mult با دو آرگومان ورودی تعریف شدهاند که به ترتیب حاصل جمع و حاصل ضرب دو آرگومان را بر میگردانند. تابع calc نیز با 3 آرگومان ورودی تعریف شدهاست که قصد داریم برای آرگومان سوم یک تابع را ارسال نماییم. تابع calc در 2 مرحله فراخوانی شدهاست که در یک مرحله تابع add و در مرحلهی دیگر تابع mult به عنوان آرگومان ورودی ارسال شدهاند. همانطور که از قبل میدانید، نام تابع اشارهگری به خود تابع میباشد. در تابع calc نیز با فراخوانی آرگومان fn در واقع داریم تابع ارسالی را فراخوانی مینماییم. حال به مثال زیر توجه کنید که یک تابع را به عنوان خروجی بر میگرداند:
function createFunction ( a , b ) { return function ( c ) { var d = ( a + b ) * c ; return d ; }; } var fn = createFunction ( 10 , 20 ); alert ( fn ( 30 ));
خروجی :
900
تابع createFunction دارای 2 آرگومان ورودی میباشد و یک تابع را با 1 آرگومان ورودی بر میگرداند. در ابتدا تابع createFunction را با آرگومانهای 10 و 20 فراخوانی نمودیم. خروجی این تابع که خود یک تابع با یک آرگومان ورودی میباشد، به عنوان خروجی برگردانده شده و در متغیر fn قرار میگیرد. سپس تابع fn با آرگومان ورودی 30 فراخوانی میگردد که مقادیر 10 و 20 را با هم جمع نموده و در 30 ضرب مینماید.
پروژه DNTFrameworkCore که قصد پشتیبانی از آن را دارم، یک زیرساخت سبک وزن و توسعه پذیر با پشتیبانی از طراحی چند مستاجری، با تمرکز بر کاهش زمان و افزایش کیفیت توسعه سیستمهای تحت وب مبتنی بر ASP.NET Core، توسعه داده شده است.
اهدافی که این زیرساخت دنبال میکند
- ارائه ساختارهای مشترک بین پروژههای مختلف از جمله Cross-Cutting Concernها و ...
- دنبال کردن اصل DRY به منظور متمرکز شدن صرف برروی منطق تجاری سیستم نه انجام و حل یکسری مسائل تکراری
- کاهش زمان توسعه و اختصاص زمان بیشتر برای نوشتن آزمونهای واحد منطق تجاری
- کاهش باگ و جلوگیری از پخش شدن باگهای پیاده سازی در سراسر سیستم
- کاهش زمان آموزش نیروهای جدید برای ملحق شدن به تیم تولید شما با حداقل دانش طراحی و برنامه نویسی شیء گرا
- ارائه راهکاری یکپارچه برای توسعه پذیر بودن منطق تجاری پیاده سازی شده از طریق در معرض دید قرار دادن یکسری «Extensibility Point» با استفاده از رویکرد Event-Driven
امکانات این زیرساخت در زمان نگارش مطلب جاری
- مکانیزم اعتبارسنجی خودکار ورودیهای متدهای مرتبط با منطق تجاری
- مکانیزم به روز رسانی یک AggregateRoot، به همراه موجودیتهای وابسته به آن (سناریوهای Master-Detail)
- مکانیزم Eventing، به منظور آگاهی از تغییرات و رخدادهای یک AggregateRoot خاص
- مکانیزم مدیریت خودکار تراکنشها
- مکانیزم شماره گذاری و تولید خودکار کد منحصر به فرد برای موجودیتهای سیستم
- اعمال مفاهیم Functional Programming برای مدیریت خطاهای قابل پیش بینی و همچنین مواجهه با حالتهای شکست متدها
- مکانیزم اعمال دسترسیهای پویا
- پیاده سازی سرویس CRUD پایه، مبتنی بر EF Core
- پیاده سازی کنترلر CRUD پایه، مبتنی بر ASP.NET Core Web API
- پروایدر Logging به منظور ذخیره سازی لاگ سیستم در بانک اطلاعاتی، با استفاده از EF Core
- مکانیزم Auditing به منظور ذخیره اطلاعات آماری از وضعیت انجام شدن متدها به همراه ورودی ها، خروجی و همچنین موفقیت یا عدم موفقیت آنها در بانک اطلاعاتی با پروایدر مبتنی بر EF Core
- مکانیزم ذخیره سازی کلیدهای موقتی تولید شده Data Protecction API در بانک اطلاعاتی با استفاده از EF Core
- مکانیزم Configuration به منظور ذخیره سازی و خواندن تنظیمات مبتنی بر Name-Value در بانک اطلاعاتی با استفاده از EF Core
- مکانیزم Hooks به منظور توسعه پذیر کردن DbContext مبتنی EF Core به همراه تعدادی Hook پیش فرض تعریف شده در زیرساخت
- مکانیزم ردیابی تغییرات
- امکان طراحی چند مستاجری به همراه مکانیزم فیلتر خودکار اطلاعات با امکان غیرفعال کردن آن مبتنی بر EF Core
- مکانیزم حذف نرم به همراه فیلتر خودکار اطلاعات حذف شده با امکان غیرفعال کردن آن مبتنی بر EF Core
- بسته نیوگت DNTFrameworkCore.FluentValidation به عنوان Adapter کتابخانه FluentValidation با مکانیزم اعتبارسنجی خودکار ورودیهای متدها
- کتابخانه DNTFrameworkCore.Cqrs به عنوان ابزار کمکی برای اعمال الگوی معماری CQRS (به زودی)
- امکان انجام کارهای طولانی در پس زمینه
- لاگ تغییرات موجودیتها یا Entity History (به زودی)
نحوه استفاده از بستههای نیوگت مرتبط
PM> Install-Package DNTFrameworkCore -Version 1.0.0
services.AddDNTFramework() .AddDataAnnotationValidation() .AddModelValidation() .AddValidationOptions(options => { /*options.IgnoredTypes.Add(typeof());*/ }) .AddMemoryCache() .AddAuditingOptions(options => { // options.Enabled = true; // options.EnabledForAnonymousUsers = false; // options.IgnoredTypes.Add(typeof()); // options.Selectors.Add(new NamedTypeSelector("SelectorName", type => type == typeof())); }).AddTransactionOptions(options => { // options.Timeout=TimeSpan.FromMinutes(3); //options.IsolationLevel=IsolationLevel.ReadCommitted; });
متدهای الحاقی بالا برای ثبت سرویسها و تنظیمات مرتبط با مکانیزمهای اعتبارسنجی خودکار، مدیریت تراکنشها، لاگ آماری، Eventing و سایر امکانات ذکر شده، در IoC Container توکار ASP.NET Core استفاده خواهند شد.
PM> Install-Package DNTFrameworkCore.EntityFramework -Version 1.0.0
services.AddDNTUnitOfWork<ProjectDbContext>();
بسته نیوگت بالا شامل پیاده سازی مبتنی بر EF Core برای واسطهای تعریف شده در بسته نیوگت DNTFrameworkCore، میباشد؛ از جمله آن میتوان به CrudService پایه اشاره کرد. متد الحاقی AddDNTUnitOfWork برای ثبت و معرفی واسطهای IUnitOfWork و ITransactionProvider به عنوان مهیا کننده تراکنش به همراه پیاده سازهای آنها و همچنین ثبت یک سری Hook تعریف شده برای ردیابی تغییرات، در سیستم تزریق وابستگی، استفاده خواهد شد.
همچنین با نصب بسته بالا، امکان استفاده از مهیا کننده Logging با امکان ذخیره سازی در بانک اطلاعاتی را خواهید داشت:
public static IWebHostBuilder CreateWebHostBuilder(string[] args) => WebHost.CreateDefaultBuilder(args) .UseDefaultServiceProvider((context, options) => { options.ValidateScopes = context.HostingEnvironment.IsDevelopment(); }) .ConfigureLogging((hostingContext, logging) => { logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging")); logging.AddConsole(); logging.AddDebug(); logging.AddEntityFramework<ProjectDbContext>(options => options.MinLevel = LogLevel.Warning); }) .UseStartup<Startup>();
متد جنریک الحاقی AddEntityFramework برای ثبت مهیا کننده مذکور استفاده میشود.
PM> Install-Package DNTFrameworkCore.Web -Version 1.0.0
بسته نیوگت بالا شامل یکسری سرویس برای اعمال دسترسیهای پویا، CrudController مبتنی بر ASP.NET Core Web API، فیلتر مدیریت سراسری خطاهای برنامه و سایر امکاناتی که در ادامه مقالات با جزئیات بیشتری بررسی خواهیم کرد، میباشد. برای ثبت سرویسهای تعریف شده میتوانید از متد الحاقی AddDNTCommonWeb و به منظور تغییر محل ذخیره سازی کلیدهای موقت رمزنگاری مرتبط با Data Protection API و انتقال آنها به بانک اطلاعاتی، استفاده کنید.
services.AddDNTCommonWeb() .AddDNTDataProtection();
نکته: برای انتقال کلیدهای موقت رمزنگاری به بانک اطلاعاتی، نیاز است تا از متد الحاقی زیر که در بسته نیوگت DNTFrameworkCore.EntityFramework موجود میباشد، به شکل زیر استفاده کنید:
services.AddDNTProtectionRepository<ProjectDbContext>();
PM> Install-Package DNTFrameworkCore.EntityFramework.SqlServer -Version 1.0.0
بسته بالا از امکانات مخصوص SqlServer برای اعمال قفل منطقی برای مدیریت مباحث همزمانی استفاده میکند؛ همچنین PreUpdateHook مرتبط با تولید خودکار کد منحصر به فرد، در این کتابخانه پیاده سازی شده است. به شکل زیر میتوانید سرویسهای مرتبط با آن را به سیستم تزریق وابستگیهای معرفی کنید:
services.AddDNTNumbering(options => { options.NumberedEntityMap[typeof(Task)] = new NumberedEntityOption { Start = 100, Prefix = "Task-", IncrementBy = 5 }; });
به عنوان مثال برای شماره گذاری موجودیت Task، لازم است تنظیمات مرتبط آن را به شکل بالا به سیستم شماره گذاری معرفی کنید.
اگر قصد استفاده از کتابخانه FluentValidation را داشته باشید، میبایست بسته زیر را نیز نصب کنید:
PM> Install-Package DNTFrameworkCore.FluentValidation -Version 1.0.0
برای ثبت و معرفی Adapter مرتبط، به سیستم اعتبارسنجی خودکار معرفی شده، لازم است از طریق متد الحاقی AddFluentModelValidation به شکل زیر اقدام کنید:
services.AddDNTFramework() .AddDataAnnotationValidation() .AddModelValidation() .AddFluentModelValidation() .AddValidationOptions(options => { /*options.IgnoredTypes.Add(typeof());*/ }) .AddMemoryCache() .AddAuditingOptions(options => { // options.Enabled = true; // options.EnabledForAnonymousUsers = false; // options.IgnoredTypes.Add(typeof()); // options.Selectors.Add(new NamedTypeSelector("SelectorName", type => type == typeof())); }).AddTransactionOptions(options => { // options.Timeout=TimeSpan.FromMinutes(3); //options.IsolationLevel=IsolationLevel.ReadCommitted; });
برای شروع پروژه جدید، نصب این بستهها کفایت میکند. اگر نیاز به طراحی MultiTenancy دارید، بسته زیر را برای شناسایی Tenant جاری و از این قبیل کارها نیز میبایست نصب کنید:
PM> Install-Package DNTFrameworkCore.Web.MultiTenancy -Version 1.0.0
services.AddMultiTenancy<TenantResolver>();
app.UseMultiTenancy();
services.Scan(scan => scan .FromCallingAssembly() .AddClasses(classes => classes.AssignableTo<ISingletonDependency>()) .AsMatchingInterface() .WithSingletonLifetime() .AddClasses(classes => classes.AssignableTo<IScopedDependency>()) .AsMatchingInterface() .WithScopedLifetime() .AddClasses(classes => classes.AssignableTo<ITransientDependency>()) .AsMatchingInterface() .WithTransientLifetime() .AddClasses(classes => classes.AssignableTo(typeof(IDomainEventHandler<>))) .AsImplementedInterfaces() .WithTransientLifetime()); foreach (var descriptor in services.Where(s => typeof(IApplicationService).IsAssignableFrom(s.ServiceType)) .ToList()) { services.Decorate(descriptor.ServiceType, (target, serviceProvider) => ProxyGenerator.CreateInterfaceProxyWithTargetInterface( descriptor.ServiceType, target, serviceProvider.GetRequiredService<ValidationInterceptor>(), (IInterceptor) serviceProvider.GetRequiredService<TransactionInterceptor>())); }
قسمت قبل بیشتر آشنایی با یک سری از اصطلاحات مرتبط با فریم ورک MAF بود و همچنین نحوهی کلی استفاده از آن. در این قسمت یک مثال ساده را با آن پیاده سازی خواهیم کرد و فرض قسمت دوم بر این است که افزونهی Visual Studio Pipeline Builder را نیز نصب کردهاید.
یک نکته پیش از شروع:
- اگر افزونهی Visual Studio Pipeline Builder پس از نصب به منوی Tools اضافه نشده است، یک پوشهی جدید را به نام Addins در مسیر Documents\Visual Studio 2008 ایجاد کرده و سپس فایلهای آنرا در این مسیر کپی کنید.
ساختار اولیه یک پروژه MAF
- پروژههایی که از MAF استفاده میکنند، نیاز به ارجاعاتی به دو اسمبلی استاندارد System.AddIn.dll و System.AddIn.Contract.dll دارند (مطابق شکل زیر):
- ساختار آغازین یک پروژه MAF از سه پروژه تشکیل میشود که توسط افزونهی Visual Studio Pipeline Builder به 7 پروژه بسط خواهد یافت.
این سه پروژه استاندارد آغازین شامل موارد زیر هستند:
- هاست: همان برنامهی اصلی که قرار است از افزونه استفاده کند.
- قرار داد: نحوهی تعامل هاست و افزونه در این پروژه تعریف میشود. (یک پروژه از نوع class library)
- افزونه: کار پیاده سازی قرار داد را عهده دار خواهد شد. (یک پروژه از نوع class library)
- همچنین مرسوم است جهت مدیریت بهتر خروجیهای حاصل شده یک پوشه Output را نیز به این solution اضافه کنند:
اکنون با توجه به این محل خروجی، به خواص Build سه پروژه موجود مراجعه کرده و مسیر Build را اندکی اصلاح خواهیم کرد (هر سه مورد بهتر است اصلاح شوند)، برای مثال:
نکتهی مهم هم اینجا است که خروجی host باید به ریشه این پوشه تنظیم شود و سایر پروژهها هر کدام خروجی خاص خود را در پوشهای داخل این ریشه باید ایجاد کنند.
تا اینجا قالب اصلی کار آماده شده است. قرارداد ما هم به شکل زیر است (ویژگی AddInContract آن نیز نباید فراموش شود):
using System.AddIn.Pipeline;
using System.AddIn.Contract;
namespace CalculatorConract
{
[AddInContract]
public interface ICalculatorContract : IContract
{
double Operate(string operation, double a, double b);
}
}
به عبارت دیگر برنامهای محاسباتی داریم (هاست) که دو عدد double را در اختیار افزونههای خودش قرار میدهد و سپس این افزونهها یک عملیات ریاضی را بر روی آنها انجام داده و خروجی را بر میگردانند. نوع عملیات توسط آرگومان operation مشخص میشود. این آرگومان به کلیه افزونههای موجود ارسال خواهد شد و احتمالا یکی از آنها این مورد را پیاده سازی کرده است. در غیر اینصورت یک استثنای عملیات پیاده سازی نشده صادر میشود.
البته روش بهتر طراحی این افزونه، اضافه کردن متد یا خاصیتی جهت مشخص کردن نوع و یا انواع عملیات پشتیبانی شده توسط افزونه است که جهت سادگی این مثال، به این طراحی ساده اکتفا میشود.
ایجاد pipeline
اگر قسمت قبل را مطالعه کرده باشید، یک راه حل مبتنی بر MAF از 7 پروژه تشکیل میشود که عمدهترین خاصیت آنها مقاوم کردن سیستم در مقابل تغییرات نگارش قرارداد است. در این حالت اگر قرار داد تغییر کند، نه هاست و نه افزونهی قدیمی، نیازی به تغییر در کدهای خود نخواهند داشت و این پروژههای میانی هستند که کار وفق دادن (adapters) نهایی را برعهده میگیرند.
برای ایجاد خودکار View ها و همچنین Adapters ، از افزونهی Visual Studio Pipeline Builder که پیشتر معرفی شد استفاده خواهیم کرد.
سه گزینهی آن هم مشخص هستند. نام پروژهی قرارداد، مسیر پروژهی هاست و مسیر خروجی نهایی معرفی شده. پیش از استفاده از این افزونه نیاز است تا یکبار solution مورد نظر کامپایل شود. پس از کلیک بر روی دکمهی OK، پروژههای ذکر شده ایجاد خواهند شد:
پس از ایجاد این پروژهها، نیاز به اصلاحات مختصری در مورد نام اسمبلی و فضای نام هر کدام میباشد؛ زیرا به صورت پیش فرض هر کدام به نام template نامگذاری شدهاند:
پیاده سازی افزونه
قالب کاری استفاده از این فریم ورک آماده است. اکنون نوبت به پیاده سازی یک افزونه میباشد. به پروژه AddIn مراجعه کرده و ارجاعی را به اسمبلی AddInView خواهیم افزود. به این صورت افزونهی ما به صورت مستقیم با قرارداد سروکار نداشته و ارتباطات، در راستای همان pipeline تعریف شده، جهت مقاوم شدن در برابر تغییرات صورت میگیرد:
using System;
using CalculatorConract.AddInViews;
using System.AddIn;
namespace CalculatorAddIn
{
[AddIn]
public class MyCalculatorAddIn : ICalculator
{
public double Operate(string operation, double a, double b)
{
throw new NotImplementedException();
}
}
}
در اینجا افزونهی ما باید اینترفیس ICalculator مربوط به AddInView را پیاده سازی نماید که برای مثال خواهیم داشت:
using System;
using CalculatorConract.AddInViews;
using System.AddIn;
namespace CalculatorAddIn
{
[AddIn("افزونه یک", Description = "توضیحات", Publisher = "نویسنده", Version = "نگارش یک")]
public class MyCalculatorAddIn : ICalculator
{
public double Operate(string operation, double a, double b)
{
switch (operation)
{
case "+":
return a + b;
case "-":
return a - b;
case "*":
return a * b;
default:
throw new NotSupportedException("عملیات مورد نظر توسط این افزونه پشتیبانی نمیشود");
}
}
}
}
همانطور که در قسمت قبل نیز ذکر شد، این کلاس باید با ویژگی AddIn مزین شود که توسط آن میتوان توضیحاتی در مورد نام ، نویسنده و نگارش افزونه ارائه داد.
استفاده از افزونهی تولید شده
هاست برای استفاده از افزونههایی با قرارداد ذکر شده، مطابق pipeline پروژه، نیاز به ارجاعی به اسمبلی HostView دارد و در اینجا نیز هاست به صورت مستقیم با قرارداد کاری نخواهد داشت. همچنین هاست هیچ ارجاع مستقیمی به افزونهها نداشته و بارگذاری و مدیریت آنها به صورت پویا انجام خواهد شد.
نکتهی مهم!
در هر دو ارجاع به HostView و یا AddInView باید خاصیت Copy to local به false تنظیم شود، در غیر اینصورت افزونهی شما بارگذاری نخواهد شد.
پس از افزودن ارجاعی به HostView، نمونهای از استفاده از افزونهی تولید شده به صورت زیر میتواند باشد که توضیحات مربوطه به صورت کامنت آورده شده است:
using System;
using System.AddIn.Hosting;
using CalculatorConract.HostViews;
namespace Calculator
{
class Program
{
private static ICalculator _calculator;
static void doOperation()
{
Console.WriteLine("1+2: {0}", _calculator.Operate("+", 1, 2));
}
static void Main(string[] args)
{
//مسیر پوشه ریشه مربوطه به خط لوله افزونهها
string path = Environment.CurrentDirectory;
//مشخص سازی مسیر خواندن و کش کردن افزونهها
AddInStore.Update(path);
//یافتن افزونههایی سازگار با شرایط قرارداد پروژه
//در اینجا هیچ افزونهای بارگذاری نمیشود
var addIns = AddInStore.FindAddIns(typeof(ICalculator), path);
//اگر افزونهای یافت شد
if (addIns.Count > 0)
{
var addIn = addIns[0]; //استفاده از اولین افزونه
Console.WriteLine("1st addIn: {0}", addIn.Name);
//فعال سازی افزونه و همچنین مشخص سازی سطح دسترسی آن
_calculator = addIn.Activate<ICalculator>(AddInSecurityLevel.Intranet);
//یک نمونه از استفاده آن
doOperation();
}
Console.WriteLine("Press a key...");
Console.ReadKey();
}
}
}
چند نکته جالب توجه در مورد قابلیتهای ارائه شده:
- مدیریت load و unload پویا
- امکان تعریف سطح دسترسی و ویژگیهای امنیتی اجرای یک افزونه
- امکان ایزوله سازی پروسه اجرای افزونه از هاست (در ادامه توضیح داده خواهد شد)
- مقاوم بودن پروژه به نگارشهای مختلف قرارداد
اجرای افزونه در یک پروسه مجزا
حتما با امکانات مرورگر کروم و یا IE8 در مورد اجرای هر tab آنها در یک پروسهی مجزا از پروسه اصلی هاست مطلع هستید. به این صورت پروسهی هاست از رفتار tab ها محافظت میشود، همچنین پروسهی هر tab نیز از tab دیگر ایزوله خواهد بود. یک چنین قابلیتی در این فریم ورک نیز پیش بینی شده است.
//فعال سازی افزونه و همچنین مشخص سازی سطح دسترسی آن
//همچنین جدا سازی پروسه اجرایی افزونه از هاست
_calculator = addIn.Activate<ICalculator>(
new AddInProcess(),
AddInSecurityLevel.Intranet);
در این حالت اگر پس از فعال شدن افزونه، یک break point قرار دهیم و به task manager ویندوز مراجعه نمائیم، پروسهی مجزای افزونه قابل مشاهده است.
برای مطالعه بیشتر + ، + ، + و +
حالت forms authentication در ASP.Net ، امکان تعریف کوکیهایی ماندگار را نیز جهت ورود خودکار کاربران در دفعات بعدی بازدید آنها فراهم میکند. اما زمان منقضی شدن این کوکیهای ماندگار در ASP.Net 1.1 و ASP.Net 2.0 به بعد کاملا با هم متفاوت بوده و اگر برنامه نویس از این تغییر حاصل شده مطلع نباشد ممکن است بارها و بارها برنامه را آزمایش کند اما نتیجهای نگیرد.
مدت زمان منقضی شدن کوکیهای ماندگار forms authentication در ASP.Net 1.1 به صورت زیر است (بود):
زمان منقضی شدن کوکی سشن = زمان جاری + زمان منقضی شدن سشن کاربر
زمان منقضی شدن کوکی ماندگار (persistent) = زمان جاری + 50 سال
این عدد 50 سال بسیار غیرمنطقی بوده و در ASP.Net 2.0 به بعد به صورت زیر اصلاح شده است:
زمان منقضی شدن کوکی سشن = زمان جاری + زمان منقضی شدن سشن کاربر
زمان منقضی شدن کوکی ماندگار (persistent) = زمان جاری + زمان منقضی شدن سشن کاربر (پیش فرض آن 30 دقیقه است)
و جهت بیشتر کردن طول عمر این ماندگاری باید زمان مورد نظر را در وب کانفیگ به صورت زیر اعمال کرد:
<authentication mode="Forms">
<forms
…
name="MyCookieName"
slidingExpiration="true"
timeout="43200"
/>
</authentication>
همچنین نکتهی مهم دیگری را که باید رعایت کرد، name ایی است که در این فایل config عنوان میکنید (در قسمت تنظیمات form authentication). اگر بر روی یک وب سرور، چندین برنامه وب ASP.Net را در حال اجرا دارید، باید برای هر کدام از اینها نامی جداگانه و منحصربفرد انتخاب کنید، در غیراینصورت تداخل رخ داده و باز هم گزینه مرا به خاطر بسپار شما کار نخواهد کرد.
کار slidingExpiration که در اینجا تنظیم شده است نیز به صورت زیر میباشد:
اگر لاگین موفقیت آمیزی ساعت 5 عصر صورت گیرد و timeout شما به عدد 10 تنظیم شده باشد، این لاگین به صورت خودکار در 5:10 منقضی خواهد شد. اما اگر در این حین در ساعت 5:05 ، کاربر یکی از صفحات سایت شما را مرور کند، زمان منقضی شدن کوکی ذکر شده به 5:15 تنظیم خواهد شد. (مفهوم تنظیم slidingExpiration)
لازم به ذکر است که اگر کاربر پیش از نصف زمان منقضی شدن کوکی (مثلا در 5:04)، یکی از صفحات را مرور کند، تغییری در این زمان نهایی منقضی شدن رخ نخواهد داد.
تزریق خودکار وابستگیها در ASP.NET Web API به همراه رها سازی خودکار منابع IDisposable
- context را باید از طریق الگوی واحد کار مورد استفاده قرار داد (و تفاوتی هم نمیکند که چه نوع پروژهای باشد؛ اصول آن یکی است).
لغو Lazy Loading در حین کار با AutoMapper و Entity Framework
- با توجه به اینکه از نگارش 4.2 به بعد AutoMapper تغییرات مهمی مانند حذف API استاتیک آن صورت گرفتهاست، مثالهای این دوره جهت اعمال این تغییرات به روز شدند.
- خلاصهای از نحوهی به روز رسانی به نگارش 4.2 را در مستندان رسمی آن نیز میتوانید مطالعه کنید.
تزریق وابستگیهای AutoMapper در لایه سرویس برنامه
در نگارش 4 صرفا این دو سطر را حذف کنید:
var platformSpecificRegistry = PlatformAdapter.Resolve<IPlatformSpecificMapperRegistry>(); platformSpecificRegistry.Initialize();
پلتفرمهای مختلف در نگارش 4 به صورت یک اسمبلی مجزا به ازای هر پلتفرم ارائه شدهاند و اینبار مانند قبل یکی نیستند.
PVS-Studio 7.27 منتشر شد
ابزار فوقالعادهای است جهت بررسی کیفیت کدها که توسط برادران روسی توسعه داده میشود؛ همانند تمام محصولات باکیفیت jetbrains مانند Rider، ReSharper و غیره که آنها هم در اصل و بنیان، روسی هستند. در این نگارش، عدم سازگاری آن با آخرین نگارش Rider برطرف شده (با آن یکپارچه میشود) و همچنین قابلیت استفاده در VSCode را هم پیدا کردهاست.