مطالب
مروری مقدماتی بر ساخت برنامه‌های موبایل در MVC4
برای شروع، پروژه‌ای از نوع InternetApplication را انتخاب نموده مطابق شکل زیر:


 سپس اگر صفحه Layout.cshtml_ باز کنید، با متا تگی به فرم زیر روبرو می‌شوید که توسط آن می‌توانیم اندازه صفحه نمایش را مشخص کنیم:


زمانیکه Request ای صادر می‌شود، موارد ذیل تشکیل خواهند شد:


اگر دقت کنید در قسمت User-Agent یکسری مشخصات از سیستم نمایان است که ما طبق آن میتوانیم صفحه مربوطه را بارگذاری کنیم.
سپس می‌بایست به فایل Global.asax  رفته و تنظیماتی را در Application Start اعمال کنیم:
       protected void Application_Start() 
            {
            AreaRegistration.RegisterAllAreas();

            WebApiConfig.Register(GlobalConfiguration.Configuration);
            FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
            RouteConfig.RegisterRoutes(RouteTable.Routes);
            BundleConfig.RegisterBundles(BundleTable.Bundles);
            AuthConfig.RegisterAuth(); 
            InitializeDisplayModeProvider(); 
        }
        protected void InitializeDisplayModeProvider()
        {
            var phone = new DefaultDisplayMode("Phone")
                {
                    ContextCondition = ctx => ctx.GetOverriddenUserAgent() != null &&
                                                           ctx.GetOverriddenUserAgent().Contains("iPhone")
                };
            var mobile = new DefaultDisplayMode("Tablet")
                {
                    ContextCondition = ctx => ctx.GetOverriddenUserAgent() != null && 
                                                           ctx.GetOverriddenUserAgent().Contains("iPad")
                };

            DisplayModeProvider.Instance.Modes.Insert(0, phone);
            DisplayModeProvider.Instance.Modes.Insert(1, mobile);
        }
توسط متد InitialDisplayModeProvider  دو متغیر را تعریف می‌کنیم که بواسط آنها می‌توان مثلا UserAgent ای را که می‌خواهیم، مشخص کنیم و سپس در DisplayModelProvider آن را بعنوان Instance ای درج و سپس تابع InitialDisplayModeProvider را در Application_start تنظیم کنیم.
حال می‌بایست Page‌های مربوط به هر کدام را طراحی کرده و با پسوند مثلا (Phone-Tablet)ذخیره کنیم.

اگر Extension ای را برای تست شبیه سازی محیط موبایل یا تبلت، نصب کنید، می‌توانید آن را مشاهده کنید.
حال می‌توان برای شکیل‌تر کردن آن از نیوگت jquerymobile را گرفته و طبق مطالب گفته شده از صفحه Layout خود هم چند نمونه برای مرورگرهای مختلف درست کرده وبا jquerymobile روی آن کار کنید.
مطالب
یکی کردن اسمبلی‌های یک پروژه‌ی WPF
فرض کنید پروژه‌ی WPF شما از چندین پروژه‌ی ‍Class library و اسمبلی‌های جانبی دیگر، تشکیل شده‌است. اکنون نیاز است جهت سهولت توزیع آن، تمام این فایل‌ها را با هم یکی کرده و تبدیل به یک فایل EXE نهایی کنیم. مایکروسافت ابزاری را به نام ILMerge، برای یک چنین کارهایی تدارک دیده‌است؛ اما این برنامه با WPF سازگار نیست. در ادامه قصد داریم اسمبلی‌های جانبی را تبدیل به منابع مدفون شده در فایل EXE برنامه کرده و سپس آن‌ها را در اولین بار اجرای برنامه، به صورت خودکار بارگذاری و در برنامه مورد استفاده قرار دهیم.

یک مثال جهت بازتولید کدهای این مطلب
الف) یک پروژه‌ی WPF جدید را به نام MergeAssembliesIntoWPF ایجاد کنید.
ب) یک پروژه‌ی Class library جدید را به نام MergeAssembliesIntoWPF.ViewModels به این Solution اضافه کنید. از آن برای تعریف ViewModelهای برنامه استفاده خواهیم کرد.
برای نمونه کلاس ذیل را به آن اضافه کنید:
namespace MergeAssembliesIntoWPF.ViewModels
{
    public class ViewModel1
    {
        public string Data { set; get; }

        public ViewModel1()
        {
            Data = "Test";
        }
    }
}
ج) یک پروژه‌ی WPF User control library را نیز به نام MergeAssembliesIntoWPF.Shell به این Solution اضافه کنید. از آن برای تعریف Viewهای برنامه کمک خواهیم گرفت.
به این پروژه ارجاعی را به اسمبلی قسمت (ب) اضافه نموده و برای نمونه User control ذیل را به نام View1.xaml به آن اضافه نمائید:
<UserControl x:Class="MergeAssembliesIntoWPF.Shell.View1"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             mc:Ignorable="d" 
             xmlns:VM="clr-namespace:MergeAssembliesIntoWPF.ViewModels;assembly=MergeAssembliesIntoWPF.ViewModels"
             d:DesignHeight="300" d:DesignWidth="300">
    <UserControl.Resources>
        <VM:ViewModel1 x:Key="ViewModel1" />
    </UserControl.Resources>
    <Grid DataContext="{Binding Source={StaticResource ViewModel1}}">
        <TextBlock Text="{Binding Data}" />
    </Grid>
</UserControl>
در پروژه اصلی Solution (قسمت الف)، ارجاعاتی را به دو اسمبلی قسمت‌های ب و ج اضافه کنید. سپس MainWindow.xaml آن‌را به نحو ذیل تغییر داده و برنامه را اجرا کنید:
<Window x:Class="MergeAssembliesIntoWPF.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:V="clr-namespace:MergeAssembliesIntoWPF.Shell;assembly=MergeAssembliesIntoWPF.Shell"
        Title="MainWindow" Height="350" Width="525">
    <Window.Resources>
        <V:View1 x:Key="View1" />
    </Window.Resources>
    <Grid>
        <V:View1 />
    </Grid>
</Window>
تا اینجا باید متن Test در پنجره اصلی برنامه ظاهر شود.


ب) مدفون کردن خودکار اسمبلی‌های جانبی برنامه در فایل EXE آن
فایل csproj پروژه اصلی را خارج از VS.NET باز کنید. در انتهای آن سطر ذیل قابل مشاهده است:
 <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
پس از این سطر، چند سطر ذیل را اضافه کنید:
  <Target Name="AfterResolveReferences">
    <ItemGroup>
      <EmbeddedResource Include="@(ReferenceCopyLocalPaths)" Condition="'%(ReferenceCopyLocalPaths.Extension)' == '.dll'">
        <LogicalName>%(ReferenceCopyLocalPaths.DestinationSubDirectory)%(ReferenceCopyLocalPaths.Filename)%(ReferenceCopyLocalPaths.Extension)</LogicalName>
      </EmbeddedResource>
    </ItemGroup>
  </Target>
این task جدید MSBuild سبب خواهد شد تا با هر بار Build برنامه، اسمبلی‌هایی که در ارجاعات برنامه دارای خاصیت Copy local مساوی true هستند، به صورت خودکار به صورت یک resource جدید در فایل exe برنامه مدفون شوند. عموما ارجاعاتی که دستی اضافه می‌شوند، مانند دو اسمبلی یاد شده در ابتدای بحث، دارای خاصیت Copy local=true نیز هستند.
پس از این تغییر نیاز است یکبار پروژه را بسته و مجددا باز کنید. اکنون پروژه را build کنید و جهت اطمینان بیشتر آن‌را برای مثال توسط ILSpy مورد بررسی قرار دهید:


همانطور که مشاهده می‌کنید، دو اسمبلی مورد استفاده در برنامه به صورت خودکار در قسمت منابع فایل EXE مدفون شده‌اند.
اگر به مسیر LogicalName تنظیمات فوق دقت کنید، DestinationSubDirectory نیز ذکر شده‌است. علت این است که بسیاری از اسمبلی‌های بومی سازی شده WPF با نام‌هایی یکسان اما در پوشه‌هایی مانند fa، fr و امثال آن ذخیره می‌شوند. به همین جهت نیاز است بین این‌ها تمایز قائل شد.


ج) بارگذاری خودکار اسمبلی‌ها در AppDomain برنامه

تا اینجا اسمبلی‌های جانبی را در فایل EXE مدفون کرده‌ایم. اکنون نوبت به بارگذاری آن‌ها در AppDomain برنامه است. برای اینکار نیاز است تا روال رخدادگردان AppDomain.CurrentDomain.AssemblyResolve را تحت نظر قرار داده و اسمبلی‌هایی را که برنامه درخواست می‌کند، در همینجا از منابع خوانده و به AppDomain اضافه کرد.
انجام اینکار در برنامه‌های WinForms ساده‌است. فقط کافی است به متد Program.Main برنامه مراجعه کرده و تعریف یاد شده را به ابتدای متد Main اضافه کرد. اما در WPF هرچند فایل App.xaml.cs به نظر نقطه‌ی آغازین برنامه است، اما در واقع اینطور نیست. برای نمونه، پوشه‌ی obj\Debug برنامه را گشوده و فایل App.g.i.cs آن‌را بررسی کنید. در اینجا می‌توانید همان رویه شبیه به برنامه‌های WinForm را در متد Program.Main آن، مشاهده کنید. بنابراین نیاز است کنترل این مساله را راسا در دست بگیریم:
using System;
using System.Globalization;
using System.Reflection;

namespace MergeAssembliesIntoWPF
{
    public class Program
    {
        [STAThreadAttribute]
        public static void Main()
        {
            AppDomain.CurrentDomain.AssemblyResolve += OnResolveAssembly;
            App.Main();
        }

        private static Assembly OnResolveAssembly(object sender, ResolveEventArgs args)
        {
            var executingAssembly = Assembly.GetExecutingAssembly();
            var assemblyName = new AssemblyName(args.Name);

            var path = assemblyName.Name + ".dll";
            if (assemblyName.CultureInfo.Equals(CultureInfo.InvariantCulture) == false)
            {
                path = String.Format(@"{0}\{1}", assemblyName.CultureInfo, path);
            }

            using (var stream = executingAssembly.GetManifestResourceStream(path))
            {
                if (stream == null)
                    return null;

                var assemblyRawBytes = new byte[stream.Length];
                stream.Read(assemblyRawBytes, 0, assemblyRawBytes.Length);
                return Assembly.Load(assemblyRawBytes);
            }
        }
    }
}
کلاس Program را با تعاریف فوق به پروژه خود اضافه نمائید. در اینجا Program.Main مورد نیاز خود را تدارک دیده‌ایم. کار آن مدیریت روال رخدادگردان AppDomain.CurrentDomain.AssemblyResolve برنامه پیش از شروع به هر کاری است. در روال رخداد گردان OnResolveAssembly، برنامه اعلام می‌کند که به چه اسمبلی خاصی نیاز دارد. ما آن‌را از قسمت منابع خوانده و سپس توسط متد Assembly.Load آن‌را در AppDomain برنامه بارگذاری می‌کنیم.
پس از اینکه کلاس فوق را اضافه کردید، نیاز است کلاس Program اضافه شده را به عنوان Startup object برنامه نیز معرفی کنید:

انجام اینکار ضروری است؛ در غیراینصورت با متد Main موجود در فایل App.g.i.cs تداخل می‌کند.
اکنون برای آزمایش برنامه، یکبار آن‌را Build کرده و بجز فایل Exe، مابقی فایل‌های موجود در پوشه‌ی bin را حذف کنید. سپس برنامه را خارج از VS.NET اجرا کنید. کار می‌کند!
MergeAssembliesIntoWPF.zip
 
مطالب
فارسی نویسی با SkiaSharp
تا نگارش 4x دات نت که فقط از ویندوز پشتیبانی می‌کند، از وابستگی System.Drawing.Common برای انجام امور روزمره‌ی گرافیکی استفاده می‌شد؛ چون در پشت صحنه، محصور کننده‌ی امکانات بومی گرافیکی ویندوز است. همچنین از زمان ارائه‌ی دات نت Core چندسکویی، تا نگارش 5 دات نت، این وابستگی، در لینوکس، به کمک کتابخانه‌ی جانبی به نام libgdiplus پشتیبانی می‌شد که البته هیچگاه پشتیبانی رسمی را از طرف مایکروسافت پیدا نکرد؛ چون libgdiplus متشکل از چند ده‌هزار سطر کد نوشته شده‌ی به زبان C است که به‌خوبی آزمایش نشده و همچنین برای کارکرد کامل آن نیز به کتابخانه‌های جانبی دیگری مانند pango نیاز است تا برای مثال از نمایش متون فارسی پشتیبانی کند. Libgdiplus در حقیقت بازمانده‌ای از دوران Mono است که مایکروسافت در نگارش 6 دات نت، آن‌را منسوخ شده اعلام کرد و در نگارش 7 دات نت، دیگر از آن پشتیبانی نمی‌کند. یعنی تمام برنامه‌هایی که از وابستگی System.Drawing.Common استفاده می‌کنند، قابل انتقال به دات نت 7 چندسکویی نیستند؛ البته هنوز هم می‌توان از System.Drawing.Common در ویندوز، بدون مشکل استفاده کرد. اما در صورت استفاده‌، برنامه‌ی شما در لینوکس اجرا نخواهد شد و یک چنین برنامه‌هایی با استثناهای TypeInitializationException و PlatformNotSupportedException در زمان اجرا، خاتمه خواهند یافت.
در حال حاضر توصیه‌ی مایکروسافت ، عدم استفاده‌ی از System.Drawing.Common و جایگزینی آن با یکی از کتابخانه‌های زیر است:
- SkiaSharp
- Microsoft.Maui.Graphics

البته پیشتر در این لیست توصیه شده، کتابخانه‌ی SixLabors.ImageSharp.Drawing هم وجود داشت که به علت تغییر مجوز آن، به یک مجوز نیمه تجاری، نیمه سورس باز، از لیست فوق حذف شده‌است.


مشکل فارسی نویسی با SkiaSharp

اگر سعی کنیم با استفاده از مثال‌های متداول SkiaSharp، یک متن فارسی را نمایش دهیم، به خروجی زیر خواهیم رسید:
// crate a surface
var info = new SKImageInfo(256, 256);
using var surface = SKSurface.Create(info);
// the the canvas and properties
var canvas = surface.Canvas;

// make sure the canvas is blank
canvas.Clear(SKColors.White);

// draw some text
using var typeface = SKTypeface.FromFamilyName("Tahoma");
using var paint = new SKPaint
    {
      Color = SKColors.Black,
      IsAntialias = true,
      Style = SKPaintStyle.Fill,
      TextAlign = SKTextAlign.Center,
      TextSize = 24,
      Typeface = typeface,
    };
var coord = new SKPoint(info.Width / 2, (info.Height + paint.TextSize) / 2);
canvas.DrawText("آزمایش", coord, paint);

// save the file
using var image = surface.Snapshot();
using var data = image.Encode(SKEncodedImageFormat.Png, 100);
using var stream = File.OpenWrite("farsi-text-1.png");
data.SaveTo(stream);
قطعه کد فوق برای اجرا، نیاز به وابستگی زیر را دارد:
<ItemGroup>
   <PackageReference Include="SkiaSharp" Version="2.88.3" />
</ItemGroup>
که در آن، در ابتدا یک Canvas برای نقاشی ایجاد شده و سپس متنی بر روی آن نمایش داده می‌شود و در آخر این نتیجه را در یک فایل ذخیره می‌کنیم؛ با این خروجی:

همانطور که مشاهده می‌کنید، حروف فارسی در آن از هم جدا هستند و همچنین از چپ به راست نمایش داده شده‌است.


رفع مشکل فارسی نویسی با SkiaSharp

برای رفع مشکل فوق، نیاز است از افزونه‌ی «حرف باز» این کتابخانه استفاده کرد که روش نصب آن به صورت زیر است:
<ItemGroup>
   <PackageReference Include="SkiaSharp" Version="2.88.3" />
   <PackageReference Include="SkiaSharp.HarfBuzz" Version="2.88.3" />
</ItemGroup>
اینبار تنها تفاوت مورد نیاز جهت نمایش صحیح حروف فارسی، استفاده از SKShaper جهت شکل دادن به متن نهایی است و استفاده از متد DrawShapedText آن به صورت زیر:
// crate a surface
var info = new SKImageInfo(256, 256);
using var surface = SKSurface.Create(info);
// the the canvas and properties
var canvas = surface.Canvas;

// make sure the canvas is blank
canvas.Clear(SKColors.White);

// draw some text
using var typeface = SKTypeface.FromFamilyName("Tahoma");
using var shaper = new SKShaper(typeface);
using var paint = new SKPaint
  {
      Color = SKColors.Black,
      IsAntialias = true,
      Style = SKPaintStyle.Fill,
      TextAlign = SKTextAlign.Center,
      TextSize = 24,
      Typeface = typeface,
  };
var coord = new SKPoint(info.Width / 2, (info.Height + paint.TextSize) / 2);
canvas.DrawShapedText(shaper, "آزمایش", coord, paint);

// save the file
using var image = surface.Snapshot();
using var data = image.Encode(SKEncodedImageFormat.Png, 100);
using var stream = File.OpenWrite("farsi-text-2.png");
data.SaveTo(stream);
که خروجی صحیح زیر را تولید می‌کند:

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


کدهای سمت سرور دریافت تصویر

در اینجا کدهای سمت سرور برنامه، نکته‌ی خاصی را به همراه نداشته و صرفا یک تصویر ساده (محتوای باینری) را بازگشت می‌دهد:
using Microsoft.AspNetCore.Mvc;

namespace AngularTemplateDrivenFormsLab.Controllers
{
    [Route("api/[controller]")]
    public class ShowImagesController : Controller
    {
        [HttpGet("[action]")]
        public IActionResult GetImage()
        {
            return File("~/assets/resume.png", "image/png");
        }
    }
}
که در نهایت با آدرس api/ShowImages/GetImage در سمت کلاینت قابل دسترسی خواهد بود.


سرویس دریافت محتوای باینری در برنامه‌های Angular

برای اینکه HttpClient برنامه‌های Angular بتواند محتوای باینری را بجای محتوای JSON پیش‌فرض آن دریافت کند، نیاز است نوع خروجی سمت سرور آن‌را به blob تنظیم کرد:
import { Injectable } from "@angular/core";
import { Observable } from "rxjs/Observable";
import { HttpClient } from "@angular/common/http";

@Injectable()
export class DownloadBinaryDataService {

  constructor(private httpClient: HttpClient) { }

  public getImage(): Observable<Blob> {
    return this.httpClient.get("/api/ShowImages/GetImage", { responseType: "blob" });
  }
}
به این ترتیب پس از اشتراک به متد getImage این سرویس، اطلاعات باینری این تصویر را دریافت خواهیم کرد.


ساخت URL برای دسترسی به اطلاعات باینری

تمام مرورگرهای جدید از ایجاد URL برای اشیاء Blob دریافتی از سمت سرور، توسط متد توکار URL.createObjectURL پشتیبانی می‌کنند. این متد، شیء URL را از شیء window جاری دریافت می‌کند و سپس اطلاعات باینری را دریافت کرده و آدرسی را جهت دسترسی موقت به آن تولید می‌کند.
بنابراین ابتدا سرویس جدیدی را در مسیر src\app\core\window.service.ts جهت دسترسی به این شیء پنجره ایجاد می‌کنیم:
import { Injectable } from "@angular/core";

function getWindow(): any {
  return window;
}

@Injectable()
export class WindowRefService {
  get nativeWindow(): any {
    return getWindow();
  }
}
هدف این است که در برنامه مستقیما با شیء window کار نکنیم و این سرویس تامین کننده‌ی آن باشد.
چون این سرویس، یک سرویس سراسری است، آن‌را در قسمت providers مربوط به CoreModule ثبت خواهیم کرد تا در تمام برنامه قابل دسترسی شود:
import { WindowRefService } from "./window.service";

@NgModule({
  providers: [
    WindowRefService
  ]
})
export class CoreModule {}
اکنون هر قسمتی از برنامه که می‌خواهد برای دسترسی به این تصویر و نمایش آن، آدرسی از آن‌را داشته باشد، باید به صورت ذیل عمل کند:
const urlCreator = this.nativeWindow.URL;
imageBlobUrl = urlCreator.createObjectURL(imageDataBlob);


اصلاح Content Security Policy سمت سرور جهت نمایش تصاویر blob

زمانیکه از متد createObjectURL استفاده می‌شود، یک نمونه آدرس تولیدی آن چنین شکلی را پیدا می‌کند:
<img src="blob:http://localhost:5000/9d4bae44-00bc-4ed8-9f27-cac2de5ecd5d">
در این حالت در Content Security Policy سمت سرور، نیاز است امکان دسترسی به تصاویر از نوع blob را نیز آزاد معرفی کنید:
 img-src 'self' data: blob:
در غیراینصورت مرورگر نمایش یک چنین تصاویری را سد خواهد کرد.


دریافت تصویر از سرور و نمایش آن در برنامه‌ی Angular

پس از این مقدمات، کامپوننتی که یک تصویر را از سمت سرور دریافت کرده و نمایش می‌دهد، چنین کدی را خواهد داشت:
import { WindowRefService } from "./../../core/window.service";
import { DownloadBinaryDataService } from "app/angular-http-client-blob/download-binary-data.service";
import { Component, OnInit, ViewChild, ElementRef } from "@angular/core";
import { DomSanitizer, SafeUrl } from "@angular/platform-browser";

@Component({
  templateUrl: "./download-blobs.component.html",
  styleUrls: ["./download-blobs.component.css"]
})
export class DownloadBlobsComponent implements OnInit {

  @ViewChild("sampleImage1") sampleImage1: ElementRef;
  private nativeWindow: Window;
  imageBlobUrl: string;
  sanitizedImageBlobUrl: SafeUrl;

  constructor(private downloadService: DownloadBinaryDataService,
    private windowRefService: WindowRefService, private sanitizer: DomSanitizer) { }

  ngOnInit() {
    this.nativeWindow = this.windowRefService.nativeWindow;
    this.downloadService.getImage().subscribe(imageDataBlob => {
      const urlCreator = this.nativeWindow.URL;
      this.imageBlobUrl = urlCreator.createObjectURL(imageDataBlob); // doesn't work -> <img [src]="imageBlobUrl">
      this.sampleImage1.nativeElement.src = this.imageBlobUrl; // works -> <img #sampleImage1>
      this.sanitizedImageBlobUrl = this.sanitizer.bypassSecurityTrustUrl(this.imageBlobUrl); // works -> <img [src]="sanitizedImageBlobUrl">
    });
  }
}
با این قالب:
<h1>Angular HttpClient Blob</h1>

<h4>#sampleImage1</h4>
<img #sampleImage1>

<h4>imageBlobUrl</h4>
<img [src]="imageBlobUrl">

<h4>sanitizedImageBlobUrl</h4>
<img [src]="sanitizedImageBlobUrl">
در اینجا در ngOnInit، به سرویس پنجره دسترسی یافته و وهله‌ای از آن‌را جهت کار با متد createObjectURL شیء URL آن دریافت می‌کنیم.
سپس مشترک متد getImage دریافت تصویر شده و اطلاعات نهایی آن‌را به صورت imageDataBlob دریافت می‌کنیم.
این اطلاعات باینری را به متد createObjectURL ارسال کرده و آدرس موقتی این تصویر را در مرورگر بدست می‌آوریم.

در ادامه سه روش کار با این URL نهایی را بررسی کرده‌ایم:
- دسترسی مستقیم به imageBlobUrl
this.imageBlobUrl = urlCreator.createObjectURL(imageDataBlob); // doesn't work -> <img [src]="imageBlobUrl">
و سپس انتساب آن به خاصیت src یک تصویر در قالب این کامپوننت:
<h4>imageBlobUrl</h4>
<img [src]="imageBlobUrl">
چون در این حالت Angular این URL را امن سازی می‌کند، یک چنین خروجی unsafe:blob بجای blob تولید خواهد شد:
<img _ngcontent-c1="" src="unsafe:blob:http://localhost:5000/a4505339-5da2-4303-949c-8e6a7cfff2fc">
که این مورد نیز توسط مرورگر با خطای ذیل سد می‌شود:
Refused to load the image 'unsafe:blob:http://localhost:5000/a4505339-5da2-4303-949c-8e6a7cfff2fc' 
because it violates the following Content Security Policy directive: "img-src 'self' data: blob:".

- برای رفع این مشکل، می‌توان از سرویس DomSanitizer آن که به سازنده‌ی کلاس تزریق شده‌است استفاده کرد:
this.sanitizedImageBlobUrl = this.sanitizer.bypassSecurityTrustUrl(this.imageBlobUrl); // works -> <img [src]="sanitizedImageBlobUrl">
اینبار یک چنین انتسابی به صورت مستقیم کار می‌کند:
<img [src]="sanitizedImageBlobUrl">
چون خروجی آن دیگر unsafe:blob نیست:
<img _ngcontent-c1="" src="blob:http://localhost:5000/a4505339-5da2-4303-949c-8e6a7cfff2fc">

- روش دیگر نمایش این تصویر، انتساب این آدرس به شیء بومی این تصویر است. برای اینکار در قالب این کامپوننت، یک template reference variable را به img نسبت می‌دهیم:
<img #sampleImage1>
سپس در کامپوننت جاری، توسط تعریف یک ViewChild، می‌توان به این متغیر دسترسی یافت:
@ViewChild("sampleImage1") sampleImage1: ElementRef;
اکنون که دسترسی مستقیمی را به این شیء پیدا کرده‌ایم، نحوه‌ی مقدار دهی خاصیت src آن به صورت ذیل خواهد بود:
this.sampleImage1.nativeElement.src = this.imageBlobUrl; // works -> <img #sampleImage1>

در نهایت Angular یک چنین خروجی را برای نمایش اینگونه تصاویر، بر اساس کدهای فوق رندر می‌کند:
<ng-component _nghost-c1=""><h1 _ngcontent-c1="">Angular HttpClient Blob</h1>

<h4 _ngcontent-c1="">#sampleImage1</h4>
<img _ngcontent-c1="" src="blob:http://localhost:5000/a4505339-5da2-4303-949c-8e6a7cfff2fc">

<h4 _ngcontent-c1="">imageBlobUrl</h4>
<img _ngcontent-c1="" src="unsafe:blob:http://localhost:5000/a4505339-5da2-4303-949c-8e6a7cfff2fc">

<h4 _ngcontent-c1="">sanitizedImageBlobUrl</h4>
<img _ngcontent-c1="" src="blob:http://localhost:5000/a4505339-5da2-4303-949c-8e6a7cfff2fc">
</ng-component>



کدهای کامل این مطلب را از اینجا می‌توانید دریافت کنید.
مطالب دوره‌ها
آشنایی با AOP Interceptors
در حین استفاده از Interceptors، کار مداخله و تحت نظر قرار دادن قسمت‌های مختلف کدها، توسط کامپوننت‌های خارجی صورت خواهد گرفت. این کامپوننت‌های خارجی، به صورت پویا، تزئین کننده‌هایی را جهت محصور سازی قسمت‌های مختلف کدهای شما تولید می‌کنند. این‌ها، بسته به توانایی‌هایی که دارند، در زمان اجرا و یا حتی در زمان کامپایل نیز قابل تنظیم می‌باشند.


ابزارهایی جهت تولید AOP Interceptors

متداول‌ترین کامپوننت‌های خارجی که جهت تولید AOP Interceptors مورد استفاده قرار می‌گیرند، همان IOC Containers معروف هستند مانند StructureMap، Ninject، MS Unity و غیره.
سایر ابزارهای تولید AOP Interceptors، از روش تولید Dynamic proxies بهره می‌گیرند. به این ترتیب مزین کننده‌هایی پویا، در زمان اجرا، کدهای شما را محصور خواهند کرد. (نمونه‌ای از آن‌را شاید در حین کار با ORMهای مختلف دیده باشید).


نگاهی به فرآیند Interception

زمانیکه از یک IOC Container در کدهای خود استفاده می‌کنید، مراحلی چند رخ خواهند داد:
الف) کد فراخوان، از IOC Container، یک شیء مشخص را درخواست می‌کند. عموما اینکار با درخواست یک اینترفیس صورت می‌گیرد؛ هرچند محدودیتی نیز وجود نداشته و امکان درخواست یک کلاس از نوعی مشخص نیز وجود دارد.
ب) در ادامه IOC Container به لیست اشیاء قابل ارائه توسط خود نگاه کرده و در صورت وجود، وهله سازی شیء درخواست شده را انجام و نهایتا شیء مطلوب را بازگشت خواهد داد.
ج) سپس، کد فراخوان، وهله دریافتی را مورد پردازش قرار داده و شروع به استفاده از متدها و خواص آن خواهد نمود.

اکنون با اضافه کردن Interception به این پروسه، چند مرحله دیگر نیز در این بین به آن اضافه خواهند شد:
الف) در اینجا نیز در ابتدا کد فراخوان، درخواست وهله‌ای را بر اساس اینترفیسی خاص به IOC Container ارائه می‌دهد.
ب) IOC Container نیز سعی در وهله سازی درخواست رسیده بر اساس تنظیمات اولیه خود می‌کند.
ج) اما در این حالت IOC Container تشخیص می‌دهد، نوعی که باید بازگشت دهد، علاوه بر وهله سازی، نیاز به مزین سازی توسط  Aspects و پیاده سازی Interceptors را نیز دارد. بنابراین نوع مورد انتظار را در صورت وجود، به یک Dynamic Proxy، بجای بازگشت مستقیم به فراخوان ارائه می‌دهد.
د) در  ادامه Dynamic Proxy، نوع مورد انتظار را توسط Interceptors محصور کرده و به فراخوان بازگشت می‌دهد.
ه) اکنون فراخوان، در حین استفاده از امکانات شیء وهله سازی شده، به صورت خودکار مراحل مختلف اجرای یک Aspect را که در قسمت قبل بررسی شدند، سبب خواهد شد.


نحوه ایجاد Interceptors

برای ایجاد یک Interceptor دو مرحله باید انجام شود:
الف) پیاده سازی یک اینترفیس
ب) اتصال آن به کدهای اصلی برنامه

در ادامه قصد داریم از یک IOC Container معروف به نام StructureMap در یک برنامه کنسول استفاده کنیم. برای دریافت آن نیاز است دستور پاورشل ذیل را در کنسول نوگت ویژوال استودیو فراخوانی کنید:
 PM> Install-Package structuremap
پس از آن یک برنامه کنسول جدید را ایجاد کنید. (هدف از استفاده از این نوع پروژه خاص، توضیح جزئیات یک فناوری، بدون درگیر شدن با لایه UI است)
البته باید دقت داشت که برای استفاده از StructureMap نیاز است به خواص پروژه مراجعه و سپس حالت Client profile را به Full profile تغییر داد تا برنامه قابل کامپایل باشد.
using System;
using StructureMap;

namespace AOP00
{
    public interface IMyType
    {
        void DoSomething(string data, int i);
    }

    public class MyType : IMyType
    {
        public void DoSomething(string data, int i)
        {
            Console.WriteLine("DoSomething({0}, {1});", data, i);
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            ObjectFactory.Initialize(x =>
            {
                x.For<IMyType>().Use<MyType>();
            });

            var myType = ObjectFactory.GetInstance<IMyType>();
            myType.DoSomething("Test", 1);
        }
    }
}
اکنون کدهای این برنامه را به نحو فوق تغییر دهید.
در اینجا یک اینترفیس نمونه و پیاده سازی آن‌را ملاحظه می‌کنید. همچنین نحوه آغاز تنظیمات StructureMap و نحوه دریافت یک وهله متناظر با IMyType نیز بیان شده‌اند.
نکته‌ی مهمی که در اینجا باید به آن دقت داشت، وضعیت شیء myType حین فراخوانی متد myType.DoSomething است. شیء myType در اینجا، دقیقا یک وهله‌ی متداول از کلاس myType است و هیچگونه دخل و تصرفی در نحوه اجرای آن صورت نگرفته است.
خوب! تا اینجای کار را احتمالا پیشتر نیز دیده بودید. در ادامه قصد داریم یک Interceptor را طراحی و مراحل چهارگانه اجرای یک Aspect را در اینجا بررسی کنیم.

در ادامه نیاز خواهیم داشت تا یک Dynamic proxy را نیز مورد استفاده قرار دهیم؛ از این جهت که StructureMap تنها دارای Interceptorهای وهله سازی اطلاعات است و نه Method Interceptor. برای دسترسی به Method Interceptors نیاز به یک Dynamic proxy نیز می‌باشد. در اینجا از Castle.Core استفاده خواهیم کرد:
 PM> Install-Package Castle.Core
برای دریافت آن تنها کافی است دستور پاور شل فوق را در خط فرمان کنسول پاورشل نوگت در VS.NET اجرا کنید.
سپس کلاس ذیل را به پروژه جاری اضافه کنید:
using System;
using Castle.DynamicProxy;

namespace AOP00
{
    public class LoggingInterceptor : IInterceptor
    {
        public void Intercept(IInvocation invocation)
        {
            try
            {
                Console.WriteLine("Logging On Start.");

                invocation.Proceed(); //فراخوانی متد اصلی در اینجا صورت می‌گیرد

                Console.WriteLine("Logging On Success.");
            }
            catch (Exception ex)
            {
                Console.WriteLine("Logging On Error.");
                throw;
            }
            finally
            {
                Console.WriteLine("Logging On Exit.");
            }
        }
    }
}
در کلاس فوق کار Method Interception توسط امکانات Castle.Core انجام شده است. این کلاس باید اینترفیس IInterceptor را پیاده سازی کند. در این متد سطر invocation.Proceed دقیقا معادل فراخوانی متد مورد نظر است. مراحل چهارگانه شروع، پایان، خطا و موفقیت نیز توسط try/catch/finally پیاده سازی شده‌اند.

اکنون برای معرفی این کلاس به برنامه کافی است سطرهای ذیل را اندکی ویرایش کنیم:
        static void Main(string[] args)
        {
            ObjectFactory.Initialize(x =>
            {
                var dynamicProxy = new ProxyGenerator();
                x.For<IMyType>().Use<MyType>();
                x.For<IMyType>().EnrichAllWith(myTypeInterface => dynamicProxy.CreateInterfaceProxyWithTarget(myTypeInterface, new LoggingInterceptor()));
            });

            var myType = ObjectFactory.GetInstance<IMyType>();
            myType.DoSomething("Test", 1);
        }
در اینجا تنها سطر EnrichAllWith آن جدید است. ابتدا یک پروکسی پویا تولید شده است. سپس این پروکسی پویا کار دخالت و تحت نظر قرار دادن اجرای متدهای اینترفیس IMyType را عهده دار خواهد شد.
برای مثال اکنون با فراخوانی متد myType.DoSomething، ابتدا کنترل برنامه به پروکسی پویای تشکیل شده توسط Castle.Core منتقل می‌شود. در اینجا هنوز هم متد DoSomething فراخوانی نشده است. ابتدا وارد بدنه متد public void Intercept خواهیم شد. سپس سطر invocation.Proceed، فراخوانی واقعی متد DoSomething اصلی را انجام می‌دهد. در ادامه باز هم فرصت داریم تا مراحل موفقیت، خطا یا خروج را لاگ کنیم.
تنها زمانیکه کار متد public void Intercept به پایان می‌رسد، سطر پس از فراخوانی متد  myType.DoSomething اجرا خواهد شد.
در این حالت اگر برنامه را اجرا کنیم، چنین خروجی را نمایش می‌دهد:
 Logging On Start.
DoSomething(Test, 1);
Logging On Success.
Logging On Exit.
بنابراین در اینجا نحوه دخالت و تحت نظر قرار دادن اجرای متدهای یک کلاس عمومی خاص را ملاحظه می‌کنید. برای اینکه کنترل کامل را در دست بگیریم، کلاس پروکسی پویا وارد عمل شده و اینجا است که این کلاس پروکسی تصمیم می‌گیرد چه زمانی باید فراخوانی واقعی متد مورد نظر انجام شود.
برای اینکه فراخوانی قسمت On Error را نیز ملاحظه کنید، یک استثنای عمدی را در متد DoSomething قرار داده و مجددا برنامه را اجرا کنید.
مطالب
شروع به کار با EF Core 1.0 - قسمت 11 - بررسی رابطه‌ی Self Referencing
پیشنیازها
- بررسی نحوه تعریف نگاشت جداول خود ارجاع دهنده (Self Referencing Entity)
- مباحث تکمیلی مدل‌های خود ارجاع دهنده در EF Code first
- آشنایی با SQL Server Common Table Expressions - CTE
- بدست آوردن برگهای یک درخت توسط Recursive CTE


در پیشنیازهای بحث، روش تعریف روابط خود ارجاع دهنده و یا Self Referencing را تا EF 6.x می‌توانید مطالعه کنید. در این قسمت قصد داریم معادل این روش‌ها را در EF Core بررسی کنیم.


روش تعریف روابط خود ارجاع دهنده توسط Fluent API در EF Core

اگر همان مدل کامنت‌های چندسطحی یک بلاگ را درنظر بگیریم:
    public class BlogComment
    {
        public int Id { get; set; }

        public string Body { get; set; }

        public DateTime Date1 { get; set; }


        public virtual BlogComment Reply { get; set; }
        public int? ReplyId { get; set; }

        public virtual ICollection<BlogComment> Children { get; set; }
    }
اینبار تنظیمات Fluent API معادل EF Core آن به صورت ذیل خواهد بود:
    public class MyDBDataContext : DbContext
    {
        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            optionsBuilder.UseSqlServer(@"Data Source=(local);Initial Catalog=testdb2;Integrated Security = true");
        }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<BlogComment>(entity =>
            {
                entity.HasIndex(e => e.ReplyId);

                entity.HasOne(d => d.Reply)
                    .WithMany(p => p.Children)
                    .HasForeignKey(d => d.ReplyId);
            });
        }

        public virtual DbSet<BlogComment> BlogComments { get; set; }
    }
هدف از مدل‌های خود ارجاع دهنده، مدلسازی اطلاعات چند سطحی است؛ مانند منوهای چندسطحی یک سایت، کامنت‌های چند سطحی یک مطلب، سلسله مراتب کارمندان یک شرکت و امثال آن.
نکته‌ها‌ی مهم مدلسازی این نوع روابط، موارد ذیل هستند:
الف) وجود خاصیتی از جنس کلاس اصلی در همان کلاس
   public virtual BlogComment Reply { get; set; }
ب) که در حقیقت مشخص می‌کند، والد رکورد جاری کدام رکورد قبلی است:
   public int? ReplyId { get; set; }
برای اینکه چنین رابطه‌ای تشکیل شود، باید این فیلد، مقدارش را از کلید اصلی جدول تامین کند (تشکیل یک کلید خارجی که به کلید اصلی جدول اشاره می‌کند).
علت نال پذیر بودن این خاصیت، نیاز به ثبت ریشه‌ای است که خودش والد است و فرزند رکوردی پیشین، نیست.


ج) همچنین برای سهولت دریافت فرزندان یک ریشه، مجموعه‌ای از جنس همان کلاس نیز تشکیل می‌شود.
 public virtual ICollection<BlogComment> Children { get; set; }


روش تعریف روابط خود ارجاع دهنده توسط Data Annotations در EF Core

در ادامه معادل تنظیمات فوق را توسط ویژگی‌ها ملاحظه می‌کنید که در اینجا توسط ویژگی‌های InverseProperty و ForeignKey، کار تشکیل روابط صورت گرفته‌است:
    public class BlogComment
    {
        public int Id { get; set; }
        public string Body { get; set; }
        public DateTime Date1 { get; set; }

        [ForeignKey("ReplyId")]
        [InverseProperty("Children")]
        public virtual BlogComment Reply { get; set; }
        public int? ReplyId { get; set; }

        [InverseProperty("Reply")]
        public virtual ICollection<BlogComment> Children { get; set; }
    }
البته قسمت تشکیل ایندکس بر روی ReplyId را فقط توسط Fluent API می‌توان انجام داد:
        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<BlogComment>(entity =>
            {
                entity.HasIndex(e => e.ReplyId);
            });
        }


ثبت اطلاعات و کوئری گرفتن از روابط خود ارجاع دهنده در EF Core

در اینجا نحوه‌ی ثبت دو سری نظر و زیر نظر را مشاهده می‌کنید:
var comment1 = new BlogComment { Body = "نظر من این است که" };
var comment12 = new BlogComment { Body = "پاسخی به نظر اول", Reply = comment1 };
var comment121 = new BlogComment { Body = "پاسخی به پاسخ به نظر اول", Reply = comment12 };

context.BlogComments.Add(comment121);

var comment2 = new BlogComment { Body = "نظر من این بود که" };
var comment22 = new BlogComment { Body = "پاسخی به نظر قبلی", Reply = comment2 };
var comment221 = new BlogComment { Body = "پاسخی به پاسخ به نظر من اول", Reply = comment22 };
context.BlogComments.Add(comment221);

context.SaveChanges();


نظرات اصلی، با ReplyId مساوی نال قابل تشخیص هستند. سایر نظرات، توسط همین ReplyId به یکی از Idهای موجود، متصل شده‌اند.

در تصویر فوق و با توجه به اطلاعات ثبت شده، فرض کنید می‌خواهیم ریشه‌ی با id مساوی 1 و تمام زیر ریشه‌های آن‌را بیابیم. انجام یک چنین کاری نه در EF Core و نه در EF 6.x، پشتیبانی نمی‌شود. بدیهی است در اینجا هنوز روش‌های Include و ThenInclue هم جواب می‌دهند؛ اما چون Lazy loading فعال نیست، عملا نمی‌توان تمام زیر ریشه‌ها را یافت و همچنین به چندین و چند رفت و برگشت به ازای هر زیر ریشه خواهیم رسید که اصلا بهینه نیست.
برای اینکار نیاز است مستقیما کوئری نویسی کرد که در مطلب «شروع به کار با EF Core 1.0 - قسمت 10 - استفاده از امکانات بومی بانک‌های اطلاعاتی» زیر ساخت آن‌را بررسی کردیم:
var id = 1;
var childIds = new List<int>();
 
var connection = context.Database.GetDbConnection();
connection.Open();
var command = connection.CreateCommand();
command.CommandText =
    @"WITH Hierachy(ChildId, ParentId) AS (
                SELECT Id, ReplyId
                    FROM BlogComments
                UNION ALL
                SELECT h.ChildId, bc.ReplyId
                    FROM BlogComments bc
                    INNER JOIN Hierachy h ON bc.Id = h.ParentId
            )
 
            SELECT h.ChildId
                FROM Hierachy h
                WHERE h.ParentId = @Id";
command.Parameters.Add(new SqlParameter("id", id));
 
var reader = command.ExecuteReader();
while (reader.Read())
{
    var childId = (int)reader[0];
    childIds.Add(childId);
}
reader.Dispose();

 
var list = context.BlogComments
                .Where(blogComment => blogComment.Id == id ||
                                      childIds.Contains(blogComment.Id))
                .ToList();
زمانیکه کدهای فوق اجرا می‌شوند، تنها دو کوئری ذیل به بانک اطلاعاتی ارسال خواهند شد:
exec sp_executesql N'WITH Hierachy(ChildId, ParentId) AS (
                            SELECT Id, ReplyId
                                FROM BlogComments
                            UNION ALL
                            SELECT h.ChildId, bc.ReplyId
                                FROM BlogComments bc
                                INNER JOIN Hierachy h ON bc.Id = h.ParentId
                        )

                        SELECT h.ChildId
                            FROM Hierachy h
                            WHERE h.ParentId = @Id',N'@id int',@id=1
که لیست Idهای تمام زیر ریشه‌های مربوط به id مساوی یک را بر می‌گرداند:


و پس از آن:
 exec sp_executesql N'SELECT [blogComment].[Id], [blogComment].[Body], [blogComment].[Date1], [blogComment].[ReplyId]
FROM [BlogComments] AS [blogComment]
WHERE [blogComment].[Id] IN (@__id_0, 3, 5)',N'@__id_0 int',@__id_0=1
اکنون که Idهای مساوی 3 و 5 را یافتیم، با استفاده از متد Contains آن‌ها را تبدیل به where in کرده و لیست نهایی گزارش را تهیه می‌کنیم:


یافتن Idهای زیر ریشه‌ها توسط روش CTE (پیشنیازهای ابتدای بحث) و سپس کوئری گرفتن بر روی این Idها، بهینه‌ترین روشی‌است که در EF می‌توان جهت کار با مدل‌های خود ارجاع دهنده بکار گرفت.
مطالب
توسعه سرویس‌های مبتنی بر REST در AngularJs با استفاده از RestAngular : بخش دوم
در بخش پیشین  کلیات کتابخانه‌ی Restangular  را بررسی کردیم. در این بخش قصد داریم تا در طی یک پروژه، امکانات و قابلیت‌های بی‌نظیر این سرویس را در یک پروژه‌ی واقعی مشاهده کنیم.

کلیات پروژه

در این پروژه قصد داریم تا لیست کتاب‌های یک کتابخانه را نمایش دهیم. این کتابها قابلیت ویرایش نام دارند و همچنین شما می‌توانید کتابهای جدیدی را به لیست کتابها اضافه نمایید. تصویر زیر خروجی این پروژه است:

پایگاه داده‌ی برنامه با نام LibDb درون پوشه‌ی app_data قرار داده شده‌است. این پایگاه داده تنها دارای یک جدول است؛ با نام Books که دیاگرام آن را در شکل زیر مشاهده می‌کنید:


پیاده سازی

در ابتدا یک پروژه‌ی Empty را با رفرنس web API ایجاد می‌کنیم. حال درون فایل WebApiConfig.cs تکه کد زیر را اضافه می‌کنیم. این تکه کد فیلدها و آبجکت‌هایی را که از سمت سرور بازگشت داده می‌شوند، به صورت camelCase تبدیل می‌کند.
var jsonFormatter = config.Formatters.OfType<JsonMediaTypeFormatter>().First();

jsonFormatter.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
در ادامه مدل edmx را ساخته و پس از آن Books Web Api را درون پوشه‌ی کنترلر ایجاد می‌کنیم. این کنترلر به صورت Default web Api Scaffold ساخته شده است و به دلیل اینکه به بحث ما مرتبط نیست؛ از توضیح بیشتر آن می‌گذریم.
حال پوشه‌ای را با نام app برای نگه داری فایل‌های AngularJs اضافه می‌کنیم و درون آن فایل app.js را ایجاد می‌کنیم:
var angularExample = angular.module('angularexample', ["restangular"])
angularExample.config(["RestangularProvider", function (RestangularProvider) {
    //RestangularProvider.setRestangularFields({
    //    id: "id"
    //});
    RestangularProvider.setBaseUrl('/api');
}]);

angularExample.controller("MainCtrl", ["Restangular", "$scope", function (Restangular, $scope) {
    var resource = Restangular.all('books');
    resource.getList().then(function (response) {
        $scope.books = response;
    });
    $scope.add = function () {
        resource.post($scope.newBook).then(function (newResource) {
            $scope.books.push(newResource);
        })
    }
}]);
محتویات فایل app.js را مشاهده می‌کنید. در ادامه درباره‌ی این قسمت بیشتر صحبت می‌کنیم.
حال در روت پروژه فایل index.html را ایجاد می‌کنیم:
<!DOCTYPE html>
<html ng-app="angularexample" xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title>Rest Angular Sample</title>
    <script src="//ajax.googleapis.com/ajax/libs/angularjs/1.1.5/angular.min.js"></script>
    <script src="http://cdn.jsdelivr.net/underscorejs/1.5.1/underscore-min.js"></script>
    <script src="/Scripts/restangular.min.js"></script>
    <script src="/app/app.js"></script>
</head>
<body>
    <div ng-controller="MainCtrl">
        <div ng-repeat="item in books">
            name is: {{item.name}}<br />
            change name: <input type="text" ng-model="item.name" /><button type="submit" ng-click="item.put();">update</button>
        </div>
        <div>
            add new: <br />
            <input type="text" ng-model="newBook.name" /><button type="submit" ng-click="add()">add</button>
        </div>
    </div>
</body>
و در نهایت در پوشه‌ی Scripts فایل‌های سرویس Restangular را قرار می‌دهیم.
تا به اینجای کار تمامی کارهای مورد نیاز تشکیل پروژه را انجام داده‌ایم. حال به بررسی بیشتر سرویس‌های این پروژه می‌پردازیم؛ یعنی کدهای درون فایل app.js.
ببینیم که برای دریافت لیست تمامی کتابها، ما چه کارهایی را انجام داده‌ایم! به تکه کد زیر دقت کنید:
    var resource = Restangular.all('books');
    resource.getList().then(function (response) {
        $scope.books = response;
    });
این تمامی آن چیزی است که ما برای دریافت لیست تمامی کتابها انجام داده‌ایم. به همین سادگی! در خط اول از متد all که در بخش قبل توضیح مختصری راجع به آن داده بودم برای دریافت لیست تمامی کتابها استفاده کردم که پارامتر درون آن آدرس Web Api است. البته همانطور که می‌دانید در ASP.NET Web Api ما همیشه به base address یک api نیز اضافه می‌کنیم. حال اینکه چرا api در اینجا نیامده در ادامه و در بخش تنظیمات کلی Restangular توضیح می‌دهم. در خط دوم نیز از اشاره‌گر resources متد getList را فراخوانی کرده و لیست کتابها را در response دریافت می‌کنیم.
پس از آن می‌خواهیم ببینیم که عملیات ایجاد یک کتاب جدید چگونه انجام می‌گردد. تکه کد زیر این عملیات را انجام می‌دهد:
    $scope.add = function () {
        resource.post($scope.newBook).then(function (newResource) {
            $scope.books.push(newResource);
        })
    }
ما یک متد با نام add ایجاد کرده‌ایم که در سمت View توسط دایرکتیو ng-click آن را فراخوانی می‌کنیم.
همانطور که مشاهده میکنید درون app.js متدی برای update موارد قبلی نیست. بیایید سری به View بزنیم. به المنت div زیر توجه کنید. همانطور که می‌بینید تمامی عملیات update با یک دستور ساده item.put حل و فصل شده.
        <div ng-repeat="item in books">
            name is: {{item.name}}<br />
            change name: <input type="text" ng-model="item.name" /><button type="submit" ng-click="item.put();">update</button>
        </div>
تمامی آنچه که گفته شد تنها بخشی از قابلیت‌های شگفت انگیز این افزونه بود. امیدوارم که مطلب مفید واقع شده باشد. سورس این پروژه را می‌توانید از لینک زیر دریافت کنید.

سورس پروژه: RestangularSample.rar  

مطالب
سرعت ایجاد اشیاء CLR

به نظر شما چه تعداد شیء CLR را می‌توان در یک ثانیه ایجاد کرد؟
برنامه کنسول زیر دو نسخه معمولی و نسخه پردازش موازی یک آزمایش ساده را برای اندازه گیری این مطلب ارائه می‌دهد:
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;

namespace ObjectInitSpeedTest
{
class Program
{
//Note: don't forget to build it in Release mode.
static void Main()
{
normalSpeedTest();
parallelSpeedTest();

Console.ForegroundColor = ConsoleColor.White;
Console.WriteLine("Press a key ...");
Console.ReadKey();
}

private static void parallelSpeedTest()
{
Console.ForegroundColor = ConsoleColor.Yellow;
Console.WriteLine("parallelSpeedTest");

long totalObjectsCreated = 0;
long totalElapsedTime = 0;

var tasks = new List<Task>();
var processorCount = Environment.ProcessorCount;

Console.WriteLine("Running on {0} cores", processorCount);

for (var t = 0; t < processorCount; t++)
{
tasks.Add(Task.Factory.StartNew(
() =>
{
const int reps = 1000000000;
var sp = Stopwatch.StartNew();
for (var j = 0; j < reps; ++j)
{
new object();
}
sp.Stop();

Interlocked.Add(ref totalObjectsCreated, reps);
Interlocked.Add(ref totalElapsedTime, sp.ElapsedMilliseconds);
}
));
}

// let's complete all the tasks
Task.WaitAll(tasks.ToArray());

Console.WriteLine("Created {0:N} objects in 1 sec\n", (totalObjectsCreated / (totalElapsedTime / processorCount)) * 1000);
}

private static void normalSpeedTest()
{
Console.ForegroundColor = ConsoleColor.Green;
Console.WriteLine("normalSpeedTest");

const int reps = 1000000000;
var sp = Stopwatch.StartNew();
sp.Start();
for (var j = 0; j < reps; ++j)
{
new object();
}
sp.Stop();

Console.WriteLine("Created {0:N} objects in 1 sec\n", (reps / sp.ElapsedMilliseconds) * 1000);
}
}
}

هنگام اجرای برنامه فراموش نکنید که حالت Build را بر روی Release قرار دهید.



مطالب دوره‌ها
کوئری نویسی مقدماتی در RavenDB
با شروع کوئری نویسی مقدماتی در RavenDB، در قسمت اول این مباحث، توسط فراخوانی متد Load یک سشن، آشنا شدید. در ادامه مباحث تکمیلی آن‌را مرور خواهیم کرد.

امکان استفاده از LINQ در RavenDB

RavenDB از LINQ جهت کوئری نویسی پشتیبانی می‌کند. برای استفاده از آن، در ادامه مطلب اول، ابتدا سرور RavenDB را اجرا نموده و سپس برنامه کنسول را به نحو ذیل تغییر دهید:
using System;
using System.Linq;
using Raven.Client.Document;
using RavenDBSample01.Models;

namespace RavenDBSample01
{
    class Program
    {
        static void Main(string[] args)
        {
            using (var store = new DocumentStore
            {
                Url = "http://localhost:8080"
            }.Initialize())
            {
                using (var session = store.OpenSession())
                {
                    var questions = session.Query<Question>().Where(x => x.Title.StartsWith("Raven"));
                    foreach (var question in questions)
                    {
                        Console.WriteLine(question.Title);
                    }
                }
            }
        }
    }
}
در RavenDB برای دسترسی به امکانات LINQ، کار با متد Query یک سشن آغاز می‌شود و پس از آن، امکان استفاده از متدهای متداول LINQ مانند مثال فوق وجود خواهد داشت. البته بدیهی است مباحثی مانند JOIN و امثال آن در یک بانک اطلاعاتی NoSQL پشتیبانی نمی‌شود. ضمنا باید درنظر داشت که مبحث safe by default در اینجا نیز اعمال می‌شود. برای مثال اگر به کنسول سرور RavenDB که در حال اجرا است مراجعه کنید، یک چنین خروجی را حین اجرای مثال فوق می‌توان مشاهده کرد که در آن pageSize پیش فرضی اعمال شده است:
Available commands: cls, reset, gc, q
Request #   1: GET     -   179 ms - <system>   - 404 - /docs/Raven/Replication/Destinations
Request #   2: GET     - 3,818 ms - <system>   - 200 - /indexes/dynamic/Questions?&query=Title%3ARaven*&pageSize=128
        Query: Title:Raven*
        Time: 3,494 ms
        Index: Auto/Questions/ByTitle
        Results: 2 returned out of 2 total.
یعنی در عمل کوئری‌را که اجرا کرده است، شبیه به کوئری ذیل می‌باشد و یک Take پیش فرض بر روی آن اعمال شده است:
var questions = session.Query<Question>().Where(x => x.Title.StartsWith("Raven")).Take(128);
علت این مساله نیز به تصمیم نویسنده اصلی آن بر می‌گردد؛ ایشان پیش از شروع به تهیه RavenDB، کار تهیه انواع و اقسام پروفایلرهای مهم ORMهای معروف مانند NHibernate و Entity framework را انجام داده است و در این حین، یکی از مهم‌ترین مشکلاتی را که با آن‌ها در کدهای متداول برنامه نویس‌ها یافته است، unbounded queries است. کوئری‌هایی که حد و مرزی برای بازگشت اطلاعات قائل نمی‌شوند. داشتن این نوع کوئری‌ها با تعداد بالای کاربر، یعنی مصرف بیش از حد RAM بر روی سرور، به همراه بار پردازشی بیش از حد و غیر ضروری. چون عملا حتی اگر 10 هزار رکورد بازگشت داده شوند، عموم برنامه نویس‌ها حداکثر 100 رکورد آن‌را در یک صفحه نمایش می‌دهند و نه تمام رکوردها را.


ارتباط Lucene.NET و RavenDB

کل LINQ API تهیه شده در RavenDB یک محصور کننده امکانات Lucene.NET است. اگر پیشتر با Lucene.NET کار کرده باشید، در خروجی حالت دیباگ کنسول سرور فوق، سطر «Query: Title:Raven*» آشنا به نظر خواهد رسید. دقیقا کوئری LINQ نوشته شده به یک کوئری با Syntax مخصوص Lucene.NET ترجمه شده‌است. برای نمونه اگر علاقمند باشید که مستقیما کوئری‌های خاص لوسین را در RavenDB اجرا کنید، از Syntax ذیل می‌توان استفاده کرد:
var questions = session.Advanced.LuceneQuery<Question>().Where("Title:Raven*").ToList();
و یا اگر علاقمند به حفظ کردن Syntax خاص لوسین نیستید، یک سری متد الحاقی خاص نیز در اینجا برای LuceneQuery تدارک دیده شده است. برای مثال کوئری رشته‌ای فوق، معادل کوئری strongly typed ذیل است:
var questions = session.Advanced.LuceneQuery<Question>().WhereStartsWith(x => x.Title, "Raven").ToList();

استفاده مجدد از کوئری‌ها در RavenDB

در RavenDB، متد Query به صورت immutable تعریف شده است و متد LuceneQuery حالت mutable دارد (ترکیبات آن نیز یک وهله است).
یک مثال:
var query = session.Query<User>().Where(x => x.Name.StartsWith("A"));
var ageQuery = query.Where(x => x.Age > 21);
var eyeQuery = query.Where(x => x.EyeColor == "blue");
در اینجا از کوئری ابتدایی، در دو کوئری مجزا استفاده مجدد شده است. ترجمه خروجی سه کوئری فوق به نحو زیر است:
query - Name:A*
ageQuery - (Name:A*) AND (Age_Range:{Ix21 TO NULL})
eyeQuery - (Name:A*) AND (EyeColor:blue)
به این معنا که زمانیکه به eyeQuery رسیدیم، نتیجه ageQuery با آن ترکیب نمی‌شود؛ چون متد Query از نوع immutable است.
در ادامه اگر همین سه کوئری فوق را با فرمت LuceneQuery تهیه کنیم، به عبارات ذیل خواهیم رسید:
var luceneQuery = session.Advanced.LuceneQuery<User>().WhereStartsWith(x => x.Name, "A");
var ageLuceneQuery = luceneQuery.WhereGreaterThan(x => x.Age, 21);
var eyeLuceneQuery = luceneQuery.WhereEquals(x => x.EyeColor, "blue");
در خروجی‌های این سه کوئری، مورد سوم مهم است:
luceneQuery - Name:A* 
ageLuceneQuery - Name:A* Age_Range:{Ix21 TO NULL}
eyeLuceneQuery - Name:A* Age_Range:{Ix21 TO NULL} EyeColor:blue
همانطور که مشاهده می‌کنید، کوئری سوم، عبارت کوئری دوم را نیز به همراه دارد؛ این مورد دقیقا مفهوم اشیاء mutable یا تک وهله‌ای است مانند LuceneQuery در اینجا.


And و Or شدن کوئری‌های ترکیبی در RavenDB
در مثال استفاده مجدد از کوئری‌ها، زمانیکه از Where استفاده شد، بین عبارات حاصل AND قرار گرفته است. این مورد را به نحو ذیل می‌توان تنظیم کرد و مثلا به OR تغییر داد:
session.Advanced.LuceneQuery<User>().UsingDefaultOperator(QueryOperator.And);

صفحه بندی اطلاعات در RavenDB

در ابتدای بحث عنوان شد که کوئری LINQ اجرا شده در RavenDB، یک Take مخفی و پیش فرض تنظیم شده به 128 آیتم را دارد. اکنون سؤال این خواهد بود که چگونه می‌توان اطلاعات را به صورت صفحه بندی شده، بر اساس شماره صفحه خاصی نمایش داد.
using System;
using System.Linq;
using Raven.Client.Document;
using RavenDBSample01.Models;

namespace RavenDBSample01
{
    class Program
    {
        static void Main(string[] args)
        {
            using (var store = new DocumentStore
            {
                Url = "http://localhost:8080"
            }.Initialize())
            {
                using (var session = store.OpenSession())
                {
                    int pageNumber = 0;
                    int resultsPerPage = 2;

                    var questions = session.Query<Question>()
                                           .Where(x => x.Title.StartsWith("Raven"))
                                           .Skip(pageNumber * resultsPerPage)
                                           .Take(resultsPerPage);
                    foreach (var question in questions)
                    {
                        Console.WriteLine(question.Title);
                    }
                }
            }
        }
    }
}
برای انجام صفحه بندی در RavenDB، کافی است از متدهای Skip و Take بر اساس محاسباتی که مشاهده می‌کنید، استفاده گردد.


دریافت اطلاعات آماری کوئری اجرا شده

در RavenDB امکان دریافت یک سری اطلاعات آماری از کوئری اجرا شده نیز وجود دارد؛ برای مثال یک کوئری چند ثانیه طول کشیده است، چه تعدادی رکورد را بازگشت داده است و امثال آن. برای پیاده سازی آن، نیاز است از متد الحاقی Statistics به نحو ذیل استفاده کرد:
using System;
using System.Linq;
using Raven.Client.Document;
using RavenDBSample01.Models;
using Raven.Client;

namespace RavenDBSample01
{
    class Program
    {
        static void Main(string[] args)
        {
            using (var store = new DocumentStore
            {
                Url = "http://localhost:8080"
            }.Initialize())
            {
                using (var session = store.OpenSession())
                {
                    int pageNumber = 0;
                    int resultsPerPage = 2;
                    RavenQueryStatistics stats;
                    var questions = session.Query<Question>()
                                           .Statistics(out stats)
                                           .Where(x => x.Title.StartsWith("Raven"))
                                           .Skip(pageNumber * resultsPerPage)
                                           .Take(resultsPerPage);
                    foreach (var question in questions)
                    {
                        Console.WriteLine(question.Title);
                    }

                    Console.WriteLine("TotalResults: {0}", stats.TotalResults);
                }
            }
        }
    }
}
متد الحاقی Statistics پس از متد Query که نقطه آغازین نوشتن کوئری‌های LINQ است، فراخوانی شده و یک پارامتر out از نوع RavenQueryStatistics تعریف شده در فضای نام Raven.Client را دریافت می‌کند. پس از پایان کوئری می‌توان از این خروجی جهت نمایش اطلاعات آماری کوئری استفاده کرد.


امکانات ویژه فضای نام Raven.Client.Linq

یک سری متد الحاقی خاص جهت تهیه ساده‌تر کوئری‌های LINQ در فضای نام Raven.Client.Linq قرار دارند که پس از تعریف آن قابل دسترسی خواهند بود:
var list = session.Query<Question>().Where(q => q.By.In<string>(arrayOfUsers))).ToArray()
برای مثال در اینجا متد الحاقی جدید In را مشاهده می‌کنید که شبیه به کوئری SQL ذیل در دنیای بانک‌های اطلاعاتی رابطه‌ای عمل می‌کند:
 SELECT * FROM tbl WHERE data IN (1, 2, 3)

اتصال به RavenDB با استفاده از برنامه معروف LINQPad

اگر علاقمند باشید که کوئری‌های خود را در محیط برنامه معروف LINQPad نیز آزمایش کنید، درایور مخصوص RavenDB آن‌را از آدرس ذیل می‌توانید دریافت نمائید:
مطالب دوره‌ها
ایجاد یک اسمبلی جدید توسط Reflection.Emit
مطابق استاندارد ECMA-335 قسمت دوم آن، یک اسمبلی از یک یا چند ماژول تشکیل می‌شود. هر ماژول از تعدادی نوع، enum و delegate تشکیل خواهد شد و هر نوع دارای تعدادی متد، فیلد، خاصیت و غیره می‌باشد. به همین جهت در حین کار با Reflection.Emit نیز این مراحل رعایت می‌شوند. ابتدا یک اسمبلی (AppDomain.DefineDynamicAssembly) ایجاد خواهد شد (یا از اسمبلی موجود استفاده می‌شود). سپس یک ماژول (AssemblyBuilder.DefineDynamicModule) را باید به آن اضافه کنیم (یا از ماژول اسمبلی جاری استفاده نمائیم). در ادامه یک Type باید به این ماژول اضافه شود (ModuleBuilder.DefineType) و اکنون می‌توان به این نوع جدید، سازنده (TypeBuilder.DefineConstructor)، متد (TypeBuilder.DefineMethod)، فیلد (TypeBuilder.DefineField)، خاصیت (TypeBuilder.DefineProperty) و رخداد (TypeBuilder.DefineEvent) اضافه کرد.


using System;
using System.Reflection;
using System.Reflection.Emit;

namespace FastReflectionTests
{
    class Program
    {
        static void Main(string[] args)
        {
            var name = "HelloWorld.exe";
            var assemblyName = new AssemblyName(name);
            // ایجاد یک اسمبلی جدید با قابلیت ذخیره سازی آن
            var assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(
                                            name: assemblyName,
                                            access: AssemblyBuilderAccess.RunAndSave);
            // افزودن یک ماژول به اسمبلی
            var moduleBuilder = assemblyBuilder.DefineDynamicModule(name);
            // تعریف یک کلاس در این ماژول
            var programmClass = moduleBuilder.DefineType("Program", TypeAttributes.Public);
            // افزودن یک متد به این کلاس
            // این متد خروجی ندارد اما ورودی آن شبیه به متد اصلی یک برنامه کنسول است
            var mainMethod = programmClass.DefineMethod(name: "Main",
                                            attributes: MethodAttributes.Public | MethodAttributes.Static,
                                            returnType: null,
                                            parameterTypes: new Type[] { typeof(string[]) });
            // تعیین بدنه متد اصلی برنامه
            var il = mainMethod.GetILGenerator();
            il.Emit(OpCodes.Ldstr, "Hello World!");
            il.Emit(OpCodes.Call, (typeof(Console)).GetMethod("WriteLine", new Type[] { typeof(string) }));
            il.Emit(OpCodes.Call, (typeof(Console)).GetMethod("ReadKey", new Type[0]));
            il.Emit(OpCodes.Pop);
            il.Emit(OpCodes.Ret);

            // تکمیل کار ایجاد نوع جدید
            programmClass.CreateType();

            // تعیین نقطه شروع فایل اجرایی برنامه کنسول تهیه شده
            assemblyBuilder.SetEntryPoint(((Type)programmClass).GetMethod("Main"));

            // ذخیره سازی این اسمبلی بر روی دیسک سخت
            assemblyBuilder.Save(name);
        }
    }
}
مراحلی را که توضیح داده شد، در کدهای فوق ملاحظه می‌کنید. انتخاب حالت دسترسی AssemblyBuilderAccess.RunAndSave سبب می‌شود تا بتوان نتیجه حاصل را ذخیره کرد. فایل Exe نهایی را اگر در برنامه ILSpy باز کنیم چنین شکلی دارد:
using System;
public class Program
{
    public static void Main(string[] array)
    {
       Console.WriteLine("Hello World!");
       Console.ReadKey();
    }
}