آشنایی با Leaflet
<script src="http://cdn.leafletjs.com/leaflet-0.7.3/leaflet.js"></script> <link rel="stylesheet" href="http://cdn.leafletjs.com/leaflet-0.7.3/leaflet.css" />
#map { height: 600px; }
var map = L.map('map').setView([29.6760859,52.4950737], 13);
var osmUrl='http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png'; var osmAttrib='Map data © <a href="http://openstreetmap.org">OpenStreetMap</a> contributors'; var osm = new L.TileLayer(osmUrl, { maxZoom: 18, attribution: osmAttrib}).addTo(map);
Marker، دایره و چندضلعی
در کنار نمایش Tileها میتوان اشکال گرافیکی نیز به نقشه اضافه کرد؛ مثل مارکر(نقطه)، مستطیل، دایره و یا یک Popup. اضافه کردن یک Marker به سادگی، با کد زیر صورت میپذیرد:
var marker = L.marker([29.623116,52.497856]).addTo(map);
محل مورد نظر به شیء مارکر پاس داده شده و مقدار بازگشتی به map اضافه شده است.
نمایش چند ضلعی و دایره هم کار ساده ای است. برای دایره باید ابتدا مختصات مرکز دایره و شعاع به متر را به L.circle پاس داد:
var circle = L.circle([29.6308217,52.5048021], 500, { color: 'red', fillColor: '#f03', fillOpacity: 0.5 }).addTo(map);
در کد بالا علاوه بر محل و اندازه دایره، رنگ محیط، رنگ داخل و شفافیت (opacity) نیز مشخص شدهاند.
برای چند ضلعیها میتوان به این صورت عمل کرد:
var polygon = L.polygon([ [29.628453, 52.488838], [29.637368, 52.493987], [29.637168, 52.503987] ]).addTo(map);
کار کردن با Popup ها
از Popup میتوان برای نمایش اطلاعات اضافهای بر روی یک محل خاص یا یک عنوان به مانند Marker استفاده کرد. برای مثال میتوان اطلاعاتی دربارهی محل یک Marker یا دایره نمایش داد. در هنگام ایجاد marker، دایره و چندضلعی مقادیر بازگشتی در متغیرهای جدایی ذخیره شدند. اکنون میتوان به آن اشیاء یک popup اضافه کرد:
marker.bindPopup("باغ عفیف آباد").openPopup(); circle.bindPopup("I am a circle."); polygon.bindPopup("I am a polygon.");
به علت اینکه openPopup برای Marker صدا زده شده، به صورت پیشفرض popup را نمایش میدهد. اما برای بقیه، نمایش با کلیک خواهد بود. البته الزاما نیازی نیست که popup روی یک شیء نمایش داده شود، میتوان popupهای مستقلی نیز ایجاد کرد:
var popup = L.popup() .setLatLng([51.5, -0.09]) .setContent("I am a standalone popup.") .openOn(map);
Could not load file or assembly Newtonsoft.Json or one of its dependencies. The system cannot find the file specified.
<?xml version="1.0" encoding="utf-8"?> <configuration> <!-- ... --> <runtime> <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1"> <dependentAssembly> <assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral" /> <bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0" /> </dependentAssembly> </assemblyBinding> </runtime> </configuration>
به روز رسانی قسمت assembly redirects توسط NuGet
البته اگر بستهی جدیدی را توسط نیوگت نصب کنیم، این assembly redirects به صورت خودکار توسط آن اضافه خواهند شد اما ... گاهی ممکن است موارد قدیمی را به روز نکند یا موردی فراموش شوند یا حتی اگر اطلاعاتی را پیشتر از وب یافتهاید، دارای خطای تایپی باشند. برای یک چنین مواردی اگر با خطای Could not load file or assembly ابتدای بحث مواجه شدید، مراحل ذیل را طی کنید تا بتوانید قسمت assembly redirects را بازسازی کنید:
الف) به فایل config برنامه مراجعه کرده و کلا قسمت assemblyBinding آنرا حذف کنید.
ب) سپس دستور ذیل را در کنسول پاورشل نیوگت اجرا کنید:
PM> Get-Project -All | Add-BindingRedirect
مشاهده گر سایت dotnettips
- قسمت (بر اساس تگ) به درستی کار نمیکند.
- پروژه بر اساس فایل json سایت کار میکند که میتوان آن را به xml تغییر داد.
- سرعت لود مطالب به علت سنگین بودن فایل سایت جاری کمی در نسخه آنلاین طول میکشد.
Interceptors پایهی پروکسیهای پویا هستند
برای پیاده سازی پروکسیهای پویا نیاز است با مفهوم Interceptors آشنا شویم. به کمک Interceptors فرآیند فراخوانی متدها و خواص یک کلاس، تحت کنترل و نظارت قرار خواهند گرفت. زمانیکه یک IOC Container کار وهله سازی کلاس سرویس خاصی را انجام میدهد، در همین حین میتوان مراحل شروع، پایان و خطاهای متدها یا فراخوانیهای خواص را نیز تحت نظر قرار داد و به این ترتیب مصرف کننده، امکان تزریق کدهایی را در این مکانها خواهد یافت. مزیت مهم استفاده از Interceptors، عدم نیاز به کامپایل ثانویه اسمبلیهای موجود، برای تغییری در کدهای آنها است (برای تزریق نواحی تحت کنترل قرار دادن اعمال) و تمام کارها به صورت خودکار در زمان اجرای برنامه مدیریت میگردند.
با اضافه کردن Interception به پروسه وهله سازی سرویسها توسط یک IoC Container، مراحل کار اینبار به صورت زیر تغییر میکنند:
الف) در اینجا نیز در ابتدا فراخوان، درخواست وهلهای را بر اساس اینترفیسی خاص، به IOC Container ارائه میدهد.
ب) IOC Container نیز سعی در وهله سازی درخواست رسیده را بر اساس تنظیمات اولیهی خود میکند.
ج) اما در این حالت IOC Container تشخیص میدهد نوعی که باید بازگشت دهد، علاوه بر وهله سازی، نیاز به مزین سازی و پیاده سازی Interceptors را نیز دارد. بنابراین نوع مورد انتظار را در صورت وجود، به یک Dynamic Proxy، بجای بازگشت مستقیم به فراخوان ارائه میدهد.
د) در ادامه Dynamic Proxy، نوع مورد انتظار را توسط Interceptors محصور کرده و به فراخوان بازگشت میدهد.
ه) اکنون فراخوان، در حین استفاده از امکانات شیء وهله سازی شده، به صورت خودکار مراحل مختلف اجرای یک Decorator را سبب خواهد شد.
یعنی به صورت خلاصه، فراخوان سرویسی را درخواست میدهد، اما وهلهای را که دریافت میکند، یک لایهی اضافهتر تزئین کننده را نیز به همراه دارد که کاملا از دید فراخوان مخفی است و نحوهی کار کردن با آن سرویس، با و بدون این تزئین کننده، دقیقا یکی است. وجود این لایهی تزئین کننده سبب میشود تا فراخوانی هر متد این سرویس، از این لایه گذشته و سبب اجرای یک سری کد سفارشی، پیش و پس از اجرای این متد نیز گردد.
پیاده سازی پروکسیهای پویا توسط کتابخانهی Castle.Core در برنامههای NET Core.
در ادامه از کتابخانهی بسیار معروف Castle.Core برای پیاده سازی پروکسیهای پویا استفاده خواهیم کرد. از این کتابخانه در پروژهی EF Core، برای پیاده سازی Lazy loading نیز استفاده شدهاست.
برای دریافت آن یکی از دستورات زیر را اجرا نمائید:
> Install-Package Castle.Core > dotnet add package Castle.Core
<Project Sdk="Microsoft.NET.Sdk.Web"> <ItemGroup> <PackageReference Include="castle.core" Version="4.3.1" /> </ItemGroup> </Project>
تبدیل ExceptionHandlingDecorator مثال قسمت قبل، به یک Interceptor مخصوص Castle.Core
در قسمت قبل، کلاس MyTaskServiceDecorator را جهت اعمال یک try/catch به همراه logging، به متد Run سرویس MyTaskService، تهیه کردیم. در اینجا قصد داریم نگارش عمومیتر این تزئین کننده را با طراحی یک Interceptor مخصوص Castle.Core انجام دهیم:
using System; using Castle.DynamicProxy; using Microsoft.Extensions.Logging; namespace CoreIocSample02.Utils { public class ExceptionHandlingInterceptor : IInterceptor { private readonly ILogger<ExceptionHandlingInterceptor> _logger; public ExceptionHandlingInterceptor(ILogger<ExceptionHandlingInterceptor> logger) { _logger = logger; } public void Intercept(IInvocation invocation) { try { invocation.Proceed(); //فراخوانی متد اصلی در اینجا صورت میگیرد } catch (Exception ex) { _logger.LogCritical(ex, "An unhandled exception has been occurred."); } } } }
public void Run() { try { _decorated.Run(); } catch (Exception ex) { _logger.LogCritical(ex, "An unhandled exception has been occurred."); } }
اتصال ExceptionHandlingInterceptor تهیه شده به سیستم تزریق وابستگیها
در ادامه روش معرفی ExceptionHandlingInterceptor تهیه شده را به سیستم تزریق وابستگیها، توسط متد Decorate کتابخانهی Scrutor که آنرا در قسمت قبل بررسی کردیم، ملاحظه میکنید:
namespace CoreIocSample02 { public class Startup { private static readonly ProxyGenerator _dynamicProxy = new ProxyGenerator(); public void ConfigureServices(IServiceCollection services) { services.AddTransient<ITaskService, MyTaskService>(); services.AddTransient<ExceptionHandlingInterceptor>(); services.Decorate(typeof(ITaskService), (target, serviceProvider) => _dynamicProxy.CreateInterfaceProxyWithTargetInterface( interfaceToProxy: typeof(ITaskService), target: target, interceptors: serviceProvider.GetRequiredService<ExceptionHandlingInterceptor>()) );
- سپس نیاز است خود سرویس اصلی غیر تزئین شده، به نحو متداولی به سیستم معرفی شود.
- در ادامه توسط متد الحاقی Decorate، کار تزئین ITaskService را با یک dynamicProxy که ExceptionHandlingInterceptor را به صورت پویا تبدیل به یک Decorator کرده و بر روی تک تک متدهای سرویس ITaskService اجرا میکند، انجام میدهیم.
- کاری که Scrutor در اینجا انجام میدهد، یافتن سرویس ITaskService معرفی شدهی پیشین و تعویض آن با dynamicProxy میباشد. بنابراین نیاز است تعریف services.AddTransient، پیش از تعریف services.Decorate انجام شده باشد.
یک نکته: چون ExceptionHandlingInterceptor دارای پارامتر تزریق شدهای در سازندهی آن است، بهتر است خود آنرا نیز به صورت یک سرویس ثبت کنیم و سپس وهلهای از آنرا از طریق serviceProvider.GetRequiredService در قسمت interceptors متد CreateInterfaceProxyWithTargetInterface معرفی کنیم تا نیازی به مقدار دهی دستی تک تک پارامترهای سازندهی آن نباشد.
اکنون اگر برنامه را اجرا کنیم و برای مثال ITaskService را در سازندهی یک کنترلر تزریق کنیم، بجای دریافت وهلهای از کلاس MyTaskService، اینبار وهلهای از Castle.Proxies.ITaskServiceProxy را دریافت میکنیم.
به این معنا که Castle.Core به صورت پویا وهلهی سرویس MyTaskService را داخل یک Castle.Proxies پیچیدهاست و از این پس ما از طریق این واسط، با سرویس اصلی MyTaskService ارتباط برقرار خواهیم کرد. برای درک بهتر این مراحل، بر روی سازندهی کلاس کنترلر و همچنین متد Intercept، تعدادی break-point را قرار دهید.
در این مطلب نحوهی استفاده از ReportGenerator را برای ایجاد گزارش مربوط به Code coverage، ارائه میدهیم. ReportGenerator دیتاهای تولید شدهی توسط coverlet, OpenCover, dotCover, Visual Studio, NCover, Cobertura, JaCoCo, Clover و ... را به یک گزارش قابل درک در فرمتهای Html, Coberura و CSV تبدیل میکند. در این مطلب چند موردتست نویسی با xUnit در Asp.Net Core را نوشتهام و اکنون میخواهیم برای تستهای نوشته شده، Code coverage آنها را بدست آوریم.
در کد زیر، سه تست برای متد AverageUsersAge نوشته شده است:
[Fact] public async Task AverageUsersAge_When_Repository_Return_Null_Then_Zero_Should_Be_Returned() { _userRepository.Setup(a => a.GetAllUsers()) .ReturnsAsync((List<User>)null); var result = await _userService.AverageUsersAge(); result.Should().Be(0); } [Fact] public async Task AverageUsersAge_When_Repository_Return_Empty_Then_Zero_Should_Be_Returned() { _userRepository.Setup(a => a.GetAllUsers()) .ReturnsAsync(new List<User>()); var result = await _userService.AverageUsersAge(); result.Should().Be(0); } [Fact] public async Task AverageUsersAge_When_Repository_Return_List_Of_Users_Then_Average_Of_Age_Should_Be_Retunred() { _userRepository.Setup(a => a.GetAllUsers()) .ReturnsAsync(UserMockData.People); var result = await _userService.AverageUsersAge(); var expected = (UserMockData.AdultUser.Age + UserMockData.ChildUser.Age + UserMockData.InfantUser.Age) / 3; result.Should().Be(expected); }
dotnet tool install -g dotnet-reportgenerator-globaltool
dotnet test --collect:"XPlat Code Coverage"
reportgenerator -reports:"C:\Users\Farhad\source\repos\xUnitExample\xUnitExample.Tests\TestResults\*\coverage.cobertura.xml" -targetdir:"coveragereport" -reporttypes:Html
2021-11-18T14:05:02: Arguments 2021-11-18T14:05:02: -reports:C:\Users\Farhad\source\repos\xUnitExample\xUnitExample.Tests\TestResults\*\coverage.cobertura.xml 2021-11-18T14:05:02: -targetdir:coveragereport 2021-11-18T14:05:02: -reporttypes:Html 2021-11-18T14:05:02: Writing report file 'coveragereport\index.html' 2021-11-18T14:05:02: Report generation took 0.3 seconds
در عکس ارسال شده، تستهای نوشته شده برای تمامی لایهها به صورت جدا ایجاد شدهاست.
تست مربوط به UserService:
در عکس بالا قسمتهایی که تست شدهاند، با رنگ سبز مشخص میشود و اگر قسمتی از کد تست نشده باشد، با رنگ قرمز مشخص میشود.
برای مثال در عکس زیر مشخص شدهاست که برای فایل ApplicationConfiguration هیچ تستی نوشته نشدهاست.
اگر از CICD استفاده میکنید، میتوانید در قسمت CI پروژه، هربار دستورات بالا را اجرا کنید و Code coverage مربوط به هر Build را به صورت جداگانه در کنار فایلهای آپلود شده داشته باشد.
نکته: برای اجرای دستورات بالا و ساخت گزارش باید ورژن SDK نصب شده بر روی سیستم شما برابر 2.2.401 یا بیشتر باشد و ورژن Microsoft.NET.Test.Sdk برابر 16.5.0 یا بیشتر باشد.
در این حالت اگر علاقمند به آشنایی با جزئیات این فایل و در حقیقت پشت صحنهی Angular CLI بودید، میتوانید به سری « Web Performance Optimization with webpack » مراجعه کنید.
Head.InnerHtml = SEO.GenerateMetaTag(....)
ادامه آشنایی با NUnit
فرض کنید یک RSS reader نوشتهاید که فیدهای فارسی و انگلیسی را دریافت میکند. به صورت پیش فرض هم مشخص نیست که کدام فید اطلاعات فارسی را ارائه خواهد داد و کدامیک انگلیسی. تشخیص محتوای فارسی و از راست به چپ نشان دادن خودکار مطالب آنها به عهدهی برنامه نویس است. بهترین روش برای تشخیص این نوع الگوها، استفاده از regular expressions است.
برای مثال الگوی تشخیص اینکه آیا متن ما حاوی حروف انگلیسی است یا خیر به صورت زیر است:
[a-zA-Z]
در مورد تشخیص وجود حروف فارسی در یک عبارت، یکی از دو الگوی زیر بکار میرود:
[\u0600-\u06FF]
[ا-یءئ]
کلاس زیر را در مورد استفاده از این الگوها تهیه کردهایم:
using System.Text.RegularExpressions;
namespace sample
{
public static class CDetectFarsi
{
public static bool ContainsFarsiData(this string txt)
{
return !string.IsNullOrEmpty(txt) &&
Regex.IsMatch(txt, "[ا-یءئ]");
}
public static bool ContainsFarsi(this string txt)
{
return !string.IsNullOrEmpty(txt) &&
Regex.IsMatch(txt, @"[\u0600-\u06FF]");
}
}
}
اکنون میخواهیم بررسی کنیم آیا این الگوها مقصود ما را برآورده میسازند یا خیر.
using NUnit.Framework;
using sample;
namespace TestLibrary
{
[TestFixture]
public class TestFarsiClass
{
/*******************************************************************************/
[Test]
public void TestContainsFarsi1()
{
Assert.IsTrue("وحید".ContainsFarsi());
}
[Test]
public void TestContainsFarsi2()
{
Assert.IsTrue("گردان".ContainsFarsi());
}
[Test]
public void TestContainsFarsi3()
{
Assert.IsTrue("سپیدTest".ContainsFarsi());
}
[Test]
public void TestContainsFarsi4()
{
Assert.IsTrue("123بررسی456".ContainsFarsi());
}
[Test]
public void TestContainsFarsi5()
{
Assert.IsFalse("Book".ContainsFarsi());
}
[Test]
public void TestContainsFarsi6()
{
Assert.IsTrue("۱۳۸۷".ContainsFarsi());
}
[Test]
public void TestContainsFarsi7()
{
Assert.IsFalse("Здравствуйте!".ContainsFarsi()); //Russian hello!
}
/*******************************************************************************/
[Test]
public void TestContainsFarsiData1()
{
Assert.IsTrue("وحید".ContainsFarsiData());
}
[Test]
public void TestContainsFarsiData2()
{
Assert.IsTrue("گردان".ContainsFarsiData());
}
[Test]
public void TestContainsFarsiData3()
{
Assert.IsTrue("سپیدTest".ContainsFarsiData());
}
[Test]
public void TestContainsFarsiData4()
{
Assert.IsTrue("123بررسی456".ContainsFarsiData());
}
[Test]
public void TestContainsFarsiData5()
{
Assert.IsFalse("Book".ContainsFarsiData());
}
[Test]
public void TestContainsFarsiData6()
{
Assert.IsTrue("۱۳۸۷".ContainsFarsiData());
}
[Test]
public void TestContainsFarsiData7()
{
Assert.IsFalse("Здравствуйте!".ContainsFarsiData()); //Russian hello!
}
}
}
کلاس CDetectFarsi در برنامه اصلی قرار دارد و کلاس TestFarsiClass در یک پروژه class library دیگر قرار گرفته است (در این مورد و جدا سازی آزمایشها از پروژه اصلی در قسمتهای قبل بحث شد)
همچنین به ازای هر عبارت Assert یک متد ایجاد گردید تا شکست یکی، سبب اختلال در بررسی سایر موارد نشود.
نتیجه اجرای این آزمایش واحد با استفاده از امکانات مجتمع افزونه ReSharper به صورت زیر است:
منهای یک مورد، سایر آزمایشات ما با موفقیت انجام شدهاند. موردی که با شکست مواجه شده، بررسی اعداد کاملا فارسی است که البته در الگوی دوم لحاظ نشده است و انتظار هم نمیرود که آنرا به این شکل پشتیبانی کند.
برای اینکه در حین اجرای آزمایشات بعدی این متد در نظر گرفته نشود، میتوان ویژگی Test آنرا به صورت زیر تغییر داد:
[Test,Ignore]
Test[MethodToBeTested][SomeAttribute]