- برای SASS فعلا Web Workbench هست. افزونه معروف Web essentials هم به زودی ...
IIS Hosting:
if (request.Properties.ContainsKey["MS_HttpContext"]) { var ctx = request.Properties["MS_HttpContext"] as HttpContextWrapper; if (ctx != null) { var ip = ctx.Request.UserHostAddress; } }
public static class HttpRequestMessageExtensions { private const string HttpContext = "MS_HttpContext"; public static string GetClientIpAddress(this HttpRequestMessage request) { if (request.Properties.ContainsKey(HttpContext)) { dynamic ctx = request.Properties[HttpContext]; if (ctx != null) { return ctx.Request.UserHostAddress; } } return null; } }
Self Hosting:
if (request.Properties.ContainsKey[RemoteEndpointMessageProperty.Name]) { var remote = request.Properties[RemoteEndpointMessageProperty.Name] as RemoteEndpointMessageProperty; if (remote != null) { var ip = remote.Address; } }
ترکیب حالتهای قبلی:
»در این صورت دیگر نیازی به اضافه کردن اسمبلی System.ServiceModel نیست.
public static class HttpRequestMessageExtensions { private const string HttpContext = "MS_HttpContext"; private const string RemoteEndpointMessage = "System.ServiceModel.Channels.RemoteEndpointMessageProperty"; public static string GetClientIpAddress(this HttpRequestMessage request) { if (request.Properties.ContainsKey(HttpContext)) { dynamic ctx = request.Properties[HttpContext]; if (ctx != null) { return ctx.Request.UserHostAddress; } } if (request.Properties.ContainsKey(RemoteEndpointMessage)) { dynamic remoteEndpoint = request.Properties[RemoteEndpointMessage]; if (remoteEndpoint != null) { return remoteEndpoint.Address; } } return null; } }
public class MyHandler : DelegatingHandler { private readonly HashSet<string> deniedIps; protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { if (deniedIps.Contains(request.GetClientIpAddress())) { return Task.FromResult( new HttpResponseMessage( HttpStatusCode.Unauthorized ) ); } return base.SendAsync(request, cancellationToken); } }
Owin :
زمانی که از Owin برای هاست سرویسهای Web Api خود استفاده میکنید کمی روال انجام کار متفاوت خواهد شد. در این مورد نیز میتوانید از DelegatingHandlerها استفاده کنید. معرفی DelegatingHandler طراحی شده به Asp.Net PipeLine به صورت زیر خواهد بود:
public class Startup { public void Configuration( IAppBuilder appBuilder ) { var config = new HttpConfiguration(); var routeHandler = HttpClientFactory.CreatePipeline( new HttpControllerDispatcher( config ), new DelegatingHandler[] { new MyHandler(), } ); config.Routes.MapHttpRoute( name: "Default", routeTemplate: "{controller}/{action}", defaults: null, constraints: null, handler: routeHandler ); config.EnsureInitialized(); appBuilder.UseWebApi( config ); } }
public class IpMiddleware : OwinMiddleware { private readonly HashSet<string> _deniedIps; public IpMiddleware(OwinMiddleware next, HashSet<string> deniedIps) : base(next) { _deniedIps = deniedIps; } public override async Task Invoke(OwinRequest request, OwinResponse response) { var ipAddress = (string)request.Environment["server.RemoteIpAddress"]; if (_deniedIps.Contains(ipAddress)) { response.StatusCode = 403; return; } await Next.Invoke(request, response); } }
در نهایت برای معرفی این Middleware طراحی شده به Application، مراحل زیر را انجام دهید.
public class Startup { public void Configuration( IAppBuilder appBuilder ) { var config = new HttpConfiguration(); var deniedIps = new HashSet<string> {"192.168.0.100", "192.168.0.101"};
app.Use(typeof(IpMiddleware), deniedIps); appBuilder.UseWebApi( config ); } }
ASP.NET Web API - قسمت اول
«مزیت واقعی Web API چیست و چه زمانی پروژه ای رو با Web API شروع میکنید ؟»
به علاوه هدف اصلی Web API و یکپارچگی آن با خصوصا MVC (و بعد وب فرمها) در درجه اول توسعه ActionResultهای پیش فرض MVC است (به همین جهت اول اسم آن ASP.NET است و نه مثلا اندروید):
ASP.NET Web API - قسمت چهارم
- چه کسانی این فریم ورک را توسعه میدهند؟ گوگل. این مورد خیلی مهم است. در این بین شاید Aurelia مدرنتر به نظر برسد اما تیم آن سابقهی خوبی در نگهداری محصولات قبلی آن ندارد. برای مثال Durandal آنها به طور کامل رها شده و الان Aurelia را شروع کردهاند.
- تیم Angular اینقدر شجاعت داشته که اقرار کند نگارش 1 آن مشکلات زیادی دارد و دست به یک بازنویسی کامل زدهاند. باید دقت کرد که چقدر اینکار برای یک تیم محبوب در وب مشکل است و قطعا نگارش 2 آن که با این حجم بالای اعتراضات عدم سازگاری با نگارش 1 آن تولید شدهاست، بسیاری از مشکلات نگارش قبلی را ندارد. همچنین همین مساله سازگاری آنرا با نگارشهای بعد از 2 نیز تضمین خواهد کرد. چون اینبار دست به یک طراحی مجدد زدهاند و ... این طراحی مجدد خیلی برای آنها گران تمام شدهاست (خصوصا از لحاظ حجم انتقادهای رسیده). بنابراین در آینده در زمینه سازگاری با نگارشهای قبلی به شدت محتاط خواهند بود. این موردی است که استفاده کنندگان از ReactJS به زودی با آن مواجه خواهند شد.
- گروه کاربری مصرف کنندگان آن. چه تعداد مقاله، ویدیوی آموزشی، انجمن رفع اشکال و امثال اینها را در مورد یک محصول میتوانید پیدا کنید؟ قطعا AngularJS در این زمینه حرف اول را میزند.
- Angular 2 بر مبنای استاندارد web component طراحی شدهاست که در دراز مدت نیز سبب برد AngularJS 2 خواهد شد و نیاز کمتر به بازنویسیهای کلی.
- Angular 2 برای کار با محصولات موبایل بهینه سازی شدهاست و مانند بوت استرپ 3 یک فریم ورک mobile first است.
- استفادهی از Type Script به عنوان زبان اول این پلتفرم (البته استفادهی از آن اجباری نیست). این مساله در دراز مدت سبب تولید کدهایی با کیفیت بالاتر میشود. برای مثال الان ReactJS مخلوطی است از ES 5 و ES 6. اما AngularJS 2 تصمیم بهتری را اتخاذ کردهاست. همین مساله سبب شدهاست که توسعهی Type Script توسط مایکروسافت سرعت بیشتری پیدا کند.
- داشتن امکانات توکار بیشتری نسبت به رقبا. برای مثال ReactJS یک کتابخانه است؛ اما AngularJS 2 یک فریم ورک کامل که تمام کتابخانههای جانبی رقبا را یکجا از روز اول دارد.
بنابراین آیا پس از ارائهی نهایی Angular 2، ارزش یادگیری را دارد؟ بله؛ حتما. ابتدا (و هم اکنون) اعتراضهای شدیدی در مورد عدم سازگاری آن با نگارشهای قبلی وجود خواهد داشت و پس از مدتی همه آنرا فراموش کرده و خودشان را وفق میدهند.
.NET Core -> ASP.NET Core Web Application (.NET Core) -> Select `Empty` Template
در اینجا نقش Solution همانند نگارشهای قبلی ویژوال استودیو است: ظرفی است برای ساماندهی موارد مورد نیاز جهت تشکیل یک برنامهی وب و شامل مواردی است مانند پروژهها، تنظیمات آنها و غیره. بنابراین هنوز در اینجا فایل sln. تشکیل میشود.
نقش فایل global.json
زمانیکه یک پروژهی جدید ASP.NET Core 1.0 را آغاز میکنیم، ساختار پوشههای آن به صورت زیر هستند:
در اینجا هنوز فایل sln. قابل مشاهده است. همچنین در اینجا فایل جدیدی به نام global.json نیز وجود دارد، با این محتوا:
{ "projects": [ "src", "test" ], "sdk": { "version": "1.0.0-preview2-003121" } }
خاصیت projects در اینجا به صورت یک آرایه تعریف شدهاست و بیانگر محل واقع شدن پوشههای اصلی پروژهی جاری هستند. پوشهی src یا source را در تصویر فوق مشاهده میکنید و محلی است که سورسهای برنامه در آن قرار میگیرند. یک پوشهی test نیز در اینجا ذکر شدهاست و اگر در حین ایجاد پروژه، گزینهی ایجاد unit tests را هم انتخاب کرده باشید، این پوشهی مخصوص نیز ایجاد خواهد شد.
نکتهی مهم اینجا است، هرکدی که درون پوشههای ذکر شدهی در اینجا قرار نگیرد، قابلیت build را نخواهد داشت. به عبارتی این نسخهی از ASP.NET پوشهها را قسمتی از پروژه به حساب میآورد. در نگارشهای قبلی ASP.NET، مداخل تعریف فایلهای منتسب به هر پروژه، درون فایلی با پسوند csproj. قرار میگرفتند. معادل این فایل در اینجا اینبار پسوند xproj را دارد و اگر آنرا با یک ادیتور متنی باز کنید، فاقد تعاریف مداخل فایلهای پروژه است.
در این نگارش جدید اگر فایلی را به پوشهی src اضافه کنید یا حذف کنید، بلافاصله در solution explorer ظاهر و یا حذف خواهد شد.
یک آزمایش: به صورت معمول از طریق windows explorer به پوشهی src برنامه وارد شده و فایل پیش فرض Project_Readme.html را حذف کنید. سپس به solution explorer ویژوال استودیو دقت کنید. مشاهده خواهید کرد که این فایل، بلافاصله از آن حذف میشود. در ادامه به recycle bin ویندوز مراجعه کرده و این فایل حذف شده را restore کنید تا مجددا به پوشهی src برنامه اضافه شود. اینبار نیز افزوده شدن خودکار و بلافاصلهی این فایل را میتوان در solution explorer مشاهده کرد.
بنابراین ساختار مدیریت فایلهای این نگارش از ASP.NET در ویژوال استودیو، بسیار شبیه به ساختار مدیریت فایلهای VSCode شدهاست که آن نیز بر اساس پوشهها کار میکند و یک پوشه و تمام محتوای آنرا به صورت پیش فرض به عنوان یک پروژه میشناسد. به همین جهت دیگر فایل csproj ایی در اینجا وجود ندارد و file system همان project system است.
یک نکته: در اینجا مسیرهای مطلق را نیز میتوان ذکر کرد:
"projects": [ "src", "test", "c:\\sources\\Configuration\\src" ],
کامپایل خودکار پروژه در ASP.NET Core 1.0
علاوه بر تشخیص خودکار کم و زیاد شدن فایلهای سیستمی پروژه، بدون نیاز به Add new item کردن آنها در ویژوال استودیو، اگر سورسهای برنامه را نیز تغییر دهید، فایل سورس جدیدی را اضافه کنید و یا فایل سورس موجودی را حذف کنید، کل پروژه به صورت خودکار کامپایل میشود و نیازی نیست اینکار را به صورت دستی انجام دهید.
یک آزمایش: برنامه را از طریق منوی debug و گزینهی start without debugging اجرا کنید. اگر برنامه را در حالت معمول debug->start debugging اجرا کنید، حالت کامپایل خودکار را مشاهده نخواهید کرد. در اینجا (پس از start without debugging) یک چنین خروجی را مشاهده خواهید کرد:
این خروجی حاصل اجرای کدهای درون فایل Startup.cs برنامه است:
app.Run(async (context) => { await context.Response.WriteAsync("Hello World!"); });
await context.Response.WriteAsync("Hello DNT!");
این مساله قابلیت استفادهی از ASP.NET Core را در سایر ادیتورهای موجود، مانند VSCode سهولت میبخشد.
نقش فایل project.json
فایل جدید project.json مهمترین فایل تنظیمات یک پروژهی ASP.NET Core است و مهمترین قسمت آن، قسمت وابستگیهای آن است:
"dependencies": { "Microsoft.NETCore.App": { "version": "1.0.0", "type": "platform" }, "Microsoft.AspNetCore.Diagnostics": "1.0.0", "Microsoft.AspNetCore.Server.IISIntegration": "1.0.0", "Microsoft.AspNetCore.Server.Kestrel": "1.0.0", "Microsoft.Extensions.Logging.Console": "1.0.0" },
در نگارشهای قبلی ASP.NET، فایلی XML ایی به نام packages.config حاوی تعاریف مداخل بستههای نیوگت برنامه بود. این فایل در اینجا جزئی از محتوای فایل project.json در قسمت dependencies آن است.
با قسمت وابستگیهای این فایل، به دو طریق میتوان کار کرد:
الف) ویرایش مستقیم این فایل که به همراه intellisense نیز هست. با افزودن مداخل جدید به این فایل و ذخیره کردن آن، بلافاصله کار restore و دریافت و نصب آنها آغاز میشود:
ب) از طریق NuGet package manager
روش دیگر کار با وابستگیها، کلیک راست بر روی گره references و انتخاب گزینهی manage nuget packages است:
برای نمونه جهت نصب ASP.NET MVC 6 این مراحل باید طی شوند:
ابتدا برگهی browse را انتخاب کنید و سپس تیک مربوط به include prerelease را نیز انتخاب نمائید.
البته بستهی اصلی MVC در اینجا Microsoft.AspNetCore.Mvc نام دارد و نه MVC6.
اینبار بستههایی که restore میشوند، در مسیر اشتراکی C:\Users\user_name\.nuget\packages ذخیره خواهند شد.
یک نکتهی مهم:
قرار هست در نگارشهای پس از RTM، فایلهای project.json و xproj را جهت سازگاری با MSBuild، اندکی تغییر دهند (که این تغییرات به صورت خودکار توسط VS.NET انجام میشود). اطلاعات بیشتر
انتخاب فریم ورکهای مختلف در فایل project.json
در قسمت قبل عنوان شد که ASP.NET Core را میتوان هم برفراز NET Core. چندسکویی اجرا کرد و هم NET 4.6. مختص به ویندوز. این انتخابها در قسمت frameworks فایل project.json انجام میشوند:
"frameworks": { "netcoreapp1.0": { "imports": [ "dotnet5.6", "portable-net45+win8" ] } },
در اینجا اگر علاقمند بودید که از دات نت کامل مخصوص ویندوز نیز استفاده کنید، میتوانید آنرا در لیست فریم ورکها اضافه نمائید (در این مثال، دات نت کامل 4.5.2 نیز ذکر شدهاست):
"frameworks": { "netcoreapp1.0": { }, "net452": { } }
- “netcoreapp1.0” برای معرفی و استفادهی از NET Core 1.0. بکار میرود.
- جهت معرفی فریم ورکهای کامل و ویندوزی دات نت، اسامی “net45”, “net451”, “net452”, “net46”, “net461” مجاز هستند.
- “portable-net45+win8” برای معرفی پروفایلهای PCL یا portable class libraries بکار میرود.
- “dotnet5.6”, “dnxcore50” برای معرفی نگارشهای پیش نمایش NET Core.، پیش از ارائهی نگارش RTM استفاده میشوند.
- “netstandard1.2”, “netstandard1.5” کار معرفی برنامههای NET Standard Platform. را انجام میدهند.
بر این مبنا، dotnet5.6 ذکر شدهی در قسمت تنظیمات نگارش RTM، به این معنا است که قادر به استفادهی از بستههای نیوگت و کتابخانههای تولید شدهی با نگارشهای RC نیز خواهید بود (هرچند برنامه از netcoreapp1.0 استفاده میکند).
یک مثال: قسمت فریم ورکهای فایل project.json را به نحو ذیل جهت معرفی دات نت 4.6.1 تغییر دهید:
"frameworks": { "netcoreapp1.0": { "imports": [ "dotnet5.6", "portable-net45+win8" ] }, "net461": { "imports": [ "portable-net45+win8" ], "dependencies": { } } },
در این حالت پس از ذخیرهی فایل و شروع خودکار بازیابی وابستگیها، با پیام خطای Package Microsoft.NETCore.App 1.0.0 is not compatible with net461 متوقف خواهید شد.
برای رفع این مشکل باید وابستگی Microsoft.NETCore.App را حذف کنید، چون با net461 سازگاری ندارد
"dependencies": { //"Microsoft.NETCore.App": { // "version": "1.0.0", // "type": "platform" //},
فریم ورک دوم استفاده شده نیز در اینجا قابل مشاهده است.
فایل project.lock.json چیست؟
ذیل فایل project.json، فایل دیگری به نام project.lock.json نیز وجود دارد. اگر به محتوای آن دقت کنید، این فایل حاوی لیست دقیق بستههای نیوگت مورد استفادهی توسط برنامه است و الزاما با آنچیزی که در فایل project.json قید شده، یکی نیست. از این جهت که در فایل project.json، قید میشود netcoreapp1.0؛ ولی این netcoreapp1.0 دقیقا شامل چه بستههایی است؟ لیست کامل آنها را در این فایل میتوانید مشاهده کنید.
در ابتدای این فایل یک خاصیت locked نیز وجود دارد که مقدار پیش فرض آن false است. اگر به true تنظیم شود، در حین restore وابستگیهای برنامه، تنها از نگارشهای ذکر شدهی در این فایل استفاده میشود. از این جهت که در فایل project.json میتوان شماره نگارشها را با * نیز مشخص کرد؛ مثلا *.1.0.0
پوشهی جدید wwwroot و گره dependencies
یکی از پوشههای جدیدی که در ساختار پروژهی ASP.NET Core معرفی شدهاست، wwwroot نام دارد:
از دیدگاه هاستینگ برنامه، این پوشه، پوشهای است که در معرض دید عموم قرار میگیرد (وب روت). برای مثال فایلهای ایستای اسکریپت، CSS، تصاویر و غیره باید در این پوشه قرار گیرند تا توسط دنیای خارج قابل دسترسی و استفاده شوند. بنابراین سورس کدهای برنامه خارج از این پوشه قرار میگیرند.
گره dependencies که ذیل پوشهی wwwroot قرار گرفتهاست، جهت مدیریت این وابستگیهای سمت کلاینت برنامه است. در اینجا میتوان از NPM و یا Bower برای دریافت و به روز رسانی وابستگیهای اسکریپتی و شیوهنامههای برنامه کمک گرفت (علاوه بر نیوگت که بهتر است صرفا جهت دریافت وابستگیهای دات نتی استفاده شود).
یک مثال: فایل جدیدی را به نام bower.json به پروژهی جاری با این محتوا اضافه کنید:
{ "name": "asp.net", "private": true, "dependencies": { "bootstrap": "3.3.6", "jquery": "2.2.0", "jquery-validation": "1.14.0", "jquery-validation-unobtrusive": "3.2.6" } }
پس از اضافه شدن فایل bower.json، بلافاصله کار restore بستهها از اینترنت شروع میشود:
و یا با کلیک راست بر روی گره dependencies، گزینهی restore packages نیز وجود دارد.
فایلهای نهایی دریافت شده را در پوشهی bower_components خارج از wwwroot میتوانید مشاهده کنید.
در مورد نحوهی توزیع و دسترسی به فایلهای استاتیک یک برنامهی ASP.NET Core 1.0، نکات خاصی وجود دارند که در قسمتهای بعد، بررسی خواهند شد.
یک نکته: اگر خواستید نام پوشهی wwwroot را تغییر دهید، فایل جدیدی را به نام hosting.json با این محتوا به پروژه اضافه کنید:
{ "webroot":"AppWebRoot" }
نقطهی آغازین برنامه کجاست؟
اگر به فایل project.json دقت کنید، چنین تنظیمی در آن موجود است:
"buildOptions": { "emitEntryPoint": true, "preserveCompilationContext": true },
public static void Main(string[] args) { var host = new WebHostBuilder() .UseKestrel() .UseContentRoot(Directory.GetCurrentDirectory()) .UseIISIntegration() .UseStartup<Startup>() .Build(); host.Run(); }
var outputKind = compilerOptions.EmitEntryPoint.GetValueOrDefault() ? OutputKind.ConsoleApplication : OutputKind.DynamicallyLinkedLibrary;
در فایل Program.cs تنظیماتی را مشاهده میکنید، در مورد راه اندازی Kestler که وب سرور بسیار سریع و چندسکویی کار با برنامههای ASP.NET Core 1.0 است و قسمت مهم دیگر آن به استفادهی از کلاس Startup بر میگردد (()<UseStartup<Startup). این کلاس را در فایل جدید Startup.cs میتوانید ملاحظه کنید که کار تنظیمات آغازین برنامه را انجام میدهد. اگر پیشتر با OWIN، در نگارشهای قبلی ASP.NET کار کرده باشید، قسمتی از این فایل برای شما آشنا است:
public class Startup { public void ConfigureServices(IServiceCollection services) { } public void Configure(IApplicationBuilder app) { app.Run(async (context) => { await context.Response.WriteAsync("Hello World!"); }); } }
نقش فایل launchsetting.json
محتویات پیش فرض این فایل برای قالب empty پروژههای ASP.NET Core 1.0 به صورت ذیل است:
{ "iisSettings": { "windowsAuthentication": false, "anonymousAuthentication": true, "iisExpress": { "applicationUrl": "http://localhost:7742/", "sslPort": 0 } }, "profiles": { "IIS Express": { "commandName": "IISExpress", "launchBrowser": true, "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } }, "Core1RtmEmptyTest": { "commandName": "Project", "launchBrowser": true, "launchUrl": "http://localhost:5000", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } } } }
پروفایلهایی که در اینجا ذکر شدهاند، در تنظیمات پروژه نیز قابل مشاهده هستند: (کلیک راست بر روی پروژه و مشاهدهی properties آن و یا دوبار کلیک بر روی گره properties)
به علاوه امکان انتخاب این پروفایلها در زمان آغاز برنامه نیز وجود دارند:
نکتهی مهم تمام این موارد به قسمت environment variable قابل مشاهدهی در تصاویر فوق بر میگردد. این متغیر محیطی میتواند سه مقدار Development ، Staging و Production را داشته باشد و بر اساس این متغیر و مقدار آن، میتوان پروفایل جدیدی را تشکیل داد. زمانیکه برنامه بر اساس پروفایل خاصی بارگذاری میشود، اینکه چه متغیر محیطی انتخاب شدهاست، در کلاس Startup قابل استخراج و بررسی بوده و بر این اساس میتوان اقدامات خاصی را انجام داد. برای مثال تنظیمات خاصی را بارگذاری کرد و یا صفحات ویژهای را فعال و غیرفعال کرد (این موارد را در قسمتهای بعدی مرور میکنیم).
همچنین در اینجا به ازای هر پروفایل مختلف میتوان Url آغازین یا launch url و پورت آنرا مجزا درنظر گرفت و یا از وب سرور دیگری استفاده کرد.
نصب پیشنیازهای کار با Gulp و TypeScript
فایل package.json در قسمت اول این سری معرفی شد. دراینجا قسمت devDependencies آنرا به نحو ذیل تکمیل کنید:
"devDependencies": { "typescript": "^1.8.10", "gulp": "^3.9.1", "path": "^0.12.7", "gulp-clean": "^0.3.2", "fs": "^0.0.2", "gulp-concat": "^2.6.0", "gulp-typescript": "^2.13.1", "gulp-tsc": "^1.1.5", "del": "^2.2.0", "gulp-autoprefixer": "^3.1.0", "gulp-cssnano": "^2.0.0", "gulp-html-replace": "^1.5.4", "gulp-htmlmin": "^1.0.5", "gulp-uglify": "^1.5.3", "merge-stream": "^1.0.0", "systemjs-builder": "^0.15.16", "typings": "^0.8.1" },
نکتهی مهم آن systemjs-builder است. این کتابخانه کار کامپایل systemjs.config.js را به یک تک اسکریپت انجام میدهد. به این ترتیب مشکل صدها بار رفت و برگشت به سرور، برای دریافت وابستگیهای AngularJS 2.0، به طور کامل برطرف میشود.
افزودن فایل gulpfile.js به پروژه
یا یک فایل جدید جاوا اسکریپتی را به نام gulpfile.js به ریشهی پروژه اضافه کنید و یا از منوی project -> add new item نیز میتوانید گزینهی gulp configuration file را در VS 2015 انتخاب نمائید. محتوای این فایل را به نحو ذیل تغییر دهید:
var gulp = require("gulp"), concat = require("gulp-concat"), tsc = require("gulp-typescript"), jsMinify = require("gulp-uglify"), cssPrefixer = require("gulp-autoprefixer"), cssMinify = require("gulp-cssnano"), del = require("del"), merge = require("merge-stream"), minifyHTML = require('gulp-htmlmin'), SystemBuilder = require("systemjs-builder"); var appFolder = "./app"; var outFolder = "wwwroot"; gulp.task("clean", () => { return del(outFolder); }); gulp.task("shims", () => { return gulp.src([ "node_modules/es6-shim/es6-shim.js", "node_modules/zone.js/dist/zone.js", "node_modules/reflect-metadata/Reflect.js" ]) .pipe(concat("shims.js")) .pipe(jsMinify()) .pipe(gulp.dest(outFolder + "/js/")); }); gulp.task("tsc", () => { var tsProject = tsc.createProject("./tsconfig.json"); var tsResult = gulp.src([ appFolder + "/**/*.ts" ]) .pipe(tsc(tsProject), undefined, tsc.reporter.fullReporter()); return tsResult.js.pipe(gulp.dest("build/")); }); gulp.task("system-build", ["tsc"], () => { var builder = new SystemBuilder(); return builder.loadConfig("systemjs.config.js") .then(() => builder.buildStatic(appFolder, outFolder + "/js/bundle.js")) .then(() => del("build")); }); gulp.task("buildAndMinify", ["system-build"], () => { var bundle = gulp.src(outFolder + "/js/bundle.js") .pipe(jsMinify()) .pipe(gulp.dest(outFolder + "/js/")); var css = gulp.src(outFolder + "/css/styles.css") .pipe(cssMinify()) .pipe(gulp.dest(outFolder + "/css/")); return merge(bundle, css); }); gulp.task("favicon", function () { return gulp.src("./app/favicon.ico") .pipe(gulp.dest(outFolder)); }); gulp.task("css", function () { return gulp.src(appFolder + "/**/*.css") .pipe(cssPrefixer()) .pipe(cssMinify()) .pipe(gulp.dest(outFolder)); }); gulp.task("templates", function () { return gulp.src(appFolder + "/**/*.html") .pipe(minifyHTML()) .pipe(gulp.dest(outFolder)); }); gulp.task("assets", ["templates", "css", "favicon"], function () { return gulp.src(appFolder + "/**/*.png") .pipe(gulp.dest(outFolder)); }); gulp.task("otherScriptsAndStyles", () => { gulp.src([ "jquery/dist/jquery.*js", "bootstrap/dist/js/bootstrap*.js" ], { cwd: "node_modules/**" }) .pipe(gulp.dest(outFolder + "/js/")); gulp.src([ "node_modules/bootstrap/dist/css/bootstrap.css" ]).pipe(cssMinify()).pipe(gulp.dest(outFolder + "/css/")); gulp.src([ "node_modules/bootstrap/fonts/*.*" ]).pipe(gulp.dest(outFolder + "/fonts/")); }); //gulp.task("watch.tsc", ["tsc"], function () { // return gulp.watch(appFolder + "/**/*.ts", ["tsc"]); //}); //gulp.task("watch", ["watch.tsc"]); gulp.task("default", [ "shims", "buildAndMinify", "assets", "otherScriptsAndStyles" //,"watch" ]);
در این فایل فرض شدهاست که خروجی نهایی برنامه قرار است در پوشهای به نام wwwroot کپی شود و پوشهی اصلی برنامه، همان پوشهای به نام app، در ریشهی پروژه است.
var appFolder = "./app"; var outFolder = "wwwroot";
1) وظیفهی clean، کار تمیز کردن پوشهی نهایی خروجی برنامه را انجام میدهد (حذف تمام فایلهای آن).
2) وظیفهی shims، کار بسته بندی، یکی کردن و فشرده کردن سه اسکریپت es6-shim.js، zone.js و Reflect.js را انجام میدهد. سپس تک فایل حاصل را به نام shims.js، در پوشهی wwwroot/js کپی میکند.
3) وظیفهی tsc، یکبار دیگر کامپایلر TypeScript را اجرا میکند تا مطمئن شویم با آخرین نگارش فایلهای js برنامه کار میکنیم.
4) وظیفهی system-build، کار پردازش خودکار مداخل فایل systemjs.config.js را انجام میدهد. در آخرین نگارش ارائه شدهی AngularJS 2.0، بجای ذکر مداخل مورد نیاز آن، این تک فایل systemjs.config.js را به صفحه پیوست میکنیم تا اسکریپتهای لازم را (چند صد عدد)، به صورت خودکار بارگذاری کند. برای یکی کردن این چند صد عدد اسکریپت، از کتابخانهی SystemBuilder آن کمک گرفته و کار کامپایل نهایی را انجام میدهیم. خروجی تمام این فایلها، به همراه کلیه فایلهای js حاصل از کامپایل فایلهای TypeScript برنامه، در فایلی به نام bundle.js کپی شدهی در پوشهی wwwroot/js نوشته میشود. بنابراین دیگر نیازی نیست تا فایلهای js پوشهی app و همچنین فایلهای js وابستگیهای AngularJS 2.0 را توزیع کنیم. تک فایل bundle.js، حاوی تمام اینها است.
5) وظیفهی buildAndMinify کار اجرای وظیفهی system-bulder را به همراه فشرده سازی تک فایل bundle.js، به عهده دارد. به علاوه اگر در پوشهی css آن نیز فایل styles.css موجود باشد، آن را فشرده میکند.
6) در ادامه یک سری وظیفهی کپی کردن منابع برنامه را مشاهده میکنید. مانند favicon که کار کپی کردن این آیکن را به پوشهی wwwroot انجام میدهد. وظیفهی css، فایلهای css موجود در پوشههای برنامه را به wwwroot و زیر پوشههای آن کپی میکند. وظیفهی templates، کار کپی کردن فایلهای html قالبهای کامپوننتها را بر عهده دارد. وظیفهی assets، کار کپی کردن فایلهای png را انجام میدهد.
7) وظیفهی otherScriptsAndStyles یک سری css و js ثالث را به پوشهی wwwroot کپی میکند؛ مانند فایلهای بوت استرپ و جیکوئری.
8) وظیفهی default، کار اجرای تمام این وظایف را با هم به عهده دارد.
اکنون اگر بر روی gulpfile.js کلیک راست کنید، گزینهی task runner explorer ظاهر خواهد شد. آنرا انتخاب کنید:
بر روی وظیفهی default کلیک راست کرده و آنرا اجرا کنید. پس از مدتی پوشهی جدید wwwroot ساخته شده و فایلهای نهایی برنامه به آن کپی میشوند.
اصلاح فایل index.html و یا Views\Shared\_Layout.cshtml
اکنون که تمام فایلهای مورد نیاز پروژه در پوشهی wwwroot کپی شدهاند، نیاز است فایل index.html را به نحو ذیل تغییر داد:
<!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="~/wwwroot/css/bootstrap.css" rel="stylesheet" /> <link href="~/wwwroot/app.component.css" rel="stylesheet" /> <link href="~/Content/Site.css" rel="stylesheet" type="text/css" /> <script src="~/wwwroot/js/shims.js"></script> </head> <body> <div> @RenderBody() <pm-app>Loading App...</pm-app> </div> <script src="~/wwwroot/js/jquery/dist/jquery.min.js"></script> <script src="~/wwwroot/js/bootstrap/dist/js/bootstrap.min.js"></script> <script src="~/wwwroot/js/bundle.js"></script> @RenderSection("Scripts", required: false) </body> </html>
اسکریپتهای shims که برای مرورگرهای قدیمیتر درنظر گرفته شدهاند، به تک فایل wwwroot/js/shims.js منتقل شدهاند.
تمام اسکریپتهای AngularJS 2.0 و وابستگیهای آن به همراه تمام اسکریپتهای برنامهی خودمان، به تک فایل wwwroot/js/bundle.js منتقل شدهاند.
اکنون اگر برنامه را اجرا کنید، سرعت آن با قبل قابل مقایسه نیست! اینبار دیگر نه نیازی به بارگذاری تمام وابستگیهای AngularJS 2.0 به صورت مجزا توسط systemjs.config.js وجود دارد و نه به ازای مشاهدهی هر صفحهای، یکبار قرار است فایل js کامپوننت آن بارگذاری شود. تمام اینها داخل فایل wwwroot/js/bundle.js قرار گرفتهاند و تنها یکبار بارگذاری میشوند.
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید: MVC5Angular2.part12.zip
خلاصهی بحث
با نوشتن یک Gulp Task جدید میتوان بر اساس فایل systemjs.config.js، تمام اسکریپتهای دخیل در اجرای برنامه را به صورت خودکار یافته و به صورت یک تک فایل نهایی، بسته بندی و توزیع کرد.
طراحی Url در Restful API
Url بخش اصلی و راه ارتباطی API شما با توسعه دهنده است .بنابراین طراحی یک ساختار مناسب و یکپارچه برای Url ها دارای اهمیت زیادی است .
Url پایه API خود را ساده و خوانا ، حفظ کنید . داشتن یک Url پایه ساده استفاده از API را آسان کرده و خوانایی آن را بالا میبرد و باعث میشود که توسعه دهنده برای استفاده از آن نیاز کمتری به مراجعه به مستندات داشته باشد. پیشنهاد میشود که برای هر منبع تنها دو Url پایه وجود داشته باشد . یکی برای مجموعه ای از منبع موردنظر و دیگری برای یک واحد مشخص از آن منبع . برای مثال اگر منبع موردنظر ما کتاب باشد ، خواهیم داشت :
.../books
.../books/1001
استفاده از این روش یک مزیت دیگر هم به همراه دارد و آن دور کردن افعال از Urlها است.
بسیاری در زمان طراحی Urlها و در نامگذاری از فعلها استفاده میکنند. برای هر منبعی که مدلسازی میکنید هیچ وقت نمیتوانید آن را به تنهایی و جداافتاده در نظر بگیرید. بلکه همیشه منابع مرتبطی وجود دارند که باید در نظر گرفته شوند. در مثال کتاب میتوان منابعی مثل نویسنده ، ناشر ، موضوع و ... را بیان کرد. حالا سعی کنید به تمام Url هایی که برای پوشش دادن تمام درخواستهای مربوط به منبع کتاب نیاز داریم فکر کنید . احتمالا به چیزی شبیه این میرسیم :
.../getAllBooks .../getBook .../newBook .../getNewBooksSince .../getComputerBooks .../BooksNotPublished .../UpdateBookPriceTo .../bookForPublisher .../GetLastBooks .../DeleteBook ….
پس حالا این درخواستهای متنوع را چطور با دو Url اصلی انجام دهیم ؟
1- از افعال Http برای کار کردن بر روی منابع استفاده کنید . با استفاده از افعال Http شامل POST ، GET ، PUT و DELETE و دو Url اصلی ، یک مجموعهی مناسب از عملیاتها در دسترس توسعه دهنده خواهد بود . به جدول زیر نگاه کنید .
توسعه دهندگان احتمالا نیازی به این جدول برای درک اینکه API چطور کار میکند نخواهند داشت.
2- با استفاده از نکته قبلی بخشی از Urlهای بالا حذف خواهند شد. اما هنوز با روابط بین منابع چکار کنیم؟ منابع تقریبا همیشه دارای روابطی با دیگر منابع هستند . یک روش ساده برای بیان این روابط در API چیست ؟ به مثال کتاب برمیگردیم. کتابها دارای نویسنده هستند. اگر بخواهیم کتابهای یک نویسنده را برگردانیم چه باید بکنیم؟ با استفاده از Urlهای پایه و افعال Http میتوان اینکار را انجام داد. یکی از ساختارهای ممکن این است :
GET .../authors/1001/books
POST .../authors/1001/books
3- بیشتر APIها دارای پیچیدگیهای بیشتری نسبت به Url اصلی یک منبع هستند . هر منبع مشخصات
و روابط متنوعی دارد که قابل جستجو کردن، مرتب سازی، بروزرسانی و تغییر هستند. Url
اصلی را ساده نگه دارید و این پیچیدگیها را به کوئری استرینگ منتقل کنید.
برای برگرداندن تمام کتابهای با قیمت پنچ هزار تومان با قطع جیبی که دارای امتیاز 8 به بالا هستند از کوئری زیر میشود استفاده کرد :
GET .../books?price=5000&size=pocket&score=8
و البته فراموش نکنید که لیستی از فیلدهای مجاز را در مستندات خود ارائه کنید.
4 - گفتیم که بهتر است افعال را از Url ها خارج کنیم . ولی در مواردی که درخواست ارسال شده در مورد یک منبع نیست چطور؟ مواردی مثل محاسبه مالیات پرداختی یا هزینه بیمه ، جستجو در کل منابع ، ترجمه یک عبارت یا تبدیل واحدها . هیچکدام از اینها ارتباطی با یک منبع خاص ندارند. در این موارد بهتر است از افعال استفاده شود. و حتما در مستندات خود ذکر کنید که در این موارد از افعال استفاده میشود..../convert?value=25&from=px&to=em .../translate?term=web&from=en&to=fa
5 - استفاده از اسامی جمع یا مفرد
با توجه به ساختاری که تا اینجا طراحی کرده ایم بکاربردن اسامی جمع بامعناتر و خواناتر است. اما مهمتر از روشی که بکار میبرید ، اجتناب از بکاربردن هر دو روش با هم است ، اینکه در مورد یک منبع از اسم منفرد و در مورد دیگری از اسم جمع استفاده کنید . یکدستی API را حفظ کنید و به توسعه دهنده کمک کنید راحتتر API شما را یاد بگیرد.
6- استفاده از نامهای عینی به جای نامهای کلی و انتزاعیAPI ی را در نظر بگیرید که محتواهایی را در فرمتهای مختلف ارائه میدهد. بلاگ ، ویدئو ، اخبار و .... حالا فرض کنیداین API منابع را در بالاتری سطح مدسازی کرده باشد مثل /items یا /assets . درک کردن محتوای این API و کاری که میتوان با این API انجام داد برای توسعه دهنده سخت است . خیلی راحتتر و مفیدتر است که منابع را در قالب بلاگ ، اخبار ، ویدئو مدلسازی کنیم .