پاسخ به بازخورد‌های پروژه‌ها
استفاده از داده ها در هدر گزارش
بله. امکان تعریف یک هدر سفارشی با اطلاعات پویا در آن وجود دارد. یک مثال
پارامتر IList<SummaryCellData> summaryData اطلاعات پویای مورد نیاز را تهیه می‌کند.
پاسخ به بازخورد‌های پروژه‌ها
ساختار گزارش خاص
این گزارش شبیه به مثال تولید برگه سؤالات امتحانی است. دو روش برای حل آن وجود دارد:
الف) استفاده از قالب سفارشی سلول‌ها: (^)
ب) استفاده از قالب‌های open office و ترکیب آن با PdfReport: (^)
نظرات نظرسنجی‌ها
نظر شما در رابطه با استفاده از کامپوننت های رایگان و آماده Blazor چیست؟
من درپروژه هام از mudblazor استفاده میکنم.نکته مثبتی که داره اینه که میتونین کلاس‌های هر کامپوننت رو با ارث بری دریک کلاس دیگه سفارشی سازی کنین
مطالب
پیاده سازی Remote Validation در Blazor
فرم‌های Blazor به همراه پشتیبانی از ویژگی Remote که به همراه ASP.NET Core ارائه می‌شود، نیستند. هرچند می‌توان در حین ارسال فرم به سرور، نتیجه‌ی اعتبارسنجی از راه دور و سمت سرور را به کاربر نمایش داد، اما تجربه‌ی کاربری آن در حد Remote validation نیست. یعنی می‌خواهیم در حین ورود اطلاعات و یا انتقال focus به کنترل دیگری، اعتبارسنجی سمت سرور صورت گیرد و نه فقط در زمان ارسال کل اطلاعات به سرور، در پایان کار. در این مطلب روشی را جهت پیاده سازی این عملیات بررسی خواهیم کرد.


ایجاد یک پروژه‌ی ابتدایی Blazor WASM

پروژه‌ای را که در این مطلب تکمیل خواهیم کرد، از نوع Blazor WASM هاست شده‌است. بنابراین در پوشه‌ی فرضی BlazorAsyncValidation، دستور «dotnet new blazorwasm --hosted» را صادر می‌کنیم تا ساختار ابتدایی پروژه که به همراه یک کلاینت Blazor WASM و یک سرور ASP.NET Core Web API است، تشکیل شود. از قسمت Web API، برای پیاده سازی اعتبارسنجی سمت سرور استفاده خواهیم کرد.


مدل ثبت نام برنامه

مدل ثبت نام برنامه تنها از یک خاصیت نام تشکیل شده و در پروژه‌ی Shared قرار می‌گیرد تا هم در کلاینت و هم در سرور قابل استفاده باشد:
using System.ComponentModel.DataAnnotations;

namespace BlazorAsyncValidation.Shared;

public class UserDto
{
    [Required] public string Name { set; get; } = default!;
}
این نام است که می‌خواهیم عدم تکراری بودن آن‌را حین ثبت نام در سمت سرور، بررسی کنیم. به همین جهت کنترلر API زیر را برای آن تدارک خواهیم دید.


کنترلر API ثبت نام برنامه

کنترلر زیر که در پوشه‌ی BlazorAsyncValidation\Server\Controllers قرار می‌گیرد، منطق بررسی تکراری نبودن نام دریافتی از برنامه‌ی کلاینت را شبیه به منطق remote validation استاندارد MVC، پیاده سازی می‌کند که در نهایت یک true و یا false را باز می‌گرداند.
در اینجا خروجی بازگشت داده کاملا در اختیار شما است و نیازی نیست تا حتما ارتباطی با MVC داشته باشد؛ چون مدیریت سمت کلاینت بررسی آن‌را خودمان انجام خواهیم داد و نه یک کتابخانه‌ی از پیش نوشته شده و مشخص.
using BlazorAsyncValidation.Shared;
using Microsoft.AspNetCore.Mvc;

namespace BlazorAsyncValidation.Server.Controllers;

[ApiController, Route("api/[controller]/[action]")]
public class RegisterController : ControllerBase
{
    [HttpPost]
    public IActionResult IsUserNameUnique([FromBody] UserDto userModel)
    {
        if (string.Equals(userModel?.Name, "Vahid", StringComparison.OrdinalIgnoreCase))
        {
            return Ok(false);
        }

        return Ok(true);
    }
}

غنی سازی فرم استاندارد Blazor جهت انجام Remote validation



اگر بخواهیم از EditForm استاندارد Blazor در حالت متداول آن و بدون هیچ تغییری استفاده کنیم، مانند مثال زیر که InputText متصل به خاصیت Name مربوط به Dto برنامه را نمایش می‌دهد:
@page "/"

<PageTitle>Index</PageTitle>

<h2>Register</h2>

<EditForm EditContext="@EditContext" OnValidSubmit="DoSubmitAsync">
    <DataAnnotationsValidator/>
    <div class="row mb-2">
        <label class="col-form-label col-lg-2">Name:</label>
        <div class="col-lg-10">
            <InputText @bind-Value="Model.Name" class="form-control"/>
            <ValidationMessage For="@(() => Model.Name)"/>
        </div>
    </div>

    <button class="btn btn-secondary" type="submit">Submit</button>
</EditForm>
 تنها رخ‌دادی که در اختیار ما قرار می‌گیرد، رخ‌داد submit (در حالت موفقیت اعتبارسنجی سمت کلاینت و یا تنها submit معمولی) است. بنابراین برای دسترسی به رخ‌دادهای بیشتر EditForm، نیاز است با EditContext آن کار کنیم:
public partial class Index
{
    private const string UserValidationUrl = "/api/Register/IsUserNameUnique";

    private ValidationMessageStore? _messageStore;
    [Inject] private HttpClient HttpClient { set; get; } = default!;
    private EditContext? EditContext { set; get; }

    private UserDto Model { get; } = new();
به همین جهت EditContext را در سطح کامپوننت جاری تعریف کرده و همچنین سرویس HttpClient را جهت ارسال اطلاعات Name و دریافت پاسخ true/false از سرور و یک ValidationMessageStore را برای نگهداری تعاریف خطاهای سفارشی که قرار است در فرم نمایش داده شوند، معرفی می‌کنیم.
ValidationMessageStore به همراه متد Add است و اگر به آن نام فیلد مدنظر را به همراه پیامی، اضافه کنیم، این اطلاعات را به صورت خطای اعتبارسنجی توسط کامپوننت ValidationMessage نمایش می‌دهد.

محل مقدار دهی اولیه‌ی این اشیاء نیز در روال رویدادگردان OnInitialized به صورت زیر است:
    protected override void OnInitialized()
    {
        EditContext = new EditContext(Model);
        _messageStore = new ValidationMessageStore(EditContext);
        EditContext.OnFieldChanged += (sender, eventArgs) =>
        {
            var fieldIdentifier = eventArgs.FieldIdentifier;
            _messageStore?.Clear(fieldIdentifier);
            _ = InvokeAsync(async () =>
            {
                var errors = await OnValidateFieldAsync(fieldIdentifier.FieldName);
                if (errors?.Any() != true)
                {
                    return;
                }

                foreach (var error in errors)
                {
                    _messageStore?.Add(fieldIdentifier, error);
                }

                EditContext.NotifyValidationStateChanged();
            });
            StateHasChanged();
        };
        EditContext.OnValidationStateChanged += (sender, eventArgs) => StateHasChanged();
        EditContext.OnValidationRequested += (sender, eventArgs) => _messageStore?.Clear();
    }
در اینجا ابتدا یک نمونه‌ی جدید از EditContext، بر اساس Model فرم، تشکیل می‌شود. سپس ValidationMessageStore سفارشی که قرار است خطاهای اعتبارسنجی remote ما را نمایش دهد نیز نمونه سازی می‌شود. در ادامه امکان دسترسی به رخ‌دادهای OnFieldChanged ، OnValidationStateChanged و OnValidationRequested را خواهیم داشت که در حالت معمولی کار با EditForm میسر نیستند.
برای مثال اگر فیلدی تغییر کند، رویداد OnFieldChanged صادر می‌شود. در همینجا است که کار فراخوانی متد OnValidateFieldAsync که در ادامه معرفی می‌شود را انجام می‌دهیم تا کار اعتبارسنجی Async سمت سرور را انجام دهد. اگر نتیجه‌ای به همراه آن بود، توسط messageStore به صورت یک خطای اعتبارسنجی نمایش داده خواهد شد و همچنین EditContext نیز با فراخوانی متد NotifyValidationStateChanged، وادار به به‌روز رسانی وضعیت اعتبارسنجی خود می‌گردد.

متد سفارشی OnValidateFieldAsync که کار اعتبارسنجی سمت سرور را انجام می‌دهد، به صورت زیر تعریف شده‌است:
    private async Task<IList<string>?> OnValidateFieldAsync(string fieldName)
    {
        switch (fieldName)
        {
            case nameof(UserDto.Name):
                var response = await HttpClient.PostAsJsonAsync(
                    UserValidationUrl,
                    new UserDto { Name = Model.Name });
                var responseContent = await response.Content.ReadAsStringAsync();
                if (string.Equals(responseContent, "false", StringComparison.OrdinalIgnoreCase))
                {
                    return new List<string>
                    {
                        $"`{Model.Name}` is in use. Please choose another name."
                    };
                }

                // TIP: It's better to use the `DntDebounceInputText` component for this case to reduce the network round-trips.

                break;
        }

        return null;
    }
در اینجا درخواستی به سمت آدرس api/Register/IsUserNameUnique ارسال شده و سپس بر اساس مقدار true و یا false آن، پیامی را بازگشت می‌دهد. اگر نال را بازگشت دهد یعنی مشکلی نبوده.

یک نکته: InputText استاندارد در حالت معمول آن، پس از تغییر focus به یک کنترل دیگر، سبب بروز رویداد OnFieldChanged می‌شود و نه در حالت فشرده شدن کلیدها. به همین جهت اگر برنامه پیوستی را می‌خواهید آزمایش کنید، نیاز است فقط focus را تغییر دهید و یا یک کنترل سفارشی را برای اینکار توسعه دهید.


کدهای کامل این مطلب را از اینجا می‌توانید دریافت کنید: BlazorAsyncValidation.zip
مطالب
اعمال کنترل دسترسی پویا در پروژه‌های ASP.NET Core با استفاده از AuthorizationPolicyProvider سفارشی

در مطلب «سفارشی سازی ASP.NET Core Identity - قسمت پنجم - سیاست‌های دسترسی پویا» به طور مفصل به قضیه کنترل دسترسی پویا در ASP.NET Core Identity پرداخته شده‌است؛ در این مطلب روش دیگری را بررسی خواهیم کرد.

مشخص می‌باشد که بدون وابستگی به روش خاصی، خیلی ساده می‌توان به شکل زیر عمل کرد:

services.AddAuthorization(options =>
{
    options.AddPolicy("View Projects", 
        policy => policy.RequireClaim(CustomClaimTypes.Permission, "projects.view"));
});
با یک ClaimType مشخص برای دسترسی‌ها، یک سیاست جدید را تعریف کرده و برای استفاده از آن نیز همانند قبل به شکل زیر می‌توان عمل کرد:
[Authorize("View Projects")]
public IActionResult Index(int siteId)
{
    return View();
}
روشی یکپارچه و بدون نیاز به کوچکترین سفارشی سازی؛ ولی در مقیاس بزرگ تعریف سیاست‌ها برای تک تک دسترسی‌های مورد نیاز، قطعا آزار دهنده خواهد بود. خبر خوب اینکه زیرساخت احراز هویت و کنترل دسترسی در ASP.NET Core مکانیزمی برای خودکار کردن فرآیند تعریف options.AddPolicy‌ها در کلاس آغازین برنامه، ارائه داده‌است که دقیقا یکی از موارد استفاده‌ی آن، راه حلی می‌باشد برای همین مشکلی که مطرح شد.
Using a large range of policies (for different room numbers or ages, for example), so it doesn’t make sense to add each individual authorization policy with an AuthorizationOptions.AddPolicy call. 


کار با پیاده سازی واسط IAuthorizationPolicyProvider شروع می‌شود؛ یا شاید ارث بری از DefaultAuthorizationPolicyProvider رجیستر شده‌ی در سیستم DI و توسعه آن هم کافی باشد.

public class AuthorizationPolicyProvider : DefaultAuthorizationPolicyProvider
{
    public AuthorizationPolicyProvider(IOptions<AuthorizationOptions> options)
        : base(options)
    {
    }

    public override Task<AuthorizationPolicy> GetPolicyAsync(string policyName)
    {
        if (!policyName.StartsWith(PermissionAuthorizeAttribute.PolicyPrefix, StringComparison.OrdinalIgnoreCase))
        {
            return base.GetPolicyAsync(policyName);
        }

        var permissionNames = policyName.Substring(PermissionAuthorizeAttribute.PolicyPrefix.Length).Split(',');

        var policy = new AuthorizationPolicyBuilder()
            .RequireClaim(CustomClaimTypes.Permission, permissionNames)
            .Build();

        return Task.FromResult(policy);
    }
}

متد GetPolicyAsync موظف به یافتن و بازگشت یک Policy ثبت شده می‌باشد؛ با این حال می‌توان با بازنویسی آن و با استفاده از وهله‌ای از AuthorizationPolicyBuilder، فرآیند تعریف سیاست درخواست شده را که احتمالا در تنظیمات آغازین پروژه تعریف نشده و پیشوند مدنظر را نیز دارد، خوکار کرد. در اینجا امکان ترکیب کردن چندین دسترسی را هم خواهیم داشت که برای این منظور می‌توان دسترسی‌های مختلف را به صورت comma separated به سیستم معرفی کرد. 

نکته‌ی مهم در تکه کد بالا مربوط است به PolicyPrefix که با استفاده از آن مشخص کرده‌ایم که برای هر سیاست درخواستی، این فرآیند را طی نکند و موجب اختلال در سیستم نشود. 


پس از پیاده سازی واسط مطرح شده، لازم است این پیاده سازی جدید را به سیستم DI هم معرفی کنید:

services.AddSingleton<IAuthorizationPolicyProvider, AuthorizationPolicyProvider>();


خوب، تا اینجا فرآیند تعریف سیاست‌ها به صورت خودکار انجام شد. در ادامه نیاز است با تعریف یک فیلتر Authorization، بتوان لیست دسترسی‌های مورد نظر برای اکشنی خاص را نیز مشخص کرد تا در متد GetPolicyAsync فوق، کار ثبت خودکار سیاست دسترسی متناظر با آن‌را توسط فراخوانی متد policyBuilder.RequireClaim، انجام دهد تا دیگر نیازی به تعریف دستی و جداگانه‌ی آن، در کلاس آغازین برنامه نباشد. برای این منظور به شکل زیر عمل خواهیم کرد:

    public class PermissionAuthorizeAttribute : AuthorizeAttribute
    {
        internal const string PolicyPrefix = "PERMISSION:";

        /// <summary>
        /// Creates a new instance of <see cref="AuthorizeAttribute"/> class.
        /// </summary>
        /// <param name="permissions">A list of permissions to authorize</param>
        public PermissionAuthorizeAttribute(params string[] permissions)
        {
            Policy = $"{PolicyPrefix}{string.Join(",", permissions)}";
        }
    }

همانطور که مشخص می‌باشد، رشته PERMISSION به عنوان پیشوند رشته تولیدی از لیست اسامی دسترسی‌ها، استفاده شده‌است و در پراپرتی Policy قرار داده شده‌است. این بار برای کنترل دسترسی می‌توان به شکل زیر عمل کرد:

[PermissionAuthorize(PermissionNames.Projects_View)]
public IActionResult Get(FilteredQueryModel query)
{
   //...
}
[PermissionAuthorize(PermissionNames.Projects_Create)]
public IActionResult Post(ProjectModel model)
{
   //...
}

برای مثال در اولین فراخوانی فیلتر PermissionAuthorize فوق، مقدار ثابت PermissionNames.Projects_View به عنوان یک Policy جدید به متد GetPolicyAsync کلاس AuthorizationPolicyProvider سفارشی ما ارسال می‌شود. چون دارای پیشوند «:PERMISSION» است، مورد پردازش قرار گرفته و توسط متد policyBuilder.RequireClaim به صورت خودکار به سیستم معرفی و ثبت خواهد شد.


همچنین راه حل مطرح شده برای مدیریت دسترسی‌های پویا، در gist به اشتراک گذاشته شده «موجودیت‌های مرتبط با مدیریت دسترسی‌های پویا» را نیز مد نظر قرار دهید.

مطالب
یکپارچه سازی Angular CLI و ASP.NET Core در VS 2017
در این مطلب مثالی را در مورد نحوه‌ی تنظیمات یک پروژه‌ی خالی ASP.NET Core، جهت استفاده‌ی از یک پروژه‌ی Angular CLI قرار گرفته‌ی در پوشه‌ی آن‌را بررسی خواهیم کرد.

پیشنیازها

 - مطالعه‌ی سری کار با Angular CLI خصوصا قسمت نصب و قسمت ساخت برنامه‌های آن، پیش از مطالعه‌ی این مطلب ضروری است.
 - همچنین فرض بر این است که سری ASP.NET Core را نیز یکبار مرور کرده‌اید و با نحوه‌ی برپایی یک برنامه‌ی MVC آن و ارائه‌ی فایل‌های استاتیک توسط یک پروژه‌ی ASP.NET Core آشنایی دارید.


ایجاد یک پروژه‌ی جدید ASP.NET Core در VS 2017

در ابتدا یک پروژه‌ی خالی ASP.NET Core را در VS 2017 ایجاد خواهیم کرد. برای این منظور:
 - ابتدا از طریق منوی File -> New -> Project (Ctrl+Shift+N) گزینه‌ی ایجاد یک ASP.NET Core Web application را انتخاب کنید.
 - در صفحه‌ی بعدی آن هم گزینه‌ی «empty template» را انتخاب نمائید.


تنظیمات یک برنامه‌ی ASP.NET Core خالی برای اجرای یک برنامه‌ی Angular CLI

برای اجرای یک برنامه‌ی مبتنی بر Angular CLI، نیاز است بر روی فایل csproj برنامه‌ی ASP.NET Core کلیک راست کرده و گزینه‌ی Edit آن‌را انتخاب کنید.
سپس محتوای این فایل را به نحو ذیل تکمیل نمائید:

الف) درخواست عدم کامپایل فایل‌های TypeScript
  <PropertyGroup>
    <TargetFramework>netcoreapp1.1</TargetFramework>
    <TypeScriptCompileBlocked>true</TypeScriptCompileBlocked>
  </PropertyGroup>
چون نحوه‌ی کامپایل پروژه‌های Angular CLI صرفا مبتنی بر کامپایل مستقیم فایل‌های TypeScript آن نیست و در اینجا از یک گردش کاری توکار مبتنی بر webpack، به صورت خودکار استفاده می‌کند، کامپایل فایل‌های TypeScript توسط ویژوال استودیو، مفید نبوده و صرفا سبب دریافت گزارش‌های خطای بیشماری به همراه کند کردن پروسه‌ی Build آن خواهد شد. بنابراین با افزودن تنظیم TypeScriptCompileBlocked به true، از VS 2017 خواهیم خواست تا در این زمینه دخالت نکند.

ب) مشخص کردن پوشه‌هایی که باید الحاق و یا حذف شوند
  <ItemGroup>
    <Folder Include="Controllers\" />
    <Folder Include="wwwroot\" />
  </ItemGroup>
 
  <ItemGroup>
    <Compile Remove="node_modules\**" />
    <Content Remove="node_modules\**" />
    <EmbeddedResource Remove="node_modules\**" />
    <None Remove="node_modules\**" />
  </ItemGroup>
 
  <ItemGroup>
    <Compile Remove="src\**" />
    <Content Remove="src\**" />
    <EmbeddedResource Remove="src\**" />    
  </ItemGroup>
در اینجا پوشه‌های کنترلرها و wwwroot به پروژه الحاق شده‌اند. پوشه‌ی wwwroot جایی است که فایل‌هایی خروجی را Angular CLI ارائه خواهد کرد.
سپس دو پوشه‌ی node_modules و src واقع در ریشه‌ی پروژه را نیز به طور کامل از سیستم ساخت و توزیع VS 2017 حذف کرده‌ایم. پوشه‌ی node_modules وابستگی‌های Angular را به همراه دارد و src همان پوشه‌ی برنامه‌ی Angular ما خواهد بود.

ج) افزودن وابستگی‌های سمت سرور مورد نیاز
  <ItemGroup>
    <PackageReference Include="Microsoft.AspNetCore" Version="1.1.1" />
    <PackageReference Include="Microsoft.AspNetCore.Mvc" Version="1.1.2" />
    <PackageReference Include="Microsoft.AspNetCore.StaticFiles" Version="1.1.1" />
  </ItemGroup>
 
  <ItemGroup>
    <DotNetCliToolReference Include="Microsoft.DotNet.Watcher.Tools" Version="1.0.0" />
  </ItemGroup>
 
  <ItemGroup>
    <!-- extends watching group to include *.js files -->
    <Watch Include="**\*.js" Exclude="node_modules\**\*;**\*.js.map;obj\**\*;bin\**\*" />
  </ItemGroup>
برای اجرای یک برنامه‌ی تک صفحه‌ای وب Angular، صرفا به وابستگی MVC و StaticFiles آن نیاز است.
در اینجا Watcher.Tools هم به همراه تنظیمات آن اضافه شده‌اند که در ادامه‌ی بحث به آن اشاره خواهد شد.


افزودن یک کنترلر Web API جدید

با توجه به اینکه دیگر در اینجا قرار نیست با فایل‌های cshtml و razor کار کنیم، کنترلرهای ما نیز از نوع Web API خواهند بود. البته در ASP.NET Core، کنترلرهای معمولی آن، توانایی ارائه‌ی Web API و همچنین فایل‌های Razor را دارند و از این لحاظ تفاوتی بین این دو نیست و یکپارچگی کاملی صورت گرفته‌است.
using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;
 
namespace ASPNETCoreIntegrationWithAngularCLI.Controllers
{
  [Route("api/[controller]")]
  public class ValuesController : Controller
  {
    // GET: api/values
    [HttpGet]
    [ResponseCache(NoStore = true, Location = ResponseCacheLocation.None)]
    public IEnumerable<string> Get()
    {
      return new string[] { "Hello", "DNT" };
    }
  }
}
در اینجا کدهای یک کنترلر نمونه را جهت بازگشت یک خروجی JSON ساده مشاهده می‌کنید که در ادامه، در برنامه‌ی Angular CLI تهیه شده از آن استفاده خواهیم کرد.


تنظیمات فایل آغازین یک برنامه‌ی ASP.NET Core جهت ارائه‌ی برنامه‌های Angular

در ادامه به فایل Startup.cs برنامه‌ی خالی جاری، مراجعه کرده و آن‌را به نحو ذیل تغییر دهید:
using System;
using System.IO;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
 
namespace ASPNETCoreIntegrationWithAngularCLI
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc();
        }
 
        public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
        {
            loggerFactory.AddConsole();
 
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
 
            app.Use(async (context, next) => {
                await next();
                if (context.Response.StatusCode == 404 &&
                    !Path.HasExtension(context.Request.Path.Value) &&
                    !context.Request.Path.Value.StartsWith("/api/", StringComparison.OrdinalIgnoreCase))
                {
                    context.Request.Path = "/index.html";
                    await next();
                }
            });
 
            app.UseMvcWithDefaultRoute();
            app.UseDefaultFiles();
            app.UseStaticFiles();
        }
    }
}
در اینجا برای ارائه‌ی کنترلر Web API، نیاز به ثبت سرویس‌های MVC است. همچنین ارائه‌ی فایل‌های پیش فرض و فایل‌های استاتیک (همان پوشه‌ی wwwroot برنامه) نیز فعال شده‌اند.
در قسمت app.Use آن، تنظیمات URL Rewriting مورد نیاز جهت کار با مسیریابی برنامه‌های Angular را مشاهده می‌کنید. برای نمونه اگر کاربری در ابتدای کار آدرس /products را درخواست کند، این درخواست به سمت سرور ارسال می‌شود و چون چنین صفحه‌ای در سمت سرور وجود ندارد، خطای 404 بازگشت داده می‌شود و کار به پردازش برنامه‌ی Angular نخواهد رسید. اینجا است که تنظیم میان‌افزار فوق، کار مدیریت خروجی‌های 404 را بر عهده گرفته و کاربر را به فایل index.html برنامه‌ی تک صفحه‌ای وب، هدایت می‌کند. به علاوه در اینجا اگر درخواست وارد شده، دارای پسوند باشد (یک فایل باشد) و یا با api/ شروع شود (اشاره کننده‌ی به کنترلرهای Web API برنامه)، از این پردازش و هدایت به صفحه‌ی index.html معاف خواهد شد.


ایجاد ساختار اولیه‌ی برنامه‌ی Angular CLI در داخل پروژه‌ی جاری

اکنون از طریق خط فرمان به پوشه‌ی ریشه‌ی برنامه‌ی ASP.NET Core‌، جائیکه فایل Startup.cs قرار دارد، وارد شده و دستور ذیل را اجرا کنید:
 >ng new ClientApp --routing --skip-install --skip-git --skip-commit
به این ترتیب پوشه‌ی جدید ClientApp، در داخل پوشه‌ی برنامه اضافه خواهد شد که در آن تنظیمات اولیه‌ی مسیریابی Angular نیز انجام شده‌است؛ از دریافت وابستگی‌های npm آن صرفنظر شده و همچنین کار تنظیمات git آن نیز صورت نگرفته‌است (تا از تنظیمات git پروژه‌ی اصلی استفاده شود).
پس از تولید ساختار برنامه‌ی Angular CLI، به پوشه‌ی آن وارد شده و تمام فایل‌های آن را Cut کنید. سپس به پوشه‌ی ریشه‌ی برنامه‌ی ASP.NET Core جاری، وارد شده و این فایل‌ها را در آنجا paste نمائید. به این ترتیب به حداکثر سازگاری ساختار پروژه‌های Angular CLI و VS 2017 خواهیم رسید. زیرا اکثر فایل‌های تنظیمات آن‌را می‌شناسد و قابلیت پردازش آن‌ها را دارد.
پس از این cut/paste، پوشه‌ی خالی ClientApp را نیز حذف کنید.


تنظیم محل خروجی نهایی Angular CLI به پوشه‌ی wwwroot

برای اینکه سیستم Build پروژه‌ی Angular CLI جاری، خروجی خود را در پوشه‌ی wwwroot قرار دهد، تنها کافی است فایل .angular-cli.json را گشوده و outDir آن‌را به wwwroot تنظیم کنیم:
"apps": [
    {
      "root": "src",
      "outDir": "wwwroot",
به این ترتیب پس از هر بار build آن، فایل‌های index.html و تمام فایل‌های js نهایی، در پوشه‌ی wwwroot که در فایل Startup.cs‌، کار عمومی کردن آن انجام شد، تولید می‌شوند.


فراخوانی کنترلر Web API برنامه در برنامه‌ی Angular CLI

در ادامه صرفا جهت آزمایش برنامه، فایل src\app\app.component.ts را گشوده و به نحو ذیل تکمیل کنید:
import { Component, OnInit } from '@angular/core';
import { Http } from '@angular/http'; 
 
@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
  constructor(private _httpService: Http) { }
 
  apiValues: string[] = [];
 
  ngOnInit() {
    this._httpService.get('/api/values').subscribe(values => {
      this.apiValues = values.json() as string[];
    });
  }
}
در اینجا خروجی JSON کنترلر Web API برنامه دریافت شده و به آرایه‌ی apiValues انتساب داده می‌شود.

سپس این آرایه را در فایل قالب این کامپوننت (src\app\app.component.html) استفاده خواهیم کرد:
<h1>Application says:</h1>
<ul *ngFor="let value of apiValues">
  <li>{{value}}</li>
</ul>
<router-outlet></router-outlet>
در اینجا یک حلقه ایجاد شده و عناصر آرایه‌ی apiValues به صورت یک لیست نمایش داده می‌شوند.


نصب وابستگی‌های برنامه‌ی Angular CLI

در ابتدای ایجاد پوشه‌ی ClientApp، از پرچم skip-install استفاده شد، تا صرفا ساختار پروژه، جهت cut/paste آن با سرعت هر چه تمام‌تر، ایجاد شود. اکنون برای نصب وابستگی‌های آن یا می‌توان در solution explorer به گره dependencies مراجعه کرده و npm را انتخاب کرد. در ادامه با کلیک راست بر روی آن، گزینه‌ی restore packages ظاهر می‌شود. و یا می‌توان به روش متداول این نوع پروژه‌ها، از طریق خط فرمان به پوشه‌ی ریشه‌ی پروژه وارد شد و دستور npm install را صادر کرد. بهتر است اینکار را از طریق خط فرمان انجام دهید تا مطمئن شوید که از آخرین نگارش‌های این ابزار که بر روی سیستم نصب شده‌است، استفاده می‌کنید.


روش اول اجرای برنامه‌های مبتنی بر ASP.NET Core و Angular CLI

تا اینجا اگر برنامه را از طریق VS 2017 اجرا کنید، خروجی را مشاهده نخواهید کرد. چون هنوز فایل index.html آن تولید نشده‌است.
بنابراین روش اول اجرای این نوع برنامه‌ها، شامل مراحل ذیل است:
الف) ساخت پروژه‌ی Angular CLI در حالت watch
 > ng build --watch
برای اجرای آن از طریق خط فرمان، به پوشه‌ی ریشه‌ی پروژه وارد شده و دستور فوق را وارد کنید. به این ترتیب کار build پروژه انجام شده و همچنین فایل‌های نهایی آن در پوشه‌ی wwwroot قرار می‌گیرند. به علاوه چون از پرچم watch استفاده شده‌است، با هر تغییری در پوشه‌ی src برنامه، این فایل‌ها به صورت خودکار به روز رسانی می‌شوند. بنابراین این پنجره‌ی خط فرمان را باید باز نگه داشت تا watcher آن بتواند کارش را به صورت مداوم انجام دهد.

ب) اجرای برنامه از طریق ویژوال استودیو
اکنون که کار ایجاد محتوای پوشه‌ی wwwroot برنامه انجام شده‌است، می‌توان برنامه را از طریق VS 2017 به روش متداولی اجرا کرد:


یک نکته: می‌توان قسمت الف را تبدیل به یک Post Build Event هم کرد. برای این منظور باید فایل csproj را به نحو ذیل تکمیل کرد:
<Target Name="AngularBuild" AfterTargets="Build">
    <Exec Command="ng build" />
</Target>
به این ترتیب با هربار Build پروژه در VS 2017، کار تولید مجدد محتوای پوشه‌ی wwwroot نیز انجام خواهد شد.
تنها مشکل روش Post Build Event، کند بودن آن است. زمانیکه از روش ng build --watch به صورت مستقل استفاده می‌شود، برای بار اول اجرا، اندکی زمان خواهد برد؛ اما اعمال تغییرات بعدی به آن بسیار سریع هستند. چون صرفا نیاز دارد این تغییرات اندک و تدریجی را کامپایل کند و نه کامپایل کل پروژه را از ابتدا.


روش دوم اجرای برنامه‌های مبتنی بر ASP.NET Core و Angular CLI

روش دومی که در اینجا بررسی خواهد شد، مستقل است از قسمت «ب» روش اول که توضیح داده شد. برنامه‌های NET Core. نیز به همراه CLI خاص خودشان هستند و نیازی نیست تا حتما از VS 2017 برای اجرای آن‌ها استفاده کرد. به همین جهت وابستگی Microsoft.DotNet.Watcher.Tools را نیز در ابتدای کار به وابستگی‌های برنامه اضافه کردیم.

الف) در ادامه، VS 2017 را به طور کامل ببندید؛ چون نیازی به آن نیست. سپس دستور ذیل را در خط فرمان، در ریشه‌ی پروژه‌، صادر کنید:
> dotnet watch run
این دستور پروژه‌ی ASP.NET Core را کامپایل کرده و بر روی پورت 5000 ارائه می‌دهد:
>dotnet watch run
[90mwatch : [39mStarted
Hosting environment: Production
Now listening on: http://localhost:5000
Application started. Press Ctrl+C to shut down.
به علاوه پارامتر watch آن سبب خواهد شد تا هر تغییری که در کدهای پروژه‌ی ASP.NET Core صورت گیرند، بلافاصله کامپایل شده و قابل استفاده شوند.

ب) در اینجا چون برنامه بر روی پورت 5000 ارائه شده‌است، بهتر است دستور ng serve -o را صادر کرد تا بتوان به نحو ساده‌تری از سرور وب ASP.NET Core استفاده نمود. در این حالت برنامه‌ی Angular CLI بر روی پورت 4200 ارائه شده و بلافاصله در مرورگر نیز نمایش داده می‌شود.
مشکل! سرور وب ما بر روی پورت 5000 است و سرور آزمایشی Angular CLI بر روی پورت 4200. اکنون برنامه‌ی Angular ما، یک چنین درخواست‌هایی را به سمت سرور، جهت دریافت اطلاعات ارسال می‌کند: localhost:4200/api
برای رفع این مشکل می‌توان فایلی را به نام proxy.config.json با محتویات ذیل ایجاد کرد:
{
  "/api": {
    "target": "http://localhost:5000",
    "secure": false
  }
}
سپس دستور ng server صادر شده، اندکی متفاوت خواهد شد:
 >ng serve --proxy-config proxy.config.json -o
در اینجا به ng serve اعلام کرده‌ایم که تمامی درخواست‌های ارسالی به مسیر api/  (و یا همان localhost:4200/api جاری) را به سرور وب ASP.NET Core، بر روی پورت 5000 ارسال کن و نتیجه را در همینجا بازگشت بده. به این ترتیب مشکل عدم دسترسی به سرور وب، جهت تامین اطلاعات برطرف خواهد شد.
مزیت این روش، به روز رسانی خودکار مرورگر با انجام هر تغییری در کدهای قسمت Angular برنامه است.

نکته 1: بدیهی است می‌توان قسمت «ب» روش دوم را با قسمت «الف» روش اول نیز جایگزین کرد (ساخت پروژه‌ی Angular CLI در حالت watch). اینبار گشودن مرورگر بر روی پورت 5000 (و یا آدرس http://localhost:5000) را باید به صورت دستی انجام دهید. همچنین هربار تغییر در کدهای Angular، سبب refresh خودکار مرورگر نیز نمی‌شود که آن‌را نیز باید خودتان به صورت دستی انجام دهید (کلیک بر روی دکمه‌ی refresh پس از هر بار پایان کار ng build).
نکته 2: می‌توان قسمت «الف» روش دوم را حذف کرد (حذف dotnet run در حالت watch). یعنی می‌خواهیم هنوز هم ویژوال استودیو کار آغاز IIS Express را انجام دهد. به علاوه می‌خواهیم برنامه را توسط ng serve مشاهده کنیم (با همان پارامترهای قسمت «ب» روش دوم). در این حالت تنها موردی را که باید تغییر دهید، پورتی است که برای IIS Express تنظیم شده‌است. عدد این پورت را می‌توان در فایل Properties\launchSettings.json مشاهده کرد و سپس به تنظیمات فایل proxy.config.json اعمال نمود.


کدهای کامل این مطلب را از اینجا می‌توانید دریافت کنید: ASPNETCoreIntegrationWithAngularCLI.zip
به همراه این کدها تعدادی فایل bat نیز وجود دارند که جهت ساده سازی عملیات یاد شده‌ی در این مطلب، می‌توان از آن‌ها استفاده کرد:
 - فایل restore.bat کار بازیابی و نصب وابستگی‌های پروژه‌ی دات نتی و همچنین Angular CLI را انجام می‌دهد.
 - دو فایل ng-build-dev.bat و ng-build-prod.bat بیانگر قسمت «الف» روش اول هستند. فایل dev مخصوص حالت توسعه است و فایل prod مخصوص ارائه‌ی نهایی.
 - دو فایل dotnet_run.bat و ng-serve-proxy.bat خلاصه کننده‌ی قسمت‌های «الف» و «ب» روش دوم هستند.
مطالب
آشنایی با Catel MVVM Frameowork
در این مقاله به بررسی اولیه فریمورک Catel و برخی ویژگی‌های آن خواهیم پرداخت.
همانطور که می‌دانید فریمورک‌های متعددی برای MVVM به وجود آمده اند، مانند MVVM Light یا Caliburn و Chinch و ... که هر کدام از آن‌ها دارای ویژگی هایی می‌باشند اما Catel تنها یک فریمورک برای MVVM نیست بلکه دارای قسمت‌های دیگری مانند کنترل‌های اختصاصی و سرویس‌های متعدد و پرکاربرد و Extension‌‌های مفید و ... نیز می‌باشد که کار توسعه یک برنامه MVVM را فوق العاده لذتبخش می‌کند.
برای شروع کار با این فریمورک ابتدا بایستی قالب پروژه را از این آدرس دریافت نمایید. بعد از دریافت و نصب آن یک زیرگروه جدید به نام Catel به قسمت افزودن پروژه جدید اضافه خواهد شد که شامل قالب پروژه برای WPF و Silverlight و Windows Phone و Windows Store می باشد. در این قسمت گزینه WPF Application with Catel را انتخاب نمایید و پروژه را ایجاد کنید. بعد از ایجاد پروژه نوبت به نصب بسته های nuget مورد نیاز Catel می رسد. تنها بسته مورد نیاز Catel.Extensions.Controls می باشد که به صورت خودکار بسته های Catel.MVVM و Catel.Core را نیز نصب خواهد کرد. البته بسته‌های دیگری مانند Catel.Extensions.Prism, Catel.Extensions.FluentValidation و Catel.Extensions.Data و Catel.Fody و ... نیز برای این فریمورک وجود دارد که در این مطلب به آن‌ها نیازی نداریم.
اکنون ساختار اصلی پروژه ما ایجاد شده است. در این ساختار پوشه‌های Models ،Views و ViewModels به صورت پیش فرض وجود دارند. Catel برای برقراری ارتباط بین View و ViewModel از IViewLocator، IViewModelLocator و یکسری قواعد نام گذاری پیروی میکند تا نیاز به رجیستر کردن تک تک ویوها و ویومدل‌ها به صورت دستی نباشد که البته این قواعد قابل تغییر و شخصی سازی هستند. قرارداد پیش فرض برای پروژه‌های کوچک ممکن است مناسب باشد ولی در پروژه‌های بزرگ نیاز به سفارشی سازی دارد که در قسمت‌های بعد به آن خواهیم پرداخت. 
View و ViewModel:

برای ایجاد یک ViewModel جدید، باید از منوی Add New Item قسمت Catel گزینه (ViewModel (Catel را انتخاب نمایید. با توجه به code snippet های تهیه شده برای این فریمورک، کار تهیه ViewModel‌ها فوق العاده سریع انجام می‌شود. به عنوان مثال برای اضافه کردن یک Command در ویومدل، از vmcommand و یا vmcommandwithcanexecute و برای ایجاد پروپرتی هم از vmprop و vmpropchanged میتوان استفاده نمود. همانطور که ملاحظه می‌کنید نام این snippet‌‌ها کاملا واضح می‌باشد و نیاز به توضیح اضافی ندارند.
همینطور برای ایجاد یک View گزینه (DataWindow (WPF with Catel را انتخاب نمایید. ViewModel‌‌‌ها در Catel از کلاس پایه ViewModelBase و View‌‌ها نیز از کلاس DataWindow مشتق می‌شوند.
DataWindow یک Window پیشرفته با قابلیت هایی مانند افزودن خودکار دکمه‌های Ok / Cancel یا Ok / Cancel / Apply یا Close می‌باشد که می‌تواند باعث تسریع روند ایجاد Window‌های تکراری شود. اما اگر به هیچ کدام از این دکمه‌های ذکر شده نیاز نداشتید DataWindowMode.Custom را انتخاب می‌کنید. نشان دادن Validation در بالای پنجره به صورت popup نیز یکی دیگر از قابلیت‌های این Window پیشرفته است. البته DataWindow دارای overload‌‌های مختلفی است که می‌توانید به کمک آن ویژگی‌های ذکر شده را فعال یا غیر فعال کنید.
حال برای درک بهتر command‌ها و نحوه تعریف و بکارگیری آن‌ها یک command جدید در MainWindowViewModel با استفاده از vmcommand ایجاد کنید. مانند قطعه کد زیر:
public class MainWindowViewModel : ViewModelBase
    {
        public MainWindowViewModel()
            : base()
        {
            ShowPleaseWait = new Command(OnShowPleaseWaitExecute);
        }

        public override string Title { get { return "View model title"; } }

        public Command ShowPleaseWait { get; private set; }
        private void OnShowPleaseWaitExecute()
        {
            var pleaseWaitService = GetService<IPleaseWaitService>();
            pleaseWaitService.Show(() =>
            {
                Thread.Sleep(3000);
            });
        }
    }
در داخل بدنه این command از PleaseWaitService استفاده کردیم که در ادامه توضیح داده خواهد شد. در MainView نیز یک button اضافه کنید و پروپرتی Command آن را به صورت زیر تنظیم کنید:
<Button Margin="6"
                Command="{Binding ShowPleaseWait}"
                Content="Show PleaseWait!" />
اکنون با فشردن button کد داخل بدنه command اجرا خواهد شد.

سرویس ها:

کتابخانه Catel.MVVM دارای سرویس‌های مختلف و پرکاربردی می‌باشد که در ادامه به بررسی آن‌ها خواهیم پرداخت:
PleaseWaitService: از این سرویس برای نشان دادن یک loading به کاربر در حین انجام یک کار سنگین استفاده می‌شود و نحوه استفاده از آن به صورت زیر است:
var pleaseWaitService = GetService<IPleaseWaitService>();
pleaseWaitService.Show(() =>
{
        Thread.Sleep(3000);
});
UIVisualizerService: از این سرویس برای باز کردن پنجره‌های برنامه استفاده می‌شود. هر View در برنامه دارای یک ViewModel می باشد. برای باز کردن View ابتدا یک نمونه از ViewModel مربوطه را ایجاد میکنیم و با دادن viewmodel به متد Show یا ShowDialog پنجره مورد نظر را باز میکنیم.
var uiService = GetService<IUIVisualizerService>();
var viewModel = new AnotherWindowViewModel();
uiService.Show(viewModel);
OpenFileService: برای نشان دادن OpenFileDialog جهت باز کردن یک فایل در برنامه.
var openFileService = GetService<IOpenFileService>();
openFileService.Filter = "ZIP files (*.zip)|*.zip";
openFileService.IsMultiSelect = false;
openFileService.Title = "Open file";
if (openFileService.DetermineFile())
{
       // ?
}
SaveFileService: برای نشان دادن SaveFileDialog جهت ذخیره سازی.
var saveFileService = GetService<ISaveFileService>();
saveFileService.Filter = "ZIP files (*.zip)|*.zip";
saveFileService.FileName = "test";
saveFileService.Title = "Save file";
if (saveFileService.DetermineFile())
{
       // ?
}
ProcessService: برای اجرا کردن یک process. به عنوان مثال برای باز کردن ماشین حساب ویندوز به صورت زیر عمل می‌کنیم:
var processService = GetSetvice<IProcessService>();
processService.StartProcess(@"C:\Windows\System32\calc.exe");
SplashScreenService: برای نشان دادن SplashScreen در ابتدای برنامه هایی که سرعت بالا آمدن پایینی دارند.
var splashScreenService = GetService<ISplashScreenService>();
splashScreenService.Enqueue(new ActionTask("Creating the shell", OnCreateShell));
splashScreenService.Enqueue(new ActionTask("Initializing modules", OnInitializeModules));
splashScreenService.Enqueue(new ActionTask("Starting application", OnStartApplication));
MessageService: برای نشان دادن MessageBox به کاربر.
var messageService = GetService<IMessageService>();
if (messageService.Show("Are you sure?", "?", MessageButton.YesNo, MessageImage.Warning) == MessageResult.Yes)
{
       // ?
}
همانطور که ملاحظه کردید اکثر کارهای مورد نیاز یک پروژه با کمک سرویس‌های ارائه شده در این فریمورک به آسانی انجام می‌شود.
دریافت مثال و پروژه کامل این قسمت:
اشتراک‌ها
ساخت یک محیط توسعه زیبا در اندروید استودیو
 این مطلب برنامه نویسان اندروید را از تنظیمات متعدد و سفارشی سازی محیط کاری  دور خواهدکرد و در واقع با انجام تنظیمات ذیل به محیط کاری که در عکس مشاهده میکنند خواهند رسید .
  • ترکیب رنگی زیبا و طراحی متریال محیط در عکس زیر قابل مشاهده می‌باشد 


۱. تنظیمات GUI Theme :
از منوی File بر روی Settings کلیک می‌کنیم و بر روی Plugin کلیک کرده در قسمت جستجو عبارت مقابل ( Material Theme UI ) را  جستجو کرده و بر روی Install jetBrains Plugin کلیک می‌کنیم که Plugin مورد نظر ما نصب خواهد شد که بعد نصب اندروید استودیو را Restart می‌کنیم . (تنظیمات طبق عکس زیر انجام گردد) 
دسترسی به مخزن گیت هاب ‍   Material Theme UI for jetBrains  
⌘⇧a → "Plugins" → ↩ → ⌃⌥b → <search> → "Material Theme UI" → [Install plugin] → ⌃⌥c → ⎋ → <restart>

۲. تنظیمات Editor Scheme :
از منوی File بر روی Settings کلیک کرده و از قسمت Editor بر روی Color Scheme کلیک کرده و از قسمت Scheme که chroMATERIAL را انتخاب کرده و بر روی Apply/OK  کلیک می‌کنیم.
⌘⇧a → "Color Scheme" → [3. ChroMATERIAL]
و از قسمت Color Scheme Font که Use color scheme font instead را انتخاب و فونت را Lucida Snas Typewriter انتخاب و Apply/ok می‌کنیم.

۳. تنظیمات HOLO Logcat :
به مسیر زیر مراجعه کرده Preferences → Editor → Colors & Fonts → Android Logcat و تنظیمات Logcat را طبق سلیقه خود مشخص می‌کنیم و در نهایت Apply/ok می‌کنیم .
⌘⇧a → "Android Logcat" → [Save as...] → "ChroMATERIAL + HOLO"¹ → ↩ → <set foreground colors as in the table ↑> → ⎋
Type   : Color
verbose: #BBB debug: #33B5E5 info: #9C0 assert: #A6C error: #F44 warning: #FB3 

ساخت یک محیط توسعه زیبا در اندروید استودیو
نظرات مطالب
ASP.NET MVC #11
سلام،
چند عدد سوال داشتم:

1- از آنجایی که بنده در مورد استفاده کمتر از منابع سرور و ... خیلی حساسم و در پی یافتن بهینه‌ترین روش کدینگ هستم ، سوالی برام پیش اومده :
 آیا تعریف یک پراپرتی با دسترسی private  از نوع EntityFrameWorkContext در سطح کلاس کنترلر (یا سطح کلاس سرویس یا کلا در سطح یک کلاس) و استفاده از آن در متدهای کلاس و استفاده نکردن از using در داخل متدها از نظر حرفه ای درست می‌باشد ؟
(این روش رو در چند جا مشاهده کردم و شک کردم که نکنه روش بنده که همیشه using می‌زنم بهینه نیست....)
{بهترین روش چیه ؟}

2- اگر از استفاده غیرضروری از منابع سرور صرف نظر کنیم ؛ اگر ما ViewModel استفاده نکنیم و درون اکشن‌های ویرایش مثلا اینجوری کد بزنیم :

public ActionResult Edit(Member member)
{
var updatedItem = db.Members.FirstOrDefault(c => c.id == 1);

updatedItem.Name = member.Name;
updatedItem.Family = member.Family;

db.saveChanges();

return View();

}
آیا به دلیل استفاده نکردن از پراپرتی‌های غیر ضروری ، مشکل امنیتی برطرف میشه ؟
(بدین صورت اگر کاربر شیطونی کنه و مثلا فیلدای IsAdmin رو دستی بسازه و .... ازش استفاده نمیشه و مشکلی پیش نمیاد)
(نهایتا Model.IsValid هم می‌تونیم در اینجا استفاده کنیم)

مسلما موقع ثبت هم مقدار پروپرتی‌های حساس رو خودمون دستی پر می‌کنیم و اصلا کاری به ورودی دریافتی اکشن نخواهیم داشت.
public ActionResult Create(Member member)
{

If (Model.IsValid)
{

   db.Members.AddObject(new Member{ Name = member.Name , Family = member.Family , IsAdmin = False});
   db.saveChanges();

// ...

}

return View();

}

3- سفارشی سازی پیام‌های خطای اعتبار سنجی فرم رو هم من که تست کردم ، همش انگلیسی پیام میده!
و متن فارسی منو نادیده می‌گیره ...
یک توضیح بیشتر اگر مرحمت کنین ، ممنون میشم.

با تشکر