نظرات مطالب
طراحی گردش کاری با استفاده از State machines - قسمت اول
با توجه به اینکه اطلاعات مربوط به State ها، همینطور Transition‌ها و ... در بانک اطلاعاتی ذخیره می‌شود  برای به گردش درآوردن باید با fetch کردن داده‌ها از بانک اطلاعات یک Object را به گردش درآوریم. حال برای ساختن و کانفیگ کلاس مربوط به State Machine به نظر می‌رسد یک راه این باشد: 
 
private StateMachine<string, string> stateMachine;
        private StateMachineCOM source;
        private string startState;
        public delegate void UnhandledTriggerDelegate(State state,  StateConfig trigger);
        public delegate void EntryExitDelegate();
        public delegate bool GuardClauseDelegate();
        public string Id;
        public EntryExitDelegate OnEntry = null;
        public EntryExitDelegate OnExit = null;
        public GuardClauseDelegate GuardClauseFromToTrigger = null;
        public UnhandledTriggerDelegate OnUnhandledTrigger = null;

        public StateMachineRequest(StateMachineCOM source, string startStateId)
        {
            this.source = source;   
            this.startState = startStateId;
        }

        public void Configure() 
        {
            this.stateMachine = new StateMachine<string, string>(startState);
            var states = source.States;
            states.ForEach(state =>
            {
                var triggers = source.StateConfigs.AsQueryable()
                                   .Where(config => config.FromStateId == state.StateId)
                                   .Select(config => new {Id=config.TransitionId.ToString(), From= config.FromStateId.ToString(), To= config.ToStateId.ToString(), Permit=config.PermiteAction })
                                   .ToList();

                triggers.ForEach(trig =>
                {
                    this.stateMachine.Configure(state.StateId.ToString())
                });
            });
        }

        public bool TryFireTrigger(string TrigerId)
        {
            if (!stateMachine.CanFire(TrigerId))
            {
                return false;
            }
            stateMachine.Fire(TrigerId);
            return true;
        }
        
        public string GetCurrentState()
        {
            return this.stateMachine.State;
        }

باشد یعنی State‌ها Transition‌ها و ... را بعد از Fetch کردن از بانک اطلاعاتی به State Machine ارسال کنیم. حالا برای در نظر گرفتن شروط مربوط به OnEntry و OnExit یا GuardClauseFromToTrigger پیشنهاد شما توجه به اینکه براساس State می‌بایست این متدها ساخته شوند چیست؟
- آیا بهتر است delegate پارامتر دریافت کند؟
اگر بله پیاده سازی آن در هنگام کانفیگ به چه صورت است؟ به این صورت ؟
this.stateMachine.Configure(state.StateId.ToString())
                        .OnEntry(() => { if (state.OnEnter) OnEntry(trig.Id);})
                        .OnExit(() => { if (state.OnExit) OnExit(trig.Id); })
                        .PermitIf(trig.From, trig.To, () => { if (trig.Permit) return GuardClauseFromToTrigger(); return true; });
- اگر خیر چگونه می‌توان این متدها را بصورت دینامیک ایجاد کرد و به هنگام کانفیگ ماشین حالت به آن انتساب داد و بعد در هنگام گردش آبجکت به آن دسترسی داشت؟
مطالب
سفارشی سازی Resourceها بر اساس نوع مشتری در Asp.net Core
فرض کنید یک برنامه‌ی تحت وب را نوشته‌ایم که برای مدارس و همچنین برای هنرستان‌ها مورد استفاده قرار می‌گیرد. هنگامیکه برنامه را برای مشتری پابلیش می‌کنیم، از کلمات مدرسه و دانش آموز استفاده کرده‌ایم. اما مشتری هنرستان از ما می‌خواهد این عبارت‌ها، به هنرستان و هنرآموز تغییر کنند. خوب یک راه‌حل این هست که ریسورس‌ها را قبل از هر پابلیش تغییر دهیم و همیشه باید حواسمان به این موضوع باشد که الان برنامه را برای مشتری مدرسه پابلیش می‌کنیم، یا مشتری هنرستان. در این مطلب به کمک DI توکار  Asp.net Core، یک راهکار را برای این موضوع ارائه کرده‌ایم.

ابتدا دو کلاس را به نام‌های SchoolResource و ArtSchoolResource و فایلهای ریسورس مربوطه را به نام‌های  SchoorResource.fa.resx و ArtSchoolResource.fa.resx ایجاد می‌کنیم. این دو فایل در حقیقت Shared Resource  هستند:

همچنین یک Interface را نیز به نام IResourceManager تعریف می‌کنیم:

public interface IResourceManager
    {
         IStringLocalizer Localizer { get; }
    }

سپس دو کلاس دیگر را ایجاد کرده و آنها را از این اینترفیس مشتق می‌کنیم. در این اینترفیس، یک خصوصیت Localizer از نوع IStringLocalizer وجود دارد که باید در این کلاس‌ها پیاده سازی شود. این کار را به کمک  IStringLocalizerFactory انجام داده و مشخص می‌کنیم هر کلاس، از کدام ریسورس استفاده کند:

 public class SchoolResourceManager : IResourceManager
    {       
        public SchoolResourceManager (IStringLocalizerFactory factory)
        {
            Localizer = factory.Create(typeof(SchoolResource));
        }
        public IStringLocalizer Localizer { get; }
    }
 public class ArtSchoolResourceManager : IResourceManager
    {       
        public ArtSchoolResourceManager(IStringLocalizerFactory factory)
        {
            Localizer = factory.Create(typeof(ArtSchoolResource));
        }
        public IStringLocalizer Localizer { get; }
    }


سپس در فایل appsetting.json پروژه، یک کلید را تعریف می‌کنیم تا مشخص کند، نوع application، از نوع مدرسه هست یا از نوع هنرستان:

{
  "ConnectionStrings": {
    "str": "..."
  },
  "Type": "ArtSchool" 
}

در متد ConfigureServices در فایل  startup تعیین می‌کنیم هنگام شروع به کار برنامه، با توجه به مقدار درون appsetting، کدام سرویس (manager) تزریق شود:

 public void ConfigureServices(IServiceCollection services)
{
            services.AddLocalization();
            services.AddSingleton<SchoolResourceManager>();
            services.AddSingleton<ArtSchoolResourceManager>();
            services.AddSingleton<IResourceManager>(cnf =>
            {
                var type = Configuration.GetSection("Type").Value;
                if (type == "ArtSchool")
                    return cnf.GetServices<ArtSchoolResourceManager>().FirstOrDefault();
                    

                return cnf.GetServices<SchoolResourceManager>().FirstOrDefault();
            });
}

خوب حالا با خیال راحت برنامه را توسعه می‌دهیم و در view‌ها و Controller‌ها از همان کلیدهای School و Student استفاده می‌کنیم:

: View

@inject IResourceManager ResourceManager

<h2>@ResourceManager.Localizer["Student"] : </h2>

 :Controller

 public class TestController : Controller
    {        
        private readonly IResourceManager _resourceManager;
       
        public TestController (IResourceManager resourceManager)
        {
             _resourceManager = resourceManager;
        }
}
مطالب
C# 8.0 - Default implementations in interfaces
اگر مطلب «تفاوت بین Interface و کلاس Abstract در چیست؟» را مطالعه کرده باشید، به این نتیجه می‌رسید که طراحی یک کتابخانه‌ی عمومی با اینترفیس‌ها، بسیار شکننده‌است. اگر عضو جدیدی را به یک اینترفیس عمومی اضافه کنیم، تمام پیاده سازی کننده‌های آن‌را از درجه‌ی اعتبار ساقط می‌کند و آن‌ها نیز باید این عضو را حتما پیاده سازی کنند تا برنامه‌ای که پیش از این به خوبی کار می‌کرده، باز هم بدون مشکل کامپایل شده و کار کند. هدف از ویژگی جدید «پیاده سازی‌های پیش‌فرض در اینترفیس‌ها» در C# 8.0، پایان دادن به این مشکل مهم است. با استفاده از این ویژگی جدید، می‌توان یک عضو جدید را با پیاده سازی پیش‌فرضی داخل خود اینترفیس قرار داد. به این ترتیب تمام برنامه‌هایی که از کتابخانه‌های عمومی شما استفاده می‌کنند، با به روز رسانی آن، به یکباره از کار نخواهند افتاد.
همچنین مزیت دیگر آن، انتقال ساده‌تر کدهای جاوا به سی‌شارپ است؛ از این لحاظ که ویژگی مشابهی در زبان جاوا تحت عنوان «Default Methods» سال‌ها است که وجود دارد.


یک مثال از ویژگی «پیاده سازی‌های پیش‌فرض در اینترفیس‌ها»

interface ILogger
{
    void Log(string message);
}

class ConsoleLogger : ILogger
{
    public void Log(string message)
    {
        Console.WriteLine(message);
    }
}
فرض کنید کتابخانه‌ی شما، اینترفیس ILogger را ارائه داده‌است و در برنامه‌ای دیگر، استفاده کننده، کلاس ConsoleLogger را بر مبنای آن پیاده سازی و استفاده کرده‌است.
مدتی بعد بر اساس نیازمندی‌های مشخصی به این نتیجه خواهید رسید که بهتر است overload دیگری را برای متد Log در اینترفیس ILogger، درنظر بگیریم. مشکلی که این تغییر به همراه دارد، کامپایل نشدن کلاس ConsoleLogger در یک برنامه‌ی ثالث است و این کلاس باید الزاما این overload جدید را پیاده سازی کند؛ در غیراینصورت قادر به کامپایل برنامه‌ی خود نخواهد شد. اکنون در C# 8.0 می‌توان برای این نوع تغییرات، در همان اینترفیس اصلی، یک پیاده سازی پیش‌فرض را نیز قرار داد:
interface ILogger
{
    void Log(string message);
    void Log(Exception exception) => Console.WriteLine(exception);
}
به این ترتیب استفاده کنندگان از این اینترفیس، برای کامپایل برنامه‌ی خود به مشکلی برنخواهند خورد و اگر از این overload جدید استفاده کنند، از همان پیاده سازی پیش‌فرض آن بهره خواهند برد. بدیهی است هنوز هم پیاده سازی کننده‌های اینترفیس ILogger می‌توانند پیاده سازی‌های سفارشی خودشان را در مورد این overload جدید ارائه دهند. در این حالت از پیاده سازی پیش‌فرض صرفنظر خواهد شد.


ویژگی «پیاده سازی‌های پیش‌فرض در اینترفیس‌ها» چگونه پیاده سازی شده‌است؟

واقعیت این است که امکان پیاده سازی این ویژگی، سال‌ها است که در سطح کدهای IL دات نت وجود داشته (از زمان دات نت 2) و اکنون از طریق کدهای برنامه با بهبود کامپایلر آن، قابل دسترسی شده‌است.


تاثیر زمینه‌ی کاری بر روی دسترسی به پیاده سازی‌های پیش‌فرض

مثال زیر را درنظر بگیرید:
    interface IDeveloper
    {
        void LearnNewLanguage(string language, DateTime dueDate);

        void LearnNewLanguage(string language)
        {
            // default implementation
            LearnNewLanguage(language, DateTime.Now.AddMonths(6));
        }
    }

    class BackendDev : IDeveloper // compiles OK
    {
        public void LearnNewLanguage(string language, DateTime dueDate)
        {
            // Learning new language...
        }
    }
در اینجا اینترفیس IDeveloper، به همراه یک پیاده سازی پیش‌فرض است و بر این اساس، کلاس BackendDev پیاده سازی کننده‌ی آن، دیگر نیازی به پیاده سازی اجباری متد LearnNewLanguage ای که تنها یک رشته را می‌پذیرد، ندارد.
سؤال: به نظر شما اکنون کدامیک از کاربردهای زیر از کلاس BackendDev، کامپایل می‌شود و کدامیک خیر؟
IDeveloper dev1 = new BackendDev();
dev1.LearnNewLanguage("Rust");

var dev2 = new BackendDev();
dev2.LearnNewLanguage("Rust");
پاسخ: فقط مورد اول. مورد دوم با خطای کامپایلر زیر مواجه خواهد شد:
 There is no argument given that corresponds to the required formal parameter 'dueDate' of 'BackendDev.LearnNewLanguage(string, DateTime)' (CS7036) [ConsoleApp]
به این معنا که اگر کلاس BackendDev را به خودی خود (دقیقا از نوع BackendDev) و بدون معرفی آن از نوع اینترفیس IDeveloper، بکار بگیریم، فقط همان متدهایی که داخل این کلاس تعریف شده‌اند، قابل دسترسی می‌باشند و نه متدهای پیش‌فرض تعریف شده‌ی در اینترفیس مشتق شده‌ی از آن.


ارث‌بری چندگانه چطور؟

احتمالا حدس زده‌اید که این قابلیت ممکن است ارث‌بری چندگانه را که در سی‌شارپ ممنوع است، میسر کند. تا C# 8.0، یک کلاس تنها از یک کلاس دیگر می‌تواند مشتق شود؛ اما این محدودیت در مورد اینترفیس‌ها وجود ندارد. به علاوه تاکنون اینترفیس‌ها مانند کلاس‌ها، امکان تعریف پیاده سازی خاصی را نداشتند و صرفا یک قرارداد بیشتر نبودند. بنابراین اکنون این سؤال مطرح می‌شود که آیا می‌توان با ارائه‌ی پیاده سازی پیش‌فرض متدها در اینترفیس‌ها، ارث‌بری چندگانه را در سی‌شارپ پیاده سازی کرد؛ مانند مثال زیر؟!
using System;

namespace ConsoleApp
{
    public interface IDev
    {
        void LearnNewLanguage(string language) => Console.Write($"Learning {language} in a default way.");
    }

    public interface IBackendDev : IDev
    {
        void LearnNewLanguage(string language) => Console.Write($"Learning {language} in a backend way.");
    }

    public interface IFrontendDev : IDev
    {
        void LearnNewLanguage(string language) => Console.Write($"Learning {language} in a frontend way.");
    }

    public interface IFullStackDev : IBackendDev, IFrontendDev { }

    public class Dev : IFullStackDev { }
}
سؤال: کد فوق بدون مشکل کامپایل می‌شود. اما در فراخوانی زیر، دقیقا از کدام متد LearnNewLanguage استفاده خواهد شد؟ آیا پیاده سازی آن از IBackendDev فراهم می‌شود و یا از IFrontendDev؟
IFullStackDev dev = new Dev();
dev.LearnNewLanguage("TypeScript");
پاسخ: هیچکدام! برنامه با خطای زیر کامپایل نخواهد شد:
The call is ambiguous between the following methods or properties: 'IBackendDev.LearnNewLanguage(string)' and 'IFrontendDev.LearnNewLanguage(string)' (CS0121)
کامپایلر سی‌شارپ در این مورد خاص از قانونی به نام «the most specific override rule» استفاده می‌کند. یعنی اگر برای مثال در IFullStackDev متد LearnNewLanguage به صورت صریحی بازنویسی و تامین شد، آنگاه امکان استفاده‌ی از آن وجود خواهد داشت. یا حتی می‌توان این پیاده سازی را در کلاس Dev نیز ارائه داد و از نوع آن (بجای نوع اینترفیس) استفاده کرد.


تفاوت امکانات کلاس‌های Abstract با متدهای پیش‌فرض اینترفیس‌ها چیست؟

اینترفیس‌ها هنوز نمی‌توانند مانند کلاس‌ها، سازنده‌ای را تعریف کنند. نمی‌توانند متغیرها/فیلدهایی را در سطح اینترفیس داشته باشند. همچنین در اینترفیس‌ها همه‌چیز public است و امکان تعریف سطح دسترسی دیگری وجود ندارد.
بنابراین باید بخاطر داشت که هدف از تعریف اینترفیس‌ها، ارائه‌ی «یک رفتار» است و هدف از تعریف کلاس‌ها، ارائه «یک حالت».


یک نکته: در نگارش‌های پیش از C# 8.0 هم می‌توان ویژگی «متدهای پیش‌فرض» را شبیه سازی کرد

واقعیت این است که توسط ویژگی «متدهای الحاقی»، سال‌ها است که امکان افزودن «متدهای پیش‌فرضی» به اینترفیس‌ها در زبان سی‌شارپ وجود دارد:
namespace MyNamespace
{
    public interface IMyInterface
    {
        IList<int> Values { get; set; }
    }

    public static class MyInterfaceExtensions
    {
        public static int CountGreaterThan(this IMyInterface myInterface, int threshold)
        {
            return myInterface.Values?.Where(p => p > threshold).Count() ?? 0;
        }
    }
}
و در این حالت هرچند به نظر اینترفیس IMyInterface دارای متدی نیست، اما فراخوانی زیر مجاز است:
var myImplementation = new MyInterfaceImplementation();
// Note that there's no typecast to IMyInterface required
var countGreaterThanFive = myImplementation.CountGreaterThan(5);
مطالب
کامپایل پویای کد در دات نت

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

مثال یک:
رشته زیر را کامپایل کرده و تبدیل به یک فایل exe کنید:

string source =
@"
namespace Foo
{
public class Bar
{
static void Main(string[] args)
{
Bar.SayHello();
}

public static void SayHello()
{
System.Console.WriteLine(""Hello World"");
}
}
}
";
روش انجام کار به همراه توضیحات مربوطه به صورت کامنت:

using System;
using System.Collections.Generic;
//دو فضای نامی که برای این منظور اضافه شده‌اند
using Microsoft.CSharp;
using System.CodeDom.Compiler;

namespace compilerTest
{
class Program
{
static void compileIt1()
{
//سورس کد ما جهت کامپایل
string source =
@"
namespace Foo
{
public class Bar
{
static void Main(string[] args)
{
Bar.SayHello();
}

public static void SayHello()
{
System.Console.WriteLine(""Hello World"");
}
}
}
";

//تعیین نگارش کامپایلر مورد استفاده
Dictionary<string, string> providerOptions = new Dictionary<string, string>
{
{"CompilerVersion", "v3.5"}
};
//تعیین اینکه کد ما سی شارپ است
CSharpCodeProvider provider = new CSharpCodeProvider(providerOptions);

//تعیین اینکه خروجی یک فایل اجرایی است بعلاوه مشخص سازی محل ذخیره سازی فایل نهایی
CompilerParameters compilerParams = new CompilerParameters
{
OutputAssembly = "D:\\Foo.EXE",
GenerateExecutable = true
};

//عملیات کامپایل در اینجا صورت می‌گیرد
CompilerResults results = provider.CompileAssemblyFromSource(compilerParams, source);

//اگر خطایی وجود داشته باشد نمایش داده خواهد شد
Console.WriteLine("Number of Errors: {0}", results.Errors.Count);
foreach (CompilerError err in results.Errors)
{
Console.WriteLine("ERROR {0}", err.ErrorText);
}
}

static void Main(string[] args)
{
compileIt1();

Console.WriteLine("Press a key...");
Console.ReadKey();
}
}
}
مثال 2:
کد مورد نظر را به صورت یک فایل dll کامپایل کنید.
برای این منظور تمامی مراحل مانند قبل است فقط GenerateExecutable ذکر شده به false تنظیم شده و نام خروجی نیز به foo.dll باید تنظیم شود.


مثال 3:
کد مورد نظر را در حافظه کامپایل کرده (خروجی dll یا exe نمی‌خواهیم)، سپس متد SayHello آن را به صورت پویا فراخوانی نموده و خروجی را نمایش دهید.
در این حالت روش کار همانند مثال 1 است با این تفاوت که GenerateInMemory = true و GenerateExecutable = false تنظیم می‌شوند. همچنین جهت دسترسی به متد کلاس ذکر شده،‌ از قابلیت‌های ریفلکشن موجود در دات نت فریم ورک استفاده خواهد شد.

using System;
using System.Collections.Generic;
using Microsoft.CSharp;
using System.CodeDom.Compiler;
using System.Reflection;

namespace compilerTest
{
class Program
{
static void compileIt2()
{
//سورس کد ما جهت کامپایل
string source =
@"
namespace Foo
{
public class Bar
{
static void Main(string[] args)
{
Bar.SayHello();
}

public static void SayHello()
{
System.Console.WriteLine(""Hello World"");
}
}
}
";

//تعیین نگارش کامپایلر مورد استفاده
Dictionary<string, string> providerOptions = new Dictionary<string, string>
{
{"CompilerVersion", "v3.5"}
};
//تعیین اینکه کد ما سی شارپ است
CSharpCodeProvider provider = new CSharpCodeProvider(providerOptions);

//نحوه تعیین مشخص سازی کامپایل در حافظه
CompilerParameters compilerParams = new CompilerParameters
{
GenerateInMemory = true,
GenerateExecutable = false
};

//عملیات کامپایل در اینجا صورت می‌گیرد
CompilerResults results = provider.CompileAssemblyFromSource(compilerParams, source);

// اگر خطایی در کامپایل وجود نداشت متد دلخواه را فراخوانی می‌کنیم
if (results.Errors.Count == 0)
{
//استفاده از ریفلکشن برای دسترسی به متد و فراخوانی آن
Type type = results.CompiledAssembly.GetType("Foo.Bar");
MethodInfo method = type.GetMethod("SayHello");
method.Invoke(null, null);
}
}


static void Main(string[] args)
{
compileIt2();

Console.WriteLine("Press a key...");
Console.ReadKey();
}
}
}
نکته: نحوه‌ی استفاده از اسمبلی‌های دیگر در رشته سورس کد خود
مثال:
اگر رشته سورس ما به صورت زیر بوده و از اسمبلی System.Drawing.Dll نیز کمک گرفته باشد،‌

string source =
@"
namespace Foo
{

public class Bar
{
static void Main(string[] args)
{
Bar.SayHello();
}

public static void SayHello()
{
System.Console.WriteLine(""Hello World"");
var r = new System.Drawing.Rectangle(0,0,100,100);
System.Console.WriteLine(r);
}
}
}
";
هنگام کامپایل آن توسط روش مثال یک، با خطای زیر مواجه خواهیم شد.

Number of Errors: 1
ERROR The type or namespace name 'Drawing' does not exist in the namespace 'System' (are you missing an assembly reference?)

برای رفع این مشکل و معرفی این اسمبلی،‌ سطر زیر باید پس از تعریف compilerParams اضافه شود.

compilerParams.ReferencedAssemblies.Add("System.Drawing.Dll");
اکنون کد کامپایل شده و مشکلی نخواهد داشت.
نمونه‌ای دیگر از این دست، استفاده از LINQ می‌باشد. در این حالت اسمبلی System.Core.Dll نیز به روش ذکر شده باید معرفی گردد تا مشکلی در کامپایل کد رخ ندهد.


کاربردها:
1- استفاده در ابزارهای تولید کد (برای مثال در برنامه Linqer از این قابلیت استفاده می‌شود)
2- استفاده‌های امنیتی (ایجاد روش‌های تولید یک سریال به صورت پویا و کامپایل پویای کد مربوطه در حافظه‌ای محافظت شده)
3- استفاده جهت مقاصد محاسباتی پیشرفته
4- دادن اجازه‌ی کد نویسی به کاربران برنامه‌ی خود (شبیه به سیستم‌های ماکرو و اسکریپت نویسی موجود)
و ...

مطالب
ارتقاء به ASP.NET Core 1.0 - قسمت 8 - فعال سازی ASP.NET MVC
پیشنیازهای بحث (از قسمت 8 به بعد این سری)
اگر پیشتر سابقه‌ی کار کردن با ASP.NET MVC را ندارید، نیاز است «15 مورد» ابتدایی مطالب ASP.NET MVC سایت را پیش از ادامه‌ی این سری مطالعه کنید؛ از این جهت که این سری از مطالب «ارتقاء» نام دارند و نه «بازنویسی مجدد». دراینجا بیشتر تفاوت‌ها و روش‌های تبدیل کدهای قدیمی، به جدید را بررسی خواهیم کرد؛ تا اینکه بخواهیم تمام مطالبی را که وجود دارند از صفر بازنویسی کنیم.


فعال سازی ASP.NET MVC

تا اینجا خروجی برنامه را صرفا توسط میان افزار app.Run نمایش دادیم. اما در نهایت می‌خواهیم یک برنامه‌ی ASP.NET MVC را برفراز ASP.NET Core 1.0 اجرا کنیم و این قابلیت نیز به صورت پیش فرض غیرفعال است. برای فعال سازی آن نیاز است ابتدا بسته‌ی نیوگت آن‌را نصب کرد. سپس سرویس‌های مرتبط با آن‌را ثبت و معرفی نمود و در آخر میان افزار خاص آن‌را فعال کرد.


نصب وابستگی‌های ASP.NET MVC

برای این منظور بر روی گره references کلیک راست کرده و گزینه‌ی manage nuget packages را انتخاب کنید. سپس در برگه‌ی browse آن Microsoft.AspNetCore.Mvc را جستجو کرده و نصب نمائید:


انجام این مراحل معادل هستند با افزودن یک سطر ذیل به فایل project.json برنامه:
 {
    "dependencies": {
      //same as before  
      "Microsoft.AspNetCore.Mvc": "1.0.0"
 },


تنظیم سرویس‌ها و میان افزارهای ASP.NET MVC

پس از نصب بسته‌ی نیوگت ASP.NET MVC، دو تنظیم ذیل در فایل آغازین برنامه، برای شروع به کار با ASP.NET MVC کفایت می‌کنند:
الف) ثبت یکجای سرویس‌های ASP.NET MVC
public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc();

ب) معرفی میان افزار ASP.NET MVC
public void Configure(IApplicationBuilder app)
{
   app.UseFileServer();
   app.UseMvcWithDefaultRoute();
در مورد متد UseFileServer در مطلب «ارتقاء به ASP.NET Core 1.0 - قسمت 4 - فعال سازی پردازش فایل‌های استاتیک» بیشتر بحث شد.
در اینجا دو متد UseMvc و UseMvcWithDefaultRoute را داریم. اولی، امکان تعریف مسیریابی‌های سفارشی را میسر می‌کند و دومی به همراه یک مسیریابی پیش فرض است.


افزودن اولین کنترلر برنامه و معرفی POCO Controllers


در ویژوال استودیو بر روی نام پروژه کلیک راست کرده و پوشه‌ی جدیدی را به نام کنترلر اضافه کنید (تصویر فوق). سپس به این پوشه کلاس جدید HomeController را با این محتوا اضافه کنید:
namespace Core1RtmEmptyTest.Controllers
{
    public class HomeController
    {
        public string Index()
        {
            return "Running a POCO controller!";
        }
    }
}
در ادامه برای اینکه فایل index.html موجود در پوشه‌ی wwwroot بجای محتوای اکشن متد Index ما نمایش داده نشود (با توجه به تقدم و تاخر میان افزارهای ثبت شده‌ی در کلاس آغازین برنامه)، این فایل را حذف کره و یا تغییر نام دهید.
سپس برنامه را اجرا کنید. این خروجی باید قابل مشاهده باشد:


اگر با نگارش‌های قبلی ASP.NET MVC کار کرده باشید، تفاوت این کنترلر با آن‌ها، در عدم ارث بری آن از کلاس پایه‌ی Controller است. به همین جهت به آن POCO Controller نیز می‌گویند (plain old C#/CLR object).
در ASP.NET Core، همینقدر که یک کلاس public غیر abstract را که نامش به Controller ختم شود، داشته باشید و این کلاس در اسمبلی باشد که ارجاعی را به وابستگی‌های ASP.NET MVC داشته باشد، به عنوان یک کنترلر معتبر شناخته شده و مورد استفاده قرار خواهد گرفت. در نگارش‌های قبلی، شرط ارث بری از کلاس پایه Controller نیز الزامی بود؛ اما در اینجا خیر. هدف از آن نیز کاهش سربارهای وهله سازی یک کنترلر است. اگر صرفا می‌خواهید یک شیء را به صورت JSON بازگشت دهید، شاید وهله سازی یک کلاس ساده، بسیار بسیار سریعتر از نمونه سازی یک کلاس مشتق شده‌ی از Controller، به همراه تمام وابستگی‌های آن باشد.

 البته هنوز هم مانند قبل، کنترلرهای مشتق شده‌ی از کلاس پایه‌ی Controller قابل تعریف هستند:
using Microsoft.AspNetCore.Mvc;
 
namespace Core1RtmEmptyTest.Controllers
{
    public class AboutController : Controller
    {
        public IActionResult Index()
        {
            return Content("Hello from DNT!");
        }
    }
}
با این خروجی:


تفاوت دیگری را که ملاحظه می‌کنید، خروجی IActionResult بجای ActionResult نگارش‌های قبلی است. در اینجا هنوز هم ActionResult را می‌توان بکار برد و اینبار ActionResult، پیاده سازی پیش فرض اینترفیس IActionResult است.
و اگر بخواهیم در POCO Controllers شبیه به return Content فوق را پیاده سازی کنیم، نیاز است تا تمام جزئیات را از ابتدا پیاده سازی کنیم (چون کلاس پایه و ساده ساز Controller در اختیار ما نیست):
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
 
namespace Core1RtmEmptyTest.Controllers
{
    public class HomeController
    {
        [ActionContext]
        public ActionContext ActionContext { get; set; }
 
        public HttpContext HttpContext => ActionContext.HttpContext;
 
        public string Index()
        {
            return "Running a POCO controller!";
        }
 
        public IActionResult About()
        {
            return new ContentResult
            {
                Content = "Hello from DNT!",
                ContentType = "text/plain; charset=utf-8"
            };
        }
    }
}
همانطور که ملاحظه می‌کنید اینبار بجای return Content ساده، باید وهله سازی شیء ContentResult از ابتدا صورت گیرد؛ به همراه تمام جزئیات آن.
به علاوه در اینجا نحوه‌ی دسترسی به HttpContext را هم مشاهده می‌کنید. ویژگی ActionContext سبب تزریق اطلاعات آن به کنترلر جاری شده و سپس از طریق آن می‌توان به HttpContext و تمام قابلیت‌های آن دسترسی یافت.
اینجا است که می‌توان میزان سبکی و سریعتر بودن POCO Controllers را احساس کرد. شاید در کنترلری نیاز به این وابستگی‌ها نداشته باشید. اما زمانیکه کنترلری از کلاس پایه‌ی Controller مشتق می‌شود، تمام این وابستگی‌ها را به صورت پیش فرض و حتی در صورت عدم استفاده، در اختیار خواهد داشت و این در اختیار داشتن یعنی وهله سازی شدن تمام وابستگی‌های مرتبط با شیء پایه‌ی Controller. به همین جهت است که POCO Controllers بسیار سبک‌تر و سریع‌تر از کنترلرهای متداول مشتق شده‌ی از کلاس پایه‌ی Controller عمل می‌کنند.
مطالب
Value Types ارجاعی در C# 7.2
در C# 7.2 می‌توان با value types (مانند structs) همانند reference types (مانند کلاس‌ها) رفتار کرد. جائیکه کارآیی برنامه بسیار حائز اهمیت باشد (مانند بازی‌ها)، استفاده از structs و value types بسیار مرسوم است؛ از این جهت که این نوع‌ها بر روی heap تخصیص داده نمی‌شوند. اما مشکل آن‌ها این است که زمانیکه به متدها ارسال می‌شوند، مقدار آن‌ها ارسال خواهد شد و برای این منظور نیاز به ایجاد یک کپی جدید از آن‌ها می‌باشد. برای رفع این مشکل و کاهش سربار کپی کردن اشیاء، اکنون در C# 7.2 می‌توان value types را همانند reference types به متدها ارسال کرد.


واژه‌ی کلیدی جدید in

C# 7.2‌، واژه‌ی کلیدی جدیدی را به نام in جهت تعریف پارامترها، معرفی کرده‌است. زمانیکه از آن استفاده می‌شود به این معنا است که value type ارسالی به آن، توسط ارجاعی از آن، در اختیار متد قرار می‌گیرد و نه توسط مقدار کپی شده‌ی آن (حالت پیش‌فرض) و همچنین متد استفاده کننده‌ی از آن، مقدار این شیء را تغییر نمی‌دهد.
واژه‌ی کلیدی in مکمل واژه‌های کلیدی ref و out است که پیشتر به همراه زبان #C ارائه شده بودند:
- واژه‌ی کلیدی out: مقدار آرگومان مزین شده‌ی توسط آن، باید درون متد تنظیم شود و صرفا کاربرد ارائه‌ی یک خروجی اضافه‌تر توسط آن متد را دارد.
- واژه‌ی کلیدی ref: مقدار آرگومان مزین شده‌ی توسط آن، ممکن است درون متد تنظیم شود، یا خیر و همچنین توسط ارجاع به آن منتقل می‌شود.
- واژه‌ی کلیدی in: مقدار آرگومان مزین شده‌ی توسط آن، درون متد تغییر نخواهد کرد و همچنین توسط ارجاع به آن منتقل می‌شود.

برای مثال اگر پارامترهای value type متد زیر را از نوع in معرفی کنیم، امکان تغییر مقدار آن‌ها درون متد وجود نخواهد داشت:
public static int Add(in int number1, in int number2)
{
   number1 = 5; // Cannot assign to variable 'in int' because it is a readonly variable
   return number1 + number2;
}
و کامپایلر با صدور خطای readonly بودن پارامتر number1، از انجام اینکار جلوگیری می‌کند


واژه‌ی کلیدی جدید in تا چه اندازه‌ای بر روی کارآیی برنامه تاثیر دارد؟

زمانیکه یک value type را به متدی ارسال می‌کنیم، ابتدا به مکان جدیدی از حافظه کپی شده و سپس مقدار clone شده‌ی آن، به متد ارسال می‌شود. با استفاده از واژه‌ی کلیدی in، دقیقا همان ارجاع به مقدار اولیه، به متد ارسال خواهد شد؛ بدون ایجاد کپی اضافه‌تری از آن. برای بررسی تاثیر این عملیات بر روی کارآیی برنامه، می‌توان از BenchmarkDotNet استفاده کرد. برای این منظور ابتدا ارجاعی را به BenchmarkDotNet اضافه می‌کنیم:
<ItemGroup>
     <PackageReference Include="BenchmarkDotNet" Version="0.10.12" />
</ItemGroup>
سپس متدهایی را که قرار است کارآیی آن‌ها بررسی شوند، با ویژگی Benchmark مزین خواهیم کرد:
using BenchmarkDotNet.Attributes;

namespace CS72Tests
{
    public struct Input
    {
        public decimal Number1;
        public decimal Number2;
    }

    [MemoryDiagnoser]
    public class InBenchmarking
    {
        const int loops = 50000000;
        Input inputInstance = new Input();

        [Benchmark(Baseline = true)]
        public decimal RunNormalLoop_Pass_By_Value()
        {
            decimal result = 0M;
            for (int i = 0; i < loops; i++)
            {
                result = Run(inputInstance);
            }
            return result;
        }

        [Benchmark]
        public decimal RunInLoop_Pass_By_Reference()
        {
            decimal result = 0M;
            for (int i = 0; i < loops; i++)
            {
                result = RunIn(in inputInstance);
            }
            return result;
        }

        public decimal Run(Input input)
        {
            return input.Number1;
        }

        public decimal RunIn(in Input input)
        {
            return input.Number1;
        }
    }
}
در آخر برای اجرای آن خواهیم داشت:
static void Main(string[] args)
{
    var summary = BenchmarkRunner.Run<InBenchmarking>();
و در این حالت برنامه را باید توسط دستور «dotnet run -c release» اجرا کرد (اندازه گیری کارآیی در حالت release و نه دیباگ پیش‌فرض)
با این خروجی نهایی:
                      Method |      Mean |    Error |   StdDev | Scaled | Allocated |
---------------------------- |----------:|---------:|---------:|-------:|----------:|
 RunNormalLoop_Pass_By_Value | 280.04 ms | 2.219 ms | 1.733 ms |   1.00 |       0 B |
 RunInLoop_Pass_By_Reference |  91.75 ms | 1.733 ms | 1.780 ms |   0.33 |       0 B |
همانطور که ملاحظه می‌کنید، کارآیی برنامه در حالت استفاده‌ی از پارامترهای in، حداقل 3 برابر شده‌است.


امکان استفاده‌ی از واژه‌ی کلیدی in در حین تعریف متدهای الحاقی

در حین تعریف متدهای الحاقی، واژه‌ی کلیدی in باید پیش از واژه‌ی کلیدی this ذکر شود:
    public static class Factorial
    {
        public static int Calculate(in this int num)
        {
            int result = 1;
            for (int i = num; i > 1; i--)
                result *= i;

            return result;
        }
    }
در این حالت اگر برنامه را به صورت زیر اجرا کنیم (یکبار با ذکر صریح in، بار دیگر بدون in و یکبار هم به صورت فراخوانی متد الحاقی بر روی عدد):
int num = 3;
Console.WriteLine($"(in num) -> {Factorial.Calculate(in num)}");
Console.WriteLine($"(num) -> {Factorial.Calculate(num)}");
Console.WriteLine($"num. -> {num.Calculate()}");
خروجی‌های ذیل را دریافت خواهیم کرد:
(in num) -> 6
(num) -> 6
num. -> 6
به عبارتی حین فراخوانی و استفاده‌ی از متدی که پارامتر آن به صورت in تعریف شده‌است، ذکر in ضروری نیست.

و به طور کلی استفاده‌ی از in در مکان‌های ذیل مجاز است:
• methods
• delegates
• lambdas
• local functions
• indexers
• operators
 

محدودیت‌های استفاده‌ی از پارامترهای in

الف) محدودیت استفاده از پارامترهای in در تعریف overloads
مثال زیر را در نظر بگیرید:
    public class CX
    {
        public void A(Input a)
        {
            Console.WriteLine("int a");
        }

        public void A(in Input a)
        {
            Console.WriteLine("in int a");
        }
    }
در اینجا overloadهای تعریف شده‌ی متد A تنها در ذکر واژه‌ی کلیدی in یا modifier متفاوت هستند.
اگر سعی کنیم وهله‌ای از این کلاس را ایجاد کرده و از متدهای A آن استفاده کنیم:
    public class Y
    {
        public void Test()
        {
            var inputInstance = new Input();
            var cx = new CX();
            cx.A(inputInstance); // The call is ambiguous between the following methods or properties: 'CX.A(Input)' and 'CX.A(in Input)'
        }
    }
خطای کامپایلر مبهم بودن متد A مورد استفاده صادر خواهد شد. یعنی نمی‌توان overload ایی را تعریف کرد که تنها در modifier از نوع in با دیگری متفاوت باشد؛ چون ذکر in در حین فراخوانی متد، اختیاری است.

ب) پارامترهای از نوع in را در متدهای iterator نمی‌توان استفاده کرد:
public IEnumerable<int> B(in int a) // Iterators cannot have ref or out parameters
{
   Console.WriteLine("in int a");
   yield return 1;
}

ج) پارامترهای از نوع in را در متدهای async نمی‌توان استفاده کرد:
public async Task C(in int a) // Async methods cannot have ref or out parameters
{
   await Task.Delay(1000);
}


تاثیر کار با متدهای داخلی تغییر دهنده‌ی وضعیت یک struct

مثال زیر را درنظر بگیرید. به نظر شما خروجی آن چیست؟
using System;

namespace CS72Tests
{
    struct MyStruct
    {
        public int MyValue { get; set; }

        public void UpdateMyValue(int value)
        {
            MyValue = value;
        }
    }

    public static class TestInStructs
    {
        public static void Run()
        {
            var myStruct = new MyStruct();
            myStruct.UpdateMyValue(1);

            UpdateMyValue(myStruct);

            Console.WriteLine(myStruct.MyValue);
        }

        static void UpdateMyValue(in MyStruct myStruct)
        {
            myStruct.UpdateMyValue(5);
        }
    }
}
در اینجا اگر متد TestInStructs.Run را اجرا کنیم، خروجی آن، نمایش عدد 1 خواهد بود.
در ابتدا مقدار struct را به 1 تنظیم و سپس ارجاع آن‌را به متدی دیگر که مقدار آن‌را به 5 تنظیم می‌کند، ارسال کردیم. در این حالت برنامه بدون مشکل کامپایل و اجرا می‌شود.  علت اینجا است که کامپایلر #C زمانیکه متدی را در داخل یک struct فراخوانی می‌کند، یک clone از آن struct را ایجاد کرده و متد را بر روی آن clone اجرا می‌کند؛ چون نمی‌داند که آیا این متد وضعیت و مقدار این struct را تغییر می‌دهد یا خیر. در این حالت کپی اصلی بدون تغییر باقی می‌ماند (در نهایت عدد 1 را مشاهده خواهیم کرد)، اما در آخر فراخوان، ارجاعی از struct را دریافت نکرده و بر روی کپی آن کار می‌کند. بنابراین مزیت بهبود کارآیی، از دست خواهد رفت.

البته در اینجا اگر می‌خواستیم مقدار MyValue را مستقیما تغییر دهیم، کامپایلر از آن جلوگیری می‌کرد و این کد هیچگاه کامپایل نمی‌شد:
static void UpdateMyValue(in MyStruct myStruct)
{
   myStruct.MyValue = 5; // Cannot assign to a member of variable 'in MyStruct' because it is a readonly variable
   myStruct.UpdateMyValue(5);
}
مطالب
INPC استاندارد با بهره گیری از صفت CallerMemberName
یکی از Attribute‌های بسیار کاربردی که در سی شارپ 5 اضافه شد CallerMemberNameAttribute بود. این صفت به یک متد اجازه میدهد که از فراخواننده‌ی خود مطلع شود. این صفت را می‌توان بر روی یک پارامتر انتخابی که مقدار پیش‌فرضی دارد اعمال نمود.

استفاده از این صفت هم بسیار ساده است:

private void A ( [CallerMemberName] string callerName = "") 
{
  Console.WriteLine("Caller is " + callerName);
}

private static void B()
{
        // let's call A
        A();
}
در کد فوق، متد A به راحتی می‌تواند بفهمد چه کسی آن را فراخوانی کرده است. از جمله کاربردهای این صفت در ردیابی و خطایابی است.

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

معمولا هنگام پیاده سازی INotifyPropertyChanged کدی شبیه به این را می‌نویسیم:

    public class PersonViewModel : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        private void OnPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }

        private string name;
        public string Name
        {
            get { return name; }
            set
            {
                this.name = value;
                OnPropertyChanged("Name");
            }
        }
    }

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

برای مثال فرض کنید پس از نوشتن کلاس PersonViewModel تصمیم می‌گیرد نام ویژگی Name را به FirstName تغییر دهید؛ چرا که می‌خواهید اجزای نام یک شخص را به صورت مجزا نگهداری و پردازش کنید. پس احتمالا با زدن کلید F2 روی فیلد name آن را به firstName و ویژگی Name را به FirstName تغییر نام می‌دهید. همانند کد زیر:

private string firstName;
public string FirstName
{
            get { return firstName; }
            set
            {
                this.firstName = value;
                OnPropertyChanged("Name");
            }
}

برنامه را کامپایل کرده و در کمال تعجب می‌بینید که بخشی از برنامه درست رفتار نمی‌کند و تغییراتی که در نام کوچک شخص توسط کاربر ایجاد می‌شود به درستی بروزرسانی نمی‌شوند. علت ساده است: ما کد را به صورت اتوماتیک Refactor کرده ایم و گزینه‌ی Include String را در حین Refactor، در حالت پیشفرض غیرفعال رها کرده‌ایم. پس جای تعجبی ندارد که در هر جای کد که رشته‌ای به نام "Name" با ماهیت نام شخص داشته ایم، دست نخورده باقی مانده است. در واقع در کد تغییر یافته، هنگام تغییر FirstName، ما به سیستم گزارش می‌کنیم که ویژگی Name (که اصلا وجود ندارد) تغییر یافته است و این یعنی خطا.

حال احتمال بروز این خطا را در ViewModel هایی با ده‌ها ویژگی و ترکیب‌های مختلف در نظر بگیرید. پس کاملا محتمل است و برای خیلی از دوستان این اتفاق رخ داده است.

و اما راه حل چیست؟ به کارگیری صفت CallerMemberName

بهتر است که یک کلاس انتزاعی برای تمام ViewModel‌های خود داشته باشیم و پیاده سازی جدید INPC را در درون آن قرار دهیم تا براحتی VM‌های ما از آن مشتق شوند:

public abstract class ViewModelBase : INotifyPropertyChanged
{
        public event PropertyChangedEventHandler PropertyChanged;

        protected void OnPropertyChanged([CallerMemberName] string propertyName = "")
        {
            OnPropertyChangedExplicit(propertyName);
        }

        protected void OnPropertyChanged<TProperty>(Expression<Func<TProperty>> projection)
        {
            var memberExpression = (MemberExpression)projection.Body;
            OnPropertyChangedExplicit(memberExpression.Member.Name);
        }

        void OnPropertyChangedExplicit(string propertyName)
        {
            this.CheckPropertyName(propertyName);

            PropertyChangedEventHandler handler = this.PropertyChanged;

            if (handler != null)
            {
                var e = new PropertyChangedEventArgs(propertyName);
                handler(this, e);
            }
        }

        #region Check property name

        [Conditional("DEBUG")]
        [DebuggerStepThrough]
        public void CheckPropertyName(string propertyName)
        {
            if (TypeDescriptor.GetProperties(this)[propertyName] == null)
                throw new Exception(String.Format("Could not find property \"{0}\"", propertyName));
        }

        #endregion // Check property name
}

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

پس کلاس PersonViewModel را به صورت زیر می‌توانیم اصلاح و تکمیل کنیم:

public class PersonViewModel : ViewModelBase
{
        private string firstName;
        public string FirstName
        {
            get { return firstName; }
            set
            {
                this.firstName = value;

                OnPropertyChanged();
                OnPropertyChanged(() => this.FullName);
            }
        }

        private string lastName;
        public string LastName
        {
            get { return lastName; }
            set
            {
                this.lastName = value;

                OnPropertyChanged();
                OnPropertyChanged(() => this.FullName);
            }
        }

        public string FullName
        {
            get { return string.Format("{0} {1}", FirstName, LastName); }
        }
}
همانطور که می‌بینید متد OnPropertyChanged بدون آرگومان فراخوانی میشود. اکنون اگر شما اقدام به Refactor کردن کد خود بکنید دیگر نگرانی از بابت تغییر نکردن رشته‌ها و کامنت‌ها نخواهید داشت و مطمئن هستید، نام ویژگی هر چیزی که باشد، به صورت خودکار به متد ارسال خواهد شد.

کلاس ViewModelBase یک پیاده سازی دیگر از OnPropetyChanged هم دارد که به شما اجازه می‌دهد با استفاده دستورات لامبدا، OnPropertyChanged را برای هر یک از اعضای دلخواه کلاس نیز فراخوانی کنید. همانطور که در مثال فوق می‌بینید، تغییرات نام خانوادگی در نام کامل شخص نیز اثرگذار است. در نتیجه به وسیله‌ی یک Func به راحتی بیان می‌کنیم که FullName هم تغییر کرده است و اطلاع رسانی برای آن نیز باید صورت پذیرد.

برای استفاده از صفت CallerMemberName باید دات نت هدف خود را 4.5 یا 4.6 قرار دهید.

ارجاع:
Raise INPC witout string name
مطالب
نمایش بلادرنگ اعلامی به تمام کاربران در هنگام درج یک رکورد جدید
در ادامه می‌خواهیم اعلام عمومی نمایش افزوده شدن یک پیام جدید را بعد از ثبت رکوردی جدید، به تمامی کاربران متصل به سیستم ارسال کنیم. پیش نیاز مطلب جاری موارد زیر می‌باشند:
namespace ShowAlertSignalR.Models
{
    public class Product
    {
        public int Id { get; set; }
        public string Title { get; set; }
        public string Description { get; set; }
        public float Price { get; set; }
        public Category Category { get; set; }

    }

    public enum Category
    {
        [Display(Name = "دسته بندی اول")]
        Cat1,
        [Display(Name = "دسته بندی دوم")]
        Cat2,
        [Display(Name = "دسته بندی سوم")]
        Cat3
    }
}
در اینجا مدل ما شامل عنوان، توضیح، قیمت و یک enum برای دسته‌بندی یک محصول ساده می‌باشد.
کلاس context نیز به صورت زیر می‌باشد:
namespace ShowAlertSignalR.Models
{
    public class ProductDbContext : DbContext
    {
        public ProductDbContext() : base("productSample")
        {
            Database.Log = sql => Debug.Write(sql);
        }
        public DbSet<Product> Products { get; set; }
    }
}
همانطور که در ابتدا عنوان شد، می‌خواهیم بعد از ثبت یک رکورد جدید، پیامی عمومی به تمامی کاربران متصل به سایت نمایش داده شود. در کد زیر اکشن متد Create را مشاهده می‌کنید: 
[HttpPost]
        [ValidateAntiForgeryToken]
        public ActionResult Create(Product product)
        {
            if (ModelState.IsValid)
            {
                db.Products.Add(product);
                db.SaveChanges();
                return RedirectToAction("Index");
            }

            return View(product);
        }
می‌توانیم از ViewBag برای اینکار استفاده کنیم؛ به طوریکه یک پارامتر از نوع bool برای متد Index تعریف کرده و سپس مقدار آن را درون این شیء ViewBag انتقال دهیم، این متغییر بیانگر حالتی است که آیا اطلاعات جدیدی برای نمایش وجود دارد یا خیر؟ بنابراین اکشن متد Index را به اینصورت تعریف می‌کنیم:
public ActionResult Index(bool notifyUsers = false)
        {
            ViewBag.NotifyUsers = notifyUsers;
            return View(db.Products.ToList());
        }
در اینجا مقدار پیش‌فرض این متغیر، false می‌باشد. یعنی اطلاعات جدیدی برای نمایش موجود نمی‌باشد. در نتیجه اکشن متد Create را به صورتی تغییر می‌دهیم که بعد از درج رکورد موردنظر و هدایت کاربر به صفحه‌ی Index، مقدار این متغییر به true تنظیم شود:
[HttpPost]
        [ValidateAntiForgeryToken]
        public ActionResult Create(Product product)
        {
            if (ModelState.IsValid)
            {
                db.Products.Add(product);
                db.SaveChanges();
                return RedirectToAction("Index", new { notifyUsers = true });
            }

            return View(product);
        }
قدم بعدی ایجاد یک هاب SignalR می‌باشد:
namespace ShowAlertSignalR.Hubs
{
    public class NotificationHub : Hub
    {
        public void SendNotification()
        {
            Clients.Others.ShowNotification();
        }
    }
}
در ادامه کدهای سمت کلاینت را برای هاب فوق، داخل ویوی Index اضافه می‌کنیم:
@section scripts
{
    
    <script src="~/Scripts/jquery.signalR-2.0.2.min.js"></script>
    <script src="~/signalr/hubs"></script>
    <script>

        var notify = $.connection.notificationHub;
        notify.client.showNotification = function() {
            $('#result').append("<div class='alert alert-info alert-dismissable'>" +
                "<button type='button' class='close' data-dismiss='alert' aria-hidden='true'>&times;</button>" +
            "رکورد جدیدی هم اکنون ثبت گردید، برای مشاهده آن صفحه را بروزرسانی کنید" + "</div>");
        };
        $.connection.hub.start().done(function() {
            @{
                if (ViewBag.NotifyUsers)
                {
                    <text>notify.server.sendNotification();</text>
                }
            }
        });
    </script>
}
همانطور که در کدهای فوق مشاهده می‌کنید، بعد از اینکه اتصال با موفقیت برقرار شد (درون متد done) شرط چک کردن متغییر NotifyUsers را بررسی کرده‌ایم. یعنی در این حالت اگر مقدار آن true بود، متد درون هاب را فراخوانی کرده‌ایم. در نهایت پیام به یک div با آی‌دی result اضافه شده است.
لازم به ذکر است برای حالت‌های حذف و به‌روزرسانی نیز روال کار به همین صورت می‌باشد.
سورس مثال جاری : ShowAlertSignalR.zip
نظرات مطالب
الگوی طراحی Factory Method به همراه مثال
اینکه نمیتوان مستقیما از کلاس abstract استفاده کرد که واضح است. سوال بنده این است که چه لزومی برای abstract کردن این کلاس وجود داشت؟ در حالی که کلاس مشتق شده از آن تنها همان یک متد کلاس والد را دارد. با این وجود چرا کلاس فرزند را حذف نکردیم و کلاس والد را از abstract خارج کنیم و مستقیما از همان کلاس استفاده کنیم؟
مطالب
آشنایی با الگوی طراحی Template Method
سناریویی وجود دارد که در آن شما می‌خواهید تنها یک کار را انجام دهید، ولی برای انجام آن n روش وجود دارد. برای مثال قصد مرتب سازی دارید و برای اینکار روش‌های مختلفی وجود دارند. برای حل این مساله پیشتر از الگوی طراحی استراتژی استفاده نمودیم.(مطالعه بیشتر در مورد الگوی طراحی استراتژی)
حال به سناریویی برخورد کردیم که بصورت زیر است:
می‌خواهیم یک کار را انجام دهیم ولی برای انجام این کار تنها برخی بخش‌های کار با هم متفاوت هستند. برای مثال قصد تولید گزارش و چاپ آن را داریم. در این سناریو خواندن اطلاعات و پردازش آن‌ها رخدادهایی ثابت هستند. ولی اگر بخواهیم گزارش را چاپ کنیم به مشکل می‌خوریم؛ چرا که چاپ گزارش به فرمت اکسل، فرمت و روش خود را دارد و چاپ به فرمت PDF شرایط خود را دارد.
در این سناریو دیگر الگوی طراحی استراتژی جواب نخواهد داد و نیاز داریم با یک الگوی طراحی جدید آشنا بشویم. این الگوی طراحی Template Method نام دارد.
در این الگو یک کلاس انتزاعی داریم به صورت زیر:
    public abstract class DataExporter
    {
        public void ReadData()
        {
            Console.WriteLine("Data is reading from SQL Server Database");
        }

        public void ProcessData()
        {
            Console.WriteLine("Data is processing...!");
        }

        public abstract void PrintData();

        public void GetReport()
        {
            ReadData();
            ProcessData();
            PrintData();
        }
    }
این کلاس abstract، یک متد بنام GetReport دارد که نحوه‌ی انجام کار را مشخص می‌کند. متدهای ReadData و ProcessData نشان می‌دهند که انجام این دو عمل همیشه ثابت هستند (منظور در این سناریو همیشه ثابت هستند). متد PrintData همانطور که مشاهده می‌شود بصورت انتزاعی تعریف شده است، چرا که چاپ عملی است که در هر فرمت دارای خروجی متفاوتی می‌باشد.
لذا در ادامه داریم:
    public class ExcelExporter : DataExporter
    {
        public override void PrintData()
        {
            Console.WriteLine("Data exported to Microsoft Excel!");
        }
    }

    public class PDFExporter : DataExporter
    {
        public override void PrintData()
        {
            Console.WriteLine("Data exported to PDF!");
        }
    }
کلاس ExcelExporter برای چاپ به فرمت اکسل می‌باشد. همانطور که مشاهده می‌شود این کلاس از کلاس انتزاعی DataExporter ارث بری کرده است. این بدین معنا است که کلاس ExcelExporter کارهای ReadData و ProcessData را از کلاس پدر خود می‌گیرد و در ادامه نحوه‌ی چاپ مختص به خود را پیاده می‌کند. همین توضیحات در مورد PDFExporter نیز صادق است.
حال برای استفاده‌ی از این کدها داریم:
            DataExporter dataExporter = new ExcelExporter();
            dataExporter.GetReport();
            Console.WriteLine("****************************");
            dataExporter = new PDFExporter();
            dataExporter.GetReport();
شما شاید بخواهید متدهای ReadData و ExportData و ProcessData را با سطح دسترسی متفاوتی از public تعریف نمایید که در این مقاله به این دلیل که خارج از بحث بود به آنها اشاره نشد و بصورت پیش فرض public در نظر گرفته شد.