مطالب
ارتقاء به ASP.NET Core 1.0 - قسمت 15 - بررسی تغییرات Caching
در نگارش‌های پیشین ASP.NET MVC با استفاده از Output Cache، امکان کش کردن خروجی یک اکشن متد، وجود دارد. مکانیزم Output Cache از ASP.NET Core حذف شده‌است؛ اما جایگزین‌های قابل توجهی برای آن تدارک دیده شده‌اند.


معرفی Response Cache

جایگزین ویژگی حذف شده‌ی OutputCache در ASP.NET Core، ویژگی جدیدی است به نام ResponseCache و هدف آن تنظیم هدرهای مرتبط با caching مخصوص HTTP Response ارائه شده‌است. به همین جهت با مکانیزم OutputCache قدیمی ASP.NET MVC که اطلاعات را در حافظه‌ی سرور کش می‌کرد، کاملا متفاوت است.
البته قرار است میان افزار OutputCache را در نگارش‌های آتی ASP.NET Core نیز ارائه کنند.
[ResponseCache(Duration = 60)]
public IActionResult Contact()
{
   ViewData["Message"] = "Your contact page.";
   return View();
}
در اینجا مثالی را از نحوه‌ی تعریف این ویژگی جدید، ملاحظه می‌کنید که در آن مقدار خاصیت مدت زمان کش شدن، برحسب ثانیه است. استفاده‌ی از آن سبب خواهد شد تا هدر HTTP ذیل به خروجی از سرور اضافه شود:
 Cache-Control: public,max-age=60

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


تعیین مکان کش شدن خروجی یک اکشن متد

در هدر فوق، عبارت public را مشاهده می‌کنید. این public بودن به این معنا است که امکان کش شدن این خروجی، توسط کش سرورهای اشتراکی بین راه هم وجود دارد.
اگر می‌خواهید این امکان را غیرفعال کنید، نیاز است این public به private تنظیم شود:
[ResponseCache(Duration = 60, Location = ResponseCacheLocation.Client)]
تنظیم Location فوق به Client به معنای private شدن هدر تنظیم شده و صرفا کش شدن خروجی، توسط کش مرورگر کاربر می‌باشد.


غیرفعال کردن کش شدن خروجی یک اکشن متد

اگر خواستید از کش شدن خروجی یک اکشن متد تحت هر حالتی جلوگیری کنید، مکان آن‌را به None و NoStore آن‌را به true تنظیم کنید:
[ResponseCache(Location = ResponseCacheLocation.None, NoStore = true)]
public IActionResult Error()
{
   return View();
}
این تنظیم سبب افزوده شدن یک چنین هدر HTTP ایی به خروجی از سرور می‌شود:
Cache-Control: no-store,no-cache
Pragma: no-cache


امکان تعریف پروفایل‌های کش

بجای اینکه تنظیمات کش کردن تکراری را به انواع و اقسام اکشن متدها اعمال کنیم، می‌توان برای آن‌ها پروفایل ایجاد کرده و از نام این پروفایل، جهت به اشتراک گذاری تنظیمات استفاده کنیم. برای این منظور به کلاس آغازین برنامه مراجعه کرده و جایی که سرویس ASP.NET MVC را فعال سازی کرده‌اید، پروفایل کش جدیدی را تعریف کنید:
public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc(options =>
    {
        options.CacheProfiles.Add("PrivateCache",
            new CacheProfile
            {
                Duration = 60,
                Location = ResponseCacheLocation.Client
            }); 
    });
پس از آن برای استفاده‌ی از این تنظیمات اشتراکی، فقط کافی است تا نام پروفایل مرتبطی را ذکر کنیم:
[ResponseCache(CacheProfileName = "PrivateCache")]


معرفی سرویس کش درون حافظه‌ای

در نگارش‌های پیشین ASP.NET، متدهایی برای کش کردن موقتی اطلاعات در حافظه و سپس بازیابی آن‌ها وجود داشتند. در ASP.NET Core، این متدها توسط سرویس ارائه کننده‌ی IMemoryCache در اختیار برنامه قرار می‌گیرند. برای فعال سازی این سرویس جدید باید مراحل ذیل طی شوند:
الف) ابتدا بسته‌ی Microsoft.Extensions.Caching.Memory را به لیست وابستگی‌های پروژه در فایل project.json اضافه کنید:
{
    "dependencies": {
      //same as before
      "Microsoft.Extensions.Caching.Memory": "1.0.0"
 },
ب) سپس به کلاس آغازین برنامه مراجعه کرده و سرویس آ‌ن‌را معرفی و ثبت کنید:
public void ConfigureServices(IServiceCollection services)
{
  services.AddMemoryCache();
ج) پس از آن سرویس پیاده سازی کننده‌ی IMemoryCache،  در تمام اجزای برنامه در دسترس خواهد بود. برای مثال:
[Route("DNT/[controller]")]
public class AboutController : Controller
{
    private readonly IMemoryCache _memoryCache;
 
    public AboutController(IMemoryCache memoryCache)
    {
        _memoryCache = memoryCache;
    }
 
    [Route("")]
    public ActionResult Hello()
    {
 
        string cacheKey = "my-cache-key";
        string greeting;
 
        if (!_memoryCache.TryGetValue(cacheKey, out greeting))
        {
             greeting = "Hello";
            // store in the cache
            _memoryCache.Set(cacheKey, greeting,
                new MemoryCacheEntryOptions()
                .SetAbsoluteExpiration(TimeSpan.FromMinutes(1)));
        }
 
        return Content($"{greeting} from DNT!");
    }
در مثال فوق، ابتدا وابستگی سرویس کش درون حافظه‌ای، به سازنده‌ی کنترلر تزریق شده‌است. تامین آن هم توسط سرویسی که در کلاس آغازین برنامه ثبت کردیم، انجام می‌شود. پس از آن در اکشن متد Hello، سعی کرده‌ایم بر اساس کلید کشی که مشخص کرده‌ایم، مقداری را بازیابی کنیم. اگر این مقدار وجود نداشته باشد، آن‌را توسط متد Set تنظیم خواهیم کرد تا برای دفعات آتی فراخوانی این متد، مورد استفاده قرار گیرد.
تنظیمات منقضی شدن کش نیز به حالت absolute تنظیم شده‌است. یعنی پس از یک دقیقه حتما منقضی می‌شود. اگر فراخوانی‌های این متد زیاد است، می‌توان حالت منقضی شدن sliding را تنظیم کرد:
 new MemoryCacheEntryOptions()
  .SetSlidingExpiration(TimeSpan.FromMinutes(5))
در این حالت اگر پیش از اتمام 5 دقیقه‌ی تنظیم شده، درخواستی به سرور رسید، این کش برای 5 دقیقه‌ی بعد نیز مجددا تمدید می‌شود.
اگر خواستیم تا این کش سر ساعت منقضی شود، اما در طی این یک ساعت به صورت sliding عمل کند، می‌توان از ترکیب دو حالت مطلق و لغزشی استفاده کرد:
 new MemoryCacheEntryOptions()
  .SetSlidingExpiration(TimeSpan.FromMinutes(5))
  .SetAbsoluteExpiration(TimeSpan.FromHours(1))

یک نکته: اگر فشار حافظه‌ی سرور زیاد شود، مدیر حافظه‌ی این کش، شروع به منقضی کردن آیتم‌هایی با حق تقدم پایین می‌کند. بالاترین حق تقدم را حالت NeverRemove ذیل دارد:
 new MemoryCacheEntryOptions()
  .SetPriority(CacheItemPriority.NeverRemove))


معرفی Tag Helpers مخصوص کش کردن قسمتی از صفحه

در ادامه‌ی مبحث معرفی Tag Helpers، تعدادی از آن‌ها جهت کش کردن محتوای قسمتی از صفحه، طراحی شده‌اند:
<cache expires-after="@TimeSpan.FromMinutes(10)">
    @Html.Partial("_WhatsNew")
    *last updated  @DateTime.Now.ToLongTimeString()
</cache>
تگ جدید cache محتوای دربرگیرنده‌ی آن‌را «در حافظه‌ی سرور» کش می‌کند (و در پشت صحنه از همان کش درون حافظه‌ای که پیشتر بحث شد، استفاده می‌کند). تگ cache در خروجی HTML نهایی مشاهده نمی‌شود و صرفا مفهومی سمت سرور است.
برای نمونه در مثال فوق، محتوای پارشال ویوو رندر شده و همچنین تاریخی که پس از آن نمایش داده شده‌است، به مدت 10 دقیقه در حافظه‌ی سرور کش می‌شوند. اگر این زمان تنظیم نشود، تا زمانیکه برنامه در سرور مشغول به کار است، این قسمت منقضی نخواهد شد.
در اینجا اگر expires-after ذکر شده بود، یعنی پس از این مدت زمان، کش منقضی می‌شود.
<cache expires-after="@TimeSpan.FromSeconds(5)">
    <!--View Component or something that gets data from the database-->
    *last updated  @DateTime.Now.ToLongTimeString()
</cache>
 اگر expires-on آن ذکر شود، می‌توان تاریخ و زمان مشخصی را در اینجا ذکر کرد (برای مثال فردا ساعت 10، با فراخوانی DateTime.Today.AddDays).
<cache expires-on="@DateTime.Today.AddDays(1).AddTicks(-1)">
  <!--View Component or something that gets data from the database-->
 *last updated  @DateTime.Now.ToLongTimeString()
</cache>
همچنین می‌توان از expires-sliding نیز استفاده کرد. به این معنا که اگر در طی مدتی خاص این صفحه درخواست نشد، آنگاه این کشی منقضی می‌شود.
<cache expires-sliding="@TimeSpan.FromMinutes(5)">
    <!--View Component or something that gets data from the database-->
    *last updated  @DateTime.Now.ToLongTimeString()
</cache>
همچنین در اینجا می‌توان کش کردن را به ازای کاربران مختلف، کوئری استرینگ‌های مختلف و امثال آن انجام داد (با ارائه‌ی محتوای متفاوتی به ازای پارامترهای مختلف):
<cache vary-by-user="true">
    <!--View Component or something that gets data from the database-->
    *last updated @DateTime.Now.ToLongTimeString()
</cache>
در این حالت دیگر نیازی نیست تا نگران این باشیم که آیا محتوای قسمت کش شده‌ی از صفحه برای تمام کاربران در دسترس است یا خیر؟ در اینجا هر کاربر لاگین شده‌ی به سیستم، نگارش کش شده‌ی خاص خودش را دریافت می‌کند.

<cache vary-by-route="id">
    <!--View Component or something that gets data from the database-->
    *last updated  @DateTime.Now.ToLongTimeString()
</cache>
در اینجا به ازای پارامتر آی‌دی مسیریابی، نگارش‌های مختلف کش شده‌ای از صفحه تامین می‌شوند. در اینجا می‌توان لیستی از پارامترهای جدا شده‌ی با کاما را مشخص کرد.

<cache vary-by-query="search">
    <!--View Component or something that gets data from the database-->
    *last updated  @DateTime.Now.ToLongTimeString()
</cache>
امکان کش کردن محتوای صفحه به ازای کوئری استرینگ‌های مختلف تنظیم شده نیز وجود دارد.

<cache vary-by-cookie="MyAppCookie">
    <!--View Component or something that gets data from the database-->
    *last updated  @DateTime.Now.ToLongTimeString()
</cache>
در اینجا به ازای محتواهای مختلف کوکی خاصی به نام MyAppCookie، نگارش‌های مختلف کش شده‌ای از صفحه ذخیره می‌شوند.

 <cache vary-by-header="User-Agent">
    <!--View Component or something that gets data from the database-->
    *last updated  @DateTime.Now.ToLongTimeString()
</cache>
در اینجا می‌توان به ازای هدرهای مختلف پروتکل HTTP نگارش‌های کش شده‌ی متفاوتی را ارائه داد.

<cache vary-by="@ViewBag.ProductId">
    <!--View Component or something that gets data from the database-->
    *last updated  @DateTime.Now.ToLongTimeString()
</cache>
اگر خواستید کلید کش را خودتان تعیین کنید از vary-by استفاده کنید.

 <cache vary-by-user="true" vary-by-route="id">
    <!--View Component or something that gets data from the database-->
    *last updated  @DateTime.Now.ToLongTimeString()
</cache>
امکان ترکیب این موارد با هم نیز وجود دارد.

به علاوه چون زیر ساخت این Tag Helper همان Microsoft.Extensions.Caching.Memory است، امکان تنظیم حق تقدم حذف شدن آیتم‌های کش شده نیز وجود دارد:
<cache expires-sliding="@TimeSpan.FromMinutes(10)"
priority="@Microsoft.Extensions.Caching.Memory.CacheItemPriority.NeverRemove">
    <!--View Component or something that gets data from the database-->
    *last updated  @DateTime.Now.ToLongTimeString()
</cache>


مبحث تکمیلی

امکان ذخیره سازی آیتم‌های کش شده در بانک اطلاعاتی (بجای حافظه‌ی فرار) نیز پیش بینی شده‌است که تحت عنوان «کش توزیع شده» در دسترس است.
Working with a Distributed Cache
مطالب
توسعه برنامه های Cross Platform با Xamarin Forms & Bit Framework - قسمت پنجم
همانطور که در قسمت‌های قبلی گفتیم، کد UI و Logic پروژه مابین Android-iOS-Windows مشترک بوده و از یک کد، سه بار استفاده مجدد می‌شود. تا این جا نیز می‌دانید که چطور کد را روی ویندوز و Android تست کرده و پابلیش بگیرید. این قسمت، نوبت به iOS می‌رسد.
برای دیباگ و تست پروژه‌ها بر روی iOS چه بر روی Simulator و چه بر روی Device، نیاز به وجود یک Mac در شبکه است. حال این می‌تواند یک Mac Book Pro فیزیکی باشد یا یک Virtual Mac روی VM Ware. ابتدا به آموزش راه اندازی Mac بر روی VM Ware می‌پردازیم، سپس ادامه راه برای Mac، روی یک سخت افزار واقعی و Virtual Mac یکی است.

برای شروع لازم است فایل iso مک را دانلود کنید. من "Mac OS 10.14 iso download" را جستجو کردم به این سایت رسیدم که فایل مناسب برای دانلود را در این لینک قرار داده بود.
سپس اقدام به دانلود VM Ware کنید و چون VM Ware به صورت پیش فرض از Mac پشتیبانی نمی‌کند، VM Ware Unlocker را نیز جستجو کرده، دانلود و نصب کنید. مطمئن شوید که نسخه‌های مورد استفاده شما قدیمی نباشند. نسخه مورد استفاده بر روی سیستم من 14.1 است.
برای این که VM Ware کار کند، باید اقدام به " فعال سازی intel virtualization" هم کرده باشید که  این سایت  یکی از آموزش‌های موجود بر روی بستر اینترنت، برای این کار است. همچنین برای نصب Mac احتیاج به Apple ID دارید که رایگان است.
علاوه بر نصب Mac، لازم است روی آن XCode نیز نصب شود. رابطه مستقیمی بین نسخه ویژوال استدیو، نسخه Mac و نسخه XCode برقرار است. مثلا برای استفاده از iOS SDK 12 که آخرین نسخه است، لازم است XCode 10 داشته باشید. خود XCode 10 بر روی Mac با نسخه 10.13.6 و بالاتر نصب می‌شود و در نهایت نیاز به Visual Studio 15.8.5 به بالا است. صرف به روز رسانی ویژوال استودیو از مثلا 15.7 به 15.8.5 دردی را دوا نمی‌کند و نیاز به بروز رسانی Mac و XCode نیز هست. (تمامی این موارد را خود ویژوال استودیو چک می‌کند و در صورت لزوم خطا می‌دهد)
پس از دانلود فایل iso برای Mac و نصب و Unlock کردن VM Ware، آماده راه اندازی Mac هستید. در VM Ware از منوی File روی New virtual machine کلیک کرده و در مراحل بعدی آن iso دانلود شده را معرفی و Virtual machine را ذخیره کنید.
بهتر است برای Mac خود حدود 40GB فضای هارد SSD در نظر بگیرید. در دادن CPU و رم، دست و دل باز باشید. توجه داشته باشید که لازم است Mac به اینترنت دسترسی داشته باشد. برای بهبود سرعت Virtual Machine تان، فولدر محل ذخیره سازی فایل‌های آن و Process ای با نام vmware-vmx.exe را  در آنتی ویروس ویندوزتان Exclude کنید.
قبل از این که Mac را روشن کنید، یک فایل با پسوند vmx را در فولدر Virtual Machine تان پیدا کنید و خط زیر را به آخر آن اضافه کرده و Save کنید:
smc.version = "0"
سپس روی Mac راست کلیک نموده و از Settings > Hardware > USB Controller مقدار USB Compatibility را روی 2.0 بگذارید و تیک Show all usb input devices را نیز بزنید.
Mac را روشن کنید تا مراحل نصب آغاز شود. ابتدا از macOS Utilities، گزینه Disk Utility هارد 40 گیگا بایتی مجازی را که ساخته بودید، انتخاب و Erase کنید و سپس برنامه را بسته و Install MacOS را انتخاب کنید. در واقع هاردی را که ساخته بودید، مثل یک هارد فیزیکی بوده که پارتشن بندی نشده و غیر قابل استفاده بوده‌است. با کمک Disk Utility این هارد آماده به استفاده می‌شود.
حال به قسمت تنظیمات Mac رفته و در قسمت Energy Saver، تیک گزینه Put hard disk to sleep را بردارید. همچنین Computer Sleep و Display Sleep را روی Never بگذارید. این یک Virtual Machine است و به این تمهیدات احتیاجی ندارد. سپس از قسمت Desktop & screen saver، تنظیم کنید که هیچ وقت Screen saver را نمایش ندهد. از قسمت Security & Privacy، تیک Require password را نیز بردارید.
در Mac ترمینال (Terminal) را باز کنید و عبارت زیر را در آن اجرا کنید تا فایل‌های مخفی را نیز بتوانید مشاهده کنید. در ادامه به آن احتیاج داریم.
defaults write com.apple.finder AppleShowAllFiles YES
همچنین برای بهبود عملکرد Simulator در virtual machine که فاقد کارت گرافیک است، دستور زیر را در ترمینال وارد کنید:
defaults write com.apple.CoreSimulator.IndigoFramebufferServices FramebufferEmulationHint 1
در تنظیمات Mac، قسمت Sharing، تیک Remote Login را بزنید تا فعال شود. File Sharing را نیز بر اساس  این آموزش  برقرار کنید. سپس Mac را Re Start کنید.

برای نصب XCode دو گزینه دارید:
۱- نصب از Apple Store که نیاز به Apple ID و ابزارهای دور زدن تحریم بر روی خود Mac را دارد.
۲- دانلود مستقیم فایل xip (فایل فشرده در Mac) از  اینجا که نیاز به Apple ID دارد، ولی نیازی به ابزارهای دور زدن تحریم را ندارد. در مقاله‌ای جداگانه به نحوه دانلود ویژوال استودیو 2017، برای نصب روی چندین سیستم خواهم پرداخت. در کنار آن اگر فایل iso مربوط به mac و xip مربوط به xcode را نیز داشته باشید، روی سیستم دوم به بعد، نیاز به دانلود چندانی ندارید.
اگر فایل xip مربوط به XCode را مستقیما درون Mac دانلود کنید یا از Apple Store بگیرید که هیچ، ولی اگر آن را در ویندوز دانلود کرده‌اید و یا از کسی گرفته‌اید، از طریق File Sharing، آن را به Mac انتقال دهید. برای به دست آوردن IP، از طریق System Preferences به Network بروید.
برای نصب فایل xip در mac، ابتدا روی آن دو بار کلیک کنید تا extract شود. سپس در Mac از منوی Go، به Applications رفته (یا Shift و آرم ویندوز و A را با هم بفشارید) و XCode را روی آن Drag & Drop کنید؛ به طوری که یکی از برنامه‌های قسمت Applications شود.  سپس آن را باز کرده و از منوی XCode، به Preferences رفته و در تب Locations، جلوی Command Line Tools، گزینه Xcode 10 را انتخاب کنید. حالا یک Mac را درون شبکه دارید که Remote Login دارد و Command Line Tools مربوط به XCode آن نیز تنظیم شده‌است.  به هیچ روی، نیازی به کد زدن در Mac یا XCode نیست.

برای اتصال ویژوال استودیو به Mac، ابتدا پروژه مثال XamApp را باز کرده و بعد به منوی Tools رفته و Options را باز کنید و به Xamarin بروید. در قسمت iOS Settings روی Pair Mac کلیک کنید و IP مربوط به Mac خود را وارد کنید. بعد از زدن نام کاربری و رمز عبور، ویژوال استودیو درخواست نصب Mono را می‌کند که روی Install کلیک کنید. پس از اتمام نصب Mono، ممکن است ویژوال استودیو گمان کند که موفق نبوده‌است! برای اطمینان، در Mac ترمینال را باز کرده و دستور mono --version را وارد نمایید. در صورتی که عبارتی شبیه به Mono JIT compiler version 5.12 را ببینید، عملا پروسه با موفقیت انجام شده است، وگرنه Mono را بر اساس  این آموزش  در Mac نصب کنید. سپس ویژوال استودیو، Xamarin.iOS را در Mac نصب می‌کند.
همچنین در صورت داشتن Apple Developer Account می‌توانید در ویژوال استودیو با آن لاگین کنید. ابتدا به منوی Tools رفته و Options را باز کنید و از قسمت Xamarin به  Apple Accounts بروید. روی Install fastlane کلیک کنید و بعد User/Pass خود را وارد کنید. بدون Apple Developer Account، تست برنامه روی گوشی کمی سخت می‌شود و پابلیش و گرفتن فایل ipa بدون آن غیر ممکن است. اینها تماما سیاست‌های Apple بوده و به Xamarin ارتباطی ندارند. در مقاله آموزش App Center، به نحوه پابلیش فایل‌های Android apk و iOS ipa برای تستر‌ها و عموم مردم خواهیم پرداخت.
حال می‌توانید پروژه‌های XamApp.Android و XamApp.UWP را Unload نموده و پروژه XamApp.iOS را Load کنید و پروژه XamApp.iOS را Set as start up project نموده و پس از اطمینان از انتخاب بودن iPhone Simulator و iPhone 6، برنامه را اجرا کنید.

امکان تست بر روی بر روی آخرین نسخه iOS یعنی 12 بر روی iPhone 5s تا iPhone XS Max وجود دارد. علاوه بر این می‌توانید iOS 12 را روی iPad نسل پنج و شش و iPad Air 1 و 2 و iPad Pro تست کنید. اگر قصد تست روی نسخه‌های قدیمی‌تر iOS، چون iOS 11 یا سایر دستگاه‌ها را دارید، باید ابتدا در XCode، از منوی Window به Devices and simulators بروید و در تب Simulators روی + کلیک کنید. در قسمت OS version می‌توانید نسخه‌های قدیمی‌تر را دانلود کنید یا برای Apple TV و Apple Watch نیز Simulator بسازید.

برنامه را روی iPhone 6 یا یک گوشی سبک و ساده دیگر گذاشته و برنامه را اجرا کنید و تست بگیرید. دقت کنید که Simulator روی ویندوز اجرا می‌شود و نیازی به سوئیچ مداوم بین ویندوز و Mac نیست. ولی اگر Simulator روی ویندوز از لحاظ UI ای عملکرد مناسبی را نداشت، در ویژوال استودیو به منوی Tools رفته و از Options > Xamarin > iOS settings تیک Remote simulator to Windows را بردارید که باعث می‌شود Simulator در Mac اجرا شود. در هر بار عوض کردن Simulator از گوشی ای به گوشی دیگر، پروژه را Clean - Rebuild کنید.

در سری‌های بعد که ویژوال استودیو را باز می‌کنید، از منوی Tools > iOS گزینه Pair Mac را بزنید و به Mac خود متصل شوید.


برای اینکه بتوانید روی گوشی تست بگیرید، در همین عکس بالا، به جای iPhoneSimulator، گزینه iPhone را انتخاب کنید. بهتر است گوشی به آخرین نسخه iOS آپدیت شده باشد. همچنین iTunes for windows را نصب کنید تا ابتدا ویندوز، گوشی شما را که با کابل به کامپیوتر وصل کرده‌اید بشناسد. سپس در VM Ware درخواست کنید که گوشی به جای ویندوز، به Virtual Machine مک شما وصل شود. در قسمت پایین - سمت راست VM Ware برای هر سخت افزار متصل به کامپیوتر، یک گزینه هست که آنهایی که پر رنگ هستند، سخت افزارهای متصل به Virtual Mac بوده و کمرنگ‌ها را Mac نمی‌بیند. روی سخت افزار گوشی خود کلیک کنید و Connect را بزنید. حال باید بتوانید در iTunes موجود در Mac، اطلاعات مربوط به گوشی خود را مشاهده کنید. 

به ویژوال استودیو برگشته و در قسمت Properties پروژه XamApp.iOS، به تب iOS Bundle signing رفته و ضمن انتخاب Automatic provisioning، در قسمت Team، تیم خود را انتخاب کنید. این Team را در زمان ساخت Apple Developer account ایجاد کرده‌اید و همانطور که قبلا در این آموزش گفته شد، اگر در ویژوال استودیو با آن لاگین کرده باشید، می‌توانید آن را ببینید. در صورتی که Apple Developer account ندارید، بر اساس این آموزش پیش بروید.

زمانیکه برنامه را روی گوشی اجرا می‌کنید، ممکن است در Mac دیالوگ گرفتن نام کاربری و رمز عبور کاربر Mac باز شود، پس نیم نگاهی به آن داشته باشید. پس از اولین اجرای موفق روی گوشی می‌توانید در XCode به منوی Window رفته و سپس Devices & simulators را باز کنید و گوشی خود را در قسمت Devices انتخاب کرده و تیک Connect via network را بزنید تا از این به بعد، بدون کابل نیز بتوانید روی گوشی خود تست کنید. البته گوشی، ویندوز و مک، باید در یک شبکه باشند.

نحوه پابلیش پروژه را در مقاله مربوط به App Center خواهیم نوشت. اما به صورت خلاصه حجم فایل ipa حدود 10 مگ است که شامل تمامی مواردی است که در قسمت Android توضیح دادیم و پروژه نهایی شما حجمی در همین حدود خواهد داشت و با اضافه کردن چندین فرم حجم اضافه نمی‌شود. همانطور که در قسمت توضیحات پیشرفته پروژه اندروید توضیح دادیم، اینجا نیز از AOT و LLVM برای دست‌یابی به بالاترین سرعت ممکن کدهای Native استفاده شده و کدهای اضافه از پروژه Link (حذف) می‌شوند. برای اینکار، در ویژوال استودیو iPhone - Release را انتخاب کنید و پروژه را بیلد کنید. فایل ipa درون Mac در فولدر 

Library⁩ ▸ ⁨Caches⁩ ▸ ⁨Xamarin⁩ ▸ ⁨mtbs⁩ ▸ ⁨builds⁩ ▸ ⁨XamApp.iOS⁩ ▸ ⁨e9979ba2348d1c5a87390643d62c4a1b⁩ ▸ ⁨bin⁩ ▸ ⁨iPhone⁩ ▸ Release ▸ XamApp.iOS.ipa 

ساخته می‌شود که Library فولدری در User شما بوده و مقدار Guid مربوطه، Random است. همچنین XamApp.iOS نام پروژه است.

حالت‌های متصور برای عکس بالا عبارتند از [Debug - iPhoneSimulator] و [Debug - iPhone] و [Release - iPhone] که دو تای اول برای تست روی Simulator و Device بوده و حالت سوم برای Release کردن فایل ipa. طبیعی است که تنظیم [Release - Simulator] معنی نمی‌دهد.

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

اشتراک‌ها
چگونه npm را در ویندوز به روز رسانی کنیم؟

اگر نگارش‌های آغازین ابزارهای node.js را در ویندوز نصب کرده باشید، با نصب نگارش آخرین نگارش آن ... هیچ تغییری را مشاهده نخواهید کرد و عملا به روز رسانی نمی‌شود و هنوز با اجرای دستور npm -v، همان نگارش قبلی نمایش داده می‌شود. برای رفع این مشکل، ابتدا آخرین بسته‌ی node.js را نصب کنید. سپس دستور npm -g i npm را صادر کنید؛ اگر باز هم به روز رسانی صورت نگرفت، از روش زیر استفاده نمائید:

Set-ExecutionPolicy Unrestricted -Scope CurrentUser -Force
npm install -g npm-windows-upgrade
npm-windows-upgrade --npm-version latest
چگونه npm را در ویندوز به روز رسانی کنیم؟
اشتراک‌ها
Docker ها در ویندوز سرور 2016
 Today the Docker team announced the availability of a technical preview of the Docker Engine for Windows Server 2016. This is huge for us and all software teams that work primarily on the Windows platform
یک لینک هم در مورد Dockerها در ویندوز سرور 2016 از سایت weblogs.asp.net
Visual Studio 2015 Tools for Docker - August Preview 

برای اطلاعات بیشتر در مورد Docker‌ها هم می‌توانید به ترجمه این مصاحبه مراجعه کنید  ^^^
Docker ها در ویندوز سرور 2016
نظرات مطالب
VS Code برای توسعه دهندگان ASP.NET Core - قسمت دوم - ایجاد و اجرای اولین برنامه
نکته : اگر نیاز دارید در نوار آدرس بالای صفحه دسترسی به  Windows Terminal  داشته باشید به مانند cmd توضیح داده شده در این مطلب :

ویندوز ترمینال رو باز کرده به قسمت "Settings"  بروید.
فایل profiles.json را ویرایش کرده و starting directory را به آن اضافه نمایید.
"profiles": {
    "defaults": {
       // Put settings here that you want to apply to all profiles
       "startingDirectory": "."
    }...
 در نوار آدرس بالای صفحه تایپ کنید wt به این صورت بلافاصله  Windows Terminal  در پوشه‌ی جاری در دسترس خواهد بود .



 
مطالب
آشنایی با SQL Server Data Tools
مقدمه
یکی از امکانات اضافه شده در Sql Server 2012 ابزار Sql Server Data Tools یا به اختصار SSDT می‌باشد. این ابزار در واقع جایگزین ابزار Business Intelligence Development Studio می باشد که همه امکانات قبلی را داشته و همچنین قابلیت‌های جدیدی نیز به آن اضافه شد است. اما کاربرد این ابزار ایجاد محیطی یکپارچه برای طراحی و توسعه تمامی نسخه‌های پایگاه داده Sql Server با استفاده از Visual Studio می‌باشد. در این مقاله من به بیشتر بر روی امکانات زیر تمرکز دارم:
  • Installation
  • Creating a SQL Server Database Project
  • Modify Database Schema
  • Schema Compare and Update
  • Snapshot Project
  • Publish
  1. نصب Sql Server Data Tools
    آخرین نسخه این ابزار را از این آدرس دانلود کنید یا زمان نصب Sql Server 2012 گزینه آن را انتخاب کنید


  2. ایجاد یک پروژه جدید از نوع  SQL Server Database Project
    پس از نصب SSDT شما از طریق Visual Studio 2012 Shell که همراه SqlServer 2012 نصب می‌شود یا با Visual Stadio 2012 یک پروژه جدید از نوع SQL Server Database Project ایجاد کنید.

     Server Database Project Project به شما امکان توسعه پایگاه داده Sql Server را با استفاده  از محیط یکپارچه Visual Studio با در اختیار گذاشتن ابزار‌های همچون navigation, intellisense, validation, debugging, declarative editing و غیره را می‌دهد. شی پایگاه داده شما در پروژه و فایل‌های مجزا ذخیره می‌شود مثل آن که شما در حال توسعه برنامه #C یا VB.NET هستید.
  3. ورود پایگاه داده موجود به پروژه SQL Server Database
    شما می‌توانید شمای پایگاه داده موجود یا SQL Server DAC Package File (.dacpac) یا هر T-SQL دیگری را با راست کلیک بر روی پروژه و انتخاب گزینه Import به پروژه خود اضافه و تغییرات لازم را اعمال نماید شکل زیر پنجره Import Database  را نشان می‌دهد:
     

      پنجره 
    Solution Explorer امکانات زیر را در اختیار شما قرارمی دهد:

    - اضافه کردن اشیای همچون Table، View  و غیره با راست کیلک کردن بر روی پروژه و انتخاب گزینه Add
    - ویرایش اشیا موجود با دبل کیلک کردن بر روی اشیا
    - مقایسه شمای پایگاه داده با پایگاه داده دیگر و یا Microsoft SQL Server DAC Package File کافیست بر روی پروژه راست کیلک و گزینه Schema Compare را         انتخاب نمایید.
    - ایجاد یک Snapshot از شمای پایگاه داده در یک Microsoft SQL Server DAC Package File . یک snapshot برای ایجاد یک پایگاه داده یا ورود در پروژه ای دیگر یا در مقایسه دو پایگاه داده کاربرد دارد.
    - انتشار پایگاه داده که امکان ایجاد پایگاه داده یر روی یک سرور SQL Server را فراهم می‌نماید.  

  4. مقایسه شمای دو پایگاه داده با هم
    SSDT ابزاری برای مقایسه دو پایگاه داده و بروز رسانی پایگاه داده مقصد از روی شمای منبع از طریق ایجاد یک Script یا به صورت مستقیم را می‌دهد.



  5. انتشار پروژه با استفاده از SQL Server Data Tools
    این ابزار امکان گسترش پروژه شما بر روی SQL Server 2005, 2008, 2008 R2, 2012, یا SQL Azure instance را می‌دهد. برای انتشار کافیت از پنجره Solution Explorer  بر روی پروژه راست کیلک و گزینه Publish را انتخاب کنید.

  6. پنجره SQL Server Object Explorer
    این پنجره با اتصال به سرور SQL Server  امکان دسترسی و مدیریت پایگاه داده را به ما می‌دهد. علاوه بر دسترسی به اجزای یک پایگاه داده امکان مدیریت پوشه‌های Security , Server Objects نیز فراهم می‌باشد. همچنین امکان اتصال به Sql Server Express , localdb نیز وجود دارد. 


  7.   
مطالب
Blazor 5x - قسمت 15 - کار با فرم‌ها - بخش 3 - ویرایش اطلاعات
در قسمت قبل، ویژگی‌های ثبت اطلاعات یک اتاق جدید و سپس نمایش لیست آن‌ها را تکمیل کردیم. در این قسمت می‌خواهیم امکان ویرایش آن‌ها را نیز اضافه کنیم.


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

تا اینجا کامپوننت Pages\HotelRoom\HotelRoomUpsert.razor دارای یک چنین مسیریابی است:
@page "/hotel-room/create"
در ادامه می‌خواهیم اگر کاربری برای مثال به آدرس hotel-room/edit/1 مراجعه کرد، اطلاعات رکورد متناظر با آن نمایش داده شود؛ تا بتواند آن‌ها را ویرایش کند. یعنی می‌خواهیم از همین صفحه، هم برای ویرایش اطلاعات موجود و هم برای ثبت اطلاعات جدید استفاده کنیم. بنابراین نیاز به تعریف مسیریابی دومی در کامپوننت HotelRoomUpsert وجود دارد:
@page "/hotel-room/create"
@page "/hotel-room/edit/{Id:int}"
در اینجا مسیریابی دوم تعریف شده، یک پارامتر مقید شده‌ی به نوع int را انتظار دارد. مزیت این مقید سازی، نمایش خودکار صفحه‌ی «یافت نشد» تعریف شده‌ی در فایل BlazorServer.App\App.razor، با ورود اطلاعاتی غیر عددی است. مسیریابی اول، برای ثبت اطلاعات مورد استفاده قرار می‌گیرد و مسیریابی دوم، برای ویرایش اطلاعات.
پس از تعریف مسیریابی دریافت پارامتر Id رکورد در حال ویرایش، نحوه‌ی واکنش نشان دادن به آن در کامپوننت HotelRoomUpsert.razor به صورت زیر است:
@code
{
    // ...

    [Parameter] public int? Id { get; set; }

    protected override async Task OnInitializedAsync()
    {
        if (Id.HasValue)
        {
            // Update Mode
            Title = "Update";
            HotelRoomModel = await HotelRoomService.GetHotelRoomAsync(Id.Value);
        }
        else
        {
            // Create Mode
            HotelRoomModel = new HotelRoomDTO();
        }
    }

    // ...
}
- در اینجا پارامتر عددی Id مسیریابی را از نوع nullable تعریف کرده‌ایم. علت اینجا است که اگر کاربر با مسیریابی اول تعریف شده، به این کامپوننت برسد، یعنی قصد ثبت اطلاعات جدیدی را دارد. بنابراین ذکر Id، اختیاری است.
- سپس می‌خواهیم در زمان بارگذاری کامپوننت جاری، اگر مقدار Id، تنظیم شده بود، تمام فیلدهای فرم متصل به شیء HotelRoomModel را به صورت خودکار بر اساس اطلاعات رکورد متناظر با آن در بانک اطلاعاتی، مقدار دهی اولیه کنیم.
<EditForm Model="HotelRoomModel" OnValidSubmit="HandleHotelRoomUpsert">
چون فرم جاری توسط کامپوننت EditForm تعریف شده‌است و مدل آن به HotelRoomModel متصل است، بر اساس two-way binding‌های تعریف شده‌ی در اینجا، اگر مقدار Model را به روز رسانی کنیم، به صورت خودکار تمام فیلدهای متصل به آن نیز مقدار دهی اولیه خواهند شد.
کار مقدار دهی اولیه‌ی فیلدهای یک کامپوننت نیز باید در روال رویداد گردان OnInitializedAsync انجام شود که نمونه‌ای از آن را در کدهای فوق مشاهده می‌کنید. در این مثال اگر Id مقدار داشته باشد، مقدار آن‌را به متد GetHotelRoomAsync ارسال کرده و سپس شیء DTO دریافتی از آن‌را به مدل فرم انتساب می‌دهیم تا فرم ویرایشی، قابل استفاده شود:


در ادامه برای ساده سازی رسیدن به مسیرهایی مانند hotel-room/edit/1، به کامپوننت Pages\HotelRoom\HotelRoomList.razor مراجعه کرده و در همان ردیفی که اطلاعات رکورد یک اتاق را نمایش می‌دهیم، لینکی را نیز به صفحه‌ی ویرایش آن، اضافه می‌کنیم:
<tr>
    <td>@room.Name</td>
    <td>@room.Occupancy</td>
    <td>@room.RegularRate.ToString("c")</td>
    <td>@room.SqFt</td>
    <td>
        <NavLink href="@($"hotel-room/edit/{room.Id}")" class="btn btn-primary">Edit</NavLink>
    </td>
</tr>
برای این منظور فقط کافی است در جائیکه tr هر رکورد رندر می‌شود، یک td مخصوص NavLink منتهی به hotel-room/edit/{room.Id} را نیز تعریف کنیم:



مدیریت ثبت اطلاعات ویرایش شده‌ی یک اتاق، در بانک اطلاعاتی

در حین تکمیل این قسمت می‌خواهیم پیام‌هایی مانند موفقیت آمیز بودن عملیات را نیز به کاربر نمایش دهیم. به همین جهت مراحل «Blazor 5x - قسمت یازدهم - مبانی Blazor - بخش 8 - کار با جاوا اسکریپت» را برای افزودن کتابخانه‌ی معروف جاوا اسکریپتی Toastr طی می‌کنیم که شامل این قسمت‌ها است:
- دریافت و نصب بسته‌های jquery و toastr
- اصلاح فایل Pages\_Host.cshtml برای افزودن مداخل فایل‌های CSS و JS بسته‌های نصب شده
- تعریف فایل جدید wwwroot\js\common.js برای سادگی کار با توابع جاوا اسکریپتی toastr و افزودن مدخل آن به فایل Pages\_Host.cshtml
- تعریف متدهای الحاقی JSRuntimeExtensions ، جهت کاهش کدهای تکراری فراخوانی متدهای toastr و افزودن فضای نام آن به فایل Imports.razor_

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

همچنین پیش از تکمیل ادامه‌ی کدهای ویرایش اطلاعات، نیاز است متد IsRoomUniqueAsync را به صورت زیر اصلاح کنیم:
namespace BlazorServer.Services
{
    public interface IHotelRoomService
    {
        Task<bool> IsRoomUniqueAsync(string name, int roomId);

        // ...
    }
}

namespace BlazorServer.Services
{
    public class HotelRoomService : IHotelRoomService
    {
        // ...
 
        public Task<bool> IsRoomUniqueAsync(string name, int roomId)
        {
            if (roomId == 0)
            {
                // Create Mode
                return _dbContext.HotelRooms
                                .ProjectTo<HotelRoomDTO>(_mapperConfiguration)
                                .AnyAsync(x => x.Name != name);
            }
            else
            {
                // Edit Mode
                return _dbContext.HotelRooms
                                .ProjectTo<HotelRoomDTO>(_mapperConfiguration)
                                .AnyAsync(x => x.Name != name && x.Id != roomId);
            }
        }
    }
}
پیشتر در قست 13، بررسی منحصربفرد بودن نام اتاق، از طریق بررسی وجود نام آن در بانک اطلاعاتی صورت می‌گرفت. اینکار در حین ثبت اطلاعات یک رکورد جدید (Id==0) مشکلی را ایجاد نمی‌کند. اما اگر در حال ویرایش اطلاعات باشیم، چون این نام پیشتر ثبت شده‌است، پیام تکراری بودن نام اتاق را دریافت می‌کنیم؛ حتی اگر در اینجا نام اتاق در حال ویرایش را تغییر نداده باشیم و همان نام قبلی باشد. به همین جهت نیاز است در حالت ویرایش اطلاعات، اگر نامی در سایر رکوردها و نه رکوردی با Id مساوی فرم در حال ویرایش، با نام جدید یکی بود، آنگاه آن نام را به صورت تکراری گزارش دهیم که نمونه‌ای از آن‌را در اینجا مشاهده می‌کنید.

اکنون متد HandleHotelRoomUpsert کامپوننت Pages\HotelRoom\HotelRoomUpsert.razor جهت مدیریت ثبت و ویرایش اطلاعات به صورت زیر تغییر می‌کند:
// ...
@inject IJSRuntime JsRuntime


@code
{
   // ...

    private async Task HandleHotelRoomUpsert()
    {
        var isRoomUnique = await HotelRoomService.IsRoomUniqueAsync(HotelRoomModel.Name, HotelRoomModel.Id);
        if (!isRoomUnique)
        {
            await JsRuntime.ToastrError($"The room name: `{HotelRoomModel.Name}` already exists.");
            return;
        }

        if (HotelRoomModel.Id != 0 && Title == "Update")
        {
            // Update Mode
            var updateResult = await HotelRoomService.UpdateHotelRoomAsync(HotelRoomModel.Id, HotelRoomModel);
            await JsRuntime.ToastrSuccess($"The `{HotelRoomModel.Name}` updated successfully.");
        }
        else
        {
            // Create Mode
            var createResult = await HotelRoomService.CreateHotelRoomAsync(HotelRoomModel);
            await JsRuntime.ToastrSuccess($"The `{HotelRoomModel.Name}` created successfully.");
        }

        NavigationManager.NavigateTo("hotel-room");
    }
}
- در ابتدا چون می‌خواهیم پیام‌هایی را توسط Toastr نمایش دهیم، سرویس IJSRuntime را به کامپوننت جاری تزریق کرده‌ایم که این سرویس، امکان دسترسی به متدهای الحاقی ToastrError و ToastrSuccess تعریف شده‌ی در فایل Utils\JSRuntimeExtensions.cs را می‌دهد (کدهای آن در قسمت 11 ارائه شدند).
- در ابتدا عدم تکراری بودن نام اتاق بررسی می‌شود:


- در آخر بر اساس Id مدل فرم، حالت ویرایش و یا ثبت اطلاعات را تشخیص می‌دهیم. البته Id مدل فرم، در حالت ثبت اطلاعات نیز صفر است؛ به علت فراخوانی ()HotelRoomModel = new HotelRoomDTO که سبب مقدار دهی Id آن با عدد پیش‌فرض صفر می‌شود. بنابراین صرفا بررسی Id مدل، کافی نیست و برای مثال می‌توان عنوان تنظیم شده‌ی در متد OnInitializedAsync را نیز بررسی کرد.
- پس از تشخیص حالت ویرایش و یا ثبت، یکی از متدهای متناظر HotelRoom Service را مانند UpdateHotelRoomAsync و یا CreateHotelRoomAsync فراخوانی کرده و سپس پیامی را به کاربر نمایش داده و او را به صفحه‌ی نمایش لیست اتاق‌ها، هدایت می‌کنیم:




کدهای کامل این مطلب را از اینجا می‌توانید دریافت کنید: Blazor-5x-Part-15.zip
مطالب
breeze js به همراه ایجاد سایت آگهی قسمت دوم
نصب: پکیج‌های متنوعی از breeze وجود دارند. برای ما بسته‌ی زیر بهترین انتخاب می‌باشد. با نصب پکیج زیر، breeze در سمت سرور و کلاینت، به همراه ASP.NET Web API 2.2 and Entity Framework 6 نصب می‌شود:
Install-Package Breeze.WebApi2.EF6
بعد از نصب، دو فایل جاوا اسکریپتی به پروژه اضافه میشوند: breeze.debug.js  فایل اصلی breeze می‌باشد که از Backbone و Knockout پشتیبانی می‌کند و breeze.min.js که فایل فشرده شده breeze.debug میباشد. برای بهتر کار کردن با angularjs و breezejs، کتابخانه‌ی Breeze.Angular را  نصب نمایید. یکی از مواردی که این سرویس برای ما انجام می‌دهد،interceptor ایی را برای درخواست‌های http فعال می‌کند. یکی از موارد استفاده‌ی آن، ارسال token امنیتی، قبل از درخواست‌های breeze به کنترلر میباشد:
var instance = breeze.config.initializeAdapterInstance("ajax", "angular");
instance.setHttp($http);
Install-Package Breeze.Angular
 قلب تپنده‌ی breezejs در کلاینت EntityManager است که نقش data context را در کلاینت، بازی می‌کند. به برخی از خصوصیات آن می‌پردازیم:
  var manager = new breeze.EntityManager({  
  dataService: dataService,          
  metadataStore: metadataStore,                     
  saveOptions: new breeze.SaveOptions({    allowConcurrentSaves: true, tag: [{}] })   
                 });

var dataService = new breeze.DataService({  
serviceName: "/breeze/"+ "Automobile",             
hasServerMetadata: false,
namingConvention: breeze.NamingConvention.camelCase        
});
var metadataStore = new breeze.MetadataStore({});

- serviceName: نام سرویس دهنده یا کنترلر سمت سرور میباشد. درمورد کنترلر سمت سرور کمی جلوتر بحث می‌کنیم.
- metadataStore: اطلاعاتی را در مورد تمام آبجکت‌ها (جداول دیتابیس) می‌دهد. مثل نام فیلدها، نوع فیلدها و...
برای کار با متادیتا دو راه وجود دارد:
1- متا دیتا را خودتان در سمت کلاینت ایجاد نمایید:
var myMetadataStore = new breeze.MetadataStore();
myMetadataStore.addEntityType({...});
یا برای اضافه کردن فیلد شهر به جدول customer:
 var customer = function () {
                    this.City = "";
                };
myMetadataStore.registerEntityTypeCtor("Customer", customer);
2- اطلاعات  را از سرور دریافت  نمایید. در این صورت  کنترلر شما باید دارای متد Metadata باشد. بنابراین کنترلی را در سرور به نام Automobile و با محتویات زیر ایجاد نمایید. همانطور که مشاهده می‌کنید، این کنترلر از ApiController مشتق شده است که تفاوت خاصی با Api‌‌های دیگر ندارد و تنها به BreezeController مزین شده است. این attribute به NET WebApi  کمک میکند که فیلترینگ و مرتب سازی با فرمت oData را فراهم کند و همچنین درک صحیح فرمت json را نیز به کنترلر می‌دهد.
EFContextProvider: کامپوننتی که تعامل بین کنترلر breeze با Entity Framework را ساده‌تر می‌کند و در واقع یک  wrapper بر روی دیتاکانتکس یا آبجکت کانتکس می‌باشد. یکی از وظایف آن  ارسال متا دیتا، برای کلاینت‌های breeze است.
[BreezeController]
public class AutomobileController : ApiController
    {
        readonly EFContextProvider<ApplicationDbContext> _contextProvider =
        new EFContextProvider<ApplicationDbContext>();
        [HttpGet]
        public string Metadata()
        {
            return _contextProvider.Metadata();
        }
        [HttpGet]
        public IQueryable<Customer> Customers() {
           return _contextProvider.Context.Customers;
        }

        [System.Web.Http.HttpPost]
        public SaveResult SaveChanges(JObject saveBundle)
        {
           _contextProvider.BeforeSaveEntitiesDelegate = BeforeSaveEntities;
           _contextProvider.AfterSaveEntitiesDelegate = afterSaveEntities;
            return _contextProvider.SaveChanges(saveBundle);
        }
protected Dictionary<Type, List<EntityInfo>> BeforeSaveEntities(Dictionary<Type, List<EntityInfo>> saveMap)
        {
        }
private void afterSaveEntities(Dictionary<Type, List<EntityInfo>> saveMap, List<KeyMapping> keyMappings)
        {
        }
    }
در اینجا متدی مانند Customers، از طریق کلاینت‌های breeze قابل دسترسی می‌باشد.

- saveOptions: نحوه‌ی چگونگی برخورد با ذخیره کردن اطلاعات را مشخص می‌کند. با ذخیره سازی تغییرات، متد SaveChanges سمت سرور فراخوانی می‌شود. در breeze می‌توان به قبل و بعد از ذخیره سازی اطلاعات دسترسی داشت. یکی از موارد رایج کاربرد آن، اعمال چک کردن دسترسی‌ها، قبل از ذخیره سازی می‌باشد.
برای ذخیره سازی تغییرات:
manger.saveChanges().then(function success() {
                    }, function failer(e) {
                    });
برای نادیده گرفتن تغییرات:
manger.rejectChanges()

کوئری:
بعد از تعریف Entity Manger می‌توانیم کوئری خود را اجرا نماییم. کوئری ما شامل گرفتن اطلاعات از جدول Customer، با مرتب سازی بر روی فیلد آیدی می‌باشد و با اجرا کردن کوئری می‌توانیم موفقیت یا عدم موفقیت آن‌را بررسی نماییم. 
   var query = breeze.EntityQuery
            .from("Customer")   
            .orderBy("Id");
   var result= manager.executeQuery(query);
   result.then(querySucceeded)
    .fail(queryFailed);

   query = query.where("Id", "==", 1)
با نوشتن Predicate تکی یا ترکیب آنها نیز می‌توان شرط‌های پیچیده‌تری را ایجاد کرد:
var predicate = new breeze.Predicate("Id", "==", false);
query = query.where(predicate)

var p1 = new breeze.Predicate("IsArchived", "==", false);
var p2 = breeze.Predicate("IsDone", "==", false); 
var predicate = p1.and(p2);
query = query.where(predicate).orderBy("Id")  
در اینجا خروجی مشابه زیر برای کنترلر ارسال میشود:
?$filter=IsArchived eq false&IsDone eq false  &$orderby=Id

اعتبارسنجی
:اعتبارسنجی در breeze، هم در سمت کلاینت و هم در سمت سرور امکان پذیر می‌باشد که در مثالی، در قسمت بعدی، validator سفارشی خودمان را خواهیم ساخت و به entity مورد نظر اعمال خواهیم کرد.
breeze دارای یک سری Validator در سطح پراپرتی‌ها است:
- برای انواع اقسام dataType ها مانند Int,string,..
- برای نیازهای رایجی چون: emailAddress,creditCard,maxLength,phone,regularExpression,required,url 
هم چنین در breeze امکان تغییر دادن اعتبارسنجی‌های پیش فرض نیز وجود دارند. برای مثال برای اینکه در فیلدهای required بتوان متن خالی هم وارد کرد، از دستور زیر می‌توان استفاده کرد:
breeze.Validator.required({ allowEmptyStrings: true });

ردیابی تغییرات
: هر آیتم Entity دارای EntityAspect است که وضعیت آن‌را مشخص می‌کند و می‌تواند یکی از وضعیت‌های Added،Modified،Deleted،Detached،Unchanged باشد. با مشخص کردن حالت هر آیتم، با فراخوانی SaveChanges تغییرات بر روی دیتابیس اعمال می‌گردد.
ایجاد آیتم جدید:
manager.createEntity('Customer', jsonValue);
 ویرایش اطلاعات:
manager.createEntity("Customer", jsonValue, breeze.EntityState.Modified, breeze.MergeStrategy.OverwriteChanges)
 حذف اطلاعات:
manager.createEntity("Customer", item, breeze.EntityState.Deleted)

برای اشنایی بیشتر با امکانات Breeze، قصد داریم یک سایت ایجاد آگهی را راه اندازی کنیم. پیش نیازهای ضروری این بخش typescript ،angularjs ،requirejs هستند. قصد داریم سایتی را برای آگهی‌های خرید و فروش خودرو، مشابه با سایت باما ایجاد نماییم:

امکانات این سایت:
- ثبت نام کاربران 
- ثبت آگهی توسط کاربران 
- ایجاد برچسب‌های آگهی‌ها 
- امتیاز دهی به آگهی‌ها
- جستجوی آگهی‌ها
- و....
ابتدا نصب پکیج‌های زیر 
Install-Package angularjs
Install-Package angularjs.TypeScript.DefinitelyTyped

Install-Package bootstrap
Install-Package bootstrap.TypeScript.DefinitelyTyped

Install-Package jQuery
Install-Package jquery.TypeScript.DefinitelyTyped

Install-Package RequireJS
Install-Package requirejs.TypeScript.DefinitelyTyped

bower install angularAMD

مدلهای برنامه:
ایجاد کلاس BaseEntity 
 public  class BaseEntity
    {
        public int Id { get; set; }
        public bool Status { get; set; }
        public DateTime CreatedDateTime { get; set; }
    }
ایجاد جدول آگهی
    public class Ad : BaseEntity
    {
        public string Title { get; set; }
        public float Price { get; set; }
        public double Rating { get; set; }
        public int? RatingNumber { get; set; }
        public string UserId { get; set; }
        public DateTime ModifieDateTime { get; set; }
        public string Description { get; set; }
        public virtual ICollection<Comment> Comments { get; set; }
        public virtual IdentityUser User { get; set; }
        public virtual ICollection<AdLabel> Labels { get; set; }
        public virtual ICollection<AdMedia> Medias { get; set; }
    }
ایجاد جدول برچسب 
public class Label 
    {
        public int Id { get; set; }
        public string Title { get; set; }
        public int? ParentId { get; set; }
        public virtual Label Parent { get; set; }
        public virtual ICollection<Label> Items { get; set; }
    }
ایجاد جدول مدیا
 public class Media 
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public string MimeType { get; set; }
    }
ایجاد جدول واسط برچسب‌های آگهی
  public class AdLabel
    {
        public int Id { get; set; }
        public virtual Ad Ad { get; set; }
        public virtual Label Label { get; set; }
        [Index("IX_AdLabel", 1, IsUnique = true)]
        public int AdId { get; set; }
        [Index("IX_AdLabel", 2, IsUnique = true)]
        public int LabelId { get; set; }
        public string Value { get; set; }
    }
ایجاد جدول واسط مدیا‌های مرتبط با آگهی
 public class AdMedia
    {
        public int Id { get; set; }
        public virtual Ad Ad { get; set; }
        public virtual Media Media { get; set; }
        [Index("IX_AdMedia", 1, IsUnique = true)]
        public int AdId { get; set; }
        [Index("IX_AdMedia", 2, IsUnique = true)]
        public int MediaId { get; set; }
    }
ایجاد جدول کامنت‌ها
  public class Comment : BaseEntity
    {
        public string Body { get; set; }
        public double Rating { get; set; }
        public int? RatingNumber { get; set; }
        public string EntityName { get; set; }
        public string UserId { get; set; }
        public int? ParentId { get; set; }
        public int? AdId { get; set; }
        public virtual Comment Parent { get; set; }
        public virtual Ad Ad { get; set; }
        public virtual ICollection<Comment> Items { get; set; }
        public virtual IdentityUser User { get; set; }
    }
ایجاد جدول اعضاء
public class Customer:BaseEntity
    {
        public string UserId { get; set; }
        public virtual string DisplayName { get; set; }
        public virtual string BirthDay { get; set; }
        public string City { get; set; }
        public string Address { get; set; }
        public int? MediaId { get; set; }
        public bool? NewsLetterSubscription { get; set; }
        public string PhoneNumber { get; set; }
        public virtual IdentityUser User { get; set; }
        public virtual Media Media { get; set; }
    }
ایجاد جدول امتیاز دهی به آگهی‌ها
public class Rating 
    {
      public int Id { get; set; }
       public string UserId { get; set; }
       public Double Rate { get; set; }
       public string EntityName { get; set; }
       public int DestinationId { get; set; }
    }

اضافه کردن مدلهای برنامه به ApplicationDbContext 
 public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
    {
        public ApplicationDbContext()
            : base("DefaultConnection", throwIfV1Schema: false)
        {
        }
        public DbSet<Ad> Ads { get; set; }
        public DbSet<AdLabel> AdLabels { get; set; }
        public DbSet<AdMedia> AdMedias { get; set; }
        public DbSet<Comment> Comments { get; set; }
        public DbSet<Label> Labels { get; set; }
        public DbSet<Media> Medias { get; set; }
        public static ApplicationDbContext Create()
        {
            return new ApplicationDbContext();
        }
    }

لود کردن فایل main.js در فایل layout.cshtml ترجیحا در انتهای body
    <script src="~/Scripts/require.js" data-main="/app/main"></script>
RequireJS  کتابخانه‌ی جاوااسکریپتی برای بارگزاری فایل‌ها در صورت نیاز می‌باشد. تنها کاری که ما باید انجام بدهیم این است که کدهای خود را داخل module‌ها قرار دهیم (در فایل‌های جداگانه) و RequireJS در صورت نیاز آنها را load خواهد کرد. همچنین RequireJS وابستگی بین module‌ها را نیز مدیریت می‌کند.

ایجاد فایل main.ts 
path: مسیر فایل‌های جاوا اسکریپتی
shim: وابستگی‌های فایل‌ها(ماژول ها) و export کردن آنها را مشخص می‌کند.
requirejs.config({
    paths: {
        "app": "app",
        "angularAmd":"/Scripts/angularAmd",
        "angular": "/Scripts/angular",
        "bootstrap": "/Scripts/bootstrap",
        "angularRoute": "/Scripts/angular-route",
        "jquery": "/Scripts/jquery-2.2.2",
    },
    waitSeconds: 0,
    shim: {
        "angular": { exports: "angular" },
        "angularRoute": { deps: ["angular"] },
        "bootstrap": { deps: ["jquery"] },
        "app": {
            deps: ["bootstrap","angularRoute"]
        }
    }
});
require(["app"]);

ایجاد فایل app.ts: کارهایی که در فایل app انجام داده‌ایم:
ایجاد کنترلر SecurityCtrl و اعمال آن به تگ body
<body ng-controller="SecurityCtrl">
...
</body>
ایجاد ماژول AdApps و قرار دادن کلاس SecurityCtrl در آن. از این به بعد برای مدیریت بهتر، تمام کدهای خود را درون ماژول‌ها قرار می‌دهیم. 
"use strict";
module AdApps {
    class SecurityCtrl {
        private $scope: Interfaces.IAdvertismentScope;
        constructor($scope: Interfaces.IAdvertismentScope) {
           // security check
      this.$scope = $scope;
        }
    }
 define(["angularAmd", "angular"], (angularAmd, ng) => {
   angularAmd = angularAmd.__proto__;
        var app = ng.module("AngularTypeScript", ['ngRoute']);
        var viewPath = "app/views/";
        var controllerPath = "app/controller/";
        app.config(['$routeProvider', $routeProvider => {
                $routeProvider
                    .when("/", angularAmd.route({
                        templateUrl: viewPath + "home.html",
                        controllerUrl: controllerPath + "home .js"
                    }))
                    .otherwise({ redirectTo: '/' });
            }
        ]);
        app.controller('SecurityCtrl', ['$scope', SecurityCtrl]);
        return angularAmd.bootstrap(app);
 })}
مطالب
خواندنی‌های 5 اردیبهشت

  • - پیش نمایش MySQL 5.4 توسط شرکت سان ارائه شد. این شرکت مدعی است که response times آن 90 درصد نسبت به نگارش قبلی سریعتر شده (+ و +)
  • - سایت GeoCities بسته شد. سایت Google pages هم قرار است تا یکی دو ماه دیگر بسته شود (به عبارت دیگر شکل و شمایل این وبلاگ در آن تاریخ کلا به هم خواهد ریخت چون فایل‌های سایت را در آن‌جا هاست کرده‌ام ... به دریا هم که برویم ...)