نظرات مطالب
ASP.NET MVC #4
RouteTable.Routes یک شیء با طول عمر singleton است (پارامتر ورودی متد RegisterRoutes در مطلب فوق که در فایل global.asax.cs مقدار دهی می‌شود). یک وهله از آن در طول عمر برنامه با تمام اجزای آن وجود دارد. هر جزئی که اطلاعاتی را به RouteTable.Routes اضافه کند، توسط کل سیستم در حین آغاز به کار اولیه آن خوانده شده و استفاده خواهد شد. نمونه این مورد در مفهومی به نام Area در خود ASP.NET MVC استفاده می‌شود. هر Area دارای تنظیمات روت خاص خودش است که یکبار در آغاز برنامه خوانده شده و مورد استفاده قرار می‌گیرد.
نظرات مطالب
EF Code First #4
- InitialCreate زمانی ایجاد خواهد شد که دیتابیسی موجود است و اکنون در مدل‌های شما تغییری حاصل شده‌است. اگر بار اول است، نیازی به آن نیست (ایجاد نخواهد شد) و در حین ایجاد اولیه بانک اطلاعاتی، تمام مراحل لازم طی می‌شوند.
- رشته اتصالی را در فایل کانفیگ پروژه‌ای که مهاجرت روی آن فعال می‌شود نیز قرار دهید.
+ تمام این دستورات پارامتر رشته اتصالی هم دارند:
Update-Database -Verbose 
 -ConnectionString "CONNECTIONSTRING" 
 -ConnectionProviderName "System.Data.SqlClient"
 -StartupProjectName WEBSITE_PROJECT -ProjectName MIGRATION_PROJECT
مطالب
برش تصاویر قبل از آپلود (Crop)
چند روز پیش در یکی از سیستم‌های مدیریتی که امکان آپلود چند تصویر وجود داشت، مسئله‌ای پیش آمد که نیاز بود تصاویر وارد شده، اندازه‌ای به یک نسبت را داشته باشند و کاربران مرتبا تصاویری با اندازه‌های مختلف را وارد میکردند که باعث میشد UI در حین نمایش تصاویر، از شکل اصلی خود دور شود. به همین دلیل امکان برش تصاویر یا Crop برای این امر احساس میشد. کتابخانه‌های مختلف و زیادی برای برش تصاویر و کار بر روی تصاویر وجود دارند، ولی این کتابخانه باید چند خصوصیت زیر را دارا باشد:

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

کتابخانه jcrop کتابخانه‌ای است که این امکانات را برای شما فراهم می‌کند. این کتابخانه بدین صورت است که در حین برش به شما 4 عدد x1,x2,y1,y2 را داده و شما با ارسال آن به سمت سرور میتوانید بر اساس این اعداد، عکس اصلی را برش بزنید. بدین صورت شما هم عکس اصلی را دارید و هم مختصات برش را دارید و اگر دوست دارید در جاهای مختلف از عکس اصلی برش داشته باشید، بسیار مفید خواهد بود.

مرحله اول:
ابتدا فایل jcrop را دانلود نمایید.

مرحله دوم:
 کد Html زیر را به صفحه اضافه کنید:
<div>
    <!-- upload form -->
    <!-- hidden crop params -->
    <input type="hidden" id="x1" name="x1" />
    <input type="hidden" id="y1" name="y1" />
    <input type="hidden" id="x2" name="x2" />
    <input type="hidden" id="y2" name="y2" />
    <h2>ابتدا تصویر خود را انتخاب کنید</h2>
    <div><input type="file" name="postedFileBase" data-buttonText="انتخاب تصویر" id="image_file" onchange="fileSelectHandler()" /></div>
    <div></div>
    <div>
        <h2>قسمتی از تصویر را انتخاب نمایید</h2>
        <img id="preview" />
        <div>
            <label>حجم فایل </label> <input type="text" id="filesize" name="filesize" />
            <label>نوع فایل</label> <input type="text" id="filetype" name="filetype" />
            <label>ابعاد فایل</label> <input style="direction: ltr;" type="text" id="filedim" name="filedim" />

        </div>
    </div>

</div>
در اینجا 4 عدد input به صورت مخفی قرار گرفته‌اند که مختصات برش به ترتیب در آن‌ها ذخیره می‌شوند و هنگام post یا قرار دادن در formData جهت ارسال ایجکسی نیز می‌توانند مورد استفاده قرار بگیرند. جهت input file مورد نظر برای زیبایی و پشتیبانی از عبارت فارسی دلخواه به جای «Browse» از کتابخانه  Bootstrap FileStyle استفاده شده است و رویداد Onchange آن نیز به یک تابع جاوا اسکریپتی بایند شده تا بعد از تایید تصویر، باقی عملیات برش تصویر انجام شوند.

 تگ step2 نیز بعد از نمایش موفقیت آمیز تصویر نشان داده میشود که کاربر میتواند در آن تصویر را برش دهد و شامل بخش info نیز می‌باشد تا بتوان اندازه اصلی تصویر، نوع فایل تصویر Content Type و حجم آن را نمایش داد.

مرحله سوم:
سپس برای استایل دهی کدهای بالا از کد Css زیر استفاده میکنیم:
.bheader {
background-color: #DDDDDD;
border-radius: 10px 10px 0 0;
padding: 10px 0;
text-align: center;
}
.bbody {
color: #000;
overflow: hidden;
padding-bottom: 20px;
text-align: center;
background: -moz-linear-gradient(#ffffff, #f2f2f2);
background: -ms-linear-gradient(#ffffff, #f2f2f2);
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #ffffff), color-stop(100%, #f2f2f2));
background: -webkit-linear-gradient(#ffffff, #f2f2f2);
background: -o-linear-gradient(#ffffff, #f2f2f2);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#f2f2f2');
-ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#f2f2f2')";
background: linear-gradient(#ffffff, #f2f2f2);
}
.bbody h2, .info, .error {
margin: 10px 0;
}
.step2, .error {
display: none;
}
.error {
color: red;
}
.info {
}
label {
margin: 0 5px;
}
.roundinput {
border: 1px solid #CCCCCC;
border-radius: 10px;
padding: 4px 8px;
text-align: center;
width: 150px;
}
.jcrop-holder {
display: inline-block;
}
input[type=submit] {
background: #e3e3e3;
border: 1px solid #bbb;
border-radius: 3px;
-webkit-box-shadow: inset 0 0 1px 1px #f6f6f6;
box-shadow: inset 0 0 1px 1px #f6f6f6;
color: #333;
padding: 8px 0 9px;
text-align: center;
text-shadow: 0 1px 0 #fff;
width: 150px;
}
input[type=submit]:hover {
background: #d9d9d9;
-webkit-box-shadow: inset 0 0 1px 1px #eaeaea;
box-shadow: inset 0 0 1px 1px #eaeaea;
color: #222;
cursor: pointer;
}
input[type=submit]:active {
background: #d0d0d0;
-webkit-box-shadow: inset 0 0 1px 1px #e3e3e3;
box-shadow: inset 0 0 1px 1px #e3e3e3;
color: #000;
}

مرحله چهارم:
افزودن کد جاوااسکریپتی زیر برای کار کردن با کتابخانه Jcrop می‌باشد:
function bytesToSize(bytes) {
    var sizes = ['بایت', 'کیلو بایت', 'مگابایت'];
    if (bytes == 0) return 'n/a';
    var i = parseInt(Math.floor(Math.log(bytes) / Math.log(1024)));
    return (bytes / Math.pow(1024, i)).toFixed(1) + ' ' + sizes[i];
};

function updateInfo(e) {
    $('#x1').val(e.x);
    $('#y1').val(e.y);
    $('#x2').val(e.x2);
    $('#y2').val(e.y2);
};

var jcrop_api, boundx, boundy;
function fileSelectHandler() {

    var oFile = $('#image_file')[0].files[0];

    $('.error').hide();
    var rFilter = /^(image\/jpeg|image\/png)$/i;
    if (!rFilter.test(oFile.type)) {
        $('.error').html('فقط تصویر معتبر انتخاب نمایید').show();
        return;
    }

   
    var oImage = document.getElementById('preview');

    var oReader = new FileReader();
    oReader.onload = function (e) {

        oImage.src = e.target.result;
        

        oImage.onload = function () { 
          

            $('.step2').fadeIn(500);

            var sResultFileSize = bytesToSize(oFile.size);
            $('#filesize').val(sResultFileSize);
            $('#filetype').val(oFile.type);
            $('#filedim').val(oImage.naturalWidth + ' x ' + oImage.naturalHeight);
     
            if (typeof jcrop_api != 'undefined') {
                jcrop_api.destroy();
                jcrop_api = null;
                $('#preview').width(oImage.naturalWidth);
                $('#preview').height(oImage.naturalHeight);
            }
  
                $('#preview').Jcrop({

                    aspectRatio: 2,
                    bgFade: true,
                    bgOpacity: .3, 
                    onChange: updateInfo,
                    onSelect: updateInfo
                }, function () {
 
                    //var bounds = this.getBounds();
                    //var boundx = bounds[0];
                    //var boundy = bounds[1];

                    // Store the Jcrop API in the jcrop_api variable
                    jcrop_api = this;
                });
        };
    };
    oReader.readAsDataURL(oFile);
}

تابع fileSelectHandler

function fileSelectHandler() {
    var oFile = $('#image_file')[0].files[0];
    $('.error').hide();
    var rFilter = /^(image\/jpeg|image\/png)$/i;
    if (!rFilter.test(oFile.type)) {
        $('.error').html('فقط تصویر معتبر انتخاب نمایید').show();
        return;
    }
این تابع که مستقیما به input file رویداد Onchange متصل است. فایل انتخابی آن را خوانده و نوع تصویر را بررسی میکند. اگر تصویر از نوع Jpeg یا Png نباشد، یک خطا را به کاربر نشان میدهد و با return شدن از ادامه کد جلوگیری میکنیم.
در ادامه همین تابع بالا، کدهای زیر را اضافه می‌کنیم:
   var oImage = document.getElementById('preview');
    var oReader = new FileReader();
    oReader.onload = function (e) {

        oImage.src = e.target.result;
        oImage.onload = function () {
       
            $('.step2').fadeIn(500);

            var sResultFileSize = bytesToSize(oFile.size);
            $('#filesize').val(sResultFileSize);
            $('#filetype').val(oFile.type);
            $('#filedim').val(oImage.naturalWidth + ' x ' + oImage.naturalHeight);
   
            if (typeof jcrop_api != 'undefined') {
                jcrop_api.destroy();
                jcrop_api = null;
                $('#preview').width(oImage.naturalWidth);
                $('#preview').height(oImage.naturalHeight);
            }
            $('#preview').Jcrop({

                aspectRatio: 2,
                bgFade: true, 
                bgOpacity: .3, 
                onChange: updateInfo,
                onSelect: updateInfo,
                onRelease: clearInfo
            }, function () {

                //var bounds = this.getBounds();
                //var boundx = bounds[0];
                //var boundy = bounds[1];

                jcrop_api = this;
            });
        };
    };
    oReader.readAsDataURL(oFile);

FileReader یکی از توابع موجود در HTML است که مستندات آن در سایت موزیلا موجود است و قابلیت خواندن غیرهمزمان فایلها و اشیا Blob را دارد. در خط آخر به عنوان پارامتر ما فایلی را که در آپلودر خوانده ایم و در مرحله قبل نوع فایل آن را بررسی کردیم، پاس میکنیم و باعث می‌شود که رویداد Load شیء FileReader صدا زده شود.
در این رویداد ابتدا اطلاعات این فایل را از قبیل سایز و ابعاد و نوع فایل، خوانده و در همان تگ Div که با کلاس info تعیین شده بود، نمایش می‌دهیم. سپس متغیر jcrop_api  را که به صورت global در بالای تابع صدا زدیم، بررسی میکنم که آیا از قبل پر شده‌است یا خیر؟ اگر از قبل پرشده‌است باید شیء Jcrop را که به آن اعمال شده است، نابود و آن را نال کنیم تا برای تصویر جدید آماده شود. این کد زمانی کاربرد دارد که کاربر از تصویر قبلی انصراف داده‌است و تصویر جدیدی را انتخاب نموده است یا اینکه عملیات دارد به صورت ایجکسی پیاده می‌شود. اگر عملیات نابودی روی این پلاگین صورت نگیرد، برای مرتبه دوم کار نخواهد کرد.


سپس پلاگین جی‌کوئری Jcrop را بر روی آن اعمال می‌کنیم. در پرامتر اول یک سری تنظیمات اولیه را انجام می‌دهیم که در ادامه با آن آشنا می‌شویم و در پارامتر دوم یک callback را به آن پاس میکنیم تا بعد از آماده شدن پلاگین اجرا شود که در آن شیء جدید ایجاد شده یعنی this را در متغیری به اسم jcrop_api دخیره میکنیم تا در بررسی‌های آتی که در بند بالا توضیح داده شد، در دسترس داشته باشیم. همچنین در این تابع شما می‌توانید اندازه تصویر انتخابی را نیز داشته باشید.

این پلاگین شامل option‌های متفاوتی در پارامتر اول است که آن‌ها را بررسی می‌کنیم:
MinSize : شما میتوانید حداقل پهنا و ارتفاعی را برای برش زدن تصویر در نظر بگیرید.
minSize:[40,20]
پارامتر دوم aspectRatio جهت نگهداری تناسب بین پهنا و ارتفاع می‌باشد. تنظیم زیر برای تناسب 3 به دو می‌باشد.
aspectRatio:1.5
bgFade اگر با true مقدار دهی شود عملیات برش در حین تاریک شدن صفحه (محلی که جزء برش نیست) و یا بالعکس آن با یک Fade صورت خواهد گرفت.
bgOpacity از 0 تا یک مقدار میگیرد و میزان opacity محل‌های تاریک را تعیین می‌کند. همچنین شامل سه رویداد onSelect,onChange,onrelease هم می‌باشد که به ترتیب در موارد زیر رخ میدهند:
ناحیه مورد نظر انتخاب شد.
ناحیه مورد نظر در حالت انتخاب است و ماوس در حال درگ شدن است و با هر حرکتی ماوس اجرا می‌گردد.
ناحیه انتخابی از حالت انتخاب خارج شد.


دو رویداد اول یعنی onchange و onSelect را برای به روزسانی فیلدهای مخفی و مختصات استفاده میکنیم:

function updateInfo(e) {
    $('#x1').val(e.x);
    $('#y1').val(e.y);
    $('#x2').val(e.x2);
    $('#y2').val(e.y2);
};

این مختصات از طریق یک پارامتر به آن‌ها پاس می‌شود. به غیر از این چهار عدد مختصات می‌توانید با استفاده از متغیرهای w و h هم اندازه پهنا و ارتفاع محل برش خورده را نیز به دست آورید. هر چند که این اعداد، از تفریق خود مختصات هم به دست می‌آیند.
یک تابع جزئی دیگر هم در این فایل وجود دارد که حین نمایش اندازه تصویر، واحد نمایش مناسب آن را برای ما انتخاب میکند:
function bytesToSize(bytes) {
    var sizes = ['بایت', 'کیلو بایت', 'مگابایت'];
    if (bytes == 0) return 'n/a';
    var i = parseInt(Math.floor(Math.log(bytes) / Math.log(1024)));
    return (bytes / Math.pow(1024, i)).toFixed(1) + ' ' + sizes[i];
};

بعد از اینکه کدهای سمت کلاینت را تمام کردیم لازم است با نحوه برش تصویر در سمت سرور هم آشنا شویم:
public static byte[] Resize(this byte[] byteImageIn, int x1,int y1,int x2,int y2)
        {

            ImageConverter ic = new ImageConverter();
            Image src = (Image)(ic.ConvertFrom(byteImageIn)); 

            Bitmap target = new Bitmap(x2 - x1, y2 - y1);
            using (Graphics graphics = Graphics.FromImage(target))
                graphics.DrawImage(src, new Rectangle(0, 0, target.Width, target.Height),
                    new Rectangle(x1,y1,x2-x1,y2-y1), 
                    GraphicsUnit.Pixel);
            src = target;
            using (var ms = new MemoryStream())
            {
                src.Save(ms, System.Drawing.Imaging.ImageFormat.Jpeg);
                return ms.ToArray();
            }

 }

از آنجا که ما تصاویر را در دیتابیس به صورت آرایه‌ای از بایت‌ها ذخیره میکنیم، extension method ذکر شده در بالا تصویر را در حالت آرایه‌ای از بایت‌ها برش می‌دهد. بدیهی که بسته به نیاز شما کد بالا دست خوش تغییراتی خواهد شد. ابتدا تصویر باینری را به شی Image تبدیل میکنیم و یک شیء Bitmap جدید را به عنوان بوم خالی و به اندازه کادر برش ایجاد می‌کنیم تا تصویر برش خورده در آن قرار بگیرد و سپس توسط متد DrawImage میخواهیم که تصویر مبدا را با مختصات شیء Rectangle از نقطه 0 و 0 بوم آغاز کرده و تا انتهای آن شروع به ترسیم کند. سپس آن را ذخیره و مجددا در قالب همان آرایه‌ای از بایت‌ها بر می‌گردانیم.

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

مطالب
MVC Scaffolding #3
شاید کیفیت کدهای تولیدی یا کدهای View حاصل از MVC Scaffolding مورد تائید شما نباشد. در این قسمت به نحوه تغییر و سفارشی سازی این موارد خواهیم پرداخت.

آشنایی با ساختار اصلی MVC Scaffolding

پس از نصب MVC Scaffolding از طریق NuGet به پوشه Packages مراجعه نمائید. در اینجا پوشه‌های MvcScaffolding، T4Scaffolding و T4Scaffolding.Core ساختار اصلی این بسته را تشکیل می‌دهند. برای نمونه اگر پوشه T4Scaffolding\tools را باز کنیم، شاهد تعدادی فایل ps1 خواهیم بود که همان فایل‌های پاورشل هستند. مطابق طراحی NuGet، همواره فایلی با نام init.ps1 در ابتدا اجرا خواهد شد. همچنین در  اینجا پوشه‌های T4Scaffolding\tools\EFRepository و T4Scaffolding\tools\EFDbContext نیز قرار دارند که حاوی قالب‌های اولیه کدهای مرتبط با الگوی مخزن و DbContext تولیدی می‌باشند.
در پوشه MvcScaffolding\tools، ساختار قالب‌های پیش فرض تولید Viewها و کنترلرهای تولیدی قرار دارند. در اینجا به ازای هر مورد، دو نگارش vb و cs قابل مشاهده است.


سفارشی سازی قالب‌های پیش فرض Viewهای MVC Scaffolding

برای سفارشی سازی قالب‌های پیش فرض از دستور کلی زیر استفاده می‌شود:
Scaffold CustomTemplate Name Template
مانند دستور زیر:
Scaffold CustomTemplate View Index
در اینجا View نام یک Scaffolder است و Index نام قالبی در آن.
اگر دستور فوق را اجرا کنیم، فایل جدیدی به نام CodeTemplates\Scaffolders\MvcScaffolding.RazorView\Index.cs.t4 به پروژه جاری اضافه می‌شود. از این پس کلیه فرامین اجرایی، از نسخه محلی فوق بجای نمونه‌های پیش فرض استفاده خواهند کرد.
در ادامه قصد داریم اندکی این قالب پیش فرض را جهت اعمال ویژگی DisplayName به هدر جدول تولیدی نمایش اطلاعات Tasks تغییر دهیم. در کلاس Task، خاصیت زمان موعود با ویژگی DisplayName مزین شده است. این نام نمایشی حین تولید فرم‌های ثبت و ویرایش اطلاعات بکار گرفته می‌شود، اما در زمان تولید جدول اطلاعات ثبت شده، به هدر جدول اعمال نمی‌گردد.
[DisplayName("Due Date")]
public DateTime? DueDate { set; get; }
برای تغییر و بهبود این مساله، فایل Index.cs.t4 را که پیشتر به پروژه اضافه کردیم باز کنید. کلاس ModelProperty را یافته و خاصیت جدید DisplayName را به آن اضافه کنید:
// Describes the information about a property on the model
class ModelProperty {
    public string Name { get; set; }
    public string DisplayName { get; set; }
    public string ValueExpression { get; set; }
    public EnvDTE.CodeTypeRef Type { get; set; }
    public bool IsPrimaryKey { get; set; }
    public bool IsForeignKey { get; set; }
    public bool IsReadOnly { get; set; }
}
در حالت پیش فرض فقط از خاصیت Name برای تولید هدر جدول در ابتدای فایل t4 در حال ویرایش استفاده می‌شود. در پایان فایل t4 جاری، متد زیر را اضافه کنید:
static string GetDisplayName(EnvDTE.CodeProperty prop)
{
  var displayAttr = prop.Attributes.OfType<EnvDTE80.CodeAttribute2>().Where(x => x.FullName == typeof(System.ComponentModel.DisplayNameAttribute).FullName).FirstOrDefault();
  if(displayAttr == null)
  {
    return prop.Name;
  }
  return displayAttr.Value.Replace("\"","");
}
در اینجا بررسی می‌شود که آیا ویژگی DisplayNameAttribute بر روی خاصیت در حال بررسی وجود دارد یا خیر. اگر خیر از نام خاصیت استفاده خواهد شد و اگر بلی، مقدار ویژگی نام نمایشی استخراج شده و بازگشت داده می‌شود.
اکنون برای اعمال متد GetDisplayName، متد GetEligibleProperties را یافته و به نحو زیر تغییر دهید:
results.Add(new ModelProperty {
Name = prop.Name,
DisplayName = GetDisplayName(prop), 
ValueExpression = "Model." + prop.Name,
Type = prop.Type,
IsPrimaryKey = Model.PrimaryKeyName == prop.Name,
IsForeignKey = ParentRelations.Any(x => x.RelationProperty == prop),
IsReadOnly = !prop.IsWriteable()
});
در اینجا خاصیت DisplayName به لیست خروجی اضافه شده است.
اکنون قسمت هدر جدول تولیدی را در ابتدای فایل t4 یافته و به نحو زیر تغییر می‌دهیم تا از DisplayName استفاده کند:
<#
List<ModelProperty> properties = GetModelProperties(Model.ViewDataType, true);
foreach (ModelProperty property in properties) {
    if (!property.IsPrimaryKey && !property.IsForeignKey) {
#>
        <th>
            <#= property.DisplayName #>
        </th>
<#
    }
}
#>
در ادامه برای آزمایش تغییرات فوق، دستور ذیل را صادر می‌کنیم:
PM> Scaffold Controller -ModelType Task -ControllerName TasksController -DbContextType TasksDbContext -Repository -Force
پس از اجرای دستور، به فایل Views\Tasks\Index.cshtml مراجعه نمائید. اینبار هدر خودکار تولیدی از Due Date بجای DueDate استفاده کرده است.


سفارشی سازی قالب‌های پیش فرض کنترلرهای MVC Scaffolding

در ادامه قصد داریم کدهای الگوی مخزن تهیه شده را اندکی تغییر دهیم. برای مثال با توجه به اینکه از تزریق وابستگی‌ها استفاده خواهیم کرد، نیازی به سازنده اولیه پیش فرض کنترلر که در بالای آن ذکر شده «در صورت استفاده از یک DI این مورد را حذف کنید»، نداریم. برای این منظور دستور زیر را اجرا کنید:
 PM> Scaffold CustomTemplate Controller ControllerWithRepository
در اینجا قصد ویرایش قالب پیش فرض کنترلرهای تشکیل شده با استفاده از الگوی مخزن را داریم. نام ControllerWithRepository از فایل ControllerWithRepository.cs.t4 موجود در پوشه packages\MvcScaffolding\tools\Controller گرفته شده است.
به این ترتیب فایل جدید CodeTemplates\Scaffolders\MvcScaffolding.Controller\ControllerWithRepository.cs.t4 به پروژه جاری اضافه خواهد شد. در این فایل چند سطر ذیل را یافته و سپس حذف کنید:
// If you are using Dependency Injection, you can delete the following constructor
        public <#= Model.ControllerName #>() : this(<#= String.Join(", ", Repositories.Values.Select(x => "new " + x.RepositoryTypeName + "()")) #>)
        {
        }
برای آزمایش آن دستور زیر را صادر نمائید:
PM> Scaffold Controller -ModelType Task -ControllerName TasksController -DbContextType TasksDbContext -Repository -Force -ForceMode ControllerOnly
چون تنها قصد تغییر کنترلر را داریم از پارامتر ForceMode با مقدار ControllerOnly استفاده شده است.
یا اگر نیاز به تغییر کدهای الگوی مخزن مورد استفاده است می‌توان از دستور ذیل استفاده کرد:
 Scaffold CustomScaffolder EFRepository
به این ترتیب فایل جدید CodeTemplates\Scaffolders\EFRepository\EFRepositoryTemplate.cs.t4 جهت ویرایش به پروژه جاری اضافه خواهد شد.
لیست Scaffolder‌های مهیا با دستور Get-Scaffolder قابل مشاهده است.
مطالب
نمایش بلادرنگ اعلامی به تمام کاربران در هنگام درج یک رکورد جدید به صورت notification
در ادامه می‌خواهیم مثالی را که در این مطلب مورد بررسی قرار گرفت، به صورتی تغییر دهیم که با ثبت یک آیتم جدید درون دیتابیس، یک notification، به تمامی کاربران متصل به هاب ارسال شود. همچنین با کلیک بر روی Notification سطر جدید نیز بلافاصله نمایش داده شود:

در این مثال برای نمایش پیام به صورت notification، از کتابخانه toastr استفاده می‌کنیم که از طریق nuget می‌توانید آن را به پروژه اضافه کنید:
PM> Install-Package toastr
کار با این کتابخانه خیلی ساده است؛ کافی است فایل‌های js و css آن را به فایل layout اضافه کرده و به این صورت از آن استفاده کنیم:
toastr.info("نمایش یک پیام - info");
toastr.success("نمایش یک پیام - success");
toastr.error("نمایش یک پیام - error");
toastr.warning("نمایش یک پیام - warning");
دستورات فوق خروجی‌های زیر را نمایش می‌دهد:

برای پیام‌های فوق نیز می‌توانید عنوانی را انتخاب کنید:
toastr.success("نمایش یک پیام - success", "عنوان");
اگر به فایل js این کتابخانه مراجعه کنید، می‌توانید مقادیر پیش‌فرض آن را برای نمایش یک پیام مشاهده کنید. برای سفارشی‌سازی آن نیز می‌توانید به این صورت عمل کنید:
toastr.options = {
            tapToDismiss: true,
            toastClass: 'toast',
            containerId: 'toast-container',
            debug: false,

            showMethod: 'fadeIn', //fadeIn, slideDown, and show are built into jQuery
            showDuration: 300,
            showEasing: 'swing', //swing and linear are built into jQuery
            onShown: undefined,
            hideMethod: 'fadeOut',
            hideDuration: 1000,
            hideEasing: 'swing',
            onHidden: undefined,

            extendedTimeOut: 1000,
            iconClasses: {
                error: 'toast-error',
                info: 'toast-info',
                success: 'toast-success',
                warning: 'toast-warning'
            },
            iconClass: 'toast-info',
            positionClass: 'toast-top-right',
            timeOut: 5000, // Set timeOut and extendedTimeOut to 0 to make it sticky
            titleClass: 'toast-title',
            messageClass: 'toast-message',
            target: 'body',
            closeHtml: '<button>&times;</button>',
            newestOnTop: true,
            preventDuplicates: false,
            progressBar: false
};
اکنون برای نمایش این نوع پیام‌ها در زمان اتصال به هاب (در واقع در زمان ثیت یک رکورد جدید) نیاز به ارسال پارامتر خاصی به سرور (از سمت کلاینت) نمی‌باشد. تنها باید کدهای سمت سرور یعنی هاب را به گونه‌ایی تغییر دهیم تا به محض فراخوانی SendNotification، آخرین رکورد ثبت شده در دیتابیس را به تمامی کلاینت‌های متصل به هاب ارسال کند:
public class NotificationHub : Hub
{
        private readonly IProductService _productService;

        public NotificationHub(IProductService productService)
        {
            _productService = productService;
        }

        public void SendNotification()
        {
            Clients.Others.ShowNotification(_productService.GetLastProduct());
        }
}
در سمت کلاینت نیز کدها همانند مثال قبل هستند؛ با این تفاوت که در متد سمت کلاینت باید اطلاعات ارسال شده از سمت سرور را با نمایش یک notification به کاربران اطلاع دهیم:
var notify = $.connection.notificationHub;
notify.client.showNotification = function (data) {
toastr.info("رکورد جدیدی ثبت گردید جهت نمایش اینجا کلیک کنید");
};
$.connection.hub.start().done(function () {
            @{
                if (ViewBag.NotifyUsers)
                {
                    <text>notify.server.sendNotification();</text>
                }
            }
});
تا اینجا همانند مثال قبلی عمل کردیم. یعنی به جای نمایش یک alert بوت‌استرپ، از کتابخانه toastr استفاده گردید. در مثال قبلی کاربر برای دیدن تغییرات می‌بایستی یکبار صفحه را ریفرش کند، اکنون می‌خواهیم کاربر بعد از کلیک بر روی پیام، بلافاصله سطر جدید را نیز مشاهده کند:
var positionClasses = {
            topRight: 'toast-top-right',
            bottomRight: 'toast-bottom-right',
            bottomLeft: 'toast-bottom-left',
            topLeft: 'toast-top-left',
            topCenter: 'toast-top-center',
            bottomCenter: 'toast-bottom-center'
        };
        var notify = $.connection.notificationHub;
        notify.client.showNotification = function (data) {
            toastr.options = {
                showDuration: 300,
                positionClass: positionClasses.bottomRight,
                onclick: function () {
                    $('#table tr:last').after("<tr>" +
                    "<td>" + data.Title + "</td>" +
                    "<td>" + data.Description + "</td>" +
                    "<td>" + data.Price + "</td>" +
                    "<td>" + data.Category + "</td>" +
                    "<td>  </td>" +
                    "</tr>");

                }
            };
            toastr.info("رکورد جدیدی ثبت گردید جهت نمایش اینجا کلیک کنید");
            
            
        };
        $.connection.hub.start().done(function () {
            @{
                if (ViewBag.NotifyUsers)
                {
                    <text>notify.server.sendNotification();</text>
                }
            }
});
همانطور که مشاهده می‌کنید از onClick برای toastr استفاده کرده‌ایم. با این callback گفته‌ایم که اگر بر روی پیام کلیک شد، اطلاعات را به صورت یک سطر جدید به جدول اضافه کن:
onclick: function () {
                    $('#table tr:last').after("<tr>" +
                    "<td>" + data.Title + "</td>" +
                    "<td>" + data.Description + "</td>" +
                    "<td>" + data.Price + "</td>" +
                    "<td>" + data.Category + "</td>" +
                    "<td>  </td>" +
                    "</tr>");

}
مقادیر به صورت یک شیء جاوااسکریپتی برگردانده خواهند شد:
data {Id: 12, Title: "Item1", Description: "Des", Price: 100000, Category: 0}
که توسط data می‌توانیم به هر کدام از فیلدها، جهت نمایش در خروجی، دسترسی داشته باشیم.
دریافت سورس مثال جاریShowAlertSignalR 
مطالب
استفاده از مسیرهای مطلق در حین import ماژول‌ها در برنامه‌های مبتنی بر TypeScript
در حین import ماژول‌های TypeScript ایی پس از مدتی به یک چنین کدهایی خواهیم رسید:
import { SpecialCollection } from "../../special";
import { LoginComponent } from "../login";
import { TextUtils } from ".../../utils/text";
import { Router } from "../../../core/router";
در این حالت، در یک پوشه برای import ماژولی مشخص، چنین import ایی را خواهیم داشت:
import { Data } from '../data';
و در پوشه‌ی تو در توی دیگری، این تعریف به صورت زیر تغییر می‌کند:
import { Data } from '../../../data';
و در آخر برنامه پر می‌شود از مسیرهای نسبی ‘../../../..’ مانند. به این ترتیب جابجا کردن فایل‌ها و Refactoring آن‌ها، مشکل می‌شود.
خوشبختانه کامپایلر TypeScript به همراه تنظیمات baseUrl و paths است که توسط آن‌ها می‌توان این مسیرهای نسبی را به مسیرهای مطلق تبدیل کرد و در این حالت اهمیتی ندارد که ماژول مدنظر از چه سطحی و درون چه پوشه‌ی تو در تویی فراخوانی می‌شود، این مسیر import همواره ثابت خواهد بود.


تنظیمات فایل tsconfig.json برای معرفی مسیرهای مطلق ماژول‌ها

فرض کنید می‌خواهید از یکی از سرویس‌های Core Module استفاده کنید:


بسته به عمق پوشه‌ی استفاده کننده، به یک چنین تعریفی خواهید رسید:
import { BrowserStorageService } from "./../../core/browser-storage.service";
برای بهبود این وضعیت، فایل tsconfig.json و یا همان تنظیمات کامپایلر TypeScript را به نحو ذیل تکمیل می‌کنیم:
{
  "compilerOptions": {
    "baseUrl": "src",
    "paths": {
      "@app/*": [
        "app/*"
      ],
      "@app/core/*": [
        "app/core/*"
      ],
      "@app/shared/*": [
        "app/shared/*"
      ],
      "@env/*": [
        "environments/*"
      ]
    }
  }
}
در اینجا baseUrl به پوشه‌ی src برنامه اشاره می‌کند و مسیرهای بعدی بر این اساس محاسبه می‌شوند. در ادامه در قسمت paths، ابتدا یک نام مستعار ذکر می‌شود و سپس مسیری که ارائه دهنده‌ی آن است. ذکر @ در اینجا اختیاری است؛ اما ذکر */‌ها اجباری است.
پس از این تغییرات، اکنون افزونه‌ی پیشنهاد دهنده‌ی imports، هر دو حالت استفاده‌ی از مسیر مطلق بر اساس نام مستعار تعریف شده:
 import { BrowserStorageService } from "@app/core/browser-storage.service";
و یا استفاده‌ی از مسیر نسبی را نیز پیشنهاد می‌دهد:
 import { BrowserStorageService } from "./../../core/browser-storage.service";


برای مثال اگر دقت کرده باشید، روش import اجزای خود Angular به صورت زیر است:
 import { Component } from '@angular/core';
علت اینجا است که Angular از تعریف مشابهی به صورت زیر برای نگاشت پوشه‌ی node_modules آن به angular@ استفاده می‌کند:
"paths": {
    "@angular/*": ["node_modules/@angular/*"]
},
و ذکر @ اختیاری هم از اینجا اقتباس شده‌است.


یک نکته‌ی مهم: تنظیمات فوق بدون تنظیمات معادل webpack ناقص هستند

اگر از برنامه‌ی Angular CLI استفاده می‌کنید، تنظیمات ذکر شده، تا همینجا به پایان می‌رسند؛ چون webpack جزئی از Angular CLI است و تنظیمات پیش فرض آن، قسمت baseUrl و paths فایل tsconfig.json را به صورت خودکار پردازش می‌کند. اما اگر از TypeScript در محیط‌های دیگری استفاده می‌کنید که از webpack به صورت مجزایی استفاده می‌کنند، نیاز است قسمت resolve.alias فایل webpack.config.js را نیز جهت معرفی این تغییرات، اصلاح کنید. از این جهت که کامپایلر TypeScript این مسیرهای مطلق را در حین تولید فایل‌های نهایی JavaScript ایی معادل، به مسیرهای نسبی بازنویسی نمی‌کند و در این حالت webpack نمی‌داند که چطور باید این ماژول‌ها را یافته و یکی کند.
resolve: {
  extensions: ['*', '.js', '.ts'],
  modules: [
    rootDir,
    path.join(rootDir, 'node_modules')
  ],
  alias: {
    '@app': 'src/app'
  }
},


کوتاه کردن مسیرهای مطلق با معرفی فایل ویژه‌ی index.ts

تا اینجا بجای ذکر مسیر
import { BrowserStorageService } from "./../../core/browser-storage.service";
به مسیر مطلق زیر رسیدیم (صرفنظر از محل قرارگیری ماژولی که قرار است آن‌را import کند):
import { BrowserStorageService } from "@app/core/browser-storage.service";
این را هم می‌خواهیم به صورت زیر کوتاه‌تر کنیم:
import { BrowserStorageService } from "@app/core";
یعنی فقط app/core@ را ذکر کنیم.

برای اینکار نیاز است فایل ویژه‌ای را به نام index.ts، در ریشه‌ی پوشه‌ی core ایجاد کنیم (src\app\core\index.ts)، با این محتوا:
export * from "./browser-storage.service";
export * from "./app-config.service";
export * from "./seo-service";
در اینجا تمام ماژول‌هایی که توسط Core Module ارائه می‌شوند را یکبار export می‌کنیم.
برای نمونه اگر به پوشه‌ی node_modules\@angular خود مجموعه‌ی Angular هم مراجعه کنید، هر پوشه‌ی src آن به همراه یک فایل index.d.ts شبیه تعاریف فوق نیز هست.

پس از افزودن فایل index.ts به ریشه‌ی پوشه‌ی مدنظر، اکنون در لیست پیشنهادات، ذکر app/core@ تنها نیز ظاهر شده و استفاده‌ی از آن مجاز است:

نظرات مطالب
چگونه پروژه‌های Angular ی سبکی داشته باشیم - قسمت اول
بله برای web application ‌ها اصلا حجم زیادی نیستن اما از نظر خیلی از کارفرماها، حجم بالای 1 مگ برای web site‌ها حجم زیادی است. و همانطور که در موارد ذکر شده مشاهده می‌شود، خیلی از web site‌ها حجم بالایی دارند. 

دلیل خیلی از شرکت‌ها برای انتخاب react  و vue نسبت به انگیولار حجم زیاد و کند بودن آن است (حداقل از همکاران 3 شرکت مختلف این را شنیده ام) . در صورتی که دلیل اصلی آن ساختار اشتباه پروژه هاست
نظرات مطالب
شروع به کار با AngularJS 2.0 و TypeScript - قسمت هشتم - دریافت اطلاعات از سرور
من یه لیست از مدل را در MVC6 به انگیولار بازگشت میدهم و در دریافت مشکلی ندارم ولی موردی که هست این هست که قاعده نام فیلدها را تغییر میدهد مثلا به جای FirstName مقدار جیسون شامل فیلد firstName است که در قاعده جیسون تعریف شده است و بنابراین باعث میشود که در مدل بازگشتی تایپ اسکریپت که من همه فیلدها را دقیقا مشابه Net Core. تعریف کردم قرار نگیرد و در استفاده باید بنویسم firstName نه FirstName.
چگونه میشود این قاعده را از بین برد و دقیقا همانند مدل‌ها این نامگذاری انجام شود؟
مطالب
آموزش MDX Query - قسمت دوازدهم – استفاده از توابع Head , Filter , TopCount , tail
در ادامه به بررسی توابع Head , Filter , TopCount و tail  می‌پردازیم

Select
{
[Measures].[Internet Sales Amount],
[Measures].[Internet Tax Amount]
} on columns,
head(
[Customer].[Customer Geography].[Country],
2
)on rows
From [Adventure Works]

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

حال تصور کنید بخواهیم شرط زیر را بر روی کوئری بالا اجرا کنیم

( [Measures].[Internet Sales Amount] >= '2500000' )

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

کوئری مشابه زیر می‌باشد

 Select  {
[Measures].[Internet Sales Amount],
[Measures].[Internet Tax Amount]
} on columns,
head(
[Customer].[Customer Geography].[Country],
2
)on rows
From [Adventure Works]
Where
( [Measures].[Internet Sales Amount] >= '2500000' )

البته خطای زیر را خواهیم داشت. 

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

Select
Filter(
{
[Measures].[Internet Sales Amount],
[Measures].[Internet Tax Amount]
} ,
[Measures].[Internet Sales Amount] >= 2644017.71
  ) on columns,
head(
[Customer].[Customer Geography].[Country],
3
)on rows
From [Adventure Works]

تابع Filter دو پارامتر می گیرد. پارامتر اول نام ردیف یا ستونی می باشد که روی آن می خواهیم عمل فیلتر را انجام دهیم. پارامتر دوم شرط فیلترینگ می باشد که می بایست مانند T/SQL دارای یک خروجی Boolean باشد

همچنان نتیجه درست نمی‌باشد ! چرا؟

اگر بخواهیم شرط روی Axis  ردیف (کشور ها) اعمال گردد، باید عملیات فیلترینگ در این  Axis  انجام شود . بنابر این خروجی بدست  آمده صحیح نمی باشد زیرا ما عملیات فیلترینگ را روی ستون ها انجام داده ایم.

کوئری زیر را اجرا نمایید

Select {
[Measures].[Internet Sales Amount]
,[Measures].[Internet Tax Amount]
  }
on columns,
head(
Filter(
  [Customer].[Customer Geography].[Country] ,
  [Measures].[Internet Sales Amount] >= 2644017.71
),
3)
on rows
From [Adventure Works]

البته توجه کنید که این کوئری، سه کشور اول که در شرط زیر قرار دارند را بر می گرداند و الزاما این سه کشور از تمام  کشور های دیگر بیشتر نمی باشند.

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

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

Select {
[Measures].[Internet Sales Amount]
,[Measures].[Internet Tax Amount]
  }
on columns,
TopCount(
Filter(
  [Customer].[Customer Geography].[Country] ,
  [Measures].[Internet Sales Amount] >= 2644017.71
  ),
3, [Measures].[Internet Sales Amount])
on rows
From [Adventure Works]

در این حالت به جای تابع Head از تابع  TopCount استفاده گردیده است .این تابع سه کشوری را که بیشترین فروش اینترنتی را داشته اند و این فروش بالاتر از مقدار ذکر شده در شرط می‌ باشد را بر می‌ گرداند .البته در اینجا تابع  topcount  دارای سه پارامتر می‌ باشد و در پارامتر سوم اعلام میکند که تعداد بالای مجموعه  براساس چه شاخصی باید به دست بیاید.

حال اگر بخواهیم سه ردیف انتهایی جدول را واکشی کنیم داریم:

Select
{
[Measures].[Internet Sales Amount],
[Measures].[Internet Tax Amount]
}on columns,
tail([Customer].[Customer Geography].[Country],
3)on rows
From [Adventure Works]

این تابع برعکس تابع Head  کار میکند و N ردیف آ اخر مجموعه را بدست می‌ آورد . البته در بالا فقط 3 ردیف انتهایی را در خروجی آورده ایم و هیچ شرطی اعمال نگردیده است.


مطالب
اجرای برنامه‌های ASP.NET به کمک وب سرور Apache توسط Mono در Ubuntu
در قسمت قبل نحوه‌ی اجرای برنامه‌های ASP.NET را به کمک وب سرور آزمایشی Mono بررسی کردیم. بدیهی است این وب سرور برای هاست کردن سایت‌ها و خدمات دهی عمومی، مناسب نیست و صرفا جهت کارهای برنامه نویسی طراحی شده است. در ادامه، ابتدا وب سرور معروف آپاچی را نصب کرده و سپس سعی خواهیم کرد تا همان دو برنامه ASP.NET Web forms و ASP.NET MVC قسمت قبل را بر روی آن اجرا کنیم (دو برنامه خالی ASP.NET کامپایل شده که از ویندوز به لینوکس کپی شده بودند).


نصب وب سرور آپاچی
به اینترنت متصل شده، ترمینال Ubuntu را گشوده (با میانبر ctrl+alt+t) و سپس فرمان‌های ذیل را صادر کنید:
 sudo apt-get update
sudo apt-get upgrade -y
sudo apt-get install apache2
به این ترتیب Apache web server بر روی سیستم نصب خواهد شد.


نصب ماژول‌های ASP.NET مخصوص آپاچی
سپس نیاز است ASP.NET runtime for Apache را نصب کنیم:
 sudo apt-get install libapache2-mod-mono mono-apache-server4
mod_mono درون پروسه آپاچی اجرا شده و تمام درخواست‌های رسیده به یک وب سایت ASP.NET را به mod-mono-server که کار هاست سایت را انجام می‌دهد، انتقال خواهد داد.
اگر علاقمند به مشاهده تنظیمات آن بودید باید به مسیر etc/apache2/mods-enabled مراجعه کرده و فایل mod_mono.conf را بررسی کنید (اختیاری). برای مثال، در آن حالت اجرا، بر روی ASP.NET 4 تنظیم شده‌است.

تنظیمات آپاچی برای کار با ASP.NET
نیاز است فایل تنظیمات پیش فرض وب سرور آپاچی را جهت معرفی ASP.NET به آن، اندکی ویرایش کنیم:
 sudo gedit /etc/apache2/sites-available/default
در ادیتور باز شده، ذیل قسمت ServerAdmin، موارد زیر را اضافه کنید:
 MonoAutoApplication disabled
AddHandler mono .aspx .ascx .asax .ashx .config .cs .asmx .axd
MonoApplications "/:/var/www"
MonoServerPath default "/usr/bin/mod-mono-server4"
در ادامه به المان Directory /var/www سطرهای زیر را اضافه کنید:
 SetHandler mono
DirectoryIndex index.aspx index.html default.aspx Default.aspx
المان جدید ذیل را در انتهای فایل اضافه کنید تا یک سری از پوشه‌های خاص ASP.NET همانند محیط‌های ویندوزی در آپاچی نیز محافظت شوند:
 <DirectoryMatch "/([bB]in|[Aa]pp_[Cc]ode|[Aa]pp_[Dd]ata|[Aa]pp_[Gg]lobal[Rr]esources|[Aa]pp_[Ll]ocal[Rr]esources)/">
Order deny,allow
Deny from all
</DirectoryMatch>
فایل را ذخیره کرده و ادیتور را ببندید. اکنون وب سرور آپاچی را اجرا و ری استارت کنید:
 sudo service apache2 restart
همچنین سطح دسترسی مسیر var/www که حاوی برنامه‌های ما خواهد بود نیز باید تغییر کند:
 # r,w,exec for user + group
sudo chgrp -R www-data /var/www
sudo chown -R www-data /var/www
sudo chown -R www-data:www-data /var/www
sudo chmod -R 755 /var/www
اکنون اگر آدرس http://127.0.0.1 را در مرورگر وارد کنید، باید پیام «It Works» را مشاهده کنید و اگر خواستید محتوای آن‌را ویرایش کنید، دستور ذیل را صادر نمائید:
 gedit /var/www/index.html

اجرای مثال‌ها

با تنظیمات فوق، برنامه‌های کپی شده در مسیر var/www به کمک مونو و آپاچی اجرا خواهند شد.
دسترسی‌های فعلی کاربر وارد شده به لینوکس اجازه کپی فایل‌ها را به مسیر var/www نمی‌دهد. همچنین می‌خواهیم این کارها را توسط File browser آن انجام دهیم و نه خط فرمان. برای این منظور دستور ذیل را اجرا کنید تا File browser آن با دسترسی مدیریتی اجرا شود:
 sudo nautilus
الان می‌توان بدون مشکل در همان File browser گرافیکی آن، پوشه مثال وب فرم‌های خود را به درون پوشه var/www کپی کرد.
پس از کپی دو برنامه Web forms و MVC ابتدای بحث، نیاز است مجددا فایل تنظیمات آپاچی را ویرایش کنیم:
 sudo gedit /etc/apache2/sites-available/default
اکنون تنظیم برنامه وب فرم را به نحو ذیل به این فایل اضافه کنید:
 AddMonoApplications default "/webforms_test:/var/www/WebFormsApp/"
<Location /webforms_test>
SetHandler mono
</Location>
در تنظیم webforms_test:/var/www/WebFormsApp/، قسمت webforms_test نامی است که در مرورگر وارد خواهد شد و قسمت بعد از : مسیر فایل‌های برنامه و ریشه سایت است.
و تنظیم برنامه MVC به صورت زیر می‌باشد:
 AddMonoApplications default "/mvc_test:/var/www/Mvc4ProjectApp/"
<Location /mvc_test>
SetHandler mono
</Location>
بعد از هر بار تغییر فایل تنظیمات آپاچی باید یکبار وب سرور را توسط دستور sudo service apache2 restart ری استارت کنیم.
اکنون برنامه وب فرم‌ها در مسیر http://127.0.0.1/webforms_test و برنامه MVC در مسیر http://127.0.0.1/mvc_test قابل دسترسی است.


خلاصه بحث
پس از نصب وب سرور آپاچی و ماژول مونوی مخصوص آن، فایل etc/apache2/sites-available/default را به نحو ذیل ویرایش کنید و مسیرهای برنامه‌های خود را در آن تعریف نمائید:

<VirtualHost *:80>
ServerAdmin webmaster@localhost
        MonoAutoApplication disabled
        AddHandler mono .aspx .ascx .asax .ashx .config .cs .asmx .axd
        MonoApplications "/:/var/www"
        MonoServerPath default "/usr/bin/mod-mono-server4"

DocumentRoot "/var/www"
<Directory />
Options FollowSymLinks
AllowOverride None
</Directory>
<Directory /var/www/>
                SetHandler mono
                DirectoryIndex index.aspx index.html default.aspx Default.aspx
Options Indexes Includes FollowSymLinks MultiViews
AllowOverride None
Order allow,deny
allow from all                
</Directory>

ScriptAlias /cgi-bin/ /usr/lib/cgi-bin/
<Directory "/usr/lib/cgi-bin">
AllowOverride None
Options +ExecCGI -MultiViews +SymLinksIfOwnerMatch
Order allow,deny
Allow from all
</Directory>

        <DirectoryMatch "/([bB]in|[Aa]pp_[Cc]ode|[Aa]pp_[Dd]ata|[Aa]pp_[Gg]lobal[Rr]esources|[Aa]pp_[Ll]ocal[Rr]esources)/">
          Order deny,allow
          Deny from all
        </DirectoryMatch>

        AddMonoApplications default "/webforms_test:/var/www/WebFormsApp/"
        <Location /webforms_test> 
         SetHandler mono
        </Location>

       AddMonoApplications default "/mvc_test:/var/www/Mvc4ProjectApp/"
        <Location /mvc_test> 
         SetHandler mono
        </Location>

ErrorLog ${APACHE_LOG_DIR}/error.log

# Possible values include: debug, info, notice, warn, error, crit,
# alert, emerg.
LogLevel warn

CustomLog ${APACHE_LOG_DIR}/access.log combined
</VirtualHost>