اشتراک‌ها
کتاب رایگان Implementing Domain Driven Design

This is a practical guide for implementing Domain Driven Design (DDD). While the implementation details are based on the ABP Framework infrastructure, the basic concepts, principles and models can be applied to any solution, even if it is not a .NET solution.  

کتاب رایگان Implementing Domain Driven Design
اشتراک‌ها
روش پیاده سازی DDD

This is a practical guide for implementing the Domain Driven Design (DDD). While the implementation details rely on the ABP Framework infrastructure, core concepts, principles and patterns are applicable in any kind of solution, even if it is not a .NET solution. 

روش پیاده سازی DDD
نظرات مطالب
سفارشی سازی ASP.NET Core Identity - قسمت سوم - نرمال سازها و اعتبارسنج‌ها
یک نکته‌ی تکمیلی: چگونه پس از نصب SDK 3x جدید، بتوانیم همان پروژه‌ی قبلی را بدون به روز رسانی یا هیچگونه تغییری در آن، باز هم استفاده کنیم؟

با توجه به اینکه امضای یکسری از اینترفیس‌های نگارش 3x با 2x یکی نیست (مانند ILookupNormalizer)، پس از نصب SDK 3x، دیگر قادر به اجرای برنامه‌های 2x خود نخواهید شد؛ چون پروژه‌های NET Core. همواره از آخرین نگارش SDK نصب شده استفاده می‌کنند. برای قفل کردن شماره SDK یک Solution به نگارش 2x به صورت زیر عمل کنید:
dotnet --list-sdks
dotnet new globaljson --sdk-version 2.2.106
دستور اول لیست SDKهای نصب شده را نمایش می‌دهد و دستور دوم یک فایل global.json جدید را بر اساس شماره‌ای که از طریق اجرای دستور اول یافته‌اید، تولید می‌کند. این فایل باید در ریشه‌ی Solution قرار گیرد.
بازخوردهای پروژه‌ها
سرعت نمایش ویزارد ها و فعال نبودن پکیج ها
سلام؛ بعد از استفاده از Solution Template Generator با این موارد برخورد داشتم :
1- با کلیک سمت راست روی پوشه کنترولر وانتخاب گزینه add  گزینه Controller  موجود نمی‌باشد . البته بعد از انتخاب Add  و بعد New Scaffold Item فقط لیست موارد مربوط به Web API موجود است . در ضمن مدت زمانی که طول می‌کشد تا ویزارد مربوط به مثلا New Scaffold Item باز شود گاهی اوقات تا 10 ثانیه هم طول می‌کشد در شرایطی که در پروژه‌های دیگر با این مورد برخوردی نداشتم 
2-در پروژه اصلی که از آن یک نصاب ایجاد کردیم یک سری پکیج مثل Entity نصب کردم ولی در این قالب توانایی استفاده از آن را ندارم و وقتی که روی Solution راست کلیک می‌کنم و گزینه Enable Package Restore  هم می‌زنم با این خطا مواجه می‌شوم : This item dose not preview
مطالب
PowerShell 7.x - قسمت دوم - آشنایی با Pipelineها
PowerShell برای نام‌گذاری Commandها، از ساختار verb-noun استفاده میکند. به عنوان مثال Get-Command, New-Service, Get-Help نمونه‌هایی از این Commandها در PowerShell هستند. لازم به ذکر است که در PowerShell، منظور از cmdlet یا Command let، همان Commandهای native در PowerShell هستند؛ نه Commandهای عمومی مانند dir, cd, ipconfig و غیره. به عنوان مثال از Get-Help برای نمایش مستندات یک cmdlet میتوان استفاده کرد و دقیقاً مشابه man page در لینوکس است.

Get-Help Get-Command
با فلگ Online میتوان مستندات cmdlet موردنظر را درون مرورگر مشاهده کرد:
Get-Help Get-Command -Online
برای بیشتر cmdletها میتوانیم فیلتر نیز اعمال کنیم. به عنوان مثال با دستور زیر میتوان لیست تمام processهای سیستم را که به واژه‌ی Process ختم میشوند، مشاهده کنیم:
Get-Command -Name '*Process'
خروجی دستور فوق، یک جدول به صورت زیر خواهد بود:
CommandType     Name                                               Version    Sour
                                                                              ce
-----------     ----                                               -------    ----
Cmdlet          Debug-Process                                      7.0.0.0    Mic…
Cmdlet          Enter-PSHostProcess                                7.2.6.500  Mic…
Cmdlet          Exit-PSHostProcess                                 7.2.6.500  Mic…
Cmdlet          Get-Process                                        7.0.0.0    Mic…
Cmdlet          Start-Process                                      7.0.0.0    Mic…
Cmdlet          Stop-Process                                       7.0.0.0    Mic…
Cmdlet          Wait-Process                                       7.0.0.0    Mic…
Application     mysqltest_safe_process                             0.0.0.0

Pipeline
توسط Pipeline میتوان خروجی یک command را به عنوان ورودی یک command دیگر ارسال کرد. در دیگر زبان‌های اسکریپتی مانند bash یا batch (در ویندوز) چیزی که به command بعدی ارسال میشود، در واقع یک text است:
ls -l | grep "\.pdf$"
در مثال فوق، خروجی برنامه ls -1 را به ورودی برنامه grep ارسال کرده‌ایم. در حالت عادی، خروجی دستورات به standard output یا به طور خلاصه stout ارسال میشوند. توسط pipe یا pipeline میتوانیم خروجی متنی را به اصطلاح redirect کنیم و به کامندهای بعدی به صورت یک زنجیره ارسال کنیم. اما در PowerShell این objectها هستند که ارسال (pipe) میشوند. به عنوان مثال میتوانیم با کمک Pipelineها، خروجی مثال قبل را محدود به نمایش ستون‌های دلخواهی کنیم. به عبارتی تنها ستون‌های Name و CommandType را در خروجی نمایش دهیم:
Get-Command -Name '*Process' | Select-Object Name,CommandType
همچنین میتوانیم خروجی را با کمک Sort-Object مرتب کنیم:
Get-Command -Name '*Process' | Select-Object Name,CommandType | Sort-Object Name -Descending
یا اینکه خروجی را محدود به نمایش ۳ آیتم کنیم:
PS /> Get-Command -Name '*Process' | Select-Object Name,CommandType -First 3 | Sort-Object Name -Descending

Name                CommandType
----                -----------
Exit-PSHostProcess       Cmdlet
Enter-PSHostProcess      Cmdlet
Debug-Process            Cmdlet
همچنین میتوانیم از Where-Object برای اعمال شرط نیز استفاده کنیم. به عنوان مثال، در ادامه لیست ۵ پروسس سیستم را که مقدار CPU بیشتر از 1.24، در اختیار دارند نمایش داده‌ایم:
PS /> Get-Process | Where-Object CPU -gt 1.24 | Sort-Object WorkingSet -Descending | Select-Object -First 5

Pipelineها چطور کار میکنند؟
در PowerShell در واقع stdinی وجود ندارد که shell از آن استفاده کند؛ در نتیجه PowerShell باید بداند خروجی cmdlet قبلی را به کدام پراپرتی از cmdlet بعدی در pipeline ارسال کند:


PowerShell از مکانیزمی تحت عنوان pipeline binding برای انجام این نگاشت استفاده میکند. دو روش برای انجام این binding وجود دارد:
  • ByValue
  • ByPropertyName 
در نظر داشته باشید که هر کدام از روش‌های فوق توسط کسی که cmdlet موردنظر را پیاده‌سازی خواهد کرد میتواند پشتیبانی شود. برای درک بهتر این مکانیزم دستور زیر را در نظر بگیرید:
Get-Process Slack | Stop-Process
قبل از هر چیزی باید بدانیم خروجی cmdlet اول یعنی Get-Process چه چیزی است. اینکار را میتوانیم توسط دستور زیر انجام دهیم:
Get-Process | Get-Member
دستور فوق یک لیست از خواص Get-Process خواهد بود و خط اول این خروجی دقیقاً تایپی است که Get-Process برمیگرداند:
TypeName: System.Diagnostics.Process
بنابراین این تایپی است که به عنوان ورودی، به Stop-Process درون pipeline ارسال میشود. در ادامه توسط دستور Get-Help Stop-Process -Full لیست پارامترهایی را که Stop-Process دریافت میکند، لیست خواهیم کرد:
PS /Users/sirwanafifi> Get-Help Stop-Process -Full

NAME
    Stop-Process

SYNTAX
    Stop-Process [-Id] <int[]> [-PassThru] [-Force] [-WhatIf] [-Confirm] [<CommonParameters>]

    Stop-Process -Name <string[]> [-PassThru] [-Force] [-WhatIf] [-Confirm] [<CommonParameters>]

    Stop-Process [-InputObject] <Process[]> [-PassThru] [-Force] [-WhatIf] [-Confirm] [<CommonParameters>]


PARAMETERS
    -Confirm

        Required?                    false
        Position?                    Named
        Accept pipeline input?       false
        Parameter set name           (All)
        Aliases                      cf
        Dynamic?                     false
        Accept wildcard characters?  false

    -Force

        Required?                    false
        Position?                    Named
        Accept pipeline input?       false
        Parameter set name           (All)
        Aliases                      None
        Dynamic?                     false
        Accept wildcard characters?  false

    -Id <int[]>

        Required?                    true
        Position?                    0
        Accept pipeline input?       true (ByPropertyName)
        Parameter set name           Id
        Aliases                      None
        Dynamic?                     false
        Accept wildcard characters?  false

    -InputObject <Process[]>

        Required?                    true
        Position?                    0
        Accept pipeline input?       true (ByValue)
        Parameter set name           InputObject
        Aliases                      None
        Dynamic?                     false
        Accept wildcard characters?  false

    -Name <string[]>

        Required?                    true
        Position?                    Named
        Accept pipeline input?       true (ByPropertyName)
        Parameter set name           Name
        Aliases                      ProcessName
        Dynamic?                     false
        Accept wildcard characters?  false

    -PassThru

        Required?                    false
        Position?                    Named
        Accept pipeline input?       false
        Parameter set name           (All)
        Aliases                      None
        Dynamic?                     false
        Accept wildcard characters?  false

    -WhatIf

        Required?                    false
        Position?                    Named
        Accept pipeline input?       false
        Parameter set name           (All)
        Aliases                      wi
        Dynamic?                     false
        Accept wildcard characters?  false

    <CommonParameters>
        This cmdlet supports the common parameters: Verbose, Debug,
        ErrorAction, ErrorVariable, WarningAction, WarningVariable,
        OutBuffer, PipelineVariable, and OutVariable. For more information, see
        about_CommonParameters (https://go.microsoft.com/fwlink/?LinkID=113216).


INPUTS
    System.Int32[]
    System.String[]
    System.Diagnostics.Process[]


OUTPUTS
    System.Diagnostics.Process


ALIASES
    spps


REMARKS
    Get-Help cannot find the Help files for this cmdlet on this computer. It is displaying only partial help.
        -- To download and install Help files for the module that includes this cmdlet, use Update-Help.
        -- To view the Help topic for this cmdlet online, type: "Get-Help Stop-Process -Online" or
           go to https://go.microsoft.com/fwlink/?LinkID=2097058.
از پارامترهای فوق تنها Id, Name و InputObject هستند که خاصیت Accept pipeline inputشان به true تنظیم شده‌است. همانطور که مشاهده میکنید InputObject از نوع ByValue است و Id و Name نیز از نوع ByPropertyName هستند:
-Id <int[]>

    Required?                    true
    Position?                    0
    Accept pipeline input?       true (ByPropertyName)
    Parameter set name           Id
    Aliases                      None
    Dynamic?                     false
    Accept wildcard characters?  false

-InputObject <Process[]>

    Required?                    true
    Position?                    0
    Accept pipeline input?       true (ByValue)
    Parameter set name           InputObject
    Aliases                      None
    Dynamic?                     false
    Accept wildcard characters?  false

-Name <string[]>

    Required?                    true
    Position?                    Named
    Accept pipeline input?       true (ByPropertyName)
    Parameter set name           Name
    Aliases                      ProcessName
    Dynamic?                     false
    Accept wildcard characters?  false
نوع ورودی این پارامتر نیز یک آرایه از Processها میباشد. بنابراین در اینجا ByValue به این معنا است که اگر مقدار pipe شده از نوع Process بود، پراپرتی InputObject مقداردهی میشود. برای حالت ByPropertyName دستور زیر را در نظر بگیرید:
"Slack" | Stop-Process
با اجرای دستور فوق خطای زیر را دریافت خواهیم کرد:
Stop-Process: The input object cannot be bound to any parameters for the command either because the command does not take pipeline input or the input and its properties do not match any of the parameters that take pipeline input.
علت آن نیز مشخص است؛ چون هیچ پراپرتی از نوع ByValue که ورودی string یا آرایه‌ایی از stringها را دریافت کند برای Stop-Process وجود ندارد، بنابراین pipeline binding اتفاق نخواهد افتاد. برای درک بهتر موضوع، یک شیء تستی ایجاد خواهیم کرد که شامل یک پراپرتی Name با مقدار Slack است؛ سپس شیء جدید را به Stop-Process ارسال میکنیم:
PS /Users/sirwanafifi> $newObject = [pscustomobject]@{ Name = "Slack" }
PS /Users/sirwanafifi> $newObject | Stop-Process
با اجرای دستور فوق، پراسس موردنظر stop خواهد شد.
لازم به ذکر است که اگر یک پارامتر، هم ByValue و هم ByPropertyName باشد، PowerShell ابتدا سعی میکند ByValue را امتحان کند و اگر با شکست مواجه شد از ByPropertyName استفاده خواهد کرد.
مطالب
استفاده از SQL-CE به کمک NHibernate

خلاصه‌ای را در مورد SQL Server CE قبلا در این سایت مطالعه‌ کرده‌اید. در ادامه خلاصه‌ای کاربردی را از تنظیمات و نکات مرتبط به کار با SQL-CE به کمک NHibernate ملاحظه خواهید نمود:

1) دریافت SQL-CE 4.0


همین مقدار برای استفاده از SQL-CE 4.0 به کمک NHibernate کفایت می‌کند و حتی نیازی به نصب سرویس پک یک VS 2010 هم نیست.

2) ابزار سازی جهت ایجاد یک بانک اطلاعاتی خالی SQL-CE

using System;
using System.IO;

namespace NHibernate.Helper.DbSpecific
{
public class SqlCEDbHelper
{
const string engineTypeName = "System.Data.SqlServerCe.SqlCeEngine, System.Data.SqlServerCe";

/// <summary>
/// note: this method will delete existing db and then creates a new one.
/// </summary>
/// <param name="filename"></param>
/// <param name="password"></param>
public static void CreateEmptyDatabaseFile(string filename, string password = "")
{
if (File.Exists(filename))
File.Delete(filename);

var type = System.Type.GetType(engineTypeName);
var localConnectionString = type.GetProperty("LocalConnectionString");
var createDatabase = type.GetMethod("CreateDatabase");

var engine = Activator.CreateInstance(type);

string connectionStr = string.Format("Data Source='{0}';Password={1};Encrypt Database=True", filename, password);
if (string.IsNullOrWhiteSpace(password))
connectionStr = string.Format("Data Source='{0}'", filename);

localConnectionString.SetValue(
obj: engine,
value: connectionStr,
index: null);
createDatabase.Invoke(engine, new object[0]);
}

/// <summary>
/// use this method to compact or encrypt existing db or decrypt it to a new db with all records
/// </summary>
/// <param name="sourceConnection"></param>
/// <param name="destConnection"></param>
public static void CompactDatabase(string sourceConnection, string destConnection)
{
var type = System.Type.GetType(engineTypeName);
var engine = Activator.CreateInstance(type);

var localConnectionString = type.GetProperty("LocalConnectionString");
localConnectionString.SetValue(
obj: engine,
value: sourceConnection,
index: null);

var compactDatabase = type.GetMethod("Compact");
compactDatabase.Invoke(engine, new object[] { destConnection });
}
}
}

کلاس فوق، یک کلاس عمومی است و مرتبط به NHibernate نیست و در همه جا قابل استفاده است.
متد CreateEmptyDatabaseFile یک فایل بانک اطلاعاتی خالی با فرمت مخصوص SQL-CE را برای شما تولید خواهد کرد. به این ترتیب می‌توان بدون نیاز به ابزار خاصی، سریعا یک بانک خالی را تولید و شروع به کار کرد. در این متد اگر کلمه عبوری را وارد نکنید، بانک اطلاعاتی رمزنگاری شده نخواهد بود و اگر کلمه عبور را وارد کنید، دیتابیس اولیه به همراه کلیه اعمال انجام شده بر روی آن در طول زمان، با کمک الگوریتم AES به صورت خودکار رمزنگاری خواهند شد. کل کاری را هم که باید انجام دهید ذکر این کلمه عبور در کانکشن استرینگ است.
متد CompactDatabase، یک متد چند منظوره است. اگر بانک اطلاعاتی SQL-CE رمزنگاری نشده‌ای دارید و می‌خواهید کل آن‌را به همراه تمام اطلاعات درون آن رمزنگاری کنید، می‌توانید جهت سهولت کار از این متد استفاده نمائید. آرگومان اول آن به کانکشن استرینگ بانکی موجود و آرگومان دوم به کانکشن استرینگ بانک جدیدی که تولید خواهد شد، اشاره می‌کند.
همچنین اگر یک بانک اطلاعاتی SQL-CE رمزنگاری شده دارید و می‌خواهید آن‌را به صورت یک بانک اطلاعاتی جدید به همراه تمام رکوردهای آن رمزگشایی کنید، باز هم می‌توان از این متد استفاده کرد. البته بدیهی است که کلمه عبور را باید داشته باشید و این کلمه عبور جایی درون فایل بانک اطلاعاتی ذخیره نمی‌شود. در این حالت در کانکشن استرینگ اول باید کلمه عبور ذکر شود و کانکشن استرینگ دوم نیازی به کلمه عبور نخواهد داشت.

فرمت کلی کانکشن استرینگ SQL-CE هم به شکل زیر است:

Data Source=c:\path\db.sdf;Password=1234;Encrypt Database=True

البته این برای حالتی است که قصد داشته باشید بانک اطلاعاتی مورد استفاده را رمزنگاری کنید یا از یک بانک اطلاعاتی رمزنگاری شده استفاده نمائید. اگر بانک اطلاعاتی شما کلمه عبوری ندارد، ذکر Data Source=c:\path\db.sdf کفایت می‌کند.

این کلاس هم از این جهت مطرح شد که NHibernate می‌تواند ساختار بانک اطلاعاتی را بر اساس تعاریف نگاشت‌ها به صورت خودکار تولید و اعمال کند، «اما» بر روی یک بانک اطلاعاتی خالی SQL-CE از قبل تهیه شده (در غیراینصورت خطای The database file cannot be found. Check the path to the database را دریافت خواهید کرد).

نکته:
اگر دقت کرده باشید در این کلاس engineTypeName به صورت رشته ذکر شده است. چرا؟
علت این است که با ذکر engineTypeName به صورت رشته، می‌توان از این کلاس در یک کتابخانه عمومی هم استفاده کرد، بدون اینکه مصرف کننده نیازی داشته باشد تا ارجاع مستقیمی را به اسمبلی SQL-CE به برنامه خود اضافه کند. اگر این ارجاع وجود داشت، متدهای یاد شده کار می‌کنند، در غیراینصورت در گوشه‌ای ساکت و بدون دردسر و بدون نیاز به اسمبلی خاصی برای روز مبادا قرار خواهند گرفت.


3) ابزار مرور اطلاعات بانک اطلاعاتی SQL-CE

با استفاده از management studio خود SQL Server هم می‌شود با بانک‌های اطلاعاتی SQL-CE کار کرد، اما ... اینبار برخلاف نگارش کامل اس کیوال سرور، با یک نسخه‌ی بسیار بدوی، که حتی امکان rename فیلدها را هم ندارد مواجه خواهید شد. به همین جهت به شخصه برنامه SqlCe40Toolbox را ترجیح می‌دهم و اطمینان داشته باشید که امکانات آن برای کار با SQL-CE از امکانات ارائه شده توسط management studio مایکروسافت، بیشتر و پیشرفته‌تر است!



4) تنظیمات NHibernate جهت کار با SQL-CE

الف) پس از نصب SQL-CE ، فایل‌های آن‌را در مسیر C:\Program Files\Microsoft SQL Server Compact Edition\v4.0 می‌توان یافت. درایور ADO.NET آن هم در مسیر C:\Program Files\Microsoft SQL Server Compact Edition\v4.0\Desktop قرار دارد. بنابراین در ابتدا نیاز است تا ارجاعی را به اسمبلی System.Data.SqlServerCe.dll به برنامه خود اضافه کنید (نام پوشه desktop آن هم غلط انداز است. از این جهت که نگارش 4 آن، به راحتی در برنامه‌های ذاتا چند ریسمانی ASP.Net بدون مشکل قابل استفاده است).
نکته مهم: در این حالت NHibernate قادر به یافتن فایل درایور یاد شده نخواهد بود و پیغام خطای «Could not create the driver from NHibernate.Driver.SqlServerCeDriver» را دریافت خواهید کرد. برای رفع آن، اسمبلی System.Data.SqlServerCe.dll را در لیست ارجاعات برنامه یافته و در برگه خواص آن، خاصیت «Copy Local» را true کنید. به این معنا که NHibernate این اسمبلی را در کنار فایل اجرایی برنامه شما جستجو خواهد کرد.

ب) مطلب بعد، تنظیمات ابتدایی NHibernate‌ است جهت شناساندن SQL-CE . مابقی مسایل (نکات mapping، کوئری‌ها و غیره) هیچ تفاوتی با سایر بانک‌های اطلاعاتی نخواهد داشت و یکی است. به این معنا که اگر برنامه شما از ویژگی‌های خاص بانک‌های اطلاعاتی استفاده نکند (مثلا اگر از رویه‌های ذخیره شده اس کیوال سرور استفاده نکرده باشد)، فقط با تغییر کانکشن استرینگ و معرفی dialect و driver جدید، به سادگی می‌تواند به یک بانک اطلاعاتی دیگر سوئیچ کند؛ بدون اینکه حتی بخواهید یک سطر از کدهای اصلی برنامه خود را تغییر دهید.



تنها نکته جدید آن این متد است:

private Configuration getConfig()
{
var configure = new Configuration();
configure.SessionFactoryName("BuildIt");

configure.DataBaseIntegration(db =>
{
db.ConnectionProvider<DriverConnectionProvider>();
db.Dialect<MsSqlCe40Dialect>();
db.Driver<SqlServerCeDriver>();
db.KeywordsAutoImport = Hbm2DDLKeyWords.AutoQuote;
db.IsolationLevel = IsolationLevel.ReadCommitted;
db.ConnectionString = ConnectionString;
db.Timeout = 10;

//for testing ...
db.LogFormattedSql = true;
db.LogSqlInConsole = true;
});

return configure;
}

که در آن نحوه تعریف MsSqlCe40Dialect و SqlServerCeDriver مشخص شده است.

نکته حاشیه‌ای!
در این مثال primary key از نوع identity تعریف شده و بدون مشکل کار کرد. همین را اگر با EF تست کنید، این خطا را دریافت می‌کنید: «Server-generated keys and server-generated values are not supported by SQL Server Compact». بله، EF نمی‌تواند با primary key از نوع identity حین کار با SQL-CE کار کند. برای رفع آن توصیه شده است که از Guid استفاده کنید!

نکته تکمیلی:
استفاده از Dialect سفارشی در NHibernate


نکته پایانی!
و در پایان باید اشاره کرد که SQL-CE یک بانک اطلاعاتی نوشته شده با دات نت نیست (با CPP نوشته شده است و نصب آن هم نیاز به ران تایم به روز VC را دارد). به این معنا که جهت سیستم‌های 64 بیتی و 32 بیتی باید نسخه مناسب آن‌را توزیع کنید. یا اینکه Target platform پروژه جاری دات نت خود را بر روی X86 قرار دهید (نه بر روی Any CPU پیش فرض) و در این حالت تنها یک نسخه X86 بانک اطلاعاتی SQL-CE و همچنین برنامه خود را برای تمام سیستم‌ها توزیع کنید.