تست میزان امنیت پسوردها
EF Code First #10
EF Code First #10
فراخوانی Web API از طریق مرورگر
با فشردن کلید F5، پروژه را اجرا کنید. شکل ذیل ظاهر میشود.
صفحه ای که ظاهر میشود، یک View است که توسط HomeController و متد Index آن برگشت داده شده است. برای فراخوانی متدهای موجود در کلاس Controller مثال قسمت قبل که مربوط به Web API است، باید به یکی از آدرسهای اشاره شده در قسمت قبل برویم. به عنوان مثال، برای به دست آوردن لیست تمامی محصولات، به آدرس http://localhost:xxxx/api/products بروید. xxxx، شمارهی پورتی است که Web Server داخلی Visual Studio در هنگام اجرای پروژه به آن اختصاص میدهد. آن را نسبت به پروژهی خود تغییر دهید.
نتیجهی دریافتی بستگی به نوع مرورگری دارد که استفاده میکنید. Internet Explorer از شما در مورد باز کردن یا ذخیرهی فایلی با نام products پرسش میکند (شکل ذیل).
محتوای فایل، بدنهی پاسخ دریافتی است. اگر این فایل را باز کنید، خواهید دید که که محتوای آن، لیستی از محصولات با فرمت JSON مانند ذیل است.
[{"Id":1,"Name":"Tomato soup","Category":"Groceries","Price":1.39},{"Id":2,"Name": "Yo-yo","Category":"Toys","Price":3.75},{"Id":3,"Name":"Hammer","Category": "Hardware","Price":16.99}]
دلیل تفاوت در نتیجهی دریافتی این است که مرورگر Internet Explorer و Firefox، هر یک مقدار متفاوتی را در هدر Accept درخواست، ارسال میکنند. بنابراین، Web API نیز مقدار متفاوتی را در پاسخ برگشت میدهد.
حال به آدرسهای ذیل بروید:
http://localhost:xxxx/api/products/1
http://localhost:xxxx/api/products?category=hardware
اولین آدرس، باید محصولی با مشخصهی 1 را برگشت دهد و دومین آدرس، لیستی از تمامی محصولاتی که در دستهی hardware قرار دارند را برگشت میدهد (در مثال ما فقط یک آیتم این شرط را دارد).
نکته: در صورتی که در هنگام فراخوانی هر یک از متدهای Web API با خطای ذیل مواجه شدید، دستور [("AcceptVerbs("GET", "POST] را به ابتدای متدها اضافه کنید.
The requested resource does not support http method 'GET'
فراخوانی Web API با استفاده از کتابخانهی jQuery
در قسمت قبل، متدهای Web API را مستقیماً از طریق وارد کردن آدرس آنها در نوار آدرس مرورگر فراخوانی کردیم. اما در اکثر اوقات، این متدها با روشهای برنامه نویسی توسط یک Client فراخوانی میشوند. اجازه بدهید Clientیی ایجاد کنیم که با استفاده از jQuery، متدهای ما را فراخوانی میکند.
در Solution Explorer، از پوشهی Views و سپس Home، فایل Index.cshtml را باز کنید.
تمامی محتویات این View را حذف و کدهای ذیل را در آن قرار دهید.
<!DOCTYPE html> <html> <head> <title>ASP.NET Web API</title> <script src="../../Scripts/jquery-1.7.2.min.js" type="text/javascript"></script> </head> <body> <div> <h1>All Products</h1> <ul id='products' /> </div> <div> <label for="prodId">ID:</label> <input type="text" id="prodId" size="5"/> <input type="button" value="Search" onclick="find();" /> <p id="product" /> </div> </body> </html>
بازیابی لیستی از محصولات
برای بازیابی لیستی از محصولات، فقط کافی است تا یک درخواست از نوع GET به آدرس "/api/products" بفرستید. این کار با jQuery به صورت ذیل انجام میشود.
<script type="text/javascript"> $(document).ready(function () { // Send an AJAX request $.getJSON("api/products/", function (data) { // On success, 'data' contains a list of products. $.each(data, function (key, val) { // Format the text to display. var str = val.Name + ': $' + val.Price; // Add a list item for the product. $('<li/>', { html: str }) .appendTo($('#products')); }); }); }); </script>
متد getJSON، یک درخواست AJAX از نوع GET را ارسال میکند و پاسخ دریافتی آن نیز با فرمت JSON خواهد بود. دومین پارامتر متد getJSON، یک callback است که پس از دریافت موفقیت آمیز پاسخ اجرا میشود.
بازیابی یک محصول با استفاده از مشخصهی آن
برای بازیابی یک محصول با استفاده از مشخصهی آن، یک درخواست از نوع GET به آدرس "api/products/id/" ارسال کنید. id، مشخصهی محصول است. کد ذیل را در ادامهی کد قبل و پیش از تگ <script/> قرار دهید.
function find() { var id = $('#prodId').val(); $.getJSON("api/products/" + id, function (data) { var str = data.Name + ': $' + data.Price; $('#product').html(str); }) .fail( function (jqXHR, textStatus, err) { $('#product').html('Error: ' + err); }); }
باز هم از متد getJSON استفاده کردیم، اما این بار مقدار id برای آدرس از یک Text Box خوانده و آدرس ایجاد میشود. پاسخ دریافتی، یک محصول در قالب JSON است.
اجرای پروژه
پروژه را با فشردن کلید F5 اجرا کنید. پس از نمایش فرم، تمامی محصولات بر روی صفحه نمایش داده میشوند. عدد 1 را وارد و بر روی دکمهی Search کلیک کنید، محصولی که مشخصهی آن 1 است نمایش داده میشود (شکل ذیل).
اگر مشخصه ای را وارد کنید که وجود ندارد، خطای 404 با مضمون "Error: Not Found" بر روی صفحه نمایش داده میشود و در صورتی که به جای عدد، عبارتی غیر عددی وارد کنید، خطای 400 با مضمون: "Error: Bad Request" نمایش داده میشود. در Web API، تمامی پاسخها باید در قالب کدهای وضعیت HTTP باشند (شکل ذیل). این یکی از اصول اساسی کار با وب سرویسها است. وفادار ماندن به مفاهیم پایهی وب، دید بهتری در مورد اتفاقاتی که میافتد به شما میدهد.
در قسمت بعد با مفهوم مسیریابی در ASP.NET Web API آشنا میشوید.
using System; namespace TestUsing { public class MyResource : IDisposable { public void DoWork() { throw new ArgumentException("A"); } public void Dispose() { throw new ArgumentException("B"); } } public static class TestClass { public static void Test() { using (MyResource r = new MyResource()) { throw new ArgumentException("C"); r.DoWork(); } } } }
try { TestClass.Test(); } catch (Exception ex) { Console.WriteLine(ex.Message); }
پاسخ: برخلاف تصور (که احتمالا C است؛ چون قبل از فراخوانی متد DoWork سبب بروز استثناء شده است)، فقط B را در خروجی مشاهده خواهیم کرد!
و این دقیقا مشکلی است که در حین کار با کتابخانه iTextSharp برای اولین بار با آن مواجه شدم. روش استفاده متداول از iTextSharp به نحو زیر است:
using (var pdfDoc = new Document(PageSize.A4)) { //todo: ... }
و خلاصه نتایج هم این است:
اگر به ثبت جزئیات خطاهای سیستم اهمیت میدهید (یکی از مهمترین مزیتهای دات نت نسبت به بسیاری از فریم ورکهای مشابه که حداکثر خطای 0xABC12EF را نمایش میدهند)، از using استفاده نکنید! using در پشت صحنه به try/finally ترجمه میشود و بهتر است این مورد را دستی نوشت تا اینکه کامپایلر اینکار را به صورت خودکار انجام دهد.
در اینجا باز هم به یک سری کد تکراری try/finally خواهیم رسید و همانطور که در مباحث کاربردهای Action و Func در این سایت ذکر شد، میتوان آنرا تبدیل به کدهایی با قابلیت استفاده مجدد کرد. یک نمونه از پیاده سازی آنرا در این سایت «C# Using Blocks can Swallow Exceptions » میتوانید مشاهده کنید که خلاصه آن کلاس زیر است:
using System; namespace Guard { public static class SafeUsing { public static void SafeUsingBlock<TDisposable>(this TDisposable disposable, Action<TDisposable> action) where TDisposable : IDisposable { disposable.SafeUsingBlock(action, d => d); } internal static void SafeUsingBlock<TDisposable, T>(this TDisposable disposable, Action<T> action, Func<TDisposable, T> unwrapper) where TDisposable : IDisposable { try { action(unwrapper(disposable)); } catch (Exception actionException) { try { disposable.Dispose(); } catch (Exception disposeException) { throw new AggregateException(actionException, disposeException); } throw; } disposable.Dispose(); } } }
new Document(PageSize.A4).SafeUsingBlock(pdfDoc => { //todo: ... });
current=1&rowCount=10&sort[sender]=asc&searchPhrase=&id=b0df282a-0d67-40e5-8558-c9e93b7befed
[AttributeUsage(AttributeTargets.Property,Inherited = true)] public class RequestBodyField:Attribute { public string Field; public RequestBodyField(string field) { this.Field = field; } }
public class EmployeesRequestBody { [RequestBodyField("current")] public int CurrentPage { get; set; } [RequestBodyField("rowcount")] public int RowCount { get; set; } [RequestBodyField("searchPhrase")] public string SearchPhrase { get; set; } [RequestBodyField("sort")] public NameValueCollection SortDictionary { get; set; } }
public T GetFromQueryString<T>() where T : new() { var obj = new T(); var queryString = HttpContext.Current.Request.QueryString; var queries = HttpUtility.ParseQueryString(queryString.ToString()); var properties = typeof(T).GetProperties(); foreach (var property in properties) { foreach (Attribute attribute in property.GetCustomAttributes(true)) { var requestBodyField = attribute as RequestBodyField; if (requestBodyField == null) continue; //get value of query string var valueAsString = queries[requestBodyField.Field]; var converter = TypeDescriptor.GetConverter(property.PropertyType); var value = converter.ConvertFrom(valueAsString); if (value == null) continue; property.SetValue(obj, value, null); } } return obj; }
HttpContext.Current.Request.QueryString
سپس در مرحلهی بعدی با استفاده از Reflection پراپرتیهایی را که دارای attribute تعریف شده هستند، پیدا میکنیم.
var converter = TypeDescriptor.GetConverter(property.PropertyType); var value = converter.ConvertFrom(valueAsString);
سپس در صورتی که مقدار صحیح دریافت شود و برابر null نباشد، مقدار را در پراپرتی مربوطه جا میدهیم.
نکتهای که در اینجا نیاز به تلاش بیشتر دارد، کلید sort در کوئری استرینگ است. با نگاهی دقیقتر متوجه میشوید که خود کلید دو مقدار دارد که یکی از مقادیرش با کلید ترکیب شده است. این حالت روش ارسال آرایهها با نام کلیدی متفاوت در کوئری استرینگ است. این حالت ارسال باعث میشود که گرید بتواند حالت multi sort را نیز پیاده سازی کند.
پس برای دریافت این نوع مقادیر کمی کد به آن اضافه میکنیم. برای دریافت مقادیر آرایهای کد زیر را به سیستم اضافه میکنیم:
if (valueAsString == null) { var keys = from key in queries.AllKeys where key.StartsWith(requestBodyField.Field) select key; var collection = new NameValueCollection(); foreach (var key in keys) { var openBraketIndex = key.IndexOf("[", StringComparison.Ordinal); var closeBraketIndex = key.IndexOf("]", StringComparison.Ordinal); if (openBraketIndex < 0 || closeBraketIndex < 0) throw new Exception("query string is corrupted."); openBraketIndex++; //get key in [...] var fieldName = key.Substring(openBraketIndex, closeBraketIndex - openBraketIndex); collection.Add(fieldName, queries[key] ); } property.SetValue(obj, collection, null); continue; }
public T GetFromQueryString<T>() where T : new() { var obj = new T(); var properties = typeof(T).GetProperties(); var queryString = HttpContext.Current.Request.QueryString; var queries = HttpUtility.ParseQueryString(queryString.ToString()); foreach (var property in properties) { foreach (Attribute attribute in property.GetCustomAttributes(true)) { var requestBodyField = attribute as RequestBodyField; if (requestBodyField == null) continue; //get value of query string var valueAsString = queries[requestBodyField.Field]; if (valueAsString == null) { var keys = from key in queries.AllKeys where key.StartsWith(requestBodyField.Field) select key; var collection = new NameValueCollection(); foreach (var key in keys) { var openBraketIndex = key.IndexOf("[", StringComparison.Ordinal); var closeBraketIndex = key.IndexOf("]", StringComparison.Ordinal); if (openBraketIndex < 0 || closeBraketIndex < 0) throw new Exception("query string is corrupted."); openBraketIndex++; //get key in [...] var fieldName = key.Substring(openBraketIndex, closeBraketIndex - openBraketIndex); collection.Add(fieldName, queries[key]); } property.SetValue(obj, collection, null); continue; } var converter = TypeDescriptor.GetConverter(property.PropertyType); var value = converter.ConvertFrom(valueAsString); if (value == null) continue; property.SetValue(obj, value, null); } } return obj; }
حال به صورت زیر این متد را صدا میزنیم:
public virtual ActionResult GetEmployees() { var request = new Requests().GetFromQueryString<EmployeesRequestBody>(); }
بعنوان مدیر تیم نرم افزاری با تعداد متوسط (6-7 نفر) با متدولوژی اسکرام، کدام روش را بیشتر می پسندید و استفاده می کنید؟
برای بررسی ویژگیهای جاوا اسکریپت مدرن، یک پروژهی جدید React را ایجاد میکنیم.
> create-react-app sample-02 > cd sample-02 > npm start
به علاوه چون در این قسمت خروجی UI نخواهیم داشت، تمام خروجی را در کنسول developer tools مرورگر خود میتوانید مشاهده کنید (فشردن دکمهی F12).
var، let و const
در اکثر زبانهای برنامه نویسی، متغیرها در محدودهی دید قطعه کدی که تعریف شدهاند (scope)، قابل دسترسی هستند. برای نمونه محتوای فایل index.js پروژه را به صورت زیر تغییر داده و با فرض اجرای دستور npm start، خروجی آنرا میتوان در کنسول مرورگر مشاهده کرد.
function sayHello() { for (var i = 0; i < 5; i++) { console.log(i); } console.log(i); } sayHello();
در آخرین پیمایش حلقه، i مساوی 5 شده و از حلقه خارج میشود. اما چون در اینجا برای تعریف متغیر از واژهی کلیدی var استفاده شدهاست، محدودهی دید آن به کل تابعی که در آن تعریف شدهاست، بسط پیدا میکند. به همین جهت در این خروجی، عدد 5 را نیز مشاهده میکند که حاصل دسترسی به i، خارج از حلقهاست.
برای یک دست سازی این رفتار با سایر زبانهای برنامه نویسی، در ES6، واژهی کلیدی جدیدی به نام let تعریف شدهاست که میدان دید متغیر را به قطعه کدی که در آن تعریف شدهاست، محدود میکند. اکنون اگر در حلقهی فوق بجای var از let استفاده شود، یک چنین خطایی در مرورگر ظاهر خواهد شد که عنوان میکند، i استفاده شدهی در خارج از حلقه، تعریف نشدهاست.
./src/index.js Line 14:15: 'i' is not defined no-undef Search for the keywords to learn more about each error.
علاوه بر let، واژهی کلیدی جدید const نیز به ES6 اضافه شدهاست که از آن برای تعریف ثوابت استفاده میشود. constها نیز همانند let، میدان دید محدود شدهای به قطعه کد تعریف شدهی در آن دارند؛ اما قابلیت انتساب مجدد را ندارند:
const x = 1; x = 2; // Attempting to override 'x' which is a constant.
به صورت خلاصه از این پس واژهی کلیدی var را فراموش کنید. همیشه با const جهت تعریف متغیرها شروع کنید. اگر به خطا برخوردید و نیاز به انتساب مجدد وجود داشت، آنرا به let تغییر دهید. بنابراین استفاده از const همیشه نسبت به let ارجحیت دارد.
اشیاء در جاوا اسکریپت
اشیاء در جاوا اسکریپت به صورت مجموعهای از key/valueها تعریف میشوند:
const person = { name: "User 1", walk: function() {}, // method talk() {} // concise method };
const person = { name: "User 1", walk() {}, talk() {} };
person.talk(); person.name = "User 3"; person["name"] = "User 2";
مورد آخر همان روش استفاده از key/valueها است که اساس اشیاء جاوا اسکریپتی را تشکیل میدهد. البته از این روش فقط زمانی استفاده کنید که قرار است یکسری کار پویا صورت گیرند (مقدار key به صورت متغیر دریافت شود) و از ابتدا مشخص نیست که کدام خاصیت یا متد قرار است تعریف و استفاده شود:
const targetMember = "name"; person[targetMember] = "User 2";
واژهی کلیدی this در جاوا اسکریپت
از واژهی کلیدی this، در قسمتهای بعدی زیاد استفاده خواهیم کرد. به همین جهت نیاز است تفاوتهای اساسی آنرا با سایر زبانهای برنامه نویسی بررسی کنیم.
همان شیء person را که پیشتر تعریف کردیم درنظر بگیرید. در متد walk آن، مقدار this را لاگ میکنیم:
const person = { name: "User 1", walk() { console.log(this); }, talk() {} }; person.walk();
شیء this در جاوا اسکریپت، همانند سایر زبانهای برنامه نویسی مانند سیشارپ و یا جاوا رفتار نمیکند. در سایر زبانهای نامبرده شده، this همواره ارجاعی را به وهلهای از شیء جاری، باز میگرداند؛ دقیقا همانند تصویری که در بالا مشاهده میکنید. در اینجا نیز this جاوا اسکریپتی لاگ شده، ارجاعی را به وهلهی جاری شیء person، بازگشت دادهاست. اما مشکل اینجا است که this در جاوا اسکریپت، همیشه به این صورت رفتار نمیکند!
برای نمونه در ادامه یک ثابت را به نام walk تعریف کرده و آنرا به person.walk مقدار دهی میکنیم:
const walk = person.walk; console.log(walk);
سؤال: اکنون اگر این function را با فراخوانی ()walk اجرا کنیم، چه خروجی را میتوان مشاهده کرد؟
اینبار this لاگ شده، به شیء person اشاره نمیکند و شیء استاندارد window مرورگر را بازگشت دادهاست!
اگر یک function به صورت متدی از یک شیء فراخوانی شود، مقدار this همواره اشارهگری به وهلهای از آن شیء خواهد بود. اما اگر این تابع به صورت متکی به خود و به صورت یک function و نه متد یک شیء، فراخوانی شود، اینبار this، شیء سراسری جاوا اسکریپت یا همان شیء window را بازگشت میدهد.
یک نکته: اگر strict mode جاوا اسکریپت را در پروژهی جاری فعال کنیم، بجای شیء window، مقدار undefined را در خروجی فوق شاهد خواهیم بود.
اتصال مجدد this به شیء اصلی در جاوا اسکریپت
تا اینجا دریافتیم که اگر یک function را به صورت متکی به خود و نه جزئی از یک شیء فراخوانی کنیم، شیء this در این حالت به شیء window سراسری مرورگر اشاره میکند و اگر strict mode فعال باشد، فقط undefined را بازگشت میهد. اکنون میخواهیم بررسی کنیم که چگونه میتوان این مشکل را برطرف کرد؛ یعنی صرفنظر از نحوهی فراخوانی متدها یا تابعها، this همواره ارجاعی را به شیء person بازگشت دهد.
در جاوا اسکریپت، تابعها نیز شیء هستند. برای مثال person.walk نوشته شده نیز یک شیء است. برای اثبات سادهی آن فقط یک دات را پس از person.walk قرار دهید:
همانطور که مشاهده میکنید، شیء person.walk مانند تمام اشیاء دیگر جاوا اسکریپت، به همراه متد bind نیز هست. کار آن، انقیاد یک تابع، به یک شیء است. یعنی هرچیزی را که به عنوان آرگومان آن، به آن ارسال کنیم، به عنوان مقدار شیء this درنظر میگیرد:
const walk2 = person.walk.bind(person); console.log(walk2); walk2();
Arrow functions
تابع زیر را درنظر بگیرید که به یک ثابت انتساب داده شدهاست:
const square = function(number) { return number * number; };
const square2 = (number) => { return number * number; };
const square2 = number => { return number * number; };
در ادامه اگر بدنهی این تابع، فقط حاوی یک return بود، میتوان آنرا به صورت زیر نیز خلاصه کرد (در اینجا {} به همراه واژهی کلیدی return حذف میشوند):
const square3 = number => number * number; console.log(square3(5));
اکنون مثال مفید دیگری را در مورد Arrow functions بررسی میکنیم که بیشتر شبیه به عبارات LINQ در #C است:
const jobs = [ { id: 1, isActive: true }, { id: 2, isActive: true }, { id: 3, isActive: true }, { id: 4, isActive: true }, { id: 5, isActive: false } ];
var activeJobs = jobs.filter(function(job) { return job.isActive; });
در ادامه میتوان این تابع را توسط arrow functions به صورت سادهتر زیر نیز نوشت:
var activeJobs2 = jobs.filter(job => job.isActive);
ارتباط بین arrow functions و شیء this
نکتهی مهمی را که باید در مورد arrow functions دانست این است که شیء this را rebind نمیکنند (rebind: مقدار دهی مجدد؛ ریست کردن).
در مثال زیر، ابتدا شیء user با متد talk که در آن شیء this، لاگ شده، ایجاد شده و سپس این متد فراخوانی گردیدهاست:
const user = { name: "User 1", talk() { console.log(this); } }; user.talk();
اکنون اگر متد لاگ کردن را داخل یک تایمر قرار دهیم چه اتفاقی رخ میدهد؟
const user = { name: "User 1", talk() { setTimeout(function() { console.log(this); }, 1000); } }; user.talk();
در این حالت خروجی console.log، مجددا همان شیء سراسری window مرورگر است و دیگر به وهلهی جاری شیء user اشاره نمیکند. علت اینجا است که پارامتر اول متد setTimeout که یک callback function نام دارد، جزئی از هیچ شیءای نیست. بنابراین دیگر مانند فراخوانی متد ()user.talk در مثال قبلی کار نمیکند؛ چون متکی به خود است. هر زمان که یک متد متکی به خود غیر وابستهی به یک شیء را اجرا کنیم، به صورت پیشفرض this آن، به شیء window مرورگر اشاره میکند.
سؤال: چگونه میتوان درون یک callback function متکی به خود، به this همان شیء user جاری دسترسی یافت؟
یک روش حل این مساله، ذخیره this شیء user در یک متغیر و سپس ارسال آن به متد متکی به خود setTimeout است:
const user2 = { name: "User 2", talk() { var self = this; setTimeout(function() { console.log(self); }, 1000); } }; user2.talk();
const user3 = { name: "User 3", talk() { setTimeout(() => console.log(this), 1000); } }; user3.talk();
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید: sample-02.zip
در قسمت بعد نیز بررسی پیشنیازهای جاوا اسکریپتی شروع به کار با React را ادامه خواهیم داد.