مطالب
فعالسازی Windows Authentication در برنامه‌های ASP.NET Core 2.0
اعتبارسنجی مبتنی بر ویندوز، بر اساس قابلیت‌های توکار ویندوز و اختیارات اعطا شده‌ی به کاربر وارد شده‌ی به آن، کار می‌کند. عموما محل استفاده‌ی از آن، در اینترانت داخلی شرکت‌ها است که بر اساس وارد شدن افراد به دومین و اکتیودایرکتوری آن، مجوز استفاده‌ی از گروه‌های کاربری خاص و یا سطوح دسترسی خاصی را پیدا می‌کنند. میان‌افزار اعتبارسنجی ASP.NET Core، علاوه بر پشتیبانی از روش‌های اعتبارسنجی مبتنی بر کوکی‌‌ها و یا توکن‌ها، قابلیت استفاده‌ی از اطلاعات کاربر وارد شده‌ی به ویندوز را نیز جهت اعتبارسنجی او به همراه دارد.


فعالسازی Windows Authentication در IIS

پس از publish برنامه و رعایت مواردی که در مطلب «ارتقاء به ASP.NET Core 1.0 - قسمت 22 - توزیع برنامه توسط IIS» بحث شد، باید به قسمت Authentication برنامه‌ی مدنظر، در کنسول مدیریتی IIS رجوع کرد:


و سپس Windows Authentication را با کلیک راست بر روی آن و انتخاب گزینه‌ی Enable، فعال نمود:


این تنظیم دقیقا معادل افزودن تنظیمات ذیل به فایل web.config برنامه است:
  <system.webServer>
    <security>
      <authentication>
        <anonymousAuthentication enabled="true" />
        <windowsAuthentication enabled="true" />
      </authentication>
    </security>
  </system.webServer>
اما اگر این تنظیمات را به فایل web.config اضافه کنید، پیام و خطای قفل بودن تغییرات مدخل windowsAuthentication را مشاهده خواهید کرد. به همین جهت بهترین راه تغییر آن، همان مراجعه‌ی مستقیم به کنسول مدیریتی IIS است.


فعالسازی Windows Authentication در IIS Express

اگر برای آزمایش می‌خواهید از IIS Express به همراه ویژوال استودیو استفاده کنید، نیاز است فایلی را به نام Properties\launchSettings.json با محتوای ذیل در ریشه‌ی پروژه‌ی خود ایجاد کنید (و یا تغییر دهید):
{
  "iisSettings": {
    "windowsAuthentication": true,
    "anonymousAuthentication": true,
    "iisExpress": {
      "applicationUrl": "http://localhost:3381/",
      "sslPort": 0
    }
  }
}
در اینجا الزامی به خاموش کردن anonymousAuthentication نیست. اگر برنامه‌ی شما قرار است هم توسط کاربران ویندوزی و هم توسط کاربران وارد شده‌ی از طریق اینترنت (و نه صرفا اینترانت داخلی) به برنامه، قابلیت دسترسی داشته باشد، نیاز است anonymousAuthentication به true تنظیم شده باشد (همانند تنظیمی که برای IIS اصلی ذکر شد).


تغییر مهم فایل web.config برنامه جهت هدایت اطلاعات ویندوز به آن

اگر پروژه‌ی شما فایل web.config ندارد، باید آن‌را اضافه کنید؛ با حداقل محتوای ذیل:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <system.webServer>
    <handlers>
      <add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModule" resourceType="Unspecified"/>
    </handlers>
    <aspNetCore processPath="%LAUNCHER_PATH%" arguments="%LAUNCHER_ARGS%" 
            stdoutLogEnabled="false" stdoutLogFile=".\logs\stdout" 
            forwardWindowsAuthToken="true"/>
  </system.webServer>
</configuration>
که در آن خاصیت forwardWindowsAuthToken، حتما به true تنظیم شده باشد. این مورد است که کار اعتبارسنجی و یکی‌سازی اطلاعات کاربر وارد شده به ویندوز و ارسال آن‌را به میان‌افزار IIS برنامه‌ی ASP.NET Core انجام می‌دهد. بدون تنظیم آن، با مراجعه‌ی به سایت، شاهد نمایش صفحه‌ی login ویندوز خواهید بود.


تنظیمات برنامه‌ی ASP.NET Core جهت فعالسازی Windows Authentication

پس از فعالسازی windowsAuthentication در IIS و همچنین تنظیم forwardWindowsAuthToken به true در فایل web.config برنامه، اکنون جهت استفاده‌ی از windowsAuthentication دو روش وجود دارد:

الف) تنظیمات مخصوص برنامه‌های Self host
اگر برنامه‌ی وب شما قرار است به صورت self host ارائه شود (بدون استفاده از IIS)، تنها کافی است در تنظیمات ابتدای برنامه در فایل Program.cs، استفاده‌ی از میان‌افزار HttpSys را ذکر کنید:
namespace ASPNETCore2WindowsAuthentication
{
    public class Program
    {
        public static void Main(string[] args)
        {
            var host = new WebHostBuilder()
                                     .UseKestrel()
                                     .UseContentRoot(Directory.GetCurrentDirectory())
                                     .UseStartup<Startup>()
                                     .UseHttpSys(options => // Just for local tests without IIS, Or self-hosted scenarios on Windows ...
                                     {
                                         options.Authentication.Schemes =
                                              AuthenticationSchemes.Negotiate | AuthenticationSchemes.NTLM;
                                         options.Authentication.AllowAnonymous = true;
                                         //options.UrlPrefixes.Add("http://+:80/");
                                     })
                                     .Build();
            host.Run();
        }
    }
}
در اینجا باید دقت داشت که استفاده‌ی از UseHttpSys با تنظیمات فوق، اعتبارسنجی یکپارچه‌ی با ویندوز را برای برنامه‌های self host خارج از IIS مهیا می‌کند. اگر قرار است برنامه‌ی شما در IIS هاست شود، نیازی به تنظیم فوق ندارید و کاملا اضافی است.


ب) تنظیمات مخصوص برنامه‌هایی که قرار است در IIS هاست شوند

در این‌حالت تنها کافی است UseIISIntegration در تنظیمات ابتدایی برنامه ذکر شود و همانطور که عنوان شد، نیازی به UseHttpSys در این حالت نیست:
namespace ASPNETCore2WindowsAuthentication
{
    public class Program
    {
        public static void Main(string[] args)
        {
            var host = new WebHostBuilder()
                                     .UseKestrel()
                                     .UseContentRoot(Directory.GetCurrentDirectory())
                                     .UseIISIntegration()
                                     .UseDefaultServiceProvider((context, options) =>
                                     {
                                         options.ValidateScopes = context.HostingEnvironment.IsDevelopment();
                                     })
                                     .UseStartup<Startup>()
                                     .Build();
            host.Run();
        }
    }
}


فعالسازی میان‌افزار اعتبارسنجی ASP.NET Core جهت یکپارچه شدن با Windows Authentication

در پایان تنظیمات فعالسازی Windows Authentication نیاز است به فایل Startup.cs برنامه مراجعه کرد و یکبار AddAuthentication را به همراه تنظیم ChallengeScheme آن به IISDefaults افزود:
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc();

            services.Configure<IISOptions>(options =>
            {
                // Sets the HttpContext.User
                // Note: Windows Authentication must also be enabled in IIS for this to work.
                options.AutomaticAuthentication = true;
                options.ForwardClientCertificate = true;
            });
            
            services.AddAuthentication(options =>
            {
                // for both windows and anonymous authentication
                options.DefaultChallengeScheme = IISDefaults.AuthenticationScheme;
            });
        }
برای مثال اگر از ASP.NET Core Identity استفاده می‌کنید، سطر services.AddAuthentication فوق، پس از تنظیمات ابتدایی آن باید ذکر شود؛ هرچند روش فوق کاملا مستقل است از ASP.NET Core Identity و اطلاعات کاربر را از سیستم عامل و اکتیودایرکتوری (در صورت وجود) دریافت می‌کند.


آزمایش برنامه با تدارک یک کنترلر محافظت شده

در اینجا قصد داریم اطلاعات ذیل را توسط تعدادی اکشن متد، نمایش دهیم:
        private string authInfo()
        {
            var claims = new StringBuilder();
            if (User.Identity is ClaimsIdentity claimsIdentity)
            {
                claims.Append("Your claims: \n");
                foreach (var claim in claimsIdentity.Claims)
                {
                    claims.Append(claim.Type + ", ");
                    claims.Append(claim.Value + "\n");
                }
            }

            return $"IsAuthenticated: {User.Identity.IsAuthenticated}; Identity.Name: {User.Identity.Name}; WindowsPrincipal: {(User is WindowsPrincipal)}\n{claims}";
        }
کار آن نمایش نام کاربر، وضعیت لاگین او و همچنین لیست تمام Claims متعلق به او می‌باشد:
namespace ASPNETCore2WindowsAuthentication.Controllers
{
    public class HomeController : Controller
    {
        public IActionResult Index()
        {
            return View();
        }

        [Authorize]
        public IActionResult Windows()
        {
            return Content(authInfo());
        }

        private string authInfo()
        {
            var claims = new StringBuilder();
            if (User.Identity is ClaimsIdentity claimsIdentity)
            {
                claims.Append("Your claims: \n");
                foreach (var claim in claimsIdentity.Claims)
                {
                    claims.Append(claim.Type + ", ");
                    claims.Append(claim.Value + "\n");
                }
            }

            return $"IsAuthenticated: {User.Identity.IsAuthenticated}; Identity.Name: {User.Identity.Name}; WindowsPrincipal: {(User is WindowsPrincipal)}\n{claims}";
        }

        [AllowAnonymous]
        public IActionResult Anonymous()
        {
            return Content(authInfo());
        }

        [Authorize(Roles = "Domain Admins")]
        public IActionResult ForAdmins()
        {
            return Content(authInfo());
        }

        [Authorize(Roles = "Domain Users")]
        public IActionResult ForUsers()
        {
            return Content(authInfo());
        }
    }
}
برای آزمایش برنامه، ابتدا برنامه را توسط دستور ذیل publish می‌کنیم:
 dotnet publish
سپس تنظیمات مخصوص IIS را که در ابتدای بحث عنوان شد، بر روی پوشه‌ی bin\Debug\netcoreapp2.0\publish که محل قرارگیری پیش‌فرض خروجی برنامه است، اعمال می‌کنیم.
اکنون اگر برنامه را در مرورگر مشاهده کنیم، یک چنین خروجی قابل دریافت است:


در اینجا نام کاربر وارد شده‌ی به ویندوز و همچنین لیست تمام Claims او مشاهده می‌شوند. مسیر Home/Windows نیز توسط ویژگی Authorize محافظت شده‌است.
برای محدود کردن دسترسی کاربران به اکشن متدها، توسط گروه‌های دومین و اکتیودایرکتوری، می‌توان به نحو ذیل عمل کرد:
[Authorize(Roles = @"<domain>\<group>")]
//or
[Authorize(Roles = @"<domain>\<group1>,<domain>\<group2>")]
و یا می‌توان بر اساس این نقش‌ها، یک سیاست دسترسی جدید را تعریف کرد:
services.AddAuthorization(options =>
{
   options.AddPolicy("RequireWindowsGroupMembership", policy =>
   {
     policy.RequireAuthenticatedUser();
     policy.RequireRole(@"<domain>\<group>"));  
   }
});
و در آخر از این سیاست دسترسی استفاده نمود:
 [Authorize(Policy = "RequireWindowsGroupMembership")]
و یا با برنامه نویسی نیز می‌توان به صورت ذیل عمل کرد:
[HttpGet("[action]")]
public IActionResult SomeValue()
{
    if (!User.IsInRole(@"Domain\Group")) return StatusCode(403);
    return Ok("Some Value");
}


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

همانطور که در شکل فوق ملاحظه می‌کنید، یک سری Claims حاصل از Windows Authentication در اینجا به شیء User اضافه شده‌اند؛ بدون اینکه برنامه، صفحه‌ی لاگینی داشته باشد و همینقدر که کاربر به ویندوز وارد شده‌است، می‌تواند از برنامه استفاده کند.
اگر نیاز باشد تا Claims خاصی به لیست Claims کاربر جاری اضافه شود، می‌توان از پیاده سازی یک IClaimsTransformation سفارشی استفاده کرد:
    public class ApplicationClaimsTransformation : IClaimsTransformation
    {
        private readonly ILogger<ApplicationClaimsTransformation> _logger;

        public ApplicationClaimsTransformation(ILogger<ApplicationClaimsTransformation> logger)
        {
            _logger = logger;
        }

        public Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal)
        {
            if (!(principal.Identity is ClaimsIdentity identity))
            {
                return Task.FromResult(principal);
            }

            var claims = addExistingUserClaims(identity);
            identity.AddClaims(claims);

            return Task.FromResult(principal);
        }

        private IEnumerable<Claim> addExistingUserClaims(IIdentity identity)
        {
            var claims = new List<Claim>();
            var user = @"VahidPC\Vahid";
            if (identity.Name != user)
            {
                _logger.LogError($"Couldn't find {identity.Name}.");
                return claims;
            }

            claims.Add(new Claim(ClaimTypes.GivenName, user));
            return claims;
        }
    }
و روش ثبت آن نیز در متد ConfigureServices فایل آغازین برنامه به صورت ذیل است:
            services.AddScoped<IClaimsTransformation, ApplicationClaimsTransformation>();
            services.AddAuthentication(options =>
            {
                // for both windows and anonymous authentication
                options.DefaultChallengeScheme = IISDefaults.AuthenticationScheme;
            });
هر زمانیکه کاربری به برنامه وارد شود و متد HttpContext.AuthenticateAsync فراخوانی گردد، متد TransformAsync به صورت خودکار اجرا می‌شود. در اینجا چون forwardWindowsAuthToken به true تنظیم شده‌است، میان‌افزار IIS کار فراخوانی HttpContext.AuthenticateAsync و مقدار دهی شیء User را به صورت خودکار انجام می‌دهد. بنابراین همینقدر که برنامه را اجرا کنیم، شاهد اضافه شدن یک Claim سفارشی جدید به نام ClaimTypes.GivenName که در متد addExistingUserClaims فوق آن‌را اضافه کردیم، خواهیم بود:


به این ترتیب می‌توان لیست Claims ثبت شده‌ی یک کاربر را در یک بانک اطلاعاتی استخراج و به لیست Claims فعلی آن افزود و دسترسی‌های بیشتری را به او اعطاء کرد (فراتر از دسترسی‌های پیش‌فرض سیستم عامل).

برای دسترسی به مقادیر این Claims نیز می‌توان به صورت ذیل عمل کرد:
var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
var userName = User.FindFirstValue(ClaimTypes.Name);
var userName = User.FindFirstValue(ClaimTypes.GivenName);


کدهای کامل این برنامه را از اینجا می‌توانید دریافت کنید: ASPNETCore2WindowsAuthentication.zip
مطالب
ASP.NET MVC #10

آشنایی با روش‌های مختلف ارسال اطلاعات یک درخواست به کنترلر

تا اینجا با روش‌های مختلف ارسال اطلاعات از یک کنترلر به View متناظر آن آشنا شدیم. اما حالت عکس آن چطور؟ مثلا در ASP.NET Web forms، دوبار بر روی یک دکمه کلیک می‌کردیم و در روال رویدادگردان کلیک آن، همانند برنامه‌های ویندوزی، دسترسی به اطلاعات اشیاء قرار گرفته بر روی فرم را داشتیم. در ASP.NET MVC که کلا مفهوم Events را حذف کرده و وب را همانگونه که هست ارائه می‌دهد و به علاوه کنترلرهای آن، ارجاع مستقیمی را به هیچکدام از اشیاء بصری در خود ندارند (برای مثال کنترلر و متدی در آن نمی‌دانند که الان بر روی View آن، یک گرید قرار دارد یا یک دکمه یا اصلا هیچی)، چگونه می‌توان اطلاعاتی را از کاربر دریافت کرد؟
در اینجا حداقل سه روش برای دریافت اطلاعات از کاربر وجود دارد:
الف) استفاده از اشیاء Context مانند HttpContext، Request، RouteData و غیره
ب) به کمک پارامترهای اکشن متدها
ج) با استفاده از ویژگی جدیدی به نام Data Model Binding

یک مثال کاربردی
قصد داریم یک صفحه لاگین ساده را طراحی کنیم تا بتوانیم هر سه حالت ذکر شده فوق را در عمل بررسی نمائیم. بحث HTML Helpers استاندارد ASP.NET MVC را هم که در قسمت قبل شروع کردیم، لابلای توضیحات قسمت جاری و قسمت‌های بعدی با مثال‌های کاربردی دنبال خواهند شد.
بنابراین یک پروژه جدید خالی ASP.NET MVC را شروع کرده و مدلی را به نام Account با محتوای زیر به پوشه Models برنامه اضافه کنید:

namespace MvcApplication6.Models
{
public class Account
{
public string Name { get; set; }
public string Password { get; set; }
}
}

یک کنترلر جدید را هم به نام LoginController به پوشه کنترلرهای برنامه اضافه کنید. بر روی متد Index پیش فرض آن کلیک راست نمائید و یک View خالی را اضافه نمائید.
در ادامه به فایل Global.asax.cs مراجعه کرده و نام کنترلر پیش‌فرض را به Login تغییر دهید تا به محض شروع برنامه در VS.NET، صفحه لاگین ظاهر شود.
کدهای کامل کنترلر لاگین را در ادامه ملاحظه می‌کنید:

using System.Web.Mvc;
using MvcApplication6.Models;

namespace MvcApplication6.Controllers
{
public class LoginController : Controller
{
[HttpGet]
public ActionResult Index()
{
return View(); //Shows the login page
}

[HttpPost]
public ActionResult LoginResult()
{
string name = Request.Form["name"];
string password = Request.Form["password"];

if (name == "Vahid" && password == "123")
ViewBag.Message = "Succeeded";
else
ViewBag.Message = "Failed";

return View("Result");
}

[HttpPost]
[ActionName("LoginResultWithParams")]
public ActionResult LoginResult(string name, string password)
{
if (name == "Vahid" && password == "123")
ViewBag.Message = "Succeeded";
else
ViewBag.Message = "Failed";

return View("Result");
}

[HttpPost]
public ActionResult Login(Account account)
{
if (account.Name == "Vahid" && account.Password == "123")
ViewBag.Message = "Succeeded";
else
ViewBag.Message = "Failed";

return View("Result");
}
}
}

همچنین Viewهای متناظر با این کنترلر هم به شرح زیر هستند:
فایل index.cshtml به نحو زیر تعریف خواهد شد:

@model MvcApplication6.Models.Account
@{
ViewBag.Title = "Index";
}
<h2>
Login</h2>
@using (Html.BeginForm(actionName: "LoginResult", controllerName: "Login"))
{
<fieldset>
<legend>Test LoginResult()</legend>
<p>
Name: @Html.TextBoxFor(m => m.Name)</p>
<p>
Password: @Html.PasswordFor(m => m.Password)</p>
<input type="submit" value="Login" />
</fieldset>
}
@using (Html.BeginForm(actionName: "LoginResultWithParams", controllerName: "Login"))
{
<fieldset>
<legend>Test LoginResult(string name, string password)</legend>
<p>
Name: @Html.TextBoxFor(m => m.Name)</p>
<p>
Password: @Html.PasswordFor(m => m.Password)</p>
<input type="submit" value="Login" />
</fieldset>
}
@using (Html.BeginForm(actionName: "Login", controllerName: "Login"))
{
<fieldset>
<legend>Test Login(Account acc)</legend>
<p>
Name: @Html.TextBoxFor(m => m.Name)</p>
<p>
Password: @Html.PasswordFor(m => m.Password)</p>
<input type="submit" value="Login" />
</fieldset>
}

و فایل result.cshtml هم محتوای زیر را دارد:

@{
ViewBag.Title = "Result";
}
<fieldset>
<legend>Login Result</legend>
<p>
@ViewBag.Message</p>
</fieldset>

توضیحاتی در مورد View لاگین برنامه:
در View صفحه لاگین سه فرم را مشاهده می‌کنید. در برنامه‌های ASP.NET Web forms در هر صفحه، تنها یک فرم را می‌توان تعریف کرد؛ اما در ASP.NET MVC این محدودیت برداشته شده است.
تعریف یک فرم هم با متد کمکی Html.BeginForm انجام می‌شود. در اینجا برای مثال می‌شود یک فرم را به کنترلری خاص و متدی مشخص در آن نگاشت نمائیم.
از عبارت using هم برای درج خودکار تگ بسته شدن فرم، در حین dispose شیء MvcForm کمک گرفته شده است.
برای نمونه خروجی HTML اولین فرم تعریف شده به صورت زیر است:

<form action="/Login/LoginResult" method="post">   
<fieldset>
<legend>Test LoginResult()</legend>
<p>
Name: <input id="Name" name="Name" type="text" value="" /></p>
<p>
Password: <input id="Password" name="Password" type="password" /></p>
<input type="submit" value="Login" />
</fieldset>
</form>

توسط متدهای کمکی Html.TextBoxFor و Html.PasswordFor یک TextBox و یک PasswordBox به صفحه اضافه می‌شوند، اما این For آن‌ها و همچنین lambda expression ایی که بکارگرفته شده برای چیست؟
متدهای کمکی Html.TextBox و Html.Password از نگارش‌های اولیه ASP.NET MVC وجود داشتند. این متدها نام خاصیت‌ها و پارامترهایی را که قرار است به آن‌ها بایند شوند، به صورت رشته می‌پذیرند. اما با توجه به اینکه در اینجا می‌توان یک strongly typed view را تعریف کرد،‌ تیم ASP.NET MVC بهتر دیده است که این رشته‌ها را حذف کرده و از قابلیتی به نام Static reflection استفاده کند (^ و ^).

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


توضیحاتی در مورد نحوه عملکرد کنترلر لاگین برنامه:

در این کنترلر صرفنظر از محتوای متدهای آن‌ها، دو نکته جدید را می‌توان مشاهده کرد. استفاده از ویژگی‌های HttpPost، HttpGet و ActionName. در اینجا به کمک ویژگی‌های HttpGet و HttpPost در مورد نحوه دسترسی به این متدها، محدودیت قائل شده‌ایم. به این معنا که تنها در حالت Post است که متد LoginResult در دسترس خواهد بود و اگر شخصی نام این متدها را مستقیما در مرورگر وارد کند (یا همان HttpGet پیش فرض که نیازی هم به ذکر صریح آن نیست)، با پیغام «یافت نشد» مواجه می‌گردد.
البته در نگارش‌های اولیه ASP.NET MVC از ویژگی دیگری به نام AcceptVerbs برای مشخص سازی نوع محدودیت فراخوانی یک اکشن متد استفاده می‌شد که هنوز هم معتبر است. برای مثال:

[AcceptVerbs(HttpVerbs.Get)]

یک نکته امنیتی:
همیشه متدهای Delete خود را به HttpPost محدود کنید. به این علت که ممکن است در طی مثلا یک ایمیل، آدرسی به شکل http://localhost/blog/delete/10 برای شما ارسال شود و همچنین سشن کار با قسمت مدیریتی بلاگ شما نیز در همان حال فعال باشد. URL ایی به این شکل، در حالت پیش فرض، محدودیت اجرایی HttpGet را دارد. بنابراین احتمال اجرا شدن آن بالا است. اما زمانیکه متد delete را به HttpPost محدود کردید، دیگر این نوع حملات جواب نخواهند داد و حتما نیاز خواهد بود تا اطلاعاتی به سرور Post شود و نه یک Get ساده (مثلا کلیک بر روی یک لینک معمولی)، کار حذف را انجام دهد.


توسط ActionName می‌توان نام دیگری را صرفنظر از نام متد تعریف شده در کنترلر، به آن متد انتساب داد که توسط فریم ورک در حین پردازش نهایی مورد استفاده قرار خواهد گرفت. برای مثال در اینجا به متد LoginResult دوم، نام LoginResultWithParams را انتساب داده‌ایم که در فرم دوم تعریف شده در View لاگین برنامه مورد استفاده قرار گرفته است.
وجود این ActionName هم در مثال فوق ضروری است. از آنجائیکه دو متد هم نام را معرفی کرده‌ایم و فریم ورک نمی‌داند که کدامیک را باید پردازش کند. در این حالت (بدون وجود ActionName معرفی شده)، برنامه با خطای زیر مواجه می‌گردد:

The current request for action 'LoginResult' on controller type 'LoginController' is ambiguous between the following action methods:
System.Web.Mvc.ActionResult LoginResult() on type MvcApplication6.Controllers.LoginController
System.Web.Mvc.ActionResult LoginResult(System.String, System.String) on type MvcApplication6.Controllers.LoginController

برای اینکه بتوانید نحوه نگاشت فرم‌ها به متدها را بهتر درک کنید، بر روی چهار return View موجود در کنترلر لاگین برنامه، چهار breakpoint را تعریف کنید. سپس برنامه را در حالت دیباگ اجرا نمائید و تک تک فرم‌ها را یکبار با کلیک بر روی دکمه لاگین، به سرور ارسال نمائید.


بررسی سه روش دریافت اطلاعات از کاربر در ASP.NET MVC

الف) استفاده از اشیاء Context

در ویژوال استودیو، در کنترلر لاگین برنامه، بر روی کلمه Controller کلیک راست کرده و گزینه Go to definition را انتخاب کنید. در اینجا بهتر می‌توان به خواصی که در یک کنترلر به آن‌ها دسترسی داریم، نگاهی انداخت:

public HttpContextBase HttpContext { get; }
public HttpRequestBase Request { get; }
public HttpResponseBase Response { get; }
public RouteData RouteData { get; }

در بین این خواص و اشیاء مهیا، Request و RouteData بیشتر مد نظر ما هستند. در مورد RouteData در قسمت ششم این سری، توضیحاتی ارائه شد. اگر مجددا Go to definition مربوط به HttpRequestBase خاصیت Request را بررسی کنیم، موارد ذیل جالب توجه خواهند بود:

public virtual NameValueCollection QueryString { get; } // GET variables
public NameValueCollection Form { get; } // POST variables
public HttpCookieCollection Cookies { get; }
public NameValueCollection Headers { get; }
public string HttpMethod { get; }

توسط خاصیت Form شیء Request می‌توان به مقادیر ارسالی به سرور در یک کنترلر دسترسی یافت که نمونه‌ای از آن‌را در اولین متد LoginResult می‌توانید مشاهده کنید. این روش در ASP.NET Web forms هم کار می‌کند. جهت اطلاع این روش با ASP کلاسیک دهه نود هم سازگار است!
البته این روش آنچنان مرسوم نیست؛ چون NameValueCollection مورد استفاده، ایندکسی عددی یا رشته‌ای را می‌پذیرد که هر دو با پیشرفت‌هایی که در زبان‌های دات نتی صورت گرفته‌اند، دیگر آنچنان مطلوب و روش مرجح به حساب نمی‌آیند. اما ... هنوز هم قابل استفاده است.
به علاوه اگر دقت کرده باشید در اینجا HttpContextBase داریم بجای HttpContext. تمام این کلاس‌های پایه هم به جهت سهولت انجام آزمون‌های واحد در ASP.NET MVC ایجاد شده‌اند. کار کردن مستقیم با HttpContext مشکل بوده و نیاز به شبیه سازی فرآیندهای رخ داده در یک وب سرور را دارد. اما این کلاس‌های پایه جدید، مشکلات یاد شده را به همراه ندارند.


ب) استفاده از پارامترهای اکشن متدها

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

Request.Form
Request.QueryString
Request.Files
RouteData.Values

برای نمونه در متدی که با نام LoginResultWithParams مشخص شده، چون نام‌های دو پارامتر آن، با نام‌های بکارگرفته شده در Html.TextBoxFor و Html.PasswordFor یکی هستند، با مقادیر ارسالی آن‌ها مقدار دهی شده و سپس در متد قابل استفاده خواهند بود. در پشت صحنه هم از همان رکوردهای موجود در Request.Form (یا سایر موارد ذکر شده)، استفاده می‌شود. در اینجا هر رکورد مثلا مجموعه Request.Form، کلیدی مساوی نام ارسالی به سرور را داشته و مقدار آن هم، مقداری است که کاربر وارد کرده است.
اگر همانندی یافت نشد، آن پارامتر با نال مقدار دهی می‌گردد. بنابراین اگر برای مثال یک پارامتر از نوع int را معرفی کرده باشید و چون نوع int، نال نمی‌پذیرد، یک استثناء بروز خواهد کرد. برای حل این مشکل هم می‌توان از Nullable types استفاده نمود (مثلا بجای int id نوشت int? id تا مشکلی جهت انتساب مقدار نال وجود نداشته باشد).
همچنین باید دقت داشت که این بررسی تطابق‌های بین نام عناصر HTML و نام پارامترهای متدها، case insensitive است و به کوچکی و بزرگی حروف حساس نیست. برای مثال، پارامتر معرفی شده در متد LoginResult مساوی string name است، اما نام خاصیت تعریف شده در کلاس Account مساوی Name بود.


ج) استفاده از ویژگی جدیدی به نام Data Model Binding

در ASP.NET MVC چون می‌توان با یک Strongly typed view کار کرد، خود فریم ورک این قابلیت را دارد که اطلاعات ارسالی یکی فرم را به صورت خودکار به یک وهله از یک شیء نگاشت کند. در اینجا model binder وارد عمل می‌شود، مقادیر ارسالی را استخراج کرده (اطلاعات دریافتی از Form یا کوئری استرینگ‌ها یا اطلاعات مسیریابی و غیره) و به خاصیت‌های یک شیء نگاشت می‌کند. بدیهی است در اینجا این خواص باید عمومی باشند و هم نام عناصر HTML ارسالی به سرور. همچنین model binder پیش فرض ASP.NET MVC را نیز می‌توان کاملا تعویض کرد و محدود به استفاده از model binder توکار آن نیستیم.
وجود این Model binder، کار با ORMها را بسیار لذت بخش می‌کند؛ از آنجائیکه خود فریم ورک ASP.NET MVC می‌تواند عناصر شیءایی را که قرار است به بانک اطلاعاتی اضافه شود، یا در آن به روز شود، به صورت خودکار ایجاد کرده یا به روز رسانی نماید.
نحوه کار با model binder را در متد Login کنترلر فوق می‌توانید مشاهده کنید. بر روی return View آن یک breakpoint قرار دهید. فرم سوم را به سرور ارسال کنید و سپس در VS.NET خواص شیء ساخته شده را در حین دیباگ برنامه، بررسی نمائید.
بنابراین تفاوتی نمی‌کند که از چندین پارامتر استفاده کنید یا اینکه کلا یک شیء را به عنوان پارامتر معرفی نمائید. فریم ورک سعی می‌کند اندکی هوش به خرج داده و مقادیر ارسالی به سرور را به پارامترهای تعریفی، حتی به خواص اشیاء این پارامترهای تعریف شده، نگاشت کند.

در ASP.NET MVC سه نوع Model binder وجود دارند:
1) Model binder پیش فرض که توضیحات آن به همراه مثالی ارائه شد.
2) Form collection model binder که در ادامه توضیحات آن‌را مشاهده خواهید نمود.
3) HTTP posted file base model binder که توضیحات آن به قسمت بعدی موکول می‌شود.

یک نکته:
اولین متد LoginResult کنترلر را به نحو زیر نیز می‌توان بازنویسی کرد:
[HttpPost]
[ActionName("LoginResultWithFormCollection")]
public ActionResult LoginResult(FormCollection collection)
{
string name = collection["name"];
string password = collection["password"];

if (name == "Vahid" && password == "123")
ViewBag.Message = "Succeeded";
else
ViewBag.Message = "Failed";

return View("Result");
}

در اینجا FormCollection به صورت خودکار بر اساس مقادیر ارسالی به سرور توسط فریم ورک تشکیل می‌شود (FormCollection هم یک نوع model binder ساده است) و اساسا یک NameValueCollection می‌باشد.
بدیهی است در این حالت باید نگاشت مقادیر دریافتی، به متغیرهای متناظر با آن‌ها، دستی انجام شود (مانند مثال فوق) یا اینکه می‌توان از متد UpdateModel کلاس Controller هم استفاده کرد:

[HttpPost]
public ActionResult LoginResultUpdateFormCollection(FormCollection collection)
{
var account = new Account();
this.UpdateModel(account, collection.ToValueProvider());

if (account.Name == "Vahid" && account.Password == "123")
ViewBag.Message = "Succeeded";
else
ViewBag.Message = "Failed";

return View("Result");
}

متد توکار UpdateModel، به صورت خودکار اطلاعات FormCollection دریافتی را به شیء مورد نظر، نگاشت می‌کند.
همچنین باید عنوان کرد که متد UpdateModel، در پشت صحنه از اطلاعات Model binder پیش فرض و هر نوع Model binder سفارشی که ایجاد کنیم استفاده می‌کند. به این ترتیب زمانیکه از این متد استفاده می‌کنیم، اصلا نیازی به استفاده از FormCollection نیست و متد بدون آرگومان زیر هم به خوبی کار خواهد کرد:

[HttpPost]
public ActionResult LoginResultUpdateModel()
{
var account = new Account();
this.UpdateModel(account);

if (account.Name == "Vahid" && account.Password == "123")
ViewBag.Message = "Succeeded";
else
ViewBag.Message = "Failed";

return View("Result");
}

استفاده از model binderها همینجا به پایان نمی‌رسد. نکات تکمیلی آن‌ها در قسمت بعدی بررسی خواهند شد.

اشتراک‌ها
یک framework سبک جهت نوشتن برنامه ها با الگوی CQRS و Eventsourcing

پروژه ای سورس باز برای نوشتن اپلیکیشن هایی که با الگوی CQRS و Eventsourcing  پیاده میشوند . در مقایسه با  SimpleCQRS به روز‌تر با قابلیت‌های مدرن‌تر شده و از .Net Core هم پشتیبانی میکند.

یک framework سبک جهت نوشتن برنامه ها با الگوی CQRS و Eventsourcing
اشتراک‌ها
احراز هویت (Identity) در ASP.Net با استفاده از بانک اطلاعاتی MongoDB

برای انتقال جداول احراز هویت (Identity) از SQL Server به بانک اطلاعاتی MongoDB و نحوه استفاده از آن از سورس نمونه در لینک بالا استفاده کنید.
همچنین در این پروژه از پکیج AspNet.Identity.MongoDB 2.0.8   که بر روی Nuget قرار دارد استفاده شده است.

احراز هویت (Identity) در ASP.Net با استفاده از بانک اطلاعاتی MongoDB
اشتراک‌ها
پروژه تک صفحه ای WebApi و Angular 2

این مقاله برای دوستانی که علاقه دارند از Angular 2  در پروژه هاشون استفاده کنند واقعا مناسبه، سورس سرور و هم بسیار مرتب نوشته شده است.

  • Best Web Dev Article of October 2016 
پروژه تک صفحه ای WebApi و Angular 2
نظرات مطالب
کار با یک مخزن کد GitHub‌ از طریق VSCode
پلاگین  Azure Repos مناسب افرادی است که علاوه بر کنترل سورس برنامه به مدیریت پروژه هم نیاز دارند.(در vscode) همانند TFS میتوانید در برخورد کنید و  work items و... ایجاد کنید.
نظرات مطالب
ASP.NET MVC #21
- فایل jquery.unobtrusive-ajax.js سورس باز هست و جزو پروژه. خاصیت confirm آن مستقیما از window.confirm استاندارد مرورگرها استفاده می‌کند و قابلیت سفارشی سازی ندارد. فقط یک متن را می‌شود اینجا تنظیم کرد.
- AjaxOptions خاصیت OnFailure هم دارد.
نظرات مطالب
یکپارچه سازی CKEditor با Lightbox
با سلام 
من یه مشکلی دارم اونم اینه که عکس‌ها آپلود میشه و اضافه هم میشه ولی تگ a ساخته نمیشه
امکانش هست کدهای مثال(سورس پروژه ) رو بذارید؟ 
نظرات مطالب
مدیریت سفارشی سطوح دسترسی کاربران در MVC
دوست عزیز چند ماه گذشته امکانش هست پروژه ای که برای سطح دسترسی نوشتید به صورت سورس باز بگذارید؟
اگر هم به صورت مطلب جدید بگذارید خیلی ممنون میشم.تا بتونیم بازخوردهای خوبی داشته باشیم.

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