مطالب
ایجاد نصاب یک قالب پروژه جدید چند پروژه‌ای در ویژوال استودیو
در ویژوال استودیو ذیل منوی File، گزینه‌ای وجود دارد به نام  Export template که کار آن تهیه یک قالب، بر اساس ساختار پروژه جاری است. این قابلیت جهت تهیه قالب‌های سفارشی، برای کاهش زمان تهیه پروژه‌ها بسیار مفید است. به این ترتیب می‌توان بسیاری از نکات مدنظر را، در یک قالب ویژه لحاظ کرد و به دفعات بدون نیاز به copy/paste مداوم فایل‌ها و تنظیمات اولیه، بسیار سریع یک پروژه جدید دلخواه را ایجاد نمود.
اما ... این قالب تهیه شده، صرفا بر اساس یکی از چندین پروژه Solution جاری تهیه می‌شود و همچنین نصب و توزیع آن نیز دستی است. در ادامه قصد داریم با نحوه تهیه یک قالب جدید پروژه متشکل از چندین پروژه، به همراه تهیه فایل VSI نصاب آن، آشنا شویم.


تهیه یک ساختار نمونه

یک پروژه جدید کنسول را به نام MyConsoleApplication ایجاد کنید. سپس به Solution جاری، یک Class library جدید را به نام مثلا MyConsoleApplication.Tests اضافه نمائید. تا اینجا به شکل زیر خواهیم رسید:


اکنون قصد داریم از این پروژه خاص، یک قالب تهیه کنیم؛ تا هربار نخواهیم یک چنین مراحلی را تکرار کنیم.


تهیه قالب به ازای هر پروژه در Solution

در همین حال که Solution باز است، به منوی File و گزینه Export template مراجعه کنید.


در اینجا تنها امکان انتخاب یک پروژه وجود دارد. به همین جهت این مرحله را باید به ازای هر تعداد پروژه موجود در Solution یکبار تکرار کرد.


اکنون در پوشه My Documents\Visual Studio 2010\My Exported Templates دو فایل zip به نام‌های MyConsoleApplication.zip و MyConsoleApplication.Tests.zip وجود دارند. هر دو فایل را توسط برنامه‌های مخصوص گشودن فایل‌های Zip گشوده و تبدیل به دو پوشه باز شده MyConsoleApplication و MyConsoleApplication.Tests کنید.



افزودن فایل MyTemplate.vstemplate چند پروژه‌ای

در همین پوشه جاری که اکنون حاوی دو پوشه باز شده است، یک فایل متنی جدید را با محتوای ذیل به نام MyTemplate.vstemplate ایجاد کنید:
<VSTemplate Version="3.0.0" Type="ProjectGroup"
xmlns="http://schemas.microsoft.com/developer/vstemplate/2005">
  <TemplateData>
    <Name>MyConsoleApplication</Name>
    <Description>MyConsoleApplication Desc</Description>
    <ProjectType>CSharp</ProjectType>
  </TemplateData>
  <TemplateContent>
    <ProjectCollection>
      <ProjectTemplateLink ProjectName="MyConsoleApplication">
      MyConsoleApplication\MyTemplate.vstemplate</ProjectTemplateLink>
      <ProjectTemplateLink ProjectName="MyConsoleApplication.Tests">
      MyConsoleApplication.Tests\MyTemplate.vstemplate</ProjectTemplateLink>
    </ProjectCollection>
  </TemplateContent>
</VSTemplate>
در اینجا به ازای هر پروژه، یک ProjectTemplateLink ایجاد خواهد شد که به فایل MyTemplate.vstemplate موجود در قالب آن اشاره می‌کند.
در ادامه این دو پوشه باز شده و فایل MyTemplate.vstemplate فوق را انتخاب کرده:


و همگی را تبدیل به یک فایل zip جدید کنید؛ مثلا به نام MyConsoleApplicationTemplates.zip.


تهیه فایل نصاب از قالب پروژه جدید

تا اینجا موفق شدیم، چندین قالب پروژه تهیه شده را به هم متصل کرده و تبدیل به یک فایل zip نهایی کنیم. مرحله بعد ایجاد فایلی است متنی به نام MyConsoleApplicationTemplates.vscontent با محتویات زیر:
<VSContent xmlns="http://schemas.microsoft.com/developer/vscontent/2005">
  <Content>
    <FileName>MyConsoleApplicationTemplates.zip</FileName>
    <DisplayName>MyConsoleApplication</DisplayName>
    <Description>A C# project that ...</Description>
    <FileContentType>VSTemplate</FileContentType>
    <ContentVersion>1.0</ContentVersion>
    <Attributes>
      <Attribute name="ProjectType" value="Visual C#" />
      <Attribute name="ProjectSubType" value="Web" />
      <Attribute name="TemplateType" value="Project" />
    </Attributes>
  </Content>
</VSContent>
در اینجا توسط قسمت Attributes مشخص می‌کنیم که قالب پروژه جدید باید در صفحه new project، در کدام مدخل قرار گیرد. بنابراین مطابق تنظیمات فوق، قالب جدید ذیل پروژه‌های وب سی‌شارپ قرار خواهد گرفت. مقدار FileName آن دقیقا معادل نام فایل zip ایی است که در مرحله قبل ایجاد کردیم.

مرحله بعد انتخاب دو فایل MyConsoleApplicationTemplates.vscontent و MyConsoleApplicationTemplates.zip و تبدیل ایندو به یک فایل zip جدید است. پس از ایجاد فایل جدید، پسوند آن‌را به VSI تغییر دهید؛ برای مثال نام آن‌را به MyConsoleApplicationTemplates.vsi تغییر دهید. اکنون این فایل نهایی با دوبار کلیک بر روی آن قابلیت اجرا و نصب خودکار را پیدا می‌کند.


پس از نصب، بلافاصله ذیل قسمت پروژه‌های وب قابل دسترسی و استفاده خواهد بود:



بنابراین به صورت خلاصه:
1) به ازای هر پروژه، یک فایل قالب zip معادل آن باید تهیه شود.
2) تمام این فایل‌های zip را گشوده و تبدیل به پوشه‌های متناظری کنید.
3) یک فایل MyTemplate.vstemplate را در پوشه ریشه مرحله 2 جهت تعریف ProjectTemplateLink‌ها اضافه کنید.
4) فایل جدید MyTemplate.vstemplate مرحله 3 و تمام پوشه‌های قالب‌های باز شده مرحله 2 را zip کنید.
5) سپس یک فایل vscontent نصاب را تهیه و آن‌را با فایل zip مرحله 4 مجددا zip کرده و پسوند آن‌را به VSI تغییر دهید.
اکنون می‌توان این فایل VSI را توزیع کرد.
نظرات مطالب
نمایش ساختارهای درختی توسط jqGrid
سلام
امکان افزودن، ویرایش و حذف بصورت پویا در jqGrid درختی وجود دارد؟
من لینک ویرایش و حذف را به هر رکورد اضافه کردم ولی گرید بهم ریخته شد.
نظرات مطالب
انجام کارهای زمانبندی شده در برنامه‌های ASP.NET توسط DNT Scheduler
سلام؛ من یه سیستم ماژولار بیس نوشتم که تمام اجزا اون جداست و بروی چهار چوب اصلی یا همون هسته سوار میشه و اجرا میشه .
یه ماژول دارم ارسال ایمیل‌های زمانبندی شده که همین کلاس شما رو قرار دادم و مستقل داره کار می‌کنه .
الان می‌خوام دو سه ماژول دیگه ایجاد کنم که کاری رو بر مبنای زمانبندی انجام میدن .
حالا مشکل اینجاست چطوری از این کلاس استفاده کنم که بتونه تداخل انجام تسک نداشته باشه ؟
اولین و بهترین حالتی که به ذهنم رسید سوار کردن این کلاس بروی هسته هست که بتونه کل تسک‌ها رو مدیرت کنه و این کار با قرار دادن کلاس "ScheduledTasksRegistry " در هسته و قرار دادن کلاس "ScheduledTaskTemplate " برای هر ماژول قابل اجراست .
اما مشکل اینجاست که فایل ScheduledTasksRegistry  کامپایل شده و نمی‌توان متد :
ScheduledTasksCoordinator.Current.AddScheduledTasks(
new Listener(),
new Sender()
);
را تغییر داد .
مجدد توی ذهنم overwrite کردن این متد امد ولی چطوری ؟ ایا امکانپدیر هست ؟
دقیقا من باید بتونم این قسمت رو runtime تغییر بدم و کلاس‌های دیگر رو اضافه کنم .
مطالب
React 16x - قسمت 8 - ترکیب کامپوننت‌ها - بخش 2 - مدیریت state
در ادامه‌ی بحث ترکیب کامپوننت‌ها، پس از نمایش لیستی از کامپوننت‌های شمارشگر و مقدار دهی عدد آغازین آن‌ها، به همراه مدیریت حذف هر ردیف در قسمت قبل، اکنون می‌خواهیم دکمه‌ای را اضافه کنیم تا تمام شمارشگرها را به حالت اول خودشان بازگرداند. برای این منظور دکمه‌ی Reset را به ابتدای المان‌های کامپوننت Counters اضافه می‌کنیم:
<button
  onClick={this.handleReset}
  className="btn btn-primary btn-sm m-2"
>
  Reset
</button>
سپس متد رویدادگردان handleReset آن‌را به صورت زیر با تنظیم مقدار value هر counter به صفر و بازگشت آن و در نهایت به روز رسانی state کامپوننت با این آرایه‌ی جدید، پیاده سازی می‌کنیم:
  handleReset = () => {
    const counters = this.state.counters.map(counter => {
      counter.value = 0;
      return counter;
    });
    this.setState({ counters }); // = this.setState({ counters: counters });
  };


اکنون پس از ذخیره سازی فایل counters.jsx و بارگذاری مجدد برنامه در مرورگر، هرچقدر بر روی دکمه‌ی Reset کلیک کنیم ... اتفاقی رخ نمی‌دهد! حتی اگر به افزونه‌ی React developer tools نیز مراجعه کنیم، مشاهده خواهیم کرد که عمل تنظیم value به صفر، در تک تک کامپوننت‌های شمارشگر، به درستی صورت گرفته‌است؛ اما تغییرات به DOM اصلی منعکس نشده‌اند:


البته اگر به همین تصویر دقت کنید، هنوز مقدار count، در state آن 4 است. علت اینجا است که هر کدام از Counterها دارای local state خاص خودشان هستند و در آن‌ها، مقدار count به صورت زیر مقدار دهی شده‌است که در آن تغییرات بعدی این this.props.value، متصل به count نیست و count، فقط یکبار مقدار دهی می‌شود:
class Counter extends Component {
  state = {
    count: this.props.counter.value
  };
این قطعه‌ی از کد، تنها زمانی اجرا می‌شود که یک وهله از کلاس کامپوننت Counter، در حال ایجاد است. به همین جهت زمانیکه صفحه برای بار اول بارگذاری می‌شود، مقدار آغازین count به درستی دریافت می‌شود. اما با کلیک بر روی دکمه‌ی Reset، هرچند مقدار value هر شیء counter تعریف شده‌ی در کامپوننت والد تغییر می‌کند، اما local state کامپوننت‌های فرزند به روز رسانی نمی‌شوند و مقدار جدید value را دریافت نمی‌کنند. برای رفع یک چنین مشکلی نیاز است یک مرجع مشخص را برای مقدار دهی stateهای کامپوننت‌های فرزند ایجاد کنیم.


حذف Local state

اکنون می‌خواهیم در کامپوننت Counter، قسمت local state آن‌را به طور کامل حذف کرده و تنها از this.props جهت دریافت اطلاعاتی که نیاز دارد، استفاده کنیم. به این نوع کامپوننت‌ها، «‍Controlled component» نیز می‌گویند. یک کامپوننت کنترل شده دارای local state خاص خودش نیست و تمام داده‌های دریافتی را از طریق this.props دریافت می‌کند و هر زمانیکه قرار است داده‌ای تغییر کند، رخ‌دادی را به والد خود صادر می‌کند. بنابراین این کامپوننت به طور کامل توسط والد آن کنترل می‌شود.
برای پیاده سازی این مفهوم، ابتدا خاصیت state کامپوننت Counter را حذف می‌کنیم. سپس تمام ارجاعات به this.state را در این کامپوننت یافته و آن‌ها را تغییر می‌دهیم. اولین ارجاع، در متد handleIncrement به صورت this.state.count تعریف شده‌است:
  handleIncrement = () => {
    this.setState({ count: this.state.count + 1 });
  };
 از این جهت که دیگر دارای local state نیستیم، داشتن متد this.setState در اینجا بی‌مفهوم است. در یک کامپوننت کنترل شده، هر زمانیکه قرار است داده‌ای ویرایش شود، این کامپوننت باید رخ‌دادی را صادر کرده و از والد خود درخواست تغییر اطلاعات را ارائه دهد؛ شبیه به this.props.onDelete ای که در قسمت قبل کامل کردیم. بنابراین کل متد handleIncrement را نیز حذف می‌کنیم. اینبار رخ‌داد onClick، سبب بروز رخداد onIncrement در والد خود خواهد شد:
<button
  onClick={() => this.props.onIncrement(this.props.counter)}
  className="btn btn-secondary btn-sm"
>
  Increment
</button>
همچنین دو متد دیگری که ارجاعی را به this.state داشتند، به صورت زیر جهت استفاده‌ی از this.props.counter.value، به روز رسانی می‌شوند:
  getBadgeClasses() {
    let classes = "badge m-2 badge-";
    classes += this.props.counter.value === 0 ? "warning" : "primary";
    return classes;
  }

  formatCount() {
    const { value } = this.props.counter; // Object Destructuring
    return value === 0 ? "Zero" : value;
  }
تا اینجا به صورت کامل local state این کامپوننت حذف و با this.props جایگزین شده و در نتیجه تحت کنترل کامپوننت والد آن قرار می‌گیرد.

در ادامه به کامپوننت Counters مراجعه کرده و متد رویدادگردانی را جهت پاسخگویی به رخ‌داد onIncrement رسیده‌ی از کامپوننت‌های فرزند، تعریف می‌کنیم:
  handleIncrement = counter => {
    console.log("handleIncrement", counter);
  };
سپس ارجاعی از این متد را به ویژگی onIncrement تعریف شده‌ی در المان Counter، متصل می‌کنیم:
  <Counter
    key={counter.id}
    counter={counter}
    onDelete={this.handleDelete}
    onIncrement={this.handleIncrement}
  />
اکنون هر زمانیکه بر روی دکمه‌ی Increment کلیک شود، this.props.onIncrement آن، سبب فراخوانی متد handleIncrement والد خود خواهد شد.

پیاده سازی کامل متد handleIncrement اینبار به صورت زیر است:
  handleIncrement = counter => {
    console.log("handleIncrement", counter);
    const counters = [...this.state.counters]; // cloning an array
    const index = counters.indexOf(counter);
    counters[index] = { ...counter }; // cloning an object
    counters[index].value++;
    console.log("this.state.counters", this.state.counters[index]);
    this.setState({ counters });
  };
همانطور که در قسمت‌های قبل نیز عنوان شد، در React نباید مقدار state را به صورت مستقیم ویرایش کرد؛ مانند مراجعه‌ی مستقیم به this.state.counters[index] و سپس تغییر خاصیت value آن‌. بنابراین باید یک clone از آرایه‌ی counters و سپس یک clone از شیء counter رسیده‌ی از کامپوننت فرزند را ایجاد کنیم تا این cloneها دیگر ارجاعی را به اشیاء اصلی ساخته شده‌ی از روی آن‌ها نداشته باشند (مهم‌ترین خاصیت یک clone) تا اگر خاصیت و مقداری را در آن‌ها تغییر دادیم، دیگر به شیء اصلی که از روی آن‌ها clone شده‌اند، منعکس نشوند. در اینجا از spread operator برای ایجاد این cloneها استفاده شده‌است. اکنون مقادیر خواص این cloneها را تغییر می‌دهیم و درنهایت این counters جدید را که خودش نیز یک clone است، به متد this.setState جهت به روز رسانی UI و همچنین state کامپوننت، ارسال می‌کنیم.

تا اینجا اگر برنامه را ذخیره کرده و منتظر به روز رسانی آن در مرورگر شویم، با کلیک بر روی Reset، تمام کامپوننت‌ها با هر وضعیتی که پیشتر داشته باشند، به حالت اول خود باز می‌گردند:



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


در ادامه می‌خواهیم یک منوی راهبری (یا همان NavBar در بوت استرپ) را به بالای صفحه اضافه کنیم و در آن جمع کل تعداد Counterهای رندر شده را نمایش دهیم؛ مانند نمایش تعداد آیتم‌های انتخاب شده‌ی توسط یک کاربر، در یک سبد خرید. برای پیاده سازی آن، درخت کامپوننت‌های React را مطابق شکل فوق تغییر می‌دهیم. یعنی مجددا کامپوننت App را در به عنوان کامپوننت ریشه‌ای انتخاب کرده که سایر کامپوننت‌ها از آن مشتق می‌شوند و همچنین کامپوننت مجزای NavBar را نیز اضافه خواهیم کرد.
برای این منظور به index.js مراجعه کرده و مجددا کامپوننت App را که غیرفعال کرده بودیم و بجای آن Counters را نمایش می‌دادیم، اضافه می‌کنیم:
import App from "./App";

ReactDOM.render(<App />, document.getElementById("root"));

سپس کامپوننت جدید NavBar را توسط فایل جدید src\components\navbar.jsx اضافه می‌کنیم تا منوی راهبری سایت را نمایش دهد:
import React, { Component } from "react";

class NavBar extends Component {
  render() {
    return (
      <nav className="navbar navbar-light bg-light">
        <a className="navbar-brand" href="#">
          Navbar
        </a>
      </nav>
    );
  }
}

export default NavBar;

اکنون به App.js مراجعه کرده و متد render آن‌را جهت نمایش درخت کامپوننت‌هایی که مشاهده کردید، تکمیل می‌کنیم:
import "./App.css";

import React from "react";

import Counters from "./components/counters";
import NavBar from "./components/navbar";

function App() {
  return (
    <React.Fragment>
      <NavBar />
      <main className="container">
        <Counters />
      </main>
    </React.Fragment>
  );
}

export default App;
ابتدا کامپوننت NavBar در بالای صفحه رندر می‌شود و سپس کامپوننت Counters در میانه‌ی صفحه. چون در اینجا چندین المان قرار است رندر شوند، از React.Fragment برای محصور کردن آن‌ها استفاده کرده‌ایم.
تا اینجا اگر برنامه را ذخیره کنیم تا در مرورگر بارگذاری مجدد شود، چنین شکلی حاصل شده‌است:


اکنون می‌خواهیم تعداد کامپوننت‌های شمارشگر را در navbar نمایش دهیم. پیشتر state کامپوننت Counters را توسط props، به کامپوننت‌های Counter رندر شده‌ی توسط آن انتقال دادیم. استفاده‌ی از این ویژگی به دلیل وجود رابطه‌ی والد و فرزندی بین این کامپوننت‌ها میسر شد. اما همانطور که در تصویر درخت کامپوننت‌های جدید تشکیل شده مشاهده می‌کنید، رابطه‌ی والد و فرزندی بین دو کامپوننت Counters و NavBar وجود ندارد. بنابراین اکنون این سؤال مطرح می‌شود که چگونه باید تعداد کل شمارشگرهای کامپوننت Counters را به کامپوننت NavBar، برای نمایش آن‌ها انتقال داد؟ در یک چنین حالت‌هایی که رابطه‌ی والد و فرزندی بین کامپوننت‌ها وجود ندارد و می‌خواهیم آن‌ها را همگام سازی کنیم و داده‌هایی را بین آن‌ها به اشتراک بگذاریم، باید state را به یک سطح بالاتر انتقال داد. یعنی در این مثال باید state کامپوننت Counters را به والد آن که اکنون کامپوننت App است، منتقل کرد. پس از آن چون هر دو کامپوننت NavBar و Counters، از کامپوننت App مشتق می‌شوند، اکنون می‌توان این state را به تمام فرزندان App توسط props منتقل کرد و به اشتراک گذاشت.


انتقال state به یک سطح بالاتر

برای انتقال state به یک سطح بالاتر، به کامپوننت Counters مراجعه کرده و خاصیت state آن‌را به همراه تمامی متدهایی که آن‌را تغییر می‌دهند و از آن استفاده می‌کنند، انتخاب و cut می‌کنیم. سپس به کامپوننت App مراجعه کرده و آن‌ها را در اینجا paste می‌کنیم. یعنی خاصیت state و متدهای handleDelete، handleReset و handleIncrement را از کامپوننت Counters به کامپوننت App منتقل می‌کنیم. این مرحله‌ی اول است. سپس نیاز است به کامپوننت Counters مراجعه کرده و ارجاعات به state و متدهای یاد شده را توسط props اصلاح می‌کنیم. برای این منظور ابتدا باید این props را در کامپوننت App مقدار دهی کنیم تا بتوانیم آن‌ها را در کامپوننت Counters بخوانیم؛ یعنی متد render کامپوننت App، تمام این خواص و متدها را باید به صورت ویژگی‌هایی به تعریف المان Counters اضافه کند تا خاصیت props آن بتواند به آن‌ها دسترسی داشته باشد:
  render() {
    return (
      <React.Fragment>
        <NavBar />
        <main className="container">
          <Counters
            counters={this.state.counters}
            onReset={this.handleReset}
            onIncrement={this.handleIncrement}
            onDelete={this.handleDelete}
          />
        </main>
      </React.Fragment>
    );
  }

پس از این تعاریف می‌توانیم به کامپوننت Counters بازگشته و ارجاعات فوق را توسط خاصیت props، در متد render آن اصلاح کنیم:
  render() {
    return (
      <div>
        <button
          onClick={this.props.onReset}
          className="btn btn-primary btn-sm m-2"
        >
          Reset
        </button>
        {this.props.counters.map(counter => (
          <Counter
            key={counter.id}
            counter={counter}
            onDelete={this.props.onDelete}
            onIncrement={this.props.onIncrement}
          />
        ))}
      </div>
    );
  }
در اینجا سه رویدادگردان و یک خاصیت counters، از طریق خاصیت props والد کامپوننت Counter که اکنون کامپوننت App است، خوانده می‌شوند.

پس از این نقل و انتقالات، اکنون می‌توانیم تعداد counters را در NavBar نمایش دهیم. برای این منظور ابتدا در کامپوننت App، به همان روشی که ویژگی counters={this.state.counters} را به تعریف المان Counters اضافه کردیم، شبیه به همین کار را برای کامپوننت NavBar نیز می‌توانیم انجام دهیم تا از طریق خاصیت props آن قابل دسترسی شود و یا حتی می‌توان به صورت زیر، تنها جمع کل را به آن کامپوننت ارسال کرد:
<NavBar
    totalCounters={this.state.counters.filter(c => c.value > 0).length}
/>

سپس در کامپوننت NavBar، عدد totalCounters فوق را که به تعداد کامپوننت‌هایی که مقدار value آن‌ها بیشتر از صفر است، اشاره می‌کند، از طریق خاصیت props خوانده و نمایش می‌دهیم:
class NavBar extends Component {
  render() {
    return (
      <nav className="navbar navbar-light bg-light">
        <a className="navbar-brand" href="#">
          Navbar{" "}
          <span className="badge badge-pill badge-secondary">
            {this.props.totalCounters}
          </span>
        </a>
      </nav>
    );
  }
}
که با ذخیره کردن این فایل و بارگذاری مجدد برنامه در مرورگر، به خروجی زیر خواهیم رسید:



کامپوننت‌های بدون حالت تابعی

اگر به کدهای کامپوننت NavBar دقت کنیم، تنها یک تک متد render در آن ذکر شده‌است و تمام اطلاعات مورد نیاز آن نیز از طریق props تامین می‌شود و دارای state و یا هیچ رویدادگردانی نیست. یک چنین کامپوننتی را می‌توان به یک «Stateless Functional Component» تبدیل کرد؛ کامپوننت‌های بدون حالت تابعی. در اینجا بجای اینکه از یک کلاس برای تعریف کامپوننت استفاده شود، می‌توان از یک function استفاده کرد (به همین جهت به آن functional می‌گویند). احتمالا نمونه‌ی آن‌را با کامپوننت App پیش‌فرض قالب create-react-app نیز مشاهده کرده‌اید که در آن فقط یک ()function App وجود دارد. البته در کدهای فوق چون نیاز به ذکر state، در کامپوننت App وجود داشت، آن‌را از حالت تابعی، به حالت کلاس استاندارد کامپوننت، تبدیل کردیم.
اگر بخواهیم کامپوننت بدون حالت NavBar را نیز تابعی کنیم، می‌توان به صورت زیر عمل کرد:
import React from "react";

// Stateless Functional Component
const NavBar = props => {
  return (
    <nav className="navbar navbar-light bg-light">
      <a className="navbar-brand" href="#">
        Navbar{" "}
        <span className="badge badge-pill badge-secondary">
          {props.totalCounters}
        </span>
      </a>
    </nav>
  );
};

export default NavBar;
برای اینکار قسمت return متد render کامپوننت را cut کرده و به داخل تابع NavBar منتقل می‌کنیم. بدنه‌ی این تابع را هم می‌توان توسط میان‌بر sfc که مخفف Stateless Functional Component است، در VSCode تولید کرد.
پیشتر در کامپوننت NavBar از شیء this استفاده شده بود. این روش تنها با کلاس‌های استاندارد کامپوننت کار می‌کند. در اینجا باید props را به عنوان پارامتر متد دریافت (همانند مثال فوق) و سپس از آن استفاده کرد.

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

یک نکته: امکان Destructuring Arguments نیز در اینجا وجود دارد. یعنی بجای اینکه یکبار props را به عنوان پارامتر دریافت کرد و سپس توسط آن به خاصیت totalCounters دسترسی یافت، می‌توان نوشت:
const NavBar = ({ totalCounters }) => {
در این حالت شیء props دریافت شده توسط ویژگی Objects Destructuring، به totalCounters تجزیه می‌شود و سپس می‌توان تنها از همین متغیر دریافتی، به صورت {totalCounters} در کدها استفاده کرد.



کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید: sample-08.zip
نظرات مطالب
تگ گذاری در کامنت‌ها

فوق العاده بود ، تگ TODO را وقتی از پروژه اجرا میگیرم هم به من در زمان اجرا یاد آوری میکنه ، ممنونم

نظرات مطالب
ASP.NET MVC #5
مشکل از وجود خطا در فایل وب کانفیگ بود. محل تگ location اشتباه تعریف شده بود.
نظرات مطالب
حذف فضاهای خالی در خروجی صفحات ASP.NET MVC
با تشکر از مطلب ارسالی شما 
برای اینکه فضای خالی به درستی حذف شود و همچنین تگ Pre هم در این الگوریتم لحاظ نشود. می‌توان از اکشن فیلتر زیر استفاده کرد 
public class RemoveWhitespacesAttribute : ActionFilterAttribute
    {

        public override void OnActionExecuted(ActionExecutedContext filterContext)
        {

            var response = filterContext.HttpContext.Response;
      
            if (filterContext.HttpContext.Request.RawUrl != "/sitemap.xml")
            {

                if (response.ContentType == "text/html" && response.Filter != null)
                {
                    response.Filter = new HelperClass(response.Filter);
                }
            }
        }

        private class HelperClass : Stream
        {

            private System.IO.Stream Base;

            public HelperClass(System.IO.Stream ResponseStream)
            {

                if (ResponseStream == null)
                    throw new ArgumentNullException("ResponseStream");
                this.Base = ResponseStream;
            }

            StringBuilder s = new StringBuilder();

            public override void Write(byte[] buffer, int offset, int count)
            {

                string HTML = Encoding.UTF8.GetString(buffer, offset, count);

                Regex reg = new Regex(@"(?<=\s)\s+(?![^<>]*</pre>)");
                HTML = reg.Replace(HTML, string.Empty);

                buffer = System.Text.Encoding.UTF8.GetBytes(HTML);
                this.Base.Write(buffer, 0, buffer.Length);
            }

            #region Other Members

            public override int Read(byte[] buffer, int offset, int count)
            {

                throw new NotSupportedException();
            }

            public override bool CanRead { get { return false; } }

            public override bool CanSeek { get { return false; } }

            public override bool CanWrite { get { return true; } }

            public override long Length { get { throw new NotSupportedException(); } }

            public override long Position
            {

                get { throw new NotSupportedException(); }
                set { throw new NotSupportedException(); }
            }

            public override void Flush()
            {

                Base.Flush();
            }

            public override long Seek(long offset, SeekOrigin origin)
            {

                throw new NotSupportedException();
            }

            public override void SetLength(long value)
            {

                throw new NotSupportedException();
            }

            #endregion
        }

    }
برای اجرا هم در Global.asax آن را فراخوانی کرد.  
 protected void Application_Start()
        {
            try
            {
                GlobalFilters.Filters.Add(new App_Start.RemoveWhitespacesAttribute());
            }
            catch
            {
                HttpRuntime.UnloadAppDomain(); // سبب ری استارت برنامه و آغاز مجدد آن با درخواست بعدی می‌شود
                throw;
            }

        }
در نهایت خروجی به شکل زیر رندر می‌شود

برای Gzip هم  اکثر در این حالت که هردو مورد با هم قرار داده شده است در برخی از موارد فایل‌های جاواسکریپ را با مشکل روبرو می‌کند .به نظر من از Gzip توکار IIS استفاده شود بهتر است. البته باید ماژول آن در ISS فعال شده باشد.

برای اینکار هم داخل Web.config کد‌های زیر را داخل configuration قرار بدید.


<httpCompression directory="%SystemDrive%\inetpub\temp\IIS Temporary Compressed Files">
      <scheme name="gzip" dll="%Windir%\system32\inetsrv\gzip.dll" staticCompressionLevel="9" />
      <dynamicTypes>
        <add mimeType="text/*" enabled="true" />
        <add mimeType="message/*" enabled="true" />
        <add mimeType="application/x-javascript" enabled="true" />
        <add mimeType="application/javascript" enabled="true" />
        <add mimeType="application/json" enabled="true" />
        <add mimeType="application/json; charset=utf-8" enabled="true" />
        <add mimeType="application/atom+xml" enabled="true" />
        <add mimeType="application/xaml+xml" enabled="true" />
        <add mimeType="*/*" enabled="false" />
      </dynamicTypes>
      <staticTypes>
        <add mimeType="text/*" enabled="true" />
        <add mimeType="message/*" enabled="true" />
        <add mimeType="application/x-javascript" enabled="true" />
        <add mimeType="application/javascript" enabled="true" />
        <add mimeType="application/json" enabled="true" />
        <add mimeType="application/json; charset=utf-8" enabled="true" />
        <add mimeType="application/atom+xml" enabled="true" />
        <add mimeType="application/xaml+xml" enabled="true" />
        <add mimeType="*/*" enabled="false" />
      </staticTypes>
    </httpCompression>
    <urlCompression doStaticCompression="true" doDynamicCompression="true" />
  </system.webServer>
  <location path="Default Web Site">
    <system.webServer>
      <serverRuntime enabled="true"
         frequentHitThreshold="1"
         frequentHitTimePeriod="10:00:00" />
    </system.webServer>
  </location>


مطالب
نحوه تنظیم زمانبندی (Scheduling) برای Crawler در شیرپوینت

برای جستجو در بین Documents ، Task ، Web Pages و .... نیاز است که در ابتدا Index شوند تا بتوان به آنها دسترسی داشت که این کار توسط پردازه Crawler انجام می‌گیرد . در این پست نحوه تنظیم زمانبندی برای Crawler و از نوع incremental بیان شده است . 

برای این منظور با نقش مدیر وارد Central Administration شده و به روش زیر عمل می‌کنیم : 


ابتدا روی Manage Service Application کلیک میکنیم 

 

سپس در صفحه باز شده که سرویس‌ها را در خود نگه میدارد ، روی Search Service Applicarion کلیک می‌کنیم 

در قسمت چپ صفحه روی Content Sources کلیک می‌کنیم تا صفحه مربوط به منابع جستجو باز شود 

در این قسمت روی Local Search Sites که به صورت پیش فرض وجود دارد کلی می‌کنیم 

سپس صفحه را به سمت پایین Scroll می‌کنیم تا به قسمت Crawl Schedules برسیم

(نکته : در قسمت Start Addresses به تنظیمات آن دست نزنید مگر آنکه از آنها مطمئن باشید . چرا که ممکن است indexing به خوبی کار نکند . معمولا به صورت پیش فرض نام sps3://srv1 وجود دارد که فرض میکند نام سرور شما srv1است . این لینک‌ها به تمام منابع که قرار است index شوند ، اشاره می‌کند ) 

همانطور که در بالا مشاهده می‌کنید ، هیچ زمانبندی تنظیم نشده است و باید یک مورد جدید ایجاد کرد . (اگر قبلا تنظیم کرده باشید ، به جای Create واژه Edit نمایان می‌شد ) 

در پنجره باز شده ، تنظیمات را به این صورت (مانند شکل زیر ) انجام دهید : 

Daily

Check the Repeat Within the Day

Every : 10 Min

 
این تنظیمات یعنی در طول روز و هر 10 دقیقه indexing‌ها را به صورت افزایشی انجام بده 



روی OK کلیک کنید . 

اکنون تغییرات به صور زیر قابل مشاهده است

 

دلیل اینکه Indexing را به صورت Full تنظیم نکردیم این است که زمانی که شما چند صد گیگابایت اطلاعات دارید ، crawler زمان بسیار زیادی را (چندین روز یا حتی هفته) صرف کامل کردن indexing می‌کند . در صورتی که شما به indexing به صورت کامل نیاز دارید ، گزینه Start Full Crawl را علامت یزنید 

موفق باشید