نظرات مطالب
EF Code First #12
- نیاز خواهید داشت یک چنین تعریفی را اضافه کنید:
public interface IUnitOfWork
{
   //...
   DbEntityEntry<TEntity> Entry<TEntity>(TEntity entity) where TEntity : class;
}
+ در این حالت این IUnitOfWork آنچنان قابل انتقال و تعویض نخواهد بود چون DbEntityEntry کلاس خاصی است در EF و در سایر ORMها معادلی ندارد. اما اگر فقط با EF کار می‌کنید و قصد تعویض ORM را ندارید، این روش کار می‌کنه و مناسب است.
- از این موارد زیاد است. فرقی هم نمی‌کند (سرعت هم به تنهایی ملاک نیست). با هر کدام که راحت هستید، همان مطلوب است.

نظرات مطالب
انتقال فایل‌های دیتابیس اس کیوال سرور 2008
- البته منظور از انتقال در اینجا بحث move و cut بود نه copy . بنابراین در حالت move ، سرور به نحوی باید از محل جدید مطلع بشه و نیاز به attach هست.
- ضمنا اگر به دنبال امنیت هستید، کپی کردن mdf و ldf ممکن است کار دست شما بده و یک وقت خیلی ساده attach نشه. ولی بک آپ این مشکل رو نداره.
مطالب
آشنایی با Feature Toggle - بخش اول
فرض کنید میخواهید برای بخش‌هایی از نرم افزاری که طراحی کرده‌اید ، امکانی را در نظر بگیرید که بتوانید زمانیکه نرم افزار در حال استفاده‌است، قابلیت‌هایی از آن‌را فعال یا غیرفعال نمایید؛ بدون اینکه نرم افزار از دسترس خارج شود. Feature Toggle که تحت عنوان Feature Flag هم شناخته می‌شود همین امکان را برای ما به ارمغان می‌آورد و ما را قادر می‌سازد تا قابلیت‌هایی را از نرم افزار، فعال یا غیرفعال کنیم، بدون اینکه نیاز باشد نرم افزار از دسترس مشتریان خارج شود و یا نیاز باشد نسخه‌ی جدیدی از نرم افزار  منتشر شود. برای مثال قابلیت ثبت نام کاربران را در بازه‌های خاصی غیرفعال کنیم و یا فرض کنید قابلیت جدیدی به نرم افزار اضافه کرده‌اید و میخواهید بعد از پابلیش، در یک بازه زمانی که نرم افزار شما بازدید کننده‌های کمتری دارد، آن‌را موقتا فعال کنید، نتیجه خروجی را ببینید و سپس آن را غیر فعال نمایید. در ادامه این مقاله سعی خواهیم کرد ابتدا با یک مثال ساده با این قابلیت آشنا شویم و سپس به معرفی یکی از کتابخانه‌های محبوب در این زمینه بپردازیم.
Feature Toggle چیزی بیشتر از یک دستور IF نیست، اگر شرط مورد نظر برقرار بود، کد را اجرا میکند، در غیر اینصورت از اجرای آن بخش صرف نظر میکند.
IF (currentYear<2023){
alert('Wear a mask!');
}
در قطعه کد فوق، سال جاری را چک کرده‌ایم و گفته‌ایم اگر سال جاری کمتر از سال 2023 بود، به بازدید کننده یک پیغام را نمایش دهیم. حال فرض کنید بیماری کرونا، پیش از سال 2023 از بین برود، ولی طبق این شرط همچنان پیغام به کاربران نمایش داده میشود. میتوانیم فعال و غیر فعال بودن نمایش این پیغام را یا از دیتابیس و یا از فایل appsetting.json  بخوانیم که در این حالت  به صورت زیر می‌باشد :
var showCoronaAlert=_cofiguration.GetValue<bool>("Features:showCoronaAlert"); // or read this from Database
If(showCoronaAlert){
alert(Wear a amask!);
}
در این روش بجای اینکه تاریخ را چک کنیم و بر اساس آن تصمیم بگیریم که آیا پیغامی نمایش داده شود یا نه، وضعیت نمایش آن را از فایل تنظیمات و یا دیتابیس خوانده‌ایم. در این حالت دیگر نیازی به تغییر و انتشار نسخه‌ی جدیدی از نرم افزار نیست و فقط کافی‌است مقدار مربوط به نمایش پیغام را در دیتابیس و یا فایل تنظیمات، به روزسانی نماییم.

 کتابخانه  Microsoft.FeatureManagement
کتابخانه  Microsoft.FeatureManagement  توسط تیم اژور پیاده سازی و نوشته شده‌است و برای خواندن اطلاعات، از همان IConfiguration استفاده میکند که ما را قادر می‌سازد تنظیمات را از منابع مختلفی بخوانیم  و همچنین  قابلیت‌های آن فراتر از تنظیم یک مقدار با true/false می‌باشد که در ادامه با بعضی از آنها آشنا خواهیم شد.
ابتدا نیاز هست این کتابخانه را به صورت زیر نصب نماییم :
Install-Package Microsoft.FeatureManagement

سپس نیاز هست در متد ConfigureService، سرویس مربوطه را اضافه نماییم :
using Microsoft.FeatureManagement;
public void ConfigureServices(IServiceCollection services)
{
    services.AddFeatureManagement();
}

این کتابخانه به صورت پیش فرض، اطلاعات feature‌ها را از بخشی (section) تحت عنوان FeatureManagement  از فایل appsetting.json می‌خواند. پس نیاز داریم این بخش را در appsetting.json تعریف نماییم  ( لیست تمامی قابلیت‌هایی را که قصد داریم به صورت داینامیک فعال/غیرفعال کنیم، در این بخش اضافه خواهیم کرد):
"FeatureManagement": {
   
}
اگر تمایل داشتید از اسم دیگری برای بخش تنظیمات، در فایل appsetting. json  استفاده نمایید، می‌توانید به صورت زیر این کار را انجام دهید :
public void ConfigureServices(IServiceCollection services)
{
 services.AddFeatureManagement(Configuration.GetSection("MyFeatureManagement"))
}
در این مقاله از همان اسم پیش فرض استفاده شده است.
افزودن یک قابلیت جدید
"FeatureManagement": {
   "MaskAlert":true
}

همان مثال بالا را  در بخش FeatureManagement  اضافه کرده‌ایم  و مقدار true را به معنی فعال بودن، برای آن در نظر گرفته‌ایم. این حالت، ساده‌ترین روش ثبت یک قابلیت با استفاده از این کتابخانه می‌باشد. برای بررسی وضعیت هر کدام از قابلیت‌ها باید اینترفیس  IFeatureManager   را به کلاس مربوطه تزریق نماییم و سپس بر اساس نام قابلیت، وضعیت آن را بررسی نماییم:
 public class HomeController : Controller
    {
        private readonly IFeatureManager _featureManager;

        public HomeController(IFeatureManager featureManager)
        {
            _featureManager = featureManager;
        }
        public async Task<IActionResult> Index()
        {
            if(await _featureManager.IsEnabledAsync("MaskAlert"))
            {
                // show messeage
            }

            return View();
        }
    }
اگر نیاز هست از اسم دیگری برای بخش (section)

فعال سازی بر اساس تاریخ (TimeWindowsFilter)
یکی از قابلیت‌های این کتابخانه، فعال سازی بر اساس بازه زمانی هست. اگر نیاز دارید یک قابلیت در یک بازه‌ی خاص فعال شود، میتوانید از این قابلیت استفاده کنید. برای فعال سازی این امکان، باید فیلتر TimeWindowFilter را که به صورت توکار به همراه کتابخانه وجود دارد، به صورت زیر در متد configureServices ثبت نماییم:
public void ConfigureServices(IServiceCollection services)
{ 
    services.AddFeatureManagement().AddFeatureFilter<TimeWindowFilter>();
}

و سپس یک Feature را در بخش FeatureManagement همانند زیر تعریف میکنیم که توسط آن مشخص کرده‌ایم این قابلیت در بازه‌ی زمانی بین دو تاریخ تعریف شده، فعال باشد :
 "FeatureManagement": {
    "EmergencyBanner": {
      "EnabledFor": [
        {
          "Name": "Microsoft.TimeWindow",
          "Parameters": {
            "Start": "01 Mar 2021 12:00:00 +00:00",
            "End": "01 Apr 2021 12:00:00 +00:00"
          }
        }
      ]
    }
  }
و نحوه‌ی بررسی فعال بودن آن، همانند روش قبل می‌باشد و فقط کافیست اسم Feature را به متد IsEnabledAsync بدهیم:
if(await _featureManager.IsEnabledAsync("EmergencyBanner")){
// show Emergency banner 
}

 پارامتر‌های Start و End میتوانند به صورت تکی هم استفاده شوند؛ به این معنا که میتوانید فقط پارامتر start را مقدار دهی کنید و در این حالت از تاریخ مورد نظر به بعد، Feature مورد نظر فعال می‌باشد و یا اگر فقط پارامتر End مقدار دهی شود، Feature مورد نظر فقط تا تاریخ تعیین شده فعال هست و بعد از آن برای همیشه غیرفعال می‌شود.
در زیر، نمونه‌ای از این حالت تنظیم شده‌است :
"FeatureManagement": {
    "EmergencyBanner": {
      "EnabledFor": [
        {
          "Name": "Microsoft.TimeWindow",
          "Parameters": {
            "End": "01 Apr 2021 12:00:00 +00:00"
          }
        }
      ]
    }
  }

فیلتر‌های سفارشی
از دیگر مزایای این کتابخانه این هست که محدود به فیلترهای توکار خود آن نیستیم و امکان توسعه و نوشتن فیلتر‌های سفارشی را به ما میدهد. برای مثال اگر یک قابلیت را در نرم افزار پیاده سازی کرده‌ایم که میخواهیم فقط بر روی مرورگر‌های خاصی در دسترس باشد، میتوانیم به صورت زیر این کار را انجام دهیم:
ابتدا در appsetting.json قابلیت (Feature) مورد نظر را به صورت زیر تعریف می‌کنیم :
"FeatureManagement": {
    "ChatV2": {
      "EnabledFor": [
        {
          "Name": "BrowserFilter",
          "Parameters": {
            "AllowedBrowsers": [ "Chrome" ]
          }
        }
      ]
    }
  }
سپس فیلتر سفارشی را به صورت زیر پیاده سازی میکنیم :
[FilterAlias("BrowserFilter")]
public class BrowserFilter:IFeatureFilter
    {
        private readonly IHttpContextAccessor _httpContextAccessor;

        public BrowserFilter(IHttpContextAccessor httpContextAccessor)
        {
            _httpContextAccessor = httpContextAccessor ?? throw new ArgumentNullException(nameof(httpContextAccessor));
        }

        public Task<bool> EvaluateAsync(FeatureFilterEvaluationContext context)
        {
            var userAgent = _httpContextAccessor.HttpContext.Request.Headers["User-Agent"].ToString();
            var settings = context.Parameters.Get<BrowserFilterSettings>();
            return Task.FromResult(settings.AllowedBrowsers.Any(userAgent.Contains));
        }
    }

کلاس BrowserFilter :
  public class BrowserFilterSettings
    {
        public string[] AllowedBrowsers { get; set; }
    }
بعد از پیاده سازی فیلتر فوق نیاز هست فیلتر سفارشی را که در بالا نوشتیم، در متد ConfigureServices ثبت نماییم. با توجه به اینکه برای تشخیص نوع مروگر کاربر نیاز هست  هدر درخواست را بررسی کنیم، پس نیاز هست IHttpContextAccessor را هم ثبت نماییم:
public void ConfigureServices(IServiceCollection services)
        {
            services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
            services.AddFeatureManagement()
                .AddFeatureFilter<BrowserFilter>();
        }
و برای بررسی فعال بودن قابلیت مورد نظر فقط کافیست مانند قبل، اسم قابلیت مورد نظر را به صورت زیر بررسی کنیم :
if(await _featureManager.IsEnabledAsync("ChatV2")){
// do something 
}

* از دیگر قابلیت‌های این کتابخانه، فعال و غیر فعال کردن کنترلر و اکشن متدها بر اساس وضعیت Feature‌ها می‌باشد که در بخش دوم این مقاله به توضیح این موارد خواهیم پرداخت.
مطالب
بررسی وجود اتصال اینترنتی در اندروید
یکی از اصلی‌ترین کارهایی که در اپلیکیشن‌هایی که قصد اتصال به اینترنت را دارند انجام می‌دهیم این است که قبل از هر کاری وضعیت اتصال اینترنتی را مشخص کنیم تا در هنگام اجرای فرآیندها به مشکل یا خطایی برخورد نکنیم تا برنامه منجر به خطای Force Close شود. با یک جست و جوی ساده در گوگل به تکه کد زیر می‌رسیم:
 public boolean isNetworkAvailable(Context context) {
        ConnectivityManager connectivityManager
                = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
        NetworkInfo activeNetworkInfo = connectivityManager.getActiveNetworkInfo();
        return activeNetworkInfo != null && activeNetworkInfo.isConnected();
    }
برای اجرای تکه کد بالا شما نیاز به مجوز زیر دارید:
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
تا به اینجای کار بسیار راحت بود. ولی اگر با دقت مجوز بالا را بخوانید نوشته است که شما به وضعیت شبکه دسترسی دارید. یعنی اینکه ممکن است گوشی شما از طریق هر آداپتور یا قطعه‌ای به یک شبکه متصل باشد. ولی الزامی نیست که شبکه مورد نظر اینترنت باشد. این تکه کد فقط وضعیت شبکه‌های فعالی را که عموما از طریق wifi و mobile data متصل می‌باشد را برای شما باز می‌گرداند.

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

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

در همه مثال‌های بالا اینترنتی وجود ندارد، ولی تکه کد بالا true را برخواهد گرداند.
برای اینکار می‌توان از دو راهکار استفاده کرد. یکی از روش‌ها پینگ زدن به سرورهای اینترنتی است که اگر وضعیت پینگ پاسخ داده شود، شما مطمئن می‌شوید که به شبکه جهانی متصلید. تکه کد زیر می‌تواند اینکار را انجام دهد.
 public boolean IsInternetConnected()
    {
        try {
            Process ipProcess = Runtime.getRuntime().exec("/system/bin/ping -c 1 4.2.2.4");
            int     exitValue = ipProcess.waitFor();
            return (exitValue == 0);
        } catch (IOException e)          { e.printStackTrace(); }
        catch (InterruptedException e) { e.printStackTrace(); }
        return false;
    }
در خط اول از متد exec استفاده کردیم که معادل آن در دات نت استفاده از
System.Diagnostics.Process.Start("processName");
می‌باشد. عبارتی که به عنوان نام پروسس نوشتیم در واقع نحوه اجرای یک ICMP ساده در سیستم‌های مبتنی بر یونیکس است و از آنجا که اندروید از لینوکس وام گرفته شده است پس می‌توان پروسه بالا را صدا زد.
فرمان یا دستور بالا به شرح زیر است:
در سیستم عامل لینکوس تمام برنامه‌های سیستمی مورد نیاز، در شاخه bin قرار می‌گیرند و پینگ، یکی از آن هاست. سوییچ c هم که مخفف count است، به معنی تعداد درخواست‌های یک اکو است که در این دستور گفته‌ایم تنها یک درخواست اکو echo ارسال کن. (ااطلاعات بیشتر در مورد دستور پینگ ).
در خط بعدی از آنجا که این دستور، یک دستور زمان بر است، باید مدتی در این کد توقف شود تا مقدار مورد نظر دریافت شود. در صورتی که مقدار 0 بازگردانده شود، اکو پاسخ داده شده است و یعنی اینکه شما به اینترنت متصلید. (مشاهده کدهای وضعیتی ICMP )
 وجود catch‌های بالا الزامی است از آنجا که متد‌های استفاده شده توسط استثناءهای زیر throw شده‌اند، جاوا شما را ملزم به استفاده از catch‌های این استثناها خواهد کرد.
 public Process exec(String prog) throws java.io.IOException {
        return exec(prog, null, null);
    }

  public abstract int waitFor() throws InterruptedException;

برای اجرا این تکه کد شما نیاز به مجوز اتصال به اینترنت دارید:
 <uses-permission android:name="android.permission.INTERNET"/>

نکته مهم اینکه نگران اجرای این دستور در گوشی کاربر نباشید. این دستور نیاز به مجوز روت ندارد.

با اجرای این تکه کد تمام مسائل بالا حل می‌شود، به جز سرعت کند آن جهت پینگ زدن و دیگری مورد آخر که سیستم شما توسط یک روتر با وضعیت گفته شده کنترل شود. در این سیستم‌ها حتی اگر به اینترنت هم متصل باشید، پینگ شما پاسخی داده نمی‌شود. به همین علت یک روش ساده‌تر نیز وجود دارد و آن درخواست یک صفحه اینترنتی مطمئن و با دوام چون گوگل است که در بسیاری از سایت‌ها این روش نیز پیشنهاد شده است.
تکه کد زیر صفحه گوگل را درخواست می‌کند و در نهایت وضعیت کد http آن را دریافت می‌کنیم و اگر این کد وضعیت برابر 200 بود به این معنی است که اینترنت متصل می‌باشد. ولی در یک سیستم میکروتیک که هنوز وارد سیستم آن نشده باشید، به صفحه لاگین هدایت می‌شوید و وضعیت دیگری را دریافت خواهید مانند آدرس درخواستی شما redirect شده است یا اینکه باز هم کد 200 را دریافت می‌کنید که در بیشتر حالات هم به همین شکل است. برای رفع این مسئله بهتر است url فعلی را با url درخواستی مطابقت دهیم. برای این قضیه گوگل در بخش Handling Network Sign-On این صفحه چنین کدی را پیشنهاد داده است:
 HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
   try {
     InputStream in = new BufferedInputStream(urlConnection.getInputStream());
     if (!url.getHost().equals(urlConnection.getURL().getHost())) {
       // we were redirected! Kick the user out to the browser to sign on?
     
     ...
   } finally {
     urlConnection.disconnect();
   }
 }

ولی با توجه به تحقیقات و مشاهداتی که کرده‌ام، در بعضی از گوشی‌ها این کد کارکرد مناسبی ندارد و برای خودم هم پاسخی دریافت نکردم. اگر واقعا باز هم مصر هستید که این وضعیت را بررسی کنید، می‌تواند بررسی یک url با محتوای خاص باشد و بعد از دریافت این صفحه محتوای آن را بررسی کنید.
ولی با این همه بسیاری از برنامه‌ها از همان تکه کد بالا استفاده می‌کنند و با مدیریت استثناءها سعی در جلوگیری از خطا دارند. در غیر این صورت شما باید مدام در حال بررسی وضعیت اینترنت به شکل بالا باشید که بسیار زمان بر خواهد شد.
در صورتی که شما روش بهتری را برای بررسی وضعیت اینترنت دارید یا راه حل خاصی به نظرتان می‌رسد بسیار عالی خواهد بود که آن را با ما به اشتراک بگذارید.
نظرات مطالب
تهیه‌ی کارت با فرمت PDF با استفاده از کتابخانه iTextSharp
سلام.. بنده این کد رو نوشتم ولی اصلا اون سلول ایمیج رو در نظر نمیگیره.هرکاری میکنم اصلا نشون نمیده.. دلیلش چیه؟
var infoTable = new PdfPTable(6)
            {
                WidthPercentage = 100,
                RunDirection = PdfWriter.RUN_DIRECTION_RTL,
                ExtendLastRow = false,
                SpacingBefore = 15,
                
            };

            infoTable.DefaultCell.Border = 2;

            infoTable.AddCell(new PdfPCell(new Phrase("اطلاعات شناسنامه ای", tfont))
            {
                RunDirection = PdfWriter.RUN_DIRECTION_RTL,
                HorizontalAlignment = Element.ALIGN_LEFT,
                Border = 2,
                PaddingBottom = 15,
                Colspan=2
            });
            infoTable.AddCell(new PdfPCell(new Phrase("اطلاعات خانوادگی", tfont))
            {
                RunDirection = PdfWriter.RUN_DIRECTION_RTL,
                HorizontalAlignment = Element.ALIGN_LEFT,
                Border = 2,
                PaddingBottom = 15,
                Colspan = 2
            });
            infoTable.AddCell(new PdfPCell(new Phrase("اطلاعات دانشگاهی", tfont))
            {
                RunDirection = PdfWriter.RUN_DIRECTION_RTL,
                HorizontalAlignment = Element.ALIGN_LEFT,
                Border = 2,
                PaddingBottom = 15,
                Colspan = 2
            });

           
            var universityLogoImage = Image.GetInstance(imgPath);
            universityLogoImage.ScaleAbsolute(30, 30);  
//این عکس رو اصلا نشون نمیده
            topTable.AddCell(new PdfPCell(universityLogoImage)
            {
                HorizontalAlignment = Element.ALIGN_CENTER,
                Border = 2,
            });

            infoTable.AddCell(new PdfPCell(new Phrase("وضعیت تاهل:", docFont))
            {
                RunDirection = PdfWriter.RUN_DIRECTION_RTL,
                HorizontalAlignment = Element.ALIGN_LEFT,
                Border = 2,
                PaddingBottom = 10,
            });

            infoTable.AddCell(new PdfPCell(new Phrase(dt.Rows[0][22].ToString() == "0" ? "مجرد" : "متاهل", docFont))
            {
                RunDirection = PdfWriter.RUN_DIRECTION_RTL,
                HorizontalAlignment = Element.ALIGN_LEFT,
                Border = 2,
                PaddingBottom = 10,
            });

            infoTable.AddCell(new PdfPCell(new Phrase("کد تحصیلی:", docFont))
            {
                RunDirection = PdfWriter.RUN_DIRECTION_RTL,
                HorizontalAlignment = Element.ALIGN_LEFT,
                Border = 2,
                PaddingBottom = 10,
            });

            infoTable.AddCell(new PdfPCell(new Phrase(dt.Rows[0][0].ToString(), docFont))
            {
                RunDirection = PdfWriter.RUN_DIRECTION_RTL,
                HorizontalAlignment = Element.ALIGN_LEFT,
                Border = 2,
                PaddingBottom = 10,
            });


            infoTable.AddCell(new PdfPCell(new Phrase("تحصیلات همسر:", docFont))
            {
                RunDirection = PdfWriter.RUN_DIRECTION_RTL,
                HorizontalAlignment = Element.ALIGN_LEFT,
                Border = 2,
                PaddingBottom = 10,
            });

            infoTable.AddCell(new PdfPCell(new Phrase(dt.Rows[0]["wife_edu"].ToString(), docFont))
            {
                RunDirection = PdfWriter.RUN_DIRECTION_RTL,
                HorizontalAlignment = Element.ALIGN_LEFT,
                Border = 2,
                PaddingBottom = 10,
            });

            infoTable.AddCell(new PdfPCell(new Phrase("شماره پرونده:", docFont))
            {
                RunDirection = PdfWriter.RUN_DIRECTION_RTL,
                HorizontalAlignment = Element.ALIGN_LEFT,
                Border = 2,
                PaddingBottom = 10,
            });

            infoTable.AddCell(new PdfPCell(new Phrase(dt.Rows[0]["t_parvande_num"].ToString(), docFont))
            {
                RunDirection = PdfWriter.RUN_DIRECTION_RTL,
                HorizontalAlignment = Element.ALIGN_LEFT,
                Border = 2,
                PaddingBottom = 10,
            });

            infoTable.AddCell(new PdfPCell(new Phrase("تحصیلات پدر:", docFont))
            {
                RunDirection = PdfWriter.RUN_DIRECTION_RTL,
                HorizontalAlignment = Element.ALIGN_LEFT,
                Border = 2,
                PaddingBottom = 10,
            });

            infoTable.AddCell(new PdfPCell(new Phrase(dt.Rows[0]["father_edu"].ToString(), docFont))
            {
                RunDirection = PdfWriter.RUN_DIRECTION_RTL,
                HorizontalAlignment = Element.ALIGN_LEFT,
                Border = 2,
                PaddingBottom = 10,
            });

            infoTable.AddCell(new PdfPCell(new Phrase("کد مرکز خدمات:", docFont))
            {
                RunDirection = PdfWriter.RUN_DIRECTION_RTL,
                HorizontalAlignment = Element.ALIGN_LEFT,
                Border = 2,
                PaddingBottom = 10,
            });

            infoTable.AddCell(new PdfPCell(new Phrase(dt.Rows[0]["bime_code"].ToString(), docFont))
            {
                RunDirection = PdfWriter.RUN_DIRECTION_RTL,
                HorizontalAlignment = Element.ALIGN_LEFT,
                Border = 2,
                PaddingBottom = 10,
            });

            infoTable.AddCell(new PdfPCell(new Phrase("شغل پدر:", docFont))
            {
                RunDirection = PdfWriter.RUN_DIRECTION_RTL,
                HorizontalAlignment = Element.ALIGN_LEFT,
                Border = 2,
                PaddingBottom = 10,
            });

            infoTable.AddCell(new PdfPCell(new Phrase(dt.Rows[0]["father_job"].ToString(), docFont))
            {
                RunDirection = PdfWriter.RUN_DIRECTION_RTL,
                HorizontalAlignment = Element.ALIGN_LEFT,
                Border = 2,
                PaddingBottom = 10,
            });
            infoTable.AddCell(new PdfPCell(new Phrase("پایه قبولی:", docFont))
            {
                RunDirection = PdfWriter.RUN_DIRECTION_RTL,
                HorizontalAlignment = Element.ALIGN_LEFT,
                Border = 2,
                PaddingBottom = 10,
            });

            infoTable.AddCell(new PdfPCell(new Phrase(dt.Rows[0]["enter_paye"].ToString(), docFont))
            {
                RunDirection = PdfWriter.RUN_DIRECTION_RTL,
                HorizontalAlignment = Element.ALIGN_LEFT,
                Border = 2,
                PaddingBottom = 10,
            });
            infoTable.AddCell(new PdfPCell(new Phrase("", docFont))
            {
                RunDirection = PdfWriter.RUN_DIRECTION_RTL,
                HorizontalAlignment = Element.ALIGN_LEFT,
                Border = 0
            });

            infoTable.AddCell(new PdfPCell(new Phrase("", docFont))
            {
                RunDirection = PdfWriter.RUN_DIRECTION_RTL,
                HorizontalAlignment = Element.ALIGN_LEFT,
                Border = 0
            });
int[] infoTableColumnsWidth = { 20, 15, 20, 15,20,15 };

            infoTable.SetWidths(infoTableColumnsWidth);

            doc.Add(infoTable);
لازم به ذکره که من قبلش یه جدول سه ستونه درست کردم و داخلش یه لوگو گذاشتم که اون به خوبی چاپ میشه ولی این اصلا!
مطالب
نمایش خودکار مقدار یکDropDownList با کمک jQuery

نیاز بود هنگام انتخاب یک آیتم دراپ داون لیست در کل برنامه و تمامی دراپ داون‌های آن، مقدار آن‌ها نیز به صورت یک برچسب در کنار آن نمایش داده شود.
برای مثال در لیست زیر:

<asp:DropDownList ID="ddlActive" runat="server">
<asp:ListItem Value="Active">فعال</asp:ListItem>
<asp:ListItem Value="Inactive">غیرفعال</asp:ListItem>
</asp:DropDownList>
اگر آیتم فعال انتخاب شد، مقدار active نیز کنار آن نمایش داده شود و الی آخر.

راه حل اول:
در تمام صفحات به ازای تک تک دراپ داون‌ها یک label اضافه کنیم و همچنین کدهای تمام قسمت‌های برنامه را نیز اصلاح کنیم تا این مورد را لحاظ کند.

راه دوم:
یک کنترل دراپ داون سفارشی را با خاصیت مورد نظر (همراه بودن با یک لیبل) ایجاد کرده و سپس تمام فرم‌ها را باید اصلاح کرد تا از این کنترل جدید استفاده کنند.

راه سوم:
استفاده از jQuery برای اعمال این مهم به کل برنامه بدون نیاز به تغییرات اساسی در آن (و همچنین سازگاری با تمام مرورگرها):

//فقط در این محدوده
$("#mainFormReq select").change(function() {
var currentId = $(this).attr("id"); //آی دی شیء جاری
var val = $(this).val(); //مقدار
var text = $('#' + currentId + ' option:selected').text(); //متن
$("#lbl" + currentId).remove(); //اگر نمونه‌ی قبلی موجود است حذف شود
if (val && (val.length > 0) && (text != val)) {
//اگر متن و مقدار یکی نیست نمایش داده شود
$(this).after('<label id="lbl' + currentId + '">' + val + '</label>');
}
});
توضیحات:
در یک محدوده مشخص شده با ID مساوی mainFormReq (مثلا استفاده از master page ها و نسبت دادن این ID به content آن)، به دنبال تمام select های موجود در آن ناحیه می‌گردیم (اگر mainFormReq حذف شود، این جستجو در کل صفحه صورت خواهد گرفت) و تغییرات آن‌ها را تحت نظر قرار خواهیم داد.
سپس آی دی این کنترل انتخابی را دریافت می‌کنیم (از این ID برای تولید ID برچسب مورد نظر استفاده خواهیم کرد).
در ادامه مقدارهای text و value گزینه انتخابی دریافت می‌شوند (+).
سپس بررسی خواهیم کرد که آیا برچسبی با ID مشخص شده ما وجود دارد (در صورت انتخاب آیتم‌های دیگر، نباید برچسبی غیر منحصربفرد و تکراری در صفحه ایجاد کرد)
در ادامه اگر این مقدار null نبود و همچنین مقدار text و value هم یکی نبودند (اگر یکی بودند لزوم وجود این برچسب بی معنا است)، با استفاده از متد after کتابخانه jQuery یک برچسب را تولید و مقدار مورد نظر را پس از محل نمایش دراپ داون خود، نمایش خواهیم داد.

بهبود کد:
صورت مساله: اکنون نیاز است بجز ناحیه mainFormReq، به سه ناحیه دیگر نیز این تغییرات اعمال گردد. آیا باید همین مقدار کد را سه بار دیگر copy/paste کرد؟
روش صحیح انجام اینکار در jQuery ، نوشتن یک افزونه بر اساس کدهای فوق است که روش انجام آن به صورت زیر می‌باشد (+):

//<![CDATA[
(function($) {
$.fn.dropdownlabel = function() {
return this.change(function() {
var obj = $(this);
var currentId = obj.attr("id"); //آی دی شیء جاری
var val = obj.val(); //مقدار
var text = $('#' + currentId + ' option:selected').text(); //متن
$("#lbl" + currentId).remove(); //اگر نمونه‌ی قبلی موجود است حذف شود
if (val && (val.length > 0) && (text != val)) {
//اگر متن و مقدار یکی نیست نمایش داده شود
obj.after('<label id="lbl' + currentId + '">' + val + '</label>');
}
});
};
})(jQuery);
//]]>
و در نهایت نحوه استفاده از آن (فایلی به نام jquery.dropdownlabel.js ) به صورت زیر خواهد بود:

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="TestDropdownlabel.aspx.cs"
Inherits="testWebForms87.TestDropdownlabel" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title></title>

<script src="jquery.min.js" type="text/javascript"></script>
<script src="jquery.dropdownlabel.js" type="text/javascript"></script>

<script type="text/javascript">
$(document).ready(function() {
$("#mainFormReq select").dropdownlabel();
});
</script>

</head>
<body>
<form id="form1" runat="server">
<div id="mainFormReq">
<asp:DropDownList ID="ddlActive" runat="server">
<asp:ListItem Value=""></asp:ListItem>
<asp:ListItem Value="Active">فعال</asp:ListItem>
<asp:ListItem Value="Inactive">غیرفعال</asp:ListItem>
</asp:DropDownList>
</div>
</form>
</body>
</html>

مطالب
ویرایش قالب پیش فرض Add View در ASP.NET MVC برای سازگار سازی آن با Twitter bootstrap
همانطور که در مطلب «اعمال کلاس‌های ویژه اعتبارسنجی Twitter bootstrap به فرم‌های ASP.NET MVC» ملاحظه کردید، برای سازگار سازی یک فرم جدید ایجاد شده ASP.NET MVC با پیش فرض‌های Twitter bootstrap، حداقل 8 مرحله باید طی شود و ... چقدر خوب می‌شد اگر این‌کارها به صورت خودکار توسط VS.NET بجای قالب پیش فرض ایجاد فرم آن، تولید می‌شد. در ادامه قصد داریم این سفارشی سازی را انجام دهیم.


مراحل کلی سفارشی سازی قالب‌های Scaffolding پیش فرض ASP.NET MVC

قالب‌های Scaffolding پیش فرض ASP.NET در مسیر Microsoft Visual Studio X\Common7\IDE\ItemTemplates\CSharp\Web\MVC X\CodeTemplates قرار دارند. برای نمونه اگر بخواهیم پیش فرض‌های تولید فرم‌های MVC4 را تغییر دهیم، باید به پوشه MVC 4\CodeTemplates\AddView\CSHTML مراجعه و فایل Create.tt را ویرایش کنیم.
اینکار هرچند عملی است اما آنچنان جالب نیست؛ از این جهت که تاثیری کلی و سراسری خواهد داشت.
برای اعمال محلی این تغییرات فقط به یک پروژه خاص، تنها کافی است همین مسیر CodeTemplates\AddView\CSHTML به همراه تمام فایل‌های tt آن، در پوشه جاری پروژه مدنظر ما کپی شود. به این ترتیب ابتدا به این پوشه محلی مراجعه خواهد شد.
روش دوم کپی کردن این فایل‌ها، استفاده از بسته نیوگت ذیل است:
 PM> Install-Package Mvc4CodeTemplatesCSharp


سفارشی سازی فایل Create.tt پیش فرض ASP.NET MVC جهت سازگار سازی آن با Twitter bootstrap

در اینجا قصد داریم همان 8 مرحله مطلب «اعمال کلاس‌های ویژه اعتبارسنجی Twitter bootstrap به فرم‌های ASP.NET MVC» را به فایل Create.tt که اکنون در پوشه CodeTemplates\AddView\CSHTML\Create.tt ریشه پروژه جاری قرار دارد، اعمال کنیم.
الف) ابتدا نام این فایل را به CreateBootstrapForm.tt تغییر می‌دهیم. از این لحاظ که این نام جدید در drop down مرتبط با scaffold template صفحه Add view ظاهر خواهد شد. به علاوه نیازی نیست تا این فایل tt در همان لحظه اجرا شود، بنابراین به خواص آن در VS.NET مراجعه کرده و مقدار گزینه custom tool آن‌را خالی می‌کنیم (مانند سایر فایل‌های tt اضافه شده).
ب) قسمت ابتدایی فایل CreateBootstrapForm.tt را که همان کپی مطابق اصل فایل Create.tt است، به نحو ذیل تغییر می‌دهیم:
<#
    if (!mvcHost.IsContentPage) {
#>
<script src="~/Scripts/jquery-1.9.1.min.js"></script>
<script src="~/Scripts/jquery.validate.min.js"></script>
<script src="~/Scripts/jquery.validate.unobtrusive.min.js"></script>

<#
    }
}
#>
@using (Html.BeginForm()) {
    @Html.ValidationSummary(true, null, new { @class = "alert alert-error alert-block" })

    <fieldset class="form-horizontal">
        <legend><#= mvcHost.ViewDataType.Name #></legend>

<#
foreach (ModelProperty property in GetModelProperties(mvcHost.ViewDataType)) {
    if (!property.IsPrimaryKey && !property.IsReadOnly && property.Scaffold) {
#>
        <div class="control-group">
<#
        if (property.IsForeignKey) {
#>
            @Html.LabelFor(model => model.<#= property.Name #>, "<#= property.AssociationName #>",new {@class="control-label"})
<#
        } else {
#>
            @Html.LabelFor(model => model.<#= property.Name #>,new {@class="control-label"})
<#
        }
#>
        
           <div class="controls">
<#
        if (property.IsForeignKey) {
#>
            @Html.DropDownList("<#= property.Name #>", String.Empty)
<#
        } else {
#>
            @Html.EditorFor(model => model.<#= property.Name #>)
<#
        }
#>
            @Html.ValidationMessageFor(model => model.<#= property.Name #>,null,new{@class="help-inline"})
</div>
        </div>

<#
    }
}
#>
<div class="form-actions">
            <button type="submit" class="btn btn-primary">ارسال</button>
            <button class="btn">لغو</button>
          </div>
    </fieldset>
}

<div>
    @Html.ActionLink("Back to List", "Index")
</div>
<#
if(mvcHost.IsContentPage && mvcHost.ReferenceScriptLibraries) {
#>

@section JavaScript {    

}
که حاصل آن به صورت ذیل قابل استفاده و دسترسی خواهد بود:


دریافت فایل CreateBootstrapForm.tt اصلاح شده:
همانطور که عنوان شد، برای استفاده از آن فقط کافی است آن‌را در مسیر CodeTemplates\AddView\CSHTML\CreateBootstrapForm.tt ریشه پروژه جاری خود کپی کنید.
مطالب
توسعه برنامه های 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] معنی نمی‌دهد.

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

مطالب
استفاده از کتابخانه‌ی moment-jalaali در برنامه‌های Angular
چندی قبل مطلب «نمایش تاریخ شمسی توسط JavaScript در AngularJS» را در این سایت مطالعه کردید. در اینجا قصد داریم معادل Angular آن‌را تهیه کنیم (واژه‌ی AngularJS به نگارش‌های 1x اشاره می‌کند و Angular به تمام نگارش‌های پس از 2).


نصب پیشنیازهای کار با moment-jalaali

ابتدا نیاز است بسته‌ی npm این کتابخانه را نصب کنیم که به همراه فایل‌های js مرتبط با آن می‌باشد:
 npm install moment-jalaali --save

سپس جهت بهبود تجربه‌ی کاربری با آن در IDEهای امروزی، خصوصا VSCode، بهتر است typings آن‌را نیز نصب کنیم؛ تا علاوه بر داشتن Intellisense، بتوان به صورت strongly typed با آن کار کرد:
 npm install @types/moment-jalaali --save-dev


VSCode به صورت خودکار پوشه‌ی مخصوص node_modules\@types را تحت نظر قرار می‌دهد و نصب بسته‌های typings در آن، سبب بارگذاری آنی آن‌ها خواهد شد.
به علاوه اگر به فایل tsconfig.json واقع در ریشه‌ی پروژه نیز دقت کنید، وجود تعریف ذیل، امکان خوانده شدن این تعاریف را توسط کامپایلر TypeScript میسر می‌کند:
{
    "typeRoots": [
      "node_modules/@types"
    ]
}

 
نحوه‌ی کار Strongly Typed با کتابخانه‌ی moment-jalaali در برنامه‌های مبتنی بر TypeScript

پس از نصب پیشنیازهای یاد شده، ابتدا برای دسترسی به امکانات این کتابخانه، ماژول آن‌را import می‌کنیم:
import * as momentJalaali from "moment-jalaali";

export class MomentJalaaliTestComponent implements OnInit {
  now: string;
  nowLongDateFormat: string;
  nowExtraLongDateFormat: string;

  ngOnInit() {
    this.persianDateTests();
  }

  persianDateTests() {
    // https://github.com/jalaali/moment-jalaali
    momentJalaali.loadPersian(/*{ usePersianDigits: true }*/); // نمایش فارسی نام ماه‌ها، روزها و امثال آن

    this.now = momentJalaali().format("jYYYY/jMM/jDD HH:mm");
    this.nowLongDateFormat = momentJalaali().format("jD jMMMM jYYYY [ساعت] LT");
    this.nowExtraLongDateFormat = momentJalaali().format(
      "dddd، jD jMMMM jYYYY [ساعت] LT"
    );
  }
}
- پس از import ماژولی به نام moment-jalaali، اکنون نحوه‌ی استفاده‌ی از آن‌را در متد persianDateTests مشاهده می‌کنید.
- متد momentJalaali.loadPersian باید تنها یکبار فراخوانی شود. کار آن تبدیل نام‌های روزها و ماه‌های میلادی، به شمسی است.
- پس از آن از طریق متد format آن، می‌توان انواع و اقسام حالات مختلف را بررسی کرد که در اینجا سه نمونه را مشاهده می‌کنید.



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

پس آشنا شدن با نحوه‌ی استفاده‌ی از این کتابخانه در یک برنامه‌ی تایپ‌اسکریپتی، تبدیل کردن آن به یک Pipe سفارشی بسیار ساده‌است. برای این منظور ابتدا یک Pipe جدید را به ماژول فرضی custom-pipe.module اضافه می‌کنیم:
 ng g p CustomPipe/moment-jalaali -m custom-pipe.module
با این محتوا:
import { Pipe, PipeTransform } from "@angular/core";

import * as momentJalaali from "moment-jalaali";

@Pipe({
  name: "momentJalaali"
})
export class MomentJalaaliPipe implements PipeTransform {
  transform(value: any, args?: any): any {
    return momentJalaali(value).format(args);
  }
}
در اینجا نیز ابتدا ماژول moment-jalaali تعریف شده‌است و سپس توسط آن، value به عنوان پارامتر متد momentJalaali و args به عنوان پارامتر متد format ارسال شده‌اند. در حین استفاده‌ی از Pipe، مقدار value همان تاریخ دریافتی است و args به فرمت خاصی که توسط استفاده کننده مشخص می‌شود، تنظیم خواهد شد.
به این ترتیب می‌توان یک چنین تبدیلات سمت کاربری را انجام داد که نمونه‌ای از خروجی آن‌را در تصویر فوق نیز ملاحظه می‌کنید:
<h2>Server side dates:</h2>
<div *ngFor="let date of dates">
  <span dir="ltr">{{date | momentJalaali:'jYYYY/jMM/jDD hh:mm' }}</span>,
  <span dir="rtl">{{date | momentJalaali:'jD jMMMM jYYYY [ساعت] LT'}}</span>
</div>


کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید.
مطالب
ارتقاء به ASP.NET Core 1.0 - قسمت 12 - معرفی Tag Helpers
یکی دیگر از تغییرات مهم Razor در ASP.NET Core، معرفی Tag Helpers است که همانند HTML Helpers نگارش‌های پیشین ASP.NET MVC، کار رندر کردن HTML را انجام می‌دهند و در اغلب موارد می‌توان آن‌ها را جایگزین HTML Helpers کرد. مزیت استفاده‌ی از Tag helpers، شبیه بودن آن‌ها به المان‌ها و ویژگی‌های HTML است. در کل اینکه باید از HTML Helpers استفاده کرد و یا از Tag Helpers، بیشتر یک انتخاب شخصی و سلیقه‌ای است.


فعال سازی استفاده‌ی از Tag Helpers برای تمام Viewهای برنامه

برای اینکه تمام Viewهای سایت بتوانند به امکانات Tag Helpers دسترسی پیدا کنند، باید یک سطر ذیل را به فایل ViewImports.cshtml_ اضافه کرد:
 @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
در اینجا * به معنای استفاده‌ی از تمام Tag Helpers موجود در اسمبلی ذکر شده‌است.

Microsoft.AspNetCore.Mvc.TagHelpers به همراه افزودن وابستگی Microsoft.AspNetCore.Mvc در حین فعال سازی ASP.NET MVC، به پروژه اضافه می‌شود:



فعال سازی Intellisense مربوط به Tag Helpers در ویژوال استودیو

هرچند فعال سازی ASP.NET MVC، تنها وابستگی است که برای کار با Tag Helpers نیاز است، اما برای فعال سازی Intellisense آن‌ها باید بسته‌ی Microsoft.AspNetCore.Razor.Tools را نیز به فایل prject.json برنامه، جهت نصب معرفی کرد:
{
    "dependencies": {
         //same as before
         "Microsoft.AspNetCore.Mvc.TagHelpers": "1.0.0",
         "Microsoft.AspNetCore.Razor.Runtime": "1.0.0",
         "Microsoft.AspNetCore.Razor.Tools": {
            "version": "1.0.0-preview2-final",
            "type": "build"
        }
    },
 
    "tools": {
         //same as before
        "Microsoft.AspNetCore.Razor.Tools": "1.0.0-preview2-final"
    } 
}
ضمنا اگر از ReSharper استفاده می‌کنید (تا نگارش resharper-2016.1)، فعلا مجبور هستید که آن‌را غیرفعال کنید. اطلاعات بیشتر


یک مثال: ایجاد لینکی به یک اکشن متد
 <a asp-controller="Home" asp-action="Index" asp-route-id="123">Home</a>
در اینجا نحوه‌ی ایجاد لینکی را مشاهده می‌کنید که به کنترلر Home و اکشن متد Index آن اشاره می‌کند. این syntax جدید، جایگزین ActionLink مربوط به HTML Helperها است. در اینجا asp-route-id را نیز مشاهده می‌کنید. قسمت asp-route آن جهت مقدار دهی پارامترهای مسیریابی است و قسمت id- بنابر نام پارامتری که قرار است مقدار دهی شود، متغیر خواهد بود.
اگر نیاز به اشاره‌ی به مسیریابی خاصی از طریق نام آن وجود دارد (همان نام‌هایی که در حین تعریف یک مسیریابی ذکر می‌شوند) می‌توان به صورت ذیل عمل کرد:
 <a asp-route="login">Login</a>
و یا برای مشخص سازی پروتکل خاصی و یا ذکر دقیق نام هاست، می‌توان از روش زیر استفاده کرد:
 <a asp-controller="Account"
   asp-action="Register"
   asp-protocol="https"
   asp-host="asepecificdomain.com"
   asp-fragment="fragment">Register</a>


راهنمای تبدیل HTML Helpers به Tag Helpers

در جدول ذیل، مثال‌هایی را از HTML Helpers متداول و معادل‌های Tag Helper آن‌ها مشاهده می‌کنید:

Tag Helper
HTML Helper
<label asp-for="Email" class="col-md-2 control-label"></label>
@Html.LabelFor(m => m.Email, new { @class = "col-md-2 control-label" })
<a asp-controller="MyController" asp-action="MyAction" 
class="my-css-classname" my-attr="my-attribute">Click me</a>
@Html.ActionLink("Click me", "MyController", "MyAction", 
{ @class="my-css-classname", data_my_attr="my-attribute"})
<input asp-for="FirstName" style="width:100px;"/>
@Html.TextBox("FirstName", Model.FirstName, new { style = "width: 100px;" })
<input asp-for="Email" class="form-control" />
@Html.TextBoxFor(m => m.Email, new { @class = "form-control" })
<input asp-for="Password" class="form-control" />
@Html.PasswordFor(m => m.Password, new { @class = "form-control" })
<input asp-for="UserName" class="form-control" />
@Html.EditorFor(l => l.UserName,
 new { htmlAttributes = new { @class = "form-control" } })
<form asp-controller="Account" asp-action="Register" 
method="post" class="form-horizontal" role="form">
@using (Html.BeginForm("Register", "Account",
 FormMethod.Post, new { @class = "form-horizontal", role = "form" }))
{
    @Html.AntiForgeryToken()
<span asp-validation-for="UserName" class="text-danger"></span>
@Html.ValidationMessageFor(m => m.UserName, "",
 new { @class = "text-danger" })
<div asp-validation-summary="ValidationSummary.All" class="text-danger"></div>
@Html.ValidationSummary("", new { @class = "text-danger" })


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

نمونه‌ای از مثال Tag helper کار با فرم‌ها را در جدول فوق ملاحظه می‌کنید. چند نکته‌ی تکمیلی ذیل را می‌توان به آن اضافه کرد:
- در حین کار با Tag Helpers، درج anti forgery token به صورت خودکار صورت می‌گیرد. اگر می‌خواهید که این توکن ذکر نشود، آن‌را توسط ویژگی "asp-anti-forgery="false خاموش کنید.
- برای درج پارامترهای مسیریابی خاص، از asp-route به همراه نام پارامتر مدنظر استفاده کنید:
 <form asp-controller="Account"
      asp-action="Login"
      asp-route-returnurl="@ViewBag.ReturnUrl"
      method="post" >
</form>
که در نهایت به یک چنین حالتی رندر می‌شود
 <form action="/Account/Login?returnurl=%2FHome%2FAbout" method="post">
- همانند action linkها در اینجا نیز برای اشاره‌ی به یک مسیریابی از طریق نام آن می‌توان از ویژگی asp-route استفاده کرد
 <form asp-route="login"
      asp-route-returnurl="@ViewBag.ReturnUrl"
      method="post" >
</form>


Tag helpers مخصوص تعریف اسکریپت‌ها و CSSها

 در اینجا Tag Helpers صرفا به عنوان جایگزین‌های HTML Helpers مطرح نیستند. توسط آن‌ها قابلیت‌های جدیدی نیز ارائه شده‌است. برای مثال اگر تگ اسکریپت را به صورت ذیل تعریف کنیم:
 <script asp-src-include="~/app/**/*.js"></script>
یک چنین خروجی فرضی را تولید می‌کند:
 <script src="/app/app.js"></script>
<script src="/app/controllers/controller1.js"></script>
<script src="/app/controllers/controller2.js"></script>
<script src="/app/controllers/controller3.js"></script>
<script src="/app/controllers/controller4.js"></script>
<script src="/app/services/service1.js"></script>
<script src="/app/services/service2.js"></script>
به این معنا که یک سطر asp-src-include، بر اساس الگویی که دریافت می‌کند، تمام فایل‌های اسکریپت موجود در یک پوشه را یافته و برای آن‌ها، تگ اسکریپت تولید می‌کند. دراینجا ذکر ** به معنای بررسی تمام زیرپوشه‌های app است. اگر تنها پوشه‌ی خاصی مدنظر است، باید ** را حذف کرد.
در این بین اگر می‌خواهید از پوشه‌ی خاصی صرفنظر کنید، از asp-src-exclude استفاده کنید:
 <script asp-src-include="~/app/**/*.js"
        asp-src-exclude="~/app/services/**/*.js">
</script>
همچنین در اینجا امکان تعریف CDN و fallback هم وجود دارد. استفاده‌ی از CDNها جهت کاهش ترافیک سرور و بهبود کارآیی برنامه با ارائه‌ی نمونه‌های کش شده‌ی فریم ورک‌های معروف، متداول هستند که در اینجا نمونه‌ای از نحوه‌ی تعریف آن‌ها را مشاهده می‌کنید. همچنین تعریف fallback در اینجا به این معنا است که اگر CDN در دسترس نبود، به نمونه‌ی محلی موجود بر روی سرور مراجعه شود.
 <link rel="stylesheet" href="//ajax.aspnetcdn.com/ajax/bootstrap/3.0.0/css/bootstrap.min.css"
      asp-fallback-href="~/lib/bootstrap/css/bootstrap.min.css"
      asp-fallback-test-class="hidden"
      asp-fallback-test-property="visibility"
      asp-fallback-test-value="hidden" />
 
<script src="//ajax.aspnetcdn.com/ajax/bootstrap/3.0.0/bootstrap.min.js"
        asp-fallback-src="~/lib/bootstrap/js/bootstrap.min.js"
        asp-fallback-test="window.jQuery">
</script>

به علاوه اگر ویژگی asp-file-version را نیز ذکر کنید:
 <link rel="stylesheet" href="~/css/site.min.css" asp-file-version="true"/>
یک چنین لینکی تولید می‌شود:
 <link rel="stylesheet" href="/css/site.min.css?v=UdxKHVNJA5vb1EsG9O9uURFDfEE3j1E3DgwL6NiDGMc" />
هدف آن نیز اصطلاحا cache busting است. به این معنا که با تغییر محتوای این فایل‌ها، کوئری استرینگ تولید شده، مجددا محاسبه شده و مرورگر همواره آخرین نگارش موجود را دریافت خواهد کرد و دیگر از نمونه‌ی کش شده‌ی قدیمی استفاده نمی‌کند.

یک نکته: ویژگی asp-file-version را برای تصاویر هم می‌توان بکار برد:
 <img src="~/images/logo.png"
     alt="company logo"
     asp-file-version="true" />
که یک چنین خروجی را تولید می‌کند و هدف آن نیز جلوگیری از کش شدن تصویر، با تغییر محتوای آن است:
 <img src="/images/logo.png?v=W2F5D366_nQ2fQqUk3URdgWy2ZekXjHzHJaY5yaiOOk"
     alt="company logo"/>


بررسی Environment Tag Helper

با متغیرهای محیطی و نحوه‌ی تعریف آن‌ها در قسمت‌های قبل آشنا شدیم. در اینجا tag helper سفارشی خاصی برای کار با آن‌ها ارائه شده‌است که شیبه به if/else عمل می‌کنند:
<environment names="Development">    
   <link rel="stylesheet" href="~/css/site1.css" />
   <link rel="stylesheet" href="~/css/site2.css" />
</environment>

<environment names="Staging,Production">
   <link rel="stylesheet" href="~/css/site.min.css" asp-file-version="true"/>
</environment>
هدف این است که اگر متغیر محیطی به Development تنظیم شده بود، لینک‌های ساده و اصلی فایل‌های css یا اسکریپت در HTML نهایی درج شوند و اگر حالت توسعه تنظیم شده بود، لینک‌های min یا فشرده شده‌ی آن‌ها ارائه شوند؛ به همراه asp-file-version که cache busting را فعال می‌کند.


کار با دراپ داون‌ها توسط Tag helpers

فرض کنید ViewModel یک view جهت نمایش یک دراپ داون به این صورت تنظیم شده‌است:
public class CustomerViewModel
{
   public string Vehicle { get; set; }  
   public List<SelectListItem> Vehicles { get; set; }
برای نمایش SelectListItem توسط tag helpers می‌توان به صورت ذیل عمل کرد:
 <select asp-for="Vehicle" asp-items="Model.Vehicles">
</select>
asp-for به نام خاصیتی اشاره می‌کند که در نهایت مقدار انتخاب شده را دریافت می‌کند و asp-items لیست آیتم‌های دراپ داون را رندر می‌کند.