EF Code First #1
وقتی کانکش استرینگو به این صورت تعربف میکنم :
<configuration> <configSections> </configSections> <connectionStrings> <clear/> <add name="Context" connectionString="Data Source=localhost;Initial Catalog=test;Integrated Security = true" providerName="System.Data.SqlClient"/> </connectionStrings> <system.web> <compilation debug="true"/></system.web> </configuration>
این erorr میده :
An error occurred while getting provider information from the database. This can be caused by Entity Framework using an incorrect connection string. Check the inner exceptions for details and ensure that the connection string is correct.
علتش چی میتونه باشه ؟
State machine مدلی است بیانگر نحوه واکنش سیستم به وقایع مختلف. یک ماشین حالت وضعیت جاری قسمتی از سیستم را نگهداری کرده و به ورودیهای مختلف پاسخ میدهد. این ورودیها در نهایت وضعیت سیستم را تغییر خواهند داد.
نحوه پاسخگویی یک ماشین حالت (State machine) را به رویدادی خاص، انتقال (Transition) مینامند. در یک انتقال مشخص میشود که ماشین حالت بر اساس وضعیت جاری خود، با دریافت یک رویداد، چه عکس العملی را باید بروز دهد. عموما (و نه همیشه) در حین پاسخگویی ماشین حالت به رویدادهای رسیده، وضعیت آن نیز تغییر خواهد کرد. در اینجا گاهی از اوقات پیش از انجام عملیاتی، نیاز است شرطی بررسی شده و سپس انتقالی رخ دهد. به این شرط، guard گفته میشود.
بنابراین به صورت خلاصه، یک ماشین حالت، مدلی است از رفتاری خاص، تشکیل شده از حالات، رویدادها، انتقالات، اعمال (actions) و شرطها (Guards). در اینجا:
- یک حالت (State)، شرطی منحصربفرد در طول عمر ماشین حالت است. در هر زمان مشخصی، ماشین حالت در یکی از حالات از پیش تعریف شده خود قرار خواهد داشت.
- یک رویداد (Event)، اتفاقی است که به ماشین حالت اعمال میشود؛ یا همان ورودیهای سیستم.
- یک انتقال (Transition)، بیانگر نحوه رفتار ماشین حالت جهت پاسخگویی به رویداد وارده بر اساس وضعیت جاری خود میباشد. در طی یک انتقال، سیستم از یک حالت به حالتی دیگر منتقل خواهد شد.
- برای انجام یک انتقال، نیاز است یک شرط (Guard/Conditional Logic) بررسی شده و در صورت true بودن آن، انتقال صورت گیرد.
- یک عمل (Action)، بیانگر نحوه پاسخگویی ماشین حالت در طول دوره انتقال است.
چگونه میتوان الگوی ماشین حالت را تشخیص داد؟
اکثر برنامههای وب، متشکل از پیاده سازی چندین ماشین حالت میباشند؛ مانند ثبت نام در سایت، درخواست یک کتاب از کتابخانه، ارسال درخواستها و پاسخگویی به آنها و یا حتی ارسال یک مطلب در سایت، تائید و انتشار آن.
البته عموما در حین طراحی برنامهها، کمتر به این نوع مسایل به شکل یک ماشین حالت نگاه میشود. به همین جهت بهتر است معیارهایی را برای شناخت زود هنگام آنها مدنظر داشته باشیم:
- آیا در جدول بانک اطلاعاتی خود فیلدهایی مانند State (حالت) یا Status (وضعیت)دارید؟ اگر بله، به این معنا است که در حال کار با یک ماشین حالت هستید.
- عموما فیلدهای Bit و Boolean، بیانگر حضور ماشینهای حالت هستند. مانند IsPublished ، IsPaid و یا حتی داشتن یک فیلد timeStamp که میتواند NULL بپذیرد نیز بیانگر استفاده از ماشین حالت است؛ مانند فیلدهای published_at، paid_at و یا confirmed_at.
- داشتن رکوردهایی که تنها در طول یک بازه زمانی خاص، معتبر هستند. برای مثال آبونه شدن در یک سایت در طول یک بازه زمانی مشخص.
- اعمال چند مرحلهای؛ مانند ثبت نام در سایت و دریافت ایمیل فعال سازی. سپس فعال سازی اکانت از طریق ایمیل.
مثالی ساده از یک ماشین حالت
یک کلید برق را در نظر بگیرید. این کلید دارای دو حالت (states) روشن و خاموش است. زمانی که خاموش است، با دریافت رخدادی (event)، به وضعیت (state/status) روشن، منتقل خواهد شد (Transition) و برعکس.
در اینجا حالات با مستطیلهای گوشه گرد نمایش داده شدهاند. انتقالات توسط فلشهایی انحناء دار که حالات را به یکدیگر متصل میکنند، مشخص گردیدهاند. برچسبهای هر فلش، مشخص کننده نام رویدادی است که سبب انتقال و تغییر حالت میگردد. با شروع یک ماشین حالت، این ماشین در یکی از وضعیتهای از پیش تعیین شدهاش قرار خواهد گرفت (initial state)؛ که در اینجا حالت خاموش است.
این نوع نمودارها میتوانند شامل جزئیات بیشتری نیز باشند؛ مانند برچسبهایی که نمایانگر اعمال قابل انجام در طی یک انتقال هستند.
رسم ماشینهای حالت در برنامههای وب، به کمک کتابخانه jsPlumb
کتابخانههای زیادی برای رسم فلوچارت، گردشهای کاری، ماشینهای حالت و امثال آن جهت برنامههای وب وجود دارند و یکی از معروفترینهای آنها کتابخانه jsPlumb است. این کتابخانه به صورت یک افزونه jQuery طراحی شده است؛ اما به عنوان افزونهای برای کتابخانههای MooTools و یا YUI3/Yahoo User Interface 3 نیز قابل استفاده میباشد. کتابخانه jsPlumb در مرورگرهای جدید از امکانات ترسیم SVG و یا HTML5 Canvas استفاده میکند. برای سازگاری با مرورگرهای قدیمیتر مانند IE8 به صورت خودکار به VML سوئیچ خواهد کرد. همچنین این کتابخانه امکانات ترسیم تعاملی قطعات به هم متصل شونده را نیز دارا است (شبیه به طراح یک گردش کاری). البته برای اضافه شدن امکاناتی مانند کشیدن و رها کردن در آن نیاز به jQuery-UI نیز خواهد داشت.
برای نمونه اگر بخواهیم مثال فوق را توسط jsPlumb ترسیم کنیم، روش کار به صورت زیر خواهد بود:
<!doctype html> <html> <head> <title>State Machine Demonstration</title> <style type="text/css"> #opened { left: 10em; top: 5em; } #off { left: 12em; top: 15em; } #on { left: 28em; top: 15em; } .w { width: 5em; padding: 1em; position: absolute; border: 1px solid black; z-index: 4; border-radius: 1em; border: 1px solid #346789; box-shadow: 2px 2px 19px #e0e0e0; -o-box-shadow: 2px 2px 19px #e0e0e0; -webkit-box-shadow: 2px 2px 19px #e0e0e0; -moz-box-shadow: 2px 2px 19px #e0e0e0; -moz-border-radius: 0.5em; border-radius: 0.5em; opacity: 0.8; filter: alpha(opacity=80); cursor: move; } .ep { float: right; width: 1em; height: 1em; background-color: #994466; cursor: pointer; } .labelClass { font-size: 20pt; } </style> <script type="text/javascript" src="jquery.min.js"></script> <script type="text/javascript" src="jquery-ui.min.js"></script> <script type="text/javascript" src="jquery.jsPlumb-all-min.js"></script> <script type="text/javascript"> $(document).ready(function () { jsPlumb.importDefaults({ Endpoint: ["Dot", { radius: 5}], HoverPaintStyle: { strokeStyle: "blue", lineWidth: 2 }, ConnectionOverlays: [ ["Arrow", { location: 1, id: "arrow", length: 14, foldback: 0.8}] ] }); jsPlumb.makeTarget($(".w"), { dropOptions: { hoverClass: "dragHover" }, anchor: "Continuous" }); $(".ep").each(function (i, e) { var p = $(e).parent(); jsPlumb.makeSource($(e), { parent: p, anchor: "Continuous", connector: ["StateMachine", { curviness: 20}], connectorStyle: { strokeStyle: '#42a62c', lineWidth: 2 }, maxConnections: 2, onMaxConnections: function (info, e) { alert("Maximum connections (" + info.maxConnections + ") reached"); } }); }); jsPlumb.bind("connection", function (info) { }); jsPlumb.draggable($(".w")); jsPlumb.connect({ source: "opened", target: "off" }); jsPlumb.connect({ source: "off", target: "on", label: "Turn On" }); jsPlumb.connect({ source: "on", target: "off", label: "Turn Off" }); }); </script> </head> <body> <div class="w" id="opened"> Begin <div class="ep"> </div> </div> <div class="w" id="off"> Off <div class="ep"> </div> </div> <div class="w" id="on"> On <div class="ep"> </div> </div> </body> </html>
در مثال فوق، ابتدا css و فایلهای js مورد نیاز ذکر شدهاند. توسط css، مکان قرارگیری اولیه المانهای متناظر با حالات، مشخص میشوند.
سپس زمانیکه اشیاء صفحه در دسترس هستند، تنظیمات jsPlumb انجام خواهد شد. برای مثال در اینجا نوع نمایشی Endpointها به نقطه تنظیم شده است. موارد دیگری مانند مستطیل نیز قابل تنظیم است. سپس نیاز است منبع و مقصدها به کتابخانه jsPlumb معرفی شوند. به کمک متد jsPlumb.makeTarget، تمام المانهای دارای کلاس w به عنوان منبع و با شمارش divهایی با class=ep، مقصدهای قابل اتصال تعیین شدهاند (jsPlumb.makeSource). متد jsPlumb.bind یک callback function است و هربار که اتصالی برقرار میشود، فراخوانی خواهد شد. متد jsPlumb.draggable تمام عناصر دارای کلاس w را قابل کشیدن و رها کردن میکند و در آخر توسط متدهای jsPlumb.connect، مقصد و منبعهای مشخصی را هم متصل خواهیم کرد. نمونه نهایی تهیه شده برای بررسی بیشتر.
برای مطالعه بیشتر
Finite-state machine
UML state machine
UML 2 State Machine Diagrams
مثالهایی در این مورد
تبدیلگر ایران سیستم به یونیکد
string dbfDirectory = Server.MapPath("~/Content/") + "myDBF.dbf"; string connectionString = "Provider=VFPOLEDB.1;Data Source=" + dbfDirectory + ";Password=;Collating Sequence=MACHINE"; using (OleDbConnection connection = new OleDbConnection(connectionString)) { connection.Open(); string CreateTableK = "Create Table myDBF (dsw_id1 int, dsw_fname v(250), dsw_lname v(250) )"; OleDbCommand cmdCreateTable = new OleDbCommand(CreateTableK, connection); cmdCreateTable.ExecuteNonQuery(); OleDbCommand cmd2 = new OleDbCommand("Insert Into myDBF Values (1, @param1, @param2)", connection); cmd2.Parameters.AddWithValue("@param1", "شهرزاد"); cmd2.Parameters.AddWithValue("@param2", "جواهری"); OleDbCommand cmd3 = new OleDbCommand("Insert Into myDBF Values (2, 'ma', 'bi')", connection); cmd2.ExecuteNonQuery(); cmd3.ExecuteNonQuery(); connection.Close(); }
EF Code First #5
- EF Code first زمانیکه مشاهده کنه دیتابیس تعریف شده در رشته اتصالی وجود خارجی ندارد، سعی در ایجاد آن خواهد کرد. شما در هاستها عموما دسترسی dbo ندارید. یعنی دسترسی ساخت دیتابیس ندارید. به عبارتی باید یک دیتابیس خالی از پیش تعیین شده داشته باشید. اگر پنل خاصی دارید از آن استفاده کنید برای ساخت دیتابیس. اگر ندارید باید تماس بگیرید تا دیتابیس برای شما ایجاد شود.
- زمانیکه خطای یافت نشدن بانک اطلاعاتی DataLayer.Context.MedicallexiconContex را دریافت میکنید یعنی پیش فرضهای EF Code first رو رعایت نکردید. در اینجا name ایی که در رشته اتصالی تعریف میکنید مهم است. به صورت پیش فرض این name باید همان نام کلاس Context شما باشد (صرفنظر از اینکه رشته اتصالی شما به چه بانک اطلاعاتی اشاره میکند).
- Configuration
- Routing
- MVC
- Application
- و ...
- کاربر یک درخواست Http را توسط مرورگر ارسال میکند.
- یکی از اولین میان افزارها یعنی میان افزار Routing، آدرس درخواست را میخواند، کنترلر و اکشن مورد نظر را مییابد و بهوسیلهی Activator Utility، سعی در فعال سازی آن کنترلر میکند.
- DI Container لیست پارامترهای سازندهی کنترلر را مشاهده میکند و سرویسهای مورد نیاز را از درون خود واکشی کرده، از آنها نمونه سازی میکند و نمونههای ساخته شده را به درون شیء کنترلر تزریق میکند.
- Routing درخواست HttpRequest را تجزیه کرده و اکشن متد مورد نظر را برای اجرای آن فراخوانی کرده
- و نتیجهی اجرای اکشن را به درخواست دهنده بر میگرداند.
هر چند که کنترلرها درون DI Container ثبت نشدهاند، ولی توسط کلاسهایی درون فریم ورک، از آنها نمونه سازی میشود و در حین نمونه سازی، DI Container سرویسهای مورد نظر آنها را در صورت وجود، فراهم میکند.
ثبت تنظیمات وبسایت و فراخوانی آنها در برنامه
در تمام برنامههای ASP.NET Core شما نیاز به تنظیماتی برای پیکربندی کار برنامهی خود دارید. این تنظیمات میتوانند شامل Connection String اتصال به پایگاه داده، تنظیمات اتصال به سرویسهای خارجی مثل درگاههای پرداخت آنلاین بانکها و ... باشند. در اینجا ما تنظیمات اختصاصی را درون فایل AppSetting اضافه میکنیم. بعد برای هر بخش از تنظیمات، در پوشهی Configs یک کلاس سادهی سی شارپ را میسازیم و سپس با گرفتن و تزریق کردن این فایلهای Config درون DI Container، هر زمانی خواستیم، از آنها استفاده میکنیم.
ابتدا به سراغ تنظیمات کلی میرویم و دو تنظیم نام برنامه و پیغام خوش آمد گویی را به برنامه اضافه میکنیم (فایل appSettings را به صورت زیر تغییر میدهیم) :
"ApplicationName": "Dependency Injection Demo", "GreetingMessage": "Welcome to Dependency Injection Demo", "AllowedHosts": "*", "Logging": { "LogLevel": { "Default": "Information", "Microsoft": "Warning", "Microsoft.Hosting.Lifetime": "Information" } },
برای سادگی کار، با بخش Logging
کاری نداریم . اکنون فایل AppConfig.cs را به برنامه اضافه میکنیم:
namespace AspNetCoreDependencyInjection.Configs { public class AppConfig { public string ApplicationName { get; set; } public string GreetingMessage { get; set; } public string AllowedHosts { get; set; } } }
برای دسترسی بهتر میتوانیم سازندهی کلاس Startup را تغییر دهیم:
public IWebHostEnvironment Environment { get; } public IConfiguration Configuration { get; } public IServiceCollection Services { get; set; } public Startup(IWebHostEnvironment environment) { var builder = new ConfigurationBuilder() .SetBasePath(environment.ContentRootPath) .AddJsonFile("appsettings.json", optional: true) .AddEnvironmentVariables(); this.Environment = environment; this.Configuration = builder.Build(); }
var appSettingsFile = environment.IsProduction() ? "appsettings.json" : "appsettings_dev.json"; var builder = new ConfigurationBuilder() .SetBasePath(environment.ContentRootPath) .AddJsonFile( appSettingsFile , optional: true) .AddEnvironmentVariables();
services.AddSingleton(services => new AppConfig { ApplicationName = this.Configuration["ApplicationName"], GreetingMessage = this.Configuration["GreetingMessage"], AllowedHosts = this.Configuration["AllowedHosts"] });
در کد بالا در هنگام اجرای برنامه، یک نمونه از کلاس AppConfig را با طول حیات Singleton ثبت کردیم و Property های این شیء را به وسیلهی ایندکس Configuration[“FieldName”]، تک تک پر کردیم.
حالا میتوانیم سرویس AppConfig را
در هر کلاسی از برنامهی خودمان تزریق و از آن استفاده کنیم. برای مثل در اینجا یک
کنترلر به نام AppSettingsController ساختم و کلاس فوق را به آن تزریق کردم:
public class AppSettingsController : Controller { private readonly AppConfig _appConfig; public AppSettingsController(AppConfig appConfig) { _appConfig = appConfig; } // codes here … }
می توانیم از همین الگو برای تعریف، ثبت و استفاده از سایر تنظیمات نیز استفاده کنیم:
"UserOptionConfig": { "UsersAvatarsFolder": "avatars", "UserDefaultPhoto": "icon-user-default.png", "UserAvatarImageOptions": { "MaxWidth": 150, "MaxHeight": 150 } }, "LiteDbConfig": { "ConnectionString": "Filename=\\Data\\DependencyInjectionDemo.db;Connection=direct;Password=@123456;" }
برای LiteDbConfig
مانند AppConfig عمل میکنیم، ولی در هنگام ثبت آن، به روش زیر عمل میکنیم. تنها تفاوتی
که وجود دارد، نحوهی دستیابی به فیلدهای درونی فایل JSON به وسیلهی شیء Configuration
است:
services.AddSingleton(services => new LiteDbConfig { ConnectionString = this.Configuration["LiteDbConfig:ConnectionString"], });
اکنون برای استفادهی از مدخل UserOptionConfig،
کلاسهای زیر را میسازیم:
namespace AspNetCoreDependencyInjection.Configs { public class UserOptionConfig { public string UsersAvatarsFolder { get; set; } public string UserDefaultPhoto { get; set; } public UserAvatarImageOptions UserAvatarImageOptions { get; set; } } public class UserAvatarImageOptions { public int MaxHeight { get; set; } public int MaxWidth { get; set; } } }
جداسازی بخشهای مختلف تنظیمات پیکربندی باعث میشود تا بتوانیم دو اصل اساسی از طراحی نرم افزار را رعایت کنیم :
- Interface Segregation Principle (ISP) or Encapsulation : کلاسهایی که به تنظیمات نیاز دارند، فقط به آن بخشی از تنظیمات دسترسی خواهند داشتند که واقعا مورد نیازشان باشد.
- Separation Of Concerns : تنظیمات بخشهای مختلف برنامه، به یکدیگر وابسته و جفت شده نیستند.
در اینجا نیاز به استفاده از پکیج Microsoft.Extensions.Options.ConfigurationExtensions را داریم که به صورت درونی در ASP.NET Core تعبیه شده است.
برای ثبت این تنظیمات درون DI Container، از نمونهی جنریک متد Configure در IServiceCollection به صورت زیر استفاده میکنیم:
services.Configure<UserOptionConfig>(this.Configuration.GetSection("UserOptionConfig"));
متد GetSection بر اساس نام بخش تنظیمات، خود آن تنظیم و تمامی تنظیمات درونی آن را به صورت یک IConfigurationSection بر میگرداند و متد Configure<TOption> یک IConfiguration را گرفته و به صورت خودکار به TOption اتصال میدهد و سپس این شیء را درون DI Container به عنوان یک IConfigurationOptions<TOption> و با طول حیات Singleton ثبت میکند.
برای دسترس به UserOptionConfig درون کلاس مورد نظر ما، اینترفیس <IOptionMonitor<TOption را به سازندهی کلاس مورد نظر تزریق میکنیم. کد زیر را که نسخهی تغییر یافتهی کلاس AppSettingsController است را مشاهده کنید:private readonly LiteDbConfig _liteDbConfig; private readonly AppConfig _appConfig; private readonly UserOptionConfig _userOptionConfig; public AppSettingsController(AppConfig appConfig , LiteDbConfig liteDbConfig , IOptionsMonitor<UserOptionConfig> userOptionConfig) { _appConfig = appConfig; _liteDbConfig = liteDbConfig; _userOptionConfig = userOptionConfig.CurrentValue; }
نکته ای که وجود دارد، کلاسهای تعریف شده برای استفادهی از این الگو باید شرایط زیر را داشته باشند ( مثل کلاس UserOptionConfig ) :
- باید سطح دسترسی public داشته باشند.
- باید دارای سازندهی پیش فرض باشند.
- باید نام Property های آنها دقیقا همنام فیلدهای تنظیمات باشد تا فرایند mapping خودکار به درستی انجام شود.
- باید Property ها و Setter آنها ، سطح دسترسی public داشته باشند.
هر دو روش بالا که یکی به
صورت عادی تنظیمات را ثبت میکند و دیگری با استفاده از Option Pattern بخشهای مختلف را ثبت میکند،
مناسب هستند. البته گاهی اوقات فایلهای تنظیمات پروژهی شما در لایههای زیرین (یا درونیتر اگر از onion architecture استفاده میکنید) قرار دارند و شما نمیخواهید
در آن لایهها و لایههای درونیتر، وابستگی به پکیجهای ASP.NET Core ایجاد کنید. در این حالت با در
نظر گرفتن دو اصل ISP و Separation of Concerns ،
به ازای هر بخش مختلف از تنظیمات، فایلهای تنظیمات را در لایههای زیرین/درونی
تعریف کرده، بعد در لایههای بالاتر/بیرونیتر آنها را به درون سرویسها یا کلاسهای مورد نیاز، تزریق کنید. البته مثل همین مثال، ثبت این سرویسها درون برنامهی ASP.NET Core که
معمولا بالاترین/بیرونیترین لایه از پروژهی ما هست، انجام میشود.
CSRF یا Cross Site Request Forgery به صورت خلاصه به این معنا است که شخص مهاجم اعمالی را توسط شما و با سطح دسترسی شما بر روی سایت انجام دهد و اطلاعات مورد نظر خود را استخراج کرده (محتویات کوکی یا سشن و امثال آن) و به هر سایتی که تمایل دارد ارسال کند. اینکار عموما با تزریق کد در صفحه صورت میگیرد. مثلا ارسال تصویری پویا به شکل زیر در یک صفحه فوروم، بلاگ یا ایمیل:
شخصی که این صفحه را مشاهده میکند، متوجه وجود هیچگونه مشکلی نخواهد شد و مرورگر حداکثر جای خالی تصویر را به او نمایش میدهد. اما کدی با سطح دسترسی شخص بازدید کننده بر روی سایت اجرا خواهد شد.
روشهای مقابله:
- هر زمانیکه کار شما با یک سایت حساس به پایان رسید، log off کنید. به این صورت بجای منتظر شدن جهت به پایان رسیدن خودکار طول سشن، سشن را زودتر خاتمه دادهاید یا برنامه نویسها نیز باید طول مدت مجاز سشن در برنامههای حساس را کاهش دهند. شاید بپرسید این مورد چه اهمیتی دارد؟ مرورگری که امکان اجازهی بازکردن چندین سایت با هم را به شما در tab های مختلف میدهد، ممکن است سشن یک سایت را در برگهای دیگر به سایت مهاجم ارسال کند. بنابراین زمانیکه به یک سایت حساس لاگین کردهاید، سایتهای دیگر را مرور نکنید. البته مرورگرهای جدید مقاوم به این مسایل شدهاند ولی جانب احتیاط را باید رعایت کرد.
- در برنامه خود قسمت Referrer header را بررسی کنید. آیا متد POST رسیده، از سایت شما صادر شده است یا اینکه صفحهای دیگر در سایتی دیگر جعل شده و به برنامه شما ارسال شده است؟ هر چند این روش آنچنان قوی نیست و فایروالهای جدید یا حتی بعضی از مرورگرها با افزونههایی ویژه، امکان عدم ارسال این قسمت از header درخواست را میسر میسازند.
- برنامه نویسها نباید مقادیر حساس را از طریق GET requests ارسال کنند. استفاده از روش POST نیز به تنهایی کارآمد نیست و آنرا باید با random tokens ترکیب کرد تا امکان جعل درخواست منتفی شود. برای مثال استفاده از ViewStateUserKey در ASP.Net . جهت خودکار سازی اعمال این موارد در ASP.Net، اخیرا HTTP ماژول زیر ارائه شده است:
تنها کافی است که فایل dll آن در دایرکتوری bin پروژه شما قرار گیرد و در وب کانفیگ برنامه ارجاعی به این ماژول را لحاظ نمائید.
کاری که این نوع ماژولها انجام میدهند افزودن نشانههایی اتفاقی ( random tokens ) به صفحه است که مرورگر آنها را بخاطر نمیسپارد و این token به ازای هر سشن و صفحه منحصر بفرد خواهد بود.
برای PHP نیز چنین تلاشهایی صورت گرفته است:
http://csrf.htmlpurifier.org/
مراجعی برای مطالعه بیشتر
Prevent Cross-Site Request Forgery (CSRF) using ASP.NET MVC’s AntiForgeryToken() helper
Cross-site request forgery
Top 10 2007-Cross Site Request Forgery
CSRF - An underestimated attack method
Install-Package Breeze.WebApi2.EF6
var instance = breeze.config.initializeAdapterInstance("ajax", "angular"); instance.setHttp($http);
Install-Package Breeze.Angular
var manager = new breeze.EntityManager({ dataService: dataService, metadataStore: metadataStore, saveOptions: new breeze.SaveOptions({ allowConcurrentSaves: true, tag: [{}] }) });
var dataService = new breeze.DataService({ serviceName: "/breeze/"+ "Automobile", hasServerMetadata: false, namingConvention: breeze.NamingConvention.camelCase }); var metadataStore = new breeze.MetadataStore({});
var myMetadataStore = new breeze.MetadataStore(); myMetadataStore.addEntityType({...});
var customer = function () { this.City = ""; }; myMetadataStore.registerEntityTypeCtor("Customer", customer);
[BreezeController] public class AutomobileController : ApiController { readonly EFContextProvider<ApplicationDbContext> _contextProvider = new EFContextProvider<ApplicationDbContext>(); [HttpGet] public string Metadata() { return _contextProvider.Metadata(); } [HttpGet] public IQueryable<Customer> Customers() { return _contextProvider.Context.Customers; } [System.Web.Http.HttpPost] public SaveResult SaveChanges(JObject saveBundle) { _contextProvider.BeforeSaveEntitiesDelegate = BeforeSaveEntities; _contextProvider.AfterSaveEntitiesDelegate = afterSaveEntities; return _contextProvider.SaveChanges(saveBundle); } protected Dictionary<Type, List<EntityInfo>> BeforeSaveEntities(Dictionary<Type, List<EntityInfo>> saveMap) { } private void afterSaveEntities(Dictionary<Type, List<EntityInfo>> saveMap, List<KeyMapping> keyMappings) { } }
manger.saveChanges().then(function success() { }, function failer(e) { });
manger.rejectChanges()
کوئری:بعد از تعریف Entity Manger میتوانیم کوئری خود را اجرا نماییم. کوئری ما شامل گرفتن اطلاعات از جدول Customer، با مرتب سازی بر روی فیلد آیدی میباشد و با اجرا کردن کوئری میتوانیم موفقیت یا عدم موفقیت آنرا بررسی نماییم.
var query = breeze.EntityQuery .from("Customer") .orderBy("Id"); var result= manager.executeQuery(query); result.then(querySucceeded) .fail(queryFailed); query = query.where("Id", "==", 1)
var predicate = new breeze.Predicate("Id", "==", false); query = query.where(predicate) var p1 = new breeze.Predicate("IsArchived", "==", false); var p2 = breeze.Predicate("IsDone", "==", false); var predicate = p1.and(p2); query = query.where(predicate).orderBy("Id")
?$filter=IsArchived eq false&IsDone eq false &$orderby=Id
اعتبارسنجی :اعتبارسنجی در breeze، هم در سمت کلاینت و هم در سمت سرور امکان پذیر میباشد که در مثالی، در قسمت بعدی، validator سفارشی خودمان را خواهیم ساخت و به entity مورد نظر اعمال خواهیم کرد.
breeze.Validator.required({ allowEmptyStrings: true });
ردیابی تغییرات: هر آیتم Entity دارای EntityAspect است که وضعیت آنرا مشخص میکند و میتواند یکی از وضعیتهای Added،Modified،Deleted،Detached،Unchanged باشد. با مشخص کردن حالت هر آیتم، با فراخوانی SaveChanges تغییرات بر روی دیتابیس اعمال میگردد.
manager.createEntity('Customer', jsonValue);
manager.createEntity("Customer", jsonValue, breeze.EntityState.Modified, breeze.MergeStrategy.OverwriteChanges)
manager.createEntity("Customer", item, breeze.EntityState.Deleted)
برای اشنایی بیشتر با امکانات Breeze، قصد داریم یک سایت ایجاد آگهی را راه اندازی کنیم. پیش نیازهای ضروری این بخش typescript ،angularjs ،requirejs هستند. قصد داریم سایتی را برای آگهیهای خرید و فروش خودرو، مشابه با سایت باما ایجاد نماییم:
امکانات این سایت:
Install-Package angularjs Install-Package angularjs.TypeScript.DefinitelyTyped Install-Package bootstrap Install-Package bootstrap.TypeScript.DefinitelyTyped Install-Package jQuery Install-Package jquery.TypeScript.DefinitelyTyped Install-Package RequireJS Install-Package requirejs.TypeScript.DefinitelyTyped bower install angularAMD
مدلهای برنامه:
public class BaseEntity { public int Id { get; set; } public bool Status { get; set; } public DateTime CreatedDateTime { get; set; } }
public class Ad : BaseEntity { public string Title { get; set; } public float Price { get; set; } public double Rating { get; set; } public int? RatingNumber { get; set; } public string UserId { get; set; } public DateTime ModifieDateTime { get; set; } public string Description { get; set; } public virtual ICollection<Comment> Comments { get; set; } public virtual IdentityUser User { get; set; } public virtual ICollection<AdLabel> Labels { get; set; } public virtual ICollection<AdMedia> Medias { get; set; } }
public class Label { public int Id { get; set; } public string Title { get; set; } public int? ParentId { get; set; } public virtual Label Parent { get; set; } public virtual ICollection<Label> Items { get; set; } }
public class Media { public int Id { get; set; } public string Name { get; set; } public string MimeType { get; set; } }
public class AdLabel { public int Id { get; set; } public virtual Ad Ad { get; set; } public virtual Label Label { get; set; } [Index("IX_AdLabel", 1, IsUnique = true)] public int AdId { get; set; } [Index("IX_AdLabel", 2, IsUnique = true)] public int LabelId { get; set; } public string Value { get; set; } }
public class AdMedia { public int Id { get; set; } public virtual Ad Ad { get; set; } public virtual Media Media { get; set; } [Index("IX_AdMedia", 1, IsUnique = true)] public int AdId { get; set; } [Index("IX_AdMedia", 2, IsUnique = true)] public int MediaId { get; set; } }
public class Comment : BaseEntity { public string Body { get; set; } public double Rating { get; set; } public int? RatingNumber { get; set; } public string EntityName { get; set; } public string UserId { get; set; } public int? ParentId { get; set; } public int? AdId { get; set; } public virtual Comment Parent { get; set; } public virtual Ad Ad { get; set; } public virtual ICollection<Comment> Items { get; set; } public virtual IdentityUser User { get; set; } }
public class Customer:BaseEntity { public string UserId { get; set; } public virtual string DisplayName { get; set; } public virtual string BirthDay { get; set; } public string City { get; set; } public string Address { get; set; } public int? MediaId { get; set; } public bool? NewsLetterSubscription { get; set; } public string PhoneNumber { get; set; } public virtual IdentityUser User { get; set; } public virtual Media Media { get; set; } }
public class Rating { public int Id { get; set; } public string UserId { get; set; } public Double Rate { get; set; } public string EntityName { get; set; } public int DestinationId { get; set; } }
اضافه کردن مدلهای برنامه به ApplicationDbContext
public class ApplicationDbContext : IdentityDbContext<ApplicationUser> { public ApplicationDbContext() : base("DefaultConnection", throwIfV1Schema: false) { } public DbSet<Ad> Ads { get; set; } public DbSet<AdLabel> AdLabels { get; set; } public DbSet<AdMedia> AdMedias { get; set; } public DbSet<Comment> Comments { get; set; } public DbSet<Label> Labels { get; set; } public DbSet<Media> Medias { get; set; } public static ApplicationDbContext Create() { return new ApplicationDbContext(); } }
لود کردن فایل main.js در فایل layout.cshtml ترجیحا در انتهای body
<script src="~/Scripts/require.js" data-main="/app/main"></script>
ایجاد فایل main.ts
requirejs.config({ paths: { "app": "app", "angularAmd":"/Scripts/angularAmd", "angular": "/Scripts/angular", "bootstrap": "/Scripts/bootstrap", "angularRoute": "/Scripts/angular-route", "jquery": "/Scripts/jquery-2.2.2", }, waitSeconds: 0, shim: { "angular": { exports: "angular" }, "angularRoute": { deps: ["angular"] }, "bootstrap": { deps: ["jquery"] }, "app": { deps: ["bootstrap","angularRoute"] } } }); require(["app"]);
ایجاد فایل app.ts: کارهایی که در فایل app انجام دادهایم:
<body ng-controller="SecurityCtrl"> ... </body>
"use strict"; module AdApps { class SecurityCtrl { private $scope: Interfaces.IAdvertismentScope; constructor($scope: Interfaces.IAdvertismentScope) { // security check this.$scope = $scope; } } define(["angularAmd", "angular"], (angularAmd, ng) => { angularAmd = angularAmd.__proto__; var app = ng.module("AngularTypeScript", ['ngRoute']); var viewPath = "app/views/"; var controllerPath = "app/controller/"; app.config(['$routeProvider', $routeProvider => { $routeProvider .when("/", angularAmd.route({ templateUrl: viewPath + "home.html", controllerUrl: controllerPath + "home .js" })) .otherwise({ redirectTo: '/' }); } ]); app.controller('SecurityCtrl', ['$scope', SecurityCtrl]); return angularAmd.bootstrap(app); })}
Count یا Any
با وجود امکانات مهیای توسط LINQ ، یک سری از عادات متداول حین کار با گروهی از اشیاء باید کنار گذاشته شوند؛ برای مثال چگونگی بررسی این مطلب که آیا شیء IEnumerable ما حاوی عنصری هست یا خیر.
روش متداول انجام اینکار استفاده از متد Count است. چون این متد پیش از تدارک امکانات LINQ نیز وجود داشته، بنابراین اولین موردی که جهت بررسی آن به ذهن خطور میکند، استفاده از متد Count میباشد؛ برای مثال:
void Method(IEnumerable<Status> statuses)
{
if (statuses != null && statuses.Count() > 0)
// do something...
}
ولی در اینجا هدف ما این است که آیا شیء IEnumerable دارای حداقل یک عنصر است یا خیر؟ بنابراین بجای استفاده از متد Count بهتر است از یکی از extension methods فراهم شده توسط LINQ به نام Any استفاده شود.
کار متد Any ، پس از بررسی اولین عنصر یک مجموعه، خاتمه خواهد یافت و بدیهی است که نسبت به متد Count بسیار سریعتر و کم هزینهتر خواهد بود. علاوه بر آن حین کار با بانکهای اطلاعاتی برای مثال توسط LINQ to Entities ، در SQL نهایی تولیدی به EXISTS ترجمه خواهد شد.
void Method(IEnumerable<Status> statuses)
{
if (statuses != null && statuses.Any())
// do something...
}
از این پس حین استفاده از انواع و اقسام لیستها، آرایهها، IEnumerable ها و امثال آنها، جهت بررسی خالی بودن یا نبودن آنها تنها از متد Any فراهم شده توسط LINQ استفاده نمائید.
if (myArray != null && myArray.Any())
// do something...
پیاده سازی ویژگی Health Check بدون استفاده از قابلیتهای ASP.NET Core 2.2
اگر بخواهیم در بررسی سلامت برنامه، وضعیت بانک اطلاعاتی آنرا گزارش دهیم، میتوان یک چنین اکشن متدی را طراحی کرد که در آن اتصالی به بانک اطلاعاتی باز شده و اگر در حین فراخوانی مسیر working/، استثنائی رخ داد، با بازگشت status code مساوی 503، عدم سلامت برنامه اعلام شود؛ کاری که سرویسهای ping متداول نمیتوانند آنرا با این دقت انجام دهند:
[Route("working")] public ActionResult Working() { using (var connection = new SqlConnection(_connectionString)) { try { connection.Open(); } catch (SqlException) { return new HttpStatusCodeResult(503, "Generic error"); } } return new EmptyResult(); }
بازنویسی قطعه کد فوق با ویژگی جدید Health Check در ASP.NET Core 2.2
اکنون اگر بخواهیم قطعه کد فوق را با کمک ویژگیهای جدید ASP.NET Core 2.2 بازنویسی کنیم، روش کار به صورت زیر خواهد بود:
namespace MvcHealthCheckTest { public class Startup { public void ConfigureServices(IServiceCollection services) { services.AddHealthChecks() .AddCheck("sql", () => { using (var connection = new SqlConnection(Configuration["connectionString"])) { try { connection.Open(); } catch (SqlException) { return HealthCheckResult.Unhealthy(); } } return HealthCheckResult.Healthy(); }); } public void Configure(IApplicationBuilder app, IHostingEnvironment env) { app.UseHealthChecks("/working");
- سپس توسط متد app.UseHealthChecks، بدون اینکه نیاز باشد کنترلر و اکشن متد جدیدی را جهت بازگشت وضعیت سلامت برنامه، تعریف کنیم، مسیر working/ قابل دسترسی خواهد شد.
تا اینجا اگر این مسیر را به سرویس بررسی uptime برنامهی خود معرفی کنید، صرفا وضعیت قابل دسترسی بودن مسیر working/ را دریافت خواهید کرد. اگر نیاز به گزارش دقیقتری وجود داشت، میتوان به کمک متد AddCheck، یک منطق سفارشی را نیز به آن افزود؛ همانند بررسی امکان اتصال به بانک اطلاعاتی، به روشی که ملاحظه میکنید. در اینجا اگر منطق مدنظر با موفقیت اجرا شد، HealthCheckResult.Healthy بازگشت داده میشود و یا HealthCheckResult.Unhealthy در صورت عدم موفقیت. هر کدام از این متدها میتوانند توضیحات و یا اطلاعات بیشتری را نیز توسط پارامترهای خود ارائه دهند.
امکان تهیه سرویسهای سفارشی بررسی سلامت برنامه
در مثال قبل، منطق بررسی سلامت برنامه را همانجا داخل متد ConfigureServices، به کمک متد services.AddHealthChecks().AddCheck معرفی کردیم. امکان انتقال این کدها به سرویسهای سفارشی، با پیاده سازی اینترفیس IHealthCheck نیز وجود دارد:
public class SqlServerHealthCheck : IHealthCheck { private readonly IConfiguration _configuration; public SqlServerHealthCheck(IConfiguration configuration) { _configuration = configuration; } public Task<HealthCheckResult> CheckHealthAsync( HealthCheckContext context, CancellationToken cancellationToken = default(CancellationToken)) { using (var connection = new SqlConnection(_configuration["connectionString"])) { try { connection.Open(); } catch (SqlException) { return Task.FromResult(HealthCheckResult.Unhealthy()); } } return Task.FromResult(HealthCheckResult.Healthy()); } }
namespace MvcHealthCheckTest { public class Startup { public void ConfigureServices(IServiceCollection services) { services.AddHealthChecks() .AddCheck<SqlServerHealthCheck>("sql");
سفارشی سازی خروجی بررسی سلامت برنامهها
تا اینجا از متدهای کلی Unhealthy و Healthy برای بازگشت وضعیت سلامت برنامه استفاده کردیم؛ خروجیهای بهتری را نیز میتوان ارائه داد:
public Task<HealthCheckResult> CheckHealthAsync( HealthCheckContext context, CancellationToken cancellationToken = default(CancellationToken)) { using (var connection = new SqlConnection(_configuration["connectionString"])) { try { connection.Open(); } catch (SqlException) { return Task.FromResult(new HealthCheckResult( status: context.Registration.FailureStatus, description: "It is dead!")); } } return Task.FromResult(HealthCheckResult.Healthy("Healthy as a horse")); }
روش دیگر سفارشی سازی خروجی آن، استفاده از پارامتر دوم متد app.UseHealthChecks است:
namespace MvcHealthCheckTest { public class Startup { public void Configure(IApplicationBuilder app, IHostingEnvironment env) { app.UseHealthChecks("/working", new HealthCheckOptions { ResponseWriter = async (context, report) => { var result = JsonConvert.SerializeObject(new { status = report.Status.ToString(), errors = report.Entries.Select(e => new { key = e.Key, value = Enum.GetName(typeof(HealthStatus), e.Value.Status) }) }); context.Response.ContentType = MediaTypeNames.Application.Json; await context.Response.WriteAsync(result); } });
معرفی کتابخانهای از IHealthCheckهای سفارشی
از مخزن کد AspNetCore.Diagnostics.HealthChecks میتوانید IHealthCheckهای سفارشی مخصوص SQL Server، MySQL و غیره را نیز دریافت و استفاده کنید.