مطالب
مدیریت پیشرفته‌ی حالت در React با Redux و Mobx - قسمت نهم - مثالی از کتابخانه‌ی mobx-react
در ادامه‌ی سری کار با MobX، می‌خواهیم نکاتی را که در سه قسمت قبل مرور کردیم، در قالب یک برنامه پیاده سازی کنیم:


این برنامه از چهار کامپوننت تشکیل شده‌است:
- کامپوننت App که در برگیرنده‌ی سه کامپوننت زیر است:
- کامپوننت BasketItemsCounter: جمع تعداد آیتم‌های انتخابی توسط کاربر را نمایش می‌دهد؛ به همراه دکمه‌ای برای خالی کردن لیست انتخابی.
- کامپوننت ShopItemsList: لیست محصولات موجود در فروشگاه را نمایش می‌دهد. با کلیک بر روی هر آیتم آن، آیتم انتخابی به لیست انتخاب‌های او اضافه خواهد شد.
- کامپوننت BasketItemsList: لیستی را نمایش می‌دهد که حاصل انتخاب‌های کاربر در کامپوننت ShopItemsList است (یا همان سبد خرید). در ذیل این لیست، جمع نهایی قیمت قابل پرداخت نیز درج می‌شود. همچنین اگر کاربر بر روی دکمه‌ی remove هر ردیف کلیک کند، یک واحد از چند واحد انتخابی، حذف خواهد شد.

بنابراین در اینجا سه کامپوننت مجزا را داریم که با هم تبادل اطلاعات می‌کنند. یکی جمع تعداد محصولات خریداری شده را، دیگری لیست محصولات موجود را و آخری لیست خرید نهایی را نمایش می‌دهد. همچنین این سه کامپوننت، فرزند یک دیگر هم محسوب نمی‌شوند و انتقال اطلاعات بین این‌ها نیاز به بالا بردن state هر کدام و قرار دادن آن‌ها در کامپوننت App را دارد تا بتوان پس از آن از طریق props آن‌ها را بین سه کامپوننت فوق که اکنون فرزند کامپوننت App محسوب می‌شوند، به اشتراک گذاشت. روش بهتر اینکار، استفاده از یک مخزن حالت سراسری است تا حالت‌های این کامپوننت‌ها را نگهداری کرده و داده‌‌ها را بین آن‌ها به اشتراک بگذارد که در اینجا برای حل این مساله از کتابخانه‌های mobx و mobx-react استفاده خواهیم کرد.


برپایی پیش‌نیازها

برای پیاده سازی برنامه‌ی فوق، یک پروژه‌ی جدید React را ایجاد می‌کنیم:
> create-react-app state-management-with-mobx-part4
> cd state-management-with-mobx-part4
در ادامه کتابخانه‌ها‌ی زیر را نیز در آن نصب می‌کنیم. برای این منظور پس از باز کردن پوشه‌ی اصلی برنامه توسط VSCode، دکمه‌های ctrl+` را فشرده (ctrl+back-tick) و دستور زیر را در ترمینال ظاهر شده وارد کنید:
> npm install --save bootstrap mobx mobx-react mobx-react-devtools mobx-state-tree
توضیحات:
- برای استفاده از شیوه‌نامه‌های بوت استرپ، بسته‌ی bootstrap نیز در اینجا نصب می‌شود.
- اصل کار برنامه توسط دو کتابخانه‌ی mobx و کتابخانه‌ی متصل کننده‌ی آن به برنامه‌های react که mobx-react نام دارد، انجام خواهد شد.
- چون می‌خواهیم از افزونه‌ی  mobx-devtools نیز استفاده کنیم، نیاز است دو بسته‌ی mobx-react-devtools و همچنین mobx-state-tree را که جزو وابستگی‌های آن است، نصب کنیم.

سپس بسته‌های زیر را که در قسمت devDependencies فایل package.json درج خواهند شد، باید نصب شوند:
> npm install --save-dev babel-eslint customize-cra eslint eslint-config-react-app eslint-loader eslint-plugin-babel eslint-plugin-css-modules eslint-plugin-filenames eslint-plugin-flowtype eslint-plugin-import eslint-plugin-no-async-without-await eslint-plugin-react eslint-plugin-react-hooks eslint-plugin-react-redux eslint-plugin-redux-saga eslint-plugin-simple-import-sort react-app-rewired typescript
علت آن‌را در قسمت قبل بررسی کردیم. این وابستگی‌ها برای فعالسازی react-app-rewired و همچنین eslint غنی سازی شده‌ی آن مورد استفاده قرار می‌گیرند. به علاوه سه قسمت زیر را نیز از قسمت قبل، به پروژه اضافه می‌کنیم:
- افزودن فایل جدید config-overrides.js به ریشه‌ی پروژه، تا پشتیبانی ازlegacy" decorators spec" فعال شود.
- اصلاح فایل package.json و ویرایش قسمت scripts آن برای استفاده‌ی از react-app-rewired، تا امکان تغییر تنظیمات webpack به صورت پویا در زمان اجرای برنامه، میسر شود.
- همچنین فایل غنی شده‌ی eslintrc.json. را نیز به ریشه‌ی پروژه اضافه می‌کنیم.


تهیه سرویس لیست محصولات موجود در فروشگاه

این برنامه از یک لیست درون حافظه‌ای، برای تهیه‌ی لیست محصولات موجود در فروشگاه استفاده می‌کند. به همین جهت پوشه‌ی service را افزوده و سپس فایل جدید src\services\productsService.js را با محتوای زیر، ایجاد می‌کنیم:
const products = [
  {
    id: 1,
    name: "Item 1",
    price: 850
  },
  {
    id: 2,
    name: "Item 2",
    price: 900
  },
  {
    id: 3,
    name: "Item 3",
    price: 1500
  },
  {
    id: 4,
    name: "Item 4",
    price: 1000
  }
];

export default products;


ایجاد کامپوننت نمایش لیست محصولات


پس از مشخص شدن لیست محصولات قابل فروش، کامپوننت جدید src\components\ShopItemsList.jsx را به صورت زیر ایجاد می‌کنیم:
import React from "react";

import products from "../services/productsService";

const ShopItemsList = ({ onAdd }) => {
  return (
    <table className="table table-hover">
      <thead className="thead-light">
        <tr>
          <th>Name</th>
          <th>Price</th>
          <th>Action</th>
        </tr>
      </thead>
      <tbody>
        {products.map(product => (
          <tr key={product.id}>
            <td>{product.name}</td>
            <td>{product.price}</td>
            <td>
              <button
                className="btn btn-sm btn-info"
                onClick={() => onAdd(product)}
              >
                Add
              </button>
            </td>
          </tr>
        ))}
      </tbody>
    </table>
  );
};

export default ShopItemsList;
- این کامپوننت آرایه‌ی products را از طریق سرویس services/productsService دریافت کرده و سپس با استفاده از متد Array.map، حلقه‌ای را بر روی عناصر آن تشکیل داده که در نتیجه، سبب درج trهای متناظر با آن می‌شود؛ تا هر ردیف این جدول، یک آیتم از محصولات موجود را نیز نمایش دهد.
- در اینجا همچنین هر ردیف، به همراه یک دکمه‌ی Add نیز هست که قرار است با کلیک بر روی آن، متد رویدادگردان onAdd فراخوانی شود. این متد نیز از طریق props این کامپوننت دریافت می‌شود. کتابخانه‌های مدیریت حالت، تمام خواص و رویدادگردان‌های مورد نیاز یک کامپوننت را از طریق props، تامین می‌کنند.
- فعلا این کامپوننت به هیچ مخزن داده‌ای متصل نیست و فقط طراحی ابتدایی آن آماده شده‌است.


ایجاد کامپوننت نمایش لیست خرید کاربر (سبد خرید)


اکنون که می‌توان توسط کامپوننت لیست محصولات، تعدادی از آن‌ها را خریداری کرد، کامپوننت جدید src\components\BasketItemsList.jsx را برای نمایش لیست نهایی خرید کاربر، به صورت زیر پیاده سازی می‌کنیم:
import React from "react";

const BasketItemsList = ({ items, totalPrice, onRemove }) => {
  return (
    <>
      <table className="table table-hover">
        <thead className="thead-light">
          <tr>
            <th>Name</th>
            <th>Price</th>
            <th>Count</th>
            <th>Action</th>
          </tr>
        </thead>
        <tbody>
          {items.map(item => (
            <tr key={item.id}>
              <td>{item.name}</td>
              <td>{item.price}</td>
              <td>{item.count}</td>
              <td>
                <button
                  className="btn btn-sm btn-danger"
                  onClick={() => onRemove(item.id)}
                >
                  Remove
                </button>
              </td>
            </tr>
          ))}

          <tr>
            <td align="right">
              <strong>Total: </strong>
            </td>
            <td>
              <strong>{totalPrice}</strong>
            </td>
            <td></td>
            <td></td>
          </tr>
        </tbody>
      </table>
    </>
  );
};

export default BasketItemsList;
- عملکرد این کامپوننت نیز شبیه به کامپوننت نمایش لیست محصولات است؛ با این تفاوت که لیستی که به آن از طریق props ارسال می‌شود:
const BasketItemsList = ({ items, totalPrice, onRemove }) => {
لیست محصولات انتخابی کاربر است.
- همچنین هر ردیف نمایش داده شده، به همراه یک دکمه‌ی Remove آیتم انتخابی نیز هست که به متد رویدادگردان onRemove متصل شده‌است.
- در ردیف انتهایی این لیست، مقدار totalPrice که یک خاصیت محاسباتی است، درج می‌شود.
- فعلا این کامپوننت نیز به هیچ مخزن داده‌ای متصل نیست و فقط طراحی ابتدایی آن آماده شده‌است.


ایجاد کامپوننت نمایش تعداد آیتم‌های خریداری شده


کاربر اگر آیتمی را از لیست محصولات انتخاب کند و یا محصول انتخاب شده را از لیست خرید حذف کند، تعداد نهایی باقی مانده را می‌توان در کامپوننت src\components\BasketItemsCounter.jsx مشاهده کرد:
import React, { Component } from "react";

class BasketItemsCounter extends Component {
  render() {
    const { count, onRemoveAll } = this.props;
    return (
      <div>
        <h1>Total items: {count}</h1>
        <button
          type="button"
          className="btn btn-sm btn-danger"
          onClick={() => onRemoveAll()}
        >
          Empty Basket
        </button>
      </div>
    );
  }
}

export default BasketItemsCounter;
- این کامپوننت یک خاصیت و یک رویدادگردان را از طریق props خود دریافت می‌کند. خاصیت count، جمع نهایی موجود در سبد خرید را نمایش می‌دهد و فراخوانی onRemoveAll، سبب پاک شدن تمام آیتم‌های موجود در سبد خرید خواهد شد.
- فعلا این کامپوننت نیز به هیچ مخزن داده‌ای متصل نیست و فقط طراحی ابتدایی آن آماده شده‌است.


نمایش ابتدایی سه کامپوننت توسط کامپوننت App

اکنون که این سه کامپوننت تکمیل شده‌اند، می‌توان المان‌های آن‌ها را در فایل src\App.js درج کرد تا در صفحه نمایش داده شوند:
import React, { Component } from "react";

import BasketItemsCounter from "./components/BasketItemsCounter";
import BasketItemsList from "./components/BasketItemsList";
import ShopItemsList from "./components/ShopItemsList";

class App extends Component {
  render() {
    return (
      <main className="container">
        <div className="row">
          <BasketItemsCounter />
        </div>

        <hr />

        <div className="row">
          <h2>Products</h2>
          <ShopItemsList />
        </div>

        <div className="row">
          <h2>Basket</h2>
          <BasketItemsList />
        </div>
      </main>
    );
  }
}

export default App;


طراحی مخزن‌های حالت MobX مخصوص برنامه


می‌توان همانند Redux کل state برنامه را داخل یک شیء store ذخیره کرد و یا چون در اینجا می‌توان طراحی مخزن حالت MobX را به دلخواه انجام داد، می‌توان چندین مخزن حالت را تهیه و به هم متصل کرد؛ مانند تصویری که مشاهده می‌کنید. در اینجا:
- src\stores\counter.js: مخزن داده‌ی حالت کامپوننت شمارشگر است.
- src\stores\market.js: مخزن داده‌ی کامپوننت‌های لیست محصولات و سبد خرید است.
- src\stores\index.js: کار ترکیب دو مخزن قبل را انجام می‌دهد.

در ادامه کدهای کامل این مخازن را مشاهده می‌کنید:

مخزن حالت src\stores\counter.js
import { action, observable } from "mobx";

export default class CounterStore {
  @observable totalNumbersInBasket = 0;

  constructor(rootStore) {
    this.rootStore = rootStore;
  }

  @action
  increase = () => {
    this.totalNumbersInBasket++;
  };

  @action
  decrease = () => {
    this.totalNumbersInBasket--;
  };
}
- کار این مخزن، تامین عدد جمع آیتم‌های انتخابی توسط کاربر است که در کامپوننت شمارشگر نمایش داده می‌شود.
- در اینجا خاصیت totalNumbersInBasket به صورت observable تعریف شده‌است و با تغییر آن چه به صورت مستقیم، با مقدار دهی آن و یا توسط دو action تعریف شده، سبب به روز رسانی UI خواهد شد.
- می‌شد این مخزن را با مخزن src\stores\market.js یکی کرد؛ اما جهت ارائه‌ی مثالی در مورد نحوه‌ی تعریف چند مخزن و روش برقراری ارتباط بین آن‌ها، به صورت مجزایی تعریف شد.

مخزن حالت src\stores\market.js
import { action, computed, observable } from "mobx";

export default class MarketStore {
  @observable basketItems = [];

  constructor(rootStore) {
    this.rootStore = rootStore;
  }

  @action
  add = product => {
    const selectedItem = this.basketItems.find(item => item.id === product.id);
    if (selectedItem) {
      selectedItem.count++;
    } else {
      this.basketItems.push({
        ...product,
        count: 1
      });
    }

    this.rootStore.counterStore.increase();
  };

  @action
  remove = id => {
    const selectedItem = this.basketItems.find(item => item.id === id);
    selectedItem.count--;

    if (selectedItem.count === 0) {
      this.basketItems.remove(selectedItem);
    }

    this.rootStore.counterStore.decrease();
  };

  @action
  removeAll = () => {
    this.basketItems = [];
    this.rootStore.counterStore.totalNumbersInBasket = 0;
  };

  @computed
  get totalPrice() {
    return this.basketItems.reduce((previous, current) => {
      return previous + current.price * current.count;
    }, 0);
  }
}
- کار این مخزن تامین مدیریت آرایه‌ی basketItems است که بیانگر اشیاء انتخابی توسط کاربر می‌باشد.
- توسط متد add آن در کامپوننت نمایش لیست محصولات، می‌توان آیتمی را به این آرایه اضافه کرد. در اینجا چون شیء product مورد استفاده دارای خاصیت count نیست، روش افزودن آن‌را توسط spread operator برای درج خواص شیء product اصلی و سپس تعریف آن‌را مشاهده می‌کنید. این فراخوانی، سبب افزایش یک واحد به عدد شمارشگر نیز می‌شود.
- متد remove آن در کامپوننت سبد خرید، مورد استفاده قرار می‌گیرد تا کاربر بتواند اطلاعاتی را از این لیست حذف کند. این فراخوانی، سبب کاهش یک واحد از عدد شمارشگر نیز می‌شود.
- متد removeAll آن در کامپوننت شمارشگر بالای صفحه استفاده می‌شود تا سبب خالی شدن آرایه‌ی آیتم‌های انتخابی گردد و همچنین عدد آن‌را نیز صفر کند.
- خاصیت محاسباتی totalPrice آن در پایین جدول سبد خرید، جمع کل هزینه‌ی قابل پرداخت را مشخص می‌کند.

مخزن حالت src\stores\index.js

در اینجا روش یکی کردن دو مخزن حالت یاد شده را به صورت خاصیت‌های عمومی یک مخزن کد ریشه، مشاهده می‌کنید:
import CounterStore from "./counter";
import MarketStore from "./market";

class RootStore {
  counterStore = new CounterStore(this);
  marketStore = new MarketStore(this);
}

export default RootStore;
هر مخزن مجزایی که تعریف شده، دارای یک پارامتر سازنده‌است که با مقدار شیء this کلاس RootStore مقدار دهی می‌شود. با این روش می‌توان بین مخازن کد مختلف ارتباط برقرار کرد. برای نمونه درمخزن حالت MarketStore، این پارامتر سازنده، امکان دسترسی به خاصیت counterStore و سپس تمام خاصیت‌ها و متدهای عمومی آن‌را فراهم می‌کند:
export default class MarketStore {
  @observable basketItems = [];

  constructor(rootStore) {
    this.rootStore = rootStore;
  }

  @action
  removeAll = () => {
    this.basketItems = [];
    this.rootStore.counterStore.totalNumbersInBasket = 0;
  };
}


تامین مخازن حالت تمام کامپوننت‌های برنامه

پس از ایجاد مخازن حالت، اکنون نیاز است آن‌ها را در اختیار سلسه مراتب کامپوننت‌های برنامه قرار دهیم. به همین جهت به فایل src\index.js مراجعه کرده و آن‌را به صورت زیر تغییر می‌دهیم:
import "./index.css";
import "bootstrap/dist/css/bootstrap.css";

import makeInspectable from "mobx-devtools-mst";
import { Provider } from "mobx-react";
import React from "react";
import ReactDOM from "react-dom";

import App from "./App";
import * as serviceWorker from "./serviceWorker";
import RootStore from "./stores";

const rootStore = new RootStore();

if (process.env.NODE_ENV === "development") {
  makeInspectable(rootStore); // https://github.com/mobxjs/mobx-devtools
}

ReactDOM.render(
  <Provider {...rootStore}>
    <App />
  </Provider>,
  document.getElementById("root")
);

serviceWorker.unregister();
- در اینجا ابتدا import فایل css بوت استرپ را مشاهده می‌کنید که در برنامه استفاده شده‌است.
- سپس یک وهله‌ی جدید از RootStore را که حاوی خاصیت‌های عمومی counterStore و marketStore است، ایجاد می‌کنیم.
- اگر علاقمند باشید تا حین کار با MobX، جزئیات پشت صحنه‌ی آن‌را توسط افزونه‌ی mobx-devtools ردیابی کنید، روش آن‌را در اینجا با فراخوانی متد makeInspectable مشاهده می‌کنید. مقدار process.env.NODE_ENV نیز بر اساس پروسه‌ی جاری node.js اجرا کننده‌ی برنامه‌ی React تامین می‌شود. اطلاعات بیشتر
- قسمت آخر این تنظیمات، محصور کردن کامپوننت App که بالاترین کامپوننت در سلسله مراتب کامپوننت‌های برنامه است، با شیء Provider می‌باشد. در این شیء توسط spread operator، سبب درج خواص عمومی rootStore، به عنوان مخازن قابل استفاده شده‌ایم. تنظیم {rootStore...} معادل عبارت زیر است:
<Provider counterStore={rootStore.counterStore} marketStore={rootStore.marketStore}>
به این ترتیب تمام کامپوننت‌های برنامه می‌توانند با دو مخزن کد ارسالی به آن‌ها کار کنند. در ادامه مشاهده می‌کنیم که چگونه این ویژگی‌ها، سبب تامین props کامپوننت‌ها خواهند شد.


اتصال کامپوننت ShopItemsList به مخزن حالت marketStore

پس از ایجاد rootStore و محصور کردن کامپوننت App توسط شیء Provider در فایل src\index.js، اکنون باید قسمت export default کامپوننت‌های برنامه را جهت استفاده‌ی از مخازن حالت، یکی یکی ویرایش کرد:
import { inject, observer } from "mobx-react";
import React from "react";

import products from "../services/productsService";

const ShopItemsList = ({ onAdd }) => {
  return (
  // ...
  );
};

export default inject(({ marketStore }) => ({
  onAdd: marketStore.add
}))(observer(ShopItemsList));
در اینجا فراخوانی متد inject، سبب دسترسی به ویژگی marketStore تامین شده‌ی توسط شیء Provider می‌شود. تمام ویژگی‌هایی که به شیء Provider ارائه می‌شوند، در اینجا به صورت خواصی که توسط Object Destructuring قابل استخراج هستند، قابل دسترسی می‌شوند. سپس props این کامپوننت را که متد onAdd را می‌پذیرد، از طریق marketStore.add تامین می‌کنیم. در آخر کامپوننت ShopItemsList باید به صورت یک observer بازگشت داده شود تا تغییرات store را تحت نظر قرار داده و به این صورت امکان به روز رسانی UI را پیدا کند.


اتصال کامپوننت BasketItemsList به مخزن حالت marketStore

در اینجا نیز سطر export default را جهت دریافت خاصیت marketStore، از شیء Provider تامین شده‌ی در فایل src\index.js، ویرایش می‌کنیم. به این ترتیب سه props مورد انتظار این کامپوننت، توسط خاصیت‌های basketItems (آرایه‌ی اشیاء انتخابی توسط کاربر)، totalPrice (خاصیت محاسباتی جمع کل هزینه) و  متد رویدادگردان onRemove (برای حذف یک آیتم) تامین می‌شوند. در آخر کامپوننت را به صورت observer محصور کرده و بازگشت می‌دهیم تا تغییرات در مخزن حالت آن، سبب به روز رسانی UI آن شوند:
import { inject, observer } from "mobx-react";
import React from "react";

const BasketItemsList = ({ items, totalPrice, onRemove }) => {
  return (
  // ...
  );
};

export default inject(({ marketStore }) => ({
  items: marketStore.basketItems,
  totalPrice: marketStore.totalPrice,
  onRemove: marketStore.remove
}))(observer(BasketItemsList));


اتصال کامپوننت BasketItemsCounter به دو مخزن حالت counterStore و marketStore

در اینجا روش استفاده‌ی از decorator syntax کتابخانه‌ی mobx-react را بر روی یک کامپوننت کلاسی مشاهده می‌کنید. تزئین کننده‌ی inject، امکان دسترسی به مخازن حالت تزریقی به شیء Provider را میسر کرده و سپس توسط آن می‌توان props مورد انتظار کامپوننت را از مخازن متناظر استخراج کرده و در اختیار کامپوننت قرار داد. همچنین این کامپوننت توسط تزئین کننده‌ی observer نیز علامت گذاری شده‌است. در این حالت نیازی به تغییر سطر export default نیست.
import { inject, observer } from "mobx-react";
import React, { Component } from "react";

@inject(rootStore => ({
  count: rootStore.counterStore.totalNumbersInBasket,
  onRemoveAll: rootStore.marketStore.removeAll
}))
@observer
class BasketItemsCounter extends Component {
  render() {
    const { count, onRemoveAll } = this.props;
    return (
      // ...
    );
  }
}

export default BasketItemsCounter;

کدهای کامل این قسمت را می‌توانید از اینجا دریافت کنید: state-management-with-mobx-part4.zip
مطالب
فعال‌سازی HSTS در ASP.NET Core
برای فعال‌سازی SSL در ASP.NET Core می‌توانیم از ویژگی RequireHttps برای کنترلرها و همچنین اکشن‌متدها استفاده کنیم:
[RequireHttps]
public class AccountController : Controller
{
    public IActionResult Login()
    {
        return Content("Login Page");
    }
}
اکنون اگر پروژه را اجرا کنید، خروجی به صورت زیر خواهد بود و اگر به آدرس Account/Login مراجعه کنید، چیزی در خروجی نمایش داده نخواهد شد:

علت آن نیز این است که اگر درخواست HTTPS نباشد، یک Permanent Redirect به همان URL خواهیم داشت؛ زیرا بر روی پورتی که پروژه توسط آن اجرا شده‌است، هیچ certificateی نصب نشده‌است. در ادامه می‌خواهیم یک self-signed certificate تستی را برای اجرای پروژه ایجاد کنیم. 
توسط دستورات زیر می‌توانید یک certificate را برای localhost ایجاد کنید:
$ openssl genrsa -out key.pem 2048
$ openssl req -new -sha256 -key key.pem -out csr.csr
$ openssl req -x509 -sha256 -days 365 -key key.pem -in csr.csr -out certificate.pem
openssl pkcs12 -export -out localhost.pfx -inkey key.pem -in certificate.pem
با اجرای دستورات فوق، باید یک فایل جدید با نام localhost.pfx و تعدادی فایل دیگر، درون پروژه ایجاد شده باشند:

اکنون باید Kestrel را از وجود این certificate مطلع کنیم. برای انجام اینکار ابتدا باید پکیج زیر را نصب کنیم:
$ dotnet add package Microsoft.AspNetCore.Server.Kestrel.Https
سپس فایل Program.cs را به صورت زیر تغییر دهیم:
namespace testingSSL
{
    public class Program
    {
        public static void Main(string[] args)
        {
            BuildWebHost(args).Run();
        }

        public static IWebHost BuildWebHost(string[] args) =>
            WebHost.CreateDefaultBuilder(args)
                .UseKestrel(options =>
                {
                    options.Listen(IPAddress.Any, 8080);
                    options.Listen(IPAddress.Any, 443,
                        listenOptions => listenOptions.UseHttps("localhost.pfx", "qwe123456"));
                })
                .UseStartup<Startup>()
                .Build();
    }
}
در این حالت با مراجعه به کنترلری که با ویژگی RequireHttps مزین شده‌است، به صورت خودکار، درخواست به نسخه HTTPS هدایت خواهد شد:

البته تا اینجا، هدف بررسی ویژگی RequireHttps بود؛ طبیعتاً به SSL در حین Development نیازی نخواهید داشت. در نتیجه می‌توانیم به صورت Global تمامی کنترلرها را در زمان Production به ویژگی گفته شده مزین کنیم:

private readonly IHostingEnvironment _env;
public Startup(IConfiguration configuration,
IHostingEnvironment env)
{
    Configuration = configuration;
    _env = env;
}

public IConfiguration Configuration { get; }

// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc();

    if (!_env.IsDevelopment())
        services.Configure<MvcOptions>(o => o.Filters.Add(new RequireHttpsAttribute()));
}


(Http Strict Transport Security (HSTS

هدایت کردن خودکار درخواست از حالت HTTP به HTTPS، توسط خیلی از سایت‌ها انجام می‌شود:

البته این روش بهتر از استفاده نکردن از SSL است؛ اما در نظر داشته باشید که همیشه اولین درخواست به صورت رمزنگاری نشده ارسال خواهد شد. فرض کنید در یک محیط پابلیک از طریق WiFi به اینترنت متصل شده‌ایم. شخصی (هکر) که بر روی مودم کنترل دارد، طوری WiFi را پیکربندی کرده‌است که به جای آدرس اصلی که در تصویر مشاهده می‌کنید، یک نسخه جعلی از سایت باز شود؛ به طوریکه URL همانند URL اصلی باشد. در این‌حالت کاربر به جای اینکه نام‌کاربری و کلمه‌عبور را وارد سایت اصلی کند، آن را درون سایت جعلی وارد خواهد کرد. برای حل این مشکل می‌توانیم وب‌سایت‌مان را طوری تنظیم کنیم که هدر Strict-Transport-Security را به هدر اولین responseی که توسط مرورگر دریافت می‌شود اضافه کند:

Strict-Transport-Security: max-age=31536000

بنابراین مرورگر وب‌سایت را درون یک لیست internal به مدت یکسال (مقدار max-age) نگهداری خواهد کرد؛ در طول این زمان به هیچ درخواست ناامنی اجازه داده نخواهد شد. به این قابلیت HSTS گفته می‌شود. البته ASP.NET Core به صورت توکار روشی را جهت اضافه کردن این هدر ارائه نداده است؛ اما می‌توانیم خودمان یک Middleware سفارشی را به pipeline اضافه کنیم تا اینکار را برایمان انجام دهد:

namespace testingSSL.Middleware
{
    public class HstsMiddleware
    {
        private readonly RequestDelegate _next;
        public HstsMiddleware(RequestDelegate next)
        {
            _next = next;
        }

        public Task Invoke(HttpContext context)
        {
            if (!context.Request.IsHttps)
                return _next(context);

            if (IsLocalhost(context))
                return _next(context);

            context.Response.Headers.Add("Strict-Transport-Security", "max-age=31536000");

            return _next(context);
        }
        private bool IsLocalhost(HttpContext context)
        {
            return string.Equals(context.Request.Host.Host, "localhost", StringComparison.OrdinalIgnoreCase);
        }
    }
}


یا اینکه می‌توانیم از کتابخانه NWebSec استفاده کنیم:

$ dotnet add package NWebsec.AspNetCore.Middleware

برای استفاده از آن نیز خواهیم داشت:

app.UseHsts(h => h.MaxAge(days: 365));


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

app.UseHsts(h => h.MaxAge(days: 365).Preload());

در اینحالت حتی اگر کسی به وب‌سایت شما مراجعه نکند، مرورگر می‌داند که باید از HTTPS استفاده کند. زیرا این لیست به صورت توکار درون مرورگر تعبیه شده‌است. بنابراین در این‌حالت مطمئن خواهیم شد که تمامی connectionها به سایت‌مان امن می‌باشند.


دریافت کدهای مطلب جاری (+)

نظرات مطالب
Globalization در ASP.NET MVC - قسمت دوم
با سلام و با تشکر؛ پس از پیاده سازی مطالب در محیط لوکال، جواب صحیح را گرفتم ولی پس از آپلود نمودن سایت در محیط وب در هنگام Build نمودن پروژه پوشه fa_ir در پوشه bin ایجاد نمی‌گردد بنابراین قابلیت تغییر عناوین فارسی را ندارم. لازم به عرض که دسترسی write برای پوشه bin و تمامی زیر پوشه‌ها فعال می‌باشد. نکته بعدی اینکه فایل dll اصلی ساخته می‌شود و فقط در ساخت پوشه fa-ir که فایل dll زبان فارسی در آن می‌باشد اشکال وجود دارد.
نظرات مطالب
حذف فضاهای خالی در خروجی صفحات ASP.NET MVC
بله کاملا حق با شماست و مشکل زمانی زیاد می‌شود که در صفحه کد js داشته باشیم و یکی از خطوظ با استفاده از // کامنت کنیم.
با فشرده سازی دستورات بعدی کامنت خواهد شد و تا حدودی ممکن است صفحه از کار بیفتد.
که باید حتی الامکان از این نوع کامنت‌ها استفاده نشود.
در هر صورت از نظر شما متشکرم
مطالب
کار با modal dialogs مجموعه Bootstrap در برنامه‌های Angular
در مطلب «Angular CLI - قسمت ششم - استفاده از کتابخانه‌های ثالث» با نحوه‌ی دریافت، نصب و راه اندازی کتابخانه‌ی ngx-bootstrap آشنا شدیم. در اینجا می‌خواهیم نحوه‌ی کار با کامپوننت Modal آن را بررسی کنیم.


سازماندهی بهتر کامپوننت‌های ngx-bootstrap

پس از نصب بسته‌ی npm کتابخانه‌ی ngx-bootstrap و تنظیم فایل angular-cli.json. که در مطلب «Angular CLI - قسمت ششم - استفاده از کتابخانه‌های ثالث» بررسی شدند، برای کار با کامپوننت‌های این کتابخانه باید متدهای BsDropdownModule.forRoot، TooltipModule.forRoot، ModalModule.forRoot و ... را به قسمت imports فایل app.module.ts اضافه کرد. با انجام این‌کار پس از مدتی به یک فایل بسیار شلوغ app.module.ts خواهیم رسید. برای مدیریت بهتر آن می‌توان شبیه به مطلب «سازماندهی برنامه‌های Angular توسط ماژول‌ها» در پوشه‌ی Shared برنامه، ماژول ذیل را تدارک دید. برای اینکار ابتدا فایل جدید src\app\shared\shared.bootstrap.module.ts را ایجاد نمائید. سپس کامپوننت‌های این کتابخانه را به صورت ذیل در این تک ماژول اختصاصی قرار دهید:
import { NgModule } from "@angular/core";
import { CommonModule } from "@angular/common";
import { BsDropdownModule } from "ngx-bootstrap/dropdown";
import { TooltipModule } from "ngx-bootstrap/tooltip";
import { ModalModule } from "ngx-bootstrap/modal";

@NgModule({
  imports: [
    CommonModule,
    BsDropdownModule.forRoot(),
    TooltipModule.forRoot(),
    ModalModule.forRoot()
  ],
  exports: [
    BsDropdownModule,
    TooltipModule,
    ModalModule
  ]
})
export class SharedBootstrapModule { }
متدهای forRoot در قسمت imports قرار می‌گیرند (فلسفه‌ی وجودی این متد و الگوی ویژه را در مطلب «سازماندهی برنامه‌های Angular توسط ماژول‌ها» پیشتر بررسی کرده‌ایم). سپس برای اینکه این کامپوننت‌ها در سایر ماژول‌های برنامه قابل استفاده باشند، باید نام ماژول مرتبط با هر کدام را در قسمت exports نیز ذکر کرد.
اکنون برای استفاده‌ی از SharedBootstrapModule اختصاصی فوق، می‌توان دو روش را بکار برد:
الف) import مستقیم آن در فایل app.module.ts
import { SharedBootstrapModule } from './shared/shared.bootstrap.module';
@NgModule({
  imports: [BrowserModule, SharedBootstrapModule],
  // ...
})
export class AppModule {}
ب) import آن در SharedModule
و یا اگر فایل src\app\shared\shared.module.ts را مطابق مطلب «سازماندهی برنامه‌های Angular توسط ماژول‌ها» ایجاد کرده‌اید، این ماژول به صورت ذیل، در دو قسمت imports و exports آن اضافه خواهد شد:
import { SharedBootstrapModule } from "./shared.bootstrap.module";

@NgModule({
  imports: [
    CommonModule,
    SharedBootstrapModule
  ],
  exports: [
    CommonModule,
    SharedBootstrapModule
  ]
})


نمایش یک modal dialog توسط کامپوننت Modal

پس از تعریف ModalModule.forRoot، اکنون می‌توان به کامپوننت Modal این ماژول دسترسی یافت. برای این منظور کامپوننتی که قرار است یک Modal را نمایش دهد، چنین ساختاری را پیدا می‌کند:
import { Component, OnInit, TemplateRef } from "@angular/core";
import { BsModalRef, BsModalService } from "ngx-bootstrap";

@Component({
  selector: "app-modal-dialog-test",
  templateUrl: "./modal-dialog-test.component.html",
  styleUrls: ["./modal-dialog-test.component.css"]
})
export class ModalDialogTestComponent implements OnInit {

  modalRef: BsModalRef;

  constructor(private modalService: BsModalService) { }

  openModal(template: TemplateRef<any>) {
    this.modalRef = this.modalService.show(template,
      { animated: true, keyboard: true, backdrop: true, ignoreBackdropClick: false });
  }

  closeModal() {
    this.modalRef.hide();
  }
}
توسط سرویس BsModalService که به سازنده‌ی کلاس کامپوننت تزریق شده‌است، می‌توان به متد show آن دسترسی یافت. این متد یک ng-template را قبول می‌کند. بنابراین در قالب این کامپوننت باید قسمتی را که قرار است به صورت modal نمایش داده شود، توسط یک ng-template تعریف کرد.
سپس با فراخوانی متد this.modalService.show می‌توان این قالب را نمایش داد. خروجی این متد ارجاعی را به این modal بازگشت می‌دهد. از این ارجاع می‌توان در جهت بستن آن استفاده کرد (مانند متد closeModal).

بنابراین در ادامه، قالب کامپوننت مثال این قسمت، یک چنین شکلی را پیدا می‌کند:
<h1>Displaying modal bootstrap dialogs</h1>

<button type="button" class="btn btn-info" (click)="openModal(template1)">Create template modal</button>

<ng-template #template1>
  <div class="modal-header">
    <h4 class="modal-title pull-left">Modal</h4>
    <button type="button" class="close pull-right" aria-label="Close" (click)="closeModal()">
      <span aria-hidden="true">&times;</span>
    </button>
  </div>
  <div class="modal-body">
    This is a modal.
  </div>
</ng-template>
در اینجا محتوای modal داخل یک ng-template قرار گرفته‌است و این قالب توسط یک template reference variable به نام template1 مشخص شده‌است. این نام را در متد openModal(template1) استفاده خواهیم کرد تا به متد show سرویس نمایش modal منتقل شود.



طراحی یک کامپوننت عمومی مودال جهت دریافت تائید انجام عملیات

در ادامه می‌خواهیم توسط یک modal dialog، کار دریافت تائید و یا لغو انجام یک عملیات را انجام دهیم. چون این کامپوننت عمومی قرار است در بیش از یک ماژول استفاده شود، بنابراین نیاز است آن‌را در Shared Module ثبت کرد. به همین جهت این کامپوننت را به نحو ذیل در پوشه‌ی Shared ایجاد می‌کنیم:
 ng g c Shared/ConfirmModal --skip-import
پرچم skip-import نیز ذکر شده‌است، چون قصد نداریم به صورت مستقیم از طریق درج selector آن در صفحه، با آن کار کنیم. سرویس سفارشی مودالی که برای این منظور تدارک خواهیم دید، کار نمایش آن‌را انجام می‌دهد.
import { ConfirmModalComponent } from "./confirm-modal/confirm-modal.component";

@NgModule({
  imports: [
  ],
  entryComponents: [
    ConfirmModalComponent
  ],
  declarations: [
    ConfirmModalComponent
  ]
})
export class SharedModule {}
نحوه‌ی درج و تعریف این کامپوننت اندکی متفاوت است. چون این کامپوننت قرار است «به صورت پویا» توسط متد show سرویس BsModalService نمایش داده شود (پارامتر اول آن می‌تواند یک قالب و یا یک کامپوننت کامل باشد)، باید در قسمت entryComponents و declarations مربوط به SharedModule درج شود. آن‌را در قسمت exports ذکر نمی‌کنیم، چون قرار نیست با درج مستقیم selector آن در صفحه، آن‌را نمایش دهیم.

این کامپوننت دریافت تائید کاربر به صورت ذیل تعریف می‌شود:
import { Component } from "@angular/core";

@Component({
  selector: "app-confirm-modal",
  templateUrl: "./confirm-modal.component.html",
  styleUrls: ["./confirm-modal.component.css"]
})
export class ConfirmModalComponent {

  args: {
    title: string;
    message: string;
  };

  close: (val?: any) => void;

}
در اینجا args آن توسط سرویسی که در ادامه طراحی می‌کنیم، مقدار دهی خواهد شد (طراحی args در اینجا کاملا دلخواه است و در کامپوننت‌های مشابه دیگر می‌تواند متفاوت باشد). متد close آن نیز کار گزارش دهی به فراخوان را انجام می‌دهد.
قالب این کامپوننت نیز بدون استفاده از ng-template تعریف می‌شود:
<div class="modal-header">
  <h4 class="modal-title pull-left">{{ args?.title }}</h4>
  <button type="button" class="close pull-right" aria-label="Close" (click)="close()">
    <span aria-hidden="true">&times;</span>
  </button>
</div>
<div class="modal-body">
  <p>{{ args?.message }}</p>
</div>
<div class="modal-footer">
  <button class="btn btn-danger" (click)="close(true)">Yes</button>
  <button class="btn btn-primary" (click)="close()">Cancel</button>
</div>
چون این کامپوننت قرار است به صورت پویا توسط متد show بارگذاری شود، نیازی نیست محتوای قالب آن‌را توسط ng-template مخفی کرد و سپس نمایش داد. زمانیکه این کامپوننت بارگذاری شد، یعنی قصد داریم یک modal کامل را نمایش دهیم.

تا اینجا یک کامپوننت نمایش دریافت تائید انجام عملیات را تهیه کردیم. در ادامه نیاز است یک سرویس را جهت بارگذاری پویای اینگونه کامپوننت‌های مودال طراحی کنیم. این سرویس عمومی در پوشه‌ی Core و CoreModule ثبت خواهد شد:
 >ng g s Core/Modal
با این محتوا
import { Injectable } from "@angular/core";
import { BsModalService } from "ngx-bootstrap";

@Injectable()
export class ModalService {

  constructor(private bsModalService: BsModalService) { }

  show(component: any, args?: any, options?: any): Promise<any> {
    return new Promise(resolve => {
      options = options || {};
      const modal = this.bsModalService.show(component, options);
      let result: any;
      const sub = this.bsModalService.onHidden.subscribe(() => {
        sub.unsubscribe();
        resolve(result);
      });
      modal.content.args = args;
      modal.content.close = (val?: any) => {
        result = val;
        modal.hide();
      };
    });
  }
}
کار این سرویس، نمایش یک کامپوننت مودال مانند ConfirmModalComponent به صورت پویا است؛ از این جهت که متد this.bsModalService.show هم امکان نمایش یک ng-template را دارد و هم یک کامپوننت کامل را به صورت پویا.

یک مودال در سه حالت ممکن است بسته شود:
الف) کلیک بر روی دکمه‌ی close و یا cancel
ب) کلیک بر روی علامت ضربدر درج شده‌ی در یک سمت عنوان آن
ج) کلیک بر روی قسمتی از صفحه، خارج از مودال
در حالات ب و ج، رخ‌داد this.bsModalService.onHidden فراخوانی می‌شود. در حالت الف، همان متد close درج شده‌ی در کامپوننت فراخوانی می‌شود.
برای اینکه بتوان نتیجه‌ی عملیات را از طرف یک سرویس به کامپوننت فراخوان آن گزارش دهیم، یکی از روش‌ها، استفاده از Promiseها است که مشاهده می‌کنید. با فراخوانی resolve(result)، کار ارسال نتیجه‌ی فراخوانی متدهای close(true) و ()close صورت می‌گیرد (یا true و یا undefined).

خاصیت modal.content امکان دسترسی به خواص عمومی کامپوننت در حال استفاده را میسر می‌کند (content به کامپوننت بارگذاری شده اشاره می‌کند). اینجا است که می‌توان برای مثال به خاصیت args یک کامپوننت، مقادیری را نسبت داد و یا به متد close آن دسترسی یافت.

پس از افزودن این سرویس، محل تعریف آن در قسمت providers مربوط به CoreModule است تا در تمام برنامه قابل دسترسی شود:
import { ModalService } from "./modal.service";

@NgModule({
  providers: [
    ModalService
  ]
})
export class CoreModule {}
در پایان برای آزمایش این سرویس جدید، یک دکمه و یک برچسب را به قالب کامپوننت ModalDialogTestComponent ابتدای بحث اضافه می‌کنیم:
<button type="button" class="btn btn-danger" (click)="deleteRecord()">Delete record</button>
<div *ngIf="confirmResult" class="alert alert-info">{{confirmResult}}</div>
با این کدها:
import { ModalService } from "./../../core/modal.service";
import { ConfirmModalComponent } from "./../../shared/confirm-modal/confirm-modal.component";

export class ModalDialogTestComponent implements OnInit {

  confirmResult: string;

  constructor(private modalService: ModalService) { }

  deleteRecord() {
    this.confirmResult = "";
    this.modalService.show(
      ConfirmModalComponent,
      {
        title: "Confirm", message: "Do you want to delete this record?"
      },
      {
        animated: true, keyboard: true, backdrop: true, ignoreBackdropClick: false
      }).then(confirmed => {
        if (confirmed) {
          this.confirmResult = "Deleted!";
        } else {
          this.confirmResult = "Canceled!";
        }
      });
  }
}
در اینجا نحوه‌ی استفاده‌ی از این ModalService سفارشی را ملاحظه می‌کنید. ابتدا به سازنده‌ی کلاس تزریق شده‌است و سپس در متد deleteRecord توسط متد show آن، کامپوننت ConfirmModalComponent به صورت پویا بارگذاری شده‌است. همچنین خاصیت args آن نیز با خواص title و message سفارشی، مقدار دهی شده‌است. چون این متد یک Promise را باز می‌گرداند، می‌توان مشترک آن شد و نتیجه‌ی نهایی را از آن دریافت کرد و بر اساس آن تصمیم گرفت که آیا باید عملیاتی رخ‌دهد، یا خیر.


توسط this.modalService.show می‌توان انواع و اقسام کامپوننت‌های مودال را به صورت پویا بارگذاری کرد و نمایش داد.


کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید.
نظرات مطالب
Url Routing در ASP.Net WebForms
var id = Page.RouteData.Values["id"];
var title = Page.RouteData.Values["title"];

var blogPost = _postsService.FindPost(id);
if(blogPost == null)
{
  Response.RedirectPermanent("~/notfound");
}

if(blogPost.Title != title)
{
  Response.RedirectPermanent("~/post/" + id + "/" + blogPost.Title);
}
سیستم مسیریابی، اطلاعات id و title وارد شده توسط کاربر را به شما می‌دهد. زمانیکه می‌خواهید مطلب را نمایش دهید، این title دریافتی را با title واقعی مطلب مانند کدهای فوق مقایسه کنید. (البته بهتر است از GenerateSlug استفاده کنید و عناوین نهایی را بر این اساس مقایسه و اصلاح کنید)
نظرات اشتراک‌ها
رایگان شدن بیش از ۷۰۰۰ دوره سایت Pluralsight
برنامه‌ای برای دریافت لینک‌های دانلود دوره‌های پلورال‌سایت

حدودا 23 روز دیگر تا پایان دسترسی رایگان به پلورال‌سایت باقی است. به همین جهت، برنامه‌ای تهیه شد که توسط آن می‌توانید لینک‌های مستقیم دریافت فایل‌های دوره‌های پلورال‌سایت را یافته و توسط دانلودمنیجر خود، آن‌ها را دریافت کنید: PluralsightLinks.7z

روش استفاده:
- سورس کامل برنامه قرار داده شده‌است و برای اجرا، نیاز به NET Core 3.1. را دارد.
- فایل appsettings.json آن‌را باز کنید. سپس در آن Username و Password ورود به سایت پلورال‌سایت خود را وارد کنید.
- سپس آرایه‌ی CoursesToCheck را با فرمتی که مشاهده می‌کنید، بر اساس لینک‌های اول صفحات دوره‌های مورد علاقه‌ی خود تکمیل کنید.

و در آخر با کلیک بر روی فایل dotnet_run.bat، می‌توانید برنامه را اجرا کرده و نتایج نهایی را در پوشه‌ی Output تشکیل شده، مشاهده کنید. این نتایج به صورت فایل‌های txt ذخیره می‌شوند که به سادگی قابلیت import در دانلودمنیجرها را دارند.

دو نکته‌ی مهم:
- لینک‌های یافت شده، مدت‌دار هستند. بنابراین سریعتر نسبت به دریافت آن‌ها اقدام کنید! بدیهی است در صورت منقضی شدن لینک‌ها، باید مجددا لینک‌های جدید را با اجرای مجدد برنامه، دریافت کنید.
- اگر با IP ایران می‌خواهید از این برنامه استفاده کنید، بلافاصله پس از لاگین، خطای 403 و عدم دسترسی را مشاهده خواهید کرد. برای رفع این مشکل، می‌توانید DNS خود را به «شکن» تنظیم کنید؛ یعنی تنظیم DNS به 178.22.122.100 به صورت زیر:


پس از این تغییر، چون IP قابل مشاهده‌ی سیستم شما توسط سایت پلورال‌سایت، تغییر می‌کند، مرحله‌ی لاگین و کار با سایت را بدون مشکل طی خواهید کرد.

به روز رسانی‌ها:
- برنامه را کمی تغییر دادم تا خودش فایل‌ها را هم یکی یکی دریافت کند؛ آهسته و پیوسته، به همراه ایجاد پوشه‌ها، به ازای هر ماژول دوره و نام‌گذاری صحیح فایل‌های ویدیوهای دریافتی: PluralsightLinks-V2.7z 
- امکان دریافت زیرنویس‌های هر ویدیو هم اضافه شد: PluralsightLinks-V5.7z  
مطالب
طراحی گزارش در Stimulsoft Reports.Net – بخش 1

برای طراحی گزارش شما میتوانید به سه روش این کار را انجام دهید.

1- طراحی در برنامه طراح گزارش

2- طراحی از داخل ویژوال استودیو

3- طراحی گزارش در زمان اجرا

برای شروع شما میتوانید نسخه آزمایشی این گزارش‌ساز را دریافت کنید. تنها محدودیت این نسخه نمایش عبارت Demo در چاپ میباشد.

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

Header, Footer, Data, Page Header, Page Footer, Report Title, Report Summery

*به ابزارهای بالا Band گفته میشود.

Header , Footer :

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

Data :

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

Page Header , Page Footer :

این دو بند با توجه به نامشان جهت نمایش در بالا و پایین هر صفحه از گزارش میباشد. البته باز هم یادآور میشوم که با تغییر در خصوصیات‌شان میتوانید رفتار و نحوه نمایش آنها را تغییر دهید.

Report Title :

این بند فقط در ابتدای گزارش نمایش داده خواهد شد.

Report Summery :

این بند بلافاصله بعد از اتمام گزارش نمایش داده خواهد شد.

مثال :

برای شروع در Designer یک گزارش جدید از نوع Blank Report ایجاد نمایید. سپس در پنل Dictionary بر روی New Item کلیک کرده و گزینه XML Data را انتخاب نمایید. با توجه به محل نصب گزارش‌ساز وارد مسیر …\Bin\Data شده و فایلهای Demo.xsd و Demo.xml را برای قسمتهای مربوطه انتخاب نمایید. یک بار دیگر بر رو New Item کلیک کرده و گزینه New Data Source را انتخاب نمایید، از لیست ظاهر شده کانکشنی را که ایجاد کرده‌اید را انتخاب نمایید؛ نتیجه کار تا اینجا باید به صورت زبر باشد.

جدول Product را دراگ کرده و بر روی صفحه طراحی گزارش رها کنید. فرم Data ظاهر میشود این فرم را مطابق تصویر زیر تنظیم نمایید.

حال بر روی صفحه طراحی گزارش بندهای Header, Data, Footer مشاهده میشود؛ حال شما میتوانید با کلیک بر روی سربرگ Preview خروجی گزارش را ببینید.

توابع :

این گزارش‌ساز دارای توابع بسیاری است که اکثر نیازهای شما را برطرف می‌کند به عنوان مثال تابع تبدیل عدد به حروف به زبان فارسی. همچنین شما میتوانید توابع خاص خود را ساخته و به صورت رفرنس به گزارش اضافه نمایید.

در این بخش ما از توابع موجود در گزارش استفاده خواهیم کرد. برای شروع بر روی کامپوننت Text در بند Footer  زیر ستون UnitPrice دابل کلیک کرده تا فرم TextEditor ظاهر شود. سربرگ Summery را انتخاب نمایید. مطابق اطلاعات زیر بخشها را تنظیم نمایید.

Summery Function: Sum

Data Band: DataProducts

Data Column: Products.UnitPrice

حال بر روی سربرگ Preview کلیک نمایید تا خروجی گزارش را ببینید. جمع ستون UnitPrice فقط در صفحه آخر نمایش داده خواهد شد. اگر بخواهید جمع ستون در پایین هر صفحه نمایش داده شود ابتدا باید خصوصیت Print on All Pages بند Footer به True ست شود. سپس بر روی کامپوننت Text در بند Footer، دابل کلیک نمایید و در فرم TextEditor سربرگ Summery تیک Running Total را به حالت انتخاب شده در بیاورید، حال خروجی گزارش را ببینید، جمع در انتهای هر صفحه ظاهر میشود.

متغیرها :

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

برای مثال شما میتوانید متغیر سیستمی Line را برای روی صفحه طراحی دراگ کنید. دو کامپوننت Text بر روی صفحه ایجاد میشود. اولی با محتوای Line و دومی با محتوای {Line}. اولی را به بند Header و دومی را به بند Data منتقل کنید و سپس خروجی گزارش را مشاهده نمایید، حال گزارش شما دارای شماره ردیف است.

متغیرهای نرمال تقریبا همانند متغیرهایی هستند که همه روزه شما در برنامه‌های خود از آنها استفاده می‌کنید. با کلیک بر روی New Item گزینه New Variable را انتخاب نمایید و نوع متغیر را Decimal انتخاب نمایید، سپس متغیر ایجاد شده را دراگ کرده و بروی صفحه طراحی قرار دهید و مشابه متغیر Line عمل کرده و کامپوننتهای Text را در بندهای مناسب قرار دهید. سپس بند Data بر روی صفحه طراحی را انتخاب نمایید، در پنل Properties بر روی Eventes کلیک کرده سپس در رویداد Rendering کد زیر را وارد نمایید.

Variable1 += Products.UnitPrice

حال در خروجی گزارش میتوانید مقادیر محاسبه شده را ببینید. توجه داشته باشید که شما میتوانید در رویدادهای این گزارش‌ساز به زبان VB و C# برنامه نویسی کنید و محدود به یک خط کد نمی‌باشید.

شما میتوانید گزارش ساخته شده را به صورتهای مختلف ذخیره کنید از جمله کد C# و یا یک اپلیکیشن قابل اجرا. 

Report.mrt 

مطالب
LocalDB FAQ
SQL Server Express LocalDB یا به صورت خلاصه LocalDB، یک بانک اطلاعاتی‌است که به صورت متصل به پروسه‌ی برنامه‌ی جاری اجرا می‌شود؛ برخلاف رویه‌ی متداول بانک‌های اطلاعاتی که به صورت یک سرویس مستقل اجرا می‌شوند. هدف آن، جایگزین کردن نگارش Express نیست و بیشتر حجم کم و سهولت توزیع آن مدنظر بوده‌است. برای مثال نگارش Express به صورت یک سرویس مجزا و مستقل بر روی سیستم نصب می‌شود؛ اما LocalDB به همراه و متصل به برنامه‌ی نوشته شده، اجرا می‌شود:


اگر به تصویر فوق دقت کنید، یک child process جدید به نام sqlservr.exe نیز به همراه برنامه‌ی آزمایشی ما به صورت خودکار اجرا شده‌است. این child process به همراه پارامترهای ذیل است (که توسط NET Framework. مقدار دهی می‌شوند و مدیریت نهایی آن خودکار است):
 "C:\Program Files\Microsoft SQL Server\120\LocalDB\Binn\\sqlservr.exe"   
-c -SMSSQL12E.LOCALDB
-sLOCALDB#5657074F
-d"C:\Users\Vahid\AppData\Local\Microsoft\Microsoft SQL Server Local DB\Instances\MSSQLLocalDB\master.mdf"
-l"C:\Users\Vahid\AppData\Local\Microsoft\Microsoft SQL Server Local DB\Instances\MSSQLLocalDB\mastlog.ldf"
-e"C:\Users\Vahid\AppData\Local\Microsoft\Microsoft SQL Server Local DB\Instances\MSSQLLocalDB\error.log"
بنابراین LocalDB برخلاف SQL Server CE، یک بانک اطلاعاتی in-process نیست و به صورت یک پروسه‌ی مجزا اجرا می‌شود. زمانیکه از SQL Server CE استفاده می‌شود، موتور این بانک اطلاعاتی چند فایل DLL بیشتر نیستند و نهایتا اجرای آن داخل پروسه‌ی برنامه‌ی ما و همانند اجرای سایر DLLهای متصل و مورد استفاده‌ی به آن است.
اما LocalDB یک بانک اطلاعاتی user-mode است و در پروفایل کاربر جاری سیستم اجرا می‌شود. این بانک اطلاعاتی یک بار بر روی سیستم نصب می‌شود و در هر برنامه‌ای که از آن استفاده می‌کنید، یک child process مجزای خاص خودش را (sqlservr.exe) اجرا خواهد کرد. اجرا و خاتمه‌ی این child processها نیز خودکار هستند و نیازی به دخالت مستقیم برنامه ندارند.
البته به نظر توسعه‌ی SQL Server CE متوقف شده‌است و دیگر پشتیبانی نمی‌شود. بنابراین گزینه‌ی ترجیح داده شده‌ی برای کارهایی با حجم‌های بانک اطلاعاتی زیر 10 گیگابایت ، می‌تواند LocalDB باشد. به علاوه اینکه قابلیت‌های T-SQL بیشتری را نیز پشتیبانی می‌کند و همچنین پشتیبانی منظمی نیز از آن وجود دارد. برای مثال پیش نمایش نگارش 2016 آن نیز موجود است.

در ادامه، یک سری پرسش و پاسخ متداول جهت کار با LocalDB را مرور خواهیم کرد.


محل دریافت آخرین نگارش مستقل آن کجاست؟

همانطور که عنوان شد، یکی از مهم‌ترین اهداف LocalDB، سهولت توزیع آن است و عدم نیاز به یک Admin سیستم، برای نصب و نگهداری آن. نگارش 2014 SP1 آن‌را از آدرس ذیل می‌توانید دریافت کنید:
https://www.microsoft.com/en-us/download/details.aspx?id=46697

در اینجا نسخه‌های متعددی وجود دارند. برای مثال اگر سیستم شما 64 بیتی است، تنها نیاز است ENU\x64\SqlLocalDB.msi را دریافت و نصب کنید:



پارامترهای نصب خاموش آن برای توزیع ساده‌ی برنامه کدامند؟

اگر می‌خواهید نصاب LocalDB را به همراه setup برنامه‌ی خود توزیع کنید، می‌توانید روش توزیع خاموش را با ذکر پارامترهای ذیل، مورد استفاده قرار دهید:
 msiexec /i SqlLocalDB.msi /qn IACCEPTSQLLOCALDBLICENSETERMS=YES


رشته‌ی اتصالی مخصوص آن کدام است؟

  <connectionStrings>
    <add name="Sample35Context"
        connectionString="Data Source=(LocalDB)\MSSQLLocalDB;AttachDbFilename=|DataDirectory|\test.mdf;Integrated Security=True;"
        providerName="System.Data.SqlClient" />
  </connectionStrings>
اگر نگارش 2014 SP1 آن‌را نصب کرده باشید، رشته‌ی اتصالی فوق، تمام آن‌چیزی است که برای شروع به کار با آن، نیاز دارید و دارای دو قسمت مهم است:
الف) ذکر وهله‌ی مدنظر
در اینجا وهله‌ی MSSQLLocalDB ذکر شده‌است؛ اما چه وهله‌هایی بر روی سیستم نصب هستند و چطور می‌توان وهله‌ی دیگری را ایجاد کرد؟ برای این منظور، به پارامترهای sqlservr.exe ابتدای بحث دقت کنید. اکثر آن‌ها به پوشه‌ی ذیل اشاره می‌کنند:
 C:\Users\your_user_name_here\AppData\Local\Microsoft\Microsoft SQL Server Local DB\Instances
با یک چنین محتوایی

در این پوشه، وهله‌‌های موجود و نصب شده‌ی بر روی سیستم شما نمایش داده می‌شوند که یکی از آن‌ها را می‌توانید در رشته‌ی اتصالی فوق ذکر کنید.
به علاوه، این لیست را توسط برنامه‌ی کمکی SqlLocalDB.exe، به همراه پارامتر info یا i نیز می‌توانید دریافت و بررسی کنید:


برنامه‌ی کمکی SqlLocalDB.exe به همراه نصاب LocalDB، نصب می‌شود و توسط آن می‌توان نگارش‌های مختلف نصب شده‌را با پارامتر v و وهله‌ی مختلف موجود را با پارامتر i مشاهده کرد.
همچنین اگر می‌خواهید وهله‌ی جدیدی را بجز وهله‌ی پیش فرض MSSQLLocalDB ایجاد کنید، می‌توانید از پارامتر create آن به نحو ذیل استفاده نمائید:
For LocalDB SQL EXPRESS 2014
 "C:\Program Files\Microsoft SQL Server\120\Tools\Binn\SqlLocalDB.exe" create "v12.0" 12.0 -s

For LocalDB SQL Express 2012
 "C:\Program Files\Microsoft SQL Server\110\Tools\Binn\SqlLocalDB.exe" create "v11.0" 11.0 -s

ب) ذکر DataDirectory
در رشته‌ی اتصالی فوق، پارامتر DataDirectory نیز ذکر شده‌است تا بتوان مسیر بانک اطلاعاتی را به صورت نسبی و بدون ذکر عبارت دقیق آن که ممکن است در سیستم‌های دیگر متفاوت باشد، پردازش کرد. این پارامتر در برنامه‌های وب به پوشه‌ی استاندارد app_data اشاره می‌کند و نیازی به تنظیم اضافه‌تری ندارد. اما در برنامه‌های دسکتاپ باید به نحو ذیل به صورت دستی، در آغاز برنامه مقدار دهی شود:
 AppDomain.CurrentDomain.SetData("DataDirectory", AppDomain.CurrentDomain.BaseDirectory);
به این ترتیب DataDirectory به محل قرارگیری فایل exe برنامه اشاره می‌کند. بدیهی است در اینجا هر پوشه‌ی دیگری را نیز می‌توانید ذکر کنید:
 AppDomain.CurrentDomain.SetData("DataDirectory", Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "db"));
برای نمونه تنظیم فوق به زیر پوشه‌ی db، در کنار فایل exe برنامه اشاره می‌کند.


محل نصب بانک‌های اطلاعاتی پیش فرض آن کدام است؟

ذکر AttachDbFilename در رشته‌ی اتصالی فوق، اختیاری است. در صورت عدم ذکر آن، بانک اطلاعاتی ایجاد شده را در یکی از مسیرهای ذیل می‌توانید جستجو کنید:
 C:\Users\USERNAME\AppData\Local\Microsoft\Microsoft SQL Server Local DB\Instances
C:\Users\USERNAME\AppData\Local\Microsoft\VisualStudio\SSDT
همچنین در این محل‌ها فایل‌های log متنی خطاهای این بانک اطلاعاتی را نیز می‌توان مشاهده کرد. بنابراین اگر به خطای خاصی برخوردید، بهترین کار، بررسی این فایل‌‌ها است.


آیا می‌توان فایل‌های mdf و ldf آن‌را به نگارش کامل SQL Server متصل (attach) کرد؟

بله. اما باید دقت داشته باشید که SQL Server به محض اتصال یک بانک اطلاعاتی با نگارش پایین‌تر به آن، ابتدا شماره نگارش آن‌‌را به روز می‌کند. یعنی دیگر نخواهید توانست این بانک اطلاعاتی را با نگارش پایین‌تر LocalDB باز کنید و یک چنین پیام خطایی را دریافت خواهید کرد:
 The database xyz cannot be opened because it is version 706. This server supports version 663 and earlier. A downgrade path is not supported.


چگونه محتوای بانک‌های اطلاعاتی LocalDB را با VS.NET مشاهده کنیم؟

از منوی view گزینه‌ی server explorer را انتخاب کنید. بر روی data connections کلیک راست کرده و گزینه‌ی Add connection را انتخاب کنید.


در صفحه‌ی باز شده، گزینه‌ی Microsoft SQL server  را انتخاب کنید. در صفحه‌ی بعد، ذکر server name مطابق data source رشته‌ی اتصالی بحث شده و سپس انتخاب گزینه‌ی attach a database file کفایت می‌کند:


پس از کلیک بر روی ok، امکان کار با اجزای این بانک اطلاعاتی را خواهید داشت:



چگونه از LocalDB با EF استفاده کنیم؟

EF 6.x به صورت پیش فرض از بانک اطلاعاتی LocalDB استفاده می‌کند و تنها داشتن یک چنین تنظیمی در فایل کانفیگ برنامه، برای کار با آن کافی است:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <configSections>
    <section name="entityFramework" type="System.Data.Entity.Internal.ConfigFile.EntityFrameworkSection, EntityFramework, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" />
  </configSections>
  <connectionStrings>
    <add name="Sample35Context"
        connectionString="Data Source=(LocalDB)\MSSQLLocalDB;AttachDbFilename=|DataDirectory|\test.mdf;Integrated Security=True;"
        providerName="System.Data.SqlClient" />
  </connectionStrings>
  <startup>
    <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
  </startup>
  <entityFramework>
    <defaultConnectionFactory type="System.Data.Entity.Infrastructure.LocalDbConnectionFactory, EntityFramework">
      <parameters>
        <parameter value="mssqllocaldb" />
      </parameters>
    </defaultConnectionFactory>
    <providers>
      <provider invariantName="System.Data.SqlClient" type="System.Data.Entity.SqlServer.SqlProviderServices, EntityFramework.SqlServer" />
    </providers>
  </entityFramework>
</configuration>
یک قسمت آن ذکر رشته‌ی اتصالی است که در مورد آن بحث شد و قسمت دوم آن، ذکر connection factory مخصوص localdb است که به صورت فوق می‌باشد. تنظیم دیگری برای کار با LocalDB و EF 6.x نیازی نیست.
البته باید دقت داشت که اسمبلی EntityFramework.SqlServer نیز به صورت خودکار به همراه بسته‌ی نیوگت EF 6.x به برنامه اضافه می‌شود که استفاده‌ی از connection factory ذکر شده را میسر می‌کند.


استفاده‌ی از LocalDB به همراه برنامه‌های وب چگونه است؟

سه نکته را باید در حین استفاده‌ی از LocalDB، در برنامه‌های وب اجرا شده‌ی بر روی IIS مدنظر داشت:
الف) LocalDB یک بانک اطلاعاتی user-mode است و child process آن تحت مجوز اکانت تنظیم شده‌ی برای آن کار می‌کند.
ب) همانطور که عنوان شد، در رشته‌ی اتصالی ذکر شده، پارامتر DataDirectory به پوشه‌ی استاندارد app_data اشاره می‌کند که فایل‌های قرار گرفته‌ی در آن توسط IIS محافظت می‌شوند و از طریق وب قابل دسترسی و دانلود نیستند.
ج) child process مربوط به LocalDB، نیاز به دسترسی write، برای کار با فایل‌های mdf و ldf خود دارد.

برای مورد الف نیاز است تا به تنظیمات application pool برنامه مراجعه کرده و سپس بر روی آن کلیک راست کرد و گزینه‌ی advanced settings را انتخاب نمود. در اینجا گزینه‌ی load user profile باید true باشد:


تنظیم load user profile ضروری است اما کافی نیست. پس از آن باید setProfileEnvironment را نیز به true تنظیم کرد. تنظیم این مورد در کنسول مدیریتی IIS به صورت زیر است.
ابتدا ریشه‌ی اصلی سرور را انتخاب کنید و سپس به configuration editor آن وارد شوید:


در ادامه از دارپ داون آن، گزینه‌ی system.applicationHost و زیر شاخه‌ی applicationPools آن‌را انتخاب کنید:


در اینجا application pool defaults و سپس در آن processModel را نیز باز کنید:


اکنون امکان ویرایش setProfileEnvironment را به true خواهید داشت:


پس از این تنظیم، ابتدا بر روی دکمه‌ی apply سمت راست صفحه کلیک کرده و سپس نیاز است یکبار IIS را نیز ریست کنید تا تنظیمات اعمال شوند.


در ادامه برای تنظیم دسترسی write (موارد ب و ج)، ابتدا بر روی پوشه‌ی app_data برنامه، کلیک راست کرده و برگه‌ی security آن‌را باز کنید. سپس بر روی دکمه‌ی edit کلیک کرده و در صفحه‌ی باز شده بر روی دکمه‌ی add کلیک کنید تا بتوان به کاربر application pool برنامه دسترسی write داد:


در اینجا iis apppool\TestLocalDB را وارد کرده و بر روی دکمه‌ی check name کلیک کنید.

iis apppool آن که مشخص است. عبارت TestLocalDB نام application pool ایی است که برای برنامه‌ی وب خود ایجاد کرده‌ایم (بهتر است به ازای هر برنامه‌ی وب، یک application pool مجزا تعریف شود).


در اینجا بر روی OK کلیک کرده و به این کاربر جدید اضافه شده، دسترسی full control را بدهید تا برنامه و یوزر آن بتواند فایل‌های mdf و ldf را ایجاد کرده و به روز رسانی کنند.

پس از تنظیم load user profile و همچنین set profile environment و دادن دسترسی write به کاربر application pool برنامه، اکنون child process مربوط به local db را می‌توان ذیل پروسه‌ی IIS مشاهده کرد و برنامه قادر به استفاده‌ی از LocalDB خواهد بود:

مطالب
بررسی جزئیات برنامه نویسی افزونه تاریخ فارسی برای outlook 2007 - قسمت اول

قبل از شروع بحث، سورس کامل پروژه را از اینجا دریافت کنید (یک پروژه VSTO از نوع outlook add-in در VS.Net 2008 SP1).

توضیحات مربوطه را به دو قسمت تقسیم کرده‌ام. قسمت اول یافتن تاریخ‌های sent و فارسی کردن آنها و قسمت بعدی نحوه اضافه کردن یک ستون و مقدار دهی آن (در روزی دیگر).

متن ایمیل‌های دریافتی در آوت‌لوک‌های جدید عموما به دو فرمت HTML و یا RichText دریافت می‌شوند. حالت‌های دیگری هم مانند plain و unspecified هم موجود هستند که حتی اگر ایمیلی را به صورت plain ارسال نمائید، با فرمت RichText نمایش داده خواهد شد (بنابراین بر اساس آزمایشات من بررسی این دو فرمت کفایت می‌کند).

برای اینکه قسمت‌های sent را پیدا کنیم در ابتدا باید سورس صفحه را بررسی نمائیم (کلیک راست و view source).
در حالت فرمت HTML داریم:

<p class=MsoNormal><b><span style='font-size:10.0pt;font-family:"Tahoma","sans-serif"'>From:</span></b><span
style='font-size:10.0pt;font-family:"Tahoma","sans-serif"'> Nasiri, Vahid <br>
<b>Sent:</b> <span lang=AR-SA dir=RTL>our date goes here</span><br>
<b>To:</b> xyz<br>
<b>Subject:</b> our subject<o:p></o:p></span></p>


و در حالت ایمیل‌های RichText خواهیم داشت:

From: tst@tst.net<br>
Sent:<span lang=AR-SA dir=RTL>our date goes here</span><br>
To: Nasiri, Vahid<br>
Subject: <span lang=AR-SA dir=RTL>xyz</span><br>

خوب، برای پیدا کردن عبارت تاریخ قسمت sent چه باید کرد؟ (our date goes here در اینجا)
استفاده از روش‌های متداول کار با رشته‌ها در اینجا به علت انبوهی از تگ‌های HTML اصلا مقرون به صرفه نیست و کند خواهد بود. خوشبختانه با وجود کتابخانه regular expressions در دات نت، پیدا کردن عباراتی که از یک الگوی خاص پیروی می‌کنند به سادگی و با سرعت بسیار بالایی قابل انجام است.
پیشنهاد من برای دو فرمت بالا به صورت زیر بوده: (شاید شما الگوی دیگری را یافتید، زیبایی اوپن سورس :))

private const string REGEXHTMLPATTERN = @"(?s)>\s(.+?)<br>";
private const string REGEXPLAINTEXTPATTERN = "(?s)Sent:(.+?)<br>";

برای مثال در حالت دوم هر چیزی که بین sent و br قرار می‌گیرد در کل متن بررسی خواهد شد (با استفاده از MatchCollection فضای نام System.Text.RegularExpressions). در اینجا اگر Convert.ToDateTime آن عبارت موفق بود یعنی تاریخ قابل تبدیل است (البته قبل از تبدیل تمام تگ‌های HTML احتمالی هم تمیز خواهند شد) و ما آنرا با استفاده از تابع DateTimeToFarsiStr در کلاس cDate به نمونه شمسی تبدیل کرده (لطفا به سورس برنامه مراجعه کنید) و نهایتا آنرا در متن جایگزین می‌کنیم.
سرعت استفاده از RegularExpressions فوق العاده بالا است و برای نمونه در ایمیلی با بیش از 20 ریپلای در کسری از ثانیه کل این عملیات انجام خواهد شد.

تا اینجا بررسی کلی الگوریتم مورد استفاده قسمت اول به پایان می‌رسد.

بیشترین وقتی که در این پروژه صرف شد نحوه پیدا کردن شیء MailItem جاری باز شده با استفاده از رخدادهای آوت‌لوک بود (مدت مدیدی را برای این مورد وقت گذاشتم! چون عملا در هیچ کتابی به این مباحث پرداخته نمی‌شود و باید کل نت را زیر و رو کرد). دو مورد را باید بررسی کرد. الف) inspector ها (صفحه‌ای که جهت ایجاد یک ایمیل جدید یا ریپلای به ایمیل جاری باز می‌شود، inspector نام دارد) ب) ActiveExplorer ها (صفحه‌ای که لیست ایمیل‌ها را نمایش می‌دهد و این صفحه می‌تواند در فولدرهای مختلفی که شما ایجاد کرده‌اید نیز نمایش داده شود بنابراین بررسی inbox به تنهایی کافی نیست)
نحوه ایجاد اشیاء مربوطه و تحت نظر قرار دادن آنها را در روال ThisAddIn_Startup فایل ThisAddIn.cs می‌توانید مشاهده نمائید. نکته مهمی که اینجا وجود دارد، تعریف این اشیاء در سطح کلاس است. در غیراینصورت با اولین خانه تکانی garbage collector ، اشیاء شما (بدلیل نبود ارجاعی فعال به آنها) معدوم خواهند شد(!) و دیگر روال‌های رخداد گردان تعریف شده کار نخواهند.