متدی تحت عنوان ValidateEmail را تصور کنید. این متد از حیث بازگشت نتیجه به عنوان خروجی میتواند به اشکال مختلفی پیاده سازی شود که در ادامه مشاهده میکنیم:
متد ValidateEmail با خروجی Boolean
public bool ValidateEmail(string email) { var valid = true; if (string.IsNullOrWhiteSpace(email)) { valid = false; } var isValidFormat = true;//todo: using RegularExpression if (!isValidFormat) { valid = false; } var isRealDoamin = true;//todo: Code here that confirms whether domain exists. if (!isRealDoamin) { valid = false; } return valid; }
همانطور که در تکه کد زیر مشخص میباشد، استفاده کننده از متد بالا، امکان بررسی خروجی آن را در قالب یک شرط خواهد داشت و علاوه بر اینکه پیاده سازی آن ساده میباشد، خوانایی کد را نیز بالا میبرد؛ ولی با این حال نمیتوان متوجه شد مشکل اصلی آدرس ایمیل ارسالی به عنوان آرگومان، دقیقا چیست.
var email = "email@example.com"; var isValid = ValidateEmail(email); if(isValid) { //do something }
متد ValidateEmail با صدور استثناء
public void ValidateEmail(string email) { if (string.IsNullOrWhiteSpace(email)) throw new ArgumentNullException(nameof(email)); var isValidFormat = true;//todo: using RegularExpression if (!isValidFormat) throw new ArgumentException("email is not in a correct format"); var isRealDoamin = true;//todo: Code here that confirms whether domain exists. if (!isRealDoamin) throw new ArgumentException("email does not include a valid domain.") }
روش بالا هم جواب میدهد ولی بهتر است کلاس Exception سفارشی به عنوان مثال ValidationException برای این قضیه در نظر گرفته شود تا بتوان وهلههای صادر شده از این نوع را در لایههای بالاتر مدیریت کرد.
متد ValidateEmail با چندین خروجی
برای این منظور چندین راه حل پیش رو داریم.
با استفاده از پارامتر out:
public bool ValidateEmail(string email, out string message) { var valid = true; message = string.Empty; if (string.IsNullOrWhiteSpace(email)) { valid = false; message = "email is null."; } if (valid) { var isValidFormat = true;//todo: using RegularExpression if (!isValidFormat) { valid = false; message = "email is not in a correct format"; } } if (valid) { var isRealDoamin = true;//todo: Code here that confirms whether domain exists. if (!isRealDoamin) { valid = false; message = "email does not include a valid domain."; } } return valid; }
var email = "email@example.com"; var isValid = ValidateEmail(email, out string message); if (isValid) { //do something }
Tuple<bool, List<string>> result = Tuple.Create<bool, List<string>>(true, new List<string>());
public class OperationResult { public bool Success { get; set; } public IList<string> Messages { get; } = new List<string>(); public void AddMessage(string message) { Messages.Add(message); } }
public OperationResult ValidateEmail(string email) { var result = new OperationResult(); if (string.IsNullOrWhiteSpace(email)) { result.Success = false; result.AddMessage("email is null."); } if (result.Success) { var isValidFormat = true;//todo: using RegularExpression if (!isValidFormat) { result.Success = false; result.AddMessage("email is not in a correct format"); } } if (result.Success) { var isRealDoamin = true;//todo: Code here that confirms whether domain exists. if (!isRealDoamin) { result.Success = false; result.AddMessage("email does not include a valid domain."); } } return result; }
این بار خروجی متد مذکور از نوع OperationResult ای میباشد که هم موفقیت آمیز بودن یا عدم آن را مشخص میکند و همچنین امکان دسترسی به لیست پیغامهای مرتبط با اعتبارسنجیهای انجام شده، وجود دارد.
استفاده از Exception برای نمایش پیغام برای کاربر نهایی
با صدور یک استثناء و مدیریت سراسری آن در بالاترین (خارجی ترین) لایه و نمایش پیغام مرتبط با آن به کاربر نهایی، میتوان از آن به عنوان ابزاری برای ارسال هر نوع پیغامی به کاربر نهایی استفاده کرد. اگر قوانین تجاری با موفقیت برآورده نشدهاند یا لازم است به هر دلیلی یک پیغام مرتبط با یک اعتبارسنجی تجاری را برای کاربر نمایش دهید، این روش بسیار کارساز میباشد و با یکبار وقت گذاشتن برای توسعه زیرساخت برای این موضوع به عنوان یک Cross Cutting Concern تحت عنوان Exception Management آزادی عمل زیادی در ادامه توسعه سیستم خود خواهید داشت.
به عنوان مثال داشتن یک کلاس Exception سفارشی تحت عنوان UserFriendlyException در این راستا یک الزام میباشد.
[Serializable] public class UserFriendlyException : Exception { public string Details { get; private set; } public int Code { get; set; } public UserFriendlyException() { } public UserFriendlyException(SerializationInfo serializationInfo, StreamingContext context) : base(serializationInfo, context) { } public UserFriendlyException(string message) : base(message) { } public UserFriendlyException(int code, string message) : this(message) { Code = code; } public UserFriendlyException(string message, string details) : this(message) { Details = details; } public UserFriendlyException(int code, string message, string details) : this(message, details) { Code = code; } public UserFriendlyException(string message, Exception innerException) : base(message, innerException) { } public UserFriendlyException(string message, string details, Exception innerException) : this(message, innerException) { Details = details; } }
و همچنین لازم است در بالاترین لایه سیستم خود به عنوان مثال برای یک پروژه ASP.NET MVC یا ASP.NET Core MVC میتوان یک ExceptionFilter سفارشی نیز تهیه کرد که هم به صورت سراسری استثناءهای سفارشی شما را مدیریت کند و همچنین خروجی مناسب Json برای استفاده در سمت کلاینت را نیز مهیا کند. به عنوان مثال برای درخواستهای Ajax ای لازم است در سمت کلاینت نیز پاسخهای رسیده از سمت سرور به صورت سراسری مدیریت شوند و برای سایر درخواستها همان نمایش صفحات خطای پیغام مرتبط با استثناء رخ داده شده کفایت میکند.
یک مدل پیشنهادی برای تهیه خروجی مناسب برای ارسال جزئیات استثنا رخ داده در درخواستهای Ajax ای
[Serializable] public class MvcAjaxResponse : MvcAjaxResponse<object> { public MvcAjaxResponse() { } public MvcAjaxResponse(bool success) : base(success) { } public MvcAjaxResponse(object result) : base(result) { } public MvcAjaxResponse(ErrorInfo error, bool unAuthorizedRequest = false) : base(error, unAuthorizedRequest) { } } [Serializable] public class MvcAjaxResponse<TResult> : MvcAjaxResponseBase { public MvcAjaxResponse(TResult result) { Result = result; Success = true; } public MvcAjaxResponse() { Success = true; } public MvcAjaxResponse(bool success) { Success = success; } public MvcAjaxResponse(ErrorInfo error, bool unAuthorizedRequest = false) { Error = error; UnAuthorizedRequest = unAuthorizedRequest; Success = false; } /// <summary> /// The actual result object of AJAX request. /// It is set if <see cref="MvcAjaxResponseBase.Success" /> is true. /// </summary> public TResult Result { get; set; } } public class MvcAjaxResponseBase { public string TargetUrl { get; set; } public bool Success { get; set; } public ErrorInfo Error { get; set; } public bool UnAuthorizedRequest { get; set; } public bool __mvc { get; } = true; }
[Serializable] public class ErrorInfo { public int Code { get; set; } public string Message { get; set; } public string Detail { get; set; } public Dictionary<string, string> ValidationErrors { get; set; } public ErrorInfo() { } public ErrorInfo(string message) { Message = message; } public ErrorInfo(int code) { Code = code; } public ErrorInfo(int code, string message) : this(message) { Code = code; } public ErrorInfo(string message, string details) : this(message) { Detail = details; } public ErrorInfo(int code, string message, string details) : this(message, details) { Code = code; } }
public async Task CheckIsDeactiveAsync(long id) { if (await _organizationalUnits.AnyAsync(a => a.Id == id && !a.IsActive).ConfigureAwait(false)) throw new UserFriendlyException("واحد سازمانی جاری غیرفعال میباشد."); }
روش نام گذاری متدهایی که امکان بازگشت خروجی Null را دارند
public User GetById(long id);
[Serializable] public class EntityNotFoundException : Exception { public Type EntityType { get; set; } public object Id { get; set; } public EntityNotFoundException() { } public EntityNotFoundException(string message) : base(message) { } public EntityNotFoundException(string message, Exception innerException) : base(message, innerException) { } public EntityNotFoundException(SerializationInfo serializationInfo, StreamingContext context) : base(serializationInfo, context) { } public EntityNotFoundException(Type entityType, object id) : this(entityType, id, null) { } public EntityNotFoundException(Type entityType, object id, Exception innerException) : base($"There is no such an entity. Entity type: {entityType.FullName}, id: {id}", innerException) { EntityType = entityType; Id = id; } }
یک مثال واقعی
public async Task<UserOrganizationalUnitInfo> GetCurrentOrganizationalUnitInfoOrNullAsync(long userId) { return (await _setting.GetSettingValueForUserAsync( UserSettingNames.CurrentOrganizationalUnitInfo, userId).ConfigureAwait(false)) .FromJsonString<UserOrganizationalUnitInfo>(); }
ابتدا فایل yml زیر را در پوشهی github\workflows\deploy.yml. قرار دهید (پوشهای را به این نام، در ریشهی پروژهی خود ایجاد کنید):
name: Deploy to GitHub Pages # Run workflow on every push to the main branch on: push: branches: [ main ] jobs: deploy-to-github-pages: # use ubuntu-latest image to run steps on runs-on: ubuntu-latest steps: # uses GitHub's checkout action to checkout code form the main branch - uses: actions/checkout@v2 # sets up .NET Core SDK - name: Setup .NET Core SDK uses: actions/setup-dotnet@v1 with: dotnet-version: 5.0.302 # publishes Blazor project to the release-folder - name: Publish .NET Core Project run: dotnet publish ./src/DNTPersianComponents.Blazor.WasmSample/Server/DNTPersianComponents.Blazor.WasmSample.Server.csproj -c Release -o release --nologo # changes the base-tag in index.html from '/' to 'DNTPersianComponents.Blazor' to match GitHub Pages repository subdirectory - name: Change base-tag in index.html from / to DNTPersianComponents.Blazor run: sed -i 's/<base href="\/" \/>/<base href="\/DNTPersianComponents.Blazor\/" \/>/g' release/wwwroot/index.html # copy index.html to 404.html to serve the same file when a file is not found - name: copy index.html to 404.html run: cp release/wwwroot/index.html release/wwwroot/404.html # add .nojekyll file to tell GitHub pages to not treat this as a Jekyll project. (Allow files and folders starting with an underscore) - name: Add .nojekyll file run: touch release/wwwroot/.nojekyll - name: Commit wwwroot to GitHub Pages uses: JamesIves/github-pages-deploy-action@3.7.1 with: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} BRANCH: github-pages FOLDER: release/wwwroot
- نام شاخهی اصلی پروژه؛ که یا main است و یا master.
- شماره نگارش دات نت مورد استفاده.
- مسیر فایل csproj پروژهی wasm.
- نام اصلی مخزن کد.
سپس آنرا به مخزن کد خود commit کنید. بعد به قسمت settings->pages در github مراجعه کرده و source را بر روی نام شاخهی جدید github-pages (فوق در قسمت آخر کار) قرار داده و آنرا ذخیره کنید. الان سایت دموی شما در مسیری که در همین قسمت pages پس از ذخیره سازی، نمایش میدهد، آمادهاست.
یک نکتهی مهم
چون base href، توسط action فوق اصلاح میشود تا به پوشهی نسبی محل قرارگیری برنامه اشاره کند، نیاز است navlinkها با href شروع شدهی با / نباشند؛ چون به ریشهی سایت اشاره میکنند و نه مسیر نسبی محل قرارگیری برنامه. کلا در هر قسمتی از برنامه، این نکته باید رعایت شود. مثلا اگر فونت وبی را در فایل app.css تعریف کردهاید، مسیر آن نباید با / شروع شود؛ وگرنه یافت نخواهد شد. یک مثال:
فایل app.css برنامه در مسیر wwwroot\css\app.css قرار دارد و داخل آن فایل، فونتهای پوشهی دیگر wwwroot\lib\samim-font را به صورت زیر تعریف کردهایم؛ که یعنی مسیر فونت را از ریشهی سایت پیدا کن:
src: url('/lib/samim-font/Samim-Bold.eot?v=4.0.5');
src: url('../lib/samim-font/Samim-Bold.eot?v=4.0.5');
ممکن است پس از ارتقاء به دات نت 6، شاهد خطاهای BadHttpRequestException زیادی در سمت Web API شوید (البته فقط در حالت توسعه):
Microsoft.AspNetCore.Server.Kestrel.Core.BadHttpRequestException: Unexpected end of request content.
اگر برنامهی شما اطلاعاتی را در پوشهی wwwroot ثبت کند، برای مثال یک بانک اطلاعاتی SQLite را در آن قرار دادهاید، hot reload حتی این پوشه را هم تحت کنترل خود دارد و با هر تغییری در آن، حتی نوشته شدن یک رکورد در فایل بانک اطلاعاتی، مجددا برنامه را کامپایل میکند. همین کامپایل پشت صحنه، سبب abort شدن درخواستهای HTTP سمت کلاینت آن و مشاهدهی خطاهای BadHttpRequestException سمت سرور میشود که عدم اطلاع از دلیل آن میتواند این تصور را پیش بیاورد که کدهای برنامه مشکلی دارند، اما خیر.
اگر با CLI کار میکنید، دستور dotnet watch run --no-hot-reload کمی وضع را بهتر میکند و معادل افزودن تنظیم زیر به فایل Properties\launchSettings.json است:
"profiles": { "Name.Server": { "hotReloadEnabled": false,
<ItemGroup> <Watch Remove="wwwroot\**\*" /> </ItemGroup>
روش به کارگیری تقویم در Blazor
<link href="css/js-persian-cal.css" rel="stylesheet"/>
<script src="js/js-persian-cal.min.js"></script>
<input type="text" id="pcal1" />
protected override async Task OnAfterRenderAsync(bool firstRender) { int dateFieldCount = 1; if (firstRender) { for (int i = 1; i <= dateFieldCount; i++) { await JsRuntime.InvokeVoidAsync("CallAmib", "pcal" + i.ToString()); } } }
@inject IJSRuntime JsRuntime
window.CallAmib = (objCal1) => { new AMIB.persianCalendar(objCal1); }
@using Microsoft.AspNetCore.Components.Web @namespace ShamsiDatePickerBlazor.Pages @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers <!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <base href="~/" /> <link rel="stylesheet" href="css/bootstrap/bootstrap.min.css" /> <link href="css/site.css" rel="stylesheet" /> <link href="css/js-persian-cal.css" rel="stylesheet" /> <component type="typeof(HeadOutlet)" render-mode="ServerPrerendered" /> </head> <body> @RenderBody() <div id="blazor-error-ui"> <environment include="Staging,Production"> An error has occurred. This application may no longer respond until reloaded. </environment> <environment include="Development"> An unhandled exception has occurred. See browser dev tools for details. </environment> <a href="">Reload</a> <a>🗙</a> </div> <script src="js/js-persian-cal.min.js"></script> <script src="js/CallAmib.js"></script> <script src="_framework/blazor.server.js"></script> </body> </html>
مشکل!!
window.CallCall = (objCal1) => { new AMIB.persianCalendar(objCal1,{ onchange: function(pdate) { DotNet.invokeMethodAsync('ShamsiDatePickerBlazor', 'DateChanged', pdate.toString()).then( (date) => { console.log(data); } ); } }); }
static string selectedDate; [JSInvokable] public static void DateChanged(string pdate) { selectedDate = pdate; }
بررسی وضعیت فعلی پروژه Roslyn
یکی از مواردی که ممکن است در محیط کاری با آن برخورد داشت، تقاضای تولید فایل word یک گزارش با فرمتی مشخص از یک برنامه ASP.Net است. برای مثال یک قالب درست کردهاند که header و footer و کلا یک فرمت رسمی دارد. الان برنامه شما باید این فایل word رسمی را با گزارشی که تولید میکند پر کند. حالا اینجاست که گرفتاری برنامه نویس شروع میشود! روی سرور باید word نصب باشد تا توسط اشیاء COM آن بتوان یک چنین کارهایی را آنهم با ASP.Net که به صورت پیش فرض کمترین سطح دسترسی را روی سیستم دارد انجام داد. یا اینکه باید به سراغ کامپوننتهای تجاری رفت و حالا اینجا با این وضع تحریم و غیره چگونه بتوان آنها را خریداری کرد یا شاید احتمالا در سایتهای وارز بتوان نسخه تکه پاره شده آنها را یافت. مشکلی هم که این نوع کامپوننتها دارند این است که ممکن است سال دیگر اصلا ساپورت نشوند. محصولات مایکروسافت هم که مرتبا در حال به روز رسانی هستند. در این حالت برنامه متکی به این نوع کامپوننتهای تجاری سورس بسته در همان نگارش قبلی خود مجبور است باقی بماند.
خوشبختانه با ارائه آفیس 2007 و فرمت OpenXML فایلهای آن، این مشکل تقریبا مرتفع شده است. مایکروسافت نیز برای سهولت تولید این نوع اسناد، OpenXML SDK را ارائه داده است که از آدرس زیر قابل دریافت است:
Open XML Format SDK 1.0
البته پیش نمایش نگارش دو SDK آن نیز موجود است که در مطلب جاری به آن پرداخته نخواهد شد.
فایلهای office 2007 از یک فایلzip تشکیل شده از چند فایل xml داخل آن، ایجاد شدهاند. برای مثال یک فایل docx را با winrar یا امثال آن باز کنید (تصویر زیر):
برای کار با اینگونه اسناد باید با اصطلاحات زیر آشنا شد:
Package : فایل zip شما (همان فایل برای مثال docx) اینجا یک بسته نام دارد.
Parts : اجزای این بسته که همان فایلهای آن هستند، parts نامیده شده اند.
Relations : اگر به فایلهای موجود در یک بسته دقت کنید، فایلهایی با پسوند rels را خواهید دید که بیانگر نحوه ارتباط Parts با یکدیگر هستند.
Relations Ids: هر ارتباط با یک ID منحصربفرد تعریف میگردد.
اگر علاقمند باشید که پوستری را در این رابطه مشاهده نمائید میتوان به آدرس زیر مراجعه نمود.
Open XML Developer Map
نحوه استفاده از OpenXML SDK در دات نت:
ابتدا باید ارجاعی را به فایل DocumentFormat.OpenXml.dll که پس از نصب در مسیر OpenXMLSDK\1.0.1825\lib قرار گرفته است به پروژه افزود. سپس نیاز است تا ارجاعی به کتابخانه WindowsBase نیز به برنامه افزوده شود (تصویر زیر). افزودن ارجاعی به این کتابخانه جهت کامپایل برنامه ضروری است (شکل زیر).
تا اینجا ارجاعات برنامه به صورت زیر خواهند بود:
یک مثال ساده:
قصد داریم یک فایل docx ساده را با استفاده از OpenXML SDK ایجاد کنیم. در مثال زیر فرمت متغیر docXml را میتوان با ایجاد یک فایل docx ساده در word و سپس باز کردن بسته فشرده شده آن و مشاهده محتوای فایل word\document.xml بدست آورد.
using System.IO;
using System.Text;
using DocumentFormat.OpenXml;
using DocumentFormat.OpenXml.Packaging;
namespace OpenXMLTestApp
{
class CWord
{
public static void CreateDocument(string documentFileName, string text)
{
using (WordprocessingDocument wordDoc =
WordprocessingDocument.Create(documentFileName, WordprocessingDocumentType.Document))
{
MainDocumentPart mainPart = wordDoc.AddMainDocumentPart();
string docXml =
@"<?xml version=""1.0"" encoding=""UTF-8"" standalone=""yes""?>
<w:document xmlns:w=""http://schemas.openxmlformats.org/wordprocessingml/2006/main"">
<w:body><w:p><w:r><w:t>#REPLACE#</w:t></w:r></w:p></w:body>
</w:document>";
docXml = docXml.Replace("#REPLACE#", text);
using (Stream stream = mainPart.GetStream())
{
byte[] buf = (new UTF8Encoding()).GetBytes(docXml);
stream.Write(buf, 0, buf.Length);
}
}
}
}
}
CWord.CreateDocument("test.docx", "سلام دنیا");
برای مطالعه بیشتر میتوان به منابع زیر مراجعه نمود:
یک ویدیوی آموزشی رایگان از مایکروسافت
دریافت
سؤالات متداول در MSDN
http://msdn.microsoft.com/en-us/library/bb491088.aspx
البته اگر پس از نصب SDK به پوشه doc آن مراجعه نمائید، این سؤال و جوابها را در فایل راهنمای chm آن نیز میتوان پیدا کرد.
مثال دیگری در مورد ایجاد یک گزارش از بانک اطلاعاتی و گرفتن خروجی docx از آن
http://openxmldeveloper.org/articles/GenerateWordTable.aspx
البته این مثال خیلی قدیمی است و قسمتهای کار با پکیج را با SDK ارائه شده میتوان به صورت خودکار انجام داد. اما حداقل نحوه تولید جداول استاندارد OpenXML را میتوان از آن ایده گرفت.
مثالی در مورد نحوه قرار دادن عکس در فایل docx تولیدی
همچنین مثالهای بیشتری را در وبلاگهای مربوطه میتوان یافت:
http://blogs.msdn.com/brian_jones/
http://blogs.msdn.com/ericwhite/default.aspx
روش تعیین نوع useState Hook
برای این منظور در ابتدا فایل جدید src\components\Input.tsx را ایجاد کرده و به صورت زیر تکمیل میکنیم:
import React, { useState } from "react"; export const Input = () => { const [name, setName] = useState(""); return <input value={name} onChange={(e) => setName(e.target.value)} />; };
پس از این تعاریف ... برنامه بدون مشکل کار میکند و کامپایل میشود. اکنون سؤال اینجا است که آیا تایپاسکریپت در ایجا اصلا کاری را هم انجام میدهد؟ برای درک این موضوع، سطر useState را به صورت زیر تغییر میدهیم:
const [name, setName] = useState(0);
عنوان میکند که مقدار رشتهای e.target.value، به مقدار عددی name قابل انتساب نیست. به عبارتی TypeScript از قابلیت Type Inference خود در اینجا استفاده میکند. درست است که به ظاهر نوعی را برای useState و خروجی منتسب به آن تعیین نکردهایم، اما بر اساس نوع مقدار پیشفرض آن، نوع name و setName به صورت خودکار مشخص میشوند و نیازی به ذکر صریح این نوع، نیست. برای مثال در حالت اول چون مقدار پیشفرض useState را یک رشتهی خالی معرفی کرده بودیم، نوع name نیز string درنظر گرفته شده بود. در حالت دوم بر اساس مقدار پیشفرض عددی useState، اینبار نوع name نیز یک number خواهد بود و دیگر نمیتوان یک مقدار رشتهای را مانند e.target.value به آن انتساب داد. مزیت کار کردن با TypeScript در اینجا، مشاهدهی آنی خطای یک چنین استفادهها و انتسابهای نادرستی است.
مفهوم Type Inference را در تصاویر زیر بهتر میتوان مشاهده کرد. اشارهگر ماوس را به تعریف useState نزدیک کنید. در توضیحاتی که ظاهر میشود، بر اساس نوع مقدار پیشفرض آن، نوع آرگومان جنریک متد useState نیز به صورت خودکار تغییر میکند:
و نکتهی مهم اینجا است که نیازی به ذکر صریح این نوع جنریک، مانند مثال زیر نیست:
const [name, setName] = useState<string>("");
const [name, setName] = useState("");
سؤال: اگر مقدار اولیهی useState را null تعیین کردیم و یا اصلا تعریف نکردیم و undefined بود، چطور؟
در یک چنین حالتی که نوع دقیق state، از طریق مقدار اولیهی آن قابل استنتاج نیست، نیاز هست همانند تصاویر فوق، تعریف جنریک useState را به نحو صریحی ذکر کرده و آنرا با union types تکمیل کنیم:
const [name, setName] = useState<string | null>(null);
روش تعیین نوع useRef Hook
در ادامه میخواهیم نحوهی تعیین نوع DOM Elements را در React بررسی کنیم. با استفاده از useRef میتوان به ارجاعی از یک DOM Element دسترسی یافت.
import React, { useRef, useState } from "react"; export const Input = () => { const [name, setName] = useState(""); const inputRef = useRef(null); if (inputRef && inputRef.current) { console.log("ref", inputRef.current.value); } return ( <input ref={inputRef} value={name} onChange={(e) => setName(e.target.value)} /> ); };
عنوان میکند نوعی که inputRef.current دارد، نال است و نال به همراه خاصیت value نیست. برای اینکه نوع inputRef را بهتر بتوانیم بررسی کنیم، دقیقا بر روی آن کلیک راست کرده و گزینهی Go to Type Definition را انتخاب کنید. بلافاصله به تعریف زیر هدایت خواهیم شد:
interface MutableRefObject<T> { current: T; }
برای رفع این مشکل فقط کافی است نوع المان مدنظر را صریحا به عنوان آرگومان جنریک useRef معرفی کنیم:
const inputRef = useRef<HTMLInputElement>(null);
فعال بودن Strict Null Checking در پروژههای TypeScript ای React
نکات مطلب «نوعهای نال نپذیر در TypeScript» به صورت پیشفرض در پروژههای تایپ اسکریپتی React هم فعال هستند؛ چون پرچم strict واقع در فایل tsconfig.json پروژه، به صورت پیشفرض به true تنظیم شدهاست و این پرچم، Strict Null Checking را نیز شامل میشود. برای آزمایش فعال بودن آن، کدهای فوق را به صورت زیر تغییر دهید تا صرفا if آن حذف شود:
//if (inputRef && inputRef.current) { console.log("ref", inputRef.current.value); //}
که برای رفع آن، همانند قبل باید ذکر if بررسی نال بودن inputRef و خاصیت current آنرا اضافه کرد؛ تا دیگر در زمان اجرای برنامه، با شانس نال بودن یکی از ایندو مواجه نشویم و به کیفیت بالاتری در برنامهی خود برسیم.
روش بررسی if (inputRef && inputRef.current) معادل سادهتری را نیز در TypeScript 3.7 به بعد دارد که به Optional Chaining معروف است؛ به صورت زیر:
console.log("ref", inputRef?.current?.value);
»اگر با #F آشنایی ندارید میتوانید از اینجا شروع کنید.
به صورت کلی برای استفاده گسترده از #F در پروژههای وب نیاز به یک سری templateهای آماده داریم در غیر این صورت کار کمی سخت خواهد شد. به تصویر زیر دقت نمایید:
واضح است که با توجه به تصویر بالا کنترلرها و البته مدلهای app و هر آنچه که سمت سرور به آن نیاز است باید با استفاده از #F پیاده سازی شوند. اما هنگامی که کنترلرها با استفاده از #F نوشته شوند سیستم مسیر یابی نیز تحت تاثیر قرار خواهد گرفت. علاوه بر آن باید فکری برای بخش Bundling و همچنین فیلترهاو... نمود. در نتیجه با توجه به template پروژه مورد نظر بر خلاف حالت پیش فرض #C آن که در قالب یک پروژه ارائه میشود در این جا حداقل به دو پروژه نیاز داریم. خوشبختانه همانند پروژه FSharpX که برای WPF مناسب است برای MVC نیز template آماده موجود است که در ادامه با آن آشنا خواهیم شد.
شروع به کار
ابتدا در VS.Net یک پروژه جدید ایجاد نمایید. از بخش Online Template گزینه FSharp MVC 4 را جستجو کنید.
بعد از انتخاب نام پروژه و کلیک بر روی Ok ( و البته دانلود حدود ده MB اطلاعات) صفحه زیر نمایان میشود. در این قسمت تنظیمات مربوط به انتخاب View Engine و نوع قالب پروژه را وجود دارد. در صورتی که قصد استفاده از Web Api را دارید گزینه Web Api Project را انتخاب کنید در غیر این صورت گزینه Empty Project.
البته از Visual Studio 2012 به بعد این بخش به صورت زیر خواهد بود که قسمت Single Page App به آن اضافه شده است:
بعد از کلیک بر روی Ok یک پروژه بر اساس Template مورد نظر ساخته میشود. همانند تصویر زیر:
بررسی تغییرات
در یک نگاه به راحتی میتوان تغییرات زیر را در پروژه Web تشخیص داد:
»پوشه Controller وجود ندارد؛
»پوشه مدل وجود ندارد؛
»فایل Global.asax دیگر فایلی به نام Global.asax.cs را همراه با خود ندارد.
دلیل اصلی عدم وجود موارد بالا این است که تمام این موارد باید به صورت #F پیاده سازی شوند در نتیجه به پروژه #F ساخته شده منتقل شده اند. فایل Global.asax را باز نمایید. سورس زیر قابل مشاهده است:
<%@ Application Inherits="FsWeb.Global" Language="C#" %> <script Language="C#" RunAt="server">
// Defines the Application_Start method and calls Start in // System.Web.HttpApplication from which Global inherits. protected void Application_Start(Object sender, EventArgs e) { base.Start(); } </script>
حال به بررسی پروژه #F ساخته شده خواهیم پرداخت. در این پروژه یک فایل Global.fs وجود دارد که سورس آن به صورت زیر است:
namespace FsWeb open System open System.Web open System.Web.Mvc open System.Web.Routing type Route = { controller : string action : string id : UrlParameter } type Global() = inherit System.Web.HttpApplication() static member RegisterRoutes(routes:RouteCollection) = routes.IgnoreRoute("{resource}.axd/{*pathInfo}") routes.MapRoute("Default", "{controller}/{action}/{id}", { controller = "Home"; action = "Index" id = UrlParameter.Optional } ) member this.Start() = AreaRegistration.RegisterAllAreas() Global.RegisterRoutes(RouteTable.Routes)
RouteConfig.RegisterRoutes(RouteTable.Routes);
public class RouteConfig { public static void RegisterRoutes(RouteCollection routes) { routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); routes.MapRoute( name: "Default", url: "{controller}/{action}/{id}", defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional } ); } }
//تعریف الکوی مسیر یابی type Route = { controller : string action : string id : UrlParameter } type Global() = inherit System.Web.HttpApplication() static member RegisterRoutes(routes:RouteCollection) = //فراخوانی و انتساب الگوی مسیر یابی به مسیرهای تعریف شده routes.IgnoreRoute("{resource}.axd/{*pathInfo}") routes.MapRoute("Default", "{controller}/{action}/{id}", { controller = "Home"; action = "Index" id = UrlParameter.Optional } )
Global.RegisterRoutes(RouteTable.Routes)
در این پست با Template پروژههای F# MVC 4 اشنا شدیم و از طرفی مشخص شد که برای پیاده سازی این گونه پروژهها حداقل نیاز به دو پروژه داریم. یک پروژه که از نوع #C است ولی در آن فقط View ها و فایل جاوااسکریپتی و البته Css وجود دارد. از طرف دیگر کنترلرها و مدلها و هر چیز دیگر که مربوط به سمت سرور است در قالب یک پروژه #F پیاده سازی میشود.