活動雲項目
在本文中,我們將展示本項目的關鍵部分並且給予注釋信息和說明。建議從網站模板中輸入“EventCloud”,下載並且使用Vistual Studio 2013+的版本打開。
我將遵循一些DDD(領域驅動設計)的技術來進行創建領域層和應用層。
Event Cloud是一個免費的SaaS(多租戶)應用程序。我們可以創建一個擁有自己的活動,用戶,角色,租戶,版本,創建、取消和參與活動的一些簡單的業務規則。
現在我們開始寫代碼吧。
# 實體[Entities]
實體文件信息包含在領域層,位於EventCloud.Core項目中。ASP.NET Boilerplate啟動模板自帶的Tenant,User,Role ...實體是zero模塊中封裝好了的常用實體。我們可以根據我們的需要定制它們。當然,我們可以給自己的程序添加特定的實體信息。
## 第一個實體:Event
[Table("AppEvents")]
public class Event : FullAuditedEntity<Guid>, IMustHaveTenant
{
public const int MaxTitleLength = 128;
public const int MaxDescriptionLength = 2048;
public virtual int TenantId { get; set; }
[Required]
[StringLength(MaxTitleLength)]
public virtual string Title { get; protected set; }
[StringLength(MaxDescriptionLength)]
public virtual string Description { get; protected set; }
public virtual DateTime Date { get; protected set; }
public virtual bool IsCancelled { get; protected set; }
/// <summary>
/// Gets or sets the maximum registration count.
/// 0: Unlimited.
/// </summary>
[Range(0, int.MaxValue)]
public virtual int MaxRegistrationCount { get; protected set; }
[ForeignKey("EventId")]
public virtual ICollection<EventRegistration> Registrations { get; protected set; }
/// <summary>
/// We don't make constructor public and forcing to create events using <see cref="Create"/> method.
/// But constructor can not be private since it's used by EntityFramework.
/// Thats why we did it protected.
/// </summary>
protected Event()
{
}
public static Event Create(int tenantId, string title, DateTime date, string description = null, int maxRegistrationCount = 0)
{
var @event = new Event
{
Id = Guid.NewGuid(),
TenantId = tenantId,
Title = title,
Description = description,
MaxRegistrationCount = maxRegistrationCount
};
@event.SetDate(date);
@event.Registrations = new Collection<EventRegistration>();
return @event;
}
public bool IsInPast()
{
return Date < Clock.Now;
}
public bool IsAllowedCancellationTimeEnded()
{
return Date.Subtract(Clock.Now).TotalHours <= 2.0; //2 hours can be defined as Event property and determined per event
}
public void ChangeDate(DateTime date)
{
if (date == Date)
{
return;
}
SetDate(date);
DomainEvents.EventBus.Trigger(new EventDateChangedEvent(this));
}
internal void Cancel()
{
AssertNotInPast();
IsCancelled = true;
}
private void SetDate(DateTime date)
{
AssertNotCancelled();
if (date < Clock.Now)
{
throw new UserFriendlyException("Can not set an event's date in the past!");
}
if (date <= Clock.Now.AddHours(3)) //3 can be configurable per tenant
{
throw new UserFriendlyException("Should set an event's date 3 hours before at least!");
}
Date = date;
DomainEvents.EventBus.Trigger(new EventDateChangedEvent(this));
}
private void AssertNotInPast()
{
if (IsInPast())
{
throw new UserFriendlyException("This event was in the past");
}
}
private void AssertNotCancelled()
{
if (IsCancelled)
{
throw new UserFriendlyException("This event is canceled!");
}
}
}
- Event實體具有set/get屬性,它沒有public(公共set屬性) ,他的set屬性是被保護起來了(protected)。它還有一些領域邏輯。所有屬性都必須滿足它自身的領域邏輯之后才能正常的執行。
- Event實體的構造函數也是Protected。所以創建活動的唯一方法就是Event.Create方法(我們這里不把他設置為private 私有方法。因為私有方法不能很好地與EF框架一起使用,因為從數據庫查詢實體時,Entity Framework不能設置私有)。
- Event 需要實現 IMustHaveTenant接口。這個是ABP框架的接口,它可以確保這個實體是每個租戶都可以使用。這個是多租戶需要的。因此,不同的租戶將具有不同 的事件,並且不會看到彼此的活動信息。ABP自動過濾當前租戶的實體信息。
- Event實體繼承FullAuditedEntity,它包含創建,修改,刪除審計字段。FullAuditedEntity也實現了ISoftDelete,所以事件不能從數據庫中刪除。當您刪除它們的時候,它們會被標記為已刪除。當您查詢數據庫的時候,ABP會自動過濾(隱藏)已刪除的實體信息。
- 在DDD中,實體擁有領域(業務)邏輯。我們有一些簡單的業務規則,當你檢查實體時,可以很容易地理解。
第二個實體:EventRegistration
[Table("AppEventRegistrations")]
public class EventRegistration : CreationAuditedEntity, IMustHaveTenant
{
public int TenantId { get; set; }
[ForeignKey("EventId")]
public virtual Event Event { get; protected set; }
public virtual Guid EventId { get; protected set; }
[ForeignKey("UserId")]
public virtual User User { get; protected set; }
public virtual long UserId { get; protected set; }
/// <summary>
/// We don't make constructor public and forcing to create registrations using <see cref="CreateAsync"/> method.
/// But constructor can not be private since it's used by EntityFramework.
/// Thats why we did it protected.
/// </summary>
protected EventRegistration()
{
}
public async static Task<EventRegistration> CreateAsync(Event @event, User user, IEventRegistrationPolicy registrationPolicy)
{
await registrationPolicy.CheckRegistrationAttemptAsync(@event, user);
return new EventRegistration
{
TenantId = @event.TenantId,
EventId = @event.Id,
Event = @event,
UserId = @user.Id,
User = user
};
}
public async Task CancelAsync(IRepository<EventRegistration> repository)
{
if (repository == null) { throw new ArgumentNullException("repository"); }
if (Event.IsInPast())
{
throw new UserFriendlyException("Can not cancel event which is in the past!");
}
if (Event.IsAllowedCancellationTimeEnded())
{
throw new UserFriendlyException("It's too late to cancel your registration!");
}
await repository.DeleteAsync(this);
}
}
與Event類似,我們有一個靜態create方法。創建新的EventRegistration的唯一方法是CreateAsync方法。它獲得一個event,user和參加的邏輯處理。它檢查該用戶是否可以使用registrationPolicy參與到活動中。CheckRegistrationAttemptAsync方法,我為了保證如果該用戶不能參與到該活動中,該方法就會彈出異常。通過這樣的業務設計,我們可以確保只有該方法可以來創建
如果給定用戶無法注冊到給定事件,此方法將拋出異常。通過這樣的設計,我們確保在創建注冊時應用所有業務規則。沒有使用注冊政策,沒有辦法創建注冊。
有關實體的更多信息,請參閱實體文檔。
業務邏輯:EventRegistrationPolicy
EventRegistrationPolicy 代碼:
public class EventRegistrationPolicy : EventCloudServiceBase, IEventRegistrationPolicy
{
private readonly IRepository<EventRegistration> _eventRegistrationRepository;
public EventRegistrationPolicy(IRepository<EventRegistration> eventRegistrationRepository)
{
_eventRegistrationRepository = eventRegistrationRepository;
}
public async Task CheckRegistrationAttemptAsync(Event @event, User user)
{
if (@event == null) { throw new ArgumentNullException("event"); }
if (user == null) { throw new ArgumentNullException("user"); }
CheckEventDate(@event);
await CheckEventRegistrationFrequencyAsync(user);
}
private static void CheckEventDate(Event @event)
{
if (@event.IsInPast())
{
throw new UserFriendlyException("Can not register event in the past!");
}
}
private async Task CheckEventRegistrationFrequencyAsync(User user)
{
var oneMonthAgo = Clock.Now.AddDays(-30);
var maxAllowedEventRegistrationCountInLast30DaysPerUser = await SettingManager.GetSettingValueAsync<int>(EventCloudSettingNames.MaxAllowedEventRegistrationCountInLast30DaysPerUser);
if (maxAllowedEventRegistrationCountInLast30DaysPerUser > 0)
{
var registrationCountInLast30Days = await _eventRegistrationRepository.CountAsync(r => r.UserId == user.Id && r.CreationTime >= oneMonthAgo);
if (registrationCountInLast30Days > maxAllowedEventRegistrationCountInLast30DaysPerUser)
{
throw new UserFriendlyException(string.Format("Can not register to more than {0}", maxAllowedEventRegistrationCountInLast30DaysPerUser));
}
}
}
}
- 用戶無法參與過期(結束)的活動
- 用戶30天內,參與活動有最大參與活動數量的限制。
領域服務:EventManager
EventManager 作為Event的業務領域邏輯。所有活動的(數據庫)操作都應該使用這個類來執行。
public class EventManager : IEventManager
{
public IEventBus EventBus { get; set; }
private readonly IEventRegistrationPolicy _registrationPolicy;
private readonly IRepository<EventRegistration> _eventRegistrationRepository;
private readonly IRepository<Event, Guid> _eventRepository;
public EventManager(
IEventRegistrationPolicy registrationPolicy,
IRepository<EventRegistration> eventRegistrationRepository,
IRepository<Event, Guid> eventRepository)
{
_registrationPolicy = registrationPolicy;
_eventRegistrationRepository = eventRegistrationRepository;
_eventRepository = eventRepository;
EventBus = NullEventBus.Instance;
}
public async Task<Event> GetAsync(Guid id)
{
var @event = await _eventRepository.FirstOrDefaultAsync(id);
if (@event == null)
{
throw new UserFriendlyException("Could not found the event, maybe it's deleted!");
}
return @event;
}
public async Task CreateAsync(Event @event)
{
await _eventRepository.InsertAsync(@event);
}
public void Cancel(Event @event)
{
@event.Cancel();
EventBus.Trigger(new EventCancelledEvent(@event));
}
public async Task<EventRegistration> RegisterAsync(Event @event, User user)
{
return await _eventRegistrationRepository.InsertAsync(
await EventRegistration.CreateAsync(@event, user, _registrationPolicy)
);
}
public async Task CancelRegistrationAsync(Event @event, User user)
{
var registration = await _eventRegistrationRepository.FirstOrDefaultAsync(r => r.EventId == @event.Id && r.UserId == user.Id);
if (registration == null)
{
//No need to cancel since there is no such a registration
return;
}
await registration.CancelAsync(_eventRegistrationRepository);
}
public async Task<IReadOnlyList<User>> GetRegisteredUsersAsync(Event @event)
{
return await _eventRegistrationRepository
.GetAll()
.Include(registration => registration.User)
.Where(registration => registration.EventId == @event.Id)
.Select(registration => registration.User)
.ToListAsync();
}
}
- 領域服務用於執行業務邏輯處理完畢之后的方法。
- 有關ABP的領域服務的詳細信息,可以參閱領域服務
領域活動(Domain Event)
我們可能需要一些特殊的業務處理情景來滿足我們的系統,這個時候就需要我們來定義一些特殊的事件。
- EventCancelledEvent:當活動被取消時觸發。它在EventManager.Cancel方法中觸發。
- EventDateChangedEvent:當活動的日期更改時觸發。它在Event.ChangeDate方法中觸發。
我們處理這些活動並會通知相關用戶(已經參與該活動的用戶)發生的變化。會通過ABP框架定義好的事件:**EntityCreatedEventDate
要處理一個事件,我們定義一個事件處理類,我們定義一個EventUserEmailer,用來處理需要給用戶發送電子郵件:
public class EventUserEmailer :
IEventHandler<EntityCreatedEventData<Event>>,
IEventHandler<EventDateChangedEvent>,
IEventHandler<EventCancelledEvent>,
ITransientDependency
{
public ILogger Logger { get; set; }
private readonly IEventManager _eventManager;
private readonly UserManager _userManager;
public EventUserEmailer(
UserManager userManager,
IEventManager eventManager)
{
_userManager = userManager;
_eventManager = eventManager;
Logger = NullLogger.Instance;
}
[UnitOfWork]
public virtual void HandleEvent(EntityCreatedEventData<Event> eventData)
{
//TODO: Send email to all tenant users as a notification
var users = _userManager
.Users
.Where(u => u.TenantId == eventData.Entity.TenantId)
.ToList();
foreach (var user in users)
{
var message = string.Format("Hey! There is a new event '{0}' on {1}! Want to register?",eventData.Entity.Title, eventData.Entity.Date);
Logger.Debug(string.Format("TODO: Send email to {0} -> {1}", user.EmailAddress, message));
}
}
public void HandleEvent(EventDateChangedEvent eventData)
{
//TODO: Send email to all registered users!
var registeredUsers = AsyncHelper.RunSync(() => _eventManager.GetRegisteredUsersAsync(eventData.Entity));
foreach (var user in registeredUsers)
{
var message = eventData.Entity.Title + " event's date is changed! New date is: " + eventData.Entity.Date;
Logger.Debug(string.Format("TODO: Send email to {0} -> {1}",user.EmailAddress, message));
}
}
public void HandleEvent(EventCancelledEvent eventData)
{
//TODO: Send email to all registered users!
var registeredUsers = AsyncHelper.RunSync(() => _eventManager.GetRegisteredUsersAsync(eventData.Entity));
foreach (var user in registeredUsers)
{
var message = eventData.Entity.Title + " event is canceled!";
Logger.Debug(string.Format("TODO: Send email to {0} -> {1}", user.EmailAddress, message));
}
}
}
We can handle same events in different classes or different events in same class (as in this sample). Here, we handle these events and send email to related users as a notification (not implemented emailing actually to make the sample application simpler). An event handler should implement IEventHandler
處理同一個類中的不同事件,或者不同事件中的相同類(在本例中)。在這里我們可以給這些活動有關的所有都發送郵件通知信息過去(不實現電子郵件功能,我們的這個例子會更加的簡單)。
我們可以處理不同的類中的同一事件或同一類 (如本示例) 中的不同事件。在這里,我們處理這些事件並發送電子郵件給相關用戶作為通知 (不實現電子郵件實際上為了使示例應用程序更簡單)。事件處理程序應實現 IEventHandler
有關領域事件的具體更多信息,請參考文檔:領域事件。
應用層服務
應用層服務通過是調用領域層的方法,來實現服務(通常是通過展現層表示出來)。EventAppService 是執行活動邏輯業務的方法。
[AbpAuthorize]
public class EventAppService : EventCloudAppServiceBase, IEventAppService
{
private readonly IEventManager _eventManager;
private readonly IRepository<Event, Guid> _eventRepository;
public EventAppService(
IEventManager eventManager,
IRepository<Event, Guid> eventRepository)
{
_eventManager = eventManager;
_eventRepository = eventRepository;
}
public async Task<ListResultOutput<EventListDto>> GetList(GetEventListInput input)
{
var events = await _eventRepository
.GetAll()
.Include(e => e.Registrations)
.WhereIf(!input.IncludeCanceledEvents, e => !e.IsCancelled)
.OrderByDescending(e => e.CreationTime)
.ToListAsync();
return new ListResultOutput<EventListDto>(events.MapTo<List<EventListDto>>());
}
public async Task<EventDetailOutput> GetDetail(EntityRequestInput<Guid> input)
{
var @event = await _eventRepository
.GetAll()
.Include(e => e.Registrations)
.Where(e => e.Id == input.Id)
.FirstOrDefaultAsync();
if (@event == null)
{
throw new UserFriendlyException("Could not found the event, maybe it's deleted.");
}
return @event.MapTo<EventDetailOutput>();
}
public async Task Create(CreateEventInput input)
{
var @event = Event.Create(AbpSession.GetTenantId(), input.Title, input.Date, input.Description, input.MaxRegistrationCount);
await _eventManager.CreateAsync(@event);
}
public async Task Cancel(EntityRequestInput<Guid> input)
{
var @event = await _eventManager.GetAsync(input.Id);
_eventManager.Cancel(@event);
}
public async Task<EventRegisterOutput> Register(EntityRequestInput<Guid> input)
{
var registration = await RegisterAndSaveAsync(
await _eventManager.GetAsync(input.Id),
await GetCurrentUserAsync()
);
return new EventRegisterOutput
{
RegistrationId = registration.Id
};
}
public async Task CancelRegistration(EntityRequestInput<Guid> input)
{
await _eventManager.CancelRegistrationAsync(
await _eventManager.GetAsync(input.Id),
await GetCurrentUserAsync()
);
}
private async Task<EventRegistration> RegisterAndSaveAsync(Event @event, User user)
{
var registration = await _eventManager.RegisterAsync(@event, user);
await CurrentUnitOfWork.SaveChangesAsync();
return registration;
}
}
應用層服務未實現領域業務邏輯本身,他只是調用實體和領域服務(EventManager)來執行,實現功能需求。
展現層
使用angular js與bootstrap作為前端頁面展示。
活動列表
當我們登錄系統后,看到的第一個頁面為活動列表頁面:

我們直接訪問EventAppService來獲取活動列表信息。在這里我們需要創建一個angular的控制器:
(function() {
var controllerId = 'app.views.events.index';
angular.module('app').controller(controllerId, [
'$scope', '$modal', 'abp.services.app.event',
function ($scope, $modal, eventService) {
var vm = this;
vm.events = [];
vm.filters = {
includeCanceledEvents: false
};
function loadEvents() {
eventService.getList(vm.filters).success(function (result) {
vm.events = result.items;
});
};
vm.openNewEventDialog = function() {
var modalInstance = $modal.open({
templateUrl: abp.appPath + 'App/Main/views/events/createDialog.cshtml',
controller: 'app.views.events.createDialog as vm',
size: 'md'
});
modalInstance.result.then(function () {
loadEvents();
});
};
$scope.$watch('vm.filters.includeCanceledEvents', function (newValue, oldValue) {
if (newValue != oldValue) {
loadEvents();
}
});
loadEvents();
}
]);
})();
我們注入EventAppService 服務,在angular 控制器中需要寫為:abp.services.app.event。 我們使用ABP的動態webapi方式,他會自動創建webapi服務於angularjs來進行調用。
因此我們在調用應用層方法的時候就會像調用普通的JavaScript 函數一樣,因此如果我們要調用C#中的EventAppService.GetList方法,我們在例子中的寫法為:eventService.getList 的js函數即可,然后他將返回
一個對象:promise(angular 中為 $q )
關於promise有興趣的可以訪問Promise介紹
我們也可以點擊“new event”按鈕打開一個新的對話框(模態框,觸發vm.openNewEventDialog 函數方法)。這里沒有深入講解關於怎么來操作angular 相關的前端代碼
,你可以在代碼自己查詢研究。
活動詳情列表
當我們點擊“Details”按鈕時,我們會跳轉到活動詳情頁面,比如"http://eventcloud.aspnetboilerplate.com/#/events/e9499e3e-35c0-492c-98ce-7e410461103f".
事件的主鍵為Guid.

在這里,我們可以看到活動的詳情信息以及參與的用戶。我們可以選參與或者退出該活動。此視圖控制器在"Detail.js"中進行定義:
(function () {
var controllerId = 'app.views.events.detail';
angular.module('app').controller(controllerId, [
'$scope', '$state','$stateParams', 'abp.services.app.event',
function ($scope, $state, $stateParams, eventService) {
var vm = this;
function loadEvent() {
eventService.getDetail({
id: $stateParams.id
}).success(function (result) {
vm.event = result;
});
}
vm.isRegistered = function () {
if (!vm.event) {
return false;
}
return _.find(vm.event.registrations, function(registration) {
return registration.userId == abp.session.userId;
});
};
vm.isEventCreator = function() {
return vm.event && vm.event.creatorUserId == abp.session.userId;
};
vm.getUserThumbnail = function(registration) {
return registration.userName.substr(0, 1).toLocaleUpperCase();
};
vm.register = function() {
eventService.register({
id: vm.event.id
}).success(function (result) {
abp.notify.success('Successfully registered to event. Your registration id: ' + result.registrationId + ".");
loadEvent();
});
};
vm.cancelRegistertration = function() {
eventService.cancelRegistration({
id: vm.event.id
}).success(function () {
abp.notify.info('Canceled your registration.');
loadEvent();
});
};
vm.cancelEvent = function() {
eventService.cancel({
id: vm.event.id
}).success(function () {
abp.notify.info('Canceled the event.');
vm.backToEventsPage();
});
};
vm.backToEventsPage = function() {
$state.go('events');
};
loadEvent();
}
]);
})();
這里只展示了event實體服務層的方法,以及操作。
主菜單
頂部菜單欄是由ABP框架動態創建的。我們可以在類”EventCloudNavigationProvider “中定義菜單欄:
public class EventCloudNavigationProvider : NavigationProvider
{
public override void SetNavigation(INavigationProviderContext context)
{
context.Manager.MainMenu
.AddItem(
new MenuItemDefinition(
AppPageNames.Events,
new LocalizableString("Events", EventCloudConsts.LocalizationSourceName),
url: "#/",
icon: "fa fa-calendar-check-o"
)
).AddItem(
new MenuItemDefinition(
AppPageNames.About,
new LocalizableString("About", EventCloudConsts.LocalizationSourceName),
url: "#/about",
icon: "fa fa-info"
)
);
}
}
我們在這里可以添加新的菜單欄。具體可以參考導航文檔來閱讀。
angular Route Angular的路由
菜單定義好了之后,只是展示在頁面上而已。angular有自己的路由系統。 本次例子是通過Angular ui-router .js來進行路由控制的。他定義在“app.js”中,如下代碼:
//Configuration for Angular UI routing.
app.config([
'$stateProvider', '$urlRouterProvider',
function($stateProvider, $urlRouterProvider) {
$urlRouterProvider.otherwise('/events');
$stateProvider
.state('events', {
url: '/events',
templateUrl: '/App/Main/views/events/index.cshtml',
menu: 'Events' //Matches to name of 'Events' menu in EventCloudNavigationProvider
})
.state('eventDetail', {
url: '/events/:id',
templateUrl: '/App/Main/views/events/detail.cshtml',
menu: 'Events' //Matches to name of 'Events' menu in EventCloudNavigationProvider
})
.state('about', {
url: '/about',
templateUrl: '/App/Main/views/about/about.cshtml',
menu: 'About' //Matches to name of 'About' menu in EventCloudNavigationProvider
});
}
]);
單元測試和集成測試
ABP框架提供了這樣的單元測試和集成測試服務工具,它使得測試更加的容易。
你可以在你的項目中測試所有的代碼。
這里僅僅對基本的測試進行說明。
我們創建EventAppService_Tests 類文件來進行EventAPPService的單元測試:
public class EventAppService_Tests : EventCloudTestBase
{
private readonly IEventAppService _eventAppService;
public EventAppService_Tests()
{
_eventAppService = Resolve<IEventAppService>();
}
[Fact]
public async Task Should_Create_Event()
{
//Arrange
var eventTitle = Guid.NewGuid().ToString();
//Act
await _eventAppService.Create(new CreateEventInput
{
Title = eventTitle,
Description = "A description",
Date = Clock.Now.AddDays(2)
});
//Assert
UsingDbContext(context =>
{
context.Events.FirstOrDefault(e => e.Title == eventTitle).ShouldNotBe(null);
});
}
[Fact]
public async Task Should_Not_Create_Events_In_The_Past()
{
//Arrange
var eventTitle = Guid.NewGuid().ToString();
//Act
await Assert.ThrowsAsync<UserFriendlyException>(async () =>
{
await _eventAppService.Create(new CreateEventInput
{
Title = eventTitle,
Description = "A description",
Date = Clock.Now.AddDays(-1)
});
});
}
private Event GetTestEvent()
{
return UsingDbContext(context => GetTestEvent(context));
}
private static Event GetTestEvent(EventCloudDbContext context)
{
return context.Events.Single(e => e.Title == TestDataBuilder.TestEventTitle);
}
}
在ABP框架中使用的是xUnit作為測試框架。
- 在第一個測試中,我們創建了一個活動並且檢查了數據庫,它是否存在。
- 在第二次測試中,我們要創建一個過去的活動,當然因為我們的業務上對他進行了限制,他不會創建成功,所以這里會拋出一個異常。
對於單元測試,我們需要測試很多東西,考慮ABP框架本身,以及驗證,工作單元等。
社交登錄
在ABP生成的模板解決方案中,默認是提供了:Facebook、Google+、Twitter。所以我們只需要在web.config中啟用它,並且輸入API的憑據即可。
<add key="ExternalAuth.Facebook.IsEnabled" value="false" />
<add key="ExternalAuth.Facebook.AppId" value="" />
<add key="ExternalAuth.Facebook.AppSecret" value="" />
<add key="ExternalAuth.Twitter.IsEnabled" value="false" />
<add key="ExternalAuth.Twitter.ConsumerKey" value="" />
<add key="ExternalAuth.Twitter.ConsumerSecret" value="" />
<add key="ExternalAuth.Google.IsEnabled" value="false" />
<add key="ExternalAuth.Google.ClientId" value="" />
<add key="ExternalAuth.Google.ClientSecret" value="" />
具體怎么用請自己百度、bing、google來獲取這些憑據信息。
基於令牌(Token)的身份驗證
ABP的模板是基於cookie做的身份驗證。但是,如果你想通過移動端應用 來進行WEBAPI訪問的話,你就需要基於token的身份驗證機制了。
ABP框架自身包含token的身份認證的基礎服務。在Webapi類庫中的AccountController類中,就包含了身份驗證的方法,然后返回token值的方法(服務)。
然后就可以使用該token進行下一個請求。
在這里我們使用postman 進行演示,他是chrome瀏覽器的一個插件,用於演示請求和響應。
只是向 http://localhost:6234/api/Account/Authenticate 發送請求,請求類型為json(Context-Type="application/json")
下圖所示:

{
"tenancyName":"default",
"userNameOrEmailAddress":"admin",
"password":"123qwe"
}
我們發送了一個Json請求,正文為:
其中包括了tenancyName(租戶名稱)、userNameOrEmailAddress(用戶名)、password(密碼)。
相應並且返回的result就是令牌。我們可以將其保存,在下一次的請求中使用。
使用 API
我們在上面的身份授權中,得到了令牌,那么我們就可以用它來做該賬戶權限范圍內的任何事情。所有的應用層的服務都是可以通過遠程來調用的。
例如,我們可以使用“userservice”來獲取用戶列表。

圖上的為一個POST請求,訪問路徑:http://localhost:6234/api/services/app/user/GetUsers請求類型依舊為json,內容則為Authorization="Bearer 剛剛得到的令牌內容"。請求的正文為{}。
當然請求不同的API返回的響應正文也會不同嘛。
幾乎所有的UI層都可以使用webapi來訪問,畢竟UI使用相同的webapi嘛。(and can be consumed easily.)
原文鏈接:
https://www.codeproject.com/articles/1043326/a-multi-tenant-saas-application-with-asp-net-mvc-a
