سلاممن پروژه رو اجرا کردم و مشکلی ندارم فقط 1 سوالImageها دقیقا کجا ان من پیدا نمیکنم آدرسشونhttp://localhost:2986/Handler/Catego....ashx?catId=10http://localhost:2986/Handler/ProductHandler.ashx?Id=10من میخوام با provider تو نرم افزار اندروید صداشون بزنم
HTTP Error 500.30 - ANCM In-Process Start Failure
در ابتدا بعد از ایجاد یک پروژهی جدید، نیاز دارید تا اسمبلی RazorEngin را توسط Package Manager Console به پروژه اضافه نماید.
Install-Package RazorEngine -Version 3.7.0
در گام بعدی نیاز است در کنترلری، یک اکشن متد را تعریف نماید که خروجی آن از نوع رشته خواهد بود و دستورات زیر در آن تعریف میشوند:
using System.Web.Mvc; using RazorEngine; namespace dynamicCSS.Controllers { public class StyleController : Controller { /// <summary> /// نام متد ارجاعی به فایل سی اس اس /// </summary> /// <returns></returns> public string Index() { //The ContentType property specifies the HTTP content type for the response. If no ContentType is specified, the default is text/HTML. Response.ContentType = "text/css"; //با استفاه از متد //ReadAllText //فایل رو خوانده و سپس از متد //Parse in Razor Class //به صورت رشته برگشت خواهیم داد return Razor.Parse(System.IO.File.ReadAllText(Server.MapPath("/Content/Site.css"))); } } }
#region Assembly RazorEngine.dll, v2.1.4039.23635 // Your Address\dynamicCSS\packages\RazorEngine.2.1\lib\.NetFramework 4.0\RazorEngine.dll #endregion using RazorEngine.Templating; using System; using System.Collections.Generic; namespace RazorEngine { public static class Razor { public static TemplateService DefaultTemplateService { get; } public static IDictionary<string, TemplateService> Services { get; } public static void AddResolver(Func<string, string> resolverDelegate); public static void AddResolver(ITemplateResolver resolver); public static void Compile(string template, string name); public static void Compile(string template, Type modelType, string name); public static void CompileWithAnonymous(string template, string name); public static string Parse(string template, string name = null); public static string Parse<T>(string template, T model, string name = null); public static string Run(string name); public static string Run<T>(T model, string name); public static void SetActivator(Func<Type, ITemplate> activator); public static void SetActivator(IActivator activator); public static void SetTemplateBase(Type type); } }
در این حالت میتوان از دستورات Razor در فایل Css نیز استفاده کرد:
@{ // در اینجا دو متغییر با کلمه کلیدی // var // ساخته و به صورت پیش فرض مقدار دهی نمودیم var redColor = "red"; var sizeMode = "100px"; } h1 { // روش استفاده از متغییرها color: @redColor !important; font-size : @sizeModel !impotant; }
//تغییر ادرس فایل به اکشن متد در کنترلر //Home //<link href="/Content/Site.Css" rel="stylesheet" /> //شکل صحیح آدرس دهی <link href="@Url.Action("Style", "Home")" rel="stylesheet" />
نکته: در صورتیکه متغیری بعد از دستورات استفاده شده تعریف گردد، با خطای زیر روبرو خواهید شد:
در خروجی نهایی تگ h1 با فونت 100 پیکسل و رنگ قرمز به نمایش در میآید:
{ // Use IntelliSense to find out which attributes exist for C# debugging // Use hover for the description of the existing attributes // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md "version": "0.2.0", "configurations": [ { "name": ".NET Core Launch (console)", "type": "coreclr", "request": "launch", "preLaunchTask": "build", // If you have changed target frameworks, make sure to update the program path. "program": "${workspaceFolder}/bin/Debug/netcoreapp3.1/EFCoreDbFunctionsSample.dll", "args": [], "cwd": "${workspaceFolder}", // For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console "console": "internalConsole", "stopAtEntry": false, "justMyCode": false, "symbolOptions": { "searchMicrosoftSymbolServer": true }, "suppressJITOptimizations": true, "env": { "COMPlus_ZapDisable": "1" } }, { "name": ".NET Core Attach", "type": "coreclr", "request": "attach", "processId": "${command:pickProcess}" } ] }
Loaded 'C:\Program Files\dotnet\shared\Microsoft.NETCore.App\3.1.8\System.Private.CoreLib.dll'. Symbols loaded. Loaded 'D:\Prog\1399\EFCoreDbFunctionsSample\bin\Debug\netcoreapp3.1\EFCoreDbFunctionsSample.dll'. Symbols loaded. . . . Loaded 'D:\Prog\1399\EFCoreDbFunctionsSample\bin\Debug\netcoreapp3.1\EFCoreSecondLevelCacheInterceptor.dll'. Symbols loaded. . . .
پیشنیازها
برای دنبال کردن این مثال فرض بر این است که NET Core 2.0 SDK. و همچنین Angular CLI را نیز پیشتر نصب کردهاید. مابقی بحث توسط خط فرمان و ابزارهای dotnet cli و angular cli ادامه داده خواهند شد و الزامی به نصب هیچگونه IDE نیست و این مثال تنها توسط VSCode پیگیری شدهاست.
تدارک ساختار ابتدایی مثال جاری
ساخت برنامهی وب، توسط dotnet cli
ابتدا یک پوشهی جدید را به نام SignalRCore2Sample ایجاد میکنیم. سپس داخل این پوشه، پوشهی دیگری را به نام SignalRCore2WebApp ایجاد خواهیم کرد (تصویر فوق). از طریق خط فرمان به این پوشه وارد شده (در ویندوز، در نوار آدرس، دستور cmd.exe را تایپ و enter کنید) و سپس فرمان ذیل را صادر میکنیم:
dotnet new mvc
ساخت برنامهی کلاینت، توسط angular cli
سپس از طریق خط فرمان به پوشهی SignalRCore2Sample بازگشته و دستور ذیل را صادر میکنیم:
ng new SignalRCore2Client
اکنون که در پوشهی ریشهی SignalRCore2Sample قرار داریم، اگر در خط فرمان، دستور . code را صادر کنیم، VSCode هر دو پوشهی وب و client را با هم در اختیار ما قرار میدهد:
تکمیل پیشنیازهای برنامهی وب
پس از ایجاد ساختار اولیهی برنامههای وب ASP.NET Core و کلاینت Angular، اکنون نیاز است وابستگی جدید AspNetCore.SignalR را به آن معرفی کنیم. به همین جهت به فایل SignalRCore2WebApp.csproj مراجعه کرده و تغییرات ذیل را به آن اعمال میکنیم:
<Project Sdk="Microsoft.NET.Sdk.Web"> <PropertyGroup> <TargetFramework>netcoreapp2.0</TargetFramework> </PropertyGroup> <ItemGroup> <PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.0" /> <PackageReference Include="Microsoft.AspNetCore.SignalR" Version="1.0.0-alpha1-final" /> </ItemGroup> <ItemGroup> <DotNetCliToolReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Tools" Version="2.0.0" /> <DotNetCliToolReference Include="Microsoft.DotNet.Watcher.Tools" Version="2.0.0" /> </ItemGroup> </Project>
پس از این تغییرات، دستور ذیل را در خط فرمان صادر میکنیم تا وابستگیهای پروژه نصب شوند:
dotnet restore
یک نکته: نگارش فعلی افزونهی #C مخصوص VSCode، با تغییر فایل csproj و restore وابستگیهای آن نیاز دارد یکبار آنرا بسته و سپس مجددا اجرا کنید، تا اطلاعات intellisense خود را به روز رسانی کند. بنابراین اگر VSCode بلافاصله کلاسهای مرتبط با بستههای جدید را تشخیص نمیدهد، علت صرفا این موضوع است.
پس از بازیابی وابستگیها، به ریشهی پروژهی برنامهی وب وارد شده و دستور ذیل را صادر کنید:
dotnet watch run
تکمیل برنامهی وب جهت ارسال پیامهایی به کلاینتهای متصل به آن
پس از افزودن وابستگیهای مورد نیاز، بازیابی و build برنامه، اکنون نوبت به تعریف یک Hub است، تا از طریق آن بتوان پیامهایی را به کلاینتهای متصل ارسال کرد. به همین جهت یک پوشهی جدید را به نام Hubs به پروژهی وب افزوده و سپس کلاس جدید MessageHub را به صورت ذیل به آن اضافه میکنیم:
using System.Threading.Tasks; using Microsoft.AspNetCore.SignalR; namespace SignalRCore2WebApp.Hubs { public class MessageHub : Hub { public Task Send(string message) { return Clients.All.InvokeAsync("Send", message); } } }
پس از تعریف این Hub، نیاز است به کلاس Startup مراجعه کرده و دو تغییر ذیل را اعمال کنیم:
الف) ثبت و معرفی سرویس SignalR
ابتدا باید SignalR را فعالسازی کرد. به همین جهت نیاز است سرویسهای آنرا به صورت یکجا توسط متد الحاقی AddSignalR در متد ConfigureServices به نحو ذیل معرفی کرد:
public void ConfigureServices(IServiceCollection services) { services.AddSignalR(); services.AddMvc(); }
ب) ثبت مسیریابی دسترسی به Hub
پس از تعریف Hub، مرحلهی بعدی، مشخص سازی نحوهی دسترسی به آن است. به همین جهت در متد Configure، به نحو ذیل Hub را معرفی کرده و سپس یک path را برای آن مشخص میکنیم:
public void Configure(IApplicationBuilder app, IHostingEnvironment env) { app.UseSignalR(routes => { routes.MapHub<MessageHub>(path: "message"); });
http://localhost:5000/message
انتشار پیامهایی به تمام کاربران متصل به برنامه
آدرس فوق به تنهایی کار خاصی را انجام نمیدهد. از آن جهت اتصال کلاینتهای برنامه استفاده میشود و این کلاینتها پیامهای رسیدهی از طرف برنامه را از این آدرس دریافت خواهند کرد. بنابراین مرحلهی بعد، ارسال تعدادی پیام به سمت کلاینتها است. برای این منظور به HomeController برنامهی وب مراجعه کرده و آنرا به نحو ذیل تغییر میدهیم:
using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.SignalR; using SignalRCore2WebApp.Hubs; namespace SignalRCore2WebApp.Controllers { public class HomeController : Controller { private readonly IHubContext<MessageHub> _messageHubContext; public HomeController(IHubContext<MessageHub> messageHubContext) { _messageHubContext = messageHubContext; } public IActionResult Index() { return View(); // show the view } [HttpPost] public async Task<IActionResult> Index(string message) { await _messageHubContext.Clients.All.InvokeAsync("Send", message); return View(); } } }
در این مثال ابتدا View ذیل نمایش داده میشود:
@{ ViewData["Title"] = "Home Page"; } <form method="post" asp-action="Index" asp-controller="Home" role="form"> <div class="form-group"> <label label-for="message">Message: </label> <input id="message" name="message" class="form-control"/> </div> <button class="btn btn-primary" type="submit">Send</button> </form>
تکمیل برنامهی کلاینت Angular جهت نمایش پیامهای رسیدهی از طرف سرور
تا اینجا ساختار ابتدایی برنامهی Angular را توسط Angular CLI ایجاد کردیم. اکنون نیاز است وابستگی سمت کلاینت SignalR Core را نصب کنیم. به همین جهت از طریق خط فرمان به پوشهی SignalRCore2Client وارد شده و دستور ذیل را صادر کنید:
npm install @aspnet/signalr-client --save
کلاینت رسمی signalr، هم جاوا اسکریپتی است و هم تایپاسکریپتی. به همین جهت به سادگی توسط یک برنامهی تایپ اسکریپتی Angular قابل استفاده است. کلاسهای آنرا در مسیر node_modules\@aspnet\signalr-client\dist\src میتوانید مشاهده کنید.
در ابتدا، فایل app.component.ts را به نحو ذیل تغییر میدهیم:
import { Component, OnInit } from "@angular/core"; import { HubConnection } from "@aspnet/signalr-client"; @Component({ selector: "app-root", templateUrl: "./app.component.html", styleUrls: ["./app.component.css"] }) export class AppComponent implements OnInit { hubPath = "http://localhost:5000/message"; messages: string[] = []; ngOnInit(): void { const connection = new HubConnection(this.hubPath); connection.on("send", data => { this.messages.push(data); }); connection.start().then(() => { // connection.invoke("send", "Hello"); console.log("connected."); }); } }
آرایهی messages را به نحو ذیل توسط یک حلقه در قالب این کامپوننت نمایش خواهیم داد:
<div> <h1> The messages from the server: </h1> <ul> <li *ngFor="let message of messages"> {{message}} </li> </ul> </div>
ng serve -o
همانطور که مشاهده میکنید، پیام خطای ذیل را صادر کردهاست:
Failed to load http://localhost:5000/message: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:4200' is therefore not allowed access.
برای این منظور به فایل آغازین برنامهی وب مراجعه کرده و سرویسهای AddCors را به مجموعهی سرویسهای برنامه اضافه میکنیم:
public void ConfigureServices(IServiceCollection services) { services.AddSignalR(); services.AddCors(options => { options.AddPolicy("CorsPolicy", builder => builder .AllowAnyOrigin() .AllowAnyMethod() .AllowAnyHeader() .AllowCredentials()); }); services.AddMvc(); }
public void Configure(IApplicationBuilder app, IHostingEnvironment env) { app.UseCors(policyName: "CorsPolicy");
در آخر برای آزمایش برنامه، به آدرس http://localhost:5000 یا همان برنامهی وب، مراجعه کرده و پیامی را ارسال کنید. بلافاصله مشاهده خواهید کرد که این پیام توسط کلاینت Angular دریافت شده و نمایش داده میشود:
کدهای کامل این مثال را از اینجا میتوانید دریافت کنید: SignalRCore2Sample.zip
برای اجرا آن، ابتدا به پوشهی SignalRCore2WebApp مراجعه کرده و دو فایل bat آنرا به ترتیب اجرا کنید. اولی وابستگیهای برنامه را بازیابی میکند و دومی برنامه را بر روی پورت 5000 ارائه میدهد.
سپس به پوشهی SignalRCore2Client مراجعه کرده و در آنجا نیز دو فایل bat ابتدایی آنرا به ترتیب اجرا کنید. اولی وابستگیهای برنامهی Angular را بازیابی میکند و دومی برنامهی Angular را بر روی پورت 4200 اجرا خواهد کرد.
به زبان ساده، وقتی شما متغیر جدیدی را ایجاد میکنید، با توجه به نوع (Type) آن متغیر، "مقدار" متغیر شما در Stack یا Heap قرار خواهد گرفت.
Stack
این ساختمان داده، داخل Memory پیاده سازی شده است و تعدادی از متغیرهایی را که ما داخل کد ایجاد میکنیم، در این نوع ساختمان داده از Memory نگهداری میشوند.
شرط قرار گرفتن مقدار یک متغیر داخل Stack این است که متغیر از نوع Value Type باشد. در زبان #C، بطور کلی Struct و Enumها Value Type هستند و بصورت پیشفرض داخل Stack قرار میگیرند. تمامی ValueTypeها در #C، بطور implicit از System.ValueType ارث بری میکنند.
Typeهای زیر، Value Typeهای پیشفرض تعریف شدهی در زبان #C هستند که به آنها Simple Type نیز گفته میشوند:
Represents | Type |
Boolean value | bool |
8-bit unsigned integer | byte |
16-bit Unicode character | char |
128-bit precise decimal values with 28-29 significant digits | decimal |
64-bit double-precision floating point type | double |
32-bit single-precision floating point type | float |
32-bit signed integer type | int |
64-bit signed integer type | long |
8-bit signed integer type | sbyte |
16-bit signed integer type | short |
32-bit unsigned integer type | uint |
64-bit unsigned integer type | ulong |
16-bit unsigned integer type | ushort |
اگر سورس هرکدام از این تایپها مانند Int32 را در ریپازیتوری CoreFX مایکروسافت بررسی کنید، متوجه خواهید شد که تمامی این تایپها از نوع Struct تعریف شدهاند و همانطور که گفتیم، بطور پیشفرض، Structها داخل Stack قرار خواهند گرفت.
طول عمر متغیرهایی که داخل Stack قرار گرفتهاند، منحصر به پایان اجرای یک متد است. بدین معنا که بعد از به پایان رسیدن یک متد، تمامی متغیرهای مورد استفاده در آن متد، از حافظه Stack بطور خودکار حذف خواهند شد. متغیرهایی که داخل Stack قرار میگیرند، نوع و حجم مقادیرشان بر اساس Type ای دارند، در زمان Compile-Time مشخص است.
public static int Add(int number1, int number2) { // number1 is on the stack (function parameter) // number2 is on the stack (function parameter) int sum = number1 + number2; // sum is on the stack (local variable) return sum; }
در زبان #C و در مرحله Compile-Time، کدها به زبان IL (مخفف Intermediate Language) ترجمه میشوند که با نامهای MSIL (مخفف Microsoft Intermediate Language ) و CIL (مخفف Common Intermediate Language ) نیز، این زبان شناخته میشود. ساختار این زبان Stack-based بوده و با شناخت آن، با مفهوم Stack نیز بهتر میتوانیم آشنا شویم.
IL زبانی است که CLR (مخفف Common Language Runtime) را که همان Runtime مایکروسافت است، شناخته و اجرا میکند. قابل ذکر است که Runtime مایکروسافت Open-Source بوده و سورس آن با نام CoreCLR در گذشته از این آدرس و در حال حاضر با نام Runtime از این آدرس قابل دسترسی است.
C:\Program Files (x86)\Microsoft SDKs\Windows\{version}\Bin\ildasm.exe
.method private hidebysig static int32 Add(int32 number1, int32 number2) cil managed { .locals init (int32 V_0, int32 V_1) IL_0001: ldarg.0 // Stack is: [2] IL_0002: ldarg.1 // Stack is: [2, 5] IL_0003: add // Stack is: [7] IL_0004: stloc.0 // Stack is: [] and V_0's value is: 7 IL_0005: ldloc.0 // Stack is: [7] IL_0006: stloc.1 // Stack is: [] and V_1's value is: 7 IL_0009: ldloc.1 // Stack is: [7] IL_000a: ret // Return [7] }
میتوانید لیست دستورات مورد استفاده در CIL را از اینجا ببینید.
در ادامه، خط به خط، خروجی حاصل را بررسی میکنیم:
2- از کلمه کلیدی ldarg (مخفف Load Argument) برای لود کردن آرگومان یا همان پارامتر ورودی متد، داخل Stack استفاده میشود.
• ldarg.0 به معنای لود کردن پارامتر ورودی اول، داخل Stack است و با فراخوانی آن، Stack Frame دارای یک عضو که مقدار آن 2 است، میشود.
3- با استفاده از کلمه کلیدی add، مقادیر موجود در Stack با یکدیگر جمع میشوند و Stack Frame دارای یک عضو که مقدار آن 7 است، میشود.
4- با استفاده از کلمه کلیدی stloc (مخفف Store Local)، آخرین عضو موجود در Stack، داخل متغیر محلی ذکر شده، قرار گرفته و ذخیره میشود.
5- با استفاده از کلمه کلیدی ldloc (مخفف Load Local)، میتوان متغیر محلی ذخیره شده را داخل Stack قرار داد.
6- در نهایت، مقدار 7، داخل متغیر 1 یا همان V_1 با دستور stloc.1 بار دیگر ذخیره، با ldloc.1 لود شده و با استفاده از دستور ret، برگشت داده میشود.
* نکته: اگر کدها را بطور دقیق بررسی کرده باشید، احتمالا فکر کرده اید که چه نیازی به ایجاد یک متغیر اضافی و ریختن نتیجه داخل آن و سپس برگشت دادن نتیجه، در مرحله 6 است؟!
* نکته: احتمالا تا به اینجا دلیل بوجود آمدن StackOverflowException را متوجه شده باشید. فضای Stack محدود است. این فضا در سیستمهای 32 بیت برابر با 1 مگابایت و در سیستمهای 64 بیت برابر با 4 مگابایت است (Reference). اگر حجم متغیرهایی که روی استک Push میشوند، این محدودیت را رد کنند و یا اگر یک متد بطور دائم خودش را صدا بزند (Recursive) و هیچگاه از آن خارج نشود، با خطای StackOverflowException مواجه میشوید.
Heap
.Heap: a group of things placed, thrown, or lying one on another
در مقابل ساختار ترتیبی و منظم Stack، ساختار Heap قرار دارد. Heap قسمتی از حافظه است که ساختار، ترتیب و Layout خاصی ندارد.
وقتی یک متغیر از نوع string را ایجاد میکنیم، مقدار آن داخل Heap و Memory-Address آن متغیر روی Heap، در Stack نگه داری میشود:
public static void SayHi() { string name = "Moien"; }
در این مثال، چون string یک class است، مقدار آن داخل heap ذخیره شده و آدرس آن قسمت (segment) از memory، روی Stack قرار میگیرد:
.method private hidebysig static void SayHi() cil managed { .locals init (string V_0) IL_0001: ldstr "Moien" // Stack is: [memory-address of string in heap] IL_0006: stloc.0 IL_0007: ret }
به متغیرهایی که مقادیرشان داخل Heap ذخیره میشوند، Reference-Type گفته میشود.
* نکته: در این مثال متغیری به نام name ایجاد شده که از آن هیچ استفادهای نشده است. در زمان JIT-Compilation، با توجه با Optimizationهای موجود در سطح CLR، این متد بطور کلی اضافه تشخیص داده شده و از آن صرفنظر خواهد شد.
Boxing and Unboxing
به فرایند تبدیل یک Value-Type مانند int که بصورت پیشفرض داخل Stack ذخیره میشود، به یک object که در داخل Heap ذخیره میشود، Boxing گفته میشود. انجام این عمل باعث allocation بر روی memory میشود که سربار زیادی دارد.
با انجام عمل Boxing، قادر خواهیم بود تا بعنوان مثال یک عدد را بر خلاف روال عادی آن، روی Heap ذخیره کنیم:
public static void Boxing() { const int number = 5; object boxedNumber = number; // implicit boxing using implicit cast object boxedNumber = (object)number; // explicit boxing using direct cast }
در ابتدا عدد 5 روی Stack ذخیره شده بود، اما با Box کردن آن، یعنی قرار دادن مقدار آن داخل یک object، مقدار از Stack به Heap انتقال داده شده و allocation اتفاق خواهد افتاد:
.method public hidebysig static void Boxing() cil managed { .locals init (object V_0) IL_0001: ldc.i4.5 // Stack is: [5] IL_0002: box [System.Runtime]System.Int32 // Stack is: [memory-address of 5 in heap] IL_0007: stloc.0 IL_0008: ret }
به عکس این عمل، یعنی تبدیل یک Reference-Type به یک Value-Type، اصطلاحا Unboxing گفته میشود:
public static void Unboxing() { object boxedNumber = 5; int number = (int)boxedNumber; }
که نتیجه آن، به این صورت خواهد بود:
.method public hidebysig static void Unboxing() cil managed { .locals init (object V_0, int32 V_1) IL_0001: ldc.i4.5 // Stack is: [5] IL_0002: box [System.Runtime]System.Int32 // Stack is: [memory-address of 5 in heap] IL_0007: stloc.0 // Stack is: [] IL_0008: ldloc.0 // Stack is: [memory-address of 5 in heap] IL_0009: unbox.any [System.Runtime]System.Int32 // Stack is: [5] IL_000e: stloc.1 // Stack is: [] IL_000f: ret }
تلاش تیمهای مایکروسافت طی سالهای اخیر، باعث افزایش Performance فوق العاده در NET Core. و ASP.NET Core شده است. یکی از دلایل این Performance، جلوگیری بسیار زیاد از allocation در کدهای خود NET. است، که این امر به واسطه اولویت قرار دادن استفاده از Structها میسر گردیده است.
برخلاف Stack که طول عمر متغیرهای موجود در آن، در انتهای یک متد پایان مییابند، متغیرهای allocate شدهی در Heap به این شکل نبوده و در صورت حذف نکردن آنها بصورت دستی، تا پایان طول عمر اجرای برنامه داخل memory باقی خواهند ماند. اینجا، جاییست که Garbage Collector در NET. وارد عمل میشود.
- تنظیمات برنامه را از چندین منبع مختلف خوانده و آنها را یکی کنید.
- تنظیمات را بر اساس تنظیمات مختلف محیطی برنامه، بارگذاری کنید.
- امکان نگاشت اطلاعات خوانده شدهی از فایلهای کانفیگ به کلاسها پیش بینی شدهاست.
- امکان بارگذاری مجدد فایلهای کانفیگ درصورت تغییر، بدون ریاستارت کل برنامه وجود دارد.
- امکان تزریق وابستگیهای تنظیمات برنامه، به قسمتهای مختلف آن پیش بینی شدهاست.
نصب پیشنیاز خواندن تنظیمات برنامه از یک فایل JSON
برای شروع به کار با خواندن تنظیمات برنامه در ASP.NET Core، نیاز است ابتدا بستهی نیوگت Microsoft.Extensions.Configuration.Json را نصب کنیم.
برای این منظور بر روی گره references کلیک راست کرده و گزینهی manage nuget packages را انتخاب کنید. سپس در برگهی browse آن Microsoft.Extensions.Configuration.Json را جستجو کرده و نصب نمائید:
البته همانطور که در تصویر مشاهده میکنید، اگر صرفا Microsoft.Extensions.Configuration را جستجو کنید (بدون ذکر JSON)، بستههای مرتبط با خواندن فایلهای کانفیگ از نوع XML و یا حتی INI را هم خواهید یافت.
انجام این مراحل معادل هستند با افزودن یک سطر ذیل به فایل project.json برنامه:
{ "dependencies": { //same as before "Microsoft.Extensions.Configuration.Json": "1.0.0" },
افزودن یک فایل کانفیگ JSON دلخواه
بر روی پروژه کلیک راست کرده و از طریق منوی add->new item یک فایل خالی جدید را به نام appsettings.json ایجاد کنید (نام این فایل دلخواه است)؛ با این محتوا:
{ "Key1": "Value1", "Auth": { "Users": [ "Test1", "Test2", "Test3" ] }, "Logging": { "IncludeScopes": false, "LogLevel": { "Default": "Debug", "System": "Information", "Microsoft": "Information" } } }
<appSettings> <add key="Logging-IncludeScopes" value="false" /> <add key="Logging-Level-Default" value="verbose" /> <add key="Logging-Level-System" value="Information" /> <add key="Logging-Level-Microsoft" value="Information" /> </appSettings>
در فایل JSON فوق، نمونهای از key/valueها، آرایهها و اطلاعات چندین سطحی را مشاهده میکنید.
خواندن فایل تنظیمات appsettings.json در برنامه
پس از نصب پیشنیاز خواندن فایلهای کانفیگ از نوع JSON، به فایل آغازین برنامه مراجعه کرده و سازندهی جدیدی را به آن اضافه کنید:
public class Startup { public IConfigurationRoot Configuration { set; get; } public Startup(IHostingEnvironment env) { var builder = new ConfigurationBuilder() .SetBasePath(env.ContentRootPath) .AddJsonFile("appsettings.json"); Configuration = builder.Build(); }
الف) این خواندن، در سازندهی کلاس آغازین برنامه و پیش از تمام تنظیمات دیگر باید انجام شود.
ب) جهت در معرض دید قرار دادن اطلاعات خوانده شده، آنرا به یک خاصیت عمومی انتساب دادهایم.
ج) متد SetBasePath جهت مشخص کردن محل یافتن فایل appsettings.json ذکر شدهاست. این اطلاعات را میتوان از سرویس توکار IHostingEnvironment و خاصیت ContentRootPath آن دریافت کرد. همانطور که ملاحظه میکنید، این تزریق وابستگی نیز به صورت خودکار توسط ASP.NET Core مدیریت میشود.
دسترسی به تنظیمات خوانده شده توسط اینترفیس IConfigurationRoot
تا اینجا موفق شدیم تا تنظیمات خوانده شده را به خاصیت عمومی Configuration از نوع IConfigurationRoot انتساب دهیم. اما ساختار ذخیره شدهی در این اینترفیس به چه صورتی است؟
همانطور که مشاهده میکنید، هر سطح از سطح قبلی آن با : جدا شدهاست. همچنین اعضای آرایه، دارای ایندکسهای 0: و 1: و 2: هستند. بنابراین برای خواندن این اطلاعات میتوان نوشت:
var key1 = Configuration["Key1"]; var user1 = Configuration["Auth:Users:0"]; var authUsers = Configuration.GetSection("Auth:Users").GetChildren().Select(x => x.Value).ToArray(); var loggingIncludeScopes = Configuration["Logging:IncludeScopes"]; var loggingLoggingLogLevelDefault = Configuration["Logging:LogLevel:Default"];
یک نکته: خاصیت Configuration، دارای متد GetValue نیز هست که توسط آن میتوان نوع مقدار دریافتی و یا حتی مقدار پیش فرضی را در صورت عدم وجود این key، مشخص کرد:
var val = Configuration.GetValue<int>("key-name", defaultValue: 10);
سرویس IConfigurationRoot قابل تزریق است
در قسمت قبل، سرویسها و تزریق وابستگیها را بررسی کردیم. نکتهی جالبی را که میتوان به آن اضافه کرد، قابلیت تزریق خاصیت عمومی
public class Startup { public IConfigurationRoot Configuration { set; get; }
الف) اعلام موجودیت IConfigurationRoot به IoC Container
اگر از استراکچرمپ استفاده میکنید، باید مشخص کنید، زمانیکه IConfigurationRoot درخواست شد، آنرا چگونه باید از خاصیت مرتبط با آن دریافت کند:
var container = new Container(); container.Configure(config => { config.For<IConfigurationRoot>().Singleton().Use(() => Configuration);
public IServiceProvider ConfigureServices(IServiceCollection services) { services.AddSingleton<IConfigurationRoot>(provider => { return Configuration; });
ب) فایل project.json کتابخانهی Core1RtmEmptyTest.Services را گشوده و وابستگی Microsoft.Extensions.Configuration.Abstractions را به آن اضافه کنید:
{ "dependencies": { //same as before "Microsoft.Extensions.Configuration.Abstractions": "1.0.0" }
ج) سپس فایل MessagesService.cs را گشوده و این اینترفیس را به سازندهی سرویس MessagesService تزریق میکنیم:
public interface IMessagesService { string GetSiteName(); } public class MessagesService : IMessagesService { private readonly IConfigurationRoot _configurationRoot; public MessagesService(IConfigurationRoot configurationRoot) { _configurationRoot = configurationRoot; } public string GetSiteName() { var key1 = _configurationRoot["Key1"]; return $"DNT {key1}"; } }
اکنون اگر برنامه را اجرا کنید، با توجه به اینکه میان افزار Run از این سرویس سفارشی استفاده میکند:
public void Configure( IApplicationBuilder app, IHostingEnvironment env, IMessagesService messagesService) { app.Run(async context => { var siteName = messagesService.GetSiteName(); await context.Response.WriteAsync($"Hello {siteName}"); }); }
خواندن تنظیمات از حافظه
الزاما نیازی به استفاده از فایلهای JSON و یا XML در اینجا وجود ندارد. ابتداییترین حالت کار با بستهی Microsoft.Extensions.Configuration، متد AddInMemoryCollection آن است که در اینجا میتوان لیستی از key/valueها را ذکر کرد:
var builder = new ConfigurationBuilder() .AddInMemoryCollection(new[] { new KeyValuePair<string,string>("the-key", "the-value"), });
var theValue = Configuration["the-key"];
امکان بازنویسی تنظیمات انجام شده، بسته به شرایط محیطی
در اینجا محدود به یک فایل JSON و یک فایل تنظیمات برنامه، نیستیم. برای کار با ConfigurationBuilder میتوان از Fluent interface آن استفاده کرد و به هر تعدادی که نیاز بود، متدهای خواندن از فایلهای کانفیگ دیگر را اضافه کرد:
public class Startup { public IConfigurationRoot Configuration { set; get; } public Startup(IHostingEnvironment env) { var builder = new ConfigurationBuilder() .SetBasePath(env.ContentRootPath) .AddInMemoryCollection(new[] { new KeyValuePair<string,string>("the-key", "the-value"), }) .AddJsonFile("appsettings.json", reloadOnChange: true, optional: false) .AddJsonFile($"appsettings.{env}.json", optional: true); Configuration = builder.Build(); }
برای مثال در اینجا آخرین AddJsonFile تعریف شده، بنابر متغیر محیطی فعلی به appsettings.development.json تفسیر شده و در صورت وجود این فایل (با توجه به optional بودن آن) اطلاعات آن دریافت گردیده و اطلاعات مشابه فایل appsettings.json قبلی را بازنویسی میکند.
امکان دسترسی به متغیرهای محیطی سیستم عامل
در انتهای زنجیرهی ConfigurationBuilder میتوان متد AddEnvironmentVariables را نیز ذکر کرد:
var builder = new ConfigurationBuilder() .SetBasePath(env.ContentRootPath) .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true) .AddEnvironmentVariables();
امکان نگاشت تنظیمات برنامه به کلاسهای متناظر
کار کردن با key/valueهای رشتهای، هرچند روش پایهای استفادهی از تنظیمات برنامه است، اما آنچنان refactoring friendly نیست. در ASP.NET Core امکان تعریف تنظیمات strongly typed نیز پیش بینی شدهاست. برای این منظور باید مراحل زیر طی شوند:
به عنوان نمونه تنظیمات فرضی smtp ذیل را به انتهای فایل appsettings.json اضافه کنید:
{ "Key1": "Value1", "Auth": { "Users": [ "Test1", "Test2", "Test3" ] }, "Logging": { "IncludeScopes": false, "LogLevel": { "Default": "Debug", "System": "Information", "Microsoft": "Information" } }, "Smtp": { "Server": "0.0.0.1", "User": "user@company.com", "Pass": "123456789", "Port": "25" } }
در این کتابخانهی جدید که محل نگهداری ViewModelهای برنامه خواهد بود، کلاس معادل قسمت smtp فایل config فوق را اضافه کنید:
namespace Core1RtmEmptyTest.ViewModels { public class SmtpConfig { public string Server { get; set; } public string User { get; set; } public string Pass { get; set; } public int Port { get; set; } } }
سپس به پروژهی Core1RtmEmptyTest مراجعه کرده و بر روی گره references آن کلیک راست کنید. در اینجا گزینهی add reference را انتخاب کرده و سپس Core1RtmEmptyTest.ViewModels را انتخاب کنید، تا اسمبلی آنرا بتوان در پروژهی جاری استفاده کرد.
انجام اینکار معادل است با افزودن یک سطر ذیل به فایل project.json پروژه:
{ "dependencies": { // same as before "Core1RtmEmptyTest.ViewModels": "1.0.0-*" },
و سپس در کلاس آغازین برنامه و متد ConfigureServices آن، نحوهی نگاشت قسمت Smtp فایل کانفیگ را مشخص کنید:
public IServiceProvider ConfigureServices(IServiceCollection services) { services.Configure<SmtpConfig>(options => Configuration.GetSection("Smtp").Bind(options));
سپس برای استفاده از این تنظیمات strongly typed (برای نمونه در لایه سرویس برنامه)، ابتدا ارجاعی را به پروژهی Core1RtmEmptyTest.ViewModels به لایهی سرویس برنامه اضافه میکنیم (بر روی گره references آن کلیک راست کنید. در اینجا گزینهی add reference را انتخاب کرده و سپس Core1RtmEmptyTest.ViewModels را انتخاب کنید).
در ادامه نیاز است بستهی نیوگت جدیدی را به نام Microsoft.Extensions.Options به لایهی سرویس برنامه اضافه کنیم. به این ترتیب قسمت وابستگیهای فایل project.json این لایه چنین شکلی را پیدا میکند:
"dependencies": { "Core1RtmEmptyTest.ViewModels": "1.0.0-*", "Microsoft.Extensions.Configuration.Abstractions": "1.0.0", "Microsoft.Extensions.Options": "1.0.0", "NETStandard.Library": "1.6.0" }
public interface IMessagesService { string GetSiteName(); } public class MessagesService : IMessagesService { private readonly IConfigurationRoot _configurationRoot; private readonly IOptions<SmtpConfig> _settings; public MessagesService(IConfigurationRoot configurationRoot, IOptions<SmtpConfig> settings) { _configurationRoot = configurationRoot; _settings = settings; } public string GetSiteName() { var key1 = _configurationRoot["Key1"]; var server = _settings.Value.Server; return $"DNT {key1} - {server}"; } }
اکنون اگر برنامه را جرا کنید، این خروجی را میتوان مشاهده کرد (که در آن آدرس Server دریافت شدهی از فایل کانفیگ نیز مشخص است):
البته همانطور که در قسمت قبل نیز عنوان شد، این تزریق وابستگیها در تمام قسمتهای برنامه کار میکند. برای مثال در کنترلرها هم میتوان <IOptions<SmtpConfig را به همین نحو تزریق کرد.
نحوهی واکنش به تغییرات فایلهای کانفیگ
در نگارشهای قبلی ASP.NET، هر تغییری در فایل web.config، سبب ریاستارت شدن کل برنامه میشد که این مساله نیز خود سبب بروز مشکلات زیادی مانند از دست رفتن سشن تمام کاربران میشد.
در ASP.NET Core، برنامهی وب ما دیگر متکی به فایل web.config نبوده و همچنین میتوان چندین و چند نوع فایل config داشت. به علاوه در اینجا متدهای مرتبط معرفی فایلهای کانفیگ دارای پارامتر مخصوص reloadOnChange نیز هستند:
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
public class ChatHub : Hub { public void Send(string name, string message) { Clients.All.messageReceived(name, message); } }
قدم بعدی افزودن کدهای سمت کلاینت برای SignalR میباشد. دقت داشته باشید که کدهایی که در ادامه مشاهده خواهید کرد دقیقاً مطابق دستورالمعلهایی است که قبلاً مشاهده کردهاید. برای اینکار داخل کلاس MainActivity.java کدهای زیر را اضافه کنید:
Platform.loadPlatformComponent( new AndroidPlatformComponent() ); HubConnection connection = new HubConnection(DEFAULT_SERVER_URL); HubProxy hub = connection.createHubProxy("ChatHub"); connection.error(new ErrorCallback() { @Override public void onError(final Throwable error) { runOnUiThread(new Runnable() { public void run() { Toast.makeText(getApplicationContext(), error.getMessage(), Toast.LENGTH_LONG).show(); } }); } }); hub.subscribe(new Object() { @SuppressWarnings("unused") public void messageReceived(final String name, final String message) { runOnUiThread(new Runnable() { public void run() { Toast.makeText(getApplicationContext(), name + ": " + message, Toast.LENGTH_LONG).show(); } }); } }); connection.start() .done(new Action<Void>() { @Override public void run(Void obj) throws Exception { runOnUiThread(new Runnable() { public void run() { Toast.makeText(getApplicationContext(), "Done Connecting!", Toast.LENGTH_LONG).show(); } }); } }); connection.received(new MessageReceivedHandler() { @Override public void onMessageReceived(final JsonElement json) { runOnUiThread(new Runnable() { public void run() { JsonObject jsonObject = json.getAsJsonObject(); JsonArray jsonArray =jsonObject.getAsJsonArray("A"); Toast.makeText(getApplicationContext(), jsonArray.get(0).getAsString() + ": " + jsonArray.get(1).getAsString(), Toast.LENGTH_LONG).show(); } }); } });
همانطور که مشاهده میکنید توسط قطعه کد زیر SKD مربوطه در نسخههای قدیمی اندروید نیز بدون مشکل کار خواهد کرد:
Platform.loadPlatformComponent( new AndroidPlatformComponent() );
در ادامه توسط متد createHubProxy ارجاعی به هابی که در سمت سرور ایجاد کردیم، دادهایم:
HubProxy hub = connection.createHubProxy("ChatHub");
در ادامه نیز توسط یک روال رویدادگردان وضعیت اتصال را چک کردهایم. یعنی در زمان بروز خطا در نحوه ارتباط یک پیام بر روی صفحه نمایش داده میشود:
connection.error(new ErrorCallback() { @Override public void onError(final Throwable error) { runOnUiThread(new Runnable() { public void run() { Toast.makeText(getApplicationContext(), error.getMessage(), Toast.LENGTH_LONG).show(); } }); } });
hub.subscribe(new Object() { @SuppressWarnings("unused") public void messageReceived(final String name, final String message) { runOnUiThread(new Runnable() { public void run() { Toast.makeText(getApplicationContext(), name + ": " + message, Toast.LENGTH_LONG).show(); } }); } });
connection.start() .done(new Action<Void>() { @Override public void run(Void obj) throws Exception { runOnUiThread(new Runnable() { public void run() { Toast.makeText(getApplicationContext(), "Done Connecting!", Toast.LENGTH_LONG).show(); } }); } });
connection.received(new MessageReceivedHandler() { @Override public void onMessageReceived(final JsonElement json) { runOnUiThread(new Runnable() { public void run() { JsonObject jsonObject = json.getAsJsonObject(); JsonArray jsonArray =jsonObject.getAsJsonArray("A"); Toast.makeText(getApplicationContext(), jsonArray.get(0).getAsString() + ": " + jsonArray.get(1).getAsString(), Toast.LENGTH_LONG).show(); } }); } });
بر روی ماشینهای ویندوزی و ویندوزهای سرور، استفادهی از IIS به عنوان پروکسی درخواستها و ارسال آنها به Kestrel، روش توصیه شدهاست؛ از این جهت که حداقل قابلیتهایی مانند «port 80/443 forwarding»، مدیریت طول عمر برنامه، مدیریت مجوزهای SLL آن و خیلی از موارد دیگر توسط Kestrel پشتیبانی نمیشود.
معماری پردازش نگارشهای پیشین ASP.NET در IIS
در نگارشهای پیشین ASP.NET، همه چیز داخل پروسهای به نام w3wp.exe و یا IIS Worker Process پردازش میشود که در اصل چیزی نیست بجز همان IIS Application Pool. این AppPoolها، برنامههای ASP.NET شما را هاست میکنند و همچنین سبب وهله سازی و اجرای آنها نیز خواهند شد.
در اینجا درایور http.sys ویندوز، درخواستهای رسیده را دریافت کرده و سپس آنها را به سمت سایتهایی نگاشت شدهی به AppPoolهای مشخص، هدایت میکند.
معماری پردازش برنامههای ASP.NET Core در IIS
روش اجرای برنامههای ASP.NET Core با نگارشهای پیشین آنها کاملا متفاوت هستند؛ از این جهت که داخل پروسهی w3wp.exe اجرا نمیشوند. این برنامهها در یک پروسهی مجزای کنسول خارج از پروسهی w3wp.exe اجرا میشوند و حاوی وب سرور توکاری به نام کسترل (Kestrel) هستند.
این وب سرور، وب سروری است تماما دات نتی و به شدت برای پردازش تعداد بالای درخواستها بهینه سازی شدهاست؛ تا جایی که کارآیی آن در این یک مورد چند 10 برابر IIS است. هرچند این وب سرور فوق العاده سریع است، اما «تنها» یک وب سرور خام است و به همراه سرویسهای مدیریت وب، مانند IIS نیست.
در تصویر فوق مفهوم «پروکسی» بودن IIS را در حین پردازش برنامههای ASP.NET Core بهتر میتوان درک کرد. ابتدا درخواستهای رسیده به IIS میرسند و سپس IIS آنها را به طرف Kestrel هدایت میکند.
برنامههای ASP.NET Core، برنامههای کنسول متکی به خودی هستند که توسط دستور خط فرمان dotnet اجرا میشوند. این اجرا توسط ماژولی ویژه به نام AspNetCoreModule در IIS انجام میشود.
همانطور که در تصویر نیز مشخص است، AspNetCoreModule یک ماژول بومی IIS است و هنوز برای اجرا نیاز به IIS Application Pool دارد؛ با این تفاوت که در تنظیم AppPoolهای برنامههای ASP.NET Core، باید NET CLR Version. را به No managed code تنظیم کرد.
اینکار از این جهت صورت میگیرد که IIS در اینجا تنها نقش یک پروکسی هدایت درخواستها را به پروسهی برنامهی حاوی وب سرور Kestrel، دارد و کار آن وهله سازی NET Runtime. نیست. کار AspNetCoreModule این است که با اولین درخواست رسیدهی به برنامهی شما، آنرا بارگذاری کند. سپس درخواستهای رسیده را دریافت و به سمت برنامهی ASP.NET Core شما هدایت میکند (به این عملیات reverse proxy هم میگویند).
اگر دقت کرده باشید، برنامههای ASP.NET Core، هنوز دارای فایل web.config ایی با محتوای ذیل هستند:
<?xml version="1.0" encoding="utf-8"?> <configuration> <system.webServer> <handlers> <add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModule" resourceType="Unspecified"/> </handlers> <aspNetCore processPath="%LAUNCHER_PATH%" arguments="%LAUNCHER_ARGS%" stdoutLogEnabled="false" stdoutLogFile=".\logs\stdout" forwardWindowsAuthToken="false"/> </system.webServer> </configuration>
یک نکته: در زمان publish برنامه، تنظیم و تبدیل مقادیر LAUNCHER_PATH و LAUNCHER_ARGS به معادلهای اصلی آنها صورت میگیرد (در ادامه مطلب بحث خواهد شد).
آیا واقعا هنوز نیازی به استفادهی از IIS وجود دارد؟
هرچند میتوان Kestrel را توسط یک IP و پورت مشخص، عمومی کرد و استفاده نمود، اما حداقل در ویندوز چنین توصیهای نمیشود و بهتر است از IIS به عنوان یک front end proxy استفاده کرد؛ به این دلایل:
- اگر میخواهید چندین برنامه را بر روی یک وب سرور که از طریق پورتهای 80 و 443 ارائه میشوند داشته باشید، نمیتوانید از Kestrel به صورت مستقیم استفاده کنید؛ زیرا از مفهوم host header routing که قابلیت ارائهی چندین برنامه را از طریق پورت 80 و توسط یک IP میسر میکند، پشتیبانی نمیکند. برای اینکار نیاز به IIS و یا در حقیقت درایور http.sys ویندوز است.
- IIS خدمات قابل توجهی را به برنامهی شما ارائه میکند. برای مثال با اولین درخواست رسیده، به صورت خودکار آنرا اجرا و بارگذاری میکند؛ به همراه تمام مدیریتهای پروسهای که در اختیار برنامههای ASP.NET در طی سالیان سال قرار داشتهاست. برای مثال اگر پروسهی برنامهی شما در اثر استثنایی کرش کرد، دوباره با درخواست بعدی رسیده، حتما برنامه را بارگذاری و آمادهی خدمات دهی مجدد میکند.
- در اینجا میتوان تنظیمات SSL را بر روی IIS انجام داد و سپس درخواستهای معمولی را به Kestrel ارسال کرد. به این ترتیب با یک مجوز میتوان چندین برنامهی Kestrel را مدیریت کرد.
- IISهای جدید به همراه ماژولهای بومی بسیار بهینه و کم مصرفی برای مواردی مانند gzip compression of static content, static file caching, Url Rewriting هستند که با فعال سازی آنها میتوان از این قابلیتها، در برنامههای ASP.NET Core نیز استفاده کرد.
نحوهی توزیع برنامههای ASP.NET Core به IIS
روش اول: استفاده از دستور خط فرمان dotnet publish
برای این منظور به ریشهی پروژهی خود وارد شده و دستور dotnet publish را با توجه به پارامترهای ذیل اجرا کنید:
dotnet publish --framework netcoreapp1.0 --output "c:\temp\mysite" --configuration Release
{ "publishOptions": { "include": [ "wwwroot", "Features", "appsettings.json", "web.config" ] }, "scripts": { "precompile": [ "dotnet bundle" ], "prepublish": [ //"bower install" ], "postpublish": [ "dotnet publish-iis --publish-folder %publish:OutputPath% --framework %publish:FullTargetFramework%" ] } }
پس از انتقال این فایلها به سرور، مابقی مراحل آن مانند قبل است. یک Application جدید تعریف شده و سپس ابتدا مسیر آن مشخص میشود و نکتهی اصلی، انتخاب AppPool ایی است که پیشتر شرح داده شد:
برنامههای ASP.NET Core باید به AppPool ایی تنظیم شوند که NET CLR Version. آنها No Managed Code است. همچنین بهتر است به ازای هر برنامهی جدید یک AppPool مجزا را ایجاد کنید تا کرش یک برنامه تاثیر منفی را بر روی برنامهی دیگری نگذارد.
روش دوم: استفاده از ابزار Publish خود ویژوال استودیو
اگر علاقمند هستید که روش خط فرمان فوق را توسط ابزار publish ویژوال استودیو انجام دهید، بر روی پروژه در solution explorer کلیک راست کرده و گزینهی publish را انتخاب کنید. در صفحهای که باز میشود، بر روی گزینهی custom کلیک کرده و نامی را وارد کنید. از این نام پروفایل، جهت ساده سازی مراحل publish، در دفعات آتی فراخوانی آن استفاده میشود.
در صفحهی بعدی اگر گزینهی file system را انتخاب کنید، دقیقا همان مراحل روش اول تکرار میشوند:
سپس میتوانید فریم ورک برنامه و نوع ارائه را مشخص کنید:
و در آخر کار، Publish به این پوشهی مشخص شده که به صورت پیش فرض در ذیل پوشهی bin برنامهاست، صورت میگیرد.
روش عیب یابی راه اندازی اولیهی برنامههای ASP.NET Core
در اولین سعی در اجرای برنامهی ASP.NET Core بر روی IIS به این خطا رسیدم:
در event viewer ویندوز چیزی ثبت نشده بود. اولین کاری را که در این موارد میتوان انجام داد به این صورت است. از طریق خط فرمان به پوشهی publish برنامه وارد شوید (همان پوشهای که توسط IIS عمومی شدهاست). سپس دستور dotnet prog.dll را صادر کنید. در اینجا prog.dll نام dll اصلی برنامه یا همان نام پروژه است:
همانطور که مشاهده میکنید، برنامه به دنبال پوشهی bower_components ایی میگردد که کار publish آن انجام نشدهاست (این پوشه در تنظیمات آغازین برنامه عمومی شدهاست و در لیست include قسمت publishOptions فایل project.json فراموش شدهاست).
روش دوم، فعال سازی stdoutLogEnabled موجود در فایل وب کانفیگ، به true است. در اینجا web.config نهایی تولیدی توسط عملیات publish را مشاهده میکنید که در آن پارامترهای processPath و arguments مقدار دهی شدهاند (همان قسمت postpublish فایل project.json). در اینجا مقدار stdoutLogEnabled به صورت پیش فرض false است. اگر true شود، همان خروجی تصویر فوق را در پوشهی logs خواهید یافت:
<?xml version="1.0" encoding="utf-8"?> <configuration> <system.webServer> <handlers> <add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModule" resourceType="Unspecified" /> </handlers> <aspNetCore processPath="dotnet" arguments=".\Core1RtmEmptyTest.dll" stdoutLogEnabled="true" stdoutLogFile=".\logs\stdout" forwardWindowsAuthToken="false" /> </system.webServer> </configuration>
Warning: Could not create stdoutLogFile \\?\D:\Prog\1395\Core1RtmEmptyTest\src\Core1RtmEmptyTest\bin\Release\PublishOutput\logs\stdout_10064_201672893654.log, ErrorCode = -2147024893.
حداقلهای یک هاست ویندوزی که میخواهد برنامههای ASP.NET Core را ارائه دهد
پس از نصب IIS، نیاز است ASP.NET Core Module نیز نصب گردد. برای اینکار اگر بستهی NET Core Windows Server Hosting. را نصب کنید، کافی است:
https://go.microsoft.com/fwlink/?LinkId=817246
این بسته به همراه NET Core Runtime, .NET Core Library. و ASP.NET Core Module است. همچنین همانطور که عنوان شد، برنامههای ASP.NET Core باید به AppPool ایی تنظیم شوند که NET CLR Version. آنها No Managed Code است. اینها حداقلهای راه اندازی یک برنامهی ASP.NET Core بر روی سرورهای ویندوزی هستند.
هنوز فایل app_offline.htm نیز در اینجا معتبر است
یکی از خواص ASP.NET Core Module، پردازش فایل خاصی است به نام app_offline.htm. اگر این فایل را در ریشهی سایت قرار دهید، برنامه پردازش تمام درخواستهای رسیده را قطع خواهد کرد و سپس پروسهی برنامه خاتمه مییابد. هر زمانیکه این فایل حذف شد، مجددا با درخواست بعدی رسیده، برنامه آمادهی پاسخگویی میشود.
https://www.dntips.ir/(xxxxxx)/page.aspx
Response.ApplyAppPathModifier
Set-Cookie: <name>=<value>[; <Max-Age>=<age>] [; expires=<date>][; domain=<domain_name>] [; path=<some_path>][; secure][; HttpOnly]
<httpCookies httpOnlyCookies="true" …>
HttpCookie myCookie = new HttpCookie("myCookie"); myCookie.HttpOnly = true; Response.AppendCookie(myCookie);