در قسمت قبلی به اصل وجودی CLR پرداختیم. در این قسمت تا حدودی به بررسی ماژول مدیریت شده managed module که از زبانهای دیگر، کامپایل شده و به زبان میانی تبدیل گشته است صحبت میکنیم.
یک ماژول مدیریت شده شامل بخشهای زیر است:
نام بخش | توضیح |
هدر PE32 یا PE32+ | CLR باید بداند که برنامهی نوشته شده قرار است روی چه پلتفرمی و
با چه معماری، اجرا گردد. این برنامه یک برنامهی 32 بیتی است یا 64
بیتی. همچنین این هدر اشاره میکند که نوع فایل از چه نوعی است؛ GUI,CUI یا
DLL. به علاوه تاریخ ایجاد یا کامپایل فایل هم در آن ذکر شده است. در صورتیکه
این فایل شامل کدهای بومی native CPU هم باشد، اطلاعاتی در مورد این نوع
کدها نیز در این هدر ذکر میشود و اگر ماژول ارائه شده تنها شامل کد IL باشد،
قسمت بزرگی از اطلاعات این هدر در نظر گرفته نمیشود. |
CLR Header | اطلاعاتی را در مورد CLR ارائه میکند. اینکه برای
اجرا به چه ورژنی از CLR نیاز دارد. منابع مورد استفاده. آدرس و اندازه جداول
و فایلهای متادیتا و جزئیات دیگر. |
metadata | هر کد یا ماژول مدیریت شدهای، شامل جداول متادیتا است که این جداول
بر دو نوع هستند. اول جداولی که نوعها و اعضای تعریف شده در کد را توصیف
میکنند و دومی جداولی که نوعها و اعضایی را که در کد به آن ارجاع شده است،
توصیف میکنند. |
IL Code | اینجا محل قرار گیری کدهای میانی تبدیل شده است که در زمان اجرا، CLR آنها را به کدهای بومی تبدیل میکند. |
کامپایلرهایی که بر اساس CLR کار میکنند، وظیفه دارند جداول متادیتاها را به طور کامل ساخته و داخل فایل نهایی embed کنند. متادیتاها مجموعهی کاملی از فناوریهای قدیمی چون فایلهای COM یا Component Object Model و همچنین IDL یا Interface Definition (Description) Language هستند. گفتیم که متادیتاها همیشه داخل فایل IL که ممکن است DLL باشد یا EXE، ترکیب یا Embed شدهاند و جدایی آنها غیر ممکن است. در واقع کامپایلر در یک زمان، هم کد IL و هم متادیتاها را تولید کرده و آنها را به صورت یک نتیجهی واحد در میآورد.
متادیتاها استفادههای زیادی دارند که در زیر به تعدادی از آنان اشاره میکنیم:
- موقع کامپایل نیاز به هدرهای C و ++C از بین میرود؛ چرا که فایل نهایی شامل تمامی اطلاعات ارجاع شده میباشد. کامپایلرها میتوانند مستقیما اطلاعات را از داخل متادیتاها بخوانند.
- ویژوال استودیو از آنها برای کدنویسی راحتتر بهره میگیرد. با استفاده از قابلیت IntelliSense، متادیتاها به شما خواهند گفت چه متدهایی، چه پراپرتیهایی، چه رویدادهایی و ... در دسترس شماست و هر متد انتظار چه پارامترهایی را از شما دارد.
- CLR Code Verification از متادیتا برای اینکه اطمینان کسب کند که کدها تنها عملیات type Safe را انجام میدهند، استفاده میکند.
- متادیتاها به فیلد یک شیء اجازه میدهند که خود را به داخل بلوکهای حافظ انتقال داده و بعد از ارسال به یک ماشین دیگر، همان شیء را با همان وضعیت، ایجاد نماید.
- متادیتاها به GC اجازه میدهند که طول عمر یک شیء را رصد کند. GC برای هر شیء موجود میتواند نوع هر شیء را تشخیص داده و از طریق متادیتاها میتواند تشخیص دهد که فیلدهای یک شیء به اشیاء دیگری هم متصل هستند.
در آینده بیشتر در مورد متادیتاها صحبت خواهیم کرد.
پیشنیازها
در اینجا فرض بر این است که موارد ذیل را نصب کردهاید:
- آخرین نگارش مرورگر Chrome
- افزونهی Debugger for Chrome که از آن برای دیباگ کدهای جاوا اسکریپتی و تایپاسکریپتی یکپارچهی با VSCode میتوان استفاده کرد.
تنظیمات VSCode جهت دیباگ برنامههای مبتنی بر Angular CLI
کدهای مطلب «فرمهای مبتنی بر قالبها در Angular - قسمت پنجم - ارسال اطلاعات به سرور» از انتهای بحث آن قابل دریافت هستند. این کدها را دریافت کرده و سپس پوشهی اصلی آنرا در VSCode باز کنید. اگر در ویندوز هستید با کلیک راست بر روی پوشه، گزینهی Open With Code ظاهر میشود و یا حتی میتوان از طریق خط فرمان به پوشهی اصلی برنامه مراجعه کرده و سپس دستور . code را صادر نمود.
در ادامه نیاز است دیباگر VSCode را تنظیم کنیم. اگر پیشتر هیچ تنظیمی را نداشته باشید، پس از مراجعهی به برگهی debug آن، بر روی دکمهی سبز رنگ Start Debugging آن کلیک کنید. در ادامه در منوی باز شده، گزینهی Chrome را انتخاب کنید:
اینکار سبب میشود تا فایل جدید vscode\launch.json. به پوشهی جاری اضافه شده و سپس تنظیمات دیباگر کروم در آن قرار گیرند.
حالت دوم شبیه به حالتی است که برنامهی مورد بحث جاری دارد: پیشتر دیباگری مانند NET Core. در آن تنظیم شدهاست. در این حالت، منوی drop down مربوط به دیباگرهای تنظیم شده را گشوده و سپس گزینهی آخر آن یعنی Add configuration را انتخاب کنید:
در اینجا نیز منوی Intellisense آن ظاهر شده و امکان انتخاب گزینهی Chrome را میدهد:
در نهایت هر دو حالت به ایجاد فایل جدید vscode\launch.json. و یا ویرایش آن منتهی میشوند. در اینجا تنها کاری را که باید انجام داد، تغییر پورت پیش فرض آن است:
- اگر از دستور ng serve استفاده میکنید، این پورت را به 4200 تغییر دهید (پورت پیش فرض این دستور است؛ برای تغییر آن، از پارامتر port-- در دستور ng serve استفاده کنید):
{ "version": "0.2.0", "configurations": [ { "type": "chrome", "request": "launch", "name": "Launch Chrome with ng serve", "url": "http://localhost:4200/#", "webRoot": "${workspaceRoot}" } ] }
- اگر از دستورات dotnet watch run و سپس ng build -watch استفاده میکنید (اولی وب سرور آزمایشی NET Core. را راه اندازی میکند و دومی کار ساخت پیوستهی برنامهی Angular را انجام میدهد)، این پورت را بر روی 5000 تنظیم کنید:
{ "version": "0.2.0", "configurations": [ { "type": "chrome", "request": "launch", "name": "Launch Chrome for dotnet build & ng build", "url": "http://localhost:5000/#", "webRoot": "${workspaceRoot}" } ] }
آزمایش یک break point و بررسی مقادیر دریافتی از سرور
تا اینجا کاری را که انجام دادیم، به افزودن یک قطعه تنظیم جدید به فایل vscode\launch.json. خلاصه میشود.
در ادامه به کامپوننت employee-register.component.ts مراجعه کرده و سطر this.languages = data را تبدیل به یک سطر مستقل درون {} میکنیم تا بتوانیم بر روی آن break point قرار دهیم:
ngOnInit() { this.formPoster.getLanguages().subscribe( data => { this.languages = data; }, err => console.log("get error: ", err) ); }
پس از آن از طریق خط فرمان به ریشهی پروژه وارد شده و دو پنجرهی کنسول مجزا را باز کنید. در اولی دستورات
>npm install >ng build --watch
>dotnet restore >dotnet watch run
سپس به برگهی دیباگ مراجعه کرده و گزینهی جدید Launch Chrome for dotnet build & ng build را انتخاب و سپس بر روی دکمهی سبز رنگ اجرای آن کلیک کنید:
اکنون اگر صفحهی مشاهدهی فرم را در مرورگر کروم باز شده درخواست کنیم، به این break point خواهیم رسید؛ اما ... حاوی اطلاعات data نیست. برای رفع این مشکل نیاز است تنظیمات پیشفرض را به صورت ذیل بهبود بخشید:
{ "version": "0.2.0", "configurations": [ { "type": "chrome", "request": "launch", "name": "Launch Chrome for dotnet build & ng build", "url": "http://localhost:5000", "runtimeArgs": [ "--user-data-dir", "--remote-debugging-port=9222", "--disable-session-crashed-bubble" ], "sourceMaps": true, "trace": true, "webRoot": "${workspaceRoot}/wwwroot/", "userDataDir": "${workspaceRoot}/.vscode/chrome" } ] }
- تنظیم sourceMaps و همچنین مشخص سازی محل دقیق پوشهی قرارگیری فایلهای نهایی ng build که همان پوشهی wwwroot است در webRoot سبب خواهند شد تا اینبار break point ما حاوی اطلاعات واقعی data دریافتی از سرور باشند (با نزدیک کردن اشارهگر ماوس به data، اطلاعات تکمیلی آن ظاهر میشود):
- اگر از دستور ng serve استفاده میکنید، در این تنظیمات پورت را به 4200 و محل پوشهی wwwroot را به dist تغییر دهید (مطابق تنظیمات فایل angular-cli.json.).
ASP.NET Web API فریم ورکی برای ساختن APIهای وب بر روی فریم ورک دات نت است. در این مقاله با استفاده از این فریم ورک، API وبی خواهیم ساخت که لیستی از محصولات را بر میگرداند. صفحه وب کلاینت، با استفاده از jQuery نتایج را نمایش خواهد داد.
یک پروژه Web API بسازید
در ویژوال استودیو 2013 پروژه جدیدی از نوع ASP.NET Web Application بسازید و نام آن را "ProductsApp" انتخاب کنید.
در دیالوگ New ASP.NET Project قالب Empty را انتخاب کنید و در قسمت "Add folders and core references for" گزینه Web API را انتخاب نمایید.
می توانید از قالب Web API هم استفاده کنید. این قالب با استفاده از ASP.NET MVC صفحات راهنمای API را خواهد ساخت. در این مقاله از قالب Empty استفاده میکنیم تا تمرکز اصلی، روی خود فریم ورک Web API باشد. بطور کلی برای استفاده از این فریم ورک لازم نیست با ASP.NET MVC آشنایی داشته باشید.
افزودن یک مدل
یک مدل (model) آبجکتی است که داده اپلیکیشن شما را معرفی میکند. ASP.NET Web API میتواند بصورت خودکار مدل شما را به JSON, XML و برخی فرمتهای دیگر مرتب (serialize) کند، و سپس داده مرتب شده را در بدنه پیام HTTP Response بنویسد. تا وقتی که یک کلاینت بتواند فرمت مرتب سازی دادهها را بخواند، میتواند آبجکت شما را deserialize کند. اکثر کلاینتها میتوانند XML یا JSON را تفسیر کنند. بعلاوه کلاینتها میتوانند فرمت مورد نظرشان را با تنظیم Accept header در پیام HTTP Request مشخص کنند.
بگذارید تا با ساختن مدلی ساده که یک محصول (product) را معرفی میکند شروع کنیم.
کلاس جدیدی در پوشه Models ایجاد کنید.
نام کلاس را به "Product" تغییر دهید، و خواص زیر را به آن اضافه کنید.
namespace ProductsApp.Models { public class Product { public int Id { get; set; } public string Name { get; set; } public string Category { get; set; } public decimal Price { get; set; } } }
افزودن یک کنترلر
در Web API کنترلرها آبجکت هایی هستند که درخواستهای HTTP را مدیریت کرده و آنها را به اکشن متدها نگاشت میکنند. ما کنترلری خواهیم ساخت که میتواند لیستی از محصولات، یا محصولی بخصوص را بر اساس شناسه برگرداند. اگر از ASP.NET MVC استفاده کرده اید، با کنترلرها آشنا هستید. کنترلرهای Web API مشابه کنترلرهای MVC هستند، با این تفاوت که بجای ارث بری از کلاس Controller از کلاس ApiController مشتق میشوند.
کنترلر جدیدی در پوشه Controllers ایجاد کنید.
در دیالوگ Add Scaffold گزینه Web API Controller - Empty را انتخاب کرده و روی Add کلیک کنید.
در دیالوگ Add Controller نام کنترلر را به "ProductsController" تغییر دهید و روی Add کلیک کنید.
توجه کنید که ملزم به ساختن کنترلرهای خود در پوشه Controllers نیستید، و این روش صرفا قراردادی برای مرتب نگاه داشتن ساختار پروژهها است. کنترلر ساخته شده را باز کنید و کد زیر را به آن اضافه نمایید.
using ProductsApp.Models; using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Web.Http; namespace ProductsApp.Controllers { public class ProductsController : ApiController { Product[] products = new Product[] { new Product { Id = 1, Name = "Tomato Soup", Category = "Groceries", Price = 1 }, new Product { Id = 2, Name = "Yo-yo", Category = "Toys", Price = 3.75M }, new Product { Id = 3, Name = "Hammer", Category = "Hardware", Price = 16.99M } }; public IEnumerable<Product> GetAllProducts() { return products; } public IHttpActionResult GetProduct(int id) { var product = products.FirstOrDefault((p) => p.Id == id); if (product == null) { return NotFound(); } return Ok(product); } }
کنترلر ما دو متد برای دریافت محصولات تعریف میکند:
- متد GetAllProducts لیست تمام محصولات را در قالب یک <IEnumerable<Product بر میگرداند.
- متد GetProductById سعی میکند محصولی را بر اساس شناسه تعیین شده پیدا کند.
همین! حالا یک Web API ساده دارید. هر یک از متدهای این کنترلر، به یک یا چند URI پاسخ میدهند:
URI | Controller Method |
api/products/ | GetAllProducts |
api/products/id/ | GetProductById |
برای اطلاعات بیشتر درباره نحوه نگاشت درخواستهای HTTP به اکشن متدها توسط Web API به این لینک مراجعه کنید.
فراخوانی Web API با جاوا اسکریپت و jQuery
در این قسمت یک صفحه HTML خواهیم ساخت که با استفاده از AJAX متدهای Web API را فراخوانی میکند. برای ارسال درخواستهای آژاکسی و بروز رسانی صفحه بمنظور نمایش نتایج دریافتی از jQuery استفاده میکنیم.
در پنجره Solution Explorer روی نام پروژه کلیک راست کرده و گزینه Add, New Item را انتخاب کنید.
در دیالوگ Add New Item قالب HTML Page را انتخاب کنید و نام فایل را به "index.html" تغییر دهید.
حال محتوای این فایل را با لیست زیر جایگزین کنید.
<!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <title>Product App</title> </head> <body> <div> <h2>All Products</h2> <ul id="products" /> </div> <div> <h2>Search by ID</h2> <input type="text" id="prodId" size="5" /> <input type="button" value="Search" onclick="find();" /> <p id="product" /> </div> <script src="http://ajax.aspnetcdn.com/ajax/jQuery/jquery-2.0.3.min.js"></script> <script> var uri = 'api/products'; $(document).ready(function () { // Send an AJAX request $.getJSON(uri) .done(function (data) { // On success, 'data' contains a list of products. $.each(data, function (key, item) { // Add a list item for the product. $('<li>', { text: formatItem(item) }).appendTo($('#products')); }); }); }); function formatItem(item) { return item.Name + ': $' + item.Price; } function find() { var id = $('#prodId').val(); $.getJSON(uri + '/' + id) .done(function (data) { $('#product').text(formatItem(data)); }) .fail(function (jqXHR, textStatus, err) { $('#product').text('Error: ' + err); }); } </script> </body> </html>
گرفتن لیستی از محصولات
برای گرفتن لیستی از محصولات، یک درخواست HTTP GET به آدرس "api/products/" ارسال کنید.
تابع getJSON یک درخواست آژاکسی ارسال میکند. پاسخ دریافتی هم آرایه ای از آبجکتهای JSON خواهد بود. تابع done در صورت موفقیت آمیز بودن درخواست، اجرا میشود. که در این صورت ما DOM را با اطلاعات محصولات بروز رسانی میکنیم.
$(document).ready(function () { // Send an AJAX request $.getJSON(apiUrl) .done(function (data) { // On success, 'data' contains a list of products. $.each(data, function (key, item) { // Add a list item for the product. $('<li>', { text: formatItem(item) }).appendTo($('#products')); }); }); });
گرفتن محصولی مشخص
برای گرفتن یک محصول توسط شناسه (ID) آن کافی است یک درخواست HTTP GET به آدرس "api/products/id/" ارسال کنید.
function find() { var id = $('#prodId').val(); $.getJSON(apiUrl + '/' + id) .done(function (data) { $('#product').text(formatItem(data)); }) .fail(function (jqXHR, textStatus, err) { $('#product').text('Error: ' + err); }); }
اجرای اپلیکیشن
اپلیکیشن را با F5 اجرا کنید. صفحه وب باز شده باید چیزی مشابه تصویر زیر باشد.
برای گرفتن محصولی مشخص، شناسه آن را وارد کنید و روی Search کلیک کنید.
اگر شناسه نامعتبری وارد کنید، سرور یک خطای HTTP بر میگرداند.
استفاده از F12 برای مشاهده درخواستها و پاسخ ها
هنگام کار با سرویسهای HTTP، مشاهدهی درخواستهای ارسال شده و پاسخهای دریافتی بسیار مفید است. برای اینکار میتوانید از ابزار توسعه دهندگان وب استفاده کنید، که اکثر مرورگرهای مدرن، پیاده سازی خودشان را دارند. در اینترنت اکسپلورر میتوانید با F12 به این ابزار دسترسی پیدا کنید. به برگه Network بروید و روی Start Capturing کلیک کنید. حالا صفحه وب را مجددا بارگذاری (reload) کنید. در این مرحله اینترنت اکسپلورر ترافیک HTTP بین مرورگر و سرور را تسخیر میکند. میتوانید تمام ترافیک HTTP روی صفحه جاری را مشاهده کنید.
به دنبال آدرس نسبی "api/products/" بگردید و آن را انتخاب کنید. سپس روی Go to detailed view کلیک کنید تا جزئیات ترافیک را مشاهده کنید. در نمای جزئیات، میتوانید headerها و بدنه درخواستها و پاسخها را ببینید. مثلا اگر روی برگه Request headers کلیک کنید، خواهید دید که اپلیکیشن ما در Accept header دادهها را با فرمت "application/json" درخواست کرده است.
اگر روی برگه Response body کلیک کنید، میتوانید ببینید چگونه لیست محصولات با فرمت JSON سریال شده است. همانطور که گفته شده مرورگرهای دیگر هم قابلیتهای مشابهی دارند. یک ابزار مفید دیگر Fiddler است. با استفاده از این ابزار میتوانید تمام ترافیک HTTP خود را مانیتور کرده، و همچنین درخواستهای جدیدی بسازید که این امر کنترل کاملی روی HTTP headers به شما میدهد.
قدمهای بعدی
<Project Sdk="Microsoft.NET.Sdk.Web"> <PropertyGroup> <TargetFramework>netcoreapp2.1</TargetFramework> </PropertyGroup> <ItemGroup> <PackageReference Include="Microsoft.AspNetCore.App" Version="2.1.4" /> <PackageReference Include="Serilog.AspNetCore" Version="2.1.1" /> <PackageReference Include="Serilog.Sinks.RollingFile" Version="3.3.0" />
public class Program { public static int Main(string[] args) { Log.Logger = new LoggerConfiguration() .MinimumLevel.Debug() .MinimumLevel.Override("Microsoft", LogEventLevel.Information) .Enrich.FromLogContext() .WriteTo.RollingFile("logs/log-{Date}.txt") .CreateLogger(); try { Log.Information("Starting web host"); BuildWebHost(args).Run(); return 0; } catch (Exception ex) { Log.Fatal(ex, "Host terminated unexpectedly"); return 1; } finally { Log.CloseAndFlush(); } } public static IWebHost BuildWebHost(string[] args) => WebHost.CreateDefaultBuilder(args) .UseStartup<Startup>() .UseSerilog() // <-- Add this line .Build(); }
نحوه استفاده از ViewModel در ASP.NET MVC
حدس من اینه که با این دستور بشه توی یک مدل کلی بخش (مثل ماژول) رو تعریف کرد و توی یک ویو بتونیم در جاهای مختلف این Sectionها رو نمایش بدیم ؟ (عدم نیاز به چند Partial View و نوشتن همه توی یک ویو)
در مورد کاهش حجم فایلهای XAP سیلورلایت زمانیکه از اسمبلیهای کتابخانههای دیگر مانند Silverlight toolkit استفاده میشود، در این فصل بحث شده است و راه حل، استفاده از گزینهی reduce XAP size by using application library caching است. به این صورت کاربران دیگر به ازای هر بار مشاهدهی سایت نیازی نخواهند داشت تا یک سری کتابخانهی کمکی را که هیچ تغییری در آنها حاصل نخواهد شد، دریافت کنند و اطلاعات آنها از cache مرورگر خوانده میشود. این مورد با کتابخانهها و ابزارهای کمکی تولید شده توسط مایکروسافت کار میکند. اما اگر خودتان یک Silverlight library را تولید کنید، چنین اتفاقی رخ نخواهد داد و باز هم فایل اسمبلی کتابخانهی شما درون فایل XAP اصلی برنامه قرار گرفته و خبری از caching مجزای آن نیست. چرا اینطور است؟ چکار باید کرد؟!
علت آن بر میگردد به نحوهی پیاده سازی library caching در VS.NET و Silverlight . برای این منظور چند مرحله باید طی شود تا این قابلیت برای کتابخانههای ساخت خودمان نیز فعال گردد:
الف) به کتابخانهی خود باید امضای دیجیتال اضافه کنید:
اینکار با استفاده از امکانات خود VS.NET بسیار ساده است. به خواص پروژه مراجعه کنید. سپس برگهی Signing را باز کرده و گزینهی Sign the assembly را انتخاب کنید (شکل زیر). در قسمت choose a strong name key file ، گزینهی new را انتخاب کرده و پس از وارد کردن یک نام دلخواه و گذر واژهای، فایل pfx امضای دیجیتال اسمبلی شما تولید خواهد شد. اکنون تنها کافی است یکبار دیگر برنامه را کامپایل کنید.
ب) به یک فایل extMap.xml هم نیاز است:
هنگام پیاده سازی قابلیت library caching ، VS.NET به دنبال فایلی به نام AssemblyFileName.extmap.xml دقیقا در کنار فایل اسمبلی مورد نظر میگردد. ساختار عمومی این فایل XML به صورت زیر است:
<?xml version="1.0"?>
<manifest xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<assembly>
<name>SLHelper</name>
<version>1.0.0.0</version>
<publickeytoken>f265933def965412</publickeytoken>
<relpath>SLHelper.dll</relpath>
<extension downloadUri="SLHelper.zip" />
</assembly>
</manifest>
نام، شماره نگارش، مسیر قرارگیری فایل اسمبلی مورد نظر و همچنین نام نهایی آن حین جدا سازی آن از XAP برنامه باید مشخص گردد. گزینهی publickeytoken مهمترین تنظیم این فایل است و قسمت الف را به همین منظور نیاز داشتیم. این عدد را به سادگی با استفاده از برنامهی reflector میتوان بدست آورد (شکل زیر).
جهت ساده سازی قسمت (ب)، برنامهی کمکی را از آدرس ذیل میتوانید دریافت کنید:
Utility: Extmap Maker
برای مطالعه بیشتر
Silverlight 3: Cached Assemblies and you can to
- نحوه ارسال فایل به سرور توسط ASP.NET MVC
- نحوه اعتبار سنجی سمت سرور ارسال فایلها
در ASP.NET MVC برای آپلود فایلها عموما عنوان میشود که از تگ input به نحو زیر استفاده شود:
<input type="file" name="file" />
<input type="file" data-val="true" data-val-required="لطفا فایلی را انتخاب کنید" name="file" /> @Html.ValidationMessage("file")
از این نکته خصوصا در طراحی html helperهای سفارشی نیز میتوان استفاده کرد.
برای مثال فرض کنید مشغول طراحی یک کنترل captcha هستید. قسمتی از آن مربوط به دریافت ورودی از کاربر است. به علاوه، پیغام خطایی هم که باید نمایش داده شود نیز باید توسط کاربر قابلیت سفارشی شدن را داشته باشد (و نمیتوان آنرا توسط یک attribute به سادگی به یک مدل خاص انتساب داد).
در این حالت تنظیم data-val و data-val-required به کمک anonymously typed objects پارامتر htmlAttributes میسر نیست (چون بین حروف آن dash وجود دارد) و باید از overload و نوع dictionary دار آن به نحو زیر استفاده کرد:
htmlHelper.TextBox("CaptchaInputText", string.Empty, new Dictionary<string, object> { { "data-val", "true"}, { "data-val-required", validationErrorMessage}, })
ماژولها در ES 6
هدف از سیستم ماژولها در ES 6، مدیریت بهتر تعدادی قطعه کد جاوا اسکریپتی، به صورت یک واحد مشخص است. همچنین ماژولها امکان مخفی کردن قسمتهایی از کد را که نباید به صورت عمومی در دسترس قرارگیرند، نیز میسر میکنند. این مسایل سالها آرزوی برنامه نویسان جاوا اسکریپت بودهاند و برای برآورده کردن آنها به روشهای غیراستاندارد و کتابخانههای ثالثی روی آورده بودند. به همین جهت برای آشنایی بهتر با ماژولها در ES 6، ابتدا نیاز است با روشهای متداول فعلی بسته بندی کدها در جاوا اسکریپت آشنا شد.
روش IIFE Module
الگوی ماژولها، سالها است که در جاوا اسکریپت مورد استفادهاست:
(function(target){ var privateDoWork = function(name) { return name +" is working"; }; var Employee = function(name) { this.name = name; } Employee.prototype = { doWork: function() { return privateDoWork(this.name); } } target.Employee = Employee; }(window));
بنابراین با روش IIFE به مزیتهای یک سیستم ماژول میرسیم:
الف) امکان مدیریت کدها را به صورت یک unit و واحد فراهم میکند.
ب) همچنین در اینجا امکان کنترل میدان دید متغیرها و متدها نیز میسر است.
روش CommonJS
از سال 2009 استفاده از جاوا اسکریپت به خارج از مرورگرها گسترش یافت؛ برای مثال نوشتن برنامههای سمت سرور NodeJS یا MongoDB با جاوا اسکریپت. در یک چنین حالتی برای مدیریت پیچیدگی برنامههای گستردهی سمت سرور و پرهیز از متغیرها و اشیاء عمومی، پروژهی CommonJS شکل گرفت. در CommonJS نحوهی تعریف ماژولها بسیار شبیه است به IIFE. با این تفاوت که دیگر خبری از متد خود اجرا شونده وجود ندارد و همچنین بجای target از exports، جهت درمعرض دید قرار دادن اشیاء استفاده میکند.
var privateDoWork = function(name) { return name +" is working"; }; var Employee = function(name) { this.name = name; } Employee.prototype = { doWork: function() { return privateDoWork(this.name); } } exports.Employee = Employee;
var Employee = require("./Employee").Employee; var e1 = new Employee("Vahid"); console.log(e1.doWork());
روش AMD
از CommonJS بیشتر در برنامههای جاوا اسکریپتی که خارج از مرورگر اجرا میشوند، استفاده میشود. برای حالتهای اجرای برنامهها درون مرورگرها و خصوصا بلاک نشدن ترد نمایش صفحه در حین پردازش ماژولها، روش دیگری به نام AMD API و یا Asynchronous module definition به وجود آمد. پیاده سازی محبوب این API عمومی، توسط کتابخانهای به نام RequireJS انجام شدهاست.
define(function(){ var privateDoWork = function(name) { // ... }; var Employee = function(name) { // ... } return Employee; });
تفاوت مهم این روش با روش IIFE این است که در روش IIFE تمام کد باید مهیا بوده و همچنین بلافاصله قابل اجرا باشد. در اینجا تنها زمانیکه نیاز به کار با ماژولی باشد، اطلاعات آن بارگذاری شده و استفاده میشود.
برای استفادهی از این ماژولها نیز از همان define استفاده میشود و پارامتر اول ارسالی، آرایهای است که ماژولهای مورد نیاز را تعریف میکند (تعریف وابستگیها). برای مثال employee تعریف شده در اینجا سبب بارگذاری فایل employee.js میشود و سپس امکانات آن به صورت یک پارامتر، به متدی که به آن نیاز دارد ارسال میگردد:
define(["employee"], function(Employee){ var e = new Employee("Vahid"); });
ماژولها در ES 6
سیستم تعریف ماژولها در ES 6 بسیار شبیه است به روشهای CommonJS و AMD API. در اینجا یک نمونه از روش تعریف ماژولها را در نگارش جدید جاوا اسکریپت مشاهده میکنید:
export class Employee { constructor(name) { this[s_name] = name; } get name() { return this[s_name]; } doWork() { return `${this.name} is working`; } }
پس از این export، اکنون برای استفادهی از آن در یک فایل js دیگر، از واژهی کلیدی import کمک گرفته میشود:
import {Employee} from "./employee"; var e1 = new Employee("Vahid"); console.log(e1.doWork());
و یا برای ارائهی یک متد خروجی، به نحو ذیل عمل میشود:
export function multiply (x, y) { return x * y; };
var hello = 'Hello World', multiply = function (x, y) { return x * y; }; export { hello, multiply };
حالت پیش فرض ماژولهای ES 6 همان strict mode است
در سیستم ماژولهای ES 6، حالت strict به صورت پیش فرض روشن است. برای مثال متغیرها حتما باید تعریف شوند.
امکان تعریف خروجیهای متفاوت از یک ماژول در ES 6
در همان فایلی که export class Employee فوق را در آن تعریف کردهایم، یک چنین تعریفهایی را نیز میتوان ارائه داد:
export let log = function(employee) { console.log(employee.name); } export let defaultRaise = 0.03; export let modelEmployee = new Employee("Vahid");
import {Employee, log, defaultRaise, modelEmployee} from "./employee"; log(modelEmployee);
module m from "./employee";
console.log(m.defaultRaise);
var e1 = new m.Employee("Vahid"); console.log(e1.doWork());
روش دیگر انجام اینکار، استفاده از * است برای درخواست تمام وابستگیهای مورد نیاز:
import * from "./employee";
امکان استفاده از یک ماژول در ماژولی دیگر
برای اینکه از امکانات یک ماژول در ماژولی دیگر استفاده کنیم نیز میتوان از همان روش تعریف import در ابتدای ماژول استفاده کرد:
import {Employee} from "./employee";
امکان تعریف ماژول پیش فرض در ES 6
اگر ماژول شما (همان فایل js) تنها دارای یک export است، میتوانید آنرا با واژهی کلیدی default مشخص کنید:
export default class Employee {
import factory from "./employee"; var e1 = new factory("Vahid"); console.log(e1.doWork());
البته باید دقت داشت که یک چنین تعریفهایی نیز مجاز است و میتوان خروجی پیش فرض و همچنین نامداری را نیز با هم ترکیب کرد:
export hello = 'Hello World'; export default function (x, y) { return x * y; };
import pow2, { hello } from 'modules';
امکان مخفی سازی اطلاعات در ماژولهای ES 6
یکی از انتظارات از سیستم ماژول، امکان مخفی سازی اطلاعات است. در اینجا تنها کافی است شیء، متد و یا متغیر تعریف شده، با واژهی کلیدی export مزین نشوند:
let privateFunction = function() { } export default class Employee {
یک نکته: اگر در کلاس export شده، خواستید تا دسترسی به s_name را محدود کنید، از Symbol ها به نحو ذیل کمک بگیرید:
let s_name = Symbol(); export class Employee { constructor(name) { this[s_name] = name; } get name() { return this[s_name]; } doWork() { return `${this.name} is working`; } }
افزودن متغیرهای محیطی
در برنامهی نمایش لیست فیلمهایی که تا قسمت 29 آنرا بررسی کردیم، از فایل src\config.json برای ذخیره سازی اطلاعات تنظیمات برنامه استفاده شد. هرچند این روش کار میکند اما بر اساس محیطهای مختلف توسعه، متغیر نیست. اغلب برنامهها باید بتوانند حداقل در سه محیط توسعه، آزمایش و تولید، بر اساس متغیرها و تنظیمات خاص هر کدام، کار کنند. برای مثال بر روی سیستمی که کار توسعه در آن انجام میشود، میخواهیم apiUrl متفاوتی را نسبت به حالتیکه برنامه توزیع میشود، داشته باشیم.
برای رفع این مشکل، برنامههایی که توسط create-react-app تولید میشوند، دارای پشتیبانی توکاری از متغیرهای محیطی هستند. برای این منظور نیاز است در ریشهی پروژه (جائیکه فایل package.json قرار دارد) فایل جدید env. را ایجاد کرد. در ویندوز برای ایجاد یک چنین فایلهایی که فقط از یک پسوند تشکیل میشوند، باید نام فایل را به صورت .env. وارد کرد؛ سپس خود ویندوز نقطهی نهایی را حذف میکند. البته اگر از ادیتور VSCode برای ایجاد این فایل استفاده میکنید، نیازی به درج نقطهی انتهایی نیست. در این فایل environment ایجاد شده میتوان تمام متغیرهای محیطی مورد نیاز را با مقادیر پیشفرض آنها درج کرد. همچنین میتوان این مقادیر پیشفرض را بر اساس محیطهای مختلف کاری، بازنویسی کرد. برای مثال میتوان فایل env.development. را اضافه کرد؛ به همراه فایلهای env.test. و env.production.
متغیرهای محیطی به صورت key=value درج میشوند. این کلیدها نیر باید با REACT_APP_ شروع شوند؛ در غیر اینصورت، کار نخواهند کرد. برای مثال در فایل env.، دو متغیر پیشفرض زیر را تعریف میکنیم:
REACT_APP_NAME=My App REACT_APP_VERSION=1
console.log(process.env);
در این خروجی، متغیر "NODE_ENV: "development به صورت خودکار با تولید بستههای مخصوص ارائهی نهایی، به production تنظیم میشود. سایر متغیرهای محیطی تعریف شده را نیز در اینجا ملاحظه میکنید. با توجه به خواص شیء env، برای مثال جهت دسترسی به نام برنامه میتوان از مقدار process.env.REACT_APP_NAME استفاده کرد.
یک نکته: با هر تغییری در مقادیر متغیرهای محیطی، نیاز است یکبار دیگر برنامه را از ابتدا توسط دستور npm start، راه اندازی مجدد کرد؛ چون این فایلها به صورت خودکار ردیابی نمیشوند.
نحوهی پردازش متغیرهای محیطی درج شدهی در برنامه
اگر همان سطر لاگ کردن خروجی process.env را به صورت زیر تغییر دهیم:
console.log("My App Name", process.env.REACT_APP_NAME);
همانطور که مشاهده میکنید، فراخوانی console.log ما، دیگر به همراه متغیر process.env.REACT_APP_NAME نیست؛ بلکه مقدار اصلی این متغیر در اینجا درج شدهاست. بنابراین اگر در در حین توسعهی برنامه، از متغیرهای محیطی استفاده شود، این متغیرها با مقادیر اصلی آنها در حین پروسهی Build نهایی، جایگزین میشوند.
Build برنامههای React برای محیط تولید
اجرای دستور npm start، سبب ایجاد یک Build مخصوص محیط توسعه میشود که بهینه سازی نشدهاست و به همراه اطلاعات اضافی قابل توجهی جهت دیباگ سادهتر برنامهاست. برای رسیدن به یک خروجی بهینه سازی شدهی مخصوص محیط تولید و ارائهی نهایی باید دستور npm run build را در خط فرمان اجرا کرد. خروجی نهایی این دستور، در پوشهی جدید build واقع در ریشهی پروژه، قرار میگیرد. اکنون میتوان کل محتویات این پوشه را جهت ارائهی نهایی در وب سرور خود، مورد استفاده قرار داد.
پس از پایان اجرای دستور npm run build، پیام «امکان ارائهی آن توسط static server زیر نیز وجود دارد» ظاهر میشود:
> npm install -g serve > serve -s build
البته با توجه به اینکه backend سرور برنامههای ما نیز در همین آدرس قرار دارد و در صورت ورود این آدرس، به صورت خودکار به https://localhost:5001/index.html هدایت خواهید شد، میتوان این پورت پیشفرض را با اجرای دستور serve -s build -l 1234 تغییر داد. اکنون میتوان آدرس جدید http://localhost:1234 را در مرورگر آزمایش کرد که ... با خطای زیر کار نمیکند:
Access to XMLHttpRequest at 'https://localhost:5001/api/genres' from origin 'http://localhost:1234' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.
WithOrigins("http://localhost:3000", "http://localhost:1234")
یک نکته: زمانیکه از دستور npm start استفاده میشود، متغیرهای محیطی از فایل env.development. خوانده خواهند شد و زمانیکه از دستور npm run build استفاده میشود، این متغیرها از فایل env.production. تامین میشوند. در این حالتها اگر متغیری در این دو فایل درج نشده بود، از مقدار پیشفرض موجود در فایل env. استفاده میگردد. از فایل env.test. با اجرای دستور npm test، به صورت خودکار استفاده میشود.
آماده سازی برنامهی React، برای توزیع نهایی
تا اینجا برنامهی React تهیه شده، اطلاعات apiUrl خودش را از فایل config.json دریافت میکند. اکنون میخواهیم بر اساس حالات مختلف توسعه و تولید، از apiUrlهای متفاوتی استفاده شود. به همین جهت به فایل env.production. مراجعه کرده و تنظیمات ذیل را به آن اضافه میکنیم:
REACT_APP_API_URL=https://localhost:5001/api REACT_APP_ADMIN_ROLE_NAME=Admin
اکنون به برنامه مراجعه کرده و در هرجائی که ارجاعی به فایل config.json وجود دارد، سطر import آنرا حذف میکنیم. با این تغییر، تمام آدرسهایی مانند:
const apiEndpoint = apiUrl + "/users";
const apiEndpoint = "/users";
axios.defaults.baseURL = process.env.REACT_APP_API_URL;
همچنین adminRoleName مورد نیاز در فایل src\services\authService.js را نیز از همان فایل env جاری تامین میکنیم:
const adminRoleName = process.env.REACT_APP_ADMIN_ROLE_NAME;
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید: sample-34-frontend.zip و sample-34-backend.zip