نظرات مطالب
بررسی خطاهای ممکن در حین راه اندازی اولیه برنامه‌های ASP.NET Core در IIS
یکی از شرایطی که در زمان publish برنامه و share کردن برنامه در IIS باعث بروز خطای 502.5 می‌شود  مطلب فعالسازی Development time IIS support می‌باشد.
برای انجام این فعالسازی، تغییر زیر در تگ aspNetCore در فایل web.config بوجود می‌آید که در زمان publish برنامه هم باقی می‌ماند:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <system.webServer>
    ...
    <aspNetCore processPath="bin\IISSupport\VSIISExeLauncher.exe" arguments="-argFile IISExeLauncherArgs.txt" forwardWindowsAuthToken="false" startupTimeLimit="3600" requestTimeout="23:00:00" stdoutLogEnabled="false" />
    ...
  </system.webServer>
</configuration>
که باعث بروز 
خطای 502.5  می‌باشد. برای حل این مشکل و همچنین استفاده از قابلیت ذکر شده در زمان توسعه برنامه، پس از publish برنامه در پوشه خروجی به فایل web.config مراجعه کرده تگ  aspNetCore  را به حالت اولیه آن باز می‌گردانیم:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <system.webServer>
    ...
     <aspNetCore processPath=".\ApplicationName.exe" stdoutLogEnabled="false" stdoutLogFile=".\logs\stdout" />
    ...
  </system.webServer>
</configuration>
که در آن ApplicationName.exe نام فایل خروجی است که باید با نام مناسب جایگزین گردد.
نظرات مطالب
UrlRewriter توسط Intelligencia.UrlRewriter
سلام , با تشکر از پست خوبتون .
بعد از اجرا سایت ارور 500 میده چیکار کنم ؟
 هم لوکال و هم روی هاست .
خطا مربوط به این خط برنامه در Web.Config   است :
<httpModules>
      <add type="Intelligencia.UrlRewriter.RewriterHttpModule,Intelligencia.UrlRewriter" name="UrlRewriter" />
    </httpModules>

مطالب
آشنایی با ساختار IIS قسمت دوازدهم
پیکربندی قسمت لاگ‌ها، میتواند برای یک سرور و یا وب سایت خاص از طریق فایل کانفیگ یا از طریق خود IIS انجام گیرد. برای اینکه به بیشتر این قابلیت‌ها در IIS دسترسی داشت، باید یکی از نسخه‌های ویندوز سرور 2012 و ویندوز 8 را نصب کرده باشید. لاگ‌ها به ثبت خطاها و درخواست‌های HTTP می‌پردازند و با تحلیل آن‌ها میتوان عملیات بهینه سازی را بر روی سرو اجرا کرد. تمامی ثبت لاگ‌ها توسط Http.sys انجام می‌گیرد.

نحوه‌ی ذخیره سازی لاگها
در این بخش نحوه‌ی ذخیره سازی و فرمت ذخیره‌ی لاگ‌ها را در دو سطح سایت و سرور به طور جداگانه بررسی می‌کنیم. در IIS ماژول Logging را باز کنید و در لیست One log file per می‌توانید مشخص کنید که لاگ‌ها در چه سطحی اجرا شوند. اگر گزینه‌ی server باشد، تمامی خطاها و درخواست‌های رسیده به سرور در یک فایل لاگ ثبت می‌شوند. ولی اگر سطح سایت باشد، برای هر سایت بر روی IIS لاگ‌ها، جداگانه بررسی می‌شوند. به طور پیش فرض سطح سایت انتخاب شده است.

سطح سایت
موقعی‌که در لیست، سایت را انتخاب کنید، در لیست format می‌توانید تعیین کنید که لاگ‌ها به چه صورتی باید ذخیره شوند. مواردی که در این حالت لیست می‌شوند گزینه‌های W3C,IIS,NCSA,Custom می‌باشند که در زیر یکایک آن‌ها را بررسی می‌کنیم:

فرمت IIS: این فرمت توسط مایکروسافت ارائه شده و در این حالت لاگ‌های همه‌ی وب سایت‌ها ذخیره می‌شوند. به این فرمت  Fixed ASCII Based Text نیز می‌گویند؛ چرا که اجازه‌ی خصوصی سازی ندارد و نمی‌توانید بگویید چه فیلدهایی در لاگ قرار داشته باشند. لاگ فایل‌های این فرمت با ، (کاما) از هم جدا می‌شوند و مقدار زمانی که برای هر فیلد ثبت می‌شود، به صورت محلی local Time می‌باشد.
فیلدهایی که در لاگ این نوع فرمت خواهند آمد، به شرح زیر است:
  • Client IP address 
  • User name 
  • Date 
  • Time 
  • Service and instance 
  • Server name 
  • Server IP address 
  • Time taken 
  • Client bytes sent 
  • Server bytes sent 
  • (Service status code (A value of 200 indicates that the request was fulfilled successfully 
  • (Windows status code (A value of 0 indicates that the request was fulfilled successfully. 
  • Request type 
  • Target of operation 
  • (Parameters (the parameters that are passed to a script 
احتمال این وجود دارد که بعضی از فیلدها در بعضی رکوردها، شامل اطلاعاتی نباشند که به جای مقدار آن علامت -  ثبت می‌گردد و برای کاراکترهایی که قابل نمایش نیستند یا کاراکتر نمایشی ندارند، از علامت + استفاده می‌شود. دلیل اینکار هم این است که ممکن است یک کاربر مهاجم، به ارسال اطلاعات کلیدهای کنترلی چون Carriage return اختصارا CR یا Line Feed به اختصار LF کند، که باعث شکسته شدن خط لاگ فایل می‌شود و در نتیجه از استاندارد خارج خواهد شد و هنگام خواندن آن هم با خطا روبرو می‌شویم؛ در نتیجه با جایگزینی چنین کاراکترهایی با + از این اتفاق جلوگیری می‌شود.
شکل زیر نمونه ای از یک خط لاگ در این فرمت است:
192.168.114.201, -, 03/20/01, 7:55:20, W3SVC2, SERVER, 172.21.13.45, 4502, 163, 3223, 200, 0, GET, /DeptLogo.gif, -,
 نام فیلد نوع حالت مقداردهی  توضیح اتفاقات افتاده
 Client IP address   192.168.114.201   آی پی کلاینت
 User name   -  کاربر ناشناس است
 Date  03/20/01   تاریخ فعالیت
 Time  7:55:20  ساعت فعالیت 
 Service and instance  W3SVC2  لاگی که مربوط به سایت خاصی می‌شود به صورت  #W3SVC  نمایش داده می‌شود که علامت # شماره سایت می‌باشد که در اینجا این لاگ مربوط به سایت شماره 2 است 
 Server name   SERVER   نام سرور
 Server IP   172.21.13.45   آی پی سرور
 Time taken  4502   چقدر انجام عملیات این درخواست به طول انجامیده است که بر حسب میلی ثانیه است.
 Client bytes sent   163   تعداد بایت هایی که از طرف کلاینت به سرور ارسال شده است
 Server bytes sent   3223   تعداد بایت هایی که از طرف سرور به سمت کلاینت ارسال شده است
 Service status code   200   درخواست کاملا موفقیت آمیز بوده است
 Windows status code  0  درخواست کاملا موفقیت آمیز بوده است
 Request type  GET   نوع درخواست کاربر
 Target of operation   /DeptLogo.gif   کاربر قصد دانلود یک فایل تصویری GIF داشته است که نامش Deptlogo است
 Parameters   -  پارامتری ارسال نشده است

فرمت NCSA:
 این فرمت توسط مرکز علمی کاربردهای ابرمحاسباتی National Center for Supercomputing Applications ایجاد شده و دقیقا مانند قبلی نمیتوان در آن نوع فیلدها را مشخص کرد و برای جدا سازی، از فاصله space استفاده می‌کند و ثبت مقدار زمان در آن هم به صورت محلی و هم UTC می‌باشد.
این فیلدها در لاگ آن نمایش داده می‌شوند:
  • Remote host address 
  • (Remote log name (This value is always a hyphen 
  • User name 
  • Date, time, and Greenwich mean time (GMT) offset 
  • Request and protocol version 
  • (Service status code (A value of 200 indicates that the request was fulfilled successfully   
  • Bytes sen 

نمونه ای از یک لاگ ثبت شده:
172.21.13.45 - Microsoft\JohnDoe [08/Apr/2001:17:39:04 -0800] "GET /scripts/iisadmin/ism.dll?http/serv HTTP/1.0" 200 3401
نام فیلد  مقدار ثبت شده  توضیح اتفاق افتاده 
 Remote host address  172.21.13.45  آی پی کلاینت
 Remote log name  - نامی وجود ندارد 
 User name  Microsoft\JohnDoe  نام کاربری
 Date, time, and GMT offset  [08/Apr/2001:17:39:04 -0800]  تاریخ و ساعت فعالیت به صورت محلی که 8 ساعت از مبدا گرینویچ بیشتر است
 Request and protocol version  GET /scripts/iisadmin/ism.dll?http/serv HTTP/1.0  کاربر با متد GET و Http نسخه‌ی یک، درخواست فایل ism.dll را کرده است.
 Service status code  200  عملیات کاملا موفقیت آمیز بود.
 Bytes sent  3401  تعداد بایت‌های ارسال شده به سمت کاربر

امنیت در برابر کاربران مهاجم مانند همان فرمت قبلی صورت گرفته است.


فرمت W3C: توسط W3C توسط کنسرسیوم جهانی وب ارائه شده است و یک فرمت customizable ASCII text-based است. به این معنی که میتوان فیلدهایی که در گزارش نهایی می‌آید را خودتان مشخص کنید، که برای اینکار در کنار لیست، دکمه‌ی Select وجود دارد که میتوانید هر کدام از فیلد‌هایی را که خواستید، انتخاب کنید تا به ترتیب در خط لاگ ظاهر شوند. تاریخ ثبت به صورت UTC است.

نام فیلد   توضیح به طور پیش فرض انتخاب شده است 
 Date  تاریخ رخ دادن فعالیت  بله
 Time  ساعت زخ دادن فعالیت بر اساس UTC  بله
 Client IP Address آی پی کلاینت  بله
 User Name نام کاربری که هویت آن تایید شده و در صورتی که هویت تایید شده نباشد و کاربر ناشناس باشد، جای آن - قرار می‌گیرد   بله
 Service Name and Instance Number  نام و شماره سایتی که درخواست در آن صورت گرفته است  خیر
 Server Name  نام سروری که لاگ روی آن ثبت می‌شود  خیر
 Server IP Address  آی پی سرور که لاگ روی آن ثبت می‌شود  بله
 Server Port  شمره پورتی که سرویس مورد نظر روی آن پورت اعمال می‌شود.  بله
 Method  متد درخواست مثل GET  بله
 URI Stem   هدف درخواست یا Target مثل index.htm  بله
 URI Query  کوئری ارسال شده برای صفحات داینامیک  بله
 HTTP Status  کد وضیعینی HTTP status  بله
 Win32 Status  کد وضعیتی ویندوز  خیر
 Bytes Sent  تعداد بایت‌های ارسال شده به سمت کلاینت  خیر
 Bytes Received  تعداد بایت‌های دریافت شده از سمت کلاینت  خیر
 Time Taken  زمان به طول انجامیدن درخواست بر حسب میلی ثانیه  خیر
 Protocol Version درخواست با چه نسخه‌ای از پروتکل http یا ftp ارسال شده است  خیر
 Host  اگر در هدر درخواست ارسالی این گزینه بوده باشد، نوشته خواهد شد.  خیر
 User Agent  اطلاعات را از هدر درخواست می‌گیرد.  بله
 Cookie اگر کوکی رد و بدل شده باشد، محتویات کوکی ارسالی یا دریافت شده  خیر
 Referrer  کاربر از چه سایتی به سمت سایت ما آمده است.  خیر
 Protocol Substatus 
 در صورت رخ دادن خطا در IIS ، کد خطا بازگردانده میشود. در IIS به منظور امنیت بیشتر و کاهش حملات، محتوای خطاهای رخ داده در IIS به صورت متنی نمایش داده نمی‌شوند و شامل کد خطایی به اسم Substatus Code هستند تا مدیران شبکه با ردیابی لاگ‌ها پی به دلیل خطا و درخواست‌های ناموفق ببرند. برای مثال Error 404.2 به این معنی است که فایل درخواستی به دلیل قوانین محدود کنده، قفل شده و قابل ارائه نیست. ولی هکر تنها با خطای 404 یعنی وجود نداشتن فایل روبرو می‌شود. در حالت substatus code، کد شماره 2 را هم خواهید داشت که در لاگ ثبت می‌شود.
هر شخصی که در سرور توانایی دسترسی به لاگ‌ها را داشته باشد، می‌تواند کد دوم خطا را نیز مشاهده کند. برای مثال مدیر سرور متوجه میشود که یکی از فایل‌های مورد نظر به کاربران، خطای 404 نمایش میدهد و با بررسی لاگ‌ها متوجه می‌شود که کد خطا 404.9 هست. از آنجا که ما همه‌ی کدها را حفظ نیستیم به این صفحه رجوع می‌کنیم و متوجه میشویم تعداد کاربرانی که برای این فایل، اتصال connection ایجاد کرده‌اند بیش از مقدار مجاز است و مدیر میتواند این وضع را کنترل کند. برای مثال تعداد اتصالات مجاز را نامحدود unlimited تعیین کند.
 بله
حروف - و + برای موارد بالا هم صدق می‌کند. در ضمن گزینه‌های زیر در حالتی که درخواست از پروتکل FTP باشد مقداری نخواهند گرفت:
  • uri-query
  • host
  • (User-Agent)
  • Cookie
  • Referrer
  • substatus

گزینه Custom
 : موقعی که شما این گزینه را انتخاب کنید ماژول logging غیرفعال خواهد شد. زیرا این امکان در IIS قابل پیکربندی نیست و نوشتن ماژول آن بر عهده شما خواهد بود؛ با استفاده از اینترفیس های ILogPlugin ، ILogPluginEx و  ILogUIPlugin  آن را پیاده سازی کنید.

ذخیره اطلاعات به انکدینگ UTF-8 و موضوع امنیت
در صورتی که شما از سایتی با زبانی غیر از انگلیسی و لاتین و فراتر از ANSI  استفاده می‌کنید، این گزینه حتما باید انتخاب شده باشد تا درخواست را بهتر لاگ کند. حتی برای وب سایت‌های انگلیسی زبان هم انتخاب این گزینه بسیار خوب است؛ چرا که اگر به سمت سرور کاراکترهای خاصی در URL ارسال شوند، نمی‌تواند با کدپیج موجود آن‌ها را درست تبدیل کند.

ادامه‌ی تنظیمات
موارد بعدی که در تنظیمات لاگ‌ها کاملا مشخص و واضح است، عملیات زمان بندی  است که برای ساخت یک فایل لاگ جدید به کار می‌رود؛ برای مثال هر ساعت یک لاگ فایل جدید بسازد و فعالیت‌های موجود در هر ساعت در یک لاگ ذخیره می‌شوند.
گزینه‌ی بعدی حداکثر حجم هر فایل لاگ است که به صورت بایت مشخص می‌شود. اگر مقداری که تعیین میکنید کمتر از 1048576 بایت باشد، خودش به طور پیش فرض همان 1048576 بایت را در نظر خواهد گرفت.
گزینه بعدی do not create a new logfile بدین معناست که همه‌ی لاگ‌ها در یک فایل ذخیره می‌شوند و فایل جدیدی برای لاگ‌ها ایجاد نمی‌شود.
گزینه آخری به اسم use local time for filenaming and rollover است که اگر انتخاب شود، نامگذاری هر فایل لاگ بر اساس زمان محلی ساخت فایل لاگ خواهد بود. در صورتیکه انتخاب نشود، نامگذاری با زمان  UTC درج خواهد شد.

سطح سرور
لاگ‌ها فقط در سمت سرور انجام می‌گیرد و لاگ هر سایت در یک فایل لاگ ثبت می‌شود. اگر بخواهید لاگ‌ها را در سطح سرور انجام دهید، گزینه‌ی binary هم اضافه خواهد شد.
Binary: در این گزینه دیگر از قالب بندی یا فرمت بندی لاگ‌ها خبری نیست و لاگ هر وب سایت به صورت اختصاصی صورت نمی‌گیرد. عملیات ذخیره سازی و ثبت هر لاگ می‌تواند از منابع یک سرور از قبیل حافظه و CPU و ... استفاده کند و اگر تعداد این وب سایت‌ها بالا باشد، باقی روش‌ها باعث فشار به سرور می‌شوند. برای همین ایجاد یک فایل خام از لاگ‌ها در این مواقع می‌تواند راهگشا باشد. برای همه یک فایل لاگ ایجاد شده  و بدون قالب بندی ذخیره می‌کند. پسوند این نوع لاگ‌ها ibl است که مخفف Internet Binary Log می‌باشد. دلیل این تغییر پسوند این است که اطمینان کسب شود کاربر، با برنامه‌های متنی چون notepad یا امثال آن که به Text Utilities معروفند فایل را باز نمی‌کند. برای خواندن این فایل‌های میتوان از برنامه‌ی Log parser استفاده کرد. پروتکل‌های FTP,NNTP و SMTP در این حالت لاگشان ثبت نمی‌شود.
نظرات مطالب
طراحی یک ماژول IpBlocker در ASP.NET MVC
- خیر. تنظیم این اعداد به مقادیر کوچک، کاربران عادی را هم از انجام کارهای ساده عاجز می‌کند.
- اطلاعات IPهای بسته شده توسط خود این ماژول لاگ می‌شود. اگر سیستم لاگر مبتنی بر دیتابیس را به برنامه اضافه کنید، این لاگ‌ها به صورت خودکار در بانک اطلاعاتی ذخیره خواهند شد. در آنجا آی‌دی کاربر یا هر اطلاعات دیگری را هم خودتان به ازای هر لاگ ارائه شده، ذخیره کنید. البته اگر از Shadow properties و یا سیستم Tracking استفاده می‌کنید، لاگر مبتنی بر دیتابیس  اطلاعات کاملی را از کاربر جاری ذخیره می‌کند و نیازی به کدهای اضافه‌تری ندارد.
مطالب
کار با Visual Studio در ASP.NET Core
پیش نویس: این مقاله ترجمه شده فصل 6 کتاب Pro Asp.Net Core MVC2 می‌باشد.

کار با Visual Studio

در این مقاله، یکسری توضیحاتی در مورد ویژگی‌های کلیدی ویژوال استودیو به برنامه نویس‌های (توسعه دهنده‌های) پروژه‌های Asp.net Core MVC ارائه می‌دهیم.

 
ایجاد یک پروژه

در ابتدا یک پروژه‌ی وب جدید Asp.net core را به نام Working و بر اساس قالب Empty ایجاد می‌کنیم. سپس در کلاس startup، قابلیت MVC را فعال میکنیم (کدهای این قسمت، در فصل 5 کامل شرح داده شده‌است)
 

namespace Working
{
    public class Startup
    {
        // This method gets called by the runtime. Use this method to add services to the container.
        // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc();
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            //app.Run(async (context) =>
            //{
            //    await context.Response.WriteAsync("Hello World!");
            //});
        }
    }
}

ایجاد مدل:

یک پوشه جدید را به نام Models ایجاد می‌کنیم و بعد در این پوشه یک کلاس جدید را به نام Product ایجاد می‌کنیم و کدهای زیر را در کلاس ایجاد شده قرار میدهیم (این قسمت در فصل 5 نیز شرح داده شده‌است):
namespace Working.Models
{
    public class Product
    {
        public string Name { get; set; }
        public decimal Price { get; set; }
    }
}
برای ایجاد یک فروشگاه ساده از اشیاء محصول، من یک فایل کلاس را به نام SimpleRepository.cs به پوشه Models اضافه و از آن برای تعریف کلاس استفاده کردم.
 

namespace WorkingWithVisualStudio.Models
{
    public class SimpleRepository
    {
        private static SimpleRepository sharedRepository = new SimpleRepository();
        private Dictionary<string, Product> products = new Dictionary<string, Product>();
        public static SimpleRepository SharedRepository => sharedRepository;

        public SimpleRepository()
        {
            var initialItems = new[] { new Product { Name = "Kayak", Price = 275M }, new Product { Name = "Lifejacket", Price = 48.95M }, new Product { Name = "Soccer ball", Price = 19.50M }, new Product { Name = "Corner flag", Price = 34.95M } };
            foreach (var p in initialItems)
            {
                AddProduct(p);
            }
        }
        public IEnumerable<Product> Products => products.Values;
        public void AddProduct(Product p) => products.Add(p.Name, p);

    }
}
کلاس Stores، اشیا مدل را در حافظه ذخیره می‌کند. یعنی هر تغییری را که در Model داده باشید، زمانیکه نرم افزار متوقف یا Restart شود، از بین می‌رود. یک فروشگاه ناپیوسته برای مثال در این فصل کافی است. اما این رویکردی نیست که در بسیاری از پروژه‌های واقعی استفاده شود. در فصل 8 یک مثال را پیاده سازی می‌کنیم تا اطلاعات مدل Store را به صورت مداوم در بانک اطلاعاتی ذخیره کند.

نکته: من یک مشخصه (Property) استاتیک را به نام SharedRepository تعریف کردم که دسترسی به SimpleRepository را فراهم می‌کند و می‌تواند در طول برنامه از آن استفاده شود. این بهترین کار نیست، ولی میخواهم یک مشکل رایج را که در توسعه MVC روبرو میشوید، نشان دهم. من راه بهتری را برای کار با اجزای مشترک، در فصل 18 توضیح می‌دهم.


ایجاد Controller و View

در پوشه Controllers، یک فایل جدید را به نام HomeController.cs ایجاد می‌کنیم و کدهای زیر را در آن قرار میدهیم:
namespace WorkingWithVisualStudio.Controllers
{
    public class HomeController : Controller
    {
        public IActionResult Index() => View(SimpleRepository.SharedRepository.Products);
    }
}
این یکی Action Method ایی به نام Index است که اطلاعات مدل را دریافت می‌کند و برای View پیشفرض، جهت نمایش ارسال می‌کند. برای ایجاد View هم بر روی پوشه Views/Home راست کلیک کرده و یک View را به نام index.cshtml ایجاد کنید؛ با کدهای زیر:
@{
    Layout = null;
}

<!DOCTYPE html>

<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>>Working with Visual Studio</title>
</head>
<body>
    <table>
    <thead>
        <tr>
        <td>Name</td>
        <td>Price</td>
        </tr>
        </thead>
    <tbody>            @foreach (var p in Model)
    {<tr>
        <td>@p.Name</td>
        <td>@p.Price</td>
        </tr>}
        </tbody>
    </table>
</body>
</html>
این View شامل یک جدول است که از حلقه foreach Razor، برای ایجاد ردیف‌هایی برای هر شیء مدل استفاده می‌کند. جایی که هر ردیف، حاوی سلول‌هایی برای خواص نام و قیمت است. اگر شما برنامه کاربردی را اجرا کنید، نتایج حاصل را در شکل خواهید دید:

مدیریت بسته‌های نرم افزاری

دو نوع مختلف از بسته‌های نرم افزاری مورد نیاز برای Asp.Net Core MVC وجود دارند.

معرفی NuGet 

ویژوال استودیو به همراه یک ابزار گرافیکی برای مدیریت بسته‌های NET. است که در یک پروژه گنجانده شده‌است. برای باز کردن این ابزار، گزینه Management NuGet Packages for Solution را از منوی Tools ➤ NuGet Package Manager انتخاب کنید. به این ترتیب ابزار NuGet باز می‌شود و لیستی از بسته‌هایی که قبلا نصب شده‌اند، نمایش داده می‌شود؛ همانطور که در شکل زیر نشان داده شده‌است:
 


برگه‌ی Installed، خلاصه‌ای از بسته‌هایی را که قبلا در پروژه نصب شده‌اند، نشان می‌دهد. از برگه‌ی Browse، برای یافتن و نصب بسته‌های جدید می‌توان استفاده کرد و برگه‌ی Updates، فهرست package هایی را که نسخه‌های اخیر آن‌ها منتشر شده‌اند، نمایش می‌دهد.

 
معرفی بسته‌ی MICROSOFT.ASPNETCORE.ALL

اگر شما از نسخه‌های قبلی Asp.Net Core استفاده کرده باشید، باید یک لیست طولانی از بسته‌های NuGet را به پروژه جدید خود اضافه نمایید. Asp.Net Core2 یک بسته‌ی متفاوت را به نام Microsoft.AspNetCore.All معرفی می‌کند.

پکیچ Microsoft.AspNetCore.All یک meta-package است که شامل تمام بسته‌های Nuget مورد نیاز Asp.net Core و MVC Framework است. یعنی شما دیگر نیازی به نصب تک به تک این نوع بسته‌ها ندارید و هنگامیکه برنامه خود را منتشر می‌کنید، هر بسته‌ای از بسته‌های Meta-package که مورداستفاده قرار نمی‌گیرند، حذف خواهند شد. البته این بسته در نگارش 2.1، قسمت All آن به App تغییر نام یافته‌است.
 
معرفی بسته‌های Nuget و موقعیت ذخیره سازی آن‌ها

ابزار NuGet لیست بسته‌های پروژه را در فایل projectname.csproj نگهداری می‌کند. در اینجا <projectname> با نام پروژه جایگزین میشود. برای مثال در پروژه فوق اطلاعات Nuget، در فایل WorkingWithVisualStudio.csproj ذخیره می‌شوند. ویژوال استودیو محتویات فایل csproj را در پنجره‌ی Solution Explorer نمایش نمی‌دهد. برای ویرایش این فایل، روی پروژه در پنجره‌ی Solution Explorer راست کلیک کنید و گزینه‌ی Edit WorkWithVisualStudio.csproj را از منوی باز شده، انتخاب کنید. ویژوال استودیو فایل را برای ویرایش باز می‌کند. فایل csproj یک فایل XML است و شما در آن عنصری را مانند قطعه کد زیر در آن می‌بینید که Asp.net Core Meta package را به پروژه اضافه می‌کند:
<ItemGroup>
    <PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.0" />
</ItemGroup>
در اینجا یک بسته با نام و شماره نسخه مورد نیاز مشخص شده‌است. اگرچه بسته Meta-Package شامل تمام ویژگی‌های مورنیاز Asp.Net Core MVC می‌باشد، اما شما هنوز هم باید بسته‌های دیگری را به پروژه اضافه کنید تا بتوانید از ویژگی‌های اضافی خاص آن‌ها استفاده کنید. این بسته‌ها را می‌توان توسط رابط‌های خط فرمان و یا ابزار گرافیکی آن اضافه کرد. حتی شما می‌توانید فایل Csproj را به صورت مستقیم ویرایش کنید و ویژوال استودیو میتواند تغییرات بسته‌ها را شناسایی کرده، دانلود و نصب کند.
 


هنگامیکه از NuGet برای اضافه کردن یک بسته به پروژه‌ی خود استفاده می‌کنید، به صورت خودکار به همراه هر بسته‌ای که به آن وابستگی دارد، نصب می‌شود. شما می‌توانید بسته‌های Nuget و وابستگی‌های آن‌ها را در SolutionExpolrer از طریق گزینه‌ی Dependencies -> Nuget مشاهده کنید که هر یک از بسته‌های موجود در فایل csproj و وابستگی‌های آن‌ها را نشان می‌دهد. برای نمونه بسته Meta-Package ASP.Net Core دارای تعداد زیادی وابستگی است؛ برخی از آنها در شکل زیر دیده میشوند:


 
معرفی Bower

یک بسته Client-Side، شامل محتوایی است که به مشتری ارسال می‌شود؛ مانند فایل‌های جاوا اسکریپت، Css Stylesheets و یا تصاویر. از Nuget برای مدیریت این نوع فایل‌ها در پروژه نیز استفاده میشود. اما اکنون Asp.Net Core MVC پشتیبانی توکاری را از یک ابزار مدیریت بسته‌های سمت کاربر، به نام Bower نیز ارائه می‌دهد. Bower یک ابزار منبع باز ( Open Source ) است که در خارج از مایکروسافت و دنیای NET. توسعه داده شده و نگهداری می‌شود.

نکته: Bower به تازگی منسوخ شده اعلام گردیده‌است. ممکن است هشدارهایی را که ابزارهای جایگزین را پیشنهاد می‌کنند نیز مشاهده کنید. با این حال پشتیبانی از Bower با ویژوال استودیو یکپارچه شده‌است و در نگارش 2.1 ابزار مدیریت سمت کلاینت جدید دیگری را نیز بجای آن معرفی کرده‌اند.
 

معرفی لیست بسته‌های Bower

بسته‌های Bower از طریق فایل ویژه‌ی bower.json مشخص می‌شوند. برای ایجاد این فایل در پنجره Solution Explorer روی پروژه WorkingWithVisualStudio راست کلیک کنید و Add -> New Item را از منوی باز شده انتخاب کنید. سپس قالب مورد نظر Bower Configuration File را از Asp.net Core -> Web -> General  Category انتخاب نمائید؛ مانند تصویر زیر:
 


ویژوال استودیو نام bower.json را برای آن قرار میدهد. برروی ok کلیک می‌کنیم و یک فایل جدید، با محتویات پیشفرض زیر به پروژه اضافه میشود:
{
  "name": "asp.net",
  "private": true,
  "dependencies": {}
}
به علاوه برای فایل Bower.json، تصویر زیر بسته‌های Client Side وابسته به Bower را نشان میدهد. از این قسمت برای اضافه کردن وابستگی‌های برنامه نیز استفاده میشود.


نکته: منبع بسته‌های Bower در لینک http://bower.io/search وجود دارد. شما می‌توانید بسته‌ها مورنظر را در اینجا جستجو و به پروژه اضافه کنید.

بعد از اینکه بسته‌ها نصب شدند، محتویات فایل bower.json به صورت زیر می‌باشد:
{
  "name": "asp.net",
  "private": true,
  "resolutions": {
    "jquery": "3.3.1"
  }
}

در ادامه بسته Bootstrap CSS به پروژه اضافه شده‌است. زمانیکه شما فایل Bower.json را ویرایش می‌کنید، ویژوال استادیو لیستی از نام بسته‌ها و نسخه‌های بسته‌های موجود را نمایش می‌دهد؛ مانند تصویر زیر:


در زمان نوشتن این مطلب، آخرین نسخه‌ی پایدار بسته بوت استرپ، 3،3،7 است. البته اگر در دقت کنید، در اینجا سه گزینه‌ی ارائه شده‌ی توسط ویژوال استودیو وجود دارند: 3.3.7 و 3.3.7^ و 3.3.7~. شماره نسخه می‌تواند در طیف وسیعی از روش‌های مختلف در فایل bower.json مشخص شود. مفیدترین آنها در جدول زیر شرح داده شده‌اند. استفاده از شماره نسخه صریح یک بسته، امن‌ترین راه برای مشخص کردن یک بسته است. این تضمین می‌کند که شما همیشه با همان نسخه کار می‌کنید؛ مگر اینکه عمدا فایل bower.json را برای پاسخ گویی به درخواست‌های دیگری به روز رسانی کنید:
  فرمت    توضیحات 
  3.3.7  بیان شماره مستقیم بسته نصب شده و تطبیق دقیق آن با شمار نسخه ، e.g ، 3.3.7 
  *  با استفاده از یک ستاره به Bower اجازه نصب آخرین نسخه را می‌دهد
3.3.7 =<3.3.7<
پیشوند یک شماره نسخه با < یا =< به Bower اجازه می‌دهد تا هر نسخه از بسته‌ای که بزرگتر یا بزرگتر مساوی آن نسخه‌ی معین است، نصب شود 
3.3.7 =>3.3.7
پیشوند یک شماره نسخه با > یا => به Bower اجازه می‌دهد تا هر نسخه از بسته‌ای را که کوچکتر یا کوچکتر و مساوی نسخه‌ی معین است، نصب شود 
  3.3.7~  پیشوند یک شماره نسخه با یک tilde (با کاراکتر ~ ) به نسخه‌هایی که دو شماره اولیه آن‌ها مشابه باشند، اجازه نصب میدهد؛ حتی اگر شماره آخر آن نسخه متفاوت باشد. مانند نسخه‌های 3.3.9 و 3.3.8 و اجازه نصب نسخه 3.4.0 را نمیدهد؛ چون شماره دوم آن متفاوت است.
  3.3.7^  پیشوند یک شماره نسخه با یک قلم (کاراکتر ^) به نسخه‌هایی که شماره اول آنها مشابه باشند، اجازه نصب می‌دهد؛ حتی اگر شماره دوم آن‌ها متفاوت باشد. مانند نسخه‌های 3.3.1 و 3.4.1 و 3.5.1 اما نسخه 4.0.0 اجازه نصب ندارد 
 
نکته: برای مثال در این کتاب، من فایل bower.json را مستقیما ایجاد و ویرایش می‌کنم. ویرایش این فایل ساده است و به شما کمک می‌کند تا اطمینان حاصل کنید که نتایج مورد انتظار را در صورت پیگیری به همراه داشته باشد. همچنین ویژوال استودیو ابزار گرافیکی را نیز برای مدیریت بسته‌های bower فراهم می‌کند. شما می‌توانید با کلیک راست بر روی فایل bower.json و انتخاب Manage Bower packages به منوی باز شده دسترسی داشته باشید. ویژوال استادیو فایلهای bower.json را برای تغییرات نظارت می‌کند و به صورت خودکار از ابزار Bower برای دانلود و نصب بسته‌ها استفاده می‌کند. هنگامیکه شما تغییرات فایل را ذخیره می‌کنید، ویژوال استودیو بسته‌ی BootStrap را دانلود می‌کند و در پوشه‌ی wwwroot/lib ذخیره می‌کند.


مانند Nuget نیز Bower وابستگی‌های مرتبط با بسته‌های اضافه شده‌ی به یک پروژه را مدیریت می‌کند. BootStrap برای دسترسی به برخی از ویژگی‌های پیشرفته، به JQuery که یک کتابخانه‌ی جاوا اسکریپتی است، تکیه می‌کند. به همین دلیل است که دو بسته را در شکل فوق نشان داده است. شما می‌توانید لیست بسته‌ها و وابستگی‌های آنها را به صورت باز شده در بخش مورد نظر در Solution Explorer مشاهده کنید.

به روزرسانی بسته Bootstrap

در ادامه کتاب، من از نسخه قبلی Bootstrap CSS framework استفاده می‌کنم. هنگامی که دارم این را می‌نویسم، تیم Bootstrap در حال توسعه‌ی نسخه‌ی 4 bootStrap است و چندین بار منتشر شده‌است. این نسخه‌ها به عنوان "آلفا" برچسب گذاری شده‌اند، اما کیفیت آن‌ها بالا است و برای استفاده در نمونه‌های این کتاب به اندازه کافی پایدار است. با توجه به انتخاب نوشتن این کتاب با استفاده از Bootstrap 3 و نسخه پیش از نسخه بوت استرپ 4 و به زودی بایگانی شدن آن، تصمیم گرفتم از نسخه جدید استفاده کنم؛ حتی اگر برخی از نام‌های کلاس‌ها که برای شیوه نامه‌های عناصر HTML استفاده می‌شوند، احتمالا قبل از انتشار نهایی تغییر یابند. این مورد به این معنا است که شما باید همان نسخه از Bootstrap را که برای گرفتن نتایج موردنظر از خروجی نیاز دارید، استفاده  کنید.

برای به روزرسانی بسته Bootstrap، شماره نسخه را در فایل bower.json تغییر دهید. مانند کد زیر:
{
  "name": "asp.net",
  "private": true,
  "dependencies": {
    "bootstrap": "4.0.0-alpha.6"
  }
}
زمانی که شما تغییرات فایل bower.json را ذخیره می‌کنید، ویژوال استودیو نسخه جدید BootStrap را دانلود می‌کند.
معرفی توسعه و کامپایل مداوم

توسعه نرم افزار وب اغلب می‌تواند یک فرآیند تکراری باشد، جایی که تغییرات کوچکی را به ویووها یا کلاس‌ها می‌دهید و برنامه را اجرا می‌کنید تا اثرات آن را آزمایش کنید. MVC و ویژوال استودیو همکاری می‌کنند تا از این رویکرد مداوم استفاده کنند تا تغییرات را سریع‌تر و آسان‌تر ببینید.

 اعمال تغییرات در Razor Views  
در زمان توسعه، تغییراتی که به Razor View اعمال میشوند، به محض رسیدن درخواست‌های HTTP، از مرورگر دریافت میشوند. برای اینکه ببینید چطور کار می‌کند، برنامه را با انتخاب گزینه Start Debugging از منوی Debug اجرا کنید و هنگامیکه یک برگه‌ی مرورگر باز شد و اطلاعات نمایش داده شد، تغییراتی را که در زیر نمایش میدهم در فایل Index.cshtml اعمال کنید.

@model IEnumerable<WorkingWithVisualStudio.Models.Product>
@{
    Layout = null;
}

<!DOCTYPE html>

<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>>Working with Visual Studio</title>
</head>
<body>
    <h3>Products</h3>
    <table>
        <thead>
            <tr>
                <td>Name</td>
                <td>Price</td>
            </tr>
        </thead>
        <tbody>
            @foreach (var p in Model)
            {
            <tr>
                <td>@p.Name</td>
                <td>@($"{p.Price:C2}")</td>
            </tr>}
        </tbody>
    </table>
</body>
</html>
تغییرات را در فایل Index ذخیره می‌کنیم و صفحه وب را با استفاده از دکمه browser Reload مجددا بارگذاری می‌کنیم. تغییرات در View (یک عنوان و فرمت را برای مشخصه Price به عنوان ارز وارد کردیم) در مرورگر هم اعمال شده است؛ مانند تصویر زیر:



اعمال تغییرات در کلاس‌های #C

برای کلاس‌های #C، از جمله کنترلرها و مدل‌ها، دو رویکرد موجود را که از طریق آیتم‌های مختلف در منوی Debug انتخاب می‌شوند، شرح می‌دهم:

Start Without Debugging
تغییرات در کلاس‌ها در پروژه به صورت خودکار زمانیکه یک درخواست HTTP دریافت می‌شود، برای مشاهده‌ی یک تجربه‌ی توسعه‌ی پویا، کامپایل می‌شوند. در این حالت برنامه بدون امکانات دیباگ و اشکال‌زادیی اجرا می‌شود.

Start Debugging
به شما اجزا میدهد صریح تغییرات را کامپایل کنید و برنامه را اجرا کنید ، بررسی مشکلات هم در زمان اجرا پروژه انجام میگیرد.به شما اجرا بررسی و تجزیه و تحلیل هر گونه مشکل در کد را میدهد.

 
کامپایل خودکار کلاس ها

در طول توسعه عادی، این چرخه کامپایل سریع به شما اجازه می‌دهد تا فورا تاثیر تغییرات خود را ببینید؛ حالا می‌تواند این تغییر اضافه نمودن یک اکشن جدید و یا ویرایش نمایش اطلاعات یک Model باشد. برای ارائه‌ی این نوع از توسعه، ویژوال استودیو به محض رسیدن درخواست HTTP از مرورگر، تغییرات را دریافت و کلاس‌ها را به صورت خودکار کامپایل می‌کند. برای دیدن اینکه چگونه کار می‌کند، گزینه Start Without Debugging را از منوی Debug در ویژوال استودیو انتخاب کنید. هنگامیکه مرورگر داده‌های برنامه را نمایش می‌دهد، تغییرات زیر را در فایل Home controller ایجاد کنید:
namespace WorkingWithVisualStudio.Controllers
{
    public class HomeController : Controller
    {
        public IActionResult Index() => View(SimpleRepository.SharedRepository.Products
            .Where(p => p.Price < 50));
    }
}
در این تغییرات با استفاده از LINQ محصولات را فیلتر می‌کنیم به طوری که فقط کالاهایی را که price آنها کمتر از 50 است، نمایش داده می‌شوند. تغییرات را در فایل کلاس controller ذخیره کنید و پنجره مرورگر را دوباره باز کنید. بدون توقف و یا راه اندازی مجدد برنامه در ویژوال استادیو، درخواست HTTP از مرورگر باعث عملیات کامپایل میشود و برنامه با استفاده از تغییرات کلاس Controller، دوباره راه اندازی خواهد شد و نتیجه را در زیر میتوانید ببینیدکه محصولات Kayak را از جدول حذف می‌کند.

ویژگی کامپایل خودکار زمانی مفید است که همه چیز برنامه ریزی شود. مشکل این است که خطاهای کامپایلر، در زمان اجرا و در مرورگر بجای ویژوال استودیو نمایش داده می‌شوند. در این حالت زمانیکه یک مشکل وجود دارد، سخت می‌توان متوجه شد که چه مشکلی ایجاد شده است. برای مثال، کدهای زیر اضافه کردن یک مقدار Null را به مجموعه نمایش میدهد. 
namespace WorkingWithVisualStudio.Models
{
    public class SimpleRepository
    {
        private static SimpleRepository sharedRepository = new SimpleRepository();
        private Dictionary<string, Product> products = new Dictionary<string, Product>();
        public static SimpleRepository SharedRepository => sharedRepository;

        public SimpleRepository()
        {
            var initialItems = new[] { new Product { Name = "Kayak", Price = 275M }, new Product { Name = "Lifejacket", Price = 48.95M }, new Product { Name = "Soccer ball", Price = 19.50M }, new Product { Name = "Corner flag", Price = 34.95M } };
            foreach (var p in initialItems)
            {
                AddProduct(p);
            }
            products.Add("Error", null);
        }
        public IEnumerable<Product> Products => products.Values;
        public void AddProduct(Product p) => products.Add(p.Name, p);

    }
}
مشکلی مانند ورودی Null تا زمانیکه برنامه اجرا نشود، نمایش داده نمیشود. بارگذاری صفحه مرورگر باعث می‌شود کلاس SimpleRepository به صورت خودکار کامپایل شود و برنامه دوباره راه اندازی خواهد شد. هنگامیکه MVC نمونه‌ای از کلاس Controller را برای پردازش درخواست HTTP از مرورگر ایجاد می‌کند، سازنده HomeController کلاس SimpleRepository را ایجاد خواهد کرد که به نوبه خود سعی می‌کند مقدار Null اضافه شده در لیست را پردازش کند. مقدار Null باعث بروز یک مشکل می‌شود، اما مشخص نیست مشکل چیست. مرورگر یک پیام مفید را نمایش نمی‌دهد.
توانایی نمایش صفحات خطاها  
زمانیکه مشکلی در پنجره‌ی مرورگر ایجاد شد، می‌توان یک راهنمای با اطلاعات مفید را نمایش داد. این قابلیت را می‌توانید با فعال کردن نمایش صفحات انجام داد که باید در تنظیمات کلاس Startup تغییرات زیر را اعمال کنید.

namespace WorkingWithVisualStudio
{
    public class Startup
    {
        // This method gets called by the runtime. Use this method to add services to the container.
        // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc();
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
                app.UseDeveloperExceptionPage();
            }
        }
    }
}
اگر پنجره مرورگر را دوباره بارگذاری کنید، فرآیند کامپایل خودکار به صورت خودکار برنامه را بازسازی می‌کند و یک پیام خطای مفید‌تری را در مرورگر ایجاد می‌کند. مانند تصویر زیر:

پیام خطایی که توسط مرورگر نشان داده می‌شود، می‌تواند برای کشف مشکلات ساده، کافی باشد. اما برای مشکلات پیچیده‌تر و برای مشکلاتی که بلافاصله آشکار نمی‌شوند، ویژوال استودیو برای پیگیری خطا مورد نیاز است.

 
استفاده از Debugger

ویژوال استادیو از اجرای یک برنامه MVC با استفاده از Debugger نیز پشتیبانی می‌کند که اجازه می‌دهد برنامه برای بررسی وضعیت نرم افزار و دنبال کردن درخواستی که به برنامه ارسال میشود، متوقف و از این طریق، پیگیری شود. این مورد نیاز به یک سبک متفاوت از توسعه را دارد. زیرا تغییراتی را در کلاس‌های #C میدهیم، تا زمانیکه برنامه مجددا راه اندازی نشود، اعمال نمی‌شوند ( هرچند تغییرات Razor View هنوز هم به صورت خودکار اعمال میشوند). این سبک توسعه به همراه استفاده‌ی از ویژگی کامپایل خودکار نیست؛ اما Debugger ویژوال استودیو عالی است و می‌تواند بینش عمیق‌تری را در مورد نحوه‌ی کارکرد برنامه داشته باشد. برای اجرای برنامه با استفاده Debugger، در ویژوال استودیو از منوی Debug گزینه‌ی Start Debugging را انتخاب کنید. ویژوال استودیو کلاسهای #C در پروژه را قبل از اجرای برنامه کامپایل می‌کند. اما شما همچنان می‌توانید با استفاده از موارد موجود در منوی Build، کد خود را به صورت دستی نیز کامپایل کنید.

مثال فوق حاوی مقدار NULL است که سبب می‌شود یک NullReferenceException توسط کلاس SimpleRepository پرتاب شود. این حالت برنامه را قطع و کنترل اجرا را به توسعه دهنده منتقل می‌کند؛ همانطور که در شکل زیر نشان داده شده است



نکته: اگر Debugger خطا را نفهمد، گزینه‌ی Windows ➤ exception settings را از منوی Debugger ویژوال استودیو انتخاب کنید و اطمینان حاصل کنید که تمام انواع خطاهای در لیست خطاهای زمان اجرای زبان مشترک، تایید شده‌است.
تنظیم یک Break-point

Debugger عامل اصلی خطا را نمایش نمی‌دهد؛ تنها مکان آن‌را آشکار می‌کند. عبارتیکه ویژوال استودیو برجسته می‌کند نشان می‌دهد که این مشکل زمانی رخ می‌دهد که فیلتر کردن اشیاء با استفاده از LINQ انجام شود، اما یک کار کوچک لازم است تا از جزئیات کاسته شود و به علت اصلی برسد.
Breakpoint عبارتی است که به Debugger می‌گوید تا برنامه را متوقف کند و کنترل دستی برنامه را به برنامه نویس میدهد. شما می‌توانید وضعیت برنامه را بازبینی کنید و ببینید چه اتفاقی می‌افتد و به صورت اختیاری روند کاری را دوباره ادامه دهید.
برای ایجاد Breakpoint، روی عبارت راست کلیک کنید و در منوی باز شده، گزینه Breakpoint -> Insert Breakpoint را انتخاب کنید.

به عنوان مثال: یک Breakpoint به خط کد AddProduct در کلاس SimpleRepository اعمال کنید. همانطور که در شکل زیر نمایش داده میشود:
 


برنامه را اجرا کنید؛ با استفاده از Debug -> Start Debugging و یا با استفاده از Debug -> Restart برنامه را Restart می‌کنیم. در طی درخواست اولیه HTTP، برنامه اجرا میشود تا به نقطه‌ای که Break Point دارد برسد و در آنجا برنامه متوقف میشود. در این نقطه، شما می‌توانید از آیتم‌های منوی Debug ویژوال استودیو یا کنترل‌ها در بالای پنجره، برای کنترل اجرای برنامه استفاده کنید؛ یا از نمایش‌های مختلف Debugger موجود از طریق Debug -> Windows برای بررسی وضعیت برنامه استفاده می‌کنیم.
مشاهده مقادیر داده در ویرایشگر کد
رایج‌ترین استفاده Break Point، ردیابی مشکلات در کد شماست. قبل از اینکه بتوانید یک مشکل را رفع کنید، باید بدانید چه اتفاقی در حال رخ دادن است و یکی از ویژگیهای مفید ویژوال استودیو این است که توانایی مشاهده و کنترل ارزش متغیرها را درست در ویرایشگر کد، میدهد.
اگر اشاره‌گر ماوس را بر روی پارامتر p به متد AddProduct که توسط Debugger برجسته شده‌است، حرکت دهید، یک فرم ظاهر خواهد شد که ارزش فعلی p را نشان می‌دهد؛ همانطور که در شکل زیر نشان داده شده‌است. من یک نمونه بزرگ شده از محتویات فرم ظاهر شده را نمایش میدهم تا به راحتی بتوانید متن در آن را بخوانید.
 


این مورد ممکن است مؤثر به نظر نرسد، چون شیء داده در یک سازنده همانند BreakPoint تعریف شده‌است. اما این ویژگی‌ها برای هر متغیری کار می‌کند. شما می‌توانید مقادیر را مشاهده کنید تا مقادیر خود و فیلد آنها را ببینید. هر مقدار دارای یک دکمه پین​​ کوچک به سمت راست است. برای زمانیکه کد در حال اجراست، برای نظارت بر مقدار، از آن استفاده کنید.
اشاره‌گر ماوس را بر روی متغیر P قرار دهید و مرجع محصول را پین کنید. مرجع پیوست شده را باز کنید تا بتوانید نام و قیمت را نیز ببینید؛ مانند شکل زیر:
 


گزینه Continue را از منوی Debug در ویژوال استادیو انتخاب کنید تا برنامه ادامه پیدا کند. از آنجا که در برنامه حلقه Foreach وجود دارد، برنامه که دوباره اجرا میشود، وقتی مجددا به BreakPoint رسید، برنامه متوقف میشود. مقادیر پین شده در شکل زیر نشان میدهند که چگونه متغیر P و خواص آن تغییر می‌کنند.
 


استفاده از پنجره متغیرهای محلی ( Local Windows )

یکی از ویژگی‌های مرتبط، پنجره Locals است که با انتخاب گزینه‌ی منوی Debug ➤ Windows ➤ Locals باز می‌شود. پنجره‌ی Locals، مقدار متغیرها را به شکلی مشابه پنل پین شده نمایش می‌دهد، اما در اینجا تمام اشیاء محلی را نسبت به Break Point نمایش می‌دهد؛ همانطور که در شکل زیر نشان داده شده‌است:
 


هربار که Continue را انتخاب می‌کنید، اجرای برنامه ادامه یافته و یک شیء دیگر توسط حلقه foreach پردازش می‌شود.
اگر ادامه دهید، در زمان ویرایش کد، در هر دو پنجره Locals و در مقادیر پنل پین شده، شما مرجع Null را می‌بینید. برای کنترل اجرای برنامه، می‌توانید جریان را از طریق کد خود در دیباگر دنبال کنید و احساس کنید که چه اتفاقی می‌افتد.

برای غیرفعال کردن BreakPoint، روی  عبارت راست کلیک کنید و از منوی باز شده گزینه Delete BreakPoint را انتخاب کنید. برنامه را دوباره راه اندازی کنید و جدول داده ساده‌ای را که در شکل نشان داده شده، مشاهده خواهید کرد.


 
استفاده از Browser Link

ویژگی Browser Link می‌تواند روند توسعه را با قرار دادن یک یا چند مرورگر تحت کنترل ویژوال استودیو، ساده سازی کند. این ویژگی مخصوصا مفید است اگر شما نیاز به دیدن اثر تغییرات را در طیف وسیعی از مرورگرها دارید. قابلیت Browser Link با و یا بدون Debugger کار می‌کند و به این معنا است که می‌توانیم هر فایلی را در پروژه تغییر دهیم و تاثیر تغییر را بدون نیاز به تغییری در مرورگر مشاهده کنیم.

 
راه اندازی BrowserLink

برای فعال کردن Browser Link باید در کلاس Startup، تنظیمات را تغییر دهید. مانند کد زیر:

namespace WorkingWithVisualStudio
{
    public class Startup
    {
        // This method gets called by the runtime. Use this method to add services to the container.
        // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc();
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
                app.UseBrowserLink();
                app.UseDeveloperExceptionPage();
            }

            app.Run(async (context) =>
            {
                await context.Response.WriteAsync("Hello World!");
            });
        }
    }
}


استفاده از Browser Link

برای درک اینکه Browser Link چگونه کار می‌کند، در ویژوال استودیو گزینه Start Without Debugging را از منوی Debug انتخاب می‌کنیم. ویژوال استودیو برنامه را اجرا می‌کند و یک برگه جدید مرورگر را برای نمایش نتیجه باز می‌کند. با بازبینی HTML ارسال شده به مرورگر، شما خواهید دید که حاوی بخش دیگری مانند این است:
 

<!DOCTYPE html>

<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>>Working with Visual Studio</title>
</head>
<body>
    <h3>Products</h3>
    <table>
        <thead>
            <tr><td>Name</td><td>Price</td></tr>
        </thead>
        <tbody>
            <tr><td>Lifejacket</td><td>&#xA3;48.95</td></tr>
            <tr><td>Soccer ball</td><td>&#xA3;19.50</td></tr>
            <tr><td>Corner flag</td><td>&#xA3;34.95</td></tr>
        </tbody>
    </table>
    <!-- Visual Studio Browser Link -->
    <script type="application/json" id="__browserLink_initializationData">
        {"requestId":"968949d8affc47c4a9c6326de21dfa03","requestMappingFromServer":false}
    </script>
    <script type="text/javascript" src="http://localhost:55356/d1a038413c804e178ef009a3be07b262/browserLink" async="async"></script> <!-- End Browser Link -->
</body>
</html>
نکته: اگر قسمت اضافی را نمی‌بینید، لینک مرورگر را از منوی نشان داده شده‌ی در شکل زیر فعال کنید و مرورگر را دوباره بارگذاری کنید.


ویژوال استادیو یک جفت عناصر اسکریپت را به HTML فرستاده شده‌ی به مرورگر اضافه می‌کند که برای بازکردن یک اتصال طولانی مدت HTTP با سرور برنامه کاربردی است؛ تا زمانیکه ویژوال استودیو مجددا برنامه را ری‌استارت کند. کد زیر تغییر در فایل Index و تاثیر استفاده از Browser Link را نشان میدهد.
 

@model IEnumerable<WorkingWithVisualStudio.Models.Product>
@{
    Layout = null;
}

<!DOCTYPE html>

<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>>Working with Visual Studio</title>
</head>
<body>
    <h3>Products</h3>
    <p>Request Time: @DateTime.Now.ToString("HH:mm:ss")</p>
    <table>
        <thead>
            <tr>
                <td>Name</td>
                <td>Price</td>
            </tr>
        </thead>
        <tbody>
            @foreach (var p in Model)
            {
                <tr>
                    <td>@p.Name</td>
                    <td>@($"{p.Price:C2}")</td>
                </tr>}
        </tbody>
    </table>
</body>
</html>
تغییر در فایل View را ذخیره کنید و Refresh Linked Browsers را از منوی Browser Link در نوار ابزار ویژوال استودیو انتخاب کنید؛ همانطور که در شکل نشان داده شده است.  اگر Browser Link کار نمی‌کند، بارگیری مجدد مرورگر یا راه اندازی مجدد ویژوال استادیو را امتحان کنید).
 


کد جاوا اسکریپتی که در HTML ارسال شده به مرورگر جاسازی شده، صحفه را دوباره بارگذاری می‌کند؛ برای دیدن تاثیرات کد اضافه شده که اضافه کردن  یک timestamp ساده است.
 
نکته: عناصر اسکریپت Browser Link فقط در پاسخ‌های موفق جاسازی شده است. به این معنا که اگر یک خطا هنگام کامپایل در هنگام اجرا کردن یک Razor View یا مدیریت یک درخواست ایجاد شود، اتصال بین مرورگر و ویژوال استودیو از بین میرود و شما بعد از حل مشکل باید صفحه را مجدد بارگذاری کنید.

 
استفاده از مرورگرهای متعدد

Browser Link می‌تواند برای نمایش یک برنامه در مرورگرهای متعددی به طور همزمان استفاده شود و می‌تواند زمانی مفید باشد که شما می‌خواهید تفاوت‌های پیاده سازی را بین مرورگرهای مختلف کنترل کنید و یا ببینید که چگونه یک برنامه بر روی ترکیبی از مرورگرهای دسکتاپ و تلفن همراه ارائه می‌شود.
برای انتخاب مرورگرهایی که استفاده می‌شوند، مرورگر را با استفاده از دکمه IIS Express در نوار ابزار ویژوال استودیو، انتخاب کنید؛ همانطور که در شکل زیر نشان داده شده است.
 


ویژوال استودیو لیستی از مرورگرهایی را که در مورد آنها اطلاعاتی دارد، نمایش میدهد. در عکس زیر مرورگرهایی را که من در سیستم خود نصب کرده‌ام، نشان می‌دهد. برخی از آنها با ویندوز مانند Internet Explorer و Edge نصب می‌شوند.

 
ویژوال استادیو معمولا مرورگرهای رایجی را که نصب میشوند، نمایش میدهد. اما شما می‌توانید با استفاده از دکمه‌ی Add، برای اضافه کردن مرورگری که به صورت خودکار لیست نشده نیز استفاده کنید. همچنین می‌توانید ابزار تست شخص ثالث مانند Browser Stack را نیز راه اندازی کنید که مرورگرها را بر روی سرویس‌های ابری میزبان ( cloud-hosted ) و ماشین‌های مجازی اجرا می‌کند.

من سه مرورگر را در شکل انتخاب کردم: Chrome ، Internet Explorer و Edge. با کلیک بر روی دکمه Browse، فعالیت هر سه مرورگر شروع می‌شود و باعث می‌شود URL مثال برنامه را بارگذاری کند؛ همانطور که در شکل نشان داده شده است.
 


با استفاده از منوی Browser Link Dashboard، شما می‌توانید ببینید که چه مرورگرهایی در Browser Link انتخاب شده‌اند. داشبورد آن نشانی اینترنتی نمایش داده شده توسط هر مرورگر را نشان می‌دهد و در اینجا هر مرورگر را می‌توان به صورت جداگانه رفرش کرد.
 


آماده سازی جاوا اسکریپت و CSS برای استقرار

هنگامی که Client-Side بخشی از یک برنامه وب را ایجاد می‌کنید، معمولا تعدادی از فایل‌های جاوا اسکریپت و CSS سفارشی را تهیه می‌کنید که برای تکمیل آن‌ها، از بسته‌های نصب شده‌ی توسط Bower استفاده می‌شود. این فایل‌ها نیاز به پردازش دارند تا آنها را برای تحویل در یک محیط تولید، بهینه سازی کنند تا تعداد درخواستهای HTTP و میزان پهنای باند شبکه مورد نیاز برای ارسال آنها به مشتری، به حداقل برسد. این فرآیند به عنوان بسته بندی شناخته می‌شود.
 

فعال کردن تحویل محتوای استاتیک

ASP.Net Core شامل پشتیبانی از ارائه فایل‌های استاتیک از پوشه wwwroot به مشتریان است. اما این امکان به صورت پیشفرض در زمان ایجاد یک پروژه‌ی خالی جدید فعال نیست و شما باید با قرار دادن عبارتی در فایل StartUp آن را فعال کنید؛ مانند کد زیر:
 

namespace WorkingWithVisualStudio
{
    public class Startup
    {
        // This method gets called by the runtime. Use this method to add services to the container.
        // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc();
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
                app.UseBrowserLink();
                app.UseStaticFiles();
                app.UseDeveloperExceptionPage();
            }

            app.Run(async (context) =>
            {
                await context.Response.WriteAsync("Hello World!");
            });
        }
    }
}


اضافه کردن محتوای استاتیک به پروژه

برای نشان دادن فرآیند بسته بندی، من نیاز به اضافه کردن تعدادی محتوای استاتیک به پروژه و یکی کردن آن‌ها با برنامه‌ی نمونه را دارم. برای این منظور ابتدا یک پوشه‌ی جدید را به نام wwwroot/css ایجاد کنید که محل متداولی برای فایل‌های سفارشی CSS است. من فایلی را به نام First.css با استفاده از قالب آیتم Style Sheet اضافه کردم؛ همانطور که در شکل زیر نشان داده شده است. قالب Style Sheet در مسیر Asp.Net Core -> Web -> Content Section وجود دارد.
 


فایل First.Css را ویرایش کنید و محتوای زیر را در آن قرار دهید.
h3 {
}

table, td {
    border: 2px solid black;
    border-collapse: collapse;
    padding: 5px;
}
من این روند را تکرار کردم و یک فایل دیگر را نیز به نام second.css در پوشه wwwroot/css ایجاد کردم.

فایل‌های جاوا اسکریپت معمولا در پوشه wwwroot/js قرار میگیرند. من این پوشه را ایجاد کردم. فایل‌های جاوا اسکریپت را می‌توانید در مسیر Asp.Net Core -> Web -> Script انتخاب کنید. همانطور که در شکل زیر نشان داده شده است.


من کد جاوا اسکریپتی ساده زیر را به این فایل جدید اضافه کردم؛ همانطور که در لیست نشان داده شده است.
document.addEventListener("DOMContentLoaded", function ()
{
    var element = document.createElement("p");
    element.textContent = "This is the element from the third.js file";
    document.querySelector("body").appendChild(element);
});

من به بیش از یک فایل جاوا اسکریپت نیاز دارم. بنابراین فایل دیگری را به نام fourth.js نیز در پوشه wwwroot ایجاد می‌کنم و محتوای زیر را در آن قرار میدهم.
document.addEventListener("DOMContentLoaded", function ()
{
    var element = document.createElement("p");
    element.textContent = "This is the element from the fourth.js file";
    document.querySelector("body").appendChild(element);
});


به روز رسانی View

گام نهایی، به روز رسانی فایل Index.cshtml برای استفاده از Css و فایل جاوا اسکریپت است. کد‌های آن در زیر نشان داده شده است:
@model IEnumerable<WorkingWithVisualStudio.Models.Product>
@{
    Layout = null;
}

<!DOCTYPE html>

<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>>Working with Visual Studio</title>
    <link rel="stylesheet" href="css/first.css" />
    <link rel="stylesheet" href="css/second.css" />
    <script src="js/third.js"></script>
    <script src="js/fourth.js"></script>
</head>
<body>
    <h3>Products</h3>
    <p>Request Time: @DateTime.Now.ToString("HH:mm:ss")</p>
    <table>
        <thead>
            <tr>
                <td>Name</td>
                <td>Price</td>
            </tr>
        </thead>
        <tbody>
            @foreach (var p in Model)
            {
                <tr>
                    <td>@p.Name</td>
                    <td>@($"{p.Price:C2}")</td>
                </tr>}
        </tbody>
    </table>
</body>
</html>
اگر برنامه کاربردی را اجرا کنید، محتویات نشان داده شده‌ی در شکل زیر را مشاهده خواهید کرد. محتوای موجود توسط شیوه نامه‌های CSS شبیه سازی شده است و کد جاوا اسکریپتی جدیدی را اضافه کرده است.


یکی کردن فایل‌های سمت کلاینت در برنامه‌های MVC

در حال حاضر چهار فایل استاتیک وجود دارند و مرورگر باید چهار درخواست را برای دریافت فایل‌های استاتیک ایجاد کند و هر یک از این فایل‌ها نیازمند پهنای باند بیشتری است که باید به مشتری تحویل داده شود؛ زیرا آنها حاوی فضای سفید و نام متغیرها هستند که برای توسعه دهنده‌ها معنا دار هستند؛ اما برای مرورگرها اهمیتی ندارند.
ترکیب فایل‌هایی هم نوع، تلفیق نامیده می‌شود و در آن کار ساختن فایل‌ها به صورتی کوچکتر انجام می‌شود. هر دوی این کارها در برنامه Asp.Net Core MVC توسط  Bundler & Minifier مخصوص ویژوال استودیو انجام میشود.


نصب افزونه‌های ویژوال استودیو

اولین قدم برای نصب افزونه، انتخاب از منوی Tools -> Extensions and Update و کلیک بر روی مجموعه Online است تا افزونه‌های ویژوال استودیو را در مجموعه نمایش بدهد. نام افزونه را در جعبه جستجوی در گوشه‌ی سمت راست بالای پنجره وارد کنید؛ همانطور که در شکل زیر نشان داده شده است. محل نصب افزونه را مشخص می‌کنیم و بر روی دانلود کلیک می‌کنیم تا آن را به ویژوال استودیو اضافه کند. ویژوال استودیو را مجدد راه اندازی کنید تا فرآیند نصب تکمیل شود.


دسته بندی و یکی کردن فایل‌ها

پس از نصب افزونه، ویژوال استودیو را مجددا راه اندازی کنید و پروژه نمونه را باز کنید. با افزودن افزونه، می‌توانید چندین فایل هم نوع را در Solution Explorer انتخاب کنید. آنها را با یکدیگر ترکیب کرده و محتویات آنها را کوچکتر کنید. به عنوان مثال فایل‌های First.css و Second.css را در Solution Explorer را انتخاب و کلیک راست کرده و سپس Bundler & Minifier -> Bundle and Minify Files را از منوی باز شده انتخاب کنید . همانطور که در شکل زیر نشان داده شده است.
 


فایل خروجی را با عنوان bundle.css ذخیره کنید. در Solution Explorer یک بسته جدید ایجاد میشود. اگر شما این فایل را باز کنید، خواهید دید که محتویات هر دو فایل CSS جداگانه ترکیب شده‌اند و تمام فضای سفید آن‌ها حذف شده‌است. البته شما نمی‌خواهید به طور مستقیم با این فایل کار کنید؛ اما این فایل کوچکتر است و فقط یک اتصال HTTP را برای ارائه CSS styles به مشتری نیاز دارد.

مراحل قبل را برای فایل‌های third.js و fourth.js تکرار کنید تا فایل‌های جدید bundle.js و bundle.min.js در پوشه wwwroot ایجاد شوند.

احتیاط: اطمینان حاصل کنید که فایل‌ها را به ترتیبی که توسط مرورگر بارگیری می‌شوند، انتخاب کنید تا ترتیب دستورات Style‌ها یا دستورات کد را در فایل‌های خروجی حفظ کنید. به عنوان مثال دقت کنید که فایل third.js قبل از فایل fourth.js انتخاب شده باشد تا مطمئن باشید دستورات به ترتیب و به درستی اجرا می‌شوند.
کد زیر، عناصر پیوند فایل‌های جداگانه‌ای را که باید در فایل Index.cshtml قرار گیرند، نمایش میدهد:
@model IEnumerable<WorkingWithVisualStudio.Models.Product>
@{
    Layout = null;
}

<!DOCTYPE html>

<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>>Working with Visual Studio</title>
    <link rel="stylesheet" href="css/bundle.min.css" />
    <script src="js/bundle.min.js"></script>
</head>
<body>
    <h3>Products</h3>
    <p>Request Time: @DateTime.Now.ToString("HH:mm:ss")</p>
    <table>
        <thead>
            <tr>
                <td>Name</td>
                <td>Price</td>
            </tr>
        </thead>
        <tbody>
            @foreach (var p in Model)
            {
                <tr>
                    <td>@p.Name</td>
                    <td>@($"{p.Price:C2}")</td>
                </tr>}
        </tbody>
    </table>
</body>
</html>
اگر برنامه را اجرا کنید، هیچ تغییر بصری وجود نخواهد داشت؛ اما فایلهای آن یکی شده‌اند و با حجم کمتر و با تعداد اتصالات کمتری از سرور دریافت می‌شوند.

همان زمان که عملیات جمع آوری و یکی کردن را انجام می‌دهید، رکورد عملیات انجام شده را در فایلی به نام bundleconfig.json در پوشه‌ی wwwroot پروژه نگهداری می‌کند. در اینجا یک نمونه از فایل تولیدی را مشاهده می‌کنید:
[
  {
    "outputFileName": "Views/wwwroot/css/bundle.css",
    "inputFiles": [
      "Views/wwwroot/css/First.css",
      "Views/wwwroot/css/second.css"
    ]
  },
  {
    "outputFileName": "Views/wwwroot/js/bundle.js",
    "inputFiles": [
      "Views/wwwroot/js/fourth.js",
      "Views/wwwroot/js/third.js"
    ]
  }
]
 

خلاصه
در این بخش من توضیحاتی را در مورد ویژگی‌هایی که ویژوال استودیو برای طراحی برنامه‌های وب به توسعه دهنده‌ها ارائه میدهد، شرح دادم که شامل کامپایل خودکار کلاس‌ها، Browser Link و یکی کردن فایل‌های سمت کلاینت ( bundling and minification ) بود. 
مطالب
طراحی ValidationAttribute دلخواه و هماهنگ سازی آن با ASP.NET MVC
در سری پست‌های آقای مهندس یوسف نژاد با عنوان Globalization در ASP.NET MVC روشی برای پیاده سازی کار با Resource‌ها در ASP.NET با استفاده از دیتابیس شرح داده شده است. یکی از کمبودهایی که در این روش وجود داشت عدم استفاده از این نوع Resourceها از طریق Attributeها در ASP.NET MVC بود. برای استفاده از این روش در یک پروژه به این مشکل برخورد کردم و پس از تحقیق و بررسی چند پست سرانجام در این پست  پاسخ خود را پیدا کرده و با ترکیب این روش با روش آقای یوسف نژاد موفق به پیاده سازی Attribute دلخواه شدم.
در این پست و با استفاده از سری پست‌های آقای مهندس یوسف نژاد  در این زمینه، یک Attribute جهت هماهنگ سازی با سیستم اعتبار سنجی ASP.NET MVC در سمت سرور و سمت کلاینت (با استفاده از jQuery Validation) بررسی خواهد شد.

قبل از شروع مطالعه سری پست‌های MVC و Entity Framework الزامی است.

برای انجام این کار ابتدا مدل زیر را در برنامه خود ایجاد می‌کنیم.

using System;

public class SampleModel
{
public DateTime StartDate { get; set; }
public string Data { get; set; }
public int Id { get; set; }
با استفاده از این مدل در برنامه در زمان ثبت داده‌ها هیچ گونه خطایی صادر نمی‌شود. برای اینکه بتوان از سیستم خطای پیش فرض ASP.NET MVC کمک گرفت می‌توان مدل را به صورت زیر تغییر داد.
using System;
using System.ComponentModel.DataAnnotations;

public class SampleModel
{
    [Required(ErrorMessage = "Start date is required")]
    public DateTime StartDate { get; set; }

    [Required(ErrorMessage = "Data is required")]
    public string Data { get; set; }

    public int Id { get; set; }
}
حال ویو این مدل را طراحی می‌کنیم.
@model SampleModel
@{
    ViewBag.Title = "Index";
    Layout = "~/Views/Shared/_Layout.cshtml";
}

<section>
    <header>
        <h3>SampleModel</h3>
    </header>
    @Html.ValidationSummary(true, null, new { @class = "alert alert-error alert-block" })

    @using (Html.BeginForm("SaveData", "Sample", FormMethod.Post))
    {
        <p>
            @Html.LabelFor(x => x.StartDate)
            @Html.TextBoxFor(x => x.StartDate)
            @Html.ValidationMessageFor(x => x.StartDate)
        </p>
        <p>
            @Html.LabelFor(x => x.Data)
            @Html.TextBoxFor(x => x.Data)
            @Html.ValidationMessageFor(x => x.Data)
        </p>
        <input type="submit" value="Save"/>
    }
</section>
و بخش کنترلر آن را به صورت زیر پیاده سازی می‌کنیم.
 public class SampleController : Controller
    {
        //
        // GET: /Sample/

        public ActionResult Index()
        {
            return View();
        }

        public ActionResult SaveData(SampleModel item)
        {
            if (ModelState.IsValid)
            {
                //save data
            }
            else
            {
                ModelState.AddModelError("","لطفا خطاهای زیر را برطرف نمایید");
                RedirectToAction("Index", item);
            }
            return View("Index");
        }
    }
حال با اجرای این کد و زدن دکمه Save صفحه مانند شکل پایین خطاها را نمایش خواهد داد.

تا اینجای کار روال عادی همیشگی است. اما برای طراحی Attribute دلخواه جهت اعتبار سنجی (مثلا برای مجبور کردن کاربر به وارد کردن یک فیلد با پیام دلخواه و زبان دلخواه) باید یک کلاس جدید تعریف کرده و از کلاس RequiredAttribute ارث ببرد. در پارامتر ورودی این کلاس جهت کار با Resource‌های ثابت در نظر گرفته شده است اما برای اینکه فیلد دلخواه را از دیتابیس بخواند این روش جوابگو نیست. برای انجام آن باید کلاس RequiredAttribute بازنویسی شود.

کلاس طراحی شده باید به صورت زیر باشد:

public class VegaRequiredAttribute : RequiredAttribute, IClientValidatable
    {
#region Fields (2) 

        private readonly string _resourceId;
        private String _resourceString = String.Empty;

#endregion Fields 

#region Constructors (1) 

        public VegaRequiredAttribute(string resourceId)
        {
            _resourceId = resourceId;
            ErrorMessage = _resourceId;
            AllowEmptyStrings = true;
        }

#endregion Constructors 

#region Properties (1) 

        public new String ErrorMessage
        {
            get { return _resourceString; }
            set { _resourceString = GetMessageFromResource(value); }
        }

#endregion Properties 

#region Methods (2) 

// Public Methods (1) 

        public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
        {

            yield return new ModelClientValidationRule
            {
                ErrorMessage = GetMessageFromResource(_resourceId),
                ValidationType = "required"
            };
        }
// Private Methods (1) 

        private string GetMessageFromResource(string resourceId)
        {
            var errorMessage = HttpContext.GetGlobalResourceObject(_resourceId, "Yes") as string;
            return errorMessage ?? ErrorMessage;
        }

#endregion Methods 
    }
در این کلاس دو نکته وجود دارد.
1- ابتدا دستور
HttpContext.GetGlobalResourceObject(_resourceId, "Yes") as string;  

که عنوان کلید Resource را از سازنده کلاس گرفته (کد اقای یوسف نژاد) رشته معادل آن را از دیتابیس بازیابی میکند.

2- ارث بری از اینترفیس IClientValidatable، در صورتی که از این اینترفیس ارث بری نداشته باشیم. Validator طراحی شده در طرف کلاینت کار نمی‌کند. بلکه کاربر با کلیک بروی دکمه مورد نظر داده‌ها را به سمت سرور ارسال می‌کند. در صورت وجود خطا در پست بک خطا نمایش داده خواهد شد. اما با ارث بری از این اینترفیس و پیاده سازی متد GetClientValidationRules می‌توان تعریف کرد که در طرف کلاینت با استفاده از Unobtrusive jQuery  پیام خطای مورد نظر به کنترل ورودی مورد نظر (مانند تکست باکس) اعمال می‌شود. مثلا در این مثال خصوصیت data-val-required به input هایی که قبلا در مدل ما Reqired تعریف شده اند اعمال می‌شود.

حال در مدل تعریف شده می‌توان به جای Required می‌توان از VegaRequiredAttribute مانند زیر استفاده کرد. (همراه با نام کلید مورد نظر در دیتابیس)

public class SampleModel
{
    [VegaRequired("RequiredMessage")]
    public DateTime StartDate { get; set; }

    [VegaRequired("RequiredMessage")]
    public string Data { get; set; }

    public int Id { get; set; }
}
ورودی Validator مورد نظر نام کلیدی است به زبان دلخواه که عنوان آن RequiredMessage تعریف شده است و مقدار آن در دیتابیس مقداری مانند "تکمیل این فیلد الزامی است" است. با این کار در زمان اجرا با استفاده از این ولیدتور ابتدا کلید مورد نظر با توجه به زبان فعلی از دیتابیس بازیابی شده و در متادیتابی مدل ما قرار می‌گیرد. به جای استفاده از Resource‌ها می‌توان پیام‌های خطای دلخواه را در دیتابیس ذخیره کرد و در مواقع ضروری جهت جلوگیری از تکرار از آنها استفاده نمود.
با اجرای برنامه اینبار خروجی به شکل زیر خواهد بود.

جهت فعال ساری اعتبار سنجی سمت کلاینت ابتدا باید اسکریپت‌های زیر به صفحه اضافه شود.
<script src="@Url.Content("~/Scripts/jquery-1.9.1.min.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script>
سپس در فایل web.config تنظیمات زیر باید اضافه شود
<appSettings>
    <add key="ClientValidationEnabled" value="true" />
    <add key="UnobtrusiveJavaScriptEnabled" value="true"/>
</appSettings>
سپس برای اعمال Validator طراحی شده باید توسط کدهای جاوا اسکریپت زیر داده‌های مورد نیاز سمت کلاینت رجیستر شوند.
<script type="text/javascript">
        jQuery.validator.addMethod('required', function (value, element, params) {
            if (value == null | value == "") {
                return false;
            } else {
                return true;
            }

        }, '');

        jQuery.validator.unobtrusive.adapters.add('required', {}, function (options) {
            options.rules['required'] = true;
            options.messages['required'] = options.message;
        });
    </script>
البته برای مثال ما قسمت بالا به صورت پیش فرض رجیستر شده است اما در صورتی که بخواهید یک ولیدتور دلخواه و غیر استاندارد بنویسید روال کار به همین شکل است.
موفق و موید باشید.
منابع ^ و ^ و ^ و ^
بازخوردهای دوره
تزریق خودکار وابستگی‌ها در برنامه‌های ASP.NET MVC
- اگر مطلب را مطالعه کرده باشید، نحوه صحیح استفاده از Controller factory به این صورت است:
        if (controllerType == null)
            throw new InvalidOperationException(string.Format("Page not found: {0}", requestContext.HttpContext.Request.Url.AbsoluteUri.ToString(CultureInfo.InvariantCulture)));
به عمد این استثناء صادر می‌شود تا مشخص شود اگر controllerType نال است، حتما مسیری بوده که در سیستم وجود ندارد و این مورد برای خطایابی در دراز مدت حائز اهمیت است. به صورت خودکار هم توسط ELMAH لاگ می‌شود و برای بررسی‌های بعدی مورد استفاده خواهد بود.
- اگر اینکار رو انجام بدید خواهید دید که مسیر ذکر شده توسط شما به صورت یافت نشد اعلام می‌گردد. چرا؟
public override void RegisterArea(AreaRegistrationContext context)
        {
            context.MapRoute(
                "Admin_default",
                "Admin/{controller}/{action}/{id}",
                new { action = "Index", id = UrlParameter.Optional }
            );
        }
چون در مسیریابی پیش فرض یک Area نام کنترلر پیش فرض ذکر نشده (در فایل AdminAreaRegistration.cs). نام action هست اما نام controller نیست. به همین جهت یا باید این مسیریابی رو اصلاح کنید و یا اینکه مسیر کامل را به نحو زیر وارد نمائید:
http://localhost:2215/admin/home
نظرات مطالب
معرفی ELMAH
با سلام ودرود خدمت آقای نصیری:
elmah معرفی شده یکی از پرکاربردترین و دوست داشتنی ترین مواردی که توسط استاد بنده (آقای مهدی کرامتی) معرفی (ارجاع به وبلاگ شما) و از اینجا مورد استفاده قرار گرفت.
به نظر در پیاده سازی و عملکرد elmah در پروژه هیچ مشکلی وجود ندارد ...
اما مشکلی در مورد ارسال ایمیل وجود دارد که خدمتتان عرض می کنم.
در حالت local‌ایمیل به زیبایی هر چه تمام تر ارسال می شود ...
اما پس از آپلود سایت در ارسال ایمیل مشکل وجود دارد !
به نظر شما چگونه باید این مشکل را حل نمایم ؟
این مشکل به سرور مربوط می شود یا به web.config

من قسمت نظرات رو هر روز بررسی می کنم تا بلکه پاسخی از طرف شما در یافت نمایم و یا اینکه پاسخ را بدون زحمت به آدرس زیر ایمیل بزنید:
majid.darab@gmail.com

با تشکر فراوان از وب لاگ تخصصی حضرتعالی
مطالب
چه زمان‌هایی یک برنامه‌ی ASP.NET ری استارت می‌شود؟

برای ری استارت کردن یک برنامه‌ی ASP.NET حتما نیازی نیست تا IIS را متوقف و سپس راه اندازی کرد یا تنظیمات App pool برنامه را در IIS تغییر داد. روش‌های دیگری نیز وجود دارند که عدم آگاهی از آن‌ها می‌تواند سبب بروز مشکلات عدیده‌ای گردد و گاها خطایابی آن‌ها بسیار مشکل است؛ زیرا ری استارت شدن برنامه = از دست رفتن آنی تمام سشن‌های InProc تمام کاربران سایت؛ پاک شدن کش برنامه در IIS؛ از دست رفتن تمام متغیرهای استاتیک، Application State و مواردی از این دست:
  • - نوشتن در پوشه bin برنامه (ایجاد فایل یا ایجاد پوشه یا تغییر نام پوشه‌ها و مواردی از این دست). (بنابراین قرار دادن بانک اطلاعاتی برنامه در این پوشه، اشتباه بوده و به همین جهت پوشه‌ی استاندارد App_Data طراحی شده است)
  • - نوشتن در فایل web.config برنامه (به صورت دستی، حتی در حد اضافه کردن یک فاصله یا توسط خود برنامه و یا استفاده از متد File.SetLastWriteTime). در این حالت ASP.NET FileSystemWatcher بلافاصله وارد عمل شده و برنامه را ری استارت می‌کند. (هدف اصلی وجودی این فایل، برخورد فقط خواندنی با آن است نه ذخیره سازی تنظیمات پویای برنامه در آن. برای ذخیره سازی تنظیمات پویا، از بانک اطلاعاتی و یا یک فایل XML مجزای از وب کانفیگ استفاده کنید یا مواردی مشابه)
  • - هر گونه تغییری در فایل‌ها و یا پوشه‌های App_WebReferences ، App_Code ، Global.asax ، machine.config ، App_GlobalResources و App_LocalResources نیز سبب ری استارت برنامه خواهند شد.
  • - با ایجاد، حذف یا تغییر نام یکی از ساب دایرکتوری‌های واقع شده در ریشه برنامه. بنابراین اگر برنامه‌ی شما به صورت پویا پوشه‌هایی را ایجاد یا حذف می‌کند باید منتظر ری استارت‌های پی در پی باشید (البته این مورد با از کار انداختن FileChangesMonitor مربوط به HttpRuntime قابل حل می‌باشد (+)، ولی همانطور که عنوان شد به صورت پیش فرض همواره فعال است)
  • - فراخوانی متد System.Web.HttpRuntime.UnloadAppDomain شبیه به همان Application.Exit در برنامه‌های دسکتاپ است و بلافاصله سبب خاتمه‌ی برنامه می‌شود. قرار دادن فایل App_Offline.htm در پوشه اصلی برنامه نیز چنین رفتاری را سبب خواهد شد. علاوه بر آن تگ httpRuntime در وب کانفیگ نیز دارای گزینه‌ی enable است و تنظیم آن به false ، سبب خاتمه‌ی سرویس دهی برنامه خواهد شد.
  • - رسیدن به عدد numRecompilesBeforeAppRestart تعریف شده در فایل machine.config که عموما به عدد 15 تنظیم شده است. اگر تغییرات زیادی را در فایل‌های (مرتبط با ASP.NET مانند aspx ، asmx و غیره) برنامه داده باشید (بیشتر از 15 مورد) و نیازی به ری کامپایل اساسی وجود داشته باشد، ASP.NET FileSystemWatcher به صورت خودکار برنامه را ری استارت خواهد کرد.

مطالب
React 16x - قسمت 32 - React Hooks - بخش 3 - نکات ویژه‌ی برقراری ارتباط با سرور
در قسمت‌های 22 تا 25 این سری، روش برقراری ارتباط با سرور را در برنامه‌های React، توسط کتابخانه‌ی معروف Axios، بررسی کردیم. در این قسمت می‌خواهیم همان نکات را زمانیکه قرار است از کامپوننت‌های تابعی، به همراه useState hook و useEffect hook استفاده کنیم، مرور نمائیم.


برپایی پیش‌نیازها

در اینجا نیز از همان برنامه‌ای که در قسمت 30، برای بررسی مثال‌های React hooks ایجاد کردیم، استفاده خواهیم کرد. فقط در آن، کتابخانه‌ی Axios را نیز نصب می‌کنید. به همین جهت در ریشه‌ی پروژه‌ی React این قسمت، دستور زیر را در خط فرمان صادر کنید:
> npm install --save axios
برنامه‌ی backend مورد استفاده هم همان برنامه‌ای است که از قسمت 22 شروع به توسعه‌ی آن کردیم و کدهای کامل آن‌را از پیوست‌های انتهای بحث، می‌توانید دریافت کنید. این برنامه که در مسیر شروع شده‌ی با https://localhost:5001/api قرار می‌گیرد، جهت پشتیبانی از افعال مختلف HTTP مانند Get/Post/Delete/Update طراحی شده‌است. برای راه اندازی آن، به پوشه‌ی این برنامه، مراجعه کرده و فایل dotnet_run.bat آن‌را اجرا کنید، تا endpointهای REST Api آن قابل دسترسی شوند. برای مثال باید بتوان به مسیر https://localhost:5001/api/posts آن در مرورگر دسترسی یافت.
در ادامه می‌خواهیم در برنامه‌ی React خود، لیست مطالب برنامه‌ی backend را از سرور دریافت کرده و نمایش دهیم. همچنین یک search box را به همراه دکمه‌های search و clear نیز به آن اضافه کنیم.


دریافت اطلاعات اولیه از سرور، درون useEffect Hook

پس از نصب پیش‌نیازها و راه اندازی برنامه‌ی backend، در ابتدا فایل src\config.json را جهت درج مشخصات آدرس REST Api آن، ایجاد می‌کنیم:
{
   "apiUrl": "https://localhost:5001/api"
}
سپس فایل جدید src\components\part03\Search.jsx را جهت توسعه‌ی کامپوننت جستجوی این قسمت ایجاد می‌کنیم و ساختار ابتدایی آن‌را با import وابستگی‌های React و مسیر فوق، به صورت یک function که در همان محل قابل export است، ایجاد می‌کنیم، تا فعلا یک React.Fragment را بازگشت دهد:
import React from "react";
import {apiUrl} from "../../config.json";

export default function App() {
  return <></>;
}
بر این اساس، کامپوننت App فایل index.js را به صورت زیر از کامپوننت App فوق، تامین خواهیم کرد:
import App from "./components/part03/Search";
اکنون می‌خواهیم اولین درخواست خود را به سمت backend server ارسال کنیم. برای این منظور در کامپوننت‌های تابعی، از useEffect Hook استفاده می‌شود؛ چون کار با یک API خارجی نیز یک side effect محسوب می‌گردد. بنابراین متد useEffect را import کرده و سپس آن‌را بالای return، فراخوانی می‌کنیم. درون آن نیاز است اطلاعات را از سرور دریافت کنیم و برای اینکار از کتابخانه‌ی axios که آن‌را در قسمت 23 معرفی کردیم، استفاده خواهیم کرد. به همین جهت import آن‌را نیز در این ماژول خواهیم داشت:
import axios from "axios";
import React, { useEffect, useState } from "react";

import { apiUrl } from "../../config.json";

export default function App() {
  useEffect(() => {
    axios
      .get(apiUrl + "/posts/search?query=")
      .then(response => console.log(response.data));
  });
  return <></>;
}
در اینجا با استفاده از متد get کتابخانه‌ی axios، درخواستی را به آدرس https://localhost:5001/api/posts/search، با یک کوئری استرینگ خالی، ارسال کرده‌ایم تا تمام داده‌ها را بازگشت دهد. روش قدیمی استفاده‌ی از axios را که با استفاده از Promiseها و متد then آن است، در اینجا ملاحظه می‌کنید که خروجی خاصیت data شیء response دریافتی را لاگ کرده‌است:


اکنون می‌خواهیم این اطلاعات دریافتی را در برنامه‌ی خود نیز نمایش دهیم. به همین جهت نیاز است تا response.data را درون state کامپوننت جاری قرار داده و در حین رندر کامپوننت، با تشکیل حلقه‌ای بر روی آن، اطلاعات نهایی را نمایش دهیم. بنابراین نیاز به useState Hook خواهیم داشت که ابتدا آن‌را import کرده و سپس آن‌را تعریف و در قسمت then، فراخوانی می‌کنیم:
import axios from "axios";
import React, { useEffect, useState } from "react";

import { apiUrl } from "../../config.json";

export default function App() {
  const [results, setResults] = useState([]);

  useEffect(() => {
    axios.get(apiUrl + "/posts/search?query=").then(response => {
      console.log(response.data);
      setResults(response.data);
    });
  });
چون اطلاعات بازگشتی به صورت یک آرایه‌است، مقدار اولیه‌ی متد useState را با یک آرایه‌ی خالی مقدار دهی کرده‌ایم. سپس برای مقدار دهی متغیر results موجود در state، به متد setResults تعریف شده‌ی توسط useState، مقدار response.data را ارسال می‌کنیم. در این حالت اگر برنامه را ذخیره کرده و اجرا کنید .... برنامه و همچنین مرورگر، هنگ می‌کنند!


همانطور که مشاهده می‌کنید، یک حلقه‌ی بی پایان در اینجا رخ داده‌است! برای پایان آن، مجبور خواهیم شد ابتدا کنسول اجرایی برنامه‌ی React را به صورت دستی خاتمه داده و سپس مرورگر را نیز refresh کنیم تا این حلقه، خاتمه پیدا کند.
علت این مشکل را در قسمت 30 بررسی کردیم؛ effect method تابع useEffect (همان متد در برگیرنده‌ی قطعه کدهای axios.get در اینجا)، پس از هربار رندر کامپوننت، یکبار دیگر نیز اجرا می‌شود. یعنی این متد، هر دو حالت componentDidMount و componentDidUpdate کامپوننت‌های کلاسی را با هم پوشش می‌دهد و چون در اینجا setState را با فراخوانی متد setResults داریم، یعنی درخواست رندر مجدد کامپوننت انجام شده‌است و پس از آن، مجددا effect method فراخوانی می‌شود و ... این حلقه هیچ‌گاه خاتمه نخواهد یافت. به همین جهت مرورگر و برنامه، هر دو با هم هنگ می‌کنند!

در این برنامه فعلا می‌خواهیم که فقط در حالت componentDidMount، کار درخواست اطلاعات از backend صورت گیرد. به همین جهت پارامتر دوم متد useEffect را با یک آرایه‌ی خالی مقدار دهی می‌کنیم:
  useEffect(() => {
   // ...
  }, []);
تا اینجا موفق شدیم متد setResults را تنها در اولین بار نمایش کامپوننت، فراخوانی کنیم که در نتیجه‌ی آن، متغیر results موجود در state، مقدار دهی شده و همچنین کار رندر مجدد کامپوننت در صف قرار می‌گیرد. بنابراین مرحله‌ی بعد، تکمیل قسمت return کامپوننت تابعی است تا آرایه‌ی results را نمایش دهد:
//...

export default function App() {
  // ...
  return (
    <>
      <table className="table">
        <thead>
          <tr>
            <th>Title</th>
          </tr>
        </thead>
        <tbody>
          {results.map(post => (
            <tr key={post.id}>
              <td>{post.title}</td>
            </tr>
          ))}
        </tbody>
      </table>
    </>
  );
}
در اینجا ابتدا یک فرگمنت را توسط </><> تعریف کرده‌ایم و سپس در داخل آن می‌توان المان‌های فرزند را قرار داد. سپس برای ایجاد trهای جدول، یک حلقه را توسط results.map، بر روی عناصر دریافتی از آرایه‌ی مطالب، تشکیل داده‌ایم. چون این حلقه بر روی trهای پویا تشکیل می‌شود، هر tr، نیاز به یک key دارد، تا در DOM مجازی React قابل شناسایی و ردیابی شود که در آخر یک چنین شکلی را ایجاد می‌کند:



استفاده ازAsync/Await  برای دریافت اطلاعات، درون یک  useEffect Hook

اکنون می‌خواهیم درون effect method یک useEffect Hook، روش قدیمی استفاده‌ی از callbackها و متد then را برای دریافت اطلاعات، با روش جدیدتر async/await که در قسمت 23 آن‌را بیشتر بررسی کردیم، جایگزین کنیم.
  useEffect(async () => {
    const { data } = await axios.get(apiUrl + "/posts/search?query=");
    console.log(data);
    setResults(data);
  }, []);
خروجی متد axios.get، یک شیء Promise است که نتیجه‌ی عملیات async را بازگشت می‌دهد. در جاوا اسکریپت مدرن، می‌توان از واژه‌ی کلیدی await برای دسترسی به شیء response دریافتی از آن، استفاده کرد. سپس هر جائیکه از واژه‌ی کلیدی await استفاده می‌شود، متد جاری را باید با واژه‌ی کلیدی async نیز مزین کرد. با انجام اینکار و اجرای برنامه، اخطار زیر در کنسول توسعه دهندگان مرورگر ظاهر می‌شود؛ هرچند نتیجه نهایی هم هنوز نمایش داده می‌شود:
Warning: An effect function must not return anything besides a function, which is used for clean-up.
It looks like you wrote useEffect(async () => ...) or returned a Promise.
این اخطار به این معنا است که effect function تعریف شده را نمی‌توان به صورت async تعریف کرد و از چنین قابلیتی پشتیبانی نمی‌شود. یک effect function حداکثر می‌تواند یک متد دیگر را بازگشت دهد (و یا هیچ چیزی را بازگشت ندهد) که نمونه‌ی آن‌را در قسمت 30، با متدهایی که کار پاکسازی منابع را انجام می‌دادند، بررسی کردیم. اگر متدی را مزین به واژه‌ی کلیدی async کردیم، یعنی این متد در اصل یک Promise را بازگشت می‌دهد؛ اما یک effect function، حداکثر یک تابع دیگر را می‌تواند بازگشت دهد تا componentWillUnmount را پیاده سازی کند.

برای رفع این مشکل، روش توصیه شده، ایجاد یک تابع مجزای async و سپس فراخوانی آن درون effect function است:
  useEffect(() => {
    getResults();
  }, []);

  const getResults = async () => {
    const { data } = await axios.get(apiUrl + "/posts/search?query=");
    console.log(data);
    setResults(data);
  };
مشکل یا محدودیتی برای ایجاد متدهای async، در خارج از یک effect function وجود ندارد. به همین جهت اعمالی را که نیاز به Async/Await دارند، در این متدهای مجزا انجام داده و سپس می‌توان آن‌ها را درون effect function، به نحوی که ملاحظه می‌کنید، فراخوانی کرد. با این تغییر، هنوز هم اطلاعات نهایی، بدون مشکل دریافت می‌شوند، اما دیگر اخطاری در کنسول توسعه دهندگان مرورگر درج نخواهد شد.


پیاده سازی componentDidUpdate با یک useEffect Hook، جهت انجام جستجوهای پویا

تا اینجا با اضافه کردن پارامتر دومی به متد useEffect، رویداد componentDidUpdate آن‌را از کار انداختیم، تا برنامه با هربار فراخوانی setState و اجرای مجدد effect function، در یک حلقه‌ی بی‌نهایت وارد نشود. اکنون این سؤال مطرح می‌شود که اگر یک textbox را برای جستجوی در عناوین نمایش داده شده، در بالای جدول آن قرار دهیم، نیاز است با هربار تغییر ورودی آن، کار فراخوانی مجدد effect function صورت گیرد، تا بتوان نتایج جدیدتری را از سرور دریافت و به کاربر نشان داد؛ این مشکل را چگونه باید حل کرد؟
برای دریافت عبارت وارد شده‌ی توسط کاربر و جستجو بر اساس آن، ابتدا متغیر state و متد تنظیم آن‌را با استفاده از useState Hook و یک مقدار اولیه‌ی دلخواه تنظیم می‌کنیم:
export default function App() {
  // ...
  const [query, setQuery] = useState("Title");
سپس المان textbox زیر را هم به بالای المان جدول، اضافه می‌کنیم:
<input
  type="text"
  name="query"
  className="form-control my-3"
  placeholder="Search..."
  onChange={event => setQuery(event.target.value)}
  value={query}
/>
این کنترل توسط رویداد onChange، عبارت تایپ شده را به متد setQuery ارسال کرده و در نتیجه‌ی آن، کار تنظیم متغیر query در state کامپوننت جاری، صورت می‌گیرد. همچنین با تنظیم value={query}، سبب خواهیم شد تا این کنترل، به یک المان کنترل شده‌ی توسط state تبدیل شود و در ابتدای نمایش فرم، مقدار ابتدایی useState را نمایش دهد.
اکنون که متغیر query دارای مقدار شده‌است، می‌توان از آن در متد axios.get، به نحو زیر و با ارسال یک کوئری استرینگ به سمت سرور، استفاده کرد:
const { data } = await axios.get(
   `${apiUrl}/posts/search?query=${encodeURIComponent(query)}`
);
استفاده از تابع encodeURIComponent، سبب می‌شود تا اگر کاربر برای مثال "Text 1" را وارد کرد، فاصله‌ی بین دو عبارت، به درستی encode شده و یک کوئری مانند https://localhost:5001/api/posts/search?query=Title%201 به سمت سرور ارسال گردد.

تا اینجا اگر برنامه را ذخیره کرده و اجرا کنید، با تایپ در textbox جستجو، تغییری در نتایج حاصل نمی‌شود؛ چون effect function تعریف شده که سبب اجرای مجدد axios.get می‌شود، طوری تنظیم شده‌است که فقط یکبار، آن‌هم پس از رندر اولیه‌ی کامپوننت، اجرا شود. برای رفع این مشکل، با مقدار دهی آرایه‌ای که به عنوان پارامتر دوم متد useEffect تعریف شده، می‌توان اجرای مجدد effect function آن‌را وابسته‌ی به تغییرات متغیر query در state کامپوننت کرد:
  useEffect(() => {
    getResults();
  }, [query]);
اکنون اگر برنامه را ذخیره کرده و اجرا کنید، با هربار ورود اطلاعات درون textbox جستجو، یک کوئری جدید به سمت سرور ارسال شده و نتیجه‌ی جستجوی انجام شده، به صورت یک جدول رندر می‌شود:



دریافت اطلاعات جستجو، تنها با ارسال اطلاعات یک فرم به سمت سرور


تا اینجا کاربر با هر حرفی که درون textbox جستجو وارد می‌کند، یک کوئری، به سمت سرور ارسال خواهد شد. برای کاهش آن می‌توان یک دکمه‌ی جستجو را در کنار این textbox قرار داد تا تنها پس از کلیک بر روی آن، این جستجو صورت گیرد.
برای پیاده سازی این قابلیت، ابتدا وابستگی به query را از متد useEffect حذف می‌کنیم، تا دیگر با تغییر اطلاعات textbox، متد callback آن اجرا نشود (پارامتر دوم آن‌را مجددا به یک آرایه‌ی خالی تنظیم می‌کنیم). سپس یک دکمه را که از نوع button است و رویداد onClick آن به getResults اشاره می‌کند، در بالای جدول نتایج مطالب، قرار می‌دهیم:
<button
  className="btn btn-primary"
  type="button"
  onClick={getResults}
>
  Search
</button>
تا اینجا اگر کاربر اطلاعاتی را وارد کرده و سپس بر روی دکمه‌ی Search فوق کلیک کند، نتایج جستجوی خودش را در جدول ذیل آن مشاهده می‌کند. اکنون می‌خواهیم این امکان را به کاربران بدهیم که با فشردن دکمه‌ی enter درون textbox جستجو، همین قابلیت جستجو را در اختیار داشته باشند؛ تا دیگر الزامی به کلیک بر روی دکمه‌ی Search، نباشد. برای اینکار تنها کافی است، کل مجموعه‌ی textbox و دکمه را درون یک المان form قرار دهیم و نوع button را نیز به submit تغییر دهیم. سپس onClick دکمه را حذف کرده و بجای آن رویداد onSubmit فرم را پیاده سازی می‌کنیم:
<form onSubmit={handleSearch}>
  <div className="input-group my-3">
    <label htmlFor="query" className="form-control-label sr-only"></label>
    <input
type="text"
id="query"
name="query"
className="form-control"
placeholder="Search ..."
onChange={event => setQuery(event.target.value)}
value={query}
    />
    <div className="input-group-append">
<button className="btn btn-primary" type="submit">
  Search
</button>
    </div>
  </div>
</form>
در اینجا یک المان فرم، به همراه یک textbox و button از نوع submit تعریف شده‌اند. رویداد onSubmit نیز به متد منتسب به متغیر handleSearch، متصل شده‌است تا با فشردن دکمه‌ی enter توسط کاربر در این textbox، کار جستجوی مجدد، صورت گیرد:
  const handleSearch = event => {
    event.preventDefault();
    getResults();
  };
تا اینجا اگر برنامه را ذخیره کرده و "Text 1" را در textbox جستجو، وارد کرده و enter کنیم، همانند تصویر فوق، رکورد متناظری از سرور دریافت و نمایش داده می‌شود.


افزودن قابلیت پاک کردن textbox جستجو و معرفی useRef Hook

در ادامه می‌خواهیم یک دکمه‌ی جدید را در کنار دکمه‌ی Search، اضافه کنیم تا با کلیک کاربر بر روی آن، نه فقط محتوای وارد شده‌ی در textbox پاک شود، بلکه focus نیز به آن منتقل گردد. برای پاک کردن textbox، فقط کافی است متد setQuery را با یک رشته‌ی خالی ارسالی به آن فراخوانی کنیم. اما برای انتقال focus به textbox، نیاز به داشتن ارجاع مستقیمی به آن المان وجود دارد که با مفهوم آن در قسمت 18 آشنا شدیم: «برای دسترسی به یک المان DOM در React، باید یک reference را به آن نسبت داد. برای این منظور یک خاصیت جدید را در سطح کلاس کامپوننت ایجاد کرده و آن‌را با React.RefObject مقدار دهی اولیه کرده و سپس ویژگی ref المان مدنظر را به این RefObject تنظیم می‌کنیم». برای انجام یک چنین کاری در اینجا، Hook ویژه‌ای به نام useRef معرفی شده‌است. بنابراین برای پیاده سازی این نیازمند‌ی‌ها، ابتدا دکمه‌ی Clear را در کنار دکمه‌ی Search قرار می‌دهیم:
<button
  type="button"
  onClick={handleClearSearch}
  className="btn btn-info"
>
  Clear
</button>
سپس رویداد onClick آن‌را به متد منتسب به متغیر handleClearSearch، مرتبط می‌کنیم:
import React, { useEffect, useRef, useState } from "react";
// ...

export default function App() {
  // ...
  const searchInputRef = useRef();


  const handleClearSearch = () => {
    setQuery("");
    searchInputRef.current.focus();
  };
در اینجا ابتدا useRef را import کرده‌ایم، تا توسط آن بتوان یک متغیر از نوع React.MutableRefObject را ایجاد کرد. سپس در متد منتسب به handleClearSearch، ابتدا با فراخوانی setQuery، مقدار query را در state کامپوننت، پاک کرده و سپس به کمک این شیء Ref، دسترسی مستقیمی به شیء textbox یافته و متد focus آن‌را فراخوانی می‌کنیم (شیء current آن، معادل DOM Element متناظر است).
البته این searchInputRef برای اینکه دقیقا به textbox تعریف شده اشاره کند، باید آن‌را به ویژگی ref المان، انتساب داد:
<input
  type="text"
  id="query"
  name="query"
  className="form-control"
  placeholder="Search ..."
  onChange={event => setQuery(event.target.value)}
  value={query}
  ref={searchInputRef}
/>
تا اینجا اگر برنامه را ذخیره کرده و اجرا کنیم، با کلیک بر روی دکمه‌ی Clear، متن textbox جستجو حذف شده و سپس کرسر مجددا به همان textbox برای ورود اطلاعات، منتقل می‌شود.


نمایش «لطفا منتظر بمانید» در حین دریافت اطلاعات از سرور


البته در اینجا با هر بار کلیک بر روی دکمه‌ی جستجو، نتیجه‌ی نهایی به سرعت نمایش داده می‌شود؛ اما اگر سرعت اتصال کاربر کمتر باشد، با یک وقفه این امر رخ می‌دهد. به همین جهت بهتر است یک پیام «لطفا منتظر بمانید» را در این حین به او نمایش دهیم. به همین جهت در ابتدا state مرتبطی را به کامپوننت اضافه می‌کنیم:
const [loading, setLoading] = useState(false);
تا با فراخوانی متد setLoading آن بتوان سبب رندر مجدد UI شد و پیامی را نمایش داد و یا مخفی کرد:
  const getResults = async () => {
    setLoading(true);
    const { data } = await axios.get(
      `${apiUrl}/posts/search?query=${encodeURIComponent(query)}`
    );
    console.log(data);
    setResults(data);
    setLoading(false);
  };
متد setLoading در ابتدای متد منتسب به متغیر getResults، مقدار متغیر loading را در state به true تنظیم می‌کند و در پایان عملیات، به false. اکنون بر این اساس می‌توان UI متناظری را نمایش داد:
      {loading ? (
        <div className="alert alert-info">Loading results...</div>
      ) : (
        <table className="table">
          <thead>
            <tr>
              <th>Title</th>
            </tr>
          </thead>
          <tbody>
            {results.map(post => (
              <tr key={post.id}>
                <td>{post.title}</td>
              </tr>
            ))}
          </tbody>
        </table>
      )}
در اینجا با استفاده از یک ternary operator، اگر loading به true تنظیم شده باشد، یک div به همراه عبارت Loading results، نمایش داده می‌شود؛ در غیراینصورت، جدول اطلاعات مطالب، نمایش داده خواهد شد.

برای آزمایش آن می‌توان سرعت اتصال را در برگه‌ی شبکه‌ی ابزارهای توسعه دهندگان مرورگر، تغییر داد:



مدیریت خطاها در حین اعمال async

آخرین امکانی را که به این مطلب اضافه خواهیم کرد، مدیریت خطاهای اعمال async است که با try/catch صورت می‌گیرد:
// ...

export default function App() {
  // ...
  const [error, setError] = useState(null);

  // ...

  const getResults = async () => {
    setLoading(true);

    try {
      const { data } = await axios.get(
        `${apiUrl}/posts/search?query=${encodeURIComponent(query)}`
      );
      console.log(data);
      setResults(data);
    } catch (err) {
      setError(err);
    }

    setLoading(false);
  };
در حین فراخوانی await axios.get، اگر خطایی رخ دهد، این کتابخانه استثنایی را صادر خواهد کرد که می‌توان به جزئیات آن در بدنه‌ی catch نوشته شده دسترسی یافت و برای مثال آن‌را به کاربر نمایش داد. برای این منظور ابتدا state مخصوص آن‌را ایجاد می‌کنیم و سپس توسط فراخوانی متد setError آن، کار رندر مجدد کامپوننت را در صف انجام قرار خواهیم داد.در نهایت برای نمایش آن می‌توان یک div را به پایین جدول اضافه نمود:
{error && <div className="alert alert-warning">{error.message}</div>}
برای آزمایش آن، برنامه‌ی backend را که در حال اجرا است، خاتمه دهید و سپس در برنامه سعی کنید به آن متصل شوید:




کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید: sample-30-part-03-frontend.zip و sample-30-part-03-backend.zip