وقتی شما یک توسعه دهندهی وب هستین معمولاً بصورت انفرادی شروع به ایجاد پروژه جدید خودتون میکنید و اینطور به نظر میرسه که توانایی این کار رو دارید چون یک فرد باهوش و تکنیکی هستید. درسته؟ اما وقتی صحبت از طراحی میشه بهترین و زیرکترین توسعه دهندگان هم دچار مشکل میشن. چرا اینطوریه و آیا راهی برای تغییر اون وجود داره؟
اگر جدیدا قصد برنامه نویسی اندروید را کردهاید، یا هنوز روشهای متدوالی را برای
کار با این زبان انتخاب نکردهاید؛ به نظرم این مقاله میتواند کمک خوبی
برای شما باشد. مسائلی که بیان میکنم در واقع از تجربیات شخصی و راه حل
هایی است که برای خودم تعیین کردهام و تعدادی از آنها را در طول مدتی که
در این زمینه فعالیت کردهام، از جاهای مختلف دیده و در یک جا گردآوری
کردهام. برای نامگذاری اشیاء و متغیرها و دیگر موارد، من از این قاعده پیروی میکنم که به نظرم بسیار ایده آل میباشد. الگوی معماری هم که جدیدا مورد استفاده قرار دادهام، الگوی MVP است که نمونهای از آن، در گیت هاب قرار گرفته است. البته این مثال ساده تر نیز وجود دارد. تشریح کامل این معماری را به همراه آزمون واحد آن، میتوانید در این مقاله سه قسمتی ببینید.
در اینجا، یک سری نکات را در طول برنامه نویسی، متذکر میشوم تا مدیریت کدهای شما را در اندروید راحتتر کند.
یک نکتهی دیگر را که باید متذکر شوم این است که همه اصطلاحاتی که در این مقاله استفاده میشوند بر اساس اندروید استادیو و مستندات رسمی گوگل است است؛ به عنوان نمونه عبارتهای ماژول و پروژه آن چیزی هستند که ما در اندروید استادیو به آنها اشاره میکنیم، نه آنچه که کاربران Eclipse به آن اشاره میکنند.
یک. برای هر تکه کد و یا متدی که مینویسید مستندات کافی قرار دهید و اگر این متد نیاز به مجوز خاصی دارد مانند نمونه زیر، آن را حتما ذکر کنید:
در اینجا، یک سری نکات را در طول برنامه نویسی، متذکر میشوم تا مدیریت کدهای شما را در اندروید راحتتر کند.
یک نکتهی دیگر را که باید متذکر شوم این است که همه اصطلاحاتی که در این مقاله استفاده میشوند بر اساس اندروید استادیو و مستندات رسمی گوگل است است؛ به عنوان نمونه عبارتهای ماژول و پروژه آن چیزی هستند که ما در اندروید استادیو به آنها اشاره میکنیم، نه آنچه که کاربران Eclipse به آن اشاره میکنند.
یک. برای هر تکه کد و یا متدی که مینویسید مستندات کافی قرار دهید و اگر این متد نیاز به مجوز خاصی دارد مانند نمونه زیر، آن را حتما ذکر کنید:
/** * * <p> * check network is available or not <br/> * internet connection is not matter,for check internet connection refer to IsInternetConnected() Method in this class * </p> * <p> * Required Permission : <b>android.permission.ACCESS_NETWORK_STATE</b> * </p> * @param context * @return returns true if a network is available */ public boolean isNetworkAvailable(Context context) { ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); NetworkInfo activeNetworkInfo = connectivityManager.getActiveNetworkInfo(); return activeNetworkInfo != null && activeNetworkInfo.isConnected(); }
همچنین اگر، مورد خاص دیگری مثل بالا بود، حتما آن را ذکر کنید. میتوانید از تگ گذاری در کامنت ها
نیز استفاده کنید. از ویژگیهای کامنت todo در اندروید استادیو این است
که میتوانید در حین کار با سیستم گیت نیز از آن بهره ببرید و قبل از کامیت
کردن کد، کدهای todo به شما یادآوری شوند و هر پیکربندی را که لازم دارید، روی آن انجام دهید.
دو.
از یک کلاس واحد جهت استفاده از اطلاعات عمومی و یا ثابتها استفاده
نمایید. این اطلاعات میتوانند شامل: مسیرها، آدرسهای وب سرویس، شماره
اختصاصی هر نوتیفیکیشن و .... باشند. برای اینکار میتوان هر کدام از اطلاعات را
داخل یک کلاس قرار داد و همه این کلاسها را به صورت استاتیک تعریف کنید تا
بدین شکل در دسترس قرار بگیرند (از الگوی singleton هم میتوان استفاده
کرد).
public class ProjectSettings { public static NotificationsId=new NotificationsId(); public static UrlAddresss=new UrlAddresss(); public static SdPath=new SdPath(); ...... }
ProjectSettings.NotificationsId.UpdateNotificationId
بدین شکل هم به طور ساده و مفهومی صدا زده میشود و هم اینکه در همه جای
برنامه این ثابتها و مقادیر قابل استفاده هستند. به عنوان مثال به شماره
هر نوتیفیکیشن از همه جا دسترسی دارید و هم اینکه شمارهای تکراری اشتباها
انتخاب نمیشود.
سه. حداکثر استفاده از اینترفیس را به خصوص برای UI انجام بدهید:
به عنوان نمونه، بسیاری نمایش یک toast را به شکل زیر انجام میدهند:
سه. حداکثر استفاده از اینترفیس را به خصوص برای UI انجام بدهید:
به عنوان نمونه، بسیاری نمایش یک toast را به شکل زیر انجام میدهند:
Toast.makeText(context, message, Toast.LENGTH_SHORT).show();
یا اینکه برای یک دیالوگ مستقیما و در جا همانجا به کدنویسی مشغول میشوند.
این روشها هیچ مشکلی ندارند ولی در آینده نگهداری کد را مشکل میکنند.
مثلا تصور کنید شما بسیاری از جاهای برنامه، Toast زدید و حالا قصد دارید در
نسخه بعدی برنامه، toastهای دلخواه و یا custom ایی را ایجاد کنید. در این صورت
مجبورید کل برنامه را رصد کرده و هر جا toast هست آن را تغییر دهید. در
اینجا هم اصول DRY را نادیده گرفتهاید و هم زحمت شما زیاد شدهاست و حتی
ممکن است یک یا چندتایی از قلم بیفتند. برای دیالوگها هم بدین صورت خواهد
بود و خیلی از مسائل دیگر. به همین جهت استفاده از اینترفیسها توصیه
میشود و فردا نیز اگر باز یک کلاس دیگر را نوشتید، خیلی راحت آن را با کلاس
فعلی تعویض میکنید.
public interface IMessageUI { void ShowToast(Context context,String message); } public class MessageUI impelement IMessageUI { public void ShowToast(Context context,string message) { Toast.makeText(context, message, Toast.LENGTH_SHORT).show(); } }
چهار. اگر برای اولین بار است وارد اندروید میشوید، خوب چرخههای یک شیء، چون اکتیویتی یا فراگمنت را یاد بگیرید تا در آینده با مشکلات خاصی روبرو نشوید.
به عنوان مثال درست است که اولین رویداد فراخوانی در onCreate رخ میدهد ولی
همیشه محل مناسبی برای دریافت دیتاها در زمان اولیه نیست. به عنوان مثال
تصور کنید که لیستی در اکتیویتی A دارید و به اکتیویتی B میروید و یک آیتم
به اطلاعات اضافه میشود و موقعی که به اکتیویتی A بر میگردید، زیاد تعجب نکنید که لیست دقیقا به
همان شکل قبلی است و خبری از آیتم جدید نیست.
چون اکتیویتی در حالت stop بوده و بعد از آن به حالت Resume رفته و تا موقعی که این اکتیویتی از حافظه خارج نشود یا گوشی چرخش نداشته باشد، واکشی دیتاها صورت نخواهد گرفت. پس بهترین مکان در این حالت، رویداد OnStart است که در هر دو وضعیت صدا زده میشود؛ یا اینکه در OnRestatr روی آداپتور تغییرات جدید را اعمال کنید تا نیازی به واکشی مجدد دادهها نباشد.
چون اکتیویتی در حالت stop بوده و بعد از آن به حالت Resume رفته و تا موقعی که این اکتیویتی از حافظه خارج نشود یا گوشی چرخش نداشته باشد، واکشی دیتاها صورت نخواهد گرفت. پس بهترین مکان در این حالت، رویداد OnStart است که در هر دو وضعیت صدا زده میشود؛ یا اینکه در OnRestatr روی آداپتور تغییرات جدید را اعمال کنید تا نیازی به واکشی مجدد دادهها نباشد.
به طور خلاصه نحوه اجرای رویدادها بدین شکل است که ابتدای رویداد OnCreate اجرا میشود که هنوز هیچ UI ئی در آن پیاده سازی نشدهاست و شما در اینجا موظفید Layout خود را معرفی کنید. رویداد OnStart بعد از آن موقعی که UI آماده شده است، اجرا میگردد. سپس رویداد OnResume اجرا میشود.
تا بدینجا اکتیویتی مشکلی ندارد و میتواند به عملیات پاسخ دهد ولی اگر قسمتی از اکتیویتی در زیر لایهای از UI پنهان شود، به عنوان مثال دیالوگی باز شود که قسمتی از اکتیویتی را بپوشاند و یا منویی همانند تلگرام قسمتی از صفحه را بپوشاند، اکتیویتی اصطلاحا در حالت Pause قرار گرفته و بدین ترتیب رویداد OnPause اجرا میگردد. اگر همین دیالوگ بسته شود و مجددا اکتیویتی به طور کامل نمایان گردد مجددا رویداد OnResume اجرا میگردد.
از رویداد Onresume میتوانید برای کارهایی که بین زمان آغاز اکتیویتی و برگشت اکتیویتی مشترکند استفاده کرد. اگر به هر نحوی اکتیویتی به طور کامل پنهان شود٬، به این معناست که شما به اکتیویتی دیگری رفتهاید رویداد OnStop اجرا شدهاست و در صورت بازگشت، رویداد OnRestart اجرا خواهد شد. ولی اگر مدت طولانی از رویداد OnStop بگذرد احتمال اینکه سیستم مدیریت منابع اندروید، اکتیویتی شما را از حافظه خارج کند زیاد است و رویداد OnDestroy صورت خواهد گرفت. در این حالت دفعه بعد، مجددا همه عملیات از ابتدا آغاز میگردند.
تا بدینجا اکتیویتی مشکلی ندارد و میتواند به عملیات پاسخ دهد ولی اگر قسمتی از اکتیویتی در زیر لایهای از UI پنهان شود، به عنوان مثال دیالوگی باز شود که قسمتی از اکتیویتی را بپوشاند و یا منویی همانند تلگرام قسمتی از صفحه را بپوشاند، اکتیویتی اصطلاحا در حالت Pause قرار گرفته و بدین ترتیب رویداد OnPause اجرا میگردد. اگر همین دیالوگ بسته شود و مجددا اکتیویتی به طور کامل نمایان گردد مجددا رویداد OnResume اجرا میگردد.
از رویداد Onresume میتوانید برای کارهایی که بین زمان آغاز اکتیویتی و برگشت اکتیویتی مشترکند استفاده کرد. اگر به هر نحوی اکتیویتی به طور کامل پنهان شود٬، به این معناست که شما به اکتیویتی دیگری رفتهاید رویداد OnStop اجرا شدهاست و در صورت بازگشت، رویداد OnRestart اجرا خواهد شد. ولی اگر مدت طولانی از رویداد OnStop بگذرد احتمال اینکه سیستم مدیریت منابع اندروید، اکتیویتی شما را از حافظه خارج کند زیاد است و رویداد OnDestroy صورت خواهد گرفت. در این حالت دفعه بعد، مجددا همه عملیات از ابتدا آغاز میگردند.
پنج.
سرویس را با تردهای UI ترکیب نکنید. بعضا دیده میشود که کاربران
AsyncTask را داخل سرویس استفاده میکنند ولی این را بدانید که سرویس یک
ترد پردازشی جداگانه است و تضمینی برای ارتباط با UI به شما نمیدهند. هر
چند گوگل جدیدا تمهیداتی را برای آن اندیشیده است که به شما اجازه اینکار را
نمیدهد. ولی اگر باز هم اندروید استادیو به شما خوردهای نگرفت، خودتان این
قانون را اجرا کنید. قرار نیست یک AsyncTask با سرویس ترکیب شود.
شش. اگر برنامه شما قرار است در چندین حالت مختلفی که اتفاق میافتد، یک کار خاصی را انجام دهد، برای برنامهتان یک Receiver بنویسید و در آن کدهای تکراری را نوشته و در محلهای مختلف وقوع آن رویدادها، رسیور را صدا بزنید. برای نمونه برنامه تلگرام یک سرویس پیام رسان پشت صحنه دارد که در دو رویداد قرار است اجرا شوند. یکی موقعی که گوشی بوت خود را تکمیل کرده است و در حال آغاز فرایندهای سیستم عامل است و دیگر زمانی است که برنامه اجرا میشود. در اینجا تلگرام از یک رسیور سیستمی برای آگاهی از بوت شدن و یک رسیور داخل برنامه جهت آگاهی از اجرای برنامه استفاده میکند و هر دو به یک کلاس از جنس BroadcastReceiver متصلند:
<receiver android:name=".AppStartReceiver" android:enabled="true"> <intent-filter> <action android:name="org.telegram.start" /> <action android:name="android.intent.action.BOOT_COMPLETED" /> </intent-filter> </receiver>
public class AppStartReceiver extends BroadcastReceiver { public void onReceive(Context context, Intent intent) { AndroidUtilities.runOnUIThread(new Runnable() { @Override public void run() { ApplicationLoader.startPushService(); } }); } }
برنامه تلگرام حتی برای حالتهای پخش هم رسیورها استفاده کرده است که در همین رسیور وضعیت تغییر پلیر مشخص میشود:
<receiver android:name=".MusicPlayerReceiver" > <intent-filter> <action android:name="org.telegram.android.musicplayer.close" /> <action android:name="org.telegram.android.musicplayer.pause" /> <action android:name="org.telegram.android.musicplayer.next" /> <action android:name="org.telegram.android.musicplayer.play" /> <action android:name="org.telegram.android.musicplayer.previous" /> <action android:name="android.intent.action.MEDIA_BUTTON" /> <action android:name="android.media.AUDIO_BECOMING_NOISY" /> </intent-filter> </receiver>
اینگونه تلگرام میتواند از همه جا سرویس را کنترل کند. مثلا موقعی که
دانلود یک موزیک تمام شده، سریعا پخش آن موزیک دانلود شده را آغاز کند.
هفت. اگر از یک ORM برای لایه دادهها استفاده میکنید (قبلا در سایت جاری در مورد ORMهای اندروید صحبت کردهایم و ORMهای خوش دستی که خودم از آنها استفاده میکنم ActiveAndroid و CPORM هستند که هم کار کردن با آنها راحت است و هم اینکه امکانات خوبی را عرضه میکنند) در این نوع ORMها شما نباید انتظار چیزی مانند EF را داشته باشید و در بعضی موارد باید کمی خودتان کمک کنید. به عنوان مثال در Active Android برای ایجاد یک inner join باید به شکل زیر بنویسید:
هفت. اگر از یک ORM برای لایه دادهها استفاده میکنید (قبلا در سایت جاری در مورد ORMهای اندروید صحبت کردهایم و ORMهای خوش دستی که خودم از آنها استفاده میکنم ActiveAndroid و CPORM هستند که هم کار کردن با آنها راحت است و هم اینکه امکانات خوبی را عرضه میکنند) در این نوع ORMها شما نباید انتظار چیزی مانند EF را داشته باشید و در بعضی موارد باید کمی خودتان کمک کنید. به عنوان مثال در Active Android برای ایجاد یک inner join باید به شکل زیر بنویسید:
From query= new Select() .from(Poem.class) .innerJoin(BankPoemsGroups.class) .on("poems.id=bank_poems_groups.poem") .where("BankGroup=?", String.valueOf(groupId)); return query.execute();
@Table(name="poems") public class Poem extends Model { public static String tableName="poems"; public static String codeColumn="code"; public static String titleColumn="title"; public static String bookColumn="book"; ...... @Column(name="code",index = true) public int Code; @Column(name="title") public String Title; @Column(name="book") public Book Book; .....}
From query= new Select() .from(Poem.class) .innerJoin(BankPoemsGroups.class) .on(Poem.TableName+"."+ Poem.IdColumn+"="+ BankPoemsGroups.TableName+"."+ BankPoemsGroups.PoemColumn) .where(Poem.BankGroupColumn+"=?", String.valueOf(groupId)); return query.execute();
حالا کمی بهتر شد. هم برای تغییر آینده بهتر شد و هم اینکه احتمال خطای
تایپی کاهش یافت. ولی باز هم ایجاد کوئری هنوز سخت است و نوشتن مرتب یک
رابطه جوین و شرطی و چسباندن مداوم رشتهها کار خسته کنندهای است و احتمال
خطای سهوی و انسانی هم در آن بالاست. برای رفع این مشکل بهتر است یک کلاس
جدید برای ساخت این کوئریها داشته باشیم که یک نمونه از آن را در این
پایین میبینید:
public class QueryConcater { public String GetInnerJoinQuery(String table1,String field1,String table2,String field2) { String query=table1 +"." +field1+"="+table2+"."+field2; return query; } ...... }
return new Select() .from(Color.class) .innerJoin(ProductItem.class) .on(queryConcater.GetInnerJoinQuery(ProductItem.TableName, ProductItem.ColorColumn, Color.TableName)) .where(queryConcater.WhereConditionQuery (ProductItem.TableName, ProductItem.ProductColumn), productId) .execute();
در دستورات بالا از این کلاس دو متد برای کوئری جوین و یکی هم برای ساخت
شرط ایجاد شده است و مقادیر به صورت پارامتر داده شدهاند. این الگو کمک
میکند که اگر هم این تکه کد اشتباه باشد، با تغییر یکجا بقیه کدها هم تغییر
میکنند و اگر در آینده هم ORM تغییر یافت، نحوه کوئری نویسیها در این کلاس
تغییر کنند، نه اینکه در طول لایه سرویس پراکنده باشند.
هشت. سعی کنید همیشه از یک سیستم گزارش خطا در اپلیکیشن خود استفاده کنید. در حال حاضر معروفترین سیستم گزارش خطا Acra است که میتوانید backend آن را هم از اینجا تهیه کنید و اگر هم نخواستید، سایت Tracepot امکانات خوبی را به رایگان برای شما فراهم میکند. از این پس با سیستم آکرا شما به یک سیستم گزارش خطا متصلید که خطاهای برنامه شما در گوشی کاربر به شما گزارش داده خواهد شد. این گزارشها شامل:
هشت. سعی کنید همیشه از یک سیستم گزارش خطا در اپلیکیشن خود استفاده کنید. در حال حاضر معروفترین سیستم گزارش خطا Acra است که میتوانید backend آن را هم از اینجا تهیه کنید و اگر هم نخواستید، سایت Tracepot امکانات خوبی را به رایگان برای شما فراهم میکند. از این پس با سیستم آکرا شما به یک سیستم گزارش خطا متصلید که خطاهای برنامه شما در گوشی کاربر به شما گزارش داده خواهد شد. این گزارشها شامل:
- وضعیت گوشی در حین باز شدن برنامه و در حین خطا چگونه بوده است.
- مشخصات گوشی
- این خطا به چه تعداد رخ داده است و برای چه تعداد کاربر
- گزارش گیری بر اساس اولین تاریخ رخداد خطا و آخرین تاریخ، نسخه سیستم عامل اندروید، ورژن برنامه شما و...
نه. آکرا همانند Elmah نمیتواند خطاهای catch شده را دریافت کند. برای حل این مشکل عبارت زیر را در catchها بنویسید:
ACRA.getErrorReporter().handleException(caughtException);
ده. بر خلاف سیستم دات نت که شما اجباری به استفاده از Try Catchها ندارید. در
جاوا اینگونه نیست و هر متدی که Throw روی آن انجام شده باشد مستلزم استفاده از catch است. به همین دلیل در شماره نه
گفتیم که چگونه باید این مشکل را حل کنیم. ولی در بسیاری از اوقات پیش
میآید که ما داریم از ماژولهای متفاوتی استفاده میکنیم که جدا از ماژول
اصلی برنامه هستند و این مورد باعث میشود که بعضی افراد یا Acra را در همه
ماژولها صدا بزنند یا اینکه بی خیال آن شوند. ولی کار راحتتر این است که
شما هم همانند برنامه نویسان جاوا متد خود را به Throw مزین کنید تا در
هنگام استفاده از آن در برنامه اصلی نیاز به catch شدن باشد. در واقع شما
نباید catchها را داخل یک کتابخانه جدا و مستقل قرار دهید و روش صحیح هم
همین است حالا چه استفاده از آکرا نیاز باشد و چه نباشد.
نمونه اشتباه:
public void CopyFile(String source,String destination,CopyFileListener copyFileListener) { try { InputStream in = new FileInputStream(source); OutputStream out = new FileOutputStream(destination); long fileLength=new File(source).length(); // Transfer bytes from in to out byte[] buf = new byte[64*1024]; int len; long total=0; while ((len = in.read(buf)) > 0) { out.write(buf, 0, len); total+=len; copyFileListener.PublishProgress(fileLength,total); } in.close(); out.close(); } catch (IOException e) { e.printStackTrace(); } }
public void CopyFile(String source,String destination,CopyFileListener copyFileListener) throws IOException { InputStream in = new FileInputStream(source); OutputStream out = new FileOutputStream(destination); long fileLength=new File(source).length(); // Transfer bytes from in to out byte[] buf = new byte[64*1024]; int len; long total=0; while ((len = in.read(buf)) > 0) { out.write(buf, 0, len); total+=len; copyFileListener.PublishProgress(fileLength,total); } in.close(); out.close(); }
در این مقاله آموزشی قصد داریم به یکی از مهمترین و اساسیترین مفاهیم تعریف شده در پایگاه داده بنام تراکنشها بپردازیم. بعنوان تعریف میتوان اینگونه بیان نمود که تراکنش یک واحد کاری منطقی است که عملی را بر روی پایگاه داده انجام میدهد. عموما تراکنشها دنباله ای از عملیات پایگاه داده هستند که رویه هم رفته انجام یک کار یا وظیفه را بر عهده دارند. نکته مهمی که در مورد تراکنشها مطرح میشود اینست که آنها باید به گونه ای مدیریت شوند که پایگاه داده را از یک وضعیت سازگار و درست (consistent) به وضعیت سازگار دیگری ببرند. به بیان دیگر اگر تراکنش از چند عملیات تشکیل شده باشد، پس از پایان اجرای تمامی عملیات مربوط به تراکنش نباید در دادههای پایگاه داده هیچ تناقضی با قوانین پایگاه داده (integrity rules) بوجود بیاید. مزیت استفاده از تراکنش نیز همین مسئله است که به توسعه دهنده نرم افزار این اطمینان را میدهد که صحت و درستی پایگاه داده در اثر اجرای دستورات او از بین نخواهد رفت. علاوه بر آن اگر در حین اجرای یکی از دستورات خللی ایجاد گردد، پایگاه داده دوباره به وضعیت سازگار قبلی خود باز گردانده خواهد شد. نسلهای اولیه سیستمهای مدیریت پایگاه داده فاقد پیاده سازی تراکنش بودند و بهمین دلیل توسعه دهندگان کار بسیار مشکلی در شبیه سازی این واحدهای یکپارچه منطقی داشتند. خوشبختانه اکثر DBMSهای امروزی این مفهوم مهم را پشتیبانی میکنند و نیازی به نگرانی در مورد پیاده سازی آن نیست. تنها کاری که لازم است انجام گیرد کسب مهارت در زمینه استفاده از آنهاست.
تعریف تراکنشها و مشخص کردن عملیات موجود در آنها اغلب توسط خود توسعه دهنده برنامه صورت میگیرد. اوست که تعیین میکند تراکنشش باید چه عملیاتی را با چه ترتیبی انجام دهد. اما در کنار این قسم از تراکنشها که توسط کاربران تعریف میشود، تراکنشهای دیگری نیز وجود دارند که توسط خود سیستم مدیریت پایگاه داده تعریف میشوند. به این قبیل تراکنشها که واحدهای کاری بسیار کوچک و عموما تجزیه ناپذیری هستند تراکنشهای خودکار یا auto transactions گفته میشود. بعنوان مثال اگر ما تراکنشی را تعریف کرده باشیم که شامل یک عمل خواندن و یک عمل درج باشد، در هنگام اجرا سیستم این تراکنش را به دو تراکنش کوچکتر میشکند که در یکی عمل خواندن و در دیگری عملی نوشتن و درج را انجام میدهد. البته توجه داشته باشید که اگر چه این دو عملیات جدا و مستقل از هم اجرا میشوند اما رابطه منطقی آنها با یکدیگر حفظ میشود و در صورت خللی در یکی از آنها اثر دیگری نیز بازگردانده شده و پایگاه داده دوباره به حالت قبل از جرا برگردانده میشود. به این کار عمل undo شدن تراکنش گفته میشود.
گفتیم که تعریف تراکنش توسط کاربر صورت میپذیرد و مدیریت آن بر عهده پایگاه داده قرار میگیرد. در این میان نکته حائز اهمیتی وجود دارد که در اینجا باید به آن اشاره شود. اندازه تراکنش نقشی بسیار موثر در کارایی پایگاه داده ایفا میکند. توجه داشته باشید که اندازه تراکنشها نباید خیلی بزرگ باشد. چراکه منجر به بزرگ شدن بیرویه فایل مربوط به ثبت وقایع پایگاه داده (log file) میگردد. تمامی علیات تاثیر گذار بر روی پایگاه داده در این فایل ثبت میشوند تا در موقع لزوم بتوان با استفاده از عمل بازیابی و ترمیم پایگاه داده (recovery) را انجام داد. بزرگ بودن این فایل در هنگام ترمیم میتواند بر روی کارایی تاثیر گذار باشد. علاوه بر این موضوع اندازه تراکنشها اثر سوء دیگری نیز میتواند در پی داشته باشد و آن محدود نمودن درجه همروندی است. یعنی اگر اندازه تراکنش بیش از حد معمول باشد ممکن است بر روی تعداد تراکنش هایی که میتوانند بطور موازی و همزمان اجرا شوند تاثیر منفی بگذارد. چرا که معمولا در آغاز تراکنش بر روی منابعی که مورد استفاده تراکنش قرار میگیرد قفل گذاری میشود تا بگونه ای مسئله نواحی بحرانی حل شود. این قفلها زمانی آزاد میشوند که تمامی عملیات داخل تراکنش بطور کامل اجرا شده باشند یا اینکه مشکلی در حین اجرا بوجود آید. در این صورت هرچه تراکنش بزرگتر باشد اجرای آن بیشتر طول خواهد کشید و در نتیجه قفلهای آن نیز دیرتر آزاد میشوند. بدین ترتیب سایر تراکنش هایی که میخواهند از منابع مشترک استفاده کنند باید تا پایان اجرای تراکنش بزرگ ما منتظر بمانند. این مسئله یعنی کاهش درجه اجرای موازی با همروندی که اگر در سیستمهای بزرگ به آن دقت نشود به گلوگاهی تبدیل خواهد شد و کارایی را به نحو قابل توجهی کاهش میدهد.
تعریف تراکنشها :
بدنه اصلی هر تراکنش را چهار کلمه کلیدی تشکیل میدهند که البته ممکن است صریحا در تعریف توسط کاربر لحاظ نشوند اما این چهار کلمه کلیدی باید در تمامی تراکنشها چه بصورت صریح و چه بصورت ضمنی آورده شوند. این کلمات عبارتند از BEGIN TRANSACTION، END TRANSACTION، ROLLBACK و COMMIT. کلمات کلیدی BEGIN TRANSACTION و END TRANSACTION همانطور که از نامشان پیداست آغاز و پایان یک تراکنش را نشان میدهد. اینکه تراکنش از چه نقطه ای آغاز و در چه نقطه ای به پایان رسیده است برای مدیریت آن بسیار مهم و حیاتی است بخصوص در مواقعی که در حین انجام مشکلی پیش بیاید. از کلمه کلیدی ROLLBACK هنگامی استفاده میکنیم که بخواهیم تغییراتی که تا این لحظه بر روی پایگاه داده صورت گرفته است را مجددا بی اثر کنیم و پایگاه داده را به حالت پیش از شروع تراکنش بازگردانیم. توجه داشته باشید که در برخی از مواقع ممکن است این کلمه را خودمان در بدنه تراکنش مستقیما قرار دهیم. بعنوان مثال یک خطای منطقی را در بخشی از روال انجام تراکنش با یک عبارت شرطی تشخیص میدهیم و با استفاده از ROLLBACK به مدیریت پایگاه داده اعلام میکنیم که عملیات بازگردانی را انجام بده. گاهی ممکن است ما صریحا این کلمه را در تراکنش نیاورده باشیم اما درحین انجام تراکنش خطایی رخ دهد، در این صورت خود سیستم مدیریت پایگاه داده خطا را شناسایی کرده و عملیات مربوط به ROLLBACK را انجام میدهد تا صحت و سازگاری پایگاه داده حفظ گردد. کلمه کلیدی COMMIT نیز باید در انتهای تراکنش آورده شود تا به مدیریت پایگاه داده اعلام شود که عملیات کامل شده است و تغییرات باید در پایگاه داده بطور فیزیکی اعمال شوند. توجه داشته باشید که تا زمانی که مدیریت پایگاه داده به دستور COMMIT نرسیده باشد، تغییرات را جهت اعمال بر روی حافظه فیزیکی به واحد مدیریت حافظه نمیدهد و بنابراین این تغییرات تا پیش از COMMIT از چشم سایر کاربران مخفی خواهد ماند.
نکته ای که در اینجا وجود دارد این است که فرمان COMMIT به معنی این نیست که بلافاصله تغییرات بر روی دیسک و حافظه جانبی نوشته میشود. بلکه به این معنی است که تمامی عملیات تراکنش با موفقیت انجام شده است و سیستم مدیریت پایگاه میتواند آنها را برای نوشته شدن در حافظه جانبی به واحد مدیریت حافظه تحویل دهد. در اینجاست که یکی دیگر از پیچیدگیهای طراحی سیستم مدیریت پایگاه داده روشن میشود و آن اینست که این سیستم باید بنحوی این دادهها را در فاصله بین COMMIT و نوشته شدن در حافظه برای سایر کاربران قابل مشاهده نماید.
در ادامه نمونه ای از یک تراکنش را مشاهده میکنید :
BEGIN TRANSACTION; INSERT INTO SP RELATION {S# S#(‘S5’), P# P#(‘P1’), QTY QTY(1000)}}; IF any error occurred THEN GOTO UNDO; END IF; UPDATE P WHERE P# = P#(‘P1’) TOTAL:=TOTAL + QTY(1000); IF any error occurred THEN GOTO UNDO; END IF; COMMIT; GOTO FINISH; UNDO: ROLLBACK; FINISH: RETURN;
همانطور که مشاهده میکنید تراکنش بالا دارای تمامی بخشهای اصلی تراکنش که ذکر شد میباشد. البته این امکان وجود دارد که صراحتا این کلمات را در تعریف بدنه تراکنش نیاوریم. بعنوان مثال میتوان از آوردن COMMIT صرف نظر کرد. در این صورت خود سیستم مدیریت پایگاه داده پس از اجرای آخرین دستور تراکنش در صورتی که هیچ خطایی رخ نداده باشد بطور خودکار عمل COMMIT را انجام میدهد. این امر در مورد ROLLBACK و END نیز صادق است. اما در مورد BEGIN TRANSACTION نکته ای وجود دارد و آن اینست که ما باید به پایگاه داده اعلام کنیم که بطور خودکار در پایان یک تراکنش برای شروع تراکنش بعدی BEGIN TRANSACTION را لحاظ کند. این کار را باید با دستور SET IMPLICIT TRANSACTION ON انجام دهیم.
گفتیم که وقوع خطا میتواند توسط برنامه نویس شناسایی شود و یا توسط سیستم. یک نمونه از تشخیص خطا توسط برنامه نویس را در مثال بالا مشاهده میکنید. عموما دراین قبیل خطاها پس از انجام عمل ROLLBACK تراکنش UNDO شده و اجرای آن متوقف میشود که اصطلاحا میگوییم تراکنش ABORT میشود. اما در مورد خطاهایی که خود سیستم تشخیص میدهد وضع به این منوال نیست. در شرایط خطا، سیستم پس از UNDO کردن تراکنش عموما آن را ABORT نمیکند بلکه مجددا اجرا میکند که به این عمل REDO گفته میشود. در بخشهای بعدی بطور کامل در مورد دو عمل REDO و UNDO بحث خواهیم کرد.
ویژگیهای تراکنشها :
هر تراکنشی که در سیستم اجرا میشود باید دارای چهار ویژگی باشد. در حقیقت این ویژگیها باید به نحوی تامین شوند تا مقصود و هدف کلی تراکنشها که بردن پایگاه داده از یک وضعیت صحیح به وضعیت صحیح دیگری است برآورده شود. در ادامه هر کدام را یک به یک شرح میدهیم :
Atomicity:
اولین ویژگی ای که یک تراکنش باید داشته باشد اینست که اثری که بر روی پایگاه داده ما میگذارد اثری کامل و بدون نقص باشد. به این معنا که اگر قرار است مجموعه از عملیات تغییراتی را اعمال کنند باید تمامی آن تغییرات بر روی جداول اعمال شوند. در صورتی که حتی یکی از عملیات با مشکل مواجه شود باید تاثیرات عملیات قبلی بازگردانده شوند. به بیانی سادهتر در تراکنش یا تمامی عملیات باید بطور کامل انجام شوند و یا هیچ یک از آنها نباید اجرا شده و اثرگذار باشند. به این ویژگی Atomicity گفته میشود.
توجه داشته باشید که در حین اجرای یک تراکنش احتمالا پایگاه داده به وضعیت غیر سازگار و نادرست خواهد رفت. یکی از وظایف سیستم مدیریت پایگاه داده اینست که این وضعیت ناسازگار را از دید سایر تراکنشها مخفی بسازد تا زمانی که تراکنش COMMIT شود.
در مورد Atomicity در برخی مقالات و مطالب آموزشی گفته میشود که این مفهوم یعنی تراکنش نباید قابل شکسته شدن باشد که این تعریف چندان صحیحی از Atomicity نمیباشد. چراکه یک تراکنش در حین اجرا ممکن است بارها و بارها شکسته شود و یا از یک تراکنش بر روی تراکنش دیگری سوئیچ شود. بنابراین مراد از Atomicity همان واحد کاری کامل است نه واحد کاری غیر قابل شکسته شدن.
Consistency:
تراکنش باید تغییرات را به گونه ای اعمال کند که پایگاه داده را از وضعیت صحیح به وضعیت صحیح دیگری ببرد.از آنجا که صحت پایگاه داده را قوانین جامعیت پایگاه داده (integrity rules) تضمین میکنند بنابراین تراکنش باید تغییرات را بگونه ای اعمال کند که این قوانین نقض نشوند. به این خاصیت از تراکنشها Consistency گفته میشود.
Isolation:
عموما برنامههای مبتنی بر پایگاه در دنیای واقعی برنامه هایی چند کاربره هستند که در برخی از آنها ممکن است میلیونها تراکنش بطور همزمان با یکدیگر در حال اجرا باشند. در چنین حجم بالایی یکی از مسائلی که مطرح میشود اینست که تراکنشهای موازی تاثیر سوئی بر روی یکدیگر نداشته باشند. بعنوان مثال یکی از مشکلاتی که در اجرای همروند و موازی تراکنشها ممکن است رخ دهد مشکل lost update میباشد. بر همین اساس یکی دیگر از ویژگی هایی که یک تراکنش باید داشته باشد که اینست که اثر سوئی بر روی تراکنشهای همروند دیگر نداشته باشد. به این ویژگی Isolation گفته میشود.
در مورد ایزولاسیون (isolation) تراکنشها باید گفت که ایزولاسیون سطوح و درجه بندی هایی دارد که هر کدام از این سطوح مشخص میکنند که تراکنشها تا چه حدی اجازه دارند بر روی هم تاثیر گذار باشند. در واقع این سطوح، میزان عایق بندی تراکنشها را نسبت به یکدیگر مشخص میکنند. هرچه درجه ایزولاسیون بالاتر باشند به این معنی است که تراکنشها تاثیر کمتری بر روی یکدیگر خواهند داشت. خوب در ظاهر ممکن است این قضیه بسیار خوب در نظر بیاید چرا که به ما اطمینان می دهد که اثر ناخواسته ای بر روی یکدیگر نخواهند داشت. اما باید این نکته را نیز در نظر بگیریم که هر چه درجه ایزولاسیون بالاتر باشد درجه همروندی (concurrency) پایین میآید و این به معنای کاهش امکان پردازش موازی تراکنشها میباشد. این مسئله در مورد پایگاههای داده بسیار بزرگ که میلیونها تراکنش همزمان در خواست اجرا داده میشوند به یک مسئله بحرانی و یک گلوگاه میتواند تبدیل شود. بنابراین تعیین درجه ایزولاسیون بسیار مهم است و باید با درنظر گرفته شرایط پروژه انجام گیرد.
اینکه پایگاه داده ما در چه سطحی از ایزولاسیون باید عمل نماید توسط کاربر تعیین میشود. البته بحث در مورد ارجای موازی تراکنشها و ایزولاسیون آنها بسیار مفصل است و انشاالله در مطلبی دیگر به آن خواهیم پرداخت.
Durability:
تغییراتی که تراکنشها بر روی پایگاه داده میگذارند باید بعد از COMMIT شدن آن پایدار و قابل مشاهده باشند. به این خاصیت durability گفته میشود.
وضعیتهای یک تراکنش :
تراکنشها در سیستم همانند یک موجودیت (entity) فعال است هستند. همانطور که میدانید سادهترین موجودیت فعال در سیستم فرآیندها (process) میباشند که cpu را بعنوان یک ابزار در اختیار گرفته و وظایفی را انجام میدهند. تراکنش نیز یک موجودیت فعال میباشد و همانند سایر موجودیتهای فعال دارای وضعیت هایی (state) میباشند که در ادامه هریک شرح داده شده اند :
• فعال (Active) : تراکنشی که در حالت اجرا است در وضعیت فعال میباشد.
• کامیت جزئی (Partially Committed): پس از اجرای آخرین دستور تراکنش به وضعیت کامیت جزئی میرود.
• شکست (Failed): در این وضعیت، در روند اجرا خطایی رخ داده و اجرای ادامه تراکنش امکان پذیر نمیباشد.
• خاتمه (Aborted): پس از تشخیص خطا تراکنش میتواند به وضعیت Aborted که در انجا اجرا متوفق شده و تغییرات ROLLBACK میشوند.
• Committed: در این وضعیت اجرای تراکنش با موفقیت انجام شده و تراکنش پایان میپذیرد.
در ادامه نمودار حالت تراکنشها نشاد داده شده است :
نکته ای که در اینجا لازم به ذکر است اینست که در حالت پس از حالت شکست به دو شکل امکان ادامه کار وجود دارد. در صورتی که خطای منطقی در تراکنش دیده شود که عموما توسط کاربر تشخیص داده میشود تراکش پس از شکست به حالت خاتمه برده میشود و کار تمام است. اما در برخی از شرایط خطایی سیستم توسط خود سیستم رخ میدهد. که در چنین حالاتی پس از شکست تراکنش مجددا تراکنش ممکن است به حالت فعال برگردانده شود و اجرای ان دوباره از ابتدای تراکنش شروع شود. به این وضعیت اصطلاحا REDO شدن تراکنش گفته میشود که در بخش RECOVERY و ترمیم پایگاه داده باید به آن پرداخته شود.
اعمال زمان COMMIT:
در زمان COMMIT (بصورت صریح و یا ضمنی) باید اعمالی انجام شود که در اینجا به آن میپردازیم. اولین کاری که صورت میگیرد اینست که سیگنالی به DBMS ارسال میشود مبنی بر اینکه تراکنش با موفقیت به پایان رسیده است. پس از اینکار سیستم مدیریت پایگاه داده شروع به آزاد کردن قفل هایی میکند که در طول اجرای تراکنش بر روی منابع مختلف پایگاه داده زده شده است تا از تاثیر سوء تراکنشها بر روی یکدیگر جلوگیری به عمل آید. علاوه بر کار ذکر شده تغییراتی که توسط تراکنش داده شده است باید پایدار و قابل رویت توسط سایر تراکنشها گردد.
همانطور که در بخش ابتدایی این مطلب آموزشی اشاره کردیم COMMIT به معنی نوشته شدن تغییرات بر روی دیسک سخت نیست. سیستم مدیریت پایگاه داده تنها درخواست نوشتن دادهها را به سیستم مدیریت حافظه میدهد و نوشتن ان بر عهده مدیریت حافظه میباشد. سیستم مدیریت پایگاه داده باید اطلاع داشته باشد که چه تغییراتی نوشته شده است و چه تغییراتی هنوز در حافظه نوشته نشده است. بنابراین یکی دیگر از پیچیدگیهای طراحی سیستمهای مدیریت پایگاه داده اینست که تغییراتی را برای سایرین قابل رویت کند که هنوز در حافظه سخت نوشته نشده است.
اعمال زمان ROLLBACK:
در زمان ROLLBACK ناموفق بودن تراکنش باید به DBMS اطلاع داده شود. پس از انکه سیستم مدیریت پایگاه داده مطلع شد تمامی تغییرات اعمال شده تا آن لحظه را UNDO میکند. البته توجه داشته باشید که در این زمان همانند زمان COMMIT قفلها نیز آزاد میشوند تا سایر تراکنشها بتوانند از منابع در اختیار این تراکنش استفاده کنند و درجه همروندی پایین نیاید.
پردازش پیامها در زمان اجرای تراکنشها :
به مثال زیر توجه کنید.
Read Sav_Amt Sav_Amt := Sav-Amt - 500 if Sav-Amt <0 then do put (“insufficient fund”) rollback end else do Write Sav_Amt Read Chk_Amt Chk_Amt := Chk_Amt + 500 Write Chk-Amt put (“transfer complete”) End transaction
در تراکنش بالا مبلغ 500 دلار از حساب فردی برداشته شده و به حساب دیگر او منتقل میشود. همانطور که مشاهده میکنید در خلال اجرای یک تراکنش ممکن است پیام هایی را به کاربر نمایش دهیم. حال در نظر بگیرید که در حین اجرا ما پیامی را در خروجی نمایش میدهیم و پس از آن تراکنش با شکست مواجه شده و ROLLBACK میگردد. در این شرایط پیامی به کاربر مبنی بر انتقال موفق نمایش داده شده است در حالی که در عمل تراکنش با شکست رو به رو شده است. برای حل این مشکل در ضمن کار پیامهای مختلفی که در خروجی باید نمایش داده شوند بافر میشوند تا پس از COMMIT یا ROLLBACK شدن به کاربر نمایش داده شوند. توجه داشته باشید که در زمان بافر کردن پیام ها، انها در دو گره پیامهای مربوط به COMMIT و پیامهای زمان ROLLBACK تقسیم میشوند تا هرکدام در شرایط خود نمایش داد شوند. این عمل توسط زیر سیستمی از DBMS بنام سیستم مدیریت ارتباطات داده ای (Data Communication Manager) انجام میگیرد.
انواع تراکنشها :
تراکنشها انواع و اقسام مختلفی دارند که به سبب پیچیدگی بعضی از آنها به لحاظ پیاده سازی ممکن است آنها را در برخی از پایگاه دادهها نداشته باشیم.
Flat Transactions:
سادهترین نوع تراکنشها میباشند که در تمامی پایگاههای داده پشتیبانی میشوند و مثال هایی که تا کنون در این نقاله زده شد از این دست میباشند.
Distributed Transactions:
این قبیل تراکنشها مربوط به پایگاه دادههای توزیع شده میباشند که دادههای آنها بر روی ماشینهای مختلفی قرار دارند. بر روی هریک از این ماشینها ممکن است DBMSهای مختلفی نیز نصب شده باشد که هر یک سیستم مدیریتی مربطو به خود را دارند. از آنجایی که هر یک از این ماشینها یک سیستم مدیریت پایگاه داده مستقل دارند بنابراین قوانین جامعیتی محلی ای را نیز باید لحاظ نمایند. البته باید توجه داشت که علاوع بر این قوانین محلی یک سری قوانین سراسری نیز وجود خواهد داشت که مربوط به کل پایگاه داده توزیع شده میباشد. بعنوان مثال سیستم در یکی سیستم دانشگاهی که در شهرهای مختلفی توزیع شده است، ممکن است بخواهیم تعداد کل دانشجویان ثبت نام شده در سیستم از هزار نفر بیشتر نباشد. عموما درچنین سیستم هایی یک DBMS مدیریت کننده نیز وجود دارد که مسئول برقراری هماهنگی بین سایر DBMSها و نیز اعمال اینگونه قوانین جامعیتی سراسری میباشد.
تراکنشهای توزیع شده یک یا چند تراکنش جزئی تشکیل شده اند که ممکن است هریک از آنها مربوط به یکی از DBMSهای سیستم باشد. چنین تراکنش هایی معمولا ابتدا توسط سیستم مدیریتی مرکزی دریافت میشوند و سپس هرکدام از پرس و جوهای داخلی آن به DBMS مربوطه ارسال میگردد. اجرای هرکدام از پرس و جوهای جزئی (که خود میتوانند تراکنشی مستقل نیر باشند) بطور مستقل و محلی بر روی ماشین مربوطه اجرا شده و در انتها نیز نتیجه اجرا به سیستم مدیریتی باز گردانده میشود. سیستم مدیریتی مرکزی منتظر میماند که تمامی تراکنشها اعلام COMMIT کنند تا از انجام موفقیت آمیز همه انها اطمینان حاصل نماید. پس از کسب اطمینان کل تراکنش توسط این سیستم مرکزی COMMIT شده و در نتیجه تغییرات بر روی پایگاه داده توزیه شده اعمال میشوند. به این سیاست COMMIT کردن، کامیت دو مرحله ای یا Two-phase Commit گفته میشود. توجه داشته باشید که در صورتی که هریک از DBMSها اعلام شکست نمایند تمامی تراکنش توزیع شده ROLLBACK میگردد.
tx_begin(); execute T1 //at site D execute T2 //at site C Execute T3 //at site B … tX_commit ();
همانطور که در مثال بالا مشاهده میکنید تراکنش اصلی از سه تراکنش T1، T2 و T3 تشکیل شده که مر بوط به سه سایت متفاوت میباشند. در زمانی تراکنش اصلی COMMIT خواهد شد که هر سه سایت اعلام موفقیت کنند.
تراکنشهای تو در تو (Nested Transaction):
این نوع از تراکنش نسبت به دو نوع تراکنش قبلی پیچیدگی بیشتری به لحاظ پیاده سازی و مدیریت دارند. این گونه تراکنشها عموما واحدهای کاری بزرگی هستند که در داخل آنها درختی از تراکنشهای تو در تو را داریم که مجموعه تمامی انها در نهایت یک کار واحد بلحاظ منطقی را انجام میدهند. هر یک از تراکنشهای داخلی بعنوان یک گره در این ساختار درختی قرار دارند که میتوانند پدر و یا فرزندانی داشته باشند.
• در تراکنشهای تو در تو شرایطی حاکم است.
• هر گره در ساختار درختی تراکنش تنها قادر به دیدن برادرهای خود میباشد. به بیان دیگر فرزندان برادران خود را نمیبیند و نسبت به انها هیچ اطلاعی ندارد.
• در تراکنشهای تو در تو امکان اجرای موازی فرزندان یک گره وجود دارد.
• امکان اجرای موازی تراکنشها منجر میشود به این که تراکنشهای داخلی قادر به دیدن خروجی حاصل از اجرا همدیگر نباشند.
• هر تراکنشی به طور مستقل ویژگی atomicity را دارد اما پایداری (durability) و کامیت شدن آنها وابسته به پدرانشان میباشد.
• در صورتی که پدری تصمیم بگیرد میتواند تمامی زیر تراکنش هایش را خاتمه (abort) دهد.
در تراکنشهای موازی COMMIT شدن یک گره پدر به دو صورت امکان پذیر است.
حالت AND: در این حالت یک تراکنش در صورتی کامیت خواهد شده که تمامی فرزندان آن با موفقیت اجرا و COMMIT شده باشند.
حالت OR: در این حالت اگر حتی یکی از تراکنشهای فرزند نیز موفق به COMMIT شده باشد تراکنش پدر نیز COMMIT خواهد شد.
تراکنشهای چند سطحی (Multi-level Transactions) :
این نوع نیز همانند تراکنشهای تو در تو پیچیده است. از نظر ساختاری تراکنشهای چند سطحی مشابه تراکنشهای تو در تو میباشند ولی به لحاظ مفهومی با یکدیگر متفاوت هستند. اولین تفاوت موجود بین این دو نوع اینست که هر زیر تراکنشی قادر است خروجی زیر تراکنشهای دیگر را ببیند. این مسئله باعث میشود که تنوانیم زیر تراکنشها را بصورت همروند و موازی اجرا کنیم که این دومین تفاوت مفهومی بین این دو میباشد. هنگامی که زیر تراکنش کامل شد (COMMIT) تمامی قفلهای مربوط به خود را آزاد میکند که این مورد نیز در مورد تراکنشهای تو در تو صادق نمیباشد. یکی از مهمترین تفاوتهای دیگر بین این دو نوع در اینست که در تراکنشهای چند سطحی تمامی برگها در یک سطح از درخت قرار دارند و تنها تراکنشهای برگ هستند که مستقیما به پایگاه داده مراجعه میکنند. در مورد کایت شدن نیز شروط مربوط به تراکنشهای تو در تو در اینجا وجود ندارند و زیر تراکنشها میتوانند بدون هیچ شرطی کامیت شوند.
تراکنشهای زنجیره ای (Chained Transaction):
همانطور که از نام این نوع از تراکنشها پیداست، این تراکنشها از زنجیره ای از زیر تراکنشهای پی در پی تشکیل شده اند. تا زمانی که تمامی حلقههای این زنجیر با موفقیت اجرا نشوند سیستم به حالت سازگاری نخواهد نرفت. دراین نوع از تراکنشهای COMMIT هر حلقه باعث پایداری شدن (durable) دادههای در پایگاه داده خواهد شد. این مسئله ممکن است پایگاه داده را به وضعیت ناسازگاری ببرد. در هنگام کامیت شدن هر حلقه قفلهای مربوط به آن نیز آزاد میشود.
حلقههای مختلف زنجیره تراکنشی میتوانند با یکدیگر تبادل اطلاعات کنند. البته توجه داشته باشید که منابعی که هر کدام از آنها بر روی آن کار میکنند با دیگری متفاوت میباشد. بعنوان نمونه تراکنشی را نظر بگیرد که قصد دارد متوسط مبلغ مکالمه تلفن همراه مشترکان یک مخابرات را محاسبه کند. بدلیل تعداد بالای مشترکان ممکن است این تراکنش را در قالب یک تراکنش زنجیره ای پیاده سازی کنیم که هر حلقه از آن مسئول محاسبه این مبلغ برای ده هزار نفر از کاربران باشد. توجه داشته باشید که برای بدست آوردن مقدار متوسط نیاز داریم که هر زیر تراکنشها قادر به تبادل اطلاعات باشند. از طرفی منابع مورد استفاده آنها (رکورد ها) با یکدیگر متفاوت خواهد بود و نمیتوانند تغییرات یکدیگر را ببینند. سوالی که مطرح میشود اینست که مبادله اطلاعات بین حلقههای تراکنش به چه صورت باید انجام شود؟ در جواب این سوال باید گفت که مبادله اطلاعات بین تراکنشها از طریق متغیرهای رابطه ای که هما متغیرهای پایگاه داده هستند انجام میگیرد.
SavePoint:
در برخی شرایط ممکن است بخواهیم در هنگام ROLLBACK مجددا به ابتدای تراکنش باز نگردیم تا مجبور باشیم دوباره کار را از ابتدا از سر بگیریم. بعنوان مثال تا قسمتی از تراکنش پیش رفتیم، به خطایی بر خورد میکنیم و میخواهیم از نقطه ای خاص از تراکنش کا را از سر بگیریم. در چنین کاربرد هایی از ابزاری بنام SavePoint استفاده میکنم.
برای روشنتر شدن مفهوم SavePoint فرض کنید قصد داریم بلیطی از تهران به سیدنی رزرو کنیم. برای این منظور ابتدا عمل رزرواسیون را از تهران به دوبی انجام میدهیم و سپس از دوبی به سنگاپور و در نهایت از سنگاپور به سیدنی. حال در این بین میتوانیم در نقطه تهران – دوبی SavePoint قرار دهیم تا در صورت بروز هرگونه خطا مجددا رزرواسیون را از ابتدا آغاز نکنیم. اگر در هنگام رزرو بلیط دوبی – سنگاپور خطایی بروز دهد میتوانیم به نقطه تهران – دوبی ROLLBACK کنیم و از آنجا مسیر دیگری را انتخاب کنیم. توجه داشته باشید که ROLLBACK به SavePoint وضعیت پایگاه داده به همان نقطه بازگردانده میشود.
begin transaction(); s1; sp1:= create savepoint(0); s2; sp2:= create savepoint(0); if (condition) rollback (spi); … … commit
Auto Transaction:
این قبیل تراکنشها تراکنشهای کوچکی هستند که توسط سیستم تعریف میشوند. بعنوان مثال سیستم برای انجام دستورات زیر تراکنش تعریف میکند :
Alter table, Create, delete, insert, open, drop, fetch, grant, revoke, select, truncate table, update
یکی از علتهای این امر اینست که در صورت بروز خطا در حین این تراکنشهای خود کار امکان اجرای مجدد هر کدام فراهم گردد.
شروع تراکنشها :
همانطور که گفته شد برای شروع تراکنشها میتوانیم صراحتا از BEGIN TRANSACTION استفاده کنیم. البته راهکار دیگری نیز وجود دارد که در آن میتوانیم به DBMS اعلام کنیم که با پایان یک تراکنش پیش از شروع تراکنش بعدی BEGIN TRANSACTION را قرار بده. برای این منظور از دستور زیر استفاده میکنیم :
Set implicit_transaction on
SET TRANSACTION READ ONLY
همچنین میتوان اجازه تغییر را به آن داد :
SET TRANSACTION READ WRITE
READ UNCOMMITTED, READ COMMITTED, REPEATABLE READ, SERIALIZABLE
یک: ASP.NET Core مستقل از Platform است
آیندهی محتوم نرمافزار، توسعه به شیوههای مستقل از Platform است. شاید این دلیل به تنهایی برای مهاجرت به ASP.NET Core کافی باشد. امروزه نرمافزارهایی که مبتنی بر یک Platform خاص نیستند، نسبت به سایر نرمافزارها مزیت رقابتیتری دارند. نرمافزارهای Cross Platform یا مستقل از Platform، بر روی هر سیستم عاملی اجرا میشوند. برای اجرای آنها در کامپیوترهای شخصی یا Server کافیست معماری پردازندهی مرکزی x86 باشد و سیستم عامل نیز یکی از انواع ویندوز، لینوکس یا مک.
دلیل مستقل بودن ASP.NET Core از Platform، مبتنی بودن آن بر NET Core. است. این نسخه از داتنت را میتوان برای سیستمعاملهای مختلف از http://dot.net دانلود و نصب کرد.
برای اجرای نرمافزارهایی که مبتنی بر NET Core. هستند نیاز به بازنویسی کدها یا استفاده از زبانهای برنامهنویسی جداگانهای نیست. این خاصیت همچنین برای libraryهای استانداردی که از این نسخه از داتنت استفاده میکنند نیز صادق است.
دو: Open Source است
یکی از انتقادهایی که سالها به مایکروسافت میشد، ناشناخته بودن سورس نرمافزارهای این شرکت و انحصار طلبیهایش بود. اما در سالهای اخیر مایکروسافت نشان دادهاست که این جنبه از کاراکترش را به تدریج اصلاح کردهاست. به طوری که اسکات هانسلمن، یکی از کارمندان این شرکت و وبلاگنویس مشهور در این مورد گفته است:
دلیل آمدن من به مایکروسافت این بود که میخواستیم هر چقدر میتوانیم کارها را Open Source کنیم و یک جامعهی کاربری برای داتنت و اوپن سورس بسازیم و حالا به NET Core 1.0. رسیدهایم.
زمانی شایعهی لو رفتن بخشی از سورس کد ویندوز ۹۵، در صدر اخبار تکنولوژی بود و این یک شکست برای مایکروسافت محسوب میشد. اما امروزه ASP.NET Core با لایسنس MIT عرضه شده است که یکی از آزادترین مجوزهای اوپن سورس است. نرمافزارهایی که با این مجوز عرضه میشوند، آزادی تغییر کد، ادغام با مجوزهای دیگر، عرضه به عنوان محصول دیگر، استفاده تجاری و ... را به همهی توسعهدهندگان میدهد.
سه: جدا بودن از Web Server
این نسخهی از APS.NET، کاملاً از وبسرور که نرمافزارها را هاست میکند، جدا (decouple) شده است. اگرچه همچنان استفاده از IIS بر روی ویندوز منطقی به نظر میرسد اما مایکروسافت یک پروژهی اوپن سورس دیگری را به نام Kestrel نیز منتشر کرده است.
وبسرور Kestrel مبتنی بر پروژه libuv است و libuv در اصل برای هاست کردن Node.js تولید شده بود و تأکید آن بر روی انجام عملیات I/O به صورت asynchronous است.
نکته جالب این است که یک برنامهی مبتنی بر ASP.NET Core، در واقع یک Console Application است که در متد Main آن وبسرور فراخوانی میشود:
using System; using Microsoft.AspNetCore.Hosting; namespace aspnetcoreapp { public class Program { public static void Main(string[] args) { var host = new WebHostBuilder() .UseKestrel() .UseStartup<Startup>() .Build(); host.Run(); } } }
چهار: تزریق وابستگی (Dependency Injection) تو کار
تزریق وابستگیها برای ایجاد وابستگی سست (loosely coupling) بین اشیاء مرتبط و وابسته به یکدیگر است. به جای نمونهسازی مستقیم اشیاء مرتبط، یا استفاده از ارجاعهای ایستا به آن اشیاء، زمانی که یک کلاس به آنها احتیاج داشته باشد، با روشهای خاصی از طریق DI به اشیاء مورد نیاز دسترسی پیدا میکند. در این استراتژی، ماژولهای سطح بالا نباید به ماژولهای سطح پایین وابسته باشند، بلکه هر دو باید به abstraction (معمولا Interface ها) وابسته باشند.
وقتی یک سیستم برای استفادهی از DI طراحی شدهاست و کلاسهای زیادی دارد که وابستگیهایش را از طریق constructor یا propertyها درخواست میکند، بهتر است یک کلاس مخصوص ایجاد آن کلاسها و وابستگیهایشان داشته باشد. به این کلاسها container، یا Inversion of Control (IoC) container یا Dependency Injection (DI) container گفته میشود.
با این روش، بدون نیاز به hard code کردن instance سازی از کلاسها، میتوان گرافهای پیچیده وابستگی را در اختیار یک کلاس قرار داد.
طراحی ASP.NET Core از پایه طوری است که حداکثر استفاده را از Dependency Injection میکند. یک container ساده توکار با نام IServiceProvider وجود دارد که به صورت پیشفرض constructor injection را پشتیبانی میکند.
در ASP.NET Core با مفهومی به نام service سر و کار خواهید داشت که در واقع به type هایی گفته میشود که از طریق DI در اختیار شما قرار میگیرند. سرویسها در متد ConfigureServices کلاس Startup برنامه شما تعریف میشوند. این serviceها میتوانند Entity Framework Core یا ASP.NET Core MVC باشند یا سرویسهایی که توسط شما تعریف شدهاند. مثال:
// This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices (IServiceCollection services) { // Add framework services. services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"))); services.AddIdentity<ApplicationUser, IdentityRole>() .AddEntityFrameworkStores<ApplicationDbContext>() .AddDefaultTokenProviders(); services.AddMvc(); // Add application services. services.AddTransient<IEmailSender, AuthMessageSender>(); services.AddTransient<ISmsSender, AuthMessageSender>(); }
پنج: یکپارچگی با frameworkهای مدرن سمت کلاینت
فرآیند build در برنامههای تحت وب مدرن معمولاً این وظایف را انجام میدهد:
- bundle و minify کردن فایلهای جاوا اسکریپت و همینطور CSS
- اجرای ابزارهایی برای bundle و minify کردن قبل از هر build
- کامپایل کردن فایلهای LESS و SASS در CSS
- کامپیال کردن فایلهای CoffeeScript و TypeScript در JavaScript
برای اجرای چنین فرآیندهایی از ابزاری به نام task runner استفاده میشود که Visual Studio از دو ابزار task runner مبتنی برا جاوا اسکریپت به نامهای Gulp و Grunt بهره میبرد. از این ابزارها با استفاده از ASP.NET Core Web Application template میتوان در ASP.NET Core استفاده کرد.
همچنین امکان استفاده از Bower که یک package manager (مانند NuGet) برای وب است، وجود دارد. معمولاً از Bower برای نصب فایلهای CSS ، فونتها، frameworkهای سمت کلاینت و کتابخانههای جاوا اسکریپت استفاده میشود. اگرچه بسیاری از packageها در NuGet هم وجود دارند، اما تمرکز بیشتر NuGet بر روی کتابخانههای داتنتی است.
نصب و استفادهی سایر libraryهای سمت کلاینت مانند Bootstrap ، Knockout.js ، Angular JS و زبان TypeScript نیز در Visual Studio و هماهنگی آن با ASP.NET Core نیز بسیار ساده است.
پس همین حالا دست به کار شوید و با نصب -حداقل - Microsoft Visual Studio 2015 Update 3 بر روی ویندوز یا Visual Studio Code بر روی هر سیستم عاملی از برنامهنویسی با ASP.NET Core لذت ببرید!
منابع :
در مطالب گذشته، دربارهی پیاده سازی API Versioning در ASP.NET Web API و الزامات استفادهی از آن، صحبت شدهاست. اگر مطلب ذکر شده را مطالعه کنید، میبینید که پیاده سازی Versioning در ASP.NET Web API کاری دشوار و زمانبر بود؛ اما در ASP.NET Core انجام تمامی آن مراحل، در 1 خط صورت میگیرد که در ادامه آن را بررسی میکنیم.
بعد از نصب، کافیست کد زیر را داخل متد ConfigureServices در فایل Startup.cs پروژهی خود اضافه کنید:
پارامتر DefaultApiVersion را برابر با یک ApiVersion قرار دادهایم. کلاس ApiVersion دارای Overloadهای مختلفی است. Overload ای را که ما در اینجا از آن استفاده کردهایم، بعنوان پارامتر اول Major Version و برای پارامتر دوم، Minor Version را میگیرد. همچنین بجای Major و Minor میتوان از یک DateTime بعنوان ورژن استفاده کرد:
و در این صورت API شما به شکل زیر قابل دسترسی میباشد:
با انجام این تغییر، برای تست API خود دیگر نمیتوانید از Browser استفاده کنید که این یکی از مشکلات این روش است. برای تست کردن یک درخواست GET ساده مجبور به استفاده از ابزارهایی همچون Postman, CURL و ... هستید. ما در اینجا برای تست از Postman استفاده میکنیم:
Deprecating
که در نتیجهی آن، Response Header برگشتی به این شکل خواهد بود :
Ignoring Versioning
اجرای این متد در صورت غیرفعال بودن AssumeDefaultVersionWhenUnspecified باعث وقوع خطای NullReferenceException میشود و بدین معناست که همانطور که انتظار داشتیم، Version ای برای این Endpoint تنظیم نشده است.
مطلب تکمیلی:
برای شروع با اجرای این دستور در Package Manager Console، پکیج Microsoft.AspNetCore.Mvc.Versioning را داخل پروژه نصب میکنیم:
Install-Package Microsoft.AspNetCore.Mvc.Versioning
public void ConfigureServices(IServiceCollection services) { services.AddMvc(); services.AddApiVersioning(); // ... }
در ابتدا بعد از نصب این پکیج، ممکن است شما API هایی داشته باشید که برای آنها از قبل ورژنی مشخص نکرده باشید (بصورت explicit ). میتوانید یک Version پیشفرض را به برنامه اضافه کرده و برای Endpoint هایی که ورژن ندارند، از آن استفاده کنید :
services.AddApiVersioning(opt => { opt.AssumeDefaultVersionWhenUnspecified = true; opt.DefaultApiVersion = new ApiVersion(1, 0); });
در این صورت، API شما به شکل زیر قابل دسترسی خواهد بود:
- api/foo?api-version=1.0/
opt.DefaultApiVersion = new ApiVersion(new DateTime(2018, 10, 22));
- api/foo?api-version=2018-10-22/
URL Path Segment Versioning
تا به اینجا API Versioning ما بر اساس Query String Parameters انجام میشود؛ اما اگر بخواهیم بجای آن به شکل مقابل به APIهای خود دسترسی داشته باشیم چطور؟ : api/v1/foo/
برای پیاده سازی به این صورت، کافیست Route کنترلر خود را به این شکل تغییر دهید:
[Route("api/v{version:apiVersion}/[controller]")] public class FooController : ControllerBase { public ActionResult<IEnumerable<string>> Get() { return new[] { "value1", "value2" }; } }
Header Versioning
روش سوم انجام Versioning، استفاده از Header است. برای فعال کردن Header Versioning، داخل Startup، کد خود را به شکل زیر تغییر دهید:
public void ConfigureServices(IServiceCollection services) { services.AddMvc(); services.AddApiVersioning(opt => opt.ApiVersionReader = new HeaderApiVersionReader("api-version")); }
Deprecating
ممکن است بخواهید یک ورژن را منسوخ دانسته و آن را Deprecate کنید. دقت کنید که Deprecate کردن یک API، به معنی از کار افتادن آن نیست. به این صورت میتوانید یک Endpoint از برنامه خود را Deprecate شده «معرفی» کنید:
[ApiVersion("2")] [ApiVersion("1", Deprecated = true)] [Route("api/v{version:apiVersion}/[controller]")] public class FooController : ControllerBase { [HttpGet] public string Get() => "I'm deprecated, Bye bye :("; [HttpGet, MapToApiVersion("2.0")] public string GetV2() => "Hello world ! :D"; }
برای برگرداندن نام APIها و وضعیت Support شان داخل Response Header، باید ReportApiVersions فعال شود:
services.AddApiVersioning(opt => { opt.DefaultApiVersion = new ApiVersion(1, 0); opt.AssumeDefaultVersionWhenUnspecified = true; opt.ReportApiVersions = true; });
Ignoring Versioning
اگر داخل برنامهی خود، کنترلری را دارید که در طی زمان آپدیت نشده و تغییر نخواهد کرد، میتوانید از Version زدن آن با استفاده از ApiVersionNeutral جلوگیری کنید:
[ApiVersionNeutral] [Route("api/[controller]")] public class BarController : ControllerBase { public string Get() => HttpContext.GetRequestedApiVersion().ToString(); }
مطلب تکمیلی:
برای آپدیت کردن و یا معرفی نسخهی جدیدی از یک کنترلر با ورژنی متفاوت، نیازی به Rename کردن کلاس قبلی برای رفع Conflict با نام فایل جدید نیست؛ با استفاده از namespaceها میتوانید کنترلری همنام، اما با ورژن و عملکردی متفاوت داشته باشید:
namespace TestVersioning.Controllers.V1 { [ApiVersion("1", Deprecated = true)] [Route("api/v{version:apiVersion}/[controller]")] public class FooController : ControllerBase { public string Get() => "I'm deprecated, Bye bye :("; } } namespace TestVersioning.Controllers.V2 { [ApiVersion("2")] [Route("api/v{version:apiVersion}/[controller]")] public class FooController : ControllerBase { public string GetV2() => "Hello world ! :D"; } }
دست شما درد نکنه.
اگه امکان داره در مورد NH3 که به تازگی ریلیز شده مطلب بنویسید
اگه امکان داره در مورد NH3 که به تازگی ریلیز شده مطلب بنویسید
در این مطلب قصد داریم پیامها و اخطارهای برنامه را توسط کامپوننت Angular2 Toasty نمایش داده و همچنین برای کاهش میزان تکرار قسمتهای نمایش خطا در برنامه، کار مدیریت متمرکز و سراسری آنها را نیز انجام دهیم.
نمایش پیامها و اخطارهای یک برنامهی Angular توسط ng2-toasty
در مطلب «ایجاد Drop Down Listهای آبشاری در Angular» در قسمت دریافت اطلاعات drop down دوم از سرور، اگر کاربر مجددا گروه را بر روی حالت «لطفا گروهی را انتخاب کنید ...» قرار دهد، مقدار categoryId به undefined تغییر میکند:
در اینجا میخواهیم توسط کامپوننت Angular2 Toasty، پیام متناسبی را نمایش دهیم:
پیشنیازهای کار با کامپوننت Angular2 Toasty توسط یک برنامهی Angular CLI
برای کار با کامپوننت Angular2 Toasty، ابتدا از طریق خط فرمان به پوشهی ریشهی برنامه وارد شده و سپس دستور ذیل را صادر میکنیم:
اینکار سبب خواهد شد تا این کامپوننت در پوشهی node_modules\ng2-toasty نصب شده و همچنین فایل package.json نیز جهت درج مدخل آن به روز رسانی شود:
یک نکته: اگر در حین اجرای این دستور به خطای ذیل برخوردید:
چون VSCode پوشهی node_modules را تحت نظر قرار میدهد، ممکن است یک سری اعمال npm مجوز اجرا را پیدا نکنند. بنابراین ابتدا VSCode را بسته و مجددا دستور npm را اجرا کنید.
پس از آن نیاز است یکی از شیوهنامههایی را که در تصویر فوق ملاحظه میکنید، در فایل angular-cli.json. مشخص کنیم:
که برای نمونه در اینجا، شیوهنامهی بوت استرپ آن انتخاب شدهاست.
سپس باید به فایل src\app\app.module.ts مراجعه کرد و ماژول این کامپوننت را معرفی نمود:
همچنین در همین قسمت، به فایل قالب src\app\app.component.html مراجعه کرده و selector tag این کامپوننت را در ابتدای آن تعریف میکنیم:
در اینجا با استفاده از property binding و تعیین مقدار رشتهای top-right، محل نمایش اعلانات برنامه را مشخص میکنیم. مقدارهای ممکن آن شامل bottom-right، bottom-left، top-right، top-left، top-center، bottom-center، center-center هستند. برای مثال اگر میخواهید آنرا در میانهی صفحه نمایش دهید، مقدار center-center را انتخاب کنید. همچنین باید دقت داشت که این مقدار باید درون '' قرار گیرد تا مشخص شود که رشتهای به خاصیت position انتساب داده شدهاست و این مقدار یک خاصیت عمومی تعریف شدهی در کامپوننت متناظر با قالب، نیست.
نمایش یک پیام خطا توسط ToastyService
اکنون که کار برپایی کامپوننت Angular2 Toasty به پایان رسید، کار کردن با آن به سادگی تزریق سرویس آن به سازندهی یک کامپوننت و فراخوانی متدهای info، success ، wait ، error و warning آن است:
- در اینجا در ابتدا ماژولهای مورد نیاز import شدهاند.
- سپس ToastyService به سازندهی کلاس کامپوننت مدنظر تزریق شدهاست تا بتوان از امکانات آن استفاده کرد.
- در ادامه، فراخوانی متد this.toastyService.error سبب نمایش اخطار قرمز رنگی میشود که تصویر آنرا در ابتدای مطلب جاری مشاهده کردید.
- علت ذکر <ToastOptions> در اینجا این است که وجود آن سبب خواهد شد تا intellisense در VSCode فعال شود و پس از آن بتوان تمام گزینههای این متد و تنظیمات را بدون مراجعهی به مستندات آن از طریق intellisense یافت و درج کرد:
مدیریت سراسری خطاهای مدیریت نشده، در یک برنامهی Angular
در برنامههای Angular از این دست کدها بسیار مشاهده میشوند:
تا اینجا قسمت err یا بروز خطا را با console.log مدیریت کردهایم. در این حالت کاربر ممکن است 10 بار بر روی دکمهای کلیک کند یا صفحهای را بارگذاری کند و دست آخر متوجه نشود که مشکل کار چیست. به همین جهت میتوان خطاها را نیز توسط ToastyService نمایش داد تا کاربران دقیقا متوجه بروز مشکل رخ داده شوند. اما ... به این ترتیب تکرار کد زیادی را خواهیم داشت و باید به ازای تمام این موارد، یکبار this.toastyService.error را فراخوانی کنیم. برای مدیریت بهتر یک چنین سناریویی در Angular، کلاس و سرویس توکاری به نام ErrorHandler وجود دارد. در هر قسمتی از برنامهی Angular که استثنایی مدیریت نشده رخ دهد، ابتدا از این کلاس رد شده و سپس به برنامه انتشار پیدا میکند. بنابراین میتوان یک ErrorHandler سفارشی را با ارث بری از آن تهیه کرد و سپس بجای سرویس توکار اصلی، به برنامه معرفی و از آن استفاده نمود. به این ترتیب میتوان یک Global Error Interceptor را طراحی نمود.
به همین منظور کلاس جدیدی را به صورت ذیل در پوشهی src\app اضافه میکنیم:
با این خروجی
سپس این کلاس را به نحو ذیل تکمیل خواهیم کرد:
کلاس جدید AppErrorHandler از کلاس پایه ErrorHandler ارث بری میکند. بنابراین import آنرا در ابتدای کار مشاهده میکنید. سپس باید متد handleError آنرا با امضایی که مشاهده میکنید، پیاده سازی کنیم. فعلا با استفاده از console.log این خطا را در کنسول developer tools نمایش میدهیم.
اکنون نیاز است این ErrorHandler سفارشی را بجای نمونهی اصلی به برنامه معرفی کنیم. برای این منظور به فایل src\app\app.module.ts مراجعه کرده و تغییرات ذیل را اعمال میکنیم:
ابتدا ErrorHandler به لیست imports اضافه شدهاست و همچنین محل تامین AppErrorHandler نیز مشخص گردیدهاست. سپس در قسمت providers ماژول جاری، از تعریف خاصی که ملاحظه میکنید، استفاده خواهد شد. به این ترتیب به Angular اعلام میکنیم، هرگاه نیازی به وهلهای از کلاس توکار ErrorHandler بود، وهلهای از کلاس سفارشی AppErrorHandler را مورد استفاده قرار بده.
اکنون برای آزمایش آن، در کدهای سمت سرور مطلب «ایجاد Drop Down Listهای آبشاری در Angular»، یک استثنای عمدی را قرار میدهیم:
به این ترتیب هر زمانیکه گروهی انتخاب شد، دریافت محصولات آن گروه با خطا مواجه میشود.
برای اینکه AppErrorHandler، مورد استفاده قرار گیرد، قسمت err دریافت لیست محصولات را نیز حذف میکنیم (تا تبدیل به یک استثنای مدیریت نشده شود):
اکنون اگر برنامه را اجرا کنیم، چنین پیامی، در کنسول developer tools ظاهر میشود و مشخص است از فایل AppErrorHandler صادر شدهاست:
افزودن ToastyService به AppErrorHandler
در ادامه میخواهیم بجای console.log از ToastyService برای نمایش خطاهای مدیریت نشدهی برنامه در کلاس AppErrorHandler استفاده کنیم:
به همین منظور سرویس آنرا به سازندهی کلاس AppErrorHandler تزریق کرده و سپس از آن به نحو متداولی در متد handleError استفاده میکنیم. به این ترتیب بجای دهها و یا صدها قسمت مدیریت err=>this.toastyService.error در برنامه، تنها یک مورد مدیریت مرکزی را خواهیم داشت.
مشکل اول! اکنون اگر برنامه را اجرا کنیم، در کنسول developer tools چنین خطایی ظاهر میشود:
به این معنا که Angular قادر نیست وهلهای از AppErrorHandler را ایجاد کند؛ چون نمیداند که چگونه باید پارامتر سازندهی ToastyService را وهله سازی و تزریق نماید. علت اینجا است که کار آغاز کلاس ویژهی ErrorHandler سراسری، پیش از کار بارگذاری ماژول مرتبط با ToastyService انجام میشود. به همین جهت، این مورد جزو معدود مواردی است که باید به صورت دستی تزریق شود:
در اینجا توسط Inject decorator، کار تزریق دستی ToastyService انجام خواهد شد. اکنون اگر برنامه را مجدد اجرا کنیم، خطای قبلی برطرف شده؛ یعنی کلاس AppErrorHandler با موفقیت وهله سازی شدهاست.
مشکل دوم! اینبار برنامه را اجرا کنید. سپس گروهی را انتخاب نمائید. مشاهده میکنید که خطایی نمایش داده نشد؛ هرچند در کنسول developer tools میتوان اثری از آن را مشاهده کرد. مجددا گروه دیگری را انتخاب کنید، در این بار دوم است که خطای ارائه شدهی توسط this.toastyService.error ظاهر میشود. توضیح آن نیاز به بررسی مفهومی به نام Zones در Angular دارد.
مفهوم Zones در Angular
زمانیکه متد this.toastyService.error در یک کامپوننت برنامه مورد استفاده قرار گرفت، به خوبی کار میکرد و در همان بار اول فراخوانی، پیام را نمایش میداد. اما با انتقال آن به کلاسAppErrorHandler ، این قابلیت از کار افتاد. علت اینجا است که زمینهی اجرایی این قطعه کد، اکنون خارج از Zone یا ناحیهی Angular است و به همین دلیل متوجه تغییرات آن نمیشود. Zone زمینهی اجرایی اعمال async است و اگر به فایل package.json یک برنامهی Angular دقت کنید، بستهی zone.js، یکی از وابستگیهای همراه آن است.
تغییرات حالت برنامه، توسط یکی از اعمال ذیل رخ میدهند:
الف) بروز رخدادهایی مانند کلیک، ورود اطلاعات و یا ارسال فرم
ب) اعمال Ajax ایی
ج) استفاده از Timers مانند استفاده از setTimeout و setInterval
هر سه مورد یاد شده از نوع async بوده و زمانیکه رخ میدهند، حالت برنامه را تغییر خواهند داد. Angular نیز تنها به این موارد علاقمند بوده و به آنها در جهت به روز رسانی رابط کاربری برنامه واکنش نشان میدهد.
برای مثال this.toastyService.error دارای خاصیتی است به نام timeout: 5000 که در آن، مورد «ج» فوق رخ میدهد؛ یعنی یک Timer پس از 5 ثانیه سبب بسته شدن آن خواهد شد. به همین جهت است که اگر پیش از پایان این 5 ثانیه مجددا درخواست واکشی لیست محصولات یک گروه را بدهیم، خطای مربوطه مشاهده میشود. چون Angular زمینهی اجرایی لازم را فراهم کرده (یا همان Zone در اینجا) و مجبور به واکنش به عملیات async از نوع Timer است.
برای دسترسی به امکانات کتابخانهی zone.js، میتوان از طریق تزریق سرویس آن به نام NgZone به سازندهی کلاس شروع کرد:
در اینجا فراخوانی this.ngZone.run سبب میشود تا درخواست نمایش خطای رخداده وارد Angular Zone شده و بلافاصله سبب نمایش آن گردد:
چند نکته
1- اگر میخواهید علاوه بر رخدادگردانی سراسری خطاها، این خطاها را به محل اصلی آنها نیز انتشار دهید، نیاز است سطر throw error را در انتهای متد handleError نیز ذکر کنید. در غیر اینصورت، کار در همینجا به پایان خواهد رسید و این خطاها دیگر منتشر نمیشوند.
2- روش دریافت URL جاری صفحه را نیز در اینجا مشاهده میکنید. این اطلاعات میتوانند جهت ارسال به سرور برای ثبت و بررسیهای بعدی مفید باشند.
3- مقدار new Error().stack معادل stack trace جاری است و تقریبا در تمام مرورگرهای جدید پشتیبانی میشود.
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید: angular-template-driven-forms-lab-07.zip
برای اجرای آن فرض بر این است که پیشتر Angular CLI را نصب کردهاید. سپس به ریشهی پروژه وارد شده و دو پنجرهی کنسول مجزا را باز کنید. در اولی دستورات
و در دومی دستورات ذیل را اجرا کنید:
اکنون میتوانید برنامه را در آدرس http://localhost:5000 مشاهده و اجرا کنید.
نمایش پیامها و اخطارهای یک برنامهی Angular توسط ng2-toasty
در مطلب «ایجاد Drop Down Listهای آبشاری در Angular» در قسمت دریافت اطلاعات drop down دوم از سرور، اگر کاربر مجددا گروه را بر روی حالت «لطفا گروهی را انتخاب کنید ...» قرار دهد، مقدار categoryId به undefined تغییر میکند:
fetchProducts(categoryId?: number) { console.log(categoryId); this.products = []; if (categoryId === undefined || categoryId.toString() === "undefined") { return; }
پیشنیازهای کار با کامپوننت Angular2 Toasty توسط یک برنامهی Angular CLI
برای کار با کامپوننت Angular2 Toasty، ابتدا از طریق خط فرمان به پوشهی ریشهی برنامه وارد شده و سپس دستور ذیل را صادر میکنیم:
> npm install ng2-toasty --save
یک نکته: اگر در حین اجرای این دستور به خطای ذیل برخوردید:
npm ERR! Error: EPERM: operation not permitted, rename
پس از آن نیاز است یکی از شیوهنامههایی را که در تصویر فوق ملاحظه میکنید، در فایل angular-cli.json. مشخص کنیم:
"styles": [ "../node_modules/bootstrap/dist/css/bootstrap.min.css", "../node_modules/ng2-toasty/bundles/style-bootstrap.css", "styles.css" ],
سپس باید به فایل src\app\app.module.ts مراجعه کرد و ماژول این کامپوننت را معرفی نمود:
import { ToastyModule } from "ng2-toasty"; @NgModule({ imports: [ BrowserModule, ToastyModule.forRoot(),
همچنین در همین قسمت، به فایل قالب src\app\app.component.html مراجعه کرده و selector tag این کامپوننت را در ابتدای آن تعریف میکنیم:
<ng2-toasty [position]="'top-right'"></ng2-toasty>
نمایش یک پیام خطا توسط ToastyService
اکنون که کار برپایی کامپوننت Angular2 Toasty به پایان رسید، کار کردن با آن به سادگی تزریق سرویس آن به سازندهی یک کامپوننت و فراخوانی متدهای info، success ، wait ، error و warning آن است:
import { ToastyService, ToastOptions } from "ng2-toasty"; export class ProductGroupComponent implements OnInit { constructor( private productItemsService: ProductItemsService, private toastyService: ToastyService) { } fetchProducts(categoryId?: number) { console.log(categoryId); this.products = []; if (categoryId === undefined || categoryId.toString() === "undefined") { this.toastyService.error(<ToastOptions>{ title: "Error!", msg: "Please select a category.", theme: "bootstrap", showClose: true, timeout: 5000 }); return; }
- سپس ToastyService به سازندهی کلاس کامپوننت مدنظر تزریق شدهاست تا بتوان از امکانات آن استفاده کرد.
- در ادامه، فراخوانی متد this.toastyService.error سبب نمایش اخطار قرمز رنگی میشود که تصویر آنرا در ابتدای مطلب جاری مشاهده کردید.
- علت ذکر <ToastOptions> در اینجا این است که وجود آن سبب خواهد شد تا intellisense در VSCode فعال شود و پس از آن بتوان تمام گزینههای این متد و تنظیمات را بدون مراجعهی به مستندات آن از طریق intellisense یافت و درج کرد:
مدیریت سراسری خطاهای مدیریت نشده، در یک برنامهی Angular
در برنامههای Angular از این دست کدها بسیار مشاهده میشوند:
this.productItemsService.getCategories().subscribe( data => { this.categories = data; }, err => console.log("get error: ", err) );
به همین منظور کلاس جدیدی را به صورت ذیل در پوشهی src\app اضافه میکنیم:
> ng g cl app.error-handler
installing class create src\app\app.error-handler.ts
import { ErrorHandler } from "@angular/core"; export class AppErrorHandler implements ErrorHandler { handleError(error: any): void { console.log("Error:", error); } }
اکنون نیاز است این ErrorHandler سفارشی را بجای نمونهی اصلی به برنامه معرفی کنیم. برای این منظور به فایل src\app\app.module.ts مراجعه کرده و تغییرات ذیل را اعمال میکنیم:
import { NgModule, ErrorHandler } from "@angular/core"; import { AppErrorHandler } from "./app.error-handler"; @NgModule({ providers: [ { provide: ErrorHandler, useClass: AppErrorHandler } ]
اکنون برای آزمایش آن، در کدهای سمت سرور مطلب «ایجاد Drop Down Listهای آبشاری در Angular»، یک استثنای عمدی را قرار میدهیم:
[HttpGet("[action]/{categoryId:int}")] public async Task<IActionResult> GetProducts(int categoryId) { throw new Exception();
برای اینکه AppErrorHandler، مورد استفاده قرار گیرد، قسمت err دریافت لیست محصولات را نیز حذف میکنیم (تا تبدیل به یک استثنای مدیریت نشده شود):
this.productItemsService.getProducts(categoryId).subscribe( data => { this.products = data; this.isLoadingProducts = false; }// , // err => { // console.log("get error: ", err); // this.isLoadingProducts = false; // } );
افزودن ToastyService به AppErrorHandler
در ادامه میخواهیم بجای console.log از ToastyService برای نمایش خطاهای مدیریت نشدهی برنامه در کلاس AppErrorHandler استفاده کنیم:
import { ToastyService, ToastOptions } from "ng2-toasty"; import { ErrorHandler } from "@angular/core"; export class AppErrorHandler implements ErrorHandler { constructor(private toastyService: ToastyService) { } handleError(error: any): void { // console.log("Error:", error); this.toastyService.error(<ToastOptions>{ title: "Error!", msg: "Fatal error!", theme: "bootstrap", showClose: true, timeout: 5000 }); } }
مشکل اول! اکنون اگر برنامه را اجرا کنیم، در کنسول developer tools چنین خطایی ظاهر میشود:
Uncaught Error: Can't resolve all parameters for AppErrorHandler: (?).
import { ErrorHandler, Inject } from "@angular/core"; export class AppErrorHandler implements ErrorHandler { constructor( @Inject(ToastyService) private toastyService: ToastyService ) { }
مشکل دوم! اینبار برنامه را اجرا کنید. سپس گروهی را انتخاب نمائید. مشاهده میکنید که خطایی نمایش داده نشد؛ هرچند در کنسول developer tools میتوان اثری از آن را مشاهده کرد. مجددا گروه دیگری را انتخاب کنید، در این بار دوم است که خطای ارائه شدهی توسط this.toastyService.error ظاهر میشود. توضیح آن نیاز به بررسی مفهومی به نام Zones در Angular دارد.
مفهوم Zones در Angular
زمانیکه متد this.toastyService.error در یک کامپوننت برنامه مورد استفاده قرار گرفت، به خوبی کار میکرد و در همان بار اول فراخوانی، پیام را نمایش میداد. اما با انتقال آن به کلاسAppErrorHandler ، این قابلیت از کار افتاد. علت اینجا است که زمینهی اجرایی این قطعه کد، اکنون خارج از Zone یا ناحیهی Angular است و به همین دلیل متوجه تغییرات آن نمیشود. Zone زمینهی اجرایی اعمال async است و اگر به فایل package.json یک برنامهی Angular دقت کنید، بستهی zone.js، یکی از وابستگیهای همراه آن است.
تغییرات حالت برنامه، توسط یکی از اعمال ذیل رخ میدهند:
الف) بروز رخدادهایی مانند کلیک، ورود اطلاعات و یا ارسال فرم
ب) اعمال Ajax ایی
ج) استفاده از Timers مانند استفاده از setTimeout و setInterval
هر سه مورد یاد شده از نوع async بوده و زمانیکه رخ میدهند، حالت برنامه را تغییر خواهند داد. Angular نیز تنها به این موارد علاقمند بوده و به آنها در جهت به روز رسانی رابط کاربری برنامه واکنش نشان میدهد.
برای مثال this.toastyService.error دارای خاصیتی است به نام timeout: 5000 که در آن، مورد «ج» فوق رخ میدهد؛ یعنی یک Timer پس از 5 ثانیه سبب بسته شدن آن خواهد شد. به همین جهت است که اگر پیش از پایان این 5 ثانیه مجددا درخواست واکشی لیست محصولات یک گروه را بدهیم، خطای مربوطه مشاهده میشود. چون Angular زمینهی اجرایی لازم را فراهم کرده (یا همان Zone در اینجا) و مجبور به واکنش به عملیات async از نوع Timer است.
برای دسترسی به امکانات کتابخانهی zone.js، میتوان از طریق تزریق سرویس آن به نام NgZone به سازندهی کلاس شروع کرد:
import { ToastyService, ToastOptions } from "ng2-toasty"; import { ErrorHandler, Inject, NgZone } from "@angular/core"; import { LocationStrategy, PathLocationStrategy } from "@angular/common"; export class AppErrorHandler implements ErrorHandler { constructor( @Inject(NgZone) private ngZone: NgZone, @Inject(ToastyService) private toastyService: ToastyService, @Inject(LocationStrategy) private locationProvider: LocationStrategy ) { } handleError(error: any): void { // console.log("Error:", error); const url = this.locationProvider instanceof PathLocationStrategy ? this.locationProvider.path() : ""; const message = error.message ? error.message : error.toString(); this.ngZone.run(() => { this.toastyService.error(<ToastOptions>{ title: "Error!", msg: `URL:${url} \n ERROR:${message}`, theme: "bootstrap", showClose: true, timeout: 5000 }); }); // IMPORTANT: Rethrow the error otherwise it gets swallowed // throw error; } }
چند نکته
1- اگر میخواهید علاوه بر رخدادگردانی سراسری خطاها، این خطاها را به محل اصلی آنها نیز انتشار دهید، نیاز است سطر throw error را در انتهای متد handleError نیز ذکر کنید. در غیر اینصورت، کار در همینجا به پایان خواهد رسید و این خطاها دیگر منتشر نمیشوند.
2- روش دریافت URL جاری صفحه را نیز در اینجا مشاهده میکنید. این اطلاعات میتوانند جهت ارسال به سرور برای ثبت و بررسیهای بعدی مفید باشند.
3- مقدار new Error().stack معادل stack trace جاری است و تقریبا در تمام مرورگرهای جدید پشتیبانی میشود.
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید: angular-template-driven-forms-lab-07.zip
برای اجرای آن فرض بر این است که پیشتر Angular CLI را نصب کردهاید. سپس به ریشهی پروژه وارد شده و دو پنجرهی کنسول مجزا را باز کنید. در اولی دستورات
>npm install >ng build --watch
>dotnet restore >dotnet watch run
در قسمت قبل، با تنظیمات پَرباد آشنا شدیم. در این مقاله قصد داریم سایر امکانات قابل استفاده را آموزش دهیم.
آنچه شما در این مقاله یاد خواهید گرفت:
- ایجاد صورت حساب پرداخت با استفاده از InvoiceBuilder
- درگاه مجازی
- استفاده از پروکسی
- توکن پرداخت
- تزریق وابستگی
- Logging
ایجاد صورت حساب با استفاده از InvoiceBuilder
InvoiceBuilder به شما کمک میکند تا یک صورت حساب را جهت پرداخت آماده کنید.
مثال زیر را در نظر بگیرید:
var result = _onlinePayment.Request(Gateways.Mellat, 123, 25000, "http://www.mywebsite.com/foo/bar/");
اما همین دستور را با کمک InvoiceBuilder نیز میتوان ایجاد کرد.
نمونه مثال بالا با استفاده از InvoiceBuilder
var result = _onlinePayment.Request(invoice => { invoice .SetTrackingNumber(123) .SetAmount(25000) .SetCallbackUrl("http://www.mywebsite.com/foo/bar/") .UseGateway(Gateways.Mellat); });
در زیر نمونههایی از کارایی آن را بررسی میکنیم.
- تولید اتوماتیک کد رهگیری به صورت افزایشی
- تولید اتوماتیک کد رهگیری به صورت تصادفی
- ایجاد یک تولید کننده کد رهگیری توسط شما
- صورت حساب سفارشی برای امکانات اختصاصی درگاههای بانکی
تولید اتوماتیک کد رهگیری به صورت افزایشی
در این روش، کد رهگیری (TrackingNumber) که مورد نیاز درگاههای بانکی است، به صورت اتوماتیک در هنگام ایجاد درخواست پرداخت، توسط پَرباد تولید میشود.
var result = _onlinePayment.Request(invoice => { invoice .UseAutoIncrementTrackingNumber() .SetAmount(25000) .SetCallbackUrl("http://www.mywebsite.com/foo/bar/") .UseGateway(Gateways.Mellat); });
کد تولید شده، به صورت افزایشی است. در واقع در هر درخواست پرداخت جدید، یک
کد رهگیری تولید میشود که یک واحد از کد تولید شدهی قبلی بیشتر است.
شما همچنین میتوانید مقدار اولیه این عدد را جهت شروع تولید، در پارامتر متد تعیین کنید. این مقدار همچنین در قسمت تنظیمات پَرباد نیز توسط متد ConfigureAutoTrackingNumber قابل تنظیم است.
در این روش کد رهگیری، به صورت تصادفی در محدوده Int64 توسط پَرباد تولید خواهد شد. کدهای تولید شده در این روش تقریبا ٪۹۹.۹ غیر تکراری هستند. اما اگر به تمیز بودن کدهای تولید شده اهمیت میدهید، بهتر است از روش AutoIncrement که بالاتر توضیح داده شد، استفاده کنید.
نکته ۱: شما همچنین میتوانید در منبع خود، از تزریق وابستگیها نیز استفاده کنید. بدیهی است سرویسی را که تزریق میکنید، باید از قبل توسط سیستم تزریق وابستگیهای اپلیکیشن شما، ثبت شده باشد.
همانطور که میبینید، متدهای مختلفی جهت استفاده از منبع مورد نظر شما موجود است.
در مثال بالا، درگاه مجازی توسط آدرس داده شده در دسترس خواهد بود. توجه داشته باشید که این آدرس حتما باید با یک ( / ) آغاز شده باشد.
ASP.NET WebForms, ASP.NET MVC (Startup.cs)
در مثال بالا، درگاه بانک ملت (صرفا جهت مثال) با یک پروکسی تنظیم شده است. برای سایر درگاههای بانکی، فرمت تنظیمات، کاملا مشابه مثال بالا است.
ServiceLifetime، تایین کننده طول عمر سرویس شما است.
تولید اتوماتیک کد رهگیری به صورت تصادفی
var result = _onlinePayment.Request(invoice => { invoice .UseAutoRandomTrackingNumber() .SetAmount(25000) .SetCallbackUrl("http://www.mywebsite.com/foo/bar/") .UseGateway(Gateways.Mellat); });
ایجاد یک تولید کننده کد رهگیری توسط شما
اگر بنا به دلایلی قصد دارید خودتان نیز یک منبع تولید کد رهگیری را ایجاد کنید، میتوانید به روش زیر عمل کنید.
public class MyTrackingNumberProvider : ITrackingNumberProvider { public Task<long> ProvideAsync(CancellationToken cancellationToken = new CancellationToken()) { // تولید و برگشت کد در اینجا } }
نکته ۲؛ شما همچنین میتوانید بدون ایجاد هیچ منبعی، به راحتی از متد SetTrackingNumber در InvoiceBuilder (که بالاتر توضیح داده شده) استفاده کنید.
سپس در هنگام ایجاد درخواست پرداخت به روش زیر از منبع خود استفاده کنید:
var result = _onlinePayment.Request(invoice => { invoice .UseTrackingNumberProvider<MyTrackingNumberProvider>() // یا .UseTrackingNumberProvider(new MyTrackingNumberProvider()) // یا .UseTrackingNumberProvider(services => new MyTrackingNumberProvider()) });
صورت حساب سفارشی برای امکانات اختصاصی درگاههای بانکی
درگاههای بانکی علاوه بر سرویسهای پرداخت عادی، امکانات مختلفی دیگری را نیز ارائه میکنند. برای مثال بانک ملت دارای سرویسی به نام "باشگاه مشتریان بانک ملت" است که امکان واریز مبلغ را از حساب مشتری، به چندین حساب مختلف میدهد.
نکته مهم: همانطور که میدانید قبل از استفاده از این گونه سرویسها، کلیه شماره حسابهایی که قصد واریز مبلغ به آنها را دارید، باید از قبل (در هنگام عقد قرارداد با بانک) به بانک مورد نظر داده شده باشد. در غیر اینصورت امکان استفاده از این گونه سرویسها را ندارید.
نکته: در هنگام نوشتار این مقاله، این سرویس هنوز در پَرباد آماده نیست و در حال توسعه است.
درگاه مجازی
درگاه مجازی پَرباد، یک درگاه بانکی شبیه سازی شده و بسیار ساده است که از آن جهت تست اپلیکیشن خود و عملیات پرداخت میتوانید استفاده کنید. به عبارت دیگر، برای تست اپلیکیشن خود، نیازی به داشتن یک حساب واقعی در یک درگاه بانکی ندارید و میتوانید از این درگاه مجازی استفاده کنید.
ابتدا در قسمت تنظیمات پَرباد، آدرس مورد نظر برای درگاه مجازی را مانند کد زیر مشخص میکنیم:
services.AddParbad() .ConfigureGateways(gateways => { gateways .AddParbadVirtual() .WithOptions(options => options.GatewayPath = "/MyVirtualGateway"); });
سپس درگاه مجازی را در اپلیکیشن خود ثبت میکنید:
ASP.NET CORE (Startup.cs)
public void Configure(IApplicationBuilder app, IHostingEnvironment env) { app.UseMvc(routes => { routes.MapRoute( name: "default", template: "{controller=Home}/{action=Index}/{id?}"); }); // ثبت درگاه مجازی app.UseParbadVirtualGateway(); }
public void Configuration(IAppBuilder app) { var parbad = ParbadBuilder.CreateDefaultBuilder() .ConfigureGateways(gateways => { gateways .AddParbadVirtual() .WithOptions(options => options.GatewayPath = "/MyVirtualGateway"); }) .Build(); app.UseParbadVirtualGateway(parbad.Services); }
تنظیمات درگاه مجازی تا اینجا به پایان رسیده و فقط در هنگام ایجاد درخواست پرداخت، از میان درگاهها، درگاه مجازی پَرباد را انتخاب کنید.
var result = _onlinePayment.Request(Gateways.ParbadVirtualGateway, 123, 25000, "http://www.mywebsite.com/foo/bar/");
و در نهایت به درگاه مجازی هدایت خواهید شد:
نمونه پروژههای کامل را در انتهای مقاله میتوانید مشاهده کنید.
استفاده از پروکسی
با توجه به تغییرات اخیر در بانکداری کشور، احتمال آن وجود دارد که در آینده، بعضی از درگاههای بانکی فقط به IPهای داخل کشور سرویس دهی کنند. اگر وب سایت شما بنا به دلایلی در سروری خارج از کشور میزبانی میشود، شما جهت استفاده از درگاههای بانکی، نیاز به یک سرور در داخل کشور دارید که نقش پروکسی را برای شما بازی کند. پَرباد این امکان را برای شما محیا کرده و کافیست اطلاعات پروکسی سرور خود را به شکل زیر برای درگاه بانکی مورد نظر ثبت کنید.
services.AddParbad() .ConfigureGateways(gateways => { gateways .AddMellat() .WithOptions(options => { options.TerminalId = 123; options.UserName = "abc"; options.UserPassword = "xyz; ) .WithProxy(new Uri("Proxy Server URL"), "UserName", "Password"); });
توکن پرداخت
پَرباد جهت شناسایی و تبادل اطلاعات پرداخت با خارج از سیستم خود و بانکها، از یک توکن به ازاء هر پرداخت استفاده میکند. به عبارت دیگر، به ازاء هر درخواست پرداخت یک توکن تولید میشود. به این صورت، درخواستهای پرداخت، غیر قابل دستکاری و غیر قابل حدس زدن توسط کاربران میشود.
نکته: پَرباد از یک تولید کننده پیش فرض توکن استفاده میکند و شما نیازی به انجام هیچگونه تنظیماتی ندارید. تولید کننده پیش فرض، از یک GUID در Query String استفاده میکند.
اگر قصد دارید روش مورد نظر خود را برای تولید توکن جهت شناسایی یک پرداخت پیادهسازی کنید، میتوانید به روش زیر عمل کنید:
ابتدا تولید کننده توکن را تعریف کنید.
public class MyTokenProvider : IPaymentTokenProvider { public Task<string> ProvideTokenAsync(Invoice invoice, CancellationToken cancellationToken = new CancellationToken()) { // تولید و برگرداندن توکن در اینجا } public Task<string> RetrieveTokenAsync(CancellationToken cancellationToken = new CancellationToken()) { // خواندن و برگرداندن توکن در اینجا } }
نکته: شما همچنین میتوانید از تزریق وابستگیها نیز استفاده کنید. بدیهی است که در اینصورت باید سرویسی که تزریق میکنید، از قبل در سیستم تزریق وابستگیهای اپلیکیشن شما ثبت شده باشد.
سپس تولید کننده توکن خود را در تنظیمات به پَرباد معرفی کنید:
services.AddParbad() .ConfigurePaymentToken(builder => builder.AddPaymentTokenProvider<MyTokenProvider>(ServiceLifetime.Transient));
به این صورت پَرباد، شناسایی و ردیابی یک صورت حساب را با استفاده از تولید کننده توکن شما انجام خواهد داد.
تزریق وابستگی
همانطور که قبلا در مقاله آموزش تنظیمات نیز گفته شد، پَرباد به صورت توکار، از تزریق وابستگی استاندارد مایکروسافت استفاده میکند. بنابراین اگر اپلیکیشن شما نیز از تزریق وابستگی مشابهی استفاده میکند، نیازی به خواندن و یاد گرفتن این بخش ندارید و به راحتی میتوانید از اینترفیس IOnlinePayment در هر کجا که نیاز داشتید جهت عملیات پرداخت استفاده کنید.
اما در صورتیکه در اپلیکیشن خود از تزریق وابستگی دیگری ( مانند Autofac ) استفاده میکنید، باید این دو سیستم را با یکدیگر هماهنگ کنید. خوشبختانه تمام کتابخانههای معروف تزریق وابستگی ( مانند Autofac ) از قبل این کار را برای شما محیا کردهاند و شما فقط نیاز به افزودن چند خط کد به اپلیکیشن فعلی خود را دارید.
جهت فهم بهتر و آموزش عملی، یک اپلیکیشن کامل ASP.NET MVC برای شما تهیه شده که از Autofac جهت تزریق وابستگی استفاده میکند. در این پروژه خواهید دید چگونه به راحتی پَرباد و Autofac را با یکدیگر هماهنگ کرده و هچنین اینترفیس IOnlinePayment را درون کنترلر تزریق میکنیم.
لینک پروژهها در انتهای همین مقاله قابل مشاهده هستند.
Logging
لاگ کردن در پَرباد توسط سیستم استاندارد مایکروسافت انجام میشود. این بدان معنی است که شما امکان استفاده از کتابخانههای Logging بسیار زیادی را دارید. نحوه استفاده و تنظیم کتابخانههای معروف Logging در وب سایت آنها آورده شده و به راحتی میتوانید آنها را با لاگ مایکروسافت تطابق دهید.
نمونه پروژهها
مقالههای مرتبط
مطالب
لینکهای هفته سوم دی
وبلاگها ، سایتها و مقالات ایرانی (داخل و خارج از ایران)
ASP. Net
- ویدیویی دربارهی ASP.Net 4 (بهبودهای حاصل شده در web forms از جهت کار با اسکریپتها خصوصا با تاکید بر jQuery و همچنین ذکر اینکه با آمدن ASP.Net MVC ، وب فرمها کهنه نشده و همچنان توسعه و بهبود داده خواهند شد)، یا مقالهای در این مورد
طراحی و توسعه وب
PHP
اسکیوال سرور
سی شارپ
عمومی دات نت
مسایل اجتماعی و انسانی برنامه نویسی
متفرقه