نظرات مطالب
فعال سازی قسمت ارسال فایل و تصویر ویرایشگر آنلاین RedActor در ASP.NET MVC
درسته. علت این است که پوشه App_Data محافظت شده است و از طریق فراخوانی لینک آن به صورت مستقیم قابل دسترسی نیست.
اگر فایل‌ها رو در این پوشه ارسال می‌کنید نیاز هست مثلا یک کنترلر دیگر به نام file درست کنید و کار این کنترلر فایل، دریافت نام تصویر و ارسال آن به صورت return File به مرورگر کاربر باشد.
یا اینکه کلا از یک پوشه معمولی استفاده کنید.
+
ضمن اینکه کاراکتر ~ برای تگ معمولی img مفهومی ندارد و نباید از آن در پایان کار استفاده شود.
مطالب
NET Just-In-Time Optimization.
هدف از توابع خطی(Inline)
استفاده از توابع، مقداری بر زمان اجرای برنامه می‌افزاید؛ هرچند که این زمان بسیار کم و در حد میلی ثانیه است، اما باری را بر روی برنامه قرار می‌دهد و علت این تاخیر زمانی این است که در فراخوانی و اعلان توابع، کامپایلر یک کپی از تابع مورد نظر را در حافظه قرار می‌دهد و در فراخوانی تابع، به آدرس مذکور مراجعه می‌کند و در عین حال آدرس موقعیت توقف دستورات در تابع main را نیز ذخیره می‌کند تا پس از پایان تابع، به آدرس قبل برگردد و ادامه‌ی دستورات را اجرا کند. در نتیجه این آدرس دهی‌ها و نقل و انتقالات بین آنها بار زمانی را در برنامه ایجاد می‌کند که در صورت زیاد بودن توابع در برنامه و تعداد فراخوانی‌های لازم، زمان قابل توجهی خواهد شد.
یکی از تکنیک‌های بهینه که برای کاهش زمان اجرای برنامه توسط  کامپایلر‌ها استفاده  می‌شود استفاده از  توابع خطی (inline) است. این امکان در زبان C با عنوان توابع ماکرو(Macro function) و در ++C با عنوان توابع خطی (inline function) وجود دارد.
در واقع توابع خطی به  کامپایلر پیشنهاد می‌دهند، زمانی که سربار فراخوانی تابع بیشتر از سربار بدنه خود متد باشد، برای کاهش هزینه و زمان اجرای برنامه از تابع به صورت خطی استفاده کند و یک کپی از بنده‌ی تابع را در قسمتی که تابع ما فراخوانی شده است، قرار دهد که مورد آدرس دهی از میان خواهد رفت!

نمونه ای از پیاده سازی این تکنیک در زبان ++C :
inline type name(parameters) 
{
    ...
}

بررسی متدهای خطی در سی شارپ
به مثال زیر توجه کنید:
قسمت‌های getter و setter مربوط به پراپرتی‌ها سربار اضافی بر کلاس Vector می‌افزایند. این موضوع شاید آنچنان مسئله‌ی مهمی نباشد. ولی فرض کنید این پراپرتی‌ها به شکل زیر داخل حلقه‌ای طولانی قرار گیرند. اگر با استفاده از یک پروفایلر زمان اجرای برنامه را زیر نظر بگیرید، خواهید دید که بیش از 90 درصد آن صرف فراخوانی‌های متد‌های بخش‌های get , set پراپرتی‌ها است. برای این منظور باید مطمئن شویم که فراخوانی این متد‌ها، به صورت خطی صورت می‌گیرد!
 public class Vector
  {
    public double X { get; set; }
    public double Y { get; set; }
    public double Z { get; set; }

    // ...
  }
برای این منظور آزمایشی را انجام می‌دهیم. فرض کنید کلاسی را به شکل زیر داشته باشیم:
public class MyClass
  {
    public int A { get; set; }
    public int C;
  }
و برای استفاده از آن به شکل زیر عمل کنیم:
  static void Main()
{
    MyClass target = new MyClass();
    int a = target.A;
    Console.WriteLine("A = {0}", a);
    int c = target.C;
    Console.WriteLine("C = {0}", c);
  }
بعد از دیباگ برنامه و مشاهده‌ی کدهای ماشین مربوط به آن خواهیم دید که متد مربوط به getter پراپرتی A به صورت خطی فراخوانی نشده است:
            int a = target.A;
  0000003e  mov         ecx,edi
  00000040  cmp         dword ptr [ecx],ecx
  00000042  call        dword ptr ds:[05FA29A8h]
  00000048  mov         esi,eax
  0000004a  mov         dword ptr [esp+4],esi
             int c = target.C;
  00000098  mov         edi,dword ptr [edi+4]
    MyClass.get_A() looks like this:
  00000000  push        esi
  00000001  mov         esi,ecx
  00000003  cmp         dword ptr ds:[03B701DCh],0
  0000000a  je          00000011
  0000000c  call        76BA6BA7
  00000011  mov         eax,dword ptr [esi+0Ch]
  00000014  pop         esi
  00000015  ret

چه اتفاقی افتاده است؟
 کامپایلر سی شارپ در زمان کامپایل، کد‌های برنامه را به کد‌های IL تبدیل می‌کند و JITکامپایلر، این کد‌های IL را گرفته و  کد ساده‌ی ماشین را تولید می‌کند. لذا به دلیل اینکه JIT با معماری پردازنده آشنایی کافی دارد، مسئولیت تصمیم گیری اینکه کدام متد به صورت خطی فراخوانی شود برعهده‌ی آن است. در واقع این JIT است که تشخیص می‌دهد که آیا فراخونی متد به صورت خطی مناسب است یا نه و به صورت یک معاوضه کار بین خط لوله دستورالعمل‌ها و کش است.
اگر شما برنامه‌ی خود را با  (F5) و همگام با دیباگ اجرا کنید، تمام بهینه سازی‌های JIT که Inline Method هم یکی از آنهاست، از کار خواهند افتاد. برای مشاهده‌ی کد بهینه شده باید با بدون دیباگ (CTRL+F5) برنامه خود را اجرا کنید که در آن صورت مشاهده خواهید کرد، متد getter  مربوط به پراپرتی A به صورت خطی استفاده شده است.
  int a = target.A;  
00000024  mov         ebx,dword ptr [edi+0Ch]


JIT محدودیت هایی برای فراخونی به صورت خطی متد‌ها دارد :
  1.  متد هایی که حجم کد IL آنها بیشتر از 32 بایت است.
  2. متد‌های بازگشتی.
  3. متدهایی که با اتریبیوت MethodImpl علامتگذاری شدند و MethodImplOptions.NoInlining  اعمال شده بر آن
  4. متدهای virtual
  5. متدهایی که دارای کد مدیریت خطا هستند
  6. Methods that take a large value type as a parameter 
  7. Methods with complicated flowgraphs 
برای اینکه در سی شارپ به کامپایلر اعلام کنیم تا متد مورد نظر به صورت خطی مورد استفاده قرار گیرد، در دات نت 4.5 توسط اتریبیوت MethodImpl و  اعمال  MethodImplOptions.AggressiveInlining که یک نوع شمارشی است می‌توان این کار را انجام داد. مثال:
using System;
using System.Diagnostics;
using System.Runtime.CompilerServices;
class Program
{
    const int _max = 10000000;
    static void Main()
    {
         // ... Compile the methods.
        Method1();
        Method2();  
        int sum = 0;
        var s1 = Stopwatch.StartNew();
        for (int i = 0; i < _max; i++)
           {
              sum += Method1();
           }
         s1.Stop();
         var s2 = Stopwatch.StartNew();
         for (int i = 0; i < _max; i++)
             {
                sum += Method2();
             }
         s2.Stop();
         Console.WriteLine(((double)(s1.Elapsed.TotalMilliseconds * 1000000) /
        _max).ToString("0.00 ns"));
        Console.WriteLine(((double)(s2.Elapsed.TotalMilliseconds * 1000000) /
      _max).ToString("0.00 ns"));
        Console.Read();
      }
      static int Method1()
      {
         // ... No inlining suggestion.
         return "one".Length + "two".Length + "three".Length +
              "four".Length + "five".Length + "six".Length +
              "seven".Length + "eight".Length + "nine".Length +
               "ten".Length;
    }
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    static int Method2()
    {
        // ... Aggressive inlining.
        return "one".Length + "two".Length + "three".Length +
           "four".Length + "five".Length + "six".Length +
           "seven".Length + "eight".Length + "nine".Length +
            "ten".Length;
    }
}
Output
7.34 ns    No options
0.32 ns    MethodImplOptions.AggressiveInlining
در واقع با اعمال این اتریبیوت، محدودیت شماره یک مبنی بر محدودیت حجم کد IL مربوط به متد، در نظر گرفته نخواهد شد .

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


مدل برنامه، جهت تامین داده‌های خود ارجاع دهنده و درختی

فرض کنید قصد داریم لیستی از کامنت‌های تو در تو را مدل سازی کنیم که در آن هر کامنت، می‌تواند چندین کامنت تا بی‌نهایت سطح تو در تو را داشته باشد:
namespace BlazorTreeView.ViewModels;

public class Comment
{
    public IList<Comment> Comments = new List<Comment>();
    public string? Text { set; get; }
}
برای نمونه بر اساس این مدل، منبع داده‌ی فرضی زیر را تهیه می‌کنیم:
using BlazorTreeView.ViewModels;

namespace BlazorTreeView.Pages;

public partial class TreeView
{
    private IReadOnlyDictionary<string, object> ChildrenHtmlAttributes { get; } =
        new Dictionary<string, object>(StringComparer.Ordinal)
        {
            { "style", "list-style: none;" },
        };

    private IList<Comment> Comments { get; } =
        new List<Comment>
        {
            new()
            {
                Text = "پاسخ یک",
            },
            new()
            {
                Text = "پاسخ دو",
                Comments =
                    new List<Comment>
                    {
                        new()
                        {
                            Text = "پاسخ اول به پاسخ دو",
                            Comments =
                                new List<Comment>
                                {
                                    new()
                                    {
                                        Text = "پاسخی به پاسخ اول پاسخ دو",
                                    },
                                },
                        },
                        new()
                        {
                            Text = "پاسخ دوم به پاسخ دو",
                        },
                    },
            },
            new()
            {
                Text = "پاسخ سوم",
            },
        };
}
این قطعه کد partial class که مربوط به فایل TreeView.razor.cs برنامه‌است، در حقیقت کدهای پشت صحنه‌ی کامپوننت مثال TreeView.razor است که در ادامه آن‌را توسعه خواهیم داد. در نهایت قرار است بتوانیم آن‌را به صورت زیر رندر کنیم:



طراحی کامپوننت DntTreeView

برای اینکه بتوانیم به یک کامپوننت با قابلیت استفاده‌ی مجدد بررسیم، کدهای نمایش اطلاعات تو در تو و درختی را توسط کامپوننت سفارشی DntTreeView پیاده سازی خواهیم کرد. پیشنیازهای آن نیز به صورت زیر است:
- این کامپوننت باید جنریک باشد؛ یعنی باید به صورت زیر شروع شود:
/// <summary>
///   A custom DntTreeView
/// </summary>
public partial class DntTreeView<TRecord>
{
چون باید بتوان یک لیست جنریک <IEnumerable<TRecord را به آن، جهت رندر ارسال کرد و قرار نیست این کامپوننت، تنها به شیء سفارشی Comment مثال جاری ما وابسته باشد. بنابراین اولین خاصیت آن، شیء جنریک Items است که لیست کامنت‌ها/عناصر را دریافت می‌کند:
    /// <summary>
    ///     The treeview's self-referencing items
    /// </summary>
    [Parameter]
    public IEnumerable<TRecord>? Items { set; get; }
- هنگام رندر هر آیتم کامنت باید بتوان یک قالب سفارشی را از کاربر دریافت کرد. نمی‌خواهیم صرفا برای مثال Text شیء Comment فوق را به صورت متنی و ساده نمایش دهیم. می‌خواهیم در حین رندر، کل شیء TRecord جاری را به مصرف کننده ارسال و یک قالب سفارشی را از آن دریافت کنیم. یعنی باید یک RenderFragment جنریک را به صورت زیر نیز داشته باشیم تا مصرف کننده بتواند TRecord در حال رندر را دریافت و قالب Htmlای خودش را بازگشت دهد:
    /// <summary>
    ///     The treeview item's template
    /// </summary>
    [Parameter]
    public RenderFragment<TRecord>? ItemTemplate { set; get; }
- همچنین همیشه باید به فکر عدم وجود اطلاعاتی برای نمایش نیز بود. به همین جهت بهتر است قالب دیگری را نیز از مصرف کننده برای اینکار درخواست کنیم و نحوه‌ی رندر سفارشی این قسمت را نیز به مصرف کننده واگذار کنیم:
    /// <summary>
    ///     The content displayed if the list is empty
    /// </summary>
    [Parameter]
    public RenderFragment? EmptyContentTemplate { set; get; }
- زمانیکه با شیء از پیش تعریف شده‌ی Comment این مثال کار می‌کنیم، کاملا مشخص است که خاصیت Comments آن تو در تو است:
public class Comment
{
    public IList<Comment> Comments = new List<Comment>();
    public string? Text { set; get; }
}
اما زمانیکه با یک کامپوننت جنریک کار می‌کنیم، نیاز است از مصرف کننده، نام این خاصیت تو در تو را به نحو واضحی دریافت کنیم؛ به صورت زیر:
    /// <summary>
    ///     The property which returns the children items
    /// </summary>
    [Parameter]
    public Expression<Func<TRecord, IEnumerable<TRecord>>>? ChildrenSelector { set; get; }
دلیل استفاده از Expression Funcها را در مطلب «static reflection» می‌توانید مطالعه کنید. زمانیکه قرار است از کامپوننت DntTreeView استفاده کنیم، ابتدا نوع جنریک آن‌را مشخص می‌کنیم، سپس لیست اشیاء ارسالی به آن‌را و در ادامه با استفاده از ChildrenSelector به صورت زیر، مشخص می‌کنیم که خاصیت Comments است که به همراه Children می‌باشد و تو در تو است:
        <DntTreeView
            TRecord="Comment"
            Items="Comments"
            ChildrenSelector="m => m.Comments"
و مرسوم است جهت بالابردن کارآیی Expression Funcها، آن‌ها را کامپایل و کش کنیم که نمونه‌ای از روش آن‌را به صورت زیر مشاهده می‌کنید:
public partial class DntTreeView<TRecord>
{
    private Expression? _lastCompiledExpression;
    internal Func<TRecord, IEnumerable<TRecord>>? CompiledChildrenSelector { private set; get; }

    // ...

    protected override void OnParametersSet()
    {
        if (_lastCompiledExpression != ChildrenSelector)
        {
            CompiledChildrenSelector = ChildrenSelector?.Compile();
            _lastCompiledExpression = ChildrenSelector;
        }
    }
}
تا اینجا ساختار کدهای پشت صحنه‌ی DntTreeView.razor.cs مشخص شد. اکنون UI این کامپوننت را به صورت زیر تکمیل می‌کنیم:
@namespace BlazorTreeView.Pages.Components
@typeparam TRecord

@if (Items is null || !Items.Any())
{
    @EmptyContentTemplate
}
else
{
    <CascadingValue Value="this">
        <ul @attributes="AdditionalAttributes">
            @foreach (var item in Items)
            {
                <DntTreeViewChildrenItem TRecord="TRecord" ParentItem="item"/>
            }
        </ul>
    </CascadingValue>
}
در ابتدای کار، اگر آیتمی برای نمایش وجود نداشته باشد، EmptyContentTemplate دریافتی از استفاده کننده را رندر می‌کنیم. در غیراینصورت، حلقه‌ای را بر روی لیست Items ایجاد کرده و آن‌‌ها را یکی نمایش می‌دهیم. این نمایش، نکات زیر را به همراه دارد:
- نمایش توسط کامپوننت دومی به نام DntTreeViewChildrenItem انجام می‌شود که آن‌‌هم جنریک است و شیء item جاری را توسط خاصیت ParentItem دریافت می‌کند.
- در اینجا یک CascadingValue اشاره کننده به شیء this را هم مشاهده می‌کنید. این روش، یکی از روش‌های اجازه دادن دسترسی به خواص و امکانات یک کامپوننت والد، در کامپوننت‌های فرزند است که در ادامه از آن استفاده خواهیم کرد.


تکمیل کامپوننت بازگشتی DntTreeViewChildrenItem.razor

اگر به حلقه‌ی foreach (var item in Items) در کامپوننت DntTreeView.razor دقت کنید، یک سطح را بیشتر پوشش نمی‌دهد؛ اما کامنت‌های ما چندسطحی و تو در تو هستند و عمق آن‌ها هم مشخص نیست. به همین جهت نیاز است به نحوی بتوان یک طراحی recursive و بازگشتی را در کامپوننت‌های Blazor داشت که خوشبختانه این مورد پیش‌بینی شده‌است و هر کامپوننت Blazor، می‌تواند خودش را نیز فراخوانی کند:
@namespace BlazorTreeView.Pages.Components
@typeparam TRecord

<li @attributes="@SafeOwnerTreeView.ChildrenHtmlAttributes" @key="ParentItem?.GetHashCode()">
    @if (SafeOwnerTreeView.ItemTemplate is not null && ParentItem is not null)
    {
        @SafeOwnerTreeView.ItemTemplate(ParentItem)
    }
    @if (Children is not null)
    {
        <ul>
            @foreach (var item in Children)
            {
                <DntTreeViewChildrenItem TRecord="TRecord" ParentItem="item"/>
            }
        </ul>
    }
</li>
این‌ها کدهای DntTreeViewChildrenItem.razor هستند که در آن، ابتدا ItemTemplate دریافتی از والد یا همان DntTreeView.razor رندر می‌شود. سپس به کمک CompiledChildrenSelector ای که عنوان شد، یک شیء Children را تشکیل داده و آن‌را به خودش (فراخوانی مجدد DntTreeViewChildrenItem در اینجا)، ارسال می‌کند. این فراخوانی بازگشتی، سبب رندر تمام سطوح تو در توی شیء جاری می‌شود.

کدهای پشت صحنه‌ی این کامپوننت یعنی فایل DntTreeViewChildrenItem.razor.cs به صورت زیر است:
/// <summary>
///     A custom DntTreeView
/// </summary>
public partial class DntTreeViewChildrenItem<TRecord>
{
    /// <summary>
    ///     Defines the owner of this component.
    /// </summary>
    [CascadingParameter]
    public DntTreeView<TRecord>? OwnerTreeView { get; set; }

    private DntTreeView<TRecord> SafeOwnerTreeView =>
        OwnerTreeView ??
        throw new InvalidOperationException("`DntTreeViewChildrenItem` should be placed inside of a `DntTreeView`.");

    /// <summary>
    ///     Nested parent item to display
    /// </summary>
    [Parameter]
    public TRecord? ParentItem { set; get; }

    private IEnumerable<TRecord>? Children =>
        ParentItem is null || SafeOwnerTreeView.CompiledChildrenSelector is null
            ? null
            : SafeOwnerTreeView.CompiledChildrenSelector(ParentItem);
}
با استفاده از یک پارامتر از نوع CascadingParameter، می‌توان به اطلاعات شیء CascadingValue ای که در کامپوننت والد DntTreeView.razor قرا دادیم، دسترسی پیدا کنیم. سپس یکبار هم بررسی می‌کنیم که آیا نال هست یا خیر. یعنی قرار نیست که این کامپوننت فرزند، درجائی به صورت مستقیم استفاده شود. فقط قرار است داخل کامپوننت والد فراخوانی شود. به همین جهت اگر این CascadingParameter نال بود، یعنی این کامپوننت فرزند، به اشتباه فراخوانی شده و با صدور استثنائی این مساله را گوشزد می‌کنیم. اکنون که به SafeOwnerTreeView یا همان نمونه‌ای از شیء والد دسترسی پیدا کردیم، می‌توانیم پارامتر CompiledChildrenSelector آن‌را نیز فراخوانی کرده و توسط آن، به شیء تو در توی جدیدی در صورت وجود، جهت رندر بازگشتی آن رسید.
یعنی این کامپوننت ابتدا ParentItem، یا اولین سطح ممکن و در دسترس را رندر می‌کند. سپس با استفاده از Expression Func مهیای در کامپوننت والد، شیء فرزند را در صورت وجود یافته و سپس به صورت بازگشتی آن‌را با فراخوانی مجدد خودش ، رندر می‌کند.


روش استفاده از کامپوننت DntTreeView

اکنون که کار توسعه‌ی کامپوننت جنریک DntTreeView پایان یافت، روش استفاده‌ی از آن به صورت زیر است:
<div class="card" dir="rtl">
    <div class="card-header">
        DntTreeView
    </div>
    <div class="card-body">
        <DntTreeView
            TRecord="Comment"
            Items="Comments"
            ChildrenSelector="m => m.Comments"
            style="list-style: none;"
            ChildrenHtmlAttributes="ChildrenHtmlAttributes">
            <ItemTemplate Context="record">
                <div class="card mb-1">
                    <div class="card-body">
                        <span>@record.Text</span>
                    </div>
                </div>
            </ItemTemplate>
            <EmptyContentTemplate>
                <div class="alert alert-warning">
                    There is no item to display!
                </div>
            </EmptyContentTemplate>
        </DntTreeView>
    </div>
</div>
همانطور که مشاهده می‌کنید، چون کامپوننت جنریک است، باید نوع TRecord را که در مثال ما، شیء Comment است، مشخص کرد. سپس لیست نظرات، خاصیت تو در تو، قالب سفارشی نمایش Text نظرات (با توجه به Context دریافتی که امکان دسترسی به شیء جاری در حال رندر را میسر می‌کند) و همچنین قالب سفارشی نبود اطلاعاتی برای نمایش را تعریف می‌کنیم.

کدهای کامل این مطلب را از اینجا می‌توانید دریافت کنید: BlazorTreeView.zip
کامپوننت توسعه یافته‌ی در اینجا در هر دو حالت Blazor WASM و Blazor Server کار می‌کند.
مطالب
نحوه‌ی شناسایی مرورگر Edge در برنامه‌های ASP.NET
 قطعه کد زیر در برنامه‌های ASP.NET، نام مرورگر کاربر و همچنین شماره نگارش آن‌را باز می‌گرداند:
 var browser = Request.Browser.Browser + " " + Request.Browser.Version;

برای مثال با فایرفاکس، چنین خروجی را دارد:


اما ... با مرورگر جدید Edge مایکروسافت، خروجی کروم را مشاهده خواهیم کرد:


از این جهت که user agent این مرورگر، چنین شکلی را دارد و ختم به edge است:
Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.135 Safari/537.36 Edge/12.10240

برای رفع این مشکل، نیاز است فایل جدیدی را به مجموعه‌ی «browser definition files» دات نت اضافه کنیم. این فایل‌ها عموما در مسیر زیر یافت می‌شوند:
 <windir>\Microsoft.NET\Framework\<ver>\CONFIG\Browsers
برای نمونه مسیر ذیل را برای مشاهده‌ی فایل‌های مرورگرهای موجود، بررسی کنید:
 C:\Windows\Microsoft.NET\Framework\v4.0.30319\Config\Browsers
در این بین اثری از تعریف مرورگر edge نیست. برای حل این مشکل، الزاما نیازی نیست تا فایل مرورگر جدیدی را به پوشه‌ی فوق اضافه کنیم. می‌توان تعریف این فایل را در پوشه‌ی استانداردی به نام App_Browsers نیز در ریشه‌ی پروژه، قرار داد:


با این محتویات:
<browsers>
  <browser id="Edge" parentID="Chrome">
    <identification>
      <userAgent match="Edge/(?'version'(?'major'\d+)(?'minor'\.\d+))" />
    </identification>
    <capabilities>
      <capability name="browser" value="Edge" />
      <capability name="version" value="${version}" />
      <capability name="majorversion" value="${major}" />
      <capability name="minorversion" value="${minor}" />
    </capabilities>
  </browser>
</browsers>
در اینجا user agent مرورگر کاربر دریافت شده و اگر ختم به Edge بود، نام و شماره نگارش صحیح آن، دریافت خواهد شد.
اکنون پس از این تنظیمات، برنامه (تفاوتی نمی‌کند که وب فرم باشد یا MVC) اطلاعات صحیحی را نمایش می‌دهد:

نظرات مطالب
ارتقاء به ASP.NET Core 1.0 - قسمت 15 - بررسی تغییرات Caching
یک نکته‌ی تکمیلی: MemoryCache در ASP.NET Core 3.0

از نگارش 2.0، به تنظیمات MemoryCache خاصیت SizeLimit اضافه شده‌است تا اگر به این حد رسید، شروع به حذف کردن آیتم‌های موجود کند:
public class MyMemoryCache 
{
    public MemoryCache Cache { get; set; }
    public MyMemoryCache()
    {
        Cache = new MemoryCache(new MemoryCacheOptions
        {
            SizeLimit = 1024
        });
    }
}
تنظیم این خاصیت در ASP.NET Core 3.0 در هر جائیکه که وجود داشته، انجام شده. در این حالت اگر برنامه‌ی قدیمی خود را اجرا کنید، یک چنین استثنایی را دریافت خواهید کرد:
Exception: Cache entry must specify a value for Size when SizeLimit is set.
at Microsoft.Extensions.Caching.Memory.MemoryCache.SetEntry(CacheEntry entry)
راه حل آن، یافتن تمام MemoryCacheEntryOptions‌ها در کل برنامه و تمام وابستگی‌های آن (یافتن تمام متدهای cache.Set و تنظیم پارامتر سوم آن) و سپس افزودن خاصیت Size =1 به آن است. در این حالت واحد به 1 تنظیم می‌شود. یعنی SizeLimit به تعداد آیتم موجود در کش، تفسیر خواهد شد. روش دیگر تنظیم آن، استفاده از متد الحاقی SetSize است:
 var cacheEntryOptions = new MemoryCacheEntryOptions()
                .SetSize(1)
                .SetSlidingExpiration(TimeSpan.FromSeconds(3));
مطالب
روش اتصال دومین‌های ir. به یک DNS Server
با از دست رفتن دومین info. سایت به علت معلق سازی اینگونه دومین‌های ایرانی توسط مهم‌ترین ثبت کنندگان دومین در دنیا:


 در سایت رسمی nic.ir ، دومین جاری را ثبت و سپس برای تعیین DNS آن، از سرویس رایگان cloudflare.com استفاده کردم که خلاصه‌ای از روش انجام اینکار را در ادامه مطالعه خواهید کرد.


ثبت دومین در سایت nic.ir

صرفنظر از معلق شدن دومین info. سایت، شاید جالب باشد بدانید قیمت تمدید این نوع دامنه‌ها برای یکسال چقدر شده‌است:


این رقم، بیش 10 برای رقمی است که در ابتدای کار این سایت، برای ثبت دامنه پرداخت کرده بودم. اما ... ثبت یک دامنه‌ی ir.، در سایت رسمی nic.ir برای 5 سال، دقیقا 48 هزار تومان تمام می‌شود که فوق العاده‌است!
برای شروع به کار با سایت nic.ir، ابتدا نیاز است یک شناسه را در این سایت ایجاد کنید. برای مثال اگر فقط می‌خواهید یک دومین ir. ساده را داشته باشید، همان انتخاب گزینه‌ی اول «شخص حقیقی» کفایت می‌کند.
مرحله‌ی بعد، تکمیل فرم متناظر با آن است که در اینجا اطلاعات را باید با همان قالبی که در مثال‌های آن ذکر کرده، وارد کنید. در این فرم، پرسش محرمانه را خوب بخاطر بسپارید؛ چون در حین تکمیل قسمت‌های بعدی کار، مدام سؤال پرسیده می‌شود. همچنین تمام مکاتبه‌ها و اطلاعات مراحل بعدی را در میل‌باکس خود دریافت خواهید کرد.
پس از تائید ایمیل خود، می‌توانید از منوی «دامنه‌ها / ثبت دامنه»، نسبت به ثبت یک دومین جدید و در همانجا پرداخت وجه متناظر با آن اقدام کنید. پس از مدتی (تا سه ساعت بعد)، این اطلاعات توسط nic.ir بررسی شده و تائیدیه نهایی را در میل باکس خود دریافت خواهید کرد.


تعریف name-server‌های مخصوص یک دومین ir.

تا اینجا، دومین شما تعریف و فعال شده ... اما قابل استفاده نیست. مرحله‌ی بعدی، تعریف رکوردهای DNS دومین است، تا پس از وارد کردن آدرس سایت در مرورگر، به آدرس IP متناظری (برای مثال آدرس IP ثابت سرور مجازی / VPS شما) اشاره کند. برای اینکار می‌توان از سرویس رایگان cloudflare.com استفاده کرد.
در این سایت ثبت نام کنید و پس از فعالسازی ایمیل خود، از پلن‌های مختلف کاربری آن، پلن رایگان آن‌را که خدمات DNS را ارائه می‌دهد، انتخاب کنید. در اینجا می‌توان از طریق منوی بالای صفحه و انتخاب گزینه‌ی Add site، آدرس دومین خود را وارد کنید، تا مراحل ثبت اطلاعات DNS آن آغاز شود.
پس از ثبت سایت خود در cloudflare.com، باید به اطلاعاتی که ارائه می‌کند، دقت داشت:


عنوان می‌کند که به محل ثبت دامنه‌ی خود مراجعه کرده و اطلاعات فوق را در آن وارد کنید (منظور همان دو nameserver جدید lovisa.ns.cloudflare.com و todd.ns.cloudflare.com است)؛ همچنین اگر پیشتر اطلاعات دیگری را در آنجا وارد کرده بودید، باید تمام آن‌ها را هم حذف کنید. در غیراینصورت درخواست شما پردازش نخواهد شد.
برای این منظور به اکانت nic.ir خود وارد شده و به قسمت «دامنه / دامنه‌های من» وارد شوید. در اینجا بر روی لینک NS ای که مشاهده می‌کنید، کلیک کنید:


منظور از NS، همان nameserver هایی است که عنوان شد. اکنون در صفحه‌ی تنظیمات DNS، اطلاعات NSهای cloudflare.com را وارد کرده و ذخیره کنید:



تکمیل ثبت رکوردهای DNS یک دومین ir.

پس از ثبت nameserver‌های cloudflare.com در سایت nic.ir، مرحله‌ی آخر کار، تکمیل رکوردهای DNS دومین است. به همین جهت به اکانت cloudflare.com خود وارد شده و در تنظیمات دومین ثبت شده، گزینه‌ی DNS را انتخاب کرده و رکوردهای آن را به صورت زیر تکمیل کنید:



بدیهی است در اینجا تنها تفاوت‌های مورد نیاز، تغییر نام دامنه و آدرس IP متناظر با آن است. در مورد رکورد spf1 در اینجا بیشتر توضیح داده شده‌است.

پس از اینکار، بر روی لینک منوی overview در بالای صفحه کلیک کرده و در پایین این صفحه، بر روی دکمه‌ی «check nameservers» کلیک کنید، تا cloudflare کار بررسی اطلاعات تنظیم شده‌ی توسط شما را شروع کند:


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


اکنون دومین شما قابل استفاده‌است!
مطالب
آموزش (jQuery) جی کوئری 5#
در ادامه مطلب قبلی  آموزش (jQuery) جی کوئری 4# به ادامه بحث  می‌پردازیم.
در پست قبل به بررسی انتخاب عناصر بر اساس موقعیت پرداختیم، در این پست به بحث "استفاده از انتخاب کننده‌های سفارشی jQuery" خواهیم پرداخت.

4-1- استفاده از انتخاب کننده‌های سفارشی jQuery
در پست‌های قبلی (^ و ^ ) تعدادی از انتخاب کننده‌های CSS که هر کدامشان موجب قدرت و انعطاف پذیری انتخاب اشیا موجود در صفحه می‌شوند را بررسی کردیم. با این وجود  فیلتر‌های انتخاب کننده قدرتمندتری وجود دارند که توانایی ما را برای انتخاب بیشتر می‌کنند.
به عنوان مثال اگر بخواهید از میان تمام چک باکس ها، گزینه هایی را که تیک خورده اند انتخاب نمایید، از آنجا که تلاش برای مطابقت حالت‌های اولیه کنترل‌های HTML را بررسی می‌کنیم، jQuery انتخابگر سفارشی checked: را پیشنهاد می‌کند، که مجموعه از عناصر را که خاصیت checked آنها فعال باشد را برای ما برمی گرداند. براس مثال انتخاب کننده input تمامی المان‌های <input> را انتخاب می‌کند، و انتخاب کننده input:checked تمامی inputهایی را انتخاب می‌کند که checked هستند. انتخاب کننده سفارشی checked:یک انتخاب کننده خصوصیت CSS عمل می‌کند (مانند [foo=bar]). ترکیب این انتخاب کننده‌ها می‌تواند قدرت بیشتری به ما بدهد، انتخاب کننده هایی مانند radio:checked: و checkbox:checked: .
همانطور هم که قبلا بیان شد، jQuery علاوه بر پشتیبانی از انتخاب کننده‌های CSS تعدادی انتخاب کننده سفارشی را نیز شامل می‌شود که در جدول 3-2 شرح داده شده است.

جدول 3-2: انتخاب کننده‌های سفارشی jQuery
 توضیح انتخاب کننده
 عناصری را انتخاب می‌کند که تحت کنترل انیمیشن می‌باشند. در پست‌های بعدی انیمیشن‌ها توضیح داده می‌شوند.
animated:
 عناصر دکمه را انتخاب می‌کند، عناصری مانند (input[type=submit]، input[type=reset]، input[type=button]،  یا button) 
button:
عناصر Checkbox را انتخاب می‌کند، مانند ([input[type=checkbox).
checkbox:
عناصر checkboxها یا دکمه‌های رادیویی را انتخاب می‌کند که در حالت انتخاب باشند.
checked:
عناصری ر انتخاب می‌کند که دارای عبارت foo باشند.
contains(foo) //c:
عناصر در حالت disabled را انتخاب می‌کند. disabled:
عناصر در حالت enabledرا انتخاب می‌کند.
enabled:
عناصر فایل را انتخاب می‌کند، مانند ([input[type=file).
file:
عناصر هدر مانند h1 تا h6 را انتخاب می‌کند.
header:
عناصر مخفی شده را انتهاب می‌کند.
hidden:
عناصر تصویر را انتخاب می‌کند، مانند ([input[type=image).
image:
عناصر فرم مانند input ، select، textarea، button را انتخاب می‌کند.
input:
انتخاب کننده‌ها را برعکس می‌کند.
not(filter)//c:
عناصری که فرزندی دارند را انتخاب می‌کند.
parent:
عناصر password را انتخاب می‌کند، مانند ([input[type=password). password:
عناصر radio را انتخاب می‌کند، مانند ([input[type=radio). 
radio:
دکمه‌های reset را انتخاب می‌کند، مانند  ([input[type=reset یا [button[type=reset).
raset:
عناصری (عناصر option) را انتخاب می‌کند که در وضعیت selected قراردارند.
selected:
دکمه‌های submit را انتخاب می‌کند، مانند  ([input[type=submit یا [button[type=submit). submit:
عناصر text را انتخاب می‌کند، مانند ([input[type=text).   
text:
عناصری را که در وضعیت visibleباشند انتخاب می‌کند.
visible:
بسیاری از انتخاب کننده‌های سفارشی jQuery بررسی شده برای انتخاب عناصر فرم ورود اطلاعات کاربر استفاده می‌شوند. این فیلتر‌ها قابلیت ادغام را دارند، برای مثال در زیر دستوری را به منظور انتخاب آن دسته از گزینه‌های Checkbox که تیک خورده اند و فعال هستند را مشاهده می‌کنید:
:checkbox:checked:enabled

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

استفاده از فیلتر not:
برای آنکه نتیجه انتخاب کننده‌ها را معکوس کنیم می‌توانیم از این فیلتر استفاده کنیم. برای مثال دستور زیر تمام عناصری را که checkBox نیستند را انتخاب می‌کند:
input:not(:checkbox)
اما استفاده از این فیلتر دقت زیادی را می‌طلبد زیرا به سادگی ممکن است با نتیجه ای غیر منتظره مواجه شویم.

استفاده از فیلتر has:
در اینجا دیدیم که CSS انتخاب کننده قدرتمندی را ارایه کرده است که فرزندران یک عنصر را در هر سطحی که باشند (حتی اگر فرزند مستقیم هم نباشند) انتخاب می‌کند. برای مثال دستور زیر تمام عناصر span را که در div معرفی شده باشند را انتخاب می‌کند:
div span

اما اگر بخواهیم انتخابی برعکس این انتخاب داشته باشیم، باید چه کنیم؟ برای این کار باید تمام divهایی که دارای عنصر span می‌باشد را انتخاب کرد. برای چنین انتخابی از فیلتر has: استفاده می‌کنیم. به دستور زیر توجه نمایید، این دستور تمام عناصر div را که در آنها عنصر span معرفی شده است را انتخاب می‌کند:
div:has(span)

برای برخی انتخاب‌های پیچیده و مشکل، این فیلتر و مکانیزم بسیار کارا می‌باشد و به سادگی ما را به هدف دلخواه می‌رساند. فرض کنید می‌خواهیم آن خانه از جدول که دارای یک عنصر عکس خاص می‌باشد را پیدا کنیم. با توجه به این نکته که آن عکس از طریق مقدار src قابل تشخیص می‌باشد، با استفاده از فیلتر has: دستوری مانند زیر می‌نویسیم:
$('tr:has(img[src$="foo.png"])')

این دستور هر خانه از جدول را که این عکس در آن قرار گرفته باشد را انتخاب می‌کند.
همانگونه که دیدیم jQuery گزینه‌های بسیار متعددی را به منظور انتخاب عناصر موجود در صفحه برای ما مهیا کرده است که می‌توانیم هر عنصری از صفحه را انتخاب و سپس تغییر دهیم که تغییر این عناصر در پست‌های آینده بحث خواهد شد.


موفق و موید باشید.
مطالب
MIME Sniffing و استاندارد OWASP

 یکی از نکات امنیتی که استاندارد  Owasp  بررسی مینماید هدر X-Content-Type-Options است که جهت جلوگیری از حملات از طریق فایل‌های نامرتبط میباشد. در این رخنه ممکن است فایلی که مرورگر دریافت میکند با آنچه که وب سایت ما آن را میشناسد متفاوت باشد. به عنوان مثال یک فایل اسکریپت که به عنوان یک فایل استایل معرفی میگردد ولی  قابلیت اجرای کدهای آن در مرورگر امکان پذیر است؛ به این نوع حملات MIME Sniffing میگویند. در یکی از سایت‌هایی که در حال حاضر در حال توسعه آن هستیم بخشی از گزارش‌ها با استفاده از ابزار FastReport  ایجاد شده بود و با توجه به اینکه در این ابزار از فایل‌های axd استفاده می‌گردید و مرورگر نوع دیتای برگشتی و MimeType معرفی شده را همخوان نمیدانست، از نمایش و بارگذاری آن ممانعت به عمل آورده و در نتیجه فایل گزارش دیده نمیشد. در این مقاله قصد داریم به معرفی این نوع حمله، روش جلوگیری از آن و همچنین رفع محدودیت پیش آمده را بررسی کرده تا در موارد مشابه از آن استفاده نماییم.


Mime Sniffing

زمانیکه مرورگر در هدر(پاسخ) Response، نوع محتوای ارسال شده، Content-Type را دریافت نکند، یا مرورگر متوجه مغایرتی در آن شود، این نوع رفتار را Mime Sniffing شناسایی میکند. نحوه این شناسایی‌ها در هر مرورگری میتواند متفاوت باشد؛ ولی عموما بر اساس type ارسالی و پسوند مورد نظر میباشد. در بعضی از موارد نیز خواندن بایت‌های ابتدایی یک فایل نیز میتواند نشان دهد که محتوای ارسالی واقعا چیست. به عنوان مثال برای فایل‌هایی با پسوند Gif، الگوی بایت‌های ابتدایی شامل  47 49 46 38 39 میباشد؛ ولی از آنجا که در همه فایل‌ها، بایت‌های ابتدایی الگوی یکسانی ندارند، پس نمی‌توان به این روش نیز بسنده کرد.


روش اینکه به مروگر بگوییم جلوی این نوع حملات را بگیرد و در صورت شناسایی Sniffing از اجرای آن سر باز بزند، استفاده از هدر X-Content-Type-Options میباشد که نحوه افزودن آن در فایل web.config  به شکل زیر است:

 <httpProtocol>
      <customHeaders>
  ...
        <remove name="X-Content-Type-Options"/>
        <add name="X-Content-Type-Options" value="nosniff" />
        ..
      </customHeaders>
    </httpProtocol>

در پروژه ما به دلیل اینکه بخشی از گزارش‌ها با استفاده از FastReport طراحی شده بود این مورد برای ما ایجاد مشکل می‌کرد و در گزارش نمایش داده نمی‌شد و در کنسول پیام‌هایی به شکل زیر دریافت میکردیم:

Refused to execute script from 'https://localhost:44377/WebResource.axd?d=xxx' because its MIME type ('text/js') is not executable, and strict MIME type checking is enabled.

با نگاهی به Response  دریافتی نیز میتوان بازگشت هدر امنیتی X-Content-Type-Options را نیز مشاهده نمود:

 پس باید این هدر را برای بعضی از آدرس‌ها که میتوانند دچار مشکلات اجرایی گردند حذف کرده و برای مابقی بخش‌ها همچنان این هدر فعال باشد؛ پس با افزودن کد زیر به web.config، هدر مورد نظر را برای این نوع فایل حذف میکنیم:

  <location path="WebResource.axd" >
    <system.webServer>
      <httpProtocol>
        <customHeaders>
          <remove name="X-Content-Type-Options" />
        </customHeaders>
      </httpProtocol>
    </system.webServer>
  </location>

 نتیجه آن را میتوانید در صفحه ذیل برای همان درخواست و پاسخ قبلی نیز مشاهده نمایید:

 
مطالب
تشخیص غیرفعال بودن JavaScript در مرورگر کاربر

اکثر کنترل‌های تعیین اعتبار ASP.Net بر اساس جاوا اسکریپت کار می‌کنند (مانند RangeValidator و امثال آن). حال اگر کاربری افزونه no script فایرفاکس را نصب کرده بود چه باید کرد؟
با استفاده از این افزونه، این نوع کنترل‌ها از کار خواهند افتاد (چون دیگر کدهای جاوا اسکریپتی آنها اجرا نخواهند شد).
خوشبختانه برای بررسی صحت عملکرد این کنترل‌ها در ASP.Net امکان بررسی خاصیت Page.IsValid نیز وجود دارد که در ادامه به آن خواهیم پرداخت.

صفحه‌ی بسیار ساده ASP.Net زیر را در نظر بگیرید:
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="WebForm3.aspx.cs" Inherits="testWebForms87.WebForm3" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title></title>
</head>
<body>
<form id="form1" runat="server">
<div>
<asp:TextBox ID="txtData" runat="server"></asp:TextBox>
<asp:RangeValidator ID="RangeValidator1" runat="server" ControlToValidate="txtData"
ErrorMessage="لطفا یک عدد وارد کنید" MaximumValue="100000" MinimumValue="0" SetFocusOnError="True"
Type="Integer"></asp:RangeValidator>
<asp:RequiredFieldValidator ID="RequiredFieldValidator1" runat="server" ControlToValidate="txtData"
ErrorMessage="لطفا مقداری را وارد نمائید" SetFocusOnError="True"></asp:RequiredFieldValidator>
<br />
<asp:Button ID="btnSubmit" runat="server" OnClick="btnSubmit_Click" Text="Submit" />
<br />
<asp:Label ID="lblValue" runat="server"></asp:Label>
</div>
</form>
</body>
</html>

یکبار این صفحه را با فعال کردن افزونه یاد شده بررسی کنید.
سپس برای بررسی سمت سرور عملکرد کنترل‌های تعیین اعتبار در ASP.Net می‌توان به صورت زیر عملکرد:

        protected void btnSubmit_Click(object sender, EventArgs e)
{
if (btnSubmit.CausesValidation)
{
// Validate the Page
Page.Validate();

// Ensure Page is Valid
if (!Page.IsValid)
{
lblValue.Text = "لطفا جاوا اسکریپت را در مرورگر خود فعال نمائید";
return;
}
}

lblValue.Text = txtData.Text;
}

ابتدا بررسی می‌شود که آیا دکمه مورد استفاده جهت ارسال مقادیر صفحه به سرور سبب فعال شدن کنترل‌های تعیین اعتبار می‌شود؟ اگر اینطور بود، سپس صفحه تعیین اعتبار شده و با استفاده از مقدار خاصیت Page.IsValid مشخص می‌شود که آیا این عملیات به درستی صورت گرفته است یا خیر.

راه دیگر بررسی غیرفعال بودن جاوا اسکریپت در یک صفحه استفاده از روش سنتی تگ noscript است:

<noscript>
<meta http-equiv="refresh" content="0;url=http://www.google.com">
</noscript>

در این مثال اگر جاوا اسکریپت غیرفعال باشد کاربر به گوگل هدایت خواهد شد. البته بهتر است یک صفحه‌ی خطای از پیش تعیین شده برای این مورد در نظر گرفته شود.

در پایان باید خاطر نشان کرد که هیچگاه به کنترل‌های تعیین اعتبار سمت کاربر اطمینان نکنید و حتما یا از روش فوق استفاده نمائید، یا در روال submit صفحه به سرور، یکبار دیگر داده‌ها را به صورت دستی نیز بررسی کنید. برای مثال اگر کاربر قرار است آدرس ایمیلی را وارد کند، حتما یکبار دیگر با استفاده از regular expressions ، در سمت سرور نیز عبارت ورودی را بررسی کنید.