کار بر روی توسعهی NET 7.0. شروع شده
ابتدا فایل yml زیر را در پوشهی github\workflows\deploy.yml. قرار دهید (پوشهای را به این نام، در ریشهی پروژهی خود ایجاد کنید):
name: Deploy to GitHub Pages # Run workflow on every push to the main branch on: push: branches: [ main ] jobs: deploy-to-github-pages: # use ubuntu-latest image to run steps on runs-on: ubuntu-latest steps: # uses GitHub's checkout action to checkout code form the main branch - uses: actions/checkout@v2 # sets up .NET Core SDK - name: Setup .NET Core SDK uses: actions/setup-dotnet@v1 with: dotnet-version: 5.0.302 # publishes Blazor project to the release-folder - name: Publish .NET Core Project run: dotnet publish ./src/DNTPersianComponents.Blazor.WasmSample/Server/DNTPersianComponents.Blazor.WasmSample.Server.csproj -c Release -o release --nologo # changes the base-tag in index.html from '/' to 'DNTPersianComponents.Blazor' to match GitHub Pages repository subdirectory - name: Change base-tag in index.html from / to DNTPersianComponents.Blazor run: sed -i 's/<base href="\/" \/>/<base href="\/DNTPersianComponents.Blazor\/" \/>/g' release/wwwroot/index.html # copy index.html to 404.html to serve the same file when a file is not found - name: copy index.html to 404.html run: cp release/wwwroot/index.html release/wwwroot/404.html # add .nojekyll file to tell GitHub pages to not treat this as a Jekyll project. (Allow files and folders starting with an underscore) - name: Add .nojekyll file run: touch release/wwwroot/.nojekyll - name: Commit wwwroot to GitHub Pages uses: JamesIves/github-pages-deploy-action@3.7.1 with: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} BRANCH: github-pages FOLDER: release/wwwroot
- نام شاخهی اصلی پروژه؛ که یا main است و یا master.
- شماره نگارش دات نت مورد استفاده.
- مسیر فایل csproj پروژهی wasm.
- نام اصلی مخزن کد.
سپس آنرا به مخزن کد خود commit کنید. بعد به قسمت settings->pages در github مراجعه کرده و source را بر روی نام شاخهی جدید github-pages (فوق در قسمت آخر کار) قرار داده و آنرا ذخیره کنید. الان سایت دموی شما در مسیری که در همین قسمت pages پس از ذخیره سازی، نمایش میدهد، آمادهاست.
یک نکتهی مهم
چون base href، توسط action فوق اصلاح میشود تا به پوشهی نسبی محل قرارگیری برنامه اشاره کند، نیاز است navlinkها با href شروع شدهی با / نباشند؛ چون به ریشهی سایت اشاره میکنند و نه مسیر نسبی محل قرارگیری برنامه. کلا در هر قسمتی از برنامه، این نکته باید رعایت شود. مثلا اگر فونت وبی را در فایل app.css تعریف کردهاید، مسیر آن نباید با / شروع شود؛ وگرنه یافت نخواهد شد. یک مثال:
فایل app.css برنامه در مسیر wwwroot\css\app.css قرار دارد و داخل آن فایل، فونتهای پوشهی دیگر wwwroot\lib\samim-font را به صورت زیر تعریف کردهایم؛ که یعنی مسیر فونت را از ریشهی سایت پیدا کن:
src: url('/lib/samim-font/Samim-Bold.eot?v=4.0.5');
src: url('../lib/samim-font/Samim-Bold.eot?v=4.0.5');
DebuggerStepThroughAttribute
class Program { public static void Main(string[] args) { DebuggerStepThroughMethod1(); } [DebuggerStepThrough] public static void DebuggerStepThroughMethod1() { Console.WriteLine( "Method 1" ); DebuggerStepThroughMethod2(); } [DebuggerStepThrough] public static void DebuggerStepThroughMethod2() { Console.WriteLine( "Method 2" ); } }
ConditionalAttribute
class Program { public static void Main(string[] args) { DebugMode(); } [Conditional("DEBUG")] public static void DebugMode() { Console.WriteLine( "Debug mode" ); } }
#define ReleaseMode
Flags Enum Attribute
[System.Flags] public enum Permission { View = 1, Insert = 2, Update = 4, Delete = 8 }
public static void Main( string[] args ) { var permission = ( Permission.View | Permission.Insert ).ToString(); Console.WriteLine( permission ); // Displays ‘View, Insert’ var userPermission = Permission.View | Permission.Insert | Permission.Update | Permission.Delete; // To retrieve the value from property you can do this if ( ( userPermission & Permission.Delete ) == Permission.Delete ) { Console.WriteLine( "کاربر دارای مجوز دسترسی به عملیات حذف میباشد" ); } // In .NET 4 and later Console.WriteLine( userPermission.HasFlag( Permission.Delete ) ? "کاربر دارای مجوز دسترسی به عملیات حذف میباشد" : "کاربر مجوز دسترسی به عملیات حذف را ندارد"); }
نکته: در صورتیکه مقداری را برای enum تعریف کرده باشید، نمیتوانید آن را با مقدار 0 مشخص کنید (در زمانی که ویژگی flags را بر روی enum اضافه کرده باشید)، چرا که با استفاده از عملیات بیتی AND نمیتوانید دارا بودن آن مقدار را تست کنید و همیشه نتیجه صفر خواهد بود.
Dynamically Compile and Execute C# Code
CodeDOM
public static void Main( string[] args ) { var sourceCode = @"class DotNetTips { public void Print() { System.Console.WriteLine("".Net Tips""); } }"; var compiledAssembly = CompileSourceCodeDom( sourceCode ); ExecuteFromAssembly( compiledAssembly ); } static Assembly CompileSourceCodeDom( string sourceCode ) { CodeDomProvider csharpCodeProvider = new CSharpCodeProvider(); var cp = new CompilerParameters { GenerateExecutable = false }; cp.ReferencedAssemblies.Add( "System.dll" ); var cr = csharpCodeProvider.CompileAssemblyFromSource( cp, sourceCode ); return cr.CompiledAssembly; }
Roslyn
سکوی کامپایلر دات نت " Roslyn "، کامپایلرهای متن باز #C و VB.NET را به همراه APIهای تجزیه و تحلیل کد ارائه کرده است که با استفاده از این APIها میتوان ابزارهای آنالیز کد جهت استفاده در ویژوال استودیو را ایجاد کرد.
برای استفاده از Roslyn باید این کتابخانه را نصب کنید
Install-Package Microsoft.CodeAnalysis
حال مثال قبل را با استفاده از Roslyn بازنویسی میکنیم:
public static void Main(string[] args) { var sourceCode = @"class DotNetTips { public void Print() { System.Console.WriteLine("".Net Tips""); } }"; var compiledAssembly = CompileSourceRoslyn( sourceCode ); ExecuteFromAssembly( compiledAssembly ); } private static Assembly CompileSourceRoslyn(string sourceCode) { using ( var memoryStream = new MemoryStream() ) { var assemblyFileName = string.Concat( Guid.NewGuid().ToString(), ".dll" ); var compilation = CSharpCompilation.Create( assemblyFileName, new[] { CSharpSyntaxTree.ParseText( sourceCode ) }, new[] { MetadataReference.CreateFromFile( typeof( object ).Assembly.Location ) }, new CSharpCompilationOptions( OutputKind.DynamicallyLinkedLibrary ) ); compilation.Emit( memoryStream ); var assembly = Assembly.Load( memoryStream.GetBuffer() ); return assembly; } }
و جهت فراخوانی اسمبلی ساخته شده به هر دو روش بالا، از کد زیر استفاده میکنیم.
static void ExecuteFromAssembly( Assembly assembly ) { var helloKittyPrinterType = assembly.GetType( "DotNetTips" ); var printMethod = helloKittyPrinterType.GetMethod( "Print" ); var kitty = assembly.CreateInstance( "DotNetTips" ); printMethod.Invoke( kitty, BindingFlags.InvokeMethod, null, null, CultureInfo.CurrentCulture ); }
تدارک یک آزمایش برای بررسی میزان افزایش کارآیی Reflection در دات نت 7
یک برنامهی کنسول جدید را ایجاد کرده و سپس کلاس Person را به صورت زیر به آن اضافه میکنیم:
namespace NET7Reflection; public class Person { private int _age; internal Person(int age) => _age = age; private int GetAge() => _age; private void SetAge(int age) => _age = age; }
به همین جهت ابتدا کتابخانهی BenchmarkDotNet را با TargetFramework دات نت 7 به صورت زیر به پروژه اضافه میکنیم:
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <OutputType>Exe</OutputType> <TargetFramework>net7.0</TargetFramework> <ImplicitUsings>enable</ImplicitUsings> <Nullable>enable</Nullable> </PropertyGroup> <ItemGroup> <PackageReference Include="BenchmarkDotNet" Version="0.13.4" /> </ItemGroup> </Project>
using System.Reflection; using BenchmarkDotNet.Attributes; namespace NET7Reflection; [MemoryDiagnoser(false)] public class Benchmarks { private readonly object?[] _ageParams = { 30 }; private readonly ConstructorInfo _ctor = typeof(Person).GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance, new[] { typeof(int) })!; private readonly MethodInfo _getAgeMethod = typeof(Person).GetMethod("GetAge", BindingFlags.NonPublic | BindingFlags.Instance)!; private readonly Person _person = new(10); private readonly MethodInfo _setAgeMethod = typeof(Person).GetMethod("SetAge", BindingFlags.NonPublic | BindingFlags.Instance)!; [Benchmark] public int GetAge() => (int)typeof(Person).GetMethod("GetAge", BindingFlags.NonPublic | BindingFlags.Instance)! .Invoke(_person, Array.Empty<object?>())!; [Benchmark] public int GetAgeCachedMethod() => (int)_getAgeMethod.Invoke(_person, Array.Empty<object?>())!; [Benchmark] public void SetAge() => typeof(Person).GetMethod("SetAge", BindingFlags.NonPublic | BindingFlags.Instance)! .Invoke(_person, new object?[] { 30 }); [Benchmark] public void SetAgeCachedMethod() => _setAgeMethod.Invoke(_person, new object?[] { 30 }); [Benchmark] public void SetAgeCachedMethodCachedParams() => _setAgeMethod.Invoke(_person, _ageParams); [Benchmark] public Person Ctor() => (Person)typeof(Person).GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance, new[] { typeof(int) })! .Invoke(_person, new object?[] { 30 })!; [Benchmark] public Person CtorCachedCtorInfo() => (Person)_ctor.Invoke(_person, new object?[] { 30 })!; [Benchmark] public Person CtorCachedCtorInfoCachedParams() => (Person)_ctor.Invoke(_person, _ageParams)!; }
- در اینجا نحوهی کار با متدهای خصوصی کلاس Person را توسط Reflection مشاهده میکنید. برای مثال در متد GetAge، به نحو متداولی این کار صورت گرفتهاست. در متد GetAgeCachedMethod، قسمت دریافت اطلاعات متد، کش شدهاست و برای نمونه در متد SetAgeCachedMethodCachedParams، هم کش شدن قسمت دریافت اطلاعات متد را مشاهده میکنید و هم کش شدن پارامتر ارسالی به آنرا.
- این آزمایش را با فراخوانی زیر و تنظیم target framework به دات نت 6 و سپس دات نت 7، به صورت جداگانهای اجرا میکنیم:
using BenchmarkDotNet.Running; using NET7Reflection; BenchmarkRunner.Run<Benchmarks>();
و با target framework دات نت 7 به صورت زیر:
همانطور که مشاهده میکنید، در اکثر موارد، کارآیی Reflection در دات نت 7، حداقل 2 برابر نمونهی مشابه دات نت 6 است.
چه تغییری در دات نت 7 سبب بهبود قابل ملاحظهی کارآیی Reflection شدهاست؟
جزئیات تغییرات صورت گرفتهی در Reflection دات نت 7 را میتوانید در این pull request مشاهده کنید که در حقیقت از امکانات سطح پایین IL Emit استفاده کردهاند. در این مورد پیشتر تعدادی مطلب ذیل عنوان «آشنایی با Reflection.Emit» در این سایت منتشر شدهاند که میتوانید آنها را بررسی کنید.
در کل هرچند تغییرات جدید دات نت مانند ارائهی انواع و اقسام source generators، در تعدادی از موارد نیاز به Reflection را کمتر کردهاند و کارآیی بیشتری را ارائه دادهاند، اما Reflection هیچگاه منسوخ نخواهد شد و هرگونه بهبود کارآیی در این زمینه، بر روی کل فریمورک و برنامههای مشتق شدهی از آن، تاثیر قابل توجهی را خواهد گذاشت.
در زبانهای CLR شما دیگر وقت خود را به موضوعاتی چون مدیریت حافظه، هماهنگ سازی تردها و مباحث امنیتی و صدور استثناء در سطوح پایینتر نمیدهید و فرقی هم نمیکند که از چه زبانی استفاده میکنید. بلکه CLR هست که این امور را انجام میدهد و این مورد بین تمامی زبانهای CLR مشترک است. برای مثال کاربری که قرار است در زمان اجرا استثناءها را صادر کند، در واقع مهم نیست که از چه زبانی برای آن استفاده میکند. بلکه آن CLR است که مدیریت آن را به عهده دارد و روال کار CLR برای همه زبانها یکی است. پس این سوال پیش میآید که وقتی مبنا و زیر پایهی همه زبانهای CLR یکی است، چرا تعدد زبان دیده میشود و مزیت هر کدام بر دیگری چیست؟ اولین مورد syntax آن است. هر کاربر رو به چه زبانی کشیده میشود و شاید تجربهی سابق در قدیم با یک برنامهی مشابه بوده است که همچنان همان رویه سابق را ادامه میدهد و یا اینکه نحوهی تحلیل و آنالیز کردن کدهای آن زبان است که کاربر را به سمت خود جذب کرده است. گاهی اوقات بعضی از زبانها با تمرکز در انجام بعضی از کارها چون امور مالی یا ریاضیات، موارد فنی و ... باعث جذب کاربران آن گروه کاری به سمت خود میشوند. البته بعدا در آینده متوجه میشویم که بسیاری از زبانها مثل سی شارپ و ویژوال بیسیک هر کدام قسمتی از امکانات CLR را پوشش میدهند نه تمام آن را.
زبانهای CLR چگونه کار میکنند؟
در اولین گام بعد از نوشتن برنامه، کامپایلر آن زبان دست به کار شده و برنامه را برای شما کامپایل میکند. ولی اگر تصور میکنید که برنامه را به کد ماشین تبدیل میکند و از آن یک فایل اجرایی میسازد، سخت در اشتباه هستید. کامپایلر هر زبان CLR، کدها را به یک زبان میانی Intermediate Language به اختصار IL تبدیل میکند. فرقی نمیکند چه زبانی کار کردهاید، کد شما تبدیل شده است به یک زبان میانی مشترک. CLR نمیتواند برای تک تک زبانهای شما یک مفسر داشته باشد. در واقع هر کمپایلر قواعد زبان خود را شناخته و آن را به یک زبان مشترک تبدیل میسازد و حالا CLR میتواند حرف تمامی زبانها را بفهمد. به فایل ساخته شده managed module گویند و به زبانهایی که از این قواعد پیروی نمیکنند unmanaged گفته میشود؛ مثل زبان سی ++ که در دات نت هم managed و هم unmanaged داریم که اولی بدون فریم ورک دات نت کار میکند و مستقیما به کد ماشین تبدیل میشود و دومی نیاز به فریم ورک دات نت داشته و به زبان میانی کامپایل میشود. جدول زیر نشان میدهد که کد همهی زبانها تبدیل به یک نوع شده است.
فایل هایی که ساخته میشوند بر دو نوع هستند؛ یا بر اساس استاندارد windows Portable Executable 32bits برای سیستمهای 32 بیتی و 64 بیتی هستند و یا بر اساس windows Portable Executable 64bits مختص سیستمهای 64 بیتی هستند که به ترتیب PE32 و +PE32 نامیده میشوند که CLR بر اساس این اطلاعات آنها را به کد اجرایی تبدیل میکند. زبانهای CLR همیشه این مزیت را داشتهاند که اصول امنیتی چون DEP یا Data Execution Prevention و همچنین ASLR یا Address Space Layout Randomization در آنها لحاظ شده باشد.
یکبار سعی کنید مثال ساده زیر را اجرا کنید:
using System;
using System.Text.RegularExpressions;
namespace RegexLoop
{
class Program
{
static void Main(string[] args)
{
var emailAddressRegex = new Regex(@"^[A-Za-z0-9]([_\.\-]?[A-Za-z0-9]+)*\@[A-Za-z0-9]([_\.\-]?[A-Za-z0-9]+)*\.[A-Za-z0-9]([_\.\-]?[A-Za-z0-9]+)*$|^$");
if (emailAddressRegex.IsMatch("an.infinite.loop.sample.just_for.test"))
{
Console.WriteLine("Matched!");
}
var input = "The quick brown fox jumps";
var pattern = @"([a-z ]+)*!";
if (Regex.IsMatch(input, pattern))
{
Console.WriteLine("Matched!");
}
}
}
}
پس از اجرا، برنامه هنگ خواهد کرد یا به عبارتی برنامه در یک حلقه بینهایت قرار میگیرد (در هر دو مثال؛ اطلاعات بیشتر و آنالیز کامل در اینجا). بنابراین نیاز به مکانیزمی امنیتی جهت محافظت در برابر این نوع ورودیها وجود خواهد داشت؛ مثلا یک Timeout . اگر تا 2 ثانیه به جواب نرسیدیم، اجرای Regex متوقف شود. تا دات نت 4، چنین timeout ایی پیش بینی نشده؛ اما در دات نت 4 و نیم آرگومانی جهت تعریف حداکثر مدت زمان قابل قبول اجرای یک عبارت باقاعده در نظر گرفته شده است (^) و اگر در طی مدت زمان مشخص شده، کار انجام محاسبات به پایان نرسد، استثنای RegexMatchTimeoutException صادر خواهد شد.
خیلی هم خوب. به این ترتیب کسی نمیتونه با یک ورودی ویژه، CPU Usage سیستم رو تا مدت زمان نامحدودی به 100 درصد برساند و عملا استفاده از سیستم رو غیرممکن کنه.
اما تا قبل از دات نت 4 و نیم چکار باید کرد؟ روش کلی حل این مساله به این ترتیب است که باید اجرای Regex را به یک ترد دیگر منتقل کرد؛ اگر مدت اجرای عملیات، از زمان تعیین شده بیشتر گردید، آنگاه میشود ترد را Abort کرد و به عملیات خاتمه داد. روش پیاده سازی و نحوه استفاده از آنرا در ادامه ملاحظه خواهید نمود:
using System;
using System.Text.RegularExpressions;
using System.Threading;
namespace RegexLoop
{
public static class TimedRunner
{
public static R RunWithTimeout<R>(Func<R> proc, TimeSpan duration)
{
using (var waitHandle = new AutoResetEvent(false))
{
var ret = default(R);
var thread = new Thread(() =>
{
ret = proc();
waitHandle.Set();
}) { IsBackground = true };
thread.Start();
bool timedOut = !waitHandle.WaitOne(duration, false);
waitHandle.Close();
if (timedOut)
{
try
{
thread.Abort();
}
catch { }
return default(R);
}
return ret;
}
}
}
class Program
{
static void Main(string[] args)
{
var emailAddressRegex = new Regex(@"^[A-Za-z0-9]([_\.\-]?[A-Za-z0-9]+)*\@[A-Za-z0-9]([_\.\-]?[A-Za-z0-9]+)*\.[A-Za-z0-9]([_\.\-]?[A-Za-z0-9]+)*$|^$");
if (TimedRunner.RunWithTimeout(
() => emailAddressRegex.IsMatch("an.infinite.loop.sample.just_for.test"),
TimeSpan.FromSeconds(2)))
{
Console.WriteLine("Matched!");
}
var input = "The quick brown fox jumps";
var pattern = @"([a-z ]+)*!";
if (TimedRunner.RunWithTimeout(() => Regex.IsMatch(input, pattern), TimeSpan.FromSeconds(2)))
{
Console.WriteLine("Matched!");
}
}
}
}
اینبار به هر کدام از عبارات باقاعده 2 ثانیه زمان برای اتمام کار داده شده است. در غیراینصورت مقدار پیش فرض خروجی متد فراخوانی شده، بازگشت داده میشود که در اینجا false است.
فقط یک HTTP Server ساده
خیلی موقعها نیاز میشه یه http server ساده لانچ کنیم. مثلا نیازه فایلهای زیر رو
D:\Folder\file.zip D:\Folder\file2.html D:\Folder\folder\file-in-folder.jpg
از طریق http قابل درسترس کنیم مانند زیر
در اینجا با ابزار http-server به راحتی میشه این کارو کرد
ابتدا نصب
npm install http-server -g
و سپس اجرا
cd D:\Folder http-server -------------- OR ------------- http-server D:\Folder
نمونه دات نتی این ابزار
https://github.com/natemcmaster/dotnet-serve
Simple command-line HTTPS server for the .NET Core CLI
dotnet tool install --global dotnet-serve
و سپس اجرا
dotnet serve
دورهی کامل میکروسرویسها در دات نت
.NET Microservices – Full Course, Les Jackson
In this step-by-step tutorial I take you through an introduction on building microservices using .NET. As the name suggests we build everything completely from start to finish –with the full scope of the course outlined in the time-stamp section below. However, at a high-level we’ll cover:
• Building two .NET Microservices using the REST API pattern
• Working with dedicated persistence layers for both services
• Deploying our services to Kubernetes cluster
• Employing the API Gateway pattern to route to our services
• Building Synchronous messaging between services (HTTP & gRPC)
• Building Asynchronous messaging between services using an Event Bus (RabbitMQ)