اشتراک‌ها
Visual Studio 2019 version 16.11.4 منتشر شد
حجم تقریبی آپدیت از نسخه قبلی (16.11.3) حدود 1.11G میشه.
  • Windows 11 SDK support.
  • Adds Xcode 13.0 support.
  • Add AMD64 math functions to ARM64X CRT.
  • Updates to the ARM64 and ARM64EC interfaces between the binary and the POGO instrumentation runtime.
  • Fixed several problems with IntelliSense responsiveness and correctness affecting C++20 concepts, ranges, and abbreviated function templates.
  • Fixed a false positive in local lifetime checks.
  • Corrected an issue where arrays allocated with a constant of size > 32bits could allocate less memory than requested.
  • Ensures that ATL string initialization occurs during static variable initialization, in the default AppDomain.
  • Fixed a bug in C++ Concurrency::parallel_for_each that was crashing the calling process due to integer overflow.
  • Fixed a bug in the STL's iterator debugging machinery that could cause crashes in multithreaded programs using STL containers.
  • We have fixed a fatal internal compiler error caused by unnamed structs whose fields are referenced from SAL annotations.
  • Fixes a rare crash when analyzing templated code that uses __uuidof.
  • Fixed an issue that caused C++ static analysis results to sometimes not display correctly in the FixIt action.
  • Fixed opening .uitest extension files in Coded UI project
  • Fire component change events for non-component objects also in WinForms .NET designer
  • Fix for crash on deleting ContextMenuStrip control in Windows Forms .NET designer.
  • Guard against crashes when the Windows Forms designer reloads when dragging.
  • Fix for intermittent VS crash while interacting with WinForms .NET designer during solution or project rebuild.
  • Fixed a bug causing .NET 5 projects to be reported as out of date when they should have been up to date, causing slower builds.
  • Automatically disable asset-indexing for large scale Unity projects.
  • This release fixes an issue with deploying certain Windows Application Packaging projects where deployment is unnecessarily copying unmodified files. 
Visual Studio 2019 version 16.11.4 منتشر شد
مطالب
روش محاسبه‌ی لحظه‌ی سال تحویل
سال قبل نتیجه‌ی جستجوی من برای یافتن فرمول محاسبه‌ی زمان سال تحویل، برای ارسال ایمیل‌های خودکار تبریک آن، در سایت‌های ایرانی حاصلی نداشت. اما واژه‌ی انگلیسی Equinox سرآغازی شد برای یافتن این الگوریتم.
نام علمی لحظه‌ی سال تحویل، Vernal Equinox است. Equinox به معنای نقطه‌ای است که یک فصل، به فصلی دیگر تبدیل می‌شود:


Equinox واژه‌ای است لاتین به معنای «شب‌های مساوی» و به این نکته اشاره دارد که در Equinox، طول شب و روز یکی می‌شوند. هر سال دارای دو Equinox است: vernal equinox و autumnal equinox (بهاری و پائیزی). البته باید درنظر داشت که Equinox بهاری در نیم کره‌ی شمالی بیشتر معنا پیدا می‌کند؛ زیرا در نیم کره‌ی جنوبی در همین زمان، پائیز شروع می‌شود.
بنابراین می‌توان enum زیر را برای تعریف این چهار ثابت رخدادهای خورشیدی تعریف کرد:
public enum SunEvent
{
    /// <summary>
    /// march equinox
    /// </summary>
    VernalEquinox,
 
    /// <summary>
    /// june solstice
    /// </summary>
    SummerSolstice,
 
    /// <summary>
    /// september equinox
    /// </summary>
    AutumnalEquinox,
 
    /// <summary>
    /// december solstice
    /// </summary>
    WinterSolstice
}

در ادامه برای محاسبه‌ی زمان equinox از فصل 27 کتاب Astronomical Algorithms کمک گرفته شده و تمام اعداد و ارقام و جداولی را که ملاحظه می‌کنید از این کتاب استخراج شده‌اند.
/// <summary>
/// Based on Jean Meeus book _Astronomical Algorithms_
/// </summary>
public static class EquinoxCalculator
{
    /// <summary>
    /// Degrees to Radians conversion factor.
    /// </summary>
    public static readonly double Deg2Radian = Math.PI / 180.0;
 
    public static bool ApproxEquals(double d1, double d2)
    {
        const double epsilon = 2.2204460492503131E-16;
        if (d1 == d2)
            return true;
        var tolerance = ((Math.Abs(d1) + Math.Abs(d2)) + 10.0) * epsilon;
        var difference = d1 - d2;
        return (-tolerance < difference && tolerance > difference);
    }
 
    /// <summary>
    /// Calculates time of the Equinox and Solstice.
    /// </summary>
    /// <param name="year">Year to calculate for.</param>
    /// <param name="sunEvent">Event to calculate.</param>
    /// <returns>Date and time event occurs as a fractional Julian Day.</returns>
    public static DateTime GetSunEventUtc(this int year, SunEvent sunEvent)
    {
        double y;
        double julianEphemerisDay;
 
        if (year >= 1000)
        {
            y = (Math.Floor((double)year) - 2000) / 1000;
 
            switch (sunEvent)
            {
                case SunEvent.VernalEquinox:
                    julianEphemerisDay = 2451623.80984 + 365242.37404 * y + 0.05169 * (y * y) - 0.00411 * (y * y * y) - 0.00057 * (y * y * y * y);
                    break;
                case SunEvent.SummerSolstice:
                    julianEphemerisDay = 2451716.56767 + 365241.62603 * y + 0.00325 * (y * y) - 0.00888 * (y * y * y) - 0.00030 * (y * y * y * y);
                    break;
                case SunEvent.AutumnalEquinox:
                    julianEphemerisDay = 2451810.21715 + 365242.01767 * y + 0.11575 * (y * y) - 0.00337 * (y * y * y) - 0.00078 * (y * y * y * y);
                    break;
                case SunEvent.WinterSolstice:
                    julianEphemerisDay = 2451900.05952 + 365242.74049 * y + 0.06223 * (y * y) - 0.00823 * (y * y * y) - 0.00032 * (y * y * y * y);
                    break;
                default:
                    throw new NotSupportedException();
            }
        }
        else
        {
            y = Math.Floor((double)year) / 1000;
 
            switch (sunEvent)
            {
                case SunEvent.VernalEquinox:
                    julianEphemerisDay = 1721139.29189 + 365242.13740 * y + 0.06134 * (y * y) - 0.00111 * (y * y * y) - 0.00071 * (y * y * y * y);
                    break;
                case SunEvent.SummerSolstice:
                    julianEphemerisDay = 1721233.25401 + 365241.72562 * y + 0.05323 * (y * y) - 0.00907 * (y * y * y) - 0.00025 * (y * y * y * y);
                    break;
                case SunEvent.AutumnalEquinox:
                    julianEphemerisDay = 1721325.70455 + 365242.49558 * y + 0.11677 * (y * y) - 0.00297 * (y * y * y) - 0.00074 * (y * y * y * y);
                    break;
                case SunEvent.WinterSolstice:
                    julianEphemerisDay = 1721414.39987 + 365242.88257 * y + 0.00769 * (y * y) - 0.00933 * (y * y * y) - 0.00006 * (y * y * y * y);
                    break;
                default:
                    throw new NotSupportedException();
            }
        }
 
        var julianCenturies = (julianEphemerisDay - 2451545.0) / 36525;
 
        var w = 35999.373 * julianCenturies - 2.47;
 
        var lambda = 1 + 0.0334 * Math.Cos(w * Deg2Radian) + 0.0007 * Math.Cos(2 * w * Deg2Radian);
 
        var sumOfPeriodicTerms = getSumOfPeriodicTerms(julianCenturies);
 
        return JulianToUtcDate(julianEphemerisDay + (0.00001 * sumOfPeriodicTerms / lambda));
    }
 
    /// <summary>
    /// Converts a fractional Julian Day to a .NET DateTime.
    /// </summary>
    /// <param name="julianDay">Fractional Julian Day to convert.</param>
    /// <returns>Date and Time in .NET DateTime format.</returns>
    public static DateTime JulianToUtcDate(double julianDay)
    {
        double a;
        int month, year;
 
        var j = julianDay + 0.5;
        var z = Math.Floor(j);
        var f = j - z;
 
        if (z >= 2299161)
        {
            var alpha = Math.Floor((z - 1867216.25) / 36524.25);
            a = z + 1 + alpha - Math.Floor(alpha / 4);
        }
        else
            a = z;
 
        var b = a + 1524;
 
        var c = Math.Floor((b - 122.1) / 365.25);
 
        var d = Math.Floor(365.25 * c);
 
        var e = Math.Floor((b - d) / 30.6001);
 
        var day = b - d - Math.Floor(30.6001 * e) + f;
 
        if (e < 14)
            month = (int)(e - 1.0);
        else if (ApproxEquals(e, 14) || ApproxEquals(e, 15))
            month = (int)(e - 13.0);
        else
            throw new NotSupportedException("Illegal month calculated.");
 
        if (month > 2)
            year = (int)(c - 4716.0);
        else if (month == 1 || month == 2)
            year = (int)(c - 4715.0);
        else
            throw new NotSupportedException("Illegal year calculated.");
 
        var span = TimeSpan.FromDays(day);
 
        return new DateTime(year, month, (int)day, span.Hours, span.Minutes,
            span.Seconds, span.Milliseconds, new GregorianCalendar(), DateTimeKind.Utc);
    }
 
    /// <summary>
    /// These values are from Table 27.C
    /// </summary>
    private static double getSumOfPeriodicTerms(double julianCenturies)
    {
        return 485 * Math.Cos(Deg2Radian * 324.96 + Deg2Radian * (1934.136 * julianCenturies))
               + 203 * Math.Cos(Deg2Radian * 337.23 + Deg2Radian * (32964.467 * julianCenturies))
               + 199 * Math.Cos(Deg2Radian * 342.08 + Deg2Radian * (20.186 * julianCenturies))
               + 182 * Math.Cos(Deg2Radian * 27.85 + Deg2Radian * (445267.112 * julianCenturies))
               + 156 * Math.Cos(Deg2Radian * 73.14 + Deg2Radian * (45036.886 * julianCenturies))
               + 136 * Math.Cos(Deg2Radian * 171.52 + Deg2Radian * (22518.443 * julianCenturies))
               + 77 * Math.Cos(Deg2Radian * 222.54 + Deg2Radian * (65928.934 * julianCenturies))
               + 74 * Math.Cos(Deg2Radian * 296.72 + Deg2Radian * (3034.906 * julianCenturies))
               + 70 * Math.Cos(Deg2Radian * 243.58 + Deg2Radian * (9037.513 * julianCenturies))
               + 58 * Math.Cos(Deg2Radian * 119.81 + Deg2Radian * (33718.147 * julianCenturies))
               + 52 * Math.Cos(Deg2Radian * 297.17 + Deg2Radian * (150.678 * julianCenturies))
               + 50 * Math.Cos(Deg2Radian * 21.02 + Deg2Radian * (2281.226 * julianCenturies))
               + 45 * Math.Cos(Deg2Radian * 247.54 + Deg2Radian * (29929.562 * julianCenturies))
               + 44 * Math.Cos(Deg2Radian * 325.15 + Deg2Radian * (31555.956 * julianCenturies))
               + 29 * Math.Cos(Deg2Radian * 60.93 + Deg2Radian * (4443.417 * julianCenturies))
               + 28 * Math.Cos(Deg2Radian * 155.12 + Deg2Radian * (67555.328 * julianCenturies))
               + 17 * Math.Cos(Deg2Radian * 288.79 + Deg2Radian * (4562.452 * julianCenturies))
               + 16 * Math.Cos(Deg2Radian * 198.04 + Deg2Radian * (62894.029 * julianCenturies))
               + 14 * Math.Cos(Deg2Radian * 199.76 + Deg2Radian * (31436.921 * julianCenturies))
               + 12 * Math.Cos(Deg2Radian * 95.39 + Deg2Radian * (14577.848 * julianCenturies))
               + 12 * Math.Cos(Deg2Radian * 287.11 + Deg2Radian * (31931.756 * julianCenturies))
               + 12 * Math.Cos(Deg2Radian * 320.81 + Deg2Radian * (34777.259 * julianCenturies))
               + 9 * Math.Cos(Deg2Radian * 227.73 + Deg2Radian * (1222.114 * julianCenturies))
               + 8 * Math.Cos(Deg2Radian * 15.45 + Deg2Radian * (16859.074 * julianCenturies));
    }
}
خروجی‌های زمانی ستاره شناسی، عموما بر اساس فرمت Julian Date است که آغاز آن  4713BCE January 1, 12 hours GMT است. به همین جهت در انتهای این مباحث، تبدیل Julian Date به DateTime دات نت را نیز ملاحظه می‌کنید. همچنین باید دقت داشت که خروجی نهایی بر اساس UTC است و برای زمان ایران، باید 3.5 ساعت به آن اضافه شود.

خروجی این الگوریتم را برای سال‌های 2014 تا 2022 به صورت ذیل مشاهده می‌کنید:
2014 -> 1392/12/29 20:28:08
2015 -> 1394/01/01 02:16:29
2016 -> 1395/01/01 08:01:21
2017 -> 1395/12/30 14:00:00
2018 -> 1396/12/29 19:46:10
2019 -> 1398/01/01 01:29:29
2020 -> 1399/01/01 07:21:03
2021 -> 1399/12/30 13:08:41
2022 -> 1400/12/29 19:04:37
برای نمونه زمان محاسبه شده‌ی 1394/01/01 02:16:29 با زمان رسمی اعلام شده‌ی ساعت 2 و 15 دقیقه و 10 ثانیه روز شنبه 1 فروردین 1394 و یا برای سال 93 زمان محاسبه شده‌ی 1392/12/29 20:28:08 با زمان رسمی ساعت ۲۰ و ۲۷ دقیقه و ۷ ثانیه پنجشنبه ۲۹ اسفند ۱۳۹۲، تقریبا برابری می‌کند.

کدهای کامل این پروژه را از اینجا می‌توانید دریافت کنید
 Equinox.zip
اشتراک‌ها
نگاهی به مراحل تکامل زبان #C

Since its original release in 2002, C# has been regularly updated with new features. Today, we will look at the most important new features of each major language version and explore how the C# code we have been writing, has evolved through years.  

نگاهی به مراحل تکامل زبان #C
اشتراک‌ها
کار با فایل‌های اکسل در برنامه‌های ASP.NET Core توسط کتابخانه‌ی EPPlus.Core

This post shows how to import and export .xls or .xlsx (Excel files) in ASP.NET Core. And when thinking about dealing with excel with .NET, we always look for third-party libraries or component. And one of the most popular .net library that reads and writes Excel 2007/2010 files using the Open Office Xml format (xlsx) is EPPlus. However, at the time of writing this post, this library is not updated to support .NET Core. But there exists an unofficial version of this library EPPlus.Core which can do the job of import and export xlsx in ASP.NET Core. This works on Windows, Linux and Mac. 

کار با فایل‌های اکسل در برنامه‌های ASP.NET Core توسط کتابخانه‌ی EPPlus.Core
اشتراک‌ها
معرفی رسمی C# 7.3

Better late than never! Some of you may have noticed that C# 7.3 already shipped, back in Visual Studio 2017 update 15.7. Some of you may even be using the features already. 

معرفی رسمی C# 7.3
اشتراک‌ها
پروژه C# to JavaScript Compiler
This compiler can compile C# into JavaScript. By doing this you can leverage all the advantages of C#, such as static type checking, IntelliSense (the kind that works) and lambda expressions when writing code for the browser 
پروژه C# to JavaScript Compiler
اشتراک‌ها
هرچند قرار است Visual Basic در NET 5x. حضور داشته باشد، اما این زبان دیگر به روز رسانی نخواهد شد

"Going forward, we do not plan to evolve Visual Basic as a language," the .NET team said. "This supports language stability and maintains compatibility between the .NET Core and .NET Framework versions of Visual Basic. Future features of .NET Core that require language changes may not be supported in Visual Basic. Due to differences in the platform, there will be some differences between Visual Basic on .NET Framework and .NET Core."  

هرچند قرار است Visual Basic در NET 5x. حضور داشته باشد، اما این زبان دیگر به روز رسانی نخواهد شد
اشتراک‌ها
طراحی جدول Calendar یا DateDimension

What is a Calendar Table and Why is it Useful?

A calendar table is a permanent table containing a list of dates and various components of those dates. These may be the result of DATEPART operations, time of year, holiday analysis, or any other creative operations we can think of. 

از این جدول به عنوان راه حلی عمومی برای حل مشکل گروهبندی براساس بخش‌های مختلف تاریخ در تقویم‌های موجود و همچنین در طراحی تقویم کاری یک سازمان نیز می‌توان استفاده کرد. 


طراحی جدول Calendar یا DateDimension
اشتراک‌ها
Visual Studio 2017 15.5.7 منتشر شد

Issues Fixed in this Release

Projects targeting .NET Core 2.1 or newer are not supported by Visual Studio 2017 version 15.5 .
Fixed issue where installation of the SDK for .NET Core 2.1 or newer would cause the option to create ASP.NET Core 2.0 Web applications to disappear .
Visual Studio 2017 15.5.7 منتشر شد
مطالب
نمایش خطاهای اعتبارسنجی سمت سرور ASP.NET Core در برنامه‌های Angular
در مطلب «فرم‌های مبتنی بر قالب‌ها در Angular - قسمت چهارم - اعتبارسنجی ورودی‌ها» با نحوه‌ی تنظیمات اعتبارسنجی سمت کلاینت برنامه‌های Angular آشنا شدیم. اما اگر مدل سمت سرور ما یک چنین شکلی را داشته باشد که به همراه خطاهای اعتبارسنجی سفارشی نیز هست:
using System;
using System.ComponentModel.DataAnnotations;

namespace AngularTemplateDrivenFormsLab.Models
{
    public class Movie
    {
        public int Id { get; set; }

        [Required(ErrorMessage = "Movie Title is Required")]
        [MinLength(3, ErrorMessage = "Movie Title must be at least 3 characters")]
        public string Title { get; set; }

        [Required(ErrorMessage = "Movie Director is Required.")]
        public string Director { get; set; }

        [Range(0, 100, ErrorMessage = "Ticket price must be between 0 and 100.")]
        public decimal TicketPrice { get; set; }

        [Required(ErrorMessage = "Movie Release Date is required")]
        public DateTime ReleaseDate { get; set; }
    }
}
و همچنین کنترلر و اکشن متد دریافت کننده‌ی آن نیز به صورت ذیل تعریف شده باشد:
using AngularTemplateDrivenFormsLab.Models;
using Microsoft.AspNetCore.Mvc;

namespace AngularTemplateDrivenFormsLab.Controllers
{
    [Route("api/[controller]")]
    public class MoviesController : Controller
    {
        [HttpPost]
        public IActionResult Post([FromBody]Movie movie)
        {
            if (ModelState.IsValid)
            {
                // TODO: save ...
                return Ok(movie);
            }

            ModelState.AddModelError("", "This record already exists."); // a cross field validation
            return BadRequest(ModelState);
        }
    }
}
دو نوع خطای اعتبارسنجی سمت سرور را به سمت کلاینت ارسال خواهیم کرد:
الف) خطاهای اعتبارسنجی در سطح فیلدها
زمانیکه return BadRequest(ModelState) صورت می‌گیرد، محتویات شیء ModelState به همراه status code مساوی 400 به سمت کلاینت ارسال خواهد شد. در شیء ModelState یک دیکشنری که کلیدهای آن، نام خواص و مقادیر متناظر با آن‌ها، خطاهای اعتبارسنجی تنظیم شده‌ی در مدل است، قرار دارند.
ب) خطاهای اعتبارسنجی عمومی
در این بین می‌توان دیکشنری ModelState را توسط متد AddModelError نیز تغییر داد و برای مثال کلید آن‌را مساوی "" تعریف کرد. در این حالت یک چنین خطایی به کل فرم اشاره می‌کند و نه به یک خاصیت خاص.

نمونه‌ای از خروجی نهایی ارسالی به سمت کاربر:
 {"":["This record already exists."],"TicketPrice":["Ticket price must be between 0 and 100."]}

به همین جهت نیاز است بتوان خطاهای حالت (الف) را دقیقا در ذیل هر فیلد و خطاهای حالت (ب) را در بالای فرم به صورت عمومی به کاربر نمایش داد:



پردازش و دریافت خطاهای اعتبارسنجی سمت سرور در یک برنامه‌ی Angular

با توجه به اینکه سرور، شیء ModelState را توسط return BadRequest به سمت کلاینت ارسال می‌کند، برای پردازش دیکشنری دریافتی از سمت آن، تنها کافی است قسمت بروز خطای عملیات ارسال اطلاعات را بررسی کنیم:


در این HttpErrorResponse دریافتی، دو خاصیت error که همان آرایه‌ی دیکشنری نام خواص و پیام‌های خطای مرتبط با هر کدام و status code دریافتی مهم هستند:
  errors: string[] = [];

  processModelStateErrors(form: NgForm, responseError: HttpErrorResponse) {
    if (responseError.status === 400) {
      const modelStateErrors = responseError.error;
      for (const fieldName in modelStateErrors) {
        if (modelStateErrors.hasOwnProperty(fieldName)) {
          const modelStateError = modelStateErrors[fieldName];
          const control = form.controls[fieldName] || form.controls[this.lowerCaseFirstLetter(fieldName)];
          if (control) {
            // integrate into Angular's validation
            control.setErrors({
              modelStateError: { error: modelStateError }
            });
          } else {
            // for cross field validations -> show the validation error at the top of the screen
            this.errors.push(modelStateError);
          }
        }
      }
    } else {
      this.errors.push("something went wrong!");
    }
  }

  lowerCaseFirstLetter(data: string): string {
    return data.charAt(0).toLowerCase() + data.slice(1);
  }
توضیحات:
در اینجا از آرایه‌ی errors برای نمایش خطاهای عمومی در سطح فرم استفاده می‌کنیم. این خطاها در ModelState، دارای کلید مساوی "" هستند. به همین جهت حلقه‌ای را بر روی شیء responseError.error تشکیل می‌دهیم. به این ترتیب می‌توان به نام خواص و همچنین خطاهای متناظر با آن‌ها رسید.
 const control = form.controls[fieldName] || form.controls[this.lowerCaseFirstLetter(fieldName)];
از نام خاصیت یا فیلد، جهت یافتن کنترل متناظر با آن، در فرم جاری استفاده می‌کنیم. ممکن است کنترل تعریف شده camel case و یا pascal case باشد. به همین جهت دو حالت بررسی را در اینجا مشاهده می‌کنید.
در ادامه اگر control ایی یافت شد، توسط متد setErrors، کلید جدید modelStateError را که دارای خاصیت سفارشی error است، تنظیم می‌کنیم. با اینکار سبب خواهیم شد تا خطای اعتبارسنجی دریافتی از سمت سرور، با سیستم اعتبارسنجی Angular یکی شود. به این ترتیب می‌توان این خطا را دقیقا ذیل همین کنترل در فرم نمایش داد. اگر کنترلی یافت نشد (کلید آن "" بود و یا جزو نام کنترل‌های موجود در آرایه‌ی form.controls نبود)، این خطا را به آرایه‌ی errors اضافه می‌کنیم تا در بالاترین سطح فرم قابل نمایش شود.

نحوه‌ی استفاده‌ی از متد processModelStateErrors فوق را در متد submitForm، در قسمت شکست عملیات ارسال اطلاعات، مشاهده می‌کنید:
  model = new Movie("", "", 0, "");
  successfulSave: boolean;
  errors: string[] = [];

  constructor(private movieService: MovieService) { }

  ngOnInit() {
  }

  submitForm(form: NgForm) {
    console.log(form);

    this.errors = [];
    this.movieService.postMovieForm(this.model).subscribe(
      (data: Movie) => {
        console.log("Saved data", data);
        this.successfulSave = true;
      },
      (responseError: HttpErrorResponse) => {
        this.successfulSave = false;
        console.log("Response Error", responseError);
        this.processModelStateErrors(form, responseError);
      });
  }


نمایش خطاهای اعتبارسنجی عمومی فرم

اکنون که کار مقدار دهی آرایه‌ی errors انجام شده‌است، می‌توان حلقه‌ای را بر روی آن تشکیل داد و عناصر آن‌را در بالای فرم، به صورت عمومی و مستقل از تمام فیلدهای آن نمایش داد:
<form #form="ngForm" (submit)="submitForm(form)" novalidate>
  <div class="alert alert-danger" role="alert" *ngIf="errors.length > 0">
    <ul>
      <li *ngFor="let error of errors">
        {{ error }}
      </li>
    </ul>
  </div>
  <div class="alert alert-success" role="alert" *ngIf="successfulSave">
    Movie saved successfully!
  </div>



نمایش خطاهای اعتبارسنجی در سطح فیلدهای فرم

با توجه به تنظیم خطاهای اعتبارسنجی کنترل‌های Angular در متد processModelStateErrors و داشتن کلید جدید modelStateError
control.setErrors({
  modelStateError: { error: modelStateError }
});
اکنون می‌توان از این کلید جدید (ctrl.errors.modelStateError)، به صورت ذیل جهت نمایش خطای متناظر با آن (ctrl.errors.modelStateError.error) استفاده کرد:


<ng-template #validationErrorsTemplate let-ctrl="control">
  <div *ngIf="ctrl.invalid && ctrl.touched">
    <div class="alert alert-danger"  *ngIf="ctrl.errors.required">
      This field is required.
    </div>
    <div class="alert alert-danger"  *ngIf="ctrl.errors.minlength">
      This field should be minimum {{ctrl.errors.minlength.requiredLength}} characters.
    </div>
    <div class="alert alert-danger"  *ngIf="ctrl.errors.maxlength">
      This field should be max {{ctrl.errors.maxlength.requiredLength}} characters.
    </div>
    <div class="alert alert-danger"  *ngIf="ctrl.errors.pattern">
      This field's pattern: {{ctrl.errors.pattern.requiredPattern}}
    </div>
    <div class="alert alert-danger"  *ngIf="ctrl.errors.modelStateError">
      {{ctrl.errors.modelStateError.error}}
    </div>
  </div>
</ng-template>
چون تکرار خطاهای اعتبارسنجی در ذیل هر فیلد، فرم را بیش از اندازه شلوغ می‌کند، می‌توان توسط یک ng-template این کدهای تکراری را تبدیل به یک قالب کرد و اکنون استفاده‌ی از این قالب، به سادگی فراخوانی یک ng-container است:
  <div class="form-group" [class.has-error]="releaseDate.invalid && releaseDate.touched">
    <label class="control-label" for="releaseDate">Release Date</label>
    <input type="text" name="releaseDate" #releaseDate="ngModel"  class="form-control"
      required [(ngModel)]="model.releaseDate" />
    <ng-container *ngTemplateOutlet="validationErrorsTemplate; context:{ control: releaseDate }"></ng-container>
  </div>
در اینجا در ngTemplateOutlet، ابتدا نام قالب متناظر ذکر می‌شود و سپس در context آن، نام خاصیت control را که توسط قالب دریافت می‌شود، به template reference variable متناظری تنظیم می‌کنیم، تا به کنترل جاری اشاره کند. به این ترتیب می‌توان به فرم‌هایی خلوت‌تر و با قابلیت مدیریت بهتری رسید.


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