مطالب
استفاده از AvalonEdit در WPF
AvalonEdit یکی از زیرساخت‌های برنامه‌ی SharpDevelop است که ویرایشگر متنی به همراه syntax highlighting زبان‌های مختلف را در آن پشتیبانی می‌کند. کیفیت بالایی داشته و بسیاری از برنامه‌های دیگر نیز از آن جهت ارائه ویرایشگر و یا syntax highlighting متون ارائه شده، استفاده می‌کنند. در ادامه نحوه‌ی استفاده از این ویرایشگر را در برنامه‌های WPF خصوصا با دید MVVM بررسی خواهیم کرد.



دریافت و نصب AvalonEdit

برای نصب AvalonEdit می‌توان دستور ذیل را در کنسول پاورشل نیوگت صادر کرد:
 PM> install-package AvalonEdit


استفاده‌ی مقدماتی از AvalonEdit

برای استفاده از این ویرایشگر ابتدا نیاز است فضای نام xmlns:avalonEdit تعریف شود. سپس کنترل avalonEdit:TextEditor در دسترس خواهد بود:
<Window x:Class="SyntaxHighlighter.MainWindow"
   xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
   xmlns:avalonEdit="http://icsharpcode.net/sharpdevelop/avalonedit"
   Title="MainWindow" Height="401" Width="617">
  <Grid>
<avalonEdit:TextEditor  
    Name="txtCode"
    SyntaxHighlighting="C#"
  FontFamily="Consolas"
  FontSize="10pt"/>   
  </Grid>
</Window>
توسط خاصیت SyntaxHighlighting آن می‌توان زبان مشخصی را تعریف کرد. لیست زبان‌های توکار پشتیبانی شده


استفاده از AvalonEdit در برنامه‌های MVVM

خاصیت Text این ویرایشگر به صورت معمولی تعریف شده (DependencyProperty نیست) و امکان binding دو طرفه به آن وجود ندارد. به همین جهت نیاز است یک چنین DependencyProperty را به آن اضافه کرد:
using System;
using System.Collections.Concurrent;
using System.Reflection;
using System.Windows;
using System.Xml;
using ICSharpCode.AvalonEdit;
using ICSharpCode.AvalonEdit.Highlighting;
using ICSharpCode.AvalonEdit.Highlighting.Xshd;

namespace AvalonEditWpfTest.Controls
{
    public class BindableAvalonTextEditor : TextEditor
    {
        public static readonly DependencyProperty BoundTextProperty =
            DependencyProperty.Register("BoundText",
                typeof(string),
                typeof(BindableAvalonTextEditor),
                new FrameworkPropertyMetadata(default(string), propertyChangedCallback));
 
        public static string GetBoundText(DependencyObject obj)
        {
            return (string)obj.GetValue(BoundTextProperty);
        }

        public static void SetBoundText(DependencyObject obj, string value)
        {
            obj.SetValue(BoundTextProperty, value);
        }

        protected override void OnTextChanged(EventArgs e)
        {
            SetCurrentValue(BoundTextProperty, Text);
            base.OnTextChanged(e);
        }

        private static void propertyChangedCallback(DependencyObject obj,
                                    DependencyPropertyChangedEventArgs args)
        {
            var target = (BindableAvalonTextEditor)obj;
            var value = args.NewValue;
            if (value == null)
                return;

            if (string.IsNullOrWhiteSpace(target.Text) ||
                !target.Text.Equals(args.NewValue.ToString()))
            {
                target.Text = args.NewValue.ToString();
            }
        }
    }
}
کار با ارث بری از TextEditor (ویرایشگر AvalonEdit) شروع می‌شود. سپس یک DependencyProperty به نام BoundText در اینجا اضافه شده‌است. هر زمان که متن داخل آن تغییر کرد، آن‌را به خاصیت متنی Text این ویرایشگر نسبت می‌دهد. به این ترتیب binding یک طرفه (از کدها به کنترل) کار می‌کند. فعال سازی binding دو طرفه با پشتیبانی از انتقال تغییرات از ویرایشگر به خواص ViewModel در متد بازنویسی شده‌ی OnTextChanged انجام می‌شود.

اکنون برای استفاده از این کنترل جدید که BindableAvalonTextEditor نام دارد، می‌توان به نحو ذیل عمل کرد:
<Window x:Class="AvalonEditWpfTest.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:viewModels="clr-namespace:AvalonEditTests.ViewModels"
        xmlns:controls="clr-namespace:AvalonEditWpfTest.Controls"
        Title="MainWindow" Height="350" Width="525">
    <Window.Resources>
        <viewModels:MainWindowViewModel x:Key="MainWindowViewModel"/>
    </Window.Resources>
    <Grid DataContext="{Binding Source={StaticResource MainWindowViewModel}}">
        <controls:BindableAvalonTextEditor
                BoundText="{Binding SourceCode, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
                        WordWrap="True"
                        ShowLineNumbers="True"
                        LineNumbersForeground="MediumSlateBlue"
                        FontFamily="Consolas"
                        VerticalScrollBarVisibility="Auto"
                        Margin="3"          
                        HorizontalScrollBarVisibility="Auto"
                        FontSize="10pt"/>
    </Grid>
</Window>
ابتدا فضای نام جدید کنترل BindableAvalonTextEditor مشخص می‌شود و سپس به controls:BindableAvalonTextEditor دسترسی خواهیم داشت. در اینجا نحوه‌ی استفاده از خاصیت جدید BoundText را نیز مشاهده می‌کنید.


افزودن syntax highlighting زبان‌هایی که به صورت رسمی پشتیبانی نمی‌شوند

به خاصیت SyntaxHighlighting این کنترل صرفا مقادیری را می‌توان نسبت داد که به صورت توکار پشتیبانی می‌شوند. برای مثال#XML، C و امثال آن.
فرض کنید نیاز است SyntaxHighlighting زبان SQL را فعال کنیم. برای اینکار نیاز به فایل‌های ویژه‌ای است، با پسوند xshd. برای نمونه فایل sql-ce.xshd را در اینجا می‌توانید مطالعه کنید. در آن یک سری واژه‌های کلیدی و حروفی که باید با رنگی متفاوت نمایش داده شوند، مشخص می‌گردند.
برای استفاده از فایل sql-ce.xshd باید به نحو ذیل عمل کرد:
الف) فایل sql-ce.xshd را به پروژه اضافه کرده و سپس در برگه‌ی خواص آن در VS.NET، مقدار build action آن‌را به embedded resource تغییر دهید.



ب) با استفاده از متد ذیل، این فایل مدفون شده در اسمبلی را گشوده و به متد HighlightingLoader.Load ارسال می‌کنیم:
        private static IHighlightingDefinition getHighlightingDefinition(string resourceName)
        {
            if (string.IsNullOrWhiteSpace(resourceName))
                throw new NullReferenceException("Please specify SyntaxHighlightingResourceName.");

            using (var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(resourceName))
            {
                if (stream == null)
                    throw new NullReferenceException(string.Format("{0} resource is null.", resourceName));

                using (var reader = new XmlTextReader(stream))
                {
                    return HighlightingLoader.Load(reader, HighlightingManager.Instance);
                }
            }
        }
نحوه استفاده از آن نیز به صورت ذیل است:
 txtCode.SyntaxHighlighting = getHighlightingDefinition(resourceName);
به این ترتیب می‌توان یک فایل xhsd را به صورت پویا بارگذاری و به خاصیت SyntaxHighlighting کنترل انتساب داد.

برای سهولت استفاده از این قابلیت شاید بهتر باشد یک DependencyProperty دیگر به نام SyntaxHighlightingResourceName را به کنترل جدید BindableAvalonTextEditor اضافه کنیم:
using System;
using System.Collections.Concurrent;
using System.Reflection;
using System.Windows;
using System.Xml;
using ICSharpCode.AvalonEdit;
using ICSharpCode.AvalonEdit.Highlighting;
using ICSharpCode.AvalonEdit.Highlighting.Xshd;

namespace AvalonEditWpfTest.Controls
{
    public class BindableAvalonTextEditor : TextEditor
    {
         public static readonly DependencyProperty SyntaxHighlightingResourceNameProperty =
           DependencyProperty.Register("SyntaxHighlightingResourceName",
               typeof(string),
               typeof(BindableAvalonTextEditor),
               new FrameworkPropertyMetadata(default(string), resourceNamePropertyChangedCallback));
 
        public static string GetSyntaxHighlightingResourceName(DependencyObject obj)
        {
            return (string)obj.GetValue(SyntaxHighlightingResourceNameProperty);
        }

        public static void SetSyntaxHighlightingResourceName(DependencyObject obj, string value)
        {
            obj.SetValue(SyntaxHighlightingResourceNameProperty, value);
        }

        private static void loadHighlighter(TextEditor @this, string resourceName)
        {
            if (@this.SyntaxHighlighting != null)
                return;

            @this.SyntaxHighlighting = getHighlightingDefinition(resourceName);
        }

        private static void resourceNamePropertyChangedCallback(DependencyObject obj,
                                            DependencyPropertyChangedEventArgs args)
        {
            var target = (BindableAvalonTextEditor)obj;
            var value = args.NewValue;
            if (value == null)
                return;

            loadHighlighter(target, value.ToString());
        }
    }
}
کاری که در اینجا انجام شده، افزودن یک خاصیت جدید به نام SyntaxHighlightingResourceName به کنترل BindableAvalonTextEditor است. هر زمانیکه مقدار آن تغییر کند، متد getHighlightingDefinition بحث شده، فراخوانی گردیده و به صورت پویا مقدار خاصیت SyntaxHighlighting این کنترل، مقدار دهی می‌شود.
استفاده از آن نیز به شکل زیر است:
   <controls:BindableAvalonTextEditor
SyntaxHighlightingResourceName = "AvalonEditWpfTest.Controls.sql-ce.xshd"
/>

کدهای کامل این مثال را از اینجا می‌توانید دریافت کنید:
AvalonEditWpfTest.zip 
نظرات مطالب
Blazor 5x - قسمت 31 - احراز هویت و اعتبارسنجی کاربران Blazor WASM - بخش 1 - انجام تنظیمات اولیه
- فلسفه‌ی کار با Blazor server، امکان دسترسی مستقیم به لایه سرویس‌های برنامه، بدون نیاز به طراحی یک Web API خاص آن‌ها است (کار با آن ساده‌تر است). اگر قرار است با Web API کار کنید، شاید بهتر باشد از WASM استفاده کنید.
- سرویس‌های Blazor Server، طول عمر خاصی دارند و تا زمانیکه اتصال برقرار است، از دست نمی‌روند. بنابراین خیلی از اطلاعات را می‌توان به صورت متداولی در آن‌ها ذخیره کرد و نیازی به تمهید خاصی نیست؛ هرچند encrypted local storage هم دارند.
- امکان طراحی interceptor برای HTTP Client هم وجود دارد تا هربار نیازی به مقدار دهی هدر Authorization نباشد.
- بله. در این سری اگر از Identity استفاده شده، بیشتر هدف مدیریت کاربران بوده و یا برای Blazor Server، دسترسی به کوکی خودکار پس از لاگین. نکات تهیه‌ی authentication provider سفارشی مطرح شده‌ی در قسمت wasm این سری برای کار با JWT، همه جا یکسان است و وابستگی به Identity و یا حتی Identity server پیش‌فرض مطرح شده‌ی توسط مایکروسافت، ندارد.
نظرات مطالب
کاهش حجم لاگ‌ فایل‌های اس‌کیوال سرور 2005 و 2008
زمانیکه صحبت از دیتابیس شیرپوینت می‌شود یعنی دیتابیس بالای 50 گیگ.
و به 2 دلیل در اینجا نباید از auto shrink استفاده کرد:
- کاهش حجم قابل ملاحظه ذکر شده توسط auto shrink رخ نمی‌دهد.
- auto shrink یکی از مواردی است که به شدت روی کارآیی دیتابیسی حجیم تاثیر منفی دارد و همچنین سبب ایجاد fragmentation می‌شود و در اکثر مقالات استفاده از آن به شدت نهی شده‌است.
مطالب دوره‌ها
تغییرات صورت گرفته در المان‌های تایپوگرافی و شیوه‌ نامه‌های بوت استرپ 3
مبحث پیشین «استفاده از Twitter Bootstrap در کارهای روزمره طراحی وب» را احتمالا بخاطر دارید. آن مطلب برای بوت استرپ 2 تهیه شده بود و پس از مطالعه نکات «بررسی سیستم جدید گرید بوت استرپ 3» تفاوت‌های حاصل در سیستم طرحبندی آن‌را به خوبی می‌توان تشخیص داد. در ادامه مباحث دوره جاری، به تکمیل این نکات، جهت ارتقاء به بوت استرپ 3 پرداخته و تفاوت‌های مهم را برخواهیم شمرد.


تغییرات انجام شده در تعاریف ستون‌ها جهت سازگاری با اندازه‌های مختلف صفحه

علاوه بر نکات یاد شده در قسمت قبل مانند چهار اندازه جدید سیستم گریدهای بوت استرپ 3، یا امکان ترکیب این‌ها در ستون‌های مختلف، امکان مخفی کردن یا نمایش دادن مثلا یک پاراگراف یا حتی یک div ساده بر اساس اندازه صفحه نیز از بوت استرپ 2 میسر بوده است. برای به روز رسانی یک چنین کدهایی تنها کافی است به جدول ذیل دقت داشت. در این جدول نام کلاس‌های قدیمی بوت استرپ 2 و جدید بوت استرپ 3 را ملاحظه می‌کنید:
Bootstrap 2           Bootstrap 3
.visible-phone        .visible-sm
.visible-tablet       .visible-md
.visible-desktop      .visible-lg
.hidden-phone         .hidden-sm
.hidden-tablet        .hidden-md
.hidden-desktop       .hidden-lg


تغییرات صورت گرفته در تعاریف دکمه‌ها

تعاریف دکمه‌ها با نکات عنوان شده در مطلب «استفاده از Twitter Bootstrap در کارهای روزمره طراحی وب» آنچنان تفاوتی ندارند. تنها باید دقت داشت که اینبار اندازه دکمه‌ها نیز همانند اندازه ستون‌های گریدهای بوت استرپ باید مقدار دهی شوند. مثلا اگر در بوت استرپ 2، یک دکمه کوچک را به صورت btn btn-success btn-mini تعریف می‌کردیم، اینبار معادل btn-mini را باید همانند ستون‌ها، به btn-xs تغییر دهیم؛ یعنی باید نوشت btn btn-success btn-xs. خلاصه کاربردی این تغییرات را جهت به روز رسانی کدهای بوت استرپ 2 به 3 در جدول ذیل مشاهده می‌نمائید:
Bootstrap 2     Bootstrap 3
.btn.btn        .btn-default
.btn-mini       .btn-xs
.btn-small      .btn-sm
.btn-large      .btn-lg


واکنشگرا کردن تصاویر و جداول سایت‌های طراحی شده با بوت استرپ 3

اگر تصویری در یک div یا یک لینک محصور شده، یا حتی در حالت معمولی نمایش داده می‌شود، برای اینکه با تغییر اندازه صفحه به صورت خودکار بزرگ و کوچک شود، تنها کافی است کلاس img-responsive بوت استرپ 3 را به المان‌های یاد شده اضافه کنیم.
در مورد جداول HTML نیز مساله واکنشگرا بودن درنظر گرفته شده است. در اینجا تنها کافی است کل جدول را با یک div محصور کنیم و سپس به این div کلاس table-responsive را انتساب دهیم تا جداول بوت استرپ 3 نیز به اندازه‌های مختلف صفحه واکنش نشان دهند.


تغییرات لازم جهت تعاریف آیکن‌ها در بوت استرپ 3

همانطور که در قسمت‌های پیشین نیز ذکر شد، در بوت استرپ 3 دیگر از PNG image sprites استفاده نمی‌شود و بجای آن‌ها از قلم‌هایی که حاوی آیکن‌ها هستند، کمک گرفته شده است. به این ترتیب رنگ آمیزی این آیکن‌ها ساده‌تر شده و همچنین به علت نمایش برداری گلیف‌های یک قلم، در اندازه‌های مختلف، به خوبی رندر و بدون افت کیفیت نمایش داده خواهند شد. در این حالت نحوه تعریف آیکن‌ها به صورت زیر تغییر یافته است:
<span class="glyphicon glyphicon-pushpin"></span>
لیست کامل آیکن‌های پیش فرض بوت استرپ 3 را در اینجا می‌توانید ملاحظه نمائید.
مطالب
رمزنگاری خودکار فیلدها توسط Entity Framework Core
از EF Core 2.1 به بعد، قابلیت جدیدی تحت عنوان «تبدیلگرهای مقدار»، به آن اضافه شده‌است. برای مثال در EF Core، زمانیکه اطلاعات Enums، در بانک اطلاعاتی ذخیره می‌شوند، معادل عددی آن‌ها درج خواهند شد. اگر علاقمند باشید تا بجای این مقادیر عددی دقیقا همان رشته‌ی تعریف کننده‌ی Enum درج شود، می‌توان یک «تبدیلگر مقدار» را برای آن نوشت. برای مثال در موجودیت Rider زیر، خاصیت Mount از نوع یک enum است.
public class Rider
{
    public int Id { get; set; }
    public EquineBeast Mount { get; set; }
}

public enum EquineBeast
{
    Donkey,
    Mule,
    Horse,
    Unicorn
}
برای اینکه در حین درج رکوردهای Rider در بانک اطلاعاتی دقیقا از مقادیر رشته‌ای EquineBeast استفاده شود، می‌توان به صورت زیر عمل کرد:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder
        .Entity<Rider>()
        .Property(e => e.Mount)
        .HasConversion(
            v => v.ToString(),
            v => (EquineBeast)Enum.Parse(typeof(EquineBeast), v));
}
در اینجا در حین تعریف جزئیات نگاشت یک مدل می‌توان متد جدید HasConversion را نیز فراخوانی کرد. پارامتر اول آن، روش تبدیل مقدار enum را به یک رشته، جهت درج در بانک اطلاعاتی و پارامتر دوم آن، روش تبدیل مقدار رشته‌ای خوانده شده‌ی از بانک اطلاعاتی را جهت وهله سازی یک Rider داری خاصیت enum، مشخص می‌کند.

نکته 1: مقادیر نال، هیچگاه به تبدیلگرهای مقدار، ارسال نمی‌شوند. اینکار پیاده سازی آن‌ها را ساده‌تر می‌کند و همچنین می‌توان آن‌ها را بین خواص نال‌پذیر و نال‌نپذیر، به اشتراک گذاشت. بنابراین برای مقادیر نال نمی‌توان تبدیلگر نوشت.

نکته 2: کاری که در متد HasConversion فوق انجام شده‌است، در حقیقت وهله سازی ضمنی یک ValueConverter و استفاده از آن است. می‌توان اینکار را به صورت صریح نیز انجام داد:
var converter = new ValueConverter<EquineBeast, string>(
    v => v.ToString(),
    v => (EquineBeast)Enum.Parse(typeof(EquineBeast), v));
modelBuilder
    .Entity<Rider>()
    .Property(e => e.Mount)
    .HasConversion(converter);
مزیت اینکار این است که اگر قرار شد برای چندین خاصیت از تبدیلگر مقدار مشابهی استفاده کنیم، می‌توان از یک converter تعریف شده بجای تکرار کدهای آن استفاده کرد.


تبدیلگرهای مقدار توکار EF Core

برای بسیاری از اعمال متداول، در فضای نام Microsoft.EntityFrameworkCore.Storage.ValueConversion، تعدادی تبدیلگر مقدار تدارک دیده شده‌اند که به این شرح می‌باشند:
BoolToZeroOneConverter: تبدیلگر bool به صفر و یک
BoolToStringConverter: تبدیلگر bool به Y و یا N
BoolToTwoValuesConverter: تبدیلگر bool به دو مقداری دلخواه
BytesToStringConverter: تبدیلگر آرایه‌ای از بایت‌ها به یک رشته‌ی Base64-encoded
CastingConverter: تبدیلگر یک نوع به نوعی دیگر
CharToStringConverter: تبدیلگر char به string
DateTimeOffsetToBinaryConverter: تبدیلگر DateTimeOffset به یک مقدار 64 بیتی باینری
DateTimeOffsetToBytesConverter: تبدیلگر DateTimeOffset به آرایه‌ای از بایت‌ها
DateTimeOffsetToStringConverter: تبدیلگر DateTimeOffset به رشته
DateTimeToBinaryConverter: تبدیلگر DateTime به یک مقدار 64 بیتی با درج DateTimeKind
DateTimeToStringConverter: تبدیلگر DateTime به یک رشته
DateTimeToTicksConverter: تبدیلگر DateTime به ticks آن
EnumToNumberConverter: تبدیلگر Enum به عدد متناظر با آن
EnumToStringConverter: تبدیلگر Enum به رشته
GuidToBytesConverter: تبدیلگر Guid به آرایه‌ای از بایت‌ها
GuidToStringConverter: تبدیلگر Guid به رشته
NumberToBytesConverter: تبدیلگر اعداد به آرایه‌ای از بایت‌ها
NumberToStringConverter: تبدیلگر اعداد به رشته
StringToBytesConverter: تبدیلگر رشته به آرایه‌ای از بایت‌های UTF8 معادل آن
TimeSpanToStringConverter: تبدیلگر TimeSpan به رشته
TimeSpanToTicksConverter: تبدیلگر TimeSpan به ticks آن

برای نمونه در این لیست، EnumToStringConverter نیز وجود دارد. بنابراین نیازی به تعریف دستی آن مانند مثال ابتدای بحث نیست و می‌توان به صورت زیر از آن استفاده کرد:
var converter = new EnumToStringConverter<EquineBeast>();
modelBuilder
    .Entity<Rider>()
    .Property(e => e.Mount)
    .HasConversion(converter);
نکته: تمام تبدیل کننده‌های مقدار توکار EF Core، بدون حالت هستند. بنابراین می‌توان یک تک وهله‌ی از آن‌ها را بین چندین خاصیت به اشتراک گذاشت.


تعیین نوع تبدیلگر مقدار، جهت ساده سازی تعاریف

برای حالاتی که تبدیلگر مقدار توکاری تعریف شده‌است، صرفا تعریف نوع تبدیل، کفایت می‌کند:
modelBuilder
    .Entity<Rider>()
    .Property(e => e.Mount)
    .HasConversion<string>();
برای نمونه در اینجا با ذکر نوع رشته، تبدیل enum به string به صورت خودکار انجام خواهد شد. معادل اینکار، تعریف نوع سمت بانک اطلاعاتی این خاصیت است:
public class Rider
{
    public int Id { get; set; }

    [Column(TypeName = "nvarchar(24)")]
    public EquineBeast Mount { get; set; }
}
در این حالت حتی نیازی به تعریف HasConversion هم نیست.


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

پس از آشنایی با مفهوم «تبدیلگرهای مقدار» در +EF Core 2.1، اکنون می‌توانیم یک نمونه‌ی سفارشی از آن‌را نیز طراحی کنیم:
namespace DbConfig.Web.DataLayer.Context
{
    public class MyAppContext : DbContext
    {
      // …

        protected override void OnModelCreating(ModelBuilder builder)
        {
            var encryptedConverter = new ValueConverter<string, string>(
               convertToProviderExpression: v => new string(v.Reverse().ToArray()), // encrypt
               convertFromProviderExpression: v => new string(v.Reverse().ToArray()) // decrypt
            );

            // Custom application mappings
            builder.Entity<ConfigurationValue>(entity =>
            {
                entity.Property(e => e.Value).IsRequired().HasConversion(encryptedConverter);
            });
        }
    }
}
در اینجا معکوس کردن رشته‌ها به عنوان الگوریتم ساده‌ی رمزنگاری اطلاعات انتخاب شده‌است. نحوه‌ی اعمال این ValueConverter جدید را نیز ملاحظه می‌کنید.
می‌توان قسمت HasConversion را به صورت زیر خودکار کرد:
ابتدا یک Attribute جدید را به نام Encrypted به برنامه اضافه می‌کنیم:
using System;

namespace Test
{
    [AttributeUsage(AttributeTargets.Property, Inherited = false, AllowMultiple = false)]
    public sealed class EncryptedAttribute : Attribute
    { }
}
هدف از این Attribute خالی، صرفا نشانه گذاری خاصیت‌هایی است که قرار است به صورت رمزنگاری شده در بانک اطلاعاتی ذخیره شوند؛ مانند خاصیت Value زیر:
namespace DbConfig.Web.DomainClasses
{
    public class ConfigurationValue
    {
        public int Id { get; set; }
        public string Key { get; set; }

        [Encrypted]
        public string Value { get; set; }
    }
}
پس از آن، متد OnModelCreating را به صورت زیر اصلاح می‌کنیم تا به کمک Reflection و اطلاعات موجودیت‌های ثبت شده‌ی در سیستم، متد SetValueConverter را بر روی خواصی که دارای EncryptedAttribute هستند، به صورت خودکار فراخوانی کند:
namespace DbConfig.Web.DataLayer.Context
{
    public class MyAppContext : DbContext
    {
        protected override void OnModelCreating(ModelBuilder builder)
        {
            var encryptedConverter = new ValueConverter<string, string>(
               convertToProviderExpression: v => new string(v.Reverse().ToArray()), // encrypt
               convertFromProviderExpression: v => new string(v.Reverse().ToArray()) // decrypt
            );

            foreach (var entityType in builder.Model.GetEntityTypes())
            {
                foreach (var property in entityType.GetProperties())
                {
                    var attributes = property.PropertyInfo.GetCustomAttributes(typeof(EncryptedAttribute), false);
                    if (attributes.Any())
                    {
                        property.SetValueConverter(encryptedConverter);
                    }
                }
            }
        }


تاثیر ValueConverter‌ها بر روی اعمال متداول کار با بانک اطلاعاتی

از دیدگاه برنامه، ValueConverterهای تعریف شده، هیچگونه تاثیری را بر روی کوئری نوشتن و یا ثبت و ویرایش اطلاعات ندارند و عملکرد آن‌ها کاملا از دیدگاه سایر قسمت‌های برنامه مخفی است. برای مثال در برنامه، فرمان به روز رسانی خاصیت Value را با مقدار .A new value to test صادر کرده‌ایم (مقدار دهی متداول)، اما همانطور که ملاحظه می‌کنید، نمونه‌ی رمزنگاری شده‌ی آن به صورت خودکار در بانک اطلاعاتی درج شده‌است (پارامتر p0):
 Executed DbCommand (22ms) 
   [Parameters=[@p1='1', 
                @p0='.tset ot eulav wen A' (Nullable = false) (Size = 4000)],
CommandType='Text', CommandTimeout='180']
SET NOCOUNT ON;
UPDATE [Configurations] SET [Value] = @p0
WHERE [Id] = @p1;
SELECT @@ROWCOUNT;

و یا کوئری زیر
 db.Set<ConfigurationValue>().Where(x => x.Value.EndsWith("world!"))
به این نحو ترجمه خواهد شد:
SELECT [x].[Id], [x].[Key], [x].[Value]
FROM [Configurations] AS [x]
WHERE RIGHT([x].[Value], LEN(N'world!')) = N'!dlrow'
یعنی نیازی نیست تا مقداری را که در حال جستجوی آن هستیم، خودمان به صورت دستی رمزنگاری کرده و سپس در کوئری قرار دهیم. اینکار به صورت خودکار انجام می‌شود.
مطالب
بارگذاری پویای کامپوننت‌های Angular به همراه امکان Lazy loading پویای ماژول‌ها

در نسخه‌های قبل از Angular CLI 6.0، صرفا امکان Bundle کردن جداگانه‌ی ماژول‌هایی که در قسمت  loadChildren مرتبط با تنظیمات مسیریابی  ذکر شده بودند، وجود داشت. بنابراین در برخی از شرایط اگر نیاز به امکان بارگذاری ماژولی به صورت Lazy load بود، باید از سیستم مسیریابی استفاده می‌شد یا اینکه با یکسری ترفند، CLI و Webpack را مجبور به ساخت فایل chunk جداگانه برای ماژول مورد نظر می‌کردید. از زمان انتشار Angular CLI 6.0 امکان Lazy loading پویا نیز مهیا می‌باشد؛ به این ترتیب بدون وابستگی به سیستم مسیریابی، باز هم می‌توان از مزایای Lazy loading بهره برد. در این مطلب روش استفاده از این قابلیت و همچنین نحوه‌ی بارگذاری پویای یک کامپوننت مرتبط با یک ماژول Lazy load شده را بررسی خواهیم کرد. برای این منظور در ادامه با ایجاد یک TabLayout با استفاده از Angular Material Tabs با یکی از موارد پر استفاده‌ی قابلیت مذکور آشنا خواهیم شد.

پیش نیازها

کار را با طراحی و پیاده سازی TabService شروع می‌کنیم. برای این منظور یک سرویس را در فولدر services موجود در کنار CoreModule ایجاد خواهیم کرد؛ به این جهت ابتدا مدل‌های زیر را خواهیم داشت:

import { Type, ValueProvider } from '@angular/core';

export interface OpenNewTabModel {
  label: string;
  componentType: Type<any>;
  iconName: string;
  modulePath?: string;
  data?: ValueProvider[];
}
واسط تعریف شده‌ی در بالا به عنوان قرارداد مدل ورودی متد open مرتبط با سرویس TabService استفاده می‌شود. در اینجا componentType، نوع کامپوننت را مشخص می‌کند که قرار است داخل برگه‌ی جدید نمایش داده شود. modulePath هم به مسیر ماژولی که باید به صورت پویا بارگذاری شود، اشاره می‌کند. دلیل وجود خصوصیت data را نیز در ادامه خواهیم دید.
import { TabItemComponent } from './tab-item-component';

export interface TabItem {
  label: string;
  iconName: string;
  component: TabItemComponent;
}

OpenNewTabModel برای ارسال داده توسط مصرف کننده از این سرویس در نظر گرفته شده است. ولی واسط TabItem دارای خصوصیاتی می‌باشد که ما برای نمایش یک برگه‌ی جدید نیازمندیم. TabItemComponent نیز دارای خصوصیاتی است که مورد نیاز دایرکتیو« NgComponentOutlet» است. 

import { Injector, NgModuleFactory, Type } from '@angular/core';

export interface TabItemComponent {
  componentType: Type<any>;
  moduleFactory?: NgModuleFactory<any>;
  injector: Injector;
}

همانطور که اشاره شد، برای بارگذاری پویای یک کامپوننت از NgComponentOutlet استفاده خواهیم کرد؛ لذا اگر modulePath ای توسط مصرف کننده از TabService، مهیا شده باشد، لازم است ابتدا ماژول مورد نظر به صورت پویا بارگذاری شود و moduleFactory بدست آمده را به عنوان ورودی دایرکتیو مذکور ارسال کنیم. TabService، پیاده سازی به شکل زیر خواهد داشت:
import { BehaviorSubject, Observable } from 'rxjs';
import {
  Injectable,
  Injector,
  NgModuleFactory,
  NgModuleFactoryLoader
} from '@angular/core';

import { OpenNewTabModel } from '../models/open-new-tab-model';
import { TabItem } from '../models/tab-item';

@Injectable({
  providedIn: 'root'
})
export class TabService {
  private tabItemSubject: BehaviorSubject<TabItem[]> = new BehaviorSubject<
    TabItem[]
  >([]);

  constructor(
    private loader: NgModuleFactoryLoader,
    private injector: Injector
  ) {}

  get tabItems$(): Observable<TabItem[]> {
    return this.tabItemSubject.asObservable();
  }

  open(newTab: OpenNewTabModel) {
    if (newTab.modulePath) {
      this.loader
        .load(newTab.modulePath)
        .then((moduleFactory: NgModuleFactory<any>) => {
          this.openInternal(newTab, moduleFactory);
        });
    } else {
      this.openInternal(newTab);
    }
  }

  private openInternal(newTab: OpenNewTabModel, moduleFactory?: NgModuleFactory<any>) {
    const newTabItem: TabItem = {
      label: newTab.label,
      iconName: newTab.iconName,
      component: {
        componentType: newTab.componentType,
        moduleFactory: moduleFactory,
        injector: newTab.data
          ? Injector.create(newTab.data, this.injector)
          : this.injector
      }
    };

    this.tabItemSubject.getValue().push(newTabItem);
    this.tabItemSubject.next(this.tabItemSubject.getValue());
  }

  close(index: number) {
    this.tabItemSubject.getValue().splice(index, 1);
    this.tabItemSubject.next(this.tabItemSubject.getValue());
  }
}
روش کار به این شکل می‌باشد که یک مخزن، برای لیست برگه‌های درخواستی برای نمایش، تحت عنوان tabItemSubject و از نوع BehaviorSubject در نظر گرفته شده تا مصرف کننده از این سرویس که قصد نمایش برگه‌ها را دارد، از تغییرات لیست موجود آگاه شود. عموما TabsComponent، مشترک پراپرتی فقط خواندنی ‎‎‎tabItems‎$ خواهد شد و بقیه بخش‌ها صرفا دستور گشودن برگه‌ی جدید را با متد open صادر خواهند کرد.
یکی از وابستگی‌های این سرویس، وهله‌ای می‌باشد از کلاس  NgModuleFactoryLoader  که در سیستم مسیریابی نیز از همین کلاس برای بارگذاری ماژول‌ها استفاده می‌شود. البته نیاز است که یکی از پیاده سازی‌های این کلاس انتزاعی را به سیستم تزریق وابستگی‌ها نیز معرفی کنید:
{ provide: NgModuleFactoryLoader, useClass: SystemJsNgModuleLoader }
در بدنه متد open، ابتدا بررسی می‌شود که اگر modulePath مشخص شده‌است، ماژول مورد نظر ابتدا توسط متد load مرتبط با وهله NgModuleFactoryLoader به صورت پویا بارگذاری شود و سپس با استفاده از moduleFactory بدست آمده، متد openInternal فراخوانی خواهد شد.
 در بدنه متد openInternal، تنهای نکته‌ای که ذکر آن اهمیت دارد، مرتبط است به مقداردهی خصوصیت injector شیء ایجاد شده. باتوجه به اینکه تا زمان نگارش مطلب جاری امکان کار با Input‌ها و Output‌های کامپوننت مورد نظر که قرار است با استفاده از NgComponentOutlet بارگذاری شود، وجود ندارد، لذا راه حل فعلی، استفاده از سیستم تزریق وابستگی‌ها می‌باشد. برای این منظور، با استفاده از متد استاتیک create کلاس Injector یک child injector ایجاد شده و ValueProvider‌های مشخص شده توسط خصوصیت data، به صورت خودکار رجیستر خواهند شد. در نهایت آگاه سازی مشترکین خصوصیت ‎‎‎tabItems‎با استفاده از فراخوانی متد next مرتبط با tabItemSubject انجام می‌گیرد.

پیاده سازی TabsComponent
import { Component, OnInit } from '@angular/core';

import { TabService } from './../../../services/tab.service';

@Component({
  selector: 'app-tabs',
  templateUrl: './tabs.component.html',
  styleUrls: ['./tabs.component.scss']
})
export class TabsComponent implements OnInit {
  constructor(public service: TabService) {}

  ngOnInit() {}
}

همانطور که عنوان شد، مشترک اصلی خصوصیت tabItems سرویس TabService، کامپوننت تعریف شده‌ی بالا می‌باشد. قالب مرتبط با آن به شکل زیر است:
<mat-tab-group>
  <mat-tab
    *ngFor="let tabItem of (service.tabItems$ | async); let index = index"
  >
    <ng-template mat-tab-label>
      <mat-icon
        class="icon"
        aria-label="icon for tab"
      >{{tabItem.iconName}}</mat-icon>
      <span class="full">{{ tabItem.label }}</span>
    
      <mat-icon
        class="close"
        (click)="service.close(index)"
        aria-label="close tab button"
        >close</mat-icon
      >
      <!-- </button> -->
    </ng-template>

    <ng-container *ngIf="tabItem.component.moduleFactory">
      <ng-container
        *ngComponentOutlet="
          tabItem.component.componentType;
          ngModuleFactory: tabItem.component.moduleFactory;
          injector: tabItem.component.injector
        "
      >
      </ng-container>
    </ng-container>
    <ng-container *ngIf="!tabItem.component.moduleFactory">
      <ng-container
        *ngComponentOutlet="
          tabItem.component.componentType;
          injector: tabItem.component.injector
        "
      >
      </ng-container>
    </ng-container>
  </mat-tab>
</mat-tab-group>

در تکه کد بالا، ابتدا با استفاده از وهله تزریق شده TabService در کامپوننت مذکور، به شکل زیر، مشترک تغییرات لیست برگه‌ها شده‌ایم و با استفاده از دایرکتیو ‎*ngFor به ازای تک تک tabItem‌های درخواست شده برای گشوده شدن، به شکل زیر کار وهله سازی پویا از کامپوننت مشخص شده انجام می‌شود:

<ng-container *ngComponentOutlet="tabItem.component.componentType; ngModuleFactory: tabItem.component.moduleFactory; injector: tabItem.component.injector">
</ng-container>

خوب، با استفاده از آنچه تا اینجای مطلب بررسی شد، می‌توان یک سیستم راهبری مبتنی بر Tab را نیز برپا کرد که مطلب جدایی را می‌طلبد. برای تکمیل مکانیزم بارگذاری پویای ماژول‌ها، نیاز است تا مسیر ماژول مورد نظر را در فایل angular.json و بخش lazyModules به شکل زیر معرفی کنید:

"build": {
          "builder": "@angular-devkit/build-angular:browser",
          "options": {
            "outputPath": "dist/MaterialAngularTabLayout",
            "index": "src/index.html",
            "main": "src/main.ts",
            "polyfills": "src/polyfills.ts",
            "tsConfig": "src/tsconfig.app.json",
            "assets": [
              "src/favicon.ico",
              "src/assets"
            ],
            "styles": [
              "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css",
              "src/styles.scss"
            ],
            "lazyModules": [
              "src/app/lazy/lazy.module"
            ],
            "scripts": []
          },

به عنوان مثال قصد داریم ماژول LazyModule را به صورت پویا بارگذاری کرده و LazyComponent موجود در این ماژول را به صورت پویا در برگه‌ی جدیدی نمایش دهیم. برای این منظور کدهای فایل AppComponent.ts را به شکل زیر تغییر خواهیم داد:

import { Component } from '@angular/core';
import { IdModel } from './core/models/id-model';
import { LazyComponent } from './lazy/lazy.component';
import { OpenNewTabModel } from './core/models/open-new-tab-model';
import { TabService } from './core/services/tab.service';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss']
})
export class AppComponent {
  title = 'MaterialAngularTabLayout';
  constructor(private tabService: TabService) {}
  loadLazyComponent() {
    this.tabService.open(<OpenNewTabModel>{
      label: 'Loaded Lazy Component',
      iconName: 'thumb_up',
      componentType: LazyComponent,
      modulePath: 'src/app/lazy/lazy.module#LazyModule',
      data: [{ provide: IdModel, useValue: <IdModel>{ id: 1 } }]
    });
  }
}

در تکه کد بالا با تزریق TabService به سازنده‌ی آن، قصد گشودن برگه‌ی جدیدی را توسط متد open آن، داریم. در بدنه‌ی متد loadLazyComponent یک شیء با قرارداد OpenNewTabModel ایجاد شده و به عنوان آرگومان به متد open ارسال شده است. توجه داشته باشید که modulePath اینجا نیز به مانند خصوصیت loadChildren مرتبط با اشیاء مسیریابی، باید مقدار دهی شود. همچنین قصد داشتیم اطلاعاتی را نیز به کامپوننت مورد نظر ارسال کنیم؛ همانند مکانیزم مسیریابی که با پارامترها این چنین کارهایی صورت می‌پذیرد. در اینجا از یک کلاس به شکل زیر استفاده شده‌است:

export class IdModel {
  constructor(public id: number) {}
}

در این صورت پیاده سازی LazyComponent نیز به شکل زیر خواهد بود:

import { Component, OnInit } from '@angular/core';

import { IdModel } from './../core/models/id-model';

@Component({
  selector: 'app-lazy',
  templateUrl: './lazy.component.html',
  styleUrls: ['./lazy.component.scss']
})
export class LazyComponent implements OnInit {
  constructor(private model: IdModel) {}

  ngOnInit() {
    console.log(this.model);
  }
}

البته فراموش نکنید کامپوننتی را که نیاز است به صورت پویا بارگذاری شود، در قسمت entryComponents مرتبط با NgModule متناظر به شکل نیز معرفی کنید:

import { CommonModule } from '@angular/common';
import { LazyComponent } from './lazy.component';
import { NgModule } from '@angular/core';

@NgModule({
  imports: [CommonModule],
  declarations: [LazyComponent],
  entryComponents: [LazyComponent]
})
export class LazyModule {}

با خروجی زیر:

و chunk تولید شده برای ماژول مورد نظر:


در صورتیکه در حالت production پروژه را بیلد کنید، هش مرتبط برای chunk تولید شده نیز ایجاد خواهد شد.


کدهای کامل این قسمت را می‌توانید از اینجا دریافت کنید.
نظرات مطالب
مباحث تکمیلی مدل‌های خود ارجاع دهنده در EF Code first
علت این است که p.Children به تمام خواص عمومی شیء BlogComment اشاره می‌کند؛ این رو هم میشه یک سطح دیگر با Projection سبک‌تر کرد و یا بجای Projection در حالت شما ساده‌تر است که JsonIgnore را روی تمام خواصی قرار دهید که نباید توسط JSON.NET بررسی شود. با توجه به lazy loading، این خواص virtual توسط EF در بدو امر بارگذاری نمی‌شوند و همچنین چون توسط JSON.NET به دلیل JsonIgnore معرفی شدن واکاوی مجدد نخواهند شد، بنابراین مشکلی از لحاظ کارآیی یا حجم بالای خروجی نخواهید داشت.
بازخوردهای دوره
استفاده از AOP Interceptors برای حذف کدهای تکراری کش کردن اطلاعات در لایه سرویس برنامه
با سلام
من از ورژن structuremap 3.2 استفاده می‌کنم.ولی  متدی  بنام EnrichWith نداره  متدی که مشاهد میشه DecorateAllWith  می‌باشه. ممنون میشم فرق این دوتا متد  رو  توضیح بفرمایید.
نظرات مطالب
NHibernate 3 Beginners Guide
تمرکز اصلی آن روی FHN است. خلاصه‌ای از ConfOrm رو هم گفته. البته می‌دونید که NH 3.2 قسمتی از ConfOrm رو به ارث برده، بنابراین یادگیری آن مفید است. اشاره‌ای هم به سیستم تعریف نگاشت XML ایی متداول داشته.
نظرات اشتراک‌ها
بررسی آینده‌ی Blazor در دات نت 8
خلاصه‌ی این مفهوم «United» به این صورت است:
- blazor server از لحاظ عدم نیاز به دریافت چند صد فایل مرتبط با web assembly و همراه بودن با رندر سمت سرور، در ابتدای کار نمایش برنامه، سریعتر نمایش داده می‌شود و این نمایش اولیه تقریبا آنی است.
- Blazor WASM چون به طور کامل در مرورگر اجرا می‌شود و در جهت رندر صفحات نیازی به رفت و برگشت به سرور ندارد، سرعت اجرایی فوق العاده‌ای دارد؛ اما به همراه بارگذاری تعداد زیادی فایل در ابتدای کار نمایش آن است که ... کمی طول می‌کشد که البته می‌توان با استفاده از فعال سازی per-rendering کمی آن‌را سریعتر کرد که نیاز به تنظیمات قابل توجهی دارد که شاید همه از آن استفاده نکنند.
اکنون قرار است در دات نت 8 بتوان به صورت خودکار ابتدا یک صفحه را با استفاده از server side rendering موجود در Blazor Server، سریع نمایش داد (بدون نیاز به دریافت فایل‌های WASM و منتظر شدن برای آن‌ها) و بعد در همان حال در پشت صحنه و به صورت خودکار، همین برنامه با قالب Blazor web assembly هم دریافت می‌شود تا در بار بعدی که این صفحه قرار است نمایش داده شود، بتوان به حداکثر سرعت کار با Blazor، با استفاده از اجرای کامل آن در مرورگر توسط web assembly رسید. یعنی ترکیب همزمان blazor server و blazor web assembly، بدون نیاز به تنظیمات خاصی در جهت از پیش رندر کردن صفحات و یا منتظر ماندن اولیه و نمایش یک صفحه‌ی خالی در جهت بارگذاری تمام فایل‌های یک برنامه‌ی web assembly؛ هر دو با هم به صورت یکپارچه و یک‌دست. حتی می‌توان مشخص کرد که صفحه‌ای فقط بر اساس blazor server اجرا شود و صفحه‌ای دیگر فقط قسمتی از آن از blazor wasm استفاده کند.