در نگارشهای پیشین ASP.NET MVC، یک چنین مسائلی را با معرفی Child Actionها
public partial class SidebarMenuController : Controller { const int Min15 = 900; [ChildActionOnly] [OutputCache(Duration = Min15)] public virtual ActionResult Index() { return PartialView("_SidebarMenu"); } }
یک مثال: تهیهی اولین View Component
ساختار یک View Component، بسیار شبیه است به ساختار یک Controller، اما با عملکردی محدود. به همین جهت کار تعریف آن با افزودن یک کلاس سیشارپ شروع میشود و این کلاس را میتوان در پوشهای به نام ViewComponents در ریشهی پروژه قرار داد (اختیاری).
سپس برای نمونه، کلاس ذیل را به این پوشه اضافه کنید:
using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using Core1RtmEmptyTest.Services; namespace Core1RtmEmptyTest.ViewComponents { public class SiteCopyright : ViewComponent { private readonly IMessagesService _messagesService; public SiteCopyright(IMessagesService messagesService) { _messagesService = messagesService; } public IViewComponentResult Invoke(int numberToTake) { var name = _messagesService.GetSiteName(); return View(viewName: "Default", model: name); } //public async Task<IViewComponentResult> InvokeAsync(int numberToTake) //{ // return View(); //} } }
ساختار کلی یک کلاس ViewComponent شامل دو جزء اصلی است:
الف) از کلاس پایه ViewComponent مشتق میشود. به این ترتیب توسط ASP.NET Core قابل شناسایی خواهد شد.
ب) دارای متد Invoke ایی است که بجای Html.RenderAction در نگارشهای پیشین ASP.NET MVC، قابل فراخوانی است. این متد یک View را باز میگرداند.
ج) در اینجا امکان تعریف نمونهی Async متد Invoke نیز وجود دارد (برای مثال جهت کار با متدهای Async بانک اطلاعاتی).
روش فراخوانی این متدها نیز به این صورت است: ابتدا به دنبال نمونهی async میگردد. اگر یافت شد، همینجا کار خاتمه مییابد. اگر یافت نشد، نمونهی sync یا معمولی آن فراخوانی میشود و اگر این هم یافت نشد، یک استثناء صادر خواهد شد.
د) متد Invoke میتواند دارای پارامترهای دلخواهی نیز باشد و حالت پیش فرض آن بدون پارامتر است.
روش یافتن یک view component توسط ASP.NET Core به این صورت است:
الف) این کلاس باید عمومی بوده و همچنین abstract نباشد.
ب) «یکی» از مشخصههای ذیل را داشته باشد:
1) نامش به ViewComponent ختم شده باشد.
2) از کلاس ViewComponent ارث بری کرده باشد.
3) با ویژگی ViewComponent مزین شده باشد.
نحوه و محل تعریف View یک View Component
پس از تعریف کلاس ViewComponent مورد نظر، اکنون نیاز است View آنرا اضافه کرد. روش یافتن این Viewها توسط ASP.NET Core نیز بر این مبنا است که
الف) اگر این View Component عمومی و سراسری است، باید درون پوشهی shared، پوشهی جدیدی را به نام Components ایجاد کرده و سپس ذیل این پوشه، بر اساس نام کلاس ViewComponent، یک زیر پوشهی دیگر را ایجاد و داخل آن، View مدنظر را اضافه کرد (تصویر ذیل).
/Views/Shared/Components/[NameOfComponent]/Default.cshtml
/Views/[CurrentController]/Components/[NameOfComponent]/Default.cshtml
یک نکته: اگر نام کلاسی به ViewComponent ختم شده بود، نیازی نیست تا ViewComponent را هم در حین ساخت پوشهی آن ذکر کرد.
نحوهی استفادهی از View Component تعریف شده و ارسال پارامتر به آن
و در آخر برای استفادهی از این View Component تعریف شده، به فایل layout برنامه مراجعه کرده و آنرا به نحو ذیل فراخوانی کنید:
<footer> <p>@await Component.InvokeAsync("SiteCopyright", new { numberToTake = 5 })</p> </footer>
یک نکته: متدهای قدیمی Component.Invoke و Component.Renderدر اینجا حذف شدهاند (اگر مقالات پیش از RTM را مطالعه کردید) و روش توصیه شدهی در اینجا، کار با متدهای async است.
تفاوتهای View Components با Child Actions نگارشهای پیشین ASP.NET MVC
پارامترهای یک View Component از طریق یک HTTP Request تامین نمیشوند و همانطور که ملاحظه کردید در همان زمان فراخوانی آنها به صورت مستقیم فراهم خواهند شد. بنابراین مباحث model binding در اینجا دیگر وجود خارجی ندارند. همچنین View Components جزئی از طول عمر یک کنترلر نیستند. بنابراین اکشن فیلترهای مختلف تعریف شده، تاثیری را بر روی آنها نخواهند داشت (این مشکلی بود که با Child Actions در نگارشهای قبلی مشاهده میشد). همچنین View Components به صورت مستقیم از طریق درخواستهای HTTP قابل دسترسی نیستند. به علاوه Child actions قدیمی، از فراخوانیهای async پشتیبانی نمیکنند.
زمانیکه کلاسی از کلاس پایه ViewComponent ارث بری میکند، تنها به این خواص عمومی از درخواست HTTP جاری دسترسی خواهد داشت:
[ViewComponent] public abstract class ViewComponent { protected ViewComponent(); public HttpContext HttpContext { get; } public ModelStateDictionary ModelState { get; } public HttpRequest Request { get; } public RouteData RouteData { get; } public IUrlHelper Url { get; set; } public IPrincipal User { get; } [Dynamic] public dynamic ViewBag { get; } [ViewComponentContext] public ViewComponentContext ViewComponentContext { get; set; } public ViewContext ViewContext { get; } public ViewDataDictionary ViewData { get; } public ICompositeViewEngine ViewEngine { get; set; } //... }
فراخوانی Ajax ایی یک View Component
در ASP.NET Core، یک اکشن متد میتواند خروجی ViewComponent نیز داشته باشد و این تنها روشی است که میتوان یک View Component را از طریق درخواستهای HTTP، مستقیما قابل دسترسی کرد:
public IActionResult AddURLTest() { return ViewComponent("AddURL"); }
$(document).ready (function(){ $("#LoadSignIn").click(function(){ $('#UserControl').load("/Home/AddURLTest"); }); });
امکان بارگذاری View Components از اسمبلیهای دیگر نیز وجود دارد
در مطلب «ارتقاء به ASP.NET Core 1.0 - قسمت 10 - بررسی تغییرات Viewها» روش دسترسی به Viewهای برنامه را که در اسمبلی آن قرار گرفته بودند، بررسی کردیم. دقیقا همان روش در مورد view components نیز صادق است و کاربرد دارد. جهت یادآوری، این مراحل باید طی شوند:
الف) اسمبلی ثالث حاوی View Componentهای برنامه باید ارجاعاتی را به ASP.NET Core و قابلیتهای Razor آن داشته باشد:
"dependencies": { "NETStandard.Library": "1.6.0", "Microsoft.AspNetCore.Mvc": "1.0.0", "Microsoft.AspNetCore.Razor.Tools": { "version": "1.0.0-preview2-final", "type": "build" } }, "tools": { "Microsoft.AspNetCore.Razor.Tools": "1.0.0-preview2-final" }
"buildOptions": { "embed": "Views/**/*.cshtml" }
public void ConfigureServices(IServiceCollection services) { services.AddMvc(); //Get a reference to the assembly that contains the view components var assembly = typeof(ViewComponentLibrary.ViewComponents.SimpleViewComponent).GetTypeInfo().Assembly; //Create an EmbeddedFileProvider for that assembly var embeddedFileProvider = new EmbeddedFileProvider(assembly,"ViewComponentLibrary"); //Add the file provider to the Razor view engine services.Configure<RazorViewEngineOptions>(options => { options.FileProviders.Add(embeddedFileProvider); });
[ViewComponent(Name = "ViewComponentLibrary.Simple")] public class SimpleViewComponent : ViewComponent
@await Component.InvokeAsync("ViewComponentLibrary.Simple", new { number = 5 })
In this section, I’m going to cover how you can use SignalR outside of a Hub. In most asp.net core applications, you will likely want to communicate with the connect clients from within your application but outside of a Hub. You can accomplish this by using the HubContext.
For example, an ASP.NET Core MVC Controller or any other class that is instantiated by ASP.NET Core’s Dependency Injection.
The HubContext allows you to send messages to your connected clients. It has many of the same features to communicate with clients as when you are inside of a Hub.
public class HomeViewModel { public string Id { get; set; } public string Message { get; set; } public DateTime DateTime { get; set; } }
اکنون به پوشهی Views بروید و فایل Index.cshtml را به این صورت تغییر دهید:
@model AspNetCoreDependencyInjection.Models.HomeViewModel @{ ViewData["Title"] = "Home"; } <div> <div> <div> <p> <b>Id : </b><span>@Model.Id</span> <br /> <b>Date And Time : </b><span> @Model.DateTime </span> <br/> <b>Message : </b><span>@Model.Message</span> </p> </div> </div> </div>
using AspNetCoreDependencyInjection.Services; namespace AspNetCoreDependencyInjection.ServicesImplentaions { public class MessageServiceAA { public string Message() { return "A message from MessageServiceAA"; } } }
namespace AspNetCoreDependencyInjection.Helpers { public class GuidProvider { private readonly Guid _serviceGuid; public GuidProvider() { _serviceGuid = Guid.NewGuid(); } public Guid GetNewGuid() => Guid.NewGuid(); public string GetGuidAsFormatedString(string prefix = "") => getFormatedGuid(_serviceGuid, prefix); private string getFormatedGuid(Guid guid, string prefix = "") { var guidString = guid.GetHashCode().ToString("x"); if (string.IsNullOrEmpty(prefix) == false) guidString = new StringBuilder($"{prefix}-").Append(guidString).ToString(); return guidString; } } }
حالا درون کنترل HomeController، این تغییرات را انجام میدهیم:
private readonly ILogger<HomeController> _logger; private readonly MessageServiceAA _messageService; private readonly GuidProvider _ guidProvider; public HomeController(ILogger<HomeController> logger) { _logger = logger; _messageService = new MessageServiceAA(); _guidProvider = new GuidProvider(); } public IActionResult Index() { var model = new HomeViewModel() { Id = _ guidProvider.GetGuidAsFormatedString(), Message = _messageService.Message(), DateTime = DateTime.Now, }; return View(model); }
همانطور که میبینید، در کد بالا، کنترلر HomeController، به دو شیء از کلاسها و یا سرویسهای GuidProvider و MessageServiceAA به صورت مستقیم وابسته شدهاست و با هر تغییری در هر کدام از این سرویسها، باید دوباره کامپایل شود. علاوه بر این اگر بخواهیم پیاده سازیهای مختلفی را برای هر کدام از این موارد، ارائه دهیم، به مشکل بر میخوریم. خب بیاید تغییراتی را در کد بالا بدهیم تا مشکلات ذکر شده را حل کنیم.
برای این منظور پوشهای را به نام Services میسازیم و اینترفیسی را
به نام IMessageBrokerA ایجاد میکنیم و سپس کاری میکنیم که MessageServiceAA از این
اینترفیس ارث بری کند:
namespace AspNetCoreDependencyInjection.Services { public interface IMessageServiceA { string Message(); } }
و حالا میخواهیم با
استفاده از تزریق وابستگی، وابستگی کنترلر HomeController را از کلاس MessageBrokerAA لغو کرده و آن را به اینترفیس IMessageBrokerA (انتزاع) وابسته کنیم. در
اینجا ما از تکنیک تزریق درون سازنده یا Constructor Injection استفاده میکنیم.
تزریق درون سازنده
در این تکنیک، ما لیستی از وابستگیهای مورد نیاز را به عنوان پارامترهای ورودی سازندهی کلاس، تعریف میکنیم:private readonly ILogger<HomeController> _logger; private readonly IMessageServiceA _messageService; private readonly GuidProvider _guidHelper; public HomeController(ILogger<HomeController> logger , IMessageServiceA messageService) { _logger = logger; _messageService = messageService; _messageService = new MessageServiceAA(); _guidHelper = new GuidProvider(); }
- IServiceCollection : برای ثبت سرویسها
- IServiceProvider : برای واکشی سرویسها
در ASP.NET Core معمولترین مکان برای ثبت کردن سرویسها درون Container، به صورت پیش فرض درون کلاس Startup و درون متد ConfigureServices انجام میگیرد.
به صورت پیش فرض کلاس Startup دو متد دارد:
- ConfigureServices : برای پیکربندی و ثبت سرویسهای درونی DI Container استفاده میشود.
- Configure : برای تنظیمات pipeline میان افزارها ( Middlewares ) بکار میرود.
در اینجا پیاده سازی پیش فرض کلاس Startup را میبینیم که البته کدهای درون متد Configure را برای درگیر نکردن ذهن شما، مخفی کردهایم:
public class Startup { public Startup(IConfiguration configuration) { Configuration = configuration; } public IConfiguration Configuration { get; } // This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { services.AddControllersWithViews(); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { // کدها جهت خوانایی بیشتر مخفی شده اند } }
همانطورکه میبینید، متد ConfigureService پارامتر IServiceCollection را میگیرد که به وسیلهی WebHost در زمان اجرای برنامه، مقدار دهی میشود.
تعداد زیادی Extension method برای IServiceCollection وجود دارند که برای پشتیبانی از ثبت کردن سرویسهای مختلف در سناریوهای گوناگون به کار میروند. در اینجا ما از نسخهی 3.1 چارچوب ASP.NET Core استفاده میکنیم. برای همین هم برای ثبت سرویسهای پیش فرض فریمورک MVC از متد توسعهی services.AddControllersWithViews() استفاده میکنیم. متد توسعهی AddControllersWithViews() سرویسهایی را که معمولا در فریم ورک MVC استفاده میشوند، درون IServiceCollection ثبت میکند. در نسخههای قبلی چارچوب ASP.NET Core، مانند نسخههای 2.1 و 2.2 برای این کار از متد توسعهی AddMvc() استفاده میشد.
در Microsoft Dependency Injection Container ، معمولا ترتیب ثبت سرویسها مهم نیست.
خب، اولین سرویس اختصاصی برنامهی خودمان را با چرخهی حیات Transient و زیر سرویس پیشین، به شکل زیر ثبت میکنیم :
public void ConfigureServices(IServiceCollection services) { services.AddControllersWithViews(); services.AddTransient<IMessageServiceA, MessageServiceAA>(); }
public static IServiceCollection AddTransient<TService, TImplementation>(this IServiceCollection services)
در اینجا وقتی ما برای IMessageServiceA ، پیاده سازی MessageServiceA را ثبت میکنیم، از این به بعد DI Container، هر زمانیکه در لیست پارامترهای سازندهی یک کلاس، IMessageServiceA را مشاهده کند، بررسی میکند که چه کلاسی به عنوانی پیاده سازی این اینترفیس ثبت شدهاست، سپس از آن نمونه سازی میکند و درون سازندهی مورد نظر تزریق میکند. خب، حالا برنامه را دوباره اجرا کنید؛ میبینید که برنامه اجرا میشود.
پیشنیازهای کار با EF Core Migrations
در قسمت قبل در حین بررسی «برپایی تنظیمات اولیهی EF Core 1.0 در یک برنامهی ASP.NET Core 1.0»، چهار مدخل جدید را به فایل project.json برنامه اضافه کردیم. مدخل جدید Microsoft.EntityFrameworkCore.Tools که به قسمت tools آن اضافه شد، پیشنیاز اصلی کار با EF Core Migrations است.
بررسی ابزارهای خط فرمان EF Core و تشکیل ساختار بانک اطلاعاتی بر اساس کلاسهای برنامه
پس از تکمیل پیشنیازهای کار با EF Core، از طریق خط فرمان به پوشهی جاری پروژه وارد شده و دستور dotnet ef را صادر کنید.
یک نکته: در ویندوز اگر در پوشهای، کلید shift را نگه دارید و در آن پوشه کلیک راست کنید، در منوی باز شده، گزینهی جدیدی را به نام Open command window here مشاهده خواهید کرد که میتواند به سرعت خط فرمان را از پوشهی جاری شروع کند.
خروجی صدور فرمان dotnet ef را در ذیل مشاهده میکنید:
D:\Prog\1395\Core1RtmEmptyTest\src\Core1RtmEmptyTest>dotnet ef _/\__ ---==/ \\ ___ ___ |. \|\ | __|| __| | ) \\\ | _| | _| \_/ | //|\\ |___||_| / \\\/\\ Entity Framework .NET Core CLI Commands 1.0.0-preview2-21431 Usage: dotnet ef [options] [command] Options: -h|--help Show help information -v|--verbose Enable verbose output --version Show version information --assembly <ASSEMBLY> The assembly file to load. --startup-assembly <ASSEMBLY> The assembly file containing the startup class. --data-dir <DIR> The folder used as the data directory (defaults to current working directory). --project-dir <DIR> The folder used as the project directory (defaults to current working directory). --content-root-path <DIR> The folder used as the content root path for the application (defaults to application base directory). --root-namespace <NAMESPACE> The root namespace of the target project (defaults to the project assembly name). Commands: database Commands to manage your database dbcontext Commands to manage your DbContext types migrations Commands to manage your migrations Use "dotnet ef [command] --help" for more information about a command.
D:\Prog\1395\Core1RtmEmptyTest\src\Core1RtmEmptyTest>dotnet ef migrations add InitialDatabase
نام دلخواه InitialDatabase را در انتهای نام فایل 13950526050417_InitialDatabase مشاهده میکنید.
اگر قصد حذف این مرحله را داشته باشیم، میتوان دستور dotnet ef migrations remove را مجددا صادر کرد.
فایل 13950526050417_InitialDatabase به همراه کلاسی است که در آن دو متد Up و Down قابل مشاهده هستند. متد Up نحوهی ایجاد جدول جدیدی را از کلاس Person بیان میکند و متد Down نحوهی Drop این جدول را پیاده سازی کردهاست.
فایل ApplicationDbContextModelSnapshot.cs دارای کلاسی است که خلاصهای از تعاریف موجودیتهای ذکر شدهی در DB Context برنامه را به همراه دارد و تفسیر آنها را از دیدگاه EF در اینجا میتوان مشاهده کرد.
پس از مرحلهی افزودن migrations، نوبت به اعمال آن به بانک اطلاعاتی است. تا اینجا EF تنها متدهای Up و Down مربوط به ساخت و حذف ساختار جداول را ایجاد کردهاست. اما هنوز آنها را به بانک اطلاعاتی برنامه اعمال نکردهاست. برای اینکار در پوشهی جاری دستور ذیل را صادر کنید:
D:\Prog\1395\Core1RtmEmptyTest\src\Core1RtmEmptyTest>dotnet ef database update Applying migration '13950526050417_InitialDatabase'. Done.
اکنون اگر به لیست بانکهای اطلاعاتی مراجعه کنیم، بانک اطلاعاتی جدید TestDbCore2016 را به همراه جدول متناظر کلاس Person میتوان مشاهده کرد:
در اینجا جدول دیگری به نام __EFMigrationsHistory نیز قابل مشاهدهاست که کار آن ذخیره سازی وضعیت فعلی Migrations در بانک اطلاعاتی، جهت مقایسههای آتی است. این جدول صرفا توسط ابزارهای EF استفاده میشود و نباید به صورت مستقیم تغییری در آن ایجاد کنید.
مقدار دهی اولیهی جداول بانکهای اطلاعاتی در EF Core
در همین حالت اگر کنترلر TestDB مطرح شدهی در انتهای بحث قسمت قبل را اجرا کنیم، به این استثناء خواهیم رسید:
این تصویر بدین معنا است که کار Migrations موفقیت آمیز بودهاست و اینبار امکان اتصال و کار با بانک اطلاعاتی وجود دارد، اما این جدول حاوی اطلاعات اولیهای برای نمایش نیست.
در نگارش قبلی EF Code First، امکانات Migrations به همراه یک متد Seed نیز بود که توسط آن کار مقدار دهی اولیهی جداول را میتوان انجام داد (زمانیکه جدولی ایجاد میشود، در همان هنگام، چند رکورد خاص نیز به آن اضافه شوند. برای مثال به جدول کاربران، رکورد اولین کاربر یا همان Admin اضافه شود). این متد در EF Core 1.0 وجود ندارد.
برای این منظور کلاس جدیدی را به نام ApplicationDbContextSeedData به همان پوشهی جدید Migrations اضافه کنید؛ با این محتوا:
using System.Collections.Generic; using System.Linq; using Core1RtmEmptyTest.Entities; using Microsoft.Extensions.DependencyInjection; namespace Core1RtmEmptyTest.Migrations { public static class ApplicationDbContextSeedData { public static void SeedData(this IServiceScopeFactory scopeFactory) { using (var serviceScope = scopeFactory.CreateScope()) { var context = serviceScope.ServiceProvider.GetService<ApplicationDbContext>(); if (!context.Persons.Any()) { var persons = new List<Person> { new Person { FirstName = "Admin", LastName = "User" } }; context.AddRange(persons); context.SaveChanges(); } } } } }
public void Configure(IServiceScopeFactory scopeFactory) { scopeFactory.SeedData();
public void ConfigureServices(IServiceCollection services) { services.AddDbContext<ApplicationDbContext>(ServiceLifetime.Scoped);
- برای پیاده سازی الگوی واحد کار، اولین قدم، مشخص سازی طول عمر Db Context برنامه است. برای اینکه تنها یک Context در طول یک درخواست وهله سازی شود، نیاز است به نحو صریحی طول عمر آنرا به حالت Scoped تنظیم کرد. متد AddDbContext دارای پارامتری است که این طول عمر را دریافت میکند. بنابراین در اینجا ServiceLifetime.Scoped ذکر شدهاست. همچنین در این مثال از نمونهای که IConfigurationRoot به سازندهی کلاس ApplicationDbContext تزریق شده (نکتهی انتهای بحث قسمت قبل)، استفاده شدهاست. به همین جهت تنظیمات options آنرا ملاحظه نمیکنید.
- مرحلهی بعد نحوهی دسترسی به این سرویس ثبت شده در یک کلاس static دارای متدی الحاقی است. در اینجا دیگر دسترسی مستقیمی به تزریق وابستگیها نداریم و باید کار را با IServiceScopeFactory شروع کنیم. در اینجا میتوانیم به صورت دستی یک Scope را ایجاد کرده، سپس توسط ServiceProvider آن، به سرویس ApplicationDbContext دسترسی پیدا کنیم و در ادامه از آن به نحو متداولی استفاده نمائیم. IServiceScopeFactory جزو سرویسهای توکار ASP.NET Core است و در صورت ذکر آن به عنوان پارامتر جدیدی در متد Configure، به صورت خودکار وهله سازی شده و در اختیار ما قرار میگیرد.
- نکتهی مهمی که در اینجا بکار رفتهاست، ایجاد Scope و dispose خودکار آن توسط عبارت using است. باید دقت داشت که ایجاد Scope و تخریب آن به صورت خودکار در ابتدا و انتهای درخواستها توسط ASP.NET Core انجام میشود. اما چون شروع کار ما از متد Configure است، در اینجا خارج از Scope قرار داریم و باید مدیریت ایجاد و تخریب آنرا به صورت دستی انجام دهیم که نمونهای از آنرا در متد SeedData کلاس ApplicationDbContextSeedData ملاحظه میکنید. در اینجا Scope ایی ایجاد شدهاست. سپس دادههای اولیهی مدنظر به بانک اطلاعاتی اضافه گردیده و در آخر این Scope تخریب شدهاست.
- اگر کار ایجاد و تخریب scope، به نحوی که مشخص شدهاست انجام نگیرد، طول عمر درخواستی خارج از Scope، همواره Singleton خواهد بود. چون خارج از طول عمر درخواست جاری قرار داریم و هنوز کار به سرویس دهی درخواستها نرسیدهاست. بنابراین مدیریت Scopeها هنوز شروع نشدهاست و باید به صورت دستی انجام شود.
در این حالت اگر برنامه را اجرا کنیم، این خروجی قابل مشاهده است:
که به معنای کار کردن متد SeedData و ثبت اطلاعات اولیهای در بانک اطلاعاتی است.
اعمال تغییرات به مدلهای برنامه و به روز رسانی ساختار بانک اطلاعاتی
فرض کنید به کلاس Person قسمت قبل، خاصیت Age را هم اضافه کردهایم:
namespace Core1RtmEmptyTest.Entities { public class Person { public int PersonId { get; set; } public string FirstName { get; set; } public string LastName { get; set; } public int Age { get; set; } } }
An unhandled exception occurred while processing the request. SqlException: Invalid column name 'Age'.
D:\Prog\1395\Core1RtmEmptyTest\src\Core1RtmEmptyTest>dotnet ef migrations add v2 D:\Prog\1395\Core1RtmEmptyTest\src\Core1RtmEmptyTest>dotnet ef database update
با اجرا این دستورات، فایل جدید 13950526073248_v2 به پوشهی Migrations اضافه میشود. این فایل حاوی نحوهی به روز رسانی بانک اطلاعاتی، بر اساس خاصیت جدید Age است. سپس با اجرای دستور dotnet ef database update، کار به روز رسانی بانک اطلاعاتی بر اساس مرحلهی v2 انجام میشود.
بنابراین هر بار که تغییری را در مدلهای خود ایجاد میکنید، یکبار باید کلاس مهاجرت آنرا ایجاد کنید و سپس آنرا به بانک اطلاعاتی اعمال نمائید.
تهیه اسکریپت تغییرات بجای اعمال تغییرات توسط ابزارهای EF
شاید علاقمند باشید که پیش از اعمال تغییرات به بانک اطلاعاتی، یک اسکریپت SQL از آن تهیه کنید (جهت مطالعه و یا اعمال دستی آن توسط خودتان). برای اینکار میتوانید دستور ذیل را در پوشهی جاری پروژه اجرا کنید:
D:\Prog\1395\Core1RtmEmptyTest\src\Core1RtmEmptyTest>dotnet ef migrations script -o v2.sql
تغییرات ساختار جدول __EFMigrationsHistory در EF Core 1.0
در EF 6.x، ساختار اطلاعات جدول نگهداری تاریخچهی تغییرات، بسیار پیچیده بود و شامل رشتهای gzip شدهی حاوی یک snapshot از کل ساختار دیتابیس در هر مرحلهی migration بود. در این نگارش، این snapshot حذف شدهاست و بجای آن فایل ApplicationDbContextModelSnapshot.cs را مشاهده میکنید (تنها یک snapshot به ازای کل context برنامه). همچنین در اینجا کاملا مشخص است که چه مراحلی به بانک اطلاعاتی اعمال شدهاند و دیگر خبری از رشتهی gzip شدهی قبلی نیست (تصویر فوق).
در شکل زیر ساختار قبلی این جدول را در EF 6.x مشاهده میکنید. در EF 6.x حتی فضای نام کلاسهای موجودیتهای برنامه هم مهم هستند و در صورت تغییر، مشکل ایجاد میشود:
مهاجرت خودکار از EF Core حذف شدهاست
در EF 6.x در کنار کلاس Db Context یک کلاس Configuration هم وجود داشت که برای مثال امکان چنین تعریفی در آن میسر هست:
public Configuration() { AutomaticMigrationsEnabled = true; }
با حذف مهاجرت خودکار:
- دیگر نیازی نیست تا model snapshots در بانک اطلاعاتی ذخیره شوند (همان ساده شدن ساختار جدول ذخیره سازی تاریخچهی مهاجرتهای فوق).
- در حالت افزودن یک مرحلهی مهاجرت، دیگر نیازی به کوئری گرفتن از بانک اطلاعاتی نخواهد بود (سرعت بیشتر).
- میتوان چندین مرحلهی مهاجرت را افزود بدون اینکه الزاما مجبور به اعمال آنها به بانک اطلاعاتی باشیم.
- کاهش کدهای مدیریت ساختار بانک اطلاعاتی.
- تیمها برای یکی کردن تغییرات خود مشکلی نخواهند داشت چون دیگر snapshot مدلها در جدول __EFMigrationsHistory ذخیره نمیشود.
بنابراین در EF Core میتوان مهاجرت v1 را اضافه کرد. سپس تغییراتی را در کدها اعمال کرد. در ادامه مهاجرت v2 را تولید کرد و در آخر کار اعمال یکجای اینها را به بانک اطلاعاتی انجام داد.
هرچند در اینجا اگر میخواهید مرحلهی اجرای دستور dotnet ef database update را حذف کنید، میتوانید از کدهای ذیل نیز استفاده نمائید:
using Core1RtmEmptyTest.Entities; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; namespace Core1RtmEmptyTest.Migrations { public static class DbInitialization { public static void Initialize(this IServiceScopeFactory scopeFactory) { using (var serviceScope = scopeFactory.CreateScope()) { var context = serviceScope.ServiceProvider.GetService<ApplicationDbContext>(); // Applies any pending migrations for the context to the database. // Will create the database if it does not already exist. context.Database.Migrate(); } } } }
کار متد Migrate، ایجاد بانک اطلاعاتی در صورت عدم وجود و سپس اعمال تمام مراحل migration ایی است که در جدول __EFMigrationsHistory ثبت نشدهاند (دقیقا همان کار دستور dotnet ef database update را انجام میدهد).
تفاوت متد Database.EnsureCreated با متد Database.Migrate
اگر به متدهای context.Database دقت کنید، یکی از آنها EnsureCreated نام دارد. این متد نیز سبب تولید بانک اطلاعاتی بر اساس ساختار Context برنامه میشود. اما هدف آن صرفا استفادهی از آن در آزمونهای واحد سریع است. از این جهت که جدول __EFMigrationsHistory را تولید نمیکند (برخلاف متد Migrate). بنابراین بجز آزمونهای واحد، در جای دیگری از آن استفاده نکنید چون به دلیل عدم تولید جدول __EFMigrationsHistory توسط آن، قابلیت استفادهی از بانک اطلاعاتی تولید شدهی توسط آن با امکانات migrations وجود ندارد. در پایان آزمون واحد نیز میتوان از متد EnsureDeleted برای حذف این بانک اطلاعاتی موقتی استفاده کرد.
در قسمت بعد، مطالب تکمیلی مهاجرتها را بررسی خواهیم کرد. برای مثال چگونه میتوان کلاسهای موجودیتها را به اسمبلیهای دیگری منتقل کرد.
یکی از تغییرات مهم قالب ایجاد پروژههای ASP.NET Core 3.0، تغییر فایل program.cs آن است که در آن از یک Generic Host بجای روش قبلی Web Host، استفاده شدهاست. علت آن فراهم آوردن امکان استفادهی از قابلیتهایی مانند تزریق وابستگیها، logging، تنظیمات برنامه و غیره، در برنامههای غیر وب نیز میباشد. یکی از این انواع برنامهها، سرویسهای پسزمینهی غیر HTTP هستند. به این ترتیب میتوان برنامهای شبیه به یک برنامهی وب ASP.NET Core را ایجاد کرد که تنها کارش اجرای سرویسهای غیر وبی است؛ اما به تمام امکانات و زیر ساختهای ASP.NET Core دسترسی دارد.
برای ایجاد این نوع برنامهها در NET Core 3x. میتوانید دستور زیر را در پوشهی خالی که ایجاد کردهاید، اجرا کنید:
dotnet new worker
appsettings.Development.json
appsettings.json
MyWorkerServiceApp.csproj
Program.cs
Worker.cs
- فایل csproj آن دارای این محتوا است:
<Project Sdk="Microsoft.NET.Sdk.Worker"> <PropertyGroup> <TargetFramework>netcoreapp3.0</TargetFramework> <UserSecretsId>dotnet-MyWorkerServiceApp-B76DB08E-FFBB-4AD1-89B5-93BF483D1BD0</UserSecretsId> </PropertyGroup> <ItemGroup> <PackageReference Include="Microsoft.Extensions.Hosting" Version="3.0.0-preview8.19405.4" /> </ItemGroup> </Project>
- محتوای فایل Program.cs آن بسیار آشنا است و دقیقا کپی همان فایلی است که در برنامههای ASP.NET Core 3x حضور دارد:
namespace MyWorkerServiceApp { public class Program { public static void Main(string[] args) { CreateHostBuilder(args).Build().Run(); } public static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args) .ConfigureServices((hostContext, services) => { services.AddHostedService<Worker>(); }); } }
در متد ConfigureServices، انواع اقسام سرویسها را منجمله یک HostedService که در مطلب جاری به آن پرداخته شده، میتوان افزود. سرویس Worker ای که در اینجا به آن ارجاعی وجود دارد، به صورت زیر تعریف شدهاست:
public class Worker : BackgroundService { private readonly ILogger<Worker> _logger; public Worker(ILogger<Worker> logger) { _logger = logger; } protected override async Task ExecuteAsync(CancellationToken stoppingToken) { while (!stoppingToken.IsCancellationRequested) { _logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now); await Task.Delay(1000, stoppingToken); } } }
یک نکتهی تکمیلی: روش تبدیل کردن یک BackgroundService به یک Windows Service
اگر برنامهی NET Core. شما در ویندوز اجرا میشود، میتوانید این برنامهی BackgroundService را به یک سرویس ویندوز NT نیز تبدیل کنید. برای اینکار ابتدا بستهی نیوگت Microsoft.Extensions.Hosting.WindowsServices را به پروژه اضافه کنید. سپس جائیکه CreateHostBuilder صورت میگیرد، متد UseWindowsService را فراخوانی کنید:
public static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args) .UseWindowsService() .ConfigureServices((hostContext, services) => { //services.AddHttpClient(); services.AddHostedService<Worker>(); });
cs create WorkerServiceDemo binPath=C:\Path\To\WorkerServiceDemo.exe
public static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args) .UseSystemd() .ConfigureServices((hostContext, services) => { services.AddHostedService<Worker>(); });
public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseHttpsRedirection(); app.UseStaticFiles(); JqGridRequest.ParametersNames = new JqGridParametersNames() { PagesCount = "npage" }; app.UseRouting(); app.UseAuthorization(); app.UseEndpoints(endpoints => { endpoints.MapControllerRoute( name: "default", pattern: "{controller=JavaScript}/{action=Basics}/{id?}"); endpoints.MapControllerRoute( name: "default2", pattern: "{controller=StarWars}/{action=Characters}"); endpoints.MapRazorPages(); }); }
یعنی تبدیل رشتهی JSON به شیء متناظر با آن و یا حالت عکس آن گویا به صورت صحیح انجام نمیشه احتمالا باید پروژه Lib.AspNetCore.Mvc.JqGrid بر حسب ASP.NET Core 3.0 کامپایل بشه
در اینجاهم یه کامنت در مورد تغییرات JSON serialization/deserialization گذاشتند
در ASP.NET Core 2.0، دو روش جدید برای ثبت Loggerهای مختلف ارائه شدهاست:
روش اول: معرفی Loggers در ConfigureServices فایل آغازین برنامه
public void ConfigureServices(IServiceCollection services) { services.AddLogging(builder => builder.AddConsole().AddDebug()); services.AddMvc(); }
روش دوم: معرفی Loggers در فایل Program.cs
public static IWebHost BuildWebHost(string[] args) => WebHost.CreateDefaultBuilder(args) .UseStartup<Startup>() .ConfigureLogging((hostingContext, logging) => { logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging")); logging.AddConsole(); logging.AddDebug(); }) .Build();
{ "Logging": { "IncludeScopes": false, "Console": { "LogLevel": { "Default": "Warning" } } } }
کار با Visual Studio
در این مقاله، یکسری توضیحاتی در مورد ویژگیهای کلیدی ویژوال استودیو به برنامه نویسهای (توسعه دهندههای) پروژههای Asp.net Core MVC ارائه میدهیم.
ایجاد یک پروژه
در ابتدا یک پروژهی وب جدید Asp.net core را به نام Working و بر اساس قالب Empty ایجاد میکنیم. سپس در کلاس startup، قابلیت MVC را فعال میکنیم (کدهای این قسمت، در فصل 5 کامل شرح داده شدهاست)
namespace Working { public class Startup { // This method gets called by the runtime. Use this method to add services to the container. // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940 public void ConfigureServices(IServiceCollection services) { services.AddMvc(); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IHostingEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } //app.Run(async (context) => //{ // await context.Response.WriteAsync("Hello World!"); //}); } } }
ایجاد مدل:
یک پوشه جدید را به نام Models ایجاد میکنیم و بعد در این پوشه یک کلاس جدید را به نام Product ایجاد میکنیم و کدهای زیر را در کلاس ایجاد شده قرار میدهیم (این قسمت در فصل 5 نیز شرح داده شدهاست):
namespace Working.Models { public class Product { public string Name { get; set; } public decimal Price { get; set; } } }
namespace WorkingWithVisualStudio.Models { public class SimpleRepository { private static SimpleRepository sharedRepository = new SimpleRepository(); private Dictionary<string, Product> products = new Dictionary<string, Product>(); public static SimpleRepository SharedRepository => sharedRepository; public SimpleRepository() { var initialItems = new[] { new Product { Name = "Kayak", Price = 275M }, new Product { Name = "Lifejacket", Price = 48.95M }, new Product { Name = "Soccer ball", Price = 19.50M }, new Product { Name = "Corner flag", Price = 34.95M } }; foreach (var p in initialItems) { AddProduct(p); } } public IEnumerable<Product> Products => products.Values; public void AddProduct(Product p) => products.Add(p.Name, p); } }
نکته: من یک مشخصه (Property) استاتیک را به نام SharedRepository تعریف کردم که دسترسی به SimpleRepository را فراهم میکند و میتواند در طول برنامه از آن استفاده شود. این بهترین کار نیست، ولی میخواهم یک مشکل رایج را که در توسعه MVC روبرو میشوید، نشان دهم. من راه بهتری را برای کار با اجزای مشترک، در فصل 18 توضیح میدهم.
ایجاد Controller و View
در پوشه Controllers، یک فایل جدید را به نام HomeController.cs ایجاد میکنیم و کدهای زیر را در آن قرار میدهیم:
namespace WorkingWithVisualStudio.Controllers { public class HomeController : Controller { public IActionResult Index() => View(SimpleRepository.SharedRepository.Products); } }
@{ Layout = null; } <!DOCTYPE html> <html> <head> <meta name="viewport" content="width=device-width" /> <title>>Working with Visual Studio</title> </head> <body> <table> <thead> <tr> <td>Name</td> <td>Price</td> </tr> </thead> <tbody> @foreach (var p in Model) {<tr> <td>@p.Name</td> <td>@p.Price</td> </tr>} </tbody> </table> </body> </html>
دو نوع مختلف از بستههای نرم افزاری مورد نیاز برای Asp.Net Core MVC وجود دارند.
معرفی NuGet
ویژوال استودیو به همراه یک ابزار گرافیکی برای مدیریت بستههای NET. است که در یک پروژه گنجانده شدهاست. برای باز کردن این ابزار، گزینه Management NuGet Packages for Solution را از منوی Tools ➤ NuGet Package Manager انتخاب کنید. به این ترتیب ابزار NuGet باز میشود و لیستی از بستههایی که قبلا نصب شدهاند، نمایش داده میشود؛ همانطور که در شکل زیر نشان داده شدهاست:
برگهی Installed، خلاصهای از بستههایی را که قبلا در پروژه نصب شدهاند، نشان میدهد. از برگهی Browse، برای یافتن و نصب بستههای جدید میتوان استفاده کرد و برگهی Updates، فهرست package هایی را که نسخههای اخیر آنها منتشر شدهاند، نمایش میدهد.
معرفی بستهی MICROSOFT.ASPNETCORE.ALL
اگر شما از نسخههای قبلی Asp.Net Core استفاده کرده باشید، باید یک لیست طولانی از بستههای NuGet را به پروژه جدید خود اضافه نمایید. Asp.Net Core2 یک بستهی متفاوت را به نام Microsoft.AspNetCore.All معرفی میکند.
معرفی بستههای Nuget و موقعیت ذخیره سازی آنها
ابزار NuGet لیست بستههای پروژه را در فایل projectname.csproj نگهداری میکند. در اینجا <projectname> با نام پروژه جایگزین میشود. برای مثال در پروژه فوق اطلاعات Nuget، در فایل WorkingWithVisualStudio.csproj ذخیره میشوند. ویژوال استودیو محتویات فایل csproj را در پنجرهی Solution Explorer نمایش نمیدهد. برای ویرایش این فایل، روی پروژه در پنجرهی Solution Explorer راست کلیک کنید و گزینهی Edit WorkWithVisualStudio.csproj را از منوی باز شده، انتخاب کنید. ویژوال استودیو فایل را برای ویرایش باز میکند. فایل csproj یک فایل XML است و شما در آن عنصری را مانند قطعه کد زیر در آن میبینید که Asp.net Core Meta package را به پروژه اضافه میکند:
<ItemGroup> <PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.0" /> </ItemGroup>
معرفی Bower
یک بسته Client-Side، شامل محتوایی است که به مشتری ارسال میشود؛ مانند فایلهای جاوا اسکریپت، Css Stylesheets و یا تصاویر. از Nuget برای مدیریت این نوع فایلها در پروژه نیز استفاده میشود. اما اکنون Asp.Net Core MVC پشتیبانی توکاری را از یک ابزار مدیریت بستههای سمت کاربر، به نام Bower نیز ارائه میدهد. Bower یک ابزار منبع باز ( Open Source ) است که در خارج از مایکروسافت و دنیای NET. توسعه داده شده و نگهداری میشود.
نکته: Bower به تازگی منسوخ شده اعلام گردیدهاست. ممکن است هشدارهایی را که ابزارهای جایگزین را پیشنهاد میکنند نیز مشاهده کنید. با این حال پشتیبانی از Bower با ویژوال استودیو یکپارچه شدهاست و در نگارش 2.1 ابزار مدیریت سمت کلاینت جدید دیگری را نیز بجای آن معرفی کردهاند.
معرفی لیست بستههای Bower
بستههای Bower از طریق فایل ویژهی bower.json مشخص میشوند. برای ایجاد این فایل در پنجره Solution Explorer روی پروژه WorkingWithVisualStudio راست کلیک کنید و Add -> New Item را از منوی باز شده انتخاب کنید. سپس قالب مورد نظر Bower Configuration File را از Asp.net Core -> Web -> General Category انتخاب نمائید؛ مانند تصویر زیر:
ویژوال استودیو نام bower.json را برای آن قرار میدهد. برروی ok کلیک میکنیم و یک فایل جدید، با محتویات پیشفرض زیر به پروژه اضافه میشود:
{ "name": "asp.net", "private": true, "dependencies": {} }
نکته: منبع بستههای Bower در لینک http://bower.io/search وجود دارد. شما میتوانید بستهها مورنظر را در اینجا جستجو و به پروژه اضافه کنید.
بعد از اینکه بستهها نصب شدند، محتویات فایل bower.json به صورت زیر میباشد:
{ "name": "asp.net", "private": true, "resolutions": { "jquery": "3.3.1" } }
فرمت | توضیحات |
3.3.7 | بیان شماره مستقیم بسته نصب شده و تطبیق دقیق آن با شمار نسخه ، e.g ، 3.3.7 |
* | با استفاده از یک ستاره به Bower اجازه نصب آخرین نسخه را میدهد |
3.3.7 =<3.3.7< | پیشوند یک شماره نسخه با < یا =< به Bower اجازه میدهد تا هر نسخه از بستهای که بزرگتر یا بزرگتر مساوی آن نسخهی معین است، نصب شود |
3.3.7 =>3.3.7> | پیشوند یک شماره نسخه با > یا => به Bower اجازه میدهد تا هر نسخه از بستهای را که کوچکتر یا کوچکتر و مساوی نسخهی معین است، نصب شود |
3.3.7~ | پیشوند یک شماره نسخه با یک tilde (با کاراکتر ~ ) به نسخههایی که دو شماره
اولیه آنها مشابه باشند، اجازه نصب میدهد؛ حتی اگر شماره آخر آن نسخه متفاوت
باشد. مانند نسخههای 3.3.9 و 3.3.8 و اجازه نصب نسخه 3.4.0 را نمیدهد؛ چون
شماره دوم آن متفاوت است. |
3.3.7^ | پیشوند یک شماره نسخه با یک قلم (کاراکتر ^) به نسخههایی که شماره اول آنها مشابه باشند، اجازه نصب میدهد؛ حتی اگر شماره دوم آنها متفاوت باشد. مانند نسخههای 3.3.1 و 3.4.1 و 3.5.1 اما نسخه 4.0.0 اجازه نصب ندارد |
مانند Nuget نیز Bower وابستگیهای مرتبط با بستههای اضافه شدهی به یک پروژه را مدیریت میکند. BootStrap برای دسترسی به برخی از ویژگیهای پیشرفته، به JQuery که یک کتابخانهی جاوا اسکریپتی است، تکیه میکند. به همین دلیل است که دو بسته را در شکل فوق نشان داده است. شما میتوانید لیست بستهها و وابستگیهای آنها را به صورت باز شده در بخش مورد نظر در Solution Explorer مشاهده کنید.
در ادامه کتاب، من از نسخه قبلی Bootstrap CSS framework استفاده میکنم. هنگامی که دارم این را مینویسم، تیم Bootstrap در حال توسعهی نسخهی 4 bootStrap است و چندین بار منتشر شدهاست. این نسخهها به عنوان "آلفا" برچسب گذاری شدهاند، اما کیفیت آنها بالا است و برای استفاده در نمونههای این کتاب به اندازه کافی پایدار است. با توجه به انتخاب نوشتن این کتاب با استفاده از Bootstrap 3 و نسخه پیش از نسخه بوت استرپ 4 و به زودی بایگانی شدن آن، تصمیم گرفتم از نسخه جدید استفاده کنم؛ حتی اگر برخی از نامهای کلاسها که برای شیوه نامههای عناصر HTML استفاده میشوند، احتمالا قبل از انتشار نهایی تغییر یابند. این مورد به این معنا است که شما باید همان نسخه از Bootstrap را که برای گرفتن نتایج موردنظر از خروجی نیاز دارید، استفاده کنید.
برای به روزرسانی بسته Bootstrap، شماره نسخه را در فایل bower.json تغییر دهید. مانند کد زیر:
{ "name": "asp.net", "private": true, "dependencies": { "bootstrap": "4.0.0-alpha.6" } }
توسعه نرم افزار وب اغلب میتواند یک فرآیند تکراری باشد، جایی که تغییرات کوچکی را به ویووها یا کلاسها میدهید و برنامه را اجرا میکنید تا اثرات آن را آزمایش کنید. MVC و ویژوال استودیو همکاری میکنند تا از این رویکرد مداوم استفاده کنند تا تغییرات را سریعتر و آسانتر ببینید.
اعمال تغییرات در Razor Views
در زمان توسعه، تغییراتی که به Razor View اعمال میشوند، به محض رسیدن درخواستهای HTTP، از مرورگر دریافت میشوند. برای اینکه ببینید چطور کار میکند، برنامه را با انتخاب گزینه Start Debugging از منوی Debug اجرا کنید و هنگامیکه یک برگهی مرورگر باز شد و اطلاعات نمایش داده شد، تغییراتی را که در زیر نمایش میدهم در فایل Index.cshtml اعمال کنید.
@model IEnumerable<WorkingWithVisualStudio.Models.Product> @{ Layout = null; } <!DOCTYPE html> <html> <head> <meta name="viewport" content="width=device-width" /> <title>>Working with Visual Studio</title> </head> <body> <h3>Products</h3> <table> <thead> <tr> <td>Name</td> <td>Price</td> </tr> </thead> <tbody> @foreach (var p in Model) { <tr> <td>@p.Name</td> <td>@($"{p.Price:C2}")</td> </tr>} </tbody> </table> </body> </html>
اعمال تغییرات در کلاسهای #C
برای کلاسهای #C، از جمله کنترلرها و مدلها، دو رویکرد موجود را که از طریق آیتمهای مختلف در منوی Debug انتخاب میشوند، شرح میدهم:
Start Without Debugging
تغییرات در کلاسها در پروژه به صورت خودکار زمانیکه یک درخواست HTTP دریافت میشود، برای مشاهدهی یک تجربهی توسعهی پویا، کامپایل میشوند. در این حالت برنامه بدون امکانات دیباگ و اشکالزادیی اجرا میشود.
Start Debugging
به شما اجزا میدهد صریح تغییرات را کامپایل کنید و برنامه را اجرا کنید ، بررسی مشکلات هم در زمان اجرا پروژه انجام میگیرد.به شما اجرا بررسی و تجزیه و تحلیل هر گونه مشکل در کد را میدهد.
کامپایل خودکار کلاس ها
در طول توسعه عادی، این چرخه کامپایل سریع به شما اجازه میدهد تا فورا تاثیر تغییرات خود را ببینید؛ حالا میتواند این تغییر اضافه نمودن یک اکشن جدید و یا ویرایش نمایش اطلاعات یک Model باشد. برای ارائهی این نوع از توسعه، ویژوال استودیو به محض رسیدن درخواست HTTP از مرورگر، تغییرات را دریافت و کلاسها را به صورت خودکار کامپایل میکند. برای دیدن اینکه چگونه کار میکند، گزینه Start Without Debugging را از منوی Debug در ویژوال استودیو انتخاب کنید. هنگامیکه مرورگر دادههای برنامه را نمایش میدهد، تغییرات زیر را در فایل Home controller ایجاد کنید:
namespace WorkingWithVisualStudio.Controllers { public class HomeController : Controller { public IActionResult Index() => View(SimpleRepository.SharedRepository.Products .Where(p => p.Price < 50)); } }
namespace WorkingWithVisualStudio.Models { public class SimpleRepository { private static SimpleRepository sharedRepository = new SimpleRepository(); private Dictionary<string, Product> products = new Dictionary<string, Product>(); public static SimpleRepository SharedRepository => sharedRepository; public SimpleRepository() { var initialItems = new[] { new Product { Name = "Kayak", Price = 275M }, new Product { Name = "Lifejacket", Price = 48.95M }, new Product { Name = "Soccer ball", Price = 19.50M }, new Product { Name = "Corner flag", Price = 34.95M } }; foreach (var p in initialItems) { AddProduct(p); } products.Add("Error", null); } public IEnumerable<Product> Products => products.Values; public void AddProduct(Product p) => products.Add(p.Name, p); } }
namespace WorkingWithVisualStudio { public class Startup { // This method gets called by the runtime. Use this method to add services to the container. // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940 public void ConfigureServices(IServiceCollection services) { services.AddMvc(); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IHostingEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); app.UseDeveloperExceptionPage(); } } } }
استفاده از Debugger
ویژوال استادیو از اجرای یک برنامه MVC با استفاده از Debugger نیز پشتیبانی میکند که اجازه میدهد برنامه برای بررسی وضعیت نرم افزار و دنبال کردن درخواستی که به برنامه ارسال میشود، متوقف و از این طریق، پیگیری شود. این مورد نیاز به یک سبک متفاوت از توسعه را دارد. زیرا تغییراتی را در کلاسهای #C میدهیم، تا زمانیکه برنامه مجددا راه اندازی نشود، اعمال نمیشوند ( هرچند تغییرات Razor View هنوز هم به صورت خودکار اعمال میشوند). این سبک توسعه به همراه استفادهی از ویژگی کامپایل خودکار نیست؛ اما Debugger ویژوال استودیو عالی است و میتواند بینش عمیقتری را در مورد نحوهی کارکرد برنامه داشته باشد. برای اجرای برنامه با استفاده Debugger، در ویژوال استودیو از منوی Debug گزینهی Start Debugging را انتخاب کنید. ویژوال استودیو کلاسهای #C در پروژه را قبل از اجرای برنامه کامپایل میکند. اما شما همچنان میتوانید با استفاده از موارد موجود در منوی Build، کد خود را به صورت دستی نیز کامپایل کنید.
Debugger عامل اصلی خطا را نمایش نمیدهد؛ تنها مکان آنرا آشکار میکند. عبارتیکه ویژوال استودیو برجسته میکند نشان میدهد که این مشکل زمانی رخ میدهد که فیلتر کردن اشیاء با استفاده از LINQ انجام شود، اما یک کار کوچک لازم است تا از جزئیات کاسته شود و به علت اصلی برسد.
Breakpoint عبارتی است که به Debugger میگوید تا برنامه را متوقف کند و کنترل دستی برنامه را به برنامه نویس میدهد. شما میتوانید وضعیت برنامه را بازبینی کنید و ببینید چه اتفاقی میافتد و به صورت اختیاری روند کاری را دوباره ادامه دهید.
برای ایجاد Breakpoint، روی عبارت راست کلیک کنید و در منوی باز شده، گزینه Breakpoint -> Insert Breakpoint را انتخاب کنید.
به عنوان مثال: یک Breakpoint به خط کد AddProduct در کلاس SimpleRepository اعمال کنید. همانطور که در شکل زیر نمایش داده میشود:
برنامه را اجرا کنید؛ با استفاده از Debug -> Start Debugging و یا با استفاده از Debug -> Restart برنامه را Restart میکنیم. در طی درخواست اولیه HTTP، برنامه اجرا میشود تا به نقطهای که Break Point دارد برسد و در آنجا برنامه متوقف میشود. در این نقطه، شما میتوانید از آیتمهای منوی Debug ویژوال استودیو یا کنترلها در بالای پنجره، برای کنترل اجرای برنامه استفاده کنید؛ یا از نمایشهای مختلف Debugger موجود از طریق Debug -> Windows برای بررسی وضعیت برنامه استفاده میکنیم.
اگر اشارهگر ماوس را بر روی پارامتر p به متد AddProduct که توسط Debugger برجسته شدهاست، حرکت دهید، یک فرم ظاهر خواهد شد که ارزش فعلی p را نشان میدهد؛ همانطور که در شکل زیر نشان داده شدهاست. من یک نمونه بزرگ شده از محتویات فرم ظاهر شده را نمایش میدهم تا به راحتی بتوانید متن در آن را بخوانید.
این مورد ممکن است مؤثر به نظر نرسد، چون شیء داده در یک سازنده همانند BreakPoint تعریف شدهاست. اما این ویژگیها برای هر متغیری کار میکند. شما میتوانید مقادیر را مشاهده کنید تا مقادیر خود و فیلد آنها را ببینید. هر مقدار دارای یک دکمه پین کوچک به سمت راست است. برای زمانیکه کد در حال اجراست، برای نظارت بر مقدار، از آن استفاده کنید.
اشارهگر ماوس را بر روی متغیر P قرار دهید و مرجع محصول را پین کنید. مرجع پیوست شده را باز کنید تا بتوانید نام و قیمت را نیز ببینید؛ مانند شکل زیر:
گزینه Continue را از منوی Debug در ویژوال استادیو انتخاب کنید تا برنامه ادامه پیدا کند. از آنجا که در برنامه حلقه Foreach وجود دارد، برنامه که دوباره اجرا میشود، وقتی مجددا به BreakPoint رسید، برنامه متوقف میشود. مقادیر پین شده در شکل زیر نشان میدهند که چگونه متغیر P و خواص آن تغییر میکنند.
استفاده از پنجره متغیرهای محلی ( Local Windows )
یکی از ویژگیهای مرتبط، پنجره Locals است که با انتخاب گزینهی منوی Debug ➤ Windows ➤ Locals باز میشود. پنجرهی Locals، مقدار متغیرها را به شکلی مشابه پنل پین شده نمایش میدهد، اما در اینجا تمام اشیاء محلی را نسبت به Break Point نمایش میدهد؛ همانطور که در شکل زیر نشان داده شدهاست:
هربار که Continue را انتخاب میکنید، اجرای برنامه ادامه یافته و یک شیء دیگر توسط حلقه foreach پردازش میشود.
اگر ادامه دهید، در زمان ویرایش کد، در هر دو پنجره Locals و در مقادیر پنل پین شده، شما مرجع Null را میبینید. برای کنترل اجرای برنامه، میتوانید جریان را از طریق کد خود در دیباگر دنبال کنید و احساس کنید که چه اتفاقی میافتد.
استفاده از Browser Link
ویژگی Browser Link میتواند روند توسعه را با قرار دادن یک یا چند مرورگر تحت کنترل ویژوال استودیو، ساده سازی کند. این ویژگی مخصوصا مفید است اگر شما نیاز به دیدن اثر تغییرات را در طیف وسیعی از مرورگرها دارید. قابلیت Browser Link با و یا بدون Debugger کار میکند و به این معنا است که میتوانیم هر فایلی را در پروژه تغییر دهیم و تاثیر تغییر را بدون نیاز به تغییری در مرورگر مشاهده کنیم.
راه اندازی BrowserLink
برای فعال کردن Browser Link باید در کلاس Startup، تنظیمات را تغییر دهید. مانند کد زیر:
namespace WorkingWithVisualStudio { public class Startup { // This method gets called by the runtime. Use this method to add services to the container. // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940 public void ConfigureServices(IServiceCollection services) { services.AddMvc(); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IHostingEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); app.UseBrowserLink(); app.UseDeveloperExceptionPage(); } app.Run(async (context) => { await context.Response.WriteAsync("Hello World!"); }); } } }
استفاده از Browser Link
برای درک اینکه Browser Link چگونه کار میکند، در ویژوال استودیو گزینه Start Without Debugging را از منوی Debug انتخاب میکنیم. ویژوال استودیو برنامه را اجرا میکند و یک برگه جدید مرورگر را برای نمایش نتیجه باز میکند. با بازبینی HTML ارسال شده به مرورگر، شما خواهید دید که حاوی بخش دیگری مانند این است:
<!DOCTYPE html> <html> <head> <meta name="viewport" content="width=device-width" /> <title>>Working with Visual Studio</title> </head> <body> <h3>Products</h3> <table> <thead> <tr><td>Name</td><td>Price</td></tr> </thead> <tbody> <tr><td>Lifejacket</td><td>£48.95</td></tr> <tr><td>Soccer ball</td><td>£19.50</td></tr> <tr><td>Corner flag</td><td>£34.95</td></tr> </tbody> </table> <!-- Visual Studio Browser Link --> <script type="application/json" id="__browserLink_initializationData"> {"requestId":"968949d8affc47c4a9c6326de21dfa03","requestMappingFromServer":false} </script> <script type="text/javascript" src="http://localhost:55356/d1a038413c804e178ef009a3be07b262/browserLink" async="async"></script> <!-- End Browser Link --> </body> </html>
ویژوال استادیو یک جفت عناصر اسکریپت را به HTML فرستاده شدهی به مرورگر اضافه میکند که برای بازکردن یک اتصال طولانی مدت HTTP با سرور برنامه کاربردی است؛ تا زمانیکه ویژوال استودیو مجددا برنامه را ریاستارت کند. کد زیر تغییر در فایل Index و تاثیر استفاده از Browser Link را نشان میدهد.
@model IEnumerable<WorkingWithVisualStudio.Models.Product> @{ Layout = null; } <!DOCTYPE html> <html> <head> <meta name="viewport" content="width=device-width" /> <title>>Working with Visual Studio</title> </head> <body> <h3>Products</h3> <p>Request Time: @DateTime.Now.ToString("HH:mm:ss")</p> <table> <thead> <tr> <td>Name</td> <td>Price</td> </tr> </thead> <tbody> @foreach (var p in Model) { <tr> <td>@p.Name</td> <td>@($"{p.Price:C2}")</td> </tr>} </tbody> </table> </body> </html>
کد جاوا اسکریپتی که در HTML ارسال شده به مرورگر جاسازی شده، صحفه را دوباره بارگذاری میکند؛ برای دیدن تاثیرات کد اضافه شده که اضافه کردن یک timestamp ساده است.
نکته: عناصر اسکریپت Browser Link فقط در پاسخهای موفق جاسازی شده است. به این معنا که اگر یک خطا هنگام کامپایل در هنگام اجرا کردن یک Razor View یا مدیریت یک درخواست ایجاد شود، اتصال بین مرورگر و ویژوال استودیو از بین میرود و شما بعد از حل مشکل باید صفحه را مجدد بارگذاری کنید.
استفاده از مرورگرهای متعدد
Browser Link میتواند برای نمایش یک برنامه در مرورگرهای متعددی به طور همزمان استفاده شود و میتواند زمانی مفید باشد که شما میخواهید تفاوتهای پیاده سازی را بین مرورگرهای مختلف کنترل کنید و یا ببینید که چگونه یک برنامه بر روی ترکیبی از مرورگرهای دسکتاپ و تلفن همراه ارائه میشود.
برای انتخاب مرورگرهایی که استفاده میشوند، مرورگر را با استفاده از دکمه IIS Express در نوار ابزار ویژوال استودیو، انتخاب کنید؛ همانطور که در شکل زیر نشان داده شده است.
ویژوال استادیو معمولا مرورگرهای رایجی را که نصب میشوند، نمایش میدهد. اما شما میتوانید با استفاده از دکمهی Add، برای اضافه کردن مرورگری که به صورت خودکار لیست نشده نیز استفاده کنید. همچنین میتوانید ابزار تست شخص ثالث مانند Browser Stack را نیز راه اندازی کنید که مرورگرها را بر روی سرویسهای ابری میزبان ( cloud-hosted ) و ماشینهای مجازی اجرا میکند.
من سه مرورگر را در شکل انتخاب کردم: Chrome ، Internet Explorer و Edge. با کلیک بر روی دکمه Browse، فعالیت هر سه مرورگر شروع میشود و باعث میشود URL مثال برنامه را بارگذاری کند؛ همانطور که در شکل نشان داده شده است.
با استفاده از منوی Browser Link Dashboard، شما میتوانید ببینید که چه مرورگرهایی در Browser Link انتخاب شدهاند. داشبورد آن نشانی اینترنتی نمایش داده شده توسط هر مرورگر را نشان میدهد و در اینجا هر مرورگر را میتوان به صورت جداگانه رفرش کرد.
آماده سازی جاوا اسکریپت و CSS برای استقرار
هنگامی که Client-Side بخشی از یک برنامه وب را ایجاد میکنید، معمولا تعدادی از فایلهای جاوا اسکریپت و CSS سفارشی را تهیه میکنید که برای تکمیل آنها، از بستههای نصب شدهی توسط Bower استفاده میشود. این فایلها نیاز به پردازش دارند تا آنها را برای تحویل در یک محیط تولید، بهینه سازی کنند تا تعداد درخواستهای HTTP و میزان پهنای باند شبکه مورد نیاز برای ارسال آنها به مشتری، به حداقل برسد. این فرآیند به عنوان بسته بندی شناخته میشود.
فعال کردن تحویل محتوای استاتیک
ASP.Net Core شامل پشتیبانی از ارائه فایلهای استاتیک از پوشه wwwroot به مشتریان است. اما این امکان به صورت پیشفرض در زمان ایجاد یک پروژهی خالی جدید فعال نیست و شما باید با قرار دادن عبارتی در فایل StartUp آن را فعال کنید؛ مانند کد زیر:
namespace WorkingWithVisualStudio { public class Startup { // This method gets called by the runtime. Use this method to add services to the container. // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940 public void ConfigureServices(IServiceCollection services) { services.AddMvc(); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IHostingEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); app.UseBrowserLink(); app.UseStaticFiles(); app.UseDeveloperExceptionPage(); } app.Run(async (context) => { await context.Response.WriteAsync("Hello World!"); }); } } }
اضافه کردن محتوای استاتیک به پروژه
برای نشان دادن فرآیند بسته بندی، من نیاز به اضافه کردن تعدادی محتوای استاتیک به پروژه و یکی کردن آنها با برنامهی نمونه را دارم. برای این منظور ابتدا یک پوشهی جدید را به نام wwwroot/css ایجاد کنید که محل متداولی برای فایلهای سفارشی CSS است. من فایلی را به نام First.css با استفاده از قالب آیتم Style Sheet اضافه کردم؛ همانطور که در شکل زیر نشان داده شده است. قالب Style Sheet در مسیر Asp.Net Core -> Web -> Content Section وجود دارد.
فایل First.Css را ویرایش کنید و محتوای زیر را در آن قرار دهید.
h3 { } table, td { border: 2px solid black; border-collapse: collapse; padding: 5px; }
فایلهای جاوا اسکریپت معمولا در پوشه wwwroot/js قرار میگیرند. من این پوشه را ایجاد کردم. فایلهای جاوا اسکریپت را میتوانید در مسیر Asp.Net Core -> Web -> Script انتخاب کنید. همانطور که در شکل زیر نشان داده شده است.
من کد جاوا اسکریپتی ساده زیر را به این فایل جدید اضافه کردم؛ همانطور که در لیست نشان داده شده است.
document.addEventListener("DOMContentLoaded", function () { var element = document.createElement("p"); element.textContent = "This is the element from the third.js file"; document.querySelector("body").appendChild(element); });
من به بیش از یک فایل جاوا اسکریپت نیاز دارم. بنابراین فایل دیگری را به نام fourth.js نیز در پوشه wwwroot ایجاد میکنم و محتوای زیر را در آن قرار میدهم.
document.addEventListener("DOMContentLoaded", function () { var element = document.createElement("p"); element.textContent = "This is the element from the fourth.js file"; document.querySelector("body").appendChild(element); });
به روز رسانی View
گام نهایی، به روز رسانی فایل Index.cshtml برای استفاده از Css و فایل جاوا اسکریپت است. کدهای آن در زیر نشان داده شده است:
@model IEnumerable<WorkingWithVisualStudio.Models.Product> @{ Layout = null; } <!DOCTYPE html> <html> <head> <meta name="viewport" content="width=device-width" /> <title>>Working with Visual Studio</title> <link rel="stylesheet" href="css/first.css" /> <link rel="stylesheet" href="css/second.css" /> <script src="js/third.js"></script> <script src="js/fourth.js"></script> </head> <body> <h3>Products</h3> <p>Request Time: @DateTime.Now.ToString("HH:mm:ss")</p> <table> <thead> <tr> <td>Name</td> <td>Price</td> </tr> </thead> <tbody> @foreach (var p in Model) { <tr> <td>@p.Name</td> <td>@($"{p.Price:C2}")</td> </tr>} </tbody> </table> </body> </html>
یکی کردن فایلهای سمت کلاینت در برنامههای MVC
در حال حاضر چهار فایل استاتیک وجود دارند و مرورگر باید چهار درخواست را برای دریافت فایلهای استاتیک ایجاد کند و هر یک از این فایلها نیازمند پهنای باند بیشتری است که باید به مشتری تحویل داده شود؛ زیرا آنها حاوی فضای سفید و نام متغیرها هستند که برای توسعه دهندهها معنا دار هستند؛ اما برای مرورگرها اهمیتی ندارند.
ترکیب فایلهایی هم نوع، تلفیق نامیده میشود و در آن کار ساختن فایلها به صورتی کوچکتر انجام میشود. هر دوی این کارها در برنامه Asp.Net Core MVC توسط Bundler & Minifier مخصوص ویژوال استودیو انجام میشود.
نصب افزونههای ویژوال استودیو
دسته بندی و یکی کردن فایلها
پس از نصب افزونه، ویژوال استودیو را مجددا راه اندازی کنید و پروژه نمونه را باز کنید. با افزودن افزونه، میتوانید چندین فایل هم نوع را در Solution Explorer انتخاب کنید. آنها را با یکدیگر ترکیب کرده و محتویات آنها را کوچکتر کنید. به عنوان مثال فایلهای First.css و Second.css را در Solution Explorer را انتخاب و کلیک راست کرده و سپس Bundler & Minifier -> Bundle and Minify Files را از منوی باز شده انتخاب کنید . همانطور که در شکل زیر نشان داده شده است.
فایل خروجی را با عنوان bundle.css ذخیره کنید. در Solution Explorer یک بسته جدید ایجاد میشود. اگر شما این فایل را باز کنید، خواهید دید که محتویات هر دو فایل CSS جداگانه ترکیب شدهاند و تمام فضای سفید آنها حذف شدهاست. البته شما نمیخواهید به طور مستقیم با این فایل کار کنید؛ اما این فایل کوچکتر است و فقط یک اتصال HTTP را برای ارائه CSS styles به مشتری نیاز دارد.
مراحل قبل را برای فایلهای third.js و fourth.js تکرار کنید تا فایلهای جدید bundle.js و bundle.min.js در پوشه wwwroot ایجاد شوند.
احتیاط: اطمینان حاصل کنید که فایلها را به ترتیبی که توسط مرورگر بارگیری میشوند، انتخاب کنید تا ترتیب دستورات Styleها یا دستورات کد را در فایلهای خروجی حفظ کنید. به عنوان مثال دقت کنید که فایل third.js قبل از فایل fourth.js انتخاب شده باشد تا مطمئن باشید دستورات به ترتیب و به درستی اجرا میشوند.
@model IEnumerable<WorkingWithVisualStudio.Models.Product> @{ Layout = null; } <!DOCTYPE html> <html> <head> <meta name="viewport" content="width=device-width" /> <title>>Working with Visual Studio</title> <link rel="stylesheet" href="css/bundle.min.css" /> <script src="js/bundle.min.js"></script> </head> <body> <h3>Products</h3> <p>Request Time: @DateTime.Now.ToString("HH:mm:ss")</p> <table> <thead> <tr> <td>Name</td> <td>Price</td> </tr> </thead> <tbody> @foreach (var p in Model) { <tr> <td>@p.Name</td> <td>@($"{p.Price:C2}")</td> </tr>} </tbody> </table> </body> </html>
همان زمان که عملیات جمع آوری و یکی کردن را انجام میدهید، رکورد عملیات انجام شده را در فایلی به نام bundleconfig.json در پوشهی wwwroot پروژه نگهداری میکند. در اینجا یک نمونه از فایل تولیدی را مشاهده میکنید:
[ { "outputFileName": "Views/wwwroot/css/bundle.css", "inputFiles": [ "Views/wwwroot/css/First.css", "Views/wwwroot/css/second.css" ] }, { "outputFileName": "Views/wwwroot/js/bundle.js", "inputFiles": [ "Views/wwwroot/js/fourth.js", "Views/wwwroot/js/third.js" ] } ]
خلاصه
در این بخش من توضیحاتی را در مورد ویژگیهایی که ویژوال استودیو برای طراحی برنامههای وب به توسعه دهندهها ارائه میدهد، شرح دادم که شامل کامپایل خودکار کلاسها، Browser Link و یکی کردن فایلهای سمت کلاینت ( bundling and minification ) بود.
برای ارتقاء به نگارش RC0، این مراحل را باید طی کنید:
1) پیش از هر کاری، پوشهی node_modules قدیمی خود را حذف کنید (با تمام محتوای آن).
2) به روز رسانی فایل package.json به صورت ذیل:
{ "name": "asp-net-mvc5x-angular2x", "version": "1.0.0", "author": "DNT", "description": "", "scripts": { "postinstall": "typings install" }, "license": "Apache-2.0", "dependencies": { "@angular/common": "^2.0.0-rc.0", "@angular/compiler": "^2.0.0-rc.0", "@angular/core": "^2.0.0-rc.0", "@angular/http": "2.0.0-rc.0", "@angular/router": "2.0.0-rc.0", "@angular/router-deprecated": "^2.0.0-rc.0", "@angular/platform-browser": "^2.0.0-rc.0", "@angular/platform-browser-dynamic": "^2.0.0-rc.0", "bootstrap": "^3.3.6", "es6-promise": "^3.1.2", "es6-shim": "^0.35.0", "jquery": "^2.2.3", "reflect-metadata": "^0.1.3", "rxjs": "^5.0.0-beta.6", "systemjs": "^0.19.27", "zone.js": "^0.6.12" }, "devDependencies": { "typescript": "^1.8.9", "typings": "^0.8.1" }, "repository": { } }
3) افزودن فایلی به نام typings.json در ریشهی پروژه؛ با این محتوا:
{ "ambientDependencies": { "es6-shim": "github:DefinitelyTyped/DefinitelyTyped/es6-shim/es6-shim.d.ts#7de6c3dd94feaeb21f20054b9f30d5dabc5efabd" } }
es6-shim.d.ts
بدون این فایل، کامپایلر TypeScript تعاریف ES 6 را مانند Map و Promise و امثال آن، نمیشناسد و پروژه را کامپایل نخواهد کرد.
اکنون یکبار فایل package.json را ذخیره کنید تا کار به روز رسانی بستهها انجام شود. البته اگر بر روی این فایل کلیک راست کنید، در منوی ظاهر شده، گزینهی restore packages هم موجود است.
4) پس از آن، چند تغییر جزئی ذیل باید در کدهای این سری، اعمال شوند:
هر جایی angular2 تعریف شده، اینبار میشود angular@. مثلا:
import { PipeTransform, Pipe } from '@angular/core';
import { RouteParams, Router } from '@angular/router-deprecated';
/// <reference path="../typings/es6-shim.d.ts" /> import { bootstrap } from '@angular/platform-browser-dynamic'; // Our main component import { AppComponent } from "./app.component"; bootstrap(AppComponent);
یک نکته: اگر میخواهید این تعاریف را در یک فایل razor، وارد کنید، چون @ به ابتدای پوشهی angular2 اضافه شده (node_modules\@angular)، مشکل پردازشی razor را ایجاد خواهد کرد و باید escape شود. به همین جهت بجای @ بهتر است معادل آن را یعنی ("@")Html.Raw@ وارد کنید.
سپس ابتدا فایل systemjs.config.js را از اینجا دریافت کنید.
در ادامه مداخل جدید را هم در فایل index.html مثال رسمی آغازین آن بررسی کنید.
بنابراین، فایل systemjs.config.js را به ریشهی سایت اضافه کنید (از این جهت که مسیر بستههای node_modules را از ریشهی سایت میخواند). سپس فایل Views\Shared\_Layout.cshtml را به نحو ذیل تغییر دهید:
<!DOCTYPE html> <html> <head> <base href="/"> <meta charset="utf-8"/> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>@ViewBag.Title - My ASP.NET Application</title> <link href="~/node_modules/bootstrap/dist/css/bootstrap.css" rel="stylesheet"/> <link href="~/app/app.component.css" rel="stylesheet"/> <link href="~/Content/Site.css" rel="stylesheet" type="text/css"/> <!-- 1. Load libraries --> <!-- IE required polyfills, in this exact order --> <script src="~/node_modules/es6-shim/es6-shim.min.js"></script> <script src="~/node_modules/zone.js/dist/zone.js"></script> <script src="~/node_modules/reflect-metadata/Reflect.js"></script> <script src="~/node_modules/systemjs/dist/system.src.js"></script> <script src="~/systemjs.config.js"></script> <!-- 2. Configure SystemJS --> <script> System.import('app/main').then(null, console.error.bind(console)); </script> </head> <body> <div> @RenderBody() <pm-app>Loading App...</pm-app> </div> @RenderSection("Scripts", required: false) </body> </html>
خلاصهی سریع این موارد
الف) تغییرات آخرین بستههای npm را از مستندات آن پیگیری و اعمال کنید. آخرین نگارش آن همیشه در اینجا قابل دسترسی است.
ب) تغییرات index.html، فایل main.ts و مداخل آغازین آنرا از مثال آغازین رسمی آن پیگیری و اعمال کنید.