نظرات مطالب
Blazor 5x - قسمت 34 - توزیع برنامه‌های Blazor بر روی IIS
یک نکته: کاهش زمان build پروژه‌های Blazor در حالت توسعه

در پروژه‌های Blazor، خاصیت ویژه‌ی BlazorLinkOnBuild به true تنظیم شده‌است که سبب می‌شود تا موارد استفاده نشده‌ی در برنامه، از خروجی نهایی حذف شوند. این مورد هرچند حجم نهایی را کاهش می‌دهد، اما در زمان توسعه نیز فعال است و سبب بالا رفتن زمان build برنامه می‌شود؛ گاهی از اوقات تا 10 برابر! برای غیرفعال کردن آن در زمان debug و توسعه، می‌توان فایل csproj را به صورت زیر ویرایش کرد. این تنظیم در حین publish در حالت release، غیرفعال می‌شود:
<PropertyGroup>
    <BlazorLinkOnBuild  Condition="'$(Configuration)'!='Release'">false</BlazorLinkOnBuild>
</PropertyGroup>
مطالب
React 16x - قسمت 21 - کار با فرم‌ها - بخش 4 - چند تمرین
پس از فراگیری اصول کار کردن با فرم‌ها در React، اکنون می‌خواهیم چند فرم جدید را برای تمرین بیشتر، به برنامه‌ی نمایش لیست فیلم‌ها اضافه کنیم؛ مانند فرم ثبت نام، فرمی برای ثبت و یا ویرایش فیلم‌ها و یک فرم جستجوی سریع در لیست فیلم‌های موجود.

تمرین 1 - ایجاد فرم ثبت نام


می‌خواهیم به برنامه، فرم ثبت نام را که حاوی سه فیلد نام کاربری، کلمه‌ی عبور و نام است، اضافه کنیم. نام کاربری باید از نوع ایمیل باشد. بنابراین اعتبارسنجی مرتبطی نیز باید برای این فیلد تعریف شود. کلمه‌ی عبور وارد شده باید حداقل 5 حرف باشد. همچنین تا زمانیکه اعتبارسنجی فرم تکمیل نشده‌است، باید دکمه‌ی submit فرم، غیرفعال باقی بماند. لینک ورود به این فرم نیز باید به منوی راهبری سایت اضافه شود.

برای حل این تمرین، فایل جدید registerForm.jsx را در پوشه‌ی components ایجاد می‌کنیم و سپس توسط میانبرهای imrc و cc در VSCode، ساختار ابتدایی کامپوننت RegisterForm را ایجاد کرده و سپس آن‌را به صورت زیر تکمیل می‌کنیم:
- ابتدا در فایل app.js، پس از import ماژول آن:
import RegisterForm from "./components/registerForm";
در ابتدای سوئیچ تعریف شده، مسیریابی آن‌را تعریف می‌کنیم:
<Route path="/register" component={RegisterForm} />
- سپس در فایل src\components\navBar.jsx، لینک به آن‌را، در انتهای لیست اضافه می‌کنیم، تا در منوی راهبری ظاهر شود:
<NavLink className="nav-item nav-link" to="/register">
   Register
</NavLink>
- در ادامه کدهای کامل کامپوننت ثبت نام را ملاحظه می‌کنید:
import Joi from "@hapi/joi";
import React from "react";

import Form from "./common/form";

class RegisterForm extends Form {
  state = {
    data: { username: "", password: "", name: "" },
    errors: {}
  };

  schema = {
    username: Joi.string()
      .required()
      .email({ minDomainSegments: 2, tlds: { allow: ["com", "net"] } })
      .label("Username"),
    password: Joi.string()
      .required()
      .min(5)
      .label("Password"),
    name: Joi.string()
      .required()
      .label("Name")
  };

  doSubmit = () => {
    // Call the server
    console.log("Submitted");
  };

  render() {
    return (
      <div>
        <h1>Register</h1>
        <form onSubmit={this.handleSubmit}>
          {this.renderInput("username", "Username")}
          {this.renderInput("password", "Password", "password")}
          {this.renderInput("name", "Name")}
          {this.renderButton("Register")}
        </form>
      </div>
    );
  }
}

export default RegisterForm;
- ابتدا این کامپوننت را بجای ارث بری از Component خود React، از کامپوننت Form که در قسمت قبل ایجاد کردیم، ارث بری می‌کنیم تا به تمام امکانات آن مانند اعتبارسنجی، مدیریت حالت و متدهای کمکی تعریف فیلدها و دکمه‌ها بهره‌مند شویم.
- سپس state این کامپوننت را با شیءای حاوی دو خاصیت data و error، مقدار دهی اولیه می‌کنیم. خواص متناظر با المان‌های فرم را نیز به صورت یک شیء، به خاصیت data انتساب داده‌ایم.
- پس از آن، خاصیت schema تعریف شده‌است؛ تا قواعد اعتبارسنجی تک تک فیلدهای فرم را به کمک کتابخانه‌ی Joi، مطابق نیازمندی‌هایی که در ابتدای تعریف این تمرین مشخص کردیم، ایجاد کند.
- در ادامه، متد doSubmit را ملاحظه می‌کنید. این متد پس از کلیک بر روی دکمه‌ی Register و پس از اعتبارسنجی موفقیت آمیز فرم، به صورت خودکار فراخوانی می‌شود.
- در آخر، تعریف فرم ثبت‌نام را مشاهده می‌کنید که نکات آن‌را در قسمت قبل، با معرفی کامپوننت Form و افزودن متدهای کمکی رندر input و button به آن، بررسی کردیم و در کل با نکات بررسی شده‌ی در فرم لاگینی که تا به اینجا ایجاد کردیم، تفاوتی ندارد.


تمرین 2- ایجاد فرم ثبت و یا ویرایش یک فیلم


فرم جدید ثبت و ویرایش یک فیلم، نکات بیشتری را به همراه دارد. در اینجا می‌خواهیم در بالای لیست نمایش فیلم‌ها، یک دکمه‌ی new movie را اضافه کنیم تا با کلیک بر روی آن، به فرم ثبت و ویرایش فیلم‌ها هدایت شویم. این فرم، از فیلدهای یک عنوان متنی، انتخاب ژانر از یک drop down list، تعداد موجود (بین 1 و 100) و امتیاز (بین صفر تا 10) تشکیل شده‌است. همچنین تا زمانیکه اعتبارسنجی فرم تکمیل نشده‌است، دکمه‌ی submit فرم باید غیرفعال باقی بماند. پس از ذخیره شدن این فیلم (در لیست درون حافظه‌ای برنامه)، با مراجعه‌ی به لیست فیلم‌ها و انتخاب آن از لیست (با کلیک بر روی لینک آن)، باید مجددا به همین فرم، در حالت ویرایش این رکورد هدایت شویم. به علاوه اگر در بالای صفحه یک id اشتباه وارد شد، باید صفحه‌ی «پیدا نشد» نمایش داده شود.

کامپوننت MovieForm و مسیریابی آن‌را در قسمت 17، تعریف و اضافه کردیم. برای تعریف لینکی به آن، به کامپوننت movies مراجعه کرده و بالای متنی که تعداد کل آیتم‌های موجود در بانک اطلاعاتی را نمایش می‌دهد، المان زیر را اضافه می‌کنیم:
import { Link } from "react-router-dom";
// ...


<div className="col">
  <Link
    to="/movies/new"
    className="btn btn-primary"
    style={{ marginBottom: 20 }}
  >
    New Movie
  </Link>
  <p>Showing {totalCount} movies in the database.</p>
این Link را هم با کلاس btn مزین کرده‌ایم تا شبیه به یک دکمه، به نظر برسد. با کلیک بر روی آن، به آدرس movies/new هدایت خواهیم شد؛ یعنی id جدید این مسیریابی را به "new" تنظیم کرده‌ایم که در ادامه بر اساس آن، تفاوت بین حالت ویرایش و حالت ثبت اطلاعات، مشخص می‌شود.


سپس به کامپوننت src\components\movieForm.jsx که پیشتر آن‌را اضافه کرده بودیم، مراجعه کرده و به صورت زیر آن‌را تکمیل می‌کنیم:
import Joi from "@hapi/joi";
import React from "react";

import { getGenres } from "../services/fakeGenreService";
import { getMovie, saveMovie } from "../services/fakeMovieService";
import Form from "./common/form";

class MovieForm extends Form {
  state = {
    data: {
      title: "",
      genreId: "",
      numberInStock: "",
      dailyRentalRate: ""
    },
    genres: [],
    errors: {}
  };
- ابتدا importهای مورد نیاز به Joi، React و همچنین سرویس‌های لیست فیلم‌ها و لیست ژانرهای سینمایی، به همراه کامپوننت فرم، تعریف شده‌اند.
- سپس این کامپوننت نیز از کامپوننت Form ارث بری می‌کند تا به امکانات ویژه‌ی آن دسترسی پیدا کند.
- در ادامه در خاصیت state، طبق روالی که در کامپوننت فرم درنظر گرفته‌ایم، دو خاصیت data و errors باید حضور داشته باشند. در خاصیت data، شیءای که نام خاصیت‌های آن با فیلدهای فرم تطابق دارد، ذکر شده‌اند. در اینجا برای ذخیره سازی اطلاعات انتخاب شده‌ی از drop down list مرتبط با ژانرهای سینمایی، از خاصیت genreId استفاده می‌شود؛ این تنها اطلاعاتی است که از کل آیتم‌های یک drop down list نیاز داریم. آرایه‌ی genres که آیتم‌های این drop down list را مقدار دهی می‌کند، در روال componentDidMount، از سرویس مرتبطی دریافت و مقدار دهی خواهد شد.

در ادامه‌ی کدهای کامپوننت MovieForm، کدهای schema اعتبارسنجی شیء data را ملاحظه می‌کنید:
  schema = {
    _id: Joi.string(),
    title: Joi.string()
      .required()
      .label("Title"),
    genreId: Joi.string()
      .required()
      .label("Genre"),
    numberInStock: Joi.number()
      .required()
      .min(0)
      .max(100)
      .label("Number in Stock"),
    dailyRentalRate: Joi.number()
      .required()
      .min(0)
      .max(10)
      .label("Daily Rental Rate")
  };
در اینجا، id به required تنظیم نشده‌است؛ چون زمانیکه قرار است یک شیء movie جدید را  ایجاد کنیم، هنوز این id نامشخص است. سایر موارد خاصیت schema، به لطف fluent api کتابخانه‌ی Joi، بسیار خوانا بوده و نیاز به توضیحات خاصی ندارند. برای مثال هر دو خاصیت numberInStock و  dailyRentalRate باید عددی وارد شده و بین بازه‌ی مشخصی قرار گیرند.

اکنون به مرحله‌ی componentDidMount می‌رسیم:
  componentDidMount() {
    const genres = getGenres();
    this.setState({ genres });

    const movieId = this.props.match.params.id;
    if (movieId === "new") return;

    const movie = getMovie(movieId);
    if (!movie) return this.props.history.replace("/not-found");

    this.setState({ data: this.mapToViewModel(movie) });
  }
- در اینجا لیست ژانرهای سینمایی از متد getGenres فایل src\services\fakeGenreService.js دریافت شده و پس از آن کار به روز رسانی خاصیت genres در state را انجام می‌دهیم. این به روز رسانی state، سبب می‌شود تا این خاصیت که آرایه‌ای است، در رندر بعدی این کامپوننت، به لیست options مربوط به drop down list درج شده‌ی در فرم، ارسال شده و در فرم رندر شود.
- پس از آن، نحوه‌ی دریافت پارامتر id مسیریابی رسیده را ملاحظه می‌کنید. این id اگر به "new" تنظیم شده بود، یعنی قرار است، اطلاعات جدیدی ثبت شوند. بنابراین متد جاری را خاتمه می‌دهیم (چون کار ادامه‌ی این متد، مقدار دهی اولیه‌ی تمام فیلدهای فرم، بر اساس اطلاعات شیء دریافت شد‌ه‌ی از سرویس فیلم‌ها است). در غیراینصورت (و با مشخص بودن id)، با استفاده از این id و متد getMovie سرویس src\services\fakeMovieService.js، سعی خواهیم کرد تا اطلاعات شیء movie متناظری را دریافت کنیم. اگر خروجی این متد null بود، یعنی id وارد شده معتبر نیست. به همین جهت کاربر را به صفحه‌ی not-found هدایت می‌کنیم. اگر دقت کنید در اینجا بجای متد push، از متد replace استفاده کرده‌ایم. چون اگر از متد push استفاده می‌کردیم و کاربر بر روی دکمه‌ی back مرورگر کلیک می‌کرد، دوباره به همین صفحه، با id غیرمعتبر قبلی وارد می‌شد و یک حلقه‌ی بی‌پایان رخ می‌داد. همچنین به return ای هم که به همراه متد replace استفاده شده، دقت کنید. کار redirect به یک صفحه‌ی دیگر، به معنای عدم اجرای کدهای پس از آن نیست. بنابراین اگر می‌خواهیم کار این متد با redirect، به پایان برسد، ذکر return الزامی است.
- در پایان این متد، خاصیت data موجود در state را به روز رسانی می‌کنیم؛ تا سبب رندر فرم، با اطلاعات شیء movie یافت شده گردد و چون ساختار شیء movie دریافت شده‌ی از سرویس، با ساختار data تعریف شده‌ی در state یکی نیست، نیاز به نگاشت این دو به هم، توسط متد سفارشی mapToViewModel زیر است:
  mapToViewModel(movie) {
    return {
      _id: movie._id,
      title: movie.title,
      genreId: movie.genre._id,
      numberInStock: movie.numberInStock,
      dailyRentalRate: movie.dailyRentalRate
    };
  }
این سناریو بسیار متداول است و اکثر داده‌های دریافت شده‌ی از سرور، الزاما با ساختار داده‌هایی که در فرم‌های خود تعریف می‌کنیم (که در اینجا view-model نام گرفته)، یکی نیستند و نیاز به نگاشت بین آن‌ها وجود دارد. برای مثال genreId موجود در view-model این فرم (همان شیء منتسب به data در state)، دقیقا به همین نام، در شیء movie تعریف نشده‌است و نیاز به نگاشت این دو به هم است.

در ادامه‌ی کدهای کامپوننت فرم فیلم‌ها، به متد doSubmit می‌رسیم:
  doSubmit = () => {
    saveMovie(this.state.data);

    this.props.history.push("/movies");
  };
این متد پس از کلیک کاربر بر روی دکمه‌ی submit و اعتبارسنجی کامل فرم، فراخوانی می‌شود. در این مرحله می‌توان اطلاعات موجود در شیء data را به متد saveMovie سرویس src\services\fakeMovieService.js ارسال کرد، تا آن‌را به لیست خودش اضافه کند. سپس کاربر را به لیست به روز شده‌ی فیلم‌ها هدایت می‌کنیم.

در انتهای این کامپوننت نیز به متد رندر آن می‌رسیم:
  render() {
    return (
      <div>
        <h1>Movie Form</h1>
        <form onSubmit={this.handleSubmit}>
          {this.renderInput("title", "Title")}
          {this.renderSelect("genreId", "Genre", this.state.genres)}
          {this.renderInput("numberInStock", "Number in Stock", "number")}
          {this.renderInput("dailyRentalRate", "Rate")}
          {this.renderButton("Save")}
        </form>
      </div>
    );
  }
تمام قسمت‌های این فرم را منهای متد جدید renderSelect آن، پیشتر در قسمت قبل، مرور کرده‌ایم و نکته‌ی جدیدی ندارند.
برای تعریف متد جدید renderSelect به این صورت عمل می‌کنیم:
- ابتدا فایل جدید src\components\common\select.jsx را ایجاد کرده و سپس آن‌را جهت نمایش یک drop down list، ویرایش می‌کنیم:
import React from "react";

const Select = ({ name, label, options, error, ...rest }) => {
  return (
    <div className="form-group">
      <label htmlFor={name}>{label}</label>
      <select name={name} id={name} {...rest} className="form-control">
        <option value="" />
        {options.map(option => (
          <option key={option._id} value={option._id}>
            {option.name}
          </option>
        ))}
      </select>
      {error && <div className="alert alert-danger">{error}</div>}
    </div>
  );
};

export default Select;
شبیه به یک چنین کامپوننتی را در قسمت قبل، در فایل src\components\common\input.jsx ایجاد کردیم و ساختار کلی آن‌ها با هم یکی است. ابتدا تمام تگ‌ها و کلاس‌های بوت استرپی مورد نیاز، در این کامپوننت محصور می‌شوند. سپس آرایه‌ای بر روی لیست options رسیده، ایجاد شده و به صورت پویا، لیست نمایش داده شده‌ی توسط drop down آن‌را تشکیل می‌دهد. در پایان آن هم کار نمایش اخطار اعتبارسنجی متناظری، در صورت وجود خطایی، قرار گرفته‌است.

- پس از آن به کامپوننت src\components\common\form.jsx مراجعه کرده و متد رندر آن‌را اضافه می‌کنیم:
import Select from "./select";
// ...

class Form extends Component {

  // ...

  renderSelect(name, label, options) {
    const { data, errors } = this.state;

    return (
      <Select
        name={name}
        value={data[name]}
        label={label}
        options={options}
        onChange={this.handleChange}
        error={errors[name]}
      />
    );
  }
}
کار این متد، مقدار دهی ویژگی‌های مورد نیاز کامپوننت Select، بر اساس نام فیلد، یک برچسب و آیتم‌های ارسالی به آن است. مزیت وجود یک چنین متد کمکی، کم شدن کدهای تکراری Selectهای مورد نیاز و همچنین عدم فراموشی قسمتی از این اتصالات و در نهایت یک‌دست شدن کدهای کل برنامه‌است. این متد در نهایت سبب رندر یک drop down list، بر اساس اطلاعات خاصیت genres موجود در state می‌شود:



تمرین 3- جستجوی در لیست فیلم‌ها


می‌خواهیم در بالای لیست نمایش فیلم‌ها، یک search box را قرار دهیم تا توسط آن بتوان بر اساس عنوان وارد شده، در فیلم‌های موجود جستجو کرد. همچنین این جستجو قرار است کلی بوده و حتی در صورت انتخاب ژانر خاصی از منوی کنار صفحه، باید در کل اطلاعات موجود جستجو کند. به علاوه اگر کاربر ژانری را انتخاب کرد، این text box باید خالی شود.

برای اینکار ابتدا فایل جدید src\components\searchBox.jsx را ایجاد کرده و به صورت زیر آن‌را تکمیل می‌کنیم:
import React from "react";

const SearchBox = ({ value, onChange }) => {
  return (
    <input
      type="text"
      name="query"
      className="form-control my-3"
      placeholder="Search..."
      value={value}
      onChange={e => onChange(e.currentTarget.value)}
    />
  );
};

export default SearchBox;
این SeachBox، یک controlled component است و دارای state خاص خودش نیست. تمام اطلاعات مورد نیاز خود را از طریق props دریافت کرده و خروجی خود را (اطلاعات تایپ شده‌ی در input box را) از طریق صدور رخ‌دادها، اطلاع رسانی می‌کند.

سپس به کامپوننت movies مراجعه کرده و آن‌را ذیل متن نمایش تعداد رکوردها، درج می‌کنیم:
<p>Showing {totalCount} movies in the database.</p>
<SearchBox value={searchQuery} onChange={this.handleSearch} />
که البته نیاز به import کامپوننت مربوطه، تعریف واژه‌ی جستجو شده در state و مدیریت رخ‌داد onChange را نیز دارد:
import SearchBox from "./searchBox";
//...

class Movies extends Component {
  state = {
    //...
    selectedGenre: {},
    searchQuery: ""
  };


  handleSearch = query => {
    this.setState({ searchQuery: query, selectedGenre: null, currentPage: 1 });
  };

  handleGenreSelect = genre => {
    console.log("handleGenreSelect", genre);
    this.setState({ selectedGenre: genre, searchQuery: "", currentPage: 1 });
  };
در متد handleSearch، اطلاعات وارد شده‌ی توسط کاربر دریافت شده و توسط آن سه خاصیت state به روز رسانی می‌شوند تا توسط آن‌ها در حین رندر مجدد کامپوننت، کار فیلتر صحیح اطلاعات صورت گیرد. همچنین selectedGenre نیز به حالت اول بازگشت داده می‌شود. به علاوه اگر کاربر در حین مشاهده‌ی صفحه‌ی 3 بود، نیاز است currentPage صحیحی را به او نمایش  داد.
متد handleGenreSelect را نیز اندکی تغییر داده‌ایم تا اگر گروهی انتخاب شد، مقدار searchQuery را خالی کند. اگر در اینجا searchQuery را به نال تنظیم می‌کردیم، controlled component جعبه‌ی جستجو، تبدیل به کامپوننت کنترل نشده‌ای می‌شد و در این حالت، React، اخطار تبدیل بین این دو را صادر می‌کرد.

در آخر، ابتدای متد getPageData هم جهت اعمال searchQuery، به صورت زیر تغییر می‌کند:
  getPagedData() {
    const {
      pageSize,
      currentPage,
      selectedGenre,
      movies: allMovies,
      sortColumn,
      searchQuery
    } = this.state;

    let filteredMovies = allMovies;
    if (searchQuery) {
      filteredMovies = allMovies.filter(m =>
        m.title.toLowerCase().startsWith(searchQuery.toLowerCase())
      );
    } else if (selectedGenre && selectedGenre._id) {
      filteredMovies = allMovies.filter(m => m.genre._id === selectedGenre._id);
    }
در اینجا اگر searchQuery مقداری داشته باشد، یک جستجوی غیرحساس به کوچکی و بزرگی حروف، بر روی خاصیت title اشیاء فیلم، انجام می‌شود.



کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید: sample-21.zip
مطالب
استفاده از jQuery Ajax جهت تعیین اعتبار یک فرم

فرض کنید تعیین اعتبار یکی از فیلدهای فرم نیاز به انجام محاسباتی در سمت سرور دارد و این‌کار را می‌خواهیم با استفاده از jQuery Ajax‌ انجام دهیم. مشکلی که در اینجا وجود دارد، این است که A در Ajax به معنای asynchronous است. یعنی زمانیکه کاربر دکمه submit را فشرد، دیگر برنامه منتظر این نخواهد شد که پاسخ کامل دریافت شود ، سایر پردازش‌ها صورت گیرد و سپس فرم را به سرور ارسال نماید (شبیه به ایجاد یک ترد جدید در برنامه‌های ویندوزی). مثال زیر را در نظر بگیرید:

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="TestCustomValidation.aspx.cs"
Inherits="TestJQueryAjax.TestCustomValodation" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title></title>

<script src="js/jquery.js" type="text/javascript"></script>

<script type="text/javascript">
function validate() {
var number1 = $("#<%=txtNumber1.ClientID %>").val();
var number2 = $("#<%=txtNumber2.ClientID %>").val();
var result = false;
$.ajax({
type: "POST",
url: 'AjaxSrv.asmx/ValidateIt',
data: '{"number1":' + number1 + ',"number2":' + number2 + '}',
contentType: "application/json; charset=utf-8",
dataType: "json",
success:
function(msg) {
if (msg.d) {
result = true;
alert('بسیار خوب');
}
else {
result = false;
alert('دوباره سعی کنید');
}
},
error:
function(XMLHttpRequest, textStatus, errorThrown) {
result = false;
alert("خطایی رخ داده است");
}
});
//debugger;
return result;
}
</script>

</head>
<body>
<form id="form1" runat="server">
<div>
number 1 :
<asp:TextBox runat="server" ID="txtNumber1" />
<br />
number 2 :
<asp:TextBox runat="server" ID="txtNumber2" />
<br />
<asp:Button ID="btnSubmit" Text="Submit" UseSubmitBehavior="false" runat="server"
OnClientClick="if(!validate()){ return false;}" OnClick="btnSubmitClick" />
</div>
</form>
</body>
</html>

این مثال یک نوع اعتبار سنجی سفارشی را در حین submit با استفاده از وب سرویس زیر انجام می‌دهد (حاصلضرب دو عدد دریافتی را بررسی می‌کند که باید مساوی 10 باشند. البته هدف از این مثال ساده، آشنایی با نحوه‌ی انجام این نوع عملیات است که می‌تواند شامل کار با دیتابیس و غیره هم باشد. و گرنه بدیهی است این بررسی را با دو سطر کد جاوا اسکریپتی نیز می‌شد انجام داد):

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Services;
using System.Web.Script.Services;

namespace TestJQueryAjax
{
/// <summary>
/// Summary description for AjaxSrv
/// </summary>
[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
[System.ComponentModel.ToolboxItem(false)]
[ScriptService]
public class AjaxSrv : System.Web.Services.WebService
{
[WebMethod(EnableSession = true)]
[ScriptMethod(ResponseFormat = ResponseFormat.Json)]
public bool ValidateIt(int number1, int number2)
{
return number1 * number2 == 10;
}
}
}
در این مثال هنگام submit ، عملیات اعتبار سنجی با توجه به وضعیت asynchronous عملیات Ajax ، تمام و کمال رخ نداده ، یا اعتبار سنجی انجام نمی‌شود و یا پیغام خطایی را دریافت خواهیم کرد.

راه حل چیست؟
راه حل‌های فضایی بسیاری را در وب در این مورد می‌توان پیدا کرد؛ اما راه حل استاندارد آن در این حالت ویژه، استفاده از Ajax در حالت غیرهمزمان است. یعنی این فریم ورک Ajax را وادار کنیم که تا پایان عملیات مورد نظر، منتظر بماند و سپس فرم را ارسال کند. برای این منظور تنها کافی است یک سطر زیر را پیش از فراخوانی تابع Ajax ، اضافه و فراخوانی نمائیم:

$.ajaxSetup({async: false}) ;
نکته:
UseSubmitBehavior دکمه ما را به شکل زیر رندر می‌کند (دکمه به یک button معمولی (بجای حالت submit) تبدیل شده و سپس یک doPostBack را اضافه خواهد کرد):

<input id="btnSubmit" type="button" onclick="if(!validate()){ return false;};__doPostBack('btnSubmit','')" value="Submit" name="btnSubmit"/>


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


ایجاد و حذف بانک‌های اطلاعاتی جدید

برای این منظور به آدرس http://localhost:8080 مراجعه و از طریق کنسول مدیریتی تحت وب RavenDB بر روی دکمه New database کلیک کنید.


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


پس از ایجاد دیتابیس، برای حذف آن، بر روی نام دیتابیس کلیک راست کرده و گزینه Delete را انتخاب کنید


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


و یا سرور RavenDB را خاموش یا stop کنید. سپس به پوشه Database کنار فایل Raven.Server.exe مراجعه کرده، بانک اطلاعاتی خود را یافته و سپس کل پوشه آن‌را Delete کنید.


سؤال: چگونه با دیتابیس‌های ایجاد شده کار کنیم؟

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


برای نمونه در اینجا به System database این مجموعه وارد شده‌ایم که تعریف جزئیات بانک اطلاعاتی جدید ایجاد شده را در خود دارد.

جهت استفاده از بانک اطلاعاتی جدید ایجاد شده در کدهای خود، فقط کافی است خاصیت DefaultDatabase یک DocumentStore مقدار دهی شود:
 using (var store = new DocumentStore
{
    Url = "http://localhost:8080",
    DefaultDatabase = "Test2"
}.Initialize())
{
  //...
}


تهیه پشتیبان از بانک‌های اطلاعاتی و بازیابی آن‌ها

ابتدا نیاز است تمام بسته‌های مورد نیاز را یکجا از آدرس http://ravendb.net/download تهیه کنید. سپس به پوشه backup آن مراجعه کرده و از فایل اجرایی Raven.Backup.exe آن می‌توان جهت تهیه پشتیبان از بانک اطلاعاتی خاصی استفاده نمود. لازم به ذکر است که این برنامه باید با سطح دسترسی ادمین اجرا شود.
 Raven.Backup.exe --url==http://localhost:8080 --dest=d:\backup
برنامه backup، آدرس سرور را گرفته و سپس فایل‌های پشتیبان تهیه شده را در آدرس مشخصی ذخیره می‌کند. برای مدیریت اجرای روزانه آن نیز از برنامه استاندارد windows task schedule manager استفاده نمائید. به علاوه امکانات Shadow copy ویندوز نیز در اینجا مفید خواهند بود.

برای بازیابی و Restore یک بانک اطلاعاتی ابتدا دستور Raven.Server.exe /help را صادر کنید تا کلیه سوئیچ‌های این برنامه مشخص شوند. یکی از آن‌ها Restore نام دارد که پارامترهای dest و src را دریافت می‌کند (کجا بازیابی شود و از کجا اطلاعات را بخواند).

همچنین بجای backup و restore، امکان export و import نیز وجود دارند و برای انجام آن از برنامه Raven.Smuggler.exe که کنار Raven.Server.exe قرار دارد، می‌توان استفاده کرد.
برای تهیه خروجی (که در حقیقت تهیه یک dump فشرده شده از اسناد JSON موجود است):
 Raven.Smuggler.exe out http://localhost:8080/ dump.raven
و برای بازیابی خروجی تولید شده:
 Raven.Smuggler.exe in http://localhost:8080/ dump.raven
اشتراک‌ها
معرفی ابزارهای مفید و رایگان fishcode
نرم افزارهای ارائه شده این تیم به صورت رایگان بوده و عموما بدون نیاز به نصب و با حجم بسیار کم میباشد که ابزارهای بسیار کارآمدی در اختیار برنامه نویسان قرار داده است.   
توضیحات تکمیلی در مورد نرم افزار‌ها در سایت این تیم قرار دارد ولی جهت آشنایی دوستان به صورت مختصر در مورد چند نرم افزار مفید این تیم توضحاتی خواهم داد. 

  • Convert.Net
یکی از مفیدترین و بهترین ابزارهای این تیم نرم افزار Convert.Net است که امکانات مفیدی در اختیار شما قرار میدهد از جمله :
  1. Regular Expression Tester : که جهت تست Regex‌های نوشته شده استفاده میشود.
  2. Encoding & Decoding : جهت تبدیل انواع رشته‌های Encoded ویا Decoded به یکدیگر استفاده میشود و از html - url - EScape-js - Base64 - string و ... پشتیبانی میکند.
  3. Encryption & Decryption : جهت Encrypt و Decrypt انواع رشته استفاده میشود که از انکریپتورهای معروفی همچون  AES - Rijndael - DES - SHA - TripleDES پشتیبانی میکند.
  4. Language Translation : یک دیکشنری Multi Language آنلاین در اختیار شما برای ترجمه متون قرار میدهد.
  5. C# & VB.Net Convertor : برای تبدیل کدهای C# به Vb و برعکس استفاده میشود و  طبق تست هایی که روش به شخصه انجام دادم در اکثر موارد بدون خطا تا حدود 90 درصد تبدیلات رو به صورت موفق انجام میده ولی مانند سایر Convertor‌ها با Lambda Expression کمی مشکل دارد.
  6.  Xml & Json Browser : برای مشاهده و تبدیل Xml به Json و برعکس بسیار مفید است .. 
  7. Linq Tester : برای تست کوئری‌های Linq استفاده میشود . (برای استفاده از این امکان باید Roslyne روی سیستم شما نصب باشد)
حجم برنامه 2 مگابایت : دانلود

  • Database.Net
نرم افزار کم حجم جهت اتصال و مدیریت انواع دیتابیس میباشد در عین سادگی و حجم کم ابزار مفیدی جهت اجرای Query‌ها ساخت جداول , مشاهده و ویرایش رکوردها و .... در اختیارتان قرار میدهد.
دیتابیس‌های پشتیابنی شده در این نرم افزار : 
SQL Server 2000/2005/2008/2012/2014/Express/LocalDB         
SQL Server Compact 3.1/3.5/4.0 (*.sdf;*.*)         
SQL Azure 10/11        
MS Access 97/2000/2002/2003 (*.mdb;*.mde;*.*), 2007/2010/2013 (*.accdb;*.accde;*.*)         
MS Excel 97/2000/2002/2003(*.xls;*.*), 2007/2010/2013 (*.xlsx;*.xlsm;*.xlsb;*.*)         
Firebird SuperServer/Embedded 1.5/2.0/2.1/2.5 (*.gdb;*.fdb;*.*)       
SQLite 3.x (*.db;*.db3;*.sqlite;*.*)       
MySQL 5.x, MariaDB 5.x/10.x         
PostgreSQL 8.x/9.x     
Oracle 10g/11g/12c       
IBM DB2 9.x/10.x         
IBM Informix 11.x/12.x         
Sybase ASE 15.x         
dBASE IV (*.dbf)   
Visual FoxPro (*.dbc)    
Data Sources (OleDB)(*.udl;*.*)     
ODBC DSN (Data Source Name)(*.dsn;*.*)       
OData (Open Data Protocol) v1/v2/v3/v4
حجم برنامه 9 مگابایت : دانلود

  • Resource.Net
این نرم افزار جهت ساخت , ویرایش و مدیریت انواع فایل Resource برنامه‌های دات نت بسیار مفید میباشد که از نسخه‌های مختلف دات نت پشتیبانی میکند.

سایر نرم افزار‌های این تیم هم مانند نرم افزارهای معرفی شده بین کاربران محبوبیت زیادی کسب کرده اند که میتوانید برای کسب اطلاعات بیشتر و دانلود این نرم افزار‌ها به وب سایت این تیم مراجعه فرمائید. 
معرفی ابزارهای مفید و رایگان fishcode
مطالب
پیاده سازی Unobtrusive Ajax در ASP.NET Core 1.0
پیاده سازی Unobtrusive Ajax را در ASP.NET MVC 5.x، می‌توانید در مطلب «ASP.NET MVC #21» مطالعه کنید. HTML Helpers مرتبط با Ajax، به طور کامل از ASP.NET Core 1.0 حذف شده‌اند. اما این مورد به این معنا نیست که نمی‌توان Unobtrusive Ajax را در ASP.NET Core که تمرکزش بیشتر بر روی Tag Helpers جدید هست تا HTML Helpers قدیمی، پیاده سازی کرد.


Unobtrusive Ajax چیست؟

در حالت معمولی، با استفاده از متد ajax جی‌کوئری، کار ارسال غیرهمزمان اطلاعات، به سمت سرور صورت می‌گیرد. چون در این روش کدهای جی‌کوئری داخل صفحات برنامه‌های ما قرار می‌گیرند، به این روش، «روش چسبنده» می‌گویند. اما با استفاده از افزونه‌ی «jquery.unobtrusive-ajax.min.js» مایکروسافت، می‌توان این کدهای چسبنده را تبدیل به کدهای غیرچسنبده یا Unobtrusive کرد. در این حالت، پارامترهای متد ajax، به صورت ویژگی‌ها (attributes) به شکل data-ajax به المان‌های مختلف صفحه اضافه می‌شوند و به این ترتیب، افزونه‌ی یاد شده به صورت خودکار با یافتن مقادیر ویژگی‌های data-ajax، این المان‌ها را تبدیل به المان‌های ای‌جکسی می‌کند. در این حالت به کدهایی تمیزتر و عاری از متدهای چسبنده‌ی ajax قرار گرفته‌ی در داخل صفحات وب خواهیم رسید.
روش طراحی Unobtrusive را در کتابخانه‌های معروفی مانند بوت استرپ هم می‌توان مشاهده کرد.


پیشنیازهای فعال سازی Unobtrusive Ajax در ASP.NET Core 1.0

توزیع افزونه‌ی «jquery.unobtrusive-ajax.min.js» مایکروسافت، از طریق bower صورت می‌گیرد که پیشتر در مطلب «ارتقاء به ASP.NET Core 1.0 - قسمت 14 - فعال سازی اعتبارسنجی ورودی‌های کاربران» با آن آشنا شدیم. در اینجا نیز برای دریافت آن، تنها کافی است فایل bower.json را به نحو ذیل تکمیل کرد:
{
  "name": "asp.net",
  "private": true,
  "dependencies": {
   "bootstrap": "3.3.6",
   "jquery": "2.2.0",
   "jquery-validation": "1.14.0",
   "jquery-validation-unobtrusive": "3.2.6",
   "jquery-ajax-unobtrusive": "3.2.4"
  }
}
و پس از آن فایل bundleconfig.json مطلب «ارتقاء به ASP.NET Core 1.0 - قسمت 21 - بررسی تغییرات Bundling و Minification» یک چنین شکلی را پیدا می‌کند:
[
  {
   "outputFileName": "wwwroot/css/site.min.css",
   "inputFiles": [
    "bower_components/bootstrap/dist/css/bootstrap.min.css",
    "content/site.css"
   ]
  },
  {
   "outputFileName": "wwwroot/js/site.min.js",
   "inputFiles": [
    "bower_components/jquery/dist/jquery.min.js",
    "bower_components/jquery-validation/dist/jquery.validate.min.js",
    "bower_components/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js",
    "bower_components/jquery-ajax-unobtrusive/jquery.unobtrusive-ajax.min.js",
    "bower_components/bootstrap/dist/js/bootstrap.min.js"
   ],
   "minify": {
    "enabled": true,
    "renameLocals": true
   },
   "sourceMap": false
  }
]
در اینجا فایل‌های css و اسکریپت مورد نیاز برنامه، به ترتیب اضافه شده و یکی خواهند شد. خروجی نهایی آن‌ها به شکل زیر در صفحات وب مورد استفاده قرار می‌گیرند:
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>@ViewBag.Title - My ASP.NET Application</title>
    <link href="~/css/site.min.css" rel="stylesheet" />
</head>
<body>
    <div>
        <div>
                @RenderBody()
        </div>
    </div>

    <script src="~/js/site.min.js" type="text/javascript" asp-append-version="true"></script>
    @RenderSection("Scripts", required: false)
</body>
</html>
در اینجا تنها دو فایل نهایی این عملیات، یعنی css/site.min.css و js/site.min.js به صفحه الحاق شده‌اند که حاوی تمام پیشنیازهای اسکریپتی و شیوه‌نامه‌های برنامه هستند و در این حالت دیگر نیاز به افزودن آن‌ها به دیگر صفحات سایت نیست.


استفاده از معادل‌های واقعی Unobtrusive Ajax در ASP.NET Core 1.0

واقعیت این است که HTML Helper ای‌جکسی حذف شده‌ی از ASP.NET Core 1.0، کاری بجز افزودن ویژگی‌های data-ajax را که توسط افزونه‌ی jquery.unobtrusive-ajax.min.js پردازش می‌شوند، انجام نمی‌دهد و این افزونه مستقل است از مباحث سمت سرور و به نگارش خاصی از ASP.NET گره نخورده است. بنابراین در اینجا تنها کاری را که باید انجام داد، استفاده از همان ویژگی‌های اصلی است که این افزونه قادر به شناسایی آن‌ها است.
خلاصه‌ی آن‌ها را جهت انتقال کدهای قدیمی و یا تهیه‌ی کدهای جدید، در جدول ذیل می‌توانید مشاهده کنید:

 HTML attribute   AjaxOptions 
 data-ajax-confirm   Confirm 
 data-ajax-method   HttpMethod 
 data-ajax-mode   InsertionMode 
 data-ajax-loading-duration   LoadingElementDuration 
 data-ajax-loading   LoadingElementId 
 data-ajax-begin   OnBegin 
 data-ajax-complete   OnComplete 
 data-ajax-failure   OnFailure 
 data-ajax-success   OnSuccess 
 data-ajax-update   UpdateTargetId 
 data-ajax-url   Url 
   
در ASP.NET Core 1.0، به علت حذف متدهای کمکی Ajax دیگر خبری از AjaxOptions نیست. اما اگر علاقمند به انتقال کدهای قدیمی به ASP.NET Core 1.0 هستید، معادل‌های اصلی این پارامترها را می‌توانید در ستون HTML attribute مشاهده کنید.

چند نکته:
- اگر قصد استفاده‌ی از این ویژگی‌ها را دارید، باید ویژگی "data-ajax="true را نیز حتما قید کنید تا سیستم Unobtrusive Ajax فعال شود.
- ویژگی data-ajax-mode تنها با ذکر data-ajax-update (و یا همان UpdateTargetId پیشین) معنا پیدا می‌کند.
- ویژگی data-ajax-loading-duration نیاز به ذکر data-ajax-loading (و یا همان LoadingElementId پیشین) را دارد.
- ویژگی data-ajax-mode مقادیر before، after و replace-with را می‌پذیرد. اگر قید نشود، کل المان با data دریافتی جایگزین می‌شود.
- سه callback قابل تعریف data-ajax-complete، data-ajax-failure و data-ajax-success، یک چنین پارامترهایی را از سمت سرور در اختیار کلاینت قرار می‌دهند:

parameters  
 Callback  
 xhr, status   data-ajax-complete 
 data, status, xhr   data-ajax-success 
 xhr, status, error   data-ajax-failure 

برای مثال می‌توان ویژگی data-ajax-success را به نحو ذیل در سمت کلاینت مقدار دهی کرد:
 data-ajax-success = "myJsMethod"
این متد جاوا اسکریپتی یک چنین امضایی را دارد:
  function myJsMethod(data, status, xhr) {
}
در این حالت در سمت سرور، پارامتر data در یک اکشن متد، به صورت ذیل مقدار دهی می‌شود:
 return Json(new { param1 = 1, param2 = 2, ... });
و در سمت کلاینت در متد myJsMethod این پارامترها را به صورت data.param1 می‌توان دریافت کرد.


مثال‌هایی از افزودن ویژگی‌های data-ajax به المان‌های مختلف

 در حالت استفاده از Form Tag Helpers که در مطلب «ارتقاء به ASP.NET Core 1.0 - قسمت 12 - معرفی Tag Helpers» بررسی شدند، یک فرم ای‌جکسی، چنین تعاریفی را پیدا خواهد کرد:
با این ViewModel فرضی
using System.ComponentModel.DataAnnotations;
 
namespace Core1RtmEmptyTest.ViewModels.Account
{
    public class RegisterViewModel
    {
        [Required]
        [EmailAddress]
        [Display(Name = "Email")]
        public string Email { get; set; }
    }
}
که در View متناظر Ajax ایی ذیل استفاده شده‌است:
@using Core1RtmEmptyTest.ViewModels.Account
@model RegisterViewModel
@{
}
 
<form method="post"
      asp-controller="TestAjax"
      asp-action="Index"
      asp-route-returnurl="@ViewBag.ReturnUrl"
      class="form-horizontal"
      role="form"
      data-ajax="true"
      data-ajax-loading="#Progress"
      data-ajax-success="myJsMethod">
 
    <input asp-for="Email" class="form-control" />
    <span asp-validation-for="Email" class="text-danger"></span>
 
    <button type="submit">ارسال</button>
 
    <div id="Progress" style="display: none">
        <img src="images/loading.gif" alt="loading..." />
    </div>
</form>
 
@section scripts{
    <script type="text/javascript">
        function myJsMethod(data, status, xhr) {
            alert(data.param1);
        }
    </script>
}
در اینجا تمام تعاریف مانند قبل است؛ تنها سه ویژگی data-ajax جهت فعال سازی jquery-ajax-unobtrusive به فرم اضافه شده‌اند. همچنین یک callback دریافت پیام موفقیت آمیز بودن عملیات Ajax ایی نیز تعریف شده‌است.

این View از کنترلر ذیل استفاده می‌کند:
using Core1RtmEmptyTest.ViewModels.Account;
using Microsoft.AspNetCore.Mvc;
 
namespace Core1RtmEmptyTest.Controllers
{
    public class TestAjaxController : Controller
    {
 
        public IActionResult Index()
        {
            return View();
        }
 
        [HttpPost]
        public IActionResult Index([FromForm]RegisterViewModel vm)
        {
            var ajax = isAjax();
            if (ajax)
            {
                // it's an ajax post
            }
 
 
            if (ModelState.IsValid)
            {
                //todo: save data
 
                return Json(new { param1 = 1, param2 = 2 });
            }
            return View();
        }
 
        private bool isAjax()
        {
            return Request?.Headers != null && Request.Headers["X-Requested-With"] == "XMLHttpRequest";
        }
    }
}
به ASP.NET Core 1.0، متد کمکی IsAjax اضافه نشده‌است؛ اما تعریف آن‌را در این کنترلر مشاهده می‌کنید. در مورد قید FromForm در ادامه توضیح داده خواهد شد (هرچند در این مورد خاص، حالت پیش فرض است و الزامی به قید آن نیست).

و یا Action Link ای‌جکسی نیز به صورت خلاصه به این نحو قابل تعریف است:
<div id="EmployeeInfo">
<a 
 asp-controller="MyController" asp-action="MyAction"
 data-ajax="true" 
 data-ajax-loading="#Progress" 
 data-ajax-method="POST" 
 data-ajax-mode="replace" 
 data-ajax-update="#EmployeeInfo">
 Get Employee-1 info
</a>

  <div id="Progress" style="display: none">
    <img src="images/loading.gif" alt="loading..."  />
  </div>
</div>


نکته‌ای در مورد اکشن متدهای ای‌جکسی در ASP.NET Core 1.0

همانطور که در مطلب «ارتقاء به ASP.NET Core 1.0 - قسمت 18 - کار با ASP.NET Web API»، قسمت «تغییرات Model binding پیش فرض، برای پشتیبانی از ASP.NET MVC و ASP.NET Web API» نیز ذکر شد:
public IActionResult Index([FromBody] MyViewModel vm)
{
   return View();
}
ذکر ویژگی FromBody در اینجا الزامی است. از این جهت که اطلاعات با فرمت JSON، از قسمت body درخواست استخراج و به MyViewModel بایند خواهند شد (در حالت dataType: json). و اگر dataType : application/x-www-form-urlencoded; charset=utf-8 بود (مانند حالت پیش فرض Unobtrusive Ajax)، باید از ویژگی FromForm استفاده شود. در غیر اینصورت در سمت سرور نال دریافت خواهیم کرد.
نظرسنجی‌ها
کدام روش مدیریت پروژه مناسب‌‌تر است؟
هر کسی بتواند یک ماژول را ۰ تا ۱۰۰ انجام بدهد و نهایتا ماژول انجام شده خود را به پروژه وصل کند.(مثلا دیتابیس را طراحی کند سرویس ها را اماده کند و رابط کاربری را پیاده کند و هر سه قسمت را به هم وصل کند و نهایتا متخصصان هر حوزه بازبینی خود را در هر قسمت انجام دهند و اصلاحات لازم در صورت نیاز انجام شوند).
هر کسی در حوزه کاری خود تمرکز کند و کاملا تخصصی در حوزه کاری خود انجام وظیفه نماید. (مثلا متخصص Back-End در حوزه خود، متخصص Front-End در حوزه خود فعالیت کنند و کارها تفکیک شده باشند).
مطالب
پیاده‌سازی میکروسرویس‌ها توسط Seneca
همانطور که در مطلب آشنایی با معماری Microservices گفته شد، Seneca یک فریم‌ورک مبتنی بر Node.js برای ساخت برنامه‌های سمت سرور بر مبنای معماری Microservices با هسته Monolithic است. در این مطلب قصد ارائه یک مثال عملی را بر اساس این فریم‌ورک ندارم. هدف، آشنایی با اجزای اصلی Seneca و چهارچوب کاری آن است.


فواید استفاده از فریم‌ورک Seneca  

  • فریم‌ورک Seneca کدنویسی برای ایجاد درخواست‌ها، ارسال پاسخ به درخواست‌های رسیده و تبدیل داده‌ها را که از روتین‌های هر سرویسی میتوانند باشند، ساده‌تر میکند.
  • فریم‌ورک Seneca با معرفی ایده Actionها و Pluginها که جلوتر توضیح داده خواهند شد، روند تبدیل کامپوننت‌های یک برنامه Monolithic را به نوع سرویسی، تسهیل میکند. روندی که به صورت عادی میتواند کاری طاقت فرسا باشد و نیاز به Refactoring زیادی دارد. 
  • سرویس‌های نوشته شده با زبانهای برنامه‌نویسی مختلف یا فریم‌ورک‌های متفاوت میتوانند با سرویس‌های نوشته شده توسط فریم‌ورک Seneca در ارتباط و تبادل باشند.


نصب فریم‌ورک Seneca

نصب Seneca تفاوتی با نصب سایر فریم‌ورک‌ها توسط npm ندارد. مثلا میشود فایلی با نام package.json را با تنظیماتی که در پی خواهد آمد داشت و با اجرای دستور npm install در خط فرمان، Seneca را نصب کرد. البته دستور نصب را باید در پوشه‌ای که فایل package.json در آن  ایجاد شده‌است، اجرا کرد.
{
    "name": "seneca-example",
    "dependencies": {
        "seneca": "0.6.5",
        "express": "latest"
    }
}
بعد برای استفاده از فریم‌ورک نصب شده باید نمونه‌ای از آن را ایجاد کنیم. 
var seneca = require("seneca")();

 Actionها

‌Actionها عملکرد بخصوصی را ارائه می‌دهند که باید به نمونه‌ای از فریم‌ورک Seneca که ایجاد کرده‌ایم، اضافه شوند. اینکه یک Action چه عملکرد بخصوصی را ارائه می‌هد، در زمان اضافه کردن آن به نمونه‌ای ایجاد شده، مشخص می‌شود. در مطلب گذشته گفته شد که برای تغییر یک برنامه Monolithic به نوع سرویسی، می‌شود فقط کامپوننت‌های دردسر ساز را تغییر داد. Actionها عملکردهای آن کامپوننت‌های مشکل ساز را به عهده خواهند گرفت. زمان اضافه کردن یک Action به نمونه‌ای از Seneca، از متد add استفاده میکنیم که دو آرگومان را دریافت میکند. اولین آرگومان، یک رشته JSON یا یک object است که هویت عملکرد Action را نشان می‌دهد و دومی در واقع یک متد Callback است و زمانیکه Action فراخوانی میشود، اجرا خواهد شد.  
seneca.add({role: "accountManagement", cmd: "login"}, function(args, respond){
});
seneca.add({role: "accountManagement", cmd: "register"}, function(args, respond){
});
در مثال بالا دو Action را به نمونه‌ای از Seneca اضافه کرده‌ایم. خواص role و cmd موارد بخصوصی نیستند. می‌شود آن‌ها را با  موارد دلخواه، بسته به عملکردی که از Action انتظار داریم، جایگزین کنیم. برای فراخوانی Actionهایی که اضافه کرده‌ایم، باید از متد act استفاده کنیم. البته متد act میتواند Actionهایی را که در سایر سرویس‌ها قرار دارند هم فراخوانی کند. اولین آرگومان act، برای تطبیق با الگوهایی که در زمان اضافه کردن Actionها به نمونه تنظیم شده‌اند، استفاده می‌شود. دومین آرگومان آن هم باز یک Callback است و در زمانیکه Action فراخوانی شده‌است اجرا میشود. 
seneca.act({role: "accountManagement", cmd: "register", username:
"parham", password: "12345!"}, function(error, response){
});

seneca.act({role: "accountManagement", cmd: "login", username:
"parham", password: "12345!"}, function(error, response){
});
در مثال بالا و در پارامتر اول، مشخصه‌های بیشتری نسبت به الگوی Actionهایی که اضافه کرده‌ایم، دیده می‌شود. این مسئله مشکلی ایجاد نمیکند و به هر حال Seneca تمامی الگوهای مشابه را شناسایی میکند و آن موردی که بیشترین تطبیق را دارد، فرا میخواند. 


Pluginها

  Pluginها در Seneca در واقع بسته بندیهایی از Actionهایی هستند که کاربرد مشابهی دارند. در مثال بالا دو Action داشتیم که یکی عملکرد ورود را برعهده دارد و دیگری ثبت‌نام. از آنجایی که هر دو برای بخش مدیریت کاربران تشابهاتی دارند، می‌شود آنها را در یک Plugin بسته بندی کرد. ارزش Pluginها زمانی است که می‌خواهیم آنها را توزیع کنیم. با توزیع Plugin، تمام Actionهای زیرمجموعه آن همزمان توزیع می‌شوند. Pluginها را میشود در قالب توابع یا ماژول‌ها ایجاد کرد.
function account(options){
    this.add({init: "account"}, function(pluginInfo, respond){
        console.log(options.message);
        respond();
    })  

    this.add({role: "accountManagement", cmd: "login"}, function(args, respond){
     }); 

    this.add({role: "accountManagement", cmd: "register"}, function(args, respond){
    });
}

seneca.use(account, {message: "Plugin Added"});
در مثال بالا از روش تابع برای ایجاد Plugin استفاده شده‌است. Plugin را ایجاد می‌کنیم و Actionهایی را که میخواهیم به Plugin اضافه شوند، توسط this اضافه می‌کنیم و در نهایت با متد use به نمونه Seneca میگوییم که از Plugin ساخته شده، استفاده کند. کلمه کلیدی This درون Plugin به نمونه‌ای از Seneca که ایجاد کرده‌ایم، ارجاع دارد. یعنی هر this.add برابر با seneca.add می‌باشد. نکته باقی مانده، Action اضافه‌ای است با مشخصه {init: "account"} که چه نقشی دارد. این Action نقش یک سازنده را برای مقداردهی اولیه، دارد. برای نمونه میشود از آن برای ارتباط با پایگاه داده، پیش از اجرا شدن سایر Actionها که نیاز به آن ارتباط دارند، استفاده کرد. مثال بالا را می‌شود با یک ماژول هم پیاده‌سازی کرد.
module.exports = function(options){
    this.add({init: "account"}, function(pluginInfo, respond){
        console.log(options.message);
        respond();
    })

    this.add({role: "accountManagement", cmd: "login"}, function(args, respond){
    });

    this.add({role: "accountManagement", cmd: "register"},function(args, respond){
    });

    return "account";
}  

seneca.use("./account.js", {message: "Plugin Added"});
 ماژول، نام Plugin را برگشت میدهد که با فرض بر اینکه ماژول در فایل account.js قرار دارد، با متد use به نمونه Seneca میگوییم که از Plugin ساخته شده استفاده کند.


Serviceها  

یک سرویس، یک نمونه از فریم‌ورک Seneca است که Actionهایی را برای استفاده بیرونی، در معرض شبکه قرار می‌دهد. 
var seneca = require("seneca")();
seneca.use("./account.js", {message: "Plugin Added"});
seneca.listen({port: "9090", pin: {role: "accountManagement"}});
در مثال بالا، نمونه‌ای از فریم‌ورک Seneca ایجاد شده، مجموعه‌ای از Actionها تحت یک Plugin به آن اضافه شده و در نهایت Actionهایی که در زمان ساخت و اضافه کردن آنها نقش accountManagement گرفته‌اند، برای استفاده در معرض شبکه و در بستر HTTP قرار می‌گیرند. هر دو بخش port و pin، اختیاری هستند و با صرفنظر از این دو بخش، اولا نمونه‌ی Seneca برای دریافت درخواست‌های ورودی، به درگاه پیش فرض 10101 گوش می‌دهد و ثانیا تمامی Actionهایی را که به نمونه اضافه کرده‌ایم، در معرض شبکه قرار میگیرند.
در سمت دیگر، برای اینکه سایر سرویس‌ها و برنامه‌های Monolithic قادر باشند Actionهای در معرض شکبه قرار داده شده را فراخوانی کنند، باید آنچه را که در معرض قرار داده شده، در خود ثبت کنند. برای مثال سرویس دیگری از Seneca می‌تواند از قطعه کد زیر استفاده کند.
seneca.client({port: "9090", pin: {role: "accountManagement"}});

در اینجا هم موارد port و pin اختیاری هستند. اگر سرویسی که میخواهیم ثبت کنیم در سرور دیگری باشد، بایستی خاصیت host و با مقدار آدرس IP سرور مورد نظر در زمان ثبت، اعمال شود. سوالی که باقی می‌ماند این است که یک سرویس چطور Actionی از یک سرویس دیگر را فراخوانی می‌کند؟

در بخش Actionها آمد که برای فراخوانی یک Action از متد act،  از نمونه‌ی Seneca استفاده میکنیم. رفتار Seneca به این صورت است که ابتدا بر اساس امضای Action درخواست شده، Actionهای محلی را که به سرویس جاری اضافه شده‌اند، جستجو میکند. اگر تطبیقی نیافت به سراغ Actionهای ثبت شده خارجی که دارای خاصیت pin هستند، خواهد رفت و در نهایت اگر آنجا هم موردی نیافت، برای تک تک سرویس‌هایی که آنها را ثبت کرده، اما خاصیت pin را ندارند، درخواستی را ارسال میکند.


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

مطالب
شروع به کار با EF Core 1.0 - قسمت 5 - استراتژهای تعیین کلید اصلی جداول و ایندکس‌ها
پس از بررسی نحوه‌ی انجام تنظیمات اولیه‌ی کار با EF Core و همچنین آشنایی با مهاجرت‌های آن، مرحله‌ی بعد، مرحله‌ی مدلسازی داده‌ها است و اولین مرحله‌ی آن، نحوه‌ی تعیین کلید اصلی جداول است که در این زمینه، EF Core پیشرفت‌هایی قابل ملاحظه‌ای را نسبت به EF 6.x داشته‌است. در EF 6.x تنها دو حالت کلیدهای اصلی خود افزاینده که توسط بانک اطلاعاتی مدیریت می‌شوند و یا تولید کلید اصلی در سمت کلاینت و توسط برنامه، پشتیبانی می‌شوند. در EF Core، مواردی مانند Sequence و Alternate keys نیز اضافه شده‌اند.


پیش فرض‌های تعیین کلید اصلی در EF Core

به صورت پیش فرض هر خاصیتی که به نام Id و یا type name>Id> باشد، به عنوان primary key تفسیر خواهد شد؛ مانند:
public class Car
{
    public string Id { get; set; }
و یا
public class Car
{
   public string CarId { get; set; }
در مثال اول، نام خاصیت، Id است و در مثال دوم، جمع نام کلاس به همراه Id ذکر شده‌است. یک چنین مواردی، نیازی به تنظیم اضافه‌تری ندارند.


نحوه‌ی تعیین کلید اصلی به صورت صریح

اگر یکی از دو حالت فوق برقرار نباشند، باید کلید اصلی را به نحو صریحی مشخص کرد.
الف) از طریق ویژگی‌ها
public class Car
{
   [Key]
   public string LicensePlate { get; set; }
در اینجا چون LicensePlate نه Id نام دارد و نه جمع نام کلاس به همراه Id است، باید به نحو صریحی توسط ویژگی Key مشخص شود.
ب) با استفاده از روش Fluent API
public class MyContext : DbContext
{
    public DbSet<Car> Cars { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
         modelBuilder.Entity<Car>()
                 .HasKey(c => c.LicensePlate);
    }
 }
روش تنظیم کلید اصلی به صورت صریح، از طریق کدنویسی است که به آن Fluent API یا API روان هم گفته می‌شود. برای اینکار باید متد OnModelCreating کلاس Context برنامه را بازنویسی کرد و سپس از طریق متد HasKey، نام خاصیت کلید اصلی را ذکر نمود.


پیشنیاز کار با ویژگی‌ها در EF Core

در اسمبلی که مدل‌های موجودیت‌ها شما قرار دارند، نیاز است وابستگی System.ComponentModel.Annotations به فایل project.json پروژه اضافه شود، تا ویژگی‌هایی مانند Key، شناسایی و قابل استفاده شوند:
{
   "dependencies": {
          "System.ComponentModel.Annotations": "4.1.0"
   }
}


تعیین کلید ترکیبی و یا Composite key

اگر نیاز است چندین خاصیت را به صورت کلید اصلی معرفی کرد که به آن composite key هم می‌گویند، تنها روش ممکن، استفاده از Fluent API و به صورت زیر است:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
   modelBuilder.Entity<Car>()
                       .HasKey(c => new { c.State, c.LicensePlate });
}
در قسمت HasKey می‌توان چندین خاصیت را نیز جهت تعیین کلید ترکیبی مشخص کرد.


روش‌های مختلف تولید خودکار مقادیر خواص

حالت پیش فرض تولید مقدار فیلدهای Id عددی، همان حالت خود افزاینده‌ای است که توسط بانک اطلاعاتی کنترل می‌شود و یا کلید اصلی که از نوع Guid تعیین شود نیز به صورت خودکار توسط بانک اطلاعاتی در حین عملیات Add، مقدار دهی می‌شود (با استفاده از الگوریتم Guid سری در SQL Server).
 اگر این حالات مطلوب شما نیست، حالت‌های سه گانه‌ی ذیل را می‌توان استفاده کرد:

الف) هیچ داده‌ی خودکاری تولید نشود
برای اینکار می‌توان با استفاده از ویژگی DatabaseGenerated و تنظیم مقدار آن به None، جلوی تولید خودکار کلید اصلی را گرفت. در این حالت باید هم در حین عملیات Add و هم در حین عملیات Update، مقادیر را خودتان مقدار دهی کنید:
public class Blog
{
    [DatabaseGenerated(DatabaseGeneratedOption.None)]
    public int BlogId { get; set; }

    public string Url { get; set; }
}
و یا معادل این تنظیم با استفاده از Fluent API به صورت ذیل است:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
           .Property(b => b.BlogId)
           .ValueGeneratedNever();
}

ب) تولید داده‌های خودکار فقط در حالت Add
حالت Add به این معنا است که داده‌های خواص مشخصی، برای موجودیت‌های «جدید»، به صورت خودکار تولید خواهند شد. اینکه آیا واقعا این مقادیر به صورت خودکار تولید می‌شوند یا خیر، صرفا وابسته‌است به بانک اطلاعاتی در حال استفاده. برای مثال SQL Server برای نوع‌های Guid، به صورت خودکار با کمک الگوریتم SQL Server sequential GUID، کار مقدار دهی یک چنین فیلدهایی را انجام می‌دهد.
این فیلدها باید توسط ویژگی DatabaseGenerated و با مقدار Identity مشخص شوند. در اینجا Identity به معنای فیلدهایی است که به صورت خودکار توسط بانک اطلاعاتی مقدار دهی می‌شوند و الزاما به کلید اصلی اشاره نمی‌کنند. برای مثال در موجودیت ذیل، خاصیت تاریخ ثبت رکورد، از نوع Identity مشخص شده‌است. به این معنا که در حین ثبت اولیه‌ی رکورد آن، نیازی نیست تا خاصیت Inserted را مقدار دهی کرد. اما اینکه آیا SQL Server یک چنین کاری را به صورت خودکار انجام می‌دهد، پاسخ آن خیر است. SQL server فقط برای فیلدهای عددی و Guid ایی که با DatabaseGeneratedOption.Identity مزین شده باشند، مقادیر متناظری را به صورت خودکار تولید می‌کند. برای حالت DateTime نیاز است، مقدار پیش فرض فیلد را صریحا مشخص کرد که توسط ویژگی‌ها میسر نیست و فقط fluent API از آن پشتیبانی می‌کند.
public class Blog
{
   public int BlogId { get; set; }
   public string Url { get; set; }

   [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
   public DateTime Inserted { get; set; }
}
و یا معادل این تنظیم با استفاده از Fluent API به صورت ذیل است:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
           .Property(b => b.Inserted)
           .ValueGeneratedOnAdd();
}
برای تعیین مقدار پیش فرض خاصیت Inserted به نحوی که توسط SQL Server به صورت خودکار مقدار دهی شود، می‌توان از متد HasDefaultValueSql به نحو ذیل استفاده کرد:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .Property(b => b.Inserted)
        .HasDefaultValueSql("getdate()");
}
البته باید درنظر داشت که اگر خاصیت DateTime تعریف شده در اینجا به همین نحو بکاربرده شود، اگر مقداری برای آن در حین تعریف یک وهله جدید از کلاس Blog درکدهای برنامه درنظر گرفته نشود، یک مقدار پیش فرض حداقل به آن انتساب داده خواهد شد (چون value type است). بنابراین نیاز است این خاصیت را از نوع nullable تعریف کرد (public DateTime? Inserted).

یک نکته: در حالت DatabaseGeneratedOption.Identity و یا ValueGeneratedOnAdd فوق، اگر مقداری به این نوع فیلدها انتساب داده شده باشد که با مقدار پیش فرض آن‌ها (property.ClrType.GetDefaultValue) متفاوت باشد، از این مقدار جدید، بجای تولید مقداری خودکار، استفاده خواهد شد. برای مثال مقدار پیش فرض رشته‌ها، نال، مقادیر عددی، صفر و برای Guid مقدار Guid.Empty است. اگر هر مقدار دیگری بجای این‌ها به فیلدهای فوق انتساب داده شوند، از آن‌ها استفاده می‌شود.

ج) تولید داده‌های خودکار در هر دو حالت Add و Update
تولید داده‌ها در حالت‌های Add و Update به این معنا است که یک چنین خواصی، همواره با فراخوانی متد SaveChanges، دارای مقدار خودکار جدیدی خواهند شد و نیازی نیست در کدها مقدار دهی شوند. برای مشخص سازی این نوع خواص، از ویژگی DatabaseGenerated با مقدار Computed و یا متد ValueGeneratedOnAddOrUpdate در حالت Fluent API می‌توان استفاده کرد:
public class Blog
{
    public int BlogId { get; set; }
    public string Url { get; set; }

    [DatabaseGenerated(DatabaseGeneratedOption.Computed)]
    public DateTime LastUpdated { get; set; }
}
و یا معادل این تنظیم با استفاده از Fluent API به صورت ذیل است:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
       .Property(b => b.LastUpdated)
       .ValueGeneratedOnAddOrUpdate();
}
همانطور که پیشتر نیز عنوان شد، تولید خودکار مقادیر فیلدها فقط در حالت‌های int و Guid انجام می‌شود (که برای مثال SQL Server از آن‌ها پشتیبانی می‌کند). در مثال فوق، خاصیت LastUpdated از نوع DateTime اینگونه تعریف شده‌است و SQL Server برای یک چنین فیلدهای خاصی، مقدار خودکاری را تولید نکرده و به دنبال مقدار پیش فرض آن می‌گردد. بنابراین در اینجا نیز باید مشخص سازی HasDefaultValueSql("getdate()") را که در قسمت قبل عنوان کردیم، صراحتا در قسمت تنظیمات Fluent API ذکر و تنظیم کرد.

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


خواص محاسباتی (Computed Columns) و تفاوت آن‌ها با DatabaseGeneratedOption.Computed

خواص محاسباتی (Computed Columns)، خواصی هستند که مقادیر آن‌ها در بانک اطلاعاتی محاسبه می‌شوند و کاملا متفاوت هستند با DatabaseGeneratedOption.Computed که مفهوم دیگری دارد. DatabaseGeneratedOption.Computed به این معنا است که این فیلد خاص، با هر بار فراخوانی SaveChanges باید مقدار محاسبه شده‌ی جدیدی را داشته باشد و روش تولید این مقدار خودکار، یا بر اساس Guidهای سری است، یا توسط فیلدهای خود افزاینده‌ی عددی و یا از طریق مقادیر پیش فرضی مانند getdate در حین ثبت یا به روز رسانی، مقدار دهی می‌شوند. اما خواص محاسباتی، یکی از امکانات «گزارشگیری سریع» SQL Server هستند و به نحو ذیل، تنها توسط Fluent API قابل تنظیم می‌باشند:
public class Person
{
    public int PersonId { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string DisplayName { get; set; }
}

public class MyContext : DbContext
{
    public DbSet<Person> People { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
          modelBuilder.Entity<Person>()
              .Property(p => p.DisplayName)
               .HasComputedColumnSql("[LastName] + ', ' + [FirstName]");
     }
 }
در اینجا فیلد DisplayName یک فیلد محاسباتی بوده و از حاصل جمع دو فیلد دیگر در سمت دیتابیس تشکیل می‌شود. این نگاشت و محاسبه چون در سمت بانک اطلاعاتی انجام می‌شود، بازدهی بیشتری دارد نسبت به حالتی که ابتدا دو فیلد به کلاینت منتقل شده و سپس در این سمت جمع زده شوند.


امکان تعریف Sequence در EF Core 1.0

Sequence قابلیتی است که به SQL Server 2012 اضافه شده‌است و توضیحات بیشتر آن‌را در مطلب «نحوه ایجاد Sequence و استفاده آن در Sql Server 2012» می‌توانید مطالعه کنید.
در EF Core، امکان مدلسازی Sequence نیز پیش بینی شده‌است. آن‌ها به صورت پیش فرض در مدل‌ها ذکر نمی‌شوند و همچنین وابستگی به جدول خاصی ندارند. به همین جهت امکان تعریف آن‌ها صرفا توسط Fluent API وجود دارد:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
     modelBuilder.HasSequence<int>("OrderNumbers", schema: "shared") 
           .StartsAt(1000).IncrementsBy(5);

     modelBuilder.Entity<Order>()
         .Property(o => o.OrderNo)
         .HasDefaultValueSql("NEXT VALUE FOR shared.OrderNumbers");
}
پس از اینکه یک Sequence  تعریف شد، می‌توان برای نمونه از آن جهت تولید مقادیر پیش فرض ستون‌ها استفاده کرد.
در مثال فوق، ابتدا یک Sequence نمونه به نام OrderNumbers تعریف شده‌است که از عدد 1000 شروع شده و واحد افزایش آن 5 است. سپس از این نام در قسمت مقدار پیش فرض ستون OrderNo استفاده شده‌است.

و یا از Sequence ‌ها می‌توان برای تعیین مقدار پیش فرض Primary key بجای حالت identity خود افزایش یابنده استفاده کرد:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.HasSequence<int>("PrimaryKeyWithSequenceSequence");
    modelBuilder.Entity<PrimaryKeyWithSequence>(entity =>
     {
       entity.Property(e => e.PrimaryKeyWithSequenceId).HasDefaultValueSql("NEXT VALUE FOR [PrimaryKeyWithSequenceSequence]");
     });
}
در اینجا یک توالی از نوع int تعریف شده و سپس هربار که قرار است رکوردی درج شود، مقدار id آن به صورت خودکار از طریق کوئری Select NEXT VALUE FOR
[PrimaryKeyWithSequenceSequence] دریافت و سپس بجای فیلد id درج می‌شود.

به این روش الگوریتم Hi-Low هم می‌گویند که یکی از مهم‌ترین اهداف آن داشتن یک سری Id منحصربفرد، جهت بالابردن سرعت insertها در یک batch است. در حالت عادی insertها، ابتدا یک insert انجام می‌شود، سپس کوئری گرفته شده و آخرین Id درج شده به کلاینت بازگشت داده می‌شود. این روش، برای انجام تنها یک insert، سریع است. اما برای batch insert، به شدت کارآیی پایینی دارد. به همین جهت دسترسی به بازه‌ای از اعداد منحصربفرد، پیش از شروع به insert تعداد زیادی رکورد، سرعت نهایی کار را بالا می‌برد.


نحوه‌ی تعریف ایندکس‌ها در EF Core 1.0

برای افزودن ایندکس‌ها به EF Core 1.0، تنها روش میسر، استفاده از Fluent API است (و برخلاف EF 6.x از روش data annotations فعلا پشتیبانی نمی‌کند؛ هرچند API جدید آن نسبت به EF 6.x بسیار واضح‌تر است و با ابهامات کمتر).
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
      modelBuilder.Entity<Blog>()
          .HasIndex(b => b.Url)
          .HasName("Index_Url");
اگر قسمت HasName را ذکر نکنید، نام آن <IX_<type name>_<property name درنظر گرفته می‌شود و برای اینکه ایندکس منحصربفردی را تعریف کنید، می‌توان متد IsUnique را به انتهای این زنجیره اضافه کرد:
 modelBuilder.Entity<Blog>().HasIndex(b => b.Url).HasName("Index_Url").IsUnique();
همچنین می‌توان همانند composite keys، در اینجا نیز ترکیبی از خواص را به صورت یک ایندکس معرفی نمود:
modelBuilder.Entity<Person>()
   .HasIndex(idx => new { idx.FirstName, idx.LastName })
   .IsUnique();
در این حالت اگر HasName ذکر نشود، نام آن همانند الگویی است که پیشتر عنوان شد؛ با این تفاوت که قسمت property name آن، جمع نام تمام خواص ذکر شده و جدا شده‌ی با _ خواهد بود.

یک نکته: اگر از پروایدر SQL Server استفاده می‌کنید، می‌توان متد الحاقی ویژه‌ای را به نام ForSqlServerIsClustered نیز برای تعریف clustered indexes، در این زنجیره ذکر کرد.


امکان تعریف Alternate Keys در EF Core 1.0

به Unique Constraints در EF Core، نام Alternate Keys را داده‌اند و این مورد نیز تنها از طریق Fluent API قابل تنظیم است:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
   modelBuilder.Entity<Car>()
     .HasAlternateKey(c => c.LicensePlate)
     .HasName("AlteranteKey_LicensePlate");
}
برای یک Alternate Key به صورت خودکار هم ایندکس ایجاد می‌شود و هم اینکه این ایندکس منحصربفرد خواهد بود.
اگر متد HasName در اینجا ذکر نشود، نام پیش فرض آن  <type name>_<property name> خواهد بود و اگر همانند composite keys و یا ایندکس‌های ترکیبی، چند خاصیت ذکر شوند، قسمت property name به جمع نام تمام خواص ذکر شده و جدا شده‌ی با _ تنظیم می‌شود.
برای نمونه اگر یک Alternate Key ترکیبی را به صورت ذیل تعریف کنیم:
modelBuilder.Entity<Person>()
     .HasAlternateKey(x => new { x.FirstName, x.LastName });
در قسمت مهاجرت‌هایی که قرار است به بانک اطلاعاتی اعمال شوند، به یک UniqueConstraint ترجمه می‌شود:
 table.UniqueConstraint("AK_Persons_FirstName_LastName", x => new { x.FirstName, x.LastName });


سؤال: یک Unique Constraint با Unique Index چه تفاوتی دارد؟

در پشت صحنه، پیاده سازی یک Unique Constraint با Unique Index تفاوتی ندارند. فقط از دیدگاه روشن‌تر شدن مقصود، استفاده‌ی از Unique Constraint ترجیح داده می‌شود.
البته از دیدگاه بانک اطلاعاتی پیاده سازی کننده نیز برای نمونه SQL Server، این تفاوت‌ها وجود دارند:
الف) یک Unique Constraint را نمی‌توان غیرفعال کرد؛ برخلاف Unique Indexها.
ب) Unique Constraint‌ها موارد اضافه‌تری را مانند FILLFACTOR و IGNORE_DUP_KEY نیز می‌توانند تنظیم کنند.
ج) امکان تعریف فیلترها برای Unique Indexها وجود دارد؛ برخلاف Unique Constraint ها.

که البته از دیدگاه EF، این سه مورد اهمیتی ندارند و بیشتر روشن‌تر شدن مقصود، هدف اصلی آن‌ها است.
نظرات مطالب
افزودن هدرهای Content Security Policy به برنامه‌های ASP.NET
یک نکته‌ی تکمیلی: روش ساده‌تر تنظیم هدرهای امنیتی در فایل web.config که سازگار با تمام نگارش‌های ASP.NET است
<system.webServer>
  <httpProtocol>
    <customHeaders>
      <!-- SECURITY HEADERS - https://securityheaders.io/? -->
      <!-- Protects against Clickjacking attacks. ref.: http://stackoverflow.com/a/22105445/1233379 -->
      <add name="X-Frame-Options" value="SAMEORIGIN" />
      <!-- Protects against Clickjacking attacks. ref.: https://www.owasp.org/index.php/HTTP_Strict_Transport_Security_Cheat_Sheet -->
      <add name="Strict-Transport-Security" value="max-age=31536000; includeSubDomains"/>
      <!-- Protects against XSS injections. ref.: https://www.veracode.com/blog/2014/03/guidelines-for-setting-security-headers/ -->
      <add name="X-XSS-Protection" value="1; mode=block" />
      <!-- Protects against MIME-type confusion attack. ref.: https://www.veracode.com/blog/2014/03/guidelines-for-setting-security-headers/ -->
      <add name="X-Content-Type-Options" value="nosniff" />
      <!-- CSP modern XSS directive-based defence, used since 2014. ref.: http://content-security-policy.com/ -->
      <add name="Content-Security-Policy" value="default-src 'self'; font-src *;img-src * data:; script-src *; style-src *;" />
      <!-- Prevents from leaking referrer data over insecure connections. ref.: https://scotthelme.co.uk/a-new-security-header-referrer-policy/ -->
      <add name="Referrer-Policy" value="strict-origin" />
    </customHeaders>
  </httpProtocol>
</system.webServer>