نظرات مطالب
شروع به کار با AngularJS 2.0 و TypeScript - قسمت اول - نصب پیشنیازها
- این خطا نیست، warning هست و صرفا به این معنا است که وابستگی Mac یاد شده در ویندوز وجود ندارد و مهم نیست (skip شده، صرفنظر شده).
- در کل یکبار node را به طور کامل از سیستم حذف کنید. آخرین نگارش این‌ها را دریافت و مجددا نصب کنید. همچنین نکته‌ی جایگزین کردن npm پیش فرض ویژوال استودیو را که در متن عنوان شد، فراموش نکنید.
مطالب
معماری module based در TypeScript قسمت اول
در صورت استفاده از TypeScript، قطعا با module‌ها و هدف استفاده‌ی از آن‌ها آشنایی دارید. در این مقاله می‌خواهیم با متداول‌ترین روش‌های بسته بندی آن‌ها آشنا شده و به صورت عملیاتی آن را پیاده نماییم. اولین روش commonjs میباشد. از آنجایی که این روش بیشتر برای برنامه‌های خارج از مرورگر میباشد، به همین قدر معرفی آن بسنده میکنیم. اما دو روش مهم دیگری که در typeScript برای ماژول‌ها اهمیت فراوانی دارند:

1) AMD یا Asynchronous Module Definition
2) در این روش از امکانات توکار کامپایلر typescript برای combine کردن فایل‌ها استفاده میشود

ابتدا AMD را بررسی مینماییم
این روش بدین صورت عمل میکند که هر ماژول، در صورتیکه بطور صریح نیاز به ماژول دیگری داشته باشد، آن را import کرده و به صورت async آن ماژول درخواستی را دریافت مینماید. بهترین ابزار برای انجام اینکار requirejs میباشد و در وبسایت رسمی آن، خودش را اینگونه معرفی کرده است:
RequireJS is a JavaScript file and module loader ، ما عملیات load کردن فایل‌ها را به requirejs واگذار میکنیم.
حال میخواهیم به پیاده سازی AMD با استفاده از typescript و البته requirejs بپردازیم.
بنده برای اینکار از IDE محبوب خود visual studio استفاده خواهم کرد. قطعا شما از IDE/Text editor مورد نظر خود میتوانید استفاده نمایید (البته با کمی تغییرات جزیی در محتوای آموزشی این مقاله).
ابتدا یک پروژه‌ی asp.net از نوع empty را بدون هیچ وابستگی ایجاد کرده و index.html را بدان اضافه مینماییم:

نام پروژه‌ی من AMD و فایل index.html بدان اضافه شده است. فرض کنید یک پوشه‌ی جدید را به نام modules به آن اضافه میکنیم و در آن دو فایل typescript ی را به نام‌های module1.ts و module2.ts، اضافه میکنیم.

محتویات module1 را اینگونه مینویسیم:

export module module1 {
    export abstract class firstCls {
        static f1(str: string) {
            console.log(str);
        }
    }
}

و همچنین module2 به شکل زیر خواهد بود:

import * as Amd from 'module1';

module module2 {
    export class secondCls {
        f2(str: string) {
            Amd.module1.firstCls.f1(str);
        }
    }
}

new module2.secondCls().f2(`amd work's`);

(دقت کنید بعد از کامپایل شدن لفظ import تبدیل به define میشود)

از طریق add - new item فایل tsconfig.json را به مسیر اصلی پروژه اضافه کنید. در صورتی که آن را پیدا نکردید، به صورت دستی فایل آن را ساخته و محتویات زیر را در آن کپی نمایید:

{
  "compilerOptions": {
    "noImplicitAny": false,
    "noEmitOnError": true,
    "removeComments": false,
    "sourceMap": false,
    "target": "es6",
    "module": "amd"
  },
  "exclude": [
    "node_modules"
  ]
}
exclude مسیرهایی هستند که قرار نیست کامپایل شوند. target نوع کد تولید شده را مشخص مینماید (در صورتیکه مرورگر شما از es6 پشتیبانی نمیکند، میتوانید target را به es5 تغییر دهید) و module نیز از نوع amd تعریف میشود. کاری که قرار است انجام دهیم این است که به محض فراخوانی module2 به صورت async ، ماژول module1 را load کرده تا بتوان از آن بهره برد. متاسفانه در حال حاضر هیچ مرورگری به صورت توکار این ویژگی را پشتیبانی نمیکند! به همین دلیل فعلا مجبور به استفاده از ابزار‌های third party همچون requirejs هستیم.

در ادامه به root پروژه رفته و دستور npm init را ارسال کرده تا فایل package.json تولید شود. همچنین برای requirejs نیز دستور زیر را ارسال مینماییم:

npm install requirejs --save-dev

حال requirejs به پروژه‌ی شما اضافه شده است.


برای مدیریت کردن فراخوانی initial module در پوشه‌ی modules که قبلا ساخته بودیم فایل main.js راساخته و کد‌های زیر را بدان اضافه مینماییم.

(لازم به ذکر است این فایل را میتوانیم با استفاده از typescript نوشته و  requirejs definitely typed  را به پروژه اضافه کرده و از مزایای intellisense بودن بهره ببریم)

کد‌های زیر را درون main.js مینویسیم:

require(['modules/module2.js'], modules_module2());

function modules_module2() {
    //additionals config goes here
}

از آنجاییکه ممکن است تعداد وابستگی فایل‌ها زیاد باشد و ترتیب load شدن آن‌ها نیز اهمیت داشته باشد، در این قسمت میتوان configهای بیشتری را همچون sequence در load کردن فایل‌ها، تعریف کرد که میتوانید در وبسایت رسمی requirejs آن را مطالعه بفرمایید.


حال فایل index.html را باز کرده و config برای فراخوانی requirejs, main.js را مینویسیم؛ به صورت زیر:

<h1>Hello requirejs and amd modules</h1>

<!--src means require js address-->
<!--data-main means initial require config-->

<script src="node_modules/requirejs/require.js" data-main="modules/main.js"></script>

<script></script>

پر واضح است که src آدرس فایل require.js و همچنین data-main آدرس initial require config پروژه را مشخص می‌کند.

اکنون پروژه را run کرده و می‌بینید که فایل‌های مورد نیاز به صورت async برای ما load میشوند. اگر از مرورگر کروم استفاده می‌نمایید، بدین صورت میتوانید network و همچنین console را مشاهد نمایید:


مشاهد میکنید که فایل‌های مورد نیاز load شده‌اند و همچنین amd work's در console نمایش داده شده است.

requirejs بدین صورت عمل میکند: بعد از یافتن هر فایل، با استفاده از regex کل فایل را بررسی کرده و به دنبال وابستگی‌های آن فایل میگردد (منظور همان import‌ها میباشد و آن فایل به صورت async لود میشود) و در فایل‌های بعدی نیز همین روال ادامه خواهد یافت. هر چند راهکارهایی برای بهبود کارآیی در آن اندیشیده شده است؛ بدین صورت که اساس کارش با استفاده از singleton میباشد و بعد از require کردن فایلی، هر دفعه که فراخوانی میشود، نیاز به پردازش مجدد ندارد. با این وجود ممکن است در بعضی مواقع و مخصوصا با اشتباهات سهوی برنامه نویسان از کارآیی نرم افزار مطبوع شما بکاهد.


کد‌های این برنامه را میتوانید از اینجا دریافت نمایید (ضمن اینکه وابستگی‌های اضافه‌تری مانند پوشه‌ی node_modules حذف شده‌اند؛ بنابراین npm install فراموش نشود)

دانلود AMD.zip  


در قسمت بعد به امکانات توکار کامپایلر typescript برای معماری ماژول‌ها میپردازیم

مطالب
کمی درباره httpmodule
قبل از اینکه به httpmodule‌ها بپردازیم، اجازه بدید کمی در در مورد  httphandler  اطلاعات کسب کنیم. httphandler ویژگی است که از asp.net  به بعد ایجاد شد و در asp کلاسیک خبری از آن نیست.
یک httphandler کامپوننتی است که از طریق اینترفیس System.Web.IHttpHandler پیاده سازی میشود و به پردازش درخواست‌های رسیده از httprequest رسیدگی می‌کند.
فرض کنید کاربری درخواست صفحه default.aspx را کرده است و سرور هم پاسخ آن را می‌دهد. در واقع پردازش اینکه چه پاسخی باید به کاربر یا کلاینت ارسال شود بر عهده این کامپوننت می‌باشد. برای وب سرویس هم موضوع به همین صورت است؛ هر نوع درخواست HTTP از این طریق انجام می‌شود.
حال به سراغ httpmodule می‌رویم. httpmodule‌ها اسمبلی یا ماژول‌هایی هستن که بر سر راه هر درخواست کاربر از سرور قرار گرفته و قبل از اینکه درخواست شما به httphandler برسد، اول از فیلتر این‌ها رد می‌شود. در واقع موقعی که شما درخواست صفحه default.aspx را می‌کنید، درخواست شما به موتور asp.net ارسال می‌شود و از میان فیلترهایی رد می‌شود تا به دست httphandler برای پردازش خروجی برسد. برای همین اگر گاهی به جای گفتن asp.net engine عبارت asp.net pipeline هم میگویند همین هست؛ چون درخواست شما از بین بخش‌های زیادی می‌گذرد تا به httphandler برسد که httpmodule یکی از آن بخش هاست. با هر درخواستی که سرور ارسال می‌شود، httpmodule‌ها صدا زده می‌شوند و به برنامه نویس امکان بررسی اطلاعات درخواستی و پردازش درخواست‌ها را در ورودی و خروجی، می‌دهد و شما میتوانید هر عملی را که نیاز دارید انجام دهید. تعدادی از این ماژول‌های آماده، همان state‌ها و Authentication می‌باشند.
تصویر زیر نحوه‌ی ارسال و بازگشت یک درخواست را به سمت httphandler نشان می‌دهد


برنامه نویس هم میتواند با استفاده از اینترفیس‌های IHttpModule و IHttpHandler در درخواست‌ها دخالت نماید.
برای شروع یک کلاس ایجاد کنید که اینترفیس IHttpModule را پیاده سازی می‌کند. شما دو متد را باید در این کلاس بنویسید؛ یکی Init و دیگر Dispose. همانطور که مطلع هستید، اولی در ابتدای ایجاد شیء و دیگر موقع از دست رفتن شی صدا زده می‌شود.
متد Init یک آرگومان از نوع httpapplication دارد که مانند رسم نامگذاری متغیرها، بیشتر به اسم context یا app نام گذاری می‌شوند:
public void Init(HttpApplication app)
{
   app.BeginRequest += new EventHandler(OnBeginRequest);
}

public void Dispose(){ }
همانطور که می‌بینید این شیء یک رویداد دارد که ما این رویداد را به تابعی به نام OnBeginRequest متصل کردیم. سایر رویداد‌های موجود در httpapplication  به شرح زیر می‌باشند:
 BeginRequest  این رویداد اولین رویدادی است که اجرا می‌شود، هر نوع عملی که میخواهید در ابتدای ارسال درخواست انجام دهید، باید در این قسمت قرار بگیرد؛ مثلا قرار دادن یک بنر بالای صفحه 
AuthenticateRequest خود دانت از یک سیستم امنیتی توکار بهره مند است و اگر می‌خواهید در مورد آن خصوصی سازی انجام بدهید، این رویداد می‌تواند کمکتان کند 
AuthorizeRequest بعد از رویداد بالا، این رویداد برای شناسایی انجام می‌شود. مثلا دسترسی ها؛ دسترسی به قسمت هایی خاصی از منابع به او داده شود و قسمت هایی بعضی از منابع از او گرفته شود.
ResolveRequestCache این رویداد برای کش کردن اطلاعات استفاده می‌شود. خود دانت تمامی این رویدادها را به صورت تو کار فراهم آورده است؛ ولی اگر باز خصوصی سازی خاصی مد نظر شماست می‌توانید در این قسمت‌ها، تغییراتی را اعمال کنید. مثلا ایجاد file caching به جای memory cache و ... 
AcquireRequestState این قسمت برای مدیریت state می‌باشد مثلا مدیریت session ها
PreRequestHandlerExecute این رویداد قبل از httphandler اجرا می‌شود.
PostRequestHandlerExecute این رویداد بعد از httphandler اجرا می‌شود.
ReleaseRequestState این رویداد برای این صدا زده می‌شود که به شما بگوید عملیات درخواست پایان یافته است و باید state‌های ایجاد شده را release یا رها کنید.
UpdateRequestCache   برای خصوصی سازی output cache بکار می‌رود.
EndRequest عملیات درخواست پایان یافته است. در صورتیکه قصد نوشتن دیباگری در طی تمامی عملیات دارید، میتواند به شما کمک کند. 
PreSendRequestHeaders این رویداد قبل از ارسال طلاعات هدر هست. اگر قصد اضافه کردن اطلاعاتی به هدر دارید، این رویداد را به کار ببرید.
PreSendRequestContent  این رویداد موقعی صدا زده می‌شود که متد response.flush فراخوانی شود.، اگر می‌خواهید به محتوا چیزی اضافه کنید، از اینجا کمک بگیرید.
Error این رویداد موقعی رخ می‌دهد که یک استثنای مدیریت نشده رخ بدهد. برای نوشتن سیستم خطایابی خصوصی از این قسمت عمل کنید.
Disposed  این رویداد موقعی صدا زده میشود که درخواست، بنا به هر دلیلی پایان یافته است. برای عملیات پاکسازی و .. می‌شود از آن استفاده کرد. مثلا یک جور rollback برای کارهای انجام گرفته.

کد زیر را در نظر بگیرید:
کد زیر یک رویداد را تعریف کرده و سپس خود httpapplication را به عنوان sender استفاده می‌کند.
در اینجا قصد داریم یکی از صفحات را در خروجی تغییر دهیم. آدرس تایپ شده همان باشد ولی صفحه‌ی درخواست شده، صفحه‌ی دیگری است. این کار موقعی بیشتر کاربردی است که آدرس یک صفحه تغییر کرده و کاربر آدرس قبلی را وارد می‌کند. حالا یا از طریق بوک مارک یا از طریق یک لینک، در یک جای دیگر و شما میخواهید او را به صفحه‌ای جدید انتقال دهید، ولی در نوار آدرس، همان آدرس قبلی باقی بماند. همچنین کار دیگری که قرار است انجام بگیرد محاسبه مدت زمان رسیدگی به درخواست را محاسبه کند ، برای همین در رویداد BeginRequest زمان شروع درخواست را ذخیره و در رویداد EndRequest با به دست آوردن اختلاف زمان فعلی با زمان شروع به مدت زمان مربوطه پی خواهیم برد.
با استفاده از app.Context.Request.RawUrl آدرس اصلی و درخواست شده را یافته و در صورتی که شامل نام صفحه مربوطه بود، با نام صفحه‌ی جدید جابجا می‌کنیم تا اطلاعات به صفحه‌ی جدید پاس شوند ولی در نوار آدرس، هنوز آدرس قبلی یا درخواست شده، قابل مشاهده است.
در خط ["app.Context.Items["start  که یک کلاس ارث بری شده از اینترفیس IDictionary است، بر اساس کلید، داده شما را ذخیره و در مواقع لزوم در هر رویداد به شما باز می‌گرداند.
 public class UrlPath : IHttpModule
    {
        public void Init(HttpApplication app)
        {
            app.BeginRequest+=new EventHandler(_BeginRequest);
            app.EndRequest+=new EventHandler(_EndRequest);
        }

        public void Dispose()
        {

        }

         void _BeginRequest(object sender, EventArgs e)
         {
             
             HttpApplication app = (HttpApplication) sender;
             app.Context.Items["start"] = DateTime.Now;

             if (app.Context.Request.RawUrl.ToLower().Contains("tours_list.aspx"))
             {
                 app.Context.RewritePath(app.Context.Request.RawUrl.ToLower().Replace("tours_list.aspx","tours_cat.aspx"));
             }

         }
         void _EndRequest(object sender, EventArgs e)
         {
             HttpApplication app = (HttpApplication)sender;
             string log = (DateTime.Now - DateTime.Parse(app.Context.Items["start"].ToString())).ToString();
             Debugger.Log(0,"duration","request took " + log+Environment.NewLine); 
             
         } 
    }
حالا باید کلاس نوشته شده را به عنوان یک httpmodule به سیستم معرفی کنیم. به همین منظور وارد web.config شوید و کلاس جدید را معرفی کنید:
  <httpModules>
     <add name="UrlPath" type="UrlPath"/> 
   </httpModules>
اگر کلاس شما داخل یک namespace قرار دارد، در قسمت type حتما قبل از نام کلاس، آن را تعریف کنید namspace.ClassName
حالا دیگر کلاس UrlPath  به عنوان یک httpmodule به سیستم معرفی شده است. تگ httpmodule را بین تگ <system.web> قرار داده ایم.
در ادامه پروژه را start بزنید تا نتیجه کار را ببینید:
اگر IIS شما، هم نسخه‌ی IIS من باشد، نباید تفاوتی مشاهده کنید و می‌بینید که درخواست‌ها هیچ تغییری نکردند؛ چرا که اصلا httpmodule اجرا نشده است. در واقع در نسخه‌های قدیمی IIS یعنی 6 به قبل، این تعریف درست است ولی از نسخه‌ی 7 به بعد IIS، روش دیگری برای تعریف را قبول دارد و باید تگ httpmodule، بین دو تگ <syste.webserver> قرار بگیرد و نام تگ httpmodule به module تغییر پیدا کند.
پس کد فوق به این صورت تغییر می‌کند:
<system.webServer>
  
    <modules>
      <add name="UrlRewrite" type="UrlRewrite"/>
    </modules>
</system.webServer>
حالا اگر قصدا دارید که پروژه‌ی شما در هر دو IIS مورد حمایت قرار گیرد، باید این ماژول را در هر دو جا معرفی کرده و در تگ system.webserver  نیاز است تگ زیر تعریف شود که به طوری پیش فرض در webconfig می‌باشد:
    <validation validateIntegratedModeConfiguration="false"/>
در غیر این صورت خطای زیر را دریافت می‌کنید:

HTTP Error 500.22 - Internal Server Error 

در کل استفاده از این ماژول به شما کمک می‌کند تمامی اطلاعات ارسالی به سیستم را قبل از رسیدن به قسمت پردازش بررسی نمایید و هر نوع تغییری را که میخواهید اعمال کنید و لازم نیست این تغییرات را روی هر بخش، جداگانه انجام دهید یا یک کلاس بنویسید که هر بار در یک جا صدا بزنید و خیلی از موارد دیگر

Global.asax و HttpModule
اگر با global.asax کار کرده باشید حتما می‌پرسید که الان چه تفاوتی با httpmodule دارد؟ در فایل global هم همین‌ها را دارید و دقیقا همین مزایا مهیاست؛ در واقع global.asax یک پیاده سازی از httpapplication هست.
کلاس‌های httpmodule  نام دیگری هم دارند به اسم Portable global .asax به معنی یک فایل global.asax قابل حمل یا پرتابل. دلیل این نام گذاری این هست که شما موقعی که یک کد را در فایل global می‌نویسید، برای همیشه آن کد متعلق به همان پروژه هست و قابل انتقال به یک پروژه دیگر نیست ولی شما میتوانید httpmodule‌ها را در قالب یک پروژه به هر پروژه ای که دوست دارید رفرنس کنید و کد شما قابلیت استفاده مجدد و Reuse پیدا می‌کند و هم اینکه در صورت نیاز می‌توانید آن‌ها را در قالب یک dll منتشر کنید.
مطالب
مبانی TypeScript؛ ماژول‌ها
تاریخچه

تا پیش از نگارش 1.5 تایپ اسکریپت، مفاهیم internal modules و external modules وجود داشتند. جهت نامگذاری بهتر و کاهش سردرگمی در استفاده‌ی آن‌ها، از نگارش 1.5 به بعد، ماژول‌های داخلی به namespaces (فضاهای نام) تغییر نام یافتند و ماژول‌های خارجی به نام «ماژول» خلاصه شدند.
همچنین از نگارش 1.5 به بعد، پشتیبانی کاملی از نحوه‌ی تعریف «ماژول‌ها در ES 6» نیز به عمل می‌آید. بنابراین مطالعه‌ی آن نیز پیشنهاد می‌گردد.


مفهوم ماژول‌ها

هدف اصلی از ماژول‌ها، ارائه‌ی روشی برای مدیریت و ساماندهی پروژه‌های بزرگ با تعداد فایل‌های زیاد است. در اینجا فایل‌های ارجاعی، در زمان اجرا، توسط runtime جاوا اسکریپت بارگذاری شده و سپس به امکانات آن‌ها دسترسی خواهیم داشت. ماژول‌ها به صورت توکار در Node.JS نیز پشتیبانی می‌شوند؛ البته با فرمت common.js که کامپایلر TypeScript نیز قادر به تولید آن است.


امکان کامپایل به روش‌های قدیمی‌تر تعریف ماژول‌ها در TypeScript

در مورد انواع روش‌های قدیمی‌تر نحوه‌ی تعریف ماژول‌های در جاوا اسکریپت مانند common.js، AMD و امثال آن‌ها، مطالعه‌ی مطلب «ماژول‌ها در ES 6» توصیه می‌شود. فقط نکته‌ای که در اینجا حائز اهمیت است، این است که چون TypeScript قادر است به ES 5 نیز کامپایل شود و در ES 5 روش جدید ES 6 جهت تعریف ماژول‌ها وجود ندارد، امکان تبدیل و ترجمه‌ی کدهای TypeScript به تمام نوع‌های معروف و شناخته شده‌ی ماژول‌ها مانند common.js توسط کامپایلر TypeScript به صورت خودکار وجود دارد. برای این منظور از سوئیچ module کامپایلر استفاده می‌شود.


نحوه‌ی تعریف ماژول‌ها در TypeScript

برای تبدیل یک فایل ts به یک ماژول، تنها کافی است موردی را از آن export کنیم. آیتم‌های موجود در یک ماژول، تنها زمانی در سایر فایل‌ها قابل استفاده خواهند بود که از آن export شده باشند:
 // periodicals.ts
export interface Periodical {
   issueNumber: number;
}

export class Magazine implements Periodical {
   issueNumber: number;
}

export function GetMagazineByIssueNumber(issue: number): Magazine {
   // retrieve and return a magazine
}
در این مثال، یک اینترفیس، کلاس و متد export شده‌اند. برای این منظور واژه‌ی کلیدی export به پیش از هر کدام از آیتم‌های مدنظر اضافه شده‌است.
روش دیگر انجام این تعاریف، حذف واژه‌ی کلیدی export از تمام موارد تعریف شده و سپس خلاصه کردن آن‌ها در یک سطر، توسط روش export statement است؛ به نحو ذیل:
 // periodicals.ts
interface Periodical {
   issueNumber: number;
}

class Magazine implements Periodical {
   issueNumber: number;
}

function GetMagazineByTitle(title: string): Magazine {
   // retrieve and return a magazine
}

export { Periodical, Magazine, GetMagazineByTitle as GetMag}
مزیت این روش، مشخص بودن محل تعاریف خروجی‌ها است؛ بدون اینکه نیازی باشد تا تمام فایل‌را جهت یافتن exportها جستجو کرد.
همچنین در اینجا می‌توان نام دیگری را نیز برای خروجی‌ها درنظر گرفت. برای مثال بجای نام GetMagazineByTitle، با استفاده از as syntax، یک نام جدید معرفی شده‌است.


نحوه‌ی استفاده‌ی از ماژول‌ها در TypeScript

برای استفاده‌ی از امکانات خروجی مثال قبل، در یک ماژول دیگر، به نحو ذیل عمل می‌کنیم:
 // news.ts
import { Magazine, GetMag as GetMagazine} from './periodicals';
let newsMag: Magazine = GetMagazine('Weekly News');
در اینجا پس از تعریف واژه‌ی کلیدی import، لیست موارد مدنظر از خروجی‌های فایل periodicals را داخل یک {} می‌توان قید کرد. بنابراین نیازی نیست تا تمام خروجی‌های یک ماژول را import کرد. همچنین در اینجا نیز با استفاده از as syntax می‌توان نام جدیدی را برای موارد import شده تعیین کرد.
در انتها نیز مسیر نسبی فایل ts ماژول، بدون ذکر پسوند آن، پس از واژه‌ی کلیدی from ذکر می‌شود.

اگر نیاز است تمام خروجی‌های یک ماژول به صورت خودکار import شوند، می‌توان از * استفاده کرد:
 // kids.ts
import * as mag from './periodicals';
اینبار با توجه به as syntax استفاده شده، نحوه‌ی دسترسی به خروجی‌های ماژول مدنظر به صورت ذیل خواهد بود (ابتدا ذکر نام alias تعریف شده، به همراه یک دات):
 let kidMag: mag.Magazine= mag.GetMag('Games and Stuff!');


خروجی پیش فرض یک ماژول

اگر تنها قرار است یک آیتم از ماژولی export شود، می‌توان از مفهوم default export استفاده کرد:
 // movie.ts
export default class{
   title: string;
   director: string;
}
در این مثال export default بر روی یک کلاس بدون نام تعریف شده‌است. تعریف نام کلاس در اینجا اختیاری است و ماژول import کننده‌ی آن نیازی به دانستن این نام ندارد؛ زیرا در این حالت import کننده می‌تواند نام دلخواهی را به این خروجی پیش فرض بدهد؛ مانند AnimatedMovie بدون نیاز به ذکر {}:
 // kids.ts
import AnimatedMovie from './movie’;
let cartoon = new AnimatedMovie();
مطالب
چک لیست تهیه یک هاست خوب برای تازه کاران
برای بسیاری از تازه کاران که پا به عرصه‌ی برنامه‌های تحت وب می‌گذارند، اینکه چگونه، از کجا و چطور باید هاستی را انتخاب کنند، دچار سردرگمی هستند. دیدن پلن‌های مختلف با قیمت‌های مختلف، باعث افزایش سردرگمی آن‌ها می‌شود. در این مقاله به بررسی اینکه چطور باید هاستی خریداری شود و اینکه اصلا خود برنامه‌ی نوشته شده نیازش چقدر هست، صحبت می‌کنیم.

قبل از اینکه صحبت را آغاز کنیم باید این نکته اشاره کنیم که انواع هاست چیست؟

انواع هاست
هاست‌ها بر سه نوع تقسیم می‌شوند:
  1. هاست اشتراکی
  2. سرورهای مجازی
  3. سرور اختصاصی

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

سرورهای مجازی VPS یا Virtual Private Server
این‌ها هم تقریبا مثل هاست‌های اشتراکی هستند با این تفاوت که منابع بیشتر و دسترسی بیشتری به شما داده می‌شوند؛ به طوری که احساس می‌کنید به شما یک سرور واقعی را داده‌اند و عموما تعداد سایت هایی که روی آن سرویس می‌گیرند، به مراتب کمتر از هاست اشتراکی است. اکثر وب سایت‌هایی که توانایی استفاده از هاست‌های اشتراکی را ندارند، از این نوع هاستینگ بهره می‌برند. قیمتش بالا‌تر از یک هاست اشتراکی است ولی به مراتب پایین‌تر از یک سرور اختصاصی است. پرداخت این نوع هاست عموما به دو صورت ماهیانه و یا سالانه است که احتمال زیادی دارد پرداخت سالیانه تخفیف خوبی را شامل شود. در صورت رسیدن به حد نهایت منابع همانند هاست اشتراکی با شما رفتار خواهد شد. این سرورهای مجازی در ایران عموما از مبلغ ماهیانه 20 هزار تومان به بالا آغاز می‌شوند.


سروهای اختصاصی یا Dedicate
این‌ها دیگر سروهای واقعی هستند و صاحب اول و آخرشان شمایید و دسترسی کامل به همه اجزا و منابع آن را دارید. این سرورهای عموما برای فعالیت‌های بزرگ تجاری و بازدیدهای همزمان به شدت بالا استفاده می‌شوند. قیمت، بسته به مشخصات آن متفاوت است و ممکن است یکی ماهیانه 200 هزار تومان، یکی ماهیانه 500 هزار تومان و .. آغاز شود.
نکته ای در مورد سرورهای اختصاصی و حتی گاها VPSها هست اینکه عموما پشتیبانی این‌ها توسط شما تامین می‌شود و  یا باید یک مدیر سرور با حق ماهیانه اختیار کنید یا در صورت بروز مشکل به صورت ساعتی حق حل مشکل را بدهید. بهتر هست این مورد را از قبل توسط میزبان جویا شوید.

سرورهای ابری
اگر قصد انجام عملیات رایانش برای را دارید بهتر هست که دنبال چنین سرورهایی باشید که مورد بحث این مقاله نیست و صرفا جهت تکمیل معرفی انواع هاست‌ها آمده است.


چک لیست


موقعی که شما نوع هاست خود را انتخاب می‌کنید به یک سری پلن یا پکیج‌های مختلفی می‌رسید که مشخصات مختلفی دارند و هر شرکت مبلغ و پیکربندی مختلفی را در نظر  می‌گیرد. نگاهی به چک لیست پایین و بررسی گام به گام آن  می‌تواند شما را در رسیدن به انتخاب بهتر یاری کند.


فضای دیسک و پهنای باند (ترافیک)
اولین نکاتی که همیشه توسط میزبان‌ها و خریداران مورد توجه قرار می‌گیرند، این دو مورد هست. در صورتیکه سایت شما اجازه آپلودی به کاربران نمی‌دهد، یا خودتان هم آپلود چندانی ندارید، می‌توانید فضای دیسک سخت پایین‌تری را انتخاب کنید. مثلا اگر شما تعدادی تصویر دارید و مقدار زیادی فایل متنی و ... به نظر یک گیگ کفایت می‌کند و در صورتیکه بعدها دیدید این فضا رو به اتمام است، می‌توانید به میزبان درخواست افزایش و ارتقاء آن را بدهید؛ اما برای بار اول یک گیگ به خوبی کفایت می‌کند. ولی اگر چنانچه با فایل‌های حجیم سرو کله میزنید و یا تعداد فایل‌هایی چون تصاویر، صوت و ویدیو در آن زیاد دیده می‌شود، بهتر است به فکر فضایی بیشتر و حتی گاها unlimited باشید. برای سایت‌هایی که حجم به شدت بالایی دارند و یا مثلا نیاز به راه اندازی بخش دانلود دارند، بهتر هست از یک هاستینگ به نام هاست دانلود استفاده کنند. این نوع هاست‌ها به شما اجازه میزبانی فایل‌های وب سایت‌هایی چون aspx,mvc,php را نمی‌دهند و بیشتر پهنای باند و فضای دیسک سخت آن مدنظر است. در این حالت سایت خود را روی یک هاست معمولی به اشتراک می‌گذارید و فایل‌های خود را روی هاست دانلود قرار می‌دهید و ارتباط آن‌ها را لینک می‌کنید.

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

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

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

سرعت Speed و توان سرویس دهی(Uptime)
بعد از ارزیابی موارد بالا نوبت به این دو مورد می‌رسد. اینکه هاست شما به قولی چقدر دوام و ایستادگی دارد، بسیار مهم است و در صورتیکه این امر محقق نگردد، می‌تواند موجب از دست دادن و شنیدن اعتراض کاربرها باشید. uptime به معنی این است که در یک دوره‌ی زمانی چقدر وب سایت شما در دسترس بوده است؛ درست  شنیدید!  حتما پیش خودتان میگویید خوب، یک وب سایت همیشه در دسترس است. ولی واقعیت را بخواهید خیر، اینگونه نیست. اگر سرویس دهنده‌ی خوبی را انتخاب نکنید، ممکن است در روز یا هفته یا در هرمقطع زمانی، با در دسترس نبودن وب سایت روبرو شوید؛ تقریبا مشابه زمانیکه adsl شما قطع می‌شود، چند لحظه‌ای (طولانی‌تر از زمان عادی) مرورگر شما سعی کرده و می‌بیند که زورش نمی‌رسد و یک پیام عدم دسترسی را به شما نمایش می‌دهد.

به خوبی به یاد دارم که یک میزبان، هاست‌های ارزان قیمتی را ارائه می‌کرد، ولی گاها در روز دو الی سه بار پنج دقیقه‌ای سرور از دسترس خارج می‌شد و می‌گفتیم این هاست برای کسانی است که پول کافی ندارند و یا خیلی نمی‌خواهند خرج کنند و حالا پنج دقیقه‌ای هم در روز در دسترس نباشد اهمیتی ندارد. در سایت‌ها بیشتر این مورد را با اعدادی شبیه 99.9% مشخص می‌کنند و پیشنهاد می‌کنم هاستی را بخرید که این عدد را به شما نشان می‌دهد، یا حداقل دیگر 99.5% کمتر نباشد. البته تمامی هاست‌ها همین را می‌نویسند، ولی واقعیت چیز دیگری است و بهتر از از دوستان و آشنایان و جستجوی در اینترنت و انجمنها به این مورد برسید.

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

برای اینکه بدانیم که سرعت یک هاست چگونه باید ارزیابی شود باید سه مورد را بررسی کنیم :
  1. دیتاسنتر
  2. سرور
  3. نزدیکی سرور به محل زندگی کاربران هدف
دو مورد اول تا حدی فنی هستند و اصلا ممکن هست به چنین اطلاعاتی دسترسی هم نداشته باشید؛ هر چند میزبان - در صورتی که قصد خرید سروری را دارید - در صورت پرسش شما باید این اطلاعات را در اختیار شما بگذارد. عموما چیزی که پیشنهاد می‌شود دیتاسنترهای SAS70 Type 2 هستند و سرورهای DELL، امروزه خوب مورد استقبال قرار گرفته‌اند. در رده‌های بعدی میتوان HP را هم مثال زد.
مورد سوم نزدیکی سرور به کاربران هدف است. هر چقدر traceroute و پینگ زمانی کمتری به سرور داشته باشید، دسترسی به اطلاعات آن سریعتر می‌شود. در ایران عموما از کشورهایی چون آمریکا، کانادا ، انگلیس ، آلمان و استرالیا سرور خریده می‌شود و به مدت چندسالی است که در ایران هم این امکان فراهم شده است ولی خب مسائلی که این سرورها در ایران دارند باعث می‌شوند عده‌ی زیادی دور آن‌ها را که هیچ روی آن‌ها را هم خط بکشند.

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

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

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

جایگاه پایین‌تر SEO : گفتیم که یکی از عوامل سرعت، نزدیک بودن سرور به کاربر هدف است. این مورد برای سرور موتورهای جستجویی مثل گوگل که در ایران سروری ندارند هم اتفاق می‌افتد و در نتیجه گوگل از لحاظ امتیاز بندی سرعت، امتیاز شما را کم می‌کند ولی این مورد همیشه چندان صدق نمی‌کند و مبحث ترافیک سایت بیشتر مورد ارزیابی قرار میگیرد.

امنیت
این مورد شامل دو بخش می‌شود یکی امنیت دسترسی به سرور و دیگر امنیت نگهداری اطلاعات. نصب فایروال‌ها روی سرور و نظارت 24 ساعته روی سرورهای شرکت (که شامل بخش پشتیبانی می‌شود) می‌تواند این موردها را پوشش دهد. هر چند این مورد را زیاد نمی‌توانید محک بزنید، ولی اگر اخباری چون «همکاری با یک شرکت امنیتی جهت تست سرور و همکاری با تعدادی هکر جهت تست سرور» و ... را شنیدید، می‌توانید این اطمینان را کسب کنید که آن‌ها به این مورد اهمیت میدهند.

امنیت اطلاعات چون پشتیبان‌های روزانه و نگهداری اطلاعات تا مدتی معین پس از اتمام قرارداد و موارد این چنینی، می‌تواند شما را مطمئن سازد. در مورد یکی از مشتریانم به خاطر دارم که فراموش کرده بود هاست را تمدید کند و یک روز بعد از انقضاء ما درخواست دیتابیس را داشتیم که برای ما ارسال کنند و به گفته‌ی خودشان ما مسئولیتی در قبال نگه داری اطلاعات نداریم، ولی تا 5 روز نگه میداریم که البته گفتند هر چه به دنبال دیتابیس گشتند، چیزی پیدا نکردند که البته این مشکل را از راهکار دیگری حل کردیم و ماجرا به خوشی تمام شد. ولی جالب این بود که اینقدر که من حرص خوردم، چطوری به مدیر این‌ها بگوئیم که اطلاعات تیمشان پریده و این‌ها حرص نخوردند و بی خیال.

کنترل پنل
می‌گویند کنترل پنل هم چیز مهمی است ولی در اکثر اوقات اکثر هاستینگ‌ها از همان پنل‌های معروف استفاده می‌کنند. برای مثال در لینوکس "سی پنل" و در ویندوز "وب سایت پنل" و "پلسک" می‌باشد . تا آخرین باری که من با آن‌ها کار کردم، وب سایت پنل، یک پنل سبک بود که برای انجام عملیات ساده تا متوسط در زمینه کاری پنل‌ها می‌باشد و پلسک نسبت به آن امکانات خیلی زیادتری دارد. اکثرا آنها حاوی امکانی چون نصب یک کلیکی CMS هایی چون وردپرس و جوملا و ... هستند.

اگر از یک سرور اختصاصی بهره ببرید خودتان مختار نصب هر نوع چیزی روی آن هستید و در مورد کنترل پنل هم صدق می‌کند ولی عموما شرکتها یک سری الگوهای پیش فرضی را برای سرویس‌های خود دارند که در صورت تغییر باید به آن‌ها، تغییرات را طلاع دهید.

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

پشتیبانی هر نوع سرور را مطلع شوید. بعضی‌ها واقعا پشتیبانی دارند!؟ ولی وقتی زنگ می‌زنید می‌گویند این پشتیبانی مربوط به لینوکس است و بچه‌های ویندوز فقط ساعات اداری هستند. خلاصه حواستان را جمع کنید که بچه‌های آن بخش همیشه باشند.
بنابراین مطمئن شوید که یک پشتیبانی 24/7 واقعی داشته باشند.

امکانات اضافه
در کنار خود هاست یک سری ویژگی‌هایی چون FTP و تعدادی دامنه‌های پشتیبانی شده و زیر دامنه‌ها و تعداد دیتابیس‌ها و ایمیل و ... هم هستند که در قدیم یادم هست محدودیت‌هایی در این رابطه ایجاد کرده بودند که امروزه از این نظر در اکثر هاست‌ها آن طور که دیدم این محدودیت‌ها رفع شده است که البته خیلی هم خوب هست و این محدودیت‌ها بیشتر شبیه سودجویی بود تا چیز دیگر.

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

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

هاستینگ در قرارداد

هنگام عقد قرارداد با مشتری، در قرارداد در مورد هاستینگ که خودش تهیه می‌کند، این نکته ذکر شود که شما در مورد مسائلی که مربوط به هاست و دامنه می‌شود هیچ مسئولیتی ندارید و باید مشکلات را با مسئولان آن در میان بگذارد.
همچنین این نکته هم ذکر شود در مورد مسائلی چون عدم پشتیبانی مناسب هاست، از محصول یا سرویس شما، شما هیچگونه مسئولیتی در قبال آن ندارید و یا باید این مورد توسط شما دنبال شود یا خرید ایشان با مشاوره و تایید شما از هاست مربوطه باشد.

در صورتی که مشتری نمیتواند شخصا خرید هاستینگ را انجام دهد یا در شرکت مسئولین IT ندارند که این کار را انجام دهند و این مورد به شما محول میشود، در قرارداد ذکر شود که شما هیچ گونه مسئولیتی در قبال مشکلاتی که برای هاست و دامنه رخ میدهد ندارید و تنها میتوانید به عنوان یک واسط یا وکیل در ازای مبلغ توافق شده‌ای مشکل را با مسئولین هاست و دامنه در میان گذاشته و ماجرا را برای حل مشکل ایجاد شده دنبال کنید یا این کار را به عنوان هدیه ای از طرف خدمات طلایی شما به مشتریان به صورت رایگان انجام دهید.
مطالب
OpenCVSharp #10
محاسبه و ترسیم Histogram تصاویر

هیستوگرام یک تصویر، توزیع میزان روشنایی آن تصویر را نمایش می‌دهد و در آن تعداد نقاط قسمت‌های روشن تصویر، ترسیم می‌شوند. محاسبه‌ی هیستوگرام تصاویر در حین دیباگ الگوریتم‌های پردازش تصویر، کاربرد زیادی دارند.
OpenCV به همراه متد توکاری است به نام cv::calcHist که قادر است هیستوگرام تعدادی آرایه را محاسبه کند و در C++ API آن قرار دارد. البته هدف اصلی این متد، انجام محاسبات مرتبط است و در اینجا قصد داریم این محاسبات را نمایش دهیم.


تغییر میزان روشنایی و وضوح تصاویر در OpenCV

همانطور که عنوان شد، کار هیستوگرام تصاویر، نمایش توزیع میزان روشنایی نقاط و اجزای آن‌ها است. بنابراین می‌توان جهت مشاهده‌ی تغییر هیستوگرام محاسبه شده با تغییر میزان روشنایی و وضوح تصویر، از متد ذیل کمک گرفت:
private static void updateBrightnessContrast(Mat src, Mat modifiedSrc, int brightness, int contrast)
{
    brightness = brightness - 100;
    contrast = contrast - 100;
 
    double alpha, beta;
    if (contrast > 0)
    {
        double delta = 127f * contrast / 100f;
        alpha = 255f / (255f - delta * 2);
        beta = alpha * (brightness - delta);
    }
    else
    {
        double delta = -128f * contrast / 100;
        alpha = (256f - delta * 2) / 255f;
        beta = alpha * brightness + delta;
    }
    src.ConvertTo(modifiedSrc, MatType.CV_8UC3, alpha, beta);
}
در اینجا src تصویر اصلی است. brightness و contrast، مقادیر میزان روشنایی و وضوح دریافتی از کاربر هستند. این مقادیر را می‌توان به متد ConvertTo ارسال کرد تا src را تبدیل به modifiedSrc نماید و وضوح و روشنایی آن‌را تغییر دهد.

پس از اینکه متد تغییر وضوح تصویر اصلی را تهیه کردیم، می‌توان به پنجره‌ی نمایش تصویر اصلی، دو tracker جهت دریافت brightness و contrast اضافه کرد و به این ترتیب امکان نمایش پویای تغییرات را مهیا نمود:
using (var src = new Mat(@"..\..\Images\Penguin.Png", LoadMode.AnyDepth | LoadMode.AnyColor))
{
    using (var sourceWindow = new Window("Source", image: src,
           flags: WindowMode.AutoSize | WindowMode.FreeRatio))
    {
        using (var histogramWindow = new Window("Histogram",
               flags: WindowMode.AutoSize | WindowMode.FreeRatio))
        {
            var brightness = 100;
            var contrast = 100;
 
            var brightnessTrackbar = sourceWindow.CreateTrackbar(
                    name: "Brightness", value: brightness, max: 200,
                    callback: pos =>
                    {
                        brightness = pos;
                        updateImageCalculateHistogram(sourceWindow, histogramWindow, src, brightness, contrast);
                    });
 
            var contrastTrackbar = sourceWindow.CreateTrackbar(
                name: "Contrast", value: contrast, max: 200,
                callback: pos =>
                {
                    contrast = pos;
                    updateImageCalculateHistogram(sourceWindow, histogramWindow, src, brightness, contrast);
                });
 
 
            brightnessTrackbar.Callback.DynamicInvoke(brightness);
            contrastTrackbar.Callback.DynamicInvoke(contrast);
 
            Cv2.WaitKey();
        }
    }
}
در اینجا src تصویر اصلی است. پنجره‌ی Source کار نمایش تصویر اصلی را به عهده دارد. همچنین به این پنجره، دو tracker اضافه شده‌اند تا کار دریافت مقادیر روشنایی و وضوح را از کاربر، مدیریت کنند.
پنجره‌ی دومی نیز به نام هیستوگرام در اینجا تعریف شده‌است. در این پنجره قصد داریم هیستوگرام تغییرات پویای تصویر اصلی را نمایش دهیم.



روش محاسبه‌ی هیستوگرام تصاویر و نمایش آن‌ها در OpenCVSharp

کدهای کامل محاسبه‌ی هیستوگرام تصویر اصلی تغییر یافته (modifiedSrc) و سپس نمایش آن‌را در پنجره‌ی histogramWindow، در ادامه ملاحظه می‌کنید:
private static void calculateHistogram1(Window histogramWindow, Mat src, Mat modifiedSrc)
{
    const int histogramSize = 64;
    int[] dimensions = { histogramSize }; // Histogram size for each dimension
    Rangef[] ranges = { new Rangef(0, histogramSize) }; // min/max
 
    using (var histogram = new Mat())
    {
        Cv2.CalcHist(
            images: new[] { modifiedSrc },
            channels: new[] { 0 },
            mask: null,
            hist: histogram,
            dims: 1,
            histSize: dimensions,
            ranges: ranges);
 
        using (var histogramImage = (Mat)(Mat.Ones(rows: src.Rows, cols: src.Cols, type: MatType.CV_8U) * 255))
        {
            // Scales and draws histogram
 
            Cv2.Normalize(histogram, histogram, 0, histogramImage.Rows, NormType.MinMax);
            var binW = Cv.Round((double)histogramImage.Cols / histogramSize);
 
            var color = Scalar.All(100);
 
            for (var i = 0; i < histogramSize; i++)
            {
                Cv2.Rectangle(histogramImage,
                    new Point(i * binW, histogramImage.Rows),
                    new Point((i + 1) * binW, histogramImage.Rows - Cv.Round(histogram.Get<float>(i))),
                    color,
                    -1);
            }
 
            histogramWindow.Image = histogramImage;
        }
    }
}
معادل متد cv::calcHist، متد Cv2.CalcHist در OpenCVSharp است. این متد آرایه‌ای از تصاویر را قبول می‌کند که در اینجا تنها قصد داریم با یک تصویر کار کنیم. به همین جهت آرایه‌های images، اندازه‌های آن‌ها و بازه‌های min/max این تصاویر تنها یک عضو دارند. خروجی این متد پارامتر hist آن است که توسط یک new Mat تامین شده‌است. مقدار dims به یک تنظیم شده‌است؛ زیرا در اینجا تنها قصد داریم شدت نقاط را اندازه گیری کنیم. پارامتر ranges مشخص می‌کند که مقادیر اندازه گیری شده باید در چه بازه‌ایی جمع آوری شوند.
پس از محاسبه‌ی هیستوگرام، یک تصویر خالی پر شده‌ی با عدد یک را توسط متد Mat.Ones ایجاد می‌کنیم. این تصویر به عنوان منبع تصویر هیستوگرام نمایش داده شده، مورد استفاده قرار می‌گیرد. سپس نیاز است اطلاعات محاسبه شده، در مقیاسی قرار گیرند که قابل نمایش باشد. به همین جهت با استفاده از متد Normalize، آن‌ها را در مقیاس و بازه‌ی ارتفاع تصویر، تغییر اندازه خواهیم داد. سپس به کمک متد مستطیل، خروجی آرایه هیستوگرام را در صفحه، با رنگ خاکستری مشخص شده توسط متد Scalar.All ترسیم خواهیم کرد.


همانطور که در این تصویر ملاحظه می‌کنید، با کدرتر شدن تصویر اصلی، هیستوگرام آن، توزیع روشنایی کمتری را نمایش می‌دهد.


کدهای کامل این مثال را از اینجا می‌توانید دریافت کنید.
مطالب
OpenCVSharp #11
خوشه بندی تصویر به کمک الگوریتم K-Means توسط OpenCV

الگوریتم k-Means clustering را می‌توان به کمک یک مثال بهتر بررسی کرد. فرض کنید شرکت منسوجاتی قرار است پیراهن‌های جدیدی را به بازار ارائه کند. بدیهی است برای فروش بیشتر، بهتر است پیراهن‌هایی را با اندازه‌های متفاوتی تولید کرد تا برای عموم مردم مفید باشد. اما ... برای این شرکت مقرون به صرفه نیست تا برای تمام اندازه‌های ممکن، پیراهن تولید کند. بنابراین اندازه‌های اشخاص را در سه گروه کوچک، متوسط و بزرگ تعریف می‌کند. این گروه بندی را می‌توان توسط الگوریتم k-means clustering نیز انجام داد و به کمک آن به سه اندازه‌ی بسیار مناسب رسید تا برای عموم اشخاص مناسب باشد. حتی اگر این سه گروه ناکافی باشند، این الگوریتم می‌تواند تعداد خوشه بندی‌های متغیری را دریافت کند تا بهینه‌ترین پاسخ حاصل شود. [برای مطالعه بیشتر]

ارتباط الگوریتم k-means clustering با مباحث پردازش تصویر، در پیش پردازش‌های لازمی است که جهت سرفصل‌هایی مانند تشخیص اشیاء، آنالیز صحنه، ردیابی و امثال آن ضروری هستند. از الگوریتم خوشه بندی k-means عموما جهت مفهومی به نام Color Quantization یا کاهش تعداد رنگ‌های تصویر استفاده می‌شود. یکی از مهم‌ترین مزایای این کار، کاهش فشار حافظه و همچنین بالا رفتن سرعت پردازش‌های بعدی بر روی تصویر است. همچنین گاهی از اوقات برای چاپ پوسترها نیاز است تعداد رنگ‌های تصویر را کاهش داد که در اینجا نیز می‌توان از این الگوریتم استفاده کرد.


پیاده سازی الگوریتم خوشه بندی K-means

در ادامه کدهای بکارگیری متد kmeans کتابخانه‌ی OpenCV را به کمک OpenCVSharp مشاهده می‌کنید:
var src = new Mat(@"..\..\Images\fruits.jpg", LoadMode.AnyDepth | LoadMode.AnyColor);
Cv2.ImShow("Source", src);
Cv2.WaitKey(1); // do events
 
Cv2.Blur(src, src, new Size(15, 15));
Cv2.ImShow("Blurred Image", src);
Cv2.WaitKey(1); // do events
 
// Converts the MxNx3 image into a Kx3 matrix where K=MxN and
// each row is now a vector in the 3-D space of RGB.
// change to a Mx3 column vector (M is number of pixels in image)
var columnVector = src.Reshape(cn: 3, rows: src.Rows * src.Cols);
 
// convert to floating point, it is a requirement of the k-means method of OpenCV.
var samples = new Mat();
columnVector.ConvertTo(samples, MatType.CV_32FC3);
 
for (var clustersCount = 2; clustersCount <= 8; clustersCount += 2)
{
    var bestLabels = new Mat();
    var centers = new Mat();
    Cv2.Kmeans(
        data: samples,
        k: clustersCount,
        bestLabels: bestLabels,
        criteria:
            new TermCriteria(type: CriteriaType.Epsilon | CriteriaType.Iteration, maxCount: 10, epsilon: 1.0),
        attempts: 3,
        flags: KMeansFlag.PpCenters,
        centers: centers);
 
 
    var clusteredImage = new Mat(src.Rows, src.Cols, src.Type());
    for (var size = 0; size < src.Cols * src.Rows; size++)
    {
        var clusterIndex = bestLabels.At<int>(0, size);
        var newPixel = new Vec3b
        {
            Item0 = (byte)(centers.At<float>(clusterIndex, 0)), // B
            Item1 = (byte)(centers.At<float>(clusterIndex, 1)), // G
            Item2 = (byte)(centers.At<float>(clusterIndex, 2)) // R
        };
        clusteredImage.Set(size / src.Cols, size % src.Cols, newPixel);
    }
 
    Cv2.ImShow(string.Format("Clustered Image [k:{0}]", clustersCount), clusteredImage);
    Cv2.WaitKey(1); // do events
}
 
Cv2.WaitKey();
Cv2.DestroyAllWindows();
با این خروجی


توضیحات

- ابتدا تصویر اصلی برنامه بارگذاری می‌شود و در یک پنجره نمایش داده خواهد شد. در اینجا متد Cv2.WaitKey را با پارامتر یک، مشاهده می‌کنید. این فراخوانی ویژه‌، شبیه به متد do events در برنامه‌های WinForms است. اگر فراخوانی نشود، تمام تصاویر پنجره‌های مختلف برنامه تا زمان پایان پردازش‌های مختلف برنامه، نمایش داده نخواهند شد و تا آن زمان صرفا یک یا چند پنجره‌ی خاکستری رنگ را مشاهده خواهید کرد.
- در ادامه متد Blur بر روی این تصویر فراخوانی شده‌است تا مقداری تصویر را مات کند. هدف از بکارگیری این متد در این مثال، برجسته کردن خوشه بندی گروه‌های رنگی مختلف در تصویر اصلی است.
- سپس متد Reshape بر روی ماتریس تصویر اصلی بارگذاری شده فراخوانی می‌شود.
هدف از بکارگیری الگوریتم k-means، انتساب برچسب‌هایی به هر نقطه‌ی RGB تصویر است. در اینجا هر نقطه به شکل یک بردار در فضای سه بعدی مشاهده می‌شود. سپس سعی خواهد شد تا این MxN بردار، به k قسمت تقسیم شوند.
متد Reshape تصویر اصلی MxNx3 را به یک ماتریس Kx3 تبدیل می‌کند که در آن K=MxN است و اکنون هر ردیف آن برداری است در فضای سه بعدی RGB.
- پس از آن توسط متد ConvertTo، نوع داده‌های این ماتریس جدید به float تبدیل می‌شوند تا در متد kmeans قابل استفاده شوند.
- در ادامه یک حلقه را مشاهده می‌کنید که عملیات کاهش رنگ‌های تصویر و خوشه بندی آن‌ها را 4 بار با مقادیر مختلف clustersCount انجام می‌دهد.
- در متد kmeans، پارامتر data یک ماتریس float است که هر نمونه‌ی آن در یک ردیف قرار گرفته‌است. K بیانگر تعداد خوشه‌ها، جهت تقسیم داده‌ها است.
در اینجا پارامترهای labels و centers خروجی‌های متد هستند. برچسب‌ها بیانگر اندیس‌های هر خوشه به ازای هر نمونه هستند. Centers ماتریس مراکز هر خوشه است و دارای یک ردیف به ازای هر خوشه است.
پارامتر criteria آن مشخص می‌کند که الگوریتم چگونه باید خاتمه یابد که در آن حداکثر تعداد بررسی‌ها و یا دقت مورد نظر مشخص می‌شوند.
پارامتر attempts مشخص می‌کند که این الگوریتم چندبار باید اجرا شود تا بهترین میزان فشردگی و کاهش رنگ حاصل شود.
- پس از پایان عملیات k-means نیاز است تا اطلاعات آن مجددا به شکل ماتریسی هم اندازه‌ی تصویر اصلی برگردانده شود تا بتوان آن‌را نمایش داد. در اینجا بهتر می‌توان نحوه‌ی عملکرد متد k-means را درک کرد. حلقه‌ی تشکیل شده به اندازه‌ی تمام نقاط طول و عرض تصویر اصلی است. به ازای هر نقطه، توسط الگوریتم k-means یک برچسب تشکیل شده (bestLabels) که مشخص می‌کند این نقطه متعلق به کدام خوشه و cluster رنگ‌های کاهش یافته است. سپس بر اساس این اندیس می‌توان رنگ این نقطه را از خروجی centers یافته و در یک تصویر جدید نمایش داد.



کدهای کامل این مثال را از اینجا می‌توانید دریافت کنید.
نظرات مطالب
فقط به خاطر یک نیم فاصله!
سلام،

راه حلی که من برای دور زدن این اشکال MKLC پیدا کرده بودم این بود که یک برنامه کوچک که خط فرمان را در جایی ذخیره می‌کرد نوشتم و آن را جایگزین فایل‌های cl.exe و link.exe که به همراه MKLC می‌آیند کردم.

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

من به این روش فایل C تولید شده را ویرایش می‌کردم و ترکیب Shift+Space را در آن اصلاح می‌کردم و با خط فرمانی که به کمک برنامه ذکر شده به دست می‌آوردم صفحه کلید را کامپایل می‌کردم.

ولی راه حل شما هم خیلی جالبه و فکر می‌کنم که برای نسخه‌های بعدی من هم از آن استفاده کنم.
مطالب
کار با بانک‌های اطلاعاتی مختلف در PdfReport
تعدادی از منابع داده پیش فرض PdfReport جهت کار مستقیم با بانک‌های اطلاعاتی مختلف، کوئری نوشتن و نمایش نتایج آن‌ها طراحی شده‌اند.
در این بین با توجه به اینکه دات نت پشتیبانی توکاری از SQL Server دارد، اتصال و استفاده از توانمندی‌های آن نیاز به کتابخانه جانبی خاصی ندارد. اما برای کار با بانک‌های اطلاعاتی دیگر نیاز خواهد بود تا پروایدر ADO.NET آن‌ها را تهیه و به برنامه اضافه کنیم.
چهار نمونه از منابع داده پیش فرضی که در متد MainTableDataSource قابل تعریف هستند به شرح زیر می‌باشند:
public void SqlDataReader(string connectionString, string sql, params object[] parametersValues)

//.mdb or .accdb files
public void AccessDataReader(string filePath, string password, string sql, params object[] parametersValues)

public void OdbcDataReader(string connectionString, string sql, params object[] parametersValues)
SqlDataReader برای کار با بانک‌های اطلاعاتی SQL Server بهینه سازی شده است.
AccessDataReader قابلیت اتصال به بانک‌های اطلاعاتی اکسس جدید (فایل‌های accdb) و اکسس قدیم (فایل‌های mdb) را دارد.
OdbcDataReader یک پروایدر عمومی است که از روز اول دات نت به همراه آن بوده است. برای مثال جهت اتصال به بانک‌های اطلاعاتی فاکس‌پرو می‌تواند مورد استفاده قرار گیرد.
اما ... برای مابقی بانک‌های اطلاعاتی چطور؟
برای سایر بانک‌های اطلاعاتی، منبع داده عمومی زیر تدارک دیده شده است:
public void GenericDataReader(string providerName, string connectionString, string sql, params object[] parametersValues)
تنها تفاوت آن با نمونه‌های قبل، ذکر providerName آن است. برای مثال جهت اتصال به SQLite ابتدا پروایدر مخصوص ADO.NET آن‌را دریافت و به پروژه خود اضافه نمائید. سپس پارامتر providerName فوق را با "System.Data.SQLite" مقدار دهی کنید.

یک نکته:
در تمام منابع داده فوق، امکان نوشتن کوئری‌های پارامتری نیز پیش بینی شده است. فقط باید دقت داشت که پارامترهای معرفی شده باید با @ شروع شوند که یک نمونه از آن‌را در مثال جاری ملاحظه خواهید نمود.

در ادامه نحوه تهیه گزارش از یک بانک اطلاعاتی SQLite را توسط PdfReport بررسی خواهیم کرد:

using System;
using PdfRpt.Core.Contracts;
using PdfRpt.Core.Helper;
using PdfRpt.FluentInterface;

namespace PdfReportSamples.SQLiteDataReader
{
    public class SQLiteDataReaderPdfReport
    {
        public IPdfReportData CreatePdfReport()
        {
            return new PdfReport().DocumentPreferences(doc =>
            {
                doc.RunDirection(PdfRunDirection.RightToLeft);
                doc.Orientation(PageOrientation.Portrait);
                doc.PageSize(PdfPageSize.A4);
                doc.DocumentMetadata(new DocumentMetadata { Author = "Vahid", Application = "PdfRpt", Keywords = "Test", Subject = "Test Rpt", Title = "Test" });
            })
            .DefaultFonts(fonts =>
            {
                fonts.Path(AppPath.ApplicationPath + "\\fonts\\irsans.ttf",
                                  Environment.GetEnvironmentVariable("SystemRoot") + "\\fonts\\verdana.ttf");
            })
            .PagesFooter(footer =>
            {
                footer.DefaultFooter(DateTime.Now.ToString("MM/dd/yyyy"));
            })
            .PagesHeader(header =>
            {
                header.DefaultHeader(defaultHeader =>
                {
                    defaultHeader.RunDirection(PdfRunDirection.RightToLeft);
                    defaultHeader.ImagePath(AppPath.ApplicationPath + "\\Images\\01.png");
                    defaultHeader.Message("گزارش جدید ما");
                });
            })
            .MainTableTemplate(template =>
            {
                template.BasicTemplate(BasicTemplate.SilverTemplate);
            })
            .MainTablePreferences(table =>
            {
                table.ColumnsWidthsType(TableColumnWidthType.Relative);
                table.NumberOfDataRowsPerPage(5);
            })
            .MainTableDataSource(dataSource =>
            {
                dataSource.GenericDataReader(
                    providerName: "System.Data.SQLite",
                    connectionString: "Data Source=" + AppPath.ApplicationPath + "\\data\\blogs.sqlite",
                    sql: @"SELECT [url], [name], [NumberOfPosts], [AddDate]
                               FROM [tblBlogs]
                               WHERE [NumberOfPosts]>=@p1",
                    parametersValues: new object[] { 10 }
                );
            })
            .MainTableSummarySettings(summarySettings =>
            {
                summarySettings.OverallSummarySettings("جمع کل");
                summarySettings.PreviousPageSummarySettings("نقل از صفحه قبل");
                summarySettings.PageSummarySettings("جمع صفحه");
            })
            .MainTableColumns(columns =>
            {
                columns.AddColumn(column =>
                {
                    column.PropertyName("rowNo");
                    column.IsRowNumber(true);
                    column.CellsHorizontalAlignment(HorizontalAlignment.Center);
                    column.IsVisible(true);
                    column.Order(0);
                    column.Width(1);
                    column.HeaderCell("ردیف");
                });

                columns.AddColumn(column =>
                {
                    column.PropertyName("url");
                    column.CellsHorizontalAlignment(HorizontalAlignment.Center);
                    column.IsVisible(true);
                    column.Order(1);
                    column.Width(2);
                    column.HeaderCell("آدرس");
                    column.ColumnItemsTemplate(template =>
                    {
                        template.Hyperlink(foreColor: System.Drawing.Color.Blue, fontUnderline: true);
                    });
                });

                columns.AddColumn(column =>
                {
                    column.PropertyName("name");
                    column.CellsHorizontalAlignment(HorizontalAlignment.Center);
                    column.IsVisible(true);
                    column.Order(2);
                    column.Width(2);
                    column.HeaderCell("نام");
                });

                columns.AddColumn(column =>
                {
                    column.PropertyName("NumberOfPosts");
                    column.CellsHorizontalAlignment(HorizontalAlignment.Center);
                    column.IsVisible(true);
                    column.Order(3);
                    column.Width(2);
                    column.HeaderCell("تعداد مطلب");
                    column.ColumnItemsTemplate(template =>
                    {
                        template.TextBlock();
                        template.DisplayFormatFormula(obj => obj == null ? string.Empty : string.Format("{0:n0}", obj));
                    });
                    column.AggregateFunction(aggregateFunction =>
                    {
                        aggregateFunction.NumericAggregateFunction(AggregateFunction.Sum);
                        aggregateFunction.DisplayFormatFormula(obj => obj == null ? string.Empty : string.Format("{0:n0}", obj));
                    });
                });

                columns.AddColumn(column =>
                {
                    column.PropertyName("AddDate");
                    column.CellsHorizontalAlignment(HorizontalAlignment.Center);
                    column.IsVisible(true);
                    column.Order(4);
                    column.Width(2);
                    column.HeaderCell("تاریخ ثبت");
                    column.ColumnItemsTemplate(template =>
                    {
                        template.TextBlock();
                        template.DisplayFormatFormula(obj => obj == null ? string.Empty : PersianDate.ToPersianDateTime((DateTime)obj) /*((DateTime)obj).ToString("dd/MM/yyyy HH:mm")*/);
                    });
                });
            })
            .MainTableEvents(events =>
            {
                events.DataSourceIsEmpty(message: "There is no data available to display.");
            })
            .Export(export =>
            {
                export.ToExcel();
            })
            .Generate(data => data.AsPdfFile(AppPath.ApplicationPath + "\\Pdf\\RptSqlDataReaderSample.pdf"));
        }
    }
}

توضیحات:

- در مثال فوق نحوه استفاده از یک بانک اطلاعاتی SQLite را ملاحظه می‌کنید. این بانک اطلاعاتی نمونه در پوشه bin\data سورس به روز شده پروژه موجود است.
                dataSource.GenericDataReader(
                    providerName: "System.Data.SQLite",
                    connectionString: "Data Source=" + AppPath.ApplicationPath + "\\data\\blogs.sqlite",
                    sql: @"SELECT [url], [name], [NumberOfPosts], [AddDate]
                               FROM [tblBlogs]
                               WHERE [NumberOfPosts]>=@p1",
                    parametersValues: new object[] { 10 }
                );
فرض بر این است که فایل‌های System.Data.SQLite.dll و SQLite.Interop.dll را از سایت SQLite دریافت کرده و سپس ارجاعی را به اسمبلی System.Data.SQLite.dll به پروژه خود افزوده‌اید.
در مرحله بعد به کمک GenericDataReader می‌توان به این پروایدر دسترسی یافت. همانطور که ملاحظه می‌کنید یک کوئری پارامتری با مقدار پارامتر مساوی 10 جهت تهیه گزارش، تعریف شده است.
همچنین باید دقت داشت که اگر پروژه جاری شما مبتنی بر دات نت 4 است، نیاز خواهید داشت چند سطر زیر را به فایل config برنامه اضافه نمائید تا با SQLite مشکلی نداشته باشد:
<?xml version="1.0"?>
   <configuration>
      <startup useLegacyV2RuntimeActivationPolicy="true">
           <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0"/>
      </startup>
   </configuration>                  
- مرحله بعد نوبت به معرفی ستون‌های گزارش است. هر ستون، معادل یک فیلد معرفی شده در کوئری SQL ارسال شده به GenericDataReader خواهد بود (کوچکی و بزرگی حروف باید در اینجا رعایت شوند).
- در حین معرفی ستون AddDate، نحوه نمایش و تبدیل تاریخ دریافتی که با فرمت DateTime است را به تاریخ شمسی ملاحظه می‌کنید. متد PersianDate.ToPersianDateTime در فضای نام PdfRpt.Core.Helper قرار دارد. توسط DisplayFormatFormula، فرصت خواهید داشت مقدار متناظر با سلول در حال رندر را پیش از نمایش، به هر نحو دلخواهی فرمت کنید.
- در ستون url از قالب نمایشی پیش فرض Hyperlink، برای نمایش اطلاعات فیلد جاری به صورت یک لینک قابل کلیک استفاده شده است.

یک نکته:
ذکر قسمت MainTableColumns و تمام تعاریف مرتبط با آن در PdfReports اختیاری است. به این معنا که می‌توانید قسمت گزارش سازی و تعاریف گزارشات برنامه خود را پویا کنید (شبیه به حالت auto generate columns در گرید‌های معروف). کوئری‌های SQL متناظر با گزارشات را در بانک اطلاعاتی ذخیره کنید و به گزارش ساز فوق ارسال نمائید. حاصل یک گزارش جدید است.