مطالب
ساخت یک برنامه ساده‌ی جاوااسکریپتی با استفاده از الگوی MVC

در این مقاله قصد داریم با استفاده از جاوااسکریپت خالص، یک برنامه‌ی ساده را با الگوی MVC انجام دهیم. این برنامه، عملیات CRUD را پیاده سازی میکند و تنها به سه فایل index.html , script.js , style.css  نیاز دارد و از هیچ کتابخانه یا فریم ورک دیگری در آن استفاده نمیکنیم.

در الگوی MVC
  • M مخفف  Model می‌باشد و کار مدیریت داده‌ها را بر عهده دارد.
  • V مخفف View  می‌باشد و وظیفه‌ی نمایش داده‌ها به کاربر را بر عهده دارد.
  • C  مخفف  Controller می‌باشد و پل ارتباطی بین Model و  View می‌باشد و مدیریت درخواست‌ها را بر عهده دارد.
در برنامه‌ی جاری همه چیز با جاوا اسکریپت هندل میشود و فایل  index.html  فقط دارای یک المنت با آیدی مشخصی است. کد زیر ساختار فایل  index.html می‌باشد:
<!DOCTYPE html>
<html>

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>الگوی MVC در جاوااسکریپت</title>
    <link rel="stylesheet" href="style.css">
</head>

<body>

    <div id="root"></div>

    <script src="script.js"></script>
</body>

</html>
فایل  style.css  آن نیز دارای دستورات ساده‌ای است و یا میتوان از Normalize.css به همراه استایل دلخواه استفاده کرد و یا از فریم ورکهای مطرح دیگر استفاده نمود. کدهای فایل sytle.css آن نیز به شکل زیر خواهد بود:
*,
*::before,
*::after {
  box-sizing: border-box
}

html {
  color: #444;
}

#root {
  max-width: 450px;
  margin: 2rem auto;
  padding: 0 1rem;
}

form {
  display: flex;
  margin-bottom: 2rem;
}

[type="text"],
button {
  display: inline-block;
  -webkit-appearance: none;
  padding: .5rem 1rem;
  border: 2px solid #ccc;
  border-radius: 4px;
}

button {
  cursor: pointer;
  background: #007bff;
  color: white;
  border: 2px solid #007bff;
  margin: 0 .5rem;
}

[type="text"] {
  width: 100%;
}

[type="text"]:active,
[type="text"]:focus {
  outline: 0;
  border: 2px solid #007bff;
}

[type="checkbox"] {
  margin-right: 1rem;
}

h1 {
  color: #222;
}

ul {
  padding: 0;
}

li {
  display: flex;
  align-items: center;
  padding: 1rem;
  margin-bottom: 1rem;
  background: #f4f4f4;
  border-radius: 4px;
}

li span {
  display: inline-block;
  padding: .5rem;
  width: 250px;
  border-radius: 4px;
  border: 2px solid transparent;
}

li span:hover {
  background: rgba(179, 215, 255, 0.52);
}

li span:focus {
  outline: 0;
  border: 2px solid #007bff;
  background: rgba(179, 207, 255, 0.52)
}


فایلهای HTML و CSS را برای شروع کار آماده نمودیم و از این پس با فایل  script.js، ادامه کار را پیش می‌بریم. برای جداسازی هر قسمت از اجزای MVC، کلاسی خاص را تدارک می‌بینیم. پس سه کلاس خواهیم داشت به‌نام‌های  Model , View , Controller و در سازنده کلاس کنترلر، دو شی از View و  Model را بعنوان ورودی دریافت میکنیم. همانطور که پیش‌تر توضیح داده شد، قسمت Controller، پل ارتباطی بین View و Model می‌باشد. کد فایل  script.js را به شکل زیر تغییر میدهیم:
class Model {
    constructor() {}
}

class View {
    constructor() {}
}

class Controller {
    constructor(model, view) {
        this.model = model
        this.view = view
    }
}


const app = new Controller(new Model(), new View())

در ادامه کار در کلاس Model شروع به کدنویسی میکنیم و متدهای مد نظر را برای عملیات CRUD، در آن اضافه میکنیم. چهار تابع را به کلاس Model به‌نامهای addTodo  ، editTodo  ، deleteTodo ، toggleTodo اضافه میکنیم. در کد زیر، در بالای هر تابع، توضیحی در مورد عملکرد تابع ذکر شده است:
class Model {
    constructor() {
        // یک آرایه از اطلاعات پیش فرض 
        this.todos = [{
                id: 1,
                text: 'Run a marathon',
                complete: false
            },
            {
                id: 2,
                text: 'Plant a garden',
                complete: false
            },
        ]
    }

    // متدی برای افزودن آیتم جدید به آرایه
    addTodo(todoText) {
        const todo = {
            id: this.todos.length > 0 ? this.todos[this.todos.length - 1].id + 1 : 1,
            text: todoText,
            complete: false,
        }

        this.todos.push(todo)
    }

    // متدی برای بروزسانی آیتم مورد نظر
    editTodo(id, updatedText) {
        this.todos = this.todos.map(todo =>
            todo.id === id ? {
                id: todo.id,
                text: updatedText,
                complete: todo.complete
            } : todo
        )
    }

    // انجام میدهد filter با استفاده از متد id  تابعی که عملیات حذف را بوسیله فیلد   
    deleteTodo(id) {
        this.todos = this.todos.filter(todo => todo.id !== id)
    }

    //  متدی که در آن مشخص میکنیم کار مد نظرانجام شده یا خیر 
    toggleTodo(id) {
        this.todos = this.todos.map(todo =>
            todo.id === id ? {
                id: todo.id,
                text: todo.text, 
                complete: !todo.complete
            } : todo
        )
    }
}

میتوانیم برای تست و نحوه عملکرد آن با استفاده از شیء app،  با دستور زیر، آیتمی را به آرایه اضافه کنیم و در کنسول آن را نمایش دهیم:
app.model.addTodo('Take a nap')
console.log(app.model.todos)

در حال حاضر با هر بار reload  شدن صفحه، فقط اطلاعات پیش فرض، درون آرایه todos قرار میگیرد؛ ولی در ادامه آن را در local storage ذخیره میکنیم.

برای ساختن قسمت View، از جاوااسکریپت استفاده میکنیم و DOM را تغییر میدهیم. البته اینکار را بدون استفاده از JSX و یا یک templating language انجام خواهیم داد. قسمت‌های دیگر برنامه مانند Controller و Model نباید درگیر تغییرات DOM یا CSS یا عناصر HTML باشند و تمام این موارد توسط View هندل میشود. کد View به نحو زیر خواهد بود:

class View {
    constructor() {}

    // ایجاد یک المنت با کلاسهای استایل دلخواه
    createElement(tag, className) {
        const element = document.createElement(tag)
        if (className) element.classList.add(className)

        return element
    }

    // DOM انتخاب و گرفتن آیتمی خاص از 
    getElement(selector) {
        const element = document.querySelector(selector)

        return element
    }
}

سپس قسمت سازنده کلاس View را تغییر میدهیم و تمام المنت‌های مورد نیاز را در آن ایجاد میکنیم:

  • ارجاعی به المنتی با آی‌دی root
  • تگ h1 برای عنوان
  • یک form، input  و دکمه‌ای برای افزودن آیتمی جدید به آرایه‌ی todos
  • یک المنت ul برای نمایش آیتم‌های  todos
سپس کلاس  View به شکل زیر خواهد بود:
    constructor() {
        // root ارجاعی به المنتی با آیدی
        this.app = this.getElement('#root')

        // عنوان برنامه
        this.title = this.createElement('h1')
        this.title.textContent = 'Todos'

        // فرم ، اینپوت ورودی و دکمه
        this.form = this.createElement('form')

        this.input = this.createElement('input')
        this.input.type = 'text'
        this.input.placeholder = 'Add todo'
        this.input.name = 'todo'

        this.submitButton = this.createElement('button')
        this.submitButton.textContent = 'Submit'

        // برای نمایش عناط آرایه یا همان لیست کارها
        this.todoList = this.createElement('ul', 'todo-list')

        // افزودن اینپوت ورودی و دکمه به فرم
        this.form.append(this.input, this.submitButton)

        // ایجاد شده است app که اینجا ارجاعی به آن بنام  root اضافه کردن تمام آیتمهای بالا در المنتی با آیدی 
        this.app.append(this.title, this.form, this.todoList)
    }

در قسمت View، دو تابع هم برای getter و setter داریم که از underscore در اول نام آنها استفاده شده که نشان دهنده این است، توابع از خارج از کلاس در دسترس نیستند (شبیه private  در سی شارپ؛ البته این یک قرارداد هست یا convention)
get _todoText() {
  return this.input.value
}

_resetInput() {
  this.input.value = ''
}

در ادامه این کلاس، یک تابع دیگر هم برای نمایش آرایه داریم که هر زمان عناصر آن تغییر کردند، بتواند نمایش به‌روز اطلاعات را نشان دهد:
displayTodos(todos){
 //...
}

متد displayTodos یک المنت ul و li‌هایی را به تعداد عناصر todos ایجاد میکند و آنها را نمایش میدهد. هر زمانکه تغییراتی مانند اضافه شدن، حذف و ویرایش در todos صورت گیرد، این متد دوباره فراخوانی میشود و لیست جدید را نمایش میدهد. محتوای متد dispayTodos به شکل زیر خواهد بود:

  displayTodos(todos) {
    // حذف تمام نودها
    while (this.todoList.firstChild) {
      this.todoList.removeChild(this.todoList.firstChild)
    }

    // اگر هیچ آیتمی در آرایه نبود این پاراگراف با متن پیش فرض نمایش داده میشود
    if (todos.length === 0) {
      const p = this.createElement('p')
      p.textContent = 'Nothing to do! Add a task?'
      this.todoList.append(p)
    } else {
      // وعناصرمربوطه را ایجاد میکند liاگه درون آرایه آیتمی قرار دارد پس به ازای آن یک عنصر 
      todos.forEach(todo => {
        const li = this.createElement('li')
        li.id = todo.id

        const checkbox = this.createElement('input')
        checkbox.type = 'checkbox'
        checkbox.checked = todo.complete

        const span = this.createElement('span')
        span.contentEditable = true
        span.classList.add('editable')

        if (todo.complete) {
          const strike = this.createElement('s')
          strike.textContent = todo.text
          span.append(strike)
        } else {
          span.textContent = todo.text
        }

        const deleteButton = this.createElement('button', 'delete')
        deleteButton.textContent = 'Delete'
        li.append(checkbox, span, deleteButton)

        // نود ایجاد شده به لیست اضافه میکند
        this.todoList.append(li)
      })
    }

    // برای خطایابی و نمایش در کنسول
    console.log(todos)
  }


در نهایت قسمت Controller را که پل ارتباطی بین View و Model می‌باشد، کامل میکنیم. اولین تغییراتی که در کلاس Controller ایجاد میکنیم، استفاده از متد displayTodos در سازنده‌ی این کلاس می‌باشد و با هر بار تغییر این متد، دوباره فراخوانی میشود:

class Controller {
  constructor(model, view) {
    this.model = model
    this.view = view

    // نمایش اطلاعات پیش فرض
    this.onTodoListChanged(this.model.todos)
  }

  onTodoListChanged = todos => {
    this.view.displayTodos(todos)
  }
}


چهار تابعی را که در قسمت Model ایجاد نمودیم و کار ویرایش، حذف، افزودن و اتمام کار را انجام میدادند، در کلاس کنترلر آنها را هندل میکنیم و زمانیکه کاربر دکمه‌ای را برای افزودن یا تیک حذف آیتمی، زد، تابع مربوطه توسط کنترلر در Model فراخوانی شود:

handleAddTodo = todoText => {
  this.model.addTodo(todoText)
}

handleEditTodo = (id, todoText) => {
  this.model.editTodo(id, todoText)
}

handleDeleteTodo = id => {
  this.model.deleteTodo(id)
}

handleToggleTodo = id => {
  this.model.toggleTodo(id)
}

چون کنترلر نمیتواند بصورت مستقیم فراخوانی شود و این توابع باید درون DOM تنظیم شوند تا به ازای رخدادهایی همچون click و change، فراخوانی شوند. پس از این توابع در قسمت View استفاده میکنیم و به کلاس View، موارد زیر را اضافه میکنیم:

bindAddTodo(handler) {
  this.form.addEventListener('submit', event => {
    event.preventDefault()

    if (this._todoText) {
      handler(this._todoText)
      this._resetInput()
    }
  })
}

bindDeleteTodo(handler) {
  this.todoList.addEventListener('click', event => {
    if (event.target.className === 'delete') {
      const id = parseInt(event.target.parentElement.id)

      handler(id)
    }
  })
}

bindToggleTodo(handler) {
  this.todoList.addEventListener('change', event => {
    if (event.target.type === 'checkbox') {
      const id = parseInt(event.target.parentElement.id)

      handler(id)
    }
  })
}


برای bind کردن این متدها در کلاس Controller، کدهای زیر را اضافه میکنیم:

this.view.bindAddTodo(this.handleAddTodo)
this.view.bindDeleteTodo(this.handleDeleteTodo)
this.view.bindToggleTodo(this.handleToggleTodo)


برای ذخیره اطلاعات در local storage، در سازنده کلاس Model، کد زیر را اضافه میکنیم:

 this.todos = JSON.parse(localStorage.getItem('todos')) || []

متد دیگری هم در کلاس Model برای به‌روز رسانی مقادیر local storage قرار میدهیم:

_commit(todos) {
  this.onTodoListChanged(todos)
  localStorage.setItem('todos', JSON.stringify(todos))
}

متدی هم برای تغییراتی که هر زمان بر روی todos  اتفاق می‌افتد، فراخوانی شود:

deleteTodo(id) {
  this.todos = this.todos.filter(todo => todo.id !== id)

  this._commit(this.todos)
}
در پایان میتوانید سورس کد مقاله جاری را از اینجا دانلود نمایید.
این مقاله صرفا جهت آشنایی و نمونه کدی از پیاده سازی الگوی  MVC  در جاوااسکریپت می‌باشد.
نظرات مطالب
استفاده از Fluent Validation در برنامه‌های ASP.NET Core - قسمت پنجم - اعتبارسنجی تنظیمات آغازین برنامه
سلام
چطور باید validator هایی که در یک class library هستند رو به یک پروژه Asp.net core اضافه کرد ؟
زمانی که از یک class library  اسمبلی‌ها رو اضافه میکنیم بخش client side کار نمی‌کنه
ولی اگه validator توی خود پروژه asp.net core باشه کار میکنه
نظرات مطالب
افزونه مدیریت فایل های رایگان Roxy FileMan برای TinyMce و CkEditor
برای امنیت آپلود، مراجعه کنید به مطالبی مانند:
«تشخیص نوع فایل با استفاده از محتوای فایل»
«کنترل عمومی فایل‌های آپلودی در ASP.NET MVC»
«محدود کردن کاربر‌ها به آپلود فایل‌هایی خاص در ASP.NET MVC»
        
در مورد accessPolicy=Read در نظرات مورد آخری بیشتر بحث شده‌است و در این حالت حتی اگر یک فایل شل هم آپلود شود، قابلیت اجرا نخواهد داشت.
مطالب
طرح پیشنهادی برای بارگذاری پویای ماژول‌های JS
آقای Domenic Denicola در نسخه‌های بعدی، طرح پیشنهادی را مطرح کرده است که مربوط به بارگذاری داینامیک ماژول‌های JS می‌باشد. البته کتابخانه‌ها و روش‌هایی در حال حاضر برای این کار وجود دارند. با هم مثال‌هایی از این قابلیت را بررسی میکنیم. 

در نسخه جدید Javascript قابلیتی برای import کردن ماژول‌ها وجود دارد؛ ولی این قابلیت کاملا استاتیک می‌باشد. کد زیر را مشاهده کنید:
import someModule from './dir/someModule.js';
خوب سوالی که مطرح می‌شود این است که چه نیازی به بارگذاری داینامیک ماژول‌ها داریم؟               
جواب این سوال خیلی مشخص است. در import معمولی و استاتیک JS، ما تمام ماژول‌های مورد نیاز در پروژه را فراخوانی میکنیم. اما شاید خیلی از این ماژول‌ها در طول اجرای پروژه مورد نیاز نباشند و بر حسب رفتارهای کاربر نیاز به این ماژول‌ها داشته باشیم. در این صورت هست که بارگذاری داینامیک ماژول‌ها مطرح می‌شود. این قابلیت در جاوا اسکریپت به صورت built-in وجود ندارد. ولی با کتابخانه‌هایی مانند RequireJS این قابلیت قابل استفاده هست. این Proposal توسط آقای Domenic Denicola مطرح شده است.                  
 کد زیر مثال ساده از این قابلیت می‌باشد:
import('./dir/someModule.js')
    .then(someModule => someModule.foo());
                   
 یا یک مثال عملیاتی؛ فرض کنید با کلیک بر روی دکمه‌ای می‌خواهیم یک Dialog را باز کنیم که منطق و قوائد مخصوص به خود را دارد و به صورت یک ماژول جداگانه نوشته شده‌است. کد زیر را مشاهده کنید:
 button.addEventListener('click', event => {
        import('./dialogBox.js')
        .then(dialogBox => {
            dialogBox.open();
        })
        .catch(error => {
            /* Error handling */
        })
    });
                 
 این قابلیت هم وجود دارد که دو ماژول را که در یک فایل نوشته شده‌اند نیز به صورت جداگانه استفاده کنید.
import('./myModule.js')
    .then(({export1, export2}) => {
        export1.run();
        export2.fire();
    });
             
 شما حتی می‌توانید چند ماژول را باهم بارگذاری کنید و بعد از پایان بارگذاری همه ماژول‌ها، یک عمل خاصی را انجام دهید. کد زیر را مشاهده کنید:
Promise.all([
        import('./module1.js'),
        import('./module2.js'),
        import('./module3.js'),
    ])
    .then(([module1, module2, module3]) => {
        // code 
    });
این موضوع را به کمک Promise با متد all انجام دادیم.                

 حتی شما می‌توانید با قابلیت async و await نیز کدهای تمیزتر و با قابلیت خوانایی بالاتری را بنویسید. مثال زیر را مشاهده کنید:
async function main() {
        const myModule = await import('./myModule.js');
    
        myModule.getInfo();

        const {export1, export2} = await import('./myModule.js');      
        
        export1.run();
        
        export2.fire()
    }
    main();
                    
 خوب خوشبختانه طرافداران NodeJS ماژول مربوط به این قابلیت را قبل از ارائه این قابلیت جدید در JS به صورت Packages در NPM فراهم کرده‌اند. لینک زیر را مشاهده کنید:
                       
 و Developer‌های که از Webpack در پروژه‌های خود استفاده میکنند می‌توانند از ماژول زیر استفاده کنند که توسط تیم Airbnb تهیه شده است:
                   
 و Developer‌هایی که از نسخه ۲ Webpack استفاده میکنند، می‌توانند بحث Code Splitting را در راهنمای زیر مشاهده کنند:
                      

البته آقای Jake Archibald کد جالبی را برای این قابلیت پیشنهاد داده‌است که ترکیبی از import استاتیک ES6 می‌باشد:

function importModule (url) {
  return new Promise((resolve, reject) => {
    const script = document.createElement("script");
    const tempGlobal = "__tempModuleLoadingVariable" + Math.random().toString(32).substring(2);
    script.type = "module";
    script.textContent = `import * as m from "${url}"; window.${tempGlobal} = m;`;

    script.onload = () => {
      resolve(window[tempGlobal]);
      delete window[tempGlobal];
      script.remove();
    };

    script.onerror = () => {
      reject(new Error("Failed to load module script with URL " + url));
      delete window[tempGlobal];
      script.remove();
    };

    document.documentElement.appendChild(script);
  });
}               
مطالب
نکاتی در مورد نوشتن یک مطلب خوب و گیرا در یک سایت
تفاوتی نمی‌کند در چه رشته‌ای یا حرفه‌ای مشغول به کار هستید؛ تفاوتی نمی‌کند در چه زمینه‌ای قصد دارید مطلبی را منتشر کنید. برای تهیه یک مطلب خوب و ماندگار، باید یک سری اصول کلی را در نوشتن  رعایت کرد که در ادامه به مرور آن‌ها خواهیم پرداخت.

1) مطلب شما نیاز به مقدمه دارد
نیاز به مقدمه داشتن به معنای نوشتن کلمه «مقدمه» در ابتدای یک متن نیست. به این معنا است که به خواننده بگوئید مشکل کجا بوده یا به چه دلیلی قصد دارید مطلب جاری را منتشر کنید. بنابراین جهت تهیه یک مطلب خوب، یک راست اصل مطلب را شروع نکنید. لازم است چند سطری در مورد علت انتشار آن توضیح دهید.

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

3) سعی کنید در عنوان مطلب خود از کلمات کلیدی استفاده کنید
استفاده از کلمات کلیدی در عنوان مطالب، جستجوی آن‌ها را برای خواننده ساده‌تر کرده و همچنین کمک بزرگی است به موتورهای جستجو در یافتن نتایجی بهتر.

4) تکرار کلمات و جملات یکسان را در متن خود به حداقل برسانید
برای مثال مدام در متن خود جمله «همانطور که ملاحظه می‌کنید» را تکرار نکنید. استفاده از افعال تکراری و جملات تکراری در یک متن باید حداقل باشند. برای نمونه اگر جمله جاری به «می‌شود» ختم خواهد شد، جمله بعدی را به «می‌گردد» ختم کنید. اگر جمله‌ای دارای کلمات «برای مثال» است، جمله بعدی بهتر است به همراه کلمات «برای نمونه» باشد.

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

6) استفاده از علامت تعجب را به حداقل برسانید
اشخاصی که مدام از چندین علامت تعجب پشت سرهم استفاده می‌کنند یا مدام از علامت سؤال به همراه چندین علامت تعجب بهره می‌گیرند، حس مسخره کردن شخص مقابل و همچنین عدم تعادل روانی خود را القاء می‌کنند.

7) در متن خود از تصاویر استفاده کنید
انسان موجودی است بصری. قدرت یادگیری ما از طریق دیدن چند برابر زمانی است که از طریق شنیدن یا خواندن نسبت به فراگیری مطلبی اقدام می‌کنیم.
« ما ...
10 درصد چیزهایی را که می‌خوانیم
20 درصد چیزهایی را که می‌شنویم
30 درصد چیزهایی را که می‌بینیم
50 درصد چیزهایی را که می‌بینیم و می‌شنویم
70 درصد چیزهایی را که در موردشان بحث می‌کنیم
80 درصد چیزهایی را که تجربه می‌کنیم
95 درصد چیزهایی را که به دیگران می‌آموزیم
... یاد می‌گیریم
»

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

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

10) دست به اختراع برچسب‌های جدید، پراکنده و بیهوده نزنید
اگر گروه بندی مطالب یک سایت بر اساس برچسب‌ها است و تاکنون برچسب‌های متعددی تعریف شده است، بهتر است از همان‌ها استفاده کنید تا اینکه دست به اختراع زده و یک برچسب کاملا جدید را ثبت کنید. برای مثال اگر مطلب شما در مورد Entity framework است و تا کنون 20 مطلب ذیل این گروه ثبت شده، اختراع برچسب جدید EF Code first نه تنها کمکی نخواهد کرد، بلکه خواننده‌ای را که به دنبال یافتن مطالب یک گروه خاص است، سر در گم می‌کند. یا اگر قصد دارید یک برچسب کاملا جدید را اضافه کنید، حتما از یک برچسب کلی موجود نیز استفاده کنید تا روابط بین مطالب حفظ شوند.

11) مطالب شما بهتر است یک قسمت نتیجه گیری نیز داشته باشد
بهتر است در پایان یک مطلب، خلاصه بحث، پیشنهادها یا حتی سؤالاتی را مطرح کنید تا بتوانید خواننده را تا حدودی وادار به عکس العمل نمائید.

12) تا حد امکان از منابع سایت خود استفاده کنید
اگر قرار  است تصویری در متن قرار گیرد، اگر نیاز است فایلی در سایت مطرح شود، بهتر است این موارد در سایت جاری هاست شوند؛ تا اینکه تصویر یک سایت دیگر را مستقیما در سایت خود لینک کنیم.

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

14) تصاویر ارائه شده را فشرده کنید
فرمت مناسب ارائه تصویر در یک سایت bmp نیست. بهترین فرمت برای سایت‌ها png است؛ از لحاظ حفظ تعداد رنگ و همچنین کاهش حجم. به علاوه ابزارهای زیادی برای کاهش حجم فایل‌های png با حداقل افت کیفیت وجود دارند.

15) در مورد کدهای خود توضیح دهید
این مورد خصوصا به سایت‌های برنامه نویسی مرتبط می‌شود. اینکه چند سطر کد بدون توضیح را در یک مطلب قرار داده‌اید، نه لطفی است و نه اهمیتی دارد! هزاران هزار سطر از این دست کدها در GitHub، CodPlex و sourceforge وجود دارند. فرق کار شما با آن‌ها در چیست؟
باید یک قسمت «توضیحات» ذیل کدهای ارائه شده وجود داشته باشد. همان نکاتی را که حین تهیه کدها در ذهن داشتید باید بتوانید توضیح دهید و گرنه ... کار شما ارزشی نخواهد داشت.

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

17) یک راست به سراغ کدنویسی و اصل مطلب نروید
اگر قرار است در مورد یک فناوری جدید در طی چند جلسه بحث کنید، لازم است یک جلسه کامل در مورد «چرا به این فناوری نیاز داریم» توضیح دهید. بنابراین ذکر اینکه بدون مقدمه به سراغ کدنویسی می‌رویم، سؤالات بسیاری را به جا خواهند گذاشت مانند ... «مشکل روش‌‌های قبلی چی هست؟» «مزیت این روش جدید در کجاست؟» و تا نتوانید این مسایل را شرح دهید، کار شما خریدار نخواهد داشت.

18) در زبان فارسی نیم فاصله‌ها را رعایت کنید
در نگارش زبان فارسی فرق است بین «آمده ام» و «آمده‌ام» و یا «می شود» را باید «می‌شود» نوشت. می‌شود اندکی وقت گذاشت و با مبحث نیم فاصله آشنا شد .

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

 
مطالب
Kendo UI MVVM
پیشنیازها
- «استفاده از Kendo UI templates »
- «اعتبار سنجی ورودی‌های کاربر در Kendo UI»
- «فعال سازی عملیات CRUD در Kendo UI Grid» جهت آشنایی با نحوه‌ی تعریف DataSource ایی که می‌تواند اطلاعات را ثبت، حذف و یا ویرایش کند.


در این مطلب قصد داریم به یک چنین صفحه‌ای برسیم که در آن در ابتدای نمایش، لیست ثبت نام‌های موجود، از سرور دریافت و توسط یک Kendo UI template نمایش داده می‌شود. سپس امکان ویرایش و حذف هر ردیف، وجود خواهد داشت، به همراه امکان افزودن ردیف‌های جدید. در این بین مدیریت نمایش لیست ثبت نام‌ها توسط امکانات binding توکار فریم ورک MVVM مخصوص Kendo UI صورت خواهد گرفت. همچنین کلیه اعمال مرتبط با هر ردیف نیز توسط data binding دو طرفه مدیریت خواهد شد.



Kendo UI MVVM

الگوی MVVM یا Model-View-ViewModel که برای اولین بار جهت کاربردهای WPF و Silverlight معرفی شد، برای ساده سازی اتصال تغییرات کنترل‌های برنامه به خواص ViewModel یک View کاربرد دارد. برای مثال با تغییر عنصر انتخابی یک DropDownList در یک View، بلافاصله خاصیت متصل به آن که در ViewModel برنامه تعریف شده‌است، مقدار دهی و به روز خواهد شد. هدف نهایی آن نیز جدا سازی منطق کدهای UI، از کدهای جاوا اسکریپتی سمت کاربر است. برای این منظور کتابخانه‌هایی مانند Knockout.js به صورت اختصاصی برای این کار تهیه شده‌اند؛ اما Kendo UI نیز جهت یکپارچگی هرچه تمامتر اجزای آن، دارای یک فریم ورک MVVM توکار نیز می‌باشد. طراحی آن نیز بسیار شبیه به Knockout.js است؛ اما با سازگاری 100 درصد با کل مجموعه.
پیاده سازی الگوی MVVM از 4 قسمت تشکیل می‌شود:
- Model که بیانگر خواص متناظر با اشیاء رابط کاربری است.
- View همان رابط کاربری است که به کاربر نمایش داده می‌شود.
- ViewModel واسطی است بین Model و View. کار آن انتقال داده‌ها و رویدادها از View به مدل است و در حالت binding دوطرفه، عکس آن نیز صحیح می‌باشد.
- Declarative data binding جهت رهایی برنامه نویس‌ها از نوشتن کدهای هماهنگ سازی اطلاعات المان‌های View و خواص ViewModel کاربرد دارد.

در ادامه این اجزا را با پیاده سازی مثالی که در ابتدای بحث مطرح شد، دنبال می‌کنیم.


تعریف Model و ViewModel

در سمت سرور، مدل ثبت نام برنامه چنین شکلی را دارد:
namespace KendoUI07.Models
{
    public class Registration
    {
        public int Id { set; get; }
        public string UserName { set; get; }
        public string CourseName { set; get; }
        public int Credit { set; get; }
        public string Email { set; get; }
        public string Tel { set; get; }
    }
}
در سمت کاربر، این مدل را به نحو ذیل می‌توان تعریف کرد:
    <script type="text/javascript">
        $(function () {
            var model = kendo.data.Model.define({
                id: "Id",
                fields: {
                    Id: { type: 'number' }, // leave this set to 0 or undefined, so Kendo knows it is new.
                    UserName: { type: 'string' },
                    CourseName: { type: 'string' },
                    Credit: { type: 'number' },
                    Email: { type: 'string' },
                    Tel: { type: 'string' }
                }
            });
        });
    </script>
و ViewModel برنامه در ساده‌ترین شکل آن اکنون چنین تعریفی را خواهد یافت:
    <script type="text/javascript">
        $(function () {
            var viewModel = kendo.observable({
                accepted: false,
                course: new model()
            });
        });
    </script>
یک viewModel در Kendo UI به صورت یک observable object تعریف می‌شود که می‌تواند دارای تعدادی خاصیت و متد دلخواه باشد. هر خاصیت آن به یک عنصر HTML متصل خواهد شد. در اینجا این اتصال دو طرفه است؛ به این معنا که تغییرات UI به خواص viewModel و برعکس منتقل و منعکس می‌شوند.


اتصال ViewModel به View برنامه

تعریف فرم ثبت نام را در اینجا ملاحظه می‌کنید. فیلدهای مختلف آن بر اساس نکات اعتبارسنجی HTML 5 با ویژگی‌های خاص آن، مزین شده‌اند. جزئیات آن‌را در مطلب «اعتبار سنجی ورودی‌های کاربر در Kendo UI» پیشتر بررسی کرده‌ایم.
اگر به تعریف هر فیلد دقت کنید، ویژگی data-bind جدیدی را هم ملاحظه خواهید کرد:
    <div id="coursesSection" class="k-rtl k-header">
        <div class="box-col">
            <form id="myForm" data-role="validator" novalidate="novalidate">
                <h3>ثبت نام</h3>
                <ul>
                    <li>
                        <label for="Id">Id</label>
                        <span id="Id" data-bind="text:course.Id"></span>
                    </li>
                    <li>
                        <label for="UserName">نام</label>
                        <input type="text" id="UserName" name="UserName" class="k-textbox"
                               data-bind="value:course.UserName"
                               required />
                    </li>
                    <li>
                        <label for="CourseName">دوره</label>
                        <input type="text" dir="ltr" id="CourseName" name="CourseName" required
                               data-bind="value:course.CourseName" />
                        <span class="k-invalid-msg" data-for="CourseName"></span>
                    </li>
                    <li>
                        <label for="Credit">مبلغ پرداختی</label>
                        <input id="Credit" name="Credit" type="number" min="1000" max="6000"
                               required data-max-msg="عددی بین 1000 و 6000" dir="ltr"
                               data-bind="value:course.Credit"
                               class="k-textbox k-input" />
                        <span class="k-invalid-msg" data-for="Credit"></span>
                    </li>
                    <li>
                        <label for="Email">پست الکترونیک</label>
                        <input type="email" id="Email" dir="ltr" name="Email"
                               data-bind="value:course.Email"
                               required class="k-textbox" />
                    </li>
                    <li>
                        <label for="Tel">تلفن</label>
                        <input type="tel" id="Tel" name="Tel" dir="ltr" pattern="\d{8}"
                               required class="k-textbox"
                               data-bind="value:course.Tel"
                               data-pattern-msg="8 رقم" />
                    </li>
                    <li>
                        <input type="checkbox" name="Accept"
                               data-bind="checked:accepted"
                               required />
                        شرایط دوره را قبول دارم.
                        <span class="k-invalid-msg" data-for="Accept"></span>
                    </li>
                    <li>
                        <button class="k-button"
                                data-bind="enabled: accepted, click: doSave"
                                type="submit">
                            ارسال
                        </button>
                        <button class="k-button" data-bind="click: resetModel">از نو</button>
                    </li>
                </ul>
                <span id="doneMsg"></span>
            </form>
        </div>
برای اتصال ViewModel تعریف شده به ناحیه‌ی مشخص شده با DIV ایی با Id مساوی coursesSection، می‌توان از متد kendo.bind استفاده کرد.
    <script type="text/javascript">
        $(function () {
            var model = kendo.data.Model.define({
            // ...
            });

            var viewModel = kendo.observable({
            // ...
            });

            kendo.bind($("#coursesSection"), viewModel);
        });
    </script>
به این ترتیب Kendo UI به بر اساس تعریف data-bind یک فیلد، برای مثال تغییرات خواص course.UserName را به text box نام کاربر منتقل می‌کند و همچنین اگر کاربر اطلاعاتی را در این text box وارد کند، بلافاصله این تغییرات در خاصیت course.UserName منعکس خواهند شد.
<input type="text" id="UserName" name="UserName" class="k-textbox"
       data-bind="value:course.UserName"
       required />

بنابراین تا اینجا به صورت خلاصه، مدلی را توسط متد kendo.data.Model.define، معادل مدل سمت سرور خود ایجاد کردیم. سپس وهله‌ای از این مدل را به صورت یک خاصیت جدید دلخواهی در ViewModel تعریف شده توسط متد kendo.observable در معرض دید View برنامه قرار دادیم. در ادامه اتصال ViewModel و View، با فراخوانی متد kendo.bind انجام شد. اکنون برای دریافت تغییرات کنترل‌های برنامه، تنها کافی است ویژگی‌های data-bind ایی را به آن‌ها اضافه کنیم.
در ناحیه‌ی تعریف شده توسط متد kendo.bind، کلیه خواص ViewModel در دسترس هستند. برای مثال اگر به تعریف ViewModel دقت کنید، یک خاصیت دیگر به نام accepted با مقدار false نیز در آن تعریف شده‌است (این خاصیت چون صرفا کاربرد UI داشت، در model برنامه قرار نگرفت). از آن برای اتصال checkbox تعریف شده، به button ارسال اطلاعات، استفاده کرده‌ایم:
<input type="checkbox" name="Accept"
       data-bind="checked:accepted"
       required />

<button class="k-button"
        data-bind="enabled: accepted, click: doSave"
        type="submit">
       ارسال
</button>
برای مثال اگر کاربر این checkbox را انتخاب کند، مقدار خاصیت accepted، مساوی true خواهد شد. تغییر مقدار این خاصیت، توسط ViewModel بلافاصله در کل ناحیه coursesSection منتشر می‌شود. به همین جهت ویژگی enabled: accepted که به معنای مقید بودن فعال یا غیرفعال بودن دکمه بر اساس مقدار خاصیت accepted است، دکمه را فعال می‌کند، یا برعکس و برای انجام این عملیات نیازی نیست کدنویسی خاصی را انجام داد. در اینجا بین checkbox و button یک سیم کشی برقرار است.


ارسال داده‌های تغییر کرده‌ی ViewModel به سرور

تا اینجا 4 جزء اصلی الگوی MVVM که در ابتدای بحث عنوان شد، تکمیل شده‌اند. مدل اطلاعات فرم تعریف گردید. ViewModel ایی که این خواص را به المان‌های فرم متصل می‌کند نیز در ادامه اضافه شده‌است. توسط ویژگی‌های data-bind کار Declarative data binding انجام می‌شود.
در ادامه نیاز است تغییرات ViewModel را به سرور، جهت ثبت، به روز رسانی و حذف نهایی منتقل کرد.
    <script type="text/javascript">
        $(function () {
            var model = kendo.data.Model.define({
                //...
            });

            var dataSource = new kendo.data.DataSource({
                type: 'json',
                transport: {
                    read: {
                        url: "api/registrations",
                        dataType: "json",
                        contentType: 'application/json; charset=utf-8',
                        type: 'GET'
                    },
                    create: {
                        url: "api/registrations",
                        contentType: 'application/json; charset=utf-8',
                        type: "POST"
                    },
                    update: {
                        url: function (course) {
                            return "api/registrations/" + course.Id;
                        },
                        contentType: 'application/json; charset=utf-8',
                        type: "PUT"
                    },
                    destroy: {
                        url: function (course) {
                            return "api/registrations/" + course.Id;
                        },
                        contentType: 'application/json; charset=utf-8',
                        type: "DELETE"
                    },
                    parameterMap: function (data, type) {
                        // Convert to a JSON string.  Without this step your content will be form encoded.
                        return JSON.stringify(data);
                    }
                },
                schema: {
                    model: model
                },
                error: function (e) {
                    alert(e.errorThrown);
                },
                change: function (e) {
                    // فراخوانی در زمان دریافت اطلاعات از سرور و یا تغییرات محلی
                    viewModel.set("coursesDataSourceRows", new kendo.data.ObservableArray(this.view()));
                }
            });

            var viewModel = kendo.observable({
                //...
            });

            kendo.bind($("#coursesSection"), viewModel);
            dataSource.read(); // دریافت لیست موجود از سرور در آغاز کار
        });
    </script>
در اینجا تعریف DataSource کار با منبع داده راه دور ASP.NET Web API را مشاهده می‌کنید. تعاریف اصلی آن با تعاریف مطرح شده در مطلب «فعال سازی عملیات CRUD در Kendo UI Grid» یکی هستند. هر قسمت آن مانند read، create، update و destory به یکی از متدهای کنترلر ASP.NET Web API اشاره می‌کنند. حالت‌های update و destroy بر اساس Id ردیف انتخابی کار می‌کنند. این Id را باید در قسمت model مربوط به اسکیمای تعریف شده، دقیقا مشخص کرد. عدم تعریف فیلد id، سبب خواهد شد تا عملیات update نیز در حالت create تفسیر شود.


متصل کردن DataSource به ViewModel

تا اینجا DataSource ایی جهت کار با سرور تعریف شده‌است؛ اما مشخص نیست که اگر رکوردی اضافه شد، چگونه باید اطلاعات خودش را به روز کند. برای این منظور خواهیم داشت:
    <script type="text/javascript">
        $(function () {
            $("#coursesSection").kendoValidator({
                // ...
            });

            var model = kendo.data.Model.define({
                // ...
            });

            var dataSource = new kendo.data.DataSource({
                // ...
            });

            var viewModel = kendo.observable({
                accepted: false,
                course: new model(),
                doSave: function (e) {
                    e.preventDefault();
                    console.log("this", this.course);
                    var validator = $("#coursesSection").data("kendoValidator");
                    if (validator.validate()) {
                        if (this.course.Id == 0) {
                            dataSource.add(this.course);
                        }
                        dataSource.sync(); // push to the server
                        this.set("course", new model()); // reset controls
                    }
                },
                resetModel: function (e) {
                    e.preventDefault();
                    this.set("course", new model());
                }            
             });

            kendo.bind($("#coursesSection"), viewModel);
            dataSource.read(); // دریافت لیست موجود از سرور در آغاز کار
        });
    </script>
همانطور که در تعاریف تکمیلی viewModel مشاهده می‌کنید، اینبار دو متد جدید دلخواه doSave و resetModel را اضافه کرده‌ایم.
در متد doSave، ابتدا بررسی می‌کنیم آیا اعتبارسنجی فرم با موفقیت انجام شده‌است یا خیر. اگر بله، توسط متد add منبع داده، اطلاعات فرم جاری را توسط شیء course که هم اکنون به تمامی فیلدهای آن متصل است، اضافه می‌کنیم. در اینجا بررسی شده‌است که آیا Id این اطلاعات صفر است یا خیر. از آنجائیکه از همین متد برای به روز رسانی نیز در ادامه استفاده خواهد شد، در حالت به روز رسانی، Id شیء ثبت شده، از طرف سرور دریافت می‌گردد. بنابراین غیر صفر بودن این Id به معنای عملیات به روز رسانی است و در این حالت نیازی نیست کار بیشتری را انجام داد؛ زیرا شیء متناظر با آن پیشتر به منبع داده اضافه شده‌است.
استفاده از متد add صرفا به معنای مطلع کردن منبع داده محلی از وجود رکوردی جدید است. برای ارسال این تغییرات به سرور، از متد sync آن می‌توان استفاده کرد. متد sync بر اساس متد add یک درخواست POST، بر اساس شیءایی که Id غیر صفر دارد، یک درخواست PUT و با فراخوانی متد remove بر روی منبع داده، یک درخواست DELETE را به سمت سرور ارسال می‌کند.
متد دلخواه  resetModel سبب مقدار دهی مجدد شیء course با یک وهله‌ی جدید از شیء model می‌شود. همینقدر برای پاک کردن تمامی کنترل‌های صفحه کافی است.

تا اینجا دو متد جدید را در ViewModel برنامه تعریف کرده‌ایم. در مورد نحوه‌ی اتصال آن‌ها به View، به کدهای دو دکمه‌ی موجود در فرم دقت کنید:
<button class="k-button"
        data-bind="enabled: accepted, click: doSave"
        type="submit">
       ارسال
</button>
<button class="k-button" data-bind="click: resetModel">از نو</button>
این متدها نیز توسط ویژگی‌های data-bind به هر دکمه نسبت داده شده‌اند. به این ترتیب برای مثال با کلیک کاربر بر روی دکمه‌ی submit، متد doSave موجود در ViewModel فراخوانی می‌شود.


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

در حالت ثبت، متد Post توسط آدرس مشخص شده در قسمت create منبع داده، فراخوانی می‌گردد. نکته‌ی مهمی که در اینجا باید به آن دقت داشت، نحوه‌ی بازگشت Id رکورد جدید ثبت شده‌است.  اگر این تنظیم صورت نگیرد، Id رکورد جدید را در لیست، مساوی صفر مشاهده خواهید کرد و منبع داده این رکورد را همواره به عنوان یک رکورد جدید، مجددا به سرور ارسال می‌کند.
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web.Http;
using KendoUI07.Models;

namespace KendoUI07.Controllers
{
    public class RegistrationsController : ApiController
    {
        public HttpResponseMessage Delete(int id)
        {
            var item = RegistrationsDataSource.LatestRegistrations.FirstOrDefault(x => x.Id == id);
            if (item == null)
                return Request.CreateResponse(HttpStatusCode.NotFound);

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

        public IEnumerable<Registration> Get()
        {
            return RegistrationsDataSource.LatestRegistrations;
        }

        public HttpResponseMessage Post(Registration registration)
        {
            if (!ModelState.IsValid)
                return Request.CreateResponse(HttpStatusCode.BadRequest);

            var id = 1;
            var lastItem = RegistrationsDataSource.LatestRegistrations.LastOrDefault();
            if (lastItem != null)
            {
                id = lastItem.Id + 1;
            }
            registration.Id = id;
            RegistrationsDataSource.LatestRegistrations.Add(registration);

            // ارسال آی دی مهم است تا از ارسال رکوردهای تکراری جلوگیری شود
            return Request.CreateResponse(HttpStatusCode.Created, registration);
        }

        [HttpPut] // Add it to fix this error: The requested resource does not support http method 'PUT'
        public HttpResponseMessage Update(int id, Registration registration)
        {
            var item = RegistrationsDataSource.LatestRegistrations
                                        .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 != registration.Id)
                return Request.CreateResponse(HttpStatusCode.BadRequest);

            RegistrationsDataSource.LatestRegistrations[item.Index] = registration;
            return Request.CreateResponse(HttpStatusCode.OK);
        }
    }
}
در اینجا بیشتر امضای این متدها مهم هستند، تا منطق پیاده سازی شده در آن‌ها. همچنین بازگشت Id رکورد جدید، توسط متد Post نیز بسیار مهم است و سبب می‌شود تا DataSource بداند با فراخوانی متد sync آن، باید عملیات Post یا create انجام شود یا Put و update.


نمایش آنی اطلاعات ثبت شده در یک لیست

ردیف‌های اضافه شده به منبع داده را می‌توان بلافاصله در همان سمت کلاینت توسط Kendo UI Template که قابلیت کار با ViewModelها را دارد، نمایش داد:
    <div id="coursesSection" class="k-rtl k-header">
        <div class="box-col">
            <form id="myForm" data-role="validator" novalidate="novalidate">
                           <!--فرم بحث شده در ابتدای مطلب-->
            </form>
        </div>
        <div id="results">
            <table class="metrotable">
                <thead>
                    <tr>
                        <th>Id</th>
                        <th>نام</th>
                        <th>دوره</th>
                        <th>هزینه</th>
                        <th>ایمیل</th>
                        <th>تلفن</th>
                        <th></th>
                        <th></th>
                    </tr>
                </thead>
                <tbody data-template="row-template" data-bind="source: coursesDataSourceRows"></tbody>
                <tfoot data-template="footer-template" data-bind="source: this"></tfoot>
            </table>
            <script id="row-template" type="text/x-kendo-template">
                <tr>
                    <td data-bind="text: Id"></td>
                    <td data-bind="text: UserName"></td>
                    <td dir="ltr" data-bind="text: CourseName"></td>
                    <td>
                        #: kendo.toString(get("Credit"), "c0") #
                    </td>
                    <td data-bind="text: Email"></td>
                    <td data-bind="text: Tel"></td>
                    <td><button class="k-button" data-bind="click: deleteCourse">حذف</button></td>
                    <td><button class="k-button" data-bind="click: editCourse">ویرایش</button></td>
                </tr>
            </script>
            <script id="footer-template" type="text/x-kendo-template">
                <tr>
                    <td colspan="3"></td>
                    <td>
                        جمع کل: #: kendo.toString(totalPrice(), "c0") #
                    </td>
                    <td colspan="2"></td>
                    <td></td>
                    <td></td>
                </tr>
            </script>
        </div>
    </div>
در ناحیه‌ی coursesSection که توسط متد kendo.bind به viewModel برنامه متصل شده‌است، یک جدول را برای نمایش ردیف‌های ثبت شده توسط کاربر اضافه کرده‌ایم. thead آن بیانگر سر ستون جدول است. قسمت tbody و tfoot این جدول توسط دو Kendo UI Template مقدار دهی شد‌ه‌اند. هر کدام نیز منبع داده‌اشان را از view model دریافت می‌کنند. در row-template معادل خواص شیء course را مشاهده می‌کنید. در footer-template متد totalPrice برای نمایش جمع ستون هزینه اضافه شده‌است. بنابراین مطابق این قسمت از View، به یک خاصیت جدید coursesDataSourceRows و سه متد deleteCourse، editCourse و totalPrice نیاز است:
    <script type="text/javascript">
        $(function () {
            // ...
            var viewModel = kendo.observable({
                accepted: false,
                course: new model(),
                coursesDataSourceRows: new kendo.data.ObservableArray([]),
                doSave: function (e) {
                       // ...
                },
                resetModel: function (e) {
                      // ...
                },
                totalPrice: function () {
                    var sum = 0;
                    $.each(this.get("coursesDataSourceRows"), function (index, item) {
                        sum += item.Credit;
                    });
                    return sum;
                },
                deleteCourse: function (e) {
                    // the current data item is passed as the "data" field of the event argument
                    var course = e.data;
                    dataSource.remove(course);
                    dataSource.sync(); // push to the server
                },
                editCourse: function(e) {
                    // the current data item is passed as the "data" field of the event argument
                    var course = e.data;
                    this.set("course", course);
                }
            });

            kendo.bind($("#coursesSection"), viewModel);
            dataSource.read(); // دریافت لیست موجود از سرور در آغاز کار
        });
    </script>
نحوه‌ی اتصال خاصیت جدید coursesDataSourceRows که به عنوان منبع داده ردیف‌های row-template عمل می‌کند، به این صورت است:
- ابتدا خاصیت دلخواه coursesDataSourceRows به viewModel اضافه می‌شود تا در ناحیه‌ی coursesSection در دسترس قرار گیرد.
- سپس اگر به انتهای تعریف DataSource دقت کنید، داریم:
    <script type="text/javascript">
        $(function () {
            var dataSource = new kendo.data.DataSource({
                //...
                change: function (e) {
                    // فراخوانی در زمان دریافت اطلاعات از سرور و یا تغییرات محلی
                    viewModel.set("coursesDataSourceRows", new kendo.data.ObservableArray(this.view()));
                }
            });
        });
    </script>
متد change آن، هر زمانیکه اطلاعاتی در منبع داده تغییر کنند یا اطلاعاتی به سمت سرور ارسال یا دریافت گردد، فراخوانی می‌شود. در همینجا فرصت خواهیم داشت تا خاصیت coursesDataSourceRows را جهت نمایش اطلاعات موجود در منبع داده، مقدار دهی کنیم. همین مقدار دهی ساده سبب اجرای row-template برای تولید ردیف‌های جدول می‌شود. استفاده از new kendo.data.ObservableArray سبب خواهد شد تا اگر اطلاعاتی در فرم برنامه تغییر کند، این اطلاعات بلافاصله در لیست گزارش برنامه نیز منعکس گردد.



کدهای کامل این مثال را از اینجا می‌توانید دریافت کنید:
KendoUI07.zip
مطالب
Blazor 5x - قسمت 15 - کار با فرم‌ها - بخش 3 - ویرایش اطلاعات
در قسمت قبل، ویژگی‌های ثبت اطلاعات یک اتاق جدید و سپس نمایش لیست آن‌ها را تکمیل کردیم. در این قسمت می‌خواهیم امکان ویرایش آن‌ها را نیز اضافه کنیم.


افزودن دکمه‌ی ویرایش، به رکوردهای لیست اتاق‌ها و نمایش جزئیات رکورد در حال ویرایش

تا اینجا کامپوننت Pages\HotelRoom\HotelRoomUpsert.razor دارای یک چنین مسیریابی است:
@page "/hotel-room/create"
در ادامه می‌خواهیم اگر کاربری برای مثال به آدرس hotel-room/edit/1 مراجعه کرد، اطلاعات رکورد متناظر با آن نمایش داده شود؛ تا بتواند آن‌ها را ویرایش کند. یعنی می‌خواهیم از همین صفحه، هم برای ویرایش اطلاعات موجود و هم برای ثبت اطلاعات جدید استفاده کنیم. بنابراین نیاز به تعریف مسیریابی دومی در کامپوننت HotelRoomUpsert وجود دارد:
@page "/hotel-room/create"
@page "/hotel-room/edit/{Id:int}"
در اینجا مسیریابی دوم تعریف شده، یک پارامتر مقید شده‌ی به نوع int را انتظار دارد. مزیت این مقید سازی، نمایش خودکار صفحه‌ی «یافت نشد» تعریف شده‌ی در فایل BlazorServer.App\App.razor، با ورود اطلاعاتی غیر عددی است. مسیریابی اول، برای ثبت اطلاعات مورد استفاده قرار می‌گیرد و مسیریابی دوم، برای ویرایش اطلاعات.
پس از تعریف مسیریابی دریافت پارامتر Id رکورد در حال ویرایش، نحوه‌ی واکنش نشان دادن به آن در کامپوننت HotelRoomUpsert.razor به صورت زیر است:
@code
{
    // ...

    [Parameter] public int? Id { get; set; }

    protected override async Task OnInitializedAsync()
    {
        if (Id.HasValue)
        {
            // Update Mode
            Title = "Update";
            HotelRoomModel = await HotelRoomService.GetHotelRoomAsync(Id.Value);
        }
        else
        {
            // Create Mode
            HotelRoomModel = new HotelRoomDTO();
        }
    }

    // ...
}
- در اینجا پارامتر عددی Id مسیریابی را از نوع nullable تعریف کرده‌ایم. علت اینجا است که اگر کاربر با مسیریابی اول تعریف شده، به این کامپوننت برسد، یعنی قصد ثبت اطلاعات جدیدی را دارد. بنابراین ذکر Id، اختیاری است.
- سپس می‌خواهیم در زمان بارگذاری کامپوننت جاری، اگر مقدار Id، تنظیم شده بود، تمام فیلدهای فرم متصل به شیء HotelRoomModel را به صورت خودکار بر اساس اطلاعات رکورد متناظر با آن در بانک اطلاعاتی، مقدار دهی اولیه کنیم.
<EditForm Model="HotelRoomModel" OnValidSubmit="HandleHotelRoomUpsert">
چون فرم جاری توسط کامپوننت EditForm تعریف شده‌است و مدل آن به HotelRoomModel متصل است، بر اساس two-way binding‌های تعریف شده‌ی در اینجا، اگر مقدار Model را به روز رسانی کنیم، به صورت خودکار تمام فیلدهای متصل به آن نیز مقدار دهی اولیه خواهند شد.
کار مقدار دهی اولیه‌ی فیلدهای یک کامپوننت نیز باید در روال رویداد گردان OnInitializedAsync انجام شود که نمونه‌ای از آن را در کدهای فوق مشاهده می‌کنید. در این مثال اگر Id مقدار داشته باشد، مقدار آن‌را به متد GetHotelRoomAsync ارسال کرده و سپس شیء DTO دریافتی از آن‌را به مدل فرم انتساب می‌دهیم تا فرم ویرایشی، قابل استفاده شود:


در ادامه برای ساده سازی رسیدن به مسیرهایی مانند hotel-room/edit/1، به کامپوننت Pages\HotelRoom\HotelRoomList.razor مراجعه کرده و در همان ردیفی که اطلاعات رکورد یک اتاق را نمایش می‌دهیم، لینکی را نیز به صفحه‌ی ویرایش آن، اضافه می‌کنیم:
<tr>
    <td>@room.Name</td>
    <td>@room.Occupancy</td>
    <td>@room.RegularRate.ToString("c")</td>
    <td>@room.SqFt</td>
    <td>
        <NavLink href="@($"hotel-room/edit/{room.Id}")" class="btn btn-primary">Edit</NavLink>
    </td>
</tr>
برای این منظور فقط کافی است در جائیکه tr هر رکورد رندر می‌شود، یک td مخصوص NavLink منتهی به hotel-room/edit/{room.Id} را نیز تعریف کنیم:



مدیریت ثبت اطلاعات ویرایش شده‌ی یک اتاق، در بانک اطلاعاتی

در حین تکمیل این قسمت می‌خواهیم پیام‌هایی مانند موفقیت آمیز بودن عملیات را نیز به کاربر نمایش دهیم. به همین جهت مراحل «Blazor 5x - قسمت یازدهم - مبانی Blazor - بخش 8 - کار با جاوا اسکریپت» را برای افزودن کتابخانه‌ی معروف جاوا اسکریپتی Toastr طی می‌کنیم که شامل این قسمت‌ها است:
- دریافت و نصب بسته‌های jquery و toastr
- اصلاح فایل Pages\_Host.cshtml برای افزودن مداخل فایل‌های CSS و JS بسته‌های نصب شده
- تعریف فایل جدید wwwroot\js\common.js برای سادگی کار با توابع جاوا اسکریپتی toastr و افزودن مدخل آن به فایل Pages\_Host.cshtml
- تعریف متدهای الحاقی JSRuntimeExtensions ، جهت کاهش کدهای تکراری فراخوانی متدهای toastr و افزودن فضای نام آن به فایل Imports.razor_

جزئیات این موارد را در قسمت یازدهم این سری می‌توانید مطالعه کنید و از تکرار آن‌ها در اینجا صرفنظر می‌شود. همچنین کدهای تکمیل شده‌ی این قسمت را از انتهای مطلب جاری نیز می‌توانید دریافت کنید.

همچنین پیش از تکمیل ادامه‌ی کدهای ویرایش اطلاعات، نیاز است متد IsRoomUniqueAsync را به صورت زیر اصلاح کنیم:
namespace BlazorServer.Services
{
    public interface IHotelRoomService
    {
        Task<bool> IsRoomUniqueAsync(string name, int roomId);

        // ...
    }
}

namespace BlazorServer.Services
{
    public class HotelRoomService : IHotelRoomService
    {
        // ...
 
        public Task<bool> IsRoomUniqueAsync(string name, int roomId)
        {
            if (roomId == 0)
            {
                // Create Mode
                return _dbContext.HotelRooms
                                .ProjectTo<HotelRoomDTO>(_mapperConfiguration)
                                .AnyAsync(x => x.Name != name);
            }
            else
            {
                // Edit Mode
                return _dbContext.HotelRooms
                                .ProjectTo<HotelRoomDTO>(_mapperConfiguration)
                                .AnyAsync(x => x.Name != name && x.Id != roomId);
            }
        }
    }
}
پیشتر در قست 13، بررسی منحصربفرد بودن نام اتاق، از طریق بررسی وجود نام آن در بانک اطلاعاتی صورت می‌گرفت. اینکار در حین ثبت اطلاعات یک رکورد جدید (Id==0) مشکلی را ایجاد نمی‌کند. اما اگر در حال ویرایش اطلاعات باشیم، چون این نام پیشتر ثبت شده‌است، پیام تکراری بودن نام اتاق را دریافت می‌کنیم؛ حتی اگر در اینجا نام اتاق در حال ویرایش را تغییر نداده باشیم و همان نام قبلی باشد. به همین جهت نیاز است در حالت ویرایش اطلاعات، اگر نامی در سایر رکوردها و نه رکوردی با Id مساوی فرم در حال ویرایش، با نام جدید یکی بود، آنگاه آن نام را به صورت تکراری گزارش دهیم که نمونه‌ای از آن‌را در اینجا مشاهده می‌کنید.

اکنون متد HandleHotelRoomUpsert کامپوننت Pages\HotelRoom\HotelRoomUpsert.razor جهت مدیریت ثبت و ویرایش اطلاعات به صورت زیر تغییر می‌کند:
// ...
@inject IJSRuntime JsRuntime


@code
{
   // ...

    private async Task HandleHotelRoomUpsert()
    {
        var isRoomUnique = await HotelRoomService.IsRoomUniqueAsync(HotelRoomModel.Name, HotelRoomModel.Id);
        if (!isRoomUnique)
        {
            await JsRuntime.ToastrError($"The room name: `{HotelRoomModel.Name}` already exists.");
            return;
        }

        if (HotelRoomModel.Id != 0 && Title == "Update")
        {
            // Update Mode
            var updateResult = await HotelRoomService.UpdateHotelRoomAsync(HotelRoomModel.Id, HotelRoomModel);
            await JsRuntime.ToastrSuccess($"The `{HotelRoomModel.Name}` updated successfully.");
        }
        else
        {
            // Create Mode
            var createResult = await HotelRoomService.CreateHotelRoomAsync(HotelRoomModel);
            await JsRuntime.ToastrSuccess($"The `{HotelRoomModel.Name}` created successfully.");
        }

        NavigationManager.NavigateTo("hotel-room");
    }
}
- در ابتدا چون می‌خواهیم پیام‌هایی را توسط Toastr نمایش دهیم، سرویس IJSRuntime را به کامپوننت جاری تزریق کرده‌ایم که این سرویس، امکان دسترسی به متدهای الحاقی ToastrError و ToastrSuccess تعریف شده‌ی در فایل Utils\JSRuntimeExtensions.cs را می‌دهد (کدهای آن در قسمت 11 ارائه شدند).
- در ابتدا عدم تکراری بودن نام اتاق بررسی می‌شود:


- در آخر بر اساس Id مدل فرم، حالت ویرایش و یا ثبت اطلاعات را تشخیص می‌دهیم. البته Id مدل فرم، در حالت ثبت اطلاعات نیز صفر است؛ به علت فراخوانی ()HotelRoomModel = new HotelRoomDTO که سبب مقدار دهی Id آن با عدد پیش‌فرض صفر می‌شود. بنابراین صرفا بررسی Id مدل، کافی نیست و برای مثال می‌توان عنوان تنظیم شده‌ی در متد OnInitializedAsync را نیز بررسی کرد.
- پس از تشخیص حالت ویرایش و یا ثبت، یکی از متدهای متناظر HotelRoom Service را مانند UpdateHotelRoomAsync و یا CreateHotelRoomAsync فراخوانی کرده و سپس پیامی را به کاربر نمایش داده و او را به صفحه‌ی نمایش لیست اتاق‌ها، هدایت می‌کنیم:




کدهای کامل این مطلب را از اینجا می‌توانید دریافت کنید: Blazor-5x-Part-15.zip
مطالب
طراحی گردش کاری با استفاده از State machines - قسمت دوم
معرفی کتابخانه stateless به عنوان جایگزین سبک وزنی برای Windows workflow foundation

کتابخانه سورس باز Stateless، برای طراحی و پیاده سازی «ماشین‌های حالت گردش کاری مانند» تهیه شده و مزایای زیر را نسبت به Windows workflow foundation دارا است:
- جمعا  30 کیلوبایت است!
- تمام اجزای آن سورس باز است.
- دارای API روان و ساده‌ای است.
- امکان تبدیل UML state diagrams، به نمونه معادل Stateless بسیار ساده و سریع است.
- به دلیل code first بودن، کار کردن با آن برای برنامه نویس‌ها ساده‌تر بوده و افزودن یا تغییر اجزای آن با کدنویسی به سادگی میسر است.

دریافت کتابخانه Stateless از Google code و یا از NuGet


پیاده سازی مثال کلید برق با Stateless

در ادامه همان مثال ساده  کلید برق قسمت قبل را با Stateless پیاده سازی خواهیم کرد:
using System;
using Stateless;

namespace StatelessTests
{
    class Program
    {
        static void Main(string[] args)
        {
            try
            {
                string on = "On", off = "Off";
                var space = ' ';

                var onOffSwitch = new StateMachine<string, char>(initialState: off);

                onOffSwitch.Configure(state: off).Permit(trigger: space, destinationState: on);
                onOffSwitch.Configure(state: on).Permit(trigger: space, destinationState: off);

                Console.WriteLine("Press <space> to toggle the switch. Any other key will raise an error.");

                while (true)
                {
                    Console.WriteLine("Switch is in state: " + onOffSwitch.State);
                    var pressed = Console.ReadKey(true).KeyChar;
                    onOffSwitch.Fire(trigger: pressed);
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine("Exception: " + ex.Message);
                Console.WriteLine("Press any key to continue...");
                Console.ReadKey(true);
            }
        }
    }
}
کار با ایجاد یک وهله از ماشین حالت (new StateMachine) آغاز می‌شود. حالت آغازین آن (initialState) مطابق مثال قسمت قبل، مساوی off است.
امضای کلاس StateMachine را در ذیل مشاهده می‌کنید؛ جهت توضیح آرگومان‌های جنریک string و char معرفی شده در مثال:
public class StateMachine<TState, TTrigger>
که اولی بیانگر نوع حالات قابل تعریف است و دومی نوع رویداد قابل دریافت را مشخص می‌کند.
برای مثال در اینجا حالات روشن و خاموش، با رشته‌های on و off مشخص شده‌اند و رویداد قابل قبول دریافتی، کاراکتر فاصله است.
سپس نیاز است این ماشین حالت را برای معرفی رویدادهایی (trigger در اینجا) که سبب تغییر حالت آن می‌شوند، تنظیم کنیم. اینکار توسط متدهای Configure و Permit انجام خواهد شد. متد Configure، یکی از حالات از پیش تعیین شده را جهت تنظیم، مشخص می‌کند و سپس در متد Permit تعیین خواهیم کرد که بر اساس رخدادی مشخص (برای مثال در اینجا فشرده شدن کلید space) وضعیت حالت جاری، به وضعیت جدیدی (destinationState) منتقل شود.
نهایتا این ماشین حالت در یک حلقه بی‌نهایت مشغول به کار خواهد شد. برای نمونه یک Thread پس زمینه (BackgroundWorker) نیز می‌تواند همین کار را در برنامه‌های ویندوزی انجام دهد.


یک نکته
علاوه بر روش‌های یاد شده‌ی تشخیص الگوی ماشین حالت که در قسمت قبل بررسی شدند، مورد refactoring انبوهی از if و elseها و یا switchهای بسیار طولانی را نیز می‌توان به این لیست افزود.



استفاده از Stateless Designer برای تولید کدهای ماشین حالت

کتابخانه Stateless دارای یک طراح و Code generator بصری سورس باز است که آن‌را به شکل افزونه‌ای برای VS.NET می‌توانید در سایت Codeplex دریافت کنید. این طراح از کتابخانه GLEE برای رسم گراف استفاده می‌کند.

کار مقدماتی با آن به نحو زیر است:
الف) فایل StatelessDesignerPackage.vsix را از سایت کدپلکس دریافت و نصب کنید. البته نگارش فعلی آن فقط با VS 2012 سازگار است.
ب) ارجاعی را به اسمبلی stateless به پروژه خود اضافه نمائید (به یک پروژه جدید یا از پیش موجود).
ج) از منوی پروژه، گزینه Add new item را انتخاب کرده و سپس در صفحه ظاهر شده، گزینه جدید Stateless state machine را انتخاب و به پروژه اضافه نمائید.
کار با این طراح، با ادیت XML آن شروع می‌شود. برای مثال گردش کاری ارسال و تائید یک مطلب جدید را در بلاگی فرضی، به نحو زیر وارد نمائید:
<statemachine xmlns="http://statelessdesigner.codeplex.com/Schema">
  <settings>
    <itemname>BlogPostStateMachine</itemname>
    <namespace>StatelessTests</namespace>
    <class>public</class>
  </settings>
  <triggers>     
    <trigger>Save</trigger>
    <trigger>RequireEdit</trigger>
    <trigger>Accept</trigger>
    <trigger>Reject</trigger>
  </triggers>
  <states>     
    <state start="yes">Begin</state>
    <state>InProgress</state>     
    <state>Published</state>      
    <state>Rejected</state>      
  </states>
  <transitions>
    <transition trigger="Save" from="Begin" to="InProgress" />

    <transition trigger="Accept" from="InProgress" to="Published" />
    <transition trigger="Reject" from="InProgress" to="Rejected" />

    <transition trigger="Save" from="InProgress" to="InProgress" />

    <transition trigger="RequireEdit" from="Published" to="InProgress" />
    <transition trigger="RequireEdit" from="Rejected" to="InProgress" />
  </transitions>
</statemachine>
حاصل آن گراف زیر خواهد بود:


به علاوه کدهای زیر که به صورت خودکار تولید شده‌اند:
using Stateless;

namespace StatelessTests
{
  public class BlogPostStateMachine
  {
    public delegate void UnhandledTriggerDelegate(State state, Trigger trigger);
    public delegate void EntryExitDelegate();
    public delegate bool GuardClauseDelegate();

    public enum Trigger
    {
      Save,
      RequireEdit,
      Accept,
      Reject,
    }

    public enum State
    {
      Begin,
      InProgress,
      Published,
      Rejected,
    }

    private readonly StateMachine<State, Trigger> stateMachine = null;

    public EntryExitDelegate OnBeginEntry = null;
    public EntryExitDelegate OnBeginExit = null;
    public EntryExitDelegate OnInProgressEntry = null;
    public EntryExitDelegate OnInProgressExit = null;
    public EntryExitDelegate OnPublishedEntry = null;
    public EntryExitDelegate OnPublishedExit = null;
    public EntryExitDelegate OnRejectedEntry = null;
    public EntryExitDelegate OnRejectedExit = null;
    public GuardClauseDelegate GuardClauseFromBeginToInProgressUsingTriggerSave = null;
    public GuardClauseDelegate GuardClauseFromInProgressToPublishedUsingTriggerAccept = null;
    public GuardClauseDelegate GuardClauseFromInProgressToRejectedUsingTriggerReject = null;
    public GuardClauseDelegate GuardClauseFromInProgressToInProgressUsingTriggerSave = null;
    public GuardClauseDelegate GuardClauseFromPublishedToInProgressUsingTriggerRequireEdit = null;
    public GuardClauseDelegate GuardClauseFromRejectedToInProgressUsingTriggerRequireEdit = null;
    public UnhandledTriggerDelegate OnUnhandledTrigger = null;

    public BlogPost()
    {
      stateMachine = new StateMachine<State, Trigger>(State.Begin);
      stateMachine.Configure(State.Begin)
        .OnEntry(() => { if (OnBeginEntry != null) OnBeginEntry(); })
        .OnExit(() => { if (OnBeginExit != null) OnBeginExit(); })
        .PermitIf(Trigger.Save, State.InProgress , () => { if (GuardClauseFromBeginToInProgressUsingTriggerSave != null) return GuardClauseFromBeginToInProgressUsingTriggerSave(); return true; } )
      ;
      stateMachine.Configure(State.InProgress)
        .OnEntry(() => { if (OnInProgressEntry != null) OnInProgressEntry(); })
        .OnExit(() => { if (OnInProgressExit != null) OnInProgressExit(); })
        .PermitIf(Trigger.Accept, State.Published , () => { if (GuardClauseFromInProgressToPublishedUsingTriggerAccept != null) return GuardClauseFromInProgressToPublishedUsingTriggerAccept(); return true; } )
        .PermitIf(Trigger.Reject, State.Rejected , () => { if (GuardClauseFromInProgressToRejectedUsingTriggerReject != null) return GuardClauseFromInProgressToRejectedUsingTriggerReject(); return true; } )
        .PermitReentryIf(Trigger.Save , () => { if (GuardClauseFromInProgressToInProgressUsingTriggerSave != null) return GuardClauseFromInProgressToInProgressUsingTriggerSave(); return true; } )
      ;
      stateMachine.Configure(State.Published)
        .OnEntry(() => { if (OnPublishedEntry != null) OnPublishedEntry(); })
        .OnExit(() => { if (OnPublishedExit != null) OnPublishedExit(); })
        .PermitIf(Trigger.RequireEdit, State.InProgress , () => { if (GuardClauseFromPublishedToInProgressUsingTriggerRequireEdit != null) return GuardClauseFromPublishedToInProgressUsingTriggerRequireEdit(); return true; } )
      ;
      stateMachine.Configure(State.Rejected)
        .OnEntry(() => { if (OnRejectedEntry != null) OnRejectedEntry(); })
        .OnExit(() => { if (OnRejectedExit != null) OnRejectedExit(); })
        .PermitIf(Trigger.RequireEdit, State.InProgress , () => { if (GuardClauseFromRejectedToInProgressUsingTriggerRequireEdit != null) return GuardClauseFromRejectedToInProgressUsingTriggerRequireEdit(); return true; } )
      ;
      stateMachine.OnUnhandledTrigger((state, trigger) => { if (OnUnhandledTrigger != null) OnUnhandledTrigger(state, trigger); });
    }

    public bool TryFireTrigger(Trigger trigger)
    {
      if (!stateMachine.CanFire(trigger))
      {
        return false;
      }
      stateMachine.Fire(trigger);
      return true;
    }

    public State GetState
    {
      get
      {
        return stateMachine.State;
      }
    }
  }
}
توضیحات:

ماشین حالت فوق دارای چهار حالت شروع، در حال بررسی، منتشر شده و رد شده است. معمول است که این چهار حالت را به شکل یک enum معرفی کنند که در کدهای تولیدی فوق نیز به همین نحو عمل گردیده و public enum State معرف چهار حالت ذکر شده است. همچنین رویدادهای ذخیره، نیاز به ویرایش، ویرایش، تائید و رد نیز توسط public enum Trigger معرفی شده‌اند.
در قسمت Transitions، بر اساس یک رویداد (Trigger در اینجا)، انتقال از یک حالت به حالتی دیگر را سبب خواهیم شد.
تعاریف اصلی تنظیمات ماشین حالت، در سازنده کلاس BlogPostStateMachine انجام شده است. این تعاریف نیز بسیار ساده هستند. به ازای هر حالت، یک Configure داریم. در متدهای OnEntry و OnExit هر حالت، یک سری callback function فراخوانی خواهند شد. برای مثال در حالت Rejected یا Approved می‌توان ایمیلی را به ارسال کننده مطلب جهت یادآوری وضعیت رخ داده، ارسال نمود.
متدهای PermitIf سبب انتقال شرطی، به حالتی دیگر خواهند شد. برای مثال رد یا تائید یک مطلب نیاز به دسترسی مدیریتی خواهد داشت. این نوع موارد را توسط delgateهای Guard ایی که برای مدیریت شرط‌ها ایجاد کرده است، می‌توان تنظیم کرد. PermitReentryIf سبب بازگشت مجدد به همان حالت می‌گردد. برای مثال ویرایش و ذخیره یک مطلب در حال انتشار، سبب تائید یا رد آن نخواهد شد؛ صرفا عملیات ذخیره صورت گرفته و ماشین حالت مجددا در همان مرحله باقی خواهد ماند.

نحوه استفاده از ماشین حالت تولیدی:
همانطور که عنوان شد، حداقل استفاده از ماشین‌های حالت، refactoing انبوهی از if و else‌ها است که در حالت مدیریت یک چنین گردش‌های کاری باید تدارک دید.
namespace StatelessTests
{
    public class BlogPostManager
    {
        private BlogPostStateMachine _stateMachine;
        public BlogPostManager()
        {
            configureWorkflow();
        }

        private void configureWorkflow()
        {
            _stateMachine = new BlogPostStateMachine();

            _stateMachine.GuardClauseFromBeginToInProgressUsingTriggerSave = () => { return UserCanPost; };
            _stateMachine.OnBeginExit = () => { /* save data + save state + send an email to admin */ };

            _stateMachine.GuardClauseFromInProgressToPublishedUsingTriggerAccept = () => { return UserIsAdmin; };
            _stateMachine.GuardClauseFromInProgressToRejectedUsingTriggerReject = () => { return UserIsAdmin; };
            _stateMachine.GuardClauseFromInProgressToInProgressUsingTriggerSave = () => { return UserHasEditRights; };
            _stateMachine.OnInProgressExit = () => { /* save data + save state + send an email to user */ };

            _stateMachine.OnPublishedExit = () => { /* save data + save state + send an email to admin */ };
            _stateMachine.GuardClauseFromPublishedToInProgressUsingTriggerRequireEdit = () => { return UserHasEditRights; };

            _stateMachine.OnRejectedExit = () => { /* save data + save state + send an email to admin */ };
            _stateMachine.GuardClauseFromRejectedToInProgressUsingTriggerRequireEdit = () => { return UserHasEditRights; };
        }

        public bool UserIsAdmin
        {
            get
            {
                return true; // TODO: Evaluate if user is an admin.
            }
        }

        public bool UserCanPost
        {
            get
            {
                return true; // TODO: Evaluate if user is authenticated
            }
        }

        public bool UserHasEditRights
        {
            get
            {
                return true; // TODO: Evaluate if user is owner or admin
            }
        }

        // User actions
        public void Save() { _stateMachine.TryFireTrigger(BlogPostStateMachine.Trigger.Save); }
        public void RequireEdit() { _stateMachine.TryFireTrigger(BlogPostStateMachine.Trigger.RequireEdit); }

        // Admin actions        
        public void Accept() { _stateMachine.TryFireTrigger(BlogPostStateMachine.Trigger.Accept); }
        public void Reject() { _stateMachine.TryFireTrigger(BlogPostStateMachine.Trigger.Reject); }
    }
}
در کلاس فوق، نحوه استفاده از ماشین حالت تولیدی را مشاهده می‌کنید. در delegateهای Guard، سطوح دسترسی انجام عملیات بررسی خواهند شد. برای مثال، از بانک اطلاعاتی بر اساس اطلاعات کاربر جاری وارد شده به سیستم اخذ می‌گردند. در متدهای Exit هر مرحله، کارهای ذخیره سازی اطلاعات در بانک اطلاعاتی، ذخیره سازی حالت (مثلا در یک فیلد که بعدا قابل بازیابی باشد) صورت می‌گیرد و در صورت نیاز ایمیلی به اشخاص مختلف ارسال خواهد شد.
برای به حرکت درآوردن این ماشین، نیاز به یک سری اکشن متد نیز می‌باشد. تعدادی از این موارد را در انتهای کلاس فوق ملاحظه می‌کنید. کد نویسی آن‌ها در حد فراخوانی متد TryFireTrigger ماشین حالت است.

یک نکته:
ماشین حالت تولیدی به صورت پیش فرض در حالت State.Begin قرار دارد. می‌توان این مورد را از بانک اطلاعاتی خواند و سپس مقدار دهی نمود تا با هر بار وهله سازی ماشین حالت دقیقا مشخص باشد که در چه مرحله‌ای قرار داریم و TryFireTrigger بتواند بر این اساس تصمیم‌گیری کند که آیا مجاز است عملیاتی را انجام دهد یا خیر.