نظرات مطالب
ارتقاء به ASP.NET Core 1.0 - قسمت 2 - بررسی ساختار جدید Solution
این مورد را در قسمت اول ذیل «اما هنوز تعداد زیادی از کتابخانه‌های Full framework به NET Core. انتقال پیدا نکرده‌اند »، توضیح دادم.

شما در ASP.NET Core امکان کار با هر دو فریم ورک یاد شده را دارید و این دو به هم وابستگی ندارند. به عبارتی چندین target را دراینجا می‌توانید معرفی و استفاده کنید. اگر دات نت 4.6 را هم استفاده کردید، برنامه فقط قابلیت چندسکویی خودش را از دست خواهد داد. برای مثال شما هم اکنون می‌توانید EF 6.x را با ASP.NET Core 1.0 استفاده کنید (اگر نمی‌خواهید تا زمان تکمیل نهایی EF Core صبر کنید). فقط در این حالت باید دقت داشته باشید که کدهای شما بر روی لینوکس اجرا نخواهند شد (چون EF 6.x مبتنی بر دات نت 4x است).
مطالب
یکسان سازی "ی" و "ک" دریافتی در حین استفاده از Entity framework

در مورد یکسان سازی ی و ک در حین استفاده از WCF RIA Services پیشتر مطلبی را در این سایت خوانده بودید. جهت تکمیل این بحث، بسط این روش به Entity framework به صورت زیر خواهد بود:

using System.Data;
using System.Data.Objects;
using System.Linq;
using System.Reflection;

namespace EfExt
{
public static class CorrectYeKe
{
public static void ApplyCorrectYeKe(this ObjectContext ctx)
{
if (ctx == null)
return;

//پیدا کردن موجودیت‌های تغییر کرده
var changedEntities = ctx.ObjectStateManager.GetObjectStateEntries(
EntityState.Added | EntityState.Modified
);

foreach (var entity in changedEntities)
{
if (entity.Entity == null) continue;

//یافتن خواص قابل تنظیم و رشته‌ای این موجودیت‌ها
var propertyInfos = entity.Entity.GetType().GetProperties(
BindingFlags.Public | BindingFlags.Instance
).Where(p => p.CanRead && p.CanWrite && p.PropertyType == typeof(string));

var pr = new PropertyReflector();

//اعمال یکپارچگی نهایی
foreach (var propertyInfo in propertyInfos)
{
var propName = propertyInfo.Name;
var val = pr.GetValue(entity.Entity, propName);
if (val != null)
{
pr.SetValue(
entity.Entity,
propName,
val.ToString().ApplyUnifiedYeKe());
}
}
}
}
}
}

ابتدا موجودیت‌های تغییر کرده یافت خواهند شد (اگر از self tracking entities استفاده می‌کنید استفاده از Context.DetectChanges پیش از فراخوانی این متد ضروری خواهد بود)، سپس در این لیست در مورد تک تک اشیاء، خواص رشته‌ای که readonly نیستند یافت شده و ی و ک آن‌ها یک دست می‌شوند.
محل اعمال آن هم باید پیش از فراخوانی Context.SaveChanges باشد.

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

مسیرراه‌ها
ASP.NET MVC
              نظرات مطالب
              مروری بر کدهای کلاس SqlHelper
              این سربار اینقدر نیست که اهمیتی داشته باشد. فقط قرار است یک کوئری LINQ به معادل SQL آن ترجمه شود. خیلی سریع است. همچنین امکان تهیه Compiled linq queries هم وجود دارد (^).
              ضمن اینکه مثلا NHibernate قابلیتی دارد به نام second level cache که اساسا برای پروژه‌های وب طراحی شده. قابلیت کش در سطح کوئری یا اطلاعات پرکاربرد و عمومی سایت را به صورت خودکار دارد. در موردش قبلا مطلب نوشتم : (^). سطح اول کش آن هم پیاده سازی حرفه‌ای همین باز نگه داشتن کانکشنی است که در کد SqlHelper بالا نویسنده موفق به پیاده سازی آن نشده، به علاوه کاهش رفت و آمدها به سرور: (^)
              به علاوه NHibernate یک قابلیت دیگر هم دارد به نام ToFuture که می‌تونه چندین کوئری رو در طی یک رفت و برگشت برای شما انجام بده (^).
              و ... خیلی از best practices دیگر هم در آن لحاظ شده. خلاصه اینکه توانایی‌های بسیار ارزنده‌ای رو با عدم استفاده از ORMs از دست خواهید داد. منجمله همان بحث کوئری‌های پارامتری که عموما از نوشتن آن طفره می‌روند اما اینجا به صورت خودکار برای شما انجام می‌شود.
              مطالب
              انجام کارهای پس زمینه در ASP.NET 4.5.2
              دات نت 4.5.2 قابلیت توکاری را به نام در صف قرار دادن یک کار پس زمینه، اضافه کرده‌است که در ادامه خلاصه‌ای از آن‌را مرور خواهیم کرد.


              روش متداول ایجاد کارهای پس زمینه

              ساده‌ترین روش انجام کارهای پس زمینه در برنامه‌های دات نتی، استفاده از متدهایی هستند که یک ترد جدید را ایجاد می‌کنند مانند Task.Run, Task.Factory.StartNew, Delegate.BeginInvoke, ThreadPool.QueueUserWorkItem و امثال آن. اما ... این روش‌ها در برنامه‌های ASP.NET ایده‌ی خوبی نیستند!
              از این جهت که موتور ASP.NET در این حالات اصلا نمی‌داند که شما کار پس زمینه‌ای را ایجاد کرده‌اید. به همین جهت اگر پروسه‌ی برنامه پس از مدتی recycle شود، تمام کارهای پس زمینه‌ی موجود نیز از بین خواهند رفت.


              معرفی HostingEnvironment.QueueBackgroundWorkItem

              متد HostingEnvironment.QueueBackgroundWorkItem به دات نت 4.5.2 اضافه شده‌است تا بتوان توسط آن یک کار پس زمینه را توسط موتور ASP.NET شروع کرد و نه مانند قبل، بدون اطلاع آن. البته باید دقت داشت که این کارهای پس زمینه مستقل از هر نوع درخواستی اجرا می‌شوند.
              در این حالت چون موتور ASP.NET از وجود کار پس زمینه‌ی آغاز شده مطلع است، در صورت فرا رسیدن زمان recycle شدن برنامه، کل AppDomain را به یکباره نابود نخواهد کرد. البته این مورد فقط به این معنا است که در صورت فرا رسیدن زمان recycle شدن پروسه، با تنظیم یک CancellationToken، اطلاع رسانی خواهد کرد. در این حالت حداکثر 30 ثانیه فرصت خواهید داشت تا کارهای پس زمینه را بدون مشکل خاتمه دهید. اگر کار پس زمینه در این مدت به پایان نرسد، همانند قبل، کل AppDomain نابود خواهد شد.

              این متد دو overload دارد و در هر دو حالت، تنظیم خودکار پارامتر CancellationToken توسط ASP.NET، بیانگر آغاز زمان خاتمه‌ی کل برنامه است:
               public static void QueueBackgroundWorkItem(Action<CancellationToken> workItem);
              public static void QueueBackgroundWorkItem(Func<CancellationToken, Task> workItem);
              در متد اول، یک متد معمولی از نوع void قابل پردازش است. در متد دوم، می‌توان متدهای async Task دار را که قرار است کارهای async را پردازش کنند، معرفی نمود.
              علت استفاده از Action و Func در اینجا، امکان تعریف خلاصه و inline یک متد و ارسال پارامتری به آن از طرف برنامه است، بجای تعریف یک اینترفیس جدید، نیاز به پیاده سازی آن اینترفیس و بعد برای مثال ارسال یک مقدار از طرف برنامه به متد Stop آن (بجای تعریف یک اینترفیس تک متدی، از Action و یا Func نیز می‌توان استفاده کرد).

              نمونه‌ای از نحوه‌ی فراخوانی این دو overload را در ذیل مشاهده می‌کنید:
               HostingEnvironment.QueueBackgroundWorkItem(cancellationToken =>
              {
                      //todo: ...
              });
              
              HostingEnvironment.QueueBackgroundWorkItem(async cancellationToken =>  
              {
                      //todo: ...
                      await Task.Delay(20000, cancellationToken);
              });


              پشت صحنه‌ی HostingEnvironment.QueueBackgroundWorkItem

              روش استاندارد ثبت و معرفی یک کار پس زمینه در ASP.NET، توسط پیاده سازی اینترفیسی به نام IRegisteredObject انجام می‌شود. سپس توسط متد HostingEnvironment.RegisterObject می‌توان این کلاس را به موتور ASP.NET معرفی کرد. در این حالت زمانیکه AppDomain قرار است خاتمه یابد، متد Stop اینترفیس IRegisteredObject کار اطلاع رسانی را انجام می‌دهد. توسط QueueBackgroundWorkItem دقیقا از همین روش به همراه فراخوانی  ThreadPool.QueueUserWorkItemجهت اجرای متد معرفی شده‌ی به آن استفاده می‌شود.
              از مکانیزم IRegisteredObject در DNT Scheduler نیز استفاده شده‌است.


              پیشنیازها

              ابتدا نیاز است به خواص پروژه مراجعه کرده و Target framework را بر روی 5.4.2 قرار داد. اگر به روز رسانی دوم VS 2013 را نصب کرده باشید، این نگارش هم اکنون بر روی سیستم شما فعال است. اگر خیر، امکان دریافت و نصب آن، به صورت جداگانه نیز وجود دارد:
              .NET Framework 4.5.2
              Developer pack



              محدودیت‌های QueueBackgroundWorkItem

              - از آن در خارج از یک برنامه‌ی وب ASP.NET نمی‌توان استفاده کرد.
              - توسط آن، خاتمه‌ی یک AppDomain تنها به مدت 30 ثانیه به تاخیر می‌افتد؛ تا فرصت داشته باشید کارهای در حال اجرا را با حداقل خسارت به پایان برسانید.
              - یک work item، اطلاعاتی را از فراخوان خود دریافت نمی‌کند. به این معنا که مستقل از زمینه‌ی یک درخواست اجرا می‌شود.
              - استفاده‌ی از آن الزاما به این معنا نیست که کار درخواستی شما حتما اجرا خواهد شد. زمانیکه که کار خاتمه‌ی AppDomain آغاز می‌شود، فراخوانی‌های QueueBackgroundWorkItem دیگر پردازش نخواهند شد.
              - اگر برنامه به مقدار CancellationToken تنظیم شده توسط ASP.NET دقت نکند، جهت پایان یافتن کار در حال اجرا، صبر نخواهد شد.
              نظرات مطالب
              شروع به کار با EF Core 1.0 - قسمت 11 - بررسی رابطه‌ی Self Referencing
              استفاده از second level caching in EF Core برای واکشی Self Referencing در زمان واکشی ToList دوم به درستی عمل نمیکند و Null برگشت میدهد

              var menuesFirst = await _publicMenus.Where(p => p.Language == _caltureName).Cacheable().ToListAsync();
              var menues = menuesFirst.Where(x => x.MenuId == null).ToList();

              پاسخ به بازخورد‌های پروژه‌ها
              توضیح گام های اجرا شده در پروژه
              سلام؛
              هدف از انجام این پروژه برای من چسباندن قطعات مختلف یک پازل به هم بودند تا بتوان به یک تصویر خوب رسید.منظور من این است که entity framework و ASP.NET MVC و bootstrap و best practice‌های آن‌ها به تنهایی و جدا از هم به نظر ساده و راحت و خوب بیایند، اما درگیر شدن همه‌ی آن‌ها در یک پروژه‌ی واقعی، واقعا چالش بر انگیز است.
              من دانشجو هستم و تقریبا استارت این پروژه را از آبان ماه زدم، اما به دلیل یک سری مشکلات از جمله همین دانشجو بودن، کار به کندی پیش رفت و حتی وقفه‌های چند ماهه در آن پیش اومد. هدف من این بود که اساسا یک سیستم با کیفیت بنویسم و در ابتدای کار هم، کار به خوبی پیش می‌رفت، اما با توجه به مشکلات ذکر شده، عمده کار کدنویسی در تعطیلات عید نوروز صورت گرفت، و کاملا از کدنویسی انجام شده مشهود است ک ههمان قسمت هایی که در عید نوروز کدنویسی شده اند، اصطلاحا سرهم بندی شده اند( به خصوص در کدهای سمت کلاینت)
              در مورد گام‌های انجام شده؛ پروژه به این منوال انجام شد:
              - تحلیل ساختار بانک اطلاعاتی مورد نیاز
              - شروع به تحقیق در مورد امکانات مورد نیاز 
              - دعوت همکای برای کار گروهی توسط دوستان ( کسی قبول نکرد البته دی:)
              - با توجه به محدودیت‌های یافت شده در تحقیقات، ساختار بانک اطلاعاتی نهایی می‌شود.
              - انتخاب فریم ورک‌های مناسب( که در اینجا Entity Framework برای orm و ASP.NET MVC برای کدنویسی سمت سرور و bootstrap برای css و jquery هم برای جاوا اسکریپت)
              - تحقیق در مورد best practice‌های موجود در مورد هر یک از فریم ورک‌های فوق
              -شروع کدنویسی
              در مورد قسمت مدیریت کاربران، هدف طراحی یک سیستم خیلی منعطف بود که قطعا با memebrship خود دات نت امکان پذیر نبود. متاسفانه به دلیل مشکلات پیش اومده این قسمت از پروژه هم سرهم بندی کردم و به یک سیستم ساده اکتفا کردم.
              برای پیاده سازی آن هم شما کافیست در گوگل عبارت implement custom membership in asp.net mvc را سرچ کنید. مطمئن باشید کلی مطلب پیدا خوهید کرد که با جمع بندی آن یک سیستم خوب می‌توانید پیاده سازی کنید.
              الان همین سیستم پیاده سازی شده در سایت یک باگ دارد که بعد از مدتی remember me آ از کار می‌افتد.کوکی کاربر اعتبار دارد، اما رویداد متناظر آن برای اعتبار سنجی اتفاق نمی‌افتد!
              الان هم در حال تحقیق برای پیاده سازی یک سیستم اعتبارسنجی  کامل‌تر و اصولی‌تر و یک پارچه‌تر با ASP.NET MVC  هستم که مقاله‌ی زیر خیلی به من کمک کرد.(امیدوارم برای شما هم مفید باشد)
              الان هم برنامه ای برای ارتقا این سیستم دارم و مهمترین تغییر آن را می‌توان به استفاده از angularjs برای نوشتن بخش مدیریتی و پیاده سازی آن به صورت single page دانست.( البته اگر این کمردرد بزاره دی:)
              امیدوارم دوستان با بازخوردهای خوب خودشون، در ارتقای سطح کیفی کار کمک کنند.
              مطالب
              بازسازی جدول MigrationHistory با کد نویسی در EF Code first
              فرض کنید با استفاده از ابزار EF Power tools معادل Code first یک بانک اطلاعاتی موجود را تهیه کرده‌اید. اکنون برای استفاده از آن با گردش کاری متداول EF Code first نیاز است تا جدولی را به نام MigrationHistory نیز به این بانک اطلاعاتی اضافه کنیم. از این جدول برای نگهداری سوابق به روز رسانی ساختار بانک اطلاعاتی بر اساس مدل‌های برنامه و سپس مقایسه آن‌ها استفاده می‌شود. یا حتی ممکن است به اشتباه در حین کار با بانک اطلاعاتی این جدول حذف شده باشد. روش باز تولید آن توسط دستورهای پاور شل به سادگی اجرای سه دستور ذیل است:
              enable-migrations
              add-migration Initial -IgnoreChanges
              update-database
              IgnoreChanges سبب می‌شود تا EF فرض کند، تطابق یک به یکی بین مدل‌های برنامه و ساختار جداول بانک اطلاعاتی وجود دارد. سپس بر این اساس، جدول MigrationHistory جدیدی را آغاز می‌کند.

              سؤال: چگونه می‌توان همین عملیات را با کدنویسی انجام داد؟

              متد UpdateDatabase کلاس ذیل، دقیقا معادل است با اجرای سه دستور فوق :
              using System.Data.Entity.Migrations;
              using System.Data.Entity.Migrations.Design;
              
              namespace EFTests
              {
                  /// <summary>
                  /// Using Entity Framework Code First with an existing database.
                  /// </summary>
                  public static class CreateMigrationHistory
                  {
                      /// <summary>
                      /// Creates a new '__MigrationHistory' table.
                      /// Enables migrations using Entity Framework Code First on an existing database.
                      /// </summary>        
                      public static void UpdateDatabase(DbMigrationsConfiguration configuration)
                      {            
                          var scaffolder = new MigrationScaffolder(configuration);
                          // Creates an empty migration, so that the future migrations will start from the current state of your database.
                          var scaffoldedMigration = scaffolder.Scaffold("IgnoreChanges", ignoreChanges: true);
              
                          // enable-migrations
                          // add-migration Initial -IgnoreChanges
                          configuration.MigrationsAssembly = new MigrationCompiler(ProgrammingLanguage.CSharp.ToString())
                                                              .Compile(configuration.MigrationsNamespace, scaffoldedMigration);
              
                          // update-database  
                          var dbMigrator = new DbMigrator(configuration);            
                          dbMigrator.Update();
                      }
                  }
              }
              توضیحات
              MigrationScaffolder کار تولید خودکار کلاس‌های cs مهاجرت‌های EF را انجام می‌دهد. زمانیکه به متد Scaffold آن پارامتر ignoreChanges: true ارسال شود، کلاس مهاجرتی را ایجاد می‌کند که خالی است (متدهای up و down آن خالی تشکیل می‌شوند). سپس این کلاس‌ها کامپایل شده و در حین اجرای برنامه مورد استفاده قرار می‌گیرند.

              برای استفاده از آن، نیاز به کلاس MigrationCompiler خواهید داشت. این کلاس در مجموعه آزمون‌های واحد EF به عنوان یک کلاس کمکی وجود دارد: MigrationCompiler.cs
              صرفا جهت تکمیل بحث و همچنین سهولت ارجاعات آتی، کدهای آن در ذیل نیز ذکر خواهد شد:
              using System;
              using System.CodeDom.Compiler;
              using System.Collections.Generic;
              using System.ComponentModel;
              using System.Data.Common;
              using System.Data.Entity;
              using System.Data.Entity.Migrations;
              using System.Data.Entity.Migrations.Design;
              using System.Data.Entity.Spatial;
              using System.IO;
              using System.Linq;
              using System.Linq.Expressions;
              using System.Reflection;
              using System.Resources;
              using System.Text;
              
              namespace EF_General.Models.Ex22
              {
                  public enum ProgrammingLanguage
                  {
                      CSharp,
                      VB
                  }
              
                  public class MigrationCompiler
                  {
                      private readonly CodeDomProvider _codeProvider;
              
                      public MigrationCompiler(string language)
                      {
                          _codeProvider = CodeDomProvider.CreateProvider(language);
                      }
              
                      public Assembly Compile(string @namespace, params ScaffoldedMigration[] scaffoldedMigrations)
                      {
                          var options = new CompilerParameters
                          {
                              GenerateExecutable = false,
                              GenerateInMemory = true
                          };
              
                          options.ReferencedAssemblies.Add(typeof(string).Assembly.Location);
                          options.ReferencedAssemblies.Add(typeof(Expression).Assembly.Location);
                          options.ReferencedAssemblies.Add(typeof(DbMigrator).Assembly.Location);
                          options.ReferencedAssemblies.Add(typeof(DbContext).Assembly.Location);
                          options.ReferencedAssemblies.Add(typeof(DbConnection).Assembly.Location);
                          options.ReferencedAssemblies.Add(typeof(Component).Assembly.Location);
                          options.ReferencedAssemblies.Add(typeof(MigrationCompiler).Assembly.Location);
                          options.ReferencedAssemblies.Add(typeof(DbGeography).Assembly.Location);
              
                          var embededResources = GenerateEmbeddedResources(scaffoldedMigrations, @namespace);
                          foreach (var resource in embededResources)
                              options.EmbeddedResources.Add(resource);
              
                          var sources = scaffoldedMigrations.SelectMany(g => new[] { g.UserCode, g.DesignerCode });
              
                          var compilerResults = _codeProvider.CompileAssemblyFromSource(options, sources.ToArray());
                          foreach (var resource in embededResources)
                              File.Delete(resource);
              
                          if (compilerResults.Errors.Count > 0)
                          {
                              throw new InvalidOperationException(BuildCompileErrorMessage(compilerResults.Errors));
                          }
              
                          return compilerResults.CompiledAssembly;
                      }
              
                      private static string BuildCompileErrorMessage(CompilerErrorCollection errors)
                      {
                          var stringBuilder = new StringBuilder();
              
                          foreach (CompilerError error in errors)
                          {
                              stringBuilder.AppendLine(error.ToString());
                          }
              
                          return stringBuilder.ToString();
                      }
              
                      private static IEnumerable<string> GenerateEmbeddedResources(IEnumerable<ScaffoldedMigration> scaffoldedMigrations, string @namespace)
                      {
                          foreach (var scaffoldedMigration in scaffoldedMigrations)
                          {
                              var className = GetClassName(scaffoldedMigration.MigrationId);
                              var embededResource = Path.Combine(
                                  Path.GetTempPath(),
                                  @namespace + "." + className + ".resources");
              
                              using (var writer = new ResourceWriter(embededResource))
                              {
                                  foreach (var resource in scaffoldedMigration.Resources)
                                      writer.AddResource(resource.Key, resource.Value);
                              }
              
                              yield return embededResource;
                          }
                      }
              
                      private static string GetClassName(string migrationId)
                      {
                          return migrationId
                              .Split(new[] { '_' }, 2)
                              .Last()
                              .Replace(" ", string.Empty);
                      }
                  }
              }
              جهت مطالعه توضیحات بیشتری در مورد CodeDom می‌توان به مطلب «کامپایل پویای کد در دات نت» مراجعه کرد.
              استفاده از این کلاس‌ها نیز بسیار ساده است. یکبار دستور ذیل را در ابتدای کار برنامه فراخوانی کنید تا جدول MigrationHistory دوباره ساخته شود:
               CreateMigrationHistory.UpdateDatabase(new Configuration());
              با این فرض که کلاس Configuration شما چنین شکلی را دارد:
                  public class Configuration : DbMigrationsConfiguration<MyContext>
                  {
                      public Configuration()
                      {
                          AutomaticMigrationsEnabled = true;
                          AutomaticMigrationDataLossAllowed = true;
                      }
                  }

              مطالب
              تغییرات متدهای بازگشت فایل‌ها به سمت کلاینت در ASP.NET Core
              اگر خروجی return File را در اکشن متدهای ASP.NET Core همانند ASP.NET MVC 5.x مورد استفاده قرار دهید و در آن مسیرکامل فایل را برای بازگشت قید کرده باشید، پیام یافت نشدن فایل را دریافت خواهید کرد؛ هرچند این فایل بر روی سرور و در مسیر ذکر شده وجود خارجی دارد. علت آن‌را در تصویر ذیل می‌توانید مشاهده کنید:



              روش‌های مختلف بازگشت فایل‌ها به سمت کلاینت در ASP.NET Core

              در ASP.NET Core، نوع‌های کاملتری از Action Result‌های مرتبط با بازگشت فایل‌ها تدارک دیده شده‌اند که نحوه‌ی طراحی آن‌ها را در شکل فوق ملاحظه می‌کنید. در اینجا FileResult والد تمام حالت‌های بازگشت فایل است که شامل موارد ذیل می‌شود:
              FileContentResult: از آن برای بازگشت آرایه‌ای از بایت‌ها استفاده می‌شود:
              //returns the file content as an array of bytes
              public FileContentResult FileContentActionResult()
              {
                 var file = System.IO.File.ReadAllBytes(@"C:\path\dir1\HomeController.cs");
                 return File(file, "text/plain", "HomeController.cs");
              }
              زمانیکه Controller جاری از کلاس پایه Controller ارث بری می‌کند، متد File در این کلاس پایه قرار دارد. به همین جهت مانند مثال فوق به سادگی می‌توان به آن، بدون ذکر new دسترسی یافت. روش دیگر دسترسی به FileContentResult به صورت ذیل است که معادل قطعه کد فوق می‌باشد:
              public IActionResult TestFileContentActionResult()
              {
                 var file = System.IO.File.ReadAllBytes(@"C:\path\dir1\HomeController.cs");
                 return new FileContentResult(file, "text/plain") { FileDownloadName = "HomeController.cs" };
              }

              FileStreamResult: این Action Result قابلیت Streaming بازگشت فایل‌ها را مهیا می‌کند:
              //return the file as a stream
              public FileStreamResult FileStreamActionResult()
              {
                 //var file = System.IO.File.ReadAllBytes(@"C:\path\dir1\HomeController.cs");
                 //var stream = new MemoryStream(file, writable:true);
              
                 var fileStream = new FileStream(@"C:\path\dir1\HomeController.cs", FileMode.Open, FileAccess.Read);
                 return File(fileStream, "text/plain", "HomeController.cs");
              }
              در اینجا برای مثال می‌توان یک MemoryStream و یا یک FileStream را به سمت کاربر ارسال کرد. این روش نسبت به خواندن فایل‌ها در آرایه‌ای از بایت‌ها و سپس ارسال یکجای آن، بهینه‌تر است و حافظه‌ی کمتری را مصرف می‌کند.
              اگر خواستیم مستقیما با FileStreamResult کار کنیم، روش کار به صورت ذیل است:
              public IActionResult TestFileStreamActionResult()
              {
                 //var file = System.IO.File.ReadAllBytes(@"C:\path\dir1\HomeController.cs");
                 //var stream = new MemoryStream(file, writable:true);
                 var fileStream = new FileStream(@"C:\path\dir1\HomeController.cs", FileMode.Open, FileAccess.Read);
                 return new FileStreamResult(fileStream, "text/plain") { FileDownloadName = "HomeController.cs" };
              }

              VirtualFileResult: در این مورد آدرسی را که ارائه می‌دهید، باید به فایلی درون پوشه‌ی wwwroot اشاره کند (علت اصلی بروز مشکلی که در مقدمه‌ی بحث عنوان شد). در اینجا آدرس کامل فایل مدنظر نیست.
              //returns a file specified with a virtual path
              public VirtualFileResult VirtualFileActionResult()
              {
                 return File("/css/site.css", "text/plain", "site.css");
              }
              و یا معادل همین قطعه کد با استفاده از VirtualFileResult اصلی به صورت ذیل است:
              public IActionResult TestVirtualFileActionResult()
              {
                 return new VirtualFileResult("/css/site.css", "text/plain") { FileDownloadName = "site.css" };
              }

              PhysicalFileResult: اگر قصد دارید آدرس کامل فایلی را مشخص کنید (بجای مسیر نسبی آن که از wwwroot شروع می‌شود؛ مانند حالت قبل)، اینبار باید از متد PhysicalFile استفاده کرد:
              //returns the specified file on disk, that is it's physical address
              public PhysicalFileResult PhysicalFileActionResult()
              {
                 return PhysicalFile(@"C:\path\dir1\HomeController.cs", "text/plain", "HomeController.cs");
              }
              این قطعه کد نیز بر اساس استفاده‌ی مستقیم از PhysicalFileResult شکل زیر را می‌تواند پیدا کند:
              public IActionResult TestPhysicalFileActionResult()
              {
                 return new PhysicalFileResult(@"C:\path\dir1\HomeController.cs", "text/plain")
                 {
                    FileDownloadName = "HomeController.cs"
                 };
              }

              در این متدها و کلاس‌ها، اگر FileDownloadName حاوی حروف اسکی نباشد، به صورت خودکار encoding از نوع RFC5987 بر روی آن اعمال خواهد شد.
              مطالب
              پشتیبانی توکار از GDPR در ASP.NET Core 2.1
              دیروز (25 ماه May سال 2018) اولین روز فعالسازی GDPR یا General Data Protection Regulation بود و به همین خاطر است که اگر به سرویس‌های مهم اینترنتی دقت کرده باشید، پر شده‌است از پیام‌هایی مانند «ما از کوکی استفاده می‌کنیم»، «ما اطلاعات شما را به این صورت ذخیره می‌کنیم» و امثال آن. همچنین تعداد زیادی از سرویس‌های اینترنتی نیز به کاربران خود پیام‌هایی را جهت تائید قوانین جدید رعایت حریم خصوصی آن‌ها ارسال کرده‌اند. برای مثال اگر این قوانین جدید را تائید نکنید، از دریافت بسیاری از خبرنامه‌ها محروم خواهید شد. این مورد نیز از بخش‌نامه‌ی اتحادیه‌ی اروپا نشات می‌گیرد که از روز جمعه ۲۵ می‌(۴ خرداد) تمامی شرکت‌ها، افراد، وب‌سایت‌ها و ارائه‌دهندگان خدمات آنلاین، موظف به رعایت آن هستند. موضوع بخش‌نامه قبل از هرچیزی، حفاظت از اطلاعات خصوصی کاربران است. نهادها و شرکت‌ها و وب‌سایت‌هایی که تا ۲۵ می‌۲۰۱۸ زمینه اجرای این بخش‌نامه را فراهم نکرده باشند، در خطر جریمه‌های سنگین هستند. بخش‌نامه جدید حریم خصوصی اطلاعات، تعیین می‌کند که چه میزان اطلاعاتی درباره‌ی هرکسی می‌تواند جمع‌آوری و بررسی شود، مورد پردازش قرار گیرد و البته تبدیل به پول شود. این بخش‌نامه حق تک‌تک کاربران، بر اطلاعات‌شان را تقویت می‌کند. کاربران حالا حق بیشتری بر اطلاعات‌شان، برای «پاک کردن» آن‌ها و «پس‌گرفتن» آن‌ها دارند. البته آن‌چه که احتمالا برای همه قابل رؤیت خواهد بود توضیحات مربوط به حفاظت از اطلاعات در وبسایت‌ها است. این توضیحات باید جزئی‌تر و دقیق‌تر و برای مخاطبان قابل فهم‌تر باشند و این به این معنا است که این توضیحات به‌مراتب طولانی‌تر خواهند شد.
              در این بین اگر به قالب پیش‌فرض پروژه‌های MVC تولید شده‌ی توسط ASP.NET Core 2.1 نیز دقت کنید، پشتیبانی توکار از پیشنیازهای GDPR در آن لحاظ شده‌است؛ چه از لحاظ گوشزد کردن شرایط حریم خصوصی و پذیرش آن و چه از لحاظ «پاک کردن» و «پس گرفتن» اطلاعات شخصی.


              قالب و کوکی پذیرش شرایط حریم خصوصی سایت (Cookie Consent)


              اگر قالب پیش‌فرض یک پروژه‌ی ASP.NET Core 2.1 را اجرا کنید، تصویر فوق را که در آن نوار پذیرش شرایط حریم خصوصی سایت در بالای صفحه درج شده‌است، مشاهده خواهید کرد.
              قالب جدید نوار پذیرش شرایط حریم خصوصی در مسیر Views\Shared\_CookieConsentPartial.cshtml واقع شده‌است و در فایل layout برنامه توسط tag helper جدید Partial، رندر و نمایش داده می‌شود:
              <partial name="_CookieConsentPartial" />
              در ابتدای این partial view، یک چنین کدهایی درج شده‌اند:
              @using Microsoft.AspNetCore.Http.Features
              @{
                var consentFeature = Context.Features.Get<ITrackingConsentFeature>();
                var showBanner = !consentFeature?.CanTrack ?? false;
                var cookieString = consentFeature?.CreateConsentCookie();
              }
              بنابراین پذیرش شخص را در یک کوکی درج می‌کند و در دفعات بعدی بازدید او بر اساس این کوکی است که در مورد نمایش یا عدم نمایش این نوار پذیرش شرایط، تصمیم گیری خواهد شد. این کوکی نیز که تحت عنوان میان‌افزار CookiePolicy در سیستم مدیریت و پردازش می‌شود، به صورت زیر در فایل آغازین برنامه مدیریت می‌گردد:
              الف) تنظیم نیاز به دریافت پذیرش
              public void ConfigureServices(IServiceCollection services)
              {
                 services.Configure<CookiePolicyOptions>(options =>
                 {
                   // This lambda determines whether user consent for non-essential cookies is needed for a given request.
                   options.CheckConsentNeeded = context => true;
                   options.MinimumSameSitePolicy = SameSiteMode.None;
                 });
              ب) فعالسازی میان‌افزار مدیریت کوکی پذیرش شرایط حریم خصوصی
              public void Configure(IApplicationBuilder app, IHostingEnvironment env)
              {
                 // ...
                 app.UseCookiePolicy();


              دادن حق فراموش شدن به کاربران

              علاوه بر Cookie Consent فوق که در یک قالب ابتدایی MVC نیز درج شده‌است، در قالب پروژه‌های ASP.NET Core Identity، دو گزینه‌ی جدید دریافت اطلاعات شخصی و همچنین حذف اکانت (دادن حق فراموشی به کاربران) نیز پیش‌بینی شده‌است: PersonalData.cshtml


              البته این صفحه جزو بسته‌ی جدید Microsoft.AspNetCore.Identity.UI است که به همراه ASP.NET Core 2.1 ارائه می‌شود:
               dotnet add package Microsoft.AspNetCore.Identity.UI --version 2.1.0-rc1-final
              در این بسته تمام کدها و صفحات مخصوص Identity به داخل یک Class library جدید منتقل شده‌اند و دیگر جزو قالب پروژه‌ی «dotnet new mvc --auth Individual» یا همان تنظیم اعتبارسنجی به individual user accounts نیستند و باید به صورت جداگانه دریافت و تنظیم شوند (اختیاری است).