در کتابخانهی Microsoft AspNetCore Identity میتوان با این کد، فیلد Email را منحصر بهفرد کرد:
//Program.cs file builder.Services.AddIdentity<User, Role>(options => { options.User.RequireUniqueEmail = true; }).AddEntityFrameworkStores<DatabaseContext>();
برنامه را اجرا و درخواستها را یکی یکی به سمت سرور ارسال میکنیم و اگر ایمیل تکراری باشد به ما خطا میده و میگه: "ایمیل تکراری است".
ولی مشکل اینجاست که کد بالا فیلد Email رو داخل دیتابیس منحصر بهفرد نمیکنه و فقط از سمت نرم افزار بررسی تکراری بودن ایمیل رو انجام میده. حالا اگه ما با استفاده از نرم افزارهای "تست برنامههای وب" مثل Apache JMeter تعداد زیادی درخواست را به سمت برنامهمان ارسال کنیم و بعد رکوردهای داخل جدول کاربران را نگاه کنیم، با وجود اینکه داخل نرم افزارمان پراپرتی Email را منحصر بهفرد کردهایم، ولی چندین رکورد، با یک ایمیل مشابه در داخل جدول User وجود خواهد داشت.
برای تست این سناریو، برنامه Apache JMeter را از این لینک دانلود میکنیم (در بخش Binaries فایل zip رو دانلود می کنیم).
نکته: داشتن jdk ورژن 8 به بالا پیش نیاز است. برای اینکه بدونید ورژن جاوای سیستمتون چنده، داخل cmd دستور java -version رو صادر کنید.
اگه تمایل به نصب، یا به روز رسانی jdk را داشتید، میتونید از این لینک استفاده کنید و بسته به سیستم عاملتون، یکی از تبهای Windows, macOS یا Linux رو انتخاب کنید و فایل مورد نظر رو دانلود کنید (برای Windows فایل x64 Compressed Archive رو دانلود و نصب میکنیم).
حالا فایل دانلود شده JMeter رو استخراج میکنیم، وارد پوشهی bin میشیم و فایل jmeter.bat رو اجرا میکنیم تا برنامهی JMeter اجرا بشه.
قبل از اینکه وارد برنامه JMeter بشیم، کدهای برنامه رو بررسی میکنیم.
موجودیت کاربر:
public class User : IdentityUser<int>;
ویوو مدل ساخت کاربر:
public class UserViewModel { public string UserName { get; set; } = null!; public string Email { get; set; } = null!; public string Password { get; set; } = null!; }
کنترلر ساخت کاربر:
[ApiController] [Route("/api/[controller]")] public class UserController(UserManager<User> userManager) : Controller { [HttpPost] public async Task<IActionResult> Add(UserViewModel model) { var user = new User { UserName = model.UserName, Email = model.Email }; var result = await userManager.CreateAsync(user, model.Password); if (result.Succeeded) { return Ok(); } return BadRequest(result.Errors); } }
حالا وارد برنامه JMeter میشیم و اولین کاری که باید انجام بدیم این است که مشخص کنیم چند درخواست را در چند ثانیه قرار است ارسال کنیم. برای اینکار در برنامه JMeter روی TestPlan کلیک راست میکنیم و بعد:
Add -> Threads (Users) -> Thread Group
حالا باید بر روی Thread Group کلیک کنیم و بعد در بخش Number of threads (users) تعداد درخواستهایی را که قرار است به سمت سرور ارسال کنیم، مشخص کنیم؛ برای مثال عدد 100.
گزینه Ramp-up period (seconds) برای اینه که مشخص کنیم این 100 درخواست قرار است در چند ثانیه ارسال شوند که آن را روی 0.1 ثانیه قرار میدهیم تا درخواستها را با سرعت بسیار زیاد ارسال کند.
الان باید مشخص کنیم چه دیتایی قرار است به سمت سرور ارسال شود:
برای اینکار باید یک Http Request اضافه کنیم. برای این منظور روی Thread Group که از قبل ایجاد کردیم، کلیک راست میکنیم و بعد:
Add -> Sampler -> Http Request
حالا روی Http Request کلیک میکنیم و متد ارسال درخواست رو که روی Get هست، به Post تغییر میدیم و بعد Path رو هم به آدرسی که قراره دیتا رو بهش ارسال کنیم، تغییر میدهیم:
https://localhost:7091/api/User
حالا پایینتر Body Data رو انتخاب میکنیم و دیتایی رو که قراره به سمت سرور ارسال کنیم، در قالب Json وارد میکنیم:
{ "UserName": "payam${__Random(1000, 9999999)}", "Email": "payam@gmail.com", "Password": "123456aA@" }
چون بخش UserName در پایگاه داده منحصر بهفرد است، با این دستور:
${__Random(1000, 9999999)}
یک عدد Random رو به UserName اضافه میکنیم که دچار خطا نشیم.
حالا فقط باید یک Header رو هم به درخواستمون اضافه کنیم، برای اینکار روی Http Request که از قبل ایجاد کردیم، کلیک راست میکنیم و بعد:
Add -> Config Element -> Http Header Manager
حالا روی دکمهی Add در پایین صفحه کلیک میکنیم و این Header رو اضافه میکنیم:
Name: Content-Type Value: application/json
همچنین میتونیم یک View result رو هم اضافه کنیم تا وضعیت تمامی درخواستهای ارسال شده رو مشاهده کنیم. برای اینکار روی Http Request که از قبل ایجاد کردیم، کلیک راست میکنیم و بعد:
Add -> Listener -> View Results Tree
فایل Backup، برای اینکه مراحل بالا رو سریعتر انجام بدید:
File -> Open
حالا بر روی دکمهی سبز رنگ Play در Toolbar بالا کلیک میکنیم تا تمامی درخواست ها را به سمت سرور ارسال کنه و همچنین میتونیم از طریق View result tree ببینیم که چند درخواست موفقیت آمیز و چند درخواست ناموفق انجام شدهاست.
حالا اگر وارد پایگاه داده بشیم، میبینیم که چندین رکورد، با Email یکسان، در جدول User وجود داره:
در حالیکه ایمیل رو در تنظیمات کتابخانه Microsoft AspNetCore Identity به صورت Unique تعریف کردهایم:
//Program.cs file builder.Services.AddIdentity<User, Role>(options => { options.User.RequireUniqueEmail = true; }).AddEntityFrameworkStores<DatabaseContext>();
دلیل این مشکل این است که درخواستها در قالب یک صف، یک به یک اجرا نمیشوند؛ بلکه به صورت همزمان فریم ورک ASP.NET Core برای بالا بردن سرعت اجرای درخواستها از تمامی Thread هایی که در اختیارش هست استفاده میکند و در چندین Thread جداگانه، درخواستهایی رو به کنترلر User میفرسته و در نتیجه، در یک زمان مشابه، چندین درخواست ارسال میشه که آیا یک ایمیل برای مثال با مقدار payam@yahoo.com وجود داره یا خیر و در تمامی درخواستها چون همزمان انجام شده، جواب خیر است. یعنی ایمیل تکراری با آن مقدار، در پایگاه داده وجود ندارد و تمامی درخواستهایی که همزمان به سرور رسیدهاند، کاربر جدید را با ایمیل مشابهی ایجاد میکنند.
این مشکل را میتوان حتی در سایتهای فروش بلیط نیز پیدا کرد؛ یعنی چند نفر یک صندلی را رزرو کردهاند و همزمان وارد درگاه پرداخت شده و هزینهایی را برای آن پرداخت میکنند. اگر آن درخواستها را وارد صف نکنیم، امکان دارد که یک صندلی را به چند نفر بفروشیم. این سناریو برای زمانی است که در پایگاه داده، فیلدها را Unique تعریف نکرده باشیم. هر چند که اگر فیلدها را نیز Unique تعریف کرده باشیم تا یک صندلی را به چند نفر نفروشیم، در آن صورت هم برنامه دچار خطای 500 خواهد شد. پس بهتر است که حتی در زمانهایی هم که فیلدها را Unique تعریف میکنیم، باز هم از ورود چند درخواست همزمان به اکشن رزرو صندلی جلوگیری کنیم.
راه حل
برای حل این مشکل میتوان از Lock statement استفاده کرد که این راه حل نیز یک مشکل دارد که در ادامه به آن اشاره خواهم کرد.
Lock statement به ما این امکان رو میده تا اگر بخشی از کد ما در یک Thread در حال اجرا شدن است، Thread دیگری به آن بخش از کد، دسترسی نداشته باشد و منتظر بماند تا آن Thread کارش با کد ما تموم شود و بعد Thread جدید بتونه کد مارو اجرا کنه.
نحوه استفاده از Lock statement هم بسیار سادهاست:
public class TestClass { private static readonly object _lock1 = new(); public void Method1() { lock (_lock1) { // Body } } }
حالا باید کدهای خودمون رو در بخش Body اضافه کنیم تا دیگر چندین Thread به صورت همزمان، کدهای ما رو اجرا نکنند.
اما یک مشکل وجود داره و آن این است که ما نمیتوانیم در Lock statement، از کلمه کلیدی await استفاده کنیم؛ در حالیکه برای ساخت User جدید باید از await استفاده کنیم:
var result = await userManager.CreateAsync(user, model.Password);
برای حل این مشکل میتوان از کلاس SemaphoreSlim بجای کلمهی کلیدی lock استفاده کرد:
[ApiController] [Route("/api/[controller]")] public class UserController(UserManager<User> userManager) : Controller { private static readonly SemaphoreSlim Semaphore = new (initialCount: 1, maxCount: 1); [HttpPost] public async Task<IActionResult> Add(UserViewModel model) { var user = new User { UserName = model.UserName, Email = model.Email }; // Acquire the semaphore await Semaphore.WaitAsync(); try { // Perform user creation var result = await userManager.CreateAsync(user, model.Password); if (result.Succeeded) { return Ok(); } return BadRequest(result.Errors); } finally { // Release the semaphore Semaphore.Release(); } } }
این کلاس نیز مانند lock عمل میکند، ولی تواناییهای بیشتری را در اختیار ما قرار میدهد؛ برای مثال میتوان تعیین کرد که همزمان چند ترد میتوانند به این کد دسترسی داشته باشند؛ در حالیکه در lock statement فقط یک Thread میتوانست به کد دسترسی داشته باشد. مزیت دیگر کلاس SemaphoreSlim این است که میتوان برای اجرای کدمان Timeout در نظر گرفت تا از بلاک شدن نامحدود Thread جلوگیری کنیم.
با فراخوانی await semaphore.WaitAsync، دسترسی کد ما توسط سایر Thread ها محدود و با فراخوانی Release، کد ما توسط سایر Thread ها قابل دسترسی میشود.
مشکل قفل کردن Thread ها
هنگام قفل کردن Thread ها، مشکلی وجود دارد و آن این است که اگر برنامهی ما روی چندین سرور مختلف اجرا شود، این روش جوابگو نخواهد بود؛ چون قفل کردن Thread روی یک سرور تاثیری در سایر سرورها جهت محدود کردن دسترسی به کد ما ندارد. اما به صورت کلی میتوان از این روش برای بخشهایی خاص از برنامههایمان استفاده کنیم.
پیاده سازی با کمک الگوی AOP
برای اینکه کارمون راحت تر بشه، میتونیم کدهای بالا رو به یک Attribute انتقال بدیم و از اون Attribute در بالای اکشنهامون استفاده کنیم تا کل عملیات اکشنهامونو رو در یک Thread قفل کنیم:
[AttributeUsage(AttributeTargets.Method)] public class SemaphoreLockAttribute : Attribute, IAsyncActionFilter { private static readonly SemaphoreSlim Semaphore = new (1, 1); public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) { // Acquire the semaphore await Semaphore.WaitAsync(); try { // Proceed with the action await next(); } finally { // Release the semaphore Semaphore.Release(); } } }
حالا میتونیم این Attribute را برای هر اکشنی استفاده کنیم:
[HttpPost] [SemaphoreLock] public async Task<IActionResult> Add(UserViewModel model) { var user = new User { UserName = model.UserName, Email = model.Email }; var result = await userManager.CreateAsync(user, model.Password); if (result.Succeeded) { return Ok(); } return BadRequest(result.Errors); }
پیاده سازی PWA در Angular
در این مثال به کمک MVC5، یک کپچای ساده و قابل فهم را تولید و استفاده خواهیم کرد. این نوشته بر اساس این مقاله ایجاد شده و جزئیات زیادی برای درک افراد مبتدی به آن افزوده شده است که امیدوارم راهنمای مفیدی برای علاقمندان باشد.
با کلیک راست بر روی پوشه کنترلر، یک کنترلر به منظور ایجاد کپچا بسازید و اکشن متد زیر را در آن کنترلر ایجاد کنید:
public class CaptchaController : Controller { public ActionResult CaptchaImage(string prefix, bool noisy = true) { var rand = new Random((int)DateTime.Now.Ticks); //generate new question int a = rand.Next(10, 99); int b = rand.Next(0, 9); var captcha = string.Format("{0} + {1} = ?", a, b); //store answer Session["Captcha" + prefix] = a + b; //image stream FileContentResult img = null; using (var mem = new MemoryStream()) using (var bmp = new Bitmap(130, 30)) using (var gfx = Graphics.FromImage((Image)bmp)) { gfx.TextRenderingHint = TextRenderingHint.ClearTypeGridFit; gfx.SmoothingMode = SmoothingMode.AntiAlias; gfx.FillRectangle(Brushes.White, new Rectangle(0, 0, bmp.Width, bmp.Height)); //add noise if (noisy) { int i, r, x, y; var pen = new Pen(Color.Yellow); for (i = 1; i < 10; i++) { pen.Color = Color.FromArgb( (rand.Next(0, 255)), (rand.Next(0, 255)), (rand.Next(0, 255))); r = rand.Next(0, (130 / 3)); x = rand.Next(0, 130); y = rand.Next(0, 30); gfx.DrawEllipse(pen, x - r, y - r, r, r); } } //add question gfx.DrawString(captcha, new Font("Tahoma", 15), Brushes.Gray, 2, 3); //render as Jpeg bmp.Save(mem, System.Drawing.Imaging.ImageFormat.Jpeg); img = this.File(mem.GetBuffer(), "image/Jpeg"); } return img; }
همانطور که از کد فوق پیداست، دو مقدار a و b، به شکل اتفاقی ایجاد میشوند و حاصل جمع آنها در یک Session نگهداری خواهد شد. سپس تصویری بر اساس تصویر a+b ایجاد میشود (مثل 3+4). این تصویر خروجی این اکشن متد است. به سادگی میتوانید این اکشن را بر اساس خواسته خود اصلاح کنید؛ مثلا به جای حاصل جمع دو عدد، از کاربرد چند حرف یا عدد که بصورت اتفاقی تولید کردهاید، استفاده نمائید.
فرض کنید میخواهیم کپچا را هنگام ثبت نام استفاده کنیم.
در فایل AccountViewModels.cs در پوشه مدلها در کلاس RegisterViewModel خاصیت زیر را اضافه کنید:
[Required(ErrorMessage = "لطفا {0} را وارد کنید")] [Display(Name = "حاصل جمع")] public string Captcha { get; set; }
حالا در پوشه View/Account به فایل Register.Cshtml خاصیت فوق را اضافه کنید:
<div class="form-group"> <input type="button" value="" id="refresh" /> @Html.LabelFor(model => model.Captcha) <img alt="Captcha" id="imgcpatcha" src="@Url.Action("CaptchaImage","Captcha")" style="" /> </div>
وظیفه این بخش، نمایش کپچاست. تگ img دارای آدرسی است که توسط اکشن متدی که در ابتدای این مقاله ایجاد نمودهایم تولید میشود. این آدرس تصویر کپچاست. یک دکمه هم با شناسه refresh برای به روز رسانی مجدد تصویر در نظر گرفتهایم.
حالا کد ایجکسی برای آپدیت کپچا توسط دکمه refresh را به شکل زیر بنویسید (من در پایین ویوی Register، اسکریپت زیر را قرار دادم):
<script type="text/javascript"> $(function () { $('#refresh').click(function () { $.ajax({ url: '@Url.Action("CaptchaImage","Captcha")', type: "GET", data: null }) .done(function (functionResult) { $("#imgcpatcha").attr("src", "/Captcha/CaptchaImage?" + functionResult); }); }); }); </script>
آنچه در url نوشته شده است، شاید اصولیترین شکل فراخوانی یک اکشن متد باشد. این اکشن در ابتدای مقاله تحت کنترلری به نام Captcha معرفی شده بود و خروجی آن آدرس یک فایل تصویری است. نوع ارتباط، Get است و هیچ اطلاعاتی به اکشن متد فرستاده نمیشود، اما اکشن متد ما آدرسی را به ما برمیگرداند که تحت نام FunctionResult آن را دریافت کرده و به کمک کد جی کوئری، مقدارش را در ویژگی src تصویر موجود در صفحه جاری جایگزین میکنیم. دقت کنید که برای دسترسی به تصویر، لازم است جایگزینی آدرس، در ویژگی src به شکل فوق صورت پذیرد.*
تنها کار باقیمانده اضافه کردن کد زیر به ابتدای اکشن متد Register درون کنترلر Account است.
if (Session["Captcha"] == null || Session["Captcha"].ToString() != model.Captcha) { ModelState.AddModelError("Captcha", "مجموع اشتباه است"); }
واضح است که اینکار پیش از شرط if(ModelState.IsValidate) صورت میگیرد و وظیفه شرط فوق، بررسی ِ برابریِ مقدار Session تولید شده در اکشن CaptchaImage (ابتدای این مقاله) با مقدار ورودی کاربر است. (مقداری که از طریق خاصیت تولیدی خودمان به آن دسترسی داریم) . بدیهیاست اگر این دو مقدار نابرابر باشند، یک خطا به ModelState اضافه میشود و شرط ModelState.IsValid که در اولین خط بعد از کد فوق وجود دارد، برقرار نخواهد بود و پیغام خطا در صفحه ثبت نام نمایش داده خواهد شد.
تصویر زیر نمونهی نتیجهای است که حاصل خواهد شد :
* اصلاح : دقت کنید بدون استفاده از ایجکس هم میتوانید تصویر فوق را آپدیت کنید:
$('#refresh').click(function () { var d = new Date(); $("#imgcpatcha").attr("src", "Captcha/CaptchaImage?" + d.getTime()); });
رویداد کلیک را با کد فوق جایگزین کنید؛ دو نکته در اینجا وجود دارد :
یک. استفاده از زمان در انتهای آدرس به خاطر مشکلاتیست که فایرفاکس یا IE با اینگونه آپدیتهای تصویری دارند. این دو مرورگر (بر خلاف کروم) تصاویر را نگهداری میکنند و آپدیت به روش فوق به مشکل برخورد میکند مگر آنکه آدرس را به کمک اضافه کردن زمان آپدیت کنید تا مرورگر متوجه داستان شود
دو. همانطور که میبینید آدرس تصویر در حقیقت خروجی یک اکشن است. پس نیازی نیست هر بار این اکشن را به کمک ایجکس صدا بزنیم و روش فوق در مرورگرهای مختلف جواب خواهد داد.
سایتهای بسیاری خودشان را با این الگو وفق دادهاند. برای نمونه Twitter و Github از مفهوم pjax استفادهی وسیعی دارند. برای نمونه، layout یا master page یک سایت را درنظر بگیرید. به ازای مرور هر صفحه، یکبار باید تمام قسمتهای تکراری layout از سرور بارگذاری شوند. توسط pjax به سرور اعلام میکنیم، ما تنها نیاز به body صفحات را داریم و نه کل صفحه را. همچنین اگر مرورگر از جاوا اسکریپت استفاده نمیکند، لطفا کل صفحه را همانند گذشته بازگشت بده. به علاوه مسایل سمت کلاینت مانند تغییر آدرس مرورگر و تغییر عنوان صفحه نیز به صورت خودکار مدیریت شوند. این تکنیک را دقیقا در حین مرور مخزنهای کد Github میتوانید مشاهده کنید. فقط قسمتی که لیست فایلها را ارائه میدهد، از سرور دریافت میگردد و نه کل صفحه.
بکارگیری pjax در ASP.NET MVC
مطابق توضیحاتی که ارائه شد، برای پیاده سازی سازی pjax نیاز به دو فایل layout داریم. یکی برای حالت ajax ایی و دیگری برای حالت بارگذاری کامل صفحه. حالت ajax ایی آن تنها از رندرکردن body پشتیبانی میکند؛ و نه ارائه تمام قسمتهای صفحه مانند هدر، فوتر، منوها و غیره. بنابراین خواهیم داشت:
الف) تعریف فایلهای layout سازگار با pjax
ابتدا یک فایل جدید را به نام _PjaxLayout.cshtml به پوشهی Shared اضافه کنید؛ با این محتوا:
<title>@ViewBag.Title</title> @RenderBody()
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width" /> <title>@ViewBag.Title</title> <link href="~/Content/Site.css" rel="stylesheet" /> <script src="~/Scripts/jquery-1.8.2.min.js"></script> <script src="~/Scripts/jquery.pjax.js"></script> <script type="text/javascript"> $(function () { $(document).pjax('a[withpjax]', '#pjaxContainer', { timeout: 5000 }); }); </script> </head> <body> <div>Main layout ...</div> <div id="pjaxContainer"> @RenderBody() </div> </body> </html>
فایل layout اصلی سایت همانند قبل است. فقط RenderBody آن داخل یک div با id مساوی pjaxContainer قرار گرفته و از آن در فراخوانی افزونهی pjax استفاده شدهاست. همانطور که ملاحظه میکنید، مطابق تنظیمات ابتدای هدر layout، فقط لینکهایی که دارای ویژگی withpjax باشند، توسط pjax پردازش خواهند شد.
ب) تغییر فایل ViewStart برنامه
در فایل ViewStart، کار مقدار دهی layout پیش فرض صورت گرفتهاست. اکنون نیاز است این فایل را جهت معرفی layout دوم تعریف شده مخصوص pjax، اندکی ویرایش کنیم:
@{ if (Request.Headers["X-PJAX"] != null) { Layout = "~/Views/Shared/_PjaxLayout.cshtml"; } else { Layout = "~/Views/Shared/_Layout.cshtml"; } }
ج) آزمایش برنامه
using System.Web.Mvc; namespace PajxMvcApp.Controllers { public class HomeController : Controller { public ActionResult Index() { return View(); } public ActionResult About() { return View(); } } }
سپس View متد Index را به نحو ذیل تغییر دهید:
@{ ViewBag.Title = "Index"; } <h2>Index</h2> @Html.ActionLink(linkText: "About", actionName:"About", routeValues: null, controllerName:"Home", htmlAttributes: new { withpjax = "with-pjax"})
اکنون اگر برنامه را اجرا کنید، چنین خروجی را در برگهی network آن مشاهده خواهید کرد:
همانطور که ملاحظه میکنید، با کلیک بر روی لینک About، یک درخواست pjax ایی به سرور ارسال شدهاست؛ به همراه هدرهای ویژه آن. هنوز قسمتهای اصلی layout سایت مشخص هستند (و مجددا از سرور درخواست نشدهاند). آدرس صفحه عوض شدهاست. به علاوه قسمت body آن تنها تغییر کردهاست.
این مثال را از اینجا نیز میتوانید دریافت کنید
PajxMvcApp.zip
برای مطالعه بیشتر
A Faster Web With PJAX
Favour PJAX over dynamically loaded partial views
What is PJAX and why
Pjax.Mvc
Using pjax with ASP.Net MVC3
Getting started with PJAX with ASP.NET MVC
ASP.NET MVC with PAjax or PushState/ReplaceState and Ajax
WAS یا Windows Process Activation Service
پیکربندی مدیریتی در WAS
- Global configuration information
- Protocol configuration information for both HTTP and non-HTTP protocols
- Application pool configuration, such as the process account information
- Site configuration, such as bindings and applications
- Application configuration, such as the enabled protocols and the application pools to which the applications belong
مدیریت پروسهها Process Managment
نکته: از آنجایی که WAS هم پروسههای http و هم non-http را مدیریت میکند، پس میتوانید از یک applicatio pool برای چندین protocol استفاده کنید. به عنوان مثال شما یکی سرویس XML دارید که میتوانید از آن برای سرویس دهی به پروتوکلهای Http و net.tcp بهره بگیرید.
ماژولها در IIS
ماژولهای کد ماشین یا native
جدول ماژولهای HTTP
نام ماژول | توضیحات | نام فایل منبع |
CustomErrorModule | موقعی که هنگام response، کد خطایی تولید میگردد، پیام خطا را پیکربندی و سپس ارسال میکند. | Inetsrv\Custerr.dll |
HttpRedirectionModule | تنظمیات redirection برای درخواستهای http را در دسترس قرار میدهد. | Inetsrv\Redirect.dll |
ProtocolSupportModule | انجام عملیات مربوط به پروتوکلها بر عهده این ماژول است؛ مثل تنظیم کردن قسمت هدر برای response. | Inetsrv\Protsup.dll |
RequestFilteringModule | این ماژول از IIS 7.5 به بعد اضافه شد. درخواستها را فیلتر میکند تا پروتوکل و رفتار محتوا را کنترل کند. | Inetsrv\modrqflt.dll |
WebDAVModule | این ماژول از IIS 7.5 به بعد اضافه شد. امنیت بیشتر در هنگام انتشار محتوا روی HTTP SSL | Inetsrv\WebDAV.dll |
ماژولهای امنیتی
نام ماژول | توضیحات | نام فایل منبع |
AnonymousAuthenticationModule | موقعی که هیچ کدام از عملیات authentication با موفقیت روبرو نشود، عملیات Anonymous authentication انجام میشود. | Inetsrv\Authanon.dll |
BasicAuthenticationModule | عمل ساده و اساسی authentication را انجام میدهد. | Inetsrv\Authbas.dll |
CertificateMappingAuthenticationModule | انجام عمل Certificate Mapping authentication در Active Directory | Inetsrv\Authcert.dll |
DigestAuthenticationModule | Digest authentication | Inetsrv\Authmd5.dll |
IISCertificateMappingAuthenticationModule | همان Certificate Mapping authentication ولی اینبار با IIS Certificate . | Inetsrv\Authmap.dll |
RequestFilteringModule | عملیات اسکن URL از قبیل نام صفحات و دایرکتوریها ، توع verb و یا کاراکترهای مشکوک و خطرآفرین | Inetsrv\Modrqflt.dll |
UrlAuthorizationModule | عمل URL authorization | Inetsrv\Urlauthz.dll |
WindowsAuthenticationModule | عمل NTLM integrated authentication | Inetsrv\Authsspi.dll |
IpRestrictionModule | محدود کردن IPهای نسخه 4 لیست شده در IP Security در قسمت پیکربندی | Inetsrv\iprestr.dll |
ماژولهای محتوا
نام ماژول | توضیحات | نام فایل منبع |
CgiModule | ایجاد پردازشهای (Common Gateway Interface (CGI به منظور ایجاد خروجی response | Inetsrv\Cgi.dll |
DefaultDocumentModule | تلاش برای ساخت یک سند پیش فرض برای درخواست هایی که دایرکتوری والد ارسال میشود | Inetsrv\Defdoc.dll |
DirectoryListingModule | لیست کردن محتوای یک دایرکتوری | Inetsrv\dirlist.dll |
IsapiModule | میزبانی فایل های ISAPI | Inetsrv\Isapi.dll |
IsapiFilterModule | پشتیبانی از فیلتر های ISAPI | Inetsrv\Filter.dll |
ServerSideIncludeModule | پردازش کدهای include شده سمت سرور | Inetsrv\Iis_ssi.dll |
StaticFileModule | ارائه فایلهای ایستا | Inetsrv\Static.dll |
FastCgiModule | پشتبانی از CGI | Inetsrv\iisfcgi.dll |
ماژولهای فشرده سازی
DynamicCompressionModule | فشرده سازی پاسخ response با gzip | Inetsrv\Compdyn.dll |
StaticCompressionModule | فشرده سازی محتوای ایستا | Inetsrv\Compstat.dll |
ماژولهای کش کردن
FileCacheModule | تهیه کش در مد کاربری برای فایلها. | Inetsrv\Cachfile.dll |
HTTPCacheModule | تهیه کش مد کاربری و مد کرنل برای http.sys | Inetsrv\Cachhttp.dll |
TokenCacheModule | تهیه کش مد کاربری بر اساس جفت نام کاربری و یک token که توسط Windows user principals تولید شده است. | Inetsrv\Cachtokn.dll |
UriCacheModule | تهیه یک کش مد کاربری از اطلاعات URL | Inetsrv\Cachuri.dll |
ماژولهای عیب یابی و لاگ کردن
CustomLoggingModule | بارگزاری ماژولهای خصوصی سازی شده جهت لاگ کردن | Inetsrv\Logcust.dll |
FailedRequestsTracingModule | برای ردیابی درخواستهای ناموفق | Inetsrv\Iisfreb.dll |
HttpLoggingModule | دریافت اطلاعات و پردازش وضعیت http.sys برای لاگ کردن | Inetsrv\Loghttp.dll |
RequestMonitorModule | ردیابی درخواست هایی که در حال حاضر در پروسههای کارگر در حال اجرا هستند و گزارش اطلاعاتی در مورد وضعیت اجرا و کنترل رابط برنامه نویسی کاربردی. | Inetsrv\Iisreqs.dll |
TracingModule | گزارش رخدادهای Microsoft Event Tracing for Windows یا به اختصار ETW | Inetsrv\Iisetw.dll |
ماژولهای مدیریتی و نظارتی بر کل ماژولها
ManagedEngine | مدیرتی بر ماژولهای غیر native که در پایین قرار دارند. | Microsoft.NET\Framework\v2.0.50727\webengine.dll |
ConfigurationValidationModule | اعتبارسنجی خطاها، مثل موقعی که برنامه در حالت integrated اجرا شده و ماژولها یا هندلرها در system.web تعریف شدهاند. | Inetsrv\validcfg.dll |
ماژول | توضیحات | منبع |
AnonymousIdentification | مدیریت منابع تعیین هویت برای کاربران ناشناس مانند asp.net profile | System.Web.Security.AnonymousIdentificationModule |
DefaultAuthentication | اطمینان از وجود شی Authentication در context مربوطه | System.Web.Security.DefaultAuthenticationModule |
FileAuthorization | تایید هویت کاربر برای دسترسی به فایل درخواست | System.Web.Security.FileAuthorizationModule |
FormsAuthentication | با این قسمت که باید کاملا آشنا باشید؛ برای تایید هویت کاربر | System.Web.Security.FormsAuthenticationModule |
OutputCache | مدیریت کش | System.Web.Caching.OutputCacheModule |
Profile | مدیریت پروفایل کاربران که تنظیماتش را در یک منبع دادهای چون دیتابیس ذخیره و بازیابی میکند. | System.Web.Profile.ProfileModule |
RoleManager | مدیریت نقش و سمت کاربران | System.Web.Security.RoleManagerModule |
Session | مدیریت session ها | System.Web.SessionState.SessionStateModule |
UrlAuthorization | آیا کاربر جاری حق دسترسی به URL درخواست را دارد؟ | System.Web.Security.UrlAuthorizationModule |
UrlMappingsModule | تبدیل یک Url واقعی به یک Url کاربرپسند | System.Web.UrlMappingsModule |
WindowsAuthentication | شناسایی و تایید و هویت یک کاربر بر اساس لاگین او به ویندوز | System.Web.Security.WindowsAuthenticationModule |
<add key="VisualStudioDesignTime:Enabled" value="true" />
Tools -> Code Snippets Manager (Ctrl+K,Ctrl+B)
در ویژوال استودیو 2010 دو نوع snippet وجود دارد :
1- Expansion snippets : که در محل کرسر (Cursor) اضافه میشوند . مثل cw و enum که به ترتیب دستور writeLine و ساختار یک enum را ایجاد میکنند .
2- SurroundsWith snippets : که میتوانند یک تکه کد انتخاب شده را در بر بگیرند مثل for و یا do که کد انتخاب شده را در یک حلقه for و do-while محصور میکنند .
نکته ای که باید توجه داشت این است که یک snippet میتواند از هر دو نوع باشد . برای مثال for و do و یا if ، در صورتی که کدی انتخاب شده باشد آن را محصور میکنند و گرنه ساختار خالی مرتبط را در محل cursor اضافه میکنند .
همانطور که در ابتدا هم ذکر شد ، علاوه بر snippetهای آمادهی موجود ، توسعه دهنده میتواند قطعه کدهایی را خود ایجاد کرده و مورد استفاده قرار دهد .
در اینجا یک expansion snippet خواهیم ساخت تا کار اضافه کردن بلاک try-catch-finally را برای ما انجام دهد .
- ابتدا یک فایل xml به پروژه اضافه میکنیم و آنرا TryCatchFinally.snippet مینامیم . توجه کنید که نام فایل باید به .snippet ختم شود .
- فایل را باز و درون آن راست کلیک کرده و گزینه Insert snippet > Snippet را انتخاب میکنیم . با اینکار یک قالب پایه snippet ( که یک ساختار xml ) است به فایل اضافه میشود . هر فایل snippet از دو بخش اصلی header و snippet تشکیل شده که بخش header اطلاعاتی کلی درباره قطعه کد را نگهداری میکند و بخش snippet مربوط به تعریف محتوای قطعه کد است .
<codesnippet format="1.0.0" xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet"> <header> <title>title</title> <author>author</author> <shortcut>shortcut</shortcut> <description>description</description> <snippettypes> <snippettype>SurroundsWith</snippettype> <snippettype>Expansion</snippettype> </snippettypes> </header> <snippet> <declarations> <literal> <id>name</id> <default>value</default> </literal> </declarations> <code language="XML"> <!--[CDATA[<test--> <name>$name$</name> $selected$ $end$]]> </code> </snippet> </codesnippet>
- قالب پیش فرض شامل هر دو نوع snippet است .
<codesnippet format="1.0.0" xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet"> <header> ... <snippettypes> <snippettype>SurroundsWith</snippettype> <snippettype>Expansion</snippettype> </snippettypes> </header> ... </codesnippet>
از آنجا که قصد داریم یک Expansion snippet بسازیم پس تگ SurroundsWith را حذف میکنیم .
<snippettypes> <snippettype>Expansion</snippettype> </snippettypes>
- در بخش header مقدار تگ Title را به “Try Catch Finally”و مقدار تگ Shortcut را به “trycf” و Description را به “Adds a try-catch-finally block ” تغییر میدهیم . Title عنوان snippet است و وجود آن ضروری است . اضافه کردن shortcut اختیاری است و به عنوان یک متن میانبر برای اضافه کردن snippet استفاده میشود .
<Header> <Title>Try Catch Finally</Title> <Author>mohsen.d</Author> <Shortcut>trycf</Shortcut> <Description>Adds a try-catch-finally block</Description>
- تگ Literal برای تعریف جایگزین برای بخشی از کد درون snippet که احتمال دارد پس از اضافه شدن ، توسط برنامه نویس و یا در صورت استفاده از function توسط خود ویژوال استودیو تغییر کند استفاده میشود . در قطعه کد try-catch-finally ، ما میخواهیم به کاربر اجازه بدهیم که نوع استثنائی را که catch میشود تغییر دهد .
تگ id نامی برای بخش قابل ویرایش تعریف میکند ( که از آن در ادامه در تعریف خود قطعه کد استفاده میکنیم ) . آنرا به “ExceptionName” تغییر میدهیم . تگ default هم مقدار پیش فرضی را برای آن بخش مشخص میکند . ما میخواهیم تمام استثناها را Catch کنیم پس مقدار پیش فرض را برابر "Exception" قرار میدهیم .
..... <declarations> <literal> <id>ExceptionName</id> <default>Exception</default> </literal> </declarations> ...
- و در تگ Code ، خود قطعه کدی که ویژوال استودیو باید آن را اضافه کند ، تعریف میشود . مقدار مشخصه Language آن را به CSharp تغییر میدهیم و محتویات داخل آنرا به شکل زیر اضافه میکنیم .
<code language="CSharp"> <!--[CDATA[ try { $end$ } catch($ExceptionName$) { } finally { } ]]--> </code>
به نحوه استفاده از ExceptionName که در قسمت Literal تعریف کردیم توجه کنید . عبارت $end$ هم یک کلمه رزرو شده است که محل قرار گرفتن cursor را بعد از اضافه شدن قطعه کد مشخص میکند .
- در نهایت snippet ما به شکل زیر خواهد بود :
<codesnippet format="1.0.0" xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet"> <header> <title>Try Catch Finally</title> <author>mohsen.d</author> <shortcut>trycf</shortcut> <description>Adds a try-catch-finally block</description> <snippettypes> <snippettype>Expansion</snippettype> </snippettypes> </header> <snippet> <declarations> <literal> <id>ExceptionName</id> <default>Exception</default> </literal> </declarations> <code language="CSharp"> <!--[CDATA[ try { $end$ } catch($ExceptionName$) { } finally { } ]]--> </code> </snippet> </codesnippet>
اضافه کردن snippet ساخته شده به visual studio
دو راه برای اضافه کردن snippet تعریف شده به ویژوال استودیو وجود دارد :
روش اول قرار دادن فایل .snippet در پوشه code snippets ویژوال استودیو است که مسیر پیش فرض آن
C:\Users\<UserName>\Documents\Visual Studio 2010\Code Snippets\
گزینه دوم import کردن فایل .snippet به داخل ویژوال استودیو است . در ویژوال استودیو به مسیر
Tools -> Code Snippets Manager (Ctrl+K,Ctrl+B)
استفاده از snippet ساخته شده
برای استفاده از snippet میتوانیم متن میانبر تعریف شده را تایپ کنیم و با دو بار فشردن کلید tab قطعه کد تعریف شده به محل کرسر اضافه میشودهمینطور با فشردن کلیدهای Ctrl+K و Ctrl+X به صورت پشت سر هم منوی “Insert Snippet” ظاهر میشود که از طریق آن میتوانیم Snippet موردنظر را یافته ( بدنبال Title تعریف شده برای snippet در پوشه ای که آنرا ذخیره کرده اید بگردید ) و با انتخاب آن کد تعریف شده اضافه خواهد شد .
برای آشنایی با روشهای مختلف دسترسی به snippetها اینجا را بررسی کنید .
ابزارها
در این پروژه هم مجموعه snippetهای موجود ویژوال استودیو 2010 برای زبان سی شارپ ، جهت سازگاری با stylecop ویرایش و refactor شده اند ( در کنار تعریف snippetهای دیگر ).
در اینجا نیز برای بررسی ویژگیهای جاوا اسکریپت مدرن، یک پروژهی جدید React را ایجاد میکنیم.
> create-react-app sample-03 > cd sample-03 > npm start
همچنین چون در این قسمت خروجی UI نخواهیم داشت، تمام خروجی را در کنسول developer tools مرورگر خود میتوانید مشاهده کنید (فشردن دکمهی F12).
متد Array.map
در برنامههای مبتنی بر React، از متد Array.map برای رندر لیستها استفاده میشود و نمونههای بیشتری از آنرا در قسمتهای بعدی مشاهده خواهید کرد.
فرض کنید آرایهای از رنگها را داریم. اکنون میخواهیم لیستی را به صورت <li>color</li> به ازای هر آیتم آن، تشکیل دهیم:
const colors = ["red", "green", "blue"];
const items = colors.map(function(color) { return "<li>" + color + "</li>"; }); console.log(items);
این مثال را توسط arrow functions نیز میتوان بازنویسی کرد:
const items2 = colors.map(color => "<li>" + color + "</li>"); console.log(items2);
یک مرحلهی دیگر هم میتوانیم این قطعه کد را زیباتر کنیم؛ جمع زدن رشتهها در ES6 معادل بهتری پیدا کردهاست که template literals نام دارد:
const items3 = colors.map(color => `<li>${color}</li>`); console.log(items3);
Object Destructuring
فرض کنید شیء آدرس را به صورت زیر تعریف کردهایم:
const address = { street: "street 1", city: "city 1", country: "country 1" };
const street1 = address.street; const city1 = address.city; const country1 = address.country;
const { street, city, country } = address;
در اینجا باید نام متغیرهای تعریف شده با نام خواص شیء آدرس یکی باشند. همچنین ذکر تمامی این متغیرها نیز ضرورتی ندارد و برای مثال اگر فقط نیاز به street بود، میتوان تنها آنرا ذکر کرد.
اگر خواستیم نام متغیر دیگری را بجای نام خواص شیء آدرس انتخاب کنیم، میتوان از یک نام مستعار ذکر شدهی پس از : استفاده کرد:
const { street: st } = address; console.log(st);
Spread Operator
فرض کنید دو آرایهی زیر را داریم:
const first = [1, 2, 3]; const second = [4, 5, 6];
const combined = first.concat(second); console.log(combined);
در ES6 با استفاده از عملگر ... که spread نیز نام دارد، میتوان قطعه کد فوق را به صورت زیر بازنویسی کرد:
const combined2 = [...first, ...second]; console.log(combined2);
شاید اینطور به نظر برسد که بین دو راه حل ارائه شده آنچنانی تفاوتی نیست. اما مزیت قطعه کد دوم، سهولت افزودن المانهای جدید، به هر قسمتی از آرایه است:
const combined2 = [...first, "a", ...second, "b"]; console.log(combined2);
کاربرد دیگر عملگر spread امکان clone سادهی یک آرایهاست:
const clone = [...first]; console.log(clone);
به علاوه امکان اعمال آن به اشیاء نیز وجود دارد:
const firstObject = { name: "User 1" }; const secondObject = { job: "Job 1" }; const combinedObject = { ...firstObject, ...secondObject, location: "Here" }; console.log(combinedObject);
{name: "User 1", job: "Job 1", location: "Here"}
و امکان clone اشیاء توسط آن هم وجود دارد:
const clonedObject = { ...firstObject }; console.log(clonedObject);
کلاسها در ES 6
قطعه کد کلاسیک زیر را که کار ایجاد اشیاء را در جاوا اسکریپت انجام میدهد، در نظر بگیرید:
const person = { name: "User 1", walk() { console.log("walk"); } }; const person2 = { name: "User 2", walk() { console.log("walk"); } };
class Person { constructor(name) { this.name = name; } walk() { console.log("walk"); } }
باید دقت داشت که class Person تنها یک قالب است و const person ای که پیشتر تعریف شد، یک شیء. برای اینکه از روی قالب تعریف شدهی Person، یک شیء را ایجاد کنیم، به صورت زیر توسط واژهی کلیدی new عمل میشود:
const person3 = new Person("User 3"); console.log(person3.name); person3.walk();
یک نکته: در جاوا اسکریپت، کلاسها نیز شیء هستند! از این جهت که کلاسها در جاوا اسکریپت صرفا یک بیان نحوی زیبای تابع constructor هستند و توابع در جاوا اسکریپت نیز شیء میباشند!
ارث بری کلاسها در ES6
فرض کنید میخواهیم کلاس Teacher را به نحو زیر تعریف کنیم:
class Teacher { teach() { console.log("teach"); } }
class Teacher extends Person { teach() { console.log("teach"); } }
علت اینجا است که کلاس Teacher، نه فقط متد walk کلاس Person را به ارث بردهاست، بلکه سازندهی آنرا نیز به ارث میبرد:
const teacher = new Teacher("User 4");
console.log(teacher.name); teacher.teach(); teacher.walk();
در ادامه فرض کنید علاوه بر ذکر نام، نیاز به ذکر مدرک معلم نیز در سازندهی کلاس وجود دارد:
class Teacher extends Person { constructor(name, degree) {}
Uncaught ReferenceError: Must call super constructor in derived class before accessing 'this' or returning from derived constructor
class Teacher extends Person { constructor(name, degree) { super(name); this.degree = degree; } teach() { console.log("teach"); } }
const teacher = new Teacher("User 4", "MSc");
در برنامههای React، هر زمانیکه یک کامپوننت جدید تعریف میشود، کلاس آن، از کلاس پایهی کامپوننت، ارث بری خواهد کرد. به این ترتیب میتوان به تمام امکانات این کلاس پایه، بدون نیاز به تکرار آنها در کلاسهای مشتق شدهی از آن، دسترسی یافت.
ماژولها در ES 6
تا اینجا اگر مثالها را دنبال کرده باشید، تمام آنها را داخل همان فایل index.js درج کردهایم. به این ترتیب کم کم دارد مدیریت این فایل از دست خارج میشود. امکان تقسیم کدهای index.js به چندین فایل، مفهوم ماژولها را در ES6 تشکیل میدهد. برای این منظور قصد داریم هر کلاس تعریف شده را به یک فایل جداگانه که ماژول نامیده میشود، منتقل کنیم. از کلاس Person شروع میکنیم و آنرا به فایل جدید person.js و کلاس Teacher را به فایل جدید teacher.js منتقل میکنیم.
البته اگر از افزونههای VSCode استفاده میکنید، اگر کرسر را بر روی نام کلاس قرار دهید، یک آیکن لامپ مانند ظاهر میشود. با کلیک بر روی آن، منویی که شامل گزینهی move to a new file هست، برای انجام سادهتر این عملیات (ایجاد یک فایل جدید js، سپس انتخاب و cut کردن کل کلاس و در آخر کپی کردن آن در این فایل جدید) پیشبینی شدهاست.
هرچند این عملیات تا به اینجا خاتمه یافته به نظر میرسد، اما نیاز به اصلاحات زیر را نیز دارد:
- هنگام کار با ماژولها، اشیاء تعریف شدهی در آن به صورت پیشفرض، خصوصی و private هستند و خارج از آنها قابل دسترسی نمیباشند. به این معنا که class Teacher ما که اکنون در یک ماژول جدید قرار گرفتهاست، توسط سایر قسمتهای برنامه قابل مشاهده و دسترسی نیست.
- برای public تعریف کردن یک کلاس تعریف شدهی در یک ماژول، نیاز است آنرا export کنیم. انجام این کار نیز سادهاست. فقط کافی است واژهی کلیدی export را به پیش از class اضافه کنیم:
export class Teacher extends Person {
برای رفع این مشکل، باید این وابستگی را import کرد:
import { Person } from "./Person"; export class Teacher extends Person {
- مرحلهی آخر، اصلاح فایل index.js است؛ چون اکنون تعاریف Person و Teacher را نمیشناسد.
import { Person } from "./Person"; import { Teacher } from "./Teacher";
Exportهای پیشفرض و نامدار در ES6
اشیاء تعریف شدهی در یک ماژول، به صورت پیشفرض private هستند؛ مگر اینکه export شوند. برای مثال export class Teacher و یا export function xyz. به اینها named exports گویند. حال اگر ماژول ما تنها یک شیء عمومی شده را داشت (کلاسها هم شیء هستند!)، میتوان از واژهی کلیدی default نیز در اینجا استفاده کرد:
export default class Teacher extends Person {
import Teacher from "./Teacher";
در ادامه اگر یک export نامدار دیگر را به این ماژول اضافه کنیم (مانند تابع testTeacher):
import { Person } from "./Person"; export function testTeacher() { console.log("Test Teacher"); } export default class Teacher extends Person {
import Teacher, { testTeacher } from "./Teacher";
import React, { Component } from 'react';
یک نکته: اگر در VSCode داخل {}، دکمههای ctrl+space را فشار دهید، میتوانید منوی exportهای ماژول تعریف شده را مشاهده کنید.
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید: sample-03.zip
نکته: دقت کنید که تنها یک فولدر App_GlobalResources به هر پروزه میتوان افزود. همچنین در ریشه هر مسیر موجود در پروژه تنها میتوان یک فولدر Appp_LocalResources داشت. پس از افزودن هر یک از این فولدرهای مخصوص، منوی فوق به صورت زیر در خواهد آمد:
protected object GetLocalResourceObject(string resourceKey) protected object GetLocalResourceObject(string resourceKey, Type objType, string propName)
txtTest.Text = GetLocalResourceObject("txtTest.Text") as string;
protected object GetGlobalResourceObject(string className, string resourceKey) protected object GetGlobalResourceObject(string className, string resourceKey, Type objType, string propName)
TextBox1.Text = GetGlobalResourceObject("Resource1", "String1") as string;
public static object GetLocalResourceObject(string virtualPath, string resourceKey) public static object GetLocalResourceObject(string virtualPath, string resourceKey, CultureInfo culture)
txtTest.Text = HttpContext.GetLocalResourceObject("~/Default.aspx", "txtTest.Text") as string;
public static object GetGlobalResourceObject(string classKey, string resourceKey) public static object GetGlobalResourceObject(string classKey, string resourceKey, CultureInfo culture)
TextBox1.Text = HttpContext.GetGlobalResourceObject("Resource1", "String1") as string;
//------------------------------------------------------------------------------ // <auto-generated> // This code was generated by a tool. // Runtime Version:4.0.30319.17626 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. // </auto-generated> //------------------------------------------------------------------------------ namespace Resources { using System; /// <summary> /// A strongly-typed resource class, for looking up localized strings, etc. /// </summary> // This class was auto-generated by the StronglyTypedResourceBuilder // class via a tool like ResGen or Visual Studio. // To add or remove a member, edit your .ResX file then rerun ResGen // with the /str option or rebuild the Visual Studio project. [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Web.Application.StronglyTypedResourceProxyBuilder", "10.0.0.0")] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] internal class Resource1 { private static global::System.Resources.ResourceManager resourceMan; private static global::System.Globalization.CultureInfo resourceCulture; [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] internal Resource1() { } /// <summary> /// Returns the cached ResourceManager instance used by this class. /// </summary> [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] internal static global::System.Resources.ResourceManager ResourceManager { get { if (object.ReferenceEquals(resourceMan, null)) { global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Resources.Resource1", global::System.Reflection.Assembly.Load("App_GlobalResources")); resourceMan = temp; } return resourceMan; } } /// <summary> /// Overrides the current thread's CurrentUICulture property for all /// resource lookups using this strongly typed resource class. /// </summary> [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] internal static global::System.Globalization.CultureInfo Culture { get { return resourceCulture; } set { resourceCulture = value; } } /// <summary> /// Looks up a localized string similar to String1. /// </summary> internal static string String1 { get { return ResourceManager.GetString("String1", resourceCulture); } } } }
TextBox1.Text = Resources.Resource1.String1;