نظرات مطالب
ASP.NET MVC #18
ببخشید، من منظورم کلاس RoleService است،  اینترفیس IRoleService را به این صورت تعریف کرده ام :
public interface IRoleService
    {
        bool IsUserInRole(string username, string roleName);
        string[] GetRolesForUser(string username);
        void AddUsersToRoles(string[] usernames, string[] roleNames);
        string ApplicationName { get; set; }
        void CreateRole(string roleName);
        bool DeleteRole(string roleName, bool throwOnPopulatedRole);
        string[] FindUsersInRole(string roleName, string usernameToMatch);
        string[] GetAllRoles();
        string[] GetUsersInRole(string roleName);
        void RemoveUsersFromRoles(string[] usernames, string[] roleNames);
        bool RoleExists(string roleName);
    }
کلاس CustomRoleProvider هم که همونطور که فرمودید باید پیاده سازی کنیم، حالا من منظورم کلاس RoleService است :
public class RoleService: IRoleService
{
        private readonly IDbSet<Role> _role;
        private readonly IUnitOfWork _uow;
        public RoleService(IUnitOfWork uow)
        {
            _uow = uow;
            _role = uow.Set<Role>();
        }
        public bool IsUserInRole(string username, string roleName)
        {
            throw new NotImplementedException();
        }
        public string[] GetRolesForUser(string username)
        {
            throw new NotImplementedException();
        }
//...
}

مطالب
سری بررسی SQL Smell در EF Core - ایجاد روابط Polymorphic - بخش دوم
در مطلب قبل نحوه‌ی ایجاد روابط Polymorphic را بررسی کردیم و همچنین چندین راه‌حل جایگزین را نیز ارائه دادیم. همانطور که عنوان شد این نوع روابط اساساً از لحاظ طراحی دیتابیس اصولی نیستند و تا حد امکان نباید استفاده شوند. این نوع روابط بیشتر ORM friendly هستند و اکثر فریم‌ورک‌های غیردات‌نتی به عنوان یک گزینه‌ی توکار، امکان ایجاد این روابط را فراهم میکنند. به عنوان مثال درLaravel Eloquent ORM به صورت توکار از این قابلیت پشتیبانی می‌شود: 
<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Comment extends Model
{
    /**
     * Get the owning commentable model.
     */
    public function commentable()
    {
        return $this->morphTo();
    }
}

class Post extends Model
{
    /**
     * Get all of the post's comments.
     */
    public function comments()
    {
        return $this->morphMany('App\Comment', 'commentable');
    }
}

class Video extends Model
{
    /**
     * Get all of the video's comments.
     */
    public function comments()
    {
        return $this->morphMany('App\Comment', 'commentable');
    }
}

اما در Entity Framework Core هنوز این قابلیت پیاده‌سازی نشده است. در اینجا می‌توانید وضعیت پیاده‌سازی Polymorphic Association را پیگیری کنید. پیاده‌سازی این قابلیت از این جهت مهم است که امکان کوئری نویسی را برای این نوع روابط ساده‌تر خواهد کرد به عنوان مثال در کدهای PHP فوق جهت واکشی کامنت‌های یک مطلب می‌توانیم اینگونه عمل کنیم:
$post = App\Post::find(1);

foreach ($post->comments as $comment) {
    //
}

همچنین امکان واکشی owner این رابطه را نیز حین کار با کامنت‌ها را خواهیم داشت:
$comment = App\Comment::find(1);

$commentable = $comment->commentable;

در ادامه می‌خواهیم معادل LINQ آن را پیاده‌سازی کنیم. در مطلب قبل مدل Comment این چنین ساختاری داشت:
public enum CommentType
{
    Article,
    Video,
    Event
}

public class Comment
{
    public int Id { get; set; }
    public string CommentText { get; set; }
    public string User { get; set; }
    public int? TypeId { get; set; }
    public CommentType CommentType { get; set; }
}

در اینجا همانطور که مشاهده میکنید هیچگونه ارتباط معناداری بین Comment و همچنین owner رابطه (که ممکن است هر کدام از مقادیر Enum فوق باشد) وجود ندارد. اگر این مدل به تنها یک مدل مثلاً Article اشاره داشته باشد، نیاز به تعیین Navigation Property در دو طرف رابطه خواهد بود:
public class Comment
{
    public int Id { get; set; }
    public string CommentText { get; set; }
    public string User { get; set; }

    public virtual Article Article { get; set; }
    public int ArticleId { get; set; }
}

public class Article
{
    public int Id { get; set; }
    public string Title { get; set; }
    public string Slug { get; set; }
    public string Description { get; set; }
    
    public virtual ICollection<Article> Articles { get; set; }
}

اما از آنجائیکه رابطه یک حالت پویا دارد، نمی‌توانیم به صورت صریح نوع ارجاعات را در دو طرف رابطه تعیین کنیم. برای داشتن همچین قابلیتی می‌توانیم Navigation Property را به صورت [NotMapped] تعیین کنیم که EF Core آنها را در نظر نگیرید. بنابراین به صورت دستی عملکرد آنها را پیاده‌سازی خواهیم کرد. برای اینکار می‌توانیم یک اینترفیس با عنوان ICommentable را تعریف کنیم و برای هر مدلی که نیاز به قابلیت کامنت دارد، این اینترفیس را پیاده‌سازی کنیم. همچنین یک ارجاع به لیستی از کامنت‌ها را به صورت Navigation Property به هر کدام از مدلها نیز اضافه خواهیم کرد:  
interface ICommentable
{
    int Id { get; set; }
}

public class Article : ICommentable
{
    public int Id { get; set; }
    public string Title { get; set; }
    public string Slug { get; set; }
    public string Description { get; set; }
    
    [NotMapped]
    public ICollection<Comment> Comments { get; set; }
}

public class Video : ICommentable
{
    public int Id { get; set; }
    public string Url { get; set; }
    public string Description { get; set; }
    
    [NotMapped]
    public ICollection<Comment> Comments { get; set; }
}

public class Event : ICommentable
{
    public int Id { get; set; }
    public string Name { get; set; }
    public DateTimeOffset? Start { get; set; }
    public DateTimeOffset? End { get; set; }
    
    [NotMapped]
    public ICollection<Comment> Comments { get; set; }
}

سپس درون مدل Comment ارجاع به Polymorphic relation را نیز به صورت [NotMapped] پیاده‌سازی خواهیم کرد:
public class Comment
{
    public int Id { get; set; }
    public string CommentText { get; set; }
    public string User { get; set; }
    public int? TypeId { get; set; }
    public CommentType CommentType { get; set; }

    ICommentable _parent;

    [NotMapped]
    public ICommentable Parent
    {
        get => _parent;
        set
        {
            _parent = value;
            TypeId = value.Id;
            CommentType = (CommentType) Enum.Parse(typeof(CommentType), value.GetType().Name);
        }
    }
}

کاری که در بالا انجام شده، تنظیم تایپ مدلی است که می‌خواهیم واکشی کنیم. یعنی به محض مقداردهی، پراپرتی Comments مدل مورد نظر به همراه Id و در نهایت نوع آن را تنظیم کرده‌ایم. اکنون برای واکشی کامنت‌های یک مطلب خواهیم داشت:
var article = dbContext.Articles.Find(1);
            
article.Comments = dbContext.Comments
    .Where(c => c.TypeId == article.Id
                && c.CommentType == CommentType.Article)
    .ToList();

foreach (var comment in article.Comments)
    comment.Parent = article;

foreach (var comment in article.Comments)
{
    Console.WriteLine($"{comment.User} - ${comment.CommentText} - {((Article) comment.Parent).Title}");
}

همانطور که مشاهده می‌کنید اکنون می‌توانیم از هر دو طرف رابطه به اطلاعات موردنیازمان دسترسی داشته باشیم.
نظرات مطالب
یکپارچه سازی سیستم اعتبارسنجی ASP.NET MVC با Kendo UI validator
من برای فعال سازی remote validation از پروژه  Moon استفاده کردم اما باز هم عمل نمیکنه!
  [Moon.Web.Validation.RemoteValidator("IsUserExist", "Admin", HttpMethod = "POST", ErrorMessage = "نام کاربری قبلا ثبت شده است.")]
        public string Username { get; set; }
  @Html.EditorFor(model => model.Username)
  @Html.ValidationMessageFor(model => model.Username)
  [HttpPost]
        public ActionResult IsUserExist(string Username)
        {
            if (userSerive.isUser(Username)) return Json(true); 
            return Json(false);
        }

مطالب
بررسی مقدمات کتابخانه‌ی JSON.NET
چرا JSON.NET؟
JSON.NET یک کتابخانه‌ی سورس باز کار با اشیاء JSON در دات نت است. تاریخچه‌ی آن به 8 سال قبل بر می‌گردد و توسط یک برنامه نویس نیوزیلندی به نام James Newton King تهیه شده‌است. اولین نگارش آن در سال 2006 ارائه شد؛ مقارن با زمانی که اولین استاندارد JSON نیز ارائه گردید.
این کتابخانه از آن زمان تا کنون، 6 میلیون بار دانلود شده‌است و به علت کیفیت بالای آن، این روزها پایه اصلی بسیاری از کتابخانه‌ها و فریم ورک‌های دات نتی می‌باشد؛ مانند RavenDB تا ASP.NET Web API و SignalR مایکروسافت و همچنین گوگل نیز از آن جهت تدارک کلاینت‌های کار با API خود استفاده می‌کنند.
هرچند دات نت برای نمونه در نگارش سوم آن جهت مصارف WCF کلاسی را به نام DataContractJsonSerializer ارائه کرد، اما کار کردن با آن محدود است به فرمت خاص WCF به همراه عدم انعطاف پذیری و سادگی کار با آن. به علاوه باید درنظر داشت که JSON.NET از دات نت 2 به بعد تا مونو، Win8 و ویندوز فون را نیز پشتیبانی می‌کند.

برای نصب آن نیز کافی است دستور ذیل را در کنسول پاورشل نیوگت اجرا کنید:
 PM> install-package Newtonsoft.Json

معماری JSON.NET

کتابخانه‌ی JSON.NET از سه قسمت عمده تشکیل شده‌است:
الف) JsonSerializer
ب) LINQ to JSON
ج) JSON Schema


الف) JsonSerializer
کار JsonSerializer تبدیل اشیاء دات نتی به JSON و برعکس است. مزیت مهم آن امکانات قابل توجه تنظیم عملکرد و خروجی آن می‌باشد که این تنظیمات را به شکل ویژگی‌های خواص نیز می‌توان اعمال نمود. به علاوه امکان سفارشی سازی هر کدام نیز توسط کلاسی به نام JsonConverter، پیش بینی شده‌است.
یک مثال:
 var roles = new List<string>
{
   "Admin",
   "User"
};
string json = JsonConvert.SerializeObject(roles, Formatting.Indented);
در اینجا نحوه‌ی استفاده از JSON.NET را جهت تبدیل یک شیء دات نتی، به معادل JSON آن مشاهده می‌کنید. اعمال تنظیم Formatting.Indented سبب خواهد شد تا خروجی آن دارای Indentation باشد. برای نمونه اگر در برنامه‌ی خود قصد دارید فرمت JSON تو در تویی را به نحو زیبا و خوانایی نمایش دهید یا چاپ کنید، همین تنظیم ساده کافی خواهد بود.
و یا در مثال ذیل استفاده از یک anonymous object را مشاهده می‌کنید:
 var jsonString = JsonConvert.SerializeObject(new
{
   Id =1,
   Name = "Test"
}, Formatting.Indented);
به صورت پیش فرض تنها خواص عمومی کلاس‌ها توسط JSON.NET تبدیل خواهند شد.


تنظیمات پیشرفته‌تر JSON.NET

مزیت مهم JSON.NET بر سایر کتابخانه‌ها‌ی موجود مشابه، قابلیت‌های سفارشی سازی قابل توجه آن است. در مثال ذیل نحوه‌ی معرفی JsonSerializerSettings را مشاهده می‌نمائید:
var jsonData = JsonConvert.SerializeObject(new
{
   Id = 1,
   Name = "Test",
   DateTime = DateTime.Now
}, new JsonSerializerSettings
{
   Formatting = Formatting.Indented,
   Converters =
   {
      new JavaScriptDateTimeConverter()
   }
});
در اینجا با استفاده از تنظیم JavaScriptDateTimeConverter، می‌توان خروجی DateTime استانداردی را به مصرف کنندگان جاوا اسکریپتی سمت کاربر ارائه داد؛ با خروجی ذیل:
 {
  "Id": 1,
  "Name": "Test",
  "DateTime": new Date(1409821985245)
}


نوشتن خروجی JSON در یک استریم

خروجی متد JsonConvert.SerializeObject یک رشته‌است که در صورت نیاز به سادگی توسط متد File.WriteAllText در یک فایل قابل ذخیره می‌باشد. اما برای رسیدن به حداکثر کارآیی و سرعت می‌توان از استریم‌ها نیز استفاده کرد:
using (var stream = File.CreateText(@"c:\output.json"))
{
    var jsonSerializer = new JsonSerializer
   {
      Formatting = Formatting.Indented
   };
   jsonSerializer.Serialize(stream, new
   {
     Id = 1,
     Name = "Test",
     DateTime = DateTime.Now
   });
}
کلاس JsonSerializer و متد Serialize آن یک استریم را نیز جهت نوشتن خروجی می‌پذیرند. برای مثال response.Output برنامه‌های وب نیز یک استریم است و در اینجا نوشتن مستقیم در استریم بسیار سریعتر است از تبدیل شیء به رشته و سپس ارائه خروجی آن؛ زیرا سربار تهیه رشته JSON از آن حذف می‌گردد و نهایتا GC کار کمتری را باید انجام دهد.


تبدیل JSON رشته‌ای به اشیاء دات نت

اگر رشته‌ی jsonData ایی را که پیشتر تولید کردیم، بخواهیم تبدیل به نمونه‌ای از شیء User ذیل کنیم:
public class User
{
   public int Id { set; get; }
   public string Name { set; get; }
   public DateTime DateTime { set; get; }
}
خواهیم داشت:
 var user = JsonConvert.DeserializeObject<User>(jsonData);
در اینجا از متد DeserializeObject به همراه مشخص سازی صریح نوع شیء نهایی استفاده شده‌است.
البته در اینجا با توجه به استفاده از JavaScriptDateTimeConverter برای تولید jsonData، نیاز است چنین تنظیمی را نیز در حالت DeserializeObject مشخص کنیم:
var user = JsonConvert.DeserializeObject<User>(jsonData, new JsonSerializerSettings
{
   Converters = {  new JavaScriptDateTimeConverter() }
});


مقدار دهی یک نمونه یا وهله‌ی از پیش موجود

متد JsonConvert.DeserializeObject یک شیء جدید را ایجاد می‌کند. اگر قصد دارید صرفا تعدادی از خواص یک وهله‌ی موجود، توسط JSON.NET مقدار دهی شوند از متد PopulateObject استفاده کنید:
 JsonConvert.PopulateObject(jsonData, user);


کاهش حجم JSON تولیدی

زمانیکه از متد JsonConvert.SerializeObject استفاده می‌کنیم، تمام خواص عمومی تبدیل به معادل JSON آن‌ها خواهند شد؛ حتی خواصی که مقدار ندارند. این خواص در خروجی JSON، با مقدار null مشخص می‌شوند. برای حذف این خواص از خروجی JSON نهایی تنها کافی است در تنظیمات JsonSerializerSettings، مقدار NullValueHandling = NullValueHandling.Ignore مشخص گردد.
var jsonData = JsonConvert.SerializeObject(object, new JsonSerializerSettings
{
   NullValueHandling = NullValueHandling.Ignore,
   Formatting = Formatting.Indented
});
مورد دیگری که سبب کاهش حجم خروجی نهایی خواهد شد، تنظیم DefaultValueHandling = DefaultValueHandling.Ignore است. در این حالت کلیه خواصی که دارای مقدار پیش فرض خودشان هستند، در خروجی JSON ظاهر نخواهند شد. مثلا مقدار پیش فرض خاصیت int مساوی صفر است. در این حالت کلیه خواص از نوع int که دارای مقدار صفر می‌باشند، در خروجی قرار نمی‌گیرند.
به علاوه حذف Formatting = Formatting.Indented نیز توصیه می‌گردد. در این حالت فشرده‌ترین خروجی ممکن حاصل خواهد شد.


مدیریت ارث بری توسط JSON.NET

در مثال ذیل کلاس کارمند و کلاس مدیر را که خود نیز در اصل یک کارمند می‌باشد، ملاحظه می‌کنید:
public class Employee
{
    public string Name { set; get; }
}

public class Manager : Employee
{
    public IList<Employee> Reports { set; get; }
}
در اینجا هر مدیر لیست کارمندانی را که به او گزارش می‌دهند نیز به همراه دارد. در ادامه نمونه‌ای از مقدار دهی این اشیاء ذکر شده‌اند:
 var employee = new Employee { Name = "User1" };
var manager1 = new Manager { Name = "User2" };
var manager2 = new Manager { Name = "User3" };
manager1.Reports = new[] { employee, manager2 };
manager2.Reports = new[] { employee };
با فراخوانی
 var list = JsonConvert.SerializeObject(manager1, Formatting.Indented);
یک چنین خروجی JSON ایی حاصل می‌شود:
{
  "Reports": [
    {
      "Name": "User1"
    },
    {
      "Reports": [
        {
          "Name": "User1"
        }
      ],
      "Name": "User3"
    }
  ],
  "Name": "User2"
}
این خروجی JSON جهت تبدیل به نمونه‌ی معادل دات نتی خود، برای مثال جهت رسیدن به manager1 در کدهای فوق، چندین مشکل را به همراه دارد:
- در اینجا مشخص نیست که این اشیاء، کارمند هستند یا مدیر. برای مثال مشخص نیست User2 چه نوعی دارد و باید به کدام شیء نگاشت شود.
- مشکل دوم در مورد کاربر User1 است که در دو قسمت تکرار شده‌است. این شیء JSON اگر به نمونه‌ی معادل دات نتی خود نگاشت شود، به دو وهله از User1 خواهیم رسید و نه یک وهله‌ی اصلی که سبب تولید این خروجی JSON شده‌است.

برای حل این دو مشکل، تغییرات ذیل را می‌توان به JSON.NET اعمال کرد:
var list = JsonConvert.SerializeObject(manager1, new JsonSerializerSettings
{
   Formatting = Formatting.Indented,
   TypeNameHandling = TypeNameHandling.Objects,
   PreserveReferencesHandling = PreserveReferencesHandling.Objects
});
با این خروجی:
{
  "$id": "1",
  "$type": "JsonNetTests.Manager, JsonNetTests",
  "Reports": [
    {
      "$id": "2",
      "$type": "JsonNetTests.Employee, JsonNetTests",
      "Name": "User1"
    },
    {
      "$id": "3",
      "$type": "JsonNetTests.Manager, JsonNetTests",
      "Reports": [
        {
          "$ref": "2"
        }
      ],
      "Name": "User3"
    }
  ],
  "Name": "User2"
}
- با تنظیم TypeNameHandling = TypeNameHandling.Objects سبب خواهیم شد تا خاصیت اضافه‌ای به نام $type به خروجی JSON اضافه شود. این نوع، در حین فراخوانی متد JsonConvert.DeserializeObject جهت تشخیص صحیح نگاشت اشیاء بکار گرفته خواهد شد و اینبار مشخص است که کدام شیء، کارمند است و کدامیک مدیر.
- با تنظیم PreserveReferencesHandling = PreserveReferencesHandling.Objects شماره Id خودکاری نیز به خروجی JSON اضافه می‌گردد. اینبار اگر به گزارش دهنده‌ها با دقت نگاه کنیم، مقدار $ref=2 را خواهیم دید. این مورد سبب می‌شود تا در حین نگاشت نهایی، دو وهله متفاوت از شیء با Id=2 تولید نشود.

باید دقت داشت که در حین استفاده از JsonConvert.DeserializeObject نیز باید JsonSerializerSettings یاد شده، تنظیم شوند.


ویژگی‌های قابل تنظیم در JSON.NET

علاوه بر JsonSerializerSettings که از آن صحبت شد، در JSON.NET امکان تنظیم یک سری از ویژگی‌ها به ازای خواص مختلف نیز وجود دارند.
- برای نمونه ویژگی JsonIgnore معروفترین آن‌ها است:
public class User
{
   public int Id { set; get; }

   [JsonIgnore]
   public string Name { set; get; }

   public DateTime DateTime { set; get; }
}
JsonIgnore سبب می‌شود تا خاصیتی در خروجی نهایی JSON تولیدی حضور نداشته باشد و از آن صرفنظر شود.

- با استفاده از ویژگی JsonProperty اغلب مواردی را که پیشتر بحث کردیم مانند NullValueHandling، TypeNameHandling و غیره، می‌توان تنظیم نمود. همچنین گاهی از اوقات کتابخانه‌های جاوا اسکریپتی سمت کاربر، از اسامی خاصی که از روش‌های نامگذاری دات نتی پیروی نمی‌کنند، در طراحی خود استفاده می‌کنند. در اینجا می‌توان نام خاصیت نهایی را که قرار است رندر شود نیز صریحا مشخص کرد. برای مثال:
[JsonProperty(PropertyName = "m_name", NullValueHandling = NullValueHandling.Ignore)]
public string Name { set; get; }
همچنین در اینجا امکان تنظیم Order نیز وجود دارد. برای مثال مشخص کنیم که خاصیت X در ابتدا قرار گیرد و پس از آن خاصیت Y رندر شود.

- استفاده از ویژگی JsonObject به همراه مقدار OptIn آن به این معنا است که از کلیه خواصی که دارای ویژگی JsonProperty نیستند، صرفنظر شود. حالت پیش فرض آن OptOut است؛ یعنی تمام خواص عمومی در خروجی JSON حضور خواهند داشت منهای مواردی که با JsonIgnore مزین شوند.
[JsonObject(MemberSerialization.OptIn)]
public class User
{
    public int Id { set; get; }

    [JsonProperty]
    public string Name { set; get; }
 
    public DateTime DateTime { set; get; }
}

- با استفاده از ویژگی JsonConverter می‌توان نحوه‌ی رندر شدن مقدار خاصیت را سفارشی سازی کرد. برای مثال:
[JsonConverter(typeof(JavaScriptDateTimeConverter))]
public DateTime DateTime { set; get; }


تهیه یک JsonConverter سفارشی

با استفاده از JsonConverterها می‌توان کنترل کاملی را بر روی اعمال serialization و deserialization مقادیر خواص اعمال کرد. مثال زیر را در نظر بگیرید:
public class HtmlColor
{
   public int Red { set; get; }
   public int Green { set; get; }
   public int Blue { set; get; }
}

var colorJson = JsonConvert.SerializeObject(new HtmlColor
{
  Red = 255,
  Green = 0,
  Blue = 0
}, Formatting.Indented);
در اینجا علاقمندیم، در حین عملیات serialization، بجای اینکه مقادیر اجزای رنگ تهیه شده به صورت int نمایش داده شوند، کل رنگ با فرمت hex رندر شوند. برای اینکار نیاز است یک JsonConverter سفارشی را تدارک دید:
    public class HtmlColorConverter : JsonConverter
    {

        public override bool CanConvert(Type objectType)
        {
            return objectType == typeof(HtmlColor);
        }

        public override object ReadJson(JsonReader reader, Type objectType,
                                        object existingValue, JsonSerializer serializer)
        {
            throw new NotSupportedException();
        }

        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        {
            var color = value as HtmlColor;
            if (color == null)
                return;

            writer.WriteValue("#" + color.Red.ToString("X2")
                + color.Green.ToString("X2") + color.Blue.ToString("X2"));
        }
    }
کار با ارث بری از کلاس پایه JsonConverter شروع می‌شود. سپس باید تعدادی از متدهای این کلاس پایه را بازنویسی کرد. در متد CanConvert اعلام می‌کنیم که تنها اشیایی از نوع کلاس HtmlColor را قرار است پردازش کنیم. سپس در متد WriteJson منطق سفارشی خود را می‌توان پیاده سازی کرد.
از آنجائیکه این تبدیلگر صرفا قرار است برای حالت serialization استفاده شود، قسمت ReadJson آن پیاده سازی نشده‌است.

در آخر برای استفاده از آن خواهیم داشت:
var colorJson = JsonConvert.SerializeObject(new HtmlColor
{
  Red = 255,
  Green = 0,
  Blue = 0
},  new JsonSerializerSettings
    {
      Formatting = Formatting.Indented,
      Converters = { new HtmlColorConverter() }
    });   
نظرات مطالب
ASP.NET MVC #13
سلام آقای نصیری خسته نباشید
من الان واسه ثبت کاربر توی دیتابیس یه کلاس دارم به اسم CustomProfile که تمامی خصوصیت‌های یک کاربر رو توی یه شیئ از این کلاس میریزم و بعد توی کدهای لایه مدل، این شیئ رو توسط کلاس Membership و ProfileBase خود دات نت توی پایگاه داده میریزیم. الان منظور شما اینه که من باید برای ویرایش یک کلاس CustomProfile دیگه با همون مشخصات بسازم و متد اعتبار سنجی برای Username اون رو یه متد دیگه قرار بدم ؟ ببینید کدهای من اینه:
public class CustomProfile
    {
        [Required(ErrorMessage="*")]
        [StringLength(50)]
        [System.Web.Mvc.Remote(action: "CheckUserName",
                                       controller: "User",
                                       HttpMethod = "POST",
                                       ErrorMessage = "این نام کاربری وجود دارد")]
        public string Username{ get; set; }
        [Required(ErrorMessage = "*")]
        public string Password{ get; set; }
        [Required(ErrorMessage = "*")]
        public string FirstName{ get; set; }
        [Required(ErrorMessage = "*")]
        public string LastName{ get; set; }
        
        [Required(ErrorMessage = "*")]
        [StringLength(10, ErrorMessage = "کدملی باید 10 رقم باشد")]
        public string NationalCode { get; set; }
        [Required(ErrorMessage = "*")]
        public string Address { get; set; }

        [RegularExpression(@"\w+([-+.']\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*",
                           ErrorMessage = "آدرس ایمیل معتبر نیست")]
        [System.Web.Mvc.Remote(action: "CheckEmail",
                                       controller: "User",
                                       AdditionalFields = "Username",
                                       HttpMethod = "POST",
                                       ErrorMessage = "این ایمیل وجود دارد")]
        public string Email{ get; set; }

        [StringLength(11, ErrorMessage = "تلفن باید 11 رقم باشد")]
        public string PhoneNo{ get; set; }

        [StringLength(11, ErrorMessage = "موبایل باید 11 رقم باشد")]
        public string MobileNo{ get; set; }
        
        
        public string[] Roles{ get; set; }
        
        public string LastActivityDate{ get; set; }
        public string LastLoginDate { get; set; }
        public string CreationDate { get; set; }
        public bool IsLockedOut{ get; set; }
    }
این کد کلاسم بود. من توی View هام اومدم و این کلاس را به عنوان ViewModel خودم قرار دادم. و وقتی فرم Create و یا Edit رو Submit میکنم متد اعتبار سنجی من وارد عمل میشه
اینم کدViewها:
@model Sama.Models.CustomProfile
@{
    ViewBag.Title = "Create";
    Layout = "~/Views/Shared/_Layout.cshtml";
    
}
<script src="@Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script>
@using (Html.BeginForm(actionName: "Create", controllerName: "User"))
    {
        @Html.ValidationSummary(true)
     
        <fieldset>
            <legend>کاربر جدید</legend>
            <div>
                <table>
                    <tr>
                        <td>
                            <div>
                                @Html.LabelFor(model => model.Username, "نام کاربری")
                            </div>
                        </td>
                        <td>
                            <div>
                                @Html.EditorFor(model => model.Username)
                                @Html.ValidationMessageFor(model => model.Username)
                            </div>
                        </td>
                    </tr>
                    <tr>
                        <td>
                            <div>
                                @Html.LabelFor(model => model.Password, "کلمه عبور")
                            </div>
                        </td>
                        <td>
                            <div>
                                @Html.PasswordFor(model => model.Password)
                                @Html.ValidationMessageFor(model => model.Password)
                            </div>
                        </td>
                    </tr>
                    <tr>
                        <td>
                            <div>
                                @Html.LabelFor(model => model.FirstName, "نام")
                            </div>
                        </td>
                        <td>
                            <div>
                                @Html.EditorFor(model => model.FirstName)
                                @Html.ValidationMessageFor(model => model.FirstName)
                            </div>
                        </td>
                    </tr>
                    <tr>
                        <td>
                            <div>
                                @Html.LabelFor(model => model.LastName, "نام خانوادگی")
                            </div>
                        </td>
                        <td>
                            <div>
                                @Html.EditorFor(model => model.LastName)
                                @Html.ValidationMessageFor(model => model.LastName)
                            </div>
                        </td>
                    </tr>
                    <tr>
                        <td>
                            <div>
                                @Html.LabelFor(model => model.NationalCode, "کد ملی")
                            </div>
                        </td>
                        <td>
                            <div>
                                @Html.EditorFor(model => model.NationalCode)
                                @Html.ValidationMessageFor(model => model.NationalCode)
                            </div>
                        </td>
                    </tr>
                    <tr>
                        <td>
                            <div>
                                @Html.LabelFor(model => model.Email, "ایمیل")
                            </div>
                        </td>
                        <td>
                            <div>
                                @Html.EditorFor(model => model.Email)
                                @Html.ValidationMessageFor(model => model.Email)
                            </div>
                        </td>
                    </tr>
                    <tr>
                        <td>
                            <div>
                                @Html.LabelFor(model => model.PhoneNo, "تلفن")
                            </div>
                        </td>
                        <td>
                            <div>
                                @Html.EditorFor(model => model.PhoneNo)
                                @Html.ValidationMessageFor(model => model.PhoneNo)
                            </div>
                        </td>
                    </tr>
                    <tr>
                        <td>
                            <div>
                                @Html.LabelFor(model => model.MobileNo, "موبایل")
                            </div>
                        </td>
                        <td>
                            <div>
                                @Html.EditorFor(model => model.MobileNo)
                                @Html.ValidationMessageFor(model => model.MobileNo)
                            </div>
                        </td>
                    </tr>
                    <tr>
                        <td>
                            <div>
                                @Html.LabelFor(model => model.Address, "آدرس")
                            </div>
                        </td>
                        <td>
                            <div>
                                @Html.TextAreaFor(model => model.Address, 5, 30, null)
                                @Html.ValidationMessageFor(model => model.Address)
                            </div>
                        </td>
                    </tr>
                    <tr>
                        <td>
                            نقش‌ها </td>
                        <td>
                            @Helper.CheckBoxList("Roles", (List<SelectListItem>)ViewBag.Roles)
                        </td>
                    </tr>
                    <tr>
                        <td>
                        </td>
                        <td>
                            <input type="submit" value="ذخیره" class="btn btn-primary" />
                        </td>
                    </tr>
                </table>
            </div>
        </fieldset>
    
    }
این View برای Create بود و برای Edit هم مشابه همینه
حالا اگر من درست متوجه شده باشم باید برای Edit ، از یک ViewModel دیگه مثلا CustomProfileEdit استفاده کنم و اونجا متد اعتبار سنجی دیگه ای تعریف کنم. منظور شما همین بود ؟
مطالب
نحوه استفاده از Text template ها در دات نت - قسمت سوم
 خوب در دو قسمت قبلی (^ و ^) با T4 و قواعد کد نویسی Text Template آشنا شدید. در این قسمت برخی مفاهیم را با یک مثال کاربردی‌تر بررسی می‌کنیم.
در ادامه قواعد زیر را در نظر بگیرید :
1 - برای استفاده از یک کتابخانه خارجی (dll) در داخل کد Text Template از بلوک <#@ assembly #> استفاده می‌شود .
مثلا برای استفاده از System.xml کد <#@ assembly name="System.xml" #> رو قرار بدید .
2- برای import کردن یک فضای نام نیز می‌توانید از بلوک #> <#@ استفاده نمایید .
به عنوان مثال : <#@ import namespace="System.Data" #>
3- encoding خروجی قابل تظیم می‌باشد. مثال: <#@ " output  extension = ".html" encoding = "utf-8 #>
4- برای استفاده از یک فایل tt درون فایل دیگر میتوان از include  استفاده کرد. مثال :   <#@ " include file=" testpath \basetest.tt #>
 
حالا به مثال زیر توجه کنید:

در بسیاری از پروژه‌ها، معادل تمامی جداول موجود در یک دیتابیس Class هایی به نام DTO  یا Data transfer object ساخته میشود که عموما" کلاسهای سبکی هستند که فقط شامل خصوصیت‌های معادل فیلدهای جداول می‌باشند و از آن‌ها جهت مدل کردن داده‌ها و ... استفاده می‌گردد. تولید این کلاسهای ساده می‌تواند بصورت اتوماتیک صورت گیرد و از این جهت در زمان تولید پروژه صرفه جویی شود. همچنین با تغییر ساختار دیتابیس می‌توان همواره کلاسها را بروزرسانی کرد.  نمونه ای از کد T4 برای تولید تمامی کلاسهای DTO به شکل زیر میباشد . فقط برای تست کد زیر دقت کنید که ConnectionString  را در داخل کد متناسب با دیتابیس خود تغییر دهید:  
<#@ template language="C#" debug="True" hostspecific="True" #>
<#@ output extension=".cs" #>
<#@ assembly name="System.Data" #>

<#@ assembly name="System.xml" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ import namespace="System.Data.SqlClient" #>
<#@ import namespace="System.Data" #>

using System;
namespace MyProject.Entities 
{    
        <#
            string connectionString = "Password=22125110;Persist Security Info=True;User ID=sa;Initial Catalog=DnnDB;Data Source=ABBASPOOR299"; 
            SqlConnection conn = new SqlConnection(connectionString); 
            conn.Open(); 
            System.Data.DataTable schema = conn.GetSchema("TABLES"); 
            string selectQuery = "select * from @tableName"; 
            SqlCommand command = new SqlCommand(selectQuery,conn); 
            SqlDataAdapter ad = new SqlDataAdapter(command); 
            System.Data.DataSet ds = new DataSet(); 
            
            foreach(System.Data.DataRow row in schema.Rows) 
            { 
            #> 
            public class <#= row["TABLE_NAME"].ToString().Trim('s') #>                
            
            {
                <#                 
                    command.CommandText = selectQuery.Replace("@tableName",row["TABLE_NAME"].ToString()); 
                    ad.FillSchema(ds, SchemaType.Mapped, row["TABLE_NAME"].ToString());

                    foreach (DataColumn dc in ds.Tables[row["TABLE_NAME"].ToString()].Columns)
                    {
                        #>
                        
                    private <#= dc.DataType.Name  #> _<#= dc.ColumnName.Replace(dc.ColumnName[0].ToString(), dc.ColumnName[0].ToString().ToLower())      #>;
                    public <#= dc.DataType.Name #> <#= dc.ColumnName #> 
                    {
                        get { return _<#= dc.ColumnName.Replace(dc.ColumnName[0].ToString(), dc.ColumnName[0].ToString().ToLower()) #>; } 
                        set { _<#= dc.ColumnName.Replace(dc.ColumnName[0].ToString(), dc.ColumnName[0].ToString().ToLower()) #> = value; }
                    }                    
                    
                <#    }                 #>
                
            }                
            
            <#    
            }  #>            
}
خوب؛ خودتون می‌توانید تست کنید ... این یکی از رایج‌ترین استفاده‌های T4 Text Template هست که برنامه نویسان از آن استفاده می‌کنند. 
مطالب
React 16x - قسمت 20 - کار با فرم‌ها - بخش 3 - بهبود کیفیت کدهای فرم لاگین
تا اینجا اگر به کدهای کامپوننت فرم لاگینی که ایجاد کردیم دقت کنید، تبدیل شده‌است به محلی برای انباشت حجم قابل توجهی از کد. به این ترتیب اگر قرار باشد فرم‌های جدیدی را تعریف کنیم، نیاز خواهد بود قسمت‌های عمده‌ای از این کدها را در هر جایی تکرار کنیم. بنابراین جهت کاهش مسئولیت‌های آن، نیاز است بازسازی کد (refactoring) قابل ملاحظه‌ای بر روی آن صورت گیرد.


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

قصد داریم قسمت‌هایی از کامپوننت لاگین فعلی را استخراج کرده و آن‌ها را درون یک کامپوننت با قابلیت استفاده‌ی مجدد قرار دهیم:
- خاصیت state: می‌خواهیم تمام فرم‌هایی را که تعریف می‌کنیم، دارای خاصیت errors باشند. بنابراین این خاصیت قابلیت استفاده‌ی مجدد را دارد.
- خاصیت schema: قابلیت استفاده‌ی مجدد را ندارد و مختص فرم لاگین تعریف شده‌است. این منطق از هر فرمی با فرم دیگر، متفاوت است.
- متد validate: در این متد، هیچ نوع وابستگی از آن به مفهوم لاگین وجود ندارد و کاملا قابلیت استفاده‌ی مجدد را دارد. تنها this.state.account آن وابسته‌ی به کامپوننت لاگین است و بدیهی است شیء account را در سایر فرم‌ها نخواهیم داشت و ممکن است نام آن movie یا customer باشد. بنابراین قاعده‌ای را در اینجا تعریف می‌کنیم، بر این مبنا که از این پس، تمام فرم‌های ما دارای خاصیتی به نام data خواهند بود که بیانگر اطلاعات آن فرم می‌باشد. با این تغییر، برای مثال در فرم لاگین، data به شیء account تنظیم می‌شود و در فرمی دیگر به شیء customer.
- متد validateProperty: همانند متد validate است و کاملا قابلیت استفاده‌ی مجدد را دارد.
- متد handleSubmit: قسمت ابتدایی این متد که شامل غیرفعال کردن post back به سرور و اعتبارسنجی فرم است، قابلیت استفاده‌ی مجدد را دارد. اما قسمت دوم آن مانند ارسال فرم به سرور و یا هر عملیات دیگری، از یک فرم به فرم دیگر می‌تواند متفاوت باشد.
 - متد handleChange: این متد نیز قابلیت استفاده‌ی مجدد را دارد؛ چون می‌خواهیم در تمام فرم‌ها در حین تایپ اطلاعات، کار اعتبارسنجی ورودی‌ها صورت گیرد. این متد نیز به this.state.account وابسته‌است که قاعده‌ی تعریف خاصیت data در state، می‌تواند این مشکل را حل کند.
- متد رندر: طراحی آن کاملا وابسته‌است به نوع فرمی که مدنظر می‌باشد؛ اما دکمه‌ی submit آن خیر. بجز برچسب دکمه‌ی submit، مابقی قسمت‌های آن مانند کلاس‌های CSS و منطق فعال‌سازی و غیرفعال‌سازی آن، قابلیت استفاده‌ی مجدد را دارند.

بنابراین در ادامه کار، refactoring کامپوننت فرم لاگین را برای استخراج قسمت‌های با قابلیت استفاده‌ی مجدد آن، انجام خواهیم داد.


تبدیل قسمت‌های با قابلیت استفاده‌ی مجدد کامپوننت لاگین، به یک کامپوننت عمومی

ابتدا کامپوننت عمومی Form را که قابلیت استفاده‌ی مجدد دارد، در فایل جدید src\components\common\form.jsx تعریف کرده و سپس کامپوننت فرم لاگین را طوری تغییر می‌دهیم که از آن، بجای کلاس پیش‌فرض Component، ارث بری کند. به این ترتیب تمام متدهای تعریف شده‌ی در این کامپوننت با قابلیت استفاده‌ی مجدد، در کامپوننت‌های مشتق شده‌ی از آن، در دسترس خواهند بود.

1- در ادامه همانطور که عنوان شد، خاصیت state فرم‌ها باید دارای شیء data و شیء errors باشند تا توسط آن‌ها بتوان اطلاعات کل فرم و اطلاعات خطاهای اعتبارسنجی را ذخیره کرد:
import React, { Component } from "react";

class Form extends Component {
    state = {
        data:{},
        errors:{}
     }
با این تغییر، به فرم login بازگشته و خاصیت account موجود در state آن‌را به data تغییر نام می‌دهیم. برای اینکار بهتر است دکمه‌ی F2 را بر روی نام انتخاب شده‌ی account در VSCode فشار دهید تا تکست باکس تغییر نام آن ظاهر شود. مزیت کار با این ابزار refactoring توکار، اصلاح خودکار تمام ارجاعات به account قبلی، با این نام جدید است. همچنین نام تمام خواصی و متغیرهایی را هم که به account تنظیم کرده بودیم، به data تغییر می‌دهیم تا کار به روز رسانی state بر روی data صورت گیرد و نه account قبلی. در این حالت شاید استفاده از امکانات replace کلی ادیتور، بهتر از استفاده از ویژگی F2 باشد.

2- در ادامه، کاری با خاصیت schema تعریف شده‌ی در کامپوننت لاگین نداریم؛ چون کاملا مختص به آن است. اما متدهای validate و validateProperty آن‌را طور کامل cut کرده و به کامپوننت Form، منتقل می‌کنیم. با این انتقال، چون این متدها از کتابخانه‌ی Joi استفاده می‌کنند، باید import آن‌را نیز به ابتدای ماژول جدید فرم، اضافه کرد:
import Joi from "@hapi/joi";

3- سپس متد رندر کامپوننت Form را کاملا حذف می‌کنیم؛ چون این کامپوننت قرار نیست چیزی را رندر کند.

4- در قسمت دوم متد handleSubmit، برای مثال قرار است ارسال داده‌ها به سرور صورت گیرد. به همین جهت آن‌را تبدیل به متدی مانند doSubmit کرده و سپس کل متد handleSubmit را نیز به کامپوننت Form منتقل می‌کنیم.
  doSubmit = () => {
    // call the server
    console.log("Submitted!");
  };

5- متد handleChange را نیز از کامپوننت فرم لاگین cut کرده و به کامپوننت Form منتقل می‌کنیم.

6- پس از این نقل و انتقالات، کار ارث بری از کامپوننت فرم را در کامپوننت فرم لاگین انجام می‌دهیم:
import Form from "./common/form";
// ...

class LoginForm extends Form {

اکنون اگر برنامه را ذخیره کرده و اجرا کنیم، همانند قبل و آن‌چیزی که در انتهای قسمت قبلی به آن رسیدیم، بدون مشکل کار می‌کند؛ اما کدهای کامپوننت فرم لاگین به شدت کاهش یافته و ساده شده‌است. همچنین اگر دفعه‌ی بعد، نیاز به ایجاد فرمی وجود داشت، دیگر نیازی به تکرار این حجم از کد نیست. تنها نیاز خواهیم داشت تا state را تعریف کرده و schema را اضافه کنیم و همچنین نیاز است متد doSumbit را پیاده سازی کنیم تا مشخص شود پس از تکمیل فرم و اعتبارسنجی آن، قرار است چه رخ‌دادی واقع شود.

کدهای کامل کامپوننت فرم را از پیوست انتهای بحث می‌توانید دریافت کنید؛ البته تمام متدهای آن‌را در قسمت قبل تکمیل کرده بودیم و در اینجا صرفا یکسری cut/paste صورت گرفتند.


ساده کردن و بهبود پیاده سازی متد رندر

1- در متد رندر فعلی کامپوننت فرم لاگین، اگر به دکمه‌ی submit آن دقت کنیم، بجز برچسب آن، مابقی قسمت‌های آن در تمام فرم‌های دیگری که تعریف خواهیم کرد، یکسان خواهند بود. به همین جهت این قسمت را می‌توان تبدیل به یک متد کمکی در کلاس Form کرد:
  renderButton(label) {
    return (
      <button disabled={this.validate()} className="btn btn-primary">
        {label}
      </button>
    );
  }
سپس در متد رندر کامپوننت فرم لاگین، تنها کافی است بجای المان button قبلی، از متد فوق استفاده کنیم:
{this.renderButton("Login")}

2- در قسمت‌های قبل، برچسب، فیلدهای ورودی و تگ‌ها و کلاس‌های بوت استرپی را به کامپوننت Input منتقل کردیم، تا به یک فرم ساده‌تر و با قابلیت نگهداری بالاتری برسیم. هرچند این هدف حاصل شده، اما باز هم تعاریف المان‌های Input قرارگرفته‌ی در متد رندر کامپوننت لاگین، دارای الگوی تکراری ذکر یک خاصیت مشخص، تعریف رویدادگردان‌های مشخص و اطلاعات اعتبارسنجی کاملا مشخصی هستند. به همین جهت تعریف المان Input را هم مانند متد renderButton فوق می‌توان به کلاس پایه Form انتقال داد:
  import Input from "./input";
  //...

  renderInput(name, label) {
    const { data, errors } = this.state;
    return (
      <Input
        name={name}
        label={label}
        value={data[name]}
        onChange={this.handleChange}
        error={errors[name]}
      />
    );
همانطور که مشاهده می‌کنید، با استفاده از [] و دسترسی پویای به خواص اشیاء، می‌توان رندر المان Input را تبدیل به متدی با قابلیت نگهداری بهتر کرد و از تکرار ویژگی‌های name ، label ، value ، onChange و error به ازای هر فیلد مورد نیاز، پرهیز کرد. اکنون با این تغییر، متد رندر کامپوننت فرم لاگین به صورت زیر خلاصه می‌شود که بسیار بهتر است از تعریف تعداد قابل ملاحظه‌ای div و کلاس بوت استرپی، تعریف المان‌ها، اتصال تک تک آن‌ها به خواص تعریف شده، اتصال آن‌ها به رویداد گردان‌ها و همچنین به اعتبارسنج‌ها:
  render() {
    return (
      <form onSubmit={this.handleSubmit}>
        {this.renderInput("username", "Username")}
        {this.renderInput("password", "Password")}
        {this.renderButton("Login")}
      </form>
    );
  }

3- تا اینجا فرم لاگین تعریف شده، یک مشکل کوچک را دارد: فیلد پسورد آن، از نوع text تعریف شده و اطلاعات وارد شده را همانند یک textbox معمولی نمایش می‌دهد. برای رفع این مشکل، پارامتر type را با یک مقدار پیش‌فرض پر استفاده، تعریف کرده و به المان Input اعمال می‌کنیم:
  renderInput(name, label, type = "text") {
    const { data, errors } = this.state;
    return (
      <Input
        name={name}
        type={type}
        label={label}
        value={data[name]}
        onChange={this.handleChange}
        error={errors[name]}
      />
    );
  }

سپس این type را در قسمتی که المان مرتبط را رندر می‌کنیم، با password مقدار دهی خواهیم کرد:
  render() {
    return (
      <form onSubmit={this.handleSubmit}>
        {this.renderInput("username", "Username")}
        {this.renderInput("password", "Password", "password")}
        {this.renderButton("Login")}
      </form>
    );
}
نیازی به ذکر type، در اولین renderInput ذکر شده، نیست؛ چون مقدار این پارامتر را ازمقدار پیش‌فرض text، دریافت می‌کند.

البته این تغییرات تا به اینجا کار نخواهند کرد؛ چون هنوز کلاس المان Input را جهت پذیرش ویژگی جدید type، ویرایش نکرده‌ایم. بنابراین به فایل src\components\common\input.jsx مراجعه کرده و type را به آن اعمال می‌کنیم:
import React from "react";

const Input = ({ name, type, label, value, error, onChange }) => {
  return (
    <div className="form-group">
      <label htmlFor={name}>{label}</label>
      <input
        value={value}
        onChange={onChange}
        id={name}
        name={name}
        type={type}
        className="form-control"
      />
      {error && <div className="alert alert-danger">{error}</div>}
    </div>
  );
};

export default Input;
اکنون اگر تغییرات را ذخیره کرده و به مرورگر مراجعه کنیم، فیلد کلمه‌ی عبور، دیگر حروف وارد شده را نمایش نمی‌دهد و بر اساس نوع استاندارد password، عمل می‌کند.

4- مشکل! آیا باید به ازای هر ویژگی جدیدی که قرار است به این input اعمال کنیم، مانند type در اینجا، نیاز است یک پارامتر جدید را تعریف و سپس از آن استفاده کرد؟ در این حالت اینترفیس این کامپوننت از کنترل خارج می‌شود و همچنین هربار باید آن‌را ویرایش کرد و تغییر داد. به علاوه اگر به تعریف این input دقت کنیم، نام 4 ویژگی آن، با مقادیری که دریافت می‌کنند،  هم نام هستند (ویژگی value با مقدار value و ...):
<input
  value={value}
  name={name}
  type={type}
  onChange={onChange}
  id={name}
  className="form-control"
/>
در کامپوننت جاری، منهای پارامترهایی که نام ویژگی‌های تعریف شده، با نام آن پارامترها در تمام قسمت‌های کامپوننت (نه فقط المان input)، یکی نیستند (name، label و error)، مابقی را می‌توان توسط یک «rest operator»، به این متد ارسال کرد:
import React from "react";

const Input = ({ name, label, error, ...rest }) => {
  return (
    <div className="form-group">
      <label htmlFor={name}>{label}</label>
      <input {...rest} name={name} id={name} className="form-control" />
      {error && <div className="alert alert-danger">{error}</div>}
    </div>
  );
};

export default Input;
بنابراین منهای name، label و error که در قسمت‌های دیگر کامپوننت استفاده می‌شوند، مابقی پارامترهای این کامپوننت تابعی را حذف کرده و با یک rest operator، دریافت می‌کنیم. سپس آن‌ها را به کمک یک spread operator، در المان input، گسترده و درج می‌کنیم. شبیه به اینکار را در قسمت 15 و بخش «ارسال props سفارشی در حین مسیریابی به کامپوننت‌ها» آن انجام داده بودیم. با کمک عملگرهای rest و spread، به سادگی می‌توان هرنوع ویژگی جدیدی را که برای کار با المان input نیاز داریم، به کامپوننت جاری ارسال کرد؛ بدون اینکه نیازی باشد هربار تعریف پارامترهای آن را تغییر دهیم. پارامتر rest تعریف شده، یعنی هر خاصیت دیگری را بجز سه خاصیت name، label و error، به صورت خودکار به این کامپوننت تابعی ارسال کن.
با این تغییر در کامپوننت Input، سایر قسمت‌های برنامه نیازی به تغییر ندارند. برای مثال در متد renderInput، سه ویژگی name، label و error تبدیل به سه پارامتر دریافتی از props می‌شوند (ترتیب ذکر آن‌ها اهمیتی ندارد). مابقی ویژگی‌های تعریف شده‌ی در آن، به صورت خودکار در قسمت input {...rest} درج خواهند شد.


کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید: sample-20.zip
مطالب
کار با بانک‌های اطلاعاتی مختلف در PdfReport
تعدادی از منابع داده پیش فرض PdfReport جهت کار مستقیم با بانک‌های اطلاعاتی مختلف، کوئری نوشتن و نمایش نتایج آن‌ها طراحی شده‌اند.
در این بین با توجه به اینکه دات نت پشتیبانی توکاری از SQL Server دارد، اتصال و استفاده از توانمندی‌های آن نیاز به کتابخانه جانبی خاصی ندارد. اما برای کار با بانک‌های اطلاعاتی دیگر نیاز خواهد بود تا پروایدر ADO.NET آن‌ها را تهیه و به برنامه اضافه کنیم.
چهار نمونه از منابع داده پیش فرضی که در متد MainTableDataSource قابل تعریف هستند به شرح زیر می‌باشند:
public void SqlDataReader(string connectionString, string sql, params object[] parametersValues)

//.mdb or .accdb files
public void AccessDataReader(string filePath, string password, string sql, params object[] parametersValues)

public void OdbcDataReader(string connectionString, string sql, params object[] parametersValues)
SqlDataReader برای کار با بانک‌های اطلاعاتی SQL Server بهینه سازی شده است.
AccessDataReader قابلیت اتصال به بانک‌های اطلاعاتی اکسس جدید (فایل‌های accdb) و اکسس قدیم (فایل‌های mdb) را دارد.
OdbcDataReader یک پروایدر عمومی است که از روز اول دات نت به همراه آن بوده است. برای مثال جهت اتصال به بانک‌های اطلاعاتی فاکس‌پرو می‌تواند مورد استفاده قرار گیرد.
اما ... برای مابقی بانک‌های اطلاعاتی چطور؟
برای سایر بانک‌های اطلاعاتی، منبع داده عمومی زیر تدارک دیده شده است:
public void GenericDataReader(string providerName, string connectionString, string sql, params object[] parametersValues)
تنها تفاوت آن با نمونه‌های قبل، ذکر providerName آن است. برای مثال جهت اتصال به SQLite ابتدا پروایدر مخصوص ADO.NET آن‌را دریافت و به پروژه خود اضافه نمائید. سپس پارامتر providerName فوق را با "System.Data.SQLite" مقدار دهی کنید.

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

در ادامه نحوه تهیه گزارش از یک بانک اطلاعاتی SQLite را توسط PdfReport بررسی خواهیم کرد:

using System;
using PdfRpt.Core.Contracts;
using PdfRpt.Core.Helper;
using PdfRpt.FluentInterface;

namespace PdfReportSamples.SQLiteDataReader
{
    public class SQLiteDataReaderPdfReport
    {
        public IPdfReportData CreatePdfReport()
        {
            return new PdfReport().DocumentPreferences(doc =>
            {
                doc.RunDirection(PdfRunDirection.RightToLeft);
                doc.Orientation(PageOrientation.Portrait);
                doc.PageSize(PdfPageSize.A4);
                doc.DocumentMetadata(new DocumentMetadata { Author = "Vahid", Application = "PdfRpt", Keywords = "Test", Subject = "Test Rpt", Title = "Test" });
            })
            .DefaultFonts(fonts =>
            {
                fonts.Path(AppPath.ApplicationPath + "\\fonts\\irsans.ttf",
                                  Environment.GetEnvironmentVariable("SystemRoot") + "\\fonts\\verdana.ttf");
            })
            .PagesFooter(footer =>
            {
                footer.DefaultFooter(DateTime.Now.ToString("MM/dd/yyyy"));
            })
            .PagesHeader(header =>
            {
                header.DefaultHeader(defaultHeader =>
                {
                    defaultHeader.RunDirection(PdfRunDirection.RightToLeft);
                    defaultHeader.ImagePath(AppPath.ApplicationPath + "\\Images\\01.png");
                    defaultHeader.Message("گزارش جدید ما");
                });
            })
            .MainTableTemplate(template =>
            {
                template.BasicTemplate(BasicTemplate.SilverTemplate);
            })
            .MainTablePreferences(table =>
            {
                table.ColumnsWidthsType(TableColumnWidthType.Relative);
                table.NumberOfDataRowsPerPage(5);
            })
            .MainTableDataSource(dataSource =>
            {
                dataSource.GenericDataReader(
                    providerName: "System.Data.SQLite",
                    connectionString: "Data Source=" + AppPath.ApplicationPath + "\\data\\blogs.sqlite",
                    sql: @"SELECT [url], [name], [NumberOfPosts], [AddDate]
                               FROM [tblBlogs]
                               WHERE [NumberOfPosts]>=@p1",
                    parametersValues: new object[] { 10 }
                );
            })
            .MainTableSummarySettings(summarySettings =>
            {
                summarySettings.OverallSummarySettings("جمع کل");
                summarySettings.PreviousPageSummarySettings("نقل از صفحه قبل");
                summarySettings.PageSummarySettings("جمع صفحه");
            })
            .MainTableColumns(columns =>
            {
                columns.AddColumn(column =>
                {
                    column.PropertyName("rowNo");
                    column.IsRowNumber(true);
                    column.CellsHorizontalAlignment(HorizontalAlignment.Center);
                    column.IsVisible(true);
                    column.Order(0);
                    column.Width(1);
                    column.HeaderCell("ردیف");
                });

                columns.AddColumn(column =>
                {
                    column.PropertyName("url");
                    column.CellsHorizontalAlignment(HorizontalAlignment.Center);
                    column.IsVisible(true);
                    column.Order(1);
                    column.Width(2);
                    column.HeaderCell("آدرس");
                    column.ColumnItemsTemplate(template =>
                    {
                        template.Hyperlink(foreColor: System.Drawing.Color.Blue, fontUnderline: true);
                    });
                });

                columns.AddColumn(column =>
                {
                    column.PropertyName("name");
                    column.CellsHorizontalAlignment(HorizontalAlignment.Center);
                    column.IsVisible(true);
                    column.Order(2);
                    column.Width(2);
                    column.HeaderCell("نام");
                });

                columns.AddColumn(column =>
                {
                    column.PropertyName("NumberOfPosts");
                    column.CellsHorizontalAlignment(HorizontalAlignment.Center);
                    column.IsVisible(true);
                    column.Order(3);
                    column.Width(2);
                    column.HeaderCell("تعداد مطلب");
                    column.ColumnItemsTemplate(template =>
                    {
                        template.TextBlock();
                        template.DisplayFormatFormula(obj => obj == null ? string.Empty : string.Format("{0:n0}", obj));
                    });
                    column.AggregateFunction(aggregateFunction =>
                    {
                        aggregateFunction.NumericAggregateFunction(AggregateFunction.Sum);
                        aggregateFunction.DisplayFormatFormula(obj => obj == null ? string.Empty : string.Format("{0:n0}", obj));
                    });
                });

                columns.AddColumn(column =>
                {
                    column.PropertyName("AddDate");
                    column.CellsHorizontalAlignment(HorizontalAlignment.Center);
                    column.IsVisible(true);
                    column.Order(4);
                    column.Width(2);
                    column.HeaderCell("تاریخ ثبت");
                    column.ColumnItemsTemplate(template =>
                    {
                        template.TextBlock();
                        template.DisplayFormatFormula(obj => obj == null ? string.Empty : PersianDate.ToPersianDateTime((DateTime)obj) /*((DateTime)obj).ToString("dd/MM/yyyy HH:mm")*/);
                    });
                });
            })
            .MainTableEvents(events =>
            {
                events.DataSourceIsEmpty(message: "There is no data available to display.");
            })
            .Export(export =>
            {
                export.ToExcel();
            })
            .Generate(data => data.AsPdfFile(AppPath.ApplicationPath + "\\Pdf\\RptSqlDataReaderSample.pdf"));
        }
    }
}

توضیحات:

- در مثال فوق نحوه استفاده از یک بانک اطلاعاتی SQLite را ملاحظه می‌کنید. این بانک اطلاعاتی نمونه در پوشه bin\data سورس به روز شده پروژه موجود است.
                dataSource.GenericDataReader(
                    providerName: "System.Data.SQLite",
                    connectionString: "Data Source=" + AppPath.ApplicationPath + "\\data\\blogs.sqlite",
                    sql: @"SELECT [url], [name], [NumberOfPosts], [AddDate]
                               FROM [tblBlogs]
                               WHERE [NumberOfPosts]>=@p1",
                    parametersValues: new object[] { 10 }
                );
فرض بر این است که فایل‌های System.Data.SQLite.dll و SQLite.Interop.dll را از سایت SQLite دریافت کرده و سپس ارجاعی را به اسمبلی System.Data.SQLite.dll به پروژه خود افزوده‌اید.
در مرحله بعد به کمک GenericDataReader می‌توان به این پروایدر دسترسی یافت. همانطور که ملاحظه می‌کنید یک کوئری پارامتری با مقدار پارامتر مساوی 10 جهت تهیه گزارش، تعریف شده است.
همچنین باید دقت داشت که اگر پروژه جاری شما مبتنی بر دات نت 4 است، نیاز خواهید داشت چند سطر زیر را به فایل config برنامه اضافه نمائید تا با SQLite مشکلی نداشته باشد:
<?xml version="1.0"?>
   <configuration>
      <startup useLegacyV2RuntimeActivationPolicy="true">
           <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0"/>
      </startup>
   </configuration>                  
- مرحله بعد نوبت به معرفی ستون‌های گزارش است. هر ستون، معادل یک فیلد معرفی شده در کوئری SQL ارسال شده به GenericDataReader خواهد بود (کوچکی و بزرگی حروف باید در اینجا رعایت شوند).
- در حین معرفی ستون AddDate، نحوه نمایش و تبدیل تاریخ دریافتی که با فرمت DateTime است را به تاریخ شمسی ملاحظه می‌کنید. متد PersianDate.ToPersianDateTime در فضای نام PdfRpt.Core.Helper قرار دارد. توسط DisplayFormatFormula، فرصت خواهید داشت مقدار متناظر با سلول در حال رندر را پیش از نمایش، به هر نحو دلخواهی فرمت کنید.
- در ستون url از قالب نمایشی پیش فرض Hyperlink، برای نمایش اطلاعات فیلد جاری به صورت یک لینک قابل کلیک استفاده شده است.

یک نکته:
ذکر قسمت MainTableColumns و تمام تعاریف مرتبط با آن در PdfReports اختیاری است. به این معنا که می‌توانید قسمت گزارش سازی و تعاریف گزارشات برنامه خود را پویا کنید (شبیه به حالت auto generate columns در گرید‌های معروف). کوئری‌های SQL متناظر با گزارشات را در بانک اطلاعاتی ذخیره کنید و به گزارش ساز فوق ارسال نمائید. حاصل یک گزارش جدید است.

مطالب
توسعه سیستم مدیریت محتوای DNTCms - قسمت اول
قصد داریم طی یک سری مقالات به توسعه یک سیستم مدیریت محتوا بپردازیم. مسلما فاصله‌ی زمانی بین انتشار مقالات این سری، کمی زیاد خواهد بود. ولی سعی خواهیم کرد تا قدم به قدم و با تحلیل و توضیح کافی هر بخش به این هدف برسیم.
همکاران این قسمت:
پیشنیاز‌ها:
در زیر بخش هایی که برای این سیستم تا به امروز در نظر گرفتیم (مسلما با ارائه ایده‌ها و بازخورد‌های دوستان این امکانات دستخوش تغییر خواهند شد) ، به شرح زیر میباشد:
  • انجمن
  • ارتباط دوستی
  • سیستم ترفیع رتبه
  • Themeable
  • سیستم Following
  • صفحات داینامیک
  • سیستم پیام رسانی
  • امکان ساخت گروه‌های شخصی برای انتشار مطالب خود (توسط کاربران) با اعمال دسترسی مختلف
  • پیغام خصوصی
  • وبلاگ
  • نظرسنجی ها
  • مدیریت کاربران با دسترسی‌ها داینامیک
  • اخبار
  • آگهی ها
در این قسمت مدل‌های مربوط به بخش وبلاگ را بررسی میکنیم. ابتدا قصد داشتیم تا امکان نگارش یک پست توسط چندین کاربر را به طور همزمان، در سیستم قرار دهیم و ایده کار هم که توسط یکی دوستان (محمد شریفی) پیشنهاد شد به صورت زیر بود:
ابتدا کاربری به عنوان ایجاد کننده اصلی پست، بخش‌ها مختلفی را برای یک پست در نظر بگیرد و هر قسمت را به یک همکار نسبت دهد و با اتمام تمام بخش‌ها و اعلام آن توسط همکاران، کاربر اصلی ایجاد کننده پست بتواند بخش‌ها را باهم ادغام کند و برای آماده انتشار نهایی باشد.
ولی خب به نظر بنده الان سیستم‌های ارتباطی قدرتمندتری هم هست که همکاران در مورد نگارش یک پست به صورت مشارکتی بپردازند و صرفا در مرحله‌ی انتشار، این کاربران به عنوان کاربران همکار به پست منتشر شده نسبت داده شوند تا در زیر پست مشخصات کامل آنها قابل مشاهد باشد (راه حلی که پیاده سازی خواهیم کرد).
مدل‌های در نظر گرفته شده برای سیستم وبلاگ به صورت زیر می‌باشد:

مخزن برچسب ها
/// <summary>
    /// Represents the lable 
    /// </summary>
    public class Tag
    {
        #region Ctor
        /// <summary>
        /// Create one instance of <see cref="Tag"/>
        /// </summary>
        public Tag()
        {
            Id = SequentialGuidGenerator.NewSequentialGuid();
        }
        #endregion

        #region Properties
        /// <summary>
        /// sets or gets Tag's identifier
        /// </summary>
        public virtual Guid  Id { get; set; }
        /// <summary>
        /// sets or gets Tag's name
        /// </summary>
        public virtual string Name { get; set; }
        #endregion

        #region NavigationProperties
        /// <summary>
        /// sets or gets Tag's posts
        /// </summary>
        public virtual ICollection<BlogPost> BlogPosts { get; set; }
        #endregion
    }
کلاس بالا مشخص کننده‌ی مخزن برچسب‌های سیستم ما خواهد بود. جدول حاصل از این مدل با جداول سایر بخش‌ها که نیاز به داشتن برچسب خواهند داشت، ارتباط چند به چند خواهد داشت. برای این منظور همانطور که مشخص است در قسمت NavigationProperties یک لیست از BlogPost معرفی شده است و در ادامه خواهیم دید که لیستی از کلاس Tag هم در کلاس BlogPost معرفی خواهد شد.

مدل پیش نویس ها
  /// <summary>
    /// Represents the Post's Draft
    /// </summary>
    public  class BlogDraft
    {
        #region Ctor
        /// <summary>
        /// create one instance of <see cref="BlogDraft"/>
        /// </summary>
        public  BlogDraft()
        {
            Id = SequentialGuidGenerator.NewSequentialGuid();
        }
        #endregion

        #region Properties
        /// <summary>
        /// gets or sets Id of post's draft
        /// </summary>
        public virtual  Guid Id { get; set; }
        /// <summary>
        /// gets or sets body of post's draft
        /// </summary>
        public virtual  string Body { get; set; }
        /// <summary>
        /// gets or set title of post's draft
        /// </summary>
        public virtual  string Title { get; set; }
        /// <summary>
        /// gets or sets tags of post's draft that seperated using ','
        /// </summary>
        public virtual  string TagNames { get; set; }
        /// <summary>
        /// gets or sets value indicating whether this draft is ready to publish
        /// </summary>
        public virtual  bool IsReadyForPublish { get; set; }
        /// <summary>
        /// ges ro sets DateTime that this draft added
        /// </summary>
        public virtual  DateTime CreatedOn { get; set; }
        /// <summary>
        /// gets or sets information of User-Agent
        /// </summary>
        public virtual string Agent { get; set; }
        /// <summary>
        /// gets or sets date that this draft publish as ready
        /// </summary>
        public virtual DateTime? ReadyForPublishOn { get; set; }
        #endregion

        #region NavigationProperties
        /// <summary>
        /// gets or sets Id of user that he is owner of this draft
        /// </summary>
        public virtual  long OwnerId { get; set; }
        /// <summary>
        /// gets or sets user that he is owner of this draft
        /// </summary>
        public virtual  User Owner { get; set; }
        #endregion
    }
کلاس فوق برای ذخیره سازی پست‌های پیشنویس کاربران در نظر گرفته شده است. شاید بهتر بود این پیش نویس‌ها هم در همان مدل پستی که در ادامه مشاهده می‌کنید ادغام شوند. ولی این یک تصمیم شخصی برای این کار بوده و برای سیستی بزرگی که امکان دارد حذف و درج پیشنویس‌ها زیاد باشد و فرض بر اینکه long هم برای آی دی جواب گو نخواهد بود و نیاز است از Guid ای که به صورت متوالی افزایش میابد به منظور جلوگیری از Fragmentation، استفاده کرد. بقیه فیلد‌ها هم مشخص هستند و نیازی به توضیح اضافی نخواهد بود. مسلما هر کاربر می‌تواند چندین پیشنویس داشته باشد. لذا ارتباط یک به چند مابین کاربر و پیشنویس پست، خواهیم داشت.
مدل امتیاز دهی کاربران
/// <summary>
    /// Section of Rating
    /// </summary>
    public enum RatingSection
    {
        News,
        Announcement,
        ForumTopic,
        BlogComment,
        NewsComment,
        PollComment,
        AnnouncementComment,
        ForumPost,
        ...
    }


    /// <summary>
    /// Represents Rating Record regard by section type for Rating System
    /// </summary>
    public class UserRating
    {
        #region Ctor
        /// <summary>
        /// Create one instance of <see cref="UserRating"/>
        /// </summary>
        public UserRating()
        {
            Id = SequentialGuidGenerator.NewSequentialGuid();
        }
        #endregion

        #region Properties
        /// <summary>
        /// gets or sets Id of Rating Record
        /// </summary>
        public virtual Guid Id { get; set; }
        /// <summary>
        /// gets or sets value of rate
        /// </summary>
        public virtual double RatingValue { get; set; }
        /// <summary>
        /// gets or sets Section's Id 
        /// </summary>
        public virtual long SectionId { get; set; }
        /// <summary>
        /// gets or sets Section 
        /// </summary>
        public virtual RatingSection Section { get; set; }
        #endregion

        #region Navigation Properties
        /// <summary>
        /// gets or sets user that rate one section
        /// </summary>
        public virtual User Rater { get; set; }
        /// <summary>
        /// gets or sets Rater Id that rate one section
        /// </summary>
        public virtual long RaterId { get; set; }

        #endregion
    }
نوع داده‌ی شمارشی RatingSection مشخص کننده‌ی بخش‌های مختلف سیستم می‌باشد که نیاز به امتیاز دهی دارند. چون سیستم ما یک سیستم بسته می‌باشد و برای امتیاز دهی نیاز به احراز هویت خواهد بود، لذا باید از امتیاز دادن بیش از یک بار برای هر بخش جلوگیری کنیم. برای این کار می‌توان بین تمام بخش‌های موجود ارتباط‌های چند به چند در نظر گرفت. برای مثال یک ارتباط چند به چند بین کاربر و نظرات که مشخص کننده‌ی این است که یک کاربر می‌تواند به چند نظر امتیاز دهد و هر نظر هم میتواند چندین امتیاز دهنده داشته باشد ولی تعداد جداول بالا خواهد رفت و کار آن هم زیاد خواهد شد. برای این منظور میتوان یک جدول به مانند UserRating در نظر گرفت که صرفا با جدول کاربر ما یک ارتباط یک به چند دارد و با استفاده از خصوصیت RatingSection موجود در این کلاس وخصوصیت SectionId و همچنین در نظر گرفتن یک کلید منحصر به فرد بر روی خصوصیت‌های RatingSection ، RaterId و SectionId، از امتیاز دادن چند باره‌ی کاربر به یک بخش جلوگیری کرد.
/// <summary>
    /// Represent the rating as ComplexType
    /// </summary>
    [ComplexType]
    public class Rating
    {
        /// <summary>
        /// sets or gets total of rating
        /// </summary>
        public virtual double? TotalRating { get; set; }
        /// <summary>
        /// sets or gets rater's count
        /// </summary>
        public virtual long? RatersCount { get; set; }
        /// <summary>
        /// sets or gets average of rating
        /// </summary>
        public virtual double? AverageRating { get; set; }
    }
کلاس بالا یک ComplexType می‌باشد که در نهایت به هیچ جدولی مپ نخواهد شد و صرفا به منظور کپسوله کردن یکسری فیلد مورد استفاده قرار گرفته است و از این کلاس می‌توان در هر بخش که لازم است امتیاز دهی داشته باشیم، به عنوان یک خصوصیت، استفاده کرد.

کلاس پایه‌ی محتوا
/// <summary>
    /// Represents a base class for every content in system
    /// </summary>
    public abstract class BaseContent
    {
        #region Properties

        /// <summary>
        /// get or set identifier of record
        /// </summary>
        public virtual long Id { get; set; }
        /// <summary>
        /// gets or sets date of publishing content
        /// </summary>
        public virtual DateTime PublishedOn { get; set; }
        /// <summary>
        /// gets or sets Last Update's Date
        /// </summary>
        public virtual DateTime ModifiedOn { get; set; }
        /// <summary>
        /// gets or sets the blog pot body
        /// </summary>
        public virtual string Body { get; set; }
        /// <summary>
        /// gets or sets the content title
        /// </summary>
        public virtual string Title { get; set; }
        /// <summary>
        /// gets or sets value  indicating Custom Slug
        /// </summary>
        public virtual string SlugUrl { get; set; }
        /// <summary>
        /// gets or sets meta title for seo
        /// </summary>
        public virtual string MetaTitle { get; set; }
        /// <summary>
        /// gets or sets meta keywords for seo
        /// </summary>
        public virtual string MetaKeywords { get; set; }
        /// <summary>
        /// gets or sets meta description of the content
        /// </summary>
        public virtual string MetaDescription { get; set; }
        /// <summary>
        /// gets or sets 
        /// </summary>
        public virtual string FocusKeyword { get; set; }
        /// <summary>
        /// gets or sets value indicating whether the content use CanonicalUrl
        /// </summary>
        public virtual bool UseCanonicalUrl { get; set; }
        /// <summary>
        /// gets or sets CanonicalUrl That the Post Point to it
        /// </summary>
        public virtual string CanonicalUrl { get; set; }
        /// <summary>
        /// gets or sets value indicating whether the content user no Follow for Seo
        /// </summary>
        public virtual bool UseNoFollow { get; set; }
        /// <summary>
        /// gets or sets value indicating whether the content user no Index for Seo
        /// </summary>
        public virtual bool UseNoIndex { get; set; }
        /// <summary>
        /// gets or sets value indicating whether the content in sitemap
        /// </summary>
        public virtual bool IsInSitemap { get; set; }
        /// <summary>
        /// gets or sets a value indicating whether the content comments are allowed 
        /// </summary>
        public virtual bool AllowComments { get; set; }
        /// <summary>
        /// gets or sets a value indicating whether the content comments are allowed for anonymouses 
        /// </summary>
        public virtual bool AllowCommentForAnonymous { get; set; }
        /// <summary>
        /// gets or sets  viewed count by rss
        /// </summary>
        public virtual long ViewCountByRss { get; set; }
        /// <summary>
        /// gets or sets viewed count 
        /// </summary>
        public virtual long ViewCount { get; set; }
        /// <summary>
        /// Gets or sets the total number of  comments
        /// <remarks>The same as if we run Item.Comments.where(a=>a.Status==Status.Approved).Count()
        /// We use this property for performance optimization (no SQL command executed)
        /// </remarks>
        /// </summary>
        public virtual int ApprovedCommentsCount { get; set; }
        /// <summary>
        /// Gets or sets the total number of  comments
        /// <remarks>The same as if we run Item.Comments.where(a=>a.Status==Status.UnApproved).Count()
        /// We use this property for performance optimization (no SQL command executed)</remarks></summary>
        public virtual int UnApprovedCommentsCount { get; set; }
        /// <summary>
        /// gets or sets value  indicating whether the content is logical deleted or hidden
        /// </summary>
        public virtual bool IsDeleted { get; set; }
        /// <summary>
        /// gets or sets rating complex instance
        /// </summary>
        public virtual Rating Rating { get; set; }
        /// <summary>
        /// gets or sets value indicating whether the content show with rssFeed
        /// </summary>
        public virtual bool ShowWithRss { get; set; }
        /// <summary>
        /// gets or sets value indicating maximum days count that users can send comment
        /// </summary>
        public virtual int DaysCountForSupportComment { get; set; }
        /// <summary>
        /// gets or sets information of User-Agent
        /// </summary>
        public virtual string Agent { get; set; }
        /// <summary>
        /// gets or sets icon name with size 200*200 px for snippet 
        /// </summary>
        public virtual string SocialSnippetIconName { get; set; }
        /// <summary>
        /// gets or sets title for snippet
        /// </summary>
        public virtual string SocialSnippetTitle { get; set; }
        /// <summary>
        /// gets or sets description for snippet
        /// </summary>
        public virtual string SocialSnippetDescription { get; set; }
        /// <summary>
        /// gets or sets body of content's comment
        /// </summary>
        public virtual byte[] RowVersion { get; set; }
        /// <summary>
        /// gets or sets name of tags seperated by comma that assosiated with this content fo increase performance
        /// </summary>
        public virtual string TagNames { get; set; }
        /// <summary>
        /// gets or sets counter for Content's report
        /// </summary>
        public virtual int ReportsCount { get; set; }
        #endregion

        #region NavigationProperties

        /// <summary>
        /// get or set user that create this record
        /// </summary>
        public virtual User Author { get; set; }

        /// <summary>
        /// gets or sets Id of user that create this record
        /// </summary>
        public virtual long AuthorId { get; set; }
        /// <summary>
        /// get or set  the tags integrated with content
        /// </summary>
        public virtual ICollection<Tag> Tags { get; set; }
        #endregion

    }

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

نکته‌ای که وجود دارد فیلد‌های ApprovedCommentsCount  UnApprovedCommentsCount و TagNames می‌باشند که هنگام درج نظر جدید باید تعداد نظرات ذخیره شده را  ویرایش کنیم و هنگام ویرایش خود پست یا خبر با ... و یا حتی ویرایش خود تگ یا حذف آن تگ باید TagNames که لیست برچسب‌های محتوا را به صورت جدا شده با (,) از هم دیگر می‌باشد، ویرایش کنیم (جای بحث دارد).

مشخص است که هر یک از مطالب منتشر شده در بخش‌های وبلاگ، اخبار، نظرسنجی و آگهی‌ها، یک کابر ایجاد کننده (Author نامیده‌ایم) خواهد داشت و هر کاربر هم می‌تواند چندین مطلب را ایجاد کند. لذا رابطه‌ی یک به چند بین تمام این بخش‌ها مذکور و کاربر ایجاد خواهد شد.

مدل  LinkBack

 /// <summary>
    /// Represents link for implemention linkback
    /// </summary>
    public class LinkBack
    {

        #region Ctor
        /// <summary>
        /// create one instance of <see cref="LinkBack"/>
        /// </summary>
        public LinkBack()
        {
            CreatedOn = DateTime.Now;
            Id = SequentialGuidGenerator.NewSequentialGuid();
        }
        #endregion

        #region Properties
        /// <summary>
        /// gets or sets link's Id
        /// </summary>
        public virtual Guid Id { get; set; }
        /// <summary>
        /// gets or sets text for show Link
        /// </summary>
        public virtual string Title { get; set; }
        /// <summary>
        /// gets or sets link's address 
        /// </summary>
        public virtual string Url { get; set; }
        /// <summary>
        /// gets or set value indicating whether this link is internal o external
        /// </summary>
        public virtual LinkBackType Type { get; set; }
        /// <summary>
        /// gets or sets date that this record is added
        /// </summary>
        public virtual DateTime CreatedOn { get; set; }
        #endregion

        #region NavigationProperties
        /// <summary>
        /// gets or sets Post that associated
        /// </summary>
        public virtual BlogPost Post { get; set; }
        /// <summary>
        /// gets or sets id of Post that associated
        /// </summary>
        public virtual long PostId { get; set; }
        #endregion
    }


 /// <summary>
    /// represents Type of ReferrerLinks
    /// </summary>
    public enum LinkBackType
    {
        /// <summary>
        /// Internal link
        /// </summary>
        Internal,
        /// <summary>
        /// External Link
        /// </summary>
        External
    }

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

مدل پست ها

  /// <summary> 
    /// Represents a blog post
    /// </summary>
    public  class BlogPost : BaseContent
    {
        #region Ctor
        /// <summary>
        /// Create one Instance of <see cref="BlogPost"/>
        /// </summary>
        public BlogPost()
        {
            Rating = new Rating();
            PublishedOn = DateTime.Now;
        }
        #endregion

       #region Properties
        /// <summary>
        /// gets or sets Status of LinkBack Notifications
        /// </summary>
        public virtual LinkBackStatus LinkBackStatus { get; set; }
        #endregion

        #region NavigationProperties

        /// <summary>
        /// get or set  blog post's Reviews
        /// </summary>
        public virtual ICollection<BlogComment> Comments { get; set; }
        /// <summary>
        /// get or set collection of links that reference to this blog post
        /// </summary>
        public virtual ICollection<LinkBack> LinkBacks { get; set; }
        /// <summary>
        /// get or set Collection of Users that Contribute on this post
        /// </summary>
        public virtual ICollection<User> Contributors  { get; set; }
        
        #endregion
    }


  /// <summary>
    /// Represents Status for ReferrerLinks
    /// </summary>
    public enum  LinkBackStatus
    {
        [Display(Name ="غیرفعال")]
        Disable,
        [Display(Name = "فعال")]
        Enable,
        [Display(Name = "لینک‌ها داخلی")]
        JustInternal,
        [Display(Name = "لینک‌ها خارجی")]
        JustExternal
    }
 کلاس بالا نشان دهنده‌ی مدل پست‌های ما در سیستم می‌باشد که بیشتر خصوصیات خود را از کلاس پایه‌ی BaseContent به ارث برده و لازم به تکرار آنها نیست. به علاوه یکسری خصوصیات دیگر، از جلمه‌ی آنها یک لیست از کلاس LinkBack، لیستی از کاربران مشارکت کننده به منظور نمایش مشخصات آنها در زیر پست و لیستی از کلاس BlogComment که نشان دهنده‌ی نظرات پست می‌باشد، هم خواهد داشت. خصوصیتی از نوع داده‌ی LinkBackStatus هم صرفا به منظور تنظیمات مدیریتی در نظر گرفته شده است. 

کلاس پایه نظرات
  /// <summary>
    /// Represents a base class for every comment in system 
    /// </summary>

    public abstract class BaseComment
    {
        #region Properties
        /// <summary>
        /// get or set identifier of record
        /// </summary>
        public virtual long Id { get; set; }
        /// <summary>
        /// gets or sets date of creation 
        /// </summary>
        public virtual DateTime CreatedOn { get; set; }
        /// <summary>
        /// gets or sets displayName of this comment's Creator if  he/she is Anonymous
        /// </summary>
        public virtual string CreatorDisplayName { get; set; }
        /// <summary>
        /// gets or sets body of blog post's comment
        /// </summary>
        public virtual string Body { get; set; }
        /// <summary>
        /// gets or sets body of blog post's comment
        /// </summary>
        public virtual Rating Rating { get; set; }
        /// <summary>
        /// gets or sets informations of agent
        /// </summary>
        public virtual string UserAgent { get; set; }
        /// <summary>
        /// gets or sets siteUrl of Creator if he/she is Anonymous
        /// </summary>
        public virtual string SiteUrl { get; set; }
        /// <summary>
        /// gets or sets Email of Creator if he/she is anonymous
        /// </summary>
        public virtual string Email { get; set; }
        /// <summary>
        /// gets or sets status of comment
        /// </summary>
        public virtual CommentStatus Status { get; set; }
        /// <summary>
        /// gets or sets Ip Address of Creator
        /// </summary>
        public virtual string CreatorIp { get; set; }
        /// <summary>
        /// gets or sets datetime that is modified
        /// </summary>
        public virtual DateTime? ModifiedOn { get; set; }
        /// <summary>
        /// gets or sets counter for report this comment
        /// </summary>
        public virtual int ReportsCount { get; set; }
        #endregion

        #region NavigationProperties

        /// <summary>
        /// get or set user that create this record
        /// </summary>
        public virtual User Creator { get; set; }

        /// <summary>
        /// get or set Id of user that create this record
        /// </summary>
        public virtual long? CreatorId { get; set; }
        #endregion
    }

public enum CommentStatus
    {
        /* 0 - approved, 1 - pending, 2 - spam, -1 - trash */
        [Display(Name = "تأیید شده")]
        Approved = 0,
        [Display(Name = "در انتظار بررسی")]
        Pending = 1,
        [Display(Name = "جفنگ")]
        Spam = 2,
        [Display(Name = "زباله دان")]
        Trash = -1
    }
به مانند BaseContent که برای کپسوله کردن خصوصیات تکراری و نه برای اعمال ارث بری TPH یا TPT، کلاس BaseComment را در نظر گرفته‌ایم. نکته‌ی مهم این است که هر نظری یک درج کننده دارد. ولی اگر فیلد AllowCommentForAnonymous مربوط به مطلب True باشد، این امکان وجود خواهد داشت تا کاربران احراز هویت نشده نیز نظر ارسال کنند. لذا CreatorId در کلاس BaseComment به صورت Nullable در نظر گرفته شده است و یک سری خصوصیت‌ها از قبیل SiteUrl ، CreatorDisplayName ، Email برای کاربران احراز هویت نشده در نظر گرفته شده است. نوع شمارشی CommentStatus نیز برای اعمال مدیریتی در نظر گرفته شده است.
مدل نظرات پست ها
/// <summary>
    /// Represents a blog post's comment
    /// </summary>
    public class BlogComment : BaseComment
    {
        #region Ctor
        /// <summary>
        /// Create One Instance for <see cref="BlogComment"/>
        /// </summary>
        public BlogComment()
        {
            Rating = new Rating();
            CreatedOn = DateTime.Now;
        }
        #endregion

        #region NavigationProperties
        /// <summary>
        /// gets or sets BlogComment's identifier for Replying and impelemention self referencing
        /// </summary>
        public virtual long? ReplyId { get; set; }
        /// <summary>
        /// gets or sets blog's comment for Replying and impelemention self referencing
        /// </summary>
        public virtual BlogComment Reply { get; set; }
        /// <summary>
        /// get or set collection of blog's comment for Replying and impelemention self referencing
        /// </summary>
        public virtual ICollection<BlogComment> Children { get; set; }
        /// <summary>
        /// gets or sets post that this comment sent to it
        /// </summary>
        public virtual BlogPost Post { get; set; }
        /// <summary>
        /// gets or sets post'Id that this comment sent to it
        /// </summary>
        public virtual long PostId { get; set; }
        #endregion
    }
کلاس بالا مشخص کننده‌ی نظرات پست‌های وبلاگ می‌باشند که بیشتر خصوصیت‌های خود را از کلاس پایه‌ی BaseComment به ارث برده است و همچنین ساختار سلسله مراتبی آن نیز  قابل مشاهده است. 
برای سیستم Report یا همان گزارش خطا هم سیستمی شبیه به امتیاز دهی ستاره‌ای در نظر گرفته ایم. برای اینکه مقاله خیلی طولانی شد، بهتر است در مقاله‌ای جدا کار را ادامه داد.
نتیجه این قسمت

نظرات مطالب
اعمال تزریق وابستگی‌ها به مثال رسمی ASP.NET Identity
بهتر است اینکار را انجام ندهید تا تمام claims و طراحی برنامه را به هم نریزد. یک فیلد جداگانه و مخصوص کدملی را به جدول سفارشی کاربران اضافه کنید. سپس در ابتدای لاگین بر اساس کد ملی شخص، شیء user را از بانک اطلاعاتی واکشی کنید. در آخر بر اساس اطلاعات این شیء دریافتی که شامل username و غیره است، PasswordSignInAsync اصلی را فراخوانی کنید تا کوکی‌های برنامه و claims آن به درستی تشکیل شوند.