‫۶ سال و ۱۰ ماه قبل، سه‌شنبه ۲۳ آبان ۱۳۹۶، ساعت ۱۴:۱۲
برای حالت‌های پیشرفته‌تر بهتر است columnsMap را با System.Linq.Dynamic.Core جایگزین کنید و همچنین نوع مقایسه را هم از کاربر دریافت کنید (برای مثال حالت‌های مساوی، مخالف، شروع شده با، تمام شده با، حاوی عبارت و غیره).
‫۶ سال و ۱۰ ماه قبل، دوشنبه ۲۲ آبان ۱۳۹۶، ساعت ۱۸:۰۲
پیاده سازی جستجوی بر روی این گرید، شامل موارد زیر است:
اضافه کردن دو خاصیت جدید به کلاس PagedQueryModel سمت کلاینت جهت مشخص سازی ستونی که قرار است بر روی آن جستجو انجام شود و همچنین مقدار آن:
export class PagedQueryModel {
  constructor(
    // ...
    public filterByColumn: string,
    public filterByValue: string,
  ) { }
}
سپس به ProductsListComponent دو متد زیر را اضافه می‌کنیم:
  doFilter() {
    this.queryModel.page = 1;
    this.getPagedProductsList();
  }

  resetFilter() {
    this.queryModel.page = 1;
    this.queryModel.filterByColumn = "";
    this.queryModel.filterByValue = "";
    this.getPagedProductsList();
  }
اولی کار جستجو را انجام می‌دهد و دومی بازگشت حالت گرید به وضعیت اول آن است. متد getPagedProductsList قابلیت واکشی خودکار اطلاعات دو خاصیت جدیدی را که اضافه کردیم دارد و نیازی به تنظیمات اضافه‌تری ندارد. یعنی filterByColumn و filterByValue را به صورت خودکار به سمت سرور ارسال می‌کند.

پس از آن، قالب این گرید (products-list.component.html) جهت افزودن جستجو، به صورت زیر تغییر می‌کند:
<div class="panel panel-default">
  <div class="panel-body">
    <div class="form-group">
      <input type="text" [(ngModel)]="queryModel.filterByValue" placeholder="Search For ..."
        class="form-control" />
    </div>
    <div class="form-group">
      <select class="form-control" name="filterColumn" [(ngModel)]="queryModel.filterByColumn">
        <option value="">Filter by ...</option>
        <option *ngFor="let column of columns" [value]="column.propertyName">
          {{ column.title }}
        </option>
      </select>
    </div>
    <button class="btn btn-primary" type="button" (click)="doFilter()">Search</button>
    <button class="btn btn-default" type="button" (click)="resetFilter()">Reset</button>
  </div>
</div>
که در آن queryModel.filterByColumn و queryModel.filterByValue از کاربر دریافت می‌شوند. همچنین دو متد doFilter و resetFilter را نیز فراخوانی می‌کند.
با این شکل:


تغییرات سمت سرور آن نیز به صورت ذیل است:
ابتدا IPagedQueryModel را با همان دو خاصیت جدید ستون فیلتر شونده و مقدار آن، تکمیل می‌کنیم:
    public interface IPagedQueryModel
    {
    // ....
        string FilterByColumn { get; set; }
        string FilterByValue { get; set; }
    }

    public class ProductQueryViewModel : IPagedQueryModel
    {
        // ... other properties ...

// ...
        public string FilterByColumn { get; set; }
        public string FilterByValue { get; set; }
    }
از این دو خاصیت جدید، جهت افزودن متد اعمال جستجو، همانند متد ApplyOrdering که پیشتر تعریف شد، استفاده می‌کنیم:
    public static class IQueryableExtensions
    {
        public static IQueryable<T> ApplyFiltering<T>(
          this IQueryable<T> query,
          IPagedQueryModel model,
          IDictionary<string, Expression<Func<T, object>>> columnsMap)
        {
            if (string.IsNullOrWhiteSpace(model.FilterByValue) || !columnsMap.ContainsKey(model.FilterByColumn))
            {
                return query;
            }

            var func = columnsMap[model.FilterByColumn].Compile();
            return query.Where(x => func(x).ToString() == model.FilterByValue);
        }
در اینجا همان columnsMap مورد استفاده در متد ApplyOrdering جهت نگاشت نام‌های رشته‌ای ستون‌ها به معادل Expression آن‌ها استفاده شده‌است.

در آخر، به کنترلر ProductController و اکشن متد GetPagedProducts آن مراجعه کرده و پیش از ApplyOrdering، متد جدید ApplyFiltering فوق را اضافه می‌کنیم:
var columnsMap = new Dictionary<string, Expression<Func<Product, object>>>()
            {
                ["productId"] = p => p.ProductId,
                ["productName"] = p => p.ProductName,
                ["isAvailable"] = p => p.IsAvailable,
                ["price"] = p => p.Price
            };
query = query.ApplyFiltering(queryModel, columnsMap);
query = query.ApplyOrdering(queryModel, columnsMap);

کدهای کامل این تغییرات را از اینجا می‌توانید دریافت کنید.
‫۶ سال و ۱۰ ماه قبل، یکشنبه ۲۱ آبان ۱۳۹۶، ساعت ۱۸:۰۹
یک نکته‌ی تکمیلی: تاثیر منفی VS 2017 به روز رسانی نشده بر روی افزونه‌ی #C مخصوص VSCode در ویندوز

اگر VS 2017 خود را به روز رسانی نکرده باشید، ممکن است با باز کردن یک پروژه‌ی NET Core. در VSCode یک چنین خطاهایی را مشاهده کنید:
 Predefined type 'System.Void' is not defined or imported #1855
علت اینجا است که افزونه‌ی #C مخصوص VSCode، در صورت نصب بودن VS 2017، سعی می‌کند از موتور MSBuild آن استفاده کند؛ بجای نمونه‌ای که به همراه خودش نصب می‌شود:
[info]: OmniSharp.MSBuild.Discovery.MSBuildLocator
        Located 2 MSBuild instance(s)
            1: Visual Studio Enterprise 2017 15.0.26228.4 - "e:\Program Files (x86)\Microsoft Visual Studio\2017\Enterprise\MSBuild\15.0\Bin"
            2: StandAlone 15.0 - "C:\Users\xyz\.vscode\extensions\ms-vscode.csharp-1.13.0\.omnisharp\msbuild\15.0\Bin"
[info]: OmniSharp.MSBuild.Discovery.MSBuildLocator
        Registered MSBuild instance: Visual Studio Enterprise 2017 15.0.26228.4 - "e:\Program Files (x86)\Microsoft Visual Studio\2017\Enterprise\MSBuild\15.0\Bin"
همانطور که در این لاگ نیز مشخص است، Registered MSBuild را از نمونه‌ی قدیمی نصب شده‌ی VS 2017 انتخاب کرده‌است (و نه از پوشه‌ی omnisharp\msbuild\15.0\Bin مخصوص خودش).
بنابراین اگر VS 2017 خود را به روز رسانی نکرده باشید، این موتور قدیمی MSBuild، سبب تداخل خواهد شد و خطاهایی مانند عدم یافت شدن نوع void ،int و امثال آن‌را مشاهده می‌کنید. در اینجا یا باید VS 2017 خود را به روز رسانی کنید و یا کلا آن‌را از سیستم حذف کنید. البته قرار است این تداخل در نگارش بعدی این افزونه برطرف شود.

روش مشاهده‌ی لاگ فوق نیز به صورت ذیل است:
VS Code ->  select View->Output -> select "OmniSharp Log"


این مشکل در نگارش 1.13.1 برطرف شده‌است.

‫۶ سال و ۱۰ ماه قبل، یکشنبه ۲۱ آبان ۱۳۹۶، ساعت ۱۴:۴۲
یک نکته‌ی تکمیلی: تولید خودکار بسته‌ی نیوگت پس از Build

در فایل‌‌های csproj بجای ذکر post build event زیر:
<Target Name="PostcompileScript" AfterTargets="Build">
    <Exec Command="dotnet pack --no-build --configuration $(Configuration)" />
</Target>
می‌توان خاصیت GeneratePackageOnBuild را true کرد:
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>netcoreapp1.1</TargetFramework>
    <Version>1.1.1</Version>
    <GeneratePackageOnBuild>True</GeneratePackageOnBuild>
  </PropertyGroup>
</Project>
و یا اگر ترجیح می‌دهید آن‌را توسط NET Core CLI. به صورت مجزا و در زمان مناسبی انجام دهید، دستور نهایی آن به صورت ذیل است:
dotnet pack -c release
‫۶ سال و ۱۰ ماه قبل، یکشنبه ۲۱ آبان ۱۳۹۶، ساعت ۱۴:۱۳
یک نکته‌ی تکمیلی: روش تولید خودکار Binding Redirects در پروژه‌های NET Core.

برای این منظور تنها کافی است تنظیمات ذیل را به فایل csproj اضافه کنیم:
<PropertyGroup>   
  <AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
  <GenerateBindingRedirectsOutputType>true</GenerateBindingRedirectsOutputType> 
</PropertyGroup>
‫۶ سال و ۱۰ ماه قبل، یکشنبه ۲۱ آبان ۱۳۹۶، ساعت ۱۳:۵۱
یک نکته‌ی تکمیلی: چگونه فایل README.txt را به بسته‌های نیوگت NET Core. اضافه کنیم؟

Visual Studio اگر فایل README.txt را در پوشه‌ی ریشه‌ی یک بسته‌ی نیوگت پیدا کند، پس از نصب بسته، به صورت خودکار آن‌را نمایش می‌دهد. بنابراین با استفاده از این فایل ویژه می‌توان نکات جدید مرتبط با بسته‌ی نیوگت، راهنما و یا مثال‌هایی را به استفاده کننده نمایش داد. اما در پروژه‌های NET Core.، چگونه می‌توان این فایل را در زمان فراخوانی دستور dotnet pack، به بسته‌ی نهایی تولید شده اضافه کرد؟
<Project>
  …
  <ItemGroup>
    <Content Include="README.txt">
      <Pack>true</Pack>
      <PackagePath>README.txt</PackagePath>
    </Content>
  </ItemGroup>
  …
</Project>
در اینجا تغییرات لازم فایل csproj را جهت الحاق فایل README.txt مشاهده می‌کنید.
Include به مسیر فایل الحاق شده اشاره می‌کند. این مسیر نسبت به ریشه‌ی پروژه‌ی جاری محاسبه می‌شود.
Pack الحاق و یا عدم الحاق فایل را تنظیم می‌کند.
PackagePath به مسیر نهایی این فایل در بسته‌ی نیوگت اشاره می‌کند و نسبت به ریشه‌ی آن درنظر گرفته خواهد شد.
‫۶ سال و ۱۰ ماه قبل، شنبه ۲۰ آبان ۱۳۹۶، ساعت ۱۳:۴۷
// فقط یک ترد امکان دسترسی به کد را داشته باشد
private static readonly SemaphoreSlim _semaphoreSlim = new SemaphoreSlim(1,1);

public override async Task RunAsync()
{
   // شروع تمام تردها معلق خواهند شد تا این سمافور به پایان برسد
   // پس از پایان کار ترد جاری، فقط یک ترد در حال انتظار، مجوز دسترسی به قطعه کد بعدی را خواهد یافت و به همین ترتیب برای سایر تردها
   await _semaphoreSlim.WaitAsync(); 
   try
   {
       await Task.Delay(20000); // Your code here
   }
   finally
   {
      _semaphoreSlim.Release();
   }
}
‫۶ سال و ۱۰ ماه قبل، جمعه ۱۹ آبان ۱۳۹۶، ساعت ۱۷:۱۳
یک نکته‌ی تکمیلی
فایل assets/config.json که در اینجا بارگذاری می‌شود، در پوشه‌ی src\assets\config.json یک برنامه‌ی مبتنی بر Angular CLI قرار می‌گیرد و همچنین باید در فایل angular-cli.json. به نحو ذیل ذکر شود:
{  
  "apps": [
    {
 //...
      "assets": [
        "assets",
        "favicon.ico",
        "config.json"
      ],
 //...
}
یک مثال کامل