<services> <service name="MyNewsWCFLibrary.MyNewsService"> <host> <baseAddresses> <add baseAddress="http://localhost:8080/MyNewsWCFLibrary/MyNewsService/" /> </baseAddresses> </host> <!-- Service Endpoints --> <!-- Unless fully qualified, address is relative to base address supplied above --> <endpoint address="" binding="basicHttpBinding" contract="MyNewsWCFLibrary.IMyNewsService"> <!-- Upon deployment, the following identity element should be removed or replaced to reflect the identity under which the deployed service runs. If removed, WCF will infer an appropriate identity automatically. --> <identity> <dns value="localhost" /> </identity> </endpoint> <!-- Metadata Endpoints --> <!-- The Metadata Exchange endpoint is used by the service to describe itself to clients. --> <!-- This endpoint does not use a secure binding and should be secured or removed before deployment --> <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" /> </service> </services>
در صورت مشاهده پیام خطا، ویژوال استودیو را ببندید و اینبار به صورت Run as administrator باز کنید.
برای نمونه روی متد AddCategory کلیک کنید. در پنجره نشان داده شده همانند شکل در برابر فیلد CatName مقداری وارد کنید و روی دکمه Invoke کلیک کنید. متد مورد نظر اجرا شده و مقداری که وارد کرده ایم در پایگاه دادهها ذخیره میشود. مقداری که در قسمت پایین دیده میشود خروجی متد است که در اینجا شناسه رکورد درجشده است.
بار دیگر برای مشاهده رکورد درجشده روی متد GetAllCategory کلیک کنید. به علت اینکه این متد ورودی ندارد در قسمت بالا چیزی نشان داده نمیشود. روی دکمه Invoke کلیک کنید. با پیغام خطای زیر روبهرو خواهید شد:
افزودن ویژگی Virtual به tblNews و tblCategory در بخش دوم خواندید؛ باعث میشود که Entity Framework در هنگام اجرا کلاسهایی با عنوان "پروکسیهای پویا" به کلاسهای Address و Customer بیفزاید و بنابراین قابلیت Lazy Loading برای این کلاسها در زمان اجرای برنامه فراهم میگردد.
ولی با افزودن پروکسیهای پویا به کلاسهای ما، این کلاسها قابلیت انتقال خود از طریق سرویسهای WCF را از دست میدهند زیرا پروکسیهای پویا به طور پیشگزیده قابلیت سریالایز و دیسریالایز شدن را ندارند!
خوشبختانه میتوانیم این ویژگی را در کلاس DBContext غیرفعال کنیم. برای این منظور قالب سازندهی آن یا MyNewsModel.Context.tt را از Solution Explorer باز کنید و کد زیر را در آن پیدا کنید:
<#=Accessibility.ForType(container)#> partial class <#=code.Escape(container)#> : DbContext { public <#=code.Escape(container)#>() : base("name=<#=container.Name#>") {
سپس در ادامهی آن کدغیرفعالکردن پروکسی پویا را به این شکل بنویسید:
<#=Accessibility.ForType(container)#> partial class <#=code.Escape(container)#> : DbContext { public <#=code.Escape(container)#>() : base("name=<#=container.Name#>") { Configuration.ProxyCreationEnabled = false;
اکنون اگر فایل را ذخیره کنیم سپس فایل MyNewsModel.Context.cs را از Solution Explorer باز کنید؛ خواهید دید که این خط کد در جای خود قرارگرفته است.
بار دیگر پروژه را اجرا کنید روی متد GetAllCategory کلیک کنید. این بار اگر دکمه Invoke را بفشارید با همانند شکل زیر را خواهید دید:
در بخش ششم پیرامون ارتباط جدولهای tblNews و tblCategory و نمایش محتویات وابسته جدول خبر به دسته و تنظیمات آن در t4 و کلاس Service
در بخش هفتم پیرامون میزبانی WCFLibrary در یک Web Application
و در بخش هشتم پیرامون ایجاد یک برنامهی ویندوزی جهت استفاده از سرویسهای WCF خواهم نوشت.
- استفاده از T4MVC اجباری است. به هیچ عنوان نباید از رشتهها برای مشخص سازی نام کنترلرها یا اکشن متدها در قسمتهای مختلف برنامه استفاده شود.
- تا حد امکان از ViewBag ، ViewData و امثال آن استفاده نشده و به ازای هر View یک مدل متناظر (ViewModel) ایجاد شود.
- فایل پروژه برنامه توسط یک ادیتور متنی ویرایش شده و MvcBuildViews آن به True تنظیم شود.
- مدلهای متناظر با جداول بانک اطلاعاتی نباید مستقیما در Viewهای برنامه استفاده شوند.
- پوشه Models، از پروژه اصلی حذف شود. یک پروژه class library جدید به نام MyProjectName.Models برای نگهداری ViewModels ایجاد گردد.
- یک پروژه Class library دیگر به نام MyProjectName.DomainClasses برای نگهداری کلاسهای متناظر با جداول بانک اطلاعاتی ایجاد شود.
- از سیستم minification و bundling، برای یکی سازی اسکریپتها و CSSهای برنامه استفاده شود.
- قسمت custom errors فایل web.config برنامه به نحو صحیحی مقدار دهی شود.
- تمام فرمهای عمومی برنامه باید دارای AntiForgeryToken باشند.
- تمام فرمهای عمومی برنامه باید captcha داشته باشند.
- پوشههای Content و Scripts از سیستم مسیریابی تعریف شده در Global.asax خارج شوند.
- MvcHandler.DisableMvcResponseHeader = true به Application_Start اضافه شود.
- اگر فقط از Razor به عنوان ViewEngine استفاده میشود، در Application_Start، باید سایر ViewEngineهای مورد استفاده، حذف شوند.
- فیلتر پیش فرض مدیریت خطاها حذف و بجای آن از ELMAH استفاده شود.
- در web.config، مقادیر executionTimeout و maxRequestLength مرتبط با httpRuntime تنظیم شوند. همچنین enableVersionHeader آن نیز خاموش گردد.
- استفاده از سشنها کلا باید حذف شود. ماژول توکار آن از قسمت httpModules حذف گردد تا پردازش موازی صفحات فعال گردد. (سشن مربوط است به دوران ASP کلاسیک دهه نود و هیچ نیازی به استفاده از آن در MVC نیست)
- در هیچ کنترلری نباید جزئیات پیاده سازی متدی مشاهده شود. تمام پیاده سازیها باید به لایه سرویسهای مختلف برنامه منتقل و از طریق تزریق وابستگیها در دسترس باشند.
- اگر نیاز به مشخص سازی آدرسی در سایت است (خصوصا در اسکریپتها) باید از Url.Action استفاده شود و نه رشتهها.
- بهتر است بومی سازی برنامه از روز اول آن درنظر گرفته شده و تمام عبارات مورد استفاده در فایلهای Resource درج شوند.
- برای مدیریت سادهتر بستههای مورد استفاده (وابستگیهای برنامه) بهتر است از NuGet استفاده شود.
- از یک ماژول HTTP compression مستقل و با کیفیت استفاده شود (برای سازگاری بهتر با نگارشهای مختلف IIS).
- برای معرفی HTTP modules و سادگی تعریف و فعال سازی آنها در انواع و اقسام IISها بهتر است از کتابخانه WebActivator استفاده شود.
- امکان دوبار کلیک کردن بر روی تمام دکمهها نباید وجود داشته باشد.
- از هشهای ترکیبی استفاده شود. مستقیما از MD5 یا SHA1 استفاده نشود.
- با اسکریپتهای anti IE6,7، این مرورگرها به رحمت ایزدی واصل شوند.
- اگر کاربری JavaScript را در مرورگر خود غیرفعال کرد، نباید بتواند از سایت استفاده کند.
- کلیه تغییرات تنظیمات و محتوای مهم سایت باید برای مدیر سایت بلافاصله ایمیل شوند.
- یک سری کارهای متداول مانند تهیه فایلهای favicon.ico، apple-touch-icon-XxY.png، crossdomain.xml، robots.txt و sitemap.xml (ترجیحا پویا) فراموش نشود.
- در web.config و در زمان ارائه، compilation debug=false تنظیم شود.
- در تمام قسمتهایی که AlllowHtml فعال شده باید از پاکسازی Html دریافتی جهت مقابله با XSS مطمئن شد.
- جهت سهولت طراحی table less از یک فریم ورک CSS ایی استفاده شود.
- در تمام قسمتهایی که فایلی آپلود میشود باید بررسی شود فایلهای نا امن (فایلهای اجرایی ASP.NET) قابل آپلود نباشند.
- حین کار با بانکهای اطلاعاتی یا از ORM استفاده شود و یا از کوئریهای پارامتری.
- هر برنامه ASP.NET باید داری یک Application pool مجزا به همراه تنظیمات حافظه مشخصی باشد.
- تمام صفحات باید عنوان داشته باشند. به همین منظور مقدار دهی پیش فرض آن در فایل ViewStart صورت گیرد.
- در صفحه لاگین سایت، autocomplete خاموش شود.
- تمام deleteهای برنامه باید به HttpPost محدود شوند. تمام گزارشات و نمایش اطلاعات غیرعمومی برنامه به HttpGet محدود شوند.
- تعداد رفت و برگشتهای به بانک اطلاعاتی در یک صفحه توسط پروفایلرها بررسی شده و اطلاعات عمومی پرمصرف کش شوند.
- در هیچکدام از کلاسهای ASP.NET MVC نباید از HttpContext مستقیما استفاده شود. کلاس پایه جدید آن باید مورد استفاده قرار گیرد یا از Action Result صحیحی استفاده گردد.
- کش کردن فایلهای استاتیک درنظر گرفته شود.
- تمام درخواستهای jQuery Ajax باید بررسی شوند. آیا واقعا مرتبط هستند به سایت جاری و آیا واقعا Ajax ایی هستند.
یک نکته:
امکان تهیه قالبهای سفارشی VS.NET و لحاظ موارد فوق در آن جهت استفادههای بعدی نیز وجود دارد:
Create Reusable Project And Item Templates For Your Development Team
Write Templates for Visual Studio 2010
Building a Custom Project Wizard in Visual Studio .NET
// JavaScript var vm = { firstName = ko.observable('John') }; ko.applyBindings(vm);
<!-- HTML --> <input data-bind="value:firstName"/>
// JavaScript // Inside of a personController this.firstName = 'John';
<!-- HTML --> <div ng-controller="personController as vm"> <input ng-model="vm.firstName"/> </div>
پیشنیازهای کار با کش توزیع شدهی مبتنی بر SQL Server
برای کار با کش توزیع شدهی با قابلیت ذخیره سازی در یک بانک اطلاعاتی SQL Server، نیاز است دو بستهی ذیل را به فایل project.json برنامه اضافه کرد:
{ "dependencies": { "Microsoft.Extensions.Caching.SqlServer": "1.1.0" }, "tools": { "Microsoft.Extensions.Caching.SqlConfig.Tools": "1.1.0-preview4-final" } }
ایجاد جدول ذخیره سازی اطلاعات کش توزیع شده به کمک ابزار sql-cache
پس از افزودن و بازیابی ارجاعات فوق، با استفاده از خط فرمان، به پوشهی جاری برنامه وارد شده و دستور ذیل را صادر کنید:
dotnet sql-cache create "Data Source=(localdb)\MSSQLLocalDB;Initial Catalog=sql_cache;Integrated Security=True;" "dbo" "AppSqlCache"
- در اینجا میتوان هر نوع رشتهی اتصالی معتبری را به انواع و اقسام بانکهای SQL Server ذکر کرد. برای نمونه در مثال فوق این رشتهی اتصالی به یک بانک اطلاعاتی از پیش ایجاد شدهی LocalDB اشاره میکند. نام دلخواه این بانک اطلاعاتی در اینجا sql_cache ذکر گردیده و نام دلخواه جدولی که قرار است این اطلاعات را ثبت کند AppSqlCache تنظیم شدهاست و dbo، نام اسکیمای جدول است:
در اینجا تصویر ساختار جدولی را که توسط ابزار dotnet sql-cache ایجاد شدهاست، مشاهده میکنید. اگر خواستید این جدول را خودتان دستی ایجاد کنید، یک چنین کوئری را باید بر روی دیتابیس مدنظرتان اجرا نمائید:
CREATE TABLE AppSqlCache ( Id NVARCHAR (449) COLLATE SQL_Latin1_General_CP1_CS_AS NOT NULL, Value VARBINARY (MAX) NOT NULL, ExpiresAtTime DATETIMEOFFSET NOT NULL, SlidingExpirationInSeconds BIGINT NULL, AbsoluteExpiration DATETIMEOFFSET NULL, CONSTRAINT pk_Id PRIMARY KEY (Id) ); CREATE NONCLUSTERED INDEX Index_ExpiresAtTime ON AppSqlCache(ExpiresAtTime);
ایجاد جدول ذخیره سازی اطلاعات کش توزیع شده به کمک ابزار Migrations در EF Core
زیر ساخت کش توزیع شدهی مبتنی بر SQL Server هیچگونه وابستگی به EF Core ندارد و تمام اجزای آن توسط Async ADO.NET نوشته شدهاند. اما اگر خواستید قسمت ایجاد جدول مورد نیاز آنرا به ابزار Migrations در EF Core واگذار کنید، روش کار به صورت زیر است:
- ابتدا یک کلاس دلخواه جدید را با محتوای ذیل ایجاد کنید:
public class AppSqlCache { public string Id { get; set; } public byte[] Value { get; set; } public DateTimeOffset ExpiresAtTime { get; set; } public long? SlidingExpirationInSeconds { get; set; } public DateTimeOffset? AbsoluteExpiration { get; set; } }
public class MyDBDataContext : DbContext { public virtual DbSet<AppSqlCache> AppSqlCache { get; set; } protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<AppSqlCache>(entity => { entity.ToTable(name: "AppSqlCache", schema: "dbo"); entity.HasIndex(e => e.ExpiresAtTime).HasName("Index_ExpiresAtTime"); entity.Property(e => e.Id).HasMaxLength(449); entity.Property(e => e.Value).IsRequired(); }); } }
البته این مورد به شرطی است که بخواهید از یک دیتابیس، هم برای برنامه و هم برای ذخیره سازی اطلاعات کش استفاده کنید.
معرفی تنظیمات رشتهی اتصالی و نام جدول ذخیره سازی اطلاعات کش به برنامه
پس از ایجاد جدول مورد نیاز جهت ذخیره سازی اطلاعات کش، اکنون نیاز است این اطلاعات را به برنامه معرفی کرد. برای این منظور به کلاس آغازین برنامه مراجعه کرده و متد الحاقی AddDistributedSqlServerCache را بر روی مجموعهی سرویسهای موجود فراخوانی کنید؛ تا سرویسهای این کش توزیع شده نیز به برنامه معرفی شوند:
public void ConfigureServices(IServiceCollection services) { services.AddDistributedSqlServerCache(options => { options.ConnectionString = @"Data Source=(localdb)\MSSQLLocalDB;Initial Catalog=sql_cache;Integrated Security=True;"; options.SchemaName = "dbo"; options.TableName = "AppSqlCache"; });
آزمایش کش توزیع شدهی تنظیمی با فعال سازی سشنها
سشنها را همانند نکات ذکر شدهی در مطلب «ارتقاء به ASP.NET Core 1.0 - قسمت 16 - کار با Sessions» فعال کنید و سپس مقداری را در آن بنویسید:
public IActionResult Index() { HttpContext.Session.SetString("User", "VahidN"); return Json(true); } public IActionResult About() { var userContent = HttpContext.Session.GetString("User"); return Json(userContent); }
همانطور که مشاهده میکنید، سیستم سشن اینبار بجای حافظه، به صورت خودکار از جدول بانک اطلاعاتی SQL Server تنظیم شده، برای ذخیره سازی اطلاعات خود استفاده کردهاست.
کار با کش توزیع شده از طریق برنامه نویسی
همانطور که در مقدمهی بحث نیز عنوان شد، استفادهی از زیر ساخت کش توزیع شده منحصر به استفادهی از آن جهت ذخیره سازی اطلاعات سشنها نیست و از آن میتوان جهت انواع و اقسام سناریوهای مختلف مورد نیاز استفاده کرد. در این حالت روش دسترسی به این زیر ساخت، از طریق اینترفیس IDistributedCache است. زمانیکه متد AddDistributedSqlServerCache را فراخوانی میکنیم، در حقیقت کار ثبت یک چنین سرویسی به صورت خودکار انجام خواهد شد:
services.Add(ServiceDescriptor.Singleton<IDistributedCache, SqlServerCache>());
در اینجا یک نمونه از این تزریق وابستگی و سپس استفادهی از متدهای Set و Get اینترفیس IDistributedCache را مشاهده میکنید:
using System; using System.Text; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Caching.Distributed; namespace Core1RtmEmptyTest.Controllers { public class CacheTestController : Controller { readonly IDistributedCache _cache; public CacheTestController(IDistributedCache cache) { _cache = cache; } public IActionResult SetCacheData() { var time = DateTime.Now.ToLocalTime().ToString(); var cacheOptions = new DistributedCacheEntryOptions { AbsoluteExpiration = DateTime.Now.AddYears(1) }; _cache.Set("Time", Encoding.UTF8.GetBytes(time), cacheOptions); return View(); } public IActionResult GetCacheData() { var time = Encoding.UTF8.GetString(_cache.Get("Time")); ViewBag.data = time; return View(); } public bool RemoveCacheData() { _cache.Remove("Time"); return true; } } }
Value VARBINARY (MAX) NOT NULL,
public byte[] Value { get; set; }
در این حالت اگر برنامه را اجرا و مسیر http://localhost:7742/CacheTest/SetCacheData را فراخوانی کنیم، اطلاعات ذخیره شدهی با کلید Test را میتوان در بانک اطلاعاتی مشاهده کرد:
Tag helper مخصوص کش توزیع شده
در ASP.NET Core، میتوان از یک Tag Helper جدید به نام distributed-cache برای کش سمت سرور توزیع شدهی محتوای قسمتی از یک View به نحو ذیل استفاده کرد:
<distributed-cache name="MyCacheItem2" expires-sliding="TimeSpan.FromMinutes(30)"> <p>From distributed-cache</p> @DateTime.Now.ToString() </distributed-cache>
در اینجا name به صورت هش شده به صورت کلید کش مورد استفاده قرار میگیرد. سپس محتوای تگ distributed-cache رندر شده، تبدیل به آرایهای از بایتها گردیده و در بانک اطلاعاتی ذخیره میگردد.
ذکر name در اینجا اجباری است و باید دقت داشت که چون به عنوان کلید بازیابی کش مورد استفاده قرار خواهد گرفت، نباید به اشتباه در قسمتهای دیگر برنامه با همین نام وارد شود. در غیر اینصورت دو قسمتی که name یکسانی داشته باشند، یک محتوا را نمایش خواهند داد.
ترکیب کامپوننتها
در ادامه، همان برنامهی تا قسمت 5 را که کار نمایش یک counter را انجام میدهد، تکمیل میکنیم. در این برنامه اگر به فایل index.js دقت کنید، کار رندر تک کامپوننت Counter را انجام میدهیم:
ReactDOM.render(<Counter />, document.getElementById("root"));
برای نمایش این مفهوم، کامپوننت جدید src\components\counters.jsx را ایجاد میکنیم. قصد داریم در این کامپوننت، لیستی از کامپوننتهای Counter را رندر کنیم. سپس در index.js، بجای رندر کامپوننت Counter، کامپوننت جدید Counters را رندر میکنیم. به این ترتیب درخت کامپوننتهای برنامه، در سطح بالایی خودش از کامپوننت Counters شروع میشود و سپس فرزندان آنرا کامپوننتهای Counter تشکیل میدهند. به همین جهت فایل index.js را به صورت زیر ویرایش میکنیم تا به کامپوننت Counters اشاره کند:
import Counters from "./components/counters"; ReactDOM.render(<Counters />, document.getElementById("root"));
import React, { Component } from "react"; import Counter from "./counter"; class Counters extends Component { state = {}; render() { return ( <div> <Counter /> <Counter /> <Counter /> <Counter /> </div> ); } } export default Counters;
در مرحلهی بعد، بجای رندر و درج دستی این کامپوننتها، آرایهای از اشیاء counter را ایجاد کرده و سپس آنها را توسط متد Array.map رندر میکنیم:
import React, { Component } from "react"; import Counter from "./counter"; class Counters extends Component { state = { counters: [ { id: 1, value: 0 }, { id: 2, value: 0 }, { id: 3, value: 0 }, { id: 4, value: 0 } ] }; render() { return ( <div> {this.state.counters.map(counter => ( <Counter key={counter.id} /> ))} </div> ); } } export default Counters;
ارسال دادهها به کامپوننتها
مشکل! مقدار value هر شیء شمارشگر تعریف شده، به کامپوننتهای مرتبط رندر شده اعمال نشدهاست. برای مثال اگر value اولین شیء را به 4 تغییر دهیم، هنوز هم این کامپوننت با همان مقدار صفر شروع به کار میکند. برای رفع این مشکل، به همان روشی که ویژگی key کامپوننت Counter را مقدار دهی کردیم، میتوان ویژگیهای سفارشی دیگری را تعریف و مقدار دهی کرد:
render() { return ( <div> {this.state.counters.map(counter => ( <Counter key={counter.id} value={counter.value} selected={true} /> ))} </div> );
class Counter extends Component { state = { count: 0 }; render() { console.log("props", this.props); //...
خاصیت this.props، یک شیء سادهی جاوا اسکریپتی است و شامل تمام ویژگیهایی میباشد که ما در کامپوننت Counters برای هر کدام از کامپوننتهای Counter رندر شدهی توسط آن، تعریف کردیم. برای نمونه دو ویژگی جدید value و selected را که به تعاریف المانهای Counter در کامپوننت Counters اضافه کردیم، در اینجا به همراه مقادیر منتسب به آنها، قابل مشاهده هستند. البته در این خروجی، key را ملاحظه نمیکنید؛ چون هدف اصلی آن، معرفی یکتای المانها در DOM مجازی React است.
بنابراین اکنون میتوان به value تنظیم شدهی در کامپوننت Counters به صورت this.props.value در کامپوننت Counter دسترسی یافت و سپس از آن جهت مقدار دهی اولیهی counter استفاده کرد.
class Counter extends Component { state = { count: this.props.value };
یک نکته: در اینجا selected={true} را داریم. اگر مقدار آنرا حذف کنیم، یعنی selected تنها درج شود، مقدار آن، همان true دریافت خواهد شد.
تعریف فرزند برای المانهای کامپوننتها
ویژگیهای اضافه شدهی به تعاریف المانهای کامپوننتها، توسط خاصیت this.props، به هر کدام از آن کامپوننتها منتقل میشوند. این خاصیت props، یک خاصیت ویژه را به نام children، نیز دارا است و از آن برای دسترسی به المانهای تعریف شدهی بین تگهای یک المان اصلی استفاده میشود:
render() { return ( <div> {this.state.counters.map(counter => ( <Counter key={counter.id} value={counter.value} selected={true}> <h4>Counter #{counter.id}</h4> </Counter> ))} </div> ); }
یک نمونه مثال واقعی این قابلیت، امکان تعریف محتوای دیالوگ باکسها، توسط استفاده کنندهی از آن است.
روش دیباگ برنامههای React
افزونهی مفید React developer tools را میتوانید برای مرورگرهای کروم و فایرفاکس، دریافت و نصب کنید. برای نمونه پس از نصب آن در مرورگر کروم، یک برگهی جدید به لیست برگههای کنسول توسعه دهندگان آن اضافه میشود:
همانطور که مشاهده کنید، درخت کامپوننتهای برنامه را در برگهی جدید Components، میتوان مشاهده کرد. در اینجا با انتخاب هر کدام از فرزندان این درخت، مشخصات آن نیز مانند props و state، در کنار صفحه ظاهر میشوند. همچنین در بالای همین قسمت، 4 آیکن مشاهدهی سورس، مشاهدهی DOM و یا لاگ کردن جزئیات شیء کامپوننت انتخابی در کنسول هم درج شدهاند:
که برای نمونه چنین خروجی را لاگ میکند:
بررسی تفاوتهای خواص props و state
در کامپوننت Counter، از props برای مقدار دهی اولیهی state استفاده میکنیم:
class Counter extends Component { state = { count: this.props.value };
- props حاوی اطلاعاتی است که به یک کامپوننت ارسال میکنیم؛ اما state حاوی اطلاعاتی است که مختص به آن کامپوننت بوده و private است. یعنی سایر کامپوننتها نمیتوانند به state کامپوننت دیگری دسترسی پیدا کنند. برای مثال در کامپوننت Counters، تمام attributes سفارشی تنظیم شدهی بر روی تعاریف المانهای کامپوننت Counter، جزئی از اطلاعات props خواهند بود. در اینجا نمیتوان به state کامپوننت مدنظری دسترسی یافت و آنرا مقدار دهی کرد. به همین ترتیب state کامپوننت Counters نیز در سایر کامپوننتها قابل دسترسی نیست.
- همچنین باید درنظر داشت که props، در مقایسه با state، فقط خواندنی است. به عبارتی مقدار ورودی به یک کامپوننت را داخل آن کامپوننت نمیتوان تغییر داد. برای مثال سعی کنید در داخل متد رویدادگردان کلیک موجود در کامپوننت Counter، مقدار this.props.value را به صفر تنظیم کنید. در این حالت با کلیک بر روی دکمهی Increment، بلافاصله خطای readonly بودن خواص شیء منتسب به props را دریافت میکنیم. در اینجا اگر نیاز است این مقدار را داخل کامپوننت تغییر دهیم، باید ابتدا این مقدار را دریافت کرده و سپس آنرا داخل state قرار دهیم. پس از آن امکان ویرایش اطلاعات منتسب به state، داخل یک کامپوننت وجود خواهد داشت.
صدور و مدیریت رخدادها
در ادامه میخواهیم در کنار هر دکمهی Increment کامپوننت شمارشگر، یک دکمهی Delete هم قرار دهیم:
مشکل! اگر کد مدیریتی handleDelete را در کامپوننت Counter قرار دهیم، چگونه باید به لیست آرایهی اشیاء counters والد آن، یعنی کامپوننت Counters که سبب رندر شدن کامپوننتهای شمارشگر شده (state = { counters: [ ] })، دسترسی یافت و شیءای را از آن حذف کرد؟ در React، کامپوننتی که state ای را تعریف میکند، باید کامپوننتی باشد که قرار است آنرا تغییر دهد و اطلاعات state هر کامپوننت، صرفا متعلق به آن کامپوننت بوده و جزو اطلاعات خصوصی آن است. بنابراین مدیریت حذف و یا افزودن کامپوننتها در لیست نمایش داده شده، باید جزو وظایف کامپوننت Counters باشد و نه Counter.
برای حل این مشکل، کامپوننت Counter تعریف شده (کامپوننت فرزند) باید سبب بروز رخداد onDelete شود تا کامپوننت Counters (کامپوننت والد)، آنرا توسط متد handleDelete مدیریت کند. بنابراین ابتدا به کامپوننت Counters (کامپوننت والد) مراجعه کرده و متد رویدادگردان handleDelete را به آن اضافه میکنیم:
handleDelete = () => { console.log("handleDelete called."); };
<Counter key={counter.id} value={counter.value} selected={true} onDelete={this.handleDelete} />
<button onClick={this.props.onDelete} className="btn btn-danger btn-sm m-2" > Delete </button>
اکنون اگر برنامه را ذخیره کرده و پس از بارگذاری مجدد برنامه در مرورگر بر روی دکمهی Delete کلیک کنیم، پیام «handleDelete called» در کنسول توسعه دهندگان مرورگر لاگ میشود. به این ترتیب کامپوننت فرزند سبب بروز رخدادی شده و والد آن، این رخداد را مدیریت میکند.
به روز رسانی state
تا اینجا دکمهی Delete فرزند، به متد handleDelete والد متصل شدهاست. مرحلهی بعد، پیاده سازی واقعی حذف یک المان از DOM مجازی و به روز رسانی state است. برای اینکار ابتدا به رخدادگردان onClick، در کامپوننت شمارشگر، مراجعه کرده و id دریافتی را به سمت والد ارسال میکنیم:
onClick={() => this.props.onDelete(this.props.id)}
<Counter key={counter.id} value={counter.value} selected={true} onDelete={this.handleDelete} id={counter.id} />
handleDelete = counterId => { console.log("handleDelete called.", counterId); const counters = this.state.counters.filter( counter => counter.id !== counterId ); this.setState({ counters }); // = this.setState({ counters: counters }); };
البته پیاده سازی ما تا به اینجا بدون مشکل کار میکند، اما به ازای هر خاصیت counter، یک ویژگی جدید را به تعریف المان مرتبط اضافه کردهایم که در طول زمان بیش از اندازه طولانی خواهد شد. برای رفع این مشکل، خود شیء counter را به صورت یک ویژگی جدید به کامپوننت مرتبط با آن ارسال میکنیم. به این ترتیب اگر در آینده خاصیتی را به این شیء اضافه کردیم، دیگر نیازی نیست تا آنرا به صورت دستی و مجزا تعریف کنیم. به همین جهت ابتدا تعریف المان Counter را به صورت زیر خلاصه میکنیم که در آن ویژگی جدید counter، حاوی کل شیء counter است:
<Counter key={counter.id} counter={counter} onDelete={this.handleDelete} />
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید: sample-07.zip
Tree for GitLab
Browser extension that makes navigating a GitLab repository feel like a breeze by providing a familiar tree structure.
توصیه هایی در استفاده از NGEN
تزریق خودکار وابستگیها در برنامههای ASP.NET Web forms
ولی در یک موضوع به بن بست خوردم
public class EfOurServiceService : IOurServiceService { readonly IUnitOfWork _uow; readonly IDbSet<OurService> _ourServices; readonly IDbSet<OurServiceCategory> _ourServiceCategories; public EfOurServiceService(IUnitOfWork uow) { _uow = uow; _ourServices = _uow.Set<OurService>(); _ourServiceCategories = _uow.Set<OurServiceCategory>(); } public IList GetMasterDetailsFilterLang(string language) { var query = (_ourServiceCategories .Where(c => (c.Language == language)) .Select( c => new { CatId=c.Id, CatName=c.Title, OurServices = c.OurServices .Select( o => new { ServId= o.Id, ServName= o.Title } ) } )); var ss = query.ToList(); //در اینجا که در لایه سرویس در پروژه ای جداگانه است به ایتمهای کوئری دسترسی کامل دارم foreach (var master in ss) { var s = master.CatId; foreach (var details in master.OurServices) { var cc = details.ServName; } } return ss; } }
در اینجا که در لایه سرویس در پروژه ای جداگانه است به ایتمهای کوئری دسترسی کامل دارم
اما زمانی که در پروژه UI که وب سایت Webform قرار داره به شکل زیر از این متود استفاده میکنم :
public string CreateServiceMnu() { var ds = OurServiceService.GetMasterDetailsFilterLang(_LangSar); foreach (var master in ds) { var s = master.CatId; foreach (var details in master.OurServices) { var cc = details.ServName; } } return string.Empty; }
اما به Propertyها دسترسی ندارم : از IList استفاده کردم (پر میشه اما قابل دسترس نیست)
نمیدونم دلیلش چیه ؟
تو لایه Service به Propertyها دسترسی دارم اما در لایه UI که وابستگیها تزریق میشه Propertyها قابل دسترس نیست.