This unexpected app port is arrives courtesy of the Uno Platform. They took the source code for Windows’s default Calculator app (which Microsoft made open source last year) and got it up and running on Linux desktops.
اشتراکها
همانطور که در قسمت قبل اشاره شد، توابع نیز یکی از ویژگیهای اصلی PowerShell هستند. قبل از بررسی بیشتر توابع بهتر است ابتدا با مفهوم script block آشنا شویم. script blocks به مجموعهایی از دستورات گفته میشود که داخل یک بلاک قرار میگیرند. در واقع هر چیزی داخل {} یک script block محسوب میشود (البته به جز hash tables). به عنوان مثال در کد زیر از یک script block مخصوص، با نام فیلتر استفاده شده است که یک ورودی برای پارامتر FilterScript مربوط به دستور Where-Object میباشد. چیزی که این script block را متمایز میکند، خروجی آن است. به این معنا که خروجی آن باید یک مقدار بولین باشد:
script blocks را به صورت مستقیم درون command line هم میتوانیم استفاده کنیم. به محض تایپ کردن } و زدن کلید enter، امکان نوشتن اسکریپتهای چندخطی را درون ترمینال خواهیم داشت. در نهایت با بستن script block و زدن کلید enter، از بلاک خارج خواهیم شد:
با اینکار یک بلاک از کد را داخل متغیری با اسم block ذخیره کردهایم. برای فراخوانی این قطعه کد میتوانیم از یک عملگر مخصوص با نام invocation operator یا call operator استفاده کنیم:
یا حتی میتوانیم از Invoke-Command نیز برای اجرای بلاک استفاده کنیم. همچنین از عملگر & برای فراخوانی یک expression رشتهایی نیز میتوان استفاده کرد:
البته این نکته را در نظر داشته باشید که & قادر به پارز کردن (parse) یک expression نیست. به عنوان مثال اجرای کد زیر با خطا مواجه خواهد شد (برای حل این مشکل میتوانید بجای آن از Invoke-Expression استفاده کنید که امکان پارز کردن پارامترها را نیز دارد):
اما با کمک advanced functions میتوانیم چنین قابلیتی را داشته باشیم:
یکی دیگر از ویژگیهای advanced functions امکان استفاده فلگ Verbose حین فراخوانی دستورات میباشد. به عنوان مثال قطعه کد زیر را در نظر بگیرید:
کاری که تابع فوق انجام میدهد، دریافت دیتای پیشبینی وضعیت آبوهوای یک شهر است. در حالت عادی فراخوانی تابع فوق پیامهای Verbose را نمایش نمیدهد. از آنجائیکه تابع فوق یک advanced function است، میتوانیم فلگ Verbose را نیز وارد کنیم. با اینکار به صورت صریح گفتهایم که پیامهای از نوع Verbose را نیز نمایش دهد:
در ادامه یک مثال از نحوه هندل کردن ورودیهای یک تابع را بررسی خواهیم کرد. تابع زیر یک لیست از URLها را از کاربر دریافت کرده و یک health check توسط دستور Test-Connection انجام میدهد. در کد زیر پارامتر Websites را با تعدادی اتریبیوت مزین کردهایم. توسط اتریبیوت Parameter تعیین کردهایم که ورودی الزامی است و همچنین مقدار آن میتواند از pipeline نیز دریافت شود. در ادامه توسط ValidatePattern یک عبارت باقاعده را برای بررسی صحیح بودن URL دریافتی نوشتهایم. از آنجائیکه ورودی از نوع آرایهایی از string تعریف شده است، این تست برای هر آیتم از آرایه بررسی خواهد شد. برای پارامتر دوم یعنی Count نیز رنج مقداری را که کاربر وارد میکند، حداقل ۳ و حداکثر ۳ انتخاب کردهایم:
یکی دیگر از اعتبارسنجیهایی که میتوانیم برای پارامترهای یک تابع انتخاب کنیم، ValidateScript است. توسط این اتریبیوت میتوانیم یک منطق سفارشی برای اعتبارسنجی مقادیر پارامترها بنویسیم. به عنوان مثال تابع فوق را به گونهایی تغییر خواهیم داد که لیست وبسایتها را از طریق یک فایل JSON دریافت کند. میخواهیم قبل از دریافت فایل مطمئن شویم که فایل، به صورت فیزیکی روی دیسک وجود دارد، در غیراینصورت باید یک خطا را به کاربر نمایش دهیم:
تابع Ping-Website را جهت بررسی فیچر جدیدی که همراه با دستور ForEach-Object استفاده میشود، تغییر دادهایم تا به صورت Parallel عمل کند؛ این قابلیت از نسخه ۷ به بعد به PowerShell اضافه شده است. از آنجائیکه این قابلیت باعث میشود script block مربوط به ForEach-Object درون یک context دیگر با نام runspace اجرا شود. در نتیجه برای دسترسی به متغیرهای بیرون از script block نیاز خواهیم داشت از یک متغیر خودکار تحتعنوان using قبل از نام متغیر و بعد از علامت $ استفاده کنیم. همچنین آرایه مثال قبل را نیز به ArrayList تغییر دادهایم. زیرا در حالت قبلی امکان تغییر سایز یک آرایه با سایز ثابت را نخواهیم داشت. نکته دیگری که در مورد کد فوق میتوان به آن توجه کرد، نال کردن خروجی متد Add مربوط به آرایهی Results است. همانطور که در قسمت قبل توضیح دادیم، از این تکنیک برای suppress کردن خروجی استفاده میکنیم و چون در اینجا خروجی متد Add یک عدد میباشد، با تکنیک فوق، خروجی را دیگر درون کنسول مشاهده نخواهیم کرد. توسط اتریبیوت Alias نیز نامهای دیگری را که میتوان برای پارامتر Path حین فراخوانی تابع استفاده کرد، تعیین کردهایم. لیست کامل اتریبیوتهایی را که میتوان برای پارامترهای یک تابع تعیین کرد، میتوانید در مستندات PowerShell ببینید.
خروجی بلاک فوق Some variable value: 20 خواهد بود؛ زیرا قبل از فراخوانی doSomeWork مقدار متغیر عددی someVariable را به ۲۰ تغییر دادهایم. برای script blocks این امکان را داریم که دقیقاً در همان جایی که بلاک را تعریف میکنیم، یک snapshot تهیه کنیم. در اینحالت خروجی، مقدار Some variable value: 10 خواهد شد:
یکسری بلاکهای ویژه نیز درون توابع و script blockها میتوانیم بنویسیم که اصطلاحاً به name blocks معروف هستند:
دلیل آن نیز این است که به صورت صریح کدها را درون بلاک process ننوشته بودیم. همانطور که عنوان شد، در حالت پیشفرض، بدنه توابع درون بلاک end قرار خواهند گرفت و تنها یکبار اجرا خواهند شد. بنابراین:
برای اینکار میتوانیم با کمک dynamic param یک پارامتر را در زمان اجرا ایجاده کرده و مقادیری را که کاربر برای ستونها مجاز است وارد کند، براساس هدر فایل CSV تنظیم کنیم:
درون کنسول PowerShell هم یک IntelliSense برای مقادیر مجاز نمایش داده خواهد شد:
Get-Process | Where-Object { $_.Name -eq 'Dropbox' }
PS /Users/sirwanafifi/Desktop> $block = { >> $newVar = 10 >> Write-Host $newVar >> }
PS /Users/sirwanafifi/Desktop> & $block
PS /Users/sirwanafifi/Desktop> & "Get-Process"
PS /Users/sirwanafifi/Desktop> & "1 + 1" or PS /Users/sirwanafifi/Desktop> & "Get-Process -Name Slack"
توابع
در قسمت قبل با نحوه ایجاد توابع آشنا شدیم. به این نوع توابع، basic functions گفته میشود و سادهترین نوع توابع در PowerShell هستند. همچنین خیلی محدود نیز میباشند؛ یکسری ورودی/خروجی دارند. برای کنترل بیشتر روی نحوه فراخوانی توابع (به عنوان مثال دریافت ورودی از pipeline و…) باید از advanced functions یا توابع پیشرفته استفاده کنیم. در واقع به محض استفاده از اتریبیوتی با نام [()CmdletBinding] تابع ما تبدیل به یک advanced function خواهد شد. منظور از دریافت ورودی از pipeline این است که بتوانیم خروجی دستورات را به تابعمان pipe کنیم اینکار در basic function امکانپذیر نیست: Function Add-Something { Write-Host "$_ World" } "Hello" | Add-Something
Function Add-Something { [CmdletBinding()] Param( [Parameter(ValueFromPipeline = $true)] [string]$Name ) Write-Host "$Name World" } "Hello" | Add-Something
$API_KEY = "...." Function Read-WeatherData { [CmdletBinding()] Param( [Parameter(ValueFromPipeline = $true)] [string]$CityName ) $Url = "https://api.openweathermap.org/data/2.5/forecast?q=$CityName&cnt=40&appid=$API_KEY&units=metric" Try { Write-Verbose "Reading weather data for $CityName" $Response = Invoke-RestMethod -Uri $Url $Response.list | ForEach-Object { Write-Verbose "Processing $($_.dt_txt)" [PSCustomObject]@{ City = $Response.city.name DateTime = [DateTime]::Parse($_.dt_txt) Temperature = $_.main.temp Humidity = $_.main.humidity Pressure = $_.main.pressure WindSpeed = $_.wind.speed WindDirection = $_.wind.deg Cloudiness = $_.clouds.all Weather = $_.weather.main WeatherDescription = $_.weather.description } } | Where-Object { $_.DateTime.Date -eq (Get-Date).Date } Write-Verbose "Done processing $CityName" } Catch { Write-Error $_.Exception.Message } }
Read-WeatherData -CityName "London" -Verbose
هر چند این مقدار را همانطور که در قسمتهای قبلی عنوان شد میتوانیم تغییر دهیم که دیگر مجبور نباشیم با فراخوانی هر تابع، این فلگ را نیز ارسال کنیم. بیشتر دستورات native نیز قابلیت نمایش پیامهای Verbose را با ارسال همین فلگ در اختیارمان قرار میدهند. بنابراین بهتر است برای امکان مشاهده جزئیات بیشتر حین فراخوانی توابعمان از Write-Verbose استفاده کنیم. در ادامه اجزای دیگر توابع را بررسی خواهیم کرد (بیشتر این اجزا درون یک script block نیز قابل استفاده هستند)
کنترل کامل بر روی ورودیهای توابع
بر روی ورودیهای یک تابع میتوانیم کنترل نسبتاً کاملی داشتیم باشیم. PowerShell یک مجموعه وسیع از قابلیتها را برای هندل کردن پارامترها و همچنین اعتبارسنجی ورودیها ارائه میدهد. به عنوان مثال میتوانیم یک پارامتر را mandatory کنیم یا اینکه امکان positional binding و غیره را تعیین کنیم. اتریبیوت Parameter در واقع یک وهله از System.Management.Automation.ParameterAttribute میباشد. میتوانید با نوشتن دستور زیر لیستی از خواصی را که میتوانید همراه با این اتریبیوت تعیین کنید، مشاهده کنید:
PS /> [Parameter]::new() ExperimentName : ExperimentAction : None Position : -2147483648 ParameterSetName : __AllParameterSets Mandatory : False ValueFromPipeline : False ValueFromPipelineByPropertyName : False ValueFromRemainingArguments : False HelpMessage : HelpMessageBaseName : HelpMessageResourceId : DontShow : False TypeId : System.Management.Automation.ParameterAttribute
Function Ping-Website { [CmdletBinding()] Param( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [ValidatePattern('^www\..*')] [string[]]$Websites, [ValidateRange(1, 3)] [int]$Count = 3 ) $Results = @() $Websites | ForEach-Object { $Website = $_ $Result = Test-Connection -ComputerName $Website -Count $Count -Quiet $ResultText = $Result ? 'Success' : 'Failed' $Results += @{ Website = $Website Result = $ResultText } Write-Verbose "The result of pinging $Website is $ResultText" } $Results | ForEach-Object { $_ | Select-Object @{ Name = "Website"; Expression = { $_.Website }; }, @{ Name = "Result"; Expression = { $_.Result }; }, @{ Name = "Number Of Attempts"; Expression = { $Count }; } } }
Function Ping-Website { [CmdletBinding()] Param( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [ValidateScript({ If (-Not ($_ | Test-Path) ) { Throw "File or folder does not exist" } If (-Not ($_ | Test-Path -PathType Leaf) ) { Throw "The Path argument must be a file. Folder paths are not allowed." } If ($_ -NotMatch "(\.json)$") { throw "The file specified in the path argument must be either of type json" } Return $true })] [Alias("src", "source", "file")] [System.IO.FileInfo]$Path, [int]$Count = 1 ) $Results = [System.Collections.ArrayList]@() $Urls = Get-Content -Path $Path | ConvertFrom-Json $Urls | ForEach-Object -Parallel { $Website = $_.url $Result = Test-Connection -ComputerName $Website -Count $using:Count -Quiet $ResultText = $Result ? 'Success' : 'Failed' $Item = @{ Website = $Website Result = $ResultText } $null = ($using:Results).Add($Item) } $Results | ForEach-Object -Parallel { $_ | Select-Object @{ Name = "Website"; Expression = { $_.Website }; }, @{ Name = "Result"; Expression = { $_.Result }; }, @{ Name = "Number Of Attempts"; Expression = { $using:Count }; } } }
نکته: اگر تابع فوق را همراه با فلگ Verbose فراخوانی کنیم، لاگهای موردنظر را درون کنسول مشاهده نخواهیم کرد؛ زیرا همانطور که اشاره شد script block درون یک context جدا اجرا میشود و باید متغیرهای خودکار مربوط به Output را مجدداً مقداردهی کنیم:
Function Ping-Website { [CmdletBinding()] Param( # As before ) # As before $Urls | ForEach-Object -Parallel { $DebugPreference = $using:DebugPreference $VerbosePreference = $using:VerbosePreference $InformationPreference = $using:InformationPreference # As before } # As before }
قابلیت تعریف بلاکها/توابع، به صورت تودرتو
درون توابع و script block امکان نوشتن بلاکهای تودرتو را نیز داریم:
$scriptBlock = { $logOutput = { param($message) Write-Host $message } [int]$someVariable = 10 $doSomeWork = { & $logOutput -message "Some variable value: $someVariable" } $someVariable = 20 & $doSomeWork }
$scriptBlock = { $logOutput = { param($message) Write-Host $message } [int]$someVariable = 10 $doSomeWork = { & $logOutput -message "Some variable value: $someVariable" }.GetNewClosure() $someVariable = 20 & $doSomeWork }
begin process end dynamicparam
درون یک تابع اگر هیچکدام از بلاکهای فوق استفاده نشود، به صورت پیشفرض بدنه تابع، درون بلاک end قرار خواهد گرفت. بلاک begin قبل از شروع pipeline اجرا میشود. process به ازای هر آیتم pipe شده اجرا خواهد شد. end نیز در پایان اجرا میشود. به عنوان مثال تابع زیر را در نظر بگیرید:
function Show-Pipeline { begin { Write-Host "Pipeline start" } process { Write-Host "Pipeline process $_" } end { Write-Host "Pipeline end $_" } }
در ادامه یکسری آیتم را به ورودی این تابع pipe خواهیم کرد:
PS /> 1..2 | Show-Pipeline Pipeline start Pipeline process 1 Pipeline process 2 Pipeline end 2
همانطور که مشاهده میکنید، به ازای هر آیتم pipe شده، یکبار بلاک process اجرا شده است. همچنین برای دسترسی به مقدار آیتم pipe شده نیز از متغیر خودکار _$ استفاده کردهایم (PSItem$ نیز به همین متغیر اشاره دارد).
با توجه به توضیحات named blockهای فوق، اکنون اگر بخواهیم نسخه اول تابع Ping-Website را با pipe کردن یک آرایه فراخوانی کنیم، خروجی که در کنسول نمایش داده خواهد شد، تنها آیتم آخر از آرایه خواهد بود:
PS /> "www.google.com", "www.yahoo.com" | Ping-Website Website Result Number Of Attempts ------- ------ ------------------ www.yahoo.com Success 3
Function Ping-Website { [CmdletBinding()] Param( # As before ) process { # As before } }
اینبار اگر تابع را مجدداً فراخوانی کنیم، خروجی مطلوب را نمایش خواهد داد:
PS /> "www.google.com", "www.yahoo.com" | Ping-Website Website Result Number Of Attempts ------- ------ ------------------ www.google.com Success 3 www.yahoo.com Success 3
بلاک dynamicparam
از این بلاک برای تعریف پارامترهای داینامیک که به صورت on the fly نیاز هست ایجاد شوند، استفاده میشود. برای درک بهتر آن فرض کنید میخواهیم تابعی را بنویسیم که امکان خواندن یک فایل CSV را به ما میدهد. تا اینجای کار توسط Import-CSV به یک خط دستور قابل انجام است. اما فرض کنید میخواهیم به کاربر این امکان را بدهیم که یک ستون موردنظر از فایل را مشاهده کند. همچنین میخواهیم یک اعتبارسنجی هم روی نام ستونی که کاربر قرار است وارد کند نیز داشته باشیم. به عنوان مثال یک فایل CSV با ستونهای name, lname, age داریم و کاربر میخواهد تنها ستون اول یک name را واکشی کند:
PS /> Read-Csv ./users.csv -Columns name
using namespace System.Management.Automation Function Read-Csv { Param ( [Parameter(Mandatory = $true, Position = 0)] [string]$Path ) DynamicParam { $firstLine = Get-Content $Path | Select-Object -First 1 [String[]]$headers = $firstLine -split ', ' $parameters = [RuntimeDefinedParameterDictionary]::new() $parameter = [RuntimeDefinedParameter]::new( 'Columns', [String[]], [Attribute[]]@( [Parameter]@{ Mandatory = $false; Position = 1 } [ValidateSet]::new($headers) ) ) $parameters.Add($parameter.Name, $parameter) Return $parameters } Begin { $csvContent = Import-Csv $Path If ($PSBoundParameters.ContainsKey('Columns')) { $columns = $PSBoundParameters['Columns'] $csvContent | Select-Object -Property $columns } Else { $csvContent } } }
اشتراکها
۳۰ روز با TDD
پروژهی ASP.NET Identity که نسل جدید سیستم Authentication و Authorization مخصوص ASP.NET است، دارای دو سری مثال رسمی است:
الف) مثالهای کدپلکس
ب) مثال نیوگت
در ادامه قصد داریم مثال نیوگت آنرا که مثال کاملی است از نحوهی استفاده از ASP.NET Identity در ASP.NET MVC، جهت اعمال الگوی واحد کار و تزریق وابستگیها، بازنویسی کنیم.
پیشنیازها
- برای درک مطلب جاری نیاز است ابتدا دورهی مرتبطی را در سایت مطالعه کنید و همچنین با نحوهی پیاده سازی الگوی واحد کار در EF Code First آشنا باشید.
- به علاوه فرض بر این است که یک پروژهی خالی ASP.NET MVC 5 را نیز آغاز کردهاید و توسط کنسول پاور شل نیوگت، فایلهای مثال Microsoft.AspNet.Identity.Samples را به آن افزودهاید:
ساختار پروژهی تکمیلی
همانند مطلب پیاده سازی الگوی واحد کار در EF Code First، این پروژهی جدید را با چهار اسمبلی class library دیگر به نامهای
AspNetIdentityDependencyInjectionSample.DataLayer
AspNetIdentityDependencyInjectionSample.DomainClasses
AspNetIdentityDependencyInjectionSample.IocConfig
AspNetIdentityDependencyInjectionSample.ServiceLayer
تکمیل میکنیم.
ساختار پروژهی AspNetIdentityDependencyInjectionSample.DomainClasses
مثال Microsoft.AspNet.Identity.Samples بر مبنای primary key از نوع string است. برای نمونه کلاس کاربران آنرا به نام ApplicationUser در فایل Models\IdentityModels.cs میتوانید مشاهده کنید. در مطلب جاری، این نوع پیش فرض، به نوع متداول int تغییر خواهد یافت. به همین جهت نیاز است کلاسهای ذیل را به پروژهی DomainClasses اضافه کرد:
در اینجا نحوهی تغییر primary key از نوع string را به نوع int، مشاهده میکنید. این تغییر نیاز به اعمال به کلاسهای کاربران و همچنین نقشهای آنها نیز دارد. به همین جهت صرفا تغییر کلاس ابتدایی ApplicationUser کافی نیست و باید کلاسهای فوق را نیز اضافه کرد و تغییر داد.
بدیهی است در اینجا کلاس پایه کاربران را میتوان سفارشی سازی کرد و خواص دیگری را نیز به آن افزود. برای مثال در اینجا یک کلاس جدید آدرس تعریف شدهاست که ارجاعی از آن در کلاس کاربران نیز قابل مشاهده است.
سایر کلاسهای مدلهای اصلی برنامه که جداول بانک اطلاعاتی را تشکیل خواهند داد نیز در آینده به همین اسمبلی DomainClasses اضافه میشوند.
ساختار پروژهی AspNetIdentityDependencyInjectionSample.DataLayer جهت اعمال الگوی واحد کار
اگر به همان فایل Models\IdentityModels.cs ابتدایی پروژه که اکنون کلاس ApplicationUser آنرا به پروژهی DomainClasses منتقل کردهایم، مجددا مراجعه کنید، کلاس DbContext مخصوص ASP.NET Identity نیز در آن تعریف شدهاست:
این کلاس را به پروژهی DataLayer منتقل میکنیم و از آن به عنوان DbContext اصلی برنامه استفاده خواهیم کرد. بنابراین دیگر نیازی نیست چندین DbContext در برنامه داشته باشیم. IdentityDbContext، در اصل از DbContext مشتق شدهاست.
اینترفیس IUnitOfWork برنامه، در پروژهی DataLayer چنین شکلی را دارد که نمونهای از آنرا در مطلب آشنایی با نحوهی پیاده سازی الگوی واحد کار در EF Code First، پیشتر ملاحظه کردهاید.
اکنون کلاس ApplicationDbContext منتقل شده به DataLayer یک چنین امضایی را خواهد یافت:
تعریف آن باید جهت اعمال کلاسهای سفارشی سازی شدهی کاربران و نقشهای آنها برای استفاده از primary key از نوع int به شکل فوق، تغییر یابد. همچنین در انتهای آن مانند قبل، IUnitOfWork نیز ذکر شدهاست. پیاده سازی کامل این کلاس را از پروژهی پیوست انتهای بحث میتوانید دریافت کنید.
کار کردن با این کلاس، هیچ تفاوتی با DbContextهای متداول EF Code First ندارد و تمام اصول آنها یکی است.
در ادامه اگر به فایل App_Start\IdentityConfig.cs مراجعه کنید، کلاس ذیل در آن قابل مشاهدهاست:
نیازی به این کلاس به این شکل نیست. آنرا حذف کنید و در پروژهی DataLayer، کلاس جدید ذیل را اضافه نمائید:
در این مثال، بحث migrations به حالت خودکار تنظیم شدهاست و تمام تغییرات در پروژهی DomainClasses را به صورت خودکار به بانک اطلاعاتی اعمال میکند. تا همینجا کار تنظیم DataLayer به پایان میرسد.
ساختار پروژهی AspNetIdentityDependencyInjectionSample.ServiceLayer
در ادامه مابقی کلاسهای موجود در فایل App_Start\IdentityConfig.cs را به لایه سرویس برنامه منتقل خواهیم کرد. همچنین برای آنها یک سری اینترفیس جدید نیز تعریف میکنیم، تا تزریق وابستگیها به نحو صحیحی صورت گیرد. اگر به فایلهای کنترلر این مثال پیش فرض مراجعه کنید (پیش از تغییرات بحث جاری)، هرچند به نظر در کنترلرها، کلاسهای موجود در فایل App_Start\IdentityConfig.cs تزریق شدهاند، اما به دلیل عدم استفاده از اینترفیسها، وابستگی کاملی بین جزئیات پیاده سازی این کلاسها و نمونههای تزریق شده به کنترلرها وجود دارد و عملا معکوس سازی واقعی وابستگیها رخ ندادهاست. بنابراین نیاز است این مسایل را اصلاح کنیم.
الف) انتقال کلاس ApplicationUserManager به لایه سرویس برنامه
کلاس ApplicationUserManager فایل App_Start\IdentityConfig.c را به لایه سرویس منتقل میکنیم:
تغییراتی که در اینجا اعمال شدهاند، به شرح زیر میباشند:
- متد استاتیک Create این کلاس حذف و تعاریف آن به سازندهی کلاس منتقل شدهاند. به این ترتیب با هربار وهله سازی این کلاس توسط IoC Container به صورت خودکار این تنظیمات نیز به کلاس پایه UserManager اعمال میشوند.
- اگر به کلاس پایه UserManager دقت کنید، به آرگومانهای جنریک آن یک int هم اضافه شدهاست. این مورد جهت استفاده از primary key از نوع int ضروری است.
- در کلاس پایه UserManager تعدادی متد وجود دارند. تعاریف آنها را به اینترفیس IApplicationUserManager منتقل خواهیم کرد. نیازی هم به پیاده سازی این متدها در کلاس جدید ApplicationUserManager نیست؛ زیرا کلاس پایه UserManager پیشتر آنها را پیاده سازی کردهاست. به این ترتیب میتوان به یک تزریق وابستگی واقعی و بدون وابستگی به پیاده سازی خاص UserManager رسید. کنترلری که با IApplicationUserManager بجای ApplicationUserManager کار میکند، قابلیت تعویض پیاده سازی آنرا جهت آزمونهای واحد خواهد یافت.
- در کلاس اصلی ApplicationDbInitializer پیش فرض این مثال، متد Seed هم قابل مشاهدهاست. این متد را از کلاس جدید Configuration اضافه شده به DataLayer حذف کردهایم. از این جهت که در آن از متدهای کلاس ApplicationUserManager مستقیما استفاده شدهاست. متد Seed اکنون به کلاس جدید اضافه شده به لایه سرویس منتقل شده و در آغاز برنامه فراخوانی خواهد شد. DataLayer نباید وابستگی به لایه سرویس داشته باشد. لایه سرویس است که از امکانات DataLayer استفاده میکند.
- اگر به سازندهی کلاس جدید ApplicationUserManager دقت کنید، چند اینترفیس دیگر نیز به آن تزریق شدهاند. اینترفیس IApplicationRoleManager را ادامه تعریف خواهیم کرد. سایر اینترفیسهای تزریق شده مانند IUserStore، IDataProtectionProvider و IIdentityMessageService جزو تعاریف اصلی ASP.NET Identity بوده و نیازی به تعریف مجدد آنها نیست. فقط کلاسهای EmailService و SmsService فایل App_Start\IdentityConfig.c را نیز به لایه سرویس منتقل کردهایم. این کلاسها بر اساس تنظیمات IoC Container مورد استفاده، در اینجا به صورت خودکار ترزیق خواهند شد. حالت پیش فرض آن، وهله سازی مستقیم است که مطابق کدهای فوق به حالت تزریق وابستگیها بهبود یافتهاست.
ب) انتقال کلاس ApplicationSignInManager به لایه سرویس برنامه
کلاس ApplicationSignInManager فایل App_Start\IdentityConfig.c را نیز به لایه سرویس منتقل میکنیم.
در اینجا نیز اینترفیس جدید IApplicationSignInManager را برای مخفی سازی پیاده سازی کلاس پایه توکار SignInManager، اضافه کردهایم. این اینترفیس دقیقا حاوی تعاریف متدهای کلاس پایه SignInManager است و نیازی به پیاده سازی مجدد در کلاس ApplicationSignInManager نخواهد داشت.
ج) انتقال کلاس ApplicationRoleManager به لایه سرویس برنامه
کلاس ApplicationRoleManager فایل App_Start\IdentityConfig.c را نیز به لایه سرویس منتقل خواهیم کرد:
روش کار نیز در اینجا همانند دو کلاس قبل است. اینترفیس جدید IApplicationRoleManager را که حاوی تعاریف متدهای کلاس پایه توکار RoleManager است، به لایه سرویس اضافه میکنیم. کنترلرهای برنامه با این اینترفیس بجای استفاده مستقیم از کلاس ApplicationRoleManager کار خواهند کرد.
تا اینجا کار تنظیمات لایه سرویس برنامه به پایان میرسد.
ساختار پروژهی AspNetIdentityDependencyInjectionSample.IocConfig
پروژهی IocConfig جایی است که تنظیمات StructureMap را به آن منتقل کردهایم:
در اینجا نحوهی اتصال اینترفیسهای برنامه را به کلاسها و یا نمونههایی که آنها را میتوانند پیاده سازی کنند، مشاهده میکنید. برای مثال IUnitOfWork به ApplicationDbContext مرتبط شدهاست و یا دوبار تعاریف متناظر با DbContext را مشاهده میکنید. از این تعاریف به صورت توکار توسط ASP.NET Identity زمانیکه قرار است UserStore و RoleStore را وهله سازی کند، استفاده میشوند و ذکر آنها الزامی است.
در تعاریف فوق یک مورد را به فایل Startup.cs موکول کردهایم. برای مشخص سازی نمونهی پیاده سازی کنندهی IDataProtectionProvider نیاز است به IAppBuilder کلاس Startup برنامه دسترسی داشت. این کلاس آغازین Owin اکنون به نحو ذیل بازنویسی شدهاست و در آن، تنظیمات IDataProtectionProvider را به همراه وهله سازی CreatePerOwinContext مشاهده میکنید:
این تعاریف از فایل پیش فرض Startup.Auth.cs پوشهی App_Start دریافت و جهت کار با IoC Container برنامه، بازنویسی شدهاند.
تنظیمات برنامهی اصلی ASP.NET MVC، جهت اعمال تزریق وابستگیها
الف) ابتدا نیاز است فایل Global.asax.cs را به نحو ذیل بازنویسی کنیم:
در اینجا در متد setDbInitializer، نحوهی استفاده و تعریف فایل Configuration لایه Data را ملاحظه میکنید؛ به همراه متد آغاز بانک اطلاعاتی و اعمال تغییرات لازم به آن در ابتدای کار برنامه. همچنین ControllerFactory برنامه نیز به StructureMapControllerFactory تنظیم شدهاست تا کار تزریق وابستگیها به کنترلرهای برنامه به صورت خودکار میسر شود. در پایان کار هر درخواست نیز منابع Disposable رها میشوند.
ب) به پوشهی Models برنامه مراجعه کنید. در اینجا در هر کلاسی که Id از نوع string وجود داشت، باید تبدیل به نوع int شوند. چون primary key برنامه را به نوع int تغییر دادهایم. برای مثال کلاسهای EditUserViewModel و RoleViewModel باید تغییر کنند.
ج) اصلاح کنترلرهای برنامه جهت اعمال تزریق وابستگیها
اکنون اصلاح کنترلرها جهت اعمال تزریق وابستگیها سادهاست. در ادامه نحوهی تغییر امضای سازندههای این کنترلرها را جهت استفاده از اینترفیسهای جدید مشاهده میکنید:
پس از این تغییرات، فقط کافی است بجای خواص برای مثال RoleManager سابق از فیلدهای تزریق شده در کلاس، مثلا roleManager_ جدید استفاده کرد. امضای متدهای یکی است و تنها به یک search و replace نیاز دارد.
البته تعدادی اکشن متد نیز در اینجا وجود دارند که از string id استفاده میکنند. اینها را باید به int? Id تغییر داد تا با نوع primary key جدید مورد استفاده تطابق پیدا کنند.
کدهای کامل این مثال را از اینجا میتوانید دریافت کنید:
AspNetIdentityDependencyInjectionSample
معادل این پروژه جهت ASP.NET Core Identity : «سفارشی سازی ASP.NET Core Identity - قسمت اول - موجودیتهای پایه و DbContext برنامه »
الف) مثالهای کدپلکس
ب) مثال نیوگت
در ادامه قصد داریم مثال نیوگت آنرا که مثال کاملی است از نحوهی استفاده از ASP.NET Identity در ASP.NET MVC، جهت اعمال الگوی واحد کار و تزریق وابستگیها، بازنویسی کنیم.
پیشنیازها
- برای درک مطلب جاری نیاز است ابتدا دورهی مرتبطی را در سایت مطالعه کنید و همچنین با نحوهی پیاده سازی الگوی واحد کار در EF Code First آشنا باشید.
- به علاوه فرض بر این است که یک پروژهی خالی ASP.NET MVC 5 را نیز آغاز کردهاید و توسط کنسول پاور شل نیوگت، فایلهای مثال Microsoft.AspNet.Identity.Samples را به آن افزودهاید:
PM> Install-Package Microsoft.AspNet.Identity.Samples -Pre
ساختار پروژهی تکمیلی
همانند مطلب پیاده سازی الگوی واحد کار در EF Code First، این پروژهی جدید را با چهار اسمبلی class library دیگر به نامهای
AspNetIdentityDependencyInjectionSample.DataLayer
AspNetIdentityDependencyInjectionSample.DomainClasses
AspNetIdentityDependencyInjectionSample.IocConfig
AspNetIdentityDependencyInjectionSample.ServiceLayer
تکمیل میکنیم.
ساختار پروژهی AspNetIdentityDependencyInjectionSample.DomainClasses
مثال Microsoft.AspNet.Identity.Samples بر مبنای primary key از نوع string است. برای نمونه کلاس کاربران آنرا به نام ApplicationUser در فایل Models\IdentityModels.cs میتوانید مشاهده کنید. در مطلب جاری، این نوع پیش فرض، به نوع متداول int تغییر خواهد یافت. به همین جهت نیاز است کلاسهای ذیل را به پروژهی DomainClasses اضافه کرد:
using System.ComponentModel.DataAnnotations.Schema; using Microsoft.AspNet.Identity.EntityFramework; namespace AspNetIdentityDependencyInjectionSample.DomainClasses { public class ApplicationUser : IdentityUser<int, CustomUserLogin, CustomUserRole, CustomUserClaim> { // سایر خواص اضافی در اینجا [ForeignKey("AddressId")] public virtual Address Address { get; set; } public int? AddressId { get; set; } } } using System.Collections.Generic; namespace AspNetIdentityDependencyInjectionSample.DomainClasses { public class Address { public int Id { get; set; } public string City { get; set; } public string State { get; set; } public virtual ICollection<ApplicationUser> ApplicationUsers { set; get; } } } using Microsoft.AspNet.Identity.EntityFramework; namespace AspNetIdentityDependencyInjectionSample.DomainClasses { public class CustomRole : IdentityRole<int, CustomUserRole> { public CustomRole() { } public CustomRole(string name) { Name = name; } } } using Microsoft.AspNet.Identity.EntityFramework; namespace AspNetIdentityDependencyInjectionSample.DomainClasses { public class CustomUserClaim : IdentityUserClaim<int> { } } using Microsoft.AspNet.Identity.EntityFramework; namespace AspNetIdentityDependencyInjectionSample.DomainClasses { public class CustomUserLogin : IdentityUserLogin<int> { } } using Microsoft.AspNet.Identity.EntityFramework; namespace AspNetIdentityDependencyInjectionSample.DomainClasses { public class CustomUserRole : IdentityUserRole<int> { } }
بدیهی است در اینجا کلاس پایه کاربران را میتوان سفارشی سازی کرد و خواص دیگری را نیز به آن افزود. برای مثال در اینجا یک کلاس جدید آدرس تعریف شدهاست که ارجاعی از آن در کلاس کاربران نیز قابل مشاهده است.
سایر کلاسهای مدلهای اصلی برنامه که جداول بانک اطلاعاتی را تشکیل خواهند داد نیز در آینده به همین اسمبلی DomainClasses اضافه میشوند.
ساختار پروژهی AspNetIdentityDependencyInjectionSample.DataLayer جهت اعمال الگوی واحد کار
اگر به همان فایل Models\IdentityModels.cs ابتدایی پروژه که اکنون کلاس ApplicationUser آنرا به پروژهی DomainClasses منتقل کردهایم، مجددا مراجعه کنید، کلاس DbContext مخصوص ASP.NET Identity نیز در آن تعریف شدهاست:
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
اینترفیس IUnitOfWork برنامه، در پروژهی DataLayer چنین شکلی را دارد که نمونهای از آنرا در مطلب آشنایی با نحوهی پیاده سازی الگوی واحد کار در EF Code First، پیشتر ملاحظه کردهاید.
using System.Collections.Generic; using System.Data.Entity; namespace AspNetIdentityDependencyInjectionSample.DataLayer.Context { public interface IUnitOfWork { IDbSet<TEntity> Set<TEntity>() where TEntity : class; int SaveAllChanges(); void MarkAsChanged<TEntity>(TEntity entity) where TEntity : class; IList<T> GetRows<T>(string sql, params object[] parameters) where T : class; IEnumerable<TEntity> AddThisRange<TEntity>(IEnumerable<TEntity> entities) where TEntity : class; void ForceDatabaseInitialize(); } }
public class ApplicationDbContext : IdentityDbContext<ApplicationUser, CustomRole, int, CustomUserLogin, CustomUserRole, CustomUserClaim>, IUnitOfWork { public DbSet<Category> Categories { set; get; } public DbSet<Product> Products { set; get; } public DbSet<Address> Addresses { set; get; }
کار کردن با این کلاس، هیچ تفاوتی با DbContextهای متداول EF Code First ندارد و تمام اصول آنها یکی است.
در ادامه اگر به فایل App_Start\IdentityConfig.cs مراجعه کنید، کلاس ذیل در آن قابل مشاهدهاست:
public class ApplicationDbInitializer : DropCreateDatabaseIfModelChanges<ApplicationDbContext>
using System.Data.Entity.Migrations; namespace AspNetIdentityDependencyInjectionSample.DataLayer.Context { public class Configuration : DbMigrationsConfiguration<ApplicationDbContext> { public Configuration() { AutomaticMigrationsEnabled = true; AutomaticMigrationDataLossAllowed = true; } } }
ساختار پروژهی AspNetIdentityDependencyInjectionSample.ServiceLayer
در ادامه مابقی کلاسهای موجود در فایل App_Start\IdentityConfig.cs را به لایه سرویس برنامه منتقل خواهیم کرد. همچنین برای آنها یک سری اینترفیس جدید نیز تعریف میکنیم، تا تزریق وابستگیها به نحو صحیحی صورت گیرد. اگر به فایلهای کنترلر این مثال پیش فرض مراجعه کنید (پیش از تغییرات بحث جاری)، هرچند به نظر در کنترلرها، کلاسهای موجود در فایل App_Start\IdentityConfig.cs تزریق شدهاند، اما به دلیل عدم استفاده از اینترفیسها، وابستگی کاملی بین جزئیات پیاده سازی این کلاسها و نمونههای تزریق شده به کنترلرها وجود دارد و عملا معکوس سازی واقعی وابستگیها رخ ندادهاست. بنابراین نیاز است این مسایل را اصلاح کنیم.
الف) انتقال کلاس ApplicationUserManager به لایه سرویس برنامه
کلاس ApplicationUserManager فایل App_Start\IdentityConfig.c را به لایه سرویس منتقل میکنیم:
using System; using System.Security.Claims; using System.Threading.Tasks; using AspNetIdentityDependencyInjectionSample.DomainClasses; using AspNetIdentityDependencyInjectionSample.ServiceLayer.Contracts; using Microsoft.AspNet.Identity; using Microsoft.AspNet.Identity.Owin; using Microsoft.Owin.Security.Cookies; using Microsoft.Owin.Security.DataProtection; namespace AspNetIdentityDependencyInjectionSample.ServiceLayer { public class ApplicationUserManager : UserManager<ApplicationUser, int>, IApplicationUserManager { private readonly IDataProtectionProvider _dataProtectionProvider; private readonly IIdentityMessageService _emailService; private readonly IApplicationRoleManager _roleManager; private readonly IIdentityMessageService _smsService; private readonly IUserStore<ApplicationUser, int> _store; public ApplicationUserManager(IUserStore<ApplicationUser, int> store, IApplicationRoleManager roleManager, IDataProtectionProvider dataProtectionProvider, IIdentityMessageService smsService, IIdentityMessageService emailService) : base(store) { _store = store; _roleManager = roleManager; _dataProtectionProvider = dataProtectionProvider; _smsService = smsService; _emailService = emailService; createApplicationUserManager(); } public void SeedDatabase() { } private void createApplicationUserManager() { // Configure validation logic for usernames this.UserValidator = new UserValidator<ApplicationUser, int>(this) { AllowOnlyAlphanumericUserNames = false, RequireUniqueEmail = true }; // Configure validation logic for passwords this.PasswordValidator = new PasswordValidator { RequiredLength = 6, RequireNonLetterOrDigit = true, RequireDigit = true, RequireLowercase = true, RequireUppercase = true, }; // Configure user lockout defaults this.UserLockoutEnabledByDefault = true; this.DefaultAccountLockoutTimeSpan = TimeSpan.FromMinutes(5); this.MaxFailedAccessAttemptsBeforeLockout = 5; // Register two factor authentication providers. This application uses Phone and Emails as a step of receiving a code for verifying the user // You can write your own provider and plug in here. this.RegisterTwoFactorProvider("PhoneCode", new PhoneNumberTokenProvider<ApplicationUser, int> { MessageFormat = "Your security code is: {0}" }); this.RegisterTwoFactorProvider("EmailCode", new EmailTokenProvider<ApplicationUser, int> { Subject = "SecurityCode", BodyFormat = "Your security code is {0}" }); this.EmailService = _emailService; this.SmsService = _smsService; if (_dataProtectionProvider != null) { var dataProtector = _dataProtectionProvider.Create("ASP.NET Identity"); this.UserTokenProvider = new DataProtectorTokenProvider<ApplicationUser, int>(dataProtector); } } } }
- متد استاتیک Create این کلاس حذف و تعاریف آن به سازندهی کلاس منتقل شدهاند. به این ترتیب با هربار وهله سازی این کلاس توسط IoC Container به صورت خودکار این تنظیمات نیز به کلاس پایه UserManager اعمال میشوند.
- اگر به کلاس پایه UserManager دقت کنید، به آرگومانهای جنریک آن یک int هم اضافه شدهاست. این مورد جهت استفاده از primary key از نوع int ضروری است.
- در کلاس پایه UserManager تعدادی متد وجود دارند. تعاریف آنها را به اینترفیس IApplicationUserManager منتقل خواهیم کرد. نیازی هم به پیاده سازی این متدها در کلاس جدید ApplicationUserManager نیست؛ زیرا کلاس پایه UserManager پیشتر آنها را پیاده سازی کردهاست. به این ترتیب میتوان به یک تزریق وابستگی واقعی و بدون وابستگی به پیاده سازی خاص UserManager رسید. کنترلری که با IApplicationUserManager بجای ApplicationUserManager کار میکند، قابلیت تعویض پیاده سازی آنرا جهت آزمونهای واحد خواهد یافت.
- در کلاس اصلی ApplicationDbInitializer پیش فرض این مثال، متد Seed هم قابل مشاهدهاست. این متد را از کلاس جدید Configuration اضافه شده به DataLayer حذف کردهایم. از این جهت که در آن از متدهای کلاس ApplicationUserManager مستقیما استفاده شدهاست. متد Seed اکنون به کلاس جدید اضافه شده به لایه سرویس منتقل شده و در آغاز برنامه فراخوانی خواهد شد. DataLayer نباید وابستگی به لایه سرویس داشته باشد. لایه سرویس است که از امکانات DataLayer استفاده میکند.
- اگر به سازندهی کلاس جدید ApplicationUserManager دقت کنید، چند اینترفیس دیگر نیز به آن تزریق شدهاند. اینترفیس IApplicationRoleManager را ادامه تعریف خواهیم کرد. سایر اینترفیسهای تزریق شده مانند IUserStore، IDataProtectionProvider و IIdentityMessageService جزو تعاریف اصلی ASP.NET Identity بوده و نیازی به تعریف مجدد آنها نیست. فقط کلاسهای EmailService و SmsService فایل App_Start\IdentityConfig.c را نیز به لایه سرویس منتقل کردهایم. این کلاسها بر اساس تنظیمات IoC Container مورد استفاده، در اینجا به صورت خودکار ترزیق خواهند شد. حالت پیش فرض آن، وهله سازی مستقیم است که مطابق کدهای فوق به حالت تزریق وابستگیها بهبود یافتهاست.
ب) انتقال کلاس ApplicationSignInManager به لایه سرویس برنامه
کلاس ApplicationSignInManager فایل App_Start\IdentityConfig.c را نیز به لایه سرویس منتقل میکنیم.
using AspNetIdentityDependencyInjectionSample.DomainClasses; using AspNetIdentityDependencyInjectionSample.ServiceLayer.Contracts; using Microsoft.AspNet.Identity.Owin; using Microsoft.Owin.Security; namespace AspNetIdentityDependencyInjectionSample.ServiceLayer { public class ApplicationSignInManager : SignInManager<ApplicationUser, int>, IApplicationSignInManager { private readonly ApplicationUserManager _userManager; private readonly IAuthenticationManager _authenticationManager; public ApplicationSignInManager(ApplicationUserManager userManager, IAuthenticationManager authenticationManager) : base(userManager, authenticationManager) { _userManager = userManager; _authenticationManager = authenticationManager; } } }
ج) انتقال کلاس ApplicationRoleManager به لایه سرویس برنامه
کلاس ApplicationRoleManager فایل App_Start\IdentityConfig.c را نیز به لایه سرویس منتقل خواهیم کرد:
using AspNetIdentityDependencyInjectionSample.DomainClasses; using AspNetIdentityDependencyInjectionSample.ServiceLayer.Contracts; using Microsoft.AspNet.Identity; namespace AspNetIdentityDependencyInjectionSample.ServiceLayer { public class ApplicationRoleManager : RoleManager<CustomRole, int>, IApplicationRoleManager { private readonly IRoleStore<CustomRole, int> _roleStore; public ApplicationRoleManager(IRoleStore<CustomRole, int> roleStore) : base(roleStore) { _roleStore = roleStore; } public CustomRole FindRoleByName(string roleName) { return this.FindByName(roleName); // RoleManagerExtensions } public IdentityResult CreateRole(CustomRole role) { return this.Create(role); // RoleManagerExtensions } } }
تا اینجا کار تنظیمات لایه سرویس برنامه به پایان میرسد.
ساختار پروژهی AspNetIdentityDependencyInjectionSample.IocConfig
پروژهی IocConfig جایی است که تنظیمات StructureMap را به آن منتقل کردهایم:
using System; using System.Data.Entity; using System.Threading; using System.Web; using AspNetIdentityDependencyInjectionSample.DataLayer.Context; using AspNetIdentityDependencyInjectionSample.DomainClasses; using AspNetIdentityDependencyInjectionSample.ServiceLayer; using AspNetIdentityDependencyInjectionSample.ServiceLayer.Contracts; using Microsoft.AspNet.Identity; using Microsoft.AspNet.Identity.EntityFramework; using Microsoft.Owin.Security; using StructureMap; using StructureMap.Web; namespace AspNetIdentityDependencyInjectionSample.IocConfig { public static class SmObjectFactory { private static readonly Lazy<Container> _containerBuilder = new Lazy<Container>(defaultContainer, LazyThreadSafetyMode.ExecutionAndPublication); public static IContainer Container { get { return _containerBuilder.Value; } } private static Container defaultContainer() { return new Container(ioc => { ioc.For<IUnitOfWork>() .HybridHttpOrThreadLocalScoped() .Use<ApplicationDbContext>(); ioc.For<ApplicationDbContext>().HybridHttpOrThreadLocalScoped().Use<ApplicationDbContext>(); ioc.For<DbContext>().HybridHttpOrThreadLocalScoped().Use<ApplicationDbContext>(); ioc.For<IUserStore<ApplicationUser, int>>() .HybridHttpOrThreadLocalScoped() .Use<UserStore<ApplicationUser, CustomRole, int, CustomUserLogin, CustomUserRole, CustomUserClaim>>(); ioc.For<IRoleStore<CustomRole, int>>() .HybridHttpOrThreadLocalScoped() .Use<RoleStore<CustomRole, int, CustomUserRole>>(); ioc.For<IAuthenticationManager>() .Use(() => HttpContext.Current.GetOwinContext().Authentication); ioc.For<IApplicationSignInManager>() .HybridHttpOrThreadLocalScoped() .Use<ApplicationSignInManager>(); ioc.For<IApplicationUserManager>() .HybridHttpOrThreadLocalScoped() .Use<ApplicationUserManager>(); ioc.For<IApplicationRoleManager>() .HybridHttpOrThreadLocalScoped() .Use<ApplicationRoleManager>(); ioc.For<IIdentityMessageService>().Use<SmsService>(); ioc.For<IIdentityMessageService>().Use<EmailService>(); ioc.For<ICustomRoleStore>() .HybridHttpOrThreadLocalScoped() .Use<CustomRoleStore>(); ioc.For<ICustomUserStore>() .HybridHttpOrThreadLocalScoped() .Use<CustomUserStore>(); //config.For<IDataProtectionProvider>().Use(()=> app.GetDataProtectionProvider()); // In Startup class ioc.For<ICategoryService>().Use<EfCategoryService>(); ioc.For<IProductService>().Use<EfProductService>(); }); } } }
در تعاریف فوق یک مورد را به فایل Startup.cs موکول کردهایم. برای مشخص سازی نمونهی پیاده سازی کنندهی IDataProtectionProvider نیاز است به IAppBuilder کلاس Startup برنامه دسترسی داشت. این کلاس آغازین Owin اکنون به نحو ذیل بازنویسی شدهاست و در آن، تنظیمات IDataProtectionProvider را به همراه وهله سازی CreatePerOwinContext مشاهده میکنید:
using System; using AspNetIdentityDependencyInjectionSample.IocConfig; using AspNetIdentityDependencyInjectionSample.ServiceLayer.Contracts; using Microsoft.AspNet.Identity; using Microsoft.Owin; using Microsoft.Owin.Security.Cookies; using Microsoft.Owin.Security.DataProtection; using Owin; using StructureMap.Web; namespace AspNetIdentityDependencyInjectionSample { public class Startup { public void Configuration(IAppBuilder app) { configureAuth(app); } private static void configureAuth(IAppBuilder app) { SmObjectFactory.Container.Configure(config => { config.For<IDataProtectionProvider>() .HybridHttpOrThreadLocalScoped() .Use(()=> app.GetDataProtectionProvider()); }); SmObjectFactory.Container.GetInstance<IApplicationUserManager>().SeedDatabase(); // Configure the db context, user manager and role manager to use a single instance per request app.CreatePerOwinContext(() => SmObjectFactory.Container.GetInstance<IApplicationUserManager>()); // Enable the application to use a cookie to store information for the signed in user // and to use a cookie to temporarily store information about a user logging in with a third party login provider // Configure the sign in cookie app.UseCookieAuthentication(new CookieAuthenticationOptions { AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie, LoginPath = new PathString("/Account/Login"), Provider = new CookieAuthenticationProvider { // Enables the application to validate the security stamp when the user logs in. // This is a security feature which is used when you change a password or add an external login to your account. OnValidateIdentity = SmObjectFactory.Container.GetInstance<IApplicationUserManager>().OnValidateIdentity() } }); app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie); // Enables the application to temporarily store user information when they are verifying the second factor in the two-factor authentication process. app.UseTwoFactorSignInCookie(DefaultAuthenticationTypes.TwoFactorCookie, TimeSpan.FromMinutes(5)); // Enables the application to remember the second login verification factor such as phone or email. // Once you check this option, your second step of verification during the login process will be remembered on the device where you logged in from. // This is similar to the RememberMe option when you log in. app.UseTwoFactorRememberBrowserCookie(DefaultAuthenticationTypes.TwoFactorRememberBrowserCookie); } } }
تنظیمات برنامهی اصلی ASP.NET MVC، جهت اعمال تزریق وابستگیها
الف) ابتدا نیاز است فایل Global.asax.cs را به نحو ذیل بازنویسی کنیم:
using System; using System.Data.Entity; using System.Web; using System.Web.Mvc; using System.Web.Optimization; using System.Web.Routing; using AspNetIdentityDependencyInjectionSample.DataLayer.Context; using AspNetIdentityDependencyInjectionSample.IocConfig; using StructureMap.Web.Pipeline; namespace AspNetIdentityDependencyInjectionSample { public class MvcApplication : HttpApplication { protected void Application_Start() { AreaRegistration.RegisterAllAreas(); FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters); RouteConfig.RegisterRoutes(RouteTable.Routes); BundleConfig.RegisterBundles(BundleTable.Bundles); setDbInitializer(); //Set current Controller factory as StructureMapControllerFactory ControllerBuilder.Current.SetControllerFactory(new StructureMapControllerFactory()); } protected void Application_EndRequest(object sender, EventArgs e) { HttpContextLifecycle.DisposeAndClearAll(); } public class StructureMapControllerFactory : DefaultControllerFactory { protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType) { if (controllerType == null) throw new InvalidOperationException(string.Format("Page not found: {0}", requestContext.HttpContext.Request.RawUrl)); return SmObjectFactory.Container.GetInstance(controllerType) as Controller; } } private static void setDbInitializer() { Database.SetInitializer(new MigrateDatabaseToLatestVersion<ApplicationDbContext, Configuration>()); SmObjectFactory.Container.GetInstance<IUnitOfWork>().ForceDatabaseInitialize(); } } }
ب) به پوشهی Models برنامه مراجعه کنید. در اینجا در هر کلاسی که Id از نوع string وجود داشت، باید تبدیل به نوع int شوند. چون primary key برنامه را به نوع int تغییر دادهایم. برای مثال کلاسهای EditUserViewModel و RoleViewModel باید تغییر کنند.
ج) اصلاح کنترلرهای برنامه جهت اعمال تزریق وابستگیها
اکنون اصلاح کنترلرها جهت اعمال تزریق وابستگیها سادهاست. در ادامه نحوهی تغییر امضای سازندههای این کنترلرها را جهت استفاده از اینترفیسهای جدید مشاهده میکنید:
[Authorize] public class AccountController : Controller { private readonly IAuthenticationManager _authenticationManager; private readonly IApplicationSignInManager _signInManager; private readonly IApplicationUserManager _userManager; public AccountController(IApplicationUserManager userManager, IApplicationSignInManager signInManager, IAuthenticationManager authenticationManager) { _userManager = userManager; _signInManager = signInManager; _authenticationManager = authenticationManager; } [Authorize] public class ManageController : Controller { // Used for XSRF protection when adding external logins private const string XsrfKey = "XsrfId"; private readonly IAuthenticationManager _authenticationManager; private readonly IApplicationUserManager _userManager; public ManageController(IApplicationUserManager userManager, IAuthenticationManager authenticationManager) { _userManager = userManager; _authenticationManager = authenticationManager; } [Authorize(Roles = "Admin")] public class RolesAdminController : Controller { private readonly IApplicationRoleManager _roleManager; private readonly IApplicationUserManager _userManager; public RolesAdminController(IApplicationUserManager userManager, IApplicationRoleManager roleManager) { _userManager = userManager; _roleManager = roleManager; } [Authorize(Roles = "Admin")] public class UsersAdminController : Controller { private readonly IApplicationRoleManager _roleManager; private readonly IApplicationUserManager _userManager; public UsersAdminController(IApplicationUserManager userManager, IApplicationRoleManager roleManager) { _userManager = userManager; _roleManager = roleManager; }
البته تعدادی اکشن متد نیز در اینجا وجود دارند که از string id استفاده میکنند. اینها را باید به int? Id تغییر داد تا با نوع primary key جدید مورد استفاده تطابق پیدا کنند.
کدهای کامل این مثال را از اینجا میتوانید دریافت کنید:
AspNetIdentityDependencyInjectionSample
معادل این پروژه جهت ASP.NET Core Identity : «سفارشی سازی ASP.NET Core Identity - قسمت اول - موجودیتهای پایه و DbContext برنامه »
اشتراکها
PostgreSQL 17 منتشر شد
PostgreSQL 17 Released — The big one is here. It's the newest major version of Postgres, and it takes a bigger step forward than even v16. Some of what’s new:
- Overhauled memory management for vacuuming, resulting in significantly lower memory usage and running time. More on this here.
- Incremental backup support.
- Faster B-tree index scans.
- MERGE enhancements, including view support.
- New functions to extract elements from UUIDs.
- WAL improvements – up to 2x write throughput on some workloads.
- Improvements to SQL/JSON support, including JSON_TABLE.
- Bulk loading improvements and perf improvements for COPY which gains the ON_ERROR ignore option to ignore errors.
- Identity columns on partitioned tables.
PostgreSQL, or Postgres, is a relational database management system that provides an implementation of the SQL querying language. It’s standards-compliant and has many advanced features like reliable transactions and concurrency without read locks.
This guide demonstrates how to quickly get Postgres up and running on an Ubuntu 20.04 server, from installing PostgreSQL to setting up a new user and database. If you’d prefer a more in-depth tutorial on installing and managing a PostgreSQL database, see How To Install and Use PostgreSQL on Ubuntu 20.04.
نظرات اشتراکها
روش امن نگهداری پسورد کاربران
پیاده سازی روش گفته شده در این سایت :
/* * Password Hashing With PBKDF2 (http://crackstation.net/hashing-security.htm). * Copyright (c) 2013, Taylor Hornby * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ using System; using System.Text; using System.Security.Cryptography; namespace PasswordHash { /// <summary> /// Salted password hashing with PBKDF2-SHA1. /// Author: havoc AT defuse.ca /// www: http://crackstation.net/hashing-security.htm /// Compatibility: .NET 3.0 and later. /// </summary> public class PasswordHash { // The following constants may be changed without breaking existing hashes. public const int SALT_BYTE_SIZE = 24; public const int HASH_BYTE_SIZE = 24; public const int PBKDF2_ITERATIONS = 1000; public const int ITERATION_INDEX = 0; public const int SALT_INDEX = 1; public const int PBKDF2_INDEX = 2; /// <summary> /// Creates a salted PBKDF2 hash of the password. /// </summary> /// <param name="password">The password to hash.</param> /// <returns>The hash of the password.</returns> public static string CreateHash(string password) { // Generate a random salt RNGCryptoServiceProvider csprng = new RNGCryptoServiceProvider(); byte[] salt = new byte[SALT_BYTE_SIZE]; csprng.GetBytes(salt); // Hash the password and encode the parameters byte[] hash = PBKDF2(password, salt, PBKDF2_ITERATIONS, HASH_BYTE_SIZE); return PBKDF2_ITERATIONS + ":" + Convert.ToBase64String(salt) + ":" + Convert.ToBase64String(hash); } /// <summary> /// Validates a password given a hash of the correct one. /// </summary> /// <param name="password">The password to check.</param> /// <param name="correctHash">A hash of the correct password.</param> /// <returns>True if the password is correct. False otherwise.</returns> public static bool ValidatePassword(string password, string correctHash) { // Extract the parameters from the hash char[] delimiter = { ':' }; string[] split = correctHash.Split(delimiter); int iterations = Int32.Parse(split[ITERATION_INDEX]); byte[] salt = Convert.FromBase64String(split[SALT_INDEX]); byte[] hash = Convert.FromBase64String(split[PBKDF2_INDEX]); byte[] testHash = PBKDF2(password, salt, iterations, hash.Length); return SlowEquals(hash, testHash); } /// <summary> /// Compares two byte arrays in length-constant time. This comparison /// method is used so that password hashes cannot be extracted from /// on-line systems using a timing attack and then attacked off-line. /// </summary> /// <param name="a">The first byte array.</param> /// <param name="b">The second byte array.</param> /// <returns>True if both byte arrays are equal. False otherwise.</returns> private static bool SlowEquals(byte[] a, byte[] b) { uint diff = (uint)a.Length ^ (uint)b.Length; for (int i = 0; i < a.Length && i < b.Length; i++) diff |= (uint)(a[i] ^ b[i]); return diff == 0; } /// <summary> /// Computes the PBKDF2-SHA1 hash of a password. /// </summary> /// <param name="password">The password to hash.</param> /// <param name="salt">The salt.</param> /// <param name="iterations">The PBKDF2 iteration count.</param> /// <param name="outputBytes">The length of the hash to generate, in bytes.</param> /// <returns>A hash of the password.</returns> private static byte[] PBKDF2(string password, byte[] salt, int iterations, int outputBytes) { Rfc2898DeriveBytes pbkdf2 = new Rfc2898DeriveBytes(password, salt); pbkdf2.IterationCount = iterations; return pbkdf2.GetBytes(outputBytes); } } }
مطالب
Dependency Injection
در ادامه مباحث بهتر کد بنویسیم و الگوهایی که در این رابطه معرفی شدند، اخیرا کتابی از انتشارات manning منتشر شده تحت عنوان Dependency Injection . هر چند به ظاهر این کتاب برای جاوا کارها تهیه شده اما قسمت عمدهای از آن برای سایر زبانهای برنامه نویسی دیگر نیز قابل استفاده است.
DESCRIPTION
In object-oriented programming, a central program normally controls other objects in a module, library, or framework. With dependency injection, this pattern is inverted—a reference to a service is placed directly into the object which eases testing and modularity. Spring or Google Guice use dependency injection so you can focus on your core application and let the framework handle infrastructural concerns.
Dependency Injection explores the DI idiom in fine detail, with numerous practical examples that show you the payoffs. You'll apply key techniques in Spring and Guice and learn important pitfalls, corner-cases, and design patterns. Readers need a working knowledge of Java but no prior experience with DI is assumed.
WHAT'S INSIDE:
◊ How to apply it (Understand it first!)
◊ Design patterns and nuances
◊ Spring, Google Guice, PicoContainer, and more
◊ How to integrate DI with Java frameworks
In object-oriented programming, a central program normally controls other objects in a module, library, or framework. With dependency injection, this pattern is inverted—a reference to a service is placed directly into the object which eases testing and modularity. Spring or Google Guice use dependency injection so you can focus on your core application and let the framework handle infrastructural concerns.
Dependency Injection explores the DI idiom in fine detail, with numerous practical examples that show you the payoffs. You'll apply key techniques in Spring and Guice and learn important pitfalls, corner-cases, and design patterns. Readers need a working knowledge of Java but no prior experience with DI is assumed.
WHAT'S INSIDE:
◊ How to apply it (Understand it first!)
◊ Design patterns and nuances
◊ Spring, Google Guice, PicoContainer, and more
◊ How to integrate DI with Java frameworks
راستی، این کتاب تر و تازه رو میتونید از همین کتاب فروشیهای دور و اطراف نیز تهیه کنید! در سایت booktraining دات ارگ در قسمت graphics-and-design به تاریخ 4 آگوست.