مطالب دوره‌ها
مروری مختصر بر زبان DMX
این بخش مروری اجمالی است بر زبان (DMX (Data Mining eXtensions که به منظور انجام عملیات داده کاوی توسط شرکت ماکروسافت ایجاد شده است. (از آنجا که هدف این دوره معرفی الگوریتم‌های داده کاوی است از این رو به صورت کلی به بررسی این زبان می‌پردازیم)
برای بسیاری داده کاوی تنها مجموعه ای از تعدادی الگوریتم تعبیر می‌شود؛ به همان طریقی که در گذشته تصورشان از بانک اطلاعاتی تنها ساختاری سلسله مراتبی به منظور ذخیره داده‌ها بود. بدین ترتیب داده کاوی به ابزاری تبدیل شده که تنها در انحصار تعدادی متخصص (بویژه PhD‌های علم آمار و یادگیری ماشین) قرار دارد که آشنائی با اصطلاحات یک زمینه خاص را دارند. هدف از ایجاد زبان DMX تعریف مفاهیمی استاندارد و گزارهایی متداول است که در دنیای داده کاوی استفاده می‌شود به شکلی که زبان SQL برای بانک اطلاعاتی این کار را انجام می‌دهد.
فرضیه اساسی در داده کاوی و همچنین یادگیری ماشین از این قرار است که تعدادی نمونه به الگوریتم نشان داده می‌شود و الگوریتم با استفاده از این نمونه‌ها قادر است به استخراج الگوها بپردازد. بدین ترتیب به منظور بازبینی و همچنین استنتاج از اطلاعات درباره نمونه‌های جدید می‌تواند مورد استفاده قرار گیرد.
ذکر این نکته ضروری است که الگوهای استخراج شده می‌توانند مفید، آموزنده و دقیق باشند. تصویر زیر به اختصار مراحل فرآیند داده کاوی را نمایان می‌سازد:

در گام نخست اقدام به تعریف مسئله و فرموله کردن آن می‌کنیم که اصطلاحاً Mining Model نامیده می‌شود. در واقع Mining Model توصیف کننده این است که داده نمونه به چه شکل به نظر می‌رسد و چگونه الگوریتم داده کاوی باید داده‌ها را تفسیر کند. در گام بعدی به فراهم کردن نمونه‌های داده برای الگوریتم می‌پردازیم، الگوریتم با بهره گیری از Mining Model به طریقی که یک لنز داده‌ها را مرتب می‌کند، به بررسی داده‌ها و استخراج الگوها می‌پردازد؛ این عملیات را اصطلاحاً Training Model می‌نامیم. هنگامی که این عملیات به پایان رسید، بسته به اینکه چگونه آنرا انجام داده اید، می‌توانید به تحلیل الگوهایی که توسط الگوریتم از روی نمونه هایتان بدست آمده بپردازید. و در نهایت می‌توانید اقدام به فراهم کردن داده‌های جدید و فرموله کردن آنها، به همان طریقی که نمونه‌ها آموزش دیده اند، به منظور انجام پیش بینی و استنتاج از اطلاعات با استفاده از الگوهای کشف شده توسط الگوریتم پرداخت.

زبان DMX وظیفه تبدیل داده‌های موجودتان (سطرها و ستون‌های Tables) به داده‌های مورد نیاز الگوریتم‌های داده کاوی (Cases و Attributes) را دارد. به منظور انجام این تبدیل به Mining Structure و Mining Model (که در قسمت اول به شرح آن پرداخته شد) نیاز است. بطور خلاصه Mining Structure صورت مسئله را توصیف می‌کند و Mining Model وظیفه تبدیل سطرهای داده ای به درون Case‌ها و انجام عملیات یادگیری ماشین با استفاده از الگوریتم داده کاوی مشخص شده را بر عهده دارد.

Syntax زبان DMX
مشابه زبان SQL دستورات زبان DMX نیز به محیطی جهت اجرا نیاز دارند که می‌توان با استفاده از (SQL Server Management Studio (SSMS به اجرای دستورات DMX اقدام نمود. ایجاد ساختار کاوش (Mining Structure) و مدل کاوشی (Mining Model) مشابه دستورات ایجاد Table در زبان SQL می‌باشد. همانطور که اشاره شد، گام اول (از سه مرحله اصلی در داده کاوی) ایجاد یک مدل کاوش است؛ شامل تعیین تعداد ستون‌های ورودی، ستون‌های قابل پیش بینی و مشخص کردن نام الگوریتم مورد استفاده در مدل. گام دوم آموزش مدل که پردازش نیز نامیده می‌شود و گام سوم مرحله پیش بینی است که نیاز به یک مدل کاوش آموزش دیده و مجموعه اطلاعات جدید دارد. در طول پیش بینی، موتور داده کاوی قوانین (Rules) پیدا شده در مرحله‌ی آموزش (یادگیری) را با مجموعه اطلاعات جدید تطبیق داده و نتیجه پیش بینی را برای هر Case ورودی انجام می‌دهد. دو نوع پرس و جوی پیش بینی وجود دارد Batch و Singleton که به ترتیب چند Case ورودی دارد و خروجی در یک جدول ذخیره می‌شود و دیگری تنها یک Case ورودی دارد و خروجی در زمان اجرا ساخته می‌شود.

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

عناصر سازنده‌ی ساختار کاوش، ستون‌های ساختار کاوشی هستند که داده هایی را که منبع اصلی داده فراهم می‌کند، توصیف می‌کند. این ستون‌ها شامل اطلاعاتی از قبیل نوع داده (Data Type)، نوع محتوا (Content Type)، ماهیت داده و اینکه داده چگونه توزیع شده است می‌باشند. نوع محتوا پیوسته و یا گسسته بودن آن را مشخص می‌کند و بدین ترتیب به الگوریتم راه درست مدل کردن ستون را نشان می‌دهیم. کلمه کلیدی Discrete برای ماهیت گسسته داده و از کلمه Continuous برای ماهیت پیوسته داده استفاده می‌شود. مقادیر نوع داده و نوع محتوا به قرار زیر می‌باشند:

Data Type
کاربرد
 LONG   اعداد صحیح 
 DOUBLE   اعداد اعشاری 
 TEXT   داده‌های رشته ای 
 DATE   داده‌های تاریخی 
 BOOLEAN   داده‌های منطقی (True و False) 
 TABLE   برای تعریف Nested Case 
Content Type 
 کاربرد 
 KEY   مشخص کننده کلید 
 DISCRETE   داده‌های گسسته 
 CONTINUOUS   داده‌های پیوسته 
 DISCRETIZED   داده‌های گسسته شده 
 KEY TIME   کلید زمان، تنها در مدل‌های Time Series استفاده می‌شود 
 KEY SEQUENCE   کلید توالی، تنها در بخش Nested Table مدل‌های Sequence Clustering استفاده می‌شود 

همچنین یک مدل کاوش استفاده و کاربرد هر ستون و الگوریتمی که برای ساخت مدل استفاده می‌شود را تعریف می‌کند، می‌توانید با استفاده از کلمه کلیدی Predict و یا Predict_Only خاصیت پیش بینی را به ستون‌ها اضافه نمود، برای نمونه به دستورات زیر توجه نمائید:

CREATE MINING STRUCTURE [New Mailing]
(
CustomerKey LONG KEY,
Gender TEXT DISCRETE,
[Number Cars Owned] LONG DISCRETE,
[Bike Buyer] LONG DISCRETE
)
GO
ALTER MINING STRUCTURE [New Mailing]
ADD MINING MODEL [Naive Bayes]
(
CustomerKey,
Gender,
[Number Cars Owned],
[Bike Buyer] PREDICT
)
USING Microsoft_Naive_Bayes
شکل زیر نشان دهنده ارتباط بین ساختار کاوش و مدل کاوشی پس از ایجاد در محیط SSMS می‌باشد. 

به منظور آموزش یک مدل کاوش از دستور Insert به شکل زیر استفاده می‌شود: 

INSERT INTO <mining model name>
[<mapped model columns>]
<source data query>
که source data query می‌تواند یک پرس و جوی Select از بانک اطلاعاتی باشد که معمولاً با استفاده از سه طریق OPENQUERY، OPENROWSET و SHAPE  بدست می‌آید.
در ادامه به شکل عملی می‌توانید با طی مراحل و اجرای کوئری‌های زیر به بررسی بیشتر موضوع بپردازید.
ابتدا به سرویس SSAS متصل شوید و اقدام به ایجاد یک Database با تنظیمات پیش فرض (مثلاً با نام DM-02) نمائید و در ادامه کوئری XMLA زیر را جهت ایجاد Data Source ای به بانک AdventureWorksDW2012 موجود روی دستگاه تان، اجرا نمائید.
<Create xmlns="http://schemas.microsoft.com/analysisservices/2003/engine">
<ParentObject>
  <DatabaseID>DM-02</DatabaseID>
</ParentObject>
<ObjectDefinition>
  <DataSource xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:ddl2="http://schemas.microsoft.com/analysisservices/2003/engine/2"
xmlns:ddl2_2="http://schemas.microsoft.com/analysisservices/2003/engine/2/2"
xmlns:ddl100_100="http://schemas.microsoft.com/analysisservices/2008/engine/100/100"
xmlns:ddl200="http://schemas.microsoft.com/analysisservices/2010/engine/200"
xmlns:ddl200_200="http://schemas.microsoft.com/analysisservices/2010/engine/200/200"
xmlns:ddl300="http://schemas.microsoft.com/analysisservices/2011/engine/300"
xmlns:ddl300_300="http://schemas.microsoft.com/analysisservices/2011/engine/300/300"
xmlns:ddl400="http://schemas.microsoft.com/analysisservices/2012/engine/400"
xmlns:ddl400_400="http://schemas.microsoft.com/analysisservices/2012/engine/400/400"
xsi:type="RelationalDataSource">
<ID>Adventure Works DW2012</ID>
<Name>Adventure Works DW2012</Name>
<ConnectionString>Provider=SQLNCLI11.1;Data Source=(local);Integrated Security=SSPI;
Initial Catalog=AdventureWorksDW2012</ConnectionString>
<ImpersonationInfo>
<ImpersonationMode>ImpersonateCurrentUser</ImpersonationMode>
</ImpersonationInfo>
<Timeout>PT0S</Timeout>
  </DataSource>
</ObjectDefinition>
</Create>
و در ادامه کوئری‌های DMX زیر را اجرا نمائید و خروجی هر یک را تحلیل نمائید.
 /* Step 1 */
CREATE MINING MODEL [NBSample]
(
CustomerKey LONG KEY,
Gender TEXT DISCRETE,
[Number Cars Owned] LONG DISCRETE,
[Bike Buyer] LONG DISCRETE PREDICT
)
USING Microsoft_Naive_Bayes
Go

/* Step 2 */
INSERT INTO NBSample (CustomerKey, Gender, [Number Cars Owned],
[Bike Buyer])
OPENQUERY([Adventure Works DW2012],'Select CustomerKey, Gender, [NumberCarsOwned], [BikeBuyer]
FROM [vTargetMail]')

/*  */
SELECT * FROM [NBSample].CONTENT

/*  */
SELECT * FROM [NBSample_Structure].CASES

/* Step 3*/

SELECT FLATTENED MODEL_NAME,
(SELECT ATTRIBUTE_NAME, ATTRIBUTE_VALUE, [SUPPORT], [PROBABILITY], VALUETYPE FROM NODE_DISTRIBUTION) AS t
FROM [NBSample].CONTENT
WHERE NODE_TYPE = 26
در قسمت‌های بعد تا حدی که از هدف اصلی دوره بررسی الگوریتم‌های داده کاوی موجود در SSAS دور نیافتیم، به بررسی بیشتر دستورات DMX می‌پردازیم. جهت اطلاعات بیشتر در مورد زبان DMX می‌توانید به Books Online for SQL Server مراجعه نمائید.
 
مطالب
Blazor 5x - قسمت 20 - کار با فرم‌ها - بخش 8 - استفاده از یک کامپوننت ثالث HTML Editor
در این قسمت می‌خواهیم بجای دریافت اطلاعات توضیحات یک اتاق، توسط یک text area متداول، برای مثال از Quill rich text editor استفاده کنیم. برای این منظور می‌توان از کامپوننت Blazor محصور کننده‌ی آن به نام Blazored TextEditor کمک گرفت.


نصب کامپوننت Blazored TextEditor

ابتدا نیاز است بسته‌ی نیوگت آن‌را با اجرای دستور زیر، به پروژه‌ی Blazor خود اضافه کرد:
dotnet add package Blazored.TextEditor
و همچنین کتابخانه‌ی اصلی quill را نیز در مسیر wwwroot/lib/quill نصب می‌کنیم:
libman install quill --provider unpkg --destination wwwroot/lib/quill
سپس به فایل Pages\_Host.cshtml مراجعه کرده و ابتدا مداخل تعریف فایل‌های CSS آن‌را اضافه می‌کنیم:
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>BlazorServer.App</title>
    <base href="~/" />
    <link href="lib/quill/dist/quill.snow.css" rel="stylesheet" />
    <link href="lib/quill/dist/quill.bubble.css" rel="stylesheet" />
و در ادامه سه مدخل اسکریپتی زیر را نیز به قسمت پیش از بسته شدن تگ body، اضافه می‌کنیم:
 <script src="lib/quill/dist/quill.min.js"></script>
<script src="_content/Blazored.TextEditor/quill-blot-formatter.min.js"></script>
<script src="_content/Blazored.TextEditor/Blazored-BlazorQuill.js"></script>
<script src="_framework/blazor.server.js"></script>
</body>
اگر برنامه‌ی مورد نظر از نوع Blazor WASM است، این تنظیمات به فایل wwwroot\index.html منتقل می‌شوند.

و در آخر جهت سهولت کار با این کامپوننت می‌توان فضای نام آن‌را به فایل BlazorServer.App\_Imports.razor به صورت زیر اضافه کرد:
@using Blazored.TextEditor


استفاده از کامپوننت Blazored.TextEditor در کامپوننت HotelRoomUpsert.razor

می‌خواهیم در کامپوننت HotelRoomUpsert.razor مثال این سری، بجای کامپوننت InputTextArea مورد استفاده، از یک HTML Editor استفاده کنیم:
<div class="form-group">
    <label>Details</label>
    @*<InputTextArea @bind-Value="HotelRoomModel.Details" class="form-control"></InputTextArea>*@
    <BlazoredTextEditor @ref="@QuillHtml">
        <ToolbarContent>
            <select class="ql-header">
                <option selected=""></option>
                <option value="1"></option>
                <option value="2"></option>
                <option value="3"></option>
                <option value="4"></option>
                <option value="5"></option>
            </select>
            <span class="ql-formats">
                <button class="ql-bold"></button>
                <button class="ql-italic"></button>
                <button class="ql-underline"></button>
                <button class="ql-strike"></button>
            </span>
            <span class="ql-formats">
                <select class="ql-color"></select>
                <select class="ql-background"></select>
            </span>
            <span class="ql-formats">
                <button class="ql-list" value="ordered"></button>
                <button class="ql-list" value="bullet"></button>
            </span>
            <span class="ql-formats">
                <button class="ql-link"></button>
            </span>
        </ToolbarContent>
        <EditorContent>
        </EditorContent>
    </BlazoredTextEditor>
</div>
- در اینجا قسمت محتوای EditorContent مثال آن‌را خالی کرده‌ایم.
- همانطور که ملاحظه می‌کنید، این تعریف به همراه یک ارجاع به وهله‌ای از آن نیز هست:
<BlazoredTextEditor @ref="@QuillHtml">
به همین جهت نیاز است فیلد متناظر با آن‌را در قسمت کدهای کامپوننت، به صورت زیر تعریف کرد:
@code
{
   private BlazoredTextEditor QuillHtml;
تا اینجا اگر برنامه را اجرا کنیم، به خروجی زیر می‌رسیم:


برای تغییر اندازه و مقدار placeholder پیش‌فرض آن، می‌توان به صورت زیر عمل کرد:
<div class="form-group pb-4" style="height:250px;">
    <label>Details</label>
    <BlazoredTextEditor @ref="@QuillHtml" Placeholder="Please enter the room's detail">


تنظیم و دریافت متن نمایشی HTML Editor

مطابق مستندات این کامپوننت، روش تنظیم متن نمایشی آن، به کمک متد LoadHTMLContent است. به همین جهت متد زیر را به کدهای کامپوننت جاری اضافه می‌کنیم:
    private async Task SetHTMLAsync()
    {
        if(!string.IsNullOrEmpty(HotelRoomModel.Details))
        {
            await QuillHtml.LoadHTMLContent(HotelRoomModel.Details);
        }
    }
بنابراین روش متداول two-way binding در اینجا کار نمی‌کند و باید متن این ادیتور را به نحو فوق تنظیم کرد و برای مثال در زمان بارگذاری اولیه‌ی این کامپوننت و در حالت ویرایش، متن دریافتی از بانک اطلاعاتی را به ادیتور فوق ارسال نمود:
    protected override async Task OnInitializedAsync()
    {
        if (Id.HasValue)
        {
            // Update Mode
            Title = "Update";
            HotelRoomModel = await HotelRoomService.GetHotelRoomAsync(Id.Value);
            await SetHTMLAsync();
        }

        // ... 
    }
و یا در زمان ثبت اولیه و یا حتی در حالت ویرایش اطلاعات در متد HandleHotelRoomUpsert، با استفاده از متد GetHTML آن، خاصیت HotelRoomModel.Details را مقدار دهی اولیه کرد:
    private async Task HandleHotelRoomUpsert()
    {
       // ...

       // Create Mode
       HotelRoomModel.Details = await QuillHtml.GetHTML();

       // ...
    }

مشکل! ادیتور در زمان ویرایش یک رکورد، اطلاعات پیشین را نمایش نمی‌دهد!

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

پس از هر بار رندر کامپوننت، این متدها فراخوانی می‌شوند. در این مرحله کار بارگذاری کامپوننت، دریافت اطلاعات و نمایش آن‌ها به پایان رسیده‌است. یکی از کاربردهای آن، آغاز کامپوننت‌های جاوا اسکریپتی است که برای کار، نیاز به DOM را دارند؛ مانند نمایش یک modal بوت استرپی.»

بنابراین در این حالت خاص که ادیتور جاوا اسکریپتی مورد استفاده، پس از رندر کامل UI نمایش داده می‌شود، قرار دادن متد SetHTML در روال رویدادگردان OnInitializedAsync کار نخواهد کرد و باید آن‌را به روال رویدادگردان OnAfterRender انتقال دهیم:
protected override async Task OnAfterRenderAsync(bool firstRender)
{
   await SetHTMLAsync();
}
پس از این تغییرات هم باز متن وارد شده‌ی در قسمت توضیحات، در حالت ویرایش نمایش داده نمی‌شود! علت آن‌را نیز در مطلب بررسی چرخه‌ی حیات کامپوننت‌ها بررسی کردیم: «یک نکته: هر تغییری که در مقادیر فیلدها در این رویدادها صورت گیرند، به UI اعمال نمی‌شوند؛ چون در مرحله‌ی آخر رندر UI قرار دارند.» به همین جهت نیاز به فراخوانی دستی StateHasChanged وجود دارد:
    private async Task SetHTMLAsync()
    {
        if(!string.IsNullOrEmpty(HotelRoomModel.Details))
        {
            await QuillHtml.LoadHTMLContent(HotelRoomModel.Details);
            StateHasChanged();
        }
    }


مشکل! اگر در این حالت سعی کنیم متنی را در ادیتور وارد کنیم، میسر نیست و همچنین CPU Usage سیستم به 100 درصد رسیده‌است!

علت اینجا است که فراخوانی StateHasChanged، هر چند سبب رندر مجدد UI می‌شود، اما چون در پایان کار رندر قرار داریم، یک حلقه‌ی بی‌نهایت را سبب خواهد شد. به همین جهت باید در متد OnAfterRenderAsync، بر اساس پارامتر firstRender، از رندرهای بعدی جلوگیری کرد:
    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (!firstRender)
        {
            return;
        }

        while (true)
        {
            try
            {
                await SetHTMLAsync();
                break;
            }
            catch
            {
                await Task.Delay(100); // Quill needs some time to load
            }
        }
    }
در اینجا هم مدیریت firstRender را مشاهده می‌کنید، تا دیگر یک حلقه‌ی بی‌نهایت رخ ندهد و هم حلقه‌ای را جهت منتظر ماندن تا بارگذاری کامل Quill در این مثال. این افزونه‌ی جاوا اسکریپتی، حتی پس از پایان رندر کامپوننت هم نیاز به مدت زمانی دارد تا بتواند کامل بارگذاری شده و قابل استفاده شود.



کدهای کامل این مطلب را از اینجا می‌توانید دریافت کنید: Blazor-5x-Part-20.zip
مطالب
معرفی Xamarin و مزیت‌های استفاده از آن

چرا برنامه نویسی موبایل؟

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

شرکت xamarin کار خود را در سال 2011 با ارایه نسخه Cross Platform پلتفرم .Net به نام Mono آغاز کرد. بعد از ارایه این نسخه از .Net، زامارین به کمک Mono توانست پیاده سازی بر روی  Android و IOS را به نام‌های MonoForAndroid  و MonoTouch ارایه دهد. بعد از این نسخه‌ها برنامه نویسان توانستند بر روی سیستم عامل‌های اندروید و آی او اس به صورت Native کد خود را به زبان C# نوشته و آن‌ها را اجرا کنند.


چرا باید از Xamarin استفاده کنم؟

در ادامه مقاله قصد دارم شما را با برخی از ویژگی‌های زامارین آشنا کرده و مزایای استفاده از آن را بیان کنم. 

Xamarin امکانی را فراهم کرده‌است که برنامه نویسان به دو روش متفاوت قادر خواهند بود برنامه‌های خود را بنویسند:


Xamarin Native :1

زامارین به شما این امکان را میدهد که بتوانید به صورت مستقیم برای هر پلتفرم به صورت جداگانه برنامه نویسی کنید. در اندروید اینکار با Xamarin.Droid و در IOS اینکار با Xamarin.Touch امکانپذیر است. مزیت‌های استفاده از این روش عبارتند از:

·   بهره گیری از یک زبان برنامه نویسی

همانطور که میدانید یادگیری یک زبان برنامه نویسی هزینه‌ی زیادی را برای یک سازمان و یا یک شخص به همراه دارد. در زامارین این امکان فراهم شده‌است که با استفاده از تنها یک زبان برنامه نویسی مانند C# ، برنامه نویسان بتوانند برای پلتفرم‌های مختلف برنامه بنویسند. در نظر داشته باشید که UI در هر پلتفرم به صورت جداگانه پیاده سازی میشود. به طور مثال در اندروید به وسیله‌ی Android Xml میتوانید ظاهر برنامه خود را پیاده سازی کنید و منطق‌های خود را با زبان C# برای تمامی پلت فرم‌ها به صورت یکسان بنویسید.

·   تجربه کاربری Native

زامارین به شما این امکان را خواهد داد که با استفاده از کنترل‌های Native هر پلتفرم به تجربه کاربری همان پلت فرم دسترسی پیدا کنید و اپلیکیشنی با ظاهر و UX همان پلتفرم بسازید.

·   استفاده  100% از امکانات هر پلتفرم

زامارین به دلیل Native بودن این امکان را به برنامه نویسان ارائه میدهد که با استفاده از یک زبان و با بکارگیری Cycle  Life مخصوص هر پلتفرم،  به 100% امکانات و API ‌های هر پلتفرم دسترسی پیدا کنند.

·   Performance

به دلیل اینکه برنامه‌های زامارین به صورت Native اجرا میشوند Performance بالایی دارند.

·   دسترسی به API ‌های موجود در .Net

شما قادر خواهید بود با دانش موجود مانند Entity Framework Code و.. به فریم ورک .Net  دسترسی پیدا کرده و از API ‌های درون آن استفاده کنید. زامارین از یکی از پیاده سازی‌های .Net Standard استفاده میکند.

·   استفاده مجدد از کد

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

·   تست خودکار

در زامارین شما میتوانید برای کدهای خود تست خودکار نوشته و آنها را به صورت خودکار تست کنید.

·   Bind کردن Library ‌های Objective-C و Java

زامارین طوری طراحی شده‌است که دست شما را در هیچگونه شرایطی نخواهد بست. شما میتوانید به صورت مستقیم کدهایی را که به زبان های Java  و Objective-C نوشته شده‌اند، به پروژه اضافه کرده و هیچگونه نگرانی از بابت کدهای از قبل نوشته شده که به زبان‌های Objective-C و Java هستند، نداشته باشید.

·   Designer

در زمارین این امکان وجود دارد که در هر پلتفرم از طریق Designer مخصوص به آن پلتفرم، UI خود را طراحی و پیاده سازی کنید.

·   Async

  در برنامه نویسی غیر همزمان ( Asynchronous Programming ) این امکان وجود دارد که برنامه شما بدون توقف، یک قسمت از کد را اجرا کرده و منتظر اجرای قسمت‌های دیگر کد نشود؛ یا به اصطلاح برنامه از حالت Response خارج نشود. در زبان‌های Java ، Objective-C و Swift اینکار باید با CallBack و به صورت Manual مدیریت شود؛ اما #C این امکان را فراهم آورده است که به راحتی اینکار را انجام داده و برنامه خود را همیشه در حالت پاسخ دهی نگه دارید.  

public async Task<List<FeedItem>> GetFeedItems(DateTime date) {
  var feed = "http://planet.xamarin.com/feed/";
  var response = await httpClient.GetStringAsync(feed);
  var items = await ParseFeedAsync(response);
  return items.Where(item => item.Published.Date == date).ToList();
}


·   Parallel Programming

در برنامه نویسی موازی( Parallel Programming )  برخلاف برنامه نویسی MultiThread که بر روی یک هسته CPU اجرا میشود، بر روی چندین هسته CPU به صورت موازی اجرا میشود. زامارین از این نوع برنامه نویسی پشتیبانی میکند.


Xamarin.Forms: 2

پس از معرفی Xamarin Forms API شما میتوانید علاوه بر مزیت‌هایی که در بالا اشاره شد، کدهای Logic خود را با زبان C# و کدهای UI خود را با زبان XAML پیاده سازی کرده و با یک بار نوشتن کد، در پلتفرم‌های مختلف خروجی خود را مشاهده کنید.  مزیت استفاده از Xamarin Forms عبارتند از:

·   استفاده از کد واحد برای پیاده سازی UI و Logic

یکی از بهترین مزیت‌هایی را که میتوان به آن اشاره نمود این است که شما کافیست یک بار کد خود را بنویسید و Xamarin Forms کد شما را در پلت فرم‌های متفاوت پیاده سازی خواهد کرد.

<?xml version="1.0" encoding="UTF-8"?>
<TabbedPage xmlns="http://xamarin.com/schemas/2014/forms"
            xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
            x:Class="MyApp.MainPage">
    <TabbedPage.Children>
        <ContentPage Title="Profile" Icon="Profile.png">
            <StackLayout Spacing="20" Padding="20"
                         VerticalOptions="Center">
                <Entry Placeholder="Username"
                       Text="{Binding Username}"/>
                <Entry Placeholder="Password"
                       Text="{Binding Password}"
                       IsPassword="true"/>
                <Button Text="Login" TextColor="White"
                        BackgroundColor="#77D065"
                        Command="{Binding LoginCommand}"/>
            </StackLayout>
        </ContentPage>
        <ContentPage Title="Settings" Icon="Settings.png">
            <!-- Settings -->
        </ContentPage>
    </TabbedPage.Children>
</TabbedPage>


برای پیاده سازی UI در Xamarin Forms باید از XAML استفاده کنید. همچنین مانند روش قبلی میتوانید از زبان C# برای پیاده سازی منطق، استفاده نمایید.

با استفاده از Xamarin Forms شما تجربه نوشتن کد Cross Platform را در کنار Native بودن آن خواهید داشت.

·   استفاده از یک کنترل مخصوص یک پلتفرم در بین کد ( Embedding )

در Xamarin Forms این امکان وجود دارد که اگر شما خواستید یک کنترل مخصوص IOS را در بین کدهای خود استفاده کنید، بتوانید به راحتی اینکار را انجام دهید.( (Embedding

اگر به تصویر بالا دقت کنید متوجه خواهید شد که در یکسری از کنترل‌ها، تصاویر متفاوت هستند. در نسخه اندروید یک Action Button در قسمت پایین صفحه مشاهده میکنید که در نسخه‌ی IOS آن موجود نیست. یعنی به صورت مستقیم کنترل Action Button که مختص به پلت فرم اندروید میباشد، درون Xamarin Forms استفاده شده است.

·   دسترسی به هر پلتفرم به طور مستقیم

شما قادر خواهید بود به طور مستقیم به هر پلت فرم دسترسی پیدا کرده و به طور مثال در هر پلتفرم، UI مخصوص به خود را با Property ‌های مخصوص به خود طراحی کنید.

·   UITest و Test Cloud

در Xamarin Forms  میتوانید برای UI خود تست نوشته و آن‌ها را به وسیله Xamarin Test Cloud بر روی صدها Device متفاوت تست کنید. (این امکان فقط برای Android و IOS وجود دارد.)

·   Life Cycle مشابه در تمامی پلتفرم ها

همانطور که میدانید پلتفرم‌های مختلف، Life Cycle ‌های متفاوتی برای مدیریت اپلیکیشن دارند. یکی از مزیت‌های استفاده از Xamarin Forms این است که شما میتوانید برای تمامی پلتفرم‌ها به‌وسیله‌ی یک Life Cycle یکسان کد بنویسید.

·   Previewer

یکی از بهترین قابلیت‌هایی که در Xamarin Forms اضافه شده‌است این است که شما قادر خواهید بود به صورت Real Time خروجی فایل XAML خود را به وسیله Previewer مشاهده نمایید.

·   Performance Profiler

به وسیله Xamarin Profiler شما میتوانید میزان مصرف حافظه، Performance و ... را در اندروید و IOS اندازه گیری نمایید.

نکات قابل توجه:

Ø   استفاده همزمان از Xamarin Forms و Xamarin Native

شما میتوانید کدهای خود را با حداکثر Reusability نوشته و در صورت لزوم با کدهای Xamarin Native ترکیب کنید.

Ø   Documentation خیلی خوب

زمارین  مستندات جامع و کاملی را در سایت خود گردآوری کرده که میتوانید به راحتی از آن برای فهم تمامی قسمت‌های Xamarin استفاده کنید.

اشتراک‌ها
نگاهی به تاریخچه‌ی ASP.NET - قسمت دوم
Part 1 took an overview of the initial design of ASP.NET and how Microsoft reacted to the various changes in webdev. In Part II, we will now look at how those changes influenced the development of ASP.NET MVC and ended up transforming ASP.NET into a much more flexible framework composed of multiple libraries that solved different problems.
نگاهی به تاریخچه‌ی ASP.NET - قسمت دوم
مطالب
EF Code First #12

پیاده سازی الگوی Context Per Request در برنامه‌های مبتنی بر EF Code first

در طراحی برنامه‌های چند لایه مبتنی بر EF مرسوم نیست که در هر کلاس و متدی که قرار است از امکانات آن استفاده کند، یکبار DbContext و کلاس مشتق شده از آن وهله سازی شوند؛ به این ترتیب امکان انجام امور مختلف در طی یک تراکنش از بین می‌رود. برای حل این مشکل الگویی مطرح شده است به نام Session/Context Per Request و یا به اشتراک گذاری یک Unit of work در لایه‌های مختلف برنامه در طی یک درخواست، که در ادامه یک پیاده سازی آن‌را با هم مرور خواهیم کرد.
البته این سشن با سشن ASP.NET یکی نیست. در NHibernate معادل DbContextایی که در اینجا ملاحظه می‌کنید، Session نام دارد.


اهمیت بکارگیری الگوی Unit of work و به اشتراک گذاری آن در طی یک درخواست

در الگوی واحد کار یا همان DbContext در اینجا، تمام درخواست‌های رسیده به آن، در صف قرار گرفته و تمام آن‌ها در پایان کار، به بانک اطلاعاتی اعمال می‌شوند. برای مثال زمانیکه شیءایی را به یک وهله از DbContext اضافه/حذف می‌کنیم، یا در ادامه مقدار خاصیتی را تغییر می‌دهیم، هیچکدام از این تغییرات تا زمانیکه متد SaveChanges فراخوانی نشود، به بانک اطلاعاتی اعمال نخواهند شد. این مساله مزایای زیر را به همراه خواهد داشت:

الف) کارآیی بهتر
در اینجا از یک کانکشن باز شده، حداکثر استفاده صورت می‌گیرد. چندین و چند عملیات در طی یک batch به بانک اطلاعاتی اعمال می‌گردند؛ بجای اینکه برای اعمال هرکدام، یکبار اتصال جداگانه‌ای به بانک اطلاعاتی باز شود.

ب) بررسی مسایل همزمانی
استفاده از یک الگوی واحد کار، امکان بررسی خودکار تمام تغییرات انجام شده بر روی یک موجودیت را در متدها و لایه‌های مختلف میسر کرده و به این ترتیب مسایل مرتبط با ConcurrencyMode عنوان شده در قسمت‌های قبل به نحو بهتری قابل مدیریت خواهند بود.

ج) استفاده صحیح از تراکنش‌ها
الگوی واحد کار به صورت خودکار از تراکنش‌ها استفاده می‌کند. اگر در حین فراخوانی متد SaveChanges مشکلی رخ دهد، کل عملیات Rollback خواهد شد و تغییری در بانک اطلاعاتی رخ نخواهد داد. بنابراین استفاده از یک تراکنش در حین چند عملیات ناشی از لایه‌های مختلف برنامه، منطقی‌تر است تا اینکه هر کدام، در تراکنشی جدا مشغول به کار باشند.


کلاس‌های مدل مثال جاری

در مثالی که در این قسمت بررسی خواهیم کرد، از کلاس‌های مدل گروه محصولات کمک گرفته شده است:

using System.Collections.Generic; 
namespace EF_Sample07.DomainClasses { public class Category { public int Id { get; set; } public virtual string Name { get; set; } public virtual string Title { get; set; } public virtual ICollection<Product> Products { get; set; } } }

using System.ComponentModel.DataAnnotations; 
namespace EF_Sample07.DomainClasses { public class Product { public int Id { get; set; } public string Name { get; set; } public decimal Price { get; set; }
[ForeignKey("CategoryId")] public virtual Category Category { get; set; } public int CategoryId { get; set; } } }


در کلاس Product، یک خاصیت اضافی به نام CategoryId اضافه شده است که توسط ویژگی ForeignKey، به عنوان کلید خارجی جدول معرفی خواهد شد. از این خاصیت در برنامه‌های ASP.NET برای مقدار دهی یک کلید خارجی توسط یک DropDownList پر شده با لیست گروه‌ها، استفاده خواهیم کرد.



پیاده سازی الگوی واحد کار

همانطور که در قسمت قبل نیز ذکر شد، DbContext در EF Code first بر اساس الگوی واحد کار تهیه شده است، اما برای به اشتراک گذاشتن آن بین لایه‌های مختلف برنامه نیاز است یک لایه انتزاعی را برای آن تهیه کنیم، تا بتوان آن‌را به صورت خودکار توسط کتابخانه‌های Dependency Injection یا به اختصار DI در زمان نیاز به استفاده از آن‌، به کلاس‌های استفاده کننده تزریق کنیم. کتابخانه‌ی DI ایی که در این قسمت مورد استفاده قرار می‌گیرد، کتابخانه معروف StructureMap است. برای دریافت آن می‌توانید از Nuget استفاده کنید؛ یا از صفحه اصلی آن در Github : (^).
اینترفیس پایه الگوی واحد کار ما به شرح زیر است:

using System.Data.Entity;
using System; 
namespace EF_Sample07.DataLayer.Context { public interface IUnitOfWork { IDbSet<TEntity> Set<TEntity>() where TEntity : class; int SaveChanges(); } }

برای استفاده اولیه آن، تنها تغییری که در برنامه حاصل می‌شود به نحو زیر است:

using System.Data.Entity;
using EF_Sample07.DomainClasses; 
namespace EF_Sample07.DataLayer.Context { public class Sample07Context : DbContext, IUnitOfWork { public DbSet<Category> Categories { set; get; } public DbSet<Product> Products { set; get; }
#region IUnitOfWork Members public new IDbSet<TEntity> Set<TEntity>() where TEntity : class { return base.Set<TEntity>(); } #endregion } }

توضیحات:
با کلاس Context در قسمت‌های قبل آشنا شده‌ایم. در اینجا به معرفی کلاس‌هایی خواهیم پرداخت که در معرض دید EF Code first قرار خواهند گرفت.
DbSetها هم معرف الگوی Repository هستند. کلاس Sample07Context، معرفی الگوی واحد کار یا Unit of work برنامه است.
برای اینکه بتوانیم تعاریف کلاس‌های سرویس برنامه را مستقل از تعریف کلاس Sample07Context کنیم، یک اینترفیس جدید را به نام IUnitOfWork به برنامه اضافه کرده‌ایم.
در اینجا کلاس Sample07Context پیاده سازی کننده اینترفیس IUnitOfWork خواهد بود (اولین تغییر).
دومین تغییر هم استفاده از متد base.Set می‌باشد. به این ترتیب به سادگی می‌توان به DbSetهای مختلف در حین کار با IUnitOfWork دسترسی پیدا کرد. به عبارتی ضرورتی ندارد به ازای تک تک DbSetها یکبار خاصیت جدیدی را به اینترفیس IUnitOfWork اضافه کرد. به کمک استفاده از امکانات Generics مهیا، اینبار
uow.Set<Product> 

معادل همان db.Products سابق است؛ در حالتیکه از Sample07Context به صورت مستقیم استفاده شود.
همچنین نیازی به پیاده سازی متد SaveChanges نیست؛ زیرا پیاده سازی آن در کلاس DbContext قرار دارد.


استفاده از الگوی واحد کار در کلاس‌های لایه سرویس برنامه

using EF_Sample07.DomainClasses;
using System.Collections.Generic; 
namespace EF_Sample07.ServiceLayer { public interface ICategoryService { void AddNewCategory(Category category); IList<Category> GetAllCategories(); } }

using EF_Sample07.DomainClasses;
using System.Collections.Generic; 
namespace EF_Sample07.ServiceLayer { public interface IProductService { void AddNewProduct(Product product); IList<Product> GetAllProducts(); } }

لایه سرویس برنامه را با دو اینترفیس جدید شروع می‌کنیم. هدف از این اینترفیس‌ها، ارائه پیاده سازی‌های متفاوت، به ازای ORMهای مختلف است. برای مثال در کلاس‌های زیر که نام آن‌ها با Ef شروع شده است، پیاده سازی خاص Ef Code first را تدارک خواهیم دید. این پیاده سازی، قابل انتقال به سایر ORMها نیست چون نه پیاده سازی یکسانی را از مباحث LINQ ارائه می‌دهند و نه متدهای الحاقی همانندی را به همراه دارند و نه اینکه مباحث نگاشت کلاس‌های آن‌ها به جداول مختلف یکی است:

using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
using EF_Sample07.DataLayer.Context;
using EF_Sample07.DomainClasses; 
namespace EF_Sample07.ServiceLayer { public class EfCategoryService : ICategoryService { IUnitOfWork _uow; IDbSet<Category> _categories; public EfCategoryService(IUnitOfWork uow) { _uow = uow; _categories = _uow.Set<Category>(); }
public void AddNewCategory(Category category) { _categories.Add(category); }
public IList<Category> GetAllCategories() { return _categories.ToList(); } } }

using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
using EF_Sample07.DataLayer.Context;
using EF_Sample07.DomainClasses; 
namespace EF_Sample07.ServiceLayer { public class EfProductService : IProductService { IUnitOfWork _uow; IDbSet<Product> _products; public EfProductService(IUnitOfWork uow) { _uow = uow; _products = _uow.Set<Product>(); }
public void AddNewProduct(Product product) { _products.Add(product); }
public IList<Product> GetAllProducts() { return _products.Include(x => x.Category).ToList(); } } }


توضیحات:
همانطور که ملاحظه می‌کنید در هیچکدام از کلاس‌های سرویس برنامه، وهله سازی مستقیمی از الگوی واحد کار وجود ندارد. این لایه از برنامه اصلا نمی‌داند که کلاسی به نام Sample07Context وجود خارجی دارد یا خیر.
همچنین لایه اضافی دیگری را به نام Repository جهت مخفی سازی سازوکار EF به برنامه اضافه نکرده‌ایم. این لایه شاید در نگاه اول برنامه را مستقل از ORM جلوه دهد اما در عمل قابل انتقال نیست و سبب تحمیل سربار اضافی بی موردی به برنامه می‌شود؛ ORMها ویژگی‌های یکسانی را ارائه نمی‌دهند. حتی در حالت استفاده از LINQ، پیاده سازی‌های یکسانی را به همراه ندارند.
بنابراین اگر قرار است برنامه مستقل از ORM کار کند، نیاز است لایه استفاده کننده از سرویس برنامه، با دو اینترفیس IProductService و ICategoryService کار کند و نه به صورت مستقیم با پیاده سازی آن‌ها. به این ترتیب هر زمان که لازم شد، فقط باید پیاده سازی‌های کلاس‌های سرویس را تغییر داد؛ باز هم برنامه نهایی بدون نیاز به تغییری کار خواهد کرد.

تا اینجا به معماری پیچیده‌ای نرسیده‌ایم و اصطلاحا over-engineering صورت نگرفته است. یک اینترفیس بسیار ساده IUnitOfWork به برنامه اضافه شده؛ در ادامه این اینترفیس به کلاس‌های سرویس برنامه تزریق شده است (تزریق وابستگی در سازنده کلاس). کلاس‌های سرویس ما «می‌دانند» که EF وجود خارجی دارد و سعی نکرده‌ایم توسط لایه اضافی دیگری آن‌را مخفی کنیم. شیوه کار با IDbSet تعریف شده دقیقا همانند روال متداولی است که با EF Code first کار می‌شود و بسیار طبیعی جلوه می‌کند.


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

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

using System.Collections.Generic;
using System.Data.Entity;
using EF_Sample07.DataLayer.Context;
using EF_Sample07.DomainClasses;
using EF_Sample07.ServiceLayer;
using StructureMap; 
namespace EF_Sample07 { class Program { static void Main(string[] args) { Database.SetInitializer(new MigrateDatabaseToLatestVersion<Sample07Context, Configuration>());
HibernatingRhinos.Profiler.Appender.EntityFramework.EntityFrameworkProfiler.Initialize(); ObjectFactory.Initialize(x => { x.For<IUnitOfWork>().CacheBy(InstanceScope.Hybrid).Use<Sample07Context>(); x.For<ICategoryService>().Use<EfCategoryService>(); });
var uow = ObjectFactory.GetInstance<IUnitOfWork>(); var categoryService = ObjectFactory.GetInstance<ICategoryService>();
var product1 = new Product { Name = "P100", Price = 100 }; var product2 = new Product { Name = "P200", Price = 200 }; var category1 = new Category { Name = "Cat100", Title = "Title100", Products = new List<Product> { product1, product2 } }; categoryService.AddNewCategory(category1); uow.SaveChanges(); } } }

در اینجا بیشتر هدف، معرفی نحوه استفاده از StructureMap است.
ابتدا توسط متد ObjectFactory.Initialize مشخص می‌کنیم که اگر برنامه نیاز به اینترفیس IUnitOfWork داشت، لطفا کلاس Sample07Context را وهله سازی کرده و مورد استفاده قرار بده. اگر ICategoryService مورد استفاده قرار گرفت، وهله مورد نظر باید از کلاس EfCategoryService تامین شود.
توسط ObjectFactory.GetInstance نیز می‌توان به وهله‌ای از این کلاس‌ها دست یافت و نهایتا با فراخوانی uow.SaveChanges می‌توان اطلاعات را ذخیره کرد.

چند نکته:
- به کمک کتابخانه StructureMap، تزریق IUnitOfWork به سازنده کلاس EfCategoryService به صورت خودکار انجام می‌شود. اگر به کدهای فوق دقت کنید ما فقط با اینترفیس‌ها مشغول به کار هستیم، اما وهله‌سازی‌ها در پشت صحنه انجام می‌شود.
- حین معرفی IUnitOfWork از متد CacheBy با پارامتر InstanceScope.Hybrid استفاده شده است. این enum مقادیر زیر را می‌تواند بپذیرد:

public enum InstanceScope
{
        PerRequest = 0,
        Singleton = 1,
        ThreadLocal = 2,
        HttpContext = 3,
        Hybrid = 4,
        HttpSession = 5,
        HybridHttpSession = 6,
        Unique = 7,
        Transient = 8,
} 

برای مثال اگر در برنامه‌ای نیاز داشتید یک کلاس به صورت Singleton عمل کند، فقط کافی است نحوه کش شدن آن‌را تغییر دهید.
حالت PerRequest در برنامه‌های وب کاربرد دارد (و حالت پیش فرض است). با انتخاب آن وهله سازی کلاس مورد نظر به ازای هر درخواست رسیده انجام خواهد شد.
در حالت ThreadLocal، به ازای هر Thread، وهله‌ای متفاوت در اختیار مصرف کننده قرار می‌گیرد.
با انتخاب حالت HttpContext، به ازای هر HttpContext ایجاد شده، کلاس معرفی شده یکبار وهله سازی می‌گردد.
حالت Hybrid ترکیبی است از حالت‌های HttpContext و ThreadLocal. اگر برنامه وب بود، از HttpContext استفاده خواهد کرد در غیراینصورت به ThreadLocal سوئیچ می‌کند.


استفاده از الگوی واحد کار و کلاس‌های سرویس تهیه شده در یک برنامه ASP.NET MVC

یک برنامه خالی ASP.NET MVC را آغاز کنید. سپس یک HomeController جدید را نیز به آن اضافه نمائید و کدهای آن‌را مطابق اطلاعات زیر تغییر دهید:
using System.Web.Mvc;
using EF_Sample07.DomainClasses;
using EF_Sample07.ServiceLayer;
using EF_Sample07.DataLayer.Context;
using System.Collections.Generic; 
namespace EF_Sample07.MvcAppSample.Controllers { public class HomeController : Controller { IProductService _productService; ICategoryService _categoryService; IUnitOfWork _uow; public HomeController(IUnitOfWork uow, IProductService productService, ICategoryService categoryService) { _productService = productService; _categoryService = categoryService; _uow = uow; }
[HttpGet] public ActionResult Index() { var list = _productService.GetAllProducts(); return View(list); }
[HttpGet] public ActionResult Create() { ViewBag.CategoriesList = new SelectList(_categoryService.GetAllCategories(), "Id", "Name"); return View(); }
[HttpPost] public ActionResult Create(Product product) { if (this.ModelState.IsValid) { _productService.AddNewProduct(product); _uow.SaveChanges(); }
return RedirectToAction("Index"); }
[HttpGet] public ActionResult CreateCategory() { return View(); }
[HttpPost] public ActionResult CreateCategory(Category category) { if (this.ModelState.IsValid) { _categoryService.AddNewCategory(category); _uow.SaveChanges(); }
return RedirectToAction("Index"); } } }

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

using System;
using System.Data.Entity;
using System.Web.Mvc;
using System.Web.Routing;
using EF_Sample07.DataLayer.Context;
using EF_Sample07.ServiceLayer;
using StructureMap; 
namespace EF_Sample07.MvcAppSample
{ // Note: For instructions on enabling IIS6 or IIS7 classic mode, // visit http://go.microsoft.com/?LinkId=9394801
public class MvcApplication : System.Web.HttpApplication { public static void RegisterGlobalFilters(GlobalFilterCollection filters) { filters.Add(new HandleErrorAttribute()); }
public static void RegisterRoutes(RouteCollection routes) { routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute( "Default", // Route name "{controller}/{action}/{id}", // URL with parameters new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults ); }
protected void Application_Start() { Database.SetInitializer(new MigrateDatabaseToLatestVersion<Sample07Context, Configuration>()); HibernatingRhinos.Profiler.Appender.EntityFramework.EntityFrameworkProfiler.Initialize(); AreaRegistration.RegisterAllAreas(); RegisterGlobalFilters(GlobalFilters.Filters); RegisterRoutes(RouteTable.Routes); initStructureMap(); }
private static void initStructureMap() { ObjectFactory.Initialize(x => { x.For<IUnitOfWork>().HttpContextScoped().Use(() => new Sample07Context()); x.ForRequestedType<ICategoryService>().TheDefaultIsConcreteType<EfCategoryService>(); x.ForRequestedType<IProductService>().TheDefaultIsConcreteType<EfProductService>(); });
//Set current Controller factory as StructureMapControllerFactory ControllerBuilder.Current.SetControllerFactory(new StructureMapControllerFactory()); }
protected void Application_EndRequest(object sender, EventArgs e) { ObjectFactory.ReleaseAndDisposeAllHttpScopedObjects(); } }
public class StructureMapControllerFactory : DefaultControllerFactory { protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType) { return ObjectFactory.GetInstance(controllerType) as Controller; } } }

توضیحات:
کدهای فوق متعلق به کلاس Global.asax.cs هستند. در اینجا در متد Application_Start، متد initStructureMap فراخوانی شده است.
با پیاده سازی ObjectFactory.Initialize در کدهای برنامه کنسول معرفی شده آشنا شدیم. اینبار فقط حالت کش شدن کلاس Context برنامه را HttpContextScoped قرار داده‌ایم تا به ازای هر درخواست رسیده یک بار الگوی واحد کار وهله سازی شود.
نکته مهمی که در اینجا اضافه شده‌است، استفاده از متد ControllerBuilder.Current.SetControllerFactory می‌باشد. این متد نیاز به وهله‌ای از نوع DefaultControllerFactory دارد که نمونه‌ای از آن‌را در کلاس StructureMapControllerFactory مشاهده می‌کنید. به این ترتیب در زمان وهله سازی خودکار یک کنترلر، اینبار StructureMap وارد عمل شده و وابستگی‌های برنامه را مطابق تعاریف ObjectFactory.Initialize ذکر شده، به سازنده کلاس کنترلر تزریق می‌کند.
همچنین در متد Application_EndRequest با فراخوانی ObjectFactory.ReleaseAndDisposeAllHttpScopedObjects از نشتی اتصالات به بانک اطلاعاتی جلوگیری خواهیم کرد. چون وهله الگوی کار برنامه HttpScoped تعریف شده، در پایان یک درخواست به صورت خودکار توسط StructureMap پاکسازی می‌شود و به نشتی منابع نخواهیم رسید.


استفاده از الگوی واحد کار و کلاس‌های سرویس تهیه شده در یک برنامه ASP.NET Web forms

در یک برنامه ASP.NET Web forms نیز می‌توان این مباحث را پیاده سازی کرد:

using System;
using System.Data.Entity;
using EF_Sample07.DataLayer.Context;
using EF_Sample07.ServiceLayer;
using StructureMap; 
namespace EF_Sample07.WebFormsAppSample { public class Global : System.Web.HttpApplication { private static void initStructureMap() { ObjectFactory.Initialize(x => { x.For<IUnitOfWork>().HttpContextScoped().Use(() => new Sample07Context()); x.ForRequestedType<ICategoryService>().TheDefaultIsConcreteType<EfCategoryService>(); x.ForRequestedType<IProductService>().TheDefaultIsConcreteType<EfProductService>();
x.SetAllProperties(y=> { y.OfType<IUnitOfWork>(); y.OfType<ICategoryService>(); y.OfType<IProductService>(); }); }); }
void Application_Start(object sender, EventArgs e) { Database.SetInitializer(new MigrateDatabaseToLatestVersion<Sample07Context, Configuration>()); HibernatingRhinos.Profiler.Appender.EntityFramework.EntityFrameworkProfiler.Initialize(); initStructureMap(); }
void Application_EndRequest(object sender, EventArgs e) { ObjectFactory.ReleaseAndDisposeAllHttpScopedObjects(); }

در اینجا کدهای کلاس Global.asax.cs را ملاحظه می‌کنید. توضیحات آن با قسمت ASP.NET MVC آنچنان تفاوتی ندارد و یکی است. البته منهای تعاریف SetAllProperties که جدید است و در ادامه به علت اضافه کردن آن‌ها خواهیم رسید.
در ASP.NET Web forms برخلاف ASP.NET MVC نیاز است کار وهله سازی اینترفیس‌ها را به صورت دستی انجام دهیم. برای این منظور و کاهش کدهای تکراری برنامه می‌توان یک کلاس پایه را به نحو زیر تعریف کرد:

using System.Web.UI;
using StructureMap; 
namespace EF_Sample07.WebFormsAppSample { public class BasePage : Page { public BasePage() { ObjectFactory.BuildUp(this); } } }

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

using System;
using EF_Sample07.DataLayer.Context;
using EF_Sample07.DomainClasses;
using EF_Sample07.ServiceLayer; 
namespace EF_Sample07.WebFormsAppSample { public partial class AddProduct : BasePage { public IUnitOfWork UoW { set; get; } public IProductService ProductService { set; get; } public ICategoryService CategoryService { set; get; }
protected void Page_Load(object sender, EventArgs e) { if (!IsPostBack) { bindToCategories(); } }
private void bindToCategories() { ddlCategories.DataTextField = "Name"; ddlCategories.DataValueField = "Id"; ddlCategories.DataSource = CategoryService.GetAllCategories(); ddlCategories.DataBind(); }
protected void btnAdd_Click(object sender, EventArgs e) { var product = new Product { Name = txtName.Text, Price = int.Parse(txtPrice.Text), CategoryId = int.Parse(ddlCategories.SelectedItem.Value) }; ProductService.AddNewProduct(product); UoW.SaveChanges(); Response.Redirect("~/Default.aspx"); } } }


اینبار وابستگی‌های کلاس افزودن محصولات، به صورت خواصی عمومی تعریف شده‌اند. این خواص عمومی توسط متد SetAllProperties که در فایل global.asax.cs معرفی شدند، باید یکبار تعریف شوند (مهم!).
سپس اگر دقت کرده باشید، اینبار کلاس AddProduct از BasePage ما ارث بری کرده است. در سازند کلاس BasePage، با فراخوانی متد ObjectFactory.BuildUp، تزریق وابستگی‌ها به خواص عمومی کلاس جاری صورت می‌گیرد.
در ادامه نحوه استفاده از این اینترفیس‌ها را جهت مقدار دهی یک DropDownList یا ذخیره سازی اطلاعات یک محصول مشاهده می‌کنید. در اینجا نیز کار با اینترفیس‌ها انجام شده و کلاس جاری دقیقا نمی‌داند که با چه وهله‌ای مشغول به کار است. تنها در زمان اجرا است که توسط StructureMap ، به ازای هر اینترفیس معرفی شده، وهله‌ای مناسب بر اساس تعاریف فایل Global.asax.cs در اختیار برنامه قرار می‌گیرد.

کدهای کامل مثال‌های این سری را از آدرس زیر هم می‌توانید دریافت کنید: (^)


به روز رسانی
کدهای قسمت جاری را به روز شده جهت استفاده از EF 6 و StructureMap 3 در VS 2013، از اینجا می‌توانید دریافت کنید:
EF_Sample07  
مطالب
استخراج آدرس‌های ایمیل از یک متن

در قسمت اول بررسی نحوه برنامه نویسی افزونه outlook ، در مورد استفاده از regular expressions اندکی توضیح داده شد. امروز مثالی دیگر از همین دست را بررسی خواهیم کرد.

چند روز قبل یک ایمیل تبلیغاتی به دست من رسید که فرد ارسال کننده انبوهی از ایمیل‌ها را در قسمت To قرار داده بود (بجای قسمت BCC (رونوشت مخفی)).
خوب، برای جدا کردن انبوهی از ایمیل‌های مخلوط با سایر متون چه باید کرد؟ چند ساعت وقت گذاشت و تک تک آنها را به صورت دستی جدا کرد؟ (برای ذخیره سازی در یک دیتابیس برای مثال :) )
یا برای مثال برنامه‌های download manager توانایی استخراج لینک‌های موجود در یک متن کپی شده در حافظه را دارند. آنها به چه صورتی عمل می‌کنند؟ چگونه می‌توانند لینک‌ها را با دقتی بالا و بسیار سریع از لابلای متن موجود تشخیص دهند؟

بهینه‌ترین و سریعترین‌ راه برای این نوع جستجوها استفاده از کتابخانه regular expressions (عبارات با قاعده) در دات نت فریم ورک است. اگر نیاز به یک برگه تقلب (!) در این زمینه داشتید می‌توانید به اینجا مراجعه کنید. همچنین در همان سایت، کاربران بسیاری را خواهید یافت که الگوهای ابداعی خود را با دیگران به اشتراگ می‌گذارند.

برای مثال فرض کنید فایلی را که حاوی مخلوطی از متن و ایمیل است را در یک رشته بارگذاری کرده‌اید. نحوه استخراج ایمیل‌های موجود با استفاده از این امکانات به صورت زیر خواهد بود:
using System.IO;
using System.Text.RegularExpressions;
using System.Text;

class CRegEx
{
/// <summary>
/// استخراج ایمیل‌های یک فایل متنی و ذخیره آن در فایلی جدید
/// </summary>
/// <param name="inFilePath">فایل ورودی</param>
/// <param name="outFilePath">فایل خروجی</param>
public static void ExtractEmails(string inFilePath, string outFilePath)
{
string data = File.ReadAllText(inFilePath); //خواندن فایل متنی
//ایجاد شیء عبارت با قاعده بر اساس الگوی تشخیص ایمیل‌ها
Regex emailRegex = new Regex(@"\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*",
RegexOptions.IgnoreCase);
//پیدا کردن گروه تطابق یافته با الگوی ما
MatchCollection emailMatches = emailRegex.Matches(data);
//ایجاد شیء استرینگ بیلدر برای ذخیره سازی سریع اطلاعات دریافتی
StringBuilder sb = new StringBuilder();
//ذخیره ایمیل‌های استخراج شده
foreach (Match emailMatch in emailMatches)
{
sb.AppendLine(emailMatch.Value);
}
//ذخیره کردن اطلاعات استخراج شده در فایلی جدید
File.WriteAllText(outFilePath, sb.ToString());
}
}

راستی، اگر روزی خواستید تعداد بالایی ایمیل ارسال کنید، آنها را به قسمت bcc اضافه کنید (Message.Bcc.Add)، در قالب یک ایمیل، نه چند هزار ایمیل متوالی (در طی یک حلقه برای مثال). به این صورت (استفاده از قسمت BCC) میل سرور تمام آدرس‌ها را در صف قرار خواهد داد و متحمل بار اضافی شدید نخواهد شد. در این حالت اگر میل باکس خود را چک کنید شاید بلافاصله ایمیل مورد نظر را دریافت نکنید. نگران نباشید، انجام عملیات در صف قرار گرفته و در طی دقایق و یا حتی ساعات بعدی پردازش خواهد شد (بسته به بار سرور).
چند نکته را باید در اینجا در نظر داشت. حتما آدرس‌های اضافه شده را با استفاده از عبارات باقاعده یکبار پیش از اضافه شدن بررسی نمائید (Regex.IsMatch). در صورتیکه یکی از ایمیل‌ها فرمت غیراستانداردی داشته باشد کل کار برگشت خواهد خورد.
و همچنین باید دقت داشت که برای این موضوع حد نصاب وجود دارد. بر روی یکی از میل سرورهای یک هاست ایرانی تست کردم، حداکثر 100 رونوشت مخفی را بیشتر قبول نمی‌کرد. بنابراین هر بار می‌شود 100 ایمیل را به صورت یکجا ارسال کرد (که باز هم از روش استفاده از حلقه‌ای که 100 بار ایمیل می‌زند بسیار بهتر است و هاست دار به علت ایجاد بار اضافی شدید بر روی سرور با شما تماس نخواهد گرفت)

مطالب
آشنایی با ساختار IIS قسمت یازدهم
در این مطلب و همینطور مطلب بعدی قرار است به مبحث لاگ فایل‌ها Logfile بپردازیم. همانطور که می‌دانید سیستم IIS مثل هر سیستم دیگری لاگ هایی دارد که به مرور زمان این لاگ‌ها میتوانند مقدار زیادی از ظرفیت دیسک سخت را به خود اختصاص بدهند و این عمل میتواند موجب بروز مشکلاتی در سرور شود. به خوبی به یاد دارم که برای یکی از مشتریانم VPS تهیه نموده بودیم و بعد از یک سال با من تماس گرفت که سایت بالا نمی‌آید و وقتی بررسی شد، دیدم که از فضای دیسک سخت چند گیگابایتی، تنها چند مگابایت به طور ناچیز فضا برایش باقی مانده است و باعث شده است سرور از کار بیفتد. پس از بررسی متوجه شدیم تمام این فضاها توسط لاگ فایل‌ها پر شده است و از آنجا که سرویس دهنده تا مبلغی را به عنوان مدیریت سرور، ماهانه دریافت نکند، مدیریت این سرور مجازی را به عهده نداشته اند که البته بعدها با انتقال به یک سرور دیگر از یک سرویس دهنده دیگر مشکلات ما در مورد سرور برای همیشه حل شد.
پس این داستان به خوبی روشن می‌کند که مدیریت این لاگ‌ها چقدر میتواند مهم و حیاتی باشد. آقای تیم وَن از تیم تحریریه مایکروسافت در بخش IIS موارد زیر را برای مدیریت لاگ‌ها بر می‌شمارد:
  • فعال سازی فشرده سازی
  • انتقال لاگ‌ها به یک سیستم راه دور
  • حذف لاگ فایل‌های قدیمی از طریق اسکریپت نویسی
  • حذف لاگ فایل‌های قدیمی توسط IIS Log File Cleaner

فشرده سازی دایرکتوری لاگ فایل ها
مسیر ذخیره لاگ فایل‌ها در آدرس زیر می‌باشد. به این آدرس رفته و Properties دایرکتوری مورد نظر  را باز کنید و در برگه‌ی General آن، گزینه‌ی Advanced را زده تا در کادر جدیدی که باز می‌شود، گزینه‌ی Compress contents to save disk space را انتخاب کنید تا محتویات در هنگام ذخیره روی دیسک سخت فشرده شوند.
%SystemDrive%\inetpub\logs\LogFiles

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


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

همانطور که در بالا اشاره کردیم محل پیش فرض ذخیره سازی لاگ‌ها درمسیر

%SystemDrive%\inetpub\logs\LogFiles

قرار دارد و این محل ذخیره سازی برای هر سرور یا حتی یک وب سایت خاص در صفحه تنظیمات Logging مشخص شده است و شما در میتوانید این لاگ‌ها را حتی برای کل سرور یا مربوط به یک سایت خاص، به سروری دیگر انتقال دهید. این امکان می‌تواند به امنیت سیستم هم کمک فراوانی کند تا اگر دیسک محلی Local Disk هم دچار مشکل شد، باز خواندن لاگ فایل‌ها میسر باشد و با استفاده از ابزارهای تحلیل لاگ فایل ها، آن‌ها را مورد بررسی قرار دهیم. برای تغییر محل ذخیره سازی لاگ‌ها به یک سیستم راه دور، راه حل زیر را طی کنید.

در IIS وب سایتی را که میخواهید لاگ آن انتقال یابد، انتخاب کنید؛ یا اگر لاگ کل سیستم IIS را میخواهید انتقال بدهید نام سرور را در لیست درختی انتخاب کنید و از ماژول‌های سمت راست، ماژول Logging را انتخاب کنید و در قسمت Directory که محل ذخیره سازی فعلی لاگ‌ها را نوشته شده است، به صورت UNC آدرس دهی کنید. در آدرس زیر اولی نام سرور است Contoso-server1\\ و دومی هم Logs نام پوشه‌ای که به اشتراک گذاشته شده است.


حذف لاگ فایل‌های قدیمی با استفاده از اسکریپت

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

با ستفاده از VBScript بررسی می‌کنیم که اگر مثلا عمر لاگ فایل به 30 روز رسیده است، باید حذف شوند. خط دوم کد زیر نهایت عمر یک لاگ فایل را مشخص می‌کند:

sLogFolder = "c:\inetpub\logs\LogFiles"
iMaxAge = 30   'in days
Set objFSO = CreateObject("Scripting.FileSystemObject")
set colFolder = objFSO.GetFolder(sLogFolder)
For Each colSubfolder in colFolder.SubFolders
        Set objFolder = objFSO.GetFolder(colSubfolder.Path)
        Set colFiles = objFolder.Files
        For Each objFile in colFiles
                iFileAge = now-objFile.DateCreated
                if iFileAge > (iMaxAge+1)  then
                        objFSO.deletefile objFile, True
                end if
        Next
Next

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

برای اجرای دستی اسکریپت در cmd تایپ کنید:

cscript.exe c:\scripts\retentionscript.vbs

ولی اگر میخواهید این اسکریپت در هر دوره‌ی زمانی خاص اجرا شود، یا زمان بندی Scheduling گردد، دیگر مجبور نیستید هر بار به فکر نگهداری از لاگ‌ها باشید.


زمان بندی اجرای اسکریپت

server manager (قابل تست در ویندوزهای سرور)  را باز کرده و از منوی Tools گزینه Task Scheduler را انتخاب کنید و در قسمت Actions گزینه Create Task را انتخاب نمایید. در کادر باز شده نام "Delete Log Files " را برای مثال برگزینید و در قسمت Security هم کاربری که اجازه اجرای اسکریپت را دارد مشخص کنید.

برگه Triggers را انتخاب کرده و گزینه New را انتخاب کنید و عملیات زمان بندی را تنظیم کنید و حتما بعد از زمان بندی مطمئن باشید که تیک Enabled فعال است.

در برگه Actions هم گزینه New را انتخاب کنید؛ در کادر باز شده از لیست Start a program را انتخاب کرده و در قسمت Program\script، دستور cscript را ذکر نمایید و به عنوان آرگومان ورودی Add arguments  هم مسیر اسکریپت خود را ذکر نمایید و کادر را تایید کنید.

برای آغاز زمان بندی در لیست وظیفه‌های فعال active task pane، وظیفه ای که الان ساخته اید را اجرا کرده و به مسیر ذخیره لاگ‌ها رفته و می‌بینید که لاگ‌های مورد نظر حذف شده‌اند؛ پس از صحت اجرای اسکریپت مطمئن می‌شویم. دوباره به لیست وظایف رفته و گزینه End را بزنید تا وظیفه، در حالت Ready قرار گیرد تا از همین الان فرایند زمان بندی اجرای اسکریپت آغاز شود.


حذف لاگ فایل‌ها با استفاده از IIS Log Cleaner Tools

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

ابتدا برنامه را از اینجا  دانلود کنید. موقعیکه برنامه را اجرا کنید، در نوتیفیکیشن taskbar می‌نشیند و برنامه با یک پیغام به شما اعلام می‌کند، این اولین بار است که برنامه را باز کرده‌اید. پس یک سر به setting آن بزنید؛ با انتخاب گزینه‌ی settings برنامه بسته شده و فایل Settings.txt برای شما باز می‌شود که مدت زمان عمر لاگ فایل و مسیر ذخیره آن‌ها، از شما پرسیده می‌شود که مقدار عمر هر لاگ فایل به طور پیش فرض 30 روز و مسیر ذخیره‌ی لاگ‌ها همان مسیر پیش فرض IIS است که اگر شما دستی آن را تغییر داده اید، با پرسیدن آن، از محل لاگ‌ها اطمینان کسب می‌کند. در صورتی که قصد تغییری را در فایل، دارید آن را تغییر داده و ذخیره کنید و برنامه را مجددا اجرا کنید.

نکات نهایی در مورد این برنامه :

  • اگر از ابزار IIS Cleaner Tool استفاده می‌کنید باید دستی سطل بازیافت را هم پاک کنید و هم اینکه میتوانید یک محدودیت حجمی برای Recycle Bin قرار دهید که اگر به یک حدی رسید، خودکار پاک کند تا مشکلی برای سیستم عامل ایجاد نشود که البته به طور پیش فرض چنین است.
  • برنامه بالا به طور پیش فرض ریشه‌ی لاگ‌ها را حذف می‌کند. پس اگر میخواهید فقط سایت خاصی را مد نظر داشته باشد، آدرس دایرکتوری آن را اضافه کنید. البته چون این برنامه فقط روی یک دایرکتوری کار می‌کند و شما چند وب سایت دارید و مثلا میخواهید سه تای آن‌ها را پاکسازی کنید، چاره‌ی جز استفاده از اسکریپت‌های با زمان بندی ندارید.
  • برنامه‌ی بالا فقط فایل هایی با پسوند log را به سطل بازیافت انتقال می‌دهد.
  • برنامه‌ی بالا یک سرویس نیست و باید به طور دستی توسط کاربر اجرا گردد. پس اگر ریست هم شد باید دستی اجرا شود یا آن را به داخل پوشه startup بکشید.
  • برنامه برای اجرایش نیاز به لاگین کاربر و مجوز نوشتن در آن پوشه را دارد تا به درستی کار کند.
در قسمت بعدی مبحث لاگ‌ها را ادامه خواهیم داد و با ماژول Logging در IIS و تنظیماتش آشنا خواهیم شد.
مطالب
Protocol Buffers فرمتی برای تبادل دیتا
Protocol Buffers  فرمتی جدید برای تبادل دیتا بین سرور و کلاینت میباشد که توسط گوگل طراحی و ساخته شده است و همچنین اکثر زیرساخت‌های گوگل از این فرمت برای تبادل اطلاعات بین سرویس‌ها استفاده میکنند. Protocol Buffer را میتوان به عنوان جایگزینی برای JSON/XML بکار برد و به دلایل زیادی که در ادامه درباره‌ی آن صحبت میکنیم میتواند گزینه‌ی مناسبی برای Microservices‌ها باشد و همچنین سرعت بالا، سادگی در استفاده، پشتیبانی از زبان‌های برنامه نویسی متعدد از ویژگی‌های منحصر به فرد این زبان برای تبادل اطلاعات است.
در ابتدا میخواهم کمی راجع به تبادل دیتا، از گذشته تا به حال صحبت کنم:
مدت‌ها است از csv‌ها برای تبادل اطلاعات استفاده میشود؛ اما مزایا و معایب خاص خود را دارد، از جمله اینکه parse کردن راحتی دارد، راحتی در خواندن و غیره. معایبش هم این‌است که گارانتی برای نوع type ندارد و اینکه المان‌هایی که حاوی کاما هستند با مشکل رو به رو میشوند و غیره...
بعد از آن دیتابیس‌ها وارد کار شدند که همه‌ی ما کم و بیش با آنها آشنا هستیم؛ در آن‌ها دیتا‌ها کاملا با type مشخصی هستند و اینکه در table‌های مجزا ذخیره میشوند. مشکلاتش هم این است که دیتا باید حتما flat باشد و اینکه بین دیتابیس‌های مختلف definition‌های مختلفی وجود دارد.
بعد از آن با JSON آشنایی داریم که مزایای زیادی دارد و مدت هاست که مورد استفاده قرار گرفته و شامل این‌است که دیتا در آن میتواند تو در تو ذخیره شود، آرایه داشته باشد، کاملا در دنیای وب مورد قبول واقع شده، به وسیله‌ی هر زبانی قابل خواندن‌است و اینکه خیلی راحت در شبکه قابل انتقال می‌باشد. معایبش هم این‌است که خیلی راحت میتواند خیلی بزرگ شود و اینکه قابلیت کامنت، متادیتا و داکیومنتیشن هم ندارد.
اما میرسیم به گزینه‌ی آخر که protocol buffers است و ابزاری هست که ممکن است خیلی‌ها با آن آشنا نباشند. قبل از بررسی دقیقش به مزایا و معایبش می‌پردازیم. مزایا آن این‌است که دیتا در آن کاملا typed میباشد. دیتای آن به صورت اتوماتیک compressed می‌شود. اسکیما در آن توسط زبان منحصر به فردش قابل تعریف است و توسط تقریبا همه‌ی زبان‌های برنامه نویسی مشهور قابل استفاده‌است. تغییرات اسکیما در آن کنترل شده‌است. 3 تا 10 بار کم حجم‌تر و 20 تا 100 بار سریعتر از xml است و اینکه از روی آن می‌توان کد آماده برای استفاده تولید کرد که سرعت برنامه نویسی را خیلی بالا می‌برد. از مشکلاتش هم این است که ممکن است در یک سری از زبان‌های برنامه نویسی خاص قابل استفاده نباشد. البته بر روی C#, Nodejs, C, Go, Python ,... به خوبی کار می‌کند. مشکل دیگرش هم این‌است که نمی‌شود فایلش را با ادیتورها باز کرد و قابل خواندن نیست؛ چون serialized و compressed شده‌است.

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

ابتدا از طریق فرمت protocol buffer، فایل‌های خود را که قرار است انتقال داده شوند، مینویسیم.

سپس بصورت خودکار برای زبان برنامه نویسی مطبوع خود آن را generate میکنیم.

کد‌های تولید شده بصورت خودکار و کاملا آماده هستند و ضمن اینکه encode/decode شدن بصورت خودکار توسط فریم ورک انجام شده و قابلیت تعامل بین زبان‌های مختلف برنامه نویسی یا سرویس‌های مختلف برقرار است.


نکته:

  •  بعضی از دیتابیس‌ها از فرمت protocol buffers پشتیبانی میکنند.
  • اکثر فریم ورک‌های RPC شامل gRPC از پروتکل بافر برای تبادل دیتا استفاده میکنند.
  • گوگل برای تمام سرویس‌های داخلی خود از آن استفاده میکند.
  • بعضی از پروژه‌های خیلی بزرگ مثل etcd از پروتکل بافر برای تبادل دیتا استفاده میکنند.
  • ما در این مقاله از ورژن 3 پروتکل بافر استفاده میکنیم.


نصب Code generator

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

اگر از MacOSX استفاده میکنید، به راحتی با استفاده از دستور زیر می‌توانید آن را نصب کنید:

brew install protobuf

اگر هم از ویندوز استفاده میکنید، از این طریق میتوانید نسخه‌ی مورد نظر را به راحتی دانلود و مورد استفاده قرار بدهید:

https://github.com/google/protobuf/releases
https://github.com/google/protobuf/releases/download/v3.5.1/protoc-3.5.1-win32.zip

حالا میخواهیم اولین فایل خود را با این فرمت بسازیم.

اول از همه با هم نگاهی به ساختار فایل مربوطه میاندازیم:

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

حالا میخواهیم بصورت واقعی protocol buffer خود را طراحی کرده و سپس از روی آن کد‌های مربوطه را generate نماییم؛ به نام sample.proto بصورت زیر:

syntax = "proto3";

package helloworld;

service Greeter {
  rpc SayHello (HelloRequest) returns (HelloReply) {}
}

message HelloRequest {
  string name = 1;
}

message HelloReply {
  string message = 1;
}

در فایل فوق علاوه بر تعریف‌های اولیه، یک سرویس را هم اضافه کرده‌ایم و همچنین متدی را با ورودی و خروجی‌های مشخصی ایجاد کرده‌ایم (امکانات پروتکل بافر خیلی بیشتر از این موارد است؛ از جمله فرمت‌های آرایه و غیره را نیز پشتیبانی میکند، همچنین از روشی برای versioning استفاده میکند که obsolete کردن پراپرتی‌ها و نسخه بندی را بسیار راحت می‌کند و ...). به سادگی قابلیت طراحی و پیاده سازی سرور و کلاینت مربوط به این آیتم ایجاد شده با استفاده از زبان‌های برنامه نویسی مختلف فراهم میباشد. حال کافی‌است که پروتکل بافر خود را با زبان دلخواه خود generate کنیم. در قسمت زیر برای زبان‌های برنامه نویسی Go و #C، کد‌ها را تولید میکنیم.

protoc sample.proto --go_out=plugins=grpc:.  
protoc sample.proto --csharp_out=.

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

در مقاله‌ی بعدی به آشنایی با gRPC می‌پردازیم و ضمن اینکه یک سرور با #C و یک کلاینت با زبان برنامه نویسی Go را نوشته که از طریق پروتکل بافر با هم به تبادل اطلاعات می‌پردازند!

مطالب
انجام عملیات طولانی مدت با Web Workers
امروزه استفاده از صفحات وب، در همه امور به خوبی به چشم می‌خورد و تاثیر این فناوری را می‌توان در تمام عرصه‌های تولید و استفاده از نرم افزار دید. web worker یکی از فناوری‌های تحت وب بوده که توسط W3C ارائه شده است. وب ورکر به شما اجازه می‌دهد تا بتوانید عملیاتی را که نیاز به زمان زیادی برای پردازش دارد، در پشت صحنه انجام دهید؛ بدون اینکه وقفه‌ای در پردازش UI ایجاد شود. وب ورکر حتی به شما اجازه می‌دهد چند thread را همزمان اجرا کنید و پردازش‌هایی موازی یکدیگر داشته باشید. از آنجا که وب ورکرها یک ترد پردازشی جدا از UI به حساب می‌آیند، شما دسترسی به DOM ندارید؛ ولی می‌توانید از طریق ارسال پیام، با صفحه وب تعامل داشته باشد.

قبل از استفاده از وب ورکر، بهتر هست مرورگر را بررسی کنیم که آیا از این قابلیت پشتیبانی می‌کند یا خیر؟ روش بررسی کردن این قابلیت، شیوه‌های مختلفی دارد که به تعدادی از آن‌ها اشاره می‌کنیم:
typeof(Worker) !== "undefined"

 <script src="/js/modernizr-1.5.min.js"></script>
Modernizr.webworkers
هر کدام از عبارات بالا را اگر در شرطی بگذارید و جواب true بازگردانند به معنی پشتیبانی مرورگر این ویژگی است. modernizr فریمورکی جهت بررسی قابلیت‌های موجود در مرورگر است.
نحوه پشتیبانی وب ورکرها در مروگرهای مختلف به شرح زیر است:

برای ایجاد یک وب ورکر ابتدا لازم است تا کدهای پردازشی را داخل یک فایل js جداگانه بنویسیم. در این مثال ما قصد داریم که شمارنده‌ای را بنویسیم:
var i=0;

function timedCount() {
    i=i+1;
    postMessage(i);
    setTimeout("timedCount()", 500);
}

timedCount();

سپس در فایل HTML به شکل زیر وب ورکر را مورد استفاده قرار می‌دهیم. در سازنده Worker، ما آدرس فایل js را وارد می‌کنیم و برای توقف آن نیز از متد terminate استفاده می‌کنیم:
<!DOCTYPE html>
<html>
  <head>
    <script>
    var worker;

    function Start()
    {
      worker=new Worker("webwroker-even-numbers.js");
      worker.onmessage=(event)=>
      {
        document.getElementById("output").value=event.data;
      }
    }
    function Stop()
    {
      worker.terminate();
    }
    </script>
    <meta charset="utf-8">
    <title></title>
  </head>
  <body>
    <input type="text" id="output"/>
    <button onclick="Start();">Start Worker</button>
<button onclick="Stop();">Stop Worker</button>
  </body>
</html>
در صورتی که خطایی در ورکر رخ بدهد، می‌توانید از طریق متد onerror آن را دریافت کنید:
      worker.onerror = function (event) {
            console.log(event.message, event);
         };
مقدار برگشتی event شامل اطلاعات زیادی در مورد خطاست که شامل نام و مسیر فایل خطا، شماره خط و شماره ستون خطا، پیام خطا و ... می‌شود.
همچنین برای ورکر هم می‌توانید پیامی را ارسال کنید، برای همین کد زیر را به کد ورکر اضافه می‌کنیم:
self.onmessage=(event)=>{
  i=event.data;
};
و در صفحه HTML هم کد دریافت پیام از ورکر را به شکل زیر تغییر میدهیم:
  worker.onmessage=(event)=>
      {
        document.getElementById("output").value=event.data;
        if(event.data==8)
        worker.postMessage(100);
      }
در این حالت اگر عدد به هشت برسد، ما به ورکر می‌گوییم که عدد را به صد تغییر بده.

روش‌های ارسال پیام
به این نوع ارسال پیام، Structure Cloning گویند و با استفاده از این شیوه، امکان ارسال نوع‌های مختلفی امکان پذیر شده است؛ مثل فایل‌ها، Blob‌ها، آرایه‌ها و کلاس‌ها و ... ولی باید دقت داشته باشید که این ارسال پیام‌ها به صورت کپی بوده و آدرسی ارجاع داده نمی‌شود و باید مدنظر داشته باشید که ارسال یک فایل، به فرض پنجاه مگابایتی، به خوبی قابل تشخیص است. طبق نظر گوگل، از حجم 32 مگابایت به بعد، این گفته به خوبی مشهود بوده و زمانبر می‌شود. به همین علت فناوری با نام Transferable Objects ایجاد شده است که "کپی صفر" Zero-Copy را پیاده سازی می‌کند و باعث بهبود عملگر کپی می‌شود:
worker.postMessage(arrayBuffer, [arrayBuffer]);
 پارامتر اول آن، آرایه بافر شده است و دومی هم لیست آیتم‌هایی است که قرار است انتقال یابند:
var ab = new ArrayBuffer(1);
worker.postMessage(ab, [ab]);
if (ab.byteLength) {
  alert('Transferables are not supported in your browser!');
} else {
  // Transferables are supported.
}
یا ارسال اطلاعات بیشتر:
worker.postMessage({data: ab1, moreData: ab2},
                   [ab1, ab2]);
در صورتیکه بتواند انتقال را انجام بدهد، byteLength حجم اطلاعات ارسالی را بر می‌گرداند؛ در غیر اینصورت عدد 0 را به عنوان خروجی بر می‌گرداند. در این پرسش و پاسخ می‌توانید نمونه یک انتقال و دریافت را در این روش، ببینید.
نمودار زیر مقایسه‌ای بین Structure Cloning و Transferable Objects است که توسط گوگل منتشر شده است:

RTT=Round Trip Time 

نمودار بالا برای یک فایل 32 مگابایتی است که زمان رفت به ورکر و پاسخ (برگشت از ورکر) را اندازه گرفته‌اند. در ستون‌های اول، این موضوع برای فایرفاکس به روش Structure Cloning  به مدت 302 میلی ثانیه زمان برد که همین موضوع برای Transferables حدود 6.6 میلی ثانیه زمان برد.

آقای اریک بایدلمن در بخش مهندسی کروم گوگل می‌گوید: همین سرعت به ما در انتقال تکسچرها و مش‌ها در WebGL کمک می‌کند.


استفاده از اسکریپت خارجی

در صورتیکه قصد دارید از یک اسکرپیت خارجی، در ورکر استفاده کنید، تابع importScripts برای اینکار ایجاد شده است:

importScripts('script1.js');
importScripts('script2.js');
که البته به طور خلاصه‌تری نیز می‌توان نوشت:
importScripts('script1.js', 'script2.js');

Inline Worker
اگر بخواهید در همان صفحه اصلی یک ورکر را ایجاد کنید و فایل جاوا اسکریپتی خارجی نداشته باشید، می‌توانید از inline worker استفاده کنید. در این روش باید یک نوع blob را ایجاد کنید:
var blob = new Blob([
    "onmessage = function(e) { postMessage('msg from worker'); }"]);

// یک آدرس همانند آدرس ارجاع به فایل درست میکند
var blobURL = window.URL.createObjectURL(blob);

var worker = new Worker(blobURL);
worker.onmessage = function(e) {
  // e.data...
};
worker.postMessage(); // ورکر آغاز می‌شود
کاری که متد حیرت انگیز createObjectURL انجام می‌دهد این است که از داده‌های ذخیره شده در یک blob یا نوع فایل، یک آدرس ارجاعی شبیه آدرس زیر را ایجاد می‌کند:
blob:http://localhost/c745ef73-ece9-46da-8f66-ebes574789b1
آدرس‌هایی که این متد تولید می‌کند، یکتا بوده و تا پایان عمر صفحه، اعتبار دارند. به همین دلیل هر موقع به آن‌ها نیاز نداشتید، از دست آن‌ها خلاص شوید، تا حافظه به هدر نرود. برای آزادسازی حافظه می‌توان دستور زیر را به کار برد:
window.URL.revokeObjectURL(blobURL);
مرورگر کروم با دستور زیر به شما اجازه می‌دهد همه آدرس‌های blob‌ها را ببینید:
chrome://blob-internals