بازخوردهای پروژه‌ها
استفاده از table لیست و pagedlist
با سلام و خسته نباشید ، چطور میشه در این پروژه از تیبل بصورت معمول یعنی سطر و ستون داشته باشه به همراه paged list استفاده کرد؟
مطالب
آموزش فایرباگ - #10 - DOM Panel
در پنل DOM توابع و متغییرهایی که در صفحه وجود دارند بصورت درختی نمایش داده می‌شوند. object‌ها و array‌ها قابل باز شدن هستند و بصورت درختی می‌توانید محتویات آن‌ها را مشاهده کنید. توابع هم بصورت لینک هستند که با کلیک برون آن ها، کد مربوط در پنل Script نمایش داده می‌شود. توجه کنید که محتویاتی که مشاهده می‌کنید برای همان لحظه ای است که پنل را باز کردید و برای مشاهده تغییرات ثانویه باید محتویات پنل را بروزرسانی کرد.

قبل از بررسی این پنل اجازه دهید نگاهی به تعریف DOM بیندازیم.

DOM چیست؟
مدل شیءگرای سند یا دام (DOM - Document Object Model) عنوان یکی از دو ساختوارۀ (architecture) اصلی است (در کنار اس‌اِی‌اکس) که بر اساس آن سندهای اکس‌ام‌ال را به اشیایی که در بردارندهٔ آن است، تجزیه نموده، و آن‌ها را به‌صورت یک ساختار درختی داده‌ها در فضای حافظه اصلی پهن می‌کند. ساختوارۀ دام، نه به زبان برنامه‌نویسی خاصّی وابستگی دارد و نه به سکّوی برنامه‌نویسی ویژه‌ای، بلکه، به منظور اجراء و پیاده‌سازی آن باید از یک زبان برنامه‌نویسی بلندتراز همچون جاوا، سی‌شارپ، جاوااسکریپت یا مشابه آن‌ها سود بجوییم. آنسوی رابط کاربر سند با مدلی شیءگرا نمایانده می‌شود. 


Options Menu
این منو با راست کلیک کردن بروی نام پنل یا کلیک کردن بروی مثلثی که روی پنل قرار دارد، نمایش داده می‌شود.

  • Show User-defined Properties
    در صورت فعال بودن، پراپرتی هایی که توسط کاربر به صفحه اضافه شده اند را نمایش می‌دهد.
  • Show User-defined Functions
    در صورت فعال بودن، توابعی که توسط کاربر به صفحه اضافه شده اند را نمایش می‌دهد.
  • Show DOM Properties
    در صورت فعال بودن، پراپرتی هایی که بصورت پیشفرض در DOM وجود دارند را نمایش می‌دهد.
  • Show DOM Functions
     در صورت فعال بودن، توابعی که بصورت پیشفرض در DOM وجود دارند را نمایش می‌دهد.
  • Show DOM Constants
    در صورت فعال بودن، const هایی که بصورت پیشفرض در DOM وجود دارند را نمایش می‌دهد.
  •  Show Inline Event Handlers
    در صورت فعال بودن، رویدادهایی که بصورت خطی در تگ‌ها تعریف شده اند را نمایش می‌دهد.
  • Show Closures
    در صورت فعال بودن، Closure‌ها را نمایش می‌دهد.
  • Show Own Properties Only
    در صورت فعال بودن، فقط پراپرتی هایی که بروی خود شئ تعریف شده اند را نمایش می‌دهد.
  • Show Enumerable Properties Only
    در صورت فعال بودن، فقط پراپرتی‌های شمارشی را نمایش می‌دهد.
  • Refresh
    محتویات پنل را بروزرسانی می‌کند.


Property Path
این قسمت در بالاترین بخش پنل قرار دارد و وظیفه‌ی آن نمایش مسیر شئ از خود شئ تا window است.
همچنین با راست کلیک کردن بروی این قسمت دو گزینه نمایش داده می‌شود. Refresh برای بروزسازی آدرس نمایش داده شده و Use in Command Line هم برای استفاده از شئ در خط فرمان است. پس از راست کلیک کردن بروی یک شئ و انتخاب گزینه‌ی Use in Command Line فایرباگ تمرکز برنامه را به خط فرمان منتقل می‌کند و شئ را تحت متغییری به نام $p در خط فرمان کپی می‌کند.


رنگ ها
برای مشخص کردن نوع متغییرهای این پنل، فایرباگ برای هر نوع متغییر از یک رنگ استفاده می‌کند.
اشیاء
، اشیاء DOM ، توابع Getter ، توابع تعریفی کاربر ، توابع DOM ، توابع Constructor ، پراپرتی‌های Read-only 


Auto-Completion
مشابه پنل‌های Console, CSS, HTML در این پنل هم امکان اعمال تغییرات همراه با قابلیت تکمیل خودکار وجود دارد.


localStorage
در HTML5 سیستمی برای ذخیره مقادیر در سمت کاربر، به نام localStorage معرفی شد. در این پنل می‌توانید محتویات آن را بررسی/ویرایش کنید. برای کار با توابع آن هم می‌توانید از پنل Console استفاده کنید. ( همچنین می‌توانید از پنل Console بصورت Popup در این پنل و پنل‌های دیگر هم استفاده کنید. به تصویر زیر توجه فرمایید. )
توجه کنید که برای مشاهده‌ی این شئ، باید گزینه‌ی Show DOM Properties فعال باشد و همیچنین در Property Path، شئ window فعال باشد.

Breakpoint Column
شما می‌توانید با کلیک بروی ستون سمت چپ پراپرتی ها، آن پراپرتی را تحت نظر گرفته و در صورت تغییر یافتن مقدار آن پراپرتی، کنترل روند اجرای برنامه از همان نقطه را بدست بگیرید. به این صورت که زمانی که کدی پراپرتی موردنظر را تغییر دهد، پنل Script روند اجرای کد را در همان قسمت متوقف می‌کند.
( ممکن است فایرباگ بصورت خودکار به پنل Script سوئیچ نکند و بعد از متوقف شدن برنامه هم اگر به پنل Script سوئیچ کنید نتیجه را نبینید. پس بهتر است قبل از تغییر یافتن پراپرتی مورد نظر و بعد از قرار دادن Breakpoint به پنل Script بروید. )


Context Menu
با راست کلیک کردن در قسمت‌های مختلف پنل، منوهای متفاوتی را خواهید دید. همچنین با راست کلیک کردن بروی مقادیر پراپرتی ها، منوی متناسب با آن مقدار را خواهید دید. مثلا اگر بروی یک تگ HTML در این پنل راست کلیک کنید، منویی که خواهید دید همان منویی است که در پنل HTML مشاهده می‌کردید.

 گزینه Context
توضیحات
Copy Name
Property List نام پراپرتی را در حافظه کپی می‌کند.
Copy Path Property List آدرس پراپرتی را در حافظه کپی می‌کند.
Copy Value
String and Number values محتوای پراپرتی را در حافظه کپی می‌کند.
Edit Property... Property List ( پراپرتی و توابع کاربر )
پراپرتی را به حالت ویرایش می‌آورد.
Delete Property Property List ( پراپرتی و توابع کاربر ) پراپرتی را حذف می‌کند.
Break On Property Change Property List ( پراپرتی و توابع کاربر ) مشابه پاراگرف قبلی.
Refresh Property List, Property Path محتویات پنل را بروزرسانی می‌کند.

برای توابع هم دو منوی اضافی وجود دارد:

    • ‪ Log Calls to "<function name>"
      فراخوانی‌های تابع مورد نظر را Log می‌کند. ( برای توضیحات بیشتر دستور monitor که از توابع خط فرمان است را ملاحظه بفرمایید. )
    • Copy Function
      نام و بنده‌ی تابع را در حافظه کپی می‌کند.
اشتراک‌ها
ذخیره سازی داده ها در کلاینت، ساده و سریع

localForage is a fast and simple storage library for JavaScript.localForage uses localStorage in browsers with no IndexedDB or WebSQL.

localforage.setItem('key', 'value', function (err) {
  // if err is non-null, we got an error
  localforage.getItem('key', function (err, value) {
    // if err is non-null, we got an error. otherwise, value is the value
  });
});

:Framework Support

AngularJS

  Angular 4 and up

  Backbone

  Ember

  Vue 

TypeScript:

import localForage from "localforage"; 

ذخیره سازی داده ها در کلاینت، ساده و سریع
مطالب
React 16x - قسمت 24 - ارتباط با سرور - بخش 3 - نکات تکمیلی کار با Axios
پس از آشنایی با مقدمات کار با Axios، در این قسمت امکانات پیشرفته‌تر آن‌را مانند خطایابی سراسری، interceptors  و ... بررسی می‌کنیم.


به روز رسانی‌های خوشبینانه‌ی UI

پیاده سازی اعمال CRUD توسط Axios در قسمت قبل، به همراه یک مشکل مهم است: اعمال کار با شبکه و سرور، زمانبر هستند و مدتی طول می‌کشد تا پاسخ عملیات از سمت سرور دریافت شود. در این بین اگر خطایی رخ دهد، مابقی کدهای نوشته شده‌ی در متدهایی مانند Update و Delete، اجرا نمی‌شوند. به این حالت «به روز رسانی بدبینانه‌ی UI» گفته می‌شود. در حالت خوشبینانه، فرض بر این است که در اکثر موارد، فراخوانی سرور با موفقیت به پایان می‌رسد. در یک چنین حالتی، ابتدا UI به روز رسانی می‌شود و سپس فراخوانی‌های سمت سرور صورت می‌گیرند. اگر این فراخوانی با شکست مواجه شد، مجددا UI را به حالت قبلی آن باز می‌گردانیم:
  handleDelete = async post => {
    const posts = this.state.posts.filter(item => item.id !== post.id);
    this.setState({ posts });

    await axios.delete(`${apiEndpoint}/${post.id}`);
  };
در کدهای فوق، ابتدا UI به روز رسانی می‌شود (که بسیار سریع است)، سپس حذف سمت سرور صورت می‌گیرد. یک چنین پیاده سازی، به کاربر حس کار با یک برنامه‌ی بسیار سریع را القاء می‌کند؛ هرچند فراخوانی سمت سرور انجام شده، ممکن است مدتی طول بکشد.
اما اگر در این بین خطایی رخ داد، چه باید کرد؟ باید آخرین تغییر انجام شده را به حالت اول باز گرداند. انجام یک چنین کاری در React ساده‌است. چون ما state را به صورت مستقیم ویرایش نمی‌کنیم، همیشه می‌توان ارجاعی را به state قبلی، ذخیره و سپس در صورت نیاز آن‌را بازیابی کرد:
  handleDelete = async post => {
    const originalPosts = this.state.posts;

    const posts = this.state.posts.filter(item => item.id !== post.id);
    this.setState({ posts }); // Optimistic Update

    try {
      await axios.delete(`${apiEndpoint}/${post.id}`);
    } catch (ex) {
      alert("An error occurred when deleting a post!");
      this.setState({ posts: originalPosts }); // Undo changes
    }
  };
در اینجا در ابتدا توسط متغیر originalPosts، ارجاعی را به وضعیت قبلی آرایه‌ی posts موجود در state (وضعیت ابتدایی UI)، نگهداری می‌کنیم. سپس کار حذف بسیار سریع آیتم درخواستی را از UI انجام می‌دهیم. اکنون کار حذف اصلی رکورد را از سرور، درون یک try/catch انجام خواهیم داد. اگر خطایی رخ دهد، پیامی را به کاربر نمایش داده و سپس مجددا state را به همان originalPosts پیشین، باز خواهیم گرداند.


مدیریت خطاهای رخ داده‌ی در حین فراخوانی سرور

تا اینجا مشاهده کردیم که یک روش مدیریت خطاها در کدهای Axios، قرار دادن آن‌ها در یک قطعه کد try/catch است. در اینجا نیز باید بتوان بین خطاهای پیش بینی شده و نشده، تفاوت قائل شد.
- خطاهای پیش بینی شده: برای مثال اگر درخواست حذف رکوردی را دادیم که در بانک اطلاعاتی موجود نیست، انتظار داریم سرور، خطای 404 یا return NotFound را بازگشت دهد و یا 400 که معادل bad request است و در حالت ارسال داده‌هایی غیرمعتبر، رخ می‌دهد. در این موارد بهتر است خطاهایی خاص را به کاربران نمایش داد؛ برای مثال رکورد درخواستی وجود ندارد یا پیشتر حذف شده‌است.
- خطاهای پیش بینی نشده: این نوع خطاها نباید و یا قرار نیست در شرایط عادی رخ دهند. برای مثال اگر شبکه در دسترس نیست، امکان ارتباط با سرور نیز میسر نخواهد بود و یا حتی ممکن است خطایی در کدهای سمت سرور، سبب بروز خطایی شده باشد. این نوع خطاها ابتدا باید لاگ شوند تا با بررسی‌های آتی آن‌ها، بتوان مشکلات پیش بینی نشده را بهتر برطرف کرد. همچنین در یک چنین مواردی، باید یک پیام خطای خیلی عمومی را به کاربر نمایش داد؛ برای مثال «یک خطای پیش بینی نشده رخ داده‌است.».

برای مدیریت این دو حالت باید به جزئیات شیء ex، در بدنه‌ی catch، دقت کرد که دارای دو خاصیت request و response است. اگر ex.response تنظیم شده بود، یعنی دریافت خروجی از سرور موفقیت آمیز بوده‌است. اگر سرور در دسترس نباشد و یا برنامه‌ی سمت سرور کرش کرده باشد، ex.response نال خواهد بود. اگر ex.request نال نبود، یعنی ارسال درخواست به سمت سرور با موفقیت انجام شده‌است. برای مثال جهت بررسی خطای مورد انتظار 404، می‌توان در قسمت catch(ex) به صورت زیر عمل کرد:
try {
  await axios.delete(`${apiEndpoint}/${post.id}`);
} catch (ex) {
  if (ex.response && ex.response.status === 404) {
     alert("This post has already been deleted!");
  } else {
     console.log("Error", ex);
     alert("An unexpected error occurred when deleting a post!");
  }

  this.setState({ posts: originalPosts }); // Undo changes
}
در اینجا ابتدا بررسی می‌شود که آیا شیء response نال است یا خیر؟ سپس خاصیت status آن‌را برای بررسی خطاهای پیش بینی شده، بررسی می‌کنیم. خطایی که در اینجا نمایش داده می‌شود، اختصاصی‌تر است. در غیراینصورت، ابتدا باید این خطا لاگ شود و سپس یک اخطار عمومی نمایش داده می‌شود. پس از بررسی هر دو حالت، باید UI را مجددا به حالت اول آن بازگشت داد.
عموما خطاهای پیش‌بینی شده را لاگ نمی‌کنیم؛ چون ممکن است کاربر، یک صفحه را در چندین برگه باز کرده باشد و در یکی، رکوردی را حذف کند. در این حال، این رکورد هنوز در برگه‌های دیگر موجود است و اگر مجددا درخواست حذف آن‌را صادر کند، مشکل خاصی از دیدگاه برنامه رخ نداده‌است و نیازی به پیگیری‌های آتی را ندارد. یعنی صرفا یک client error است.


مدیریت سراسری خطاهای رخ داده‌ی در حین فراخوانی سرور

برای مدیریت خطاها، نیاز است یک چنین try/catchهایی را در تمام قسمت‌های برنامه که با سرور کار می‌کنند، قرار دهیم. برای کاهش این کدهای تکراری، از interceptors کتابخانه‌ی Axios استفاده می‌شود. در این کتابخانه می‌توان در جاهائیکه درخواستی به سمت سرور ارسال می‌شود و یا پاسخی از سمت سرور دریافت می‌شود، قطعه کدهایی سراسری را قرار داد و بر روی درخواست و یا پاسخ، تغییراتی را اعمال کرد و یا حتی اطلاعات مربوطه را لاگ کرد؛ به این نوع قطعه کدها، interceptor گفته می‌شود و برای تعریف آن‌ها می‌توان از axios.interceptors.request و یا axios.interceptors.response، خارج از کلاس جاری استفاده کرد. برای مثال بر روی شیء axios.interceptors.response، می‌توان متد use را فراخوانی کرد که دو پارامتر را که هر کدام یک callback function هستند، می‌پذیرد. اولی در صورت موفقیت آمیز بودن response فراخوانی می‌شود و دومی در صورت شکست آن. اگر نیازی به هر کدام نبود، می‌توان آن‌را به null مقدار دهی کرد. اگر مدیریت قسمت شکست علمیات مدنظر است، نیاز خواهد بود در پایان این callback function، یک Rejected Promise را بازگشت داد تا ادامه‌ی برنامه، به درستی مدیریت شود. در این حالت اگر خطایی رخ دهد، ابتدا این interceptor فراخوانی می‌شود و سپس کنترل به بدنه‌ی catch منتقل خواهد شد:
import "./App.css";

import axios from "axios";
import React, { Component } from "react";

axios.interceptors.response.use(null, error => {
  console.log("interceptor called.");
  return Promise.reject(error);
});

const apiEndpoint = "https://localhost:5001/api/posts";
class App extends Component {
اکنون می‌خواهیم قطعه کد نمایش خطاهای عمومی پیش بینی نشده را از تمام بدنه‌های catch حذف کرده و به یک interceptor منتقل کنیم:
axios.interceptors.response.use(null, error => {
  const expectedError =
    error.response &&
    error.response.status >= 400 &&
    error.response.status < 500;

  if (!expectedError) {
    console.log("Error", error);
    alert("An unexpected error occurred when deleting a post!");
  }

  return Promise.reject(error);
});
خطاهای پیش بینی شده عموما در بازه‌ی 400 تا 500 قرار دارند. به همین جهت اگر یک چنین خطاهایی را دریافت کردیم، اخطاری را نمایش نداده و صرفا کنترل را به catch block منتقل می‌کنیم. اما اگر خطا، پیش بینی نشده بود، کار لاگ کردن خطا و همچنین نمایش اخطار را در اینجا انجام خواهیم داد.

یک نکته: استفاده از try/catchها فقط برای بازگشت UI به حالت قبلی و یا نمایش خطایی خاص به کاربر توصیه می‌شوند. اگر از روش «به روز رسانی‌های خوشبینانه‌ی UI» استفاده نمی‌کنید و همچنین خطاهای ویژه‌ای بجز خطای عمومی لاگ شده‌ی در interceptor فوق مدنظر شما نیست، نیازی هم به try/catch نخواهد بود و پس از بروز خطا، قسمت‌های بعدی کد اجرا نمی‌شوند؛ اما خطای عمومی فوق نمایش داده خواهد شد.


ایجاد یک HTTP Service با قابلیت استفاده‌ی مجدد

تا اینجا تعریف interceptor را پیش از کلاس کامپوننت جاری قرار داده‌ایم که هم سبب شلوغی این ماژول شده‌است و هم در صورت نیاز به آن در سایر برنامه‌ها، باید همین قطعه کد را مجددا در آن‌ها کپی کرد. به همین جهت پوشه‌ی جدید src\services را ایجاد کرده و سپس فایل src\services\httpService.js را در آن با محتوای زیر ایجاد می‌کنیم:
import axios from "axios";

axios.interceptors.response.use(null, error => {
  const expectedError =
    error.response &&
    error.response.status >= 400 &&
    error.response.status < 500;

  if (!expectedError) {
    console.log("Error", error);
    alert("An unexpected error occurred when deleting a post!");
  }

  return Promise.reject(error);
});

export default {
  get: axios.get,
  post: axios.post,
  put: axios.put,
  delete: axios.delete
};
در اینجا علاوه بر انتقال interceptor تعریف شده، کار export متدهای axios نیز به صورت یک شیء جدید صورت گرفته‌است.
سپس به app.js مراجعه کرده و این ماژول را با یک نام دلخواه import می‌کنیم:
import http from "./services/httpService";
در ادامه هرجائیکه ارجاعی به axios وجود دارد، آن‌را با http فوق جایگزین می‌کنیم. در این حالت می‌توان "import axios from "axios را نیز از ابتدای app.js حذف کرد. مزیت اینکار، مخفی کردن Axios، در پشت صحنه‌ی ماژول جدیدی است که ایجاد کردیم. به این ترتیب اگر در آینده خواستیم، Axios را با کتابخانه‌ی دیگری جایگزین کنیم، در کل برنامه تنها نیاز است این httpService.js جدید را تغییر دهیم.


ایجاد یک ماژول Config

بهبود دیگری را که می‌توانیم اعمال کنیم، انتقال const apiEndpoint تعریف شده، به یک ماژول مجزا است؛ تا اگر نیاز به استفاده‌ی از آن در قسمت‌های دیگری نیز وجود داشت، به سادگی بتوان آن‌را مدیریت کرد. به همین جهت فایل جدید src\config.json را با محتوای زیر ایجاد می‌کنیم:
{
   "apiEndpoint" : "https://localhost:5001/api/posts"
}
سپس به فایل app.js بازگشته و ابتدا const apiEndpoint را حذف و سپس import زیر را به ابتدای فایل، اضافه می‌کنیم:
import config from "./config.json";
اکنون هر جائی در کدهای خود که apiEndpoint را داریم، تبدیل به config.apiEndpoint می‌کنیم.


نمایش بهتر خطاها به کاربر توسط کتابخانه‌ی react-toastify

بجای alert توکار مرورگرها، می‌توان یک صفحه‌ی دیالوگ زیباتر را برای نمایش خطاها درنظر گرفت. به همین جهت ابتدا کتابخانه‌ی react-toastify را نصب می‌کنیم:
> npm i react-toastify --save
سپس به فایل app.js مراجعه کرده و importهای لازم آن‌را اضافه می‌کنیم:
import { ToastContainer } from "react-toastify";
import "react-toastify/dist/ReactToastify.css";
همچنین نیاز است ToastContainer را به ابتدای متد render نیز اضافه کرد:
  render() {
    return (
      <React.Fragment>
        <ToastContainer />
اکنون به src\services\httpService.js مراجعه کرده و alert آن‌را به صورت زیر تغییر می‌دهیم:
import { toast } from "react-toastify";
// ...

axios.interceptors.response.use(null, error => {
  // ...
  if (!expectedError) {
    // ...
    toast.error("An unexpected error occurrred.");
  }
ابتدا، شیء toast آن import می‌شود و سپس توسط این شیء می‌توان از متد error آن، جهت نمایش خطاهایی شکیل‌تر استفاده کرد؛ با این خروجی:



کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید: sample-22-backend-part-03.zip و sample-22-frontend-part-03.zip
مطالب
آشنایی با WebDav و نحوه استفاده از آن

WebDAV  استانداردی است بر روی پروتکل HTTP که Request‌ها و Responseهای مدیریت یک فایل را بر روی سرویس دهنده وب، تشریح می‌کند.

برای درک چرایی وجود این استاندارد بهتر است ذهن خود را معطوف به نحوه‌ی عملکرد سیستم فایل در OS کنیم که شامل API‌های خاص برای دسترسی نرم افزارهای گوناگون به فایل‌های روی یک سیستم است.
حال فکر کنید یک سرور Cloud راه اندازی نموده‌اید که قرار است مدیریت فایل‌ها و پرونده‌های Office را بر عهده داشته باشد و چون امکان ویرایش اسناد Office بر روی وب را ندارید، نیاز است تا اجازه دهید نرم افزار‌های Office مستقیما فایل‌ها را از روی سرور شما باز کنند و بعد از تغییرات، به جای ذخیره در سیستم local، محتوا را به فایل روی سرور ارسال کنند.
در مفهوم web عملا این کار غیر استاندارد و نادرست است. همه درخواست‌ها و جواب‌ها باید بر روی پروتکل Http باشند. خوب حال تصور کنید نرم افزارهای Office قابلیت آن را داشته باشند که به جای تحویل محتوا به سیستم عامل برای ذخیره‌ی آن بر روی سیستم local، محتوا را به یک آدرس ارسال نمایند و پشت آن آدرس، متدی باشد که بتواند به درخواست رسیده، به درستی پاسخ دهد.
این یعنی باید سمت سرور متدی با قابلیت ارسال پاسخ‌های درست و در سمت کلاینت نرم افزاری با قابلیت ارسال درخواست‌های مناسب وجود داشته باشد.
WebDAV استاندارد تشریح محتوای درخواست‌ها و پاسخ‌های مربوط به مدیریت فایل‌ها است.
خوشبختانه نرم افزارهای Office و بسیاری از نرم افزار‌های دیگر، استاندارد WebDAV را پشتیبانی می‌کنند و فقط لازم است برای سرورتان متدی با قابلیت پشتیبانی از درخواست‌های WebDAV پیاده سازی نمایید و البته متاسفانه کتابخانه‌های سورس باز چندانی برای WebDAV در سرور دات نت وجود ندارد. من ماژولی را برای کار با WebDAV نوشتم و سورسش را در Git  قرار دادم. برای این مثال هم از همین کتابخانه  استفاده می‌کنم.
ابتدا یک پروژه‌ی وب MVC ایجاد نمایید و پکیج xDav را از nugget نصب کنید.

PM> Install-Package xDav

اگر به web.config نگاهی بیاندازیم می‌بینیم یک module به نام xDav به وب سرور اضافه شده که بررسی درخواست‌های WebDAV  را به عهده دارد.

<system.webServer>
        <modules>
            <add name="XDav" type="XDav.XDavModule, XDav" />
        </modules>
  </system.webServer>

همچنین یک Section جدید هم به config برای پیکربندی xDav اضافه شده است. 

  <XDavConfig Name="xdav">
    <FileLocation URL="xdav" PathType="Local"></FileLocation>
  </XDavConfig>

خاصیت Name برای xDav نشانگر درخواست‌هایی است که باید توسط این ماژول اجرا شوند. در اینجا یعنی درخواست‌هایی که آدرس آن‌ها شامل "/xdav/" باشد، توسط این ماژول Handle می‌شوند. عبارت بعد از مقدار Name در URL هم طبیعتا نام فایل مورد نظر شماست.

FileLocation آدرس پوشه ای است که فایل‌ها در آن ذخیره و یا بازخوانی می‌شوند. اگر FileType با مقدار Local تنظیم شود، یعنی باید یک پوشه به نام خاصیت URL که در اینجا  xdav است در پوشه‌ی اصلی وب شما وجود داشته باشد و اگر با Server مقدار دهی شود URL باید یک آدرس فیزیکی بر روی سرور داشته باشد . مثل "c:\webdav"URL=

ما در این مثال مقادیر را به صورت پیش‌فرض نگه می‌داریم. یعنی باید در پوشه‌ی وب، یک Folder با نام xdav ایجاد کنیم.

در ادامه چند فایل word را برای تست در این پوشه کپی می‌کنم.

می خواهیم در صفحه Index، لیستی از فایل‌های درون این پوشه را نمایش دهیم طوری که در صورت کلیک بر روی هر کدام از آن‌ها، آدرس WebDav فایل مورد نظر را به Word ارسال کنیم.

بعد از نصب Office، در registry چند نوع Url تعریف می‌شود که معرف اپلیکیشنی است که آدرس به آن فرستاده شود. این دقیقا همان چیزیست که ما به آن نیاز داریم. کافیست آدرس WebDav فایل را بعد از عبارت " ms-word:ofe|u| " در یک لینک قرار دهیم تا آدرس به نرم افزار Word ارسال شود. یعنی آدرس URL باید این شکلی باشد:

  ms-word:ofe|u|http://Webaddress/xdav/filename

Webaddress آدرس وبسایت و filename نام فایل مورد نظرمان است. عبارت /xdav/ هم که نشان می‌دهد درخواست هایی که این آدرس را دارند باید توسط ماژول xDav پردازش شوند.

کلاسی با نام DavFile در پوشه‌ی Model ایجاد می‌کنم: 

public class DavFile
    {
        public string Name { get; set; }
        
        public string Href(string webAddress)
        {
            return string.Format("ms-word:ofe|u|http://{0}/xdav/{1}", webAddress, Name);
        }
    }

اکشن متد Index را در Home Controller، مانند زیر تغییر دهید:

var dir = new DirectoryInfo(XDav.Config.ConfigManager.DavPath);
            var model = dir.GetFiles().ToList()
                                      .Select(f => 
                                          new DavFile() { 
                                            Name = f.Name
                                        });
            return View(model);

یک لیست از فایل هایی که در پوشه‌ی webDav قرار دارند تهیه می‌کنیم و به View ارسال می‌کنیم. View را هم مثل زیر بازنویسی می‌کنیم. 

@model IEnumerable<WebDavServer.Models.DavFile>
<h1>
    File List
</h1>

<ul>
    @foreach (var item in Model)
    {
        <li> <a href="@Html.Raw(item.Href(ViewContext.HttpContext.Request.Url.Authority))">  @Html.Raw(item.Name) </a></li>
    }
</ul>

قرار است به ازای هر فایل، لینکی نمایش داده شود که با کلیک بر روی آن، آدرس فایل به word ارسال می‌شود. بعد از ثبت تغییرات، word محتوا را به همان آدرس ارسال می‌کند و ماژول xDav محتوا را در فایل فیزیکی سرور ذخیره خواهد کرد.

برنامه را اجرا کنید و بر روی فایل‌ها کلیک نمایید. اگر نرم افزار Office روی کامپیوترتان باز باشد با کلیک بر روی هر کدام از فایل‌ها، فایل word باز شده و می‌توایند محتوا را تغییر داده و ذخیره نمایید.

نرم افزار کلاینت (word) درخواست هایی با verb‌های مشخص که در استاندارد WebDav ذکر شده به آدرس مورد نظر می‌فرستد.  سرور WebDav درخواست را بر اساس Verb آن Request پردازش کرده و Response استاندارد را ایجاد میکند.

نرم افزار word پس از دریافت یک URL، به جای فرمت فیزیکی فایل، درخواست هایی را با تایپ‌های Option, Head, lock, get, post و unlock ارسال می‌کند. محتوای درخواست و پاسخ هر کدام از تایپ‌ها در استاندارد webDav تعریف شده و ماژول xDav آن را پیاده سازی نموده است.

  دریافت پروژه مثال

نظرات مطالب
روش‌هایی برای بهبود سرعت برنامه‌های مبتنی بر Entity framework
به نظر شما کوئری‌های پایین رو چطور میشه بهینه نوشت؟
List<Stat> allQuestion = (from a in TempClass.Stats
         where a.Person.PersonID == TempClass.ActiveUser.PersonID && 
               a.Subject.SubjectID == tileNumber
         select a).AsParallel().ToList();

int allQuestionCount = allQuestion.Count;

int correctCount = (from a in allQuestion
                    where a.Person.PersonID == TempClass.ActiveUser.PersonID && 
                          a.Subject.SubjectID == tileNumber
                    select a.CorrectQuestionCount).Sum();

int totalTime = (from a in allQuestion
                 where a.Person.PersonID == TempClass.ActiveUser.PersonID && 
                       a.Subject.SubjectID == tileNumber
                 select a.TotalTime).Sum();

double score = (from a in allQuestion
                where a.Person.PersonID == TempClass.ActiveUser.PersonID && 
                      a.Subject.SubjectID == tileNumber
                select a.Score).Sum(); 
50 بار دستورات بالا اجرا میشه و یک مکث حدودا 20 ثانیه‌ای داره
نظرات مطالب
ارتقاء به ASP.NET Core 1.0 - قسمت 13 - معرفی View Components
- همانطور که در مقدمه‌ی بحث هم عنوان شد، مفهوم Child Actions از نگارش Core حذف شده‌است؛ چون مشکلات زیادی دارد (به این لیست، مشکلات async و همچنین آغاز یک چرخه‌ی جدید MVC را هم اضافه کنید که کارآیی مناسبی ندارد و یک سربار به شمار می‌رود).
- جایگزین آن ViewComponet است و از لحاظ دسترسی به منابع و سرویس‌ها محدودیتی ندارد. یک مثال
- هنوز هم اگر صرفا نیاز به رندر یک پارشال View را به صورت Ajax ایی دارید، روش زیر کار می‌کند:
جایی که می‌خواستید Html.RenderAction را قرار دهید، قطعه کد Ajax ایی زیر را فراخوانی کنید:
<div id="dynamicContentContainer"></div>
<script>   
    $.get('@Url.Action("GetData", "Home")', {id : 1}, function(content){
            $("#dynamicContentContainer").html(content);
        });
</script>
کار آن دریافت محتوای html ایی اکشن متد ذیل و افزودن آن به div مشخص شده‌است.
[HttpGet]
public IActionResult GetData(int id)
{
   return PartialView(id);
}
با این پارشال View فرضی:
@model int 
<span>Values from controler :</span> @Model
مطالب
3 نکته کاربردی TypeScript برای Angular
اگر چه من  این نکات را در حین کار کردن بر روی پروژه‌های انگیولار یافتم، اما همه آنها مشخصه‌های انگیولار نیستند؛ فقط کد‌های تایپ اسکریپت می‌باشند.


Eliminating the need to import interfaces 

من interface ‌ها را دوست دارم با این حال نمی‌خواهم هر بار آنها را import  کنم. تمایلی ندارم فایل‌های من بخاطر import ‌های چند خطی کثیف شوند (فقط به منظور strong typing )، اگر چه Visual Studio Code ویژگی auto-import را دارا می‌باشد. در حالت معمول به صورت زیر کار می‌کنیم :
// api.model.ts
export interface Customer {
    id: number;
    name: string;
}

export interface User {
    id: number;
    isActive: boolean;
}
و استفاده از آن  :
// using the interfaces
import { Customer, User } from './api.model'; 

export class MyComponent {
    cust: Customer; 
}
اگر به خط شماره 2 دقت کنید ، در صورتی که interface ‌های بیشتری استفاده شود، این خط بزرگتر می‌شود و کمی به‌هم ریختگی ایجاد می‌کند.

راه حل شماره 1 : استفاده از namespace 

با استفاده از namespaceها می‌توان نیاز به import فایل‌های interface را حذف کرد: 
// api.model.ts
namespace ApiModel {
    export interface Customer {
        id: number;
        name: string;
    }

    export interface User {
        id: number;
        isActive: boolean;
    }
}
و استفاده از آن :
// using the interfaces
export class MyComponent {
    cust: ApiModel.Customer; 
}
هم چنین با استفاده از namespaceها می‌توان خیلی بهتر interface‌ها را سازماندهی و گروه بندی کرد.

اجازه بدهید بگویم اگر شما فایل دیگری به نام مثلا api.v2.model.ts را داشته باشید و بخواهید interface ‌های جدیدی را به این فایل اضافه کنید، می‌توانید از همان نام namespace مشخص شده قبلی استفاده کنید:
// api.v2.model.ts
namespace ApiModel {
    export interface Order {
        id: number;
        total: number;
    }
}

برای استفاده از interface  جدیدا ایجاد شده، می‌توانید همانند مثال قبل کار کنید: ( استفاده از interface ‌ها با namespace های یکسان و فایل‌های متفاوت)
export class MyComponent {
    cust: ApiModel.Customer; 
    order: ApiModel.Order;
}

راه حل شماره 2 : استفاده از فایل‌های d

راه دیگر حذف کردن import ها، ایجاد یک فایل typescript  با پسوند d.ts. می‌باشد ( d مخفف declaration file در typescript می‌باشد). در ضمن نیازی به export کردن interface ‌ها در فایل‌های d نمی‌باشد:
// api.model.d.ts
interface Customer {
    id: number;
    name: string;
}
در زیر از Customer  بدون نیاز به import کردن استفاده شده است :
// using the interfaces of d file
export class MyComponent {
    cust: Customer; 
}

من راه حل شماره 1 را در مقابل راه حل شماره 2 پیشنهاد می‌کنم زیرا :
از فایل d معمولا برای کتابخانه‌های ثالث استفاده می‌شود.
namespace ‌ها این اجازه را به ما میدهند تا فایل‌ها را خیلی بهتر سازماندهی کنیم.
 
Making all interface properties optional  

این موضوع خیلی رایج است که شما از یک interface برای عملیات CRUD استفاده کنید. مثلا شما یک interface را به نام customer دارید که تمامی فیلد‌های آن در زمان ایجاد اجباری می‌باشند؛ اما در زمان به‌روز رسانی، همه آنها اختیاری هستند. آیا شما نیاز به دو interface برای مدیریت کردن این سناریو دارید؟ 

راه حل : استفاده از Partial 

Partial یک type است که خصوصیات یک شیء را اختیاری می‌کند. تعریف آن در فایل d پیش فرضی به نام lib.es5.d.ts قرار داده شده‌است:
// lib.es5.d.ts
type Partial<T> = {
    [P in keyof T]?: T[P];
};
چگونه می‌توان از آن استفاده کرد؟ به کدهای زیر توجه کنید: 
import { Customer } from './api.model';

export class MyComponent {
    cust: Partial<Customer>;  /

    ngOninit() {
        this.cust = { name: 'jane' };
    }
}

در کد بالا هیچ خطایی پرتاب نمی‌شود؛ به دلیل اینکه همه‌ی فیلد‌ها اختیاری شده‌اند (اگر Partial را حذف کنیم با خطا مواجه می‌شویم). 
اگر شما تعریف Partial را پیدا نکردید، می‌توانید یک فایل d را ایجاد کنید؛ مثلا با نام util.d.ts، سپس قطعه کد تعریف Partial را در آن قرار دهید.

Stop throwing me error, I know what I'm doing 

در بعضی از سناریو‌ها شما می‌خواهید به Typescript  بگویید که  "من می‌دانم دارم چکار می‌کنم لطفا اینجا بی خیال من شو".

راه حل : استفاده از ts-ignore comment@ 

از نگارش 2.6 به بعد Typescript، شما می‌توانید این کار را به منظور مانع شدن از ایجاد خطا  با استفاده از کامنت ts-ignore@  انجام دهید. 
برای مثال در کد زیر، Typescript خطای Unreachable code detected را پرتاب خواهد کرد:
if (false) {
    console.log('x');
}

شما می‌توانید با استفاده از کامنت ts-ignore@ مانع از پرتاب خطا شوید :
if (false) {
    // @ts-ignore
    console.log('x');
}

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

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

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

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