کاملا قبول دارم که اینجا یک پایگاه کاملا فنی هستش و امتیاز مثبتش هم همینه. اما قبول کنید برای یک برنامه نویس ( یا هر حرفه دیگه ای) علاوه بر نکات فنی، یک سری نکات هم هست که شاید بشه بهش گفت اخلاقیات حرفه، یا ترفندهای حرفه یا ...هر اسم دیگه ای. مثل تجربیاتی که مثلا شما از کار در محیطهای مختلف به دست آوردید، روشهای به روز نگه داشتن خود، چگونگی طی کردن مراحل پیشرفت و ..
JET یک فریم ورک SPA از اوراکل
وبینار برنامه نویسی واکنشی با RxJS
برنامهنویسی واکنشی (reactive) یک پارادایم برنامهنویسی اظهاری (declarative) است که در آن با جریان (stream)های داده و انتشار تغییرات کار میکنیم. این نوع برنامهنویسی بیشترین شباهت را به مدارهای سختافزاری دارد. RxJS نمونه موفق و بسیار پرکاربرد Reactive Programming است که در برنامهنویسی JavaScript امروزی نقش پر رنگی دارد.
در این وبینار مبانی برنامهنویسی واکنشی و RxJS به زبان ساده ارائه میشود و پس از آن به چند نمونه از مسائل دنیای واقعی به شکل عملی پرداخته میشود. در انتها برخی مباحث پیشرفتهتر هم عنوان خواهند شد.
زمان برگزاری: یکشنبه 23 آذر، ساعت 18:30 تا 20
محورهای اصلی این وبینار:
- Introduction to Reactive Programming
- Observables: Hot/Cold
- Piping and Operators
- High Order Observables
- Advanced Topics
مزیت مدیریت وابستگیها
public class ShoppingCartController : Controller { public ActionResult GetCart() { //shopping cart service as a concrete dependency ShoppingCartService shoppingCartService = new ShoppingCartService(); ShoppingCart cart = shoppingCartService.GetContents(); return View("Cart", cart); } public ActionResult AddItemToCart(int itemId, int quantity) { //shopping cart service as a concrete dependency ShoppingCartService shoppingCartService = new ShoppingCartService(); ShoppingCart cart = shoppingCartService.AddItemToCart(itemId, quantity); return View("Cart", cart); } }
public class ShoppingCartController : Controller { private ShoppingCartService _shoppingCartService; public ShoppingCartController() { _shoppingCartService = new ShoppingCartService(); } public ActionResult GetCart() { //now using the shared instance of the shoppingCartService dependency ShoppingCart cart = _shoppingCartService.GetContents(); return View("Cart", cart); } public ActionResult AddItemToCart(int itemId, int quantity) { //now using the shared instance of the shoppingCartService dependency ShoppingCart cart = _shoppingCartService.AddItemToCart(itemId, quantity); return View("Cart", cart); } }
آشنایی با فریمورک الکترون Electron
روی گزینهی Next کلیک کنید و در پنجرهای که باز میشود در قسمت Address نشانی وبسایتی که در بخش پیشین تولید کردیم و ممکن است شما در IIS افزوده باشید؛ قرار دهید و روی دکمهی GO بفشارید تا سرویس در کادر پایین افزوده شود. سپس در قسمت Namespace نامی مناسب برای فراخوانی سرویس وارد کنید آنگاه دکمهی OK را بفشارید.
از پنجرهی بازشده روی دکمهی Finish کلیک کنید. پس از مکثی کوتاه سرویس به همراه دو موجودیت آن درون Data Sources دیده خواهد شد. از آنطرف در Solution Explorer نیز در پوشهی Service References سرویس تعریفشده ارجاع داده خواهد گرفت.
از Data Sources روی tblNews کلیک کنید سپس آنرا کشیده و به روی فرم رها کنید. خواهید دید که یک DataGridView شامل همهی ویژگیهای موجودیت tblNews و یک Binding Navigator که با موجودیت tblNews در پیوند است و یک منبع داده به نام tblNewsBindingSource به صورت خودکار در فرم افزوده خواهد شد.
چیدمان فرم، رنگها، اندازهها و فونت را آنگونه که میپسندید تنظیم کنید. سپس ستونهایی که به آنها نیازی ندارید حذف یا پنهان کرده و عنوان ستونهای مانده را ویرایش کنید. کلیدهای افزودن، حذف و ذخیره را روی Navigator ایجاد کنید و بقیهی کلیدها را اگر به آن نیازی ندارید حذف کنید. البته میتوانید بنا به سلیقهی کاریتان یک Panel برای اینکار اختصاص دهید. در اینجا یک فرم ساده در نظر گرفته شده است:
اکنون نوبت به کدنویسی است. سورس فرم را بازکنید و نخست سرویس را به این صورت در جای مناسب تعریف کنید:
MyNewsService.MyNewsServiceClient MyNews = new MyNewsService.MyNewsServiceClient();
یک تابع کوچک برای تبدیل تاریخ میلادی به شمسی بنویسید سپس رویداد Load فرم را به این صورت بنویسید:
string MiladiToShamsi(DateTime MyDate) { System.Globalization.PersianCalendar pers = new System.Globalization.PersianCalendar(); return string.Format("{0}/{1}/{2}", pers.GetYear(MyDate), pers.GetMonth(MyDate).ToString("D2"), pers.GetDayOfMonth(MyDate).ToString("D2")); } private void Form1_Load(object sender, EventArgs e) { tblNewsBindingSource.DataSource = MyNews.GetAllNews().Select(p => new {p.tblNewsId, p.tblCategory.CatName, p.Title, p.Description, RegDate= MiladiToShamsi( p.RegDate) }); }
پیش از اجرای پروژه از Solution Explorer روی نام راه حل راستکلیک کنید و گزینهی Properties را انتخاب کنید. در پنجرهی بازشده تنظیمات زیر را انجام دهید:
این کار باعث میشود که به طور همزمان پروژهی وبسایت و ویندوز اجرا شود. اکنون پروژه را اجرا کنید. اگر با پیغام خطا روبهرو شدید؛ تگ Connection String را از App.Config پروژه WCF Library به Web.Config پروژه وبسایت کپی کنید. در این صورت پروژه به راحتی اجرا خواهد شد.
در بخش پسین پیرامون افزودن، ویرایش و حذف و برخی توضیحات برای توسعهی کار خواهم نوشت.
- چرا مسیریابی نشانه ای؟
- فعال سازی مسیریابی نشانه ای
- پارامترهای اختیاری URI و مقادیر پیش فرض
- پیشوند مسیر ها
- مسیر پیش فرض
- محدودیتهای مسیر ها
- محدودیتهای سفارشی
- نام مسیر ها
- ناحیهها (Areas)
چرا مسیریابی نشانه ای
- {productId:int}/{productTitle}
- {username}
- {username}/catalogs/{catalogId:int}/{catalogTitle}
routes.MapRoute( name: "ProductPage", url: "{productId}/{productTitle}", defaults: new { controller = "Products", action = "Show" }, constraints: new { productId = "\\d+" } );
[Route("{productId:int}/{productTitle}")] public ActionResult Show(int productId) { ... }
فعال سازی Attribute Routing
public class RouteConfig { public static void RegisterRoutes(RouteCollection routes) { routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); routes.MapMvcAttributeRoutes(); } }
public static void RegisterRoutes(RouteCollection routes) { routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); routes.MapMvcAttributeRoutes(); routes.MapRoute( name: "Default", url: "{controller}/{action}/{id}", defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional } ); }
پارامترهای اختیاری URI و مقادیر پیش فرض
public class BooksController : Controller { // eg: /books // eg: /books/1430210079 [Route("books/{isbn?}")] public ActionResult View(string isbn) { if (!String.IsNullOrEmpty(isbn)) { return View("OneBook", GetBook(isbn)); } return View("AllBooks", GetBooks()); } // eg: /books/lang // eg: /books/lang/en // eg: /books/lang/he [Route("books/lang/{lang=en}")] public ActionResult ViewByLanguage(string lang) { return View("OneBook", GetBooksByLanguage(lang)); } }
پیشوند مسیرها (Route Prefixes)
public class ReviewsController : Controller { // eg: /reviews [Route("reviews")] public ActionResult Index() { ... } // eg: /reviews/5 [Route("reviews/{reviewId}")] public ActionResult Show(int reviewId) { ... } // eg: /reviews/5/edit [Route("reviews/{reviewId}/edit")] public ActionResult Edit(int reviewId) { ... } }
[RoutePrefix("reviews")] public class ReviewsController : Controller { // eg.: /reviews [Route] public ActionResult Index() { ... } // eg.: /reviews/5 [Route("{reviewId}")] public ActionResult Show(int reviewId) { ... } // eg.: /reviews/5/edit [Route("{reviewId}/edit")] public ActionResult Edit(int reviewId) { ... } }
[RoutePrefix("reviews")] public class ReviewsController : Controller { // eg.: /spotlight-review [Route("~/spotlight-review")] public ActionResult ShowSpotlight() { ... } ... }
مسیر پیش فرض
[RoutePrefix("promotions")] [Route("{action=index}")] public class ReviewsController : Controller { // eg.: /promotions public ActionResult Index() { ... } // eg.: /promotions/archive public ActionResult Archive() { ... } // eg.: /promotions/new public ActionResult New() { ... } // eg.: /promotions/edit/5 [Route("edit/{promoId:int}")] public ActionResult Edit(int promoId) { ... } }
محدودیتهای مسیر ها
// eg: /users/5 [Route("users/{id:int}"] public ActionResult GetUserById(int id) { ... } // eg: users/ken [Route("users/{name}"] public ActionResult GetUserByName(string name) { ... }
مثال | توضیحات | محدودیت |
{x:alpha} | کاراکترهای الفبای لاتین را تطبیق (match) میدهد (a-z, A-Z). | alpha |
{x:bool} | یک مقدار منطقی را تطبیق میدهد. | bool |
{x:datetime} | یک مقدار DateTime را تطبیق میدهد. | datetime |
{x:decimal} | یک مقدار پولی را تطبیق میدهد. | decimal |
{x:double} | یک مقدار اعشاری 64 بیتی را تطبیق میدهد. | double |
{x:float} | یک مقدار اعشاری 32 بیتی را تطبیق میدهد. | float |
{x:guid} | یک مقدار GUID را تطبیق میدهد. | guid |
{x:int} | یک مقدار 32 بیتی integer را تطبیق میدهد. | int |
{(x:length(6} {(x:length(1,20} | رشته ای با طول تعیین شده را تطبیق میدهد. | length |
{x:long} | یک مقدار 64 بیتی integer را تطبیق میدهد. | long |
{(x:max(10} | یک مقدار integer با حداکثر مجاز را تطبیق میدهد. | max |
{(x:maxlength(10} | رشته ای با حداکثر طول تعیین شده را تطبیق میدهد. | maxlength |
{(x:min(10} | مقداری integer با حداقل مقدار تعیین شده را تطبیق میدهد. | min |
{(x:minlength(10} | رشته ای با حداقل طول تعیین شده را تطبیق میدهد. | minlength |
{(x:range(10,50} | مقداری integer در بازه تعریف شده را تطبیق میدهد. | range |
{(${x:regex(^\d{3}-\d{3}-\d{4} | یک عبارت با قاعده را تطبیق میدهد. | regex |
// eg: /users/5 // but not /users/10000000000 because it is larger than int.MaxValue, // and not /users/0 because of the min(1) constraint. [Route("users/{id:int:min(1)}")] public ActionResult GetUserById(int id) { ... }
// eg: /greetings/bye // and /greetings because of the Optional modifier, // but not /greetings/see-you-tomorrow because of the maxlength(3) constraint. [Route("greetings/{message:maxlength(3)?}")] public ActionResult Greet(string message) { ... }
محدودیتهای سفارشی
public class ValuesConstraint : IRouteConstraint { private readonly string[] validOptions; public ValuesConstraint(string options) { validOptions = options.Split('|'); } public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection) { object value; if (values.TryGetValue(parameterName, out value) && value != null) { return validOptions.Contains(value.ToString(), StringComparer.OrdinalIgnoreCase); } return false; } }
public class RouteConfig { public static void RegisterRoutes(RouteCollection routes) { routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); var constraintsResolver = new DefaultInlineConstraintResolver(); constraintsResolver.ConstraintMap.Add("values", typeof(ValuesConstraint)); routes.MapMvcAttributeRoutes(constraintsResolver); } }
public class TemperatureController : Controller { // eg: temp/celsius and /temp/fahrenheit but not /temp/kelvin [Route("temp/{scale:values(celsius|fahrenheit)}")] public ActionResult Show(string scale) { return Content("scale is " + scale); } }
نام مسیر ها
[Route("menu", Name = "mainmenu")] public ActionResult MainMenu() { ... }
<a href="@Url.RouteUrl("mainmenu")">Main menu</a>
ناحیهها (Areas)
[RouteArea("Admin")] [RoutePrefix("menu")] [Route("{action}")] public class MenuController : Controller { // eg: /admin/menu/login public ActionResult Login() { ... } // eg: /admin/menu/show-options [Route("show-options")] public ActionResult Options() { ... } // eg: /stats [Route("~/stats")] public ActionResult Stats() { ... } }
Url.Action("Options", "Menu", new { Area = "Admin" })
[RouteArea("BackOffice", AreaPrefix = "back-office")]
public static void RegisterRoutes(RouteCollection routes) { routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); routes.MapMvcAttributeRoutes(); AreaRegistration.RegisterAllAreas(); routes.MapRoute( name: "Default", url: "{controller}/{action}/{id}", defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional } ); }
پروژههای کوچک عموما دارای ساختاری مشابه تصویر ذیل میباشند:
این مورد، روش پیشنهادی در Angular Seed است و بدین صورت است که تعاریف ماژولها در فایل app.js انجام میگیرد. تعاریف و پیاده سازی تمام کنترلرها در فایل controller.js است. و همچنین دایرکتیوها و فیلترها و سرویسها هر کدام در فایلها جداگانه تعریف و پیاده سازی میشوند. این روش راه حلی سریع برای پروژههای کوچک با تعداد developerهای کم است. برای مثال زمانی که یک developer در حال ویرایش فایل controller.js است، از آن جا که فایل مورد نظر checkout خواهد شد در نتیجه سایر developerها امکان تغییر در فایل مورد نظر را نخواهند داشت. سورس فایلها به مرور زیاد خواهد شد و در نتیجه debug آن سخت میشود.
روش دوم
در این حالت تعاریف کنترلر ها، مدلها و سرویسها هرکدام در یک دایرکتوری مجزا قرار خواهد گرفت. برای هر view یک کنترلر و بنا بر نیاز مدل تعریف میکنیم. ساختار آن به صورت زیر میشود:
دایرکتیوها و فیلترها عموما در یک فایل قرار داده خواهند شد تا بنابر نیاز در جای مناسب رفرنس داده شوند. این روش ساختار مناسبتری نسبه به روش قبلی دارد اما دارای معایبی هم چون موارد زیر است:
»وابستگی بین فایلها مشخص نیست در نتیجه بدون استفاده از کتابخانه هایی نظیر requireJs با مشکل مواجه خواهید شد.
»refactoring کدها تا حدودی سخت است.
روش سوم
این ساختار مناسب برای پیاده سازی پروژهها به صورت ماژولار است و برای پروژههای بزرگ نیز بسیار مناسب است. در این حالت شما فایلهای مربوط به هر ماژول را در دایرکتوری خاص آن قرار خواهید داد. به صورت زیر:
همان طور که ملاحظه میکنید سرویس ها، کنترلرها و حتی مدلهای مربوط به هر بخش در یک مسیر جداگانه قرار میگیرند. علاوه بر آن فایل هایی که قابلیت اشتراکی دارند در مسیری به نام common وجود دارند تا بتوان در جای مناسب برای استفاده از آنها رفرنس داده شود. حتی اگر در پروژه خود فقط یک ماژول دارید باز سعی کنید از این روش برای مدیریت فایلهای خود استفاده نمایید. اگر با ngStart آشنایی داشته باشید به احتمال زیاد با این روش بیگانه نیستید.
بررسی چند نکته درباره کدهای مشترک
»اگر ماژولها وابستگی شدیدی به فایلها و سورسهای مشترک دارند باید اطمینان حاصل نمایید که این ماژولها فقط به اطلاعات مورد نیاز دسترسی دارند. این اصل interface segregation principle اصول SOLID است.
»توابعی که کاربرد زیادی دارند و اصطلاحا به عنوان Utility شناخته میشوند باید به rootScope$ اضافه شوند تا scopeهای وابسته نیز به آنها دسترسی داشته باشند. این مورد به ویژه باعث کاهش تکرار وابستگیهای مربوط به هر کنترلر میشود.
»برای جداسازی وابستگیهای بین دو component بهتر از eventها استفاده نمایید. AngularJs این امکان را با استفاده از سرویسهای on$ و emit$ و broadcast$ به راحتی میسر کرده است.