مروری بر نحوهی توزیع برنامههای Blazor بر روی IIS
1- پیش از هر کاری باید مطابق نگارش ASP.NET Core در حال استفاده (که به عنوان هاست Blazor Server و یا ارائه دهندهی قسمت Web API برنامهی سمت کلاینت WASM مطرح است)، بستهی NET Core hosting bundle. را نصب کرد که عموما تحت عنوان «Hosting Bundle Installer» قابل دریافت است.
نکتهی مهم: همانند تمام نگارشهای دات نت، در اینجا نیز باید Hosting Bundle را پس از نصب IIS، بر روی سیستم نصب کرد. اگر این ترتیب تغییر کند، یکبار دیگر نصاب آنرا اجرا کرده و گزینهی ترمیم نصب را انتخاب کنید تا یکپارچگی آن با IIS صورت گیرد.
2- نیاز است برنامهی خود را اصطلاحا publish کرد تا به همراه فایلهای نهایی قابل کپی باشد که در پوشهای توسط IIS هاست خواهند شد. برای اینکار اگر از نگارش کامل ویژوال استودیو استفاده میکنید، فقط کافی است بر روی پروژهی مدنظر کلیک راست کرده و از منوی باز شده، گزینهی publish را انتخاب کنید و مراحل آنرا طی نمائید و یا این مراحل را میتوان توسط دستور خط فرمان زیر نیز خلاصه کرد که وابستگی خاصی، به IDE ویژهای ندارد و چند سکویی است:
dotnet publish -o "c:\dir1\dir2" -c Release
و یا اگر فقط دستور dotnet publish -c Release را در ریشهی پروژه اجرا کنیم، خروجی نهایی را در پوشهی bin\Release\net5.0\publish میتوان مشاهده کرد که به همراه یک web.config مخصوص برنامههای blazor هم هست و در آن mime typeهای متناظری، به همراه URL rewriting مناسب برنامههای تک صفحهای وب از پیش تنظیم شدهاست. بنابراین در اینجا نصب ماژول URL rewrite بر روی IIS نیز الزامی است.
3- در اینجا نیز همانند تنظیمات برنامههای ASP.NET Core، باید application pool منتسب به برنامه را ویرایش کرده و NET Clr Version. آنرا بر روی No Managed Code قرار داد.
روش فعالسازی توزیع مبتنی بر فشرده سازی Brotli در IIS
در حین عملیات publish استاندارد، به صورت پیشفرض از تمام فایلها، سه نسخهی اصلی، gz شده (gzip) و یا br شده (فشرده سازی Brotli که فایلهای کم حجمتری را نسبت به gz ارائه میدهد) نیز تهیه میشوند که بسته به نوع مرورگر و پشتیبانی آن از روشهای مختلف فشرده سازی، یکی از آنها در اختیار کلاینت قرار خواهد گرفت که به این صورت کاربران، تجربهی دریافت کم حجمتر و سریعتری را خواهند داشت.
باید دقت داشت Web.config ای که به همراه دستور dotnet publish ایجاد میشود، روش توزیع پیشفرض فایلهای br. تولیدی را ندارد. برای اینکار نیاز است تنظیمات این فایل web.config توصیه شدهی توسط تیم Blazor را به web.config خود اضافه کرد تا در نهایت حجم دریافتی از سرور به شدت کاهش یابد.
یک نکته: اگر میخواهید فایل web.config سفارشی خودتان را داشته باشید، نمونهای از آنرا در ریشهی پروژه قرار داده و سپس فایل csproj را به نحو زیر ویرایش کنید تا از آن در حین publish استفاده کند:
<PropertyGroup> <PublishIISAssets>true</PublishIISAssets> </PropertyGroup>
در حین publish برنامههای Blazor WASM کار IL trimming نیز انجام میشود
برای کاهش حجم نهایی برنامههای Blazor WASM، در حین publish در حالت release، کار IL Trimming نیز به صورت خودکار انجام میشود تا کدهای IL ای که در برنامه نقش نداشتهاند و مستقیما در جائی استفاده نشدهاند، به صورت خودکار حذف شوند و به این ترتیب حجم ارائهی نهایی به شدت کاهش یابد.
فقط باید دقت داشت که در این حالت اگر عملیات پویایی مانند reflection در کدهای شما صورت میگیرد، به علت نداشتن ارجاع استاتیکی به منابع مورد استفاده، در زمان اجرا با مشکل مواجه خواهد شد. اگر میخواهید اخطارهایی را در این زمینه مشاهده کنید، گزینهی زیر را به فایل csproj اضافه نمائید:
<SuppressTrimAnalysisWarnings>false</SuppressTrimAnalysisWarnings>
حذف مباحث بومی سازی در صورت عدم نیاز
اگر در برنامهی خود از مباحث time-zones استفاده نمیکنید، میتوانید با غیرفعال کردن آن در فایل csproj، حداقل 100 کیلوبایت از حجم برنامهی نهایی را کاهش دهید:
<BlazorEnableTimeZoneSupport>false</BlazorEnableTimeZoneSupport>
<InvariantGlobalization>true</InvariantGlobalization>
همه چیز در مورد CLR : قسمت اول
- ربطی به زبان برنامه نویسی ندارند؛ درست است. اما CLR یعنی Common Language Runtime . این محیط اجرایی و JIT آن ASLR را میسر میکنند . حتی اسمبلیهای ngen شده هم از دات نت 3.5 به بعد دارای ASLR فعال هستند. همچنین DEP هم از طریق روشن کردن سوئیچ NXCOMPAT کامپایلر فراهم شدهاست (از زمان دات نت 2 به بعد). یعنی اگر OpenSSL را با دات نت مینوشتند، هیچ وقت مشکل heartbleed رخ نمیداد.
معرفی ELMAH
Roslyn #2
Roslyn از زمان ارائهی نگارش Visual Studio 14 CTP3 با ویژوال استودیو یکپارچه شد. بنابراین اگر از نگارش نهایی آن یعنی Visual Studio 2015 استفاده میکنید، اولین پیشنیاز کار با آن را در اختیار دارید. البته نگارش پیش نمایش آن نیز برای VS 2013 ارائه شد که در این تاریخ منسوخ شده درنظر گرفته میشود و دیگر به روز رسانی نخواهد شد. بنابراین نیاز است حتما Visual Studio 2015 را نصب کنید.
در حین نصب Visual Studio 2015 نیز باید گزینهی نصب SDK آنرا انتخاب کرده باشید، تا امکان تولید فایلهای VSIX مرتبط را پیدا کنید. از این جهت که قالبهای پروژهی Roslyn، امکان تولید افزونههای آنالیز کدها را نیز میسر میکنند.
علاوه بر اینها نیاز است Syntax Visualizer و قالبهای پروژهی مخصوص Roslyn را نیز جداگانه نصب کنید. برای این منظور به آدرس ذیل مراجعه کرده و افزونهی آنرا نصب کنید.
NET Compiler Platform SDK.
پس از نصب این افزونه، دو قابلیت جدید به Visual studio اضافه میشوند:
الف) در منوی view، ذیل گزینهی other windows، گزینهی جدیدی به نام Syntax visualizer اضافه شدهاست.
ب) ذیل گزینهی extensibility پنجرهی new project، تعدادی قالب پروژهی جدید مخصوص Roslyn نصب شدهاند.
البته باید دقت داشت که این قالبهای جدید برای نمایش در این لیست، حداقل نیاز به انتخاب دات نت 4.5.2 را دارند.
از Syntax visualizer جهت مشاهدهی ریز جزئیات ساختار دستوری کدهای جاری استفاده میشود:
زمانیکه گزینهی View->Other windows->Syntax visualizer را اجرا کردید، اندکی صبر کنید تا بارگذاری شود و سپس اشارهگر ویرایشگر را به قسمتی دلخواه حرکت دهید. در این حالت میتوان ساختار کدها را بر اساس تفسیر Roslyn مشاهده کرد. همچنین اگر در Syntax visualizer یک نود را انتخاب کنید، بلافاصله معادل آن در ادیتور ویژوال استودیو انتخاب میشود. از این ابزار جهت تولید آنالایزرهای مبتنی بر Roslyn زیاد استفاده میشود.
تغییرات خط فرمان Visual Studio 2015 نسبت به نگارشهای پیشین آن
خط فرمان Visual Studio 2015 به همراه کامپایلر قدیمی خط فرمان C# 5 و همچنین کامپایلر جدید مبتنی بر Roslyn مخصوص C# 6 است. این کامپایلرها را در مسیرهای ذیل میتوانید پیدا کنید:
کامپایلر جدید مبتنی بر Roslyn در مسیر C:\Program Files (x86)\MSBuild\14.0\Bin قرار دارد و کامپایلر قدیمی C# 5 در مسیر نصب دات نت فریم ورک یعنی C:\Windows\Microsoft.NET\Framework\v4.0.30319 قرار گرفتهاست.
همانطور که مشاهده میکنید، نگارش کامپایلر مبتنی بر Roslyn، یک است و شماره build آن، بیانگر تاریخ کامپایل آن است:
50618=2015/06/18
اکنون شاید این سؤال مطرح شود که کامپایلر پیش فرض کدام است؟ برای یافتن آن، به منوی استارت ویندوز مراجعه کرده و گزینهی developer command prompt for vs 2015 را اجرا کنید. در اینجا اگر دستور csc را اجرا کنید، خروجی آن همان کامپایلر مبتنی بر Roslyn است:
در همینجا اگر سوئیچ ? را برای مشاهدهی راهنمای کامپایلر خط فرمان Roslyn صادر کنید، یکی از گزینههای جدید آن، سوئیچ analyzer است:
C:\Program Files (x86)\Microsoft Visual Studio 14.0>csc /? Microsoft (R) Visual C# Compiler version 1.0.0.50618 Copyright (C) Microsoft Corporation. All rights reserved. Visual C# Compiler Options /analyzer:<file list> Run the analyzers from this assembly (Short form: /a)
همچنین این کامپایلر نسبت به نگارش قبلی آن، دارای سوئیچ و گزینهی parallel نیز میباشد که به کمک ساختارهای دادهی immutable جدید دات نت مسیر شدهاست.
ASP.NET MVC #5
بررسی نحوه انتقال اطلاعات از یک کنترلر به Viewهای مرتبط با آن
در ASP.NET Web forms در فایل code behind یک فرم مثلا میتوان نوشت Label1.Text و سپس مقداری را به آن انتساب داد. اما اینجا به چه ترتیبی میتوان شبیه به این نوع عملیات را انجام داد؟ با توجه به اینکه در کنترلرها هیچ نوع ارجاع مستقیمی به اشیاء رابط کاربری وجود ندارد و این دو از هم مجزا شدهاند.
در پاسخ به این سؤال، همان مثال ساده قسمت قبل را ادامه میدهیم. یک پروژه جدید خالی ایجاد شده است به همراه HomeController ایی که به آن اضافه کردهایم. همچنین مطابق روشی که ذکر شد، View ایی به نام Index را نیز به آن اضافه کردهایم. سپس برای ارسال اطلاعات از یک کنترلر به View از یکی از روشهای زیر میتوان استفاده کرد:
الف) استفاده از اشیاء پویا
ViewBag یک شیء dynamic است که در دات نت 4 امکان تعریف آن میسر شده است. به این معنا که هر نوع خاصیت دلخواهی را میتوان به این شیء انتساب داد و سپس این اطلاعات در View نیز قابل دسترسی و استخراج خواهد بود. مثلا اگر در اینجا به شیء ViewBag، خاصیت دلخواه Country را اضافه کنیم و سپس مقداری را نیز به آن انتساب دهیم:
using System.Web.Mvc;
namespace MvcApplication1.Controllers
{
public class HomeController : Controller
{
public ActionResult Index()
{
ViewBag.Country = "Iran";
return View();
}
}
}
این اطلاعات در View مرتبط با اکشنی به نام Index به نحو زیر قابل بازیابی خواهد بود (نحوه اضافه کردن View متناظر با یک اکشن یا متد را هم در قسمت قبل با تصویر مرور کردیم):
@{
ViewBag.Title = "Index";
}
<h2>
Index</h2>
<p>
Country : @ViewBag.Country
</p>
در این مثال، @ در View engine جاری که Razor نام دارد، به این معنا میباشد که این مقداری است که میخواهم دریافت کنی (ViewBag.Country) و سپس آنرا در حین پردازش صفحه نمایش دهی.
ب) انتقال اطلاعات یک شیء کامل و غیر پویا به View
هر پروژه جدید MVC به همراه پوشهای به نام Models است که در آن میتوان تعاریف اشیاء تجاری برنامه را قرار داد. در پروژه جاری، یک کلاس ساده را به نام Employee به این پوشه اضافه میکنیم:
namespace MvcApplication1.Models
{
public class Employee
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
}
}
اکنون برای نمونه یک وهله از این شیء را در متد Index ایجاد کرده و سپس به view متناظر با آن ارسال میکنیم (در قسمت return View کد زیر مشخص است). بدیهی است این وهله سازی در عمل میتواند از طریق دسترسی به یک بانک اطلاعاتی یا یک وب سرویس و غیره باشد.
using System.Web.Mvc;
using MvcApplication1.Models;
namespace MvcApplication1.Controllers
{
public class HomeController : Controller
{
public ActionResult Index()
{
ViewBag.Country = "Iran";
var employee = new Employee
{
Email = "name@site.com",
FirstName = "Vahid",
LastName = "N."
};
return View(employee);
}
}
}
امضاهای متفاوت (overloads) متد کمکی View هم به شرح زیر هستند:
ViewResult View(Object model)
ViewResult View(string viewName, Object model)
ViewResult View(string viewName, string masterName, Object model)
اکنون برای دسترسی به اطلاعات این شیء employee در View متناظر با این متد، چندین روش وجود دارد:
@{
ViewBag.Title = "Index";
}
<h2>
Index</h2>
<div>
Country: @ViewBag.Country <br />
FirstName: @Model.FirstName
</div>
میتوان از طریق شیء استاندارد دیگری به نام Model (که این هم یک شیء dynamic است مانند ViewBag قسمت قبل)، به خواص شیء یا مدل ارسالی به View جاری دسترسی پیدا کرد که یک نمونه از آنرا در اینجا ملاحظه میکنید.
روش دوم، بر اساس تعریف صریح نوع مدل است به نحو زیر:
@model MvcApplication1.Models.Employee
@{
ViewBag.Title = "Index";
}
<h2>
Index</h2>
<div>
Country: @ViewBag.Country
<br />
FirstName: @Model.FirstName
</div>
در اینجا در مقایسه با قبل، تنها یک سطر به اول فایل View اضافه شده است که در آن نوع شیء Model تعیین میگردد (کلمه model هم در اینجا با حروف کوچک شروع شده است). به این ترتیب اینبار اگر سعی کنیم به خواص این شیء دسترسی پیدا کنیم، Intellisense ویژوال استودیو ظاهر میشود. به این معنا که شیء Model بکارگرفته شده اینبار دیگر dynamic نیست و دقیقا میداند که چه خواصی را باید پیش از اجرای برنامه در اختیار استفاده کننده قرار دهد.
به این روش، روش Strongly typed view هم گفته میشود؛ چون View دقیقا میداند که چون نوعی را باید انتظار داشته باشد؛ تحت نظر کامپایلر قرار گرفته و همچنین Intellisense نیز برای آن مهیا خواهد بود.
به همین جهت این روش Strongly typed view، در بین تمام روشهای مهیا، به عنوان روش توصیه شده و مرجح مطرح است.
به علاوه استفاده از Strongly typed views یک مزیت دیگر را هم به همراه دارد: فعال شدن یک code generator توکار در VS.NET به نام scaffolding. یک مثال ساده:
تا اینجا ما اطلاعات یک کارمند را نمایش دادیم. اگر بخواهیم یک لیست از کارمندها را نمایش دهیم چه باید کرد؟
روش کار با قبل تفاوتی نمیکند. اینبار در return View ما، یک شیء لیستی ارائه خواهد شد. در سمت View هم با یک حلقه foreach کار نمایش این اطلاعات صورت خواهد گرفت. راه سادهتری هم هست. اجازه دهیم تا خود VS.NET، کدهای مرتبط را برای ما تولید کند.
یک کلاس دیگر به پوشه مدلهای برنامه اضافه کنید به نام Employees با محتوای زیر:
using System.Collections.Generic;
namespace MvcApplication1.Models
{
public class Employees
{
public IList<Employee> CreateEmployees()
{
return new[]
{
new Employee { Email = "name1@site.com", FirstName = "name1", LastName = "LastName1" },
new Employee { Email = "name2@site.com", FirstName = "name2", LastName = "LastName2" },
new Employee { Email = "name3@site.com", FirstName = "name3", LastName = "LastName3" }
};
}
}
}
سپس متد جدید زیر را به کنترلر Home اضافه کنید.
public ActionResult List()
{
var employeesList = new Employees().CreateEmployees();
return View(employeesList);
}
برای اضافه کردن View متناظر با آن، روی نام متد کلیک راست کرده و گزینه Add view را انتخاب کنید. در صفحه ظاهر شده:
تیک مربوط به Create a strongly typed view را قرار دهید. سپس در قسمت Model class، کلاس Employee را انتخاب کنید (نه Employees جدید را، چون از آن میخواهیم به عنوان منبع داده لیست تولیدی استفاده کنیم). اگر این کلاس را مشاهده نمیکنید، به این معنا است که هنوز برنامه را یکبار کامپایل نکردهاید تا VS.NET بتواند با اعمال Reflection بر روی اسمبلی برنامه آنرا پیدا کند. سپس در قسمت Scaffold template گزینه List را انتخاب کنید تا Code generator توکار VS.NET فعال شود. اکنون بر روی دکمه Add کلیک نمائید تا View نهایی تولید شود. برای مشاهده نتیجه نهایی مسیر http://localhost/Home/List باید بررسی گردد.
ج) استفاده از ViewDataDictionary
ViewDataDictionary از نوع IDictionary با کلیدی رشتهای و مقداری از نوع object است. توسط آن شیءایی به نام ViewData در ASP.NET MVC به نحو زیر تعریف شده است:
public ViewDataDictionary ViewData { get; set; }
این روش در نگارشهای اولیه ASP.NET MVC بیشتر مرسوم بود. برای مثال:
using System;
using System.Web.Mvc;
namespace MvcApplication1.Controllers
{
public class HomeController : Controller
{
public ActionResult Index()
{
ViewData["DateTime"] = "<br/>" + DateTime.Now;
return View();
}
}
}
و سپس جهت استفاده از این ViewData تعریف شده با کلید دلخواهی به نام DateTime در View متناظر با اکشن Index خواهیم داشت:
@{
ViewBag.Title = "Index";
}
<h2>
Index</h2>
<div>
DateTime: @ViewData["DateTime"]
</div>
یک نکته امنیتی:
اگر به مقدار انتساب داده شده به شیء ViewDataDictionary دقت کنید، یک تگ br هم به آن اضافه شده است. برنامه را یکبار اجرا کنید. مشاهده خواهید کرد که این تگ به همین نحو نمایش داده میشود و نه به صورت یک سطر جدید HTML . چرا؟ چون Razor به صورت پیش فرض اطلاعات را encode شده (فراخوانی متد Html.Encode در پشت صحنه به صورت خودکار) در صفحه نمایش میدهد و این مساله از لحاظ امنیتی بسیار عالی است؛ زیرا جلوی بسیاری از حملات cross site scripting یا XSS را خواهد گرفت.
احتمالا الان این سؤال پیش خواهد آمد که اگر «عالمانه» بخواهیم این رفتار نیکوی پیش فرض را غیرفعال کنیم چه باید کرد؟
برای این منظور میتوان نوشت:
@Html.Raw(myString)
و یا:
<div>@MvcHtmlString.Create("<h1>HTML</h1>")</div>
به این ترتیب خروجی Razor دیگر encode شده نخواهد بود.
د) استفاده از TempData
TempData نیز یک dictionary دیگر برای ذخیره سازی اطلاعات است و به نحو زیر در فریم ورک تعریف شده است:
public TempDataDictionary TempData { get; set; }
TempData در پشت صحنه از سشنهای ASP.NET جهت ذخیره سازی اطلاعات استفاده میکند. بنابراین اطلاعات آن در سایر کنترلرها و View ها نیز در دسترس خواهد بود. البته TempData یک سری تفاوت هم با سشن معمولی ASP.NET دارد:
- بلافاصله پس از خوانده شدن، حذف خواهد شد.
- پس از پایان درخواست از بین خواهد رفت.
هر دو مورد هم به جهت بالابردن کارآیی برنامههای ASP.NET MVC و مصرف کمتر حافظه سرور درنظر گرفته شدهاند.
البته کسانی که برای بار اول هست با ASP.NET مواجه میشوند، شاید سؤال بپرسند این مسایل چه اهمیتی دارد؟ پروتکل HTTP، ذاتا یک پروتکل «بدون حالت» است یا Stateless هم به آن گفته میشود. به این معنا که پس از ارائه یک صفحه وب توسط سرور، تمام اشیاء مرتبط با آن در سمت سرور تخریب خواهند شد. این مورد متفاوت است با برنامههای معمولی دسکتاپ که طول عمر یک شیء معمولی تعریف شده در سطح فرم به صورت یک فیلد، تا زمان باز بودن آن فرم، تعیین میگردد و به صورت خودکار از حافظه حذف نمیشود. این مساله دقیقا مشکل تمام تازه واردها به دنیای وب است که چرا اشیاء ما نیست و نابود شدند. در اینجا وب سرور قرار است به هزاران درخواست رسیده پاسخ دهد. اگر قرار باشد تمام این اشیاء را در سمت سرور نگهداری کند، خیلی زود با اتمام منابع مواجه میگردد. اما واقعیت این است که نیاز است یک سری از اطلاعات را در حافظه نگه داشت. به همین منظور یکی از چندین روش مدیریت حالت در ASP.NET استفاده از سشنها است که در اینجا به نحو بسیار مطلوبی، با سربار حداقل توسط TempData مدیریت شده است.
یک مثال کاربردی در این زمینه:
فرض کنید در متد جاری کنترلر، ابتدا بررسی میکنیم که آیا ورودی دریافتی معتبر است یا خیر. در غیراینصورت، کاربر را به یک View دیگر از طریق کنترلری دیگر جهت نمایش خطاها هدایت خواهیم کرد.
همین «هدایت مرورگر به یک View دیگر» یعنی پاک شدن و تخریب اطلاعات کنترلر قبلی به صورت خودکار. بنابراین نیاز است این اطلاعات را در TempData قرار دهیم تا در کنترلری دیگر قابل استفاده باشد:
using System;
using System.Web.Mvc;
namespace MvcApplication1.Controllers
{
public class HomeController : Controller
{
public ActionResult InsertData(string name)
{
// Check for input errors.
if (string.IsNullOrWhiteSpace(name))
{
TempData["error"] = "name is required.";
return RedirectToAction("ShowError");
}
// No errors
// ...
return View();
}
public ActionResult ShowError()
{
var error = TempData["error"] as string;
if (!string.IsNullOrWhiteSpace(error))
{
ViewBag.Error = error;
}
return View();
}
}
}
در همان HomeController دو متد جدید به نامهای InsertData و ShowError اضافه شدهاند. در متد InsertData ابتدا بررسی میشود که آیا نامی وارد شده است یا خیر. اگر خیر توسط متد RedirectToAction، کاربر به اکشن یا متد ShowError هدایت خواهد شد.
برای انتقال اطلاعات خطایی که میخواهیم در حین این Redirect نمایش دهیم نیز از TempData استفاده شده است.
بدیهی است برای اجرا این مثال نیاز است دو View جدید برای متدهای InsertData و ShowError ایجاد شوند (کلیک راست روی نام متد و انتخاب گزینه Add view برای اضافه کردن View مرتبط با آن اکشن).
محتوای View مرتبط با متد افزودن اطلاعات فعلا مهم نیست، ولی View نمایش خطاها در سادهترین حالت مثلا میتواند به صورت زیر باشد:
@{
ViewBag.Title = "ShowError";
}
<h2>Error</h2>
@ViewBag.Error
برای آزمایش برنامه هم مطابق مسیریابی پیش فرض و با توجه به قرار داشتن در کنترلری به نام Home، مسیر http://localhost/Home/InsertData ابتدا باید بررسی شود. چون آرگومانی وارد نشده، بلافاصله صفحه به آدرس http://localhost/Home/ShowError به صورت خودکار هدایت خواهد شد.
نکتهای تکمیلی در مورد Strongly typed viewها:
عنوان شد که Strongly typed view روش مرجح بوده و بهتر است از آن استفاده شود، زیرا اطلاعات اشیاء و خواص تعریف شده در یک View تحت نظر کامپایلر قرار میگیرند که بسیار عالی است. یعنی اگر در View بنویسم FirstName: @Model.FirstName1 چون FirstName1 وجود خارجی ندارد، برنامه نباید کامپایل شود. یکبار این را بررسی کنید. برنامه بدون مشکل کامپایل میشود! اما تنها در زمان اجرا است که صفحه زرد رنگ معروف خطاهای ASP.NET ظاهر میشود که چنین خاصیتی وجود ندارد (این حالت پیش فرض است؛ یعنی کامپایل یک View در زمان اجرا). البته این باز هم خیلی بهتر است از ViewBag، چون اگر مثلا ViewBag.Country1 را وارد کنیم، در زمان اجرا تنها چیزی نمایش داده نخواهد شد؛ اما با روش Strongly typed view، حتما خطای Compilation Error به همراه نمایش محل مشکل نهایی، در صفحه ظاهر خواهد شد.
سؤال: آیا میشود پیش از اجرای برنامه هم این بررسی را انجام داد؟
پاسخ: بله. باید فایل پروژه را اندکی ویرایش کرده و مقدار MvcBuildViews را که به صورت پیش فرض false هست، true نمود. یا خارج از ویژوال استودیو با یک ادیتور متنی ساده مثلا فایل csproj را گشوده و این تغییر را انجام دهید. یا داخل ویژوال استودیو، بر روی نام پروژه کلیک راست کرده و سپس گزینه Unload Project را انتخاب کنید. مجددا بر روی این پروژه Unload شده کلیک راست نموده و گزینه edit را انتخاب نمائید. در صفحه باز شده، MvcBuildViews را یافته و آنرا true کنید. سپس پروژه را Reload کنید.
اکنون اگر پروژه را کامپایل کنید، پیغام خطای زیر پیش از اجرای برنامه قابل مشاهده خواهد بود:
'MvcApplication1.Models.Employee' does not contain a definition for 'FirstName1'
and no extension method 'FirstName1' accepting a first argument of type 'MvcApplication1.Models.Employee'
could be found (are you missing a using directive or an assembly reference?)
d:\Prog\MvcApplication1\MvcApplication1\Views\Home\Index.cshtml 10 MvcApplication1
البته بدیهی است این تغییر، زمان Build پروژه را مقداری افزایش خواهد داد؛ اما امنترین حالت ممکن برای جلوگیری از این نوع خطاهای تایپی است.
یا حداقل بهتر است یکبار پیش از ارائه نهایی برنامه این مورد فعال و بررسی شود.
و یک خبر خوب!
مجوز سورس کد ASP.NET MVC از MS-PL به Apache تغییر کرده و همچنین Razor و یک سری موارد دیگر هم سورس باز شدهاند. این تغییرات به این معنا خواهند بود که پروژه از حالت فقط خواندنی MS-PL به حالت متداول یک پروژه سورس باز که شامل دریافت تغییرات و وصلهها از جامعه برنامه نویسها است، تغییر کرده است (^ و ^).
PHP سریعتر از ASP.NET! افسانه یا واقعیت؟
چندین علت داره:
- پروسه کامپایل کدهای دات نت یک مرحلهای نیست. کلا در دات نت کدها به یک زبان میانی به نام IL ترجمه میشن. بعد این IL توسط JIT compiler تبدیل به کدهای ماشین میشه. در ASP.NET این مساله هم برای کدهای پشت صحنه برنامه و هم برای خود صفحات رخ میده. بنابراین برای بار اول مشاهده، روند این پروسه الزامی هست. ولی برای دفعات بعدی مشاهده خیر. البته روش برای پیش کامپایل کردن کامل صفحات هم وجود داره و یا در IISهای جدید یک سری مبحث warmup توکار پیش بینی شده.
- همچنین IIS برای مدیریت منابع سرور، یک سایت رو مدام در حافظه نگه نمیداره. فقط زمانیکه اولین درخواست به سرور میرسه سایت رو بارگذاری میکنه و application pool اون رو استارت. این هم یک زمان اولیه اندکی رو ممکنه به خودش اختصاص بده. بعلاوه پس از مدتی، یک سایت بیکار رو از حافظه خارج میکنه. بهش میگن ریسایکل کردن. در ASP.NET 4.0 به بعد امکان تنظیم auto-start برای سایتها هست.
همانطور که قبلا ذکر کردیم یک اسمبلی شامل کدهای IL و متادیتا هاست. IL یک زبان غیر وابسته به معماری سی پی یو است که مایکروسافت پس از مشاورههای زیاد از طریق نویسندگان کامپایلر و زبانهای آکادمی و تجاری آن را ایجاد کرده است. IL یک زبان کاملا سطح بالا نسبت به زبانهای ماشین سی پی یو است. IL میتواند به انواع اشیاء دسترسی داشته و آنها را دستکاری نماید و شامل دستورالعمل هایی برای ایجاد و آماده سازی اشیاست. صدا زدن متدهای مجازی بر روی اشیاء و دستکاری المانهای یک آرایه به صورت مستقیم، از جمله کارهایی است که انجام میدهد. همچنین شامل دستوراتی برای صدور و کنترل استثناء هاست . شما میتوانید IL را به عنوان یک زبان ماشین شیء گرایی تصور کنید.
معمولا برنامه نویسها در یک زبان سطح بالا چون سی شارپ به نوشتن میپردازند و کمپایلر کد IL آنها را ایجاد میکند و این کد IL میتواند به صورت اسمبلی نوشته شود. به همین علت مایکروسافت ابزار ILASM.exe و برای دی اسمبل کردن ILDASM.exe را ارائه کرده است.
این را همیشه به یاد داشته باشید که زبانهای سطح بالا تنها به زیر قسمتی از قابلیتهای CLR دسترسی دارند؛ ولی در IL Assembly توسعه دهنده به تمامی قابلیتهای CLR دسترسی دارد. این انتخاب شما در زبان برنامه نویسی است که میخواهید تا چه حد به قابلیتهای CLR دسترسی داشته باشید. البته یکپارچه بودن محیط در CLR باعث پیوند خوردن کدها به یکدیگر میشود. برای مثال میتوانید قسمتی از یک پروژه که کار خواندن و نوشتن عملیات را به عهده دارد بر دوش #C قرار دهید و محاسبات امور مالی را به APL بسپارید.
برای اجرا شدن کدهای IL، ابتدا CLR باید بر اساس معماری سی پی یو کد ماشین را به دست آورد که وظیفهی تبدیل آن بر عهده Jit یا Just in Time است . شکل زیر نحوه انجام این کار را انجام میدهد:
موقعیکه کنسول اولین متدش مثلا WriteLine را فراخوانی میکند، کامپایلر جیت صدا زده میشود. تابع کامپایلر جیت مسئولیت تبدیل کدهای IL را به کدهای بومی آن پلتفرم، به عهده دارد. از آنجایی که عمل کامپایل در همان لحظه یا در جا اتفاق میافتد (Just in time)، عموم این کامپایر را Jitter یا Jit Compiler مینامند.
موقعیکه صدا زدن آن متد به سمت jit انجام شد، جیت متوجه میشود که چه متدی درخواست شده و نحوهی تعریف آن متد به چه صورتی است. جیت هم در متادیتای یک اسمبلی به جست و جو پرداخته و کدهای IL آن متد را دریافت میکند. سپس کدها را تایید و عملیات کامپایل به سمت کدهای بومی را آغاز میکند. در ادامه این کدهای بومی را در قطعهای از حافظه ذخیره میکند. سپس جیت به جایی بر میگردد که CLR از آنجا جیت را وارد کار کرده؛ یعنی مدخل ورودی متد writeline و سپس آدرس آن قطعه حافظه را که شامل کد بومی است، بجای آن قطعه که به کد IL اشاره میکند، جابجا میکند و کد بومی شده را اجرا و نهایتا به محدودهی main باز میگردد.
در شکل زیر مجددا همان متد صدا زده شده است. ولی از آنجا که قبلا کد کامپایل شده را به دست آوردیم، از همان استفاده میکنیم و دیگر تابع جیت را صدا نمیزنیم.
توجه داشته باشید، در متدهای چند ریختی که شکلهای متفاوتی از پارامترها را دارند، هر کدام کمپایل جداگانهای صورت میگیرد. یعنی برای متدهای زیر جیت برای هر کدام جداگانه فراخوانی میشود.
WriteLine("Hello"); WriteLine();
در مقالهی آینده عملکرد جیت را بیشتر مورد بررسی قرار میدهیم و در مورد دیباگ کردن و به نظرم برتری CLR را نسبت به زبانهای مدیریت نشده، بررسی میکنیم.
mklink /j C:\android-sdk "C:\Program Files (x86)\Android\android-sdk"
<AotAssemblies>true</AotAssemblies> <EnableLLVM>true</EnableLLVM>
در دات نت فریم ورک امکان کامپایل پویای یک قطعه کد دریافت شده از یک رشته، توسط فضای نام CodeDom مهیا است که قدرت قابل توجهی را در اختیار برنامه نویس قرار میدهد.
مثال یک:
رشته زیر را کامپایل کرده و تبدیل به یک فایل exe کنید:
string source =
@"
namespace Foo
{
public class Bar
{
static void Main(string[] args)
{
Bar.SayHello();
}
public static void SayHello()
{
System.Console.WriteLine(""Hello World"");
}
}
}
";
using System;
using System.Collections.Generic;
//دو فضای نامی که برای این منظور اضافه شدهاند
using Microsoft.CSharp;
using System.CodeDom.Compiler;
namespace compilerTest
{
class Program
{
static void compileIt1()
{
//سورس کد ما جهت کامپایل
string source =
@"
namespace Foo
{
public class Bar
{
static void Main(string[] args)
{
Bar.SayHello();
}
public static void SayHello()
{
System.Console.WriteLine(""Hello World"");
}
}
}
";
//تعیین نگارش کامپایلر مورد استفاده
Dictionary<string, string> providerOptions = new Dictionary<string, string>
{
{"CompilerVersion", "v3.5"}
};
//تعیین اینکه کد ما سی شارپ است
CSharpCodeProvider provider = new CSharpCodeProvider(providerOptions);
//تعیین اینکه خروجی یک فایل اجرایی است بعلاوه مشخص سازی محل ذخیره سازی فایل نهایی
CompilerParameters compilerParams = new CompilerParameters
{
OutputAssembly = "D:\\Foo.EXE",
GenerateExecutable = true
};
//عملیات کامپایل در اینجا صورت میگیرد
CompilerResults results = provider.CompileAssemblyFromSource(compilerParams, source);
//اگر خطایی وجود داشته باشد نمایش داده خواهد شد
Console.WriteLine("Number of Errors: {0}", results.Errors.Count);
foreach (CompilerError err in results.Errors)
{
Console.WriteLine("ERROR {0}", err.ErrorText);
}
}
static void Main(string[] args)
{
compileIt1();
Console.WriteLine("Press a key...");
Console.ReadKey();
}
}
}
کد مورد نظر را به صورت یک فایل dll کامپایل کنید.
برای این منظور تمامی مراحل مانند قبل است فقط GenerateExecutable ذکر شده به false تنظیم شده و نام خروجی نیز به foo.dll باید تنظیم شود.
مثال 3:
کد مورد نظر را در حافظه کامپایل کرده (خروجی dll یا exe نمیخواهیم)، سپس متد SayHello آن را به صورت پویا فراخوانی نموده و خروجی را نمایش دهید.
در این حالت روش کار همانند مثال 1 است با این تفاوت که GenerateInMemory = true و GenerateExecutable = false تنظیم میشوند. همچنین جهت دسترسی به متد کلاس ذکر شده، از قابلیتهای ریفلکشن موجود در دات نت فریم ورک استفاده خواهد شد.
using System;
using System.Collections.Generic;
using Microsoft.CSharp;
using System.CodeDom.Compiler;
using System.Reflection;
namespace compilerTest
{
class Program
{
static void compileIt2()
{
//سورس کد ما جهت کامپایل
string source =
@"
namespace Foo
{
public class Bar
{
static void Main(string[] args)
{
Bar.SayHello();
}
public static void SayHello()
{
System.Console.WriteLine(""Hello World"");
}
}
}
";
//تعیین نگارش کامپایلر مورد استفاده
Dictionary<string, string> providerOptions = new Dictionary<string, string>
{
{"CompilerVersion", "v3.5"}
};
//تعیین اینکه کد ما سی شارپ است
CSharpCodeProvider provider = new CSharpCodeProvider(providerOptions);
//نحوه تعیین مشخص سازی کامپایل در حافظه
CompilerParameters compilerParams = new CompilerParameters
{
GenerateInMemory = true,
GenerateExecutable = false
};
//عملیات کامپایل در اینجا صورت میگیرد
CompilerResults results = provider.CompileAssemblyFromSource(compilerParams, source);
// اگر خطایی در کامپایل وجود نداشت متد دلخواه را فراخوانی میکنیم
if (results.Errors.Count == 0)
{
//استفاده از ریفلکشن برای دسترسی به متد و فراخوانی آن
Type type = results.CompiledAssembly.GetType("Foo.Bar");
MethodInfo method = type.GetMethod("SayHello");
method.Invoke(null, null);
}
}
static void Main(string[] args)
{
compileIt2();
Console.WriteLine("Press a key...");
Console.ReadKey();
}
}
}
مثال:
اگر رشته سورس ما به صورت زیر بوده و از اسمبلی System.Drawing.Dll نیز کمک گرفته باشد،
string source =
@"
namespace Foo
{
public class Bar
{
static void Main(string[] args)
{
Bar.SayHello();
}
public static void SayHello()
{
System.Console.WriteLine(""Hello World"");
var r = new System.Drawing.Rectangle(0,0,100,100);
System.Console.WriteLine(r);
}
}
}
";
Number of Errors: 1
ERROR The type or namespace name 'Drawing' does not exist in the namespace 'System' (are you missing an assembly reference?)
برای رفع این مشکل و معرفی این اسمبلی، سطر زیر باید پس از تعریف compilerParams اضافه شود.
compilerParams.ReferencedAssemblies.Add("System.Drawing.Dll");
نمونهای دیگر از این دست، استفاده از LINQ میباشد. در این حالت اسمبلی System.Core.Dll نیز به روش ذکر شده باید معرفی گردد تا مشکلی در کامپایل کد رخ ندهد.
کاربردها:
1- استفاده در ابزارهای تولید کد (برای مثال در برنامه Linqer از این قابلیت استفاده میشود)
2- استفادههای امنیتی (ایجاد روشهای تولید یک سریال به صورت پویا و کامپایل پویای کد مربوطه در حافظهای محافظت شده)
3- استفاده جهت مقاصد محاسباتی پیشرفته
4- دادن اجازهی کد نویسی به کاربران برنامهی خود (شبیه به سیستمهای ماکرو و اسکریپت نویسی موجود)
و ...