پاسخ به بازخورد‌های پروژه‌ها
توضیح گام های اجرا شده در پروژه
سلام؛
هدف از انجام این پروژه برای من چسباندن قطعات مختلف یک پازل به هم بودند تا بتوان به یک تصویر خوب رسید.منظور من این است که entity framework و ASP.NET MVC و bootstrap و best practice‌های آن‌ها به تنهایی و جدا از هم به نظر ساده و راحت و خوب بیایند، اما درگیر شدن همه‌ی آن‌ها در یک پروژه‌ی واقعی، واقعا چالش بر انگیز است.
من دانشجو هستم و تقریبا استارت این پروژه را از آبان ماه زدم، اما به دلیل یک سری مشکلات از جمله همین دانشجو بودن، کار به کندی پیش رفت و حتی وقفه‌های چند ماهه در آن پیش اومد. هدف من این بود که اساسا یک سیستم با کیفیت بنویسم و در ابتدای کار هم، کار به خوبی پیش می‌رفت، اما با توجه به مشکلات ذکر شده، عمده کار کدنویسی در تعطیلات عید نوروز صورت گرفت، و کاملا از کدنویسی انجام شده مشهود است ک ههمان قسمت هایی که در عید نوروز کدنویسی شده اند، اصطلاحا سرهم بندی شده اند( به خصوص در کدهای سمت کلاینت)
در مورد گام‌های انجام شده؛ پروژه به این منوال انجام شد:
- تحلیل ساختار بانک اطلاعاتی مورد نیاز
- شروع به تحقیق در مورد امکانات مورد نیاز 
- دعوت همکای برای کار گروهی توسط دوستان ( کسی قبول نکرد البته دی:)
- با توجه به محدودیت‌های یافت شده در تحقیقات، ساختار بانک اطلاعاتی نهایی می‌شود.
- انتخاب فریم ورک‌های مناسب( که در اینجا Entity Framework برای orm و ASP.NET MVC برای کدنویسی سمت سرور و bootstrap برای css و jquery هم برای جاوا اسکریپت)
- تحقیق در مورد best practice‌های موجود در مورد هر یک از فریم ورک‌های فوق
-شروع کدنویسی
در مورد قسمت مدیریت کاربران، هدف طراحی یک سیستم خیلی منعطف بود که قطعا با memebrship خود دات نت امکان پذیر نبود. متاسفانه به دلیل مشکلات پیش اومده این قسمت از پروژه هم سرهم بندی کردم و به یک سیستم ساده اکتفا کردم.
برای پیاده سازی آن هم شما کافیست در گوگل عبارت implement custom membership in asp.net mvc را سرچ کنید. مطمئن باشید کلی مطلب پیدا خوهید کرد که با جمع بندی آن یک سیستم خوب می‌توانید پیاده سازی کنید.
الان همین سیستم پیاده سازی شده در سایت یک باگ دارد که بعد از مدتی remember me آ از کار می‌افتد.کوکی کاربر اعتبار دارد، اما رویداد متناظر آن برای اعتبار سنجی اتفاق نمی‌افتد!
الان هم در حال تحقیق برای پیاده سازی یک سیستم اعتبارسنجی  کامل‌تر و اصولی‌تر و یک پارچه‌تر با ASP.NET MVC  هستم که مقاله‌ی زیر خیلی به من کمک کرد.(امیدوارم برای شما هم مفید باشد)
الان هم برنامه ای برای ارتقا این سیستم دارم و مهمترین تغییر آن را می‌توان به استفاده از angularjs برای نوشتن بخش مدیریتی و پیاده سازی آن به صورت single page دانست.( البته اگر این کمردرد بزاره دی:)
امیدوارم دوستان با بازخوردهای خوب خودشون، در ارتقای سطح کیفی کار کمک کنند.
اشتراک‌ها
پلاگین Data Table مبتنی بر AngularJs

JQuery Datatables برای برنامه نویسان وب یک پلاگین کاربردی و معروف محسوب میشود. Angular Datatables نسخه همگام شده با AngularJs است. کار با این پلاگین خیلی ساده است و کسانی که با کارکرد Datatables آشنایی دارند هیچ مشکلی با نسخه AngularJs نخواهند داشت. از امکانات ویژه این پلاگین میتوان به Binding ساده و خودکار و امکان تغییر Optionها در سمت کنترلر و مدیریت promise اشاره کرد.

پلاگین Data Table مبتنی بر AngularJs
مطالب
Blazor 5x - قسمت 27 - برنامه‌ی Blazor WASM - کار با سرویس‌های Web API
در قسمت‌های Blazor Server مثال این سری، با روش کار با سرویس‌های سمت سرور برنامه، آشنا شدیم. در این نوع برنامه‌ها، فقط کافی است اصل سرویس مدنظر را مستقیما در کامپوننت‌های Razor تزریق کرد و سپس می‌توان به نحو متداولی با آن‌ها کار کرد؛ اما در برنامه‌های Blazor WASM خیر! به این نوع برنامه‌های سمت کلاینت باید همانند برنامه‌های React ، Angular ، Vue و یا حتی برنامه‌های مبتنی بر jQuery نگاه کرد. در تمام فناوری‌های سمت کلاینت، این درخواست‌های Ajax ای هستند که با سرویس‌های یک Web API سمت سرور، ارتباط برقرار کرده، اطلاعاتی را به آن‌ها ارسال و یا دریافت می‌کنند. در برنامه‌های Blazor WASM نیز باید به همین ترتیب عمل کرد و در اینجا HttpClient دات نت، جایگزین برای مثال jQuery Ajax ، Fetch API و یا XMLHttpRequest استاندارد می‌شود (البته jQuery Ajax در اصل یک محصور کننده‌ی استاندارد XMLHttpRequest است که برای اولین بار توسط مایکروسافت در برنامه‌ی Outlook web access معرفی شد).


ایجاد سرویس سمت کلاینت دریافت اطلاعات اتاق‌ها از Web API

در قسمت 24، HotelRoomController را تکمیل کردیم که کار آن، بازگشت اطلاعات تمام اتاق‌ها و یا یک اتاق مشخص به کلاینت است. اکنون می‌خواهیم در ادامه‌ی قسمت قبل، اگر کاربری بر روی دکمه‌ی Go صفحه‌ی اول رزرو اتاقی کلیک کرد، لیست تمام اتاق‌های تعریف شده را به او نمایش دهیم. به همین جهت نیاز به سرویس سمت کلاینتی داریم که بتواند با این Web API endpoint کار کند:
namespace BlazorWasm.Client.Services
{
    public interface IClientHotelRoomService
    {
        public Task<IEnumerable<HotelRoomDTO>> GetHotelRoomsAsync(DateTime checkInDate, DateTime checkOutDate);
        public Task<HotelRoomDTO> GetHotelRoomDetailsAsync(int roomId, DateTime checkInDate, DateTime checkOutDate);
    }
}
این سرویس را در پوشه‌ی Services پروژه‌ی BlazorWasm.Client ایجاد کرده‌ایم که HotelRoomDTO خود را از پروژه‌ی BlazorServer.Models دریافت می‌کند. به این ترتیب می‌توان مدلی را بین یک Web API سمت سرور و یک سرویس سمت کلاینت، به اشتراک گذاشت. بنابراین پروژه‌ی کلاینت، باید ارجاعی را به پروژه‌ی BlazorServer.Models.csproj نیز داشته باشد.

در ادامه اینترفیس فوق را به صورت زیر پیاده سازی می‌کنیم:
namespace BlazorWasm.Client.Services
{
    public class ClientHotelRoomService : IClientHotelRoomService
    {
        private readonly HttpClient _httpClient;

        public ClientHotelRoomService(HttpClient httpClient)
        {
            _httpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient));
        }

        public Task<HotelRoomDTO> GetHotelRoomDetailsAsync(int roomId, DateTime checkInDate, DateTime checkOutDate)
        {
            throw new NotImplementedException();
        }

        public Task<IEnumerable<HotelRoomDTO>> GetHotelRoomsAsync(DateTime checkInDate, DateTime checkOutDate)
        {
            // How to url-encode query-string parameters properly
            var uri = new UriBuilderExt(new Uri(_httpClient.BaseAddress, "/api/hotelroom"))
                            .AddParameter("checkInDate", $"{checkInDate:yyyy'-'MM'-'dd}")
                            .AddParameter("checkOutDate", $"{checkOutDate:yyyy'-'MM'-'dd}")
                            .Uri;
            return _httpClient.GetFromJsonAsync<IEnumerable<HotelRoomDTO>>(uri);
        }
    }
}
توضیحات:
- HttpClient یکی از سرویس‌های تنظیم شده‌ی در فایل Program.cs پروژه‌های سمت کلاینت است. بنابراین می‌توان آن‌را از طریق تزریق به سازنده‌ی این سرویس، به دست آورد.
- در اینجا برای دریافت اطلاعات JSON دریافتی از سمت سرور و سپس Deserialize خودکار آن به لیستی از DTO تعریف شده، از متد جدید GetFromJsonAsync استفاده شده‌است. این مورد جزو تازه‌های NET 5x. است.
- در اینجا استفاده از کلاس UriBuilderExt را نیز جهت تشکیل یک URL دارای کوئری استرینگ، مشاهده می‌کنید. هیچگاه نباید URL نهایی را از طریق جمع زدن اجزای آن به سمت سرور ارسال کرد؛ از این جهت که اجزای آن باید URL-encoded شوند؛ وگرنه در سمت سرور قابلیت پردازش نخواهند داشت. در ادامه تعریف کلاس جدید UriBuilderExt را مشاهده می‌کنید:
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Net.Http.Json;
using System.Threading.Tasks;
using BlazorServer.Models;
using BlazorWasm.Client.Utils;

using System;
using System.Collections.Specialized;
using System.Web;

namespace BlazorWasm.Client.Utils
{
    public class UriBuilderExt
    {
        private readonly NameValueCollection _collection;
        private readonly UriBuilder _builder;

        public UriBuilderExt(Uri uri)
        {
            _builder = new UriBuilder(uri);
            _collection = HttpUtility.ParseQueryString(string.Empty);
        }

        public UriBuilderExt AddParameter(string key, string value)
        {
            _collection.Add(key, value);
            return this;
        }

        public Uri Uri
        {
            get
            {
                _builder.Query = _collection.ToString();
                return _builder.Uri;
            }
        }
    }
}
- در اینجا توسط متد AddParameter، کار افزودن کوئری استرینگ‌ها به یک Url از پیش مشخص، انجام می‌شود. کار encoding نهایی به صورت خودکار توسط HttpUtility استاندارد دات نت، انجام خواهد شد.
- تاریخ‌های ارسالی به سمت سرور را با فرمت yyyy'-'MM'-'dd تبدیل رشته کردیم. این قالب، یکی از قالب‌های پذیرفته شده‌است.
- جهت سهولت استفاده‌ی از سرویس فوق و همچنین مدل‌های برنامه، فضای نام آن‌ها را به فایل BlazorWasm.Client\_Imports.razor اضافه می‌کنیم تا در تمام کامپوننت‌های برنامه‌ی سمت کلاینت، قابل دسترسی شوند:
@using BlazorWasm.Client.Services
@using BlazorServer.Models
- در آخر این سرویس جدید را باید به لیست سرویس‌های برنامه معرفی کرد تا قابلیت تزریق در کامپوننت‌ها را پیدا کند:
namespace BlazorWasm.Client
{
    public class Program
    {
        public static async Task Main(string[] args)
        {
            var builder = WebAssemblyHostBuilder.CreateDefault(args);
            // ...

            builder.Services.AddScoped<IClientHotelRoomService, ClientHotelRoomService>();

            // ...
        }
    }
}

چند اصلاح جزئی در کنترلرها و سرویس‌های سمت سرور

در Url نهایی فوق، دو پارامتر جدید checkInDate و checkOutDate هم وجود دارند. به همین جهت این دو را به اکشن متدهای کنترلر HotelRoom:
namespace BlazorWasm.WebApi.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class HotelRoomController : ControllerBase
    {
        // ...

        [HttpGet]
        public async Task<IActionResult> GetHotelRooms(DateTime? checkInDate, DateTime? checkOutDate)
        {
          // ...
        }

        [HttpGet("{roomId}")]
        public async Task<IActionResult> GetHotelRoom(int? roomId, DateTime? checkInDate, DateTime? checkOutDate)
        {
           // ...
        }
    }
}
و همچنین سرویس سمت سرور IHotelRoomService نیز اضافه می‌کنیم:
namespace BlazorServer.Services
{
    public interface IHotelRoomService : IDisposable
    {
        Task<List<HotelRoomDTO>> GetAllHotelRoomsAsync(DateTime? checkInDate, DateTime? checkOutDate);
        Task<HotelRoomDTO> GetHotelRoomAsync(int roomId, DateTime? checkInDate, DateTime? checkOutDate);
        // ...
    }
}
البته فعلا پیاده سازی خاصی ندارند و آن‌ها را در قسمت‌های بعد مورد استفاده قرار خواهیم داد.


تنظیمات ویژه‌ی HttpClient برنامه‌ی سمت کلاینت

سرویس ClientHotelRoomService فوق، از HttpClient تزریق شده‌ی در سازنده‌ی خود استفاده می‌کند که BaseAddress خود را مطابق تنظیمات ابتدایی برنامه، از HostEnvironment دریافت می‌کند. در اینجا علاقمندیم تا بجای این تنظیم پیش‌فرض، فایل جدید appsettings.json را به پوشه‌ی BlazorWasm.Client\wwwroot\appsettings.json کلاینت اضافه کرده (محل قرارگیری آن در برنامه‌های سمت کلاینت، داخل پوشه‌ی wwwroot است و نه در داخل پوشه‌ی ریشه‌ی اصلی پروژه):
{
    "BaseAPIUrl": "https://localhost:5001/"
}
و از این تنظیم جدید به عنوان BaseAddress برنامه‌ی Web API استفاده کنیم که روش آن‌را در کدهای ذیل مشاهده می‌کنید:
namespace BlazorWasm.Client
{
    public class Program
    {
        public static async Task Main(string[] args)
        {
            var builder = WebAssemblyHostBuilder.CreateDefault(args);
            // ... 

            // builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
            builder.Services.AddScoped(sp => new HttpClient
            {
                BaseAddress = new Uri(builder.Configuration.GetValue<string>("BaseAPIUrl"))
            });

            // ... 
        }
    }
}

تکمیل کامپوننت دریافت لیست تمام اتاق‌ها

در قسمت قبل، کامپوننت خالی HotelRooms.razor را تعریف کردیم. کاربران پس از کلیک بر روی دکمه‌ی Go صفحه‌ی اول، به این کامپوننت هدایت می‌شوند. اکنون می‌خواهیم، لیست تمام اتاق‌ها را در این کامپوننت، از Web API برنامه دریافت کنیم:
@page "/hotel/rooms"

@inject ILocalStorageService LocalStorage
@inject IJSRuntime JsRuntime
@inject IClientHotelRoomService HotelRoomService

<h3>HotelRooms</h3>

@code {
    HomeVM HomeModel = new HomeVM();
    IEnumerable<HotelRoomDTO> Rooms = new List<HotelRoomDTO>();

    protected override async Task OnInitializedAsync()
    {
        try
        {
            var model = await LocalStorage.GetItemAsync<HomeVM>(ConstantKeys.LocalInitialBooking);
            if (model is not null)
            {
                HomeModel = model;
            }
            else
            {
                HomeModel.NoOfNights = 1;
            }
            await LoadRooms();
        }
        catch (Exception e)
        {
            await JsRuntime.ToastrError(e.Message);
        }
    }

    private async Task LoadRooms()
    {
        Rooms = await HotelRoomService.GetHotelRoomsAsync(HomeModel.StartDate, HomeModel.EndDate);
    }
}
در اینجا در ابتدا سعی می‌شود تا HomeModel، از Local Storage که در قسمت قبل آن‌را تنظیم کردیم، خوانده شود. سپس با استفاده از متد GetHotelRoomsAsync، لیست اتاق‌ها را از Web API دریافت می‌کنیم. تمام این عملیات آغازین نیز باید در روال رویدادگران OnInitializedAsync صورت گیرند.


روش اجرای پروژه‌های Blazor WASM

تا اینجا اگر برنامه‌ی سمت کلاینت را توسط دستور dotnet watch run اجرا کنیم، هرچند صفحه‌ی خالی نمایش لیست اتاق‌ها ظاهر می‌شود، اما یک خطای fetch error را هم دریافت خواهیم کرد؛ چون نیاز است ابتدا پروژه‌ی Web API را اجرا کرد و سپس پروژه‌ی WASM را.
برای ساده سازی اجرای همزمان این دو پروژه، اگر از ویژوال استودیوی کامل استفاده می‌کنید، بر روی نام Solution کلیک راست کرده و از منوی ظاهر شده، گزینه‌ی «Set Startup projects» را انتخاب کنید. در صفحه دیالوگ ظاهر شده، گزینه‌ی «multiple startup projects» را انتخاب کرده و از لیست پروژه‌های موجود، دو پروژه‌ی Web API و WASM را انتخاب کنید و Action مقابل آن‌ها را به Start تنظیم کنید. در اینجا حتی می‌توان ترتیب اجرای این پروژه‌ها را هم تغییر داد. در این حالت زمانیکه بر روی دکمه‌ی Run، در ویژوال استودیو کلیک می‌کنید، هر دو پروژه را با هم برای شما اجرا خواهد کرد.

نکته‌ی مهم! در این حالت هم برنامه‌ی کلاینت نمی‌تواند با برنامه‌ی Web API ارتباط برقرار کند! چون شماره پورت iisExpress درج شده‌ی در فایل appsettings.json آن، باید به شماره sslPort مندرج در فایل Properties\launchSettings.json پروژه‌ی Web API تغییر داده شود که برای نمونه در اینجا این عدد 44314 است:
{
  "iisSettings": {
    "iisExpress": {
      "applicationUrl": "http://localhost:62930",
      "sslPort": 44314
    }
  }
}
و یا اگر می‌خواهید پروژه را از طریق NET Core CLI. با اجرای دستور dotnet watch run اجرا کنید ... به صورت پیش‌فرض نمی‌شود! چون برای اینکار باید به پوشه‌ی ریشه‌ی پروژه‌های Web API و WASM وارد شد و دوبار دستور یاد شده را به صورت مجزا اجرا کرد. در این حالت، هر دو پروژه، بر روی پورت پیش‌فرض 5001 اجرا می‌شوند. روش تغییر این پورت، مراجعه به فایل Properties\launchSettings.json این پروژه‌ها است. برای مثال همان پورت پیش‌فرض 5001 را که در فایل appsettings.json انتخاب کردیم، ثابت نگه می‌داریم. یعنی فایل launchSettings.json پروژه‌ی Web API را ویرایش نمی‌کنیم. اما این پورت را در پروژه‌ی کلاینت برای مثال به عدد 5002 تغییر می‌دهیم تا برنامه‌ی کلاینت، بر روی پورت پیش‌فرض برنامه‌ی Web API اجرا نشود:
{
    "BlazorWasm.Client": {
      "applicationUrl": "https://localhost:5002;http://localhost:5003",
    }  
}


کدهای کامل این مطلب را از اینجا می‌توانید دریافت کنید: Blazor-5x-Part-27.zip
مطالب
رهنمودهای کد نویسی سی شارپ 3

دو فایل زیر مقاله و خلاصه مقاله‌ای در مورد روش‌های بهتر کد نویسی با سی شارپ 3 هستند.


این رهنمودها (و نه استانداردها) جهت بالا بردن کیفیت کد‌های تهیه شده، یک دست شدن آن‌ها در یک سازمان، تهیه مستندات بهتر و امکان نگهداری ساده‌تر آن‌ها، بسیار مؤثرند.
تعدادی از آن‌ها را در مقاله‌ی "زیباتر کد بنویسیم" دیده‌اید. مقالات فوق گردآوری و به روز رسانی اینگونه نکات جهت پوشش دادن سی شارپ 3 می‌باشند.

ماخذ


نظرات مطالب
سفارشی سازی ASP.NET Core Identity - قسمت سوم - نرمال سازها و اعتبارسنج‌ها
با سلام؛ بنده تغییراتی در ساختار پروژه دادم و متاسفانه اکنون در زمان اجرای پروژه در کلاس IdentityServicesRegistry خط زیر زمان ورود به کلاس AddCustomServicesExtensions.AddCustomServices 
public static void AddCustomServices(this IServiceCollection services)
        {
            var siteSettings = GetSiteSettings(services);
            AddCustomServicesExtensions.AddCustomServices(services);
به ارور
Could not load type 'Identity.CustomNormalizer' from assembly ', Version=1.0.0.0,
 Culture=neutral, PublicKeyToken=null' because the parent type is sealed.
بر می‌خورم. در اینترنت بررسی کردم و نام کلاس، و بیشتر کارهای پیشنهادی رو انجام دادم ولی متاسفانه ارور برطرف نشد. کلاس CustomNormalizer   تغییر داده نشده و مانند پروژه پیشفرض هست. تمامی این کد‌های در پروژه دیگه به درستی کار می‌کنه ولی با انتقال به این پروژه و تغییرات دیتابیس به این ارور می‌خوریم. لازم به ذکر هست که نسخه کور مورد استفاده، 3 پیش‌نمایش 4 می‌باشد.
نظرات مطالب
Blazor 5x - قسمت 21 - احراز هویت و اعتبارسنجی کاربران Blazor Server - بخش 1 - افزودن قالب ابتدایی Identity
- خارج از موضوع بحث نکنید.
- ساختار ابتدایی پروژه‌های Blazor Server را در مطلب «Blazor 5x - قسمت دوم - بررسی ساختار اولیه‌ی پروژه‌های Blazor» بررسی کردیم که در آن AddServerSideBlazor و غیره بحث شده‌اند که شامل موارد دیگری مانند مشخص سازی روش رندر کامپوننت‌ها و ... سیم‌کشی‌های دیگری هم هست برای اجرای بدون خطا:
<component type="typeof(App)" render-mode="ServerPrerendered" />
مطالب
بررسی Environment در Angular
در بین توسعه دهندگان، خیلی رایج است، چند نسخه از Application خود را داشته باشند که Environment‌‌های مختلفی را مورد هدف قرار می‌دهد؛ مثل development که مربوط به حالت توسعه می‌باشد و production که مربوط به حالت ارائه نهایی است. هر Environment تعدادی متغیر منحصر بفرد خود را خواهد داشت؛ مثلAPI Endpoint ،  app version  و ... . انگیولار تنظیمات Environment  را فراهم کرده است تا بتوانیم متغیر‌های منحصر بفردی را برای هر Environment، تعریف کنیم. 
اگر شما می‌خواهید به صورت خودکار یک flag را اعمال کنید، اجازه بدهید بگوییم prod-- ، در این حالت angular compiler متغیر API endpoint را با API endpoint مربوط به حالت ارائه نهایی برای شما جایگزین می‌کند. چیزی که شما نمی‌خواهید این است که به صورت دستی endpoint را قبل از build پروژه، تغییر دهید. این موضوع شانس دچار اشتباه شدن را کاهش می‌دهد.

Dealing with only 2 Environments  

به صورت پیش فرض انگیولار از دو Environment پشتیبانی می‌کند. فایل‌های Environment در دایرکتوری environment  آن قرار دارند که در مسیر زیر می‌باشد:
داخل دایرکتوری src، در ریشه WorkSpace یا پوشه پروژه شما( آشنایی با مفهوم WorkSpace  در انگیولار ). 
در داخل دایرکتوری environment، دو فایل با نام‌های environment.ts و environment.prod.ts وجود دارند. همانطور که ممکن است حدس زده باشید، فایل دوم برای حالت ارائه نهایی می‌باشد؛ در حالیکه فایل اول برای حالت توسعه است و همچنین به عنوان Environment پیش فرض نیز می‌باشد. Angular CLI compiler ، به صورت خودکار فایل اول را با فایل دوم،  هر زمان که شما build را با prod--  انجام میدهید، جایگزین می‌کند:
ng build --prod

اگر فایل environment.ts را باز کنید، خصوصیت production  به false تنظیم شده است؛ درحالیکه در environment.prod.ts خصویت production به true تنظیم شده‌است. همه متغیرها بر اساس Environment مشخصی، تفاوت ایجاد می‌کنند که به صورت مناسب باید در دو فایل قرار داده شوند:
// environment.ts environment variables
export const environment = {
  production: false,
  APIEndpoint: 'https://dev.endpoint.com'
};

// environment.prod.ts environment variables
export const environment = {
  production: true,
  APIEndpoint: 'https://prod.endpoint.com'
};
در اینجا خصوصیت APIEndpoint را داریم. در صورتیکه در حالت توسعه باشد، از آدرس https://dev.endpoint.com و در حالت ارائه نهایی از https://prod.endpoint.com استفاده خواهد کرد.

برای دستیابی به این متغیر‌ها، environment را import کرده و از آن همانند زیر استفاده می‌کنیم:
import { environment } from './../environments/environment';
که به صورت زیر از آن استفاده می‌شود:
const APIEndpoint = environment.APIEndpoint;
در ادامه، هر زمان که شما پروژه خود را بدون build , --prod کنید در متغیر APIEndpoint تعریف شده در بالا، مقدار https://dev.endpoint.com قرار خواهد گرفت که در این حالت از Environment پیش فرض استفاده می‌شود؛ در حالیکه، وقتی از prod-- استفاده می‌کنید، در متغیر APIEndpoint، مقدار https://prod.endpoint.com قرار خواهد گرفت که در این حالت از environment.prod.ts  استفاده می‌شود.

Dealing with 3 or More Environment 

این موضوع خیلی رایج است که برای Application ‌های خود، بیش از دو Environment داشته باشیم. ممکن است که شما نیاز داشته باشید به:
staging environment ،  beta environment ، production environment ، development environment  و ...  . انگیولار یک راه را برای ما فراهم کرده است که به صورت دستی Environment‌‌‌های بیشتری را که ممکن است نیاز داشته باشیم، اضافه کنیم. در اینجا ما نیاز به دو Environment دیگر به نام‌های staging و beta داریم. کار را با ایجاد کردن دو فایل دیگر در کنار environment ‌های موجود شروع می‌کنیم:
1-environment.staging.ts
2-environment.beta.ts
سپس هر کدام از آن‌ها را به صورت زیر ویرایش می‌کنیم: 
// environment.staging.ts environment variables
export const environment = {
  production: true
  APIEndpoint: "https://staging.endpoint.com"
};

// environment.beta.ts environment variables
export const environment = {
  production: true,
  APIEndpoint: "https://beta.endpoint.com"
};

در ادامه نیاز است در فایل angular.json، تنظیمات را تغییر دهیم ( که در ریشه Workspace  می‌باشد) . با انجام این‌کار، این امکان به Angular CLI داده خواهد شد که دو environment جدید ایجاد شده را شناسایی و در صورت نیاز از آن‌ها استفاده کند.

در ابتدا فایل angular.json را باز می‌کنیم و کلید configurations را می‌یابیم که در مسیر زیر می‌باشد:
projects -> yourappname -> architect -> build -> configurations
در اینجا به صورت پیش فرض یک کلید با نام production و تنظیمات زیر وجود دارد:
"configurations": {
   "production": {
       "fileReplacements": [
           {
              "replace": "src/environments/environment.ts",
               "with": "src/environments/environment.prod.ts"
           }
        ],
        "optimization": true,
        "outputHashing": "all",
        "sourceMap": false,
        "extractCss": true,
        "namedChunks": false,
        "aot": true,
        "extractLicenses": true,
        "vendorChunk": false,
        "buildOptimizer": true,
        "serviceWorker": true
   }
}

سپس از کلید production  و تنظمیات درون آن، یک نمونه تهیه می‌کنیم و در زیر کلید production  قرار می‌دهیم. سپس کلید production را به staging تغیر می‌دهیم. در قسمت fileReplacements مقدار کلید with را از
"with":"src/environments/environment.prod.ts"
به
"with":"src/environments/environment.staging.ts"
تغیر می‌دهیم. 
اکنون تنظیمات جدید شما برای staging environment باید به صورت زیر باشد: 
"configurations": {
   "production": {
        // ...
    },
    "staging": {
    "fileReplacements": [
     {
         "replace": "src/environments/environment.ts",
         "with": "src/environments/environment.staging.ts"
     }],
     "optimization": true,
     "outputHashing": "all",
     "sourceMap": true,
     "extractCss": false,
     "namedChunks": false,
     "aot": false,
     "extractLicenses": true,
     "vendorChunk": false,
     "buildOptimizer": true,
     "serviceWorker": true
   }
}

در ادامه همین روال را برای beta environment هم تکرار کنید.

نکته 
ممکن است شما نیاز داشته باشید تا تنظیمات بالا را به حالتی دقیق‌تر نسبت به environment مورد نظر اصلاح کنید. مثلا ممکن است نیاز داشته باشید، Service worker را در حالت staging فعال نگه دارید و در حالت beta آن را غیر فعال کنید که این تضمین می‌کند وقتی ریفرش انجام می‌شود، app ، Service worker  و منابع مربوط به آن را در مرورگر کش نکرده باشد.

Building your App 

در نهایت برای build کردن application خود با environment‌‌‌های سفارشی ایجاد شده، می‌توانید از پرچم configurations-- استفاده کنید؛ همانند زیر:
//for staging environment
ng build --configuration=staging

//for beta environment
ng build --configuration=beta

و در نهایت برای استفاده کردن از environment پیش فرض، استفاده از دستور زیر به تنهایی کافی می‌باشد:
//for production environment
ng build --prod

//for dev environment
ng build

مطالعه بیشتر جهت توزیع برنامه  : Angular CLI - ساخت و توزیع برنامه 
اکنون شما می‌توانید پروژه انگیولار خود را با هر تعداد environment ای که دوست دارید، configure کنید. 
مطالب
آشنایی با نحوه‌ی وهله سازی کنترلرها در ASP.NET MVC با ساخت یک Controller Factory سفارشی
یکی از مزایای مهم فریم ورک ASP.NET MVC، توسعه پذیری کنترلرهای آن است. با مرور قسمت‌هایی از مسیر پردازش درخواست که منجر به اجرای یک اکشن متد می‌شود، شروع می‌کنیم و روش‌های مختلفی را که می‌توان بر روی این پردازش، کنترل داشت، بررسی می‌کنیم. شکل ذیل مسیر یک درخواست را مابین کامپوننت‌های مختلف فریم ورک نشان می‌دهد:
 
 

Controller Factory و Action Invoker وظیفه‌ای مطابق نامشان را عهده دار هستند. اولی برای وهله سازی کنترلرهای مرتبط با درخواست و دومی برای پیدا کردن و تریگر نمودن یک اکشن متد به کار گرفته می‌شوند. فریم ورک MVC پیاده سازی پیش فرضی را از این دو کامپوننت، به صورت توکار دارد. در طی مقالاتی نحوه‌ی کنترل کردن رفتار پیش فرض این Controller Factory و هم نحوه‌ی جایگزین کرن کامل این کامپوننت را بررسی می‌کنیم.

ابتدا پروژه‌ی جدیدی را از نوع MVC و با الگوی Empty به نام ControllerExtensibility ایجاد می‌کنیم. در پوشه‌ی Models یک فایل را به نام Result.cs ساخته و از آن برای معرفی کلاس Result مطابق کدهای ذیل استفاده می‌کنیم:
namespace ControllerExtensibility.Models
{
    public class Result
    {
        public string ControllerName { get; set; }
        public string ActionName { get; set; }
    }
}
در مسیر /Views/Shared ویویی را به نام Result.cshtml اضافه می‌کنیم. این ویویی است که در این مثال، همه‌ی اکشن متدهای کنترلرهایمان، آن را رندر خواهند کرد:
@model ControllerExtensibility.Models.Result
@{
Layout = null;
}
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>Result</title>
</head>
<body>
<div>Controller: @Model.ControllerName</div>
<div>Action: @Model.ActionName</div>
</body>
</html>
در خط اول، مدل ویو را از نوع کلاس Result تعیین کرده‌ایم.
دو کنترلر را نیز حاوی کدهای زیر ایجاد می‌کنیم:

کنترلر product
using ControllerExtensibility.Models;
using System.Web.Mvc;
namespace ControllerExtensibility.Controllers
{
    public class ProductController : Controller
    {
        public ViewResult Index()
        {
            return View("Result", new Result
            {
                ControllerName = "Product",
                ActionName = "Index"
            });
        }
        public ViewResult List()
        {
            return View("Result", new Result
            {
                ControllerName = "Product",
                ActionName = "List"
            });
        }
    }
}

کنترلر customer

using System.Web.Mvc;
namespace ControllerExtensibility.Controllers
{
    public class CustomerController : Controller
    {
        public ViewResult Index()
        {
            return View("Result", new Result
            {
                ControllerName = "Customer",
                ActionName = "Index"
            });
        }
        public ViewResult List()
        {
            return View("Result", new Result
            {
                ControllerName = "Customer",
                ActionName = "List"
            });
        }
    }
}
اکشن‌های این دو کنترلر حاوی کد خاصی نبوده و صرفا ویوی Result.cshtml را صدا می‌زنند. ولی در این مرحله این همه‌ی آن چیزی است که برای نشان دادن نحوه‌ی سفارشی کردن کنترلرها بدان نیاز داریم.
ایجاد یک Controller Factory سفارشی بهترین راه برای درک نحوه‌ی وهله سازی کنترلر‌ها توسط MVC است. ولی این کار صرفا جنبه‌ی آموزشی داشته و در یک پروژه‌ی واقعی این نوع پیاده سازی‌ها پیشنهاد نمی‌شود؛ زیرا راه‌های مفیدتر و ساده‌تری با پیاده سازی توکار Controller Factory وجود دارند.
Controller Factory‌ها با پیاده سازی اینترفیس IControllerFactory معرفی می‌شوند. کدهای این اینترفیس را در ذیل می‌بینید:
using System.Web.Routing;
using System.Web.SessionState;
namespace System.Web.Mvc
{
    public interface IControllerFactory
    {
        IController CreateController(RequestContext requestContext,
        string controllerName);
        SessionStateBehavior GetControllerSessionBehavior(RequestContext requestContext,
        string controllerName);
        void ReleaseController(IController controller);
    }
}
پوشه‌ای را به نام Infrastructure ساخته و فایلی را به نام CustomControllerFactory.cs ، حاوی کدهای زیر اضافه کنید:
using System;
using System.Web.Mvc;
using System.Web.Routing;
using System.Web.SessionState;
using ControllerExtensibility.Controllers;

namespace ControllerExtensibility.Infrastructure
{
    public class CustomControllerFactory : IControllerFactory
    {
        public IController CreateController(RequestContext requestContext,
            string controllerName)
        {
            Type targetType = null;
            switch (controllerName)
            {
                case "Product":
                    targetType = typeof (ProductController);
                    break;
                case "Customer":
                    targetType = typeof (CustomerController);
                    break;
                default:
                    requestContext.RouteData.Values["controller"] = "Product";
                    targetType = typeof (ProductController);
                    break;
            }
            return targetType == null
                ? null
                : (IController) DependencyResolver.Current.GetService(targetType);
        }

        public SessionStateBehavior GetControllerSessionBehavior(RequestContext
            requestContext, string controllerName)
        {
            return SessionStateBehavior.Default;
        }

        public void ReleaseController(IController controller)
        {
            IDisposable disposable = controller as IDisposable;
            if (disposable != null)
            {
                disposable.Dispose();
            }
        }
    }
}
مهمترین متد کدهای فوق، CreateController است که فریم ورک، بر حسب نیاز، جهت سرویس دهی به درخواست واصله آن را صدا خواهد زد. پارامتر ورودی این متد، شیء RequestContext است که جزئیاتی در خصوص درخواست واصله را در اختیار factory خواهد گذاشت. همچنین یک رشته که نام کنترلر را بر حسب URL واصله تعیین می‌کند:
 

نام

نوع

توضیحات

HttpContext

HttpContextBase

حاوی اطلاعاتی در خصوص درخواست است.

RouteData

RouteData

حاوی اطلاعاتی در خصوص Rout است که با درخواست رسیده همخوانی دارد.

 
یکی از دلایلی که عنوان شد Controller factory سفارشی بدین روش در یک پروژه‌ی عملی به کار گرفته نشود این است که یافتن کلاس‌هایی از نوع Controller در سراسر برنامه و وهله سازی آنها کار دشواری است. چرا که لازم خواهد بود بتوانید به صورت پویا کنترلر را مکان یابی کرده و بین کلاس‌های هم نام در دیگر فضاهای نام تمییز قائل شوید و خطاهای محتمل در حین وهله سازی را کنترل کنید.
در این مثال تنها دو کنترلر داریم و آنها را به صورت مستقیم در Controller Factory وهله سازی می‌کنیم که در یک پروژه‌ی واقعی مطلوب نیست. ولی آنچه را که این روش آشکار‌تر می‌سازد، انعطاف پذیری بالای فریم ورک MVC است که دست ما را برای نفوذ و دخل و تصرف در اعمال و رفتاریهای پیش فرض خود باز گذاشته است و برای مثال در مباحث تزریق وابستگی‌ها و تنظیمات ابتدایی IoC Containers کاربرد دارد.
متد CreateController لازم است وهله‌ای از کلاسی که اینترفیس IController را پیاده سازی کرده برگرداند؛ در غیر اینصورت کار با خطا متوقف خواهد شد. لذا برای زمانی که درخواست کاربر، هیچ کدام از کنترلر‌ها را مشمول عنایت قرار نمی‌دهد، باید چاره‌ای اندیشیده شود.
می‌توان آن را به کنترلر خاصی که پیغام خطایی را رندر می‌کند، هدایت کنیم. به عبارت بهتر باید درخواست را به کنترلری که مطمئن هستیم وجود دارد (اصطلاحا کنترلر جانشین) هدایت نماییم. همان طور که در کد فوق در قسمت default می‌بینید:
default:
requestContext.RouteData.Values["controller"] = "Product";
targetType = typeof(ProductController);
break;
در صورت عدم تطابق با هیچ کدام از حالات تعیین شده، درخواست را به کنترلر ProductController جهت رسیدگی هدایت کرده‌ایم.
در MVC انتخاب ویوی مناسب، بر حسب مقدار RouteData.Values صورت می‌گیرد؛ نه نام کلاس Controller و این سبب خواهد شد فریم ورک، ویوهای مرتبط با کنترلر جانشین شده‌ی توسط ما را جستجو کند و نه کنترلری که کاربر از طریق URL ورودی آن را درخواست کرده است.
لذا Controller Factory صرفا وظیفه مپ کردن درخواست‌های واصله به کنترلر‌ها را ندارد، بلکه توانایی دخل و تصرف در درخواست واصله بر حسب مورد را نیز خواهد داشت.
در نهایت هم نحوه‌ی استفاده از DependencyResolver را برای وهله سازی کلاس‌های کنترلر می‌بینید. متد استاتیک Current یک پیاده سازی از اینترفیس IDependencyResolver را که حاوی متد GetService است، برگشت داده و سپس یک شیء System.Type را به عنوان ورودی گرفته و یک وهله‌ی ساخته شده‌ی از آن را به عنوان خروجی برمی‌گرداند.
متد GetControllerSessionBehavior نیز توسط MVC جهت تعیین اینکه Session data برای کنترلر نیاز است یا خیر به کار گرفته می‌شود.
متد ReleaseController نیز هر گاه به شیء کنترلر ساخته شده در متد CreateController دیگر نیازی نبود، صدا زده خواهد شد. در کدهای ما ابتدا بررسی می‌شود آیا اینترفیس IDisposable توسط کلاس، پیاده سازی شده است یا خیر؟ اگر بلی متد Dispose آن جهت آزاد سازی منابعی که می‌توانند آزاد شوند، صدا زده می‌شود.
جهت ثبت Controller  Factory ساخته شده در متد Application_Start موجود در فایل global.asax.cs بوسیله کلاس ControllerBuilder و مطابق کدهای ذیل عمل می‌نماییم:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Http;
using System.Web.Mvc;
using System.Web.Routing;
using ControllerExtensibility.Infrastructure;
namespace ControllerExtensibility
{
    public class MvcApplication : System.Web.HttpApplication
    {
        protected void Application_Start()
        {
            AreaRegistration.RegisterAllAreas();
            WebApiConfig.Register(GlobalConfiguration.Configuration);
            FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
            RouteConfig.RegisterRoutes(RouteTable.Routes);
            ControllerBuilder.Current.SetControllerFactory(new
            CustomControllerFactory());
        }
    }
}
پس از ثبت به شیوه‌ی فوق، controller factory ساخته شده، مسئول هندل کردن تمامی درخواست‌های واصله‌ی به برنامه خواهد بود. پس از اولین اجرا، مرورگر ریشه‌ی سایت را هدف قرار خواهد داد که توسط سیستم مسیر یابی به کنترلر Home، نگاشت شده و بر اساس تعاریف و کدهای ما، چون با هیچ کدام از کنترلرهای Product و Customer تطابق نخواهد داشت، به کنترلر جایگزین تنظیم شده، یعنی Product هدایت خواهد شد.


 
مطالب
بسته بندی نرم افزار الکترون
بعد از اینکه برنامه الکترون آماده شد، لازم است آن را به فایل‌های اجرایی پلتفرم‌های مختلف تبدیل کنیم. برای اینکار بسته معروف Electron-packager را مورد استفاده قرار می‌دهیم. برای نصب آن به شکل زیر اقدام کنید:
npm install electron-packager --save-dev
بعد از اینکه نصب شد، در فایل package.json در قسمت scripts، خصوصیت جدیدتری را وارد می‌کنیم:
"build":"electron-packager . myapp --platform=all --arch=all --overwrite"
این دستور شامل حداقل‌های آرگومان‌ها می‌باشد. در تشریح این دستور باید گفت اولین آرگومان، نام دایرکتوری است که برنامه شما در آن نوشته شده است و حاوی فایل package.json است و با قرار دادن علامت "." دایرکتوری جاری را معرفی کرده‌ایم. بعد از آن نام برنامه و فایل اجرایی برنامه است. بعد از آن فلگ‌ها آغاز می‌شوند که اولین فلگ مشخص میکند خروجی خود را برای چه پلتفرمی نیاز دارید و شامل مقادیر زیر می‌شود؛ یا اینکه عبارت all را برای در نظر گرفتن همه مقادیر وارد کنید.
darwin
سیستم عامل مکینتاش
linux
 سیستم عامل لینوکس
win32
 سیستم عامل ویندوز

فلگ بعدی معماری سیستم عامل را نشان میدهد که برای سیستم‌های 32 بیتی مقدار ia32 و برای سیستم‌های 64 بیتی مقدار x64 می‌باشد. ولی در صورتیکه همه مقادیر را در نظر دارید، می‌توانید همانند خط بالا از مقدار all استفاده کنید.

در همه حالات بالا اگر فقط تعدادی از آن‌ها را بخواهید وارد کنید، می‌توانید هر عبارت را با , از هم جدا سازید؛ مانند darwin,linux که برای این دو پلتفرم تنها نسخه اجرایی تولید می‌شود.

فلگ آخر اجباری نیست، ولی برای دفعات بعدی بسیار مناسب است. اگر از قبل یک بسته بندی وجود دارد، بسته بندی جدید بر روی قبلی‌ها رونویسی خواهد شد.

حال با دستور زیر در nodejs، عملیات بسته بندی را آغاز می‌کنیم:

npm run build
بعد از اجرای این دستور برای اولین بار، ممکن است برای هر پلتفرم، کتابخانه‌های مربوطه را دانلود کند تا بر اساس آن، عمل بسته بندی را انجام دهد. سپس در دایرکتوری پروژه، دایرکتوری‌های جدیدی را با نام‌های مشخصی خواهید یافت.
یکی از دیگر فلگ‌ها که کاربردی می‌باشد، برای نادیده گرفتن ورورد یک سری پکیج‌ها به بسته نهایی است که به طور پیش فرض جلوی ورود بسته‌های eletron-prebuilt و electron-packager را میگیرد. ولی اگر دوست دارید تا بسته‌های دیگری را نیز به این لیست اضافه کنید، دستو زیر را به کار ببرید:
--ignore=node_modules/<package_name>
یا
--ignore=node_modules/electron_[0-9]*

فلگ‌های پر استفاده دیگر این بسته:
 aap-version  نسخه برنامه
 app-copyright  متنی برای قانون کپی رایت
 asar موقعی که برنامه‌ای را بسته بندی می‌کنید، در دایرکتوری Resources/App، هنوز سورس برنامه وجود دارد که فایل اجرایی شما بدون آن قادر به ادامه فعالیت نیست. ولی اگر بخواهیم این سورس را در اختیار شخصی قرار ندهیم، باید از ویژگی asar استفاده کنیم. با استفاده از این فلگ، فایلی با نام app.asar جای این دایرکتوری ایجاد خواهد شد و دیگر نیازی نیست تا سورس برنامه همراه آن باشد.
 icon در صورتیکه قصد استفاده از آیکنی بجز آیکون الکترون را دارید.
 out به طور پیش فرض برنامه نهایی در دایرکتوری کاری پروژه اضافه می‌شود. در صورتیکه قصد دارید آن‌را در دایرکتوری بجز دایرکتوری کاری قرار دهید، از این ویژگی استفاده کنید.
 version-string این خصوصیت برای نسخه بندی برنامه است که فقط برای ویندوز کاربرد دارد و شامل خصوصیاتی چون نام محصول، نام سازنده، توصیف برنامه و ... می‌باشد:
--version-string.ProductName="Product"

                   Properties supported:
                   - CompanyName
                   - FileDescription
                   - OriginalFilename
                   - ProductName
                   - InternalName

prune
استفاده از این فلگ باعث می‌شود کلیه بسته‌های معرفی شده در dev-dependency به بسته نهایی اضافه نشوند

دستور بسته بندی بالا را نیز می‌توان به طور خلاصه‌تر نیز نوشت :

electron-packager . --all
این دستور با در نظر گرفتن همه پلتفرم‌ها و معماری آن‌ها، برای دایرکتوری جاری اجرا شده و نام برنامه و دیگر اطلاعات را از طریق فایل package.json به دست می‌آورد.