مطالب دوره‌ها
بررسی Select For XML
تعدادی افزونه‌ی T-SQL، از نگارش‌های پیشین SQL Server، جهت تولید خروجی XML از یک بانک اطلاعاتی رابطه‌ای، به همراه آن بوده‌اند که در این قسمت آن‌ها را بررسی خواهیم کرد.

پیشنیاز بحث

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


بررسی FOR XML RAW

از نگارش 2005 به بعد، Select for XML علاوه بر خروجی متنی XML، توانایی تولید خروجی از نوع XML را نیز یافته است. در ادامه 4 حالت مختلف خروجی آن‌را بررسی خواهیم کرد.
 SELECT Customers.CustomerID, Orders.OrderID
FROM Customers, Orders
WHERE  Customers.CustomerID = Orders.CustomerID
ORDER BY Customers.CustomerID
FOR XML RAW
خروجی For XML Raw کوئری فوق به نحو ذیل است:
 <row CustomerID="ALFKI" OrderID="10643" />
<row CustomerID="ALFKI" OrderID="10692" />
Select for XML در اینجا به صورت خودکار، هر ردیف کوئری را تبدیل به یک المان row نموده و همچنین هر ستون کوئری را تبدیل به ویژگی‌های این المان (attributes) کرده‌است. همچنین باید دقت داشت که خروجی آن یک fragment است و دارای یک root element  مشخص نیست.

برای تغییر حالت خروجی آن می‌توان از حالت ELEMENTS استفاده کرد:
 SELECT Customers.CustomerID, Orders.OrderID
FROM Customers, Orders
WHERE  Customers.CustomerID = Orders.CustomerID
ORDER BY Customers.CustomerID
FOR XML RAW, ELEMENTS
اینبار مقادیر هر ردیف خروجی، بجای ظاهر شدن در ویژگی‌ها، به صورت یک المان نمایش داده می‌شود:
 <row>
  <CustomerID>ALFKI</CustomerID>
  <OrderID>10643</OrderID>
</row>

حالت پیشرفته‌تر FOR XML RAW را در ادامه ملاحظه می‌کنید:
 SELECT Customers.CustomerID,
Orders.OrderID
FROM Customers,
Orders
WHERE  Customers.CustomerID = Orders.CustomerID
ORDER BY
Customers.CustomerID
FOR XML RAW('Customer'), ELEMENTS XSINIL, ROOT('Customers'), XMLSCHEMA('http://MyCustomers')
با استفاده از Root می‌توان Fragment حاصل را تبدیل به Document با یک Root element مشخص کرد. در قسمت Raw نیز می‌توان مقدار پیش فرض row را مقدار دهی کرد.
 <Customers>
  <Customer xmlns="http://MyCustomers">
     <CustomerID>ALFKI</CustomerID>
     <OrderID>10643</OrderID>
  </Customer>
از XSINIL برای مشخص سازی المان‌های نال استفاده می‌شود. اگر XSINIL ذکر نشود، المان‌های نال در خروجی وجود نخواهند داشت.
ذکر XMLSCHEMA، سبب می‌شود تا SQL Server به صورت خودکار XML Schema را بر اساس اطلاعات ستون‌های رابطه‌ای مورد استفاده تولید کند.
این نکات را برای FOR XML AUTO نیز می‌توان بکار برد.


بررسی FOR XML AUTO

حالت دوم بکارگیری Select for XML به همراه عبارت Auto است:
 SELECT Customers.CustomerID, Orders.OrderID
FROM Customers, Orders
WHERE  Customers.CustomerID = Orders.CustomerID
ORDER BY Customers.CustomerID
FOR XML AUTO, ELEMENTS
با خروجی ذیل:
 <Customers>
  <CustomerID>ALFKI</CustomerID>
  <Orders>
     <OrderID>10643</OrderID>
  </Orders>
  <Orders>
     <OrderID>10692</OrderID>
  </Orders>
</Customers>
در اینجا ابتدا شماره مشتری و سپس اطلاعات تمام خریدهای او ذکر می‌شوند.


بررسی For XML Explicit

اگر بخواهیم خروجی را تبدیل به ترکیبی از المان‌ها و ویژگی‌ها کنیم، می‌توان از For XML Explicit استفاده کرد:
 SELECT 1 AS Tag,
NULL AS Parent,
Customers.CustomerID AS [Customers!1!CustomerID],
NULL AS [Order!2!OrderId]
FROM Customers
UNION ALL
SELECT 2,
1,
Customers.CustomerID,
Orders.OrderID
FROM Customers,
Orders
WHERE  Customers.CustomerID = Orders.CustomerID
ORDER BY
[Customers!1!CustomerID]
FOR XML EXPLICIT
با خروجی:
 <Customers CustomerID="ALFKI">
  <Order OrderId="10643" />
  <Order OrderId="10692" />
  <Order OrderId="10702" />
  <Order OrderId="10835" />
  <Order OrderId="10952" />
  <Order OrderId="11011" />
</Customers>
برای استفاده از FOR XML EXPLICIT، باید به ازای هر سطح از سلسله مراتب مورد نظر، یک عبارت select را تهیه کرد که این‌ها نهایتا باید با هم UNION ALL شوند.
به علاوه دو ستون اضافی Tag و Parent نیز باید ذکر شوند. از این دو برای مشخص سازی سلسه مراتب استفاده می‌شوند.
!1! سبب تولید یک ویژگی در سطح اول می‌شود و !2! سبب تولید ویژگی دیگری در سطح دوم.


بررسی  FOR XML PATH

همانطور که مشاهده می‌کنید، نوشتن FOR XML EXPLICIT نسبتا طولانی و پیچیده‌است. برای ساده سازی آن از نگارش 2005 به بعد، روش For XML Path معرفی شده‌است:
 WITH XMLNAMESPACES('http://somens' AS au)
SELECT
  CustomerID AS [@au:CustomerID],
  CompanyName AS [Company/Name],
  ContactName AS [Contact/Name]  
FROM Customers
 FOR XML PATH('Customer')
با خروجی:
 <Customer xmlns:au="http://somens" au:CustomerID="ALFKI">
  <Company>
       <Name>Alfreds Futterkiste</Name>
  </Company>
  <Contact>
      <Name>Maria Anders</Name>
  </Contact>
</Customer>
در اینجا با استفاده از WITH XMLNAMESPACES یک فضای نام جدید را تعریف کرده و سپس نحوه‌ی استفاده از آن‌را توسط یک Alias مشاهده می‌کنید. در اینجا همچنین توسط Aliasها می‌توان یک مسیر مشخص را نیز تعریف کرد. رشته‌ای که در قسمت Path مشخص می‌شود، بیانگر نام المان‌های خروجی است.

یک نکته: اگر کوئری FOR XML PATH را اجرا کنید، نام ستون خروجی به صورت خودکار به XML_F5..6B  تنظیم می‌شود. علت اینجا است که در حالت پیش فرض، نوع خروجی این افزونه، استریم است و نه XML. برای تبدیل آن به نوع XML باید یک Type را اضافه کرد:
 FOR XML PATH('Customer'), Type
در این حالت خروجی FOR XML PATH قابل انتساب به یک متغیر T-SQL از نوع XML خواهد بود.
مطالب
دریافت اطلاعات بیشتر از Social Provider ها در VS 2013
هنگامی که یک پروژه جدید ASP.NET را در VS 2013 می‌سازید و متد احراز هویت آن را Individual User Accounts انتخاب می‌کنید، قالب پروژه، امکانات لازم را برای استفاده از تامین کنندگان ثالث، فراهم می‌کند، مثلا مایکروسافت، گوگل، توییتر و فیسبوک. هنگامی که توسط یکی از این تامین کننده‌ها کاربری را احراز هویت کردید، می‌توانید اطلاعات بیشتری درخواست کنید. مثلا عکس پروفایل کاربر یا لیست دوستان او. سپس اگر کاربر به اپلیکیشن شما سطح دسترسی کافی داده باشد می‌توانید این اطلاعات را دریافت کنید و تجربه کاربری قوی‌تر و بهتری ارائه کنید.

در این پست خواهید دید که چطور می‌شود از تامین کننده Facebook اطلاعات بیشتری درخواست کرد. پیش فرض این پست بر این است که شما با احراز هویت فیسبوک و سیستم کلی تامین کننده‌ها آشنایی دارید. برای اطلاعات بیشتر درباره راه اندازی احراز هویت فیسبوک به  این لینک  مراجعه کنید.

برای دریافت اطلاعات بیشتر از فیسبوک مراحل زیر را دنبال کنید.

  • یک اپلیکیشن جدید ASP.NET MVC با تنظیمات Individual User Accounts بسازید.
  • احراز هویت فیسبوک را توسط کلید هایی که از Facebook دریافت کرده اید فعال کنید. برای اطلاعات بیشتر در این باره می‌توانید به این لینک مراجعه کنید.
  • برای درخواست اطلاعات بیشتر از فیسبوک، فایل Startup.Auth.cs را مطابق لیست زیر ویرایش کنید.
 List<string> scope = newList<string>() { "email", "user_about_me", "user_hometown", "friends_about_me", "friends_photos" };
 var x = newFacebookAuthenticationOptions();
 x.Scope.Add("email");
 x.Scope.Add("friends_about_me");
 x.Scope.Add("friends_photos");
 x.AppId = "636919159681109";
 x.AppSecret = "f3c16511fe95e854cf5885c10f83f26f";
 x.Provider = newFacebookAuthenticationProvider()
{
    OnAuthenticated = async context =>
    {
         //Get the access token from FB and store it in the database and
        //use FacebookC# SDK to get more information about the user
        context.Identity.AddClaim(
        new System.Security.Claims.Claim("FacebookAccessToken",
                                             context.AccessToken));
    }
};
 x.SignInAsAuthenticationType = DefaultAuthenticationTypes.ExternalCookie;
 app.UseFacebookAuthentication(x);

در خط 1 مشخص می‌کنیم که چه scope هایی از داده را می‌خواهیم درخواست کنیم.
از خط 10 تا 17 رویداد OnAuthenticated را مدیریت می‌کنیم که از طرف Facebook OWIN authentication اجرا می‌شود. این متد هر بار که کاربری با فیسبوک خودش را احراز هویت می‌کند فراخوانی می‌شود. پس از آنکه کاربر احراز هویت شد و به اپلیکیشن سطح دسترسی لازم را اعطا کرد، تمام داده‌ها در FacebookContext ذخیره می‌شوند. 
خط 14 شناسه FacebookAccessToken را ذخیره می‌کند. ما این آبجکت را از فیسبوک دریافت کرده و از آن برای دریافت لیست دوستان کاربر استفاده می‌کنیم.
نکته: در این مثال تمام داده‌ها بصورت Claims ذخیره می‌شوند، اما اگر بخواهید می‌توانید از ASP.NET Identity برای ذخیره آنها در دیتابیس استفاده کنید.
در قدم بعدی لیست دوستان کاربر را از فیسبوک درخواست می‌کنیم. ابتدا فایل Views/Shared/_LoginPartial.cshtml را باز کنید و لینک زیر را به آن بیافزایید.
 <li>
      @Html.ActionLink("FacebookInfo", "FacebookInfo","Account")
</li>

 هنگامی که کاربری وارد سایت می‌شود و این لینک را کلیک می‌کند، ما لیست دوستان او را از فیسبوک درخواست می‌کنیم و بهمراه عکس‌های پروفایل شان آنها را لیست می‌کنیم.
تمام Claim‌ها را از UserIdentity بگیرید و آنها را در دیتابیس ذخیره کنید. در این قطعه کد ما تمام Claim هایی که توسط OWIN دریافت کرده ایم را می‌خوانیم، و شناسه FacebookAccessToken را در دیتابیس عضویت ASP.NET Identity ذخیره می‌کنیم.
//
        // GET: /Account/LinkLoginCallback
        publicasyncTask<ActionResult> LinkLoginCallback()
        {
            var loginInfo = await AuthenticationManager.GetExternalLoginInfoAsync(XsrfKey, User.Identity.GetUserId());
            if (loginInfo == null)
            {
                return RedirectToAction("Manage", new { Message = ManageMessageId.Error });
            }
            var result = await UserManager.AddLoginAsync(User.Identity.GetUserId(), loginInfo.Login);
            if (result.Succeeded)
            {
                var currentUser = await UserManager.FindByIdAsync(User.Identity.GetUserId());
                //Add the Facebook Claim
                await StoreFacebookAuthToken(currentUser);
                return RedirectToAction("Manage");
            }
            return RedirectToAction("Manage", new { Message = ManageMessageId.Error });
        }
خط 14-15 شناسه FacebookAccessToken را در دیتابیس ذخیره می‌کند.
StoreFacebookAuthToken تمام اختیارات (claim)‌های کاربر را از UserIdentity می‌گیرد و Access Token را در قالب یک User Claim در دیتابیس ذخیره می‌کند. اکشن LinkLoginCallback هنگامی فراخوانی می‌شود که کاربر وارد سایت شده و یک تامین کننده دیگر را می‌خواهد تنظیم کند.
اکشن ExternalLoginConfirmation هنگام اولین ورود شما توسط تامین کنندگان اجتماعی مانند فیسبوک فراخوانی می‌شود.
در خط 26 پس از آنکه کاربر ایجاد شد ما یک FacebookAccessToken را بعنوان یک Claim برای کاربر ذخیره می‌کنیم.
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> ExternalLoginConfirmation(ExternalLoginConfirmationViewModel model, string returnUrl)
        {
            if (User.Identity.IsAuthenticated)
            {
                return RedirectToAction("Manage");
            }
 
            if (ModelState.IsValid)
            {
                // Get the information about the user from the external login provider
                var info = await AuthenticationManager.GetExternalLoginInfoAsync();
                if (info == null)
                {
                    return View("ExternalLoginFailure");
                }
                var user = newApplicationUser() { UserName = model.Email };
                var result = await UserManager.CreateAsync(user);
                if (result.Succeeded)
                {
                    result = await UserManager.AddLoginAsync(user.Id, info.Login);
                    if (result.Succeeded)
                    {
                        await StoreFacebookAuthToken(user);
                        await SignInAsync(user, isPersistent: false);
                        return RedirectToLocal(returnUrl);
                    }
                }
                AddErrors(result);
            }
 
            ViewBag.ReturnUrl = returnUrl;
            return View(model);
        }


اکشن ExternalLoginCallback هنگامی فراخوانی می‌شود که شما برای اولین بار یک کاربر را به یک تامین کننده اجتماعی اختصاص می‌دهید. در خط 17 شناسه دسترسی فیسبوک را بصورت یک claim برای کاربر ذخیره می‌کنیم.
//
        // GET: /Account/ExternalLoginCallback
        [AllowAnonymous]
        publicasyncTask<ActionResult> ExternalLoginCallback(string returnUrl)
        {
            var loginInfo = await AuthenticationManager.GetExternalLoginInfoAsync();
            if (loginInfo == null)
            {
                return RedirectToAction("Login");
            }
 
            // Sign in the user with this external login provider if the user already has a login
            var user = await UserManager.FindAsync(loginInfo.Login);
            if (user != null)
            {
                //Save the FacebookToken in the database if not already there
                await StoreFacebookAuthToken(user);
                await SignInAsync(user, isPersistent: false);
                return RedirectToLocal(returnUrl);
            }
            else
            {
                // If the user does not have an account, then prompt the user to create an account
                ViewBag.ReturnUrl = returnUrl;
                ViewBag.LoginProvider = loginInfo.Login.LoginProvider;
                return View("ExternalLoginConfirmation", newExternalLoginConfirmationViewModel { Email = loginInfo.Email });
            }
        }
در آخر شناسه FacebookAccessToken را در دیتابیس ASP.NET Identity ذخیره کنید.
privateasyncTask StoreFacebookAuthToken(ApplicationUser user)
        {
            var claimsIdentity = await AuthenticationManager.GetExternalIdentityAsync(DefaultAuthenticationTypes.ExternalCookie);
            if (claimsIdentity != null)
            {
                // Retrieve the existing claims for the user and add the FacebookAccessTokenClaim
                var currentClaims = await UserManager.GetClaimsAsync(user.Id);
                var facebookAccessToken = claimsIdentity.FindAll("FacebookAccessToken").First();
                if (currentClaims.Count() <=0 )
                {
                    await UserManager.AddClaimAsync(user.Id, facebookAccessToken);
                }

پکیج Facebook C#SDK را نصب کنید. http://nuget.org/packages/Facebook
فایل AccountViewModel.cs را باز کنید و کد زیر را اضافه کنید.
    public class FacebookViewModel
     {
         [Required]
         [Display(Name = "Friend's name")]
         public string Name { get; set; }
  
        public string ImageURL { get; set; }
    }

کد زیر را به کنترلر Account اضافه کنید تا عکس‌های دوستان تان را دریافت کنید.
//GET: Account/FacebookInfo
[Authorize]
publicasyncTask<ActionResult> FacebookInfo()
{
    var claimsforUser = await UserManager.GetClaimsAsync(User.Identity.GetUserId());
    var access_token = claimsforUser.FirstOrDefault(x => x.Type == "FacebookAccessToken").Value;
    var fb = newFacebookClient(access_token);
    dynamic myInfo = fb.Get("/me/friends");
    var friendsList = newList<FacebookViewModel>();
    foreach (dynamic friend in myInfo.data)
    {
        friendsList.Add(newFacebookViewModel()
           {
               Name = friend.name,
               ImageURL = @"https://graph.facebook.com/" + friend.id + "/picture?type=large"
           });
    }
 
    return View(friendsList);
}

در پوشه Views/Account یک نمای جدید با نام FacebookInfo.cshtml بسازید و کد Markup آن را مطابق لیست زیر تغییر دهید.
@model IList<WebApplication96.Models.FacebookViewModel>
 @if (Model.Count > 0)
 {
     <h3>List of friends</h3>
     <div class="row">
             @foreach (var friend in Model)
             {
               <div class="col-md-3">
                <a href="#" class="thumbnail">
                  <img src=@friend.ImageURL alt=@friend.Name />
                 </a>
               </div>
              }
     </div>
 }
در این مرحله، شما می‌توانید لیست دوستان خود را بهمراه عکس‌های پروفایل شان دریافت کنید.
پروژه را اجرا کنید و توسط Facebook وارد سایت شوید. باید به سایت فیسبوک هدایت شوید تا احراز هویت کنید و دسترسی لازم را به اپلیکیشن اعطا کنید. پس از آن مجددا به سایت خودتان باید هدایت شوید.
حال هنگامی که روی لینک FacebookInfo کلیک می‌کنید باید صفحه ای مشابه تصویر زیر ببینید.

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

مطالب
React 16x - قسمت 33 - React Hooks - بخش 4 - useContext Hook
در سری بررسی اعتبارسنجی و احراز هویت کاربران در React، برای انتقال داده‌های کاربر وارد شده‌ی به سیستم، از روش انتقال props، از بالاترین کامپوننت موجود در component tree، به پایین‌ترین کامپوننت آن، به این نحو فرضی استفاده کردیم:
ابتدا شیء user، در بالاترین سطح، دریافت شده و به صفحه‌ای خاص از طریق ویژگی‌های props ارسال می‌شود:
<Page user={user}  />
سپس این کامپوننت Page، کامپوننت PageLayout را رندر می‌کند که آن نیز باید به اطلاعات کاربر دسترسی داشته باشد. بنابراین شیء user را مجددا به این کامپوننت از طریق props ارسال می‌کنیم:
<PageLayout user={user} />
بعد همین کامپوننت PageLayout، کامپوننت NavBar را رندر می‌کند که آن نیز باید بداند کاربر وارد شده‌ی به سیستم کیست؟ به همین جهت یکبار دیگر از طریق props، اطلاعات کاربر را به کامپوننت بعدی موجود در درخت کامپوننت‌ها انتقال می‌دهیم:
<NavigationBar user={user}  />
و همینطور الی آخر. به این روش props drilling گفته می‌شود و ... الگوی مذمومی است. در دنیای واقعی، اطلاعات کاربر و یا خصوصا تنظیمات برنامه مانند آدرس REST API endpoints استفاده شده‌ی در آن، باید بین بسیاری از کامپوننت‌ها به اشتراک گذاشته شود و عموما سطوح به اشتراک گذاری آن، بسیار عمیق‌تر است از سطوحی که در این مثال ساده عنوان شدند. از زمان ارائه‌ی React 16.3.0، راه حل بهتری برای مدیریت اینگونه مسایل با ارائه‌ی React Context ارائه شده‌است که آن‌را در ادامه در دو حالت کامپوننت‌های کلاسی و همچنین تابعی، بررسی خواهیم کرد.


ایجاد شیء Context در برنامه‌های React

React Context، راه حلی است جهت به اشتراک گذاری داده‌ها، در بین انواع و اقسام کامپوننت‌های یک برنامه، بدون اینکه نیازی باشد این اطلاعات را توسط props، از یک سطح، به سطحی دیگر، به صورت دستی انتقال داد. برای ایجاد یک نمونه‌ی از آن، ابتدا پوشه‌ی جدید src\contexts را افزوده و سپس فایل src\contexts\userContext.js را درون آن، با محتوای زیر ایجاد می‌کنیم:
import React from "react";

export const UserContext = React.createContext({ user: {} });

export const UserProvider = UserContext.Provider;
export const UserConsumer = UserContext.Consumer;
متد React.createContext، یک شیء Context را بازگشت می‌دهد. این شیء، دو کامپوننت مهم Provider و Consumer را به همراه دارد که امکان اشتراک به داده‌های مرتبط با آن‌را میسر می‌کنند. زمانیکه React کامپوننتی را رندر می‌کند که مشترک یک شیء Context است، این کامپوننت، امکان خواندن اطلاعات شیء Context را از نزدیک‌ترین کامپوننتی در درخت کامپوننت‌ها که یک Provider را برای آن ارائه داده‌است، خواهد داشت.


تامین یک شیء Context در برنامه، در یک کامپوننت کلاسی و یا تابعی

تا اینجا یک شیء Context را به همراه اجزای export شده‌ی Provider و Consumer آن ایجاد کردیم. اکنون نوبت به پیاده سازی قسمت Provider آن است:
import "../../App.css";

import React, { Component } from "react";

import { UserProvider } from "../../contexts/userContext";
import Main from "./Main";

class App extends Component {
  state = {
    user: { name: "User 1" }
  };

  componentDidMount() {
    // get user from the server or local storage and then set the currently logged in user to the this.state
  }

  render() {
    return (
      <>
        <h1>App Class</h1>
        <UserProvider value={this.state.user}>
          <Main />
        </UserProvider>
      </>
    );
  }
}

export default App;
در این کامپوننت کلاسی (و یا تابعی، نحوه‌ی تعریف UserProvider در هر دو یکی است)، خاصیت user، به state کامپوننت اضافه شده‌است. سپس برای مثال می‌توان این خاصیت را در رویداد componentDidMount از سرور و یا محل ذخیره سازی دیگری دریافت و آنگاه state را بر این اساس به روز رسانی کرد.
در ادامه قصد داریم اطلاعات این شیء user موجود در state را با تمام کامپوننت‌هایی که در درخت رندر کامپوننت جاری قرار می‌گیرند و با کامپوننت Main شروع می‌شوند، به اشتراک بگذاریم. این به اشتراک گذاری با import شیء UserProvider از ماژول contexts/userContext به نحوی که مشاهده می‌کنید، انجام می‌شود. شیء UserProvider، کار محصور سازی کامپوننت Main را انجام می‌دهد. سپس این Provider می‌تواند مقداری را توسط ویژگی value خود دریافت کند که برای مثال در اینجا شیء user است. اکنون این value تا n سطح بعدی که از کامپوننت Main مشتق می‌شوند نیز در دسترس خواهد بود.

یک نکته: متد React.createContext به همراه یک آرگومان defaultValue اختیاری است که در اختیار Consumerهای آن قرار داده می‌شود؛ اگر Provider متناظر با آن‌، در درخت کامپوننت‌های برنامه، یافت نشود. یعنی تعریف Provider الزامی نیست. اگر نیاز است مقدار ثابتی را بین چندین کامپوننت به اشتراک بگذارید، فقط کافی است آن‌ها را توسط React.createContext مقدار دهی اولیه کرده و ... استفاده کنید:
export const DefaultRouteContext = React.createContext({ path: '/welcome' });


خواندن شیء Context در کامپوننتی دیگر

اکنون که یک تامین کننده‌ی Context را ایجاد کردیم، برای خواندن اطلاعات آن در درخت کامپوننت‌های محصور شده‌ی توسط UserProvider، می‌توان به صورت زیر عمل کرد:
import React from "react";

import { UserConsumer } from "../../contexts/userContext";

export default function Main(props) {
  return (
    <>
      <UserConsumer>
        {value => <div>User name: {value.name}.</div>}
      </UserConsumer>
    </>
  );
}
ابتدا UserConsumer را از ماژول contexts/userContext دریافت می‌کنیم. سپس برای دسترسی به خاصیت name شیء ارائه شده‌ی توسط UserProvider، باید قسمتی از متد رندر کامپوننت را توسط شیء UserConsumer، محصور کرد و سپس value آن‌را به نحوی که مشاهده می‌کنید، خواند. Consumer، یک تابع را به عنوان فرزند دریافت می‌کند. این تابع مقدار شیء تامین شده‌ی توسط Context را دریافت کرده (همان value={this.state.user} نزدیک‌ترین کامپوننتی که به همراه یک Provider است) و سپس یک المان React را بازگشت می‌دهد که در این محل رندر خواهد شد.

خروجی برنامه پس از این تغییرات به صورت زیر است:



ساده سازی دسترسی به UserConsumer توسط useContext Hook

نحوه‌ی تعریف یک Provider و محصور سازی فرزندانی که باید از آن ارث‌بری کنند، در بین کامپوننت‌های کلاسی و تابعی، یکی است. اما در کامپوننت‌های تابعی حداقل می‌توان نحوه‌ی دسترسی به UserConsumer را به نحو زیر توسط useContext Hook ساده کرد:
import React, { useContext } from "react";

import { UserContext } from "../../contexts/userContext";

export default function Main() {
  const value = useContext(UserContext);
  return (
    <>
      <div>User name: {value.name}.</div>
    </>
  );
}
متد useContext ابتدا شیء UserContext مهیا شده‌ی توسط ماژول contexts/userContext را دریافت می‌کند. سپس خروجی آن، همان value تنظیم شده‌ی توسط نزدیک‌ترین Provider آن در component tree است. این روش، بار ذهنی کمتری را نسبت به حالت قبلی استفاده‌ی از UserConsumer و کار با یک تابع درون آن‌را به همراه دارد؛ ساده‌تر خوانده می‌شود، ساده‌تر استفاده می‌شود. فقط باید دقت داشت که این متد، کل شیء Context را دریافت می‌کند و نه فقط شیء UserConsumer آن‌را.

مزیت دیگر این روش، ساده سازی کار با چندین شیء Context است. برای مثال اگر دو شیء Context را تعریف کرده باشید، خواندن دو مقدار از آن‌ها، پیشتر چنین شکل تو در تویی را توسط دو Consumer پیدا می‌کرد:
function HeaderBar() {
  return (
    <CurrentUser.Consumer>
      {user =>
        <Notifications.Consumer>
          {notifications =>
            <header>
              Welcome back, {user.name}!
              You have {notifications.length} notifications.
            </header>
          }
      }
    </CurrentUser.Consumer>
  );
}
اما اکنون با استفاده از useContext، نوشتن و خواندن آن به سادگی چند سطر زیر است که بسیار منطقی‌تر و عادی‌تر به نظر می‌رسد:
function HeaderBar() {
  const user = useContext(CurrentUser);
  const notifications = useContext(Notifications);
return (
    <header>
      Welcome back, {user.name}!
      You have {notifications.length} notifications.
    </header>
  );
}


ارسال اطلاعات به کامپوننت Context Provider، از طریق کامپوننت‌های فرزند

تا اینجا با استفاده از React Context، اطلاعات یک Provider را با فرزندان آن به اشتراک گذاشتیم؛ عکس این عمل نیز میسر است. برای اینکار، همانند تمام کامپوننت‌های دیگری که برای ارسال اطلاعات به فراخوان خود از طریق رخ‌دادها عمل می‌کنند، می‌توان یک متد رویدادگردان را در کامپوننت والد، به استفاده کنند‌ه‌ی از Context ارسال کرد:
import "../../App.css";

import React, { Component } from "react";

import { UserProvider } from "../../contexts/userContext";
import Main from "./Main2";

class App extends Component {
  state = {
    user: { name: "User 1" }
  };

  componentDidMount() {
    // get user from the server or local storage and then set the currently logged in user to the this.state
  }

  logout = () => {
    console.log("logout");
    this.setState({ user: {} });
  };

  render() {
    const contextValue = {
      user: this.state.user,
      logoutUser: this.logout
    };
    return (
      <>
        <h1>App Class</h1>
        <UserProvider value={contextValue}>
          <Main />
        </UserProvider>
      </>
    );
  }
}

export default App;
در اینجا ابتدا به خاصیت logout، متدی را نسبت داده‌ایم که با فراخوانی آن، اطلاعات شیء user موجود در state کامپوننت جاری را پاک می‌کند. سپس این خاصیت را به صورت یک خاصیت جدید، به شیءای که به ویژگی value شیء UserProvider انتساب داده شده، اضافه می‌کنیم.
اکنون تمام استفاده کننده‌های از این UserProvider می‌توانند با فراخوانی متد منتسب به logout، سبب پاک شدن اطلاعات کاربر موجود در state کامپوننت App، به روز رسانی state و در نتیجه‌ی آن، رندر مجدد کامپوننت و ارائه‌ی یک UserProvider جدید، با اطلاعاتی جدید به فرزندان آن شوند:
import React, { useContext } from "react";

import { UserContext } from "../../contexts/userContext";

export default function Main() {
  const { user, logoutUser } = useContext(UserContext);
  return (
    <>
      <div>User name: {user.name}.</div>
      <button type="button" className="btn btn-primary" onClick={logoutUser}>
        Logout user
      </button>
    </>
  );
}
در این کامپوننت مصرف کننده‌ی Context، اینبار، مقدار دریافتی، یک شیء با چندین خاصیت است. بنابراین می‌توان با استفاده از Object Destructuring، خواص آن‌را استخراج و استفاده کرد. برای مثال با انتساب onClick={logoutUser} به دکمه‌ی خروج، این کامپوننت می‌تواند اطلاعات state و سپس Context ارائه شده‌ی در کامپوننت App را تغییر دهد.

روش انجام اینکار بدون استفاده از useContext را نیز در ادامه مشاهده می‌کنید که در ابتدا نیاز به تعریف تابعی را دارد که همان خواص استخراجی را دریافت می‌کند. سپس باید بر اساس آن‌ها، المان‌های مدنظر نمایش نام کاربر و دکمه‌ی خروج او را بازگشت داد:
import React from "react";

import { UserConsumer } from "../../contexts/userContext";

export default function Main(props) {
  return (
    <>
      <UserConsumer>
        {({ user, logoutUser }) => (
          <>
            <div>User name: {user.name}.</div>
            <button
              type="button"
              className="btn btn-primary"
              onClick={logoutUser}
            >
              Logout user
            </button>
          </>
        )}
      </UserConsumer>
    </>
  );
}


کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید: sample-30-part-04.zip
مطالب
مقدمه‌ای بر صفحات Razor در ASP.NET Core 2.0 - بخش اوّل
Page یا «صفحه» در Razor، یکی از ویژگی‌های جدید در  ASP.NET Core MVC است که تمرکز کدنویسی را بر روی صفحات قرار می‌دهد و این موجب راحتی کدنویسی و بالارفتن راندمان می‌شود. این «صفحات» نیازمند استفاده از نسخۀ ASP.NET Core 2.0.0 و نسخه‌های بعد از آن هستند که در  Visual Studio 2017 Update 3 و نسخه‌های بعدی در دسترس است.
«صفحات» Razor به‌طور پیش‌‎فرض در MVC در دسترس است و کافیست در فایل  Startup.cs، «صفحات» Razor فعال شود:
public class Startup
{
    public void ConfigureServices(IServiceCollections services)
    {
        services.AddMvc(); // موجب فعال شدن «صفحات»  و کنترلرها می‌شود
    }

    public void Configure(IApplicationBuilder app)
    {
        app.UseMvc();
    }
}
تمام ویژگی‌های جدید «صفحات» Razor در اسمبلی Microsoft.AspNetCore.Mvc.RazorPages در دسترس است و کافیست بدان ارجاع دهید. همچنین اگر ارجاعی به پکیج  Microsoft.AspNetCore.Mvc  داشته باشید نیز «صفحات» Razor در دسترس خواهند بود.
«صفحۀ» زیر را در نظر بگیرید:
@page

@{
    var message = "Hello, World!";
}

<html>
<body>
    <p>@message</p>
</body>
</html>
این کد، بسیار شبیه «صفحات» ویوی Razor معمولی است؛ اما آنچه آن را متفاوت می‌سازد، استفاده از دایرکتیو page@  است. با استفاده از  page@، درواقع این فایل نقش اکشن متد MVC را نیز انجام می‌دهد و بدین معناست که می‌تواند به‌طورمستقیم، درخواست‌ها را بدون وساطت هیچ کنترلری مدیریت کند. دایرکتیو  page@، باید در ابتدای صفحۀ Razor قرار بگیرد. این دایرکتیو رفتار اصلی سایر Razorها را تحت تأثیر قرار می‌دهد.
ارتباط میان مسیرهای URL با صفحات، از طریق محل قرارگیری «صفحات» در فایل سیستم، اتفاق می‌افتد. جدول زیر نحوۀ ارتباط میان مسیر «صفحات» Razor و URL متناظر آن را نشان می‌دهد: 
 URL متناظر  نام فایل و مسیر آن 
/  یا  /Index
 /Pages/Index.cshtml
/Contact /Pages/Store/Contact.cshtml
/Store/Contact /Pages/Store/Contact.cshtml
برنامه هنگام اجرا، به‌طور پیش‌فرض، برای یافتن فایل «صفحات» Razor در پوشۀ «صفحات» جستجو می‌کند.
ویژگی‌های جدید «صفحات» Razor  بدین منظور طراحی شده‌اند تا الگوهای عمومی به‌کار رفته در پیمایش‌گر صفحات وب را آسان‌تر کنند. صفحۀ «تماس با ما» را در نظر بگیرید که در آن مدل Contact پیاده‌سازی شده است. فایل  MyApp/Contact.cs  بدین شکل است:
using System.ComponentModel.DataAnnotations;

namespace MyApp 
{
    public class Contact
    {
        [Required]
        public string Name { get; set; }

        [Required]
        public string Email { get; set; }
    }
}
و فایل ویوی آن یعنی MyApp/Pages/Contact.cshtml به شکل زیر است:
@page
@using MyApp
@using Microsoft.AspNetCore.Mvc.RazorPages
@addTagHelper "*, Microsoft.AspNetCore.Mvc.TagHelpers"
@inject ApplicationDbContext Db

@functions {
    [BindProperty]
    public Contact Contact { get; set; }

    public async Task<IActionResult> OnPostAsync()
    {
        if (ModelState.IsValid)
        {
            Db.Contacts.Add(Contact);
            await Db.SaveChangesAsync();
            return RedirectToPage();
        }

        return Page();
    }
}

<html>
<body>
    <p>فرم زیر را پر کنید تا در اسرع وقت، کارشناسان ما با شما بگیرند</p> 
    <div asp-validation-summary="All"></div>
    <form method="POST">
      <div>Name: <input asp-for="Contact.Name" /></div>
      <div>Email: <input asp-for="Contact.Email" /></div>
      <input type="submit" />
    </form>
</body>
</html>
این «صفحه»، هندلری با عنوان OnPostAsync  دارد که هنگام ارسال درخواست‌های POST (هنگامی‌که کاربری فرم را ثبت می‌کند) اجرا می‌شود. البته می‌توان برای هر نوع تقاضای HTTP، هندلری را اضافه کرد که معمولاً از  OnGet  برای هرگونه تقاضای نشان دادن یک صفحۀ HTML استفاده می‌شود و از  OnPost  برای ارسال اطلاعات از طریق فرم استفاده می‌شود و همان‌طور که می‌دانیم پسوند Async اختیاری است. اما اغلب به‌طور قراردادی به‌کار برده می‌شود. کد نوشته شده داخل OnPostAsync بسیار شبیه چیزی است که به‌طور معمول در داخل یک کنترلر نوشته می‌شود. این الگویی است برای صفحاتی که در آن‌ها بیشتر اصول اوّلیۀ MVC از قبیل بایند کردن مدل‌ها، اعتبارسنجی و نتایج اکشن‌ها قابل استفاده است.  
روند اصلی  OnPostAsync  به‌شکل زیر است:
کنترل خطاهای اعتبارسنجی.
  1. اگر خطایی نبود، اطلاعات ذخیره شده و به صفحۀ دیگر ریدایرکت می‌شود.
  2. درغیراین‌صورت، صفحه را دوباره به‌همراه خطاهای اعتبار سنجی نمایش می‌دهد.
  3. اگر اطلاعات موفقت‌آمیز وارد شوند، آنگاه متد هندلر OnPostAsync، هلپر RedirctToPage را برای برگرداندن نمونه‌ای از RedirectToPageResult فراخوانی می‌کند. این یک نوع جدید بازگشتی برای اکشن متد است که شبیه RedirectToAction  یا RedirectToRoute  است؛ با این تفاوت که مخصوص صفحات طراحی شده است.
هنگامیکه فرم ثبت شده، خطای اعتبارسنجی داشته باشد، آنگاه متد هندلر OnPostAsync هلپر متد Page را فراخوانی می‌کند. این هلپر یک نمونه از PageResult را برمی‌گرداند. این شبیه کاری است که اکشن‌ها در کنترلر‌ها، یک ویو را برگردانند. PageResult خروجی پیش‌فرض یک هندلر متد است و هندلر متدی که نوع  void  را برگرداند «صفحۀ» جاری را رندر می‌کند.
خصیصۀ  Contact از attribute جدید [BindProperty] برای مقید کردن مدل استفاده می‌کند. «صفحات» به‌طور پیش‌فرض، ویژگی‌هایی را که GET نیستند، مقید می‌کنند. مقیدکردن به خواص، موجب کاهش کدی می‌شود که شما باید در فرم مورد نظر خود بیاورید؛ مثلاً به‌راحتی می‌توان نوشت ( <input asp-for="Contacts.Name" /> ) که با این کار مقیدسازی فیلد و خصیصۀ مورد نظر به‌طور خودکار انجام می‌شود.
به‌جای استفاده از دایرکتیو  model@ در اینجا، از ویژگی جدید «صفحات» استفاده می‌کنیم. به‌طور پیش‌فرض دایرکتیو کلاس Page همان مدل است و ویژگی‌هایی شبیه مقیدکردن مدل‌ها، تگ هلپرها و هلپرهای HTML، همگی فقط با ویژگی‌هایی که درون functions@ نوشته شده‌اند، کار می‌کنند. هنگام استفاده از «صفحات» به‌طور خودکار به ویومدل دسترسی وجود دارد.
دایرکتیو  inject@  قبل از شروع هندلر متد، قرار گرفته و برای تزریق وابستگی در «صفحات» استفاده می‌شود که همانند Razor ویوهای قبل کار می‌کند. متغیر Db در اینجا از نوع  ApplicationDbContext است که به «صفحه» تزریق می‌شود. 
در اینجا نیازی به نوشتن هیچ دستوری برای اعتبارسنجی anti-forgery نیست. تولید و اعتبارسنجی anti-forgery به‌طور خودکار در «صفحات» انجام می‌شود و برای دستیابی به این ویژگی امنیتی نیاز به تنظیمات اضافه‌تری نیست.
می‌توان ویوی MyApp/Pages/Contact.chsml  را از مدل آن به شکل زیر جدا کرد: 
@page
@using MyApp
@using MyApp.Pages
@using Microsoft.AspNetCore.Mvc.RazorPages
@addTagHelper "*, Microsoft.AspNetCore.Mvc.TagHelpers"
@model ContactModel

<html>
<body>
    <p>فرم زیر را پر کنید تا در اسرع وقت، کارشناسان ما با شما بگیرند </p>
    <div asp-validation-summary="All"></div>
    <form method="POST">
      <div>Name: <input asp-for="Contact.Name" /></div>
      <div>Email: <input asp-for="Contact.Email" /></div>
      <input type="submit" />
    </form>
</body>
</html>
و بخش مدل «صفحه» را به شکل زیر:
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;

namespace MyApp.Pages
{
    public class ContactModel : PageModel
    {
        public ContactModel(ApplicationDbContext db)
        {
            Db = db;
        }

        [BindProperty]
        public Contact Contact { get; set; }

        private ApplicationDbContext Db { get; }

        public async Task<IActionResult> OnPostAsync()
        {
            if (ModelState.IsValid)
            {
                Db.Contacts.Add(Contact);
                await Db.SaveChangesAsync();
                return RedirectToPage();
            }

            return Page();
        }
    }
}
براساس قرارداد، کلاس باید به‌شکل PageModel باشد و در همان فضای نامی باشد که صفحۀ آن قرار دارد و نیازی به استفاده از functions @ نیست و تنها تغییر آن، تزریق وابستگی از طریق کلاس سازنده است. 
با استفاده از PageModel می‌توان از الگوی آزمون واحد بهره برد؛ اما مستلزم نوشتن صریح کلاس و سازندۀ آن است. مزیت «صفحات» بدون فایل PageModel این است که از کامپایل در زمان اجرا پشتیبانی می‌کنند که این قابلیت در هنگام کدنویسی مفید است.
ادامه دارد...
پاسخ به بازخورد‌های پروژه‌ها
ایجاد دیتابیس
بانک اطلاعاتی آن به صورت خودکار ایجاد می‌شود. جزو مباحث automatic migrations است.
فقط به web.config برنامه مراجعه کنید و تنظیمات رشته‌ی اتصالی را مطابق تنظیمات سیستم خودتان، تغییر دهید؛ برای مثال:
  <connectionStrings>
    <add name="GoldenCityShop" connectionString="Data Source=(local);Initial Catalog=GoldenCityShop;Integrated Security=True" providerName="System.Data.SqlClient" />
  </connectionStrings>
نظرات مطالب
نمایش بلادرنگ اعلامی به تمام کاربران در هنگام درج یک رکورد جدید به صورت notification
پیشنهاد شما برای متوجه شدن کاربران از تغییرات انی دیتابیس چی هست؟
مثلا دیتابیس به یک دستگاهی مثل دستگاههای حضور و غیاب متصل باشه  و به محض ثبت لاگ اشخاص توسط دستگاه 
برنامه وب متوجه اون بشه  آیا SignalR بهمراه SqlDependency جوابگو هست؟
مطالب دوره‌ها
ارائه کاربری ساده‌تر انتخاب چندین آیتم از یک لیست به کمک افزونه TagIt در ASP.NET MVC
چندی قبل مطلبی را در مورد بررسی تفصیلی رابطه چند به چند در این سایت مطالعه کردید. در آن مطلب صرفا به بحث ذخیره سازی اطلاعات دریافتی از کاربر اشاره شد. برای مثال اگر مطلبی چندین برچسب دارد، چگونه باید این‌ها را در بانک اطلاعاتی به نحو صحیحی ذخیره کرد.
در مطلب جاری قصد داریم با نحوه ارائه یک UI کاربر پسند برای این منظور آشنا شویم و سؤال مهم هم این است: «چگونه می‌توان کار کاربر را در حین وارد کردن تعدادی از برچسب‌های مرتبط با یک مطلب ساده‌تر کرد؟». برای این منظور یکی از راه حل‌هایی که در بسیاری از سایت‌ها مرسوم شده است، استفاده از افزونه‌هایی مانند jQuery TagIt می‌باشد که در ادامه با نحوه استفاده از آن در ASP.NET MVC آشنا خواهیم شد.


پیشنیازها:
دریافت افزونه TagIt
همچنین دریافت jQuery UI (افزونه TagIt برای نمایش لیست Auto Complete آیتم‌ها از jQuery UI در پشت صحنه استفاده می‌کند)
<head>
    <title>@ViewBag.Title</title>
    <link href="@Url.Content("~/Content/TagIt/jquery-ui-1.8.23.custom.css")" rel="stylesheet" type="text/css" />
    <link href="@Url.Content("~/Content/TagIt/tagit-simple-blue.css")" rel="stylesheet" type="text/css" />
    <link href="@Url.Content("Content/Site.css")" rel="stylesheet" type="text/css" />
    <script src="@Url.Content("~/Scripts/jquery-1.9.1.min.js")" type="text/javascript"></script>
    <script src="@Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script>
    <script src="@Url.Content("~/Scripts/jquery.unobtrusive-ajax.min.js")" type="text/javascript"></script>
    <script src="@Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script>
    <script src="@Url.Content("~/Content/TagIt/jquery-ui-1.8.23.custom.min.js")" type="text/javascript"></script>
    <script src="@Url.Content("~/Content/TagIt/tagit.js")" type="text/javascript"></script>
    @RenderSection("JavaScript", required: false)
</head>
که نهایتا نیاز است یک چنین تعاریفی را به فایل layout برنامه اضافه کنیم.

آشنایی با مدل برنامه

using System.ComponentModel;
using System.ComponentModel.DataAnnotations;

namespace jQueryMvcSample04.Models
{
    public class BlogPostViewModel
    {
        [DisplayName("عنوان"), Required(ErrorMessage = "*")]
        public string Title { set; get; }

        [DisplayName("متن"), Required(ErrorMessage = "*")]
        public string Body { set; get; }

        /// <summary>
        /// آرایه‌ای محدود از برچسب‌های این مطلب خاص به صورت جی‌سون که پیشتر ثبت شده است
        /// هدف استفاده در حین ویرایش مطلب
        /// </summary>
        public string InitialTags { set; get; }

        /// <summary>
        /// آرایه‌ای جی‌سونی از تمام برچسب‌های موجود در سیستم
        /// هدف نمایش منوی انتخاب برچسب‌ها از لیست
        /// </summary>
        public string TagsSource { set; get; }

        /// <summary>
        /// آرایه‌ای از برچسب‌های وارد شده توسط کاربر در حین ثبت مطلب
        /// </summary>
        [DisplayName("برچسب‌ها"), Required(ErrorMessage = "*")]
        public string[] Tags { set; get; }

        public int? Id { set; get; }
    }
}
اگر به نام این کلاس دقت کنید، به ViewModel ختم شده است. از این لحاظ که حاوی خواصی می‌باشد که عموما جهت رندر کردن صحیح UI مورد استفاده قرار می‌گیرند و معادلی در سمت بانک اطلاعاتی نخواهند داشت.
افزونه TagIt برای نمایش اطلاعات خود به دو منبع داده نیاز دارد:
الف) TagsSource : لیستی است به فرمت JSON، از هر آنچه که در سیستم پیشتر به عنوان یک برچسب ثبت شده است. از این لیست برای نمایش منوی خودکار انتخاب آیتم‌ها استفاده می‌شود.
ب) InitialTags : لیستی است به فرمت JSON، از تمام برچسب‌های مرتبط با یک مطلب. از این اطلاعات در حین ویرایش یک مطلب استفاده خواهد شد.

در این ViewModel یک خاصیت دیگر به شکل آرایه، به نام Tags تعریف شده است که لیست برچسب‌های وارد شده توسط کاربر را دریافت خواهد کرد.


معرفی کنترلر برنامه

using System.Web.Mvc;
using jQueryMvcSample04.Extensions;
using jQueryMvcSample04.Models;

namespace jQueryMvcSample04.Controllers
{
    public class HomeController : Controller
    {
        [HttpGet]
        public ActionResult Index(int? id)
        {
            //در ابتدای کار تمام تگ‌های موجود در سیستم از بانک اطلاعاتی دریافت خواهند شد
            //از این تگ‌ها برای تشکیل منوی انتخاب برچسب‌ها استفاده می‌شود
            var tagsSource = new[] { "C#", "C++", "C", "ASP.NET", "MVC" }.ToJson();

            //همچنین صرفا برچسب‌های مطلب جاری که پیشتر ثبت شده‌اند نیز باید از بانک اطلاعاتی دریافت گردند
            //از این برچسب‌ها برای ویرایش یک مطلب موجود استفاده خواهد شد
            var init = new[] { "ASP.NET" }.ToJson();

            var model = new BlogPostViewModel
            {
                TagsSource = tagsSource,
                InitialTags = init,
                Id = id
            };
            return View(model);
        }

        [HttpPost]
        public ActionResult Index(BlogPostViewModel data)
        {
            if (this.ModelState.IsValid)
            {
                //todo: save data
                // ...
                return RedirectToAction(actionName: "index", controllerName: "home");
            }

            //در صورت بروز خطا مجددا اطلاعات موجود نمایش داده خواهند شد
            data.TagsSource = new[] { "C#", "C++", "C", "ASP.NET", "MVC" }.ToJson();
            data.InitialTags = data.Tags.ToJson();
            return View(data);
        }
    }
}


با توجه به توضیحاتی که ارائه شد، کنترلر برنامه ساختار واضح‌تری را یافته است. در اولین بار نمایش صفحه، لیست منبع داده تگ‌ها و همچنین تگ‌های مرتبط با یک مطلب (در صورت وجود) به View ارائه خواهند شد.
از همین ViewModel، در عملیات Post نیز استفاده گردیده و اطلاعات دریافت می‌گردد.
تعریف متد الحاقی ToJson مورد استفاده را نیز در ادامه ملاحظه می‌نمائید:
using System.Linq;
using System.Web.Script.Serialization;

namespace jQueryMvcSample04.Extensions
{
    public static class JsonExt
    {
        public static string ToJson(this string[] initialTags)
        {            
            if (initialTags == null || !initialTags.Any())
                return "[]";
            else
                return new JavaScriptSerializer().Serialize(initialTags);
        }
    }
}

و مرحله آخر تعریف View متناظر است

@model jQueryMvcSample04.Models.BlogPostViewModel
@{
    ViewBag.Title = "Index";
}
@using (Html.BeginForm())
{
    @Html.ValidationSummary(true)

    <fieldset>
        <legend>ثبت مطلب جدید</legend>
        @Html.HiddenFor(model => model.Id)
        <div class="editor-label">
            @Html.LabelFor(model => model.Title)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.Title)
            @Html.ValidationMessageFor(model => model.Title)
        </div>
        <div class="editor-label">
            @Html.LabelFor(model => model.Body)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.Body)
            @Html.ValidationMessageFor(model => model.Body)
        </div>
        <div class="editor-label">
            @Html.LabelFor(model => model.Tags)
        </div>
        <div class="editor-field">
            <ul id="tagsList" dir="ltr" name="Tags">
            </ul>
            @Html.ValidationMessageFor(model => model.Tags)
        </div>
        <p>
            <input type="submit" value="Create" />
        </p>
    </fieldset>
}
@section JavaScript
{
    <script type="text/javascript">
    $(document).ready(function () {
            var tagsSource = @Html.Raw(Model.TagsSource);
            $('#tagsList').tagit({
                 tagSource: tagsSource, 
                 select: true, 
                 triggerKeys: ['enter', 'comma', 'tab'],
                 initialTags:  @Html.Raw(Model.InitialTags) 
              });
});
    </script>
}
در این View دو نکته حائز اهمیت هستند:
الف) برای نمایش افزونه TagIt از یک ul با id مساوی tagsList استفاده شده است.
ب) خواص اضافی موجود در ViewModel که اطلاعات JSON ایی مورد نیاز را بازگشت می‌دهند در قسمت اسکریپت صفحه مورد استفاده قرار گرفته‌اند. در اینجا نیاز است از Html.Raw استفاده شود تا اطلاعات مرتبط با JSON اشتباها encode نشده و قابل استفاده باشند.

دریافت مثال و پروژه کامل این قسمت
jQueryMvcSample04.zip
نظرات مطالب
بیرون نگاه داشتن تنظیمات خصوصی از سورس کنترل
برای تعیین فایل خارجی تنظیمات دو روش  وجود دارد که configSource آن در مطلب فوق بحث شده:
<?xml version="1.0"?>
<configuration>
  <appSettings file="AppSettings.config">
  </appSettings>
  <connectionStrings configSource="ConnectionStrings.config">      
  </connectionStrings>
  <!-- ... -->
</configuration>
در اینجا file می‌تواند به یک مسیر خارج از پروژه اشاره کند؛ اما configSource حتما باید یک فایل داخل پوشه‌ی پروژه باشد.
یک مثال برای حالت استفاده‌ی از فایل:
Web.config:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <appSettings file="YourSettings.config">
    <add key="KeyToOverride" value="Original" />
    <add key="KeyToNotOverride" value="Standard" />
  </appSettings>
  <system.web>
    <!-- standard web settings go here -->
  </system.web>
</configuration>

YourSettings.config:
<appSettings>
  <add key="KeyToOverride" value="Overridden" />
  <add key="KeyToBeAdded" value="EntirelyNew" />
</appSettings>
مطالب
Minimal API's در دات نت 6 - قسمت چهارم - تدارک مقدمات معماری بر اساس ویژگی‌ها
در معماری vertical slices با features سر و کار داریم؛ برای مثال برنامه‌ی ما دو ویژگی نویسنده‌ها و بلاگ‌ها را خواهد داشت و هر ویژگی، کاملا متکی به خود است. برای نمونه هر ویژگی می‌تواند به همراه یک ماژول باشد که به صورت مستقل، تمام سرویس‌ها، endpoints و میان‌افزارهای مورد نیاز خودش را ثبت می‌کند. در این معماری، تمام قسمت‌های مورد نیاز جهت کارکرد یک ویژگی، در کنار هم قرار می‌گیرند تا یافتن آن‌ها و درک ارتباطات بین آن‌ها ساده‌تر شود.


تعریف ساختار ماژول‌های ویژگی‌های معماری vertical slices

برای تعریف ساختار ماژولی که کار ثبت تمام نیازمندی‌های یک ویژگی را انجام می‌دهد، مانند ثبت سرویس‌ها، endpoints و میان‌افزارها، ابتدا پوشه‌ای به نام Contracts را به پروژه‌ی Api اضافه می‌کنیم؛ با این اینترفیس:
namespace MinimalBlog.Api.Contracts;

public interface IModule
{
    IEndpointRouteBuilder RegisterEndpoints(IEndpointRouteBuilder endpoints);
}


ثبت خودکار ماژول‌های برنامه در ابتدای اجرای آن

پس از تعریف این قرارداد، اکنون می‌خواهیم هر ماژولی که در برنامه، اینترفیس فوق را پیاده سازی می‌کند، در ابتدای اجرای برنامه به صورت خودکار، یافت شده و اطلاعات آن به سیستم اضافه شود. برای این منظور متدهای الحاقی زیر را تعریف می‌کنیم:
public static class ServiceCollectionExtensions
{
    public static IServiceCollection AddApplicationServices(this IServiceCollection services,
        WebApplicationBuilder builder)
    {
        // ...

        builder.Services.AddAllModules(typeof(Program));

        return services;
    }

    private static void AddAllModules(this IServiceCollection services, params Type[] types)
    {
        // Using the `Scrutor` to add all of the application's modules at once.
        services.Scan(scan =>
            scan.FromAssembliesOf(types)
                .AddClasses(classes => classes.AssignableTo<IModule>())
                .AsImplementedInterfaces()
                .WithSingletonLifetime());
    }
}
این کلاس ساختار ساده‌ای دارد؛ ابتدا در متد AddAllModules، اسمبلی جاری جهت یافتن کلاس‌های پیاده سازی کننده‌ی اینترفیس IModule، اسکن می‌شود؛ با استفاده از کتابخانه‌ی Scrutor.
سپس کلاس‌های ثبت شده که هم اکنون جزئی از سیستم تزریق وابستگی‌های برنامه هستند، یافت شده و متد RegisterEndpoints آن‌ها فراخوانی می‌شوند تا دیگر نیازی نباشد به ازای هر ماژول، یکبار ثبت دستی این موارد در کلاس Program انجام شود.
using MinimalBlog.Api.Contracts;

namespace MinimalBlog.Api.Extensions;

public static class ModuleExtensions
{
    public static WebApplication RegisterEndpoints(this WebApplication app)
    {
        if (app == null)
        {
            throw new ArgumentNullException(nameof(app));
        }

        var modules = app.Services.GetServices<IModule>();
        foreach (var module in modules)
        {
            module.RegisterEndpoints(app);
        }

        return app;
    }
}
بنابراین در ادامه به کلاس Program مراجعه کرد و متد عمومی کلاس فوق را در آن به صورت app.RegisterEndpoints فراخوانی می‌کنیم:
using MinimalBlog.Api.Extensions;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddApplicationServices(builder);

var app = builder.Build();
app.ConfigureApplication();
app.RegisterEndpoints();

app.Run();
این چند سطر، کل محتوای فایل Program.cs برنامه را تشکیل می‌دهند.


ایجاد اولین Feature برنامه؛ ویژگی نویسندگان

برای تعریف اولین ویژگی برنامه که مختص به نویسندگان است، پوشه‌های جدید Features\Authors را در برنامه‌ی Api ایجاد می‌کنیم.
- اولین کاری را که در ادامه انجام خواهیم داد، انتقال فایل AuthorDto.cs که در قسمت قبل ایجاد کردیم، به درون این پوشه‌ی جدید است.
- سپس ماژول نویسندگان را به صورت زیر به آن اضافه می‌کنیم:
namespace MinimalBlog.Api.Features.Authors;

public class AuthorModule : IModule
{
    public IEndpointRouteBuilder RegisterEndpoints(IEndpointRouteBuilder endpoints)
    {
        endpoints.MapGet("/api/authors", async (MinimalBlogDbContext ctx) =>
        {
            var authors = await ctx.Authors.ToListAsync();
            return authors;
        });

        endpoints.MapPost("/api/authors", async (MinimalBlogDbContext ctx, AuthorDto authorDto) =>
        {
            var author = new Author();
            author.FirstName = authorDto.FirstName;
            author.LastName = authorDto.LastName;
            author.Bio = authorDto.Bio;
            author.DateOfBirth = authorDto.DateOfBirth;

            ctx.Authors.Add(author);
            await ctx.SaveChangesAsync();

            return author;
        });

        return endpoints;
    }
}
در اینجا ماژول نویسندگان را که با پیاده سازی قرارداد IModule تشکیل شده‌است، مشاهده می‌کنید. در متد RegisterEndpoints آن، دو endpoints تعریف شده‌ی در کلاس Program برنامه را در قسمت قبل، Cut کرده و به اینجا منتقل کرده‌ایم. بنابراین اکنون کلاس Program، دیگر به همراه تعریف مستقیم هیچ endpoint ای نیست و خلوت شده‌است. هدف از Features هم دقیقا همین است تا هر ویژگی برنامه، متکی به خود بوده و مستقل باشد؛ به همراه تمام تعاریف مورد نیاز جهت کار با آن در یک محل مشخص (مانند انتقال فایل Dto مربوط به آن، به درون همین پوشه). مزیت این روش، درک ساده‌تر اجزای مرتبط و یافتن سریعتر ارتباطات قسمت‌های یک ویژگی خاص است. در آینده اگر مشکلی رخ داد و باگی بروز پیدا کرد، دقیقا می‌دانیم که محدوده‌ای که باید مورد بررسی قرار گیرد، کجاست و این محدوده، کوچک و متکی به خود است و در بین چندین پروژه‌ی مختلف، پراکنده نشده‌است.
کار نمونه سازی و اجرای متدهای این ماژول‌ها نیز توسط متدهای الحاقی کلاس ModuleExtensions، در ابتدای اجرای برنامه به صورت خودکار انجام می‌شود و نیازی به شلوغ کردن کلاس Program برای ثبت دستی آن‌ها نیست.


افزودن AutoMapper و MediatR به پروژه‌ی Api

در ادامه برای ساده سازی کار نگاشت‌های Dtoهای برنامه به مدل‌های دومین آن، از AutoMapper استفاده خواهیم کرد؛ همچنین از MediatR نیز برای پیاده سازی الگوی CQRS که در قسمت بعدی پیگیری خواهد شد. بنابراین در ابتدا بسته‌های نیوگت این دو را به پروژه‌ی Api اضافه می‌کنیم:
<Project Sdk="Microsoft.NET.Sdk.Web">
  <ItemGroup>
    <PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="11.0.0" />    
    <PackageReference Include="MediatR.Extensions.Microsoft.DependencyInjection" Version="10.0.1" />  
  </ItemGroup>
</Project>
سپس به کلاس ServiceCollectionExtensions مراجعه کرده و تعاریف ثبت سرویس‌های این دو را نیز اضافه می‌کنیم:
public static class ServiceCollectionExtensions
{
    public static IServiceCollection AddApplicationServices(this IServiceCollection services,
        WebApplicationBuilder builder)
    {
        // ...

        builder.Services.AddMediatR(typeof(Program));
        builder.Services.AddAutoMapper(typeof(Program));

        return services;
    }
}
اکنون می‌توان اولین Profile مربوط به AutoMapper را که کار نگاشت AuthorDto به Author و برعکس را انجام می‌دهد، به صورت زیر تهیه کنیم:
using AutoMapper;
using MinimalBlog.Domain.Model;

namespace MinimalBlog.Api.Features.Authors;

public class AuthorProfile : Profile
{
    public AuthorProfile()
    {
        CreateMap<AuthorDto, Author>().ReverseMap();
    }
}
این فایل نیز درون پوشه‌ی Features\Authors قرار می‌گیرد.