مطالب
مستند سازی ASP.NET Core 2x API توسط OpenAPI Swagger - قسمت سوم - تکمیل مستندات یک API با کامنت‌ها
در قسمت قبل موفق شدیم بر اساس OpenAPI specification endpoint تنظیم شده، رابط کاربری خودکاری را توسط ابزار Swagger-UI تولید کنیم. در ادامه می‌خواهیم این مستندات تولید شده را غنی‌تر کرده و کیفیت آن‌را بهبود دهیم.


استفاده از XML Comments برای بهبود کیفیت مستندات API

نوشتن توضیحات XML ای برای متدها و پارامترها در پروژه‌های دات‌نتی، روشی استاندارد و شناخته شده‌است. برای نمونه در AuthorsController، می‌خواهیم توضیحاتی را به اکشن متد GetAuthor آن اضافه کنیم:
/// <summary>
/// Get an author by his/her id
/// </summary>
/// <param name="authorId">The id of the author you want to get</param>
/// <returns>An ActionResult of type Author</returns>
[HttpGet("{authorId}")]
public async Task<ActionResult<Author>> GetAuthor(Guid authorId)
در این حالت اگر برنامه را اجرا کنیم، این توضیحات XMLای هیچ تاثیری را بر روی OpenAPI specification تولیدی و در نهایت Swagger-UI تولید شده‌ی بر اساس آن، نخواهد داشت. برای رفع این مشکل، باید به فایل OpenAPISwaggerDoc.Web.csproj مراجعه نمود و تولید فایل XML متناظر با این توضیحات را فعال کرد:
<Project Sdk="Microsoft.NET.Sdk.Web">
  <PropertyGroup>
    <TargetFramework>netcoreapp2.2</TargetFramework>
    <AspNetCoreHostingModel>InProcess</AspNetCoreHostingModel>
    <GenerateDocumentationFile>true</GenerateDocumentationFile>
  </PropertyGroup>
پس از تنظیم خاصیت GenerateDocumentationFile به true، با هر بار Build برنامه، فایل xml ای مطابق نام اسمبلی برنامه، در پوشه‌ی bin آن تشکیل خواهد شد؛ مانند فایل bin\Debug\netcoreapp2.2\OpenAPISwaggerDoc.Web.xml در این مثال.
اکنون نیاز است وجود این فایل را به تنظیمات SwaggerDoc در کلاس Startup برنامه، اعلام کنیم:
namespace OpenAPISwaggerDoc.Web
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddSwaggerGen(setupAction =>
            {
                setupAction.SwaggerDoc(
                    // ... 
                   );

                var xmlCommentsFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
                var xmlCommentsFullPath = Path.Combine(AppContext.BaseDirectory, xmlCommentsFile);
                setupAction.IncludeXmlComments(xmlCommentsFullPath);
            });
        }
در متد IncludeXmlComments، بجای ذکر صریح نام و مسیر فایل OpenAPISwaggerDoc.Web.xml، بر اساس نام اسمبلی جاری، نام فایل XML مستندات تعیین و مقدار دهی شده‌است.
پس از این تنظیمات اگر برنامه را اجرا کنیم، در Swagger-UI حاصل، این تغییرات قابل مشاهده هستند:




افزودن توضیحات به Response

تا اینجا توضیحات پارامترها و متدها را افزودیم؛ اما response از نوع 200 آن هنوز فاقد توضیحات است:


علت را نیز در تصویر فوق مشاهده می‌کنید. قسمت responses در OpenAPI specification، اطلاعات خودش را از اسکیمای مدل‌های مرتبط دریافت می‌کند. بنابراین نیاز است کلاس DTO متناظر با Author را به نحو ذیل تکمیل کنیم:
using System;

namespace OpenAPISwaggerDoc.Models
{
    /// <summary>
    /// An author with Id, FirstName and LastName fields
    /// </summary>
    public class Author
    {
        /// <summary>
        /// The id of the author
        /// </summary>
        public Guid Id { get; set; }

        /// <summary>
        /// The first name of the author
        /// </summary>
        public string FirstName { get; set; }

        /// <summary>
        /// The last name of the author
        /// </summary>
        public string LastName { get; set; }
    }
}
مشکل! در این حالت اگر برنامه را اجرا کنیم، خروجی این توضیحات را در قسمت schemas مشاهده نخواهیم کرد. علت اینجا است که چون اسمبلی OpenAPISwaggerDoc.Models با اسمبلی OpenAPISwaggerDoc.Web یکی نیست و آن‌را از پروژه‌ی اصلی خارج کرده‌ایم، به همین جهت نیاز است ابتدا به فایل OpenAPISwaggerDoc.Models.csproj مراجعه و GenerateDocumentationFile آن‌را فعال کرد:
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>netstandard2.0</TargetFramework>
    <GenerateDocumentationFile>true</GenerateDocumentationFile>
  </PropertyGroup>
</Project>
سپس باید فایل xml مستندات آن‌را به صورت مجزایی به تنظیمات ابتدایی برنامه معرفی نمود:
namespace OpenAPISwaggerDoc.Web
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddSwaggerGen(setupAction =>
            {
                setupAction.SwaggerDoc(
  // ...
                   );
                var xmlFiles = Directory.GetFiles(AppContext.BaseDirectory, "*.xml", SearchOption.TopDirectoryOnly).ToList();
                xmlFiles.ForEach(xmlFile => setupAction.IncludeXmlComments(xmlFile));
            });
        }
با توجه به اینکه تمام فایل‌های xml تولید شده در آخر به پوشه‌ی bin\Debug\netcoreapp2.2 کپی می‌شوند، فقط کافی است حلقه‌ای را تشکیل داده و تمام آن‌ها را یکی یکی توسط متد IncludeXmlComments به تنظیمات AddSwaggerGen اضافه کرد.

در این حالت اگر مجددا برنامه را اجرا کنیم، خروجی ذیل را در قسمت schemas مشاهده خواهیم کرد:



بهبود مستندات به کمک Data Annotations

اگر به اکشن متد UpdateAuthor در کنترلر نویسندگان دقت کنیم، چنین امضایی را دارد:
[HttpPut("{authorId}")]
public async Task<ActionResult<Author>> UpdateAuthor(Guid authorId, AuthorForUpdate authorForUpdate)
جائیکه موجودیت Author را در پروژه‌ی OpenAPISwaggerDoc.Entities تعریف کرده‌ایم، نام و نام خانوادگی اجباری بوده و دارای حداکثر طول 150 حرف، هستند. قصد داریم همین ویژگی‌ها را به DTO دریافتی این متد، یعنی AuthorForUpdate نیز اعمال کنیم:
using System.ComponentModel.DataAnnotations;

namespace OpenAPISwaggerDoc.Models
{
    /// <summary>
    /// An author for update with FirstName and LastName fields
    /// </summary>
    public class AuthorForUpdate
    {
        /// <summary>
        /// The first name of the author
        /// </summary>
        [Required]
        [MaxLength(150)]
        public string FirstName { get; set; }

        /// <summary>
        /// The last name of the author
        /// </summary>
        [Required]
        [MaxLength(150)]
        public string LastName { get; set; }
    }
}
پس از افزودن ویژگی‌های Required و MaxLength به این DTO، خروجی Sawgger-UI به صورت زیر بهبود پیدا می‌کند:



بهبود مستندات متد HttpPatch با ارائه‌ی یک مثال

دو نگارش از اکشن متد UpdateAuthor در این مثال موجود هستند:
یکی HttpPut است
[HttpPut("{authorId}")]
public async Task<ActionResult<Author>> UpdateAuthor(Guid authorId, AuthorForUpdate authorForUpdate)
و دیگری HttpPatch:
[HttpPatch("{authorId}")]
public async Task<ActionResult<Author>> UpdateAuthor(
            Guid authorId,
            JsonPatchDocument<AuthorForUpdate> patchDocument)
این مورد آرام آرام در حال تبدیل شدن به یک استاندارد است؛ چون امکان Partial updates را فراهم می‌کند. به همین جهت نسبت به HttpPut، کارآیی بهتری را ارائه می‌دهد. اما چون پارامتر دریافتی آن از نوع ویژه‌ی JsonPatchDocument است و مثال پیش‌فرض مستندات آن، آنچنان مفهوم نیست:


بهتر است در این حالت مثالی را به استفاده کنندگان از آن ارائه دهیم تا در حین کار با آن، به مشکل برنخورند:
/// <summary>
/// Partially update an author
/// </summary>
/// <param name="authorId">The id of the author you want to get</param>
/// <param name="patchDocument">The set of operations to apply to the author</param>
/// <returns>An ActionResult of type Author</returns>
/// <remarks>
/// Sample request (this request updates the author's first name) \
/// PATCH /authors/id \
/// [ \
///     { \
///       "op": "replace", \
///       "path": "/firstname", \
///       "value": "new first name" \
///       } \
/// ] \
/// </remarks>
[HttpPatch("{authorId}")]
public async Task<ActionResult<Author>> UpdateAuthor(
    Guid authorId,
    JsonPatchDocument<AuthorForUpdate> patchDocument)
در اینجا در حین کامنت نویسی، می‌توان از المان remarks، برای نوشتن توضیحات اضافی مانند ارائه‌ی یک مثال، استفاده کرد که در آن op و path معادل‌های بهتری را نسبت به مستندات پیش‌فرض آن پیدا کرده‌‌اند. در اینجا برای ذکر خطوط جدید باید از \ استفاده کرد؛ وگرنه خروجی نهایی، در یک سطر نمایش داده می‌شود:



روش کنترل warningهای کامنت‌های تکمیل نشده

با فعالسازی GenerateDocumentationFile در فایل csproj برنامه، کامپایلر، بلافاصله برای تمام متدها و خواص عمومی که دارای کامنت نیستند، یک warning را صادر می‌کند. یک روش برطرف کردن این مشکل، افزودن کامنت به تمام قسمت‌های برنامه است. روش دیگر آن، تکمیل خواص کامپایلر، جهت مواجه شدن با عدم وجود کامنت‌ها در فایل csproj برنامه است:
<Project Sdk="Microsoft.NET.Sdk.Web">
  <PropertyGroup>
    <TargetFramework>netcoreapp2.2</TargetFramework>
    <AspNetCoreHostingModel>InProcess</AspNetCoreHostingModel>

    <GenerateDocumentationFile>true</GenerateDocumentationFile>
    <TreatWarningsAsErrors>false</TreatWarningsAsErrors>
    <WarningsAsErrors>NU1605;</WarningsAsErrors>
    <NoWarn>1701;1702;1591</NoWarn>
  </PropertyGroup>
توضیحات:
- اگر می‌خواهید خودتان را مجبور به کامنت نویسی کنید، می‌توانید نبود کامنت‌ها را تبدیل به error کنید. برای این منظور خاصیت TreatWarningsAsErrors را به true تنظیم کنید. در این حالت هر کامنت نوشته نشده، به صورت یک error توسط کامپایلر گوشزد شده و برنامه کامپایل نخواهد شد.
- اگر TreatWarningsAsErrors را خاموش کردید، هنوز هم می‌توانید یکسری از warningهای انتخابی را تبدیل به error کنید. برای مثال NU1605 ذکر شده‌ی در خاصیت WarningsAsErrors، مربوط به package downgrade detection warning است.
- اگر به warning نبود کامنت‌ها دقت کنیم به صورت عبارات warning CS1591: Missing XML comment for publicly visible type or member شروع می‌شود. یعنی  CS1591 مربوط به کامنت‌های نوشته نشده‌است. می‌توان برای صرفنظر کردن از آن، شماره‌ی این خطا را بدون CS، توسط خاصیت NoWarn ذکر کرد.


کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید: OpenAPISwaggerDoc-03.zip

در قسمت بعد، مشکل خروجی تولید response از نوع 200 را که در قسمت دوم به آن اشاره کردیم، بررسی خواهیم کرد.
مطالب
MVC Scaffolding #2
از آنجائیکه اصل کار با MVC Scaffolding از طریق خط فرمان پاورشل انجام می‌شود، بنابراین بهتر است در ادامه با گزینه‌ها و سوئیچ‌های مرتبط با آن بیشتر آشنا شویم.
دو نوع پارامتر حین کار با MVC Scaffolding مهیا هستند:

الف) سوئیچ‌ها
مانند پارامترهای boolean عمل کرده و شامل موارد ذیل می‌باشند. تمام این پارامترها به صورت پیش فرض دارای مقدار false بوده و ذکر هرکدام در دستور نهایی سبب true شدن مقدار آن‌ها می‌گردد:
Repository: برای تولید کدها بر اساس الگوی مخزن
Force: برای بازنویسی فایل‌های موجود.
ReferenceScriptLibraries: ارجاعاتی را به اسکریپت‌های موجود در پوشه Scripts، اضافه می‌کند.
NoChildItems: در این حالت فقط کلاس کنترلر تولید می‌شود و از سایر ملحقات مانند تولید Viewها، DbContext و غیره صرفنظر خواهد شد.

ب) رشته‌ها
این نوع پارامترها، رشته‌ای را به عنوان ورودی خود دریافت می‌کنند و شامل موارد ذیل هستند:
ControllerName: جهت مشخص سازی نام کنترلر مورد نظر
ModelType: برای ذکر صریح کلاس مورد استفاده در تشکیل کنترلر بکار می‌رود. اگر ذکر نشود، از نام کنترلر حدس زده خواهد شد.
DbContext: نام کلاس DbContext تولیدی را مشخص می‌کند. اگر ذکر نشود از نامی مانند ProjectNameContex استفاده خواهد کرد.
Project: پیش فرض آن پروژه جاری است یا اینکه می‌توان پروژه دیگری را برای قرار دادن فایل‌های تولیدی مشخص کرد. (برای مثال هربار یک سری کد مقدماتی را در یک پروژه جانبی تولید کرد و سپس موارد مورد نیاز را از آن به پروژه اصلی افزود)
CodeLanguage: می‌تواند cs یا vb باشد. پیش فرض آن زبان جاری پروژه است.
Area: اگر می‌خواهید کدهای تولیدی در یک ASP.NET MVC area مشخص قرار گیرند، نام Area مشخصی را در اینجا ذکر کنید.
Layout: در حالت پیش فرض از فایل layout اصلی استفاده خواهد شد. اما اگر نیاز است از layout دیگری استفاده شود، مسیر نسبی کامل آن‌را در اینجا قید نمائید.

یک نکته:
نیازی به حفظ کردن هیچکدام از موارد فوق نیست. برای مثال در خط فرمان پاورشل، دستور Scaffold را نوشته و پس از یک فاصله، دکمه Tab را فشار دهید. لیست پارامترهای قابل اجرای در این حالت ظاهر خواهند شد. اگر در اینجا برای نمونه Controller انتخاب شود، مجددا با ورود یک فاصله و خط تیره و سپس فشردن دکمه Tab، لیست پارامترهای مجاز و همراه با سوئیچ کنترلر ظاهر می‌گردند.


MVC Scaffolding و مدیریت روابط بین کلاس‌ها

مثال قسمت قبلی بسیار ساده و شامل یک کلاس بود. اگر آن‌را کمی پیچیده‌تر کرده و برای مثال روابط one-to-many و many-to-many را اضافه کنیم چطور؟
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace MvcApplication1.Models
{
    public class Task
    {
        public int Id { set; get; }

        [Required]
        public string Name { set; get; }

        [DisplayName("Due Date")]
        public DateTime? DueDate { set; get; }

        [ForeignKey("StatusId")]
        public virtual Status Status { set; get; } // one-to-many
        public int StatusId { set; get; }

        [StringLength(450)]
        public string Description { set; get; }

        public virtual ICollection<Tag> Tags { set; get; } // many-to-many
    }

    public class Tag
    {
        public int Id { set; get; }

        [Required]
        public string Name { set; get; }

        public virtual ICollection<Task> Tasks { set; get; } // many-to-many
    }

    public class Status
    {
        public int Id { set; get; }

        [Required]
        public string Name { set; get; }
    }
}
کلاس Task تعریف شده اینبار دارای رابطه many-to-many با برچسب‌های مرتبط با آن است. همچنین یک رابطه one-to-many با کلاس وضعیت هر Task نیز تعریف شده است. به علاوه نکته تعریف «کار با کلیدهای اصلی و خارجی در EF Code first» نیز در اینجا لحاظ گردیده است.
در ادامه دستور تولید کنترلر‌های Task، Tag و Status ساخته شده با الگوی مخزن را در خط فرمان پاورشل ویژوال استودیو صادر می‌کنیم:
PM> Scaffold Controller -ModelType Task -ControllerName TasksController -DbContextType TasksDbContext -Repository -Force
PM> Scaffold Controller -ModelType Tag -ControllerName TagsController -DbContextType TasksDbContext -Repository -Force
PM> Scaffold Controller -ModelType Status -ControllerName StatusController -DbContextType TasksDbContext -Repository -Force
اگر به کارهایی که در اینجا انجام می‌شود دقت کنیم، می‌توان صرفه جویی زمانی قابل توجهی را شاهد بود؛ خصوصا در برنامه‌هایی که از ده‌ها فرم ورود اطلاعات تشکیل شده‌اند. فرض کنید قصد استفاده از ابزار فوق را نداشته باشیم. باید به ازای هر عملیات CRUD دو متد را ایجاد کنیم. یکی برای نمایش و دیگری برای ثبت. بعد بر روی هر متد کلیک راست کرده و Viewهای متناظری را ایجاد کنیم. سپس مجددا یک سری پیاده سازی «مقدماتی» تکراری را به ازای هر متد جهت ثبت یا ذخیره اطلاعات تدارک ببینیم. اما در اینجا پس از طراحی کلاس‌های برنامه، با یک دستور، حجم قابل توجهی از کدهای «مقدماتی» که بعدها مطابق نیاز ما سفارشی سازی و غنی‌تر خواهند شد، تولید می‌گردند.

چند نکته:
- با توجه به اینکه مدل‌ها تغییر کرده‌اند، نیاز است بانک اطلاعاتی متناظر نیز به روز گردد. مطالب مرتبط با آن‌را در مباحث Migrations می‌توانید مطالعه نمائید.
- View تولیدی رابطه many-to-many را پشتیبانی نمی‌کند. این مورد را باید دستی اضافه و طراحی کنید: (^ و ^)
- رابطه one-to-many به خوبی با View متناظری دارای یک drop down list تولید خواهد شد. در اینجا لیست تولیدی به صورت خودکار با مقادیر خاصیت Name کلاس Status پر می‌شود. اگر این نام دقیقا Name نباشد نیاز است توسط ویژگی به نام DisplayColumn که بر روی نام کلاس قرار می‌گیرد، مشخص کنید از کدام خاصیت باید استفاده شود.
@Html.DropDownListFor(model => model.StatusId,
((IEnumerable<Status>)ViewBag.PossibleStatus).Select(option => new SelectListItem {
  Text = (option == null ? "None" : option.Name),
  Value = option.Id.ToString(),
  Selected = (Model != null) && (option.Id == Model.StatusId)
}), "Choose...")
@Html.ValidationMessageFor(model => model.StatusId)


تولید آزمون‌های واحد به کمک MVC Scaffolding

MVC Scaffolding امکان تولید خودکار کلاس‌ها و متدهای آزمون واحد را نیز دارد. برای این منظور دستور زیر را در خط فرمان پاورشل وارد نمائید:
 PM> Scaffold MvcScaffolding.ActionWithUnitTest -Controller TasksController -Action ArchiveTask -ViewModel Task
دستوری که در اینجا صادر شده است نسبت به حالت‌های کلی قبلی، اندکی اختصاصی‌تر است. این دستور بر روی کنترلری به نام TasksController، جهت ایجاد اکشن متدی به نام ArchiveTask با استفاده از کلاس ViewModel ایی به نام Task اجرا می‌شود. حاصل آن ایجاد اکشن متد یاد شده به همراه کلاس TasksControllerTest است؛ البته اگر حین ایجاد پروژه جدید در ابتدای کار، گزینه ایجاد پروژه آزمون‌های واحد را نیز انتخاب کرده باشید. نام پروژه پیش فرضی که جستجوی می‌شود YourMvcProjectName.Test/Tests است.
 نکته مهم آن، عدم حذف یا بازنویسی کامل کنترلر یاد شده است. کاری هم که در تولید متد آزمون واحد متناظر انجام می‌شود، تولید بدنه متد آزمون واحد به همراه تولید کدهای اولیه الگوی Arrange/Act/Assert است. پر کردن جزئیات بیشتر آن با برنامه نویس است.
و یا به صورت خلاصه‌تر:
 PM> Scaffold UnitTest Tasks Delete
در اینجا متد آزمون واحد کنترلر Tasks و اکشن متد Delete آن، تولید می‌شود.

کار مقدماتی با MVC Scaffolding و امکانات مهیای در آن همینجا به پایان می‌رسد. در قسمت‌های بعد به سفارشی سازی این مجموعه خواهیم پرداخت.
مطالب
استفاده از EF در اپلیکیشن های N-Tier : قسمت هفتم
در قسمت قبلی مدیریت همزمانی در بروز رسانی‌ها را بررسی کردیم. در این قسمت مرتب سازی (serialization) پراکسی‌ها در سرویس‌های WCF را بررسی خواهیم کرد.


مرتب سازی پراکسی‌ها در سرویس‌های WCF

فرض کنید یک پراکسی دینامیک (dynamic proxy) از یک کوئری دریافت کرده اید. حال می‌خواهید این پراکسی را در قالب یک آبجکت CLR سریال کنید. هنگامی که آبجکت‌های موجودیت را بصورت POCO-based پیاده سازی می‌کنید، EF بصورت خودکار یک آبجکت دینامیک مشتق شده را در زمان اجرا تولید می‌کند که dynamix proxy نام دارد. این آبجکت برای هر موجودیت POCO تولید می‌شود. این آبجکت پراکسی بسیاری از خواص مجازی (virtual) را بازنویسی می‌کند تا عملیاتی مانند ردیابی تغییرات و بارگذاری تنبل را انجام دهد.

فرض کنید مدلی مانند تصویر زیر دارید.


ما از کلاس ProxyDataContractResolver برای deserialize کردن یک آبجکت پراکسی در سمت سرور و تبدیل آن به یک آبجکت POCO روی کلاینت WCF استفاده می‌کنیم. مراحل زیر را دنبال کنید.


  • در ویژوال استودیو پروژه جدیدی از نوع WCF Service Application بسازید. یک Entity Data Model به پروژه اضافه کنید و جدول Clients را مطابق مدل مذکور ایجاد کنید.
  • کلاس POCO تولید شده توسط EF را باز کنید و کلمه کلیدی virtual را به تمام خواص اضافه کنید. این کار باعث می‌شود EF کلاس‌های پراکسی دینامیک تولید کند. کد کامل این کلاس در لیست زیر قابل مشاهده است.
public class Client
{
    public virtual int ClientId { get; set; }
    public virtual string Name { get; set; }
    public virtual string Email { get; set; }
}
نکته: بیاد داشته باشید که هرگاه مدل EDMX را تغییر می‌دهید، EF بصورت خودکار کلاس‌های موجودیت‌ها را مجددا ساخته و تغییرات مرحله قبلی را بازنویسی می‌کند. بنابراین یا باید این مراحل را هر بار تکرار کنید، یا قالب T4 مربوطه را ویرایش کنید. اگر هم از مدل Code-First استفاده می‌کنید که نیازی به این کار‌ها نخواهد بود.

  • ما نیاز داریم که DataContractSerializer از یک کلاس ProxyDataContractResolver استفاده کند تا پراکسی کلاینت را به موجودیت کلاینت برای کلاینت سرویس WCF تبدیل کند. یعنی client proxy به client entity تبدیل شود، که نهایتا در اپلیکیشن کلاینت سرویس استفاده خواهد شد. بدین منظور یک ویژگی operation behavior می‌سازیم و آن را به متد ()GetClient در سرویس الحاق می‌کنیم. برای ساختن ویژگی جدید از کدی که در لیست زیر آمده استفاده کنید. بیاد داشته باشید که کلاس ProxyDataContractResolver در فضای نام Entity Framework قرار دارد.
using System.ServiceModel.Description;
using System.ServiceModel.Channels;
using System.ServiceModel.Dispatcher;
using System.Data.Objects;

namespace Recipe8
{
    public class ApplyProxyDataContractResolverAttribute : 
        Attribute, IOperationBehavior
    {
        public void AddBindingParameters(OperationDescription description,
            BindingParameterCollection parameters)
        {
        }
        public void ApplyClientBehavior(OperationDescription description,
            ClientOperation proxy)
        {
            DataContractSerializerOperationBehavior
                dataContractSerializerOperationBehavior =
                    description.Behaviors
                    .Find<DataContractSerializerOperationBehavior>();
                dataContractSerializerOperationBehavior.DataContractResolver =
                    new ProxyDataContractResolver();
        }
        public void ApplyDispatchBehavior(OperationDescription description,
            DispatchOperation dispatch)
        {
            DataContractSerializerOperationBehavior
                dataContractSerializerOperationBehavior =
                    description.Behaviors
                    .Find<DataContractSerializerOperationBehavior>();
            dataContractSerializerOperationBehavior.DataContractResolver =
                new ProxyDataContractResolver();
        }
        public void Validate(OperationDescription description)
        {
        }
    }
}
  • فایل IService1.cs را باز کنید و کد آن را مطابق لیست زیر تکمیل نمایید.
[ServiceContract]
public interface IService1
{
    [OperationContract]
    void InsertTestRecord();
    [OperationContract]
    Client GetClient();
    [OperationContract]
    void Update(Client client);
}
  • فایل IService1.svc.cs را باز کنید و پیاده سازی سرویس را مطابق لیست زیر تکمیل کنید.
public class Client
{
    [ApplyProxyDataContractResolver]
    public Client GetClient()
    {
        using (var context = new EFRecipesEntities())
        {
            context.Cofiguration.LazyLoadingEnabled = false;
            return context.Clients.Single();
        }
    }
    public void Update(Client client)
    {
        using (var context = new EFRecipesEntities())
        {
            context.Entry(client).State = EntityState.Modified;
            context.SaveChanges();
        }
    }
    public void InsertTestRecord()
    {
        using (var context = new EFRecipesEntities())
        {
            // delete previous test data
            context.ExecuteSqlCommand("delete from [clients]");
            // insert new test data
            context.ExecuteStoreCommand(@"insert into
                [clients](Name, Email) values ('Armin Zia','armin.zia@gmail.com')");
        }
    }
}
  • حال پروژه جدیدی از نوع Console Application به راه حل جاری اضافه کنید. این اپلیکیشن، کلاینت تست ما خواهد بود. پروژه سرویس را ارجاع کنید و کد کلاینت را مطابق لیست زیر تکمیل نمایید.
using Recipe8Client.ServiceReference1;

namespace Recipe8Client
{
    class Program
    {
        static void Main(string[] args)
        {
            using (var serviceClient = new Service1Client())
            {
                serviceClient.InsertTestRecord();
                
                var client = serviceClient.GetClient();
                Console.WriteLine("Client is: {0} at {1}", client.Name, client.Email);
                
                client.Name = "Armin Zia";
                client.Email = "arminzia@live.com";
                serviceClient.Update(client);
                
                client = serviceClient.GetClient();
                Console.WriteLine("Client changed to: {0} at {1}", client.Name, client.Email);
            }
        }
    }
}
اگر اپلیکیشن کلاینت را اجرا کنید با خروجی زیر مواجه خواهید شد.

Client is: Armin Zia at armin.zia@gmail.com
Client changed to: Armin Zia at arminzia@live.com



شرح مثال جاری

مایکروسافت استفاده از آبجکت‌های POCO با WCF را توصیه می‌کند چرا که مرتب سازی (serialization) آبجکت موجودیت را ساده‌تر می‌کند. اما در صورتی که از آبجکت‌های POCO ای استفاده می‌کنید که changed-based notification دارند (یعنی خواص موجودیت را با virtual علامت گذاری کرده اید و کلکسیون‌های خواص پیمایشی از نوع ICollection هستند)، آنگاه EF برای موجودیت‌های بازگشتی کوئری‌ها پراکسی‌های دینامیک تولید خواهد کرد.

استفاده از پراکسی‌های دینامیک با WCF دو مشکل دارد. مشکل اول مربوط به سریال کردن پراکسی است. کلاس DataContractSerializer تنها قادر به serialize/deserialize کردن انواع شناخته شده (known types) است. در مثال جاری این یعنی موجودیت Client. اما از آنجا که EF برای موجودیت‌ها پراکسی می‌سازد، حالا باید آبجکت پراکسی را سریال کنیم، نه خود کلاس Client را. اینجا است که از DataContractResolver استفاده می‌کنیم. این کلاس می‌تواند حین سریال کردن آبجکت ها، نوعی را به نوع دیگر تبدیل کند. برای استفاده از این کلاس ما یک ویژگی سفارشی ساختیم، که پراکسی‌ها را به کلاس‌های POCO تبدیل می‌کند. سپس این ویژگی را به متد ()GetClient اضافه کردیم. این کار باعث می‌شود که پراکسی دینامیکی که توسط متد ()GetClient برای موجودیت Client تولید می‌شود، به درستی سریال شود.

مشکل دوم استفاده از پراکسی‌ها با WCF مربوط به بارگذاری تبل یا Lazy Loading می‌شود. هنگامی که DataContractSerializer موجودیت‌ها را سریال می‌کند، تمام خواص موجودیت را دستیابی خواهد کرد که منجر به اجرای lazy-loading روی خواص پیمایشی می‌شود. مسلما این رفتار را نمی‌خواهیم. برای رفع این مشکل، مشخصا قابلیت بارگذاری تنبل را خاموش یا غیرفعال کرده ایم.

پاسخ به بازخورد‌های پروژه‌ها
درخواست مستندات
هدف از انجام این است که کاربر(End User) بتواند گزارش‌های مورد نظر خود را ساخته، فایل xml تولید شده را به برنامه بدهد و برنامه مطابق فایل xml  و تگ‌ها گزارش را ساخته و به کاربر نمایش دهد.
در غیر اینصورت کاربر باید به ازای هر گزارش به برنامه نویس آن سفارش دهد تا گزارش مدنظر برای وی ساخته شود و به برنامه اضافه گردد.
یا حتی برای تغییر کوچکی در گزارش‌های برنامه : 
به عنوان مثال تغییر در سبک نمایش فاکتور فروش (هنگام چاپ)
هدف حذف این کارهای اضافه است که بعضا توسط کاربر برنامه نیز قابل اجراست. 
مطالب
نمایش خروجی RSS سایت‌های دیگر به کمک jQuery
شاید خیلی از دوستان (مثل گذشته نه چندان دور خودم ) خیلی بیش از اندازه به برنامه نویسی‌های سمت سرور اهمیت  می دهند که این کار باعث از دست دادن ، سرعت و سادگی برنامه نویسی  سمت کلاینت می‌شود.
معمولا ما برای کار با خروجی‌های XML از کد‌های سمت سرور استفاده می‌کنیم ، بدون اینکه از قدرت جی کوئری در این زمینه  اطلاعی داشته باشیم. البته در این مقاله خیلی به پردازش XML  توسط جی کوئری نمی‌پردازیم و کار اصلی را گوگل برای ما انجام می‌دهد.
برای انجام این کار ما ابتدا توسط یکی از کتابخانه‌های گوگل ، خروجی RSS یک وب سایت رامی خوانیم و بعد به کمک jQuery  آن‌ها را نمایش می‌دهیم.
    <script src="jquery-1.8.0.min.js" type="text/javascript"></script>
    <script type="text/javascript" src="https://www.google.com/jsapi"></script>
    <script type="text/javascript">

        google.load("feeds", "1");

        function initializeda() {
            var feed = new google.feeds.Feed("http://www.drupaleasy.ir/rss.xml");
            feed.setNumEntries(5);
            feed.setResultFormat(google.feeds.Feed.JSON_FORMAT);
            feed.load(function (result) {
                if (!result.error) {
                    for (var i = 0; i < result.feed.entries.length; i++) {
                        var entry = result.feed.entries[i];
                        $('#drupaleasy ul').append('<li><a href="' + entry.link + '">' + entry.title + '</a></li>');
                    }
                }
            });
        }

google.setOnLoadCallback(initializeda);
    </script>
توضیحات کد :
ابتدا نیاز داریم کتابخانه گوگل را به صفحه اضافه کنیم. شما می‌توانید مستندات کامل این کتابخانه در این لینک  بخوانید.
<script type="text/javascript" src="https://www.google.com/jsapi"></script>
در ابتدا معرفی می‌کنیم که به کدام امکان این کتابخانه نیاز داریم ، در این جا ما از ورژن "1" تابع feed  استفاده می‌کنیم.بد نیست برای آشنایی بیشتر سری هم به این لینک  بزنید.
google.load("feeds", "1");
ابتدا لینک مورد نظر که باید خروجی از آن گرفته شود را معرفی می‌کنیم و آن را در متغییری قرار می‌دهیم.
var feed = new google.feeds.Feed("http://www.drupaleasy.ir/rss.xml");
تنظیمات دلخواه خودمان را نظیر تعداد پست واکشی شده و نوع خروجی مشخص می‌کنیم. تجربه (شخصی )نشان داده که بهترین نوع خروجی JSON   است.
feed.setNumEntries(5);
feed.setResultFormat(google.feeds.Feed.JSON_FORMAT);
و بعد هم به کمک jQuery  ساختار مورد نظر خودمان را تنظیم می‌کنیم. شما می‌توانید به کمک مستندات ، و سلیقه شخصی خودتان این کار را انجام بدهید ، و در آخر هم تنظیم می‌کنید ، اجرای تابع شما در چه زمانی اتفاق بیافتید.
            feed.load(function (result) {
                if (!result.error) {
                    for (var i = 0; i < result.feed.entries.length; i++) {
                        var entry = result.feed.entries[i];
                        $('#drupaleasy ul').append('<li><a href="' + entry.link + '">' + entry.title + '</a></li>');
                    }
                }
            });
در این جا من خروجی فید را به صورت یک لیست (ul)  تنظیم کردم ، به این صورت که به ازای هر سطر یک li  همراه با تگ a  تولید کرده ام و به ul  مورد نظر append  کردم. 
فایل HTML :
            <div id="drupaleasy" class="feeds">
            <span>DrupalEasy.ir</span>
                <ul>
                </ul>
                <a href="http://drupaleasy.ir">more</a>
            </div>
همچنین خیلی راحت می‌توانید به کمک CSS  استایل‌های دلخواه خودتون رو اعمال کنید.
        .feeds
        {
            float: right;
            background-color: rgba(234, 242, 243, 0.73);
            margin: 5px;
            border-radius: 20px;
            padding: 8px;
            width: 293px;
            height: 217px;
            border: 1px solid #293883;
        }

        #drupaleasy ul
        {
            list-style-image: url("img/drupal.png");
        }
این هم شد خروجی کار  من 

نظرات مطالب
ASP.NET MVC #16
سلام؛ چطور میشه خطاهای اتفاق افتاده که در Elmah نمایش داده می‌شوند را در فایل xml ذخیره کرد ؟
من مثال را دانلود کردم ولی نفهمیدم چطور باید اینکار را انجام بدهم...
ممنون // 
مسیرراه‌ها
ASP.NET MVC
              مطالب
              افزونه نویسی برای مرورگرها : قسمت دوم : فایرفاکس
              در مقاله پیشین، افزونه نویسی برای فایرفاکس را آغاز و مسائل مربوط به رابط‌های کاربری را بررسی کردیم. در این قسمت که قسمت پایانی افزونه نویسی برای فایرفاکس است، به مباحث پردازشی و دیگر خصوصیت‌ها می‌پردازیم.
              اولین موردی که باید برای برنامه‌ی ما در نظر گرفت، ذخیره و بازیابی مقادیر است که باید روی پنجره‌ی popup.html اعمال گردد و همچنین مقداردهی مقادیر پیش فرض برنامه بعد از نصب افزونه اعمال شود. برای ذخیره‌ی مقادیر، طبق نوشته موجود در راهنمای موزیلا، از روش زیر بهره برده و می‌توان مقادیر زیر را به راحتی در آن‌ها ذخیره کرد:
              var ss = require("sdk/simple-storage");
              ss.storage.myArray = [1, 1, 2, 3, 5, 8, 13];
              ss.storage.myBoolean = true;
              ss.storage.myNull = null;
              ss.storage.myNumber = 3.1337;
              ss.storage.myObject = { a: "foo", b: { c: true }, d: null };
              ss.storage.myString = "O frabjous day!";
              برای خواندن موارد ذخیره شده هم که مشخصا نوشتن اسم property کفایت می‌کند و برای حذف مقادیر نیز به راحتی از عبارت delete در جلوی پراپرتی استفاده کنید:
              delete ss.storage.value;
              برای ذخیره مقادیر پیش فرض اولین، کاری که می‌کنیم اسم متغیرها را چک می‌کنیم. اگر مخالف null بود، یعنی قبلا ست شده‌اند؛ ولی اگر null شد، عمل ذخیره سازی اولیه را انجام می‌دهیم:
              if (!ss.storage.Variables)
               {
               ss.storage.Variables=[];
               ss.storage.Variables.push(true);
               ss.storage.Variables.push(false);
               ss.storage.Variables.push(false);
               ss.storage.Variables.push(false);
               }
              
              if (!ss.storage.interval)
              ss.storage.interval=1;
              
               if (!ss.storage.DateVariables)
               {
               var now=String(new Date());
               ss.storage.DateVariables=[];
               ss.storage.DateVariables.push(now);
               ss.storage.DateVariables.push(now);
               ss.storage.DateVariables.push(now);
               ss.storage.DateVariables.push(now);
               }

              برای ذخیره مقادیر popup.html، به طور مستقیم نمی‌توانیم از کدهای بالا در جاوااسکریپت استفاده کنیم. مجبور هستیم که یک پل ارتباطی بین فایل main.js و فایل جاوااسکریپت داشته باشیم. در مقاله پیشین در مورد postmessage که ارتباطی از/به محتوا یا فایل جاوااسکریپت به/از main.js برقرار می‌کرد، صحبت کردیم و در این قسمت راه حل بهتری را مورد استفاده قرار می‌دهیم. برای ایجاد چنین ارتباطی، آن هم به صورت دو طرفه از port استفاده می‌کنیم. دستور پورت در اکثر اشیایی که ایجاد می‌کنید وجود دارد ولی باز هم همیشه قبل از استفاده، از مستندات موزیلا حتما استفاده کنید تا مطمئن شوید دسترسی به شیء پورت در همه‌ی اشیا وجود دارد. ولی به صورت کلی تا آنجایی که من دیدم، در همه‌ی اشیا قرار دارد. از کد port.emit برای ارسال مقادیر به سمت فایل اسکریپت یا حتی بالعکس مورد استفاده قرار می‌گیرد و port.on هم یک شنونده برای آن است. شکل زیر به خوبی این مبحث را نشان می‌دهد.
              addon process در شکل بالا همان فایل main.js هست که کد اصلی addon داخل آن است و content process نیز محتوای اسکریپت است و حالا میتواند با استفاده از خاصیت contentscrip که به صورت رشته ای اعمال شده باشد یا اینکه با استفاده از خاصیت contentscriptfile، در یک یا چند فایل js استفاده نماید:
              contentScriptFile: self.data.url("jquery.min.js")
              contentScriptFile: [self.data.url("jquery.min.js"),self.data.url("const.js"),self.data.url("popup.js")]

              از شیء port به صورت عملی استفاده می‌کنیم. کد  main.js را به صورت زیر تغییر دادیم:
              function handleChange(state) {
                if (state.checked) {
                  panel.show({
                    position: button
                  });
              
               var v1=[],v2;
               if (ss.storage.Variables)
              v1=ss.storage.Variables;
              
              if (ss.storage.interval)
              v2=ss.storage.interval;
              
              panel.port.emit("vars",v1,v2);
                }
              }
              panel.port.on("vars", function (vars,interval) {
                ss.storage.Variables=vars;
                ss.storage.interval=interval;
              });
              در شماره پیشین گفتیم که رویداد handlechange وظیفه نمایش پنل را دارد، ولی الان به غیر آن چند سطر، کد دیگری را هم اضافه کردیم تا موقعی که پنل باز می‌شود، تنظیمات قبلی را که ذخیره کرده‌ایم روی صفحه نمایش داده شوند. با استفاده از شیء port.emit محتواهای دریافت شده را به سمت فایل اسکریپت ارسال می‌کنیم تا تنظیمات ذخیره شده را برای نمایش اعمال کند. پارامتر اول رشته vars نام پیام رسان شما خواهد بود و در فایل مقصد هم تنها به پیامی با این نام گوش داده خواهد شد. این خصوصیت زمانی سودمندی خود را نشان می‌دهد که بخواهید در زمینه‌های مختلف، از چندین پیام رسان به سمت یک مقصد استفاده کنید. شیء panel.port.on هم برای گوش دادن به متغیرهایی است که از آن سمت برای ما ارسال می‌شود و از آن برای ذخیره‌ی مواردی استفاده میگردد که کاربر از آن سمت برای ما ارسال می‌کند. پس ما در این مرحله، یک ارتباطه کاملا دو طرفه داریم.
              کد  فایل popup.js که به صورت تگ script در popup.html معرفی شده است:
              $(document).ready(function () {
              addon.port.on("vars",  function(vars,interval) {
               if (vars)
              {
               $("#chkarticles").attr("checked", vars[0]);
              $("#chkarticlescomments").attr("checked", vars[1]);
              $("#chkshares").attr("checked", vars[2]);
              $("#chksharescomments").attr("checked", vars[3]);
              }
              
              $("#interval").val(interval);
              });   
              
                  $("#btnsave").click(function() {
              
                       var Vposts = $("#chkarticles").is(':checked');
                       var VpostsComments = $("#chkarticlescomments").is(':checked');
                       var  Vshares = $("#chkshares").is(':checked');
                       var VsharesComments = $("#chksharescomments").is(':checked');
               var Vinterval = $("#interval").val() ;
               var Variables=[];
               Variables[0]=Vposts;
               Variables[1]=VpostsComments;
               Variables[2]=Vshares;
               Variables[3]=VsharesComments;
               interval=Vinterval;
               
               addon.port.emit("vars", Variables,Vinterval);
              $("#messageboard").text( Messages.SettingsSaved);
              
                  });
              });
              در همان ابتدای امر با استفاده از addon.port.on منتظر یک پیام رسان، به اسم vars می‌شود تا اطلاعات آن را دریافت کند که در اینجا بلافاصله بعد از نمایش پنل، اطلاعات برای آن ارسال شده و در صفحه، جایگذاری می‌کند. در قسمت رویداد کلیک دکمه ذخیره هم با استفاده از addon.port.emit اطلاعاتی را که کاربر به روز کرده است، به یک پیام رسان میدهیم تا برای آن سمت نیز ارسال کند تا در آن سمت، تنظیمات جدید ذخیره و جایگزین شوند.
              نکته بسیار مهم: در کد بالا ما فایل جاوااسکریت را از طریق فایل popup.html معرفی کردیم، نه از طریق خصوصیت contentscriptfile. این نکته را همیشه به خاطر داشته باشید. فایل‌های js خود را تنها در دو حالت استفاده کنید:
              1. از طریق دادن رشته به خصوصیت contentScript و استفاده از self به جای addon
              2. معرفی فایل js داخل خود فایل html با تگ script که به درد اسکریپت‌های با کد زیاد می‌خورد.
              اگر فایل شما شامل استفاده از کلمه‌ی کلیدی addon نمی‌شود، می‌توانید فایل js خود را از طریق contentScriptFile هم اعمال کنید.
              فایل popup.html
              <script src="jquery.min.js"></script> <!-- Including jQuery -->
              <script type="text/javascript" src="const.js"></script> 
              <script type="text/javascript" src="popup.js"></script>


              خواندن فید RSS سایت
              خواندن فید سایت توسط فایل Rssreader.js انجام می‌شود که تمام اسکریپت‌های مورد نیاز برای اجرای آن، توسط background.htm صدا زده شده است:
              <script type="text/javascript" src="const.js"></script>
              <script type="text/javascript" src="jquery.min.js"></script>
                  <script type="text/javascript" src="https://www.google.com/jsapi"></script>
              <script type="text/javascript" src="rssreader.js"></script>
              تنها کاری که باید انجام دهیم اجرای این فایل به عنوان یک فرآیند پس زمینه است. در کروم ما عادت داشتیم برای این کار در فایل manifest.json از خصوصیت background استفاده کنیم، ولی از آنجا که خود فایل main.js یک فایل اسکریپتی است که در پس زمینه اجرا می‌شود، طبق منابع موجود در نت چنین چیزی وجود ندارد و این فرآیند را به خود فایل main.js مربوط می‌دانستند. ولی من با استفاده از page worker چنین خصوصیتی را پیاده سازی کردم. page worker وظیفه دارد تا یک آدرس یا فایلی را در یک تب پنهان و در پشت صحنه اجرا کرده و به شما اجازه‌ی استفاده از DOM آن بدهد. نحوه‌ی دسترسی به فایل background.htm توسط page worker به صورت زیر تعریف می‌شود:
              pageWorker = require("sdk/page-worker");
                page= pageWorker.Page({
                contentScriptWhen: "ready",
                contentURL: self.data.url("./background.htm")
              });
              page.port.emit("vars",ss.storage.Variables,ss.storage.DateVariables,ss.storage.interval);
              در فایل بالا شیء pageworker ساخته شد و درخواست یک پیج نهان را برای فایل background.htm در دایرکتوری data می‌کند. استفاده از گزینه‌ی contentScriptWhen  برای دسترسی به شیء addon در فایل‌های جاوااسکریپتی که استفاده می‌کنید ضروری است. در صورتی که حذف شود و نوشته نشود با خطای addon is not defined روبرو خواهید شد، چرا که هنوز این شیء شناسایی نشده است. در خط نهایی هم برای آن سمت یک پیام ارسال شده که حاوی مقادیر ذخیره شده می‌باشد.

              فایل RSSReader.js
              در اینجا هم مانند مطلبی که برای کروم گذاشتیم، خواندن فید، در یک دوره‌ی زمانی اتفاق می‌افتد. در کروم ما از chrome.alarm استفاده می‌کردیم، ولی در فایرفاکس از همان تایمرهای جاوااسکریپتی بهره میبریم. کد زیر را به فایلی به اسم rssreader.js اضافه می‌کنیم:
              var variables=[];
              var datevariables=[];
              var period_time=60000;
              var timer;
              google.load("feeds", "1");
              
              $(document).ready(function () {
               addon.port.on("vars",  function(vars,datevars,interval) {
               if (vars)
              {
              Variables=vars;
              }
              if (datevars)
              {
              datevariables=datevars;
              }
              if(interval)
              period_time=interval*60000;
              alarmManager();
              });
              });
              
              function alarmManager()
              {
              timer = setInterval(Run,period_time);
              }
              
              function Run() {
              if(Variables[0]){RssReader(Links.postUrl,0, Messages.PostsUpdated);}
              if(Variables[1]){RssReader(Links.posts_commentsUrl,1,Messages.CommentsUpdated); }
              if(Variables[2]){RssReader(Links.sharesUrl,2,Messages.SharesUpdated);}
              if(Variables[3]){RssReader(Links.shares_CommentsUrl,3,Messages.SharesCommentsUpdated);}
              }
              
              function RssReader(URL,index,Message) {
              
              
                          var feed = new google.feeds.Feed(URL);
                          feed.setResultFormat(google.feeds.Feed.XML_FORMAT);
                                  feed.load(function (result) {
              if(result!=null)
              {
              var strRssUpdate = result.xmlDocument.firstChild.firstChild.childNodes[5].textContent;
              var RssUpdate=new Date(strRssUpdate);
              var lastupdate=new Date(datevariables[index]);
              if(RssUpdate>lastupdate)
              {
              datevariables[index]=strRssUpdate;
              addon.port.emit("notification",datevariables,Message);
              }
              
              }
                                    });
                      }
              در خطوط بالا متغیرها تعریف و توابع گوگل بارگزاری می‌شوند. سپس توسط addon.port یک شنونده ایجاد شده، تا بتواند مقادیر ذخیره شده را بازیابی کند. این مقادیر شامل موارد زیر است:
              • چه بخش‌هایی از سایت باید بررسی شوند.
              • آخرین تاریخ تغییر هر کدام که در زمان نصب افزونه، تاریخ نصب افزونه می‌شود و با اولین به روز رسانی، تاریخ جدیدی جای آن را می‌گیرد.
              • دوره‌ی سیکل زمانی یا همان interval بر اساس دقیقه
              پس از اینکه شنونده مقادیر را دریافت کرد، تابع alarmManager اجرا شده و یک تایمر ایجاد می‌کند. بر خلاف کروم که برای این کار api تدارک دیده بود، اینجا شما باید از تایمرهای خود جاوااسکریپت مانند SetTimeout یا SetInterval استفاده کنید. موقع دریافت interval یا period_time ما آن را در 60000 ضرب کردیم تا دقیقه تبدیل به میلی ثانیه شود؛ چرا که تایمر، زمان را بر حسب میلی ثانیه دریافت می‌کند. وظیفه تایمر این هست که در هر دوره‌ی زمانی تابع Run را اجرا کند.

              Run
              این تابع بررسی می‌کند کاربر درخواست بررسی چه قسمت هایی از سایت را دارد و به ازای هر کدام، اطلاعات آن را از طریق پارامترها به تابع rssreader داده تا هر قسمت جداگانه بررسی شود. این اطلاعات به ترتیب: لینک فید مورد نظر، اندیس آخرین تاریخ به روزرسانی آن قسمت، پیامی که باید در وقت به روزرسانی به کار نمایش داده شود.

              RSSReader
              این تابع را قبلا در این مقاله  توضیح دادیم. تنها تغییری که کرده است، بدنه‌ی شرط بررسی تاریخ است که در صورت موفقیت، تاریخ جدید، جایگزین تاریخ قبلی شده و یک پیام به فایل main.js ارسال می‌کند تا از آن درخواست ذخیره‌ی تاریخی جدید و هچنین ایجاد یک notification برای آگاه سازی کاربر کند. پس باز به فایل main.js رفته و شنونده آن را تعریف می‌کنیم:
              page.port.on("notification",function(lastupdate,Message)
              {
              ss.storage.DateVariables=lastupdate;
              Make_a_Notification(Message);
              })
              function Make_a_Notification(Message)
              {
              var notifications = require("sdk/notifications");
              notifications.notify({
                title: "سایت به روز شد",
                text: Message,
                iconURL:self.data.url("./icon-64.png"),
                data:"https://www.dntips.ir",
                onClick: function (data) {
              tabs.open(data);
                }
              });
              }
              شنونده مورد نظر دو پارامتر تاریخ آخرین به روزرسانی را دریافت کرده و آن را جایگزین قبلی می‌کند و پیام را به تایع Make_a_Notification پاس میکند. پارامترهای ساخت نوتیفیکیشن به ترتیب شامل عنوان، متن پیام، آیکن و نهایتا data است. دیتا شامل آدرس سایت است. زمانیکه کاربر روی نوتیفیکیشن کلیک می‌کند، استفاده شده و یک تب جدید را با آدرس سایت باز می‌کنیم. به این ترتیب افزونه‌ی ما تکمیل می‌شود. برای اجرا و تست افزونه بر روی مرورگر فایرفاکس از دستور cfx run استفاده کنید.


              البته این نکته قابل ذکر است که اگر کاربر طلاعات پنل را به روزرسانی کند، تا وقتی که مرورگر بسته نشده و دوباره باز نشود تغییری نمی‌کند؛ چرا که ما تنها در ابتدای امر مقادیر ذخیره شده را به RSSReader فرستاده و اگر کاربر آن‌ها را به روز کند، ارسال پیام دیگری توسط page worker صورت نمی‌گیرد. پس کد موجود در main.js را به صورت زیر ویرایش می‌کنیم:
                pageWorker = require("sdk/page-worker");
                page= pageWorker.Page({
                contentScriptWhen: "ready",
                contentURL: self.data.url("./background.htm")
              });
              
              function SendData()
              {
              page.port.emit("vars",ss.storage.Variables,ss.storage.DateVariables,ss.storage.interval);
              }
              SendData();
              panel.port.on("vars", function (vars,interval) {
                ss.storage.Variables=vars;
                ss.storage.interval=interval;
                SendData();
              });
              در کد بالا ما خطی که به سمت rssreader.js پیام ارسال می‌کند را داخل یک تابع به اسم SendDate قرار دادیم و بعد از تشکیل page worker آن را صدا زدیم و کد آن دقیقا مانند قبل است؛ با این تفاوت که اینبار این تابع را در جای دیگری هم صدا میزنیم و آن زمانی است که برای پنل، پیام مقادیر جدید ارسال می‌شود که در آن پس از ذخیره موارد جدید تابع SendData را صدا می‌زنیم. پس موقع به روزرسانی هم مقادیر ارسال خواهند شد. مقادیر جدید به سمت rssreader.js رفته و تشکیل یک تایمر جدید را می‌دهند و البته چون قبلا تایمر ایجاد شده است، پس باید چند خطی را هم به فایل rssreader.js اضافه کنیم تا تایمر قبلی را نابود کرده و تایمر جدیدی را ایجاد کند:
              var timer;
              
              function alarmManager()
              {
              timer = setInterval(Run,period_time);
              }
              
               addon.port.on("vars",  function(vars,datevars,interval) {
               if (vars)
              {
              Variables=vars;
              
              }
              if (datevars)
              {
              datevariables=datevars;
              }
              if(interval)
              period_time=interval*60000;
              
              if(timer!=null)
              {
              clearInterval(timer);
              }
              
              alarmManager();
              });
              در خط بالا متغیری به اسم timer ایجاد شده است که کد timer را در خود ذخیره می‌کند. پس موقع دریافت مقادیر بررسی میکنیم که اگر مقدار timer مخالف نال بود تایمر قبلی را با clearInterval از بین برده و تایمر جدیدی ایجاد کند. پس مشکل تایمری که از قبل موجود است نیز حل می‌گردد.
              افزونه‌ی ما تکمیل شد. اجازه بدهید قبل از بستن بحث چندتا از موارد مهم موجود در sdk را نام ببریم:

              Page Mod
              page mod موقعی که کاربر آدرسی را مطابق با الگویی (pattern) که ما دادیم، باز کند یک اسکریپت را اجرا خواهد کرد:
              var pageMod = require("sdk/page-mod");
              
              pageMod.PageMod({
                include: "*.mozilla.org",
                contentScript: 'window.alert("Page matches ruleset");'
              });
              var data = require("sdk/self").data;
              var pageMod = require("sdk/page-mod");
              
              pageMod.PageMod({
                include: "*.mozilla.org",
                contentScriptFile: [data.url("jquery-1.7.min.js"),
                                    data.url("my-script.js")]
              });

              پنل تنظیمات
              موقعی که شما افزونه‌ای را در فایرفاکس اضافه می‌کنید، در پنلی که مدیریت افزونه‌ها قرار دارد می‌توانید در تنظیمات هر افزونه، تغییری ایجاد کنید. برای ساخت چنین صفحه‌ای از خصوصیت preferences در فایل package.json کمک می‌گیریم که مقادیر به صورت آرایه ای داخل آن قرار می‌گیرند. مثال زیر پنج کنترل را به بخش تنظیمات افزونه اضافه می‌کند که چهار کنترل اول چک باکس Checkbox هستند؛ چرا که خصوصیت type آنها به bool ست شده است و شامل یک نام و عنوان یا برچسب label و یک توضیح کوتاه است و مقدار پیش فرض آن با خصوصیت value مشخص شده است. آخرین کنترل هم یک کادر عددی است؛ چرا که خاصیت type آن با integer مقداردهی شده و مقدار پیش فرض آن 10 می‌باشد.
                "preferences": [{
                  "description": "مطالب سایت",
                  "type": "bool",
                  "name": "post",
                  "value": true,
                  "title": "مطالب سایت"
              },
              {
                  "description": "نظرات مطالب سایت",
                  "type": "bool",
                  "name": "postcomments",
                  "value": false,
                  "title": "نظرات مطالب سایت"
              },
              {
                  "description": "اشتراک ها",
                  "type": "bool",
                  "name": "shares",
                  "value": false,
                  "title": "اشتراک ها"
              },
              {
                  "description": "نظرات اشتراک ها",
                  "type": "bool",
                  "name": "sharescomments",
                  "value": false,
                  "title": "نظرات اشتراک ها"
              },
                  {
                      "description": "دوره زمان برای بررسی سایت",
                      "name": "interval",
                      "type": "integer",
                      "value": 10,
                      "title": "دوره زمانی"
                  }]

              از آنجا که مقادیر بالا تنها مقادیر پیش فرض خودمان هست و اگر کاربر آن‌ها را تغییر دهد، در این صفحه هم باید اطلاعات تصحیح شوند، برای همین از کد زیر برای دسترسی به پنل تنظیمات و کنترل‌های موجود آن استفاده می‌کنیم. همانطور که می‌بینید کد مورد نظر را در یک تابع به نام Perf_Default_Value قرار دادیم و آن را در بدو اجرا صدا زدیم. پس کاربر اگر به پنل تنظمیات رجوع کند، می‌تواند تغییراتی را که قبلا داده است، ببیند. بنابراین اگر الان تغییری را ایجاد کند، تا باز شدن مجدد مرورگر چیزی نمایش داده نمیشود. برای همین دقیقا مانند تابع SendData این تابع را هم در کد شنود پنل panel اضافه میکنیم؛ تا اگر کاربر اطلاعات را از طریق روش قبلی تغییر داد، اطلاعات هم اینک به روز شوند.

              function Perf_Default_Value()
              {
              var preferences = require("sdk/simple-prefs").prefs;
              
              preferences.post = ss.storage.Variables[0];
              preferences.postcomments = ss.storage.Variables[1];
              preferences.shares = ss.storage.Variables[2];
              preferences.sharescomments = ss.storage.Variables[3];
              preferences["myinterval"] =parseInt(ss.storage.interval);
              
              }
              Perf_Default_Value();
              
              panel.port.on("vars", function (vars,interval) {
                ss.storage.Variables=vars;
                ss.storage.interval=interval;
                SendData();
                Perf_Default_Value();
              });
              البته کاربر فقط برای دیدن اطلاعات بالا به این صفحه‌ی تنظیمات نمی‌آید؛ بلکه بیشتر برای تغییر آن‌ها می‌آید. پس باید به تغییر مقدار کنترل‌ها گوش فرا دهیم. برای گوش دادن به تغییر تنظیمات، برای موقعی که کاربر قسمتی از تنظیمات را ذخیره کرد، از کدهای زیر بهره می‌بریم: 
              perf=require("sdk/simple-prefs");
              var preferences = perf.prefs;
              function onPrefChange(prefName) {
              
                switch(prefName)
                {
                  case "post":
              ss.storage.Variables[0]=preferences[prefName];
              break;
              case "postcomments":
              ss.storage.Variables[1]=preferences[prefName];
              break;
              case "shares":
              ss.storage.Variables[2]=preferences[prefName];
              break;
              case "sharescomments":
              ss.storage.Variables[3]=preferences[prefName];
              break;
              case "myinterval":
              ss.storage.interval=preferences[prefName];
              break;
                }
              }
              //perf.on("post", onPrefChange);
              //perf.on("postcomments", onPrefChange);
              
              perf.on("", onPrefChange);
              متد on دو پارامتر دارد: اولی، نام کنترل مورد نظر که با خصوصیت name تعریف کردیم و دومی هم تابع callback آن می‌باشد و در صورتی که پارامتر اول با "" مقداردهی شود، هر تغییری که در هر کنترلی رخ بدهد، تابع callback صدا زده می‌شود. از آنجا که نام کنترل‌ها به صورت string برگشت داده می‌شوند، برای دسترسی به مقادیر موجود در تنظیمات از همان روش داخل [""] بهره می‌گیریم. مقادیر را گرفته و داخل storage ذخیره می‌کنیم.

                اشکال زدایی Debug

              یکی از روش‌های اشکال زدایی، استفاده از console.log هست که میتونید برای بازبینی مقادیر و وضعیت‌ها، از آن استفاده کنید که نتیجه‌ی آن داخل کنسول نمایش داده می‌شود و اگر هم دربرنامه خطایی رخ دهد، داخل کنسول به شما نمایش خواهد داد.
              مطالب
              ارتقاء به ASP.NET Core 1.0 - قسمت 2 - بررسی ساختار جدید Solution
              اگر یک پروژه‌ی خالی ASP.NET Core Web Application را شروع کنید (با طی مراحل زیر جهت ایجاد یک پروژه‌ی جدید):
               .NET Core -> ASP.NET Core Web Application (.NET Core) -> Select `Empty` Template
              تغییرات ساختاری ASP.NET Core 1.0، با نگارش‌های قبلی ASP.NET، بسیار قابل ملاحظه هستند:


              در اینجا نقش 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" ],
              اما در مورد هر مسیری که ذکر می‌شود، NET Core. باید بتواند یک سطح پایین‌تر از پوشه‌ی ذکر شده، فایل مهم project.json را پیدا کند؛ در غیراینصورت از آن صرفنظر خواهد شد. برای مثال برای مسیر نسبی src، مسیر src\MyProjectName\project.json را جستجو می‌کند و برای مسیر مطلق ذکر شده، این مسیر را c:\\sources\\Configuration\\src\\SomeName\\project.json


              کامپایل خودکار پروژه در 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!");
              });
              اکنون در همین حال که برنامه در حال اجرا است و هنوز IIS Express خاتمه نیافته است، از طریق windows explorer، به پوشه‌ی src برنامه وارد شده و فایل Startup.cs را با notepad باز کنید. هدف این است که این فایل را در خارج از ویژوال استودیو ویرایش کنیم. اینبار سطر await دار را در notepad به نحو ذیل ویرایش کنید:
               await context.Response.WriteAsync("Hello DNT!");
              پس از آن اگر مرورگر را refresh کنید، بلافاصله خروجی جدید فوق را مشاهده خواهید کرد که بیانگر کامپایل خودکار پروژه در صورت تغییر فایل‌های آن است.
              این مساله قابلیت استفاده‌ی از 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"
                  ]
                }
              },
              در اینجا، netcoreapp1.0 به معنای برنامه‌‌ای است که برفراز NET Core. اجرا می‌شود. نام پیشین آن dnxcore50 بود (اگر مقالات قدیمی‌تر پیش از RTM را مطالعه کنید).
              در اینجا اگر علاقمند بودید که از دات نت کامل مخصوص ویندوز نیز استفاده کنید، می‌توانید آن‌را در لیست فریم ورک‌ها اضافه نمائید (در این مثال، دات نت کامل 4.5.2 نیز ذکر شده‌است):
               "frameworks": {
                  "netcoreapp1.0": {
                  },
                  "net452": {
                  }
                }
              لیست کامل این اسامی را در مستندات NET Starndard می‌توانید مطالعه کنید و خلاصه‌ی آن به این صورت است:
              - “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": {
                    }
                }
              },
              لیست وابستگی‌های خاص این فریم ورک در خاصیت 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"
                }
              }
              این نام‌ها استاندارد هستند. برای مثال اگر قصد استفاده‌ی از npm مربوط به node.js را داشته باشید، نام این فایل packag.json است (با ساختار خاص خودش) و هر دوی این‌ها را نیز می‌توانید با هم اضافه کنید و از این لحاظ محدودیتی وجود ندارد.
              پس از اضافه شدن فایل bower.json، بلافاصله کار restore بسته‌ها از اینترنت شروع می‌شود:


              و یا با کلیک راست بر روی گره dependencies، گزینه‌ی restore packages نیز وجود دارد.
              فایل‌های نهایی دریافت شده را در پوشه‌ی bower_components خارج از wwwroot می‌توانید مشاهده کنید.

              در مورد نحوه‌ی توزیع و دسترسی به فایل‌های استاتیک یک برنامه‌ی ASP.NET Core 1.0، نکات خاصی وجود دارند که در قسمت‌های بعد، بررسی خواهند شد.

              یک نکته: اگر خواستید نام پوشه‌ی wwwroot را تغییر دهید، فایل جدیدی را به نام hosting.json با این محتوا به پروژه اضافه کنید:
              {
                  "webroot":"AppWebRoot"
              }
              در اینجا AppWebRoot نام دلخواه پوشه‌ی جدیدی است که نیاز است به ریشه‌ی پروژه اضافه نمائید تا بجای wwwroot پیش فرض استفاده شود.


              نقطه‌ی آغازین برنامه کجاست؟

              اگر به فایل project.json دقت کنید، چنین تنظیمی در آن موجود است:
              "buildOptions": {
                "emitEntryPoint": true,
                "preserveCompilationContext": true
              },
              true بودن مقدار تولید entry point به استفاده‌ی از فایلی به نام Program.cs بر می‌گردد؛ با این محتوا:
              public static void Main(string[] args)
              {
                  var host = new WebHostBuilder()
                      .UseKestrel()
                      .UseContentRoot(Directory.GetCurrentDirectory())
                      .UseIISIntegration()
                      .UseStartup<Startup>()
                      .Build();
               
                  host.Run();
              }
              به این ترتیب، یک برنامه‌ی ASP.NET Core، دقیقا همانند سایر برنامه‌های NET Core. رفتار می‌کند و در اساس یک برنامه‌ی کنسول است.
               var outputKind = compilerOptions.EmitEntryPoint.GetValueOrDefault() ?
              OutputKind.ConsoleApplication : OutputKind.DynamicallyLinkedLibrary;
              این چند سطر، قسمتی از سورس کد ASP.NET Core 1.0 هستند. به این معنا که اگر مقدار خاصیت emitEntryPoint مساوی true بود، با این برنامه به شکل یک برنامه‌ی کنسول رفتار کن و در غیر اینصورت یک Dynamically Linked Library خواهد بود.
              در فایل 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!");
                      });
                  }
              }
              در اینجا امکان دسترسی به تنظیمات برنامه، معرفی سرویس‌ها، middleware‌ها و غیره وجود دارند که هرکدام، در قسمت‌های آتی به تفصیل بررسی خواهند شد.


              نقش فایل 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"
                    }
                  }
                }
              }
              همانطور که مشاهده می‌کنید، در اینجا تنظیمات IIS Express قابل تغییر هستند. برای مثال port پیش فرض برنامه 7742 تنظیم شده‌است.
              پروفایل‌هایی که در اینجا ذکر شده‌اند، در تنظیمات پروژه نیز قابل مشاهده هستند: (کلیک راست بر روی پروژه و مشاهده‌ی properties آن و یا دوبار کلیک بر روی گره properties)


              به علاوه امکان انتخاب این پروفایل‌ها در زمان آغاز برنامه نیز وجود دارند:


              نکته‌ی مهم تمام این موارد به قسمت environment variable قابل مشاهده‌ی در تصاویر فوق بر می‌گردد. این متغیر محیطی می‌تواند سه مقدار Development ، Staging و Production را داشته باشد و بر اساس این متغیر و مقدار آن، می‌توان پروفایل جدیدی را تشکیل داد. زمانیکه برنامه بر اساس پروفایل خاصی بارگذاری می‌شود، اینکه چه متغیر محیطی انتخاب شده‌است، در کلاس Startup قابل استخراج و بررسی بوده و بر این اساس می‌توان اقدامات خاصی را انجام داد. برای مثال تنظیمات خاصی را بارگذاری کرد و یا صفحات ویژه‌ای را فعال و غیرفعال کرد (این موارد را در قسمت‌های بعدی مرور می‌کنیم).
              همچنین در اینجا به ازای هر پروفایل مختلف می‌توان Url آغازین یا launch url و پورت آن‌را مجزا درنظر گرفت و یا از وب سرور دیگری استفاده کرد.