class Options { [Option('r', "read", Required = true, HelpText = "Input files to be processed.")] public IEnumerable<string> InputFiles { get; set; } // Omitting long name, default --verbose [Option( HelpText = "Prints all messages to standard output.")] public bool Verbose { get; set; } [Option(Default = "中文", HelpText = "Content language.")] public string Language { get; set; } [Value(0, MetaName = "offset", HelpText = "File offset.")] public long? Offset { get; set;} }
بدین منظور فریم ورک ASP.NET Web API کتابخانه ای برای تولید خودکار صفحات راهنما در زمان اجرا (run-time) فراهم کرده است.
ایجاد صفحات راهنمای API
برای شروع ابتدا ابزار ASP.NET and Web Tools 2012.2 Update را نصب کنید. اگر از ویژوال استودیو 2013 استفاده میکنید این ابزار بصورت خودکار نصب شده است. این ابزار صفحات راهنما را به قالب پروژههای ASP.NET Web API اضافه میکند.
یک پروژه جدید از نوع ASP.NET MVC Application بسازید و قالب Web API را برای آن انتخاب کنید. این قالب پروژه کنترلری بنام ValuesController را بصورت خودکار برای شما ایجاد میکند. همچنین صفحات راهنمای API هم برای شما ساخته میشوند. تمام کد مربوط به صفحات راهنما در قسمت Areas قرار دارند.
اگر اپلیکیشن را اجرا کنید خواهید دید که صفحه اصلی لینکی به صفحه راهنمای API دارد. از صفحه اصلی، مسیر تقریبی Help/ خواهد بود.
این لینک شما را به یک صفحه خلاصه (summary) هدایت میکند.
نمای این صفحه در مسیر Areas/HelpPage/Views/Help/Index.cshtml قرار دارد. میتوانید این نما را ویرایش کنید و مثلا قالب، عنوان، استایلها و دیگر موارد را تغییر دهید.
بخش اصلی این صفحه متشکل از جدولی است که APIها را بر اساس کنترلر طبقه بندی میکند. مقادیر این جدول بصورت خودکار و توسط اینترفیس IApiExplorer تولید میشوند. در ادامه مقاله بیشتر درباره این اینترفیس صحبت خواهیم کرد. اگر کنترلر جدیدی به API خود اضافه کنید، این جدول بصورت خودکار در زمان اجرا بروز رسانی خواهد شد.
ستون "API" متد HTTP و آدرس نسبی را لیست میکند. ستون "Documentation" مستندات هر API را نمایش میدهد. مقادیر این ستون در ابتدا تنها placeholder-text است. در ادامه مقاله خواهید دید چگونه میتوان از توضیحات XML برای تولید مستندات استفاده کرد.
هر API لینکی به یک صفحه جزئیات دارد، که در آن اطلاعات بیشتری درباره آن قابل مشاهده است. معمولا مثالی از بدنههای درخواست و پاسخ هم ارائه میشود.
افزودن صفحات راهنما به پروژه ای قدیمی
می توانید با استفاده از NuGet Package Manager صفحات راهنمای خود را به پروژههای قدیمی هم اضافه کنید. این گزینه مخصوصا هنگامی مفید است که با پروژه ای کار میکنید که قالب آن Web API نیست.
از منوی Tools گزینههای Library Package Manager, Package Manager Console را انتخاب کنید. در پنجره Package Manager Console فرمان زیر را وارد کنید.
Install-Package Microsoft.AspNet.WebApi.HelpPage
@Html.ActionLink("API", "Index", "Help", new { area = "" }, null)
همانطور که مشاهده میکنید مسیر نسبی صفحات راهنما "Help/" میباشد. همچنین اطمینان حاصل کنید که ناحیهها (Areas) بدرستی رجیستر میشوند. فایل Global.asax را باز کنید و کد زیر را در صورتی که وجود ندارد اضافه کنید.
protected void Application_Start() { // Add this code, if not present. AreaRegistration.RegisterAllAreas(); // ... }
افزودن مستندات API
بصورت پیش فرض صفحات راهنما از placeholder-text برای مستندات استفاده میکنند. میتوانید برای ساختن مستندات از توضیحات XML استفاده کنید. برای فعال سازی این قابلیت فایل Areas/HelpPage/App_Start/HelpPageConfig.cs را باز کنید و خط زیر را از حالت کامنت درآورید:
config.SetDocumentationProvider(new XmlDocumentationProvider( HttpContext.Current.Server.MapPath("~/App_Data/XmlDocument.xml")));
زیر قسمت Output گزینه XML documentation file را تیک بزنید و در فیلد روبروی آن مقدار "App_Data/XmlDocument.xml" را وارد کنید.
حال کنترلر ValuesController را از مسیر Controllers/ValuesController.cs/ باز کنید و یک سری توضیحات XML به متدهای آن اضافه کنید. بعنوان مثال:
/// <summary> /// Gets some very important data from the server. /// </summary> public IEnumerable<string> Get() { return new string[] { "value1", "value2" }; } /// <summary> /// Looks up some data by ID. /// </summary> /// <param name="id">The ID of the data.</param> public string Get(int id) { return "value"; }
اپلیکیشن را مجددا اجرا کنید و به صفحات راهنما بروید. حالا مستندات API شما باید تولید شده و نمایش داده شوند.
صفحات راهنما مستندات شما را در زمان اجرا از توضیحات XML استخراج میکنند. دقت کنید که هنگام توزیع اپلیکیشن، فایل XML را هم منتشر کنید.
توضیحات تکمیلی
صفحات راهنما توسط کلاس ApiExplorer تولید میشوند، که جزئی از فریم ورک ASP.NET Web API است. به ازای هر API این کلاس یک ApiDescription دارد که توضیحات لازم را در بر میگیرد. در اینجا منظور از "API" ترکیبی از متدهای HTTP و مسیرهای نسبی است. بعنوان مثال لیست زیر تعدادی API را نمایش میدهد:
- GET /api/products
- {GET /api/products/{id
- POST /api/products
اگر اکشنهای کنترلر از متدهای متعددی پشتیبانی کنند، ApiExplorer هر متد را بعنوان یک API مجزا در نظر خواهد گرفت. برای مخفی کردن یک API از ApiExplorer کافی است خاصیت ApiExplorerSettings را به اکشن مورد نظر اضافه کنید و مقدار خاصیت IgnoreApi آن را به true تنظیم نمایید.
[ApiExplorerSettings(IgnoreApi=true)] public HttpResponseMessage Get(int id) { }
همچنین میتوانید این خاصیت را به کنترلرها اضافه کنید تا تمام کنترلر از ApiExplorer مخفی شود.
کلاس ApiExplorer متن مستندات را توسط اینترفیس IDocumentationProvider دریافت میکند. کد مربوطه در مسیر Areas/HelpPage/XmlDocumentation.cs/ قرار دارد. همانطور که گفته شد مقادیر مورد نظر از توضیحات XML استخراج میشوند. نکته جالب آنکه میتوانید با پیاده سازی این اینترفیس مستندات خود را از منبع دیگری استخراج کنید. برای اینکار باید متد الحاقی SetDocumentationProvider را هم فراخوانی کنید، که در HelpPageConfigurationExtensions تعریف شده است.
کلاس ApiExplorer بصورت خودکار اینترفیس IDocumentationProvider را فراخوانی میکند تا مستندات APIها را دریافت کند. سپس مقادیر دریافت شده را در خاصیت Documentation ذخیره میکند. این خاصیت روی آبجکتهای ApiDescription و ApiParameterDescription تعریف شده است.
مطالعه بیشتر
http://en.هشام.com/post/build-customizable-language-switcher-tag-helper-with-bootstrap
WebRequest wr = WebRequest.Create(uri); using (WebResponse response = wr.GetResponse()) { }
The remote name could not be resolved: 'en.هشام.com'
البته ممکن است کد فوق را بر روی ویندوزهای جدید بدون مشکل اجرا کنید. علت اینجا است که اگر از ویندوزهای قبل از ویندوز سرور 2012 استفاده میکنید، دات نت فریم ورک از RFC 3490 استفاده میکند؛ در غیراینصورت از RFC 5891 (فقط برای Windows 8, Windows 8.1, Windows 10, or Windows Server 2012)، با این تفاوتها.
روش رفع آن هم فعال سازی پردازش این نوع دامنهها (بر روی تمام ویندوزها) در فایلهای app.config و یا web.config به صورت زیر است:
<configuration> <uri> <idn enabled="All" /> <iriParsing enabled="true" /> </uri> </configuration>
var unicode = @"en.هشام.com"; var mapping = new IdnMapping(); // IDN = Internationalizing Domain Names var ascii = mapping.GetAscii(unicode); Console.WriteLine(ascii); string convertedBackToUnicode = mapping.GetUnicode(ascii); Console.WriteLine(convertedBackToUnicode);
بررسی C# 11
As discussed previously, this adds Razor support behind a new omnisharp.preview options flag. It works by downloading a platform-specific Razor Language Server executable (equivalent to the OmniSharp-Roslyn language server) and then running some VSCode-side logic that knows how to launch and interact with it.
با استفاده از AutoComplete TextBoxes میتوان گوشهای از زندگی روزمرهی کاربران یک برنامه را سادهتر کرد. مشکل مهم dropDownList ها دریک برنامهی وب، عدم امکان تایپ قسمتی از متن مورد نظر و سپس نمایان شدن آیتمهای متناظر با آن در اسرع وقت میباشد. همچنین با تعداد بالای آیتمها هم حجم صفحه و زمان بارگذاری را افزایش میدهند. راه حلهای بسیار زیادی برای حل این مشکل وجود دارند و یکی از آنها ایجاد AutoComplete TextBoxes است. پلاگینهای متعددی هم جهت پیاده سازی این قابلیت نوشته شدهاند منجمله jQuery Autocomplete . این پلاگین دیگر توسط نویسندهی اصلی آن نگهداری نمیشود اما توسط برنامه نویسی دیگر در github ادامه یافته است. در ادامه نحوهی استفاده از این افزونه را در ASP.NET Webforms بررسی خواهیم کرد.
الف) دریافت افزونه
لطفا به آدرس GitHub ذکر شده مراجعه نمائید.
سپس برای مثال پوشهی js را به پروژه افزوده و فایلهای jquery-1.5.min.js ، jquery.autocomplete.js ، jquery.autocomplete.css و indicator.gif را در آن کپی کنید. فایل indicator.gif به همراه مجموعهی دریافتی ارائه نمیشود و یک آیکن loading معروف میتواند باشد.
علاوه بر آن یک فایل جدید custom.js را نیز جهت تعاریف سفارشی خودمان اضافه خواهیم کرد.
ب) افزودن تعاریف افزونه به صفحه
در ذیل نحوهی افزودن فایلهای فوق به یک master page نمایش داده شده است.
در اینجا از قابلیتهای جدید ScriptManager (موجود در سرویس پک یک دات نت سه و نیم و یا دات نت چهار) جهت یکی کردن اسکریپتها کمک گرفته شده است. به این صورت تعداد رفت و برگشتها به سرور بهجای سه مورد (تعداد فایلهای اسکریپت مورد استفاده)، یک مورد (نهایی یکی شده) خواهد بود و همچنین حاصل نهایی به صورت خودکار به شکلی فشرده شده به مرورگر تحویل داده شده، سرآیندهای کش شدن اطلاعات به آن اضافه میگردد (که در سایر حالات متداول اینگونه نیست)؛ به علاوه Url نهایی آن هم بر اساس hash فایلها تولید میشود. یعنی اگر محتوای یکی از این فایلها تغییر کرد، چون Url نهایی تغییر میکند، دیگر لازم نیست نگران کش شدن و به روز نشدن اسکریپتها در سمت کاربر باشیم.
<%@ Master Language="C#" AutoEventWireup="true" CodeBehind="Site.master.cs" Inherits="AspNetjQueryAutocompleteTest.Site" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title></title>
<asp:PlaceHolder Runat="server">
<link href="<%= ResolveClientUrl("~/js/jquery.autocomplete.css")%>" rel="stylesheet" type="text/css" />
</asp:PlaceHolder>
<asp:ContentPlaceHolder ID="head" runat="server">
</asp:ContentPlaceHolder>
</head>
<body>
<form id="form1" runat="server">
<asp:ScriptManager ID="ScriptManager1" runat="server">
<CompositeScript>
<Scripts>
<asp:ScriptReference Path="~/js/jquery-1.5.min.js" />
<asp:ScriptReference Path="~/js/jquery.autocomplete.js" />
<asp:ScriptReference Path="~/js/custom.js" />
</Scripts>
</CompositeScript>
</asp:ScriptManager>
<div>
<asp:ContentPlaceHolder ID="ContentPlaceHolder1" runat="server">
</asp:ContentPlaceHolder>
</div>
</form>
</body>
</html>
ج) افزودن یک صفحهی ساده به برنامه
<%@ Page Title="" Language="C#" MasterPageFile="~/Site.Master" AutoEventWireup="true"
CodeBehind="default.aspx.cs" Inherits="AspNetjQueryAutocompleteTest._default" %>
<asp:Content ID="Content1" ContentPlaceHolderID="head" runat="server">
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="ContentPlaceHolder1" runat="server">
<asp:TextBox ID="txtShenas" runat="server" />
</asp:Content>
فرض کنید میخواهیم افزونهی ذکر شده را به TextBox استاندارد فوق اعمال کنیم. ID این TextBox در نهایت به شکل ContentPlaceHolder1_txtShenas رندر خواهد شد. البته در ASP.NET 4.0 با تنظیم ClientIDMode=Static میتوان ID انتخابی خود را به جای این ID خودکار درنظر گرفت و اعمال کرد. اهمیت این مساله در قسمت (ه) مشخص میگردد.
د) فراهم آوردن اطلاعات مورد استفاده توسط افزونهی AutoComplete به صورت پویا
مهمترین قسمت استفاده از این افزونه، تهیهی اطلاعاتی است که باید نمایش دهد. این اطلاعات باید به صورت فایلی که هر سطر آن حاوی یکی از آیتمهای مورد نظر است، تهیه گردد. برای این منظور میتوان از فایلهای ASHX یا همان Generic handlers استفاده کرد:
using System;
using System.Data.SqlClient;
using System.Text;
using System.Web;
namespace AspNetjQueryAutocompleteTest
{
public class AutoComplete : IHttpHandler
{
public void ProcessRequest(HttpContext context)
{
string prefixText = context.Request.QueryString["q"];
var sb = new StringBuilder();
using (var conn = new SqlConnection())
{
//todo: این مورد باید از فایل کانفیگ خوانده شود
conn.ConnectionString = "Data Source=(local);Initial Catalog=MyDB;Integrated Security = true";
using (var cmd = new SqlCommand())
{
cmd.CommandText = @" select Field1 ,Field2 from tblData where Field1 like @SearchText + '%' ";
cmd.Parameters.AddWithValue("@SearchText", prefixText);
cmd.Connection = conn;
conn.Open();
using (var sdr = cmd.ExecuteReader())
{
if (sdr != null)
while (sdr.Read())
{
string field1 = sdr.GetValue(0) == DBNull.Value ? string.Empty : sdr.GetValue(0).ToString().Trim();
string field2 = sdr.GetValue(1) == DBNull.Value ? string.Empty : sdr.GetValue(1).ToString().Trim();
sb.AppendLine(field1 + "|" + field2);
}
}
}
}
context.Response.Write(sb.ToString());
}
public bool IsReusable
{
get
{
return false;
}
}
}
}
در این مثال از ADO.NET کلاسیک استفاده شده است تا به عمد نحوهی تعریف پارامترها یکبار دیگر مرور گردند. اگر از LINQ to SQL یا Entity framework یا NHibernate و موارد مشابه استفاده میکنید، جای نگرانی نیست؛ زیرا کوئریهای SQL تولیدی توسط این ORMs به صورت پیش فرض از نوع پارامتری هستند (+).
در این مثال اطلاعات دو فیلد یک و دوی فرضی از جدولی با توجه به استفاده از like تعریف شده دریافت میگردد. به عبارتی همان متد StartsWith معروف LINQ بکارگرفته شده است.
به صورت خلاصه افزونه، کوئری استرینگ q را به این فایل ashx ارسال میکند. سپس کلیه آیتمهای شروع شده با مقدار دریافتی، از بانک اطلاعاتی دریافت شده و هر کدام قرارگرفته در یک سطر جدید بازگشت داده میشوند.
اگر دقت کرده باشید در قسمت sb.AppendLine ، با استفاده از "|" دو مقدار دریافتی از هم جدا شدهاند. عموما یک مقدار کفایت میکند (در 98 درصد موارد) ولی اگر نیاز بود تا توضیحاتی نیز نمایش داده شود از این روش نیز میتوان استفاده کرد. برای مثال یک مقدار خاص به همراه توضیحات آن به عنوان یک آیتم نمایش داده شده مد نظر است.
ه) اعمال نهایی افزونه به TextBox
در ادامه پیاده سازی فایل custom.js برای استفاده از امکانات فراهم شده در قسمتهای قبل ارائه گردیده است:
function formatItem(row) {
return row[0] + "<br/><span style='text-align:justify;' dir='rtl'>" + row[1] + "</span>";
}
$(document).ready(function () {
$("#ContentPlaceHolder1_txtShenas").autocomplete('AutoComplete.ashx', {
//Minimum number of characters a user has to type before the autocompleter activates
minChars: 0,
delay: 5,
//Only suggested values are valid
mustMatch: true,
//The number of items in the select box
max: 20,
//Fill the input while still selecting a value
autoFill: false,
//The comparison doesn't looks inside
matchContains: false,
formatItem: formatItem
});
});
پس از این مقدمات، اعمال افزونهی autocomplete به textBox ایی با id مساوی ContentPlaceHolder1_txtShenas ساده است. اطلاعات از فایل AutoComplete.ashx دریافت میگردد و تعدادی از خواص پیش فرض این افزونه در اینجا مقدار دهی شدهاند. لیست کامل آنها را در فایل jquery.autocomplete.js میتوان مشاهده کرد.
تنها نکتهی مهم آن استفاده از پارامتر اختیاری formatItem است. اگر در حین تهیهی AutoComplete.ashx خود تنها یک آیتم را در هر سطر نمایش میدهید و از "|" استفاده نکردهاید، نیازی به ذکر آن نیست. در این مثال ویژه، فیلد یک در یک سطر و فیلد دو در سطر دوم یک آیتم نمایش داده میشوند:
اعمال کنترل دسترسی پویا در پروژههای ASP.NET Core با استفاده از AuthorizationPolicyProvider سفارشی
در مطلب «سفارشی سازی ASP.NET Core Identity - قسمت پنجم - سیاستهای دسترسی پویا» به طور مفصل به قضیه کنترل دسترسی پویا در ASP.NET Core Identity پرداخته شدهاست؛ در این مطلب روش دیگری را بررسی خواهیم کرد.
مشخص میباشد که بدون وابستگی به روش خاصی، خیلی ساده میتوان به شکل زیر عمل کرد:
services.AddAuthorization(options => { options.AddPolicy("View Projects", policy => policy.RequireClaim(CustomClaimTypes.Permission, "projects.view")); });
[Authorize("View Projects")] public IActionResult Index(int siteId) { return View(); }
Using a large range of policies (for different room numbers or ages, for example), so it doesn’t make sense to add each individual authorization policy with an AuthorizationOptions.AddPolicy call.
کار با پیاده سازی واسط IAuthorizationPolicyProvider شروع میشود؛ یا شاید ارث بری از DefaultAuthorizationPolicyProvider رجیستر شدهی در سیستم DI و توسعه آن هم کافی باشد.
public class AuthorizationPolicyProvider : DefaultAuthorizationPolicyProvider { public AuthorizationPolicyProvider(IOptions<AuthorizationOptions> options) : base(options) { } public override Task<AuthorizationPolicy> GetPolicyAsync(string policyName) { if (!policyName.StartsWith(PermissionAuthorizeAttribute.PolicyPrefix, StringComparison.OrdinalIgnoreCase)) { return base.GetPolicyAsync(policyName); } var permissionNames = policyName.Substring(PermissionAuthorizeAttribute.PolicyPrefix.Length).Split(','); var policy = new AuthorizationPolicyBuilder() .RequireClaim(CustomClaimTypes.Permission, permissionNames) .Build(); return Task.FromResult(policy); } }
متد GetPolicyAsync موظف به یافتن و بازگشت یک Policy ثبت شده میباشد؛ با این حال میتوان با بازنویسی آن و با استفاده از وهلهای از AuthorizationPolicyBuilder، فرآیند تعریف سیاست درخواست شده را که احتمالا در تنظیمات آغازین پروژه تعریف نشده و پیشوند مدنظر را نیز دارد، خوکار کرد. در اینجا امکان ترکیب کردن چندین دسترسی را هم خواهیم داشت که برای این منظور میتوان دسترسیهای مختلف را به صورت comma separated به سیستم معرفی کرد.
نکتهی مهم در تکه کد بالا مربوط است به PolicyPrefix که با استفاده از آن مشخص کردهایم که برای هر سیاست درخواستی، این فرآیند را طی نکند و موجب اختلال در سیستم نشود.
پس از پیاده سازی واسط مطرح شده، لازم است این پیاده سازی جدید را به سیستم DI هم معرفی کنید:
services.AddSingleton<IAuthorizationPolicyProvider, AuthorizationPolicyProvider>();
خوب، تا اینجا فرآیند تعریف سیاستها به صورت خودکار انجام شد. در ادامه نیاز است با تعریف یک فیلتر Authorization، بتوان لیست دسترسیهای مورد نظر برای اکشنی خاص را نیز مشخص کرد تا در متد GetPolicyAsync فوق، کار ثبت خودکار سیاست دسترسی متناظر با آنرا توسط فراخوانی متد policyBuilder.RequireClaim، انجام دهد تا دیگر نیازی به تعریف دستی و جداگانهی آن، در کلاس آغازین برنامه نباشد. برای این منظور به شکل زیر عمل خواهیم کرد:
public class PermissionAuthorizeAttribute : AuthorizeAttribute { internal const string PolicyPrefix = "PERMISSION:"; /// <summary> /// Creates a new instance of <see cref="AuthorizeAttribute"/> class. /// </summary> /// <param name="permissions">A list of permissions to authorize</param> public PermissionAuthorizeAttribute(params string[] permissions) { Policy = $"{PolicyPrefix}{string.Join(",", permissions)}"; } }
همانطور که مشخص میباشد، رشته PERMISSION به عنوان پیشوند رشته تولیدی از لیست اسامی دسترسیها، استفاده شدهاست و در پراپرتی Policy قرار داده شدهاست. این بار برای کنترل دسترسی میتوان به شکل زیر عمل کرد:
[PermissionAuthorize(PermissionNames.Projects_View)] public IActionResult Get(FilteredQueryModel query) { //... } [PermissionAuthorize(PermissionNames.Projects_Create)] public IActionResult Post(ProjectModel model) { //... }
برای مثال در اولین فراخوانی فیلتر PermissionAuthorize فوق، مقدار ثابت PermissionNames.Projects_View به عنوان یک Policy جدید به متد GetPolicyAsync کلاس AuthorizationPolicyProvider سفارشی ما ارسال میشود. چون دارای پیشوند «:PERMISSION» است، مورد پردازش قرار گرفته و توسط متد policyBuilder.RequireClaim به صورت خودکار به سیستم معرفی و ثبت خواهد شد.
همچنین راه حل مطرح شده برای مدیریت دسترسیهای پویا، در gist به اشتراک گذاشته شده «موجودیتهای مرتبط با مدیریت دسترسیهای پویا» را نیز مد نظر قرار دهید.
export class AppProduct { constructor( public productId: number, public productName: string, public price: number, public isAvailable: boolean ) {} }
export class AppProduct { public productId: number; public productName: string; public price: number; public isAvailable: boolean; }
export interface AppProduct { productId: number; productName: string; price: number; isAvailable: boolean; }
برای آزمایش آن، سعی کنید چنین برنامهای را در ویندوز 8 اجرا کنید:
using System; namespace CultureAndNumbers { class Program { static void Main(string[] args) { var number = Convert.ToDecimal("12.32"); Console.WriteLine(number); } } }
خروجی آن به نحو ذیل است:
بله! چون در فرهنگ جاری سیستم، علامت ممیز دیگر . نیست، رشتهی 12.32 نیز بیمعنا است و قابل تبدیل به یک عدد دسیمال نخواهد بود.
همچنین باید دقت داشت تاثیر فرهنگ جاری سیستم بر روی متدهای Convert.ToDecimal و decimal.Parse یکسان است.
روشی برای آزمایش موقت فرهنگهای مختلف
برای اینکه بتوان فرهنگهای مختلف را به سادگی مورد آزمایش قرار داد، نیاز است خاصیت CurrentCulture ترد جاری برنامه را تغییر داد و پس از پایان کار، مجددا این ترد را به فرهنگ پیش از آزمایش تنظیم کرد. برای این منظور میتوان از پیاده سازی الگوی IDisposable کمک گرفت:
public class CultureScope : IDisposable { private readonly CultureInfo _originalCulture; public CultureScope(string culture) { _originalCulture = Thread.CurrentThread.CurrentCulture; Thread.CurrentThread.CurrentCulture = new CultureInfo(culture); } public void Dispose() { Thread.CurrentThread.CurrentCulture = _originalCulture; } }
using (var cultureScope = new CultureScope("fa-IR")) { Console.WriteLine(Thread.CurrentThread.CurrentCulture.NumberFormat.NumberDecimalSeparator); var number = decimal.Parse("12.32"); Console.WriteLine(number); }
تعیین صریح فرهنگ مورد استفاده
یک راه حل برای رفع این مشکل، قید صریح فرهنگ مورد استفاده است. برای مثال اگر اعداد در بانک اطلاعاتی به صورت 12.32 ثبت شدهاند، میتوان نوشت:
using (var cultureScope = new CultureScope("fa-IR")) { Console.WriteLine(Thread.CurrentThread.CurrentCulture.NumberFormat.NumberDecimalSeparator); var number = decimal.Parse("12.32", new CultureInfo("en")); Console.WriteLine(number); }
اما این روش هم قابل اطمینان نیست. زیرا کاربر میتواند در کنترل پنل سیستم، به سادگی علامت ممیز را مثلا به # تغییر دهد و در این حالت باز هم برنامه کرش خواهد کرد. راه حلی که برای این مساله در دات نت وجود دارد، فرهنگی است به نام Invariant که یک کپی فقط خواندنی از فرهنگ انگلیسی را به همراه دارد و در این حالت تنظیمات اختصاصی کاربر در کنترل پنل، ندید گرفته خواهند شد:
using (var cultureScope = new CultureScope("fa-IR")) { Console.WriteLine(Thread.CurrentThread.CurrentCulture.NumberFormat.NumberDecimalSeparator); var number = decimal.Parse("12.32", CultureInfo.InvariantCulture); Console.WriteLine(number); }
معرفی کتابخانهی DNTCaptcha.Core
return _apiProvider.CreateDNTCaptcha(new DNTCaptchaTagHelperHtmlAttributes { BackColor = "#f7f3f3", FontName = "Tahoma", FontSize = 18, ForeColor = "#111111", Language = Language.Persian, DisplayMode = DisplayMode.ShowDigits, Max = 99999, Min = 1001, UseNoise = true, });