نظرات مطالب
تنظیمات و نکات کاربردی کتابخانه‌ی JSON.NET
گرفتن خروجی مرتب شده بر اساس نام خواص (جهت مقاصد نمایشی):
تعریف DefaultContractResolver :
    public class OrderedContractResolver : DefaultContractResolver
    {
        protected override IList<JsonProperty> CreateProperties(
            System.Type type, MemberSerialization memberSerialization)
        {
            return base.CreateProperties(type, memberSerialization).OrderBy(p => p.PropertyName).ToList();
        }
    }
و بعد معرفی آن به نحو ذیل:
            return JsonConvert.SerializeObject(data, new JsonSerializerSettings
            {
                ContractResolver = new OrderedContractResolver()
            });
نظرات مطالب
امن سازی برنامه‌های ASP.NET Core توسط IdentityServer 4x - قسمت ششم - کار با User Claims

تنظیمات کانفیگ :

public static class ConfigTest
{

    public static IEnumerable<IdentityResource> Resources => new List<IdentityResource>
    {
        new IdentityResources.OpenId(),
        new IdentityResources.Profile(),
        new IdentityResources.Address(),
        new IdentityResource
        {
            Name = "role",
            UserClaims = new List<string> {"role"}
        }
    };


    public static IEnumerable<ApiResource> ApiResources => new[]
        {
            new ApiResource
            {
                Name = "orderingapi",
                DisplayName = "Ordering Api",
                Description = "Allow the application to access Ordering Api on your behalf",
                Scopes = new List<string> { "api.ordering.read", "api.ordering.manager"},
                ApiSecrets = new List<Secret> {new Secret("OrderingScopeSecret".Sha256())}, // change me!
                UserClaims = new List<string> {"role"}
            }
        };


    public static IEnumerable<ApiScope> ApiScopes => new[]
        {
            new ApiScope("api.ordering.read", "Read Access to Ordering Api"),
            new ApiScope("api.ordering.manager",displayName:"manage Crud operation access to ordering api")
        };



    public static IEnumerable<Client> Clients => new List<Client>
    {
        new Client
        {
            ClientId = "oidcClient",
            ClientName = "Example Client Application",
            ClientSecrets = new List<Secret> {new Secret("VQGBtSDEK7tzIzSJyfCYqdHDTQHt7kD2VQ1hHWnY7Dw=".Sha256())}, // change me!
    
            AllowedGrantTypes = GrantTypes.Hybrid,
            RedirectUris = new List<string> {"https://localhost:6001/signin-oidc"},
            PostLogoutRedirectUris = new List<string>
            {
                "https://localhost:6001/signout-callback-oidc"
            },
            AllowedScopes = new List<string>
            {
                IdentityServerConstants.StandardScopes.OpenId,
                IdentityServerConstants.StandardScopes.Profile,
                IdentityServerConstants.StandardScopes.Address,
                "role",
                "api.ordering.manager"
            },

            RequirePkce=false,
            AllowOfflineAccess = true
        }
    };


    public static List<TestUser> Users => new List<TestUser>
    {
        new TestUser
        {
            SubjectId = "1",
            Username = "meysam",
            Password = "123",
            Claims = new List<Claim>
            {
                new Claim(JwtClaimTypes.Name,"meysanm"),
                new Claim(JwtClaimTypes.GivenName,"soleymani"),
                new Claim(JwtClaimTypes.Email, "meysam@test.com"),
                new Claim(JwtClaimTypes.Address,"amol city"),
                new Claim(JwtClaimTypes.Role, "admin")
            }
        },
        new TestUser
        {
            SubjectId = "2",
            Username = "ali",
            Password = "123",
            Claims = new List<Claim>
            {
                new Claim(JwtClaimTypes.Name,"ali"),
                new Claim(JwtClaimTypes.GivenName,"abbasi"),
                new Claim(JwtClaimTypes.Address,"tehran city"),
                new Claim(JwtClaimTypes.Role,"public")
            }
        }
    };
}


مطالب
آشنایی با M.A.F - قسمت اول

در طی چند مقاله قصد بررسی نحوه‌ی تولید برنامه‌های توسعه پذیر (extensible) را با استفاده از plug-ins و یا add-ins داریم.

افزونه‌ها عموما در سه گروه قرار می‌گیرند:
الف) افزونه، سرویسی را به هاست ارائه می‌دهد. برای مثال یک میل سرور نیاز به افزونه‌هایی برای ویروس یابی یا فیلتر کردن هرزنامه‌ها دارد؛ یا یک برنامه پردازش متنی نیاز به افزونه‌ای جهت بررسی غلط‌های املایی می‌تواند داشته باشد و یا یک مرورگر وب می‌تواند با کمک افزونه‌ها قابلیت‌های پیش فرض خود را به شدت توسعه و افزایش دهد (نمونه‌ی بارز آن فایرکس است که عمده‌ترین دلیل اقبال عمومی به آن سهولت توسعه پذیری آن می‌باشد).
ب) در گروه دوم، هاست، رفتار مشخصی را ارائه داده و سپس افزونه بر اساس آن، نحوه‌ی عملکرد هاست را مشخص می‌کند. در این حالت هاست است که سرویسی را به افزونه ارائه می‌دهد. نمونه‌ی بازر آن افزونه‌های آفیس هستند که امکان اتوماسیون فرآیندهای مختلف آن‌را میسر می‌سازند. به این صورت امکان توسعه‌ی یک برنامه به شکلی که در طراحی اولیه آن اصلا انتظار آن نمی‌رفته وجود خواهد داشت. همچنین در اینجا نیازی به داشتن سورس کد برنامه‌ی اصلی نیز نمی‌باشد.
ج) گروه سوم افزونه‌ها تنها از هاست جهت نمایش خود استفاده کرده و عملا استفاده‌ی خاصی از هاست ندارد. برای مثال نوار ابزاری که خود را به windows explorer متصل می‌کند و تنها از آن جهت نمایش خود بهره می‌جوید.


در حال حاضر حداقل دو فریم ورک عمده جهت انجام این‌کار و تولید افزونه‌ها برای دات نت فریم ورک مهیا است:
الف) managed addin framework یا MAF
ب) managed extensibility framework یا MEF

فضای نام جدیدی به دات نت فریم ورک سه و نیم به نام System.AddIn اضافه شده است که به آن Managed AddIn Framework یا MAF نیز اطلاق می‌شود. از این فریم ورک در VSTO (تولید افزونه برای مجموعه‌ی آفیس) توسط خود مایکروسافت استفاده شده است.

فریم ورک توسعه‌ی افزونه‌های مدیریت شده در دات نت فریم ورک سه و نیم، مزایای زیر را در اختیار ما خواهد گذاشت:
- امکانات load و unload افزونه‌های تولید شده
- امکان تغییر افزونه‌ها در زمان اجرای برنامه اصلی بدون نیاز به بستن آن
- ارائه‌ی محیطی ایزوله با ترسیم مرزی بین افزونه و برنامه اصلی
- مدیریت طول عمر افزونه
- مدیریت سازگاری با نگارش‌های قبلی و یا بعدی یک افزونه
- امکانات به اشتراک گذاری افزونه‌ها با برنامه‌های دیگر
- تنظیمات امنیتی و مشخص سازی سطح دسترسی افزونه‌ها
و ...

یک راه حل مبتنی بر MAF می‌تواند شامل 7 پروژه باشد (که به روابط تعریف شده در آن pipeline هم گفته می‌شود):

Host : همان برنامه‌ی اصلی است که توسط یک سری افزونه، توسعه یافته است.
Host View : بیانگر انتظارات هاست از افزونه‌ها است. به عبارت دیگر افزونه‌ها باید موارد لیست شده در این پروژه را پیاده سازی کنند.
Host Side Adapter : پل ارتباطی Host View و پروژه‌ی Contract است.
Contract: اینترفیسی است که کار برقراری ارتباط بین Host و افزونه‌ها را برعهده دارد.
Add-In Side Adapter : پل ارتباطی بین Add-In View و Contract است.
Add-In View :‌ حاوی متدها و اشیایی است که جهت برقراری ارتباط با هاست از آن‌ها استفاده می‌شود.
Add-In : اسمبلی است که توسط هاست جهت توسعه‌ی قابلیت‌های خود بارگذاری می‌شود (به آن Add-On ، Extension ، Plug-In و Snap-In هم گفته می‌شود).

هدف از این جدا سازی‌ها ارائه‌ی راه حل loosely-coupledایی است که امکان ایزوله سازی، اعمال شرایط امنیتی ویژه و همچنین کنترل نگارش‌های مختلف را تسهیل می‌بخشد و این امر با استفاده از interface های معرفی شده میسر گردیده است. این pipeline از قسمت‌های ذیل تشکیل می‌شود:



قرار داد یا Contract
برای تولید یک افزونه نیاز است تا بین هاست و افزونه قراردادی بسته شود. با توجه به استفاده از MAF ، روش تعریف این قرار داد برای مثال در یک افزونه‌ی مترجم به صورت زیر باید باشد:

[AddInContract]
public interface ITranslator : IContract
{
string Translate(string input);
}

استفاده از ویژگی AddInContract و پیاده سازی اینترفیس IContract جزو مراحل کاری استفاده از MAF است. MAF هنگام تولید پویای pipeline ذکر شده به دنبال ویژگی AddInContract می‌گردد. این موارد در فضای نام System.AddIn.Pipeline تعریف شده‌اند.

دیدگاه‌ها یا Views
دیدگاه‌ها کدهایی هستند که کار تعامل مستقیم بین افزونه و هاست را بر عهده دارند. هاست یا افزونه هر کدام می‌توانند دیدگاه خود را نسبت به قرار داد بسته شده داشته باشند. این موارد نیز همانند قرار داد در اسمبلی‌های مجزایی نگهداری می‌شوند.

دیدگاه هاست نسبت به قرار داد:
public abstract class TranslatorHostView
{
public abstract string Translate(string input);
}
دیدگاه افزونه نسبت به قرار داد:
[AddInBase]
public abstract class TranslatorHostView
{
public abstract string Translate(string input);
}

هر دو کلاس فوق بر اساس قرار موجود بنا می‌شوند اما وابسته به آن نیستند. به همین جهت به صورت کلاس‌هایی abstract تعریف شده‌اند. در سمت افزونه، کلاس تعریف شده دیدگاه آن با کلاس دیدگاه سمت هاست تقریبا یکسان می‌باشد؛ اما با ویژگی AddInBase تعریف شده در فضای نام System.AddIn.Pipeline مزین گردیده است.


وفق دهنده‌ها یا Adapters
آخرین قسمت pipeline ، وفق دهنده‌ها هستند که کار آن‌ها اتصال قرار داد به دیدگاه‌ها است و توسط آن مدیریت طول عمر افزونه و همچنین تبدیل اطلاعات بین قسمت‌های مختلف انجام می‌شود. شاید در نگاه اول وجود آن‌ها زائد به نظر برسد اما این جدا سازی کدها سبب تولید افزونه‌هایی خواهد شد که به نگارش هاست و برنامه اصلی وابسته نبوده و بر عکس (version tolerance). به دو کلاس زیر دقت نمائید:

کلاس زیر با ویژگی [HostAdapter] تعریف شده در فضای نام System.AddIn.Pipeline، مزین شده است و کار آن اتصال HostView به Contract می‌باشد. برای این منظور TranslatorHostView ایی را که پیشتر معرفی کردیم باید پیاده سازی نماید. علاوه بر این با ایجاد وهله‌ای از کلاس ContractHandle ، کار مدیریت طول عمر افزونه را نیز می‌توان انجام داد.

[HostAdapter]
public class TranslatorHostViewToContract : TranslatorHostView
{
ITranslator _contract;
ContractHandle _lifetime;

public TranslatorHostViewToContract(ITranslator contract)
{
_contract = contract;
_lifetime = new ContractHandle(contract);
}

public override string Translate (string inp)
{
return _contract.Translate(inp);
}
}
کلاس سمت افزونه نیز بسیار شبیه قسمت قبل است و کار آن اتصال AddInView به Contract می‌باشد که با پیاده سازی ContractBase و Itranslator صورت خواهد گرفت. همچنین این کلاس به ویژگی AddInAdapter مزین گردیده است.

[AddInAdapter]
public class TranslatorAddInViewToContract : ContractBase, ITranslator
{
TranslatorAddInView _view;

public TranslatorAddInViewToContract(TranslatorView view)
{
_view = view;
}

public string Translate(string inp)
{
return _view.Translate(inp);
}
}

قسمت عمده‌ای از این کدها تکراری است. جهت سهولت تولید این کلاس‌ها و پروژه‌های مرتبط، تیم مربوطه برنامه‌ای را به نام pipeline builder ارائه داده است که از آدرس زیر قابل دریافت است:


این برنامه با دریافت اسمبلی مربوط بهcontract ، کار ساخت خودکار کلاس‌های adapters و views را انجام خواهد داد.

ایجاد افزونه
پس از ساخت قسمت‌های مختلف pipeline ، اکنون می‌توان افزونه را ایجاد نمود. هر افزونه باید add-in view را پیاده سازی کرده و با ویژگی AddIn مزین شود. برای مثال:

[AddIn("GoogleTranslator", Description="Universal translator",
Version="1.0.0.0", Publisher="YourName")]
public class GoogleAddIn : TranslatorAddInView
{
public string Translate(string input)
{
...
}
}

ادامه دارد ....

نظرات مطالب
Blazor 5x - قسمت 14 - کار با فرم‌ها - بخش 2 - تعریف فرم‌ها و اعتبارسنجی آن‌ها
یک نکته‌ی تکمیلی: روش سازگار کردن اعتبارسنجی فرم‌های استاندارد Blazor با کلاس‌های CSS بوت استرپ 4 و 5
زمانیکه از EditForm و کامپوننت‌های توکار Blazor استفاده می‌کنیم، اگر کامپوننتی در وضعیت اعتبارسنجی شده قرار داشته باشد، با کلاس valid:
class="modified valid form-control"
و اگر در وضعیت شکست اعتبارسنجی قرارگیرد، با کلاس invalid مزین می‌شود:
class="modified invalid form-control"
اما برای یکپارچه سازی آن با کلاس‌های اعتبارسنجی بوت استرپ 4 و 5، نیاز است از کلاس‌های is-valid و is-invalid بجای valid و invalid استفاده شود. این تغییر نیاز به استفاده از «یک نکته‌ی تکمیلی: امکان اعتبارسنجی دستی فرم‌ها در Blazor» را دارد؛ چون با دسترسی به EditContext است که می‌توان CSS provider آن‌را سفارشی سازی کرد؛ برای مثال:
EditContext = new EditContext(Model);
EditContext.SetFieldCssClassProvider(new BootstrapFieldCssClassProvider());
که سفارشی ساز مخصوص بوت استرپ، به صورت زیر قابل تعریف است:
using System;
using System.Linq;
using Microsoft.AspNetCore.Components.Forms;

namespace BlazorComponents
{
    /// <summary>
    /// Supplies CSS class names for form fields to represent their validation state or other state information from an EditContext.
    /// </summary>
    public class BootstrapFieldCssClassProvider : FieldCssClassProvider
    {
        /// <summary>
        /// Gets a string that indicates the status of the specified field as a CSS class.
        /// </summary>
        public override string GetFieldCssClass(EditContext editContext, in FieldIdentifier fieldIdentifier)
        {
            if (editContext == null)
            {
                throw new ArgumentNullException(nameof(editContext));
            }

            var isValid = !editContext.GetValidationMessages(fieldIdentifier).Any();

            if (editContext.IsModified(fieldIdentifier))
            {
                return isValid ? "is-valid" : "is-invalid";
            }
            return isValid ? "" : "is-invalid";
        }
    }
}
در اینجا در ابتدا بررسی می‌شود که آیا فیلد جاری معتبر است یا خیر و همچنین آیا ویرایش شده‌است یا خیر؟ سپس بر این اساس، کلاس‌های ویژه‌ی بوت استرپ، بجای کلاس‌های پیش‌فرض ارائه خواهند شد.
نظرات مطالب
Implementing second level caching in EF code first
من هنگام cache کردن برخی از queryها با این خطا روبرو شدم:
System.InvalidOperationExceptionWhen called from 'VisitMemberInit', rewriting a node of type 'System.Linq.Expressions.NewExpression' must return a non-null value of the same type. Alternatively, override 'VisitMemberInit' and change it to not visit children of this type. 
برای حل اون این کد رو به کلاس داخلی SubtreeEvaluator در کلاس Evaluator در فایل QueryResultCache.cs اضافه کردم:
protected override Expression VisitMemberInit(MemberInitExpression node)
 {
       if (node.NewExpression.NodeType == ExpressionType.New) 
            return node;
        return base.VisitMemberInit(node);
 }
مطالب
مدیریت استثناءها در Blazor Server - قسمت اول
همانطور که می‌دانید Blazor Server یک فریم ورک stateful است. هنگامیکه کاربران در حال تعامل با برنامه هستند، یک ارتباط پیوسته را با سرور حفظ می‌کنند که به آن، به اصطلاح مدار می‌گویند. این مدارها، کامپوننت‌های فعال را به انضمام حالت‌های آنها که شامل موارد زیر است نگهداری می‌کند:
1- جدیدترین خروجی رندر شده‌ی کامپوننت.
2- مجموعه Event Handling‌های جاری که می‌توانند توسط کاربر صدا زده شوند.
اگر کاربری یک برنامه را در چندین تب مرورگر باز کند، در واقع چندین مدار مستقل را ایجاد کرده‌است. بنابراین اگر در یکی از تب‌های مرورگر استثنایی رخ دهد، مابقی تب‌های مرورگر متاثر نخواهند شد.
Blazor با اکثریت استثناءهای کنترل نشده در  مداری که در آن رخ می‌دهد، خیلی بد رفتار می‌کند. چرا؟
پاسخ: زیرا  کاربر فقط می‌تواند با بارگذاری مجدد آن تب مرورگر (برای ایجاد یک مدار جدید) به تعامل با برنامه ادامه دهد.
حال برای رفع این مشکل چکار باید کرد؟ آیا راه حل سراسری برای مدیریت استثناها وجود دارد؟
پاسخ: بله. 

Error boundary

یک کامپوننت از پیش تعریف شده‌ی Blazor است که رویکرد آسان آن برای مدیریت استثناءها به شکل زیر است:
  • هنگامیکه خطایی رخ نداده است، محتوای فرزند خود را رندر می‌کند. 

  • هنگامیکه یک استثناء کنترل نشده رخ می‌دهد، صفحه‌ی خطای پیش فرضی را رندر می‌کند. 

برای استفاده از این کامپوننت، فقط کافی است محتوای مورد نظر خود را داخل آن بگذارید. برای مثال می‌توان، برای سراسری تعریف کردن Error boundary، به شکل زیر در فایل  Shared/MainLayout.razor   آن را تعریف نمود:
<div>
    <div>
        <ErrorBoundary>
            @Body
        </ErrorBoundary>
    </div>
</div>
در این حالت هر استثنای کنترل نشده‌ای که در کل برنامه رخ دهد، توسط Error boundaries کنترل شده و خطایی در صفحه نشان داده می‌شود. به صورت پیش فرض کامپوننت Error boundary یک div خالی را با یک کلاس css که در site.css وجود دارد، به نام blazor-error-boundary   به عنوان صفحه خطا نشان می‌دهد که می‌توان آن را سفارشی سازی نمود. همچنین می‌توان به شکل زیر نیز برای سفارشی سازی بیشتر صفحه‌ی خطا عمل کرد:
<ErrorBoundary>
    <ChildContent>
        @Body
    </ChildContent>
    <ErrorContent>
        <p class="errorUI">متاسفانه خطایی رخ داده است!</p>
    </ErrorContent>
</ErrorBoundary>
به دلیل اینکه ما در این مثال Error boundary را در MainLayout تعریف کردیم، صفحه‌ی نمایش خطا صرفنظر از اینکه کاربر به کدام صفحه رفته‌است، نمایش داده می‌شود. پیشنهاد مایکروسافت این است که حوزه استفاده را محدودتر کنیم.
خوب تا اینجای کار توانستیم استثنای کنترل نشده را کنترل کنیم و پیغام خطایی را نشان دهیم؛ اما همچنان صفحه در حالت خطا مانده و بازهم نیاز است که صفحه بارگذاری مجدد شود تا بتوان به صفحات دیگر برنامه رفت. آیا راه حلی وجود دارد؟
پاسخ: بله خوشبختانه. کافی است با استفاده از متد Recover کامپوننت Error boundary به شکل زیر صفحه را به حالت قبل از خطا برد:
...

<ErrorBoundary @ref="errorBoundary">
    @Body
</ErrorBoundary>

...

@code {
    private ErrorBoundary? errorBoundary;

    protected override void OnParametersSet()
    {
        errorBoundary?.Recover();
    }
}
در قسمت بعدی به این موضوع می‌پردازیم که چگونه می‌توان یک کامپوننت خطای سفارشی سراسری ایجاد کرد تا علاوه بر کنترل استثناءها بتواند خطاها را نیز لاگ کند.
اشتراک‌ها
لیستی از منابع برای آموزش Blazor

این مجموعه شامل آموزش، پروژه‌های مختلف، لینک‌های ویدیوهای آموزشی، کتاب، کتابخانه‌ها و افزونه‌های نوشته شده برای Blazor به علاوه کلی مطالب دیگر است که در یک جا جمع آوری شده است.

لیستی از منابع برای آموزش Blazor