آشنایی با Refactoring - قسمت 9
یک نکتهی تکمیلی: غنی سازی کامپایلر سیشارپ جهت نمایش اخطارهایی در مورد متدهایی بیش از اندازه پیچیده
پس از فعالسازی یکسری از آنالایزرها، اکنون میتوان بررسی cyclomatic complexity را هم به آنها سپرد. برای اینکار باید مراحل زیر طی شوند:
ابتدا یک سطر زیر را به فایل editorconfig. اضافه کنید:
dotnet_diagnostic.CA1502.severity = warning
سپس فایل جدید CodeMetricsConfig.txt را به ریشهی پروژه اضافه کرده و سطر زیر را به آن اضافه کنید:
CA1502: 20
مقدار پیشفرض آستانهی گزارش خطا در اینجا، 25 است که به روش فوق، قابل بازنویسی است.
البته نیاز است این فایل را به صورت یک فایل اضافی، به فایل csproj. نیز معرفی کرد:
<ItemGroup> <AdditionalFiles Include="CodeMetricsConfig.txt"/> </ItemGroup>
همچنین میتوان تنظیمات آستانهی ریزتری را هم به متدها، نوعها و غیره اعمال کرد:
CA1505(Method): 5 CA1505(Type): 15
مقادیر مجاز در اینجا، شامل SymbolKind, Assembly, Namespace, Type, Method, Field, Event,Property هستند.
در این فایل میتوان آستانهی گزارش خطای موارد زیر را هم بازنویسی کرد:
CA1501: Avoid excessive inheritance CA1502: Avoid excessive complexity (this one) CA1505: Avoid unmaintainable code CA1506: Avoid excessive class coupling
نحوه استفاده
نحوه استفاده از آن بسیار راحت است و در دموی html همراه آن به طور ساده در سه مثال توضیح داده شده است. ابتدا از این آدرس کتابخانه آن را دریافت کنید. این کتابخانه شامل یک فایل js که شامل کدهای پلاگین است، یک فایل css جهت تغییر استایل کدهایی است که پلاگین تولید میکند که اسامی آن دقیقا مشخص میکند که هر کلاس متعلق به چه بخشی است.
گام اول:
فایلهای مورد نظر را بعد از صدا زدن کتابخانهی جی کوئری صدا بزنید.
<link type="text/css" href="css/RowAdder.css" rel="stylesheet" /> <script src="js/RowAdder.js" type="text/javascript"></script>
گام دوم :
در تکه کدهای html، کدی را که قرار است در هر سطر تکرار شود، داخل یک div قرار داده و نامی مثل row-sample را برای آن قرار دهید (فعلا حتما این نام باشد)، بعدها پلاگین، کدهای داخل این تگ div را به عنوان هر سطر خواهد شناخت:
<div id="row-sample"> <form style="margin: 0; padding: 0;"> Name:<input type="text"/> <input type="radio" name="Gender" value="male" checked="checked">Male <input type="radio" name="Gender" value="female">Female </form> </div>
گام سوم:
سپس یک div دیگر ایجاد کنید و نامی مثل mypanel را به آن بدهید تا سطرهایی که ایجاد میشوند داخل این div قرار بگیرند.
<div id="mypanel"></div>
گام چهارم:
در بخش head یک تگ اسکریپت باز کرده و کدهای زیر را به آن اضافه میکنیم. این کد باعث میشود که پلاگین فعال شود.
<script> $(document).ready(function() { $("#mypanel").RowAdder(); }); </script>
یک دکمه جهت افزودن سطر به صفحه اضافه میکنیم
<button id="addanotherform">Add New Form</button>
و در قسمت تگ اسکریپت هم کد زیر را اضافه میکنیم:
$("#addanotherform").on('click', function() { $("#mypanel").RowAdder('add'); });
حال از صفحه تست میگیریم: با هر بار کلیک بر روی دکمهی Add New Form یک سطر جدید ایجاد میگردد.
در تصویر بالا دکمههای دیگر هم دیده میشوند که به دیگر متدهای آن اشاره دارد:
جهت مخفی سازی:
$("#mypanel").RowAdder('hide');
چهت نمایش:
$("#mypanel").RowAdder('show');
جهت افزودن سطر با کد:
$("#mypanel").RowAdder('add');
جهت دریافت تعداد سطرهای ایجاد شده:
$("#mypanel").RowAdder('count')
جهت دریافت کدهای یک سطر در اندیس x
$("#mypanel").RowAdder('content', 3)
جهت حذف یک سطر با اندیس x
$("#mypanel").RowAdder('remove', 3);
همانطور که با صدا زدن اولین متد پلاگین متوجه شدید و نتیجهی آن را در دمو دیدید، این پلاگین از پیش فرضهایی جهت راه اندازی اولیه استفاده میکند که این پیش فرضها عبارتند از تگ row-sample که بدون معرفی رسمی، آن را شناسایی کرد. همچنین ممکن است بخواهید عبارت Remove را با کلمهی فارسی «حذف» جایگزین نمایید. برای اینکار میتوانید پلاگین را به شکل زیر به کار ببرید:
$("#mypanel").RowAdder({ sample: '#my-custom-sample', type: 'text', value:'حذف' });
تغییر اولین پیش فرض، تغییر نام تگ row-sample به my-custom-sample بود و در مرحلهی بعد هم نام فارسی حذف را جایگزین remove کردیم. عبارت type به طور پیش فرض بر روی text قرار دارد که اجباری به ذکر آن در کد بالا نبود. ولی اگر دوست دارید که به جای نمایش عبارت حذف، از یک آیکن یا تصویر استفاده کنید، کد را به شکل زیر تغییر دهید:
$("#mypanel").RowAdder({ type: 'image', value: 'images/remove.png' });
فایل RowAdder.css
در بردارنده هر سطر .each-section { margin: 20px; padding: 5px; } جهت استایل بندی لینک چه تصویر و چه متن .remove-link { color:#999; text-decoration: none; } a:hover.remove-link { color:#802727; } جهت تغییر استایل بر روی خود تصویر .remove-image { }
آشنایی با کد پلاگین
(function ($) { var settings = null; $.fn.RowAdder = function (method) { // call methods if (methods[method]) { return methods[method].apply(this, Array.prototype.slice.call(arguments, 1)); } else if (typeof method === 'object' || !method) { return methods.init.apply(this, arguments); } else { $.error('Method ' + method + ' does not exist on jQuery.RowAdder'); } }; })(jQuery);
متدها
//methods var methods = { init: function (options) { //default-settings settings = $.extend({ 'sample': '#row-sample', 'type': 'text', 'value': 'Remove' }, options); this.attr('data-sample', settings.sample); this.attr('data-type', settings.type); this.attr('data-value', settings.value); Do(this); }, show: function () { this.css("display", "inline"); }, hide: function () { this.css("display", "none"); }, add: function () { Do(this); }, remove: function (index) { console.log(index); this.find(".each-section")[index].remove(); }, content: function (index) { return this.find(".each-section")[index]; }, count: function (index) { return this.find(".each-section").size(); } };
تابع Do
function Do(panelDiv) { settings.sample = panelDiv.data('sample'); settings.type = panelDiv.data('type'); settings.value = panelDiv.data('value'); //find sample code var rowsample = $(settings.sample); rowsample.css("display", "none"); var sample = rowsample.html(); var i = panelDiv.find(".each-section").size(); //add html details to create a correct template var sectionDiv = $('<div />', { "class": 'each-section', 'id': 'section'+i }); var image = $("<img />", { "src": settings.value,"class":"remove-image" }); var link = $("<a />", { "text": settings.value,"class":"remove-link" }); //remove event for remove selected form //create new form sectionDiv.html(sample); link.on('click', function (e) { e.preventDefault(); var $this = $(this); $this.closest(".each-section").remove(); }); if (i > 0) { if (settings.type == 'image') { link.text(''); link.append(image); } sectionDiv.append(link); } //add new created form on document panelDiv.append(sectionDiv); }
settings.sample = panelDiv.data('sample'); settings.type = panelDiv.data('type'); settings.value = panelDiv.data('value'); //find sample code var rowsample = $(settings.sample); rowsample.css("display", "none"); var sample = rowsample.html(); var i = panelDiv.find(".each-section").size();
//add html details to create a correct template var sectionDiv = $('<div />', { "class": 'each-section', 'id': 'section'+i }); var image = $("<img />", { "src": settings.value,"class":"remove-image" }); var link = $("<a />", { "text": settings.value,"class":"remove-link" });
در خط بعدی محتویات نمونه را داخل تگ sectiondiv قرار میدهیم:
//create new form sectionDiv.html(sample);
بعد از آن برای رویداد کلیک لینک حذف، کد زیر را وارد میکنیم:
link.on('click', function (e) { e.preventDefault(); var $this = $(this); $this.closest(".each-section").remove(); });
اولین شرط زیر بررسی میکند که آیا این سطری که ایجاد شده است سطر دوم به بعد است یا خیر؟ اگر آری پس باید دکمهی حذف را به همراه داشته باشد. در صورتیکه سطر دوم به بعد باشد، وارد آن میشود. حالا بررسی میکند که کاربر برای دکمهی حذف، درخواست لینک تصویری یا لینک متنی داده است و لینک مناسب را ساخته و آن را به انتهای sectionDiv اضافه میکند.
if (i > 0) { if (settings.type == 'image') { link.text(''); link.append(image); } sectionDiv.append(link); }
در انتها کل تگ sectionDiv را به تگ داده شده اضافه میکنیم تا به کاربر نمایش داده شود.
//add new created form on document panelDiv.append(sectionDiv);
مروری بر .net core توسط اسکات هانتر
There has never been a better time to be a .NET developer, you can now build Android, iOS, Linux, Mac, and Windows applications with .NET all in Open Source. In this session we will run through some of the new innovations including the .NET Framework updates, .NET Standard, Universal Windows Platform updates, .NET Core, managed languages and more. We will also review the updates to Visual Studio and Visual Studio Code to make you a better developer, come see some of the latest productivity features in these tools including managing code style, searching your source and more.
ObsoleteAttribute
ObsoleteAttribute بر روی تمامی عناصر یک برنامه بجز assemblies, modules، پارامترها و مقادیر بازگشتی قابل استفاده است. علامتگذاری یک عنصر به عنوان منسوخ شده، به کاربر استفاده کننده اطلاع میدهد که این عنصر در نسخههای آینده حذف خواهد شد.
با استفاده از پروپرتی Message آن پیامی را به کاربر استفاده کننده نشان خواهد داد و توصیه میشود در این پیام یک راه حل نیز ارائه شود.
پروپرتی IsError در صورتی که مقدار آن به true تعیین شده باشد و کامپایلر در صورتی که عنصری که این خصوصیت بر روی آن تعریف شده است، استفاده شده باشد، در پنجره Error List، پیام مربوط به Obsolete را نشان میدهد. برای مثال پس از استفاده از کلاس زیر، OrderDetailTotal به صورت warning و CalculateOrderDetailTotal به صورت Error در پنجره Error List نشان داده میشود.
public static class ObsoleteExample { // Mark OrderDetailTotal As Obsolete. [ObsoleteAttribute("This property (OrderDetailTotal) is obsolete. Use InvoiceTotal instead.", false)] public static decimal OrderDetailTotal { get { return 12m; } } public static decimal InvoiceTotal { get { return 25m; } } // Mark CalculateOrderDetailTotal As Obsolete. [ObsoleteAttribute("This method is obsolete. Call CalculateInvoiceTotal instead.", true)] public static decimal CalculateOrderDetailTotal() { return 0m; } public static decimal CalculateInvoiceTotal() { return 1m; } }
DefaultValueAttribute
DefaultValueAttribute جهت تعیین مقدار پیش فرض یک پروپرتی استفاده میشود. شما میتوانید یک DefaultValueAttribute را با هر مقداری ایجاد کنید. ایجاد مقدار پیش فرض برای یک پروپرتی باعث نمیشود که مقداردهی اولیهای به آن انجام گیرد؛ برای این کار نیاز به کدنویسی میباشد.
مثال زیر نحوه استفاده و مقداردهی اولیه پروپرتیها را نشان میدهد.
public class DefaultValueAttributeTest { public DefaultValueAttributeTest() { // Use the DefaultValue propety of each property to actually set it, via reflection. foreach (PropertyDescriptor prop in TypeDescriptor.GetProperties(this)) { var attr = prop.Attributes[typeof(DefaultValueAttribute)] as DefaultValueAttribute; if (attr != null) prop.SetValue(this, attr.Value); } } [DefaultValue(28)] public int Age { get; set; } [DefaultValue("Vahid")] public string FirstName { get; set; } [DefaultValue("Mohammad Taheri")] public string LastName { get; set; } public override string ToString() { return $"{this.FirstName} {this.LastName} is {this.Age}."; } }
DebuggerBrowsableAttribute
در صورت استفاده از DebuggerBrowsableAttribute ، شما میتوانید نحوه نمایش یک عضو را در پنجره متغیرها، در زمان دیباگ، تعیین کنید.public class DebuggerBrowsableTest { [DebuggerBrowsable(DebuggerBrowsableState.Never)] // عدم نمایش در زمان دیباگ در پنجره متغیرها public string FirstName { get; set; } [DebuggerBrowsable(DebuggerBrowsableState.Collapsed)] // مقدار پیش فرض public string LastName { get; set; } [DebuggerBrowsable( DebuggerBrowsableState.RootHidden )] // عدم نمایش در زمان دیباگ در پنجره متغیرها public string FullName => FirstName + " " + LastName; [DebuggerBrowsable( DebuggerBrowsableState.RootHidden )] // تنها در زمانی که یک آرایه یا لیست باشد نمایش داده میشود public string[] FullNameArray => new string[] { FirstName + " " + LastName }; }
اگر از کد مثال بالا استفاده کنید و با استفاده از کلید F11 به صورت خط به خط دستورات را اجرا کنید، مشاهده خواهید کرد متغیر FirstName و FullName در پنجره Autos نشان داده نخواهد شد.
Operator ??
عملگر ?? در صورتی که عملوند سمت چپ آن تهی (null) نباشد، مقدار آن را باز میگرداند و در غیر اینصورت مقدار عملوند سمت راست خود را باز میگرداند. نوعهای تهی پذیر (nullable) میتوانند دارای مقدار و یا به صورت تعریف نشده باشند. عملگر ?? وقتی که یک نوع تهی پذیر به یک نوع غیرتهی پذیر انتساب داده میشود، مقدار پیش فرض آن را باز میگرداند.
int? x = null; int y = x ?? -1; Console.WriteLine("y now equals -1 because x was null => {0}", y); int i = DefaultValueOperatorTest.GetNullableInt() ?? default(int); Console.WriteLine("i equals now 0 because GetNullableInt() returned null => {0}", i); string s = DefaultValueOperatorTest.GetStringValue(); Console.WriteLine("Returns 'Unspecified' because s is null => {0}", s ?? "Unspecified");
1.Visual Studio 2017 15.7 منتشر شد
These are the customer-reported issues addressed in 15.7.1:
- This release includes a fix that reduces memory usage and GC pressure during solution load.
Microsoft Security Advisory for .NET Core Denial Of Service Vulnerability
CVE-2018-0765
Microsoft is releasing this security advisory to provide information about a vulnerability in .NET Core and .NET native version 2.0. This advisory also provides guidance on what developers can do to update their applications to remove this vulnerability.
Microsoft is aware of a denial of service vulnerability that exists when .NET Framework and .NET Core improperly process XML documents. An attacker who successfully exploited this vulnerability could cause a denial of service against a .NET Framework, .NET Core, or .NET native application.
The update addresses the vulnerability by correcting how .NET Framework, .NET Core, and .NET native applications handle XML document processing.
If your application is an ASP.NET Core application, developers are also advised to update to ASP.NET Core 2.0.8.
System.IO.Pipelines is a new library that is designed to make it easier to do high performance IO in .NET. It’s a library targeting .NET Standard that works on all .NET implementations.
خوب ابتدا فرض میکنیم برای نمایش تصاویر چند حالت داریم مثلا کوچک، متوسط، بزرگ و حالت واقعی (اندازه اصلی).
البته دقت نمایید که این طبقه بندی فرضی بوده و ممکن است برای پروژههای مختلف این طبقه بندی متفاوت باشد. (در این پست قصد فقط اشنایی با تغییر اندازه تصاویر است و شاید کد به درستی refactor نشده باشد).
برای تغییر اندازه تصاویر در زمان اجرا یکی از روش ها، میتواند استفاده از Handler باشد. خوب برای ایجاد Handler ابتدا در پروژه وب خود بروی پروژه راست کلیک کرده، و گزینه New Item را برگزینید، و در پنجره ظاهر شده مانند تصویر زیر گزینه Generic Handler را انتخاب نمایید.
پس از ایجاد هندلر، فایل کد آن مانند زیر خواهد بود، ما باید کدهای خود را در متد ProcessRequestبنویسیم.
using System; using System.Collections.Generic; using System.Linq; using System.Web; namespace PWS.UI.Handler { /// <summary> /// Summary description for PhotoHandler /// </summary> public class PhotoHandler : IHttpHandler { public void ProcessRequest(HttpContext context) { context.Response.ContentType = "text/plain"; context.Response.Write("Hello World"); } public bool IsReusable { get { return false; } } } }
خوب برای نوشتن کد در این مرحله ما باید چند کار انجام دهیم.
1- گرفتن پارامترهای ورودی کاربر جهت تغییر سایز از طریق روشهای انتقال مقادیر بین صفحات (در اینجا استفاده از Query String ).
2-بازیابی تصویر از دیتابیس یا از دیسک به صورت یک آرایه بایتی.
3- تغییر اندازه تصویر مرحله 2 و ارسال تصویر به خروجی.
using System; using System.Data.SqlClient; using System.Diagnostics; using System.Drawing; using System.Drawing.Drawing2D; using System.Drawing.Imaging; using System.Globalization; using System.IO; using System.Web; namespace PWS.UI.Handler { /// <summary> /// Summary description for PhotoHandler /// </summary> public class PhotoHandler : IHttpHandler { /// <summary> /// بازیابی تصویر اصلی از بانک اطلاعاتی /// </summary> /// <param name="photoId">کد تصویر</param> /// <returns></returns> private byte[] GetImageFromDatabase(int photoId) { using (var connection = new SqlConnection("ConnectionString")) { using (var command = new SqlCommand("Select Photo From tblPhotos Where Id = @PhotoId", connection)) { command.Parameters.Add(new SqlParameter("@PhotoId", photoId)); connection.Open(); var result = command.ExecuteScalar(); return ((byte[])result); } } } /// <summary> /// بازیابی فایل از دیسک /// </summary> /// <param name="photoId">با فرض اینکه نام فایل این است</param> /// <returns></returns> private byte[] GetImageFromDisk(string photoId /* or somting */) { using (var sourceStream = new FileStream("Original File Path + id", FileMode.Open, FileAccess.Read)) { return StreamToByteArray(sourceStream); } } /// <summary> /// Streams to byte array. /// </summary> /// <param name="inputStream">The input stream.</param> /// <returns></returns> /// <exception cref="System.ArgumentException"></exception> static byte[] StreamToByteArray(Stream inputStream) { if (!inputStream.CanRead) { throw new ArgumentException(); } // This is optional if (inputStream.CanSeek) { inputStream.Seek(0, SeekOrigin.Begin); } var output = new byte[inputStream.Length]; int bytesRead = inputStream.Read(output, 0, output.Length); Debug.Assert(bytesRead == output.Length, "Bytes read from stream matches stream length"); return output; } /// <summary> /// Enables processing of HTTP Web requests by a custom HttpHandler that implements the <see cref="T:System.Web.IHttpHandler" /> interface. /// </summary> /// <param name="context">An <see cref="T:System.Web.HttpContext" /> object that provides references to the intrinsic server objects (for example, Request, Response, Session, and Server) used to service HTTP requests.</param> public void ProcessRequest(HttpContext context) { // Set up the response settings context.Response.ContentType = "image/jpeg"; context.Response.Cache.SetCacheability(HttpCacheability.Public); context.Response.BufferOutput = false; // مرحله اول int size = 0; switch (context.Request.QueryString["Size"]) { case "S": size = 100; //100px break; case "M": size = 198; //198px break; case "L": size = 500; //500px break; } byte[] changedImage; var id = Convert.ToInt32(context.Request.QueryString["PhotoId"]); byte[] sourceImage = GetImageFromDatabase(id); // یا //byte[] sourceImage = GetImageFromDisk(id.ToString(CultureInfo.InvariantCulture)); //مرحله 2 if (size != 0) //غیر از حالت واقعی تصویر { changedImage = Helpers.ResizeImageFile(sourceImage, size, ImageFormat.Jpeg); } else { changedImage = (byte[])sourceImage.Clone(); } // مرحله 3 if (changedImage == null) return; context.Response.AddHeader("Content-Length", changedImage.Length.ToString(CultureInfo.InvariantCulture)); context.Response.BinaryWrite(changedImage); } public bool IsReusable { get { return false; } } } }
2- متد GetImageFromDisk: این متد نام تصویر (با فرض اینکه یک عدد میباشد) را به عنوان پارامتر گرفته و آنرا بازیابی میکند (در صورتی که تصویر در دیسک ذخیره شده باشد.)
3- متد StreamToByteArray: زمانی که تصویر از فایل خوانده میشود به صورت Stream است این متد یک Stream را گرفته و تبدیل به یک آرایه بایتی میکند.
در نهایت در متد ProcessRequestتصویر خوانده شده با توجه به پارامترهای ورودی تغییر اندازه داده شده و در نهایت به خروجی نوشته میشود.
برای استفاده این هندلر، کافی است در توصیر خود به عنوان مسیر رشته ای شبیه زیر وارد نمایید:
PhotoHandler.ashx?PhotoId=10&Size=S مانند <img src='PhotoHandler.ashx?PhotoId=10&Size=S' alt='تصویر ازمایشی' />
نظرات اقای موسوی تا حدودی اعمال شد و در پست تغییراتی انجام شد.
موفق وموید باشید
Today at .NET Conf 2019, we shared some exciting announcements for Xamarin and Visual Studio developers, including:
- XAML Hot Reload for Xamarin.Forms: Make changes to your XAML UI. See them reflected live on your emulator, simulator, or physical device.
- Xamarin Hot Restart: Test changes made to your app, including multi-file code edits, resources, and references, while using a much faster build and deploy cycle.
- iOS 13 and Android 10: Take advantage of the full power and performance of native platforms and APIs. Including iPadOS, dark mode, and foldable support.