مطالب
نگاهی به تغییرات فایل Program.cs در نگارش‌های مختلف ASP.NET Core تا نگارش 6
در طی سال‌های قبل، نحوه‌ی آغاز برنامه‌های ASP.NET Core دچار تغییرات زیادی شده‌است که حداقل سه مورد مهم آن‌ها به صورت زیر است:
- استفاده از WebHost.CreateDefaultBuilder: این روش جهت تنظیم شروع به کار یک برنامه‌ی ASP.NET Core 2x مورد استفاده قرار می‌گرفت.
- استفاده از Host.CreateDefaultBuilder: روش پیش‌فرض آغاز برنامه‌های وب NET Core 3x. و NET 5x. که با معرفی generic host، امکان تهیه‌ی Worker services را میسر کردند.
- استفاده از WebApplication.CreateBuilder: روش جدید شروع به کار با برنامه‌های وب مبتنی بر NET 6.


استفاده از WebHost.CreateDefaultBuilder در ASP.NET Core 2.x

در نگارش اول ASP.NET Core، مفهومی به نام هاست پیش‌فرض (default host) وجود نداشت. یکی از مهم‌ترین نکات درنظر گرفته شده در طراحی ASP.NET Core، مفهوم «هزینه کردن به ازای احتیاج» است. یعنی اگر نیاز به قابلیتی نیست، نباید وجود داشته باشد. به این ترتیب یک قالب آغازین نباید به همراه تعداد زیادی بسته‌‌های NuGet و مقدار زیادی کد برای تنظیم باشد؛ زمانیکه واقعا قرار نیست از تمام آن‌ها استفاده شود. به همین جهت از نگارش 2، شروع به ساده‌کردن این قالب اولیه کردند و در این زمان، WebHost.CreateDefaultBuilder ارائه شد. این تنظیم به ظاهر ساده، کدهای پیش‌فرض مورد نیاز قابل توجهی را جهت ساخت ساده‌ی IWebHostBuilder و IWebHost به همراه دارد. در قالب به همراه آن، بین مفاهیم تنظیمات application و host تفاوت قائل شده‌اند؛ یعنی دو فایل Program.cs را برای تنظیمات application و فایل Startup.cs را برای ارائه‌ی تنظیمات هاست، تدارک دیدند.
در این طراحی، بین میدان دید Program و Startup تفاوت وجود دارد. هدف از Program، ارائه‌ی تنظیمات زیرساختی برنامه، مانند تنظیمات HTTP Server، یکپارچگی با IIS و امثال آن شد و عموما در طول عمر برنامه ثابت است. اما برای تغییر رفتار برنامه، بیشتر تغییرات و تنظیمات، به Startup محول شدند؛ مانند تنظیمات تزریق وابستگی‌ها، تنظیمات میان‌افزارها، مسیریابی‌ها و غیره.
public class Program
{
    public static void Main(string[] args)
    {
        BuildWebHost(args).Run();
    }

     public static IWebHost BuildWebHost(string[] args) =>
        WebHost.CreateDefaultBuilder(args)
            .UseStartup<Startup>()
            .Build();
}
نمونه‌ا‌ی از فایل Program.cs برنامه‌های ASP.NET Core 2x را در اینجا ملاحظه می‌کنید این تنظیم ساده، کار خواندن فایل appsettings.json و برپایی تنظیمات برنامه، تنظیمات logging و همچنین تنظیمات یکپارچگی با Kestrel/IIS را در پشت صحنه انجام می‌دهد. همچنین ارجاعی را نیز به کلاس Startup برنامه دارد. متد UseStartup به صورت خودکار به دنبال متدهای ویژه‌ی ConfigureServices و Configure در کلاس آغازین برنامه گشته و آن‌ها را فراخوانی می‌کند تا تنظیمات تزریق وابستگی‌ها، مسیریابی‌ها و میان‌افزارها صورت گیرند.


معرفی Generic Host در ASP.NET Core 3.x/5

ASP.NET Core 3.x به همراه تغییرات بزرگی در کدهای آغازین برنامه‌های ASP.NET Core بود. تا پیش از آن، امکانات پایه‌ی ASP.NET Core تنها برای مقاصد وب قابل استفاده بود. اما در نگارش‌های 3x، با ارائه‌ی یک هاست عمومی، امکان پشتیبانی از سایر برنامه‌ها، مانند worker services را نیز میسر کرد؛ برای مثال پیشتیبانی از کارهای طولانی پس زمینه، هاست سرویس‌های gRPC، هاست سرویس‌های ویندوز و غیره. هدف اصلی از این تغییرات، به اشتراک گذاری فریم‌ورک پایه‌ی ASP.NET Core که تنها برای برنامه‌های وب ساخته شده بود و به همراه امکاناتی مانند تنظیمات، ثبت وقایع، تزریق وابستگی‌ها و غیره بود، با سایر انواع برنامه‌های یاد شده است. برای رسیدن به این هدف، Web Host نگارش 2x، به Generic Host نگارش 3x تغییر کرد و سپس ASP.NET Core برفراز آن بنا شد. اینبار بجای IWebHostBuilder، نمونه‌ی جدید IHostBuilder را داریم:
public class Program
{
    public static void Main(string[] args)
    {
        CreateHostBuilder(args).Build().Run();
    }

    public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            .ConfigureWebHostDefaults(webBuilder =>
            {
                webBuilder.UseStartup<Startup>();
            }; 
    }
}
در اینجا نمونه‌ا‌ی از فایل Program.cs برنامه‌های ASP.NET Core 3x را ملاحظه می‌کنید. با توجه به عمومی بودن این روش، قسمت ConfigureWebHostDefaults آن، همان کار WebHost.CreateDefaultBuilder نگارش 2 ASP.NET Core را انجام می‌دهد.
ASP.NET Core 5 به همراه تغییرات مهمی در این زمینه نبود و تنها با تغییر target framework در فایل csproj و به روز رسانی بسته‌های نیوگت مرتبط، کار ارتقاء به نگارش جدید در آن صورت می‌گرفت.


معرفی  WebApplicationBuilder در ASP.NET Core 6x  

در دات نت 6، روش آغاز برنامه‌های وب بطور کامل تغییر کرده‌است. در تمام نگارش‌های پیشین ASP.NET Core، همواره شاهد دو فایل Program.cs و Startup.cs بودیم؛ اما در اینجا فقط یک فایل Program.cs بیشتر وجود ندارد. هر چند همانطور که در مطلب «ارتقاء فایل‌های آغازین برنامه‌های ASP.NET Core 5x به 6x» نیز عنوان شد، می‌توان همان سبک و سیاق پیشین را نیز برگرداند و از این لحاظ محدودیتی وجود ندارد.
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
   app.UseDeveloperExceptionPage();
}
app.UseStaticFiles();
app.MapGet("/", () => "Hello World!");
app.MapRazorPages();
app.Run();
در اینجا نمونه‌ای از فایل Program.cs برنامه‌های دات نت 6 را مشاهده می‌کنید که تغییرات قابل ملاحظه‌ای داشته‌است:
- استفاده از top level statements
- استفاده از usingهای سراسری و سایر قابلیت‌های C# 10.0
- حذف کامل کلاس Startup؛ اینبار همه چیز فقط یک فایل است.

دیدگاهی را که در اینجا بکار گرفته‌اند شامل این موارد است و بیشتر تازه‌واردان را مدنظر دارند:
- برای شروع به کار، using statements اضافی هستند؛ پس حذف شده‌اند!
- برای شروع به کار، namespaces اضافی هستند؛ پس حذف شده‌اند!
- برای شروع به کار، Program.Main ... هم اضافی است!
- تنظیمات بین دو فایل Program.cs و Startup.cs تقیسم نشده‌اند و همه‌چیز یکجا است و این هم نیازی به توضیح اضافی به تازه‌واردان، ندارد.
- همچنین همانطور که عنوان شد، « ... متد UseStartup به صورت خودکار به دنبال متدهای ویژه‌ی ConfigureServices و Configure در کلاس آغازین برنامه گشته و آن‌ها را فراخوانی می‌کند تا تنظیمات تزریق وابستگی‌ها، مسیریابی‌ها و میان‌افزارها صورت گیرند ...»
نکته‌ی مهم این توضیح، این است که کلاس Startup، هیچ اینترفیسی را پیاده سازی نمی‌کند. یعنی نام این متدها، دقیقا باید به همین صورت باشند (بدون اینکه قرار دادی توسط یک اینترفیس برای آن‌ها وضع شده باشد) و ... چرا واقعا باید به این صورت باشد؟! به همین جهت این‌ها هم حذف شده‌اند!
- در اینجا WebApplication هم مشاهده می‌شود؛ اما آیا واقعا نیازی به آن است؟
پاسخ: خیر! WebApplication.CreateBuilder ای که در اینجا ملاحظه می‌کنید در حقیقت ساده شده‌ی قطعه کد زیر از کدهای ASP.NET Core 3x/5x است:
var hostBuilder = Host.CreateDefaultBuilder(args)
    .ConfigureServices(services => 
    {
        services.AddRazorPages();
    })
    .ConfigureWebHostDefaults(webBuilder =>
    {
        webBuilder.Configure((ctx, app) => 
        {
            if (ctx.HostingEnvironment.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            app.UseStaticFiles();
            app.UseRouting();
            app.UseEndpoints(endpoints =>
            {
                endpoints.MapGet("/", () => "Hello World!");
                endpoints.MapRazorPages();
            });
        });
    }); 
hostBuilder.Build().Run();
که ... WebApplication.CreateBuilder بدون شک ساده‌تر از قطعه کد فوق است. همچنین پس از یک سطر زیر:
 var builder = WebApplication.CreateBuilder(args);
با استفاده از خاصیت builder.Configuration، می‌توان به تنظیمات برنامه دسترسی یافت و یا با استفاده از builder.Services می‌توان سرویس‌های جدیدی را ثبت کرد:
builder.Services.AddRazorPages();
builder.Services.AddSingleton<MyThingy>();
و یا با استفاده از خاصیت builder.Logging می‌توان تنظیمات ثبت وقایع برنامه را تغییر داد:
builder.Logging.AddFile();
همچنین در اینجا زمانیکه کار تنظیمات به پایان رسید، نمونه‌ای از WebApplication را به صورت زیر تولید کرده:
var app = builder.Build();
و اکنون می‌توان تنظیمات میان‌افزارها و یا مسیریابی‌ها را انجام داد:
app.UseStaticFiles();
app.MapRazorPages();
که در مقایسه با نگارش‌های قبلی، بسیار ساده‌تر شده‌، هرچند مرز بین این‌ها و ترتیب آن‌ها اندکی کم‌رنگ‌تر شده‌است.
مطالب
CoffeeScript #3

Syntax

Object & Array

برای تعریف Object در CoffeeScript می‌توان دقیقا مانند جاوااسکریپت عمل کرد؛ با یک جفت براکت و ساختار کلید / مقدار. البته همانند تابع، نوشتن براکت اختیاری است. در واقع، شما می‌توانید از تورفتگی و هر کلید/مقدار، در خط جدید به جای کاما استفاده کنید:

object1 = {one: 1, two: 2}

# Without braces
object2 = one: 1, two: 2

# Using new lines instead of commas
object3 = 
  one: 1
  two: 2

User.create(name: "Vahid Mohammad Taheri")
به همین ترتیب، برای تعریف آرایه‌ها می‌توانید از کاما به عنوان جدا کننده و یا هر مقدار آرایه را در یک خط جدید وارد کنید؛ هر چند براکت [] هنوز هم مورد نیاز است.
array1 = [1, 2, 3]

array2 = [
  1
  2
  3
]

array3 = [1,2,3,]

Flow control


طبق قاعده‌ای که برای نوشتن پرانتز در قبل گفته شد (پرانتز اختیاری است)، در دستورات if و else نیز چنین است:
if true == true
  "We're ok"

if true != true then "Vahid"

# برابر است با:
#  (1 > 0) ? "Yes" : "No!"
if 1 > 0 then "Yes" else "No!"
همانطوری که در مثال بالا مشاهده می‌کنید، در صورتی که از if در یک خط استفاده شود باید پس از شرط، کلمه کلیدی then را بنویسید.
CoffeeScript از اپراتورهای شرطی (:?) پشتیبانی نمی کند و به جای آن از if / else استفاده کنید.
CoffeeScript نیز همانند Ruby امکان نوشتن بدنه شرط را به صورت پسوندی ایجاد کرده است.
alert "It's cold!" if 1 < 5
به جای استفاده از علامت ! برای منفی سازی شرط، می‌توانید از کلمه‌ی کلیدی not استفاده کنید که سبب خوانایی بیشتر کد نوشته شده می‌شود:
if not true then "Vahid"
CoffeeScript امکان نوشتن خلاصه‌تر if not را نیز ایجاد کرده است؛ برای این کار از کلمه‌ی کلیدی unless استفاده کنید. معادل مثال بالا:
unless true
  "Vahid"
همانند not که برای خوانایی بالاتر کد به کار می‌رود، CoffeeScript کلمه کلیدی is را مطرح کرده‌است که پس از کامپایل به === ترجمه می‌شود.
if true is 1
  "OK!"
برای نوشتن ==! نیز می‌توان از is not استفاده کرد، که شکل خلاصه‌تر آن isnt است.
if true isnt true
  alert "OK!"
همانطوری که در بالا گفته شد، CoffeeScript عملگر == را به === و =! به ==! تبدیل می‌کند. دلیلی که CoffeeScript این عمل را انجام می‌دهد این است که جاوااسکریپت عمل مقایسه را بر روی نوع و سپس مقدار آن انجام می‌دهد و سبب پیشگیری از باگ در کد نوشته شده می‌شود.

الحاق رشته ها

CoffeeScript امکان الحاق رشته‌ها را با استفاده از روش الحاق رشته‌ها در Ruby فراهم کرده است. برای انجام این عمل از {}# در داخل " " استفاده کنید که در داخل براکت می‌توانید از دستورات مختلف استفاده کنید. برای مثال:
favorite_color = "Blue. No, yel..."
question = "Sam: What... is your favorite color?
            Ben: #{favorite_color}
            Sam: Wrong!
            "
نتیجه‌ی کامپایل کد بالا می‌شود:
var favorite_color, question;
favorite_color = "Blue. No, yel...";
question = "Sam: What... is your favorite color?            Ben: " + favorite_color + "            Sam: Wrong!";



مطالب
ضمیمه کردن فایل در RavenDb
یکی از مواردی که در بانک‌های اطلاعاتی امروزه بیشتر مورد استفاده قرار میگیرد، ذخیره فایل‌ها در خود دیتابیس، بجای ذخیره نام یا آدرس آن‌ها بر روی دیسک سخت است. از همان ابتدا که Raven به بازار عرضه شد، امکان ذخیره فایل‌های باینری را با استفاده از افزونه هایی که به همراه داشت برای برنامه نویسان مهیا ساخت.  این امکان از طریق کد زیر برای ذخیره یک فایل کفایت میکرد:
using (var store = new DocumentStore
            {
                Url = "http://localhost:8080"
            }.Initialize())
            {
                using (var session = store.OpenSession())
                {
                    store.DatabaseCommands.PutAttachment(key: "file/1",
                                                         etag: null,
                                                         data: System.IO.File.OpenRead(@"D:\Prog\packages.config"),
                                                         metadata: new RavenJObject
                                                         {
                                                            { "Description", "توضیحات فایل" }
                                                         });
                    var question = new Question
                    {
                        By = "users/Vahid",
                        Title = "Raven Intro",
                        Content = "Test....",
                        FileId = "file/1"
                    };
                    session.Store(question);

                    session.SaveChanges();
                }
            }
ولی اگر از نسخه سه بعد RavenDb را استفاده کنید، می‌بینید که متد PutAttachment و دیگر خانواده این متد با ویژگی Obsolete (منسوخ شده) مزین شده‌اند و توصیه شده‌است از این پس از قابلیت جدیدی به نام RavenFs استفاده شود.
RavenFS یک فایل سیستم مجازی توزیع شده‌است و برای فایل‌های بزرگ چند گیگابایتی به طور بهینه‌ای طراحی گردیده‌است تا کارآیی بانک اطلاعاتی را بالا ببرد و وجود فایل‌های تکراری، از بین برود. این سیستم جدید شامل سیستم پیش فرض ایندکس گذاری میباشد و به شما این اجازه را میدهد تا بر روی متادیتاهای یک فایل از قبیل حجم، تاریخ آخرین نگارش و حتی متادیتاهای اختصاصی که شما در حین ذخیره سازی به آن اضافه می‌کنید، به جست‌وجو بپردازد. این سیستم جدید همچنین این امکان را به شما میدهد تا این اطلاعات را بین Node‌ها، با کمترین میزان انتقالات جابجا کنید و دسترسی سریعتری را بین نودهای مختلف داشته باشید.

برای ذخیره سازی یک فایل ابتدا باید یک FileStore را همانند آنچه که برای DocumentStore داشتید تعریف کنید. Url که شامل همان رشته اتصالی بوده و DefaultFileSystem هم  همانند DefaultDatabase که نام دیتابیس در آن ذکر میشد، در اینجا نام فایل سیستم ذکر می‌گردد:
 var fileStore = new FilesStore()
            {
                Url = "http://localhost:8080",
                DefaultFileSystem = "SampleFs"
            };
            fileStore.Initialize();

بعد از آن باید از طریق Store جدید یک سشن ایجاد شود و فایل مورد نظر را در قالب یک استریم بخوانیم:
 var session = fileStore.OpenAsyncSession();
 var stream = File.OpenRead("D:\\Apocalypse.Now.Redux.1979.BDRip.YIFY.mkv");  

توجه داشته باشید که برای کار با فایل سیستم، همه متدهای session به صورت غیرهمزمان بوده و متد همزمانی وجود ندارد. سپس در مرحله بعد میخواهم متادیتاهای شخصی نیز به آن اضافه کنیم:
  var metadata = new RavenJObject
            {
                {"User", "users/1345"},
              {"Director","Francis Ford Coppola" }, 
                {"Year","1979" }
            };

با استفاده از شیء RavenObject میتوانیم در قالب کلید و مقدار، مقادیر خود را ذخیره کنیم و بعد از آن همه موارد بالا که شامل فایل هدر، استریم و متادیتای اختصاصی است را رجیستر کنیم. اگر هم چندین فایل داریم میتوانید آن‌ها را هم در همینجا رجیستر کنید:
session.RegisterUpload("mkv/sample.mkv", stream, metadata);

در مرحله بعدی تغییرات را تایید و عملیات آپلود آغاز میگردد:
await session.SaveChangesAsync();

همانطور که می‌بینید تمامی متدهای کاربردی این سشن به طور غیرهمزمان طراحی شده‌اند.
کلیه عملیاتی که در بالا انجام شد:
    var fileStore = new FilesStore()
            {
                Url = "http://localhost:8080",
                DefaultFileSystem = "SampleFs"
            };
            fileStore.Initialize();

            var session = fileStore.OpenAsyncSession();
             var stream = File.OpenRead("D:\\Apocalypse.Now.Redux.1979.BDRip.YIFY.mkv"); 
            var metadata = new RavenJObject
            {
                {"User", "users/1345"},
               {"Director","Francis Ford Coppola" }, 
                {"Year","1979" }
            };

            session.RegisterUpload("Mkv/sample.mkv", stream, metadata);
            await session.SaveChangesAsync();

حالا اگر به نسخه سرور ravenDb مراجعه کنید می‌بینید که فایل طبق فایل هدر داده شده قرار گرفته است و اطلاعات مربوط به آن ذخیره شده است:



{
    "User": "users/1345",
    "Country": "Iran",
    "City": "Kashan",
    "Raven-Synchronization-History": [
        {
            "Version": 4,
            "ServerId": "42d0cccb-103d-4bf0-9f3d-6f635b1c8ba4"
        },
        {
            "Version": 5,
            "ServerId": "42d0cccb-103d-4bf0-9f3d-6f635b1c8ba4"
        }
    ],
    "Raven-Synchronization-Version": "6",
    "Raven-Synchronization-Source": "42d0cccb-103d-4bf0-9f3d-6f635b1c8ba4"
}

برای خواندن هم به شیوه زیر عمل میکنیم:
 از طریق Store ایجاد شده، یک سشن جدید را باز می‌کنیم و فایل مورد نظر را از طریق یکی از متادیتاهای تعریف شده بازیابی میکنیم:
var fileStore = new FilesStore()
            {
                Url = "http://localhost:8080",
                DefaultFileSystem = "SampleFs"
            };
            fileStore.Initialize();

            var session = fileStore.OpenAsyncSession();
            var file = await session.Query()
                   .WhereEquals("Year", "1979")
                   .FirstOrDefaultAsync();
سپس با آدرس دهی فایل هدر، فایل باینری را داخل استریم قرار می‌دهیم:
var stream = await session.DownloadAsync("mkv/"+file.Name);

سپس استریم را روی دیسک سخت دخیره یا به هر مکانی که مد نظر است ارسال می‌کنیم:
   var fs = File.Create("D:\\file2.mkv");
            stream.CopyTo(fs);
            fs.Flush();
            fs.Close();
البته از آنجائیکه عملیات بازیابی توسط بانک اطلاعاتی به صورت غیرهمزمان انجام می‌گیرد، بهتر هست که باقی عملیات هم به صورت غیرهمزمان انجام شود:
await stream.CopyToAsync(fs);

سپس کل کد بازیابی را به شکل زیر می‌نویسیم:
            var fileStore = new FilesStore()
            {
                Url = "http://localhost:8080",
                DefaultFileSystem = "SampleFs"
            };
            fileStore.Initialize();

            var session = fileStore.OpenAsyncSession();
            var file = await session.Query()
                   .WhereEquals("Year", "1979")
                   .FirstOrDefaultAsync();

            var stream = await session.DownloadAsync("mkv/"+file.Name);

            var fs = File.Create("D:\\file2.mkv");

            await stream.CopyToAsync(fs);


اشتراک‌ها
مشارکت در تعیین موارد پر اهمیت مسیر راه آتی WPF

We kindly ask you for your valuable input on the priorities for modernizing investments that the WPF team should focus on, as outlined in the roadmap document for 2023.

Please use thumbs up (no other emoji responses will be considered to avoid duplication) on options below which you would like us to prioritize.

مشارکت در تعیین موارد پر اهمیت مسیر راه آتی WPF
اشتراک‌ها
نرم افزاری جهت مدیریت زمان پروژه ها

برخی مواقع مدت زمانی که روی یک پروژه کار کردید را به طور دقیق نمی‌توانید اندازی گیری کنید تا بر اساس آن سود و هزینه‌های خود را محاسبه نمایید این نرم افزار که به صورت رایگان می‌باشد و تمامی این موارد را در اختیار شما قرار می‌دهد.

HourGuard Timesheet Software

Time tracking software to log time on customer projects

HourGuard makes it easy to track work hours with its simple-to-use interface. Click Start when you begin work, and Stop when you finish—the time tracking software does the rest for you, generating time sheets and even creating invoices for you.  

نرم افزاری جهت مدیریت زمان پروژه ها
نظرات مطالب
امکان تعریف ساده‌تر خواص Immutable در C# 9.0 با معرفی ویژگی خواص Init-Only
یک نکته‌ی تکمیلی: امکان استفاده‌ی از records و init-only properties در نگارش‌های پیشین دات نت

فقط اگر از NET 5x. به عنوان Target Framework استفاده کنید، زبان تنظیم شده‌ی پیش‌فرض آن سی‌شارپ 9 است. اما اگر برای مثال بخواهید این زبان را در پروژه‌های مبتنی بر net standard 2.1 که زبان پیش‌فرض آن‌ها ‍C# 8.0 است نیز فعال کنید، اینکار با بازنویسی صریح شماره نگارش زبان آن در فایل csproj ممکن است:
<LangVersion>9.0</LangVersion>
اما پس از آن به یک مشکل برخواهید خورد: برای کار با records و init-only properties، نوع جدید IsExternalInit باید به کامپایلر معرفی شود که این نوع، جزئی از NET 5x SDK. هست. بنابراین برای سایر SDKها، نیاز است قطعه کد زیر را به صورت دستی به پروژه‌ی خود اضافه کنید:
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.ComponentModel;

namespace System.Runtime.CompilerServices
{
   /// <summary>
   /// Reserved to be used by the compiler for tracking metadata.
   /// This class should not be used by developers in source code.
   /// </summary>
   [EditorBrowsable(EditorBrowsableState.Never)]
   internal static class IsExternalInit
   {
   }
}