مطالب
نگاشت JSON به کلاس‌های معادل آن

یکی از مواردی که عموما در برنامه نویسی با آن سر و کار داریم، parse اطلاعات با فرمت‌های مختلف است. از CSV تا XML تا ... JSON .
در مورد کار با XML در دات نت فریم ورک، فضاهای نام مرتبط زیادی وجود دارند؛ برای مثال System.Xml.Linq و System.Xml . همچنین یک روش دیگر هم برای کار با اطلاعات XML ایی در دات نت وجود دارد. می‌شود کلاس معادل یک فایل XML را تولید و سپس اطلاعات آن‌را به این کلاس نگاشت کرد. اطلاعات بیشتر : (^). این برنامه کار خود مایکروسافت است.
در مورد JSON از دات نت سه و نیم به بعد کارهایی صورت گرفته مانند : (^). اما آنچنان دلچسب نیست. جهت رفع این خلاء کتابخانه‌ی سورس باز و بسیار کاملی در این زمینه به نام JSON.NET تهیه شده که از این آدرس قابل دریافت است: (^)
و خبر خوب اینکه امکان تهیه کلاس‌های معادل اطلاعات JSON ایی هم مدتی‌است توسط برنامه نویس‌های مستقل تهیه شده است. یا می‌توان از امکانات توکار دات نت استفاده کرد یا از کتابخانه‌‌هایی مانند JSON.NET یا از هیچکدام! می‌توان یک راست کل اطلاعات JSON ایی دریافتی را به یک یا چند کلاس معادل آن نگاشت کرد:
  • و یا یک ابزار آنلاین مشابه: json2csharp

مطالب
Roslyn #3
بررسی Syntax tree

زمانیکه صحبت از Syntax می‌شود، منظور نمایش متنی سورس کدها است. برای بررسی و آنالیز آن، نیاز است این نمایش متنی، به ساختار داده‌ای ویژه‌ای به نام Syntax tree تبدیل شود و این Syntax tree مجموعه‌ای است از tokenها. Tokenها بیانگر المان‌های مختلف یک زبان، شامل کلمات کلیدی، عملگرها و غیره هستند.


در تصویر فوق، مراحل تبدیل یک قطعه کد #C را به مجموعه‌ای از tokenهای معادل آن مشاهده می‌کنید. علاوه بر این‌ها، Roslyn syntax tree شامل موارد ویژه‌ای به نام Trivia نیز هست. برای مثال در حین نوشتن کدها، در ابتدای سطرها تعدادی space یا tab وجود دارند و یا در این بین ممکن است کامنتی نوشته شود. هرچند این موارد از دیدگاه یک کامپایلر بی‌معنا هستند، اما ابزارهای Refactoring ایی که به Trivia دقت نداشته باشند، خروجی کد به هم ریخته‌ای را تولید خواهند کرد و سبب سردرگمی استفاده کنندگان می‌شوند.


در تصویر فوق، اشاره‌گر ادیتور پس از تایپ semicolon قرار گرفته‌است. در این حالت می‌توانید دو نوع trivia مخصوص فضای خالی و کامنت‌ها را در syntax visualizer، مشاهده کنید.
به علاوه پس از هر token بازه‌ای از اعداد را مشاهده می‌کنید که بیانگر محل قرارگیری آن‌ها در سورس کد هستند. این محل‌ها جهت ارائه‌ی خطاهای دقیق مرتبط با آن نقاط، بسیار مفید هستند.
یک Syntax tree از مجموعه‌ای از syntax nodes تشکیل می‌شود و هر node شامل مواردی مانند تعاریف، عبارات و امثال آن است. در افزونه‌ی Syntax visualizer نودهایی که رنگ قرمز متمایل به قهوه‌ای دارند، بیانگر نودهای Trivia، نودهای آبی، Syntax nodes و نودهای سبز، Syntax token هستند.


مفاهیم این رنگ‌ها را با کلیک بر روی دکمه‌ی Legend هم می‌توان مشاهده کرد.


تفاوت Syntax با Semantics

در Roslyn امکان کار با Syntax و Semantics کدها وجود دارد.
یک Syntax، از گرامر زبان خاصی پیروی می‌کند. در Syntax اطلاعات بسیار زیادی وجود دارند که معنای برنامه را تغییر نمی‌دهند؛ مانند کامنت‌ها، فضاهای خالی و فرمت ویژه‌ی کدها. البته فضاهای خالی در زبان‌هایی مانند پایتون دارای معنا هستند؛ اما در سی‌شارپ خیر. همچنین در Syntax، توافق نامه‌ای وجود دارد که بیانگر تعدادی واژه‌ی از پیش رزرو شده، مانند کلمات کلیدی هستند.
اما Semantics در نقطه‌ی مقابل Syntax قرار می‌گیرد و بیانگر معنای سورس کد است. برای مثال در اینجا تقدم و تاخر عملگرها مفهوم پیدا می‌کنند و یا اینکه Type system چیست و چه نوع‌هایی را می‌توان به دیگری نسبت داد و تبدیل کرد. عملیات Binding در این مرحله رخ می‌دهد و مفهوم identifierها را مشخص می‌کند. برای مثال x در این قسمت از سورس کد، به چه معنایی است و به کجا اشاره می‌کند؟


خواص ویژه‌ی Syntax tree در Roslyn

- تمام اجزای کد را شامل عناصر سازنده‌ی زبان و همچنین Trivia، به همراه دارد.
- API آن توسط کتابخانه‌های ثالث قابل دسترسی است.
- Immutable طراحی شده‌است. به این معنا که زمانیکه syntax tree توسط Roslyn ایجاد شد، دیگر تغییر نمی‌کند. به این ترتیب امکان دسترسی همزمان و موازی به آن بدون نیاز به انواع قفل‌های مسایل همزمانی وجود دارد. اگر کتابخانه‌ی ثالثی به Syntax tree ارائه شده دسترسی پیدا می‌کند، می‌تواند کاملا مطمئن باشد که این اطلاعات دیگر تغییری نمی‌کنند و نیازی به قفل کردن آن‌ها نیست. همچنین این مساله امکان استفاده‌ی مجدد از sub treeها را در حین ویرایش کدها میسر می‌کند. به آن‌ها mutating trees نیز گفته می‌شود.
- مقاوم است در برابر خطاها. اگر از قسمت اول به خاطر داشته باشید، Roslyn می‌بایستی جایگزین کامپایلر دومی به نام کامپایلر پس زمینه‌ی ویژوال استودیو که خطوط قرمزی را ذیل سطرهای مشکل دار ترسیم می‌کند، نیز می‌شد. فلسفه‌ی طراحی این کامپایلر، مقاوم بودن در برابر خطاهای تایپی و هماهنگی آن با تایپ کدها توسط برنامه نویس بود. Syntax tree در Roslyn نیز چنین خاصیتی را دارد و اگر مشغول به تایپ شوید، باز هم کار کرده و اینبار خطاهای موجود را نمایش می‌دهد که می‌تواند توسط ابزارهای نمایش دهنده‌ی ویژوال استودیو یا سایر ابزارهای ثالث استفاده شود.


برای نمونه در تصویر فوق، تایپ semicolon فراموش شده‌است؛ اما همچنان Syntax tree در دسترس است و به علاوه گزارش می‌دهد که semicolon مفقود است و تایپ نشده‌است.


Parse سورس کد توسط Roslyn

ابتدا یک پروژه‌ی کنسول ساده‌ی دات نت 4.6 را در VS 2015 آغاز کنید. سپس از طریق خط فرمان نیوگت، دستور ذیل را صادر نمائید:
 PM> Install-Package Microsoft.CodeAnalysis
به این ترتیب API لازم جهت کار با Roslyn به پروژه اضافه خواهند شد.
سپس کدهای ذیل را به آن اضافه کنید:
using System;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
 
namespace Roslyn01
{
    class Program
    {
        static void Main(string[] args)
        {
            parseText();
        }
 
        static void parseText()
        {
            var tree = CSharpSyntaxTree.ParseText("class Foo { void Bar(int x) {} }");
            Console.WriteLine(tree.ToString());
            Console.WriteLine(tree.GetRoot().NormalizeWhitespace().ToString());
 
            var res = SyntaxFactory.ClassDeclaration("Foo")
                .WithMembers(SyntaxFactory.List<MemberDeclarationSyntax>(new[] {
                    SyntaxFactory.MethodDeclaration(
                        SyntaxFactory.PredefinedType(
                            SyntaxFactory.Token(SyntaxKind.VoidKeyword)
                        ),
                        "Bar"
                    )
                    .WithBody(SyntaxFactory.Block())
                }))
                .NormalizeWhitespace();
 
            Console.WriteLine(res);
        } 
    }
}
توضیحات:
کار Parse سورس کد دریافتی، بر اساس سرویس‌های زبان متناظر با آن‌ها آغاز می‌شود. برای مثال سرویس‌هایی مانند VisualBasicSyntaxTree و یا CSharpSyntaxTree مثال فوق که سورس کد مورد آنالیز آن، از نوع سی‌شارپ است.
این کلاس‌های Factory، دارای دو متد Create و ParseText هستند. کار متد ParseText آن مشخص است؛ یک قطعه‌ی متنی از کد را آنالیز کرده و معادل Syntax Tree آن‌را تولید می‌کند. متد Create آن، اشیایی مانند نودهای Syntax visualizer را دریافت کرده و بر اساس آن‌ها یک Syntax tree را تولید می‌کند.
کار با متد Create آنچنان ساده نیست. به همین جهت یکی از اعضای تیم Roslyn برنامه‌ای را به نام Roslyn Quoter ایجاد کرده‌است که نسخه‌ی آنلاین آن‌را در اینجا و سورس کد آن‌را در اینجا می‌توانید بررسی کنید.
جهت آزمایش، همان قطعه‌ی متنی سورس کد مثال فوق را در نسخه‌ی آنلاین آن جهت آنالیز و تولید ورودی متد Create، وارد کنید. خروجی آن‌را می‌توان مستقیما در متد Create بکار برد.


فرمت کردن خودکار کدها به کمک Roslyn

اگر بر روی tree حاصل، متد ToString را فراخوانی کنیم، خروجی آن مجددا سورس کد مورد آنالیز است. اگر علاقمند بودید که Roslyn به صورت خودکار کدهای ورودی را فرمت کند و تمام آن‌ها را در یک سطر نمایش ندهد، متد NormalizeWhitespace را بر روی ریشه‌ی Syntax tree فراخوانی کنید:
 tree.GetRoot().NormalizeWhitespace().ToString()
اینبار خروجی فراخوانی فوق به صورت ذیل است:
class Foo
{
    void Bar(int x)
    {
    }
}


کوئری گرفتن از سورس کد توسط Roslyn

در ادامه قصد داریم با سه روش مختلف کوئری گرفتن از Syntax tree، آشنا شویم. برای این منظور متد ذیل را به پروژه‌ای که در ابتدای برنامه آغاز کردیم، اضافه کنید:
static void querySyntaxTree()
{
    var tree = CSharpSyntaxTree.ParseText("class Foo { void Bar() {} }");
    var node = (CompilationUnitSyntax)tree.GetRoot();
 
    // Using the object model
    foreach (var member in node.Members)
    {
        if (member.Kind() == SyntaxKind.ClassDeclaration)
        {
            var @class = (ClassDeclarationSyntax)member;
 
            foreach (var member2 in @class.Members)
            {
                if (member2.Kind() == SyntaxKind.MethodDeclaration)
                {
                    var method = (MethodDeclarationSyntax)member2;
                    // do stuff
                }
            }
        }
    }
 
 
    // Using LINQ query methods
    var bars = from member in node.Members.OfType<ClassDeclarationSyntax>()
               from member2 in member.Members.OfType<MethodDeclarationSyntax>()
               where member2.Identifier.Text == "Bar"
               select member2;
    var res = bars.ToList();
 
 
    // Using visitors
    new MyVisitor().Visit(node);
}
توضیحات:
روش اول کوئری گرفتن از Syntax tree، استفاده از object model آن است. در اینجا هربار، نوع و Kind هر نود را بررسی کرده و در نهایت به اجزای مدنظر خواهیم رسید. شروع کار هم با دریافت ریشه‌ی syntax tree توسط متد GetRoot و تبدیل نوع آن نود به CompilationUnitSyntax می‌باشد.
روش دوم استفاده از روش LINQ است؛ با توجه به اینکه ساختار یک Syntax tree بسیار شبیه است به LINQ to XML. در اینجا یک سری نود، ریشه و فرزندان آن‌ها را داریم که با روش LINQ بسیار سازگار هستند. برای نمونه در مثال فوق، در ریشه‌ی Parse شده، در تمام کلاس‌های آن، به دنبال متد یا متدهایی هستیم که نام آن‌ها Bar است.
و در نهایت روش مرسوم و متداول کار با Syntax trees، استفاده از الگوی Visitors است. همانطور که در کدهای دو روش قبل مشاهده می‌کنید، باید تعداد زیادی حلقه و if و else نوشت تا به جزء و المان مدنظر رسید. راه ساده‌تری نیز برای مدیریت این پیچیدگی وجود دارد و آن استفاده از الگوی Visitor است. کار این الگو ارائه‌ی متدهایی قابل override شدن است و فراخوانی آن‌ها، در طی حلقه‌هایی پشت صحنه که این Visitor را اجرا می‌کنند، صورت می‌گیرد. بنابراین در اینجا دیگر برای رسیدن به یک متد، حلقه نخواهید نوشت. تنها کاری که باید صورت گیرد، override کردن متد Visit المانی خاص در Syntax tree است.
هر نود در syntax tree دارای متدی است به نام Accept که یک Visitor را دریافت می‌کند. همچنین Visitorهای نوشته شده نیز دارای متد Visit یک نود هستند.
نمونه‌ای از این Visitors را در کلاس ذیل مشاهده می‌کنید:
class MyVisitor : CSharpSyntaxWalker
{
    public override void VisitMethodDeclaration(MethodDeclarationSyntax node)
    {
        if (node.Identifier.Text == "Bar")
        {
            // do stuff
        }
 
        base.VisitMethodDeclaration(node);
    }
}
در اینجا برای رسیدن به تعاریف متدها دیگر نیازی نیست تا حلقه نوشت. بازنویسی متد VisitMethodDeclaration، دقیقا همین کار را انجام می‌دهد و در طی پروسه‌ی Visit یک Syntax tree، اگر متدی در آن تعریف شده باشد، متد VisitMethodDeclaration حداقل یکبار فراخوانی خواهد شد.
کلاس پایه‌ی CSharpSyntaxWalker از کلاس CSharpSyntaxVisitor مشتق شده‌است و به تمام امکانات آن دسترسی دارد. علاوه بر آن‌ها، کلاس CSharpSyntaxWalker به Tokens و Trivia نیز دسترسی دارد.
نحوه‌ی استفاده از Visitor سفارشی نوشته شده نیز به صورت ذیل است:
 new MyVisitor().Visit(node);
در اینجا متد Visit این Visitor را بر روی نود ریشه‌ی Syntax tree اجرا کرده‌ایم.
نظرات مطالب
BloggerToCHM 1.3
سلام، بنظرم اومد که اگه این برنامه امکانی داشته باشه تا کاربرای برنامه بتونن تقریبا مثل خود بلاگر آخرین کامنت ها رو فهرست شده نشون بده مثل لیستی که تو صفحه اول فایل خروجی وجود داره و الان پست ها رو فهرست کرده یا شبیه به اون باشه برای خواندن نظرات امکان خیلی مناسبی میشه.
مطالب
کار با Razor در ASP.NET Core 2.0
پیش نویس: این مقاله ترجمه شده فصل 5 کتاب Pro Asp.Net Core MVC2 می‌باشد.


ایجاد یک پروژه با استفاده Razor

در ادامه با هم یک مثال را با استفاده از Razor ایجاد می‌کنیم. یک پروژه جدید را با قالب Empty و با نام Razor ایجاد می‌کنیم.

مراحل:

1- ابتدا در کلاس startup قابلیت MVC را فعال می‌کنیم؛ با قرار دادن کد زیر در متد ConfigureServices:
 services.AddMvc();
و بعد کد زیر را که مربوط به اجرای پروژه‌ی hello Word است ، از متد Configure حذف می‌کنیم:
app.Run(async (context) =>
{
   await context.Response.WriteAsync("Hello World!");
});
در نهایت محتویات  فایل StartUp به صورت زیر می‌باشد:

namespace Razor
{
    public class Startup
    {
        // This method gets called by the runtime. Use this method to add services to the container.
        // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc();
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            //app.Run(async (context) =>
            //{
            //    await context.Response.WriteAsync("Hello World!");
            //});
        }
    }
}


ایجاد یک Model
 یک پوشه جدید را به نام Models ایجاد و بعد در این پوشه یک کلاس را به نام Product ایجاد می‌کنیم و کدهای زیر را در آن قرار میدهیم:
namespace Razor.Models
{
    public class Product
    {
        public int ProductID { get; set; }
        public string Name { get; set; }
        public string Description { get; set; }
        public decimal Price { get; set; }
        public string Category { set; get; }
    }
}

ایجاد Controller
تنظیمات پیشفرض را در فایل Startup انجام داده‌ایم. درخواست‌هایی را که توسط کاربر ارسال میشوند، به controller پیشفرضی که نامش در اینجا Home است، ارسال می‌کند. حالا ما یک پوشه جدید را به نام Controllers ایجاد می‌کنیم و در آن یک کنترلر جدید را به نام HomeController ایجاد می‌کنیم و کدهای زیر را در آن قرار میدهیم:
namespace Razor.Controllers
{
    public class HomeController : Controller
    {
        // GET: /<controller>/
        public ViewResult Index()
        {
            Product myProduct = new Product
            {
                ProductID = 1,
                Name = "Kayak",
                Description = "A boat for one person",
                Category = "Watersports",
                Price = 275M
            };
            return View(myProduct);
        }
    }
}
در این کلاس یک Action Method را به نام index ایجاد می‌کنیم. سپس در آن یک شیء را از مدل ایجاد و مقدار دهی و آن‌را به View ارسال می‌کنیم تا در زمان بارگذاری View از این شیء استفاده نماییم. نیاز نیست نام View را مشخص کنید. به صورت پیشفرض نام View با نام اکشن متد یکسان می‌باشد.

 
ایجاد View
 برای ایجاد یک View پیشفرض برای Action Method فوق در پوشه Views/Home یک MVC View Page (Razor View Page) را به نام Index.schtml ایجاد می‌کنیم.
- نکته1: پوشه View و داخل آن Home را ایجاد کنید.
- نکته2: معادل MVC View Page در نسخه جدید، Razor View می‌باشد. اگر در لیست این آیتم را انتخاب کنید، در توضیحات پنل سمت راست میتوانید این مطلب را مشاهده کنید.
- نکته3: دقت نمایید برای اینکه پروژه net Core2. باشد و تمام مشخصات موردنظر را داشته باشد، باید نگارش ویژوال استودیو VS 2017.15.6.6 و یا بیشتر باشد.
 
@model Razor.Models.Product
@{
    Layout = null;
}

<!DOCTYPE html>
<html>
<head>
    <meta name="viewport" content="width=device-width"/>
    <title>Index</title>
</head>
<body>
    Content will go here
</body>
</html>

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


استفاده از Model در یک View
برای استفاده از شیء مدل در View، باید در View به آن شیء و مشخصات آن دسترسی داشته باشیم که این دسترسی را Razor با استفاده از کاراکتر @ برای ما ایجاد می‌کند. برای اتصال به Model از عبارت model@ (حتما باید حروف کوچک باشد) استفاده می‌کنیم و برای دسترسی به مشخصات مدل از عبارت Model@ (حتما باید حرف اول آن بزرگ باشد) استفاده می‌کنیم. به کد زیر دقت کنید:

@model Razor.Models.Product
@{
    Layout = null;
}

<!DOCTYPE html>
<html>
<head>
    <meta name="viewport" content="width=device-width"/>
    <title>Index</title>
</head>
<body>
  @Model.Name
</body>
</html>
خط اولی که در View تعریف شده است، با استفاده از عبارت model@ مانند تعریف نوع مدل می‌باشد و کار اتصال مدل به View را انجام میدهد و همین خط باعث میشود زمانی که شما در تگ body عبارت Model@ وبعد دات (.) را میزنید، لیست خصوصیات آن مدل ظاهر میشوند. لیست شدن خصوصیات بعد از دات(.) یکی از کارهای پیشفرض ویژوال استودیو می‌باشد؛ برای اینکه از خطاهای احتمالی کاربر جلوگیری کند.

نتیجه خروجی بالا مانند زیر می‌باشد:

 



معرفی View Imports

زمانیکه بخواهیم به یک کلاس در View دسترسی داشته باشیم، باید فضای نام آن کلاس را مانند کد زیر در بالای View اضافه کنیم. حالا اگر بخواهیم به چند کلاس دسترسی داشته باشیم، باید این کار را به ازای هر کلاس در هر View انجام دهیم که سبب ایجاد کدهای اضافی در View‌ها میشود. برای بهبود این وضعیت می‌توانید یک کلاس View Import را در پوشه‌ی Views ایجاد کنید و تمام فضاهای نام را در آن قرار دهید. با اینکار تمام فضاهای نامی که در این کلاس View Import قرار گرفته‌اند، در تمام Viewهای موجود در پوشه Views قابل دسترسی خواهند بود.

در پوشه View راست کلیک کرده و گزینه Add و بعد New Item را انتخاب می‌کنیم و در کادر باز شده، آیتم MVC View Import Page (در نسخه جدید نام آن  Razor View Imports است) انتخاب می‌کنیم. ویژوال استودیو به صورت پیش فرض نام ViewImports.cshtml_ را برای آن قرار میدهد.


نکته: استاندارد نام گذاری این View این می‌باشد که ابتدای آن کاراکتر (_) حتما وجود داشته باشد.
 
در کلاس تعریف شده با استفاده از عبارت using@ فضای نام‌های خود را قرار میدهیم؛ مانند زیر:
 @using Razor.Models
در این کلاس شما فقط میتوانید فضاهای نام را مانند بالا قرار دهید. پس از آم قسمت فضاهای نام اضافی در Viewها قابل حذف میشوند و در این حالت فقط نام کلاس مدل را در بالای فرم قرار میدهیم مانند زیر:
@model Product
@{
    Layout = null;
}

<!DOCTYPE html>
<html>
<head>
    <meta name="viewport" content="width=device-width"/>
    <title>Index</title>
</head>
<body>
  @Model.Name
</body>
</html>


Layout ها

یکی دیگر از عبارت‌های مهم Razor که در فایل Index وجود دارد، عبارت زیر است:
@{
    Layout = null;
}
شما می‌توانید در بین {} کدهای سی شارپ را قرار دهید. حالا مقدار Layout را مساوی نال قرار داده‌ایم که بگوییم View مستقلی است و از قالب مشخصی استفاده نمی‌کند.

از Layout برای طراحی الگوی Viewها استفاده می‌کنیم. اگر بخواهیم برای View ها یک قالب طراحی کنیم و این الگو بین تمام یا چندتای از آن‌ها مشترک باشد، کدهای مربوط به الگو را با استفاده از Layout ایجاد می‌کنیم و از آن در View ها استفاده می‌کنیم. اینکار برای جلوگیری از درج کدهای تکراری قالب در برنامه انجام میشود. با اینکار اگر بخواهیم در الگو تغییری را انجام دهیم، این تغییر را در یک قسمت انجام میدهم و سپس به تمام Viewها اعمال میشود.
 
Layout
طرحبندی  Viewهای برنامه بطور معمول بین چند View مشترک است و طبق استاندارد ویژوال استودیو در پوشه‌ی Views/Shared قرار میگیرد. برای ایجاد Layout، روی پوشه Views/shared راست کلیک کرده و بعد گزینه Add وبعد NewItem و سپس گزینه MVC View Layout Page (نام آن در نسخه جدید Razor Layout است) را انتخاب می‌کنیم و ابتدای نام آن را به صورت پیشفرض کاراکتر (_) قرار میدهیم.
 


هنگام ایجاد این فایل توسط ویژوال استودیو، کدهای زیر به صورت پیش فرض در فایل ایجاد شده وجود دارند: 
<!DOCTYPE html>

<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>@ViewBag.Title</title>
</head>
<body>
    <div>
        @RenderBody()
    </div>
</body>
</html>
طرحبندی‌ها فرم خاصی از View هستند و دو عبارت @ در کدهای آن وجود دارد. در اینجا فراخوانی RenderBody@ سبب درج محتویات View مشخص شده توسط Action Method در این مکان می‌شود. عبارت دیگری که در اینجا وجود دارد، ViewBag است که برای مشخص کردن عنوان در اینجا استفاده شده‌است.
ViewBag ویژگی مفیدی است که اجازه می‌دهد تا مقادیر و داده‌ها در برنامه گردش داشته باشند و در این مورد بین یک View و Layout منتقل شوند. در ادامه خواهید دید وقتی Layout را به یک نمایه اعمال می‌کنیم، این مورد چگونه کار می‌کند.

عناصر HTML در یک Layout به هر View که از آن استفاده می‌کند، اعمال و توسط آن یک الگو برای تعریف محتوای معمولی ارائه می‌شود؛ مانند کدهای زیر. من برخی از نشانه گذاری‌های ساده را به Layout اضافه کردم تا اثر قالب آن آشکارتر شود:
<!DOCTYPE html>

<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>@ViewBag.Title</title>
    <style>
        #mainDiv {
            padding: 20px;
            border: solid medium black;
            font-size: 20pt
        }
    </style>
</head>
<body>
    <h1>Product Information</h1>
    <div id="mainDiv">
        @RenderBody()
    </div>
</body>
</html>
در اینجا یک عنصر عنوان و همچنین بعضی از CSS‌ها را به عنصر div که حاوی عبارت RenderBody@ است، اضافه کرده‌ام؛ فقط برای اینکه مشخص شود، چه محتوایی از طرحبندی سایت می‌آید و چه چیزی از View.
 

اعمال Layout

برای اعمال کردن Layout به یک View، نیاز است مشخصه Layout آن‌را مقدار دهی و سپس Htmlهای اضافی موجود در آن‌را مانند المنت‌های head و Body حذف کنید؛ همانند کدهای زیر:
@model Product
@{
    Layout = "_BasicLayout";
    ViewBag.Title = "Product";
}
در خاصیت Layout، مقدار را برابر نام فایل Layout، بدون پسوند cshtml آن قرار میدهیم. Razor در مسیر پوشه Views/shared و پوشه Views/Home فایل Layout را جستجو می‌کند.
در اینجا عبارت ViewBag.Title را نیز مقدار دهی می‌کنیم. زمانیکه فایل فراخوانی میشود، عنوان آن صفحه با این مقدار، جایگزین خواهد شد.
تغییرات این View بسیار چشمگیر است؛ حتی برای چنین برنامه ساده‌ای. طرحبندی شامل تمام ساختار مورد نیاز برای هر پاسخ HTML است که View را به صورت یک محتوای پویا ارائه می‌دهد و داده‌ها را به کاربر منتقل می‌کند. هنگامیکه MVC فایل Index.cshtmal را پردازش می‌کند، این طرحبندی برای ایجاد پاسخ HTML نهایی یکپارچه می‌شود؛ مانند عکس زیر:
 


 
View Start

بعضی موارد هنوز در برنامه وجود دارند که می‌توان کنترل بیشتری بر روی آن‌ها داشته باشید. مثلا اگر بخواهیم نام یک فایل layout را تغییر دهیم، مجبور هستیم تمام Viewهایی را که از آن Layout استفاده می‌کنند، پیدا کنید و نام Layout استفاده شده در آن‌ها را تغییر دهیم. اینکار احتمال خطای بالایی دارد و امکان دارد بعضی View ها از قلم بیفتند و برنامه دچار خطا شود. بنابراین با استفاده از View Start می‌توانیم این مشکل را برطرف کنیم. وقتی نام Layout تغییر کرد، تنها کافی است نام آن‌را در View Start تغییر دهیم. اکنون زمانیکه برنامه را اجرا می‌کنیم، MVC به دنبال فایل View Start می‌گردد و اگر اطلاعاتی داشته باشد، آن را اجرا می‌کند و الویت این فایل از تمام فایل‌های دیگر بیشتر است و ابتدا تمام آنها اجرا میشوند.

برای ایجاد یک فایل شروع مشاهده، روی پوشه‌ی Views کلیک راست کرده و گزینه add->New Items را انتخاب می‌کنیم و از پنجره باز شده گزینه ( Razor View Start ) Mvc View Start Page را انتخاب می‌کنیم؛ مانند تصویر زیر:


ویژوال استودیو به صورت پیش فرض نام ViewStart.cshtml_ را به عنوان نام آن قرار میدهد؛ شما گزینه‌ی Create را در این حالت انتخاب کنید. محتویات فایل ایجاد شده به صورت زیر می‌باشد:
@{
    Layout = "_Layout";
}
برای اعمال Layout جدید به تمام Viewها، مقدار Layout را معادل طرحبندی خود تغییر میدهیم؛ مانند کد زیر: 
@{
    Layout = "_BasicLayout";
}
از آنجا که فایل View Start دارای مقداری برای Layout می‌باشد، می‌توانیم عبارت‌های مربوطه را در Index.cshtml‌ها حذف کنیم:
@model Product
@{
    ViewBag.Title = "Product";
}
در اینجا لازم نیست مشخص کنیم که من می‌خواهم از فایل View Start استفاده کنم. MVC این فایل را پیدا خواهد کرد و از محتویات آن به طور خودکار استفاده می‌کند. البته باید دقت داشت که مقادیر تعریف شده‌ی در فایل View اولویت دارند و باعث میشوند با معادل‌های فایل View Start جایگزین شوند.

شما همچنین می‌توانید چندین فایل View Start را برای تنظیم مقادیر پیش فرض قسمت‌های مختلف برنامه، استفاده کنید. یک فایل Razor همواره توسط نزدیک‌ترین فایل View start، پردازش می‌شود. به این معنا که شما می‌توانید تنظیمات پیش فرض را با افزودن یک فایل View Start به پوشه Views / Home و یا Views / Shared لغو کنید.

نکته: درک تفاوت میان حذف محتویات فایل View Start یا مساوی Null قرار دادن آن مهم است. اگر View شما مستقل است و شما نمی‌خواهید از آن استفاده کنید، بنابراین مقدار Layout آن‌را صریحا برابر Null قرار دهید. اگر مقدار دهی صریح شما مشخصه Layout را نادیده بگیرید، Mvc فرض می‌کند که میخواهید layout را داشته باشید و مقدار آن را از فایل View Start تامین می‌کند.
 

استفاده از عبارت‌های شرطی در Razor
 
حالا که من اصول و مبانی View و Layout را به شما نشان دادم، قصد دارم به انواع مختلفی از اصطلاحات که Razor آن‌ها را پشتیبانی می‌کند و نحوه استفاده‌ی از آنها را برای ایجاد محتوای نمایشی، ارائه دهم. در یک برنامه MVC، بین نقش‌هایی که توسط View و Action متدها انجام می‌شود، جدایی روشنی وجود دارد. در اینجا قوانین ساده‌ای وجود دارند که در جدول زیر مشخص شده‌اند:

کامپوننت 
انجام میشود 
انجام نمیشود 
  Action Method    یک شیء ViewModel را به View ارسال می‌کند.
  یک فرمت داده را به View ارسال می‌کند.
  View    از شیء ViewModel برای ارائه محتوا به کاربر استفاده می‌کند.
  هر جنبه‌ای از شیء View Model مشخصات را تغییر می‌دهد.
 
برای به دست آوردن بهترین نتیجه از MVC، نیاز به تفکیک و جداسازی بین قسمت‌های مختلف برنامه را دارید. همانطور که می‌بینید، می‌توانید کاملا با Razor کار کنید و این نوع فایل‌ها شامل دستورالعمل‌های سی شارپ نیز هستند. اما شما نباید از Razor برای انجام منطق کسب و کار استفاده کنید و یا هر گونه اشیاء Domain Model خود را دستکاری کنید. کد زیر نشان میدهد که یک عبارت جدید به View اضافه میشود:
*@
@model Product
@{

    ViewBag.Title = "Product";
}
<p>Product Name: @Model.Name</p> <p>Product Price: @($"{Model.Price:C2}")</p>
می‌توان برای خصوصیت price، در اکشن متد فرمتی را تعریف و بعد آن را به View ارسال کنیم. این روش کار می‌کند، اما استفاده از این رویکرد منافع الگوی MVC را تضعیف می‌کند و توانایی من برای پاسخ دادن به تغییرات در آینده را کاهش می‌دهد. باید به یاد داشته باشید که در ASP NET Core MVC، استفاده مناسب از الگوی MVC اجتناب ناپذیر است و شما باید از تاثیر تصمیمات طراحی و کدگذاری که انجام می‌دهید مطلع باشید.
 

پردازش داده‌ها در مقابل فرمت

تفاوت بین پردازش داده و قالب بندی داده مهم است.
- نمایش فرمت داده‌ها: به همین دلیل در آموزش قبل من یک نمونه از شیء کلاس Product را برای View ارسال کرده‌ام و نه فرمت خاص یک شیء را به صورت یک رشته نمایشی.
- پردازش داده: انتخاب اشیاء داده‌‌ای برای نمایش، مسئولیت کنترلر است و در این حالت مدلی را برای دریافت و تغییر داده مورد نیاز، فراخوانی می‌کند.
گاهی سخت است که متوجه شویم کدی جهت پردازش داده است و یا فرمت آن.


اضافه نمودن مقدار داده ای

ساده‌ترین کاری را که می‌توانید با یک عبارت Razor انجام دهید این است که یک مقدار داده را در نمایش دهید. رایج‌ترین کار برای انجام آن، استفاده از عبارت Model@ است. ویوو Index یک مثال از این مورد است؛ شبیه به این مورد:
 <p>Product Name: @Model.Name</p>
شما همچنین می‌توانید یک مقدار را با استفاده قابلیت ViewBag نیز به View ارسال نمایید که از این قابلیت در Layout برای تنظیم کردن محتوای عنوان استفاده کردیم. اما در حالت زیر یک مدل نوع دار را به سمت View ارسال کرده‌ایم:
using Microsoft.AspNetCore.Mvc;
using Razor.Models;


namespace Razor.Controllers
{
    public class HomeController : Controller
    {
        // GET: /<controller>/
        public ViewResult Index()
        {
            Product myProduct = new Product
            {
                ProductID = 1,
                Name = "Kayak",
                Description = "A boat for one person",
                Category = "Watersports",
                Price = 275M
            };
            return View(myProduct);
        }
    }
}

خصوصیت ViewBag یک شیء پویا را باز می‌گرداند که می‌تواند برای تعیین خواص دلخواهی مورد استفاده قرار گیرد. از آنجا که ویژگی ViewBag پویا است، لازم نیست که نام خصوصیات را پیش از آن اعلام کنم. اما این بدان معنا است که ویژوال استودیو قادر به ارائه پیشنهادهای تکمیل کننده برای ViewBag نیست.
در مثال زیر از یک مدل نوع دار و مزایای به همراه آن استفاده شده‌است: 
 <p>Product Name: @Model.Name</p> <p>Product Price: @($"{Model.Price:C2}")</p> <p>Stock Level: @ViewBag.StockLevel</p>
نتیجه آن‌را در زیر می‌توانید مشاهده کنید:



تنظیم مقادیر مشخص

شما همچنین می‌توانید از عبارات Razor برای تعیین مقدار عناصر، استفاده کنید:
@model Product
@{

    ViewBag.Title = "Product";
}
p>Product Name: @Model.Name</p> <p>Product Price: @($"{Model.Price:C2}")</p> 
<p>Stock Level: @ViewBag.StockLevel</p>
<div data-productid="@Model.ProductID" data-stocklevel="@ViewBag.StockLevel">    
<p>Product Name: @Model.Name</p>    
<p>Product Price: @($"{Model.Price:C2}")</p>   
 <p>Stock Level: @ViewBag.StockLevel</p> 
</div>
در اینجا از عبارات Razor، برای تعیین مقدار برای برخی از ویژگی‌های داده در عنصر div استفاده کرده‌ام.

نکته: ویژگی‌های داده‌ها که نام آنها *-data است، روشی برای ایجاد ویژگی‌های سفارشی برای سال‌ها بوده است و بعنوان بخشی از استاندارد HTML5 است. عموما کدهای جاوا اسکریپت از آن‌ها برای یافتن اطلاعات استفاده می‌کنند.

اگر برنامه را اجرا کنید و به منبع HTML که به مرورگر فرستاده شده نگاهی بیندازید، خواهید دید که Razor مقادیر صفات را تعیین کرده است؛ مانند این:
<div data-productid="1" data-stocklevel="2">    <p>Product Name: Kayak</p>    <p>Product Price: £275.00</p>    <p>Stock Level: 2</p> </div>


استفاده از عبارت‌های شرطی

Razor قادر به پردازش عبارات شرطی است. در ادامه کدهای Index View را که در آن دستورات شرطی اضافه شده‌اند می‌بینید:

@model Product
@{ ViewBag.Title = "Product Name"; }
<div data-productid="@Model.ProductID" data-stocklevel="@ViewBag.StockLevel">  
  <p>Product Name: @Model.Name</p>   
 <p>Product Price: @($"{Model.Price:C2}")</p> 
   <p>Stock Level:       
 @switch (ViewBag.StockLevel)
{
    case 0:@:Out of Stock                break;           
    case 1:          
    case 2:        
    case 3:            
    <b>Low Stock (@ViewBag.StockLevel)</b>         
       break;      
    default:            
    @: @ViewBag.StockLevel in Stock          
      break;      
  }    
</p>
</div>


برای شروع یک عبارت شرطی، یک علامت @ را در مقابل کلمه کلیدی if یا swicth سی شارپ قرار دهید. سپس بخش کد را داخل } قرار می‌دهیم. درون قطعه کد Razor، می‌توانید عناصر HTML و مقادیر داده را در خروجی نمایش دهید؛ مانند:
 <b>Low Stock (@ViewBag.StockLevel)</b>
در اینجا لازم نیست عناصر یا عبارات را در نقل قول قرار دهیم و یا آنها را به روش خاصی تعریف کنیم. موتور Razor این را به عنوان خروجی برای پردازش تفسیر خواهد کرد.
با این حال، اگر می‌خواهید متن واقعی را در نظر بگیرید و دستورات Razor را لغو کنید،‌می‌توانید از :@ استفاده کنید تا عین آن عبارت درج شود.
مطالب
ایجاد یک HtmlHelper سفارشی با پشتیبانی از UnobtrusiveValidationAttributes
همانطور که می‌دانید، در MVC برای اعتبارسنجی داده‌ها در سمت کلاینت از کتابخانه‌ی jquery استفاده می‌شود. مایکروسافت از طریق jquery.validate.unobtrusive و گسترش کتابخانه‌ی jquery.validate توانسته منطق خود را برای اعتبارسنجی داده‌ها در سمت کلاینت پیاده سازی کند. 
برای این منظور MVC به کنترلهایی که باید اعتبارسنجی شوند، خصوصیاتی را از طریق Data Attribute اضافه می‌کند. برای مثال اگر در مدل خود فیلد ایمیل را به شکل زیر امضاء کرده باشید:
[Display(Name = "رایانامه")]
[Required(AllowEmptyStrings = false, ErrorMessage = "رایانامه خود را وارد کنید.")]
[RegularExpression("\\w+([-+.']\\w+)*@\\w+([-.]\\w+)*\\.\\w+([-.]\\w+)*", ErrorMessage = "نشانی رایانامه پذیرفتنی نمی‌باشد.")]
[ExistField(Action = "EmailExist", Namespace = "Parsnet.Controllers", Controller = "Account", ErrorMessage = "این رایانامه پیشتر به کار گرفته شده است.")]
        public string Email { get; set; }
و در View مورد نظر از Htmlhlper مربوطه به شکل زیر استفاده کرده باشید:
@Html.TextBoxFor(m => m.Email, new { @class = "form-control en", placeholder = @Html.DisplayNameFor(m => m.Email) })
در نهایت، Html خروجی در سمت کلاینت به شکل زیر خواهد بود:
<input data-val="true" data-val-existfiledvalidator="این رایانامه پیشتر به کار گرفته شده است." data-val-existfiledvalidator-url="/account/emailexist" data-val-regex="نشانی رایانامه پذیرفتنی نمی‌باشد." data-val-regex-pattern="\w+([-+.']\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*" data-val-required="رایانامه خود را وارد کنید." id="Email" name="Email" placeholder="رایانامه" value="" type="text">
و در اینجا کتابخانه‌ی اعتبارسنجی MVC با استفاده از همین خصوصیات *-data، اطلاعات مورد نیاز را جهت نمایش، اعتبارسنجی، تنظیم و بکارگیری، مورد استفاده قرار می‌دهد.
در یکی از پروژه‌هایی که در حال کار کردن بر روی آن هستم لازم شد تا این اطلاعات اعتبارسنجی به یک تگ span اعمال شوند. سناریوی مورد نظر به این صورت است که در بخش پروفایل کاربر، کاربر می‌تواند اطلاعات خود را بصورت inline ویرایش کنید. برای اینکار از کتابخانه X-editable استفاده کردم که از این لینک قابل دریافت است.
ابتدا اطلاعات موردنیاز در یک تگ span نمایش داده می‌شوند و در ادامه کاربر پس از کلیک بر روی آیکن ویرایش، امکان تغییر آن فیلد را دارد. برای اعتبارسنجی داده‌ها لازم بود تا تمامی اطلاعات مورد نیاز اعتبارسنجی در سمت کلاینت را به شکلی در اختیار داشته باشم و به ذهنم رسید تا با ایجاد یک Helper سفارشی، خصوصیات موردنظر را به تگ span اعمال کنم و سپس در سمت کلاینت از آن استفاده کنم. در واقع با اینکار با استفاده از همان کلاس مدل و این Helper سفارشی، از وارد کردن دستی داده‌ها و خصوصیات اجتناب کنم. (تصور کنید چیزی حدود 30 فیلد که هرکدام حداقل 4 خصوصیت دارند)
با نگاهی به سورس MVC دیدم پیاده سازی این قابلیت چندان سخت نیست و به راحتی با ایجاد یک Helper سفارشی، منطق خود را پیاده سازی و اعتبارسنجی در سمت کلاینت را به راحتی اعمال کردم.
برای ایجاد این Helper سفارشی ابتدا یک کلاس استاتیک ایجاد کنید و با استفاده از extension Method‌ها یک helper جدید را ایجاد کنید:
namespace Parsnet
{
     public static MvcHtmlString SpanFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, object htmlAttributes)
        {
            
            var sb = new StringBuilder();

            var span = new TagBuilder("span");

            var metadata = ModelMetadata.FromLambdaExpression<TModel, TProperty>(expression, htmlHelper.ViewData);
            var name = ExpressionHelper.GetExpressionText(expression);
            var fullName = htmlHelper.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(name);
            var value = "";

            if (metadata.Model != null && metadata.Model.GetType() == typeof(List<IdentityProvider.IdentityRole>))
            {
                var modelList = (List<IdentityProvider.IdentityRole>)metadata.Model;
                value = String.Join("، ", modelList.Select(r => r.Name));
            }
            else
            {
                value = htmlHelper.FormatValue(metadata.Model, null);
            }

            span.MergeAttributes<string, object>(((IDictionary<string, object>)HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes)));

            var fieldName = fullName.Split('.')[1];
            span.MergeAttribute("data-name", fieldName, true);
            span.MergeAttributes<string, object>(htmlHelper.GetUnobtrusiveValidationAttributes(name, metadata));

            sb.Append(span.ToString(TagRenderMode.StartTag));
            sb.Append(value);
            sb.Append(span.ToString(TagRenderMode.EndTag));

            return new MvcHtmlString(sb.ToString());
        }
    }
}
ما در این helper سفارشی از عبارت‌های لامبدا استفاده می‌کنیم و با استفاده از این عبارات، فیلد مورد نظر مدل خود را به helper معرفی می‌کنیم. آرگومان htmlAttributes در متد helper نیز برای دریافت خصوصیات اضافی helper است؛ خصوصیاتی مانند class، id, style و غیره.
با استفاده از کلاس TagBuilder تگ مورد نظر خود را ایجاد می‌کنیم. در اینجا من تگ span را ایجاد کرده‌ام که شما می‌توانید هر تگ دلخواه دیگری را نیز ایجاد کنید. اولین مرحله، استخراج اطلاعات موردنیاز از metadata مدل است که در خط زیر با پردازش عبارت لامبدا اینکار صورت می‌گیرد:
var metadata = ModelMetadata.FromLambdaExpression<TModel, TProperty>(expression, htmlHelper.ViewData);
سپس نام فیلد مورد نظر را از مدل استخراج می‌کنیم:
var name = ExpressionHelper.GetExpressionText(expression);
var fullName = htmlHelper.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(name);
کدهای فوق نام فیلد جاری (در اینجا Email) را از MetaData برای ما استخراج می‌کند. متغیر value برای نگهداری مقدار این فیلد از مدل است. مرحله بعد استخراج مقدار فیلد و انتساب آن به متغیر value است.
در سناریوی من کاربر می‌تواند زمینه‌ی فعالیت خود را انتخاب کند که به صورت IdentityRole پیاده سازی شده است. من در اینجا چک می‌کنیم که اگر نوع داده‌ای این فیلد List<IdentityProvider.IdentityRole> بود زمینه فعالیت کاربر را از طریق "،" از هم جدا کرده و به صورت یک رشته تبدیل می‌کنم. در غیر اینصورت همان مقدار عادی فیلد را بکار می‌گیرم.
if (metadata.Model != null && metadata.Model.GetType() == typeof(List<IdentityProvider.IdentityRole>))
            {
                var modelList = (List<IdentityProvider.IdentityRole>)metadata.Model;
                value = String.Join("، ", modelList.Select(r => r.Name));
            }
            else
            {
                value = htmlHelper.FormatValue(metadata.Model, null);
            }
سپس خصوصیات سفارشی خود را که بصورت attribute‌های HTML هستند، در خط زیر به تگ سفارشی اعمال می‌شوند:
span.MergeAttributes<string, object>(((IDictionary<string, object>)HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes)));
مهمترین مرحله که در واقع هدف اصلی من بود، استخراج خصوصیت‌های *-data برای اعتبارسجی است که در خط زیر اینکار صورت گرفته است:
 span.MergeAttributes<string, object>(htmlHelper.GetUnobtrusiveValidationAttributes(name, metadata));
نحوه‌ی استفاده از این helper سفارشی هم خیلی ساده است:
@Html.SpanFor(m => m.Profile.Email, new { @class = "editor", data_type = "text" })
و در نهایت HTML خروجی به شکل زیر است:
<span class="editor" data-name="Email" data-type="text" data-val="true" data-val-existfiledvalidator="این رایانامه پیشتر به کار گرفته شده است." data-val-existfiledvalidator-url="/account/emailexist" data-val-regex="نشانی رایانامه پذیرفتنی نمی‌باشد." data-val-regex-pattern="\w+([-+.']\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*" data-val-required="رایانامه خود را وارد کنید.">alireza_s_84@yahoo.com</span>
دیدن شکل‌های زیر خالی از لطف نیستند:

و پس از ویرایش:


البته برای درک بهتر این موضوع سعی خواهم کرد تا با یک مثال عملی کامل، نحوه‌ی پیاده سازی را در همینجا قرار دهم.
مطالب
چگونه کدها را مستند سازی کنیم؟
یکی از مهمترین مسائل، به خصوص در کارهای تیمی یا پروژه‌های اشتراکی، قرار دادن کامنت‌ها یا اصطلاحا مستند نویسی است که بسیاری از برنامه نویسان با اینکه نظریه آن‌را به شدت قبول دارند، ولی از انجام آن سرباز می‌زنند که به دو عامل تنبلی و عدم دانش نحوه‌ی مستند نویسی بر می‌گردد. در این مقاله قصد داریم به سوالات زیر پاسخ دهیم:
  • چرا به کامنت گذاری یا مستند نویسی نیاز داریم؟
  • چگونه کامنت بنویسیم؟
  • انواع کامنت‌ها چیست؟
  • چه کامنت‌هایی اشتباه هستند؟

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


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


سطوح کامنت نویسی بر سه نوع هستند:

Documentary Comments:
این مستند سازی در سطح یک سند مثل فایل یا به خصوص یک پروژه رخ می‌دهد که شامل اطلاعات و تاریخچه‌ی آن سند است که این اطلاعات به شرح زیر هستند:

 File Name   نام سند
 File Number/Version Number   شماره نسخه آن سند
 Creation Date   تاریخ ایجاد آن
 Last Modification Date
 تاریخ آخرین تغییر سند
 Author's Name
 سازنده‌ی سند
 Copyright Notice
 اطلاعاتی در مورد کپی رایت سند
 Purpose Of Program
 هدف کاری برنامه. یک خلاصه از آن چه برنامه انجام می‌دهد.
 Change History
 لیستی از تغییرات مهمی که در جریان ایجاد آن رخ داده است.
 Dependencies  وابستگی‌های سند. بیشتر در سطح پروژه معنا پیدا می‌کند؛ مانند نمونه‌ی آن برای سایت جاری که به صورت عمومی منتشر شده است.
 Special Hardware Requirements
 سخت افزار مورد نیاز برای اجرای برنامه. حتی قسمتی می‌تواند شامل نیازمندی‌های نرم افزاری هم باشد.
نمونه ای از این مستند سازی برای برنامه ای که به زبان پاسکال نوشته شده است:
PCMBOAT5.PAS*************************************************************
**
File: PCMBOAT5.PAS
Author: B. Spuida
Date: 1.5.1999
Revision: 
1.1 
PCM-DAS08 and -16S/12 are supported.
Sorting routine inserted.
Set-files are read in and card as well as
amplification factor are parsed.

1.1.1
Standard deviation is calculated.

1.1.2
Median is output. Modal value is output.

1.1.4
Sign in Set-file is evaluated.
Individual values are no longer output.
(For tests with raw data use PCMRAW.EXE)

To do:
outliers routine to be revised.
Statistics routines need reworking.
Existing Datafile is backed up.

Purpose: 
Used for measurement of profiles using the
Water-SP-probes using the amplifier andthe PCM-DAS08-card, values are acquired
with n = 3000. Measurements are taken in 1
second intervals. The values are sorted using
Quicksort and are stacked "raw" as well as after
dismissing the highest and lowest 200 values as
'outliers'.


Requirements:
The Card must have an A/D-converter.
Amplifier and probes must be connected.
Analog Signal must be present.
CB.CFG must be in the directory specified by the
env-Variable "CBDIREC" or in present directory.

در بالا، خصوصیت کپی رایت حذف شده است. دلیل این امر این است که این برنامه برای استفاده در سطح داخلی یک شرکت استفاده می‌شود.


Functional Comments: کامنت نویسی در سطح کاربردی به این معنی نیست که شما اتفاقاتی را که در یک متد یا کلاس یا هر بخشی روی می‌دهد، خط به خط توضیح دهید؛ بلکه چرخه‌ی کاری آن شی را هم توضیح بدهید کفایت می‌کند. این مورد می‌تواند شامل این موارد باشد:
  • توضیحی در مورد باگ‌های این قسمت
  • یادداشت گذاری برای دیگر افراد تیم
  • احتمالاتی که برای بهبود ویژگی‌ها و کارایی کد وجود دارد.


Explanatory Comment: کامنت گذاری توصیفی در سطح کدنویسی رخ می‌دهد و شامل توضیح در مورد کارکرد یک شیء و توضیح کدهای شیء مربوطه می‌گردد. برای قرار دادن کامنت الزامی نیست که کدها را خط به خط توضیح دهید یا اینکه خطوط ساده را هم تشریح کنید؛ بلکه کامنت شما همینقدر که بتواند نحوه‌ی کارکرد هر چند خط کد مرتبط به هم را هم توضیح دهد، کافی است. این توضیح‌ها بیشتر شامل موارد زیر می‌شوند:

  • کدهای آغازین
  • کدهای خروجی
  • توضیح کوتاه از آنچه که این شیء ، متد یا ... انجام می‌دهد. 
  • حلقه‌های طولانی یا پیچیده
  • کدهای منطقی عجیب و پیچیده
  • Regular Expression

کدهای آغازین شروع خوبی برای تمرین خواهند بود. به عنوان نمونه اینکه توضیحی در مورد ورودی و خروجی یک متد بدهید که آرگومان‌های ورودی چه چیزهایی هستند و چه کاربری داردند و در آغاز برنامه، برنامه چگونه آماده سازی و اجرا می‌شود. مقادیر پیش فرض چه چیزهایی هستند و پروژه چگونه تنظیم و مقداردهی می‌شود.

کدهای خروجی هم به همین منوال است. خروجی‌های نرمال و غیرنرمال آن چیست؟ کدهای خطایی که ممکن است برگرداند و ... که باید به درستی توضیح داده شوند.

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

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

سیستم کامنت گذاری
هر زبانی از یک سیستم خاص برای کامنت گذاری استفاده می‌کند. به عنوان مثال پرل از سیستم (POD (Plain Old Documentation استفاده می‌کند یا برای Java سیستم JavaDoc یا برای PHP از سیستم PHPDoc  (+ ) که پیاده سازی از JavaDoc می‌باشد استفاده می‌کنند. این سیستم برای سی شارپ استفاده از قالب XML است. کد زیر نمونه‌ای از استفاده از این سیستم است:
// XMLsample.cs
// compile with: /doc:XMLsample.xml
using System;
/// <summary>
/// Class level summary documentation goes here.</summary>
/// <remarks>
/// Longer comments can be associated with a type or member
/// through the remarks tag</remarks>
public class SomeClass
{
    /// <summary>
    /// Store for the name property</summary>
    private string myName = null;

    /// <summary>
    /// The class constructor. </summary>
    public SomeClass()
    {
        // TODO: Add Constructor Logic here
    }

    /// <summary>
    /// Name property </summary>
    /// <value>
    /// A value tag is used to describe the property value</value>
    public string Name
    {
        get
        {
            if (myName == null)
            {
                throw new Exception("Name is null");
            }
            return myName;
        }
    }

    /// <summary>
    /// Description for SomeMethod.</summary>
    /// <param name="s"> Parameter description for s goes here</param>
    /// <seealso cref="String">
    /// You can use the cref attribute on any tag to reference a type or member
    /// and the compiler will check that the reference exists. </seealso>
    public void SomeMethod(string s)
    {
    }
}

دستورات سیستم کامنت گذاری سی شارپ

در سایت جاری، دو مقاله زیر اطلاعاتی در رابطه با نحوه‌ی کامنت گذاری ارئه داده‌اند.
- در مقاله «زیباتر کد بنویسیم» چند مورد آن به این موضوع اختصاص دارد.
- مقاله «وادار کردن خود به کامنت نوشتن» گزینه‌ی کامنت گذاری اجباری در ویژوال استودیو را معرفی می‌کند.
مطالب دوره‌ها
استفاده از StructureMap به عنوان یک IoC Container
StructureMap یکی از IoC containerهای بسیار غنی سورس باز نوشته شده برای دات نت فریم ورک است. امکان تنظیمات آن توسط کدنویسی و یا همان Fluent interfaces، به کمک فایل‌های کانفیگ XML و همچنین استفاده از ویژگی‌ها یا Attributes نیز میسر است. امکانات جانبی دیگری را نیز مانند یکی شدن با فریم ورک‌های Dynamic Proxy برای ساده سازی فرآیندهای برنامه نویسی جنبه‌گرا یا AOP، دارا است. در ادامه قصد داریم با نحوه استفاده از این فریم ورک IoC بیشتر آشنا شویم.


دریافت StructureMap

برای دریافت آن نیاز است دستور پاورشل ذیل را در کنسول نیوگت ویژوال استودیو فراخوانی کنید:
 PM> Install-Package structuremap
البته باید دقت داشت که برای استفاده از StructureMap نیاز است به خواص پروژه مراجعه و سپس حالت Client profile را به Full profile تغییر داد تا برنامه قابل کامپایل باشد (در برنامه‌های دسکتاپ البته)؛ از این جهت که StructureMap ارجاعی را به اسمبلی استاندارد System.Web دارد.


آشنایی با ساختار برنامه

ابتدا یک برنامه کنسول را آغاز کرده و سپس یک Class library جدید را به نام Services نیز به آن اضافه کنید. در ادامه کلاس‌ها و اینترفیس‌های زیر را به Class library ایجاد شده، اضافه کنید. سپس از طریق نیوگت به روشی که گفته شد، StructureMap را به پروژه اصلی (ونه پروژه Class library) اضافه نمائید و Target framework آن‌را نیز در حالت Full قرار دهید بجای حالت Client profile.
namespace DI03.Services
{
    public interface IUsersService
    {
        string GetUserEmail(int userId);
    }
}


namespace DI03.Services
{
    public interface IEmailsService
    {
        void SendEmailToUser(int userId, string subject, string body);
    }
}

using System;

namespace DI03.Services
{
    public class UsersService : IUsersService
    {
        public UsersService()
        {
            //هدف صرفا نمایش وهله سازی خودکار این وابستگی است
            Console.WriteLine("UsersService ctor.");
        }

        public string GetUserEmail(int userId)
        {
            //برای مثال دریافت از بانک اطلاعاتی و بازگشت یک نمونه جهت آزمایش برنامه
            return "name@site.com";
        }
    }
}

using System;

namespace DI03.Services
{
    public class EmailsService: IEmailsService
    {
        private readonly IUsersService _usersService;
        public EmailsService(IUsersService usersService)
        {
            Console.WriteLine("EmailsService ctor.");
            _usersService = usersService;
        }

        public void SendEmailToUser(int userId, string subject, string body)
        {
            var email = _usersService.GetUserEmail(userId);
            Console.WriteLine("SendEmailTo({0})", email);
        }
    }
}
در لایه سرویس برنامه، یک سرویس کاربران و یک سرویس ارسال ایمیل تدارک دیده شده‌اند.
سرویس کاربران بر اساس آی دی یک کاربر، برای مثال از بانک اطلاعاتی ایمیل او را بازگشت می‌دهد. سرویس ارسال ایمیل، نیاز به ایمیل کاربری برای ارسال ایمیلی به او دارد. بنابراین وابستگی مورد نیاز خود را از طریق تزریق وابستگی‌ها در سازنده کلاس و وهله سازی شده در خارج از آن (معکوس سازی کنترل)، دریافت می‌کند.
در سازنده‌های هر دو کلاس سرویس نیز از Console.WriteLine استفاده شده‌است تا زمان وهله سازی خودکار آن‌ها را بتوان بهتر مشاهده کرد.
نکته مهمی که در اینجا وجود دارد، بی‌خبری لایه سرویس از وجود IoC Container مورد استفاده است.


استفاده از لایه سرویس و تزریق وابستگی‌ها به کمک  StructureMap

using DI03.Services;
using StructureMap;

namespace DI03
{
    class Program
    {
        static void Main(string[] args)
        {
            // تنظیمات اولیه برنامه که فقط یکبار باید در طول عمر برنامه انجام شود
            ObjectFactory.Initialize(x =>
            {
                x.For<IEmailsService>().Use<EmailsService>();
                x.For<IUsersService>().Use<UsersService>();
            });

            //نمونه‌ای از نحوه استفاده از تزریق وابستگی‌های خودکار
            var emailsService = ObjectFactory.GetInstance<IEmailsService>();
            emailsService.SendEmailToUser(userId: 1, subject: "Test", body: "Hello!");
        }
    }
}
کدهای برنامه را به نحو فوق تغییر دهید. در ابتدا نحوه سیم کشی‌های آغازین برنامه را مشاهده می‌کنید. برای مثال کدهای ObjectFactory.Initialize باید در متدهای آغازین یک پروژه قرار گیرند و تنها یکبار هم نیاز است فراخوانی شوند.
به این ترتیب IoC Container ما زمانیکه قرار است object graph مربوط به IEmailsService درخواستی را تشکیل دهد، خواهد دانست ابتدا به سازنده‌ی کلاس EmailsService می‌رسد. در اینجا برای وهله سازی این کلاس به صورت خودکار، باید وابستگی‌های آن‌را نیز وهله سازی کند. بنابراین بر اساس تنظیمات آغازین برنامه می‌داند که باید از کلاس UsersService برای تزریق خودکار وابستگی‌ها در سازنده کلاس ارسال ایمیل استفاده نماید.
در این حالت اگر برنامه را اجرا کنیم، به خروجی زیر خواهیم رسید:
UsersService ctor.
EmailsService ctor.
SendEmailTo(name@site.com)
بنابراین در اینجا با مفهوم Object graph نیز آشنا شدیم. فقط کافی است وابستگی‌ها را در سازنده‌های کلاس‌ها تعریف کرده و سیم کشی‌های آغازین صحیحی را نیز در ابتدای برنامه معرفی نمائیم. کار وهله سازی چندین سطح با تمام وابستگی‌های متناظر با آن‌ها در اینجا به صورت خودکار انجام خواهد شد و نهایتا یک شیء قابل استفاده بازگشت داده می‌شود.
ابتدایی‌ترین مزیت استفاده از تزریق وابستگی‌ها امکان تعویض آن‌ها است؛ خصوصا در حین Unit testing. اگر کلاسی برای مثال قرار است با شبکه کار کند، می‌توان پیاده سازی آن‌را با یک نمونه اصطلاحا Fake جایگزین کرد و در این نمونه تنها نتیجه‌ی کار را بازگشت داد. کلاس‌های لایه سرویس ما تنها با اینترفیس‌ها کار می‌کنند. این تنظیمات قابل تغییر اولیه IoC container مورد استفاده هستند که مشخص می‌کنند چه کلاس‌هایی باید در سازنده‌های کلاس‌ها تزریق شوند.


تعیین طول عمر اشیاء در StructureMap

برای اینکه بتوان طول عمر اشیاء را بهتر توضیح داد، کلاس سرویس کاربران را به نحو زیر تغییر دهید:
using System;

namespace DI03.Services
{
    public class UsersService : IUsersService
    {
        private int _i;
        public UsersService()
        {
            //هدف صرفا نمایش وهله سازی خودکار این وابستگی است
            Console.WriteLine("UsersService ctor.");
        }

        public string GetUserEmail(int userId)
        {
            _i++;
            Console.WriteLine("i:{0}", _i);
            //برای مثال دریافت از بانک اطلاعاتی و بازگشت یک نمونه جهت آزمایش برنامه
            return "name@site.com";
        }
    }
}
به عبارتی می‌خواهیم بدانیم این کلاس چه زمانی وهله سازی مجدد می‌شود. آیا در حالت فراخوانی ذیل،
 //نمونه‌ای از نحوه استفاده از تزریق وابستگی‌های خودکار
var emailsService1 = ObjectFactory.GetInstance<IEmailsService>();
emailsService1.SendEmailToUser(userId: 1, subject: "Test1", body: "Hello!");

var emailsService2 = ObjectFactory.GetInstance<IEmailsService>();
emailsService2.SendEmailToUser(userId: 1, subject: "Test2", body: "Hello!");
ما شاهد چاپ عدد 2 خواهیم بود یا عدد یک:
 UsersService ctor.
EmailsService ctor.
i:1
SendEmailTo(name@site.com)
UsersService ctor.
EmailsService ctor.
i:1
SendEmailTo(name@site.com)
همانطور که ملاحظه می‌کنید، به ازای هربار فراخوانی ObjectFactory.GetInstance، یک وهله جدید ایجاد شده است. بنابراین مقدار i در هر دو بار مساوی عدد یک است.
اگر به هر دلیلی نیاز بود تا این رویه تغییر کند، می‌توان بر روی طول عمر اشیاء تشکیل شده نیز تاثیر گذار بود. برای مثال تنظیمات آغازین برنامه را به نحو ذیل تغییر دهید:
// تنظیمات اولیه برنامه که فقط یکبار باید در طول عمر برنامه انجام شود
ObjectFactory.Initialize(x =>
{
   x.For<IEmailsService>().Use<EmailsService>();
   x.For<IUsersService>().Singleton().Use<UsersService>();
});
اینبار اگر برنامه را اجرا کنیم، به خروجی ذیل خواهیم رسید:
 UsersService ctor.
EmailsService ctor.
i:1
SendEmailTo(name@site.com)
EmailsService ctor.
i:2
SendEmailTo(name@site.com)
بله. با Singleton معرفی کردن تنظیمات UsersService، تنها یک وهله از این کلاس ایجاد خواهد شد و نهایتا در فراخوانی دوم ObjectFactory.GetInstance، شاهد عدد i مساوی 2 خواهیم بود (چون از یک وهله استفاده شده است).

حالت‌های دیگر تعیین طول عمر مطابق متدهای زیر هستند:
 Singleton()
HttpContextScoped()
HybridHttpOrThreadLocalScoped()
با انتخاب حالت HttpContext، به ازای هر HttpContext ایجاد شده، کلاس معرفی شده یکبار وهله سازی می‌گردد.
در حالت ThreadLocal، به ازای هر Thread، وهله‌ای متفاوت در اختیار مصرف کننده قرار می‌گیرد.
حالت Hybrid ترکیبی است از حالت‌های HttpContext و ThreadLocal. اگر برنامه وب بود، از HttpContext استفاده خواهد کرد در غیراینصورت به ThreadLocal سوئیچ می‌کند.

شاید بپرسید که کاربرد مثلا HttpContextScoped در کجا است؟
در یک برنامه وب نیاز است تا یک وهله از DbContext (مثلا Entity framework) را در اختیار کلاس‌های مختلف لایه سرویس قرار داد. به این ترتیب چون هربار new Context صورت نمی‌گیرد، هربار هم اتصال جداگانه‌ای به بانک اطلاعاتی باز نخواهد شد. نتیجه آن رسیدن به یک برنامه سریع، با سربار کم و همچنین کار کردن در یک تراکنش واحد است. چون هربار فراخوانی new Context به معنای ایجاد یک تراکنش جدید است.
همچنین در این برنامه وب قصد نداریم از حالت طول عمر Singleton استفاده کنیم، چون در این حالت یک وهله از Context در اختیار تمام کاربران سایت قرار خواهد گرفت (و DbContext به صورت Thread safe طراحی نشده است). نیاز است به ازای هر کاربر و به ازای طول عمر هر درخواست، تنها یکبار این وهله سازی صورت گیرد. بنابراین در این حالت استفاده از HttpContextScoped توصیه می‌شود. به این ترتیب در طول عمر کوتاه Object graph‌های تشکیل شده، فقط یک وهله از DbContext ایجاد و استفاده خواهد شد که بسیار مقرون به صرفه است.
مزیت دیگر مشخص سازی طول عمر به نحو HttpContextScoped، امکان Dispose خودکار آن به صورت زیر است:
protected void Application_EndRequest(object sender, EventArgs e)  
{  
  ObjectFactory.ReleaseAndDisposeAllHttpScopedObjects();  
}

تنظیمات خودکار اولیه در StructureMap

اگر نام اینترفیس‌های شما فقط یک I در ابتدا بیشتر از نام کلاس‌های متناظر با آن‌ها دارد، مثلا مانند ITest و کلاس Test هستند؛ فقط کافی است از قراردادهای پیش فرض StructureMap برای اسکن یک یا چند اسمبلی استفاده کنیم:
 // تنظیمات اولیه برنامه که فقط یکبار باید در طول عمر برنامه انجام شود
ObjectFactory.Initialize(x =>
{
   //x.For<IEmailsService>().Use<EmailsService>();
   //x.For<IUsersService>().Singleton().Use<UsersService>();  
   x.Scan(scan =>
   {
       scan.AssemblyContainingType<IEmailsService>();
       scan.WithDefaultConventions();
   });  
});
در این حالت دیگر نیازی نیست به ازای اینترفیس‌های مختلف و کلاس‌های مرتبط با آن‌ها، تنظیمات اضافه‌تری را تدارک دید. کار یافتن و برقراری اتصالات لازم در اینجا خودکار خواهد بود.


دریافت مثال قسمت جاری
DI03.zip

به روز شده‌ی این مثال‌ها را بر اساس آخرین تغییرات وابستگی‌های آن‌ها از مخزن کد ذیل می‌توانید دریافت کنید:
Dependency-Injection-Samples
 
مطالب
برنامه‌ی ++ Notepad و syntax highlighting فایل‌های دات نتی

برنامه‌ی ++ Notepad به عنوان یک ادیتور رایگان، سبک و بسیار سریع به همراه ارائه‌ی syntax highlighting عالی، یکی از ویرایشگرهای محبوب برنامه نویس‌ها (در هر گرایش و صنفی) به شمار می‌رود. اکثر فایل‌های مرتبط با دات نت فریم ورک هم منهای فایل‌های سورس‌ها با پسوند cs و vb و امثال آن، دقیقا از نوع XML هستند؛ برای مثال: xaml, config, edmx, csproj و بسیاری موارد دیگر. این پسوندها به صورت پیش فرض در برنامه‌ی ++ Notepad جهت اعمال syntax highlighting تعریف نشده‌اند و برای اضافه کردن آن‌ها کافی است به صورت زیر عمل کرد:
به منوی Settings گزینه‌ی Style configurator مراجعه کنید. سپس در قالبی که آن‌را انتخاب کرده‌اید، زبان XML را انتخاب کرده و فیلد User ext را تکمیل کنید (شکل زیر):



مطالب
کامپوننت‌های متداول طرحبندی صفحات در بوت استرپ 4
بوت استرپ، به همراه کامپوننت‌هایی برای پیاده سازی اعمال متداول طرحبندی صفحات است؛ مانند jumbotron ،media ،table و card.


کامپوننت jumbotron

از Jumbotron برای نمایش متنی مشخص در بالای یک صفحه، استفاده می‌شود. دو روش استفاده‌ی از آن در بوت استرپ 4 وجود دارند:
- داخل container:
    <div class="container">
        <header class="jumbotron mt-4">
            <div class="display-2 mb-4">Our Mission</div>
            <p class="lead">Wisdom Pet Medicine strives to blend the best in
                traditional and alternative medicine in the diagnosis and
                treatment of companion animals including dogs, cats, birds,
                reptiles, rodents, and fish. We apply the wisdom garnered in
                the centuries old tradition of veterinary medicine, to find the
                safest treatments and cures.</p>
        </header>
با این خروجی:


در اینجا با اعمال کلاس jumbotron، متن header، داخل یک قاب با گوشه‌های گرد قرار می‌گیرد و مشخص‌تر نمایش داده خواهد شد. همچنین با mt-4، فاصله‌ای را بین آن و بالای صفحه ایجاد کرده‌ایم.

- خارج از container:
    <header class="jumbotron jumbotron-fluid">
        <div class="container">
            <div class="display-2 mb-4">Our Mission</div>
            <p class="lead">Wisdom Pet Medicine strives to blend the best in
                traditional and alternative medicine in the diagnosis and
                treatment of companion animals including dogs, cats, birds,
                reptiles, rodents, and fish. We apply the wisdom garnered in
                the centuries old tradition of veterinary medicine, to find the
                safest treatments and cures.</p>
        </div>
    </header>
با این خروجی:


اگر می‌خواهیم این قاب، تمام عرض صفحه را پر کند و همچمنین لبه‌های گرد آن نیز حذف شوند، می‌توان از کلاس jumbotron-fluid استفاده کرد و آن‌را خارج از container قرار داد. سپس برای اینکه متن داخل آن با container زیر آن تراز شود، می‌توان یک container را در اینجا داخل jumbotron تعریف کرد.


کنترل ظاهر جداول، در بوت استرپ 4

بوت استرپ 4 به همراه تعدادی کلاس ویژه است که برای بهبود ظاهر المان استاندارد جدول، ارائه شده‌اند. آن‌ها را در طی مثال‌هایی بررسی خواهیم کرد.


برای رسیدن به چنین تصویری، تغییرات زیر را بر روی یک جدول استاندارد HTML اعمال کرده‌ایم:
<table class="table table-striped table-hover table-bordered table-responsive">
  <thead class="thead-light">
- کلاس table، کلاس پایه اعمال شیوه‌نامه‌های بوت استرپ 4 به المان جدول است که سبب خواهد شد آیتم‌های آن با فاصله‌ی بهتری نسبت به یکدیگر ظاهر شوند. با استفاده از کلاس table-dark می‌توان یک قالب مشکی را به جدول اعمال کرد.
- کلاس table-striped سبب می‌شود تا ردیف‌ها، یک در میان با رنگی متمایز نمایش داده شوند.
- با افزودن table-hover، رنگ ردیف‌های جدول با عبور اشاره‌گر ماوس از روی آن‌ها تغییر می‌کند.
- کلاس table-bordered کار نمایش قاب جدول را انجام می‌دهد.
- کلاس table-responsive سبب می‌شود تا در اندازه‌های کوچک صفحه، یک اسکرول بار افقی برای نمایش آیتم‌های جدول ظاهر شود و یا می‌توان از کلاس table-sm نیز استفاده کرد تا padding تعریف شده‌ی در جدول، کاهش یابند. این کلاس، قابلیت پذیرش break-pointها را نیز دارد؛ مانند table-responsive-md.
- کلاس‌های thead-light و یا thead-dark که بر روی تگ thead قرار می‌گیرند، رنگ پس زمینه‌ی هدر جدول را مشخص می‌کنند.
- برای تغییر رنگ پس زمینه و متن یک ردیف می‌توان از کلاس‌های bg-color و text-color استفاده کرد:
<tr class="bg-danger text-light">
- برای تغییر رنگ سلول‌های جدول از کلاس‌های table-color استفاده می‌کنیم:
<td class="table-success">$100.00 </td>
فرمول‌های رنگ‌های قابل اعمال به ردیف‌ها، سلول‌ها و متون جداول بوت استرپ 4 را در تصویر ذیل مشاهده می‌کنید:



کامپوننت جدید card در بوت استرپ 4

پنل‌های بوت استرپ 3 حذف و بجای آن کامپوننت جدیدی به نام card در نگارش 4 آن ارائه شده‌است که با افزودن کلاس آن به یک div، بلافاصله قابی با گوشه‌های گرد به آن اضافه می‌شود.


        <section class="card mb-5" id="drwinthrop">
            <div class="card-body">
                <img class="card-img img-fluid" src="images/testimonial-mcphersons.jpg"
                    alt="Doctor Winthrop Photo">
                <h2 class="card-title">Dr. Stanley Winthrop</h2>
                <h5 class="card-subtitle">Behaviorist</h5>
                <p class="card-text">Dr. Winthrop is the guardian of Missy, a
                    three-year old Llaso mix, who he adopted at the shelter.
                    Dr. Winthrop is passionate about spay and neuter and pet
                    adoption, and works tireless hours outside the clinic,
                    performing free spay and neuter surgeries for the shelter.</p>
                <a class="card-link" href="#">About Me</a>
                <a class="card-link" href="#">My Pets</a>
                <a class="card-link" href="#">Client Slideshow</a>
            </div>
        </section>
- برای اینکه عناصر داخل card با فاصله‌ی مناسبی از لبه‌های آن قرار گیرند و همچنین شیوه‌نامه‌های قسمت‌های مختلف آن به درستی اعمال شوند، نیاز است محتوای section ای که با کلاس card مشخص شده (تعیین container)، داخل یک div با کلاس card-body قرار گیرد. در اینجا امکان تعریف card-header و card-footer نیز وجود دارد.
- سپس یک card می‌تواند دارای تصویری واکنشگرا باشد که عرض card را پوشش می‌دهد. این تصویر با کلاس card-img مشخص می‌شود.
در اینجا امکان تعریف card-img-top و card-img-bottom نیز وجود دارند. این موارد تصویر card را در بالا و یا پایین آن، بدون padding، نمایش می‌دهند. اگر می‌خواهید متنی را بر روی این تصویر نمایش دهید، از کلاس card-img-overlay استفاده کنید. در این حالت‌ها باید تصویر را خارج از card-body قرار دهید.
- عنوان و زیرعنوان یک card، توسط کلاس‌های card-title و card-subtitle تعیین می‌شوند.
- متن داخل آن‌را با کلاس card-text مشخص می‌کنیم.
- لینک‌های ذیل آن نیز توسط کلاس card-link در طی یک ردیف نمایش داده می‌شوند.

امکان تعیین رنگ پس زمینه، حاشیه و متن یک card نیز وجود دارند:
<section class="card mb-5 bg-primary text-light border-warning" id="drchase">
با این خروجی:


و فرمول کلی رنگ‌های آن نیز به صورت زیر می‌باشد:


می‌توان برای یک card، هدر و فوتر نیز تعریف کرد:
        <section class="card mb-5" id="drsanders">
            <div class="card-header">
                <h2 class="card-title">Dr. Kenneth Sanders</h2>
                <h5 class="card-subtitle">Nutritionist</h5>
            </div>
            <div class="card-body">
                <img class="card-img img-fluid" src="images/testimonial-mcphersons.jpg"
                    alt="Doctor Sanders Photo">
                <p class="card-text">Leroy walked into Dr. Sanders front door
                    when she was moving into a new house. After searching for
                    weeks for Leroy's guardians, she decided to make Leroy a
                    part of her pet family, and now has three cats.</p>
            </div>
            <div class="card-footer">
                <a class="card-link" href="#">About Me</a>
                <a class="card-link" href="#">My Pets</a>
                <a class="card-link" href="#">Client Slideshow</a>
            </div>
        </section>
در اینجا همان card قبلی را مشاهده می‌کنید که عناوین آن به card-header و لینک‌های ذیل آن به card-footer منتقل شده‌اند:


برای تعریف یک list-group در داخل یک card، به صورت زیر عمل می‌کنیم:
        <section class="card mb-5" id="drwong">
            <div class="card-body">
                <img class="card-img img-fluid" src="images/testimonial-mcphersons.jpg"
                    alt="Doctor Wong Photo">
                <h2 class="card-title">Dr. Olivia Wong</h2>
                <h5 class="card-subtitle">Preventive Care</h5>
                <p class="card-text">Dr. Wong is a cancer survivor who was
                    fortunate enough to get to spend time with a therapy dog
                    during her recovery. She became passionate about therapy
                    animals, and has started her own foundation to train and
                    provide education to patients in recovery. Now she gets her
                    own dose of daily therapy from her husky, Lilla.</p>
            </div>
            <div class="list-group list-group-flush">
                <a class="list-group-item" href="#">About Me</a>
                <a class="list-group-item" href="#">My Pets</a>
                <a class="list-group-item" href="#">
                    Client Slideshow
                </a>
            </div>
        </section>
ابتدا list-group را به خارج از card-body منتقل می‌کنیم. سپس برای حذف حاشیه‌ی آن و همچنین گوشه‌های گرد آن، جهت یکی شدن با قاب card، کلاس list-group-flush را به آن اضافه می‌کنیم:



تعیین نحوه‌ی چیدمان cards در بوت استرپ 4

اگر چندین card در یک صفحه تعریف شده‌اند، برای تعیین نحوه‌ی قرارگیری آن‌ها در کنار یکدیگر می‌توان یا از سیستم طرحبندی متداول بوت استرپ استفاده کرده و یا امکان تعریف گروهی از آن‌ها نیز وجود دارد. برای اینکار کافی است یک div با کلاس card-group را تعریف و سپس تمام cards را داخل آن قرار دهیم:
    <div class="container">
        <div class="card-group">
که سبب خواهد شد تمام cards در کنار یکدیگر بدون فاصله‌ای نمایش داده شوند. اگر بجای آن از کلاس card-deck استفاده شود، فاصله‌ای بین cards قرار می‌گیرد.


اگر از کلاس card-columns استفاده کنیم، تمام cards را به صورت خودکار در ستون‌ها و ردیف‌ها، قرار می‌دهد که بعضی از آن‌ها بلندتر و بعضی دیگر کوتاه‌تر هستند (نوعی نمایش کاشی‌کاری شده‌است):


ولی در کل اگر نیاز به کنترل بیشتری دارید، از همان روش متداول تعریف ردیف‌ها و ستون‌های سیستم طرحبندی بوت استرپ استفاده کنید.


المان media در بوت استرپ 4

برای نمایش متداول متن و تصویر که قرار است تصویر، در یک ستون و متن، در ستونی دیگر باشد، بوت استرپ 4 به همراه کلاس media است که بر اساس Flexbox بازنویسی شده‌است.
<body>
    <div class="container">
        <section class="media mb-5" id="drwinthrop">
            <img class="d-flex align-self-center img-fluid rounded mr-3" style="width:30%"
                src="images/testimonial-mcphersons.jpg" alt="Doctor Winthrop Photo">
            <div class="media-body">
                <h2>Dr. Stanley Winthrop</h2>
                <h5>Behaviorist</h5>
                <p>Dr. Winthrop is the guardian of Missy,
                    a three-year old Llaso mix, who he adopted at the
                    shelter. Dr. Winthrop is passionate about spay and neuter
                    and pet adoption, and works tireless hours outside the
                    clinic, performing free spay and neuter surgeries for the
                    shelter.</p>
        </section>
    </div>
</body>
با این خروجی:


ابتدا توسط کلاس media یک container را تعریف می‌کنیم. سپس تصویر، یک ستون و media-body ستون دیگر را تشکیل می‌دهد.
با استفاده از d-flex، المان تصویر را به یک Flexbox container تبدیل کرده و با استفاده از کلاس align-self-center، آن‌را در میانه‌ی ستون قرار می‌دهیم. همچنین در اینجا توسط mr-3، فاصله‌ی آن‌را با متن ستون کناری تنظیم کرده‌ایم.


کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید: Bootstrap4_09.zip