ایجاد و توزیع برنامههای جدید AngularJS به همراه تمام وابستگیهای آنها و همچنین رعایت بهترین تجربههای کاری آن، اندکی مشکل است. به همین جهت تیم Angular برنامهای را به نام Angular CLI تدارک دیدهاست که تمام این مراحل را به سادگی هرچه تمامتر مدیریت میکند. ممکن است قالبهای زیادی را در مورد شروع به کار با AngularJS 2.0+ در وب پیدا کنید؛ اما هیچکدام از آنها تمام قابلیتهای Angular CLI را ارائه نمیدهند و همواره چندین قدم عقبتر از تیم Angular هستند. به همین جهت در طی یک سری قصد داریم قابلیتهای گوناگون این ابزار را بررسی کنیم.
Angular CLI چیست؟
ایجاد برنامههای جدید Angular لذت بخش هستند؛ اما ایجاد برنامههایی که از بهترین تجربههای کاری توصیه شدهی توسط تیم Angular پیروی میکنند، به همراه Unit tests هستند و همچنین برای توزیع بهینه سازی شدهاند، بسیار چالش برانگیز میباشند. به همین جهت برنامهی خط فرمانی به نام Angular CLI برای مدیریت این مسایل توسط تیم Angular ایجاد شدهاست، تا توسعه دهندگان بیشتر وقت خود را صرف بهینه سازی کدهای خود کنند تا اینکه درگیر تدارک مسایل جانبی این فریم ورک باشند.
اگر به پروژههای سورس باز ارائه شدهی جهت شروع کار با +AngularJS 2.0 دقت کنید، تعداد بیشماری پروژهی seed، قالبهای آماده، کدساز و غیره را خواهید یافت. اکثر آنها تفاوتهای قابل ملاحظهای را با یکدیگر داشته و در اغلب موارد بهترین تجربههای کاری Angular را نیز رعایت نمیکنند. برای مثال خبری از style guide آن و یا مباحث بهینه سازی ساخت و توزیع لحاظ شدهی در نگارشهای جدید Angular، در آنها نیست.
در اینجا بود که تیم Angular تصمیم گرفت تا در جهت ساماندهی به این وضعیت آشفته، برنامهی Angular CLI را ایجاد کند تا برنامه نویسها به همراه ابزاری باشند که بر اساس بهترین تجربههای کاری Angular تهیه شدهاست؛ سبب ایجاد برنامههایی خواهد شد که یکدست به نظر میرسند و همچنین همواره آخرین تغییرات توزیع و آزمایش برنامهها را نیز به همراه دارد.
پیشنیازهای نصب Angular CLI
پیش از شروع به نصب Angular CLI باید مطمئن شوید که آخرین نگارش NodeJS را نصب کردهاید. برای این منظور خط فرمان را گشوده و دستور ذیل را صادر کنید:
C:\>node -v v5.10.1
اگر این مطلب را در چند ماه بعد پس از نگارش آن مطالعه میکنید، به پروژهی Angular CLI مراجعه کرده و قسمت Prerequisites مستندات ابتدایی آنرا برای مشاهدهی آخرین نگارش NodeJS مورد نیاز آن، بررسی کنید.
نصب Angular CLI
پس از نصب پیشنیاز آن، اکنون خط فرمان را گشوده و دستور ذیل را صادر کنید:
C:\>npm install -g @angular/cli
پس از نصب آن، جهت اطمینان از عملیات انجام شده، دستور ذیل را در خط فرمان صادر کنید:
C:\>npm list -g @angular/cli --depth=0
C:\>npm list -g @angular/cli --depth=0 C:\Users\Vahid\AppData\Roaming\npm `-- @angular/cli@1.0.0
و همچنین برای مشاهدهی نگارش CLI نصب شده، دستور ذیل را اجرا نمائید:
C:\>ng -v _ _ ____ _ ___ / \ _ __ __ _ _ _| | __ _ _ __ / ___| | |_ _| / △ \ | '_ \ / _` | | | | |/ _` | '__| | | | | | | / ___ \| | | | (_| | |_| | | (_| | | | |___| |___ | | /_/ \_\_| |_|\__, |\__,_|_|\__,_|_| \____|_____|___| |___/ @angular/cli: 1.0.0 node: 6.10.2 os: win32 x64
ایجاد یک برنامهی جدید توسط Angular CLI
پس از نصب Angular CLI، اکنون میتوان از آن جهت ساخت یک برنامهی جدید Angular استفاده کرد. برای این منظور یک پوشهی جدید را ایجاد کرده و سپس از طریق خط فرمان به آن وارد شده (نگه داشتن دکمهی shift و سپس کلیک راست و انتخاب گزینهی Open command window here) و دستور ذیل را صادر کنید:
> ng new ngtest --skip-install
در اینجا ساختار یک پروژهی جدید Angular را مشاهده میکنید.
فایل | توضیحات |
.angular-cli.json | تنظیمات cli را به همراه دارد. |
editorconfig | مربوط به تنظیمات VSCode است. |
karma.conf.js | برای انجام unit tests است. |
package.json | وابستگیهای npm برنامه را به همراه دارد (که در زمان نگارش این مطلب تنظیمات Angular 4 را به همراه دارد). |
protractor.conf.js | برای اجرای آزمونهای end to end که در اینجا e2e نام گرفتهاست، میباشد. |
tsconfig.json | تنظیمات کامپایلر TypeScript را به همراه دارد. |
tslint.json | جهت اجرای Lint و ارائهی بهترین تجربههای کاری با TypeScript است. |
داخل پوشهی src، فایلهای اصلی پروژه قرار دارند:
- فایل index.html کار ارائه و شروع برنامه را انجام میدهد.
- فایل main.ts نقطهی آغاز برنامه است.
با توجه به استفادهی از پارامتر skip-install، هنوز وابستگیهای فایل package.json نصب نشدهاند. برای این منظور به پوشهی اصلی پروژه وارد شده (جایی که پوشهی ngtest و فایل package.json قرار دارد) و دستور npm install را صادر کنید تا وابستگیهای برنامه نیز دریافت شوند. البته اگر از پارامتر یاد شده استفاده نمیشد، اینکار به صورت خودکار توسط ng new انجام میگرفت.
>npm install
به این ترتیب وابستگیهای پروژه در پوشهی node_modules تشکیل خواهند شد.
به روز رسانی Angular CLI
روش به روز رسانی AngularCLI شامل این مراحل است:
الف) به روز رسانی بستهی عمومی نصب شدهی آن
npm uninstall -g @angular/cli npm cache clean npm install -g @angular/cli@latest
ب) به روز رسانی یک برنامهی محلی
در ادامه به پوشهی برنامهی خود وارد شده و دستورات ذیل را اجرا کنید:
rm rmdir /S/Q node_modules dist npm install --save-dev @angular/cli@latest npm install
مورد «الف» را به ازای هر نگارش جدید CLI، تنها یکبار باید انجام داد. اما مورد «ب» به ازای هر پروژهی موجود باید یکبار انجام شود (که سریعترین روش به روز رسانی وابستگیهای یک برنامه، به آخرین نگارش Angular است).
- Web Essentials for Visual Studio - Working with images
- Automatic optimization of images, CSS and JavaScript
- Creating Image Sprite in Visual Studio - Web Essential
- Paste Image from clipboard onto Visual Studio Editor
- Image Optimization in Website - Web Essential
- Base64 Encode Image in Website - Web Essential
مجموعهای از APIهای عمومی
افزونه Extention pack 2017
همان طور که میدانید کاربرد پذیری در خیلی از
پروژهها حرف اول رو میزند و کاربر دوست دارد کارهایی که انجام میدهد خیلی راحت
و با استفاده از موس باشد.یکی از کار هایی که در اکثر پروژهها نیاز است ، چیدمان
ترتیب رکوردها است. ما میخواهیم در این پست ترتیبی اتخاذ کنیم که کاربر بتواند رکوردها را به هر ترتیبی که دوست دارد نمایش دهد.
از توضیحاتی که قبلا دادم مشخص است که این کار احتمالا در ASP.NET WebForm کار سختی نیست ولی این کار باید در MVC از ابتدا طراحی شود.
طرح سوال : یک سری رکورد از یک Table داریم که میخواهیم به ترتیب وارد شدن رکوردها نباشد و ترتیبی که ما میخواهیم نمایش داده شود.
پاسخ کوتاه : خب باید ابتدا یک فیلد (برای اولویت بندی) به Table اضافه کنیم بعد اون فیلد رو بنا به ترتیبی که دوست داریم رکوردها نمایش داده شود پر کنیم (Sort می کنیم ) و در آخر هم هنگام نمایش در View رکوردها را بر اساس این فیلد نمایش میدهیم.
(این پست هم در ادامه پست قبلی در همان پروژه است و از همان Table ها استفاده شده است)
اضافه کردن فیلد :
ابتدا یک فیلد به Table مورد نظر اضافه میکنیم. من اسم این فیلد رو Priority گذاشتم. Table من چنین وضعیتی دارد.
افزودن فایلهای jQuery UI :
در این مرحله شما نیاز دارید فایلهای مورد نیاز برای Sort کردن رکوردها را اضافه کنید. شما میتوانید فقط فایلهای مربوط به Sortable را به صفحه خودتان اضافه کنید و یا مثل من فایل هایی که حاوی تمام قسمتهای jQuery UI هست را اضافه کنید.
من برای این کار از Section استفاده کردم ، ابتدا در Head فایل Layout دو Section تعریف کردم برای CSS و JavaScript . و فایلهای مربوط به Sort کردن را در صفحه ای که باید عمل Sort انجام بشود در این Section ها قرار دادم.
فایل Layout
<head> <meta charset="utf-8" /> @RenderSection("meta", false) <title>@ViewBag.Title</title> <link href="@Url.Content("~/Content/~Site.css")" rel="stylesheet" type="text/css" /> <link href="@Url.Content("~/Content/redactor/css/redactor.css")" rel="stylesheet" type="text/css" /> <link href="@Url.Content("~/Content/css/bootstrap-rtl.min.css")" rel="stylesheet" type="text/css" /> @RenderSection("css", false) <script src="@Url.Content("~/Scripts/jquery-1.8.2.min.js")" type="text/javascript"></script> <script src="@Url.Content("~/Scripts/modernizr-1.7.min.js")" type="text/javascript"></script> <script src="@Url.Content("~/Content/js/bootstrap-rtl.js")" type="text/javascript"></script> <script src="@Url.Content("~/Content/redactor/redactor.min.js")" type="text/javascript"></script> <script src="@Url.Content("~/Scripts/jquery.unobtrusive-ajax.min.js")" type="text/javascript"></script> @RenderSection("js", false) </head>
فایل Index.chtml در پوشه کنترلر Type
@model IEnumerable<KhazarCo.Models.Type> @{ ViewBag.Title = "Index"; Layout = "~/Areas/Administrator/Views/Shared/_Layout.cshtml"; } @section css {<link href="@Url.Content("~/Content/themes/base/jquery-ui.css")" rel="stylesheet" type="text/css" /> } @section js { <script src="@Url.Content("~/Scripts/jquery-ui-1.9.0.min.js")" type="text/javascript"></script> }
در آخر فایل Index.chtml به اینصورت شده است:
<h2> نوع ها</h2> <p> @Html.ActionLink("ایجاد یک مورد جدید", "Create", null, new { @class = "btn btn-info" }) </p> <table> <thead> <tr> <th> عنوان </th> <th> توضیحات </th> <th> فعال </th> <th> </th> </tr> </thead> <tbody> @foreach (var item in Model.OrderBy(m => m.Priority)) { <tr id="@item.Id"> <td> @Html.DisplayFor(modelItem => item.Title) </td> <td> @(new HtmlString(item.Description)) </td> <td> @Html.DisplayFor(modelItem => item.IsActive) </td> <td> @Html.ActionLink("ویرایش", "Edit", new { id = item.Id }, new { @class = "btnEdit label label-warning" }) | @Html.ActionLink("مشاهده", "Details", new { id = item.Id }, new { @class = "btnDetails label label-info" }) | @Html.ActionLink("حذف", "Delete", new { id = item.Id }, new { @class = "btnDelete label label-important" }) </td> </tr> } </tbody> </table>
** توجه داشته باشید که من به هر tr یک id اختصاص داده ام که این مقدار id همان مقدار فیلد Id همان رکورد هست ، ما برای مرتب کردن به این Id نیاز داریم (خط 25).
افزودن کدهای کلاینت:
حالا باید کدی بنویسم که دو کار را برای ما انجام دهد : اول حالت Sort پذیری را به سطرهای Table بدهد و دوم اینکه هنگامی که ترتیب سطرهای تغییر کرد ما را با خبر کند:
<script type="text/javascript"> $(function () { $("table tbody").sortable({ helper: fixHelper, update: function (event, ui) { jQuery.ajax('@Url.Action("Sort", "Type", new { area = "Administrator" })', { data: { s: $(this).sortable('toArray').toString() } }); } }).disableSelection(); }); var fixHelper = function (e, ui) { ui.children().each(function () { $(this).width($(this).width()); }); return ui; }; </script>
توضیح کد :
در این کد ما حالت ترتیب پذیری را به Table می دهیم و هنگامی که عمل Update در Table انجام شد تابع مربوطه اجرا میشود. ما در این تایع، ترتیب جدید سطرها را میگیریم ( ** به کمک مقدار Id که به هر سطر دادیم ، این مقدار Id برابر بود با Id خود رکورد در Database ) و به کمکjQuery.ajax به تابع Sort از کنترلر Type در منطقه (area ) Administrator ارسال میکنیم و در آنجا ادامه کار را انجام میدهیم.
تابع fixHelper هم به ما کمک میکند که هنگامی که سطرها از جای خود جدا میشوند ، دارای عرض یکسانی باشند و عرض آنها تغییری نکند.
افزودن کد Server:
حالا باید تابع Sort که مقادیر را به آن ارسال کردیم بنویسم. من این تابع را بر اساس مقداری که از کلاینت ارسال میشود اینگونه طراحی کردم.
public EmptyResult Sort(string s) { if (s != null) { var ids = new List<int>(); foreach (var item in s.Split(',')) { ids.Add(int.Parse(item)); } int intpriority = 0; foreach (var item in ids) { intpriority++; db.Types.Single(m => m.Id == item).Priority = intpriority; } db.SaveChanges(); } return new EmptyResult(); }
در ایتدا مقادیر Id که از کلاینت به صورت String ارسال شده است را میگیریم و بعد به همان ترتیب ارسال در لیستی از int قرار میدهیم ids.
سپس به اضای هر رکورد Type مقدار اولویت را به فیلدی که برای همین مورد اضافه کردیم Priority اختصاص میدهیم. و در آخر هم تغییرات را ذخیره میکنیم. (خود کد کاملا واضح است و نیاری به توضیح بیشتر نیست )
حالا باید هنگامی که لیست Type ها نمایش داده میشود به ترتیب (OrderBy) فیلد Priority نمایش داده شود پس تابع Index را اینطور تغییر میدهیم.
public ViewResult Index() { return View(db.Types.Where(m => m.IsDeleted == false).OrderBy(m => m.Priority)); }
این هم خروجی کار من:
این عکس مربوط به است به قسمت مدیریت پروژه شیرآلات مرجان خزر.
بررسی امنیتی، حین استفاده از jQuery Ajax
به نظر شما استفاده از هندلرها در چنین مواقعی که مثلن قرار است از بانک اطلاعاتی واکشی داشته باشیم بهتر است ؟ یا همان وب سرویس استفاده کنیم ؟
طراحی لایه DataAccess
در این نوع طراحی، هر لایه تنها از طریق Interfaceها به لایه بالاتر از خودش سرویس میدهد و در مواردی که ما در نظر بگیریم، میتوانیم به لایههای دیگر نیز دسترسیهای غیر مستقیم و کنترل شدهای را بدهیم. بطور مثال هر کلاس Repository میتواند بهصورت Internal تعریف شود؛ پس تنها در لایه DataAccess در دسترس است. برای دسترسی سایر لایهها به Repositoryها، هر Repository میتواند از یک IRepository (این Interface دسترسی خواندنی نوشتنی به کلاس Repository دارد) که در لایه DataAccess بصورت public تعریف شده و تنها در لایه Business قابل دسترس است و یک IRepositoryReadOnly (این Interface دسترسی فقط خواندنی به کلاس Repository دارد) که در قسمت Common تعریف شده، ارث ببرد. به این صورت همان طور که در شکل بالا نیز میبینید، دسترسیهایی که از بیرون به لایه DataAccess صورت میگیرد، به دو قسمت تقسیم میشوند: اول دسترسی کامل به IRepository که تنها از طریق Business صورت میپذیرد و دوم دسترسی از طریق IRepositoryReadOnly که میتواند از هر جایی در سیستم که به قسمت Common دسترسی دارد صورت بپذیرد (البته استفاده از IRepositoryReadOnly به سیاستهایی که شما در نظر میگیرید بستگی دارد). با این کار مطمئن میشوید که تغییرات تنها میتوانند از طریق Business صورت بپذیرند. همچنین حتی در صورتیکه نیاز بدانید، یک دسترسی فقط خواندنی را نیز به توسعه دهندگان سایر قسمتها دادهاید.
حال با توجه به توضیحات فوق، تنظیمات مربوط به Autofac را در لایه DataAccess انجام میدهیم.
طراحی تنظیمات Autofac در لایه DataAccess
public class DataAccessSetupDependency : Autofac.Module { protected override void Load(ContainerBuilder builder) { base.Load(builder); builder.RegisterType<EFContext>().As<IDbContext>(); builder.RegisterType<UnitOfWork>().As<IUnitOfWork>(); builder.RegisterAssemblyTypes(Assembly.GetExecutingAssembly()) .Where(x => x.Namespace.EndsWith("Repositories")) .AsImplementedInterfaces(); } }
طراحی لایه Business
در لایه Business نیز دسترسیهای خود را تنها از طریق Interface هایی که کلاسهای Business از آنها ارث بردهاند و آنها را پیاده سازی کردهاند، به قسمت Web میدهید.
طراحی تنظیمات Autofac در لایه Business
public class BusinessSetupDependency : Autofac.Module { protected override void Load(ContainerBuilder builder) { base.Load(builder); builder.RegisterAssemblyModules(typeof(SqlServerDataAccess.SetupDependencies.DataAccessSetupDependency).Assembly); //builder.RegisterAssemblyModules(typeof(CassandraDataAccess.SetupDependecies.SetupDependency).Assembly); builder.RegisterAssemblyTypes(Assembly.GetExecutingAssembly()) .Where(x => x.Namespace.EndsWith("Business.Core")) .AsImplementedInterfaces(); builder.RegisterAssemblyTypes(Assembly.GetExecutingAssembly()) .Where(x => x.Namespace.EndsWith("Business.Businesses")) .AsImplementedInterfaces(); } }
طراحی لایه Web
جمع بندی تمام تنظیمات Autofac در لایه Web
public class WebSetupDependency : Autofac.Module { protected override void Load(ContainerBuilder builder) { base.Load(builder); builder.RegisterAssemblyModules(typeof(Business.SetupDependencies.BusinessSetupDependency).Assembly); builder.RegisterControllers(Assembly.GetExecutingAssembly()); //سایر تنظیمات var container = builder.Build(); CommonDependencyResolver.SetContainer(container); DependencyResolver.SetResolver(new AutofacDependencyResolver(container)); } } public class AutofacConfig { public static void SetupContainer() { var builder = new ContainerBuilder(); builder.RegisterAssemblyModules(Assembly.GetExecutingAssembly()); var container = builder.Build(); CommonDependencyResolver.SetContainer(container); DependencyResolver.SetResolver(new AutofacDependencyResolver(container)); } }
همچنین کلاس AutofacConfig مسئول جمع بندی تمام ماژولها و ایجاد Container آنهاست و سپس این Container را در DependencyResolver ثبت میکند. نکتهای را که باید در اینجا در نظر بگیرید، CommonDependencyResolver است که مسئول ثبت Container در قسمت Common است. به این صورت دیگر تنها لایهای که به Container دسترسی دارد، لایه Web نیست. در واقع با ثبت Container در قسمت Common شما دسترسی کنترل شدهای را از Container به سایر لایههای سیستم دادهاید و در این حالت در صورتیکه لایههای دیگر مانند DataAccess یا Business به Container نیاز پیدا کنند، میتوانند از طریق CommonDependencyResolver این دسترسی را داشته باشند.
جمع بندی
با طراحی ماژولار تنظیمات Autofac و استفاده از Interfaceها برای ارتباط با دیگر قسمتها، دیگر نیازی نیست دسترسیهای بیموردی از یک لایه را به سایر قسمتها داد و دیگر نیازی نیست شما نگران این باشید که ممکن است یکی از توسعه دهندگان بهدلایلی مانند کم تجربگی، کاری را خارج از زیرساختی که شما یا گروه پیاده سازی زیرساخت، پیاده سازی کردهاید انجام دهد. همه چیز آنطور که شما میخواهید و برنامه ریزی کردهاید، انجام میشود.