AutoMapper 4.0 منتشر شد
There’s a ton of small bug fixes in this release, quite a few enhancements and a few larger new features. Configuration performance went up quite a bit, and I’ve laid the groundwork to make in-memory mapping a lot faster in the future. LINQ projection has gotten to the point where you can do anything that the major query providers support.
تشخیص تفاوت بین IIS سرورهای مختلف
I commonly hear the phrase “The web application worked in the pre-production environment and now is encountering issues in production and the server’s configuration are identical!” when I appear onsite to help assist with the resolution of the issues. Upon further investigation, an IIS module has not been installed on the production server, or the configuration is different for an application pool setting between the pre-production and production environments. This is a very common scenario I encounter in the field and here are some suggestions on how to determine differences between IIS servers in an IIS farm environment or between servers in different environments, such as pre-production and production. Keeping server configuration and content synchronized is always a challenge and I hope these suggestions help out.
بر اساس جستجوهایی که انجام دادهام، CHM پشتیبانی کاملی را از یونیکد انجام نمیدهد (مشکل جستجو و همچنین ایندکس کردن).
اما با ترفندی میتوان این مساله را حل کرد و آن هم تبدیل encoding فایلها به عربی است (windows-1256). در این حالت هم جستجو کار میکند و هم عنوان صفحات هنگام جستجو در لیست موارد یاد شده درست نمایش داده میشود و صفحه add to favorites نیز مشکلی در نمایش عنوانهای صفحهها نخواهد داشت. روش کار به شرح زیر است:
الف) encoding تمام فایلهای html خود را به صورت زیر تغییر دهید (از utf-8 به windows-1256):
<meta content="text/html; charset=Windows-1256" http-equiv="Content-Type">
using System.IO;
using System.Text;
public static void SaveAs1256(string fileName)
{
string content = File.ReadAllText(fileName);
File.WriteAllText(fileName, content, Encoding.GetEncoding("windows-1256"));
}
ج) اصلاح فایل hhp پروژه خود
فایل hhp مربوط به html help work shop را باز کنید. (همان فایل پروژه ساخت راهنما)
اگر مثال قبل را دنبال کرده باشید، محتوای فایل آن چیزی شبیه به خطوط زیر خواهد بود:
[OPTIONS]
Compatibility=1.1 or later
Compiled file=test.chm
Contents file=Table of Contents.hhc
Default Window=win1
Default topic=page1.html
Display compile progress=No
Full-text search=Yes
Index file=Index.hhk
Language=0x429 Farsi
Title=راهنمای یک
[WINDOWS]
win1=,"Table of Contents.hhc","Index.hhk","page1.html","page1.html",,,,,0x3420,,0x304e,,,,,,2,,0
[FILES]
page1.html
page2.html
[INFOTYPES]
به قسمت options چند سطر زیر را اضافه کنید: (زبان فارسی و فونت تاهومای عربی)
Default Font=Tahoma,8,178
Language=0x429 Farsi
محض نمونه، کل وبلاگ جاری را به یک فایل chm تبدیل کردهام که آنرا از آدرس زیر میتوانید دریافت نمائید:
دریافت فایل
برای آزمایش، یک عبارت فارسی را در آن جستجو نمائید.
پ.ن.
این راه حلی است که به نظر من رسیده و جواب داده. اگر شما با encoding های دیگر هم جواب گرفتهاید (مشکل جستجوی فارسی حل شده) لطفا پیغام بگذارید. با تشکر.
9.Visual Studio 2017 15.9 منتشر شد
These are the customer-reported issues addressed in 15.9.9:
- SSDT: We fixed a crash in the SSIS Foreach Loop container.
- A few .NET native for UWP customer issues were fixed in .NET native tools 2.2 (UWP 6.2.4).
- We have corrected dual signing of Visual C++ Redistributable installers.
Security Advisory Notices
برای استفاده از FTP نیاز به یک اکانت FTP در سایت مورد نظر بهمراه دسترسی به پوشه ای مشخص میباشد.
برای مثال ما یک اکانت FTP در سایت dotnettip.info داریم که به پوشه upload دسترسی داره.
ابتدا در فایل Web.config و در بین تگ های appSettings مقادیر زیر را برای دسترسی به اکانت و نام کاربری و رمز عبور ذخیره میکنیم.
<add key="FtpAddress" value="ftp://ftp.dotnetips.info" /> <add key="FtpUser" value="uploadcenter" /> <add key="FtpPass" value="123123" /> <add key="FolderPath" value="~/Upload/" />
در ادامه یک کلاس در App_code پروژه خود با نام FTPHelper ایجاد میکنیم و کد زیر را در آن قرار میدهیم:
تکه کد بالا برای ست کردن مقادیر نام کاربری و رمز عبور و آدرس FTP در کلاس مذکور که بصورت پیشفرض از web.config پر میشود ایجاد و بکار خواهد رفت.
using System.Net; using System.IO; using System.Configuration; public class FtpHelper { public FtpHelper() { //Default Value Set From Application _hostname = ConfigurationManager.AppSettings.GetValues("FtpAddress")[0]; _username = ConfigurationManager.AppSettings.GetValues("FtpUser")[0]; _password = ConfigurationManager.AppSettings.GetValues("FtpPass")[0]; } #region "Properties" private string _hostname; /// <summary> /// Hostname /// </summary> /// <value></value> /// <remarks>Hostname can be in either the full URL format /// ftp://ftp.myhost.com or just ftp.myhost.com /// </remarks> public string Hostname { get { if (_hostname.StartsWith("ftp://")) { return _hostname; } else { return "ftp://" + _hostname; } } set { _hostname = value; } } private string _username; /// <summary> /// Username property /// </summary> /// <value></value> /// <remarks>Can be left blank, in which case 'anonymous' is returned</remarks> public string Username { get { return (_username == "" ? "anonymous" : _username); } set { _username = value; } } private string _password; public string Password { get { return _password; } set { _password = value; } } #endregion }
سپس فضای نامهای زیر را در کلاس خود قرار میدهیم.
using System.Net; using System.IO;
public static bool Upload(string fileUrl) { if (File.Exists(fileUrl)) { FtpHelper ftpClient = new FtpHelper(); string ftpUrl = ftpClient.Hostname + System.IO.Path.GetFileName(fileUrl); FtpWebRequest ftp = (FtpWebRequest)FtpWebRequest.Create(ftpUrl); ftp.Credentials = new NetworkCredential(ftpClient.Username, ftpClient.Password); ftp.KeepAlive = true; ftp.UseBinary = true; ftp.Timeout = 3600000; ftp.KeepAlive = true; ftp.Method = WebRequestMethods.Ftp.UploadFile; const int bufferLength = 102400; byte[] buffer = new byte[bufferLength]; int readBytes = 0; //open file for reading using (FileStream fs = File.OpenRead(fileUrl)) { try { //open request to send using (Stream rs = ftp.GetRequestStream()) { do { readBytes = fs.Read(buffer, 0, bufferLength); fs.Write(buffer, 0, readBytes); } while (!(readBytes < bufferLength)); rs.Close(); } } catch (Exception) { //Optional Alert for Exeption To Application Layer //throw (new ApplicationException("بارگذاری فایل با خطا رو به رو شد")); } finally { //ensure file closed //fs.Close(); } } ftp = null; return true; } return false; }
تکه کد بالا فایل مورد نظر را در صورت وجود به صورت تکههای 100 کیلوبایتی بر روی ftp بارگذاری میکند، که میتوانید مقدار آنرا نیز تغییر دهید.
اینکار باعث افزایش سرعت بارگذاری در فایلهای با حجم بالا برای بارگذاری میشود.
در بخشهای بعدی نحوه ایجاد پوشه ، حذف فایل ، حذف پوشه و دانلود فایل از روی FTP را بررسی خواهیم کرد.
مقایسه تعریف سطوح دسترسی «مبتنی بر نقشها» با سطوح دسترسی «مبتنی بر سیاستهای امنیتی»
- در سطوح دسترسی «مبتنی بر نقشها»
یکسری نقش از پیش تعریف شده وجود دارند؛ مانند PayingUser و یا FreeUser که کاربر توسط هر نقش، به یکسری دسترسیهای خاص نائل میشود. برای مثال PayingUser میتواند نگارش قاب شدهی تصاویر را سفارش دهد و یا تصویری را به سیستم اضافه کند.
- در سطوح دسترسی «مبتنی بر سیاستهای امنیتی»
سطوح دسترسی بر اساس یک سری سیاست که بیانگر ترکیبی از منطقهای دسترسی هستند، اعطاء میشوند. این منطقها نیز از طریق ترکیب User Claims حاصل میشوند و میتوانند منطقهای پیچیدهتری را به همراه داشته باشند. برای مثال اگر کاربری از کشور A است و نوع اشتراک او B است و اگر در بین یک بازهی زمانی خاصی متولد شده باشد، میتواند به منبع خاصی دسترسی پیدا کند. به این ترتیب حتی میتوان نیاز به ترکیب چندین نقش را با تعریف یک سیاست امنیتی جدید جایگزین کرد. به همین جهت نسبت به روش بکارگیری مستقیم کار با نقشها ترجیح داده میشود.
جایگزین کردن بررسی سطوح دسترسی توسط نقشها با روش بکارگیری سیاستهای دسترسی
در ادامه میخواهیم بجای بکارگیری مستقیم نقشها جهت محدود کردن دسترسی به قسمتهای خاصی از برنامهی کلاینت، تنها کاربرانی که از کشور خاصی وارد شدهاند و نیز سطح اشتراک خاصی را دارند، بتوانند دسترسیهای ویژهای داشته باشند؛ چون برای مثال امکان ارسال مستقیم تصاویر قاب شده را به کشور دیگری نداریم.
تنظیم User Claims جدید در برنامهی IDP
برای تنظیم این سیاست امنیتی جدید، ابتدا دو claim جدید subscriptionlevel و country را به خواص کاربران در کلاس src\IDP\DNT.IDP\Config.cs در سطح IDP اضافه میکنیم:
namespace DNT.IDP { public static class Config { public static List<TestUser> GetUsers() { return new List<TestUser> { new TestUser { Username = "User 1", // ... Claims = new List<Claim> { // ... new Claim("subscriptionlevel", "PayingUser"), new Claim("country", "ir") } }, new TestUser { Username = "User 2", // ... Claims = new List<Claim> { // ... new Claim("subscriptionlevel", "FreeUser"), new Claim("country", "be") } } }; }
namespace DNT.IDP { public static class Config { // identity-related resources (scopes) public static IEnumerable<IdentityResource> GetIdentityResources() { return new List<IdentityResource> { // ... new IdentityResource( name: "country", displayName: "The country you're living in", claimTypes: new List<string> { "country" }), new IdentityResource( name: "subscriptionlevel", displayName: "Your subscription level", claimTypes: new List<string> { "subscriptionlevel" }) }; }
namespace DNT.IDP { public static class Config { public static IEnumerable<Client> GetClients() { return new List<Client> { new Client { ClientName = "Image Gallery", // ... AllowedScopes = { // ... "country", "subscriptionlevel" } // ... } }; } }
استفادهی از User Claims جدید در برنامهی MVC Client
در ادامه به کلاس ImageGallery.MvcClient.WebApp\Startup.cs برنامهی MVC Client مراجعه کرده و دو scope جدیدی را که در سمت IDP تعریف کردیم، در اینجا در تنظیمات متد AddOpenIdConnect، درخواست میدهیم:
options.Scope.Add("subscriptionlevel"); options.Scope.Add("country");
البته همانطور که در قسمتهای قبل نیز ذکر شد، اگر claim ای در لیست نگاشتهای تنظیمات میانافزار OpenID Connect مایکروسافت نباشد، آنرا در لیست this.User.Claims ظاهر نمیکند. به همین جهت همانند claim role که پیشتر MapUniqueJsonKey را برای آن تعریف کردیم، نیاز است برای این دو claim نیز نگاشتهای لازم را به سیستم افزود:
options.ClaimActions.MapUniqueJsonKey(claimType: "role", jsonKey: "role"); options.ClaimActions.MapUniqueJsonKey(claimType: "subscriptionlevel", jsonKey: "subscriptionlevel"); options.ClaimActions.MapUniqueJsonKey(claimType: "country", jsonKey: "country");
ایجاد سیاستهای دسترسی در برنامهی MVC Client
برای تعریف یک سیاست دسترسی جدید در کلاس ImageGallery.MvcClient.WebApp\Startup.cs برنامهی MVC Client، به متد ConfigureServices آن مراجعه کرده و آنرا به صورت زیر تکمیل میکنیم:
namespace ImageGallery.MvcClient.WebApp { public class Startup { public void ConfigureServices(IServiceCollection services) { services.AddAuthorization(options => { options.AddPolicy( name: "CanOrderFrame", configurePolicy: policyBuilder => { policyBuilder.RequireAuthenticatedUser(); policyBuilder.RequireClaim(claimType: "country", requiredValues: "ir"); policyBuilder.RequireClaim(claimType: "subscriptionlevel", requiredValues: "PayingUser"); }); });
به علاوه policyBuilder شامل متد RequireRole نیز هست. به همین جهت است که این روش تعریف سطوح دسترسی، روش قدیمی مبتنی بر نقشها را جایگزین کرده و در برگیرندهی آن نیز میشود؛ چون در این سیستم، role نیز تنها یک claim است، مانند country و یا subscriptionlevel فوق.
بررسی نحوهی استفادهی از Authorization Policy تعریف شده و جایگزین کردن آن با روش بررسی نقشها
تا کنون از روش بررسی سطوح دسترسیها بر اساس نقشهای کاربران در دو قسمت استفاده کردهایم:
الف) اصلاح Views\Shared\_Layout.cshtml برای استفادهی از Authorization Policy
در فایل Layout با بررسی نقش PayingUser، منوهای مرتبط با این نقش را فعال میکنیم:
@if(User.IsInRole("PayingUser")) { <li><a asp-area="" asp-controller="Gallery" asp-action="AddImage">Add an image</a></li> <li><a asp-area="" asp-controller="Gallery" asp-action="OrderFrame">Order a framed picture</a></li> }
@using Microsoft.AspNetCore.Authorization @inject IAuthorizationService AuthorizationService
@if (User.IsInRole("PayingUser")) { <li><a asp-area="" asp-controller="Gallery" asp-action="AddImage">Add an image</a></li> } @if ((await AuthorizationService.AuthorizeAsync(User, "CanOrderFrame")).Succeeded) { <li><a asp-area="" asp-controller="Gallery" asp-action="OrderFrame">Order a framed picture</a></li> }
ب) اصلاح کنترلر ImageGallery.MvcClient.WebApp\Controllers\GalleryController.cs برای استفادهی از Authorization Policy
namespace ImageGallery.MvcClient.WebApp.Controllers { [Authorize] public class GalleryController : Controller { [Authorize(Policy = "CanOrderFrame")] public async Task<IActionResult> OrderFrame() {
اکنون برای آزمایش برنامه یکبار از آن خارج شده و سپس توسط اکانت User 1 که از نوع PayingUser در کشور ir است، به آن وارد شوید.
ابتدا به قسمت IdentityInformation آن وارد شوید. در اینجا لیست claims جدید را میتوانید مشاهده کنید. همچنین لینک سفارش تصویر قاب شده نیز نمایان است و میتوان به آدرس آن نیز وارد شد.
استفاده از سیاستهای دسترسی در سطح برنامهی Web API
در سمت برنامهی Web API، در حال حاضر کاربران میتوانند به متدهای Get ،Put و Delete ای که رکوردهای آنها الزاما متعلق به آنها نیست دسترسی داشته باشند. بنابراین نیاز است از ورود کاربران به متدهای تغییرات رکوردهایی که OwnerID آنها با هویت کاربری آنها تطابقی ندارد، جلوگیری کرد. در این حالت Authorization Policy تعریف شده نیاز دارد تا با سرویس کاربران و بانک اطلاعاتی کار کند. همچنین نیاز به دسترسی به اطلاعات مسیریابی جاری را برای دریافت ImageId دارد. پیاده سازی یک چنین سیاست دسترسی پیچیدهای توسط متدهای RequireClaim و RequireRole میسر نیست. خوشبختانه امکان بسط سیستم Authorization Policy با پیاده سازی یک IAuthorizationRequirement سفارشی وجود دارد. RequireClaim و RequireRole، جزو Authorization Requirementهای پیشفرض و توکار هستند. اما میتوان نمونههای سفارشی آنها را نیز پیاده سازی کرد:
using System; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.Extensions.Logging; namespace ImageGallery.WebApi.Services { public class MustOwnImageRequirement : IAuthorizationRequirement { } public class MustOwnImageHandler : AuthorizationHandler<MustOwnImageRequirement> { private readonly IImagesService _imagesService; private readonly ILogger<MustOwnImageHandler> _logger; public MustOwnImageHandler( IImagesService imagesService, ILogger<MustOwnImageHandler> logger) { _imagesService = imagesService; _logger = logger; } protected override async Task HandleRequirementAsync( AuthorizationHandlerContext context, MustOwnImageRequirement requirement) { var filterContext = context.Resource as AuthorizationFilterContext; if (filterContext == null) { context.Fail(); return; } var imageId = filterContext.RouteData.Values["id"].ToString(); if (!Guid.TryParse(imageId, out Guid imageIdAsGuid)) { _logger.LogError($"`{imageId}` is not a Guid."); context.Fail(); return; } var subClaim = context.User.Claims.FirstOrDefault(c => c.Type == "sub"); if (subClaim == null) { _logger.LogError($"User.Claims don't have the `sub` claim."); context.Fail(); return; } var ownerId = subClaim.Value; if (!await _imagesService.IsImageOwnerAsync(imageIdAsGuid, ownerId)) { _logger.LogError($"`{ownerId}` is not the owner of `{imageIdAsGuid}` image."); context.Fail(); return; } // all checks out context.Succeed(requirement); } } }
<Project Sdk="Microsoft.NET.Sdk"> <ItemGroup> <PackageReference Include="Microsoft.EntityFrameworkCore" Version="2.1.0" /> <PackageReference Include="Microsoft.AspNetCore.Authorization" Version="2.1.1.0" /> <PackageReference Include="Microsoft.AspNetCore.Mvc.Abstractions" Version="2.1.1.0" /> </ItemGroup> </Project>
پیاده سازی سیاستهای پویای دسترسی شامل مراحل ذیل است:
1- تعریف یک نیازمندی دسترسی جدید
public class MustOwnImageRequirement : IAuthorizationRequirement { }
2- پیاده سازی یک AuthorizationHandler استفاده کنندهی از نیازمندی دسترسی تعریف شده
که کدهای کامل آنرا در کلاس MustOwnImageHandler مشاهده میکنید. کار آن با ارث بری از AuthorizationHandler شروع شده و آرگومان جنریک آن، همان نیازمندی است که پیشتر تعریف کردیم. از این آرگومان جنریک جهت یافتن خودکار AuthorizationHandler متناظر با آن توسط ASP.NET Core استفاده میشود. بنابراین در اینجا MustOwnImageRequirement تهیه شده صرفا کارکرد علامتگذاری را دارد.
در کلاس تهیه شده باید متد HandleRequirementAsync آنرا بازنویسی کرد و اگر در این بین، منطق سفارشی ما context.Succeed را فراخوانی کند، به معنای برآورده شدن سیاست دسترسی بوده و کاربر جاری میتواند به منبع درخواستی بلافاصله دسترسی یابد و اگر context.Fail فراخوانی شود، در همینجا دسترسی کاربر قطع شده و HTTP status code مساوی 401 (عدم دسترسی) را دریافت میکند.
در این پیاده سازی از filterContext.RouteData برای یافتن Id تصویر مورد نظر استفاده شدهاست. همچنین Id شخص جاری نیز از sub claim موجود استخراج گردیدهاست. اکنون این اطلاعات را به سرویس تصاویر ارسال میکنیم تا توسط متد IsImageOwnerAsync آن مشخص شود که آیا کاربر جاری سیستم، همان کاربری است که تصویر را در بانک اطلاعاتی ثبت کردهاست؟ اگر بله، با فراخوانی context.Succeed به سیستم Authorization اعلام خواهیم کرد که این سیاست دسترسی و نیازمندی مرتبط با آن با موفقیت پشت سر گذاشته شدهاست.
3- معرفی سیاست دسترسی پویای تهیه شده به سیستم
معرفی سیاست کاری پویا و سفارشی تهیه شده، شامل دو مرحلهی زیر است:
مراجعهی به کلاس ImageGallery.WebApi.WebApp\Startup.cs و افزودن نیازمندی آن:
namespace ImageGallery.WebApi.WebApp { public class Startup { public void ConfigureServices(IServiceCollection services) { services.AddAuthorization(authorizationOptions => { authorizationOptions.AddPolicy( name: "MustOwnImage", configurePolicy: policyBuilder => { policyBuilder.RequireAuthenticatedUser(); policyBuilder.AddRequirements(new MustOwnImageRequirement()); }); }); services.AddScoped<IAuthorizationHandler, MustOwnImageHandler>();
سپس یک Policy جدید را با نام دلخواه MustOwnImage تعریف کرده و نیازمندی علامتگذار خود را به عنوان یک policy.Requirements جدید، اضافه میکنیم. همانطور که ملاحظه میکنید یک وهلهی جدید از MustOwnImageRequirement در اینجا ثبت شدهاست. همین وهله به متد HandleRequirementAsync نیز ارسال میشود. بنابراین اگر نیاز به ارسال پارامترهای بیشتری به این متد وجود داشت، میتوان خواص مرتبطی را به کلاس MustOwnImageRequirement نیز اضافه کرد.
همانطور که مشخص است، در اینجا یک نیازمندی را میتوان ثبت کرد و نه Handler آنرا. این Handler از سیستم تزریق وابستگیها بر اساس آرگومان جنریک AuthorizationHandler پیاده سازی شده، به صورت خودکار یافت شده و اجرا میشود (بنابراین اگر Handler شما اجرا نشد، مطمئن شوید که حتما آنرا به سیستم تزریق وابستگیها معرفی کردهاید).
پس از آن هر کنترلر یا اکشن متدی که از این سیاست دسترسی پویای تهیه شده استفاده کند:
[Authorize(Policy ="MustOwnImage")]
اعمال سیاست دسترسی پویای تعریف شده به Web API
پس از تعریف سیاست دسترسی MustOwnImage که پویا عمل میکند، اکنون نوبت به استفادهی از آن در کنترلر ImageGallery.WebApi.WebApp\Controllers\ImagesController.cs است:
namespace ImageGallery.WebApi.WebApp.Controllers { [Route("api/images")] [Authorize] public class ImagesController : Controller { [HttpGet("{id}", Name = "GetImage")] [Authorize("MustOwnImage")] public async Task<IActionResult> GetImage(Guid id) { } [HttpDelete("{id}")] [Authorize("MustOwnImage")] public async Task<IActionResult> DeleteImage(Guid id) { } [HttpPut("{id}")] [Authorize("MustOwnImage")] public async Task<IActionResult> UpdateImage(Guid id, [FromBody] ImageForUpdateModel imageForUpdate) { } } }
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید.
برای اجرای برنامه:
- ابتدا به پوشهی src\WebApi\ImageGallery.WebApi.WebApp وارد شده و dotnet_run.bat آنرا اجرا کنید تا WebAPI برنامه راه اندازی شود.
- سپس به پوشهی src\IDP\DNT.IDP مراجعه کرده و و dotnet_run.bat آنرا اجرا کنید تا برنامهی IDP راه اندازی شود.
- در آخر به پوشهی src\MvcClient\ImageGallery.MvcClient.WebApp وارد شده و dotnet_run.bat آنرا اجرا کنید تا MVC Client راه اندازی شود.
اکنون که هر سه برنامه در حال اجرا هستند، مرورگر را گشوده و مسیر https://localhost:5001 را درخواست کنید. در صفحهی login نام کاربری را User 1 و کلمهی عبور آنرا password وارد کنید.
بیشتر زمانی کاربرد دارد که میخواهید اپلیکیشن خود را روی ماشینی میزبانی نمایید که فاقد dot Net Runtime میباشد.
Publishing your app as native AOT produces an app that is self-contained and that has been ahead-of-time (AOT) compiled to native code. Native AOT apps start up very quickly and use less memory. Users of the application can run it on a machine that doesn't have the .NET runtime installed.
کتاب رایگان توسعه نرم افزار Agile
Learning new development processes can be difficult, but switching to Agile doesn’t need to be complicated. Explore the theories behind Agile and learn how to make it work for you. In Agile Software Development Succinctly, author Stephen Haunts will guide you to a fuller understanding of Agile, its advantages and disadvantages, and how to get the most out of it.