بازخوردهای دوره
تزریق وابستگی‌های AutoMapper در لایه سرویس برنامه
ممنون از شما،
یک سوال: بنده کلاس ObjectFactory را همانطور که فرمودید به  این  صورت تغییر دادم. در لایه سرویس نیز این متد را تهیه کرده‌ام:
public IList<AdvertismentViewModel> GetAdvertisementsByMe(int userId)
{
            var adsList = _advertisements.Where(x => x.UserId == userId).ToList();
            var adsViewModel = new List<AdvertismentViewModel>();
            _mappingEngine.Map(source: adsList, destination: adsViewModel);
            return adsViewModel;
}
در متد فوق کلاس Advertisment به کلاس زیر نگاشت داده شده است:
public class AdvertismentViewModel
    {
        public string Image { get; set; }
        public string Title { get; set; }
        public string ExpireDate { get; set; }
    }
اما با فراخوانی متد GetAdvertisementsByMe استثناء AutoMapperMappingException صادر می‌شود:
Missing type map configuration or unsupported mapping.

Mapping types:
Advertisement -> AdvertismentViewModel
Project.DomainClasses.Advertisement -> Project.Models.AdvertismentViewModel

Destination path:
List`1[0]

Source value:
System.Data.Entity.DynamicProxies.Advertisement_E82DFF273E08C95AA785F8F7A0D2B5ABC8E54C4566DFE1C8A92D8D3C447608AE

مطالب
تولید SiteMap استاندارد و ایجاد یک ActionResult اختصاصی برای Return کردن SiteMap تولید شده
یکی از item‌های مهم در بهینه سازی SEO یک وب‌سایت وجود یک SiteMap استاندارد متشکل از لینک‌های موجود در سایت هست که در وب‌سایت‌های داینامیک معمولا این لینک‌ها بر اساس داده‌های موجود در بانک اطلاعاتی ایجاد میشه. برای مثال مطالب، اخبار و ....
در اینجا بنده قبلا یک کلاس برای تولید SiteMap آماده کردم که در پروژه‌های خودم ازش استفاده میکنم. توسط این کلاس میتونید به صورت داینامیک SiteMap وب‌سایت مبتنی بر ASP.NET MVC خودتون رو ایجاد کنید.

برای آشنایی با ساختار یک SiteMap استاندارد میتونید به لینک رسمی روبرو مراجعه کنید : http://www.sitemaps.org/de/protocol.html 

بنده کلاس‌های زیر رو بر مبنای لینک مذکور در سایت رسمی SiteMaps تولید کردم.  بعد از تولید SiteMap نیاز دارید که اون رو مثلا به عنوان خروجی یک ActionResult بازگردونید. برای این کار هم یک کلاس با نام XmlResult  مشتق شده از ActionResult آماده سازی کردم که کلاس تولید شده SiteMap رو Serialize میکنه و به عنوان نتیجه‌ی یک Action باز می‌گردونه .
using System;
using System.Collections;
using System.Web.Mvc;
using System.Xml.Serialization;

namespace Neoox.Core.SeoTools
{
    [XmlRoot("urlset", Namespace = "http://www.sitemaps.org/schemas/sitemap/0.9")]
    public class Sitemap
    {
        private ArrayList map;

        public Sitemap()
        {
            map = new ArrayList();
        }

        [XmlElement("url")]
        public Location[] Locations
        {
            get
            {
                Location[] items = new Location[map.Count];
                map.CopyTo(items);
                return items;
            }
            set
            {
                if (value == null)
                    return;
                Location[] items = (Location[])value;
                map.Clear();
                foreach (Location item in items)
                    map.Add(item);
            }
        }

        public int Add(Location item)
        {
            return map.Add(item);
        }
    }

    public class Location
    {
        public enum eChangeFrequency
        {
            always,
            hourly,
            daily,
            weekly,
            monthly,
            yearly,
            never
        }

        [XmlElement("loc")]
        public string Url { get; set; }

        [XmlElement("changefreq")]
        public eChangeFrequency? ChangeFrequency { get; set; }
        public bool ShouldSerializeChangeFrequency() { return ChangeFrequency.HasValue; }

        [XmlElement("lastmod")]
        public DateTime? LastModified { get; set; }
        public bool ShouldSerializeLastModified() { return LastModified.HasValue; }

        [XmlElement("priority")]
        public double? Priority { get; set; }
        public bool ShouldSerializePriority() { return Priority.HasValue; }
    }

    public class XmlResult : ActionResult
    {
        private object objectToSerialize;

        public XmlResult(object objectToSerialize)
        {
            this.objectToSerialize = objectToSerialize;
        }

        public object ObjectToSerialize
        {
            get { return this.objectToSerialize; }
        }

        public override void ExecuteResult(ControllerContext context)
        {
            if (this.objectToSerialize != null)
            {
                context.HttpContext.Response.Clear();
                var xs = new System.Xml.Serialization.XmlSerializer(this.objectToSerialize.GetType());
                context.HttpContext.Response.ContentType = "text/xml";
                xs.Serialize(context.HttpContext.Response.Output, this.objectToSerialize);
            }
        }
    }
}
 و اما نحوه استفاده از این کلاس‌ها هم خیلی سادست. به مثال زیر توجه کنید ... فقط این نکته رو در نظر داشته باشید که item هایی که به sitemap اضافه میشه واکشی شده از بانک اطلاعاتی هست، در این مثال چون خواستم ساده توضیح داده بشه نحوه استفاده از این کلاس‌ها، این داده‌ها به صورت static در نظر گرفته شد ولی شما میتونید داده‌ها رو بر اساس ساختار بانک اطلاعاتی خودتون واکشی کرده و به SiteMap اضافه کنید تا یک SiteMap کاملا پویا و Dynamic داشته باشید...
        public ActionResult Sitemap()
        {
            Sitemap sm = new Sitemap();
            sm.Add(new Location()
            {
                Url = string.Format("http://www.TechnoDesign.ir/Articles/{0}/{1}", 1, "SEO-in-ASP.NET-MVC"),
                LastModified = DateTime.UtcNow,
                Priority = 0.5D
            });
            return new XmlResult(sm);
        }
مطالب
روش دیگر نوشتن Model binderهای سفارشی در ASP.NET Core 7x با معرفی IParseable
ASP.NET MVC از روش بکارگیری binding providerها برای تدارک زیرساخت model binding استفاده می‌کند که در این روش، داده‌های پارامترهای یک action method از طریق هدرها، کوئری استرینگ‌ها، بدنه‌ی درخواست و غیره تهیه می‌شوند. در حالت پیش‌فرض اگر این پارامترها از نوع‌های ساده‌ای مانند اعداد و یا DateTime تشکیل شده باشند و یا به همراه یک TypeConverter باشند که امکان تبدیل این رشته را به آن نوع خاص بدهد، به صورت خودکار bind خواهند شد و هر نوع دیگری، به صورت یک نوع پیچیده درنظر گرفته می‌شود. نوع پیچیده یعنی bind برای مثال اطلاعات بدنه‌ی درخواست به تک تک خواص آن نوع. برای نمونه در کنترلر زیر:
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
    private static readonly string[] Summaries =
    {
        "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy",
        "Hot", "Sweltering", "Scorching",
    };

    // /WeatherForecast/GetForecast2?from=1&to=3
    [HttpGet("[action]")]
    public IEnumerable<WeatherForecast> GetForecast2(Duration days)
    {
        return Enumerable.Range(days.From, days.To)
                         .Select(index => new WeatherForecast
                                          {
                                              Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
                                              TemperatureC = Random.Shared.Next(-20, 55),
                                              Summary = Summaries[Random.Shared.Next(Summaries.Length)],
                                          })
                         .ToArray();
    }
}
که از دو مدل زیر استفاده می‌کند:
public class Duration
{
    public int From { get; set; }
    public int To { get; set; }
}

public class WeatherForecast
{
    public DateOnly Date { get; set; }

    public int TemperatureC { get; set; }

    public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);

    public string? Summary { get; set; }
}
می‌توان خواص پارامتر days را از طریق کوئری استرینگ‌های HttpGet، برای مثال با ارائه‌ی آدرس WeatherForecast/GetForecast2?from=1&to=3 به صورت خودکار تامین کرد. زمانیکه اطلاعات رسیده چنین شکلی را داشته باشند، کار پردازش و bind آن‌ها در حالت HttpGet، خودکار است.


روش دیگر پردازش اطلاعات رشته‌ای رسیده و تشکیل یک Model Binder سفارشی در ASP.NET Core 7x

اکنون فرض کنید بجای آدرس WeatherForecast/GetForecast2?from=1&to=3 که اطلاعات را از طریق کوئری استرینگ مشخص و استانداردی دریافت می‌کند، می‌خواهیم اطلاعات آن‌را از طریق یک قالب سفارشی و غیراستاندارد مانند WeatherForecast/GetForecast3/1-3 دریافت کنیم:
// /WeatherForecast/GetForecast3/1-3
[HttpGet("[action]/{days}")]
public IEnumerable<WeatherForecast> GetForecast3(Days days)
    {
        return Enumerable.Range(days.From, days.To)
                         .Select(index => new WeatherForecast
                                          {
                                              Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
                                              TemperatureC = Random.Shared.Next(-20, 55),
                                              Summary = Summaries[Random.Shared.Next(Summaries.Length)],
                                          })
                         .ToArray();
    }
یکی از راه‌های انجام اینکار، نوشتن model binderهای سفارشی مخصوص است و یا اکنون در ASP.NET Core 7x می‌توان با پیاده سازی اینترفیس IParsable به صورت خودکار و با روشی دیگر به این مقصود رسید:
using System.Diagnostics.CodeAnalysis;
using System.Globalization;

namespace NET7Mvc.Models;

public class Days : IParsable<Days>
{
    public Days(int from, int to)
    {
        From = from;
        To = to;
    }

    public int From { get; }
    public int To { get; }

    public static bool TryParse([NotNullWhen(true)] string? value,
                                IFormatProvider? provider,
                                [MaybeNullWhen(false)] out Days result)
    {
        var items = value?.Split('-', StringSplitOptions.RemoveEmptyEntries);
        if (items is { Length: 2 })
        {
            if (int.TryParse(items[0], NumberStyles.None, provider, out var from)
                && int.TryParse(items[1], NumberStyles.None, provider, out var to))
            {
                result = new Days(from, to);
                return true;
            }
        }

        result = default;
        return false;
    }

    public static Days Parse(string value, IFormatProvider? provider)
    {
        if (!TryParse(value, provider, out var result))
        {
            throw new ArgumentException("Could not parse the given value.", nameof(value));
        }

        return result;
    }
}
- برای پیاده سازی این اینترفیس باید دو متد TryParse و Parse آن‌را به صورت فوق پیاده سازی کرد و توسط آن، روش تبدیل رشته‌ی دریافتی از کاربر را به شیء مدنظر، مشخص کرد.
- همینقدر که مدلی IParsable را پیاده سازی کرده باشد، از امکانات آن به صورت خودکار استفاده خواهد شد و نیازی به معرفی و یا تنظیمات خاص دیگری ندارد.
- البته این قابلیت جدید نیست و پشتیبانی از IParsable، پیشتر در Minimal API دات نت 6 ارائه شده بود؛ اما در دات نت 7 توسط ASP.NET Core MVC نیز قابل استفاده شده‌است.
نظرات مطالب
مقابله با XSS ؛ یکبار برای همیشه!
سلام 
من یک سوال داشتم . من از کلاس‌های شما در برنامه ام استفاده کردم مدل من به شرح زیر است :
  public class Contact
    {
        public int Id { get; set; }
        [AllowHtml]
        public string Name { get; set; }
        public string Address { get; set; }
    }
و برای تست اکشن‌های زیر رو داخل کنترلر نوشتم :
 [HttpGet]
        public ActionResult ContactUs()
        {
            return View();
        }
        [HttpPost]
        public ActionResult ContactUs(Contact con)
        {

            ViewBag.Name = con.Name.ToSafeHtml();
            ViewBag.MyAddress = con.Address;
            return View();
        }

خوب همه چیز به درستی کار می‌کنه . اما سوالات من به شرح زیر است :
۱ - آیا واقعا نیاز نیست پروپرتی هایی رو که AllowHTML نیستند رو کنترل کنیم ؟ خود Asp.net MVC جلوی این قضیه رو می‌گیره ؟
۲ - اگر ما یک پروپرتی رو AllowHtml نگذاریم و قصد وارد کردن یک تگ Script داشته باشیم با خطای زیر مواجه می‌شویم :
A potentially dangerous Request.Form value was detected from the client (Name="<script>alert("aaa")...").
چطور این پیام رو سیستم نشون نده و یک پیام سفارشی نشون بده ؟
۳ - از متد‌های دیگه ای که در کلاس‌های شما است در کجا‌ها با مثال عملیاتی می‌شه استفاده کرد ؟
تشکر از لطفتون
نظرات مطالب
چگونگی دسترسی به فیلد و خاصیت غیر عمومی
آیا می‌توان به کمک رفلکشن به خصوصیتی که مثلا Set ندارد مقدار دهی کرد. بعنوان مثال
class Program
{
  static void Main(string[] args)
  {
      Book book = new Book();
      var codeprop = book.GetType().GetProperty("Code", BindingFlags.Public | BindingFlags.Instance);
      codeprop.SetValue(book, 20, null);
      var value = codeprop.GetValue(book, null);
  }
}

public class Book
{
  public int code;
  public int Code
  {
      get { return code; }
  }
}
مطالب
شروع به کار با EF Core 1.0 - قسمت 5 - استراتژهای تعیین کلید اصلی جداول و ایندکس‌ها
پس از بررسی نحوه‌ی انجام تنظیمات اولیه‌ی کار با EF Core و همچنین آشنایی با مهاجرت‌های آن، مرحله‌ی بعد، مرحله‌ی مدلسازی داده‌ها است و اولین مرحله‌ی آن، نحوه‌ی تعیین کلید اصلی جداول است که در این زمینه، EF Core پیشرفت‌هایی قابل ملاحظه‌ای را نسبت به EF 6.x داشته‌است. در EF 6.x تنها دو حالت کلیدهای اصلی خود افزاینده که توسط بانک اطلاعاتی مدیریت می‌شوند و یا تولید کلید اصلی در سمت کلاینت و توسط برنامه، پشتیبانی می‌شوند. در EF Core، مواردی مانند Sequence و Alternate keys نیز اضافه شده‌اند.


پیش فرض‌های تعیین کلید اصلی در EF Core

به صورت پیش فرض هر خاصیتی که به نام Id و یا type name>Id> باشد، به عنوان primary key تفسیر خواهد شد؛ مانند:
public class Car
{
    public string Id { get; set; }
و یا
public class Car
{
   public string CarId { get; set; }
در مثال اول، نام خاصیت، Id است و در مثال دوم، جمع نام کلاس به همراه Id ذکر شده‌است. یک چنین مواردی، نیازی به تنظیم اضافه‌تری ندارند.


نحوه‌ی تعیین کلید اصلی به صورت صریح

اگر یکی از دو حالت فوق برقرار نباشند، باید کلید اصلی را به نحو صریحی مشخص کرد.
الف) از طریق ویژگی‌ها
public class Car
{
   [Key]
   public string LicensePlate { get; set; }
در اینجا چون LicensePlate نه Id نام دارد و نه جمع نام کلاس به همراه Id است، باید به نحو صریحی توسط ویژگی Key مشخص شود.
ب) با استفاده از روش Fluent API
public class MyContext : DbContext
{
    public DbSet<Car> Cars { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
         modelBuilder.Entity<Car>()
                 .HasKey(c => c.LicensePlate);
    }
 }
روش تنظیم کلید اصلی به صورت صریح، از طریق کدنویسی است که به آن Fluent API یا API روان هم گفته می‌شود. برای اینکار باید متد OnModelCreating کلاس Context برنامه را بازنویسی کرد و سپس از طریق متد HasKey، نام خاصیت کلید اصلی را ذکر نمود.


پیشنیاز کار با ویژگی‌ها در EF Core

در اسمبلی که مدل‌های موجودیت‌ها شما قرار دارند، نیاز است وابستگی System.ComponentModel.Annotations به فایل project.json پروژه اضافه شود، تا ویژگی‌هایی مانند Key، شناسایی و قابل استفاده شوند:
{
   "dependencies": {
          "System.ComponentModel.Annotations": "4.1.0"
   }
}


تعیین کلید ترکیبی و یا Composite key

اگر نیاز است چندین خاصیت را به صورت کلید اصلی معرفی کرد که به آن composite key هم می‌گویند، تنها روش ممکن، استفاده از Fluent API و به صورت زیر است:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
   modelBuilder.Entity<Car>()
                       .HasKey(c => new { c.State, c.LicensePlate });
}
در قسمت HasKey می‌توان چندین خاصیت را نیز جهت تعیین کلید ترکیبی مشخص کرد.


روش‌های مختلف تولید خودکار مقادیر خواص

حالت پیش فرض تولید مقدار فیلدهای Id عددی، همان حالت خود افزاینده‌ای است که توسط بانک اطلاعاتی کنترل می‌شود و یا کلید اصلی که از نوع Guid تعیین شود نیز به صورت خودکار توسط بانک اطلاعاتی در حین عملیات Add، مقدار دهی می‌شود (با استفاده از الگوریتم Guid سری در SQL Server).
 اگر این حالات مطلوب شما نیست، حالت‌های سه گانه‌ی ذیل را می‌توان استفاده کرد:

الف) هیچ داده‌ی خودکاری تولید نشود
برای اینکار می‌توان با استفاده از ویژگی DatabaseGenerated و تنظیم مقدار آن به None، جلوی تولید خودکار کلید اصلی را گرفت. در این حالت باید هم در حین عملیات Add و هم در حین عملیات Update، مقادیر را خودتان مقدار دهی کنید:
public class Blog
{
    [DatabaseGenerated(DatabaseGeneratedOption.None)]
    public int BlogId { get; set; }

    public string Url { get; set; }
}
و یا معادل این تنظیم با استفاده از Fluent API به صورت ذیل است:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
           .Property(b => b.BlogId)
           .ValueGeneratedNever();
}

ب) تولید داده‌های خودکار فقط در حالت Add
حالت Add به این معنا است که داده‌های خواص مشخصی، برای موجودیت‌های «جدید»، به صورت خودکار تولید خواهند شد. اینکه آیا واقعا این مقادیر به صورت خودکار تولید می‌شوند یا خیر، صرفا وابسته‌است به بانک اطلاعاتی در حال استفاده. برای مثال SQL Server برای نوع‌های Guid، به صورت خودکار با کمک الگوریتم SQL Server sequential GUID، کار مقدار دهی یک چنین فیلدهایی را انجام می‌دهد.
این فیلدها باید توسط ویژگی DatabaseGenerated و با مقدار Identity مشخص شوند. در اینجا Identity به معنای فیلدهایی است که به صورت خودکار توسط بانک اطلاعاتی مقدار دهی می‌شوند و الزاما به کلید اصلی اشاره نمی‌کنند. برای مثال در موجودیت ذیل، خاصیت تاریخ ثبت رکورد، از نوع Identity مشخص شده‌است. به این معنا که در حین ثبت اولیه‌ی رکورد آن، نیازی نیست تا خاصیت Inserted را مقدار دهی کرد. اما اینکه آیا SQL Server یک چنین کاری را به صورت خودکار انجام می‌دهد، پاسخ آن خیر است. SQL server فقط برای فیلدهای عددی و Guid ایی که با DatabaseGeneratedOption.Identity مزین شده باشند، مقادیر متناظری را به صورت خودکار تولید می‌کند. برای حالت DateTime نیاز است، مقدار پیش فرض فیلد را صریحا مشخص کرد که توسط ویژگی‌ها میسر نیست و فقط fluent API از آن پشتیبانی می‌کند.
public class Blog
{
   public int BlogId { get; set; }
   public string Url { get; set; }

   [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
   public DateTime Inserted { get; set; }
}
و یا معادل این تنظیم با استفاده از Fluent API به صورت ذیل است:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
           .Property(b => b.Inserted)
           .ValueGeneratedOnAdd();
}
برای تعیین مقدار پیش فرض خاصیت Inserted به نحوی که توسط SQL Server به صورت خودکار مقدار دهی شود، می‌توان از متد HasDefaultValueSql به نحو ذیل استفاده کرد:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .Property(b => b.Inserted)
        .HasDefaultValueSql("getdate()");
}
البته باید درنظر داشت که اگر خاصیت DateTime تعریف شده در اینجا به همین نحو بکاربرده شود، اگر مقداری برای آن در حین تعریف یک وهله جدید از کلاس Blog درکدهای برنامه درنظر گرفته نشود، یک مقدار پیش فرض حداقل به آن انتساب داده خواهد شد (چون value type است). بنابراین نیاز است این خاصیت را از نوع nullable تعریف کرد (public DateTime? Inserted).

یک نکته: در حالت DatabaseGeneratedOption.Identity و یا ValueGeneratedOnAdd فوق، اگر مقداری به این نوع فیلدها انتساب داده شده باشد که با مقدار پیش فرض آن‌ها (property.ClrType.GetDefaultValue) متفاوت باشد، از این مقدار جدید، بجای تولید مقداری خودکار، استفاده خواهد شد. برای مثال مقدار پیش فرض رشته‌ها، نال، مقادیر عددی، صفر و برای Guid مقدار Guid.Empty است. اگر هر مقدار دیگری بجای این‌ها به فیلدهای فوق انتساب داده شوند، از آن‌ها استفاده می‌شود.

ج) تولید داده‌های خودکار در هر دو حالت Add و Update
تولید داده‌ها در حالت‌های Add و Update به این معنا است که یک چنین خواصی، همواره با فراخوانی متد SaveChanges، دارای مقدار خودکار جدیدی خواهند شد و نیازی نیست در کدها مقدار دهی شوند. برای مشخص سازی این نوع خواص، از ویژگی DatabaseGenerated با مقدار Computed و یا متد ValueGeneratedOnAddOrUpdate در حالت Fluent API می‌توان استفاده کرد:
public class Blog
{
    public int BlogId { get; set; }
    public string Url { get; set; }

    [DatabaseGenerated(DatabaseGeneratedOption.Computed)]
    public DateTime LastUpdated { get; set; }
}
و یا معادل این تنظیم با استفاده از Fluent API به صورت ذیل است:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
       .Property(b => b.LastUpdated)
       .ValueGeneratedOnAddOrUpdate();
}
همانطور که پیشتر نیز عنوان شد، تولید خودکار مقادیر فیلدها فقط در حالت‌های int و Guid انجام می‌شود (که برای مثال SQL Server از آن‌ها پشتیبانی می‌کند). در مثال فوق، خاصیت LastUpdated از نوع DateTime اینگونه تعریف شده‌است و SQL Server برای یک چنین فیلدهای خاصی، مقدار خودکاری را تولید نکرده و به دنبال مقدار پیش فرض آن می‌گردد. بنابراین در اینجا نیز باید مشخص سازی HasDefaultValueSql("getdate()") را که در قسمت قبل عنوان کردیم، صراحتا در قسمت تنظیمات Fluent API ذکر و تنظیم کرد.

تذکر: در اینجا نیز همانند حالت ValueGeneratedOnAdd، اگر این خواص مشخص شده، دارای مقدار متفاوتی با مقدار پیش فرض آن‌ها باشند، از این مقادیر جدید بجای تولید خودکار مقادیر استفاده خواهد شد.


خواص محاسباتی (Computed Columns) و تفاوت آن‌ها با DatabaseGeneratedOption.Computed

خواص محاسباتی (Computed Columns)، خواصی هستند که مقادیر آن‌ها در بانک اطلاعاتی محاسبه می‌شوند و کاملا متفاوت هستند با DatabaseGeneratedOption.Computed که مفهوم دیگری دارد. DatabaseGeneratedOption.Computed به این معنا است که این فیلد خاص، با هر بار فراخوانی SaveChanges باید مقدار محاسبه شده‌ی جدیدی را داشته باشد و روش تولید این مقدار خودکار، یا بر اساس Guidهای سری است، یا توسط فیلدهای خود افزاینده‌ی عددی و یا از طریق مقادیر پیش فرضی مانند getdate در حین ثبت یا به روز رسانی، مقدار دهی می‌شوند. اما خواص محاسباتی، یکی از امکانات «گزارشگیری سریع» SQL Server هستند و به نحو ذیل، تنها توسط Fluent API قابل تنظیم می‌باشند:
public class Person
{
    public int PersonId { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string DisplayName { get; set; }
}

public class MyContext : DbContext
{
    public DbSet<Person> People { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
          modelBuilder.Entity<Person>()
              .Property(p => p.DisplayName)
               .HasComputedColumnSql("[LastName] + ', ' + [FirstName]");
     }
 }
در اینجا فیلد DisplayName یک فیلد محاسباتی بوده و از حاصل جمع دو فیلد دیگر در سمت دیتابیس تشکیل می‌شود. این نگاشت و محاسبه چون در سمت بانک اطلاعاتی انجام می‌شود، بازدهی بیشتری دارد نسبت به حالتی که ابتدا دو فیلد به کلاینت منتقل شده و سپس در این سمت جمع زده شوند.


امکان تعریف Sequence در EF Core 1.0

Sequence قابلیتی است که به SQL Server 2012 اضافه شده‌است و توضیحات بیشتر آن‌را در مطلب «نحوه ایجاد Sequence و استفاده آن در Sql Server 2012» می‌توانید مطالعه کنید.
در EF Core، امکان مدلسازی Sequence نیز پیش بینی شده‌است. آن‌ها به صورت پیش فرض در مدل‌ها ذکر نمی‌شوند و همچنین وابستگی به جدول خاصی ندارند. به همین جهت امکان تعریف آن‌ها صرفا توسط Fluent API وجود دارد:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
     modelBuilder.HasSequence<int>("OrderNumbers", schema: "shared") 
           .StartsAt(1000).IncrementsBy(5);

     modelBuilder.Entity<Order>()
         .Property(o => o.OrderNo)
         .HasDefaultValueSql("NEXT VALUE FOR shared.OrderNumbers");
}
پس از اینکه یک Sequence  تعریف شد، می‌توان برای نمونه از آن جهت تولید مقادیر پیش فرض ستون‌ها استفاده کرد.
در مثال فوق، ابتدا یک Sequence نمونه به نام OrderNumbers تعریف شده‌است که از عدد 1000 شروع شده و واحد افزایش آن 5 است. سپس از این نام در قسمت مقدار پیش فرض ستون OrderNo استفاده شده‌است.

و یا از Sequence ‌ها می‌توان برای تعیین مقدار پیش فرض Primary key بجای حالت identity خود افزایش یابنده استفاده کرد:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.HasSequence<int>("PrimaryKeyWithSequenceSequence");
    modelBuilder.Entity<PrimaryKeyWithSequence>(entity =>
     {
       entity.Property(e => e.PrimaryKeyWithSequenceId).HasDefaultValueSql("NEXT VALUE FOR [PrimaryKeyWithSequenceSequence]");
     });
}
در اینجا یک توالی از نوع int تعریف شده و سپس هربار که قرار است رکوردی درج شود، مقدار id آن به صورت خودکار از طریق کوئری Select NEXT VALUE FOR
[PrimaryKeyWithSequenceSequence] دریافت و سپس بجای فیلد id درج می‌شود.

به این روش الگوریتم Hi-Low هم می‌گویند که یکی از مهم‌ترین اهداف آن داشتن یک سری Id منحصربفرد، جهت بالابردن سرعت insertها در یک batch است. در حالت عادی insertها، ابتدا یک insert انجام می‌شود، سپس کوئری گرفته شده و آخرین Id درج شده به کلاینت بازگشت داده می‌شود. این روش، برای انجام تنها یک insert، سریع است. اما برای batch insert، به شدت کارآیی پایینی دارد. به همین جهت دسترسی به بازه‌ای از اعداد منحصربفرد، پیش از شروع به insert تعداد زیادی رکورد، سرعت نهایی کار را بالا می‌برد.


نحوه‌ی تعریف ایندکس‌ها در EF Core 1.0

برای افزودن ایندکس‌ها به EF Core 1.0، تنها روش میسر، استفاده از Fluent API است (و برخلاف EF 6.x از روش data annotations فعلا پشتیبانی نمی‌کند؛ هرچند API جدید آن نسبت به EF 6.x بسیار واضح‌تر است و با ابهامات کمتر).
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
      modelBuilder.Entity<Blog>()
          .HasIndex(b => b.Url)
          .HasName("Index_Url");
اگر قسمت HasName را ذکر نکنید، نام آن <IX_<type name>_<property name درنظر گرفته می‌شود و برای اینکه ایندکس منحصربفردی را تعریف کنید، می‌توان متد IsUnique را به انتهای این زنجیره اضافه کرد:
 modelBuilder.Entity<Blog>().HasIndex(b => b.Url).HasName("Index_Url").IsUnique();
همچنین می‌توان همانند composite keys، در اینجا نیز ترکیبی از خواص را به صورت یک ایندکس معرفی نمود:
modelBuilder.Entity<Person>()
   .HasIndex(idx => new { idx.FirstName, idx.LastName })
   .IsUnique();
در این حالت اگر HasName ذکر نشود، نام آن همانند الگویی است که پیشتر عنوان شد؛ با این تفاوت که قسمت property name آن، جمع نام تمام خواص ذکر شده و جدا شده‌ی با _ خواهد بود.

یک نکته: اگر از پروایدر SQL Server استفاده می‌کنید، می‌توان متد الحاقی ویژه‌ای را به نام ForSqlServerIsClustered نیز برای تعریف clustered indexes، در این زنجیره ذکر کرد.


امکان تعریف Alternate Keys در EF Core 1.0

به Unique Constraints در EF Core، نام Alternate Keys را داده‌اند و این مورد نیز تنها از طریق Fluent API قابل تنظیم است:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
   modelBuilder.Entity<Car>()
     .HasAlternateKey(c => c.LicensePlate)
     .HasName("AlteranteKey_LicensePlate");
}
برای یک Alternate Key به صورت خودکار هم ایندکس ایجاد می‌شود و هم اینکه این ایندکس منحصربفرد خواهد بود.
اگر متد HasName در اینجا ذکر نشود، نام پیش فرض آن  <type name>_<property name> خواهد بود و اگر همانند composite keys و یا ایندکس‌های ترکیبی، چند خاصیت ذکر شوند، قسمت property name به جمع نام تمام خواص ذکر شده و جدا شده‌ی با _ تنظیم می‌شود.
برای نمونه اگر یک Alternate Key ترکیبی را به صورت ذیل تعریف کنیم:
modelBuilder.Entity<Person>()
     .HasAlternateKey(x => new { x.FirstName, x.LastName });
در قسمت مهاجرت‌هایی که قرار است به بانک اطلاعاتی اعمال شوند، به یک UniqueConstraint ترجمه می‌شود:
 table.UniqueConstraint("AK_Persons_FirstName_LastName", x => new { x.FirstName, x.LastName });


سؤال: یک Unique Constraint با Unique Index چه تفاوتی دارد؟

در پشت صحنه، پیاده سازی یک Unique Constraint با Unique Index تفاوتی ندارند. فقط از دیدگاه روشن‌تر شدن مقصود، استفاده‌ی از Unique Constraint ترجیح داده می‌شود.
البته از دیدگاه بانک اطلاعاتی پیاده سازی کننده نیز برای نمونه SQL Server، این تفاوت‌ها وجود دارند:
الف) یک Unique Constraint را نمی‌توان غیرفعال کرد؛ برخلاف Unique Indexها.
ب) Unique Constraint‌ها موارد اضافه‌تری را مانند FILLFACTOR و IGNORE_DUP_KEY نیز می‌توانند تنظیم کنند.
ج) امکان تعریف فیلترها برای Unique Indexها وجود دارد؛ برخلاف Unique Constraint ها.

که البته از دیدگاه EF، این سه مورد اهمیتی ندارند و بیشتر روشن‌تر شدن مقصود، هدف اصلی آن‌ها است.
مطالب
آشنایی با Refactoring - قسمت 11

قسمت یازدهم آشنایی با Refactoring به توصیه‌هایی جهت بالا بردن خوانایی تعاریف مرتبط با اعمال شرطی می‌پردازد.

الف) شرط‌های ترکیبی را کپسوله کنید

عموما حین تعریف شرط‌های ترکیبی، هدف اصلی از تعریف آن‌ها پشت انبوهی از && و || گم می‌شود و برای بیان مقصود، نیاز به نوشتن کامنت خواهند داشت. مانند:

using System;

namespace Refactoring.Day11.EncapsulateConditional.Before
{
public class Element
{
private string[] Data { get; set; }
private string Name { get; set; }
private int CreatedYear { get; set; }

public string FindElement()
{
if (Data.Length > 1 && Name == "E1" && CreatedYear > DateTime.Now.Year - 1)
return "Element1";

if (Data.Length > 2 && Name == "RCA" && CreatedYear > DateTime.Now.Year - 2)
return "Element2";

return string.Empty;
}
}
}

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

using System;

namespace Refactoring.Day11.EncapsulateConditional.After
{
public class Element
{
private string[] Data { get; set; }
private string Name { get; set; }
private int CreatedYear { get; set; }

public string FindElement()
{
if (hasOneYearOldElement)
return "Element1";

if (hasTwoYearsOldElement)
return "Element2";

return string.Empty;
}

private bool hasTwoYearsOldElement
{
get { return Data.Length > 2 && Name == "RCA" && CreatedYear > DateTime.Now.Year - 2; }
}

private bool hasOneYearOldElement
{
get { return Data.Length > 1 && Name == "E1" && CreatedYear > DateTime.Now.Year - 1; }
}
}
}


همانطور که ملاحظه می‌کنید پس از این جایگزینی، خوانایی متد FindElement بهبود یافته است و برنامه نویس اگر 6 ماه بعد به این کدها مراجعه کند نخواهد گفت: «من این کدها رو نوشتم؟!»؛ چه برسد به سایرینی که احتمالا قرار است با این کدها کار کرده و یا آن‌ها را نگهداری کنند.


ب) از تعریف خواص Boolean با نام‌های منفی پرهیز کنید

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

namespace Refactoring.Day11.RemoveDoubleNegative.Before
{
public class Customer
{
public decimal Balance { get; set; }

public bool IsNotFlagged
{
get { return Balance > 30m; }
}
}
}

همچنین از تعریف این نوع خواص در فایل‌های کانفیگ برنامه‌ها نیز جدا پرهیز کنید؛ چون عموما کاربران برنامه‌ها با این نوع نامگذاری‌های منفی، مشکل مفهومی دارند.
Refactoring قطعه کد فوق بسیار ساده است و تنها با معکوس کردن شرط و نحوه‌ی نامگذاری خاصیت IsNotFlagged پایان می‌یابد:

namespace Refactoring.Day11.RemoveDoubleNegative.After
{
public class Customer
{
public decimal Balance { get; set; }

public bool IsFlagged
{
get { return Balance <= 30m; }
}
}
}

مطالب
تنظیمات و نکات کاربردی کتابخانه‌ی JSON.NET
پس از بررسی مقدماتی امکانات کتابخانه‌ی JSON.NET، در ادامه به تعدادی از تنظیمات کاربردی آن با ذکر مثال‌هایی خواهیم پرداخت.


گرفتن خروجی CamelCase از JSON.NET

یک سری از کتابخانه‌های جاوا اسکریپتی سمت کلاینت، به نام‌های خواص CamelCase نیاز دارند و حالت پیش فرض اصول نامگذاری خواص در دات نت عکس آن است. برای مثال بجای UserName به userName نیاز دارند تا بتوانند صحیح کار کنند.
روش اول حل این مشکل، استفاده از ویژگی JsonProperty بر روی تک تک خواص و مشخص کردن نام‌های مورد نیاز کتابخانه‌ی جاوا اسکریپتی به صورت صریح است.
روش دوم، استفاده از تنظیمات ContractResolver می‌باشد که با تنظیم آن به CamelCasePropertyNamesContractResolver به صورت خودکار به تمامی خواص به صورت یکسانی اعمال می‌گردد:
var json = JsonConvert.SerializeObject(obj, new JsonSerializerSettings
{
   ContractResolver = new CamelCasePropertyNamesContractResolver()
});


درج نام‌های المان‌های یک Enum در خروجی JSON

اگر یکی از عناصر در حال تبدیل به JSON، از نوع enum باشد، به صورت پیش فرض مقدار عددی آن در JSON نهایی درج می‌گردد:
using Newtonsoft.Json;

namespace JsonNetTests
{
    public enum Color
    {
        Red,
        Green,
        Blue,
        White
    }

    public class Item
    {
        public string Name { set; get; }
        public Color Color { set; get; }
    }

    public class EnumTests
    {
        public string GetJson()
        {
            var item = new Item
            {
                Name = "Item 1",
                Color = Color.Blue 
            };

            return JsonConvert.SerializeObject(item, Formatting.Indented);
        }
    }
}
با این خروجی:
{
  "Name": "Item 1",
  "Color": 2
}
اگر علاقمند هستید که بجای عدد 2، دقیقا مقدار Blue در خروجی JSON درج گردد، می‌توان به یکی از دو روش ذیل عمل کرد:
الف) مزین کردن خاصیت از نوع enum به ویژگی JsonConverter از نوع StringEnumConverter:
  [JsonConverter(typeof(StringEnumConverter))]
  public Color Color { set; get; }
ب) و یا اگر می‌خواهید این تنظیم به تمام خواص از نوع enum به صورت یکسانی اعمال شود، می‌توان نوشت:
return JsonConvert.SerializeObject(item, new JsonSerializerSettings
{
   Formatting = Formatting.Indented,
   Converters = { new StringEnumConverter() }
});


تهیه خروجی JSON از مدل‌های مرتبط، بدون Stack overflow

دو کلاس گروه‌های محصولات و محصولات ذیل را درنظر بگیرید:
   public class Category
    {
        public int Id { get; set; }
        public string Name { get; set; }

        public virtual ICollection<Product> Products { get; set; }

        public Category()
        {
            Products = new List<Product>();
        }
    }

    public class Product
    {
        public int Id { get; set; }
        public string Name { get; set; }

        public virtual Category Category { get; set; }
    }
این نوع طراحی در Entity framework بسیار مرسوم است. در اینجا طرف‌های دیگر یک رابطه، توسط خاصیتی virtual معرفی می‌شوند که به آن‌ها خواص راهبری یا navigation properties هم می‌گویند.
با توجه به این دو کلاس، سعی کنید مثال ذیل را اجرا کرده و از آن، خروجی JSON تهیه کنید:
using System.Collections.Generic;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;

namespace JsonNetTests
{
    public class SelfReferencingLoops
    {
        public string GetJson()
        {
            var category = new Category
            {
                Id = 1,
                Name = "Category 1"
            };
            var product = new Product
            {
                Id = 1,
                Name = "Product 1"
            };

            category.Products.Add(product);
            product.Category = category;

            return JsonConvert.SerializeObject(category, new JsonSerializerSettings
            {
                Formatting = Formatting.Indented,
                Converters = { new StringEnumConverter() }
            });
        }
    }
}
برنامه با این استثناء متوقف می‌شود:
 An unhandled exception of type 'Newtonsoft.Json.JsonSerializationException' occurred in Newtonsoft.Json.dll
Additional information: Self referencing loop detected for property 'Category' with type 'JsonNetTests.Category'. Path 'Products[0]'.
اصل خطای معروف فوق «Self referencing loop detected» است. در اینجا کلاس‌هایی که به یکدیگر ارجاع می‌دهند، در حین عملیات Serialization سبب بروز یک حلقه‌ی بازگشتی بی‌نهایت شده و در آخر، برنامه با خطای stack overflow خاتمه می‌یابد.

راه حل اول:
به تنظیمات JSON.NET، مقدار ReferenceLoopHandling = ReferenceLoopHandling.Ignore را اضافه کنید تا از حلقه‌ی بازگشتی بی‌پایان جلوگیری شود:
return JsonConvert.SerializeObject(category, new JsonSerializerSettings
{
   Formatting = Formatting.Indented,
   ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
   Converters = { new StringEnumConverter() }
});
راه حل دوم:
به تنظیمات JSON.NET، مقدار PreserveReferencesHandling = PreserveReferencesHandling.Objects را اضافه کنید تا مدیریت ارجاعات اشیاء توسط خود JSON.NET انجام شود:
return JsonConvert.SerializeObject(category, new JsonSerializerSettings
{
   Formatting = Formatting.Indented,
   PreserveReferencesHandling = PreserveReferencesHandling.Objects,
   Converters = { new StringEnumConverter() }
});
خروجی حالت دوم به این شکل است:
{
  "$id": "1",
  "Id": 1,
  "Name": "Category 1",
  "Products": [
    {
      "$id": "2",
      "Id": 1,
      "Name": "Product 1",
      "Category": {
        "$ref": "1"
      }
    }
  ]
}
همانطور که ملاحظه می‌کنید، دو خاصیت $id و $ref توسط JSON.NET به خروجی JSON اضافه شده‌است تا توسط آن بتواند ارجاعات و نمونه‌های اشیاء را تشخیص دهد.