مطالب
نمایش Subgrid در jqGrid
اگر به مثال «فرمت کردن اطلاعات نمایش داده شده در jqGrid» دقت کنید، لینکی را جهت نمایش یک popup جزئیات رکورد انتخاب شده قرار دادیم. شاید طراحی بهتر به این صورت باشد که یک دکمه‌ی + در کنار ردیف قرار دهیم. با کلیک کاربر بر روی این دکمه، جزئیات این ردیف، از سرور دریافت شده و به صورت یک زیر گرید نمایش داده شود. در ادامه همان مثال را با همان ساختار داده‌ای و کدهای سمت سرور، جهت کار با subgrids بازنویسی خواهیم کرد.



فعال سازی Subgrid

در اینجا مواردی را که باید جهت فعال سازی subgrid به تعاریف اولیه‌ی jqGrid اضافه کرد، مشاهده می‌کنید:
            $('#list').jqGrid({
                caption: "آزمایش یازدهم",
                // ... 
                jsonReader: {
                // ... 
                    subgrid: { root: "Rows", repeatitems: true, cell: "RowCells" }
                },
                // ... 
                subGrid: true,
                subGridModel: [{
                    name: ['شرکت', 'آدرس', 'کد پستی', 'شهر', 'کشور', 'تلفن', 'وب سایت'],
                    width: [100, 100, 100, 100, 100, 100, 100],
                    align: ['center', 'center', 'center', 'center', 'center', 'center', 'center'],
                    params: ['@(StronglyTyped.PropertyName<Product>(x=>x.Name))']
                }],
                subGridOptions:{ 
                    reloadOnExpand : false //load only once
                },
                subGridUrl: '@Url.Action("GetGetSupplierData", "Home")'
            });
چون قصد داریم در سمت سرور، از همان ساختار JqGridData خودمان برای بازگشت اطلاعات subgrid استفاده کنیم، نیاز است خاصیت subgrid مربوط به jsonReader را اندکی ویرایش کنیم تا jqGrid بداند که بجای cell قرار است RowCells دریافت کند.
با تنظیم subGrid: true نمایش ستون + داری که در تصویر فوق مشخص است، انجام می‌شود.
subGridModel بیانگر ساختار اطلاعاتی است که قرار است نمایش داده شوند.
آرایه name، نام سر ستون‌ها را مشخص می‌کند.
 آرایه width، عرض ستون‌های زیرگرید را مقدار دهی خواهد کرد.
آرایه align محل و سمت قرارگیری هر یک از مقادیر سلول‌ها را تعیین می‌کند.
 آرایه params اختیاری است. زمانیکه کاربر بر روی یک + ستون subgrid، برای باز شدن این زیرگرید کلیک می‌کند، صرفا Id ردیف به سرور ارسال می‌شود. اگر در این بین می‌خواهید، خاصیت خاصی از گرید اصلی نیز به سرور ارسال شود، آرایه params را مقدار دهی کنید. برای نمونه در اینجا Name ردیف انتخاب شده نیز به ارسال ارسال خواهد شد (برگه شبکه شکل فوق).
subGridOptions یک سری تنظیمات اضافه را به همراه دارد. اگر می‌خواهید اطلاعات زیرگرید فقط یکبار بارگذاری شود و با هربار کلیک کاربر از سرور دریافت نگردد، خاصیت reloadOnExpand آن‌را false کنید.
 subGridUrlآدرسی که تامین کننده اطلاعات JSON زیرگرید می‌باشد.

در این حالت، کدهای سمت سرور بازگشت اطلاعات زیر گرید به شکل زیر می‌باشد:
        public ActionResult GetGetSupplierData(int id, string name)
        {
            var list = ProductDataSource.LatestProducts;
            var products = list.Where(x => x.Id == id).ToList();
            if (!products.Any())
                return Json(null, JsonRequestBehavior.AllowGet);

            var productsData = new JqGridData
            {
                Rows = (products.Select(product => new JqGridRowData
                {
                    Id = product.Id,
                    RowCells = new List<string> 
                               {
                                    product.Supplier.CompanyName,
                                    product.Supplier.Address,
                                    product.Supplier.PostalCode,
                                    product.Supplier.City,
                                    product.Supplier.Country,
                                    product.Supplier.Phone,
                                    product.Supplier.HomePage
                                }
                })).ToList()
            };
            return Json(productsData, JsonRequestBehavior.AllowGet);
        }
همانطور که ملاحظه می‌کنید، حالت ساده شده‌ی JqGridData بازگشت داده می‌شود. زیرا در حالت نمایش زیرگرید، مباحث مرتب سازی اطلاعات و همچنین paging فعال نیستند و نیازی به اطلاعات آن‌ها نیست.


کدهای کامل این مثال را از اینجا می‌توانید دریافت کنید
jqGrid11.zip
مطالب
کدام سلسله متدها، متد جاری را فراخوانی کرده‌اند؟
یکی از نیازهای نوشتن یک برنامه‌ی پروفایلر، نمایش اطلاعات متدهایی است که سبب لاگ شدن اطلاعاتی شده‌اند. برای مثال در طراحی interceptorهای EF 6 به یک چنین متدهایی می‌رسیم:
        public void ScalarExecuted(DbCommand command,
                                   DbCommandInterceptionContext<object> interceptionContext)
        {
        }

سؤال: در زمان اجرای ScalarExecuted دقیقا در کجا قرار داریم؟ چه متدی در برنامه، در کدام کلاس، سبب رسیدن به این نقطه شده‌است؟
تمام این اطلاعات را در زمان اجرا توسط کلاس StackTrace می‌توان بدست آورد:
        public static string GetCallingMethodInfo()
        {
            var stackTrace = new StackTrace(true);
            var frameCount = stackTrace.FrameCount;

            var info = new StringBuilder();
            var prefix = "-- ";
            for (var i = frameCount - 1; i >= 0; i--)
            {
                var frame = stackTrace.GetFrame(i);
                var methodInfo = getStackFrameInfo(frame);
                if (string.IsNullOrWhiteSpace(methodInfo))
                    continue;

                info.AppendLine(prefix + methodInfo);
                prefix = "-" + prefix;
            }

            return info.ToString();
        }
ایجاد یک نمونه جدید از کلاس StackTrace با پارامتر true به این معنا است که می‌خواهیم اطلاعات فایل‌های متناظر را نیز در صورت وجود دریافت کنیم.
خاصیت stackTrace.FrameCount مشخص می‌کند که در زمان فراخوانی متد GetCallingMethodInfo که اکنون برای مثال درون متد ScalarExecuted قرار گرفته‌است، از چند سطح بالاتر این فراخوانی صورت گرفته‌است. سپس با استفاده از متد stackTrace.GetFrame می‌توان به اطلاعات هر سطح دسترسی یافت.
در هر StackFrame دریافتی، با فراخوانی stackFrame.GetMethod می‌توان نام متد فراخوان را بدست آورد. متد stackFrame.GetFileLineNumber دقیقا شماره سطری را که فراخوانی از آن صورت گرفته، بازگشت می‌دهد و stackFrame.GetFileName نیز نام فایل مرتبط را مشخص می‌کند.

یک نکته:
شرط عمل کردن متدهای stackFrame.GetFileName و stackFrame.GetFileLineNumber در زمان اجرا، وجود فایل PDB اسمبلی در حال بررسی است. بدون آن اطلاعات محل قرارگیری فایل سورس مرتبط و شماره سطر فراخوان، قابل دریافت نخواهند بود.


اکنون بر اساس این اطلاعات، متد getStackFrameInfo چنین پیاده سازی را خواهد داشت:
        private static string getStackFrameInfo(StackFrame stackFrame)
        {
            if (stackFrame == null)
                return string.Empty;

            var method = stackFrame.GetMethod();
            if (method == null)
                return string.Empty;

            if (isFromCurrentAsm(method) || isMicrosoftType(method))
            {
                return string.Empty;
            }

            var methodSignature = method.ToString();
            var lineNumber = stackFrame.GetFileLineNumber();
            var filePath = stackFrame.GetFileName();

            var fileLine = string.Empty;
            if (!string.IsNullOrEmpty(filePath))
            {
                var fileName = Path.GetFileName(filePath);
                fileLine = string.Format("[File={0}, Line={1}]", fileName, lineNumber);
            }

            var methodSignatureFull = string.Format("{0} {1}", methodSignature, fileLine);
            return methodSignatureFull;
        }
و خروجی آن برای مثال چنین شکلی را خواهد داشت:
 Void Main(System.String[]) [File=Program.cs, Line=28]
که وجود file و line آن تنها به دلیل وجود فایل PDB اسمبلی مورد بررسی است.

در اینجا خروجی نهایی متد GetCallingMethodInfo به شکل زیر است که در آن چند سطح فراخوانی را می‌توان مشاهده کرد:
 -- Void Main(System.String[]) [File=Program.cs, Line=28]
--- Void disposedContext() [File=Program.cs, Line=76]
---- Void Opened(System.Data.Common.DbConnection, System.Data.Entity.Infrastructure.Interception.DbConnectionInterceptionContext) [File=DatabaseInterceptor.cs,Line=157]

جهت تعدیل خروجی متد GetCallingMethodInfo، عموما نیاز است مثلا از کلاس یا اسمبلی جاری صرفنظر کرد یا اسمبلی‌های مایکروسافت نیز در این بین شاید اهمیتی نداشته باشند و بیشتر هدف بررسی سورس‌های موجود است تا فراخوانی‌های داخلی یک اسمبلی ثالث:
        private static bool isFromCurrentAsm(MethodBase method)
        {
            return method.ReflectedType == typeof(CallingMethod);
        }

        private static bool isMicrosoftType(MethodBase method)
        {
            if (method.ReflectedType == null)
                return false;

            return method.ReflectedType.FullName.StartsWith("System.") ||
                   method.ReflectedType.FullName.StartsWith("Microsoft.");
        }


کد کامل CallingMethod.cs را از اینجا می‌توانید دریافت کنید:
CallingMethod.cs
مطالب
استفاده از BroadcastChannel در جاوا اسکریپت

یکی از API ‌های کاربردی و جدید در دنیای وب، BroadcastChannel است که امکان ارسال اطلاعات بین window‌ها ، Tab‌ها و iframe‌های مختلف را که در یک دامنه هستند، فراهم می‌کند.  برای مثال اگر شما در مرورگری در پنجره‌های مختلف یک سایت را باز کرده باشید، با تغییر در یکی از این پنجره‌ها، قادر خواهید بود سایر پنجر‌ها را هم مطلع کنید تا در صورت نیاز، مجددا بارگذاری شوند. 


چرا از این API استفاده کنیم؟ 

یکی از وب سایت‌های مورد علاقه‌ی خود را در مرورگر باز کنید. مثلا یوتیوب و لاگین کنید. حالا در پنجره‌ی جدیدی، همین وب سایت را مجددا باز کرده و لاگین کنید. حالا در یکی از پنجره‌ها، از یوتیوب Logout کنید. خب شما حالا در یکی از پنجره‌ها لاگین هستید و در یکی دیگر Logout کرده‌اید. حالا پنجره‌های مرورگر شما دارای دو وضعیت متفاوت هستند. Logged-in در برابر Logged-out و این گاهی باعث دردسر خواهد شد.

این وضعیت حتی میتواند باعث خطرهای امنیتی نیز بشود. تصور کنید که کاربری در یک فضای عمومی مثل یک کافی شاپ وارد سایت شما شده‌است و داشبرد مخصوص به خود را باز کرده‌است. بنا به دلایلی کاربر قصد ترک محل را کرده و طبیعتا از برنامه شما Logout خواهد کرد . در این حالت اگر این کاربر برنامه شما را در صفحات مختلف مرورگر باز کرده باشد و لاگین نیز کرده باشد، هر کسی که بعد از او قصد استفاده از این کامپیوتر را داشته باشد ،  میتواند به اطلاعات کاربر مورد نظر در آن صفحات دسترسی پیدا کند؛ چه این اطلاعات روی صفحه باشد و چه مثلا اطلاعات یک JWT token.  چون کاربر فراموش کرده در صفحات دیگر هم Logout کند.


کد نویسی BroadcastChannel API 

در نگاه اول، استفاده از این API  ممکن است سخت به نظر برسد؛ ولی در واقع خیلی راحت است. برای نمونه قطعه کد زیر را درنظر بگیرید: 

<!DOCTYPE html>
<body>
  <!-- The title will change to greet the user -->
  <h1 id="title">Hey</h1>
  <input id="name-field" placeholder="Enter Your Name"/>
</body>
<script>
var bc = new BroadcastChannel('gator_channel');
(()=>{
  const title = document.getElementById('title');
  const nameField = document.getElementById('name-field');
  const setTitle = (userName) => {
    title.innerHTML = 'Hey ' + userName;
  }
  bc.onmessage = (messageEvent) => {
    // If our broadcast message is 'update_title' then get the new title from localStorage
    if (messageEvent.data === 'update_title') {
      // localStorage is domain specific so when it changes in one window it changes in the other
      setTitle(localStorage.getItem('title'));
    }
  }

  // When the page loads check if the title is in our localStorage
  if (localStorage.getItem('title')) {
    setTitle(localStorage.getItem('title'));
  } else {
    setTitle('please tell us your name');
  }
  nameField.onchange = (e) => {
    const inputValue = e.target.value;
    // In the localStorage we set title to the user's input
    localStorage.setItem('title', inputValue);
    // Update the title on the current page
    setTitle(inputValue);
    // Tell the other pages to update the title
    bc.postMessage('update_title');
  }
})()
</script>

این کد شامل یک Input باکس و یک title است. وقتی کاربر نام خود را در Input باکس وارد می‌کند، برنامه آن را در Localstorage با کلیدی به نام userName ذخیره می‌کند و بعد title صفحه جاری را به سلام + userName تغییر می‌دهد. مثلا اگر کاربر در Input باکس، عبارت بابک را وارد کند، title صفحه به سلام بابک تغییر داده میشود.

بدون BroadcastChannel، چنانچه کاربر در پنجره‌های مختلف مرورگر، برنامه را باز کرده باشد، تغییری در Title آن صفحات داده نخواهد شد؛ مگر اینکه مجددا توسط کاربر بارگذاری شود. 

در کد فوق ما یک وهله از BroadcastChannel را به نام gator_channel ایجاد کرده‌ایم و بعد onmessage را مساوی متدی با یک آرگومان جهت دریافت پیام قرار داده‌ایم. در این متد چک شده که اگر نام پیام، مساوی update_title باشد، متغیر ذخیره شده‌ی در LocalStorage خوانده شود.

هربار که متد postMessage ، از BroadcastChannel را فراخوانی میکنیم، این متد، باعث اجرای متد onmessage در سایر پنجره‌ها می‌شود. پس اگر در پنجره‌ی جاری در Input باکس، کلمه فرهاد را بنویسیم، متد bc.postMessage('update_title') در پنجره جاری اجرا شده و باعث اجرای متد onmessage در سایر پنجره‌هایی که سایتمان در آن باز است میشود.


این API در چه حالت‌هایی کار میکند

برخلاف سایر API‌ها مثل window.postMessage، شما لازم نیست چیزی در مورد اینکه چند تا صفحه از سایتتان بر روی مرورگر جاری باز شده را بدانید. (توجه کنید که روی عبارت «مرورگر جاری» تاکید میکنم. چون اگر برنامه روی دو مرورگر مثلا Chrome و Firefox به صورت همزمان باز باشد، این API فقط روی صفحات باز مرورگر جاری فراخوانی خواهد شد و نه مرورگر دوم؛ توضیحات بیشتر در ادامه داده شده است)

BroadcastChannel فقط روی مرورگر جاری و صفحاتی از یک دامنه، اجرا خواهد شد. این به این معنا است که شما می‌توانید پیام‌هایتان را از دامنه مثلا : https://alligator.io به دامنه https://alligator.io/js/broadcast_channels ارسال کنید. تنها نکته‌ای که لازم است تا رعایت شود این است که آبجکت BroadcastChannel در هر دو صفحه، از یک نام برای channel استفاده کرده باشند:

const bc = new BroadcastChannel('alligator_channel');
bc.onmessage = (eventMessage) => {
  // do something different on each page
}


در حالتهای زیر این API کار نخواهد کرد:

هاست‌های متفاوت:

https://alligator.io 

https://www.aligator.io

پورت‌های متفاوت:

https://alligator.io 

https://alligator.io :8080

پروتکل‌های متفاوت:

https://alligator.io 

http://alligator.io 

و یا برای مثال اگر مثلا در مرورگر Chrome یکی از صفحات به صورت Incognito باز شده باشد.


سازگاری این API با مرورگرهای مختلف

با توجه به اطلاعات سایت caniuse.com، این API در 75.6% مرورگرها پشتیبانی میشود. ولی مرورگرهای Safari و Internet Explorer از این API پشتیبانی نمی‌کنند. همچنین امکان استفاده از این API توسط کتابخانه sysend.js نیز فراهم شده‌است.


چه نوع پیامهایی را می‌توانید به کمک این API ارسال کنید

  • تمامی تایپ‌ها (Boolean,Null, Undefined,Number,BigInt, String) به غیر از symbol
  • آبجکتهای Boolean و String
  • Dates
  • Regular Expressions
  • Blobs
  • Files, File Lists
  • Array Buffer, ArrayBufferViews
  • ImageBitmaps, ImageDates
  • Arrays,Objects,Maps and Sets
در صورت ارسال تایپی از نوع symbol، با خطایی در سمت ارسال کننده مواجه خواهید شد.

قطعه کد زیر، بجای string، یک object را ارسال می‌کند:

bc.onmessage = (messageEvent) => {
  const data = messageEvent.data
  // If our broadcast message is 'update_title' then get the new title from localStorage
  switch (data.type) {
    case 'update_title':
      if (data.title){
        setTitle(data.title);
      }
      else
        setTitle(localStorage.getItem('title'));
      break
    default:
      console.log('we received a message')
  }
};
// ... Skipping Code
bc.postMessage({type: 'update_title', title: inputValue});


چه کارهایی را میتوانید به کمک این API انجام دهید

چیزهای زیادی را میتوان مجسم کرد. محتمل‌ترین گزینه، به اشتراک گذاری state جاری برنامه است. برای مثال اگه از کتابخانه‌های flux یا redux برای مدیریت state برنامه استفاده می‌کنید، به کمک این API می‌توانید state جاری را در تمامی صفحات باز برنامه، بروز رسانی کنید. حتی می‌توانید به چیزی شبیه به machine state فکر کنید.

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

به کمک دستور ()bc.close در هر زمانی میتوانید channel باز شده را ببندید و مجددا بسته به وضعیت برنامه، آن را باز کنید.

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

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

انواع هاست
هاست‌ها بر سه نوع تقسیم می‌شوند:
  1. هاست اشتراکی
  2. سرورهای مجازی
  3. سرور اختصاصی

هاست اشتراکی
در این حالت شرکت میزبان یک سرور را به چند سایت تقسیم کرده و به هر وب سایت، با توجه به پلنی که مشتری انتخاب کرده، مقداری از منابع را اختصاص می‌دهد که عموما این تخصیص منابع در زمان خرید توسط WHMCS به طور خودکار صورت می‌گیرد. در این حالت ممکن است بر روی یک سرور بیش از یکصد وب سایت در حال سرویس گرفتن باشند. مزیت این هاست‌ها قیمت ارزان آن‌ها می‌باشد . عموما وب سایت‌های با بازدید کم و نیاز به منابع کمتر، از این دست هاست‌ها استفاده می‌کنند. در این نوع هاست‌ها در صورتیکه استفاده‌ی از منابع به نهایت مقداری که برای آن مشخص شده است برسد، از قبیل ترافیک (که بیشتر مسئله مربوط به آن است) یا دیسک سخت و ...  به حد مشخص شده برسند، سایت را متوقف یا suspend می‌کنند. پرداخت این نوع هاست‌ها به خاطر قیمت پایین به صورت یکساله دریافت می‌شود. قیمست سالیانه آن‌ها در بعضی جاها از 50 هزار تومان تا صدهزار تومان به عنوان مبلغ آغازین شروع می‌شود.

سرورهای مجازی VPS یا Virtual Private Server
این‌ها هم تقریبا مثل هاست‌های اشتراکی هستند با این تفاوت که منابع بیشتر و دسترسی بیشتری به شما داده می‌شوند؛ به طوری که احساس می‌کنید به شما یک سرور واقعی را داده‌اند و عموما تعداد سایت هایی که روی آن سرویس می‌گیرند، به مراتب کمتر از هاست اشتراکی است. اکثر وب سایت‌هایی که توانایی استفاده از هاست‌های اشتراکی را ندارند، از این نوع هاستینگ بهره می‌برند. قیمتش بالا‌تر از یک هاست اشتراکی است ولی به مراتب پایین‌تر از یک سرور اختصاصی است. پرداخت این نوع هاست عموما به دو صورت ماهیانه و یا سالانه است که احتمال زیادی دارد پرداخت سالیانه تخفیف خوبی را شامل شود. در صورت رسیدن به حد نهایت منابع همانند هاست اشتراکی با شما رفتار خواهد شد. این سرورهای مجازی در ایران عموما از مبلغ ماهیانه 20 هزار تومان به بالا آغاز می‌شوند.


سروهای اختصاصی یا Dedicate
این‌ها دیگر سروهای واقعی هستند و صاحب اول و آخرشان شمایید و دسترسی کامل به همه اجزا و منابع آن را دارید. این سرورهای عموما برای فعالیت‌های بزرگ تجاری و بازدیدهای همزمان به شدت بالا استفاده می‌شوند. قیمت، بسته به مشخصات آن متفاوت است و ممکن است یکی ماهیانه 200 هزار تومان، یکی ماهیانه 500 هزار تومان و .. آغاز شود.
نکته ای در مورد سرورهای اختصاصی و حتی گاها VPSها هست اینکه عموما پشتیبانی این‌ها توسط شما تامین می‌شود و  یا باید یک مدیر سرور با حق ماهیانه اختیار کنید یا در صورت بروز مشکل به صورت ساعتی حق حل مشکل را بدهید. بهتر هست این مورد را از قبل توسط میزبان جویا شوید.

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


چک لیست


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


فضای دیسک و پهنای باند (ترافیک)
اولین نکاتی که همیشه توسط میزبان‌ها و خریداران مورد توجه قرار می‌گیرند، این دو مورد هست. در صورتیکه سایت شما اجازه آپلودی به کاربران نمی‌دهد، یا خودتان هم آپلود چندانی ندارید، می‌توانید فضای دیسک سخت پایین‌تری را انتخاب کنید. مثلا اگر شما تعدادی تصویر دارید و مقدار زیادی فایل متنی و ... به نظر یک گیگ کفایت می‌کند و در صورتیکه بعدها دیدید این فضا رو به اتمام است، می‌توانید به میزبان درخواست افزایش و ارتقاء آن را بدهید؛ اما برای بار اول یک گیگ به خوبی کفایت می‌کند. ولی اگر چنانچه با فایل‌های حجیم سرو کله میزنید و یا تعداد فایل‌هایی چون تصاویر، صوت و ویدیو در آن زیاد دیده می‌شود، بهتر است به فکر فضایی بیشتر و حتی گاها unlimited باشید. برای سایت‌هایی که حجم به شدت بالایی دارند و یا مثلا نیاز به راه اندازی بخش دانلود دارند، بهتر هست از یک هاستینگ به نام هاست دانلود استفاده کنند. این نوع هاست‌ها به شما اجازه میزبانی فایل‌های وب سایت‌هایی چون aspx,mvc,php را نمی‌دهند و بیشتر پهنای باند و فضای دیسک سخت آن مدنظر است. در این حالت سایت خود را روی یک هاست معمولی به اشتراک می‌گذارید و فایل‌های خود را روی هاست دانلود قرار می‌دهید و ارتباط آن‌ها را لینک می‌کنید.

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

در سناریوی اول یک انجمن VB را بررسی می‌کنیم: عموم انجمن‌ها در زمینه‌ی چند رسانه‌ای مثل تصاویر و ...، چیزی جز تصاویر پوسته خود  (که کش هم می‌شوند)   را انتقال نمی‌دهند. به این دلیل که اجازه‌ی آپلود را به کار نمی‌دهند و کاربرها بیشتر از سرویس‌های ثالث برای تصاویر بهره می‌برند و به غیر از پوسته‌ی، سایت متون انجمن هستند که متن هم ترافیک پایینی را مصرف می‌کند. پس در این حالت ترافیک ماهیانه‌ی 10 گیگ میتواند برای شروع کار تا مدتی که سایت بازدیدکننده‌های خودش را پیدا کند، مناسب باشد. از نظر دیسک سخت هم به نظر من اگر شما نیاز به قرار دادن تصاویری چون اسلایدشو‌ها و ... را دارید، به انضمام آواتار کاربران، 500 مگابایت می‌تواند کافی باشد. حجم آواتار کاربران در قسمت مدیریت، کنترل شده است و تنظیم دستی آن می‌تواند این مقدار حجم آواتارها را افزایش دهد.

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

سرعت Speed و توان سرویس دهی(Uptime)
بعد از ارزیابی موارد بالا نوبت به این دو مورد می‌رسد. اینکه هاست شما به قولی چقدر دوام و ایستادگی دارد، بسیار مهم است و در صورتیکه این امر محقق نگردد، می‌تواند موجب از دست دادن و شنیدن اعتراض کاربرها باشید. uptime به معنی این است که در یک دوره‌ی زمانی چقدر وب سایت شما در دسترس بوده است؛ درست  شنیدید!  حتما پیش خودتان میگویید خوب، یک وب سایت همیشه در دسترس است. ولی واقعیت را بخواهید خیر، اینگونه نیست. اگر سرویس دهنده‌ی خوبی را انتخاب نکنید، ممکن است در روز یا هفته یا در هرمقطع زمانی، با در دسترس نبودن وب سایت روبرو شوید؛ تقریبا مشابه زمانیکه adsl شما قطع می‌شود، چند لحظه‌ای (طولانی‌تر از زمان عادی) مرورگر شما سعی کرده و می‌بیند که زورش نمی‌رسد و یک پیام عدم دسترسی را به شما نمایش می‌دهد.

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

فاکتور بعدی سرعت سرور است و کاربران به این مورد هم اهمیت ویژه‌ای می‌دهند. به خوبی به یاد دارم، اولین هاستی که در کارم استفاده کردم و به پیشنهاد دوستم که در آن کار می‌کرد این هاست را خریداری کردم، اوایل خوب بود، ولی مشکل سرعت بعدها اضافه شد که حتی ورود به پنل مدیریتی چند دقیقه‌ای طول می‌کشید. یا اینکه چند وقت پیش وب سایتی را مطالعه می‌کردم که فقط متن جابجا میکرد، ولی به طوری که کندتر از زمان اجرای یک سایت با گرافیک متوسط طول می‌کشید. (البته این نکته حائز اهمیت است خود وب سایت هم باید از این لحاظ مورد بررسی قرار گیرد و مشکل را سریعا گردن سرویس دهنده نیندازیم).

برای اینکه بدانیم که سرعت یک هاست چگونه باید ارزیابی شود باید سه مورد را بررسی کنیم :
  1. دیتاسنتر
  2. سرور
  3. نزدیکی سرور به محل زندگی کاربران هدف
دو مورد اول تا حدی فنی هستند و اصلا ممکن هست به چنین اطلاعاتی دسترسی هم نداشته باشید؛ هر چند میزبان - در صورتی که قصد خرید سروری را دارید - در صورت پرسش شما باید این اطلاعات را در اختیار شما بگذارد. عموما چیزی که پیشنهاد می‌شود دیتاسنترهای SAS70 Type 2 هستند و سرورهای DELL، امروزه خوب مورد استقبال قرار گرفته‌اند. در رده‌های بعدی میتوان HP را هم مثال زد.
مورد سوم نزدیکی سرور به کاربران هدف است. هر چقدر traceroute و پینگ زمانی کمتری به سرور داشته باشید، دسترسی به اطلاعات آن سریعتر می‌شود. در ایران عموما از کشورهایی چون آمریکا، کانادا ، انگلیس ، آلمان و استرالیا سرور خریده می‌شود و به مدت چندسالی است که در ایران هم این امکان فراهم شده است ولی خب مسائلی که این سرورها در ایران دارند باعث می‌شوند عده‌ی زیادی دور آن‌ها را که هیچ روی آن‌ها را هم خط بکشند.

مشکلاتی که این سرورها دارند بیشتر به سه مورد زیر بر میگردد:

هزینه‌ی سنگین ترافیک : این مورد آن قدر به وضوح پیداست که شما را به کل برای هر نوع خریدی پشیمان می‌کند؛ مگر اینکه پولتان از جایی تامین میشود. ادارات دولتی عموما این نوع هاست را بر میدارند و یا سایت‌های اشتراکی که منابع زیادی را مصرف نمی‌کنند و یا شرکت‌ها یا اشخاصی که پول تامین را دارند.

عدم دسترسی به شبکه‌های اجتماعی یا هرسرویس دهنده‌ی مسدود شده : من این را تست نکردم ولی با دو دوتا چهارتا کردن این حساب دستم آمد که وقتی سایتی مثل فیس بوک و توئیتر در ایران مسدود باشند، باید روی این سرورها هم مسدود باشند. در نتیجه اگر سایت شما مطالبش را در این سایت‌ها معرفی می‌کند و می‌خواهید پست و توئیتی روی آن‌ها داشته باشید، احتمالا توانایی این کار را نخواهید داشت. مگه اینکه خودتان دستی بروید در سایت مربوطه و اضافه کنید.

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

امنیت
این مورد شامل دو بخش می‌شود یکی امنیت دسترسی به سرور و دیگر امنیت نگهداری اطلاعات. نصب فایروال‌ها روی سرور و نظارت 24 ساعته روی سرورهای شرکت (که شامل بخش پشتیبانی می‌شود) می‌تواند این موردها را پوشش دهد. هر چند این مورد را زیاد نمی‌توانید محک بزنید، ولی اگر اخباری چون «همکاری با یک شرکت امنیتی جهت تست سرور و همکاری با تعدادی هکر جهت تست سرور» و ... را شنیدید، می‌توانید این اطمینان را کسب کنید که آن‌ها به این مورد اهمیت میدهند.

امنیت اطلاعات چون پشتیبان‌های روزانه و نگهداری اطلاعات تا مدتی معین پس از اتمام قرارداد و موارد این چنینی، می‌تواند شما را مطمئن سازد. در مورد یکی از مشتریانم به خاطر دارم که فراموش کرده بود هاست را تمدید کند و یک روز بعد از انقضاء ما درخواست دیتابیس را داشتیم که برای ما ارسال کنند و به گفته‌ی خودشان ما مسئولیتی در قبال نگه داری اطلاعات نداریم، ولی تا 5 روز نگه میداریم که البته گفتند هر چه به دنبال دیتابیس گشتند، چیزی پیدا نکردند که البته این مشکل را از راهکار دیگری حل کردیم و ماجرا به خوشی تمام شد. ولی جالب این بود که اینقدر که من حرص خوردم، چطوری به مدیر این‌ها بگوئیم که اطلاعات تیمشان پریده و این‌ها حرص نخوردند و بی خیال.

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

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

پشتیبانی فنی
موردی را تصور کنید که ساعت 2 شب هست و سایت شما هم که در زمینه‌ی مهم و حساسی فعالیت می‌کند، یک دفعه سرویس آن قطع می‌شود و برای سرور مشکلی پیش می‌آید. در این موقع چکاری را انجام می‌دهید؟ صبر میکنید تا صبح شود و سرویس شما راه بیفتد و کاربران هم از سایت شما نا امید شوند؟ اگر این مشکل به دفعات رخ بدهد چه؟ اگر چند روز پشت سرهم تعطیلی باشد چه؟
همه‌ی این موارد مربوط به پشتیبانی می‌شود. این پشتیبانی‌ها عموما به صورت چت و تلفنی (بیشتر مربوط به ساعات اداری) و ارسال تیکت در سامانه پشتیبانی می‌شود. البته تجربه‌ی من میگوید در ایران زیاد به متون نوشته‌ی روی سایت میزبان توجهی نکنید و این را هم به موضوع تحقیق خود اضافه کنید. بعضی‌ها می‌گویند 24 ساعته پشتیبانی تلفنی دارند ولی هر بار زنگ میزنی کسی پاسخ‌ی نمیدهد. یا تیکت می‌زنید و بار‌ها و بارها پشت سر هم تیکت "چی شد؟" را برای جویا شدن ارسال می‌کنید. (البته انصاف هم داشته باشید و پنج دقیقه پنج دقیقه این تیکت را نفرستید).

پشتیبانی هر نوع سرور را مطلع شوید. بعضی‌ها واقعا پشتیبانی دارند!؟ ولی وقتی زنگ می‌زنید می‌گویند این پشتیبانی مربوط به لینوکس است و بچه‌های ویندوز فقط ساعات اداری هستند. خلاصه حواستان را جمع کنید که بچه‌های آن بخش همیشه باشند.
بنابراین مطمئن شوید که یک پشتیبانی 24/7 واقعی داشته باشند.

امکانات اضافه
در کنار خود هاست یک سری ویژگی‌هایی چون FTP و تعدادی دامنه‌های پشتیبانی شده و زیر دامنه‌ها و تعداد دیتابیس‌ها و ایمیل و ... هم هستند که در قدیم یادم هست محدودیت‌هایی در این رابطه ایجاد کرده بودند که امروزه از این نظر در اکثر هاست‌ها آن طور که دیدم این محدودیت‌ها رفع شده است که البته خیلی هم خوب هست و این محدودیت‌ها بیشتر شبیه سودجویی بود تا چیز دیگر.

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

کل مطالب گفته شده در چک لیست به طور خلاصه : اول از همه بررسی فضای ذخیره سازی و پهنای باند، بعد از آن بررسی توانایی سرویس دهی و سرعت سرورها، داشتن محیط امن و پشتیبانی مناسب بود. 

هاستینگ در قرارداد

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

در صورتی که مشتری نمیتواند شخصا خرید هاستینگ را انجام دهد یا در شرکت مسئولین IT ندارند که این کار را انجام دهند و این مورد به شما محول میشود، در قرارداد ذکر شود که شما هیچ گونه مسئولیتی در قبال مشکلاتی که برای هاست و دامنه رخ میدهد ندارید و تنها میتوانید به عنوان یک واسط یا وکیل در ازای مبلغ توافق شده‌ای مشکل را با مسئولین هاست و دامنه در میان گذاشته و ماجرا را برای حل مشکل ایجاد شده دنبال کنید یا این کار را به عنوان هدیه ای از طرف خدمات طلایی شما به مشتریان به صورت رایگان انجام دهید.
مطالب
Minimal API's در دات نت 6 - قسمت سوم - ایجاد endpoints مقدماتی
در دو قسمت قبل، ساختار ابتدایی برنامه‌ی Minimal API's بلاگ دهی را ایجاد کردیم. در این قسمت می‌خواهیم بررسی کنیم، معادل‌های کنترلرهای MVC و اکشن متدهای آن‌ها در سیستم جدید Minimal API، به چه صورتی ایجاد می‌شوند.


ایجاد اولین endpoint از نوع Get مبتنی بر Minimal API

برای افزودن اولین endpoint برنامه، به فایل Program.cs برنامه مراجعه کرده و آن‌را به صورت زیر تکمیل می‌کنیم:
// ...

app.UseHttpsRedirection();

app.MapGet("/api/authors", async (MinimalBlogDbContext ctx) =>
{
    var authors = await ctx.Authors.ToListAsync();
    return authors;
});

app.Run();
app.MapGet، معادل یک اکشن متد کنترلرهای MVC را که از نوع HttpGet هستند، ارائه می‌دهد. در همینجا می‌توان آدرس دقیق این endpoint را به عنوان پارامتر اول، مشخص کرد که پس از فراخوانی آن در مرورگر، یک Delegate که هندلر نام دارد (پارامتر دوم این متد)، اجرا می‌شود تا Response ای را ارائه دهد.
همانطور که مشاهده می‌کنید می‌توان در اینجا، این Delegate را از نوع Lambda expressions تعریف کرد و با ذکر MinimalBlogDbContext به صورت یک پارامتر آن، کار تزریق وابستگی‌های خودکار آن نیز صورت می‌گیرد. شبیه به حالتی که می‌توان یک سرویس را به عنوان پارامتر یک اکشن متد، با ذکر ویژگی [FromServices] در کنترلرهای MVC معرفی کرد؛ البته در اینجا بدون نیاز به ذکر این ویژگی (هرچند هنوز هم قابل ذکر است). مزیت آن این است که هر endpoint، تنها سرویس‌های مورد نیاز خودش را دریافت می‌کند و نه یک لیست قابل توجه از تمام سرویس‌هایی که قرار است در قسمت‌های مختلف یک کنترلر استفاده شوند.
پس از آن می‌توان با Context ای که در اختیار داریم، عملیات مدنظر را پیاده سازی کرده و یک خروجی را ارائه دهیم. در اینجا دیگر نیازی به تعریف IActionResult‌ها و امثال آن نیست و همه چیز ساده شده‌است.


ایجاد اولین endpoint از نوع Post مبتنی بر Minimal API

app.MapPost، معادل یک اکشن متد کنترلرهای MVC را که از نوع HttpPost هستند، ارائه می‌دهد:
//...

app.UseHttpsRedirection();

//...

app.MapPost("/api/authors", async (MinimalBlogDbContext ctx, AuthorDto authorDto) =>
{
    var author = new Author();
    author.FirstName = authorDto.FirstName;
    author.LastName = authorDto.LastName;
    author.Bio = authorDto.Bio;
    author.DateOfBirth = authorDto.DateOfBirth;

    ctx.Authors.Add(author);
    await ctx.SaveChangesAsync();

    return author;
});

app.Run();

internal record AuthorDto(string FirstName, string LastName, DateTime DateOfBirth, string? Bio);
در ابتدا یک Dto را که حاوی اطلاعات نویسنده‌ی جدیدی است، معادل خواص مدل Author دومین برنامه، تعریف می‌کنیم. سپس می‌توان این Dto را نیز به صورت یک پارامتر جدید به Lambda Expression متد app.MapPost معرفی کرد تا کار نگاشت اطلاعات دریافتی به آن، به صورت خودکار انجام شود (حالت پیش‌فرض آن [FromBody] است که نیازی به ذکر آن نیست).
سعی شده‌است تا این مثال در ساده‌ترین شکل ممکن خودش ارائه شود. در ادامه کار نگاشت خواص Dto را به مدل دومین برنامه، توسط AutoMapper انجام خواهیم داد.
مابقی نکات متد app.MapPost نیز مانند متد app.MapGet است؛ برای مثال در اینجا نیز تعریف مسیر endpoint، توسط اولین پارامتر این متد صورت می‌گیرد و نحوه‌ی تزریق سرویس DbContext برنامه نیز یکی است.


آزمایش برنامه‌ی Minimal API's

برنامه‌ی Minimal API's تهیه شده، به همراه یک Swagger از پیش تنظیم شده نیز هست. به همین جهت برای کار با این API الزاما نیازی به استفاده‌ی از مثلا برنامه‌ی Postman یا راه حل‌های مشابه نیست. بنابراین فقط کافی است تا برنامه‌ی API را اجرا کرده و در رابط کاربری ظاهر شده در آدرس https://localhost:7085/swagger/index.html، بر روی دکمه‌ی Try it out هر کدام از endpointها کلیک کنیم. برای مثال اگر چنین کاری را در قسمت Post انجام دهیم، به تصویر زیر می‌رسیم:



در اینجا پس از ویرایش اطلاعات شیء JSON ای که برای ما تدارک دیده‌است، فقط کافی است بر روی دکمه‌ی execute ذیل آن کلیک کنیم تا اطلاعات این Dto را به app.MapPost متناظر فوق ارسال کند و برای نمونه خروجی بازگشتی از سرور را نیز در همینجا نمایش می‌دهد که در آن، Id رکورد نیز پس از ثبت در بانک اطلاعاتی، مشخص است:



شروع به Refactoring و خلوت کردن فایل Program.cs

اگر بخواهیم به همین نحو تمام endpoints و dtoها را داخل فایل Program.cs اضافه کنیم، پس از مدتی به یک فایل بسیار حجیم و غیرقابل نگهداری خواهیم رسید. بنابراین در مرحله‌ی اول، تنظیمات سرویس‌ها و میان افزارها را به خارج از آن منتقل می‌کنیم. برای این منظور پوشه‌ی جدید Extensions را به همراه دو کلاس زیر ایجاد می‌کنیم:
using Microsoft.EntityFrameworkCore;
using MinimalBlog.Dal;

namespace MinimalBlog.Api.Extensions;

public static class ServiceCollectionExtensions
{
    public static IServiceCollection AddApplicationServices(this IServiceCollection services,
        WebApplicationBuilder builder)
    {
        if (builder == null)
        {
            throw new ArgumentNullException(nameof(builder));
        }

        builder.Services.AddEndpointsApiExplorer();
        builder.Services.AddSwaggerGen();

        var connectionString = builder.Configuration.GetConnectionString("Default");
        builder.Services.AddDbContext<MinimalBlogDbContext>(opt => opt.UseSqlServer(connectionString));

        return services;
    }
}
کار این متد الحاقی، خارج کردن تنظیمات سرویس‌های برنامه از کلاس Program است.

همچنین نیاز به متد الحاقی دیگری برای خارج کردن تنظیمات میان‌افزارها داریم:
namespace MinimalBlog.Api.Extensions;

public static class WebApplicationExtensions
{
    public static WebApplication ConfigureApplication(this WebApplication app)
    {
        if (app == null)
        {
            throw new ArgumentNullException(nameof(app));
        }

        if (app.Environment.IsDevelopment())
        {
            app.UseSwagger();
            app.UseSwaggerUI();
        }

        app.UseHttpsRedirection();

        return app;
    }
}
پس از این تغییرات، اکنون ابتدای کلاس Program برنامه‌ی Api به صورت زیر تغییر می‌کند و خلاصه می‌شود:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddApplicationServices(builder);

var app = builder.Build();
app.ConfigureApplication();

در قسمت بعد، endpoints را از این کلاس آغازین برنامه خارج می‌کنیم.
مطالب
نمایش ای‌جکسی یک partial view در popover بوت استرپ 3
فرض کنید بخواهیم نمایش رای دهنده‌های یک مطلب را با popover بوت استرپ 3 نمایش دهیم:

Popover بوت استرپ برای کار با منابع remote طراحی نشده‌است و نیاز است توابع API آن‌را به همراه jQuery Ajax ترکیب کرد تا به تصویر فوق رسید.


مرحله‌ی اول: اکشن متدی که یک partial view را باز می‌گرداند

فرض کنید اکشن متدی که لیست کاربران رای داده‌ی به یک مطلب را باز می‌گرداند، چنین شکلی را دارد:
        public ActionResult RenderResults(string param1)
        {
            var users = new[]
            {
                new User{ Id = 1, Name = "Test 1", Rating = 3},
                new User{ Id = 2, Name = "Test 2", Rating = 4},
                new User{ Id = 3, Name = "Test 3", Rating = 5}
            };
            return PartialView("_RenderResults", model: users);
        }
در اینجا لیستی دریافت و سپس به partial view ارسال می‌شود. در ادامه این Partial view نیز به صورت ذیل اطلاعات را رندر می‌کند:
@using RemotePopOver.Models
@model IList<User>

<ul id="ratings1" data-title="Ratings" class="list-unstyled">
    @foreach (var user in Model)
    {
        <li>
            @user.Name
            <span class="badge pull-right">@user.Rating</span>
        </li>
    }
</ul>
در اینجا از یک ویژگی سفارشی data-* به نام data-title نیز استفاده کرده‌ایم. متنی که در آن قرار می‌گیرد، به صورت عنوان popover ظاهر خواهد شد.


مرحله‌ی دوم: دریافت اطلاعات partial view با استفاده از jQuery Ajax و سپس درج آن در یک popover

می‌خواهیم با حرکت ماوس بر روی دکمه‌ی سفارشی ذیل، یک popover ظاهر شده و محتوای خودش را از اکشن متد فوق تامین کند.
<span id="remotePopover1"
      aria-hidden="true"
      data-param1="test"
      data-popover-content-url="@Url.Action("RenderResults", "Home")"
      class="glyphicon glyphicon-info-sign btn btn-info"></span>
در این مثال، چند ویژگی سفارشی data-* دیگر را نیز تعریف کرده‌ایم تا نیازی به تعریف سراسری متغیرهای جاوا اسکریپتی نباشد. برای مثال data-param1 یک پارامتر دلخواه است و data-popover-content-url به آدرس اکشن متدی اشاره می‌کند که قرار است partial view مدنظر را رندر کند.
در ادامه نحوه‌ی استفاده‌ی از این ویژگی‌ها را در jQuery Ajax مشاهده می‌کنید:
@section Scripts
{
    <script type="text/javascript">
        $(document).ready(function () {
            $('body').on('mouseenter', 'span[data-popover-content-url]', function () {
                var el = $(this);
                $.ajax({
                    type: "POST",
                    url: $(this).data("popover-content-url"),
                    data: JSON.stringify({ param1: $(this).data("param1") }),
                    contentType: "application/json; charset=utf-8",
                    dataType: "json",
                    // controller is returning a simple text, not json
                    complete: function (xhr, status) {
                        var data = xhr.responseText;
                        if (status === 'error' || !data) {
                            el.popover({
                                content: 'Error connecting server!',
                                trigger: 'focus',
                                html: true,
                                container: 'body',
                                placement: 'auto',
                                title: 'Error!'
                            }).popover('show');
                        }
                        else {
                            el.popover({
                                content: data,
                                trigger: 'focus',
                                html: true,
                                container: 'body',
                                placement: 'auto',
                                title: $('<html />').html(data).find('#ratings1:first').data('title')
                            }).popover('show');
                        }
                    }
                });
            }).on('mouseleave', 'span[data-popover-content-url]', function () {
                $(this).popover('hide');
            });
        });
    </script>
}
در این مثال تمام المان‌هایی که دارای data-popover-content-url هستند، تحت نظر قرار می‌گیرند. سپس اگر ماوس به محدوده‌ی آن‌ها وارد شد، مقدار ("this).data("popover-content-url)$ مساوی آدرسی است که قرار است اطلاعات را از سرور دریافت کند. به همین جهت از آن برای استفاده‌ی در متد ajax کمک گرفته شده‌است.
خروجی partial view به صورت json نیست. بنابراین باید اطلاعات نهایی آن‌را در callback ویژه‌ی complete دریافت کرد. مقدار data دریافتی، معادل اطلاعات رندر شده‌ی partial view است. به همین جهت آن‌را به خاصیت content متد popver ارسال می‌کنیم. همچنین چون خروجی patrtial view به همراه html است، نیاز است خاصیت html متد popover نیز به true تنظیم شود. در خاصیت title، نحوه‌ی دسترسی به مقدار data-title تنظیم شده‌ی در partial view را مشاهده می‌کنید.


کدهای کامل این مثال را از اینجا می‌توانید دریافت کنید:
RemotePopOver.zip
نظرات مطالب
بخش سوم - استفاده و شخصی سازی Mapper توکار Gridify
Gridify 2.0.0  منتشر شد. 
تغییرات مهم ورژن 2
  • پشتیبانی از جستجو در لیست‌های فرزند  
  • پشتیبانی پیش فرض جستجو اطلاعات null 
  • پشتیبانی از چندین OrderBy به صورت صعودی و نزولی
  • پشتیبانی از C# 8 Nullable feature
  • اضافه شدن MapperConfiguration برای اعمال تنظیمات Mapper
  • اضافه شدن امکان دریافت Expression‌ها و کامپایل آن
  • بهبود عملکرد کلی کتابخانه
  • بازنویسی زیرساخت (تغییرات عمده در اینترفیس‌ها و اکستنشن متدها)


نظرات مطالب
ASP.NET MVC #11
این برداشت درسته : view state در خود صفحه ذخیره می‌شود ولی مکانیزم جایگزین در MVC اطلاعات را به سرور ارسال و برای نمایش مجدد اطلاعات در View اطلاعات از سرور به سمت کلاینت ارسال می‌شود از طریق مدل بایندر؟
مطالب
شروع به کار با DNTFrameworkCore - قسمت 6 - پیاده‌سازی عملیات CRUD موجودیت‌ها با استفاده از ASP.NET Core MVC
به عنوان آخرین قسمت مرتبط با تفکر مبتنی‌بر CRUD‏ ‎(‎CRUD-based thinking)‎، روش پیاده‌سازی عملیات CRUD موجودیت‌ها را با استفاده از ASP.NET Core MVC و افزونه jquery-unobtrusive-ajax بررسی خواهیم کرد. 


برای شروع لازم است بسته نیوگت زیر را نصب کنید: 
PM> Install-Package DNTFrameworkCore.Web

قراردادها، مفاهیم و نکات اولیه 
  1. Refactor کردن فرم‌های ثبت و ویرایش مرتبط با یک Aggregate، به یک PartialView که با یک ViewModel کار می‌کند. برای موجودیت‌های ساده و پایه، همان Model/DTO، به عنوان Model متناظر با یک ویو یا به اصطلاح ViewModel استفاده می‌شود؛ ولی برای سایر موارد، از مدلی که نام آن با نام موجودیت + کلمه ModalViewModel یا FormViewModel تشکیل می‌شود، استفاده خواهیم کرد.
  2. یک فرم، در قالب یک پارشال‌ویو، به صورت Ajaxای با استفاده از افزونه  jquery-unobtrusive-ajax بارگذاری شده و به سرور ارسال خواهد شد.
  3. یک فرم براساس طراحی خود می‌تواند در قالب یک مودال باز شود، یا به منظور inline-editing آن را بارگذاری و به قسمتی از صفحه که مدنظرتان می‌باشد اضافه شود.
  4. وجود ویو Index به همراه پارشال‌ویو ‎_List برای نمایش لیستی و یک پارشال‌ویو برای عملیات ثبت و ویرایش الزامی ‌می‌باشد. البته اگر از مکانیزمی که در مطلب « طراحی یک گرید با jQuery Ajax و ASP.NET MVC به همراه پیاده سازی عملیات CRUD»  مطرح شد، استفاده نمی‌کنید و نیاز دارید تا اطلاعات صفحه‌بندی شده، مرتب شده و فیلتر شده‌ای را در قالب JSON دریافت کنید، از اکشن‌متد ReadPagedList کنترلر پایه استفاده کنید.

مثال اول: پیاده‌سازی BlogsController و طراحی فرم ثبت و ویرایش
کار با ارث‌بری از کنترلر پایه طراحی شده در زیرساخت به شکل زیر آغاز می‌شود:
public class BlogsController : CrudController<IBlogService, int, BlogModel>
{
    public BlogsController(IBlogService service) : base(service)
    {
    }

    protected override string CreatePermissionName => PermissionNames.Blogs_Create;
    protected override string EditPermissionName => PermissionNames.Blogs_Edit;
    protected override string ViewPermissionName => PermissionNames.Blogs_View;
    protected override string DeletePermissionName => PermissionNames.Blogs_Delete;
    protected override string ViewName => "_BlogModal";
}
در اینجا مشخص کردن ViewName متناظر با موجودیت جاری، الزامی می‌باشد. همانطور که عنوان شد، یک پارشال‌ویو برای عملیات ثبت و ویرایش کفایت می‌کند و طراحی پشت این کنترلر پایه نیز بر این اساس می‌باشد. گام بعدی، طراحی پارشال‌ویو مذکور می‌باشد. 

<div>
    <h4 asp-if="Model.IsNew()">Create New Blog</h4>
    <h4 asp-if="!Model.IsNew()">Edit Blog</h4>
    <button type="button" data-dismiss="modal">&times;</button>
</div>
در اینجا با استفاده از تگ‌هلپر asp-if موجود در زیرساخت و متد IsNew متناظر با مدل جاری، عنوان نمایشی مودال را مشخص کرده‌ایم. 
 ‌


<form asp-action="@(Model.IsNew() ? "Create" : "Edit")" asp-controller="Blogs" asp-modal-form="BlogForm">
    <div>
        <input type="hidden" name="save-continue" value="true"/>
        <input asp-for="RowVersion" type="hidden"/>
        <input asp-for="Id" type="hidden"/>
        <div>
            <div>
                <label asp-for="Title"></label>
                <input asp-for="Title" autocomplete="off"/>
                <span asp-validation-for="Title"></span>
            </div>
        </div>
        <div>
            <div>
                <label asp-for="Url"></label>
                <input asp-for="Url" type="url"/>
                <span asp-validation-for="Url"></span>
            </div>
        </div>
    </div>

...

</form>
با استفاده از متد IsNew مدل جاری، اکشن متد متناظر با درخواست POST مشخص شده و سپس از طریق تگ‌هلپر asp-modal-form، فرآیند افزودن ویژگی‌های data-ajax به فرم را خودکار کرده‌ایم. سپس با استفاده از یک Hidden Input با نام save-continue و مقدار true به فرم، عملیات ذخیره کردن بدون بسته شدن فرم را شبیه سازی کرده‌ایم؛ این Input می‌تواند به صورت شرطی به فرم اضافه شود (دکمه‌های ذخیره/ذخیره و بستن و ...) که در سمت سرور با توجه به مقدار ارسالی تصمیم گرفته می‌شود که دوباره فرم با اطلاعات جدید (در اینجا Id و RowVersion) به کلاینت ارسال شود یا خیر.

<div>
    <a asp-modal-delete-link asp-model-id="@Model.Id" asp-modal-toggle="false"
       asp-controller="Blogs" asp-action="Delete" asp-if="!Model.IsNew()" asp-permission="@PermissionNames.Blogs_Delete"
       title="Delete Blog">
        <i></i>
    </a>
    <a title="Refresh Blog" asp-if="!Model.IsNew()" asp-modal-link asp-modal-toggle="false"
       asp-controller="Blogs" asp-action="Edit" asp-route-id="@Model.Id">
        <i></i>
    </a>
    <a title="New Blog" asp-modal-link asp-modal-toggle="false"
       asp-controller="Blogs" asp-action="Create">
        <i></i>
    </a>
    <button type="button" data-dismiss="modal">
        <i></i>&nbsp; Cancel
    </button>
    <button type="submit">
        <i></i>&nbsp; Save Changes
    </button>
</div>
بخش فوتر این مودال در برگیرنده دکمه‌های متناظر با اکشن‌های قابل انجام بر روی موجودیت جاری می‌باشد.
در این فرم ابتدا 3 دکمه میانبر برای عملیات حذف، بازنشانی و جدید درنظر گرفته شده‌اند؛ برای حذف که از طریق تگ‌هلپر asp-modal-delete-link کار افزودن خودکار ویژگی‌های data-ajax انجام شده و در نهایت با توجه به اکشن‌متد و کنترلر مشخص شده، یک مودال، برای گرفتن تأیید از کاربر باز می‌شود و در زمان پذیرش درخواست، حذف مدل جاری به سرور ارسال خواهد شد. با استفاده از تگ‌هلپر asp-permission نیز دسترسی مورد نیاز برای مشاهده دکمه مورد نظر را مشخص کرده‌ایم. دکمه‌های بازنشانی و جدید، در اینجا از طریق تگ‌هلپر asp-modal-link تبدیل به یک لینک Ajaxای شده است که کار بارگذاری یک پارشال‌ویو را انجام می‌دهند؛ همچنین با استفاده از ویژگی asp-modal-toggle متناظر با تگ‌هلپر مذکور با مقدار false، مشخص کرده‎ایم که صرفا محتوای مودال جاری جایگزین شود و نیاز به گشوده شدن دوباره آن نیست.


خروجی نهایی در حالت ویرایش به شکل زیر می‌باشد:

‌‌
مثال دوم: پیاده‌سازی RolesController و طراحی فرم ثبت و ویرایش
‌‌نکته قابل توجه در طراحی فرم‌های ثبت و ویرایش موجودیت‌های پیچیده‌تر مربوط است به ارسال اطلاعات اضافه‌تر از هر آنچه در مدل وجود دارد به کلاینت و همچنین دریافت اطلاعات در ساختار تودرتو یا به اصطلاح یک گراف از اشیاء. به عنوان مثال برای ثبت یک گروه کاربری نیاز است لیست دسترسی‌ها را نیز در فرم نمایش دهیم تا توسط کاربر انتخاب شود؛ در این حالت نیاز است یک مدل متناظر و متناسب را به شکل زیر طراحی کرد:
public class RoleModalViewModel : RoleModel
{
    public IReadOnlyList<LookupItem> PermissionList { get; set; }
}
در اینجا با ارث‌بری از RoleModel متناظر با موجودیت گروه کاربری، لیست دسترسی‌ها نیز در طریق خصوصیت PermissionList قابل استفاده می‌باشد. حال برای مقداردهی خصوصیت اضافه شده و به طور کلی برای ارسال اطلاعات اضافه به کلاینت در زمان بارگذاری فرم، نیاز است متد RenderView را به شکل زیر بازنویسی کنید:
protected override IActionResult RenderView(RoleModel role)
{
    var model = _mapper.Map<RoleModalViewModel>(role);
    model.PermissionList = ReadPermissionList();

    return PartialView(ViewName, model);
}
به عنوان مثال در اینجا از AutoMapper برای وهله سازی از RoleModalViewModel و نگاشت خصوصیات RoleModel، استفاده شده است. به این ترتیب خروجی نهایی در حالت ویرایش به شکل زیر می‌باشد:



برای مدیریت سناریوهای Master-Detail به مانند قسمت مدیریت دسترسی‌ها در تب Permissions فرم بالا، امکاناتی در زیرساخت تعبیه شده است ولی پیاده‌سازی آن را به عنوان یک تمرین و با توجه به سری مطالب «Editing Variable Length Reorderable Collections in ASP.NET MVC» به شما واگذار می‌کنم.


نکته تکمیلی: برای ارسال اطلاعات اضافی به ویو Index متناظر با یک موجودیت می‌توانید متد RenderIndex را به شکل زیر بازنویسی کنید:

protected override IActionResult RenderIndex(IPagedQueryResult<RoleReadModel> model)
{
    var indexModel = new RoleIndexViewModel
    {
        Items = model.Items,
        TotalCount = model.TotalCount,
        Permissions = ReadPermissionList()
    };

    return Request.IsAjaxRequest()
        ? (IActionResult) PartialView(indexModel)
        : View(indexModel);
}

مدل RoleIndexViewModel استفاده شده در تکه کد بالا نیز به شکل زیر خواهد بود:

public class RoleIndexViewModel : PagedQueryResult<RoleReadModel>
{
    public IReadOnlyList<LookupItem> Permissions { get; set; }
}


فرآیند بارگذاری یک پارشال‌ویو در مودال

به عنوان مثال برای استفاده از مودال‌های بوت استرپ، ایده کار به این شکل است که یک مودال را به شکل زیر در فایل Layout قرار دهید:

<div class="modal fade" @*tabindex="-1"*@ id="main-modal" data-keyboard="true" data-backdrop="static" role="dialog" aria-hidden="true">
    <div class="modal-dialog modal-dialog-centered" role="document">
        <div class="modal-content">
            <div class="modal-body">
                Loading...
            </div>
        </div>
    </div>
</div>

سپس در زمان کلیک بروی یک دکمه Ajaxای، ابتدا main-modal را نمایش داده و بعد از دریافت پارشال‌ویو از سرور، آن را با محتوای modal-content جایگزین می‌کنیم. به همین دلیل Tag Halperهای مطرح شده در مطلب جاری، callbackهای failure/complete/success متناظر با unobtrusive-ajax را نیز مقداردهی می‌کنند. برای این منظور نیاز است تا متدهای جاوااسکریپتی زیر نیز در سطح شیء window تعریف شده باشند:‌‌

    /*----------------------------------  asp-modal-link ---------------------------*/
    window.handleModalLinkLoaded = function (data, status, xhr) {
        prepareForm('#main-modal.modal form');
    };
    window.handleModalLinkFailed = function (xhr, status, error) {
        //....
    };
    /*----------------------------------  asp-modal-form ---------------------------*/
    window.handleModalFormBegin = function (xhr) {
        $('#main-modal a').addClass('disabled');
        $('#main-modal button').attr('disabled', 'disabled');
    };
    window.handleModalFormComplete = function (xhr, status) {
        $('#main-modal a').removeClass('disabled');
        $('#main-modal button').removeAttr('disabled');
    };
    window.handleModalFormSucceeded = function (data, status, xhr) {
        if (xhr.getResponseHeader('Content-Type') === 'text/html; charset=utf-8') {
            prepareForm('#main-modal.modal form');
        } else {
            hideMainModal();
        }
    };
    window.handleModalFormFailed = function (xhr, status, error, formId) {
        if (xhr.status === 400) {
            handleBadRequest(xhr, formId);
        }
    };


برای بررسی بیشتر، پیشنهاد می‌کنم پروژه DNTFrameworkCore.TestWebApp موجود در مخزن این زیرساخت را بازبینی کنید. 
مطالب
#Defensive Code in C - قسمت دوم

تعریف متد‌ها در برنامه نویسی:

متدها جزء اولین چیزهایی هستند که در هنگام شروع برنامه نویسی در هر یک از زبان‌های برنامه نویسی، برنامه نویس با آنها آشنا می‌شود. بنابراین متد‌ها به عنوان اصلی‌ترین Building Block  ها در زبان‌های برنامه نویسی دارای اهمیت بسیار زیادی می‌باشند. متد‌ها اولین جاهایی هستند که ما می‌توانیم کار خودمان را از آنها شروع کنیم و به سوی هدف خود حرکت کنیم.

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

بسیاری از برنامه نویسان انتظاری که از متد‌ها دارند فقط در همین حد می‌باشد و بیشتر از این درگیر هیچ مسئله‌ی دیگری نمی‌شوند. اما آیا فقط در نظر گرفتن این مسایل در رسیدن به یک کد خوش ساخت، قابل توسعه و بدون پیچیدگی کافی است؟

متد‌ها علاوه بر ویژگی‌های ذکر شده‌ی در بالا که بیشتر بر ویژگی‌های ذاتی و عملکردی آن تمرکز داشت، باید داری یکسری ویژگی‌های دیگر نیز باشند، متد‌ها باید Clean ، Testable و Predictable باشند. هر کدام از ویژگی‌های مذکور توسط این پارامتر‌ها تشریح می‌شوند. 

در ذیل ویژگی‌های مذکور در شکل بالا را تشریح خواهیم کرد.

Clear Purpose :

· یک متد یک کار را انجام می‌دهد و همچنین آن کار را نیز به خوبی انجام می‌دهد.

· متد به راحتی قابل درک می‌باشد.

· میزان خطا‌ها را به شدت کاهش می‌دهد.

· دیباگ کردن را در صورت وجود هر خطایی ساده‌تر می‌کند.

· قابلیت توسعه را افزایش می‌دهد.

· نوشتن تست برای این نوع متد‌ها به دلیل اینکه فقط بر روی یک هدف خاص تمرکز دارند ساده است.

Good Name :

· نام متد عمکرد آن را به روشنی بیان می‌کند

Focused Code :

· تمام کد نوشته شده‌ی در متد فقط بر روی یک هدف تمرکز دارند.

· خوانایی کد بالا است و میزان توضیحاتی که برای کد نوشته می‌شود در کمترین حد ممکن است.

· متد دارای تاثیرات ناخواسته‌ای بر سایر قسمت‌های نرم افزار نمی‌باشد. این مسئله به معنی است که این نوع متد‌ها شامل کدهایی که کارهای ناخواسته ای را انجام می‌دهند نمی‌باشد. برای مثال متدی که برای واکشی اطلاعات مشتریان استفاده می‌شود هیچگونه عملیاتی را که برای ثبت اطلاعات مشتریان انجام می‌شود، انجام نمی‌دهد.

Short Length :

· تعداد خطوط کد مربوط به متد کم می‌باشد. این مسئله خود باعث کاهش باگ‌های احتمالی در یک متد می‌شود.

Automated Code Test :

· متد این قابلیت را دارد که توسط زیر ساخت‌های تست، تست شود که این مسئله خود باعث افزایش کیفیت کد می‌شود.

Predictable Result :

· متد دارای یک نتیجه‌ی قابل پیش بینی می‌باشد.


در ادامه سعی می‌کنیم با ذکر یک مثال، مواردی را که ذکر شد بیشتر توضیح دهیم و دیدگاه کاربردی آن را بررسی کنیم.

مثالی از دنیای واقعی:

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

این روش کد نویسی روشی است که اکثر برنامه نویسان با آن آشنایی دارند. اولین مشکلی که این روش دارد این است که این کد Clean نمی‌باشد. قابلیت توسعه و نگهداری این کد بسیار پایین می‌باشد و به اصطلاح یک کد کاملا باگ خیز می‌باشد. حال نوبت این رسیده که این کد را از نظر پارامترهایی که در بالا ذکر شد بررسی کنیم.

Clear Purpose :

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

Clear Name :

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

Focused Code :

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

Short Length :

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

Automated Code Test :

 آیا این متد به وسیله‌ی  Automated Code Test  می تواند تست شود؟ چند نکته در مورد این کد وجود دارد که توانایی Automated Code Test را از این کد می‌گیرد. اولین مسئله این است که در این مثال منطق یا همان Business برنامه با UI تلفیق شده‌است. برای رفع این مشکل باید منطق برنامه را در یک پروژه‌ی مجزا از نوع Class Library قرار داد. مسئله‌ی دیگر این است که این متد برای تست شدن بسیار طولانی می‌باشد و باید به یکسری اجزای کوچکتر و منطقی‌تر شکسته شود و هر متد باید یک هدف و عملکرد روشن را داشته باشد. 

در قسمت بعدی راهکارهایی برای Refactor کردن کد بر اساس اصول ذکر شده ارائه خواهد شد.