mirror of https://github.com/abpframework/abp.git
1333 changed files with 307498 additions and 767 deletions
@ -0,0 +1,3 @@ |
|||||
|
# Event Bus |
||||
|
|
||||
|
TODO |
||||
@ -0,0 +1,30 @@ |
|||||
|
# Microservice Architecture |
||||
|
|
||||
|
*"Microservices are a software development technique—a variant of the **service-oriented architecture** (SOA) architectural style that structures an application as a collection of **loosely coupled services**. In a microservices architecture, services are **fine-grained** and the protocols are **lightweight**. The benefit of decomposing an application into different smaller services is that it improves **modularity**. This makes the application easier to understand, develop, test, and become more resilient to architecture erosion. It **parallelizes development** by enabling small autonomous teams to **develop, deploy and scale** their respective services independently. It also allows the architecture of an individual service to emerge through **continuous refactoring**. Microservices-based architectures enable **continuous delivery and deployment**."* |
||||
|
|
||||
|
— [Wikipedia](https://en.wikipedia.org/wiki/Microservices) |
||||
|
|
||||
|
## Introduction |
||||
|
|
||||
|
One of the major goals of the ABP framework is to provide a convenient infrastructure to create microservice solutions. To make this possible, |
||||
|
|
||||
|
* Provides a [module system](Module-Development-Basics.md) that allows you to split your application into modules where each module may have its own database, entities, services, APIs, UI components/pages... etc. |
||||
|
* Offers an [architectural model](Best-Practices/Module-Architecture.md) to develop your modules to be compatible to microservice development and deployment. |
||||
|
* Provides [best practices guide](Best-Practices/Index.md) to develop your module standards-compliance. |
||||
|
* Provides base infrastructure to implement [Domain Driven Design](Domain-Driven-Design.md) in your microservices. |
||||
|
* Provide services to [automatically create REST-style APIs](AspNetCore/Auto-API-Controllers.md) from your application services. |
||||
|
* Provide services to [automatically create C# API clients](AspNetCore/Dynamic-CSharp-API-Clients.md) that makes easy to consume your services from another service/application. |
||||
|
* Provides a [distributed event bus](Event-Bus.md) to communicate your services. |
||||
|
* Provides many other services to make your daily development easier. |
||||
|
|
||||
|
## Microservice for New Applications |
||||
|
|
||||
|
One common advise to start a new solution is **always to start with a monolith**, keep it modular and split into microservices once the monolith becomes a problem. This makes your progress fast in the beginning especially if your team is small and you don't want to deal with challanges of the microservice architecture. |
||||
|
|
||||
|
However, developing such a well-modular application can be a problem since it is **hard to keep modules isolated** from each other as you would do it for microservices (see [Stefan Tilkov's article](https://martinfowler.com/articles/dont-start-monolith.html) about that). Microservice architecture naturally forces you to develop well isolated services, but in a modular monolithic application it's easy to tight couple modules to each other and design **weak module boundaries** and API contracts. |
||||
|
|
||||
|
ABP can help you in that point by oferring a **microservice-compatible, strict module architecture** where your module is splitted into multiple layers/projects and developed in its own VS solution completely isolated and independent from other modules. Such a developed module is a natural microservice yet it can be easily plugged-in a monolithic application. See the [module development best practice guide](Best-Practices/Index.md) that offers a **microservice-first module design**. All [standard ABP modules](https://github.com/abpframework/abp/tree/master/modules) are developed based on this guide. So, you can use these modules by embedding into your monolithic solution or deploy them separately and use via remote APIs. They can share a single database or can have their own database based on your simple configuration. |
||||
|
|
||||
|
## Microservice Demo Solution |
||||
|
|
||||
|
The [sample microservice solution](Samples/Microservice-Demo.md) demonstrates a complete microservice solution based on the ABP framework. |
||||
@ -0,0 +1,40 @@ |
|||||
|
# Microservice Demo Solution |
||||
|
|
||||
|
*"Microservices are a software development technique—a variant of the **service-oriented architecture** (SOA) architectural style that structures an application as a collection of **loosely coupled services**. In a microservices architecture, services are **fine-grained** and the protocols are **lightweight**. The benefit of decomposing an application into different smaller services is that it improves **modularity**. This makes the application easier to understand, develop, test, and become more resilient to architecture erosion. It **parallelizes development** by enabling small autonomous teams to **develop, deploy and scale** their respective services independently. It also allows the architecture of an individual service to emerge through **continuous refactoring**. Microservices-based architectures enable **continuous delivery and deployment**."* |
||||
|
|
||||
|
— [Wikipedia](https://en.wikipedia.org/wiki/Microservices) |
||||
|
|
||||
|
## Introduction |
||||
|
|
||||
|
One of the major goals of the ABP framework is to provide a [convenient infrastructure to create microservice solutions](Microservice-Architecture.md). |
||||
|
|
||||
|
This sample aims to demonstrate a simple yet complete microservice solution; |
||||
|
|
||||
|
* Has multiple, independent, self-deployable **microservices**. |
||||
|
* Multiple **web applications**, each uses a different API gateway. |
||||
|
* Has multiple **gateways** / BFFs (Backend for Frontends) developed using the [Ocelot](https://github.com/ThreeMammals/Ocelot) library. |
||||
|
* Has an **authentication service** developed using the [IdentityServer](https://identityserver.io/) framework. It's also a SSO (Single Sign On) application with necessary UIs. |
||||
|
* Has **multiple databases**. Some microservices has their own database while some services/applications shares a database (to demonstrate different use cases). |
||||
|
* Has different types of databases: **SQL Server** (with **Entity Framework Core** ORM) and **MongoDB**. |
||||
|
* Has a **console application** to show the simplest way of using a service by authenticating. |
||||
|
* Uses [Redis](https://redis.io/) for **distributed caching**. |
||||
|
* Uses [RabbitMQ](https://www.rabbitmq.com/) for service-to-service **messaging**. |
||||
|
* Uses [Kubernates](https://kubernetes.io/) to **deploy** & run all services and applications. |
||||
|
|
||||
|
The diagram below shows the system: |
||||
|
|
||||
|
 |
||||
|
|
||||
|
### Source Code |
||||
|
|
||||
|
You can get the source code from [the GitHub repository](https://github.com/abpframework/abp/tree/master/samples/MicroserviceDemo). |
||||
|
|
||||
|
### Status |
||||
|
|
||||
|
This sample is still in development, not completed yet. |
||||
|
|
||||
|
## Microservices |
||||
|
|
||||
|
### Identity Service |
||||
|
|
||||
|
... |
||||
|
After Width: | Height: | Size: 69 KiB |
@ -0,0 +1,36 @@ |
|||||
|
using System; |
||||
|
using Microsoft.AspNetCore.Routing; |
||||
|
|
||||
|
namespace Microsoft.AspNetCore.Builder |
||||
|
{ |
||||
|
public static class AbpAspNetCoreMvcApplicationBuilderExtensions |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Adds MVC to the <see cref="T:Microsoft.AspNetCore.Builder.IApplicationBuilder" /> request execution pipeline
|
||||
|
/// with the following default routes:
|
||||
|
///
|
||||
|
/// - a default route named 'defaultWithArea' and the following template: '{area}/{controller=Home}/{action=Index}/{id?}'.
|
||||
|
/// - a default route named 'default' and the following template: '{controller=Home}/{action=Index}/{id?}'.
|
||||
|
/// </summary>
|
||||
|
/// <param name="app">The <see cref="T:Microsoft.AspNetCore.Builder.IApplicationBuilder" />.</param>
|
||||
|
/// <param name="additionalConfigurationAction">Additional action to configure routes</param>
|
||||
|
/// <returns>A reference to this instance after the operation has completed.</returns>
|
||||
|
public static IApplicationBuilder UseMvcWithDefaultRouteAndArea( |
||||
|
this IApplicationBuilder app, |
||||
|
Action<IRouteBuilder> additionalConfigurationAction = null) |
||||
|
{ |
||||
|
return app.UseMvc(routes => |
||||
|
{ |
||||
|
routes.MapRoute( |
||||
|
name: "defaultWithArea", |
||||
|
template: "{area}/{controller=Home}/{action=Index}/{id?}"); |
||||
|
|
||||
|
routes.MapRoute( |
||||
|
name: "default", |
||||
|
template: "{controller=Home}/{action=Index}/{id?}"); |
||||
|
|
||||
|
additionalConfigurationAction?.Invoke(routes); |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,26 @@ |
|||||
|
using System; |
||||
|
using System.Globalization; |
||||
|
|
||||
|
namespace Volo.Abp.AspNetCore.Mvc.Localization |
||||
|
{ |
||||
|
internal static class GlobalizationHelper |
||||
|
{ |
||||
|
public static bool IsValidCultureCode(string cultureCode) |
||||
|
{ |
||||
|
if (cultureCode.IsNullOrWhiteSpace()) |
||||
|
{ |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
try |
||||
|
{ |
||||
|
CultureInfo.GetCultureInfo(cultureCode); |
||||
|
return true; |
||||
|
} |
||||
|
catch (CultureNotFoundException) |
||||
|
{ |
||||
|
return false; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,24 @@ |
|||||
|
using Microsoft.Extensions.Caching.Distributed; |
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
|
||||
|
namespace Volo.Abp.Caching |
||||
|
{ |
||||
|
public class CacheOptions |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Global Cache entry options.
|
||||
|
/// </summary>
|
||||
|
public DistributedCacheEntryOptions GlobalCacheEntryOptions { get; set; } |
||||
|
/// <summary>
|
||||
|
/// List of all cache configurators.
|
||||
|
/// (func argument:Name of cache)
|
||||
|
/// </summary>
|
||||
|
public List<Func<string, DistributedCacheEntryOptions>> CacheConfigurators { get; set; } //TODO list item use a configurator interface instead?
|
||||
|
public CacheOptions() |
||||
|
{ |
||||
|
CacheConfigurators = new List<Func<string, DistributedCacheEntryOptions>>(); |
||||
|
GlobalCacheEntryOptions = new DistributedCacheEntryOptions(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,10 @@ |
|||||
|
namespace Volo.Abp.Caching |
||||
|
{ |
||||
|
public class DistributedCacheOptions |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Throw or hide exceptions for the distributed cache.
|
||||
|
/// </summary>
|
||||
|
public bool HideErrors { get; set; } = true; |
||||
|
} |
||||
|
} |
||||
@ -1,13 +0,0 @@ |
|||||
using Volo.Abp.Modularity; |
|
||||
using Volo.Abp.RabbitMQ; |
|
||||
|
|
||||
namespace Volo.Abp.EventBus.Distributed.RabbitMq |
|
||||
{ |
|
||||
[DependsOn( |
|
||||
typeof(AbpEventBusModule), |
|
||||
typeof(AbpRabbitMqModule))] |
|
||||
public class AbpEventBusRabbitMqModule : AbpModule |
|
||||
{ |
|
||||
|
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,19 @@ |
|||||
|
using Microsoft.Extensions.DependencyInjection; |
||||
|
using Volo.Abp.Modularity; |
||||
|
using Volo.Abp.RabbitMQ; |
||||
|
|
||||
|
namespace Volo.Abp.EventBus.RabbitMq |
||||
|
{ |
||||
|
[DependsOn( |
||||
|
typeof(AbpEventBusModule), |
||||
|
typeof(AbpRabbitMqModule))] |
||||
|
public class AbpEventBusRabbitMqModule : AbpModule |
||||
|
{ |
||||
|
public override void ConfigureServices(ServiceConfigurationContext context) |
||||
|
{ |
||||
|
var configuration = context.Services.GetConfiguration(); |
||||
|
|
||||
|
Configure<RabbitMqEventBusOptions>(configuration.GetSection("RabbitMQ:EventBus")); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1,6 +1,6 @@ |
|||||
namespace Volo.Abp.EventBus.Distributed.RabbitMq |
namespace Volo.Abp.EventBus.RabbitMq |
||||
{ |
{ |
||||
public class RabbitMqDistributedEventBusOptions |
public class RabbitMqEventBusOptions |
||||
{ |
{ |
||||
public string ConnectionName { get; set; } |
public string ConnectionName { get; set; } |
||||
|
|
||||
@ -0,0 +1,27 @@ |
|||||
|
using System.Net.Http; |
||||
|
using System.Threading.Tasks; |
||||
|
using JetBrains.Annotations; |
||||
|
|
||||
|
namespace Volo.Abp.IdentityModel |
||||
|
{ |
||||
|
public static class IdentityModelHttpClientAuthenticatorExtensions |
||||
|
{ |
||||
|
/// <param name="authenticator">Authenticator object</param>
|
||||
|
/// <param name="client"><see cref="HttpClient"/> object to be authenticated</param>
|
||||
|
/// <param name="identityClientName">The identity client name configured with the <see cref="IdentityClientOptions"/>.</param>
|
||||
|
public static Task AuthenticateAsync( |
||||
|
[NotNull] this IIdentityModelHttpClientAuthenticator authenticator, |
||||
|
[NotNull] HttpClient client, |
||||
|
string identityClientName = null) |
||||
|
{ |
||||
|
Check.NotNull(authenticator, nameof(authenticator)); |
||||
|
|
||||
|
return authenticator.AuthenticateAsync( |
||||
|
new IdentityModelHttpClientAuthenticateContext( |
||||
|
client, |
||||
|
identityClientName |
||||
|
) |
||||
|
); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1,10 +1,30 @@ |
|||||
using Volo.Abp.Modularity; |
using Microsoft.Extensions.Caching.Distributed; |
||||
|
using System; |
||||
|
using Volo.Abp.Modularity; |
||||
|
|
||||
namespace Volo.Abp.Caching |
namespace Volo.Abp.Caching |
||||
{ |
{ |
||||
[DependsOn(typeof(AbpCachingModule))] |
[DependsOn(typeof(AbpCachingModule))] |
||||
public class AbpCachingTestModule : AbpModule |
public class AbpCachingTestModule : AbpModule |
||||
{ |
{ |
||||
|
public override void ConfigureServices(ServiceConfigurationContext context) |
||||
|
{ |
||||
|
Configure<CacheOptions>(option => |
||||
|
{ |
||||
|
option.CacheConfigurators.Add(cacheName => |
||||
|
{ |
||||
|
if (cacheName == typeof(Sail.Testing.Caching.PersonCacheItem).FullName) |
||||
|
{ |
||||
|
return new DistributedCacheEntryOptions() |
||||
|
{ |
||||
|
AbsoluteExpiration = DateTime.Parse("2099-01-01 12:00:00") |
||||
|
}; |
||||
|
} |
||||
|
return null; |
||||
|
}); |
||||
|
|
||||
|
option.GlobalCacheEntryOptions.SetSlidingExpiration(TimeSpan.FromMinutes(20)); |
||||
|
}); |
||||
|
} |
||||
} |
} |
||||
} |
} |
||||
@ -0,0 +1,51 @@ |
|||||
|
using Microsoft.Extensions.Caching.Distributed; |
||||
|
using Shouldly; |
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Reflection; |
||||
|
using System.Text; |
||||
|
using System.Threading.Tasks; |
||||
|
using Xunit; |
||||
|
|
||||
|
namespace Volo.Abp.Caching |
||||
|
{ |
||||
|
public class DistributedCache_ConfigureOptions_Test : AbpIntegratedTest<AbpCachingTestModule> |
||||
|
{ |
||||
|
[Fact] |
||||
|
public async Task Configure_CacheOptions() |
||||
|
{ |
||||
|
var personCache = GetRequiredService<IDistributedCache<Sail.Testing.Caching.PersonCacheItem>>(); |
||||
|
|
||||
|
var cacheKey = Guid.NewGuid().ToString(); |
||||
|
//Get (not exists yet)
|
||||
|
var cacheItem = await personCache.GetAsync(cacheKey); |
||||
|
|
||||
|
cacheItem.ShouldBeNull(); |
||||
|
|
||||
|
GetDefaultCachingOptions(personCache).SlidingExpiration.ShouldBeNull(); |
||||
|
|
||||
|
GetDefaultCachingOptions(personCache).AbsoluteExpiration.ShouldBe(new DateTime(2099, 1, 1, 12, 0, 0)); |
||||
|
|
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Default_CacheOptions_Should_Be_20_Mins() |
||||
|
{ |
||||
|
var personCache = GetRequiredService<IDistributedCache<PersonCacheItem>>(); |
||||
|
|
||||
|
var cacheKey = Guid.NewGuid().ToString(); |
||||
|
|
||||
|
//Get (not exists yet)
|
||||
|
var cacheItem = await personCache.GetAsync(cacheKey); |
||||
|
cacheItem.ShouldBeNull(); |
||||
|
|
||||
|
GetDefaultCachingOptions(personCache).SlidingExpiration.ShouldBe(TimeSpan.FromMinutes(20)); |
||||
|
|
||||
|
} |
||||
|
private static DistributedCacheEntryOptions GetDefaultCachingOptions(object instance) |
||||
|
{ |
||||
|
var defaultOptionsField = instance.GetType().GetTypeInfo().GetField("DefaultCacheOptions", BindingFlags.Instance | BindingFlags.NonPublic); |
||||
|
return (DistributedCacheEntryOptions)defaultOptionsField.GetValue(instance); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,14 @@ |
|||||
|
using Volo.Abp.IdentityServer; |
||||
|
using Volo.Abp.Modularity; |
||||
|
|
||||
|
namespace Volo.Abp.Account.Web |
||||
|
{ |
||||
|
[DependsOn( |
||||
|
typeof(AbpAccountWebModule), |
||||
|
typeof(AbpIdentityServerDomainModule) |
||||
|
)] |
||||
|
public class AbpAccountWebIdentityServerModule : AbpModule |
||||
|
{ |
||||
|
|
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,239 @@ |
|||||
|
using IdentityModel; |
||||
|
using IdentityServer4.Events; |
||||
|
using IdentityServer4.Models; |
||||
|
using IdentityServer4.Services; |
||||
|
using IdentityServer4.Stores; |
||||
|
using Microsoft.AspNetCore.Authentication; |
||||
|
using Microsoft.AspNetCore.Mvc; |
||||
|
using Microsoft.Extensions.Options; |
||||
|
using System; |
||||
|
using System.Diagnostics; |
||||
|
using System.Linq; |
||||
|
using System.Security.Claims; |
||||
|
using System.Security.Principal; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
using Volo.Abp.MultiTenancy; |
||||
|
using Volo.Abp.Uow; |
||||
|
|
||||
|
namespace Volo.Abp.Account.Web.Pages.Account |
||||
|
{ |
||||
|
[ExposeServices(typeof(LoginModel))] |
||||
|
public class IdentityServerSupportedLoginModel : LoginModel |
||||
|
{ |
||||
|
protected IIdentityServerInteractionService Interaction { get; } |
||||
|
protected IClientStore ClientStore { get; } |
||||
|
protected IEventService IdentityServerEvents { get; } |
||||
|
|
||||
|
public IdentityServerSupportedLoginModel( |
||||
|
IAuthenticationSchemeProvider schemeProvider, |
||||
|
IOptions<AbpAccountOptions> accountOptions, |
||||
|
IIdentityServerInteractionService interaction, |
||||
|
IClientStore clientStore, |
||||
|
IEventService identityServerEvents) |
||||
|
:base( |
||||
|
schemeProvider, |
||||
|
accountOptions) |
||||
|
{ |
||||
|
_schemeProvider = schemeProvider; |
||||
|
Interaction = interaction; |
||||
|
ClientStore = clientStore; |
||||
|
IdentityServerEvents = identityServerEvents; |
||||
|
_accountOptions = accountOptions.Value; |
||||
|
} |
||||
|
|
||||
|
public override async Task OnGetAsync() |
||||
|
{ |
||||
|
LoginInput = new LoginInputModel(); |
||||
|
|
||||
|
var context = await Interaction.GetAuthorizationContextAsync(ReturnUrl); |
||||
|
|
||||
|
if (context != null) |
||||
|
{ |
||||
|
LoginInput.UserNameOrEmailAddress = context.LoginHint; |
||||
|
|
||||
|
//TODO: Reference AspNetCore MultiTenancy module and use options to get the tenant key!
|
||||
|
var tenant = context.Parameters[TenantResolverConsts.DefaultTenantKey]; |
||||
|
if (string.IsNullOrEmpty(tenant)) |
||||
|
{ |
||||
|
if (Request.Cookies.ContainsKey(TenantResolverConsts.DefaultTenantKey)) |
||||
|
{ |
||||
|
CurrentTenant.Change(null); |
||||
|
Response.Cookies.Delete(TenantResolverConsts.DefaultTenantKey); |
||||
|
} |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
CurrentTenant.Change(Guid.Parse(tenant)); |
||||
|
Response.Cookies.Append(TenantResolverConsts.DefaultTenantKey, tenant); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (context?.IdP != null) |
||||
|
{ |
||||
|
LoginInput.UserNameOrEmailAddress = context.LoginHint; |
||||
|
ExternalProviders = new[] { new ExternalProviderModel { AuthenticationScheme = context.IdP } }; |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
var schemes = await _schemeProvider.GetAllSchemesAsync(); |
||||
|
|
||||
|
var providers = schemes |
||||
|
.Where(x => x.DisplayName != null || x.Name.Equals(_accountOptions.WindowsAuthenticationSchemeName, StringComparison.OrdinalIgnoreCase)) |
||||
|
.Select(x => new ExternalProviderModel |
||||
|
{ |
||||
|
DisplayName = x.DisplayName, |
||||
|
AuthenticationScheme = x.Name |
||||
|
}) |
||||
|
.ToList(); |
||||
|
|
||||
|
EnableLocalLogin = true; //TODO: We can get default from a setting?
|
||||
|
if (context?.ClientId != null) |
||||
|
{ |
||||
|
var client = await ClientStore.FindEnabledClientByIdAsync(context.ClientId); |
||||
|
if (client != null) |
||||
|
{ |
||||
|
EnableLocalLogin = client.EnableLocalLogin; |
||||
|
|
||||
|
if (client.IdentityProviderRestrictions != null && client.IdentityProviderRestrictions.Any()) |
||||
|
{ |
||||
|
providers = providers.Where(provider => client.IdentityProviderRestrictions.Contains(provider.AuthenticationScheme)).ToList(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
ExternalProviders = providers.ToArray(); |
||||
|
|
||||
|
if (IsExternalLoginOnly) |
||||
|
{ |
||||
|
//return await ExternalLogin(vm.ExternalLoginScheme, returnUrl);
|
||||
|
throw new NotImplementedException(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
[UnitOfWork] //TODO: Will be removed when we implement action filter
|
||||
|
public override async Task<IActionResult> OnPostAsync(string action) |
||||
|
{ |
||||
|
EnableLocalLogin = true; //TODO: We can get default from a setting?
|
||||
|
|
||||
|
if (action == "Cancel") |
||||
|
{ |
||||
|
var context = await Interaction.GetAuthorizationContextAsync(ReturnUrl); |
||||
|
if (context == null) |
||||
|
{ |
||||
|
return Redirect("~/"); |
||||
|
} |
||||
|
|
||||
|
await Interaction.GrantConsentAsync(context, ConsentResponse.Denied); |
||||
|
|
||||
|
return Redirect(ReturnUrl); |
||||
|
} |
||||
|
|
||||
|
ValidateModel(); |
||||
|
|
||||
|
await ReplaceEmailToUsernameOfInputIfNeeds(); |
||||
|
|
||||
|
var result = await SignInManager.PasswordSignInAsync( |
||||
|
LoginInput.UserNameOrEmailAddress, |
||||
|
LoginInput.Password, |
||||
|
LoginInput.RememberMe, |
||||
|
true |
||||
|
); |
||||
|
|
||||
|
if (result.RequiresTwoFactor) |
||||
|
{ |
||||
|
return RedirectToPage("./SendSecurityCode", new |
||||
|
{ |
||||
|
returnUrl = ReturnUrl, |
||||
|
returnUrlHash = ReturnUrlHash, |
||||
|
rememberMe = LoginInput.RememberMe |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
if (result.IsLockedOut) |
||||
|
{ |
||||
|
Alerts.Warning(L["UserLockedOutMessage"]); |
||||
|
return Page(); |
||||
|
} |
||||
|
|
||||
|
if (result.RequiresTwoFactor) |
||||
|
{ |
||||
|
return RedirectToPage("./SendSecurityCode"); |
||||
|
} |
||||
|
|
||||
|
if (result.IsNotAllowed) |
||||
|
{ |
||||
|
Alerts.Warning(L["LoginIsNotAllowed"]); |
||||
|
return Page(); |
||||
|
} |
||||
|
|
||||
|
if (!result.Succeeded) |
||||
|
{ |
||||
|
Alerts.Danger(L["InvalidUserNameOrPassword"]); |
||||
|
return Page(); |
||||
|
} |
||||
|
|
||||
|
//TODO: Find a way of getting user's id from the logged in user and do not query it again like that!
|
||||
|
var user = await UserManager.FindByNameAsync(LoginInput.UserNameOrEmailAddress) ?? |
||||
|
await UserManager.FindByEmailAsync(LoginInput.UserNameOrEmailAddress); |
||||
|
|
||||
|
Debug.Assert(user != null, nameof(user) + " != null"); |
||||
|
await IdentityServerEvents.RaiseAsync(new UserLoginSuccessEvent(user.UserName, user.Id.ToString(), user.UserName)); //TODO: Use user's name once implemented
|
||||
|
|
||||
|
return RedirectSafely(ReturnUrl, ReturnUrlHash); |
||||
|
} |
||||
|
|
||||
|
[UnitOfWork] |
||||
|
public override async Task<IActionResult> OnPostExternalLogin(string provider) |
||||
|
{ |
||||
|
if (_accountOptions.WindowsAuthenticationSchemeName == provider) |
||||
|
{ |
||||
|
return await ProcessWindowsLoginAsync(); |
||||
|
} |
||||
|
|
||||
|
return await base.OnPostExternalLogin(provider); |
||||
|
} |
||||
|
|
||||
|
private async Task<IActionResult> ProcessWindowsLoginAsync() |
||||
|
{ |
||||
|
var result = await HttpContext.AuthenticateAsync(_accountOptions.WindowsAuthenticationSchemeName); |
||||
|
if (!(result?.Principal is WindowsPrincipal windowsPrincipal)) |
||||
|
{ |
||||
|
return Challenge(_accountOptions.WindowsAuthenticationSchemeName); |
||||
|
} |
||||
|
|
||||
|
var props = new AuthenticationProperties |
||||
|
{ |
||||
|
RedirectUri = Url.Page("./Login", pageHandler: "ExternalLoginCallback", values: new { ReturnUrl, ReturnUrlHash }), |
||||
|
Items = |
||||
|
{ |
||||
|
{"scheme", _accountOptions.WindowsAuthenticationSchemeName}, |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
var identity = new ClaimsIdentity(_accountOptions.WindowsAuthenticationSchemeName); |
||||
|
identity.AddClaim(new Claim(JwtClaimTypes.Subject, windowsPrincipal.Identity.Name)); |
||||
|
identity.AddClaim(new Claim(JwtClaimTypes.Name, windowsPrincipal.Identity.Name)); |
||||
|
|
||||
|
//TODO: Consider to add Windows groups the the identity
|
||||
|
//if (_accountOptions.IncludeWindowsGroups)
|
||||
|
//{
|
||||
|
// var windowsIdentity = windowsPrincipal.Identity as WindowsIdentity;
|
||||
|
// if (windowsIdentity != null)
|
||||
|
// {
|
||||
|
// var groups = windowsIdentity.Groups?.Translate(typeof(NTAccount));
|
||||
|
// var roles = groups.Select(x => new Claim(JwtClaimTypes.Role, x.Value));
|
||||
|
// identity.AddClaims(roles);
|
||||
|
// }
|
||||
|
//}
|
||||
|
|
||||
|
await HttpContext.SignInAsync( |
||||
|
IdentityServer4.IdentityServerConstants.ExternalCookieAuthenticationScheme, |
||||
|
new ClaimsPrincipal(identity), |
||||
|
props |
||||
|
); |
||||
|
|
||||
|
return RedirectSafely(props.RedirectUri); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,2 @@ |
|||||
|
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers |
||||
|
@addTagHelper *, Volo.Abp.AspNetCore.Mvc.UI.Bootstrap |
||||
@ -0,0 +1,27 @@ |
|||||
|
{ |
||||
|
"iisSettings": { |
||||
|
"windowsAuthentication": false, |
||||
|
"anonymousAuthentication": true, |
||||
|
"iisExpress": { |
||||
|
"applicationUrl": "http://localhost:49583/", |
||||
|
"sslPort": 0 |
||||
|
} |
||||
|
}, |
||||
|
"profiles": { |
||||
|
"IIS Express": { |
||||
|
"commandName": "IISExpress", |
||||
|
"launchBrowser": true, |
||||
|
"environmentVariables": { |
||||
|
"ASPNETCORE_ENVIRONMENT": "Development" |
||||
|
} |
||||
|
}, |
||||
|
"Volo.Abp.Account.Web.IdentityServer": { |
||||
|
"commandName": "Project", |
||||
|
"launchBrowser": true, |
||||
|
"environmentVariables": { |
||||
|
"ASPNETCORE_ENVIRONMENT": "Development" |
||||
|
}, |
||||
|
"applicationUrl": "http://localhost:49584/" |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,33 @@ |
|||||
|
<Project Sdk="Microsoft.NET.Sdk.Web"> |
||||
|
|
||||
|
<Import Project="..\..\..\..\common.props" /> |
||||
|
|
||||
|
<PropertyGroup> |
||||
|
<TargetFramework>netstandard2.0</TargetFramework> |
||||
|
<AssemblyName>Volo.Abp.Account.Web.IdentityServer</AssemblyName> |
||||
|
<PackageId>Volo.Abp.Account.Web.IdentityServer</PackageId> |
||||
|
<IsPackable>true</IsPackable> |
||||
|
<AssetTargetFallback>$(AssetTargetFallback);portable-net45+win8+wp8+wpa81;</AssetTargetFallback> |
||||
|
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute> |
||||
|
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute> |
||||
|
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute> |
||||
|
<RootNamespace>Volo.Abp.Account.Web</RootNamespace> |
||||
|
<OutputType>Library</OutputType> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<EmbeddedResource Include="Pages\**\*.*" Exclude="*.cs" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<Content Remove="Properties\launchSettings.json" /> |
||||
|
<EmbeddedResource Remove="Pages\Account\IdentityServerSupportedLoginModel.cs" /> |
||||
|
<None Include="Properties\launchSettings.json" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<ProjectReference Include="..\..\..\identityserver\src\Volo.Abp.IdentityServer.Domain\Volo.Abp.IdentityServer.Domain.csproj" /> |
||||
|
<ProjectReference Include="..\Volo.Abp.Account.Web\Volo.Abp.Account.Web.csproj" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
</Project> |
||||
@ -1,30 +1,59 @@ |
|||||
@page |
@page |
||||
@model Volo.Abp.Account.Web.Pages.Account.LoginModel |
|
||||
@using Volo.Abp.Account.Web.Settings |
@using Volo.Abp.Account.Web.Settings |
||||
@inherits Volo.Abp.Account.Web.Pages.Account.AccountPage |
@model Volo.Abp.Account.Web.Pages.Account.LoginModel |
||||
@inject Volo.Abp.Settings.ISettingManager SettingManager |
@inject Volo.Abp.Settings.ISettingManager SettingManager |
||||
<h2>@L["Login"]</h2> |
@if (Model.EnableLocalLogin) |
||||
|
{ |
||||
|
<form method="post"> |
||||
|
<input asp-for="ReturnUrl" /> |
||||
|
<input asp-for="ReturnUrlHash" /> |
||||
|
<div class="form-group"> |
||||
|
<label asp-for="LoginInput.UserNameOrEmailAddress"></label> |
||||
|
<input asp-for="LoginInput.UserNameOrEmailAddress" class="form-control" /> |
||||
|
<span asp-validation-for="LoginInput.UserNameOrEmailAddress" class="text-danger"></span> |
||||
|
</div> |
||||
|
<div class="form-group"> |
||||
|
<label asp-for="LoginInput.Password"></label> |
||||
|
<input asp-for="LoginInput.Password" class="form-control" /> |
||||
|
<span asp-validation-for="LoginInput.Password" class="text-danger"></span> |
||||
|
</div> |
||||
|
<div class="form-check"> |
||||
|
<label asp-for="LoginInput.RememberMe" class="form-check-label"> |
||||
|
<input asp-for="LoginInput.RememberMe" class="form-check-input" /> |
||||
|
@Html.DisplayNameFor(m => m.LoginInput.RememberMe) |
||||
|
</label> |
||||
|
</div> |
||||
|
<abp-button type="button" button-type="Secondary" name="Action" value="Cancel">Cancel</abp-button> @* TODO: Only show if identity server is used *@ |
||||
|
<abp-button type="submit" button-type="Primary" name="Action" value="Login">Login</abp-button> |
||||
|
</form> |
||||
|
|
||||
<form method="post"> |
<div style="padding-top: 20px"> |
||||
<abp-input asp-for="Input.UserNameOrEmailAddress" auto-focus="true" /> |
@if (string.Equals(await SettingManager.GetOrNullAsync(AccountSettingNames.IsSelfRegistrationEnabled), "true", StringComparison.OrdinalIgnoreCase)) |
||||
<abp-input asp-for="Input.Password" /> |
{ |
||||
<abp-input asp-for="Input.RememberMe" class="mb-3" /> |
<a href="@Url.Page("./Register", new {returnUrl = Model.ReturnUrl, returnUrlHash = Model.ReturnUrlHash})">Register</a> |
||||
<abp-button button-type="Primary" type="submit">@L["Login"]</abp-button> |
} |
||||
@if (string.Equals(await SettingManager.GetOrNullAsync(AccountSettingNames.IsSelfRegistrationEnabled), "true", StringComparison.OrdinalIgnoreCase)) |
</div> |
||||
{ |
} |
||||
<a abp-button="Secondary" href="@Url.Page("./Register", new {returnUrl = Model.ReturnUrl, returnUrlHash = Model.ReturnUrlHash})">@L["Register"]</a> |
|
||||
} |
|
||||
</form> |
|
||||
|
|
||||
@if (Model.ExternalLogins.Any()) |
@if (Model.VisibleExternalProviders.Any()) |
||||
{ |
{ |
||||
<h4>Use another service to log in.</h4> |
<div class="col-md-6"> |
||||
<form asp-page="./Login" asp-page-handler="ExternalLogin" asp-route-returnUrl="@Model.ReturnUrl" asp-route-returnUrlHash="@Model.ReturnUrlHash" method="post"> |
<h4>Use another service to log in.</h4> |
||||
<div> |
<form asp-page="./Login" asp-page-handler="ExternalLogin" asp-route-returnUrl="@Model.ReturnUrl" asp-route-returnUrlHash="@Model.ReturnUrlHash" method="post"> |
||||
@foreach (var provider in Model.ExternalLogins) |
<input asp-for="ReturnUrl" /> |
||||
|
<input asp-for="ReturnUrlHash" /> |
||||
|
@foreach (var provider in Model.VisibleExternalProviders) |
||||
{ |
{ |
||||
<abp-button button-type="Primary" type="submit" name="provider" value="@provider.Name">@provider.DisplayName</abp-button> |
<button type="submit" class="btn btn-primary" name="provider" value="@provider.AuthenticationScheme" title="Log in using your @provider.DisplayName account">@provider.DisplayName</button> |
||||
} |
} |
||||
</div> |
</form> |
||||
</form> |
</div> |
||||
} |
} |
||||
|
|
||||
|
@if (!Model.EnableLocalLogin && !Model.VisibleExternalProviders.Any()) |
||||
|
{ |
||||
|
<div class="alert alert-warning"> |
||||
|
<strong>Invalid login request</strong> |
||||
|
There are no login schemes configured for this client. |
||||
|
</div> |
||||
|
} |
||||
|
|||||
@ -0,0 +1,53 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Text; |
||||
|
using System.Threading.Tasks; |
||||
|
using Microsoft.AspNetCore.Mvc; |
||||
|
using Volo.Abp; |
||||
|
using Volo.Abp.AspNetCore.Mvc; |
||||
|
using Volo.Abp.Auditing; |
||||
|
using Volo.Blogging.Comments; |
||||
|
using Volo.Blogging.Comments.Dtos; |
||||
|
|
||||
|
namespace Volo.Blogging |
||||
|
{ |
||||
|
[RemoteService] |
||||
|
[Area("blogging")] |
||||
|
[Route("api/blogging/comments")] |
||||
|
public class CommentsController : AbpController, ICommentAppService |
||||
|
{ |
||||
|
private readonly ICommentAppService _commentAppService; |
||||
|
|
||||
|
public CommentsController(ICommentAppService commentAppService) |
||||
|
{ |
||||
|
_commentAppService = commentAppService; |
||||
|
} |
||||
|
|
||||
|
[HttpGet] |
||||
|
[Route("hierarchical/{postId}")] |
||||
|
public Task<List<CommentWithRepliesDto>> GetHierarchicalListOfPostAsync(Guid postId) |
||||
|
{ |
||||
|
return _commentAppService.GetHierarchicalListOfPostAsync(postId); |
||||
|
} |
||||
|
|
||||
|
[HttpPost] |
||||
|
public Task<CommentWithDetailsDto> CreateAsync(CreateCommentDto input) |
||||
|
{ |
||||
|
return _commentAppService.CreateAsync(input); |
||||
|
} |
||||
|
|
||||
|
[HttpPut] |
||||
|
[Route("{id}")] |
||||
|
public Task<CommentWithDetailsDto> UpdateAsync(Guid id, UpdateCommentDto input) |
||||
|
{ |
||||
|
return _commentAppService.UpdateAsync(id, input); |
||||
|
} |
||||
|
|
||||
|
[HttpDelete] |
||||
|
[Route("{id}")] |
||||
|
public Task DeleteAsync(Guid id) |
||||
|
{ |
||||
|
throw new NotImplementedException(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,65 @@ |
|||||
|
using System; |
||||
|
using System.Threading.Tasks; |
||||
|
using Microsoft.AspNetCore.Mvc; |
||||
|
using Volo.Abp; |
||||
|
using Volo.Abp.Application.Dtos; |
||||
|
using Volo.Abp.AspNetCore.Mvc; |
||||
|
using Volo.Abp.Auditing; |
||||
|
using Volo.Blogging.Posts; |
||||
|
|
||||
|
namespace Volo.Blogging |
||||
|
{ |
||||
|
[RemoteService] |
||||
|
[Area("blogging")] |
||||
|
[Route("api/blogging/posts")] |
||||
|
public class PostsController : AbpController, IPostAppService |
||||
|
{ |
||||
|
private readonly IPostAppService _postAppService; |
||||
|
|
||||
|
public PostsController(IPostAppService postAppService) |
||||
|
{ |
||||
|
_postAppService = postAppService; |
||||
|
} |
||||
|
|
||||
|
[HttpGet] |
||||
|
[Route("{blogId}/all")] |
||||
|
public Task<ListResultDto<PostWithDetailsDto>> GetListByBlogIdAndTagName(Guid blogId, string tagName) |
||||
|
{ |
||||
|
return _postAppService.GetListByBlogIdAndTagName(blogId, tagName); |
||||
|
} |
||||
|
|
||||
|
[HttpGet] |
||||
|
[Route("read/{id}")] |
||||
|
public Task<PostWithDetailsDto> GetForReadingAsync(GetPostInput input) |
||||
|
{ |
||||
|
return _postAppService.GetForReadingAsync(input); |
||||
|
} |
||||
|
|
||||
|
[HttpGet] |
||||
|
[Route("{id}")] |
||||
|
public Task<PostWithDetailsDto> GetAsync(Guid id) |
||||
|
{ |
||||
|
return _postAppService.GetAsync(id); |
||||
|
} |
||||
|
|
||||
|
[HttpPost] |
||||
|
public Task<PostWithDetailsDto> CreateAsync(CreatePostDto input) |
||||
|
{ |
||||
|
return _postAppService.CreateAsync(input); |
||||
|
} |
||||
|
|
||||
|
[HttpPut] |
||||
|
[Route("{id}")] |
||||
|
public Task<PostWithDetailsDto> UpdateAsync(Guid id, UpdatePostDto input) |
||||
|
{ |
||||
|
return _postAppService.UpdateAsync(id, input); |
||||
|
} |
||||
|
|
||||
|
[HttpDelete] |
||||
|
[Route("{id}")] |
||||
|
public Task DeleteAsync(Guid id) |
||||
|
{ |
||||
|
return _postAppService.DeleteAsync(id); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,32 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Threading.Tasks; |
||||
|
using Microsoft.AspNetCore.Mvc; |
||||
|
using Volo.Abp; |
||||
|
using Volo.Abp.AspNetCore.Mvc; |
||||
|
using Volo.Abp.Auditing; |
||||
|
using Volo.Blogging.Tagging; |
||||
|
using Volo.Blogging.Tagging.Dtos; |
||||
|
|
||||
|
namespace Volo.Blogging |
||||
|
{ |
||||
|
[RemoteService] |
||||
|
[Area("blogging")] |
||||
|
[Route("api/blogging/tags")] |
||||
|
public class TagsController : AbpController, ITagAppService |
||||
|
{ |
||||
|
private readonly ITagAppService _tagAppService; |
||||
|
|
||||
|
public TagsController(ITagAppService tagAppService) |
||||
|
{ |
||||
|
_tagAppService = tagAppService; |
||||
|
} |
||||
|
|
||||
|
[HttpGet] |
||||
|
[Route("popular/{blogId}")] |
||||
|
public Task<List<TagDto>> GetPopularTags(Guid blogId, GetPopularTagsInput input) |
||||
|
{ |
||||
|
return _tagAppService.GetPopularTags(blogId, input); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue