Request.ServerVariables["REMOTE_ADDR"]
var sqlDatabasePassword = pulumiConfig.RequireSecret("sql-server-nikola-dev-password"); var sqlDatabaseUserId = pulumiConfig.RequireSecret("sql-server-nikola-dev-user-id"); var resourceGroup = new ResourceGroup("rg-dds-nikola-dev", new ResourceGroupArgs { Name = "rg-dds-nikola-dev", Location = "WestUS" }); var storageAccount = new Account("storagenikoladev", new AccountArgs { Name = "storagenikoladev", ResourceGroupName = resourceGroup.Name, Location = resourceGroup.Location, AccountKind = "StorageV2", AccountReplicationType = "LRS", AccountTier = "Standard", }); var container = new Container("container-nikola-dev", new ContainerArgs { Name = "container-nikola-dev", ContainerAccessType = "blob", StorageAccountName = storageAccount.Name }); var blobStorage = new Blob("blob-nikola-dev", new BlobArgs { Name = "blob-nikola-dev", StorageAccountName = storageAccount.Name, StorageContainerName = container.Name, Type = "Block" }); var appInsights = new Insights("app-insights-nikola-dev", new InsightsArgs { Name = "app-insights-nikola-dev", ResourceGroupName = resourceGroup.Name, Location = resourceGroup.Location, ApplicationType = "web" // also general for mobile apps }); var sqlServer = new SqlServer("sql-server-nikola-dev", new SqlServerArgs { Name = "sql-server-nikola-dev", ResourceGroupName = resourceGroup.Name, Location = resourceGroup.Location, AdministratorLogin = sqlDatabaseUserId, AdministratorLoginPassword = sqlDatabasePassword, Version = "12.0" }); var sqlDatabase = new Database("sql-database-nikola-dev", new DatabaseArgs { Name = "sql-database-nikola-dev", ResourceGroupName = resourceGroup.Name, Location = resourceGroup.Location, ServerName = sqlServer.Name, RequestedServiceObjectiveName = "Basic" }); var appServicePlan = new Plan("app-plan-nikola-dev", new PlanArgs { Name = "app-plan-nikola-dev", ResourceGroupName = resourceGroup.Name, Location = resourceGroup.Location, Sku = new PlanSkuArgs { Tier = "Shared", Size = "D1" } }); var appService = new AppService("app-service-nikola-dev", new AppServiceArgs { Name = "app-service-nikola-dev", ResourceGroupName = resourceGroup.Name, Location = resourceGroup.Location, AppServicePlanId = appServicePlan.Id, SiteConfig = new AppServiceSiteConfigArgs { Use32BitWorkerProcess = true, // X64 not allowed in shared plan! AlwaysOn = false, // not allowed in shared plan! Http2Enabled = true }, AppSettings = { { "ApplicationInsights:InstrumentationKey", appInsights.InstrumentationKey }, { "APPINSIGHTS_INSTRUMENTATIONKEY", appInsights.InstrumentationKey } }, ConnectionStrings = new InputList<AppServiceConnectionStringArgs>() { new AppServiceConnectionStringArgs { Name = "AppDbConnectionString", Type = "SQLAzure", Value = Output.Tuple(sqlServer.Name, sqlDatabase.Name, sqlDatabaseUserId, sqlDatabasePassword).Apply(t => { (string _sqlServer, string _sqlDatabase, string _sqlDatabaseUserId, string _sqlDatabasePassword) = t; return $"Data Source=tcp:{_sqlServer}.database.windows.net;Initial Catalog={_sqlDatabase};User ID={_sqlDatabaseUserId};Password={_sqlDatabasePassword};Max Pool Size=1024;Persist Security Info=true;Application Name=Nikola"; }) }, new AppServiceConnectionStringArgs { Name = "AzureBlobStorageConnectionString", Type = "Custom", Value = Output.Tuple(storageAccount.PrimaryAccessKey, storageAccount.Name).Apply(t => { (string _primaryAccess, string _storageAccountName) = t; return $"DefaultEndpointsProtocol=https;AccountName={_storageAccountName};AccountKey={_primaryAccess};EndpointSuffix=core.windows.net"; }) } } }); appService.OutboundIpAddresses.Apply(ips => { foreach (string ip in ips.Split(',')) { new FirewallRule($"app-srv-{ip}", new FirewallRuleArgs { Name = $"app-srv-{ip}", EndIpAddress = ip, ResourceGroupName = resourceGroup.Name, ServerName = sqlServer.Name, StartIpAddress = ip }); } return (string?)null; });
ASP.NET MVC #23
اجرای برنامههای ASP.NET MVC توسط نگارشهای متفاوت IIS
تا اینجا برای اجرای برنامههای ASP.NET MVC از وب سرور توکار VS.NET استفاده شد که صرفا جهت آزمایش برنامهها طراحی شده است. تا این تاریخ سه رده از وب سرورهای مایکروسافت ارائه شدهاند که برای نصب ASP.NET MVC میتوانند مورد استفاده قرار گیرند و هر کدام هم نکتههای خاص خودشان را دارند که در ادامه به بررسی آنها خواهیم پرداخت.
اجرای برنامههای ASP.NET MVC بر روی IIS 5.x ویندوز XP
پس از ایجاد یک دایرکتوری مجازی بر روی پوشه یک برنامه ASP.NET MVC و سعی در اجرای برنامه، بلافاصله پیغام خطای HTTP 403 forbidden مشاهده میشود.
اولین کاری که برای رفع این مساله باید صورت گیرد، کلیک راست بر روی نام دایرکتوری مجازی در کنسول IIS، انتخاب گزینه خواص و سپس مراجعه به برگه «ASP.NET» آن است. در اینجا شماره نگارش دات نت فریم ورک مورد استفاده را به 4 تغییر دهید (برای نمونه ASP.NET MVC 3.0 مبتنی بر دات نت فریم ورک 4 است).
بعد از این تغییر، بازهم موفق به اجرای برنامههای ASP.NET MVC بر روی IIS 5.x نخواهیم شد؛ چون در آن زمان مفاهیم مسیریابی و Routing که اصل و پایه ASP.NET MVC هستند وجود خارجی نداشتند. این نگارش از IIS به صورت پیش فرض تنها قادر به پردازش درخواستهای رسیدهای که به یک فایل فیزیکی بر روی سرور اشاره میکند، میباشد (یعنی مشکلی با اجرای برنامههای ASP.NET Web forms ندارد).
برای رفع این مشکل، مجددا بر روی نام دایرکتوری مجازی برنامه در کنسول IIS کلیک راست کرده و گزینه خواص را انتخاب کنید. در صفحه ظاهر شده، در برگه «Virtual directory» آن، بر روی دکمه «Configuration» کلیک نمائید. در صفحه باز شده مجددا بر روی دکمه «Add» کلیک کنید.
در صفحه باز شده، مسیر Executable را C:\WINDOWS\Microsoft.NET\Framework\v4.0.30319\aspnet_isapi.dll وارد کرده و Extension را به .* (دات هرچی) تنظیم کنید. همین مقدار تنظیم، برای اجرای برنامههای ASP.NET MVC بر روی IIS 5.x ویندوز XP کفایت میکند.
کاری که در اینجا انجام شده است، نگاشت تمام درخواستهای رسیده صرفنظر از پسوند فایلها، به موتور ASP.NET میباشد. به صورت پیش فرض در IIS 5.x درخواستها تنها بر اساس پسوند فایلها پردازش میشوند. مثلا اگر فایل درخواستی aspx است، درخواست رسیده به aspnet_isapi.dll یاد شده هدایت خواهد شد. اگر پسوند فایل php است به isapi مخصوص آن (در صورت نصب) هدایت میگردد و به همین ترتیب برای سایر سیستمهای دیگر. زمانیکه Extension به «دات هرچی» و Executable به aspnet_isapi.dll دات نت 4 تنظیم میشود، دایرکتوری مجازی تنظیم شده تنها جهت سرویس دهی به یک برنامه ASP.NET عمل خواهد کرد و تمام درخواستهای رسیده به آن، به موتور اجرایی ASP.NET هدایت میشوند.
بدیهی است تنظیمات فوق تنها به یک دایرکتوری مجازی اعمال شدند. اگر نیاز باشد تا بر روی تمام سایتها تاثیر گذار شود، اینبار در کنسول IIS 5.x بر روی «Default web site» کلیک راست کرده و گزینه خواص را انتخاب کنید. در صفحه باز شده به برگه «Home directory» مراجعه کرده و مراحل ذکر شده را تکرار کنید.
مشکل! این روش بهینه نیست.
روش فوق خوبه، کار میکنه، اما بهینه نیست؛ از این جهت که «نگاشت تمام درخواستها به موتور ASP.NET» یعنی پروسه پردازش درخواست یک فایل تصویری، js یا css هم باید از فیلتر موتور ASP.NET عبور کند که ضروری نیست.
برای رفع این مشکل، توصیه شده است که سیستم مسیریابی ASP.NET MVC را در IIS 5.x «پسوند دار» کنید. به این نحو که با مراجعه به فایل Global.asax.cs، تعاریف مسیریابی را به نحو زیر ویرایش کنید:
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.Add(
new Route("{controller}.aspx/{action}/{id}", new MvcRouteHandler())
{
Defaults = new RouteValueDictionary(new
{
controller = "Home",
action = "Index",
id = UrlParameter.Optional
})
});
اینبار برای مثال مسیر http://localhost/MyMvcApp/home.aspx/index به علت داشتن پسوند aspx وارد موتور پردازشی ASP.NET خواهد شد. البته در این حالت URL های تمیز ASP.NET MVC را از دست خواهیم داد و مدام باید دقت داشت که مسیرهای کنترلرها حتما باید به aspx ختم شوند. ضمنا با این تنظیم، دیگر نیازی به تغییر تعاریف نگاشتها در کنسول مدیریتی IIS، نخواهد بود.
اجرای برنامههای ASP.NET MVC بر روی IIS 6.x ویندوز سرور 2003
تمام نکات عنوان شده جهت IIS 5.x در IIS 6.x نیز صادق هستند. به علاوه برای اجرای برنامههای ASP.NET بر روی IIS 6.x باید به دو نکته مهم دیگر نیز دقت داشت:
الف) ASP.NET 4 به صورت پیش فرض در IIS 6.x غیرفعال است که باید با مراجعه به قسمت Web Services Extensions در کنسول مدیریتی IIS، آنرا از حالت prohibited خارج کرد.
ب) در هر Application pool تنها از یک نگارش دات نت فریم ورک میتوان استفاده کرد. برای مثال اگر هم اکنون AppPool1 مشغول سرویس دهی به یک سایت ASP.NET 3.5 است، از آن نمیتوانید جهت اجرای برنامههای ASP.NET MVC 3 به بعد استفاده کنید. زیرا برای مثال ASP.NET MVC 3 مبتنی بر دات نت فریم ورک 4 است. به همین جهت حتما نیاز است تا یک Application pool مجزا را برای برنامههای دات نت 4 در IIS 6 اضافه نمائید و سپس در تنظیمات سایت، از این Application pool جدید استفاده نمائید.
البته روش صحیح و اصولی کار با IIS از نگارش 6 به بعد هم مطابق شرحی است که عنوان شد. برای دستیابی به بهترین کارآیی و امنیت بیشتر، بهتر است به ازای هر سایت، از یک Application pool مجزا استفاده نمائید.
اطلاعات تکمیلی:
نکات نصب برنامههای ASP.NET 4.0 بر روی IIS 6
مروری بر تاریخچه محدودیت حافظه مصرفی برنامههای ASP.NET در IIS
اجرای برنامههای ASP.NET MVC بر روی IIS 7.x ویندوز 7 و ویندوز سرور 2008
اگر برنامه ASP.NET MVC در IIS 7.x در حالت یکپارچه (integrated mode) اجرا شود، بدون نیاز به هیچگونه تغییری در تنظیمات سرور یا برنامه، بدون مشکل قابل اجرا خواهد بود. بدیهی است در اینجا نیز بهتر است به ازای هر برنامه، یک Application pool مجزا را ایجاد کرد.
اما در حالت classic (که برای برنامههای جدید توصیه نمیشود) نیاز است همان مراحل IIS 5,x تکرار شود. البته اینبار مسیر زیر را باید طی کرد تا به صفحه افزودن نگاشتها رسید:
Right-click on a web site -> Properties -> Home Directory tab -> click on the Configuration button -> Mappings tab
نکتهای مهم در تمام نگارشهای IIS
ترتیب نصب دات نت فریم ورک 4 و IIS مهم است. اگر ابتدا IIS نصب شود و سپس دات نت فریم ورک 4، به صورت خودکار، کار نگاشت اطلاعات ASP.NET به IIS صورت خواهد گرفت.
اگر ابتدا دات نت فریم ورک 4 نصب شود و سپس IIS، برای مثال دیگر از برگه ASP.NET در IIS 6.x خبری نخواهد بود. برای رفع این مشکل دستور زیر را در خط فرمان اجرا کنید:
C:\WINDOWS\Microsoft.NET\Framework\v4.0.30319\aspnet_regiis.exe /i
به این ترتیب، اطلاعات مرتبط با موتور ASP.NET مجددا به تنظیمات IIS اضافه خواهند شد.
در دنیای واقعی میزان حافظهی فیزیکی سرورها محدود است. در SQL Server 2014 راه حلی برای این مشکل تحت عنوان Buffer Pool Extensions ارائه شدهاست که محل قرارگیری آنرا در تصویر ذیل مشاهده میکنید:
Buffer Pool Extensions از یک فایل ساده که به آن Extension File نیز گفته میشود، تشکیل شدهاست و امکان ذخیره سازی آن بر روی هاردهای سریعی مانند SSD Driveها میسر است. این فایل، ساختاری را همانند page file، در سیستم عامل ویندوز دارد. در این حالت بجای اضافه کردن RAM بیشتر به سرور، یک Extension File را میتوان بکار گرفت. هر زمان که Buffer Pool اصلی تحت فشار قرار گیرد (به میزان حافظهای بیش از حافظهی فیزیکی سرور نیاز باشد)، از این افزونهی فایلی استفاده خواهد شد.
اطلاعات جزئیات Buffer Pool را توسط کوئری ذیل میتوان بدست آورد:
Select * from sys.dm_os_buffer_descriptors
نحوهی فعال سازی و تنظیم Buffer Pool Extensions
قبل از هر کاری بهتر است وضعیت افزونهی Buffer pool را بررسی کرد:
select * from sys.dm_os_buffer_pool_extension_configuration
همانطور که ملاحظه میکنید، در حالت پیش فرض غیرفعال است.
سپس یک فایل یک گیگابایتی را به عنوان افزونهی Buffer pool ایجاد میکنیم.
ALTER SERVER CONFIGURATION SET BUFFER POOL EXTENSION ON (FILENAME = 'd:\BufferPoolExt.BPE', SIZE = 1GB);
اینبار اگر کوئری اول را اجرا کنیم، چنین خروجی قابل مشاهده است:
این فایل به صورت خودکار در حین ریاستارت یا خاموش شدن سرور، حذف شده و با راه اندازی مجدد آن، باز تولید خواهد شد.
تغییر اندازهی افزونهی Buffer pool
اگر سعی کنیم، یک گیگابایت را مثلا به 10 گیگابایت افزایش دهیم:
ALTER SERVER CONFIGURATION SET BUFFER POOL EXTENSION ON (FILENAME = 'd:\BufferPoolExt.BPE', SIZE = 10GB);
Could not change the value of the 'BPoolExtensionPath' property
ALTER SERVER CONFIGURATION SET BUFFER POOL EXTENSION OFF
همچنین توصیه شدهاست که پس از غیرفعال کردن این افزونه، بهتر است یکبار instance جاری را ری استارت کنید.
چه زمانی بهتر است از افزونهی Buffer pool استفاده شود؟
در محیطهای read-heavy OLTP، استفاده از یک چنین افزونهای میتواند میزان کارآیی و پاسخگویی سیستم را به شدت افزایش دهد (تا 50 درصد).
سؤال: آیا غیرفعال کردن افزونهی Buffer pool سبب از دست رفتن اطلاعات میشود؟
خیر. BPE، تنها clean pages را در خود ذخیره میکند؛ یعنی تنها اطلاعاتی که Commit شدهاند در آن حضور خواهند داشت و در این حالت حذف آن یا ری استارت کردن سرور، سبب از دست رفتن اطلاعات نخواهند شد.
برای مطالعه بیشتر
Buffer Pool Extension
SQL Server 2014 Buffer Pool Extensions
Do you require a SSD to use the Buffer Pool Extension feature in SQL Server 2014
Buffer Pool Extensions in SQL Server 2014
SQL Server 2014 – Buffer Pool Extension
عملگرهای تولید، برای ما توالی ایجاد میکنند و تفاوتهای عمدهای با سایر عملگرهای پرس و جو دارند که در بخش زیر به آنها اشاره میکنیم:
1- هیچ توالی ورودی را دریافت نمیکنند.
2- این عملگرها بصورت متد الحاقی پیاده سازی نشدهاند و بصورت متدهای استاتیک در کلاس Enumerable قرار گرفتهاند.
امضاء زیر مربوط به متد Empty میباشد:
public static IEnumerable<TResult> Empty<TResult>()
Empty
عملگر Empty یک توالی بدون عنصر (Empty) را بر اساس نوع مشخص شده، ایجاد میکند.
در کد زیر نحوه ایجاد یک توالی خالی از نوع Ingredient نشان داده شده است.
IEnumerable<Ingredient> ingredients = Enumerable.Empty<Ingredient>(); Console.WriteLine(ingredients.Count());
0
معادل عملگر Empty، کلمه کلیدی در عبارتهای جستجو وجود ندارد. ترکیب دو روش میتواند خروجی دلخواه را تولید کند.
Range
عملگر پرس و جوی Range، یک توالی از مقادیر صحیح متوالی را برای ما ایجاد میکند. اولین پارامتر این عملگر عنصر آغاز کننده توالی است و دومین پارامتر این عملگر تعداد کل عناصر توالی تولید شده، با احتساب عنصر اول خواهد بود.
مثال:
IEnumerable<int> fiveToTen = Enumerable.Range(5,6); foreach (var num in fiveToTen) { Console.WriteLine(num); }
5 6 7 8 9 10
پیاده سازی توسط عبارتهای جستجو
معادل عملگر Range، کلمه کلیدی در عبارتهای جستجو وجود ندارد. ترکیب دو روش میتواند خروجی دلخواه را تولید کند.
Repeat
عملگر پرس و جوی Repeat یک عدد را به تعداد بار مشخصی در توالی خروجی تکرار میکند.
مثال:
IEnumerable<int> fiveToTen = Enumerable.Repeat(42, 6); foreach (var num in fiveToTen) { Console.WriteLine(num); }
42 42 42 42 42 42
معادل عملگر Repeat، کلمه کلیدی در عبارتهای جستجو وجود ندارد. ترکیب دو روش میتواند خروجی دلخواه را تولید کند.
عملگرهای کمی (Quantifier Operators)
عملگرهای Quantifier یک توالی ورودی را گرفته، آن را ارزیابی کرده و یک مقدار منطقی را باز میگردانند.
عملگر Contains
عملگر Contains عناصر یک توالی را ارزیابی میکند و در صورتیکه مقدار مورد نظر ما در توالی وجود داشته باشد، ارزش True باز میگرداند.
مثال:
int[] nums = {1, 2, 3}; bool isTowThere = nums.Contains(2); bool isFiveThere = nums.Contains(5); Console.WriteLine(isTowThere); Console.WriteLine(isFiveThere);
True False
پیاده سازی توسط عبارتهای جستجو
معادل عملگر Contains، کلمه کلیدی در عبارتهای جستجو وجود ندارد. ترکیب دو روش میتواند خروجی دلخواه را تولید کند.
عملگر Any
عملگر Any دو امضاء مختلف را دارد:
1- اولین امضاء: در صورتیکه توالی شامل حداقل یک عنصر باشد، ارزش True بازگردانده میشود.
2- دومین امضاء: یک عبارت پیش بینی را قبول میکند. در صورتیکه حداقل یکی از عناصر توالی، عبارت پیش بینی را تامین کند، ارزش صحیح باز گردانده میشود.
مثال: بررسی امضاء اول عملگر Any
int[] nums = { 1, 2, 3 }; IEnumerable<int> noNums = Enumerable.Empty<int>(); Console.WriteLine(nums.Any()); Console.WriteLine(noNums.Any());
True False
int[] nums = { 1, 2, 3 }; bool areAnyEvenNumbers = nums.Any(x => x % 2 == 0); Console.WriteLine(areAnyEvenNumbers);
True
پیاده سازی توسط عبارتهای جستجو
معادل عملگر Any، کلمه کلیدی در عبارتهای جستجو وجود ندارد. ترکیب دو روش میتواند خروجی دلخواه را تولید کند.
عملگر All
عملگر پرس و جوی All، یک عبارت پیش بینی را دریافت میکند و عناصر توالی ورودی را بر مبنای آن ارزیابی میکند تا مشخص شود همه عناصر، شرط پیش بینی را تامین میکنند.
در کد زیر بررسی میکنیم که آیا همه عناصر توالی مواد غذایی، جزء مواد غذایی کم چرب میباشند یا خیر .
Ingredient[] ingredients = { new Ingredient { Name = "Sugar", Calories = 500 }, new Ingredient { Name = "Egg", Calories = 100 }, new Ingredient { Name = "Milk", Calories = 150 }, new Ingredient { Name = "Flour", Calories = 50 }, new Ingredient { Name = "Butter", Calories = 400 } }; bool isLowFatRecipe = ingredients.All(x => x.Calories < 200); Console.WriteLine(isLowFatRecipe);
False
نکته : عملگر All به محض پیدا کردن عنصری که شرط مشخص شده را نقض کند، ارزش False را باز میگرداند و ادامه بررسی عناصر باقی مانده را متوقف میکند.
پیاده سازی توسط عبارتهای جستجو
معادل عملگر Any، کلمه کلیدی در عبارتهای جستجو وجود ندارد. ترکیب دو روش میتواند خروجی دلخواه را تولید کند.
عملگر SequenceEqual
عملگر SequenceEqual دو توالی را با هم مقایسه کرده و در صورتیکه عناصر هر دو توالی برابر و ترتیب قرار گیری آنها نیز یکسان باشند، ارزش True باز گردانده میشود.
مثال:
IEnumerable<int> sequence1 = new[] {1, 2, 3}; IEnumerable<int> sequence2 = new[] { 1, 2, 3 }; bool isSeqEqual = sequence1.SequenceEqual(sequence2); Console.WriteLine(isSeqEqual);
True
در صورتی که دو توالی عناصر یکسانی داشته باشند، ولی ترتیب قرار گیری عناصر با هم یکسان نباشند، عملگر ارزش False را باز میگرداند.
مثال :
IEnumerable<int> sequence1 = new[] { 1, 2, 3 }; IEnumerable<int> sequence2 = new[] { 3, 2, 1 }; bool isSeqEqual = sequence1.SequenceEqual(sequence2); Console.WriteLine(isSeqEqual);
False
معادل عملگر SequenceEqual، کلمه کلیدی در عبارتهای جستجو وجود ندارد. ترکیب دو روش میتواند خروجی دلخواه را تولید کند.
عملگرهای تجمیع/تجمعی Aggregate Operators
عملگرهای Aggregate یک توالی ورودی را دریافت و یک مقدار عددی (Scalar Value) را باز میگردانند. مقدار بازگردانده شده، حاصل یک عملیات محاسباتی میباشد.
لیستی از عملگرهای تجمیع ( Aggregate Operators ):
• Count
• LongCount
• Sum
• Min
• Max
• Average
• Aggregate
عملگر Count
عملگر Count، تعداد عناصر توالی ورودی را باز میگرداند. عملگر Count، دو امضاء مختلف دارد. یکی از این امضاءها یک عبارت پیش بینی را میپذیرد.
کد زیر، امضاء اول عملگر Count را نشان میدهد:
int[] nums = { 1, 2, 3 }; int numberOfElements = nums.Count(); Console.WriteLine(numberOfElements);
3
وقتی عبارت پیش بینی بکار گرفته میشود، عملگر Count تنها عناصری را که شرط را تامین کنند، شمارش میکند.
در کد زیر عملگر Count، همه عناصر زوج توالی ورودی را شمارش میکند:
int[] nums = { 1, 2, 3 }; int numberOfEvenElements = nums.Count(x => x % 2 == 0); Console.WriteLine(numberOfEvenElements);
1
پیاده سازی توسط عبارتهای جستجو
معادل عملگر Count ، کلمهی کلیدی در عبارتهای جستجو وجود ندارد. ترکیب دو روش میتواند خروجی دلخواه را تولید کند.
عملگر LongCount
این عملگر مثل عملگر Count عمل میکند، اما با این تفاوت که خروجی آن به جای نوع int از نوع long میباشد. این عملگر برای شمارش توالیهای ورودی بسیار بزرگ مورد استفاده قرار میگیرد.
پیاده سازی توسط عبارتهای جستجو
معادل عملگر LongCount، کلمه کلیدی در عبارتهای جستجو وجود ندارد. ترکیب دو روش میتواند خروجی دلخواه را تولید کند.
عملگر Sum
این عملگر مجموع تمامی عناصر یک توالی را باز میگرداند.
در کد زیر جمع عناصر یک توالی از نوع int را مشاهده میکنید:
int[] nums = { 1, 2, 3 }; int total = nums.Sum(); Console.WriteLine(total);
6
عملگر Sum میتواند بر روی توالیهایی از نوع <IEnumerable<T و بر روی اعضای عددی آنها اعمال شود.
مثال:
Ingredient[] ingredients = { new Ingredient { Name = "Sugar", Calories = 500 }, new Ingredient { Name = "Egg", Calories = 100 }, new Ingredient { Name = "Milk", Calories = 150 }, new Ingredient { Name = "Flour", Calories = 50 }, new Ingredient { Name = "Butter", Calories = 400 } }; int totalCalories = ingredients.Sum(x => x.Calories); Console.WriteLine(totalCalories);
1200
پیاده سازی توسط عبارتهای جستجو
معادل عملگر Sum، کلمه کلیدی در عبارتهای جستجو وجود ندارد. ترکیب دو روش میتواند خروجی دلخواه را تولید کند.
عملگر Average
این عملگر میانگین عناصر توالیهای عددی را محاسبه میکند.
مثال:
int[] nums = { 1, 2, 3 }; var avg = nums.Average(); Console.WriteLine(avg);
2
همانطور که در کد بالا مشاهده میکنید، نوع متغیر avg صراحتا مشخص نشده و از نوع var استفاده شده است. تابع average بر اساس توالی ورودی، انواع مختلفی از نوع دادههای عددی را به خروجی ارسال میکند (double,float,decimal).
همانند عملگر Sum، عملگر Average میتواند بر روی اعضای عددی توالیهایی که از نوع<IEnumarable<T هستند، اعمال شود.
مثال:
Ingredient[] ingredients = { new Ingredient { Name = "Sugar", Calories = 500 }, new Ingredient { Name = "Egg", Calories = 100 }, new Ingredient { Name = "Milk", Calories = 150 }, new Ingredient { Name = "Flour", Calories = 50 }, new Ingredient { Name = "Butter", Calories = 400 } }; var avgCalories = ingredients.Average(x => x.Calories); Console.WriteLine(avgCalories);
240
پیاده سازی توسط عبارتهای جستجو
معادل عملگر Average، کلمه کلیدی در عبارتهای جستجو وجود ندارد. ترکیب دو روش میتواند خروجی دلخواه را تولید کند.
عملگر Min
عملگر Min کوچکترین عنصر توالی را باز میگرداند.
مثال:
int[] nums = { 3, 2, 1 }; var smallest = nums.Min(); Console.WriteLine(smallest);
1
مثال:
Ingredient[] ingredients = { new Ingredient { Name = "Sugar", Calories = 500 }, new Ingredient { Name = "Egg", Calories = 100 }, new Ingredient { Name = "Milk", Calories = 150 }, new Ingredient { Name = "Flour", Calories = 50 }, new Ingredient { Name = "Butter", Calories = 400 } }; var smallestCalories = ingredients.Min(x => x.Calories); Console.WriteLine(smallestCalories);
پیاده سازی توسط عبارتهای جستجو
معادل عملگر Min ، کلمه کلیدی در عبارتهای جستجو وجود ندارد.ترکیب دو روش میتواند خروجی دلخواه را تولید کند.
عملگر Max
عملگر Max بزرگترین عنصر توالی را باز میگرداند.
مثال:
int[] nums = { 1 ,3, 2 }; var largest = nums.Max(); Console.WriteLine(largest);
3
پیاده سازی توسط عبارتهای جستجو
معادل عملگر Max، کلمه کلیدی در عبارتهای جستجو وجود ندارد. ترکیب دو روش میتواند خروجی دلخواه را تولید کند.
Aggregate
عملگرهای تجمعی که تا اینجا معرفی شدند، تنها یک کار را انجام میدادند. اما عملگر Aggregate امکان تعریف یک پرس و جوی تجمیع سفارشی و پیشرفتهتر را که بر روی توالی ورودی اعمال میشود نیز مهیا میکند.
عملگر Aggregate دو نسخه دارد:
1- نسخهای که اجازه استفاده از یک عدد را به عنوان مقدار Seed، به ما میدهد (مقدار آغازین یا Seed).
2- نسخهای که از عنصر ابتدایی توالی به عنوان مقدار Seed استفاده میکند.
هر دو نسخه این عملگر به یک تابع انباره (accumulator function) جهت نگهداری نتیجه نیاز دارند.
کد زیر شبیه سازی عملگر Sum توسط عملگر Aggregate میباشد:
int[] nums = {1, 2, 3}; var result = nums.Aggregate(0, (currentElement, runningTotal) => runningTotal + currentElement); Console.WriteLine(result);
6
کد زیر شبیه سازی عملیات فاکتوریل را با در نظر گرفتن عنصر اول توالی، به عنوان مقدار Seed نشان میدهد:
int[] nums = { 1, 2, 3 ,4,5}; var result = nums.Aggregate((runningProduct, nextfactor) => runningProduct * nextfactor); Console.WriteLine(result);
120
پیاده سازی توسط عبارتهای جستجو
معادل عملگر Aggregate، کلمه کلیدی در عبارتهای جستجو وجود ندارد. ترکیب دو روش میتواند خروجی دلخواه را تولید کند.
هدایت سراسری و خودکار کاربران اعتبارسنجی نشده به صفحهی لاگین
در برنامهی این سری، اگر کاربری که به سیستم وارد نشدهاست، بر روی دکمهی Book یک اتاق کلیک کند، فقط پیام «Not Authorized» را مشاهده خواهد کرد که تجربهی کاربری مطلوبی بهشمار نمیرود. بهتر است در یک چنین حالتی، کاربر را به صورت خودکار به صفحهی لاگین هدایت کرد و پس از لاگین موفق، مجددا او را به همین آدرس درخواستی پیش از نمایش صفحهی لاگین، هدایت کرد. برای مدیریت این مساله کامپوننت جدید RedirectToLogin را طراحی میکنیم که جایگزین پیام «Not Authorized» در کامپوننت ریشهای BlazorWasm.Client\App.razor خواهد شد. بنابراین ابتدا فایل جدید BlazorWasm.Client\Pages\Authentication\RedirectToLogin.razor را ایجاد میکنیم. چون این کامپوننت بدون مسیریابی خواهد بود و قرار است مستقیما داخل کامپوننت دیگری درج شود، نیاز است فضای نام آنرا نیز به فایل BlazorWasm.Client\_Imports.razor اضافه کرد:
@using BlazorWasm.Client.Pages.Authentication
@using System.Security.Claims @inject NavigationManager NavigationManager if(AuthState is not null) { <div class="alert alert-danger"> <p>You [@AuthState.User.Identity.Name] do not have access to the requested page</p> <div> Your roles: <ul> @foreach (var claim in AuthState.User.Claims.Where(c => c.Type == ClaimTypes.Role)) { <li>@claim.Value</li> } </ul> </div> </div> } @code { [CascadingParameter] private Task<AuthenticationState> AuthenticationState {set; get;} AuthenticationState AuthState; protected override async Task OnInitializedAsync() { AuthState = await AuthenticationState; if (!IsAuthenticated(AuthState)) { var returnUrl = NavigationManager.ToBaseRelativePath(NavigationManager.Uri); if (string.IsNullOrEmpty(returnUrl)) { NavigationManager.NavigateTo("login"); } else { NavigationManager.NavigateTo($"login?returnUrl={Uri.EscapeDataString(returnUrl)}"); } } } private bool IsAuthenticated(AuthenticationState authState) => authState?.User?.Identity is not null && authState.User.Identity.IsAuthenticated; }
در اینجا روش کار کردن با AuthenticationState را از طریق کدنویسی ملاحظه میکنید. در زمان بارگذاری اولیهی این کامپوننت، بررسی میشود که آیا کاربر جاری، به سیستم وارد شدهاست یا خیر؟ اگر خیر، او را به سمت صفحهی لاگین هدایت میکنیم. اما اگر کاربر پیشتر به سیستم وارد شده باشد، متن شما دسترسی ندارید، به همراه لیست نقشهای او در صفحه ظاهر میشوند که برای دیباگ برنامه مفید است و دیگر به سمت صفحهی لاگین هدایت نمیشود.
در ادامه برای استفاده از این کامپوننت، به کامپوننت ریشهای BlazorWasm.Client\App.razor مراجعه کرده و قسمت NotAuthorized آنرا به صورت زیر، با معرفی کامپوننت RedirectToLogin، جایگزین میکنیم:
<NotAuthorized> <RedirectToLogin></RedirectToLogin> </NotAuthorized>
چگونه دسترسی نقش ثابت Admin را به تمام صفحات محافظت شده برقرار کنیم؟
اگر خاطرتان باشد در زمان ثبت کاربر ادمین Identity، تنها نقشی را که برای او ثبت کردیم، Admin بود که در تصویر فوق هم مشخص است؛ اما ویژگی Authorize استفاده شده جهت محافظت از کامپوننت (attribute [Authorize(Roles = ConstantRoles.Customer)]@)، تنها نیاز به نقش Customer را دارد. به همین جهت است که کاربر وارد شدهی به سیستم، هرچند از دیدگاه ما ادمین است، اما به این صفحه دسترسی ندارد. بنابراین اکنون این سؤال مطرح است که چگونه میتوان به صورت خودکار دسترسی نقش Admin را به تمام صفحات محافظت شدهی با نقشهای مختلف، برقرار کرد؟
برای رفع این مشکل همانطور که پیشتر نیز ذکر شد، نیاز است تمام نقشهای مدنظر را با یک کاما از هم جدا کرد و به خاصیت Roles ویژگی Authorize انتساب داد؛ و یا میتوان این عملیات را به صورت زیر نیز خلاصه کرد:
using System; using BlazorServer.Common; using Microsoft.AspNetCore.Authorization; namespace BlazorWasm.Client.Utils { [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)] public class RolesAttribute : AuthorizeAttribute { public RolesAttribute(params string[] roles) { Roles = $"{ConstantRoles.Admin},{string.Join(",", roles)}"; } } }
پس از این تعریف میتوان در کامپوننتها، ویژگی Authorize نقش دار را با ویژگی جدید Roles، جایگزین کرد که همواره دسترسی کاربر Admin را نیز برقرار میکند:
@attribute [Roles(ConstantRoles.Customer, ConstantRoles.Employee)]
مدیریت سراسری خطاهای حاصل از درخواستهای HttpClient
تا اینجا نتایج حاصل از شکست اعتبارسنجی سمت کلاینت را به صورت سراسری مدیریت کردیم. اما برنامههای سمت کلاینت، به کمک HttpClient خود نیز میتوانند درخواستهایی را به سمت سرور ارسال کرده و در پاسخ، برای مثال not authorized و یا forbidden را دریافت کنند و یا حتی internal server error ای را در صورت بروز استثنایی در سمت سرور.
فرض کنید Web API Endpoint جدید زیر را تعریف کردهایم که نقش ادیتور را میپذیرد. این نقش، جزو نقشهای تعریف شدهی در برنامه و سیستم Identity ما نیست. بنابراین هر درخواستی که به سمت آن ارسال شود، برگشت خواهد خورد و پردازش نمیشود:
namespace BlazorWasm.WebApi.Controllers { [Route("api/[controller]")] [Authorize(Roles = "Editor")] public class MyProtectedEditorsApiController : Controller { [HttpGet] public IActionResult Get() { return Ok(new ProtectedEditorsApiDTO { Id = 1, Title = "Hello from My Protected Editors Controller!", Username = this.User.Identity.Name }); } } }
namespace BlazorWasm.Client.Services { public class ClientHttpInterceptorService : DelegatingHandler { private readonly NavigationManager _navigationManager; private readonly ILocalStorageService _localStorage; private readonly IJSRuntime _jsRuntime; public ClientHttpInterceptorService( NavigationManager navigationManager, ILocalStorageService localStorage, IJSRuntime JsRuntime) { _navigationManager = navigationManager ?? throw new ArgumentNullException(nameof(navigationManager)); _localStorage = localStorage ?? throw new ArgumentNullException(nameof(localStorage)); _jsRuntime = JsRuntime ?? throw new ArgumentNullException(nameof(JsRuntime)); } protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { // How to add a JWT to all of the requests var token = await _localStorage.GetItemAsync<string>(ConstantKeys.LocalToken); if (token is not null) { request.Headers.Authorization = new AuthenticationHeaderValue("bearer", token); } var response = await base.SendAsync(request, cancellationToken); if (!response.IsSuccessStatusCode) { await _jsRuntime.ToastrError($"Failed to call `{request.RequestUri}`. StatusCode: {response.StatusCode}."); switch (response.StatusCode) { case HttpStatusCode.NotFound: _navigationManager.NavigateTo("/404"); break; case HttpStatusCode.Forbidden: // 403 case HttpStatusCode.Unauthorized: // 401 _navigationManager.NavigateTo("/unauthorized"); break; default: _navigationManager.NavigateTo("/500"); break; } } return response; } } }
با ارثبری از کلاس پایهی DelegatingHandler میتوان متد SendAsync تمام درخواستهای ارسالی توسط برنامه را بازنویسی کرد و تحت نظر قرار داد. برای مثال در اینجا، پیش از فراخوانی await base.SendAsync کلاس پایه (یا همان درخواست اصلی که در قسمتی از برنامه صادر شدهاست)، یک توکن را به هدرهای درخواست، اضافه کردهایم و یا پس از این فراخوانی (که معادل فراخوانی اصل کد در حال اجرای برنامه است)، با بررسی StatusCode بازگشتی از سمت سرور، کاربر را به یکی از صفحات یافت نشد، خطایی رخ دادهاست و یا دسترسی ندارید، هدایت کردهایم. برای نمونه کامپوننت Unauthorized.razor را با محتوای زیر تعریف کردهایم:
@page "/unauthorized" <div class="alert alert-danger mt-3"> <p>You don't have access to the requested resource.</p> </div>
پس از تدارک این Interceptor سراسری، نوبت به معرفی آن به برنامهاست که ... در ابتدا نیاز به نصب بستهی نیوگت زیر را دارد:
dotnet add package Microsoft.Extensions.Http
namespace BlazorWasm.Client { public class Program { public static async Task Main(string[] args) { var builder = WebAssemblyHostBuilder.CreateDefault(args); //... // builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) }); /*builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.Configuration.GetValue<string>("BaseAPIUrl")) });*/ // dotnet add package Microsoft.Extensions.Http builder.Services.AddHttpClient( name: "ServerAPI", configureClient: client => { client.BaseAddress = new Uri(builder.Configuration.GetValue<string>("BaseAPIUrl")); client.DefaultRequestHeaders.Add("User-Agent", "BlazorWasm.Client 1.0"); } ) .AddHttpMessageHandler<ClientHttpInterceptorService>(); builder.Services.AddScoped<ClientHttpInterceptorService>(); builder.Services.AddScoped(sp => sp.GetRequiredService<IHttpClientFactory>().CreateClient("ServerAPI")); //... } } }
کدهای کامل این مطلب را از اینجا میتوانید دریافت کنید: Blazor-5x-Part-33.zip
آموزش PouchDB : معرفی
هدف این مقاله بر این است که شما را با دیتابیس PouchDB آشنا سازد .
در مطلب اول هدف فقط آشنایی و نحوه نصب PouchDB قرار خوهد داشت و در مطالب بعدی نحوه آشنایی با نحوه کدنویسی و استفاده به صورت آفلاین یا آنلاین بررسی خواهد شد .
فهرست مطالب :
- بخش اول : معرفی PouchDB
- شروع به کار با PouchDB
- نحوه استفاده از API ها
- سوالات متداول در مورد PouchDB
- خطاهای احتمالی
- پروژهها و پلاگین های PouchDB
- فایرفاکس 12 و بالاتر
- گوگل کروم 19 و بالاتر
- اپرا 12 و بالاتر
- سافاری 5 و بالاتر
- اینترنت اکسپلورر 10 و بالاتر
- NodeJs 0.10 و بالاتر
- و به صورت شگفت انگیزی در Apache Cordova
<script src="pouchdb-2.1.0.min.js"></script>
$ npm install pouchdb
یکی از نکات امنیتی که استاندارد Owasp بررسی مینماید هدر X-Content-Type-Options است که جهت جلوگیری از حملات از طریق فایلهای نامرتبط میباشد. در این رخنه ممکن است فایلی که مرورگر دریافت میکند با آنچه که وب سایت ما آن را میشناسد متفاوت باشد. به عنوان مثال یک فایل اسکریپت که به عنوان یک فایل استایل معرفی میگردد ولی قابلیت اجرای کدهای آن در مرورگر امکان پذیر است؛ به این نوع حملات MIME Sniffing میگویند. در یکی از سایتهایی که در حال حاضر در حال توسعه آن هستیم بخشی از گزارشها با استفاده از ابزار FastReport ایجاد شده بود و با توجه به اینکه در این ابزار از فایلهای axd استفاده میگردید و مرورگر نوع دیتای برگشتی و MimeType معرفی شده را همخوان نمیدانست، از نمایش و بارگذاری آن ممانعت به عمل آورده و در نتیجه فایل گزارش دیده نمیشد. در این مقاله قصد داریم به معرفی این نوع حمله، روش جلوگیری از آن و همچنین رفع محدودیت پیش آمده را بررسی کرده تا در موارد مشابه از آن استفاده نماییم.
Mime Sniffing
زمانیکه مرورگر در هدر(پاسخ) Response، نوع محتوای ارسال شده، Content-Type را دریافت نکند، یا مرورگر متوجه مغایرتی در آن شود، این نوع رفتار را Mime Sniffing شناسایی میکند. نحوه این شناساییها در هر مرورگری میتواند متفاوت باشد؛ ولی عموما بر اساس type ارسالی و پسوند مورد نظر میباشد. در بعضی از موارد نیز خواندن بایتهای ابتدایی یک فایل نیز میتواند نشان دهد که محتوای ارسالی واقعا چیست. به عنوان مثال برای فایلهایی با پسوند Gif، الگوی بایتهای ابتدایی شامل 47 49 46 38 39 میباشد؛ ولی از آنجا که در همه فایلها، بایتهای ابتدایی الگوی یکسانی ندارند، پس نمیتوان به این روش نیز بسنده کرد.
روش اینکه به مروگر بگوییم جلوی این نوع حملات را بگیرد و در صورت شناسایی Sniffing از اجرای آن سر باز بزند، استفاده از هدر X-Content-Type-Options میباشد که نحوه افزودن آن در فایل web.config به شکل زیر است:
<httpProtocol> <customHeaders> ... <remove name="X-Content-Type-Options"/> <add name="X-Content-Type-Options" value="nosniff" /> .. </customHeaders> </httpProtocol>
در پروژه ما به دلیل اینکه بخشی از گزارشها با استفاده از FastReport طراحی شده بود این مورد برای ما ایجاد مشکل میکرد و در گزارش نمایش داده نمیشد و در کنسول پیامهایی به شکل زیر دریافت میکردیم:
Refused to execute script from 'https://localhost:44377/WebResource.axd?d=xxx' because its MIME type ('text/js') is not executable, and strict MIME type checking is enabled.
با نگاهی به Response دریافتی نیز میتوان بازگشت هدر امنیتی X-Content-Type-Options را نیز مشاهده نمود:
پس باید این هدر را برای بعضی از آدرسها که میتوانند دچار مشکلات اجرایی گردند حذف کرده و برای مابقی بخشها همچنان این هدر فعال باشد؛ پس با افزودن کد زیر به web.config، هدر مورد نظر را برای این نوع فایل حذف میکنیم:
<location path="WebResource.axd" > <system.webServer> <httpProtocol> <customHeaders> <remove name="X-Content-Type-Options" /> </customHeaders> </httpProtocol> </system.webServer> </location>
نتیجه آن را میتوانید در صفحه ذیل برای همان درخواست و پاسخ قبلی نیز مشاهده نمایید:
موقعی که از کاربر یک عکس رو دریافت میکنیم نیازه که بررسی شه که آیا این فایل واقعا عکسه یا نه.
برای کنترل این موضوع Attributeهای اعتبارسنجی بهترین انتخاب هستن و با یکبار افزودن میتوان از آنها در هر جایی استفاده کرد.
سمت سرور اینکار به این صورت هست ^
اما سمت کلاینت حتی اگه بیایم فرمت فایل رو هم بررسی کنیم بازهم جوابگو نیست و میشه به راحتی فرمت یک فایل ZIP رو به PNG تغییر داد و ارسالش کرد و عملا اعتبارسنجی سمت کلاینت کار خاصی رو انجام نمیده.
در این پروژه Attribute اعتبارسنجی برای بررسی فایل که آیا واقعا عکس هست یا نه نوشته شده، هم سمت کلاینت و هم سمت سرور.
نحوه انجام کار: برای سمت کلاینت عکسی که کاربر وارد میکنه رو در داخل تگ IMG نمایش میده و اگه عکس به درستی لود بشه رخداد OnLoad تگ IMG فراخوانی میشه و اعتبارسنجی پاس میشه.
تگ IMG به کاربر نمایش داده نمیشه و به صورت display: none هستش.
از این Attribute اعتبارسنجی در کنار Attributeهای کنترل حجم فایل هم میشه استفاده کرد که قبل از اینکه بخوایم بررسی کنیم که آیا فایل واقعا عکسه یا نه ابتدا حجم فایل مورد بررسی قرار بگیره.