مطالب
بررسی Source Generators در #C - قسمت دوم - یک مثال
یک مثال: پیاده سازی INotifyPropertyChanged توسط Source Generators

هدف از اینترفیس INotifyPropertyChanged که به همراه یک رخ‌داد است:
public interface INotifyPropertyChanged  
{ 
   event PropertyChangedEventHandler PropertyChanged;  
}
مطلع سازی استفاده کننده‌ی از یک شیء، از تغییرات رخ‌داده‌ی در مقادیر خواص آن است که نمونه‌ی آن، در برنامه‌های WPF، جهت به روز رسانی UI، زیاد مورد استفاده قرار می‌گیرد. البته این رخ‌داد به خودی خود کار خاصی را انجام نمی‌دهد و برای استفاده‌ی از آن، باید مقدار زیادی کد نوشت و این مقدار کد نیز باید به ازای تک تک خواص یک کلاس مدل، تکرار شوند:
  partial class CarModel : INotifyPropertyChanged
  {

    private double _speedKmPerHour;
    
    public double SpeedKmPerHour
    {
      get => _speedKmPerHour;
      set
      {
        _speedKmPerHour = value;
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(SpeedKmPerHour)));
      }
    }

    public event PropertyChangedEventHandler? PropertyChanged;
  }
همچنین باید درنظر داشت که با تغییر نام خاصیتی، میزان قابل ملاحظه‌ای از این کدهای تکراری نیز باید به روز رسانی شوند که این عملیات می‌تواند ایده‌ی خوبی برای استفاده‌ی از Source Generators باشد.
اگر بخواهیم تولید این کدهای تکراری را به Source Generators محول کنیم، می‌توان برای مثال فیلد خصوصی مرتبط را نگه داشت و تولید مابقی کدها را خودکار کرد:
  partial class CarModel : INotifyPropertyChanged
  {
    private double _speedKmPerHour;    
  }
در این حالت کلاس مدل، به صورت partial تعریف می‌شود و فقط فیلد خصوصی، در کدهای ما حضور خواهد داشت. مابقی کدهای این کلاس partial به صورت خودکار توسط یک Source Generator سفارشی تولید خواهد شد. همانطور که ملاحظه می‌کنید، کاهش حجم قابل ملاحظه‌ای حاصل شده و همچنین اگر فیلد خصوصی دیگری نیز در اینجا اضافه شود، واکنش Source Generator ما آنی خواهد بود و بلافاصله کدهای مرتبط را تولید می‌کند و برنامه، بدون مشکلی کامپایل خواهد شد؛ هرچند به ظاهر INotifyPropertyChanged ذکر شده، در این کلاس اصلا پیاده سازی نشده‌است.


ایجاد پروژه‌ی Source Generator

در ابتدا برای ایجاد تولید کننده‌ی خودکار کدهای INotifyPropertyChanged، یک class library را به solution جاری اضافه می‌کنیم. سپس نیاز است ارجاعاتی را به دو بسته‌ی نیوگت زیر نیز افزود:
<Project Sdk="Microsoft.NET.Sdk">

  <ItemGroup>
    <PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.3">
      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
      <PrivateAssets>all</PrivateAssets>
    </PackageReference>
    <PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.2.0" PrivateAssets="all" />
  </ItemGroup>
</Project>
سپس کلاس جدید NotifyPropertyChangedGenerator را به نحو زیر به آن اضافه می‌کنیم:
  [Generator]
  public class NotifyPropertyChangedGenerator : ISourceGenerator
  {
    public void Initialize(GeneratorInitializationContext context)
    {
    }

    public void Execute(GeneratorExecutionContext context)
    {
- این کلاس باید اینترفیس ISourceGenerator را پیاده سازی کرده و همچنین مزین به ویژگی Generator باشد.
- اینترفیس ISourceGenerator به همراه دو متد Initialize و Execute است که در صورت نیاز باید پیاده سازی شوند.

در متد Execute، به خاصیت context.Compilation دسترسی داریم. این خاصیت تمام اطلاعاتی را که کامپایلر از Solution جاری در اختیار دارد، به توسعه دهنده ارائه می‌دهد. برای نمونه پیاده سازی متد Execute تولید کننده‌ی کد مثال جاری، چنین شکلی را دارد:
    public void Execute(GeneratorExecutionContext context)
    {
      // uncomment to debug the actual build of the target project
      // Debugger.Launch();
      var compilation = context.Compilation;
      var notifyInterface = compilation.GetTypeByMetadataName("System.ComponentModel.INotifyPropertyChanged");

      foreach (var syntaxTree in compilation.SyntaxTrees)
      {
        var semanticModel = compilation.GetSemanticModel(syntaxTree);
        var immutableHashSet = syntaxTree.GetRoot()
          .DescendantNodesAndSelf()
          .OfType<ClassDeclarationSyntax>()
          .Select(x => semanticModel.GetDeclaredSymbol(x))
          .OfType<ITypeSymbol>()
          .Where(x => x.Interfaces.Contains(notifyInterface))
          .ToImmutableHashSet();

        foreach (var typeSymbol in immutableHashSet)
        {
          var source = GeneratePropertyChanged(typeSymbol);
          context.AddSource($"{typeSymbol.Name}.Notify.cs", source);
        }
      }
    }
در اینجا با استفاده از context.Compilation به اطلاعات کامپایلر دسترسی پیدا کرده و سپس SyntaxTrees آن‌را یکی یکی، جهت یافتن کلاس‌ها و یا همان ClassDeclarationSyntax ها، پیمایش و بررسی می‌کنیم. سپس از بین این کلاس‌ها، کلاس‌هایی که INotifyPropertyChanged را پیاده سازی کرده باشند، انتخاب می‌کنیم که اطلاعات آن در پایان کار، به متد GeneratePropertyChanged جهت تولید مابقی کدهای partial class ارسال شده و کد تولیدی، به context اضافه می‌شود تا به نحو متداولی همانند سایر کدهای برنامه، به مجموعه کدهای مورد بررسی کامپایلر اضافه شود.

نکته‌ی مهم و جالب در اینجا این است که نیازی نیست تا قطعه کد جدید را به صورت SyntaxTrees در آورد و به کامپایلر اضافه کرد. می‌توان این قطعه کد را به نحو متداولی، به صورت یک قطعه رشته‌ی استاندارد #C، تولید و به کامپایلر با متد context.AddSource ارائه کرد که نمونه‌ای از آن‌را در ذیل مشاهده می‌کنید:
    private string GeneratePropertyChanged(ITypeSymbol typeSymbol)
    {
      return $@"
using System.ComponentModel;

namespace {typeSymbol.ContainingNamespace}
{{
  partial class {typeSymbol.Name}
  {{
    {GenerateProperties(typeSymbol)}
    public event PropertyChangedEventHandler? PropertyChanged;
  }}
}}";
    }

    private static string GenerateProperties(ITypeSymbol typeSymbol)
    {
      var sb = new StringBuilder();
      var suffix = "BackingField";

      foreach (var fieldSymbol in typeSymbol.GetMembers().OfType<IFieldSymbol>()
        .Where(x=>x.Name.EndsWith(suffix)))
      {
        var propertyName = fieldSymbol.Name[..^suffix.Length];
        sb.AppendLine($@"
    public {fieldSymbol.Type} {propertyName}
    {{
      get => {fieldSymbol.Name};
      set
      {{
        {fieldSymbol.Name} = value;
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof({propertyName})));
      }}
    }}");
      }

      return sb.ToString();
    }
در اینجا در ابتدا بدنه‌ی کلاس partial تکمیل می‌شود. سپس خواص عمومی آن بر اساس فیلدهای خصوصی تعریف شده، تکمیل می‌شوند. در این مثال اگر یک فیلد خصوصی به عبارت BackingField ختم شود، به عنوان فیلدی که قرار است معادل کدهای INotifyPropertyChanged را داشته باشد، شناسایی می‌شود و به همراه کدهای تولید شده‌ی خودکار خواهد بود.

کدهای source generator ما همین مقدار بیش‌تر نیست. اکنون می‌خواهیم از آن در یک برنامه‌ی کنسول جدید (برای مثال به نام NotifyPropertyChangedGenerator.Demo) استفاده کنیم. برای اینکار نیاز است ارجاعی را به آن اضافه کنیم؛ اما این ارجاع، یک ارجاع متداول نیست و نیاز به ذکر چنین ویژگی خاصی وجود دارد:
<Project Sdk="Microsoft.NET.Sdk">

  <ItemGroup>
    <ProjectReference Include="..\NotifyPropertyChangedGenerator\NotifyPropertyChangedGenerator.csproj"
                      OutputItemType="Analyzer" ReferenceOutputAssembly="false"/>
  </ItemGroup>
</Project>
در اینجا میسر دهی پروژه‌ی تولید کننده‌ی کد، همانند سایر پروژه‌ها است؛ اما نوع آن باید آنالایزر معرفی شود. به همین جهت از خاصیت OutputItemType با مقدار Analyzer استفاده شده‌است. همچنین تنظیم ReferenceOutputAssembly به false به این معنا است که این اسمبلی ویژه، یک وابستگی و dependency واقعی پروژه‌ی جاری نیست و ما قرار نیست به صورت مستقیمی از کدهای آن استفاده کنیم.

برای آزمایش این تولید کننده‌ی کد، کلاس CarModel را به صورت زیر به پروژه‌ی کنسول آزمایشی اضافه می‌کنیم:
using System.ComponentModel;

namespace NotifyPropertyChangedGenerator.Demo
{
  public partial class CarModel : INotifyPropertyChanged
  {
    private double SpeedKmPerHourBackingField;
    private int NumberOfDoorsBackingField;
    private string ModelBackingField = "";

    public void SpeedUp() => SpeedKmPerHour *= 1.1;
  }
}
این کلاس پیاده سازی کننده‌ی INotifyPropertyChanged است؛ اما به همراه هیچ خاصیت عمومی نیست. فقط به همراه یکسری فیلد خصوصی ختم شده‌ی به «BackingField» است که توسط تولید کننده‌ی کد شناسایی شده و اطلاعات آن‌ها تکمیل می‌شود. فقط باید دقت داشت که این کلاس حتما باید به صورت partial تعریف شود تا امکان تکمیل خودکار کدهای آن وجود داشته باشد.

یک نکته:   در این حالت هرچند برنامه بدون مشکل کامپایل و اجرا می‌شود، ممکن است خطوط قرمزی را در IDE خود مشاهده کنید که عنوان می‌کند این قطعه از کد قابل کامپایل نیست. اگر با چنین صحنه‌ای مواجه شدید، یکبار solution را بسته و مجددا باز کنید تا تولید کننده‌ی کد، به خوبی شناسایی شود. البته نگارش‌های جدیدتر Visual Studio و Rider به همراه قابلیت auto reload پروژه برای کار با تولید کننده‌‌های کد هستند و دیگر شاهد چنین صحنه‌هایی نیستیم و حتی اگر برای مثال فیلد جدیدی را به CarModel اضافه کنیم، نه فقط بلافاصله کدهای متناظر آن تولید می‌شوند، بلکه خواص عمومی تولید شده در Intellisense نیز قابل دسترسی هستند.


نحوه‌ی مشاهده‌ی کدهای خودکار تولید شده

اگر علاقمند باشید تا کدهای خودکار تولید شده را مشاهده کنید، در Visual Studio، در قسمت و درخت نمایشی dependencies پروژه، گره‌ای به نام Analyzers وجود دارد که در آن برای مثال نام NotifyPropertyChangedGenerator و ذیل آن، کلاس‌های تولید شده‌ی توسط آن، قابل مشاهده و دسترسی هستند و حتی قابل دیباگ نیز می‌باشند؛ یعنی می‌توان بر روی سطور مختلف آن، break-point قرار داد.


کدهای کامل این مطلب را از اینجا می‌توانید دریافت کنید: SourceGeneratorTests.zip

معرفی تعدادی منبع تکمیلی
- برنامه Source generator playground
در اینجا تعدادی مثال را که توسط مایکروسافت توسعه یافته‌است، مشاهده می‌کنید که اتفاقا یکی از آن‌ها پیاده سازی تولید کننده‌ی کد اینترفیس INotifyPropertyChanged است. در این برنامه، خروجی کدهای تولیدی نیز به سادگی قابل مشاهده‌است.

- برنامه SharpLab
برای توسعه‌ی تولید کننده‌های کد، عموما نیاز است تا با Roslyn API آشنا بود. در این برنامه اگر از منوی بالای صفحه قسمت results، گزینه‌ی «syntax tree» را انتخاب کنید و سپس قسمتی از کد خود را انتخاب کنید، بلافاصله معادل Roslyn API آن، در سمت راست صفحه نمایش داده می‌شود.

- معرفی مجموعه‌ای از Source Generators
در اینجا می‌توان مجموعه‌ای از پروژه‌های سورس باز Source Generators را مشاهده و کدهای آن‌ها را مطالعه کنید و یا از آن‌ها در پروژه‌های خود استفاده نمائید.

- معرفی یک cookbook در مورد Source Generators
این cookbook توسط خود مایکروسافت تهیه شده‌است و جهت شروع به کار با این فناوری، بسیار مفید است.

- مجموعه مثال‌های Source generators از مایکروسافت
در اینجا می‌توانید مجموعه مثال‌هایی از Source generators را که توسط مایکروسافت تهیه شده‌است، مشاهده کنید. شرح و توضیحات تعدادی از آن‌ها را هم در اینجا مطالعه کنید.
مطالب
ارتقاء فایل‌های آغازین برنامه‌های ASP.NET Core 5x به 6x
اگر یک پروژه‌ی جدید ASP.NET Core 6x را شروع کنیم، دو فایل قدیمی Program.cs و Startup.cs آن یکی شده‌اند و اینبار فقط یک Program.cs قابل مشاهده‌است؛ با چنین محتوای ساده شده‌ای:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapGet("/", () => "Hello World!");

app.Run();
که مفاهیم C# 10.0 مانند «ساده سازی تعریف فضاهای نام در C# 10.0» و «کاهش تعداد بار تعریف using‌ها در C# 10.0 و NET 6.0.» در آن‌ها نیز قابل مشاهده‌است. همچنین در اینجا، تمام تنظیمات WebApplication هم قرار خواهند گرفت؛ عنوان کرده‌اند که از ابتدا هم این تنظیمات در اصل متعلق به همینجا بوده‌اند، چرا تمام آن‌ها را داخل یک فایل اسکریپت مانند قرار ندهیم و تعداد لایه‌های abstractions را کاهش ندهیم؟
البته این روش شاید برای برنامه‌های کوچک جالب به‌نظر برسد، اما برای برنامه‌های بزرگتر می‌توان به گزینه‌های زیر نیز توجه داشت.


گزینه‌ی ارتقاء 1: هیچ کاری نکنید!

اگر می‌خواهید برنامه‌های NET 5. خود را به دات نت 6 ارتقاء دهید و نگران هستید که با دو فایل قدیمی Program.cs و Startup.cs آن باید چکار کنیم، پاسخ ساده‌ی ‌آن این است: هیچ کاری نکنید!
شیوه‌ی قدیمی مبتنی بر generic host و Startup، کاملا در دات نت 6 پشتیبانی می‌شوند؛ از این جهت که WebApplication جدید دات نت 6، صرفا یک محصور کننده‌ی پیچیدگی‌های generic host است. بنابراین برای ارتقاء پروژه‌های ASP.NET Core 5x به 6x، تنها کافی است فایل csproj خود را ویرایش کرده و TargetFramework آن‌را به net6.0 تغییر دهید. پس از آن Program.cs و Stratup.cs قبلی شما بدون هیچ مشکلی و بدون نیاز به هیچ تغییری، با دات نت 6 هم کار خواهند کرد.
<Project Sdk="Microsoft.NET.Sdk.Web">
  <PropertyGroup>
      <TargetFramework>net6.0</TargetFramework>
   </PropertyGroup>
</Project>


گزینه‌ی ارتقاء 2: از کلاس Startup قبلی خود استفاده‌ی مجدد کنید

اما اگر واقعا علاقمندیم که از WebApplication جدید استفاده کنیم و همچنین نمی‌خواهیم همه‌چیز را داخل Program.cs قرار دهیم، چکار باید کرد؟
فرض کنید ساختار کلاس Startup موجود شما چنین شکلی را دارد که به همراه سازنده‌ای است که IConfigurationRoot را دریافت می‌کند و همچنین دارای دو متد ConfigureServices و Configure نیز هست:
public class Startup
{
    public Startup(IConfigurationRoot configuration)
    {
        Configuration = configuration;
    }

    public IConfigurationRoot Configuration { get; }

    public void ConfigureServices(IServiceCollection services)
    {
        // ...
    }

    public void Configure(IApplicationBuilder app, IHostApplicationLifetime lifetime)
    {
        // ...
    }
}
تا پیش از دات نت 6، متد <UseStartup<T که در فایل Program.cs قرار داشت، کار استفاده از تنظیمات کلاس فوق را به صورت خودکار انجام می‌داد. این متد دیگر در سیستم جدید مبتنی بر WebApplication دات نت 6 وجود ندارد، اما می‌توان به صورت زیر آن‌را برگرداند:
var builder = WebApplication.CreateBuilder(args);
var startup = new Startup(builder.Configuration);
startup.ConfigureServices(builder.Services);
var app = builder.Build();
startup.Configure(app, app.Lifetime);
app.Run();
در اینجا روش نمونه سازی دستی کلاس Startup قدیمی را مشاهده می‌کنید که به همراه فراخوانی دستی دو متد ConfigureServices و Configure آن نیز هست. به این ترتیب می‌توان از کلاس قدیمی آغازین برنامه‌های دات نت 5، در برنامه‌های دات نت 6 نیز استفاده کرد.


گزینه‌ی ارتقاء 3: استفاده از متدهای محلی در فایل Program.cs

اگر بخواهیم سیستم طراحی مینی‌مال دات نت 6 را رعایت کنیم، می‌توان بجای ایجاد یک فایل Startup مجزا، متدهای تنظیمی آن‌را به صورت تعدادی متد محلی، در همان فایل Program.cs قرار داد تا کمی ساختار پیدا کند(!)؛ چیزی شبیه به طراحی زیر که همان متدهای قبلی فایل Startup را در انتهای فایل Program.cs جاری به صورت متدهایی محلی، مشاهده می‌کنید؛ به همراه متدهای اختیاری دیگری برای تنظیم میان‌افزارها و یا endpoints:
var builder = WebApplication.CreateBuilder(args);
ConfigureConfiguration(builder.configuration);
ConfigureServices(builder.Services);
var app = builder.Build();
ConfigureMiddleware(app, app.Services);
ConfigureEndpoints(app, app.Services);
app.Run();

void ConfigureConfiguration(ConfigurationManager configuration) => { }

void ConfigureServices(IServiceCollection services) => { }

void ConfigureMiddleware(IApplicationBuilder app, IServiceProvider services) => { }

void ConfigureEndpoints(IEndpointRouteBuilder app, IServiceProvider services) => { }
در کل این قالب جدید دات نت 6، هیچ نوع الگو و یا پیشنیاز خاصی را جهت انجام تنظیمات آغازین برنامه توصیه نمی‌کند؛ از این رو می‌توان به هر نحوی که علاقمند بودیم، آن‌را شکل دهیم.
اشتراک‌ها
بررسی عمیق بهبودهای کارآیی در NET 9.

Performance Improvements in .NET 9

Each year, summer arrives to find me daunted and excited to write about the performance improvements in the upcoming release of .NET. “Daunted,” because these posts, covering .NET 8, .NET 7, .NET 6, .NET 5, .NET Core 3.0, .NET Core 2.1, and .NET Core 2.0, have garnered a bit of a reputation I want to ensure the next iteration lives up to. And “excited,” because there’s such an abundance of material to cover due to just how much goodness has been packed into the next .NET release, I struggle to get it all written down as quickly as my thoughts whirl.

بررسی عمیق بهبودهای کارآیی در NET 9.
مطالب
بررسی تغییرات ASP.NET MVC 5 beta1
همانطور که می‌دانید، مایکروسافت در کنفرانس Build 2013  که چند روز پیش برگزار شد،  Visual Studio 2013 Preview را به همراه ASP.NET MVC 5 beta1  و Entity Framework 6 beta 1 و تعدادی محصول دیگر، معرفی کرد.
در طی این مقاله قصد دارم تجربیات کار خودم با نسخه‌ی پیش نمایش MVC 5 را به اشتراک بزارم و نه صرفا بررسی یک change-log ساده.
برای کار با MVC 5 شما ابتدا باید یکی از نسخه‌های Visual Studio 2013 را نصب کنید. من در مقاله از Visual Studio Express 2013 Preview For Web استفاده می‌کنم.
ابتدا New Project را زده تا یک پروژه جدید را آغاز کنیم. از قسمت Templates، بخش Web  را که انتخاب کنید، اولین تغییر را مشاهده خواهید کرد.بله! دیگر خبر از چند ASP.NET نیست.حداقل در دسته بندی تبدیل به یک ASP.NET واحد شده اند.

با انتخاب ok باز نیز با قالب جدیدی به شکل زیر برای انتخاب پروژه مواجه می‌شوید. 

اینجا همه چیز تکراری است به غیر از گزینه  Configure Authentication. 

همه‌ی گزینه‌ها تکراری اند به غیر از گزینه Individual User Accounts. البته این همان FormsAuthentication قبلی است. نکته قابل توجه، یکپارچی آن با سرویس‌های اجتماعی و شبکه‌های سرویس دهنده است. البته در نسخه‌ی قبلی نیز این سیستم وجود داشت، ولی این دفعه  با ASP.NET Identity یک پارچه است که در ادامه بیشتر آن را خواهید دید.

البته گویا حالت دیگری به نام Organizational Accounts  نیز وجود دارد که گویا  برای فعال سازی،باید یک بسته‌ی به روز رسانی دریافت می‌کردم، که من نکردم.(اینترنت حجمی و شبانه دانلود کردن...)

این حالت که در شکل زیر مشخص است، امکان یکپارچگی احراز هویت با Active Directory  در windows server و azure را دارد.

پس از ایجاد پروژه یک نگاهی به Solution Explorer می‌اندازیم. 

همان طور که می‌بینید ساختار اصلی با نسخه‌های قبلی هیچ تفاوتی نکرده و تنها کتاب خانه ای که اینجا خودنمایی می‌کند و به چشم آشنا نیست، twitter bootstrap است!

با توجه به پوشه‌ی مدل این را متوجه می‌شویم که مایکروسافت هم به لزوم  ViewModel اعتقاد پیدا کرده است.

با اجرا کردن پروژه bootstrap و responsive بودن آن، خودنمایی می‌کنند.

اگر نگاهی به کنترلر Account بیندازیم، با موارد جالبی روبرو می‌شویم. 

به لطف سیستم Identity جدید، Entity Framework 6 و .NET 4.5 ، می‌بینیم که تا حد امکان، عملیات به صورت آسنکرون(نامتقارن) انجام شده اند که برای برنامه‌های scalable بسیار مفید و ضروری به نظر می‌رسد.
اگر نگاهی به reference‌های پروژه هم بیندازیم، حضور بسیاری از کتاب خانه‌های نام آشنا را به صورت پیش فرض، شاهد هستیم.

 Entity Framework نسخه‌ی 6 beta1 به صورت پیش فرض در پروژه وجود دارد. خوشبختانه دیگر خبری هم از System.Data.Entity  نیست. همچنین حضور پررنگ Owin و ASP.NET Identity را متوجه خواهید شد.
خب قبلا اگر قصد افزودن کنترلر جدیدی به بروژه داشتید، به راحتی در هر جای پروژه گزینه ای به نام AddController را می‌زدید. اما اینجا سناریو کمی متفاوت است.
 

همه چیز گویا با Scaffolding یکپارچه شده. به گفته‌ی تیم ASP.NET، Scaffold کاملا از نو نوشته شده، در بررسی‌های اولیه من، کدهای تولیدی چندان تفاوتی با نسخه‌ی قبل نداشت.احتمالا تغییرات در جای دیگری است. 
حال اگر بخواهیم برای Controller ایجاد شده یه View ایجاد کنیم باید طبق روال سابق از شرتکات ctrl+m, ctrl+v استفاده کنیم. اما... 

بله، لااقل در این IDE در اقدامی ناجوانمردانه! این گزینه حذف شده است. گویا باید وارد فولدر Views شده و به صورت دستی فولدر جدیدی ایجاد و از گزینه‌ی Scaffold برای افزودن View جدید اقدام کرد. 

همین طور که می‌بینید، Intellisense ویژوال استادیو به صورت توکار، از AngularJs پشتیبانی می‌کند.
نتیجه گیری:
گویا مایکروسافت نیز به این نتیجه رسیده که ASP.NET MVC در نسخه‌ی سوم خود، کاملا پخته و به بلوغ رسیده است و پس از آن باید فقط آن را بهینه کرده و تغییرات اساسی در آن انجام ندهد .تیم ASP.NET تنها حواسش منعطف به همگام شدن با تکنولوژی‌های روز Web است و این را با پشتیبانی پیش فرض از bootstrap و Angularjs شاهد هستیم. گویا خط مشی تیم توسعه دهنده نیز این گونه است. از جمله تغییرات خوب بحث Identity هست که کاملا به EF Code First یک پارچه هست و دیگر مشکلات کار با سرویس Memberships وجود ندارد. در کل شما یک سری اینترفیس پیاده سازی می‌کنید و بقیه مسائل توسط این کتابخانه‌ی نو ظهور مدیریت می‌شود و فراموش نکنیم که این کتابخانه با OAuth یکپارچه است.(دریافت اطلاعات بیشتر )
طبق change-log رسمی، تغییر آنچنانی در MVC رخ نداده است. فقط یک سری آپدیت و بهینه سازی و همگام سازی با تکنولوژی‌های جدید.
نظر شما در مورد این تغییرات چیست؟

نظرات مطالب
کمپین ضد IF !
توضیحات تکمیلی:
سؤال : آیا refactoring صورت گرفته در مطلب فوق از نوع تزریق وابستگی‌ها (dependency injection) بود؟
پاسخ: خیر.
پیاده سازی الگوی تزریق وابستگی‌ها زمانی معنا پیدا می‌کند که شما حداقل 2 کلاس داشته باشید (مطلب فوق با یک کلاس شروع شد)، همچنین این دو کلاس ارجاعی به یکدیگر داشته باشند و اصطلاحا به هم گره خورده باشند.

سؤال : چگونه در یک پروژه بزرگ می‌توان نیاز به پیاده سازی الگوی تزریق وابستگی‌ها را تشخیص داد؟
پاسخ:
آیا نسخه‌ی ultimate ویژوال استودیوی 2010 بر روی سیستم شما نصب است؟
اگر بله: (نصب است)
برای نمونه به مطلب Discovering Circular References مراجعه کنید.

اگر خیر: (نصب نیست)
در این حالت از ابزار رایگانی به نام .NET Architecture Checker می‌توانید استفاده کنید. همان نمودارهای نسخه‌ی ultimate ویژوال استودیو را برای شما ترسیم خواهد کرد.

سؤال : آیا می‌توان از کتابخانه‌های تزریق وابستگی‌ها و فریم ورک‌های مرتبط، جهت مدیریت ساده‌تر قسمت آخر مطلب فوق یعنی تامین پیاده سازی‌های اینترفیس‌هایی که قرار است در زمان اجرا استفاده شوند، کمک گرفت؟
پاسخ: بله.
این مورد یکی از کاربردهای متداول این ابزارها است (برای مثال ساخت برنامه‌های افزونه پذیر و همچنین ساده‌تر کردن Object composition و وهله سازی‌های مرتبط) و ... این مورد را نباید با اصل refactoring صورت گرفته در مثال جاری اشتباه گرفت.
مطالب
زیرنویس فارسی ویدئوهای مقدمات AngularJS - قسمت چهارم
زیرنویس‌های فارسی قسمت چهارم را از اینجا  می‌توانید دانلود کنید.
لیست سرفصل‌های قسمت چهارم به شرح زیر است :
01. Introduction to Routing
02. Websites of Yore
03. Single Page Applications
04. Demo - Adding Your First Route
05. Demo - More Routing and Browser History
06. Demo - Creating a Default Route
07. Demo - Accessing Parameters from the Route
08. Demo - Using the $route Service
09. Demo - Enabling HTML5 Routing
10. Demo - Template and Resolve Properties
11. Demo - Using the $location Service
12. Summary
13. Suggested Exercises
در این قسمت به مبحث مسیریابی در انگولار و اهمیت آن جهت ساخت برنامه‌های تک صفحه ایی یا به اصطلاح SPA پرداخته می‌شود. همچنین به بررسی اینکه چگونه می‌توان با کمک سیستم مسیریابی، برنامه‌مان را تبدیل به یک برنامه تک صفحه‌ایی کنیم نیز پرداخته می‌شود. در واقع سیستم مسیریابی به ما کمک می‌کند یک برنامه تک صفحه ایی را به view‌های مختلفی تقسیم کنیم؛ هر چقدر برنامه درگیر جزئیات بیشتری شود، مدیریت آن نیز به مراتب سخت‌تر خواهد شد. تقسیم برنامه به view‌های مختلف و بارگذاری قسمت‌های مختلف برنامه با استفاده از Routing، مدیریت برنامه را برای ما ساده‌تر خواهد کرد.
این قسمت ابتدا شما را با ماهیت برنامه‌های تک صفحه ایی وب آشنا می‌کند و بعد از آن به بررسی مثال‌های عملی در این رابطه خواهد پرداخت. همچنین مواردی ازقبیل Browser History، ایجاد route‌های پیش فرض، افزودن پارامتر به route، استفاده از سرویس $route، فعال سازی سیستم مسیریابی مهیا در HTML5، بررسی پراپرتی‌های Template و Resolve، استفاده از سرویس location بخوبی آموزش و به آنها پرداخته می‌شود.


پ.ن. در مورد نحوه‌ی تهیه اصل ویدیوها در نظرات قسمت‌های قبل این سری مطالب، بیشتر بحث شده‌است.
اشتراک‌ها
Decrypting/Encrypting تنظیمات برنامه در ASP.NET CORE
اشتراک‌ها
چند درسی که دولوپرها می‌توانند از دعای جوشن‌کبیر بگیرند!

یک دولوپر با هر دیدگاه و نقطه‌نظری، بایستی از تک‌تک فرصت‌هایی که در محیط پیرامونش به‌وجود می‌آیند به‌منظور حرفه‌ای‌تر شدن استفاده کند و یکی از این فرصت‌های طلایی که کمک شایانی به دولوپرها در عرصه کدنویسی برنامه‌های مفیدتر و سبک کاری حرفه‌ای‌تر می‌کند، درس گرفتن از دعای جوشن‌کبیر است!  

چند درسی که دولوپرها می‌توانند از دعای جوشن‌کبیر بگیرند!
مطالب
یکی کردن اسمبلی‌های یک پروژه‌ی 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
 
اشتراک‌ها
Visual Studio 2017 version 15.7.3 منتشر شد

These are the customer-reported issues addressed in 15.7.3:

Visual Studio 2017 version 15.7.3  منتشر شد