var builder = WebApplication.CreateBuilder(args); var app = builder.Build(); app.MapGet("/", () => "Hello World!"); app.Run();
البته این روش شاید برای برنامههای کوچک جالب بهنظر برسد، اما برای برنامههای بزرگتر میتوان به گزینههای زیر نیز توجه داشت.
گزینهی ارتقاء 1: هیچ کاری نکنید!
اگر میخواهید برنامههای NET 5. خود را به دات نت 6 ارتقاء دهید و نگران هستید که با دو فایل قدیمی Program.cs و Startup.cs آن باید چکار کنیم، پاسخ سادهی آن این است: هیچ کاری نکنید!
شیوهی قدیمی مبتنی بر generic host و Startup، کاملا در دات نت 6 پشتیبانی میشوند؛ از این جهت که WebApplication جدید دات نت 6، صرفا یک محصور کنندهی پیچیدگیهای generic host است. بنابراین برای ارتقاء پروژههای ASP.NET Core 5x به 6x، تنها کافی است فایل csproj خود را ویرایش کرده و TargetFramework آنرا به net6.0 تغییر دهید. پس از آن Program.cs و Stratup.cs قبلی شما بدون هیچ مشکلی و بدون نیاز به هیچ تغییری، با دات نت 6 هم کار خواهند کرد.
<Project Sdk="Microsoft.NET.Sdk.Web"> <PropertyGroup> <TargetFramework>net6.0</TargetFramework> </PropertyGroup> </Project>
گزینهی ارتقاء 2: از کلاس Startup قبلی خود استفادهی مجدد کنید
اما اگر واقعا علاقمندیم که از WebApplication جدید استفاده کنیم و همچنین نمیخواهیم همهچیز را داخل Program.cs قرار دهیم، چکار باید کرد؟
فرض کنید ساختار کلاس Startup موجود شما چنین شکلی را دارد که به همراه سازندهای است که IConfigurationRoot را دریافت میکند و همچنین دارای دو متد ConfigureServices و Configure نیز هست:
public class Startup { public Startup(IConfigurationRoot configuration) { Configuration = configuration; } public IConfigurationRoot Configuration { get; } public void ConfigureServices(IServiceCollection services) { // ... } public void Configure(IApplicationBuilder app, IHostApplicationLifetime lifetime) { // ... } }
var builder = WebApplication.CreateBuilder(args); var startup = new Startup(builder.Configuration); startup.ConfigureServices(builder.Services); var app = builder.Build(); startup.Configure(app, app.Lifetime); app.Run();
گزینهی ارتقاء 3: استفاده از متدهای محلی در فایل Program.cs
اگر بخواهیم سیستم طراحی مینیمال دات نت 6 را رعایت کنیم، میتوان بجای ایجاد یک فایل Startup مجزا، متدهای تنظیمی آنرا به صورت تعدادی متد محلی، در همان فایل Program.cs قرار داد تا کمی ساختار پیدا کند(!)؛ چیزی شبیه به طراحی زیر که همان متدهای قبلی فایل Startup را در انتهای فایل Program.cs جاری به صورت متدهایی محلی، مشاهده میکنید؛ به همراه متدهای اختیاری دیگری برای تنظیم میانافزارها و یا endpoints:
var builder = WebApplication.CreateBuilder(args); ConfigureConfiguration(builder.configuration); ConfigureServices(builder.Services); var app = builder.Build(); ConfigureMiddleware(app, app.Services); ConfigureEndpoints(app, app.Services); app.Run(); void ConfigureConfiguration(ConfigurationManager configuration) => { } void ConfigureServices(IServiceCollection services) => { } void ConfigureMiddleware(IApplicationBuilder app, IServiceProvider services) => { } void ConfigureEndpoints(IEndpointRouteBuilder app, IServiceProvider services) => { }
همانطور که در قسمت اول گفته شد، React برای اینکه بتواند تگها را در زمان اجرا و به صورت پویا به روز کند، وضعیت فعلی تگها را دنبال میکند و در صورت وقوع تغییرات، تگها را به روز میکند. به این حالت Stateful گفته میشود. تگهای ساخته شده توسط React دو وضعیت را دارند. یکی وضعیت اولیه که به مرورگر ارسال شده، در حال نمایش است و ثابت، و دیگری در پشت زمینه در فایل جاوااسکریپت، در انتظار وقوع تغییری. React این دو وضعیت را با هم مقایسه میکند و اگر بین آنها تفاوتی وجود داشت، تغییرات را اعمال میکند.
در React.createClass به همراه متدهای داخلی React میتوانیم برای یک کامپوننت، وضعیتی اولیه را مشخص کنیم، تغییرات را دنبال کنیم و وضعیت فعلی را تغییر دهیم. برای روشن شدن نحوه کار، مثال قسمت قبل را که یک منو از نوشیدنیها بود، اینطور تغییر میدهیم که کاربر بتواند با inputها و یک دکمه، به لیست نوشیدنیها، مورد تازهای را اضافه کند:
var hotDrinks = [ { item: "Tea", price: "7000" }, { item: "Espresso", price: "10000" }, { item: "Hot Chocolate", price: "12000" } ]; var MenuItem = React.createClass({ render: function () { return ( <li className="list-group-item"> <span className="badge">{this.props.price}</span> <p>{this.props.item}</p> </li> ) } }); var Menu = React.createClass({ getInitialState: function () { return { menuList: this.props.data }; }, componentDidMount: function () { var component = this; $("#btnAddNewItem").click(function () { component.state.menuList.push( { item: $("#textInputItemName").val(), price: $("#textInputItemPrice").val() }); component.setState({ menuList: component.state.menuList }); }); }, render: function () { return ( <div className="row"> <div className="col-md-4"> <ul className="list-group"> {this.state.menuList.map(item => <MenuItem {...item} />)} </ul> </div> </div> ) } }); ReactDOM.render( <Menu data={hotDrinks} />, document.getElementById("reactTestContainer") );
توضیح کامپوننت Menu
getInitialState، componentDidMount، setState، state و render همگی از کتابخانه React هستند. اگر intelisense و code snippets مخصوص React را در VSCode نصب کرده باشید، دسترسی به سایر متدها و خاصیتهای کتابخانه سادهتر است.
شیء state، وضعیت کنونی کامپوننت است. وقتی دادهای را به state اختصاص میدهیم، آن را به عنوان وضعیت اولیه در نظر میگیرد. با تغییر داده، React وضعیت کامپوننت را تغییر یافته حساب میکند و به صورت خودکار تگها را دوباره با دادههای تازه میسازد. دادههای state همان دادههایی هستند که تگها با آنها ساخته میشوند؛ در بخش render.
getInitialState مثل یک سازنده عمل میکند؛ مقدار ورودی کامپوننت را به یک شیء اختصاص میدهد و آن را برمیگرداند. به کجا؟ به state. یعنی menuList عضوی از شیء state میشود. در مثال بالا و در این متد، لیست نوشیدنیها به menuList اعمال میشود.
componentDidMount باید حتما قبل render تعریف شود، به این دلیل که زمان اجرایش باید حتما بعد از اولین render باشد. این متد وظیفه دارد تغییرات مورد نظر ما را در سطح کد یا رابط کاربری دنبال کند. اگر تغییر دلخواهی به وجود آمد، وضعیت کامپوننت را به روز میکند که بعد از آن React به صورت خودکار تگها را دوباره میسازد. در مثال بالا متد به رویداد کلیک یک دکمه گوش میدهد. اگر کلیک زده شد، نام نوشیدنی جدید و قیمت آن را از inputها میخواند و به عنوان یک آیتم جدید به menuList در state اضافه میکند. اما هنوز یک قدم مانده و بدون آن React، شیء state را تغییر یافته به حساب نمیآورد. در بخش setState وضعیت جاری کامپوننت را با تغییرات اعمال شده، جایگزین میکنیم. در این نقطه React به صورت خودکار به سراغ render میرود و ادامه داستان!
همانطور که قبلا گفته شد، React.createClass و React.Component فقط در Syntax با هم تفاوت دارند. در نتیجه این مثال را میشود در حالت React.Component هم اجرا کرد.
در قسمت بعد موضوع دیگری را به نام Composability شرح میدهیم. مبحثی ساده با مثال که نشان میدهد چطور کامپوننتها را مستقل از هم بسازیم و در عین حال با هم استفاده کنیم.
Contact me
کاری هم که شما کردید سبب هک شدن سایت شما خواهد شد چون دسترسی به آپلودر بدون اعتبار سنجی و با دانستن مسیر آن (با توجه به سورس باز بودن این ادیتور) میسر است.
برای این حالت هم میتونید از یک متغیر سشن استفاده کنید. اگر ست شده بود یعنی کاربر لاگین کرده، اگر نه، خیر. (روش ایدهآلی نیست ولی کار میکنه)
خودم در اکثر موارد از این ادیتور استفاده میکنم:
freeTextBox.Com
امکان آپلود عکس و غیره رو هم داره با یک نکته البته، دکمهی InsertImageFromGallery را باید دستی در خواص آن ست کرد که البته در مستندات سایت آن موجود است.
بررسی مفهوم دیتاست خارجی و درونی
ابتدا در management studio از منوی Query، گزینهی Include actual execution plan را انتخاب میکنیم. سپس کوئریهای زیر را اجرا میکنیم:
USE [WideWorldImporters]; GO SET STATISTICS IO ON; GO /* What's are the inner and outer data sets? */ SELECT [ol].[OrderLineID], [o].[CustomerID] FROM [Sales].[OrderLines] [ol] INNER JOIN [Sales].[Orders] [o] ON [ol].[OrderID] = [o].[OrderID] WHERE [o].[CustomerID] = 185; GO
در اینجا دیتاست خارجی، همان index seek بالایی است که بر روی جدول Orders انجام شدهاست. اولین ردیف بازگشت داده شدهی توسط آن به همراه OrderID مربوطه را به حلقهی تو در توی Inner Join ارسال میکند. سپس index seek دوم بر روی جدول OrderLines، بر اساس OrderID دیتاست خارجی، ردیف مرتبطی را در صورت وجود یافته و به حلقهی تو در توی Inner Join بازگشت میدهد که در نهایت به select ارسال میشود و این عملیات به همین ترتیب ادامه پیدا میکند. این خلاصهی کاری است که یک حلقهی تو در تو انجام میدهد.
سؤال: اگر جای این دیتاستها را عوض کنیم چه اتفاقی رخ خواهد داد؟
در کوئری زیر توسط گزینهی FORCE ORDER سبب شدهایم تا جای دیتاستهای OUTER/INNER تغییر کند (البته این query hint، کاربرد عملی ندارد و صرفا جهت نمایش دیتاستها از آن استفاده کردهایم):
SELECT [ol].[OrderLineID], [o].[CustomerID] FROM [Sales].[OrderLines] [ol] INNER JOIN [Sales].[Orders] [o] ON [ol].[OrderID] = [o].[OrderID] WHERE [o].[CustomerID] = 185 OPTION (FORCE ORDER);
یک نکته: در این تصاویر بجای nested loop، از عملگر Hash Match استفاده شدهاست. اگر بخواهیم بهینه سازی کوئری را وادار کنیم تا از nested loop استفاده کند، میتوان کوئری فوق را توسط یک INNER LOOP JOIN به صورت زیر نوشت:
SELECT [ol].[OrderLineID], [o].[CustomerID] FROM [Sales].[OrderLines] [ol] INNER LOOP JOIN [Sales].[Orders] [o] ON [ol].[OrderID] = [o].[OrderID] WHERE [o].[CustomerID] = 185 OPTION (FORCE ORDER); GO
همانطور که مشاهده میکنید اینبار به علت بالا رفتن تعداد ردیفهایی که باید پردازش کند، به یک پلن بسیار غیر بهینه رسیدهاست که برای بهبود آن مجبور شدهاست Parallelism را نیز فعال کند.
در این حالت اگر هر سه کوئری فوق را با هم اجرا کنیم، تا بتوانیم هزینهی آنها را در کوئری پلن نهایی تولید شده، با یکدیگر مقایسه کنیم، هزینهی کوئری اول صفر درصد، کوئری دوم 1 درصد و کوئری سوم 99 درصد نسبت به کل batch محاسبه میشود. علت آن را نیز در برگهی messages، با مشاهدهی logical reads 477304 مربوط به کوئری سوم میتوان مشاهده کرد که نسبت به سایر کوئریها بسیار بیشتر است. بنابراین بهتر است در کار بهینه ساز کوئریها به صورت دستی دخالت نکنیم!
بهبود کارآیی یک کوئری، با حذف حلقهی تو در توی کوئری پلن آن در حالت Key lookup
کوئری زیر را با فرض انتخاب گزینهی Include actual execution plan در منوی کوئری، اجرا میکنیم:
SELECT [ContactPersonID], [OrderDate], [CustomerPurchaseOrderNumber] FROM [Sales].[Orders] WHERE [ContactPersonID] = 3144;
ایندکسهایی که در این کوئری پلن استفاده شدهاند، شامل موارد پیشفرض زیر هستند؛ یکی بر روی OrderID که کلید اصلی جدول است، تشکیل شده و دیگری بر روی ContactPersonID که در قسمت where کوئری فوق مورد استفاده قرار گرفتهاست:
ALTER TABLE [Sales].[Orders] ADD CONSTRAINT [PK_Sales_Orders] PRIMARY KEY CLUSTERED ( [OrderID] ASC ) GO CREATE NONCLUSTERED INDEX [FK_Sales_Orders_ContactPersonID] ON [Sales].[Orders] ( [ContactPersonID] ASC )
برای بهبود این وضعیت، NONCLUSTERED INDEX تعریف شده را به صورت زیر تغییر میدهیم تا ستونهای OrderDate و CustomerPurchaseOrderNumber را INCLUDE کند:
CREATE NONCLUSTERED INDEX [FK_Sales_Orders_ContactPersonID] ON [Sales].[Orders] ( [ContactPersonID] ASC ) INCLUDE ( [OrderDate], [CustomerPurchaseOrderNumber] ) WITH (DROP_EXISTING = ON) ON [USERDATA]; GO
SELECT [ContactPersonID], [OrderDate], [CustomerPurchaseOrderNumber] FROM [Sales].[Orders] WHERE [ContactPersonID] = 3144;
چون ایندکس جدید تعریف شده کاملا کوئری ما را پوشش میدهد، دیگر نیازی به ایجاد یک nested loop، جهت کار با چندین index متفرقه نیست.
بهبود کارآیی یک کوئری، با حذف حلقهی تو در توی کوئری پلن آن در حالت RID lookup
در اینجا یک جدول کپی را از روی جدول اصلی Orders ایجاد کردهایم؛ به همراه تعریف یک NONCLUSTERED INDEX بر روی ستون ContactPersonID آن:
USE [WideWorldImporters] GO DROP TABLE [Sales].[Copy_Orders] GO SELECT * INTO [Sales].[Copy_Orders] FROM [Sales].[Orders]; GO CREATE NONCLUSTERED INDEX [NCI_Copy_Orders_ContactPersonID] ON [Sales].[Copy_Orders] ( [ContactPersonID] ); GO
SELECT [ContactPersonID], [OrderDate], [CustomerPurchaseOrderNumber] FROM [Sales].[Copy_Orders] WHERE [ContactPersonID] = 3144;
در اینجا یک nested loop را به همراه RID lookup داریم (RID به معنای row id است). همچنین واژهی heap نیز ذکر شدهاست. در این حالت اطلاعات یک چنین جدولی بدون هیچگونه ترتیبی ذخیره شدهاند؛ بنابراین نیاز به شماره ردیف آن (RID) برای برقراری ارتباطات میباشد. Key lookup زمانی رخ میدهند که یک جدول دارای یک clustered index باشد و RID lookup، در حالت عکس آن رخ میدهد. دقیقا مانند جدول کپی ایجاد شده، که دارای یک clustered index نیست.
در صورت مشاهدهی RID lookup نیز میتوانیم ستونهایی از کوئری را که در NONCLUSTERED INDEX ذکر نشدهاند، include کنیم:
CREATE NONCLUSTERED INDEX [NCI_Copy_Orders_ContactPersonID] ON [Sales].[Copy_Orders] ( [ContactPersonID] ASC ) INCLUDE ( [OrderDate], [CustomerPurchaseOrderNumber] ) WITH (DROP_EXISTING = ON) ON [USERDATA]; GO
در ادامه آموزش Git، به بررسی مفاهیم مورد استفاده در این سیستم مدیریت کد میپردازیم. البته ذکر این نکته ضروری است که ممکن است برخی از تعاریف زیر، برای افرادی که تا کنون با اینگونه سیستمها کار نکردهاند، مبهم باشد. اما مشکلی نیست؛ زیرا در دروس بعدی کار با Git، به صورت عملی، این مفاهیم به شکل دقیقتر و کاربردیتر بیان میشوند. هدف در اینجا تنها ایجاد یک تصویر کلی از نحوه کار سیستمهای مدیریت کد توزیع شده است.
تعاریف زیر هر چند برای Git نوشته شدهاند، اما میتوانند در بقیه DVCSها نیز کاربرد داشته باشند.
Commit:
بعد از آن که برنامه نویسان از صحت کدهای خود مطمئن شدند، برای ثبت وضعیت فعلی باید آنها را commit کنند. با این کار یک نسخه جدید از فایلها ایجاد میشود. به این ترتیب امکان بازگشت به نقطه فعلی درآینده به وجود خواهد آمد.
Pushing:
بعد از انجام عملیات Commit، معمولا برنامه نویسان میخواهند کدهای نوشته شده را با دیگران به اشتراک بگذارند. این کار به وسیله عملیات Pushing صورت میگیرد. بنابراین pushing عبارت است از عملی که با استفاده از آن دادهها از یک Repository به Repository دیگر جهت به اشتراک گذاری انتقال مییابد. معمولا به این مخزن Upstream Repository میگویند. Upstream Repository یک مخزن عمومی برای تمامی برنامه نویسانی است که تغییرات فایلهای خود را در آنجا push میکنند.
Pulling:
عملیات Pushing تنها نیمی از آن چیزی است که برنامه نویسان برای حفظ به روز بودن کدهای خود به آن احتیاج دارند. در بسیاری از موارد آنها نیاز دارند تا تغییرات فایلها و آخرین به روز رسانیها را نیز دریافت کنند. این کار در دو مرحله متفاوت انجام میشود:
1)بازیابی دادهها از مخزن عمومی (fetch)
2)الحاق دادههای دریافت شده با دادههای فعلی
معمولا در بسیاری از سیستمهای مدیریت کد، چون به هر دوی این عملیات توامان نیاز است، با یک دستور هر دو کار انجام میشود. به مجموع عملیات فوق Pulling گویند.
Branchها (شاخهها):
Branch و یا همان شاخه، به ما این امکان را میدهد که بتوانیم برای قسمتهای مختلف یک پروژه که روند تولید آنها با هم ارتباط مستقیمی ندارند، سوابق فایلی متفاوتی را ایجاد کنیم.
به عنوان مثال تصور کنید که در یک پروژه سه تیم متفاوت وجود دارد
1)تیم توسعه برنامه
2)تیم تست و اشکال یابی
3)واحد گرافیکی
در این حالت منطقی است به جای آن که سوابق فایلها برای همه یکسان باشد، هر تیم، شاخه مخصوص به خود را داشته باشد، تا تنها تغییرات فایلهای مربوطه را پیگیری کند و در نهایت بعد از آن که از صحت کار خود مطمئن شد، آن را در یک شاخه اصلی برای استفاده دیگر تیمها قرار دهد.
در Git شاخه اصلی master نام دارد و فایلها به صورت پیش فرض در این شاخه قرار داده میشوند. استاندارد کار بر آن است که در شاخه master تنها فایلهای نهائی قرار گیرند.
Merging:
به عملیات ادغام دو یا چند شاخه با یکدیگر Merging گفته میشود. در بعضی موارد، در روند توسعه یک برنامه نیاز است که شاخههایی جهت مدیریت بهتر کد ایجاد شود. اما بعد از توسعه این قسمت ها، میتوان شاخههای ایجاد شده را با هم ادغام نمود تا تغییرات فایلها در یک شاخه قرار گیرند. مثلا در یک تیم توسعه فرض کنید دو گروه وجود دارند که کدهای مربوط به دسترسی داده را مینویسند و هر دو را در یک شاخه فایلهای خود، نگهداری میکنند. گروه اول بر روی کلاسهای انتزاعی و گروه دوم بر روی کلاسهای عملی کار میکنند. به منظور اینکه گروه دوم به اشتباه کلاسهای انتزاعی را که هنوز کامل نیستند پیاده سازی نکند، دو شاخه از شاخه اصلی ایجاد میشود و هر گروه در شاخهای مجزا قرار میگیرد. گروه اول تنها کلاسهای انتزاعی را در شاخه مشترک قرار میدهد که کار آنها تمام شده باشد و گروه دوم تنها همان کلاسها را پیاده سازی و در شاخه مشترک میگذارد. بعد از آنکه کار این دو بخش پایان گرفت میتوان هر سه شاخه را در یک شاخه مثلا بخش کدهای دسترسی داده قرار داد.
البته عملیات Merging می تواند باعث ایجاد مشکلی به نام Conflict شود که خوشبختانه Git روش هایی را برای مدیریت این مشکل دارد که در مقالات بعد به آن اشاره خواهد شد.
Locking:
با استفاده از این کار میتوان مانع تغییر یک فایل توسط برنامه نویسان دیگر شد. معولا Locking به 2 صورت است
1)Strict Locking
2) Optimistic Locking
در روش اول بعد از آن که فایلی قفل شد همان کسی که فایل را قفل کرده تنها امکان تغییر آن را خواهد داشت؛ که البته این روش مناسب سیستمهای توزیع شده نیست.
در روش دوم فرض بر این است که تغییراتی را که هر کس بر روی فایل میدهد، به گونهای باشد که هنگام ادغام این تغییرات، اختلالی ایجاد نشود. یعنی وظیفه بر عهده مصرف کننده فایل است که آگاهی داشته باشد چگونه فایل را تغییر دهد. هنگامی که فایلی به این روش قفل میشود، اگر در حین تغییر فایل توسط ما، شخص دیگری فایل را تغییر داده باشد و آن را pull کرده باشد ما در زمان push فایل با خطا مواجه میشویم. سیستم از ما میخواهد که ابتدا تغییرات فایل را pull کنیم و سپس فایل را push نمائیم. در هنگام pull اگر برنامه نویسی قوانین تغییرات فایل را رعایت نکرده باشد، ممکن است اعمال تغییرات با خطا همراه گردد.
تعاریف فوق بخشی از مفاهیم اولیه مورد نیاز Git بود. اما ما در ادامه به بررسی objectهای Git و همچنین نحوه ذخیره سازی و مدیریت فایلها در این سیستم مدیریت کد خواهیم پرداخت.
AutoMapper کتابخانهای برای نگاشت اطلاعات یک شیء به شیءایی دیگر به صورت خودکار میباشد.
در این مقاله چگونگی رسیدگی به Null property را در AutoMapper بررسی خواهیم کرد. فرض کنید شیء منبع دارای یک خاصیت Null است و میخواهید به وسیله Automaper شیء منبع را به مقصد نگاشت نمایید. اما میخواهید در صورت Null بودن شیء مبدا، یک مقدار پیش فرض برای شیء مقصد در نظر گرفته شود .
برای نمونه کلاسuser را که در آن از کلاس Address یک خاصیت تعریف شده، در نظر بگیرید. اگر مقدار آدرس در شیء منبع خالی بود شاید شما بخواهید مقدار آن را به صورت empty string و یا با یک مقدار پیش فرض در مقصد مقدار دهی کنید.
همانند مثال زیر :
public class UserSource { public Address Address{get;set;} } public class UserDestination { public string Address{get;set;} }
AutoMapper.Mapper.CreateMap<UserSource, UserDestination>() .ForMember(dest => dest.Address , opt => opt.NullSubstitute("Address not found") );
var model = AutoMapper.Mapper.Map<UserSource, UserDestination>(user); var models = AutoMapper.Mapper.Map<IEnumerable<UserSource>, IEnumerable<UserDestination>>(users);
public class Mailer { public static bool SendEmail() { Console.WriteLine("Sending Mail ..."); // simulate error Random rnd = new Random(); var rndNumber = rnd.Next(1, 10); if (rndNumber != 3) // * throw new SmtpFailedRecipientException(); Console.WriteLine("Mail Sent successfully"); return true; } }
public static class Retry { public static void Do(Action action,TimeSpan retryInterval,int maxAttemptCount = 3) { Do<object>(() => { action(); return null; }, retryInterval, maxAttemptCount); } public static T Do<T>(Func<T> action,TimeSpan retryInterval,int maxAttemptCount = 3) { var exceptions = new List<Exception>(); for (int attempted = 0; attempted < maxAttemptCount; attempted++) { try { if (attempted > 0) { Thread.Sleep(retryInterval); } return action(); } catch (Exception ex) { exceptions.Add(ex); } } throw new AggregateException(exceptions); } }
retryInterval= retryInterval.Add(TimeSpan.FromSeconds(10));
در سادهترین حالت، استفاده از Polly همانند زیر است:
var policy = Policy.Handle<SmtpFailedRecipientException>().Retry(); policy.Execute(Mailer.SendEmail);
متد Retry، دارای Overloadهای مختلفی است که یکی از آنها مقدار تعداد دفعات تلاش را دریافت میکند؛ همانند:
var policy = Policy.Handle<SmtpFailedRecipientException>().Retry(5);
لازم به ذکر است که باید دقیقا Exception مورد نظر را در بخش Config به کار ببرید. برای نمونه اگر کد فوق را همانند زیر به کار ببرید، در صورتیکه متد ارسال ایمیل با خطایی مواجه شود، هیچ تلاشی برای اجرای مجدد نخواهد کرد:
var policy = Policy.Handle<SqlException>().Retry(5);
برای نمونه میتوان از متد ForEver آن استفاده کرد تا زمانیکه متد مورد نظر Success نشده باشد، سعی در اجرای آن کند:
Policy .Handle<DivideByZeroException>() .RetryForever()
PM> Install-Package snap.structuremap
StructureMap (≥ 2.6.4.1) CommonServiceLocator.StructureMapAdapter (≥ 1.1.0.3) SNAP (≥ 1.8) fasterflect (≥ 2.1.2) Castle.Core (≥ 3.1.0) CommonServiceLocator (≥ 1.0)
تنظیمات SNAP
namespace Framework.UI.Asp { public class Global : HttpApplication { void Application_Start(object sender, EventArgs e) { initSnap(); initStructureMap(); } private static void initSnap() { SnapConfiguration.For<StructureMapAspectContainer>(c => { // Tell Snap to intercept types under the "Framework.ServiceLayer..." namespace. c.IncludeNamespace("Framework.ServiceLayer.*"); // Register a custom interceptor (a.k.a. an aspect). c.Bind<Framework.ServiceLayer.Aspects.AuthorizationInterceptor>() .To<Framework.ServiceLayer.Aspects.AuthorizationAttribute>(); }); } void Application_EndRequest(object sender, EventArgs e) { ObjectFactory.ReleaseAndDisposeAllHttpScopedObjects(); } private static void initStructureMap() { var thread = StructureMap.Pipeline.Lifecycles.GetLifecycle(InstanceScope.HttpSession); ObjectFactory.Configure(x => { x.For<IUserManager>().Use<EFUserManager>(); x.For<IAuthorizationManager>().LifecycleIs(thread) .Use<EFAuthorizationManager>().Named("_AuthorizationManager"); x.For<Framework.DataLayer.IUnitOfWork>() .Use<Framework.DataLayer.Context>(); x.SetAllProperties(y => { y.OfType<IUserManager>(); y.OfType<Framework.DataLayer.IUnitOfWork>(); y.OfType<Framework.Common.Web.IPageHelpers>(); }); }); } } }
namespace Framework.ServiceLayer.Aspects { public class AuthorizationInterceptor : MethodInterceptor { public override void InterceptMethod(IInvocation invocation, MethodBase method, Attribute attribute) { var AuthManager = StructureMap.ObjectFactory .GetInstance<Framework.ServiceLayer.UserManager.IAuthorizationManager>(); var FullName = GetMethodFullName(method); if (!AuthManager.IsActionAuthorized(FullName)) throw new Common.Exceptions.UnauthorizedAccessException(""); invocation.Proceed(); // the underlying method call } private static string GetMethodFullName(MethodBase method) { var TypeName = (((System.Reflection.MemberInfo)(method)).DeclaringType).FullName; return TypeName + "." + method.Name; } } public class AuthorizationAttribute : MethodInterceptAttribute { }
namespace Snap { public abstract class MethodInterceptor : IAttributeInterceptor, IInterceptor, IHideBaseTypes { protected MethodInterceptor(); public int Order { get; set; } public Type TargetAttribute { get; set; } public virtual void AfterInvocation(); public virtual void BeforeInvocation(); public void Intercept(IInvocation invocation); public abstract void InterceptMethod(IInvocation invocation, MethodBase method, Attribute attribute); public bool ShouldIntercept(IInvocation invocation); } }
یک نکته
private void Application_PreRequestHandlerExecute(object source, EventArgs e) { var page = HttpContext.Current.Handler as BasePage; // The Page handler if (page == null) return; WireUpThePage(page); WireUpAllUserControls(page); var UsrCod = HttpContext.Current.Session["UsrCod"]; if (UsrCod != null) { var _AuthorizationManager = ObjectFactory .GetNamedInstance<Framework.ServiceLayer.UserManager.IAuthorizationManager>("_AuthorizationManager"); ((Framework.ServiceLayer.UserManager.EFAuthorizationManager)_AuthorizationManager) .AuditUserId = UsrCod.ToString(); } }
namespace Framework.ServiceLayer.UserManager { public class EFUserManager : IUserManager { IUnitOfWork _uow; IDbSet<User> _users; public EFUserManager(IUnitOfWork uow) { _uow = uow; _users = _uow.Set<User>(); } [Framework.ServiceLayer.Aspects.Authorization] public List<User> GetAll() { return _users.ToList<User>(); } } }
String.format = function () { var s = arguments[0]; for (var i = 0; i < arguments.length - 1; i++) { s = s.replace("{" + i + "}", arguments[i + 1]); } return s; };
String.format = function () { var s = arguments[0]; for (var arg in arguments) { var i = parseInt(arg); s = s.replace("{" + i + "}", arguments[i + 1]); } return s; };
console.log(String.format("{0} is nice!", "donettips.info"));
donettips.info is nice!
console.log(String.format("{0} is {1} nice! {0} is {1} nice!", "donettips.info", "very"));
donettips.info is very nice! {0} is {1} nice!
String.format = function () { var original = arguments[0], replaced; for (var i = 0; i < arguments.length - 1; i++) { replaced = ''; while (replaced != original) { original = replaced || original; replaced = original.replace("{" + i + "}", arguments[i + 1]); } } return replaced; };
donettips.info is very nice! donettips.info is very nice!
String.format = function () { var s = arguments[0]; for (var i = 0; i < arguments.length - 1; i++) { s = s.replace(new RegExp("\\{" + i + "\\}", "g"), arguments[i + 1]); } return s; };
String.format = function () { var s = arguments[0], i = arguments.length - 1; while (i--) { s = s.replace(new RegExp('\\{' + i + '\\}', 'g'), arguments[i + 1]); } return s; };
console.log(String.format("{0}:0 {1}:1 {2}:2", "zero", "{2}", "two"));
zero:0 {2}:1 two:2
zero:0 two:1 two:2
console.log(String.format("{0}:0 {1}:1 {2}:2", "zero", "one", "{1}"));
zero:0 one:1 one:2
zero:0 one:1 {1}:2
String.format = function () { var args = arguments; return args[0].replace(/{(\d+)}/g, function (match, number) { return args[parseInt(number) + 1]; }); };
console.log(String.format("{0} is {1} nice!", "donettips.info"));
donettips.info is undefined nice!
String.format = function () { var s = arguments[0], args = arguments; return s.replace(/{(\d+)}/g, function (match, number) { var i = parseInt(number); return typeof args[i + 1] != 'undefined' ? args[i + 1] : match; }); };
console.log(String.format("{0}:0 {1}:1 {2}:2, {{0}} {{{1}}} {{{{2}}}} {2}", "zero", "{2}", "two"));
zero:0 {2}:1 two:2, {zero} {{{2}}} {{{two}}} two
String.format = function () { var s = arguments[0], args = arguments; return s.replace(/\{\{|\}\}|\{(\d+)\}/g, function (match, number) { if (match == "{{") { return "{"; } if (match == "}}") { return "}"; } var i = parseInt(number); return typeof args[i + 1] != 'undefined' ? args[i + 1] : match; }); };
zero:0 {2}:1 two:2, {0} {{2}} {{2}} two
String.prototype.format = function () { ... }
String.prototype.format = function () { var s = this.toString(), args = arguments; return s.replace(/\{\{|\}\}|\{(\d+)\}/g, function (match, number) { if (match == "{{") { return "{"; } if (match == "}}") { return "}"; } return typeof args[number] != 'undefined' ? args[number] : match; }); };
console.log("{0}:0 {1}:1 {2}:2, {{0}} {{{1}}} {{{{2}}}} {2}".format("zero", "{2}", "two"));
String.format = function () { var s = arguments[0], args = arguments[1]; for (var arg in args) { s = s.replace(new RegExp("{" + arg + "}", "g"), args[arg]); } return s; };
String.prototype.format = function () { var s = this.toString(), args = arguments[0]; for (var arg in args) { s = s.replace(new RegExp("{" + arg + "}", "g"), args[arg]); } return s; };
console.log(String.format("{site} is {adj}! {site} is {adj}!", { site: "donettips.info", adj: "nice" })); console.log("{site} is {adj}! {site} is {adj}!".format({ site: "donettips.info", adj: "nice" }));
String.format = function String$format(format, args) { /// <summary locid="M:J#String.format" /> /// <param name="format" type="String"></param> /// <param name="args" parameterArray="true" mayBeNull="true"></param> /// <returns type="String"></returns> // var e = Function._validateParams(arguments, [ // { name: "format", type: String }, // { name: "args", mayBeNull: true, parameterArray: true } // ]); // if (e) throw e; return String._toFormattedString(false, arguments); }; String._toFormattedString = function String$_toFormattedString(useLocale, args) { var result = ''; var format = args[0]; for (var i = 0; ; ) { var open = format.indexOf('{', i); var close = format.indexOf('}', i); if ((open < 0) && (close < 0)) { result += format.slice(i); break; } if ((close > 0) && ((close < open) || (open < 0))) { if (format.charAt(close + 1) !== '}') { throw Error.argument('format', Sys.Res.stringFormatBraceMismatch); } result += format.slice(i, close + 1); i = close + 2; continue; } result += format.slice(i, open); i = open + 1; if (format.charAt(i) === '{') { result += '{'; i++; continue; } if (close < 0) throw Error.argument('format', Sys.Res.stringFormatBraceMismatch); var brace = format.substring(i, close); var colonIndex = brace.indexOf(':'); var argNumber = parseInt((colonIndex < 0) ? brace : brace.substring(0, colonIndex), 10) + 1; if (isNaN(argNumber)) throw Error.argument('format', Sys.Res.stringFormatInvalid); var argFormat = (colonIndex < 0) ? '' : brace.substring(colonIndex + 1); var arg = args[argNumber]; if (typeof (arg) === "undefined" || arg === null) { arg = ''; } if (arg.toFormattedString) { result += arg.toFormattedString(argFormat); } else if (useLocale && arg.localeFormat) { result += arg.localeFormat(argFormat); } else if (arg.format) { result += arg.format(argFormat); } else result += arg.toString(); i = close + 1; } return result; }
console.log(String.format("{0:n}, {0:c}, {0:p}, {0:d}", 100.0001)); // result: 100.00, ¤100.00, 10,000.01 %, 100.0001 console.log(String.format("{0:d}, {0:t}", new Date(2015, 1, 1, 10, 45))); // result: 02/01/2015, 10:45
var template = jQuery.validator.format("{0} is not a valid value"); console.log(template("abc")); // result: 'abc is not a valid value'
String.format([full format string], [arguments...]); // or: [date|number].format([partial format string]);
// Object path String.format("Welcome back, {username}!", { id: 3, username: "JohnDoe" }); // Result: "Welcome back, JohnDoe!" // Date/time formatting String.format("The time is now {0:t}.", new Date(2009, 5, 1, 13, 22)); // Result: "The time is now 01:22 PM." // Date/time formatting (without using a full format string) var d = new Date(); d.format("hh:mm:ss tt"); // Result: "02:28:06 PM" // Custom number format string String.format("Please call me at {0:+##0 (0) 000-00 00}.", 4601111111); // Result: "Please call me at +46 (0) 111-11 11." // Another custom number format string String.format("The last year result was {0:+$#,0.00;-$#,0.00;0}.", -5543.346); // Result: "The last year result was -$5,543.35." // Alignment String.format("|{0,10:PI=0.00}|", Math.PI); // Result: "| PI=3.14|" // Rounding String.format("1/3 ~ {0:0.00}", 1/3); // Result: "1/3 ~ 0.33" // Boolean values String.format("{0:true;;false}", 0); // Result: "false" // Explicitly specified localization // (note that you have to include the .js file for used cultures) msf.setCulture("en-US"); String.format("{0:#,0.0}", 3641.667); // Result: "3,641.7" msf.setCulture("sv-SE"); String.format("{0:#,0.0}", 3641.667); // Result: "3 641,7"
//inline arguments String.format("some string with {0} and {1} injected using argument {{number}}", 'first value', 'second value'); //returns: 'some string with first value and second value injected argument {number}' //single array String.format("some string with {0} and {1} injected using array {{number}}", [ 'first value', 'second value' ]); //returns: 'some string with first value and second value injected using array {number}' //single object String.format("some string with {first} and {second} value injected using {{propertyName}}",{first:'first value',second:'second value'}); //returns: 'some string with first value and second value injected using {propertyName}'