اشتراکها
من این سطر e.match رو در سورس redactor (فایل redactor.js) پیدا نمیکنم. مطمئن هستید تداخلی در برنامه ندارید؟
ضمنا این ویرایشگر با نگارشهای نهایی مرورگرها سازگاری خوبی دارد.
ضمنا این ویرایشگر با نگارشهای نهایی مرورگرها سازگاری خوبی دارد.
مطالب
ASP.NET MVC #5
بررسی نحوه انتقال اطلاعات از یک کنترلر به Viewهای مرتبط با آن
در ASP.NET Web forms در فایل code behind یک فرم مثلا میتوان نوشت Label1.Text و سپس مقداری را به آن انتساب داد. اما اینجا به چه ترتیبی میتوان شبیه به این نوع عملیات را انجام داد؟ با توجه به اینکه در کنترلرها هیچ نوع ارجاع مستقیمی به اشیاء رابط کاربری وجود ندارد و این دو از هم مجزا شدهاند.
در پاسخ به این سؤال، همان مثال ساده قسمت قبل را ادامه میدهیم. یک پروژه جدید خالی ایجاد شده است به همراه HomeController ایی که به آن اضافه کردهایم. همچنین مطابق روشی که ذکر شد، View ایی به نام Index را نیز به آن اضافه کردهایم. سپس برای ارسال اطلاعات از یک کنترلر به View از یکی از روشهای زیر میتوان استفاده کرد:
الف) استفاده از اشیاء پویا
ViewBag یک شیء dynamic است که در دات نت 4 امکان تعریف آن میسر شده است. به این معنا که هر نوع خاصیت دلخواهی را میتوان به این شیء انتساب داد و سپس این اطلاعات در View نیز قابل دسترسی و استخراج خواهد بود. مثلا اگر در اینجا به شیء ViewBag، خاصیت دلخواه Country را اضافه کنیم و سپس مقداری را نیز به آن انتساب دهیم:
using System.Web.Mvc;
namespace MvcApplication1.Controllers
{
public class HomeController : Controller
{
public ActionResult Index()
{
ViewBag.Country = "Iran";
return View();
}
}
}
این اطلاعات در View مرتبط با اکشنی به نام Index به نحو زیر قابل بازیابی خواهد بود (نحوه اضافه کردن View متناظر با یک اکشن یا متد را هم در قسمت قبل با تصویر مرور کردیم):
@{
ViewBag.Title = "Index";
}
<h2>
Index</h2>
<p>
Country : @ViewBag.Country
</p>
در این مثال، @ در View engine جاری که Razor نام دارد، به این معنا میباشد که این مقداری است که میخواهم دریافت کنی (ViewBag.Country) و سپس آنرا در حین پردازش صفحه نمایش دهی.
ب) انتقال اطلاعات یک شیء کامل و غیر پویا به View
هر پروژه جدید MVC به همراه پوشهای به نام Models است که در آن میتوان تعاریف اشیاء تجاری برنامه را قرار داد. در پروژه جاری، یک کلاس ساده را به نام Employee به این پوشه اضافه میکنیم:
namespace MvcApplication1.Models
{
public class Employee
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
}
}
اکنون برای نمونه یک وهله از این شیء را در متد Index ایجاد کرده و سپس به view متناظر با آن ارسال میکنیم (در قسمت return View کد زیر مشخص است). بدیهی است این وهله سازی در عمل میتواند از طریق دسترسی به یک بانک اطلاعاتی یا یک وب سرویس و غیره باشد.
using System.Web.Mvc;
using MvcApplication1.Models;
namespace MvcApplication1.Controllers
{
public class HomeController : Controller
{
public ActionResult Index()
{
ViewBag.Country = "Iran";
var employee = new Employee
{
Email = "name@site.com",
FirstName = "Vahid",
LastName = "N."
};
return View(employee);
}
}
}
امضاهای متفاوت (overloads) متد کمکی View هم به شرح زیر هستند:
ViewResult View(Object model)
ViewResult View(string viewName, Object model)
ViewResult View(string viewName, string masterName, Object model)
اکنون برای دسترسی به اطلاعات این شیء employee در View متناظر با این متد، چندین روش وجود دارد:
@{
ViewBag.Title = "Index";
}
<h2>
Index</h2>
<div>
Country: @ViewBag.Country <br />
FirstName: @Model.FirstName
</div>
میتوان از طریق شیء استاندارد دیگری به نام Model (که این هم یک شیء dynamic است مانند ViewBag قسمت قبل)، به خواص شیء یا مدل ارسالی به View جاری دسترسی پیدا کرد که یک نمونه از آنرا در اینجا ملاحظه میکنید.
روش دوم، بر اساس تعریف صریح نوع مدل است به نحو زیر:
@model MvcApplication1.Models.Employee
@{
ViewBag.Title = "Index";
}
<h2>
Index</h2>
<div>
Country: @ViewBag.Country
<br />
FirstName: @Model.FirstName
</div>
در اینجا در مقایسه با قبل، تنها یک سطر به اول فایل View اضافه شده است که در آن نوع شیء Model تعیین میگردد (کلمه model هم در اینجا با حروف کوچک شروع شده است). به این ترتیب اینبار اگر سعی کنیم به خواص این شیء دسترسی پیدا کنیم، Intellisense ویژوال استودیو ظاهر میشود. به این معنا که شیء Model بکارگرفته شده اینبار دیگر dynamic نیست و دقیقا میداند که چه خواصی را باید پیش از اجرای برنامه در اختیار استفاده کننده قرار دهد.
به این روش، روش Strongly typed view هم گفته میشود؛ چون View دقیقا میداند که چون نوعی را باید انتظار داشته باشد؛ تحت نظر کامپایلر قرار گرفته و همچنین Intellisense نیز برای آن مهیا خواهد بود.
به همین جهت این روش Strongly typed view، در بین تمام روشهای مهیا، به عنوان روش توصیه شده و مرجح مطرح است.
به علاوه استفاده از Strongly typed views یک مزیت دیگر را هم به همراه دارد: فعال شدن یک code generator توکار در VS.NET به نام scaffolding. یک مثال ساده:
تا اینجا ما اطلاعات یک کارمند را نمایش دادیم. اگر بخواهیم یک لیست از کارمندها را نمایش دهیم چه باید کرد؟
روش کار با قبل تفاوتی نمیکند. اینبار در return View ما، یک شیء لیستی ارائه خواهد شد. در سمت View هم با یک حلقه foreach کار نمایش این اطلاعات صورت خواهد گرفت. راه سادهتری هم هست. اجازه دهیم تا خود VS.NET، کدهای مرتبط را برای ما تولید کند.
یک کلاس دیگر به پوشه مدلهای برنامه اضافه کنید به نام Employees با محتوای زیر:
using System.Collections.Generic;
namespace MvcApplication1.Models
{
public class Employees
{
public IList<Employee> CreateEmployees()
{
return new[]
{
new Employee { Email = "name1@site.com", FirstName = "name1", LastName = "LastName1" },
new Employee { Email = "name2@site.com", FirstName = "name2", LastName = "LastName2" },
new Employee { Email = "name3@site.com", FirstName = "name3", LastName = "LastName3" }
};
}
}
}
سپس متد جدید زیر را به کنترلر Home اضافه کنید.
public ActionResult List()
{
var employeesList = new Employees().CreateEmployees();
return View(employeesList);
}
برای اضافه کردن View متناظر با آن، روی نام متد کلیک راست کرده و گزینه Add view را انتخاب کنید. در صفحه ظاهر شده:
تیک مربوط به Create a strongly typed view را قرار دهید. سپس در قسمت Model class، کلاس Employee را انتخاب کنید (نه Employees جدید را، چون از آن میخواهیم به عنوان منبع داده لیست تولیدی استفاده کنیم). اگر این کلاس را مشاهده نمیکنید، به این معنا است که هنوز برنامه را یکبار کامپایل نکردهاید تا VS.NET بتواند با اعمال Reflection بر روی اسمبلی برنامه آنرا پیدا کند. سپس در قسمت Scaffold template گزینه List را انتخاب کنید تا Code generator توکار VS.NET فعال شود. اکنون بر روی دکمه Add کلیک نمائید تا View نهایی تولید شود. برای مشاهده نتیجه نهایی مسیر http://localhost/Home/List باید بررسی گردد.
ج) استفاده از ViewDataDictionary
ViewDataDictionary از نوع IDictionary با کلیدی رشتهای و مقداری از نوع object است. توسط آن شیءایی به نام ViewData در ASP.NET MVC به نحو زیر تعریف شده است:
public ViewDataDictionary ViewData { get; set; }
این روش در نگارشهای اولیه ASP.NET MVC بیشتر مرسوم بود. برای مثال:
using System;
using System.Web.Mvc;
namespace MvcApplication1.Controllers
{
public class HomeController : Controller
{
public ActionResult Index()
{
ViewData["DateTime"] = "<br/>" + DateTime.Now;
return View();
}
}
}
و سپس جهت استفاده از این ViewData تعریف شده با کلید دلخواهی به نام DateTime در View متناظر با اکشن Index خواهیم داشت:
@{
ViewBag.Title = "Index";
}
<h2>
Index</h2>
<div>
DateTime: @ViewData["DateTime"]
</div>
یک نکته امنیتی:
اگر به مقدار انتساب داده شده به شیء ViewDataDictionary دقت کنید، یک تگ br هم به آن اضافه شده است. برنامه را یکبار اجرا کنید. مشاهده خواهید کرد که این تگ به همین نحو نمایش داده میشود و نه به صورت یک سطر جدید HTML . چرا؟ چون Razor به صورت پیش فرض اطلاعات را encode شده (فراخوانی متد Html.Encode در پشت صحنه به صورت خودکار) در صفحه نمایش میدهد و این مساله از لحاظ امنیتی بسیار عالی است؛ زیرا جلوی بسیاری از حملات cross site scripting یا XSS را خواهد گرفت.
احتمالا الان این سؤال پیش خواهد آمد که اگر «عالمانه» بخواهیم این رفتار نیکوی پیش فرض را غیرفعال کنیم چه باید کرد؟
برای این منظور میتوان نوشت:
@Html.Raw(myString)
و یا:
<div>@MvcHtmlString.Create("<h1>HTML</h1>")</div>
به این ترتیب خروجی Razor دیگر encode شده نخواهد بود.
د) استفاده از TempData
TempData نیز یک dictionary دیگر برای ذخیره سازی اطلاعات است و به نحو زیر در فریم ورک تعریف شده است:
public TempDataDictionary TempData { get; set; }
TempData در پشت صحنه از سشنهای ASP.NET جهت ذخیره سازی اطلاعات استفاده میکند. بنابراین اطلاعات آن در سایر کنترلرها و View ها نیز در دسترس خواهد بود. البته TempData یک سری تفاوت هم با سشن معمولی ASP.NET دارد:
- بلافاصله پس از خوانده شدن، حذف خواهد شد.
- پس از پایان درخواست از بین خواهد رفت.
هر دو مورد هم به جهت بالابردن کارآیی برنامههای ASP.NET MVC و مصرف کمتر حافظه سرور درنظر گرفته شدهاند.
البته کسانی که برای بار اول هست با ASP.NET مواجه میشوند، شاید سؤال بپرسند این مسایل چه اهمیتی دارد؟ پروتکل HTTP، ذاتا یک پروتکل «بدون حالت» است یا Stateless هم به آن گفته میشود. به این معنا که پس از ارائه یک صفحه وب توسط سرور، تمام اشیاء مرتبط با آن در سمت سرور تخریب خواهند شد. این مورد متفاوت است با برنامههای معمولی دسکتاپ که طول عمر یک شیء معمولی تعریف شده در سطح فرم به صورت یک فیلد، تا زمان باز بودن آن فرم، تعیین میگردد و به صورت خودکار از حافظه حذف نمیشود. این مساله دقیقا مشکل تمام تازه واردها به دنیای وب است که چرا اشیاء ما نیست و نابود شدند. در اینجا وب سرور قرار است به هزاران درخواست رسیده پاسخ دهد. اگر قرار باشد تمام این اشیاء را در سمت سرور نگهداری کند، خیلی زود با اتمام منابع مواجه میگردد. اما واقعیت این است که نیاز است یک سری از اطلاعات را در حافظه نگه داشت. به همین منظور یکی از چندین روش مدیریت حالت در ASP.NET استفاده از سشنها است که در اینجا به نحو بسیار مطلوبی، با سربار حداقل توسط TempData مدیریت شده است.
یک مثال کاربردی در این زمینه:
فرض کنید در متد جاری کنترلر، ابتدا بررسی میکنیم که آیا ورودی دریافتی معتبر است یا خیر. در غیراینصورت، کاربر را به یک View دیگر از طریق کنترلری دیگر جهت نمایش خطاها هدایت خواهیم کرد.
همین «هدایت مرورگر به یک View دیگر» یعنی پاک شدن و تخریب اطلاعات کنترلر قبلی به صورت خودکار. بنابراین نیاز است این اطلاعات را در TempData قرار دهیم تا در کنترلری دیگر قابل استفاده باشد:
using System;
using System.Web.Mvc;
namespace MvcApplication1.Controllers
{
public class HomeController : Controller
{
public ActionResult InsertData(string name)
{
// Check for input errors.
if (string.IsNullOrWhiteSpace(name))
{
TempData["error"] = "name is required.";
return RedirectToAction("ShowError");
}
// No errors
// ...
return View();
}
public ActionResult ShowError()
{
var error = TempData["error"] as string;
if (!string.IsNullOrWhiteSpace(error))
{
ViewBag.Error = error;
}
return View();
}
}
}
در همان HomeController دو متد جدید به نامهای InsertData و ShowError اضافه شدهاند. در متد InsertData ابتدا بررسی میشود که آیا نامی وارد شده است یا خیر. اگر خیر توسط متد RedirectToAction، کاربر به اکشن یا متد ShowError هدایت خواهد شد.
برای انتقال اطلاعات خطایی که میخواهیم در حین این Redirect نمایش دهیم نیز از TempData استفاده شده است.
بدیهی است برای اجرا این مثال نیاز است دو View جدید برای متدهای InsertData و ShowError ایجاد شوند (کلیک راست روی نام متد و انتخاب گزینه Add view برای اضافه کردن View مرتبط با آن اکشن).
محتوای View مرتبط با متد افزودن اطلاعات فعلا مهم نیست، ولی View نمایش خطاها در سادهترین حالت مثلا میتواند به صورت زیر باشد:
@{
ViewBag.Title = "ShowError";
}
<h2>Error</h2>
@ViewBag.Error
برای آزمایش برنامه هم مطابق مسیریابی پیش فرض و با توجه به قرار داشتن در کنترلری به نام Home، مسیر http://localhost/Home/InsertData ابتدا باید بررسی شود. چون آرگومانی وارد نشده، بلافاصله صفحه به آدرس http://localhost/Home/ShowError به صورت خودکار هدایت خواهد شد.
نکتهای تکمیلی در مورد Strongly typed viewها:
عنوان شد که Strongly typed view روش مرجح بوده و بهتر است از آن استفاده شود، زیرا اطلاعات اشیاء و خواص تعریف شده در یک View تحت نظر کامپایلر قرار میگیرند که بسیار عالی است. یعنی اگر در View بنویسم FirstName: @Model.FirstName1 چون FirstName1 وجود خارجی ندارد، برنامه نباید کامپایل شود. یکبار این را بررسی کنید. برنامه بدون مشکل کامپایل میشود! اما تنها در زمان اجرا است که صفحه زرد رنگ معروف خطاهای ASP.NET ظاهر میشود که چنین خاصیتی وجود ندارد (این حالت پیش فرض است؛ یعنی کامپایل یک View در زمان اجرا). البته این باز هم خیلی بهتر است از ViewBag، چون اگر مثلا ViewBag.Country1 را وارد کنیم، در زمان اجرا تنها چیزی نمایش داده نخواهد شد؛ اما با روش Strongly typed view، حتما خطای Compilation Error به همراه نمایش محل مشکل نهایی، در صفحه ظاهر خواهد شد.
سؤال: آیا میشود پیش از اجرای برنامه هم این بررسی را انجام داد؟
پاسخ: بله. باید فایل پروژه را اندکی ویرایش کرده و مقدار MvcBuildViews را که به صورت پیش فرض false هست، true نمود. یا خارج از ویژوال استودیو با یک ادیتور متنی ساده مثلا فایل csproj را گشوده و این تغییر را انجام دهید. یا داخل ویژوال استودیو، بر روی نام پروژه کلیک راست کرده و سپس گزینه Unload Project را انتخاب کنید. مجددا بر روی این پروژه Unload شده کلیک راست نموده و گزینه edit را انتخاب نمائید. در صفحه باز شده، MvcBuildViews را یافته و آنرا true کنید. سپس پروژه را Reload کنید.
اکنون اگر پروژه را کامپایل کنید، پیغام خطای زیر پیش از اجرای برنامه قابل مشاهده خواهد بود:
'MvcApplication1.Models.Employee' does not contain a definition for 'FirstName1'
and no extension method 'FirstName1' accepting a first argument of type 'MvcApplication1.Models.Employee'
could be found (are you missing a using directive or an assembly reference?)
d:\Prog\MvcApplication1\MvcApplication1\Views\Home\Index.cshtml 10 MvcApplication1
البته بدیهی است این تغییر، زمان Build پروژه را مقداری افزایش خواهد داد؛ اما امنترین حالت ممکن برای جلوگیری از این نوع خطاهای تایپی است.
یا حداقل بهتر است یکبار پیش از ارائه نهایی برنامه این مورد فعال و بررسی شود.
و یک خبر خوب!
مجوز سورس کد ASP.NET MVC از MS-PL به Apache تغییر کرده و همچنین Razor و یک سری موارد دیگر هم سورس باز شدهاند. این تغییرات به این معنا خواهند بود که پروژه از حالت فقط خواندنی MS-PL به حالت متداول یک پروژه سورس باز که شامل دریافت تغییرات و وصلهها از جامعه برنامه نویسها است، تغییر کرده است (^ و ^).
کار متد کمکی Url.Action ایجاد یک Url بر اساس تعاریف مسیریابی برنامه است. البته متد کمکی مشابهی نیز مانند Html.ActionLink در ASP.NET MVC وجود دارد که کار آن تولید یک لینک قابل کلیک است؛ اما ممکن است در حالتی خاص تنها نیاز به خود Url داشته باشیم و نه لینک قابل کلیک آن.
الف) اگر از jQuery Ajax استفاده میکنید، حتما باید استفاده از Url.Action را لحاظ کنید
برای نمونه اگر قسمتی از عملیات Ajaxایی برنامه شما به نحو زیر تعریف شده است :
... غلط است!
در این حالت برنامه شما تنها در زمانیکه در ریشه یک دومین قرار گرفته باشد کار خواهد کرد. اگر برنامه شما در مسیری مانند http://www.site.com/myNewApp نصب شود، کلیه فراخوانیهای Ajax ایی آن دیگر کار نخواهند کرد چون مسیر url فوق به ریشه سایت اشاره میکند و نه مسیر جاری برنامه شما (در یک sub domain جدید).
به همین جهت در یک چنین حالتی حتما باید به کمک Url.Action مسیر یک اکشن متد را معرفی کرد تا به صورت خودکار بر اساس محل قرارگیری برنامه و تعاریف مسیریابی آن، Url صحیحی تولید شود.
ب) دریافت Url مطلق از یک Url.Action
Urlهای تولید شده توسط Url.Action به صورت پیش فرض نسبی هستند (نسبت به محل نصب و قرارگیری برنامه تعریف میشوند). اگر نیاز به دریافت یک مسیر مطلق که با http برای مثال شروع میشود دارید، باید به نحو زیر عمل کرد:
پارامتر چهارم تعریف شده، سبب خواهد شد لینک تولیدی از حالت نسبی خارج شود.
ج) استفاده از Url.Action در یک کنترلر
فرض کنید قصد تولید یک فید RSS را در کنترلری دارید. یکی از آیتمهایی که باید ارائه دهید، لینک به مطلب مورد نظر است. این لینک باید مطلق باشد همچنین در یک View هم قرار نداریم که به کمک @ بلافاصله به متد کمکی Url.Action دسترسی پیدا کنیم.
در کنترلرها، وهله جاری کلاس به شیء Url و متد Action آن به نحو زیر دسترسی دارد و خروجی نهایی آن یک رشته است:
بنابراین در کنترلرها نیز Urlها را دستی تولید نکنید و اجازه دهید بر اساس پارامترهای زیادی که در پشت صحنه Url.Action لحاظ میشود، Url صحیحی برای شما تولید گردد.
د) استفاده از Url.Action در کلاسهای کمکی برنامه خارج از یک کنترلر
فرض کنید قصد تهیه یک Html Helper سفارشی را به کمک کدنویسی در یک کلاس مجزا دارید. در اینجا نیز نباید Urlها را دستی تولید کرد. در Html Helperهای سفارشی نیز میتوان به کمک متد UrlHelper.GenerateUrl، به همان امکانات Url.Action دسترسی یافت:
الف) اگر از jQuery Ajax استفاده میکنید، حتما باید استفاده از Url.Action را لحاظ کنید
برای نمونه اگر قسمتی از عملیات Ajaxایی برنامه شما به نحو زیر تعریف شده است :
$.ajax({ type: "POST", url: "/Home/EmployeeInfo", ...
در این حالت برنامه شما تنها در زمانیکه در ریشه یک دومین قرار گرفته باشد کار خواهد کرد. اگر برنامه شما در مسیری مانند http://www.site.com/myNewApp نصب شود، کلیه فراخوانیهای Ajax ایی آن دیگر کار نخواهند کرد چون مسیر url فوق به ریشه سایت اشاره میکند و نه مسیر جاری برنامه شما (در یک sub domain جدید).
به همین جهت در یک چنین حالتی حتما باید به کمک Url.Action مسیر یک اکشن متد را معرفی کرد تا به صورت خودکار بر اساس محل قرارگیری برنامه و تعاریف مسیریابی آن، Url صحیحی تولید شود.
@Url.Action("EmployeeInfo", "Home")
ب) دریافت Url مطلق از یک Url.Action
Urlهای تولید شده توسط Url.Action به صورت پیش فرض نسبی هستند (نسبت به محل نصب و قرارگیری برنامه تعریف میشوند). اگر نیاز به دریافت یک مسیر مطلق که با http برای مثال شروع میشود دارید، باید به نحو زیر عمل کرد:
@Url.Action("About", "Home", null, "http")
ج) استفاده از Url.Action در یک کنترلر
فرض کنید قصد تولید یک فید RSS را در کنترلری دارید. یکی از آیتمهایی که باید ارائه دهید، لینک به مطلب مورد نظر است. این لینک باید مطلق باشد همچنین در یک View هم قرار نداریم که به کمک @ بلافاصله به متد کمکی Url.Action دسترسی پیدا کنیم.
در کنترلرها، وهله جاری کلاس به شیء Url و متد Action آن به نحو زیر دسترسی دارد و خروجی نهایی آن یک رشته است:
var url = this.Url.Action(actionName: "Index", controllerName: "Post", protocol: "http", routeValues: new { id = item.Id });
د) استفاده از Url.Action در کلاسهای کمکی برنامه خارج از یک کنترلر
فرض کنید قصد تهیه یک Html Helper سفارشی را به کمک کدنویسی در یک کلاس مجزا دارید. در اینجا نیز نباید Urlها را دستی تولید کرد. در Html Helperهای سفارشی نیز میتوان به کمک متد UrlHelper.GenerateUrl، به همان امکانات Url.Action دسترسی یافت:
public static class Extensions { public static string MyLink(this HtmlHelper html, ...) { string url = UrlHelper.GenerateUrl(null, "actionName", "controllerName", null, html.RouteCollection, html.ViewContext.RequestContext, includeImplicitMvcValues: true); //...
برنامه Cppcheck ابزار آنالیز سورس کدهای برنامههای C و CPP جهت یافتن اشتباهات برنامه نویسی، مشکلات امنیتی، نشتی حافظه و امثال آن است. این برنامه رایگان و سورس باز را میتوانید از آدرس زیر دریافت کنید:
در دو نسخهی خط فرمان و همچنین GUI عرضه میشود که نگارش دارای UI آن از QT استفاده میکند. تا به حال 22 باگ موجود در کرنل لینوکس توسط این برنامه کشف و برطرف شده و همچنین در بسیاری از برنامههای سورس باز دیگر نیز مورد استفاده قرار گرفته است.
لیست مواردی را که این برنامه بررسی میکند، در این آدرس قابل مشاهده است.
بازخوردهای پروژهها
درخواست راهنمایی بیشتر
با سلام، خیلی ممنون که سورس این وب اپلیکیشن را در اختیار کاربران قرار داده اید؛
بنده با مطالعه (بخشی از) این مطالب:
بنده با مطالعه (بخشی از) این مطالب:
مسیر راه Entity framework code-first ،
مسیر راه ASP.NET MVC ،
بررسی مفاهیم معکوس سازی وابستگیها و ابزارهای مرتبط با آن ،
AutoMapper ،
چک لیست تهیه یک برنامه ASP.NET MVC ،
تا حد زیاد نحوه پیاده سازی back end پروژه را درک کردم (البته ابتدا پروژه فروشگاه اینترنتی شهر طلایی من که سادهتر است را بررسی کردم).
با این وجود بخش هایی از پروژه از نظر بنده همچنان پیچیده است!
- مدیریت کاربران، نقشها و تعیین دسترسی ها؛
- نحوه لاگ کردن فعالیت ها؛
- ارسال و دریافت پیام؛
- نحوه پیادهسازی front end پروژه (به طور خاص)،
لطفا در صورت امکان در این موارد (به ویژه مورد آخر طراحی UI) اگر منبعی یا مسیر راهی وجود دارد، معرفی نمایید.
با تشکر،
مسیر راه ASP.NET MVC ،
بررسی مفاهیم معکوس سازی وابستگیها و ابزارهای مرتبط با آن ،
AutoMapper ،
چک لیست تهیه یک برنامه ASP.NET MVC ،
تا حد زیاد نحوه پیاده سازی back end پروژه را درک کردم (البته ابتدا پروژه فروشگاه اینترنتی شهر طلایی من که سادهتر است را بررسی کردم).
با این وجود بخش هایی از پروژه از نظر بنده همچنان پیچیده است!
- مدیریت کاربران، نقشها و تعیین دسترسی ها؛
- نحوه لاگ کردن فعالیت ها؛
- ارسال و دریافت پیام؛
- نحوه پیادهسازی front end پروژه (به طور خاص)،
لطفا در صورت امکان در این موارد (به ویژه مورد آخر طراحی UI) اگر منبعی یا مسیر راهی وجود دارد، معرفی نمایید.
با تشکر،
نظرات مطالب
نحوه ارتقاء برنامههای موجود MVC3 به MVC4
- اگر کار میکنه یعنی قسمتهای 3 و 4 مطلب فوق در حین ارتقاء، ناقص انجام شده یا هنوز تغییری نکرده.
- ضمن اینکه حتما برای مدیریت فایلهای پروژههای خودتون از سورس کنترل استفاده کنید تا نگران تغییرات و بازگشت به قبل نباشید.
- ضمن اینکه حتما برای مدیریت فایلهای پروژههای خودتون از سورس کنترل استفاده کنید تا نگران تغییرات و بازگشت به قبل نباشید.
توضیح
پروژهی سورس باز TaskoMask یک سیستم مدیریت تسک رایگان میباشد که سورس آن را میتوانید از طریق این آدرس در گیت هاب مشاهده کنید. همچنین این پروژه از طریق آدرس taskomask.ir نیز در دسترس میباشد.
برای استفاده مفیدتر از این پروژه، داکیومنتهای مورد نیاز شامل بخش دامین، معماری، وب سرویس و ... ایجاد شده اند که از طریق ریپازیتوری این پروژه بر روی گیت هاب در دسترس هستند.
هدف
هدف اصلی این پروژه، فراهم سازی بستری برای آموزش نحوهی پیادهسازی ابزارها و تکنولوژیهای پیشرفته در یک محیط واقعی است تا برنامه نویسان تازهکار یا کسانی که به تازگی با تکنولوژی خاصی آشنا شده اند بتوانند علاوه بر یادگیری نحوهی پیادهسازی، نحوهی حل کردن چند چالش واقعی در آن زمینه را نیز مشاهده کنند.
ویژگی ها
در اینجا برخی ویژگیهای مهم این پروژه را بررسی میکنیم ولی برای اطلاع از لیست دقیق ویژگیها و امکانات این پروژه و نیز دسترسی به داکیومنتها و بوردهای آن، بهتر است به ریپازیتوری آن در گیت هاب مراجعه کنید.
NET 6. #C Blazor ASP.NET Web API ASP.NET MVC MongoDB Redis MediatR | Microservices DDD CQRS Event Sourcing Notification Repository Onion Architecture | Acceptance Testing Integration Testing Unit Testing UI Testing E2E Testing |
درخواست مشارکت
پروژه تا حد امکان به عنوان یک پروژهی واقعی ولی مینیمال طراحی شده تا نگهداری آن در بلند مدت میسر بوده و همچنین کلیه داکیومنتهای مورد نیاز فراهم شده تا امکان مشارکت در آن آسانتر شود.
از تمام برنامه نویسان عزیز با هر سطح از توانایی دعوت میکنیم که در توسعهی این پروژه به عنوان یک مرجع کوچک برای کمک به مسیر رشد سایر برنامه نویسان مشارکت کنند.
درخواست حمایت
اگر این پروژه به هر نحوی برای شما سودمند بود با دادن یک ستاره ⭐ در گیت هاب ما را حمایت کنید.
تصاویر پروژه
روشی را که تا اینجا در مورد MobX بررسی کردیم، تا نگارش 5x آنرا پوشش میدهد. در همین زمان، کتابخانهی دیگری به نام mobx-react-lite ارائه شد که به همراه تعدادی Hook مخصوص MobX بود تا با سیستم جدید React که مبتنی بر Hooks است، سازگار شود. این امکانات در حال حاضر با خود کتابخانهی mobx-react 6x یکپارچه شده و به زودی mobx-react-lite منسوخ شده اعلام میشود. البته روش inject/observer بررسی شدهی تا نگارش 5x آن، هنوز هم برقرار است و قرار نیست که به این زودیها منسوخ شده اعلام شود. به همین جهت نکاتی را که در مطلب جاری بررسی میکنیم، به عنوان روش تکمیلی سازگار با نگارش جاری 6x آن مطرح است و در کل با هر روشی که علاقمند بودید میتوانید با MobX کار کنید. البته باز هم توصیه شدهاست که سیستم Provider آنرا با React Context استاندارد، جایگزین کنید؛ چون احتمال حذف آن در نگارشهای بعدی MobX هست.
به صورت خلاصه:
- اگر فقط از کامپوننتهای کلاسی استفاده میکنید، mobx-react@5 برای کار شما پاسخگو است.
- اگر از کامپوننتهای کلاسی و همچنین کامپوننتهای تابعی در برنامهی خود استفاده میکنید، mobx-react@6 به همراه mobx-react-lite نیز ارائه میشود و هر دو روش را با هم پوشش میدهد.
- اگر فقط از کامپوننتهای تابعی جدید استفاده میکنید، هوکهای کتابخانهی کوچک mobx-react-lite برای کار شما کافی است.
معرفی useLocalStore Hook و useObserver Hook
در مطالب قبلی، روش تعریف یک کلاس مخزن حالت MobX را توسط تزئین کنندههایی مانند observable، computed و action بررسی کردیم. همچنین دریافتیم که تعریف یک چنین تزئین کنندههایی، یا نیاز به استفادهی از تایپاسکریپت را دارد و یا باید پروژهی React را جهت تغییر کامپایلر Babel آن و فعالسازی decorators، مقداری ویرایش کرد. با useLocalStore Hook هرچند تمام روشهای قبلی هنوز هم پشتیبانی میشوند، اما دیگر نیاز به استفادهی از decorators نیست. useLocalStore تابعی است که یک شیء را باز میگرداند. هر خاصیتی از این شیء، به صورت خودکار observable درنظر گرفته میشود. تمام getters آن به عنوان computed properties تفسیر میشوند و تمام متدهای آن، action درنظر گرفته خواهند شد.
یک مثال:
- در اینجا نحوهی import تابع useLocalStore را از کتابخانهی mobx-react نگارش 6x ملاحظه میکنید.
- روش استفادهی از تابع useLocalStore، میتواند به صورت محلی (همانند اسم آن) مختص به یک کامپوننت باشد. یعنی میتوان بجای state استاندارد React که اجازهی تغییر مستقیم خواص آنرا نمیدهد، از MobX State محلی ارائه شدهی توسط useLocalStore استفاده کرد و یا میتوان useLocalStore را به صورت global نیز تعریف کرد که در ادامهی بحث به آن میپردازیم.
- در مثال فوق، طول عمر شیء ایجاد شدهی توسط useLocalStore، محلی و محدود به طول عمر کامپوننت تابعی تعریف شدهاست.
- در اینجا شیء بازگشت داده شدهی توسط useLocalStore، دارای دو خاصیت title و done است. این دو خاصیت بدون نیاز به هیچ تعریف خاصی، observable در نظر گرفته میشوند. Fi به علاوه خاصیت getter آن به نام emoji نیز به عنوان یک خاصیت محاسباتی MobX تفسیر شده و متد toggle آن به صورت یک action پردازش میشود. بنابراین در حین کار با MobX Hooks دیگر نیازی به تغییر ساختار پروژهی React، برای پشتیبانی از decorators نیست.
- در این مثال، return useObserver را نیز مشاهده میکنید. کار آن رندر مجدد کامپوننت، با تغییر یکی از خواص observable ردیابی شدهی توسط آن است.
امکان تعریف global state با کمک useLocalStore
نام useLocalStore از این جهت انتخاب شدهاست که مشخص کند مخزن حالت ایجاد شدهی توسط آن، درون یک کامپوننت به صورت محلی ایجاد میشود و سراسری نیست. اما این نکته به این معنا نیست که نمیتوان مخزن حالت ایجاد شدهی توسط آنرا در بین سلسه مراتب کامپوننتهای برنامه به اشتراک گذاشت. توسط تابع useLocalStore میتوان چندین مخزن حالت را ایجاد کرد و سپس توسط شیءای دیگر آنها را یکی کرده و در آخر به کمک Context API خود React آنرا در اختیار تمام کامپوننتهای برنامه قرار داد.
تا نگارش MobX 5x (و همچنین پس از آن)، توسط inject@ میتوان یک مخزن حالت را در اختیار یک کامپوننت قرار داد (مانند inject('myStore')). طراحی inject@ مربوط است به زمانیکه امکان دسترسی به Context پشت صحنهی React به صورت عمومی توسط Context API آن ارائه نشده بود. به همین جهت از این پس دیگر نیازی به استفادهی از آن نیست.
چگونه توسط MobX Hooks، یک مخزن حالت سراسری را ایجاد کنیم؟
برای ایجاد یک مخزن حالت سراسری با روش جدید MobX Hooks، مراحل زیر را میتوان طی کرد:
الف) ایجاد شیء store
ابتدا متدی را مانند createStore ایجاد میکنیم، به نحوی که یک شیء را بازگشت دهد. این شیء همانطور که عنوان شد، خواصش، getters و متدهای آن، توسط MobX ردیابی خواهند شد (مانند const todo = useLocalStore مثال فوق) و نیازی به اعمال MobX Decorators را ندارند.
ب) برپایی Context
اینبار دیگر نه از شیء Provider خود MobX استفاده میکنیم و نه از تزئین کنندهی inject@ آن؛ بلکه از React Context استاندارد استفاده خواهیم کرد:
- در اینجا فرض شدهاست که تابع createStore که شیء store ما را ارائه میدهد از ماژولی به نام createStore دریافت میشود.
- سپس توسط React.createContext، یک شیء Context استاندارد React را ایجاد میکنیم؛ به نام storeContext.
- تابع کمکی StoreProvider، جایگزین شیء Provider قبلی MobX میشود. یعنی کارش محصور کردن کامپوننت App برنامه است تا شیء store را در اختیار سلسه مراتب کامپوننتهای React قرار دهد. در اینجا children به همان کامپوننتهایی که قرار است توسط Context.Provider محصور شوند اشاره میکند.
- تابع کمکی useStore، جهت محصور کردن متد React.useContext، اضافه شدهاست. میتوانید useContext Hook را به صورت مستقیم در کامپوننتهای تابعی فراخوانی کنید و یا میتوانید از متد کمکی useStore بجای آن استفاده نمائید تا حجم کدهای تکراری برنامه کاهش یابد.
ج) استفادهی از StoreProvider تهیه شده
اکنون با استفاده از متد StoreProvider فوق که شیء Context.Provider استاندارد React را بازگشت میدهد، میتوان کامپوننتهای بالاترین کامپوننت سلسه مراتب کامپوننتهای برنامه را محصور کرد، تا تمام آنها بتوانند به store ذخیره شدهی در Provider، دسترسی پیدا کنند:
د) استفاده از store مهیا شده در کامپوننتهای تابعی برنامه
پس از تهیهی متدی کمکی useStore که در حقیقت همان useContext Hook است، میتوان به کمک آن در کامپوننتهای تابعی، به store و تمام امکانات آن دسترسی پیدا کرد:
به این ترتیب دیگر نیازی به inject@ نخواهد بود.
سؤال: آیا هنوز هم میتوان یک مخزن پیچیدهی متشکل از چندین کلاس را تشکیل داد؟
پاسخ: بله. برای مثال ابتدا دو کلاس جدید CounterStore و ThemeStore را به نحو متداولی، با استفادهی از MobX decorators طراحی میکنیم (دقیقا مانند مثال قسمت قبل). سپس بجای ذکر نال، بجای پارامتر متد createContext، آنرا با یک شیء جدید مقدار دهی میکنیم که هر کدام از خواص آن، به یک وهله از مخازن حالت ایجاد شده اشاره میکند:
با این تعییر اگر در کامپوننتی از برنامه نیاز به برای مثال شیء منتسب به خاصیت counterStore را داشتیم، میتوان به صورت زیر عمل کرد:
چند نکتهی تکمیلی
نکته 1: با اشیاء MobX از Object Destructuring استفاده نکنید!
اگر بر روی اشیاء MobX از Object Destructuring استفاده کنیم، خروجی آن تبدیل به متغیرهای سادهای خواهند شد که دیگر ردیابی نمیشوند.
برای مثال اگر counterStore مثال فوق به همراه خاصیت observable ای به نام activeUserName است، آنرا به صورت زیر تبدیل به متغیر activeUserName نکنید؛ چون دیگر reactive نخواهد بود:
فقط بالاترین سطح مخزن را به صورت زیر توسط Object Destructuring از آن استخراج و سپس استفاده کنید:
نکته 2: مدیریت side effects با MobX
در مورد اثرات جانبی و side effects در مطلب «قسمت 32 - React Hooks - بخش 3 - نکات ویژهی برقراری ارتباط با سرور» بیشتر بحث شد. اگر یک اثر جانبی مانند تنظیم document.title، به مقدار یک خاصیت observable وابسته بود، میتوان از متد autorun که تغییرات آنها را ردیابی میکند، درون useEffect Hook استاندارد، استفاده کرد:
در حین کار با MobX، هیچگاه نیازی به ذکر وابستگیهای تابع useEffect نیست؛ چون اساسا وجود خارجی ندارند و توسط خود MobX مدیریت میشوند و به store وابستهاند و نه به حالت کامپوننت جاری.
نکته 4: روش فعالسازی MobX strict mode
اگر strict mode را در Mobx به روش زیر فعال کنیم:
پس از آن باید حالت مدیریت شدهی توسط MobX را فقط و فقط توسط actionهای آن تغییر داد و اگر سعی در تغییر مقدار مستقیم یک خاصیت observable کنیم، استثنایی صادر خواهد شد. برای تغییر خواص observable باید آنها را درون یک action قرار داد؛ تا مطابق رهنمودهای طراحی کلاسهای MobX باشد.
نکته 3: روش انجام اعمال async در MobX
فرض کنید یک عملیات async را در یک اکشن متد کلاس حالت MobX، به صورت زیر انجام دادهایم و نتیجهی آن به خاصیت weatherData آن کلاس که observable است، به صورت مستقیم انتساب داده شدهاست:
هرچند loadWeather یک متد را ارائه میدهد که به صورت action معرفی شدهاست، اما هرچیزی که داخل آن قرار میگیرد، الزاما تحت کنترل آن نیست. برای مثال متد then، یک تابع callback جدید را فراخوانی میکند که اعمال آن، تحت کنترل loadWeather نیست. به همین جهت اگر strict mode را فعال کرده باشیم، عنوان میکند که خواص observable را باید درون یک اکشن متد تغییر داد و نه به صورت مستقیم؛ مانند this.weatherData در اینجا.
راه حل اول: تغییر خاصیت this.weatherData را به یک اکشن متد مجزا انتقال میدهیم:
اکنون میتوان قسمت then را به صورت then(data => this.setWeather(data)) نوشت و خطای یاد شده برطرف میشود.
راه حل دوم: اگر نمیخواهیم یک اکشن متد جدید را تعریف کنیم، میتوان از متد کمکی runInAction در داخل یک callback استفاده کرد:
runInAction یکی از متدهای قابل دریافت از mobx است.
در مورد اعمال async/await چطور؟
در اینجا هم تفاوتی نمیکند. هر چیزی پس از await، شبیه به حالت متد then پردازش میشود. به همین جهت در اینجا نیز باید از یکی از دو راه حل ارائه شده، استفاده کرد:
به صورت خلاصه:
- اگر فقط از کامپوننتهای کلاسی استفاده میکنید، mobx-react@5 برای کار شما پاسخگو است.
- اگر از کامپوننتهای کلاسی و همچنین کامپوننتهای تابعی در برنامهی خود استفاده میکنید، mobx-react@6 به همراه mobx-react-lite نیز ارائه میشود و هر دو روش را با هم پوشش میدهد.
- اگر فقط از کامپوننتهای تابعی جدید استفاده میکنید، هوکهای کتابخانهی کوچک mobx-react-lite برای کار شما کافی است.
معرفی useLocalStore Hook و useObserver Hook
در مطالب قبلی، روش تعریف یک کلاس مخزن حالت MobX را توسط تزئین کنندههایی مانند observable، computed و action بررسی کردیم. همچنین دریافتیم که تعریف یک چنین تزئین کنندههایی، یا نیاز به استفادهی از تایپاسکریپت را دارد و یا باید پروژهی React را جهت تغییر کامپایلر Babel آن و فعالسازی decorators، مقداری ویرایش کرد. با useLocalStore Hook هرچند تمام روشهای قبلی هنوز هم پشتیبانی میشوند، اما دیگر نیاز به استفادهی از decorators نیست. useLocalStore تابعی است که یک شیء را باز میگرداند. هر خاصیتی از این شیء، به صورت خودکار observable درنظر گرفته میشود. تمام getters آن به عنوان computed properties تفسیر میشوند و تمام متدهای آن، action درنظر گرفته خواهند شد.
یک مثال:
import React from 'react' import { useLocalStore, useObserver } from 'mobx-react' // 6.x export const SmartTodo = () => { const todo = useLocalStore(() => ({ title: 'Click to toggle', done: false, toggle() { todo.done = !todo.done }, get emoji() { return todo.done ? '😜' : '🏃' }, })) return useObserver(() => ( <h3 onClick={todo.toggle}> {todo.title} {todo.emoji} </h3> )) }
- روش استفادهی از تابع useLocalStore، میتواند به صورت محلی (همانند اسم آن) مختص به یک کامپوننت باشد. یعنی میتوان بجای state استاندارد React که اجازهی تغییر مستقیم خواص آنرا نمیدهد، از MobX State محلی ارائه شدهی توسط useLocalStore استفاده کرد و یا میتوان useLocalStore را به صورت global نیز تعریف کرد که در ادامهی بحث به آن میپردازیم.
- در مثال فوق، طول عمر شیء ایجاد شدهی توسط useLocalStore، محلی و محدود به طول عمر کامپوننت تابعی تعریف شدهاست.
- در اینجا شیء بازگشت داده شدهی توسط useLocalStore، دارای دو خاصیت title و done است. این دو خاصیت بدون نیاز به هیچ تعریف خاصی، observable در نظر گرفته میشوند. Fi به علاوه خاصیت getter آن به نام emoji نیز به عنوان یک خاصیت محاسباتی MobX تفسیر شده و متد toggle آن به صورت یک action پردازش میشود. بنابراین در حین کار با MobX Hooks دیگر نیازی به تغییر ساختار پروژهی React، برای پشتیبانی از decorators نیست.
- در این مثال، return useObserver را نیز مشاهده میکنید. کار آن رندر مجدد کامپوننت، با تغییر یکی از خواص observable ردیابی شدهی توسط آن است.
امکان تعریف global state با کمک useLocalStore
نام useLocalStore از این جهت انتخاب شدهاست که مشخص کند مخزن حالت ایجاد شدهی توسط آن، درون یک کامپوننت به صورت محلی ایجاد میشود و سراسری نیست. اما این نکته به این معنا نیست که نمیتوان مخزن حالت ایجاد شدهی توسط آنرا در بین سلسه مراتب کامپوننتهای برنامه به اشتراک گذاشت. توسط تابع useLocalStore میتوان چندین مخزن حالت را ایجاد کرد و سپس توسط شیءای دیگر آنها را یکی کرده و در آخر به کمک Context API خود React آنرا در اختیار تمام کامپوننتهای برنامه قرار داد.
تا نگارش MobX 5x (و همچنین پس از آن)، توسط inject@ میتوان یک مخزن حالت را در اختیار یک کامپوننت قرار داد (مانند inject('myStore')). طراحی inject@ مربوط است به زمانیکه امکان دسترسی به Context پشت صحنهی React به صورت عمومی توسط Context API آن ارائه نشده بود. به همین جهت از این پس دیگر نیازی به استفادهی از آن نیست.
چگونه توسط MobX Hooks، یک مخزن حالت سراسری را ایجاد کنیم؟
برای ایجاد یک مخزن حالت سراسری با روش جدید MobX Hooks، مراحل زیر را میتوان طی کرد:
الف) ایجاد شیء store
ابتدا متدی را مانند createStore ایجاد میکنیم، به نحوی که یک شیء را بازگشت دهد. این شیء همانطور که عنوان شد، خواصش، getters و متدهای آن، توسط MobX ردیابی خواهند شد (مانند const todo = useLocalStore مثال فوق) و نیازی به اعمال MobX Decorators را ندارند.
export function createStore() { return { // ... } }
ب) برپایی Context
اینبار دیگر نه از شیء Provider خود MobX استفاده میکنیم و نه از تزئین کنندهی inject@ آن؛ بلکه از React Context استاندارد استفاده خواهیم کرد:
import React from 'react'; import { createStore } from './createStore'; import { useLocalStore } from 'mobx-react'; // 6.x or mobx-react-lite@1.4.0 const storeContext = React.createContext(null); export const StoreProvider = ({ children }) => { const store = useLocalStore(createStore); return <storeContext.Provider value={store}>{children}</storeContext.Provider>; } export const useStore = () => { const store = React.useContext(storeContext); if (!store) { throw new Error('useStore must be used within a StoreProvider.'); } return store }
- سپس توسط React.createContext، یک شیء Context استاندارد React را ایجاد میکنیم؛ به نام storeContext.
- تابع کمکی StoreProvider، جایگزین شیء Provider قبلی MobX میشود. یعنی کارش محصور کردن کامپوننت App برنامه است تا شیء store را در اختیار سلسه مراتب کامپوننتهای React قرار دهد. در اینجا children به همان کامپوننتهایی که قرار است توسط Context.Provider محصور شوند اشاره میکند.
- تابع کمکی useStore، جهت محصور کردن متد React.useContext، اضافه شدهاست. میتوانید useContext Hook را به صورت مستقیم در کامپوننتهای تابعی فراخوانی کنید و یا میتوانید از متد کمکی useStore بجای آن استفاده نمائید تا حجم کدهای تکراری برنامه کاهش یابد.
ج) استفادهی از StoreProvider تهیه شده
اکنون با استفاده از متد StoreProvider فوق که شیء Context.Provider استاندارد React را بازگشت میدهد، میتوان کامپوننتهای بالاترین کامپوننت سلسه مراتب کامپوننتهای برنامه را محصور کرد، تا تمام آنها بتوانند به store ذخیره شدهی در Provider، دسترسی پیدا کنند:
export default function App() { return ( <StoreProvider> <main> <Component1 /> <Component2 /> <Component3 /> </main> </StoreProvider> ); }
د) استفاده از store مهیا شده در کامپوننتهای تابعی برنامه
پس از تهیهی متدی کمکی useStore که در حقیقت همان useContext Hook است، میتوان به کمک آن در کامپوننتهای تابعی، به store و تمام امکانات آن دسترسی پیدا کرد:
const store = useStore();
سؤال: آیا هنوز هم میتوان یک مخزن پیچیدهی متشکل از چندین کلاس را تشکیل داد؟
پاسخ: بله. برای مثال ابتدا دو کلاس جدید CounterStore و ThemeStore را به نحو متداولی، با استفادهی از MobX decorators طراحی میکنیم (دقیقا مانند مثال قسمت قبل). سپس بجای ذکر نال، بجای پارامتر متد createContext، آنرا با یک شیء جدید مقدار دهی میکنیم که هر کدام از خواص آن، به یک وهله از مخازن حالت ایجاد شده اشاره میکند:
export const storesContext = React.createContext({ counterStore: new CounterStore(), themeStore: new ThemeStore(), }); export const useStores = () => React.useContext(storesContext);
const { counterStore } = useStores();
چند نکتهی تکمیلی
نکته 1: با اشیاء MobX از Object Destructuring استفاده نکنید!
اگر بر روی اشیاء MobX از Object Destructuring استفاده کنیم، خروجی آن تبدیل به متغیرهای سادهای خواهند شد که دیگر ردیابی نمیشوند.
برای مثال اگر counterStore مثال فوق به همراه خاصیت observable ای به نام activeUserName است، آنرا به صورت زیر تبدیل به متغیر activeUserName نکنید؛ چون دیگر reactive نخواهد بود:
const { counterStore: { activeUserName }, } = useStores();
const { counterStore } = useStores();
نکته 2: مدیریت side effects با MobX
در مورد اثرات جانبی و side effects در مطلب «قسمت 32 - React Hooks - بخش 3 - نکات ویژهی برقراری ارتباط با سرور» بیشتر بحث شد. اگر یک اثر جانبی مانند تنظیم document.title، به مقدار یک خاصیت observable وابسته بود، میتوان از متد autorun که تغییرات آنها را ردیابی میکند، درون useEffect Hook استاندارد، استفاده کرد:
import React from 'react' import { autorun } from 'mobx' function useDocumentTitle(store) { React.useEffect( () => autorun(() => { document.title = `${store.title} - ${store.sectionName}` }), [], // note empty dependencies ) }
نکته 4: روش فعالسازی MobX strict mode
اگر strict mode را در Mobx به روش زیر فعال کنیم:
import { configure } from "mobx"; configure({ enforceActions: true });
نکته 3: روش انجام اعمال async در MobX
فرض کنید یک عملیات async را در یک اکشن متد کلاس حالت MobX، به صورت زیر انجام دادهایم و نتیجهی آن به خاصیت weatherData آن کلاس که observable است، به صورت مستقیم انتساب داده شدهاست:
@action loadWeather = city => { fetch( `https://abnormal-weather-api.herokuapp.com/cities/search?city=${city}` ) .then(response => response.json()) .then(data => { this.weatherData = data; }); };
راه حل اول: تغییر خاصیت this.weatherData را به یک اکشن متد مجزا انتقال میدهیم:
@action setWeather = data => { this.weatherData = data; };
راه حل دوم: اگر نمیخواهیم یک اکشن متد جدید را تعریف کنیم، میتوان از متد کمکی runInAction در داخل یک callback استفاده کرد:
loadWeatherInline = city => { fetch(`http://jsonplaceholder.typicode.com/comments/${city}`) .then(response => response.json()) .then(data => { runInAction(() => (this.weatherData = data)); }); };
در مورد اعمال async/await چطور؟
در اینجا هم تفاوتی نمیکند. هر چیزی پس از await، شبیه به حالت متد then پردازش میشود. به همین جهت در اینجا نیز باید از یکی از دو راه حل ارائه شده، استفاده کرد:
loadWeatherAsync = async city => { const response = await fetch( `http://jsonplaceholder.typicode.com/comments/${city}` ); const data = await response.json(); runInAction(() => { this.weatherData = data; }); };
اگر مایل هستید که پروژه خود را به صورت سورس باز ارائه دهید، نیاز است یک سری شرایط را رعایت کنید تا کاربران این پروژه بتوانند به سادگی از آن استفاده نمایند.
- فایل ReadMe را فراموش نکنید
حتی اگر پروژه شما از یک سایت اختصاصی استفاده میکند، اولین محلی که عموم کاربران برای دریافت اطلاعات کار با پروژه، به آن مراجعه میکنند، فایل ReadMe برنامه است. این فایل میتواند حاوی مشخصات ذیل باشد:
الف) وابستگیهای پروژه را مشخص کنید
واقعیت این است که برخلاف شمای برنامه نویس، عموم استفاده کنندگان، آشنایی چندانی با جزئیات محیط و شرایط تهیه برنامه شما ندارند. به این ترتیب بسیاری از مسایلی که برای شما بدیهی هستند، برای عموم اینگونه نخواهند بود. بنابراین مسالهای که به سرعت میتواند سبب خشم کاربران و صرفنظر از کار شما گردد، مشخص نبودن نحوه نصب و وابستگیهای لازم برای اجرای برنامه است.
ب) وضعیت بلوغ پروژه خود را مشخص کنید
آیا از این برنامه، مدتی است که در محیط کاری استفاده میکنید؟ آیا به نظر شما هنوز ناتمام است؟ آیا API کتابخانه شما در نگارش بعدی کاملا دگرگون خواهد شد؟ تمام این مسایل و سؤالات را به نحو واضحی توضیح دهید و مشخص کنید. همین توضیحات کوتاه میتوانند ساعتهای بسیاری از زندگی دیگران را صرفه جویی کند.
ج) اگر پروژه شما یک کتابخانه است، نوع زبان و Runtimeهای پشتیبانی شده را مشخص کنید
برای مثال اگر یک کتابخانه دات نتی را ارائه میدهید، مشخص کنید که از کدام نگارش دات نت به بعد را پشتیبانی میکنید.
د) مجوز استفاده از پروژه را مشخص کنید
مطلب مقایسه مجوزهای سورس باز را یکبار مطالعه نمائید و سپس مجوز صحیحی را برای کار خود انتخاب کنید. همچنین آنرا به نحو واضحی در مستندات پروژه خود قید نمائید.
به علاوه بهخاطر داشته باشید که امکان ارائه مجوزهای دوگانه مانند AGPL نیز وجود دارند. در این حالت کاربر یا باید سورس محصول خودش را ارائه دهد، یا مجوز کتابخانه شما را خریداری کند. مانند RavenDB که از این نوع مجوز استفاده میکند.
- یک پروژه نیاز به مستندات دارد
مستند سازی کار، سخت و زمانبر است؛ اما بهترین لطفی است که میتوانید به کاربران خود نمائید. مستندات نه تنها زمان جستجوی بسیاری را صرفه جویی خواهند کرد، همچنین حس اطمینان خاطر را به کاربر القاء میکنند. از این جهت که احساس میکنند شما برای کارتان ارزش قائل بودهاید و احتمال اینکه این برنامه در آینده نزدیک به یک abandonware تبدیل شود، کم است (منظور یک برنامه فراموش شده و خاتمه یافته).
- به روز رسانی را ساده کنید
بالاخره زمانی نیاز خواهد بود تا نگارش جدیدی از کار خود را ارائه دهید. در این حالت نیاز است یک سری از شرایط را مدنظر داشته باشید:
الف) سازگاری قبلی را مدنظر داشته باشید
یکی از بدترین حالات به روز رسانی یک کتابخانه زمانی است که کاربر آن با دهها خطای کامپایل حاصل از به روز رسانی مواجه شود. اگر نیاز است قسمتی از کد خود را حذف کنید یا تغییر دهید، استفاده از ویژگی Obsolete را فراموش نکنید و اینکار باید مرحله به مرحله انجام شود. در یک نگارش، ویژگی Obsolete را معرفی کنید. در دو نگارش بعد، API را تغییر دهید.
ب) حتما یک Change log را تکمیل کنید
پس از ارائه یک نگارش جدید، حداقل در چند سطر مشخص کنید که چه مواردی تغییر کردهاند، چه مواردی اضافه شدهاند و چه مواردی را حذف کردهاید.
همچنین اگر مواردی تغییر کردهاند، نحوه ارتقاء کدهای قدیمی را به نگارش جدید، شرح دهید. اگر مورد جدیدی اضافه شدهاست، لینکی را به مثالی دربارهی آن ارائه دهید.
- نگارشهای جدید را اعلام کنید
برای مثال در طی ارائه یک مطلب جدید در وبلاگ خود، ارائه نگارش جدیدی از کتابخانه یا برنامه خود را به عموم اعلام کنید. در این حالت، حتما لینکی را به change log، ارائه داده و مشخص کنید که وضعیت سازگاری آن با قبل چگونه است.
- محلی را برای دریافت بازخوردهای پروژه خود مشخص کنید
نیاز است بتوانید پروژه خود را پشتیبانی کنید یا به سؤالات مربوطه پاسخ دهید. اگر سورس کنترل یا برنامه مدیریت پروژه شما، امکان پرسش و پاسخ را دارد، که بسیار خوب. اگر خیر، میتوانید مثلا یک گروه گوگل جدید و امثال آنرا برای دریافت بازخوردهای پروژه ایجاد کنید.
همچنین نیاز است لینک به این محل را در فایل ReadME پروژه به صراحت مشخص کنید.
- گذر از پروژه
بالاخره روزی فراخواهد رسید که دیگر علاقهای به نگهداری پروژه نداشته باشید. این مساله را در مکان جمع آوری بازخوردهای خود اعلام کنید یا شخص دیگری را به نگهداری پروژه دعوت نمائید. اگر این کار را انجام ندهید، سبب خواهید شد forkهای متعددی از این پروژه بیجهت ایجاد شده و در نهایت مشخص نباشد که کدامیک بهتر است و کدامیک مشکلات کمتری دارند.
- فایل ReadMe را فراموش نکنید
حتی اگر پروژه شما از یک سایت اختصاصی استفاده میکند، اولین محلی که عموم کاربران برای دریافت اطلاعات کار با پروژه، به آن مراجعه میکنند، فایل ReadMe برنامه است. این فایل میتواند حاوی مشخصات ذیل باشد:
الف) وابستگیهای پروژه را مشخص کنید
واقعیت این است که برخلاف شمای برنامه نویس، عموم استفاده کنندگان، آشنایی چندانی با جزئیات محیط و شرایط تهیه برنامه شما ندارند. به این ترتیب بسیاری از مسایلی که برای شما بدیهی هستند، برای عموم اینگونه نخواهند بود. بنابراین مسالهای که به سرعت میتواند سبب خشم کاربران و صرفنظر از کار شما گردد، مشخص نبودن نحوه نصب و وابستگیهای لازم برای اجرای برنامه است.
ب) وضعیت بلوغ پروژه خود را مشخص کنید
آیا از این برنامه، مدتی است که در محیط کاری استفاده میکنید؟ آیا به نظر شما هنوز ناتمام است؟ آیا API کتابخانه شما در نگارش بعدی کاملا دگرگون خواهد شد؟ تمام این مسایل و سؤالات را به نحو واضحی توضیح دهید و مشخص کنید. همین توضیحات کوتاه میتوانند ساعتهای بسیاری از زندگی دیگران را صرفه جویی کند.
ج) اگر پروژه شما یک کتابخانه است، نوع زبان و Runtimeهای پشتیبانی شده را مشخص کنید
برای مثال اگر یک کتابخانه دات نتی را ارائه میدهید، مشخص کنید که از کدام نگارش دات نت به بعد را پشتیبانی میکنید.
د) مجوز استفاده از پروژه را مشخص کنید
مطلب مقایسه مجوزهای سورس باز را یکبار مطالعه نمائید و سپس مجوز صحیحی را برای کار خود انتخاب کنید. همچنین آنرا به نحو واضحی در مستندات پروژه خود قید نمائید.
به علاوه بهخاطر داشته باشید که امکان ارائه مجوزهای دوگانه مانند AGPL نیز وجود دارند. در این حالت کاربر یا باید سورس محصول خودش را ارائه دهد، یا مجوز کتابخانه شما را خریداری کند. مانند RavenDB که از این نوع مجوز استفاده میکند.
- یک پروژه نیاز به مستندات دارد
مستند سازی کار، سخت و زمانبر است؛ اما بهترین لطفی است که میتوانید به کاربران خود نمائید. مستندات نه تنها زمان جستجوی بسیاری را صرفه جویی خواهند کرد، همچنین حس اطمینان خاطر را به کاربر القاء میکنند. از این جهت که احساس میکنند شما برای کارتان ارزش قائل بودهاید و احتمال اینکه این برنامه در آینده نزدیک به یک abandonware تبدیل شود، کم است (منظور یک برنامه فراموش شده و خاتمه یافته).
- به روز رسانی را ساده کنید
بالاخره زمانی نیاز خواهد بود تا نگارش جدیدی از کار خود را ارائه دهید. در این حالت نیاز است یک سری از شرایط را مدنظر داشته باشید:
الف) سازگاری قبلی را مدنظر داشته باشید
یکی از بدترین حالات به روز رسانی یک کتابخانه زمانی است که کاربر آن با دهها خطای کامپایل حاصل از به روز رسانی مواجه شود. اگر نیاز است قسمتی از کد خود را حذف کنید یا تغییر دهید، استفاده از ویژگی Obsolete را فراموش نکنید و اینکار باید مرحله به مرحله انجام شود. در یک نگارش، ویژگی Obsolete را معرفی کنید. در دو نگارش بعد، API را تغییر دهید.
ب) حتما یک Change log را تکمیل کنید
پس از ارائه یک نگارش جدید، حداقل در چند سطر مشخص کنید که چه مواردی تغییر کردهاند، چه مواردی اضافه شدهاند و چه مواردی را حذف کردهاید.
همچنین اگر مواردی تغییر کردهاند، نحوه ارتقاء کدهای قدیمی را به نگارش جدید، شرح دهید. اگر مورد جدیدی اضافه شدهاست، لینکی را به مثالی دربارهی آن ارائه دهید.
- نگارشهای جدید را اعلام کنید
برای مثال در طی ارائه یک مطلب جدید در وبلاگ خود، ارائه نگارش جدیدی از کتابخانه یا برنامه خود را به عموم اعلام کنید. در این حالت، حتما لینکی را به change log، ارائه داده و مشخص کنید که وضعیت سازگاری آن با قبل چگونه است.
- محلی را برای دریافت بازخوردهای پروژه خود مشخص کنید
نیاز است بتوانید پروژه خود را پشتیبانی کنید یا به سؤالات مربوطه پاسخ دهید. اگر سورس کنترل یا برنامه مدیریت پروژه شما، امکان پرسش و پاسخ را دارد، که بسیار خوب. اگر خیر، میتوانید مثلا یک گروه گوگل جدید و امثال آنرا برای دریافت بازخوردهای پروژه ایجاد کنید.
همچنین نیاز است لینک به این محل را در فایل ReadME پروژه به صراحت مشخص کنید.
- گذر از پروژه
بالاخره روزی فراخواهد رسید که دیگر علاقهای به نگهداری پروژه نداشته باشید. این مساله را در مکان جمع آوری بازخوردهای خود اعلام کنید یا شخص دیگری را به نگهداری پروژه دعوت نمائید. اگر این کار را انجام ندهید، سبب خواهید شد forkهای متعددی از این پروژه بیجهت ایجاد شده و در نهایت مشخص نباشد که کدامیک بهتر است و کدامیک مشکلات کمتری دارند.