در این مجموعه مقالات، به بررسی و آموزش برنامه نویسی شیء گرا در جاوا اسکریپت میپردازیم. در طول آموزش، فرض را بر این قرار دادیم که شما به عنوان خوانندهی این مقاله، با مبانی جاوا اسکریپت آشنا میباشید و حداقل چند قطعه کد مفید را با جاوا اسکریپت نوشتهاید. همچنین کمی هم با مباحث شیء گرایی آشنا میباشید.
روال آموزش در این مجموعه به گونه است که در ابتدا به معرفی مباحث پیش نیاز جهت ورود به دنیای شیء گرایی در جاوا اسکریپت، پرداخته خواهد شد. سپس مباحث شیء گرایی را آغاز خواهیم نمود و تمامی نکات ریز و درشت آن را بررسی خواهیم کرد. پس از پایان این مقالات قادر خواهید بود تا تمامی کتابخانهها و Frameworkهای جاوا اسکریپتی را مطالعه نموده و به راحتی تکنیکهای کد نویسی آن را درک کنید. همچنین خود شما نیز برای نوشتن یک کتابخانه یا Framework جاوا اسکریپتی استاندارد و حرفهای، دست به کار شوید و یک کتابخانه سودمند را ارائه نمایید.
با چند مثال ساده و بصورت تقریبا مبتدی مسائل پایه را تشریح نموده و آرام آرام مباحث حرفه ای را آغاز خواهیم کرد. قرار است در پایان این آموزشها به بررسی الگوهای موجود در جاوا اسکریپت نیز بپردازم که مجموعه مقالات مربوط به الگوها را در قالب مجموعه مقالاتی مجزا ارائه مینمایم.
در تهیهی این مجموعه مقالات از منابع زیر استفاده شده است:
1. Pro JavaScript Techniques (John Resig ، خالق JQuery – فصول 2 و 3)
2. Professional JavaScript for Web Developers (Third Edition) (Nicholas C. Zakas – فصول 3، 4، 5، 6 و 7)
3. Object-Oriented JavaScript (Stoyan Stefanov – فصول 3، 4، 5، 6 و 8)
4. و تجربهی ناچیز اینجانب
توابع (Functions)
همیشه در برنامه، مجموعه دستوراتی وجود دارند که عمل خاصی را انجام میدهند و به دفعات نیز مورد استفاده قرار میگیرند. جهت جلوگیری از تکرار این دستورات، افزایش خوانایی و کاهش پیچیدگی برنامه، این دستورات را در بستهای به نام تابع قرار میدهیم تا در زمان نیاز، آن را فراخوانی یا اجرا نماییم. جهت تعریف تابع در جاوا اسکریپت به صورت زیر عمل مینماییم:
function <functionName> ([arg0, arg1, …, argN]) { <statements> }
به عنوان مثال:
function sayHello(name, message) { alert("Hello " + name + ", " + message); }
این تابع شامل دو آرگومان ورودی است که میتواند به صورت زیر فراخوانی شود:
sayHello("Meysam", "welcome to site");
خروجی این تابع “Hello Meysam, welcome to site” می باشد که به صورت یک پنجرهی پیغام، نمایش مییابد. تابع فوق هیچ مقداری را به عنوان خروجی به برنامهی اصلی یا محل فراخوانی خود بر نمیگرداند. اگر بخواهیم توسط تابع مقداری را برگردانیم میتوانیم از دستور return برای این منظور استفاده نماییم. به مثال زیر توجه کنید:
function sum(num1, num2) { return num1 + num2; }
تابع sum دو عدد را به عنوان آرگومان ورودی دریافت نموده و حاصل جمع آنها را توسط دستور return به عنوان خروجی بر میگرداند. تابع فوق را میتوان به صورت زیر فراخوانی نمود:
alert(sum(10, 20));
خروجی :
30
اینک به بررسی چند نکته در مورد دستور return میپردازیم. دستور return فقط میتواند یک مقدار را برگرداند و نمیتوان، چند مقدار را مقابل این دستور نوشت. همچنین روال اجرای تابع با رسیدن به دستور return خاتمه مییابد و دستورات بعد از آن اجرا نخواهند شد. به مثال زیر توجه کنید:
function sum(num1, num2) { return num1 + num2; alert("Hello"); }
در مثال فوق دستور alert به هیچ عنوان اجرا نخواهد شد؛ زیرا تابع با رسیدن به دستور return خاتمه مییابد. یک تابع میتواند شامل بیش از یک دستور return باشد.
function diff(num1,num2) { if (num1 > num2) return num1 - num2; else return num2 - num1; }
تابع فوق اختلاف دو عدد را بدست میآورد و اگر عدد اول بزرگتر باشد، عدد دوم را از عدد اول تفریق میکند؛ در غیر اینصورت عدد اول را از عدد دوم تفریق میکند و به عنوان خروجی بر میگرداند. نکتهی دیگری که لازم است بدانید این است که دستور return می تواند هیچ مقداری را بر نگرداند و به تنهایی بکار گرفته شود. در این صورت دقیقا بعد از دستور return سمی کالن (;) قرار میدهیم.
function checkNumber(num) { if (isNaN(num)) { alert("Not a number"); return; } alert(num+" is a number"); }
تابع فوق یک ورودی را دریافت مینماید و در صورتی که آرگومان ورودی عدد نباشد پیغام “Not a number” را نمایش میدهد و از تابع خارج میشود. در صورتی که آرگومان ورودی یک عدد باشد، پیغام دوم را نمایش میدهد.
توجه:
یک تابع بهتر است همیشه یک مقداری را به عنوان خروجی برگرداند و یا کلا هیچ مقداری را بر نگرداند. نوشتن توابعی که در برخی شرایط مقداری را به عنوان خروجی بر میگردانند و در برخی شرایط مقداری را برنمی گردانند موجب ایجاد پیچیدگی در اشکال زدایی میگردند. نوشتن تابعی به صورت زیر کمی گیج کننده و نامتعارف میباشد:
function sum(num1, num2) { if (isNaN(num1) || isNaN(num2)) return; // بهتر است حداقل مقدار 0 برگردانده شود return num1 + num2; }
کار با آرگومان ها
رفتار آرگومانها در جاوا اسکریپت نسبت به سایر زبانهای برنامه نویسی کاملا متفاوت میباشد. در جاوا اسکریپت تعداد و نوع آرگومانهای ارسالی بررسی نمیشوند و خطایی هم رخ نخواهد داد. به عنوان مثال اگر تابعی با 3 آرگومان ورودی دارید، میتوانید با 0 تا 3 آرگومان ورودی، آن تابع را فراخوانی نمایید. زیرا آرگومانها در سیستم داخلی جاوا اسکریپت به صورت یک آرایه ارسال میگردند و تابع توجه نمیکند که کدام آرگومانها به این آرایه ارسال شدهاند.
با توجه به قابلیتی که در مورد آرگومانها ذکر شد و به دلیل عدم مدیریت نوع و تعداد آرگومانهای ارسالی، مطمئنا جهت جلوگیری از بروز خطا در توابع، باید تعداد و نوع آرگومانهای ارسالی بررسی و مدیریت شوند. همچنین در هر تابع، آرایهای به نام arguments به صورت توکار تعبیه شده است که مدیریت آرگومانها را تسهیل میبخشد. به مثال زیر توجه کنید:
function sayHello() { alert("Hello " + arguments[0] + ", " + arguments[1]); } sayHello("Meysam", "welcome to site");
خروجی :
"Hello Meysam, welcome to site"
تابع فوق هیچ آرگومان ورودی ندارد ولی با دو آرگومان ورودی فراخوانی شده است. در داخل تابع توسط آرایه arguments به آرگومانهای ارسالی دسترسی پیدا کردیم. حال به مثال زیر توجه کنید:
function sum() { var s = 0; for (var i = 0; i < arguments.length; i++) s += arguments[i]; return s; } alert(sum()); alert(sum(10, 20, 30, 40, 50)); alert(sum(10));
خروجی :
0
150
10
در تابع فوق هیچ آرگومان ورودی وجود ندارد ولی این تابع را با 0، 5 و 1 آرگومان ورودی فراخوانی نمودیم. این تابع مجموع چند عدد را محاسبه و بر میگرداند و میتواند به تعداد نامحدودی عدد دریافت نماید. البته بهتر است نوع آرگومانهای ارسالی نیز بررسی شوند تا خطایی در محاسبات رخ ندهد. همچنین بجای حلقه for از حلقه for…in استفاده خواهم کرد.
function sum() { var s = 0; for (var i in arguments) { if (isNaN(arguments[i])) continue; s += arguments[i]; } return s; } alert(sum()); alert(sum(10, 20, "a", 40, 50)); alert(sum(10));
خروجی :
0
120
10
اگر دقت کرده باشید در فراخوانی دوم، رشته “a” به تابع ارسال شده است و چون آرگومانهای نامعتبر را مدیریت نمودهایم، خطایی در خروجی رخ نمیدهد. به مثال زیر نیز توجه نمایید:
function sum(a,b,c) { return a + b + c; } alert(sum(10, 20, 30)); alert(sum(10, 20)); alert(sum());
خروجی :
60
NaN
NaN
تابع فوق دارای 3 آرگومان ورودی میباشد؛ ولی ما این تابع را با 2 و 0 آرگومان ورودی فراخوانی نمودیم که خروجی نامناسبی را تولید نموده است. برای رفع این مشکل و معتبر سازی آرگومانهای ارسالی میتوانیم به صورت زیر عمل نماییم:
function sum(a, b, c) { if (isNaN(a)) a = 0; if (isNaN(b)) b = 0; if (isNaN(c)) c = 0; return a + b + c; } alert(sum(10, 20, 30)); alert(sum(10, 20)); alert(sum());
خروجی :
60
30
0
در تابع، قبل از انجام عمل محاسبه، بررسی کردیم که آرگومانهای ارسالی مقدار دهی شده باشند. در صورت عدم مقداردهی و یا مقداردهی نامناسب، آرگومان ارسالی را با صفر مقداردهی مینماید. اگر آرگومان مورد نظر به تابع ارسال نشود، مقدار پیش فرض آن undefined میباشد.
با توجه به مسائل مطرح شده در مورد توابع، این روش استفاده و کاربرد توابع، جزء معایب توابع در جاوا اسکریپت محسوب نمیشود. این تکنیک استفاده از توابع، موجب افزایش انعطاف پذیری توابع و آزادی عمل برنامه نویس میگردد که لذت بیشتری را به برنامه نویسی میدهد.
توجه:
به آرگومانهایی که در تابع دارای نام میباشند و یا به عبارتی، آرگومانهایی که نام آنها در تابع ذکر میشود، Named Arguments یا آرگومانهای نامی (اسمی و یا نامدار) میگویند. مثل آرگومان های a ، b وc در تابع sum .
عدم پشتیبانی از سربارگذاری یا Overloading
در زبانهای برنامه نویسی شیء گرا، امکان تعریف توابع هم نام وجود دارد؛ به شرطی که امضای این توابع با هم متفاوت باشند. منظور از امضاء، تعداد و نوع آرگومانهای ورودی میباشد. از آنجاییکه در سیستم داخلی جاوا اسکریپت، آرگومانها بصورت یک آرایه ارسال میشوند، بنابراین امضاء برای توابع مفهومی ندارد؛ پس نمیتوانیم توابع هم نام یا overloading داشته باشیم.
اگر دو تابع هم نام داشته باشیم، تابعی که دیرتر تعریف میشود، جایگزین تابع قبلی میگردد. به مثال زیر توجه کنید:
function calc(num1,num2) { return num1 + num2; } function calc(num1,num2) { return num1 - num2; } alert(calc(200,100));
خروجی :
100
همانطور که در مثال فوق مشاهده مینمایید، تابع دوم فراخوانی شدهاست و حاصل تفریق به عنوان خروجی نمایش یافته است.
روش ایجاد یک پروژهی کتابخانهای، از کامپوننتهای Blazor
اگر از ویژوال استودیو استفاده میکنید، نوع «Razor Class Library»، پروژههای مخصوص کتابخانههای کامپوننتهای Blazor را آغاز میکند و اگر میخواهید از CLI استفاده کنید، باید از دستور «dotnet new razorclasslib» استفاده کرد؛ که قابلیت تبدیل به بستههای نیوگت را با دستور dotnet pack داشته و به این ترتیب میتوان آنها را به اشتراک گذاشت و یا حتی میتوان ارجاعی از این پروژه را در سایر پروژههای شخصی، مورد استفاده قرار داد.
ساختار پیشفرض فایل csproj اینگونه پروژهها به صورت زیر است:
<Project Sdk="Microsoft.NET.Sdk.Razor"> <PropertyGroup> <TargetFramework>net5.0</TargetFramework> </PropertyGroup> <ItemGroup> <SupportedPlatform Include="browser" /> </ItemGroup> <ItemGroup> <PackageReference Include="Microsoft.AspNetCore.Components.Web" Version="5.0.6" /> </ItemGroup> </Project>
روش افزودن فایلهای ثابت مورد استفادهی در کتابخانه
پروژهی پیشفرضی که در اینجا ایجاد میشود، به همراه یک پوشهی wwwroot نیز هست. این پوشه محلی است که باید فایلهای ثابت اشتراکی پروژه را مانند فایلهای CSS و JS مورد استفاده، قرار داد.
این نوع پروژهها از ویژگی Isolated CSS و یا Isolated JS ارائه شدهی در Blazor 5x نیز پشتیبانی میکنند.
روش استفادهی از فایلهای ثابت موجود در یک کتابخانه
این فایلهای ثابت به همراه بستهی نهایی پروژه، به صورت خودکار توزیع میشوند (و نیازی به ارائهی مجزای آنها نیست) و برای استفادهی از آنها در پروژههای دیگر، باید از روش مسیردهی زیر استفاده کرد:
/_content/PackageId/MyImage.png
- PackageId عموما همان نام پروژهی مورد استفادهاست (نام فایل csproj مانند MyBlazorComponentLibrary). هرچند میتوان آنرا به صورت مجزایی در فایل csproj نیز مقدار دهی کرد.
- در این مثال MyImage.png، نام منبعی است که قرار است از آن استفاده کنیم و پیشتر در پوشهی wwwroot کتابخانه، کپی شدهاست و یا حتی میتوان زیر پوشههایی را نیز در اینجا ایجاد و از آنها استفاده کرد؛ مانند:
/_content/MyBlazorComponentLibrary/scripts/HelloWorld.js
<script src="_content/MyBlazorComponentLibrary/exampleJsInterop.js"></script>
روش استفادهی از کتابخانهی نهایی تولید شده
برای استفادهی از کتابخانهی نهایی تولید شده یا میتوان ارجاعی را به فایل csproj آن، به پروژهی خود افزود:
<ItemGroup> <ProjectReference Include="..\MyBlazorComponentLibrary\MyBlazorComponentLibrary.csproj" /> </ItemGroup>
پس از آن، جهت سهولت استفادهی از این کامپوننتهای اشتراکی، بهتر است فضای نام آنها را به فایل Imports.razor_ پروژهی خود افزود؛ تا نیازی به تعریف آنها در تمام کامپوننتهای مورد استفاده نباشد.
یک: ASP.NET Core مستقل از Platform است
دو: Open Source است
سه: جدا بودن از Web Server
using System; using Microsoft.AspNetCore.Hosting; namespace aspnetcoreapp { public class Program { public static void Main(string[] args) { var host = new WebHostBuilder() .UseKestrel() .UseStartup<Startup>() .Build(); host.Run(); } } }
چهار: تزریق وابستگی (Dependency Injection) تو کار
// This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices (IServiceCollection services) { // Add framework services. services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"))); services.AddIdentity<ApplicationUser, IdentityRole>() .AddEntityFrameworkStores<ApplicationDbContext>() .AddDefaultTokenProviders(); services.AddMvc(); // Add application services. services.AddTransient<IEmailSender, AuthMessageSender>(); services.AddTransient<ISmsSender, AuthMessageSender>(); }
پنج: یکپارچگی با frameworkهای مدرن سمت کلاینت
- bundle و minify کردن فایلهای جاوا اسکریپت و همینطور CSS
- اجرای ابزارهایی برای bundle و minify کردن قبل از هر build
- کامپایل کردن فایلهای LESS و SASS در CSS
- کامپیال کردن فایلهای CoffeeScript و TypeScript در JavaScript
NuGet چیست؟
روش متداول استفاده از کتابخانههای موجود دات نتی در Visual studio عموما به این صورت است: مراجعه به سایت مربوطه، دریافت بسته مورد نظر، باز کردن آن و سپس افزودن ارجاعی به اسمبلیهای آن کتابخانه. در این حالت زمانیکه نسخهی جدیدی از کتابخانهی مورد استفاده ارائه شود (و عموما تا مدتها شاید از آن بیاطلاع باشیم) تمام این مراحل باید از ابتدا تکرار شوند و همینطور الی آخر.
برای رفع این نقیصه، تیم ASP.NET، افزونهای سورس باز و رایگان را به نام NuGet جهت VS.Net 2010 طراحی کردهاند که کار مدیریت بستههای کتابخانههای مورد استفاده را بسیار ساده کرده است. امکانات این افزونه پس از نصب، در دو حالت استفاده از رابط گرافیکی کاربری آن و یا با استفاده از خط فرمان PowerShell ویندوز در دسترس خواهد بود. این افزونه در زمان بارگذاری، با مراجعه به فید سایت مرکزی خود، لیست بستههای مهیا را در اختیار علاقمندان قرار میدهد. درب این سایت مرکزی به روی تمام توسعه دهندهها جهت افزودن بستههای خود باز است.
و ... فراگیری کار با NuGet برای تمام برنامه نویسان دات نت لازم و ضروری است! از این جهت که پیغام "این بسته تنها برای NuGet عرضه شده است" کم کم در حال متداول شدن میباشد و دیگر سایتهای مرتبط، لینک مستقیمی را جهت دریافت کتابخانههای خود ارائه نمیدهند. حتی خبر به روز شدن محصولات خود را هم شاید دیگر به صورت منظم ارائه ندهند؛ زیرا NuGet کار مدیریت آنها را به عهده خواهد داشت.
دریافت و نصب NuGet
NuGet را حداقل به سه طریق میتوان دریافت و نصب کرد:
الف) با مراجعه به سایت CodePlex : (+)
ب) دریافت آن از سایت گالریهای آن : (+)
ج) با استفاده از امکانات VS.NET
هر سه روش فوق به دریافت و نصب فایل NuGet.Tools.vsix منتهی میشوند. برای مثال در روش (ج) باید به منوی Tools و گزینهی Extension Manager مراجعه کنید. سپس برگهی Online Gallery را گشوده و اندکی صبر کنید تا اطلاعات آن دریافت و نمایش داده شود. سپس NuGet را در Search box بالای صفحه نوشته و NuGet Package manager ظاهر شده را انتخاب و نصب کنید.
نحوه استفاده از NuGet
فرض کنید یک پروژه جدید ASP.NET را ایجاد کردهاید و نیاز است تا کتابخانهی ELMAH به آن اضافه شود. روش انجام اینکار را به کمک NuGet در ادامه بررسی خواهیم کرد (کمتر از یک دقیقه زمان خواهد برد):
الف) با کمک امکانات رابط گرافیکی کاربر آن
سادهترین روش استفاده از NuGet ، کلیک راست بر روی پوشه References در Solution explorer و سپس انتخاب گزینهی Add Library Package Reference میباشد:
در صفحهی باز شده، برگهی Online را باز کنید و مدتی صبر نمائید تا اطلاعات لازم دریافت گردد (در زمان نگارش این مطلب، 1135 بسته در این مخزن موجود است):
سپس در جعبهی جستجوی سمت راست بالای صفحه، نام کتابخانهی مورد نظر را نوشته و اندکی صبر کنید تا اطلاعات آن نمایش داده شود:
اکنون با کلیک بر روی دکمه Install ، بسته مرتبط با این کتابخانه دریافت شده و سپس به صورت خودکار ارجاعی به آن نیز افزوده خواهد شد. همچنین تنظیمات مرتبط با فایل Config برنامه هم اضافه میشوند.
روش دیگر ظاهر کردن این صفحه، مراجعه به منوی Tools و گزینهی Library Package Manager میباشد:
جهت دریافت به روز رسانیهای بستههای نصب شده تنها کافی است به برگهی Updates این صفحه مراجعه کرده و موارد لیست شده را نصب نمائیم:
نکته: NuGet در SharpDevelop 4.1 به بعد هم پشتیبانی میشود:
ب) با استفاده از امکانات خط فرمان PowerShell ویندوز
برای استفاده از امکانات پاورشل ویندوز نیاز است تا پاورشل نگارش 2 بر روی سیستم شما نصب باشد (نیاز به Windows XP with Service Pack 3 به بعد دارد). سپس به منوی Tools ، قسمت Library Package Manager ، گزینهی Package Manager Console آن جهت فعال سازی کنسول پاور شل در VS.NET مراجعه نمائید:
نکته: در تصویر فوق پس از نوشتن el ، دکمه tab فشرده شده است. در این حالت منوی پکیجهای مهیای شروع شده با el، از سایت مرکزی NuGet ظاهر گردیده است.
فرامین مهمی که در اینجا در دسترس هستند شامل: List-Package ، Uninstall-Package ، Update-Package و Get-Package میباشند. برای مثال اگر قصد جستجو در بین بستههای موجود را داشته باشید Get-Package بسیار مفید است:
برای مثال جهت یافتن بستههای مرتبط با wpf و silverlight به صورت زیر میتوان عمل کرد:
PM> get-package -remote -filter wpf
PM> get-package -remote -filter silverlight
نکته: روش دیگر جستجو در بین بستههای مهیا، مراجعه به سایت گالری آن است : (+) . در اینجا دستور پاورشل نصب هر بستهی یافت شده نیز نمایش داده میشود.
ج) استفاده از برنامه NuGet.exe
برنامه NuGet.exe از سایت CodePlex قابل دریافت است. این روش هم جهت علاقمندان به خط فرمان تدارک دیده شده است!
پس از دریافت آن فرض کنید میخواهیم تمام بستههای شروع شده با nhi را لیست کنیم. برای این منظور دستور خط فرمان ذیل را صادر کنید:
D:\Test>nuget list nhi
D:\Test>nuget install NHibernate
به این صورت کتابخانه NHibernate به همراه تمام وابستگیهای آن دریافت خواهد شد.
به روز رسانی خودکار NuGet
برای به روز رسانی برنامه nuget.exe دستور زیر را میتوان صادر نمود:
D:\Test>NuGet.exe u
Tools | Options, then Environment | Extension Manager and click "Automatically check for updates to installed extensions."
ادامه دارد ...
ساخت ربات تلگرامی با #C
public AddUserStatus Add(User user) { if (ExistsByEmail(user.Email)) return AddUserStatus.EmailExist; if (ExistsByUserName(user.UserName)) return AddUserStatus.UserNameExist; _users.Add(user); return AddUserStatus.AddingUserSuccessfully; }
using System.Linq; using System.Web.Mvc; using Iris.Datalayer.Context; namespace Iris.Web.Controllers { public class MigrationController : Controller { public ActionResult RemoveDuplicateUsers() { var db = new IrisDbContext(); var lstDuplicateUserGroup = db.Users .GroupBy(u => u.UserName) .Where(g => g.Count() > 1) .ToList(); foreach (var duplicateUserGroup in lstDuplicateUserGroup) { foreach (var user in duplicateUserGroup.Skip(1).Where(user => user.UserMetaData != null)) { db.UserMetaDatas.Remove(user.UserMetaData); } db.Users.RemoveRange(duplicateUserGroup.Skip(1)); } db.SaveChanges(); return new EmptyResult(); } } }
مقایسه ساختار جداول دیتابیس کاربران IRIS با ASP.NET Identity
ساختار جداول ASP.NET Identity به شکل زیر است:
ساختار جداول سیستم کنونی هم بدین شکل است:
همان طور که مشخص است در هر دو سیستم، بین ساختار جداول و رابطهی بین آنها شباهتها و تفاوت هایی وجود دارد. سیستم Identity دو جدول بیشتر از IRIS دارد و برای جداولی که در سیستم کنونی وجود ندارند نیاز به انجام کاری نیست و به هنگام پیاده سازی Identity، این جداول به صورت خودکار به دیتابیس اضافه خواهند شد.
دو جدول مشترک در این دو سیستم، جداول Users و Roles هستندکه نحوهی ارتباطشان با یکدیگر متفاوت است. در Iris بین User و Role رابطهی یک به چند وجود دارد ولی در Identity، رابطهی بین این دو جدول چند به چند است و جدول واسط بین آنها نیز UserRoles نام دارد.
از آن جایی که من قصد دارم در سیستم جدید هم رابطهی بین کاربر و نقش چند به چند باشد، به پیش فرضهای Identity کاری ندارم. به رابطهی کنونی یک به چند کاربر و نقشش نیز دست نمیگذارم تا در انتها با یک کوئری از دیتابیس، اطلاعات نقشهای کاربران را به جدول جدیدش منتقل کنم.
جدولی که در هر دو سیستم مشترک است و هستهی آنها را تشکیل میدهد، جدول Users است. اگر دقت کنید میبینید که این جدول در هر دو سیستم، دارای یک سری فیلد مشترک است که دقیقا هم نام هستند مثل Id، UserName و Email؛ پس این فیلدها از نظر کاربرد در هر دو سیستم یکسان هستند و مشکلی ایجاد نمیکنند.
یک سری فیلد هم در جدول User در سیستم IRIS هست که در Identity نیست و بلعکس. با این فیلدها نیز کاری نداریم چون در هر دو سیستم کار مخصوص به خود را انجام میدهند و تداخلی در کار یکدیگر ایجاد نمیکنند.
اما فیلدی که برای ذخیره سازی پسورد در هر دو سیستم استفاده میشود دارای نامهای متفاوتی است. در Iris این فیلد Password نام دارد و در Identity نامش PasswordHash است.
برای اینکه در سیستم کنونی، نام فیلد Password جدول User را به PasswordHash تغییر دهیم قدمهای زیر را بر میداریم:
وارد پروژهی DomainClasses شده و کلاس User را باز کنید. سپس نام خاصیت Password را به PasswordHash تغییر دهید. پس از این تغییر بلافاصله یک گزینه زیر آن نمایان میشود که میخواهد در تمام جاهایی که از این نام استفاده شده است را به نام جدید تغییر دهد؛ آن را انتخاب کرده تا همه جا Password به PasswordHash تغییر کند.
برای این که این تغییر نام بر روی دیتابیس نیز اعمال شود باید از Migration استفاده کرد. در اینجا من از مهاجرت دستی که بر اساس کد هست استفاده میکنم تا هم بتوانم کدهای مهاجرت را پیش از اعمال بررسی و هم تاریخچهای از تغییرات را ثبت کنم.
برای این کار، Package Manager Console را باز کرده و از نوار بالایی آن، پروژه پیش فرض را بر روی DataLayer قرار دهید. سپس در کنسول، دستور زیر را وارد کنید:
Add-Migration Rename_PasswordToPasswordHash_User
اگر وارد پوشه Migrations پروژه DataLayer خود شوید، باید کلاسی با نامی شبیه به 201510090808056_Rename_PasswordToPasswordHash_User ببینید. اگر آن را باز کنید کدهای زیر را خواهید دید:
public partial class Rename_PasswordToPasswordHash_User : DbMigration { public override void Up() { AddColumn("dbo.Users", "PasswordHash", c => c.String(nullable: false, maxLength: 200)); DropColumn("dbo.Users", "Password"); } public override void Down() { AddColumn("dbo.Users", "Password", c => c.String(nullable: false, maxLength: 200)); DropColumn("dbo.Users", "PasswordHash"); } }
بدیهی هست که این کدها عمل حذف ستون Password را انجام میدهند که سبب از دست رفتن اطلاعات میشود. کدهای فوق را به شکل زیر ویرایش کنید تا تنها سبب تغییر نام ستون Password به PasswordHash شود.
public partial class Rename_PasswordToPasswordHash_User : DbMigration { public override void Up() { RenameColumn("dbo.Users", "Password", "PasswordHash"); } public override void Down() { RenameColumn("dbo.Users", "PasswordHash", "Password"); } }
سپس باز در کنسول دستور Update-Database را وارد کنید تا تغییرات بر روی دیتابیس اعمال شود.
دلیل اینکه این قسمت را مفصل بیان کردم این بود که میخواستم در مهاجرت از سیستم اعتبارسنجی خودتان به ASP.NET Identity دید بهتری داشته باشید.
تا به این جای کار فقط پایگاه داده سیستم کنونی را برای مهاجرت آماده کردیم و هنوز ASP.NET Identity را وارد پروژه نکردیم. در بخشهای بعدی Identity را نصب کرده و تغییرات لازم را هم انجام میدهیم.
MVC Scaffolding #2
دو نوع پارامتر حین کار با MVC Scaffolding مهیا هستند:
الف) سوئیچها
مانند پارامترهای boolean عمل کرده و شامل موارد ذیل میباشند. تمام این پارامترها به صورت پیش فرض دارای مقدار false بوده و ذکر هرکدام در دستور نهایی سبب true شدن مقدار آنها میگردد:
Repository: برای تولید کدها بر اساس الگوی مخزن
Force: برای بازنویسی فایلهای موجود.
ReferenceScriptLibraries: ارجاعاتی را به اسکریپتهای موجود در پوشه Scripts، اضافه میکند.
NoChildItems: در این حالت فقط کلاس کنترلر تولید میشود و از سایر ملحقات مانند تولید Viewها، DbContext و غیره صرفنظر خواهد شد.
ب) رشتهها
این نوع پارامترها، رشتهای را به عنوان ورودی خود دریافت میکنند و شامل موارد ذیل هستند:
ControllerName: جهت مشخص سازی نام کنترلر مورد نظر
ModelType: برای ذکر صریح کلاس مورد استفاده در تشکیل کنترلر بکار میرود. اگر ذکر نشود، از نام کنترلر حدس زده خواهد شد.
DbContext: نام کلاس DbContext تولیدی را مشخص میکند. اگر ذکر نشود از نامی مانند ProjectNameContex استفاده خواهد کرد.
Project: پیش فرض آن پروژه جاری است یا اینکه میتوان پروژه دیگری را برای قرار دادن فایلهای تولیدی مشخص کرد. (برای مثال هربار یک سری کد مقدماتی را در یک پروژه جانبی تولید کرد و سپس موارد مورد نیاز را از آن به پروژه اصلی افزود)
CodeLanguage: میتواند cs یا vb باشد. پیش فرض آن زبان جاری پروژه است.
Area: اگر میخواهید کدهای تولیدی در یک ASP.NET MVC area مشخص قرار گیرند، نام Area مشخصی را در اینجا ذکر کنید.
Layout: در حالت پیش فرض از فایل layout اصلی استفاده خواهد شد. اما اگر نیاز است از layout دیگری استفاده شود، مسیر نسبی کامل آنرا در اینجا قید نمائید.
یک نکته:
نیازی به حفظ کردن هیچکدام از موارد فوق نیست. برای مثال در خط فرمان پاورشل، دستور Scaffold را نوشته و پس از یک فاصله، دکمه Tab را فشار دهید. لیست پارامترهای قابل اجرای در این حالت ظاهر خواهند شد. اگر در اینجا برای نمونه Controller انتخاب شود، مجددا با ورود یک فاصله و خط تیره و سپس فشردن دکمه Tab، لیست پارامترهای مجاز و همراه با سوئیچ کنترلر ظاهر میگردند.
MVC Scaffolding و مدیریت روابط بین کلاسها
مثال قسمت قبلی بسیار ساده و شامل یک کلاس بود. اگر آنرا کمی پیچیدهتر کرده و برای مثال روابط one-to-many و many-to-many را اضافه کنیم چطور؟
using System; using System.Collections.Generic; using System.ComponentModel; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; namespace MvcApplication1.Models { public class Task { public int Id { set; get; } [Required] public string Name { set; get; } [DisplayName("Due Date")] public DateTime? DueDate { set; get; } [ForeignKey("StatusId")] public virtual Status Status { set; get; } // one-to-many public int StatusId { set; get; } [StringLength(450)] public string Description { set; get; } public virtual ICollection<Tag> Tags { set; get; } // many-to-many } public class Tag { public int Id { set; get; } [Required] public string Name { set; get; } public virtual ICollection<Task> Tasks { set; get; } // many-to-many } public class Status { public int Id { set; get; } [Required] public string Name { set; get; } } }
در ادامه دستور تولید کنترلرهای Task، Tag و Status ساخته شده با الگوی مخزن را در خط فرمان پاورشل ویژوال استودیو صادر میکنیم:
PM> Scaffold Controller -ModelType Task -ControllerName TasksController -DbContextType TasksDbContext -Repository -Force PM> Scaffold Controller -ModelType Tag -ControllerName TagsController -DbContextType TasksDbContext -Repository -Force PM> Scaffold Controller -ModelType Status -ControllerName StatusController -DbContextType TasksDbContext -Repository -Force
چند نکته:
- با توجه به اینکه مدلها تغییر کردهاند، نیاز است بانک اطلاعاتی متناظر نیز به روز گردد. مطالب مرتبط با آنرا در مباحث Migrations میتوانید مطالعه نمائید.
- View تولیدی رابطه many-to-many را پشتیبانی نمیکند. این مورد را باید دستی اضافه و طراحی کنید: (^ و ^)
- رابطه one-to-many به خوبی با View متناظری دارای یک drop down list تولید خواهد شد. در اینجا لیست تولیدی به صورت خودکار با مقادیر خاصیت Name کلاس Status پر میشود. اگر این نام دقیقا Name نباشد نیاز است توسط ویژگی به نام DisplayColumn که بر روی نام کلاس قرار میگیرد، مشخص کنید از کدام خاصیت باید استفاده شود.
@Html.DropDownListFor(model => model.StatusId, ((IEnumerable<Status>)ViewBag.PossibleStatus).Select(option => new SelectListItem { Text = (option == null ? "None" : option.Name), Value = option.Id.ToString(), Selected = (Model != null) && (option.Id == Model.StatusId) }), "Choose...") @Html.ValidationMessageFor(model => model.StatusId)
تولید آزمونهای واحد به کمک MVC Scaffolding
MVC Scaffolding امکان تولید خودکار کلاسها و متدهای آزمون واحد را نیز دارد. برای این منظور دستور زیر را در خط فرمان پاورشل وارد نمائید:
PM> Scaffold MvcScaffolding.ActionWithUnitTest -Controller TasksController -Action ArchiveTask -ViewModel Task
نکته مهم آن، عدم حذف یا بازنویسی کامل کنترلر یاد شده است. کاری هم که در تولید متد آزمون واحد متناظر انجام میشود، تولید بدنه متد آزمون واحد به همراه تولید کدهای اولیه الگوی Arrange/Act/Assert است. پر کردن جزئیات بیشتر آن با برنامه نویس است.
و یا به صورت خلاصهتر:
PM> Scaffold UnitTest Tasks Delete
کار مقدماتی با MVC Scaffolding و امکانات مهیای در آن همینجا به پایان میرسد. در قسمتهای بعد به سفارشی سازی این مجموعه خواهیم پرداخت.
استانداردهای کدنویسی #C در سال 2020
foo switch { 1 => 50; 2 => 100; _ => 0; }
<Import Project="$(MSBuildExtensionsPath)\Microsoft\MicrosoftAjax\ajaxmin.tasks" /> <Target Name="AfterBuild"> <ItemGroup> <JS Include="**\*.js" Exclude="**\*.min.js;Scripts\*.js" /> </ItemGroup> <ItemGroup> <CSS Include="**\*.css" Exclude="**\*.min.css" /> </ItemGroup> <AjaxMin JsSourceFiles="@(JS)" JsSourceExtensionPattern="\.js$" JsTargetExtension=".min.js" CssSourceFiles="@(CSS)" CssSourceExtensionPattern="\.css$" CssTargetExtension=".min.css" /> </Target>