>dotnet --in Host (useful for support): Version: 2.2.7 Commit: b1e29ae826 .NET Core SDKs installed: No SDKs were found. .NET Core runtimes installed: Microsoft.AspNetCore.All 2.2.5 [C:\Program Files (x86)\dotnet\shared\Microsoft.AspNetCore.All] Microsoft.AspNetCore.All 2.2.7 [C:\Program Files (x86)\dotnet\shared\Microsoft.AspNetCore.All] Microsoft.AspNetCore.App 2.2.5 [C:\Program Files (x86)\dotnet\shared\Microsoft.AspNetCore.App] Microsoft.AspNetCore.App 2.2.7 [C:\Program Files (x86)\dotnet\shared\Microsoft.AspNetCore.App] Microsoft.NETCore.App 2.2.5 [C:\Program Files (x86)\dotnet\shared\Microsoft.NETCore.App] Microsoft.NETCore.App 2.2.7 [C:\Program Files (x86)\dotnet\shared\Microsoft.NETCore.App] To install additional .NET Core runtimes or SDKs: https://aka.ms/dotnet-download
به آمارهای خطای حاصل از سایت جاری که دقت میکردم یک نکته آن جالب بود: «محتوای تکراری»
mydomain.com/Home/Index mydomain.com/home/index mydomain.com/Home/index mydomain.com/home/Index
راه حل
برای حل این مساله دو نکته باید درنظر گرفته شود:
الف) هدایت دائمی (Redirect permanent) صفحات قدیمی به صفحاتی جدید، با آدرس lowercase
using System.Globalization; using System.Web; using System.Web.Mvc; namespace WebToolkit { public class ForceWww : ActionFilterAttribute { public override void OnActionExecuting(ActionExecutingContext filterContext) { modifyUrlAndRedirectPermanent(filterContext); base.OnActionExecuting(filterContext); } private static void modifyUrlAndRedirectPermanent(ActionExecutingContext filterContext) { if (canIgnoreRequest(filterContext)) return; var absoluteUrl = HttpUtility.UrlDecode(filterContext.RequestContext.HttpContext.Request.Url.AbsoluteUri.ToString(CultureInfo.InvariantCulture)); var absoluteUrlToLower = absoluteUrl.ToLowerInvariant(); absoluteUrlToLower = forceWwwAndLowercase(filterContext, absoluteUrlToLower); absoluteUrlToLower = avoidTrailingSlashes(filterContext, absoluteUrlToLower); if (!absoluteUrl.Equals(absoluteUrlToLower)) { filterContext.Result = new RedirectResult(absoluteUrlToLower, permanent: true); } } private static string avoidTrailingSlashes(ActionExecutingContext filterContext, string absoluteUrlToLower) { if (!isRootRequest(filterContext) && absoluteUrlToLower.EndsWith("/")) return absoluteUrlToLower.TrimEnd(new[] { '/' }); return absoluteUrlToLower; } private static bool isRootRequest(ActionExecutingContext filterContext) { return filterContext.RequestContext.HttpContext.Request.Url.AbsolutePath == "/"; } private static bool canIgnoreRequest(ActionExecutingContext filterContext) { return filterContext.IsChildAction || filterContext.HttpContext.Request.IsAjaxRequest() || filterContext.RequestContext.HttpContext.Request.Url.AbsoluteUri.Contains("?"); } private static string forceWwwAndLowercase(ActionExecutingContext filterContext, string absoluteUrlToLower) { if (isLocalRequet(filterContext)) return absoluteUrlToLower; if (absoluteUrlToLower.Contains("www")) return absoluteUrlToLower; return absoluteUrlToLower.Replace("http://", "http://www.") .Replace("https://", "https://www."); } private static bool isLocalRequet(ActionExecutingContext filterContext) { return filterContext.RequestContext.HttpContext.Request.IsLocal; } } }
- تمام آدرسهای سایت باید www داشته باشند؛ تا آدرسهای آن یکنواخت شده و خصوصا مشکلات لاگین و نوشته شدن کوکیها به ازای آدرسهای مختلف و سر درگمی کاربران کاهش یابد.
- اگر آدرس جاری lowercase نباشد، تبدیل به نمونه lowercase شده و درخواست کننده، به آدرس جدید هدایت میشود. این مورد خصوصا جهت موتورهای جستجو برای تصحیح نتایج آنها بسیار مفید است.
- اسلش انتهای لینکها در صورت وجود حذف خواهد شد. این مورد نیز در کاهش تعداد خطاهای «محتوای تکراری» مؤثر است.
- اگر آدرسی، کوئری استرینگ داشته باشد از آن صرفنظر خواهد شد؛ زیرا ممکن است اطلاعات موجود در آن به کوچکی و بزرگی حروف حساس باشند.
ب) کاهش بار سایت توسط تولید خودکار Urlهایی که در بدو امر lowercase هستند
برای پیاده سازی این مطلب میتوان از پروژه سورس باز «LowercaseRoutesMVC» استفاده کرد. سه فایل cs دارد که میتوانید به پروژه خود اضافه کنید. پس از آن، هرجایی در پروژه خود routes.MapRoute دارید تبدیل کنید به routes.MapRouteLowercase .
به این ترتیب به صورت خودکار تمام Urlهای تولید شده توسط HTML helpers توکار ASP.NET MVC (و نه Urlهایی که دستی نوشته شدهاند)، در حین درج در صفحه به صورت lowercase ظاهر خواهند شد (صرفنظر از اینکه نامهای کنترلرها و یا اکشن متدهای تعریف شده camel case هستند یا خیر). مزیت این مساله کاهش یک مرحله Redirect است که در قسمت الف ذکر شد. در این کتابخانه کمکی نیز از آدرسهایی که دارای کوئری استرینگ باشند، صرفنظر میشود.
پی نوشت: برای داشتن نیوگت شخصی سایتهای نظیر Nuget Server و Myget ( به همراه پشتیبانی از مخازن npm و Bower ) هم این سرویس را ارائه میکنند. ولی باید هزینهی آن را پرداخت کنید. البته سایت GemFury مخازن رایگان مختلفی چون Nuget را نیز پشتیبانی میکند.
نصب بر روی یک سیستم شخصی یا لوکال
در اولین قدم، شما باید یک دایرکتوری را در سیستم خود درست کنید تا پکیجهای خود را داخل آن قرار دهید. پنجرهی Package manager Settings را باز کنید و در آن گزینهی Package Sources را انتخاب کنید. سپس در کادر باز شده، بر روی دکمهی افزودن، در بالا کلیک کنید تا در پایین کادر، از شما نام محل توزیع بسته و آدرس آن را بپرسد:
بعد از ورود اطلاعات، بر روی دکمهی Update کلیک کنید. از این پس این دایرکتوری، منبع پکیجهای شماست و برای دریافت پکیجها از این آدرس میتوانید از طریق منوی کشویی موجود در کنسول، پکیج جدید خودتان را انتخاب کنید:
اگر میخواهید میتوانید این دایرکتوری را به اشتراک بگذارید تا دیگر افراد حاضر در شبکهی محلی هم بتوانند آن را به عنوان منبع توزیع خود به سیستم اضافه کنند.
مرحلهی بعدی این است که از طریق ابزار خط فرمان نیوگت نسخه 3.3 پکیجهایتان را به دایرکتوری مربوطه انتقال دهید. نحوهی صدا زدن این دستور به شکل زیر است:
nuget init e:\nuget\ e:\nuget\test
nuget add GMap.Net.1.0.1.nupkg -source e:\nuget\test
ساخت منبع راه دور (اینترنت)
شما با استفاده از ویژوال استودیو و انجام چند عمل ساده میتوانید پکیجهای خودتان را مدیریت کنید. برای شروع، یک پروژهی تحت وب خام (Empty) را ایجاد کنید و در کنسول Nuget دستور زیر را وارد کنید:
Install-Package NuGet.Server
<appSettings> <!-- Set the value here to specify your custom packages folder. --> <add key="packagesPath" value="×\Packages" /> </appSettings>
(هر محلی که نصب کنید طبق الگوی مسیریابی، آدرس nuget را در انتها وارد کنید)
Dotnettips.info/nuget
در صورتی که قصد دارید مستقیما بستهای را به سمت سرور push کنید، از یک رمز عبور قدرتمند که آن را میتوانید در web.config، بخش apiKey وارد نمایید، استفاده کنید. اگر هم نمیخواهید، میتوانید در تگ RequiredApiKey در خصوصیت Value، مقدار false را وارد نمایید.
برای اینکار میتوانید از دستور زیر استفاده کنید:
nuget push GMap.Net.1.0.1.nupkg -source http://Dotenettips.info/nuget (ApiKey)
nuget setapikey -source https://www.dntips.ir/Nuget (ApiKey)
- Improving the development experience when using popular JavaScript libraries
- Adding support for new JavaScript ECMAScript 2015 (also known as ES2015 and formerly ES6) language and web browser APIs
- Increasing your productivity in complex JavaScript code bases
Url Routing در ASP.Net WebForms
نمونه آنرا در مطلب « پیاده سازی دکمه «بیشتر» یا «اسکرول نامحدود» به کمک jQuery در ASP.NET MVC » (کامنت آخر آن) پیاده سازی شده میتوانید مشاهده و دریافت کنید. از این قابلیت در سایت جاری در حال استفاده است. مثلا اگر به قسمت مطالب در بالای صفحه مراجعه کنید، چنین آدرسی قابل مشاهده است:
https://www.dntips.ir/postsarchive#/page/1/date/desc
در پایین صفحه اگر دراپ داونهای مرتب سازی را تغییر دهید، نام فیلد یا صعودی و نزولی بودن آدرس تغییر میکنند. شماره صفحه نیز با هر بار کلیک بر روی دکمهی بیشتر یکی اضافه میشود. همچنین این آدرسها را میشود ذخیره و عینا بازیابی کرد.
When you go to https://vscode.dev, you'll be presented with a lightweight version of VS Code running fully in the browser. Open a folder on your local machine and start coding.
No install required.
روشهای زیادی برای مدیریت این مساله وجود دارند؛ مانند استفاده از ماژولهای URL Rewrite برای بازنویسی آدرسهای نهایی صفحهی در حال رندر و یا ... به روز رسانی مستقیم بانک اطلاعاتی، یافتن تمام فیلدهای رشتهای ممکن در تمام جداول موجود و سپس اعمال تغییرات.
یافتن لیست تمام جداول قابل مدیریت توسط Entity framework
در ابتدا میخواهیم لیست پویای تمام جداول مدیریت شدهی توسط EF را پیدا کنیم. از این جهت که نمیخواهیم به ازای هر کدام یک کوئری جداگانه بنویسیم.
using System; using System.Collections.Generic; using System.Data.Entity; using System.Linq; using EFReplaceAll.Models; namespace EFReplaceAll.Config { public class DbSetInfo { public IQueryable<object> DbSet { set; get; } public Type DbSetType { set; get; } } public class MyContext : DbContext { public DbSet<Product> Products { set; get; } public DbSet<Category> Categories { set; get; } public DbSet<User> Users { set; get; } public MyContext() : base("Connection1") { this.Database.Log = sql => Console.Write(sql); } public IList<DbSetInfo> GetAllDbSets() { return this.GetType() .GetProperties() .Where(p => p.PropertyType.IsGenericType && p.PropertyType.GetGenericTypeDefinition() == typeof(DbSet<>)) .Select(p => new DbSetInfo { DbSet = (IQueryable<object>)p.GetValue(this, null), DbSetType = p.PropertyType.GetGenericArguments().First() }) .ToList(); } } }
یافتن فیلدهای رشتهای رکوردهای تمام جداول و سپس به روز رسانی آنها
میخواهیم متدی را طراحی کنیم که در آن لیستی از یافتنها و جایگزینیها قابل تعیین باشد. به همین جهت مدل زیر را تعریف میکنیم:
using System; namespace EFReplaceAll.Utils { public class ReplaceOp { public string ToFind { set; get; } public string ToReplace { set; get; } public StringComparison Comparison { set; get; } } }
سپس متدی که کار یافتن تمام فیلدهای رشتهای و سپس جایگزین کردن آنها را انجام میدهد به صورت زیر خواهد بود:
using System.Collections.Generic; using System.Linq; using EFReplaceAll.Config; namespace EFReplaceAll.Utils { public static class UpdateDbContextContents { public static void ReplaceAllStringsAcrossTables(IList<ReplaceOp> replaceOps) { int dbSetsCount; using (var uow = new MyContext()) { dbSetsCount = uow.GetAllDbSets().Count; } for (var i = 0; i < dbSetsCount; i++) { using (var uow = new MyContext()) // using a new context each time to free resources quickly. { var dbSetResult = uow.GetAllDbSets()[i]; var stringProperties = dbSetResult.DbSetType.GetProperties() .Where(p => p.PropertyType == typeof(string)) .ToList(); var dbSetEntities = dbSetResult.DbSet; var haveChanges = false; foreach (var entity in dbSetEntities) { foreach (var stringProperty in stringProperties) { var oldPropertyValue = stringProperty.GetValue(entity, null) as string; if (string.IsNullOrWhiteSpace(oldPropertyValue)) { continue; } var newPropertyValue = oldPropertyValue; foreach (var replaceOp in replaceOps) { newPropertyValue = newPropertyValue.ReplaceString(replaceOp.ToFind, replaceOp.ToReplace, replaceOp.Comparison); } if (oldPropertyValue != newPropertyValue) { stringProperty.SetValue(entity, newPropertyValue, null); haveChanges = true; } } } if (haveChanges) { uow.SaveChanges(); } } } } } }
- در اینجا using (var uow = new MyContext()) را زیاد مشاهده میکنید. علت اینجا است که اگر تنها با یک Context کار کنیم، EF تمام تغییرات و تمام رکوردهای وارد شدهی به آنرا کش میکند و مصرف حافظهی برنامه با توجه به خواندن تمام رکوردهای بانک اطلاعاتی توسط آن، ممکن است به چند گیگابایت برسد. به همین جهت از Contextهایی با طول عمر کوتاه استفاده شدهاست تا میزان مصرف RAM این متد سبب کرش برنامه نشود.
- در ابتدای کار توسط متد GetAllDbSets که به Context اضافه کردیم، تعداد DbSetهای موجود را پیدا میکنیم تا بتوان بر روی آنها حلقهای را تشکیل داد و به ازای هر کدام یک (()using (var uow = new MyContext را تشکیل داد.
- سپس با استفاده از نوع DbSet که توسط خاصیت dbSetResult.DbSetType در دسترس است، خواص رشتهای ممکن این DbSet یافت میشوند.
- در ادامه dbSetResult.DbSet یک Data Reader را به صورت پویا بر روی DbSet جاری باز کرده و تمام رکوردهای این DbSet را یک به یک بازگشت میدهد.
- در اینجا با استفاده از Reflection، از رکورد جاری، مقادیر خواص رشتهای آن دریافت شده و سپس کار جستجو و جایگزینی انجام میشود.
- در آخر هم فراخوانی uow.SaveChanges کار ثبت تغییرات صورت گرفته را انجام میدهد.
متدی برای جایگزینی غیرحساس به حروف بزرگ و کوچک
متد استاندارد Replace رشتهها، حساس به حروف بزرگ و کوچک است. یک نمونهی عمومیتر را که در آن بتوان StringComparison.OrdinalIgnoreCase را تعیین کرد، در ذیل مشاهده میکنید که از آن در متد ReplaceAllStringsAcrossTables فوق استفاده شدهاست:
using System; using System.Text; namespace EFReplaceAll.Utils { public static class StringExtensions { public static string ReplaceString(this string src, string oldValue, string newValue, StringComparison comparison) { if (string.IsNullOrWhiteSpace(src)) { return src; } if (string.Compare(oldValue, newValue, comparison) == 0) { return src; } var sb = new StringBuilder(); var previousIndex = 0; var index = src.IndexOf(oldValue, comparison); while (index != -1) { sb.Append(src.Substring(previousIndex, index - previousIndex)); sb.Append(newValue); index += oldValue.Length; previousIndex = index; index = src.IndexOf(oldValue, index, comparison); } sb.Append(src.Substring(previousIndex)); return sb.ToString(); } } }
UpdateDbContextContents.ReplaceAllStringsAcrossTables( new[] { new ReplaceOp { ToFind = "https://www.dntips.ir", ToReplace = "https://www.dntips.ir", Comparison = StringComparison.OrdinalIgnoreCase }, new ReplaceOp { ToFind = "https://www.dntips.ir", ToReplace = "https://www.dntips.ir", Comparison = StringComparison.OrdinalIgnoreCase } });
کدهای کامل این مطلب را از اینجا میتوانید دریافت کنید: EFReplaceAll.zip
Top Issues Fixed in Visual Studio 2019 version 16.2.5
- Visual Studio stops respsonding on creating a new SSIS project
- Fixed UI freezes occurring after extended usage of the editor.
Security Advisory Notices
CVE-2019-1232 Diagnostics Hub Standard Collector Service Elevation of Privilege Vulnerability
An elevation of privilege vulnerability exists when the Diagnostics Hub Standard Collector Service improperly impersonates certain file operations. An attacker who successfully exploited this vulnerability could gain elevated privileges. An attacker with unprivileged access to a vulnerable system could exploit this vulnerability. The security update addresses the vulnerability by ensuring the Diagnostics Hub Standard Collector Service properly impersonates file operations.
CVE-2019-1301: Denial of Service Vulnerability in .NET Core
A denial of service vulnerability exists when .NET Core improperly handles web requests. An attacker who successfully exploited this vulnerability could cause a denial of service against a .NET Core web application. The vulnerability can be exploited remotely, without authentication.
The update addresses the vulnerability by correcting how the .NET Core web application handles web requests.
WebStorage: قسمت دوم
window.localStorage window.sessionStorage
if(typeof(Storage) !== "undefined") { // Code for localStorage/sessionStorage. } else { // Sorry! No Web Storage support.. }
localStorage.setItem("lastname", "Smith"); //======================== localStorage.getItem("lastname");
var a=localStorage.lastname;
//ذخیره مقدار store.set('username', 'marcus') //بازیابی مقدار store.get('username') //حذف مقدار store.remove('username') //حذف تمامی مقادیر ذخیره شده store.clear() //ذخیره ساختار store.set('user', { name: 'marcus', likes: 'javascript' }) //بازیابی ساختار به شکل قبلی var user = store.get('user') alert(user.name + ' likes ' + user.likes) //تغییر مستقیم مقدار قبلی store.getAll().user.name == 'marcus' //بازخوانی تمام مقادیر ذخیر شده توسط یک حلقه store.forEach(function(key, val) { console.log(key, '==', val) })
<script src="store.min.js"></script> <script> init() function init() { if (!store.enabled) { alert('Local storage is not supported by your browser. Please disable "Private Mode", or upgrade to a modern browser.') return } var user = store.get('user') // ... and so on ... } </script>
var storeWithExpiration = { // دریافت کلید و مقدار و زمان انقضا به میلی ثانیه set: function(key, val, exp) { //ایجاد زمان فعلی جهت ثبت تاریخ ایجاد store.set(key, { val:val, exp:exp, time:new Date().getTime() }) }, get: function(key) { var info = store.get(key) //در صورتی که کلید داده شده مقداری نداشته باشد نال را بر میگردانیم if (!info) { return null } //تاریخ فعلی را منهای تاریخ ثبت شده کرده و در صورتی که //از مقدار میلی ثاینه بیشتر باشد یعنی منقضی شده و نال بر میگرداند if (new Date().getTime() - info.time > info.exp) { return null } return info.val } } // استفاده عملی از کد بالا // استفاده از تایمر جهت نمایش واکشی دادهها قبل از نقضا و بعد از انقضا storeWithExpiration.set('foo', 'bar', 1000) setTimeout(function() { console.log(storeWithExpiration.get('foo')) }, 500) // -> "bar" setTimeout(function() { console.log(storeWithExpiration.get('foo')) }, 1500) // -> null
CrossStorageHub.init([ {origin: /\.example.com$/, allow: ['get']}, {origin: /:\/\/(www\.)?example.com$/, allow: ['get', 'set', 'del']} ]);
valid.example.com
invalid.example.com.malicious.com
{ 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Methods': 'GET,PUT,POST,DELETE', 'Access-Control-Allow-Headers': 'X-Requested-With', 'Content-Security-Policy': "default-src 'unsafe-inline' *", 'X-Content-Security-Policy': "default-src 'unsafe-inline' *", 'X-WebKit-CSP': "default-src 'unsafe-inline' *", }
<script type="text/javascript" src="~/Scripts/cross-storage/hub.js"></script> <script> CrossStorageHub.init([ {origin: /.*localhost:300\d$/, allow: ['get', 'set', 'del']} ]); </script>
var storage = new CrossStorageClient('http://localhost:3000/example/hub.html'); var setKeys = function () { return storage.set('key1', 'foo').then(function() { return storage.set('key2', 'bar'); }); };
storage.onConnect().then(function() { return storage.set('key', {foo: 'bar'}); }).then(function() { return storage.set('expiringKey', 'foobar', 10000); });
storage.onConnect().then(function() { return storage.get('key1'); }).then(function(res) { return storage.get('key1', 'key2', 'key3'); }).then(function(res) { // ... });
storage.onConnect() .then(function() { return storage.get('key1', 'key2'); }) .then(function(res) { console.log(res); // ['foo', 'bar'] })['catch'](function(err) { console.log(err); });
<script src="https://s3.amazonaws.com/es6-promises/promise-1.0.0.min.js"></script>
var request = indexedDB.open("library"); request.onupgradeneeded = function() { // The database did not previously exist, so create object stores and indexes. var db = request.result; var store = db.createObjectStore("books", {keyPath: "isbn"}); var titleIndex = store.createIndex("by_title", "title", {unique: true}); var authorIndex = store.createIndex("by_author", "author"); // Populate with initial data. store.put({title: "Quarry Memories", author: "Fred", isbn: 123456}); store.put({title: "Water Buffaloes", author: "Fred", isbn: 234567}); store.put({title: "Bedrock Nights", author: "Barney", isbn: 345678}); }; request.onsuccess = function() { db = request.result; };
var tx = db.transaction("books", "readwrite"); var store = tx.objectStore("books"); store.put({title: "Quarry Memories", author: "Fred", isbn: 123456}); store.put({title: "Water Buffaloes", author: "Fred", isbn: 234567}); store.put({title: "Bedrock Nights", author: "Barney", isbn: 345678}); tx.oncomplete = function() { // All requests have succeeded and the transaction has committed. };
var tx = db.transaction("books", "readonly"); var store = tx.objectStore("books"); var index = store.index("by_author"); var request = index.openCursor(IDBKeyRange.only("Fred")); request.onsuccess = function() { var cursor = request.result; if (cursor) { // Called for each matching record. report(cursor.value.isbn, cursor.value.title, cursor.value.author); cursor.continue(); } else { // No more matching records. report(null); } };