اشتراک‌ها
استفاده از MongoDB .NET Driver با NET Core Web API.

Source could be also accessed from GitHub -> https://github.com/fpetru/WebApiMongoDB.

Problem / solution format brings an easier understanding on how to build things, giving an immediate feedback. Starting from this idea, the blog post will present step by step how to build

a web application to store your ideas in an easy way, adding text notes, either from desktop or mobile, with few characteristics: run fast, save on the fly whatever you write, and be reasonably reliable and secure.

This blog post will implement just the backend, WebApi and the database access, in the most simple way. Next blog post will cover the front end, using Angular. Then, there will be an additional article on how to increase the performance and the security. 

استفاده از MongoDB .NET Driver با NET Core Web API.
مطالب
نحوه ذخیره شدن متن در فایل‌های PDF
تبدیل بی عیب و نقص یک فایل PDF (انواع و اقسام آن‌ها) به متن قابل درک بسیار مشکل است. در ادامه بررسی خواهیم کرد که چرا.
برخلاف تصور عموم، ساختار یک صفحه PDF شبیه به یک صفحه فایل Word نیست. این صفحات درحقیقت نوعی Canvas برای نقاشی هستند. در این بوم نقاشی، شکل، تصویر، متن و غیره در مختصات خاصی قرار خواهند گرفت. حتی کلمه «متن» می‌تواند به صورت سه حرف در سه مختصات خاص یک صفحه PDF نقاشی شود. برای درک بهتر این مورد نیاز است سورس یک صفحه PDF را بررسی کرد.

نحوه استخراج سورس یک صفحه PDF

using System.Diagnostics;
using System.IO;
using iTextSharp.text;
using iTextSharp.text.pdf;

namespace TestReaders
{
    class Program
    {
        static void writePdf()
        {
            using (var document = new Document(PageSize.A4))
            {
                var writer = PdfWriter.GetInstance(document, new FileStream("test.pdf", FileMode.Create));
                document.Open();

                document.Add(new Paragraph("Test"));

                PdfContentByte cb = writer.DirectContent;
                BaseFont bf = BaseFont.CreateFont();
                cb.BeginText();
                cb.SetFontAndSize(bf, 12);
                cb.MoveText(88.66f, 367);
                cb.ShowText("ld");
                cb.MoveText(-22f, 0);
                cb.ShowText("Wor");
                cb.MoveText(-15.33f, 0);
                cb.ShowText("llo");
                cb.MoveText(-15.33f, 0);
                cb.ShowText("He"); 
                cb.EndText();

                PdfTemplate tmp = cb.CreateTemplate(250, 25);
                tmp.BeginText();
                tmp.SetFontAndSize(bf, 12);
                tmp.MoveText(0, 7);
                tmp.ShowText("Hello People");
                tmp.EndText();
                cb.AddTemplate(tmp, 36, 343);
            }

            Process.Start("test.pdf");
        }

        private static void readPdf()
        {
            var reader = new PdfReader("test.pdf");
            int intPageNum = reader.NumberOfPages;
            for (int i = 1; i <= intPageNum; i++)
            {
                byte[] contentBytes = reader.GetPageContent(i);
                File.WriteAllBytes("page-" + i + ".txt", contentBytes);
            }
            reader.Close();
        }

        static void Main(string[] args)
        {
            writePdf();
            readPdf();
        }
    }
}
فایل PDF تولیدی حاوی سه عبارت کامل و مفهوم می‌باشد:


اگر علاقمند باشید که سورس واقعی صفحات یک فایل PDF را مشاهده کنید، نحوه انجام آن توسط کتابخانه iTextSharp به صورت فوق است.
هرچند متد GetPageContent آرایه‌ای از بایت‌ها را بر می‌گرداند، اما اگر حاصل نهایی را در یک ادیتور متنی باز کنیم، قابل مطالعه و خواندن است. برای مثال، سورس مثال فوق (محتوای فایل page-1.txt تولید شده) به نحو زیر است:
q
BT
36 806 Td
0 -18 Td
/F1 12 Tf
(Test)Tj
0 0 Td
ET
Q
BT
/F1 12 Tf
88.66 367 Td
(ld)Tj
-22 0 Td
(Wor)Tj
-15.33 0 Td
(llo)Tj
-15.33 0 Td
(He)Tj
ET
q 1 0 0 1 36 343 cm /Xf1 Do Q
و تفسیر این عملگرها به این ترتیب است:
SaveGraphicsState(); // q
BeginText(); // BT
MoveTextPos(36, 806); // Td
MoveTextPos(0, -18); // Td
SelectFontAndSize("/F1", 12); // Tf
ShowText("(Test)"); // Tj
MoveTextPos(0, 0); // Td
EndTextObject(); // ET
RestoreGraphicsState(); // Q
BeginText(); // BT
SelectFontAndSize("/F1", 12); // Tf
MoveTextPos(88.66, 367); // Td
ShowText("(ld)"); // Tj
MoveTextPos(-22, 0); // Td
ShowText("(Wor)"); // Tj
MoveTextPos(-15.33, 0); // Td
ShowText("(llo)"); // Tj
MoveTextPos(-15.33, 0); // Td
ShowText("(He)"); // Tj
EndTextObject(); // ET
SaveGraphicsState(); // q
TransMatrix(1, 0, 0, 1, 36, 343); // cm
XObject("/Xf1"); // Do
RestoreGraphicsState(); // Q
همانطور که ملاحظه می‌کنید کلمه Test به مختصات خاصی انتقال داده شده و سپس به کمک اطلاعات فونت F1، ترسیم می‌شود.
تا اینجا استخراج متن از فایل‌های PDF ساده به نظر می‌رسد. باید به دنبال Tj گشت و حروف مرتبط با آن‌را ذخیره کرد. اما در مورد «ترسیم» عبارات hello world و hello people اینطور نیست. عبارت hello world به حروف متفاوتی تقسیم شده و سپس در مختصات مشخصی ترسیم می‌گردد. عبارت hello people به صورت یک شیء ذخیره شده در قسمت منابع فایل PDF، بازیابی و نمایش داده می‌شود و اصلا در سورس صفحه جاری وجود ندارد.
این تازه قسمتی از نحوه عملکرد فایل‌های PDF است. در فایل‌های PDF می‌توان قلم‌ها را مدفون ساخت. همچنین این قلم‌ها نیز تنها زیر مجموعه‌ای از قلم اصلی مورد استفاده هستند. برای مثال اگر عبارت Test قرار است نمایش داده شود، فقط اطلاعات T، e و s در فایل نهایی PDF قرار می‌گیرند. به علاوه امکان تغییر کلی شماره Glyph متناظر با هر حرف نیز توسط PDF writer وجود دارد. به عبارتی الزامی نیست که مشخصات اصلی فونت حتما حفظ شود.
شاید بعضی از PDFهای فارسی را دیده باشید که پس از کپی متن آن‌ها در برنامه Adobe reader و سپس paste آن در جایی دیگر، متن حاصل قابل خواندن نیست. علت این است که نحوه ذخیره سازی قلم مورد استفاده کاملا تغییر کرده است و برای بازیابی متن اینگونه فایل‌ها، استفاده از OCR ساده‌ترین روش است. برای نمونه در این قلم جدید مدفون شده، دیگر شماره کاراکتر 0x41 مساوی A نیست. بنابر سلیقه PDF writer این شماره به Glyph دیگری انتساب داده شده و چون قلم و مشخصات هندسی Glyph مورد استفاده در فایل PDF ذخیره می‌شود، برای نمایش این نوع فایل‌ها هیچگونه مشکلی وجود ندارد. اما متن آن‌ها به سادگی قابل بازیابی نیست.
نظرات مطالب
افزودن هدرهای Content Security Policy به برنامه‌های ASP.NET
یک نکته‌ی تکمیلی: روش ساده‌تر تنظیم هدرهای امنیتی در فایل web.config که سازگار با تمام نگارش‌های ASP.NET است
<system.webServer>
  <httpProtocol>
    <customHeaders>
      <!-- SECURITY HEADERS - https://securityheaders.io/? -->
      <!-- Protects against Clickjacking attacks. ref.: http://stackoverflow.com/a/22105445/1233379 -->
      <add name="X-Frame-Options" value="SAMEORIGIN" />
      <!-- Protects against Clickjacking attacks. ref.: https://www.owasp.org/index.php/HTTP_Strict_Transport_Security_Cheat_Sheet -->
      <add name="Strict-Transport-Security" value="max-age=31536000; includeSubDomains"/>
      <!-- Protects against XSS injections. ref.: https://www.veracode.com/blog/2014/03/guidelines-for-setting-security-headers/ -->
      <add name="X-XSS-Protection" value="1; mode=block" />
      <!-- Protects against MIME-type confusion attack. ref.: https://www.veracode.com/blog/2014/03/guidelines-for-setting-security-headers/ -->
      <add name="X-Content-Type-Options" value="nosniff" />
      <!-- CSP modern XSS directive-based defence, used since 2014. ref.: http://content-security-policy.com/ -->
      <add name="Content-Security-Policy" value="default-src 'self'; font-src *;img-src * data:; script-src *; style-src *;" />
      <!-- Prevents from leaking referrer data over insecure connections. ref.: https://scotthelme.co.uk/a-new-security-header-referrer-policy/ -->
      <add name="Referrer-Policy" value="strict-origin" />
    </customHeaders>
  </httpProtocol>
</system.webServer>
اشتراک‌ها
دوره 8 ساعته ASP.NET Core Razor Pages

Web Development with ASP.NET Core Razor Pages || FULL COURSE || Trevoir Williams

00:00 Introduction
1:00 Lesson 1: Setting Up
4:36 Lesson 2: Folder Tour
23:33 Lesson 3: Understanding Razor Syntax
35:52 Lesson 4: Message From Settings
43:44 Lesson 5: Adding Entity Framework Core
55:48 Lesson 6: Connect to Database with Entity Framework Core
1:10:20 Lesson 7: Scaffolding Database Tables as Classes
1:23:26 Lesson 8: GitHub Commit 1
1:32:50 Lesson 9: Create Page
1:54:32 Lesson 10: Update Page
2:18:25 Lesson 11: Details Page
2:23:56 Lesson 12: Delete Page
2:46:26 Lesson 13: UI Enhancements
3:19:38 Lesson 14: Check-In Module
3:21:04 Lesson 15: Added New Table
3:43:19 Lesson 16: Enhance Forms
4:07:40 Lesson 17: Further Form Enhancements
4:20:00 Lesson 18: Adding More Requirements
4:46:35 Lesson 19: Adding More Requirements Continued
5:05:23 Lesson 20: Add Cascading Dropdown
5:29:26 Lesson 21: Finish Cascading Dropdown
5:45:59 Lesson 22: Cleanup Labels
5:52:42 Lesson 23: Finish Interface Cleanup
6:23:57 Lesson 24: Setup Repositories
6:49:54 Lesson 25: Add First Repository Code
7:06:41 Lesson 26: Refactoring Pages
7:21:02 Lesson 27: Complete Repositories
7:42:27 Lesson 28: Section Conclusion
7:52:14 Lesson 29: User Authentication Setup
8:03:08 Lesson 30: Extend Users Table
8:08:01 Lesson 31: Setup Registration Page
8:28:27 Lesson 32: Setup Login
8:38:12 Lesson 33: Setup Authorization
8:47:47 Lesson 34: Add Authorization

دوره 8 ساعته ASP.NET Core Razor Pages
مطالب
فشرده سازی اطلاعات توسط الگوریتم Zstandard فیسبوک
Zstandard یا به اختصار Zstd، یک الگوریتم فشرده سازی سریع و بدون افت کیفیت است که از سرعت و قدرت خوبی برخوردار است. این الگوریتم توسط Facebook توسعه داده شده و استفاده می‌شود.
الگوریتم‌های دیگری مانند  Snappy (الگوریتم فشرده سازی گوگل)،  LZ4 (جز سریع‌ترین الگوریتم ها) و LZMA (جز قوی‌ترین الگوریتم‌ها) نیز وجود دارند ولی Zstd تعادل (trade-off) خوبی بین سرعت و میزان فشرده سازی را فراهم می‌کند.
در این مقاله برای فشرده سازی اطلاعات با الگوریتم Zstd، از کتابخانه دات نتی  ZstdNet استفاده خواهیم کرد. علاوه بر این، کتابخانه دیگری نیز به نام  Zstandard.Net  با پشتیبانی از Streaming نیز وجود دارد.
ابتدا لازم است کتابخانه مذکور از Nuget نصب شود.
Install-Package ZstdNet
این کتابخانه شامل کلاسی به نام Compessor برای فشرده سازی است که متد Wrap آن آرایه‌ای byte‌ها را دریافت کرده و پس از فشرده سازی، آرایه‌ای از byte‌ها را باز می‌گرداند. به عنوان مثال :
byte[] sourceData = GetData(); //for example : File.ReadAllBytes(filePath);
byte[] compressedData;
using (var compressor = new Compressor())
{
    compressedData = compressor.Wrap(sourceData);
}  
برای تنظیمات فشرده سازی، از جمله میزان فشرده سازی می‌توان وهله‌ای از CompressionOptions را به سازنده کلاس Compressor ارسال کرد.
کلاس  CompressionOptions در سازنده خود، پارامتری را به نام  compressionLevel از نوع int، دریافت می‌کند که میزان فشرده سازی را مشخص می‌کند. مقدار پیش‌فرض آن 3 است و می‌تواند بین 1 تا 22 ( MaxCompressionLevel) باشد. افزایش این مقدار به معنی افزایش میزان فشرده سازی و طبیعتا افزایش زمان فشرده سازی است. 
var compressor = new Compressor(new CompressionOptions(compressionLevel: 5))
برای خارج کردن از حالت فشرده نیز از کلاس Decompressor و متد Unwrap آن استفاده می‌کنیم که آرایه بایتی فشرده شده را دریافت و پس از Decompress کردن، آرایه بایتی اصلی آن (حالت عادی و غیر فشرده شده) را باز می‌گرداند.
using (var decompressor = new Decompressor())
{
    byte[] sourceData = decompressor.Unwrap(compressedData);
}  
با استفاده از روش فوق می‌توانید قبل از ذخیره سازی فایل‌هایتان آنها را فشرده سازی کنید؛ مخصوصا اگر تصمیم به ذخیره سازی درون دیتابیس را دارید، این امکان می‌تواند به کاهش حجم دیتابیس شما کمک کند.
برای فشرده سازی و ذخیره فایل روی هارد نیز می‌توانید از کلاس کمکی زیر استفاده کنید.
public static class ZstdCompression
{
    public static (decimal savedPercent, long savedSizeKB) Compress(string filePath, string savePath, int compressionLevel = 5)
    {
        var sourceData = File.ReadAllBytes(filePath);
        byte[] compressedData;

        using (var compressor = new Compressor(new CompressionOptions(compressionLevel)))
            compressedData = compressor.Wrap(sourceData);

        File.WriteAllBytes(savePath, compressedData);

        var diff = sourceData.LongLength - compressedData.LongLength;
        var savedpercent = (decimal)diff * 100 / sourceData.LongLength;
        var savedSizeKB = diff / 1024;

        return (savedpercent, savedSizeKB);
    }

    public static void Decompress(string filePath, string savePath)
    {
        var compressedData = File.ReadAllBytes(filePath);
        byte[] decompressedData;

        using (var decompressor = new Decompressor())
            decompressedData = decompressor.Unwrap(compressedData);

        File.WriteAllBytes(savePath, decompressedData);
    }
}  
و برای استفاده :
var compressInfo = ZstdCompression.Compress(@"c:\video.mp4", @"c:\compressedFile");
Console.Write($"Saved : {compressInfo.savedPercent}% ({compressInfo.savedSizeKB} KB)");
//Output => Saved : 57% (7530 KB)

مطالب
روش های مختلف پردازش یک رشته و تبدیل آن به نوع داده تاریخ
DateTime در طبقه بندی سی شارپ، جزء Strcut Type‌ها قرار می‌گیرد . عمدتا از DateTime برای مدیریت تاریخ، زمان و یا تاریخ-زمان استفاده می‌شود. خیلی از اوقات ما نیاز داریم تا رشته‌ای را به نوع تاریخ تبدیل کنیم تا بتوانیم عملیات مختلفی، همچون محاسبه‌ی اختلاف دو تاریخ، روز هفته، روز ماه و غیره را بدست آوریم. در دات نت متد‌های مختلفی وجود دارند که جداسازی تاریخ را از یک رشته برای ما فراهم می‌کنند:
  • Convert.ToDateTime()
  • DateTime.Parse()
  • DateTime.ParseExact()
  • DateTime.TryParse()
  • DateTime.TryParseExact()
در این مطلب این متد‌ها و تفاوت آنها را بررسی می‌کنیم.

تابع ()Convert.ToDateTime 
این تابع یک رشته‌ی با فرمت مشخص را به تاریخ و زمان تبدیل می‌کند. overload‌ها مختلف این تابع را در بخش زیر مشاهده می‌کنید:
• ToDateTime(string value)
Value  : رشته‌ای از تاریخ و زمان است.
 • ToDateTime(string value,IFormatProvider provide)
Value  : رشته‌ای از تاریخ و زمان است.
Provider : اطلاعات فرهنگ مورد نظر را فراهم می‌کند.
CultureInfo culture = new CultureInfo("en-US");    
DateTime tempDate = Convert.ToDateTime("1/1/2010 12:10:15 PM", culture);
در اینجا en-us اطلاعاتی را درباره‌ی فرهنگ کشور آمریکا، ارائه می‌دهد. لیست کامل فرهنگ‌های موجود در net. را می‌توانید در اینجا مشاهده کنید.
اگر رشته‌ی ما تهی (null) نباشد، بصورت درونی متد ()DateTime.Parse فراخوانی و نتیجه‌ی آن بازگردانده می‌شود. اما در صورتیکه رشته‌ی ارسالی ما تهی باشد، مقدار بازگردانده شده مقدار DateTime.MinValue که برابر با 0001/1/1 می‌باشد، بازگردانده می‌شود.
نمونه‌ای از خروجی این تابع با ورودی‌های مختلف :
string datestr = null;
Console.WriteLine(Convert.ToDateTime(datestr));//0001-01-01T00:00:00
datestr = "wrong string";
Console.WriteLine(Convert.ToDateTime(datestr));
//Unhandled Exception: System.FormatException: 
//The string was not recognized as a valid DateTime. 
//There is an unknown word starting at index 0.
datestr = "Tue Dec 30,2015";
Console.WriteLine(Convert.ToDateTime(datestr));
//Unhandled Exception: System.FormatException: 
//String was not recognized as a valid DateTime.

تابع () DateTime.Parse :
این تابع یک رشته‌ی با فرمت مشخص را به تاریخ و زمان تبدیل می‌کند. دو overload این تابع را در زیر می‌بینیم:
• DateTime.Parse(string value)
Value  : رشته‌ای از تاریخ و زمان می‌باشد.
• DateTime.Parse(String value, IFormatProvider provider)
Value : رشته‌ای از تاریخ و زمان می‌باشد.
Provider : اطلاعات فرهنگ مورد نظر را فراهم می‌کند.
  • DateTime.Parse(String value, IFormatProvider provider, DateTypeStyles styles)
Value : رشته‌ای از تاریخ و زمان می‌باشد.
Provider : اطلاعات فرهنگ مورد نظر را فراهم می‌کند 
Styles  : از طریق این پارامتر، تنظیمات قالب بندی رشته‌ی دریافتی برای سفارشی سازی کردن عملیات پردازش تعریف می‌شود. فرض کنید می‌خواهید کلیه‌ی فضاهای خالی (Space) را که قبل و بعد از رشته‌ی تاریخ هستند و نه در داخل رشته‌ی تاریخ، در زمان جداسازی و پردازش در نظر نگیرید. این پارامتر می‌تواند در اینجا به کمک ما بیاید (DateTimeStyles.AllowWhiteSpaces). مشاهده‌ی لیست کامل این خصوصیت از اینجا.
اگر مقدار رشته تهی باشد، استثنای Null نمایش داده خواهد شد (ArgumentNullException ) و اگر فرمت تاریخ ورودی صحیح نباشد، با استثنای فرمت غیرمعتبر روبرو خواهیم شد (FormatException).
string datestr = null;
Console.WriteLine(DateTime.Parse(datestr));
//// Exception: Argument null exception
datestr = "wrong string";            
Console.WriteLine(DateTime.Parse(datestr));
//// Exception: The string was not recognized as a valid DateTime. 
//// There is an unknown word starting at index 0.  
datestr = "Tue Dec 30, 2015";
//Unhandled Exception: System.FormatException: 
//String was not recognized as a valid DateTime. 
Console.WriteLine(DateTime.Parse(datestr));  

تابع ()DateTime.ParsExact
این تابع رشته‌ای شامل تاریخ و زمان را به‌همراه فرهنگ ارسالی، دریافت و آن را تبدیل به نوع DateTime می‌کند. فرمت رشته‌ی ارسالی باید با فرمت استاندارد رشته‌ی تاریخ یکسان باشد. overload‌های مختلف این تابع را در زیر مشاهده می‌کنید:
 • DateTime.ParseExact(string value, string format, IFormatProvider provider)
Value : رشته‌ای از تاریخ و زمان می‌باشد.
Provider : اطلاعات فرهنگ مورد نظر را فراهم می‌کند.

• DateTime.ParseExact(string value, string format, IFormatProvider provider, DateTimeStyles style)
Value : رشته‌ای از تاریخ و زمان می‌باشد.
Format : فرمت مورد نظر برای نمایش تاریخ بعد از تبدیل را مشخص می‌کند.
Provider : اطلاعات فرهنگ مورد نظر را فراهم می‌کند.
Styles  : از طریق این پارامتر تنظیمات قالب بندی رشته برای سفارشی کردن عملیات جداسازی و پردازش تعریف می‌شود.
• DateTime.ParseExact(string value, string[] formats, IFormatProvider provider, DateTimeStyles style)
 
Value : رشته‌ای از تاریخ و زمان می‌باشد.
Format : فرمت مورد نظر برای نمایش تاریخ بعد از تبدیل را مشخص می‌کند. تفاوت این حالت با حالت قبل این است که لیستی از فرمت‌ها را قبول می‌کند و حداقل باید یک فرمت با رشته‌ی ارسالی قابل انطباق باشد.
Provider : اطلاعات فرهنگ مورد نظر را فراهم می‌کند.
Styles : از طریق این پارامتر تنظیمات قالب بندی رشته برای سفارشی کردن عملیات جداسازی تعریف می‌شود. 
اگر مقدار رشته تهی باشد، استثنای Null نمایش داده خواهد شد (ArgumentNullException) و اگر فرمت تاریخ ورودی صحیح نباشد، با استثنای فرمت غیرمعتبر روبرو خواهیم شد(FormatException).
فرمت رشته‌ی تاریخ حتما باید با فرمت نوع تاریخ منطبق باشد. به همین منظور حالت‌های مختلفی را در آرایه می‌توان پیش بینی کرد. بطور مثال ممکن است رشته‌ی ما بصورت "2012-2-1" و یا "2012/2/1" باشد. بنابر این فرمت‌های "MM/dd/yyyy" و "MM-dd-yyyy" را از طریق آرایه ارسال می‌کنیم.
برای درک بهتر این موضوع، کد‌های زیر را مشاهده کنید: 
string datestr = null;
CultureInfo provider = CultureInfo.InvariantCulture;
Console.WriteLine(DateTime.ParseExact(datestr, "mm/dd/yyyy", provider));
//Unhandled Exception: System.ArgumentNullException: 
//String reference not set to an instance of a String.
datestr = "wrong date";
Console.WriteLine(DateTime.ParseExact(datestr, "mm/dd/yyyy", provider));
//Unhandled Exception: System.FormatException: 
//String was not recognized as a valid DateTime
datestr = "Tue Dec 30, 2015";
Console.WriteLine(DateTime.ParseExact(datestr, "mm/dd/yyyy", provider));
//Unhandled Exception: System.FormatException: 
//String was not recognized as a valid DateTime.
datestr = "10-22-2015";
Console.WriteLine(DateTime.ParseExact(datestr, "MM-dd-yyyy", provider));
//30/07/1394 12:00:00 ق.ظ
datestr = "10-22-2015";
Console.WriteLine(DateTime.ParseExact(datestr, new string[]
    {"MM-dd-yyyy", "MM/dd/yyyy", "MM.dd.yyyy"}, provider, DateTimeStyles.None));
//30/07/1394 12:00:00 ق.ظ

تابع ()DateTime.TryParse
این تابع رشته‌ای شامل تاریخ و زمان را به‌همراه فرهنگ مشخصی، دریافت و آن را تبدیل به نوع DateTime می‌کند و یک مقدار bool را برای اعلان این موضوع که عملیات تبدیل با موفقیت انجام شده است یا خیر، باز می‌گرداند. فرمت رشته‌ی ارسالی باید با فرمت رشته‌ی تاریخ یکسان باشد. overload‌های مختلف این تابع را در زیر مشاهده می‌کنید:
 
• DateTime.TryParse (String value, out DateTime result)
Value : رشته‌ای از تاریخ و زمان می‌باشد.
Result : مقدار تاریخ را بعد از جداسازی در خود ذخیره می‌کند.

• DateTime.TryParse(String value, IFormatProvider provider, DateTimeStyles styles, out DateTime result)
Value : رشته‌ای از تاریخ و زمان می‌باشد.
Provider : اطلاعات فرهنگ مورد نظر را فراهم می‌کند.
Styles  : از طریق این پارامتر می‌توانیم قالب پارامتر ارسالی را مشخص کنیم (مثلا نادیده گرفتن فضاهای خالی).
Result : مقدار تاریخ را بعد از جداسازی در خود ذخیره می‌کند.
این تابع همیشه سعی می‌کند رشته‌ی تاریخ را جداسازی کند. در صورت موفقیت در عملیات جداسازی رشته، تاریخ معتبر را بر می‌گرداند و در غیر اینصورت میزان کوچکترین تاریخ یا همان MinValue را که قبلا در همین مطلب اشاره شد، باز می‌گرداند. در صورتی هم که رشته‌، null یا با فرمت رشته‌ای غیر معتبر باشد، همان مقدار DateTime.MinValue بازگردانده می‌شود. همیشه با کنترل مقدار بازگشتی صحت انجام عملیات را می‌توان متوجه شد (true موفقیت در عملیات جداسازی و false عدم موفقیت در عملیات جداسازی).
نکته‌ی مهم عدم پرتاب استثتاء در صورت عدم موفقیت در عملیات جداسازی می‌باشد.
string datestr = null;
            DateTime temp;
            Console.WriteLine(DateTime.TryParse(datestr, out temp));
            Console.WriteLine(temp);
            //False
            //0001 - 01 - 01T00: 00:00

            datestr = "wrong date";
            Console.WriteLine(DateTime.TryParse(datestr, out temp));
            Console.WriteLine(temp);
            //False
            //0001 - 01 - 01T00: 00:00

            datestr = "Tue Dec 30, 2015";
            Console.WriteLine(DateTime.TryParse(datestr, out temp));
            Console.WriteLine(temp);
            //False
            //0001 - 01 - 01T00: 00:00

تابع ()DateTime.TryParseExact
این تابع رشته‌ای شامل تاریخ و زمان را به‌همراه فرهنگ مشخصی دریافت و آن را تبدیل به نوع DateTime می‌کند. رشته‌ی ارسالی باید منطبق با فرمت تاریخ باشد. overload‌های مختلف این تابع را در زیر مشاهده می‌کنید:

• DateTime.ParseExact(string value, string format, IFormatProvider provider, DateTimeStyles style)
Value : رشته‌ای از تاریخ و زمان می‌باشد.
Format : فرمت مورد نظر را برای نمایش تاریخ بعد از تبدیل، مشخص می‌کند.
Provider : اطلاعات فرهنگ مورد نظر را فراهم می‌کند.
Styles  : از طریق این پارامتر می‌توانیم قالب پارامتر ارسالی را مشخص کنیم (مثلا نادیده گرفتن فضاهای خالی).

• DateTime.ParseExact(string value, string[] formats, IFormatProvider provider, DateTimeStyles style)
Value : رشته‌ای از تاریخ و زمان می‌باشد.
Format : فرمت مورد نظر برای نمایش تاریخ بعد از تبدیل را مشخص می‌کند و می‌توان آرایه‌ای از فرمت‌ها را در اینجا ارسال کرد.
Provider : اطلاعات فرهنگ مورد نظر را فراهم می‌کند.
Styles  : از طریق این پارامتر می‌توانیم قالب پارامتر ارسالی را مشخص کنیم (مثلا نادیده گرفتن فضاهای خالی).
این تابع را در شرایط زیر، مقدار (MinValue( 1/1/0001 12:00:00 AM را باز می‌گرداند:
• رشته null  باشد.
• رشته خالی باشد "".
• فرمت رشته‌ی تاریخ غلط باشد.
• رشته با فرمت معرفی شده‌ی در provider، انطباق نداشته باشد.
• تنها در صورتی که مقدار انتخابی DateTimeStyle معتبر نباشد، استثنایی رخ می‌دهد.
همچنین همیشه پس از انجام عملیات، مقداری بولین را برای نمایش موفقیت یا عدم موفقیت جدا سازی، باز می‌گرداند.
  string datestr = null;
            DateTime temp;
            CultureInfo provider = CultureInfo.InvariantCulture;
            Console.WriteLine(DateTime.TryParseExact(datestr, "MM/dd/yyyy", provider, DateTimeStyles.None, out temp));
            Console.WriteLine(temp);
            //False
            //1/1/0001 12:00:00 AM

            datestr = "wrong date";
            Console.WriteLine(DateTime.TryParseExact(datestr, "MM/dd/yyyy", provider, DateTimeStyles.None, out temp));
            Console.WriteLine(temp);
            //False
            //1/1/0001 12:00:00 AM

            datestr = "Tue Dec 30, 2015";
            Console.WriteLine(DateTime.TryParseExact(datestr, "MM/dd/yyyy", provider, DateTimeStyles.None, out temp));
            Console.WriteLine(temp);
            //False
            //1/1/0001 12:00:00 AM

            datestr = "10‐22‐2015";
            Console.WriteLine(DateTime.TryParseExact(datestr, "MM/dd/yyyy", provider, DateTimeStyles.None, out temp));
            Console.WriteLine(temp);
            //False
            //1/1/0001 12:00:00 AM

            datestr = "10‐22‐2015";
            Console.WriteLine(DateTime.TryParseExact(
            datestr, "MM‐dd‐yyyy", provider, DateTimeStyles.None, out temp));
            Console.WriteLine(temp);
            //True
            //30/07/1394 12:00:00 ق.ظ

            datestr = "10‐12‐2015";
            Console.WriteLine(DateTime.TryParseExact(datestr, new string[] { "MM/dd/yyyy", "MM‐dd‐yyyy", "MM.dd.yyyy" }, provider, DateTimeStyles.None, out temp));
            Console.WriteLine(temp);
            //True
            //20/07/1394 12:00:00 ق.ظ


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

تفاوت Parse و ConvertToDateTime:
این دو متد شبیه به هم هستند، اما چند تفاوت کوچک با هم دارند:
• اگر مقدار رشته‌ی ارسالی null باشد، متد Parse، یک استثناء را ارسال می‌کند و متد ConvertToDateTime مقدار MinValue را باز می‌گرداند.
• در متد Parse می‌توان یک پارامتر اضافه‌تر نیز ارسال کرد که DateTimeStyle نامیده می‌شود. اما متد ConvertToDateTime این قابلیت را ندارد.
• تابع ConvertToDateTime به‌صورت داخلی از متد DateTime.Parse به‌همراه فرهنگ جاری استفاده می‌کند.

تفاوت تابع Parseو ParseExact:
این دو تابع شبیه به هم هستند، اما می‌توان یک پارامتر اضافی format را نیز به تابع ParseExact ارسال کرد. این پارامتر به ما کمک می‌کند تا رشته‌ای را که فرمت متفاوتی از حالت معمولی دارد، به تاریخ تبدیل کنیم. مثلا اگر رشته‌ی "11232015" بدین شکل باشد، فرمت باید به شکل "MMddyyyy" تعریف شود.

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

تفاوت تابع TryParse و TryParseExact:
هر دو تابع شبیه به هم هستند؛ بجز در پارامتر format. تابع TryParseExact از یک پارامتر اضافه‌ی format استفاده می‌کند. ولی تابع TryParse اگر نتواند فرمت مورد نیاز را فراهم کند، مقدار minValue را باز می‌گرداند.
 
مطالب
بلاگ‌ها و مطالب مطالعه شده در هفته قبل (هفته دوم آبان)

وبلاگ‌ها و سایت‌های ایرانی


Visual Studio


امنیت


ASP. Net


طراحی وب


اس‌کیوال سرور


به روز رسانی‌ها


ابزارها


سی‌شارپ

  • تازه‌های سی شارپ 4 ، واژه کلیدی dynamic ، قسمت‌های یک و دو و سه

عمومی دات نت


CPP


دلفی


ویندوز


Office

  • آشنایی با یک سری از اصطلاحات outlook 2007 برای برنامه نویس‌ها. (اگر قصد داشته باشید یک add-in را برای outlook 2007 با استفاده از امکانات VSTO توسعه دهید، آشنایی با این اصطلاحات بسیار ضروری خواهد بود)

متفرقه



مطالب
صفحه بندی اطلاعات در ASP.NET MVC به روش HashChange
یکی از مواردی که درپروژه‌‌ها زیاد مورد استفاده قرار میگیرد، نمایش داده‌های ذخیره شده‌ی در بانک اطلاعاتی، به صورت صفحه بندی شده به کاربر می‌باشد. قبلا در زمینه بحث Paging، مطلبی تهیه شده بود و در این مقاله قصد داریم کتابخانه‌ای را مورد بررسی قرار دهیم که علاوه بر ارسال داده به صورت Ajax ایی، بتواند همچنین پارامترهای مورد نظر را به صورت Query String نیز در آدرس بار نمایش دهد.
اگر به جستجوی گوگل دقت کرده باشید، به صورت Ajax ایی پیاده سازی شده‌است، با این تفاوت که بعد از هر تغییر درجستجوی مورد نظر، Url صفحه نیز تغییر میکند (برای مثال بعد از جستجوی عبارت dotNetTips  آدرس بار صفحه به شکل https://www.google.com/#q=dotNetTips&* تغییر می‌کند). برای پیاده سازی این ویژگی باید از تکنیکی به نام HashChange استفاده کرد. در نتیجه با این روش مشکل ارسال صفحه‌ای خاص در یک گرید برای دیگران، به صورت Ajax ایی و بدون مشکل انجام می‌شود. از این رو با توجه به داشتن Url‌های منحصر به فرد برای هر صفحه، تا حدی مشکل سئو سایت را نیز برطرف می‌کنیم.

برای استفاده از این ویژگی در ادامه قصد داریم پیاده سازی کتابخانه‌ی MvcAjaxPager را مورد بررسی قرار دهیم. ابتدا قبل از هر کاری، با استفاده از دستور زیر اقدام به نصب کتابخانه آن می‌نماییم:
 Install-Package MvcAjaxPager

در ادامه نحوه پیاده سازی آن را به همراه مثالی، مورد بررسی قرار می‌دهیم:

ابتدا یک مدل فرضی را همانند زیر تهیه می‌کنیم :
public class Topic
{
   public int Id;
   public string Title;
   public string Text;
}
و کلاسی را همانند زیر برای دریافت یک لیست از مطالب می‌نویسیم:
public class TopicService
{
    public static IEnumerable<Topic> Topics = new List<Topic>() {
       new Topic{Id=1,Title="Title 1",Text= "Text 1"},
       new Topic{Id=2,Title="Title 2",Text="Text 2"},
       new Topic{Id=3,Title="Title 3",Text="Text 3"},
       new Topic{Id=4,Title="Title 4",Text="Text 4"},
       new Topic{Id=5,Title="Title 5",Text="Text 5"},
       new Topic{Id=6,Title="Title 6",Text="Text 6"},
       new Topic{Id=7,Title="Title 7",Text="Text 7"},
       new Topic{Id=8,Title="Title 8",Text="Text 8"},
       new Topic{Id=9,Title="Title 9",Text="Text 9"},
       new Topic{Id=10,Title="Title 10",Text="Text 10"},
       new Topic{Id=11,Title="Title 11",Text="Text 11"},
       new Topic{Id=12,Title="Title 12",Text="Text 12"},
       new Topic{Id=13,Title="Title 13",Text="Text 13"},
       new Topic{Id=14,Title="Title 14",Text="Text 14"},
       new Topic{Id=15,Title="Title 15",Text="Text 15"},
       new Topic{Id=16,Title="Title 16",Text="Text 16"},
       new Topic{Id=17,Title="Title 17",Text="Text 17"},
       new Topic{Id=18,Title="Title 18",Text="Text 18"},
       new Topic{Id=19,Title="Title 19",Text="Text 19"},
       new Topic{Id=20,Title="Title 20",Text="Text 20"},
       new Topic{Id=21,Title="Title 21",Text="Text 21"},
       new Topic{Id=22,Title="Title 22",Text="Text 22"},
      };

    public static IEnumerable<Topic> GetAll()
    {
       return Topics.OrderBy(row => row.Id);
    }
}
همچنین کلاس زیر را اضافه میکنیم:
public class ListViewModel
{
   public IEnumerable<Topic> Topics { get; set; }
   public int PageIndex { get; set; }
   public int TotalItemCount { get; set; }
}
ابتدا یک کنترلر را ایجاد می‌کنیم به همراه اکشن متدی که قصد داریم لیستی از اطلاعات را به کاربر نمایش دهیم:
public ActionResult Index(int page = 1)
{
       var topics = TopicService.GetAll ();
       int totalItemCount = topics.Count();
       var model = new ListViewModel()
       {
              PageIndex = page,
              Topics = topics.OrderBy(p => p.Id).Skip((page - 1) * 10).Take(10).ToList(),
              TotalItemCount = totalItemCount
       };

       if (!Request.IsAjaxRequest())
       {
              return View(model);
       }

       return PartialView("_TopicList", model);
}
در اینجا بعد از واکشی اطلاعات، تعداد 10 رکورد را در هر صفحه نمایش می‌دهیم. 

و در Partial view مربوطه نیز داریم :
@using MvcAjaxPager
@model ListViewModel

@Html.AjaxPager(Model.TotalItemCount, 10, Model.PageIndex, "Index", "Home", null, new PagerOptions
   {
       ShowDisabledPagerItems = true,
       AlwaysShowFirstLastPageNumber = true,
       HorizontalAlign = "center",
       ShowFirstLast = false,
       CssClass = "NavigationBox",
       AjaxUpdateTargetId = "dvTopics",
       AjaxOnBegin = "AjaxStart",
       AjaxOnComplete = "AjaxStop"
   }, null, null)

<table>
    <tr>
        <th>
            @Html.DisplayName("ID")
        </th>
        <th>
            @Html.DisplayName("Title")
        </th>
        <th>
            @Html.DisplayName("Text")
        </th>
    </tr>

    @foreach (var topic in Model.Topics)
    {
        <tr>
            <td>
                @topic.Id
        </td>
        <td>
            @topic.Title
        </td>
        <td>
            @topic.Text
        </td>
    </tr>
    }
</table>

@Html.AjaxPager(Model.TotalItemCount, 10, Model.PageIndex, "Index", "Home", null, new PagerOptions
   {
       ShowDisabledPagerItems = true,
       AlwaysShowFirstLastPageNumber = true,
       HorizontalAlign = "center",
       ShowFirstLast = true,
       FirstPageText = "اولین",
       LastPageText = "آخرین",
       MorePageText = "...",
       PrevPageText = "قبلی",
       NextPageText = "بعدی",
       CssClass = "NavigationBox",
       AjaxUpdateTargetId = "dvTopics",
       AjaxOnBegin = "AjaxStart",
       AjaxOnComplete = "AjaxStop"
   }, null, null)

 حال برای استفاده از pager مورد نظر فقط کافیست متد AjaxPager آن را فراخوانی کنیم. این متد شامل 11  OverLoad مختلف هست.
در این قسمت TotalItemCount جمع کل رکورد‌ها، PageSize تعداد رکورد‌های هر صفحه و PageIndex آدرس صفحه جاری می‌باشد.

مهمترین بخش این pager  که قابلیت‌های زیادی را به کاربر می‌دهد، قسمت PagerOptions آن است و تعدادی از پارامتر‌های آن شامل AjaxOnBeginAjaxOnCompelte، AjaxOnSuccess ،  AjaxOnFailure میتوان تعیین کرد تا بعد از شروع، وقوع خطا، موفقیت و یا خاتمه عملیات جاوا اسکریپتی، اجرا شود. 

AlwaysShowFirstLastPageNumber جهت نمایش صفحه اول و آخر
FirstPageText جهت تعیین متن اولین صفحه
LastPageText جهت تعیین متن آخرین صفحه
CssClass ، Id  جهت تعیین Id خاص

و در انتها، در view مربوطه داریم:
@using MvcAjaxPager
@model ListViewModel
@{
    Layout = null;
}

<!DOCTYPE html>

<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>Index</title>
</head>
<body>
    <div id="dvTopics">
        @{
            @Html.Partial("_TopicList", Model);
        }
    </div>

    <script type="text/javascript" src="@Url.Content("~/Scripts/jquery-1.7.2.min.js")"></script>
    <script type="text/javascript" src="@Url.Content("~/Scripts/path.min.js")"></script>
    <script type="text/javascript" src="@Url.Content("~/Scripts/jquery.pager-1.0.1.min.js")"></script>
    <script type="text/javascript">
        $('.NavigationBox').pager();

        //pagination before start
        function AjaxStart() {
            console.log('Start AJAX call. Loading message can be shown');
        }
        // pagination - after request
        function AjaxStop() {
            console.log('Stop AJAX call. Loading message can be hidden');
        };
    </script>
</body>
</html>
در انتهای صفحه مورد نظر می‌بایست دو فایل جاوااسکریپتی jquerypager و Path را که هنگام نصب Pager، به برنامه اضافه شده اند، فراخوانی کنیم و با استفاده از CssClass  یا Id که قبلا در بخش PagerOption تعیین کردیم، آن را انتخاب و متدpager را فراخوانی کنیم.
مطالب
بررسی الگوهای ایندکس‌های Non-Clustered در SQL Server

قصد داریم الگوهای مختلف ایندکس گذاری و استراتژی Non-Clustered Indexes را در Sql Server، بررسی کنیم.

مزایای ایجاد ایندکس‌های صحیح بر اساس نیازهای واقعی کاری:

  • سریعتر شدن اجرای کوئری‌های جستجو در تعداد رکوردهای بالا
  • مرتب سازی سریعتر نتایج (sorting)
  • کوئری‌هایی که بر اساس عبارت GROUP BY ایجاد شده‌اند، سریعتر اجرا خواهند شد 

Non-Clustered Indexes 

تقریبا در تمام دیتابیس‌ها به راه‌های دیگری برای دسترسی به داده‌های جداول نیاز خواهد شد که لزوما این داده‌ها براساس ترتیب هنگام ذخیره سازی، مرتب نیستند. در چنین شرایطی ایندکس‌های غیر خوشه‌ای بر سر کار خواهند آمد.
در ادامه الگوهای مختلف ایندکس گذاری مرتبط با ایندکس‌های غیر خوشه‌ای را بررسی کرده و برای هر کدام از آنها مثالی را بررسی خواهیم کرد. خواهیم دید هر ایندکسی که از جانب ما ایجاد می‌شود، نمیتوان مطمئن شد که توسط Sql Server  مورد استفاده قرار می‌گیرد!
این الگو‌ها در تعیین زمان و مکان ساخت ایندکس‌های غیر خوشه‌ای، به ما کمک خواهند کرد که به شرح زیر می‌باشند:
  • Search Columns
  • Index Intersection
  • Multiple Columns
  • Covering Indexes
  • Included Columns
  • Filterd Indexes
  • Foreign Keys

Search Columns

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

USE AdventureWorks2012;

GO
CREATE TABLE dbo.Contacts (
    ContactID         INT           IDENTITY (1, 1),
    FirstName         NVARCHAR (50),
    LastName          NVARCHAR (50),
    IsActive          BIT          ,
    EmailAddress      NVARCHAR (50),
    CertificationDate DATETIME     ,
    FillerData        CHAR (1000)  ,
    CONSTRAINT PK_Contacts PRIMARY KEY CLUSTERED (ContactID)
);

INSERT INTO dbo.Contacts (FirstName, LastName, IsActive, EmailAddress, CertificationDate)
SELECT pp.FirstName,
       pp.LastName,
       IIF (pp.BusinessEntityID / 10 = 1, 1, 0),
       pea.EmailAddress,
       IIF (pp.BusinessEntityID / 10 = 1, pp.ModifiedDate, NULL)
FROM   Person.Person AS pp
       INNER JOIN
       Person.EmailAddress AS pea
       ON pp.BusinessEntityID = pea.BusinessEntityID;

ابتدا قصد داریم از جدول Contacts بدون استفاده از هیچ ایندکس غیر خوشه‌ای، کوئری بگیریم. نتیجه‌های نشان داده شده‌ی در کوئری حاصل از کد T-SQL زیر به شرح زیر است:

SET STATISTICS IO ON;

SELECT ContactID,
       FirstName
FROM   dbo.Contacts
WHERE  FirstName = 'Catherine';

SET STATISTICS IO OFF;

22 رکورد را واکشی کرده است؛ ولی با خواندن 2866 page ! که این تعداد، تمام صفحات موجود در جدول می‌باشد. بنابراین واکشی این تعداد رکورد از کل رکورد‌های موجود در جدول (19000) نیاز به چک کردن همه‌ی صفحات را خواهد داشت که واقعا روش بهینه‌ای نمی‌باشد. 

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

حال با کد T-SQL زیر یک ایندکس غیر خوشه دار را بر روی فیلد FirstName ایجاد خواهیم کرد:

CREATE INDEX IX_Contacts_FirstName ON dbo.Contacts(FirstName);

اگر دوباره کوئری قبلی را اجرا کنیم، به نتایج خیلی بهتری خواهیم رسید و تعداد صفحات خوانده شده به 2 کاهش یافته است! 

Sql Server این بار به جای اسکن دوباره‌ی ایندکس خوشه دار، با استفاده از Index Seek و بهره بردن از ایندکس ایجاد شده‌ی توسط ما، یک پلن قابل قبول را برای ما ارائه داده است.

Index Intersection

در برخی از سناریوها لازم است یکسری ستون دیگر هم علاوه بر ستونی که ایندکس را بر روی آن تعریف کرده‌ایم، در بخش شرط یا خروجی select استفاده شوند. یکی از راه‌حل‌ها، ایجاد یک ایندکس غیر خوشه‌ای که سایر ستون‌ها را نیز Include می‌کند، می‌باشد. با وجود ایندکس‌هایی که هر کدام از آنها می‌توانند برای ادا کردن بخشی از شروط، نقش ایفا کنند، Sql Server  هم با به کار بردن آنها می‌تواند رکوردهایی که در فصل مشترک حاصل از جسجتوی این ایندکس‌ها بدست آمده را به عنوان خروجی کوئری ما بازگشت دهد. این عملیات Index Intersection نام دارد. به مثال زیر توجه کنید:

SET STATISTICS IO ON;

SELECT ContactID,
       FirstName,
       LastName
FROM   dbo.Contacts
WHERE  FirstName = 'Catherine'
       AND LastName = 'Cox';

SET STATISTICS IO OFF;

در کوئری بالا علاوه بر FirstName که یک ایندکس غیر خوشه دار را بر روی آن ایجاد کرده‌ایم، فیلد LastName را هم در بخش Select و شرط، مطرح کرده‌ایم. حالا اگر آن را اجرا کنیم، به آمار و پلن زیر دست خواهیم یافت:

بله تعداد Page‌های خوانده شده این بار به 68 افزایش یافته است که نسبت به حالت بدون LastName که 2 Page خوانده شده بود، زیاد است. همانطور که در پلن زیر مشخص است، به دلیل ایندکسی که برروی FirstName ایجاد کرده‌ایم، نمی‌تواند تمام داده‌های مورد نیاز کوئری را مهیا کند. عملیات Key Lookup و nested loop هم این بار اضافه شده‌اند. Sql Server همچنان استفاده از ایندکس موجود را در کنار Key Lookup از ایندکس خوشه دار، ارزان‌تر از اسکن ایندکس خوشه دار، تشخیص داده است.

مشکل زمانی گریبان گیر ما خواهد شد که به ازای هر مطابقتی در ایندکس غیر خوشه دار، یک بار به ایندکس خوشه دار برای بررسی شرط بعدی و واکشی دیتا، رجوع خواهد شد. باید دقت کرد که Key Lookup همیشه به عنوان مشکل مطرح نمی‌شود. ولی باعث افزایش غیرضروری هزینه‌های CPU و I/O برای کوئری خواهد شد.

برای استفاده از الگوی Index Intersection، یک ایندکس غیر خوشه دار برروی ستون LastName ایجاد خواهیم کرد:

CREATE INDEX IX_Contacts_LastName ON dbo.Contacts(LastName);

اگر این بار کوئری قبل را اجرا کنیم، به آمار و پلن زیر خواهیم رسید:

بله تعداد Page‌های خوانده شده به 5 کاهش یافته و این بار به جای استفاده از Key Lookup، از دو index seek استفاده کرده است که هزینه‌ای کمتر را نسبت به حالت قبل خواهد داشت. به دلیل اینکه این دو ایندکس تمام دیتای لازم را می‌توانند مهیا کنند، دیگر نیازی به رجوع به ایندکس خوشه دار نخواهد بود. تصویر زیر در درک پلن بالا و این الگو می‌تواند مفید باشد:

Multiple Columns

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

CREATE INDEX IX_Contacts_FirstNameLastName
    ON dbo.Contacts(FirstName, LastName);

SET STATISTICS IO ON;

SELECT ContactID,
       FirstName,
       LastName
FROM   dbo.Contacts
WHERE  FirstName = 'Catherine'
       AND LastName = 'Cox';

SET STATISTICS IO OFF;

با اجرای کوئری بالا به آمار و پلن زیر خواهیم رسید:

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

الگوی Multiple Columns هم به مانند الگوی Search Columns باید هنگام ایندکس گذاری دیتابیس در نظر گرفته شود و از اهمیت بالایی برخوردار است. باید توجه داشت اگر فیلدهایی که در قسمت شرطی کوئری مطرح می‌شوند، متغییر باشد، استفاده از الگوی Index Intersection مفید خواهد. ولی برای مواقعی که نیاز است یکسری فیلد به صورت یکجا در بخش شرطی کوئری مطرح شوند، الگوی Multiple Columns کارآیی بهتری خواهد داشت. از این دو الگوی مطرح شده که در تناقض باهم قرار دارند، می‌توان به نحوی استفاده برد تا هزینه‌ی کلی را کاهش داد.

Covering Index

الگوی بعدی، ایندکس پوشش دهنده نام گرفته است. همانند نامی که دارد، هدف آن نگهداری یکسری ستون در ستون‌های ایندکس تولیدی که اتفاقا این ستون‌ها در قسمت شرطی کوئری قرار ندارند، ولی قرار است به عنوان خروجی Select برگردانده شوند، می‌باشد.
این الگو به عنوان یک روش استاندارد ایندکس گذاری در Sql Server مطرح بوده است. البته در ادامه و با بروز شدن روش‌هایی که می‌توان ایندکس‌ها را ایجاد کرد، این الگو نسبت به قبل کمتر مفید است! از آن جهت که یک روش شناخته شده می‌باشد، در این قسمت این مورد را هم مطرح کردیم. به مثال زیر توجه کنید:

SET STATISTICS IO ON;

SELECT ContactID,
       FirstName,
       LastName,
       IsActive
FROM   dbo.Contacts
WHERE  FirstName = 'Catherine'
       AND LastName = 'Cox';

SET STATISTICS IO OFF;

در کوئری بالا این بار قصد داریم خصوصیت IsActive را که در ایندکس IX_Contacts_FirstNameLastName نگهداری نمی‌شود و همچنین در قسمت شرطی هم مطرح نشده و نیازی به آن نبوده، هم واکشی کنیم. با توجه به نتایج بدست آمده که در آمار و پلن زیر مشخص است، باز هم تعداد Page‌های خوانده شده به 5 افزایش یافته و بار دیگر، Key Lookup و Nested Loop را در کنار یک Index Seek، برروی ایندکسی که با الگوی Multiple Columns ایجاد کرده‌ایم، خواهیم داشت.


الگوی index covering پیشنهاد می‌کند ستونی را هم که در قسمت شرطی مطرح نمی‌شود، به عنوان ستونی اصلی در ایندکس، نگهداری کنیم؛ به شکل زیر:

CREATE INDEX IX_Contacts_FirstNameLastNameIsActive ON dbo.Contacts(FirstName, LastName,IsActive)

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

باز هم هزینه‌ی Key Lookup حذف شده و این بار از ایندکس جدید ما استفاده شده و تعداد Page‌های خوانده شده هم به 2 کاهش یافته است.
این الگو در بیشتر سناریو‌ها کاملا مفید بوده و پتانسیل افزایش کارآیی را در بیشتر سناریو‌ها دارد. اما در سال‌های اخیر از زمانیکه امکانات جدیدی در Sql Server 2005 به بعد ایجاد شد، از استفاده‌ی آن کاسته شده است. با وجود این امکانات جدید که در الگوی بعد به آن خواهیم پرداخت، می‌توان ستون‌های اضافی را در ایندکس‌ها، Include کنیم و نیازی نیست که جزء ستون‌های اصلی ایندکس باشند. 

Included Columns

الگوی Included Columns درواقعا پسر عموی الگوی Covering Index می‌باشد. در این الگو از عبارت INCLUDE در ایجاد یا تغییر ایندکس استفاده می‌شود و از این طریق امکان این را مهیا می‌کند تا یکسری ستون که جز ستون‌های اصلی ایندکس نیستند هم در ایندکس غیر خوشه دار ما افزوده شوند و حتی در قسمت شرطی هم مطرح شوند. این عمل خیلی شبیه به نگهداری دیتا‌های غیر کلیدی در یک ایندکس خوشه دار می‌باشد و این همان تفاوت اصلی بین دو الگو مطرح شده است.

اگر کوئری زیر را اجرا کنیم:

SET STATISTICS IO ON;

SELECT ContactID,
       FirstName,
       LastName,
       EmailAddress
FROM   dbo.Contacts
WHERE  FirstName = 'Catherine';

SET STATISTICS IO OFF;

68 Page خوانده شده خواهیم داشت که حاصل یک Index Seek بر روی ایندکس IX_Contacts_FirstName می‌باشد و برای واکشی بقیه ستون‌ها هم یک Key Lookup بر روی ایندکس خوشه دار در پلن مشخص خواهد بود.

علاوه بر ایندکس‌های ایجاد شده‌ی در مراحل قبل، حال یک ایندکس غیر خوشه‌ای را با استفاده از الگوی INC ایجاد می‌کنیم:

CREATE INDEX IX_Contacts_FirstNameINC ON dbo.Contacts(FirstName)
INCLUDE (LastName, IsActive, EmailAddress);

دوباره کوئری قبلی را اگر اجرا کنیم، نتایج به دست آمده، به شرح زیر خواهد بود:

این بار از ایندکس جدید ایجاد شده استفاده شده و تعداد Page‌های خوانده شده، به 3 کاهش یافته است. با توجه به انعطاف پذیری این الگو می‌توان از اندک افزایشی که در تعداد Page‌های خوانده شده نسبت به الگوی ایندکس پوشش دهنده وجود دارد، چشم پوشی کرد.
در مثال‌های قبل چندین ایندکس بر روی جدول Contacts ایجاد کرده‌ایم که 4 مورد از آنها به صورت اختصاصی بر روی فیلد FirstName بوده است. باید توجه کرد این ایندکس‌ها نیاز به فضا و نگهداری در مواقع ویرایش رکورد‌های جدول خواهند داشت. لذا این هزینه‌ها اثر منفی برروی تمام عملیاتی خواهند داشت که روی جدول انجام می‌شود.
الگوی INC می‌تواند این مشکل را برطرف کند. برای مثال با استفاده از آن می‌توان ایندکس‌های تولید شده‌ی در مراحل قبل را بر روی FirstName، توسط یک ایندکس نیز پوشش داد. لذا ایندکس‌های قبلی را حذف کرده و با یکسری کوئری، مشخص خواهیم کرد که گفته‌ی ما صحت دارد:

IF EXISTS(SELECT * FROM sys.indexes WHERE object_id = OBJECT_ID('dbo.Contacts')
AND name = 'IX_Contacts_FirstNameLastName')
DROP INDEX IX_Contacts_FirstNameLastName ON dbo.Contacts
GO
IF EXISTS(SELECT * FROM sys.indexes WHERE object_id = OBJECT_ID('dbo.Contacts')
AND name = 'IX_Contacts_FirstNameLastNameIsActive')
DROP INDEX IX_Contacts_FirstNameLastNameIsActive ON dbo.Contacts
GO
IF EXISTS(SELECT * FROM sys.indexes WHERE object_id = OBJECT_ID('dbo.Contacts')
AND name = 'IX_Contacts_FirstName')
DROP INDEX IX_Contacts_FirstName ON dbo.Contacts
GO

با کدهای بالا ایندکس‌هایی را که بر روی FirstName ایجاد شده بودند، حذف کرده و این بار تمام کوئری‌های مطرح شده‌ی در مراحل قبل را یکبار دیگر اجرا می‌کنیم:

SET STATISTICS IO ON;

SELECT ContactID,
       FirstName
FROM   dbo.Contacts
WHERE  FirstName = 'Catherine';

SELECT ContactID,
       FirstName,
       LastName
FROM   dbo.Contacts
WHERE  FirstName = 'Catherine'
       AND LastName = 'Cox';

SELECT ContactID,
       FirstName,
       LastName,
       IsActive
FROM   dbo.Contacts
WHERE  FirstName = 'Catherine'
       AND LastName = 'Cox';

SET STATISTICS IO OFF;

دو نکته‌ای که باید به آنها توجه کرد:

  1. کوئری‌ها بالا در مقایسه با الگوهای قبلی به چه شکلی اجرا خواهند شد؟
  2. توجه کردن به تعداد Page‌های خوانده شده
در جواب مورد اول، Sql Server از عملیات Index Seek برای فیلترینگ برروی FirstName استفاده کرده و اگر ستون دیگری هم در بخش شرطی کوئری آورده شده، باز هم از این نوع عملیات استفاده شده است. به عنوان مثلا در دو کوئری بعد، LastName هم در بخش شرطی مطرح شده است‌. دلیل این کار که باز هم از Index Seek استفاده می‌شود این است که بعد از اعمال فیلترینگ بر روی FirstName، حالا یکسری رکورد در اختیار داریم که اتفاقا به LastName آنها هم دسترسی هست و فقط رکورد‌ها براساس آن مرتب نشده اند و نیازی نیست به ایندکس خوشه دار دسترسی داشته باشیم. لذا می‌توان همینجا بر روی این فیلد هم فیلترینگ را اعمال کرد. به پلن زیر توجه کنید:

در جواب مورد دوم، با اینکه حدود 50% افزایش در تعداد Page‌های خوانده شده نسبت به حالتی که به صورت جدا از هم برای هر کوئری خاص یک ایندکس در نظر گرفته بودیم، داشته‌ایم ولی این تغییر کارآیی نمی‌تواند ساخت 4 ایندکس را به جای 1 ایندکس که تمام آنها را پوشش می‌دهد، توجیه کند! در حالیکه ما به کارآیی مورد نظر خود دست یافته‌ایم.

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

Filtered Indexes

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

بله همانطور که از نام این الگو نیز مشخص است، هدف آن کاهش تعداد رکوردهایی است که در ایندکس نگهداری می‌شوند. به دو کوئری زیر توجه کنید:
SET STATISTICS IO ON;

SELECT   ContactID,
         FirstName,
         LastName,
         CertificationDate
FROM     dbo.Contacts
WHERE    CertificationDate IS NOT NULL
ORDER BY CertificationDate;

SELECT   ContactID,
         FirstName,
         LastName,
         CertificationDate
FROM     dbo.Contacts
WHERE    CertificationDate BETWEEN '20050101' AND '20050201'
ORDER BY CertificationDate;

SET STATISTICS IO OFF;
در کوئری اول به دنبال رکورد هایی هستیم که CertificationDate آنها نال می‌باشد و در دومی هم به دنبال آنهایی هستیم که در یک بازه‌ی زمانی قرار دارند. از آمار و پلن زیر مشخص است که چون هیچ ایندکس غیر خوشه داری بر روی CertificationDate ایجاد نشده‌است، از Index Scan برروی ایندکس خوشه دار استفاده شده است که حاصل آن خوانده شدن 2866 عدد Page می‌باشد!

زمانیکه مقدار آن نال باشد، استفاده نخواهد شد. آیا عقل سلیم قبول می‌کند که این مقادیر نال را در ایندکس نگهداری و رکوردهایی با مقادیر نال داشته باشیم؟ برای پیاده سازی این الگو باید از عبارت Where به هنگام ساخت ایندکس‌های غیر خوشه‌ای استفاده کنیم.
 توجه کنید که امکان استفاده از مقادیر متغیر در بخش Where، وجود ندارد.
نکته‌ی بعدی این است که نمی‌توان مقایسه‌های پیچیده را در این مورد استفاده کرد. برای مثال استفاده از LIKE و BETWEEN امکان پذیر نیست.

این بار با استفاده از الگوی Filtered Indexes یک ایندکس غیر خوشه‌ای را بر روی ستون CertificationDate ایجاد می‌کنیم:

CREATE INDEX IX_Contacts_CertificationDate ON dbo.Contacts(CertificationDate)
INCLUDE (FirstName, LastName)
WHERE CertificationDate IS NOT NULL;

حال دوباره دو کوئری قبلی را اجرا می‌کنیم. آمار و پلن زیر نشان می‌دهند که این بار فقط 2 عدد Page خوانده شده است و عملیات به Index Seek بر روی ایندکس جدید تغییر کرده است.


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

  • کم شدن تعداد رکورد‌های ایندکس‌ها موجب کاهش تعداد Page‌های مورد نیاز برای ذخیره سازی آنها و در نتیجه کاهش حجم مورد نیاز برای ذخیره سازی خواهد شد.
  • با توجه به مورد اول، اگر تعداد Page‌های برای نگهداری ایندکس کم باشند، لذا فرصت Fragmentation برای ایندکس کم خواهد بود و در نتیجه، هزینه و تلاش کمی برای نگهداری آن لازم است.
  • زمانیکه تعداد مقادیر نگهداری شده‌ی در ایندکس محدود هستند، تعداد Page هایی که برای پیمایش نیاز است، کم خواهند بود و اینجاست که حتی Index Scan هم بروری آن خیلی بهینه‌تر از Index Scan بر روی ایندکس خوشه دار می‌باشد.
شرایطی که می‌توان و باید از Filtered Indexes استفاده کرد:
  • اگر لازم است بر روی یک ستون که به‌صورت نال‌پذیر است، ایندکس ایجاد کنید(دلایل آن پیش‌تر گفته شد).
  • اگر لازم است برروی Sparse Column، یک ایندکس یکتا ایجاد کنید.
  • مورد بعدی همان بحث کاهش تعداد رکوردهایی می‌باشد که در ایندکس ذخیره می‌شوند.
Foreign Keys
آخرین الگویی که به آن می‌پردازیم مربوط می‌شود به کلید خارجی. این مورد تنها الگویی است که به طور مستقیم به اشیاء موجود در طراحی دیتابیس مربوط می‌باشد. کلید‌های خارجی گاهی مواقع می‌توانند باعث بروز مشکلی کارآیی شوند، بدون آنکه کسی متوجه این دخالت در کارآیی باشد.
از آنجائیکه کلید خارجی یک قید را بر روی مقادیر مجاز برای یک ستون مهیا می‌کند، لذا یک بررسی برای زمانیکه مقادیر نیاز به اعتبارسنجی دارند، وجود خواهد داشت. این اعتبارسنجی با توجه به شکل زیر دو نوع می‌باشد که به شرح زیر است:

  1. اعتبارسنجی بر روی جدول ParentTable  
  2. اعتبارسنجی بر روی جدول ChildTable 

در مورد نوع اول، هر وقت که رکوردهای جدول ChildTable تغییر کند، در این صورت مقدار ParentID موجود جدول ChildTable با یک جستجو در جدول ParentTable اعتبارسنجی خواهد شد. از آنجایی که این کلید خارجی در جدول ParentTable یک کلید اصلی بوده، یک ایندکس خوشه دار بر روی آن ایجاد شده است و تأثیری در کاهش کارآیی نخواهد داشت.
در مورد نوع دوم، هروقت تغییراتی بر روی  ParentID موجود در جدول ParentTable داشته باشیم، نیاز است اعتبار سنجی بر روی جدول ChildTable انجام شود. برای مثال با حذف یک رکورد در جدول پدر، لازم است که جدول فرزند بررسی کند که آیا این ParentID در رکورد‌ها موجود استفاده شده است یا خیر؟ در این نوع از اعتبارسنجی، الگوی Foreign Key خود را نشان می‌دهد.

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

USE AdventureWorks2012;


GO
CREATE TABLE dbo.Customer (
    CustomerID  INT        ,
    FillterData CHAR (1000),
    CONSTRAINT PK_Customer_CustomerID PRIMARY KEY CLUSTERED (CustomerID)
);

CREATE TABLE dbo.SalesOrderHeader (
    SalesOrderID INT        ,
    OrderDate    DATETIME   ,
    DueDate      DATETIME   ,
    CustomerID   INT        ,
    FillterData  CHAR (1000),
    CONSTRAINT PK_SalesOrderHeader_SalesOrderID PRIMARY KEY CLUSTERED (SalesOrderID),
    CONSTRAINT GK_SalesOrderHeader_CustomerID_FROM_Customer FOREIGN KEY (CustomerID) REFERENCES dbo.Customer (CustomerID)
);

کد T-SQL بالا دو جدول مشتری و سفارش را ایجاد کرده و یک ارتباط یک به چند مابین آنها را از سمت مشتری به سفارش ایجاد می‌کند. برای انجام آزمایش خود، یکسری دیتای موجود را هم از جداول دیتابیس AdventureWorks2012 در جداول بالا درج می‌کنیم:

INSERT INTO dbo.Customer (CustomerID)
SELECT CustomerID
FROM   Sales.Customer;

INSERT INTO dbo.SalesOrderHeader (SalesOrderID, OrderDate, DueDate, CustomerID)
SELECT SalesOrderID,
       OrderDate,
       DueDate,
       CustomerID
FROM   Sales.SalesOrderHeader;

در واقع می‌خواهیم نشان دهیم که در زمان تغییر یک رکورد از جدول Customers، چه اتفاقاتی می‌افتد. برای مثال این تغییر می‌تواند حذف یک رکورد باشد که به شکل زیر آن را انجام خواهیم داد:

SET STATISTICS IO ON;

DELETE dbo.Customer
WHERE  CustomerID = 701;

SET STATISTICS IO OFF;

آمار و پلن زیر نشان می‌دهد که برای حذف یک رکورد در جدول مشتری، چون از عملیات Index Seek برروی ایندکس خوشه دار موجود برروی ستون CustomerID استفاده شده است، تنها 3 Page خوانده شده‌است؛ ولی برای اعتبارسنجی برروی جدول سفارش، با خواندن 4513 page و انجام عملیات Index Scan برروی ایندکس خوشه دار باعث کاهش کارآیی شده است.

برای پیاده سازی الگوی کلیدخارجی یک ایندکس غیر خوشه‌ای را بر روی CustomerID در جدول سفارشات ایجاد می‌کنیم:

CREATE INDEX IS_SalesOrderHeader_CustomerID ON dbo.SalesOrderHeader(CustomerID)

اگر دوباره کوئری بالا را با یک CustomerID دیگر انجام دهیم، به نتایج بهتری دست خواهیم یافت. تعداد Page‌های خوانده شده‌ی برای اعتبارسنجی جدول سفارشات، به عدد 2 کاهش یافته است! و از یک عملیات Index Seek بر روی ایندکس ایجاد شده، استفاده شده است.

اگر از EF استفاده می‌کنید، در حال حاضر به غیر از الگوهای Filtered Indexes و Include Indexes، پیاده سازی بقیه الگوهای ذکر شده به صورت توکار پشتیبانی می‌شود. برای دو الگوی مذکور هم می‌توان از نوشتن T-SQL خام استفاده کرد. برای مثال:

public partial class AddIndexes : DbMigration
    {
        private const string IndexName = "IX_LogSamples";

        public override void Up()
        {
            Sql(String.Format(@"CREATE NONCLUSTERED INDEX [{0}]
                               ON [dbo].[Logs] ([SampleId],[Date])
                               INCLUDE ([Value])", IndexName));

        }

        public override void Down()
        {
            DropIndex("dbo.Logs", IndexName);
        }
    }

یا حتی خیلی تمیزتر و  با ایده گرفتن از این مطلب می‌توان به یک کد Refactoring friendly نیز دست یافت.

پ.ن: این مطلب خلاصه‌ای از فصل 8 کتاب  Expert Performance Indexing for SQL Server 2012  می‌باشد. 

نظرات مطالب
بررسی روش آپلود فایل‌ها در ASP.NET Core
یک نکته‌ی تکمیلی: روش تعیین حداکثر اندازه‌ی فایل قابل آپلود در برنامه‌های ASP.NET Core
الف) اگر برنامه توسط IIS هاست شده‌است
<?xml version="1.0" encoding="utf-8"?>
<configuration>
 <!-- To customize the asp.net core module uncomment and edit the following section. 
 For more info see https://go.microsoft.com/fwlink/?linkid=838655 -->
 <system.webServer>
  <handlers>
  <remove name="aspNetCore"/>
  <add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModule" resourceType="Unspecified"/>
  </handlers>
  <aspNetCore processPath="%LAUNCHER_PATH%" arguments="%LAUNCHER_ARGS%" stdoutLogEnabled="false" stdoutLogFile=".\logs\stdout" />
  <security>
   <requestFiltering>
    <!-- This will handle requests up to 50MB -->
    <requestLimits maxAllowedContentLength="52428800" />
   </requestFiltering>
  </security>
  </system.webServer>
</configuration>
در اینجا (در فایل web.config برنامه) مقدار خاصیت maxAllowedContentLength است که تعیین کننده‌ی حداکثر اندازه‌ی فایل قابل آپلود است. به علاوه مطمئن شوید که فایل web.config شما حتما publish شده.
همچنین requestTimeout را نیز به صورت زیر می‌توان مقدار دهی کرد:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <system.webServer>
    <aspNetCore requestTimeout="00:20:00"  .... />
  </system.webServer>
</configuration>
به همراه این تنظیمات:
        public void ConfigureServices(IServiceCollection services)
        {
            services.Configure<IISServerOptions>(options =>
            {
                options.MaxRequestBodySize = int.MaxValue;
            });
            services.Configure<FormOptions>(options =>
            {
                options.ValueLengthLimit = int.MaxValue;
                options.MultipartBodyLengthLimit = long.MaxValue; // <-- ! long.MaxValue
                options.MultipartBoundaryLengthLimit = int.MaxValue;
                options.MultipartHeadersCountLimit = int.MaxValue;
                options.MultipartHeadersLengthLimit = int.MaxValue;
            });
 
ب) اگر برنامه در لینوکس و بر اساس وب سرور Kestrel هاست شده‌است
روش اول: استفاده از ویژگی RequestSizeLimit که از نگارش ASP.NET Core 2.0 به بعد قابل استفاده‌است و صرفا به قسمتی از برنامه اعمال می‌شود:
[HttpPost]
[RequestSizeLimit(40000000)]
public async Task<IActionResult> UploadFiles(IFormFile file)
روش دوم: تنظیم سراسری آن برای کل برنامه:
        public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                .ConfigureWebHostDefaults(webBuilder =>
                {
                    webBuilder
                        .UseStartup<Startup>()
                        .ConfigureKestrel(kestrelServerOptions =>
                        {
                            kestrelServerOptions.Limits.KeepAliveTimeout = TimeSpan.FromMinutes(10);
                            kestrelServerOptions.Limits.MaxRequestBodySize = 52428800; //50MB
                        });
                });
بدیهی است تنظیم این دو روش در حالت استفاده‌ی از IIS تاثیری نخواهند داشت.