ABP-Zero模塊


一、介紹

二、啟動模版

三、功能

  1,租戶管理

  2,版本管理

  3,用戶管理

  4,角色管理

  5,組織單位管理

  6,權限管理

  7,語言管理

  8,Identity Server集成

 

一、介紹

1,Zero模塊實現ASP.NET Boilerplate框架的所有基本概念。如:

租戶管理(多租戶)、角色管理、用戶管理、session、授權(權限管理)、設置管理、語言管理、審計管理

2,Microsoft ASP.NET Identity模塊有2個版本:

  • Abp.Zero.* 軟件包基於Microsoft ASP.NET身份和EF6.x.
  • Abp.ZeroCore.* 軟件包基於Microsoft ASP.NET Core身份和Entity Framework Core。 這些軟件包也支持.net內核。

二、啟動模版

注意:確保您已經為您的Visual Studio安裝了Typescript 2.0+,因為Abp.Web.Resources nuget包附帶d.ts,它需要Typescript 2.0+。

1,基於令牌的認證

啟動模板使用基於cookie的瀏覽器身份驗證。 但是,如果要從移動應用程序中使用Web API或應用程序服務(通過動態Web api公開),則可能需要基於令牌的身份驗證機制。 啟動模板包括承載令牌認證基礎設施。 .WebApi項目中的AccountController包含用於獲取令牌的Authenticate操作。 然后我們可以使用令牌進行下一個請求。

①認證

只需發送POST請求到http://localhost:6334/api/Account/Authenticate和 Context-Type="application/json"頭,如下所示:

我們發送了一個JSON請求體,其中包含userNameOrEmailAddress和密碼。 另外,應該為租戶用戶發送TenancyName。 如上所述,返回JSON的result屬性包含令牌。 我們可以保存並用於下一個請求。

②使用API

在認證和獲取令牌之后,我們可以使用它來調用任何授權的操作。 所有應用程序服務都可以遠程使用。 例如,我們可以使用租戶服務來獲取租戶列表: 

 

只需向http://localhost:6334/api/services/app/tenant/GetTenants發送POST請求到 Content-Type="application/json"Authorization="Bearer your-auth-token "。 請求正文只是空的{}。 當然,請求和響應機構對於不同的API將是不同的。

UI上幾乎所有可用的操作也可用作Web API(由於UI使用相同的Web API),並且可以輕松地使用。

2,Migrator 控制台應用程序

啟動模板包含一個工具Migrator.exe,可輕松遷移數據庫。 您可以運行此應用程序來創建/遷移主機和租戶數據庫。

此應用程序從它自己的.config文件獲取主機連接字符串。 在開頭的web.config中將是一樣的。 確保配置文件中的連接字符串是您想要的數據庫。 獲取主機連接后,首先創建主機數據庫或應用遷移(如果它已經存在)。 然后,它獲取租戶數據庫的連接字符串,並為這些數據庫運行遷移。 如果沒有一個專用數據庫,或者它的數據庫已經遷移到另一個租戶(用於多個租戶之間的共享數據庫),它會跳過租戶。

您可以在開發或產品環境中使用此工具來遷移部署時的數據庫,而不是EntityFramework自己的Migrate.exe(這需要一些配置,並且可以在一次運行中為單個數據庫工作)。

3,單元測試
啟動模板包括測試基礎架構設置和.Test項目下的一些測試。 您可以檢查它們並輕松編寫類似的測試。 實際上,它們是集成測試而不是單元測試,因為它們使用所有ASP.NET Boilerplate基礎架構(包括驗證,授權,工作單元...)測試代碼。

三、租戶管理

1,啟用多租戶
ASP.NET Boilerplate和Zero模塊可以運行多租戶或單租戶模式。 默認情況下禁用多租戶。 我們可以在我們的模塊的PreInitialize方法中啟用它,如下所示:

[DependsOn(typeof(AbpZeroCoreModule))]
public class MyCoreModule : AbpModule
{
    public override void PreInitialize()
    {
        Configuration.MultiTenancy.IsEnabled = true;    
    }

    ...
}

當我們創建一個基於ASP.NET Boilerplate和Zero模塊的項目模板時,我們有一個Tenant實體和TenantManager領域服務。

2,租戶實體

租戶實體代表申請的租戶。

public class Tenant : AbpTenant<Tenant, User>
{

}

它源於通用的AbpTenant類。 租戶實體存儲在數據庫中的AbpTenants表中。 您可以將自定義屬性添加到Tenant類。

AbpTenant類定義了一些基本屬性,大多數重要的是:

  • TenancyName:租戶名稱,唯一。不能正常改變 它可以用來為“mytenant.mydomain.com”這樣的租戶分配子域名。 Tenant.TenancyNameRegex常量定義命名規則。
  • Name: 任意可讀的名稱
  • IsActive:true:這個租戶可以使用該應用程序;false:該租戶的用戶不能登錄到系統

AbpTenant類繼承自FullAuditedEntity。 這意味着它具有創建,修改和刪除審計屬性。 它也是軟刪除。 所以,當我們刪除租戶時,它不會從數據庫中刪除,只是被標記為已刪除。

最后,AbpTenant的Id被定義為int。

3,租戶管理

租戶管理是為租戶執行領域邏輯的服務

public class TenantManager : AbpTenantManager<Tenant, Role, User>
{
    public TenantManager(IRepository<Tenant> tenantRepository)
        : base(tenantRepository)
    {

    }
}

TenantManager也用於管理租戶功能。 你可以在這里添加你自己的方法。 此外,您可以根據自己的需要覆蓋任何AbpTenantManager基類的方法。

4,默認租戶
ASP.NET Boilerplate和Zero模塊假設有一個預定義的租戶,TenancyName為“Default”,Id為1.在單租戶應用程序中,與租戶一樣使用。 在多租戶應用程序中,您可以將其刪除或將其設為被動。

 

四、版本管理

大多數SaaS(多租戶)應用程序都有具有不同功能的版本(包)。 因此,他們可以向租戶(客戶)提供不同的價格和功能選項。

1,版本實體(Edition Entity)

版本是一個簡單的實體代表應用程序的一個版本(或包)。 它只有Name和DisplayName屬性。

2,版本管理

public class EditionManager : AbpEditionManager
{
}

它來自AbpEditionManager類。 您可以注入並使用EditionManager來創建,刪除和更新版本。 此外,EditionManager用於管理版本的功能。 它內部緩存版本功能以獲得更好的性能。

 

五、用戶管理

1,用戶實體

用戶實體表示應用程序的用戶。 它應該從AbpUser類派生,如下所示:

public class User : AbpUser<Tenant, User>
{
    //在這里添加您自己的用戶屬性
}

此類在您安裝模塊零時創建。 用戶存儲在數據庫中的AbpUsers表中。 您可以將自定義屬性添加到User類(並為更改創建數據庫遷移)。

AbpUser類定義了一些基本屬性。 一些屬性是:

  • UserName: 用戶的登錄名對於租戶應該是唯一的。
  • EmailAddress: 用戶的電子郵件地址。 對於租戶來說應該是獨一無二的。
  • Password: 用戶的密碼。
  • IsActive:true:用戶可以登錄到系統
  • Name(用戶姓名) 和 Surname(姓氏)

還有一些屬性,如角色(Roles)、權限(Permissions)、租戶(Tenant)、設置(Settings)、IsEmailConfirmed(郵箱是否確認)、

AbpUser類繼承自FullAuditedEntity。 這意味着它具有創建,修改和刪除審計屬性。 它也是軟刪除。 所以,當我們刪除一個用戶時,它不會從數據庫中刪除,只是被標記為已刪除。

AbpUser類實現了IMayHaveTenant過濾器,以便在多租戶應用程序中正常工作。

最后,用戶的ID被定義為long。

2,用戶管理

UserManager是為用戶執行領域邏輯的服務:

public class UserManager : AbpUserManager<Tenant, Role, User>
{
    //...
}

您可以注入並使用UserManager來創建,刪除,更新用戶,授予權限,為用戶更改角色等等。 你可以在這里添加你自己的方法。 此外,您可以根據自己的需要覆蓋任何AbpUserManager基類的方法。

①多租戶

UserManager旨在一次為單個租戶工作。 它適用於當前租戶的默認值。 讓我們看看UserManager的一些用法:

public class MyTestAppService : ApplicationService
{
    private readonly UserManager _userManager;

    public MyTestAppService(UserManager userManager)
    {
        _userManager = userManager;
    }

    public void TestMethod_1()
    {
        //通過電子郵件尋找當前租戶的用戶
        var user = _userManager.FindByEmail("sampleuser@aspnetboilerplate.com");
    }

    public void TestMethod_2()
    {
        //切換到租戶42
        CurrentUnitOfWork.SetFilterParameter(AbpDataFilters.MayHaveTenant, AbpDataFilters.Parameters.TenantId, 42);

        //通過電子郵件找到租戶42的一個用戶
        var user = _userManager.FindByEmail("sampleuser@aspnetboilerplate.com");
    }

    public void TestMethod_3()
    {
        //禁用MayHaveTenant過濾器,所以我們可以覆蓋所有用戶
        using (CurrentUnitOfWork.DisableFilter(AbpDataFilters.MayHaveTenant))
        {
            //現在,我們可以在所有租戶中搜索用戶名
            var users = _userManager.Users.Where(u => u.UserName == "sampleuser").ToList();

            //或者我們可以添加TenantId過濾器,如果我們要搜索一個特定的租戶
            var user = _userManager.Users.FirstOrDefault(u => u.TenantId == 42 && u.UserName == "sampleuser");
        }
    }
}

②用戶登錄

Zero模塊定義了LoginManager,它具有用於登錄應用程序的LoginAsync方法。 它檢查所有用於登錄的邏輯,並返回登錄結果。 LoginAsync方法還會自動將所有登錄嘗試保存到數據庫(即使是嘗試失敗)。 您可以使用UserLoginAttempt實體進行查詢。

③關於IdentityResults

UserManager的一些方法返回IdentityResult,而不是在某些情況下拋出異常。 這是ASP.NET Identity Framework的本質。 Zero模塊也隨之而來。 所以,我們應該檢查這個返回的結果對象來知道操作是否成功。

Zero模塊定義了CheckErrors擴展方法,如果需要,可自動檢查錯誤並拋出異常(本地化的UserFriendlyException)。 使用示例

(await UserManager.CreateAsync(user)).CheckErrors();

要獲得本地化的例外,我們應該提供一個ILocalizationManager實例:

(await UserManager.CreateAsync(user)).CheckErrors(LocalizationManager);

3,外部認證

Zero模塊登錄方式從數據庫中的AbpUsers表中認證用戶。 一些應用程序可能需要從一些外部來源(如活動目錄,從另一個數據庫的表甚至遠程服務)來驗證用戶。

對於這種情況,UserManager定義了一個名為“外部認證來源”的擴展點。 我們可以創建一個派生自IExternalAuthenticationSource的類並注冊到配置。 有一個DefaultExternalAuthenticationSource類來簡化IExternalAuthenticationSource的實現。 我們來看一個例子:

public class MyExternalAuthSource : DefaultExternalAuthenticationSource<Tenant, User>
{
    public override string Name
    {
        get { return "MyCustomSource"; }
    }

    public override Task<bool> TryAuthenticateAsync(string userNameOrEmailAddress, string plainPassword, Tenant tenant)
    {
        //TODO:驗證用戶並返回true或false
    }
}

在TryAuthenticateAsync方法中,我們可以從某些來源檢查用戶名和密碼,如果給定用戶由此源進行身份驗證,則返回true。 此外,我們可以覆蓋CreateUser和UpdateUser方法來控制用戶創建和更新此源。

當用戶通過外部源進行身份驗證時,Zero模塊檢查該用戶是否存在於數據庫(AbpUsers表)中。 如果沒有,它調用CreateUser來創建用戶,否則調用UpdateUser來允許身份驗證源來更新現有的用戶信息。

我們可以在應用程序中定義多個外部認證源。 AbpUser實體具有AuthenticationSource屬性,該屬性顯示哪個源驗證了該用戶。

要注冊我們的認證來源,我們可以在我們的模塊的PreInitialize中使用這樣的代碼:

Configuration.Modules.Zero().UserManagement.ExternalAuthenticationSources.Add<MyExternalAuthSource>();

①LDAP/Active Directory

LdapAuthenticationSource是外部身份驗證的一種實現,使用戶可以使用其LDAP(活動目錄)用戶名和密碼登錄。

如果我們要使用LDAP認證,我們首先將Abp.Zero.Ldap nuget包添加到我們的項目(通常為Core(domain)項目)。 那么我們應該為我們的應用程序擴展LdapAuthenticationSource,如下所示:

public class MyLdapAuthenticationSource : LdapAuthenticationSource<Tenant, User>
{
    public MyLdapAuthenticationSource(ILdapSettings settings, IAbpZeroLdapModuleConfig ldapModuleConfig)
        : base(settings, ldapModuleConfig)
    {
    }
}

最后,我們應該將模塊依賴關系設置為AbpZeroLdapModule,並使用上面創建的auth源啟用LDAP:

[DependsOn(typeof(AbpZeroLdapModule))]
public class MyApplicationCoreModule : AbpModule
{
    public override void PreInitialize()
    {
        Configuration.Modules.ZeroLdap().Enable(typeof (MyLdapAuthenticationSource));    
    }

    ...
}

在這些步驟之后,將為您的應用程序啟用LDAP模塊。 但默認情況下,LDAP認證未啟用。 我們可以使用設置啟用它。

1)設置

LdapSettingNames類定義了設置名稱的常量。 您可以在更改設置(或獲取設置)時使用這些常量名稱。 LDAP設置是每個租戶(對於多租戶應用程序)。 因此,不同的租戶具有不同的設置(請參閱在github上設置定義)。

您可以在MyLdapAuthenticationSource構造函數中看到,LdapAuthenticationSource期望ILdapSettings作為構造函數參數。 此界面用於獲取LDAP設置,如域,用戶名和密碼以連接到Active Directory。 默認實現(LdapSettings類)從設置管理器獲取這些設置。

如果您使用設置管理器,那么沒有問題。 您可以使用設置管理器API更改LDAP設置。 如果需要,您可以將初始/種子數據添加到數據庫,默認情況下啟用LDAP驗證。

注意:如果您不定義域,用戶名和密碼,LDAP認證適用於當前域,如果應用程序在具有適當權限的域中運行。

2)自定義設置

如果要定義另一個設置源,可以實現自定義ILdapSettings類,如下所示:

public class MyLdapSettings : ILdapSettings
{
    public async Task<bool> GetIsEnabled(int? tenantId)
    {
        return true;
    }

    public async Task<ContextType> GetContextType(int? tenantId)
    {
        return ContextType.Domain;
    }

    public async Task<string> GetContainer(int? tenantId)
    {
        return null;
    }

    public async Task<string> GetDomain(int? tenantId)
    {
        return null;
    }

    public async Task<string> GetUserName(int? tenantId)
    {
        return null;
    }

    public async Task<string> GetPassword(int? tenantId)
    {
        return null;
    }
}

並在您的模塊的PreInitialize中注冊到IOC:

[DependsOn(typeof(AbpZeroLdapModule))]
public class MyApplicationCoreModule : AbpModule
{
    public override void PreInitialize()
    {
        IocManager.Register<ILdapSettings, MyLdapSettings>(); //更改默認設置源
        Configuration.Modules.ZeroLdap().Enable(typeof (MyLdapAuthenticationSource));
    }

    ...
}

然后,您可以從任何其他來源獲取LDAP設置。

 

六、角色管理

1,角色實體(Role Entity)

角色實體代表應用程序的角色。 它應該從AbpRole類派生,如下所示:

public class Role : AbpRole<Tenant, User>
{
    //在這里添加你自己的角色屬性
}

此類在您安裝Zero模塊時創建。 角色存儲在數據庫中的AbpRoles表中。 您可以將自定義屬性添加到Role類(並為更改創建數據庫遷移)。

AbpRole定義了一些屬性:

  • Name: 租戶角色的獨特名稱。
  • DisplayName: 顯示名稱的角色。
  • IsDefault:這個角色是否默認分配給新用戶?
  • IsStatic: 這個角色是靜態的(預構建,不能被刪除)。

角色用於分組權限。 當用戶有角色時,他/她將具有該角色的所有權限。 用戶可以有多個角色。 該用戶的權限將是所有分配角色的所有權限的合並。

2,動態vs靜態角色

在模塊零中,角色可以是動態的或靜態的:

  • Static role: 靜態角色有一個已知的名稱(如“admin”),不能更改此名稱(我們可以更改顯示名稱)。 它存在於系統啟動,無法刪除。 因此,我們可以根據靜態角色名稱編寫代碼。
  • Dynamic (non static) role: 我們可以在部署后創建動態角色。 然后我們可以授予該角色的權限,我們可以將角色分配給某些用戶,我們可以將其刪除。 在開發時期,我們無法知道動態角色的名稱。

使用IsStatic屬性為角色設置它。 此外,我們應該在我們的模塊的PreInitialize上注冊靜態角色。 假設我們對租戶有一個“Admin”靜態角色:

Configuration.Modules.Zero().RoleManagement.StaticRoles.Add(new StaticRoleDefinition("Admin", MultiTenancySides.Tenant));

因此,Zero模塊將意識到靜態角色。

3,默認角色

一個或多個角色可以設置為默認。 默認角色默認分配給新添加/注冊的用戶。 這不是開發時間屬性,可以在部署后設置或更改。 使用IsDefault屬性進行設置。

4,角色管理

public class RoleManager : AbpRoleManager<Tenant, Role, User>
{
    //...
}

您可以注入並使用RoleManager來創建,刪除,更新角色,授予角色權限等等。 你可以在這里添加你自己的方法。 此外,您可以根據自己的需要覆蓋任何AbpRoleManager基類的方法。

像UserManager一樣,RoleManager的一些方法也返回IdentityResult,而不是在某些情況下拋出異常。 有關詳細信息,請參閱用戶管理文檔

 

七、組織單位管理

組織單位(OU)可用於對用戶和實體進行分層分組。

1,OrganizationUnit實體

OU由OrganizationUnit實體表示。 這個實體的基本屬性是:

  • TenantId: 租戶的這個OU的ID。 主機OU可以為空.
  • ParentId: 父OU的ID。 如果這是根OU,則可以為null.
  • Code:租戶獨有的層次化字符串代碼.
  • DisplayName: 顯示OU的名稱.

OrganizationUnit entitiy的主鍵(id)為long類型,它源自FullAuditedEntity,它提供審計信息並實現ISoftDelete界面(因此,OU不會從數據庫中刪除,它們只被標記為已刪除)

2,組織樹
由於OU可以擁有父級,所以租戶的所有OU都是樹形結構。 這棵樹有一些規則:

  • 可以有多個根(它們具有null ParentId)。
  • 最大深度樹被定義為一個常量作為OrganizationUnit.MaxDepth,它是16.
  • 存在用於第一級子計數的OU的(因為固定OU代碼單位長度在下面解釋)的限制.

2,OU Code

OU代碼由OrganizationUnit Manager自動生成和維護。 這是一個字符串,如:

"00001.00042.00005"

該代碼可用於輕松查詢OU的所有子項(遞歸)的數據庫。 這段代碼有一些規則:

  • 這對租戶來說是獨一無二的.
  • 同一個OU的所有子代都有父OU的代碼開頭的代碼.
  • 它是基於樹中OU的級別的固定長度,如示例所示.
  • 當OU代碼是唯一的,如果您移動OU,它可以是可更改的。 因此,我們應該通過Id引用OU,而不是Code.

3,OrganizationUnit 管理

可以注入OrganizationUnitManager類並用於管理OU。 常見用例有:

  • 創建,更新或刪除OU
  • 在OU樹中移動OU.
  • 獲取關於OU樹和項目的信息.

4,多租戶

OrganizationUnitManager旨在一次為單個租戶工作。 它適用於當前租戶的默認值。

5,常用案例

在這里,我們將看到OU的常見用例。 您可以在這里找到樣品的源代碼。

6,為組織單位創建實體,
OU的最明顯的使用是將實體分配給OU。 我們來看一個示例實體:

public class Product : Entity, IMustHaveTenant, IMustHaveOrganizationUnit
{
    public virtual int TenantId { get; set; }

    public virtual long OrganizationUnitId { get; set; }
    
    public virtual string Name { get; set; }

    public virtual float Price { get; set; }
}

我們簡單地創建了OrganizationUnitId屬性來將此實體分配給OU。 IMustHaveOrganizationUnit定義了OrganizationUnitId屬性。 我們不必執行它,但建議提供標准化。 還有一個具有可空的OrganizationUnitId屬性的IMayHaveOrganizationId。

現在,我們可以將產品與OU相關聯並查詢特定OU的產品。

注意; 產品實體有一個TenantId(它是IMustHaveTenant的屬性),用於區分多租戶應用中不同租戶的產品(見多租戶文檔)。 如果您的應用程序不是多租戶,則不需要此接口和屬性。

7,獲取組織單位中的實體

獲取OU的產品很簡單。 我們來看看這個示例域服務:

public class ProductManager : IDomainService
{
    private readonly IRepository<Product> _productRepository;

    public ProductManager(IRepository<Product> productRepository)
    {
        _productRepository = productRepository;
    }

    public List<Product> GetProductsInOu(long organizationUnitId)
    {
        return _productRepository.GetAllList(p => p.OrganizationUnitId == organizationUnitId);
    }
                
}

我們可以簡單地寫一個反對Product.OrganizationUnitId的謂詞,如上所示。

8,在組織單位中獲得實體,包括其子單位單位
我們可能想要獲得包含子組織單位的組織單位的產品。 在這種情況下,OU代碼可以幫助我們:

public class ProductManager : IDomainService
{
    private readonly IRepository<Product> _productRepository;
    private readonly IRepository<OrganizationUnit, long> _organizationUnitRepository;

    public ProductManager(
        IRepository<Product> productRepository, 
        IRepository<OrganizationUnit, long> organizationUnitRepository)
    {
        _productRepository = productRepository;
        _organizationUnitRepository = organizationUnitRepository;
    }

    [UnitOfWork]
    public virtual List<Product> GetProductsInOuIncludingChildren(long organizationUnitId)
    {
        var code = _organizationUnitRepository.Get(organizationUnitId).Code;

        var query =
            from product in _productRepository.GetAll()
            join organizationUnit in _organizationUnitRepository.GetAll() on product.OrganizationUnitId equals organizationUnit.Id
            where organizationUnit.Code.StartsWith(code)
            select product;

        return query.ToList();
    }
}

首先,我們得到了給定OU的代碼。 然后我們創建了一個帶有連接和StartsWith(代碼)條件的LINQ(StartsWith在SQL中創建一個LIKE查詢)。 因此,我們可以分級地獲得OU的產品。

9,過濾用戶的實體
我們可能希望獲得特定用戶的OU中的所有產品。 示例代碼:

public class ProductManager : IDomainService
{
    private readonly IRepository<Product> _productRepository;
    private readonly UserManager _userManager;

    public ProductManager(
        IRepository<Product> productRepository, 
        UserManager userManager)
    {
        _productRepository = productRepository;
        _organizationUnitRepository = organizationUnitRepository;
        _userManager = userManager;
    }

    public async Task<List<Product>> GetProductsForUserAsync(long userId)
    {
        var user = await _userManager.GetUserByIdAsync(userId);
        var organizationUnits = await _userManager.GetOrganizationUnitsAsync(user);
        var organizationUnitIds = organizationUnits.Select(ou => ou.Id);

        return await _productRepository.GetAllListAsync(p => organizationUnitIds.Contains(p.OrganizationUnitId));
    }
}

我們簡單地發現了用戶的OU的Ids。 然后在獲得產品時使用包含條件。 當然,我們可以使用join創建一個LINQ查詢來獲取相同的列表。

我們可能想要在用戶的OU中獲得產品,包括其子OU:

public class ProductManager : IDomainService
{
    private readonly IRepository<Product> _productRepository;
    private readonly IRepository<OrganizationUnit, long> _organizationUnitRepository;
    private readonly UserManager _userManager;

    public ProductManager(
        IRepository<Product> productRepository, 
        IRepository<OrganizationUnit, long> organizationUnitRepository, 
        UserManager userManager)
    {
        _productRepository = productRepository;
        _organizationUnitRepository = organizationUnitRepository;
        _userManager = userManager;
    }

    [UnitOfWork]
    public virtual async Task<List<Product>> GetProductsForUserIncludingChildOusAsync(long userId)
    {
        var user = await _userManager.GetUserByIdAsync(userId);
        var organizationUnits = await _userManager.GetOrganizationUnitsAsync(user);
        var organizationUnitCodes = organizationUnits.Select(ou => ou.Code);

        var query =
            from product in _productRepository.GetAll()
            join organizationUnit in _organizationUnitRepository.GetAll() on product.OrganizationUnitId equals organizationUnit.Id
            where organizationUnitCodes.Any(code => organizationUnit.Code.StartsWith(code))
            select product;

        return query.ToList();
    }
}

我們將Any與StartsWith條件組合在LINQ連接語句中。

當然可能需要更復雜的要求,但是所有這些都可以用LINQ或SQL來完成。

10,設置

您可以注入並使用IOrganizationUnitSettings接口來獲取組織單位設置值。 目前,只有一個可以根據您的應用需求進行更改的設置:

MaxUserMembershipCount:用戶最大允許的會員數。
默認值為int.MaxValue,允許用戶在同一時間成為無限制OU的成員。
設置名稱是在AbpZeroSettingNames.OrganizationUnits.MaxUserMembershipCount中定義的常量。

 

八、權限管理

1,角色權限

如果我們授予權限角色,則所有用戶都有權限授權(除非明確禁止特定用戶使用)。

我們使用RoleManager更改角色的權限。 例如,SetGrantedPermissionsAsync可用於在一個方法調用中更改角色的所有權限:

public class RoleAppService : IRoleAppService
{
    private readonly RoleManager _roleManager;
    private readonly IPermissionManager _permissionManager;

    public RoleAppService(RoleManager roleManager, IPermissionManager permissionManager)
    {
        _roleManager = roleManager;
        _permissionManager = permissionManager;
    }

    public async Task UpdateRolePermissions(UpdateRolePermissionsInput input)
    {
        var role = await _roleManager.GetRoleByIdAsync(input.RoleId);
        var grantedPermissions = _permissionManager
            .GetAllPermissions()
            .Where(p => input.GrantedPermissionNames.Contains(p.Name))
            .ToList();

        await _roleManager.SetGrantedPermissionsAsync(role, grantedPermissions);
    }
}

在這個例子中,我們得到一個RoleId和已授予的權限名稱列表(input.GrantedPermissionNames是List <string>)作為輸入。 我們使用IPermissionManager按名稱查找所有權限對象。 然后我們調用SetGrantedPermissionsAsync方法來更新角色的權限。

還有其他方法,如GrantPermissionAsync和ProhibitPermissionAsync一個一個地控制權限。

2,用戶權限

雖然基於角色的權限管理對於大多數應用程序來說足夠,但我們可能需要控制每個用戶的權限。 當我們為用戶定義權限設置時,它覆蓋權限設置來自用戶的角色。

舉個例子; 假設我們有一個應用程序服務來禁止用戶的權限:

public class UserAppService : IUserAppService
{
    private readonly UserManager _userManager;
    private readonly IPermissionManager _permissionManager;

    public UserAppService(UserManager userManager, IPermissionManager permissionManager)
    {
        _userManager = userManager;
        _permissionManager = permissionManager;
    }

    public async Task ProhibitPermission(ProhibitPermissionInput input)
    {
        var user = await _userManager.GetUserByIdAsync(input.UserId);
        var permission = _permissionManager.GetPermission(input.PermissionName);

        await _userManager.ProhibitPermissionAsync(user, permission);
    }
}

UserManager有許多方法來控制用戶的權限。 在這個例子中,我們得到一個UserId和PermissionName,並使用UserManager的ProhibitPermissionAsync方法來禁止用戶的權限。

當我們禁止用戶的許可時,即使他/她的角色被授予許可,他/她也不能獲得此許可。 我們可以說同樣的原則給予。 當我們授予專門為用戶授予的權限時,該用戶被授予權限,即使用戶的角色也不被授予權限。 我們可以為用戶使用ResetAllPermissionsAsync來刪除用戶的所有用戶特定權限設置。

 

九、語言管理

雖然在大多數情況下都有好處,但我們可能希望在數據庫上動態定義語言和文本。 Zero模塊允許我們動態管理每個租戶的應用程序語言和文本。

1,介紹

①EnableDbLocalization

啟用

Configuration.Modules.Zero().LanguageManagement.EnableDbLocalization();

這應該在頂級模塊的PreInitialize方法(它是Web應用程序的Web模塊)中導入Abp.Zero.Configuration命名空間(使用Abp.Zero.Configuration)來查看Zero()擴展方法)。

②種子數據庫語言

由於ABP將從數據庫中獲得語言列表,所以我們應該將默認語言插入數據庫。 如果您使用EntityFramework,您可以像下面那樣使用種子代碼

using System.Collections.Generic;
using System.Linq;
using Abp.Localization;
using AbpCompanyName.AbpProjectName.EntityFramework;

namespace AbpCompanyName.AbpProjectName.Migrations.SeedData
{
    public class DefaultLanguagesCreator
    {
        public static List<ApplicationLanguage> InitialLanguages { get; private set; }

        private readonly AbpProjectNameDbContext _context;

        static DefaultLanguagesCreator()
        {
            InitialLanguages = new List<ApplicationLanguage>
            {
                new ApplicationLanguage(null, "en", "English", "famfamfam-flag-gb"),
                new ApplicationLanguage(null, "tr", "Türkçe", "famfamfam-flag-tr"),
                new ApplicationLanguage(null, "zh-CN", "簡體中文", "famfamfam-flag-cn"),
                new ApplicationLanguage(null, "pt-BR", "Português-BR", "famfamfam-flag-br"),
                new ApplicationLanguage(null, "es", "Español", "famfamfam-flag-es"),
                new ApplicationLanguage(null, "fr", "Français", "famfamfam-flag-fr"),
                new ApplicationLanguage(null, "it", "Italiano", "famfamfam-flag-it"),
                new ApplicationLanguage(null, "ja", "日本語", "famfamfam-flag-jp"),
                new ApplicationLanguage(null, "nl-NL", "Nederlands", "famfamfam-flag-nl"),
                new ApplicationLanguage(null, "lt", "Lietuvos", "famfamfam-flag-lt")
            };
        }

        public DefaultLanguagesCreator(AbpProjectNameDbContext context)
        {
            _context = context;
        }

        public void Create()
        {
            CreateLanguages();
        }

        private void CreateLanguages()
        {
            foreach (var language in InitialLanguages)
            {
                AddLanguageIfNotExists(language);
            }
        }

        private void AddLanguageIfNotExists(ApplicationLanguage language)
        {
            if (_context.Languages.Any(l => l.TenantId == language.TenantId && l.Name == language.Name))
            {
                return;
            }

            _context.Languages.Add(language);

            _context.SaveChanges();
        }
    }
}

③刪除靜態語言配置

如果您具有如下所示的靜態語言配置,您可以從配置代碼中刪除這些行,因為它將從數據庫中獲取語言。

Configuration.Localization.Languages.Add(new LanguageInfo("en", "English", "famfamfam-flag-england", true));

④注意現有的XML本地化源

不要刪除您的XML本地化文件和源配置代碼。 因為這些文件被用作回退源,並且所有的本地化密鑰都是從這個源獲得的。

因此,當您需要一個新的本地化文本時,按照正常情況將其定義為XML文件。 您應至少在默認語言的XML文件中定義它。 因此,您不需要將本地化文本的默認值添加到數據庫遷移代碼。

2,管理語言

IApplicationLanguageManager接口被注入並用於管理語言。 它具有GetLanguagesAsync,AddAsync,RemoveAsync,UpdateAsync等方法來管理主機和租戶的語言。

①語言列表邏輯

語言列表按租戶和主機存儲,計算方法如下:

  • 有一個為主機定義的語言列表。 該列表被視為所有租戶的默認列表.
  • 每個租戶有一個單獨的語言列表。 此列表繼承主機列表添加特定於特定語言的語言。 租戶不能刪除或更新主機定義(默認)語言(但可以覆蓋本地化文本,我們將在后面看到).

②ApplicationLanguage實體
ApplicationLanguage實體表示租戶或主機的語言。

[Serializable]
[Table("AbpLanguages")]
public class ApplicationLanguage : FullAuditedEntity, IMayHaveTenant
{
    //...
}

它的基本屬性是:

  • TenantId (可空): 包含有關租戶的Id,如果這種語言是特定於租戶的。 如果這是主機語言,則為null。
  • Name: 語言名稱 這必須是列表中的文化代碼。
  • DisplayName: 顯示語言的名稱。 這可以是任意名稱,一般是CultureInfo.DisplayName.
  • Icon: .語言的任意圖標/標志。 這可以用於在UI上顯示語言的標志

此外,ApplicationLanguage繼承了您所看到的FullAuditedEntity。 這意味着它是一個軟刪除實體,並自動審核(有關更多信息,請參閱實體文檔)。

ApplicationLanguage實體存儲在數據庫中的AbpLanguages表中。

3,管理本地化文本

IApplicationLanguageTextManager接口被注入並用於管理本地化文本。 它需要獲取/設置租戶或主機的本地化文本的方法。

①本地化文本
讓我們看看當你想本地化一個文本時會發生什么?

  • 嘗試獲得當前文化(獲得使用CurrentThread.CurrentUICulture).
    • 它檢查給定文本是否定義(覆蓋)當前租戶(獲取使用IAbpSession.TenantId)在數據庫中的當前文化。 如果定義,則返回值.
    • 然后,它檢查數據庫中當前文化中的主機是否定義(覆蓋)給定文本。 如果定義,則返回值.
    • 然后,它檢查在當前文化中的底層XML文件中是否定義了給定的文本。 如果定義,則返回值.
  • 嘗試尋找回歸文化。 這樣計算:如果現在的文化是“en-GB”,那么后備文化就是“en”.
    • 它檢查數據庫中的后備文化中當前租戶是否定義(覆蓋)給定文本。 如果定義,則返回值.
    • 然后,它檢查在數據庫中的后備文化中主機是否定義(覆蓋)給定文本。 如果定義,則返回值.
    • 然后,它會檢查在后備文化中的底層XML文件中是否定義了給定的文本。 如果定義,則返回值.
  • 嘗試找到默認文化.
    • 它檢查數據庫中默認文化中當前租戶是否定義(覆蓋)給定文本。 如果定義,則返回值.
    • 然后它檢查在數據庫中默認文化中主機是否定義(覆蓋)給定文本。 如果定義,則返回值.
    • 然后,它會檢查在默認文化中的底層XML文件中是否定義了給定的文本。 如果定義,則返回值.
  • 獲取相同的文本或拋出異常
    • 如果根本沒有找到給定的文本(鍵),ABP會拋出異常或通過用[和]包裝返回相同的文本(鍵).

因此,獲取本地化的文本有點復雜。 但它起作用很快,因為它使用緩存。

②ApplicationLanguageText實體
ApplicationLanguageText用於存儲數據庫中的本地化值。

[Serializable]
[Table("AbpLanguageTexts")]
public class ApplicationLanguageText : AuditedEntity<long>, IMayHaveTenant
{
    //...
}

它的基本屬性是:

  • TenantId (可空):如果本地化文本是特定於租戶的,則包含相關租戶的身份證號碼。 如果這是主機本地化的文本,它為null .
  • LanguageName: 語言名稱 這必須是列表中的文化代碼。 這與ApplicationLanguage.Name匹配,但不強制外鍵使其獨立於語言條目。 IApplicationLanguageTextManager正確處理它.
  • Source: 本地化源名稱.
  • Key: 本地化文本的鍵/名稱.
  • Value: 本地化值.

ApplicationLanguageText實體存儲在數據庫的AbpLanguageTexts表中。

 

十、Identity Server集成

Identity Server是一個開源OpenID Connect和OAuth 2.0框架。 它可以用於使您的應用程序在服務器上進行身份驗證/單一登錄。 它還可以為第三方客戶端發出訪問令牌。 本文檔介紹如何將IdentityServer集成到項目中。

1,安裝

由於EF核心包已經取決於第一個,您只能將Abp.ZeroCore.IdentityServer4.EntityFrameworkCore包安裝到您的項目中。 安裝到項目中包含您的DbContext(.EntityFrameworkCore項目為默認模板):

Install-Package Abp.ZeroCore.IdentityServer4.EntityFrameworkCore

然后你可以添加依賴關系到你的模塊(一般來說,你的EntityFrameworkCore項目):

[DependsOn(typeof(AbpZeroCoreIdentityServerEntityFrameworkCoreModule))]
public class MyModule : AbpModule
{
    //...
}

2,配置

使用Abp.ZeroCore配置和使用IdentityServer4類似於獨立使用IdentityServer4。 您應該閱讀它自己的文檔來了解和使用它。 在本文檔中,我們僅顯示了與Abp.ZeroCore集成所需的其他配置。

①Startup Class

在ASP.NET Core Startup類中,我們應該將IdentityServer添加到服務集合和ASP.NET Core中間件管道中。 突出了與標准IdentityServer4使用的差異:

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        //...
        
        services.AddAbpIdentity<Tenant, User, Role>()
            ...
            .AddAbpIdentityServer();
        
        //...
        
        services.AddIdentityServer()
            .AddDeveloperSigningCredential()
            .AddInMemoryIdentityResources(IdentityServerConfig.GetIdentityResources())
            .AddInMemoryApiResources(IdentityServerConfig.GetApiResources())
            .AddInMemoryClients(IdentityServerConfig.GetClients())
            .AddAbpPersistedGrants<YourDbContext>()
            .AddAbpIdentityServer<User>();

        //...
    }

    public void Configure(IApplicationBuilder app)
    {
        //...
        app.UseIdentityServer();
        //...
    }
}

1)在AddAbpIdentity ... chain之后添加了.AddAbpIdentityServer()。 在ABP中,啟動模板AddAbpIdentity位於IdentityRegistrar.Register(services)方法中。 所以,你可以像IdentityRegistrar.Register(services).AddAbpIdentityServer()鏈接。

2)在啟動項目中,在IdentityRegistrar.Register(services)之后添加了services.AddIdentityServer()並添加了app.UseIdentityServer()只是app.UseAuthentication()。

3,IdentityServerConfig類
我們使用IdentityServerConfig類來獲取身份資源,api資源和客戶端。 您可以在自己的文檔中找到有關此類的更多信息。 對於最簡單的情況,它可以是一個靜態類,如下所示

public static class IdentityServerConfig
{
    public static IEnumerable<ApiResource> GetApiResources()
    {
        return new List<ApiResource>
        {
            new ApiResource("default-api", "Default (all) API")
        };
    }

    public static IEnumerable<IdentityResource> GetIdentityResources()
    {
        return new List<IdentityResource>
        {
            new IdentityResources.OpenId(),
            new IdentityResources.Profile(),
            new IdentityResources.Email(),
            new IdentityResources.Phone()
        };
    }

    public static IEnumerable<Client> GetClients()
    {
        return new List<Client>
        {
            new Client
            {
                ClientId = "client",
                AllowedGrantTypes = GrantTypes.ClientCredentials.Union(GrantTypes.ResourceOwnerPassword),
                AllowedScopes = {"default-api"},
                ClientSecrets =
                {
                    new Secret("secret".Sha256())
                }
            }
        };
    }
}

4,DbContext Changes

 AddAbpPersistentGrants()方法用於保存持久數據存儲的同意響應。 為了使用它,YourDbContext必須實現IAbpPersistedGrantDbContext接口,如下所示:

public class YourDbContext : AbpZeroDbContext<Tenant, Role, User, YourDbContext>, IAbpPersistedGrantDbContext
{
    public DbSet<PersistedGrantEntity> PersistedGrants { get; set; }

    public YourDbContext(DbContextOptions<YourDbContext> options)
        : base(options)
    {
    }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);

        modelBuilder.ConfigurePersistedGrantEntity();
    }
}

IAbpPersistedGrantDbContext定義了PersistedGrants DbSet。 我們還應該調用上面顯示的modelBuilder.ConfigurePersistedGrantEntity()擴展方法,以便為PersistedGrantEntity配置EntityFramework。

請注意,YourDbContext中的此更改會導致新的數據庫遷移。 因此,請記住使用“添加遷移”和“更新數據庫”命令來更新數據庫。

即使您不調用AddAbpPersistedGrants <YourDbContext>()擴展方法,IdentityServer4仍將繼續工作,但在這種情況下,用戶同意響應將被存儲在內存數據存儲中(重新啟動應用程序時會被清除)。

5,JWT認證中間件
如果我們要針對相同的應用程序授權客戶端,我們可以使用IdentityServer身份驗證中間件。

首先,將IdentityServer4.AccessTokenValidation包從nuget安裝到您的項目中:

Install-Package IdentityServer4.AccessTokenValidation

然后我們可以將中間件添加到Startup類中,如下所示

app.UseIdentityServerAuthentication(
    new IdentityServerAuthenticationOptions
    {
        Authority = "http://localhost:62114/",
        RequireHttpsMetadata = false,
        AutomaticAuthenticate = true,
        AutomaticChallenge = true
    });

我剛剛在啟動項目中的app.UseIdentityServer()之后添加了這個。

6,測試

現在,我們的身份服務器已准備好從客戶端獲取請求。 我們可以創建一個控制台應用程序來發出請求並獲得響應。

  • 在解決方案中創建一個新的控制台應用.
  • 將IdentityModel nuget軟件包添加到控制台應用程序。 此包用於為OAuth端點創建客戶端.

雖然IdentityModel nuget軟件包足以創建客戶端並使用您的API,但我想以更安全的方式顯示使用API:我們將將傳入的數據轉換為應用程序服務返回的DTO。

  • 從控制台應用程序添加對應用程序層的引用。 這將允許我們使用客戶端應用層返回的相同的DTO類.
  • 添加Abp.Web.Common nuget包。 這將允許我們使用ASP.NET Boilerplate類中定義的AjaxResponse類。 否則,我們將處理原始的JSON字符串來處理服務器響應.

那么我們可以改變Program.cs,如下所示:

using System;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using Abp.Application.Services.Dto;
using Abp.Json;
using IdentityModel.Client;
using Abp.MultiTenancy;
using Abp.Web.Models;
using IdentityServerIntegrationDemo.Users.Dto;
using Newtonsoft.Json;

namespace IdentityServerIntegrationDemo.ConsoleApiClient
{
    class Program
    {
        static void Main(string[] args)
        {
            RunDemoAsync().Wait();
            Console.ReadLine();
        }

        public static async Task RunDemoAsync()
        {
            var accessToken = await GetAccessTokenViaOwnerPasswordAsync();
            await GetUsersListAsync(accessToken);
        }

        private static async Task<string> GetAccessTokenViaOwnerPasswordAsync()
        {
            var disco = await DiscoveryClient.GetAsync("http://localhost:62114");

            var httpHandler = new HttpClientHandler();
            httpHandler.CookieContainer.Add(new Uri("http://localhost:62114/"), new Cookie(MultiTenancyConsts.TenantIdResolveKey, "1")); //Set TenantId
            var tokenClient = new TokenClient(disco.TokenEndpoint, "client", "secret", httpHandler);
            var tokenResponse = await tokenClient.RequestResourceOwnerPasswordAsync("admin", "123qwe");

            if (tokenResponse.IsError)
            {
                Console.WriteLine("Error: ");
                Console.WriteLine(tokenResponse.Error);
            }

            Console.WriteLine(tokenResponse.Json);

            return tokenResponse.AccessToken;
        }

        private static async Task GetUsersListAsync(string accessToken)
        {
            var client = new HttpClient();
            client.SetBearerToken(accessToken);

            var response = await client.GetAsync("http://localhost:62114/api/services/app/user/getUsers");
            if (!response.IsSuccessStatusCode)
            {
                Console.WriteLine(response.StatusCode);
                return;
            }

            var content = await response.Content.ReadAsStringAsync();
            var ajaxResponse = JsonConvert.DeserializeObject<AjaxResponse<PagedResultDto<UserListDto>>>(content);
            if (!ajaxResponse.Success)
            {
                throw new Exception(ajaxResponse.Error?.Message ?? "Remote service throws exception!");
            }

            Console.WriteLine();
            Console.WriteLine("Total user count: " + ajaxResponse.Result.TotalCount);
            Console.WriteLine();
            foreach (var user in ajaxResponse.Result.Items)
            {
                Console.WriteLine($"### UserId: {user.Id}, UserName: {user.UserName}");
                Console.WriteLine(user.ToJsonString(indented: true));
            }
        }
    }
}

運行此應用程序之前,請確保您的Web項目已啟動並運行,因為此控制台應用程序將向Web應用程序發出請求。 另外,請確保請求端口(62114)與您的Web應用程序相同。

您可以在此處看到本教程的源代碼:https://github.com/aspnetboilerplate/aspnetboilerplate-samples/tree/master/IdentityServerDemo。 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM