مطالب
ویژگی های کمتر استفاده شده در NET. - بخش هفتم

DebuggerStepThroughAttribute

ویژگی DebuggerStepThroughAttribute باعث می‌شود که در زمان دیباگ کردن کد، با کلید F11، متدهایی که این ویژگی را دارند، بدون رفتن به داخل متد (همانند دیباگ با کلید F10 عمل می‌کند، به جز زمانی که در داخل متد break point گذاشته باشید) ، تنها اجرا می‌شوند.
به مثال زیر توجه کنید:
class Program
{
    public static void Main(string[] args)
    {
        DebuggerStepThroughMethod1();
    }

    [DebuggerStepThrough]
    public static void DebuggerStepThroughMethod1()
    {
        Console.WriteLine( "Method 1" );
        DebuggerStepThroughMethod2();
    }

    [DebuggerStepThrough]
    public static void DebuggerStepThroughMethod2()
    {
        Console.WriteLine( "Method 2" );
    }
}
و نتیجه دیباگ با استفاده از F11 به صورت زیر می‌شود:

همانطور که مشاهده می‌کنید برنامه را با کلید F11 اجرا کردم. بعد از ورود به Method1، با زدن کلید F11 دستور بعدی، break point درون Method2 است.

ConditionalAttribute

شما با استفاده از Conditional می توانید اجرای یک متد را به شناساننده پیش پردازشی ( pre-processing identifier ) وابسته کنید. ConditionalAttribute می‌تواند بر روی یک کلاس یا یک متد بکار برده شود.
class Program
{
    public static void Main(string[] args)
    {
        DebugMode();
    }

    [Conditional("DEBUG")]
    public static void DebugMode()
    {
        Console.WriteLine( "Debug mode" );
    }
}
در صورتی که مثال بالا را در حالت Debug اجرا کنید، خروجی کنسول پیام Debug mode است و در صورتی که در حالت Release اجرا کنید، متد DebugMode اجرا نخواهد شد.
نکته: شما می‌توانید با استفاده از دستور define# (در بیرون از فضای نام) مقدار سفارشی خود را تعریف کنید.
#define ReleaseMode


Flags Enum Attribute

ویژگی Flags برای پوشش فیلدهای بیتی و انجام مقایسه بیتی استفاده می‌شود. از این ویژگی باید برای زمانیکه یک داده شمارشی می‌تواند چندین مقدار را به صورت همزمان داشته باشد، استفاده کرد.
[System.Flags]
public enum Permission
{
    View = 1,
    Insert = 2,
    Update = 4,
    Delete = 8
}
این نکته خیلی مهم است که Flags به صورت خودکار، مقادیر enum را به توان دو نمی‌رساند و شما باید به صورت دستی این مقادیر را تعیین کنید. در صورتیکه مقادیر عددی را تعیین نکنید، enum در عملیات بیتی به درستی کار نخواهد کرد، چرا که مقدار enum از 0 شروع می‌شود و افزایش پیدا می‌کند.  
public static void Main( string[] args )
{
    var permission = ( Permission.View | Permission.Insert ).ToString();
    Console.WriteLine( permission ); // Displays ‘View, Insert’

    var userPermission = Permission.View | Permission.Insert | Permission.Update | Permission.Delete;
    // To retrieve the value from property you can do this
    if ( ( userPermission & Permission.Delete ) == Permission.Delete )
    {
        Console.WriteLine( "کاربر دارای مجوز دسترسی به عملیات حذف می‌باشد" );
    }

    // In .NET 4 and later
    Console.WriteLine( userPermission.HasFlag( Permission.Delete )
                            ? "کاربر دارای مجوز دسترسی به عملیات حذف می‌باشد"
                            : "کاربر مجوز دسترسی به عملیات حذف را ندارد");
}

نکته: در صورتیکه مقداری را برای enum تعریف کرده باشید، نمی‌توانید آن را با مقدار 0 مشخص کنید (در زمانی که ویژگی flags را بر روی enum اضافه کرده باشید)، چرا که با استفاده از عملیات بیتی AND نمی‌توانید دارا بودن آن مقدار را تست کنید و همیشه نتیجه صفر خواهد بود.


Dynamically Compile and Execute C# Code

CodeDOM

با استفاده از CodeDOM می‌توانید یک سورس کد را به صورت یک فایل اسمبلی کامپایل و ذخیره کنید.
public static void Main( string[] args )
{
    var sourceCode = @"class DotNetTips
                        {
                            public void Print()
                            {
                                System.Console.WriteLine("".Net Tips"");
                            }
                        }";
    var compiledAssembly = CompileSourceCodeDom( sourceCode );
    ExecuteFromAssembly( compiledAssembly );
}

static Assembly CompileSourceCodeDom( string sourceCode )
{
    CodeDomProvider csharpCodeProvider = new CSharpCodeProvider();
    var cp = new CompilerParameters
                {
                    GenerateExecutable = false
                };
    cp.ReferencedAssemblies.Add( "System.dll" );
    var cr = csharpCodeProvider.CompileAssemblyFromSource( cp,
                                                            sourceCode );
    return cr.CompiledAssembly;
}
همانطور که در مثال بالا مشاهده می‌کنید، متغیر sourceCode حاوی کد مربوط به یک کلاس #C می‌باشد که یک متد Print در آن تعریف شده است.


Roslyn

سکوی کامپایلر دات نت " Roslyn "،  کامپایلرهای متن باز #C و  VB.NET را به همراه APIهای تجزیه و تحلیل کد ارائه کرده است که با استفاده از این APIها می‌توان ابزارهای آنالیز کد جهت استفاده در ویژوال استودیو را ایجاد کرد.

برای استفاده از Roslyn باید این کتابخانه را نصب کنید

Install-Package Microsoft.CodeAnalysis

حال مثال قبل را با استفاده از Roslyn بازنویسی می‌کنیم:

public static void Main(string[] args)
{
    var sourceCode = @"class DotNetTips
                        {
                            public void Print()
                            {
                                System.Console.WriteLine("".Net Tips"");
                            }
                        }";
    var compiledAssembly = CompileSourceRoslyn( sourceCode );
    ExecuteFromAssembly( compiledAssembly );
}

private static Assembly CompileSourceRoslyn(string sourceCode)
{
    using ( var memoryStream = new MemoryStream() )
    {
        var assemblyFileName = string.Concat( Guid.NewGuid().ToString(),
                                                ".dll" );
        var compilation = CSharpCompilation.Create( assemblyFileName,
                                                    new[]
                                                    {
                                                        CSharpSyntaxTree.ParseText( sourceCode )
                                                    },
                                                    new[]
                                                    {
                                                        MetadataReference.CreateFromFile( typeof( object ).Assembly.Location )
                                                    },
                                                    new CSharpCompilationOptions( OutputKind.DynamicallyLinkedLibrary ) );
        compilation.Emit( memoryStream );
        var assembly = Assembly.Load( memoryStream.GetBuffer() );
        return assembly;
    }
}

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

static void ExecuteFromAssembly( Assembly assembly )
{
    var helloKittyPrinterType = assembly.GetType( "DotNetTips" );
    var printMethod = helloKittyPrinterType.GetMethod( "Print" );
    var kitty = assembly.CreateInstance( "DotNetTips" );
    printMethod.Invoke( kitty,
                        BindingFlags.InvokeMethod,
                        null,
                        null,
                        CultureInfo.CurrentCulture );
}


مطالب
آشنایی با سورس AndroidBreadCrumb

زمانی که سیستم عامل های GUI مثل ویندوز به بازار آمدند، یکی از قسمت‌های گرافیکی آن‌ها AddressBar   نام داشت که مسیر حرکت آن‌ها را در فایل سیستم نشان میداد و در سیستم عامل‌های متنی  CLI با دستور  cd یا pwd انجام می‌شد. بعدها در وب هم همین حرکت با نام BreadCrumb صورت گرفت که به عنوان مثال مسیر رسیدن به صفحه‌ی یک محصول یا یک مقاله را نشان می‌داد. در یک پروژه‌ی اندرویدی نیاز بود تا یک ساختار درختی را پیاده سازی کنم، ولی در برنامه‌های اندروید ایجاد یک درخت، کار هوشمندانه و مطلوبی نیست و روش کار به این صورت است که یک لیست از گروه‌های والد را نمایش داده و با انتخاب هر آیتم لیست به آیتم‌های فرزند تغییر میکند. حالا مسئله این بود که کاربر باید مسیر حرکت خودش را بشناسد. به همین علت مجبور شدم یک BreadCrumb را برای آن طراحی کنم که در زیر تصویر آن را مشاهده می‌کنید.


 از نکات جالب توجه در مورد این ماژول می‌توان گفت که قابلیت این را دارد تا تصمیمات خود را بر اساس اندازه‌های مختلف صفحه نمایش بگیرد. به عنوان مثال اگر آیتم‌های بالا بیشتر از سه عدد باشد و در صفحه جا نشود از یک مسیر جعلی استفاده می‌کند و همه‌ی آیتم‌ها با اندیس شماره 1 تا index-3 را درون یک آیتم با عنوان (...) قرار می‌دهد که من به آن می‌گویم مسیر جعلی. به عنوان نمونه مسیر تصویر بالا در صفحه جا شده است و نیازی به این کار دیده نشده است. ولی تصویر زیر از آن جا که مسیر، طول width صفحه نمایش رد کرده است، نیاز است تا چنین کاری انجام شود. موقعی‌که کاربر آیتم ... را کلیک کند، مسیر باز شده و به محل index-3 حرکت می‌کند. یعنی دو مرحله به عقب باز می‌گردد.


نگاهی به کارکرد ماژول 

قبل از توضیح در مورد سورس، اجازه دهید نحوه‌ی استفاده از آن را ببینیم.

این سورس شامل دو کلاس است که ساده‌ترین کلاس آن AndBreadCrumbItem می‌باشد که مشابه کلاس ListItem در بخش وب دات نت است و دو مقدار، یکی متن و دیگری Id را می‌گیرد:

سورس:

public class AndBreadCrumbItem {

    private int Id;
    private String diplayText;

    public AndBreadCrumbItem(int Id, String displayText)
    {
        this.Id=Id;
        this.diplayText=displayText;
    }
    public String getDiplayText() {
        return diplayText;
    }
    public void setDiplayText(String diplayText) {
        this.diplayText = diplayText;
    }
    public int getId() {
        return Id;
    }
    public void setId(int id) {
        Id = id;
    }
}

به عنوان مثال می‌خواهیم یک breadcrumb را با مشخصات زیر بسازیم:

AndBreadCrumbItem itemhome=new AndBreadCrumbItem(0,"Home");
AndBreadCrumbItem itemproducts=new AndBreadCrumbItem(12,"Products");
 AndBreadCrumbItem itemdigital=new AndBreadCrumbItem(15,"Digital");
AndBreadCrumbItem itemhdd=new AndBreadCrumbItem(56,"Hard Disk Drive");
حال از کلاس اصلی یعنی AndBreadCrumb استفاده می‌کنیم و آیتم‌ها را به آن اضافه می‌کنیم:
AndBreadCrumb breadCrumb=new AndBreadCrumb(this);

        breadCrumb.AddNewItem(itemhome);
        breadCrumb.AddNewItem(itemproducts);
        breadCrumb.AddNewItem(itemdigital);
        breadCrumb.AddNewItem(itemhdd);
به این نکته دقت داشته باشید که با هر شروع مجدد چرخه‌ی Activity، حتما شیء Context این کلاس را به روز نمایید تا در رسم المان‌ها به مشکل برنخورد. می‌توانید از طریق متد زیر context را مقداردهی نمایید:
breadCumb.setContext(this);
هر چند راه حل پیشنهادی این است که این کلاس را نگهداری ننماید و از یک لیست ایستا جهت نگهداری AndBreadCrumbItem‌ها استفاده کنید تا باهر بار  فراخوانی رویدادهای اولیه چون oncreate یا onstart و.. شی BreadCrumb را پر نمایید.

پس از افزودن آیتم ها، تنظیمات زیر را اعمال نمایید:

        LinearLayout layout=(LinearLayout)getActivity().findViewById(R.id.breadcumblayout);
        layout.setPadding(8, 8, 8, 8);
        breadCrumb.setLayout(layout);
        breadCrumb.SetTinyNextNodeImage(R.drawable.arrow);
        breadCrumb.setTextSize(25);
        breadCrumb.SetViewStyleId(R.drawable.list_item_style);
در سه خط اول، یک layout  از نوع Linear جهت رسم اشیاء به شیء breadcrumb معرفی می‌شود. سپس در صورت تمایل می‌توانید از یک شیء تصویر گرافیکی کوچک هم استفاده کنید که در تصاویر بالا می‌بینید از تصویر یک فلش جهت دار استفاده شده است تا بین هر المان ایجاد شده از آیتم‌ها قرار بگیرد. سپس در صورت تمایل اندازه‌ی قلم متون را مشخص می‌کنید و در آخر هم متد SetViewStyleId هم برای نسبت دادن یک استایل یا selector و ... استفاده می‌شود.
حال برای رسم آن متد UpdatePath را صدا می‌زنیم:
        breadCrumb.UpdatePath();

الان اگر برنامه اجرا شود باید breadcrumb از چپ به راست رسم گردد. برای استفاده‌های فارسی، راست به چپ می‌توانید از متد زیر استفاده کنید:
breadCrumb.setRTL(true);
در صورت هر گونه تغییری در تنظیمات، مجددا متد UpdatePath را فراخوانی کنید تا عملیات رسم، با تنظمیات جدید آغاز گردد.

در صورتیکه قصد دارید تنظیمات بیشتری چون رنگ متن، فونت متن و ... را روی هر المان اعمال کنید، از رویداد زیر استفاده کنید:

breadCrumb.setOnTextViewUpdate(new ITextViewUpdate() {
            @Override
            public TextView UpdateTextView(Context context, TextView tv) {
                tv.setTextColor(...);
                tv.setTypeface(...);
                return tv;
            }
        });
با هر بار ایجاد المان که از نوع TextView است، این رویداد فراخوانی شده و تنظیمات شما را روی آن اجرا می‌کند.
همچنین در صورتیکه می‌خواهید بدانید کاربر بر روی چه عنصری کلیک کرده است، از رویداد زیر استفاده کنید:
breadCumb.setOnClickListener(new IClickListener() {
            @Override
            public void onClick(int position, int Id) {
                  //...
            }
        });
کد بالا دو آرگومان را ارسال میکند که اولی position یا اندیس مکانی عنصر کلیک شده را بر می‌گرداند و دومی id هست که با استفاده ازکلاس AndBreadCrumbItem به آن پاس کرده‌اید. هنگام کلیک کاربر روی عنصر مورد نظر، برگشت به عقب به طور خودکار صورت گرفته و عناصر بعد از آن موقعیت، به طور خودکار حذف خواهند شد.

آخرین متد موجود که کمترین استفاده را دارد، متد SetNoResize است. در صورتیکه این متد با True مقداردهی گردد، عملیات تنظیم بر اساس صفحه‌ی نمایش لغو می‌شود. این متد برای زمانی مناسب است که به عنوان مثال شما از یک HorozinalScrollView استفاده کرده باشید. در این حالت layout شما هیچ گاه به پایان نمی‌رسد و بهتر هست عملیات اضافه را لغو کنید.

نگاهی به سورس

  کلاس زیر شامل بخش‌های زیر است:
فیلدهای خصوصی
 //=-=--=-=-=-=-=-=-=-=-=-=-=- Private Properties -=-=-=-=-=-=-=--=-=-=
    private List<AndBreadCrumbItem> items=null;
    private List<TextView> textViews;
    private int tinyNextNodeImage;
    private int viewStyleId;
    private Context context;
    private boolean RTL;
    private float textSize=20;
    private boolean noResize=false;

    LinearLayout layout;
    IClickListener clickListener;
    ITextViewUpdate textViewUpdate;
    LinearLayout.LayoutParams params ;

با نگاهی به نام آن‌ها میتوان حدس زد که برای چه کاری استفاده می‌شوند. به عنوان نمونه از اصلی‌ترین‌ها، متغیر items جهت نگهداری آیتم‌های پاس شده استفاده می‌شود و textviews هم برای نگهداری هر breadcrumb یا همان المان TextView که روی صفحه رسم می‌شود.
اینترفیس‌ها هم با حرف I شروع و برای تعریف رویدادها ایجاد شده‌اند. در ادامه از تعدادی متد get و Set برای مقدار دهی بعضی از فیلدهای خصوصی بالا استفاده شده است:
    //=-=---=-=-=-=-- Constructor =--=-=-=-=-=--=-=-

    public AndBreadCrumb(Context context)
    {
        this.context=context;
        params = new LinearLayout.LayoutParams
                (LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT);
    }

    //=-=-=--=--=-=-=-=-=-=-=-=-  Public Properties --=-=-=-=-=-=--=-=-=-=-=-=-

    //each category would be added to create path
    public void AddNewItem(AndBreadCrumbItem item)
    {
        if(items==null)
            items=new ArrayList<>();
        items.add(item);
    }

    // if you want a pointer or next node between categories or textviews
    public void SetTinyNextNodeImage(int resId) {this.tinyNextNodeImage=resId;}

    public void SetViewStyleId(int resId) {this.viewStyleId=resId;}

    public void setTextSize(float textSize) {this.textSize = textSize;}

    public boolean isRTL() {
        return RTL;
    }

    public void setRTL(boolean RTL) {
        this.RTL = RTL;
    }

    public void setLayout(LinearLayout layout) {

        this.layout = layout;
    }

    public void setContext(Context context) {
        this.context = context;
    }

    public boolean isNoResize() {
        return noResize;
    }

    public void setNoResize(boolean noResize) {
        this.noResize = noResize;
    }

بعد از آن به متدهای خصوصی می‌رسیم که متد زیر، متد اصلی ما برای ساخت breadcrumb است:
 //primary method for render objects on layout
    private void DrawPath() {


        //stop here if essentail elements aren't present
        if (items == null) return ;
        if (layout == null) return;
        if (items.size() == 0) return;


//we need to get size of layout,so we use the post method to run this thread when ui is ready
        layout.post(new Runnable() {
            @Override
            public void run() {


                //textviews created here one by one
                int position = 0;
                textViews = new ArrayList<>();
                for (AndBreadCrumbItem item : items) {
                    TextView tv = MakeTextView(position, item.getId());
                    tv.setText(item.getDiplayText());
                    textViews.add(tv);
                    position++;
                }


                //add textviews on layout
                AddTextViewsOnLayout();

                //we dont manage resizing anymore
                if(isNoResize()) return;

                //run this code after textviews Added to get widths of them
                TextView last_tv=textViews.get(textViews.size()-1);
                last_tv.post(new Runnable() {
                    @Override
                    public void run() {
                        //define width of each textview depend on screen width
                        BatchSizeOperation();
                    }
                });

            }
        });


    }
متد DrawPath برای ترسیم breadcumb است و می‌توان گفت اصلی‌ترین متد این کلاس است. در سه خط اول، عناصر الزامی را که باید مقداردهی شده باشند، بررسی می‌کند. این موارد وجود آیتم‌ها و layout است. اگر هیچ یک از اینها مقدار دهی نشده باشند، عملیات رسم خاتمه می‌یابد. بعد از آن یک پروسه‌ی UI جدید را در متد post شیء Layout معرفی می‌کنیم. این متد زمانی این پروسه را صدا می‌زند که layout در UI برنامه جا گرفته باشد. دلیل اینکار این است که تا زمانی که ویوها در UI تنظیم نشوند، نمی‌توانند اطلاعاتی چون پهنا و ارتفاع را برگردانند و همیشه مقدار 0 را باز می‌گردانند. پس ما بامتد post اعلام می‌کنیم زمانی این پروسه را اجرا کن که وضعیت UI خود را مشخص کرده‌ای.
به عنوان نمونه کد زیر را ببینید:
TextView tv=new TextView(this);
tv.getWidth(); //return 0
layout.add(tv);
tv.getWidth(); //return 0
در این حالت کنترل در هر صورتی عدد ۰ را به شما باز می‌گرداند و نمی‌توانید اندازه‌ی آن را بگیرید مگر اینکه درخواست یک callback بعد از رسم را داشته باشید که این کار از طریق متد post انجام می‌گیرد:
TextView tv=new TextView(this);
tv.post(new Runnable() {
                    @Override
                    public void run() {
                        tv.getWidth(); //return x
                    }
                });
در اینجا مقدار واقعی x بازگردانده می‌شود.

باز می‌گردیم به متد DrawPath و داخل متد post
 در اولین خط این پروسه به ازای هر آیتم، یک TextView توسط متد MakeTextView ساخته می‌شود که شامل کد زیر است:
  private TextView MakeTextView(final int position, final int Id)
    {
        //settings for cumbs
        TextView tv=new TextView(this.context);
        tv.setEllipsize(TextUtils.TruncateAt.END);
        tv.setSingleLine(true);
        tv.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize);
        tv.setBackgroundResource(viewStyleId);

        /*call custom event - this event will be fired when user click on one of
         textviews and returns position of textview and value that user sat as id
         */
        tv.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

                SetPosition(position);
                clickListener.onClick(position, Id);
            }
        });

        //if user wants to update each textviews
        if(textViewUpdate!=null)
            tv=textViewUpdate.UpdateTextView(context,tv);

        if(isRTL())
            tv.setRotationY(180);

        return tv;
    }

در خطوط اولیه، یک Textview ساخته و متد Ellipsize را با Truncate.END مقداردهی می‌نماید. این مقدار دهی باعث می‌شود اگر متن، در Textview جا نشد، ادامه‌ی آن با ... مشخص شود. در خط بعدی Textview را تک خطه معرفی می‌کنیم. در خط بعدی اندازه‌ی قلم را بر اساس آنچه کاربر مشخص کرده است، تغییر می‌دهیم و بعد هم استایل را برای آن مقداردهی می‌کنیم. بعد از آن رویداد کلیک را برای آن مشخص می‌کنیم تا اگر کاربر بر روی آن کلیک کرد، رویداد اختصاصی خودمان را فراخوانی کنیم.
در خط بعدی اگر rtl با true مقدار دهی شده باشد، textview را حول محور Y چرخش می‌دهد تا برای زبان‌های راست به چپ چون فارسی آماده گردد و در نهایت Textview ساخته شده و به سمت متد DrawPath باز می‌گرداند.

بعد از ساخته شدن TextViewها، وقت آن است که به Layout اضافه شوند که وظیفه‌ی اینکار بر عهده‌ی متد AddTextViewOnLayout است:
 //this method calling by everywhere to needs add textviews on the layout like master method :drawpath
    private void AddTextViewsOnLayout()
    {
        //prepare layout
        //remove everything on layout for recreate it
        layout.removeAllViews();
        layout.setOrientation(LinearLayout.HORIZONTAL);
        layout.setVerticalGravity(Gravity.CENTER_VERTICAL);
        if(isRTL())
            layout.setRotationY(180);



        //add textviews one by one

        int position=0;
        for (TextView tv:textViews)
        {
            layout.addView(tv,params);

            //add next node image between textviews if user defined a next node image
            if(tinyNextNodeImage>0)
                if(position<(textViews.size()-1)) {
                    layout.addView(GetNodeImage(), params);
                    position++;
                }
        }

    }

در چند خط اول، Layout آماده سازی می‌شود. این آماده سازی شامل پاکسازی اولیه Layout یا خالی کردن ویوهای درون آن است که می‌تواند از رندر قبلی باشد. افقی بودن جهت چینش Layout، در مرکز نگاه داشتن ویوها و نهایتا چرخش حول محور Y در صورت true بودن خاصیت RTL است. در خطوط بعدی یک حلقه وجود دارد که Textview‌های ایجاد شده را یک به یک در Layout می‌چیند و اگر کاربر تصویر گرافیکی را هم به (همان فلش‌های اشاره‌گر) متغیر tinyNextNodeImage نسبت داده باشد، آن‌ها را هم بین TextView‌ها می‌چیند و بعد از پایان یافتن کار، مجددا به متد DrawPath باز می‌گردد.
تا به اینجا کار چیدمان به ترتیب انجام شده است ولی از آنجا که اندازه‌ی Layout در هر گوشی و  در دو حالت حالت افقی یا عمودی نگه داشتن گوشی متفاوت است، نمی‌توان به این چینش اعتماد کرد که به چه نحوی عناصر نمایش داده خواهند شد و این مشکل توسط متد BatchSizeOperation (تغییر اندازه دسته جمعی) حل می‌گردد. در اینجا هم باز متد post به آخرین textview اضافه شده است. به این علت که موقعی‌که همه‌ی textview‌ها در ui جا خوش کردند، بتوانیم به خاصیت‌های ui آن‌ها دستیابی داشته باشیم. حالا بعد از ترسیم باید اندازه آن‌ها را اصلاح کنیم. قدم به قدم متد BatchSizeOperation را بررسی می‌کنیم:
//set textview width depend on screen width
private void BatchSizeOperation()
{
//get width of next node between cumbs
Bitmap tinyBmap = BitmapFactory.decodeResource(context.getResources(), tinyNextNodeImage);
int tinysize=tinyBmap.getWidth();
//get sum of nodes
tinysize*=(textViews.size()-1);
...
}
ابتدا لازم است ‍‍‍‍‍طول مسیری که همه ویوها یا المان‌های ما را دارند، به دست آوریم. اول از تصویر کوچک شروع می‌کنیم و پهنای آن را می‌گیریم. سپس عدد به دست آمده را در تعداد آن ضرب می‌کنیم تا جمع پهناها را داشته باشیم. سپس نوبت به TextView‌ها می‌رسد.

  //get width size of screen(layout is screen here)
        int screenWidth=GetLayoutWidthSize();

        //get sum of arrows and cumbs width
        int sumtvs=tinysize;
        for (TextView tv : textViews) {

            int width=tv.getWidth();
            sumtvs += width;
        }
در ادامه‌ی این متد، متد GetLayoutWidthSize را صدا می‌زنیم که وظیفه‌ی آن برگرداندن پهنای layout است و کد آن به شرح زیر است:
    private int GetLayoutWidthSize()
    {
        int width=layout.getWidth();
        int padding=layout.getPaddingLeft()+layout.getPaddingRight();
        width-=padding;
        return width;
    }
در این متد پهنا به احتساب padding‌های چپ و راست به دست می‌آید و مقدار آن را به عنوان اندازه‌ی صفحه نمایش، تحویل متد والد می‌دهد. در ادامه هم پهنای هر Textview محاسبه شده و جمع کل آن‌ها را با اندازه‌ی صفحه مقایسه می‌کند. اگر کوچکتر بود، کار این متد در اینجا تمام می‌شود و نیازی به تغییر اندازه نیست. ولی اگر نبود کد ادامه می‌یابد:
    private void  BatchSizeOperation()
    {
        ....

    //if sum of cumbs is less than screen size the state is good so return same old textviews
        if(sumtvs<screenWidth)
            return ;


        if(textViews.size()>3)
        {
            //make fake path
            MakeFakePath();

            //clear layout and add textviews again
            AddTextViewsOnLayout();
        }

        //get free space without next nodes -> and spilt rest of space to textviews count to get space for each textview
        int freespace =screenWidth-tinysize;
        int each_width=freespace/textViews.size();

        //some elements have less than each_width,so we should leave size them and calculate more space again
        int view_count=0;
        for (TextView tv:textViews)
        {
            if (tv.getWidth()<=each_width)
                freespace=freespace-tv.getWidth();
            else
                view_count++;
        }
        if (view_count==0) return;

        each_width=freespace/view_count;
        for (TextView tv:textViews)
        {
            if (tv.getWidth()>each_width)
                tv.setWidth(each_width);
        }


    }

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

نحوه‌ی کارکرد متد MakeFakePath بدین صورت است که 4 عدد TextView را ایجاد کرده که المان‌های با اندیس 0 و 2 و 3 به صورت نرمال و عادی ایجاد شده و همان کارکرد سابق را دارند. ولی المان شماره دو با اندیس 1 با متن ... نماینده‌ی آیتم‌های میانی است و رویدادکلیک  آن به شکل زیر تحریف یافته است:

 //if elements are so much(mor than 3),we make a fake path to decrease elements
    private void MakeFakePath()
    {
        //we make 4 new elements that index 1 is fake element and has a rest of real path in its heart
        //when user click on it,path would be opened
        textViews=new ArrayList<>(4);
        TextView[] tvs=new TextView[4];
        int[] positions= {0,items.size()-3,items.size()-2,items.size()-1};

        for (int i=0;i<4;i++)
        {
            //request for new textviews
            tvs[i]=MakeTextView(positions[i],items.get(positions[i]).getId());

            if(i!=1)
                tvs[i].setText(items.get(positions[i]).getDiplayText());
            else {
                tvs[i].setText("...");
                //override click event and change it to part of code to open real path by call setposition method and redraw path
                tvs[i].setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        int pos = items.size() - 3;
                        int id = items.get(pos).getId();
                        SetPosition(items.size() - 3);
                        clickListener.onClick(pos, id);
                    }
                });
            }
            textViews.add(tvs[i]);
        }
    }
این رویداد با استفاده از setPosition به آیتم index-3 بازگشته و مجددا المان‌ها رسم می‌گردند و سپس رویداد کلیک این آیتم را هم اجرا می‌کند و المان‌های با اندیس 2 و 3 را به ترتیب به رویدادهای index-1 و index-2 متصل می‌کنیم.
مطالب
رمزنگاری خودکار فیلدهای مخفی در ASP.NET MVC

جهت نگهداری بعضی از اطلاعات در صفحات کاربر، از فیلد‌های مخفی ( Hidden Inputs ) استفاده می‌کنیم. مشکلی که در این روش وجود دارد این است که اگر این اطلاعات مهم باشند (مانند کلیدها) کاربر می‌تواند توسط ابزارهایی این اطلاعات را تغییر دهد و این مورد مسئله‌‌ای خطرناک می‌باشد.

راه حل رفع این مسئله‌ی امنیتی، استفاده از یک Html Helper جهت رمزنگاری این فیلد مخفی در مرورگر کاربر و رمز گشایی آن هنگام Post شدن سمت سرور می‌باشد.

برای رسیدن به این هدف یک Controller Factory   ( Understanding and Extending Controller Factory in MVC  ) سفارشی را جهت دستیابی به مقادیر فرم ارسالی، قبل از استفاده در Action‌ها و به همراه کلاس‌های زیر ایجاد کردیم.

  کلاس EncryptSettingsProvider :  
public interface IEncryptSettingsProvider
    {
        byte[] EncryptionKey { get; }
        string EncryptionPrefix { get; }
    }

 public class EncryptSettingsProvider : IEncryptSettingsProvider
    {
        private readonly string _encryptionPrefix;
        private readonly byte[] _encryptionKey;

        public EncryptSettingsProvider()
        {
            //read settings from configuration
            var useHashingString = ConfigurationManager.AppSettings["UseHashingForEncryption"];
            var useHashing = System.String.Compare(useHashingString, "false", System.StringComparison.OrdinalIgnoreCase) != 0;

            _encryptionPrefix = ConfigurationManager.AppSettings["EncryptionPrefix"];
            if (string.IsNullOrWhiteSpace(_encryptionPrefix))
            {
                _encryptionPrefix = "encryptedHidden_";
            }

            var key = ConfigurationManager.AppSettings["EncryptionKey"];
            if (useHashing)
            {
                var hash = new SHA256Managed();
                _encryptionKey = hash.ComputeHash(Encoding.UTF8.GetBytes(key));
                hash.Clear();
                hash.Dispose();
            }
            else
            {
                _encryptionKey = Encoding.UTF8.GetBytes(key);
            }
        }

        #region ISettingsProvider Members

        public byte[] EncryptionKey
        {
            get
            {
                return _encryptionKey;
            }
        }

        public string EncryptionPrefix
        {
            get { return _encryptionPrefix; }
        }

        #endregion

    }
در این کلاس تنظیمات مربوط به Encryption را بازیابی مینماییم.

EncryptionKey : کلید رمز نگاری میباشد و در فایل Config برنامه ذخیره میباشد.

EncryptionPrefix : پیشوند نام Hidden فیلد‌ها میباشد، این پیشوند برای یافتن Hidden فیلد هایی که رمزنگاری شده اند استفاده میشود. میتوان این فیلد را در فایل Config برنامه ذخیره کرد.

  <appSettings>
    <add key="EncryptionKey" value="asdjahsdkhaksj dkashdkhak sdhkahsdkha kjsdhkasd"/>
  </appSettings>

کلاس RijndaelStringEncrypter :

  public interface IRijndaelStringEncrypter : IDisposable
    {
        string Encrypt(string value);
        string Decrypt(string value);
    }

 public class RijndaelStringEncrypter : IRijndaelStringEncrypter
    {
        private RijndaelManaged _encryptionProvider;
        private ICryptoTransform _cryptoTransform;
        private readonly byte[] _key;
        private readonly byte[] _iv;

        public RijndaelStringEncrypter(IEncryptSettingsProvider settings, string key)
        {
            _encryptionProvider = new RijndaelManaged();
            var keyBytes = Encoding.UTF8.GetBytes(key);
            var derivedbytes = new Rfc2898DeriveBytes(settings.EncryptionKey, keyBytes, 3);
            _key = derivedbytes.GetBytes(_encryptionProvider.KeySize / 8);
            _iv = derivedbytes.GetBytes(_encryptionProvider.BlockSize / 8);
        }

        #region IEncryptString Members

        public string Encrypt(string value)
        {
            var valueBytes = Encoding.UTF8.GetBytes(value);

            if (_cryptoTransform == null)
            {
                _cryptoTransform = _encryptionProvider.CreateEncryptor(_key, _iv);
            }

            var encryptedBytes = _cryptoTransform.TransformFinalBlock(valueBytes, 0, valueBytes.Length);
            var encrypted = Convert.ToBase64String(encryptedBytes);

            return encrypted;
        }

        public string Decrypt(string value)
        {
            var valueBytes = Convert.FromBase64String(value);

            if (_cryptoTransform == null)
            {
                _cryptoTransform = _encryptionProvider.CreateDecryptor(_key, _iv);
            }

            var decryptedBytes = _cryptoTransform.TransformFinalBlock(valueBytes, 0, valueBytes.Length);
            var decrypted = Encoding.UTF8.GetString(decryptedBytes);

            return decrypted;
        }

        #endregion

        #region IDisposable Members

        public void Dispose()
        {
            if (_cryptoTransform != null)
            {
                _cryptoTransform.Dispose();
                _cryptoTransform = null;
            }

            if (_encryptionProvider != null)
            {
                _encryptionProvider.Clear();
                _encryptionProvider.Dispose();
                _encryptionProvider = null;
            }
        }

        #endregion
    }
در این پروژه ، جهت رمزنگاری، از کلاس  RijndaelManaged استفاده میکنیم.
RijndaelManaged :Accesses the managed version of the Rijndael algorithm
Rijndael :Represents the base class from which all implementations of the Rijndael symmetric encryption algorithm must inherit

متغیر key در سازنده کلاس کلیدی جهت رمزنگاری و رمزگشایی میباشد. این کلید می‌تواند AntiForgeryToken تولیدی در View ‌ها و یا کلیدی باشد که در سیستم خودمان ذخیره سازی می‌کنیم.

در این پروژه از کلید سیستم خودمان استفاده میکنیم.

کلاس ActionKey :

 public class ActionKey
    {
        public string Area { get; set; }
        public string Controller { get; set; }
        public string Action { get; set; }
        public string ActionKeyValue { get; set; }
    }

در اینجا هر View که بخواهد از این فیلد رمزنگاری شده استفاده کند بایستی دارای کلیدی در سیستم باشد.مدل متناظر مورد استفاده را مشاهده می‌نمایید. در این مدل، ActionKeyValue کلیدی جهت رمزنگاری این فیلد مخفی میباشد.

کلاس ActionKeyService :

        /// <summary>
        /// پیدا کردن کلید متناظر هر ویو.ایجاد کلید جدید در صورت عدم وجود کلید در سیستم
        /// </summary>
        /// <param name="action"></param>
        /// <param name="controller"></param>
        /// <param name="area"></param>
        /// <returns></returns>
        string GetActionKey(string action, string controller, string area = "");

    }
 public class ActionKeyService : IActionKeyService
    {

        private static readonly IList<ActionKey> ActionKeys;

        static ActionKeyService()
        {
            ActionKeys = new List<ActionKey>
            {
                new ActionKey
                {
                    Area = "",
                    Controller = "Product",
                    Action = "dit",
                    ActionKeyValue = "E702E4C2-A3B9-446A-912F-8DAC6B0444BC",
                }
            };
        }

        /// <summary>
        /// پیدا کردن کلید متناظر هر ویو.ایجاد کلید جدید در صورت عدم وجود کلید در سیستم
        /// </summary>
        /// <param name="action"></param>
        /// <param name="controller"></param>
        /// <param name="area"></param>
        /// <returns></returns>
        public string GetActionKey(string action, string controller, string area = "")
        {
            area = area ?? "";
            var actionKey= ActionKeys.FirstOrDefault(a =>
                a.Action.ToLower() == action.ToLower() &&
                a.Controller.ToLower() == controller.ToLower() &&
                a.Area.ToLower() == area.ToLower());
            return actionKey != null ? actionKey.ActionKeyValue : AddActionKey(action, controller, area);
        }

        /// <summary>
        /// اضافه کردن کلید جدید به سیستم
        /// </summary>
        /// <param name="action"></param>
        /// <param name="controller"></param>
        /// <param name="area"></param>
        /// <returns></returns>
        private string AddActionKey(string action, string controller, string area = "")
        {
            var actionKey = new ActionKey
            {
                Action = action,
                Controller = controller,
                Area = area,
                ActionKeyValue = Guid.NewGuid().ToString()
            };
            ActionKeys.Add(actionKey);
            return actionKey.ActionKeyValue;
        }

    }

جهت بازیابی کلید هر View میباشد. در متد GetActionKey ابتدا بدنبال کلید View درخواستی در منبعی از ActionKey‌ها میگردیم. اگر این کلید یافت نشد کلیدی برای آن ایجاد میکنیم و نیازی به مقدار دهی آن نمیباشد.

کلاس MvcHtmlHelperExtentions :

 public static class MvcHtmlHelperExtentions
    {

        public static string GetActionKey(this System.Web.Routing.RequestContext requestContext)
        {
            IActionKeyService actionKeyService = new ActionKeyService();
            var action = requestContext.RouteData.Values["Action"].ToString();
            var controller = requestContext.RouteData.Values["Controller"].ToString();
            var area = requestContext.RouteData.Values["Area"];
            var actionKeyValue = actionKeyService.GetActionKey(
                            action, controller, area != null ? area.ToString() : null);

            return actionKeyValue;
        }

        public static string GetActionKey(this HtmlHelper helper)
        {
            IActionKeyService actionKeyService = new ActionKeyService();
            var action = helper.ViewContext.RouteData.Values["Action"].ToString();
            var controller = helper.ViewContext.RouteData.Values["Controller"].ToString();
            var area = helper.ViewContext.RouteData.Values["Area"];
            var actionKeyValue = actionKeyService.GetActionKey(
                            action, controller, area != null ? area.ToString() : null);

            return actionKeyValue;
        }

    }
از این متد‌های کمکی جهت بدست آوردن کلید‌ها استفاده میکنیم.

public static string GetActionKey(this System.Web.Routing.RequestContext requestContext)
این متد در DefaultControllerFactory  جهت بدست آوردن کلید  View در زمانیکه میخواهیم اطلاعات را بازیابی کنیم استفاده میشود.

public static string GetActionKey(this HtmlHelper helper)
از این متد در متدهای کمکی درنظر گرفته جهت ایجاد فیلدهای مخفی رمز نگاری شده، استفاده میکنیم.

کلاس InputExtensions :

 public static class InputExtensions
    {
        public static MvcHtmlString EncryptedHidden(this HtmlHelper helper, string name, object value)
        {
            if (value == null)
            {
                value = string.Empty;
            }
            var strValue = value.ToString();
            IEncryptSettingsProvider settings = new EncryptSettingsProvider();
            var encrypter = new RijndaelStringEncrypter(settings, helper.GetActionKey());
            var encryptedValue = encrypter.Encrypt(strValue);
            encrypter.Dispose();

            var encodedValue = helper.Encode(encryptedValue);
            var newName = string.Concat(settings.EncryptionPrefix, name);

            return helper.Hidden(newName, encodedValue);
        }

        public static MvcHtmlString EncryptedHiddenFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression)
        {
            var name = ExpressionHelper.GetExpressionText(expression);
            var metadata = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);
            return EncryptedHidden(htmlHelper, name, metadata.Model);
        }

    }

دو helper برای ایجاد فیلد مخفی رمزنگاری شده ایجاد شده است . در ادامه نحوه استفاده از این دو متد الحاقی را در View‌های برنامه، مشاهده مینمایید. 
   @Html.EncryptedHiddenFor(model => model.Id)
   @Html.EncryptedHidden("Id2","2")
کلاس DecryptingControllerFactory :
    public class DecryptingControllerFactory : DefaultControllerFactory
    {
        private readonly IEncryptSettingsProvider _settings;

        public DecryptingControllerFactory()
        {
            _settings = new EncryptSettingsProvider();
        }

        public override IController CreateController(System.Web.Routing.RequestContext requestContext, string controllerName)
        {
            var parameters = requestContext.HttpContext.Request.Params;
            var encryptedParamKeys = parameters.AllKeys.Where(x => x.StartsWith(_settings.EncryptionPrefix)).ToList();

            IRijndaelStringEncrypter decrypter = null;

            foreach (var key in encryptedParamKeys)
            {
                if (decrypter == null)
                {
                    decrypter = GetDecrypter(requestContext);
                }

                var oldKey = key.Replace(_settings.EncryptionPrefix, string.Empty);
                var oldValue = decrypter.Decrypt(parameters[key]);
                if (requestContext.RouteData.Values[oldKey] != null)
                {
                    if (requestContext.RouteData.Values[oldKey].ToString() != oldValue)
                        throw new ApplicationException("Form values is modified!");
                }
                requestContext.RouteData.Values[oldKey] = oldValue;
            }

            if (decrypter != null)
            {
                decrypter.Dispose();
            }

            return base.CreateController(requestContext, controllerName);
        }

        private IRijndaelStringEncrypter GetDecrypter(System.Web.Routing.RequestContext requestContext)
        {
            var decrypter = new RijndaelStringEncrypter(_settings, requestContext.GetActionKey());
            return decrypter;
        }

    }
از این DefaultControllerFactory جهت رمزگشایی داده‌هایی رمز نگاری شده و بازگرداندن آنها به مقادیر اولیه، در هنگام عملیات PostBack استفاده میشود. 
  این قسمت از کد
  if (requestContext.RouteData.Values[oldKey] != null)
                {
                    if (requestContext.RouteData.Values[oldKey].ToString() != oldValue)
                        throw new ApplicationException("Form values is modified!");
                }
زمانی استفاده میشود که کلید مد نظر ما در UrlParameter‌ها یافت شود و درصورت مغایرت این پارامتر و فیلد مخفی، یک Exception تولید میشود.
همچنین بایستی این Controller Factory را در Application_Start  فایل global.asax.cs برنامه اضافه نماییم.
 protected void Application_Start()
        {
            ....
            ControllerBuilder.Current.SetControllerFactory(typeof(DecryptingControllerFactory));
        }

کد‌های پروژه‌ی جاری
  TestHiddenEncrypt.7z

*در تکمیل این مقاله میتوان SessionId کاربر یا  AntyForgeryToken تولیدی در View را نیز در کلید دخالت داد و در هربار Post شدن اطلاعات این ActionKeyValue مربوط به کاربر جاری را تغییر داد و کلیدها را در بانکهای اطلاعاتی ذخیره نمود.


مراجع:
Automatic Encryption of Secure Form Field Data
Encrypted Hidden Redux : Let's Get Salty
مطالب
تهیه یک Clone از مخزن کدی در گوگل کد

برای مثال پروژه "unhaddins" را در نظر بگیرید. این پروژه یک سری افزونه را جهت کار ساده‌تر با NHibernate ارائه داده است. برای مثال چگونه با WPF یا WCF و امثال آن بتوان به سادگی با NHibernate ارتباط برقرار کرد. این پروژه خروجی قابل دریافتی ندارد؛ به عبارتی یک سری سورس کد است. دریافت یک مخزن کد هم که از گوگل کد در این سمت مشکل است ... اما راه بهتری هم وجود دارد. یکی از خواص کار با سورس کنترل‌ها، امکان تهیه یک clone از یک مخزن کد است. تمام پروژه‌های موجود در گوگل کد هم به این شکل با SVN در دسترس هستند:
http://someproject.googlecode.com/svn/trunk/
که به جای someproject ، نام پروژه مورد نظر قرار خواهد گرفت.

برای نمونه، در سایت https://bitbucket.org ثبت نام کنید. سپس گزینه ایجاد یک مخزن جدید را انتخاب کرده:



و در صفحه‌ی باز شده، گزینه‌ی Import from Subversion را انتخاب کنید:



در اینجا Url خواسته شده باید شبیه به همان آدرس trunk فوق باشد و اگر تیک private فعال باشد (که هست)،‌ دیگران امکان دسترسی به مخزن کد شما را نخواهند داشت. البته این تنظیم پس از دریافت، در برگه‌ی Admin مخزن ایجاد شده نیز قابل تغییر است.

به علاوه سایت github.com هم هر چند بر اساس Git کار می‌کند، اما امکان تهیه یک کپی مطابق اصل از یک مخزن کد SVN را هم دارد؛ به شرح زیر:

یک اکانت رایگان در GitHub درست کنید. بعد یک مخزن خالی جدید را ایجاد کرده و در همان صفحه روی لینک Import a Subversion Repository کلیک کنید و آدرس svn مورد نظر را بدهید.

البته GitHub در دریافت پروژه unhaddins موفق عمل نکرد، اما bitbucket خیلی سریع کل آن‌را دریافت نمود.

مطالب
ASP.NET MVC #23

اجرای برنامه‌های ASP.NET MVC توسط نگارش‌های متفاوت IIS

تا اینجا برای اجرای برنامه‌های ASP.NET MVC از وب سرور توکار VS.NET استفاده شد که صرفا جهت آزمایش برنامه‌ها طراحی شده است. تا این تاریخ سه رده از وب سرورهای مایکروسافت ارائه شده‌اند که برای نصب ASP.NET MVC می‌توانند مورد استفاده قرار گیرند و هر کدام هم نکته‌های خاص خودشان را دارند که در ادامه به بررسی آن‌ها خواهیم پرداخت.


اجرای برنامه‌های ASP.NET MVC بر روی IIS 5.x ویندوز XP

پس از ایجاد یک دایرکتوری مجازی بر روی پوشه یک برنامه ASP.NET MVC و سعی در اجرای برنامه، بلافاصله پیغام خطای HTTP 403 forbidden مشاهده می‌شود.
اولین کاری که برای رفع این مساله باید صورت گیرد، کلیک راست بر روی نام دایرکتوری مجازی در کنسول IIS، انتخاب گزینه خواص و سپس مراجعه به برگه «ASP.NET» آن است. در اینجا شماره نگارش دات نت فریم ورک مورد استفاده را به 4 تغییر دهید (برای نمونه ASP.NET MVC 3.0 مبتنی بر دات نت فریم ورک 4 است).
بعد از این تغییر، بازهم موفق به اجرای برنامه‌های ASP.NET MVC بر روی IIS 5.x نخواهیم شد؛ چون در آن زمان مفاهیم مسیریابی و Routing که اصل و پایه ASP.NET MVC هستند وجود خارجی نداشتند. این نگارش از IIS به صورت پیش فرض تنها قادر به پردازش درخواست‌های رسیده‌ای که به یک فایل فیزیکی بر روی سرور اشاره می‌کند، می‌باشد (یعنی مشکلی با اجرای برنامه‌های ASP.NET Web forms ندارد).
برای رفع این مشکل، مجددا بر روی نام دایرکتوری مجازی برنامه در کنسول IIS کلیک راست کرده و گزینه خواص را انتخاب کنید. در صفحه ظاهر شده، در برگه «Virtual directory» آن، بر روی دکمه «Configuration» کلیک نمائید. در صفحه باز شده مجددا بر روی دکمه «Add» کلیک کنید.
در صفحه باز شده، مسیر Executable را C:\WINDOWS\Microsoft.NET\Framework\v4.0.30319\aspnet_isapi.dll وارد کرده و Extension را به .* (دات هرچی) تنظیم کنید. همین مقدار تنظیم، برای اجرای برنامه‌های ASP.NET MVC بر روی IIS 5.x ویندوز XP کفایت می‌کند.

کاری که در اینجا انجام شده است، نگاشت تمام درخواست‌های رسیده صرفنظر از پسوند فایل‌ها، به موتور ASP.NET می‌باشد. به صورت پیش فرض در IIS 5.x درخواست‌ها تنها بر اساس پسوند فایل‌ها پردازش می‌شوند. مثلا اگر فایل درخواستی aspx است، درخواست رسیده به aspnet_isapi.dll یاد شده هدایت خواهد شد. اگر پسوند فایل php است به isapi مخصوص آن (در صورت نصب) هدایت می‌گردد و به همین ترتیب برای سایر سیستم‌های دیگر. زمانیکه Extension به «دات هرچی» و Executable به aspnet_isapi.dll دات نت 4 تنظیم می‌شود، دایرکتوری مجازی تنظیم شده تنها جهت سرویس دهی به یک برنامه ASP.NET عمل خواهد کرد و تمام درخواست‌های رسیده به آن، به موتور اجرایی ASP.NET هدایت می‌شوند.

بدیهی است تنظیمات فوق تنها به یک دایرکتوری مجازی اعمال شدند. اگر نیاز باشد تا بر روی تمام سایت‌ها تاثیر گذار شود، اینبار در کنسول IIS 5.x بر روی «Default web site» کلیک راست کرده و گزینه خواص را انتخاب کنید. در صفحه باز شده به برگه «Home directory» مراجعه کرده و مراحل ذکر شده را تکرار کنید.

مشکل! این روش بهینه نیست.
روش فوق خوبه، کار می‌کنه، اما بهینه نیست؛ از این جهت که «نگاشت تمام درخواست‌ها به موتور ASP.NET» یعنی پروسه پردازش درخواست یک فایل تصویری، js یا css هم باید از فیلتر موتور ASP.NET عبور کند که ضروری نیست.
برای رفع این مشکل، توصیه شده است که سیستم مسیریابی ASP.NET MVC را در IIS 5.x «پسوند دار» کنید. به این نحو که با مراجعه به فایل Global.asax.cs، تعاریف مسیریابی را به نحو زیر ویرایش کنید:

public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.Add(
new Route("{controller}.aspx/{action}/{id}", new MvcRouteHandler())
{
Defaults = new RouteValueDictionary(new
{
controller = "Home",
action = "Index",
id = UrlParameter.Optional
})
});



اینبار برای مثال مسیر http://localhost/MyMvcApp/home.aspx/index به علت داشتن پسوند aspx وارد موتور پردازشی ASP.NET خواهد شد. البته در این حالت URL های تمیز ASP.NET MVC را از دست خواهیم داد و مدام باید دقت داشت که مسیرهای کنترلرها حتما باید به aspx ختم شوند. ضمنا با این تنظیم، دیگر نیازی به تغییر تعاریف نگاشت‌ها در کنسول مدیریتی IIS، نخواهد بود.


اجرای برنامه‌های ASP.NET MVC بر روی IIS 6.x ویندوز سرور 2003

تمام نکات عنوان شده جهت IIS 5.x در IIS 6.x نیز صادق هستند. به علاوه برای اجرای برنامه‌های ASP.NET بر روی IIS 6.x باید به دو نکته مهم دیگر نیز دقت داشت:
الف) ASP.NET 4 به صورت پیش فرض در IIS 6.x غیرفعال است که باید با مراجعه به قسمت Web Services Extensions در کنسول مدیریتی IIS، آن‌را از حالت prohibited خارج کرد.
ب) در هر Application pool تنها از یک نگارش دات نت فریم ورک می‌توان استفاده کرد. برای مثال اگر هم اکنون AppPool1 مشغول سرویس دهی به یک سایت ASP.NET 3.5 است، از آن نمی‌توانید جهت اجرای برنامه‌های ASP.NET MVC 3 به بعد استفاده کنید. زیرا برای مثال ASP.NET MVC 3 مبتنی بر دات نت فریم ورک 4 است. به همین جهت حتما نیاز است تا یک Application pool مجزا را برای برنامه‌های دات نت 4 در IIS 6 اضافه نمائید و سپس در تنظیمات سایت، از این Application pool جدید استفاده نمائید.
البته روش صحیح و اصولی کار با IIS از نگارش 6 به بعد هم مطابق شرحی است که عنوان شد. برای دستیابی به بهترین کارآیی و امنیت بیشتر، بهتر است به ازای هر سایت، از یک Application pool مجزا استفاده نمائید.

اطلاعات تکمیلی:
نکات نصب برنامه‌های ASP.NET 4.0 بر روی IIS 6
مروری بر تاریخچه محدودیت حافظه مصرفی برنامه‌های ASP.NET در IIS



اجرای برنامه‌های ASP.NET MVC بر روی IIS 7.x ویندوز 7 و ویندوز سرور 2008

اگر برنامه ASP.NET MVC در IIS 7.x در حالت یکپارچه (integrated mode) اجرا شود، بدون نیاز به هیچگونه تغییری در تنظیمات سرور یا برنامه، بدون مشکل قابل اجرا خواهد بود. بدیهی است در اینجا نیز بهتر است به ازای هر برنامه، یک Application pool مجزا را ایجاد کرد.
اما در حالت classic (که برای برنامه‌های جدید توصیه نمی‌شود) نیاز است همان مراحل IIS 5,x تکرار شود. البته اینبار مسیر زیر را باید طی کرد تا به صفحه افزودن نگاشت‌ها رسید:
Right-click on a web site -> Properties -> Home Directory tab -> click on the Configuration button -> Mappings tab



نکته‌ای مهم در تمام نگارش‌های IIS

ترتیب نصب دات نت فریم ورک 4 و IIS مهم است. اگر ابتدا IIS نصب شود و سپس دات نت فریم ورک 4، به صورت خودکار، کار نگاشت اطلاعات ASP.NET به IIS صورت خواهد گرفت.
اگر ابتدا دات نت فریم ورک 4 نصب شود و سپس IIS، برای مثال دیگر از برگه ASP.NET در IIS 6.x خبری نخواهد بود. برای رفع این مشکل دستور زیر را در خط فرمان اجرا کنید:

C:\WINDOWS\Microsoft.NET\Framework\v4.0.30319\aspnet_regiis.exe /i

به این ترتیب، اطلاعات مرتبط با موتور ASP.NET مجددا به تنظیمات IIS اضافه خواهند شد.


مطالب
مسیریابی در Angular - قسمت هفتم - بهبودهای بصری
در این قسمت ویژگی‌های بصری را مانند مشخص سازی مسیر انتخاب شده، در منوی سایت و همچنین نمایش «لطفا منتظر بمانید» را در حین نمایش قسمت‌هایی که با تاخیر از سرور دریافت می‌شوند، پیاده سازی خواهیم کرد.


تزئین مسیر انتخاب شده در منوی سایت

برای بهبود ظاهر برنامه نیاز است منوی سایت را به نحوی تغییر دهیم که مشخص کند، اکنون کاربر کدام گزینه را انتخاب کرده‌است. این مورد شامل سلسه مراتب مسیریابی‌ها نیز می‌شود؛ برای مثال فعالسازی حالت انتخاب شده‌ی منوی سایت، به همراه برگه‌ی انتخاب شده در یکی از Child Routes.
برای پیاده سازی این قابلیت، دایرکتیو ویژه‌ای به نام routerLinkActive تدارک دیده شده‌است. این دایرکتیو را می‌توان به یک anchor tag و یا المان والد آن انتساب داد. مقدار آن‌را نیز می‌توان به یکی از کلاس‌های CSS برنامه مانند کلاس active تعریف شده‌ی در بوت استرپ تنظیم کرد. هر زمانیکه این مسیریابی فعال شود، مسیریاب به صورت خودکار این کلاس را با درج آن، به المان مرتبط اضافه می‌کند و برعکس.
برای نمونه فایل src\app\product\product-edit\product-edit.component.html را گشوده و سپس تغییرات ذیل را اعمال کنید:
<div class="wizard">
            <a [routerLink]="['info']" routerLinkActive="active">
                Basic Information
            </a>
            <a [routerLink]="['tags']" routerLinkActive="active">
                Search Tags
            </a>
</div>
در اینجا دایرکتیو‌های routerLinkActive، به هر کدام از لینک‌های تعریف شده اضافه گردیده‌اند. مقدار active در اینجا، به کلاس active بوت استرپ اشاره می‌کند. یا حتی می‌توان تعدادی کلاس جدا شده‌ی با کاما را نیز در اینجا ذکر کرد.

یک نکته: از آنجائیکه در اینجا مقدار active یک string است و نه یک خاصیت یا عبارت متغیر، به همین جهت نیازی نیست تا این دایرکتیو را به صورت [routerLinkActive] تعریف کنیم.


همانطور که مشاهده می‌کنید، همین دو تنظیم ساده سبب مشخص شدن برگه‌ی انتخابی شده‌اند.

منوی بالای سایت نیز چنین تنظیماتی را نیاز دارد. برای این منظور به فایل src\app\app.component.html که دربرگیرنده‌ی منوی سایت است مراجعه کرده و تغییرات ذیل را اعمال می‌کنیم:
    <ul class="nav navbar-nav">
      <li routerLinkActive="active">
        <a [routerLink]="['/home']">Home</a>
      </li>
      <li routerLinkActive="active">
        <a [routerLink]="['/products']">Product List</a>
      </li>
      <li routerLinkActive="active">
        <a [routerLink]="['/products', 0, 'edit']">Add Product</a>
       </li>      
    </ul>
اینبار routerLinkActive به المان‌های li اعمال شده‌است؛ چون این المان‌های لیست، شیوه نامه‌ی المان‌های anchor را بازنویسی می‌کنند و اگر routerLinkActive را به لینک‌ها اعمال می‌کردیم، تغییری مشاهده نمی‌شد.


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

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


اینبار هر دو گزینه با هم انتخاب شده‌اند. علت اینجا است که این دو مسیر دارای root URL segment یکسانی هستند؛ یا همان products/ در اینجا. به همین جهت routerLinkActive هر دو را به عنوان فعال انتخاب کرده‌است. برای مدیریت میدان دید آن می‌توان از دایرکتیو دیگری به نام routerLinkActiveOptions استفاده کرد:
      <li routerLinkActive="active" [routerLinkActiveOptions]="{ exact: true }">
        <a [routerLink]="['/products']">Product List</a>
      </li>
routerLinkActiveOptions را تنها به ریشه‌ی مسیر products اعمال کرده‌ایم؛ چون این مسیر است که می‌تواند با تمام مسیرهای مشتق شده‌ی از آن نیز تطابق داشته باشد. تنظیم exact: true آن سبب خواهد شد تا تطابق با مسیرهای مشتق شده‌ی از آن ندید گرفته شوند.


اکنون کاربران بهتر می‌توانند درک کنند در کجای برنامه قرار دارند.


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

در ادامه می‌خواهیم اگر برگه‌ای دارای مشکلات اعتبارسنجی بود، آیکن خطایی را در کنار برچسب آن برگه نمایش دهیم. به این ترتیب مدیریت چندین برگه برای کاربران ساده‌تر خواهد شد و به سادگی می‌توانند برگه‌های مشکل دار را پیدا کنند.
در انتهای مطلب «مسیریابی در Angular - قسمت پنجم - تعریف Child Routes» متد isValid را تعریف کردیم. این متد مسیر یک tab را دریافت کرده و اگر اعتبارسنجی آن مشکلی نداشت، مقدار true را بر می‌گرداند. از این متد جهت نمایش آیکن خطای اعتبارسنجی برگه‌ها استفاده خواهیم کرد.
        <div class="wizard">
            <a [routerLink]="['info']" routerLinkActive="active">
                Basic Information
                <span [ngClass]="{'glyphicon glyphicon-exclamation-sign': !isValid('info')}"></span>
            </a>
            <a [routerLink]="['tags']" routerLinkActive="active">
                Search Tags
                <span [ngClass]="{'glyphicon glyphicon-exclamation-sign': !isValid('tags')}"></span>
            </a>
        </div>
در اینجا دو span را تعریف کرده‌ایم که با کمک دایرکتیو ngClass سبب نمایش آیکن exclamation-sign در صورت وجود یک خطای اعتبارسنجی می‌شوند. به عبارتی اگر برگه‌ای معتبر نباشد، سبب درج کلاس آن در span جاری می‌شود:



رخ‌دادهای مسیریابی

هر زمانیکه کاربری مسیرهای مختلف برنامه را پیمایش می‌کند، مسیریاب تعدادی رخ‌داد را نیز تولید خواهد کرد. از این رخ‌دادها جهت تحت نظر قرار دادن، عیب‌یابی و یا اجرای منطقی می‌توان استفاده کرد. این رخ‌دادها شامل موارد ذیل هستند:
- NavigationStart، با آغاز پیمایش یک مسیر رخ می‌دهد.
- RoutesRecognized، با تشخیص و تطابق یک مسیر، با یکی از المان‌های تعریف شده‌ی در تنظیمات مسیریابی رخ می‌دهد.
- NavigationEnd، با پایان پیمایش یک مسیر رخ می‌دهد.
- NavigationCancel، در صورت لغو پیمایش یک مسیریابی توسط محافظ‌های مسیرها و یا هدایت به یک جهت دیگر رخ می‌دهد.
- NavigationError، با شکست پیمایش یک مسیر رخ می‌دهد.

این رخ‌دادها با فعالسازی تنظیم enableTracing تنظیمات مسیریابی به true فعال می‌شوند. برای این منظور فایل src\app\app-routing.module.ts را گشوده و به نحو ذیل تغییر دهید:
@NgModule({
  imports: [RouterModule.forRoot(routes/*, { useHash: true }*/, { enableTracing: true })],
پس از این تغییر، اگر به developer tools مرورگر دقت کنید، یک چنین خروجی را می‌توان مشاهده کرد:


در اینجا ترتیب اجرای رخ‌دادهای متفاوت پیمایش مسیر نمایش لیست محصولات را مشاهده می‌کنید.
- Router به هر مسیر، یک id خود افزایش یابنده را به صورت خودکار نسبت می‌دهد. برای نمونه، این مسیر خاص، id:2 را یافته‌است. از این id می‌توان برای دسترسی به مجموعه‌ای از رخ‌دادها استفاده کرد.
- در این خروجی، url همان آدرس اصلی مسیر است و urlAfterRedirects به معنای مسیری است که پس از تنظیم redirect در تنظیمات مسیریابی (در صورت وجود) حاصل شده‌است.
- یکی از روش‌هایی که برای دیباگ مسیریابی‌ها می‌توان استفاده کرد، همین فعالسازی enableTracing است.


کار با رخ‌دادهای مسیریابی با کدنویسی

به رخ‌دادهایی که در کنسول developer tools مرورگر مشاهده کردید، با کدنویسی نیز می‌توان دسترسی یافت. برای مثال می‌توان یک تصویر چرخنده یا لطفا منتظر بمانید را در آغاز پیمایش یک مسیریابی نمایش داد و سپس در پایان پیمایش این مسیریابی، آن‌را مخفی کرد. این events نیز از نوع Observable بوده و برای کار با آن‌ها باید مشترکشان شد:
this.router.events.subscribe((routerEvent: Event) => {
    if (routerEvent instanceof NavigationStart) {
      //...
    }
});
شیء router به همراه خاصیت events است که با گوش فرادادن به رخ‌دادهای صادر شده‌ی توسط آن می‌توان دریافت چه نوع رخ‌دادی هم اکنون صادر شده‌است.

در مثال جاری این سری، در «مسیریابی در Angular - قسمت چهارم - پیش واکشی اطلاعات»، سبب شدیم تا کل اطلاعات مورد نیاز یک مسیر، پیش از نمایش آن از سرور دریافت شوند تا به این صورت ابتدا یک قاب خالی نمایش داده نشده و پس از مدتی تکمیل شود. هرچند تجربه‌ی کاربری این روش بهتر از روش قبلی است، اما هنوز هم کاربر تاخیری را در ابتدا حس خواهد کرد (به اندازه‌ی زمان delay تنظیم شده)، بدون اینکه راهنمایی به او ارائه شود. در این حالت بهتر است در ابتدای کار، یک تصویر چرخنده نمایش داده شود تا کاربر متوجه شود، نیاز است اندکی منتظر بماند.
در اینجا می‌خواهیم این تصویر چرخنده برای تمام مسیرهای برنامه فعال شود. به همین جهت گوش فرادادن به رخ‌دادها را در نقطه‌ی آغازین برنامه و یا همان src\app\app.component.ts انجام می‌دهیم:
import { Router, Event, NavigationStart, NavigationEnd, NavigationError, NavigationCancel } from '@angular/router';

import { AuthService } from './user/auth.service';
import { Component } from '@angular/core';


@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  pageTitle: string = 'Routing Lab';

  loading: boolean = true;

  constructor(private authService: AuthService,
    private router: Router) {
    router.events.subscribe((routerEvent: Event) => {
      this.checkRouterEvent(routerEvent);
    });
  }

  checkRouterEvent(routerEvent: Event): void {
    if (routerEvent instanceof NavigationStart) {
      this.loading = true;
    }

    if (routerEvent instanceof NavigationEnd ||
      routerEvent instanceof NavigationCancel ||
      routerEvent instanceof NavigationError) {
      this.loading = false;
    }
  }

  logOut(): void {
    this.authService.logout();
    this.router.navigateByUrl('/welcome');
  }
}
کدهای کامل AppComponent را جهت گوش فرادادن به رخ‌دادهای شروع و یا خاتمه/لغو/شکست پیمایش یک مسیریابی، در اینجا مشاهده می‌کنید.
- ابتدا وابستگی‌های لازم آن import شده‌اند.
- سپس می‌خواهیم خاصیت عمومی loading را در شروع به پیمایش یک مسیر، به true تنظیم کنیم و اگر این پیمایش به هر نحوی خاتمه یافت، آن‌را false خواهیم کرد.

اکنون برای استفاده‌ی از این خاصیت عمومی و نمایش تصویر چرخنده، نیاز است قالب src\app\app.component.html را ویرایش کنیم:
<span class="glyphicon glyphicon-refresh glyphicon-spin spinner" *ngIf="loading"></span>
با افزودن span فوق به ابتدای فایل app.component.html به تغییرات خاصیت loading واکنش نشان خواهیم داد. کلاس‌های CSS ایی را که در اینجا اضافه شده‌اند، به فایل src\styles.css اضافه می‌کنیم:
/* Spinner */
.spinner {
  font-size:300%;
  position:absolute;
  top: 50%;
  left: 50%;
  z-index:10
}

.glyphicon-spin {
    -webkit-animation: spin 1000ms infinite linear;
    animation: spin 1000ms infinite linear;
}
@-webkit-keyframes spin {
    0% {
        -webkit-transform: rotate(0deg);
        transform: rotate(0deg);
    }
    100% {
        -webkit-transform: rotate(359deg);
        transform: rotate(359deg);
    }
}
@keyframes spin {
    0% {
        -webkit-transform: rotate(0deg);
        transform: rotate(0deg);
    }
    100% {
        -webkit-transform: rotate(359deg);
        transform: rotate(359deg);
    }
}


اکنون مسیرهایی که دارای route resolver هستند (مانند نمایش جزئیات/ویرایش یک محصول)، به همراه یک spinner نمایش داده خواهند شد و سایر مسیرها ابتدا نمایش داده خواهند شد و سپس اطلاعات آن‌ها از سرور دریافت می‌شود (مانند مسیر نمایش لیست محصولات که دارای route resolver نیست).
البته می‌توان این true/false کردن loading را به ابتدا و انتهای کار یک Observable، مانند حالت نمایش لیست محصولات نیز منتقل کرد. اما در این حالت باید span مرتبط را نیز به قالب همان کامپوننت انتقال داد و دیگر سراسری نخواهد بود.


کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید: angular-routing-lab-06.zip
برای اجرای آن فرض بر این است که پیشتر Angular CLI را نصب کرده‌اید. سپس از طریق خط فرمان به ریشه‌ی پروژه وارد شده و دستور npm install را صادر کنید تا وابستگی‌های آن دریافت و نصب شوند. در آخر با اجرای دستور ng s -o برنامه ساخته شده و در مرورگر پیش فرض سیستم نمایش داده خواهد شد.
مطالب
متد جدید Order در دات نت 7
دات نت 7 به همراه دو متد جدید Order و OrderDescending است که مرتب سازی مجموعه‌های ساده را انجام می‌دهند.


روش متداول مرتب سازی مجموعه‌های ساده تا پیش از دات نت 7

فرض کنید لیستی از اعداد را داریم:
var numbers = new List<int> { -7, 1, 5, -6 };
تا پیش از دات نت 7 با استفاده از متدهای OrderBy و OrderByDescending موجود به همراه LINQ، امکان مرتب سازی صعودی و نزولی این لیست وجود دارد:
var sortedNumbers1 = numbers.OrderBy(n => n);
var sortedNumbers2 = numbers.OrderByDescending(n => n);
که در اینجا ذکر پارامتر keySelector ضروری است:
public static IOrderedEnumerable<TSource> OrderBy<TSource,TKey>(
   [NotNull] this IEnumerable<TSource> source,
   [NotNull] Func<TSource,TKey> keySelector)
هرچند می‌شد طراحی آن ساده‌تر باشد و حداقل برای مجموعه‌های ساده، نیازی به ذکر آن نباشد.


روش جدید مرتب سازی مجموعه‌های ساده در دات نت 7

دات نت 7 به همراه دو متد جدید Order و OrderDescending است که دیگر نیازی به ذکر پارامتر keySelector ذکر شده را ندارند:
var sortedNumbers3 = numbers.Order();
var sortedNumbers4 = numbers.OrderDescending();
و امضای آن‌ها به صورت زیر است:
public static IOrderedEnumerable<T> Order<T>(this IEnumerable<T> source)
public static IOrderedEnumerable<T> OrderDescending<T>(this IEnumerable<T> source)
که در حقیقت دو متد الحاقی جدید قابل اعمال بر روی انواع و اقسام IEnumerableها هستند.


در مورد سایر مجموعه‌های پیچیده چطور؟

فرض کنید کلاس User را:
public class User
{
   public string Name { set; get; }
   public int Age { set; get; }
}
 به همراه لیستی از آن تعریف کرده‌ایم:
List<User> users = new()
                           {
                               new User { Name = "User 1", Age = 34 },
                               new User { Name = "User 2", Age = 24 },
                           };
سؤال: آیا اگر متد Order را بر روی این لیست فراخوانی کنیم:
var orderedUsers = users.Order();
برای مثال این مجموعه بر اساس نام و سن مرتب خواهد شد؟ که پاسخ آن خیر است و همچنین استثنائی را صادر می‌کند بر این مبنا که باید کلاس User، اینترفیس IComparable را پیاده سازی کند تا بتوان آن‌ها را مقایسه کرد؛ برای مثال چیزی شبیه به تغییرات زیر:
public class User : IComparable<User>
{
    public string Name { set; get; }
    public int Age { set; get; }

    public int CompareTo(User? other)
    {
        if (ReferenceEquals(this, other))
        {
            return 0;
        }

        if (ReferenceEquals(null, other))
        {
            return 1;
        }

        var nameComparison = string.Compare(Name, other.Name, StringComparison.Ordinal);
        if (nameComparison != 0)
        {
            return nameComparison;
        }

        return Age.CompareTo(other.Age);
    }
}
که در یک چنین مواردی شاید بهتر باشد از همان متد OrderBy پیشین استفاده کرد که الزامی به پیاده سازی اینترفیس IComparable را ندارد:
var orderedUsers2 = users.OrderBy(user => user.Name).ThenBy(user => user.Age);
مطالب
درباره Iterator methodها و yield return در #C
هنگامیکه می‌خواهید در متدهای خود مقداری (از هر نوع datatype دلخواه) را return نمایید، در حالت عادی قادر خواهید بود که فقط از یک return در بدنه متد خود استفاده نمایید:
 public int Sum(int a, int b)
{
     return a + b;
}
اما چنانچه از متدهای تکرار شونده استفاده نمایید، چطور؟

متدهای تکرار شونده یا Iterator method‌ها ، در داخل یک collection به صورت دلخواه iterate کرده یا به اصلاح پیمایش می‌کنند. این متدها از کلمه کلیدی Yield در هنگام return کردن مقادیر استفاده می‌کنند. (در C# از Yield return و در VB از Yield استفاده می‌شود)  به عبارت دیگر یک متد با خروجی از نوع قابل پیمایش (مانند IEnumerable)، با استفاده از چند yield return، دارای قابلیت پیمایش و بازگرداندن چندین مقدار به جای یک مقدار واحد می‌گردد.

برای درک بهتر مسئله از مثالی برای ادامه توضیحات استفاده می‌کنم. متد پیمایش شونده (Iterate method) زیر را در نظر بگیرید که خروجی IEnumerable دارد:
 public static IEnumerable SomeNumbers()
{
     yield return 3;
     yield return 5;
     yield return 8;
}
برای استفاده از مقادیر بازگشتی متد بالا از حلقه foreach زیر استفاده می‌نماییم:
static void Main()
{
    foreach (int number in SomeNumbers())
    {
        Console.Write(number.ToString() + " ");
    }
    // Output: 3 5 8
    Console.ReadKey();
}
حلقه foreach فوق ، در پایان اولین پیمایش، عدد 3 را باز گردانده و مکان این return را حفظ می‌کند. در چرخه بعدی عدد 5 را باز می‌گرداند و این نقطه را نیز نگه می‌دارد و در چرخه پایانی عدد 8 را برگردانده و سپس حلقه با رسیدن به نقطه پایانی متد، خاتمه می‌یابد.

برای خاتمه پیمایش در Iterator method‌ها ، میتوانید از foreach استفاده کنید و یا اینکه عبارت yield break  را بعد از تمامی yield return‌ها به کار گیرید:
 public static IEnumerable SomeNumbers()
{
   yield return 3;
   yield return 5;
   yield return 8;
   yeild break;
}
نکات:

  - در هنگام ایجاد Iterator method ها، نوع مقادیر خروجی متد ، باید یکی از انواع IEnumerable, IEnumerable<T>, IEnumerator,  و یا IEnumerator<T>. باشد.
 - در هنگام declare کردن ، نمی‌توانید از پارامترهای  ref و out استفاده نمایید.
 - در Anonymous method‌ها (متدهای بی نام) و Unsafe block‌ها نمی‌توانید از yield return (yield در VB ) استفاده نمایید.
 - نمی‌توانید از Yield return در بلوکهای try-catch استفاده کندی. اما می‌توانید در قسمت try بلوک try-finally استفاده نمایید.
 - از yield break  می‌توانید در بلوک try  و یا بلوک catch استفاده نمایید ، اما در بلوک finally خیر.
 - هنگام بروز خطا در foreach هایی که خارج از iterator method‌ها استفاده می‌شوند، بلوک finally داخل این متدها اجرا می‌گردد.

مثالی دیگر با استفاده Iterator method‌ها و yield return جهت بازگرداندن روزهای هفته:
static void Main()
{
  DaysOfTheWeek days = new DaysOfTheWeek();
  foreach (string day in days)
    {
        Console.Write(day + " ");
    }
    // Output: Sun Mon Tue Wed Thu Fri Sat
    Console.ReadKey();
}

public class DaysOfTheWeek : IEnumerable
{
  private string[] days = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };
  public IEnumerator GetEnumerator()
    {
        for (int index = 0; index < days.Length; index++)
        {
            // Yield each day of the week. 
            yield return days[index];
        }
    }
}
منابع:
 yield ، Iterators
مطالب
مشکل ی و ک فارسی و عربی در یک دیتابیس اس کیوال سرور

دیروز به من اطلاع دادند که در یکی از برنامه‌ها دو تا گروه "تاسیسات مکانیکی" پیدا شده!!
تاسیسات مکانیکی
تاسیسات مکانیکی

استاندارد این شرکت، استفاده از kbdfa.dll مخصوص و نسبتا قدیمی است. بنابراین استاندارد مورد استفاده همان ی و ک عربی است. (کاری ندارم خوب است یا بد، یا باید اینطور باشد یا نه، بحث این است که فعلا اینطور است و قرار نیست چیزی عوض بشود!)
در مثال فوق، ی و ک عبارت دوم فارسی است. یعنی نصب kbdfa.dll روی ویندوز تازه نصب شده، فراموش شده بوده.

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

public string SafeFarsiStr(string input)
{
return input.Replace("ی", "ی").Replace("ک", "ک");
}

ب) خوب، الان که این یکسان سازی صورت نگرفته چه باید کرد؟
اسکریپتی را تهیه کرده‌ام (مخصوص SQL Server 2005 به بعد) به صورت زیر که این تبدیل را برای شما انجام می‌دهد.
به صورت خودکار تمامی فیلدهای متنی کلیه جداول دیتابیس جاری شما را یافته و ی و ک آن‌ها را یکسان می‌کند. البته همانطور که عرض شد، مطابق استاندارد این شرکت و استفاده از فایل kbdfa.dll قدیمی مورد استفاده، تمام ی و ک های فارسی به عربی تبدیل می‌شوند.

--اسکریپتی برای یک دست سازی ی و ک در تمامی رکوردهای تمامی جداول دیتابیس جاری
-- اسکریپت زیر ی و ک فارسی را به عربی تبدیل می‌کند
-- در صورت نیاز به حالت عکس ، جای مقادیر عددی یونیکد را تعویض نمائید

USE TestDb;

DECLARE @Table NVARCHAR(MAX),
@Col NVARCHAR(MAX)

DECLARE Table_Cursor CURSOR
FOR
--پیدا کردن تمام فیلدهای متنی تمام جداول دیتابیس جاری
SELECT a.name, --table
b.name --col
FROM sysobjects a,
syscolumns b
WHERE a.id = b.id
AND a.xtype = 'u' --User table
AND (
b.xtype = 99 --ntext
OR b.xtype = 35 -- text
OR b.xtype = 231 --nvarchar
OR b.xtype = 167 --varchar
OR b.xtype = 175 --char
OR b.xtype = 239 --nchar
)

OPEN Table_Cursor FETCH NEXT FROM Table_Cursor INTO @Table,@Col
WHILE (@@FETCH_STATUS = 0)
BEGIN
EXEC (
'update [' + @Table + '] set [' + @Col +
']= REPLACE(REPLACE(CAST([' + @Col +
'] as nvarchar(max)) , NCHAR(1740), NCHAR(1610)),NCHAR(1705),NCHAR(1603)) '
)

FETCH NEXT FROM Table_Cursor INTO @Table,@Col
END CLOSE Table_Cursor DEALLOCATE Table_Cursor

توضیحات و نکاتی در مورد اسکریپت فوق:
الف) برای آشنایی با انواع XType Datatype مورد استفاده در کوئری فوق به این آدرس مراجعه نمائید.
ب) همانطور که در مطالب قبلی این وبلاگ نیز ذکر شد، امکان استفاده از تابع replace بر روی فیلدهای text و ntext وجود ندارد. هیچ اشکالی ندارد! تمام آن‌ها به nvarchar از نوع max دار cast شده و این مشکل به این صورت حل می‌شود.
ج) اس کیوال سرور اجازه تعریف یک جدول یا فیلد را به صورت متغیر بکار رفته در یک کوئری T-SQL نمی‌دهد. برای حل این موضوع باید عبارت SQL مورد نظر را به صورت یک رشته درآورد و سپس exec کرد.
د) مجبور شدم از معاد‌ل‌های عددی برای دقت بیشتر کار استفاده کنم



(در کل از تابع UNICODE اس کیوال سرور برای بدست آوردن این اعداد می‌توان استفاده کرد)

تذکر: این اسکریپت بر روی یک دیتابیس کاری تست شده و نتیجه رضایت بخش بوده؛ اما اگر شما روزی خواستید از آن استفاده کنید، حتما full backup را قبل از اجرای آن فراموش نکنید و پس از اجرا، تابع SafeFarsiStr فوق را برای ادامه کار حتما لحاظ نمائید یا از یک kbdfa.dll هماهنگ استفاده کنید.


مطالب
T4MVC : یکی از الزامات مدیریت پروژه‌های ASP.NET MVC
تعریف ActionLink زیر را درنظر بگیرید:
@Html.ActionLink("text", "Index", "Home")

پارامترهای دوم و سوم آن که به نام‌های یک اکشن متد و کنترلر آن اشاره می‌کنند، توسط رشته‌ها تعریف شده‌اند. مشکلاتی هم که با رشته‌ها در حالت کلی وجود دارند به شرح زیر است:
الف) می‌توان نام کنترلر یا نام متد را در برنامه تغییر داد. به این ترتیب تمام ActionLink هایی که در برنامه به این کنترلر اشاره می‌کردند از کار می‌افتند (تکرار رشته‌ها به علاوه refactoring friendly نبودن آن‌ها).
ب) برای نوشتن رشته‌ها intellisense کارآیی ندارد.
ج) امکان بروز اشتباهات تایپی در این بین بسیار زیاد است.

راه حل متداولی که برای حل این نوع مشکلات وجود دارد، تعریف یک کلاس عمومی و معرفی رشته‌ها به صورت فیلدهایی ثابت در آن‌ها می‌باشند و سپس استفاده از این فیلدها بجای استفاده مستقیم از رشته‌ها.
و ... چقدر خوب می‌شد اگر ابزاری وجود می‌داشت که کلاس‌های کنترلرهای ما را آنالیز می‌کرد و خودش این ثوابت رشته‌ای را از آن‌ها استخراج و کلاس‌های عمومی یاد شده را تشکیل می‌داد!
خوشبختانه نیازی به اختراع مجدد چرخ نیست و اینکار توسط پروژه‌ی سورس بازی به نام T4MVC انجام شده است. برای دریافت آن به سایت زیر مراجعه نمائید:
این پروژه توسط David Ebbo از اعضای تیم ASP.NET MVC تهیه شده است.

پس از دریافت پروژه، تنها به دو فایل زیر از آن نیاز داریم:
T4MVC.tt و T4MVC.tt.settings.t4

دو فایل فوق را به درون پوشه‌ای از پروژه جاری MVC خود کپی کنید (مثلا یک پوشه T4MVC را ایجاد و این دو فایل را به آن اضافه کنید). بلافاصله این فایل‌های t4 وارد عمل شده و کلاس‌های کنترلرها، Viewها، تصاویر و غیره را آنالیز و ... ثوابت رشته‌ای معادل آن‌ها را تولید می‌کنند.
اکنون برای استفاده از این کلاس‌های تولید شده می‌توان به صورت زیر عمل کرد:

اصلاح ActionLink‌ها و حذف رشته‌های موجود در آن‌ها

اینبار بجای اینکه بنویسیم:
@Html.ActionLink("text", "Index", "Home")
می‌توان نوشت:
@Html.ActionLink("text", result: MVC.Home.Index())
و یا:
@Html.ActionLink("text", MVC.Home.ActionNames.Index, MVC.Home.Name)

برای دسترسی به امکانات آن با نام کلاس MVC شروع می‌کنیم و سپس برای مثال به نام کنترلر Home رسیده و توسط ActionNames آن به تمام اکشن متدهای موجود در آن می‌توان دسترسی داشت.
البته این پروژه بسیار فراتر از تولید فیلدهای strongly typed معادل رشته‌ها است. همانطور که ملاحظه می‌کنید، یک سری overload را هم به متدهای پیش فرض ASP.NET MVC اضافه کرده است و حتی بجای معرفی رشته معادل اکشن متد Index، خود این اکشن متد را می‌توان معرفی کرد (آرگومانی از نوع ActionResult را هم اضافه کرده است). نمونه دیگر آن به نحو زیر می‌تواند باشد:

@Url.Action(result: MVC.Article.Delete())
که بجای مورد متداول ذیل قابل استفاده است:
@Url.Action("Delete", "Article")


امکان معرفی بهتر نام Partial Viewها

برای مثال اگر پیشتر یک Partial View را به این شکل تعریف می‌کردید:
@{ Html.RenderPartial("_ViewPage1"); }
اکنون امکان معرفی آن به نحو زیر فراهم شده است:
@{ Html.RenderPartial(MVC.Home.Views._ViewPage1); }
همچنین تمام این کلاس‌ها در کنترلرها نیز قابل دسترسی هستند:
return PartialView(Views._ViewPage1);
اینجا دیگر نیازی به ذکر کلاس MVC نبوده و می‌توان کار را با کلاس جدید Views شروع کرد. یا اگر نیازی به ذکر نام اکشن متدی در کنترلر جاری بود می‌توان از ActionNames مستقیما استفاده کرد.
و یا بجای:
return RedirectToAction(actionName: "Index", controllerName: "Menu");
می‌توان نوشت:
return RedirectToAction(actionName: MVC.Menu.ActionNames.Index, controllerName: MVC.Menu.Name);
و یا حتی به صورت خلاصه‌تر به نحو زیر:
return RedirectToAction(result: MVC.Menu.Index());
این overload جدیدی است که با T4MVC به پروژه اضافه می‌شود و یک Action Result را می‌تواند بپذیرد. به این ترتیب به صورت خودکار نام کنترلر و متد Index آن تنها در یک پارامتر لحاظ می‌شوند. اگر نیاز به ارسال پارامتری هم به متد Index وجود داشت، همینجا می‌توان اینکار را انجام داد (بجای استفاده از anonymously typed objects متداول).

و یا بجای مسیر دهی به شکل زیر:
return PartialView("~/Views/CommentsArchive/_LatestCommentsInfo.cshtml", data);
می‌توان نوشت:
return PartialView(MVC.CommentsArchive.Views._LatestCommentsInfo, data);
همچنین برای مسیر دهی viewهای قرار گرفته در پوشه shared می‌توان از MVC.Shared.Views شروع کرد.


امکان معرفی بهتر عناصر استاتیک سایت

این مورد نیز بسیار جالب توجه است. توسط کلاس Links آن می‌توان به محتویات استاتیک (تصاویر، فایل‌های css و غیره) پوشه‌های Content و Scripts هم دسترسی یافت و حتی این موارد را نیز refactor کرد:

<img src="@Links.Content.Images.arrow_right_png" alt="arrow" />
<script src="@Links.Scripts.jquery_1_5_1_min_js" type="text/javascript"></script>


امکان تعریف بهتر پارامترها و مقادیر route

بجای اینکه  routeValues را همانند سابق با anonymously typed objects مقدار دهی کنیم:
Html.ActionLink(linkText: "عنوان",
                       actionName: "Index",
                       controllerName: "Comments",
                       routeValues: new
                       {
                            userName = @Model.FriendlyName
                       },
                       htmlAttributes: null))
اینبار می‌توان نوشت:
Html.ActionLink(linkText: "عنوان",
                       result: MVC.Comments.Index(userName: @Model.FriendlyName)
                       htmlAttributes: null))
در overload جدیدی که ملاحظه می‌کنید، هم دسترسی به متد Index یک کنترلر از نوع strongly typed است و هم امکان تعریف پارامترهای آن به نحو منطقی‌تری فراهم شده است (متد Comments.Index واقعا وجود خارجی داشته و پارامتری از نوع userName را می‌پذیرد).


چند نکته جانبی
-این ابزار بر اساس Reflection کار می‌کند (البته فقط در حین تشکیل خودکار کلاس‌های مورد نیاز؛ وگرنه ثوابتی را که ایجاد می‌کند کامپایل شده و در زمان اجرا سرباری را به برنامه اضافه نمی‌کنند). بنابراین اگر کلاسی به پروژه اضافه شده است، کامپایل کردن آن‌را فراموش نکنید.
-اگر تغییری در فایل‌های View، در تعداد و نام آن‌ها صورت گرفت، روی فایل T4MVC.tt کلیک راست کرده و گزینه‌ی اجرای آن‌را انتخاب کنید. پس از این‌کار، مجددا کامپایل پروژه را فراموش نکنید.
-در فایل T4MVC.tt.settings.t4 یک سری تنظیمات پیش فرض قرار دارند. برای مثال اگر علاقمندید که به این فایل‌های تولید شده خودکار، فضای نام سفارشی خاصی را اضافه کنید می‌شود آرایه ReferencedNamespaces آن‌را مقدار دهی کرد.
- overloadهای جدید ActionResult دار آن نسبت به نمونه‌های استاندارد موجود، بسیار منطقی‌تر به نظر می‌رسند.
- توضیحات کامل امکانات T4MVC را در مستندات رسمی آن می‌توانید مطالعه کنید.


و ... اگر یک مدت با آن کار کنید خواهید گفت: «من قبلا چطور با ASP.NET MVC کار می‌کردم؟!»