نظرات مطالب
یافتن لیست اسمبلی‌های ارجاعی
سلام
نیازی نبود سورسی را Map کنید یا اصلا نباید این‌کار را می‌کردید. (اگر منظور مپ کردن سورس بوده نه فایل اجرایی برنامه)
SVN وظیفه مدیریت و به اشتراک گذاشتن پروژه رو داره، نه شبکه ویندوزی یا لینوکسی. (حتما از visual svn server استفاده کنید تا این موارد را برای شما ساده کند)
کلاینت‌ها هر کدام نسخه‌ی کامل و لوکال خودشون رو باید داشته باشند (از طریق check out مخزن کد این پروژه لوکال باید تشکیل شود نه کپی دستی). سپس مثل اینکه دارند لوکال کار می‌کنند (نه از روی شبکه در حالت مپ شده). کاملا حالت معمولی و قطع از شبکه. SVN برای مدیریت پروژه روی اینترنت هم بکار میره. نه چیزی map میشه و نه لازم هست کاربر همیشه به شبکه وصل باشه.
نسخه کد شما که روی سرور هست و توسط SVN نگهداری می‌شود، مخزن اصلی است که تغییرات با آن هماهنگ می‌شود. برای انتقال کد به مخزن، باید عملیات check in صورت گیرد.
بعد هر کدام از اعضای تیم زمانیکه check out می‌کنند ، یک نسخه‌ی محلی دریافت می‌کنند و این فولدر تحت کنترل SVN قرار میگیره، حالا مباحث update و commit و غیره کار می‌کند. فقط هر بار که می‌خواهند commit کنند باید اول update کنند ببینند کسی چیزی را تغییر داده، تصادمی هست یا نه؟ بعد commit‌ کنند به سرور. (یعنی ارتباط با شبکه فقط در همین چند لحظه کوتاه است و بسیار سریع هم خواهد بود)
فصل دوم کتابچه‌ای را که من تهیه کردم لطفا مطالعه کنید، گردش کاری آن توضیح داده شده است.
https://www.dntips.ir/2008/10/subversion.html
در فصل یک هم توضیح دادم که چه پورتی را باید روی سرور باز کنید تا SVN توسط فایروال بلاک نشود.

حتما توصیه می‌کنم اگر با VS.Net کار می‌کنید افزونه Visual SVN را نصب کنید تا راحت و بدون دردسر کار کنید .


خطایی رو که توضیح دادید مربوط هست به اشتراک گذاشتن فایل اجرایی یک برنامه دات نتی روی شبکه که این خطا رو می‌گیرید:
Project Location Is not Trusted
این خطا رو با دادن full trust به برنامه می‌تونید حل کنید که اینجا قدم به قدم توضیح داده شده:
http://msdn.microsoft.com/en-us/library/bs2bkwxc(VS.80).aspx
همچنین کامنت‌های اون رو لطفا بخونید. مثلا ممکن هست فایل بلاک شده باشه که با کلیک راست و unblock کردن مشکل حل میشه.

خطای زیر بیشتر مربوط به حالتی است که الف) هنوز مخزن کد ایجاد نشده و ب) عملیات initial import یا check in صورت نگرفته
Unable to open an ra_local url. unable to open repository.
حتما کتابچه فوق را مطالعه نمایید.
مطالب
React 16x - قسمت 25 - ارتباط با سرور - بخش 4 - یک تمرین
همان مثال backend قسمت 22 را با افزودن وب سرویس‌هایی برای قسمت‌های نمایش لیست فیلم‌ها، ژانرها و سایر صفحات اضافه شده‌ی به برنامه‌ی تکمیل شده‌ی تا قسمت 21، توسعه می‌دهیم. کدهای کامل آن، به علت شباهت و یکی بودن نکات آن با مطلب 22، در اینجا تکرار نخواهند شد و می‌توانید کل پروژه‌ی آن‌را از پیوست انتهای بحث دریافت کنید. سپس فایل dotnet_run.bat آن‌را اجرا کنید تا در آدرس https://localhost:5001 قابل دسترسی شود.


افزودن سرویس httpService.js به برنامه

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


دریافت اطلاعات لیست نمایش ژانرها از سرویس backend

با فراخوانی آدرس https://localhost:5001/api/Genres، می‌توان لیست ژانرهای سینمایی تعریف شده‌ی در سرویس‌های backend را مشاهده کرد. اکنون قصد داریم از این اطلاعات، در برنامه استفاده کنیم. به همین جهت به فایل src\components\movies.jsx مراجعه کرده و تغییرات زیر را اعمال می‌کنیم:
چون نمی‌خواهیم تغییراتی بسیار اساسی را در اینجا اعمال کنیم، قدم به قدم عمل کرده و سرویس قبلی fakeGenreService.js را با یک سرویس جدید که اطلاعات خودش را از سرور دریافت می‌کند، جایگزین می‌کنیم. بنابراین ابتدا فایل جدید src\services\genreService.js را ایجاد می‌کنیم. سپس آن‌را طوری تکمیل خواهیم کرد که اینترفیس آن، با اینترفیس fakeGenreService قبلی یکی باشد:
import { apiUrl } from "../config.json";
import http from "./httpService";

export function getGenres() {
  return http.get(apiUrl + "/genres");
}
همچنین در اینجا import وابستگی config.json را نیز مشاهده می‌کنید که در قسمت قبل در مورد آن توضیح دادیم. به همین جهت برای تمیزتر شدن قسمت‌های مختلف برنامه، فایل config.json را در مسیر src\config.json ایجاد کرده و به صورت زیر تکمیل می‌کنیم:
{
   "apiUrl": "https://localhost:5001/api"
}
apiUrl به ریشه‌ی URLهای ارائه شده‌ی توسط backend service ما، اشاره می‌کند.

پس از تکمیل سرویس جدید src\services\genreService.js، به فایل src\components\movies.jsx بازگشته و سطر قبلی
import { getGenres } from "../services/fakeGenreService";
را با سطر جدید زیر، جایگزین می‌کنیم:
import { getGenres } from "../services/genreService";
تا اینجا اگر برنامه را ذخیره کرده و اجرا کنید، خطای زیر را مشاهده خواهید کرد:
Uncaught TypeError: Object is not a function or its return value is not iterable
علت اینجا است که سرویس قبلی fakeGenreService، دارای متد export شده‌ای به نام getGenres بود که یک آرایه‌ی معمولی را بازگشت می‌داد. اکنون این سرویس جدید نیز همان ساختار را دارد، اما اینبار یک Promise را بازگشت می‌دهد. به همین جهت متد componentDidMount را به صورت زیر اصلاح می‌کنیم:
  async componentDidMount() {
    const { data } = await getGenres();
    const genres = [{ _id: "", name: "All Genres" }, ...data];
    this.setState({ movies: getMovies(), genres });
  }
متد getGenres باید await شود تا نتیجه‌ی آن توسط خاصیت data شیء بازگشتی از آن، قابل دسترسی شود. با این تغییر، نیاز است این متد را نیز به صورت async معرفی کرد.


دریافت اطلاعات لیست فیلم‌ها از سرویس backend

پس از دریافت لیست ژانرهای سینمایی از سرور، اکنون نوبت به جایگزینی src\services\fakeMovieService.js با یک نمونه‌ی متصل به backend است. به همین جهت ابتدا فایل جدید src\services\movieService.js را ایجاد کرده و سپس آن‌را به صورت زیر تکمیل می‌کنیم:
import { apiUrl } from "../config.json";
import http from "./httpService";

const apiEndpoint = apiUrl + "/movies";

function movieUrl(id) {
  return `${apiEndpoint}/${id}`;
}

export function getMovies() {
  return http.get(apiEndpoint);
}

export function getMovie(movieId) {
  return http.get(movieUrl(movieId));
}

export function saveMovie(movie) {
  if (movie.id) {
    return http.put(movieUrl(movie.id), movie);
  }

  return http.post(apiEndpoint, movie);
}

export function deleteMovie(movieId) {
  return http.delete(movieUrl(movieId));
}
سپس شروع به اصلاح کامپوننت movies می‌کنیم.
ابتدا دو متد دریافت لیست فیلم‌ها و حذف یک فیلم را که در این کامپوننت استفاده شده‌اند، import می‌کنیم:
import { getMovies, deleteMovie } from "../services/movieService";
بعد متد getMovies پیشین، که یک آرایه را بازگشت می‌داد، توسط متد جدیدی که یک Promise را بازگشت می‌دهد، جایگزین می‌شود:
  async componentDidMount() {
    const { data } = await getGenres();
    const genres = [{ id: "", name: "All Genres" }, ...data];

    const { data: movies } = await getMovies();
    this.setState({ movies, genres });
  }
همچنین مدیریت حذف رکوردها را نیز به صورت زیر با پیاده سازی «به‌روز رسانی خوشبینانه UI» که در قسمت قبل در مورد آن بیشتر بحث شد، تغییر می‌دهیم. در این حالت فرض بر این است که به احتمال زیاد،  await deleteMovie با موفقیت به پایان می‌رسد. بنابراین بهتر است UI را ابتدا به روز رسانی کنیم تا کاربر حس کار کردن با یک برنامه‌ی سریع را داشته باشد:
  handleDelete = async movie => {
    const originalMovies = this.state.movies;

    const movies = originalMovies.filter(m => m.id !== movie.id);
    this.setState({ movies });

    try {
      await deleteMovie(movie.id);
    } catch (ex) {
      if (ex.response && ex.response.status === 404) {
        console.log(ex);
        toast.error("This movie has already been deleted.");
      }

      this.setState({ movies: originalMovies }); //undo changes
    }
  };
ابتدا ارجاعی را از state قبلی ذخیره می‌کنیم تا در صوت بروز خطایی در سطر await deleteMovie، بتوانیم مجددا state را به حالت اول آن بازگردانیم. به همین منظور پیاده سازی «به‌روز رسانی خوشبینانه UI»، حتما نیاز به درج صریح try/catch را دارد. برای نمایش خطاهای ویژه‌ی 404 نیز از یک toast استفاده شده که نیاز به import زیر را دارد:
import { toast } from "react-toastify";
سایر خطاهای رخ داده، توسط interceptor درج شده‌ی در سرویس http، به صورت خودکار پردازش می‌شوند.


اتصال فرم ثبت و ویرایش یک فیلم به backend server

تا اینجا اگر برنامه را اجرا کنیم، با کلیک بر روی لینک هر فیلم نمایش داده شده‌ی در صفحه، به صفحه‌ی not-found هدایت می‌شویم. برای رفع این مشکل، به فایل src\components\movieForm.jsx مراجعه کرده و ابتدا
import { getGenres } from "../services/fakeGenreService";
import { getMovie, saveMovie } from "../services/fakeMovieService";
قبلی را با نمونه‌ها‌ی جدیدی که با سرور کار می‌کنند، جایگزین می‌کنیم:
import { getGenres } from "../services/genreService";
import { getMovie, saveMovie } from "../services/movieService";
سپس ارجاعات به این سه متد import شده را با await، همراه کرده و متد اصلی را به صورت async معرفی می‌کنیم:
  async componentDidMount() {
    const { data: genres } = await getGenres();
    this.setState({ genres });

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

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

    this.setState({ data: this.mapToViewModel(movie) });
  }
البته می‌توان جهت بهبود کیفیت کدها، از متد componentDidMount، دو متد با مسئولیت‌های مجزای دریافت ژانرها (populateGenres) و سپس نمایش فرم اطلاعات فیلم (populateMovie) را استخراج کرد:
  async populateGenres() {
    const { data: genres } = await getGenres();
    this.setState({ genres });
  }

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

      const { data: movie } = await getMovie(movieId);
      this.setState({ data: this.mapToViewModel(movie) });
    } catch (ex) {
      if (ex.response && ex.response.status === 404)
        this.props.history.replace("/not-found");
    }
  }

  async componentDidMount() {
    await this.populateGenres();
    await this.populateMovie();
  }
در متد populateMovie، اگر movieId اشتباهی وارد شود و یا کلا عملیات دریافت اطلاعات، با شکست مواجه شود، کاربر را به صفحه‌ی not-found هدایت می‌کنیم. یعنی وجود try/catch در اینجا ضروری است. چون اگر movieId اشتباهی وارد شود، اینبار دیگر خطوط بعدی اجرا نمی‌شوند و در همان سطر await getMovie، یک استثناء صادر شده و کار خاتمه پیدا می‌کند. بنابراین نیاز داریم بتوانیم این استثنای احتمالی را مدیریت کرده و کاربر را به صفحه‌ی not-found هدایت کنیم.
پیشتر زمانیکه متد getMovie، یک شیء ساده را از fake service، بازگشت می‌داد، چنین مشکلی را نداشتیم؛ به همین جهت در سطر بعدی آن، هدایت کاربر در صورت نال بودن نتیجه، با یک return صورت می‌گرفت. اما اینجا بجای نال، یک استثناء را ممکن است دریافت کنیم.

مرحله‌ی آخر اصلاح این فرم، اتصال قسمت ثبت اطلاعات آن است که با قرار دادن یک await، پیش از متد saveMovie و async کردن متد آن، انجام می‌شود:
  doSubmit = async () => {
    await saveMovie(this.state.data);

    this.props.history.push("/movies");
  };


کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید: sample-25-backend.zip و sample-25-frontend.zip
نظرات مطالب
افزونه فارسی به پارسی برای word 2007
سلام،
فعلا می‌تونید با استفاده از افزونه زیر:
https://addons.mozilla.org/en-US/firefox/addon/5817
و یا برنامه Lita
http://www.dehats.com/drupal/?q=node/58
فایل ErrorsBank.sqlite را باز کرده (در محل نصب قابل دسترسی است) و رکوردهای آن‌را ویرایش یا کم و زیاد کنید.
مطالب دوره‌ها
عیب یابی و دیباگ برنامه‌های SignalR
1) برنامه SignalR در IE کار نمی‌کند.
پردازشگر json، در نگارش‌های اخیر IE به آن اضافه شده است. برای رفع این مشکل در نگارش‌های قدیمی، نیاز است از اسکریپت کمکی http://nuget.org/List/Packages/json2 استفاده نمائید. همچنین مرورگر IE را نیز باید وادار ساخت تا بر اساس آخرین موتور پردازشی خود کار کند:
 <meta http-equiv="X-UA-Compatible" content="IE=edge" />

2) هنگام فراخوانی مسیر signalr/hubs پیغام 404 (یافت نشد) دریافت می‌شود.
برای رفع این مشکل ابتدا اطمینان حاصل کنید که تنظیمات مسیریابی تعریف شده در فایل global.asax.cs موجود هستند.
در ادامه اطمینان حاصل نمائید مسیر اسکریپت‌های signalr/hubs به درستی تعریف شده‌اند:
 <script type="text/javascript" src="/signalr/hubs"></script>
برای مثال در برنامه‌های MVC و وب فرم‌ها تعریف صحیح باید به شکل زیر باشد:
 MVC:
<script type="text/javascript" src="@Url.Content("~/signalr/hubs")"></script>

Web forms:
<script type="text/javascript" src='<%= ResolveClientUrl("~/signalr/hubs") %>'></script>
همچنین وجود تنظیمات ذیل را در فایل وب کانفیگ برنامه نیز بررسی کنید:
<configuration>
   <system.webServer>
        <modules runAllManagedModulesForAllRequests="true">
        </modules>
    </system.webServer>
</configuration>

3) متدهای سمت کلاینت من فراخوانی نمی‌شوند.
بهترین راه برای مشاهده ریز جرئیات خطاها، ذکر سطر ذیل در کدهای سمت کلاینت جاوا اسکریپتی برنامه است:
 $.connection.hub.logging = true;
و سپس مراجعه به کنسول جاوا اسکریپت مرورگر برای بررسی خطاهای لاگ شده.

4) خطای «Connection must be started before data can be sent» را دریافت می‌کنم.
همانطور که در قسمت قبل عنوان شد، کلیه فراخوانی‌های SignalR از نوع غیرهمزمان هستند. بنابراین باید با استفاده از callback و زمان فراخوانی آن‌ها که عموما پس از برقراری اتصال رخ می‌دهد، نسبت به انجام امور دلخواه اقدام کرد.
               var connection = $.hubConnection('http://localhost:8081/');
               proxy = connection.createProxy('collectionhub')
               connection.start()
                    .done(function () {
                        proxy.invoke('subscribe', 'Product');
                        $('#messages').append('<li>invoked subscribe</li>');
                    })
                    .fail(function () { alert("Could not Connect!"); });
همانطور که در این مثال مشاهده می‌کنید، سطر proxy.invoke در یک callback فراخوانی شده است و نه بلافاصله در سطری پس از connection.start. هر زمان که اتصال به نحو موفقیت آمیزی برقرار شد، آنگاه متد subscribe در سمت سرور فراخوانی می‌گردد.
در حالت استفاده بدون پروکسی نیز چنین callbackهایی قابل تعریف هستند:
$.connection.hub.start()
                .done(function() {
                    myHub.server.SomeFunction(SomeParam) //e.g. a login or init
                         .done(connectionReady); 
                })
                .fail(function() {
                    alert("Could not Connect!");
                 });

5) بعد از 10 اتصال به IIS، برنامه متوقف می‌شود.
این مورد، محدودیت ذاتی IIS 7 نصب شده بر روی ویندوز 7 است. بهتر است از یک IIS کامل موجود در ویندوزهای سرور استفاده کنید. در این سرورها عدد پیش فرض تنظیم شده 5000 اتصال است که در صورت نیاز با استفاده از دستور زیر قابل تغییر است:
 appcmd.exe set config /section:system.webserver/serverRuntime /appConcurrentRequestLimit:100000
به علاوه ASP.NET نیز محدودیت 5000 اتصال به ازای هر CPU را دارد. برای تغییر آن باید به مسیر ذیل مراجعه
  %windir%\Microsoft.NET\Framework\v4.0.30319\aspnet.config
و سپس مقدار maxConcurrentRequestsPerCPU را تنظیم کرد:
<?xml version="1.0" encoding="UTF-8" ?>
<configuration>
    <runtime>
        <legacyUnhandledExceptionPolicy enabled="false" />
        <legacyImpersonationPolicy enabled="true"/>
        <alwaysFlowImpersonationPolicy enabled="false"/>
        <SymbolReadingPolicy enabled="1" />
        <shadowCopyVerifyByTimestamp enabled="true"/>
    </runtime>
    <startup useLegacyV2RuntimeActivationPolicy="true" />
    <system.web>
        <applicationPool maxConcurrentRequestsPerCPU="20000" />
    </system.web>
</configuration>
به علاوه ASP.NET پس از رد شدن از حد maxConcurrentRequestsPerCPU، درخواست‌ها را در صف قرار می‌دهد. این مورد نیز قابل تنظیم است. ابتدا به مسیر ذیل مراجعه کرده
 %windir%\Microsoft.NET\Framework\v4.0.30319\Config\machine.config
و سپس در صورت نیاز و لزوم، مقدار requestQueueLimit را تغییر دهید:
 <processModel autoConfig="false" requestQueueLimit="250000" />
مطالب
تبدیل بلوک‌های یونیکد در زیرنویس برای نمایش در تلویزیون‌ها و پلیرها
مقدمه
موقعی که سینمای ناطق کار خود را آغاز کرد، بسیاری از مردم از آن استقبال کردند و بسیاری از سینماگران که این استقبال را دیدند، رفته رفته به سمت سینمای ناطق کشیده شدند. ولی در این بین یک مشکلی ایجاد شده بود؛ اینکه ناشنوایان دیگر مانند قدیم یعنی دوران صامت نمی‌توانستند فیلم‌ها را تماشا کنند، پس نیاز بود این مشکل به نحوی رفع شود. از اینجا بود که ایده‌ی زیرنویس شکل گرفت و این مشکل را رفع نمود. بعدها فیلم‌ها انتقال دهنده‌ی فرهنگ و پیوند دهنده‌ی مردم با فرهنگ‌های مختلف شدند ولی تفاوت در زبان باعث می‌شد که این امر به خوبی صورت نگیرد. به همین علت زیرنویس، وظیفه‌ی دیگری را هم پیدا کرد و آن رساندن پیام فیلم با زبان خود مخاطب بود. امروزه تهیه‌ی زیرنویس‌ها توسط بسیاری از افراد که با زبان انگلیسی (آشنایی با یک زبان میانی برای ترجمه زیرنویس) آشنایی دارند رواج پیدا کرده و روزانه نزدیک به صد زیرنویس یا گاها بیشتر با زبان‌های مختلف بر روی اینترنت قرار می‌گیرند. بزرگترین سایتی که در حال حاضر با شهرت جهانی در این زمینه فعالیت دارد سایت  subscene.com  است.

آشنایی با انواع زیرنویس‌ها
زیرنویس‌ها فرمت‌های مختلفی دارند مانند srt,sub idx,smi و ... ولی در حال حاضر معروف‌ترین و معتبرترین فرمت در بین همه‌ی فرمت‌ها Subrip  با پسوند SRT می‌باشد که قالب متنی به صورت زیر دارد:
203
00:16:38,731 --> 00:16:41,325
<i>Happy Christmas, your arse
I pray God it's our last</i>
که باعث میشود حجم بسیار کمی در حد چند کیلوبایت داشته باشد.

بررسی مشکل ما با زیرنویس در تلویزیون‌ها
یکی از مشکلاتی که ما در اجرای زیرنویس‌ها بر روی تلویزیون‌ها داریم این است که حروف فارسی را به خوبی نمی‌شناسند و در هنگام نمایش با مشکل مواجه می‌شوند که البته در اکثر مواقع با تبدیل زیرنویس از ANSI به Unicode یا UTF-8 مشکل حل می‌شود. ولی در بعضی مواقع تلویزیون یا پلیرها از پشتیبانی زبان فارسی سرباز می‌زنند و زیرنویس را به شکل زیر نمایش می‌دهند.
سلام = م ا ل س
به این جهت ما از یک برنامه به اسم srttouni استفاده می‌کنیم که با استفاده یک روش جایگزینی و معکوس سازی، مشکل ما را حل می‌کند. ولی باز هم این برنامه مشکلاتی دارد و از آنجا که برنامه نویس این برنامه که واقعا کمال تشکر را از ایشان، دارم مشخص نیست، مجبور شدم به جای گزارش، خودم این مشکلات را حل کنم. 
مشکلات این برنامه :
  • عدم حذف تگ‌ها ، گاها برنامه نویس‌ها از تگ هایی چون Bold,italic,underline,color استفاده می‌کنند که معدود برنامه‌هایی آن را پشتیبانی کرده و تلویزیون و پلیرها هم که اصلا پشتیبانی نمی‌کنند و باعث میشود که متن روی تلویزیون مثل کد html ظاهر شود
  • بعضی جملات دوبار روی صفحه ظاهر می‌شوند.
  • تنها یک فایل را در هر زمان تبدیل می‌کند. مثلا اگر یک سریال چند قسمته داشته باشید، برای هر قسمت باید زیرنویس را انتخاب کرده و تبدیل کنید، در صورتی که میتوان دستور داد تمام زیرنویس‌های داخل دایرکتوری را تبدیل کرد یا چند زیرنویس را برای این منظور انتخاب کرد.

نحوه‌ی خواندن زیرنویس با کدنویسی
با تشکر از دوست عزیز ما در این صفحه می‌توان گفت یک کد تقریبا خوب و جامعی را برای خواندن این قالب داریم. بار دیگر نگاهی به قالب یک دیالوگ در زیرنویس می‌اندازیم و آن را بررسی می‌کنیم:
203
00:16:38,731 --> 00:16:41,325
<i>Happy Christmas, your arse
I pray God it's our last</i>
اولین خط شامل شماره‌ی خط است که از یک آغاز می‌گردد تا به تعداد دیالوگ‌ها، خط دوم، زمان آغاز و پایان دیالوگ مورد نظر است، موقعی که دیالوگ روی صفحه ظاهر میشود تا موقعی که دیالوگ از روی صفحه محو شود که به ترتیب بر اساس ساعت:دقیقه:ثانیه و میلی ثانیه می‌باشد. خطوط بعدی هم متن دیالوگ است است و بعد از پایان متن دیالوگ یک خط خالی زیر آن قرار می‌گیرد تا نشان دهد این دیالوگ به پایان رسیده است. اگر همین خط خالی حذف گردد برنامه‌هایی چون Media player classic خطهای زیری را جز متن دیالوگ قبلی به حساب می‌آورند و شماره خط و زمان بندی دیالوگ بعدی به عنوان متن روی صفحه ظاهر می‌گردند و بعضی player‌ها هم قاطی کرده و کلا زیرنویس را نمی‌خوانند یا اون خط رو نشون نمیدن مثل Kmplayer و هر کدام رفتار خاص خودشان را بروز می‌دهند.
کد زیر در کلاس SubRipServices وظیفه‌ی خواندن محتوای فایل srt را بر اساس عبارتی که دادیم دارد:
private readonly static Regex regex_srt = new Regex(@"(?<sequence>\d+)\r\n(?<start>\d{2}\:\d{2}\:\d{2},\d{3}) --\> " +
            @"(?<end>\d{2}\:\d{2}\:\d{2},\d{3})\r\n(?<text>[\s\S]*?)\r\n\r\n", RegexOptions.Compiled);

 public string ToUnicode(string lines)
        {

        string subtitle= regex_srt.Replace(lines,delegate(Match m)
             {
                 string text = m.Groups["text"].Value;
                 //1.remove tags
                 text = CleanScriptTags(text);

                 //2.replace letters
                 PersianReshape reshaper = new PersianReshape();
                 text = reshaper.reshape(text);
                 string[] splitedlines = text.Split(new string[] { Environment.NewLine }, StringSplitOptions.None);
                 text = "";
                 foreach (string line in splitedlines)
                 {
                     //3.reverse tags
                     text += ReverseText(reshaper.reshape(line))+Environment.NewLine ;
                 }
                 return
                     string.Format("{0}\r\n{1} --> {2}\r\n", m.Groups["sequence"], m.Groups["start"].Value,
                         m.Groups["end"]) + text + Environment.NewLine+Environment.NewLine ;
             }
            );

            return subtitle;
        }
در اولین خط ما یک Regular Expersion یا یک عبارت با قاعده تعریف کردیم که در اینجا میتوانید با خصوصیات آن آشنا شوید. ما برای این کلاس یک الگو ایجاد کردیم و بر حسب این الگو، متن یک زیرنویس را خواهد گشت و خطوطی را که با این تعریف جور در می‌آیند و معتبر هستند، برای ما باز می‌گرداند.
عبارتهایی که به صورت <name>? تعریف شده‌اند در واقع یک نامگذاری برای هر قسمت از الگوی ما هستند تا بعدا این امکان برای ما فراهم شود که خطوط برگشتی را تجزیه کنیم که مثلا فقط قسمت متن را دریافت کنیم، یا فقط قسمت زمان شروع یا پایان را دریافت کنیم و ...
متد tounicode یک آرگومان متنی دارد (lines) که شامل محتویات فایل  زیرنویس است. متد Replace در شی regex_srt با هر بار پیدا کردن یک متن بر اساس الگو در رشته lines دلیگیتی را فرا می‌خواند که در اولین پارامتر آن که از نوع matchEvaluator است، شامل اطلاعات متنی است که بر اساس الگو، یافت شده است. خروجی آن از نوع string می‌باشد که با متن پیدا شده بر اساس الگو جابجا خواهد کرد و در نهایت بعد از چندین بار اجرا شدن، کل متن‌های تعویض شده، به داخل متغیر subtitle ارسال خواهند شد.
کاری که ما در اینجا می‌کنیم این است که هر دیالوگ داخل زیرنویس را بر اساس الگو، یافته و متن آن را تغییر داده و متن جدید را جایگزین متن قبلی می‌کنیم. اگر زیرنویس ما 800 دیالوگ داشته باشد این دلیگیت 800 مرتبه اجرا خواهد شد.
از آنجا که ما تنها می‌خواهیم متن زیرنویس را تغییر دهیم، در اولین خط فرامین این دلیگیت تعریف شده، متن مورد نظر را بر اساس همان گروه‌هایی که تعریف کرده‌ایم دریافت می‌کنیم و در متغیر text قرار می‌دهیم:
m.Groups["text"].Value
در مرحله‌ی بعدی ما اولین مشکلمان (حذف تگ‌ها)  را با تابعی به اسم CleanScriptTags برطرف میکنیم که کد آن به شرح زیر است:
 private static readonly Regex regex_tags = new Regex("<.*?>", RegexOptions.Compiled);
 private  string CleanScriptTags(string html)
        {
            return regex_tags.Replace(html, string.Empty);
        }
کد بالا از یک regular Expression دیگر جهت پیدا کردن تگ‌ها استفاده می‌کند و به جای آن‌ها عبارت "" را جایگزین می‌کند. این کد قبلا در سایت جاری در این صفحه توضیح داده شده است. خروجی این تابع را مجددا در text قرار می‌دهیم و به مرحله‌ی دوم، یعنی تعویض کاراکترها می‌رویم:
 PersianReshape reshaper = new PersianReshape();
                 text = reshaper.reshape(text);
                 string[] splitedlines = text.Split(new string[] { Environment.NewLine }, StringSplitOptions.None);
                 text = "";
                 foreach (string line in splitedlines)
                 {
                     //3.reverse tags
                     text += ReverseText(reshaper.reshape(line))+Environment.NewLine ;
                 }
برای اینکه دقیقا متوجه شویم قرار است چکاری انجام شود بیاید دو گروه یا بلوک مختلف در یونیکد را بررسی کنیم. هر بلوک کد در یونیکد شامل محدوده‌ای از کد پوینت هاست که نامی منحصرفرد برای خود دارد و هیچ کدام از کدپوینت‌ها در هر بلوک یا گروه، اشتراکی با بقیه‌ی بلوک‌ها ندارد. سایت codetable از آن دست سایت‌هایی است که اطلاعات خوبی در مورد کدهای یونیکد دارد. در قسمت Unicode Groups دو گروه برای زبان عربی وجود دارند که در جدول این گروه، هر سطر آن یکی از کدها را به صورت دسیمال، هگزا دسیمال و نام و نماد آن، نمایش می‌دهد.
^  ,   ^   Arabic Presentation Forms-A 
^^  Arabic Presentation Forms-B 
بلوک اول طبق گفته‌ی ویکی پدیا دسته‌ی متنوعی از حروف مورد نیاز برای زبان فارسی، اردو، پاکستانی و تعدادی از زبان‌های آسیای مرکزی است.
بلوک دوم شامل نمادها و نشانه‌های زبان عربی است و در حال حاضر برای کد کردن استفاده نمی‌شوند و دلیل حضور آن برای سازگاری با سیستم‌های قدیمی است.
اگر خوب به مشکلی که در بالا برای زیرنویس‌ها اشاره کردیم دقت کنید، گفتیم حروف از هم جدا نشان داده می‌شوند و اگر به بلوک دوم در لینک‌های داده شده نگاه کنید می‌بینید که حروف متصل را داراست. یعنی برای حرف س 4 حرف یا کدپوینت داراست : سـ برای کلماتی مثل سبد، ـس برای کلماتی مثل شانس، ـسـ برای کلماتی مثل بسیار، ولی خود س برای کلمات غیر متصل مثل ناس، البته بعضی حروف یک یا دو حالت می‌طلبند مثل د، ر که فقط دو حالت ـد و د ، ـر و ر را دارند یا مثل آ که یک حالت دارد.
من قبلا یک کلاس به نام lettersTable ایجاد کرده بودم (و دیگر نوشتن آن را ادامه ندادم) که برای هر حرف، یک آیتم در شی‌ءایی از نوع dictionary ساخته بودم و هر کدپوینت بلوک اول را در آن کلید و کد متقابلش را در بلوک دوم، به صورت مقدار ذخیره کرده بودم (گفتیم که هر نماد در بلوک اول، برابر با 4 نماد در بلوک دوم است؛ ولی ما در دیکشنری تنها مقدار اول را ذخیره می‌کنیم. زیرا کد بقیه نمادها دقیقا پشت سر یکدیگر قرار گرفته‌اند که می‌توان با یک جمع ساده از عدد 0 تا 3، به مقدار هر کدام از نمادها رسید. البته ناگفته نماند بعضی نمادها 2 عدد بودند که این هم باید بررسی شود). برای همین هر کاراکتر را با کاراکتر قبل و بعد می‌گرفتم و بررسی می‌کردم و از یک جدول دیکشنری دیگر هم به اسم specialchars هم استفاده کردم تا آن کاراکترهایی که تنها دو نماد یا یک نماد را دارند، بررسی کنم و این کاراکترها همان کاراکترهایی بودند که اگر قبل یک حرف هم بیایند، حرف بعدی به آن‌ها نمی‌چسبد. برای درک بهتر، این عبارت مثال زیر را  برای حرف س در نظر بگیرید:
مستطیل = چون بین هر دو طرف س حر وجود دارد قطعا باید شکل س به صورت ـسـ انتخاب شود ، حالا مثال زیر را در نظر بگیرید:
دست = دـست که اشتباه است و باید باشد دست یعنی شکل سـ باید صدا زده شود، پس این مورد هم باید لحاظ شود.
نمونه‌ای از کد این کلاس:
Dictionary<int ,int>  letters=new Dictionary<int, int>();

   //0=0x0 ,1=1x0 ,2=0x1 ,3=1x1
        private void FillPrimaryTable()
        {
            //آ
            letters.Add(1570, 65153);
            //ا
            letters.Add(1575, 65166);
            //أ
            letters.Add(1571, 65155);
            //ب
            letters.Add(1576, 65167);
            //ت
            letters.Add(1578, 65173);
            //ث
            letters.Add(1579, 65177);
            //ج
            letters.Add(1580, 65181);
.....
}

Dictionary<int,byte> specialchars=new Dictionary<int, byte>();

  private void SetSpecialChars()
        {
            //آ
            specialchars.Add(1570, 0);
            //ا
            specialchars.Add(1575, 0);
            //د2
            specialchars.Add(1583, 1);
            //ذ2
            specialchars.Add(1584, 1);
            //ر2
            specialchars.Add(1585, 1);
            //ز2
            specialchars.Add(1586, 1);
            //ژ
            specialchars.Add(1688, 1);
            //و2
            specialchars.Add(1608, 1);
            //أ
            specialchars.Add(1571, 1);

        }
کلاس بالا تنها برای ذخیره‌ی کدپوینت‌ها بود، ولی یک کلاس دیگر هم به اسم lettersCrawler نوشته بودم که متد آن وظیفه‌ی تبدیل را به عهده داشت.

در آن متد هر بار یک حرف را انتخاب می‌کرد و حرف قبلی و بعدی آن را ارسال می‌کرد تا تابع CalculateIncrease آن را محاسبه کرده و کاراکتر نهایی را باز گرداند و به متغیر finalText اضافه می‌کرد. ولی در حین نوشتن، زمانی را به یاد آوردم که اندروید به تازگی آمده بود و هنوز در آن زمان از زبان فارسی پشتیبانی نمی‌کرد و حروف برنامه‌هایی که می‌نوشتیم به صورت جدا از هم بود و همین مشکل را داشت که ما این مشکل را با استفاده از یک کلاس جاوا که دوست عزیزی آن را در اینجا به اشتراک گذاشته بود، حل می‌کردیم. پس به این صورت بود که از ادامه‌ی نوشتن کلاس انصراف دادم و از یک کلاس دقیق‌تر و آماده استفاده کردم.
در واقع این کلاس همین کار بالا را با روشی بهتر انجام می‌دهد. همه‌ی نمادها به طور دقیق‌تری کنترل می‌شوند حتی تنوین‌ها و دیگر علائم، همه نمادها با کدهای متناظر در یک آرایه ذخیره شده‌اند که ما در بالا از نوع Dictionary استفاده کرده بودیم.
تنها کاری که نیاز بود، باید این کد به سی شارپ تبدیل میشد و از آنجایی که این دو زبان خیلی شبیه به هم هستند، حدود ده دقیقه‌ای برای ویرایش کد وقت برد که می‌توانید کلاس نهایی را از اینجا دریافت کنید.
پس خط زیر در متد ToUnicode کار تبدیل اصلی را صورت می‌دهد:
  PersianReshape reshaper = new PersianReshape();
                 text = reshaper.reshape(text);
بنابراین مرحله‌ی دوم انجام شد. این تبدیل در بسیاری از سیستم‌ها همانند اندروید کافی است؛ ولی ما گفتیم که تلویزیون یا پلیر به غیر از جدا جدا نشان دادن حروف، آن‌ها را معکوس هم نشان می‌دهند. پس باید در مرحله‌ی بعد آن‌ها را معکوس کنیم که اینکار با خط زیر و صدا زدن تابع ReverseText انجام میگیرد
 //3.reverse tags
                 text = ReverseText(text);
از آنجا که یک دیالوگ ممکن است چند خطی باشد، این معکوس سازی برای ما دردسر می‌شد و ترتیب خطوط هم معکوس می‌شد. پس ما با استفاده از کد زیر هر یک خط را شکسته و هر کدام را جداگانه معکوس می‌کنیم و سپس به یکدیگر می‌چسبانیم:
string[] splitedlines = text.Split(new string[] { Environment.NewLine }, StringSplitOptions.None);
                 text = "";
                 foreach (string line in splitedlines)
                 {
                     //3.reverse tags
                     text += ReverseText(reshaper.reshape(line))+Environment.NewLine ;
                 }
همه‌ی ما معکوس سازی یک رشته را بلدیم، یکی از روش‌ها این است که رشته را خانه به خانه از آخر به اول با یک for بخوانیم یا اینکه رشته را به آرایه‌ای از کارکاکترها، تبدیل کنیم و سپس با Array.Reverse آن را معکوس کرده و خانه به خانه به سمت جلو بخوانیم و خیلی از روش‌های دیگر. ولی این معکوس سازی‌ها برای ما یک عیب هم دارد و این هست که این معکوس سازی روی نمادهایی چون . یا ! و  غیره که در ابتدا و انتهای رشته آمده‌اند و حروف انگلیسی، نباید اتفاق بیفتند. پس می‌بینیم که تابع معکوس سازی هم باز باید ویژه‌تر باشد. ابتدا قسمت‌های ابتدا و انتها را جدا کرده و از آن حذف می‌کنیم. سپس رشته را معکوس می‌کنیم. ولی ممکن هست و احتمال دارد که بین حروف فارسی هم حروف انگلیسی یا اعداد به کار رود که آن‌ها هم معکوس می‌شوند. برای همین بعد از معکوس سازی یکبار هم باید آن‌ها را با یک عبارت با قاعده یافته و سپس هر کدام را جداگانه معکوس کرده و سپس مثل روش بالا Replace کنیم و رشته‌های جدا شده را به ابتدا و انتهای آن، سر جای قبلیشان می‌چسبانیم.
این دو تابع برای معکوس کردن عادی یک رشته به کار می‌روند:
    private string Reverse(string text)
        {
            return Reverse(text,0,text.Length);
        }

        private string Reverse(string text,int start,int end)
        {
            if (end < start)
                return text;
            string reverseText = "";

            for (int i = end-1; i >=start; i--)
            {
                reverseText += text[i];
            }
            return reverseText;
        }
ولی این تابع ReverseText جمعی از عملیات معکوس سازی ویژه‌ی ماست؛ مرحله اول، مرحله دریافت و ذخیره‌ی حروف خاص در ابتدای رشته به اسم پیشوند prefix است:
  private string ReverseText(string text)
        {
            char[] chararray = text.ToCharArray();
            string reverseText = "";
            bool prefixcomp = false;
            bool postfixcomp = false;
            string prefix = "";
            string postfix = "";

            #region get prefix symbols
            for (int i = 0; i < chararray.Length; i++)
            {
                if (!prefixcomp)
                {
                    char ch =(char) chararray.GetValue(i) ;
                    if (ch< 130)
                    {
                        prefix += chararray.GetValue(i);
                    }
                    else
                    {
                        prefixcomp = true;
                        break;
                    }
                }
            }
            #endregion
}
مرحله‌ی دوم هم دریافت و ذخیره‌ی حروف خاص در انتهای رشته به اسم پسوند postfix است که به این تابع اضافه می‌کنیم:
 #region get postfix symbols
            for (int i = chararray.Length - 1; i >-1 ; i--)
            {
                if (!postfixcomp && prefix.Length!=text.Length)
                {
                    char ch = (char)chararray.GetValue(i);
                    if (ch < 130)
                    {
                        postfix += chararray.GetValue(i);
                    }
                    else
                    {
                        postfixcomp = true;
                        break;
                    }
                }
            }
            #endregion
مرحله‌ی سوم عملیات معکوس سازی روی رشته است و سپس با استفاده از یک Regular Expression حروف انگلیسی و اعداد بین حروف فارسی را یافته و یک معکوس سازی هم روی آن‌ها انجام می‌دهیم تا به حالت اولشان برگردند. کل عملیات معکوس سازی در اینجا به پایان می‌رسد:
  #region reverse text

            reverseText = Reverse(text, prefix.Length, text.Length-postfix.Length);

        
            reverseText = unTagetdLettersRegex.Replace(reverseText, delegate(Match m)
            {
                return Reverse(m.Value);
            });
            #endregion
تعریف عبارت با قاعده‌ی بالا به اسم unTargetedLetters:
private static readonly Regex unTagetdLettersRegex = new Regex(@"[A-Za-z0-9]+", RegexOptions.Compiled);
آخر سر هم رشته را به‌علاوه پیشوند و پسوند جدا شده بر می‌گردانیم:
return prefix+ reverseText+postfix;
کد کامل تابع بدین شکل در می‌آید:
private static readonly Regex unTagetdLettersRegex = new Regex(@"[A-Za-z0-9]+", RegexOptions.Compiled);
private string ReverseText(string text)
        {
            char[] chararray = text.ToCharArray();
            string reverseText = "";
            bool prefixcomp = false;
            bool postfixcomp = false;
            string prefix = "";
            string postfix = "";

            #region get prefix symbols
            for (int i = 0; i < chararray.Length; i++)
            {
                if (!prefixcomp)
                {
                    char ch =(char) chararray.GetValue(i) ;
                    if (ch< 130)
                    {
                        prefix += chararray.GetValue(i);
                    }
                    else
                    {
                        prefixcomp = true;
                        break;
                    }
                }
            }
            #endregion

            #region get postfix symbols
            for (int i = chararray.Length - 1; i >-1 ; i--)
            {
                if (!postfixcomp && prefix.Length!=text.Length)
                {
                    char ch = (char)chararray.GetValue(i);
                    if (ch < 130)
                    {
                        postfix += chararray.GetValue(i);
                    }
                    else
                    {
                        postfixcomp = true;
                        break;
                    }
                }
            }
            #endregion

            #region reverse text

            reverseText = Reverse(text, prefix.Length, text.Length-postfix.Length);

        
            reverseText = unTagetdLettersRegex.Replace(reverseText, delegate(Match m)
            {
                return Reverse(m.Value);
            });
            #endregion

          

            return prefix+ reverseText+postfix;
        }
در نهایت، خط آخر دلیگت همه چیز را طبق فرمت یک دیالوگ srt چینش کرده و بر می‌گردانیم.
return
                     string.Format("{0}\r\n{1} --> {2}\r\n", m.Groups["sequence"], m.Groups["start"].Value,
                         m.Groups["end"]) + text + Environment.NewLine+Environment.NewLine ;
رشته subtitle را به صورت srt ذخیره کرده و انکودینگ را هم Unicode انتخاب کنید و تمام.

نمایی از برنامه‌ی نهایی


اجرای زیرنویس تبدیل شده روی کامپیوتر


روی پلیر یا تلویزیون



  نکته‌ی نهایی: هنگام تست زیرنویس روی فیلم متوجه شدم پلیر خطوط بلند را که در صفحه‌ی نمایش جا نمی‌شود، می‌شکند و به دو خط تقسیم می‌کند. ولی نکته‌ی خنده دار اینجا بود که خط اول را پایین می‌اندازد و خط دوم را بالا. برای همین این تکه کد را نوشتم و به طور جداگانه در گیت هاب هم قرار داده‌ام.
 
این تکه کد را هم بعد از
//1.remove tags
                 text = CleanScriptTags(text);
 به برنامه اضافه می‌کنیم:
  text =StringUtils.ConvertToMultiLine(text);
از این پس خطوط به طولی بین 30 کاراکتر تا چهل کاراکتر  شکسته خواهند شد و مشکل خطوط بلند هم نخواهیم داشت.
کد متد ConvertToMultiline:
namespace Utils
{
    public static class StringUtils
    {
        public static string ConvertToMultiLine(String text, int min = 30, int max = 40)
        {
            if (text.Trim() == "")
                return text;

            string[] words = text.Split(new string[] { " " }, StringSplitOptions.None);

            string text1 = "";
            string text2 = "";
            foreach (string w in words)
            {
                if (text1.Length < min)
                {
                    if (text1.Length == 0)
                    {
                        text1 = w;
                        continue;
                    }

                    if (w.Length + text1.Length <= max)
                        text1 += " " + w;
                }
                else
                    text2 += w + " ";

            }
            text1 = text1.Trim();
            text2 = text2.Trim();
            if (text2.Length > 0)
            {
                text1 += Environment.NewLine + ConvertToMultiLine(text2, min, max);
            }
            return text1;
        }
      
    }
}
آرگومان‌های min و max که به طور پیش فرض 30 و 40 هستند، سعی می‌کنند که هر خط را در نهایت به طور حدودی بین 30 تا 40 کاراکتر نگه دارند.
نکته پایانی : خوشحال میشم دوستان در این پروژه مشارکت داشته باشند و اگر جایی نیاز به اصلاح، بهبود یا ایجاد امکانی جدید دارد  کمک حال باشند و سعی کنند تا آنجا که می‌شود برنامه را روی net frame work 2. نگه دارند و بالاتر نبرند. چون استفاده کننده‌های این برنامه کاربران عادی و گاها با دانش پایین هستند و خیلی از آن‌ها هنوز از ویندوز xp استفاده می‌کنند تا در اجرای برنامه خیلی دچار مشکل نشده و راحت برای بسیاری از آن‌ها اجرا شود.

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

خدمت دوستان عزیز مطلبی را عرض کنم که البته باید در ابتدای این سری مقالات متذکر می‌شدم. این سری مقالات Dart مرجع کاملی برای یادگیری Dart نمی‌باشد. فقط یک Quick Start یا Get Started محسوب می‌شود برای آشنایی مقدماتی با ساختار Dart. از عنوان مقاله هم این موضوع قابل درک و تشخیص می‌باشد. همچنین فرض شده است که دوستان آشنایی مقدماتی با جاوااسکریپت و مباحث شی گرایی را نیز دارند. البته اگر مشغله کاری به بنده این اجازه را بدهد، مطالب جامع‌تری را در این زمینه آماده و منتشر می‌کنم.

گام پنجم: ذخیره سازی اطلاعات در فضای محلی یا Local
در این گام، تغییرات badge را در فضای ذخیره سازی سیستم Local نگهداری می‌نماییم؛ بطوری که اگر دوباره برنامه را راه اندازی نمودید، badge با داده‌های ذخیره شده در سیستم Local مقداردهی اولیه می‌گردد.
کتابخانه dart:convert را به منظور استفاده از کلاس مبدل JSON به فایل piratebadge.dart اضافه نمایید.
import 'dart:html';
import 'dart:math' show Random;

import 'dart:convert' show JSON;
همچنین یک Named Constructor یا سازنده‌ی با نام را به کلاس PirateName بصورت زیر اضافه کنید.
class PirateName {
  ...
  PirateName.fromJSON(String jsonString) {
    Map storedName = JSON.decode(jsonString);
    _firstName = storedName['f'];
    _appellation = storedName['a'];
  }
}
توضیحات
- جهت کسب اطلاعات بیشتر در مورد Json به این لینک مراجعه نمایید
- کلاس JSON جهت کار با داده هایی به فرمت Json استفاده می‌شود که امکاناتی را جهت دسترسی سریعتر و راحت‌تر به این داده‌ها فراهم می‌کند.
- سازنده‌ی PirateName.fromJSON، از یک رشته حاوی داده‌ی Json، یک نمونه از کلاس PirateName ایجاد می‌کند.
- سازنده‌ی PirateName.fromJSON، یک Named Constructor می‌باشد. این نوع سازنده‌ها دارای نامی متفاوت از نام سازنده‌های معمول هستند و بصورت خودکار نمونه ای از کلاس مورد نظر را ایجاد نموده و به عنوان خروجی بر می‌گردانند.
- تابع JSON.decode یک رشته‌ی حاوی داده‌ی Json را تفسیر نموده و اشیاء Dart را از آن ایجاد می‌کند.
یک Getter به کلاس PirateName اضافه کنید که مقادیر ویژگی‌های آن را به یک رشته Json تبدیل می‌کند
class PirateName {
  ...
  String get jsonString => JSON.encode({"f": _firstName, "a": _appellation});
}
جهت ذخیره سازی آخرین تغییرات کلاس PirateName در فضای ذخیره سازی Local، از یک کلید استفاده می‌کنیم که مقدار آن محتوای PirateName می‌باشد. در واقع فضای ذخیره سازی Local داده‌ها را به صورت جفت کلید-مقدار یا Key-Value Pairs نگهداری می‌نماید. جهت تعریف کلید، یک متغیر رشته ای را بصورت top-level و به شکل زیر تعریف کنید.
final String TREASURE_KEY = 'pirateName';

void main() {
  ...
}
زمانیکه تغییری در badge name صورت گرفت، این تغییرات را در فضای ذخیره سازی Local، توسط ویژگی window.localStorage ذخیره می‌نماییم. تغییرات زیر را در تابع setBadgeName اعمال نمایید
void setBadgeName(PirateName newName) {
  if (newName == null) {
    return;
  }
  querySelector('#badgeName').text = newName.pirateName;
  window.localStorage[TREASURE_KEY] = newName.jsonString;
}
تابع getBadgeNameFromStorage را بصورت top-level تعریف نمایید. این تابع داده‌های ذخیره شده را از Local Storage بازیابی نموده و یک شی از نوع کلاس PirateName ایجاد می‌نماید.
void setBadgeName(PirateName newName) {
  ...
}

PirateName getBadgeNameFromStorage() {
  String storedName = window.localStorage[TREASURE_KEY];
  if (storedName != null) {
    return new PirateName.fromJSON(storedName);
  } else {
    return null;
  }
}
در پایان نیز تابع setBadgeName را به منظور مقدار دهی اولیه به badge name، در تابع main، فراخوانی می‌نماییم.
void main() {
  ...
  setBadgeName(getBadgeNameFromStorage());
}
حال به مانند گامهای قبل برنامه را اجرا و بررسی نمایید.

گام ششم: خواندن نام‌ها از فایل‌های ذخیره شده به فرمت Json
در این گام کلاس PirateName را به گونه‌ای تغییر می‌دهیم که نام‌ها را از فایل Json بخواند. این عمل موجب می‌شود تا به راحتی اسامی مورد نظر را به فایل اضافه نمایید تا توسط کلاس خوانده شوند، بدون آنکه نیاز باشد کد کلاس را مجددا دستکاری کنید.
به منوی File > New File... مراجعه نموده و فایل piratenames.json را با محتوای زیر ایجاد نمایید. این فایل را در پوشه 1-blankbadge و در کنار فایلهای HTML و Dart ایجاد کنید.
{ "names": [ "Anne", "Bette", "Cate", "Dawn",
        "Elise", "Faye", "Ginger", "Harriot",
        "Izzy", "Jane", "Kaye", "Liz",
        "Maria", "Nell", "Olive", "Pat",
        "Queenie", "Rae", "Sal", "Tam",
        "Uma", "Violet", "Wilma", "Xana",
        "Yvonne", "Zelda",
        "Abe", "Billy", "Caleb", "Davie",
        "Eb", "Frank", "Gabe", "House",
        "Icarus", "Jack", "Kurt", "Larry",
        "Mike", "Nolan", "Oliver", "Pat",
        "Quib", "Roy", "Sal", "Tom",
        "Ube", "Val", "Walt", "Xavier",
        "Yvan", "Zeb"],
  "appellations": [ "Awesome", "Captain",
        "Even", "Fighter", "Great", "Hearty",
        "Jackal", "King", "Lord",
        "Mighty", "Noble", "Old", "Powerful",
        "Quick", "Red", "Stalwart", "Tank",
        "Ultimate", "Vicious", "Wily", "aXe", "Young",
        "Brave", "Eager",
        "Kind", "Sandy",
        "Xeric", "Yellow", "Zesty"]}
این فایل شامل یک شی Json با دو لیست رشته ای می‌باشد.
به فایل piratebadge.html مراجعه نمایید و فیلد input و المنت button را غیر فعال نمایید.
...
  <div>
    <input type="text" id="inputName" maxlength="15" disabled>
  </div>
  <div>
    <button id="generateButton" disabled>Aye! Gimme a name!</button>
  </div>
...
این دو المنت پس از اینکه تمامی نام‌ها از فایل Json با موفقیت خوانده شدند فعال می‌گردند.
کتابخانه dart:async را در ابتدای فایل دارت import نمایید
import 'dart:html';
import 'dart:math' show Random;
import 'dart:convert' show JSON;

import 'dart:async' show Future;
توضیحات
- کتابخانه dart:async برنامه نویسی غیر همزمان یا asynchronous را فراهم می‌کند
- کلاس Future روشی را ارئه می‌کند که در آن مقادیر مورد نیاز در آینده ای نزدیک و به صورت غیر همزمان واکشی خواهند شد.
در مرحله بعد لیست‌های names و appellations را با کد زیر بصورت یک لیست خالی جایگزین نمایید.
class PirateName {
  ...
  static List<String> names = [];
  static List<String> appellations = [];
  ...
}
توضیحات
- مطمئن شوید که کلمه کلیدی final را از تعاریف فوق حذف نموده اید
- [] معادل new List() می‌باشد
- کلاس List یک نوع Generic می‌باشد که می‌تواند شامل هر نوع شی ای باشد. اگر می‌خواهید که لیست شما فقط شامل داده هایی از نوع String باشد، آن را بصورت List<String> تعریف نمایید.
دو تابع static را بصورت زیر به کلاس PirateName اضافه نمایید
class PirateName {
  ...

  static Future readyThePirates() {
    var path = 'piratenames.json';
    return HttpRequest.getString(path)
        .then(_parsePirateNamesFromJSON);
  }
  
  static _parsePirateNamesFromJSON(String jsonString) {
    Map pirateNames = JSON.decode(jsonString);
    names = pirateNames['names'];
    appellations = pirateNames['appellations'];
  }
}
توضیحات
- کلاس HttpRequest یک Utility می‌باشد که داده‌ها را از یک آدرس یا URL خاص واکشی می‌نماید.
- تابع getString یک درخواست را به صورت GET ارسال می‌نماید و رشته ای را بر می‌گرداند
- در کد فوق از کلاس Future استفاده شده است که موجب می‌شود درخواست GET بصورت غیر همزمان ارسال گردد.
- زمانیکه Future با موفقیت خاتمه یافت، تابع then فراخوانی می‌شود. پارامتر ورودی این تابع، یک تابع می‌باشد که پس از خاتمه درخواست GET اجرا خواهد شد. به این نوع توابع که پس از انجام یک عملیات خاص بصورت خودکار اجرا می‌شوند توابع CallBack می‌گویند.
- زمانیکه Future با موفقیت خاتمه یافت، اسامی از فایل Json خوانده خواهند شد
- تابع readyThePirates دارای نوع خروجی Future می‌باشد بطوری که برنامه اصلی در زمانی که فایلها در حال خوانده شدن هستند، به کار خود ادامه میدهد و متوقف نخواهد شد
یک متغیر top-level از نوع SpanElement در کلاس PirateName ایجاد کنید.
SpanElement badgeNameElement;

void main() {
  ...
}
تغییرات زیر را در تابع main ایجاد کنید.
void main() {
  InputElement inputField = querySelector('#inputName');
  inputField.onInput.listen(updateBadge);
  genButton = querySelector('#generateButton');
  genButton.onClick.listen(generateBadge);
  
  badgeNameElement = querySelector('#badgeName');
  ...
}
کد زیر را نیز به منظور خواندن نام‌ها از فایل Json اضافه کنید. در این کد اجرای موفقیت آمیز درخواست و عدم اجرای درخواست، هر دو به شکلی مناسب مدیریت شده اند. 
void main() {
  ...
  
  PirateName.readyThePirates()
    .then((_) {
      //on success
      inputField.disabled = false; //enable
      genButton.disabled = false;  //enable
      setBadgeName(getBadgeNameFromStorage());
    })
    .catchError((arrr) {
      print('Error initializing pirate names: $arrr');
      badgeNameElement.text = 'Arrr! No names.';
    });
}
توضیحات
- تابع readyThePirates فراخوانی شده است که یک Future بر می‌گرداند.
- زمانی که Future با موفقیت خاتمه یافت تابع CallBack موجود در تابع then فراخوانی می‌شود.
- (_) به عنوان پارامتر ورودی تابع then ارسال شده است، به این معنا که از پارامتر ورودی صرف نظر شود.
- تابع then المنت‌های صفحه را فعال می‌کند و داده‌های ذخیره شده را بازیابی می‌نماید
- اگر Future با خطا مواجه شود، توسط تابع catchError که یک تابع CallBack می‌باشد، پیغام خطایی را نمایش می‌دهیم.
برنامه را به مانند گامهای قبل اجرا نموده و نتیجه را مشاهده نمایید
مطالب
Ajax.BeginForm و ارسال فایل به سرور در ASP.NET MVC
Ajax.BeginForm در ASP.NET MVC از jQuery Ajax برای ارسال مقادیر فرم، به سرور استفاده می‌کند. در این بین اگر یکی از عناصر فرم، المان ارسال فایل به سرور باشد، مقدار دریافتی در سمت سرور نال خواهد بود. مشکل اینجا است که نمی‌توان به کمک Ajax معمولی (یا به عبارتی XMLHttpRequest) فایلی را به سرور ارسال کرد. یا باید از سیلورلایت یا فلش استفاده نمود و یا از مرورگرهایی که XMLHttpRequest Level 2 را پشتیبانی می‌کنند (از IE 10 به بعد مثلا) که امکان Ajax upload توکار به همراه گزارش درصد آپلود را بدون نیاز به فلش یا سیلورلایت، دارند.
در این بین راه حل دیگری نیز وجود دارد که با تمام مرورگرها سازگار است؛ اما تنها گزارش درصد آپلود را توسط آن نخواهیم داشت. در اینجا به صورت پویا یک IFrame مخفی در صفحه تشکیل می‌شود، مقادیر معمولی فرم (تمام المان‌ها، منهای file) به صورت Ajax ایی به سرور ارسال خواهند شد. المان file آن در این IFrame مخفی، به صورت معمولی به سرور Postback می‌شود. البته کاربر در این بین چیزی را مشاهده یا احساس نخواهد کرد و تمام عملیات از دیدگاه او Ajax ایی به نظر می‌رسد. برای انجام اینکار تنها کافی است از افزونه‌ی AjaxFileUpload استفاده کنیم که در ادامه نحوه‌ی استفاده از آن‌را بررسی خواهیم کرد.


پیشنیازها

در ادامه فرض بر این است که افزونه‌ی AjaxFileUpload را دریافت کرده و به فایل Layout برنامه افزوده‌اید:
<!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="~/Content/Site.css" rel="stylesheet" type="text/css" />
</head>
<body>
    <div>
        @RenderBody()
    </div>

    <script src="~/Scripts/jquery-1.11.1.min.js"></script>
    <script src="~/Scripts/jquery.unobtrusive-ajax.js"></script>
    <script src="~/Scripts/ajaxfileupload.js"></script>
    @RenderSection("Scripts", required: false)
</body>
</html>


مدل، کنترلر و View برنامه

مدل برنامه مشخصات یک محصول است:
namespace MVCAjaxFormUpload.Models
{
    public class Product
    {
        public int Id { set; get; }
        public string Name { set; get; }
    }
}

کنترلر آن از سه متد تشکیل شده‌است:
using System.Threading;
using System.Web;
using System.Web.Mvc;
using MVCAjaxFormUpload.Models;

namespace MVCAjaxFormUpload.Controllers
{
    public class HomeController : Controller
    {
        public ActionResult Index()
        {
            return View();
        }

        [HttpPost]
        public ActionResult Index(Product product)
        {
            var isAjax = this.Request.IsAjaxRequest();

            return Json(new { result = "ok" }, JsonRequestBehavior.AllowGet);
        }

        [HttpPost]
        public ActionResult UploadFiles(HttpPostedFileBase image1, int id)
        {
            var isAjax = this.Request.IsAjaxRequest();
            Thread.Sleep(3000); //شبیه سازی عملیات طولانی
            return Json(new { FileName = "/Uploads/filename.ext" }, "text/html", JsonRequestBehavior.AllowGet);
        }
    }
}
Index اول، کار نمایش صفحه‌ی ارسال اطلاعات را انجام خواهد داد.
Index دوم کار پردازش Ajax ایی اطلاعات ارسالی به سرور را به عهده دارد. HttpPost آن Ajax ایی است.
متد UploadFiles، کار پردازش اطلاعات ارسالی از طرف IFrame مخفی را انجام می‌دهد. HttpPost آن معمولی است.

و کدهای View این مثال نیز به شرح زیر است:
@model MVCAjaxFormUpload.Models.Product
@{
    ViewBag.Title = "Index";
}

<h2>Ajax Form Upload</h2>

@using (Ajax.BeginForm(actionName: "Index",
                       controllerName: "Home",
                       ajaxOptions: new AjaxOptions { HttpMethod = "POST" },
                       routeValues: null,
                       htmlAttributes: new { id = "uploadForm" }))
{
    <label>Name:</label>
    @Html.TextBoxFor(model => model.Name)
    <br />
    <label>Image:</label>
    <br />
    <input type="file" name="Image1" id="Image1" />
    <br />
    <input type="submit" value="Submit" />
    <img id="loading" src="~/Content/Images/loading.gif" style="display:none;">
}

@section Scripts
{
    <script type="text/javascript">
        $(function () {
            $('#uploadForm').submit(function () {
                $("#loading").show();
                $.ajaxFileUpload({
                    url: "@Url.Action("UploadFiles", "Home")", // مسیری که باید فایل به آن ارسال شود
                    secureuri: false,
                    fileElementId: 'Image1', // آی دی المان ورودی فایل
                    dataType: 'json',
                    data: { id: 1, data: 'test' }, // اطلاعات اضافی در صورت نیاز
                    success: function (data, status) {
                        $("#loading").hide();
                        if (typeof (data.FileName) != 'undefined') {
                            alert(data.FileName);
                        }
                    },
                    error: function (data, status, e) {
                        $("#loading").hide();
                        alert(e);
                    }
                });
            });
        });
    </script>
}
فرمی که توسط Ajax.BeginForm تشکیل شده‌است، یک فرم معمولی Ajax ایی است و نکته‌ی جدیدی ندارد. تنها در آن یک المان ارسال فایل قرار گرفته‌است و همچنین Id آن‌را نیز جهت استفاده توسط jQuery مشخص کرده‌ایم.
در ادامه نحوه‌ی فعال سازی ajaxFileUpload را دقیقا در زمان submit فرم، مشاهده می‌کنید. در اینجا url آن به اکشن متدی که اطلاعات المان file را باید دریافت کند، اشاره می‌کند. fileElementId آن مساوی Id المان فایل فرم Ajax ایی صفحه‌است. از قسمت data جهت ارسال اطلاعات اضافه‌تری به اکشن متد UploadFiles استفاده می‌شود. سایر قسمت‌های آن نیز مشخص هستند. اگر عملیات موفقیت آمیز بود، success آن و اگر خیر، error آن اجرا می‌شوند.
فقط باید دقت داشت که content type دریافتی توسط آن باید text/html باشد، که این مورد در اکشن متدهای کنترلر مشخص هستند.
به این ترتیب دیگر کاربر نیازی ندارد ابتدا یکبار بر روی دکمه‌ی دومی کلیک کرده و فایل را ارسال کند و سپس بار دیگر بر روی دکمه‌ی submit فرم کلیک نماید. هر دو کار توسط یک دکمه انجام می‌شوند.

کدهای کامل این مثال را از اینجا می‌توانید دریافت کنید
MVCAjaxFormUpload.zip
مطالب
توسعه کنترلر و مدل در F# MVC4
در پست قبلی با F# MVC4 Template آشنا شدید. در این پست به توسعه کنترلر و مدل در قالب مثال خواهم پرداخت. برای شروع ابتدا یک پروژه همانند مثال ذکر شده در پست قبلی ایجاد کنید. در پروژه #C ساخته شده که صرفا برای مدیریت View‌ها است یک View جدید به صورت زیر ایجاد نمایید:
@model IEnumerable<FsWeb.Models.Book>
<!DOCTYPE html>
<html>
<head>
    <title>@ViewBag.Title</title>
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <link rel="stylesheet" 
        href="http://code.jquery.com/mobile/1.0.1/jquery.mobile-1.0.1.min.css" />
</head>
 
<body>
    <div data-role="page" data-theme="a" id="booksPage">
        <div data-role="header">
            <h1>Guitars</h1>
        </div>
        <div data-role="content">
        <ul data-role="listview" data-filter="true" data-inset="true"> 
            @foreach(var x in Model) {
                <li><a href="#">@x.Name</a></li>
            }
        </ul>
        </div>
    </div>
    <script src="http://code.jquery.com/jquery-1.6.4.min.js">
    </script>
    <script src="http://code.jquery.com/mobile/1.0.1/jquery.mobile-1.0.1.min.js">
    </script>

    <script>
        $(document).delegate("#bookPage", 'pageshow', function (event) {
            $("div:jqmData(role='content') > ul").listview('refresh');
        });
    </script>
</body>
</html>
از آنجا که هدف از این پست آشنایی با بخش #F پروژه‌های وب است در نتیجه نیازی به توضیح کد‌های بالا دیده نمی‌شود. 
برای ساخت کنترلر جدید، در پروژه #F ساخته شده یک Source File ایجاد نمایید و کد‌های زیر را در آن کپی نمایید:
namespace FsWeb.Controllers

open System.Web.Mvc
open FsWeb.Models

[<HandleError>]
type BooksController() =
    inherit Controller()
    member this.Index () =   
        seq { yield  Book(Name = "My F# Book")
              yield Book(Name = "My C# Book") }
        |> this.View
در کد‌های بالا ابتدا یک کنترلر به نام BookController ایجاد کردیم که از کلاس Controller ارث برده است(با استفاده از inherit).  سپس یک تابع به نام Index داریم(به عنوان Action مورد نظر در کنترلر) که آرایه ای از کتاب‌ها به عنوان پارامتر به تابع View می‌فرستد.( توسط اپراتور Pipe - <|). در نهایت دستور this.View  معادل فراخوانی اکشن ()View در پروژه‌های #C است که View متناظر با اکشن را فراخوانی می‌کند. همان طور که ملاحظه می‌نمایید بسیار شبیه به پیاده سازی #C است.
اما نکته ای که در مثال بالا وجود دارد این است که دو نمونه از نوع Book را برای ساخت seq وهله سازی می‌کند. در نتیجه باید Book Type را به عنوان مدل تعریف کنیم. به صورت زیر:
namespace FsWeb.Models
 type Book = { Id : Guid; Name : string }
البته در F# 3.0 امکانی فراهم شده است به نام Auto-Properties که شبیه تعریف خواص در #C است. در نتیجه می‌توان تعریف بالا را به صورت زیر نیز بازنویسی کرد:
namespace FsWeb.Models 
type Book() = member val Name = "" with get, set
Attribute‌ها مدل
اگر همچون پروژه‌های #C قصد دارید با استفاده از Attribute‌ها مدل خود را اعتبارسنجی نمایید می‌توانید به صورت زیر اقدام نمایید:
open System.ComponentModel.DataAnnotations
 type Book() = [<Required>] member val Name = "" with get, set
هم چنین می‌توان Attribute‌های مورد نظر برای مدل EntityFramework را نیز اعمال نمود(نظیر Key):
namespace FsWeb.Models 
open System 
open System.ComponentModel.DataAnnotations 
type Book() = [<Key>] member val Id = Guid.NewGuid() with get, set 
[<Required>] member val Name = "" with get, set

نکته: دستور open معادل با using در #C است.
در پست بعدی برای تکمیل مثال جاری، روش طراحی Repository با استفاده از EntityFramework بررسی خواهد شد.
مطالب
استفاده از Web API در ASP.NET Web Forms
گرچه ASP.NET Web API بهمراه ASP.NET MVC بسته بندی شده و استفاده می‌شود، اما اضافه کردن آن به اپلیکیشن‌های ASP.NET Web Forms کار ساده ای است. در این مقاله مراحل لازم را بررسی می‌کنیم.

برای استفاده از Web API در یک اپلیکیشن ASP.NET Web Forms دو قدم اصلی باید برداشته شود:

  • اضافه کردن یک کنترلر Web API که از کلاس ApiController مشتق می‌شود.
  • اضافه کردن مسیرهای جدید به متد Application_Start.


یک پروژه Web Forms بسازید

ویژوال استودیو را اجرا کنید و پروژه جدیدی از نوع ASP.NET Web Forms Application ایجاد کنید.


کنترلر و مدل اپلیکیشن را ایجاد کنید

کلاس جدیدی با نام Product بسازید و خواص زیر را به آن اضافه کنید.

public class Product
{
    public int Id { get; set; }
    public string Name { get; set; }
    public decimal Price { get; set; }
    public string Category { get; set; }
}
همانطور که مشاهده می‌کنید مدل مثال جاری نمایانگر یک محصول است. حال یک کنترلر Web API به پروژه اضافه کنید. کنترلر‌های Web API درخواست‌های HTTP را به اکشن متدها نگاشت می‌کنند. در پنجره Solution Explorer روی نام پروژه کلیک راست کنید و گزینه Add, New Item را انتخاب کنید.

در دیالوگ باز شده گزینه Web را از پانل سمت چپ کلیک کنید و نوع آیتم جدید را Web API Controller Class انتخاب نمایید. نام این کنترلر را به "ProductsController" تغییر دهید و OK کنید.

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

namespace WebForms
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Net;
    using System.Net.Http;
    using System.Web.Http;

    public class ProductsController : ApiController
    {

        Product[] products = new Product[] 
        { 
            new Product { Id = 1, Name = "Tomato Soup", Category = "Groceries", Price = 1 }, 
            new Product { Id = 2, Name = "Yo-yo", Category = "Toys", Price = 3.75M }, 
            new Product { Id = 3, Name = "Hammer", Category = "Hardware", Price = 16.99M } 
        };

        public IEnumerable<Product> GetAllProducts()
        {
            return products;
        }

        public Product GetProductById(int id)
        {
            var product = products.FirstOrDefault((p) => p.Id == id);
            if (product == null)
            {
                throw new HttpResponseException(HttpStatusCode.NotFound);
            }
            return product;
        }

        public IEnumerable<Product> GetProductsByCategory(string category)
        {
            return products.Where(
                (p) => string.Equals(p.Category, category,
                    StringComparison.OrdinalIgnoreCase));
        }
    }
}
کنترلر جاری لیستی از محصولات را بصورت استاتیک در حافظه محلی نگهداری می‌کند. متدهایی هم برای دریافت لیست محصولات تعریف شده اند.


اطلاعات مسیریابی را اضافه کنید

مرحله بعدی اضافه کردن اطلاعات مسیریابی (routing) است. در مثال جاری می‌خواهیم آدرس هایی مانند "api/products/" به کنترلر Web API نگاشت شوند. فایل Global.asax را باز کنید و عبارت زیر را به بالای آن اضافه نمایید.

using System.Web.Http;
حال کد زیر را به متد Application_Start اضافه کنید.
RouteTable.Routes.MapHttpRoute(
    name: "DefaultApi",
    routeTemplate: "api/{controller}/{id}",
    defaults: new { id = System.Web.Http.RouteParameter.Optional }
    );

برای اطلاعات بیشتر درباره مسیریابی در Web API به این لینک مراجعه کنید.


دریافت اطلاعات بصورت آژاکسی در کلاینت

تا اینجا شما یک API دارید که کلاینت‌ها می‌توانند به آن دسترسی داشته باشند. حال یک صفحهHTML خواهیم ساخت که با استفاده از jQuery سرویس را فراخوانی می‌کند. صفحه Default.aspx را باز کنید و کدی که بصورت خودکار در قسمت Content تولید شده است را حذف کرده و کد زیر را به این قسمت اضافه کنید:

<%@ Page Title="Home Page" Language="C#" MasterPageFile="~/Site.Master" 
    AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="WebForms._Default" %>

<asp:Content ID="HeaderContent" runat="server" ContentPlaceHolderID="HeadContent">
</asp:Content>

<asp:Content ID="BodyContent" runat="server" ContentPlaceHolderID="MainContent">
    <h2>Products</h2>
    <table>
    <thead>
        <tr><th>Name</th><th>Price</th></tr>
    </thead>
    <tbody id="products">
    </tbody>
    </table>
</asp:Content>
حال در قسمت HeaderContent کتابخانه jQuery را ارجاع دهید.
<asp:Content ID="HeaderContent" runat="server" ContentPlaceHolderID="HeadContent">
    <script src="Scripts/jquery-1.7.1.min.js" type="text/javascript"></script>
</asp:Content>

همانطور که می‌بینید در مثال جاری از فایل محلی استفاده شده است اما در اپلیکیشن‌های واقعی بهتر است از CDN‌‌ها استفاده کنید.

نکته: برای ارجاع دادن اسکریپت‌ها می‌توانید بسادگی فایل مورد نظر را با drag & drop به کد خود اضافه کنید.

زیر تگ jQuery اسکریپت زیر را اضافه کنید.

<script type="text/javascript">
    function getProducts() {
        $.getJSON("api/products",
            function (data) {
                $('#products').empty(); // Clear the table body.

                // Loop through the list of products.
                $.each(data, function (key, val) {
                    // Add a table row for the product.
                    var row = '<td>' + val.Name + '</td><td>' + val.Price + '</td>';
                    $('<tr/>', { text: row })  // Append the name.
                        .appendTo($('#products'));
                });
            });
        }

        $(document).ready(getProducts);
</script>

هنگامی که سند جاری (document) بارگذاری شد این اسکریپت یک درخواست آژاکسی به آدرس "api/products/" ارسال می‌کند. سرویس ما لیستی از محصولات را با فرمت JSON بر می‌گرداند، سپس این اسکریپت لیست دریافت شده را به جدول HTML اضافه می‌کند.

اگر اپلیکیشن را اجرا کنید باید با نمایی مانند تصویر زیر مواجه شوید:

مطالب
ایجاد helper برای Nivo Slider در Asp.net Mvc

کامپوننت‌های jQuery زیادی وجود دارند که توسط آنها میتوان تصاویر را بصورت زمانبندی شده و به همراه افکت‌های زیبا در سایت خود نشان داد. مانند اینجا 

در این قصد ایجاد helper برای کامپوننت NivoSlider را داریم.

1- یک پروژه  Asp.net Mvc 4.0 ایجاد میکنیم.

2- سپس فایل jquery.nivo.slider.pack.js  ، فایل‌های css  مربوط به این کامپوننت و چهار تم موجود را از سایت این کامپوننت و یا درون سورس مثال ارائه شده دریافت می‌کنیم.

3- به کلاس BundleConfig رفته و کدهای زیر را اضافه میکنیم: 

#region Nivo Slider

   bundles.Add(new StyleBundle("~/Content/NivoSlider").Include("~/Content/nivoSlider/nivo-slider.css"));
   bundles.Add(new StyleBundle("~/Content/NivoSliderDefaultTheme").Include("~/Content/nivoSlider/themes/default/default.css"));
   bundles.Add(new StyleBundle("~/Content/NivoSliderDarkTheme").Include("~/Content/nivoSlider/themes/dark/dark.css"));
   bundles.Add(new StyleBundle("~/Content/NivoSliderLightTheme").Include("~/Content/nivoSlider/themes/light/light.css"));
   bundles.Add(new StyleBundle("~/Content/NivoSliderBarTheme").Include("~/Content/nivoSlider/themes/bar/bar.css"));
   bundles.Add(new ScriptBundle("~/bundles/NivoSlider").Include("~/Scripts/jquery.nivo.slider.pack.js"));

#endregion

سپس در فایل shared آنها را بصورت زیر اعمال میکنیم:

<!DOCTYPE html>
<html>
<head>
    <title>@ViewBag.Title</title>
    <script type="text/javascript" src="~/Scripts/jquery-1.9.1.min.js"></script>
    @Styles.Render("~/Content/NivoSlider", "~/Content/NivoSliderDefaultTheme","~/Content/NivoSliderLightTheme")
    @Scripts.Render("~/bundles/NivoSlider")
</head>
<body>
    @RenderBody()
    @RenderSection("scripts", required: false)
</body>
</html>

4- یک پوشه با عنوان helper به پروژه اضافه میکنیم. سپس کلاس‌های زیر را به آن اضافه می‌کنیم :

 public class NivoSliderHelper 
 {
        #region Fields

        private string _id = "nivo1";
        private List<NivoSliderItem> _models = null;
        private string _width = "100%";
        private NivoSliderTheme _theme = NivoSliderTheme.Default;
        private string _effect = "random"; // Specify sets like= 'fold;fade;sliceDown'
        private int _slices = 15; // For slice animations
        private int _boxCols = 8; // For box animations
        private int _boxRows = 4; // For box animations
        private int _animSpeed = 500; // Slide transition speed
        private int _pauseTime = 3000; // How long each slide will show
        private int _startSlide = 0; // Set starting Slide (0 index)
        private bool _directionNav = true; // Next & Prev navigation
        private bool _controlNav = true; // 1;2;3... navigation
        private bool _controlNavThumbs = false; // Use thumbnails for Control Nav
        private bool _pauseOnHover = true; // Stop animation while hovering
        private bool _manualAdvance = false; // Force manual transitions
        private string _prevText = "Prev"; // Prev directionNav text
        private string _nextText = "Next"; // Next directionNav text
        private bool _randomStart = false; // Start on a random slide
        private string _beforeChange = ""; // Triggers before a slide transition
        private string _afterChange = ""; // Triggers after a slide transition
        private string _slideshowEnd = ""; // Triggers after all slides have been shown
        private string _lastSlide = ""; // Triggers when last slide is shown
        private string _afterLoad = "";

        #endregion

        string makeParameters()
        {
            var builder = new StringBuilder();
            builder.Append("{");
            builder.Append(string.Format("effect:'{0}'", _effect));
            builder.AppendLine(string.Format(",slices:{0}", _slices));
            builder.AppendLine(string.Format(",boxCols:{0}", _boxCols));
            builder.AppendLine(string.Format(",boxRows:{0}", _boxRows));
            builder.AppendLine(string.Format(",animSpeed:{0}", _animSpeed));
            builder.AppendLine(string.Format(",pauseTime:{0}", _pauseTime));
            builder.AppendLine(string.Format(",startSlide:{0}", _startSlide));
            builder.AppendLine(string.Format(",directionNav:{0}", _directionNav.ToString().ToLower()));
            builder.AppendLine(string.Format(",controlNav:{0}", _controlNav.ToString().ToLower()));
            builder.AppendLine(string.Format(",controlNavThumbs:{0}", _controlNavThumbs.ToString().ToLower()));
            builder.AppendLine(string.Format(",pauseOnHover:{0}", _pauseOnHover.ToString().ToLower()));
            builder.AppendLine(string.Format(",manualAdvance:{0}", _manualAdvance.ToString().ToLower()));
            builder.AppendLine(string.Format(",prevText:'{0}'", _prevText));
            builder.AppendLine(string.Format(",nextText:'{0}'", _nextText));
            builder.AppendLine(string.Format(",randomStart:{0}", _randomStart.ToString().ToLower()));
            builder.AppendLine(string.Format(",beforeChange:{0}", _beforeChange));
            builder.AppendLine(string.Format(",afterChange:{0}", _afterChange));
            builder.AppendLine(string.Format(",slideshowEnd:{0}", _slideshowEnd));
            builder.AppendLine(string.Format(",lastSlide:{0}", _lastSlide));
            builder.AppendLine(string.Format(",afterLoad:{0}", _afterLoad));
            builder.Append("}");
            return builder.ToString();
        }
        public NivoSliderHelper (
            string id, 
            List<NivoSliderItem> models,
            string width = "100%", 
            NivoSliderTheme theme = NivoSliderTheme.Default,
            string effect = "random", 
            int slices = 15, 
            int boxCols = 8, 
            int boxRows = 4,
            int animSpeed = 500, 
            int pauseTime = 3000, 
            int startSlide = 0,
            bool directionNav = true, 
            bool controlNav = true,
            bool controlNavThumbs = false, 
            bool pauseOnHover = true, 
            bool manualAdvance = false,
            string prevText = "Prev",
            string nextText = "Next", 
            bool randomStart = false,
            string beforeChange = "function(){}", 
            string afterChange = "function(){}",
            string slideshowEnd = "function(){}", 
            string lastSlide = "function(){}", 
            string afterLoad = "function(){}")
        {
            _id = id;
            _models = models;
            _width = width;
            _theme = theme;
            _effect = effect;
            _slices = slices;
            _boxCols = boxCols;
            _boxRows = boxRows;
            _animSpeed = animSpeed;
            _pauseTime = pauseTime;
            _startSlide = startSlide;
            _directionNav = directionNav;
            _controlNav = controlNav;
            _controlNavThumbs = controlNavThumbs;
            _pauseOnHover = pauseOnHover;
            _manualAdvance = manualAdvance;
            _prevText = prevText;
            _nextText = nextText;
            _randomStart = randomStart;
            _beforeChange = beforeChange;
            _afterChange = afterChange;
            _slideshowEnd = slideshowEnd;
            _lastSlide = lastSlide;
            _afterLoad = afterLoad;
        }
        public IHtmlString GetHtml()
        {
            var thm = "theme-" + _theme.ToString().ToLower();
            var sb = new StringBuilder();
            sb.AppendLine("<div style='width:" + _width + ";height:auto;margin:0px auto' class='" + thm + "'>");
            sb.AppendLine("<div id='" + _id + "' class='nivoSlider'>");
            foreach (var model in _models)
            {
                string img = string.Format("<img src='{0}' alt='{1}' title='{2}' />", model.ImageFilePath, "", model.Caption);
                string item = "";
                if (model.LinkUrl.Trim().Length > 0 &&
                    Uri.IsWellFormedUriString(model.LinkUrl, UriKind.RelativeOrAbsolute))
                {
                    item = string.Format("<a href='{0}'>{1}</a>", model.LinkUrl, img);
                }
                else
                {
                    item = img;
                }
                sb.AppendLine(item);
            }
            sb.AppendLine("</div>");
            sb.AppendLine("</div>");

            sb.AppendLine("<script type='text/javascript'>");
            //sb.AppendLine("$('#" + _id + "').parent().ready(function () {");
            sb.Append("$(document).ready(function(){");
            sb.AppendLine("$('#" + _id + "').nivoSlider(" + makeParameters() + ");");
            //sb.AppendLine("$('.nivo-controlNav a').empty();");//semi hack for rtl layout
            //sb.AppendLine("$('#" + _id + "').nivoSlider();");
            sb.AppendLine("});");
            sb.AppendLine("</script>");

            return new HtmlString(sb.ToString());
        }
    }
    public enum NivoSliderTheme
    {
        Default, Light, Dark, Bar,
    }
    public class NivoSliderItem
    {
        public string ImageFilePath { get; set; }
        public string Caption { get; set; }
        public string LinkUrl { get; set; }
    }
6-سپس برای استفاده از این helper یک کنترلر به پروژه اضافه میکنیم مانند Home :
 public class HomeController : Controller
    {
        public ActionResult Index()
        {
            return View();
        }
        public virtual ActionResult NivoSlider()
        {
            var models = new List<NivoSliderItem>
            {
                new NivoSliderItem()
                    {
                        ImageFilePath =Url.Content("~/Images/img1.jpg"),
                        Caption = "عنوان اول",
                        LinkUrl = "http://www.google.com",
                    },
                new NivoSliderItem()
                    {
                        ImageFilePath =Url.Content("~/Images/img2.jpg"),
                        Caption = "#htmlcaption",
                        LinkUrl = "",
                    },
                new NivoSliderItem()
                    {
                        ImageFilePath =Url.Content("~/Images/img3.jpg"),
                        Caption = "عنوان سوم",
                        LinkUrl = "",
                    },
                new NivoSliderItem()
                    {
                        ImageFilePath =Url.Content("~/Images/img4.jpg"),
                        Caption = "عنوان چهارم",
                        LinkUrl = "",
                    },
                new NivoSliderItem()
                    {
                        ImageFilePath =Url.Content("~/Images/img5.jpg"),
                        Caption = "عنوان پنجم",
                        LinkUrl = "",
                    }
            };
            return PartialView("_NivoSlider", models);
        }
    }
7- سپس ویوی Home را نیز ایجاد میکنیم:
@{
    ViewBag.Title = "Index";
}

<h2>Nivo Slider Index</h2>

@{ Html.RenderAction("NivoSlider", "Home");}
8- یک پارشال ویو نیز برای رندر کردن NivoSliderهای خود ایجاد میکنیم:
@using NivoSlider.Helper
@model List<NivoSliderItem>
@{
    var nivo1 = new NivoSliderHelper("nivo1", Model.Skip(0).Take(3).ToList(), theme: NivoSliderTheme.Default, width: "500px");
    var nivo2 = new NivoSliderHelper("nivo2", Model.Skip(3).Take(2).ToList(), theme: NivoSliderTheme.Light, width: "500px");
}
@nivo1.GetHtml()

<div id="htmlcaption">
    <strong>This</strong> is an example of a <em>HTML</em> caption with <a href="#">a link</a>.
</div>

<br />
<br />
@nivo2.GetHtml()
نکته اول: اگر بخواهیم بر روی تصویر موردنظر متنی نمایش دهیم کافیست خاصیت Caption را مقداردهی کنیم. برای نمایش توضیحاتی پیچیده‌تر (مانند نمایش یک div و لینک و غیره) بعنوان توضیحات یک تصویر خاص باید خاصیت Caption را بصورت "{نام عنصر}#" مقداردهی کنیم و ویژگی class  مربوط به عنصر موردنظر را با nivo-html-caption مقداردهی کنیم.(همانند مثال بالا)
نکته دوم: این کامپوننت به همراه 4 تم (deafult- light- bar- dark) ارائه شده است. موقع استفاده از تم‌های دیگر باید رفرنس مربوط به آن تم را نیز اضافه کنید.
امیدوارم مفید واقع شود.