الگوی استراتژی (Strategy) اجازه میدهد که یک الگوریتم در یک کلاس بسته بندی شود و در زمان اجرا برای تغییر رفتار یک شیئ تعویض شود.برای مثال فرض کنید که ما در حال طراحی یک برنامه مسیریابی برای یک شبکه هستیم. همانطوریکه میدانیم برای مسیر یابی الگوریتمهای مختلفی وجود دارد که هر کدام دارای مزایا و معایبی هستند. و با توجه به وضعیت موجود شبکه یا عملی که قرار است انجام پذیرد باید الگوریتمی را که دارای بالاترین کارائی است انتخاب کنیم. همچنین این برنامه باید امکانی را به کاربر بدهد که کارائی الگوریتمهای مختلف را در یک شبکه فرضی بررسی کنید. حالا طراحی پیشنهادی شما برای این مسئله چست؟
دوباره فرض کنید که در مثال بالا در بعضی از الگوریتمها نیاز داریم که گرههای شبکه را بر اساس فاصلهی آنها از گره مبداء مرتب کنیم. دوباره برای مرتب سازی الگوریتمهای مختلف وجود دارد و هر کدام در شرایط خاص، کارائی بهتری نسبت به الگوریتمهای دیگر دارد. مسئله دقیقا شبیه مسئله بالا است و این مسله میتوانند دارای طراحی شبیه مسله بالا باشد. پس اگر ما بتوانیم یک طراحی خوب برای این مسئله ارائه دهیم میتوانیم این طراحی را برای مسائل مشابه به کار ببریم.
هر کدام از ما میتوانیم نسبت به درک خود از مسئله و سلیقه کاری، طراحهای مختلفی برای این مسئله ارائه دهیم. اما یک طراحی که میتواند یک جواب خوب و عالی باشد، الگوی استراتژی است که توانسته است بارها و بارها به این مسئله پاسخ بدهد.
الگوی استراتژی گزینه مناسبی برای مسائلی است که میتوانند از چندین الگوریتم مختلف به مقصود خود برسند.
نمودار UML الگوی استراتژی به صورت زیر است :
اجازه بدهید، شیوه کار این الگو را با مثال مربوط به مرتب سازی بررسی کنیم. فرض کنید که ما تصمیم گرفتیم که از سه الگویتم زیر برای مرتب سازی استفاده کنیم.
3 - الگوریتم مرتب سازی Merge Sort
ما برای مرتب سازی در این برنامه دارای سه استراتژی هستیم. که هر کدام را به عنوان یک کلاس جداگانه در نظر میگیریم (همان کلاسهای ConcreteStrategy ). برای اینکه کلاس Client بتواند به سادگی یک از استراتژیها را انتخاب کنید بهتر است که تمام کلاسهای استراتزی دارای اینترفیس مشترک باشند. برای این کار میتوانیم یک کلاس abstract تعریف کنیم و ویژگیهای مشترک کلاسهای استراتژی را در آن قرار دهیم و کلاسهای استراتژی آنها را به ارث ببرند(همان کلاس Strategy ) و پیاده سازی کنند.
در زیل کلاس Abstract که کل کلاسهای استراتژی از آن ارث میبرند را مشاهده میکنید :
abstract class SortStrategy { public abstract void Sort(ArrayList list); }
class QuickSort : SortStrategy { public override void Sort(ArrayList list) { // الگوریتم مربوطه }
}
کلاس مربوط به ShellSort
class ShellSort : SortStrategy { public override void Sort(ArrayList list) { // الگوریتم مربوطه } }
class MergeSort : SortStrategy { public override void Sort(ArrayList list) { // الگوریتم مربوطه } }
class SortedList { private ArrayList list = new ArrayList(); private SortStrategy sortstrategy; public void SetSortStrategy(SortStrategy sortstrategy) { this.sortstrategy = sortstrategy; } public void Add(string name) { list.Add(name); } public void Sort() { sortstrategy.Sort(list); } }
برای استفاده از FTP نیاز به یک اکانت FTP در سایت مورد نظر بهمراه دسترسی به پوشه ای مشخص میباشد.
برای مثال ما یک اکانت FTP در سایت dotnettip.info داریم که به پوشه upload دسترسی داره.
ابتدا در فایل Web.config و در بین تگ های appSettings مقادیر زیر را برای دسترسی به اکانت و نام کاربری و رمز عبور ذخیره میکنیم.
<add key="FtpAddress" value="ftp://ftp.dotnetips.info" /> <add key="FtpUser" value="uploadcenter" /> <add key="FtpPass" value="123123" /> <add key="FolderPath" value="~/Upload/" />
در ادامه یک کلاس در App_code پروژه خود با نام FTPHelper ایجاد میکنیم و کد زیر را در آن قرار میدهیم:
تکه کد بالا برای ست کردن مقادیر نام کاربری و رمز عبور و آدرس FTP در کلاس مذکور که بصورت پیشفرض از web.config پر میشود ایجاد و بکار خواهد رفت.
using System.Net; using System.IO; using System.Configuration; public class FtpHelper { public FtpHelper() { //Default Value Set From Application _hostname = ConfigurationManager.AppSettings.GetValues("FtpAddress")[0]; _username = ConfigurationManager.AppSettings.GetValues("FtpUser")[0]; _password = ConfigurationManager.AppSettings.GetValues("FtpPass")[0]; } #region "Properties" private string _hostname; /// <summary> /// Hostname /// </summary> /// <value></value> /// <remarks>Hostname can be in either the full URL format /// ftp://ftp.myhost.com or just ftp.myhost.com /// </remarks> public string Hostname { get { if (_hostname.StartsWith("ftp://")) { return _hostname; } else { return "ftp://" + _hostname; } } set { _hostname = value; } } private string _username; /// <summary> /// Username property /// </summary> /// <value></value> /// <remarks>Can be left blank, in which case 'anonymous' is returned</remarks> public string Username { get { return (_username == "" ? "anonymous" : _username); } set { _username = value; } } private string _password; public string Password { get { return _password; } set { _password = value; } } #endregion }
سپس فضای نامهای زیر را در کلاس خود قرار میدهیم.
using System.Net; using System.IO;
public static bool Upload(string fileUrl) { if (File.Exists(fileUrl)) { FtpHelper ftpClient = new FtpHelper(); string ftpUrl = ftpClient.Hostname + System.IO.Path.GetFileName(fileUrl); FtpWebRequest ftp = (FtpWebRequest)FtpWebRequest.Create(ftpUrl); ftp.Credentials = new NetworkCredential(ftpClient.Username, ftpClient.Password); ftp.KeepAlive = true; ftp.UseBinary = true; ftp.Timeout = 3600000; ftp.KeepAlive = true; ftp.Method = WebRequestMethods.Ftp.UploadFile; const int bufferLength = 102400; byte[] buffer = new byte[bufferLength]; int readBytes = 0; //open file for reading using (FileStream fs = File.OpenRead(fileUrl)) { try { //open request to send using (Stream rs = ftp.GetRequestStream()) { do { readBytes = fs.Read(buffer, 0, bufferLength); fs.Write(buffer, 0, readBytes); } while (!(readBytes < bufferLength)); rs.Close(); } } catch (Exception) { //Optional Alert for Exeption To Application Layer //throw (new ApplicationException("بارگذاری فایل با خطا رو به رو شد")); } finally { //ensure file closed //fs.Close(); } } ftp = null; return true; } return false; }
تکه کد بالا فایل مورد نظر را در صورت وجود به صورت تکههای 100 کیلوبایتی بر روی ftp بارگذاری میکند، که میتوانید مقدار آنرا نیز تغییر دهید.
اینکار باعث افزایش سرعت بارگذاری در فایلهای با حجم بالا برای بارگذاری میشود.
در بخشهای بعدی نحوه ایجاد پوشه ، حذف فایل ، حذف پوشه و دانلود فایل از روی FTP را بررسی خواهیم کرد.
واژه XSS مخفف Cross-site scripting، نوعی از آسیب پذیریست که در برنامههای تحت وب نمود پیدا میکند. به طور کلی و خلاصه، این آسیب پذیری به فرد نفوذ کننده اجازه تزریق اسکریپتهایی را به صفحات وب، میدهد که در سمت کاربر اجرا میشوند ( Client Side scripts ) . در نهایت این اسکریپتها توسط سایر افرادی که از صفحات مورد هدف قرار گرفته بازدید میکنند اجرا خواهد شد.
هدف از این نوع حمله :
بدست آوردن اطلاعات کوکیها و سشنهای کاربران ( مرتبط با آدرسی که صفحه آلوده شده در آن قرار دارد ) است. سپس فرد نفوذ کننده متناسب با اطلاعات بدست آمده میتواند به اکانت شخصی کاربران مورد هدف قرار گرفته، نفوذ کرده و از اطلاعات شخصی آنها سوء استفاده کند .
به صورت کلی دو طبقه بندی برای انواع حملات Cross-site scripting وجود دارند.
حملات XSS ذخیره سازی شده ( Stored XSS Attacks ) :
در این نوع ، کدهای مخرب تزریق شده، در سرور سایت قربانی ذخیره میشوند. محل ذخیره سازی میتواند دیتابیس سایت یا هر جای دیگری که دادهها توسط سایت یا برنامه تحت وب بازیابی میشوند و نمایش داده میشوند باشد. اما اینکه چگونه کدهای مخرب در منابع یاد شده ذخیره میشوند؟
فرض کنید در سایت جاری آسیب پذیری مذکور وجود دارد. راههای ارسال دادهها به این سایت چیست؟ نویسندگان میتوانند مطلب ارسال کنند و کاربران میتوانند نظر دهند. حال اگر در یکی از این دو بخش بررسیهای لازم جهت مقابله با این آسیب پذیری وجود نداشته باشد و نوشتههای کاربران که میتواند شامل کدهای مخرب باشد مستقیما در دیتابیس ذخیره شده و بدون هیچ اعتبار سنجی نمایش داده شود چه اتفاقی رخ خواهد داد؟ مسلما با بازدید صفحه آلوده شده، کدهای مخرب بر روی مرورگر شما اجرا و کوکیهای سایت جاری که متعلق به شما هستند برای هکر ارسال میشود و ...
حملات XSS منعکس شده ( Reflected XSS Attacks ) :
در این نوع از حمله، هیچ نوع کد مخربی در منابع ذخیره سازی وبسایت یا اپلیکیشن تحت وب توسط فرد مهاجم ذخیره نمیشود ! بلکه از ضعف امنیتی بخشهایی همچون بخش جستجو وب سایت، بخشهای نمایش پیغام خطا و ... استفاده میشود ... اما به چه صورت؟
در بسیاری از سایتها، انجمنها و سیستمهای سازمانی تحت وب، مشاهده میشود که مثلا در بخش جستجو، یک فیلد برای وارد کردن عبارت جستجو وجود دارد. پس از وارد کردن عبارت جستجو و submit فرم، علاوه بر نمایش نتایج جستجو، عبارت جستجو شده نیز به نمایش گذاشته میشود و بعضا در بسیاری از سیستمها این عبارت قبل از نمایش اعتبار سنجی نمیشود که آیا شامل کدهای مخرب میباشد یا خیر. همین امر سبب میشود تا اگر عبارت جستجو شامل کدهای مخرب باشد، آنها به همراه نتیجهی جستجو اجرا شوند.
اما این موضوع چگونه مورد سوء استفاده قرار خواهد گرفت؟ مگر نه اینکه این عبارت ذخیره نمیشود پس با توضیحات فوق، کد فقط بر روی سیستم مهاجم که کد جستجو را ایجاد میکند اجرا میشود، درست است؟ بله درست است ولی به نقطه ضعف زیر توجه کنید ؟
www.test.com/search?q=PHNjcmlwdD5hbGVydChkb2N1bWVudC5jb29raWUpOzwvc2NyaXB0Pg==
این آدرس حاصل submit شدن فرم جستجو وبسایت test (نام وبسایت واقعی نیست و برای مثال است ) و ارجاع به صفحه نتایج جستجو میباشد. در واقع این لینک برای جستجوی یک کلمه یا عبارت توسط این وبسایت تولید شده و از هر کجا به این لینک مراجعه کنید عبارت مورد نظر مورد جستجو واقع خواهد شد. در واقع عبارت جستجو به صورت Base64 به عنوان یک query String به وبسایت ارسال میشود؛ علاوه بر نمایش نتایج، عبارت جستجو شده نیز به کاربر نشان داده شده و اگر آسیب پذیری مورد بحث وجود داشته باشد و عبارت شامل کدهای مخرب باشد، کدهای مخرب بر روی مرورگر فردی که این لینک را باز کرده اجرا خواهد شد!
در این صورت کافیست فرد مهاجم لینک مخرب را به هر شکلی به فرد مورد هدف بدهد ( مثلا ایمیل و ... ). حال در صورتیکه فرد لینک را باز کند (با توجه به اینکه لینک مربوط به یک سایت معروف است و عدم آگاهی کاربر از آسیب پذیری موجود در لینک، باعث باز کردن لینک توسط کاربر میشود)، کدها بر روی مرورگرش اجرا شده و کوکیهای سایت مذکور برای مهاجم ارسال خواهد شد ... به این نوع حمله XSS ، نوع انعکاسی میگویند که کاملا از توضیحات فوق الذکر، دلیل این نامگذاری مشخص میباشد.
اهمیت مقابله با این حمله :
برای نمونه این نوع باگ حتی تا سال گذشته در سرویس ایمیل یاهو وجود داشت. به شکلی که یکی از افراد انجمن hackforums به صورت Private این باگ را به عنوان Yahoo 0-Day XSS Exploit در محیط زیر زمینی و بازار سیاه هکرها به مبلغ چند صد هزار دلار به فروش میرساند. کاربران مورد هدف کافی بود تا فقط یک ایمیل دریافتی از هکر را باز کنند تا کوکیهای سایت یاهو برای هکر ارسال شده و دسترسی ایمیلهای فرد قربانی برای هکر فراهم شود ... ( در حال حاظر این باگ در یاهو وجو ندارد ).
چگونگی جلوگیری از این آسیب پذیری
در این سری از مقالات کدهای پیرامون سرفصلها و مثالها با ASP.net تحت فریم ورک MVC و به زبان C# خواهند بود. هر چند کلیات مقابله با آسیب پذیری هایی از این دست در تمامی زبانها و تکنولوژیهای تحت وب یکسان میباشند.
خوشبختانه کتابخانهای قدرتمند برای مقابله با حمله مورد بحث وجود دارد با نام AntiXSS که میتوانید آخرین نسخه آن را با فرمان زیر از طریق nugget به پروژه خود اضافه کنید. البته ذکر این نکته حائز اهمیت است که Asp.net و فریم ورک MVC به صورت توکار تا حدودی از بروز این حملات جلوگیری میکند. برای مثال به این صورت که در View ها شما تا زمانی که از MvcHtmlString استفاده نکنید تمامی محتوای مورد نظر برای نمایش به صورت Encode شده رندر میشوند. این داستان برای Url ها هم که به صورت پیش فرض encode میشوند صدق میکند. ولی گاها وقتی شما برای ورود اطلاعات مثلا از یک ادیتور WYSWYG استفاده میکنید و نیاز دارید دادهها را بدون encoding رندر کنید. آنگاه به ناچار مجاب بر اعمال یک سری سیاستهای خاصتر بر روی داده مورد نظر برای رندر میشوید و نمیتوانید از encoding توکار فوق الذکر استفاده کنید. آنگاه این کتابخانه در اعمال سیاستهای جلوگیری از بروز این آسیب پذیری میتواند برای شما مفید واقع شود.
PM> Install-Package AntiXSS
… var reviewContent = model.UserReview; reviewContent = Microsoft.Security.Application.Encoder.HtmlEncode(review); …
امیدوارم در اولین بخش از این سری مقالات، به صورت خلاصه مطالب مهمی که باعث ایجاد فهم کلی در رابطه با حملات Xss وجود دارد، برای دوستان روشن شده و پیش زمینه فکری برای مقابله با این دست از حملات برایتان به وجود آمده باشد.
public static void CreateUniqueIndex(this DbContext context, string tableName, string fieldName) { context.Database.ExecuteSqlCommand("CREATE UNIQUE INDEX [IX_Unique_" + tableName + "_" + fieldName + "] ON [" + tableName + "]([" + fieldName + "] ASC);"); }
public class MyDbMigrationsConfiguration : DbMigrationsConfiguration<MyContext> { public BlogDbMigrationsConfiguration() { AutomaticMigrationsEnabled = true; AutomaticMigrationDataLossAllowed = true; } protected override void Seed(MyContext context) { CreateUniqueIndex(context, "table1", "field1"); base.Seed(context); } }
برای حل این مشکل و تبدیل کدهای فوق به کدهایی Refactoring friendly، نیاز است نام جدول به صورت خودکار از DbContext دریافت شود. همچنین نام خاصیت یا فیلد نیز به صورت strongly typed قابل تعریف باشد.
کدهای کامل نمونه بهبود یافته را در ذیل مشاهده میکنید:
using System; using System.Data.Entity; using System.Data.Entity.Infrastructure; using System.Data.Objects; using System.Linq; using System.Linq.Expressions; using System.Text.RegularExpressions; namespace General { public static class ContextExtensions { public static string GetTableName<T>(this DbContext context) where T : class { ObjectContext objectContext = ((IObjectContextAdapter)context).ObjectContext; return objectContext.GetTableName<T>(); } public static string GetTableName<T>(this ObjectContext context) where T : class { var sql = context.CreateObjectSet<T>().ToTraceString(); var regex = new Regex("FROM (?<table>.*) AS"); var match = regex.Match(sql); string table = match.Groups["table"].Value; return table .Replace("`", string.Empty) .Replace("[", string.Empty) .Replace("]", string.Empty) .Replace("dbo.", string.Empty) .Trim(); } private static bool hasUniqueIndex(this DbContext context, string tableName, string indexName) { var sql = "SELECT count(*) FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS where table_name = '" + tableName + "' and CONSTRAINT_NAME = '" + indexName + "'"; var result = context.Database.SqlQuery<int>(sql).FirstOrDefault(); return result > 0; } private static void createUniqueIndex(this DbContext context, string tableName, string fieldName) { var indexName = "IX_Unique_" + tableName + "_" + fieldName; if (hasUniqueIndex(context, tableName, indexName)) return; var sql = "ALTER TABLE [" + tableName + "] ADD CONSTRAINT [" + indexName + "] UNIQUE ([" + fieldName + "])"; context.Database.ExecuteSqlCommand(sql); } public static void CreateUniqueIndex<TEntity>(this DbContext context, Expression<Func<TEntity, object>> fieldName) where TEntity : class { var field = ((MemberExpression)fieldName.Body).Member.Name; createUniqueIndex(context, context.GetTableName<TEntity>(), field); } } }
توضیحات
متد GetTableName ، به کمک SQL تولیدی حین تعریف جدول متناظر با کلاس جاری، نام جدول را با استفاده از عبارات باقاعده جدا کرده و باز میگرداند. به این ترتیب به دقیقترین نامی که واقعا جهت تولید جدول مورد استفاده قرار گرفته است خواهیم رسید.
در مرحله بعد آن، همان متد createUniqueIndex ابتدای بحث را ملاحظه میکنید. در اینجا جهت حفظ سازگاری بین SQL Server کامل و SQL CE از UNIQUE CONSTRAINT استفاده شده است که همان کار ایجاد ایندکس منحصربفرد را نیز انجام میدهد. به علاوه مزیت دیگر آن امکان دسترسی به تعاریف قید اضافه شده توسط view ایی به نام INFORMATION_SCHEMA.TABLE_CONSTRAINTS است که در نگارشهای مختلف SQL Server به یک نحو تعریف گردیده و قابل دسترسی است. از این view در متد hasUniqueIndex جهت بررسی تکراری نبودن UNIQUE CONSTRAINT در حال تعریف، استفاده میشود. اگر این قید پیشتر تعریف شده باشد، دیگر سعی در تعریف مجدد آن نخواهد شد.
متد CreateUniqueIndex تعریف شده در انتهای کلاس فوق، امکان دریافت نام خاصیتی از TEntity را به صورت strongly typed میسر میسازد.
اینبار برای تعریف یک قید و یا ایندکس منحصربفرد بر روی خاصیتی مشخص در متد Seed، تنها کافی است بنویسیم:
context.CreateUniqueIndex<User>(x=>x.Name);
افزونه فارسی به پارسی برای word 2007
وحید جان همه چیز روبراه است اما در زمان نصب افزونه در پایان نصب یکدفعه note pad باز شده و متنی را نمایش میدهد که بدلیل داشتن عبارات اج تی تی پی و غیره نمیتوان آنها را در اینجا درج نمود.
البته من کلمات را جدا جدا نوشتم تا بتوان این عبارت را در اینجا درج کنم و در ضمن این افزونه در لیست add-in وجود ندارد و در Event viewer نیز خطایی نیست. وهنگامی که سعی کردم از طریق add آنرا نصب کنم باز هم همان صفحه Note Pad در پایان کار نمایش داده شد.
با سپاس
با سپاس.
ASP.NET MVC #12
public enum MyGrade { A = 20, B =15, C =10, } [UIHint("Enum")] public MyGrade Grade { set; get; }
partial view مانند مثال شما ایجادکردم اما زمانی که یک view ایجاد میکنم
Dropdownlist تولید نمیشود
در ادامه مطالب مربوط به برنامه نویسی تابعی، قصد دارم بیشتر وارد کد شویم و مباحث عنوان شده را در دنیای کد پیاده سازی کنیم. هدف این قسمت، refactor کردن کد موجود به یک معماری immutable هست. پیشتر درباره immutable ها صحبت کردیم. ابتدا برای یکسان سازی ادبیات مورد استفاده، چند کلمه را مجددا تعریف خواهیم کرد:
- Immutability: عدم توانایی تغییر داده
- State: دادههایی که در طول زمان تغییر میکنند
- Side Effect: تغییری که روی دادهها اتفاق میافتد
در قطعه کد زیر سعی شدهاست تفاوت یک کلاس Stateless و stateful را به سادگی نشان دهیم:
//Stateful public class UserProfile { private User _user; private string _address; public void UpdateUser(int userId, string name) { _user = new User(userId, name); } } //Stateless public class User { public User(int id, string name) { Id = id; Name = name; } public int Id { get; } public string Name { get; } }
چرا Immutable بودن مهم است؟
هر عمل mutable معادل کدی غیر شفاف است. در واقع وابستگی هر عملی که انجام میدهیم به state، باعث میشود که شرایط ناپایداری را در کد داشته باشیم. به طور مثال در یک عملیات چند نخی تصور کنید که چندین نخ به طور همزمان میتوانند state را تغییر دهند و مدیریت این قضیه باعث به وجود آمدن کدهایی ناخوانا و تحمیل پیچیدگی بیشتر به کد خواهد شد.
در واقع انتظار داریم که به ازای یک ورودی بر اساس بدنهی متد، یک خروجی داشته باشیم؛ ولی در واقعیت تاثیری که اجرای متد بر روی state کل کلاس خواهد گذاشت، از دید ما پنهان است و باعث به وجود آمدن مشکلات بعدی خواهد شد. برای مثال قطعه کد بالا را به صورت Honest بازنویسی میکنیم:
public class UserProfile { private readonly User _user; private readonly string _address; public UserProfile(User user,string address) { _user = user; _address = address; } public UserProfile UpdateUser(int userId, string name) { var newUser = new User(userId, name); return new UserProfile(newUser,_address); } } public class User { public User(int id, string name) { Id = id; Name = name; } public int Id { get; } public string Name { get; } }
در این مثال متد UpdateUser به جای void، یک شی از جنس کلاس UserProfile را بر میگرداند. کلاس UserProfile هم برای وهله سازی نیاز به یک شیء از جنس User و Address را دارد. بنابراین مطمئن هستیم که مقدار دهی شدهاند. نکته دیگر در قطعه کد بالا این است که به ازای هر بار فراخوانی متد، یک شیء جدید بدون وابستگی به وهله سازی اشیاء دیگر، برگردانده میشود.
Immutable
بودن باعث میشود:
- خوانایی کد افزایش پیدا کند
- جای واحدی برای Validate کردن داشته باشیم
- به صورت ذاتی Thread Safe باشیم
در مورد محدودیتهایی که در کار با اشیاء Immutable باید در نظر داشته باشیم، میتوان به مصرف بالای رم و سی پی یو، اشاره کرد. در واقع به نسبت حالت mutate، تعداد اشیاء بیشتری ساخته خواهند شد. در فریمورک دات نت برای کار با اشیا immutable امکاناتی در نظر گرفته شده که این هزینه را کاهش میدهند. به طور مثال میتوانیم از کلاس ImmutableList استفاده کنیم و از ایجاد اشیاء اضافهتر و تحمیل بار اضافی به GC جلوگیری کنیم. یک مثال:
//Create Immutable List ImmutableList<string> list = ImmutableList.Create<string>(); ImmutableList<string> list2 = list.Add("Salam"); //Builder ImmutableList<string>.Builder builder = ImmutableList.CreateBuilder<string>(); builder.Add("avali"); builder.Add("dovomi"); builder.Add("sevomi"); ImmutableList<string> immutableList = builder.ToImmutable();
چطور با side effect کنار بیایم؟
یکی از الگوهای رایج برای این کار، مفهوم جدا سازی Command/Query است. به طور ساده تمامی عملیاتی را که تاثیر گذار هستند، به صورت Command در نظر میگیریم. Command ها معمولا هیچ نوعی را بازگشت نمیدهند و همینطور بر عکس این قضیه برای Query ها صادق است. اشتباه رایج درباره این الگو، محدود کردن این الگو به معماریهای خاصی مانند Domain Driven میباشد؛ در صورتیکه الزامی برای رعایت این الگو در سایر معماریها وجود ندارد.
به مثال زیر دقت کنید. سعی کردم قسمتهای Command و Query را از هم جدا کنم:
در واقع هر برنامه میتواند شامل دو قسمت باشد:
قسمتی که در آن منطق تجاری برنامه پیاده سازی میشود و باید به صورت Immutable
باشد که یک خروجی را تولید میکند و قسمت دیگر برنامه که خروجی تولید شده را برای ذخیره
سازی وضعیت سیستم استفاده میکند.
در واقع یک هسته Immutable، ورودی را دریافت کرده و خروجیهای مورد نیاز را تولید میکند و همه اینها در دل یک پوستهMutable پیاده سازی میشوند که ما در اینجا به آن اصطلاحا Mutable Shell میگوییم.
برای مسائلی که در بالا صحبت شد، نمونهای را آماده کردهام. این نمونه به طور ساده یک سیستم مدیریت نوبت است که نوبتها را در فایلی ذخیره و بازیابی میکند ( mutate ) و منطق مربوط به نوبتها و زمان ویزیت آن میتواند به صورت immutable پیاده سازی شود. این کد در دو حالت functional و غیر functional پیاده سازی شده تا به خوبی تفاوت آن را در حالت قبل و بعد از برنامه نویسی تابعی بتوانیم درک کنیم. به جهت خوانایی بیشتر و دسترسی به کدها، آنها را روی گیتهاب قرار داده و شما میتوانید از اینجا سورس کد مورد نظر را بررسی کنید. سعی شده در این مثال تمامی مواردی که در این قسمت ذکر شد را پیاده سازی کنیم. امیدوارم که مطالب مربوط به برنامه نویسی تابعی یا functional programming توانسته باشد دیدگاه جدیدی را به کدهایی که مینویسیم بدهد. در قسمتهای بعدی به مواردی مانند مدیریت exception ها و کار با null ها و ... خواهیم پرداخت.
در این قسمت قصد داریم یک فرم Ajaxایی را در ASP.NET MVC به همراه تمام مسایل اعتبارسنجی، پردازش اطلاعات و غیره را به کمک Twitter Bootstrap و jQuery Ajax پیاده سازی کنیم.
تهیه افزونه jquery.bootstrap-modal-ajax-form.js
از این جهت که مباحث مرتبط با نمایش و پردازش فرمهای مودال Ajaxایی به کمک Twitter Bootstrap اندکی نکته دار و طولانی هستند، بهتر است این موارد را به شکل یک افزونه، کپسوله کنیم. کدهای کامل افزونه jquery.bootstrap-modal-ajax-form.js را در ادامه ملاحظه میکنید:
// <![CDATA[ (function ($) { $.bootstrapModalAjaxForm = function (options) { var defaults = { renderModalPartialViewUrl: null, renderModalPartialViewData: null, postUrl: '/', loginUrl: '/login', beforePostHandler: null, completeHandler: null, errorHandler: null }; var options = $.extend(defaults, options); var validateForm = function (form) { //فعال سازی دستی اعتبار سنجی جیکوئری var val = form.validate(); val.form(); return val.valid(); }; var enableBootstrapStyleValidation = function () { $.validator.setDefaults({ highlight: function (element, errorClass, validClass) { if (element.type === 'radio') { this.findByName(element.name).addClass(errorClass).removeClass(validClass); } else { $(element).addClass(errorClass).removeClass(validClass); $(element).closest('.control-group').removeClass('success').addClass('error'); } $(element).trigger('highlited'); }, unhighlight: function (element, errorClass, validClass) { if (element.type === 'radio') { this.findByName(element.name).removeClass(errorClass).addClass(validClass); } else { $(element).removeClass(errorClass).addClass(validClass); $(element).closest('.control-group').removeClass('error').addClass('success'); } $(element).trigger('unhighlited'); } }); } var enablePostbackValidation = function () { $('form').each(function () { $(this).find('div.control-group').each(function () { if ($(this).find('span.field-validation-error').length > 0) { $(this).addClass('error'); } }); }); } var processAjaxForm = function (dialog) { $('form', dialog).submit(function (e) { e.preventDefault(); if (!validateForm($(this))) { //اگر فرم اعتبار سنجی نشده، اطلاعات آن ارسال نشود return false; } //در اینجا میتوان مثلا دکمهای را غیرفعال کرد if (options.beforePostHandler) options.beforePostHandler(); //اطلاعات نباید کش شوند $.ajaxSetup({ cache: false }); $.ajax({ url: options.postUrl, type: "POST", data: $(this).serialize(), success: function (result) { if (result.success) { $('#dialogDiv').modal('hide'); if (options.completeHandler) options.completeHandler(); } else { $('#dialogContent').html(result); if (options.errorHandler) options.errorHandler(); } } }); return false; }); }; var mainContainer = "<div id='dialogDiv' class='modal hide fade in'><div id='dialogContent'></div></div>"; enableBootstrapStyleValidation(); //اعمال نکات خاص بوت استرپ جهت اعتبارسنجی یکپارچه با آن $.ajaxSetup({ cache: false }); $.ajax({ type: "POST", url: options.renderModalPartialViewUrl, data: options.renderModalPartialViewData, contentType: "application/json; charset=utf-8", dataType: "json", complete: function (xhr, status) { var data = xhr.responseText; var data = xhr.responseText; if (xhr.status == 403) { window.location = options.loginUrl; //در حالت لاگین نبودن شخص اجرا میشود } else if (status === 'error' || !data) { if (options.errorHandler) options.errorHandler(); } else { var dialogContainer = "#dialogDiv"; $(dialogContainer).remove(); $(mainContainer).appendTo('body'); $('#dialogContent').html(data); // دریافت پویای اطلاعات مودال دیالوگ $.validator.unobtrusive.parse("#dialogContent"); // فعال سازی اعتبارسنجی فرمی که با ایجکس بارگذاری شده enablePostbackValidation(); // و سپس نمایش آن به صورت مودال $('#dialogDiv').modal({ backdrop: 'static', //با کلیک کاربر روی صفحه، صفحه مودال بسته نمیشود keyboard: true }, 'show'); // تحت نظر قرار دادن این فرم اضافه شده processAjaxForm('#dialogContent'); } } }); }; })(jQuery); // ]]>
- توابع enableBootstrapStyleValidation و enablePostbackValidation در مطلب «اعمال کلاسهای ویژه اعتبارسنجی Twitter bootstrap به فرمهای ASP.NET MVC» بررسی شدند.
- این افزونه با توجه به مقدار renderModalPartialViewUrl، یک partial view را از برنامه ASP.NET MVC درخواست میکند.
- سپس این partial view را به صورت خودکار به صفحه اضافه کرده و آنرا به صورت modal نمایش میدهد.
- پس از افزودن فرم Ajaxایی دریافتی، مسایل اعتبارسنجی را به آن اعمال کرده و سپس دکمه submit آنرا تحت کنترل قرار میدهد.
- در زمان submit، ابتدا بررسی میکند که آیا فرم معتبر است و اعتبارسنجی آن بدون مشکل است؟ اگر اینچنین است، اطلاعات فرم را به آدرس postUrl به صورت Ajaxایی ارسال میکند.
کدهای مدل برنامه
using System.ComponentModel; using System.ComponentModel.DataAnnotations; namespace Mvc4TwitterBootStrapTest.Models { public class User { public int Id { set; get; } [DisplayName("نام")] [Required(ErrorMessage="لطفا نام را تکمیل کنید")] public string Name { set; get; } [DisplayName("نام خانوادگی")] [Required(ErrorMessage = "لطفا نام خانوادگی را تکمیل کنید")] public string LastName { set; get; } } }
کدهای کنترلر برنامه
using System.Web.Mvc; using Mvc4TwitterBootStrapTest.Models; namespace Mvc4TwitterBootStrapTest.Controllers { public class ModalFormAjaxController : Controller { [HttpGet] public ActionResult Index() { return View(); //نمایش صفحه اولیه } [HttpPost] //برای این حالت امنتر است //[AjaxOnly] public ActionResult RenderModalPartialView() { //رندر پارشال ویوو صفحه مودال به همراه اطلاعات مورد نیاز آن return PartialView(viewName: "_ModalPartialView", model: new User { Name = "", LastName = "" }); } [HttpPost] //[AjaxOnly] public ActionResult Index(User user) //ذخیره سازی اطلاعات { if (this.ModelState.IsValid) { //todo: SaveChanges; return Json(new { success = true }); } this.ModelState.AddModelError("", "خطایی رخ داده است"); return PartialView("_ModalPartialView", user); } } }
الف) متد Index حالت HttpGet که صفحه ابتدایی را نمایش خواهد داد.
ب) متد RenderModalPartialView یک partial view اضافه شده به برنامه به نام _ModalPartialView.cshtml را بازگشت میدهد. این partial view در حقیقت همان فرمی است که قرار است به صورت مودال نمایش داده شود و پردازش آن نیز Ajaxایی باشد.
ج) متد Index حالت HttpPost که نهایتا اطلاعات فرم مودال را دریافت خواهد کرد. اگر پردازش موفقیت آمیز بود، نیاز است همانند کدهای فوق return Json صورت گیرد. در غیراینصورت مجددا همان partial view را بازگشت دهید.
کدهای Index.cshtml
@{ ViewBag.Title = "Index"; var renderModalPartialViewUrl = Url.Action("RenderModalPartialView", "ModalFormAjax"); var postDataUrl = Url.Action("Index", "ModalFormAjax"); } <h2> Index</h2> <a href="#" class="btn btn-primary" id="btnCreate">ثبت اطلاعات</a> @section JavaScript { <script type="text/javascript"> $(function () { $('#btnCreate').click(function (e) { e.preventDefault(); //میخواهیم لینک به صورت معمول عمل نکند $.bootstrapModalAjaxForm({ postUrl: '@postDataUrl', renderModalPartialViewUrl: '@renderModalPartialViewUrl', renderModalPartialViewData: {}, loginUrl: '/login', beforePostHandler: function () { }, completeHandler: function () { // Refresh: برای حالتیکه نیاز به به روز رسانی کامل صفحه زیرین باشد // location.reload(); }, errorHandler: function () { } }); }); }); </script> }
- در اینجا یک لینک ساده در صفحه قرار گرفته و به کمک کلاس btn مجموعه bootstrap به شکل یک دکمه مزین شده است.
- در ادامه نحوه استفاده از افزونهای را که در ابتدای بحث طراحی کردیم، ملاحظه میکنید. کار با آن بسیار ساده است و تنها باید مسیرهای ارسال اطلاعات نهایی به سرور یا postDataUrl و مسیر دریافت partial view رندر شده یا renderModalPartialViewUrl به آن معرفی شود. سایر مسایل آن خودکار است.
کدهای _ModalPartialView.cshtml یا همان فرم مودال برنامه
@model Mvc4TwitterBootStrapTest.Models.User <div class="modal-header"> <button type="button" class="close" data-dismiss="modal" aria-hidden="true"> ×</button> <h5> افزودن کاربر جدید</h5> </div> @using (Html.BeginForm("Index", " ModalFormAjax", FormMethod.Post, new { @class = "modal-form" })) { <div class="modal-body"> @Html.ValidationSummary(true, null, new { @class = "alert alert-error alert-block" }) <fieldset class="form-horizontal"> <legend>مشخصات کاربر</legend> <div class="control-group"> @Html.LabelFor(model => model.Name, new { @class = "control-label" }) <div class="controls"> @Html.EditorFor(model => model.Name) @Html.ValidationMessageFor(model => model.Name, null, new { @class = "help-inline" }) </div> </div> <div class="control-group"> @Html.LabelFor(model => model.LastName, new { @class = "control-label" }) <div class="controls"> @Html.EditorFor(model => model.LastName) @Html.ValidationMessageFor(model => model.LastName, null, new { @class = "help-inline" }) </div> </div> </fieldset> </div> <div class="modal-footer"> <button class="btn btn-primary" type="submit"> ارسال</button> <button class="btn" data-dismiss="modal" aria-hidden="true"> انصراف</button> </div> }
حاصل نهایی این مبحث را در دو شکل ذیل ملاحظه میکنید. صفحه index نمایش دهنده یک دکمه و در ادامه باز شدن یک فرم مودال، پس از کلیک بر روی دکمه ثبت اطلاعات.
تفاوت بین Interface و کلاس Abstract در چیست؟
public interface IRepositoryBase<TEntity> : IReadOnlyRepositoryBase<TEntity> where TEntity : class, IEntity { Task<TEntity> InsertAsync([NotNull] TEntity entity); Task InsertManyAsync([NotNull] IEnumerable<TEntity> entities); Task<TEntity> UpdateAsync([NotNull] TEntity entity); Task UpdateManyAsync([NotNull] IEnumerable<TEntity> entities); // Todo InsertOrUpdate(TEntity entity); Task DeleteAsync([NotNull] TEntity entity); Task DeleteManyAsync([NotNull] IEnumerable<TEntity> entities); } public interface IRepositoryBase<TEntity, TKey> : IRepositoryBase<TEntity>, IReadOnlyRepositoryBase<TEntity, TKey> where TEntity : class, IEntity<TKey> { Task DeleteAsync(TKey id); //TODO: Return true if deleted Task DeleteManyAsync([NotNull] IEnumerable<TKey> ids); }