نظرات مطالب
خواندنی‌های 12 اردیبهشت
سلام،
بله، در خود گوگل قسمت معرفی به آن ثبت شده.
استفاده از Google analytics تاثیر دارد.
استفاده از tag ها و واژه‌های کلیدی تاثیر دارد.
لینک دادن به سایت‌های دیگر مؤثر است.
لینک دادن سایت‌های دیگر به شما نیز بسیار مؤثر است.
کم کردن حجم صفحه با انتقال css و جاوا اسکریپت‌های آن به خارج از صفحه مؤثر است. سرچ انجین‌ها به css و js شما کاری ندارند و هر چقدر صفحه سبک‌تر باشد بهتر است برای آن‌ها.
مطالب
معرفی Selector های CSS - قسمت 2
11- S1,S2
اگر بخواهیم قالب بندی را برای چند Selector به صورت یکجا انجام دهیم، این Selector‌ها را با کاما (,) از هم جدا می‌نماییم.
<style>
    div,.content,table.main {
        color: red;
    }
</style>
<div>Text 1</div>
<p>Text 2</p>
<table class="main" border="1">
    <tr>
        <td>Cell 1</td>
        <td>Cell 2</td>
        <td>Cell 3</td>
    </tr>
    <tr>
        <td>Cell 4</td>
        <td>Cell 5</td>
        <td>Cell 6</td>
    </tr>
</table>
<span class="content">Text 3</span>
<h1>Text 4</h1>
در مثال فوق Text 1 و Text 3 و همچنین محتوای تگ table به رنگ قرمز نمایش می‌یابند.
پشتیبانی در مرورگرها:

 Selector نسخه CSS
 Yes  Yes  Yes Yes   Yes S1,S2  1
توجه:
Selector‌ها می‌توانند به صورت ترکیبی نیز استفاده شوند. به مثال زیر توجه کنید:
<style>
    .content .tag {
        color: red;
    }

    h1#index {
        color: blue;
    }

    ul.list li.even {
        color: green;
    }
</style>
<h1 id="index">Index</h1>
<h1>Header 1</h1>
<div class="content">
    Lorem ipsum dolor sit amet, <span class="tag">consectetuer</span> adipiscing elit.
    Maecenas porttitor congue massa. Fusce posuere, magna sed pulvinar ultricies,
    purus lectus malesuada libero, <span class="tag">sit</span> amet commodo magna 
    eros quis urna. Nunc viverra <span class="tag">imperdiet</span> enim. Fusce est.
</div>
<h1>Header 2</h1>
<div class="content">
    <ul class="list">
        <li>Item 1</li>
        <li class="even">Item 2</li>
        <li>Item 3</li>
        <li class="even">Item 4</li>
        <li>Item 5</li>
        <li class="even">Item 6</li>
    </ul>
</div>
در مثال فوق، تگ h1 که ویژگی id آن برابر index می‌باشد، با توجه به Selector ی که بصورت h1#index تعریف شده است، به رنگ آبی نمایش می‌یابد. تمامی تگ‌های span که عضو کلاس tag می‌باشند و در داخل تگ div ی قرار دارند که عضو کلاس content است، با توجه به Selector ی که بصورت .content .tag تعریف شده است، به رنگ قرمز نمایش می‌یابند. تمامی تگهای li که ویژگی class آنها برابر even می‌باشد، و فرزند تگ ul ی هستند که عضو کلاس list است، با توجه به Selector ی که بصورت ul.list li.even تعریف شده است، به رنگ سبز نمایش می‌یابند.

12- [attribute]

تگ‌هایی را انتخاب می‌نماید که دارای یک attribute یا ویژگی خاص باشند.
<style>
    [readonly] {
        background: green;
    }
</style>
<input type="text" value="Value 1" readonly="readonly"/>
<input type="text"/>
در مثال فوق، رنگ پس زمینه تگ input اول که دارای ویژگی readonly می‌باشد، به رنگ سبز نمایش می‌یابد.
پشتیبانی در مرورگرها:

 Selector نسخه CSS
 3.1  9.6  7.0 2.0  4.0 [attribute]    2

13- 
 [attribute=value] 
تگ هایی را انتخاب می‌نماید که دارای یک attribute یا ویژگی خاص هستند و مقدار آن ویژگی دقیقا برابر با value می‌باشد.
<style>
    [lang=fa] {
        direction:rtl;
    }
</style>
<div lang="fa">متن 1</div>
در مثال فوق، جهت نوشتاری محتوای تگ div که دارای ویژگی lang با مقدار fa می‌باشد، از راست به چپ می‌شود و در سمت راست صفحه نمایش می‌یابد.
پشتیبانی در مرورگرها:

 Selector نسخه CSS
 3.1 9.6 7.0 2.0 4.0 [attribute=value] 
 2

14-
   [attribute=value i]
تگ‌هایی را انتخاب می‌نماید که دارای یک attribute یا ویژگی خاص هستند و مقدار آن ویژگی دقیقا برابر با value می‌باشد. همچنین value، نسبت به حروف کوچک و بزرگ حساس نمی‌باشد. 
<style>
    [lang=fa] {
        direction:rtl;
    }
</style>
<div lang="FA">متن 1</div>
در مثال فوق، جهت نوشتاری محتوای تگ div که دارای ویژگی lang با مقدار FA می‌باشد، از راست به چپ می‌شود و در سمت راست صفحه نمایش می‌یابد. با اینکه در Selector مقدار fa با حروف کوچک ذکر شده است.
پشتیبانی در مرورگرها:

 Selector نسخه CSS
 No   No   No  No 
 No [attribute=value i] 
 4

15- 
 [attribute|=value] 
تگ هایی را انتخاب می‌نماید که دارای یک attribute یا ویژگی خاص هستند و مقدار آن ویژگی با یک value خاص آغاز می‌شود. کلمه آغازین مقدار حتما باید با value برابر باشد یا با dash (-) از کلمه‌ی بعدی جدا شده باشد.
<style>
    [class|=info] {
        color:red
    }
</style>
<div class="info">Text 1</div>
<div class="infobar">Text 2</div>
<div class="info bar">Text 3</div>
<div class="info-bar">Text 4</div>
<div class="btninfo">Text 5</div>
<div class="btn info">Text 6</div>
<div class="btn-info">Text 7</div>
<div class="toolinfoicon">Text 8</div>
<div class="tool info icon">Text 9</div>
<div class="tool-info-icon">Text 10</div>
در مثال فوق Text 1 و Text 4 به رنگ قرمز نمایش می‌یابند.
پشتیبانی در مرورگرها:

 Selector نسخه CSS
 3.1  9.6  7.0 2.0  4.0 [attribute|=value] 
 2

16- 
 [attribute^=value] 
تگ‌هایی را انتخاب می‌نماید که دارای یک attribute یا ویژگی خاص هستند و مقدار آن ویژگی با یک value خاص آغاز می شود.
<style>
    [class^=info] {
        color: red;
    }
</style>
<div class="info">Text 1</div>
<div class="infobar">Text 2</div>
<div class="info bar">Text 3</div>
<div class="info-bar">Text 4</div>
<div class="btninfo">Text 5</div>
<div class="btn info">Text 6</div>
<div class="btn-info">Text 7</div>
<div class="toolinfoicon">Text 8</div>
<div class="tool info icon">Text 9</div>
<div class="tool-info-icon">Text 10</div>
در مثال فوق Text 1 و Text 2 و Text 3 و Text 4 به رنگ قرمز نمایش می‌یابند. 
پشتیبانی در مرورگرها:

 Selector نسخه CSS
 3.2  9.6  7.0 3.5  4.0 [attribute^=value] 
 3

17- 
 [attribute~=value] 
تگ‌هایی را انتخاب می‌نماید که دارای یک attribute یا ویژگی خاص هستند و مقدار آن ویژگی شامل یک value خاص می‌باشد که با Space یا فاصله‌ی خالی از سایر مقادیر جدا شده است. 
<style>
    [class~=info] {
        color: red;
    }
</style>
<div class="info">Text 1</div>
<div class="infobar">Text 2</div>
<div class="info bar">Text 3</div>
<div class="info-bar">Text 4</div>
<div class="btninfo">Text 5</div>
<div class="btn info">Text 6</div>
<div class="btn-info">Text 7</div>
<div class="toolinfoicon">Text 8</div>
<div class="tool info icon">Text 9</div>
<div class="tool-info-icon">Text 10</div>
در مثال فوق Text 1 و Text 3 و Text 6 و Text 9 به رنگ قرمز نمایش می‌یابند.
پشتیبانی در مرورگرها:

 Selector نسخه CSS
 3.1  9.6  7.0 2.0  4.0 [attribute~=value] 
 2

18- 
 [attribute*=value] 
تگ‌هایی را انتخاب می‌نماید که دارای یک attribute یا ویژگی خاص هستند و مقدار آن ویژگی شامل یک value خاص می‌باشد. 
<style>
    [class*=info] {
        color: red;
    }
</style>
<div class="info">Text 1</div>
<div class="infobar">Text 2</div>
<div class="info bar">Text 3</div>
<div class="info-bar">Text 4</div>
<div class="btninfo">Text 5</div>
<div class="btn info">Text 6</div>
<div class="btn-info">Text 7</div>
<div class="toolinfoicon">Text 8</div>
<div class="tool info icon">Text 9</div>
<div class="tool-info-icon">Text 10</div>
در مثال فوق تمامی متون به رنگ قرمز نمایش می‌یابند.
پشتیبانی در مرورگرها:

 Selector نسخه CSS
 3.2  9.6  7.0 3.5  4.0 [attribute*=value] 
 3

19- 
[attribute$=value]
تگ‌هایی را انتخاب می‌نماید که دارای یک attribute یا ویژگی خاص هستند و مقدار آن ویژگی به یک value خاص ختم می‌شود. 
<style>
    [class$=info] {
        color: red;
    }
</style>
<div class="info">Text 1</div>
<div class="infobar">Text 2</div>
<div class="info bar">Text 3</div>
<div class="info-bar">Text 4</div>
<div class="btninfo">Text 5</div>
<div class="btn info">Text 6</div>
<div class="btn-info">Text 7</div>
<div class="toolinfoicon">Text 8</div>
<div class="tool info icon">Text 9</div>
<div class="tool-info-icon">Text 10</div>
در مقال فوق Text 1 و Text 5 و Text 6 و Text 7 به رنگ قرمز نمایش می‌یابند.
پشتیبانی در مرورگرها:

 Selector نسخه CSS
 3.2  9.6  7.0 3.5  4.0 [attribute$=value] 
 3   
نظرات مطالب
کار با Kendo UI DataSource
بله مشکل از نام ستون‌های متناظر بود توی بخش column و تعریف template فراموش کرده بودم اونجا رو هم باید دو تا فیلدی که دارم رو همه حروفشون کوچک باشه. ممنون از دقت نظر شما. فقط یه مساله ای که دارم این هستش که تاریخ رو من به شکل Datetime دارم میخونم داخل مدل هام ولی اینجا نشون نمیده داخل جدول.  به این شکل تعریف شده داخل viewModel :
 [DisplayName("تاریخ درج")]
 public DateTime CreateDate { get; set; }
 [DisplayName("تاریخ انتشار")]
 public DateTime PublishDate { get; set; }

حتی الان یه پراپرتی دیگه از مدل رو نگاه کردم که از نوع string هستش(blogtype) و اون هم از سمت سرور برمی گرده ولی تو جدول خالی نشون میده :
[DisplayName("نوع مطلب")]
public string BlogType { get; set; }
ولی بقیه پراپرتی‌ها درست کار میکنند.
این هم کد داخل View بنده : 
 <script type="text/javascript">

            $(function () {
                var blogDataSource = new kendo.data.DataSource({
                    transport: {
                        read: {
                            url: "@Url.Action("GetLastBlogs", "Admin")",
                            dataType: "json",
                            contentType: 'application/json; charset=utf-8',
                            type: 'GET'
                        },
                        parameterMap: function (options) {
                            return kendo.stringify(options);
                        }
                    },
                    schema: {
                        data: "data",
                        total: "total",
                        model: {
                            fields: {
                                "id": { type: "number" }, //تعیین نوع فیلد برای جستجوی پویا مهم است
                                "title": { type: "string" },
                                "content": { type: "string" },
                                "imagepath": { type: "string" },
                                "attachmentid": { type: "number" },
                                "createdate": { type: "string" },
                                "publishdate": { type: "date" },
                                "blogtype": { type: "string" },
                                "lastmodified": { type: "date" },
                                "visitcount": { type: "number" },
                                "isarchived": { type: "boolean" },
                                "ispublished": { type: "boolean" },
                                "isdeleted": { type: "boolean" },
                                "tags": { type: "string" }


                            }
                        }
                    },
                    error: function (e) {
                        alert(e.errorThrown);
                    },
                    pageSize: 10,
                    sort: { field: "id", dir: "desc" },
                    serverPaging: true,
                    serverFiltering: true,
                    serverSorting: true
                });

                $("#report-grid").kendoGrid({
                    dataSource: blogDataSource,
                    autoBind: true,
                    scrollable: false,
                    pageable: true,
                    sortable: true,
                    filterable: true,
                    reorderable: true,
                    columnMenu: true,
                    columns: [
                        { field: "id", title: "شماره", width: "130px" },
                        { field: "title", title: "عنوان مطلب" },
                        { field: "createdate", title: "تاریخ درج" },
                        { field: "publishdate", title: "تاریخ انتشار" },
                        { field: "content", title: "خلاصه مطلب" },
                        { field: "blogtype", title: "نوع" },
                        { field: "lastmodified", title: "آخرین ویرایش" },
                        { field: "visitcount", title: "تعداد بازدید" },
                        {
                            field: "isarchived", title: "آرشیو شده",
                            template: '<input type="checkbox" #= isarchived ? checked="checked" : "" # disabled="disabled" ></input>'
                        },
                        {
                            field: "ispublished", title: "منتشر شده",
                            template: '<input type="checkbox" #= ispublished ? checked="checked" : "" # disabled="disabled" ></input>'
                        }

                    ]
                });
            });
            </script>
مطالب
استفاده از EF در اپلیکیشن های N-Tier : قسمت چهارم
در قسمت قبل تشخیص تغییرات توسط Web API را بررسی کردیم. در این قسمت نگاهی به پیاده سازی Change-tracking در سمت کلاینت خواهیم داشت.


ردیابی تغییرات در سمت کلاینت توسط Web API

فرض کنید می‌خواهیم از سرویس‌های REST-based برای انجام عملیات CRUD روی یک Object graph استفاده کنیم. همچنین می‌خواهیم رویکردی در سمت کلاینت برای بروز رسانی کلاس موجودیت‌ها پیاده سازی کنیم که قابل استفاده مجدد (reusable) باشد. علاوه بر این دسترسی داده‌ها توسط مدل Code-First انجام می‌شود.

در مثال جاری یک اپلیکیشن کلاینت (برنامه کنسول) خواهیم داشت که سرویس‌های ارائه شده توسط پروژه Web API را فراخوانی می‌کند. هر پروژه در یک Solution مجزا قرار دارد، با این کار یک محیط n-Tier را شبیه سازی می‌کنیم.

مدل زیر را در نظر بگیرید.

همانطور که می‌بینید مدل مثال جاری مشتریان و شماره تماس آنها را ارائه می‌کند. می‌خواهیم مدل‌ها و کد دسترسی به داده‌ها را در یک سرویس Web API پیاده سازی کنیم تا هر کلاینتی که به HTTP دسترسی دارد بتواند از آن استفاده کند. برای ساخت سرویس مذکور مراحل زیر را دنبال کنید.

  • در ویژوال استودیو پروژه جدیدی از نوع ASP.NET Web Application بسازید و قالب پروژه را Web API انتخاب کنید. نام پروژه را به Recipe4.Service تغییر دهید.
  • کنترلر جدیدی با نام CustomerController به پروژه اضافه کنید.
  • کلاسی با نام BaseEntity ایجاد کنید و کد آن را مطابق لیست زیر تغییر دهید. تمام موجودیت‌ها از این کلاس پایه مشتق خواهند شد که خاصیتی بنام TrackingState را به آنها اضافه می‌کند. کلاینت‌ها هنگام ویرایش آبجکت موجودیت‌ها باید این فیلد را مقدار دهی کنند. همانطور که می‌بینید این خاصیت از نوع TrackingState enum مشتق می‌شود. توجه داشته باشید که این خاصیت در دیتابیس ذخیره نخواهد شد. با پیاده سازی enum وضعیت ردیابی موجودیت‌ها بدین روش، وابستگی‌های EF را برای کلاینت از بین می‌بریم. اگر قرار بود وضعیت ردیابی را مستقیما از EF به کلاینت پاس دهیم وابستگی‌های بخصوصی معرفی می‌شدند. کلاس DbContext اپلیکیشن در متد OnModelCreating به EF دستور می‌دهد که خاصیت TrackingState را به جدول موجودیت نگاشت نکند.
public abstract class BaseEntity
{
    protected BaseEntity()
    {
        TrackingState = TrackingState.Nochange;
    }

    public TrackingState TrackingState { get; set; }
}

public enum TrackingState
{
    Nochange,
    Add,
    Update,
    Remove,
}
  • کلاس‌های موجودیت Customer و PhoneNumber را ایجاد کنید و کد آنها را مطابق لیست زیر تغییر دهید.
public class Customer : BaseEntity
{
    public int CustomerId { get; set; }
    public string Name { get; set; }
    public string Company { get; set; }
    public virtual ICollection<Phone> Phones { get; set; }
}

public class Phone : BaseEntity
{
    public int PhoneId { get; set; }
    public string Number { get; set; }
    public string PhoneType { get; set; }
    public int CustomerId { get; set; }
    public virtual Customer Customer { get; set; }
}
  • با استفاده از NuGet Package Manager کتابخانه Entity Framework 6 را به پروژه اضافه کنید.
  • کلاسی با نام Recipe4Context ایجاد کنید و کد آن را مطابق لیست زیر تغییر دهید. در این کلاس از یکی از قابلیت‌های جدید EF 6 بنام "Configuring Unmapped Base Types" استفاده کرده ایم. با استفاده از این قابلیت جدید هر موجودیت را طوری پیکربندی می‌کنیم که خاصیت TrackingState را نادیده بگیرند. برای اطلاعات بیشتر درباره این قابلیت EF 6 به این لینک مراجعه کنید.
public class Recipe4Context : DbContext
{
    public Recipe4Context() : base("Recipe4ConnectionString") { }
    public DbSet<Customer> Customers { get; set; }
    public DbSet<Phone> Phones { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        // Do not persist TrackingState property to data store
        // This property is used internally to track state of
        // disconnected entities across service boundaries.
        // Leverage the Custom Code First Conventions features from Entity Framework 6.
        // Define a convention that performs a configuration for every entity
        // that derives from a base entity class.
        modelBuilder.Types<BaseEntity>().Configure(x => x.Ignore(y => y.TrackingState));
        modelBuilder.Entity<Customer>().ToTable("Customers");
        modelBuilder.Entity<Phone>().ToTable("Phones");
}
}
  • فایل Web.config پروژه را باز کنید و رشته اتصال زیر را به قسمت ConnectionStrings اضافه نمایید.
<connectionStrings>
  <add name="Recipe4ConnectionString"
    connectionString="Data Source=.;
    Initial Catalog=EFRecipes;
    Integrated Security=True;
    MultipleActiveResultSets=True"
    providerName="System.Data.SqlClient" />
</connectionStrings>
  • فایل Global.asax را باز کنید و کد زیر را به متد Application_Start اضافه نمایید. این کد بررسی Entity Framework Model Compatibility را غیرفعال می‌کند و به JSON serializer دستور می‌دهد که self-referencing loop خواص پیمایشی را نادیده بگیرد. این حلقه بدلیل رابطه bidirectional بین موجودیت‌های Customer و PhoneNumber بوجود می‌آید.
protected void Application_Start()
{
    // Disable Entity Framework Model Compatibilty
    Database.SetInitializer<Recipe1Context>(null);
    // The bidirectional navigation properties between related entities
    // create a self-referencing loop that breaks Web API's effort to
    // serialize the objects as JSON. By default, Json.NET is configured
    // to error when a reference loop is detected. To resolve problem,
    // simply configure JSON serializer to ignore self-referencing loops.
    GlobalConfiguration.Configuration.Formatters.JsonFormatter
        .SerializerSettings.ReferenceLoopHandling =
            Newtonsoft.Json.ReferenceLoopHandling.Ignore;
    ...
}
  • کلاسی با نام EntityStateFactory بسازید و کد آن را مطابق لیست زیر تغییر دهید. این کلاس مقدار خاصیت TrackingState که به کلاینت‌ها ارائه می‌شود را به مقادیر متناظر کامپوننت‌های ردیابی EF تبدیل می‌کند.
public static EntityState Set(TrackingState trackingState)
{
    switch (trackingState)
    {
        case TrackingState.Add:
            return EntityState.Added;
        case TrackingState.Update:
            return EntityState.Modified;
        case TrackingState.Remove:
            return EntityState.Deleted;
        default:
            return EntityState.Unchanged;
    }
}
  • در آخر کد کنترلر CustomerController را مطابق لیست زیر بروز رسانی کنید.
public class CustomerController : ApiController
{
    // GET api/customer
    public IEnumerable<Customer> Get()
    {
        using (var context = new Recipe4Context())
        {
            return context.Customers.Include(x => x.Phones).ToList();
        }
    }

    // GET api/customer/5
    public Customer Get(int id)
    {
        using (var context = new Recipe4Context())
        {
            return context.Customers.Include(x => x.Phones).FirstOrDefault(x => x.CustomerId == id);
        }
    }

    [ActionName("Update")]
    public HttpResponseMessage UpdateCustomer(Customer customer)
    {
        using (var context = new Recipe4Context())
        {
            // Add object graph to context setting default state of 'Added'.
            // Adding parent to context automatically attaches entire graph
            // (parent and child entities) to context and sets state to 'Added'
            // for all entities.
            context.Customers.Add(customer);
            foreach (var entry in context.ChangeTracker.Entries<BaseEntity>())
            {
                entry.State = EntityStateFactory.Set(entry.Entity.TrackingState);
                if (entry.State == EntityState.Modified)
                {
                    // For entity updates, we fetch a current copy of the entity
                    // from the database and assign the values to the orginal values
                    // property from the Entry object. OriginalValues wrap a dictionary
                    // that represents the values of the entity before applying changes.
                    // The Entity Framework change tracker will detect
                    // differences between the current and original values and mark
                    // each property and the entity as modified. Start by setting
                    // the state for the entity as 'Unchanged'.
                    entry.State = EntityState.Unchanged;
                    var databaseValues = entry.GetDatabaseValues();
                    entry.OriginalValues.SetValues(databaseValues);
                }
            }

        context.SaveChanges();
    }

    return Request.CreateResponse(HttpStatusCode.OK, customer);
}

    [HttpDelete]
    [ActionName("Cleanup")]
    public HttpResponseMessage Cleanup()
    {
        using (var context = new Recipe4Context())
        {
            context.Database.ExecuteSqlCommand("delete from phones");
            context.Database.ExecuteSqlCommand("delete from customers");
            return Request.CreateResponse(HttpStatusCode.OK);
        }
    }
}
حال اپلیکیشن کلاینت (برنامه کنسول) را می‌سازیم که از این سرویس استفاده می‌کند.

  • در ویژوال استودیو پروژه جدیدی از نوع Console Application بسازید و نام آن را به Recipe4.Client تغییر دهید.
  • فایل program.cs را باز کنید و کد آن را مطابق لیست زیر تغییر دهید.
internal class Program
{
    private HttpClient _client;
    private Customer _bush, _obama;
    private Phone _whiteHousePhone, _bushMobilePhone, _obamaMobilePhone;
    private HttpResponseMessage _response;

    private static void Main()
    {
        Task t = Run();
        t.Wait();
        Console.WriteLine("\nPress <enter> to continue...");
        Console.ReadLine();
    }

    private static async Task Run()
    {
        var program = new Program();
        program.ServiceSetup();
        // do not proceed until clean-up completes
        await program.CleanupAsync();
        program.CreateFirstCustomer();
        // do not proceed until customer is added
        await program.AddCustomerAsync();
        program.CreateSecondCustomer();
        // do not proceed until customer is added
        await program.AddSecondCustomerAsync();
        // do not proceed until customer is removed
        await program.RemoveFirstCustomerAsync();
        // do not proceed until customers are fetched
        await program.FetchCustomersAsync();
    }

    private void ServiceSetup()
    {
        // set up infrastructure for Web API call
        _client = new HttpClient { BaseAddress = new Uri("http://localhost:62799/") };
        // add Accept Header to request Web API content negotiation to return resource in JSON format
        _client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue
        ("application/json"));
    }
    private async Task CleanupAsync()
    {
        // call the cleanup method from the service
        _response = await _client.DeleteAsync("api/customer/cleanup/");
    }

    private void CreateFirstCustomer()
    {
        // create customer #1 and two phone numbers
        _bush = new Customer
        {
            Name = "George Bush",
            Company = "Ex President",
            // set tracking state to 'Add' to generate a SQL Insert statement
            TrackingState = TrackingState.Add,
        };
        _whiteHousePhone = new Phone
        {
            Number = "212 222-2222",
            PhoneType = "White House Red Phone",
            // set tracking state to 'Add' to generate a SQL Insert statement
            TrackingState = TrackingState.Add,
        };
        _bushMobilePhone = new Phone
        {
            Number = "212 333-3333",
            PhoneType = "Bush Mobile Phone",
            // set tracking state to 'Add' to generate a SQL Insert statement
            TrackingState = TrackingState.Add,
        };
        _bush.Phones.Add(_whiteHousePhone);
        _bush.Phones.Add(_bushMobilePhone);
    }

    private async Task AddCustomerAsync()
    {
        // construct call to invoke UpdateCustomer action method in Web API service
        _response = await _client.PostAsync("api/customer/updatecustomer/", _bush, new JsonMediaTypeFormatter());
        if (_response.IsSuccessStatusCode)
        {
            // capture newly created customer entity from service, which will include
            // database-generated Ids for all entities
            _bush = await _response.Content.ReadAsAsync<Customer>();
            _whiteHousePhone = _bush.Phones.FirstOrDefault(x => x.CustomerId == _bush.CustomerId);
            _bushMobilePhone = _bush.Phones.FirstOrDefault(x => x.CustomerId == _bush.CustomerId);
            Console.WriteLine("Successfully created Customer {0} and {1} Phone Numbers(s)",
            _bush.Name, _bush.Phones.Count);
            foreach (var phoneType in _bush.Phones)
            {
                Console.WriteLine("Added Phone Type: {0}", phoneType.PhoneType);
            }
        }
        else
            Console.WriteLine("{0} ({1})", (int)_response.StatusCode, _response.ReasonPhrase);
    }

    private void CreateSecondCustomer()
    {
        // create customer #2 and phone numbers
        _obama = new Customer
        {
            Name = "Barack Obama",
            Company = "President",
            // set tracking state to 'Add' to generate a SQL Insert statement
            TrackingState = TrackingState.Add,
        };
        _obamaMobilePhone = new Phone
        {
            Number = "212 444-4444",
            PhoneType = "Obama Mobile Phone",
            // set tracking state to 'Add' to generate a SQL Insert statement
            TrackingState = TrackingState.Add,
        };
        // set tracking state to 'Modifed' to generate a SQL Update statement
        _whiteHousePhone.TrackingState = TrackingState.Update;
        _obama.Phones.Add(_obamaMobilePhone);
        _obama.Phones.Add(_whiteHousePhone);
    }

    private async Task AddSecondCustomerAsync()
    {
        // construct call to invoke UpdateCustomer action method in Web API service
        _response = await _client.PostAsync("api/customer/updatecustomer/", _obama, new JsonMediaTypeFormatter());
        if (_response.IsSuccessStatusCode)
        {
            // capture newly created customer entity from service, which will include
            // database-generated Ids for all entities
            _obama = await _response.Content.ReadAsAsync<Customer>();
            _whiteHousePhone = _bush.Phones.FirstOrDefault(x => x.CustomerId == _obama.CustomerId);
            _bushMobilePhone = _bush.Phones.FirstOrDefault(x => x.CustomerId == _obama.CustomerId);
            Console.WriteLine("Successfully created Customer {0} and {1} Phone Numbers(s)",
            _obama.Name, _obama.Phones.Count);
            foreach (var phoneType in _obama.Phones)
            {
                Console.WriteLine("Added Phone Type: {0}", phoneType.PhoneType);
            }
        }
        else
            Console.WriteLine("{0} ({1})", (int)_response.StatusCode, _response.ReasonPhrase);
    }

    private async Task RemoveFirstCustomerAsync()
    {
        // remove George Bush from underlying data store.
        // first, fetch George Bush entity, demonstrating a call to the
        // get action method on the service while passing a parameter
        var query = "api/customer/" + _bush.CustomerId;
        _response = _client.GetAsync(query).Result;

        if (_response.IsSuccessStatusCode)
        {
            _bush = await _response.Content.ReadAsAsync<Customer>();
            // set tracking state to 'Remove' to generate a SQL Delete statement
            _bush.TrackingState = TrackingState.Remove;
            // must also remove bush's mobile number -- must delete child before removing parent
            foreach (var phoneType in _bush.Phones)
            {
                // set tracking state to 'Remove' to generate a SQL Delete statement
                phoneType.TrackingState = TrackingState.Remove;
            }
            // construct call to remove Bush from underlying database table
            _response = await _client.PostAsync("api/customer/updatecustomer/", _bush, new JsonMediaTypeFormatter());
            if (_response.IsSuccessStatusCode)
            {
                Console.WriteLine("Removed {0} from database", _bush.Name);
                foreach (var phoneType in _bush.Phones)
                {
                    Console.WriteLine("Remove {0} from data store", phoneType.PhoneType);
                }
            }
            else
                Console.WriteLine("{0} ({1})", (int)_response.StatusCode, _response.ReasonPhrase);
        }
        else
        {
            Console.WriteLine("{0} ({1})", (int)_response.StatusCode, _response.ReasonPhrase);
        }
    }

    private async Task FetchCustomersAsync()
    {
        // finally, return remaining customers from underlying data store
        _response = await _client.GetAsync("api/customer/");
        if (_response.IsSuccessStatusCode)
        {
            var customers = await _response.Content.ReadAsAsync<IEnumerable<Customer>>();
            foreach (var customer in customers)
            {
                Console.WriteLine("Customer {0} has {1} Phone Numbers(s)",
                customer.Name, customer.Phones.Count());
                foreach (var phoneType in customer.Phones)
                {
                    Console.WriteLine("Phone Type: {0}", phoneType.PhoneType);
                }
            }
        }
        else
        {
            Console.WriteLine("{0} ({1})", (int)_response.StatusCode, _response.ReasonPhrase);
        }
    }
}

  • در آخر کلاس‌های Customer, Phone و BaseEntity را به پروژه کلاینت اضافه کنید. چنین کدهایی بهتر است در لایه مجزایی قرار گیرند و بین لایه‌های مختلف اپلیکیشن به اشتراک گذاشته شوند.

اگر اپلیکیشن کلاینت را اجرا کنید با خروجی زیر مواجه خواهید شد.








شرح مثال جاری

با اجرای اپلیکیشن Web API شروع کنید. این اپلیکیشن یک MVC Web Controller دارد که پس از اجرا شما را به صفحه خانه هدایت می‌کند. در این مرحله سایت در حال اجرا است و سرویس‌ها قابل دسترسی هستند.

سپس اپلیکیشن کنسول را باز کنید و روی خط اول کد فایل program.cs یک breakpoint قرار داده و آن را اجرا کنید. ابتدا آدرس سرویس را نگاشت می‌کنیم و از سرویس درخواست می‌کنیم که اطلاعات را با فرمت JSON بازگرداند.

سپس توسط متد DeleteAsync که روی آبجکت HttpClient تعریف شده است اکشن متد Cleanup را روی سرویس فراخوانی می‌کنیم. این فراخوانی تمام داده‌های پیشین را حذف می‌کند.

در قدم بعدی یک مشتری بهمراه دو شماره تماس می‌سازیم. توجه کنید که برای هر موجودیت مشخصا خاصیت TrackingState را مقدار دهی می‌کنیم تا کامپوننت‌های Change-tracking در EF عملیات لازم SQL برای هر موجودیت را تولید کنند.

سپس توسط متد PostAsync که روی آبجکت HttpClient تعریف شده اکشن متد UpdateCustomer را روی سرویس فراخوانی می‌کنیم. اگر به این اکشن متد یک breakpoint اضافه کنید خواهید دید که موجودیت مشتری را بعنوان یک پارامتر دریافت می‌کند و آن را به context جاری اضافه می‌نماید. با اضافه کردن موجودیت به کانتکست جاری کل object graph اضافه می‌شود و EF شروع به ردیابی تغییرات آن می‌کند. دقت کنید که آبجکت موجودیت باید Add شود و نه Attach.

قدم بعدی جالب است، هنگامی که از خاصیت DbChangeTracker استفاده می‌کنیم. این خاصیت روی آبجکت context تعریف شده و یک <IEnumerable<DbEntityEntry را با نام Entries ارائه می‌کند. در اینجا بسادگی نوع پایه EntityType را تنظیم میکنیم. این کار به ما اجازه می‌دهد که در تمام موجودیت هایی که از نوع BaseEntity هستند پیمایش کنیم. اگر بیاد داشته باشید این کلاس، کلاس پایه تمام موجودیت‌ها است. در هر مرحله از پیمایش (iteration) با استفاده از کلاس EntityStateFactory مقدار خاصیت TrackingState را به مقدار متناظر در سیستم ردیابی EF تبدیل می‌کنیم. اگر کلاینت مقدار این فیلد را به Modified تنظیم کرده باشد پردازش بیشتری انجام می‌شود. ابتدا وضعیت موجودیت را از Modified به Unchanged تغییر می‌دهیم. سپس مقادیر اصلی را با فراخوانی متد GetDatabaseValues روی آبجکت Entry از دیتابیس دریافت می‌کنیم. فراخوانی این متد مقادیر موجود در دیتابیس را برای موجودیت جاری دریافت می‌کند. سپس مقادیر بدست آمده را به کلکسیون OriginalValues اختصاص می‌دهیم. پشت پرده، کامپوننت‌های EF Change-tracking بصورت خودکار تفاوت‌های مقادیر اصلی و مقادیر ارسالی را تشخیص می‌دهند و فیلدهای مربوطه را با وضعیت Modified علامت گذاری می‌کنند. فراخوانی‌های بعدی متد SaveChanges تنها فیلدهایی که در سمت کلاینت تغییر کرده اند را بروز رسانی خواهد کرد و نه تمام خواص موجودیت را.

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

متد UpdateCustomer در سرویس ما مقادیر TrackingState را به مقادیر متناظر EF تبدیل می‌کند و آبجکت‌ها را به موتور change-tracking ارسال می‌کند که نهایتا منجر به تولید دستورات لازم SQL می‌شود.

نکته: در اپلیکیشن‌های واقعی بهتر است کد دسترسی داده‌ها و مدل‌های دامنه را به لایه مجزایی منتقل کنید. همچنین پیاده سازی فعلی change-tracking در سمت کلاینت می‌تواند توسعه داده شود تا با انواع جنریک کار کند. در این صورت از نوشتن مقادیر زیادی کد تکراری جلوگیری خواهید کرد و از یک پیاده سازی می‌توانید برای تمام موجودیت‌ها استفاده کنید.

مطالب
یکپارچه سازی سیستم اعتبارسنجی ASP.NET MVC با Kendo UI validator
روش پیش فرض اعتبارسنجی برنامه‌های ASP.NET MVC، استفاده از دو افزونه‌ی jquery.validate و jquery.validate.unobtrusive است.
    <script src="~/Scripts/jquery.validate.min.js" type="text/javascript"></script>
    <script src="~/Scripts/jquery.validate.unobtrusive.min.js" type="text/javascript"></script>
کار اصلی اعتبارسنجی، توسط افزونه‌ی jquery.validate انجام می‌شود و فایل jquery.validate.unobtrusive صرفا یک وفق دهنده و مترجم ویژگی‌های خاص ASP.NET MVC به jquery.validate است.


عدم سازگاری پیش فرض jquery.validate با بعضی از ویجت‌های Kendo UI

در حالت استفاده از Kendo UI، این سیستم هنوز هم کار می‌کند؛ اما با یک مشکل. اگر برای مثال از kendoComboBox استفاده کنید، اعتبارسنجی‌های تعریف شده در برنامه، توسط jquery.validate دیده نخواهند شد. برای مثال فرض کنید یک چنین مدلی در اختیار View برنامه قرار گرفته است:
    public class OrderDetailViewModel
    {
        [StringLength(15)]
        [Required]
        public string Destination { get; set; }
    }
با این View که در آن به فیلد Destination، یک kendoComboBox متصل شده‌است:
@model Mvc4TestViewModel.Models.OrderDetailViewModel

@using (Ajax.BeginForm(actionName: "Index", controllerName: "Home",
                       ajaxOptions:new AjaxOptions(),
                       htmlAttributes: new { id = "Form1", name ="Form1" }, routeValues: new { }
                       ))
{
    @Html.AntiForgeryToken()
    @Html.ValidationSummary(true)

    <fieldset>
        <legend>OrderDetail</legend>
        <div class="editor-label">
            @Html.LabelFor(model => model.Destination)
        </div>
        <div class="editor-field">
            @Html.TextBoxFor(model => model.Destination, new { @class = "k-textbox" })
            @Html.ValidationMessageFor(model => model.Destination)
        </div>

        <p>
            <button class="k-button" type="submit" title="Sumbit">
                Sumbit
            </button>
        </p>
    </fieldset>
}

@section JavaScript
{
    <script type="text/javascript">
        $(function () {
            $("#Destination").kendoComboBox({
                dataSource: [
                    "loc 1",
                    "loc 2"
                ]
            });
    </script>
}
اگر برنامه را اجرا کنید و بر روی دکمه‌ی submit کلیک نمائید، ویژگی Required عمل نخواهد کرد و عملا در سمت کاربر اعتبارسنجی رخ نمی‌دهد.


همانطور که در تصویر مشاهده می‌کنید، با اتصال kendoComboBox به یک فیلد، این فیلد در حالت مخفی قرار می‌گیرد و ویجت کندو یو آی بجای آن نمایش داده خواهد شد. در این حالت چون در فایل jquery.validate.js چنین تنظیمی وجود دارد:
$.extend( $.validator, {
    defaults: {
     //…
     ignore: ":hidden",
به صورت پیش فرض از اعتبارسنجی فیلدهای مخفی صرفنظر می‌شود.
راه حل آن نیز ساده‌است. تنها باید خاصیت ignore را بازنویسی کرد و تغییر داد:
    <script type="text/javascript">
        $(function () {
            var form = $('#Form1');
            form.data('validator').settings.ignore = ''; // default is ":hidden".
        });
    </script>
در اینجا صرفا خاصیت ignore فرم یک، جهت درنظر گرفتن فیلدهای مخفی تغییر کرده‌است. اگر می‌خواهید این تنظیم را به تمام فرم‌ها اعمال کنید، می‌توان از دستور ذیل استفاده کرد:
<script type="text/javascript">
    $.validator.setDefaults({
        ignore: ""
    });
</script>


یکپارچه کردن سیستم اعتبارسنجی Kendo UI با سیستم اعتبارسنجی ASP.NET MVC

در مطلب «اعتبار سنجی ورودی‌های کاربر در Kendo UI» با زیرساخت اعتبارسنجی Kendo UI آشنا شدید. برای اینکه بتوان این سیستم را با ASP.NET MVC یکپارچه کرد، نیاز است دو کار صورت گیرد:
الف) تعریف فایل kendo.aspnetmvc.js به صفحه اضافه شود:
 <script src="~/Scripts/kendo.aspnetmvc.js" type="text/javascript"></script>
ب) همانند قبل، متد kendoValidator بر روی فرم فراخوانی شود تا سیستم اعتبارسنجی Kendo UI در این ناحیه فعال گردد:
    <script type="text/javascript">
        $(function () {
            $("form").kendoValidator();
        });
    </script>
پس از آن خواهیم داشت:


فایل kendo.aspnetmvc.js که در بسته‌ی مخصوص Kendo UI تهیه شده برای ASP.NET MVC موجود است (در پوشه‌ی js آن)، عملکردی مشابه فایل jquery.validate.unobtrusive مایکروسافت دارد. کار آن وفق دادن و ترجمه‌ی اعتبارسنجی unobtrusive به روش Kendo UI است.

این فایل را از اینجا می‌توانید دریافت کنید:
kendo.mvc.zip


البته باید دقت داشت که در حال حاضر فقط ویژگی‌های ذیل از ASP.NET MVC توسط kendo.aspnetmvc.js پشتیبانی می‌شوند:
 Required
StringLength
Range
RegularExpression
برای تکمیل آن می‌توان از یک پروژه‌ی سورس باز به نام Moon.Validation for KendoUI Validator استفاده کرد. برای مثال remote validation مخصوص Kendo UI را اضافه کرده‌است.
مطالب
شروع کار با Apache Cordova در ویژوال استودیو #5

همانطور که در قسمت قبل گفته شد، در این قسمت با روش کار jQuery Mobile و plugin‌های مربوط به Cordova آشنا خواهیم شد.


تگ متای زیر برای تنظیمات مربوط به viewport است و برای jQuery Mobile توصیه می‌شود.
<!DOCTYPE html> 
<html> 
<head> 
<meta charset="utf-8"> 
<title>Title</title> 
<meta name="viewport" content="width=device-width, initial-scale=1">
device-width  نشان می‌دهد که می‌خواهیم مقیاس محتوای ما به اندازه‌ی عرض دستگاه(device) مورد نظر باشد و initial-scale هم مقدار زوم را برای Web page ما مشخص می‌کند. شما می‌توانید با مقدار دهی user-scalable=no هم امکان تغییر زوم را به کاربر ندهید. این متا تگ را در تمام صفحات html خود بعد از تگ title قرار دهید.

روال کار jQuery Mobile
برای اینکه بتواند سند HTML ما را برای استفاده‌ی در موبایل بهینه کند، ابتدا آن را لود می‌کند و سپس بر  اجزایی که با ویژگیdata-role علامت گذاری شده‌اند، CSS3 بهینه شده برای موبایل را اعمال می‌کند.


از آنجایی که مستندات jQuery Mobile به قدر کافی کامل هست، نیازی نیست تا در مورد تک تک آنها مثال بزنیم و از اصل مطلب دور شویم. در هر مثالی که زده خواهد شد، در صورت استفاده از ویجتی خاص، با آن آشنا خواهیم شد.

لیست کامل اتریبیوت‌های -data به همراه مقادیری که می‌پذیرند 

دموی مربوط به ویجت‌ها  

لیست تمام رخدادها 

شما می‌توانید از امکانات Theme Roller برای شخصی سازی تم‌های مورد نیاز استفاده کنید.

لیست کامل کلاس‌های CSS  



Cordova Plugins

از این قسمت http://plugins.cordova.io/#/viewAll و این قسمت  http://plugreg.com/plugins می‌توانید سراغ پلاگین‌های مورد نیاز خود بگردید. برای مثال وارد بخش کانفیگ پروژه شده و از قسمت plugins  و تب Core یکسری از پلاگین‌هایی را که در Cordova گنجانده شده است، مشاهده می‌کنید. با کلیک بر روی دکمه‌ی Add می‌توانید آن را دانلود کرده و از API‌های آن استفاده کنید.



برای مثال پلاگین Notification را به پروژه اضافه می‌کنم. سپس یک فایل js را با نام custom.js به فولدر scripts در ریشه پروژه اضافه کرده و  محتوای فایل‌های index.html , custome.js را به شکل زیر در نظر می‌گیرم:


$(function() {
    $("#alert").on('tap', function(event) {
        navigator.notification.alert("اطلاعات ذخیره شد",null, "alert", "تایید");
    });

    $("#prompt").on('tap', function(event) {
        navigator.notification.prompt("برای تائید نام خود را وارد کنید", onPrompt, "prompt", "تایید", "لغو"],"نام خود"]);
    });

    function onPrompt(results) {
        navigator.notification.alert(results.buttonIndex + "\n" + results.input1, null);
    }
    $("#confirm").on('tap', function(event) {
        navigator.notification.confirm("حذف انجام شود؟", onConfirm, "confirm", ["بله", "خیر", "نمیدانم"]);
    });

    function onConfirm(buttonIndex) {
        navigator.notification.alert(buttonIndex , null);
    }
    $("#beep").on('tap', function(event) {
        navigator.notification.beep(1);
    });

});

رخداد tap زمانی صادر می‌شود که کاربر، دکمه‌ی مورد نظر را لمس کند و یکی از رخداد‌های jQuery Mobile می‌باشد. بعد از نصب پلاگین Notification، با استفاده از navigator.notification می‌توانید به متد‌های مورد نظر که در بالا مشخص است، دسترسی پیدا کنید.

برای آشنایی با این پلاگین می‌توانید داکیومنت آن را مطالعه کنید.

در کد بالا با استفاده از متد‌های callback توانسته‌ایم اطلاعاتی در مورد نوع عملکرد کاربر با notification ما بدست آوریم.


<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>CordovaApp01</title>
   <meta name="viewport" content="width=device-width, initial-scale=1"/> 
    <!-- CordovaApp01 references -->
    <link href="css/index.css" rel="stylesheet" />
    <link href="jquery.mobile.rtl/css/themes/default/rtl.jquery.mobile-1.4.0.css" rel="stylesheet" />
</head>
<body>
<div data-role="page" id="page1">
    <div data-role="header">
        <h2>
            تست پلاگین Notification
        </h2>
    </div>
    <div data-role="content">
        <a href="#page2" data-transition="pop" data-rel="dialog" data-role="button" data-inline="true" data-icon="back">page 2</a>
       
        <button data-role="button" id="alert" data-inline="true" >alert</button>
        <button data-role="button" id="confirm" data-inline="true">confirm</button>
        <button data-role="button" id="beep" data-inline="true" >beep</button>
        <button data-role="button" id="prompt" data-inline="true" >prompt</button>

    </div>
    <div data-role="footer">
        <h2>من فوتر هستم</h2>
    </div>
</div>
    <div data-role="page" id="page2">
        <div data-role="header">
            <h1>Header</h1>
        </div>
        <div data-role="content">
            Content
        </div>
        <div data-role="footer">
            <h1>Footer</h1>
        </div>
    </div>
<!-- Cordova reference, this is added to your app when it's built. -->
    <script src="scripts/jquery-2.1.3.min.js"></script>
    <script src="cordova.js"></script>
    <script src="scripts/platformOverrides.js"></script>
    <script src="scripts/index.js"></script>
    <script src="jquery.mobile.rtl/js/rtl.jquery.mobile-1.4.0.js"></script>
    <script src="scripts/custom.js"></script>
</body>
</html>

در کد بالا 4 تا button دیده می‌شود که ویژگی data-role آنها مقدار button در نظر گرفته شده‌است تا توسط jQuery Mobile به عنوان button شناخته شوند و استایل‌های لازم بر روی آن‌ها اعمال گردد. قرار است طبق کد js ایی که نوشته‌ایم، با لمس کردن هر کدام از دکمه‌ها، notification هایی نمایش داده شوند.


برای اینکار شبیه ساز YouWave را دانلود کرده و نصب کنید. سپس در قسمت toolbar ویژوال، گزینه‌ی Device را به جای شبیه ساز Ripple انتخاب کنید. نرم افزار youwave را اجرا کنید حال اگر برنامه را اجرا کنید با خطای زیر مواجه خواهید شد:

Error447C:\Users\Administrator\Documents\Visual Studio 2013\Projects\CordovaApp-01\CordovaApp-01\bld\Debug\platforms\android\cordova\node_modules\q\q.js:126CordovaApp-01
Error448throw e;CordovaApp-01
Error449^CordovaApp-01
Error450Error : DEP10201 : Failed to deploy to device, no devices found.CordovaApp-01
مشخصا خطا، مبنی بر پیدا نشدن دستگاه خارجی است. برای رفع این مشکل می‌بایست شبیه ساز youwave را به ویژوال استودیو وصل کنیم. برای این منظور دستور زیر را در cmd اجرا کنید.
adb connect localhost:5558

بعد از آن اگر پروژه را اجرا کنید، فایل apk. پروژه بر روی شبیه ساز نصب شده و اجرا خواهد شد. با کلیک بر روی دکمه‌ی confirm تصویری به شکل زیر قابل مشاهده خواهد بود:


علاوه بر این ما در سند HTML خود در بالا، یک page و یک تگ a قرار داده‌ایم. 
 <a href="#page2" data-transition="pop" data-rel="dialog" data-role="button" data-inline="true" data-icon="back">page 2</a>
data-role: با مقدار button در نظر گرفته شده است؛ لذا به شکل 4 دکمه دیگر رندر خواهد شد.
data-transition: با مقدار pop در نظر گرفته شده است که مشخص کننده‌ی افکت ظاهر شدن صفحه‌ای است که قرار است بار گذاری شود.
data-rel: مشخص می‌کند که صفحه‌ی مورد نظر من به صورت دیالوگ باز شود.
data-icon: با استفاده از این ویژگی می‌توان icon مورد نظر خود را برای المنت در نظر گرفت.
data-inline: برای به خط کردن دکمه‌ها کنار هم استفاده می‌شود.
با لمس کردن این دکمه، نتیجه به شکل زیر خواهد بود:

در مقاله‌ی بعد، به مباحث Database در Cordova خواهیم پرداخت.

ادامه دارد...

مطالب
فعال سازی عملیات CRUD در Kendo UI Grid
پیشنیاز بحث
- «فرمت کردن اطلاعات نمایش داده شده به کمک Kendo UI Grid»

Kendo UI Grid دارای امکانات ثبت، ویرایش و حذف توکاری است که در ادامه نحوه‌ی فعال سازی آن‌‌ها را بررسی خواهیم کرد. مثالی که در ادامه بررسی خواهد شد، در تکمیل مطلب «فرمت کردن اطلاعات نمایش داده شده به کمک Kendo UI Grid» است.



تنظیمات Data Source سمت کاربر

برای فعال سازی صفحه بندی سمت سرور، با قسمت read منبع داده Kendo UI پیشتر آشنا شده بودیم. جهت فعال سازی قسمت‌های ثبت اطلاعات جدید (create)، به روز رسانی رکوردهای موجود (update) و حذف ردیفی مشخص (destroy) نیاز است تعاریف قسمت‌های متناظر را که هر کدام به آدرس مشخصی در سمت سرور اشاره می‌کنند، اضافه کنیم:
            var productsDataSource = new kendo.data.DataSource({
                transport: {
                    read: {
                        url: "api/products",
                        dataType: "json",
                        contentType: 'application/json; charset=utf-8',
                        type: 'GET'
                    },
                    create: {
                        url: "api/products",
                        contentType: 'application/json; charset=utf-8',
                        type: "POST"
                    },
                    update: {
                        url: function (product) {
                            return "api/products/" + product.Id;
                        },
                        contentType: 'application/json; charset=utf-8',
                        type: "PUT"
                    },
                    destroy: {
                        url: function (product) {
                            return "api/products/" + product.Id;
                        },
                        contentType: 'application/json; charset=utf-8',
                        type: "DELETE"
                    },
                    //...
                },
                schema: {
                    //...
                    model: {
                        id: "Id", // define the model of the data source. Required for validation and property types.
                        fields: {
                            "Id": { type: "number", editable: false }, //تعیین نوع فیلد برای جستجوی پویا مهم است
                            "Name": { type: "string", validation: { required: true } },
                            "IsAvailable": { type: "boolean" },
                            "Price": { type: "number", validation: { required: true, min: 1 } },
                            "AddDate": { type: "date", validation: { required: true } }
                        }
                    }
                },
                batch: false, // enable batch editing - changes will be saved when the user clicks the "Save changes" button
                //...
            });
- همانطور که ملاحظه می‌کنید، حالت‌های update و destroy بر اساس Id ردیف انتخابی کار می‌کنند. این Id را باید در قسمت model مربوط به اسکیمای تعریف شده، دقیقا مشخص کرد. عدم تعریف فیلد id، سبب خواهد شد تا عملیات update نیز در حالت create تفسیر شود.
- به علاوه در اینجا به ازای هر فیلد، مباحث اعتبارسنجی نیز اضافه شده‌اند؛ برای مثال فیلدهای اجباری با required: true مشخص گردیده‌اند.
- اگر فیلدی نباید ویرایش شود (مانند فیلد Id)، خاصیت editable آن‌را false کنید.
- در data source امکان تعریف خاصیتی به نام batch نیز وجود دارد. حالت پیش فرض آن false است. به این معنا که در حالت ویرایش، تغییرات هر ردیفی، یک درخواست مجزا را به سمت سرور سبب خواهد شد. اگر آن‌را true کنید، تغییرات تمام ردیف‌ها در طی یک درخواست به سمت سرور ارسال می‌شوند. در این حالت باید به خاطر داشت که پارامترهای سمت سرور، از حالت یک شیء مشخص باید به لیستی از آن‌ها تغییر یابند.


مدیریت سمت سرور ثبت، ویرایش و حذف اطلاعات

در حالت ثبت، متد Post، توسط آدرس مشخص شده در قسمت create منبع داده گرید، فراخوانی می‌گردد:
namespace KendoUI06.Controllers
{
    public class ProductsController : ApiController
    {
        public HttpResponseMessage Post(Product product)
        {
            if (!ModelState.IsValid)
                return Request.CreateResponse(HttpStatusCode.BadRequest);

            var id = 1;
            var lastItem = ProductDataSource.LatestProducts.LastOrDefault();
            if (lastItem != null)
            {
                id = lastItem.Id + 1;
            }
            product.Id = id;
            ProductDataSource.LatestProducts.Add(product);

            var response = Request.CreateResponse(HttpStatusCode.Created, product);
            response.Headers.Location = new Uri(Url.Link("DefaultApi", new { id = product.Id }));
            // گرید آی دی جدید را به این صورت دریافت می‌کند
            response.Content = new ObjectContent<DataSourceResult>(
                new DataSourceResult { Data = new[] { product } }, new JsonMediaTypeFormatter());
            return response;
        }
    }
}
نکته‌ی مهمی که در اینجا باید به آن دقت داشت، نحوه‌ی بازگشت Id رکورد جدید ثبت شده‌است. در این مثال، قسمت schema منبع داده سمت کاربر به نحو ذیل تعریف شده‌است:
            var productsDataSource = new kendo.data.DataSource({
                //...
                schema: {
                    data: "Data",
                    total: "Total",
                }
                //...
            });
از این جهت که خروجی متد Get بازگرداننده‌ی اطلاعات صفحه بندی شده، از نوع DataSourceResult است و این نوع، دارای خواصی مانند Data، Total و Aggergate است:
namespace KendoUI06.Controllers
{
    public class ProductsController : ApiController
    {
        public DataSourceResult Get(HttpRequestMessage requestMessage)
        {
            var request = JsonConvert.DeserializeObject<DataSourceRequest>(
                requestMessage.RequestUri.ParseQueryString().GetKey(0)
            );

            var list = ProductDataSource.LatestProducts;
            return list.AsQueryable()
                       .ToDataSourceResult(request.Take, request.Skip, request.Sort, request.Filter);
        }
    }
}
بنابراین در متد Post نیز باید بر این اساس، response.Content را از نوع لیستی از DataSourceResult تعریف کرد تا Kendo UI Grid بداند که Id رکورد جدید را باید از فیلد Data، همانند تنظیمات schema منبع داده خود، دریافت کند.
response.Content = new ObjectContent<DataSourceResult>(
                              new DataSourceResult { Data = new[] { product } }, new JsonMediaTypeFormatter());
اگر این تنظیم صورت نگیرد، Id رکورد جدید را در گرید، مساوی صفر مشاهده خواهید کرد و عملا بدون استفاده خواهد شد؛ زیرا قابلیت ویرایش و حذف خود را از دست می‌دهد.

متدهای حذف و به روز رسانی سمت سرور نیز چنین امضایی را خواهند داشت:
namespace KendoUI06.Controllers
{
    public class ProductsController : ApiController
    {
        public HttpResponseMessage Delete(int id)
        {
            var item = ProductDataSource.LatestProducts.FirstOrDefault(x => x.Id == id);
            if (item == null)
                return Request.CreateResponse(HttpStatusCode.NotFound);

            ProductDataSource.LatestProducts.Remove(item);

            return Request.CreateResponse(HttpStatusCode.OK, item);
        }

        [HttpPut] // Add it to fix this error: The requested resource does not support http method 'PUT'
        public HttpResponseMessage Update(int id, Product product)
        {
            var item = ProductDataSource.LatestProducts
                                        .Select(
                                            (prod, index) =>
                                                new
                                                {
                                                    Item = prod,
                                                    Index = index
                                                })
                                        .FirstOrDefault(x => x.Item.Id == id);
            if (item == null)
                return Request.CreateResponse(HttpStatusCode.NotFound);


            if (!ModelState.IsValid || id != product.Id)
                return Request.CreateResponse(HttpStatusCode.BadRequest);

            ProductDataSource.LatestProducts[item.Index] = product;
            return Request.CreateResponse(HttpStatusCode.OK);
        }
    }
}
حالت Update از HTTP Verb خاصی به نام Put استفاده می‌کند و ممکن است در این بین خطای The requested resource does not support http method 'PUT' را دریافت کنید. برای رفع آن ابتدا بررسی کنید که آیا Web.config برنامه دارای تعاریف ExtensionlessUrlHandler هست یا خیر. همچنین مزین کردن این متد با ویژگی HttpPut، مشکل را برطرف می‌کند.


تنظیمات Kendo UI Grid جهت فعال سازی CRUD

در ادامه کلیه تغییرات مورد نیاز جهت فعال سازی CRUD را در Kendo UI، به همراه مباحث بومی سازی عبارات متناظر با دکمه‌ها و صفحات خودکار مرتبط، مشاهده می‌کنید:
            $("#report-grid").kendoGrid({
                //....
                editable: {
                    confirmation: "آیا مایل به حذف ردیف انتخابی هستید؟",
                    destroy: true, // whether or not to delete item when button is clicked
                    mode: "popup", // options are "incell", "inline", and "popup"
                    //template: kendo.template($("#popupEditorTemplate").html()), // template to use for pop-up editing
                    update: true, // switch item to edit mode when clicked?
                    window: {
                        title: "مشخصات محصول"   // Localization for Edit in the popup window
                    }
                },
                columns: [
                //....
                    {
                        command: [
                            { name: "edit", text: "ویرایش" },
                            { name: "destroy", text: "حذف" }
                        ],
                        title: "&nbsp;", width: "160px"
                    }
                ],
                toolbar: [
                    { name: "create", text: "افزودن ردیف جدید" },
                    { name: "save", text: "ذخیره‌ی تمامی تغییرات" },
                    { name: "cancel", text: "لغو کلیه‌ی تغییرات" },
                    { template: kendo.template($("#toolbarTemplate").html()) }
                ],
                messages: {
                    editable: {
                        cancelDelete: "لغو",
                        confirmation: "آیا مایل به حذف این رکورد هستید؟",
                        confirmDelete: "حذف"
                    },
                    commands: {
                        create: "افزودن ردیف جدید",
                        cancel: "لغو کلیه‌ی تغییرات",
                        save: "ذخیره‌ی تمامی تغییرات",
                        destroy: "حذف",
                        edit: "ویرایش",
                        update: "ثبت",
                        canceledit: "لغو"
                    }
                }
            });
- ساده‌ترین حالت CRUD در Kendo UI با مقدار دهی خاصیت editable آن به true آغاز می‌شود. در این حالت، ویرایش درون سلولی یا incell فعال خواهد شد که مباحث batching ابتدای بحث، فقط در این حالت کار می‌کند. زمانیکه incell editing فعال است، کاربر می‌تواند تمام ردیف‌ها را ویرایش کرده و در آخر کار بر روی دکمه‌ی «ذخیره‌ی تمامی تغییرات» موجود در نوار ابزار، کلیک کند. در سایر حالات، هر بار تنها یک ردیف را می‌توان ویرایش کرد.
- برای فعال سازی تولید صفحات خودکار ویرایش و افزودن ردیف‌ها، نیاز است خاصیت editable را به نحوی که ملاحظه می‌کنید، مقدار دهی کرد. خاصیت mode آن سه حالت incell (پیش فرض)، inline و popup را پشتیبانی می‌کند.
- اگر حالت‌های inline و یا popup را فعال کردید، در انتهای ستون‌های تعریف شده، نیاز است ستون ویژه‌ای به نام command را مطابق تعاریف فوق، تعریف کنید. در این حالت دو دکمه‌ی ویرایش و ثبت، فعال می‌شوند و اطلاعات خود را از تنظیمات data source گرید دریافت می‌کنند. دکمه‌ی ویرایش در حالت incell کاربردی ندارد (چون در این حالت کاربر با کلیک درون یک سلول می‌تواند آن‌را مانند برنامه‌ی اکسل ویرایش کند). اما دکمه‌ی حذف در هر سه حالت قابل استفاده است.
- به نوار ابزار گرید، سه دکمه‌ی افزودن ردیف‌های جدید، ذخیره‌ی تمامی تغییرات و لغو تغییرات صورت گرفته، اضافه شده‌اند. این دکمه‌ها استاندارد بوده و در اینجا نحوه‌ی بومی سازی پیام‌های مرتبط را نیز مشاهده می‌کنید. همانطور که عنوان شد، دکمه‌های «تمامی تغییرات» در حالت فعال سازی batching در منبع داده و استفاده از incell editing معنا پیدا می‌کند. در سایر حالات این دو دکمه کاربردی ندارند. اما دکمه‌ی افزودن ردیف‌های جدید در هر سه حالت کاربرد دارد و یکسان است.


کدهای کامل این مثال را از اینجا می‌توانید دریافت کنید
KendoUI06.zip
مطالب
تعیین اعتبار یک GUID در دات نت

GUID یا Globally unique identifier یک عدد صحیح 128 بیتی است (بنابراین 2 به توان 128 حالت را می‌توان برای آن درنظر گرفت). از لحاظ آماری تولید دو GUID یکسان تقریبا صفر می‌باشد. به همین جهت از آن با اطمینان می‌توان به عنوان یک شناسه منحصربفرد استفاده کرد. برای مثال اگر به لینک‌های دانلود فایل‌ها از سایت مایکروسافت دقت کنید، این نوع GUID ها را به وفور می‌توانید ملاحظه نمائید. یا زمانیکه قرار است فایلی را که بر روی سرور آپلود شده، ذخیره نمائیم، می‌توان نام آن‌را یک GUID درنظر گرفت بدون اینکه نگران باشیم آیا فایل آپلود شده بر روی یکی از فایل‌های موجود overwrite می‌شود یا خیر. یا مثلا استفاده از آن در سناریوی بازیابی کلمه عبور در یک سایت. هنگامیکه کاربری درخواست بازیابی کلمه عبور فراموش شده خود را داد، یک GUID برای آن تولید کرده و به او ایمیل می‌زنیم و در آخر آن‌را در کوئری استرینگی دریافت کرده و با مقدار موجود در دیتابیس مقایسه می‌کنیم. مطمئن هستیم که این عبارت قابل حدس زدن نیست و همچنین یکتا است.

برای تولید GUID ها در دات نت می‌توان مانند مثال زیر عمل کرد و خروجی‌های دلخواهی را با فرمت‌های مختلفی دریافت کرد:

System.Guid.NewGuid().ToString() = 81276701-9dd7-42e9-b128-81c762a172ff
System.Guid.NewGuid().ToString("N") = 489ecfc61ee7403988efe8546806c6a2
System.Guid.NewGuid().ToString("D") = 119201d9-84d9-4126-b93f-be6576eedbfd
System.Guid.NewGuid().ToString("B") = {fd508d4b-cbaf-4f1c-894c-810169b1d20c}
System.Guid.NewGuid().ToString("P") = (eee1fe00-7e63-4632-a290-516bfc457f42)

تمام این‌ها خیلی هم خوب! اما همان سناریوی مشخص ساختن یک فایل با GUID و یا بازیابی کلمه عبور فراموش شده را درنظر بگیرید. یکی از اصول امنیتی مهم، تعیین اعتبار ورودی کاربر است. چگونه باید یک GUID را به صورت مؤثری تعیین اعتبار کرد و مطمئن شد که کاربر از این راه قصد تزریق اس کیوال را ندارد؟
دو روش برای انجام اینکار وجود دارد
الف) عبارت دریافت شده را به new Guid پاس کنیم. اگر ورودی غیرمعتبر باشد، یک exception تولید خواهد شد.
ب) استفاده از regular expressions جهت بررسی الگوی عبارت وارد شده

پیاده سازی این دو را در کلاس زیر می‌توان ملاحظه نمود:
using System;
using System.Text.RegularExpressions;

namespace sample
{
/// <summary>
/// بررسی اعتبار یک گوئید
/// </summary>
public static class CValidGUID
{
/// <summary>
/// بررسی تعیین اعتبار ورودی
/// </summary>
/// <param name="guidString">ورودی</param>
/// <returns></returns>
public static bool IsGuid(this string guidString)
{
if (string.IsNullOrEmpty(guidString)) return false;

bool bResult;
try
{
Guid g = new Guid(guidString);
bResult = true;
}
catch
{
bResult = false;
}

return bResult;
}

/// <summary>
/// بررسی تعیین اعتبار ورودی
/// </summary>
/// <param name="input">ورودی</param>
/// <returns></returns>
public static bool IsValidGUID(this string input)
{
return !string.IsNullOrEmpty(input) &&
new Regex(@"^(\{{0,1}([0-9a-fA-F]){8}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){12}\}{0,1})$").IsMatch(input);
}
}

}

سؤال: آیا متدهای فوق ( extension methods ) درست کار می‌کنند و واقعا نیاز ما را برآورده خواهند ساخت؟ به همین منظور، آزمایش واحد آن‌ها را نیز تهیه خواهیم کرد:

using NUnit.Framework;
using sample;

namespace TestLibrary
{
[TestFixture]
public class TestCValidGUID
{

/*******************************************************************************/
[Test]
public void TestIsGuid1()
{
Assert.IsTrue("81276701-9dd7-42e9-b128-81c762a172ff".IsGuid());
}

[Test]
public void TestIsGuid2()
{
Assert.IsTrue("489ecfc61ee7403988efe8546806c6a2".IsGuid());
}

[Test]
public void TestIsGuid3()
{
Assert.IsTrue("{fd508d4b-cbaf-4f1c-894c-810169b1d20c}".IsGuid());
}

[Test]
public void TestIsGuid4()
{
Assert.IsTrue("(eee1fe00-7e63-4632-a290-516bfc457f42)".IsGuid());
}

[Test]
public void TestIsGuid5()
{
Assert.IsFalse("81276701;9dd7;42e9-b128-81c762a172ff".IsGuid());
}


/*******************************************************************************/
[Test]
public void TestIsValidGUID1()
{
Assert.IsTrue("81276701-9dd7-42e9-b128-81c762a172ff".IsValidGUID());
}

[Test]
public void TestIsValidGUID2()
{
Assert.IsTrue("489ecfc61ee7403988efe8546806c6a2".IsValidGUID());
}

[Test]
public void TestIsValidGUID3()
{
Assert.IsTrue("{fd508d4b-cbaf-4f1c-894c-810169b1d20c}".IsValidGUID());
}

[Test]
public void TestIsValidGUID4()
{
Assert.IsTrue("(eee1fe00-7e63-4632-a290-516bfc457f42)".IsValidGUID());
}

[Test]
public void TestIsValidGUID5()
{
Assert.IsFalse("81276701;9dd7;42e9-b128-81c762a172ff".IsValidGUID());
}
}

}

نتیجه این آزمایش به صورت زیر است:



همانطور که ملاحظه می‌کنید حالت دوم یعنی استفاده از عبارات باقاعده دو حالت را نمی‌تواند بررسی کند (مطابق الگوی بکار گرفته شده که البته قابل اصلاح است)، اما روش معمولی استفاده از new Guid ، تمام فرمت‌های تولید شده توسط دات نت را پوشش می‌دهد.


مطالب
React 16x - قسمت 4 - کامپوننت‌ها - بخش 1 - کار با عبارات JSX
برپایی پروژه‌ی ایجاد اولین کامپوننت React

در اینجا برای بررسی مقدماتی کامپوننت‌ها، یک پروژه‌ی جدید React را ایجاد می‌کنیم.
> create-react-app sample-04
> cd sample-04
> npm start
اکنون در ادامه اولین کاری را که انجام می‌دهیم، نصب توئیتر بوت استرپ 4 است تا بتوانیم توسط امکانات آن، ظاهر بهتری را برای برنامه‌ی تهیه شده تدارک ببینیم. برای این منظور پس از باز کردن پوشه‌ی اصلی برنامه توسط VSCode، دکمه‌های `+ctrl را فشرده (ctrl+back-tick) و دستور زیر را در ترمینال ظاهر شده وارد کنید:
> npm install --save bootstrap
این دستور علاوه بر نصب بوت استرپ 4.3.1 (آخرین نگارش موجود در زمان نگارش این مطلب)، به دلیل ذکر سوئیچ save، مدخل آن‌را نیز به فایل package.json برنامه اضافه می‌کند.
پس از اجرای این دستور، ممکن است پیام‌های اخطاری مانند «requires a peer of jquery@1.9.1 - 3 but none is installed» را نیز مشاهده کنید که مهم نیستند. چون در اینجا صرفا از امکانات CSS ای بوت استرپ استفاده خواهیم کرد و کاری با jQuery نداریم. محل نصب آن نیز پوشه‌ی node_modules\bootstrap برنامه است.

سپس برای افزودن فایل bootstrap.css به پروژه‌ی React خود، ابتدای فایل index.js را به نحو زیر ویرایش خواهیم کرد:
import "bootstrap/dist/css/bootstrap.css";
این import به صورت خودکار توسط webpack ای که در پشت صحنه کار bundling & minification برنامه را انجام می‌دهد، مورد استفاده قرار می‌گیرد.


ایجاد اولین کامپوننت React

در پوشه‌ی src برنامه، پوشه‌ی جدیدی را به نام components ایجاد می‌کنیم و تمام کامپوننت‌های خود را در آن قرار خواهیم داد. سپس داخل این پوشه، یک فایل جدید و خالی را به نام counter.jsx ایجاد می‌کنیم. پسوند این فایل jsx است و نام فایل‌های کامپوننت‌ها را نیز camel case وارد می‌کنیم؛ یعنی اولین حرف اولین واژه‌ی وارد شده، با حروف کوچک و تمام واژه‌های پس از آن با حروف بزرگ شروع خواهند شد مانند coolApp. مزیت استفاده‌ی از پسوند jsx نسبت به js، فراهم شدن امکانات مخصوص React در VSCode است.
در ابتدای فایل counter.jsx، نیاز است وابستگی‌های React را import کنیم. اگر از قسمت اول بخاطر داشته باشید، «simple react snippets» را نیز در VSCode نصب کردیم. به کمک آن می‌تواند این نوع importها را ساده‌تر وارد کرد. برای این منظور imrc را تایپ کرده و سپس دکمه‌ی tab را فشار دهید.  به این ترتیب یک سطر زیر به صورت خودکار تولید می‌شود:
import React, { Component } from 'react';
پس از این سطر، cc را تایپ کرده و سپس دکمه‌ی tab را فشار دهید تا ساختار کلاس یک کامپوننت React تولید شود. همان لحظه‌ای که این ساختار تولید می‌شود، اگر دقت کنید دو کرسر در صفحه ظاهر شده‌اند که با تایپ نام کامپوننت، نام کلاس و نام export آن‌را تکمیل می‌کنند. نام این کامپوننت را Counter که با حروف بزرگ شروع می‌شود، وارد می‌کنیم. اکنون کدهای آن را به نحو زیر ویرایش می‌کنیم:
import React, { Component } from "react";

class Counter extends Component {
  render() {
    return <h1>Hello world!</h1>;
  }
}

export default Counter;
مفهوم ساختار یک چنین کلاس و export ای را در قسمت قبل با معرفی کلاس‌ها، ارث بری، ماژول‌ها و همچنین exportهای آن‌ها بررسی کردیم. البته در قسمت قبل، export default class را مشاهده کردید و در اینجا بجای آن، سطر آخر این ماژول به export default ختم شده‌است که روش دیگری برای تعریف این export است.
خروجی متد render در اینجا، یک رشته‌ی معمولی نیست؛ بلکه یک عبارت jsx است که در قسمت اول معرفی شد. این عبارت در نهایت توسط کامپایلر Babel به معادل React.createElement ترجمه می‌شود. به همین جهت نیاز است تا import React را در ابتدای این ماژول درج کرد؛ هرچند به ظاهر به صورت مستقیم از آن استفاده نمی‌کنیم.

تا اینجا این کامپوننت در UI برنامه نمایش داده نمی‌شود. به همین جهت به فایل index.js مراجعه کرده و آن‌را به صورت زیر تغییر می‌دهیم:
- ابتدا نیاز است تا شیء Counter را در اینجا import کنیم و چون خروجی پیش‌فرض است، نیازی به ذکر {} برای معرفی آن نیست:
import Counter from "./components/counter";
- سپس در سطر ReactDOM.render، بجای رندر کامپوننت App، کامپوننت Counter را ذکر می‌کنیم:
 ReactDOM.render(<Counter />, document.getElementById("root"));
اکنون برنامه هر زمانیکه به المان جدید Counter برسد، بجای آن به متد render کامپوننت متناظر مراجعه کرده و خروجی آن‌را رندر می‌کند. پس از این تغییر اگر به مرورگر مراجعه کنید، خروجی hello world را مشاهده خواهید کرد.


درج چند عنصر در عبارات JSX

می‌خواهیم در کامپوننت Counter، یک دکمه را نیز نمایش دهیم. برای انجام اینکار، به نحو زیر عمل می‌کنیم:
  render() {
    return <h1>Hello world!</h1><button>Increment</button>;
  }
در این حالت هم در VSCode و هم در کنسول توسعه دهندگان مرورگر، خطای «JSX expressions must have one parent element» ظاهر می‌شود.
عبارات JSX در نهایت باید تبدیل به متد React.createElement شوند. اولین پارامتر این متد، نوع المانی است که قرار است ایجاد شود که در اینجا h1 است. اما در اینجا دو المان را داریم. در این حالت Babel نمی‌داند که چگونه باید یک چنین عبارتی را به React.createElement ترجمه کند. یک راه حل این است که کل این عبارت را داخل یک div قرار داد:
  render() {
    return (
      <div>
        <h1>Hello world!</h1>
        <button>Increment</button>
      </div>
    );
در اینجا فرمت چند سطری return، توسط افزونه‌ی Prettier که در قسمت اول معرفی شد، پس از ذخیره‌ی فایل، به صورت خودکار اعمال شده‌است. همچنین اگر دقت کنید یک () جدید را نیز مشاهده می‌کنید. علت آن مقابله با automatic semicolon insertion است (درج ; خودکار). در جاوا اسکریپت اگر یک return را داشته باشید و پس از آن در همان سطر، چیزی درج نشده باشد، یک سمی‌کالن را به صورت خودکار درج/تفسیر می‌کند. به این ترتیب عبارت JSX چند سطری درج شده‌ی در سطرهای بعد از return، دیده نخواهد شد؛ یعنی چیزی شبیه به عبارات زیر تفسیر می‌شود:
return ;
  <div></div>
برای رفع این مشکل باید دقیقا جلوی واژه‌ی کلیدی return، یک پرانتز را باز کرد و آن‌را پس از خاتمه‌ی عبارت JSX، بست (که البته افزونه‌ی Prettier اینکار را به صورت خودکار برای شما انجام می‌دهد):
return (
  <div></div>
);
نکته 1: بدیهی است زمانیکه المان‌های درج شده را درون یک div محصور کردیم، به همین نحو نیز در DOM اصلی ظاهر خواهند شد. اگر علاقمند نیستید که این div در خروجی نهایی رندر شود، می‌توان بجای آن از React.Fragment استفاده کرد که هیچ نوع المان اضافه‌تری را در DOM بجای آن درج نمی‌کند:
    return (
      <React.Fragment>
        <h1>Hello world!</h1>
        <button>Increment</button>
      </React.Fragment>
    );

نکته 2: در VSCode برای ویرایش همزمان ابتدا و انتهای یک تگ (برای مثال ویرایش همزمان عبارت div در اینجا و تبدیل آن به React.Fragment در دو قسمت)، عبارت آن تگ را انتخاب کرده و سپس دکمه‌های ctrl+d را فشار دهید تا بتوانید همزمان هر دو عبارت انتخاب شده را با هم ویرایش کنید. به اینکار multi-cursor editing می‌گویند.


نمایش پویای اطلاعات در عبارات JSX

در ادامه بجای نمایش عبارت ثابت «Hello world»، می‌خواهیم آن‌را به صورت پویا تنظیم کنیم. برای این منظور یک خاصیت جدید را در کلاس جاری، به نام state تعریف کرده و آن‌را با یک شیء، مقدار دهی می‌کنیم. state، یک خاصیت ویژه در کامپوننت‌های React است و بیانگر داده‌هایی است که آن کامپوننت نیاز دارد. این داده‌ها می‌توانند یک key/value ساده باشند و یا حتی value تعریف شده نیز می‌تواند یک شیء پیچیده باشد.
import React, { Component } from "react";

class Counter extends Component {
  state = {
    count: 0
  };

  render() {
    return (
      <React.Fragment>
        <span>{this.state.count}</span>
        <button>Increment</button>
      </React.Fragment>
    );
  }
}

export default Counter;
در اینجا خاصیت state را با شیءای که حاوی key/value مساوی count با مقدار صفر است، مقدار دهی کرده‌ایم. سپس برای نمایش این اطلاعات در عبارت JSX، از یک {} استفاده می‌شود. داخل {}‌ها می‌توان هر نوع عبارت مجاز جاوا اسکریپتی را درج کرد. برای مثال با this شروع می‌کنیم که بیانگر اشاره‌گری به وهله‌ای از شیء جاری است. سپس می‌توان توسط آن به خاصیت state و سپس کلید count شیء منتسب به آن دسترسی یافت. به این ترتیب عدد صفر، در کنار دکمه‌ای با برچسب Increment، در مرورگر ظاهر خواهد شد.

همانطور که عنوان شد در بین {}‌ها می‌توان هر نوع عبارت مجاز جاوا اسکریپتی را ذکر کرد و عبارت چیزی است که مقداری را بازگشت می‌دهد. بنابراین عبارتی مانند {2+2} را نیز می‌توان در اینجا بکار برد و یا حتی در اینجا می‌توان متدی را فراخوانی کرد که مقداری را بازگشت می‌دهد:
import React, { Component } from "react";

class Counter extends Component {
  state = {
    count: 0
  };

  render() {
    return (
      <React.Fragment>
        <span>{this.formatCount()}</span>
        <button>Increment</button>
      </React.Fragment>
    );
  }

  formatCount() {
    const { count } = this.state; // Object Destructuring
    return count === 0 ? "Zero" : count;
  }
}

export default Counter;
در این مثال می‌خواهیم اگر مقدار count مساوی صفر بود، بجای عدد صفر، واژه‌ی Zero را نمایش دهد. به همین جهت این منطق را به یک متد مانند formatCount منتقل کرده و سپس آن‌را به صورت {()this.formatCount}، فراخوانی کرده و نمایش می‌دهیم.
در متد formatCount حتی می‌توان عبارات JSX را نیز بجای یک رشته‌ی ساده، بازگشت داد:
  formatCount() {
    const { count } = this.state; // Object Destructuring
    return count === 0 ? <h1>Zero</h1> : count;
  }


مقدار دهی ویژگی‌های عناصر در عبارات JSX

فرض کنید یک المان img را به عبارت JSX کلاس Counter اضافه کرده‌ایم. اکنون می‌خواهیم ویژگی src آن‌را مقدار دهی کنیم. در اینجا هر چیزی که بین "" قرار گیرد، به صورت یک رشته‌ی ثابت پردازش می‌شود. برای تنظیم آن به یک متغیر، ابتدا خاصیت state را به صورت زیر جهت درج imageUrl، ویرایش می‌کنیم:
  state = {
    count: 0,
    imageUrl: "/logo192.png"
  };
پس از آن عبارت مقدار خاصیت this.state.imageUrl را توسط یک {}، به ویژگی src تصویر نسبت می‌دهیم:
  render() {
    return (
      <React.Fragment>
        <img src={this.state.imageUrl} alt="" />
        <span>{this.formatCount()}</span>
        <button>Increment</button>
      </React.Fragment>
    );
  }

مقدار دهی class و style المان‌ها، نسبت به مقدار دهی attributes که مشاهده کردید، اندکی متفاوت است؛ از این جهت که در نهایت یک عبارت JSX توسط کامپایلر Babel به معادل جاوا اسکریپتی آن ترجمه می‌شود و اگر در اینجا به عنوان مثال از ویژگی class استفاده شود، چون نام class، یک نام و واژه‌ی کلیدی از پیش معین شده‌ی جاوا اسکریپتی است، امکان استفاده‌ی از آن در اینجا وجود ندارد. به همین جهت در React برای تنظیم ویژگی class عناصر، از className استفاده می‌شود:
    return (
      <React.Fragment>
        <img src={this.state.imageUrl} alt="" />
        <span className="badge badge-primary m-2">{this.formatCount()}</span>
        <button className="btn btn-secondary btn-sm">Increment</button>
      </React.Fragment>
    );
در اینجا اعمال یک سری از کلاس‌های بوت استرپ را که در ابتدای مطلب به پروژه اضافه کردیم، به ویژگی‌های className المان‌های span و button مشاهده می‌کنید.
تا اینجا اگر فایل کامپوننت Counter را ذخیره کنید، خروجی ذیل در مرورگر ظاهر خواهد شد:


روش مقدار دهی ویژگی style نیز متفاوت است. در اینجا React انتظار دارد تا شیءای را که به صورت زیر تشکیل می‌شود:
  styles = {
    fontSize: 50,
    fontWeight: "bold"
  };
به صورت {this.styles} به ویژگی style انتساب دهیم:
    return (
      <React.Fragment>
        <img src={this.state.imageUrl} alt="" />
        <span style={this.styles} className="badge badge-primary m-2">
          {this.formatCount()}
        </span>
        <button className="btn btn-secondary btn-sm">Increment</button>
      </React.Fragment>
    );
نحوه‌ی تشکیل خاصیت styles، بر اساس ذکر خواص CSS، به صورت خواصی camel-case است؛ مانند fontSize. در اینجا عدد 50 توسط react به صورت خودکار به 50px تبدیل می‌شود.
اعمال این styles نمونه، یک چنین خروجی را به همراه خواهد داشت:


مزیت تعریف شیء styles به صورت یک خاصیت در کلاس، امکان استفاده‌ی مجدد از آن در سایر المان‌ها است. اگر چنین چیزی مدنظر شما نیست، می‌توان این شیء را به صورت inline هم تعریف کرد:
<button style={{ fontSize: 30 }} className="btn btn-secondary btn-sm">
در اینجا، ابتدا یک {} درج می‌شود تا بیانگر نمایش دریافت یک عبارت معتبر جاوا اسکریپتی باشد. سپس داخل آن یک {} دیگر نیز قرار گرفته‌است که بیانگر تعریف یک شیء جاوا اسکریپتی است و در این حالت باید با نحوه‌ی تشکیل عناصر شیء style مورد نظر React که به صورت caml-case هستند، تطابق داشته باشد.


مقدار دهی پویای ویژگی className عناصر در عبارات JSX

در ادامه می‌خواهیم اگر مقدار count مساوی صفر بود، span ای که هم اکنون با یک badge آبی (با کلاس badge-primary) نمایش داده می‌شود، زرد رنگ (با کلاس badge-warning) شود و در غیراینصورت آبی رنگ. بنابراین می‌خواهیم بر اساس مقدر count، مقدار کلاس‌های انتسابی به className را به صورت پویا تغییر دهیم و این الگویی است که در برنامه‌های واقعی بسیار استفاده می‌شود:
  render() {
    let classes = "badge m-2 badge-";
    classes += this.state.count === 0 ? "warning" : "primary";

    return (
      <React.Fragment>
        <img src={this.state.imageUrl} alt="" />
        <span style={this.styles} className={classes}>
          {this.formatCount()}
        </span>
        <button style={{ fontSize: 30 }} className="btn btn-secondary btn-sm">
          Increment
        </button>
      </React.Fragment>
    );
برای این منظور متغیر classes را تعریف کرده‌ایم که در ابتدا با مقادیری که ثابت هستند، مقدار دهی شده‌اند. سپس بر اساس مقدار this.state.count، مقدار مشخص warning و یا primary به این رشته افزوده می‌شود. در آخر هم از این متغیر به صورت className={classes} استفاده شده‌است؛ با این خروجی:


البته باید دقت داشت که می‌توان منطق تشکیل متغیر classes را به یک متد، جهت خلوت سازی متد render نیز منتقل کرد. برای این کار، دو سطر مرتبط با متغیر classes را در VSCode انتخاب کنید. سپس یک آیکن لامپ مانند ظاهر می‌شود که با کلیک بر روی آن، منوی extract to method نیز قابل انتخاب است:
  render() {
    let classes = this.getBadgeClasses();
    // ...
  }

  getBadgeClasses() {
    let classes = "badge m-2 badge-";
    classes += this.state.count === 0 ? "warning" : "primary";
    return classes;
  }
البته در اینجا می‌توان متغیر classes را نیز حذف کرد و مستقیما متد getBadgeClasses را مورد استفاده قرار داد:
<span style={this.styles} className={this.getBadgeClasses()}>


کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید: sample-04-part01.zip
مطالب
C# 12.0 - Primary Constructors
قابلیتی تحت عنوان Primary Constructors به C# 12 اضافه شده‌است که ... البته جدید نیست! این قابلیت از زمان C# 9، با ارائه‌ی رکوردها، به زبان #C اضافه شد و در طی چند نگارش بعدی، توسعه و تکامل یافت (برای مثال اضافه شدن records for structs به C# 10) تا در C# 12، به کلاس‌های معمولی نیز تعمیم پیدا کرد. این ویژگی را در ادامه با جزئیات بیشتری بررسی می‌کنیم.


Primary Constructors چیست؟

Primary Constructors، قابلیتی است که به C# 12 اضافه شده‌است تا توسط آن بتوان خواص را مستقیما توسط پارامترهای سازنده‌ی یک کلاس تعریف و همچنین مقدار دهی کرد. هدف از آن، کاهش قابل ملاحظه‌ی یکسری کدهای تکراری و مشخص است تا به کلاس‌هایی زیباتر، کم‌حجم‌تر و خواناتر برسیم. برای مثال کلاس متداول زیر را درنظر بگیرید:
public class Employee
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public DateTime HireDate { get; set; }
    public decimal Salary { get; set; }

    public Employee(string firstName, string lastName, DateTime hireDate, decimal salary)
    {
        FirstName = firstName;
        LastName = lastName;
        HireDate = hireDate;
        Salary = salary;
    }
}
در زبان ‍#C، سازنده، متد ویژه‌ای است که در حین ساخت نمونه‌ای از یک کلاس، فراخوانی می‌شود. هدف از آن‌، آغاز و مقدار دهی حالت شیء ایجاد شده‌است که عموما با مقدار دهی خواص آن شیء، انجام می‌شود.
اکنون اگر بخواهیم همین کلاس را با استفاده از ویژگی Primary Constructor اضافه شده به C# 12.0 بازنویسی کنیم، به قطعه کد زیر می‌رسیم:
public class Employee(string firstName, string lastName, DateTime hireDate, decimal salary)
{
    public string FirstName { get; set; } = firstName;
    public string LastName { get; set; } = lastName;
    public DateTime HireDate { get; set; } = hireDate;
    public decimal Salary { get; set; } = salary;
}
و نحوه‌ی نمونه سازی از آن به صورت زیر است:
var employee = new Employee("John", "Doe", new DateTime(2020, 1, 1), 50000);

یک نکته: اگر از Rider و یا ReSharper استفاده می‌کنید، یک چنین Refactoring توکاری جهت سهولت کار، به آن‌ها اضافه شده‌است و به سرعت می‌توان این تبدیلات را توسط آن‌ها انجام داد.




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

نکته 1: هیچ ضرورتی وجود ندارد که به همراه یک primary constructor، خواصی هم مانند مثال فوق ارائه شوند؛ چون پارامترهای آن در تمام اعضای این کلاس، به همین شکل، قابل دسترسی هستند. در این مثال صرفا جهت بازسازی کد قبلی، این خواص اضافی را مشاهده می‌کنید. یعنی اگر تنها قرار بود، کار تزریق وابستگی‌ها صورت گیرد که عموما به همراه تعریف فیلدهایی جهت انتساب پارامترهای متد سازنده به آن‌ها است، استفاده از یک primary constructor، کدهای فوق را بیش از این هم فشرده‌تر می‌کرد و ... یک سطری می‌شد.

نکته 2: استفاده از پارامترهای سازنده‌ی اولیه، صرفا جهت مقدار دهی خواص عمومی یک کلاس، یک code smell هستند! چون می‌توان یک چنین کارهایی را به نحو شکیل‌تری توسط required properties معرفی شده‌ی در C# 11، پیاده سازی کرد.


بررسی تاریخچه‌ی primary constructors

همانطور که در مقدمه‌ی بحث نیز عنوان شد، primary constructors قابلیت جدیدی نیست و برای نمونه به همراه C# 9 و مفهوم جدید رکوردهای آن، ارائه شد:
public record class Book(string Title, string Publisher);
مثال فوق که به positional syntax هم معروف است، به همراه بکارگیری primary constructors است. در اینجا کامپایلر به صورت خودکار، کار تولید کدهای خواص متناظر را که از نوع get و init دار هستند، انجام می‌دهد. در این حالت به علت استفاده از init accessors، پس از نمونه سازی شیءای از آن، دیگر نمی‌توان مقدار خواص متناظر را تغییر داد.
پس از آن در C# 10، این توسعه ادامه یافت و به امکان تعریف record structها، بسط یافت که در اینجا هم قابلیت تعریف primary constructors وجود دارد:
public record struct Color(int R, int G, int B);
که البته در این حالت برخلاف record classها، کامپایلر، کدی را که برای خواص تولید می‌کند، get و set دار است. در اینجا اگر نیاز است به همان حالت خواص get و init دار رسید، می‌توان یک readonly record struct را تعریف کرد.

پس از این مقدمات، اکنون در C# 12 نیز می‌توان primary constructors را به تمام کلاس‌ها و structهای معمولی هم اعمال کرد؛ با این تفاوت که در اینجا برخلاف رکوردها، کدهای خواص‌های متناظر، به صورت خودکار تولید نمی‌شوند و اگر به آن‌ها نیاز دارید، باید آن‌ها را همانند مثال ابتدای بحث، خودتان به صورت دستی تعریف کنید.


primary constructors کلاس‌ها و structهای معمولی، با primary constructors رکوردها یکی نیست

در C# 12 و به همراه معرفی primary constructors مخصوص کلاس‌ها و structهای معمولی آن، از روش متفاوتی برای دسترسی به پارامترهای تعریف شده، استفاده می‌کند که به آن capturing semantics هم می‌گویند. در این حالت پارامترهای تعریف شده‌ی در یک primary constructor، توسط هر عضوی از آن کلاس قابل استفاده‌است که یکی از کاربردهای آن، ساده کردن تعاریف تزریق وابستگی‌ها است. در این حالت دیگر نیازی نیست تا ابتدا یک فیلد را برای انتساب به پارامتر تزریق شده تعریف کرد و سپس از آن فیلد، استفاده نمود؛ مستقیما می‌توان با همان پارامتر تعریف شده، در متدها و اعضای کلاس، کار کرد.
برای مثال سرویس زیر را که از تزریق وابستگی‌ها، در سازنده‌ی خود استفاده می‌کند، درنظر بگیرید:
public class MyService
{
    private readonly IDepedent _dependent;
  
    public MyService(IDependent dependent)
    {
        _dependent = dependent;
    }
  
    public void Do() 
    {
        _dependent.DoWork();
    }
}
این کلاس در C# 12 به صورت زیر خلاصه شده و پارامتر dependent تعریف شده‌ی در سازنده‌ی اولیه‌ی آن، به همان شکل و بدون نیاز به کد اضافی، در سایر متدهای این کلاس قابل استفاده‌است:
public class MyService(IDependent dependent)
{
    public void Do() 
    {
        dependent.DoWork();
    }
}

البته مفهوم Captures هم در زبان #C جدید نیست و در ابتدا به همراه anonymous methods و بعدها به همراه lambda expressions، معرفی و بکار گرفته شد. برای مثال درون یک lambda expression، اگر از متغیری خارج از آن lambda expressions استفاده شود، کامپایلر یک capture از آن متغیر را تهیه کرده و استفاده می‌کند.

بنابراین به صورت خلاصه primary constructors در رکوردها، با هدف تعریف خواص عمومی فقط خواندنی، ارائه شدند؛ اما primary constructors ارائه شده‌ی در C# 12 که اینبار قابل اعمال به کلاس‌ها و structs معمولی است، بیشتر هدف ساده سازی تعریف کدهای تکراری private fields را دنبال می‌کند. برای نمونه این کدی است که کامپایلر برای primary constructor مثال ابتدای بحث تولید می‌کند و در اینجا نحوه‌ی تولید خودکار این فیلدهای خصوصی را مشاهده می‌کنید:
using System;
using System.Diagnostics;
using System.Runtime.CompilerServices;

namespace CS8Tests
{
  [NullableContext(1)]
  [Nullable(0)]
  public class Employee
  {
    [CompilerGenerated]
    [DebuggerBrowsable(DebuggerBrowsableState.Never)]
    private string <FirstName>k__BackingField;
    [CompilerGenerated]
    [DebuggerBrowsable(DebuggerBrowsableState.Never)]
    private string <LastName>k__BackingField;
    [CompilerGenerated]
    [DebuggerBrowsable(DebuggerBrowsableState.Never)]
    private DateTime <HireDate>k__BackingField;
    [CompilerGenerated]
    [DebuggerBrowsable(DebuggerBrowsableState.Never)]
    private Decimal <Salary>k__BackingField;

    public Employee(string firstName, string lastName, DateTime hireDate, Decimal salary)
    {
      this.<FirstName>k__BackingField = firstName;
      this.<LastName>k__BackingField = lastName;
      this.<HireDate>k__BackingField = hireDate;
      this.<Salary>k__BackingField = salary;
      base..ctor();
    }

    public string FirstName
    {
      [CompilerGenerated] get
      {
        return this.<FirstName>k__BackingField;
      }
      [CompilerGenerated] set
      {
        this.<FirstName>k__BackingField = value;
      }
    }

    public string LastName
    {
      [CompilerGenerated] get
      {
        return this.<LastName>k__BackingField;
      }
      [CompilerGenerated] set
      {
        this.<LastName>k__BackingField = value;
      }
    }

    public DateTime HireDate
    {
      [CompilerGenerated] get
      {
        return this.<HireDate>k__BackingField;
      }
      [CompilerGenerated] set
      {
        this.<HireDate>k__BackingField = value;
      }
    }

    public Decimal Salary
    {
      [CompilerGenerated] get
      {
        return this.<Salary>k__BackingField;
      }
      [CompilerGenerated] set
      {
        this.<Salary>k__BackingField = value;
      }
    }
  }
}
بنابراین آیا پارامترهای سازنده‌ی اولیه، به صورت خواص تعریف می‌شوند و قابلیت تغییر میدان دید آن‌ها میسر است؟ پاسخ: خیر. این پارامترها توسط کامپایلر، به صورت فیلدهای خصوصی در سطح کلاس، تعریف و استفاده می‌شوند. یعنی تمام اعضای کلاس، البته منهای سازنده‌های ثانویه، به این پارامترها دسترسی دارند. همچنین، این تولید کد هم بهینه‌است و صرفا برای پارامترهایی انجام می‌شود که واقعا در کلاس استفاده شده باشند؛ درغیر اینصورت، فیلد خصوصی متناظری برای آن‌ها تولید نخواهد شد.

یک نکته: برای مشاهده‌ی یک چنین کدهایی می‌توانید از منوی Tools->IL Viewer برنامه‌ی Rider استفاده کرده و در برگه‌ی ظاهر شده، گزینه‌ی #Low-Level C آن‌را انتخاب نمائید.


امکان تعریف سازنده‌های دیگر، به همراه سازنده‌ی اولیه

اگر به کدهای #Low-Level C تولیدی فوق دقت کنید، این کلاس، به همراه یک سازنده‌ی خالی بدون پارامتر (parameter less constructor) نیست و سازنده‌ی پیش‌فرضی (default constructor) برای آن درنظر گرفته نشده‌است ... اما اگر کلاسی به همراه یک primary constructor تعریف شد، می‌توان با استفاده از واژه‌ی کلیدی this، سازنده‌ی ثانویه‌ای را هم برای آن تعریف کرد:
public class Person(string firstName, string lastName) 
{
    public Person() : this("John", "Smith") { }
    public Person(string firstName) : this(firstName, "Smith") { }
    public string FullName => $"{firstName} {lastName}";
}
در اینجا نحوه‌ی تعریف یک Default constructor بدون پارامتر را هم ملاحظه می‌کنید.


امکان ارث‌بری و تعریف سازنده‌ی اولیه

مثال زیر را درنظر بگیرید که در آن کلاس مشتق شده‌ی از کلاس User، یک سازنده‌ی اولیه را تعریف کرده:
public class User
{
    public User(string firstName, string lastName) { }
}

public class Editor(string firstName, string lastName) : User
{
}
در این حالت برنامه با خطای «Base class 'CS8Tests.User' does not contain parameterless constructor» کامپایل نمی‌شود. عنوان می‌کند که اگر کلاس مشتق شده می‌خواهد سازنده‌ی اولیه‌ای داشته باشد، باید کلاس پایه را به همراه یک سازنده‌ی پیش‌فرض بدون پارامتر تعریف کنید.
البته این محدودیت با structها وجود ندارد؛ چون structها، value type هستند و همواره به صورت پیش‌فرض، به همراه یک سازنده‌ی پیش فرض بدون پارامتر، تولید می‌شوند.
یک مثال: قطعه کد متداول ارث‌بری زیر را درنظر بگیرید که در آن، کلاس مشتق شده به کمک واژه‌ی کلید base، امکان تعریف سازنده‌ی جدیدی را یافته و یکی از پارامترهای سازنده‌ی کلاس پایه را مقدار دهی می‌کند:
public class Automobile
{
    public Automobile(int wheels, int seats)
    {
        Wheels = wheels;
        Seats = seats;
    }

    public int Wheels { get; }
    public int Seats { get; }
}

public class Car : Automobile
{
    public Car(int seats) : base(4, seats)
    {
    }
}
این تعاریف در C# 12 به صورت زیر خلاصه می‌شوند:
public class Automobile(int wheels, int seats)
{
    public int Wheels { get; } = wheels;
    public int Seats { get; } = seats;
}

public class Car(int seats) : Automobile(4, seats);

و یا یک نمونه مثال دیگر آن به صورت زیر است که در آن، ذکر بدنه‌ی کلاس در C# 12، الزامی ندارد:
public class MyBaseClass(string s); // no body required

public class Derived(int i, string s, bool b) : MyBaseClass(s)
{
    public int I { get; set; } = i;
    public string B => b.ToString();
}


توصیه به پرهیز از double capturing

با مفهوم capture در این مطلب آشنا شدیم. در مثال زیر دوبار از پارامتر سازنده‌ی age، در دو قسمت عمومی شده، استفاده شده‌است:
public class Human(int age)
{
    // initialization
    public int Age { get; set; } = age;

    // capture
    public string Bio => $"My age is {age}!";
}
در این حالت ممکن است استفاده کننده در طول برنامه، با وضعیت ناخواسته‌ی زیر مواجه شود:
var p = new Human(42);
Console.WriteLine(p.Age); // Output: 42
Console.WriteLine(p.Bio); // Output: My age is 42!

p.Age++;
Console.WriteLine(p.Age); // Output: 43
Console.WriteLine(p.Bio); // Output: My age is 42! // !
در اینجا پس از افزودن مقداری به خاصیت عمومی Age، زمانیکه به مقدار عبارت Bio مراجعه می‌شود، خروجی قبلی را دریافت می‌کنیم!
درک بهتر آن، نیاز به #Low-Level C کلاس Human را دارد:
using System.Diagnostics;
using System.Runtime.CompilerServices;

namespace CS8Tests
{
  [NullableContext(1)]
  [Nullable(0)]
  public class Human
  {
    [CompilerGenerated]
    [DebuggerBrowsable(DebuggerBrowsableState.Never)]
    private int <age>P;
    [CompilerGenerated]
    [DebuggerBrowsable(DebuggerBrowsableState.Never)]
    private int <Age>k__BackingField;

    public Human(int age)
    {
      this.<age>P = age;
      this.<Age>k__BackingField = this.<age>P;
      base..ctor();
    }

    public int Age
    {
      [CompilerGenerated] get
      {
        return this.<Age>k__BackingField;
      }
      [CompilerGenerated] set
      {
        this.<Age>k__BackingField = value;
      }
    }

    public string Bio
    {
      get
      {
        DefaultInterpolatedStringHandler interpolatedStringHandler = new DefaultInterpolatedStringHandler(11, 1);
        interpolatedStringHandler.AppendLiteral("My age is ");
        interpolatedStringHandler.AppendFormatted<int>(this.<age>P);
        interpolatedStringHandler.AppendLiteral("!");
        return interpolatedStringHandler.ToStringAndClear();
      }
    }
  }
}
همانطور که مشاهده می‌کنید، کامپایلر، پارامتر age را دوبار، جداگانه capture کرده‌است:
public Human(int age)
{
   this.<age>P = age;
   this.<Age>k__BackingField = this.<age>P;
   base..ctor();
}
به همین جهت است که ++p.Age، فقط بر روی یکی از فیلدهای capture شده تاثیر داشته و بر روی دیگری خیر. به این مورد، double capturing گفته می‌شود و توصیه شده از آن پرهیز کنید و بجای استفاده‌ی دوباره از پارامتر age، از خود خاصیت Age استفاده نمائید.