مطالب
Blazor 5x - قسمت 34 - توزیع برنامه‌های Blazor بر روی IIS
زمانیکه صحبت از توزیع برنامه‌های Blazor بر روی IIS است، عموما تنظیمات مرتبط با برنامه‌های Blazor Server و یا Hosted Blazor Apps که همان ترکیب WASM+Web API هستند، مطرح است؛ در غیراینصورت اگر برنامه‌ای صرفا از فایل‌های Blazor WASM تشکیل شده باشد، توزیع آن حتی بر روی صفحات static مربوط به GitHub هم میسر است و وابستگی خاصی به سروری ندارند. بنابراین در اینجا بیشتر هدف تنظیمات IIS مرتبط با قسمت ASP.NET Core این برنامه‌ها است و این مورد را پیشتر در مطلب «ارتقاء به ASP.NET Core 1.0 - قسمت 22 - توزیع برنامه توسط IIS» بررسی کرده‌ایم و نکته‌ی اضافه‌تری را به همراه ندارد.


مروری بر نحوه‌ی توزیع برنامه‌های Blazor بر روی IIS

1- پیش از هر کاری باید مطابق نگارش ASP.NET Core در حال استفاده (که به عنوان هاست Blazor Server و یا ارائه دهنده‌ی قسمت Web API برنامه‌ی سمت کلاینت WASM مطرح است)، بسته‌ی NET Core hosting bundle. را نصب کرد که عموما تحت عنوان «Hosting Bundle Installer» قابل دریافت است.
نکته‌ی مهم: همانند تمام نگارش‌های دات نت، در اینجا نیز باید Hosting Bundle را پس از نصب IIS، بر روی سیستم نصب کرد. اگر این ترتیب تغییر کند، یکبار دیگر نصاب آن‌را اجرا کرده و گزینه‌ی ترمیم نصب را انتخاب کنید تا یکپارچگی آن با IIS صورت گیرد.

2- نیاز است برنامه‌ی خود را اصطلاحا publish کرد تا به همراه فایل‌های نهایی قابل کپی باشد که در پوشه‌ای توسط IIS هاست خواهند شد. برای اینکار اگر از نگارش کامل ویژوال استودیو استفاده می‌کنید، فقط کافی است بر روی پروژه‌ی مدنظر کلیک راست کرده و از منوی باز شده، گزینه‌ی publish را انتخاب کنید و مراحل آن‌را طی نمائید و یا این مراحل را می‌توان توسط دستور خط فرمان زیر نیز خلاصه کرد که وابستگی خاصی، به IDE ویژه‌ای ندارد و چند سکویی است:
dotnet publish -o "c:\dir1\dir2" -c Release
این دستور، کار Build و توزیع پروژه را در پوشه‌ای که مشخص شده انجام می‌دهد. همچنین برای حالت build، نوع release را انتخاب می‌کند که به همراه بهینه سازی‌های بسیاری به همراه کاهش حجم نهایی فایل‌ها نیز هست.
و یا اگر فقط دستور dotnet publish -c Release را در ریشه‌ی پروژه اجرا کنیم، خروجی نهایی را در پوشه‌ی bin\Release\net5.0\publish می‌توان مشاهده کرد که به همراه یک web.config مخصوص برنامه‌های blazor هم هست و در آن mime typeهای متناظری، به همراه URL rewriting مناسب برنامه‌های تک صفحه‌ای وب از پیش تنظیم شده‌است. بنابراین در اینجا نصب ماژول URL rewrite بر روی IIS نیز الزامی است.

3- در اینجا نیز همانند تنظیمات برنامه‌های ASP.NET Core، باید application pool منتسب به برنامه را ویرایش کرده و NET Clr Version. آن‌را بر روی No Managed Code قرار داد.


روش فعالسازی توزیع مبتنی بر فشرده سازی Brotli در IIS

در حین عملیات publish استاندارد، به صورت پیش‌فرض از تمام فایل‌ها، سه نسخه‌ی اصلی، gz شده (gzip) و یا br شده (فشرده سازی Brotli که فایل‌های کم حجم‌تری را نسبت به gz ارائه می‌دهد) نیز تهیه می‌شوند که بسته به نوع مرورگر و پشتیبانی آن از روش‌های مختلف فشرده سازی، یکی از آن‌ها در اختیار کلاینت قرار خواهد گرفت که به این صورت کاربران، تجربه‌ی دریافت کم حجم‌تر و سریعتری را خواهند داشت.


باید دقت داشت Web.config ای که به همراه دستور dotnet publish ایجاد می‌شود، روش توزیع پیش‌فرض فایل‌های br. تولیدی را ندارد. برای اینکار نیاز است تنظیمات این فایل web.config توصیه شده‌ی توسط تیم Blazor را به web.config خود اضافه کرد تا در نهایت حجم دریافتی از سرور به شدت کاهش یابد.

یک نکته: اگر می‌خواهید فایل web.config سفارشی خودتان را داشته باشید، نمونه‌ای از آن‌را در ریشه‌ی پروژه قرار داده و سپس فایل csproj را به نحو زیر ویرایش کنید تا از آن در حین publish استفاده کند:
<PropertyGroup>
    <PublishIISAssets>true</PublishIISAssets>
</PropertyGroup>


در حین publish برنامه‌های Blazor WASM کار IL trimming نیز انجام می‌شود

برای کاهش حجم نهایی برنامه‌های Blazor WASM، در حین publish در حالت release، کار IL Trimming نیز به صورت خودکار انجام می‌شود تا کدهای IL ای که در برنامه نقش نداشته‌اند و مستقیما در جائی استفاده نشده‌اند، به صورت خودکار حذف شوند و به این ترتیب حجم ارائه‌ی نهایی به شدت کاهش یابد.
فقط باید دقت داشت که در این حالت اگر عملیات پویایی مانند reflection در کدهای شما صورت می‌گیرد، به علت نداشتن ارجاع استاتیکی به منابع مورد استفاده، در زمان اجرا با مشکل مواجه خواهد شد. اگر می‌خواهید اخطارهایی را در این زمینه مشاهده کنید، گزینه‌ی زیر را به فایل csproj اضافه نمائید:
<SuppressTrimAnalysisWarnings>false</SuppressTrimAnalysisWarnings>


حذف مباحث بومی سازی در صورت عدم نیاز


اگر در برنامه‌ی خود از مباحث time-zones استفاده نمی‌کنید، می‌توانید با غیرفعال کردن آن در فایل csproj، حداقل 100 کیلوبایت از حجم برنامه‌ی نهایی را کاهش دهید:
<BlazorEnableTimeZoneSupport>false</BlazorEnableTimeZoneSupport>
و یا اگر نمی‌خواهید خروجی‌های تاریخ و یا مقادیر، بر اساس فرهنگ بومی کاربر تطابق پیدا کنند، می‌توانید invariant culture را فعال کنید:
<InvariantGlobalization>true</InvariantGlobalization>
مطالب
خلاصه اشتراک‌های روز چهار شنبه 1390/06/30

نظرات نظرسنجی‌ها
برای توسعه برنامه‌های مبتنی بر NET Core. از چه محیطی استفاده می‌کنید؟
پروژه ای دارم در نسخه Core 1.1 که  شامل 10 پروژه سایت خروجی و یک پروژه مدیریتی ادمین  

بعد از افزایش ماژول‌های مختلف و  به نسبت حجیم شدن پروژه به طور وحشتناکی کاهش سرعت اجرا بعد از build پروژه رو دارم
پروژه در حالت توسعه هم این مشکل رو به ندرت در محیط Visual Studio داره ولی در VSCode این مشکل فقط پس از اجرای dotnet run با این مشکل روبرو میشم.

به طوری که بعد از اجرا CPU Usage 100% , Disk Usage 100%  تا مدتی استفاده میشه برای مثال سیستمی با این مشخصات :
cpu: i7
Ram : 16 DDR4
HDD: 500 SSD  

 این مشکل در بین تمامی اعضا تیم داریم. آیا محدودیت در حجم پروژه هست؟ آیا با مهاجرت به نسخه 2.1 این مشکل کمتر میشه؟
نظرات مطالب
مدیریت سفارشی سطوح دسترسی کاربران در MVC
- مشکل طولانی بودن حاصل BinaryFormatter serialization برطرف شده (نزدیک به یکسال قبل در January 2013). این مشکل سبب می‌شده تا حاصل RolePrincipal.ToEncryptedTicket بسیار طولانی شده و بیشتر از حد مجاز اندازه قابل ذخیره سازی در یک کوکی شود.
- نسخه‌ی ویندوز 8 و ویندوز سرور 2012 آن از اینجا قابل دریافت است؛ نسخه‌ی ویندوز 7 و ویندوز سرور 2008 از اینجا
+ آپدیت ویندوز را روشن کنید تا آخرین به روز رسانی‌ها و نگارش‌های دات نت نصب شده را به صورت خودکار دریافت کنید.
+ بررسی کنید آیا ویژگی rolemanager cacheRolesInCookie=true در وب کانفیگ تنظیم شده یا خیر. اگر خیر، چیزی کش نخواهد شد.
مطالب
معرفی DNTProfiler
پیشاپیش فرا رسیدن سال نو را به تمام همراهان گرامی سایت net tips. تبریک عرض می‌کنم. به امید سالی پر از سلامتی و رونق، به همراه اشتیاق روز افزون جستجوگری و کشف زوایای پنهان دنیای برنامه نویسی!
هد‌یه‌ی نوروزی سایت net tips. پروژه‌ی پروفایلر سورس بازی است که با EF 6.x و همچنین NHibernate 4.x سازگار است. این پروژه از دو قسمت کلاینت و سرور تشکیل می‌شود.


نصب کلاینت EF برنامه‌ی DNTProfiler

تفاوتی نمی‌کند که برنامه‌ی شما وبی است یا ویندوزی؛ برای هر دو حالت ابتدا دستور ذیل را در کنسول پاورشل نیوگت اجرا کنید:
 PM> Install-Package DNTProfiler.EntityFramework.Core
با اینکار interceptor‌های پروفایلر نصب می‌شوند. برای فعال سازی آن نیاز است چند سطر ذیل را به فایل app.config/web.config پروژه‌ی خود اضافه کنید:
<configuration>
  <entityFramework>
    <interceptors>
      <interceptor type="DNTProfiler.EntityFramework.Core.DatabaseLogger, DNTProfiler.EntityFramework.Core">
        <parameters>
          <parameter value="http://localhost:8080" />
          <parameter value="|DataDirectory|\ErrorsLog.Log" />
        </parameters>
      </interceptor>
    </interceptors>
  </entityFramework>
</configuration>


دریافت و راه اندازی برنامه‌ی DNTProfiler

آخرین نگارش برنامه‌ی DNTProfiler را از برگه‌ی releases مخزن کد آن می‌توانید دریافت کنید:
https://github.com/VahidN/DNTProfiler/releases

این برنامه برای دات نت 4 نوشته شده‌است. بنابراین اگر هنوز از ویندوز XP استفاده می‌کنید، امکان کار کردن با آن‌را خواهید داشت.
البته بسته‌ی نیوگت DNTProfiler.EntityFramework.Core آن برای دات نت 4 و 4.5 تهیه شده‌است و به صورت خودکار بر اساس ساختار پروژه‌ی شما، یکی از آن‌ها نصب خواهد شد.

تا اینجا کار راه اندازی این برنامه به پایان می‌رسد. برای استفاده‌ی از آن باید ابتدا برنامه‌ی DNTProfiler را اجرا کنید. این برنامه به پیام‌های رسیده‌ی از برنامه‌ی اصلی شما (ارسال شده توسط DNTProfiler.EntityFramework.Core) گوش فرا می‌دهد و سپس شروع به آنالیز آن‌ها خواهد کرد. ساختار این تبادل اطلاعات هم بر اساس تهیه‌ی یک ASP.NET Self host Web API است.


این برنامه به صورت ماژولار تهیه شده‌است و تمام آنالیز کننده‌های آن در حقیقت یک پلاگین هستند (در حال حاضر دارای 32 پلاگین است). این پلاگین‌ها در گروه‌های Alerts (برای مثال یافتن جوین‌های تکراری و یک سری موارد بهینه سازی سرعت)، Loggers (طبقه بندی خام اطلاعات رسیده)، Visualizers (نمایش بصری اطلاعات رسیده) قرار می‌گیرند.


نظرات، پیشنهادات و همکاری


لطفا برای طرح سؤالات و ارائه‌ی پیشنهادات خود در زمینه‌ی این پروژه، به قسمت اختصاصی آن در سایت مراجعه نمائید:
https://www.dntips.ir/projects/details/21
مطالب
استفاده از Lambda Expression در پروژه های مبتنی بر WCF
نکته : آشنایی با مفاهیم پایه WCF برای فهم بهتر مفاهیم توصیه می‌شود.

امروزه استفاده از WCF در پروژه‌های SOA بسیار فراگیر شده است. کمتر کسی است که در مورد قدرت تکنولوژی WCF نشنیده باشد یا از این تکنولوژی در پروژه‌های خود استفاده نکرده باشد. WCF مدل برنامه نویسی یکپارچه مایکروسافت برای ساخت نرم‌افزارهای سرویس گرا است و برای توسعه دهندگان امکانی را فراهم می‌کند که راهکارهایی امن، و مبتنی بر تراکنش را تولید نمایند که قابلیت استفاده در بین پلتفرم‌های مختلف را دارند. قبل از WCF توسعه دهندگان پروژه‌های نرم افزاری برای تولید پروژه‌های توزیع شده باید شرایط موجود برای تولید و توسعه را در نظر می‌گرفتند. برای مثال اگر استفاده کننده از سرویس در داخل سازمان و بر پایه دات نت تهیه شده بود از .net remoting استفاده می‌کردند و اگر استفاده کننده سرویس از خارج سازمان یا مثلا بر پایه تکنولوژی J2EE بود از Web Service‌ها استفاده می‌شد. با ظهور WCF این تکنولوژی با هم تجمیع شدند(بهتر بگم تبدیل به یک تکنولوژی واحد شدند) و دیگر خبری از net remoting یا web service‌ها نیست.
  WCF با تمام قدرت و امکاناتی که داراست دارای نقاط ضعفی هم می‌باشد که البته این معایب (یا محدودیت) بیشتر جهت سازگار سازی سرویس‌های نوشته شده با سیستم‌ها و پروتکل‌های مختلف است.
برای انتقال داده‌ها از طریق WCF بین سیستم‌های مختلف باید داده‌های مورد نظر حتما سریالایز شوند که مثال هایی از این دست رو در همین سایت می‌تونید مطالعه کنید:
(^ )  و (^ ) و (^ )

با توجه به این که داده‌ها سریالایز می‌شوند، در نتیجه امکان انقال داده هایی که از نوع object  هستند در WCF وجود ندارد. بلکه نوع داده باید صراحتا ذکر شود و این نوع باید قابیلت سریالایز شدن را دارا باشد.برای مثال شما نمی‌تونید متدی داشته باشید که پارامتر ورودی آن از نوع delegate باشد یا کلاسی باشد که صفت [Serializable] در بالای اون قرار نداشته باشد یا کلاسی باشد که صفت DataContract برای خود کلاس و صفت DataMember برای خاصیت‌های اون تعریف نشده باشد. حالا سوال مهم این است اگر متدی داشته باشیم که پارامتر ورودی آن حتما باید از نوع delegate باشد چه باید کرد؟

برای تشریح بهتر مسئله یک مثال می‌زنم؟

سرویسی داریم برای اطلاعات کتاب ها. قصد داریم متدی بنوسیم که پارامتر ورودی آن از نوع Lambda Expression است تا Query مورد نظر کاربر از سمت کلاینت به سمت سرور دریافت کند و خروجی مورد نظر را با توجه به Query ورودی به کلاینت برگشت دهد.( متدی متداول در اکثر پروژه ها). به صورت زیر عمل می‌کنیم.

*ابتدا یک Blank Solution ایجاد کنید.

*یک ClassLibrary به نام Model ایجاد کنید و کلاسی به نام Book در آن بسازید .(همانطور که میبینید کلاس مورد نظر سریالایز شده است):

   [DataContract]
    public class Book
    {
        [DataMember]
        public int Code { get; set; }

        [DataMember]
        public string Title { get; set; }
    }
* یک WCF Service Application ایجاد کنید
یک Contract برای ارتباط بین سرور و کلاینت می‌سازیم:
using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.ServiceModel;

namespace WcfLambdaExpression
{
    [ServiceContract]
    public interface IBookService
    {
        [OperationContract]
        IEnumerable<Book> GetByExpression( Expression<Func<Book, bool>> expression );
    }
}
متد GetByExpression دارای پارامتر ورودی expression است که نوع آن نیز Lambda Expression  می‌باشد. حال یک سرویس ایجاد می‌کنیم:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;

namespace WcfLambdaExpression
{    
    public class BookService : IBookService
    {        
        public BookService()
        {
            ListOfBook = new List<Book>();
        }

        public List<Book> ListOfBook 
        {
            get;
            private set;
        }

        public IEnumerable<Book> GetByExpression( Expression<Func<Book, bool>> expression )
        {
            ListOfBook.AddRange( new Book[] 
            {
                new Book(){Code = 1 , Title = "Book1"},
                new Book(){Code = 2 , Title = "Book2"},
                new Book(){Code = 3 , Title = "Book3"},
                new Book(){Code = 4 , Title = "Book4"},
                new Book(){Code = 5 , Title = "Book5"},
            } );

            return ListOfBook.AsQueryable().Where( expression );
        }       
    }
}
بعد از Build پروژه همه چیز سمت سرور آماده است. یک پروژه دیگر از نوع Console ایجاد کنید و از روش AddServiceReference سعی کنید که سرویس مورد نظر را به پروژه اضافه کنید. در هنگام Add Service Reference برای اینکه سرویس سمت سرور و کلاینت هر دو با یک مدل کار کنند باید از یک Reference assembly استفاده کنند و کافی است از قسمت Advanced گزینه Reuse types in referenced assemblies را تیک بزنید و assembly‌های مورد نظر را انتخاب کنید.( در این پروژه باید Model و System.Xml.Linq را انتخاب کنید)


به طور حتم با خطا روبرو خواهید شد. دلیل آن هم این است که امکان سریالایز کردن برای پارامتر ورودی expression میسر نیست.
خطای مربوطه به شکل زیر خواهد بود:
Type 'System.Linq.Expressions.Expression`1[System.Func`2[WcfLambdaExpression.Book,System.Boolean]]' cannot be serialized. 
Consider marking it with the DataContractAttribute attribute, and marking all of its members you want serialized with the DataMemberAttribute attribute.  
If the type is a collection, consider marking it with the CollectionDataContractAttribute.  
See the Microsoft .NET Framework documentation for other supported types
حال چه باید کرد؟
روش‌های زیادی برای بر طرف کردن این محدودیت وجود دارد. اما در این پست روشی رو که خودم از اون استفاده می‌کنم رو براتون شرح می‌دهم.
در این روش باید از XElement استفاده شود که در فضای نام System.Linq.Xml قرار دارد. یعنی آرگومان ورودی سمت کلاینت باید به فرمت Xml سریالایز شود و سمت سرور دوباره دی سریالایز شده و تبدیل به یک Lambda Expression شود. اما سریالایز کردن Lambda Expression واقعا کاری سخت و طاقت فرساست . با توجه به این که در اکثر پروژه‌ها این متد‌ها به صورت Generic نوشته می‌شوند. برای حل این مسئله بعد از مدتی جستجو، کلاسی رو پیدا کردم که این کار رو برام انجام می‌داد. بعد از مطالعه دقیق و مشاهده روش کار کلاس، تغییرات مورد نظرم رو اعمال کردم و الان در اکثر پروژه هام دارم از این کلاس استفاده می‌کنم.
یک مثال از روش استفاده :
برای اینکه از این کلاس در هر دو پروژه (سرور و کلاینت) استفاده می‌کنیم باید یک Class Library جدید به نام Common بسازید و یک ارجاع از اون رو به هر دو پروژه سمت سرور و کلاینت بدید.
سرویس و Contract بالا رو به صورت زیر باز نویسی کنید.
[ServiceContract]
    public interface IBookService
    {
        [OperationContract]
        IEnumerable<Book> GetByExpression( XElement expression );
    }
و سرویس :
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Xml.Linq;

namespace WcfLambdaExpression
{
    public class BookService : IBookService
    {
        public BookService()
        {
            ListOfBook = new List<Book>();
        }

        public List<Book> ListOfBook
        {
            get;
            private set;
        }

        public IEnumerable<Book> GetByExpression( XElement expression )
        {
            ListOfBook.AddRange( new Book[] 
            {
                new Book(){Code = 1 , Title = "Book1"},
                new Book(){Code = 2 , Title = "Book2"},
                new Book(){Code = 3 , Title = "Book3"},
                new Book(){Code = 4 , Title = "Book4"},
                new Book(){Code = 5 , Title = "Book5"},
            } );

             Common.ExpressionSerializer serializer = new Common.ExpressionSerializer();

            return ListOfBook.AsQueryable().Where( serializer.Deserialize( expression ) as Expression<Func<Book, bool>> );
        }
    }
بعد از Build پروژه از روش Add Service Reference استفاده کنید و می‌بینید که بدون هیچ گونه مشکلی سرویس مورد نظر به پروژه Console اضافه شد. برای استفاده سمت کلاینت به صورت زیر عمل کنید.

using System;
using System.Linq.Expressions;
using TestExpression.MyBookService;

namespace TestExpression
{
    class Program
    {
        static void Main( string[] args )
        {
            BookServiceClient bookService = new BookServiceClient();

            Expression<Func<Book, bool>> expression = x => x.Code > 2 && x.Code < 5;

            Common.ExpressionSerializer serializer = new Common.ExpressionSerializer();

            bookService.GetByExpression( serializer.Serialize( expression ) );
        }
    }
}
بعد از اجرای پروژه، در سمت سرور خروجی‌های زیر رو مشاهده می‌کنیم.

خروجی هم به صورت زیر خواهد بود:

دریافت سورس کامل Expression-Serialization
مطالب
لینک‌های هفته‌ی آخر دی

وبلاگ‌ها ، سایت‌ها و مقالات ایرانی (داخل و خارج از ایران)


Visual Studio


ASP. Net



طراحی و توسعه وب



PHP


اس‌کیوال سرور


سی شارپ


عمومی دات نت


ویندوز


مسایل اجتماعی و انسانی برنامه نویسی


متفرقه


مطالب
بررسی Source Generators در #C - قسمت دوم - یک مثال
یک مثال: پیاده سازی INotifyPropertyChanged توسط Source Generators

هدف از اینترفیس INotifyPropertyChanged که به همراه یک رخ‌داد است:
public interface INotifyPropertyChanged  
{ 
   event PropertyChangedEventHandler PropertyChanged;  
}
مطلع سازی استفاده کننده‌ی از یک شیء، از تغییرات رخ‌داده‌ی در مقادیر خواص آن است که نمونه‌ی آن، در برنامه‌های WPF، جهت به روز رسانی UI، زیاد مورد استفاده قرار می‌گیرد. البته این رخ‌داد به خودی خود کار خاصی را انجام نمی‌دهد و برای استفاده‌ی از آن، باید مقدار زیادی کد نوشت و این مقدار کد نیز باید به ازای تک تک خواص یک کلاس مدل، تکرار شوند:
  partial class CarModel : INotifyPropertyChanged
  {

    private double _speedKmPerHour;
    
    public double SpeedKmPerHour
    {
      get => _speedKmPerHour;
      set
      {
        _speedKmPerHour = value;
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(SpeedKmPerHour)));
      }
    }

    public event PropertyChangedEventHandler? PropertyChanged;
  }
همچنین باید درنظر داشت که با تغییر نام خاصیتی، میزان قابل ملاحظه‌ای از این کدهای تکراری نیز باید به روز رسانی شوند که این عملیات می‌تواند ایده‌ی خوبی برای استفاده‌ی از Source Generators باشد.
اگر بخواهیم تولید این کدهای تکراری را به Source Generators محول کنیم، می‌توان برای مثال فیلد خصوصی مرتبط را نگه داشت و تولید مابقی کدها را خودکار کرد:
  partial class CarModel : INotifyPropertyChanged
  {
    private double _speedKmPerHour;    
  }
در این حالت کلاس مدل، به صورت partial تعریف می‌شود و فقط فیلد خصوصی، در کدهای ما حضور خواهد داشت. مابقی کدهای این کلاس partial به صورت خودکار توسط یک Source Generator سفارشی تولید خواهد شد. همانطور که ملاحظه می‌کنید، کاهش حجم قابل ملاحظه‌ای حاصل شده و همچنین اگر فیلد خصوصی دیگری نیز در اینجا اضافه شود، واکنش Source Generator ما آنی خواهد بود و بلافاصله کدهای مرتبط را تولید می‌کند و برنامه، بدون مشکلی کامپایل خواهد شد؛ هرچند به ظاهر INotifyPropertyChanged ذکر شده، در این کلاس اصلا پیاده سازی نشده‌است.


ایجاد پروژه‌ی Source Generator

در ابتدا برای ایجاد تولید کننده‌ی خودکار کدهای INotifyPropertyChanged، یک class library را به solution جاری اضافه می‌کنیم. سپس نیاز است ارجاعاتی را به دو بسته‌ی نیوگت زیر نیز افزود:
<Project Sdk="Microsoft.NET.Sdk">

  <ItemGroup>
    <PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.3">
      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
      <PrivateAssets>all</PrivateAssets>
    </PackageReference>
    <PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.2.0" PrivateAssets="all" />
  </ItemGroup>
</Project>
سپس کلاس جدید NotifyPropertyChangedGenerator را به نحو زیر به آن اضافه می‌کنیم:
  [Generator]
  public class NotifyPropertyChangedGenerator : ISourceGenerator
  {
    public void Initialize(GeneratorInitializationContext context)
    {
    }

    public void Execute(GeneratorExecutionContext context)
    {
- این کلاس باید اینترفیس ISourceGenerator را پیاده سازی کرده و همچنین مزین به ویژگی Generator باشد.
- اینترفیس ISourceGenerator به همراه دو متد Initialize و Execute است که در صورت نیاز باید پیاده سازی شوند.

در متد Execute، به خاصیت context.Compilation دسترسی داریم. این خاصیت تمام اطلاعاتی را که کامپایلر از Solution جاری در اختیار دارد، به توسعه دهنده ارائه می‌دهد. برای نمونه پیاده سازی متد Execute تولید کننده‌ی کد مثال جاری، چنین شکلی را دارد:
    public void Execute(GeneratorExecutionContext context)
    {
      // uncomment to debug the actual build of the target project
      // Debugger.Launch();
      var compilation = context.Compilation;
      var notifyInterface = compilation.GetTypeByMetadataName("System.ComponentModel.INotifyPropertyChanged");

      foreach (var syntaxTree in compilation.SyntaxTrees)
      {
        var semanticModel = compilation.GetSemanticModel(syntaxTree);
        var immutableHashSet = syntaxTree.GetRoot()
          .DescendantNodesAndSelf()
          .OfType<ClassDeclarationSyntax>()
          .Select(x => semanticModel.GetDeclaredSymbol(x))
          .OfType<ITypeSymbol>()
          .Where(x => x.Interfaces.Contains(notifyInterface))
          .ToImmutableHashSet();

        foreach (var typeSymbol in immutableHashSet)
        {
          var source = GeneratePropertyChanged(typeSymbol);
          context.AddSource($"{typeSymbol.Name}.Notify.cs", source);
        }
      }
    }
در اینجا با استفاده از context.Compilation به اطلاعات کامپایلر دسترسی پیدا کرده و سپس SyntaxTrees آن‌را یکی یکی، جهت یافتن کلاس‌ها و یا همان ClassDeclarationSyntax ها، پیمایش و بررسی می‌کنیم. سپس از بین این کلاس‌ها، کلاس‌هایی که INotifyPropertyChanged را پیاده سازی کرده باشند، انتخاب می‌کنیم که اطلاعات آن در پایان کار، به متد GeneratePropertyChanged جهت تولید مابقی کدهای partial class ارسال شده و کد تولیدی، به context اضافه می‌شود تا به نحو متداولی همانند سایر کدهای برنامه، به مجموعه کدهای مورد بررسی کامپایلر اضافه شود.

نکته‌ی مهم و جالب در اینجا این است که نیازی نیست تا قطعه کد جدید را به صورت SyntaxTrees در آورد و به کامپایلر اضافه کرد. می‌توان این قطعه کد را به نحو متداولی، به صورت یک قطعه رشته‌ی استاندارد #C، تولید و به کامپایلر با متد context.AddSource ارائه کرد که نمونه‌ای از آن‌را در ذیل مشاهده می‌کنید:
    private string GeneratePropertyChanged(ITypeSymbol typeSymbol)
    {
      return $@"
using System.ComponentModel;

namespace {typeSymbol.ContainingNamespace}
{{
  partial class {typeSymbol.Name}
  {{
    {GenerateProperties(typeSymbol)}
    public event PropertyChangedEventHandler? PropertyChanged;
  }}
}}";
    }

    private static string GenerateProperties(ITypeSymbol typeSymbol)
    {
      var sb = new StringBuilder();
      var suffix = "BackingField";

      foreach (var fieldSymbol in typeSymbol.GetMembers().OfType<IFieldSymbol>()
        .Where(x=>x.Name.EndsWith(suffix)))
      {
        var propertyName = fieldSymbol.Name[..^suffix.Length];
        sb.AppendLine($@"
    public {fieldSymbol.Type} {propertyName}
    {{
      get => {fieldSymbol.Name};
      set
      {{
        {fieldSymbol.Name} = value;
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof({propertyName})));
      }}
    }}");
      }

      return sb.ToString();
    }
در اینجا در ابتدا بدنه‌ی کلاس partial تکمیل می‌شود. سپس خواص عمومی آن بر اساس فیلدهای خصوصی تعریف شده، تکمیل می‌شوند. در این مثال اگر یک فیلد خصوصی به عبارت BackingField ختم شود، به عنوان فیلدی که قرار است معادل کدهای INotifyPropertyChanged را داشته باشد، شناسایی می‌شود و به همراه کدهای تولید شده‌ی خودکار خواهد بود.

کدهای source generator ما همین مقدار بیش‌تر نیست. اکنون می‌خواهیم از آن در یک برنامه‌ی کنسول جدید (برای مثال به نام NotifyPropertyChangedGenerator.Demo) استفاده کنیم. برای اینکار نیاز است ارجاعی را به آن اضافه کنیم؛ اما این ارجاع، یک ارجاع متداول نیست و نیاز به ذکر چنین ویژگی خاصی وجود دارد:
<Project Sdk="Microsoft.NET.Sdk">

  <ItemGroup>
    <ProjectReference Include="..\NotifyPropertyChangedGenerator\NotifyPropertyChangedGenerator.csproj"
                      OutputItemType="Analyzer" ReferenceOutputAssembly="false"/>
  </ItemGroup>
</Project>
در اینجا میسر دهی پروژه‌ی تولید کننده‌ی کد، همانند سایر پروژه‌ها است؛ اما نوع آن باید آنالایزر معرفی شود. به همین جهت از خاصیت OutputItemType با مقدار Analyzer استفاده شده‌است. همچنین تنظیم ReferenceOutputAssembly به false به این معنا است که این اسمبلی ویژه، یک وابستگی و dependency واقعی پروژه‌ی جاری نیست و ما قرار نیست به صورت مستقیمی از کدهای آن استفاده کنیم.

برای آزمایش این تولید کننده‌ی کد، کلاس CarModel را به صورت زیر به پروژه‌ی کنسول آزمایشی اضافه می‌کنیم:
using System.ComponentModel;

namespace NotifyPropertyChangedGenerator.Demo
{
  public partial class CarModel : INotifyPropertyChanged
  {
    private double SpeedKmPerHourBackingField;
    private int NumberOfDoorsBackingField;
    private string ModelBackingField = "";

    public void SpeedUp() => SpeedKmPerHour *= 1.1;
  }
}
این کلاس پیاده سازی کننده‌ی INotifyPropertyChanged است؛ اما به همراه هیچ خاصیت عمومی نیست. فقط به همراه یکسری فیلد خصوصی ختم شده‌ی به «BackingField» است که توسط تولید کننده‌ی کد شناسایی شده و اطلاعات آن‌ها تکمیل می‌شود. فقط باید دقت داشت که این کلاس حتما باید به صورت partial تعریف شود تا امکان تکمیل خودکار کدهای آن وجود داشته باشد.

یک نکته:   در این حالت هرچند برنامه بدون مشکل کامپایل و اجرا می‌شود، ممکن است خطوط قرمزی را در IDE خود مشاهده کنید که عنوان می‌کند این قطعه از کد قابل کامپایل نیست. اگر با چنین صحنه‌ای مواجه شدید، یکبار solution را بسته و مجددا باز کنید تا تولید کننده‌ی کد، به خوبی شناسایی شود. البته نگارش‌های جدیدتر Visual Studio و Rider به همراه قابلیت auto reload پروژه برای کار با تولید کننده‌‌های کد هستند و دیگر شاهد چنین صحنه‌هایی نیستیم و حتی اگر برای مثال فیلد جدیدی را به CarModel اضافه کنیم، نه فقط بلافاصله کدهای متناظر آن تولید می‌شوند، بلکه خواص عمومی تولید شده در Intellisense نیز قابل دسترسی هستند.


نحوه‌ی مشاهده‌ی کدهای خودکار تولید شده

اگر علاقمند باشید تا کدهای خودکار تولید شده را مشاهده کنید، در Visual Studio، در قسمت و درخت نمایشی dependencies پروژه، گره‌ای به نام Analyzers وجود دارد که در آن برای مثال نام NotifyPropertyChangedGenerator و ذیل آن، کلاس‌های تولید شده‌ی توسط آن، قابل مشاهده و دسترسی هستند و حتی قابل دیباگ نیز می‌باشند؛ یعنی می‌توان بر روی سطور مختلف آن، break-point قرار داد.


کدهای کامل این مطلب را از اینجا می‌توانید دریافت کنید: SourceGeneratorTests.zip

معرفی تعدادی منبع تکمیلی
- برنامه Source generator playground
در اینجا تعدادی مثال را که توسط مایکروسافت توسعه یافته‌است، مشاهده می‌کنید که اتفاقا یکی از آن‌ها پیاده سازی تولید کننده‌ی کد اینترفیس INotifyPropertyChanged است. در این برنامه، خروجی کدهای تولیدی نیز به سادگی قابل مشاهده‌است.

- برنامه SharpLab
برای توسعه‌ی تولید کننده‌های کد، عموما نیاز است تا با Roslyn API آشنا بود. در این برنامه اگر از منوی بالای صفحه قسمت results، گزینه‌ی «syntax tree» را انتخاب کنید و سپس قسمتی از کد خود را انتخاب کنید، بلافاصله معادل Roslyn API آن، در سمت راست صفحه نمایش داده می‌شود.

- معرفی مجموعه‌ای از Source Generators
در اینجا می‌توان مجموعه‌ای از پروژه‌های سورس باز Source Generators را مشاهده و کدهای آن‌ها را مطالعه کنید و یا از آن‌ها در پروژه‌های خود استفاده نمائید.

- معرفی یک cookbook در مورد Source Generators
این cookbook توسط خود مایکروسافت تهیه شده‌است و جهت شروع به کار با این فناوری، بسیار مفید است.

- مجموعه مثال‌های Source generators از مایکروسافت
در اینجا می‌توانید مجموعه مثال‌هایی از Source generators را که توسط مایکروسافت تهیه شده‌است، مشاهده کنید. شرح و توضیحات تعدادی از آن‌ها را هم در اینجا مطالعه کنید.
مطالب
مقایسه پروژه های Web Site و Web Application در Visual Studio
 در ویژوال استودیو می‌توانید پروژه‌ی Web Application و یا Web Site ایجاد نمایید. شما Web Application را با گزینه‌ی New Project ایجاد و با Open Project باز می‌کنید؛ ولی Web Site را با گزینه‌ی New Web Site ایجاد و با Open Web Site باز می‌نمایید. قبل از ساخت یک پروژه‌ی جدید وب لازم است از تفاوت‌های این دو نوع، آگاهی کسب کرده و در انتخاب نوع پروژه دقت نمایید؛ چرا که تغییر و تبدیل یک نوع به نوع دیگر، علاوه بر سختی، موجب اتلاف زمان شده و پروژه را مستعد خطا خواهد کرد.

نکته:
برای ایجاد یک پروژه‌ی جدید مایکروسافت Web Application را به شما پیشنهاد می‌دهد. هرچند در این مبحث، مطالبی را مبنی بر فواید Web Site معرفی خواهد کرد، ولی اکثر توسعه دهندگان وب که Web Site را برگزیده اند سرانجام مضراتی از آن را می‌یابند که سنگینی آن بیشتر از فوایدش است. برای مثال تمامی خصیصه‌های (feature فیچر) ASP.net لزومآ برای وب سایت در دسترس نخواهند بود؛ مثلآ از ویژوال استودیوی 2012 به بعد، ابزاری برای تولید پروژه‌های وب وجود دارد که فقط برای Web Application در اختیار خواهد بود (برای کسب اطلاعات بیشتر می‌توانید مطلب Creating an ASP.net Web Project in Visual Studio  را مطالعه نمایید). 

سناریو :

سناریویی که مبنی برا انتخاب Web Application می‌باشد به شرح زیر است:
· شما نیاز به استفاده از Edit And Continue در دیباگر ویژوال استودیو دارید.
· تمامی کدها، فایل‌ها و کلاس‌هایی که با صفحات ASP.net مرتبط هستند، برای تست بصورت واحد و یکپارچه در نظر گرفته می‌شوند.
· شما برای کلاس‌هایی که وابسته به صفحات هستند و همچنین برای کنترل‌ها و کلاس‌های منحصر آن باید ارجاع داشته باشید.
· وابستگی در حالتی که چندین پروژه‌ی مرتبط به هم را دارید توسط شما مشخص می‌شود.
· برای کل سایت در هنگام کامپایل فقط یک اسمبلی ساخته می‌شود.
· کنترل نام اسمبلی‌ها و همچنین شمارهی ورژن ایجاد شده‌ی برای پروژه در دست شماست.
· برای کامپایل پروژه می‌توانید MSBuild و یا Team Build را انتخاب کنید؛ برای مثال می‌توانید مراحل Prebuild یا Postbuild را مشخص کنید.
· نیازی به قرار دادن سورس برنامه روی سرور نیست.

سناریویی که مبنی برا انتخاب Web Site می‌باشد به شرح زیر است:
· یک پروژه در بر دارنده‌ی  کدهای #C و هم کدهای Visual Basic می‌باشد ( درحالیکه بصورت پیشفرض در Web Application فایل پروژه بر مبنای زبان برنامه‌ی شما کامپایل می‌شود، هرچند می‌توان این حالت پیشفرض را تغییر داد؛ ولی این امر می‌تواند اندکی مشکل باشد).
· شما می‌توانید سایت ایجاد شده را بصورت Real Time توسط FTP  باز نموده و آپدیت نمایید.
· برای توزیع (deploy) پروژه مجبور به کامپایل صریح آن نیستید.
· اگر پروژه را کامپایل نمایید کامپایلر به ازای هر صفحه و یا هر پوشه، یک فایل اسمبلی جداگانه خواهد ساخت.
· برای تغییر یک فایل به تنهایی می‌توانید فقط آنرا تغییر داده و بر روی سرور قرار دهید.
· حتی بعد از کامپایل هم می‌توانید صفحات ASP.net  را بدون نیاز به کامپایل دوباره‌ی کل سایت تغییر داده و جایگزین نمایید.
· سورس کامل پروژه برای اجرا باید روی سرور قرار گیرد.

تفاوت‌ها در یک نگاه:

زمینه 
  پروژه‌های Web Application     پروژه‌های Web Site 
  ساختار فایل پروژه    فایل برنامه (.csproj / vbproj) دربردارنده اطلاعاتی از جمله لیست فایل‌ها و رفرنس‌ها پروژه به پروژه دیگر خواهد بود.    هیچ فایل برنامه ای وجود ندارد و تمامی فایل هایی که داخل پوشه می‌باشند جزو فایل‌های سایت شناخته می‌شوند. 
  کامپایل  · شما پروژه را در سیستم خود کامپایل می‌کنید.
· بصورت پیشفرض کامپایل کد‌ها در یک اسمبلی قرار می‌گیرد. 
  · سورس کدها بصورت اتوماتیک در سرور توسط Asp.net  با اولین درخواست کامپایل میشوند.
(البته شما می‌توانید کامپایل را در سیستم خود نیز انجام دهید)
· بصورت پیشفرض کامپایل برای هر کلاس یک اسمبلی جدا می‌سازد. 
فضا‌های نام  Namespace‌ها بصورت صریح در صفحات و کلاس‌ها و کنترل‌ها افزوده می‌شود.  هیچ namespace ای بصورت پیشفرض اضافه نمی‌شود (شما می‌توانید بصورت دستی آنها را اضافه کنید) 
توزیع
اسمبلی تولید شده در مرحله کامپایل را روی سرور قرار می‌دهید
اکثر مراحل کامپایل توسط ابزارهای ارائه شده ویژوال استودیو انجام می‌شود. 
کل سورس پروژه روی سرور قرار میگیرد.
اکثر مراحل کامپایل توسط ابزارهای ارائه شده ویژوال استودیو انجام می‌شود. 

ساختار فایل پروژه:
پروژه‌های Web Application از فایل پروژه ویژوال استودیو ( .csproj / .vbproj ) برای نگهداری اطلاعات پروژه استفاده می‌کنند. با این امکان می‌توان فایل‌هایی را که در پروژه دخیل هستند و یا باید کامپایل شوند، به تفکیک مشخص کرد.
در مورد Web Site تمامی فایل‌هایی که در داخل پوشه‌ی برنامه قرار دارند، به صورت پیش فرض جزیی از برنامه تلقی شده و کامپایل خواهند شد و برای اینکه فایلی را بخواهید مستثنا کنید یا باید آنرا حذف کنید و یا پسوند آنرا به نامی که توسط سرور IIS قابل شناسایی نیست تغییر دهید.

فایده‌ی فایل پروژه یعنی همان ( .csproj / .vbproj ) در Web Application :
می‌توان فایلی را به طور موقت از برنامه حذف کرد، بدون نگرانی از آنکه فایل بصورت کلی حذف شود. چرا که فایل در ساختار برنامه باقیست. برای مثال اگر صفحه‌ای برای توزیع آماده نیست، می‌توانید به راحتی آن‌را از برنامه خارج کنید ( Exclude ) و برنامه را کامپایل نمایید و بعد از اینکه این صفحه هم آماده شد، دوباره آن را وارد پروژه نمایید ( include ) که اهمیت این امر در مواردی که از برنامه‌های کنترل سورس استفاده می‌کنید، دوچندان می‌شود.

فایده‌ی عدم استفاده از فایل پروژه‌ی برنامه در Web Site :
شما مجبور به کنترل و شخصی سازی ساختار فایل برنامه در ویژوال استودیو نیستید و به راحتی هر فایل یا صفحه‌ای را که می‌خواهید، با کپی کردن به پوشه و یا حذف کردن از آن توسط فایل اکسپلورر انجام می‌دهید.

کامپایل:
برای برنامه‌های Web Application شما بصورت معمول پروژه را Build می‌نمایید و تمامی کد‌های صفحات و همچنین کلاس‌ها به صورت یک فایل اسمبلی در پوشه‌ی bin ذخیره می‌گردد.
برای Web Site شما مجبور به کامپایل دستی پروژه نیستید و می‌توانید از Batch-Compile استفاده کنید و همچنین به ازای هر صفحه و کلاسریال شما یک فایل اسمبلی خواهید داشت.

مزایای کامپایل در Web Application :
· می‌توانید از MSBuild استفاده کنید.
· می‌توانید خصیصه‌های اسمبلی، از جمله نام و ورژن را به راحتی مدیریت نمایید.
· کامپایل قبل از توزیع برنامه این مزیت را دارد که کاربران مجبور نیستند منتظر کامپایل برنامه در سرور باشند.
· مدیریت دقیقی بر روی فایل‌ها و ساختار برنامه و همچنین کلاس‌ها و ارجاعات خواهید داشت.

مزایای کامپایل در Web Site  :
· می‌توانید هر صفحه‌ای را که نیاز دارید بدون در نظر گرفتن آماده شدن دیگر صفحات تست و اجرا نمایید.
· آپدیت و جایگزینی فایل‌ها به راحتی صورت می‌گیرد؛ چرا که اسمبلی تمام فایل‌ها بصورت منحصر همان صفحه ایجاد خواهد شد.
· ایجاد شدن چند اسمبلی می‌تواند در برخی پروژه‌ها به نفع برنامه بوده و performance  را بالا ببرد. برای مثال در حالتیکه یک سایت با صفحات زیاد دارید و برخی صفحات به نسبت دیگر صفحات خیلی کمتر درخواست می‌شوند.

نکته:
هیچ فرقی بین Web Application ,  و web Site از نظر performance  وجود ندارد مگر درحالت ذکر شده در بالا و در سایت‌های خیلی بزرگ.

توزیع : ( Deployment )
در web Site کل فایل‌های پروژه را بر روی سرور قرار می‌دهید؛ درحالی که در Web Application  فایل‌های برنامه بصورت اسمبلی‌ها ( .dll ) روی سرور قرار می‌گیرند. همین امر می‌تواند در برخی حالت‌ها مثلآ زمانی که از هاست share شده استفاده می‌کنید، خیال شما را از بابت سورس برنامه مطمئن سازد.
برای Web Site نیز این مزیت وجود دارد که برای انجام تغییرات کوچک مجبور به کامپایل و آپلود دوباره‌ی کل پروژه نیستید.


ماخذ

 
مطالب
تبدیل فایل‌های pfx به snk
مرسوم است و توصیه شده است که جهت ارائه کتابخانه‌های دات نتی خود از امضای دیجیتال استفاده کنید. VS.NET برای این منظور در برگه signing خواص یک پروژه، چنین امکانی را به صورت توکار ارائه می‌دهد.
حال اگر بخواهیم همین پروژه را به صورت سورس باز ارائه دهیم، استفاده کنندگان نهایی به مشکل برخواهند خورد؛ زیرا فایل pfx حاصل، توسط کلمه عبور محافظت می‌شود و در سایر سیستم‌ها بدون درنظر گرفتن این ملاحظات قابل استفاده نخواهد بود.
معادل فایل‌های pfx، فایلهایی هستند با پسوند snk که تنها تفاوت مهم آن‌ها با فایل‌های pfx، عدم محافظت توسط کلمه عبور است و ... برای کارهای خصوصا سورس باز انتخاب مناسبی به شمار می‌روند. اگر دقت کنید، اکثر پروژه‌های سورس باز دات نتی موجود در وب (مانند NHibernate، لوسین، iTextSharp و غیره) از فایل‌های snk برای اضافه کردن امضای دیجیتال به کتابخانه نهایی تولیدی استفاده می‌کنند و نه فایل‌های pfx محافظت شده.
در اینجا اگر فایل pfx ایی دارید و می‌خواهید معادل snk آن‌را تولید کنید، قطعه کد زیر چنین امکانی را مهیا می‌سازد:
using System.IO;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;

namespace PfxToSnk
{
    class Program
    {
        /// <summary>
        /// Converts .pfx file to .snk file.
        /// </summary>
        /// <param name="pfxData">.pfx file data.</param>
        /// <param name="pfxPassword">.pfx file password.</param>
        /// <returns>.snk file data.</returns>
        public static byte[] Pfx2Snk(byte[] pfxData, string pfxPassword)
        {
            var cert = new X509Certificate2(pfxData, pfxPassword, X509KeyStorageFlags.Exportable);
            var privateKey = (RSACryptoServiceProvider)cert.PrivateKey;
            return privateKey.ExportCspBlob(true);
        }

        static void Main(string[] args)
        {
            var pfxFileData = File.ReadAllBytes(@"D:\Key.pfx");
            var snkFileData = Pfx2Snk(pfxFileData, "my-pass");
            File.WriteAllBytes(@"D:\Key.snk", snkFileData);
        }
    }
}