您的位置:首页 > Web前端 > AngularJS

基于DDD的.NET开发框架ABP实例,多租户 (Saas)应用程序,采用.NET MVC, Angularjs, EntityFrame-EventCloud

2017-02-20 13:40 831 查看

活动云项目

在本文中,我们将展示本项目的关键部分并且给予注释信息和说明。建议从网站模板中输入“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 interface. ABP automatically calls the handler when related events occur.

处理同一个类中的不同事件,或者不同事件中的相同类(在本例中)。在这里我们可以给这些活动有关的所有都发送邮件通知信息过去(不实现电子邮件功能,我们的这个例子会更加的简单)。

我们可以处理不同的类中的同一事件或同一类 (如本示例) 中的不同事件。在这里,我们处理这些事件并发送电子邮件给相关用户作为通知 (不实现电子邮件实际上为了使示例应用程序更简单)。事件处理程序应实现 IEventHandler接口。ABP框架会自动处理调用实现了这些接口的方法。

有关领域事件的具体更多信息,请参考文档:领域事件

应用层服务

应用层服务通过是调用领域层的方法,来实现服务(通常是通过展现层表示出来)。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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐