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; } |
|||
|
|||
@ -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 |
|||
{ |
|||
[DependsOn(typeof(AbpCachingModule))] |
|||
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 |
|||
@model Volo.Abp.Account.Web.Pages.Account.LoginModel |
|||
@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 |
|||
<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"> |
|||
<abp-input asp-for="Input.UserNameOrEmailAddress" auto-focus="true" /> |
|||
<abp-input asp-for="Input.Password" /> |
|||
<abp-input asp-for="Input.RememberMe" class="mb-3" /> |
|||
<abp-button button-type="Primary" type="submit">@L["Login"]</abp-button> |
|||
@if (string.Equals(await SettingManager.GetOrNullAsync(AccountSettingNames.IsSelfRegistrationEnabled), "true", StringComparison.OrdinalIgnoreCase)) |
|||
{ |
|||
<a abp-button="Secondary" href="@Url.Page("./Register", new {returnUrl = Model.ReturnUrl, returnUrlHash = Model.ReturnUrlHash})">@L["Register"]</a> |
|||
} |
|||
</form> |
|||
<div style="padding-top: 20px"> |
|||
@if (string.Equals(await SettingManager.GetOrNullAsync(AccountSettingNames.IsSelfRegistrationEnabled), "true", StringComparison.OrdinalIgnoreCase)) |
|||
{ |
|||
<a href="@Url.Page("./Register", new {returnUrl = Model.ReturnUrl, returnUrlHash = Model.ReturnUrlHash})">Register</a> |
|||
} |
|||
</div> |
|||
} |
|||
|
|||
@if (Model.ExternalLogins.Any()) |
|||
@if (Model.VisibleExternalProviders.Any()) |
|||
{ |
|||
<h4>Use another service to log in.</h4> |
|||
<form asp-page="./Login" asp-page-handler="ExternalLogin" asp-route-returnUrl="@Model.ReturnUrl" asp-route-returnUrlHash="@Model.ReturnUrlHash" method="post"> |
|||
<div> |
|||
@foreach (var provider in Model.ExternalLogins) |
|||
<div class="col-md-6"> |
|||
<h4>Use another service to log in.</h4> |
|||
<form asp-page="./Login" asp-page-handler="ExternalLogin" asp-route-returnUrl="@Model.ReturnUrl" asp-route-returnUrlHash="@Model.ReturnUrlHash" method="post"> |
|||
<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