اشتراک‌ها
مجله InfoQ درباره .NET Core

در این مجله، تجربه و دیدگاه هفت نویسنده پیرامون دات نت فریم ورک و مهاجرت پیش رو به دات نت کور گردآوری شده است.

مجله InfoQ درباره .NET Core
مطالب
پَرباد - آموزش پیاده‌سازی پرداخت آنلاین در دات نت - مقدمه
پَرباد یک کتابخانه رایگان و اوپن سورس است که امکان افزودن قابلیت پرداخت آنلاین را به وب اپلیکیشن‌ها محیا میکند.

با توجه به نسخه قدیمی و درخواست کاربران جهت ارائه نسخه‌ای برای پشتیبانی از NET Core. ، نسخه‌ی کاملا جدیدی از این پروژه آماده گردید.

مزایا و ویژگی‌ها
  • نصب آسان با استفاده از Nuget
  • بدون نیاز به هیچگونه وب سرویس و یا دانش پیاده سازی سیستم‌های پرداخت آنلاین 
  • پشتیبانی از درگاه‌های: ملت، ملی (سداد)، پارسیان، پاسارگاد، ایران کیش و سامان
  • انجام پرداخت، فقط با نوشتن ۳ خط کد
  • طراحی کاملا یکپارچه برای انجام عملیات پرداخت با تمامی بانک‌ها
  • رعایت نکات امنیتی پرداخت آنلاین
  • درگاه مجازی، برای شبیه سازی عملیات پرداخت 
  • امکان استفاده از پروکسی برای سرور‌های خارج از ایران در صورت نیاز
  • استفاده از تکنولوژی‌های مدرن و استاندارد
  • قابل نصب بر روی پروژه‌های: ASP.NET Core, ASP.NET MVC, ASP.NET WebForms

آنچه شما در این سری مقالات یاد خواهید گرفت:

نحوه نصب
  • ASP.NET WebForms
  • ASP.NET MVC
  • ASP.NET CORE

آموزش پایه
  • درخواست پرداخت
  • تایید پرداخت
  • بازگرداندن مبلغ پرداخت شده

تنظیمات
  • درگاه‌ها
  • HttpContext
  • پایگاه داده
  • پیام‌ها

آموزش پیشرفته
  • ایجاد یک صورت‌حساب پرداخت با استفاده از InvoiceBuilder
  • درگاه مجازی پرداخت
  • استفاده از پروکسی
  • توکن پرداخت
  • تزریق وابستگی
  • Logging

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

  یازده. در جاوا رویدادها با استفاده از اینترفیس‌ها پیاده سازی می‌شوند. برای نامگذاری یک رویداد، قاعده آن در جاوا بدین شکل است که نام‌ها به صورت (+ ) Camel نوشته شده و آخرین عبارت هم Listener باشد و نیازی هم به حرف I در نامگذاری اینترفیس نیست؛ چون همه می‌دانند که این Listener آخری یعنی رویدادی که با اینترفیس پیاده سازی شده است و استفاده از I بی معنی است. هر چند بر خلاف دات نت، در اینجا استفاده از قاعده I چندان متداول نیست.
 public interface CopyFileListener
    {
        void PublishProgress(long fileSize,long copiedSize);
    }

دوازده. گوگل اینترفیس‌هایی را که برای رویدادها میسازد، داخل کلاس اصلی تعریف میکند. پس بهتر هست که شما هم همین روند را ادامه بدید و از این قاعده خارج نشوید. اگر خوب دقت کرده باشید، در برنامه نویسی اندروید تمام اینترفیس‌ها داخل کلاس اصلی هستند:
 textView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                
            }
        });
در کد بالا رویداد OnclickListener در خود کلاس View تعریف شده است. پس ما هم بهتر هست همین کار را بکنیم:
public class MemoryWare {

    public interface CopyFileListener
    {
        void PublishProgress(long fileSize,long copiedSize);
    }
....
}
یک نکته دیگر اینکه موقعی که یک رویداد را به یک پراپرتی set انتساب می‌دهیم، رسم این است که نام آن پراپرتی با عبارت SetOn آغاز شود و با نام اینترفیس پایان یابد:
SetOnClickListener
البته اگر کلاس شما لیستی از رویدادها را درست میکند بهتر است از عبارت Add به جای SetOn استفاده کنید و برای آن یک Remove هم قرار دهید. نمونه آن را می‌توانید در کد زیر ببینید:
 editText.addTextChangedListener(new TextWatcher() {
            @Override
            public void beforeTextChanged(CharSequence s, int start, int count, int after) {

            }

            @Override
            public void onTextChanged(CharSequence s, int start, int before, int count) {

            }

            @Override
            public void afterTextChanged(Editable s) {

            }
        });

سیزده
. آداپتورها و آداپتور ویوها (چون لیست) قسمت مهمی از برنامه‌های اندرویدی به شمار می‌آیند؛ تا حدی که در بیشتر برنامه‌های ساده هم حضور پررنگی دارند. ولی برای استفاده از این آداپتورها باید بدانید که نحوه کار آن‌ها چگونه است. بسیاری از کاربران در این قسمت اشتباهات زیادی می‌کنند. اگر در stackOverflow هم در اینباره نگاه کنید، با حجم انبوهی از سوالات روبرو می‌شوید و فقط به خاطر اینکه نحوه کارکرد آن را نمی‌دانند، به مشکل برخورده‌اند.
کلاس BaseAdapter اصلی‌ترین کلاس آداپتور هاست که بقیه از آن مشتق شده‌اند و معروفترین مشتقات آن، کلاس‌های CursorAdapter و ArrayAdapter هستند که امکانات بیس آداپتور را افزایش داده‌اند.به عنوان مثال در کد پایین از ArrayAdapter استفاده شده است.


نحوه کار یک آداپتور بدین صورت است که متدی را به نام GetView با قابلیت override دارد که با هر تعداد آیتم موجود صدا زده می‌شود. ولی اگر تصور کنیم فقط چند صدهزار آیتم هم داشته باشیم، آیا واقعا اجرا می‌شود؟ جواب این سوال این است که با هر بار اسکرولی که شما میکنید آیتم‌های بعدی ایجاد می‌شوند ولی باز این سوال پیش می‌آید که هر آیتم برای خود جداگانه تشکیل می‌شود؟ مطمئنا جواب خیر است. آداپتورها از سیستمی به نام ViewRecycler برای کش کردن آیتم‌های ایجاد شده استفاده می‌کنند و با هر اسکرولی که انجام می‌شود آیتم‌های بعدی از روی آیتم‌های قبلی که قبلا از صفحه خارج شده‌اند، ساخته می‌شوند و آیتم‌های کش شده قبلی را با پارامتری با نام convertView به دست شما می‌رساند.
کد زیر را ببینید:
  @Override
    public View getView(int position, View rowView, ViewGroup parent) {

        ViewHolder viewHolder=null;
        if(rowView==null)
        {
            // 1. Create inflater
            LayoutInflater inflater = (LayoutInflater) context
                    .getSystemService(Context.LAYOUT_INFLATER_SERVICE);

            // 2. Get rowView from inflater
            rowView = inflater.inflate(R.layout.row_bank_group_list, parent, false);
            viewHolder=new ViewHolder();
            viewHolder.txtGroupName=(TextView) rowView.findViewById(R.id.text_groupName);
            rowView.setTag(viewHolder);
        }
        else
        {
            viewHolder=(ViewHolder)rowView.getTag();
        }
        viewHolder.txtGroupName.setText(getItem(position).getName());
        viewHolder.txtGroupName.setTypeface(new FontSystem().get_General_Font(context));
        viewHolder.txtGroupName.setTextColor(context.getResources().getColor(R.color.black));

        return rowView;
    }
کد بالا ابتدا بررسی میکند که آیا convertView نال است یا خیر. اگر نال بود به این معنا است که کش برای شما چیزی نداشته است و باید آیتم جدیدی را بسازید. پس اشیاء موجود در آن را در حافظه می‌آورید و مقداردهی می‌کنید. ولی اگر برابر نال نباشد، یعنی کش حاوی یک سری آیتم است که قبلا ایجاد شده‌اند. پس نیاز به inflate کردن مجدد ندارد و میتوانید  همان را مستقیما مورد استفاده قرار دهید و با مقادیر جدید آن را ست کنید.
کلاس داخلی ViewHolder هم یک الگو برای عدم بررسی Viewهای داخل آن است که نیازی به یافتن و تبدیل مجدد آن‌ها نداشته باشید. در این روش شیء، داخل خصوصیت tag آیتم قرار گرفته است و وقتی از کش برداشته شود، خاصیت تگ آن را می‌خوانیم و مستقیما مورد استفاده قرار می‌دهیم. در این حالت شما بهترین استفاده را از پردازش‌ها و حافظه، می‌کنید.

چهارده. یکی از کارهایی را که قبل از کار کردن در یک مسیر فیزیکی باید انجام دهید این است که مطمئن باشید اجازه نوشتن در آن ناحیه را دارید یا خیر. در غیر اینصورت برنامه شما با خطای FC روبرو می‌شود و اجرای آن خاتمه می‌یابد. به همین دلیل اکثر برنامه نویسان از متد CanWrite در کلاس File استفاده می‌کنند. ولی در هنگام استفاده از این متد باید دقت داشته باشید که کلاس File فقط باید حاوی مسیر باشد و اسمی از فایل مربوطه در آن نباشد. دلیل هم آن است که این احتمال می‌رود اگر فایلی هم وجود نداشته باشد، مقدار false را به شما برگرداند. مثال زیر قرار است فایلی را در کارت حافظه بنویسید، ولی بررسی اجازه نوشتن در مسیر، اشتباه است:
File file=new File(sdcardPath,fileName);

if(file.CanWrite())
{
  .....
}
کد بالا را به طور صحیح بازنویسی می‌کنیم:
File file=new File(sdcardPath);

if(file.CanWrite())
{
file=new File(sdcardPath,filePath);
  .....
}
اگر هنگام تست این کد مشکلی نداشتید، دلیل بر صحت کد نیست. بلکه بنابر تجربه شخصی من، زمانی این مشکل پیش آمده بود که روی چند گوشی تست شده و بعدها مشکل در گوشی پیش آمده بود که هم مدل و دقیقا مشابه یکی از گوشی‌های تستی بود.

پانزده. کارت حافظه خارجی: همه برنامه نویسان اندروید حداقل یکبار از کد زیر استفاده کرده اند:
Environment.getExternalStorageDirectory()
بررسی‌ها در استک اورفلو نشان می‌دهد که برنامه نویسان، گزارش عملکرد اشتباهی را از این متد دارند. ولی حقیقت این است که این متد به هیچ عنوان مقدار اشتباهی را بر نمی‌گرداند. بلکه منطق آن متفاوت از چیزی است که شما فکر می‌کنید. وقتی ما صحبت از حافظه خارجی برای یک گوشی میکنیم، منظور همان کارت حافظه‌ای است که به طور جداگانه داخل گوشی قرار داده‌ایم و انتظار داریم متد بالا آدرس و مدخل همین کارت را برای ما فراهم کند. ولی در کمال تعجب می‌بینیم که آدرس حافظه داخلی برگردانده می‌شود. پس باید ببینیم اندروید از  آن چه انتظاری دارد؟
هر برنامه‌ای که در اندروید نصب می‌شود در مسیر
/Data/Data
یک دایرکتوری با نام خود دارد مثل:
/Data/Data/Info.Dotnettips.MyApp
این دایرکتوری تنها متعلق به این برنامه بوده و کسی جز Root به آن دسترسی ندارد. اندروید این دایرکتوری را به عنوان حافظه داخلی در نظر میگیرد که برای کار با آن نیاز به هیچ آدرس دهی نیست. ولی در کنار این دایرکتوری حافظه داخلی خود گوشی که در آن انبوه فایل‌های خود را ذخیره می‌کنید هم هست که اندروید آن را حافظه خارجی می‌پندارد. حال آن حافظه‌ای را که شما جداگانه به صورت یک کارت یا USB OTG متصل میکنید، به عنوان حافظه خارجی در نظر نمیگیرد. در صورتی که شما چنین انتظاری را دارید، برای حل این مشکل بهتر است از کدهای موجود مثل کد زیر استفاده کنید:
    /**
     * it will returns sd path for you
     *  <p>
     *  <b>Required Permission: </b>android.permission.READ_EXTERNAL_STORAGE<br/>
     * </p>
     * @return
     */
    public  List<String> GetExternalMounts() {
        final List<String> out = new ArrayList<>();
        String reg = "(?i).*vold.*(vfat|ntfs|exfat|fat32|ext3|ext4).*rw.*";
        String s = "";
        try {
            final Process process = new ProcessBuilder().command("mount")
                    .redirectErrorStream(true).start();
            process.waitFor();
            final InputStream is = process.getInputStream();
            final byte[] buffer = new byte[1024];
            while (is.read(buffer) != -1) {
                s = s + new String(buffer);
            }
            is.close();
        } catch (final Exception e) {
            e.printStackTrace();
        }

        // parse output
        final String[] lines = s.split("\n");
        for (String line : lines) {
            if (!line.toLowerCase(Locale.US).contains("asec")) {
                if (line.matches(reg)) {
                    String[] parts = line.split(" ");
                    for (String part : parts) {
                        if (part.startsWith("/"))
                            if (!part.toLowerCase(Locale.US).contains("vold"))
                                if(new File(part).canWrite())
                                    out.add(part);
                    }
                }
            }
        }
        return out;
    }

شانزده.
یکی از روش‌های انتقال اطلاعات بین اکتیویتی‌ها مختلف استفاده از Extras هاست که شما با تعیین یک نام یا کلید، اطلاعات مربوطه را ارسال و توسط همان کلید؛ اطلاعات را در اکتیویتی مقصد دریافت میکنید:
notesIntent.putExtra("PartyId", PartyId);
startActivity(notesIntent);
و در مقصد:
PartyId=getIntent().getLongExtra("PartyId",0);
در این حالت بهتر است با این رشته‌ها نیز همانند مورد شماره دو در قسمت اول رفتار شود تا نیازی به نوشتن و تکرار این نام‌ها نباشد. ولی با یک نگاه به استانداردهای نوشته شده در خود اندروید و بسیاری از کتابخانه‌های ثالث در می‌یابیم که بهترین روش این است که این کلید‌ها را به صورت متغیرهای ایستا در خود اکتیویتی ذخیره کنیم؛ در این حالت هر کلید در جایگاه واقعی خودش قرار گرفته است. نمونه‌ای از این استفاده را می‌توانید در کتابخانه FilePicker مشاهده کنید:
i.putExtra(FilePickerActivity.EXTRA_ALLOW_MULTIPLE, false);
i.putExtra(FilePickerActivity.EXTRA_ALLOW_CREATE_DIR, false);
i.putExtra(FilePickerActivity.EXTRA_MODE, FilePickerActivity.MODE_FILE);

هفده.
قواعد نامگذاری: برای نامگذاری متغیرها از قانون CamelCase استفاده میکنیم. ولی برای حالات زیر از روش‌های دیگر استفاده می‌شود:
  • برای ثابت‌ها از حروف بزرگ و _ استفاده کنید.
  • برای متغیرهای خصوصی از حرف m در ابتدای نام متغیر استفاده کنید.
  • برای متغیرهای استاتیک از حرف s در ابتدای نام متغیر استفاده کنید.
نمونه ای از این نامگذاری که توسط مستندات گوگل سفارش شده است:
public class MyClass {
    public static final int SOME_CONSTANT = 42;
    public int publicField;
    private static MyClass sSingleton;
    int mPackagePrivate;
    private int mPrivate;
    protected int mProtected;
}

هجده:
قاعده نظم و ترتیب در import‌ها توسط مستندات گوگل بدین شکل تعریف شده است:
  1. نام پکیج‌های ارائه شده توسط گوگل
  2. نام پکیج‌های ثالث
  3. نام پکیج‌های موجود در java و javax
  4. پکیج‌های موجود در پکیج اصلی
البته رعایت این قاعده کمی سخت و عموما بدون اجراست ولی نگران نباشید. از آنجایی که ادیتور از طرف jet brains ارائه شده‌است و عمل import به طور خودکار صورت میگیرد و با ابزارهای دیگری که در دل این ادیتور قرار گرفته است، این عمل به طور خودکار انجام می‌گیرد.

نوزدهم. مرتب سازی متدهای دسترسی یک کلاس: بسیار خوب است که همیشه کدهای ما نظم خاصی را داشته باشد تا پیگیری‌های شخصی و تیمی در آن راحت‌تر صورت بگیرد. برای مثال در یک کلاس ابتدا متدهای public و سپس private قرار گیرند و الی آخر.
الگوی عمومی که برای کار با جاوا صورت گرفته است به شکل زیر می‌باشد:
public, protected, private,abstract, static, transient, volatile, synchronized, final, native.
البته این متدهای دسترسی بسته به فیلد بودن و متد بودن نیز تغییر میکند. به عنوان مثال ابتدا فیلدها در نظر گرفته می‌شوند و سپس متدها و ...
ادیتور intelij شامل تنظمیاتی برای مرتب سازی کدهاست که در این مورد بسیار سودمند است. با طی کردن مسیر زیر می‌توانید برای آن ترتیب اینگونه موارد را مشخص کنید.
Settings>Editor>Code Style>Arrangement
در اینجا شما امکان تعاریف این ترتیب‌ها را دارید. البته بهتر هست در حالت پیش فرض باشد تا حالتی عمومی بین افراد مختلف برقرار باشد.

در تصویر بالا متدها به ترتیب متدهای دستری بین بلوک‌های کامنت method start و method end قرار گرفته اند.

 همچنین شامل گزینه‌های دیگری نیز میباشد که به نظرم فعال کردنشان بسیار خوب است. گزینه keep overridden methods together به شما کمک می‌کند تا متدهایی را که رونویسی می‌شوند، در کنار یکدیگر قرار بگیرند که برای کلاس‌های اندرویدی مثل اکتیویتی‌ها و فرگمنت‌ها و ... بسیار خوب است. گزینه مفید دیگر Keep dependent methods together است که در دو حالت عمقی یا خطی متدهای وابسته (متدهایی که متدهای دیگر را در آن کلاس صدا میزنند) در کنار یکدیگر قرار میدهد و مابقی گزینه‌ها، که بسیار سودمند هست. به هر حال هر قاعده‌ای را که برای خود انتخاب میکنید اگر در حالت پیش فرض نیست بهتر است در مستندات پروژه ذکر شود تا افراد دیگر سریعتر به موضوع پی ببرند.

قسمت بیستم. این مورد برای افراد تازه کار می‌باشد که تازه اندروید استادیو را باز کرده‌اند و مشغول کدنویسی می‌باشند. یکی از مواردی که در همان مرحله اول به آن برمیخورید این است که intellisense  ادیتور به بزرگی و کوچکی حروف حساس است و تنها با حرف اول سازگاری دارد. برای تغییر این مسئله باید مسیر زیر را طی کنید:
Settings>Editor>Completion>Case-sensitive Completion>None
حالا با تایپ هر کلمه، دستورات و آبجکت‌هایی را که شامل آن کلمات باشند، به شما نمایش داده خواهند شد.

مطالب
توسعه برنامه‌های Cross Platform با Xamarin Forms & Bit Framework - قسمت سیزدهم
در این قسمت قصد داریم به بررسی نحوه‌ی بهبود Performance در پروژه‌های Xamarin Forms نگاهی بیاندازیم. صد البته امکان پوشش دادن تمامی نکات وجود ندارد و در این قسمت سعی بر پوشش دادن مهم‌ترین آنها را داریم.
توجه داشته باشید که در قسمت نهم به "x:DataType" و در قسمت چهارم به "مواردی مهم در زمینه‌ی بهبود عملکرد پروژه‌های Xamarin در Android" پرداخته بودیم که آن نکات در بهبود سرعت برنامه‌ها تاثیر گذارند. همان طور که در قسمت چهارم گفته شد، همیشه سرعت برنامه را در Release mode تست کنید.

در Xamarin Forms هر کنترل (برای مثال Entry و Button) در زمان اجرا به یک کنترل Native معادل خود تبدیل می‌شود. مشکلی که وجود دارد این است که وقتی از روی Button مربوط به Xamarin Forms، یک Button در Android و ... ساخته می‌شود، آن Button بر روی یک Container قرار می‌گیرد. در واقع به ازای هر کنترل Xamarin Forms، دو کنترل Native ساخته می‌شود(!) و تعداد کنترل‌های بیشتر در برنامه یعنی کندی بیشتر.
وظیفه تبدیل Xamarin forms control به یک Native control بر عهده ‌Renderer هاست. البته اینکه هر Xamarin Forms control به دو Native control تبدیل شود در ذیل بحث Fast Renderers در حال بهبود یافتن است. Fast Renderer یک Renderer است که به ازای هر Xamarin forms control، فقط یک کنترل Native را می‌سازد. راهنمای فعال سازی Fast Renderers را می‌توانید بخوانید و در برنامه‌هایتان اعمال کنید؛ ولی در پروژه‌های مبتنی بر Bit مانند XamApp، این مهم به صورت خودکار فعال می‌شود و نیازی به اقدام جداگانه‌ای نیست.

مورد بعدی در مورد Layout‌های Xamarin Forms است (Grid,StackLayout,FlexLayout و...) در صورتیکه قصد تعریف کردن مواردی چون Tap Gesture و Background را بر روی Layout خود ندارید و از Layout خود فقط انتظار Layout بودن را دارید، می‌توانید آنها را سریع‌تر سازید. مثال قسمت قبل را که برای نمایش هر Product در List View از Flex Layout استفاده می‌کرد، به‌خاطر بیاورید. ما آن Flex را استفاده کردیم تا نام Product، بیست و پنج درصد فضای هر ردیف از List View را بگیرد و ... به آن Flex Layout رنگی داده نشد یا هر چیزی از این قبیل. برای بهبود عملکرد آن List View می‌توانیم از Flex Layout به شکل زیر استفاده کنیم:
<sfListView:SfListView.ItemTemplate>
                <DataTemplate>
                    <FlexLayout
                        x:DataType="model:Product"
                        CompressedLayout.IsHeadless="True"
                        Direction="Row">
                        <Label
                            FlexLayout.Basis="40%"
                            Text="{Binding Name}"
                            VerticalTextAlignment="Center" /> ...
با اضافه کردن CompressedLayout.IsHeadless=True دیگر کنترل Native ای برای آن Flex Layout ساخته نمی‌شود و کنترل Native کمتر یعنی سرعت بیشتر برنامه.
برای این امکان، ذکر دو نکته الزامی است:
۱- استفاده از CompressedLayout.IsHeadless باعث می‌شود تعدادی از امکانات Layout مانند Background Color دیگر کار نکنند. فعال سازی آن فقط در DataTemplate‌های ListView توصیه می‌شود که مثلا 20 عدد Product در مثال قسمت قبل، منجر به ساخته شدن 20 عدد Flex Layout می‌شوند و اگر کاری کنیم که کنترل Native معادل آن ساخته نشود، لااقل 20 بار سود کرده‌ایم! استفاده کردن از CompressLayout در همه جای برنامه، ایده جالبی نیست.
۲- CompressedLayout فقط در Android و iOS اعمال می‌شود و تست کردن آن در UWP عملا فایده‌ای ندارد.

نکته مهم بعدی، بحث نمایش عکس است. در Xamarin Forms یک عکس می‌تواند در رزولوشن‌های مختلف با کمک Drawable در Android و Asset Catalog Image Sets در iOS و ... باشد. همچنین می‌توان عکس را از یک Url گرفت و یا به شکل Embedded در پروژه‌ی مشترک بین سه پلتفرم باشد. می‌توان علاوه بر PNG و JPG از WebP ،SVG و GIF نیز استفاده نمود. اما آنچه که مهم است این است که بعد از یادگیری اصول اولیه نمایش عکس در Xamarin Forms، حتما حتما از FF Image Loading برای نمایش عکس‌ها استفاده شود.
استفاده از FF Image Loading دارای مزایایی چون پشتیبانی از WebP | SVG است. همچنین این کتابخانه فایل‌ها را نیز Cache می‌کند (هم در صورتیکه Url باشند و قرار باشند از طریق اینترنت دریافت شوند و هم Bitmap حاصل از Render کردن آن عکس برای آن Device خاص همانند GlideX در Android). امکان گرد کردن عکس، یا سیاه و سفید نمودن آن و کوهی از امکانات دیگر، استفاده‌ی از این کتابخانه را الزامی می‌کند!
برای نمایش لوگوی Bit در برنامه برای مثال، ابتدا Package مربوطه را در پروژه XamApp نصب می‌کنیم. سپس آن را در Android ،iOS و UWP راه اندازی می‌کنیم و در Android مقدار enableFastRenderer را True می‌دهیم. سپس در Xaml داریم:
        <ffimageloading:CachedImage
            CacheType="All"
            DownsampleToViewSize="true"
            HeightRequest="100"
            HorizontalOptions="Center"
            Source="https://avatars2.githubusercontent.com/u/22663390?s=100"
            VerticalOptions="Center"
            WidthRequest="100" />
پایان
مطالب
استفاده از Unity در پیاده سازی الگوی Service locator
یکی از راهکارهای پیاده سازی IOC یا همان Inversion Of Control در پروژه‌های MVC استفاده از Unity و معرفی آن به DependencyResolver خود دات نت است.
برای آشنایی با Unity و قابلیت‌های آن میتوانید به اینجا و اینجا سر بزنید.
اما برای استفاده از Unity در پروژه‌های MVC کافی است در Global یا فایل راه انداز (bootstrapper ) تک تک انتزاع‌ها (Interface) را به کلاس‌های مرتبط شان معرفی کنید.
var container = new UnityContainer();  
container.RegisterType<ISomeService, SomeService>(new PerRequestLifetimeManager());
container.RegisterType<ISomeBusiness, SomeBusiness>(new PerRequestLifetimeManager());
container.RegisterType<ISomeController, SomeController>(new PerRequestLifetimeManager());
و بعد از ایجاد container از نوع UnityContainer میتوانیم آنرا به MVC معرفی کنیم:
DependencyResolver.SetResolver(new UnityDependencyResolver(container));
تا به اینجا به‌راحتی میتوانید از سرویس‌های معرفی شده در پروژه MVC استفاده کنید.
var someService=(ISomeService)DependencyResolver.Current.GetService(typeof(ISomeService));
var data=someService.GetData();
اما اگر بخواهیم از کلاس‌های معرفی شده در Unity در لایه‌های دیگر (مثلا Business) استفاده کنیم چه باید کرد؟
برای هر این مشکل راهکارهای متفاوتی وجود دارد. من در لایه سرویس از Service locator بهره برده ام. برای آشنایی با این الگو اینجا را بخوانید. اکثر برنامه نویسان الگوهای IOC و Service Locator را با هم اشتباه میگیرند یا آنها را اشتباها بجای هم بکار میبرند.
برای درک تفاوت الگوی IOC و Service locator اینجا را بخوانید.

در لایه سرویس یک کلاس Service Factory داریم که قرار است همه سرویس‌ها، برای برقراری ارتباط با یکدیگر از آن استفاده کنند.این کلاس معمولا در لایه سرویس به اشکال گوناگونی پیاده سازی میشود که کارش وهه سازی از Interface‌های درخواستی است. اما برای یکپارچه کردن آن با Unity من آنرا به شکل زیر پیاده سازی کرده ام

public class ServiceFactory : MarshalByRefObject
    {
        static IUnityContainer uContainer = new UnityContainer();
        public static Type DataContextType { get; set; }

        public static void Initialise(IUnityContainer unityContainer, Type dbContextType)
        {
            uContainer = unityContainer;
            DataContextType = dbContextType;
            uContainer.RegisterType(typeof(BaseDataContext), DataContextType, new HierarchicalLifetimeManager());
        }
        public static T Create<T>()
        {
            return (T)Activator.CreateInstance<T>();
        }
        public static T Create<T>(string fullTypeName)
        {
            return (T)System.Reflection.Assembly.GetExecutingAssembly().CreateInstance(fullTypeName);
        }
        public static T Create<T>(Type entityType)
        {
            return (T)Activator.CreateInstance(entityType);
        }
        public static dynamic Create(Type entityType)
        {
            return Activator.CreateInstance(entityType);
        }

        public static T Get<T>()
        {
            return uContainer.Resolve<T>();
        }
        public static object Get(Type type)
        {
            return uContainer.Resolve(type);
        }
    }

در این کلاس ما بجای ایجاد داینامیک آبجکت‌ها، از Unity استفاده کرده‌ایم. در همان ابتدا که برنامه‌ی وب ما برای اولین بار اجرا میشود و بعد از Register کردن کلاس‌ها، می‌توانیم container را به صورت پارامتر سازنده به کلاس Service Factory ارسال کنیم. به این ترتیب برای استفاده از سرویس‌ها در لایه Business از Unity بهره میبریم.

البته استفاده از Unity برای DataContext خیلی منطقی نیست و بهتر است نوع DataContext را در ابتدا بگیریم و هرجا نیاز داشتیم با استفاده از متد Create از آن وهله سازی بکنیم.

مطالب
توضیح مثالی از SIMD برای نشان دادن عملکرد آن - SIMD Performance
پیشنیازها

SIMD یا ترجمه آن به فارسی به معنی «تک دستورالعمل و چند داده»، قابلیت آن‌را دارد تا بر روی مقادیر عددی به صورت موازی و با استفاده از پردازنده کار کند. اگر بتوانیم ساختار پروژه‌های خود را به طوری ایجاد کنیم تا بتوانیم از SIMD در پردازش‌های خود استفاده کنیم، سرعت انجام فعالیت‌ها، بسیار زیاد افزایش پیدا خواهند کرد؛ به خصوص این امر در حجم‌های پردازشی زیاد محسوس خواهد بود. البته مدیریت استفاده از منابع و پردازنده نباید فراموش شوند.
اطلاعات لازم از SIMD و نحوه عملکرد آن را می‌توانید در مقاله پیشنیاز بیابید. در این مقاله قصد داریم تا یک مثال ساده از کارآیی SIMD را مطرح کنیم. مثال زیر از مثال SimdSpike الگو برداری شده است و تغییراتی نیز جهت تکمیل شدن آن انجام شده است.
در این مثال می‌خواهیم نمونه کدهایی را با روش‌های معمول اجرا کنیم و زمان اجرای آن را با زمان اجرای همان مثال‌ها با روش SIMD، مقایسه کنیم. 
با استفاده از ویژوال استودیو 2015 آپدیت 3 یک پروژه کنسول با چارچوب دات نت 4.6.1 ایجاد کرده‌ایم. البته می‌توانید ازدیگر نسخه‌ها هم استفاده کنید به شرط آنکه دات نت 4.6x را نصب کرده باشید.

در صورتی که ویژوال استودیوی شما دارای این ورژن و آپدیت نبود، می‌توانید چارچوب دات نت 4.6.1 را جداگانه در سیستم خود نصب نمایید. توجه داشته باشید که برای استفاده از چارچوب دات نت در ویژوال استودیو باید نسخه‌های DevPack یا DeveloperPack را نصب نمایید (دریافت  دات نت 4.6.1 نسخه مخصوص استفاده در ویژال استودیو). 

در پروژه ایجاد شده فایلی به نام Program.cs و در آن کلاس Program وجود دارد. در این کلاس تابع شروع کننده برنامه یعنی Main وجود دارد و برنامه از این تابع شروع خواهد شد.

نمایی از فایل‌های پروژه

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

پس از بررسی وضعیت پشتیبانی از SIMD ، تابعی را که در فایل Utilities.cs نوشته شده است، فراخوانی می‌کنیم. این تابع به بررسی وضعیت تعداد رجیسترهای SIMD و وضعیت انواع نوع‌های داده‌ای در SIMD می‌پردازد. اگر هر نوع داده‌ای از SIMD پشتیبانی کند (که بستگی به نوع پردازنده شما دارد) اندازه هر نوع داده‌ای را در SIMD چاپ می‌کند و در صورت عدم پشتیبانی هر نوع داده‌ای از SIMD مقدار «عدم پشتیبانی SIMD از آن نوع داده‌ای» چاپ خواهد شد. 

  تا به اینجای برنامه کد‌های تابع شروع کننده به صورت زیر خواهد بود. 
using System.Numerics;
using static System.Console;

namespace TestSIMD
{
    class Program
    {
        private const int ArraySize = 7680 * 4320;
        static void Main(string[] args)
        {
            // بررسی وضعیت پشتیبانی از SIMD
            if (!Vector.IsHardwareAccelerated)
            {
                WriteLine("Hardware acceleration not supported.");
                WriteLine();
                return; // عدم پشتیبانی و خاتمه برنامه
            }
            WriteLine("Hardware acceleration is supported"); // اعلام پشتیبانی از SIMD
            WriteLine();

            // بررسی وضعیت نوع‌های داده ای در مشخصات سخت افزاری SIMD
            Utilities.PrintHardwareSpecificSimdEffectiveness();

            //به منظور عدم خروج از برنامه و دیدن نتایج آزمایش
            WriteLine("Press any key to exit");
            ReadKey();
        }
    }
}
اجرای برنامه هم به صورت زیر به نمایش در خواهد آمد. 

در فایل Utilities.cs، توابع دیگری هم وجود دارند که کارآیی هر یک به صورت توضیح در بالای هر تابع نوشته شده است. این توابع برای تولید یک نوع داده‌ای تصادفی و ایجاد آرایه‌ای از نوع داده‌ای به صورت تصادفی به کار برده می‌شوند. می توانید در سورس برنامه این توضیحات را مشاهده کنید.
تا به اینجا تنها به بررسی پشتیبانی سخت افزاری از SIMD پرداختیم و همچنین توانستیم نوع‌های داده‌ای را که SIMD در سخت افزار ما پشتیبانی می‌کند، شناسایی کنیم و اندازه رجیستر‌های آنها را بیابیم.
حال به بررسی عملکرد توابع SIMD می‌پردازیم و با نوشتن چند تابع، زمان اجرای محاسباتی آنها را با نوشتن همان توابع در حالت معمولی و ساده مقایسه می‌کنیم.
 برای انجام مقایسه، زمان اجرای یک عملیات را در حالت معمول، با زمان اجرای همان عملیات در حالت SIMD بررسی می‌کنیم. هر عملیات را 3 مرتبه پشت سر هم اجرا می‌کنیم و زمان آنها را ثبت می‌کنیم تا تفاوت زمان اجرا را با تکرار عملیات نیز مشاهده کنیم. توابعی که آزمایشات را انجام می‌دهند و زمان اجرا را ثبت و نمایش می‌دهند، در فایل PerformanceTests.cs و در کلاس PerformanceTests قرار دارند و از توابع سه کلاس دیگر که عملیات در آن نوشته شده‌اند، استفاده می‌کنند.
  • فایل IntSimdProcessor.cs
    • در این فایل کلاسی به نام IntSimdProcessor قرار دارد که شامل 6 تابع می‌باشد و این تابع‌ها با نوع داده‌ای صحیح یا همان Integer کار می‌کنند. نام کلاس هم به همین خاطر نام گذاری شده است. 
    • این 6 تابع در کل 3 عملیات را شامل عملیات‌های زیر انجام می‌دهند. یکبار در حالت معمولی و یکبار با استفاده از توابع SIMD این کار را انجام می‌دهند:
      • پیدا کردن بزرگترین و کوچکترین عدد در آرایه
      • جمع عناصر دو آرایه با هم با استفاده از یک آرایه کمکی که نتیجه در آرایه کمکی ریخته می‌شود
      • جمع عناصر دو آرایه بدون استفاده از آرایه کمکی که مجموع در آرایه اول ریخته می‌شود
    • در بالای هر تابع در این فایل توضیحات لازم درباره‌ی فعالیت آن تابع ذکر شده است.
 
  • فایل FloatSimdProcessor.cs
    • در این فایل کلاسی با نام FloatSimdProcessor قرار دارد که همانطور که از نام کلاس پیداست، توابعی برای کار بر روی اعداد از نوع داده‌ای float در آن نوشته شده‌اند.
    • در این کلاس هم 6 تابع برای انجام 3 عملیات زیر نوشته شده است که به ازای هر عملیات دو تابع یکی در حالت معمولی و یکی در حالت SIMD نوشته شده است.
      • جمع دو آرایه با استفاده از یک آرایه کمکی - مجموع در آرایه کمکی ریخته می‌شود
      • جمع دو آرایه اول ورودی - مجموع در آرایه سوم ریخته می‌شود
      • جمع دو آرایه بدون استفاده از آرایه کمکی - مجموع در آرایه اول ریخته می‌شود
    • در آزمایشات نوشته شده در کلاس PerformanceTests  تنها از عملیات آخری استفاده شده است و از دو عملیات اول استفاده نشده است که در صورت تمایل می‌توانید از دیگر عملیات‌ها نیز استفاده کنید.
    • در بالای هر تابع در این فایل توضیحات لازم درباره‌ی فعالیت آن تابع نیز ذکر شده است.
 
  • فایل UShortSimdProcessor.cs
    • در این فایل کلاسی با نام UShortSimdProcessor قرار دارد و همانطور که از نام کلاس پیداست، توابعی برای کار بر روی اعداد از نوع داده‌ای ushort یا همان اعداد صحیح کوچک بدون علامت نوشته شده‌اند.
    • در این کلاس 12 تابع برای انجام 6 عملیات زیر نوشته شده‌است که به ازای هر عملیات، دو تابع یکی در حالت معمولی و یکی در حالت SIMD نوشته شده است.
      • جمع دو آرایه اول ورودی که مجموع در آرایه سوم ریخته می‌شود
      • جمع دو آرایه بدون استفاده از آرایه کمکی که مجموع در آرایه اول ریخته می‌شود
      • بدست آوردن کمترین و بیشترین مقدار در یک آرایه اعداد صحیح کوچک بدون علامت
      • جمع عناصر آرایه ورودی و ذخیره مجموع آنها در یک متغیر کمکی
      • جمع عناصر آرایه ورودی و ذخیره مجموع آنها در یک متغیر کمکی بدون بررسی سرریز (Overflow)
      • محاسبه میانگین و بدست آوردن کمترین و بیشترین مقدار در یک آرایه اعداد صحیح کوچک بدون علامت
    • در بالای هر تابع در این فایل توضیحات لازم درباره‌ی فعالیت آن تابع ذکر شده است.
 
حال در کلاس PerformanceTests برای انجام آزمایشات و مقایسه زمان اجرا، 10 تابع وجود دارند که 10 عملیات مختلف را بر روی 3 نوع داده‌ای، اجرا می‌کنند. 3 عملیات از کلاس IntSimdProcessor و یک عملیات از کلاس FloatSimdProcessor و 6 عملیات از کلاس UShortSimdProcessor را مورد آزمایش قرار داده‌ایم که در مجموع شامل 10 آزمایش در 10 تابع مختلف شده است.
public static void TestIntArrayAdditionFunctions(int testSetSize) {
    WriteLine();
    Write("Testing int array addition, generating test data...");
    var intsOne = GetRandomIntArray(testSetSize); //تولید آرایه عددی به صورت تصادفی
    var intsTwo = GetRandomIntArray(testSetSize);
    WriteLine($" done, testing...");// پایان تولید آرایه‌ها و شروع پردازش
    var naiveTimesMs = new List<long>(); // تعریف لیستی برای ریختن زمان پاسخ دهی در حالت ساده و معمولی
    var hwTimesMs = new List<long>(); // تعریف لیستی برای ریختن زمان پاسخ دهی در حالت SIMD و سخت افزاری 
    for (var i = 0; i < 3; i++) { // ایجاد حلقه برای تکرار محاسبات برای اندازه گیری زمان در حالت تکراری
        stopwatch.Restart();//شروع ثبت زمان
        var result = IntSimdProcessor.NaiveSumFunc(intsOne, intsTwo);//اجرای تابع جمع دو آرایه
        var naiveTimeMs = stopwatch.ElapsedMilliseconds;//ثبت زمان
        naiveTimesMs.Add(naiveTimeMs);//افزودن زمان ثبت شده به لیست زمان‌های ساده و معمول
        WriteLine($"Naive analysis took:                {naiveTimeMs}ms (last value = {result.Last()}).");

        stopwatch.Restart();//شروع ثبت زمان
        result = IntSimdProcessor.HWAcceleratedSumFunc(intsOne, intsTwo);//اجرای تابع جمع دو آرایه در حالت سخت افزاری
        var hwTimeMs = stopwatch.ElapsedMilliseconds;//ثبت زمان
        hwTimesMs.Add(hwTimeMs);//افزودن زمان به لیست زمان‌های سخت افزاری
        WriteLine($"Hareware accelerated analysis took: {hwTimeMs}ms (last value = {result.Last()}).");
    }//پایان حلقه و چاپ نتایج
    WriteLine("Int array addition:");
    WriteLine($"Naive method average time:          {naiveTimesMs.Average():.##}");
    WriteLine($"HW accelerated method average time: {hwTimesMs.Average():.##}");
    WriteLine($"Hardware speedup:                   {naiveTimesMs.Average() / hwTimesMs.Average():P}%");
}
در بالا تکه کدی مربوط به تابع آزمایش اول از کلاس PerformanceTests قرار دارد و وظیفه دارد عملیات جمع دو آرایه را با استفاده از یک آرایه کمکی اعداد صحیح، هم در حالت معمولی و هم در حالت SIMD انجام دهد و زمان اجرای آنها را ثبت و نمایش دهد تا بتوانیم این زمان اجرا‌ها را با هم مقایسه کنیم.
ساختار و روند اجرای کلیه آزمایش‌ها و توابع در کلاس PerformanceTests با یکدیگر یکسان است و از یک stopwatch یا همان کرنومتر برای محاسبه زمان اجرا استفاده شده است.
هر کدام از این توابع یک عملیات را مورد بررسی قرار می‌دهند و هر عملیات را 3 مرتبه اجرا می‌کنند تا زمان تکرار اجرا نیز مورد مقایسه قرار گیرد.

نام تابع ذکر شده نشان دهنده آزمایش بر روی آرایه اعداد صحیح یا همان Integer می‌باشد که شامل یک پارامتر ورودی از نوع عدد صحیح می‌باشد. این پارامتر ورودی نشان دهنده اندازه هر آرایه‌ای می‌باشد که قرار است تولید شود.  

TestIntArrayAdditionFunctions(int testSetSize)

در قدم اول این تابع، باید آرایه‌ها را تولید کنیم که کد آن به صورت زیر است.

Write("Testing int array addition, generating test data...");
var intsOne = GetRandomIntArray(testSetSize);
var intsTwo = GetRandomIntArray(testSetSize);
WriteLine($" done, testing...");

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

در پایان تولید آرایه‌ها، اتمام تولید و ایجاد آرایه‌ها را با چاپ در خروجی اعلام میکنیم.

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

var naiveTimesMs = new List<long>();
var hwTimesMs = new List<long>();

سپس با ایجاد حلقه ای از 0 تا 3 که در کل 3 مرتبه اجرا می‌شود عملیات را تکرار و زمان آن را ثبت می‌کنیم. 

for (var i = 0; i < 3; i++)

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

stopwatch.Restart();
var result = IntSimdProcessor.NaiveSumFunc(intsOne, intsTwo);
var naiveTimeMs = stopwatch.ElapsedMilliseconds;
naiveTimesMs.Add(naiveTimeMs);
WriteLine($"Naive analysis took:                {naiveTimeMs}ms (last value = {result.Last()}).");

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

stopwatch.Restart();
result = IntSimdProcessor.HWAcceleratedSumFunc(intsOne, intsTwo);
var hwTimeMs = stopwatch.ElapsedMilliseconds;
hwTimesMs.Add(hwTimeMs);
WriteLine($"Hareware accelerated analysis took: {hwTimeMs}ms (last value = {result.Last()}).");

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

WriteLine($"Naive method average time:          {naiveTimesMs.Average():.##}");
WriteLine($"HW accelerated method average time: {hwTimesMs.Average():.##}");
WriteLine($"Hardware speedup:                   {naiveTimesMs.Average() / hwTimesMs.Average():P}%");

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

برای اجرای تمامی آزمایش‌ها، کلیه توابع نوشته شده در کلاس PerformanceTests را در کلاس Program و در تابع Main که تابع شروع کننده برنامه می‌باشد، پس از بررسی وضعیت نوع‌های داده‌ای قرار می‌دهیم.

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

این جدول بر اساس یک بار اجرای برنامه در سیستم من ترسیم شده است و اجرای برنامه در سیستم‌های مختلف خروجی‌های متفاوتی را دارد. لازم به ذکر است که اندازه آرایه‌ها بسیار بزرگ است و این نتایج با آرایه‌هایی به طول بیش از هزاران هزار عنصر می‌باشد.

زمان‌ها در جدول به میلی ثانیه می‌باشد.

ردیف

عملیات

دور اول

دور دوم

دور سوم

میانگین حالت ساده

میانگین حالت SIMD

درحالت ساده

درحالت SIMD

درحالت ساده

درحالت SIMD

درحالت ساده

درحالت SIMD

1

جمع دو آرایه با استفاده از یک آرایه کمکی در اعداد صحیح

157

131

128

131

128

138

137.67

133.33

2

جمع دو آرایه بدون استفاده از آرایه کمکی در اعداد float

122

133

99

99

99

93

106.67

108.33

3

جمع دو آرایه بدون استفاده از آرایه کمکی در اعداد صحیح

83

73

86

88

78

81

82.33

80.67

4

جمع دو آرایه اول ورودی - مجموع در آرایه سوم ریخته می‌شود - در اعداد صحیح کوچک بدون علامت

58

63

50

48

58

46

55.33

52.33

5

جمع دو آرایه بدون استفاده از آرایه کمکی در اعداد صحیح کوچک بدون علامت

55

40

53

36

53

46

53.67

40.67

6

بدست آوردن کمترین و بیشترین مقدار در یک آرایه اعداد صحیح

91

36

91

39

90.67

38

90.66

38

7

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

90

20

89

19

88

18

89

19

8

جمع عناصر آرایه ورودی و ذخیره مجموع آنها در یک متغیر کمکی

33

309

32

263

31

291

32

287.67

9

جمع عناصر آرایه ورودی و ذخیره مجموع آنها در یک متغیر کمکی بدون بررسی سرریز

30

13

29

13

30

12

29.67

12.67

10

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

89

50

90

51

90

49

89.57

50



سورس کامل برنامه را که شامل تغییراتی در توابع برای بهبود و اضافه شدن کامنت برای فهم بیشتر کدها می‌باشد، در زیر می‌توانید دریافت کنید: 
   TestSIMD.zip  

مطالب
ASP.NET MVC #16

مدیریت خطاها در یک برنامه ASP.NET MVC


استفاده از فیلتر HandleError

یکی از فیلترهای توکار ASP.NET MVC به نام HandleError،‌ می‌تواند کار هدایت کاربر را به یک صفحه‌ی خطای عمومی، در حین بروز استثنایی در برنامه،‌ انجام دهد. برای آزمایش آن یک برنامه خالی جدید ASP.NET MVC را آغاز کنید. سپس یک کنترلر جدید را با محتوای زیر به آن اضافه نمائید:

using System;
using System.Web.Mvc;

namespace MvcApplication13.Controllers
{
public class HomeController : Controller
{
[HandleError]
public ActionResult Index()
{
throw new InvalidOperationException();
return View();
}
}
}

در اینجا جهت آزمایش برنامه، به عمد یک استثنای دستی را صادر می‌کنیم. برای آزمایش برنامه هم نیاز است آن‌را خارج از دیباگر VS.NET اجرا کرد (آدرس برنامه را مستقیما خارج از VS.NET در یک مرورگر وارد کنید). همچنین یک سطر زیر را نیز لازم است به فایل web.config برنامه اضافه نمائید:

<system.web>
<customErrors mode="On" />

اکنون اگر برنامه را خارج از مرورگر اجرا کنید، با توجه به استفاده از ویژگی HandleError و همچنین بروز یک استثنا در متد Index، خودبخود صفحه Views\Shared\Error.cshtml به کاربر نمایش داده خواهد شد. در غیراینصورت صفحه زرد رنگ پیش فرض خطای ASP.NET به کاربر نمایش داده می‌شود که محتوای آن‌ها بیشتر برای برنامه نویس‌ها مناسب است و نه کاربران نهایی سیستم.
اگر علاقمند باشید که این ویژگی به صورت خودکار به تمام متدهای کنترلرهای برنامه اعمال شود، کافی است یک سطر زیر را به متد Application_Start فایل Global.asax.cs اضافه نمائید:

GlobalFilters.Filters.Add(new HandleErrorAttribute());

البته نیازی به انجام اینکار نیست زیرا اگر به متد RegisterGlobalFilters فایل Global.asax.cs دقت کنیم، اینکار پیشتر توسط قالب پیش فرض VS.NET انجام شده است. فقط برای فعال سازی آن نیاز است تگ customErrors در فایل وب کانفیگ برنامه مقدار دهی و تنظیم شود.



استفاده از صفحه خطای سفارشی دیگری بجای فایل Error.cshtml

امکان تنظیم نمایش صفحه خطای سفارشی دیگری نیز وجود دارد. برای مثال استفاده از فایل Views\Shared\CustomErrorView.cshtml :

[HandleError(View = "CustomErrorView")]



استفاده از صفحات خطای متفاوت به ازای استثناهای مختلف

می‌توان فیلتر HandleError را تنها به یک نوع استثنای خاص محدود کرد. همچنین امکان استفاده از چندین ویژگی HandleError برای یک متد نیز وجود دارد:

[HandleError(ExceptionType = typeof(NullReferenceException), View = "ErrorHandling")]



دسترسی به اطلاعات استثناء در صفحه نمایش خطاها

زمانیکه برنامه به صفحه خطا هدایت می‌شود، نوع Model آن System.Web.Mvc.HandleErrorInfo می‌باشد:

@model System.Web.Mvc.HandleErrorInfo

@{
ViewBag.Title = "DbError";
}

<h2>An Error Has Occurred</h2>

@if (Model != null)
{
<p>@Model.Exception.GetType().Name<br />
thrown in @Model.ControllerName @Model.ActionName</p>
}

البته این نکته را صرفا به عنوان اطلاعات عمومی در نظر داشته باشید. زیرا اگر قرار باشد مجددا اصل استثناء را نمایش دهیم، همان صفحه زرد رنگ ASP.NET شاید بهتر باشد.



استفاده از تگ customErrors در فایل Web.config برنامه

ویژگی حالت تگ customErrors در فایل web.config برنامه، سه مقدار را می‌تواند بپذیرد:
الف) Off : صفحه زرد رنگ معرفی خطای ASP.NET را به همراه تمام اطلاعات مرتبط با استثنای رخ داده نمایش می‌دهد.
ب) RemoteOnly : همان حالت الف است با این تفاوت که صفحه خطا را فقط در کامپیوتری که وب سرور بر روی آن نصب است نمایش خواهد داد.
ج) On : یک صفحه خطای سفارشی شده را نمایش می‌دهد.

بنابراین هیچگاه از حالت Off استفاده نکنید. زیرا خطاهای نمایش داده شده، علاوه بر برنامه نویس، برای مهاجم به یک سایت نیز بسیار دلپذیر است!
حالت RemoteOnly در زمان توسعه برنامه توصیه می‌شود.
حالت On حین توزیع برنامه باید بکارگرفته شود.



مدیریت خطاهای رخ داده خارج از MVC Pipeline

HandleErrorAttribute تنها استثناهای رخ داده داخل ASP.NET MVC Pipeline را مدیریت می‌کند (یا خطاهایی از نوع 500). اگر این نوع استثناها خارج از آن رخ دهند مثلا فایلی یافت نشود (خطای 404) و امثال آن، باید به روش زیر عمل کرد:

<customErrors mode="On" defaultRedirect="error">
<error statusCode="404" redirect="error/notfound" />
<error statusCode="403" redirect="error/forbidden" />
</customErrors>

در اینجا اگر فایلی یافت نشد، کاربر به کنترلری به نام error و متدی به نام notfound هدایت خواهد شد. بنابراین نیاز به کنترلر زیر وجود دارد؛ به علاوه به ازای هر متد هم یک View متناظر باید اضافه شود (کلیک راست روی نام متد و انتخاب گزینه افزودن View جدید).

using System.Web.Mvc;

namespace MvcApplication13.Controllers
{
public class ErrorController : Controller
{
public ActionResult Index()
{
return View();
}

public ActionResult NotFound()
{
return View();
}

public ActionResult Forbidden()
{
return View();
}
}
}

برای آزمایش این قسمت، برنامه را اجرا کرده و سپس مثلا آدرس غیرموجود http://localhost/xyz را وارد کنید.



استفاده از فیلتر HandleError اجباری نیست

در همین قسمت قبل پس از افزودن customErrors و defaultRedirect آن که به نام یک کنترلر اشاره می‌کند، کلیه فیلترهای HandleError اضافه شده به برنامه را حذف کنید. سپس برنامه را خارج از محیط VS.NET اجرا کنید. باز هم متد Index کنترلر Error اجرا خواهد شد. به عبارتی الزاما نیازی به استفاده از فیلتر HandleError نیست و به کمک مقدار دهی صحیح تگ customErrors، کار نمایش خودکار صفحه سفارشی خطاها به کاربر انجام خواهد شد.
البته بدیهی است که گزینه‌های نمایش یک View خاص به ازای استثنایی ویژه، یکی از مزیت‌های استفاده از فیلتر HandleError می‌باشد که امکان تنظیم آن در فایل web.config وجود ندارد.



ثبت اطلاعات استثناهای رخ داده به کمک ELMAH

نمایش صفحه‌ی خطای سفارشی به کاربر، یکی از موارد ضروری تمام برنامه‌های ASP.NET است، اما کافی نیست. ثبت اطلاعات جزئیات استثناهای رخ داده در طول زمان می‌توانند به بالا بردن کیفیت برنامه به شدت کمک کنند. برای این منظور می‌توان همانند سابق از متد Application_Error قابل تعریف در فایل Global.asax.cs کمک گرفت؛ اما با وجود افزونه‌ای به نام ELMAH اینکار اتلاف وقت است و اصلا توصیه نمی‌شود. همچنین به کمک ELMAH می‌توان مشکلات را تبدیل به ایمیل‌های خودکار کرد یا از آن‌ها فید RSS درست نمود.
برای دریافت ELMAH یا به سایت اصلی آن مراجعه نمائید و یا به کمک NuGet هم به سادگی قابل دریافت است. پس از دریافت، ارجاعی را به اسمبلی آن (Elmah.dll) اضافه نمائید. در ادامه فایل web.config برنامه را گشوده و چند سطر زیر را به آن در قسمت configuration اضافه کنید:

<configuration>
<configSections>
<sectionGroup name="elmah">
<section name="security" requirePermission="false" type="Elmah.SecuritySectionHandler, Elmah"/>
<section name="errorLog" requirePermission="false" type="Elmah.ErrorLogSectionHandler, Elmah"/>
<section name="errorMail" requirePermission="false" type="Elmah.ErrorMailSectionHandler, Elmah"/>
<section name="errorFilter" requirePermission="false" type="Elmah.ErrorFilterSectionHandler, Elmah"/>
<section name="errorTweet" requirePermission="false" type="Elmah.ErrorTweetSectionHandler, Elmah"/>
</sectionGroup>
</configSections>

سپس ذیل قسمت appSettings، تنظیمات پروایدر ذخیره سازی اطلاعات آن‌را وارد نمائید. مثلا در اینجا از فایل‌های XML برای ذخیره سازی اطلاعات استفاده خواهد شد (که امن‌ترین حالت ممکن است؛ از این لحاظ که اگر بانک اطلاعاتی را انتخاب کنید، ممکن است مشکل اصلی از همانجا ناشی شده باشد. بنابراین خطایی ثبت نخواهد شد. همچنین در این حالت نیازی به سایر DLLهای همراه ELMAH هم نیست). در اینجا مسیر ذخیره سازی اطلاعات در پوشه app_data/errorslog تنظیم شده است:

<elmah>
<security allowRemoteAccess="1"/>
<errorLog type="Elmah.XmlFileErrorLog, Elmah" logPath="~/App_Data/ErrorsLog"/>
</elmah>

در ادامه در قسمت system.web، دو تعریف زیر را اضافه نمائید. به این ترتیب امکان دسترسی به آدرس http://server/elmah.axd مهیا می‌گردد:

<httpModules>
<add name="ErrorLog" type="Elmah.ErrorLogModule, Elmah"/>
</httpModules>
<httpHandlers>
<add verb="POST,GET,HEAD" path="elmah.axd" type="Elmah.ErrorLogPageFactory, Elmah"/>
</httpHandlers>

البته برای IIS7 تنظیمات ذیل نیز باید اضافه شوند:

<system.webServer>
<validation validateIntegratedModeConfiguration="false"/>
<modules runAllManagedModulesForAllRequests="true">
<add name="ErrorLog" type="Elmah.ErrorLogModule, Elmah"/>
</modules>
<handlers>
<add name="Elmah" verb="POST,GET,HEAD" path="elmah.axd" type="Elmah.ErrorLogPageFactory, Elmah"/>
</handlers>
</system.webServer>

و به این ترتیب تنظیمات اولیه ELMAH به پایان می‌رسد (و با ASP.NET Web forms هیچ تفاوتی ندارد).
مرحله بعد، تنظیمات مسیریابی ASP.NET MVC است برای اینکه آدرس http://server/elmah.axd را وارد سیستم پردازشی خود نکند. البته اینکار پیشتر انجام شده است:

public static void RegisterRoutes(RouteCollection routes)
{
//routes.IgnoreRoute("elmah.axd");
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

بنابراین همین تنظیمات، به همراه قالب پیش فرض یک پروژه جدید ASP.NET MVC برای استفاده از ELMAH کفایت می‌کند. اکنون پروژه جاری را یکبار دیگر خارج از VS.NET اجرا کرده و سپس به مسیر http://localhost/elmah.axd جهت مشاهده خطاهای لاگ شده به همراه جزئیات کامل آن‌ها مراجعه کنید.

مشکل: استثناهای برنامه توسط ELMAH لاگ نمی‌شوند!

فیلتر HandleError با ELMAH سازگار نیست. زیرا با استفاده از آن، متدهای کنترلرها به صورت خودکار داخل یک try/catch اجرا شده و به این ترتیب استثناهای رخ داده، مدیریت گردیده و به ELMAH هدایت نمی‌شوند. بنابراین نیاز است به متد RegisterGlobalFilters فایل Global.asax.cs مراجعه کرده و سطر زیر را حذف کنید:

filters.Add(new HandleErrorAttribute());

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

using System.Web.Mvc;
using Elmah;

namespace MvcApplication13.CustomFilters
{
public class ElmahHandledErrorLoggerFilter : IExceptionFilter
{
public void OnException(ExceptionContext context)
{
if (context.ExceptionHandled)
ErrorSignal.FromCurrentContext().Raise(context.Exception);
// all other exceptions will be caught by ELMAH anyway
}
}
}

در اینجا یک فیلتر سفارشی به برنامه اضافه شده است تا خطاهای مدیریت شده برنامه (خطاهای مدیریت شده توسط فیلتر HandleError توکار) را به موتور ELMAH هدایت کند. سایر خطاهای مدیریت نشده به صورت خودکار توسط ELMAH ثبت خواهند شد و نیازی به انجام کار اضافی در این مورد نیست.
سپس این فیلتر جدید را به صورت سراسری تعریف کنید:

public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new ElmahHandledErrorLoggerFilter());
filters.Add(new HandleErrorAttribute());
}

ترتیب این‌ها هم مهم است. ابتدا باید ElmahHandledErrorLoggerFilter معرفی شود.


تذکر مهم!
حین استفاده از ELMAH یک نکته را فراموش نکنید:
اگر allowRemoteAccess آن‌را به عدد 1 تنظیم کرده‌اید، به هیچ عنوان از نام پیش فرض elmah.axd استفاده نکنید (هر نام اختیاری دیگری را که علاقمند بودید و به سادگی قابل حدس زدن نبود، در فایل web.config وارد کنید).


خلاصه بحث
1- در ASP.NET MVC نیازی نیست تا متدهای کنترلرها را با try/catch شلوغ کنید.
2- حتما قسمت customErrors فایل وب کانفیگ برنامه را دهی کنید (این مورد را به چک لیست اجباری تهیه یک برنامه ASP.NET MVC اضافه کنید).
3- استفاده از فیلتر HandleError اختیاری است. اگر از قابلیت فیلتر کردن استثناهای ویژه آن استفاده نمی‌کنید، مقدار دهی customErrors وب کانفیگ برنامه هم همان کار را انجام می‌دهد.
4- برای ثبت جزئیات دقیق استثناهای رخ داده در برنامه، از ELMAH استفاده کنید و بی‌جهت وقت خودتان را صرف بازنویسی این افزونه ارزشمند نکنید.

مطالب مشابه
معرفی ELMAH
ثبت استثناهای مدیریت شده توسط ELMAH

مطالب
توسعه برنامه های Cross Platform با Xamarin Forms & Bit Framework - قسمت دوم
در قسمت اول در مورد ابزار انتخابی برای توسعه برنامه‌های Cross Platform صحبت کردیم. در این قسمت به آموزش نصب و راه اندازی محیط توسعه می‌پردازیم.

شما می‌توانید در هر یک از سیستم عامل‌های Mac - Windows - Linux و با هر یک از IDE‌های Visual Studio - Visual Studio for mac - Rider کار کنید. برای این که بتوانیم آموزش را کاملا عملی پیش ببریم و وارد جزئیات شویم، در عمل باید یکی را انتخاب و آموزش دهیم و آن Windows - Visual Studio است؛ اگرچه باقی تفاوت خیلی زیادی ندارند.

با توجه به این که کد نوشته شده برای UI و Logic برای هر سه پلتفرم Windows - Android - iOS یکی است و کدهای منحصر به هر پلتفرم، سهم اندکی از پروژه را تشکیل می‌دهد و همچنین تست برنامه برای Windows آسان‌تر و سریع‌تر بوده و امکانات بیشتری را دارد، توصیه اکید می‌کنم برنامه را روی نسخه Windows توسعه دهید و تست کنید و پس از انجام کارهای اصلی پروژه، آن را بر روی Android و iOS نیز تست کنید. این مورد شباهت به برنامه نویسی وب برای مرورگرها را دارد. خیلی از افراد، سایت را بر روی یک مرورگر مثل Chrome یا Firefox توسعه می‌دهند و در نهایت کار را بر روی مرورگرهای موبایل و IE - Edge - Safari و ... تست می‌کنند. همانطور که می‌شود در مرورگر Chrome هم Touch را تست کرد و هم سایزهای مختلف را، همین کارها را در تست نسخه ویندوزی نیز می‌توانید انجام دهید. در کنار این با توجه به رشد فروش تبلت‌های ویندوزی، برای خیلی از برنامه‌ها، ارائه نسخه ویندوزی می‌تواند مفید نیز باشد.

برای شروع بهتر است نسخه‌ای به روز از ویندوز 10 را داشته باشید، یا Pro یا Enterprise. برای بررسی، ابتدا Command Line را باز کنید و دستور ver (مخفف version) را اجرا کنید. چیزی مشابه مقدار
Microsoft Windows Version 10.0.17134.345
را مشاهده خواهید نمود که باید عدد پنج رقمی آن (در این مثال 17134) از 16299 کمتر نباشد. اگر فرض کنیم که فقط یک سیستم داریم که بدون سیستم عامل است، تا این جا یک ISO نصب ویندوز دانلود کرده‌ایم، به حجم 3.7 گیگ که بعد از نصب، 9.5 گیگ از فضای هارد را می‌گیرد. کمی حوصله به خرج دهید (!) و اگر می‌خواهید همه چیز را تمیز انجام دهید، با یک ویندوز تمیز شروع کنید!

آخرین نسخه پایدار ویژوال استودیو در زمان نگارش این مقاله، 2017 - 15.8.7 هست که ما نیاز به نصب Workload های زیر داریم:
Universal Windows Platform development 
Mobile development with .NET 
نصب این دو احتیاج به 5 گیگ دانلود و 14 گیگ فضای روی هارد را دارد که علاوه بر خود Visual Studio و محیط توسعه آن، موارد زیر را نیز برای شما نصب می‌کند:
 Android SDK - Android NDK - JDK (Java) - Windows SDK - iOS SDK
نکات مهم:
۱- اگر قبلا یکی از SDK‌های ذکر شده را دانلود کرده‌اید، لطفا بی خیال آن شوید! اجازه دهید تا ویژوال استودیو همه چیز را دانلود و نصب و کانفیگ کند. Android SDK، برای مثال، بالغ بر 70 گیگ فایل، برای 28 ورژن اندروید است که اگر یکی از آن‌ها را داشته‌اید که برای تست کد نویسی با Java و Android Studio جواب می‌داده، هیچ دلیلی ندارد دقیقا همان نسخه به درد Xamarin هم بخورد!
۲- ترجیحا نسخه Enterprise را نصب کنید.
۳- قسمت‌های عمده فایل‌های دانلودی از سرورهای مایکروسافت دانلود می‌شوند که محدودیتی برای کاربران ایرانی ندارد، ولی قسمت هایی نیز مستقیما از سرورهای گوگل دانلود می‌شوند که متاسفانه روی کاربرهای ایرانی بسته است. با توجه به این که ممکن است استفاده از روش‌های دور زدن تحریم مانند VPN باعث کندی سرعت اینترنت و دانلود شوند، توصیه می‌کنم که ابتدا "Universal Windows Platform development" را نصب کنید (زیرا تماما از سرورهای مایکروسافت دانلود می‌شود) و سپس مجدد Installer را باز کرده و "Mobile development with .NET" را انتخاب کنید و این بار از ابزارهای دور زدن تحریم استفاده کنید.
۴- در سمت راست گزینه‌های قابل نصب، تیک موارد "Google Android Emulator API Level 27" و "Intel Hardware Accelerated Execution Manager (HAXM) global install" را بردارید. در پستی جداگانه آپشن‌های متنوع Emulator‌های اندرویدی را بررسی خواهیم نمود.
۵- بهتر است Administrator سیستم خود باشید.

بعد از اتمام نصب باید Developer mode را فعال کنید که نحوه انجام آن در این لینک شرح داده شده است. به صورت خلاصه به Settings بروید، سپس Update & Security، سپس For developers و در نهایت انتخاب Developer mode از بین گزینه‌های موجود.
ضمن استفاده از ابزارهای دور زدن تحریم (فقط برای ساختن و بیلد کردن اولین پروژه)، ویژوال استودیو را به صورت Run as admin باز کنید و از منوی File > New > Project قسمت Cross-Platform برای CSharp، یک Mobile app Xamarin Forms بسازید که ضمن انگلیسی بودن نام پروژه و فاقد Space بودن آن، ترجیحا در فولدری باشد که مسیر آن فولدر نیز طولانی نباشد، Space و کارکترهای فارسی نیز نداشته باشد.

تنظیماتی که در پنجره New Cross Platform App هستند مناسب بوده و Ok را بزنید! اولین بیلد به علت نیاز به دانلود طول می‌کشد و در صورت بیلد شدن موفقیت آمیز پروژه شما دومین قسمت را با موفقیت طی کرده اید. در قسمت بعدی ساختار پروژه‌های Xamarin Forms را بررسی می‌کنیم و یک مثال ساده می‌نویسیم که لااقل روی ویندوز قابلیت تست را داشته باشد. دقت کنید که همان کد روی Android / iOS نیز کار می‌کند، ولی در پست هایی جداگانه باید در مورد راه اندازی Emulator‌های Android و iOS آموزش هایی را ببینید. در صورت وجود هر گونه مشکل یا سوال نیز در قسمت نظرات همین صفحه در خدمت شما عزیزان هستیم. 
نظرات مطالب
آشنایی با NHibernate - قسمت اول
@DotNetCoders
سورس اصلی کتابخانه، به زبان سی شارپ است اما نهایتا شما از اسمبلی‌های کامپایل شده مربوطه استفاده خواهید کرد و از اینجا به بعد دیگر تفاوتی نمی‌کند که زبان دات نتی مورد استفاده چی باشد.

@dadoo
باید دقت داشته باشید که LINQ به تنهایی فقط یک language feature است و نه یک data access technology . بنابراین باید دقیقا linq to sql یا linq to entities را مشخص کرد.
سابقه نزدیک به یک دهه پروژه اصلی Hibernate‌ که توسط جاوا کارها توسعه داده شده، در این فریم ورک لحاظ شده که از هر لحاظ نسبت به LINQ to entities اون رو پخته‌تر کرده. ضمنا پروایدر LINQ هم برای NH اخیرا توسعه داده شده و از این لحاظ کم و کسری ندارد.
linq to sql برای اس کیوال سرور توسعه داده شد. بعد مایکروسافت اومد اون رو با linq to entities تکمیل کرد (البته linq to sql مطابق وبلاگ رسمی برنامه نویس‌های MS هنوز هم توسعه پیدا می‌کنه و در دات 4 شاهد اون خواهیم بود) و توسط linq to entities امکان استفاده از سایر دیتابیس‌ها هم فراهم شده البته اگر پروایدر آن موجود باشد که تعدادی از آن‌ها هم تجاری هستند. اما با NH این مشکل رو ندارید چون تقریبا همه نوع دیتابیس معروفی را ساپورت می‌کند و رایگان هم هست.
learning curve مربوط به NH بیشتر است از سایر orm ها.
NH از دات نت فریم 2 به بعد را پشتیبانی می‌کند اما linq to entities فقط از دات نت فریم ورک سه و نیم سرویس پک یک به بعد به صورت کامل در دسترس است.

در کل در گوگل nhibernate vs linq را جستجو کنید.

@LoveAjax
محیط مدیوم تراست، امکان ریفلکشن رو حذف می‌کنه و این مورد برای NH و تمام ORM های دیگر نیز مساله ساز خواهد بود. اما برای NH راه حل دارد مطابق مستندات آن:
http://nhforge.org/wikis/howtonh/run-in-medium-trust.aspx