مطالب
ExtJs! رویا یا کابوس؟
چندی پیش یکی از دوستان درباره فریم ورک ExtJs سوالاتی را پرسیده بود که تصمیم گرفتم جواب‌های مورد نظر را به صورت عمومی در قالب یک پست منتشر کنم.
  •  ExtJs چیست؟
  •  چه زمانی کاربرد دارد؟
  •  تفاوت آن با سایر فریم ورک‌های جاوااسکریپ در چیست؟
شاید خیلی از شما با MODX آشنایی داشته باشید یا حتی با این CMS کار کرده باشید. اگر این طور است پس حتما با پنجره‌های زیبا و کامپوننت‌های قوی و اعتبارسنجی‌های سفارشی و تعاملاتی Ajax ای آن آشنایی دارید و شاید این سوال به ذهنتان خطور کرده باشد که در طراحی این CMS که بر پایه زبان PHP است دقیقا از چه چیز استفاده شده است؟
پاسخ یک کلمه است: ExtJs. بله درست است در طراحی این CMS تنها از یک فریم ورک جاوااسکریپتی به نام ExtJs استفاده شده است. فریم ورکی که به عقیده بعضی‌ها یک رویا برای توسعه دهندگان وب است و به عقیده سایرین شاید یک کابوس. در این پست قصد دارم به عنوان کسی که با این فریم ورک آشنایی دارم این موضوع را بررسی و مزایا و معایب این فریم ورک را عنوان کنم.
ExtJs یک فریم ورک جاوااسکریپ است بر مبنای Sencha و طراحی شده برای توسعه پروژه‌های وب در مقیاس بزرگ و به صورت cross-platform . مجوز استفاده از این فریم ورک به صورت GPLv3 است.(یعنی مجاز به استفاده رایگان از فایل‌های این فریم ورک هستید به شرطی که قصد استفاده تجاری از پروژه تهیه شده را نداشته باشید! در غیر این صورت باید زحمت خرید نسخه تجاری این فریم ورک را متحمل شوید).
نسخه ای که درباره آن بحث می‌کنیم نسخه چهار این فریم ورک (ExtJs 4) که بر مبنای ExtJs 3 تولید شده است. تفاوت عمده آن با نسخه قبلی در تکمیل ابزار و کامپوننت هاست و از طرفی نسخه چهار این فریم ورک بر مبنای مدل MVC توسعه داده شده است. یعنی همانند Angular و BackBoneJs می‌توانید مفاهیم کنترلر و مدل را به راحتی پیاده سازی کنید.

رویایی به نام ExtJs

اگر بخواهیم این فریم ورک را یک رویا برای توسعه دهندگان وب بنامیم می‌توان عناوین زیر را به عنوان مزایا برشمرد:
  • در درجه اول قابلیتی که این فریم ورک را متفاوت از سایر فریم ورک‌های جاوااسکریپتی می‌کند این است که این فریم ورک انبوهی از کامپوننت‌ها و ویجیت‌های آماده را به همراه خود دارد (با کارایی و انعطاف پذیری قابل قبول) و به نوعی شما را بی نیاز از هرگونه مجموعه کامپوننت‌های دیگر خواهد کرد. 
  • این فریم ورک به خوبی از مباحت OOP پشتیبانی می‌کند و به این صورت است که یک سری مفاهیم و مدل‌های پایه در این فریم ورک تعبیه شده و به راحتی شما می‌توانید مدل‌های مورد نظر خود را بر اساس این مفاهیم و مدل‌های پایه توسعه دهید.
  • تمام مفاهیم و ابزار لازم جهت درخواست‌های Ajax ای و اعتبار سنجی سفارشی و دستکاری عناصر DOM و... به خوبی در این فریم ورک وجود دارد.
  • به دلیل وجود کامپوننت‌های یک دست و آماده به راحتی می‌توانید امکان تغییر theme را در پروزه‌های خود بدون کوچکترین زحمت قرار دهید. 
  • کنترل GridPanel،TreeView ، کنترل‌های ورود اطلاعات، کنترل Tab با قابلیت درخواست‌های لود صفحات به صورت Ajax و Async با کمترین زحمت در کد نویسی و هم چنین چارت‌های بسیار گسترده و متنوع از دیگر مزایای این فریم ورک می‌تواند باشد.
  • ارائه مکانیزمی مناسب برای کار با عملیات داده ای Json. به عنوان نمونه:
Ext.data.JsonP.request({
  url: '@url',
  params: {
  apiKey: '1234'
  },
  callbackKey: 'myCallbackFn',
  success: function(){
 },
  failure: function(){
},
scope: this
});
  • این فریم ورک ابزارهای جالب و کارآمدی برای توسعه به صورت SPA را داراست.
  • کنترل‌های داده ای این فریم ورک در هنگام کار با حجم داده بسیار زیاد، فراتر از انتظار عمل می‌کنند(برای مثال کنترل GridPanel و DataView)
  • اگر قصد تولید و توسعه بک پروژه بزرگ درون سازمانی را دارید و سرعت تولید نیز برای شما مهم است ExtJs در این زمینه کمک شایانی به شما خواهد کرد.
  • و...

حال با همه این تفاسیر آیا این فریم ورک یک رویا برای هر توسعه دهنده وب خواهد بود؟ 

به طور قطع نه. با توجه به اصل واقع بینی! همواره به خاطر داشته باشید که اگر این فریم ورک یک ابزار بی نقص و همه منظوره بود الآن مطمئنا صدها کتاب و مستندات درباره آن نوشته شده بود و شاید شهرتی بس فراتر از این داشت. 

کابوسی به نام ExtJs

  • اگر قصد ایجاد یک وب سایت کوچک و جمع و جور را دارید به طوری که مباحث مربوط به SEO نیز برای شما اهمیت دارد تجربه نشان داده است که انتخاب ExtJs می‌تواند یکی از بزرگ‌ترین اشتباهات در طول عمر کاری شما شود.
  • ExtJs هیچ گونه کمکی برای تولید و توسعه اپلیکیشن‌های موبایل یا پروژه‌های وب گرافیکی نمی‌تواند به شما کند.
  • اگر سرعت یکی از فاکتور خیلی مهم برای شماست بهتر است به این فریم ورک علاقه نشان ندهید.(کتابخانه آن چیزی در حدود 500KB است! البته با فشرده سازی به 150KB خواهد رسید که باز هم قابل قبول نیست)
  • مجوز استفاده برای پروژه‌های تجاری به صورت رایگان نیست.(^)
  • به دلیل وجود ابزار‌های متنوع و زیاد؛ زمان یادگیری برای آشنایی و کار کردن با ابزارها، نسبتا طولانی خواهد شد.
  • کد نویسی برای استفاده از ابزار‌های آن در مقایسه با Jquery و Angular بیشتر خواهد بود(البته این به نوعی مزیت هم است، به دلیل اینکه خوانایی کد‌ها بسیار بالا می‌رود)
  • در طراحی کامپوننت‌ها آن از تگ div در حد غیر قابل قبول استفاده شده است به طوری که Debug صفحات حتی با Firebug هم در بعضی مواقع سخت می‌شود.
  • و...

Ext.Net چیست؟

Ext.Net یک پیاده سازی خاص از فریم ورک ExtJs است که برای توسعه پروژه‌های Asp.Net Web Forms و Asp.Net MVC طراحی شده است. تفاوت اصلی بین این دو محصول در نوع کدنویسی برای استفاده در پروژه‌های Asp.Net است. برای مثال در هنگام کار با Ext.Net و پروژه‌های MVC از آنجا که این محصول سازگاری کامل با موتور Razor دارد به راحتی می‌توانید به صورت سینتکس Razor صفحات خود را طراحی کنید. 

مثال:

ExtJs

Ext.create('Ext.panel.Panel', {
title: 'Fit Layout',
width: 500,
height: 200,
items: {
title: 'Inner Panel',
html: 'Panel content',
bodyPadding: 10,
border: true
},
renderTo: Ext.getBody()
});
اجرای کد بالا با استفاده از ExtJs به صورت زیر خواهد بود:

Ext.Net
@(X.Panel()
        .ID("ExpandablePanel")
        .Title("Panel")
        .Width(500)
        .Height(300)
        .Collapsible(true)
        .Loader(X.ComponentLoader()
            .Url(Url.Action("RenderChild"))
            .Mode(LoadMode.Frame)
            .DisableCaching(true)
            .Params(new { containerId = "ExpandablePanel" })
            .LoadMask(lm => lm.ShowMask = true)
        )
        .Listeners(l => {
            l.Expand.Handler = "this.reload();";
            l.Collapse.Handler = "this.clearContent();";
        })
    )  
خروجی مورد نظر برای Ext.Net:

جمع بندی:
با توجه مواردی که ذکر شد می‌توان به یک نکته مهم رسید و آن هم این است که هنگام انتخاب ExtJs یا Ext.Net (البته این شامل اکثر ابزارهای توسعه دیگر نیز خواهد شد) حتما شرایط موجود و حاکم برای توسعه محصول را مد نظر داشته باشید که این شرایط شامل محیط اجرای محصول، مدت زمان لازم برای توسعه، سطح دانش نیروی‌های توسعه دهنده و ... نیز می‌باشد.
نظرات مطالب
ASP.NET MVC #4
درود؛ دو سوال داشتم.
1)  هنگامی که من می‌خواستم یک View رو ایجاد کنم، فایل ایجاد شده برای من Index.ascx بود نه Index.cshtml. علت چی می‌تونه باشه؟
2) چرا وقتی در صفحه Index شروع به کد نویسی کردم، VS برای من ادامه دستورات رو نشون نمی‌داد؟ یعنی وقتی Ctrl+Space می‌زدم. من دستورات رو خودم نوشتم، یعنی:
 @{
ViewBag.....
مشکلی وجود دارد یا جز ویژگی‌های MVC محسوب میشود؟
بازخوردهای پروژه‌ها
مشکل در اعتبار سنجی
سلام. زمانی که قصد ایجاد پست یا نوشته جدید داریم اگه هیچ فیلدی رو پر نکنیم و دکمه ارسال رو بزنیم اعتبارسنجی بدرستی کار نمیکنه و در چند جای دیگر هم این مورد مشاهده شد و بدون نشان دادن پیامی صفحه رفرش میشه. برای چند جای دیگر هم به همین شکل بود.
با تشکر
مطالب
مونیتور کردن وضعیت یک سایت در دات نت

فرض کنید می‌خواهیم وضعیت یک سایت را از لحاظ قابلیت دسترسی مونیتور کنیم، آیا Up است، Down است و امثال آن. یک سری از وب سرورها ping را بسته‌اند (ICMP Replies). بنابراین الزاما با استفاده از این روش ساده نمی‌توان به مقصود رسید.
خوشبختانه انجام این‌کار با استفاده از فضای نام استاندارد System.Net و کلاس HttpWebRequest ، بدون نیاز به هیچگونه کلاس یا کامپوننت خارجی، به سادگی قابل انجام است. کلاس زیر به همین منظور تهیه شده است:

using System;
using System.Net;

public class CSiteMonitor
{
public struct UrlHeaderInfo
{
public DateTime _lastModified;
public string _statusCode;
public string _errorMessage;
}

/// <summary>
/// آیا آدرس اینترنتی وارد شده معتبر است؟
/// </summary>
/// <param name="url">آدرس مورد نظر جهت بررسی</param>
/// <returns></returns>
public static bool IsValidURL(string url)
{
try
{
Uri uri = new Uri(url);
return (uri.Scheme == Uri.UriSchemeHttp) || (uri.Scheme == Uri.UriSchemeHttps);
}
catch { return false; }
}
/// <summary>
/// آدرس اینترنتی جهت بررسی
/// </summary>
/// <param name="url"></param>
/// <returns></returns>
/// <exception cref="ArgumentException">آدرس اینترنتی وارد شده معتبر نیست</exception>
public static UrlHeaderInfo GetSiteHeaderInfo(string url)
{
if (!IsValidURL(url))
throw new ArgumentException("آدرس اینترنتی وارد شده معتبر نیست", "url");

UrlHeaderInfo hhi = new UrlHeaderInfo { _lastModified = DateTime.Now, _statusCode = "NOK!", _errorMessage = string.Empty };

HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
//request.Proxy
request.Method = "HEAD";
request.AllowAutoRedirect = true;
request.UserAgent = "Mozilla/5.0 (Windows; U; Windows NT 5.0; ; rv:1.8.0.7) Gecko/20060917 Firefox/1.9.0.1";
request.Timeout = 1000 * 300;
request.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate;

try
{
using (HttpWebResponse response = (HttpWebResponse) request.GetResponse())
{
hhi._statusCode = response.StatusCode.ToString();
hhi._lastModified = response.LastModified;
}
}
catch (Exception ex)
{
hhi._errorMessage = ex.Message;
}

return hhi;
}
}

توضیحات:
- در متد GetSiteHeaderInfo نیاز بود تا از یک تابع بیش از یک خروجی داشته باشیم. راه‌های زیاد برای انجام این‌کار هست.برای مثال:
الف)ارائه خروجی‌ها به صورت یک آرایه. زیاد جالب نیست، چون اگر شخصی دقیقا مستندات متد شما را مطالعه نکند نمی‌داند که ترتیب خروجی‌ها چگونه است و هر کدام چه معنایی دارند.
ب)ارائه خروجی‌ها با استفاده از آرگومان‌هایی از نوع out یا ref . در دنیای شیء گرایی این نوع روش‌ها را باید منسوخ شده در نظر گرفت و صرف سازگاری با زبان‌هایی مانند C که این روش در آن‌ها رواج دارد (استفاده از آرگومان‌هایی از نوع اشاره‌گر) باید به آن نگاه کرد و نه بیشتر.
ج)خروجی‌ها را به صورت یک کلاس یا struct درنظر گرفت تا استفاده کننده دقیقا بداند که فیلد خروجی چه معنایی دارد و هم چنین دقیقا چه تعداد خروجی مد نظر است.

- حتما باید از try/finally جهت اطمینان حاصل نمودن از بسته شدن response استفاده شود، در غیر اینصورت پس از دو خطای متوالی حاصل شده عملا دیگر نمی‌توان از شیء response استفاده کرد. البته همانطور که پیش تر نیز ذکر شد، عبارت using توسط کامپایلر به try/finally بست داده می‌شود، بنابراین جهت خوانایی بیشتر کد بهتر است از این روش استفاده شود.
- جهت بلاک نشدن درخواست بهتر است از یک UserAgent کمک گرفته شود.
- جهت بررسی اعتبار یک آدرس اینترنتی یا می‌توان از Regular expressions استفاده کرد یا از شیء Uri که روش آن‌را ملاحظه می‌کنید.
- اگر در شبکه داخلی خود از پروکسی استفاده می‌شود، می‌توان قسمت request.Proxy را با شیء پروکسی تنظیم شده مطابق مشخصات پروکسی سرور خود، بکار برد.
- در این مثال بیشتر هدف پیاده سازی کلاس دریافت اطلاعات هدر سایت بود و از ارائه کدهای مربوط به تایمر یا یک ترد جهت بررسی متوالی وضعیت سایت صرفنظر شد.

مثالی در مورد نحوه‌ی استفاده از کلاس فوق:

CSiteMonitor.UrlHeaderInfo info = CSiteMonitor.GetSiteHeaderInfo("http://www.google.com");
MessageBox.Show(info._statusCode);

نظرات مطالب
مدیریت سفارشی سطوح دسترسی کاربران در MVC
- این کوکی رو اگر خواستید مشاهده کنید از افزونه Cookies manager استفاده کنید. چنین نام و محتوای رمزنگاری شده‌ای داره:

البته این نام پیش‌فرض است. اگر نیاز به تعیین نام دیگری بود به این صورت می‌شود عمل کرد:
<roleManager 
    enabled="true" 
    cacheRolesInCookie="true" 
    defaultProvider="..."
    cookieName=".ASPXROLES" 
    cookiePath="/" 
    cookieTimeout="30" 
    cookieRequireSSL="false" 
    cookieSlidingExpiration="true"
    createPersistentCookie="false" 
    cookieProtection="All">
    <providers>
      <!-- .... -->
    </providers>
</roleManager>
- این کوکی فقط پس از اولین فراخوانی متدهای IsInRole یا GetRoles تولید می‌شود و نه پیش از آن.
- اگر از دات نت 4 و نیم استفاده می‌کنید، برای حالت کش نشدن این نقش‌ها اخیرا یک patch ارائه شده : (^). مورد چهارم آن.
Assume that you set the value of the cachedRolesInCookie property to true in your web application. 
Your application serializes the RolePrincipal object into the cookie, and then sends it in response. 
In this situation, the role cookie value is empty in the application's following request.
نظرات مطالب
متدی برای بررسی صحت کد ملی وارد شده
سلام
می‌تونین چک کنین فکر نکنم معتبر باشه
کد جاوا اسکریپت تشخیص صحت کد ملی، منبعش این سایته
function checkCodeMeli(code)
{
  
  var L=code.length;
  
  if(L<8 || parseInt(code,10)==0) return false;
  code=('0000'+code).substr(L+4-10);
  if(parseInt(code.substr(3,6),10)==0) return false;
  var c=parseInt(code.substr(9,1),10);
  var s=0;
  for(var i=0;i<9;i++)
    s+=parseInt(code.substr(i,1),10)*(10-i);
  s=s%11;
  return (s<2 && c==s) || (s>=2 && c==(11-s));
return true;
}


مطالب
Angular Material 6x - قسمت هشتم - جستجوی کاربران توسط AutoComplete
در مطلب «کنترل نرخ ورود اطلاعات در برنامه‌های Angular» جزئیات پیاده سازی جستجوی همزمان با تایپ کاربر، بررسی شدند. در اینجا می‌خواهیم از اطلاعات آن مطلب جهت پیاده سازی یک  AutoComplete جستجوی نام کاربران که اطلاعات آن از سرور تامین می‌شوند، استفاده کنیم:




استفاده از کامپوننت AutoComplete کتابخانه‌ی Angular Material

کتابخانه‌ی Angular Material به همراه یک کامپوننت Auto Complete نیز هست. در اینجا قصد داریم آن‌را در یک صفحه‌ی دیالوگ جدید نمایش دهیم و با انتخاب کاربری از لیست توصیه‌های آن و کلیک بر روی دکمه‌ی نمایش آن کاربر، جزئیات کاربر یافت شده را نمایش دهیم.


به همین جهت ابتدا کامپوننت جدید search-auto-complete را به صورت زیر به مجموعه‌ی کامپوننت‌های تعریف شده اضافه می‌کنیم:
 ng g c contact-manager/components/search-auto-complete --no-spec
همچنین چون قصد داریم آن‌را درون یک popup نمایش دهیم، نیاز است به ماژول contact-manager\contact-manager.module.ts مراجعه کرده و آن‌را به لیست entryComponents نیز اضافه کنیم:
import { SearchAutoCompleteComponent } from "./components/search-auto-complete/search-auto-complete.component";

@NgModule({
  entryComponents: [
    SearchAutoCompleteComponent
  ]
})
export class ContactManagerModule { }

در ادامه برای نمایش این کامپوننت به صورت popup، دکمه‌ی جدید جستجو را به toolbar اضافه می‌کنیم:


برای این منظور به فایل toolbar\toolbar.component.html مراجعه کرده و دکمه‌ی جستجو را پیش از دکمه‌ی نمایش منو، قرار می‌دهیم:
  <span fxFlex="1 1 auto"></span>
  <button mat-button (click)="openSearchDialog()">
    <mat-icon>search</mat-icon>
  </button>
  <button mat-button [matMenuTriggerFor]="menu">
    <mat-icon>more_vert</mat-icon>
  </button>
با این کدها برای مدیریت متد openSearchDialog در فایل toolbar\toolbar.component.ts
@Component()
export class ToolbarComponent {
  constructor(
    private dialog: MatDialog,
    private router: Router) { }

  openSearchDialog() {
    const dialogRef = this.dialog.open(SearchAutoCompleteComponent, { width: "650px" });
    dialogRef.afterClosed().subscribe((result: User) => {
      console.log("The SearchAutoComplete dialog was closed", result);
      if (result) {
        this.router.navigate(["/contactmanager", result.id]);
      }
    });
  }
}
در اینجا توسط سرویس MatDialog، کامپوننت SearchAutoCompleteComponent به صورت پویا بارگذاری شده و به صورت یک popup نمایش داده می‌شود. سپس مشترک رخ‌داد بسته شدن آن شده و بر اساس اطلاعات کاربری که توسط آن بازگشت داده می‌شود، سبب هدایت صفحه‌ی جاری به صفحه‌ی جزئیات این کاربر یافت شده، خواهیم شد.


کنترلر جستجوی سمت سرور و سرویس سمت کلاینت استفاده کننده‌ی از آن

در اینجا کنترلر و اکشن متدی را جهت جستجوی قسمتی از نام کاربران را مشاهده می‌کنید:
namespace MaterialAspNetCoreBackend.WebApp.Controllers
{
    [Route("api/[controller]")]
    public class TypeaheadController : Controller
    {
        private readonly IUsersService _usersService;

        public TypeaheadController(IUsersService usersService)
        {
            _usersService = usersService ?? throw new ArgumentNullException(nameof(usersService));
        }

        [HttpGet("[action]")]
        public async Task<IActionResult> SearchUsers(string term)
        {
            return Ok(await _usersService.SearchUsersAsync(term));
        }
    }
}
کدهای کامل متد SearchUsersAsync در مخزن کد این سری موجود هستند.

از این کنترلر به نحو ذیل در برنامه‌ی Angular برای ارسال اطلاعات و انجام جستجو استفاده می‌شود:
import { HttpClient, HttpErrorResponse } from "@angular/common/http";
import { Injectable } from "@angular/core";
import { Observable, throwError } from "rxjs";
import { catchError, map } from "rxjs/operators";

import { User } from "../models/user";

@Injectable({
  providedIn: "root"
})
export class UserService {

  constructor(private http: HttpClient) { }

  searchUsers(term: string): Observable<User[]> {
    return this.http
      .get<User[]>(`/api/Typeahead/SearchUsers?term=${encodeURIComponent(term)}`)
      .pipe(
        map(response => response || []),
        catchError((error: HttpErrorResponse) => throwError(error))
      );
  }
}
در اینجا از اپراتور pipe مخصوص RxJS 6x استفاده شده‌است.


تکمیل کامپوننت جستجوی کاربران توسط یک AutoComplete

پس از این مقدمات که شامل تکمیل سرویس‌های سمت سرور و کلاینت دریافت اطلاعات کاربران جستجو شده و نمایش صفحه‌ی جستجو به صورت یک popup است، اکنون می‌خواهیم محتوای این popup را تکمیل کنیم. البته در اینجا فرض بر این است که مطلب «کنترل نرخ ورود اطلاعات در برنامه‌های Angular» را پیشتر مطالعه کرده‌اید و با جزئیات آن آشنایی دارید.

تکمیل قالب search-auto-complete.component.html
<h2 mat-dialog-title>Search</h2>
<mat-dialog-content>
  <div fxLayout="column">
    <mat-form-field class="example-full-width">
      <input matInput placeholder="Choose a user" [matAutocomplete]="auto1" 
                (input)="onSearchChange($event.target.value)">
    </mat-form-field>
    <mat-autocomplete #auto1="matAutocomplete" [displayWith]="displayFn" 
                                 (optionSelected)="onOptionSelected($event)">
      <mat-option *ngIf="isLoading" class="is-loading">
        <mat-spinner diameter="50"></mat-spinner>
      </mat-option>
      <ng-container *ngIf="!isLoading">
        <mat-option *ngFor="let user of filteredUsers" [value]="user">
          <span>{{ user.name }}</span>
          <small> | ID: {{user.id}}</small>
        </mat-option>
      </ng-container>
    </mat-autocomplete>
  </div>
</mat-dialog-content>
<mat-dialog-actions>
  <button mat-button color="primary" (click)="showUser()">
    <mat-icon>search</mat-icon> Show User
  </button>
  <button mat-button color="primary" [mat-dialog-close]="true">
    <mat-icon>cancel</mat-icon> Close
  </button>
</mat-dialog-actions>
در این مثال چون کامپوننت search-auto-complete به صورت یک popup ظاهر خواهد شد، ساختار عنوان، محتوا و دکمه‌های دیالوگ در آن پیاده سازی شده‌اند.
سپس نحوه‌ی اتصال یک Input box معمولی را به کامپوننت mat-autocomplete مشاهده می‌کنید که شامل این موارد است:
- جعبه متنی که قرار است به یک mat-autocomplete متصل شود، توسط دایرکتیو matAutocomplete به template reference variable تعریف شده‌ی در آن autocomplete اشاره می‌کند. برای مثال در اینجا این متغیر auto1 است.
- برای انتقال دکمه‌های فشرده شده‌ی در input box به کامپوننت، از رخداد input استفاده شده‌است. این روش با هر دو نوع حالت مدیریت فرم‌های Angular سازگاری دارد و کدهای آن یکی است.

در کامپوننت mat-autocomplete این تنظیمات صورت گرفته‌اند:
- در لیست ظاهر شده‌ی توسط یک autocomplete، هر نوع ظاهری را می‌توان طراحی کرد. برای مثال در اینجا نام و id کاربر نمایش داده می‌شوند. اما برای تعیین اینکه پس از انتخاب یک آیتم از لیست، چه گزینه‌ای در input box ظاهر شود، از خاصیت displayWith که در اینجا به متد displayFn کامپوننت متصل شده‌است، کمک گرفته خواهد شد.
- از رخ‌داد optionSelected برای دریافت آیتم انتخاب شده، در کدهای کامپوننت استفاده می‌شود.
- در آخر کار نمایش لیستی از کاربران توسط mat-optionها انجام می‌شود. در اینجا برای اینکه بتوان تاخیر دریافت اطلاعات از سرور را توسط یک mat-spinner نمایش داد، از خاصیت isLoading تعریف شده‌ی در کامپوننت استفاده خواهد شد.


تکمیل کامپوننت search-auto-complete.component.ts

کدهای کامل این کامپوننت را در ادامه مشاهده می‌کنید:
import { Component, OnDestroy, OnInit } from "@angular/core";
import { MatAutocompleteSelectedEvent, MatDialogRef } from "@angular/material";
import { Subject, Subscription } from "rxjs";
import { debounceTime, distinctUntilChanged, finalize, switchMap, tap } from "rxjs/operators";

import { User } from "../../models/user";
import { UserService } from "../../services/user.service";

@Component({
  selector: "app-search-auto-complete",
  templateUrl: "./search-auto-complete.component.html",
  styleUrls: ["./search-auto-complete.component.css"]
})
export class SearchAutoCompleteComponent implements OnInit, OnDestroy {

  private modelChanged: Subject<string> = new Subject<string>();
  private dueTime = 300;
  private modelChangeSubscription: Subscription;
  private selectedUser: User = null;

  filteredUsers: User[] = [];
  isLoading = false;

  constructor(
    private userService: UserService,
    private dialogRef: MatDialogRef<SearchAutoCompleteComponent>) { }

  ngOnInit() {
    this.modelChangeSubscription = this.modelChanged
      .pipe(
        debounceTime(this.dueTime),
        distinctUntilChanged(),
        tap(() => this.isLoading = true),
        switchMap(inputValue =>
          this.userService.searchUsers(inputValue).pipe(
            finalize(() => this.isLoading = false)
          )
        )
      )
      .subscribe(users => {
        this.filteredUsers = users;
      });
  }

  ngOnDestroy() {
    if (this.modelChangeSubscription) {
      this.modelChangeSubscription.unsubscribe();
    }
  }

  onSearchChange(value: string) {
    this.modelChanged.next(value);
  }

  displayFn(user: User) {
    if (user) {
      return user.name;
    }
  }

  onOptionSelected(event: MatAutocompleteSelectedEvent) {
    console.log("Selected user", event.option.value);
    this.selectedUser = event.option.value as User;
  }

  showUser() {
    if (this.selectedUser) {
      this.dialogRef.close(this.selectedUser);
    }
  }
}
- در ابتدای کار کامپوننت، یک modelChanged از نوع Subject اضافه شده‌است. در این حالت با فراخوانی متد next آن در onSearchChange که به رخ‌داد input جعبه‌ی متنی دریافت اطلاعات متصل است، کار انتقال این تغییرات به اشتراک ایجاد شده‌ی به آن در ngOnInit انجام می‌شود. در اینجا بر اساس نکات مطلب «کنترل نرخ ورود اطلاعات در برنامه‌های Angular»، عبارات وارد شده، به سمت سرور ارسال و در نهایت نتیجه‌ی آن به خاصیت عمومی filteredUsers که به حلقه‌ی نمایش اطلاعات mat-autocomplete متصل است، انتساب داده می‌شود. در ابتدای اتصال به سرور، خاصیت isLoading به true و در پایان عملیات به false تنظیم خواهد شد تا mat-spinner را نمایش داده و یا مخفی کند.
- توسط متد displayFn، عبارتی که در نهایت پس از انتخاب از لیست نمایش داده شده در input box قرار می‌گیرد، مشخص خواهد شد.
- در متد onOptionSelected، می‌توان به شیء انتخاب شده‌ی توسط کاربر از لیست mat-autocomplete دسترسی داشت.
- این شیء انتخاب شده را در متد showUser و توسط سرویس MatDialogRef به کامپوننت toolbar که در حال گوش فرادادن به رخ‌داد بسته شدن کامپوننت جاری است، ارسال می‌کنیم. به این صورت است که کامپوننت toolbar می‌تواند کار هدایت به جزئیات این کاربر را انجام دهد.



کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید.
برای اجرای آن:
الف) ابتدا به پوشه‌ی src\MaterialAngularClient وارد شده و فایل‌های restore.bat و ng-build-dev.bat را اجرا کنید.
ب) سپس به پوشه‌ی src\MaterialAspNetCoreBackend\MaterialAspNetCoreBackend.WebApp وارد شده و فایل‌های restore.bat و dotnet_run.bat را اجرا کنید.
اکنون برنامه در آدرس https://localhost:5001 قابل دسترسی است.
نظرات مطالب
راه اندازی StimulSoft Report در ASP.NET MVC
با توجه به عرضه نسخه net core.، این گزارشگر هم تغییراتی در نسخه‌های اخیر خود داده و هم اینکه پیشرفت‌های زیادی نیز کرده است.
بعد از نصب نسخه مورد نظر (برای نسخه 3.1 نیاز به نصب استیمول 2020 است) طبق این لینک مشکل Trial بودن (برای ما)  را حل کنید.
در مورد این لینک چند نکته قابل ذکر است:
1- از شما میخواهد که dll‌های جایگزین بعد از build، با dll‌ها اصلی جابجا شوند که انجام این مورد در Visual Studio به خاطر داشتن UI برای اینکار به راحتی انجام می‌شد ولی در Visual Studio Code و Rider، چنین موردی باید به صورت دستی صورت بگیرد؛ به همین علت در فایل csproj پروژه اصلی، کدهای زیر را اضافه کنید:
  <Target Name="PostBuild" AfterTargets="PostBuildEvent">
    <Exec Command="xcopy /y /d $(ProjectDir)Packages\*.* $(OutDir)" />
  </Target>
2- همچنین در نهایت باید فایل لایسنس نیز در پروژه قرار گرفته و در Startup به استیمول معرفی گرد. به همین جهت کلاس زیر را اضافه کنید:
 public static class StimulSoftLicense
    {
        public static void LoadLicense(IWebHostEnvironment environment)
        {
            var contentRoot = environment.ContentRootPath;
            var licenseFile = System.IO.Path.Combine(contentRoot,"Reports", "license.key");
            Stimulsoft.Base.StiLicense.LoadFromFile(licenseFile);
        }
    }
که در خط دوم مسیر فایل لایسنس معرفی شده است که برای پروژه شما باید اصلاح شود و در startup.cs به صورت زیر استفاده میشود:
  public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
           StimulSoftLicense.LoadLicense(env);
}
برای نمایش گزارش همه چیز کماکان مثل سابق است فقط یک سری جزئیات تغییر نموده است:
        public IActionResult RequestReport(int id)
        {
            return View();
        }

        public IActionResult LoadReportData(int id)
        {
            StiReport report = new StiReport();
            report.Load(StiNetCoreHelper.MapPath(this, "Reports/Requests/RequestInfo.mrt"));
            var landDetailsReport = GetOwnerReportData(id);
            report.RegBusinessObject("Land", landDetailsReport);

            return StiNetCoreViewer.GetReportResult(this, report);
        }
         public IActionResult ViewerEvent()
        {
            return StiNetCoreViewer.ViewerEventResult(this);
        }
همچنین در ویو RequestReport.cshtml کد به شکل زیر تغییر میکند:
<div style="direction: ltr">
    
@Html.StiNetCoreViewer(new StiNetCoreViewerOptions()
{
    Theme = StiViewerTheme.Office2007Silver,
    
    Appearance =
    {
        RightToLeft = true,
        //ShowTooltips = false,
        ShowTooltipsHelp = false,
        
    },
Localization = "~/Reports/fa.xml",
    Actions =
    {
        GetReport = "LoadReportData",
        ViewerEvent = "ViewerEvent",
        
    },
    Toolbar =
    {
      ShowAboutButton  = false,
        ShowOpenButton = false,
        ShowSaveButton = true,
        ShowFindButton = false,
        ShowEditorButton = false,
        ShowDesignButton = false,
        ShowBookmarksButton = false,
        ShowResourcesButton = false,
        ShowParametersButton = false,
        ShowPinToolbarButton = false

    },
    Exports =
    {
        DefaultSettings =
        {
            ExportToPdf =
            {
                CreatorString = "SANA"
            }
        },
        ShowExportDialog = false,
        ShowExportToDocument = false,
        ShowExportToExcel = false,
        ShowExportToExcel2007 = false,
        ShowExportToHtml = false,
        ShowExportToHtml5 = false,
        ShowExportToImageBmp = false,
        ShowExportToImageJpeg = false,
        ShowExportToImagePcx = false,
        ShowExportToImagePng = false,
        ShowExportToImageMetafile = false,
        ShowExportToImageTiff = false,
        ShowExportToOpenDocumentCalc = false,
        ShowExportToMht = false,
        ShowExportToOpenDocumentWriter = false,
        ShowExportToXps = false,
        ShowExportToSylk = false,
        ShowExportToRtf = false,
        ShowExportToExcelXml = false,
        ShowExportToText = false,
        ShowExportToWord2007 = false,
        ShowExportToImageGif = false,
        ShowExportToCsv = false,
        ShowExportToDbf = false,
        
        ShowExportToDif = false,
        ShowExportToImageSvg = false,
        ShowExportToImageSvgz = false,
        ShowExportToPowerPoint = false,
        ShowExportToXml = false,
        ShowExportToJson = false
    }
})
</div>
با توجه به نیاز کد بالا را تغییر دهید. تقریبا تا حدی خصوصیت‌ها تغییر کرده‌اند ولی نه خیلی.
یک نکته مهم در مورد div که این helper را محصور کرده وجود دارد و آن این است که حالت RTL  قالب شما روی Helper استیمول تاثیر میگذارد ( به خصوص روی خود گزارش) برای همین از تنظیمات داخل خود Helper برای اینکار استفاده میکنیم؛ ولی اگر قالب RTL باشد و استیمول هم روی RTL تنظیم شود در نوار ابزار، ترتیب ابزار‌ها معکوس میگردد. به همین دلیل حالت پیش فرض، یعنی LTR میباشد و با تظیم Helper به این مهم دست پیدا می‌کنیم.
کدهای زیر را نیز به فایل css صفحه اضافه کنید تا در گزارش ساز استیمول اعمال گردد:
.stiJsViewerClearAllStyles
{
    font-family: "IRANSans" !important;
}

//مخصوص دیالوگ تنظیمات خروجی در صورت فعال بودن
.stiJsViewerGroupPanelContainer{
    direction: rtl !important;
}
.stiJsViewerFormButtonDefault{
    direction: rtl !important;
}
.stiJsViewerFormButtonOver{
    direction: rtl !important;
}
.stiJsViewerFormButtonDefault>table>tbody>tr>td{
    text-align: right !important;
}
.stiJsViewerFormButtonOver>table>tbody>tr>td{
    text-align: right !important;
}
.stiJsViewerFormHeader{
    direction: rtl;
}
.stiJsViewerFormHeader>table>tbody>tr>td{
    text-align: right !important;
}
.stiJsViewerCheckBox{
    direction:rtl !important;
}
.stiJsViewerDropdownPanel{
    direction:rtl !important;
}
.stiJsViewerFormContainer td.stiJsViewerClearAllStyles{
    direction:rtl !important;
}
 .stiMvcViewerReportPanel table{
     direction:ltr !important;
 }
نظرات مطالب
ASP.NET MVC #20
مثالی که نوشتم برای استفاده در Html.EditorFor بود. خواص این model قبل از return View برای استفاده در یک گزارش باید مقدار دهی شوند و گرنه مقدار پیش فرض خود یا همان نال را خواهند داشت. همچنین بعد از این تغییر، جایی که new WebGrid دارید، پارامتر source آن دیگر Model نیست و اینبار Model.IEM1 است.
مطالب
معرفی افزونه‌های مفید VSCode جهت کار با Angular
VSCode یکی از بهترین ادیتورهایی است که از آن می‌توان برای توسعه‌ی برنامه‌های Angular استفاده کرد و در این بین افزونه‌های ویژه‌ای جهت کار با Angular برای آن تدارک دیده شده‌اند که در ادامه تعدادی از مهم‌ترین‌های آن‌ها را بررسی می‌کنیم.


Angular Essentials

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


Angular Language Service

نگارش‌های اخیر Angular به همراه یک سرویس زبان نیز می‌باشند که به ادیتورهای مختلف این امکان را می‌دهد تا توسط این ویژگی بتوانند قابلیت‌های ویرایشی بهتری را جهت برنامه‌های Angular ارائه کنند. برای مثال ویرایش مطلوب قالب‌های کامپوننت‌های Angular و استفاده‌ی از Syntax خاص آن، موردی است که توسط هیچکدام از HTML ادیتورهای موجود پشتیبانی نمی‌شود. اکنون به کمک سرویس زبان Angular و افزونه‌ی ویژه‌ی آن برای VSCode که توسط تیم اصلی Angular توسعه یافته‌است، امکان ویرایش غنی قالب‌های HTML ایی آن فراهم شده‌است. این افزونه یک چنین قابلیت‌هایی را فراهم می‌کند:
الف) AOT Diagnostic messages
اگر قالب HTML ایی مورد استفاده (چه به صورت inline و چه در یک فایل html مجزا) به خاصیتی تعریف نشده اشاره کند، بلافاصله خطای مرتبطی ظاهر خواهد شد:


ب) Completions lists یا همان Intellisense
ج) امکان Go to definition با کلیک راست بر روی خواص و متدهای ذکر شده‌ی در قالب.
د) Quick info که با نزدیک کردن اشاره‌گر ماوس به خاصیت یا متدی در صفحه، اطلاعات بیشتری را در مورد آن نمایش می‌دهد.


angular2-inline

علاوه بر افزونه‌ی سرویس زبان‌های Angular، این افزونه نیز قابلیت درک قالب‌های inline کامپوننت‌ها را داشته و به همراه syntax highlighting و همچنین Intellisense است.


Auto Import

حین کار با TypeScript، هر ماژولی که در صفحه ارجاعی داشته باشد، باید در ابتدای فایل جاری import شود. افزونه‌ی Auto Import با بررسی ماژول‌های موجود و فراهم آوردن Intellisense ایی بر اساس آن‌ها، این‌کار را ساده‌تر می‌کند:


بنابراین این افزونه صرفا مختص به Angular نیست و برای کارهای متداول TypeScript نیز بسیار مفید است.


TSLint

این افزونه ابزار TSLint را با VSCode یکپارچه می‌کند. بنابراین نیاز است پیش از نصب این افزونه، وابستگی‌های ذیل را نیز به صورت سراسری نصب کرد:
 > npm install -g tslint typescript
کار TSLint انجام static code analysis است؛ چیزی شبیه به افزونه‌هایی مانند ری‌شارپر در ویژوال استودیو که راهنماهایی را در مورد بهتر کردن کیفیت کدهای نوشته شده ارائه می‌دهد.
تعدادی از امکانات آن‌را پس از نصب، با فشردن دکمه‌ی F1 می‌توان مشاهده کرد:


برای مثال تولید فایل tslint.json، امکان سفارشی سازی موارد بررسی شونده‌ی توسط این افزونه را فراهم می‌کند و اگر برنامه‌ی خود را توسط Angular CLI ایجاد کرده‌اید، این فایل هم اکنون در ریشه‌ی پروژه قرار دارد.
در مورد TSLint در مطلب «Angular CLI - قسمت دوم - ایجاد یک برنامه‌ی جدید» بیشتر توضیح داده شده‌است و اینبار به کمک این افزونه، خطاهای یاد شده را دقیقا درون محیط ادیتور و به صورت خودکار و یکپارچه‌ای مشاهده خواهید کرد.



Angular v4 TypeScript Snippets

سیستم کار VSCode مبتنی بر ایجاد فایل‌های خالی است و مفهوم قالب‌های از پیش آماده‌ی فایل‌ها در آن وجود ندارد. اما با کمک Code Snippets می‌توان این خلاء را پر کرد. افزونه‌ی Angular v4 TypeScript Snippets دقیقا به همین منظور طراحی شده‌است و زمانیکه حروف -a یا -rx را در صفحه تایپ می‌کنید، منویی ظاهر خواهد شد که توسط آن می‌توان قالب ابتدایی شروع به کار با انواع و اقسام جزئیات پروژه‌های Angular را تهیه کرد.



Path Intellisense

این افزونه مسیر فایل‌های موجود را به صورت یک Intellisense ارائه می‌کند و به این صورت به سادگی می‌توان مسیرهای اسکریپت‌ها و یا شیوه‌نامه‌ها را در ادیتور انتخاب و وارد کرد.