مطالب دوره‌ها
پشتیبانی از XML Schema در SQL Server
XML Schema چیست؟

XML Schema معرف ساختار، نوع داده‌ها و المان‌های یک سند XML است. البته باید درنظر داشت که تعریف XML Schema کاملا اختیاری است و اگر تعریف شود مزیت اعتبارسنجی داده‌های در حال ذخیره سازی در بانک اطلاعاتی را به صورت خودکار به همراه خواهد داشت. در این حالت به نوع داده‌ای XML دارای اسکیما، typed XML و به نوع بدون اسکیما، untyped XML گفته می‌شود.
به یک نوع XML، چندین اسکیمای مختلف را می‌توان نسبت داد و به آن XML schema collection نیز می‌گویند.



XML schema collections پیش فرض و سیستمی

تعدادی XML Schema پیش فرض در SQL Server تعریف شده‌اند که به آن‌ها sys schema collections گفته می‌شود.
 Prefix - Namespace
xml = http://www.w3.org/XML/1998/namespace
xs = http://www.w3.org/2001/XMLSchema
xsi = http://www.w3.org/2001/XMLSchema-instance
fn = http://www.w3.org/2004/07/xpath-functions
sqltypes = http://schemas.microsoft.com/sqlserver/2004/sqltypes
xdt = http://www.w3.org/2004/07/xpath-datatypes
(no prefix) = urn:schemas-microsoft-com:xml-sql
(no prefix) = http://schemas.microsoft.com/sqlserver/2004/SOAP
در اینجا پیشوندها و فضاهای نام sys schema collections را ملاحظه می‌کنید. از این اسکیماها برای تعاریف strongly typed امکانات موجود در SQL Server کمک گرفته شده‌است.
اگر علاقمند باشید تا این تعاریف را مشاهده کنید به مسیر Program Files\Microsoft SQL Server\version\Tools\Binn\schemas\sqlserver در جایی که SQL Server نصب شده‌است مراجعه نمائید. برای مثال در مسیر Tools\Binn\schemas\sqlserver\2006\11\events فایل events.xsd قابل مشاهده است و یا در مسیر Tools\Binn\schemas\sqlserver\2004\07 اسکیمای ابزارهای query processor و  show plan قابل بررسی می‌باشد.
مهم‌ترین آن‌ها را در پوشه Tools\Binn\schemas\sqlserver\2004\sqltypes در فایل sqltypes.xsd می‌توانید ملاحظه کنید. اگر به محتوای آن دقت کنید، قسمتی از آن به شرح ذیل است:
  <xsd:simpleType name="char">
    <xsd:restriction base="xsd:string"/>
  </xsd:simpleType>
  <xsd:simpleType name="nchar">
    <xsd:restriction base="xsd:string"/>
  </xsd:simpleType>
  <xsd:simpleType name="varchar">
    <xsd:restriction base="xsd:string"/>
  </xsd:simpleType>
  <xsd:simpleType name="nvarchar">
    <xsd:restriction base="xsd:string"/>
  </xsd:simpleType>
  <xsd:simpleType name="text">
    <xsd:restriction base="xsd:string"/>
  </xsd:simpleType>
  <xsd:simpleType name="ntext">
    <xsd:restriction base="xsd:string"/>
  </xsd:simpleType>
در اینجا نوع‌های توکار char تا ntext به xsd:string نگاشت شده‌اند و برای اعتبارسنجی datetime و نگاشت آن، از الگوی ذیل استفاده می‌شود؛ به همراه حداقل و حداکثر قابل تعریف:
  <xsd:simpleType name="datetime">
    <xsd:restriction base="xsd:dateTime">
      <xsd:pattern value="((000[1-9])|(00[1-9][0-9])|(0[1-9][0-9]{2})|([1-9][0-9]{3}))-((0[1-9])|(1[012]))-((0[1-9])|([12][0-9])|(3[01]))T(([01][0-9])|(2[0-3]))(:[0-5][0-9]){2}(\.[0-9]{2}[037])?"/>
      <xsd:maxInclusive value="9999-12-31T23:59:59.997"/>
      <xsd:minInclusive value="1753-01-01T00:00:00.000"/>
    </xsd:restriction>
  </xsd:simpleType>
ادیتور SQL Server managment studio به خوبی، گشودن، ایجاد و یا ویرایش فایل‌هایی با پسوند xsd را پشتیبانی می‌کند.



تعریف XML Schema و استفاده از آن جهت تعریف یک strongly typed XML

XML Schema مورد استفاده در SQL Server حتما باید در بانک اطلاعاتی ذخیره شود و برای خواندن آن، برای مثال از فایل سیستم استفاده نخواهد شد.
CREATE XML SCHEMA COLLECTION invcol AS
'<xs:schema ... targetNamespace="urn:invoices">
...
</xs:schema>
'

CREATE TABLE Invoices(
id int IDENTITY PRIMARY KEY,
invoice XML(invcol)
)
در اینجا نحوه‌ی تعریف کلی یک XML Schema collection و سپس انتساب آن‌را به یک ستون XML ملاحظه می‌کنید. ستون invoice که از نوع XML تعریف شده، ارجاعی را به اسکیمای تعریف شده دارد.
در ادامه نحوه‌ی تعریف یک اسکیمای نمونه قابل مشاهده است:
CREATE XML SCHEMA COLLECTION geocol AS
'<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
           targetNamespace="urn:geo"
           elementFormDefault="qualified"
           xmlns:tns="urn:geo">
  <xs:simpleType name="dim">
    <xs:restriction base="xs:int" />
  </xs:simpleType>
  <xs:complexType name="Point">
    <xs:sequence>
      <xs:element name="X" type="tns:dim"  minOccurs="0" maxOccurs="unbounded" />
      <xs:element name="Y" type="tns:dim"  minOccurs="0" maxOccurs="unbounded" />
    </xs:sequence>
  </xs:complexType>
  <xs:element name="Point" type="tns:Point" />
</xs:schema>'
در این اسکیما، یک نوع ساده به نام dim تعریف شده‌است که محدودیت آن، ورود اعداد صحیح می‌باشد. همچنین امکان تعریف نوع‌های پیچیده نیز در اینجا وجود دارد. برای مثال نوع پچیده Point دارای دو المان X و Y از نوع dim در ادامه تعریف شده‌است. المانی که نهایتا بر این اساس در XML ظاهر خواهد شد توسط xs:element تعریف شده‌است.
اکنون برای آزمایش اسکیمای تعریف شده، جدول geo_tab را به نحو ذیل تعریف می‌کنیم و سپس سعی در insert دو رکورد در آن خواهیم کرد:
 declare @geo_tab table(
 id int identity primary key,
 point xml(content geocol)
)
 
insert into @geo_tab values('<Point xmlns="urn:geo"><X>10</X><Y>20</Y></Point>')
insert into @geo_tab values('<Point xmlns="urn:geo"><X>10</X><Y>test</Y></Point>')
در اینجا اگر دقت کنید، برای تعریف نام اسکیمای مورد استفاده، واژه content نیز ذکر شده‌است. Content مقدار پیش فرض است و در آن پذیرش XML Fragments یا محتوای XML ایی با بیش از یک Root element مجاز است. حالت دیگر آن document است که تنها یک Root element را می‌پذیرد.
در این مثال، insert اول با موفقیت انجام خواهد شد؛ اما insert دوم با خطای ذیل متوقف می‌شود:
 XML Validation: Invalid simple type value: 'test'. Location: /*:Point[1]/*:Y[1]
همانطور که ملاحظه می‌کنید، چون در insert دوم، در المان عددی Y، مقدار test وارد شده‌است و تطابقی با اسکیمای تعریف شده ندارد، insert آن مجاز نخواهد بود.



یافتن محل ذخیره سازی اطلاعات اسکیما در SQL Server

اگر علاقمند باشید تا با محل ذخیره سازی اطلاعات اسکیما، نوع‌های تعریف شده و حتی محل استفاده از آن‌ها در بانک‌های اطلاعاتی مختلف موجود آشنا شوید و گزارشی از آن‌ها تهیه کنید، می‌توانید از کوئری‌های ذیل استفاده نمائید:
 select * from sys.xml_schema_collections
select * from sys.xml_schema_namespaces
select * from sys.xml_schema_elements
select * from sys.xml_schema_attributes
select * from sys.xml_schema_types
select * from sys.column_xml_schema_collection_usages
select * from sys.parameter_xml_schema_collection_usages
باید دقت داشت زمانیکه یک schema در حال استفاده است (یک رکورد ثبت شده مقید به آن تعریف شده باشد)، امکان drop آن نخواهد بود. حتما باید اطلاعات و ستون مرتبط، ارجاعی را به schema نداشته باشند تا بتوان آن schema را حذف کرد.
محتوای اسکیمای ذخیره شده به شکل xsd تعریف شده، ذخیره سازی نمی‌شود. بلکه اطلاعات آن تجزیه شده و سپس در جداول سیستمی SQL Server ذخیره می‌گردند. هدف از اینکار، بالا بردن سرعت اعتبارسنجی typed XMLها است.
بنابراین بدیهی است در این حالت اطلاعاتی مانند commnets موجود در xsd تهیه شده در بانک اطلاعاتی ذخیره نمی‌گردند.
برای بازیابی اطلاعات اسکیمای ذخیره شده می‌توان از متد xml_schema_namespace استفاده کرد:
 declare @x xml
select @x = xml_schema_namespace(N'dbo', N'geocol')
print convert(varchar(max), @x)
برای تعریف و یا تغییر یک XML Schema نیاز به دسترسی مدیریتی یا dbo است (به صورت پیش فرض). همچنین برای استفاده از Schema تعریف شده، کاربر متصل به SQL Server باید دسترسی Execute و References نیز داشته باشد.



نحوه‌ی ویرایش یک schema collection موجود

چند نکته:
- امکان alter یک schema collection وجود دارد.
- می‌توان یک schema جدید را به collection موجود افزود.
- امکان افزودن (و نه تغییر) نوع‌های یک schema موجود، میسر است.
- امکان drop یک اسکیما از collection موجودی وجود ندارد. باید کل collection را drop کرد و سپس آن‌را تعریف نمود.
- جداولی با فیلدهای nvarchar را می‌توان به فیلدهای XML تبدیل کرد و برعکس.
- امکان تغییر یک فیلد XML به حالت untyped و برعکس وجود دارد.

فرض کنید که می‌خواهیم اسکیمای متناظر با یک ستون XML را تغییر دهیم. ابتدا باید آن ستون XML ایی را Alter کرده و قید اسکیمای آن‌را برداریم. سپس باید اسکیمای موجود را drop و مجددا ایجاد کرد. همانطور که پیشتر ذکر شد، اگر اسکیمایی در حال استفاده باشد، قابل drop نیست. در ادامه مجددا باید ستون XML ایی را تغییر داده و اسکیمای آن‌را معرفی کرد.
روش دوم مدیریت این مساله، اجازه دادن به حضور بیش از یک اسکیما در مجموعه است. به عبارتی نگارش‌بندی اسکیما که به نحو ذیل قابل انجام است:
 alter XML SCHEMA COLLECTION geocol add @x
در اینجا به collection موجود، یک اسکیمای جدید (برای مثال نگارش دوم اسکیمای فعلی) اضافه می‌شود. در این حالت geocol، هر دو نوع اسکیمای موجود را پشتیبانی خواهد کرد.



نحوه‌ی import یک فایل xsd و ذخیره آن به صورت اسکیما

اگر بخواهیم یک فایل xsd موجود را به عنوان xsd معرفی کنیم می‌توان از دستورات ذیل کمک گرفت:
 declare @x xml
set @x = (select * from openrowset(bulk 'c:\path\file.xsd', single_blob) as x)
CREATE XML SCHEMA COLLECTION geocol2 AS @x
در اینجا به کمک openrowset فایل xsd موجود، در یک متغیر xml بارگذاری شده و سپس در دستور ایجاد یک اسکیما کالکشن جدید استفاده می‌شود.
از openrowset برای خواندن یک فایل xml موجود، جهت insert محتوای آن در بانک اطلاعاتی نیز می‌توان استفاده کرد.



محدودیت‌های XML Schema در SQL Server

تمام استاندارد XML Schema در SQL Server پشتیبانی نمی‌شود و همچنین این مورد از نگارشی به نگارشی دیگر نیز ممکن است تغییر یافته و بهبود یابد. برای مثال در SQL Server 2005 از xs:any پشتیبانی نمی‌شود اما در SQL Server 2008 این محدودیت برطرف شده‌است. همچنین مواردی مانند xs:include، xs:redefine، xs:notation، xs:key، xs:keyref و xs:unique در SQL Server پشتیبانی نمی‌شوند.



یک نکته‌ی تکمیلی

برنامه‌ای به نام xsd.exe به همراه Visual Studio ارائه می‌شود که قادر است به صورت خودکار از یک فایل XML موجود، XML Schema تولید کند. اطلاعات بیشتر 
نظرات مطالب
تولید SiteMap استاندارد و ایجاد یک ActionResult اختصاصی برای Return کردن SiteMap تولید شده
با تشکر
آیا راهی وجود داره بدون تعریف کردن route بتوان به اکشن مورد نظر دسترسی پیدا کرد؟
چون برای فعال کردن این قابلیت باید اجازه وارد کردن dot در url رو داد که برامون امکان پذیر نیست
مطالب
قالبی برای ایجاد آزمون‌های NUnit مخصوص ReSharper

افزونه‌ی ReSharper به‌دلیل یکپارچه کردن امکان استفاده از NUnit در ویژوال استودیو، یکی از انتخاب‌های اول جهت انجام آزمایشات واحد در این محیط به شمار می‌رود.
اخیرا آقای Genisio چند قالب ایجاد آزمون‌های NUnit را مخصوص ReSharper ایجاد کرده‌اند، که در ادامه در مورد نحوه‌ی استفاده از آن‌ها توضیح داده خواهد شد.
پس از دریافت فایل‌ها، برای استفاده، به منوی ReSharper گزینه‌ی live templates مراجعه نمائید. سپس بر روی نوار ابزار صفحه‌ی باز شده، روی دکمه‌ی import کلیک نموده و فایل‌ها را معرفی کنید.
NewTestFileTemplate.xml از نوع file template است.
TestTemplates.xml از نوع live template می‌باشد.


اکنون مجددا به منوی اصلی ReSharper مراجعه کنید و مسیر زیر را طی نمائید:

ReSharper -> new from template -> more …




گزینه‌ی Test اضافه شده را انتخاب کرده و سپس قسمت Add to quicklist را نیز انتخاب نمائید.
به این صورت گزینه‌ی Test به این منو افزوده خواهد شد و هر بار که بر روی آن کلیک شود، یک کلاس حاضر و آماده مطابق قالب اصلی یک کلاس استاندارد NUnit برای شما ایجاد خواهد شد.
همچنین در این مجموعه یک سری live template نیز موجود است که کار آن‌ها فعال سازی intellisense ویژوال استودیو جهت ایجاد یک سری متدها به صورت خودکار است. برای مثال اگر کلمه‌ی test را تایپ کنید و سپس دکمه‌ی tab و یا enter را فشار دهید، بلافاصله بدنه‌ی خالی یک متد تست برای شما ایجاد خواهد شد.
سایر میان‌بر‌های در نظر گرفته شده، به شرح زیر هستند:
test – Create a new [Test] method
setup – Create a [SetUp] method
teardown – Create a new [TearDown] method
ise – Assert that condition is equal to value
ist – Assert that condition is true
isf – Assert that condition is false
isn – Assert that condition is null
isnn – Assert that condition is not null


نظرات مطالب
مروری بر تاریخچه محدودیت حافظه مصرفی برنامه‌های ASP.NET در IIS
با سلام؛
مشکلی که من دارم نمیدانم مربوط میشود به مدیریت حافظه یا موضوعی دیگر
من یک وب سایت کوچک دارم که با تکنولوژی‌های زیر ایجاد شده:
ASP.Net MVC 4,  Entity Framework 4 , SQL CE
آن را بر روی یک ویندوز سرور 2012 نسخه دیتاسنتر نصب کردم
سرور : 2GB Ram و CPU Dual Core 1.8 
غیر از این سایت هیچ سایت دیگری بر روی این سرور میزبانی نشده است.
صفحات با سرعت نسبتاً خوبی باز میشوند، اما به هر شکلی iis را تنظیم میکنم، اگر پس از 2 یا 3 دقیقه درخواستی به سمت سرور ارسال نگردد، برنامه از حافظه خارج میشود. اگر درخواستی برای مشاهده صفحه به سرور ارسال شود 15 تا 20 ثانیه طول میکشد تا دوباره کامپایل انجام شود و صفحه درخواستی نمایش یابد.
تصویر تنظیمات Application Pool

پ.ن: لطفاً اگر امکان دارد بهترین تنظیمات را برای سروری که فقط به یک سایت میخواهد سرویس دهد عنوان کنید.
مطالب
Blazor 5x - قسمت 22 - احراز هویت و اعتبارسنجی کاربران Blazor Server - بخش 2 - ورود به سیستم و خروج از آن
در قسمت قبل، نحوه‌ی افزودن قالب ابتدایی ASP.NET Core Identity را به یک برنامه‌ی Blazor Server بررسی کردیم. در این مطلب، قسمت‌های ورود و خروج آن‌را به همراه نمایش قسمتی از صفحه، تنها به کاربران اعتبارسنجی شده، بررسی می‌کنیم تا روش دسترسی به اطلاعات ASP.NET Core Identity را در یک برنامه‌ی Blazor Server یکپارچه شده‌ی با آن، مطالعه کنیم.


نمایش قسمتی از صفحه بر اساس وضعیت اعتبارسنجی کاربر

فرض کنید می‌خواهیم در کامپوننت Shared\LoginDisplay.razor که در قسمت قبل آن‌را اضافه کردیم، لینک‌های ثبت نام و لاگین را به کاربران غیر اعتبارسنجی شده (هنوز لاگین نکرده) نمایش دهیم و اگر کاربر، اعتبارسنجی شده بود (لاگین کرده بود)، لینک خروج را به او نمایش دهیم. برای این منظور کامپوننت Shared\LoginDisplay.razor را به صورت زیر تغییر می‌دهیم:
<AuthorizeView>
    <Authorized>
        <a href="Identity/Account/Logout">Logout</a>
    </Authorized>
    <NotAuthorized>
        <a href="Identity/Account/Register">Register</a>
        <a href="Identity/Account/Login">Login</a>
    </NotAuthorized>
</AuthorizeView>
AuthorizeView، یکی از کامپوننت‌های استاندارد Blazor Server است. زمانیکه کاربری به سیستم لاگین کرده باشد، فرگمنت Authorized و در غیر اینصورت قسمت NotAuthorized آن‌را مشاهده خواهد کرد.
البته اگر برنامه را در همین حالت اجرا کنیم، به استثنای زیر خواهیم رسید:
InvalidOperationException: Authorization requires a cascading parameter of type Task<AuthenticationState>.
Consider using CascadingAuthenticationState to supply this.
Microsoft.AspNetCore.Components.Authorization.AuthorizeViewCore.OnParametersSetAsync()
برای رفع این مشکل و ارائه‌ی AuthenticationState به تمام کامپوننت‌های یک برنامه‌ی Blazor Server، نیاز است از کامپوننت CascadingAuthenticationState استفاده کرد. در مورد پارامترهای آبشاری، در قسمت نهم این سری بیشتر بحث شد و هدف از آن، ارائه‌ی یکسری اطلاعات، به تمام زیر کامپوننت‌های یک کامپوننت والد است؛ بدون اینکه نیاز باشد مدام این پارامترها را در هر زیر کامپوننتی، تعریف و تنظیم کنیم. همینقدر که آن‌ها را در بالاترین سطح سلسله مراتب کامپوننت‌های تعریف شده تعریف کردیم، در تمام زیر کامپوننت‌های آن نیز در دسترس خواهند بود.
بنابراین به فایل BlazorServer.App\App.razor که محل تعریف ریشه‌ی مسیریابی برنامه‌است، مراجعه کرده و کامپوننت آن‌را با کامپوننت توکار CascadingAuthenticationState محصور می‌کنیم:
<CascadingAuthenticationState>
    <Router AppAssembly="@typeof(Program).Assembly" PreferExactMatches="@true">
        <Found Context="routeData">
            <RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
        </Found>
        <NotFound>
            <LayoutView Layout="@typeof(MainLayout)">
                <p>Sorry, there's nothing at this address.</p>
            </LayoutView>
        </NotFound>
    </Router>
</CascadingAuthenticationState>
اینکار سبب می‌شود تا اطلاعات AuthenticationState، بین تمام کامپوننت‌های یک برنامه‌ی Blazor به اشتراک گذاشته شود.

اکنون اگر برنامه را اجرا کنیم، مشاهده خواهیم کرد که در اولین بار مراجعه‌ی به آن (پیش از لاگین)، لینک به صفحه‌ی خروج، نمایش داده نشده‌است؛ چون آن‌را در فرگمنت مخصوص Authorized قرار دادیم:



آزمایش نمایش منوی خروج برنامه

برای آزمایش برنامه، نیاز است ابتدا یک کاربر جدید را ثبت کنیم؛ چون هنوز هیچ کاربری در آن ثبت نشده‌است و همچنین کاربر پیش‌فرضی را هم به همراه ندارد. در مورد روش ثبت کاربران پیش‌فرض ASP.NET Core Identity، می‌توانید به مطلب «بازنویسی متد مقدار دهی اولیه‌ی کاربر ادمین در ASP.NET Core Identity توسط متد HasData در EF Core» مراجعه کنید و تمام نکات آن، در اینجا هم صادق است (چون پایه‌ی سیستم Identity مورد استفاده، یکی است و هدف ما در اینجا بیشتر بررسی نکات یکپارچه سازی آن با Blazor Server است و نه مرور تمام نکات ریز Identity).
بنابراین ابتدا از منوی بالای صفحه، گزینه‌ی Register را انتخاب کرده و کاربری را ثبت می‌کنیم. پس از ثبت نام، بلافاصله به منوی جدید زیر می‌رسیم که در آن گزینه‌های ورود و ثبت نام، مخفی شده‌اند و اکنون گزینه‌ی خروج از سیستم را نمایش می‌دهد:



بهبود تجربه‌ی کاربری خروج از سیستم

در همین حال که گزینه‌ی خروج نمایش داده شده‌است، اگر بر روی لینک آن کلیک کنیم، ابتدا ما را به صفحه‌ی مجزای logout هدایت می‌کند. سپس باید در این صفحه، مجددا بر روی لینک logout بالای آن کلیک کنیم. زمانیکه اینکار را انجام دادیم، اکنون صفحه‌ی دیگری را نمایش می‌دهد که به همراه پیام «خروج موفقیت آمیز از سیستم» است! در این پروسه، کاربر احساس می‌کند که کاملا از برنامه‌ی اصلی خارج شده‌است و همچنین مراحل طولانی را نیز باید طی کند.
مدیریت این مراحل توسط دو فایل زیر انجام می‌شوند:
Areas\Identity\Pages\Account\Logout.cshtml
Areas\Identity\Pages\Account\Logout.cshtml.cs

می‌خواهیم کدهای این دو فایل را به نحوی تغییر دهیم که اگر کاربری بر روی لینک logout برنامه‌ی اصلی کلیک کرد، به صورت خودکار logout شده و سپس مجددا به صفحه‌ی اصلی برنامه‌ی Blazor Server هدایت شود و مجبور نباشد تا مراحل طولانی یاد شده را تکرار کند.
به همین جهت ابتدا فایل Logout.cshtml.cs را حذف می‌کنیم؛ چون نیازی به آن نداریم. سپس محتوای فایل Logout.cshtml را به صورت زیر تغییر می‌دهیم:
@page
@using Microsoft.AspNetCore.Identity
@inject SignInManager<IdentityUser> SignInManager

@functions
{
    public async Task<IActionResult> OnGet()
    {
        if (SignInManager.IsSignedIn(User))
        {
            <p>You have successfully logged out of the application.</p>
            await SignInManager.SignOutAsync();
        }
        return Redirect("~/");
    }
}
با استفاده از سرویس SignInManager در ASP.NET Core Identity می‌توان یک کاربر را logout کرد که نمونه‌ای از آن‌را در اینجا مشاهده می‌کنید. در این حالت بررسی می‌شود که آیا کاربر جاری، به سیستم وارد شده‌است؟ اگر بله، کوکی‌های او حذف شده و سپس به صفحه‌ی اصلی برنامه، Redirect می‌شود. به این ترتیب به تجربه‌ی کاربری خروج بهتری خواهیم رسید.


نمایش User Claims، در یک برنامه‌ی Blazor Server

سیستم ASP.NET Core Identity، بر اساس User Claims کار می‌کند؛ اطلاعات بیشتر. پس از استفاده از CascadingAuthenticationState در بالاترین سطح برنامه، اطلاعات آن در سراسر برنامه‌ی Blazor Server هم قابل دسترسی است. برای مثال در کامپوننت Shared\LoginDisplay.razor، به نحو زیر می‌توان نام کاربر ثبت نام شده را که یکی از User Claims او است، نمایش داد:
<AuthorizeView>
    <Authorized>
        Hello, @context.User.Identity.Name
        <a href="Identity/Account/Logout">Logout</a>
    </Authorized>



محدود کردن دسترسی به صفحات برنامه تنها برای کاربران اعتبارسنجی شده

پس از لاگین موفق به سیستم، اکنون می‌خواهیم دسترسی به صفحات تعریف اتاق‌ها و یا امکانات رفاهی هتل را تنها به کاربران لاگین شده، محدود کنیم. برای اینکار تنها کافی است از ویژگی Authorize استفاده کنیم. برای مثال به کامپوننت Pages\HotelRoom\HotelRoomList.razor مراجعه کرده و یک سطر زیر را به آن اضافه می‌کنیم:
@attribute [Authorize]
دسترسی به کامپوننتی که دارای دایرکتیو فوق باشد، تنها مختص به کاربران اعتبارسنجی شده‌ی سیستم است.

مشکل! با اینکه تمام کامپوننت‌های مثال جاری را به ویژگی Authorize مزین کرده‌ایم، اما ... کار نمی‌کند! و هنوز هم می‌توان بدون لاگین به سیستم، به محتوای آن‌ها دسترسی داشت.
برای رفع این مشکل، مجددا نیاز است کامپوننت BlazorServer.App\App.razor را ویرایش کرد:
<CascadingAuthenticationState>
    <Router AppAssembly="@typeof(Program).Assembly" PreferExactMatches="@true">
        <Found Context="routeData">
            @*<RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />*@
            <AuthorizeRouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)">
                <NotAuthorized>
                    <p>Sorry, you do not have access to this page</p>
                </NotAuthorized>
            </AuthorizeRouteView>
        </Found>
        <NotFound>
            <LayoutView Layout="@typeof(MainLayout)">
                <p>Sorry, there's nothing at this address.</p>
            </LayoutView>
        </NotFound>
    </Router>
</CascadingAuthenticationState>
در اینجا RouteView پیشین را کامنت کرده و با AuthorizeRouteView، جایگزین کرده‌ایم. کار آن فعالسازی پردازش ویژگی Authorize افزوده شده‌ی به کامپوننت‌های برنامه‌است. همچنین در اینجا محتوای سفارشی را که در صورت درخواست یک چنین کامپوننت‌هایی نمایش داده می‌شود، در فرگمنت NotAuthorized مشاهده می‌کنید؛ که حتی می‌تواند یک کامپوننت مجزا هم باشد:



کدهای کامل این مطلب را از اینجا می‌توانید دریافت کنید: Blazor-5x-Part-22.zip
مطالب
Blazor 5x - قسمت اول - معرفی
با استفاده از Blazor می‌توان برنامه‌های وب تعاملی را با کمک زبان #C تهیه کرد که پیشتر برای نوشتن آن‌ها به جاوا اسکریپت نیاز بود. به این ترتیب می‌توان برای تهیه‌ی قسمت‌های front-end و backend پروژه‌ی خود، از زبانی که به آن تسلط دارید استفاده کنید. یکی از مزایای آن امکان به اشتراک گذاری کدهای سمت سرور و کلاینت است؛ با توجه به اینکه هر دو به یک زبان تهیه می‌شوند.


وضعیت توسعه‌ی برنامه‌های وب، پیش از ارائه‌ی Blazor

عموما برای توسعه‌ی برنامه‌های وب، در سمت سرور آن‌ها از زبان‌هایی مانند C#، Java و Python و امثال آن‌ها استفاده می‌شود؛ اما این وضعیت در سمت کلاینت فرق می‌کند. در سمت کلاینت، عموما از فریم‌ورک‌ها و کتابخانه‌های جاوا اسکریپتی مانند Angular ،React ،Vue.js ،jQuery و غیره استفاده می‌شود.


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


با استفاده از Blazor می‌توان کدهای تعاملی UI را بجای استفاده از زبان جاوا اسکریپت، با کمک زبان #C تهیه کرد. به این ترتیب با استفاده از یک زبان می‌توان کدهای سمت سرور و سمت کلاینت را پیاده سازی کرد. البته شاید این سؤال مطرح شود که مرورگرها تنها قادر به درک کدهای HTML و جاوا اسکریپت هستند و نه #C، بنابراین چگونه می‌توان از زبان #C در مرورگرها نیز استفاده کرد؟ پاسخ به آن، به فناوری جدید «وب اسمبلی» بر می‌گردد. Blazor با استفاده از «وب اسمبلی» است که می‌تواند کدهای #C را درون مرورگر اجرا کند.


حالت‌های مختلف هاست و ارائه‌ی برنامه‌های مبتنی بر Blazor

برنامه‌های مبتنی بر Blazor، به دو روش مختلف قابل ارائه هستند:

الف) Blazor Server

Blazor Server، در اساس یک برنامه‌ی استاندارد ASP.NET Core است که در آن تمام قابلیت‌های سمت سرور، مانند کار با EF-Core، میسر است و امکان دسترسی به این امکانات به صورت یکپارچه‌ای در سراسر برنامه وجود دارد. در این حالت، کامپوننت‌های Blazor، بجای اجرای بر روی مرورگر کاربر، در سمت سرور اجرا می‌شوند. این تعاملات و به روز رسانی‌های UI، توسط یک اتصال دائم SignalR مدیریت می‌شوند.


همانطور که مشاهده می‌کنید، در حالت هاست سمت سرور، همه چیز، منجمله کامپوننت‌های Blazor، در همان سمت سرور قرار دارند و این اتصال پشت صحنه‌ی SignalR است که کار تبادل اطلاعات ارسالی و رندر شده را بر عهده می‌گیرد.

ب) Blazor web assembly

در این حالت با استفاده از فناوری جدید «وب اسمبلی»، تمام کدهای یک برنامه‌ی مبتنی بر Blazor به کمک NET Runtime.، داخل مرورگر اجرا می‌شود. به Blazor web assembly باید همانند فریم‌ورک‌های SPA (تک صفحه‌ای وب)، مانند Angular و React نگاه کرد؛ با یک تفاوت مهم: در اینجا بجای استفاده از جاو اسکریپت برای نوشتن برنامه‌ی SPA، از #C استفاده می‌شود. اگر به تصویر فوق دقت کنید، در حالت اجرای برنامه‌های Blazor web assembly، تنها به مرورگر کاربر نیاز است و همه چیز داخل آن قرار می‌گیرد. در اینجا دیگر خبری از یک اتصال دائم SignalR با سرور وجود ندارد.
البته باید دقت داشت که از فناوری وب اسمبلی، در تمام مرورگرهای جدید پشتیبانی می‌شود؛ منهای IE 11. در این حالت مرورگر کل برنامه‌ی Blazor را دریافت می‌کند (همانند دریافت کل کدهای یک برنامه‌ی Angular و یا React) و بدون استفاده از رندر سمت سرور حالت الف، قابلیت تعامل با کاربر را دارد.
بدیهی است با توجه به اینکه Blazor web assembly مستقیما داخل مرورگر اجرا می‌شود، دیگر همانند حالت الف، امکان دسترسی مستقیم به فناوری‌ها و امکانات سمت سرور، مانند کار مستقیم با EF-Core را نخواهد داشت. برای این منظور دقیقا همانند روش کار با سایر فریم ورک‌های SPA، نیاز به تهیه‌ی یک ASP.NET Core Web API جهت تعامل با سرور خواهد بود.


مزایا و معایب حالت‌های مختلف هاست برنامه‌های Blazor

الف) Blazor Server
مزایا:
- حجم دریافتی توسط مرورگر در این حالت بسیار کم است.
- امکان دسترسی به تمام امکانات سمت سرور را دارد؛ مانند تمام کتابخانه‌های سمت سرور و همچنین امکان دیباگ آن نیز همانند سایر برنامه‌های سمت سرور است.
- بر روی مرورگرهای قدیمی نیز قابل اجرا است؛ چون بدون نیاز به فناوری جدید «وب اسمبلی» کار می‌کند.

معایب:
- رندر شدن UI آن نسبت به حالت ب، کندتر است. از این جهت که تمام تعاملات UI آن، توسط اتصال SignalR به سمت سرور ارسال شده و سپس نتیجه‌ی نهایی رندر شده، به سمت کلاینت بازگشت داده می‌شود.
- پشتیبانی از اجرای offline آن وجود ندارد. اگر اتصال SignalR موجود قطع شود، دیگر نمی‌توان از برنامه استفاده کرد.
- با توجه به نیاز به استفاده‌ی از یک اتصال دائم SignalR به ازای هر کاربر، مقیاس پذیری این نوع برنامه‌ها کمتر است. البته اگر تعداد کاربران برنامه‌های شما در یک شبکه‌ی اینترانت داخلی شرکتی محدود است، این مورد مشکل خاصی نخواهد بود. از دیدگاهی دیگر اگر تعداد کاربران برنامه‌ی شما بسیار زیاد است، استفاده از Blazor Server توصیه نمی‌شود. البته باید دقت داشت که سروری با 4GB RAM، می‌تواند 5000 کاربر همزمان SignalR را مدیریت کند.


ب) Blazor web assembly یا به اختصار Blazor WASM
مزایا:
- هیچ نوع وابستگی به سمت سرور ندارد. همینقدر که برنامه توسط مرورگر دریافت شد، قابل اجر است.
- برای هاست آن الزاما نیازی به یک سرور IIS و یا یک وب سرور ASP.NET Core نیست.
- امکان ارائه‌ی آن توسط یک CDN نیز وجود دارد.
- چون در این حالت کل برنامه توسط مرورگر دریافت می‌شود، قابلیت اجرای آفلاین را نیز پیدا می‌کند.
- برای کار، نیازی به اتصال دائم SignalR را ندارد؛ به همین جهت مقیاس پذیری آن بیشتر است.

معایب:
- حتما نیاز به استفاده‌ی از مرورگرهای جدید با پشتیبانی از web assembly را دارد؛ برای مثال نیاز به کروم نگارش 57 به بعد و فایرفاکس نگارش 52 به بعد را دارد و بر روی IE اجرا نمی‌شود.
- چون کل برنامه در این حالت توسط مرورگر دریافت می‌شود، حجم ابتدایی دریافت آن کمی بالا است.
- میدان دید و عملکرد آن همانند سایر برنامه‌های SPA، محدود است به امکاناتی که مرورگر، در اختیار برنامه قرار می‌دهد.



ایجاد پروژه‌های خالی Blazor Server و Blazor web assembly

یا می‌توانید از ویژوال استودیوی کامل و منوی افزودن پروژه‌ی آن برای اینکار استفاده کنید و یا اگر به خروجی دستور dotnet new --list مراجعه کنیم، SDK دات نت 5، به همراه دو قالب مرتبط زیر نیز هست:
C:\Users\Vahid>dotnet new --list
Templates                                         Short Name               Language          Tags
--------------------------------------------      -------------------      ------------      ----------------------
Blazor Server App                                 blazorserver             [C#]              Web/Blazor
Blazor WebAssembly App                            blazorwasm               [C#]              Web/Blazor/WebAssembly
بنابراین فقط کافی است دستور dotnet new blazorserver و یا dotnet new blazorwasm را در یک پوشه‌ی خالی اجرا کنیم تا بر اساس قالب‌های پیش‌فرض ارائه شده، بتوان پروژه‌های خالی Blazor Server و یا Blazor WebAssembly را ایجاد کرد.


در قسمت بعد، این دو پروژه‌ی خالی فوق را ایجاد کرده و ساختار آن‌ها را بررسی می‌کنیم. همچنین نکاتی را هم که در این قسمت در مورد نحوه‌ی هاست این برنامه‌ها عنوان شد، بر روی این پروژه‌ها مشاهده خواهیم کرد.
نظرات مطالب
نحوه تعریف Linked Server و دریافت اطلاعات از سروری دیگر
با سلام و احترام؛ ضمن سپاس از نوشتن این مطلب، نکات تکمیلی زیر برای خوانندگان ارائه می‌گردد:
پس از ایجاد Linked Server به طریقی که در مطلب فوق بدان اشاره گردید، به دو صورت می‌توان به اجرای دستورات در Server دیگر پرداخت. به شکل Distributed Query و یا استفاده از تابع ()OPENQUERY .
برای مثال دو SQL Instance به نام‌های SQL1 و SQL2 موجود است که قصد داریم عملیاتی را از SQL2 با استفاده از امکان Distributed Query و OpenQuery در SQL1 انجام دهیم، برای مثال:
  :Distributed Query
SELECT * FROM [SQL2].[test].[dbo].[emp]
:OPENQUERY
 SELECT * from OPENQUERY([SQL2], 'SELECT * FROM [test].[dbo].[emp]')
در استفاده از OpenQuery باید جانب احتیاط را رعایت نمود، چنانچه قصد اجرای دستورات ساده ای را در Remote Server دارید، توصیه می‌شود از Distributed Query استفاده شود و اگر دستورات پیچیده و محاسباتی هستند استفاده از OPENQUERY هزینه کمتری دارد. همچنین در نظر داشته باشید چنانچه در دستورات مربوط به جدولی ستونی با دیتا تایپ XML وجود داشت قادر به اجرای دستورات به هر دو شکل نخواهید بود، در واقع کوئری‌های توزیع شده این مورد را پشتیبانی نمی‌کنند. برای مثال پس از اجرای دستورات زیر با پیغام خطا زیر مواجه می‌شوید: «Xml data type is not supported in distributed queries»
SELECT * FROM [نام Linked Server].[AdventureWorks].[Person].[Person]
GO
SELECT * FROM OPENQUERY([نام Linked Server ],'SELECT * FROM [AdventureWorks].[Person].[Person]')
همچنین در نظر داشته باشید چنانچه در دستورات مربوط به جدولی ستونی با دیتا تایپ CLR وجود داشت قادر به اجرای دستورات به شکل OpenQuery خواهید بود ولی در صورت استفاده از Distributed Query  با خطایی مواجه می‌شوید با این پیغام  «Objects exposing columns with CLR types are not allowed in distributed queries»
SELECT * FROM [نام Linked Server] .[AdventureWorks].[Person].[Address]
GO
SELECT * FROM OPENQUERY([نام Linked Server] ,'SELECT * FROM [AdventureWorks].[Person].[Address]')
برای کسب اطلاعات بیشتر به لینک زیر مراجعه فرمائید:
مطالب
امن سازی برنامه‌های ASP.NET Core توسط IdentityServer 4x - قسمت دوازدهم- یکپارچه سازی با اکانت گوگل
در مطلب قبلی «استفاده از تامین کننده‌های هویت خارجی»، نحوه‌ی استفاده از اکانت‌های ویندوزی کاربران یک شبکه، به عنوان یک تامین کننده‌ی هویت خارجی بررسی شد. در ادامه می‌خواهیم از اطلاعات اکانت گوگل و IDP مبتنی بر OAuth 2.0 آن به عنوان یک تامین کننده‌ی هویت خارجی دیگر استفاده کنیم.


ثبت یک برنامه‌ی جدید در گوگل

اگر بخواهیم از گوگل به عنوان یک IDP ثالث در IdentityServer استفاده کنیم، نیاز است در ابتدا برنامه‌ی IDP خود را به آن معرفی و در آنجا ثبت کنیم. برای این منظور مراحل زیر را طی خواهیم کرد:

1- مراجعه به developer console گوگل و ایجاد یک پروژه‌ی جدید
https://console.developers.google.com
در صفحه‌ی باز شده، بر روی دکمه‌ی select project در صفحه و یا لینک select a project در نوار ابزار آن کلیک کنید. در اینجا دکمه‌ی new project و یا create را مشاهده خواهید کرد. هر دوی این مفاهیم به صفحه‌ی زیر ختم می‌شوند:


در اینجا نامی دلخواه را وارد کرده و بر روی دکمه‌ی create کلیک کنید.

2- فعالسازی API بر روی این پروژه‌ی جدید


در ادامه بر روی لینک Enable APIs And Services کلیک کنید و سپس google+ api را جستجو نمائید.
پس از ظاهر شدن آن، این گزینه را انتخاب و در صفحه‌ی بعدی، آن‌را با کلیک بر روی دکمه‌ی enable، فعال کنید.

3- ایجاد credentials


در اینجا بر روی دکمه‌ی create credentials کلیک کرده و در صفحه‌ی بعدی، این سه گزینه را با مقادیر مشخص شده، تکمیل کنید:
• Which API are you using? – Google+ API
• Where will you be calling the API from? – Web server (e.g. node.js, Tomcat)
• What data will you be accessing? – User data
سپس در ذیل این صفحه بر روی دکمه‌ی «What credentials do I need» کلیک کنید تا به صفحه‌ی پس از آن هدایت شوید. اینجا است که مشخصات کلاینت OAuth 2.0 تکمیل می‌شوند. در این صفحه، سه گزینه‌ی آن‌را به صورت زیر تکمیل کنید:
• نام: همان مقدار پیش‌فرض آن
• Authorized JavaScript origins: آن‌را خالی بگذارید.
• Authorized redirect URIs: این مورد همان callback address مربوط به IDP ما است که در اینجا آن‌را با آدرس زیر مقدار دهی خواهیم کرد.
https://localhost:6001/signin-google
این آدرس، به آدرس IDP لوکال ما اشاره می‌کند و مسیر signin-google/ آن باید به همین نحو تنظیم شود تا توسط برنامه شناسایی شود.
سپس در ذیل این صفحه بر روی دکمه‌ی «Create OAuth 2.0 Client ID» کلیک کنید تا به صفحه‌ی «Set up the OAuth 2.0 consent screen» بعدی هدایت شوید. در اینجا دو گزینه‌ی آن‌را به صورت زیر تکمیل کنید:
- Email address: همان آدرس ایمیل واقعی شما است.
- Product name shown to users: یک نام دلخواه است. نام برنامه‌ی خود را برای نمونه ImageGallery وارد کنید.
برای ادامه بر روی دکمه‌ی Continue کلیک نمائید.

4- دریافت credentials
در پایان این گردش کاری، به صفحه‌ی نهایی «Download credentials» می‌رسیم. در اینجا بر روی دکمه‌ی download کلیک کنید تا ClientId  و ClientSecret خود را توسط فایلی به نام client_id.json دریافت نمائید.
سپس بر روی دکمه‌ی Done در ذیل صفحه کلیک کنید تا این پروسه خاتمه یابد.


تنظیم برنامه‌ی IDP برای استفاده‌ی از محتویات فایل client_id.json

پس از پایان عملیات ایجاد یک برنامه‌ی جدید در گوگل و فعالسازی Google+ API در آن، یک فایل client_id.json را دریافت می‌کنیم که اطلاعات آن باید به صورت زیر به فایل آغازین برنامه‌ی IDP اضافه شود:
الف) تکمیل فایل src\IDP\DNT.IDP\appsettings.json
{
  "Authentication": {
    "Google": {
      "ClientId": "xxxx",
      "ClientSecret": "xxxx"
    }
  }
}
در اینجا مقادیر خواص client_secret و client_id موجود در فایل client_id.json دریافت شده‌ی از گوگل را به صورت فوق به فایل appsettings.json اضافه می‌کنیم.
ب) تکمیل اطلاعات گوگل در کلاس آغازین برنامه
namespace DNT.IDP
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
   // ... 

            services.AddAuthentication()
                .AddGoogle(authenticationScheme: "Google", configureOptions: options =>
                {
                    options.SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme;
                    options.ClientId = Configuration["Authentication:Google:ClientId"];
                    options.ClientSecret = Configuration["Authentication:Google:ClientSecret"];
                });
        }
خود ASP.NET Core از تعریف اطلاعات اکانت‌های Google, Facebook, Twitter, Microsoft Account و  OpenID Connect پشتیبانی می‌کند که در اینجا نحوه‌ی تنظیم اکانت گوگل آن‌را مشاهده می‌کنید.
- authenticationScheme تنظیم شده باید یک عبارت منحصربفرد باشد.
- همچنین SignInScheme یک چنین مقداری را در اصل دارد:
 public const string ExternalCookieAuthenticationScheme = "idsrv.external";
از این نام برای تشکیل قسمتی از نام کوکی که اطلاعات اعتبارسنجی گوگل در آن ذخیره می‌شود، کمک گرفته خواهد شد.


آزمایش اعتبارسنجی کاربران توسط اکانت گوگل آن‌ها

اکنون که تنظیمات اکانت گوگل به پایان رسید و همچنین به برنامه نیز معرفی شد، برنامه‌ها را اجرا کنید. مشاهده خواهید کرد که امکان لاگین توسط اکانت گوگل نیز به صورت خودکار به صفحه‌ی لاگین IDP ما اضافه شده‌است:


در اینجا با کلیک بر روی دکمه‌ی گوگل، به صفحه‌ی لاگین آن که به همراه نام برنامه‌ی ما است و انتخاب اکانتی از آن هدایت می‌شویم:


پس از آن، از طرف گوگل به صورت خودکار به IDP (همان آدرسی که در فیلد Authorized redirect URIs وارد کردیم)، هدایت شده و callback رخ‌داده، ما را به سمت صفحه‌ی ثبت اطلاعات کاربر جدید هدایت می‌کند. این تنظیمات را در قسمت قبل ایجاد کردیم:
namespace DNT.IDP.Controllers.Account
{
    [SecurityHeaders]
    [AllowAnonymous]
    public class ExternalController : Controller
    {
        public async Task<IActionResult> Callback()
        {
            var result = await HttpContext.AuthenticateAsync(IdentityServer4.IdentityServerConstants.ExternalCookieAuthenticationScheme);
            var returnUrl = result.Properties.Items["returnUrl"] ?? "~/";

            var (user, provider, providerUserId, claims) = await FindUserFromExternalProvider(result);
            if (user == null)
            {
                // user = AutoProvisionUser(provider, providerUserId, claims);
                
                var returnUrlAfterRegistration = Url.Action("Callback", new { returnUrl = returnUrl });
                var continueWithUrl = Url.Action("RegisterUser", "UserRegistration" ,
                    new { returnUrl = returnUrlAfterRegistration, provider = provider, providerUserId = providerUserId });
                return Redirect(continueWithUrl);
            }
اگر خروجی متد FindUserFromExternalProvider آن null باشد، یعنی کاربری که از سمت تامین کننده‌ی هویت خارجی/گوگل به برنامه‌ی ما وارد شده‌است، دارای اکانتی در سمت IDP نیست. به همین جهت او را به سمت صفحه‌ی ثبت نام کاربر هدایت می‌کنیم.
در اینجا نحوه‌ی اصلاح اکشن متد Callback را جهت هدایت یک کاربر جدید به صفحه‌ی ثبت نام و تکمیل اطلاعات مورد نیاز IDP را مشاهده می‌کنید.
returnUrl ارسالی به اکشن متد RegisterUser، به همین اکشن متد جاری اشاره می‌کند. یعنی کاربر پس از تکمیل اطلاعات و اینبار نال نبودن user او، گردش کاری جاری را ادامه خواهد داد و به برنامه با این هویت جدید وارد می‌شود.


اتصال کاربر وارد شده‌ی از طریق یک IDP خارجی به اکانتی که هم اکنون در سطح IDP ما موجود است

تا اینجا اگر کاربری از طریق یک IDP خارجی به برنامه وارد شود، او را به صفحه‌ی ثبت نام کاربر هدایت کرده و پس از دریافت اطلاعات او، اکانت خارجی او را به اکانتی جدید که در IDP خود ایجاد می‌کنیم، متصل خواهیم کرد. به همین جهت بار دومی که این کاربر به همین ترتیب وارد سایت می‌شود، دیگر صفحه‌ی ثبت نام و تکمیل اطلاعات را مشاهده نمی‌کند. اما ممکن است کاربری که برای اولین بار از طریق یک IDP خارجی به سایت ما وارد شده‌است، هم اکنون دارای یک اکانت دیگری در سطح IDP ما باشد؛ در اینجا فقط اتصالی بین این دو صورت نگرفته‌است. بنابراین در این حالت بجای ایجاد یک اکانت جدید، بهتر است از همین اکانت موجود استفاده کرد و صرفا اتصال UserLogins او را تکمیل نمود.
به همین جهت ابتدا نیاز است لیست Claims بازگشتی از گوگل را بررسی کنیم:
var (user, provider, providerUserId, claims) = await FindUserFromExternalProvider(result);  
foreach (var claim in claims)
{
   _logger.LogInformation($"External provider[{provider}] info-> claim:{claim.Type}, value:{claim.Value}");
}
در اینجا پس از فراخوانی FindUserFromExternalProvider، لیست Claims بازگشت داده شده‌ی توسط IDP خارجی را لاگ می‌کنیم که در حالت استفاده‌ی از گوگل چنین خروجی را دارد:
External provider[Google] info-> claim:http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name, value:Vahid N.
External provider[Google] info-> claim:http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname, value:Vahid
External provider[Google] info-> claim:http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname, value:N.
External provider[Google] info-> claim:urn:google:profile, value:https://plus.google.com/105013528531611201860
External provider[Google] info-> claim:http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress, value:my.name@gmail.com
بنابراین اگر بخواهیم بر اساس این claims بازگشتی از گوگل، کاربر جاری در بانک اطلاعاتی خود را بیابیم، فقط کافی است اطلاعات claim مخصوص emailaddress آن‌را مورد استفاده قرار دهیم:
        [HttpGet]
        public async Task<IActionResult> Callback()
        {
            // ...

            var (user, provider, providerUserId, claims) = await FindUserFromExternalProvider(result);
            if (user == null)
            {
                // user wasn't found by provider, but maybe one exists with the same email address?  
                if (provider == "Google")
                {
                    // email claim from Google
                    var email = claims.FirstOrDefault(c =>
                        c.Type == "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress");
                    if (email != null)
                    {
                        var userByEmail = await _usersService.GetUserByEmailAsync(email.Value);
                        if (userByEmail != null)
                        {
                            // add Google as a provider for this user
                            await _usersService.AddUserLoginAsync(userByEmail.SubjectId, provider, providerUserId);

                            // redirect to ExternalLoginCallback
                            var continueWithUrlAfterAddingUserLogin =
                                Url.Action("Callback", new {returnUrl = returnUrl});
                            return Redirect(continueWithUrlAfterAddingUserLogin);
                        }
                    }
                }


                var returnUrlAfterRegistration = Url.Action("Callback", new {returnUrl = returnUrl});
                var continueWithUrl = Url.Action("RegisterUser", "UserRegistration",
                    new {returnUrl = returnUrlAfterRegistration, provider = provider, providerUserId = providerUserId});
                return Redirect(continueWithUrl);
            }
در اینجا ابتدا بررسی شده‌است که آیا کاربر جاری واکشی شده‌ی از بانک اطلاعاتی نال است؟ اگر بله، اینبار بجای هدایت مستقیم او به صفحه‌ی ثبت کاربر و تکمیل مشخصات او، مقدار email این کاربر را از لیست claims بازگشتی او از طرف گوگل، استخراج می‌کنیم. سپس بر این اساس اگر کاربری در بانک اطلاعاتی وجود داشت، تنها اطلاعات تکمیلی UserLogin او را که در اینجا خالی است، به اکانت گوگل او متصل می‌کنیم. به این ترتیب دیگر کاربر نیازی نخواهد داشت تا به صفحه‌ی ثبت اطلاعات تکمیلی هدایت شود و یا اینکه بی‌جهت رکورد User جدیدی را مخصوص او به بانک اطلاعاتی اضافه کنیم.



کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید.
برای اجرای برنامه:
- ابتدا به پوشه‌ی src\WebApi\ImageGallery.WebApi.WebApp وارد شده و dotnet_run.bat آن‌را اجرا کنید تا WebAPI برنامه راه اندازی شود.
- سپس به پوشه‌ی src\IDP\DNT.IDP مراجعه کرده و و dotnet_run.bat آن‌را اجرا کنید تا برنامه‌ی IDP راه اندازی شود.
- در آخر به پوشه‌ی src\MvcClient\ImageGallery.MvcClient.WebApp وارد شده و dotnet_run.bat آن‌را اجرا کنید تا MVC Client راه اندازی شود.
اکنون که هر سه برنامه در حال اجرا هستند، مرورگر را گشوده و مسیر https://localhost:5001 را درخواست کنید. در صفحه‌ی login نام کاربری را User 1 و کلمه‌ی عبور آن‌را password وارد کنید.
نظرات مطالب
آشنایی با NHibernate - قسمت اول
@DotNetCoders
سورس اصلی کتابخانه، به زبان سی شارپ است اما نهایتا شما از اسمبلی‌های کامپایل شده مربوطه استفاده خواهید کرد و از اینجا به بعد دیگر تفاوتی نمی‌کند که زبان دات نتی مورد استفاده چی باشد.

@dadoo
باید دقت داشته باشید که LINQ به تنهایی فقط یک language feature است و نه یک data access technology . بنابراین باید دقیقا linq to sql یا linq to entities را مشخص کرد.
سابقه نزدیک به یک دهه پروژه اصلی Hibernate‌ که توسط جاوا کارها توسعه داده شده، در این فریم ورک لحاظ شده که از هر لحاظ نسبت به LINQ to entities اون رو پخته‌تر کرده. ضمنا پروایدر LINQ هم برای NH اخیرا توسعه داده شده و از این لحاظ کم و کسری ندارد.
linq to sql برای اس کیوال سرور توسعه داده شد. بعد مایکروسافت اومد اون رو با linq to entities تکمیل کرد (البته linq to sql مطابق وبلاگ رسمی برنامه نویس‌های MS هنوز هم توسعه پیدا می‌کنه و در دات 4 شاهد اون خواهیم بود) و توسط linq to entities امکان استفاده از سایر دیتابیس‌ها هم فراهم شده البته اگر پروایدر آن موجود باشد که تعدادی از آن‌ها هم تجاری هستند. اما با NH این مشکل رو ندارید چون تقریبا همه نوع دیتابیس معروفی را ساپورت می‌کند و رایگان هم هست.
learning curve مربوط به NH بیشتر است از سایر orm ها.
NH از دات نت فریم 2 به بعد را پشتیبانی می‌کند اما linq to entities فقط از دات نت فریم ورک سه و نیم سرویس پک یک به بعد به صورت کامل در دسترس است.

در کل در گوگل nhibernate vs linq را جستجو کنید.

@LoveAjax
محیط مدیوم تراست، امکان ریفلکشن رو حذف می‌کنه و این مورد برای NH و تمام ORM های دیگر نیز مساله ساز خواهد بود. اما برای NH راه حل دارد مطابق مستندات آن:
http://nhforge.org/wikis/howtonh/run-in-medium-trust.aspx