基于DDD的.NET开发框架ABP实例,多租户 (Saas)应用程序,采用.NET MVC, Angularjs, EntityFrame-EventCloud
活动云项目
在本文中,我们将展示本项目的关键部分并且给予注释信息和说明。建议从网站模板中输入“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
为了方便和大家交流我建立了几个群,欢迎大家加群交流哦~
- 【52ABP .NET CORE 实战交流】:633751348
- 【ASP.NetCore Mvc EF入门学习】: 104390185
- 【ABP代码生成器交流群】104390185:
- 【ABP架构设计交流群4】726578484:
作者:梁桐铭(52ABP:基于DDD强大稳定的WEB应用框架! )
出处:http://www.cnblogs.com/wer-ltm
本作品由角落的白板报
创作,采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
欢迎转载,但任何转载必须保留完整文章,在显要地方显示署名以及原文链接。如您有任何疑问或者授权方面的协商,请
加群沟通留言。