Logging is a big part of software development for many years now. One can argue that a logging mechanism is a must-have part of any application or library. I would agree with that statement. Logging has a crucial part to play in a scenario where you can’t use interactive debugging (that is, attaching a debugger like Visual Studio). It allows us to investigate errors after the problem already happened. In some cases, like Production Debugging, logs might be the only information you have.
اشتراکها
اشتراکها
Duende IdentityServer v5 منتشر شد
اشتراکها
1.3 TailWind CSS منتشر شد
- انجام عملی بعد از Resize شدن کامل window | mojtabasahraei.blogfa.com
- دسترسی قانونی و تقریباً رایگان به MSDN با استفاده از BizSpark | blog.afsharm.com
- نسخهٔ دوم صفحهکلید استاندارد فارسی ویندوز | persianlanguage.ir
- .NET 4.5 now includes the core AntiXSS functions | idunno.org
- .NET Gets a New Lease of Life | www.ciprianjichici.ro
- A few facts about Microsoft’s new Windows Runtime | www.itwriting.com
- Anders Hejlsberg Session on C#/VB Future Directions | www.cookcomputing.com
- BUILD keynote day 1–Metro experience with Jupiter, XAML and HTML5/JS | davidburela.wordpress.com
- BUILD keynote day 2–Windows Server 8 and developer tools | davidburela.wordpress.com
- Is the iTextSharp DLL free to use and redistribute with my web application project? | stackoverflow.com
- Microsoft Build: Developer topics to watch | www.zdnet.com
- Microsoft codename 'Redhawk' lives... in Windows 8 | www.zdnet.com
- Microsoft splits up its XAML team: What's the fallout? | www.zdnet.com
- More on Microsoft 'Jupiter' and what it means for Windows 8 | www.zdnet.com
- Paint.NET v3.5.9 Beta (build 4278) is now available | blog.getpaint.net
- Premature cries of Silverlight / WPF skill loss. Windows 8 supports all programming models | davidburela.wordpress.com
- RMS: 'Is Android Really Free Software?' | rss.slashdot.org
- Silverlight Developers Have the Smoothest Road to Metro | www.softinsight.com
- What’s new in WCF 4.5? Configuration tooltips and intellisense in config files | blogs.microsoft.co.il
The key highlights to cover this month include:
- Announcing SQL Server 2019 support
- New notebook features
- Announcing PowerShell notebooks
- Announcing collapsible code cells
- Performance improvements in notebooks
- Announcing Jupyter Books
- General availability of Schema Compare and SQL Server Dacpac extensions
- Announcing Visual Studio IntelliCode extension
- Bug fixes
اشتراکها
انتشار Unity 2018.1
The Visual Studio team is excited about the Unity 2018.1 release: It’s the start of a new release cycle packed with great new features like the Scriptable Render Pipeline and the C# Job System. You can read the full blog post by Unity for all the details on what’s new in the 2018.1 release.
Custom Elements، دارای یک چرخه حیات میباشند. در طی این چرخه حیات، میتوان تعدادی متد خاص را به المان سفارشی خود اضافه کرد که به صورت خودکار توسط مرورگر فراخوانی میشوند. به این متدها Life-cycle Callbacks یا Custom Element Reactions نیز میگویند. برای درک بهتر چرخه حیات مذکور، به تکه کد زیر توجه نمائید:
در این صورت، امکان استفاده از المان سفارشی، قبل از معرفی و ثبت آن توسط متد customElements.define نیز وجود خواهد داشت. یعنی اگر در DOM شما تعدادی المان سفارشی وجود داشته باشند که به هر دلیل نیاز است پس از گذشت یک بازه زمانی کوتاهی معرفی و ثبت شوند (مثال: lazy load اسکریپتهای متناظر با المانهای سفارشی در Angular)، این المانها معتبر هستند. فرآیند فراخوانی متد define و استفاده از کلاس معرفی شده برای ارتقاء المان سفارشی موجود در DOM، اصطلاحا Element Upgrades نامیده میشود. همچنین با استفاده از متد customElements.whenDefined که یک Promise را بازگشت میدهد، میتوان از معرفی و ثبت شدن المان خاصی آگاه شد:
یا حتی امکان استفاده از سلکتور «:defined» نیز به شکل زیر وجود دارد:
در اینجا ابتدا تمام المانهای تعریف نشده، کوئری شدهاند و با استفاده از متد map و اجرای متد whenDefined، به لیستی از Promiseها رسیدهایم و در نهایت با استفاده از Promise.all منتظر اتمام مرحله upgrade المانهای مذکور هستیم.
متد get، ارجاعی به سازنده کلاس x-component را بازگشت خواهد داد.
customElements.define("x-component", class extends HTMLElement { constructor() { super(); console.log('constructed!'); } connectedCallback() { console.log('connected!'); } disconnectedCallback() { console.log('disconnected!'); } adoptedCallback() { console.log('adopted!'); } attributeChangedCallback(name, oldValue, newValue) { console.log('attirbuteChanged!', name, oldValue, newValue); } static get observedAttributes() { return ['checked','demo','label']; } });
Element Upgrades
به صورت پیشفرض، المانهای موجود در DOM که مبتنیبر استانداردهای HTML تعریف نشدهاند، توسط مرورگر به عنوان HTMLUnknownElement تجزیه و تحلیل خواهند شد. ولی این موضوع برای المانهای سفارشی که نام معتبری دارند (وجود «-»)، صدق نمیکند. برای مثال دو خط کد زیر را در کنسول مربوط به Developer tools مرورگر خود اجرا کنید:
// "tabs" is not a valid custom element name document.createElement('tabs') instanceof HTMLUnknownElement === true //true // "x-tabs" is a valid custom element name document.createElement('x-tabs') instanceof HTMLElement === true //true
customElements.whenDefined('x-component').then(() => { console.log('x-component defined'); });
<share-buttons> <social-button type="twitter"><a href="...">Twitter</a></social-button> <social-button type="fb"><a href="...">Facebook</a></social-button> <social-button type="plus"><a href="...">G+</a></social-button> </share-buttons> // Fetch all the children of <share-buttons> that are not defined yet. let undefinedButtons = buttons.querySelectorAll(':not(:defined)'); let promises = [...undefinedButtons].map(socialButton => { return customElements.whenDefined(socialButton.localName); )); // Wait for all the social-buttons to be upgraded. Promise.all(promises).then(() => { // All social-button children are ready. });
سازنده مرتبط با کلاس المان سفارشی x-component، در هنگام وهلهسازی یا فرآیند upgrades فراخوانی میشود و میتواند برای مقداردهی اولیه خواص وهله جاری، تنظیم رخدادگردانها (Event Listeners) و یا ایجاد و اتصال ShadowDOM با استفاده از متد attachShadow، محل مناسبی باشد. طبق مستندات مرتبط، فراخوانی ()super بدون ارسال هیچ آرگومانی باید در اولین خط این سازنده انجام شود.
برای وهلهسازی المان سفارشی نیز میتوان از متد customeElements.get به شکل زیر استفاده کرد:
customeElements.define('x-component',...) let XComponent = customElements.get('x-component'); document.body.appendChild(new XComponent())
در تکه کد بالا، لیست المانهای img موجود در داخل iframe کوئری شده و سپس با پیمایش بر روی لیست بدست آمده و در زمان فراخوانی متد adoptNode که کار تغییر ownerDocument مرتبط با یک المان را انجام میدهد، متد adoptedCallback ما نیز اجرا خواهد شد.
مشخص است که امکان تعریف انواع و اقسام متدها با پارامترها و خروجیهای مختلفی و حتی نسخههای همزمان یا ناهمزمان آنها وجود دارد.
انتظار چنین خروجی داریم:
این موضوع، تحت عنوان «Reflecting Properties to Attributes» مطرح میباشد. در این صورت علاوه بر اینکه با یک نگاه به DOM، از مقادیر خصوصیات یک المان آگاه خواهیم بود، امکان استفاده از این صفات به عنوان سلکتورهایی در زمان استایلدهی نیز وجود دارد. حال از این مکانیزم برای اعمال یکسری صفات دسترسیپذیری مانند صفات ARIA به المان سفارشی خود نیز میتوان استفاده کرد. برای مثال:
یا حتی به شکل زیر:
در اینجا از همان getter که طبق پیادهسازی ما، در پشت صحنه از همان مقدار صفت disabled استفاده میکند، برای تنظیم یکسری صفات دیگر استفاده کردهایم. به عنوان مثال اگر المان ما غیرفعال شده بود، صفت tabindex آن را با «-1» مقداردهی میکنیم تا از توالی پیمایش مبتنیبر Tab خارج شود.
در این صورت اگر لود اسکریپت، معرفی و ثبت این المان سفارشی به صورت Lazy انجام شود، امکان آن وجود دارد که فریمورک، عملیات binding را قبل از مرحله upgrades انجام دهد. خوب... این موضوع چه مشکلی را ایجاد میکند؟ در این صورت چون مرحله upgrades تمام نشده است، پیادهسازی بدنه متد setter متناظر با خصوصیات المان سفارشی، توسط پراپرتی جدیدی که توسط فریمورک برروی وهله موجود تعریف میشود، بیاستفاده خواهد ماند. برای مثال، اگر سعی کنیم قبل از مرحله upgrades خصوصیت disabled المان x-component را مقداردهی کنیم، عملیات مکانیزم همگامسازی مدنظر ما اجرا نخواهد شد:
با این خروجی مواجه خواهیم شد که هیچ اثری از صفت disabled دیده نمیشود:
connectedCallback
اولین متد بعد از فراخوانی سازنده، connectedCallback نام دارد و زمانی رخ میدهد که وهلهای از یک المان سفارشی به Light DOM افزوده شدهاست و یا توسط Parser مرورگر، در DOM شناسایی شود. این متد، محل پیشنهاد شده برای اجرای کدهای زمان راهاندازی مانند دریافت منابع از سرور و یا رندر کردن محتوایی خاص، میباشد.
نکته: این متد ممکن است بیش از یکبار نیز فراخوانی شود! هنگامیکه یک المان موجود در DOM از طریق کد از DOM جداشده و سپس اضافه شود:
const el = document.createElement('x-component'); document.body.appendChild(el); // connectedCallback() called el.remove(); // disconnectedCallback() document.body.appendChild(el); // connectedCallback() called again
disconnectedCallback
این متد نیز هر وقت المانی از DOM حذف شود، اجرا خواهد شد و مانند متد connectedCallback ممکن است چندین بار فراخوانی شود. همچنین محل مناسبی برای آزادسازی منابع استفاده شده در پیادهسازی المان سفارشی، میباشد. مانند: استفاده از متد clearInterval برای پاکسازی یک تایمر که با متد setInterval ایجاد شدهاست.
نکته: هیچ تعهدی به اجرای متد disconnectedCallback در تمام حالاتی که یک المان از DOM حذف میشود، وجود ندارد. به عنوان مثال هنگامیکه یک برگهی مرورگر بسته شود، این متد فراخوانی نخواهد شد.
attributeChangedCallback
این متد هنگامی فراخوانی خواهد شد که خصوصیات مشخص شده از طریق observedAttributes به عنوان یک static getter، اضافه، حذف، ویرایش و یا جایگزین شوند. همچنین در مرحله Upgrades برای مقادیر اولیه خصوصیات که توسط استفاده کننده از المان سفارشی، مشخص شدهاست نیز فراخوانی میشود.
adoptedCallback
متدهای قبلی بیشترین استفاده را دارند؛ این متد خاص نیز زمانیکه یک المان سفارشی به یک DOM دیگری منتقل میشود، اجرا خواهد شد. برای مثال:
const iframe = document.querySelector('iframe'); const iframeImages = iframe.contentDocument.querySelectorAll('img'); const newParent = document.getElementById('images'); iframeImages.forEach(function(imgEl) { newParent.appendChild(document.adoptNode(imgEl)); });
Methods
اگر المانهای بومی و استاندارد موجود را بررسی کنید، همه آنها دارای یکسری متد، پراپرتی و صفات مشخصی هستند. در اینجا نیز تعریف رفتاری برای یک المان سفارشی و کپسوله، نکته خاصی ندارد و به شکل زیر قابل تعریف و استفاده میباشد:
class XComponent extends HTMLElement { constructor() { super(); } doSomething(){ console.log('doSomething'); } }
let component = document.querySelector('x-component'); component.doSomething();
Attributes & Properties
در HTML خیلی رایج است که مقادیر پراپرتیهای یک المان در قالب یکسری صفات، نمودی در DOM هم داشته باشند. برای مثال:
div.id = 'id-value'; div.hidden = true;
<div id="id-value" hidden>
class XComponent extends HTMLElement { constructor() { super(); } connectedCallback() { this._render(); } get disabled() { return this.hasAttribute('disabled'); } set disabled(val) { // Reflect the value of `disabled` as an attribute. if (val) { this.setAttribute('disabled', ''); } else { this.removeAttribute('disabled'); } this._render(); } _render() { //... } }
ایده کار خیلی ساده است؛ پراپرتیهای یک المانسفارشی را از طریق متدهای getter و setter تعریف کرده و در بدنه پیادهسازی آنها، صفات HTML ای المان جاری را تغییر داده و یا از مقادیر آنها استفاده کنیم.
اینبار با مقداردهی پراپرتی disabled برروی وهلهای از المان سفارشی ما، این مقادیر نمودی در DOM هم خواهند داشت. با استفاده از متدهای setAttribute یا removeAttribute کار همگامسازی پراپرتیها با صفات را انجام دادهایم.همچین با استفاده از متد attributeChangedCallback نیز میتوان برای اعمال صفات ARIA که اشاره شد، به شکل زیر استفاده کرد:
attributeChangedCallback(name, oldValue, newValue) { switch (name) { case 'checked': // Note the attributeChangedCallback is only handling the *side effects* // of setting the attribute. this.setAttribute('aria-disabled', !!newValue); break; ... }
attributeChangedCallback(name, oldValue, newValue) { // When the component is disabled, update keyboard/screen reader behavior. if (this.disabled) { this.setAttribute('tabindex', '-1'); this.setAttribute('aria-disabled', 'true'); } else { this.setAttribute('tabindex', '0'); this.setAttribute('aria-disabled', 'false'); } // TODO: also react to the other attribute changing. }
نکته: پیشنهاد میشود از مکانیزم همگامسازی پراپرتیها و صفات، برای انواع دادهای اولیه (رشته، عدد و ...) استفاده شود و برای دریافت مقادیری مانند objects و یا arrays، از متدها یا پراپرتیها استفاده کنید. در غیر این صورت نیاز خواهد بود که این مقادیر را سریالایز و در داخل المان سفارشی، عملیات معکوس آن را انجام دهید که میتواند هزینهی زیادی داشته باشد. عملیات سریالایز نیز خود باعث از دست دادن ارجاعات به آن مقادیر خواهد شد. به صورت کلی هیچکدام از المانهای بومی موجود، چنین اطلاعاتی را دریافت نمیکند. برای مثال:
constructor() { super(); this._data = []; } get data() { return _data; } set data(value) { if (this_data === value) return; this._data = value; this._render(); }
Lazy Properties
همانطور که اشاره شد حتی قبل از مرحله upgrades مربوط به المانهای سفارشی استفاده شده در سند HTML برنامه شما، به عنوان المانهای معتبری هستند که امکان کوئری کردن و مقداردهی اولیه خصوصیات آنها از طریق کد نیز ممکن است. این موضوع زمانیکه از فریمورکی مثل Angular استفاده میشود، المان موردنظر به صورت خودکار توسط فریمورک لود و به صفحه اضافه شده و در انتهای عملیات، binding پراپرتیهای آن به خصوصیات موجود در کامپوننت Angular ای انجام خواهد شد. به مثال زیر توجه کنید:
<x-component [disabled]="model.disabled"></x-component>
let el = document.querySelector('x-component'); el.disabled = true; customElements.define("x-component", class extends HTMLElement { constructor() { super(); } get disabled() { return this.hasAttribute('disabled'); } set disabled(val) { // Reflect the value of `disabled` as an attribute. if (val) { this.setAttribute('disabled', ''); } else { this.removeAttribute('disabled'); } this._render(); } });
یکی از روشهای پیشنهاد شده برای حل این مشکل، مقداردهی مجدد پراپرتیها بعد از مرحله upgrades و پس از اینکه متد setter تعریف شدهاست، میباشد:
let el = document.querySelector('x-component'); el.disabled = true; customElements.define("x-component", class extends HTMLElement { constructor() { super(); } connectedCallback() { this._upgradeProp('disabled'); } get disabled() { return this.hasAttribute('disabled'); } set disabled(val) { // Reflect the value of `disabled` as an attribute. if (val) { this.setAttribute('disabled', ''); } else { this.removeAttribute('disabled'); } this._render(); } _upgradeProp(prop) { if (this.hasOwnProperty(prop)) { let value = this[prop]; delete this[prop]; //delete instance property this[prop] = value; // set prototype property } } });
ایده کار به این صورت است که مقدار پراپرتی مورد نظر را که قبل از مرحله upgrades برروی وهله جاری (instance property) تنظیم شدهاست، در متغییری نگهداری کرده و آن پراپرتی را حذف و سپس پراپرتی تعریف شده در کلاس (prototype property) را برای وهله جاری مقداردهی کنیم.
نکته: به یاد داشته باشید که قبل از اینکه یکسری صفات خاص را مقدار دهی کنید، بررسی شود که استفاده کننده از المان سفارشی، مقداری را تنظیم نکرده باشد. برای مثال اگر قصد دارید المان سفارشی شما قابلیت focus را داشته باشد، نیاز است شما حداقل tabindex=-1 را تنظیم کنید؛ حتی اگر استفاده کننده، آن را مقداردهی نکرده باشد:
connectedCallback() { if (!this.hasAttribute('role')) this.setAttribute('role', 'checkbox'); if (!this.hasAttribute('tabindex')) this.setAttribute('tabindex', -1); //element is not reachable via sequential keyboard navigation, but could be focused }
در مطلب «فرمهای مبتنی بر قالبها در Angular - قسمت چهارم - اعتبارسنجی ورودیها» با نحوهی تنظیمات اعتبارسنجی سمت کلاینت برنامههای Angular آشنا شدیم. اما اگر مدل سمت سرور ما یک چنین شکلی را داشته باشد که به همراه خطاهای اعتبارسنجی سفارشی نیز هست:
و همچنین کنترلر و اکشن متد دریافت کنندهی آن نیز به صورت ذیل تعریف شده باشد:
دو نوع خطای اعتبارسنجی سمت سرور را به سمت کلاینت ارسال خواهیم کرد:
الف) خطاهای اعتبارسنجی در سطح فیلدها
زمانیکه return BadRequest(ModelState) صورت میگیرد، محتویات شیء ModelState به همراه status code مساوی 400 به سمت کلاینت ارسال خواهد شد. در شیء ModelState یک دیکشنری که کلیدهای آن، نام خواص و مقادیر متناظر با آنها، خطاهای اعتبارسنجی تنظیم شدهی در مدل است، قرار دارند.
ب) خطاهای اعتبارسنجی عمومی
در این بین میتوان دیکشنری ModelState را توسط متد AddModelError نیز تغییر داد و برای مثال کلید آنرا مساوی "" تعریف کرد. در این حالت یک چنین خطایی به کل فرم اشاره میکند و نه به یک خاصیت خاص.
نمونهای از خروجی نهایی ارسالی به سمت کاربر:
به همین جهت نیاز است بتوان خطاهای حالت (الف) را دقیقا در ذیل هر فیلد و خطاهای حالت (ب) را در بالای فرم به صورت عمومی به کاربر نمایش داد:
پردازش و دریافت خطاهای اعتبارسنجی سمت سرور در یک برنامهی Angular
با توجه به اینکه سرور، شیء ModelState را توسط return BadRequest به سمت کلاینت ارسال میکند، برای پردازش دیکشنری دریافتی از سمت آن، تنها کافی است قسمت بروز خطای عملیات ارسال اطلاعات را بررسی کنیم:
در این HttpErrorResponse دریافتی، دو خاصیت error که همان آرایهی دیکشنری نام خواص و پیامهای خطای مرتبط با هر کدام و status code دریافتی مهم هستند:
توضیحات:
در اینجا از آرایهی errors برای نمایش خطاهای عمومی در سطح فرم استفاده میکنیم. این خطاها در ModelState، دارای کلید مساوی "" هستند. به همین جهت حلقهای را بر روی شیء responseError.error تشکیل میدهیم. به این ترتیب میتوان به نام خواص و همچنین خطاهای متناظر با آنها رسید.
از نام خاصیت یا فیلد، جهت یافتن کنترل متناظر با آن، در فرم جاری استفاده میکنیم. ممکن است کنترل تعریف شده camel case و یا pascal case باشد. به همین جهت دو حالت بررسی را در اینجا مشاهده میکنید.
در ادامه اگر control ایی یافت شد، توسط متد setErrors، کلید جدید modelStateError را که دارای خاصیت سفارشی error است، تنظیم میکنیم. با اینکار سبب خواهیم شد تا خطای اعتبارسنجی دریافتی از سمت سرور، با سیستم اعتبارسنجی Angular یکی شود. به این ترتیب میتوان این خطا را دقیقا ذیل همین کنترل در فرم نمایش داد. اگر کنترلی یافت نشد (کلید آن "" بود و یا جزو نام کنترلهای موجود در آرایهی form.controls نبود)، این خطا را به آرایهی errors اضافه میکنیم تا در بالاترین سطح فرم قابل نمایش شود.
نحوهی استفادهی از متد processModelStateErrors فوق را در متد submitForm، در قسمت شکست عملیات ارسال اطلاعات، مشاهده میکنید:
نمایش خطاهای اعتبارسنجی عمومی فرم
اکنون که کار مقدار دهی آرایهی errors انجام شدهاست، میتوان حلقهای را بر روی آن تشکیل داد و عناصر آنرا در بالای فرم، به صورت عمومی و مستقل از تمام فیلدهای آن نمایش داد:
نمایش خطاهای اعتبارسنجی در سطح فیلدهای فرم
با توجه به تنظیم خطاهای اعتبارسنجی کنترلهای Angular در متد processModelStateErrors و داشتن کلید جدید modelStateError
اکنون میتوان از این کلید جدید (ctrl.errors.modelStateError)، به صورت ذیل جهت نمایش خطای متناظر با آن (ctrl.errors.modelStateError.error) استفاده کرد:
چون تکرار خطاهای اعتبارسنجی در ذیل هر فیلد، فرم را بیش از اندازه شلوغ میکند، میتوان توسط یک ng-template این کدهای تکراری را تبدیل به یک قالب کرد و اکنون استفادهی از این قالب، به سادگی فراخوانی یک ng-container است:
در اینجا در ngTemplateOutlet، ابتدا نام قالب متناظر ذکر میشود و سپس در context آن، نام خاصیت control را که توسط قالب دریافت میشود، به template reference variable متناظری تنظیم میکنیم، تا به کنترل جاری اشاره کند. به این ترتیب میتوان به فرمهایی خلوتتر و با قابلیت مدیریت بهتری رسید.
کدهای کامل این مطلب را از اینجا میتوانید دریافت کنید.
using System; using System.ComponentModel.DataAnnotations; namespace AngularTemplateDrivenFormsLab.Models { public class Movie { public int Id { get; set; } [Required(ErrorMessage = "Movie Title is Required")] [MinLength(3, ErrorMessage = "Movie Title must be at least 3 characters")] public string Title { get; set; } [Required(ErrorMessage = "Movie Director is Required.")] public string Director { get; set; } [Range(0, 100, ErrorMessage = "Ticket price must be between 0 and 100.")] public decimal TicketPrice { get; set; } [Required(ErrorMessage = "Movie Release Date is required")] public DateTime ReleaseDate { get; set; } } }
using AngularTemplateDrivenFormsLab.Models; using Microsoft.AspNetCore.Mvc; namespace AngularTemplateDrivenFormsLab.Controllers { [Route("api/[controller]")] public class MoviesController : Controller { [HttpPost] public IActionResult Post([FromBody]Movie movie) { if (ModelState.IsValid) { // TODO: save ... return Ok(movie); } ModelState.AddModelError("", "This record already exists."); // a cross field validation return BadRequest(ModelState); } } }
الف) خطاهای اعتبارسنجی در سطح فیلدها
زمانیکه return BadRequest(ModelState) صورت میگیرد، محتویات شیء ModelState به همراه status code مساوی 400 به سمت کلاینت ارسال خواهد شد. در شیء ModelState یک دیکشنری که کلیدهای آن، نام خواص و مقادیر متناظر با آنها، خطاهای اعتبارسنجی تنظیم شدهی در مدل است، قرار دارند.
ب) خطاهای اعتبارسنجی عمومی
در این بین میتوان دیکشنری ModelState را توسط متد AddModelError نیز تغییر داد و برای مثال کلید آنرا مساوی "" تعریف کرد. در این حالت یک چنین خطایی به کل فرم اشاره میکند و نه به یک خاصیت خاص.
نمونهای از خروجی نهایی ارسالی به سمت کاربر:
{"":["This record already exists."],"TicketPrice":["Ticket price must be between 0 and 100."]}
به همین جهت نیاز است بتوان خطاهای حالت (الف) را دقیقا در ذیل هر فیلد و خطاهای حالت (ب) را در بالای فرم به صورت عمومی به کاربر نمایش داد:
پردازش و دریافت خطاهای اعتبارسنجی سمت سرور در یک برنامهی Angular
با توجه به اینکه سرور، شیء ModelState را توسط return BadRequest به سمت کلاینت ارسال میکند، برای پردازش دیکشنری دریافتی از سمت آن، تنها کافی است قسمت بروز خطای عملیات ارسال اطلاعات را بررسی کنیم:
در این HttpErrorResponse دریافتی، دو خاصیت error که همان آرایهی دیکشنری نام خواص و پیامهای خطای مرتبط با هر کدام و status code دریافتی مهم هستند:
errors: string[] = []; processModelStateErrors(form: NgForm, responseError: HttpErrorResponse) { if (responseError.status === 400) { const modelStateErrors = responseError.error; for (const fieldName in modelStateErrors) { if (modelStateErrors.hasOwnProperty(fieldName)) { const modelStateError = modelStateErrors[fieldName]; const control = form.controls[fieldName] || form.controls[this.lowerCaseFirstLetter(fieldName)]; if (control) { // integrate into Angular's validation control.setErrors({ modelStateError: { error: modelStateError } }); } else { // for cross field validations -> show the validation error at the top of the screen this.errors.push(modelStateError); } } } } else { this.errors.push("something went wrong!"); } } lowerCaseFirstLetter(data: string): string { return data.charAt(0).toLowerCase() + data.slice(1); }
در اینجا از آرایهی errors برای نمایش خطاهای عمومی در سطح فرم استفاده میکنیم. این خطاها در ModelState، دارای کلید مساوی "" هستند. به همین جهت حلقهای را بر روی شیء responseError.error تشکیل میدهیم. به این ترتیب میتوان به نام خواص و همچنین خطاهای متناظر با آنها رسید.
const control = form.controls[fieldName] || form.controls[this.lowerCaseFirstLetter(fieldName)];
در ادامه اگر control ایی یافت شد، توسط متد setErrors، کلید جدید modelStateError را که دارای خاصیت سفارشی error است، تنظیم میکنیم. با اینکار سبب خواهیم شد تا خطای اعتبارسنجی دریافتی از سمت سرور، با سیستم اعتبارسنجی Angular یکی شود. به این ترتیب میتوان این خطا را دقیقا ذیل همین کنترل در فرم نمایش داد. اگر کنترلی یافت نشد (کلید آن "" بود و یا جزو نام کنترلهای موجود در آرایهی form.controls نبود)، این خطا را به آرایهی errors اضافه میکنیم تا در بالاترین سطح فرم قابل نمایش شود.
نحوهی استفادهی از متد processModelStateErrors فوق را در متد submitForm، در قسمت شکست عملیات ارسال اطلاعات، مشاهده میکنید:
model = new Movie("", "", 0, ""); successfulSave: boolean; errors: string[] = []; constructor(private movieService: MovieService) { } ngOnInit() { } submitForm(form: NgForm) { console.log(form); this.errors = []; this.movieService.postMovieForm(this.model).subscribe( (data: Movie) => { console.log("Saved data", data); this.successfulSave = true; }, (responseError: HttpErrorResponse) => { this.successfulSave = false; console.log("Response Error", responseError); this.processModelStateErrors(form, responseError); }); }
نمایش خطاهای اعتبارسنجی عمومی فرم
اکنون که کار مقدار دهی آرایهی errors انجام شدهاست، میتوان حلقهای را بر روی آن تشکیل داد و عناصر آنرا در بالای فرم، به صورت عمومی و مستقل از تمام فیلدهای آن نمایش داد:
<form #form="ngForm" (submit)="submitForm(form)" novalidate> <div class="alert alert-danger" role="alert" *ngIf="errors.length > 0"> <ul> <li *ngFor="let error of errors"> {{ error }} </li> </ul> </div> <div class="alert alert-success" role="alert" *ngIf="successfulSave"> Movie saved successfully! </div>
نمایش خطاهای اعتبارسنجی در سطح فیلدهای فرم
با توجه به تنظیم خطاهای اعتبارسنجی کنترلهای Angular در متد processModelStateErrors و داشتن کلید جدید modelStateError
control.setErrors({ modelStateError: { error: modelStateError } });
<ng-template #validationErrorsTemplate let-ctrl="control"> <div *ngIf="ctrl.invalid && ctrl.touched"> <div class="alert alert-danger" *ngIf="ctrl.errors.required"> This field is required. </div> <div class="alert alert-danger" *ngIf="ctrl.errors.minlength"> This field should be minimum {{ctrl.errors.minlength.requiredLength}} characters. </div> <div class="alert alert-danger" *ngIf="ctrl.errors.maxlength"> This field should be max {{ctrl.errors.maxlength.requiredLength}} characters. </div> <div class="alert alert-danger" *ngIf="ctrl.errors.pattern"> This field's pattern: {{ctrl.errors.pattern.requiredPattern}} </div> <div class="alert alert-danger" *ngIf="ctrl.errors.modelStateError"> {{ctrl.errors.modelStateError.error}} </div> </div> </ng-template>
<div class="form-group" [class.has-error]="releaseDate.invalid && releaseDate.touched"> <label class="control-label" for="releaseDate">Release Date</label> <input type="text" name="releaseDate" #releaseDate="ngModel" class="form-control" required [(ngModel)]="model.releaseDate" /> <ng-container *ngTemplateOutlet="validationErrorsTemplate; context:{ control: releaseDate }"></ng-container> </div>
کدهای کامل این مطلب را از اینجا میتوانید دریافت کنید.
اشتراکها
مسیر راه ASP.NET Core در دات نت 7
اشتراکها
پروژه C# to JavaScript Compiler
This compiler can compile C# into JavaScript. By doing this you can leverage all the advantages of C#, such as static type checking, IntelliSense (the kind that works) and lambda expressions when writing code for the browser