نظرات مطالب
پیاده سازی CQRS توسط MediatR - قسمت اول
سلام؛ اگر فرض کنیم که پروژه ای تا 60 درصد پیاده سازی شده و زیر ساخت آن برای  Event Sourcing دیده نشده باشد، حال چطور می‌توان این مفهوم را برای این پروژه پیاده سازی کرد؟ (در نظر داشته باشید که پروژه بالغ بر 100 Entity دارد و از Entity Framework Core و IUnitOfWork استفاده می‌کند)
نظرات مطالب
بالا بردن سرعت بارگذاری اولیه EF Code first با تعداد مدل‌های زیاد
بله. همین روال رو می‌تونید توسط افزونه «Entity Framework Power Tools» هم انجام بدید (گزینه Generate Views را به منوی کلیک راست بر روی Entity Data Model/*.EDMX اضافه می‌کند).
نظرات مطالب
ابزارهای سراسری در NET Core 2.1.
یک نکته‌ی تکمیلی:  روش به روز رسانی ابزارهای سراسری نصب شده
اگر دستور نصب یک ابزار سراسری مانند dotnet-outdated به این صورت باشد:
dotnet tool install --global dotnet-outdated
دستور به روز رسانی آن به این شکل خواهد بود:
dotnet tool update --global dotnet-outdated
نظرات مطالب
ASP.NET MVC #5
در پروژه MVC خود، خط فرمان پاورشل نیوگت را باز کنید و ابتدا دستور
PM> Install-Package Microsoft.AspNet.Web.Optimization
و بعد دستور زیر را صادر کنید (اگر باز هم کار نکرد، CopyLocal=True را برای اسمبلی System.Web.Optimization در خواص آن انتخاب کنید):
PM> Update-Package
مطالب
استفاده از GZip توکار IISهای جدید و تنظیمات مرتبط با آن‌ها
یکی از نقش‌های IISهای جدید (از نگارش 7 به بعد) که در ویندوز سرورهای قابل نصب است، نقش Performance است و ذیل آن دو نقش فشرده سازی استاتیک و پویا قابل انتخاب است. اگر این نقش‌ها بر روی سرور نصب باشند، دیگر نیازی به استفاده از HTTP Moduleهای متداول فشرده سازی صفحات وب نیست. برای استفاده‌ی از آن تنها کافی است کمی web.config را ویرایش کرد و ... گفته شده‌است که کار می‌کند! اما پس از اعمال تنظیمات، اگر به هدرهای خروجی Response صفحه در ابزارهای web developer مرورگرها دقت کنید، خبری از encoding جدیدی به نام gzip نیست (Content-Encoding: gzip) و به نظر اعمال نمی‌شود. در ادامه بررسی خواهیم کرد که چرا اینگونه است.


فعال سازی GZip توکار IIS

تنظیمات پیش فرض فعال سازی ماژول توکار GZip وب سرورهای جدید شامل دو مرحله است:
الف) تنظیمات سرور جهت فعال سازی فشرده سازی
بر روی ویندوزهای سرور، پس از مراجعه به Administrative Tools -> Server Manager و گشودن Roles آن، ذیل قسمت Web Server که در اینجا IIS است، نیاز است نقش جدیدی به نام Performance اضافه شود و مطابق تصویر، هر دو گزینه‌ی فشرده سازی استاتیک و پویا انتخاب گردد.


بنابراین اولین قدم برای عیب یابی کار نکردن GZip توکار IIS، از این مرحله شروع می‌شود که آیا اصلا ماژول مربوطه نصب هست یا خیر؟

ب) تنظیمات برنامه جهت فعال سازی ماژول GZip
پس از اطمینان از نصب ماژول توکار فشرده سازی صفحات وب IIS در سمت تنظیمات سرور، اکنون باید چند سطر ذیل را به Web.Config برنامه اضافه کرد:
  <system.webServer>

    <httpCompression directory="%SystemDrive%\inetpub\temp\IIS Temporary Compressed Files">
      <scheme name="gzip" dll="%Windir%\system32\inetsrv\gzip.dll" staticCompressionLevel="9" />
      <dynamicTypes>
        <add mimeType="text/*" enabled="true" />
        <add mimeType="message/*" enabled="true" />
        <add mimeType="application/x-javascript" enabled="true" />
        <add mimeType="application/javascript" enabled="true" />
        <add mimeType="application/json" enabled="true" />
        <add mimeType="application/json; charset=utf-8" enabled="true" />
        <add mimeType="application/atom+xml" enabled="true" />
        <add mimeType="application/xaml+xml" enabled="true" />
        <add mimeType="*/*" enabled="false" />
      </dynamicTypes>
      <staticTypes>
        <add mimeType="text/*" enabled="true" />
        <add mimeType="message/*" enabled="true" />
        <add mimeType="application/x-javascript" enabled="true" />
        <add mimeType="application/javascript" enabled="true" />
        <add mimeType="application/json" enabled="true" />
        <add mimeType="application/json; charset=utf-8" enabled="true" />
        <add mimeType="application/atom+xml" enabled="true" />
        <add mimeType="application/xaml+xml" enabled="true" />
        <add mimeType="*/*" enabled="false" />
      </staticTypes>
    </httpCompression>
    <urlCompression doStaticCompression="true" doDynamicCompression="true" />

  </system.webServer>
در اینجا تنظیمات مخصوص نحوه‌ی فعال سازی فشرده سازی توکار صفحات پویا و فایل‌های استاتیک را مشاهده می‌کنید. در این تنظیمات محل قرارگیری فایل‌های موقتی فشرده شده‌ی توسط این ماژول و همچنین mime typeهای مدنظر جهت فشرده سازی، ذکر شده‌اند. با این تنظیمات، تنها mime typeهایی که به صورت صریح ذکر شده‌اند فشرده خواهند شد و از سایر mime types صرفنظر می‌شود.
این تنظیماتی است که در اکثر سایت‌ها نیز یافت می‌شود. ذکر آن‌ها اجباری است و پس از اعمال، اگر برنامه را اجرا کنید ... چیزی فشرده نمی‌شود! علت اصلی را باید در تنظیماتی یافت که مخصوص سرور است و در اینجا ذکر نشده‌اند.


تنظیمات مخصوص آستانه‌ی فشرده سازی صفحات

علت اصلی عدم مشاهده‌ی هدر gzip، در Response برنامه، به frequent hit threshold تنظیم شده‌ی در IIS بر می‌گردد. مقدار آن به 2 درخواست در طی 10 ثانیه تنظیم شده‌است. یعنی اگر به صفحه‌ای در طی 10 ثانیه دو درخواست نرسد، فشرده نخواهد شد. این تنظیم را می‌توان با مراجعه‌ی به configuration editor نود اصلی سرور وب در IIS manager، ویرایش کرد:



برای نمونه در تصویر فوق، این آستانه به یک درخواست در طی 10 ساعت تنظیم شده‌است. این عدد سبب خواهد شد تا تمامی درخواست‌های رسیده حتما فشرده سازی شوند.
این تنظیم معادل یک سطر ذیل در فایل web.config است. اما چون قسمت system.webServer/serverRuntime در تنظیمات سرور قفل شده‌است، هیچ تاثیری نخواهد داشت و حتما باید در سمت سرور و توسط IIS manager اعمال شود:
<system.webServer>
   <serverRuntime frequentHitThreshold="1" frequentHitTimePeriod="10:00:00" />
</system.webServer>
برای آزاد سازی این تنظیمات نیاز است دستور ذیل بر روی سرور اجرا شود. پس از آن کاربران برنامه‌های وب می‌توانند از تنظیمات وب کانفیگ خاص خود استفاده کنند:
 C:\Windows\System32\inetsrv\appcmd.exe unlock config /section:system.webServer/serverRuntime

یک نکته
اگر از سرورهای پس از 2008 استفاده می‌کنید، گزینه‌ی staticCompressionIgnoreHitFrequency نیز به تنظیمات serverRuntime اضافه شده‌است که با تنظیم آن به true، از این حد آستانه، برای فایل‌های استاتیک صرفنظر خواهد شد.


تنظیمات مخصوص اندازه‌ی فایل‌هایی که باید فشرده سازی شوند

تنها حد آستانه‌ی درخواست صفحات وب نیست که بر روی فشرده سازی یا عدم آن ثاثیرگذار است. در اینجا میزان CPU Usage سیستم و یا حتی اندازه‌ی Response خروجی نیز مهم هستند که نمونه‌ای از تنظیمات آن‌را در شکل ذیل مشاهده می‌کنید:


در اینجا با تنظیم minFileSizeForComp به 1024، اعلام شده‌است که حجم‌هایی کمتر از یک کیلوبایت، فشرده سازی نشوند (مقدار پیش فرض آن، بیش از این عدد است).
البته این عدد را به شکل زیر نیز می‌توان به تنظیمات httpCompression وب کانفیگ اضافه کرد:
 <httpCompression directory="%SystemDrive%\inetpub\temp\IIS Temporary Compressed Files" minFileSizeForComp="1024">

پس از اعمال این تنظیمات نیاز است یکبار IIS را نیز ری استارت کرد.


نتیجه گیری

اگر پس از فعال سازی GZip وب سرور، خروجی برنامه فشرده سازی نشد (Content-Encoding: gzip)، علت اینجا است که هنوز 2 درخواست مورد نیاز، در طی 10 ثانیه به سمت سرور ارسال نشده‌اند و تنظیمات پیش فرض این ماژول، جهت حداقل مصرف CPU و فشار بر روی سرور است.
مطالب
آزمایش Web APIs توسط Postman - قسمت سوم - نکات تکمیلی ایجاد درخواست‌ها
تا اینجا، یک دید کلی را نسبت به postman و توانمندی‌های آن پیدا کرده‌ایم. در ادامه قصد داریم، تعدادی نکته‌ی تکمیلی مهم را که در حین ساخت درخواست‌ها در postman، به آن‌ها نیاز پیدا خواهیم کرد، بررسی کنیم.


ایجاد یک request bin جدید

برای مشاهده‌ی محتوای ارسالی توسط postman بدون برپایی وب سرویس خاصی، از سایت requestbin استفاده خواهیم کرد. در اینجا با کلیک بر روی دکمه‌ی create a request bin، یک آدرس موقتی را مانند http://requestbin.fullcontact.com/1gdduy21 برای شما تولید می‌کند. می‌توان انواع و اقسام درخواست‌ها را به این آدرس ارسال کرد و سپس با ریفرش کردن آن در مرورگر، دقیقا محتوای ارسالی به سمت سرور را بررسی نمود.
برای مثال اگر همین آدرس را در postman وارد کرده و سپس بر روی دکمه‌ی send آن کلیک کنیم، پس از ریفرش صفحه، چنین تصویری حاصل می‌شود:



Encoding کوئری استرینگ‌ها در postman

مثال زیر را درنظر بگیرید:


در اینجا با استفاده از گرید ساخت Query Params، دو کوئری استرینگ جدید را ایجاد کرده‌ایم که در دومی، مقدار وارد شده، دارای فاصله است. اگر این درخواست را ارسال کنیم، مشاهده خواهیم کرد که مقدار ارسالی توسط آن encoded نیست:


برای رفع این مشکل می‌توان بر روی تکست‌باکس ورود مقدار یک کلید، کلیک راست کرد و از منوی باز شده، گزینه‌ی encode URI component را انتخاب نمود:


البته برای اینکه این گزینه درست عمل کند، نیاز است یکبار کل متن را انتخاب کرد و سپس بر روی آن کلیک راست نمود، تا انتخاب گزینه‌ی encode URI component، به درستی اعمال شود:



امکان تعریف متغیرها در آدرس‌های HTTP

Postman از امکان تعریف path variables پشتیبانی می‌کند. برای مثال مسیر api.example.com/users/5/contracts/2 را در نظر بگیرید که می‌تواند سبب نمایش اطلاعات قرارداد دوم کاربر پنجم شود. برای پویا کردن یک چنین آدرسی در postman می‌توان از مسیری مانند api.example.com/users/:userid/contracts/:contractid استفاده کرد:


اگر به تصویر فوق دقت کنیم، متغیرهای شروع شده‌ی با :، در قسمت path variables ذکر شده‌اند و به سادگی قابل تغییر و مقدار دهی می‌باشند (در گریدی همانند گرید کوئری استرینگ‌ها) که برای آزمایش دستی، بسیار مفید هستند.


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

زمانیکه برای مثال، نوع درخواست به Post تغییر می‌کند، امکان تنظیم بدنه‌ی آن نیز مسیر می‌شود. در این حالت اگر گزینه‌ی form-data را انتخاب کنیم، با نزدیک کردن اشاره‌گر ماوس به هر ردیف جدید (پیش از ورود کلید آن ردیف)، می‌توان نوع Text و یا File را انتخاب کرد:


در اینجا پس از انتخاب گزینه‌ی File، می‌توان علاوه بر تعیین کلید این ردیف، با استفاده از دکمه‌ی select files، چندین فایل را نیز برای ارسال انتخاب کرد:



روش انتقال درخواست‌های پیچیده به Postman

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


در اینجا گزینه‌ی preserve log را نیز انتخاب کنید تا پس از ارسال درخواستی، سابقه‌ی عملیات، پاک نشود. سپس به صورت معمولی به مطلبی امتیاز دهید. اکنون بر روی مدخل درخواست آن کلیک راست کرده و از منوی ظاهر شده، گزینه‌ی Copy->Copy as cURL را انتخاب کنید تا این عملیات و تمام جزئیات مرتبط با آن‌را تبدیل به یک دستور cURL کرده و به حافظه کپی کند.
سپس در postman، از منوی بالای صفحه، سمت چپ آن، گزینه‌ی Import را انتخاب کنید:


در ادامه این دستور کپی شده‌ی در حافظه را در قسمت Paste Raw Text، قرار دهید و بر روی دکمه‌ی import کلیک کنید:


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

یک نکته‌ی مهم: در حالت انتخاب Copy->Copy as cURL در مرورگر کروم، دو گزینه‌ی cmd و bash موجود هستند. حالت bash را باید انتخاب کنید تا توسط postman به دسترسی parse شود. حالت cmd یک چنین خروجی مشکل داری را در postman تولید می‌کند که قابل ارسال به سمت سرور نیست:


اما جزئیات حالت bash آن، به درستی parse شده‌است و قابلیت send مجدد را دارد:



کار با کوکی‌ها در Postman

کوکی‌ها نیز در اصل به صورت یک هدر HTTP به صورت خودکار توسط مرورگرها به سمت سرور ارسال می‌شوند؛ اما Postman روش ساده‌تری را برای تعریف و کار با آن‌ها ارائه می‌دهد (و ترجیح می‌دهد که بین کوکی‌ها و هدرها تفاوت قائل شود؛ هم در زمان ارسال و هم در زمان نمایش response که در کنار قسمت هدرهای دریافتی از سرور، لیست کوکی‌های دریافتی نیز به صورت مجزایی نمایش داده می‌شوند).


با کلیک بر روی لینک Cookies، در ذیل قسمتی که آدرس یک درخواست تنظیم می‌شود، قسمت مدیریت کوکی‌ها نیز ظاهر خواهد شد که با انتخاب هر نامی در اینجا، می‌توان مقدار آن‌را ویرایش و یا حتی حذف کرد. در این لیست، تمام کوکی‌هایی را که تاکنون تنظیم کرده باشید، می‌توانید مشاهده کنید (و مختص به برگه‌ی خاصی نیست) و همانند قسمت مدیریت کوکی‌های یک مرورگر رفتار می‌کند.
روش کار با آن نیز به این صورت است: ابتدا باید یک دومین را اضافه کنید. سپس ذیل دومین اضافه شده، دکمه‌ی Add cookie ظاهر می‌شود و به هر تعدادی که لازم باشد، می‌توان برای آن دومین کوکی تعریف کرد:


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


به اشتراک گذاری سابقه‌ی درخواست‌ها

در قسمت اول مشاهده کردیم که برای ذخیره سازی درخواست‌ها، باید آن‌ها را در مجموعه‌های Postman، ذخیره و ساماندهی کرد. برای گرفتن خروجی از این مجموعه‌ها و به اشتراک گذاشتن آن‌ها، اگر اشاره‌گر ماوس را بر روی نام یک مجموعه حرکت دهیم، یک دکمه‌ی جدید با برچسب ... ظاهر می‌شود:


با کلیک بر روی آن، یکی از گزینه‌های آن، export نام دارد که جزئیات تمام درخواست‌های یک مجموعه را به صورت یک فایل JSON ذخیره می‌کند. برای نمونه فایل JSON خروجی دو قسمت قبل این سری را می‌توانید از اینجا دریافت کنید: httpbin.postman_collection.json
پس از تولید این فایل JSON، برای بازیابی آن می‌توان از دکمه‌ی Import که در منوی سمت چپ، بالای postman قرار دارد، استفاده کرد که نمونه‌ای از آن‌را برای Import جزئیات درخواست‌های cURL پیشتر مشاهده کردید. در اینجا، همان اولین گزینه‌ی دیالوگ Import که Import file نام دارد، دقیقا برای همین منظور تدارک دیده شده‌است.
مطالب
مقابله با XSS ؛ یکبار برای همیشه!

ASP.NET به صورت پیش فرض در مقابل ارسال هر نوع تگی عکس العمل نشان می‌دهد و پیغام خطای یافتن خطری بالقوه را گوشزد می‌کند. اما بین خودمان باشد، همه این قابلیت را خاموش می‌کنند! چون در یک برنامه واقعی نیاز است تا مثلا کاربران تگ html هم ارسال کنند. برای نمونه یک ادیتور متنی پیشرفته را درنظر بگیرید. خاموش کردن این قابلیت هم مساوی است با فراهم کردن امکان ارسال تگ‌های مجاز و در کنار آن بی دفاع گذاشتن برنامه در مقابل حملات XSS.
توصیه هم این است که همه جا از توابع مثلا HtmlEncode و موارد مشابه حتما استفاده کنید. ولی باز هم خودمونیم ... چند نفر از شماها اینکار را می‌کنید؟!
بهترین کار در این موارد وارد شدن به pipe line پردازشی ASP.NET و دستکاری آن است! اینکار هم توسط HttpModules میسر است. به عبارتی در ادامه می‌خواهیم ماژولی را بنویسیم که کلیه تگ‌های ارسالی کوئری استرینگ‌ها را پاک کرده و همچنین تگ‌های خطرناک موجود در مقادیر ارسالی فرم‌های برنامه را هم به صورت خودکار حذف کند. اما هنوز اجازه بدهد تا کاربران بتوانند تگ HTML هم ارسال کنند.
مشکل! در ASP.NET مقادیر ارسالی کوئری استرینگ‌ها و همچنین فرم‌ها به صورت NameValueCollection در اختیار برنامه قرار می‌گیرند و ... خاصیت IsReadOnly این مجموعه‌ها در حین ارسال، به صورت پیش فرض true است و همچنین غیرعمومی! یعنی به همین سادگی نمی‌توان عملیات تمیزکاری را روی مقادیر ارسالی، پیش از مهیا شدن آن جهت استفاده در برنامه اعمال کرد. بنابراین در ابتدای کار نیاز است با استفاده از قابلیت Reflection ، اندکی در سازوکار داخلی ASP.NET دست برد، این خاصیت فقط خواندنی غیرعمومی را برای مدت کوتاهی false کرد و سپس مقصود نهایی را اعمال نمود. پیاده سازی آن را در ادامه مشاهده می‌کنید:
using System;
using System.Collections.Specialized;
using System.Reflection;
using System.Text.RegularExpressions;
using System.Web;
using Microsoft.Security.Application;
namespace AntiXssMdl { public class AntiXssModule : IHttpModule { private static readonly Regex _cleanAllTags = new Regex("<[^>]+>", RegexOptions.Compiled); public void Init(HttpApplication context) { context.BeginRequest += CleanUpInput; }
public void Dispose() { }
private static void CleanUpInput(object sender, EventArgs e) { HttpRequest request = ((HttpApplication)sender).Request; if (request.QueryString.Count > 0) { //تمیزکاری مقادیر کلیه کوئری استرینگ‌ها پیش از استفاده در برنامه CleanUpAndEncode(request.QueryString, allowHtmltags: false); }
if (request.HttpMethod == "POST") { //تمیزکاری کلیه مقادیر ارسالی به سرور if (request.Form.Count > 0) { CleanUpAndEncode(request.Form, allowHtmltags: true); } } }
private static void CleanUpAndEncode(NameValueCollection collection, bool allowHtmltags) { //اندکی دستکاری در سیستم داخلی دات نت PropertyInfo readonlyProperty = collection .GetType() .GetProperty("IsReadOnly", BindingFlags.Instance | BindingFlags.NonPublic); readonlyProperty.SetValue(collection, false, null);//IsReadOnly=false
for (int i = 0; i < collection.Count; i++) { if (string.IsNullOrWhiteSpace(collection[i])) continue;
if (!allowHtmltags) { //در حالت کوئری استرینگ دلیلی برای ارسال هیچ نوع تگی وجود ندارد collection[collection.Keys[i]] = AntiXss.HtmlEncode(_cleanAllTags.Replace(collection[i], string.Empty)); } else { //قصد تمیز سازی ویوو استیت را نداریم چون در این حالت وب فرم‌ها از کار می‌افتند if (collection.Keys[i].StartsWith("__VIEWSTATE")) continue; //در سایر موارد کاربران مجازند فقط تگ‌های سالم را ارسال کنند و مابقی حذف می‌شود collection[collection.Keys[i]] = Sanitizer.GetSafeHtml(collection[i]); } }
readonlyProperty.SetValue(collection, true, null);//IsReadOnly=true } } }

در این کلاس از کتابخانه AntiXSS مایکروسافت استفاده شده است. آخرین نگارش آن‌را از اینجا دریافت نمائید. نکته مهم آن متد Sanitizer.GetSafeHtml است. به کمک آن با خیال راحت می‌توان در یک سایت، از یک ادیتور متنی پیشرفته استفاده کرد. کاربران هنوز می‌توانند تگ‌های HTML را ارسال کنند؛ اما در این بین هرگونه سعی در ارسال عبارات و تگ‌های حاوی حملات XSS پاکسازی می‌شود.

و یک وب کانفیگ نمونه برای استفاده از آن به صورت زیر می‌تواند باشد (تنظیم شده برای IIS6 و 7):
<?xml version="1.0"?>
<configuration>
<system.web>
  <pages validateRequest="false" enableEventValidation="false" />
  <httpRuntime requestValidationMode="2.0" />
  <compilation debug="true" targetFramework="4.0" />
  <httpModules>
    <add name="AntiXssModule" type="AntiXssMdl.AntiXssModule"/>
  </httpModules>
</system.web>
<system.webServer> <validation validateIntegratedModeConfiguration="false"/> <modules> <add name="AntiXssModule" type="AntiXssMdl.AntiXssModule"/> </modules> </system.webServer> </configuration>

برای مثال به تصویر زیر دقت کنید. ماژول فوق، فقط تگ‌های سبز رنگ را (حین ارسال به سرور) مجاز دانسته، اسکریپت ذیل لینک را کلا حذف کرده و تگ‌های موجود در کوئری استرینگ را هم نهایتا (زمانیکه در اختیار برنامه قرار می‌گیرد) حذف خواهد کرد.

دریافت نسخه جدید و نهایی این مثال
مطالب
نصب Mono Develop 4.x در Ubuntu
پیشنیازها

در قسمت قبل، موفق به نصب Mono 3.0 در لینوکس شدیم. در ادامه قصد داریم یک IDE لینوکسی مخصوص کار با Mono را به نام Mono Develop بر روی Ubuntu نصب کنیم. اگر مونو را نصب کرده‌اید، نیاز است پیشنیازهای ذیل را بر روی سیستم خود نصب کنید:
 sudo apt-get update
sudo apt-get upgrade -y
sudo apt-get install -y build-essential libc6-dev g++ gcc libglib2.0-dev pkg-config \
 git-core apache2 apache2-threaded-dev bison gettext autoconf automake libtool \
libpango1.0-dev libatk1.0-dev libgtk2.0-dev libtiff5-dev libgif-dev libglade2-dev curl \
python-software-properties gawk libjpeg-dev libexif-dev flex checkinstall intltool git \
libcairo2-dev libgnomecanvas2-dev libgnome2-dev libgnomeui-dev libgnomeprint2.2-dev \
libgnomeprintui2.2-dev libgtkhtml3.14-dev libgtksourceview2.0-dev librsvg2-dev libvte-dev \
libnspr4-dev libnss3-dev libwebkit-dev apache2-threaded-dev libpng12-dev libfontconfig1-dev \
libfreetype6-dev zlib1g-dev libjpeg8-dev libjpeg-turbo8-dev libart-2.0-dev libgnomevfs2-dev \
libgnome-desktop-dev libnautilus-extension-dev libwnck-dev libvala-0.18-dev \
mono-addins-utils gtk-sharp2 gnome-sharp2
نصب این پیشنیارها ضروری بوده و در غیر اینصورت موفق به build کامل Mono Develop نخواهید شد. برای مثال پیغام خطای ذیل را در انتهای build دریافت می‌کنید؛ به این معنا که اسمبلی‌های ذیل کامپایل نشده‌اند:
 * art-sharp.dll: no
* gnomevfs-sharp.dll: no
* gnome-sharp.dll: no
و یا عنوان می‌کند که  gnome-sharp.dll برای کامپایل نیاز به یک سری کتابخانه کمکی دیگر نیز دارد:
  gnome-sharp.dll requires libgnomecanvas, libgnome, and libgnomeui.


نصب متداول محیط برنامه نویسی Mono Develop

برای نصب یک IDE که بتوان تحت همان لینوکس نیز کار برنامه نویسی دات نت را انجام داد، می‌توان از Mono deveop استفاده کرد. برای نصب آن فرمان ذیل را در خط فرمان لینوکس صادر نکنید !
 sudo apt-get install monodevelop
این روش هر چند کار می‌کند، اما تا این تاریخ، نگارش 3 را نصب خواهد کرد؛ با توجه به اینکه آخرین نگارش موجود در سایت Mono Develop، نگارش 4 است. همچنین نصب آن نیز نگارش جاری Mono را به نگارش 2 آن تنظیم می‌کند که جالب نیست. اگر به اشتباه آن‌را نصب کرده‌اید، برای حذف مونو از دستور ذیل استفاده کنید:
 sudo apt-get purge cli-common mono-runtime
همچنین الان کلیه مسیرهای سیستم به هم ریخته است. برای رفع آن مسیر نصب Mono-3.0 را باید به نحو ذیل مجددا تنظیم کرد:
 export PATH=/opt/mono-3.0/bin:$PATH
export PKG_CONFIG_PATH=/opt/mono-3.0/lib/pkgconfig:$PKG_CONFIG_PATH


نصب محیط برنامه نویسی Mono Develop از روی مخزن کد آن

دریافت و نصب وابستگی‌های Monodevelop جهت کامپایل سورس آن، شاید نصف روز شما را به خود اختصاص دهد؛ به علاوه حداقل مصرف حدود 500 مگابایت حجم اینترنت. راه ساده‌تری نیز برای دریافت آخرین نگارش سازگار با Ubuntu آن وجود دارد و آن هم استفاده از بسته‌های شخصی کامپایل شده است؛ که اصطلاحا به آن‌ها PPA نیز گفته می‌شود. برای مثال: (^ و ^ )
چند نمونه بسته شخصی برای دریافت ساده آخرین نگارش Mono develop جهت نصب بر روی Ubuntu : (^ و ^ و ^ )
و به صورت خلاصه فرامین ذیل را در ترمینال لینوکس اجرا کنید تا از بسته شخصی keks9n استفاده کنیم:
 sudo add-apt-repository ppa:keks9n/monodevelop-latest
sudo apt-get update
sudo apt-get install monodevelop-latest
این روش، از تمام روش‌های ذکر شده تا کنون، ساده‌تر است. از این لحاظ که mono 3.2.1 را نیز به صورت خودکار بر روی سیستم شما نصب می‌کند (این بسته شخصی، به صورت خودکار هر از چندگاهی آخرین نگارش مونو، وابستگی‌های آن و monodevelop جدید را بسته بندی و ارائه می‌دهد).
بنابراین اگر مونو 3.2.1 یا جدیدتر را هنوز نصب نکرده‌اید، همین سه سطر فوق، کار نصب کلی آن‌را نیز انجام می‌دهد؛ علاوه بر نصب monodevelop در آخر کار به همراه تمام پیشنیازهای لازم مانند gtk-sharp و gnome-sharp.
پس از نصب کامل، برای اجرای آن در همان خط فرمان، دستور monodevelop را صادر کنید.


مطالب
React 16x - قسمت 23 - ارتباط با سرور - بخش 2 - شروع به کار با Axios
پس از نصب Axios در قسمت قبل، جزئیات کار با آن‌را در این بخش مرور می‌کنیم.


دریافت اطلاعات از سرور، توسط Axios

- ابتدا به پوشه‌ی sample-22-backend ای که در قسمت قبل ایجاد کردیم، مراجعه کرده و فایل dotnet_run.bat آن‌را اجرا کنید، تا endpointهای REST Api آن، قابل دسترسی شوند. برای مثال باید بتوان به مسیر https://localhost:5001/api/posts در مرورگر دسترسی یافت (و یا همانطور که عنوان شد، از آدرس https://jsonplaceholder.typicode.com/posts نیز می‌توانید استفاده کنید؛ چون ساختار یکسانی دارند).

-سپس در برنامه‌ی React ای که در قسمت قبل ایجاد کردیم، فایل app.js آن‌را گشوده و ابتدا کتابخانه‌ی Axios را import می‌کنیم:
import axios from "axios";
در قسمت 9 که Lifecycle hooks را در آن بررسی کردیم، عنوان شد که در اولین بار نمایش یک کامپوننت، بهترین مکان دریافت اطلاعات از سرور و سپس به روز رسانی UI، متد componentDidMount است. به همین جهت میانبر cdm را در VSCode نوشته و دکمه‌ی tab را فشار می‌دهیم تا به صورت خودکار این متد را ایجاد کند. در ادامه این متد را به صورت زیر تکمیل می‌کنیم:
  componentDidMount() {
    const promise = axios.get("https://localhost:5001/api/posts");
    console.log(promise);
  }
متد axios.get، کار دریافت اطلاعات از سرور را انجام می‌دهد و اولین آرگومان آن، URL مدنظر است. این متد، یک Promise را بازگشت می‌دهد. یک Promise، شیءای است که نتیجه‌ی یک عملیات async را نگهداری می‌کند و یک عملیات async، عملیاتی است که قرار است در آینده تکمیل شود. زمانیکه یک HTTP GET را ارسال می‌کنیم، وقفه‌ای تا زمان بازگشت اطلاعات از سرور وجود خواهد داشت و این عملیات، آنی نیست. بنابراین حالت آغازین یک Promise، در وضعیت pending قرار می‌گیرد. پس از پایان عملیات async، این وضعیت به یکی از حالات resolved (در حالت موفقیت آمیز بودن عملیات) و یا rejected (در حالت شکست عملیات) تغییر پیدا می‌کند.



تنظیمات CORS مخصوص React در برنامه‌های ASP.NET Core 3x

همانطور که مشاهده می‌کنید، پس از ذخیره سازی تغییرات، با اجرای برنامه، این Promise در حالت pending قرار گرفته و همچنین پس از پایان آن، حاوی نتیجه‌ی عملیات نیز می‌باشد که در اینجا rejected است. علت شکست عملیات را در سطر بعدی آن ملاحظه می‌کنید که عنوان کرده‌است «CORS policy» مناسبی در سمت سرور، برای این درخواست وجود ندارد؛ چرا؟ چون برنامه‌ی React ما در مسیر http://localhost:3000/ اجرا می‌شود و برنامه‌ی Web API در مسیر دیگری https://localhost:5001/ که شماره‌ی پورت این‌دو یکی نیست. به همین جهت عنوان می‌کند که نیاز است در سمت سرور، هدرهای خاصی برای پردازش این نوع درخواست‌های با Origin متفاوت وجود داشته باشد، تا مرورگر اجازه‌ی دسترسی به آن‌را بدهد. برای رفع این مشکل، برنامه‌ی sample-22-backend را گشوده و تغییرات زیر را اعمال می‌کنیم:
ابتدا تنظیمات AddCors را با تعریف یک CORS policy جدید مخصوص آدرس http://localhost:3000، به متد ConfigureServices کلاس آغازین برنامه اضافه می‌کنیم:
public void ConfigureServices(IServiceCollection services)
{
    services.AddCors(options =>
    {
       options.AddPolicy("ReactCorsPolicy",
          builder => builder
            .AllowAnyMethod()
            .AllowAnyHeader()
            .WithOrigins("http://localhost:3000")
            .AllowCredentials()
            .Build());
    });
    services.AddSingleton<IPostsDataSource, PostsDataSource>();
    services.AddControllers();
}
سپس میان‌افزار آن‌را با فراخوانی UseCors که باید بین UseRouting و UseEndpoints تعریف شود، فعال می‌کنیم:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
      app.UseDeveloperExceptionPage();
    }

    app.UseHttpsRedirection();

    app.UseRouting();

    //app.UseAuthentication();
    //app.UseAuthorization();

    app.UseCors("ReactCorsPolicy");

    app.UseAuthorization();

    app.UseEndpoints(endpoints =>
    {
       endpoints.MapControllers();
    });
}
اکنون اگر صفحه‌ی برنامه‌ی React را ریفرش کنیم، به نتیجه‌ی زیر خواهیم رسید:


اینبار Promise بازگشت داده شده، در حالت resolved قرار گرفته‌است که به معنای موفقیت آمیز بودن عملیات async است. وجود [[PromiseStatus]] به معنای یک internal property است که توسط dot notation قابل دسترسی نیست. در اینجا [[PromiseValue]] نیز یک internal property غیرقابل دسترسی است که نتیجه‌ی عملیات (response دریافتی از سرور) در آن قرار می‌گیرد. برای مثال در data آن، آرایه‌ی مطالب دریافتی از سرور، قابل مشاهده‌است و یا status=200 به معنای موفقیت آمیز بودن پردازش درخواست، از سمت سرور است.

البته زمانیکه درخواست افزودن رکورد جدیدی را به سمت سرور ارسال می‌کنیم، می‌توان دو درخواست را در برگه‌ی network ابزارهای توسعه دهندگان مرورگر، مشاهده کرد:


در اولین درخواست، Request Method: OPTIONS را داریم که دقیقا مرتبط است با بررسی CORS توسط مرورگر.


دریافت اطلاعات شیء response از یک Promise و نمایش آن

همانطور که عنوان شد، [[PromiseValue]] نیز یک internal property غیرقابل دسترسی است. بنابراین اکنون این سؤال مطرح می‌شود که چگونه می‌توان به اطلاعات آن دسترسی یافت؟
این شیء Promise، دارای متدی است به نام then است که نتیجه‌ی عملیات async را بازگشت می‌دهد. البته این روش قدیمی کار کردن با Promiseها است و ما از آن در اینجا استفاده نخواهیم کرد. در جاوا اسکریپت مدرن، می‌توان از واژه‌ی کلیدی await برای دسترسی به شیء response دریافتی از سرور، استفاده کرد:
  async componentDidMount() {
    const promise = axios.get("https://localhost:5001/api/posts");
    console.log(promise);
    const response = await promise;
    console.log(response);
  }
هر جائیکه از واژه‌ی کلیدی await استفاده می‌شود، متد جاری را باید با واژه‌ی کلیدی async نیز مزین کرد. پس از این تغییرات، اکنون شیء response، حاوی اطلاعات اصلی و واقعی دریافتی از سرور است؛ برای مثال خاصیت data آن، حاوی آرایه‌ی مطالب می‌باشد:



البته قطعه کد نوشته شده، صرفا جهت توضیح مراحل مختلف عملیات، به این صورت چند مرحله‌ای نوشته شد، وگرنه می‌توان واژه‌ی کلیدی await را پیش از فراخوانی متدهای Axios نیز قرار داد:
  async componentDidMount() {
    const response = await axios.get("https://localhost:5001/api/posts");
    console.log(response);
  }
با توجه به اینکه اطلاعات اصلی شیء response، در خاصیت data آن قرار دارد، می‌توان با استفاده از Object Destructuring، خاصیت data آن‌را دریافت و سپس تغییر نام داد:
class App extends Component {
  state = {
    posts: []
  };

  async componentDidMount() {
    const { data: posts } = await axios.get("https://localhost:5001/api/posts");
    this.setState({ posts }); // = { posts: posts }
  }
پس از مشخص شدن آرایه‌ی posts دریافتی از سرور، اکنون می‌توان با فراخوانی متد setState و به روز رسانی خاصیت posts آن، سبب رندر مجدد این کامپوننت و در نتیجه نمایش اطلاعات نهایی شد:



ایجاد یک مطلب جدید توسط Axios

در برنامه‌ی React ای ایجاد شده، یک دکمه‌ی Add نیز برای افزودن مطلبی جدید درنظر گرفته شده‌است. در یک برنامه‌ی واقعی‌تر، معمولا فرمی وجود دارد و نتیجه‌ی آن در حین submit، به سمت سرور ارسال می‌شود. در اینجا این سناریو را شبیه سازی خواهیم کرد:
const apiEndpoint = "https://localhost:5001/api/posts";

class App extends Component {
  state = {
    posts: []
  };

  async componentDidMount() {
    const { data: posts } = await axios.get(apiEndpoint);
    this.setState({ posts });
  }

  handleAdd = async () => {
    const newPost = {
      title: "new Title ...",
      body: "new Body  ...",
      userId: 1
    };
    const { data: post } = await axios.post(apiEndpoint, newPost);
    console.log(post);

    const posts = [post, ...this.state.posts];
    this.setState({ posts });
  };
توضیحات:
- چون قرار است از آدرس https://localhost:5001/api/posts در قسمت‌های مختلف برنامه استفاده کنیم، فعلا آن‌را به صورت یک ثابت تعریف کرده و در متدهای get و post استفاده کردیم.
- در متد منتسب به خاصیت handleAdd، یک شیء جدید post را با ساختاری مشابه آن ایجاد کرده‌ایم. این شیء جدید، دارای Id نیست؛ چون قرار است از سمت سرور پس از ثبت در بانک اطلاعاتی دریافت شود.
- سپس این شیء جدید را توسط متد post کتابخانه‌ی Axios، به سمت سرور ارسال کرده‌ایم. این متد نیز یک Promise را باز می‌گرداند. به همین جهت از واژه‌ی کلیدی await برای دریافت نتیجه‌ی واقعی آن استفاده شده‌است. همچنین هر زمانیکه await داریم، نیاز به ذکر واژه‌ی کلیدی async نیز هست. اینبار این واژه باید پیش از قسمت تعریف پارامتر متد قرار گیرد و نه پیش از نام handleAdd؛ چون handleAdd در واقع یک خاصیت است که متدی به آن انتساب داده شده‌است.
- نتیجه‌ی دریافتی از متد axios.post را اینبار به post، بجای posts تغییر نام داده‌ایم و همانطور که در تصویر زیر مشاهده می‌کنید، خاصیت id آن در سمت سرور مقدار دهی شده‌است:


- در آخر برای افزودن این رکورد، به مجموعه‌ی رکوردهای موجود، از روش spread operator استفاده کرده‌ایم تا ابتدا شیء post دریافتی از سمت سرور درج شود و سپس مابقی اعضای آرایه‌ی posts موجود در state، در این آرایه گسترده شده و یک آرایه‌ی جدید را تشکیل دهند. سپس این آرایه‌ی جدید را جهت به روز رسانی state و در نتیجه‌ی آن، به روز رسانی UI، به متد setState ارسال کرده‌ایم، که نتیجه‌ی آن درج این رکورد جدید، در ابتدای لیست است:


 
به روز رسانی اطلاعات در سمت سرور

در اینجا پیاده سازی متد put را مشاهده می‌کنید:
  handleUpdate = async post => {
    post.title = "Updated";
    const { data: updatedPost } = await axios.put(
      `${apiEndpoint}/${post.id}`,
      post
    );
    console.log(updatedPost);

    const posts = [...this.state.posts];
    const index = posts.indexOf(post);
    posts[index] = { ...post };
    this.setState({ posts });
  };
- با کلیک بر روی دکمه‌ی update هر ردیف نمایش داده شده، شیء post آن ردیف را در اینجا دریافت و سپس برای مثال خاصیت title آن‌را به مقداری جدید به روز رسانی می‌کنیم.
- اکنون امضای متد axios.put هرچند مانند متد post است، اما متد Update تعریف شده‌ی در سمت API سرور، یک چنین مسیری را نیاز دارد api/Posts/{id}. به همین جهت ذکر id مطلب، در URL نهایی نیز ضروری است.
- در اینجا نیز از واژه‌های await و async برای دریافت نتیجه‌ی واقعی عملیات put و همچنین عملیات گذاری این متد به صورت async، استفاده شده‌است.
- در آخر، ابتدا آرایه‌ی posts موجود در state را clone می‌کنیم. چون می‌خواهیم در آن، در ایندکسی که شیء post جاری قرار دارد، مقدار به روز رسانی شده‌ی آن‌را قرار دهیم. سپس این آرایه‌ی جدید را جهت به روز رسانی state و در نتیجه‌ی آن، به روز رسانی UI، به متد setState ارسال کرده‌ایم:



حذف اطلاعات در سمت سرور

برای حذف اطلاعات در سمت سرور، نیاز است یک HTTP Delete را به آن ارسال کنیم که اینکار را می‌توان توسط متد axios.delete انجام داد. URL ای را که دریافت می‌کند، شبیه به URL ای است که برای حالت put ایجاد کردیم:
  handleDelete = async post => {
    await axios.delete(`${apiEndpoint}/${post.id}`);

    const posts = this.state.posts.filter(item => item.id !== post.id);
    this.setState({ posts });
  };
پس از به روز رسانی وضعیت سرور، در چند سطر بعدی، کار فیلتر سمت کلاینت مطالبی را انجام می‌دهیم که id مطلب حذف شده، در آن‌ها نباشد. سپس state را جهت به روز رسانی UI، با این آرایه‌ی جدید posts، به روز رسانی می‌کنیم.


کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید: sample-22-backend-part-02.zip و sample-22-frontend-part-02.zip
نظرات مطالب
نحوه تعریف Linked Server و دریافت اطلاعات از سروری دیگر
ممنون.
تراکنش‌های توزیع شده فعال هستند.
در حال حاضر وقتی یک SP رو از داخل نرم افزار یا کوئری اجرا میکنم درست اجرا میشه، اما به وقتی فراخوانی رو به داخل تریگر جدول انتقال میدم خطا میده، نکته جالب اینه که دستور SELECT از MySql درست کار میکنه تو تریگر اما دستوراتی که اقدام به تغییر اطلاعات میکنن خطا میده.
نکته :
1- ویندوز 10
2- SQL Server 2016
3- Mysql 5.1

دستورات زیر در صورتی که در خارج از تریگر فراخوانی بشن عملکردشون درسته ، اما در تریگر خطا میدن :

-- Insert --
INSERT  INTO OPENQUERY(MyLinkServer, 'SELECT * FROM unit')
VALUES  ( 1, 5, 'fa', '0', GETDATE(), '1', GETDATE(), '1' );
----------------------------------------------------------------------------------------
-- Update --
--1
UPDATE  OPENQUERY(MyLinkServer , 'SELECT * FROM unit WHERE id=4')
SET   [is_deleted] = '0';
--2
EXEC('UPDATE unit set is_deleted=''1'' where id=4;') AT MyLinkServer ;
-- 3
UPDATE  OPENQUERY(MyLinkServer , 'SELECT * FROM unit')
SET   [is_deleted] = '0'
WHERE id = 4;
----------------------------------------------------------------------------------------
-- Delete --
DELETE  OPENQUERY(MyLinkServer, 'select * from unit where id=4;');