دکمه F5 را بزنید و برنامه را اجرا کنید. یک دکمه میبینید که Text آن عبارت + است.
با پیشرفت بیشتر تکنولوژی وب در سالهای اخیر و رشد کاربران فضای اینترنتی، خدمات و پیچیدگیهای بیشتری به نرم افزارها اضافه شده و به همین دلیل استفاده از میکروسرویسها بجای حالت قدیمی مونولوتیک (یک برنامه همه کاره) طرفداران بیشتری پیدا کردهاست. در این حالت برنامه به قسمتهای خرد و مجزایی تبدیل شده و هر پروژه ساختار و تکنولوژی مخصوص به خود را مدیریت میکند و در این بین با استفاده روشهای متفاوتی به ایجاد ارتباط با یکدیگر میپردازند .
مشکلی که در این حالت میتواند رخ دهد، زیاد شدن مسیرهای متفاوت برای اتصال به هر یک از سرویسها و سختتر شدن به روزرسانی این مسیرها میباشد. به همین دلیل در این بخش، نیاز به ابزاری میباشد تا بتوان از طریق آن، مسیردهی سادهای را ایجاد کرد و در پشت صحنه مسیردهیهای متفاوتی را کنترل نمود. با ایجاد چنین ابزاری در واقع شما API Gateway ایجاد نمودهاید. یکی از معروفترین کتابخانههای این حوزه، Ocelot میباشد. کار با این ابزار بسیار ساده بوده و امکانات بسیار زیاد و قدرتمندی را فراهم مینماید.
برای اینکار ابتدا سه پروژه را میسازیم که موارد زیر را شامل میگردد:
پروژه اول نوع Api : با دریافت Id در اکشنمتد مورد نظر، شیء user بازگردانده میشود:
public class User { public int Id { get; set; } public string FirstName { get; set; } public string LastName { get; set; } public string UserName { get; set; } public static List<User> GetUsers() { return new List<User>() { new() { Id = 1, FirstName = "علی", LastName = "یگانه مقدم", UserName = "yeganehaym" }, new () { Id = 2, FirstName = "وحید", LastName = "نصیری", UserName = "VahidN" }, }; } }
[ApiController] [Route("/api/[controller]/{id?}")] public class UserController : ControllerBase { [HttpGet] public User GetUser(int id) { var users = Users.User.GetUsers(); var user = users.FirstOrDefault(x => x.Id == id); return user; } }
پروژه دوم نوع Api : دریافت لیستی از محصولات:
public class Product { public int Id { get; set; } public string Name { get; set; } public int Price { get; set; } public int Quantity { get; set; } public static List<Product> GetProducts() { return new List<Product>() { new() { Id = 1, Name = "LCD", Price = 20000, Quantity = 10 }, new() { Id = 1, Name = "Mouse", Price = 320000, Quantity = 15 }, new() { Id = 1, Name = "Keyboard", Price = 50000, Quantity = 25 }, }; } }
[ApiController] [Route("api/[controller]")] public class ProductController : ControllerBase { [HttpGet] public List<Product> GetProducts() { return Product.GetProducts(); } }
پروژه سوم همان ApiGateway هست و همینکه یک پروژهی وب خالی باشد، کفایت میکند. در این پروژه Ocelot را نصب نموده و سپس فایلی با نام ocelot.json را با محتوای زیر به ریشهی پروژه همانند فایلهای appsettings.json اضافه میکنیم:
{ "Routes":[ { "DownstreamPathTemplate":"/api/User/{id}", "DownstreamScheme":"https", "DownstreamHostAndPorts":[ { "Host":"localhost", "Port":"7279" } ], "UpstreamPathTemplate":"/GetUser/{id}", "UpstreamHttpMethod":[ "GET" ]}, { "DownstreamPathTemplate":"/api/Product", "DownstreamScheme":"https", "DownstreamHostAndPorts":[ { "Host":"localhost", "Port":"7261" } ], "UpstreamPathTemplate":"/Products", "UpstreamHttpMethod":[ "GET" ] } ] }
این فایلها شامل دو قسمتUpStream و DownStream میشوند. آپاستریمها در واقع آدرسی است که شما قصد اتصال به آنرا دارید و قسمت داوناستریم، سرویس مقصدی است که ocelot باید درخواست شما را به سمت آن ارسال نماید. بهعنوان مثل شما با ارسال درخواستی به آدرس Products ، در پشت صحنه به آدرس localhost:7261/api/product ارسال میگردد. بدین صورت سیستم نهایی تنها به یک دامنه و آدرس منسجم ارسال شده، ولی در پشت صحنه این آدرسها ممکن است به تعداد زیادی سرویس در آدرسهای متفاوتی ارسال گردند.
جهت راه اندازی نهایی، کد زیر را به فایل Program.cs اضافه میکنیم:
builder.Services.AddOcelot();
app.UseOcelot();
پس از اضافه کردن پیکربندی و middleware آن، کد زیر را نیز جهت شناسایی فایل ocelot به فایل Program.cs نیز اضافه مینماییم:
builder.Configuration.SetBasePath(builder.Environment.ContentRootPath) .AddJsonFile("ocelot.json", optional: false, reloadOnChange: true);
همچنین
در صورت تمایل میتوانید کد را به شکل زیر هم نوشته تا بتوانید تنظیمات متفاوتی را برای محیط اجرایی متفاوتی ایجاد نمایید:
builder.Configuration.SetBasePath(builder.Environment.ContentRootPath) .AddJsonFile("ocelot.json", optional: false, reloadOnChange: true) .AddJsonFile($"ocelot.{builder.Environment.EnvironmentName}.json", optional: false, reloadOnChange: true);
هر
سه برنامه را با هم اجرا نمایید و با استفاده از برنامهی PostMan درخواستی را برای هر یک از موارد مورد نظر /Products و /GetUser/{1,2} به سمت پروژه ApiGateway
ارسال نمایید.
Ocelot موارد دیگری از قبیل تنظیم Load Balancer بین سرویس ها، اتصال به سرویسهای Service Discoveryچون Consul یا یوریکا و کش کردن و ... را نیز فراهم مینماید.
عملیات کشینگ
جهت بحث کشینگ، ابتدا بسته زیر را اضافه نمایید:
Install-Package Ocelot.Cache.CacheManager
سپس
پیکربندی ابتدایی را به شکل زیر تغییر دهید:
builder.Services.AddOcelot() .AddCacheManager(x => x.WithDictionaryHandle());
در ادامه در فایل Ocelot جیسون،
برای هر بخشی که مدنظر شماست تا کشی را انجام دهد، کد زیر اضافه نمایید:
"FileCacheOptions":{ "TtlSeconds":30, "Region":"custom" }
TtlSeconds : مدت
زمان کش به ثانیه
Region : یک عبارت رشتهای همانند یک عنوان یا نام که بعدا میتوانید از طریق api ها به آن متصل شوید و عملیاتی چون خالی کردن کش را صادر نمایید.
حال برای بخش محصولات این تنظیمات ذکر میگردد:
{ "Routes":[ { "DownstreamPathTemplate":"/api/User/{id}", "DownstreamScheme":"https", "DownstreamHostAndPorts":[ { "Host":"localhost", "Port":"7279" } ], "UpstreamPathTemplate":"/GetUser/{id}", "UpstreamHttpMethod":[ "GET" ] }, { "DownstreamPathTemplate":"/api/Product", "DownstreamScheme":"https", "DownstreamHostAndPorts":[ { "Host":"localhost", "Port":"7261" } ], "UpstreamPathTemplate":"/Products", "UpstreamHttpMethod":[ "GET" ], "FileCacheOptions":{ "TtlSeconds":30, "Region":"custom" } } ] }
برای اینکه متوجه عملکرد آن شوید یک نقطه توقف را در اکشن دریافت محصول قرار دهید و سپس برنامه را در حالت دیباگ اجرا نمایید. در مرتبه اول باید نقطه توقف بتواند اجرای کد را به شما نمایش دهد ولی تا 30 ثانیه آینده هر چقدر از طریق Postman درخواستی را ارسال نمایید نقطه توقف اجرا نخواهد گردید، ولی نتیجهی قبل برای شما ارسال خواهد شد.
این مورد را برای بخش کاربران هم انجام دهید و میبینید که برای هر userId و هر شکل Url، یک پاسخ منحصر به فرد، دریافت و کش خواهد شد.
جلوگیری از درخواستهای بیش از حد
یکی دیگر از ویژگیهای Ocelot، جلوگیری از درخواست بیش از حد میباشد. به همین علت ابتدا کد زیر را به هر درخواستی که مدنظر شماست اضافه نمایید:
"RateLimitOptions":{ "ClientWhitelist":[ ], "EnableRateLimiting":true, "Period":"5s", "PeriodTimespan":1, "Limit":1, "HttpStatusCode":429 }
WhiteClients : برای مشخص کردن کلاینتهایی که نباید اعمال محدودیت روی آنها صورت بگیرد.
EnableRateLimiting : این مورد باعث فعالسازی آن میگردد.
Period: مدت زمانیکه حداکثر تعداد درخواست باید در آن بازه صورت بگیرد. به ترتیب برای ثانیه، دقیقه، ساعت و روز حروف s - m - h و d استفاده میگردد.
PeriodTimespan: بعد از محدود شدن، بعد از چه مدتی دوباره بتواند درخواستی را ارسال نماید. در اینجا بعد از محدودیت ارسال درخواست، بعد از یک ثانیه مجدد اجازه ارسال درخواست باز میگردد.
Limit: در بازه زمانی مشخص شده چند درخواست مورد قبول واقع میشود و بعد از آن دیگر اجازه ارسال درخواست را نخواهد داشت.
HttpStatusCode: در صورت فیلتر شدن درخواستهای رسیده، چه کد وضعیتی باید برگردانده شود که عدد 429 به معنای Too Many Request میباشد.
با تنظیمات بالا هر کلاینت میتواند در 5 ثانیه، نهایتا یک درخواست را ارسال نماید و با ارسال بقیه درخواستها، Ocelot بجای هدایت درخواست به سرویس مربوطه، کد وضعیت 429 را باز میگرداند و یک ثانیه بعد از گذشت 5 ثانیه میتواند مجددا درخواست خود را ارسال نماید.
در نهایت به یک فایل مشابه زیر میرسیم:
{ "Routes":[ { "DownstreamPathTemplate":"/api/User/{id}", "DownstreamScheme":"https", "DownstreamHostAndPorts":[ { "Host":"localhost", "Port":"7279" } ], "UpstreamPathTemplate":"/GetUser/{id}", "UpstreamHttpMethod":[ "GET" ], "FileCacheOptions":{ "TtlSeconds":30, "Region":"custom" } }, { "DownstreamPathTemplate":"/api/Product", "DownstreamScheme":"https", "DownstreamHostAndPorts":[ { "Host":"localhost", "Port":"7261" } ], "UpstreamPathTemplate":"/Products", "UpstreamHttpMethod":[ "GET" ], "RateLimitOptions":{ "ClientWhitelist":[ ], "EnableRateLimiting":true, "Period":"5s", "PeriodTimespan":1, "Limit":1, "HttpStatusCode":429 } } ], "DangerousAcceptAnyServerCertificateValidator": true }
برای تست آن با استفاد از PostMan مرتبا به آدرس Products/ درخواست ارسال نمایید.
فایل پروژه : Ocelot.zip
سیستم ASP.NET Membership بهمراه ASP.NET 2.0 در سال 2005 معرفی شد، و از آن زمان تا بحال تغییرات زیادی در چگونگی مدیریت احزار هویت و اختیارات کاربران توسط اپلیکیشنهای وب بوجود آمده است. ASP.NET Identity نگاهی تازه است به آنچه که سیستم Membership هنگام تولید اپلیکیشنهای مدرن برای وب، موبایل و تبلت باید باشد.
پیش زمینه: سیستم عضویت در ASP.NET
- الگوی پایگاه داده آن برای SQL Server طراحی شده است، و قادر به تغییرش هم نیستید. میتوانید اطلاعات پروفایل را اضافه کنید، اما تمام دادهها در یک جدول دیگر ذخیره میشوند، که دسترسی به آنها نیز مشکلتر است، تنها راه دسترسی Profile Provider API خواهد بود.
- سیستم تامین کننده (Provider System) امکان تغییر منبع دادهها را به شما میدهد، مثلا میتوانید از بانکهای اطلاعاتی MySQL یا Oracle استفاده کنید. اما تمام سیستم بر اساس پیش فرض هایی طراحی شده است که تنها برای بانکهای اطلاعاتی relational درست هستند. میتوانید تامین کننده (Provider) ای بنویسید که دادههای سیستم عضویت را در منبعی به غیر از دیتابیسهای relational ذخیره میکند؛ مثلا Windows Azure Storage Tables. اما در این صورت باید مقادیر زیادی کد بنویسید. مقادیر زیادی هم System.NotImplementedException باید بنویسید، برای متد هایی که به دیتابیسهای NoSQL مربوط نیستند.
- از آنجایی که سیستم ورود/خروج سایت بر اساس مدل Forms Authentication کار میکند، سیستم عضویت نمیتواند از OWIN استفاده کند. OWIN شامل کامپوننت هایی برای احراز هویت است که شامل سرویسهای خارجی هم میشود (مانند Microsoft Accounts, Facebook, Google, Twitter). همچنین امکان ورود به سیستم توسط حسابهای کاربری سازمانی (Organizational Accounts) نیز وجود دارد مانند Active Directory و Windows Azure Active Directory. این کتابخانه از OAuth 2.0، JWT و CORS نیز پشتیبانی میکند.
- ذخیره دادههای سیستم عضویت در بانکهای اطلاعاتی non-relational مشکل است.
- نمی توانید از آن در کنار OWIN استفاده کنید.
- با فراهم کنندههای موجود ASP.NET Membership بخوبی کار نمیکند. توسعه پذیر هم نیست.
ASP.NET Universal Providers
ASP.NET Universal Providers برای ذخیره سازی اطلاعات سیستم عضویت در Windows Azure SQL Database توسعه پیدا کردند. با SQL Server Compact هم بخوبی کار میکنند. این تامین کنندهها بر اساس Entity Framework Code First ساخته شده بودند و بدین معنا بود که دادههای سیستم عضویت را میتوان در هر منبع داده ای که توسط EF پشتیبانی میشود ذخیره کرد. با انتشار این تامین کنندهها الگوی دیتابیس سیستم عضویت نیز بسیار سبکتر و بهتر شد. اما این سیستم بر پایه زیر ساخت ASP.NET Membership نوشته شده است، بنابراین محدودیتهای پیشین مانند محدودیتهای SqlMembershipProvider هنوز وجود دارند. به بیان دیگر، این سیستمها همچنان برای بانکهای اطلاعاتی relational طراحی شده اند، پس سفارشی سازی اطلاعات کاربران و پروفایلها هنوز مشکل است. در آخر آنکه این تامین کنندهها هنوز از مدل احراز هویت فرم استفاده میکنند.
ASP.NET Identity
- یک سیستم هویت واحد (One ASP.NET Identity system)
- سیستم ASP.NET Identity میتواند در تمام فریم ورکهای مشتق از ASP.NET استفاده شود. مانند ASP.NET MVC, Web Forms, Web Pages, Web API و SignalR
- از این سیستم میتوانید در تولید اپلیکیشنهای وب، موبایل، استور (Store) و یا اپلیکیشنهای ترکیبی استفاده کنید.
- سادگی تزریق دادههای پروفایل درباره کاربران
- روی الگوی دیتابیس برای اطلاعات کاربران و پروفایلها کنترل کامل دارید. مثلا میتوانید به سادگی یک فیلد، برای تاریخ تولد در نظر بگیرید که کاربران هنگام ثبت نام در سایت باید آن را وارد کنند.
- کنترل ذخیره سازی/واکشی اطلاعات
- بصورت پیش فرض ASP.NET Identity تمام اطلاعات کاربران را در یک دیتابیس ذخیره میکند. تمام مکانیزمهای دسترسی به دادهها توسط EF Code First کار میکنند.
- از آنجا که روی الگوی دیتابیس، کنترل کامل دارید، تغییر نام جداول و یا نوع داده فیلدهای کلیدی و غیره ساده است.
- استفاده از مکانیزمهای دیگر برای مدیریت دادههای آن ساده است، مانند SharePoint, Windows Azure Storage Table و دیتابیسهای NoSQL.
- تست پذیری
- ASP.NET Identity تست پذیری اپلیکیشن وب شما را بیشتر میکند. میتوانید برای تمام قسمت هایی که از ASP.NET Identity استفاده میکنند تست بنویسید.
- تامین کننده نقش (Role Provider)
- تامین کننده ای وجود دارد که به شما امکان محدود کردن سطوح دسترسی بر اساس نقوش را میدهد. بسادگی میتوانید نقشهای جدید مانند "Admin" بسازید و بخشهای مختلف اپلیکیشن خود را محدود کنید.
- Claims Based
- ASP.NET Identity از امکان احراز هویت بر اساس Claims نیز پشتیبانی میکند. در این مدل، هویت کاربر بر اساس دسته ای از اختیارات او شناسایی میشود. با استفاده از این روش توسعه دهندگان برای تعریف هویت کاربران، آزادی عمل بیشتری نسبت به مدل Roles دارند. مدل نقشها تنها یک مقدار منطقی (bool) است؛ یا عضو یک نقش هستید یا خیر، در حالیکه با استفاده از روش Claims میتوانید اطلاعات بسیار ریز و دقیقی از هویت کاربر در دست داشته باشید.
- تامین کنندگان اجتماعی
- به راحتی میتوانید از تامین کنندگان دیگری مانند Microsoft, Facebook, Twitter, Google و غیره استفاده کنید و اطلاعات مربوط به کاربران را در اپلیکیشن خود ذخیره کنید.
- Windows Azure Active Directory
- برای اطلاعات بیشتر به این لینک مراجعه کنید.
- یکپارچگی با OWIN
- ASP.NET Identity بر اساس OWIN توسعه پیدا کرده است، بنابراین از هر میزبانی که از OWIN پشتیبانی میکند میتوانید استفاده کنید. همچنین هیچ وابستگی ای به System.Web وجود ندارد. ASP.NET Identity یک فریم ورک کامل و مستقل برای OWIN است و میتواند در هر اپلیکیشنی که روی OWIN میزبانی شده استفاده شود.
- ASP.NET Identity از OWIN برای ورود/خروج کاربران در سایت استفاده میکند. این بدین معنا است که بجای استفاده از Forms Authentication برای تولید یک کوکی، از OWIN CookieAuthentication استفاده میشود.
- پکیج NuGet
- ASP.NET Identity در قالب یک بسته NuGet توزیع میشود. این بسته در قالب پروژههای ASP.NET MVC, Web Forms و Web API که با Visual Studio 2013 منتشر شدند گنجانده شده است.
- توزیع این فریم ورک در قالب یک بسته NuGet این امکان را به تیم ASP.NET میدهد تا امکانات جدیدی توسعه دهند، باگها را برطرف کنند و نتیجه را بصورت چابک به توسعه دهندگان عرضه کنند.
ASP.NET Identity در قالب پروژههای ASP.NET MVC, Web Forms, Web API و SPA که بهمراه Visual Studio 2013 منتشر شده اند استفاده میشود. در ادامه به اختصار خواهیم دید که چگونه ASP.NET Identity کار میکند.
- یک پروژه جدید ASP.NET MVC با تنظیمات Individual User Accounts بسازید.
-
پروژه ایجاد شده شامل سه بسته میشود که مربوط به ASP.NET Identity هستند:
- Microsoft.AspNet.Identity.EntityFramework این بسته شامل پیاده سازی ASP.NET Identity با Entity Framework میشود، که تمام دادههای مربوطه را در یک دیتابیس SQL Server ذخیره میکند.
- Microsoft.AspNet.Identity.Core این بسته محتوی تمام interfaceهای ASP.NET Identity است. با استفاده از این بسته میتوانید پیاده سازی دیگری از ASP.NET Identity بسازید که منبع داده متفاوتی را هدف قرار میدهد. مثلا Windows Azure Storage Table و دیتابیسهای NoSQL.
- Microsoft.AspNet.Identity.OWIN این بسته امکان استفاده از احراز هویت OWIN را در اپلیکیشنهای ASP.NET فراهم میکند. هنگام تولید کوکیها از OWIN Cookie Authentication استفاده خواهد شد.
هنگامیکه بر روی دکمهی Register کلیک شود، کنترلر Account، اکشن متد Register را فراخوانی میکند تا حساب کاربری جدیدی با استفاده از ASP.NET Identity API ساخته شود.
[HttpPost] [AllowAnonymous] [ValidateAntiForgeryToken] public async Task<ActionResult> Register(RegisterViewModel model) { if (ModelState.IsValid) { var user = new ApplicationUser() { UserName = model.UserName }; var result = await UserManager.CreateAsync(user, model.Password); if (result.Succeeded) { await SignInAsync(user, isPersistent: false); return RedirectToAction("Index", "Home"); } else { AddErrors(result); } } // If we got this far, something failed, redisplay form return View(model); }
اگر حساب کاربری با موفقیت ایجاد شود، کاربر توسط فراخوانی متد SignInAsync به سایت وارد میشود.
[HttpPost] [AllowAnonymous] [ValidateAntiForgeryToken] public async Task<ActionResult> Register(RegisterViewModel model) { if (ModelState.IsValid) { var user = new ApplicationUser() { UserName = model.UserName }; var result = await UserManager.CreateAsync(user, model.Password); if (result.Succeeded) { await SignInAsync(user, isPersistent: false); return RedirectToAction("Index", "Home"); } else { AddErrors(result); } } // If we got this far, something failed, redisplay form return View(model); }
private async Task SignInAsync(ApplicationUser user, bool isPersistent) { AuthenticationManager.SignOut(DefaultAuthenticationTypes.ExternalCookie); var identity = await UserManager.CreateIdentityAsync( user, DefaultAuthenticationTypes.ApplicationCookie); AuthenticationManager.SignIn( new AuthenticationProperties() { IsPersistent = isPersistent }, identity); }
از آنجا که ASP.NET Identity و OWIN Cookie Authentication هر دو Claims-based هستند، فریم ورک، انتظار آبجکتی از نوع ClaimsIdentity را خواهد داشت. این آبجکت تمامی اطلاعات لازم برای تشخیص هویت کاربر را در بر دارد. مثلا اینکه کاربر مورد نظر به چه نقش هایی تعلق دارد؟ و اطلاعاتی از این قبیل. در این مرحله میتوانید Claimهای بیشتری را به کاربر بیافزایید.
کلیک کردن روی لینک Log off در سایت، اکشن متد LogOff در کنترلر Account را اجرا میکند.
// POST: /Account/LogOff [HttpPost] [ValidateAntiForgeryToken] public ActionResult LogOff() { AuthenticationManager.SignOut(); return RedirectToAction("Index", "Home"); }
همانطور که مشاهده میکنید برای ورود/خروج کاربران از AuthenticationManager استفاده میشود که متعلق به OWIN است. متد SignOut همتای متد FormsAuthentication.SignOut است.
کامپوننتهای ASP.NET Identity
تصویر زیر اجزای تشکیل دهنده ASP.NET Identity را نمایش میدهد. بسته هایی که با رنگ سبز نشان داده شده اند سیستم کلی ASP.NET Identity را میسازند. مابقی بستهها وابستگی هایی هستند که برای استفاده از ASP.NET Identity در اپلیکیشنهای ASP.NET لازم اند.
دو پکیج دیگر نیز وجود دارند که به آنها اشاره نشد:
- Microsoft.Security.Owin.Cookies این بسته امکان استفاده از مدل احراز هویت مبتنی بر کوکی (Cookie-based Authentication) را فراهم میکند. مدلی مانند سیستم ASP.NET Forms Authentication.
- EntityFramework که نیازی به معرفی ندارد.
مهاجرت از Membership به ASP.NET Identity
قدمهای بعدی
public class Employee { public string EmployeeName { get; set; } public int EmployeeNo { get; set; } public void Insert(Employee e) { //Database Logic written here } public void GenerateReport(Employee e) { //Set report formatting } }
public class Employee { public string EmployeeName { get; set; } public int EmployeeNo { get; set; } } public class EmployeeDB { public void Insert(Employee e) { //Database Logic written here } public Employee Select() { //Database Logic written here } } public class EmployeeReport { public void GenerateReport(Employee e) { //Set report formatting } }
//Method with multiple responsibilities – violating SRP public void Insert(Employee e) { string StrConnectionString = ""; SqlConnection objCon = new SqlConnection(StrConnectionString); SqlParameter[] SomeParameters=null;//Create Parameter array from values SqlCommand objCommand = new SqlCommand("InertQuery", objCon); objCommand.Parameters.AddRange(SomeParameters); ObjCommand.ExecuteNonQuery(); }
//Method with single responsibility – follow SRP public void Insert(Employee e) { SqlConnection objCon = GetConnection(); SqlParameter[] SomeParameters=GetParameters(); SqlCommand ObjCommand = GetCommand(objCon,"InertQuery",SomeParameters); ObjCommand.ExecuteNonQuery(); } private SqlCommand GetCommand(SqlConnection objCon, string InsertQuery, SqlParameter[] SomeParameters) { SqlCommand objCommand = new SqlCommand(InsertQuery, objCon); objCommand.Parameters.AddRange(SomeParameters); return objCommand; } private SqlParameter[] GetParaeters() { //Create Paramter array from values } private SqlConnection GetConnection() { string StrConnectionString = ""; return new SqlConnection(StrConnectionString); }
قبل از پاسخگویی به سؤال بالا، به یک سری مقدمات نیاز است:
وقتی یک کوئری به اس کیو ال ارسال میشود، چه اتفاقی رخ میدهد؟
وقتی یک کوئری ارسال میشود، تعدادی از پروسسها بر روی کوئری شروع به فعالیتهایی مانند مهیا نمودن دادههای بازگشتی، یا ذخیره سازی و ... میکنند.
پروسسها به دو دسته زیر تقسیم میشوند:
- پروسسهایی که در relational engine رخ میدهند
- پروسسهایی که در storage engine رخ میدهند
در
relational engine، هر کوئری pars شده و سپس بوسیله query optimizer
پردازش و پلن اجرایی (execution plan) آن که بفرمت باینری است، ایجاد میشود و
به storage engine ارسال میگردد. در storage engine پروسسهایی مانند قفل
گذاری، نگهداری ایندکسها و تراکنشها رخ میدهد. هنگامیکه اس کیو ال
سرور کوئری را دریافت مینمایند، آن را بلافاصله به relational engine ارسال
میکند. سپس نحو (syntax) آن بررسی میشود؛ این عمل query parsing نامیده
میشود. خروجی عملیات پارسر، یک ساختار درختی (query tree) است. این ساختار
درختی مشخص کننده مراحل لازم جهت اجرای کوئری ارائه شده میباشد.
اگر یک کوئری شامل DML نباشد (مانند ساخت جدول)، علمیات بهبود برروی آن صورت
نخواهد گرفت. ولی در صورتیکه کوئری ارسالی، DML باشد، درخت اشاره شده در
بالا به algebrizer فرستاده میشود که وظیفه آن تفسیر و بررسی کلیه نام
اشیاء، جداول و ستونهای اشاره شده در متن کوئری است. فرآیند algebrizer
بسیار مهم و حیاتی است؛ بدلیل اینکه در کوئری ممکن است اشاره کنندههایی به
اشیایی باشند که در بانک اطلاعاتی موجود نیست. خروجی algebrizer یک query
processor tree باینری است که به بهبود دهنده کوئری ارسال میگردد.
معرفی Query Optimizer (بهبود دهنده پرس و جو)
بهبود
دهنده، بهترین مسیر اجرای کوئری را مشخص میکند. این بهبود دهنده است که مشخص
میکند که اطلاعات بوسیله ایندکس دریافت شوند، یا اینکه از چه اتصالی استفاده
شود و الی آخر. این تصمیمات براساس محاسبات هزینههای (میزان پردازش لازم
cpu و I/O) پلن اجرایی صورت خواهد پذیرفت. بهمین دلیل به پلن cost-based نیز شناخته میشود.
هنگامیکه کوئری سادهای مانند دریافت اطلاعات از یک جدول، که بر روی آن
ایندکس گذاری انجام نشدهاست، ارسال شود، بهبود دهنده بجای مشخص نمودن یک
پلن مناسب بهینه، از یک پلن ساده (trivial) استفاده میکند. ولی برعکس در
صورتیکه کوئری trivial نباشد (یعنی مثلا کوئری به گونهای باشد که از
ایندکسها به شکل صحیحی استفاده شده باشند)، بهبود دهنده یک پلن مناسب را
براساس اطلاعات آماری مهیا شده در اس کیو ال سرور، تولید و انتخاب مینماید.
اطلاعات
آماری از ستونها و ایندکسها جمع آوری میشود. این اطلاعات شامل نحوه
توزیع داده، یکتایی و انتخاب شوندگی است. این اطلاعات توسط یک histogram
ارائه میشود. اگر اطلاعات آماری برای یک ستون و یا ایندکس وجود داشته
باشد، بهبود دهنده از آنها برای محاسبات خود استفاده خواهد کرد. اطلاعات
آماری بصورت خودکار برای تمام ایندکسها و یا هر ستونی که بشود بر روی آنها
where یا join نوشت، فراهم خواهد شد.
بهبود دهنده با مقایسه پلنها براساس بررسی تفاوتهای انواع joinها، چیدمان
مجدد ترتیب join و بررسی ایندکسهای مختلف و سایر فعالیتهای دیگر، پلن
مناسب را انتخاب و از آن استفاده میکند. در طی هر کدام از فعالیتهای
اشاره شده، زمان اجرای آنها نیز تخمین زده (estimated cost) خواهد شد و در
پایان، زمان کل تخمینی بدست خواهد آمد و بهبود دهنده از این زمان برای انتخاب
پلن مناسب بهره خواهد برد. باید توجه داشت که این زمان تقریبی است. زمانیکه بهبود دهنده پلن اجرایی انتخاب میکند، یک actual plan را ایجاد و در حافظه ذخیره
میشود؛ بنام plan cache. البته درصورتیکه پلن مشابه و بهینهتری وجود
نداشته باشد.
استفاده مجدد از پلن ها
تولید پلن هزینه بر است. بههمین دلیل اس کیوال سرور اقدام به ذخیره سازی و نگهداری آنها میکند تا بتواند از آنها مجددا استفاده نماید؛ البته تا جایی که مقدور باشد. هنگامیکه آنها تولید میشوند، در قسمتی از حافظه بنام plan cache ذخیره میشوند. به این عمل procedure cache نیز گفته میشود.
هنگامیکه کوئری به سرور ارسال میشود، بوسیله بهبود دهنده، یک estimated plan ایجاد
خواهد شد و قبل از اینکه به storage engine ارسال شود، بهبود دهنده estimated
plan را با actual execution planهای موجود در plan cache مقایسه میکند.
در صورتیکه یک actual plan را مطابق با estimated plan پیدا نماید، از آن مجدد
استفاده خواهد کرد. این استفاده مجدد به عدم تحمیل سربار اضافهای به سرور جهت
کوئریهای بزرگ و پیچیده که در زمان واحد، هزاران بار اجرا خواهند شد، منجر میشود.
هر پلن فقط یکبار در حافظه ذخیره خواهد شد. ولی در مواقعی با تشخیص
بهبود دهنده و هزینه پلن، یک کوئری میتواند پلن دیگری نیز داشته باشد.
بنابراین پلن دوم نیز با مجموعه عملیاتی متفاوت، جهت اجرای موازی (parallel
execution) برای یک کوئری ایجاد و در حافظه ذخیره میشود.
پلنهای اجرایی برای همیشه در حافظه باقی نخواهند ماند. پلنهای اجرایی دارای
طول عمری طبق فرمول حاصل ضرب هزینه، در تعداد دفعات میباشند. مثلاً پلنی با
هزینه 10 و تعداد دفعات اجرای 5، طول عمر 50 را خواهد داشت. پروسس lazywriter
که یک پروسس داخلی است وظیفه آزاد سازی تمام انواع کشها، از جمله پلن کش را دارد. این پروسس در بازههای مشخص، تمام اشیاء درون حافظه را بررسی کرده
و یک واحد از طول عمر آنها میکاهد.
در موارد زیر، یک پلن از حافظه پاک خواهد شد:
1. به حافظه بیشتری نیاز باشد
2. طول عمر پلن صفر شده باشد
جمله آخر، معمولا باعث ایجاد مشکل میشوند.
اگر optimizer تکست کوئری مشابهی را مشاهده نماید، ولی با پارامترهای متفاوت، به کش پلن مراجعه کرده و اگر در آن جا قرار داشت، از آن مجددا استفاده مینماید. این استفاده مجدد خوب است؛ اما درصورتیکه پارامتر ارسالی نال باشد چه اتفاقی رخ میدهد؟ جدول سفارشات محصول بسیار حجیم است و متاسفانه از پلنی که برای بازگشت 40 رکورد قبلا ایجاد شده، برای بازگشت این حجم بالای از رکوردها استفاده میشود که این کشنده است.
هیچ تضمینی وجود ندارد که از وقوع این اتفاق جلوگیری نمایید؛ اما میتوانید در هنگام توسعه، پروسیجر را شناسایی و نسبت به رفع آنها اقدام نمایید. ابتدا کش پلن را خالی نمایید و سپس پروسیجر را با مقادیر متفاوت، اجرا نمایید. در صورتیکه پلنهای متفاوتی مشاهده نمودید، این یک علامت هشدار است و میبایست نسبت به رفع آنها اقدام فوری نمایید.
راحت بگویید نه!
در قسمت قبل « کار با اسکنر در برنامههای تحت وب (قسمت اول) » دیدی از کاری که قرار است انجام دهیم، رسیدیم. حالا سراغ یک پروژهی عملی و پیاده سازی مطالب مطرح شده میرویم.
ابتدا پروژهی WCF را شروع میکنیم. ویژوال استودیو را باز کرده و از قسمت New Project > Visual C# > WCF یک پروژهی WCF Service Application جدید را مثلا با نام "WcfServiceScanner" ایجاد نمایید. پس از ایجاد، دو فایل IService1.cs و Service1.scv موجود را به IScannerService و ScannerService تغییر نام دهید. سپس ابتدا محتویات کلاس اینترفیس IScannerService را به صورت زیر تعریف نمایید :
[ServiceContract] public interface IScannerService { [OperationContract] [WebInvoke(Method = "GET", BodyStyle = WebMessageBodyStyle.Wrapped, RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json, UriTemplate = "GetScan")] string GetScan(); }
public class ScannerService : IScannerService { public string GetScan() { // TODO Add code here } }
بر روی پروژهی خود راست کلیک کرده و Add Reference را انتخاب نموده و سپس در قسمت COM، گزینهی Microsoft Windows Image Acquisition Library v2.0 را به پروژهی خود اضافه نمایید.
با اضافه شدن این ارجاع به پروژه، دسترسی به فضای نام WIA برای ما امکان پذیر میشود که ارجاعی از آن را در کلاس ScannerService قرار میدهیم.
using WIA;
public string GetScan() { var imgResult = String.Empty; var dialog = new CommonDialogClass(); try { // نمایش فرم پیشفرض اسکنر var image = dialog.ShowAcquireImage(WiaDeviceType.ScannerDeviceType); // ذخیره تصویر در یک فایل موقت var filename = Path.GetTempFileName(); image.SaveFile(filename); var img = Image.FromFile(filename); // img جهت ارسال سمت کاربر و نمایش در تگ Base64 تبدیل تصویر به imgResult = ImageHelper.ImageToBase64(img, ImageFormat.Jpeg); } catch { // از آنجاییه که امکان نمایش خطا وجود ندارد در صورت بروز خطا رشته خالی // بازگردانده میشود که به معنای نبود تصویر میباشد } return imgResult; }
CommonDialogClass کلاس اصلی در اینجا جهت نمایش فرم کار با اسکنر میباشد و متدهای مختلفی را جهت ارتباط با اسکنر در اختیار ما قرار میدهد که بسته به نیاز خود میتوانید از آنها استفاده کنید. برای نمونه در مثال ما نیز متد اصلی که مورد استفاده قرار گرفته، ShowAcquireImage میباشد که این متد، فرم پیش فرض دریافت اسکنر را به کاربر نمایش میدهد و کاربر از طریق آن میتواند قبل از شروع اسکن، یکسری تنظیمات را انجام دهد.
این متد ابتدا به صورت خودکار فرم تعیین دستگاه اسکنر ورودی را نمایش داده :
و سپس فرم پیش فرض اسکنرهای TWAIN را جهت تعیین تنظیمات اسکن نمایش میدهد که این امکان نیز در این فرم فراهم است تا دستگاههای Feeder یا Flated انتخاب گردند.
خروجی این متد همان عکس اسکن شده است که از نوع WIA.ImageFile میباشد و ما پس از دریافتش، ابتدا آن را در یک فایل موقت ذخیره نموده و سپس با استفاده از یک متد کمکی آن را به فرمت Base64 برای درخواست کننده اسکن ارسال مینماییم.
کدهای کلاس کمکی ImageHelper:
public static string ImageToBase64(Image image, System.Drawing.Imaging.ImageFormat format) { if (image != null) { using (MemoryStream ms = new MemoryStream()) { // Convert Image to byte[] image.Save(ms, format); byte[] imageBytes = ms.ToArray(); // Convert byte[] to Base64 String string base64String = Convert.ToBase64String(imageBytes); return base64String; } } return String.Empty; }
این مثال به سادهترین شکل نوشته شد. کلاس دیگری هم در اینجا وجود دارد و در صورتیکه از اسکنر نوع Feeder استفاده میکنید، میتوانید از کدهای آن استفاده کنید.
جهت رفع این خطا، در قسمت Referenceهای پروژه خود، WIA را انتخاب نموده و از Propertiesهای آن خصوصیت Embed Interop Types را به False تغییر دهید؛ مشکل حل میشود.
به سراغ پروژهی ویندوز فرم جهت هاست کردن این WCF سرویس میرویم. میتوانید این سرویس را بر روی یک Console App یا Windows Service هم هاست کنید که در اینجا برای سادگی مثال، از WinForm استفاده میکنیم.
یک پروژهی WinForm جدید را ایجاد کنید و سپس از قسمت Add Reference > Solution به مسیر پروژهی قبلی رفته و dllهای آن را به پروژه خود اضافه نمایید.
Form1.cs را باز کرده و ابتدا دو متغیر زیر را در آن به صورت عمومی تعریف نمایید:
private readonly Uri _baseAddress = new Uri("http://localhost:6019"); private ServiceHost _host;
حال در رویداد Form_Load برنامه، کدهای زیر را جهت هاست کردن سرویس اضافه مینماییم:
private void Form1_Load(object sender, EventArgs e) { _host = new ServiceHost(typeof(WcfServiceScanner.ScannerService), _baseAddress); _host.Open(); } private void Form1_FormClosing(object sender, FormClosingEventArgs e) { _host.Close(); }
فایل App.Config پروژهی WinForm را باز کرده و کدهای آنرا مطابق زیر تغییر دهید:
<?xml version="1.0" encoding="utf-8" ?> <configuration> <startup> <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" /> </startup> <system.serviceModel> <behaviors> <serviceBehaviors> <behavior name="BehaviourMetaData"> <serviceMetadata httpGetEnabled="true" /> </behavior> </serviceBehaviors> </behaviors> <services> <service name="WcfServiceScanner.ScannerService" behaviorConfiguration="BehaviourMetaData"> <endpoint address="" binding="basicHttpBinding" contract="WcfServiceScanner.IScannerService" /> </service> </services> </system.serviceModel> </configuration>
اگر موفق به اجرا نشدید و احیانا با خطای زیر مواجه شدید، اطمینان حاصل کنید که ویژوال استودیو Run as Administrator باشد. مشکل حل خواهد شد.
به سراغ پروژهی بعدی، یعنی وب سایت خود میرویم. یک پروژهی MVC جدید ایجاد نمایید و در View مورد نظر خود، کدهای زیر را جهت صدا زدن متد GetScan اضافه میکنیم.
( از آنجا که کدها به صورت جاوا اسکریپت میباشد، پس مهم نیست که حتما پروژه MVC باشد؛ یک صفحهی HTML ساده هم کافی است).
<a href="#" id="get-scan">Get Scan</a> <img src="" id="img-scanned" /> <script> $("#get-scan").click(function () { var url = 'http://localhost:6019/'; $.get(url, function (data) { $("#img-scanned").attr("src","data:image/Jpeg;base64, "+ data.GetScanResult); }); }); </script>
راه حلهای زیادی برای این مشکل ارائه شده است، و متاسفانه بسیاری از آنها در شرایط پروژهی ما جوابگو نمیباشد (به دلیل هاست روی یک پروژه ویندوزی). تنها راه حل مطمئن (تست شده) استفاده از یک کلاس سفارشی در پروژهی WCF Service میباشد که مثال آن در اینجا آورده شده است.
برای رفع مشکل به پروژه WcfServiceScanner بازگشته و کلاس جدیدی را به نام CORSSupport ایجاد کرده و کدهای زیر را به آن اضافه کنید:
public class CORSSupport : IDispatchMessageInspector { Dictionary<string, string> requiredHeaders; public CORSSupport(Dictionary<string, string> headers) { requiredHeaders = headers ?? new Dictionary<string, string>(); } public object AfterReceiveRequest(ref System.ServiceModel.Channels.Message request, System.ServiceModel.IClientChannel channel, System.ServiceModel.InstanceContext instanceContext) { var httpRequest = request.Properties["httpRequest"] as HttpRequestMessageProperty; if (httpRequest.Method.ToLower() == "options") instanceContext.Abort(); return httpRequest; } public void BeforeSendReply(ref System.ServiceModel.Channels.Message reply, object correlationState) { var httpResponse = reply.Properties["httpResponse"] as HttpResponseMessageProperty; var httpRequest = correlationState as HttpRequestMessageProperty; foreach (var item in requiredHeaders) { httpResponse.Headers.Add(item.Key, item.Value); } var origin = httpRequest.Headers["origin"]; if (origin != null) httpResponse.Headers.Add("Access-Control-Allow-Origin", origin); var method = httpRequest.Method; if (method.ToLower() == "options") httpResponse.StatusCode = System.Net.HttpStatusCode.NoContent; } } // Simply apply this attribute to a DataService-derived class to get // CORS support in that service [AttributeUsage(AttributeTargets.Class)] public class CORSSupportBehaviorAttribute : Attribute, IServiceBehavior { #region IServiceBehavior Members void IServiceBehavior.AddBindingParameters(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase, System.Collections.ObjectModel.Collection<ServiceEndpoint> endpoints, BindingParameterCollection bindingParameters) { } void IServiceBehavior.ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase) { var requiredHeaders = new Dictionary<string, string>(); //Chrome doesn't accept wildcards when authorization flag is true //requiredHeaders.Add("Access-Control-Allow-Origin", "*"); requiredHeaders.Add("Access-Control-Request-Method", "POST,GET,PUT,DELETE,OPTIONS"); requiredHeaders.Add("Access-Control-Allow-Headers", "Accept, Origin, Authorization, X-Requested-With,Content-Type"); requiredHeaders.Add("Access-Control-Allow-Credentials", "true"); foreach (ChannelDispatcher cd in serviceHostBase.ChannelDispatchers) { foreach (EndpointDispatcher ed in cd.Endpoints) { ed.DispatchRuntime.MessageInspectors.Add(new CORSSupport(requiredHeaders)); } } } void IServiceBehavior.Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase) { } #endregion }
using System.ServiceModel; using System.ServiceModel.Channels; using System.ServiceModel.Description; using System.ServiceModel.Dispatcher;
[CORSSupportBehavior] public class ScannerService : IScannerService {
کار تمام است، یکبار دیگر ابتدا پروژهی WcfServiecScanner و سپس پروژه هاست را Build کرده و برنامهی هاست را اجرا کنید. اکنون مشاهده میکنید که با زدن دکمهی اسکن، اسکنر فرم تنظیمات اسکن را نمایش میدهد که پس از زدن دکمهی Scan، پروسه آغاز شده و پس از اتمام، تصویر اسکن شده در صفحهی وب سایت نمایش داده میشود.
نحوهی ذخیره سازی لاگها
سطح سایت
- Client IP address
- User name
- Date
- Time
- Service and instance
- Server name
- Server IP address
- Time taken
- Client bytes sent
- Server bytes sent
- (Service status code (A value of 200 indicates that the request was fulfilled successfully
- (Windows status code (A value of 0 indicates that the request was fulfilled successfully.
- Request type
- Target of operation
- (Parameters (the parameters that are passed to a script
192.168.114.201, -, 03/20/01, 7:55:20, W3SVC2, SERVER, 172.21.13.45, 4502, 163, 3223, 200, 0, GET, /DeptLogo.gif, -,
نام فیلد | نوع حالت مقداردهی | توضیح اتفاقات افتاده | |
Client IP address | 192.168.114.201 | آی پی کلاینت | |
User name | - | کاربر ناشناس است | |
Date | 03/20/01 | تاریخ فعالیت | |
Time | 7:55:20 | ساعت فعالیت | |
Service and instance | W3SVC2 | لاگی که مربوط به سایت خاصی میشود به صورت #W3SVC نمایش داده میشود که علامت # شماره سایت میباشد که در اینجا این لاگ مربوط به سایت شماره 2 است | |
Server name | SERVER | نام سرور | |
Server IP | 172.21.13.45 | آی پی سرور | |
Time taken | 4502 | چقدر انجام عملیات این درخواست به طول انجامیده است که بر حسب میلی ثانیه است. | |
Client bytes sent | 163 | تعداد بایت هایی که از طرف کلاینت به سرور ارسال شده است | |
Server bytes sent | 3223 | تعداد بایت هایی که از طرف سرور به سمت کلاینت ارسال شده است | |
Service status code | 200 | درخواست کاملا موفقیت آمیز بوده است | |
Windows status code | 0 | درخواست کاملا موفقیت آمیز بوده است | |
Request type | GET | نوع درخواست کاربر | |
Target of operation | /DeptLogo.gif | کاربر قصد دانلود یک فایل تصویری GIF داشته است که نامش Deptlogo است | |
Parameters | - | پارامتری ارسال نشده است |
فرمت NCSA: این فرمت توسط مرکز علمی کاربردهای ابرمحاسباتی National Center for Supercomputing Applications ایجاد شده و دقیقا مانند قبلی نمیتوان در آن نوع فیلدها را مشخص کرد و برای جدا سازی، از فاصله space استفاده میکند و ثبت مقدار زمان در آن هم به صورت محلی و هم UTC میباشد.
- Remote host address
- (Remote log name (This value is always a hyphen
- User name
- Date, time, and Greenwich mean time (GMT) offset
- Request and protocol version
- (Service status code (A value of 200 indicates that the request was fulfilled successfully
- Bytes sen
نمونه ای از یک لاگ ثبت شده:
172.21.13.45 - Microsoft\JohnDoe [08/Apr/2001:17:39:04 -0800] "GET /scripts/iisadmin/ism.dll?http/serv HTTP/1.0" 200 3401
نام فیلد | مقدار ثبت شده | توضیح اتفاق افتاده |
Remote host address | 172.21.13.45 | آی پی کلاینت |
Remote log name | - | نامی وجود ندارد |
User name | Microsoft\JohnDoe | نام کاربری |
Date, time, and GMT offset | [08/Apr/2001:17:39:04 -0800] | تاریخ و ساعت فعالیت به صورت محلی که 8 ساعت از مبدا گرینویچ بیشتر است |
Request and protocol version | GET /scripts/iisadmin/ism.dll?http/serv HTTP/1.0 | کاربر با متد GET و Http نسخهی یک، درخواست فایل ism.dll را کرده است. |
Service status code | 200 | عملیات کاملا موفقیت آمیز بود. |
Bytes sent | 3401 | تعداد بایتهای ارسال شده به سمت کاربر |
امنیت در برابر کاربران مهاجم مانند همان فرمت قبلی صورت گرفته است.
فرمت W3C: توسط W3C توسط کنسرسیوم جهانی وب ارائه شده است و یک فرمت customizable ASCII text-based است. به این معنی که میتوان فیلدهایی که در گزارش نهایی میآید را خودتان مشخص کنید، که برای اینکار در کنار لیست، دکمهی Select وجود دارد که میتوانید هر کدام از فیلدهایی را که خواستید، انتخاب کنید تا به ترتیب در خط لاگ ظاهر شوند. تاریخ ثبت به صورت UTC است.
نام فیلد | توضیح | به طور پیش فرض انتخاب شده است |
Date | تاریخ رخ دادن فعالیت | بله |
Time | ساعت زخ دادن فعالیت بر اساس UTC | بله |
Client IP Address | آی پی کلاینت | بله |
User Name | نام کاربری که هویت آن تایید شده و در صورتی که هویت تایید شده نباشد و کاربر ناشناس باشد، جای آن - قرار میگیرد | بله |
Service Name and Instance Number | نام و شماره سایتی که درخواست در آن صورت گرفته است | خیر |
Server Name | نام سروری که لاگ روی آن ثبت میشود | خیر |
Server IP Address | آی پی سرور که لاگ روی آن ثبت میشود | بله |
Server Port | شمره پورتی که سرویس مورد نظر روی آن پورت اعمال میشود. | بله |
Method | متد درخواست مثل GET | بله |
URI Stem | هدف درخواست یا Target مثل index.htm | بله |
URI Query | کوئری ارسال شده برای صفحات داینامیک | بله |
HTTP Status | کد وضیعینی HTTP status | بله |
Win32 Status | کد وضعیتی ویندوز | خیر |
Bytes Sent | تعداد بایتهای ارسال شده به سمت کلاینت | خیر |
Bytes Received | تعداد بایتهای دریافت شده از سمت کلاینت | خیر |
Time Taken | زمان به طول انجامیدن درخواست بر حسب میلی ثانیه | خیر |
Protocol Version | درخواست با چه نسخهای از پروتکل http یا ftp ارسال شده است | خیر |
Host | اگر در هدر درخواست ارسالی این گزینه بوده باشد، نوشته خواهد شد. | خیر |
User Agent | اطلاعات را از هدر درخواست میگیرد. | بله |
Cookie | اگر کوکی رد و بدل شده باشد، محتویات کوکی ارسالی یا دریافت شده | خیر |
Referrer | کاربر از چه سایتی به سمت سایت ما آمده است. | خیر |
Protocol Substatus | در صورت رخ دادن خطا در IIS ، کد خطا بازگردانده میشود. در IIS به منظور امنیت بیشتر و کاهش حملات، محتوای خطاهای رخ داده در IIS به صورت متنی نمایش داده نمیشوند و شامل کد خطایی به اسم Substatus Code هستند تا مدیران شبکه با ردیابی لاگها پی به دلیل خطا و درخواستهای ناموفق ببرند. برای مثال Error 404.2 به این معنی است که فایل درخواستی به دلیل قوانین محدود کنده، قفل شده و قابل ارائه نیست. ولی هکر تنها با خطای 404 یعنی وجود نداشتن فایل روبرو میشود. در حالت substatus code، کد شماره 2 را هم خواهید داشت که در لاگ ثبت میشود. هر شخصی که در سرور توانایی دسترسی به لاگها را داشته باشد، میتواند کد دوم خطا را نیز مشاهده کند. برای مثال مدیر سرور متوجه میشود که یکی از فایلهای مورد نظر به کاربران، خطای 404 نمایش میدهد و با بررسی لاگها متوجه میشود که کد خطا 404.9 هست. از آنجا که ما همهی کدها را حفظ نیستیم به این صفحه رجوع میکنیم و متوجه میشویم تعداد کاربرانی که برای این فایل، اتصال connection ایجاد کردهاند بیش از مقدار مجاز است و مدیر میتواند این وضع را کنترل کند. برای مثال تعداد اتصالات مجاز را نامحدود unlimited تعیین کند. | بله |
- uri-query
- host
- (User-Agent)
- Cookie
- Referrer
- substatus
گزینه Custom : موقعی که شما این گزینه را انتخاب کنید ماژول logging غیرفعال خواهد شد. زیرا این امکان در IIS قابل پیکربندی نیست و نوشتن ماژول آن بر عهده شما خواهد بود؛ با استفاده از اینترفیس های ILogPlugin ، ILogPluginEx و ILogUIPlugin آن را پیاده سازی کنید.
ذخیره اطلاعات به انکدینگ UTF-8 و موضوع امنیت
استفاده از IIS در VS.NET و پروژههای ASP.NET داستان خودش را دارد. در نگارشهای 2002 و 2003 آن، تنها وب سرور قابل استفاده جهت کار با VS.NET همان IIS اصلی بود. مهمترین مشکل این روش، نیاز به داشتن دسترسی مدیریتی بر روی سیستم بود (که در بعضی از شرکتها، این مورد برای عموم کاربران ممنوع است) به همراه نصب جداگانه و تنظیمات مخصوص IIS ، صرفا جهت آزمایش یک برنامهی ساده؛ همچنین با توجه به اینکه IIS جزو کامپوننتها ویندوز بوده و هر نگارشی، IIS خاص خودش را دار است، این مورد هم مشکلات ویژهای را به همراه دارد (برای مثال IIS5 ویندوز XP را با IIS7 ویندوز سرور 2008 در نظر بگیرید؛ یکی برای توسعه یکی جهت محیط کاری). این روش در VS.Net 2005 کنار گذاشته شد و از وب سرور توکاری به نام Cassini یا ASP.NET Development Server استفاده گردید. به این صورت دیگر نیازی به نصب مجزای IIS کامل جهت آزمایش برنامههای ASP.NET نبود و همچنین نیاز به داشتن دسترسی مدیریتی الزامی نیز منتفی گردید. این روش هنوز هم تا نگارش 2010 ویژوال استودیو مرسوم است؛ اما ... اما کسانی که با Cassini کار کرده باشند میدانند که یک سری از رفتارهای آن با IIS واقعی تطابق ندارد و اگر برنامهی ASP.NET شما با Cassini خوب نمایش داده میشود الزامی ندارد که با IIS واقعی هم به همان نحو رفتار کند، برای نمونه رفتار مسیریابی آدرسهای نسبی در IIS واقعی و Cassini یکی نیست. علاوه بر آن IIS های 7 و 7.5 هم امکانات و ویژگیهای خاص خود را دارند که Cassini آنها را پوشش نمیدهد؛ به علاوه این دو فقط در ویندوزهای جدید مانند ویندوز سرور 2008 یا ویندوز 7 قابل دسترسی هستند. به همین جهت اخیرا یک نسخهی سبک و express از IIS 7.5 به صورت جداگانه برای برنامه نویسها فقط جهت آزمودن برنامههای خود تهیه شده است و البته هدفگیری اصلی آن پروژهی WebMatrix است؛ به همراه ویژگیهای جدید IIS7 مانند امکان آزمودن تنظیمات ویژه IIS7 در وب کانفیگ برنامه، پشتیبانی کامل از SSL ، Url Rewrite و سایر ماژولهای IIS7، عدم نیاز به دسترسی مدیریتی برای اجرای آن، امکان اجرای آن بر روی پورتهای مختلف بدون تداخل با وب سرور(های) موجود بر روی سیستم و همچنین برخلاف IIS7 اصلی، بر روی ویندوز XP نیز قابل اجرا است. حجم نگارش IIS Express 7.5 تنها 3.9 مگابایت است:
سرویس پک یک ویژوال استودیوی 2010 (که در زمان نگارش این مطلب نسخهی بتای آن ارائه شده)، یک گزینهی جدید را به منوی کلیک راست بر روی نام پروژه در VS.NET به نام Use IIS Express ، اضافه کرده است تا به سادگی بتوان از این امکان جدید استفاده کرد (یا به عبارتی با IIS Express یکپارچه است و نیاز به تنظیم خاصی ندارد).
در سایر حالات (و نسخههایی که این یکپارچگی وجود ندارد و نخواهد داشت) به صورت زیر میتوان عمل کرد:
روش اول:
دستور زیر را در خط فرمان وارد نمائید:
"C:\Program Files\IIS Express\iisexpress.exe" /path:D:\Prog\1389\MySite\ /port:4326 /clr:v4.0
روش دوم (که در حقیقت همان روش اول با ارائهی پشت صحنهی موقت آن است):
الف) ابتدا به مسیر My Documents\IISExpress\config مراجعه کرده و فایل applicationhost.config را باز کنید. سپس گره مربوط به site را یافته (حدود سطر 153) و گزینهی serverAutoStart را حذف کنید:
<site name="WebSite1" id="1">
<application path="/">
<virtualDirectory path="/" physicalPath="%IIS_SITES_HOME%\WebSite1" />
</application>
<bindings>
<binding protocol="http" bindingInformation=":8080:localhost" />
</bindings>
</site>
<site name="WebSite2" id="2">
<application path="/" applicationPool="Clr4IntegratedAppPool">
<virtualDirectory path="/" physicalPath="D:\Prog\1389\MyTestSite\" />
</application>
<bindings>
<binding protocol="http" bindingInformation=":1389:localhost" />
</bindings>
</site>
Name در اینجا نامی دلخواه است که وارد خواهید نمود.
Id شماره سایتی است که ثبت خواهد شد.
applicationPool در اینجا بسیار مهم است. اگر سایت شما مبتنی بر دات نت 4 است، Clr4IntegratedAppPool را وارد نمائید و اگر غیر از این است، Clr2IntegratedAppPool باید تنظیم شود.
physicalPath همان مسیر پروژه شما است.
در قسمت bindingInformation هم میتوان شماره پورت مورد نظر را وارد کرد.
اکنون فایل applicationhost.config را ذخیره کرده و ببندید.
سپس دستور زیر را در خط فرمان ویندوز وارد نمائید:
"C:\Program Files\IIS Express\iisexpress.exe" /site:WebSite2
تنظیمات دیباگر VS.NET :
تا اینجا تنها موفق شدهایم که این وب سرور آزمایشی را راه اندازی کنیم. اما نکتهی مهم امکان دیباگ کردن برنامه توسط آنرا از دست دادهایم. برای این منظور در VS.NET به خواص پروژه، برگهی Web آن مراجعه کنید. در قسمت Servers گزینهی use custom web server را انتخاب کرده و آدرسی را که در یکی از دو روش فوق ساختهاید وارد نمایید. برای مثال http://localhost:4326/
همچنین باید دقت داشت که در همین قسمت هیچکدام از debuggers ذیل گزینهی use custom web server نباید تیک خورده باشند (چون VS.NET دقیقا نمیداند که باید به کدام پروسه در ویندوز attach شود).
اکنون برنامه را در حالت دیباگ در VS.NET آغاز کنید (بدیهی است فرض بر این است که iisexpress.exe با تنظیمات ذکر شده باید در حال اجرا باشد).
و ... حداقل مزیت آن بسیار سریعتر بودن این روش نسبت به Cassini یا ASP.NET Development Server است.
تا اینجا فقط VS.NET به صورت خودکار مرورگر را باز کرده و سایت نمایش داده میشود؛ اما اگر در قسمتی از کدهای خود breakpoint قرار دهیم کار نمیکند. برای این منظور باید در حین اجرای برنامه، از منوی debug ، گزینهی attach to process را انتخاب کرده و به iisexpress متصل شوید.
چرا چنین بویی به راه میافتد
نشانههای این کد بد بو
- تعداد خطوط زیاد: این معیار نسبت به فناوری و زبان برنامه نویسی مورد استفاده درمحصول متفاوت است؛ ولی در حالت کلی زمانیکه یک کلاس تعداد خطوط کدی بیشتر از 100 داشت، مشکلی بوجود آمده است.
- تعداد وضعیتهای داخلی (در تعریف شیء گرایی) زیاد در یک کلاس، نشان دهنده بزرگی یک کلاس هستند.
- تعداد پارامترهای زیاد سازنده کلاس نشان دهنده متورم شدن کلاس هستند. معمولا مدیریت کردن تعداد وضعیتهای داخلی زیاد منجر به دریافت تعداد زیاد پارامتر ورودی در سازنده میشوند. اگر قانون مربوط به تعداد پارامترهای یک متد را در نظر داشته باشیم و با فرض اینکه سازنده نیز یک متد است، حداکثر پارامترهای مناسب برای یک سازنده 4 خواهد بود.
- متغیرهایی وجود دارند که به صورت دستهای پیشوند یا پسوند خاصی دارند. این پیشوندها یا پسوندها نشان دهنده مواردی هستند که احتمالا میتوانند به کلاس مخصوص به خود انتقال داده شوند. زیرا از نظر منطقی ارتباطی بین آنها وجود دارد و مربوط به کلاس فعلی نمیشوند (زیرا اگر اینگونه بود نیازی به پیشوند یا پسوند نبود).
مشکل این کد بد بو چیست؟
- عدم استفاده از مکانیزمهای مشترک، به دلیل عدم تشکیل کلاس مربوط به آنها
- امکان ایجاد کدهای تکراری فراوان در کلاس
- دشواری تست نویسی برای کلاسها به دلیل وظایف فراوانی که کلاس بر عهده دارد
- افزایش احتمال ایجاد مشکلات مربوط سورس کنترلها و فعالیت همزمان چندین نفر بر روی یک فایل یا کلاس
- به دلیل انجام وظایف فراوان، تغییرات یک کلاس از جنبههای بسیار زیادی باید تست شود
چگونه این بو را رفع کنیم؟
- ایجاد کلاسی مستقل برای هریک از مسئولیتهای موجود در کلاس بزرگ
- ایجاد کلاسی پایه (Base class) برای انجام برخی از امور مشترک در کلاس