نظرات مطالب
ارتقاء به ASP.NET Core 1.0 - قسمت 19 - بومی سازی
روش انتقال منابع مرتبط با data annotations و ViewModelها به یک اسمبلی دیگر
- فرض کنید یک class library مخصوص NET Core. را به نام Core1RtmTestResources.ExternalResources جهت درج منابع تهیه کرده‌اید و پوشه‌ی Resources را از پروژه‌ی اصلی به آن انتقال داده‌اید (بدون هیچ تغییر نامی).
- نیازی نیست تا قسمت options.ResourcesPath کلاس آغازین برنامه را تغییر دهید و همان مقدار Resources در این حالت هم کار می‌کند.
- در اینجا دو مورد باید تغییر کنند:
الف) باید مشخص کنید که این اطلاعات قرار است از کدام اسمبلی خوانده شود:
public void ConfigureServices(IServiceCollection services)
{
  services.AddLocalization(options =>
  {
   options.ResourcesPath = "Resources";
  });
 
  services.AddMvc()
    .AddViewLocalization(LanguageViewLocationExpanderFormat.Suffix)
    .AddDataAnnotationsLocalization(options=>
    {
      options.DataAnnotationLocalizerProvider = (type, factory) =>
      {
       return factory.Create(
        baseName: type.FullName /* بر این اساس نام فایل منبع متناظر باید به همراه ذکر فضای نام پایه آن هم باشد */,
        location: "Core1RtmTestResources.ExternalResources" /*نام اسمبلی ثالث*/);
      };
    });
در اینجا location به نام اسمبلی اشاره می‌کند که حاوی فایل resx مرتبط است. حالت پیش فرض آن (بدون این تنظیمات)، به اسمبلی اشاره می‌کند که کلاس ViewModel در آن قرار گرفته‌است.
ب) چون در این تنظیم baseName به FullName تنظیم شده‌است، نام فایل منبع باید کامل باشد؛ یعنی باید به همراه فضای نام اصلی اسمبلی هم باشد. مثلا اگر قبلا چنین نامی را داشته
ViewModels.Account.RegisterViewModel.fa.resx 
الان باید فضای نام مرتبط را هم داشته باشد (نام کامل نوع آن کلاس):
Core1RtmTestResources.ViewModels.Account.RegisterViewModel.fa.resx 
نظرات مطالب
اصول طراحی شی گرا SOLID - #بخش دوم اصل OCP
با سلام؛ در کد public class EmployeeManagerDB : EmployeeDB در کلاس دوم در تابع Select محتویاتش با تابع select بالا باید یکی باشد؟ یا میتونه متفاوت باشه؟ این عمل به نظر من غیر معقول میاد. یا اینکه من متوجه موضوع نشدم.
نظرات مطالب
معرفی JSON Web Token
باتشکر از پاسخ شما
آیا امکان استفاده از asp.net Identity به همراه jwt وجود دارد؟
  مثلا وقتی در پروژه ای که مانند پروژه  Decision کلاس user به صورت( public class User : IdentityUser ) تعریف شده ایا دیگر  امکان استفاده از jwt برای login هست؟
اگر این امکان وجود ندارد استفاده از کدام پیشنهاد میشود؟
مطالب
کامپوننت‌ها در Vue.js
پیش‌تر در سایت مطالبی در رابطه با فریم‌ورک Vue.js منتشر شده‌است. در این مطلب می‌خواهیم نگاهی بر مفهوم کامپوننت‌ها در Vue بیندازیم و نحوه‌ی استفاده از آنها را بررسی کنیم.

قبل از معرفی کامپوننت‌ها اجازه دهید سیستم template در ویو را بررسی کنیم. سیستم template ویو براساس سینتکس HTML است:
new Vue({
  el: '#app',
  template: '<div>Hello DNT</div>'
});
البته استفاده از template کاملاً اختیاری است. بجای آن می‌توانیم از تابع رندر (همانند React) نیز استفاده کنیم:
new Vue({
    el: '#app',
    data() {
        return {
            blogTitle: 'DNT'
        }
    },
    render: function (createElement) {
        return createElement('h1', this.blogTitle)
    }
});

ایجاد یک کامپوننت ساده:
Vue.component('child', {
    template: '<div>Hello DNT users</div>'
});
در اینجا برای ایجاد یک کامپوننت، از تابع component استفاده کرده‌ایم؛ پارامتر اول این تابع، نام کامپوننت است و پارامتر دوم نیز یک شیء است. درون این شیء می‌توانیم قالب کامپوننت را تعیین کنیم. برای کامپوننت نیز می‌توانیم یک پارامتر ورودی را تعیین کنیم. اینکار را توسط مفهومی به نام Props می‌توانیم انجام دهیم:
Vue.component('child', {
    props: ['text'],
    template: `<div> {{ text }} </div>`
});

new Vue({
    el: '#app',
    data() {
        return {
            message: 'Hello DNT!'
        }
    }
});
اکنون می‌توانیم پارامتر موردنظر را به text، به عنوان ورودی کامپوننت ارسال کنیم (در واقع دیتای موجود در parent را به کامپوننت child ارسال کرده‌ایم):
<child :text="message"></child>

اعتبارسنجی پراپرتی‌ها
برای props می‌توانیم اعتبارسنجی را نیز انجام دهیم:
Vue.component('blogPost', {
    props: {
        post: {
            type: Object,
            required: true
        }
    },
    template: `<div>
                    <h1>{{ post.title }}</h1>
                    <p>{{ post.body }}</p>
               </div>`
});
در اینجا نوع خاصیت post باید شیء باشد و همچنین آن را به صورت required تعریف کرده‌ایم. در این حالت اگر مقداری به غیر از شیء را به آن ارسال کنیم، خطای زیر را در کنسول دریافت خواهیم کرد:
[Vue warn]: Invalid prop: type check failed for prop "post". Expected Object, got String.

found in

---> <BlogPost>
       <Root>

همچنین می‌توانیم نوع اعتبارسنجی را به صورت سفارشی نیز تعیین کنیم:
Vue.component('blogPost', {
    props: {
        post: {
            type: Object,
            required: true,
            validator: obj => {
                const titleIsValid = typeof obj.title === 'string';
                const bodyIsValid = typeof obj.body === 'string';
                const isValid = titleIsValid && bodyIsValid;
                if (!isValid) {
                    console.warn("prop is not valid");
                    return false;
                }
                return true;
            }
        }
    },
    template: `<div>
                    <h1>{{ post.title }}</h1>
                    <p>{{ post.body }}</p>
               </div>`
});

تعیین مقدار پیش‌فرض برای پراپرتی
برای یک prop می‌توانیم مقدار پیش‌فرضی را نیز تعیین کنیم. یعنی در صورت عدم ارسال شیء می‌توانیم تعیین کنیم که چه شیء‌ایی در حالت پیش‌فرض نمایش داده شود:
Vue.component('blogPost', {
    props: {
        post: {
            type: Object,
            validator: obj => {
                const titleIsValid = typeof obj.title === 'string';
                const bodyIsValid = typeof obj.body === 'string';
                const isValid = titleIsValid && bodyIsValid;
                if (!isValid) {
                    console.warn("prop is not valid");
                    return false;
                }
                return true;
            },
            default: function() {
                return {
                    title: 'Vue is fun!',
                    body: 'Vue is fun..................'
                }
            }
        }
    },
    template: `<div>
                    <h1>{{ post.title }}</h1>
                    <p>{{ post.body }}</p>
               </div>`
});

استفاده از دیتا درون کامپوننت
درون یک کامپوننت نیز می‌توانیم یکسری دیتا را تعریف کنیم. اما باید در نظر داشته باشید که هر وهله از کامپوننت، scope مجزای خودش را دارد. در نتیجه پیشنهاد میشود دیتا حتماً به صورت یک تابع تعریف شود و همچنین داده‌های درون آن نیز در scope کامپوننت تعریف شوند:
data: function () {
    return {
        stars: 5,
        hover: 5
    }
},
زیرا در صورت تعریف داده‌ها در خارج از scope کامپوننت، محتویات دیتا برای دیگر وهله‌های کامپوننت‌ها نیز به اشتراک گذاشته می‌شود. به عنوان مثال فرض کنید درون کامپوننت بلاگ‌پست، یک سیستم امتیاز دهی قرار داده‌ایم:
var dt = {
    stars: 5,
    hover: 5
};
Vue.component('blogPost', {
    data: function() {
        return dt;
    },
    props:  // as before...,

    template: `<div class="blog-post">
                    <h1>{{ post.title }}</h1>
                    <p>{{ post.body }}</p>
                    <div class="star-wrap">
                        <span v-for="n in 5"
                            class="star"
                            :class="{ full: hover >= n+1 }"
                            @click="stars = n+1"
                            @mouseover="hover = n+1"
                            @mouseout="hover = stars"
                        ></span>
                    </div>
               </div>`
});
در این حالت خروجی زیر را خواهیم داشت:



برای رفع این مشکل کافی است به اینصورت دیتا را تعریف کنیم:

Vue.component('blogPost', {
    data: function() {
        return {
             stars: 5,
             hover: 5
        }
    },
    props:  // as before...,
    template: // as before
});


تغییر دیتا درون کامپوننت‌ها

تا اینجا توانستیم از کامپوننت والد داده‌هایی را به کامپوننت‌های فرزند ارسال کنیم. اکنون می‌خواهیم قابلیت تغییر دیتای تعریف شده‌ی درون کامپوننت والد را درون کامپوننت‌ها نیز داشته باشیم:

Vue.component('child', {
    props: ['message'],
    methods: {
        changeName() {
            this.message = "New Name!..."
        }
    },
    template: '#child-template'
});

new Vue({
    el: '#app',
    data() {
        return {
            name: 'DNT!'
        }
    }
});

تمپلیت کامپوننت فوق نیز به صورت x-template درون DOM تعریف شده است:

<script type="text/x-template" id="child-template">
    <div>
        <p>{{ message }}</p>
        <button @click="changeName">Change Name</button>
    </div>
</script>


فراخوانی کامپوننت نیز به اینصورت می‌باشد:

<div id="app">
    <child :message="name"></child>
</div>

همانطور که مشاهده می‌کنید، دیتای name را از طریق ویژگی message توانسته‌ایم به کامپوننت child ارسال کنیم. درون تمپلیت آن نیز یک دکمه را برای تغییر مقدار این ویژگی تعریف کرده‌ایم. تغییر این ویژگی نیز یک assignment ساده است. اما اگر بر روی دکمه‌ی Change Name کلیک کنید، هشدار زیر را درون کنسول مشاهده خواهید کرد:

[Vue warn]: Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop's value. Prop being mutated: "message"

found in

---> <Child>
       <Root>

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

changeName() {
    this.message = "New Name!...",
    this.$emit("change-name", this.message);
}

برای تگ child نیز این ایونت را اضافه خواهیم کرد:

<child :message="name" @change-name="name = $event"></child>

در اینحالت با تغییر ویژگی message، مقدار دیتای name نیز بلافاصله تغییر پیدا خواهد کرد.


Slots

Slot یک روش عالی برای جایگزینی محتوای درون یک کامپوننت است. فرض کنید می‌خواهیم کامپوننت‌مان به صورت زیر باشد:

<modal>
    Hello
</modal>

در اینحالت باید درون تمپلیت مکان قرارگیری Hello را تعیین کنیم. اینکار را می‌توانیم با قرار دادن تگ slot انجام دهیم:

Vue.component('modal', {
    template: `
            ...
            <div class="modal-body">
                <slot></slot>
            </div>
            ...
    `
});

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

Vue.component('modal', {
    template: `
    <div class="modal fade" id="detailsModal" tabindex="-1" role="dialog" aria-labelledby="detailsModalLabel" aria-hidden="false">
        <div class="modal-dialog" role="document">
            <div class="modal-content">
                <div class="modal-header">
                    <h5 class="modal-title" id="detailsModalLabel">
                        <slot name="title"></slot>
                    </h5>
                    <button type="button" class="close" data-dismiss="modal" aria-label="Close">
                    <span aria-hidden="true">&times;</span>
                    </button>
                </div>
                <div class="modal-body">
                    <slot name="body"></slot>
                </div>
                <div class="modal-footer">
                    <slot name="footer"></slot>
                </div>
            </div>
        </div>
        </div>
    `
});

اکنون می‌توانیم محتوای مورد نظر را برای قرارگیری درون slotها تعیین کنیم:

<modal>
    <template slot="title">Title</template>
    <template slot="body">Lorem ipsum dolor sit amet.</template>
    <template slot="footer">
        <button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
        <button type="button" class="btn btn-primary">Save changes</button>
    </template>
</modal>


بازخوردهای دوره
انتقال خودکار Data Annotations از مدل‌ها به ViewModelهای ASP.NET MVC به کمک AutoMapper
روشی دیگر جهت انتقال Data Annotations از Model به ViewModel

در ASP MVC  با MetadataTypeAttribute .
 روش کار به این صورت است که کلاس ViewModel را به Attribute (MetadataType ) مزین می‌کنیم که این Attribute  در سازنده خود تایپ Model را دریافت می‌کند و همچنین  در فضای نامی زیر قرار دارد 
System.ComponentModel.DataAnnotations 
    public class Student
    {
        public int Id { get; set; }

        [Required(ErrorMessage = "نام ضروری است")]
        [Display(Name = "نام")]
        public string Name { get; set; }
    }

ViewModel
    [MetadataType(typeof(Student))]
    public class StudentViewModel
    {
        public int Id { get; set; }
        public string Name { get; set; }
    }

در ASP MVC Core  هم روش کار به همین صورت است بجای MetadataType از ModelMetadataType  استفاده کنید .
نظرات مطالب
ارتقاء به ASP.NET Core 1.0 - قسمت 5 - فعال سازی صفحات مخصوص توسعه دهنده‌ها
ممنون
در کنترلر Home اکشن Error : 
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
        public IActionResult Error(int? id)
        {
            string ErrorText = "";
            ErrorText = (id.Value == 401 || id.Value == 403) ? "Access Denied"
            : (id.Value == 404 ? "Not Found" : "Error Occured");

            return View(new ErrorViewModel
            {
                RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier,
                ErrorId = id,
                ErrorText = ErrorText
            });
        }
در Error.cshtml : 
<h1 class="text-danger">@Model.ErrorId : @Model.ErrorText</h1>
کلاس مدل :
public class ErrorViewModel
    {
        public string RequestId { get; set; }

        public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);

        public int? ErrorId { get; set; }

        public string ErrorText { get; set; }
    }

مطالب
آشنایی با Feature Toggle - بخش دوم
در بخش اول آشنایی با Feature Toggle، با مفهوم Feature Toggle آشنا شدیم و در بخش پایانی مقاله، به معرفی یکی از کتابخانه‌های نوشته شده توسط مایکروسافت پرداختیم.
در این مقاله به صورت کاربردی‌تر به استفاده از کتابخانه‌ی مورد استفاده می‌پردازیم. برای ادامه نیاز هست بسته‌ی زیر را که مخصوص برنامه‌های مبتنی بر ASP.NET CORE است نصب نمایید :
Install-package Microsoft.FeatureManagement.AspNetCore

فرض کنید یک قابلیت را تحت عنوان Chat پیاده سازی کرده و با توجه به تکنولوژی‌هایی که استفاده کرده‌اید، فقط با مرورگر کروم سازگار هست و شما باید این قابلیت را فقط برای کاربرانی که مروگر کروم دارند، فعال نمایید؛ در غیر اینصورت غیرفعال و در دسترس کاربران نباشد. برای این منظور فرض میکنیم کنترلر زیر مسئول تمام کارهای مربوط به قابلیت چت می‌باشد :
[FeatureGate("chat")]
public class ChatController : Controller
{
      public IActionResult Index()
      {
          // do sth
      }
}
همانطور که در کد بالا قابل مشاهده می‌باشد ، کنترلر با یک Attribute مزین شده‌است که از Attribute‌های توکار کتابخانه می‌باشد. با استفاده از این ویژگی می‌توانیم یک کنترلر و یا اکشن متد را کلا از دسترس خارج کنیم (اگر مقدار این قابلیت در appsetting.json غیرفعال باشد).
اگر درخواستی به کنترلر Chat ارسال شود و قابلیت چت در فایل appsetting.json غیرفعال باشد (طبق روش‌هایی که در مقاله قبل توضیح داده شد) کاربر با خطای 404 مواجه خواهد شد.
میتوان به FeatuteGate اسم چندین قابلیت را داد و اگر همه‌ی آنها فعال باشند، کنترلر/اکشن در دسترس خواهد بود؛ در غیر اینصورت خطای 404 دریافت می‌شود.
[FeatureGate("feature1", "feature2")]
public class ChatController : Controller
 {
        public IActionResult Index()
        {
            // do sth
        }
 }
  "FeatureManagement": {
    "feature1": true,
    "feature2": false
  },

 برای حالتیکه نیاز هست اسم چندین قابلیت را به FeatureGate بدهیم، میتوانیم تعیین کنیم که آیا همه‌ی قابلیت‌ها باید فعال باشند تا کنترلر/ اکشن در دسترس باشد یا خیر؟ برای این منظور یک Enum توکار، به اسم RequirementType به همراه این کتابخانه وجود دارد که کار آن And/OR است:
public enum RequirementType
    {
        //
        // Summary:
        //     The enabled state will be attained if any feature in the set is enabled.
        Any = 0,
        //
        // Summary:
        //     The enabled state will be attained if all features in the set are enabled.
        All = 1
    }
همانطور که از توضیحات آن قابل تشخیص است، در زمان استفاده از FeatureGate میتوانیم با استفاده از این enum مشخص کنیم که اگر فقط یکی از قابلیت‌ها فعال بود، کنترلر/اکشن موردنظر فعال و در دسترس باشد، در غیر اینصورت از دسترس خارج شود و تمامی درخواست‌ها را با خطای 404 پاسخ دهد.
نمونه‌ای از استفاده از این enum به صورت زیر است:
 [FeatureGate(RequirementType.Any,"feature1", "feature2","feature3")]
 public class ChatController : Controller
 {
        public IActionResult Index()
        {
            // do sth
        }
 }

تگ <feature>
تا اینجا موفق شدیم یک کنترلر و یا اکشن متد را غیرفعال و از دسترس خارج نماییم. فرض کنید قابلیت چت بنا بر تنظیمات انجام شده، غیرفعال می‌باشد، منتها در منوی سایت همچنان لینک آن در حال نمایش است و کاربران میتوانند لینک را کیک کنند (و در نتیجه با خطای 404 مواجه می‌شوند). برای غیر فعال کردن المان‌هایی (تگ) مربوط به یک قابلیت، می‌توانیم از tag helper مربوطه به صورت زیر استفاده نماییم :
@addTagHelper *, Microsoft.FeatureManagement.AspNetCore // put this line in _ViewImports

<feature name="feature1,feature2,feature3">
  <li>
        <a asp-area="" asp-controller="Chat" asp-action="index">Stay in contact</a>
    </li>
</feature>
 لازم به ذکر هست اینجا هم می‌توان با مقداردهی خصویت requirement با یکی از مقدارهای Any و یا All، مشخص نماییم به صورت And اجرا شود یا خیر.

نوشتن Handler سفارشی
همانطور که در بالا هم بیان شد، اگر یک قابلیت به هر دلیلی غیرفعال باشد، کاربران با خطای 404 مواجه خواهند شد. اگر نیاز داشتید کاربر را به صفحه‌ی دیگری هدایت کنید و یا Status Code بهتری را برگردانید، میتوانید این‌کار را با پیاده سازی یک هندلر سفارشی که اینترفیس IDisabledFeaturesHandler را پیاده سازی میکند، انجام دهید. در زیر یک نمونه پیاده سازی شده را مشاهده می‌کنید:
public class RedirectDisabledFeatureHandler : IDisabledFeaturesHandler
    {
        public Task HandleDisabledFeatures(IEnumerable<string> features, ActionExecutingContext context)
        {
            context.Result = new RedirectResult("url");
            return Task.CompletedTask;
        }
    }
و سپس نیاز هست تا این هندلر را به صورت زیر ثبت نماییم :
  public void ConfigureServices(IServiceCollection services)
   {
            services.AddFeatureManagement().UseDisabledFeaturesHandler(new RedirectDisabledFeatureHandler()); ;
    }
مطالب
بهبودهای کار با Lambdas در C# 9.0
C# 9.0 به همراه دو بهبود جزئی درباره‌ی کار با Lambdas است:
- امکان عدم ذکر بعضی از پارامترهای Lambdas
- امکان تعریف متدهای static anonymous


امکان عدم ذکر بعضی از پارامترهای Lambdas در C# 9.0

مثال زیر را در نظر بگیرید:
Action<object, EventArgs> handler1 = (object obj, EventArgs args) => ShowDialog();
در عبارت lambda تعریف شده، از پارامترهای obj و args استفاده نشده‌است. به همین جهت برای کاهش اخطارهای نمایش داده شده‌ی توسط کامپایلر در C# 9.0 می‌توان آن‌ها را به صورت discard parameters توسط _ معرفی کرد؛ به یکی از دو روش زیر:
Action<object, EventArgs> handler2 = (object _, EventArgs _) => ShowDialog();
// OR
Action<object, EventArgs> handler3 = (_, _) => ShowDialog();
که در یکی، نوع‌ها به همراه discard parameters ذکر شده‌اند و در دومی فقط discard parameters را داریم.

نمونه‌ی دیگر آن در حین تعریف رخ‌دادگردان‌ها است:
button1.Click += (s, e) => ShowDialog();
که اینبار پارامترهای استفاده نشده به صورت زیر قابل بیان هستند:
 button1.Click += (_, _) => ShowDialog();


امکان تعریف Static anonymous functions در C# 9.0

برای کاهش میزان تخصیص حافظه‌ی در حین کار با عبارات lambda ای که از متغیرهای محلی استفاده می‌کنند، اینبار در C# 9.0 می‌توان این عبارات را static تعریف کرد. برای نمونه مثال زیر را درنظر بگیرید:
namespace CS9Features
{
    public class LambdasTests
    {
        public void Test()
        {
            string text = "{0} is a good user !";
            PrintItems(item => string.Format(text, item));
        }

        private void PrintItems(Func<string, string> formatFunc)
        {
            foreach (var item in new[] { "User 1", "User 2" })
            {
                Console.WriteLine(formatFunc(item));
            }
        }
    }
}
در اینجا نحوه‌ی فرمت نهایی اطلاعات نمایش داده شده، توسط یک عبارت lambda تامین می‌شود. اتفاقی که در اینجا رخ می‌دهد، اصطلاحا capture شدن یک متغیر (text در اینجا) توسط یک anonymous function است (همان عبارت lambda نوشته شده). حاصل این capture شدن، ایجاد یک شیء موقتی برای مدیریت آن است که تخصیص حافظه و همچنین سربار عملیاتی اضافه‌ای را به همراه دارد. برای حذف این سربارها در C# 9.0 می‌توان متغیر استفاده شده را const تعریف کرد و سپس عبارت lambda تعریف شده را به صورت static نوشت:
const string text = "{0} is a good user !";
PrintItems(static item => string.Format(text, item));
با تعریف شدن یک عبارت lambda و یا یک anonymous method به صورت static که از تخصیص حافظه‌ی اضافی ایجاد شیء موقتی مدیریت کننده‌ی دسترسی به متغیر text جلوگیری می‌کند، اتفاقات زیر نیز رخ می‌دهند:
- این متد به عنوان static anonymous function شناخته می‌شود.
- دیگر نمی‌تواند دسترسی به حالت scope جاری را داشته باشد. بنابراین دیگر دسترسی به متغیرها، پارامترها و حتی شیء this را هم نخواهد داشت.
- می‌تواند با سایر اعضای استاتیک scope جاری کار کند.
- می‌تواند به تعاریف const مربوط به scope جاری، دسترسی داشته باشد.
نظرات مطالب
اجرای وظایف زمان بندی شده با Quartz.NET - قسمت دوم
یه برنامه نوشتم، که کاربر میتونه زمان رو انتخاب و Job رو ایجاد کنه... لیست Job‌ها هم در CheckedBoxList نشون داده میشه تا کاربر هر کدوم رو که دوست داشتم موقتا Pause و Resume کنه.

job‌ها بدون نقص کار میکنن منتها Job‌ها بعد از Pause و Resume دیگه فعال نمیشن. مشکل از کجاست؟
  public void Run()
        {
...
  chkJobs.Items.Add(job.Key, true);
}

    private void chkJobs_ItemCheck(object sender, ItemCheckEventArgs e)
        {
            int index = e.Index;
            var sch = Scheduler.GetScheduler();
            foreach (object item in chkJobs.Items)
            {
                JobKey key = (JobKey)item;

                if (e.NewValue == CheckState.Unchecked)
                {
                    sch.PauseJob(key);
                }
                else
                {
                        sch.ResumeJob(key);
                }
            }
        }

        public class Jobs : IJob
        {
            public void Execute(IJobExecutionContext context)
            {
                JobDataMap datamap = context.JobDetail.JobDataMap;
                switch (Convert.ToInt32(datamap.GetString("OperationType")))
                {
                    case 1:
                        MessageBox.Show(datamap.GetString("OperationValue"));
                        break;
                }

            }
        }
مطالب
ASP.NET MVC #11

بررسی نکات تکمیلی Model binder در ASP.NET MVC

یک برنامه خالی جدید ASP.NET MVC را شروع کنید و سپس مدل زیر را به پوشه Models آن اضافه نمائید:

using System;

namespace MvcApplication7.Models
{
public class User
{
public int Id { set; get; }
public string Name { set; get; }
public string Password { set; get; }
public DateTime AddDate { set; get; }
public bool IsAdmin { set; get; }
}
}

از این مدل چند مقصود ذیل دنبال می‌شوند:
استفاده از Id به عنوان primary key برای edit و update رکوردها. استفاده از DateTime برای اینکه اگر کاربری اطلاعات بی ربطی را وارد کرد چگونه باید این مشکل را در حالت model binding خودکار تشخیص داد و استفاده از IsAdmin برای یادآوری یک نکته امنیتی بسیار مهم که اگر حین model binding خودکار به آن توجه نشود، سایت را با مشکلات حاد امنیتی مواجه خواهد کرد. سیستم پیشرفته است. می‌تواند به صورت خودکار ورودی‌های کاربر را تبدیل به یک شیء حاضر و آماده کند ... اما باید حین استفاده از این قابلیت دلپذیر به یک سری نکات امنیتی هم دقت داشت تا سایت ما به نحو دلپذیری هک نشود!

در ادامه یک کنترلر جدید به نام UserController را به پوشه کنترلرهای پروژه اضافه نمائید. همچنین نام کنترلر پیش فرض تعریف شده در قسمت مسیریابی فایل Global.asax.cs را هم به User تغییر دهید تا در هربار اجرای برنامه در VS.NET، نیازی به تایپ آدرس‌های مرتبط با UserController نداشته باشیم.
یک منبع داده تشکیل شده در حافظه را هم برای نمایش لیستی از کاربران، به نحو زیر به پروژه اضافه خواهیم کرد:

using System;
using System.Collections.Generic;

namespace MvcApplication7.Models
{
public class Users
{
public IList<User> CreateInMemoryDataSource()
{
return new[]
{
new User { Id = 1, Name = "User1", Password = "123", IsAdmin = false, AddDate = DateTime.Now },
new User { Id = 2, Name = "User2", Password = "456", IsAdmin = false, AddDate = DateTime.Now },
new User { Id = 3, Name = "User3", Password = "789", IsAdmin = true, AddDate = DateTime.Now }
};
}
}
}

در اینجا فعلا هدف آشنایی با زیر ساخت‌های ASP.NET MVC است و درک صحیح نحوه کارکرد آن. مهم نیست از EF استفاده می‌کنید یا NH یا حتی ADO.NET کلاسیک و یا از Micro ORMهایی که پس از ارائه دات نت 4 مرسوم شده‌اند. تهیه یک ToList یا Insert و Update با این فریم ورک‌ها خارج از بحث جاری هستند.

سورس کامل کنترلر User به شرح زیر است:

using System;
using System.Linq;
using System.Web.Mvc;
using MvcApplication7.Models;

namespace MvcApplication7.Controllers
{
public class UserController : Controller
{
[HttpGet]
public ActionResult Index()
{
var usersList = new Users().CreateInMemoryDataSource();
return View(usersList); // Shows the Index view.
}

[HttpGet]
public ActionResult Details(int id)
{
var user = new Users().CreateInMemoryDataSource().FirstOrDefault(x => x.Id == id);
if (user == null)
return View("Error");
return View(user); // Shows the Details view.
}

[HttpGet]
public ActionResult Create()
{
var user = new User { AddDate = DateTime.Now };
return View(user); // Shows the Create view.
}

[HttpPost]
public ActionResult Create(User user)
{
if (this.ModelState.IsValid)
{
// todo: Add record
return RedirectToAction("Index");
}
return View(user); // Shows the Create view again.
}

[HttpGet]
public ActionResult Edit(int id)
{
var user = new Users().CreateInMemoryDataSource().FirstOrDefault(x => x.Id == id);
if (user == null)
return View("Error");
return View(user); // Shows the Edit view.
}

[HttpPost]
public ActionResult Edit(User user)
{
if (this.ModelState.IsValid)
{
// todo: Edit record
return RedirectToAction("Index");
}
return View(user); // Shows the Edit view again.
}

[HttpPost]
public ActionResult Delete(int id)
{
// todo: Delete record
return RedirectToAction("Index");
}
}
}

توضیحات:

ایجاد خودکار فرم‌های ورود اطلاعات

در قسمت قبل برای توضیح دادن نحوه ایجاد فرم‌ها در ASP.NET MVC و همچنین نحوه نگاشت اطلاعات آن‌ها به اکشن متدهای کنترلرها، فرم‌های مورد نظر را دستی ایجاد کردیم.
اما باید درنظر داشت که برای ایجاد Viewها می‌توان از ابزار توکار خود VS.NET نیز استفاده کرد و سپس اطلاعات و فرم‌های تولیدی را سفارشی نمود. این سریع‌ترین راه ممکن است زمانیکه مدل مورد استفاده کاملا مشخص است و می‌خواهیم Strongly typed views را ایجاد کنیم.
برای نمونه بر روی متد Index کلیک راست کرده و گزینه Add view را انتخاب کنید. در اینجا گزینه‌ی create a strongly typed view را انتخاب کرده و سپس از لیست مدل‌ها، User را انتخاب نمائید. Scaffold template را هم بر روی حالت List قرار دهید.
برای متد Details هم به همین نحو عمل نمائید.
برای ایجاد View متناظر با متد Create در حالت HttpGet، تمام مراحل یکی است. فقط Scaffold template انتخابی را بر روی Create قرار دهید تا فرم ورود اطلاعات، به صورت خودکار تولید شود.
متد Create در حالت HttpPost نیازی به View اضافی ندارد. چون صرفا قرار است اطلاعاتی را از سرور دریافت و ثبت کند.
برای ایجاد View متناظر با متد Edit در حالت HttpGet، باز هم مراحل مانند قبل است با این تفاوت که Scaffold template انتخابی را بر روی گزینه Edit قرار دهید تا فرم ویرایش اطلاعات کاربر به صورت خودکار به پروژه اضافه شود.
متد Edit در حالت HttpPost نیازی به View اضافی ندارد و کارش تنها دریافت اطلاعات از سرور و به روز رسانی بانک اطلاعاتی است.
به همین ترتیب متد Delete نیز، نیازی به View خاصی ندارد. در اینجا بر اساس primary key دریافتی، می‌توان یک کاربر را یافته و حذف کرد.



سفارشی سازی Viewهای خودکار تولیدی

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

@Html.ActionLink("Delete", "Delete", new { id=item.Id }

در قسمت قبل هم عنوان شد که اعمال حذف باید بر اساس HttpPost محدود شوند تا بتوان میزان امنیت برنامه را بهبود داد. متد Delete هم در کنترلر فوق تنها به حالت HttpPost محدود شده است. بنابراین ActionLink پیش فرض را حذف کرده و بجای آن فرم و دکمه زیر را قرار می‌دهیم تا اطلاعات به سرور Post شوند:

@using (Html.BeginForm(actionName: "Delete", controllerName: "User", routeValues: new { id = item.Id }))
{
<input type="submit" value="Delete"
onclick="return confirm ('Do you want to delete this record?');" />
}

در اینجا نحوه ایجاد یک فرم، که id رکورد متناظر را به سرور ارسال می‌کند، مشاهده می‌کنید.



علت وجود دو متد، به ازای هر Edit یا Create

به ازای هر کدام از متدهای Edit و Create دو متد HttpGet و HttpPost را ایجاد کرده‌ایم. کار متدهای HttpGet نمایش View‌های متناظر به کاربر هستند. بنابراین وجود آن‌ها ضروری است. در این حالت چون از دو Verb متفاوت استفاده شده، می‌توان متدهای هم نامی را بدون مشکل استفاده کرد. به هر کدام از افعال Get و Post و امثال آن، یک Http Verb گفته می‌شود.



بررسی معتبر بودن اطلاعات دریافتی

کلاس پایه Controller که کنترلرهای برنامه از آن مشتق می‌شوند، شامل یک سری خواص و متدهای توکار نیز هست. برای مثال توسط خاصیت this.ModelState.IsValid می‌توان بررسی کرد که آیا Model دریافتی معتبر است یا خیر. برای بررسی این مورد، یک breakpoint را بر روی سطر this.ModelState.IsValid در متد Create قرار دهید. سپس به صفحه ایجاد کاربر جدید مراجعه کرده و مثلا بجای تاریخ روز، abcd را وارد کنید. سپس فرم را به سرور ارسال نمائید. در این حالت مقدار خاصیت this.ModelState.IsValid مساوی false می‌باشد که حتما باید به آن پیش از ثبت اطلاعات دقت داشت.



شبیه سازی عملکرد ViewState در ASP.NET MVC

در متدهای Create و Edit در حالت Post، اگر اطلاعات Model معتبر نباشند، مجددا شیء User دریافتی، به View بازگشت داده می‌شود. چرا؟
صفحات وب، زمانیکه به سرور ارسال می‌شوند، تمام اطلاعات کنترل‌های خود را از دست خواهد داد (صفحه پاک می‌شود، چون مجددا یک صفحه خالی از سرور دریافت خواهد شد). برای رفع این مشکل در ASP.NET Web forms، از مفهومی به نام ViewState کمک می‌گیرند. کار ViewState ذخیره موقت اطلاعات فرم جاری است برای استفاده مجدد پس از Postback. به این معنا که پس از ارسال فرم به سرور، اگر کاربری در textbox اول مقدار abc را وارد کرده بود، پس از نمایش مجدد فرم، مقدار abc را در همان textbox مشاهده خواهد کرد (شبیه سازی برنامه‌های دسکتاپ در محیط وب). بدیهی است وجود ViewState برای ذخیره سازی این نوع اطلاعات، حجم صفحه را بالا می‌برد (بسته به پیچیدگی صفحه ممکن است به چند صد کیلوبایت هم برسد).
در ASP.NET MVC بجای استفاده از ترفندی به نام ViewState، مجددا اطلاعات همان مدل متناظر با View را بازگشت می‌دهند. در این حالت پس از ارسال صفحه به سرور و نمایش مجدد صفحه ورود اطلاعات، تمام کنترل‌ها با همان مقادیر قبلی وارد شده توسط کاربر قابل مشاهده خواهند بود (مدل مشخص است، View ما هم از نوع strongly typed می‌باشد. در این حالت فریم ورک می‌داند که اطلاعات را چگونه به کنترل‌های قرار گرفته در صفحه نگاشت کند).
در مثال فوق، اگر اطلاعات وارد شده صحیح باشند، کاربر به صفحه Index هدایت خواهد شد. در غیراینصورت مجددا همان View جاری با همان اطلاعات model قبلی که کاربر تکمیل کرده است به او برای تصحیح، نمایش داده می‌شود. این مساله هم جهت بالا بردن سهولت کاربری برنامه بسیار مهم است. تصور کنید که یک فرم خالی با پیغام «تاریخ وارد شده معتبر نیست» مجدا به کاربر نمایش داده شود و از او درخواست کنیم که تمام اطلاعات دیگر را نیز از صفر وارد کند چون اطلاعات صفحه پس از ارسال به سرور پاک شده‌اند؛ که ... اصلا قابل قبول نیست و فوق‌العاده برنامه را غیرحرفه‌ای نمایش می‌دهد.



خطاهای نمایش داده شده به کاربر

به صورت پیش فرض خطایی که به کاربر نمایش داده می‌شود، استثنایی است که توسط فریم ورک صادر شده است. برای مثال نتوانسته است abcd را به یک تاریخ معتبر تبدیل کند. می‌توان توسط this.ModelState.AddModelError خطایی را نیز در اینجا اضافه کرد و پیغام بهتری را به کاربر نمایش داد. یا توسط یک سری data annotations هم کار اعتبار سنجی را سفارشی کرد که بحث آن به صورت جداگانه در یک قسمت مستقل بررسی خواهد شد.
ولی به صورت خلاصه اگر به فرم‌های تولید شده توسط VS.NET دقت کنید، در ابتدای هر فرم داریم:

@Html.ValidationSummary(true)

در اینجا خطاهای عمومی در سطح مدل نمایش داده می‌شوند. برای اضافه کردن این نوع خطاها، در متد AddModelError، مقدار key را خالی وارد کنید:

ModelState.AddModelError(string.Empty, "There is something wrong with model.");

همچنین در این فرم‌ها داریم:
@Html.EditorFor(model => model.AddDate)
@Html.ValidationMessageFor(model => model.AddDate)

EditorFor سعی می‌کند اندکی هوش به خرج دهد. یعنی اگر خاصیت دریافتی مثلا از نوع bool بود، خودش یک checkbox را در صفحه نمایش می‌دهد. همچنین بر اساس متادیتا یک خاصیت نیز می‌تواند تصمیم گیری را انجام دهد. این متادیتا منظور attributes و data annotations ایی است که به خواص یک مدل اعمال می‌شود. مثلا اگر ویژگی HiddenInput را به یک خاصیت اعمال کنیم، به شکل یک فیلد مخفی در صفحه ظاهر خواهد شد.
یا متد Html.DisplayFor، اطلاعات را به صورت فقط خواندنی نمایش می‌دهد. اصطلاحا به این نوع متدها، Templated Helpers هم گفته می‌شود. بحث بیشتر درباره‌ای این موارد به قسمتی مجزا و مستقل موکول می‌گردد. برای نمونه کل فرم ادیت برنامه را حذف کنید و بجای آن بنویسید Html.EditorForModel و سپس برنامه را اجرا کنید. یک فرم کامل خودکار ویرایش اطلاعات را مشاهده خواهید کرد (و البته نکات سفارشی سازی آن به یک قسمت کامل نیاز دارند).
در اینجا متد ValidationMessageFor کار نمایش خطاهای اعتبارسنجی مرتبط با یک خاصیت مشخص را انجام می‌دهد. بنابراین اگر قصد ارائه خطایی سفارشی و مخصوص یک فیلد مشخص را داشتید، در متد AddModelError، مقدار پارامتر اول یا همان key را مساوی نام خاصیت مورد نظر قرار دهید.


مقابله با مشکل امنیتی Mass Assignment در حین کار با Model binders

استفاده از Model binders بسیار لذت بخش است. یک شیء را به عنوان پارامتر اکشن متد خود معرفی می‌کنیم. فریم ورک هم در ادامه سعی می‌کند تا اطلاعات فرم را به خواص این شیء نگاشت کند. بدیهی است این روش نسبت به روش ASP.NET Web forms که باید به ازای تک تک کنترل‌های موجود در صفحه یکبار کار دریافت اطلاعات و مقدار دهی خواص یک شیء را انجام داد، بسیار ساده‌تر و سریعتر است.
اما اگر همین سیستم پیشرفته جدید ناآگاهانه مورد استفاده قرار گیرد می‌تواند منشاء حملات ناگواری شود که به نام «Mass Assignment» شهرت یافته‌اند.
همان صفحه ویرایش اطلاعات را درنظر بگیرید. چک باکس IsAdmin قرار است در قسمت مدیریتی برنامه تنظیم شود. اگر کاربری نیاز داشته باشد اطلاعات خودش را ویرایش کند، مثلا پسوردش را تغییر دهد، با یک صفحه ساده کلمه عبور قبلی را وارد کنید و دوبار کلمه عبور جدید را نیز وارد نمائید، مواجه خواهد شد. خوب ... اگر همین کاربر صفحه را جعل کند و فیلد چک باکس IsAdmin را به صفحه اضافه کند چه اتفاقی خواهد افتاد؟ بله ... مشکل هم همینجا است. در اینصورت کاربر عادی می‌تواند دسترسی خودش را تا سطح ادمین بالا ببرد، چون model binder اطلاعات IsAdmin را از کاربر دریافت کرده و به صورت خودکار به model ارائه شده، نگاشت کرده است.
برای مقابله با این نوع حملات چندین روش وجود دارند:
الف) ایجاد لیست سفید
به کمک ویژگی Bind می‌توان لیستی از خواص را جهت به روز رسانی به model binder معرفی کرد. مابقی ندید گرفته خواهند شد:

public ActionResult Edit([Bind(Include = "Name, Password")] User user)

در اینجا تنها خواص Name و Password توسط model binder به خواص شیء User نگاشت می‌شوند.
به علاوه همانطور که در قسمت قبل نیز ذکر شد، متد edit را به شکل زیر نیز می‌توان بازنویسی کرد. در اینجا متدهای توکار UpdateModel و TryUpdateModel نیز لیست سفید خواص مورد نظر را می‌پذیرند (اعمال دستی model binding):

[HttpPost]
public ActionResult Edit()
{
var user = new User();
if(TryUpdateModel(user, includeProperties: new[] { "Name", "Password" }))
{
// todo: Edit record
return RedirectToAction("Index");
}
return View(user); // Shows the Edit view again.
}


ب) ایجاد لیست سیاه
به همین ترتیب می‌توان تنها خواصی را معرفی کرد که باید صرفنظر شوند:
public ActionResult Edit([Bind(Exclude = "IsAdmin")] User user)

در اینجا از خاصیت IsAdmin صرف نظر گردیده و از مقدار ارسالی آن توسط کاربر استفاده نخواهد شد.
و یا می‌توان پارامتر excludeProperties متد TryUpdateModel را نیز مقدار دهی کرد.

لازم به ذکر است که ویژگی Bind را به کل یک کلاس هم می‌توان اعمال کرد. برای مثال:

using System;
using System.Web.Mvc;

namespace MvcApplication7.Models
{
[Bind(Exclude = "IsAdmin")]
public class User
{
public int Id { set; get; }
public string Name { set; get; }
public string Password { set; get; }
public DateTime AddDate { set; get; }
public bool IsAdmin { set; get; }
}
}

این مورد اثر سراسری داشته و قابل بازنویسی نیست. به عبارتی حتی اگر در متدی خاصیت IsAdmin را مجددا الحاق کنیم، تاثیری نخواهد داشت.
یا می‌توان از ویژگی ReadOnly هم استفاده کرد:
using System;
using System.ComponentModel;

namespace MvcApplication7.Models
{
public class User
{
public int Id { set; get; }
public string Name { set; get; }
public string Password { set; get; }
public DateTime AddDate { set; get; }

[ReadOnly(true)]
public bool IsAdmin { set; get; }
}
}

در این حالت هم خاصیت IsAdmin هیچگاه توسط model binder به روز و مقدار دهی نخواهد شد.

ج) استفاده از ViewModels
این راه حلی است که بیشتر مورد توجه معماران نرم افزار است و البته کسانی که پیشتر با الگوی MVVM کار کرده باشند این نام برایشان آشنا است؛ اما در اینجا مفهوم متفاوتی دارد. در الگوی MVVM، کلاس‌های ViewModel شبیه به کنترلرها در MVC هستند یا به عبارتی همانند رهبر یک اکستر عمل می‌کنند. اما در الگوی MVC خیر. در اینجا فقط مدل یک View هستند و نه بیشتر. هدف هم این است که بین Domain Model و View Model تفاوت قائل شد.
کار View model در الگوی MVC، شکل دادن به چندین domain model و همچنین اطلاعات اضافی دیگری که نیاز هستند، جهت استفاده نهایی توسط یک View می‌باشد. به این ترتیب View با یک شیء سر و کار خواهد داشت و همچنین منطق شکل دهی به اطلاعات مورد نیازش هم از داخل View حذف شده و به خواص View model در زمان تشکیل آن منتقل می‌شود.
مشخصات یک View model خوب به شرح زیر است:
الف) رابطه بین یک View و View model آن، رابطه‌ای یک به یک است. به ازای هر View، بهتر است یک کلاس View model وجود داشته باشد.
ب) View ساختار View model را دیکته می‌کند و نه کنترلر.
ج) View modelها صرفا یک سری کلاس POCO (کلاس‌هایی تشکیل شده از خاصیت، خاصیت، خاصیت ....) هستند که هیچ منطقی در آن‌ها قرار نمی‌گیرد.
د) View model باید حاوی تمام اطلاعاتی باشد که View جهت رندر نیاز دارد و نه بیشتر و الزامی هم ندارد که این اطلاعات مستقیما به domain models مرتبط شوند. برای مثال اگر قرار است firstName+LastName در View نمایش داده شود، کار این جمع زدن باید حین تهیه View Model انجام شود و نه داخل View. یا اگر قرار است اطلاعات عددی با سه رقم جدا کننده به کاربر نمایش داده شوند، وظیفه View Model است که یک خاصیت اضافی را برای تهیه این مورد تدارک ببیند. یا مثلا اگر یک فرم ثبت نام داریم و در این فرم لیستی وجود دارد که تنها Id عنصر انتخابی آن در Model اصلی مورد استفاده قرار می‌گیرد، تهیه اطلاعات این لیست هم کار ViewModel است و نه اینکه مدام به Model اصلی بخواهیم خاصیت اضافه کنیم.

ViewModel چگونه پیاده سازی می‌شود؟
اکثر مقالات را که مطالعه کنید، این روش را توصیه می‌کنند:

public class MyViewModel
{
    public SomeDomainModel1 Model1 { get; set; }
    public SomeDomainModel2 Model2 { get; set; }
    ...
}

یعنی اینکه View ما به اطلاعات مثلا دو Model نیاز دارد. این‌ها را به این شکل محصور و کپسوله می‌کنیم. اگر View، واقعا به تمام فیلدهای این کلاس‌ها نیاز داشته باشد، این روش صحیح است. در غیر اینصورت، این روش نادرست است (و متاسفانه همه جا هم دقیقا به این شکل تبلیغ می‌شود).
ViewModel محصور کننده یک یا چند مدل نیست. در اینجا حس غلط کار کردن با یک ViewModel را داریم. ViewModel فقط باید ارائه کننده اطلاعاتی باشد که یک View نیاز دارد و نه بیشتر و نه تمام خواص تمام کلاس‌های تعریف شده. به عبارتی این نوع تعریف صحیح است:

public class MyViewModel
{
    public string SomeExtraField1 { get; set; }
    public string SomeExtraField2 { get; set; }
public IEnumerable<SelectListItem> StateSelectList { get; set; }
// ...
    public string PersonFullName { set; set; }
}

در اینجا، View متناظری، قرار است نام کامل یک شخص را به علاوه یک سری اطلاعات اضافی که در domain model نیست، نمایش دهد. مثلا نمایش نام استان‌ها که نهایتا Id انتخابی آن قرار است در برنامه استفاده شود.
خلاصه علت وجودی ViewModel این موارد است:
الف) Model برنامه را مستقیما در معرض استفاده قرار ندهیم (عدم رعایت این نکته به مشکلات امنیتی حادی هم حین به روز رسانی اطلاعات ممکن است ختم ‌شود که پیشتر توضیح داده شد).
ب) فیلدهای نمایشی اضافی مورد نیاز یک View را داخل Model برنامه تعریف نکنیم (مثلا تعاریف عناصر یک دراپ داون لیست، جایش اینجا نیست. مدل فقط نیاز به Id عنصر انتخابی آن دارد).

با این توضیحات، اگر View به روز رسانی اطلاعات کلمه عبور کاربر، تنها به اطلاعات id آن کاربر و کلمه عبور او نیاز دارد، فقط باید همین اطلاعات را در اختیار View قرار داد و نه بیشتر:

namespace MvcApplication7.Models
{
public class UserViewModel
{
public int Id { set; get; }
public string Password { set; get; }
}
}

به این ترتیب دیگر خاصیت IsAdming اضافه‌ای وجود ندارد که بخواهد مورد حمله واقع شود.



استفاده از model binding برای آپلود فایل به سرور

برای آپلود فایل به سرور تنها کافی است یک اکشن متد به شکل زیر را تعریف کنیم. HttpPostedFileBase نیز یکی دیگر از model binderهای توکار ASP.NET MVC است:

[HttpGet]
public ActionResult Upload()
{
return View(); // Shows the upload page
}

[HttpPost]
public ActionResult Upload(System.Web.HttpPostedFileBase file)
{
string filename = Server.MapPath("~/files/somename.ext");
file.SaveAs(filename);
return RedirectToAction("Index");
}

View متناظر هم می‌تواند به شکل زیر باشد:

@{
ViewBag.Title = "Upload";
}
<h2>
Upload</h2>
@using (Html.BeginForm(actionName: "Upload", controllerName: "User",
method: FormMethod.Post,
htmlAttributes: new { enctype = "multipart/form-data" }))
{
<text>Upload a photo:</text> <input type="file" name="photo" />
<input type="submit" value="Upload" />
}

اگر دقت کرده باشید در طراحی ASP.NET MVC از anonymously typed objects زیاد استفاده می‌شود. در اینجا هم برای معرفی enctype فرم آپلود، مورد استفاده قرار گرفته است. به عبارتی هر جایی که مشخص نبوده چه تعداد ویژگی یا کلا چه ویژگی‌ها و خاصیت‌هایی را می‌توان تنظیم کرد، اجازه تعریف آن‌ها را به صورت anonymously typed objects میسر کرده‌اند. یک نمونه دیگر آن در متد routes.MapRoute فایل Global.asax.cs است که پارامتر سوم دریافت مقدار پیش فرض‌ها نیز anonymously typed object است. یا نمونه دیگر آن‌را در همین قسمت در جایی که لینک delete را به فرم تبدیل کردیم مشاهده نمودید. مقدار routeValues هم یک anonymously typed object معرفی شد.



سفارشی سازی model binder پیش فرض ASP.NET MVC

در همین مثال فرض کنید تاریخ را به صورت شمسی از کاربر دریافت می‌کنیم. خاصیت تعریف شده هم DateTime میلادی است. به عبارتی model binder حین تبدیل رشته تاریخ شمسی دریافتی به تاریخ میلادی با شکست مواجه شده و نهایتا خاصیت this.ModelState.IsValid مقدارش false خواهد بود. برای حل این مشکل چکار باید کرد؟
برای این منظور باید نحوه پردازش یک نوع خاص را سفارشی کرد. ابتدا با پیاده سازی اینترفیس IModelBinder شروع می‌کنیم. توسط bindingContext.ValueProvider می‌توان به مقداری که کاربر وارد کرده در میانه راه دسترسی یافت. آن‌را تبدیل کرده و نمونه صحیح را بازگشت داد.
نمونه‌ای از این پیاده سازی را در ادامه ملاحظه می‌کنید:

using System;
using System.Globalization;
using System.Web.Mvc;

namespace MvcApplication7.Binders
{
public class PersianDateModelBinder : IModelBinder
{

public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var valueResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
var modelState = new ModelState { Value = valueResult };
object actualValue = null;
try
{
var parts = valueResult.AttemptedValue.Split('/'); //ex. 1391/1/19
if (parts.Length != 3) return null;
int year = int.Parse(parts[0]);
int month = int.Parse(parts[1]);
int day = int.Parse(parts[2]);
actualValue = new DateTime(year, month, day, new PersianCalendar());
}
catch (FormatException e)
{
modelState.Errors.Add(e);
}

bindingContext.ModelState.Add(bindingContext.ModelName, modelState);
return actualValue;
}
}
}

سپس برای معرفی PersianDateModelBinder جدید تنها کافی است سطر زیر را

ModelBinders.Binders.Add(typeof(DateTime), new PersianDateModelBinder());

به متد Application_Start قرار گرفته در فایل Global.asax.cs برنامه اضافه کرد. از این پس کاربران می‌توانند تاریخ‌ها را در برنامه شمسی وارد کنند و model binder بدون مشکل خواهد توانست اطلاعات ورودی را به معادل DateTime میلادی آن تبدیل کند و استفاده نماید.
تعریف مدل بایندر سفارشی در فایل Global.asax.cs آن‌را به صورت سراسری در تمام مدل‌ها و اکشن‌متدها فعال خواهد کرد. اگر نیاز بود تنها یک اکشن متد خاص از این مدل بایندر سفارشی استفاده کند می‌توان به روش زیر عمل کرد:

public ActionResult Create([ModelBinder(typeof(PersianDateModelBinder))] User user)

همچنین ویژگی ModelBinder را به یک کلاس هم می‌توان اعمال کرد:

[ModelBinder(typeof(PersianDateModelBinder))]
public class User
{