ABP框架學習

 

1、整體與公共結構
  1,ABP配置javascript

  2,多租戶css

  3,ABP Sessionhtml

  4,緩存 java

  5,日誌jquery

  6,設置管理 angularjs

  7,Timing web

  8,ABPMapperajax

  9,發送電子郵件 數據庫

2、領域層編程

  10,實體

  11,值對象

  12,倉儲

  13,領域服務 

  14,規格模式

  15,工做單元

  16,事件總線 

  17,數據過濾器

3、應用層

  18,應用服務 

  19,數據傳輸對象

  20,驗證數據傳輸對象

  21,受權

  22,功能管理

  23,審計日誌

4、分佈式服務層

  24,ASP.NET Web API Controllers

  25,動態Webapi層

  26,OData整合

  27,Swagger UI 整合 

5、展現層

  28,ASP.NET MVC

  29,本地化

  30,導航

  31,嵌入式資源文件

  32,ABP-JavaScript API

  33,CSRF / XSRF保護

6、後臺服務

  34,後臺工做(Jobs)和工做者(Workers)

  35,Hangfire集成

  36,Quartz 集成

7、實時服務 

  37,通知系統

  38,SignalR集成

 8、ORM

  39,EntityFramework集成

  40,NHibernate集成

  41,Dapper使用

 

1、ABP配置

//將全部異常發送到客戶端
Configuration.Modules.AbpWebCommon().SendAllExceptionsToClients = true;

1,自定義配置

①建立配置類

    public class CustomModule
    {
        public bool IsEnable { get; set; }
    }

 

②定義Configuration擴展

    public static class ModuleConfigrationExt
    {
        public static CustomModule GetCustomModule(this IModuleConfigurations moduleConfigurations)
        {
            return moduleConfigurations.AbpConfiguration.Get<CustomModule>();
        }
    }

③使用自定義配置

            //註冊CustomModule配置類
            IocManager.Register<CustomModule>();
            //設置CustomModule配置
            Configuration.Modules.GetCustomModule().IsEnable = true;

2、多租戶

1,在core層開啓多租戶

Configuration.MultiTenancy.IsEnabled = true; 

2,主機與租戶

主機:擁有本身的用戶,角色,權限,設置的客戶,並使用與其餘租戶徹底隔離的應用程序。 多租戶申請將有一個或多個租戶。 若是這是CRM應用程序,不一樣的租戶也有本身的賬戶,聯繫人,產品和訂單。 因此,當咱們說一個'租戶用戶'時,咱們的意思是租戶擁有的用戶。

租戶:主機是單例(有一個主機)。 主持人負責建立和管理租戶。 因此,「主機用戶」是較高層次的,獨立於全部租戶,能夠控制他們。

3,abp定義IAbpSession接口獲取UserId和TenantId

若是UserId和TenantId都爲空,則當前用戶未登陸到系統。 因此,咱們不知道是主機用戶仍是租戶用戶。 在這種狀況下,用戶沒法訪問受權的內容。
若是UserId不爲null而且TenantId爲null,那麼咱們能夠知道當前用戶是主機用戶。
若是UserId不爲null,而且TenantId不爲空,咱們能夠知道當前用戶是租戶用戶。
若是UserId爲null但TenantId不爲空,那意味着咱們能夠知道當前的租戶,可是當前的請求沒有被受權(用戶沒有登陸)

4,數據過濾器

若是實體實現的是IMustHaveTenant接口,且AbpSession.TenantId爲null的時候(即主機用戶),獲取到的數據是全部租戶的,除非你本身顯式進行過濾。而在IMayHaveTenant狀況下,AbpSession.TenantId爲null獲取到的是主機用戶的數據。

①IMustHaveTenant 接口:該接口經過定義TenantId屬性來區分不一樣租戶的實體

public class Product : Entity, IMustHaveTenant
{
    public int TenantId { get; set; }
        
    public string Name { get; set; }
    
    //...其餘屬性
}

②IMayHaveTenant接口:咱們可能須要在租戶和租戶之間共享一個實體類型。所以,一個實體可能會被一個租戶或租主擁有

public class Role : Entity, IMayHaveTenant
{
    public int? TenantId { get; set; }
        
    public string RoleName { get; set; }
    
    //...其餘屬性
}

IMayHaveTenant不像IMustHaveTenant同樣經常使用。好比,一個Product類能夠不實現IMayHaveTenant接口,由於Product和實際的應用功能相關,和管理租戶不相干。所以,要當心使用IMayHaveTenant接口,由於它更難維護租戶和租主共享的代碼。

 

3、ABP Session

1,AbpSession定義了一些關鍵屬性:

UserId:當前用戶的ID,若是沒有當前用戶,則爲null。 若是通話代碼被受權,則不能爲空。
TenantId:當前租戶的ID,若是沒有當前租戶(若是用戶未登陸或他是主機用戶),則爲null。
ImpersonatorUserId:當前會話由其餘用戶模擬時,模擬人員的身份。 若是這不是模擬登陸,它爲null。
ImpersonatorTenantId:假冒用戶的租戶的身份,若是當前會話由其餘用戶模擬。 若是這不是模擬登陸,它爲null。
MultiTenancySide:多是主機或租戶。

2,覆蓋當前會話值

public class MyService
{
    private readonly IAbpSession _session;

    public MyService(IAbpSession session)
    {
        _session = session;
    }

    public void Test()
    {
        using (_session.Use(42, null))
        {
            var tenantId = _session.TenantId; //42
            var userId = _session.UserId; //null
        }
    }
}

 

4、緩存

1,咱們能夠注入它並使用它來獲取緩存

public class TestAppService : ApplicationService
{
    private readonly ICacheManager _cacheManager;

    public TestAppService(ICacheManager cacheManager)
    {
        _cacheManager = cacheManager;
    }

    public Item GetItem(int id)
    {
        //Try to get from cache
        return _cacheManager
                .GetCache("MyCache")
                .Get(id.ToString(), () => GetFromDatabase(id)) as Item;
    }

    public Item GetFromDatabase(int id)
    {
        //... retrieve item from database
    }
}

2,配置緩存有效期

//Configuration for all caches
Configuration.Caching.ConfigureAll(cache =>
{
    cache.DefaultSlidingExpireTime = TimeSpan.FromHours(2);
});

//Configuration for a specific cache
Configuration.Caching.Configure("MyCache", cache =>
{
    cache.DefaultSlidingExpireTime = TimeSpan.FromHours(8);
});

默認緩存超時時間爲60分鐘。該代碼應該放在您的模塊的PreInitialize方法中。 有了這樣一個代碼,MyCache將有8個小時的過時時間,而全部其餘緩存將有2個小時。

3,實體緩存(例如:根據人員id查詢人員信息緩存)

①在dto中定義緩存類

[AutoMapFrom(typeof(Person))]
public class PersonCacheItem
{
    public string Name { get; set; }
}

②在application層中定義實體緩存接口

public interface IPersonCache : IEntityCache<PersonCacheItem>
{

}

③實現緩存接口

public class PersonCache : EntityCache<Person, PersonCacheItem>, IPersonCache, ITransientDependency
{
    public PersonCache(ICacheManager cacheManager, IRepository<Person> repository)
        : base(cacheManager, repository)
    {

    }
}

④應用層使用

public class MyPersonService : ITransientDependency
{
    private readonly IPersonCache _personCache;

    public MyPersonService(IPersonCache personCache)
    {
        _personCache = personCache;
    }

    public string GetPersonNameById(int id)
    {
        return _personCache[id].Name; //替代: _personCache.Get(id).Name;
    }
}

 4,使用Redis緩存

①在Web項目中Nuget引用Abp.RedisCache

②設置Module

using Abp.Runtime.Caching.Redis;

namespace MyProject.AbpZeroTemplate.Web
{
    [DependsOn(
        //...other module dependencies
        typeof(AbpRedisCacheModule))]
    public class MyProjectWebModule : AbpModule
    {
        public override void PreInitialize()
        {
            //...other configurations
            
            Configuration.Caching.UseRedis();
        }
        
        //...other code
    }
}

③Abp.Redis Cache包使用「localhost」做爲鏈接字符串做爲默認值。 您能夠將鏈接字符串添加到您的配置文件中以覆蓋它。 例:

<add name="Abp.Redis.Cache" connectionString="localhost"/>

④此外,您能夠將appSettings的設置添加到Redis的數據庫ID中。 例:

<add key="Abp.Redis.Cache.DatabaseId" value="2"/>

不一樣的數據庫標識對於在同一服務器中建立不一樣的密鑰空間(隔離緩存)頗有用。

 

5、日誌

1,在Web層NuGet安裝:Abp.Castle.Log4Net 

2,在Web層的Global.asax注入logger

    public class MvcApplication : AbpWebApplication<SimpleWebModule>
    {
        protected override void Session_Start(object sender, EventArgs e)
        {
            IocManager.Instance.IocContainer.AddFacility<LoggingFacility>(
                f => f.UseAbpLog4Net().WithConfig("log4net.config"));
            base.Session_Start(sender, e);
        }
    }

3,在Web層添加log4net配置文件(log4net.config)

<?xml version="1.0" encoding="utf-8" ?>
<log4net>
  <appender name="RollingFileAppender" type="log4net.Appender.RollingFileAppender" >
    <file value="App_Data/Logs/Logs.txt" />
    <appendToFile value="true" />
    <rollingStyle value="Size" />
    <maxSizeRollBackups value="10" />
    <maximumFileSize value="10000KB" />
    <staticLogFileName value="true" />
    <layout type="log4net.Layout.PatternLayout">
      <conversionPattern value="%-5level %date [%-5.5thread] %-40.40logger - %message%newline" />
    </layout>
  </appender>
  <root>
    <appender-ref ref="RollingFileAppender" />
    <level value="DEBUG" />
  </root>
  <logger name="NHibernate">
    <level value="WARN" />
  </logger>
</log4net>

4,在application層直接直接使用Logger

        public async Task<ListResultDto<EmployeeListDto>> GetEmpAll()
        {
            Logger.Info("查詢員工信息");
            var emps = await _employeeRepository.GetAllListAsync();
            return new ListResultDto<EmployeeListDto>(emps.MapTo<List<EmployeeListDto>>());
        }

5,客戶端

abp.log.warn('a sample log message...');

 

6、設置管理

一個設置通常是存儲在數據庫(或其餘源)的name-value字符串對。咱們能夠將非字符串的值轉換成字符串。

使用前必須定義一個設置。 ASP.NET Boilerplate設計爲模塊化。 因此,不一樣的模塊能夠有不一樣的設置

1,定義設置

public class MySettingProvider : SettingProvider
{
    public override IEnumerable<SettingDefinition> GetSettingDefinitions(SettingDefinitionProviderContext context)
    {
        return new[]
                {
                    new SettingDefinition(
                        "SmtpServerAddress",
                        "127.0.0.1"
                        ),

                    new SettingDefinition(
                        "PassiveUsersCanNotLogin",
                        "true",
                        scopes: SettingScopes.Application | SettingScopes.Tenant
                        ),

                    new SettingDefinition(
                        "SiteColorPreference",
                        "red",
                        scopes: SettingScopes.User,
                        isVisibleToClients: true
                        )

                };
    }
}

參數說明:

Name(必填):名稱

Default Name:默認值,該值能夠爲空或空字符串

Scopes:

    Application:應用範圍設置用於用戶/租戶獨立設置。 例如,咱們能夠定義一個名爲「SmtpServerAddress」的設置,以便在發送電子郵件時獲取服務器的IP地址。 若是此設置具備單個值(不根據用戶進行更改),那麼咱們能夠將其定義爲應用程序範圍。

    Tenant:若是應用程序是多租戶,咱們能夠定義特定於租戶的設置。

    User:咱們可使用用戶做用域設置來存儲/獲取每一個用戶特定設置的值。

Display Name:可用於在UI中稍後顯示設置名稱的本地化字符串。

Description:一個可本地化的字符串,可用於稍後在UI中顯示設置描述。

Group:可用於分組設置。 這只是用於UI,不用於設置管理。

IsVisibleToClients:設置爲true,使客戶端可使用設置。

 

2,建立setting provider後,咱們應該在本模塊的PreIntialize方法中註冊它:

Configuration.Settings.Providers.Add<MySettingProvider>();

因爲ISettingManager被普遍使用,一些特殊的基類(如ApplicationService,DomainService和AbpController)有一個名爲SettingManager的屬性。 若是咱們從這些類派生,不須要明確地注入它們。

 

3,獲取設置

①服務器端:

//獲取布爾值(異步調用)
var value1 = await SettingManager.GetSettingValueAsync<bool>("PassiveUsersCanNotLogin");

//獲取字符串值(同步調用)
var value2 = SettingManager.GetSettingValue("SmtpServerAddress");

②客戶端:

當定義一個setting時,若是將IsVisibleToClients設置爲true,那麼可使用javascript在客戶端得到當前的值。abp.setting命名空間定義了一些用獲得的函數和對象。例如:

var currentColor = abp.setting.get("SiteColorPreference");

也有getInt和 getBoolean 方法。你可使用abp.setting.values得到全部的值。注意:若是在服務端更改了一個setting,那麼若是頁面沒有更新,setting沒有從新加載或者經過代碼手動更新的話,那麼客戶端就不知道該setting是否發生了變化。

 

7、Timing

1,Clock類靜態屬性介紹

Now:根據當前提供商獲取當前時間

Kind:獲取當前提供者的DateTimeKind

SupportsMultipleTimezone:獲取一個值,表示當前提供程序能夠用於須要多個時區的應用程序

Normalize:對當前提供商的給定DateTime進行規範化/轉換

DateTime now = Clock.Now;

2,三種內置的clock 

ClockProviders.Unspecified (UnspecifiedClockProvider):默認的clock。就像DateTime.Now同樣

ClockProviders.Utc (UtcClockProvider):Normalize方法將給定的datetime轉換爲utc datetime,並將其設置爲DateTimeKind.UTC。 它支持多個時區。

ClockProviders.Local (LocalClockProvider):在本地計算機的時間工做。 Normalize方法將給定的datetime轉換爲本地datetime,並將其設置爲DateTimeKind.Local

您能夠設置Clock.Provider以使用不一樣的時鐘提供程序:

Clock.Provider = ClockProviders.Utc;

這一般在應用程序開始時完成(適用於Web應用程序中的Application_Start)。

 

8、ABPMapper

1,使用AbpAutoMapper

①模塊使用AbpAutoMapper須要引用依賴

namespace SimpleDemo.Application
{
    [DependsOn(typeof(SimpleDemoCoreModule), typeof(AbpAutoMapperModule))]
    public class SimpleDemoApplicationModule:AbpModule
    {
        public override void Initialize()
        {
            IocManager.RegisterAssemblyByConvention(Assembly.GetExecutingAssembly());
        }
    }
}

②使用AutoMapFrom 、AutoMapTo  兩個方向的映射

[AutoMapTo(typeof(User))]
public class CreateUserInput
{
    public string Name { get; set; }

    public string Surname { get; set; }

    public string EmailAddress { get; set; }

    public string Password { get; set; }
}
[AutoMapFrom(typeof(User))]
public class CreateUserOutput
{
    public string Name { get; set; }

    public string Surname { get; set; }

    public string EmailAddress { get; set; }

    public string Password { get; set; }
}

2,自定義映射

在某些狀況下,簡單的映射可能不合適。 例如,兩個類的屬性名可能有些不一樣,或者您可能但願在映射期間忽略某些屬性

假設咱們要忽略映射的密碼,而且用戶具備電子郵件地址的電子郵件屬性。 咱們能夠定義映射,以下所示:

[DependsOn(typeof(AbpAutoMapperModule))]
public class MyModule : AbpModule
{
    public override void PreInitialize()
    {
        Configuration.Modules.AbpAutoMapper().Configurators.Add(config =>
        {
            config.CreateMap<CreateUserInput, User>()
                  .ForMember(u => u.Password, options => options.Ignore())
                  .ForMember(u => u.Email, options => options.MapFrom(input => input.EmailAddress));
        });
    }
}

 

9、發送電子郵件

1,Abp.Net.Mail.EmailSettingNames類中定義爲常量字符串。 他們的價值觀和描述:

Abp.Net.Mail.DefaultFromAddress:在發送電子郵件時不指定發件人時,用做發件人電子郵件地址(如上面的示例所示)。
Abp.Net.Mail.DefaultFromDisplayName:在發送電子郵件時不指定發件人時,用做發件人顯示名稱(如上面的示例所示)。
Abp.Net.Mail.Smtp.Host:SMTP服務器的IP /域(默認值:127.0.0.1)。
Abp.Net.Mail.Smtp.Port:SMTP服務器的端口(默認值:25)。
Abp.Net.Mail.Smtp.UserName:用戶名,若是SMTP服務器須要身份驗證。
Abp.Net.Mail.Smtp.Password:密碼,若是SMTP服務器須要身份驗證。
Abp.Net.Mail.Smtp.Domain:域名用戶名,若是SMTP服務器須要身份驗證。
Abp.Net.Mail.Smtp.EnableSsl:一個值表示SMTP服務器使用SSL(「true」或「false」),默認值爲「false」)。
Abp.Net.Mail.Smtp.UseDefaultCredentials:True,使用默認憑據而不是提供的用戶名和密碼(「true」或「false」)。默認值爲「true」)。

2,使用qq郵箱發送郵件

①須要在qq郵箱中開啓smtp服務

②配置設置

namespace Demo.Core.Email
{
    public class AppSettingProvider:SettingProvider
    {
        public override IEnumerable<SettingDefinition> GetSettingDefinitions(SettingDefinitionProviderContext context)
        {
            return new[]
            {
                //EnableSsl必定設置爲true
                new SettingDefinition( EmailSettingNames.Smtp.EnableSsl,"true",L("SmtpEnableSsl"),scopes:SettingScopes.Application|SettingScopes.Tenant),
                //UseDefaultCredentials必定設置爲false
                new SettingDefinition( EmailSettingNames.Smtp.UseDefaultCredentials,"false",L("SmtpUseDefaultCredentials"),scopes:SettingScopes.Application|SettingScopes.Tenant),
                new SettingDefinition( EmailSettingNames.Smtp.Host,"smtp.qq.com",L("SmtpHost"),scopes:SettingScopes.Application|SettingScopes.Tenant),
                new SettingDefinition( EmailSettingNames.Smtp.Port,"25",L("SmtpPort"),scopes:SettingScopes.Application|SettingScopes.Tenant),
                new SettingDefinition( EmailSettingNames.Smtp.UserName,"962410314@qq.com",L("SmtpUserName"),scopes:SettingScopes.Application|SettingScopes.Tenant),
                //Password使用受權碼
                new SettingDefinition( EmailSettingNames.Smtp.Password,"aaa",L("SmtpPassword"),scopes:SettingScopes.Application|SettingScopes.Tenant),
                //這個EmailSettingNames.Smtp.Domain設置後總是報錯,索性直接註銷了
                //new SettingDefinition( EmailSettingNames.Smtp.Domain,"962410314@qq.com",L("SmtpDomain"),scopes:SettingScopes.Application|SettingScopes.Tenant),
                new SettingDefinition( EmailSettingNames.DefaultFromAddress,"962410314@qq.com",L("SmtpDefaultFromAddress"),scopes:SettingScopes.Application|SettingScopes.Tenant),
                new SettingDefinition( EmailSettingNames.DefaultFromDisplayName,"默認",L("SmtpDefaultFromDisplayName"),scopes:SettingScopes.Application|SettingScopes.Tenant),
            };
        }

        private static LocalizableString L(string name)
        {
            return new LocalizableString(name, AbpConsts.LocalizationSourceName);
        }
    }
}

③在PreInitialize中註冊

namespace Demo.Core
{
    public class DemoCoreModule:AbpModule
    {
        public override void PreInitialize()
        {
            Configuration.Settings.Providers.Add<AppSettingProvider>();
        }

        public override void Initialize()
        {
            IocManager.RegisterAssemblyByConvention(Assembly.GetExecutingAssembly());
        }
    }
}

④發送郵件

namespace Demo.Core.Employee
{
    public class EmployeeManager:IDomainService
    {
        private readonly IEmailSender _emailSender;
        public EmployeeManager(IEmailSender emailSender)
        {
            _emailSender = emailSender;
        }
        public void send()
        {
            _emailSender.Send(to: "qq962410314@163.com", subject: "測試", body: "測試", isBodyHtml: false);
        }
    }
}

案例下載:http://pan.baidu.com/s/1dF5wp9J

10、實體

實體具備Id並存儲在數據庫中, 實體一般映射到關係數據庫的表。

1,審計接口

①當Entity被插入到實現該接口的數據庫中時,ASP.NET Boilerplate會自動將CreationTime設置爲當前時間。

public interface IHasCreationTime
{
    DateTime CreationTime { get; set; }
}

②ASP.NET Boilerplate在保存新實體時自動將CreatorUserId設置爲當前用戶的id

public interface ICreationAudited : IHasCreationTime
{
    long? CreatorUserId { get; set; }
}

③編輯時間,編輯人員

public interface IHasModificationTime
{
    DateTime? LastModificationTime { get; set; }
}

public interface IModificationAudited : IHasModificationTime
{
    long? LastModifierUserId { get; set; }
}

④若是要實現全部審計屬性,能夠直接實現IAudited接口:

public interface IAudited : ICreationAudited, IModificationAudited
{

}

⑤能夠直接繼承AuditedEntity審計類

注意:ASP.NET Boilerplate從ABP Session獲取當前用戶的Id。

⑥軟刪除

public interface ISoftDelete
{
    bool IsDeleted { get; set; }
}
public interface IDeletionAudited : ISoftDelete
{
    long? DeleterUserId { get; set; }

    DateTime? DeletionTime { get; set; }
}

⑦全部審計接口

public interface IFullAudited : IAudited, IDeletionAudited
{

}

⑧直接使用全部審計類FullAuditedEntity 

2,繼承IExtendableObject接口存儲json字段

public class Person : Entity, IExtendableObject
{
    public string Name { get; set; }

    public string ExtensionData { get; set; }

    public Person(string name)
    {
        Name = name;
    }
}
var person = new Person("John");
//存入數據庫中的值:{"CustomData":{"Value1":42,"Value2":"forty-two"},"RandomValue":178}
person.SetData("RandomValue", RandomHelper.GetRandom(1, 1000)); 
person.SetData(
"CustomData", new MyCustomObject { Value1 = 42, Value2 = "forty-two" });
var randomValue = person.GetData<int>("RandomValue");
var customData = person.GetData<MyCustomObject>("CustomData");

 

11、值對象

與實體相反,實體擁有身份標識(id),而值對象沒有。例如地址(這是一個經典的Value Object)類,若是兩個地址有相同的國家/地區,城市,街道號等等,它們被認爲是相同的地址。

public class Address : ValueObject<Address>
{
    public Guid CityId { get; private set; } //A reference to a City entity.

    public string Street { get; private set; }

    public int Number { get; private set; }

    public Address(Guid cityId, string street, int number)
    {
        CityId = cityId;
        Street = street;
        Number = number;
    }
}

值對象基類覆蓋了相等運算符(和其餘相關的運算符和方法)來比較兩個值對象

var address1 = new Address(new Guid("21C67A65-ED5A-4512-AA29-66308FAAB5AF"), "Baris Manco Street", 42);
var address2 = new Address(new Guid("21C67A65-ED5A-4512-AA29-66308FAAB5AF"), "Baris Manco Street", 42);

Assert.Equal(address1, address2);
Assert.Equal(address1.GetHashCode(), address2.GetHashCode());
Assert.True(address1 == address2);
Assert.False(address1 != address2);

12、倉儲

是領域層與數據訪問層的中介。每一個實體(或聚合根)對應一個倉儲

在領域層中定義倉儲接口,在基礎設施層實現

1,自定義倉儲接口

public interface IPersonRepository : IRepository<Person>
{
}
public interface IPersonRepository : IRepository<Person, long>
{
}

2,基類倉儲接口方法

①獲取單個實體

TEntity Get(TPrimaryKey id);
Task<TEntity> GetAsync(TPrimaryKey id);
TEntity Single(Expression<Func<TEntity, bool>> predicate);
Task<TEntity> SingleAsync(Expression<Func<TEntity, bool>> predicate);
TEntity FirstOrDefault(TPrimaryKey id);
Task<TEntity> FirstOrDefaultAsync(TPrimaryKey id);
TEntity FirstOrDefault(Expression<Func<TEntity, bool>> predicate);
Task<TEntity> FirstOrDefaultAsync(Expression<Func<TEntity, bool>> predicate);
TEntity Load(TPrimaryKey id);//不會從數據庫中檢索實體,而是延遲加載。 (它在NHibernate中實現。 若是ORM提供程序未實現,Load方法與Get方法徹底相同)

Get方法用於獲取具備給定主鍵(Id)的實體。 若是數據庫中沒有給定Id的實體,它將拋出異常。 Single方法與Get相似,但須要一個表達式而不是Id。 因此,您能夠編寫一個lambda表達式來獲取一個實體。 示例用法:

var person = _personRepository.Get(42);
var person = _personRepository.Single(p => p.Name == "John");//請注意,若是沒有給定條件的實體或有多個實體,Single方法將拋出異常。

 

②獲取實體列表

List<TEntity> GetAllList();
Task<List<TEntity>> GetAllListAsync();
List<TEntity> GetAllList(Expression<Func<TEntity, bool>> predicate);
Task<List<TEntity>> GetAllListAsync(Expression<Func<TEntity, bool>> predicate);
IQueryable<TEntity> GetAll();

GetAllList用於從數據庫檢索全部實體。 過載可用於過濾實體。 例子:

var allPeople = _personRepository.GetAllList();
var somePeople = _personRepository.GetAllList(person => person.IsActive && person.Age > 42);

GetAll返回IQueryable <T>。 因此,你能夠添加Linq方法。 例子:

//Example 1
var query = from person in _personRepository.GetAll()
            where person.IsActive
            orderby person.Name
            select person;
var people = query.ToList();

//Example 2:
List<Person> personList2 = _personRepository.GetAll().Where(p => p.Name.Contains("H")).OrderBy(p => p.Name).Skip(40).Take(20).ToList();

③Insert

TEntity Insert(TEntity entity);
Task<TEntity> InsertAsync(TEntity entity);
TPrimaryKey InsertAndGetId(TEntity entity);//方法返回新插入實體的ID
Task<TPrimaryKey> InsertAndGetIdAsync(TEntity entity);
TEntity InsertOrUpdate(TEntity entity);//經過檢查其Id值來插入或更新給定實體
Task<TEntity> InsertOrUpdateAsync(TEntity entity);
TPrimaryKey InsertOrUpdateAndGetId(TEntity entity);//在插入或更新後返回實體的ID
Task<TPrimaryKey> InsertOrUpdateAndGetIdAsync(TEntity entity);

④Update

TEntity Update(TEntity entity);
Task<TEntity> UpdateAsync(TEntity entity);

大多數狀況下,您不須要顯式調用Update方法,由於在工做單元完成後,工做單元會自動保存全部更改

⑤Delete

void Delete(TEntity entity);
Task DeleteAsync(TEntity entity);
void Delete(TPrimaryKey id);
Task DeleteAsync(TPrimaryKey id);
void Delete(Expression<Func<TEntity, bool>> predicate);
Task DeleteAsync(Expression<Func<TEntity, bool>> predicate);

⑥ASP.NET Boilerplate支持異步編程模型。 因此,存儲庫方法有Async版本。 這裏,使用異步模型的示例應用程序服務方法:

public class PersonAppService : AbpWpfDemoAppServiceBase, IPersonAppService
{
    private readonly IRepository<Person> _personRepository;

    public PersonAppService(IRepository<Person> personRepository)
    {
        _personRepository = personRepository;
    }

    public async Task<GetPeopleOutput> GetAllPeople()
    {
        var people = await _personRepository.GetAllListAsync();

        return new GetPeopleOutput
        {
            People = Mapper.Map<List<PersonDto>>(people)
        };
    }
}

自定義存儲庫方法不該包含業務邏輯或應用程序邏輯。 它應該只是執行與數據有關的或orm特定的任務。

  

十3、領域服務

領域服務(或DDD中的服務)用於執行領域操做和業務規則。Eric Evans描述了一個好的服務應該具有下面三個特徵:

  1. 和領域概念相關的操做不是一個實體或者值對象的本質部分。
  2. 該接口是在領域模型的其餘元素來定義的。
  3. 操做是無狀態的。

 1,例子(假如咱們有一個任務系統,而且將任務分配給一我的時,咱們有業務規則):

咱們在這裏有兩個業務規則:

①任務應處於活動狀態,以將其分配給新的人員。
②一我的最多能夠有3個活動任務。

public interface ITaskManager : IDomainService
{
    void AssignTaskToPerson(Task task, Person person);//將任務分配給人
}
public class TaskManager : DomainService, ITaskManager
{
    public const int MaxActiveTaskCountForAPerson = 3;

    private readonly ITaskRepository _taskRepository;

    public TaskManager(ITaskRepository taskRepository)
    {
        _taskRepository = taskRepository;
    }

    public void AssignTaskToPerson(Task task, Person person)
    {
        if (task.AssignedPersonId == person.Id)
        {
            return;
        }

        if (task.State != TaskState.Active)
        {
       //認爲這個一個應用程序錯誤
throw new ApplicationException("Can not assign a task to a person when task is not active!"); } if (HasPersonMaximumAssignedTask(person)) {
       //向用戶展現錯誤
throw new UserFriendlyException(L("MaxPersonTaskLimitMessage", person.Name)); } task.AssignedPersonId = person.Id; } private bool HasPersonMaximumAssignedTask(Person person) { var assignedTaskCount = _taskRepository.Count(t => t.State == TaskState.Active && t.AssignedPersonId == person.Id); return assignedTaskCount >= MaxActiveTaskCountForAPerson; } }
public class TaskAppService : ApplicationService, ITaskAppService
{
    private readonly IRepository<Task, long> _taskRepository;
    private readonly IRepository<Person> _personRepository;
    private readonly ITaskManager _taskManager;

    public TaskAppService(IRepository<Task, long> taskRepository, IRepository<Person> personRepository, ITaskManager taskManager)
    {
        _taskRepository = taskRepository;
        _personRepository = personRepository;
        _taskManager = taskManager;
    }

    public void AssignTaskToPerson(AssignTaskToPersonInput input)
    {
        var task = _taskRepository.Get(input.TaskId);
        var person = _personRepository.Get(input.PersonId);

        _taskManager.AssignTaskToPerson(task, person);
    }
}

2,強制使用領域服務

將任務實體設計成這樣:

public class Task : Entity<long>
{
    public virtual int? AssignedPersonId { get; protected set; }

    //...other members and codes of Task entity

    public void AssignToPerson(Person person, ITaskPolicy taskPolicy)
    {
        taskPolicy.CheckIfCanAssignTaskToPerson(this, person);
        AssignedPersonId = person.Id;
    }
}

咱們將AssignedPersonId的setter更改成protected。 因此,這個Task實體類不能被修改。 添加了一個AssignToPerson方法,該方法接受人員和任務策略。 CheckIfCanAssignTaskToPerson方法檢查它是不是一個有效的賦值,若是沒有,則拋出正確的異常(這裏的實現不重要)。 那麼應用服務方式就是這樣的:

public void AssignTaskToPerson(AssignTaskToPersonInput input)
{
    var task = _taskRepository.Get(input.TaskId);
    var person = _personRepository.Get(input.PersonId);

    task.AssignToPerson(person, _taskPolicy);
}

 

十4、規格模式

規格模式是一種特定的軟件設計模式。經過使用布爾邏輯將業務規則連接在一塊兒能夠重組業務規則。在實際中,它主要用於爲實體或其餘業務對象定義可重用的過濾器

Abp定義了規範接口,和實現。使用時只須要繼承Specification<T>

public interface ISpecification<T>
{
    bool IsSatisfiedBy(T obj);

    Expression<Func<T, bool>> ToExpression();
}
//擁有100,000美圓餘額的客戶被認爲是PREMIUM客戶
public class PremiumCustomerSpecification : Specification<Customer>
{
    public override Expression<Func<Customer, bool>> ToExpression()
    {
        return (customer) => (customer.Balance >= 100000);
    }
}

//參數規範示例。
public class CustomerRegistrationYearSpecification : Specification<Customer>
{
    public int Year { get; }

    public CustomerRegistrationYearSpecification(int year)
    {
        Year = year;
    }

    public override Expression<Func<Customer, bool>> ToExpression()
    {
        return (customer) => (customer.CreationYear == Year);
    }
}
public class CustomerManager
{
    private readonly IRepository<Customer> _customerRepository;

    public CustomerManager(IRepository<Customer> customerRepository)
    {
        _customerRepository = customerRepository;
    }

    public int GetCustomerCount(ISpecification<Customer> spec)
    {
        return _customerRepository.Count(spec.ToExpression());
    }
}
count = customerManager.GetCustomerCount(new PremiumCustomerSpecification());
count = customerManager.GetCustomerCount(new CustomerRegistrationYearSpecification(2017));
var count = customerManager.GetCustomerCount(new PremiumCustomerSpecification().And(new CustomerRegistrationYearSpecification(2017)));

咱們甚至能夠從現有規範中建立一個新的規範類:

public class NewPremiumCustomersSpecification : AndSpecification<Customer>
{
    public NewPremiumCustomersSpecification() 
        : base(new PremiumCustomerSpecification(), new CustomerRegistrationYearSpecification(2017))
    {
    }
}

AndSpecification類是Specification類的一個子類,只有當兩個規範都知足時才能知足。使用以下

var count = customerManager.GetCustomerCount(new NewPremiumCustomersSpecification());

通常不須要使用規範類,可直接使用lambda表達式

var count = _customerRepository.Count(c => c.Balance > 100000 && c.CreationYear == 2017);

 

十5、工做單元

若是工做方法單元調用另外一個工做單元方法,則使用相同的鏈接和事務。 第一個進入方法管理鏈接和事務,其餘的使用它。

應用服務方法默認是工做單元。方法開始啓動事務,方法結束提交事務。若是發生異常則回滾。這樣應用服務方法中的全部數據庫操做都將變爲原子(工做單元)

1,顯示使用工做單元

①在方法上引用[UnitOfWork]特性。若是是應用服務方法,則不須要應用此特性

②使用IUnitOfWorkManager

public class MyService
{
    private readonly IUnitOfWorkManager _unitOfWorkManager;
    private readonly IPersonRepository _personRepository;
    private readonly IStatisticsRepository _statisticsRepository;

    public MyService(IUnitOfWorkManager unitOfWorkManager, IPersonRepository personRepository, IStatisticsRepository statisticsRepository)
    {
        _unitOfWorkManager = unitOfWorkManager;
        _personRepository = personRepository;
        _statisticsRepository = statisticsRepository;
    }

    public void CreatePerson(CreatePersonInput input)
    {
        var person = new Person { Name = input.Name, EmailAddress = input.EmailAddress };

        using (var unitOfWork = _unitOfWorkManager.Begin())
        {
            _personRepository.Insert(person);
            _statisticsRepository.IncrementPeopleCount();

            unitOfWork.Complete();
        }
    }
}

2,禁用工做單元

[UnitOfWork(IsDisabled = true)]
public virtual void RemoveFriendship(RemoveFriendshipInput input)
{
    _friendshipRepository.Delete(input.Id);
}

3,禁用事務功能

[UnitOfWork(isTransactional: false)]
public GetTasksOutput GetTasks(GetTasksInput input)
{
    var tasks = _taskRepository.GetAllWithPeople(input.AssignedPersonId, input.State);
    return new GetTasksOutput
            {
                Tasks = Mapper.Map<List<TaskDto>>(tasks)
            };
}

4,自動保存更改
若是一個方法是工做單元,ASP.NET Boilerplate會自動在方法結束時保存全部更改。 假設咱們須要更新一我的的名字的方法:

[UnitOfWork]
public void UpdateName(UpdateNameInput input)
{
    var person = _personRepository.Get(input.PersonId);
    person.Name = input.NewName;
}

5,更改工做單元配置

①一般在PreInitialize方法中完成

public class SimpleTaskSystemCoreModule : AbpModule
{
    public override void PreInitialize()
    {
        Configuration.UnitOfWork.IsolationLevel = IsolationLevel.ReadCommitted;
        Configuration.UnitOfWork.Timeout = TimeSpan.FromMinutes(30);
    }

    //...other module methods
}

6,若是訪問工做單元

①若是您的類派生自某些特定的基類(ApplicationService,DomainService,AbpController,AbpApiController ...等),則能夠直接使用CurrentUnitOfWork屬性。
②您能夠將IUnitOfWorkManager注入任何類並使用IUnitOfWorkManager.Current屬性。

6,活動

工做單位已完成,失敗和處理事件。 您能夠註冊這些事件並執行所需的操做。 例如,您可能但願在當前工做單元成功完成時運行一些代碼。 例:

public void CreateTask(CreateTaskInput input)
{
    var task = new Task { Description = input.Description };

    if (input.AssignedPersonId.HasValue)
    {
        task.AssignedPersonId = input.AssignedPersonId.Value;
        _unitOfWorkManager.Current.Completed += (sender, args) => { /* TODO: Send email to assigned person */ };
    }

    _taskRepository.Insert(task);
}

 

十6、事件總線

1,兩種方式使用事件總線

①依賴注入IEventBus(屬性注入比構造函數注入更適合於注入事件總線)

public class TaskAppService : ApplicationService
{
    public IEventBus EventBus { get; set; }

    public TaskAppService()
    {
        EventBus = NullEventBus.Instance;//NullEventBus實現空對象模式。 當你調用它的方法時,它什麼都不作
    }
}

②獲取默認實例(不建議直接使用EventBus.Default,由於它使得單元測試變得更加困難。)

若是不能注入,能夠直接使用EventBus.Default

EventBus.Default.Trigger(...); //trigger an event

2,定義事件( EventData類定義EventSource(哪一個對象觸發事件)和EventTime(觸發時)屬性)

在觸發事件以前,應首先定義事件。 事件由派生自EventData的類表示。 假設咱們要在任務完成時觸發事件:

public class TaskCompletedEventData : EventData
{
    public int TaskId { get; set; }
}

3,預約義事件

①AbpHandledExceptionData:任何異常時觸發此事件

②實體變動
還有用於實體更改的通用事件數據類:EntityCreatingEventData <TEntity>,EntityCreatedEventData <TEntity>,EntityUpdatingEventData <TEntity>,EntityUpdatedEventData <TEntity>,EntityDeletingEventData <TEntity>和EntityDeletedEventData <TEntity>。 另外還有EntityChangingEventData <TEntity>和EntityChangedEventData <TEntity>。 能夠插入,更新或刪除更改。

"ing":保存以前

"ed":保存以後

4,觸發事件

public class TaskAppService : ApplicationService
{
    public IEventBus EventBus { get; set; }

    public TaskAppService()
    {
        EventBus = NullEventBus.Instance;
    }

    public void CompleteTask(CompleteTaskInput input)
    {
        //TODO: complete the task on database...
        EventBus.Trigger(new TaskCompletedEventData {TaskId = 42});
    }
}
EventBus.Trigger<TaskCompletedEventData>(new TaskCompletedEventData { TaskId = 42 }); //明確地聲明泛型參數
EventBus.Trigger(this, new TaskCompletedEventData { TaskId = 42 }); //將「事件源」設置爲「this」
EventBus.Trigger(typeof(TaskCompletedEventData), this, new TaskCompletedEventData { TaskId = 42 }); //調用非泛型版本(第一個參數是事件類的類型)
public class ActivityWriter : IEventHandler<TaskCompletedEventData>, ITransientDependency
{
    public void HandleEvent(TaskCompletedEventData eventData)
    {
        WriteActivity("A task is completed by id = " + eventData.TaskId);
    }
}

5,處理多個事件

public class ActivityWriter : 
    IEventHandler<TaskCompletedEventData>, 
    IEventHandler<TaskCreatedEventData>, 
    ITransientDependency
{
    public void HandleEvent(TaskCompletedEventData eventData)
    {
        //TODO: handle the event...
    }

    public void HandleEvent(TaskCreatedEventData eventData)
    {
        //TODO: handle the event...
    }
}

 

十7、數據過濾器

1,ISoftDelete軟刪除接口

public class Person : Entity, ISoftDelete
{
    public virtual string Name { get; set; }

    public virtual bool IsDeleted { get; set; }
}

使用IRepository.Delete方法時將IsDeleted屬性設置爲true。_personRepository.GetAllList()不會查詢出軟刪除的數據

注:若是您實現IDeletionAudited(擴展了ISoftDelete),則刪除時間和刪除用戶標識也由ASP.NET Boilerplate自動設置。

2, IMustHaveTenant

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

    public string Name { get; set; }
}

IMustHaveTenant定義TenantId來區分不一樣的租戶實體。 ASP.NET Boilerplate默認使用IAbpSession獲取當前TenantId,並自動過濾當前租戶的查詢。

若是當前用戶未登陸到系統,或者當前用戶是主機用戶(主機用戶是可管理租戶和租戶數據的上級用戶),ASP.NET Boilerplate將自動禁用IMustHaveTenant過濾器。 所以,全部租戶的全部數據均可以被檢索到應用程序

3,IMayHaveTenant(沒有IMustHaveTenant經常使用)

public class Role : Entity, IMayHaveTenant
{
    public int? TenantId { get; set; }

    public string RoleName { get; set; }
}

空值表示這是主機實體,非空值表示由租戶擁有的該實體,其ID爲TenantId

4,禁用過濾器

var people1 = _personRepository.GetAllList();//訪問未刪除的

using (_unitOfWorkManager.Current.DisableFilter(AbpDataFilters.SoftDelete))
{
    var people2 = _personRepository.GetAllList(); //訪問全部的        
}

var people3 = _personRepository.GetAllList();//訪問未刪除的

5,全局禁用過濾器
若是須要,能夠全局禁用預約義的過濾器。 例如,要全局禁用軟刪除過濾器,請將此代碼添加到模塊的PreInitialize方法中:

Configuration.UnitOfWork.OverrideFilter(AbpDataFilters.SoftDelete, false);

 6,自定義過濾器

①定義過濾字段

public interface IHasPerson
{
    int PersonId { get; set; }
}

②實體實現接口

public class Phone : Entity, IHasPerson
{
    [ForeignKey("PersonId")]
    public virtual Person Person { get; set; }
    public virtual int PersonId { get; set; }

    public virtual string Number { get; set; }
}

③定義過濾器

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

    modelBuilder.Filter("PersonFilter", (IHasPerson entity, int personId) => entity.PersonId == personId, 0);
}

「PersonFilter」是此處過濾器的惟一名稱。 第二個參數定義了過濾器接口和personId過濾器參數(若是過濾器不是參數,則不須要),最後一個參數是personId的默認值。

最後,咱們必須在本模塊的PreInitialize方法中向ASP.NET Boilerplate的工做單元註冊此過濾器:

Configuration.UnitOfWork.RegisterFilter("PersonFilter", false);

第一個參數是咱們以前定義的惟一的名稱。 第二個參數表示默認狀況下是啓用仍是禁用此過濾器

using (CurrentUnitOfWork.EnableFilter("PersonFilter"))
{
    using(CurrentUnitOfWork.SetFilterParameter("PersonFilter", "personId", 42))
    {
        var phones = _phoneRepository.GetAllList();
        //...
    }
}

咱們能夠從某個來源獲取personId,而不是靜態編碼。 以上示例是參數化過濾器。 濾波器能夠有零個或多個參數。 若是沒有參數,則不須要設置過濾器參數值。 此外,若是默認狀況下啓用,則不須要手動啓用它(固然,咱們能夠禁用它)。

 

十8、應用服務

1,CrudAppService和AsyncCrudAppService類

若是您須要建立一個應用程序服務,該服務將爲特定實體建立「建立」,「更新」,「刪除」,「獲取」GetAll方法,則能夠從CrudAppService繼承(若是要建立異步方法,則能夠繼承AsyncCrudAppService)類來建立它。 CrudAppService基類是通用的,它將相關的實體和DTO類型做爲通用參數,而且是可擴展的,容許您在須要自定義時重寫功能。

①實體類

public class Task : Entity, IHasCreationTime
{
    public string Title { get; set; }

    public string Description { get; set; }

    public DateTime CreationTime { get; set; }

    public TaskState State { get; set; }

    public Person AssignedPerson { get; set; }
    public Guid? AssignedPersonId { get; set; }

    public Task()
    {
        CreationTime = Clock.Now;
        State = TaskState.Open;
    }
}

②建立DTO

[AutoMap(typeof(Task))]
public class TaskDto : EntityDto, IHasCreationTime
{
    public string Title { get; set; }

    public string Description { get; set; }

    public DateTime CreationTime { get; set; }

    public TaskState State { get; set; }

    public Guid? AssignedPersonId { get; set; }

    public string AssignedPersonName { get; set; }
}

③應用服務

public class TaskAppService : AsyncCrudAppService<Task, TaskDto>,ITaskAppService
 { public TaskAppService(IRepository<Task> repository) : base(repository) { } }

④應用服務接口

public interface ITaskAppService : IAsyncCrudAppService<TaskDto>
{
        
}

2,自定義CURD應用服務

1,查詢

PagedAndSortedResultRequestDto,它提供可選的排序和分頁參數。能夠定義派生類過濾

public class GetAllTasksInput : PagedAndSortedResultRequestDto
{
    public TaskState? State { get; set; }
}

如今,咱們應該更改TaskAppService以應用自定義過濾器

public class TaskAppService : AsyncCrudAppService<Task, TaskDto, int, GetAllTasksInput>
{
    public TaskAppService(IRepository<Task> repository)
        : base(repository)
    {

    }

    protected override IQueryable<Task> CreateFilteredQuery(GetAllTasksInput input)
    {
        return base.CreateFilteredQuery(input)
            .WhereIf(input.State.HasValue, t => t.State == input.State.Value);
    }
}

2,建立和更新

建立一個CreateTaskInput類 

[AutoMapTo(typeof(Task))]
public class CreateTaskInput
{
    [Required]
    [MaxLength(Task.MaxTitleLength)]
    public string Title { get; set; }

    [MaxLength(Task.MaxDescriptionLength)]
    public string Description { get; set; }

    public Guid? AssignedPersonId { get; set; }
}

並建立一個UpdateTaskInput類

[AutoMapTo(typeof(Task))]
public class UpdateTaskInput : CreateTaskInput, IEntityDto
{
    public int Id { get; set; }

    public TaskState State { get; set; }
}

如今,咱們能夠將這些DTO類做爲AsyncCrudAppService類的通用參數,以下所示:

public class TaskAppService : AsyncCrudAppService<Task, TaskDto, int, GetAllTasksInput, CreateTaskInput, UpdateTaskInput>
{
    public TaskAppService(IRepository<Task> repository)
        : base(repository)
    {

    }

    protected override IQueryable<Task> CreateFilteredQuery(GetAllTasksInput input)
    {
        return base.CreateFilteredQuery(input)
            .WhereIf(input.State.HasValue, t => t.State == input.State.Value);
    }
}

3,CURD權限

您可能須要受權您的CRUD方法。 您能夠設置預約義的權限屬性:GetPermissionName,GetAllPermissionName,CreatePermissionName,UpdatePermissionName和DeletePermissionName。 若是您設置它們,基本CRUD類將自動檢查權限。 您能夠在構造函數中設置它,以下所示:

public class TaskAppService : AsyncCrudAppService<Task, TaskDto>
{
    public TaskAppService(IRepository<Task> repository) 
        : base(repository)
    {
        CreatePermissionName = "MyTaskCreationPermission";
    }
}

或者,您能夠覆蓋適當的權限檢查方法來手動檢查權限:CheckGetPermission(),CheckGetAllPermission(),CheckCreatePermission(),CheckUpdatePermission(),CheckDeletePermission()。 默認狀況下,它們都調用具備相關權限名稱的CheckPermission(...)方法,該方法只需調用IPermissionChecker.Authorize(...)方法便可。

 

十9、數據傳輸對象

1,幫助接口和類

ILimitedResultRequest定義MaxResultCount屬性。 所以,您能夠在輸入的DTO中實現它,以便對限制結果集進行標準化。

IPagedResultRequest經過添加SkipCount擴展ILimitedResultRequest。 因此,咱們能夠在SearchPeopleInput中實現這個接口進行分頁:

public class SearchPeopleInput : IPagedResultRequest
{
    [StringLength(40, MinimumLength = 1)]
    public string SearchedName { get; set; }

    public int MaxResultCount { get; set; }
    public int SkipCount { get; set; }
}

 

二10、驗證數據傳輸對象

1,使用數據註解(System.ComponentModel.DataAnnotations

public class CreateTaskInput
{
    public int? AssignedPersonId { get; set; }

    [Required]
    public string Description { get; set; }
}

2,自定義驗證

public class CreateTaskInput : ICustomValidate
{
    public int? AssignedPersonId { get; set; }

    public bool SendEmailToAssignedPerson { get; set; }

    [Required]
    public string Description { get; set; }

    public void AddValidationErrors(CustomValidatationContext context)
    {
        if (SendEmailToAssignedPerson && (!AssignedPersonId.HasValue || AssignedPersonId.Value <= 0))
        {
            context.Results.Add(new ValidationResult("AssignedPersonId must be set if SendEmailToAssignedPerson is true!"));
        }
    }
}

3,Normalize方法在驗證以後調用(並在調用方法以前)

public class GetTasksInput : IShouldNormalize
{
    public string Sorting { get; set; }

    public void Normalize()
    {
        if (string.IsNullOrWhiteSpace(Sorting))
        {
            Sorting = "Name ASC";
        }
    }
}

 

二11、受權

1,定義權限

①不一樣的模塊能夠有不一樣的權限。 一個模塊應該建立一個派生自AuthorizationProvider的類來定義它的權限

public class MyAuthorizationProvider : AuthorizationProvider
{
    public override void SetPermissions(IPermissionDefinitionContext context)
    {
        var administration = context.CreatePermission("Administration");

        var userManagement = administration.CreateChildPermission("Administration.UserManagement");
        userManagement.CreateChildPermission("Administration.UserManagement.CreateUser");

        var roleManagement = administration.CreateChildPermission("Administration.RoleManagement");
    }
}
    

IPermissionDefinitionContext屬性定義:

Name:一個系統內的惟一名稱, 最好爲權限名稱

Display name:一個可本地化的字符串,能夠在UI中稍後顯示權限

Description:一個可本地化的字符串,可用於顯示權限的定義,稍後在UI中

MultiTenancySides:對於多租戶申請,租戶或主機可使用許可。 這是一個Flags枚舉,所以能夠在雙方使用權限。

featureDependency:可用於聲明對功能的依賴。 所以,僅當知足特徵依賴性時,才容許該權限。 它等待一個對象實現IFeatureDependency。 默認實現是SimpleFeatureDependency類。 示例用法:new SimpleFeatureDependency(「MyFeatureName」)

②權限能夠具備父權限和子級權限。 雖然這並不影響權限檢查,但可能有助於在UI中分組權限。

③建立受權提供者後,咱們應該在咱們的模塊的PreInitialize方法中註冊它:

Configuration.Authorization.Providers.Add<MyAuthorizationProvider>();

2,檢查權限

①使用AbpAuthorize特性

[AbpAuthorize("Administration.UserManagement.CreateUser")]
public void CreateUser(CreateUserInput input)
{
    //若是未授予「Administration.UserManagement.CreateUser」權限,用戶將沒法執行此方法。
}

AbpAuthorize屬性還會檢查當前用戶是否已登陸(使用IAbpSession.UserId)。 因此,若是咱們爲一個方法聲明一個AbpAuthorize,它只檢查登陸:

[AbpAuthorize]
public void SomeMethod(SomeMethodInput input)
{
    //若是用戶沒法登陸,則沒法執行此方法。
}

②Abp受權屬性說明

ASP.NET Boilerplate使用動態方法截取功能進行受權。 因此方法使用AbpAuthorize屬性有一些限制。

不能用於私有方法。
不能用於靜態方法。
不能用於非注入類的方法(咱們必須使用依賴注入)。

此外

若是方法經過接口調用(如經過接口使用的應用程序服務),能夠將其用於任何公共方法。
若是直接從類引用(如ASP.NET MVC或Web API控制器)調用,則該方法應該是虛擬的。
若是保護方法應該是虛擬的。

注意:有四種類型的受權屬性:

在應用服務(應用層)中,咱們使用Abp.Authorization.AbpAuthorize屬性。
在MVC控制器(Web層)中,咱們使用Abp.Web.Mvc.Authorization.AbpMvcAuthorize屬性。
在ASP.NET Web API中,咱們使用Abp.WebApi.Authorization.AbpApiAuthorize屬性。
在ASP.NET Core中,咱們使用Abp.AspNetCore.Mvc.Authorization.AbpMvcAuthorize屬性。

③禁止受權

您能夠經過將AbpAllowAnonymous屬性添加到應用程序服務來禁用方法/類的受權。 對MVC,Web API和ASP.NET核心控制器使用AllowAnonymous,這些框架是這些框架的本機屬性。

④使用IPermissionChecker 

雖然AbpAuthorize屬性在大多數狀況下足夠好,可是必須有一些狀況須要檢查方法體中的權限。 咱們能夠注入和使用IPermissionChecker,以下例所示:

public void CreateUser(CreateOrUpdateUserInput input)
{
    if (!PermissionChecker.IsGranted("Administration.UserManagement.CreateUser"))
    {
        throw new AbpAuthorizationException("您無權建立用戶!");
    }
    //若是用戶未授予「Administration.UserManagement.CreateUser」權限,則沒法達到此目的。
}

固然,您能夠編寫任何邏輯,由於IsGranted只返回true或false(也有Async版本)。 若是您只是檢查權限並拋出如上所示的異常,則可使用Authorize方法:

public void CreateUser(CreateOrUpdateUserInput input)
{
    PermissionChecker.Authorize("Administration.UserManagement.CreateUser");
    //若是用戶未授予「Administration.UserManagement.CreateUser」權限,則沒法達到此目的。
}

因爲受權被普遍使用,ApplicationService和一些常見的基類注入並定義了PermissionChecker屬性。 所以,能夠在不注入應用程序服務類的狀況下使用權限檢查器。

⑤Razor 視圖

基本視圖類定義IsGranted方法來檢查當前用戶是否具備權限。 所以,咱們能夠有條件地呈現視圖。 例:

@if (IsGranted("Administration.UserManagement.CreateUser"))
{
    <button id="CreateNewUserButton" class="btn btn-primary"><i class="fa fa-plus"></i> @L("CreateNewUser")</button>
}

客戶端(Javascript)
在客戶端,咱們可使用在abp.auth命名空間中定義的API。 在大多數狀況下,咱們須要檢查當前用戶是否具備特定權限(具備權限名稱)。 例:

abp.auth.isGranted('Administration.UserManagement.CreateUser');

您還可使用abp.auth.grantedPermissions獲取全部授予的權限,或者使用abp.auth.allPermissions獲取應用程序中的全部可用權限名稱。 在運行時檢查其餘人的abp.auth命名空間。

 

二12、功能管理

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

1,定義功能
檢查前應定義特徵。 模塊能夠經過從FeatureProvider類派生來定義本身的特徵。 在這裏,一個很是簡單的功能提供者定義了3個功能:

public class AppFeatureProvider : FeatureProvider
{
    public override void SetFeatures(IFeatureDefinitionContext context)
    {
     //識別功能的惟一名稱(做爲字符串).默認值
var sampleBooleanFeature = context.Create("SampleBooleanFeature", defaultValue: "false"); sampleBooleanFeature.CreateChildFeature("SampleNumericFeature", defaultValue: "10"); context.Create("SampleSelectionFeature", defaultValue: "B"); } }

建立功能提供者後,咱們應該在模塊的PreInitialize方法中註冊,以下所示:

Configuration.Features.Providers.Add<AppFeatureProvider>();

其餘功能屬性
雖然須要惟一的名稱和默認值屬性,可是有一些可選的屬性用於詳細控制。

Scope:FeatureScopes枚舉中的值。 它能夠是版本(若是此功能只能爲版本級設置),租戶(若是此功能只能爲租戶級別設置)或所有(若是此功能能夠爲版本和租戶設置,租戶設置覆蓋其版本的 設置)。 默認值爲所有。
DisplayName:一個可本地化的字符串,用於向用戶顯示該功能的名稱。
Description:一個可本地化的字符串,用於向用戶顯示該功能的詳細說明。
InputType:功能的UI輸入類型。 這能夠定義,而後能夠在建立自動功能屏幕時使用。
Attributes:鍵值對的任意自定義詞典能夠與特徵相關。

public class AppFeatureProvider : FeatureProvider
{
    public override void SetFeatures(IFeatureDefinitionContext context)
    {
        var sampleBooleanFeature = context.Create(
            AppFeatures.SampleBooleanFeature,
            defaultValue: "false",
            displayName: L("Sample boolean feature"),
            inputType: new CheckboxInputType()
            );

        sampleBooleanFeature.CreateChildFeature(
            AppFeatures.SampleNumericFeature,
            defaultValue: "10",
            displayName: L("Sample numeric feature"),
            inputType: new SingleLineStringInputType(new NumericValueValidator(1, 1000000))
            );

        context.Create(
            AppFeatures.SampleSelectionFeature,
            defaultValue: "B",
            displayName: L("Sample selection feature"),
            inputType: new ComboboxInputType(
                new StaticLocalizableComboboxItemSource(
                    new LocalizableComboboxItem("A", L("Selection A")),
                    new LocalizableComboboxItem("B", L("Selection B")),
                    new LocalizableComboboxItem("C", L("Selection C"))
                    )
                )
            );
    }

    private static ILocalizableString L(string name)
    {
        return new LocalizableString(name, AbpZeroTemplateConsts.LocalizationSourceName);
    }
}

功能層次結構
如示例功能提供者所示,功能能夠具備子功能。 父功能一般定義爲布爾特徵。 子功能僅在啓用父級時纔可用。 ASP.NET Boilerplate不執行但建議這一點。 應用程序應該照顧它。

 

2,檢查功能

咱們定義一個功能來檢查應用程序中的值,以容許或阻止每一個租戶的某些應用程序功能。 有不一樣的檢查方式。

①使用RequiresFeature屬性

[RequiresFeature("ExportToExcel")]
public async Task<FileDto> GetReportToExcel(...)
{
    ...
}

只有對當前租戶啓用了「ExportToExcel」功能(當前租戶從IAbpSession獲取),才執行此方法。 若是未啓用,則會自動拋出AbpAuthorizationException異常。

固然,RequiresFeature屬性應該用於布爾類型的功能。 不然,你可能會獲得異常。

②RequiresFeature屬性註釋

ASP.NET Boilerplate使用動態方法截取功能進行功能檢查。 因此,方法使用RequiresFeature屬性有一些限制。

不能用於私有方法。
不能用於靜態方法。
不能用於非注入類的方法(咱們必須使用依賴注入)。

此外

若是方法經過接口調用(如經過接口使用的應用程序服務),能夠將其用於任何公共方法。
若是直接從類引用(如ASP.NET MVC或Web API控制器)調用,則該方法應該是虛擬的。
若是保護方法應該是虛擬的。

③使用IFeatureChecker
咱們能夠注入和使用IFeatureChecker手動檢查功能(它自動注入並可直接用於應用程序服務,MVC和Web API控制器)。

public async Task<FileDto> GetReportToExcel(...)
{
    if (await FeatureChecker.IsEnabledAsync("ExportToExcel"))
    {
        throw new AbpAuthorizationException("您沒有此功能:ExportToExcel");
    }

    ...
}

若是您只想檢查一個功能並拋出異常,如示例所示,您只需使用CheckEnabled方法便可。

④獲取功能的值

var createdTaskCountInThisMonth = GetCreatedTaskCountInThisMonth();
if (createdTaskCountInThisMonth >= FeatureChecker.GetValue("MaxTaskCreationLimitPerMonth").To<int>())
{
    throw new AbpAuthorizationException("你超過本月的任務建立限制");
}

FeatureChecker方法也覆蓋了指定tenantId的工做功能,不只適用於當前的tenantId。

⑤客戶端
在客戶端(javascript)中,咱們可使用abp.features命名空間來獲取當前的功能值。

var isEnabled = abp.features.isEnabled('SampleBooleanFeature');
var value = abp.features.getValue('SampleNumericFeature');

 

3,深度研究

1、AbpFeatures表分析
1,Name:
名稱,必須先配置FeatureProvider,此名稱必定在FeatureProvider配置中存在。惟一
2,Value:
①值。支持兩種類型(bool類型和值類型)
②bool類型使用FeatureChecker.CheckEnabled("")獲取,不存咋拋異常
③值類型使用FeatureChecker.GetValue("").To<int>()獲取,不存咋拋異常
3,EditionId:
①版本關聯,使用EditionFeatureSetting類型建立
②使用_abpEditionManager.SetFeatureValueAsync()設置基於版本的功能
4,TenantId:
①租戶關聯,使用TenantFeatureSetting類型建立
②使用TenantManager.SetFeatureValue()設置基於租戶的功能
5,Discriminator:
①標識
②值爲TenantFeatureSetting表示基於租戶
③值爲EditionFeatureSetting表示基於版本

2、案例分析

FeatureProvider配置

AbpFeatures表

權限配置

1,租戶1沒有Product權限(當前Product權限爲菜單權限),因此租戶1在頁面上不會顯示出Product菜單權限

2,租戶2 FeatureChecker.GetValue("Count").To<int>() 值爲20

3,租戶3 FeatureChecker.GetValue("Count").To<int>() 值爲30

4,租戶1 FeatureChecker.GetValue("Count").To<int>() 值爲10(默認配置的值)

 

二十3、審計日誌

基本上保存的字段是:相關租戶信息tenant id,、user id、服務名稱(被調用方法的類)、方法名稱、執行參數(序列化爲json)、執行時間、執行持續時間(毫秒)、客戶端ip地址、客戶端計算機名稱和異常(若是方法拋出異常)

1,配置

要配置審覈,能夠在模塊的PreInitialize方法中使用Configuration.Auditing屬性。 默認狀況下啓用審計。 您能夠禁用它,以下所示。

public class MyModule : AbpModule
{
    public override void PreInitialize()
    {
        Configuration.Auditing.IsEnabled = false;
    }
    //...
}

這裏列出了審覈配置屬性:

  • IsEnabled:用於徹底啓用/禁用審計系統。 默認值:true。
  • IsEnabledForAnonymousUsers:若是設置爲true,則對於未登陸系統的用戶,還會保存審覈日誌。 默認值:false。
  • Selectors: 用於選擇其餘類來保存審覈日誌。

選擇器是選擇其餘類型以保存審覈日誌的謂詞列表。 選擇器具備惟一的名稱和謂詞。 此列表中惟一的默認選擇器用於選擇應用程序服務類。 定義以下:

Configuration.Auditing.Selectors.Add(
    new NamedTypeSelector(
        "Abp.ApplicationServices",
        type => typeof (IApplicationService).IsAssignableFrom(type)
    )
);

您能夠在您的模塊的PreInitialize方法中添加選擇器。 另外,若是不想保存應用程序服務的審覈日誌,能夠經過名稱刪除上面的選擇器。 這就是爲何它有一個惟一的名稱(使用簡單的LINQ在選擇器中找到選擇器,若是須要的話)。

注意:除了標準審覈配置外,MVC和ASP.NET Core模塊還定義了啓用/禁用審計日誌記錄的配置。

2,按屬性啓用/禁用

雖然您能夠經過配置選擇審覈類,但能夠對單個類使用「已審覈」和「禁用屬性」,也就是單一方法。 一個例子:

[Audited]
public class MyClass
{
    public void MyMethod1(int a)
    {
        //...
    }

    [DisableAuditing]
    public void MyMethod2(string b)
    {
        //...
    }

    public void MyMethod3(int a, int b)
    {
        //...
    }
}

除了MyMethod2,MyClass的全部方法都被審計,由於它被明確禁用。 經審計的屬性可用於僅爲所需方法保存審覈的方法。

DisableAuditing也能夠用於DTO的一個或一個屬性。 所以,您能夠隱藏審計日誌中的敏感數據,例如密碼。

 

二十4、ASP.NET Web API Controllers

1,AbpApiController基類 

public class UsersController : AbpApiController
{

}

2,本地化

AbpApiController定義了L方法,使本地化更容易。 例:

public class UsersController : AbpApiController
{
    public UsersController()
    {
        LocalizationSourceName = "MySourceName";
    }

    public UserDto Get(long id)
    {
        var helloWorldText = L("HelloWorld");

        //...
    }
}

您應該設置LocalizationSourceName使L方法工做。 您能夠將其設置在您本身的基本api控制器類中,以便不對每一個api控制器重複。

3,其餘
您還可使用預先注入的AbpSession,EventBus,PermissionManager,PermissionChecker,SettingManager,FeatureManager,FeatureChecker,LocalizationManager,Logger,CurrentUnitOfWork基礎屬性等。

4,過濾器

①審計日誌
AbpApiAuditFilter用於集成審計日誌系統。 它默認將全部請求記錄到全部操做(若是未禁用審覈)。 您可使用Audited和DisableAuditing屬性控制審計日誌記錄,以執行操做和控制器。

②受權
您能夠爲您的api控制器或操做使用AbpApiAuthorize屬性,以防止未經受權的用戶使用您的控制器和操做。 例:

public class UsersController : AbpApiController
{
    [AbpApiAuthorize("MyPermissionName")]
    public UserDto Get(long id)
    {
        //...
    }
}

您能夠爲操做或控制器定義AllowAnonymous屬性以抑制身份驗證/受權。 AbpApiController還將IsGranted方法定義爲在代碼中檢查權限的快捷方式。

③防僞過濾器
AbpAntiForgeryApiFilter用於自動保護來自CSRF / XSRF攻擊的POST,PUT和DELETE請求的ASP.NET Web API操做(包括動態Web api)

④工做單位
AbpApiUowFilter用於集成到工做單元系統。 在動做執行以前,它會自動開始一個新的工做單元,並在動做執行以後完成工做單元(若是沒有異常被拋出)。

您可使用UnitOfWork屬性來控制操做的UOW的行爲。 您還可使用啓動配置來更改全部操做的默認工做屬性。

⑤結果緩存
ASP.NET Boilerplate爲Web API請求的響應添加了Cache-Control頭(無緩存,無存儲)。 所以,它甚至阻止瀏覽器緩存GET請求。 能夠經過配置禁用此行爲。

⑥驗證
AbpApiValidationFilter會自動檢查ModelState.IsValid,若是該操做無效,能夠防止執行該操做。 此外,實現驗證文檔中描述的輸入DTO驗證。

⑦模型綁定
AbpApiDateTimeBinder用於使用Clock.Normalize方法對DateTime(和Nullable <DateTime>)進行歸一化。

 

二十5、動態Webapi層

1,ForAll Method

DynamicApiControllerBuilper提供了一種在一個調用中爲全部應用程序服務構建Web api控制器的方法

Configuration.Modules.AbpWebApi().DynamicApiControllerBuilder
    .ForAll<IApplicationService>(Assembly.GetAssembly(typeof(SimpleTaskSystemApplicationModule)), "tasksystem")
    .Build();

 對於此配置,服務將是「/api/services/tasksystem/task」和「/api/services/tasksystem/person」。 要計算服務名稱:Service和AppService後綴和I前綴被刪除(用於接口)

2,覆蓋ForAll 

咱們能夠在ForAll方法以後覆蓋配置。 例:

Configuration.Modules.AbpWebApi().DynamicApiControllerBuilder
    .ForAll<IApplicationService>(Assembly.GetAssembly(typeof(SimpleTaskSystemApplicationModule)), "tasksystem")
    .Build();

Configuration.Modules.AbpWebApi().DynamicApiControllerBuilder
    .For<ITaskAppService>("tasksystem/task")
    .ForMethod("CreateTask").DontCreateAction().Build();

在此代碼中,咱們爲程序集中的全部應用程序服務建立了動態Web API控制器。 而後覆蓋單個應用程序服務(ITaskAppService)的配置來忽略CreateTask方法。

3,ForMethods

使用ForAll方法時,可使用ForMethods方法來更好地調整每種方法。 例:

Configuration.Modules.AbpWebApi().DynamicApiControllerBuilder
    .ForAll<IApplicationService>(Assembly.GetExecutingAssembly(), "app")
    .ForMethods(builder =>
    {
        if (builder.Method.IsDefined(typeof(MyIgnoreApiAttribute)))
        {
            builder.DontCreate = true;
        }
    })
    .Build();

在此示例中,我使用自定義屬性(MyIgnoreApiAttribute)來檢查全部方法,而且不爲使用該屬性標記的方法建立動態Web api控制器操做。

4,Http動詞

默認狀況下,全部方法都建立爲POST

5,WithVerb Method

Configuration.Modules.AbpWebApi().DynamicApiControllerBuilder
    .For<ITaskAppService>("tasksystem/task")
    .ForMethod("GetTasks").WithVerb(HttpVerb.Get)
    .Build();

6,Http特性

public interface ITaskAppService : IApplicationService
{
    [HttpGet]
    GetTasksOutput GetTasks(GetTasksInput input);

    [HttpPut]
    void UpdateTask(UpdateTaskInput input);

    [HttpPost]
    void CreateTask(CreateTaskInput input);
}

7,WithConventionalVerbs 命名公約

Configuration.Modules.AbpWebApi().DynamicApiControllerBuilder
    .ForAll<IApplicationService>(Assembly.GetAssembly(typeof(SimpleTaskSystemApplicationModule)), "tasksystem")
    .WithConventionalVerbs()
    .Build();
  • Get: 若是方法名以「Get」開頭,則使用
  • Put:若是方法名稱以「Put」或「Update」開頭,則使用
  • Delete: 若是方法名稱以「Delete」或「Remove」開頭
  • Post: 若是方法名稱以「Post」,「Create」或「Insert」開頭,則使用
  • Patch: 若是方法名稱以「Patch」開頭,則使用
  • 不然,Post被用做默認的HTTP動詞

8,遠程服務屬性
您還可使用RemoteService屬性進行任何接口或方法定義來啓用/禁用(IsEnabled)動態API或API資源管理器設置(IsMetadataEnabled)。

9,動態Javascript代理

abp.services.tasksystem.task.getTasks({
    state: 1
}).done(function (result) {
    //use result.tasks here...
});

Javascript代理是動態建立的。 在使用以前,您應該將動態腳本添加到頁面中:

<script src="/api/AbpServiceProxies/GetAll" type="text/javascript"></script>

10,AJAX參數
您可能但願將自定義ajax參數傳遞給代理方法。 你能夠將它們做爲第二個參數傳遞下面所示:

abp.services.tasksystem.task.createTask({
    assignedPersonId: 3,
    description: 'a new task description...'
},{ //覆蓋jQuery的ajax參數
    async: false,
    timeout: 30000
}).done(function () {
    abp.notify.success('successfully created a task!');
});

jQuery.ajax的全部參數在這裏是有效的。

除了標準的jQuery.ajax參數以外,您能夠向AJAX選項添加abpHandleError:false,以便在錯誤狀況下禁用自動消息顯示。

11,單一服務腳本
'/api/AbpServiceProxies/GetAll'在一個文件中生成全部服務代理。 您還可使用'/api/AbpServiceProxies/Get?name=serviceName'生成單個服務代理,並將腳本包含在頁面中,以下所示:

<script src="/api/AbpServiceProxies/Get?name=tasksystem/task" type="text/javascript"></script>

12,Angular整合

(function() {
    angular.module('app').controller('TaskListController', [
        '$scope', 'abp.services.tasksystem.task',
        function($scope, taskService) {
            var vm = this;
            vm.tasks = [];
            taskService.getTasks({
                state: 0
            }).success(function(result) {
                vm.tasks = result.tasks;
            });
        }
    ]);
})();

爲了可以使用自動生成的服務,您應該在頁面中包含所需的腳本:

<script src="~/Abp/Framework/scripts/libs/angularjs/abp.ng.js"></script>
<script src="~/api/AbpServiceProxies/GetAll?type=angular"></script>

 

二十6、OData整合

OData被定義爲「odata.org中容許以簡單和標準的方式建立和消費可查詢和可互操做的RESTful API的開放式協議」

1,安裝包

Install-Package Abp.Web.Api.OData

2,設置模塊依賴

配置您的實體
OData須要聲明可用做OData資源的實體。 咱們應該在咱們模塊的PreInitialize方法中執行此操做,以下所示:

[DependsOn(typeof(AbpWebApiODataModule))]
public class MyProjectWebApiModule : AbpModule
{
    public override void PreInitialize()
    {
        var builder = Configuration.Modules.AbpWebApiOData().ODataModelBuilder;

        //在這裏配置您的實體
        builder.EntitySet<Person>("Persons");
    }

    ...
}

在這裏,咱們獲得了ODataModelBuilder引用,並設置了Person實體。 您可使用EntitySet添加相似的其餘實體

3,建立控制器
Abp.Web.Api.OData nuget軟件包包括AbpODataEntityController基類(擴展標準ODataController),以便更輕鬆地建立控制器。 爲Person實體建立OData端點的示例:

public class PersonsController : AbpODataEntityController<Person>
{
    public PersonsController(IRepository<Person> repository)
        : base(repository)
    {
    }
}

這很容易 AbpODataEntityController的全部方法都是虛擬的。 這意味着您能夠覆蓋Get,Post,Put,Patch,Delete和其餘操做,並添加您本身的邏輯。

4,配置

Abp.Web.Api.OData自動調用HttpConfiguration.MapODataServiceRoute方法與常規配置。 若是須要,您能夠設置Configuration.Modules.AbpWebApiOData()。MapAction本身映射OData路由。

5,例子

在這裏,一些例子請求到上面定義的控制器。 假設應用程序在http://localhost:61842上工做。 咱們將展現一些基礎知識。 因爲OData是標準協議,您能夠輕鬆地在網絡上找到更多高級示例。

①獲取全部列表

請求:GET http://localhost:61842/odata/Persons

響應:

{
  "@odata.context":"http://localhost:61842/odata/$metadata#Persons","value":[
    {
      "Name":"Douglas Adams","IsDeleted":false,"DeleterUserId":null,"DeletionTime":null,"LastModificationTime":null,"LastModifierUserId":null,"CreationTime":"2015-11-07T20:12:39.363+03:00","CreatorUserId":null,"Id":1
    },{
      "Name":"John Nash","IsDeleted":false,"DeleterUserId":null,"DeletionTime":null,"LastModificationTime":null,"LastModifierUserId":null,"CreationTime":"2015-11-07T20:12:39.363+03:00","CreatorUserId":null,"Id":2
    }
  ]
}

②獲取單個實體(where id=2)

請求:GET http://localhost:61842/odata/Persons(2)

 響應:

{
  "@odata.context":"http://localhost:61842/odata/$metadata#Persons/$entity","Name":"John Nash","IsDeleted":false,"DeleterUserId":null,"DeletionTime":null,"LastModificationTime":null,"LastModifierUserId":null,"CreationTime":"2015-11-07T20:12:39.363+03:00","CreatorUserId":null,"Id":2
}

③獲取具備導航屬性的單個實體(得到Id = 1的人,包括他的電話號碼)

請求:GET http://localhost:61842/odata/Persons(1)?$expand=Phones

響應:

{
  "@odata.context":"http://localhost:61842/odata/$metadata#Persons/$entity","Name":"Douglas Adams","IsDeleted":false,"DeleterUserId":null,"DeletionTime":null,"LastModificationTime":null,"LastModifierUserId":null,"CreationTime":"2015-11-07T20:12:39.363+03:00","CreatorUserId":null,"Id":1,"Phones":[
    {
      "PersonId":1,"Type":"Mobile","Number":"4242424242","CreationTime":"2015-11-07T20:12:39.363+03:00","CreatorUserId":null,"Id":1
    },{
      "PersonId":1,"Type":"Mobile","Number":"2424242424","CreationTime":"2015-11-07T20:12:39.363+03:00","CreatorUserId":null,"Id":2
    }
  ]
}

④更高級的查詢包括過濾,排序和獲取前2個結果

請求:GET http://localhost:61842/odata/Persons?$filter=Name eq 'Douglas Adams'&$orderby=CreationTime&$top=2

響應:

{
  "@odata.context":"http://localhost:61842/odata/$metadata#Persons","value":[
    {
      "Name":"Douglas Adams","IsDeleted":false,"DeleterUserId":null,"DeletionTime":null,"LastModificationTime":null,"LastModifierUserId":null,"CreationTime":"2015-11-07T20:12:39.363+03:00","CreatorUserId":null,"Id":1
    },{
      "Name":"Douglas Adams","IsDeleted":false,"DeleterUserId":null,"DeletionTime":null,"LastModificationTime":null,"LastModifierUserId":null,"CreationTime":"2016-01-12T20:29:03+02:00","CreatorUserId":null,"Id":3
    }
  ]
}

⑤建立新實體

請求(這裏,「Content-Type」頭是「application / json」):

POST http://localhost:61842/odata/Persons

{
    Name: "Galileo Galilei"
}

響應:

{
  "@odata.context": "http://localhost:61842/odata/$metadata#Persons/$entity",
  "Name": "Galileo Galilei",
  "IsDeleted": false,
  "DeleterUserId": null,
  "DeletionTime": null,
  "LastModificationTime": null,
  "LastModifierUserId": null,
  "CreationTime": "2016-01-12T20:36:04.1628263+02:00",
  "CreatorUserId": null,
  "Id": 4
}

⑥獲取元數據(元數據用於調查服務)

請求:GET http://localhost:61842/odata/$metadata

響應:

<?xml version="1.0" encoding="utf-8"?>

<edmx:Edmx Version="4.0" xmlns:edmx="http://docs.oasis-open.org/odata/ns/edmx">

    <edmx:DataServices>

        <Schema Namespace="AbpODataDemo.People" xmlns="http://docs.oasis-open.org/odata/ns/edm">

            <EntityType Name="Person">

                <Key>

                    <PropertyRef Name="Id" />

                </Key>

                <Property Name="Name" Type="Edm.String" Nullable="false" />

                <Property Name="IsDeleted" Type="Edm.Boolean" Nullable="false" />

                <Property Name="DeleterUserId" Type="Edm.Int64" />

                <Property Name="DeletionTime" Type="Edm.DateTimeOffset" />

                <Property Name="LastModificationTime" Type="Edm.DateTimeOffset" />

                <Property Name="LastModifierUserId" Type="Edm.Int64" />

                <Property Name="CreationTime" Type="Edm.DateTimeOffset" Nullable="false" />

                <Property Name="CreatorUserId" Type="Edm.Int64" />

                <Property Name="Id" Type="Edm.Int32" Nullable="false" />

                <NavigationProperty Name="Phones" Type="Collection(AbpODataDemo.People.Phone)" />

            </EntityType>

            <EntityType Name="Phone">

                <Key>

                    <PropertyRef Name="Id" />

                </Key>

                <Property Name="PersonId" Type="Edm.Int32" />

                <Property Name="Type" Type="AbpODataDemo.People.PhoneType" Nullable="false" />

                <Property Name="Number" Type="Edm.String" Nullable="false" />

                <Property Name="CreationTime" Type="Edm.DateTimeOffset" Nullable="false" />

                <Property Name="CreatorUserId" Type="Edm.Int64" />

                <Property Name="Id" Type="Edm.Int32" Nullable="false" />

                <NavigationProperty Name="Person" Type="AbpODataDemo.People.Person">

                    <ReferentialConstraint Property="PersonId" ReferencedProperty="Id" />

                </NavigationProperty>

            </EntityType>

            <EnumType Name="PhoneType">

                <Member Name="Unknown" Value="0" />

                <Member Name="Mobile" Value="1" />

                <Member Name="Home" Value="2" />

                <Member Name="Office" Value="3" />

            </EnumType>

        </Schema>

        <Schema Namespace="Default" xmlns="http://docs.oasis-open.org/odata/ns/edm">

            <EntityContainer Name="Container">

                <EntitySet Name="Persons" EntityType="AbpODataDemo.People.Person" />

            </EntityContainer>

        </Schema>

    </edmx:DataServices>

</edmx:Edmx>
元數據

 

二十7、Swagger UI 整合

....使用啓用Swagger的API,您能夠得到交互式文檔,客戶端SDK生成和可發現性。

1,ASP.NET Core

①安裝Nuget軟件包
將Swashbuckle.AspNetCore nuget軟件包安裝到您的Web項目中

②配置

將Swagger的配置代碼添加到Startup.cs的ConfigureServices方法中

public IServiceProvider ConfigureServices(IServiceCollection services)
{
    //your other code...
    
      services.AddSwaggerGen(options =>
            {
                options.SwaggerDoc("v1", new Info { Title = "AbpZeroTemplate API", Version = "v1" });
                options.DocInclusionPredicate((docName, description) => true);
            });
    
    //your other code...
}

而後,將如下代碼添加到Startup.cs的Configure方法中以使用Swagger

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
    //your other code...

     app.UseSwagger();
            //Enable middleware to serve swagger - ui assets(HTML, JS, CSS etc.)
            app.UseSwaggerUI(options =>
            {
                options.SwaggerEndpoint("/swagger/v1/swagger.json", "AbpZeroTemplate API V1");
            }); //URL: /swagger 
            
    //your other code...
}

就這樣。 您能夠瀏覽「/swagger」下的swagger ui。

2,ASP.NET 5.x

注意,swagger只能在webapi層使用。我在web層使用失敗

①安裝Nuget軟件包
將Swashbuckle.Core nuget軟件包安裝到WebApi項目(或Web項目)。

②配置

public class SwaggerIntegrationDemoWebApiModule : AbpModule
{
    public override void Initialize()
    {
        //your other code...

        ConfigureSwaggerUi();
    }

    private void ConfigureSwaggerUi()
    {
        Configuration.Modules.AbpWebApi().HttpConfiguration
            .EnableSwagger(c =>
            {
                c.SingleApiVersion("v1", "SwaggerIntegrationDemo.WebApi");
                c.ResolveConflictingActions(apiDescriptions => apiDescriptions.First());
            })
            .EnableSwaggerUi(c =>
            {
                c.InjectJavaScript(Assembly.GetAssembly(typeof(AbpProjectNameWebApiModule)), "AbpCompanyName.AbpProjectName.Api.Scripts.Swagger-Custom.js");
            });
    }
}

注意,在配置swagger ui時,咱們注入一個名爲「Swagger-Custom.js」的javascript文件。 此腳本文件用於在swagger ui上測試api服務時向請求中添加CSRF令牌。 您還須要將此文件做爲嵌入式資源添加到WebApi項目中,並在注入時使用InjectJavaScript方法中的邏輯名稱。

Swagger-Custom.js的內容在這裏:

var getCookieValue = function(key) {
    var equalities = document.cookie.split('; ');
    for (var i = 0; i < equalities.length; i++) {
        if (!equalities[i]) {
            continue;
        }

        var splitted = equalities[i].split('=');
        if (splitted.length !== 2) {
            continue;
        }

        if (decodeURIComponent(splitted[0]) === key) {
            return decodeURIComponent(splitted[1] || '');
        }
    }

    return null;
};

var csrfCookie = getCookieValue("XSRF-TOKEN");
var csrfCookieAuth = new SwaggerClient.ApiKeyAuthorization("X-XSRF-TOKEN", csrfCookie, "header");
swaggerUi.api.clientAuthorizations.add("X-XSRF-TOKEN", csrfCookieAuth);

3,測試

 

二十8、ASP.NET MVC

1,ASP.NET MVC Controllers

①,mvc控制器繼承AbpController 

public class HomeController : AbpController
{
    public HomeController()
    {
        //您應該設置LocalizationSourceName使L方法工做。 您能夠將其設置在您本身的基本控制器類中,以便不對每一個控制器重複。
        LocalizationSourceName = "MySourceName";
    }
    public ActionResult Index()
    {
        var helloWorldText = L("HelloWorld");
        return View();
    }
}

②,受權

您能夠爲控制器或操做使用AbpMvcAuthorize屬性,以防止未經受權的用戶使用您的控制器和操做。 例:

public class HomeController : AbpController
{
    [AbpMvcAuthorize("MyPermissionName")]
    public ActionResult Index()
    {
        return View();
    }
}

 

2,ASP.NET MVC Views

①AbpWebViewPage基類
ASP.NET Boilerplate還提供了AbpWebViewPage,它定義了一些有用的屬性和方法。 若是您使用啓動模板建立了項目,則全部視圖都將自動從該基類繼承。

②AbpWebViewPage定義L方法進行本地化,IsGranted方法受權,IsFeatureEnabled和GetFeatureValue方法進行功能管理等。

 

3,處理異常

①啓用錯誤處理
要啓用ASP.NET MVC控制器的錯誤處理,必須爲ASP.NET MVC應用程序啓用customErrors模式。

<customErrors mode="On" />//若是您不想在本地計算機中處理錯誤,它也能夠是「RemoteOnly」。 請注意,這僅適用於ASP.NET MVC控制器,不須要Web API控制器。

②非AJAX請求

若是請求不是AJAX,則會顯示錯誤頁面。

public ActionResult Index()
{
    throw new Exception("A sample exception message...");
}

固然,這個異常能夠被另外一個從這個動做調用的方法拋出。 ASP.NET Boilerplate處理這個異常,記錄它並顯示'Error.cshtml'視圖。 您能夠自定義此視圖以顯示錯誤。 示例錯誤視圖(ASP.NET Boilerplate模板中的默認錯誤視圖):

 

UserFriendlyException
UserFriendlyException是一種特殊類型的異常,直接顯示給用戶。 見下面的示例:

public ActionResult Index()
{
    throw new UserFriendlyException("Ooppps! There is a problem!", "You are trying to see a product that is deleted...");
}

錯誤模型
ASP.NET Boilerplate將ErrorViewModel對象做爲模型傳遞給「錯誤」視圖:

public class ErrorViewModel
{
    public AbpErrorInfo ErrorInfo { get; set; }

    public Exception Exception { get; set; }
}

ErrorInfo包含有關能夠向用戶顯示的錯誤的詳細信息。 異常對象是拋出的異常。 若是須要,您能夠查看並顯示其餘信息。 例如,若是是AbpValidationException,咱們能夠顯示驗證錯誤:

③AJAX請求

 若是MVC操做的返回類型是JsonResult(或異步操做的Task JsonResult),則ASP.NET Boilerplate會向客戶端返回異常的JSON對象。 示例返回對象的錯誤:

{
  "targetUrl": null,
  "result": null,
  "success": false,
  "error": {
    "message": "An internal error occured during your request!",
    "details": "..."
  },
  "unAuthorizedRequest": false
}

④異常事件
當ASP.NET Boilerplare處理任何異常時,它會觸發能夠註冊的AbpHandledExceptionData事件(有關事件總線的更多信息,請參見eventbus文檔)。 例:

public class MyExceptionHandler : IEventHandler<AbpHandledExceptionData>, ITransientDependency
{
    public void HandleEvent(AbpHandledExceptionData eventData)
    {
        //TODO: Check eventData.Exception!
    }
}

若是將此示例類放入應用程序(一般在您的Web項目中),將爲ASP.NET Boilerplate處理的全部異常調用HandleEvent方法。 所以,您能夠詳細調查Exception對象。

 

二十9、本地化

1,應用語言,首先申明支持哪些語, 這在模塊的PreInitialize方法中完成

Configuration.Localization.Languages.Add(new LanguageInfo("en", "English", "famfamfam-flag-england", true));
Configuration.Localization.Languages.Add(new LanguageInfo("tr", "Türkçe", "famfamfam-flag-tr"));

在服務器端,您能夠注入並使用ILocalizationManager。 在客戶端,您可使用abp.localization javascript API來獲取全部可用語言和當前語言的列表。 famfamfam-flag-england(和tr)只是一個CSS類,您能夠根據須要進行更改。 而後你能夠在UI上使用它來顯示相關的標誌。

2,本地化來源

本地化文本能夠存儲在不一樣的來源。 即便您能夠在同一應用程序中使用多個源(若是您有多個模塊,每一個模塊能夠定義一個獨立的本地化源,或者一個模塊能夠定義多個源)。 ILocalizationSource接口應由本地化源實現。 而後它被註冊到ASP.NET Boilerplate的本地化配置。

每一個本地化源必須具備惟一的源名稱。 有以下定義的預約義的本地化源類型(xml文件、json文件、資源文件等)。

①XML文件

本地化文本能夠存儲在XML文件中。 XML文件的內容是這樣的:

<?xml version="1.0" encoding="utf-8" ?>
<localizationDictionary culture="en">
  <texts>
    <text name="TaskSystem" value="Task System" />
    <text name="TaskList" value="Task List" />
    <text name="NewTask" value="New Task" />
    <text name="Xtasks" value="{0} tasks" />
    <text name="CompletedTasks" value="Completed tasks" />
    <text name="EmailWelcomeMessage">Hi,
Welcome to Simple Task System! This is a sample
email content.</text>
  </texts>
</localizationDictionary>

XML文件必須是unicode(utf-8)。 culture =「en」表示此XML文件包含英文文本。 對於文本節點; name屬性用於標識文本。 您可使用值屬性或內部文本(如最後一個)來設置本地化文本的值。 咱們爲每種語言建立一個分離的XML文件,以下所示:

SimpleTaskSystem是這裏的源名稱,SimpleTaskSystem.xml定義了默認語言。 當請求文本時,ASP.NET Boilerplate從當前語言的XML文件獲取文本(使用Thread.CurrentThread.CurrentUICulture查找當前語言)。 若是當前語言不存在,則會從默認語言的XML文件獲取文本。

註冊XML本地化源,XML文件能夠存儲在文件系統中,也能夠嵌入到程序集中。

對於文件系統存儲的XML,咱們能夠註冊一個XML定位源,以下所示(這在模塊的PreInitialize事件中完成):

Configuration.Localization.Sources.Add(
    new DictionaryBasedLocalizationSource(
        "SimpleTaskSystem",
        new XmlFileLocalizationDictionaryProvider(
            HttpContext.Current.Server.MapPath("~/Localization/SimpleTaskSystem")
            )
        )
    );

對於嵌入式XML文件,咱們應該將全部本地化XML文件標記爲嵌入式資源(選擇XML文件,打開屬性窗口(F4)並將Build Action做爲嵌入式資源更改)。 而後咱們能夠註冊本地化源,以下所示:

Configuration.Localization.Sources.Add(
    new DictionaryBasedLocalizationSource(
        "SimpleTaskSystem",
        new XmlEmbeddedFileLocalizationDictionaryProvider(
            Assembly.GetExecutingAssembly(),
            "MyCompany.MyProject.Localization.Sources"
            )
        )
    );

XmlEmbeddedFileLocalizationDictionaryProvider獲取一個包含XML文件的程序集(GetExecutingAssembly簡稱爲當前程序集)和XML文件的命名空間(命名空間+ XML文件的文件夾層次結構)。

注意:當添加語言後綴到嵌入式XML文件時,不要使用像MySource.tr.xml這樣的點符號,而是使用像MySource-tr.xml這樣的破折號,由於點符號在找到資源時會致使命名空間的問題。

②json文件

{
  "culture": "en",
  "texts": {
    "TaskSystem": "Task system",
    "Xtasks": "{0} tasks"
  }
}

JSON文件應該是unicode(utf-8)。 文化:「en」聲明此JSON文件包含英文文本。 咱們爲每種語言建立一個分隔的JSON文件,以下所示:

MySourceName是源名,MySourceName.json定義了默認語言。 它相似於XML文件。


註冊JSON本地化源,JSON文件能夠存儲在文件系統中,也能夠嵌入到程序集中。

文件系統存儲的JSONs,咱們能夠註冊一個JSON本地化源碼,以下所示(這在模塊的PreInitialize事件中完成):

Configuration.Localization.Sources.Add(
    new DictionaryBasedLocalizationSource(
        "MySourceName",
        new JsonFileLocalizationDictionaryProvider(
            HttpContext.Current.Server.MapPath("~/Localization/MySourceName")
            )
        )
    );

對於嵌入式JSON文件,咱們應將全部本地化JSON文件標記爲嵌入式資源(選擇JSON文件,打開屬性窗口(F4),並將Build Action做爲嵌入式資源更改)。 而後咱們能夠註冊本地化源,以下所示:

 Configuration.Localization.Sources.Add(
    new DictionaryBasedLocalizationSource(
        "MySourceName",
        new JsonEmbeddedFileLocalizationDictionaryProvider(
            Assembly.GetExecutingAssembly(),
            "MyCompany.MyProject.Localization.Sources"
            )
        )
    );

JsonEmbeddedFileLocalizationDictionaryProvider獲取一個包含JSON文件的程序集(GetExecutingAssembly簡稱爲當前程序集)和JSON文件的命名空間(命名空間是計算的程序集名稱和JSON文件的文件夾層次結構)。

注意:在嵌入式JSON文件中添加語言後綴時,請勿使用像MySource.tr.json這樣的點符號,而是使用像MySource-tr.json這樣的破折號,由於點符號在找到資源時會致使命名空間問題。

③資源文件

本地化文本也能夠存儲在.NET的資源文件中。 咱們能夠爲每種語言建立資源文件,以下所示(右鍵單擊項目,選擇添加新項目,而後查找資源文件)。

MyTexts.resx包含默認語言文本,MyTexts.tr.resx包含土耳其語的文本。 當咱們打開MyTexts.resx時,咱們能夠看到全部的文本:

在這種狀況下,ASP.NET Boilerplate使用.NET的內置資源管理器進行本地化。 您應該爲資源配置本地化源:

Configuration.Localization.Sources.Add(
    new ResourceFileLocalizationSource(
        "MySource",
        MyTexts.ResourceManager
        ));

源的惟一名稱是MySource,而MyTexts.ResourceManager是用於獲取本地化文本的資源管理器的引用。 這在模塊的PreInitialize事件中完成(有關更多信息,請參閱模塊系統)。

3,獲取本地化文本

①在服務器端,咱們能夠注入ILocalizationManager並使用它的GetString方法。

var s1 = _localizationManager.GetString("SimpleTaskSystem", "NewTask");

GetString方法根據當前線程的UI文化從本地化源獲取字符串。 若是沒有找到,它將回退到默認語言。

②爲了避免重複源的名字,你能夠先拿到源,而後獲得從源字符串:

var source = _localizationManager.GetSource("SimpleTaskSystem");
var s1 = source.GetString("NewTask");

③在mvc控制器

public class HomeController : SimpleTaskSystemControllerBase
{
    public ActionResult Index()
    {
        var helloWorldText = L("HelloWorld");
        return View();
    }
}
public abstract class SimpleTaskSystemControllerBase : AbpController
{
    protected SimpleTaskSystemControllerBase()
    {
        LocalizationSourceName = "SimpleTaskSystem";
    }
}

L方法用於本地化字符串。 固然,您必須提供源名稱。 它在SimpleTaskSystemControllerBase中完成。注意它是從AbpController派生的。 所以,您可使用L方法輕鬆本地化文本。

④在MVC視圖,一樣的L方法也存在於視圖中:

<div>
    <form id="NewTaskForm" role="form">
        <div class="form-group">
            <label for="TaskDescription">@L("TaskDescription")</label>
            <textarea id="TaskDescription" data-bind="value: task.description" class="form-control" rows="3" placeholder="@L("EnterDescriptionHere")" required></textarea>
        </div>
        <div class="form-group">
            <label for="TaskAssignedPerson">@L("AssignTo")</label>
            <select id="TaskAssignedPerson" data-bind="options: people, optionsText: 'name', optionsValue: 'id', value: task.assignedPersonId, optionsCaption: '@L("SelectPerson")'" class="form-control"></select>
        </div>
        <button data-bind="click: saveTask" type="submit" class="btn btn-primary">@L("CreateTheTask")</button>
    </form>
</div>

爲了使這項工做,您應該從設置源名稱的基類派生您的視圖:

public abstract class SimpleTaskSystemWebViewPageBase : SimpleTaskSystemWebViewPageBase<dynamic>
{

}

public abstract class SimpleTaskSystemWebViewPageBase<TModel> : AbpWebViewPage<TModel>
{
    protected SimpleTaskSystemWebViewPageBase()
    {
        LocalizationSourceName = "SimpleTaskSystem";
    }
}

並在web.config中設置此視圖基類:

<pages pageBaseType="SimpleTaskSystem.Web.Views.SimpleTaskSystemWebViewPageBase">

⑤在Javascript中

ASP.NET Boilerplate也可使用一樣的本地化文本也是JavaScript代碼。 首先,您應該在頁面中添加動態ABP腳本:

<script src="/AbpScripts/GetScripts" type="text/javascript"></script>

ASP.NET Boilerplate自動生成須要的JavaScript代碼,以便在客戶端獲取本地化的文本。 而後,您能夠輕鬆地獲取javascript中的本地化文本,以下所示:

var s1 = abp.localization.localize('NewTask', 'SimpleTaskSystem');

NewTask是文本名,SimpleTaskSystem是源名。 不要重複源名稱,您能夠先獲取源代碼,而後獲取文本:

var source = abp.localization.getSource('SimpleTaskSystem');
var s1 = source('NewTask');

格式參數,本地化方法也能夠得到附加的格式參數。 例:

abp.localization.localize('RoleDeleteWarningMessage', 'MySource', 'Admin');

//若是使用getSource獲得源,則如上所示
source('RoleDeleteWarningMessage', 'Admin');

若是RoleDeleteWarningMessage ='Role {0}將被刪除',那麼本地化的文本將被「Role Admin將被刪除」。


默認本地化源,您能夠設置默認的本地化源,並使用沒有源名稱的abp.localization.localize方法。

abp.localization.defaultSourceName = 'SimpleTaskSystem';
var s1 = abp.localization.localize('NewTask');

defaultSourceName是全局的,一次只能運行一個源。

4,擴展本地化源

ASP.NET Boilerplate還定義了一些本地化源。 例如,Abp.Web nuget軟件包將一個名爲「AbpWeb」的本地化源定義爲嵌入式XML文件:

默認(英文)XML文件以下(僅顯示前兩個文本):

<?xml version="1.0" encoding="utf-8" ?>
<localizationDictionary culture="en">
  <texts>
    <text name="InternalServerError" value="An internal error occurred during your request!" />
    <text name="ValidationError" value="Your request is not valid!" />
    ...
  </texts>
</localizationDictionary>

爲了擴展AbpWeb源,咱們能夠定義XML文件。 假設咱們只想改變InternalServerError文本。 咱們能夠定義一個XML文件,以下所示:

<?xml version="1.0" encoding="utf-8" ?>
<localizationDictionary culture="en">
  <texts>
    <text name="InternalServerError" value="Sorry :( It seems there is a problem. Let us to solve it and please try again later." />
  </texts>
</localizationDictionary>

而後咱們能夠在咱們的模塊的PreInitialize方法上註冊它:

Configuration.Localization.Sources.Extensions.Add(
    new LocalizationSourceExtensionInfo("AbpWeb",
        new XmlFileLocalizationDictionaryProvider(
            HttpContext.Current.Server.MapPath("~/Localization/AbpWebExtensions")
            )
        )
    );

5,獲取語言

ILanguageManager可用於獲取全部可用語言和當前語言的列表。

 

三10、導航

1,建立菜單

應用程序可能由不一樣的模塊組成,每一個模塊均可以擁有本身的菜單項。 要定義菜單項,咱們須要建立一個派生自NavigationProvider的類。

假設咱們有一個主菜單,以下所示:

這裏,管理菜單項有兩個子菜單項。 示例導航提供程序類建立這樣的菜單能夠以下所示:

public class SimpleTaskSystemNavigationProvider : NavigationProvider
{
    public override void SetNavigation(INavigationProviderContext context)
    {
        context.Manager.MainMenu
            .AddItem(
                new MenuItemDefinition(
                    "Tasks",
                    new LocalizableString("Tasks", "SimpleTaskSystem"),
                    url: "/Tasks",
                    icon: "fa fa-tasks"
                    )
            ).AddItem(
                new MenuItemDefinition(
                    "Reports",
                    new LocalizableString("Reports", "SimpleTaskSystem"),
                    url: "/Reports",
                    icon: "fa fa-bar-chart"
                    )
            ).AddItem(
                new MenuItemDefinition(
                    "Administration",
                    new LocalizableString("Administration", "SimpleTaskSystem"),
                    icon: "fa fa-cogs"
                    ).AddItem(
                        new MenuItemDefinition(
                            "UserManagement",
                            new LocalizableString("UserManagement", "SimpleTaskSystem"),
                            url: "/Administration/Users",
                            icon: "fa fa-users",
                            requiredPermissionName: "SimpleTaskSystem.Permissions.UserManagement"
                            )
                    ).AddItem(
                        new MenuItemDefinition(
                            "RoleManagement",
                            new LocalizableString("RoleManagement", "SimpleTaskSystem"),
                            url: "/Administration/Roles",
                            icon: "fa fa-star",
                            requiredPermissionName: "SimpleTaskSystem.Permissions.RoleManagement"
                            )
                    )
            );
    }
}

MenuItemDefinition基本上能夠有惟一的名稱,可本地化的顯示名稱,url和圖標。 也,

2,註冊導航

建立導航提供程序後,咱們應該在咱們的模塊的PreInitialize事件上註冊到ASP.NET Boilerplate配置: 

Configuration.Navigation.Providers.Add<SimpleTaskSystemNavigationProvider>(); 

3,顯示菜單

IUserNavigationManager能夠被注入並用於獲取菜單項並顯示給用戶。 所以,咱們能夠在服務器端建立菜單。

ASP.NET Boilerplate自動生成一個JavaScript API來獲取客戶端的菜單和項目。 abp.nav命名空間下的方法和對象能夠用於此目的。 例如,可使用abp.nav.menus.MainMenu來獲取應用程序的主菜單。 所以,咱們能夠在客戶端建立菜單。

 

三11、嵌入式資源文件

在一個web應用中,有供客戶端使用的javascript,css,xml等文件。它們通常是做爲分離的文件被添加到web項目中併發布。有時,咱們須要將這些文件打包到一個程序集(類庫項目,一個dll文件)中,做爲內嵌資源散佈到程序集中。ABP提供了一個基礎設施使得這個很容易實現。

1,建立嵌入式文件

咱們首先應該建立一個資源文件並把它標記爲內嵌的資源。任何程序集均可以包含內嵌的資源文件。假設咱們有一個叫作「Abp.Zero.Web.UI.Metronic.dll」程序集,並且它包含了javascript,css,和圖片文件:

①xproj / project.json格式
假設咱們有一個名爲EmbeddedPlugIn的項目,以下所示:

 

咱們想要使這些文件在一個web應用中可用,首先,咱們應該將想要暴露的文件標記爲內嵌的資源。在這裏,我選擇了 metronic.js文件,右鍵打開屬性面板(快捷鍵是F4)。

選中你想在web應用中使用的全部文件,將生成操做(build action)的屬性值選爲內嵌的 資源

2,暴露內嵌文件

ABP使得暴露這些內嵌資源很容易,只須要一行代碼:

WebResourceHelper.ExposeEmbeddedResources("AbpZero/Metronic", Assembly.GetExecutingAssembly(), "Abp.Zero.Web.UI.Metronic");

這行代碼通常放在模塊的Initialize方法中,下面解釋一下這些參數:

  • 第一個參數爲這些文件定義了根文件夾,它匹配了根命名空間。
  • 第二個參數定義了包含這些文件的程序集。本例中,我傳入了包含這行代碼的程序集。但你也能夠傳入任何包含內嵌資源的程序集。
  • 最後一個參數定義了這些文件在程序集的根命名空間。它是「默認的命名空間」加上「文件夾名」。默認的命名空間通常和程序集的名字是相同的,可是在程序集的屬性中進行更改。這裏 ,默認的命名空間是Abp(已經更改了),所以Metronic文件夾的命名空間是「Abp.Zero.Web.UI.Metronic」。

3,使用內嵌文件

能夠直接使用內嵌的資源:

<script type="text/javascript" src="~/AbpZero/Metronic/assets/global/scripts/metronic.js"></script>

ABP知道這是一個內嵌的資源,於是能夠從以前暴露的dll中得到文件。此外,還能夠在razor視圖中使用HtmlHelper的擴展方法IncludeScript:

@Html.IncludeScript("~/AbpZero/Metronic/assets/global/scripts/metronic.js")

這會產生下面的代碼:

<script src="/AbpZero/Metronic/assets/global/scripts/metronic.js?v=635438748506909100" type="text/javascript"></script>

惟一的不一樣就是參數v=635438748506909100。這樣作的好處是阻止了瀏覽器端腳本的失敗緩存。該值只有當你的dll從新生成(其實是文件的最後寫入時間)的時候纔會改變,並且若是該值改變了,瀏覽器就不會緩存這個文件了。所以,建議使用IncludeScript方式。這對於非嵌入的物理資源也是有效的。對應於css文件,也存在相應的IncludeStyle方法。

 

三十3、CSRF / XSRF保護

「跨站點請求僞造(CSRF)」是一種當惡意網站,電子郵件,博客,即時消息或程序致使用戶的Web瀏覽器對用戶所在的受信任站點執行沒必要要的操做時發生的攻擊類型 目前認證的「(OWASP)。

1,ASP.NET MVC

①ABP作了下面這些事情來客服上面的困難:

  • actions會被自動保護(經過AbpAntiForgeryMvcFilter)。自動保護能夠應對大多數狀況。固然,可使用DisableAbpAntiForgeryTokenValidation特性爲任何action和Controller關閉自動保護,也可使用 ValidateAbpAntiForgeryToken特性打開。
  • 除了HTML的表單域,AbpAntiForgeryMvcFilter也會檢查請求頭中的token。所以,能夠很容易對ajax請求使用反僞造token保護。
  • 在js中可使用abp.security.antiForgery.getToken()函數得到token。
  • 爲全部的ajax請求頭部自動添加反僞造token。

②在Layout視圖中添加如下代碼:

@{
    SetAntiForgeryCookie();
}

這樣,全部使用了這個佈局頁的頁面都會包含這句代碼了,該方法定義在ABP視圖基類中,它會建立和設置正確的token cookie,使得在js端能夠工做。若是有多個Layout的話,須要爲每一個佈局添加上面的代碼。

對於ASP.NET MVC 應用,只須要作這麼多,全部的ajax請求都會自動工做。可是對於HTML 表單仍然須要使用** @Html.AntiForgeryToken()** HTML幫助方法,由於表單不是經過Ajax提交的,可是不須要在相應的action上使用ValidateAbpAntiForgeryToken 特性了。

③配置

XSRF默認是打開的,也能夠在模塊的PreInitialize方法中關閉或配置,以下:

Configuration.Modules.AbpWeb().AntiForgery.IsEnabled = false;

也可使用Configuration.Modules.AbpWebCommon().AntiForgery對象配置token和cookie名稱。

2,ASP.NET WEB API

ASP.NET Web API不包括反僞造機制,ABP爲ASP.NET Web API Controllers提供了基礎設施來添加CSRF保護,而且是徹底自動化的。

ASP.NET MVC客戶端

若是在MVC項目中使用了Web API,那麼不須要額外的配置。只要Ajax請求是從一個配置的MVC應用中發出的,即便你的Web API層自宿主在其它進程中,也不須要配置。

②其餘客戶端

若是你的客戶端是其它類型的應用(好比,一個獨立的angularjs應用,它不能像以前描述的那樣使用SetAntiForgeryCookie()方法),那麼你應該提供一種設置反僞造token cookie的方法。一種可能的實現方式是像下面那樣建立一個api控制器:

using System.Net.Http;
using Abp.Web.Security.AntiForgery;
using Abp.WebApi.Controllers;

namespace AngularForgeryDemo.Controllers
{
    public class AntiForgeryController : AbpApiController
    {
        private readonly IAbpAntiForgeryManager _antiForgeryManager;

        public AntiForgeryController(IAbpAntiForgeryManager antiForgeryManager)
        {
            _antiForgeryManager = antiForgeryManager;
        }

        public HttpResponseMessage GetTokenCookie()
        {
            var response = new HttpResponseMessage();

            _antiForgeryManager.SetCookie(response.Headers);

            return response;
        }
    }
}

而後就能夠從客戶端調用這個action來設置cookie了。

3,客戶端類

①jQuery

abp.jquery.js中定義了一個ajax攔截器,它能夠將反僞造請求token添加到每一個請求的請求頭中,它會從abp.security.antiForgery.getToken()函數中得到token。

②Angularjs

Angularjs會將反僞造token自動添加到全部的ajax請求中,請點擊連接查看Angularjs的XSRF保護一節。ABP默認使用了相同的cookie和header名稱。所以,Angularjs集成是現成可用的。

 

三十4、後臺工做(Jobs)和工做者(Workers)

1,後臺工做

後臺做業用於將某些任務排列在後臺以排隊和持久的方式執行。 您可能須要後臺工做,緣由有幾個。 一些例子:

  • 執行長時間運行的任務。好比,一個用戶按了「report」按鈕來啓動一個長時間運行的報告工做,點擊了這個按鈕你不可能讓用戶一直處於等待狀態,因此你應該將這個工做(job)添加到 隊列(queue)中,而後,當這項工做完成時,經過郵件將報告結果發送給該用戶。
  • 建立重複嘗試(re-trying)和持續的任務來保證代碼將會 成功執行。好比,你能夠在後臺工做中發送郵件以克服 臨時失敗,並 保證郵件最終可以發送出去。所以,當發送郵件的時候用戶不須要等待。

①建立後臺工做

咱們能夠經過繼承BackgroundJob <TArgs>類或直接實現IBackgroundJob <TArgs>接口來建立一個後臺做業類。這是最簡單的後臺工做:

public class TestJob : BackgroundJob<int>, ITransientDependency
{
    public override void Execute(int number)
    {
        Logger.Debug(number.ToString());
    }
}

後臺做業定義了一個Execute方法獲取一個輸入參數。 參數類型定義爲通用類參數,如示例所示。

後臺做業必須註冊到依賴注入。 實現ITransientDependency是最簡單的方法。

咱們來定義一個更現實的工做,在後臺隊列中發送電子郵件:

public class SimpleSendEmailJob : BackgroundJob<SimpleSendEmailJobArgs>, ITransientDependency
{
    private readonly IRepository<User, long> _userRepository;
    private readonly IEmailSender _emailSender;

    public SimpleSendEmailJob(IRepository<User, long> userRepository, IEmailSender emailSender)
    {
        _userRepository = userRepository;
        _emailSender = emailSender;
    }
    
    [UnitOfWork]
    public override void Execute(SimpleSendEmailJobArgs args)
    {
        var senderUser = _userRepository.Get(args.SenderUserId);
        var targetUser = _userRepository.Get(args.TargetUserId);

        _emailSender.Send(senderUser.EmailAddress, targetUser.EmailAddress, args.Subject, args.Body);
    }
}

咱們注入了user倉儲(爲了得到用戶信息)和email發送者(發送郵件的服務),而後簡單地發送了該郵件。SimpleSendEmailJobArgs是該工做的參數,它定義以下:

[Serializable]
public class SimpleSendEmailJobArgs
{
    public long SenderUserId { get; set; }

    public long TargetUserId { get; set; }

    public string Subject { get; set; }

    public string Body { get; set; }
}

工做參數應該是serializable(可序列化),由於要將它 序列化並存儲到數據庫中。雖然ABP默認的後臺工做管理者使用了JSON序列化(它不須要[Serializable]特性),可是最好定義 [Serializable]特性,由於咱們未來可能會轉換到其餘使用二進制序列化的工做管理者。

記住,要保持你的參數簡單,不要在參數中包含實體或者其餘非可序列化的對象。正如上面的例子演示的那樣,咱們只存儲了實體的 Id,而後在該工做的內部從倉儲中得到該實體。

②添加新工做到隊列

定義後臺做業後,咱們能夠注入並使用IBackgroundJobManager將做業添加到隊列中。 參見上面定義的TestJob的示例:

[AbpAuthorize]
public class MyEmailAppService : ApplicationService, IMyEmailAppService
{
    private readonly IBackgroundJobManager _backgroundJobManager;

    public MyEmailAppService(IBackgroundJobManager backgroundJobManager)
    {
        _backgroundJobManager = backgroundJobManager;
    }

    public async Task SendEmail(SendEmailInput input)
    {
            await _backgroundJobManager.EnqueueAsync<SimpleSendEmailJob, SimpleSendEmailJobArgs>(
            new SimpleSendEmailJobArgs
            {
                Subject = input.Subject,
                Body = input.Body,
                SenderUserId = AbpSession.GetUserId(),
                TargetUserId = input.TargetUserId
            });
    }
}

Enqueu (或 EnqueueAsync)方法還有其餘的參數,好比 priority和 delay(優先級和延遲)

③IBackgroundJobManager默認實現

IBackgroundJobManager默認是由BackgroundJobManager實現的。它能夠被其餘的後臺工做提供者替代(看後面的Hangfire集成)。關於默認的BackgroundJobManager一些信息以下:

  • 它是一個在單線程中以FIFO(First In First Out)工做的簡單工做隊列,使用 IBackgroundJobStore來持久化工做。
  • 它會重複嘗試執行工做,直到工做成功執行(不會拋出任何異常)或者超時。默認的超時是一個工做2天。
  • 當成功執行後,它會從存儲(數據庫)中刪除該工做。若是超時了,就會將該工做設置爲 abandoned(廢棄的),並保留在數據庫中。
  • 在重複嘗試一個工做之間會增長等待時間。第一次重試時等待1分鐘,第二次等待2分鐘,第三次等待4分鐘等等。
  • 在固定的時間間隔輪詢工做的存儲。查詢工做時先按優先級排序,再按嘗試次數排序。

後臺工做存儲

默認的BackgroundJobManager須要一個數據存儲來保存、得到工做。若是你沒有實現IBackgroundJobStore,那麼它會使用 InMemoryBackgroundJobStore,它不會將工做持久化到數據庫中。你能夠簡單地實現它來存儲工做到數據庫或者你可使用module-zero,它已經實現了IBackgroundJobStore。

若是你正在使用第三方的工做管理者(像Hangfire),那麼不須要實現IBackgroundJobStore。

④配置

您能夠在模塊的PreInitialize方法中使用Configuration.BackgroundJobs來配置後臺做業系統。

關閉工做執行功能
你可能想關閉應用程序的後臺工做執行:

public class MyProjectWebModule : AbpModule
{
    public override void PreInitialize()
    {
        Configuration.BackgroundJobs.IsJobExecutionEnabled = false;
    }

    //...
}

2,後臺工做者

後臺工做者不一樣於後臺工做。它們是運行在應用後臺的簡單獨立線程。通常來講,它們會按期地執行一些任務。好比:

  • 後臺工做者能夠按期運行來刪除舊的日誌
  • 後臺工做者能夠按期運行來肯定不活躍的用戶,並給他們發送郵件以使他們返回你的應用。

①建立後臺工做者

要建立後臺工做者,咱們應該實現IBackgroundWorker接口。除此以外,咱們能夠基於需求從 BackgroundWorkerBase或者 PeriodicBackgroundWorkerBase繼承。

假設一個用戶在過去30天內沒有登陸到該應用,那咱們想要讓Ta的狀態爲passive。看下面的代碼:

public class MakeInactiveUsersPassiveWorker : PeriodicBackgroundWorkerBase, ISingletonDependency
{
    private readonly IRepository<User, long> _userRepository;

    public MakeInactiveUsersPassiveWorker(AbpTimer timer, IRepository<User, long> userRepository)
        : base(timer)
    {
        _userRepository = userRepository;
        Timer.Period = 5000; //5 seconds (good for tests, but normally will be more)
    }

    [UnitOfWork]
    protected override void DoWork()
    {
        using (CurrentUnitOfWork.DisableFilter(AbpDataFilters.MayHaveTenant))
        {
            var oneMonthAgo = Clock.Now.Subtract(TimeSpan.FromDays(30));

            var inactiveUsers = _userRepository.GetAllList(u =>
                u.IsActive &&
                ((u.LastLoginTime < oneMonthAgo && u.LastLoginTime != null) || (u.CreationTime < oneMonthAgo && u.LastLoginTime == null))
                );

            foreach (var inactiveUser in inactiveUsers)
            {
                inactiveUser.IsActive = false;
                Logger.Info(inactiveUser + " made passive since he/she did not login in last 30 days.");
            }

            CurrentUnitOfWork.SaveChanges();
        }
    }
}

這是現實中的代碼,而且在具備module-zero模塊的ABP中直接有效。

  • 若是你從PeriodicBackgroundWorkerBase 類繼承(如這個例子),那麼你應該實現 DoWork方法來執行按期運行的代碼。
  • 若是從BackgroundWorkerBase繼承或直接實現了 IBackgroundWorker,那麼你要重寫或者實現Start, Stop 和 WaitToStop方法。Start和Stop方法應該是 非阻塞的(non-blocking),WaitToStop方法應該等待工做者完成當前重要的工做。

②註冊後臺工做者

建立一個後臺工做者後,咱們應該把它添加到IBackgroundWorkerManager,一般放在模塊的PostInitialize方法中:

public class MyProjectWebModule : AbpModule
{
    //...

    public override void PostInitialize()
    {
        var workManager = IocManager.Resolve<IBackgroundWorkerManager>();
        workManager.Add(IocManager.Resolve<MakeInactiveUsersPassiveWorker>());
    }
}

雖然通常咱們將工做者添加到PostInitialize方法中,可是沒有強制要求。你能夠在任何地方注入IBackgroundWorkerManager,在運行時添加工做者。
當應用要關閉時,IBackgroundWorkerManager會中止並釋放全部註冊的工做者。

③後臺工做者生命週期

 後臺工做者通常實現爲單例的,可是沒有嚴格限制。若是你須要相同工做者類的多個實例,那麼可使它成爲transient(每次使用時建立),而後給IBackgroundWorkerManager添加多個實例。在這種狀況下,你的工做者極可能會參數化(好比,你有單個LogCleaner類,可是兩個LogCleaner工做者實例會監視並清除不一樣的log文件夾)。

3,讓你的應用程序一直運行

只有當你的應用運行時,後臺工做和工做者纔會工做。若是一個Asp.Net 應用長時間沒有執行請求,那麼它默認會關閉(shutdown)。若是你想讓後臺工做一直在web應用中執行(這是默認行爲),那麼你要確保web應用配置成了老是運行。不然,後臺工做只有在應用使用時纔會執行。

有不少技術來實現這個目的。最簡單的方法是從外部應用按期向你的web應用發送請求。這樣,你能夠檢查web應用是否開啓而且處於運行狀態。Hangfire文檔講解了一些其餘的方法。

 

三十5、Hangfire集成

Hangfire是一個綜合的後臺工做管理者。你能夠將Hangfire集成到ABP中,這樣就能夠不使用默認的後臺工做管理者了。但你仍然能夠爲Hangfire使用相同的後臺工做API。這樣,你的代碼就獨立於Hangfire了,可是,若是你喜歡的話,也能夠直接使用 Hangfire的API

1,ASP.NET MVC 5.x 集成

Abp.HangFire nuget包用於ASP.NET MVC 5.x項目:

Install-Package Abp.HangFire

而後,您能夠爲Hangfire安裝任何存儲。 最多見的是SQL Server存儲(請參閱Hangfire.SqlServer nuget軟件包)。 安裝這些nuget軟件包後,您能夠將項目配置爲使用Hangfire,以下所示:

[DependsOn(typeof (AbpHangfireModule))]
public class MyProjectWebModule : AbpModule
{
    public override void PreInitialize()
    {
        Configuration.BackgroundJobs.UseHangfire(configuration =>
        {
            configuration.GlobalConfiguration.UseSqlServerStorage("Default");
        });
                
    }

    //...
}

咱們添加了AbpHangfireModule做爲依賴,並使用Configuration.BackgroundJobs.UseHangfire方法啓用和配置Hangfire(「Default」是web.config中的鏈接字符串)。

Hangfire在數據庫中須要模式建立權限,由於它在第一次運行時建立本身的模式和表。

儀表板受權
Hagfire能夠顯示儀表板頁面,以實時查看全部後臺做業的狀態。 您能夠按照其說明文檔中的說明進行配置。 默認狀況下,此儀表板頁面適用於全部用戶,未經受權。 您可使用Abp.HangFire包中定義的AbpHangfireAuthorizationFilter類將其集成到ABP的受權系統中。 示例配置:

app.UseHangfireDashboard("/hangfire", new DashboardOptions
{
    Authorization = new[] { new AbpHangfireAuthorizationFilter() }
});

這將檢查當前用戶是否已登陸到應用程序。 若是你想要一個額外的權限,你能夠傳入它的構造函數:

app.UseHangfireDashboard("/hangfire", new DashboardOptions
{
    Authorization = new[] { new AbpHangfireAuthorizationFilter("MyHangFireDashboardPermissionName") }
});

注意:UseHangfireDashboard應該在您的啓動類中的認證中間件以後調用(多是最後一行)。 不然,受權老是失敗。

 

三十6、Quartz 集成

Quartz是一個全功能的開源做業調度系統,能夠從最小的應用程序到大型企業系統使用。 Abp.Quartz包簡單地將Quartz集成到ASP.NET Boilerplate。

ASP.NET Boilerplate具備內置的持久性後臺做業隊列和後臺工做系統。 若是您對後臺工做人員有高級調度要求,Quartz能夠是一個很好的選擇。 此外,Hangfire能夠成爲持久後臺做業隊列的好選擇。

1,安裝

將Abp.Quartz nuget軟件包安裝到您的項目中,並將一個DependsOn屬性添加到您的AbpQuartzModule模塊中:

[DependsOn(typeof (AbpQuartzModule))]
public class YourModule : AbpModule
{
    //...
}

要建立新做業,您能夠實現Quartz的IJob界面,也能夠從具備一些幫助屬性/方法的JobBase類(在Abp.Quartz包中定義)派生(用於記錄和本地化)。 一個簡單的工做類以下所示:

public class MyLogJob : JobBase, ITransientDependency
{
    public override void Execute(IJobExecutionContext context)
    {
        Logger.Info("Executed MyLogJob :)");
    }
}

咱們只是執行Execute方法來寫一個日誌。 您能夠看到更多的Quartz的文檔。

2,調度工做

IQuartzScheduleJobManager用於調度做業。 您能夠將其注入到您的類(或者您能夠在您的模塊的PostInitialize方法中解析並使用它)來計劃做業。 調度做業的示例控制器:

public class HomeController : AbpController
{
    private readonly IQuartzScheduleJobManager _jobManager;

    public HomeController(IQuartzScheduleJobManager jobManager)
    {
        _jobManager = jobManager;
    }
        
    public async Task<ActionResult> ScheduleJob()
    {
        await _jobManager.ScheduleAsync<MyLogJob>(
            job =>
            {
                job.WithIdentity("MyLogJobIdentity", "MyGroup")
                    .WithDescription("A job to simply write logs.");
            },
            trigger =>
            {
                trigger.StartNow()
                    .WithSimpleSchedule(schedule =>
                    {
                        schedule.RepeatForever()
                            .WithIntervalInSeconds(5)
                            .Build();
                    });
            });

        return Content("OK, scheduled!");
    }
}  

 

三十7、通知系統

1,介紹

通知(Notification)用於告知用戶系統中的特定事件。ABP提供了基於實時通知基礎設施的發佈訂閱模型(pub/sub)。

①發送模型

給用戶發送通知有兩種方式:

  • 首先用戶訂閱特定的通知類型,而後咱們發佈這種類型的通知,這種類型的通知會傳遞給全部已經訂閱的用戶。這就是發佈訂閱(pub/sub)模型。
  • 直接給目標用戶發送通知。

②通知類型

通知類型也有兩種:

  • 通常通知:是任意類型的通知。「若是一個用戶給我發送了添加好友的請求就通知我」是這種通知類型的一個例子。
  • 實體通知:和特定的實體相關聯。「若是一個用戶在 這張圖片上評論那麼通知我」是基於實體的通知,由於它和特定的實體相關聯。用戶可能想得到某些圖片的通知而不是全部的圖片通知。

③通知數據 

一個通知通常都會包括一個通知數據。好比,「若是一個用戶給我發送了添加好友的請求就通知我」,這個通知可能有兩個數據屬性:發送者用戶名(那哪個用戶發送的這個添加好友的請求)和請求內容(該用戶在這個請求中寫的東西)。很明顯,該通知數據類型緊耦合於通知類型。不一樣的通知類型有不一樣的數據類型。

  • 通知數據是可選的。某些通知可能不須要數據。一些預約義的通知數據類型可能對於大多數狀況夠用了。 MessageNotificationData能夠用於簡單的信息, LocalizableMessageNotificationData能夠用於本地化的,帶參數的通知信息。

④通知安全

通知的安全性有5個級別,它們定義在NotificationSeverity枚舉類中,分別是 Info,Success,Warn,Error和Fatal。默認值是Info。

2,訂閱通知

INotificationSubscriptionManager提供API來訂閱通知。 例子:

public class MyService : ITransientDependency
{
    private readonly INotificationSubscriptionManager _notificationSubscriptionManager;

    public MyService(INotificationSubscriptionManager notificationSubscriptionManager)
    {
        _notificationSubscriptionManager = notificationSubscriptionManager;
    }

    //訂閱通常通知(發送好友請求)
    public async Task Subscribe_SentFrendshipRequest(int? tenantId, long userId)
    {
        await _notificationSubscriptionManager.SubscribeAsync(new UserIdentifier(tenantId, userId), "SentFrendshipRequest");    
    }

    //訂閱實體通知(評論圖片)
    public async Task Subscribe_CommentPhoto(int? tenantId, long userId, Guid photoId)
    {
        await _notificationSubscriptionManager.SubscribeAsync(new UserIdentifier(tenantId, userId), "CommentPhoto", new EntityIdentifier(typeof(Photo), photoId));   
    }
}

首先,咱們注入了INotificationSubscriptionManager。第一個方法訂閱了一個通常通知。當某人發送了一個添加好友的請求時,用戶想獲得通知。第二個方法訂閱了一個和特定實體(Photo)相關的通知。若是任何人對這個特定的圖片進行了評論,那麼用戶就會收到通知。

每個通知應該有惟一的名字(好比例子中的SentFrendshipRequest和 CommentPhoto)。

INotificationSubscriptionManager還有不少方法來管理通知,好比UnsubscribeAsync, IsSubscribedAsync, GetSubscriptionsAsync等方法。

3,發佈通知

INotification Publisher用於發佈通知。 例子:

public class MyService : ITransientDependency
{
    private readonly INotificationPublisher _notiticationPublisher;

    public MyService(INotificationPublisher notiticationPublisher)
    {
        _notiticationPublisher = notiticationPublisher;
    }

    //給特定的用戶發送一個通常通知
    public async Task Publish_SentFrendshipRequest(string senderUserName, string friendshipMessage, long targetUserId)
    {
        await _notiticationPublisher.PublishAsync("SentFrendshipRequest", new SentFrendshipRequestNotificationData(senderUserName, friendshipMessage), userIds: new[] { targetUserId });
    }

    //給特定的用戶發送一個實體通知
    public async Task Publish_CommentPhoto(string commenterUserName, string comment, Guid photoId, long photoOwnerUserId)
    {
        await _notiticationPublisher.PublishAsync("CommentPhoto", new CommentPhotoNotificationData(commenterUserName, comment), new EntityIdentifier(typeof(Photo), photoId), userIds: new[] { photoOwnerUserId });
    }

    //給具特定嚴重級別程度的全部訂閱用戶發送一個通常通知
    public async Task Publish_LowDisk(int remainingDiskInMb)
    {
        //例如 "LowDiskWarningMessage"的英文內容 -> "Attention! Only {remainingDiskInMb} MBs left on the disk!"
        var data = new LocalizableMessageNotificationData(new LocalizableString("LowDiskWarningMessage", "MyLocalizationSourceName"));
        data["remainingDiskInMb"] = remainingDiskInMb;

        await _notiticationPublisher.PublishAsync("System.LowDisk", data, severity: NotificationSeverity.Warn);    
    }
}

在第一個例子中,咱們向單個用戶發佈了一個通知。 SentFrendshipRequestNotificationData應該從NotificationData派生以下:

[Serializable]
public class SentFrendshipRequestNotificationData : NotificationData
{
    public string SenderUserName { get; set; }

    public string FriendshipMessage { get; set; }

    public SentFrendshipRequestNotificationData(string senderUserName, string friendshipMessage)
    {
        SenderUserName = senderUserName;
        FriendshipMessage = friendshipMessage;
    }
}

在第二個例子中,咱們向特定的用戶發送了一個特定實體的通知。通知數據類CommentPhotoNotificationData通常不須要serializble(由於默認使用了JSON序列化)。可是建議把它標記爲serializable,由於你可能須要在應用之間移動通知,也可能在將來使用二進制的序列。此外,正如以前聲明的那樣,通知數據是可選的,並且對於全部的通知可能不是必須的。

注意:若是咱們對特定的用戶發佈了一個通知,那麼他們不須要訂閱那些通知。

在第三個例子中,咱們沒有定義一個專用的通知數據類。相反,咱們直接使用了內置的具備基於字典數據的LocalizableMessageNotificationData,而且以 Warn發佈了通知。LocalizableMessageNotificationData能夠存儲基於字典的任意數據(這對於自定義的通知數據類也是成立的,由於它們也從 NotificationData類繼承)。在本地化時咱們使用了「 remaingDiskInMb」做爲參數。本地化信息能夠包含這些參數(好比例子中的"Attention! Only {remainingDiskInMb} MBs left on the disk!")

4,用戶通知管理者

IUserNotificationManager用於管理用戶的通知,它有 get,update或delete用戶通知的方法。你能夠爲你的應用程序準備一個通知列表頁面。

5,實時通知

雖然可使用IUserNotificationManager來查詢通知,但咱們一般但願將實時通知推送到客戶端。

通知系統使用IRealTimeNotifier向用戶發送實時通知。 這能夠用任何類型的實時通訊系統來實現。 它使用SignalR在一個分開的包中實現。 啓動模板已經安裝了SignalR。 有關詳細信息,請參閱SignalR集成文檔。

注意:通知系統在後臺做業中異步調用IRealTimeNotifier。 所以,可能會以較小的延遲發送通知。

①客戶端

當收到實時通知時,ASP.NET Boilerplate會觸發客戶端的全局事件。 您能夠註冊以獲取通知:

abp.event.on('abp.notifications.received', function (userNotification) {
    console.log(userNotification);
});

每一個收到的實時通知觸發abp.notifications.received事件。 您能夠註冊到此事件,如上所示,以得到通知。 有關事件的更多信息,請參閱JavaScript事件總線文檔。 「System.LowDisk」示例傳入通知JSON示例:

{
    "userId": 2,
    "state": 0,
    "notification": {
        "notificationName": "System.LowDisk",
        "data": {
            "message": {
                "sourceName": "MyLocalizationSourceName",
                "name": "LowDiskWarningMessage"
            },
            "type": "Abp.Notifications.LocalizableMessageNotificationData",
            "properties": {
                "remainingDiskInMb": "42"
            }
        },
        "entityType": null,
        "entityTypeName": null,
        "entityId": null,
        "severity": 0,
        "creationTime": "2016-02-09T17:03:32.13",
        "id": "0263d581-3d8a-476b-8e16-4f6a6f10a632"
    },
    "id": "4a546baf-bf17-4924-b993-32e420a8d468"
}

在這個對象中

  • userId:當前的用戶Id。通常來講不須要這個,由於你知道當前的用戶。
  • state: UserNotificationState枚舉的值。0:未讀,1:已讀。
  • notification:通知細節。
    • notificationName:通知的惟一名稱。(當發佈該通知時使用相同的值)
    • data:通知數據。在本例中,咱們使用了LocalizableMessageNotificationData (正如以前的例子中發佈的)。
    • message:本地的信息通知。咱們可使用 sourceName和 name來本地化UI上的信息。
    • type:通知數據類型。全類型名稱,包括命名空間。當處理該通知數據時咱們能夠檢查該類型。
    • properties:基於字典的自定義屬性。
    • entityType, entityTypeName和entityId:和通知相關的實體的信息。
    • severity: NotificationSeverity枚舉的值。0: Info, 1: Success, 2: Warn, 3: Error, 4: Fatal。
    • creationTime:該通知建立的時間。
    • id:通知的Id。
  • id:用戶的通知id。

固然,你不會只記錄通知。 您可使用通知數據向用戶顯示通知信息。 例:

abp.event.on('abp.notifications.received', function (userNotification) {
    if (userNotification.notification.data.type === 'Abp.Notifications.LocalizableMessageNotificationData') {
        var localizedText = abp.localization.localize(
            userNotification.notification.data.message.name,
            userNotification.notification.data.message.sourceName
        );

        $.each(userNotification.notification.data.properties, function (key, value) {
            localizedText = localizedText.replace('{' + key + '}', value);
        });

        alert('New localized notification: ' + localizedText);
    } else if (userNotification.notification.data.type === 'Abp.Notifications.MessageNotificationData') {
        alert('New simple notification: ' + userNotification.notification.data.message);
    }
});

它適用於內置的通知數據類型(LocalizableMessageNotificationData和MessageNotificationData)。 若是您有自定義通知數據類型,那麼您應該註冊這樣的數據格式化程序:

abp.notifications.messageFormatters['MyProject.MyNotificationDataType'] = function(userNotification) {
    return ...; //格式和返回信息
};

6,通知存儲

通知系統使用了INotificationStore來持久化通知。爲了使通知系統合適地工做,應該實現這個接口。你能夠本身實現或者使用module-zero(它已經實現了該接口)。7,

7,通知定義

在使用以前你沒必要定義一個通知。你無需定義任何類型的通知名就可使用它們。可是,定義通知名可能會給你帶來額外的好處。好比,你之後要研究應用程序中全部的通知,在這種狀況下,咱們能夠給咱們的模塊定義一個通知提供器(notification provider ),以下所示:

public class MyAppNotificationProvider : NotificationProvider
{
    public override void SetNotifications(INotificationDefinitionContext context)
    {
        context.Manager.Add(
            new NotificationDefinition(
                "App.NewUserRegistered",
                displayName: new LocalizableString("NewUserRegisteredNotificationDefinition", "MyLocalizationSourceName"),
                permissionDependency: new SimplePermissionDependency("App.Pages.UserManagement")
                )
            );
    }
}

"App.NewUserRegistered"是該通知的惟一名稱。咱們定義了一個本地的 displayName(而後當在UI上訂閱了該通知時,咱們能夠顯示該通知)。最後,咱們聲明瞭只有擁有了"App.Pages.UserManagement"權限的用戶纔可使用該通知。

也有一些其餘的參數,你能夠在代碼中研究。對於一個通知定義,只有通知名稱是必須的。

當定義了這麼一個通知提供器以後,咱們應該在模塊的PreInitialize事件中進行註冊,以下所示:

public class AbpZeroTemplateCoreModule : AbpModule
{
    public override void PreInitialize()
    {
        Configuration.Notifications.Providers.Add<MyAppNotificationProvider>();
    }

    //...
}

最後,要得到通知定義,在應用程序中注入並使用INotificationDefinitionManager

 

三十8、SignalR集成

Abp.Web.SignalR nuget軟件包能夠方便地在基於ASP.NET Boilerplate的應用程序中使用SignalR

1,安裝

①服務器端

將Abp.Web.SignalR nuget包安裝到您的項目(一般到您的Web層),並向您的模塊添加依賴關係:

[DependsOn(typeof(AbpWebSignalRModule))]
public class YourProjectWebModule : AbpModule
{
    //...
}

而後按照慣例,在OWIN啓動類中使用MapSignalR方法:

[assembly: OwinStartup(typeof(Startup))]
namespace MyProject.Web
{
    public class Startup
    {
        public void Configuration(IAppBuilder app)
        {
            app.MapSignalR();

            //...
        }
    }
}

注意:Abp.Web.SignalR只依賴於Microsoft.AspNet.SignalR.Core包。 所以,若是之前沒有安裝,您還須要將Microsoft.AspNet.SignalR包安裝到您的Web項目中(有關更多信息,請參閱SignalR文檔)。

②客戶端

應該將abp.signalr.js腳本包含在頁面中。 它位於Abp.Web.Resources包(它已經安裝在啓動模板中)

<script src="~/signalr/hubs"></script>
<script src="~/Abp/Framework/scripts/libs/abp.signalr.js"></script>

就這樣。 SignalR已正確配置並集成到您的項目中。

2,創建鏈接

當您的頁面中包含abp.signalr.js時,ASP.NET Boilerplate會自動鏈接到服務器(從客戶端)。 這一般很好。 但可能有些狀況你不想要它。 您能夠在包括abp.signalr.js以前添加這些行以禁用自動鏈接:

<script>
    abp.signalr = abp.signalr || {};
    abp.signalr.autoConnect = false;
</script>

在這種狀況下,您能夠在須要鏈接到服務器時手動調用abp.signalr.connect()函數。

若是abp.signalr.autoConnect爲true,ASP.NET Boilerplate也會自動從新鏈接到客戶端(從客戶端)。

當客戶端鏈接到服務器時,會觸發「abp.signalr.connected」全局事件。 您能夠註冊到此事件,以便在鏈接成功創建時採起行動。 有關客戶端事件的更多信息,請參閱JavaScript事件總線文檔。

3,內置功能

您能夠在應用程序中使用SignalR的全部功能。 另外,Abp.Web.SignalR包實現了一些內置的功能。

①通知(Notification)

Abp.Web.SignalR包實現IRealTimeNotifier向客戶端發送實時通知(見通知系統)。 所以,用戶能夠得到實時推送通知。

②在線客戶端

ASP.NET Boilerplate提供IOnlineClientManager來獲取有關在線用戶的信息(注入IOnlineClientManager並使用GetByUserIdOrNull,GetAllClients,IsOnline方法)。 IOnlineClientManager須要通訊基礎設施才能正常工做。 Abp.Web.SignalR包提供了基礎架構。 所以,若是安裝了SignalR,您能夠在應用程序的任何層中注入和使用IOnlineClientManager。

③PascalCase與camelCase
Abp.Web.SignalR包覆蓋SignalR的默認ContractResolver,以便在序列化時使用CamelCasePropertyNamesContractResolver。 所以,咱們可讓類在服務器上具備PascalCase屬性,並將它們做爲camelCase在客戶端上發送/接收對象(由於camelCase是javascript中的首選符號)。 若是您想在某些程序集中忽略此類,那麼能夠將這些程序集添加到AbpSignalRContractResolver.IgnoredAssemblies列表中。

4,您的SignalR代碼

Abp.Web.SignalR包也簡化了SignalR代碼。 假設咱們要添加一個Hub到咱們的應用程序:

public class MyChatHub : Hub, ITransientDependency
{
    public IAbpSession AbpSession { get; set; }

    public ILogger Logger { get; set; }

    public MyChatHub()
    {
        AbpSession = NullAbpSession.Instance;
        Logger = NullLogger.Instance;
    }

    public void SendMessage(string message)
    {
        Clients.All.getMessage(string.Format("User {0}: {1}", AbpSession.UserId, message));
    }

    public async override Task OnConnected()
    {
        await base.OnConnected();
        Logger.Debug("A client connected to MyChatHub: " + Context.ConnectionId);
    }

    public async override Task OnDisconnected(bool stopCalled)
    {
        await base.OnDisconnected(stopCalled);
        Logger.Debug("A client disconnected from MyChatHub: " + Context.ConnectionId);
    }
}

咱們實現了ITransientDependency接口,簡單地將咱們的集線器註冊到依賴注入系統(您能夠根據須要進行單例化)。 咱們注入session和logger。

SendMessage是咱們的集線器的一種方法,能夠被客戶端使用。 咱們在這個方法中調用全部客戶端的getMessage函數。 咱們可使用AbpSession來獲取當前用戶ID(若是用戶登陸)。 咱們還覆蓋了OnConnected和OnDisconnected,這只是爲了演示,實際上並不須要。

這裏,客戶端的JavaScript代碼使用咱們的集線器發送/接收消息。

var chatHub = $.connection.myChatHub; 

chatHub.client.getMessage = function (message) { //註冊傳入消息
    console.log('received message: ' + message);
};

abp.event.on('abp.signalr.connected', function() { //註冊鏈接事件
    chatHub.server.sendMessage("Hi everybody, I'm connected to the chat!"); //向服務器發送消息
});

而後咱們能夠隨時使用chatHub發送消息到服務器。 有關SignalR的詳細信息,請參閱SignalR文檔。

 

三十9、EntityFramework集成

1,Nuget包

Abp.EntityFramework

2,DbContext

如您所知,要使用EntityFramework,您應該爲應用程序定義一個DbContext類。 示例DbContext以下所示:

public class SimpleTaskSystemDbContext : AbpDbContext
{
    public virtual IDbSet<Person> People { get; set; }
    public virtual IDbSet<Task> Tasks { get; set; }

    public SimpleTaskSystemDbContext()
        : base("Default")
    {

    }
    
    public SimpleTaskSystemDbContext(string nameOrConnectionString)
        : base(nameOrConnectionString)
    {

    }

    public SimpleTaskSystemDbContext(DbConnection existingConnection)
        : base(existingConnection, false)
    {

    }

    public SimpleTaskSystemDbContext(DbConnection existingConnection, bool contextOwnsConnection)
        : base(existingConnection, contextOwnsConnection)
    {

    }

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

        modelBuilder.Entity<Person>().ToTable("StsPeople");
        modelBuilder.Entity<Task>().ToTable("StsTasks").HasOptional(t => t.AssignedPerson);
    }
}

除了如下規則,它是一個常規的DbContext類:

①它來自AbpDbContext而不是DbContext。 

②它應該有上面的示例的構造函數(構造函數參數名也應該相同)。 說明:

  默認構造器將「Default」傳遞給基類做爲鏈接字符串。 所以,它但願在web.config / app.config文件中使用一個名爲「Default」的鏈接字符串。 這個構造函數不被ABP使用,而是由EF命令行遷移工具命令(如update-database)使用。

  構造函數獲取nameOrConnectionString由ABP用於在運行時傳遞鏈接名稱或字符串。

  構造函數獲取existingConnection能夠用於單元測試,而不是ABP直接使用。

  構造函數獲取existingConnection,而且在使用DbContextEfTransactionStrategy時,ABP在單個數據庫多個dbcontext場景中使用contextOwnsConnection來共享相同的connection和transaction()(參見下面的事務管理部分)。

3,倉儲

倉儲庫用於從較高層抽象數據訪問

①默認倉儲庫

Abp.EntityFramework爲您在DbContext中定義的全部實體實現默認存儲庫。 您沒必要建立存儲庫類以使用預約義的存儲庫方法。 例:

public class PersonAppService : IPersonAppService
{
    private readonly IRepository<Person> _personRepository;

    public PersonAppService(IRepository<Person> personRepository)
    {
        _personRepository = personRepository;
    }

    public void CreatePerson(CreatePersonInput input)
    {        
        person = new Person { Name = input.Name, EmailAddress = input.EmailAddress };

        _personRepository.Insert(person);
    }
}

②應用程序特定基礎倉儲庫類

ASP.NET Boilerplate提供了一個基類EfRepositoryBase來輕鬆實現存儲庫。 要實現IRepository接口,您只需從該類派生您的存儲庫。 可是最好建立本身的擴展EfRepositoryBase的基類。 所以,您能夠將共享/經常使用方法添加到存儲庫中,或輕鬆覆蓋現有方法。 一個示例基類所有用於SimpleTaskSystem應用程序的存儲庫:

//個人應用程序中全部存儲庫的基類
public class SimpleTaskSystemRepositoryBase<TEntity, TPrimaryKey> : EfRepositoryBase<SimpleTaskSystemDbContext, TEntity, TPrimaryKey>
    where TEntity : class, IEntity<TPrimaryKey>
{
    public SimpleTaskSystemRepositoryBase(IDbContextProvider<SimpleTaskSystemDbContext> dbContextProvider)
        : base(dbContextProvider)
    {
    }

    //爲全部存儲庫添加經常使用方法
}

//具備整數ID的實體的快捷方式
public class SimpleTaskSystemRepositoryBase<TEntity> : SimpleTaskSystemRepositoryBase<TEntity, int>
    where TEntity : class, IEntity<int>
{
    public SimpleTaskSystemRepositoryBase(IDbContextProvider<SimpleTaskSystemDbContext> dbContextProvider)
        : base(dbContextProvider)
    {
    }

    //不要在這裏添加任何方法,添加到上面的類(由於這個類繼承它)
}

請注意,咱們繼承自EfRepositoryBase <SimpleTaskSystemDbContext,TEntity,TPrimaryKey>。 這聲明ASP.NET Boilerplate在咱們的存儲庫中使用SimpleTaskSystemDbContext。

默認狀況下,您使用EfRepositoryBase實現給定DbContext(此示例中爲SimpleTaskSystemDbContext)的全部存儲庫。 您能夠將AutoRepositoryTypes屬性添加到DbContext中,將其替換爲您本身的存儲庫庫存儲庫類,以下所示:

[AutoRepositoryTypes(
    typeof(IRepository<>),
    typeof(IRepository<,>),
    typeof(SimpleTaskSystemEfRepositoryBase<>),
    typeof(SimpleTaskSystemEfRepositoryBase<,>)
)]
public class SimpleTaskSystemDbContext : AbpDbContext
{
    ...
}

③自定義存儲庫示例

要實現自定義存儲庫,只需從上面建立的應用程序特定的基礎存儲庫類派生。

public interface ITaskRepository : IRepository<Task, long>
{
    List<Task> GetAllWithPeople(int? assignedPersonId, TaskState? state);
}

public class TaskRepository : SimpleTaskSystemRepositoryBase<Task, long>, ITaskRepository
{
    public TaskRepository(IDbContextProvider<SimpleTaskSystemDbContext> dbContextProvider)
        : base(dbContextProvider)
    {
    }

    public List<Task> GetAllWithPeople(int? assignedPersonId, TaskState? state)
    {
        var query = GetAll();

        if (assignedPersonId.HasValue)
        {
            query = query.Where(task => task.AssignedPerson.Id == assignedPersonId.Value);
        }

        if (state.HasValue)
        {
            query = query.Where(task => task.State == state);
        }

        return query
            .OrderByDescending(task => task.CreationTime)
            .Include(task => task.AssignedPerson)
            .ToList();
    }
}

咱們首先定義了ITaskRepository,而後實現它。 GetAll()返回IQueryable <Task>,而後咱們可使用給定的參數添加一些Where過濾器。 最後咱們能夠調用ToList()來獲取任務列表。

您還可使用存儲庫方法中的Context對象來訪問DbContext並直接使用Entity Framework API。

④倉儲庫最佳實現

儘量使用默認存儲庫

始終爲您的應用程序建立自定義存儲庫的存儲庫基類,如上所述。

倉儲接口在領域層定義,倉儲實如今EntityFramework層

⑤事務管理

ASP.NET Boilerplate具備內置的工做系統單元來管理數據庫鏈接和事務。 實體框架有不一樣的事務管理方法。 ASP.NET Boilerplate默認使用環境TransactionScope方法,但也具備DbContext事務API的內置實現。 若是要切換到DbContext事務API,能夠在模塊的PreInitialize方法中進行配置:

Configuration.ReplaceService<IEfTransactionStrategy, DbContextEfTransactionStrategy>(DependencyLifeStyle.Transient);

記得添加「using Abp.Configuration.Startup;」 到你的代碼文件可使用ReplaceService泛型擴展方法。

此外,您的DbContext應具備本文檔DbContext部分中所述的構造函數。

 

四10、NHibernate集成

1,Nuget包

安裝Abp.NHibernate

2,配置

要開始使用NHibernate,您應該在模塊的PreInitialize中進行配置。

[DependsOn(typeof(AbpNHibernateModule))]
public class SimpleTaskSystemDataModule : AbpModule
{
    public override void PreInitialize()
    {
        var connStr = ConfigurationManager.ConnectionStrings["Default"].ConnectionString;

        Configuration.Modules.AbpNHibernate().FluentConfiguration
            .Database(MsSqlConfiguration.MsSql2008.ConnectionString(connStr))
            .Mappings(m => m.FluentMappings.AddFromAssembly(Assembly.GetExecutingAssembly()));
    }

    public override void Initialize()
    {
        IocManager.RegisterAssemblyByConvention(Assembly.GetExecutingAssembly());
    }
}

3,映射實體

在上面的示例配置中,咱們使用當前程序集中的全部映射類進行流暢映射。 示例映射類能夠以下所示:

public class TaskMap : EntityMap<Task>
{
    public TaskMap()
        : base("TeTasks")
    {
        References(x => x.AssignedUser).Column("AssignedUserId").LazyLoad();

        Map(x => x.Title).Not.Nullable();
        Map(x => x.Description).Nullable();
        Map(x => x.Priority).CustomType<TaskPriority>().Not.Nullable();
        Map(x => x.Privacy).CustomType<TaskPrivacy>().Not.Nullable();
        Map(x => x.State).CustomType<TaskState>().Not.Nullable();
    }
}

EntityMap是一個ASP.NET Boilerplate類,它擴展了ClassMap <T>,自動映射Id屬性並在構造函數中獲取表名。 因此,我從它導出,並使用FluentNHibernate映射其餘屬性。 固然,您能夠直接從ClassMap中導出,您可使用FluentNHibernate的完整API,您可使用NHibernate的其餘映射技術(如映射XML文件)。

4,倉儲

①默認實現

Abp.NHibernate包實現了應用程序中實體的默認存儲庫。 您沒必要爲實體建立存儲庫類,只需使用預約義的存儲庫方法。 例:

public class PersonAppService : IPersonAppService
{
    private readonly IRepository<Person> _personRepository;

    public PersonAppService(IRepository<Person> personRepository)
    {
        _personRepository = personRepository;
    }

    public void CreatePerson(CreatePersonInput input)
    {        
        person = new Person { Name = input.Name, EmailAddress = input.EmailAddress };

        _personRepository.Insert(person);
    }
}

②自定義倉儲

若是要添加一些自定義方法,應首先將其添加到存儲庫接口(做爲最佳實踐),而後在存儲庫類中實現。 ASP.NET Boilerplate提供了一個基類NhRepositoryBase來輕鬆實現存儲庫。 要實現IRepository接口,您只需從該類派生您的存儲庫。

public interface ITaskRepository : IRepository<Task, long>
{
    List<Task> GetAllWithPeople(int? assignedPersonId, TaskState? state);
}

public class TaskRepository : NhRepositoryBase<Task, long>, ITaskRepository
{
    public TaskRepository(ISessionProvider sessionProvider)
        : base(sessionProvider)
    {
    }

    public List<Task> GetAllWithPeople(int? assignedPersonId, TaskState? state)
    {
        var query = GetAll();

        if (assignedPersonId.HasValue)
        {
            query = query.Where(task => task.AssignedPerson.Id == assignedPersonId.Value);
        }

        if (state.HasValue)
        {
            query = query.Where(task => task.State == state);
        }

        return query
            .OrderByDescending(task => task.CreationTime)
            .Fetch(task => task.AssignedPerson)
            .ToList();
    }
}

GetAll()返回IQueryable <Task>,而後咱們可使用給定的參數添加一些Where過濾器。 最後咱們能夠調用ToList()來獲取任務列表。

您還可使用存儲庫方法中的Session對象來使用NHibernate的完整API。

③應用程序特定的基本存儲庫類

雖然您能夠從ASP.NET Boilerplate的NhRepositoryBase派生您的存儲庫,但更好的作法是建立本身的擴展NhRepositoryBase的基類。 所以,您能夠輕鬆地將共享/經常使用方法添加到您的存儲庫。 例:

//個人應用程序中全部存儲庫的基類
public abstract class MyRepositoryBase<TEntity, TPrimaryKey> : NhRepositoryBase<TEntity, TPrimaryKey>
    where TEntity : class, IEntity<TPrimaryKey>
{
    protected MyRepositoryBase(ISessionProvider sessionProvider)
        : base(sessionProvider)
    {
    }

    //爲全部存儲庫添加經常使用方法
}

//具備整數ID的實體的快捷方式
public abstract class MyRepositoryBase<TEntity> : MyRepositoryBase<TEntity, int>
    where TEntity : class, IEntity<int>
{
    protected MyRepositoryBase(ISessionProvider sessionProvider)
        : base(sessionProvider)
    {
    }

    //不要在這裏添加任何方法,添加上面的類(由於它繼承它)
}

public class TaskRepository : MyRepositoryBase<Task>, ITaskRepository
{
    public TaskRepository(ISessionProvider sessionProvider)
        : base(sessionProvider)
    {
    }

    //任務存儲庫的具體方法
}

 

四11、Dapper使用

1,安裝

nuget Abp.Dapper

2,模塊註冊

[DependsOn(
     typeof(AbpEntityFrameworkCoreModule),
     typeof(AbpDapperModule)
)]
public class MyModule : AbpModule
{
    public override void Initialize()
    {
        IocManager.RegisterAssemblyByConvention(typeof(SampleApplicationModule).GetAssembly());
    }
}

請注意,AbpDapperModule依賴關係應該晚於EF Core依賴項。

3,實體到表映射

您能夠配置映射。 例如,Person類映射到如下示例中的Persons表:

public class PersonMapper : ClassMapper<Person>
{
    public PersonMapper()
    {
        Table("Persons");
        Map(x => x.Roles).Ignore();
        AutoMap();
    }
}

您應該設置包含映射器類的程序集。 例:

[DependsOn(
     typeof(AbpEntityFrameworkModule),
     typeof(AbpDapperModule)
)]
public class MyModule : AbpModule
{
    public override void Initialize()
    {
        IocManager.RegisterAssemblyByConvention(typeof(SampleApplicationModule).GetAssembly());
        DapperExtensions.SetMappingAssemblies(new List<Assembly> { typeof(MyModule).GetAssembly() });
    }
}

4,用法

註冊AbpDapperModule後,您可使用Generic IDapperRepository接口(而不是標準IRepository)來注入dapper存儲庫。

public class SomeApplicationService : ITransientDependency
{
    private readonly IDapperRepository<Person> _personDapperRepository;
    private readonly IRepository<Person> _personRepository;

    public SomeApplicationService(
        IRepository<Person> personRepository,
        IDapperRepository<Person> personDapperRepository)
    {
        _personRepository = personRepository;
        _personDapperRepository = personDapperRepository;
    }

    public void DoSomeStuff()
    {
        var people = _personDapperRepository.Query("select * from Persons");
    }
}
相關文章
相關標籤/搜索