مطالب
تهیه خروجی RSS در برنامه‌های ASP.NET MVC
در این مطلب با کتابخانه تهیه شده جهت تولید فیدهای RSS سایت جاری آشنا خواهید شد. در این کتابخانه مسایل زیر لحاظ شده است:
1) تهیه یک ActionResult جدید به نام FeedResult برای سازگاری و یکپارچگی بهتر با ASP.NET MVC
2) اعمال زبان فارسی به خروجی نهایی (این مورد حداقل در IE محترم شمرده می‌شود و فید را، راست به چپ نمایش می‌دهد)
3) اعمال جهت‌های rtl و ltr به متون فارسی یا انگلیسی به صورت خودکار؛ به نحوی که خروجی نهایی در تمام فیدخوان‌ها یکسان به نظر می‌رسد.
4) اعمال کاراکتر یونیکد RLE به صورت خودکار به عناوین فارسی (این مساله سبب می‌شود تا عنوان‌های ترکیبی متشکل از حروف و کلمات فارسی و انگلیسی، در فیدخوان‌هایی که متون را، راست به چپ نمایش نمی‌دهند، صحیح و بدون مشکل نمایش داده شود.)
5) نیازی به کتابخانه اضافی خاصی ندارد و پایه آن فضای نام System.ServiceModel.Syndication دات نت است.
6) تنظیم صحیح ContentEncoding و ContentType جهت نمایش بدون مشکل متون فارسی

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

توضیحاتی در مورد نحوه استفاده از آن

کتابخانه کمکی MvcRssHelper به صورت یک پروژه Class library جدا تهیه شده است. بنابراین تنها کافی است ارجاعی را به اسمبلی آن به پروژه خود اضافه کنید. البته این پروژه برای MVC4 کامپایل شده است؛ اما با MVC3 هم قابل کامپایل است. فقط باید ارجاع به اسمبلی System.Web.Mvc.dll را حذف و نمونه MVC3 آن‌را جایگزین کنید.
پس از اضافه کردن ارجاعی به اسمبلی آن، اکشن متد فید شما یک چنین امضایی را باید بازگشت دهد:
 FeedResult(string feedTitle, IList<FeedItem> rssItems, string language = "fa-IR")
آیتم اول، نام فید است. مورد دوم، لیست عناوینی است که قرار است در فید ظاهر شوند. برای مثال، هر بار 20 آیتم آخر مطالب سایت را گزارش‌گیری کرده و به فرمت لیستی از FeedItemها باید ارائه دهید. FeedItem هم یک چنین ساختاری دارد و در اسمبلی MvcRssHelper قرار گرفته:
using System;

namespace MvcRssHelper
{
    public class FeedItem
    {
        public string Title { set; get; }
        public string AuthorName { set; get; }
        public string Content { set; get; }
        public string Url { set; get; }
        public DateTime LastUpdatedTime { set; get; }
    }
}
دو نکته در اینجا حائز اهمیت است:
الف) تاریخ استاندارد یک فید میلادی است نه شمسی. به همین جهت DateTime در اینجا ظاهر شده است.
ب) Url آدرسی است به مطلب متناظر در سایت و باید یک آدرس مطلق مثلا شروع شده با http باشد.


یک مثال از استفاده آن

فرض کنید مدل مطالب سایت ما به نحو زیر است:
using System;

namespace MvcRssApplication.Models
{
    public class Post
    {
        public int Id { set; get; }
        public string Title { set; get; }
        public string AuthorName { set; get; }
        public string Body { set; get; }
        public DateTime Date { set; get; }
    }
}
و یک منبع داده فرضی (کوئری از بانک اطلاعاتی یا استفاده از یک ORM یا ... موارد دیگر) نهایتا تعدادی رکورد را در اختیار ما خواهد گذاشت:
using System;
using System.Collections.Generic;
using MvcRssApplication.Models;

namespace MvcRssApplication.DataSource
{
    public static class BlogItems
    {
        public static IList<Post> GetLastBlogPostsList()
        {
            var results = new List<Post>();
            for (int i = 1; i < 21; i++)
            {
                results.Add(new Post
                {
                     AuthorName = "شخص " + i,
                     Body = "مطلب " + i,
                     Date = DateTime.Now.AddDays(-i),
                     Id = i,
                     Title = "عنوان "+i
                });
            }
            return results;
        }
    }
}
اکنون برای نمایش این اطلاعات به صورت یک فید، تنها کافی است به صورت زیر عمل کنیم:
using System.Collections.Generic;
using System.Web.Mvc;
using MvcRssApplication.DataSource;
using MvcRssApplication.Models;
using MvcRssHelper;

namespace MvcRssApplication.Controllers
{
    public class HomeController : Controller
    {
        const int Min15 = 900;

        [OutputCache(Duration = Min15)]
        public ActionResult Index()
        {
            var list = BlogItems.GetLastBlogPostsList();
            var feedItemsList = mapPostsToFeedItems(list);
            return new FeedResult("فید مطالب سایت ما", feedItemsList);
        }

        private List<FeedItem> mapPostsToFeedItems(IList<Post> list)
        {
            var feedItemsList = new List<FeedItem>();
            foreach (var item in list)
            {
                feedItemsList.Add(new FeedItem
                {
                    AuthorName = item.AuthorName,
                    Content = item.Body,
                    LastUpdatedTime = item.Date,
                    Title = item.Title,
                    //این آدرس باید مطلق باشد به نحو زیر
                    Url = this.Url.Action(actionName: "Details", controllerName: "Post", routeValues: new { id = item.Id }, protocol: "http")
                });
            }
            return feedItemsList;
        }
    }
}
توضیحات
BlogItems.GetLastBlogPostsList منبع داده فرضی ما است. در ادامه باید این اطلاعات را به صورت لیستی از FeedItemها در آوریم. می‌توانید از AutoMapper استفاده کنید یا در این مثال ساده، نحوه انجام کار را در متد mapPostsToFeedItems ملاحظه می‌کنید.
نکته مهم بکارگرفته شده در متد mapPostsToFeedItems، استفاده از Url.Action برای تولید آدرس‌هایی مطلق متناظر با کنترلر نمایش مطالب سایت است.
پس از اینکه feedItemsList نهایی به صورت پویا تهیه شد، تنها کافی است  return new FeedResult را به نحوی که ملاحظه می‌کنید فراخوانی کنیم تا خروجی حاصل به صورت یک فید RSS نمایش داده شود و قابل استفاده باشد. ضمنا جهت کاهش بار سرور می‌توان از OutputCache نیز به مدتی مشخص استفاده کرد.
نظرات مطالب
ارتقاء به ASP.NET Core 1.0 - قسمت 4 - فعال سازی پردازش فایل‌های استاتیک
ساختار نهایی متود DownloadFile در ImageHandlerController :

[AllowAnonymous]
        [ResponseCache(VaryByHeader = "id;imgSize", Duration = 30, Location = ResponseCacheLocation.Client, NoStore = true)]
        public IActionResult DownloadFile([FromRoute]string id, [FromQuery] ImgSize imgSize)
        {
         //Tuple<string, string, string>(queryResult.FileName, queryResult.FileOnDs,queryResult.FileContentType)   
          var result = _baseFileService.GetFileNameAndFileNameOnDsAndFileType(id);
            if (result == null) return View("Error");

            var fileName = result.Item1;
            string userAgent = Request.Headers["User-Agent"];
            if (IsInternetExplorer(userAgent))
            {
                var htencode = HtmlEncoder.Create();
                var attachment = string.Format("attachment; filename=\"{0}\"", htencode.Encode(fileName));
                _httpContext.HttpContext.Response.Headers.Add("Content-Disposition", attachment);
            }
            var rootPath = Path.Combine(_hostingEnvironment.WebRootPath, _settingsAppPathConfig.Value.ServerImagesRootPath);
            var filepath = Path.Combine(rootPath, imgSize.ToString().ToLower(), result.Item2);
            var filefinalpath = "~/" + _settingsAppPathConfig.Value.ServerImagesRootPath + "/" + imgSize.ToString().ToLower() + "/" + result.Item2;
            if (!System.IO.File.Exists(filepath))
            {
                const string notFoundImage = "notFound.jpg";
                var notFoundpath = "~/" + _settingsAppPathConfig.Value.ServerImagesRootPath + "/"+ notFoundImage;
                string contentType;
                new FileExtensionContentTypeProvider().TryGetContentType(notFoundImage, out contentType);
                return File(notFoundpath, contentType, notFoundImage);
            }
            string contentTypebase;
            new FileExtensionContentTypeProvider().TryGetContentType(result.Item3, out contentTypebase);
            return File(filefinalpath, contentTypebase, fileName);
        }
مطالب
غنی سازی کامپایلر C# 9.0 با افزونه‌ها
از زمانیکه کامپایلر #C، تحت عنوان Roslyn بازنویسی شد، قابلیت افزونه‌پذیری نیز پیدا کرد. برای مثال می‌توان آنالیز کننده‌ای را طراحی کرد که در پروسه‌ی کامپایل متداول کدهای  #C مورد استفاده قرار گرفته و خطاها و یا اخطارهایی را صادر کند که جزئی از پیام‌های استاندارد کامپایلر #C نیستند. در این مطلب نحوه‌ی معرفی آن‌ها را به پروژه‌های جدید NET 5.0.، بررسی می‌کنیم.


معرفی تعدادی آنالیز کننده‌ی کد که به عنوان افزونه‌ی کامپایلر #C قابل استفاده هستند

Microsoft.CodeAnalysis.NetAnalyzers
این افزونه جزئی از SDK دات نت 5 است و نیازی به نصب مجزا را ندارد. البته اگر می‌خواهید نگارش‌های جدیدتر آن‌را پیش از یکی شدن با SDKهای بعدی مورد آزمایش قرار دهید، می‌توان آن‌را به صورت صریحی نیز به کامپایلر معرفی کرد. این افزونه‌ی جایگزین FxCop است و پس از ارائه‌ی آن، FxCop را منسوخ شده اعلام کردند.

Meziantou.Analyzer
یکسری نکات بهبود کیفیت کدها که توسط Meziantou در طی سال‌های متمادی جمع آوری شده‌اند، تبدیل به افزونه‌ی فوق شده‌اند.

Microsoft.VisualStudio.Threading.Analyzers
این افزونه نکاتی را در مورد مشکلات Threading موجود در کدها، گوشزد می‌کند.

Microsoft.CodeAnalysis.BannedApiAnalyzers
با استفاده از این افزونه می‌توان استفاده‌ی از یکسری کدها را ممنوع کرد. برای مثال استفاده‌ی از System.DateTimeOffset.DateTime، در سراسر کدها ممنوع شده و استفاده‌ی از System.DateTimeOffset.UtcDateTime پیشنهاد شود.

AsyncFixer و Asyncify
این دو افزونه، مشکلات متداول در حین کار با کدهای async را گوشزد می‌کنند.

ClrHeapAllocationAnalyzer
این افزونه مکان‌هایی از کد را مشخص می‌کنند که در آن‌ها تخصیص حافظه صورت گرفته‌است. کاهش این مکان‌ها می‌تواند به بالا رفتن کارآیی برنامه کمک کنند.

SonarAnalyzer.CSharp
مجموعه‌ی معروف Sonar، که تعداد قابل ملاحظه‌ای بررسی کننده‌ی کد را به پروژه‌ی شما اضافه می‌کنند.


روش معرفی سراسری افزونه‌های فوق به تمام پروژه‌های یک Solution

می‌توان تنظیمات زیر را به یک تک پروژه اعمال کرد که برای اینکار نیاز است فایل csproj آن‌را ویرایش نمود و یا می‌توان یک تک فایل ویژه را به نام Directory.Build.props ایجاد کرد و آن‌را به صورت زیر تکمیل نمود. محل قرارگیری این فایل، در ریشه‌ی Solution و در کنار فایل sln می‌باشد.
<Project>
  <PropertyGroup>
    <AnalysisLevel>latest</AnalysisLevel>
    <AnalysisMode>AllEnabledByDefault</AnalysisMode>
    <CodeAnalysisTreatWarningsAsErrors>true</CodeAnalysisTreatWarningsAsErrors>
    <EnableNETAnalyzers>true</EnableNETAnalyzers>
    <EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
    <Nullable>enable</Nullable>
    <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
    <RunAnalyzersDuringBuild>true</RunAnalyzersDuringBuild>
    <RunAnalyzersDuringLiveAnalysis>true</RunAnalyzersDuringLiveAnalysis>
    <!--
      CA2007: Consider calling ConfigureAwait on the awaited task
      MA0004: Use Task.ConfigureAwait(false) as the current SynchronizationContext is not needed
      CA1056: Change the type of property 'Url' from 'string' to 'System.Uri'
      CA1054: Change the type of parameter of the method to allow a Uri to be passed as a 'System.Uri' object
      CA1055: Change the return type of method from 'string' to 'System.Uri'
    -->
    <NoWarn>$(NoWarn);CA2007;CA1056;CA1054;CA1055;MA0004</NoWarn>
    <NoError>$(NoError);CA2007;CA1056;CA1054;CA1055;MA0004</NoError>
    <Deterministic>true</Deterministic>
    <Features>strict</Features>
    <ReportAnalyzer>true</ReportAnalyzer>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.CodeAnalysis.NetAnalyzers" Version="5.0.1">
      <PrivateAssets>all</PrivateAssets>
      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
    </PackageReference>

    <PackageReference Include="Meziantou.Analyzer" Version="1.0.639">
      <PrivateAssets>all</PrivateAssets>
      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
    </PackageReference>

    <PackageReference Include="Microsoft.VisualStudio.Threading.Analyzers" Version="16.8.55">
      <PrivateAssets>all</PrivateAssets>
      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
    </PackageReference>

    <PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="3.3.2">
      <PrivateAssets>all</PrivateAssets>
      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
    </PackageReference>

    <PackageReference Include="AsyncFixer" Version="1.3.0">
      <PrivateAssets>all</PrivateAssets>
      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
    </PackageReference>

    <PackageReference Include="Asyncify" Version="0.9.7">
      <PrivateAssets>all</PrivateAssets>
      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
    </PackageReference>

    <PackageReference Include="ClrHeapAllocationAnalyzer" Version="3.0.0">
      <PrivateAssets>all</PrivateAssets>
      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
    </PackageReference>

    <PackageReference Include="SonarAnalyzer.CSharp" Version="8.16.0.25740">
      <PrivateAssets>all</PrivateAssets>
      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
    </PackageReference>
  </ItemGroup>

  <ItemGroup>
    <AdditionalFiles Include="$(MSBuildThisFileDirectory)BannedSymbols.txt" Link="Properties/BannedSymbols.txt" />
  </ItemGroup>
</Project>
توضیحات:
- در تنظیمات فوق، مواردی مانند AnalysisLevel، در مطلب «کامپایلر C# 9.0، خطاها و اخطارهای بیشتری را نمایش می‌دهد» پیشتر بررسی شده‌اند.
- در اینجا Nullable به true تنظیم شده‌است. اگر قرار است یک پروژه‌ی جدید را شروع کنید، بهتر است این ویژگی را نیز فعال کنید. بسیاری از API‌های دات نت 5 جهت مشخص سازی خروجی نال و یا غیرنال آن‌ها، بازنویسی و تکمیل شده‌اند و بدون استفاده از این ویژگی، بسیاری از راهنمایی‌های ارزنده‌ی دات نت 5 را از دست خواهید داد. اساسا بدون فعالسازی این ویژگی، از قابلیت‌های #C مدرن استفاده نمی‌کنید.
- وجود این PackageReference ها، به معنای بالا رفتن حجم نهایی قابل ارائه‌ی پروژه نیست؛ چون به صورت PrivateAssets و analyzers تعریف شده‌اند و فقط در حین پروسه‌ی کامپایل، جهت ارائه‌ی راهنمایی‌های بیشتر، تاثیرگذار خواهند بود.
- این تنظیمات طوری چیده شده‌اند که تا حد ممکن «درد آور» باشند! برای اینکار CodeAnalysisTreatWarningsAsErrors و TreatWarningsAsErrors به true تظیم شده‌اند تا حتی اخطارها نیز به صورت خطای کامپایلر گزارش شوند؛ تا مجبور به رفع آن‌ها شویم.
- در اینجا فایل BannedSymbols.txt را نیز مشاهده می‌کنید که مرتبط است به BannedApiAnalyzers. می‌توان در کنار فایل Directory.Build.props، فایل جدید BannedSymbols.txt را با این محتوا ایجاد کرد:
# https://github.com/dotnet/roslyn-analyzers/blob/master/src/Microsoft.CodeAnalysis.BannedApiAnalyzers/BannedApiAnalyzers.Help.md
P:System.DateTime.Now;Use System.DateTime.UtcNow instead
P:System.DateTimeOffset.Now;Use System.DateTimeOffset.UtcNow instead
P:System.DateTimeOffset.DateTime;Use System.DateTimeOffset.UtcDateTime instead
در این حالت برای مثال، از استفاده‌ی از DateTime.Now منع شده و وادار به استفاده‌ی از DateTime.UtcNow می‌شوید.


روش کاهش تعداد خطاهای نمایش داده شده

اگر از فایل Directory.Build.props فوق استفاده کرده و یکبار دستور dotnet restore را جهت بازیابی وابستگی‌های آن اجرا کنید، با تعداد خطاهایی که در IDE خود مشاهده خواهید کرد، شگفت‌زده خواهید شد! به همین جهت برای کنترل آن‌ها می‌توان فایل جدید editorconfig. را به نحو زیر در کنار فایل Directory.Build.props ایجاد و تکمیل کرد:
[*.cs]

# MA0026 : Complete the task
dotnet_diagnostic.MA0026.severity = suggestion

# CA1308: In method 'urlToLower', replace the call to 'ToLowerInvariant' with 'ToUpperInvariant' (CA1308)
dotnet_diagnostic.CA1308.severity = suggestion

# CA1040: Avoid empty interfaces
dotnet_diagnostic.CA1040.severity = suggestion

# CA1829 Use the "Count" property instead of Enumerable.Count()
dotnet_diagnostic.CA1829.severity = suggestion

# Use 'Count' property here instead.
dotnet_diagnostic.S2971.severity = suggestion

# S1135 : Complete the task
dotnet_diagnostic.S1135.severity = suggestion

# S2479: Replace the control character at position 7 by its escape sequence
dotnet_diagnostic.S2479.severity = suggestion

# CA2007: Consider calling ConfigureAwait on the awaited task
dotnet_diagnostic.CA2007.severity = none

# MA0004: Use Task.ConfigureAwait(false) as the current SynchronizationContext is not needed
dotnet_diagnostic.MA0004.severity = none

# CA1056: Change the type of property 'Url' from 'string' to 'System.Uri'
dotnet_diagnostic.CA1056.severity = suggestion

# CA1054: Change the type of parameter of the method to allow a Uri to be passed as a 'System.Uri' object
dotnet_diagnostic.CA1054.severity = suggestion

# CA1055: Change the return type of method from 'string' to 'System.Uri'
dotnet_diagnostic.CA1055.severity = suggestion

# S4457: Split this method into two, one handling parameters check and the other handling the asynchronous code.
dotnet_diagnostic.S4457.severity = none

# AsyncFixer01: Unnecessary async/await usage
dotnet_diagnostic.AsyncFixer01.severity = suggestion

# AsyncFixer02: Long-running or blocking operations inside an async method
dotnet_diagnostic.AsyncFixer02.severity = error

# VSTHRD103: Call async methods when in an async method
dotnet_diagnostic.VSTHRD103.severity = error

# AsyncFixer03: Fire & forget async void methods
dotnet_diagnostic.AsyncFixer03.severity = error

# VSTHRD100: Avoid async void methods
dotnet_diagnostic.VSTHRD100.severity = error

# VSTHRD101: Avoid unsupported async delegates
dotnet_diagnostic.VSTHRD101.severity = error

# VSTHRD107: Await Task within using expression
dotnet_diagnostic.VSTHRD107.severity = error

# AsyncFixer04: Fire & forget async call inside a using block
dotnet_diagnostic.AsyncFixer04.severity = error

# VSTHRD110: Observe result of async calls
dotnet_diagnostic.VSTHRD110.severity = error

# VSTHRD002: Avoid problematic synchronous waits
dotnet_diagnostic.VSTHRD002.severity = suggestion

# MA0045: Do not use blocking call (make method async)
dotnet_diagnostic.MA0045.severity = suggestion

# AsyncifyInvocation: Use Task Async
dotnet_diagnostic.AsyncifyInvocation.severity = error

# AsyncifyVariable: Use Task Async
dotnet_diagnostic.AsyncifyVariable.severity = error

# VSTHRD111: Use ConfigureAwait(bool)
dotnet_diagnostic.VSTHRD111.severity = none

# MA0022: Return Task.FromResult instead of returning null
dotnet_diagnostic.MA0022.severity = error

# VSTHRD114: Avoid returning a null Task
dotnet_diagnostic.VSTHRD114.severity = error

# VSTHRD200: Use "Async" suffix for async methods
dotnet_diagnostic.VSTHRD200.severity = suggestion

# MA0040: Specify a cancellation token
dotnet_diagnostic.MA0032.severity = suggestion

# MA0040: Flow the cancellation token when available
dotnet_diagnostic.MA0040.severity = suggestion

# MA0079: Use a cancellation token using .WithCancellation()
dotnet_diagnostic.MA0079.severity = suggestion

# MA0080: Use a cancellation token using .WithCancellation()
dotnet_diagnostic.MA0080.severity = error

#AsyncFixer05: Downcasting from a nested task to an outer task.
dotnet_diagnostic.AsyncFixer05.severity = error

# ClrHeapAllocationAnalyzer ----------------------------------------------------
# HAA0301: Closure Allocation Source
dotnet_diagnostic.HAA0301.severity = suggestion

# HAA0601: Value type to reference type conversion causing boxing allocation
dotnet_diagnostic.HAA0601.severity = suggestion

# HAA0302: Display class allocation to capture closure
dotnet_diagnostic.HAA0302.severity = suggestion

# HAA0101: Array allocation for params parameter
dotnet_diagnostic.HAA0101.severity = suggestion

# HAA0603: Delegate allocation from a method group
dotnet_diagnostic.HAA0603.severity = suggestion

# HAA0602: Delegate on struct instance caused a boxing allocation
dotnet_diagnostic.HAA0602.severity = suggestion

# HAA0401: Possible allocation of reference type enumerator
dotnet_diagnostic.HAA0401.severity = silent

# HAA0303: Lambda or anonymous method in a generic method allocates a delegate instance
dotnet_diagnostic.HAA0303.severity = silent

# HAA0102: Non-overridden virtual method call on value type
dotnet_diagnostic.HAA0102.severity = silent

# HAA0502: Explicit new reference type allocation
dotnet_diagnostic.HAA0502.severity = none

# HAA0505: Initializer reference type allocation
dotnet_diagnostic.HAA0505.severity = silent
روش کار هم به صورت است که برای مثال در IDE خود (حتی با VSCode هم کار می‌کند)، خطای کامپایلر مثلا CA1308 را مشاهده می‌کنید که عنوان کرده‌است بجای ToLowerInvariant از ToUpperInvariant استفاده کنید. اگر با این پیشنهاد موافق نیستید (عین خطا را به صورت C# CA1308 در گوگل جستجو کنید؛ توضیحات مایکروسافت را در مورد آن خواهید یافت)، یک سطر شروع شده‌ی با dotnet_diagnostic و سپس ID خطا را به صورت زیر، به فایل editorconfig. یاد شده، اضافه کنید:
dotnet_diagnostic.CA1308.severity = suggestion
به این ترتیب هنوز هم این مورد را به صورت یک پیشنهاد مشاهده خواهید کرد، اما دیگر جزو خطاهای کامپایلر گزارش نمی‌شود. اگر خواستید که به طور کامل ندید گرفته شود، مقدار آن‌را بجای suggestion به none تغییر دهید.

یک نکته: در ویندوز نمی‌توانید یک فایل تنها پسوند دار را به صورت معمولی در windows explorer ایجاد کنید. نام این فایل را به صورت .editorconfig. با دو نقطه‌ی ابتدایی و انتهایی وارد کنید. خود ویندوز نقطه‌ی پایانی را حذف می‌کند.


روش صرفنظر کردن از یک خطا، تنها در یک قسمت از کد

فرض کنید نمی‌خواهید خطای CA1052 را تبدیل به یک suggestion سراسری کنید و فقط می‌خواهید که در قطعه‌ی خاصی از کدهای خود، آن‌را خاموش کنید. به همین جهت بجای اضافه کردن آن به فایل editorconfig.، باید از ویژگی SuppressMessage به صورت زیر استفاده نمائید:
[SuppressMessage("Microsoft.Usage", "CA1052:Type 'Program' is a static holder type but is neither static nor NotInheritable",
  Justification = "We need it for our integration tests this way.")]
[SuppressMessage("Microsoft.Usage", "RCS1102:Type 'Program' is a static holder type but is neither static nor NotInheritable",
  Justification = "We need it for our integration tests this way.")]
[SuppressMessage("Microsoft.Usage", "S1118:Type 'Program' is a static holder type but is neither static nor NotInheritable",
  Justification = "We need it for our integration tests this way.")]
public class Program { }
در اینجا پارامتر اول با Microsoft.Usage مقدار دهی می‌شود. پارامتر دوم آن باید حاوی ID خطا باشد. در صورت تمایل می‌توانید دلیل خاموش کردن این خطا را در قسمت Justification وارد کنید.
مطالب
RequireJs
در طراحی و توسعه پروژه‌های تحت وب در مقیاس بزرگ برای اینکه مدیریت پروژه راحت‌تر شود کد‌های مورد نظر را در چند ماژول قرار می‌دهند در نتیجه کد‌های پروژه در بلاک‌های کوچک‌تر قرار خواهند داشت. نوشتن پروژه به صورت ماژولار قابلیت استفاده مجدد از کد‌های برنامه را افزایش می‌دهد، علاوه بر آن مدیریت پروژه در فاز نگهداری آسان‌تر خواهد شد؛ از طرفی دیگر وابستگی بین ماژول‌ها و تامین آن ها، همواره مهم‌ترین مفهوم برای توسعه دهندگان پروژه‌های وب است. RequireJs یکی از فریم ورک‌های محبوب برای مدیریت وابستگی‌های بین ماژول‌ها است و کاربرد اصلی آن راحت سازی مفهوم modularity در اینگونه پروژه هاست.
پروژه‌های بزرگ عموما دارای یک یا چند فایل جاوااسکریپ هستند که برای استفاده از آن‌ها در صفحات از تگ script استفاده می‌شود. اگر این فایل‌ها دارای وابستگی به هم باشند، ترتیب فراخوانی این فایل در تگ script مهم است. برای مثال:
یک پروژه دارای فایل‌های زیر خواهد بود:

purchase.js
function purchaseProduct(){
  console.log("Function : purchaseProduct");
  var credits = getCredits();
  if(credits > 0){
    reserveProduct();
    return true;
  }
  return false;
}
product.js
function reserveProduct(){
  console.log("Function : reserveProduct"); 
  return true;
}
credits.js
function getCredits(){
  console.log("Function : getCredits"); 
  var credits = "100";
  return credits;
}
همان طور که در فایل‌های بالا مشاهده می‌کنید در فایل purchase.js از دو فایل دیگر استفاده شده است. در فایل main.js پروژه کد زیر را برای استفاده از فایل‌های بالا می‌نویسیم:
main.js
var result = purchaseProduct();
فرض کنید در فایل Html تگ‌های script ما به صورت زیر باشد:
<script src="products.js"></script>
<script src="purchase.js"></script>
<script src="main.js"></script>
<script src="credits.js"></script>
از آنجا که لود فایل purchase.js وابستگی مستقیم به لود فایل credits.js دارد و از طرفی دیگر به دلیل این که فایل credit.Js بعد از تمام فایل‌ها لود می‌شود در نتیجه برنامه اجرا نخواهد شد و با خطای زیر روبرو می‌شویم:

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

 RequireJs چگونه برای حل این مشکل به ما کمک می‌کند؟
با استفاده از فریم ورک RequireJs کد‌های ما به ماژول‌های کوچک‌تر شکسته می‌شود و وابستگی ماژول‌ها در تنظیمات لود فایل ثبت می‌شود. در ضمن این فریم ورک با مرورگرها جدید و محبوب کاملا سازگار است. برای شروع فایل‌های مورد نیاز را از اینجا دانلود نمایید. البته می‌توانید از nuget هم استفاده کنید:
PM> Install-Package RequireJS
در مثال بالا فایل‌های جاوااسکریپ به صورت زیر تغییر خواهد کرد:
purchase.js
define(["credits","products"], function(credits,products) { 
  console.log("Function : purchaseProduct"); 
  return {
    purchaseProduct: function() { 
      var credit = credits.getCredits();
      if(credit > 0){
        products.reserveProduct();
        return true;
      }
      return false;
    }
  }
});
همان طور که مشاهده می‌کنید در فایل بالا از دستور define برای تعریف ماژول استفاده شده است و نام دو ماژول products و credits را به صورت پارامتر برای مشخص کردن وابستگی ماژول‌ها تعریف کردیم.
products.js
define(function(products) {
  return {
    reserveProduct: function() {
      console.log("Function : reserveProduct"); 
      return true;
    }
  }
});
credits.js
define(function() {
  console.log("Function : getCredits"); 
  return {
    getCredits: function() {
      var credits = "100";
      return credits;
    }
  }
});
به دلیل این که فایل‌های بالا وابستگی به ماژول‌های دیگر ندارند در نتیجه دستور define فقط شامل تعریف تابع است.
کافیست در فایل main.js، برای استفاده از فایل purchase.js از دستور require استفاده کنید.
require(['purchase'], function( purchase ) {
    var result = purchase.purchaseProduct(); 
});
مهم‌ترین مزیتی که این روش دارد این است که دیگر نیازی به نوشتن تگ‌های Script (آن هم به ترتیب درست) برای فراخوانی فایل‌های جاوااسکریپتی نخواهد بود؛ از طرفی دیگر وابستگی بین این فایل‌ها در هنگام تعریف ماژول مشخص خواهد شد. از آن به بعد وظیفه تامین فایل‌های مورد نیاز برای لود ماژول بر عهده RequireJs است.

تفاوت بین دستور require و define
دستور define صرفا برای تعیین وابستگی ماژول استفاده می‌شود در حالی که دستور require برای فراخوانی و لود ماژول کاربرد دارد و به نوعی به عنوان نقطه شروع اجرای برنامه خواهد بود.
در پست بعدی به پیاده سازی مثال بالا به کمک RequireJs در قالب یک پروژه Asp.Net MVC خواهم پرداخت.
مطالب
راه اندازی دیتابیس postgresql در برنامه‌های ASP.NET Core – قسمت 2

در قسمت قبل به معرفی postgresql پرداختیم; در این قسمت قصد ایجاد و راه اندازی یک api با استفاده از دیتابیس postgresql و استفاده از تکنولوژی‌های آن را با استفاده از docker داریم.


ابتدا با استفاده از دستور زیر یک پروژه‌ی جدید asp.net core را ایجاد کنید:

dotnet new webapi --minimal -o YourDirectoryPath:\YourFolderName

سپس فایل docker-compose.yaml را به روت پروژه اضافه کنید که شامل کانفیگ‌های زیر میباشد: 

version: '3.1'

services:

  db:
    image: postgres
    container_name: db
    restart: always
    environment:
      POSTGRES_PASSWORD: postgres
      POSTGRES_USERNAME: postgres
      POSTGRES_DB: BloggingDb
    ports:
        - "5432:5432"
    volumes:
      - postgres_data:/data/db

  adminer:
    image: adminer
    restart: always
    ports:
      - 8080:8080
  
  pgadmin4:
    image: dpage/pgadmin4
    restart: always
    environment:
      PGADMIN_DEFAULT_EMAIL: pgadmin4@pgadmin.org
      PGADMIN_DEFAULT_PASSWORD: admin
      PGADMIN_CONFIG_SERVER_MODE: 'False'
    ports:
      - 5050:80
    volumes:
      - pgadmin:/var/lib/pgadmin
    depends_on:
      - db

volumes:
  postgres_data:
  pgadmin:

سپس با اجرای دستور زیر در روت پروژه، سرویس‌ها را راه اندازی کنید: 

docker compose up -d


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

سرویس db

نمونه ایمیج اصلی، volume، تنظیمات connection string در آن استفاده شده است.

سرویس adminer :

https://hub.docker.com/_/adminer /

Adminer - Database management in a single PHP file

یک برنامه تحت وب مدیریت پایگاه داده ساده میباشد که ویژگی‌ها MySql را در کنار سرعت و امنیت ارائه میدهد و در آدرس http://localhost:8080 / اجرا خواهد شد.

سرویس pgadmin4 :

pgAdmin - PostgreSQL Tools

dpage/pgadmin4 - Docker Image | Docker Hub

در حال حاضر این برنامه محبوب‌ترین برنامه مدیریت پایگاه داده میباشد که ویژگی‌های پیشرفته‌ای را نیز پوشش میدهد و در آدرس http://localhost:5050 / اجرا خواهد شد. 


اکنون نوبت نوشتن کد‌ها میباشد. 

- تنظیم connection string در فایل appsettings.json:

"ConnectionStrings": {
    "BloggingContext": "Username=postgres;Password=postgres;Server=localhost;Database=BloggingDb”
}

- و همینطور پکیج‌های زیر را به برنامه خود رفرنس دهید: 

dotnet add package Npgsql.EntityFrameworkCore.PostgreSQL
dotnet add package Microsoft.EntityFrameworkCore
dotnet add package Microsoft.EntityFrameworkCore.Design

- مدل‌های برنامه را در مسیر /Models ایجاد کنید: 

namespace NpgsqlAPI.Models;

public class Post
{
    public int PostId { get; set; }
    public string Title { get; set; } = null!;
    public string Content { get; set; } = null!;

    public int BlogId { get; set; }
    public Blog Blog { get; set; } = null!;
}

namespace NpgsqlAPI.Models;

public class Blog
{
    public int BlogId { get; set; }
    public string? Url { get; set; }

    public List<Post>? Posts { get; set; }
}

- سپس BloggingContext را در مسیر /Data ایجاد کنید:

using Microsoft.EntityFrameworkCore;
using NpgsqlAPI.Models;

namespace NpgsqlAPI.Data;

public class BloggingContext : DbContext
{
    public BloggingContext(DbContextOptions<BloggingContext> options)
        : base(options)
    {
    }
    public DbSet<Blog> Blogs => Set<Blog>();
    public DbSet<Post


- سپس اینترفیس IBlogServices را در مسیر  /Servicec/Blogs ایجاد کنید: 

using NpgsqlAPI.Models;

namespace NpgsqlAPI.Services.Blogs;
public interface IBlogServices
{
    Task<IEnumerable<Blog>> GetList();
    Task<Blog?> Get(uint id);
    Task<uint> Add(Blog obj);
    Task AddRange(Blog[] obj);
    Task Update(Blog obj);
    Task UpdateRange(Blog[] obj);
    Task Remove(uint id);
}


-  و سپس پیاده سازی آن را در فایل BlogEFServices و در کنار اینترفیس آن قرار دهید: 

using Microsoft.EntityFrameworkCore;
using NpgsqlAPI.Data;
using NpgsqlAPI.Models;

namespace NpgsqlAPI.Services.Blogs;
public sealed class BlogEFServices : IBlogServices
{
    private readonly BloggingContext _context;
    public BlogEFServices(BloggingContext context)
    {
        _context = context;
    }

    public async Task<uint> Add(Blog obj)
    {
        await _context.Blogs.AddAsync(obj);
        return (uint)await SaveChangesAsync();
    }

    public async Task AddRange(Blog[] obj)
    {
        await _context.Blogs.AddRangeAsync(obj);
        await SaveChangesAsync();
    }

    public async Task<Blog?> Get(uint id)
    {
        return await _context.Blogs.FirstOrDefaultAsync(x=>x.BlogId == id);
    }

    public async Task<IEnumerable<Blog>> GetList()
    {
       return await _context.Blogs.ToListAsync();
    }

    public async Task Remove(uint id)
    {
        var entity = await Get(id);
        _context.Blogs.Remove(entity!);
        await SaveChangesAsync();
    }

    public async Task Update(Blog obj)
    {
        _context.Blogs.Update(obj);
        await SaveChangesAsync();
    }

    public async Task UpdateRange(Blog[] obj)
    {
        _context.Blogs.UpdateRange(obj);
        await SaveChangesAsync();
    }

    private async Task<int> SaveChangesAsync()
    {
        return await _context.SaveChangesAsync();
    }
}


- اکنون  endpoint‌های api را در فایل program.cs ایجاد کنید:

using System.Data;
using Microsoft.EntityFrameworkCore;
using Npgsql;
using NpgsqlAPI.Services.Blogs;
using NpgsqlAPI.Data;
using NpgsqlAPI.Models;

var builder = WebApplication.CreateBuilder(args);

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

string connectionString = builder.Configuration.GetConnectionString("BloggingContext")!;

builder.Services.AddDbContext<BloggingContext>(options =>
        options.UseNpgsql(connectionString));

builder.Services.AddTransient<IDbConnection>(_ => new NpgsqlConnection(connectionString));

// builder.Services.AddScoped<IBlogServices, BlogDapperServices>();
// builder.Services.AddScoped<IBlogServices, BlogEFRawQueryServices>();
builder.Services.AddScoped<IBlogServices, BlogEFServices>();

var app = builder.Build();

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

app.UseHttpsRedirection();

app.MapGet("/blogs", async (IBlogServices service) => await service.GetList())
.WithName("GetBlogs")
.WithOpenApi();

app.MapGet("/blogs/{id}", async (IBlogServices service, uint id) => await service.Get(id))
.WithName("GetBlog")
.WithOpenApi();

app.MapPost("/blogs", async (IBlogServices service, Blog blog) => await service.Add(blog))
.WithName("AddBlog")
.WithOpenApi();

app.MapDelete("/blogs/{id}", async (IBlogServices service, uint id) => await service.Remove(id))
.WithName("RemoveBlog")
.WithOpenApi();

app.MapPut("/blogs", async (IBlogServices service, Blog blog) => await service.Update(blog))
.WithName("UpdateBlog")
.WithOpenApi();

app.MapPut("/blogs/Bulk", async (IBlogServices service, Blog[] blogs) =>
 await service.UpdateRange(blogs))
.WithName("UpdateBulkBlog")
.WithOpenApi();

app.MapPost("/blogs/Bulk", async (IBlogServices service, Blog[] blogs) =>
 await service.AddRange(blogs))
.WithName("AddBulkBlog")
.WithOpenApi();

app.Run();

تمامی کد‌های برنامه تا به اینجا نوشته شده‌اند. اکنون migration را پس از اطمینان از اجرا بودن داکر اجرا کنید 

dotnet ef migrations add Init
dotnet ef database update

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


کد‌های کامل این مطلب

نظرات مطالب
فعال سازی و پردازش صفحات پویای افزودن، ویرایش و حذف رکوردهای jqGrid در ASP.NET MVC
Failed to load resource: the server responded with a status of 500 (Internal Server Error)
جالب اینجاست ویرایش خطی کار میکنه و وارد متد مربوطه میشه اما حذف خطی وارد متد مربوطه نمیشه.
من نوعش از هم از حالت Get به Post تغییر دادم بازم خطا میداد.
 [HttpPost]
        public ActionResult EditProductData(string oper, string title,string groupTitle,string shopTitle,int AvailableCount,int Priority, int? id)
        {
            if (oper == "add")
            {
                var product = new Product()
                {
                    Title = title,
                    
                };
                db.Products.Add(product);
                db.SaveChanges();
                return Content("true");
            }
            else if (oper == "del")
            {
                var product = db.Products.Find(id);
                db.Products.Remove(product);
                return Content("true");
            }
            else if (oper == "edit")
            {
                var product = db.Products.Find(id);
                product.Title = title;
                product.GroupId = int.Parse(groupTitle);
                product.ShopId = int.Parse(shopTitle);
                product.AvailableCount = AvailableCount;
                product.Priority = Priority;
                db.SaveChanges();
                return Content("true");
            }
            return Content("false");
        }

مطالب
معرفی System.Text.Json در NET Core 3.0.
معروفترین کتابخانه‌ی کار با JSON در دات نت، Json.NET است که این روزها، جزء جدایی ناپذیر حداقل، تمام برنامه‌های وب مبتنی بر دات نت می‌باشد. برای مثال ASP.NET Core 2x/1x و همچنین ASP.NET Web API پیش از NET Core.، به صورت پیش‌فرض از این کتابخانه برای کار با JSON استفاده می‌کنند. این کتابخانه 10 سال پیش ایجاد شد و در طول زمان، قابلیت‌های زیادی به آن اضافه شده‌است. همین حجم بالای کار صورت گرفته سبب شده‌است که برای مثال شروع به استفاده‌ی از <Span<T در آن برای بالابردن کارآیی، بسیار مشکل شده و نیاز به تغییرات اساسی در آن داشته باشد. به همین جهت خود تیم CoreFX دات نت Core گزینه‌ی دیگری را برای کار با JSON در فضای نام جدید System.Text.Json ارائه داده‌است که برای کار با آن نیاز به نصب وابستگی ثالثی نیست و همچنین کارآیی آن به علت استفاده‌ی از ویژگی‌های جدید زبان، مانند ref struct و Span، به طور میانگین دو برابر کتابخانه‌ی Json.NET است. برای مثال استفاده‌ی از string (حالت پیش‌فرض کتابخانه‌ی Json.NET) یعنی کار با رشته‌هایی از نوع UTF-16؛ اما کار با Span، امکان دسترسی مستقیم به رشته‌هایی از نوع UTF-8 را میسر می‌کند که نیازی به تبدیل به رشته‌هایی از نوع UTF-16 را ندارند.


ASP.NET Core 3x دیگر به صورت پیش‌فرض به همراه Json.NET ارائه نمی‌شود

در برنامه‌های ASP.NET Core 3x، وابستگی ثالث Json.NET حذف شده‌است و از این پس هر نوع خروجی JSON آن، مانند بازگشت مقادیر مختلف از اکشن متدهای کنترلرها، به صورت خودکار در پشت صحنه از امکانات ارائه شده‌ی در System.Text.Json استفاده می‌کند و دیگر Json.NET، کتابخانه‌ی پیش‌فرض کار با JSON آن نیست. بنابراین برای کار با آن نیاز به تنظیم خاصی نیست. همینقدر که یک پروژه‌ی جدید ASP.NET Core 3x را ایجاد کنید، یعنی در حال استفاده‌ی از System.Text.Json هستید.


روش بازگشت به Json.NET در ASP.NET Core 3x

اگر به هر دلیلی هنوز نیاز به استفاده‌ی از کتابخانه‌ی Json.NET را دارید، آداپتور ویژه‌ی آن نیز تدارک دیده شده‌است. برای اینکار:
الف) ابتدا باید بسته‌ی نیوگت Microsoft.AspNetCore.Mvc.NewtonsoftJson را نصب کنید.
ب) سپس در کلاس Startup، باید این کتابخانه را به صورت یک سرویس جدید، با فراخوانی متد AddNewtonsoftJson، معرفی کرد:
 public void ConfigureServices(IServiceCollection services)
 {
     services.AddControllers()
            .AddNewtonsoftJson()
     // ...
}
یکی از دلایل بازگشت به Json.NET می‌تواند عدم پشتیبانی از OpenAPI / Swagger در حین کار با System.Text.Json باشد و این مورد قرار نیست در نگارش نهایی 3.0، حضور داشته باشد و انطباق با آن به نگارش‌های بعدی موکول شده‌است.


روش کار مستقیم با System.Text.Json

اگر در قسمتی از برنامه‌ی خود نیاز به کار مستقیم با اشیاء JSON را داشته باشید و یا حتی بخواهید از این قابلیت در برنامه‌های کنسول و یا کتابخانه‌ها نیز استفاده کنید، روش انتقال کدهایی که از Json.NET استفاده می‌کنند به System.Text.Json، به صورت زیر است:
public class Person
{
   public string FirstName { get; set; }
   public string LastName { get; set; }
   public DateTime? BirthDay { get; set; }
}
تبدیل رشته‌ی JSON حاوی اطلاعات شخص، به شیء متناظر با آن و یا حالت عکس آن:
using System;
using System.Text.Json.Serialization;

namespace ConsoleApp
{
    class Program
    {
        static void Main(string[] args)
        {
            Person person = JsonSerializer.Parse<Person>(...);
            string json = JsonSerializer.ToString(person);
        }
    }
}
در اینجا از کلاس System.Text.Json.Serialization.JsonSerializer، روش کار با دو متد Parse را برای Deserialization و ToString را برای Serialization مشاهده می‌کنید.
کلاس JsonSerializer دارای overloadهای زیر برای کار با متدهای Parse و ToString است:
namespace System.Text.Json.Serialization
{
    public static class JsonSerializer
    {
        public static object Parse(ReadOnlySpan<byte> utf8Json, Type returnType, JsonSerializerOptions options = null);
        public static object Parse(string json, Type returnType, JsonSerializerOptions options = null);
        public static TValue Parse<TValue>(ReadOnlySpan<byte> utf8Json, JsonSerializerOptions options = null);
        public static TValue Parse<TValue>(string json, JsonSerializerOptions options = null);

        public static string ToString(object value, Type type, JsonSerializerOptions options = null);
        public static string ToString<TValue>(TValue value, JsonSerializerOptions options = null);
    }
}
یک نکته: کارآیی متد Parse با امضای ReadOnlySpan<byte> utf8Json، بیشتر است از نمونه‌ای که string json را می‌پذیرد. از این جهت که چون با آرایه‌ای از بایت‌های رشته‌ای از نوع UTF-8 کار می‌کند، نیاز به تبدیل به UTF-16 را مانند متدی که string را می‌پذیرد، ندارد. برای تولید آرایه‌ی بایت‌های utf8Json از روی یک شیء، می‌توانید از متد JsonSerializer.ToUtf8Bytes استفاده کنید و یا برای تولید آن از روی یک رشته، از متد Encoding.UTF8.GetBytes استفاده کنید.


سفارشی سازی JsonSerializer جدید

اگر به امضای متدهای Parse و ToString کلاس JsonSerializer دقت کنید، دارای یک پارامتر اختیاری از نوع JsonSerializerOptions نیز هستند که به صورت زیر تعریف شده‌است:
public sealed class JsonSerializerOptions
{
   public bool AllowTrailingCommas { get; set; }
   public int DefaultBufferSize { get; set; }
   public JsonNamingPolicy DictionaryKeyPolicy { get; set; }
   public bool IgnoreNullValues { get; set; }
   public bool IgnoreReadOnlyProperties { get; set; }
   public int MaxDepth { get; set; }
   public bool PropertyNameCaseInsensitive { get; set; }
   public JsonNamingPolicy PropertyNamingPolicy { get; set; }
   public JsonCommentHandling ReadCommentHandling { get; set; }
   public bool WriteIndented { get; set; }
}
برای نمونه معادل تنظیم NullValueHandling در Json.NET:
// Json.NET:
var settings = new JsonSerializerSettings
{
    NullValueHandling = NullValueHandling.Ignore
};
string json = JsonConvert.SerializeObject(person, settings);
اینبار توسط خاصیت IgnoreNullValues صورت می‌گیرد:
// JsonSerializer:
var options = new JsonSerializerOptions
{
    IgnoreNullValues = true
};
string json = JsonSerializer.ToString(person, options);

در برنامه‌های ASP.NET Core که این نوع متدها در پشت صحنه فراخوانی می‌شوند، روش تنظیم JsonSerializerOptions به صورت زیر است:
services.AddControllers()
   .AddJsonOptions(options => options.JsonSerializerOptions.WriteIndented = true);


نگاشت نام ویژه‌ی خواص در حین عملیات deserialization

در مثال فوق، فرض شده‌است که نام خاصیت BirthDay، دقیقا با اطلاعاتی که از رشته‌ی JSON دریافتی پردازش می‌شود، تطابق دارد. اگر این نام در اطلاعات دریافتی متفاوت است، می‌توان از ویژگی JsonPropertyName برای تعریف این نگاشت استفاده کرد:
[JsonPropertyName("birthdate")]
public DateTime? BirthDay { get; set; }
روش دیگر اینکار، برای نمونه تنظیم PropertyNamingPolicy به حالت CamelCase است:
var options = new JsonSerializerOptions
{
   PropertyNamingPolicy = JsonNamingPolicy.CamelCase
};
string json = JsonSerializer.ToString(person, options);
و یا اگر می‌خواهید حساسیت به بزرگی و کوچکی حروف را ندید بگیرید (مانند حالت پیش‌فرض JSON.NET) از تنظیم JsonSerializerOptions.PropertyNameCaseInsensitive استفاده کنید.

در این بین اگر نمی‌خواهید خاصیتی در عملیات serialization و یا برعکس آن پردازش شود، می‌توان از تعریف ویژگی [JsonIgnore] بر روی آن استفاده کرد.
مطالب
آشنایی با Refactoring - قسمت 10

یکی دیگر از روش‌هایی که جهت بهبود کیفیت کدها مورد استفاده قرار می‌گیرد، «طراحی با قراردادها» است؛ به این معنا که «بهتر است» متدهای تعریف شده پیش از استفاده از آرگومان‌های خود، آن‌ها را دقیقا بررسی کنند و به این نوع پیش شرط‌ها، قرارداد هم گفته می‌شود.
نمونه‌ای از آن‌را در قسمت 9 مشاهده کردید که در آن اگر آرگومان‌های متد AddRole، خالی یا نال باشند، یک استثناء صادر می‌شود. این نوع پیغام‌های واضح و دقیق در مورد عدم اعتبار ورودی‌های دریافتی، بهتر است از پیغام‌های کلی و نامفهوم null reference exception که بدون بررسی stack trace و سایر ملاحظات، علت بروز آن‌ها مشخص نمی‌شوند.
در دات نت 4، جهت سهولت این نوع بررسی‌ها، مفهوم Code Contracts ارائه شده است. (این نام هم از این جهت بکارگرفته شده که Design by Contract نام تجاری شرکت ثبت شده‌ای در آمریکا است!)


یک مثال:
متد زیر را در نظر بگیرید. اگر divisor مساوی صفر باشد، استثنای کلی DivideByZeroException صادر می‌شود:

namespace Refactoring.Day10.DesignByContract.Before
{
public class MathMehods
{
public double Divide(int dividend, int divisor)
{
return dividend / divisor;
}
}
}

روش متداول «طراحی با قراردادها» جهت بهبود کیفیت کد فوق پیش از دات نت 4 به صورت زیر است:

using System;

namespace Refactoring.Day10.DesignByContract.After
{
public class MathMehods
{
public double Divide(int dividend, int divisor)
{
if (divisor == 0)
throw new ArgumentException("divisor cannot be zero", "divisor");

return dividend / divisor;
}
}
}

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

در دات نت 4 ، به کمک امکانات مهیای در فضای نام System.Diagnostics.Contracts، این نوع بررسی‌ها نام و امکانات درخور خود را یافته‌اند:

using System.Diagnostics.Contracts;

namespace Refactoring.Day10.DesignByContract.After
{
public class MathMehods
{
public double Divide(int dividend, int divisor)
{
Contract.Requires(divisor != 0, "divisor cannot be zero");

return dividend / divisor;
}
}
}

البته اگر قطعه کد فوق را به همراه divisor=0 اجرا کنید، هیچ پیغام خاصی را مشاهده نخواهید کرد؛ از این لحاظ که نیاز است تا فایل‌های مرتبط با آن‌را از این آدرس دریافت و نصب کنید. این کتابخانه با VS2008 و VS2010 سازگار است. پس از آن، برگه‌ی Code contracts به عنوان یکی از برگه‌های خواص پروژه در دسترس خواهد بود و به کمک آن می‌توان مشخص کرد که برنامه حین رسیدن به این نوع بررسی‌ها چه عکس العملی را باید بروز دهد.

برای مطالعه بیشتر:
مطالب
مدیریت خطا ها در ASP.NET MVC
احتمال بوجود اومدن خطا در اجرای یک برنامه همیشه هست. خطاهایی که برنامه نویس ممکنه هیچ وقت فکر نکنه چنین خطایی در اجرای برنامه ای که نوشته بوجود بیاد و هیچ وقت هم از اون خطا اطلاع پیدا نکنه. ثبت و بررسی خطاهایی که در برنامه بوجود میاد میتونه مفید باشه اما آیا باید همه‌ی خطا‌ها رو ثبت کرد؟
خیر. ممکنه برخی از این خطا‌ها سلامت سیستم رو به خطر نندازه و اصلا ارتباطی هم به برنامه نداشته باشه . به طور مثال زمانی که کاربر یک صفحه ای و یا فایلی رو درخواست میده که وجود نداره.
توسط رویداد OnException میتونیم به خطاهای بوجود اومده در سطح یک Controller دسترسی داشته باشیم و اون رو مدیریت کنیم.
protected override void OnException(ExceptionContext filterContext)
{
    // Bail if we can't do anything
    if (filterContext == null)
        return;

    // log
    var ex = filterContext.Exception ??
            new Exception("No further information exists.");
    // save log ex.Message
    
    filterContext.ExceptionHandled = true;

    filterContext.Result = View("ViewName");
    base.OnException(filterContext);
}

ما تمام خطا هایی که در سطح یک کنترل اتفاق می‌افته رو با دوباره نویسی(override) متد OnException مدیریت میکنیم. اما اگه بخواهیم محدوده‌ی این مدیریت رو بیشتر کنیم و کاری کنیم که روی تمام نرم افزار اعمال بشه باید چه کار کنیم؟
رویداد Application_Error در Global.asax محل مناسبی برای انجام این کار هست اما باید چند مسئله رو در نظر بگیریم:

  • Server.Transfer : این متد یک فایل (میتونه یک صفحه باشه)رو به خروجی میفرسته بدون اینکه آدرس تغییر کنه(کاربر به صفحه دیگه ای منتقل بشه) نکته ای که وجود داره اینه که برای انتقال نیاز به وجود یک فایل فیزیکی بر روی سیستم داره تا اون فایل رو به خروجی منتقل کنه ، در پروژه‌های MVC فایل فیزیکی (به شکلی که در ASP.NET وجود داره) وجود نداره و بوسیله route‌ها ، controllerها و  view ‌ها یک صفحه(خروجی) تولید میشه بنابراین ما نمیتونیم  در ASP.NET MVC از این متد استفاده کنیم و باید به دنبال راه حلی برای فرستادن پاسخ مناسب باشیم.
  • Response.Redirect : این متد کاربر رو به یک صفحه‌ی دیگه منتقل میکنه و StatusCode رو برابر با 301 مقدار دهی میکنه که معنای انتقال صفحه هست و برای مشخص کردن "خطای داخلی سرور" (Internal Server Error) با کد 500 و پیدا نشدن فایل با کد 404 مناسب نیست .این کد(StatusCode) برای موتور‌های جستجو اهمیت زیادی داره لیست کامل این کدها و توضیحاتشون رو میتونید در این آدرس مطالعه کنید.
  • Response.Clear : این متد باید فراخوانی بشه تا خروجی تولید شده تا این لحظه رو پاک کنیم.
  • Server.ClearError :  این متد باید فراخوانی بشه تا از ایجاد صفحه زرد رنگ خطا جلوگیری کنه.
با توجه به نکات بالا، با طی کردن مراحل زیر میتونیم خطا‌های ایجاد شده رو مدیریت کنیم.
  1. بدست آوردن آخرین خطای ایجاد شده.
  2. بدست آوردن کد خطای ایجاد شده.
  3. ثبت خطا برای بررسی (ممکن هست شما برخی خطاها مثل پیدا نشدن فایل رو ثبت نکنید).
  4. پاک کردن خروجی ایجاد شده تا این لحظه.
  5. پاک کردن خطای ایجاد شده در سرور.
  6. فرستادن یک خروجی مناسب بدون تغییر مسیر.
protected void Application_Error(object sender, EventArgs e)
{
    var error = Server.GetLastError();
    var code = (error is HttpException) ? (error as HttpException).GetHttpCode() : 500;

    if (code != 404)
    {
        // save log error.Message
    }

    Response.Clear();
    Server.ClearError();

    string path = Request.Path;
    Context.RewritePath(string.Format("~/Errors/Http{0}", code), false);
    IHttpHandler httpHandler = new MvcHttpHandler();
    httpHandler.ProcessRequest(Context);
    Context.RewritePath(path, false);
}
  خروجی مناسب به کاربر از طریق از یکی از Action هایی انجام میشه که در ErrorsController هست. باید توجه داشته باشید که اگه در خود این ErrorsController خطایی رخ بده ، یک حلقه‌ی بی پایان بین این کنترلر و رویداد Application_Error اتفاق خواهد افتاد.
public class ErrorsController : Controller
{
    [HttpGet]
    public ActionResult Http404()
    {
        Response.StatusCode = 404;
        return View();
    }

    [HttpGet]
    public ActionResult Http500()
    {
        Response.StatusCode = 500;
        return View();
    }
}

نظرات مطالب
C# 12.0 - Primary Constructors
یک نکته‌ی تکمیلی: backing fields تولید شده‌ی در حالت سازنده‌های اولیه، readonly نیستند.

اگر به کدهای #Low-Level C مطلب فوق دقت کنید، به یک چنین تعاریفی می‌رسیم:
private string <FirstName>k__BackingField;
یعنی فیلدهای خصوصی متناظر با پارامترهای سازنده‌ی تعریف شده‌ی توسط کامپایلر در C# 12.0 از نوع readonly نیستند. بنابراین باید دقت داشت که این فیلدها قابل تغییر هستند و برخلاف رویه‌ی متداول تعریف فیلدهای متناظر با پارامترهای تزریق وابستگی‌ها (مانند مثال زیر)، readonly تعریف نشده‌اند:
public class AbcController : Controller
{
   private readonly IMyService _myService;

   public AbcController(IMyService myService) {
     _myService = myService;
   }
}
یعنی در حالت استفاده‌ی از سازنده‌های اولیه‌ی C# 12 می‌توان این فیلدها را تغییر داد که باید به این مورد دقت کافی را داشت. برای نمونه اگر مانند مثال زیر این فیلد را نال کنیم و تغییر دهیم، ممکن است در سایر قسمت‌های استفاده کننده‌ی از آن مشکل درست شود:
public class MyController(IMyService _myService) : ControllerBase
{
   [HttpGet]
   public IActionResult RandomNumber()
   {
     var number = _myService.RandomNumber();

     _myService = null;

     var number2 = MyRandom();
     return Ok(number + number2);            
   }

   public int MyRandom()
   {
     return _myService.RandomNumber();
   }
}
در این مثال ابتدا از myService_ استفاده شده، سپس نال شده (و با خطایی هم مواجه نمی‌شویم چون readonly نیست) و مجددا از آن استفاده شده که به علت نال بودن، دیگر چنین چیزی میسر نیست.