diff --git a/samples/MicroserviceDemo/.dockerignore b/.dockerignore similarity index 100% rename from samples/MicroserviceDemo/.dockerignore rename to .dockerignore diff --git a/abp_io/src/Volo.AbpWebSite.Web/AbpWebSiteWebModule.cs b/abp_io/src/Volo.AbpWebSite.Web/AbpWebSiteWebModule.cs index bbbce26cee..8031142779 100644 --- a/abp_io/src/Volo.AbpWebSite.Web/AbpWebSiteWebModule.cs +++ b/abp_io/src/Volo.AbpWebSite.Web/AbpWebSiteWebModule.cs @@ -29,6 +29,7 @@ using Volo.Abp.UI; using Volo.Abp.VirtualFileSystem; using Volo.AbpWebSite.Bundling; using Volo.Blogging; +using Volo.Blogging.Files; using Volo.Docs; namespace Volo.AbpWebSite @@ -55,24 +56,33 @@ namespace Volo.AbpWebSite var hostingEnvironment = context.Services.GetHostingEnvironment(); var configuration = context.Services.GetConfiguration(); - ConfigureLanguages(context.Services); - ConfigureDatabaseServices(context.Services, configuration); - ConfigureVirtualFileSystem(context.Services, hostingEnvironment); - ConfigureBundles(context.Services); - ConfigureTheme(context.Services); + ConfigureLanguages(); + ConfigureDatabaseServices(configuration); + ConfigureVirtualFileSystem(hostingEnvironment); + ConfigureBundles(); + ConfigureTheme(); + ConfigureBlogging(hostingEnvironment); } - private static void ConfigureLanguages(IServiceCollection services) + private void ConfigureBlogging(IHostingEnvironment hostingEnvironment) { - services.Configure(options => + Configure(options => + { + options.FileUploadLocalFolder = Path.Combine(hostingEnvironment.WebRootPath, "files"); + }); + } + + private void ConfigureLanguages() + { + Configure(options => { options.Languages.Add(new LanguageInfo("en-US", "en-US", "English")); }); } - private static void ConfigureBundles(IServiceCollection services) + private void ConfigureBundles() { - services.Configure(options => + Configure(options => { options .StyleBundles @@ -95,24 +105,24 @@ namespace Volo.AbpWebSite }); } - private static void ConfigureDatabaseServices(IServiceCollection services, IConfigurationRoot configuration) + private void ConfigureDatabaseServices(IConfigurationRoot configuration) { - services.Configure(options => + Configure(options => { options.ConnectionStrings.Default = configuration.GetConnectionString("Default"); }); - services.Configure(options => + Configure(options => { options.UseSqlServer(); }); } - private static void ConfigureVirtualFileSystem(IServiceCollection services, IHostingEnvironment hostingEnvironment) + private void ConfigureVirtualFileSystem(IHostingEnvironment hostingEnvironment) { if (hostingEnvironment.IsDevelopment()) { - services.Configure(options => + Configure(options => { options.FileSets.ReplaceEmbeddedByPhysical(Path.Combine(hostingEnvironment.ContentRootPath, string.Format("..{0}..{0}..{0}framework{0}src{0}Volo.Abp.UI", Path.DirectorySeparatorChar))); options.FileSets.ReplaceEmbeddedByPhysical(Path.Combine(hostingEnvironment.ContentRootPath, string.Format("..{0}..{0}..{0}framework{0}src{0}Volo.Abp.AspNetCore.Mvc.UI", Path.DirectorySeparatorChar))); @@ -126,9 +136,9 @@ namespace Volo.AbpWebSite } } - private void ConfigureTheme(IServiceCollection services) + private void ConfigureTheme() { - services.Configure(options => + Configure(options => { options.Themes.Add(); options.DefaultThemeName = AbpIoTheme.Name; @@ -140,6 +150,8 @@ namespace Volo.AbpWebSite var app = context.GetApplicationBuilder(); var env = context.GetEnvironment(); + app.UseCorrelationId(); + app.UseAbpRequestLocalization(); if (env.IsDevelopment()) diff --git a/abp_io/src/Volo.AbpWebSite.Web/CorrelationIdLogEventEnricher.cs b/abp_io/src/Volo.AbpWebSite.Web/CorrelationIdLogEventEnricher.cs new file mode 100644 index 0000000000..ee7b0bd248 --- /dev/null +++ b/abp_io/src/Volo.AbpWebSite.Web/CorrelationIdLogEventEnricher.cs @@ -0,0 +1,28 @@ +using Serilog.Core; +using Serilog.Events; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Tracing; + +namespace Volo.AbpWebSite +{ + //This is for trial for now + public class CorrelationIdLogEventEnricher : ILogEventEnricher, ITransientDependency + { + private readonly ICorrelationIdProvider _correlationIdProvider; + + public CorrelationIdLogEventEnricher(ICorrelationIdProvider correlationIdProvider) + { + _correlationIdProvider = correlationIdProvider; + } + + public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory) + { + logEvent.AddOrUpdateProperty( + new LogEventProperty( + "CorrelationId", + new ScalarValue("CorrId:" + _correlationIdProvider.Get()) + ) + ); + } + } +} \ No newline at end of file diff --git a/abp_io/src/Volo.AbpWebSite.Web/Program.cs b/abp_io/src/Volo.AbpWebSite.Web/Program.cs index 0ae7a19900..7b458c66de 100644 --- a/abp_io/src/Volo.AbpWebSite.Web/Program.cs +++ b/abp_io/src/Volo.AbpWebSite.Web/Program.cs @@ -1,13 +1,37 @@ -using System.IO; +using System; +using System.IO; using Microsoft.AspNetCore.Hosting; +using Serilog; +using Serilog.Events; namespace Volo.AbpWebSite { public class Program { - public static void Main(string[] args) + public static int Main(string[] args) { - BuildWebHostInternal(args).Run(); + Log.Logger = new LoggerConfiguration() + .MinimumLevel.Debug() //TODO: Should be configurable! + .MinimumLevel.Override("Microsoft", LogEventLevel.Information) + .Enrich.FromLogContext() + .WriteTo.File("Logs/logs.txt") + .CreateLogger(); + + try + { + Log.Information("Starting web host."); + BuildWebHostInternal(args).Run(); + return 0; + } + catch (Exception ex) + { + Log.Fatal(ex, "Host terminated unexpectedly!"); + return 1; + } + finally + { + Log.CloseAndFlush(); + } } internal static IWebHost BuildWebHostInternal(string[] args) => @@ -16,6 +40,7 @@ namespace Volo.AbpWebSite .UseContentRoot(Directory.GetCurrentDirectory()) .UseIISIntegration() .UseStartup() + .UseSerilog() .Build(); } } diff --git a/abp_io/src/Volo.AbpWebSite.Web/Startup.cs b/abp_io/src/Volo.AbpWebSite.Web/Startup.cs index efbfda982a..ce76e2aa8c 100644 --- a/abp_io/src/Volo.AbpWebSite.Web/Startup.cs +++ b/abp_io/src/Volo.AbpWebSite.Web/Startup.cs @@ -3,7 +3,6 @@ using System.Text; using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; -using Serilog; using Volo.Abp; namespace Volo.AbpWebSite @@ -23,15 +22,6 @@ namespace Volo.AbpWebSite public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory) { - loggerFactory - .AddConsole() - .AddDebug() - .AddSerilog(new LoggerConfiguration() - .Enrich.FromLogContext() - .WriteTo.File("Logs/logs.txt") - .CreateLogger() - ); - Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); app.InitializeApplication(); diff --git a/abp_io/src/Volo.AbpWebSite.Web/Volo.AbpWebSite.Web.csproj b/abp_io/src/Volo.AbpWebSite.Web/Volo.AbpWebSite.Web.csproj index 8fbbbfd711..b434646d99 100644 --- a/abp_io/src/Volo.AbpWebSite.Web/Volo.AbpWebSite.Web.csproj +++ b/abp_io/src/Volo.AbpWebSite.Web/Volo.AbpWebSite.Web.csproj @@ -9,13 +9,13 @@ true true false - true + c140514f-e488-4c99-8b9a-fabee0f53ce0 - + diff --git a/build/build.ps1 b/build/build.ps1 deleted file mode 100644 index c594ce2280..0000000000 --- a/build/build.ps1 +++ /dev/null @@ -1,50 +0,0 @@ -# COMMON PATHS - -$buildFolder = (Get-Item -Path "./" -Verbose).FullName -$slnFolder = Join-Path $buildFolder "../" -$outputFolder = Join-Path $buildFolder "outputs" -$abpDeskFolder = Join-Path $slnFolder "src/AbpDesk" -$abpDeskWebFolder = Join-Path $abpDeskFolder "AbpDesk.Web.Mvc" - -## CLEAR ###################################################################### - -Remove-Item $outputFolder -Force -Recurse -New-Item -Path $outputFolder -ItemType Directory - -## RESTORE NUGET PACKAGES ##################################################### - -Set-Location $slnFolder -dotnet restore - -## PUBLISH ASPDESK WEB ######################################################## - -Set-Location $abpDeskWebFolder -dotnet publish --output (Join-Path $outputFolder "AbpDesk/Web") - -New-Item -Path (Join-Path $outputFolder "AbpDesk/Web/PlugIns") -ItemType Directory -Copy-Item (Join-Path $abpDeskFolder "Web_PlugIns/*") (Join-Path $outputFolder "AbpDesk/Web/PlugIns/") - -## PUBLISH IDENTITY HTTP API HOST ############################################# - -Set-Location (Join-Path $slnFolder "src/Volo.Abp.Identity.HttpApi.Host") -dotnet publish --output (Join-Path $outputFolder "AbpIdentity/HttpApiHost") - -## CREATE DOCKER IMAGES ####################################################### - -Set-Location (Join-Path $outputFolder "AbpDesk/Web") - -docker rmi abpdesk/web -f -docker build -t abpdesk/web . - -Set-Location (Join-Path $outputFolder "AbpIdentity/HttpApiHost") - -docker rmi abpidentity/httpapihost -f -docker build -t abpidentity/httpapihost . - -## DOCKER COMPOSE FILES ####################################################### - -Copy-Item (Join-Path $slnFolder "docker/*.*") $outputFolder - -## FINALIZE ################################################################### - -Set-Location $outputFolder \ No newline at end of file diff --git a/common.props b/common.props index 040172c6d4..380276a7e1 100644 --- a/common.props +++ b/common.props @@ -1,7 +1,7 @@ latest - 0.13.0 + 0.14.0 $(NoWarn);CS1591 https://abp.io/assets/abp_nupkg.png https://abp.io diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml deleted file mode 100644 index 314feb5709..0000000000 --- a/docker/docker-compose.yml +++ /dev/null @@ -1,28 +0,0 @@ -version: '2' - -services: - - mongodb: - image: tutum/mongodb - environment: - - AUTH=no - ports: - - "27017:27017" - - "28017:28017" - - abpidentity_httpapihost: - image: abpidentity/httpapihost - environment: - - ASPNETCORE_ENVIRONMENT=Staging - - abpdesk_web: - image: abpdesk/web - environment: - - ASPNETCORE_ENVIRONMENT=Staging - - load_balancer: - image: haproxy:1.7.1 - volumes: - - "./haproxy.cfg:/usr/local/etc/haproxy/haproxy.cfg" - ports: - - "9005:8080" \ No newline at end of file diff --git a/docker/down.ps1 b/docker/down.ps1 deleted file mode 100644 index 508a7c4d74..0000000000 --- a/docker/down.ps1 +++ /dev/null @@ -1 +0,0 @@ -docker-compose down -v --rmi local \ No newline at end of file diff --git a/docker/haproxy.cfg b/docker/haproxy.cfg deleted file mode 100644 index 276ad980c8..0000000000 --- a/docker/haproxy.cfg +++ /dev/null @@ -1,18 +0,0 @@ -global - maxconn 4096 - -defaults - mode http - timeout connect 5s - timeout client 50s - timeout server 50s - -listen http-in - bind *:8080 - - server web-1 outputs_abpdesk_web_1:80 - server web-2 outputs_abpdesk_web_2:80 - - stats enable - stats uri /haproxy - stats refresh 1s \ No newline at end of file diff --git a/docker/up.ps1 b/docker/up.ps1 deleted file mode 100644 index 49ee1ee616..0000000000 --- a/docker/up.ps1 +++ /dev/null @@ -1,8 +0,0 @@ -docker rm $(docker ps -aq) -docker-compose up -d mongodb -docker-compose up -d abpidentity_httpapihost -docker-compose up -d abpdesk_web -sleep 2 -docker-compose scale abpdesk_web=2 -sleep 2 -docker-compose up -d load_balancer \ No newline at end of file diff --git a/docs/en/Blog-Posts/2019-02-22/Post.md b/docs/en/Blog-Posts/2019-02-22/Post.md new file mode 100644 index 0000000000..35f8219014 --- /dev/null +++ b/docs/en/Blog-Posts/2019-02-22/Post.md @@ -0,0 +1,54 @@ +# Microservice Demo, Projects Status and Road Map + +After [the first announcement](https://abp.io/blog/abp/Abp-vNext-Announcement) on the ABP vNext, we have a lot of improvements on the codebase (1100+ commits on the [GitHub repository](https://github.com/abpframework/abp)). We've created features, samples, documentation and much more. In this post, I want to inform you about some news and the status of the project. + +## Microservice Demo Solution + +One of the major goals of the ABP framework is to provide a [convenient infrastructure to create microservice solutions](https://abp.io/documents/abp/latest/Microservice-Architecture). + +We've been working to develop a microservice solution demo. Initial version was completed and [documented](https://abp.io/documents/abp/latest/Samples/Microservice-Demo). This sample solution 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 [Docker](https://www.docker.com/) & [Kubernates](https://kubernetes.io/) to **deploy** & run all services and applications. +- Uses [Elasticsearch](https://www.elastic.co/products/elasticsearch) & [Kibana](https://www.elastic.co/products/kibana) to store and visualize the logs (written using [Serilog](https://serilog.net/)). + +See [its documentation](https://abp.io/documents/abp/latest/Samples/Microservice-Demo) for a detailed explanation of the solution. + +## Improvements/Features + +We've worked on so many features including **distributed event bus** (with RabbitMQ integration), **IdentityServer4 integration** and enhancements for almost all features. We are continuously refactoring and adding tests to make the framework more stable and production ready. It is [rapidly growing](https://github.com/abpframework/abp/graphs/contributors). + +## Road Map + +There are still too much work to be done before the first stable release (v1.0). You can see [prioritized backlog items](https://github.com/abpframework/abp/issues?q=is%3Aopen+is%3Aissue+milestone%3ABacklog) on the GitHub repo. + +According to our estimation, we have planned to release v1.0 in Q2 of 2019 (probably in May or June). So, not too much time to wait. We are also very excited for the first stable release. + +We will also work on [the documentation](https://abp.io/documents/abp/latest) since it is far from complete now. + +First release may not include a SPA template. However, we want to prepare a simple one if it can be possible. Haven't decided yet about the SPA framework. Alternatives: **Angular, React and Blazor**. Please write your thought as a comment to this post. + +## Chinese Web Site + +There is a big ABP community in China. They have created a Chinese version of the abp.io web site: https://cn.abp.io/ They are keeping it up to date. Thanks to the Chinese developers and especially to [Liming Ma](https://github.com/maliming). + +## NDC {London} 2019 + +It was a pleasure to be in [NDC {London}](https://ndc-london.com/) 2019 as a partner. We've talked to many developers about the current ASP.NET Boilerplate and the ABP vNext and we got good feedbacks. + +We also had a chance to talk with [Scott Hanselman](https://twitter.com/shanselman) and [Jon Galloway](https://twitter.com/jongalloway). They visited our booth and we talked about the ideas for ABP vNext. They liked features, approaches and the goal of new ABP framework. See some photos and comments on twitter: + +![scott-and-jon](scott-and-jon.png) + +## Follow It + +* You can star and follow the **GitHub** repository: https://github.com/abpframework/abp +* You can follow the official **Twitter** account for news: https://twitter.com/abpframework \ No newline at end of file diff --git a/docs/en/Blog-Posts/2019-02-22/scott-and-jon.png b/docs/en/Blog-Posts/2019-02-22/scott-and-jon.png new file mode 100644 index 0000000000..79ad21aee7 Binary files /dev/null and b/docs/en/Blog-Posts/2019-02-22/scott-and-jon.png differ diff --git a/docs/en/Dependency-Injection.md b/docs/en/Dependency-Injection.md index d1ca14950c..5d8ddbf94e 100644 --- a/docs/en/Dependency-Injection.md +++ b/docs/en/Dependency-Injection.md @@ -170,7 +170,7 @@ public class BlogModule : AbpModule There are three common ways of using a service that has already been registered. -### Contructor Injection +### Constructor Injection This is the most common way of injecting a service into a class. For example: diff --git a/docs/en/Samples/Microservice-Demo.md b/docs/en/Samples/Microservice-Demo.md index fcedcd0f89..55a60dca89 100644 --- a/docs/en/Samples/Microservice-Demo.md +++ b/docs/en/Samples/Microservice-Demo.md @@ -24,7 +24,7 @@ This sample aims to demonstrate a simple yet complete microservice solution; The diagram below shows the system: -![microservice-sample-diagram](../images/microservice-sample-diagram.png) +![microservice-sample-diagram-2](../images/microservice-sample-diagram-2.png) ### Source Code @@ -32,7 +32,7 @@ You can get the source code from [the GitHub repository](https://github.com/abpf ### Status -This sample is still in development, not completed yet. +Initial version of this sample has been completed. Additional improvement are still in development. ## Running the Solution @@ -50,6 +50,20 @@ Running as docker containers is easier since all dependencies are pre-configured - Open a command line in the `samples/MicroserviceDemo` folder of the repository. +- Pull images from Docker Hub: + + ``` + docker-compose -f docker-compose.yml -f docker-compose.migrations.yml pull + ``` + +- If you want to build images locally you may skip the above step and instead use build command: + + ``` + docker-compose -f docker-compose.yml -f docker-compose.migrations.yml build + ``` + + Building images may take a **long time** depending on your machine. + - Restore SQL Server databases: ``` @@ -62,8 +76,6 @@ Running as docker containers is easier since all dependencies are pre-configured docker-compose up -d ``` - At the first run, it will take a **long time** because it will build all docker images. - - Add this line to the end of your `hosts` file: ``` diff --git a/docs/en/images/microservice-sample-diagram-2.png b/docs/en/images/microservice-sample-diagram-2.png new file mode 100644 index 0000000000..17aea07098 Binary files /dev/null and b/docs/en/images/microservice-sample-diagram-2.png differ diff --git a/framework/Volo.Abp.sln b/framework/Volo.Abp.sln index 08104ef769..1615d66e14 100644 --- a/framework/Volo.Abp.sln +++ b/framework/Volo.Abp.sln @@ -5,23 +5,8 @@ VisualStudioVersion = 15.0.27130.2036 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{CBCC288A-53C3-402F-99F7-E468738560F5}" -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{447C8A77-E5F0-4538-8687-7383196D04EA}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "docker-files", "docker-files", "{0BEA55D6-E0B8-40DD-A256-B34C4DD990A5}" - ProjectSection(SolutionItems) = preProject - ..\docker\docker-compose.yml = ..\docker\docker-compose.yml - ..\docker\down.ps1 = ..\docker\down.ps1 - ..\docker\haproxy.cfg = ..\docker\haproxy.cfg - ..\docker\up.ps1 = ..\docker\up.ps1 - EndProjectSection -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "build", "build", "{6D893E48-0739-401A-9AD1-286BCB6E5517}" - ProjectSection(SolutionItems) = preProject - ..\build\build.ps1 = ..\build\build.ps1 - EndProjectSection -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AbpTestBase", "test\AbpTestBase\AbpTestBase.csproj", "{1020F5FD-6A97-40C2-AFCA-EBDF641DF111}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.AspNetCore", "src\Volo.Abp.AspNetCore\Volo.Abp.AspNetCore.csproj", "{02BE03BA-3411-448C-AB61-CB36407CC49A}" @@ -222,9 +207,13 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.Http.Client.Identi EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.IdentityModel", "src\Volo.Abp.IdentityModel\Volo.Abp.IdentityModel.csproj", "{64D99E19-EE25-465A-82E5-17B25F4C4E18}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.AspNetCore.Mvc.Client", "src\Volo.Abp.AspNetCore.Mvc.Client\Volo.Abp.AspNetCore.Mvc.Client.csproj", "{E803DDB8-81EA-454B-9A66-9C2941100B67}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.AspNetCore.Mvc.Client", "src\Volo.Abp.AspNetCore.Mvc.Client\Volo.Abp.AspNetCore.Mvc.Client.csproj", "{E803DDB8-81EA-454B-9A66-9C2941100B67}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.AspNetCore.Mvc.Contracts", "src\Volo.Abp.AspNetCore.Mvc.Contracts\Volo.Abp.AspNetCore.Mvc.Contracts.csproj", "{88F6D091-CA16-4B71-9499-8D5B8FA2E712}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.Features", "src\Volo.Abp.Features\Volo.Abp.Features.csproj", "{01E3D389-8872-4EB1-9D3D-13B6ED54DE0E}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.AspNetCore.Mvc.Contracts", "src\Volo.Abp.AspNetCore.Mvc.Contracts\Volo.Abp.AspNetCore.Mvc.Contracts.csproj", "{88F6D091-CA16-4B71-9499-8D5B8FA2E712}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.Features.Tests", "test\Volo.Abp.Features.Tests\Volo.Abp.Features.Tests.csproj", "{575BEFA1-19C2-49B1-8D31-B5D4472328DE}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -640,13 +629,19 @@ Global {88F6D091-CA16-4B71-9499-8D5B8FA2E712}.Debug|Any CPU.Build.0 = Debug|Any CPU {88F6D091-CA16-4B71-9499-8D5B8FA2E712}.Release|Any CPU.ActiveCfg = Release|Any CPU {88F6D091-CA16-4B71-9499-8D5B8FA2E712}.Release|Any CPU.Build.0 = Release|Any CPU + {01E3D389-8872-4EB1-9D3D-13B6ED54DE0E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {01E3D389-8872-4EB1-9D3D-13B6ED54DE0E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {01E3D389-8872-4EB1-9D3D-13B6ED54DE0E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {01E3D389-8872-4EB1-9D3D-13B6ED54DE0E}.Release|Any CPU.Build.0 = Release|Any CPU + {575BEFA1-19C2-49B1-8D31-B5D4472328DE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {575BEFA1-19C2-49B1-8D31-B5D4472328DE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {575BEFA1-19C2-49B1-8D31-B5D4472328DE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {575BEFA1-19C2-49B1-8D31-B5D4472328DE}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(NestedProjects) = preSolution - {0BEA55D6-E0B8-40DD-A256-B34C4DD990A5} = {CBCC288A-53C3-402F-99F7-E468738560F5} - {6D893E48-0739-401A-9AD1-286BCB6E5517} = {CBCC288A-53C3-402F-99F7-E468738560F5} {1020F5FD-6A97-40C2-AFCA-EBDF641DF111} = {447C8A77-E5F0-4538-8687-7383196D04EA} {02BE03BA-3411-448C-AB61-CB36407CC49A} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6} {B1D860BB-6EC6-4BAE-ADAA-C2AEC2FFB510} = {447C8A77-E5F0-4538-8687-7383196D04EA} @@ -749,6 +744,8 @@ Global {64D99E19-EE25-465A-82E5-17B25F4C4E18} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6} {E803DDB8-81EA-454B-9A66-9C2941100B67} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6} {88F6D091-CA16-4B71-9499-8D5B8FA2E712} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6} + {01E3D389-8872-4EB1-9D3D-13B6ED54DE0E} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6} + {575BEFA1-19C2-49B1-8D31-B5D4472328DE} = {447C8A77-E5F0-4538-8687-7383196D04EA} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {BB97ECF4-9A84-433F-A80B-2A3285BDD1D5} diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.Client/Volo/Abp/AspNetCore/Mvc/Client/RemoteFeatureChecker.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.Client/Volo/Abp/AspNetCore/Mvc/Client/RemoteFeatureChecker.cs new file mode 100644 index 0000000000..0643620ec9 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.Client/Volo/Abp/AspNetCore/Mvc/Client/RemoteFeatureChecker.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; +using Volo.Abp.Features; + +namespace Volo.Abp.AspNetCore.Mvc.Client +{ + public class RemoteFeatureChecker : FeatureCheckerBase + { + protected ICachedApplicationConfigurationClient ConfigurationClient { get; } + + public RemoteFeatureChecker(ICachedApplicationConfigurationClient configurationClient) + { + ConfigurationClient = configurationClient; + } + + public override async Task GetOrNullAsync(string name) + { + var configuration = await ConfigurationClient.GetAsync(); + return configuration.Features.Values.GetOrDefault(name); + } + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.Client/Volo/Abp/AspNetCore/Mvc/Client/RemotePermissionChecker.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.Client/Volo/Abp/AspNetCore/Mvc/Client/RemotePermissionChecker.cs index 46e13bcc3d..cbddb7c3c7 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.Client/Volo/Abp/AspNetCore/Mvc/Client/RemotePermissionChecker.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.Client/Volo/Abp/AspNetCore/Mvc/Client/RemotePermissionChecker.cs @@ -14,19 +14,17 @@ namespace Volo.Abp.AspNetCore.Mvc.Client ConfigurationClient = configurationClient; } - public async Task CheckAsync(string name) + public async Task IsGrantedAsync(string name) { var configuration = await ConfigurationClient.GetAsync(); - return new PermissionGrantInfo( - name, - configuration.Auth.GrantedPolicies.ContainsKey(name) - ); + return configuration.Auth.GrantedPolicies.ContainsKey(name); } - public Task CheckAsync(ClaimsPrincipal claimsPrincipal, string name) + public Task IsGrantedAsync(ClaimsPrincipal claimsPrincipal, string name) { - return CheckAsync(name); + /* This provider always works for the current principal. */ + return IsGrantedAsync(name); } } } diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/ApplicationConfigurationDto.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/ApplicationConfigurationDto.cs index 6f41399c29..44e9076751 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/ApplicationConfigurationDto.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/ApplicationConfigurationDto.cs @@ -12,5 +12,7 @@ namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations public ApplicationSettingConfigurationDto Setting { get; set; } public CurrentUserDto CurrentUser { get; set; } + + public ApplicationFeatureConfigurationDto Features { get; set; } } } \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/ApplicationFeatureConfigurationDto.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/ApplicationFeatureConfigurationDto.cs new file mode 100644 index 0000000000..8917e3dcfa --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/ApplicationFeatureConfigurationDto.cs @@ -0,0 +1,9 @@ +using System.Collections.Generic; + +namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations +{ + public class ApplicationFeatureConfigurationDto + { + public Dictionary Values { get; set; } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpInputTagHelper.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpInputTagHelper.cs index 0c5ac5dd22..10ce263032 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpInputTagHelper.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpInputTagHelper.cs @@ -23,7 +23,7 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form public AbpFormControlSize Size { get; set; } = AbpFormControlSize.Default; - [HtmlAttributeNotBound] + [HtmlAttributeName("required-symbol")] public bool DisplayRequiredSymbol { get; set; } = true; [HtmlAttributeNotBound] diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpSelectTagHelper.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpSelectTagHelper.cs index f8cdd35a7e..ae46336f77 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpSelectTagHelper.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpSelectTagHelper.cs @@ -18,7 +18,7 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form [HtmlAttributeName("info")] public string InfoText { get; set; } - [HtmlAttributeNotBound] + [HtmlAttributeName("required-symbol")] public bool DisplayRequiredSymbol { get; set; } = true; [HtmlAttributeNotBound] diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.MultiTenancy/Volo/Abp/AspNetCore/Mvc/UI/MultiTenancy/Components/TenantSwitch/Default.cshtml b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.MultiTenancy/Volo/Abp/AspNetCore/Mvc/UI/MultiTenancy/Components/TenantSwitch/Default.cshtml index 33419549f0..515d4cc7dc 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.MultiTenancy/Volo/Abp/AspNetCore/Mvc/UI/MultiTenancy/Components/TenantSwitch/Default.cshtml +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.MultiTenancy/Volo/Abp/AspNetCore/Mvc/UI/MultiTenancy/Components/TenantSwitch/Default.cshtml @@ -1,14 +1,17 @@ @using Volo.Abp.AspNetCore.Mvc.UI.MultiTenancy.Components.TenantSwitch @model TenantSwitchViewComponent.TenantSwitchViewModel - \ No newline at end of file +@if (!Model.CurrentUser.IsAuthenticated) +{ + +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.MultiTenancy/Volo/Abp/AspNetCore/Mvc/UI/MultiTenancy/Components/TenantSwitch/TenantSwitchViewComponent.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.MultiTenancy/Volo/Abp/AspNetCore/Mvc/UI/MultiTenancy/Components/TenantSwitch/TenantSwitchViewComponent.cs index 739dc71b5b..bfcaebed0e 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.MultiTenancy/Volo/Abp/AspNetCore/Mvc/UI/MultiTenancy/Components/TenantSwitch/TenantSwitchViewComponent.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.MultiTenancy/Volo/Abp/AspNetCore/Mvc/UI/MultiTenancy/Components/TenantSwitch/TenantSwitchViewComponent.cs @@ -1,6 +1,7 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using Volo.Abp.MultiTenancy; +using Volo.Abp.Users; namespace Volo.Abp.AspNetCore.Mvc.UI.MultiTenancy.Components.TenantSwitch { @@ -12,18 +13,25 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.MultiTenancy.Components.TenantSwitch public const int Order = -1_000_000; protected ITenantStore TenantStore { get; } - protected ICurrentTenant CurrentTenant { get; } + protected ICurrentUser CurrentUser { get; } - public TenantSwitchViewComponent(ITenantStore tenantStore, ICurrentTenant currentTenant) + public TenantSwitchViewComponent( + ITenantStore tenantStore, + ICurrentTenant currentTenant, + ICurrentUser currentUser) { TenantStore = tenantStore; CurrentTenant = currentTenant; + CurrentUser = currentUser; } public async Task InvokeAsync() { - var model = new TenantSwitchViewModel(); + var model = new TenantSwitchViewModel + { + CurrentUser = CurrentUser + }; if (CurrentTenant.Id.HasValue) { @@ -36,6 +44,8 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.MultiTenancy.Components.TenantSwitch public class TenantSwitchViewModel { public TenantInfo Tenant { get; set; } + + public ICurrentUser CurrentUser { get; set; } } } } diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/FlagIconCss/FlagIconCssStyleContributor.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/FlagIconCss/FlagIconCssStyleContributor.cs new file mode 100644 index 0000000000..ed82ed7f62 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/FlagIconCss/FlagIconCssStyleContributor.cs @@ -0,0 +1,13 @@ +using System.Collections.Generic; +using Volo.Abp.AspNetCore.Mvc.UI.Bundling; + +namespace Volo.Abp.AspNetCore.Mvc.UI.Packages.FlagIconCss +{ + public class FlagIconCssStyleContributor : BundleContributor + { + public override void ConfigureBundle(BundleConfigurationContext context) + { + context.Files.AddIfNotContains("/libs/flag-icon-css/css/flag-icon.min.css"); + } + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/AbpAspNetCoreMvcUIBasicThemeModule.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/AbpAspNetCoreMvcUIBasicThemeModule.cs index 07f24165e3..08d04cfed2 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/AbpAspNetCoreMvcUIBasicThemeModule.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/AbpAspNetCoreMvcUIBasicThemeModule.cs @@ -52,7 +52,9 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic .ScriptBundles .Add(BasicThemeBundles.Scripts.Global, bundle => { - bundle.AddBaseBundles(StandardBundles.Scripts.Global); + bundle + .AddBaseBundles(StandardBundles.Scripts.Global) + .AddContributors(typeof(BasicThemeGlobalScriptContributor)); }); }); } diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Bundling/BasicThemeGlobalScriptContributor.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Bundling/BasicThemeGlobalScriptContributor.cs new file mode 100644 index 0000000000..76ce8b2c05 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Bundling/BasicThemeGlobalScriptContributor.cs @@ -0,0 +1,12 @@ +using Volo.Abp.AspNetCore.Mvc.UI.Bundling; + +namespace Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Bundling +{ + public class BasicThemeGlobalScriptContributor : BundleContributor + { + public override void ConfigureBundle(BundleConfigurationContext context) + { + context.Files.Add("/themes/basic/layout.js"); + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Components/Menu/Default.cshtml b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Components/Menu/Default.cshtml index 2e7e7f3f4c..7bfe8f1730 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Components/Menu/Default.cshtml +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Components/Menu/Default.cshtml @@ -5,39 +5,36 @@ var elementId = string.IsNullOrEmpty(menuItem.ElementId) ? string.Empty : $"id=\"{menuItem.ElementId}\""; var cssClass = string.IsNullOrEmpty(menuItem.CssClass) ? string.Empty : menuItem.CssClass; var disabled = menuItem.IsDisabled ? "disabled" : string.Empty; - if (menuItem.IsLeaf) { - if (menuItem.Url == null) + @if (menuItem.Url != null) { - continue; - } - - + @menuItem.DisplayName + + + } } else { - } -} +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Components/Menu/_MenuItem.cshtml b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Components/Menu/_MenuItem.cshtml new file mode 100644 index 0000000000..fb8feb2ad8 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Components/Menu/_MenuItem.cshtml @@ -0,0 +1,36 @@ +@using Volo.Abp.UI.Navigation +@model ApplicationMenuItem +@{ + var elementId = string.IsNullOrEmpty(Model.ElementId) ? string.Empty : $"id=\"{Model.ElementId}\""; + var cssClass = string.IsNullOrEmpty(Model.CssClass) ? string.Empty : Model.CssClass; + var disabled = Model.IsDisabled ? "disabled" : string.Empty; +} +@if (Model.IsLeaf) +{ + @if (Model.Url != null) + { + + @Model.DisplayName + + } +} +else +{ + +} diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/wwwroot/themes/basic/layout.css b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/wwwroot/themes/basic/layout.css index ffcc7a2dae..3b5cc467fc 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/wwwroot/themes/basic/layout.css +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/wwwroot/themes/basic/layout.css @@ -10,3 +10,27 @@ body { text-decoration: none; color: #fff; } + +/* Main Menu */ + +.navbar .dropdown-submenu { + position: relative; +} + + .navbar .dropdown-submenu a { + padding: 0.25rem 1.4rem; + } + + .navbar .dropdown-submenu a::after { + transform: rotate(-90deg); + position: absolute; + right: 16px; + top: 18px; + } + + .navbar .dropdown-submenu .dropdown-menu { + top: 0; + left: 100%; + margin-left: .1rem; + margin-right: .1rem; + } diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/wwwroot/themes/basic/layout.js b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/wwwroot/themes/basic/layout.js new file mode 100644 index 0000000000..8a5b94c7c6 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/wwwroot/themes/basic/layout.js @@ -0,0 +1,16 @@ +$(function () { + $('.dropdown-menu a.dropdown-toggle').on('click', function (e) { + if (!$(this).next().hasClass('show')) { + $(this).parents('.dropdown-menu').first().find('.show').removeClass("show"); + } + + var $subMenu = $(this).next(".dropdown-menu"); + $subMenu.toggleClass('show'); + + $(this).parents('li.nav-item.dropdown.show').on('hidden.bs.dropdown', function (e) { + $('.dropdown-submenu .show').removeClass("show"); + }); + + return false; + }); +}); \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI/Volo/Abp/AspNetCore/Mvc/UI/Layout/ContentLayout.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI/Volo/Abp/AspNetCore/Mvc/UI/Layout/ContentLayout.cs index e5b169486f..bd54aef4d9 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI/Volo/Abp/AspNetCore/Mvc/UI/Layout/ContentLayout.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI/Volo/Abp/AspNetCore/Mvc/UI/Layout/ContentLayout.cs @@ -1,4 +1,7 @@ -namespace Volo.Abp.AspNetCore.Mvc.UI.Layout +using System; +using System.Linq; + +namespace Volo.Abp.AspNetCore.Mvc.UI.Layout { public class ContentLayout { @@ -12,5 +15,20 @@ { BreadCrumb = new BreadCrumb(); } + + public virtual bool ShouldShowBreadCrumb() + { + if (BreadCrumb.Items.Any()) + { + return true; + } + + if (BreadCrumb.ShowCurrent && !Title.IsNullOrEmpty()) + { + return true; + } + + return false; + } } } \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Microsoft/AspNetCore/Http/AbpFormFileExtensions.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Microsoft/AspNetCore/Http/AbpFormFileExtensions.cs new file mode 100644 index 0000000000..c808250a3e --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Microsoft/AspNetCore/Http/AbpFormFileExtensions.cs @@ -0,0 +1,15 @@ +using System.IO; + +namespace Microsoft.AspNetCore.Http +{ + public static class AbpFormFileExtensions + { + public static byte[] GetAllBytes(this IFormFile file) + { + using (var stream = file.OpenReadStream()) + { + return stream.GetAllBytes(); + } + } + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpController.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpController.cs index e9e535c4be..48917ecd8b 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpController.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpController.cs @@ -5,6 +5,7 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using Volo.Abp.Aspects; using Volo.Abp.AspNetCore.Mvc.Validation; +using Volo.Abp.Features; using Volo.Abp.Guids; using Volo.Abp.MultiTenancy; using Volo.Abp.ObjectMapping; @@ -33,7 +34,9 @@ namespace Volo.Abp.AspNetCore.Mvc public IClock Clock { get; set; } public IModelStateValidator ModelValidator { get; set; } - + + public IFeatureChecker FeatureChecker { get; set; } + public List AppliedCrossCuttingConcerns { get; } = new List(); protected virtual void ValidateModel() diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpMvcOptionsExtensions.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpMvcOptionsExtensions.cs index 4de46b2f0b..59c7de8840 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpMvcOptionsExtensions.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpMvcOptionsExtensions.cs @@ -3,6 +3,7 @@ using Microsoft.Extensions.DependencyInjection; using Volo.Abp.AspNetCore.Mvc.Auditing; using Volo.Abp.AspNetCore.Mvc.Conventions; using Volo.Abp.AspNetCore.Mvc.ExceptionHandling; +using Volo.Abp.AspNetCore.Mvc.Features; using Volo.Abp.AspNetCore.Mvc.Uow; using Volo.Abp.AspNetCore.Mvc.Validation; @@ -26,6 +27,7 @@ namespace Volo.Abp.AspNetCore.Mvc private static void AddFilters(MvcOptions options) { options.Filters.AddService(typeof(AbpAuditActionFilter)); + options.Filters.AddService(typeof(AbpFeatureActionFilter)); options.Filters.AddService(typeof(AbpValidationActionFilter)); options.Filters.AddService(typeof(AbpUowActionFilter)); options.Filters.AddService(typeof(AbpExceptionFilter)); diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/AbpApplicationConfigurationAppService.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/AbpApplicationConfigurationAppService.cs index 086fbf1d72..2ea9282a91 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/AbpApplicationConfigurationAppService.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/AbpApplicationConfigurationAppService.cs @@ -4,10 +4,10 @@ using Microsoft.Extensions.Localization; using Microsoft.Extensions.Options; using System; using System.Collections.Generic; -using System.Linq; using System.Threading.Tasks; using Volo.Abp.Application.Services; using Volo.Abp.Authorization; +using Volo.Abp.Features; using Volo.Abp.Localization; using Volo.Abp.Settings; using Volo.Abp.Users; @@ -23,6 +23,7 @@ namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations private readonly ICurrentUser _currentUser; private readonly ISettingProvider _settingProvider; private readonly ISettingDefinitionManager _settingDefinitionManager; + private readonly IFeatureDefinitionManager _featureDefinitionManager; public AbpApplicationConfigurationAppService( IOptions localizationOptions, @@ -31,7 +32,8 @@ namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations IAuthorizationService authorizationService, ICurrentUser currentUser, ISettingProvider settingProvider, - SettingDefinitionManager settingDefinitionManager) + SettingDefinitionManager settingDefinitionManager, + IFeatureDefinitionManager featureDefinitionManager) { _serviceProvider = serviceProvider; _abpAuthorizationPolicyProvider = abpAuthorizationPolicyProvider; @@ -39,16 +41,18 @@ namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations _currentUser = currentUser; _settingProvider = settingProvider; _settingDefinitionManager = settingDefinitionManager; + _featureDefinitionManager = featureDefinitionManager; _localizationOptions = localizationOptions.Value; } - public async Task GetAsync() + public virtual async Task GetAsync() { //TODO: Optimize & cache..? return new ApplicationConfigurationDto { Auth = await GetAuthConfigAsync(), + Features = await GetFeaturesConfigAsync(), Localization = GetLocalizationConfig(), CurrentUser = GetCurrentUser(), Setting = await GetSettingConfigAsync() @@ -126,5 +130,25 @@ namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations return result; } + + protected virtual async Task GetFeaturesConfigAsync() + { + var result = new ApplicationFeatureConfigurationDto + { + Values = new Dictionary() + }; + + foreach (var featureDefinition in _featureDefinitionManager.GetAll()) + { + if (!featureDefinition.IsVisibleToClients) + { + continue; + } + + result.Values[featureDefinition.Name] = await FeatureChecker.GetOrNullAsync(featureDefinition.Name); + } + + return result; + } } } \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Features/AbpFeatureActionFilter.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Features/AbpFeatureActionFilter.cs new file mode 100644 index 0000000000..577acda339 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Features/AbpFeatureActionFilter.cs @@ -0,0 +1,42 @@ +using Microsoft.AspNetCore.Mvc.Filters; +using System; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc.Abstractions; +using Volo.Abp.Aspects; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Features; + +namespace Volo.Abp.AspNetCore.Mvc.Features +{ + public class AbpFeatureActionFilter : IAsyncActionFilter, ITransientDependency + { + private readonly IMethodInvocationFeatureCheckerService _methodInvocationAuthorizationService; + + public AbpFeatureActionFilter(IMethodInvocationFeatureCheckerService methodInvocationAuthorizationService) + { + _methodInvocationAuthorizationService = methodInvocationAuthorizationService; + } + + public async Task OnActionExecutionAsync( + ActionExecutingContext context, + ActionExecutionDelegate next) + { + if (!context.ActionDescriptor.IsControllerAction()) + { + await next(); + return; + } + + var methodInfo = context.ActionDescriptor.GetMethodInfo(); + + using (AbpCrossCuttingConcerns.Applying(context.Controller, AbpCrossCuttingConcerns.FeatureChecking)) + { + await _methodInvocationAuthorizationService.CheckAsync( + new MethodInvocationFeatureCheckerContext(methodInfo) + ); + + await next(); + } + } + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Uow/AbpUowActionFilter.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Uow/AbpUowActionFilter.cs index 5a330eb7a9..0a03a0dc67 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Uow/AbpUowActionFilter.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Uow/AbpUowActionFilter.cs @@ -12,7 +12,6 @@ namespace Volo.Abp.AspNetCore.Mvc.Uow { public class AbpUowActionFilter : IAsyncActionFilter, ITransientDependency { - private readonly IUnitOfWorkManager _unitOfWorkManager; private readonly UnitOfWorkDefaultOptions _defaultOptions; diff --git a/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Threading/HttpContextCancellationTokenProvider.cs b/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Threading/HttpContextCancellationTokenProvider.cs index 4757ce2f93..c7c961176c 100644 --- a/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Threading/HttpContextCancellationTokenProvider.cs +++ b/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Threading/HttpContextCancellationTokenProvider.cs @@ -8,7 +8,7 @@ namespace Volo.Abp.AspNetCore.Threading [Dependency(ReplaceServices = true)] public class HttpContextCancellationTokenProvider : ICancellationTokenProvider, ITransientDependency { - public CancellationToken Token => _httpContextAccessor.HttpContext?.RequestAborted ?? default; + public CancellationToken Token => _httpContextAccessor.HttpContext?.RequestAborted ?? CancellationToken.None; private readonly IHttpContextAccessor _httpContextAccessor; diff --git a/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Tracing/AspNetCoreCorrelationIdProvider.cs b/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Tracing/AspNetCoreCorrelationIdProvider.cs index 78383d1252..e62f1ed2a4 100644 --- a/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Tracing/AspNetCoreCorrelationIdProvider.cs +++ b/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Tracing/AspNetCoreCorrelationIdProvider.cs @@ -27,18 +27,21 @@ namespace Volo.Abp.AspNetCore.Tracing return CreateNewCorrelationId(); } - lock (HttpContextAccessor.HttpContext.Request.Headers) - { - string correlationId = HttpContextAccessor.HttpContext.Request.Headers[Options.HttpHeaderName]; + string correlationId = HttpContextAccessor.HttpContext.Request.Headers[Options.HttpHeaderName]; - if (correlationId.IsNullOrEmpty()) + if (correlationId.IsNullOrEmpty()) + { + lock (HttpContextAccessor.HttpContext.Request.Headers) { - correlationId = CreateNewCorrelationId(); - HttpContextAccessor.HttpContext.Request.Headers[Options.HttpHeaderName] = correlationId; + if (correlationId.IsNullOrEmpty()) + { + correlationId = CreateNewCorrelationId(); + HttpContextAccessor.HttpContext.Request.Headers[Options.HttpHeaderName] = correlationId; + } } - - return correlationId; } + + return correlationId; } protected virtual string CreateNewCorrelationId() diff --git a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/AbpAuthorizationModule.cs b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/AbpAuthorizationModule.cs index 57472d7cf1..880673f86b 100644 --- a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/AbpAuthorizationModule.cs +++ b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/AbpAuthorizationModule.cs @@ -1,4 +1,6 @@ -using Microsoft.AspNetCore.Authorization; +using System; +using System.Collections.Generic; +using Microsoft.AspNetCore.Authorization; using Microsoft.Extensions.DependencyInjection; using Volo.Abp.Authorization.Permissions; using Volo.Abp.Localization; @@ -16,6 +18,7 @@ namespace Volo.Abp.Authorization public override void PreConfigureServices(ServiceConfigurationContext context) { context.Services.OnRegistred(AuthorizationInterceptorRegistrar.RegisterIfNeeded); + AutoAddDefinitionProviders(context.Services); } public override void ConfigureServices(ServiceConfigurationContext context) @@ -31,5 +34,23 @@ namespace Volo.Abp.Authorization options.ValueProviders.Add(); }); } + + private static void AutoAddDefinitionProviders(IServiceCollection services) + { + var definitionProviders = new List(); + + services.OnRegistred(context => + { + if (typeof(IPermissionDefinitionProvider).IsAssignableFrom(context.ImplementationType)) + { + definitionProviders.Add(context.ImplementationType); + } + }); + + services.Configure(options => + { + options.DefinitionProviders.AddIfNotContains(definitionProviders); + }); + } } } diff --git a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/AuthorizationInterceptorRegistrar.cs b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/AuthorizationInterceptorRegistrar.cs index 76a12b6dfe..69a285ddc8 100644 --- a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/AuthorizationInterceptorRegistrar.cs +++ b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/AuthorizationInterceptorRegistrar.cs @@ -1,4 +1,8 @@ -using Volo.Abp.DependencyInjection; +using System; +using System.Linq; +using System.Reflection; +using Microsoft.AspNetCore.Authorization; +using Volo.Abp.DependencyInjection; namespace Volo.Abp.Authorization { @@ -6,10 +10,28 @@ namespace Volo.Abp.Authorization { public static void RegisterIfNeeded(IOnServiceRegistredContext context) { - if (typeof(IAuthorizationEnabled).IsAssignableFrom(context.ImplementationType)) + if (ShouldIntercept(context.ImplementationType)) { context.Interceptors.TryAdd(); } } + + private static bool ShouldIntercept(Type type) + { + return type.IsDefined(typeof(AuthorizeAttribute), true) || + AnyMethodHasAuthorizeAttribute(type); + } + + private static bool AnyMethodHasAuthorizeAttribute(Type implementationType) + { + return implementationType + .GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) + .Any(HasAuthorizeAttribute); + } + + private static bool HasAuthorizeAttribute(MemberInfo methodInfo) + { + return methodInfo.IsDefined(typeof(AuthorizeAttribute), true); + } } } \ No newline at end of file diff --git a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/IAuthorizationEnabled.cs b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/IAuthorizationEnabled.cs deleted file mode 100644 index 8563a98a8c..0000000000 --- a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/IAuthorizationEnabled.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Volo.Abp.Authorization -{ - public interface IAuthorizationEnabled - { - - } -} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/MethodInvocationAuthorizationService.cs b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/MethodInvocationAuthorizationService.cs index 1cca959ebe..56baba8ce4 100644 --- a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/MethodInvocationAuthorizationService.cs +++ b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/MethodInvocationAuthorizationService.cs @@ -1,4 +1,6 @@ -using System.Linq; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; using System.Threading.Tasks; using Microsoft.AspNetCore.Authorization; using Volo.Abp.Clients; @@ -30,8 +32,7 @@ namespace Volo.Abp.Authorization return; } - var authorizationAttributes = GetAuthorizationDataAttributes(context); - foreach (var authorizationAttribute in authorizationAttributes) + foreach (var authorizationAttribute in GetAuthorizationDataAttributes(context.Method)) { await CheckAsync(authorizationAttribute); } @@ -42,17 +43,23 @@ namespace Volo.Abp.Authorization return context.Method.GetCustomAttributes(true).OfType().Any(); } - protected virtual IAuthorizeData[] GetAuthorizationDataAttributes(MethodInvocationAuthorizationContext context) + protected virtual IEnumerable GetAuthorizationDataAttributes(MethodInfo methodInfo) { - var classAttributes = context.Method.DeclaringType + var attributes = methodInfo .GetCustomAttributes(true) .OfType(); - var methodAttributes = context.Method - .GetCustomAttributes(true) - .OfType(); + if (methodInfo.IsPublic) + { + attributes = attributes + .Union( + methodInfo.DeclaringType + .GetCustomAttributes(true) + .OfType() + ); + } - return classAttributes.Union(methodAttributes).ToArray(); + return attributes; } protected async Task CheckAsync(IAuthorizeData authorizationAttribute) diff --git a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/AlwaysAllowPermissionChecker.cs b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/AlwaysAllowPermissionChecker.cs index 77c8f4b665..e3f4425289 100644 --- a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/AlwaysAllowPermissionChecker.cs +++ b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/AlwaysAllowPermissionChecker.cs @@ -1,5 +1,6 @@ using System.Security.Claims; using System.Threading.Tasks; +using Volo.Abp.Threading; namespace Volo.Abp.Authorization.Permissions { @@ -11,14 +12,14 @@ namespace Volo.Abp.Authorization.Permissions /// public class AlwaysAllowPermissionChecker : IPermissionChecker { - public Task CheckAsync(string name) + public Task IsGrantedAsync(string name) { - return Task.FromResult(new PermissionGrantInfo(name, true, "AlwaysAllow")); + return TaskCache.TrueResult; } - public Task CheckAsync(ClaimsPrincipal claimsPrincipal, string name) + public Task IsGrantedAsync(ClaimsPrincipal claimsPrincipal, string name) { - return Task.FromResult(new PermissionGrantInfo(name, true, "AlwaysAllow")); + return TaskCache.TrueResult; } } } diff --git a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/ClientPermissionValueProvider.cs b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/ClientPermissionValueProvider.cs index b9eac0bab0..87b391443d 100644 --- a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/ClientPermissionValueProvider.cs +++ b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/ClientPermissionValueProvider.cs @@ -15,21 +15,18 @@ namespace Volo.Abp.Authorization.Permissions } - public override async Task CheckAsync(PermissionValueCheckContext context) + public override async Task CheckAsync(PermissionValueCheckContext context) { var clientId = context.Principal?.FindFirst(AbpClaimTypes.ClientId)?.Value; if (clientId == null) { - return PermissionValueProviderGrantInfo.NonGranted; + return PermissionGrantResult.Undefined; } - if (await PermissionStore.IsGrantedAsync(context.Permission.Name, Name, clientId)) - { - return new PermissionValueProviderGrantInfo(true, clientId); - } - - return PermissionValueProviderGrantInfo.NonGranted; + return await PermissionStore.IsGrantedAsync(context.Permission.Name, Name, clientId) + ? PermissionGrantResult.Granted + : PermissionGrantResult.Undefined; } } } diff --git a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/IPermissionChecker.cs b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/IPermissionChecker.cs index 833e09281c..b394a29f52 100644 --- a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/IPermissionChecker.cs +++ b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/IPermissionChecker.cs @@ -6,8 +6,8 @@ namespace Volo.Abp.Authorization.Permissions { public interface IPermissionChecker { - Task CheckAsync([NotNull]string name); + Task IsGrantedAsync([NotNull]string name); - Task CheckAsync([CanBeNull] ClaimsPrincipal claimsPrincipal, [NotNull]string name); + Task IsGrantedAsync([CanBeNull] ClaimsPrincipal claimsPrincipal, [NotNull]string name); } } \ No newline at end of file diff --git a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/IPermissionDefinitionContext.cs b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/IPermissionDefinitionContext.cs index 00680e0398..84530e2aa6 100644 --- a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/IPermissionDefinitionContext.cs +++ b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/IPermissionDefinitionContext.cs @@ -9,5 +9,7 @@ namespace Volo.Abp.Authorization.Permissions PermissionGroupDefinition GetGroupOrNull(string name); PermissionGroupDefinition AddGroup([NotNull] string name, ILocalizableString displayName = null); + + void RemoveGroup(string name); } } \ No newline at end of file diff --git a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/IPermissionDefinitionProvider.cs b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/IPermissionDefinitionProvider.cs index d0a41b3390..ef92125595 100644 --- a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/IPermissionDefinitionProvider.cs +++ b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/IPermissionDefinitionProvider.cs @@ -1,8 +1,6 @@ -using Volo.Abp.DependencyInjection; - -namespace Volo.Abp.Authorization.Permissions +namespace Volo.Abp.Authorization.Permissions { - public interface IPermissionDefinitionProvider : ISingletonDependency + public interface IPermissionDefinitionProvider { void Define(IPermissionDefinitionContext context); } diff --git a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/IPermissionValueProvider.cs b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/IPermissionValueProvider.cs index 73ba3bfd17..a9b5e78337 100644 --- a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/IPermissionValueProvider.cs +++ b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/IPermissionValueProvider.cs @@ -1,12 +1,12 @@ using System.Threading.Tasks; -using Volo.Abp.DependencyInjection; namespace Volo.Abp.Authorization.Permissions { - public interface IPermissionValueProvider : ISingletonDependency + public interface IPermissionValueProvider { string Name { get; } - Task CheckAsync(PermissionValueCheckContext context); + //TODO: Rename to GetResult? (CheckAsync throws exception by naming convention) + Task CheckAsync(PermissionValueCheckContext context); } } \ No newline at end of file diff --git a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/NullPermissionStore.cs b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/NullPermissionStore.cs index b5c5cb5a4b..57c76ac6c9 100644 --- a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/NullPermissionStore.cs +++ b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/NullPermissionStore.cs @@ -2,6 +2,7 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using Volo.Abp.DependencyInjection; +using Volo.Abp.Threading; namespace Volo.Abp.Authorization.Permissions { @@ -16,7 +17,7 @@ namespace Volo.Abp.Authorization.Permissions public Task IsGrantedAsync(string name, string providerName, string providerKey) { - return Task.FromResult(false); + return TaskCache.FalseResult; } } } \ No newline at end of file diff --git a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/PermissionChecker.cs b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/PermissionChecker.cs index 7113571c16..d69ac81e78 100644 --- a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/PermissionChecker.cs +++ b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/PermissionChecker.cs @@ -41,12 +41,12 @@ namespace Volo.Abp.Authorization.Permissions ); } - public virtual Task CheckAsync(string name) + public virtual Task IsGrantedAsync(string name) { - return CheckAsync(PrincipalAccessor.Principal, name); + return IsGrantedAsync(PrincipalAccessor.Principal, name); } - public virtual async Task CheckAsync(ClaimsPrincipal claimsPrincipal, string name) + public virtual async Task IsGrantedAsync(ClaimsPrincipal claimsPrincipal, string name) { Check.NotNull(name, nameof(name)); @@ -55,6 +55,8 @@ namespace Volo.Abp.Authorization.Permissions claimsPrincipal ); + var isGranted = false; + foreach (var provider in ValueProviders) { if (context.Permission.Providers.Any() && @@ -64,13 +66,18 @@ namespace Volo.Abp.Authorization.Permissions } var result = await provider.CheckAsync(context); - if (result.IsGranted) + + if (result == PermissionGrantResult.Granted) + { + isGranted = true; + } + else if (result == PermissionGrantResult.Prohibited) { - return new PermissionGrantInfo(context.Permission.Name, true, provider.Name, result.ProviderKey); + return false; } } - return new PermissionGrantInfo(context.Permission.Name, false); + return isGranted; } } } \ No newline at end of file diff --git a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/PermissionCheckerExtensions.cs b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/PermissionCheckerExtensions.cs deleted file mode 100644 index f9891862ff..0000000000 --- a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/PermissionCheckerExtensions.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System.Security.Claims; -using System.Threading.Tasks; - -namespace Volo.Abp.Authorization.Permissions -{ - public static class PermissionCheckerExtensions - { - public static async Task IsGrantedAsync(this IPermissionChecker permissionChecker, string name) - { - return (await permissionChecker.CheckAsync(name)).IsGranted; - } - - public static async Task IsGrantedAsync(this IPermissionChecker permissionChecker, ClaimsPrincipal principal, string name) - { - return (await permissionChecker.CheckAsync(principal, name)).IsGranted; - } - - //TODO: Add sync extensions - } -} diff --git a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/PermissionDefinition.cs b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/PermissionDefinition.cs index bb23757de5..de4ca1221f 100644 --- a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/PermissionDefinition.cs +++ b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/PermissionDefinition.cs @@ -22,7 +22,7 @@ namespace Volo.Abp.Authorization.Permissions /// A list of allowed providers to get/set value of this permission. /// An empty list indicates that all providers are allowed. /// - public List Providers { get; } + public List Providers { get; } //TODO: Rename to AllowedProviders? public ILocalizableString DisplayName { @@ -53,7 +53,9 @@ namespace Volo.Abp.Authorization.Permissions set => Properties[name] = value; } - protected internal PermissionDefinition([NotNull] string name, ILocalizableString displayName = null) + protected internal PermissionDefinition( + [NotNull] string name, + ILocalizableString displayName = null) { Name = Check.NotNull(name, nameof(name)); DisplayName = displayName ?? new FixedLocalizableString(name); @@ -63,7 +65,9 @@ namespace Volo.Abp.Authorization.Permissions _children = new List(); } - public virtual PermissionDefinition AddChild([NotNull] string name, ILocalizableString displayName = null) + public virtual PermissionDefinition AddChild( + [NotNull] string name, + ILocalizableString displayName = null) { var child = new PermissionDefinition(name, displayName) { diff --git a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/PermissionDefinitionContext.cs b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/PermissionDefinitionContext.cs index 59be1c2ad5..2fdb60b75f 100644 --- a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/PermissionDefinitionContext.cs +++ b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/PermissionDefinitionContext.cs @@ -36,5 +36,16 @@ namespace Volo.Abp.Authorization.Permissions return Groups[name]; } + public virtual void RemoveGroup(string name) + { + Check.NotNull(name, nameof(name)); + + if (!Groups.ContainsKey(name)) + { + throw new AbpException($"Not found permission group with name: {name}"); + } + + Groups.Remove(name); + } } } \ No newline at end of file diff --git a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/PermissionDefinitionManager.cs b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/PermissionDefinitionManager.cs index 4915721af0..bb58089c82 100644 --- a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/PermissionDefinitionManager.cs +++ b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/PermissionDefinitionManager.cs @@ -10,9 +10,6 @@ namespace Volo.Abp.Authorization.Permissions { public class PermissionDefinitionManager : IPermissionDefinitionManager, ISingletonDependency { - protected List Providers => _lazyProviders.Value; - private readonly Lazy> _lazyProviders; - protected IDictionary PermissionGroupDefinitions => _lazyPermissionGroupDefinitions.Value; private readonly Lazy> _lazyPermissionGroupDefinitions; @@ -30,9 +27,15 @@ namespace Volo.Abp.Authorization.Permissions _serviceProvider = serviceProvider; Options = options.Value; - _lazyProviders = new Lazy>(CreatePermissionProviders, true); - _lazyPermissionDefinitions = new Lazy>(CreatePermissionDefinitions, true); - _lazyPermissionGroupDefinitions = new Lazy>(CreatePermissionGroupDefinitions, true); + _lazyPermissionDefinitions = new Lazy>( + CreatePermissionDefinitions, + isThreadSafe: true + ); + + _lazyPermissionGroupDefinitions = new Lazy>( + CreatePermissionGroupDefinitions, + isThreadSafe: true + ); } public virtual PermissionDefinition Get(string name) @@ -64,14 +67,6 @@ namespace Volo.Abp.Authorization.Permissions return PermissionGroupDefinitions.Values.ToImmutableList(); } - protected virtual List CreatePermissionProviders() - { - return Options - .DefinitionProviders - .Select(p => _serviceProvider.GetRequiredService(p) as IPermissionDefinitionProvider) - .ToList(); - } - protected virtual Dictionary CreatePermissionDefinitions() { var permissions = new Dictionary(); @@ -87,7 +82,9 @@ namespace Volo.Abp.Authorization.Permissions return permissions; } - protected virtual void AddPermissionToDictionaryRecursively(Dictionary permissions, PermissionDefinition permission) + protected virtual void AddPermissionToDictionaryRecursively( + Dictionary permissions, + PermissionDefinition permission) { if (permissions.ContainsKey(permission.Name)) { @@ -106,9 +103,17 @@ namespace Volo.Abp.Authorization.Permissions { var context = new PermissionDefinitionContext(); - foreach (var provider in Providers) + using (var scope = _serviceProvider.CreateScope()) { - provider.Define(context); + var providers = Options + .DefinitionProviders + .Select(p => scope.ServiceProvider.GetRequiredService(p) as IPermissionDefinitionProvider) + .ToList(); + + foreach (var provider in providers) + { + provider.Define(context); + } } return context.Groups; diff --git a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/PermissionDefinitionProvider.cs b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/PermissionDefinitionProvider.cs index df1674dc2e..0584ffe426 100644 --- a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/PermissionDefinitionProvider.cs +++ b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/PermissionDefinitionProvider.cs @@ -1,6 +1,8 @@ -namespace Volo.Abp.Authorization.Permissions +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.Authorization.Permissions { - public abstract class PermissionDefinitionProvider : IPermissionDefinitionProvider + public abstract class PermissionDefinitionProvider : IPermissionDefinitionProvider, ITransientDependency { public abstract void Define(IPermissionDefinitionContext context); } diff --git a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/PermissionGrantResult.cs b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/PermissionGrantResult.cs new file mode 100644 index 0000000000..0b8b23e426 --- /dev/null +++ b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/PermissionGrantResult.cs @@ -0,0 +1,9 @@ +namespace Volo.Abp.Authorization.Permissions +{ + public enum PermissionGrantResult + { + Undefined, + Granted, + Prohibited + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/PermissionGroupDefinition.cs b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/PermissionGroupDefinition.cs index a52bd4f601..623db24c8a 100644 --- a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/PermissionGroupDefinition.cs +++ b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/PermissionGroupDefinition.cs @@ -37,7 +37,9 @@ namespace Volo.Abp.Authorization.Permissions set => Properties[name] = value; } - protected internal PermissionGroupDefinition(string name, ILocalizableString displayName = null) + protected internal PermissionGroupDefinition( + string name, + ILocalizableString displayName = null) { Name = name; DisplayName = displayName ?? new FixedLocalizableString(Name); @@ -46,7 +48,9 @@ namespace Volo.Abp.Authorization.Permissions _permissions = new List(); } - public virtual PermissionDefinition AddPermission(string name, ILocalizableString displayName = null) + public virtual PermissionDefinition AddPermission( + string name, + ILocalizableString displayName = null) { var permission = new PermissionDefinition(name, displayName); diff --git a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/PermissionValueCheckContext.cs b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/PermissionValueCheckContext.cs index 83602a0c46..bc39384938 100644 --- a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/PermissionValueCheckContext.cs +++ b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/PermissionValueCheckContext.cs @@ -11,7 +11,9 @@ namespace Volo.Abp.Authorization.Permissions [CanBeNull] public ClaimsPrincipal Principal { get; } - public PermissionValueCheckContext([NotNull] PermissionDefinition permission, [CanBeNull] ClaimsPrincipal principal) + public PermissionValueCheckContext( + [NotNull] PermissionDefinition permission, + [CanBeNull] ClaimsPrincipal principal) { Check.NotNull(permission, nameof(permission)); diff --git a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/PermissionValueProvider.cs b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/PermissionValueProvider.cs index 066d876637..44d23f718a 100644 --- a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/PermissionValueProvider.cs +++ b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/PermissionValueProvider.cs @@ -1,8 +1,9 @@ using System.Threading.Tasks; +using Volo.Abp.DependencyInjection; namespace Volo.Abp.Authorization.Permissions { - public abstract class PermissionValueProvider : IPermissionValueProvider + public abstract class PermissionValueProvider : IPermissionValueProvider, ISingletonDependency //TODO: to transient? { public abstract string Name { get; } @@ -13,6 +14,6 @@ namespace Volo.Abp.Authorization.Permissions PermissionStore = permissionStore; } - public abstract Task CheckAsync(PermissionValueCheckContext context); + public abstract Task CheckAsync(PermissionValueCheckContext context); } } \ No newline at end of file diff --git a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/RolePermissionValueProvider.cs b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/RolePermissionValueProvider.cs index 1d200ac49a..a008190fbd 100644 --- a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/RolePermissionValueProvider.cs +++ b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/RolePermissionValueProvider.cs @@ -16,23 +16,23 @@ namespace Volo.Abp.Authorization.Permissions } - public override async Task CheckAsync(PermissionValueCheckContext context) + public override async Task CheckAsync(PermissionValueCheckContext context) { var roles = context.Principal?.FindAll(AbpClaimTypes.Role).Select(c => c.Value).ToArray(); if (roles == null || !roles.Any()) { - return PermissionValueProviderGrantInfo.NonGranted; + return PermissionGrantResult.Undefined; } foreach (var role in roles) { if (await PermissionStore.IsGrantedAsync(context.Permission.Name, Name, role)) { - return new PermissionValueProviderGrantInfo(true, role); + return PermissionGrantResult.Granted; } } - return PermissionValueProviderGrantInfo.NonGranted; + return PermissionGrantResult.Undefined; } } } \ No newline at end of file diff --git a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/UserPermissionValueProvider.cs b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/UserPermissionValueProvider.cs index 9f6022907e..f04a85910f 100644 --- a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/UserPermissionValueProvider.cs +++ b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/UserPermissionValueProvider.cs @@ -15,21 +15,18 @@ namespace Volo.Abp.Authorization.Permissions } - public override async Task CheckAsync(PermissionValueCheckContext context) + public override async Task CheckAsync(PermissionValueCheckContext context) { var userId = context.Principal?.FindFirst(AbpClaimTypes.UserId)?.Value; if (userId == null) { - return PermissionValueProviderGrantInfo.NonGranted; + return PermissionGrantResult.Undefined; } - if (await PermissionStore.IsGrantedAsync(context.Permission.Name, Name, userId)) - { - return new PermissionValueProviderGrantInfo(true, userId); - } - - return PermissionValueProviderGrantInfo.NonGranted; + return await PermissionStore.IsGrantedAsync(context.Permission.Name, Name, userId) + ? PermissionGrantResult.Granted + : PermissionGrantResult.Undefined; } } } diff --git a/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/CacheNameAttribute.cs b/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/CacheNameAttribute.cs index a9dbe670bb..d02bc76da5 100644 --- a/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/CacheNameAttribute.cs +++ b/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/CacheNameAttribute.cs @@ -15,5 +15,20 @@ namespace Volo.Abp.Caching Name = name; } + + public static string GetCacheName(Type cacheItemType) + { + var cacheNameAttribute = cacheItemType + .GetCustomAttributes(true) + .OfType() + .FirstOrDefault(); + + if (cacheNameAttribute != null) + { + return cacheNameAttribute.Name; + } + + return cacheItemType.FullName.RemovePostFix("CacheItem"); + } } } \ No newline at end of file diff --git a/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/DistributedCache.cs b/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/DistributedCache.cs index 708633b715..477b0c764b 100644 --- a/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/DistributedCache.cs +++ b/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/DistributedCache.cs @@ -127,7 +127,7 @@ namespace Volo.Abp.Caching return value; } - using (AsyncLock.Lock()) + using (AsyncLock.Lock(CancellationTokenProvider.Token)) { value = Get(key, hideErrors); if (value != null) @@ -326,13 +326,7 @@ namespace Volo.Abp.Caching protected virtual void SetDefaultOptions() { - //CacheName - var cacheNameAttribute = typeof(TCacheItem) - .GetCustomAttributes(true) - .OfType() - .FirstOrDefault(); - - CacheName = cacheNameAttribute != null ? cacheNameAttribute.Name : typeof(TCacheItem).FullName; + CacheName = CacheNameAttribute.GetCacheName(typeof(TCacheItem)); //IgnoreMultiTenancy IgnoreMultiTenancy = typeof(TCacheItem).IsDefined(typeof(IgnoreMultiTenancyAttribute), true); diff --git a/framework/src/Volo.Abp.Core/System/Collections/Generic/AbpListExtensions.cs b/framework/src/Volo.Abp.Core/System/Collections/Generic/AbpListExtensions.cs index 171a6ac237..a123236a4e 100644 --- a/framework/src/Volo.Abp.Core/System/Collections/Generic/AbpListExtensions.cs +++ b/framework/src/Volo.Abp.Core/System/Collections/Generic/AbpListExtensions.cs @@ -32,6 +32,18 @@ namespace System.Collections.Generic source.Insert(source.Count, item); } + public static void InsertAfter(this IList source, T existingItem, T item) + { + var index = source.IndexOf(existingItem); + if (index < 0) + { + source.AddFirst(item); + return; + } + + source.Insert(index + 1, item); + } + public static void InsertAfter(this IList source, Predicate selector, T item) { var index = source.FindIndex(selector); @@ -44,6 +56,17 @@ namespace System.Collections.Generic source.Insert(index + 1, item); } + public static void InsertBefore(this IList source, T existingItem, T item) + { + var index = source.IndexOf(existingItem); + if (index < 0) + { + source.AddLast(item); + return; + } + + source.Insert(index, item); + } public static void InsertBefore(this IList source, Predicate selector, T item) { diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Aspects/AbpCrossCuttingConcerns.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Aspects/AbpCrossCuttingConcerns.cs index 35aa6d0811..4c8033a0cf 100644 --- a/framework/src/Volo.Abp.Core/Volo/Abp/Aspects/AbpCrossCuttingConcerns.cs +++ b/framework/src/Volo.Abp.Core/Volo/Abp/Aspects/AbpCrossCuttingConcerns.cs @@ -12,6 +12,7 @@ namespace Volo.Abp.Aspects public const string Validation = "AbpValidation"; public const string UnitOfWork = "AbpUnitOfWork"; public const string Authorization = "AbpAuthorization"; + public const string FeatureChecking = "AbpFeatureChecking"; public static void AddApplied(object obj, params string[] concerns) { diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/NameValue.cs b/framework/src/Volo.Abp.Core/Volo/Abp/NameValue.cs index a9525b9064..c7a4e7c0fe 100644 --- a/framework/src/Volo.Abp.Core/Volo/Abp/NameValue.cs +++ b/framework/src/Volo.Abp.Core/Volo/Abp/NameValue.cs @@ -8,17 +8,11 @@ namespace Volo.Abp [Serializable] public class NameValue : NameValue { - /// - /// Creates a new . - /// public NameValue() { } - /// - /// Creates a new . - /// public NameValue(string name, string value) { Name = name; @@ -42,17 +36,11 @@ namespace Volo.Abp /// public T Value { get; set; } - /// - /// Creates a new . - /// public NameValue() { } - /// - /// Creates a new . - /// public NameValue(string name, T value) { Name = name; diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Threading/TaskCache.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Threading/TaskCache.cs new file mode 100644 index 0000000000..0cb57ca0c5 --- /dev/null +++ b/framework/src/Volo.Abp.Core/Volo/Abp/Threading/TaskCache.cs @@ -0,0 +1,16 @@ +using System.Threading.Tasks; + +namespace Volo.Abp.Threading +{ + public static class TaskCache + { + public static Task TrueResult { get; } + public static Task FalseResult { get; } + + static TaskCache() + { + TrueResult = Task.FromResult(true); + FalseResult = Task.FromResult(false); + } + } +} diff --git a/framework/src/Volo.Abp.Data/Volo/Abp/Data/HasExtraPropertiesExtensions.cs b/framework/src/Volo.Abp.Data/Volo/Abp/Data/HasExtraPropertiesExtensions.cs index a16373eedc..15915cc94b 100644 --- a/framework/src/Volo.Abp.Data/Volo/Abp/Data/HasExtraPropertiesExtensions.cs +++ b/framework/src/Volo.Abp.Data/Volo/Abp/Data/HasExtraPropertiesExtensions.cs @@ -39,5 +39,12 @@ namespace Volo.Abp.Data source.ExtraProperties[name] = value; return source; } + + public static TSource RemoveProperty(this TSource source, string name) + where TSource : IHasExtraProperties + { + source.ExtraProperties.Remove(name); + return source; + } } } \ No newline at end of file diff --git a/framework/src/Volo.Abp.Ddd.Application/Volo.Abp.Ddd.Application.csproj b/framework/src/Volo.Abp.Ddd.Application/Volo.Abp.Ddd.Application.csproj index 57f909667b..243c94e66a 100644 --- a/framework/src/Volo.Abp.Ddd.Application/Volo.Abp.Ddd.Application.csproj +++ b/framework/src/Volo.Abp.Ddd.Application/Volo.Abp.Ddd.Application.csproj @@ -17,6 +17,7 @@ + diff --git a/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/AbpDddApplicationModule.cs b/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/AbpDddApplicationModule.cs index 84b609b99f..e5ade49532 100644 --- a/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/AbpDddApplicationModule.cs +++ b/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/AbpDddApplicationModule.cs @@ -2,6 +2,7 @@ using Volo.Abp.Application.Services; using Volo.Abp.Authorization; using Volo.Abp.Domain; +using Volo.Abp.Features; using Volo.Abp.Http; using Volo.Abp.Http.Modeling; using Volo.Abp.Modularity; @@ -20,7 +21,8 @@ namespace Volo.Abp.Application typeof(AbpValidationModule), typeof(AbpAuthorizationModule), typeof(AbpHttpAbstractionsModule), - typeof(AbpSettingsModule) + typeof(AbpSettingsModule), + typeof(AbpFeaturesModule) )] public class AbpDddApplicationModule : AbpModule { @@ -30,8 +32,7 @@ namespace Volo.Abp.Application { options.IgnoredInterfaces.AddIfNotContains(typeof(IRemoteService)); options.IgnoredInterfaces.AddIfNotContains(typeof(IApplicationService)); - options.IgnoredInterfaces.AddIfNotContains(typeof(IUnitOfWorkEnabled)); //TODO: Move to it's own module if possible? - options.IgnoredInterfaces.AddIfNotContains(typeof(IAuthorizationEnabled)); //TODO: Move to it's own module if possible? + options.IgnoredInterfaces.AddIfNotContains(typeof(IUnitOfWorkEnabled)); }); } } diff --git a/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/ApplicationService.cs b/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/ApplicationService.cs index 63a749e259..48865f38f2 100644 --- a/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/ApplicationService.cs +++ b/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/ApplicationService.cs @@ -9,6 +9,7 @@ using Volo.Abp.Aspects; using Volo.Abp.Auditing; using Volo.Abp.Authorization; using Volo.Abp.DependencyInjection; +using Volo.Abp.Features; using Volo.Abp.Guids; using Volo.Abp.MultiTenancy; using Volo.Abp.ObjectMapping; @@ -25,7 +26,6 @@ namespace Volo.Abp.Application.Services IAvoidDuplicateCrossCuttingConcerns, IValidationEnabled, IUnitOfWorkEnabled, - IAuthorizationEnabled, IAuditingEnabled, ITransientDependency { @@ -51,6 +51,8 @@ namespace Volo.Abp.Application.Services public IAuthorizationService AuthorizationService { get; set; } + public IFeatureChecker FeatureChecker { get; set; } + protected IUnitOfWork CurrentUnitOfWork => UnitOfWorkManager?.Current; protected ILogger Logger => _lazyLogger.Value; diff --git a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/EntityHelper.cs b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/EntityHelper.cs index 69a4d9ddaa..b6558f34d1 100644 --- a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/EntityHelper.cs +++ b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/EntityHelper.cs @@ -75,11 +75,10 @@ namespace Volo.Abp.Domain.Entities where TEntity : IEntity { var lambdaParam = Expression.Parameter(typeof(TEntity)); - var lambdaBody = Expression.Equal( - Expression.PropertyOrField(lambdaParam, nameof(Entity.Id)), - Expression.Constant(id, typeof(TKey)) - ); - + var leftExpression = Expression.PropertyOrField(lambdaParam, "Id"); + Expression> closure = () => id; + var rightExpression = Expression.Convert(closure.Body, leftExpression.Type); + var lambdaBody = Expression.Equal(leftExpression, rightExpression); return Expression.Lambda>(lambdaBody, lambdaParam); } } diff --git a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/AbpEmailingModule.cs b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/AbpEmailingModule.cs index 7648ddc910..b861a3898c 100644 --- a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/AbpEmailingModule.cs +++ b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/AbpEmailingModule.cs @@ -18,11 +18,6 @@ namespace Volo.Abp.Emailing { public override void ConfigureServices(ServiceConfigurationContext context) { - Configure(options => - { - options.DefinitionProviders.Add(); - }); - Configure(options => { options.FileSets.AddEmbedded(); diff --git a/framework/src/Volo.Abp.EntityFrameworkCore.MySQL/Volo.Abp.EntityFrameworkCore.MySQL.csproj b/framework/src/Volo.Abp.EntityFrameworkCore.MySQL/Volo.Abp.EntityFrameworkCore.MySQL.csproj index 52d430bbaa..5e9c7a91fb 100644 --- a/framework/src/Volo.Abp.EntityFrameworkCore.MySQL/Volo.Abp.EntityFrameworkCore.MySQL.csproj +++ b/framework/src/Volo.Abp.EntityFrameworkCore.MySQL/Volo.Abp.EntityFrameworkCore.MySQL.csproj @@ -16,9 +16,9 @@ - + - + diff --git a/framework/src/Volo.Abp.EntityFrameworkCore.MySQL/Volo/Abp/EntityFrameworkCore/AbpDbContextConfigurationContextMySQLExtensions.cs b/framework/src/Volo.Abp.EntityFrameworkCore.MySQL/Volo/Abp/EntityFrameworkCore/AbpDbContextConfigurationContextMySQLExtensions.cs index 35afda3d1e..542bf9e19e 100644 --- a/framework/src/Volo.Abp.EntityFrameworkCore.MySQL/Volo/Abp/EntityFrameworkCore/AbpDbContextConfigurationContextMySQLExtensions.cs +++ b/framework/src/Volo.Abp.EntityFrameworkCore.MySQL/Volo/Abp/EntityFrameworkCore/AbpDbContextConfigurationContextMySQLExtensions.cs @@ -1,7 +1,7 @@ using JetBrains.Annotations; using Microsoft.EntityFrameworkCore; -using MySql.Data.EntityFrameworkCore.Infraestructure; using System; +using Microsoft.EntityFrameworkCore.Infrastructure; using Volo.Abp.EntityFrameworkCore.DependencyInjection; namespace Volo.Abp.EntityFrameworkCore @@ -10,15 +10,15 @@ namespace Volo.Abp.EntityFrameworkCore { public static DbContextOptionsBuilder UseMySQL( [NotNull] this AbpDbContextConfigurationContext context, - [CanBeNull] Action mySQLOptionsAction = null) + [CanBeNull] Action mySQLOptionsAction = null) { if (context.ExistingConnection != null) { - return context.DbContextOptions.UseMySQL(context.ExistingConnection, mySQLOptionsAction); + return context.DbContextOptions.UseMySql(context.ExistingConnection, mySQLOptionsAction); } else { - return context.DbContextOptions.UseMySQL(context.ConnectionString, mySQLOptionsAction); + return context.DbContextOptions.UseMySql(context.ConnectionString, mySQLOptionsAction); } } } diff --git a/framework/src/Volo.Abp.EntityFrameworkCore.MySQL/Volo/Abp/EntityFrameworkCore/AbpDbContextOptionsMySQLExtensions.cs b/framework/src/Volo.Abp.EntityFrameworkCore.MySQL/Volo/Abp/EntityFrameworkCore/AbpDbContextOptionsMySQLExtensions.cs index f71bc631ec..3e2dd911be 100644 --- a/framework/src/Volo.Abp.EntityFrameworkCore.MySQL/Volo/Abp/EntityFrameworkCore/AbpDbContextOptionsMySQLExtensions.cs +++ b/framework/src/Volo.Abp.EntityFrameworkCore.MySQL/Volo/Abp/EntityFrameworkCore/AbpDbContextOptionsMySQLExtensions.cs @@ -1,6 +1,6 @@ using JetBrains.Annotations; -using MySql.Data.EntityFrameworkCore.Infraestructure; using System; +using Microsoft.EntityFrameworkCore.Infrastructure; namespace Volo.Abp.EntityFrameworkCore { @@ -8,7 +8,7 @@ namespace Volo.Abp.EntityFrameworkCore { public static void UseMySQL( [NotNull] this AbpDbContextOptions options, - [CanBeNull] Action mySQLOptionsAction = null) + [CanBeNull] Action mySQLOptionsAction = null) { options.Configure(context => { @@ -18,7 +18,7 @@ namespace Volo.Abp.EntityFrameworkCore public static void UseMySQL( [NotNull] this AbpDbContextOptions options, - [CanBeNull] Action mySQLOptionsAction = null) + [CanBeNull] Action mySQLOptionsAction = null) where TDbContext : AbpDbContext { options.Configure(context => diff --git a/framework/src/Volo.Abp.EventBus.RabbitMQ/Volo/Abp/EventBus/RabbitMq/AbpEventBusRabbitMqModule.cs b/framework/src/Volo.Abp.EventBus.RabbitMQ/Volo/Abp/EventBus/RabbitMq/AbpEventBusRabbitMqModule.cs index 4fc74bddec..f13f1ae2fe 100644 --- a/framework/src/Volo.Abp.EventBus.RabbitMQ/Volo/Abp/EventBus/RabbitMq/AbpEventBusRabbitMqModule.cs +++ b/framework/src/Volo.Abp.EventBus.RabbitMQ/Volo/Abp/EventBus/RabbitMq/AbpEventBusRabbitMqModule.cs @@ -15,5 +15,13 @@ namespace Volo.Abp.EventBus.RabbitMq Configure(configuration.GetSection("RabbitMQ:EventBus")); } + + public override void OnApplicationInitialization(ApplicationInitializationContext context) + { + context + .ServiceProvider + .GetRequiredService() + .Initialize(); + } } } diff --git a/framework/src/Volo.Abp.EventBus.RabbitMQ/Volo/Abp/EventBus/RabbitMq/RabbitMqDistributedEventBus.cs b/framework/src/Volo.Abp.EventBus.RabbitMQ/Volo/Abp/EventBus/RabbitMq/RabbitMqDistributedEventBus.cs index a8dd36f913..d26e489afe 100644 --- a/framework/src/Volo.Abp.EventBus.RabbitMQ/Volo/Abp/EventBus/RabbitMq/RabbitMqDistributedEventBus.cs +++ b/framework/src/Volo.Abp.EventBus.RabbitMQ/Volo/Abp/EventBus/RabbitMq/RabbitMqDistributedEventBus.cs @@ -30,7 +30,7 @@ namespace Volo.Abp.EventBus.RabbitMq protected ConcurrentDictionary> HandlerFactories { get; } protected ConcurrentDictionary EventTypes { get; } protected IRabbitMqMessageConsumerFactory MessageConsumerFactory { get; } - protected IRabbitMqMessageConsumer Consumer { get; } + protected IRabbitMqMessageConsumer Consumer { get; private set; } public RabbitMqDistributedEventBus( IOptions options, @@ -50,18 +50,23 @@ namespace Volo.Abp.EventBus.RabbitMq HandlerFactories = new ConcurrentDictionary>(); EventTypes = new ConcurrentDictionary(); + Initialize(); + } + + public void Initialize() + { Consumer = MessageConsumerFactory.Create( new ExchangeDeclareConfiguration( - RabbitMqEventBusOptions.ExchangeName, + RabbitMqEventBusOptions.ExchangeName, type: "direct", durable: true - ), + ), new QueueDeclareConfiguration( RabbitMqEventBusOptions.ClientName, durable: true, exclusive: false, autoDelete: false - ), + ), RabbitMqEventBusOptions.ConnectionName ); diff --git a/framework/src/Volo.Abp.Features/Volo.Abp.Features.csproj b/framework/src/Volo.Abp.Features/Volo.Abp.Features.csproj new file mode 100644 index 0000000000..fc59bfaa9f --- /dev/null +++ b/framework/src/Volo.Abp.Features/Volo.Abp.Features.csproj @@ -0,0 +1,22 @@ + + + + + + netstandard2.0 + Volo.Abp.Features + Volo.Abp.Features + $(AssetTargetFallback);portable-net45+win8+wp8+wpa81; + false + false + false + + + + + + + + + + diff --git a/framework/src/Volo.Abp.Features/Volo/Abp/Features/AbpFeaturesModule.cs b/framework/src/Volo.Abp.Features/Volo/Abp/Features/AbpFeaturesModule.cs new file mode 100644 index 0000000000..fed872890c --- /dev/null +++ b/framework/src/Volo.Abp.Features/Volo/Abp/Features/AbpFeaturesModule.cs @@ -0,0 +1,52 @@ +using Microsoft.Extensions.DependencyInjection; +using System; +using System.Collections.Generic; +using Volo.Abp.Localization; +using Volo.Abp.Modularity; +using Volo.Abp.MultiTenancy; +using Volo.Abp.Validation; + +namespace Volo.Abp.Features +{ + [DependsOn( + typeof(AbpLocalizationAbstractionsModule), + typeof(AbpMultiTenancyAbstractionsModule), + typeof(AbpValidationModule) + )] + public class AbpFeaturesModule : AbpModule + { + public override void PreConfigureServices(ServiceConfigurationContext context) + { + context.Services.OnRegistred(FeatureInterceptorRegistrar.RegisterIfNeeded); + AutoAddDefinitionProviders(context.Services); + } + + public override void ConfigureServices(ServiceConfigurationContext context) + { + context.Services.Configure(options => + { + options.ValueProviders.Add(); + options.ValueProviders.Add(); + options.ValueProviders.Add(); + }); + } + + private static void AutoAddDefinitionProviders(IServiceCollection services) + { + var definitionProviders = new List(); + + services.OnRegistred(context => + { + if (typeof(IFeatureDefinitionProvider).IsAssignableFrom(context.ImplementationType)) + { + definitionProviders.Add(context.ImplementationType); + } + }); + + services.Configure(options => + { + options.DefinitionProviders.AddIfNotContains(definitionProviders); + }); + } + } +} diff --git a/framework/src/Volo.Abp.Features/Volo/Abp/Features/DefaultValueSettingValueProvider.cs b/framework/src/Volo.Abp.Features/Volo/Abp/Features/DefaultValueSettingValueProvider.cs new file mode 100644 index 0000000000..275be89af2 --- /dev/null +++ b/framework/src/Volo.Abp.Features/Volo/Abp/Features/DefaultValueSettingValueProvider.cs @@ -0,0 +1,22 @@ +using System.Threading.Tasks; + +namespace Volo.Abp.Features +{ + public class DefaultValueFeatureValueProvider : FeatureValueProvider //TODO: Directly implement IFeatureValueProvider + { + public const string ProviderName = "Default"; + + public override string Name => ProviderName; + + public DefaultValueFeatureValueProvider(IFeatureStore settingStore) + : base(settingStore) + { + + } + + public override Task GetOrNullAsync(FeatureDefinition setting) + { + return Task.FromResult(setting.DefaultValue); + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Features/Volo/Abp/Features/DisableFeatureCheckAttribute.cs b/framework/src/Volo.Abp.Features/Volo/Abp/Features/DisableFeatureCheckAttribute.cs new file mode 100644 index 0000000000..194f0eaf90 --- /dev/null +++ b/framework/src/Volo.Abp.Features/Volo/Abp/Features/DisableFeatureCheckAttribute.cs @@ -0,0 +1,10 @@ +using System; + +namespace Volo.Abp.Features +{ + [AttributeUsage(AttributeTargets.Method)] + public class DisableFeatureCheckAttribute : Attribute + { + + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Features/Volo/Abp/Features/EditionFeatureValueProvider.cs b/framework/src/Volo.Abp.Features/Volo/Abp/Features/EditionFeatureValueProvider.cs new file mode 100644 index 0000000000..8ce1332ecb --- /dev/null +++ b/framework/src/Volo.Abp.Features/Volo/Abp/Features/EditionFeatureValueProvider.cs @@ -0,0 +1,32 @@ +using System.Security.Principal; +using System.Threading.Tasks; +using Volo.Abp.Security.Claims; + +namespace Volo.Abp.Features +{ + public class EditionFeatureValueProvider : FeatureValueProvider + { + public const string ProviderName = "Edition"; + + public override string Name => ProviderName; + + protected ICurrentPrincipalAccessor PrincipalAccessor; + + public EditionFeatureValueProvider(IFeatureStore featureStore, ICurrentPrincipalAccessor principalAccessor) + : base(featureStore) + { + PrincipalAccessor = principalAccessor; + } + + public override async Task GetOrNullAsync(FeatureDefinition feature) + { + var editionId = PrincipalAccessor.Principal.FindEditionId(); + if (editionId == null) + { + return null; + } + + return await FeatureStore.GetOrNullAsync(feature.Name, Name, editionId.Value.ToString("N")); + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureChecker.cs b/framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureChecker.cs new file mode 100644 index 0000000000..bc9f8565ed --- /dev/null +++ b/framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureChecker.cs @@ -0,0 +1,68 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; + +namespace Volo.Abp.Features +{ + public class FeatureChecker : FeatureCheckerBase + { + protected FeatureOptions Options { get; } + protected IServiceProvider ServiceProvider { get; } + protected IFeatureDefinitionManager FeatureDefinitionManager { get; } + protected List Providers => _providers.Value; + + private readonly Lazy> _providers; + + public FeatureChecker( + IOptions options, + IServiceProvider serviceProvider, + IFeatureDefinitionManager featureDefinitionManager) + { + ServiceProvider = serviceProvider; + FeatureDefinitionManager = featureDefinitionManager; + + Options = options.Value; + + _providers = new Lazy>( + () => Options + .ValueProviders + .Select(type => ServiceProvider.GetRequiredService(type) as IFeatureValueProvider) + .ToList(), + true + ); + } + + public override async Task GetOrNullAsync(string name) + { + var featureDefinition = FeatureDefinitionManager.Get(name); + var providers = Enumerable + .Reverse(Providers); + + if (featureDefinition.AllowedProviders.Any()) + { + providers = providers.Where(p => featureDefinition.AllowedProviders.Contains(p.Name)); + } + + return await GetOrNullValueFromProvidersAsync(providers, featureDefinition); + } + + protected virtual async Task GetOrNullValueFromProvidersAsync( + IEnumerable providers, + FeatureDefinition feature) + { + foreach (var provider in providers) + { + var value = await provider.GetOrNullAsync(feature); + if (value != null) + { + return value; + } + } + + return null; + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureCheckerBase.cs b/framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureCheckerBase.cs new file mode 100644 index 0000000000..a24e0901f7 --- /dev/null +++ b/framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureCheckerBase.cs @@ -0,0 +1,32 @@ +using System; +using System.Threading.Tasks; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.Features +{ + public abstract class FeatureCheckerBase : IFeatureChecker, ITransientDependency + { + public abstract Task GetOrNullAsync(string name); + + public virtual async Task IsEnabledAsync(string name) + { + var value = await GetOrNullAsync(name); + if (value == null) + { + return false; + } + + try + { + return bool.Parse(value); + } + catch (Exception ex) + { + throw new AbpException( + $"The value '{value}' for the feature '{name}' should be a boolean, but was not!", + ex + ); + } + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureCheckerExtensions.cs b/framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureCheckerExtensions.cs new file mode 100644 index 0000000000..9d7f608c4b --- /dev/null +++ b/framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureCheckerExtensions.cs @@ -0,0 +1,143 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using JetBrains.Annotations; +using Volo.Abp.Authorization; +using Volo.Abp.Threading; + +namespace Volo.Abp.Features +{ + public static class FeatureCheckerExtensions + { + public static async Task GetAsync( + [NotNull] this IFeatureChecker featureChecker, + [NotNull] string name, + T defaultValue = default) + where T : struct + { + Check.NotNull(featureChecker, nameof(featureChecker)); + Check.NotNull(name, nameof(name)); + + var value = await featureChecker.GetOrNullAsync(name); + return value?.To() ?? defaultValue; + } + + public static string GetOrNull( + [NotNull] this IFeatureChecker featureChecker, + [NotNull] string name) + { + Check.NotNull(featureChecker, nameof(featureChecker)); + return AsyncHelper.RunSync(() => featureChecker.GetOrNullAsync(name)); + } + + public static T Get( + [NotNull] this IFeatureChecker featureChecker, + [NotNull] string name, + T defaultValue = default) + where T : struct + { + return AsyncHelper.RunSync(() => featureChecker.GetAsync(name, defaultValue)); + } + + public static bool IsEnabled( + [NotNull] this IFeatureChecker featureChecker, + [NotNull] string name) + { + return AsyncHelper.RunSync(() => featureChecker.IsEnabledAsync(name)); + } + + public static async Task IsEnabledAsync(this IFeatureChecker featureChecker, bool requiresAll, params string[] featureNames) + { + if (featureNames.IsNullOrEmpty()) + { + return true; + } + + if (requiresAll) + { + foreach (var featureName in featureNames) + { + if (!(await featureChecker.IsEnabledAsync(featureName))) + { + return false; + } + } + + return true; + } + + foreach (var featureName in featureNames) + { + if (await featureChecker.IsEnabledAsync(featureName)) + { + return true; + } + } + + return false; + } + + public static bool IsEnabled(this IFeatureChecker featureChecker, bool requiresAll, params string[] featureNames) + { + return AsyncHelper.RunSync(() => featureChecker.IsEnabledAsync(requiresAll, featureNames)); + } + + public static async Task CheckEnabledAsync(this IFeatureChecker featureChecker, string featureName) + { + if (!(await featureChecker.IsEnabledAsync(featureName))) + { + throw new AbpAuthorizationException("Feature is not enabled: " + featureName); + } + } + + public static void CheckEnabled(this IFeatureChecker featureChecker, string featureName) + { + if (!featureChecker.IsEnabled(featureName)) + { + throw new AbpAuthorizationException("Feature is not enabled: " + featureName); + } + } + + public static async Task CheckEnabledAsync(this IFeatureChecker featureChecker, bool requiresAll, params string[] featureNames) + { + if (featureNames.IsNullOrEmpty()) + { + return; + } + + if (requiresAll) + { + foreach (var featureName in featureNames) + { + if (!(await featureChecker.IsEnabledAsync(featureName))) + { + throw new AbpAuthorizationException( + "Required features are not enabled. All of these features must be enabled: " + + string.Join(", ", featureNames) + ); + } + } + } + else + { + foreach (var featureName in featureNames) + { + if (await featureChecker.IsEnabledAsync(featureName)) + { + return; + } + } + + throw new AbpAuthorizationException( + "Required features are not enabled. At least one of these features must be enabled: " + + string.Join(", ", featureNames) + ); + } + } + + public static void CheckEnabled(this IFeatureChecker featureChecker, bool requiresAll, params string[] featureNames) + { + AsyncHelper.RunSync(() => featureChecker.CheckEnabledAsync(requiresAll, featureNames)); + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureDefinition.cs b/framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureDefinition.cs new file mode 100644 index 0000000000..bd79480326 --- /dev/null +++ b/framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureDefinition.cs @@ -0,0 +1,178 @@ +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using JetBrains.Annotations; +using Volo.Abp.Localization; +using Volo.Abp.Validation.StringValues; + +namespace Volo.Abp.Features +{ + public class FeatureDefinition + { + /// + /// Unique name of the feature. + /// + [NotNull] + public string Name { get; } + + [NotNull] + public ILocalizableString DisplayName + { + get => _displayName; + set => _displayName = Check.NotNull(value, nameof(value)); + } + private ILocalizableString _displayName; + + [CanBeNull] + public ILocalizableString Description { get; set; } + + /// + /// Parent of this feature, if one exists. + /// If set, this feature can be enabled only if the parent is enabled. + /// + [CanBeNull] + public FeatureDefinition Parent { get; private set; } + + /// + /// List of child features. + /// + public IReadOnlyList Children => _children.ToImmutableList(); + private readonly List _children; + + /// + /// Default value of the feature. + /// + [CanBeNull] + public string DefaultValue { get; set; } + + /// + /// Can clients see this feature and it's value. + /// Default: true. + /// + public bool IsVisibleToClients { get; set; } + + /// + /// A list of allowed providers to get/set value of this feature. + /// An empty list indicates that all providers are allowed. + /// + [NotNull] + public List AllowedProviders { get; } + + /// + /// Gets/sets a key-value on the . + /// + /// Name of the property + /// + /// Returns the value in the dictionary by given . + /// Returns null if given is not present in the dictionary. + /// + [CanBeNull] + public object this[string name] + { + get => Properties.GetOrDefault(name); + set => Properties[name] = value; + } + + /// + /// Can be used to get/set custom properties for this feature. + /// + [NotNull] + public Dictionary Properties { get; } + + /// + /// Input type. + /// This can be used to prepare an input for changing this feature's value. + /// Default: . + /// + [CanBeNull] + public IStringValueType ValueType { get; set; } + + public FeatureDefinition( + string name, + string defaultValue = null, + ILocalizableString displayName = null, + ILocalizableString description = null, + IStringValueType valueType = null, + bool isVisibleToClients = true) + { + Name = name; + DefaultValue = defaultValue; + DisplayName = displayName ?? new FixedLocalizableString(name); + Description = description; + ValueType = valueType; + IsVisibleToClients = isVisibleToClients; + + Properties = new Dictionary(); + AllowedProviders = new List(); + _children = new List(); + } + + /// + /// Sets a property in the dictionary. + /// This is a shortcut for nested calls on this object. + /// + public virtual FeatureDefinition WithProperty(string key, object value) + { + Properties[key] = value; + return this; + } + + /// + /// Sets a property in the dictionary. + /// This is a shortcut for nested calls on this object. + /// + public virtual FeatureDefinition WithProviders(params string[] providers) + { + if (!providers.IsNullOrEmpty()) + { + AllowedProviders.AddRange(providers); + } + + return this; + } + + /// + /// Adds a child feature. + /// + /// Returns a newly created child feature + public FeatureDefinition CreateChild( + string name, + string defaultValue = null, + ILocalizableString displayName = null, + ILocalizableString description = null, + IStringValueType valueType = null, + bool isVisibleToClients = true) + { + var feature = new FeatureDefinition( + name, + defaultValue, + displayName, + description, + valueType, + isVisibleToClients) + { + Parent = this + }; + + _children.Add(feature); + return feature; + } + + public void RemoveChild(string name) + { + var featureToRemove = _children.FirstOrDefault(f => f.Name == name); + if (featureToRemove == null) + { + throw new AbpException($"Could not find a feature named '{name}' in the Children of this feature '{Name}'."); + } + + featureToRemove.Parent = null; + _children.Remove(featureToRemove); + } + + public override string ToString() + { + return $"[{nameof(FeatureDefinition)}: {Name}]"; + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureDefinitionContext.cs b/framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureDefinitionContext.cs new file mode 100644 index 0000000000..be9c1beec9 --- /dev/null +++ b/framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureDefinitionContext.cs @@ -0,0 +1,51 @@ +using System.Collections.Generic; +using Volo.Abp.Localization; + +namespace Volo.Abp.Features +{ + public class FeatureDefinitionContext : IFeatureDefinitionContext + { + internal Dictionary Groups { get; } + + public FeatureDefinitionContext() + { + Groups = new Dictionary(); + } + + public FeatureGroupDefinition AddGroup(string name, ILocalizableString displayName = null) + { + Check.NotNull(name, nameof(name)); + + if (Groups.ContainsKey(name)) + { + throw new AbpException($"There is already an existing permission group with name: {name}"); + } + + return Groups[name] = new FeatureGroupDefinition(name, displayName); + } + + public FeatureGroupDefinition GetGroupOrNull(string name) + { + Check.NotNull(name, nameof(name)); + + if (!Groups.ContainsKey(name)) + { + return null; + } + + return Groups[name]; + } + + public void RemoveGroup(string name) + { + Check.NotNull(name, nameof(name)); + + if (!Groups.ContainsKey(name)) + { + throw new AbpException($"Undefined feature group: '{name}'."); + } + + Groups.Remove(name); + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureDefinitionManager.cs b/framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureDefinitionManager.cs new file mode 100644 index 0000000000..13d15ac95e --- /dev/null +++ b/framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureDefinitionManager.cs @@ -0,0 +1,117 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.Features +{ + public class FeatureDefinitionManager : IFeatureDefinitionManager, ISingletonDependency + { + protected IDictionary FeatureGroupDefinitions => _lazyFeatureGroupDefinitions.Value; + private readonly Lazy> _lazyFeatureGroupDefinitions; + + protected IDictionary FeatureDefinitions => _lazyFeatureDefinitions.Value; + private readonly Lazy> _lazyFeatureDefinitions; + + protected FeatureOptions Options { get; } + + private readonly IServiceProvider _serviceProvider; + + public FeatureDefinitionManager( + IOptions options, + IServiceProvider serviceProvider) + { + _serviceProvider = serviceProvider; + Options = options.Value; + + _lazyFeatureDefinitions = new Lazy>( + CreateFeatureDefinitions, + isThreadSafe: true + ); + + _lazyFeatureGroupDefinitions = new Lazy>( + CreateFeatureGroupDefinitions, + isThreadSafe:true + ); + } + + public virtual FeatureDefinition Get(string name) + { + Check.NotNull(name, nameof(name)); + + var feature = GetOrNull(name); + + if (feature == null) + { + throw new AbpException("Undefined feature: " + name); + } + + return feature; + } + + public virtual IReadOnlyList GetAll() + { + return FeatureDefinitions.Values.ToImmutableList(); + } + + public virtual FeatureDefinition GetOrNull(string name) + { + return FeatureDefinitions.GetOrDefault(name); + } + + protected virtual Dictionary CreateFeatureDefinitions() + { + var features = new Dictionary(); + + foreach (var groupDefinition in FeatureGroupDefinitions.Values) + { + foreach (var feature in groupDefinition.Features) + { + AddFeatureToDictionaryRecursively(features, feature); + } + } + + return features; + } + + protected virtual void AddFeatureToDictionaryRecursively( + Dictionary features, + FeatureDefinition feature) + { + if (features.ContainsKey(feature.Name)) + { + throw new AbpException("Duplicate feature name: " + feature.Name); + } + + features[feature.Name] = feature; + + foreach (var child in feature.Children) + { + AddFeatureToDictionaryRecursively(features, child); + } + } + + protected virtual Dictionary CreateFeatureGroupDefinitions() + { + var context = new FeatureDefinitionContext(); + + using (var scope = _serviceProvider.CreateScope()) + { + var providers = Options + .DefinitionProviders + .Select(p => scope.ServiceProvider.GetRequiredService(p) as IFeatureDefinitionProvider) + .ToList(); + + foreach (var provider in providers) + { + provider.Define(context); + } + } + + return context.Groups; + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureDefinitionProvider.cs b/framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureDefinitionProvider.cs new file mode 100644 index 0000000000..f3c7a7f9b0 --- /dev/null +++ b/framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureDefinitionProvider.cs @@ -0,0 +1,9 @@ +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.Features +{ + public abstract class FeatureDefinitionProvider : IFeatureDefinitionProvider, ISingletonDependency + { + public abstract void Define(IFeatureDefinitionContext context); + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureGroupDefinition.cs b/framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureGroupDefinition.cs new file mode 100644 index 0000000000..cba38070a7 --- /dev/null +++ b/framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureGroupDefinition.cs @@ -0,0 +1,111 @@ +using System.Collections.Generic; +using System.Collections.Immutable; +using Volo.Abp.Localization; +using Volo.Abp.Validation.StringValues; + +namespace Volo.Abp.Features +{ + public class FeatureGroupDefinition + { + /// + /// Unique name of the group. + /// + public string Name { get; } + + public Dictionary Properties { get; } + + public ILocalizableString DisplayName + { + get => _displayName; + set => _displayName = Check.NotNull(value, nameof(value)); + } + private ILocalizableString _displayName; + + public IReadOnlyList Features => _features.ToImmutableList(); + private readonly List _features; + + /// + /// Gets/sets a key-value on the . + /// + /// Name of the property + /// + /// Returns the value in the dictionary by given . + /// Returns null if given is not present in the dictionary. + /// + public object this[string name] + { + get => Properties.GetOrDefault(name); + set => Properties[name] = value; + } + + protected internal FeatureGroupDefinition( + string name, + ILocalizableString displayName = null) + { + Name = name; + DisplayName = displayName ?? new FixedLocalizableString(Name); + + Properties = new Dictionary(); + _features = new List(); + } + + public virtual FeatureDefinition AddFeature( + string name, + string defaultValue = null, + ILocalizableString displayName = null, + ILocalizableString description = null, + IStringValueType valueType = null, + bool isVisibleToClients = true) + { + var feature = new FeatureDefinition( + name, + defaultValue, + displayName, + description, + valueType, + isVisibleToClients + ); + + _features.Add(feature); + + return feature; + } + + public virtual List GetFeaturesWithChildren() + { + var features = new List(); + + foreach (var feature in _features) + { + AddFeatureToListRecursively(features, feature); + } + + return features; + } + + /// + /// Sets a property in the dictionary. + /// This is a shortcut for nested calls on this object. + /// + public virtual FeatureGroupDefinition WithProperty(string key, object value) + { + Properties[key] = value; + return this; + } + + private void AddFeatureToListRecursively(List features, FeatureDefinition feature) + { + features.Add(feature); + + foreach (var child in feature.Children) + { + AddFeatureToListRecursively(features, child); + } + } + + public override string ToString() + { + return $"[{nameof(FeatureGroupDefinition)} {Name}]"; + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureInterceptor.cs b/framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureInterceptor.cs new file mode 100644 index 0000000000..a0b15674f6 --- /dev/null +++ b/framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureInterceptor.cs @@ -0,0 +1,56 @@ +using System.Threading.Tasks; +using Volo.Abp.Aspects; +using Volo.Abp.DependencyInjection; +using Volo.Abp.DynamicProxy; +using Volo.Abp.Threading; + +namespace Volo.Abp.Features +{ + public class FeatureInterceptor : AbpInterceptor, ITransientDependency + { + private readonly IMethodInvocationFeatureCheckerService _methodInvocationFeatureCheckerService; + + public FeatureInterceptor( + IMethodInvocationFeatureCheckerService methodInvocationFeatureCheckerService) + { + _methodInvocationFeatureCheckerService = methodInvocationFeatureCheckerService; + } + + public override void Intercept(IAbpMethodInvocation invocation) + { + if (AbpCrossCuttingConcerns.IsApplied( + invocation.TargetObject, + AbpCrossCuttingConcerns.FeatureChecking)) + { + invocation.Proceed(); + return; + } + + AsyncHelper.RunSync(() => CheckFeaturesAsync(invocation)); + invocation.Proceed(); + } + + public override async Task InterceptAsync(IAbpMethodInvocation invocation) + { + if (AbpCrossCuttingConcerns.IsApplied( + invocation.TargetObject, + AbpCrossCuttingConcerns.FeatureChecking)) + { + await invocation.ProceedAsync(); + return; + } + + AsyncHelper.RunSync(() => CheckFeaturesAsync(invocation)); + await invocation.ProceedAsync(); + } + + protected virtual Task CheckFeaturesAsync(IAbpMethodInvocation invocation) + { + return _methodInvocationFeatureCheckerService.CheckAsync( + new MethodInvocationFeatureCheckerContext( + invocation.Method + ) + ); + } + } +} diff --git a/framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureInterceptorRegistrar.cs b/framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureInterceptorRegistrar.cs new file mode 100644 index 0000000000..8cb6cd7ea5 --- /dev/null +++ b/framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureInterceptorRegistrar.cs @@ -0,0 +1,36 @@ +using System; +using System.Linq; +using System.Reflection; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.Features +{ + public static class FeatureInterceptorRegistrar + { + public static void RegisterIfNeeded(IOnServiceRegistredContext context) + { + if (ShouldIntercept(context.ImplementationType)) + { + context.Interceptors.TryAdd(); + } + } + + private static bool ShouldIntercept(Type type) + { + return type.IsDefined(typeof(RequiresFeatureAttribute), true) || + AnyMethodHasRequiresFeatureAttribute(type); + } + + private static bool AnyMethodHasRequiresFeatureAttribute(Type implementationType) + { + return implementationType + .GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) + .Any(HasRequiresFeatureAttribute); + } + + private static bool HasRequiresFeatureAttribute(MemberInfo methodInfo) + { + return methodInfo.IsDefined(typeof(RequiresFeatureAttribute), true); + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureOptions.cs b/framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureOptions.cs new file mode 100644 index 0000000000..f988acf34c --- /dev/null +++ b/framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureOptions.cs @@ -0,0 +1,17 @@ +using Volo.Abp.Collections; + +namespace Volo.Abp.Features +{ + public class FeatureOptions + { + public ITypeList DefinitionProviders { get; } + + public ITypeList ValueProviders { get; } + + public FeatureOptions() + { + DefinitionProviders = new TypeList(); + ValueProviders = new TypeList(); + } + } +} diff --git a/framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureValue.cs b/framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureValue.cs new file mode 100644 index 0000000000..22b7b6ab89 --- /dev/null +++ b/framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureValue.cs @@ -0,0 +1,19 @@ +using System; + +namespace Volo.Abp.Features +{ + [Serializable] + public class FeatureValue : NameValue + { + public FeatureValue() + { + + } + + public FeatureValue(string name, string value) + { + Name = name; + Value = value; + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureValueProvider.cs b/framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureValueProvider.cs new file mode 100644 index 0000000000..7ef664e6ce --- /dev/null +++ b/framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureValueProvider.cs @@ -0,0 +1,19 @@ +using System.Threading.Tasks; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.Features +{ + public abstract class FeatureValueProvider : IFeatureValueProvider, ISingletonDependency //TODO: to transient? + { + public abstract string Name { get; } + + protected IFeatureStore FeatureStore { get; } + + protected FeatureValueProvider(IFeatureStore featureStore) + { + FeatureStore = featureStore; + } + + public abstract Task GetOrNullAsync(FeatureDefinition feature); + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Features/Volo/Abp/Features/IFeatureChecker.cs b/framework/src/Volo.Abp.Features/Volo/Abp/Features/IFeatureChecker.cs new file mode 100644 index 0000000000..2676506733 --- /dev/null +++ b/framework/src/Volo.Abp.Features/Volo/Abp/Features/IFeatureChecker.cs @@ -0,0 +1,12 @@ +using JetBrains.Annotations; +using System.Threading.Tasks; + +namespace Volo.Abp.Features +{ + public interface IFeatureChecker + { + Task GetOrNullAsync([NotNull] string name); + + Task IsEnabledAsync(string name); + } +} diff --git a/framework/src/Volo.Abp.Features/Volo/Abp/Features/IFeatureDefinitionContext.cs b/framework/src/Volo.Abp.Features/Volo/Abp/Features/IFeatureDefinitionContext.cs new file mode 100644 index 0000000000..03fc14c579 --- /dev/null +++ b/framework/src/Volo.Abp.Features/Volo/Abp/Features/IFeatureDefinitionContext.cs @@ -0,0 +1,14 @@ +using JetBrains.Annotations; +using Volo.Abp.Localization; + +namespace Volo.Abp.Features +{ + public interface IFeatureDefinitionContext + { + FeatureGroupDefinition AddGroup([NotNull] string name, ILocalizableString displayName = null); + + FeatureGroupDefinition GetGroupOrNull(string name); + + void RemoveGroup(string name); + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Features/Volo/Abp/Features/IFeatureDefinitionManager.cs b/framework/src/Volo.Abp.Features/Volo/Abp/Features/IFeatureDefinitionManager.cs new file mode 100644 index 0000000000..c0172d7bc3 --- /dev/null +++ b/framework/src/Volo.Abp.Features/Volo/Abp/Features/IFeatureDefinitionManager.cs @@ -0,0 +1,15 @@ +using System.Collections.Generic; +using JetBrains.Annotations; + +namespace Volo.Abp.Features +{ + public interface IFeatureDefinitionManager + { + [NotNull] + FeatureDefinition Get([NotNull] string name); + + IReadOnlyList GetAll(); + + FeatureDefinition GetOrNull(string name); + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Features/Volo/Abp/Features/IFeatureDefinitionProvider.cs b/framework/src/Volo.Abp.Features/Volo/Abp/Features/IFeatureDefinitionProvider.cs new file mode 100644 index 0000000000..30e2f6b9f7 --- /dev/null +++ b/framework/src/Volo.Abp.Features/Volo/Abp/Features/IFeatureDefinitionProvider.cs @@ -0,0 +1,7 @@ +namespace Volo.Abp.Features +{ + public interface IFeatureDefinitionProvider + { + void Define(IFeatureDefinitionContext context); + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Features/Volo/Abp/Features/IFeatureStore.cs b/framework/src/Volo.Abp.Features/Volo/Abp/Features/IFeatureStore.cs new file mode 100644 index 0000000000..58cce989c7 --- /dev/null +++ b/framework/src/Volo.Abp.Features/Volo/Abp/Features/IFeatureStore.cs @@ -0,0 +1,14 @@ +using System.Threading.Tasks; +using JetBrains.Annotations; + +namespace Volo.Abp.Features +{ + public interface IFeatureStore + { + Task GetOrNullAsync( + [NotNull] string name, + [CanBeNull] string providerName, + [CanBeNull] string providerKey + ); + } +} diff --git a/framework/src/Volo.Abp.Features/Volo/Abp/Features/IFeatureValueProvider.cs b/framework/src/Volo.Abp.Features/Volo/Abp/Features/IFeatureValueProvider.cs new file mode 100644 index 0000000000..67eee5228e --- /dev/null +++ b/framework/src/Volo.Abp.Features/Volo/Abp/Features/IFeatureValueProvider.cs @@ -0,0 +1,12 @@ +using System.Threading.Tasks; +using JetBrains.Annotations; + +namespace Volo.Abp.Features +{ + public interface IFeatureValueProvider + { + string Name { get; } + + Task GetOrNullAsync([NotNull] FeatureDefinition feature); + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Features/Volo/Abp/Features/IMethodInvocationFeatureCheckerService.cs b/framework/src/Volo.Abp.Features/Volo/Abp/Features/IMethodInvocationFeatureCheckerService.cs new file mode 100644 index 0000000000..7d6be55c47 --- /dev/null +++ b/framework/src/Volo.Abp.Features/Volo/Abp/Features/IMethodInvocationFeatureCheckerService.cs @@ -0,0 +1,11 @@ +using System.Threading.Tasks; + +namespace Volo.Abp.Features +{ + public interface IMethodInvocationFeatureCheckerService + { + Task CheckAsync( + MethodInvocationFeatureCheckerContext context + ); + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Features/Volo/Abp/Features/MethodInvocationFeatureCheckerContext.cs b/framework/src/Volo.Abp.Features/Volo/Abp/Features/MethodInvocationFeatureCheckerContext.cs new file mode 100644 index 0000000000..81bbecfe68 --- /dev/null +++ b/framework/src/Volo.Abp.Features/Volo/Abp/Features/MethodInvocationFeatureCheckerContext.cs @@ -0,0 +1,14 @@ +using System.Reflection; + +namespace Volo.Abp.Features +{ + public class MethodInvocationFeatureCheckerContext + { + public MethodInfo Method { get; } + + public MethodInvocationFeatureCheckerContext(MethodInfo method) + { + Method = method; + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Features/Volo/Abp/Features/MethodInvocationFeatureCheckerService.cs b/framework/src/Volo.Abp.Features/Volo/Abp/Features/MethodInvocationFeatureCheckerService.cs new file mode 100644 index 0000000000..da7becc61f --- /dev/null +++ b/framework/src/Volo.Abp.Features/Volo/Abp/Features/MethodInvocationFeatureCheckerService.cs @@ -0,0 +1,59 @@ +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Threading.Tasks; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.Features +{ + public class MethodInvocationFeatureCheckerService : IMethodInvocationFeatureCheckerService, ITransientDependency + { + private readonly IFeatureChecker _featureChecker; + + public MethodInvocationFeatureCheckerService( + IFeatureChecker featureChecker) + { + _featureChecker = featureChecker; + } + + public async Task CheckAsync(MethodInvocationFeatureCheckerContext context) + { + if (IsFeatureCheckDisabled(context)) + { + return; + } + + foreach (var requiresFeatureAttribute in GetRequiredFeatureAttributes(context.Method)) + { + await _featureChecker.CheckEnabledAsync(requiresFeatureAttribute.RequiresAll, requiresFeatureAttribute.Features); + } + } + + protected virtual bool IsFeatureCheckDisabled(MethodInvocationFeatureCheckerContext context) + { + return context.Method + .GetCustomAttributes(true) + .OfType() + .Any(); + } + + protected virtual IEnumerable GetRequiredFeatureAttributes(MethodInfo methodInfo) + { + var attributes = methodInfo + .GetCustomAttributes(true) + .OfType(); + + if (methodInfo.IsPublic) + { + attributes = attributes + .Union( + methodInfo.DeclaringType + .GetCustomAttributes(true) + .OfType() + ); + } + + return attributes; + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Features/Volo/Abp/Features/NullFeatureStore.cs b/framework/src/Volo.Abp.Features/Volo/Abp/Features/NullFeatureStore.cs new file mode 100644 index 0000000000..d9b839a664 --- /dev/null +++ b/framework/src/Volo.Abp.Features/Volo/Abp/Features/NullFeatureStore.cs @@ -0,0 +1,23 @@ +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.Features +{ + [Dependency(TryRegister = true)] + public class NullFeatureStore : IFeatureStore, ISingletonDependency + { + public ILogger Logger { get; set; } + + public NullFeatureStore() + { + Logger = NullLogger.Instance; + } + + public Task GetOrNullAsync(string name, string providerName, string providerKey) + { + return Task.FromResult((string) null); + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Features/Volo/Abp/Features/RequiresFeatureAttribute.cs b/framework/src/Volo.Abp.Features/Volo/Abp/Features/RequiresFeatureAttribute.cs new file mode 100644 index 0000000000..2f5b62c128 --- /dev/null +++ b/framework/src/Volo.Abp.Features/Volo/Abp/Features/RequiresFeatureAttribute.cs @@ -0,0 +1,33 @@ +using System; + +namespace Volo.Abp.Features +{ + /// + /// This attribute can be used on a class/method to declare that given class/method is available + /// only if required feature(s) are enabled. + /// + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)] + public class RequiresFeatureAttribute : Attribute + { + /// + /// A list of features to be checked if they are enabled. + /// + public string[] Features { get; } + + /// + /// If this property is set to true, all of the must be enabled. + /// If it's false, at least one of the must be enabled. + /// Default: false. + /// + public bool RequiresAll { get; set; } + + /// + /// Creates a new instance of class. + /// + /// A list of features to be checked if they are enabled + public RequiresFeatureAttribute(params string[] features) + { + Features = features ?? Array.Empty(); + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Features/Volo/Abp/Features/TenantFeatureValueProvider.cs b/framework/src/Volo.Abp.Features/Volo/Abp/Features/TenantFeatureValueProvider.cs new file mode 100644 index 0000000000..211174516c --- /dev/null +++ b/framework/src/Volo.Abp.Features/Volo/Abp/Features/TenantFeatureValueProvider.cs @@ -0,0 +1,25 @@ +using System.Threading.Tasks; +using Volo.Abp.MultiTenancy; + +namespace Volo.Abp.Features +{ + public class TenantFeatureValueProvider : FeatureValueProvider + { + public const string ProviderName = "Tenant"; + + public override string Name => ProviderName; + + protected ICurrentTenant CurrentTenant { get; } + + public TenantFeatureValueProvider(IFeatureStore featureStore, ICurrentTenant currentTenant) + : base(featureStore) + { + CurrentTenant = currentTenant; + } + + public override async Task GetOrNullAsync(FeatureDefinition feature) + { + return await FeatureStore.GetOrNullAsync(feature.Name, Name, CurrentTenant.Id?.ToString("N")); + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/DynamicHttpProxyInterceptor.cs b/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/DynamicHttpProxyInterceptor.cs index b108ca92f6..10d2c98126 100644 --- a/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/DynamicHttpProxyInterceptor.cs +++ b/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/DynamicHttpProxyInterceptor.cs @@ -35,7 +35,6 @@ namespace Volo.Abp.Http.Client.DynamicProxying protected IJsonSerializer JsonSerializer { get; } protected IRemoteServiceHttpClientAuthenticator ClientAuthenticator { get; } - public ILogger> Logger { get; set; } static DynamicHttpProxyInterceptor() diff --git a/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/AbpLocalizationModule.cs b/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/AbpLocalizationModule.cs index 3fcd90ac6d..a65e6863a8 100644 --- a/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/AbpLocalizationModule.cs +++ b/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/AbpLocalizationModule.cs @@ -27,11 +27,6 @@ namespace Volo.Abp.Localization .Add("en") .AddVirtualJson("/Localization/Resources/AbpValidation"); }); - - Configure(options => - { - options.DefinitionProviders.Add(); - }); } } } diff --git a/framework/src/Volo.Abp.MultiTenancy.Abstractions/Volo.Abp.MultiTenancy.Abstractions.csproj b/framework/src/Volo.Abp.MultiTenancy.Abstractions/Volo.Abp.MultiTenancy.Abstractions.csproj index 05291caa45..18660be71e 100644 --- a/framework/src/Volo.Abp.MultiTenancy.Abstractions/Volo.Abp.MultiTenancy.Abstractions.csproj +++ b/framework/src/Volo.Abp.MultiTenancy.Abstractions/Volo.Abp.MultiTenancy.Abstractions.csproj @@ -14,7 +14,6 @@ - diff --git a/framework/src/Volo.Abp.MultiTenancy.Abstractions/Volo/Abp/MultiTenancy/AbpMultiTenancyAbstractionsModule.cs b/framework/src/Volo.Abp.MultiTenancy.Abstractions/Volo/Abp/MultiTenancy/AbpMultiTenancyAbstractionsModule.cs index 58de6cdb99..5d33034993 100644 --- a/framework/src/Volo.Abp.MultiTenancy.Abstractions/Volo/Abp/MultiTenancy/AbpMultiTenancyAbstractionsModule.cs +++ b/framework/src/Volo.Abp.MultiTenancy.Abstractions/Volo/Abp/MultiTenancy/AbpMultiTenancyAbstractionsModule.cs @@ -1,10 +1,12 @@ using Volo.Abp.Data; using Volo.Abp.Modularity; +using Volo.Abp.Security; namespace Volo.Abp.MultiTenancy { [DependsOn( - typeof(AbpDataModule) + typeof(AbpDataModule), + typeof(AbpSecurityModule) )] public class AbpMultiTenancyAbstractionsModule : AbpModule //TODO: Rename to AbpMultiTenancyModule? { diff --git a/framework/src/Volo.Abp.Security/System/Security/Principal/AbpClaimsIdentityExtensions.cs b/framework/src/Volo.Abp.Security/System/Security/Principal/AbpClaimsIdentityExtensions.cs index 45d363a7a7..694fb7baa6 100644 --- a/framework/src/Volo.Abp.Security/System/Security/Principal/AbpClaimsIdentityExtensions.cs +++ b/framework/src/Volo.Abp.Security/System/Security/Principal/AbpClaimsIdentityExtensions.cs @@ -91,5 +91,33 @@ namespace System.Security.Principal return clientIdOrNull.Value; } + + public static Guid? FindEditionId([NotNull] this ClaimsPrincipal principal) + { + Check.NotNull(principal, nameof(principal)); + + var editionIdOrNull = principal.Claims?.FirstOrDefault(c => c.Type == AbpClaimTypes.EditionId); + if (editionIdOrNull == null || editionIdOrNull.Value.IsNullOrWhiteSpace()) + { + return null; + } + + return Guid.Parse(editionIdOrNull.Value); + } + + public static Guid? FindEditionId([NotNull] this IIdentity identity) + { + Check.NotNull(identity, nameof(identity)); + + var claimsIdentity = identity as ClaimsIdentity; + + var editionIdOrNull = claimsIdentity?.Claims?.FirstOrDefault(c => c.Type == AbpClaimTypes.EditionId); + if (editionIdOrNull == null || editionIdOrNull.Value.IsNullOrWhiteSpace()) + { + return null; + } + + return Guid.Parse(editionIdOrNull.Value); + } } } diff --git a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/AbpAuthorizationException.cs b/framework/src/Volo.Abp.Security/Volo/Abp/Authorization/AbpAuthorizationException.cs similarity index 100% rename from framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/AbpAuthorizationException.cs rename to framework/src/Volo.Abp.Security/Volo/Abp/Authorization/AbpAuthorizationException.cs diff --git a/framework/src/Volo.Abp.Security/Volo/Abp/Security/Claims/AbpClaimTypes.cs b/framework/src/Volo.Abp.Security/Volo/Abp/Security/Claims/AbpClaimTypes.cs index b4abf97080..d0b883e4f3 100644 --- a/framework/src/Volo.Abp.Security/Volo/Abp/Security/Claims/AbpClaimTypes.cs +++ b/framework/src/Volo.Abp.Security/Volo/Abp/Security/Claims/AbpClaimTypes.cs @@ -44,10 +44,16 @@ namespace Volo.Abp.Security.Claims public static string PhoneNumberVerified { get; set; } = "phone_number_verified"; /// - /// Default: "phone_number_verified". + /// Default: "tenantid". /// public static string TenantId { get; set; } = "tenantid"; + + /// + /// Default: "editionid". + /// + public static string EditionId { get; set; } = "editionid"; + /// /// Default: "client_id". /// diff --git a/framework/src/Volo.Abp.Settings/Volo/Abp/Settings/AbpSettingsModule.cs b/framework/src/Volo.Abp.Settings/Volo/Abp/Settings/AbpSettingsModule.cs index 8c711e12c2..1951ab61b5 100644 --- a/framework/src/Volo.Abp.Settings/Volo/Abp/Settings/AbpSettingsModule.cs +++ b/framework/src/Volo.Abp.Settings/Volo/Abp/Settings/AbpSettingsModule.cs @@ -1,8 +1,10 @@ -using Volo.Abp.Localization; +using System; +using System.Collections.Generic; +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.Localization; using Volo.Abp.Modularity; using Volo.Abp.MultiTenancy; using Volo.Abp.Security; -using Volo.Abp.Users; namespace Volo.Abp.Settings { @@ -13,6 +15,11 @@ namespace Volo.Abp.Settings )] public class AbpSettingsModule : AbpModule { + public override void PreConfigureServices(ServiceConfigurationContext context) + { + AutoAddDefinitionProviders(context.Services); + } + public override void ConfigureServices(ServiceConfigurationContext context) { Configure(options => @@ -23,5 +30,23 @@ namespace Volo.Abp.Settings options.ValueProviders.Add(); }); } + + private static void AutoAddDefinitionProviders(IServiceCollection services) + { + var definitionProviders = new List(); + + services.OnRegistred(context => + { + if (typeof(ISettingDefinitionProvider).IsAssignableFrom(context.ImplementationType)) + { + definitionProviders.Add(context.ImplementationType); + } + }); + + services.Configure(options => + { + options.DefinitionProviders.AddIfNotContains(definitionProviders); + }); + } } } diff --git a/framework/src/Volo.Abp.Settings/Volo/Abp/Settings/NullSettingStore.cs b/framework/src/Volo.Abp.Settings/Volo/Abp/Settings/NullSettingStore.cs index 705f428792..8699fae972 100644 --- a/framework/src/Volo.Abp.Settings/Volo/Abp/Settings/NullSettingStore.cs +++ b/framework/src/Volo.Abp.Settings/Volo/Abp/Settings/NullSettingStore.cs @@ -1,11 +1,11 @@ -using System.Collections.Generic; -using System.Threading.Tasks; +using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using Volo.Abp.DependencyInjection; namespace Volo.Abp.Settings { + [Dependency(TryRegister = true)] public class NullSettingStore : ISettingStore, ISingletonDependency { public ILogger Logger { get; set; } @@ -19,21 +19,5 @@ namespace Volo.Abp.Settings { return Task.FromResult((string) null); } - - public Task SetAsync(string name, string value, string providerName, string providerKey) - { - Logger.LogWarning($"Setting the value for {name} is not possible because current setting store is {nameof(NullSettingStore)}"); - return Task.CompletedTask; - } - - public Task> GetListAsync(string providerName, string providerKey) - { - return Task.FromResult(new List()); - } - - public Task DeleteAsync(string name, string providerName, string providerKey) - { - return Task.CompletedTask; - } } } \ No newline at end of file diff --git a/framework/src/Volo.Abp.Settings/Volo/Abp/Settings/SettingDefinition.cs b/framework/src/Volo.Abp.Settings/Volo/Abp/Settings/SettingDefinition.cs index 9f02389248..127c724dc1 100644 --- a/framework/src/Volo.Abp.Settings/Volo/Abp/Settings/SettingDefinition.cs +++ b/framework/src/Volo.Abp.Settings/Volo/Abp/Settings/SettingDefinition.cs @@ -40,7 +40,7 @@ namespace Volo.Abp.Settings /// A list of allowed providers to get/set value of this setting. /// An empty list indicates that all providers are allowed. /// - public List Providers { get; } + public List Providers { get; } //TODO: Rename to AllowedProviders /// /// Is this setting inherited from parent scopes. diff --git a/framework/src/Volo.Abp.Settings/Volo/Abp/Settings/SettingDefinitionManager.cs b/framework/src/Volo.Abp.Settings/Volo/Abp/Settings/SettingDefinitionManager.cs index 7241d3654d..387ea77d05 100644 --- a/framework/src/Volo.Abp.Settings/Volo/Abp/Settings/SettingDefinitionManager.cs +++ b/framework/src/Volo.Abp.Settings/Volo/Abp/Settings/SettingDefinitionManager.cs @@ -10,22 +10,19 @@ namespace Volo.Abp.Settings { public class SettingDefinitionManager : ISettingDefinitionManager, ISingletonDependency { - protected Lazy> Providers { get; } - protected Lazy> SettingDefinitions { get; } protected SettingOptions Options { get; } - private readonly IServiceProvider _serviceProvider; + protected IServiceProvider ServiceProvider { get; } public SettingDefinitionManager( IOptions options, IServiceProvider serviceProvider) { - _serviceProvider = serviceProvider; + ServiceProvider = serviceProvider; Options = options.Value; - Providers = new Lazy>(CreateSettingProviders, true); SettingDefinitions = new Lazy>(CreateSettingDefinitions, true); } @@ -53,21 +50,21 @@ namespace Volo.Abp.Settings return SettingDefinitions.Value.GetOrDefault(name); } - protected virtual List CreateSettingProviders() - { - return Options - .DefinitionProviders - .Select(p => _serviceProvider.GetRequiredService(p) as ISettingDefinitionProvider) - .ToList(); - } - protected virtual IDictionary CreateSettingDefinitions() { var settings = new Dictionary(); - foreach (var provider in Providers.Value) + using (var scope = ServiceProvider.CreateScope()) { - provider.Define(new SettingDefinitionContext(settings)); + var providers = Options + .DefinitionProviders + .Select(p => scope.ServiceProvider.GetRequiredService(p) as ISettingDefinitionProvider) + .ToList(); + + foreach (var provider in providers) + { + provider.Define(new SettingDefinitionContext(settings)); + } } return settings; diff --git a/framework/src/Volo.Abp.Settings/Volo/Abp/Settings/SettingDefinitionProvider.cs b/framework/src/Volo.Abp.Settings/Volo/Abp/Settings/SettingDefinitionProvider.cs index 04e6fc7eb2..8cd4b28003 100644 --- a/framework/src/Volo.Abp.Settings/Volo/Abp/Settings/SettingDefinitionProvider.cs +++ b/framework/src/Volo.Abp.Settings/Volo/Abp/Settings/SettingDefinitionProvider.cs @@ -2,7 +2,7 @@ namespace Volo.Abp.Settings { - public abstract class SettingDefinitionProvider : ISettingDefinitionProvider, ISingletonDependency + public abstract class SettingDefinitionProvider : ISettingDefinitionProvider, ITransientDependency { public abstract void Define(ISettingDefinitionContext context); } diff --git a/framework/src/Volo.Abp.Settings/Volo/Abp/Settings/SettingProvider.cs b/framework/src/Volo.Abp.Settings/Volo/Abp/Settings/SettingProvider.cs index 63abadae12..3629c9eadb 100644 --- a/framework/src/Volo.Abp.Settings/Volo/Abp/Settings/SettingProvider.cs +++ b/framework/src/Volo.Abp.Settings/Volo/Abp/Settings/SettingProvider.cs @@ -29,7 +29,7 @@ namespace Volo.Abp.Settings Providers = new Lazy>( () => Options .ValueProviders - .Select(c => serviceProvider.GetRequiredService(c) as ISettingValueProvider) + .Select(type => serviceProvider.GetRequiredService(type) as ISettingValueProvider) .ToList(), true ); diff --git a/framework/src/Volo.Abp.Settings/Volo/Abp/Settings/SettingValue.cs b/framework/src/Volo.Abp.Settings/Volo/Abp/Settings/SettingValue.cs index d7f48081dd..b03a5159c8 100644 --- a/framework/src/Volo.Abp.Settings/Volo/Abp/Settings/SettingValue.cs +++ b/framework/src/Volo.Abp.Settings/Volo/Abp/Settings/SettingValue.cs @@ -5,17 +5,11 @@ namespace Volo.Abp.Settings [Serializable] public class SettingValue : NameValue { - /// - /// Creates a new . - /// public SettingValue() { } - /// - /// Creates a new . - /// public SettingValue(string name, string value) { Name = name; diff --git a/framework/src/Volo.Abp.Settings/Volo/Abp/Settings/SettingValueProvider.cs b/framework/src/Volo.Abp.Settings/Volo/Abp/Settings/SettingValueProvider.cs index 5173f80540..0a383a3e3b 100644 --- a/framework/src/Volo.Abp.Settings/Volo/Abp/Settings/SettingValueProvider.cs +++ b/framework/src/Volo.Abp.Settings/Volo/Abp/Settings/SettingValueProvider.cs @@ -3,7 +3,7 @@ using Volo.Abp.DependencyInjection; namespace Volo.Abp.Settings { - public abstract class SettingValueProvider : ISettingValueProvider, ISingletonDependency + public abstract class SettingValueProvider : ISettingValueProvider, ISingletonDependency //TODO: to transient? { public abstract string Name { get; } diff --git a/framework/src/Volo.Abp.Threading/Volo/Abp/Threading/NullCancellationTokenProvider.cs b/framework/src/Volo.Abp.Threading/Volo/Abp/Threading/NullCancellationTokenProvider.cs index 1d02ecb1e9..ab6320546c 100644 --- a/framework/src/Volo.Abp.Threading/Volo/Abp/Threading/NullCancellationTokenProvider.cs +++ b/framework/src/Volo.Abp.Threading/Volo/Abp/Threading/NullCancellationTokenProvider.cs @@ -6,7 +6,7 @@ namespace Volo.Abp.Threading { public static NullCancellationTokenProvider Instance { get; } = new NullCancellationTokenProvider(); - public CancellationToken Token { get; } = default; + public CancellationToken Token { get; } = CancellationToken.None; private NullCancellationTokenProvider() { diff --git a/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/StringValues/AlwaysValidValueValidator.cs b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/StringValues/AlwaysValidValueValidator.cs new file mode 100644 index 0000000000..ec444bd62b --- /dev/null +++ b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/StringValues/AlwaysValidValueValidator.cs @@ -0,0 +1,14 @@ +using System; + +namespace Volo.Abp.Validation.StringValues +{ + [Serializable] + [ValueValidator("NULL")] + public class AlwaysValidValueValidator : ValueValidatorBase + { + public override bool IsValid(object value) + { + return true; + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/StringValues/BooleanValueValidator.cs b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/StringValues/BooleanValueValidator.cs new file mode 100644 index 0000000000..41c52894cd --- /dev/null +++ b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/StringValues/BooleanValueValidator.cs @@ -0,0 +1,24 @@ +using System; + +namespace Volo.Abp.Validation.StringValues +{ + [Serializable] + [ValueValidator("BOOLEAN")] + public class BooleanValueValidator : ValueValidatorBase + { + public override bool IsValid(object value) + { + if (value == null) + { + return false; + } + + if (value is bool) + { + return true; + } + + return bool.TryParse(value.ToString(), out _); + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/StringValues/FreeTextStringValueType.cs b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/StringValues/FreeTextStringValueType.cs new file mode 100644 index 0000000000..c02f617b48 --- /dev/null +++ b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/StringValues/FreeTextStringValueType.cs @@ -0,0 +1,19 @@ +using System; + +namespace Volo.Abp.Validation.StringValues +{ + [Serializable] + [StringValueType("FREE_TEXT")] + public class FreeTextStringValueType : StringValueTypeBase + { + public FreeTextStringValueType() + { + + } + + public FreeTextStringValueType(IValueValidator validator) + : base(validator) + { + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/StringValues/ISelectionStringValueItem.cs b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/StringValues/ISelectionStringValueItem.cs new file mode 100644 index 0000000000..1dd69158c7 --- /dev/null +++ b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/StringValues/ISelectionStringValueItem.cs @@ -0,0 +1,9 @@ +namespace Volo.Abp.Validation.StringValues +{ + public interface ISelectionStringValueItem + { + string Value { get; set; } + + LocalizableStringInfo DisplayText { get; set; } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/StringValues/ISelectionStringValueItemSource.cs b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/StringValues/ISelectionStringValueItemSource.cs new file mode 100644 index 0000000000..563503a05d --- /dev/null +++ b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/StringValues/ISelectionStringValueItemSource.cs @@ -0,0 +1,9 @@ +using System.Collections.Generic; + +namespace Volo.Abp.Validation.StringValues +{ + public interface ISelectionStringValueItemSource + { + ICollection Items { get; } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/StringValues/IStringValueType.cs b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/StringValues/IStringValueType.cs new file mode 100644 index 0000000000..674b6e55f9 --- /dev/null +++ b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/StringValues/IStringValueType.cs @@ -0,0 +1,18 @@ +using System.Collections.Generic; +using JetBrains.Annotations; + +namespace Volo.Abp.Validation.StringValues +{ + public interface IStringValueType + { + string Name { get; } + + [CanBeNull] + object this[string key] { get; set; } + + [NotNull] + Dictionary Properties { get; } + + IValueValidator Validator { get; set; } + } +} diff --git a/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/StringValues/IValueValidator.cs b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/StringValues/IValueValidator.cs new file mode 100644 index 0000000000..afcacb3e34 --- /dev/null +++ b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/StringValues/IValueValidator.cs @@ -0,0 +1,18 @@ +using System.Collections.Generic; +using JetBrains.Annotations; + +namespace Volo.Abp.Validation.StringValues +{ + public interface IValueValidator + { + string Name { get; } + + [CanBeNull] + object this[string key] { get; set; } + + [NotNull] + IDictionary Properties { get; } + + bool IsValid(object value); + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/StringValues/LocalizableSelectionStringValueItem.cs b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/StringValues/LocalizableSelectionStringValueItem.cs new file mode 100644 index 0000000000..716c9e0e1c --- /dev/null +++ b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/StringValues/LocalizableSelectionStringValueItem.cs @@ -0,0 +1,9 @@ +namespace Volo.Abp.Validation.StringValues +{ + public class LocalizableSelectionStringValueItem : ISelectionStringValueItem + { + public string Value { get; set; } + + public LocalizableStringInfo DisplayText { get; set; } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/StringValues/LocalizableStringInfo.cs b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/StringValues/LocalizableStringInfo.cs new file mode 100644 index 0000000000..f4359c967f --- /dev/null +++ b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/StringValues/LocalizableStringInfo.cs @@ -0,0 +1,15 @@ +namespace Volo.Abp.Validation.StringValues +{ + public class LocalizableStringInfo + { + public string ResourceName { get; } + + public string Name { get; } + + public LocalizableStringInfo(string resourceName, string name) + { + ResourceName = resourceName; + Name = name; + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/StringValues/NumericValueValidator.cs b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/StringValues/NumericValueValidator.cs new file mode 100644 index 0000000000..e1b1972258 --- /dev/null +++ b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/StringValues/NumericValueValidator.cs @@ -0,0 +1,61 @@ +using System; + +namespace Volo.Abp.Validation.StringValues +{ + [Serializable] + [ValueValidator("NUMERIC")] + public class NumericValueValidator : ValueValidatorBase + { + public int MinValue + { + get => (this["MinValue"] ?? "0").To(); + set => this["MinValue"] = value; + } + + public int MaxValue + { + get => (this["MaxValue"] ?? "0").To(); + set => this["MaxValue"] = value; + } + + public NumericValueValidator() + { + + } + + public NumericValueValidator(int minValue = int.MinValue, int maxValue = int.MaxValue) + { + MinValue = minValue; + MaxValue = maxValue; + } + + public override bool IsValid(object value) + { + if (value == null) + { + return false; + } + + if (value is int) + { + return IsValidInternal((int)value); + } + + if (value is string) + { + int intValue; + if (int.TryParse(value as string, out intValue)) + { + return IsValidInternal(intValue); + } + } + + return false; + } + + protected virtual bool IsValidInternal(int value) + { + return value.IsBetween(MinValue, MaxValue); + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/StringValues/SelectionStringValueType.cs b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/StringValues/SelectionStringValueType.cs new file mode 100644 index 0000000000..e2d4ecbc99 --- /dev/null +++ b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/StringValues/SelectionStringValueType.cs @@ -0,0 +1,22 @@ +using System; + +namespace Volo.Abp.Validation.StringValues +{ + [Serializable] + [StringValueType("SELECTION")] + public class SelectionStringValueType : StringValueTypeBase + { + public ISelectionStringValueItemSource ItemSource { get; set; } + + public SelectionStringValueType() + { + + } + + public SelectionStringValueType(IValueValidator validator) + : base(validator) + { + + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/StringValues/StaticSelectionStringValueItemSource.cs b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/StringValues/StaticSelectionStringValueItemSource.cs new file mode 100644 index 0000000000..de0fed9879 --- /dev/null +++ b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/StringValues/StaticSelectionStringValueItemSource.cs @@ -0,0 +1,14 @@ +using System.Collections.Generic; + +namespace Volo.Abp.Validation.StringValues +{ + public class StaticSelectionStringValueItemSource : ISelectionStringValueItemSource + { + public ICollection Items { get; } + + public StaticSelectionStringValueItemSource(params ISelectionStringValueItem[] items) + { + Items = Check.NotNullOrEmpty(items, nameof(items)); + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/StringValues/StringValueTypeAttribute.cs b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/StringValues/StringValueTypeAttribute.cs new file mode 100644 index 0000000000..5fa8bef1ae --- /dev/null +++ b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/StringValues/StringValueTypeAttribute.cs @@ -0,0 +1,27 @@ +using System; +using System.Linq; +using System.Reflection; + +namespace Volo.Abp.Validation.StringValues +{ + [AttributeUsage(AttributeTargets.Class)] + public class StringValueTypeAttribute : Attribute + { + public string Name { get; set; } + + public StringValueTypeAttribute(string name) + { + Name = name; + } + + public static string GetName(Type type) + { + if (type.IsDefined(typeof(StringValueTypeAttribute))) + { + return type.GetCustomAttributes(typeof(StringValueTypeAttribute)).Cast().First().Name; + } + + return type.Name; + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/StringValues/StringValueTypeBase.cs b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/StringValues/StringValueTypeBase.cs new file mode 100644 index 0000000000..c6fd80fc13 --- /dev/null +++ b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/StringValues/StringValueTypeBase.cs @@ -0,0 +1,31 @@ +using System.Collections.Generic; + +namespace Volo.Abp.Validation.StringValues +{ + public abstract class StringValueTypeBase : IStringValueType + { + public virtual string Name => ValueValidatorAttribute.GetName(GetType()); + + public object this[string key] + { + get => Properties.GetOrDefault(key); + set => Properties[key] = value; + } + + public Dictionary Properties { get; } + + public IValueValidator Validator { get; set; } + + protected StringValueTypeBase() + : this(new AlwaysValidValueValidator()) + { + + } + + protected StringValueTypeBase(IValueValidator validator) + { + Validator = validator; + Properties = new Dictionary(); + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/StringValues/StringValueValidator.cs b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/StringValues/StringValueValidator.cs new file mode 100644 index 0000000000..3edc176b0c --- /dev/null +++ b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/StringValues/StringValueValidator.cs @@ -0,0 +1,79 @@ +using System; +using System.Text.RegularExpressions; + +namespace Volo.Abp.Validation.StringValues +{ + [Serializable] + [ValueValidator("STRING")] + public class StringValueValidator : ValueValidatorBase + { + public bool AllowNull + { + get => (this["AllowNull"] ?? "false").To(); + set => this["AllowNull"] = value.ToString().ToLowerInvariant(); + } + + public int MinLength + { + get => (this["MinLength"] ?? "0").To(); + set => this["MinLength"] = value; + } + + public int MaxLength + { + get => (this["MaxLength"] ?? "0").To(); + set => this["MaxLength"] = value; + } + + public string RegularExpression + { + get => this["RegularExpression"] as string; + set => this["RegularExpression"] = value; + } + + public StringValueValidator() + { + + } + + public StringValueValidator(int minLength = 0, int maxLength = 0, string regularExpression = null, bool allowNull = false) + { + MinLength = minLength; + MaxLength = maxLength; + RegularExpression = regularExpression; + AllowNull = allowNull; + } + + public override bool IsValid(object value) + { + if (value == null) + { + return AllowNull; + } + + if (!(value is string)) + { + return false; + } + + var strValue = value as string; + + if (MinLength > 0 && strValue.Length < MinLength) + { + return false; + } + + if (MaxLength > 0 && strValue.Length > MaxLength) + { + return false; + } + + if (!RegularExpression.IsNullOrEmpty()) + { + return Regex.IsMatch(strValue, RegularExpression); + } + + return true; + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/StringValues/ToggleStringValueType.cs b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/StringValues/ToggleStringValueType.cs new file mode 100644 index 0000000000..a262630766 --- /dev/null +++ b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/StringValues/ToggleStringValueType.cs @@ -0,0 +1,21 @@ +using System; + +namespace Volo.Abp.Validation.StringValues +{ + [Serializable] + [StringValueType("TOGGLE")] + public class ToggleStringValueType : StringValueTypeBase + { + public ToggleStringValueType() + : this(new BooleanValueValidator()) + { + + } + + public ToggleStringValueType(IValueValidator validator) + : base(validator) + { + + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/StringValues/ValueValidatorAttribute.cs b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/StringValues/ValueValidatorAttribute.cs new file mode 100644 index 0000000000..6f40ad0651 --- /dev/null +++ b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/StringValues/ValueValidatorAttribute.cs @@ -0,0 +1,27 @@ +using System; +using System.Linq; +using System.Reflection; + +namespace Volo.Abp.Validation.StringValues +{ + [AttributeUsage(AttributeTargets.Class)] + public class ValueValidatorAttribute : Attribute + { + public string Name { get; set; } + + public ValueValidatorAttribute(string name) + { + Name = name; + } + + public static string GetName(Type type) + { + if (type.IsDefined(typeof(ValueValidatorAttribute))) + { + return type.GetCustomAttributes(typeof(ValueValidatorAttribute)).Cast().First().Name; + } + + return type.Name; + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/StringValues/ValueValidatorBase.cs b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/StringValues/ValueValidatorBase.cs new file mode 100644 index 0000000000..ee91089fd4 --- /dev/null +++ b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/StringValues/ValueValidatorBase.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; + +namespace Volo.Abp.Validation.StringValues +{ + [Serializable] + public abstract class ValueValidatorBase : IValueValidator + { + public virtual string Name => ValueValidatorAttribute.GetName(GetType()); + + public object this[string key] + { + get => Properties.GetOrDefault(key); + set => Properties[key] = value; + } + + public IDictionary Properties { get; } + + protected ValueValidatorBase() + { + Properties = new Dictionary(); + } + + public abstract bool IsValid(object value); + } +} \ No newline at end of file diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcTestModule.cs b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcTestModule.cs index b079b38188..eb0fc1ef48 100644 --- a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcTestModule.cs +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcTestModule.cs @@ -6,7 +6,6 @@ using Volo.Abp.AspNetCore.Mvc.Authorization; using Volo.Abp.AspNetCore.Mvc.Localization; using Volo.Abp.AspNetCore.Mvc.Localization.Resource; using Volo.Abp.AspNetCore.TestBase; -using Volo.Abp.Authorization.Permissions; using Volo.Abp.Autofac; using Volo.Abp.Localization; using Volo.Abp.Localization.Resources.AbpValidation; @@ -57,11 +56,6 @@ namespace Volo.Abp.AspNetCore.Mvc }); }); - Configure(options => - { - options.DefinitionProviders.Add(); - }); - Configure(options => { options.FileSets.AddEmbedded(); diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Features/FakeFeatureChecker.cs b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Features/FakeFeatureChecker.cs new file mode 100644 index 0000000000..04e7fa908a --- /dev/null +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Features/FakeFeatureChecker.cs @@ -0,0 +1,27 @@ +using System; +using System.Threading.Tasks; +using Volo.Abp.Features; + +namespace Volo.Abp.AspNetCore.Mvc.Features +{ + public class FakeFeatureChecker : FeatureCheckerBase + { + public override Task GetOrNullAsync(string name) + { + return Task.FromResult(GetOrNull(name)); + } + + private static string GetOrNull(string name) + { + switch (name) + { + case "AllowedFeature": + return true.ToString(); + case "NotAllowedFeature": + return null; //or false, doesn't matter + } + + throw new ApplicationException($"Unknown feature: '{name}'"); + } + } +} diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Features/FeatureTestController.cs b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Features/FeatureTestController.cs new file mode 100644 index 0000000000..1d2549323d --- /dev/null +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Features/FeatureTestController.cs @@ -0,0 +1,33 @@ +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using Volo.Abp.Features; + +namespace Volo.Abp.AspNetCore.Mvc.Features +{ + [Route("api/feature-test")] + public class FeatureTestController : AbpController + { + [HttpGet] + [Route("allowed-feature")] + [RequiresFeature("AllowedFeature")] + public Task AllowedFeatureAsync() + { + return Task.CompletedTask; + } + + [HttpGet] + [Route("not-allowed-feature")] + [RequiresFeature("NotAllowedFeature")] + public void NotAllowedFeature() + { + + } + + [HttpGet] + [Route("no-feature")] + public int NoFeature() + { + return 42; + } + } +} diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Features/FeatureTestController_Tests.cs b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Features/FeatureTestController_Tests.cs new file mode 100644 index 0000000000..637dbe3d2b --- /dev/null +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Features/FeatureTestController_Tests.cs @@ -0,0 +1,34 @@ +using System.Net; +using System.Threading.Tasks; +using Xunit; + +namespace Volo.Abp.AspNetCore.Mvc.Features +{ + public class FeatureTestController_Tests : AspNetCoreMvcTestBase + { + [Fact] + public async Task Should_Allow_Enabled_Features() + { + await GetResponseAsStringAsync( + "/api/feature-test/allowed-feature" + ); + } + + [Fact] + public async Task Should_Not_Allow_Not_Enabled_Features() + { + await GetResponseAsStringAsync( + "/api/feature-test/not-allowed-feature", + HttpStatusCode.Unauthorized + ); + } + + [Fact] + public async Task Should_Allow_Actions_With_No_Feature() + { + await GetResponseAsStringAsync( + "/api/feature-test/no-feature" + ); + } + } +} diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo.csproj b/framework/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo.csproj index e6843fd726..3b6d429dd0 100644 --- a/framework/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo.csproj +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo.csproj @@ -13,7 +13,7 @@ true true false - true + diff --git a/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/AbpAuthorizationTestModule.cs b/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/AbpAuthorizationTestModule.cs index 9b539e85be..51337ae871 100644 --- a/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/AbpAuthorizationTestModule.cs +++ b/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/AbpAuthorizationTestModule.cs @@ -1,5 +1,4 @@ using Microsoft.Extensions.DependencyInjection; -using Volo.Abp.Authorization.Permissions; using Volo.Abp.Authorization.TestServices; using Volo.Abp.Autofac; using Volo.Abp.Modularity; @@ -20,13 +19,5 @@ namespace Volo.Abp.Authorization } }); } - - public override void ConfigureServices(ServiceConfigurationContext context) - { - Configure(options => - { - options.DefinitionProviders.TryAdd(); - }); - } } } \ No newline at end of file diff --git a/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/Authorization_Tests.cs b/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/Authorization_Tests.cs index 7c04a9cf1b..fa069348cd 100644 --- a/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/Authorization_Tests.cs +++ b/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/Authorization_Tests.cs @@ -50,7 +50,7 @@ namespace Volo.Abp.Authorization [Fact] public void Should_Permission_Definition_GetGroup() { - _permissionDefinitionManager.GetGroups().Count.ShouldBe(2); + _permissionDefinitionManager.GetGroups().Count.ShouldBe(1); } } } \ No newline at end of file diff --git a/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/TestServices/AuthorizationTestPermissionDefinitionProvider.cs b/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/TestServices/AuthorizationTestPermissionDefinitionProvider.cs index fdfd77b986..bb28b4ea99 100644 --- a/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/TestServices/AuthorizationTestPermissionDefinitionProvider.cs +++ b/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/TestServices/AuthorizationTestPermissionDefinitionProvider.cs @@ -13,6 +13,8 @@ namespace Volo.Abp.Authorization.TestServices } PermissionGroupDefinition group = context.AddGroup("TestGroup"); group.AddPermission("MyAuthorizedService1"); + + context.RemoveGroup("TestGetGroup"); } } } diff --git a/framework/test/Volo.Abp.Caching.Tests/Volo/Abp/Caching/AbpCachingTestModule.cs b/framework/test/Volo.Abp.Caching.Tests/Volo/Abp/Caching/AbpCachingTestModule.cs index 32a954d430..0f790a03a3 100644 --- a/framework/test/Volo.Abp.Caching.Tests/Volo/Abp/Caching/AbpCachingTestModule.cs +++ b/framework/test/Volo.Abp.Caching.Tests/Volo/Abp/Caching/AbpCachingTestModule.cs @@ -13,13 +13,14 @@ namespace Volo.Abp.Caching { option.CacheConfigurators.Add(cacheName => { - if (cacheName == typeof(Sail.Testing.Caching.PersonCacheItem).FullName) + if (cacheName == CacheNameAttribute.GetCacheName(typeof(Sail.Testing.Caching.PersonCacheItem))) { return new DistributedCacheEntryOptions() { AbsoluteExpiration = DateTime.Parse("2099-01-01 12:00:00") }; } + return null; }); diff --git a/framework/test/Volo.Abp.Caching.Tests/Volo/Abp/Caching/DistributedCache_ConfigureOptions_Test.cs b/framework/test/Volo.Abp.Caching.Tests/Volo/Abp/Caching/DistributedCache_ConfigureOptions_Test.cs index 9531711932..711e1e0bce 100644 --- a/framework/test/Volo.Abp.Caching.Tests/Volo/Abp/Caching/DistributedCache_ConfigureOptions_Test.cs +++ b/framework/test/Volo.Abp.Caching.Tests/Volo/Abp/Caching/DistributedCache_ConfigureOptions_Test.cs @@ -1,9 +1,7 @@ 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; @@ -12,20 +10,11 @@ namespace Volo.Abp.Caching public class DistributedCache_ConfigureOptions_Test : AbpIntegratedTest { [Fact] - public async Task Configure_CacheOptions() + public void Configure_CacheOptions() { var personCache = GetRequiredService>(); - - 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] diff --git a/framework/test/Volo.Abp.Core.Tests/System/Collections/Generic/AbpListExtensions_Tests.cs b/framework/test/Volo.Abp.Core.Tests/System/Collections/Generic/AbpListExtensions_Tests.cs index cc65099a77..93e2ef5824 100644 --- a/framework/test/Volo.Abp.Core.Tests/System/Collections/Generic/AbpListExtensions_Tests.cs +++ b/framework/test/Volo.Abp.Core.Tests/System/Collections/Generic/AbpListExtensions_Tests.cs @@ -11,6 +11,29 @@ namespace System.Collections.Generic { var list = Enumerable.Range(1, 3).ToList(); + list.InsertAfter(2, 42); + + list.Count.ShouldBe(4); + list[0].ShouldBe(1); + list[1].ShouldBe(2); + list[2].ShouldBe(42); + list[3].ShouldBe(3); + + list.InsertAfter(3, 43); + + list.Count.ShouldBe(5); + list[0].ShouldBe(1); + list[1].ShouldBe(2); + list[2].ShouldBe(42); + list[3].ShouldBe(3); + list[4].ShouldBe(43); + } + + [Fact] + public void InsertAfter_With_Predicate() + { + var list = Enumerable.Range(1, 3).ToList(); + list.InsertAfter(i => i == 2, 42); list.Count.ShouldBe(4); @@ -30,7 +53,7 @@ namespace System.Collections.Generic } [Fact] - public void InsertAfter_Should_Insert_To_First_If_Not_Found() + public void InsertAfter_With_Predicate_Should_Insert_To_First_If_Not_Found() { var list = Enumerable.Range(1, 3).ToList(); @@ -48,6 +71,29 @@ namespace System.Collections.Generic { var list = Enumerable.Range(1, 3).ToList(); + list.InsertBefore(2, 42); + + list.Count.ShouldBe(4); + list[0].ShouldBe(1); + list[1].ShouldBe(42); + list[2].ShouldBe(2); + list[3].ShouldBe(3); + + list.InsertBefore(1, 43); + + list.Count.ShouldBe(5); + list[0].ShouldBe(43); + list[1].ShouldBe(1); + list[2].ShouldBe(42); + list[3].ShouldBe(2); + list[4].ShouldBe(3); + } + + [Fact] + public void InsertBefore_With_Predicate() + { + var list = Enumerable.Range(1, 3).ToList(); + list.InsertBefore(i => i == 2, 42); list.Count.ShouldBe(4); diff --git a/framework/test/Volo.Abp.Features.Tests/Volo.Abp.Features.Tests.csproj b/framework/test/Volo.Abp.Features.Tests/Volo.Abp.Features.Tests.csproj new file mode 100644 index 0000000000..3632aa2bf3 --- /dev/null +++ b/framework/test/Volo.Abp.Features.Tests/Volo.Abp.Features.Tests.csproj @@ -0,0 +1,22 @@ + + + + netcoreapp2.2 + latest + Volo.Abp.Features.Tests + Volo.Abp.Features.Tests + true + false + false + false + + + + + + + + + + + diff --git a/framework/test/Volo.Abp.Features.Tests/Volo/Abp/Features/AbpFeaturesTestModule.cs b/framework/test/Volo.Abp.Features.Tests/Volo/Abp/Features/AbpFeaturesTestModule.cs new file mode 100644 index 0000000000..7b0927b80d --- /dev/null +++ b/framework/test/Volo.Abp.Features.Tests/Volo/Abp/Features/AbpFeaturesTestModule.cs @@ -0,0 +1,18 @@ +using Volo.Abp.Autofac; +using Volo.Abp.Modularity; + +namespace Volo.Abp.Features +{ + [DependsOn( + typeof(AbpFeaturesModule), + typeof(AbpTestBaseModule), + typeof(AbpAutofacModule) + )] + public class AbpFeaturesTestModule : AbpModule + { + public override void ConfigureServices(ServiceConfigurationContext context) + { + + } + } +} diff --git a/framework/test/Volo.Abp.Features.Tests/Volo/Abp/Features/ClassFeatureTestService.cs b/framework/test/Volo.Abp.Features.Tests/Volo/Abp/Features/ClassFeatureTestService.cs new file mode 100644 index 0000000000..425f69337f --- /dev/null +++ b/framework/test/Volo.Abp.Features.Tests/Volo/Abp/Features/ClassFeatureTestService.cs @@ -0,0 +1,23 @@ +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.Features +{ + [RequiresFeature("BooleanTestFeature1")] + public class ClassFeatureTestService : ITransientDependency + { + /* Since this class is used with the class reference, + * need to virtual keywords, otherwise dynamic proxy can not work. + */ + + [RequiresFeature("BooleanTestFeature2")] + public virtual int Feature2() + { + return 42; + } + + public virtual void NoAdditionalFeature() + { + + } + } +} \ No newline at end of file diff --git a/framework/test/Volo.Abp.Features.Tests/Volo/Abp/Features/FeatureChecker_Tests.cs b/framework/test/Volo.Abp.Features.Tests/Volo/Abp/Features/FeatureChecker_Tests.cs new file mode 100644 index 0000000000..7ea01268a0 --- /dev/null +++ b/framework/test/Volo.Abp.Features.Tests/Volo/Abp/Features/FeatureChecker_Tests.cs @@ -0,0 +1,53 @@ +using Shouldly; +using System.Threading.Tasks; +using Volo.Abp.MultiTenancy; +using Xunit; + +namespace Volo.Abp.Features +{ + public class FeatureChecker_Tests : FeatureTestBase + { + private readonly IFeatureChecker _featureChecker; + private readonly ICurrentTenant _currentTenant; + + public FeatureChecker_Tests() + { + _featureChecker = GetRequiredService(); + _currentTenant = GetRequiredService(); + } + + [Fact] + public async Task IsEnabledAsync() + { + //Tenant is unknown + (await _featureChecker.IsEnabledAsync("BooleanTestFeature1")).ShouldBeFalse(); + + using (_currentTenant.Change(TestFeatureStore.Tenant1Id)) + { + (await _featureChecker.IsEnabledAsync("BooleanTestFeature1")).ShouldBeTrue(); + } + + using (_currentTenant.Change(TestFeatureStore.Tenant2Id)) + { + (await _featureChecker.IsEnabledAsync("BooleanTestFeature1")).ShouldBeFalse(); + } + } + + [Fact] + public async Task GetOrNullAsync() + { + //Tenant is unknown + (await _featureChecker.GetOrNullAsync("IntegerTestFeature1")).ShouldBe("1"); + + using (_currentTenant.Change(TestFeatureStore.Tenant1Id)) + { + (await _featureChecker.GetOrNullAsync("IntegerTestFeature1")).ShouldBe("1"); + } + + using (_currentTenant.Change(TestFeatureStore.Tenant2Id)) + { + (await _featureChecker.GetOrNullAsync("IntegerTestFeature1")).ShouldBe("34"); + } + } + } +} diff --git a/framework/test/Volo.Abp.Features.Tests/Volo/Abp/Features/FeatureDefinitionManager_Tests.cs b/framework/test/Volo.Abp.Features.Tests/Volo/Abp/Features/FeatureDefinitionManager_Tests.cs new file mode 100644 index 0000000000..5a4bbc9ea1 --- /dev/null +++ b/framework/test/Volo.Abp.Features.Tests/Volo/Abp/Features/FeatureDefinitionManager_Tests.cs @@ -0,0 +1,35 @@ +using Shouldly; +using Xunit; + +namespace Volo.Abp.Features +{ + public class FeatureDefinitionManager_Tests : FeatureTestBase + { + private readonly IFeatureDefinitionManager _featureDefinitionManager; + + public FeatureDefinitionManager_Tests() + { + _featureDefinitionManager = GetRequiredService(); + } + + [Fact] + public void Should_Get_Defined_Features() + { + _featureDefinitionManager.GetOrNull("BooleanTestFeature1").ShouldNotBeNull(); + _featureDefinitionManager.Get("BooleanTestFeature1").Name.ShouldBe("BooleanTestFeature1"); + + _featureDefinitionManager.GetOrNull("IntegerTestFeature1").ShouldNotBeNull(); + _featureDefinitionManager.Get("IntegerTestFeature1").Name.ShouldBe("IntegerTestFeature1"); + } + + [Fact] + public void Should_Not_Get_Undefined_Features() + { + _featureDefinitionManager.GetOrNull("UndefinedFeature").ShouldBeNull(); + Assert.Throws(() => + { + _featureDefinitionManager.Get("UndefinedFeature"); + }); + } + } +} diff --git a/framework/test/Volo.Abp.Features.Tests/Volo/Abp/Features/FeatureInterceptor_Tests.cs b/framework/test/Volo.Abp.Features.Tests/Volo/Abp/Features/FeatureInterceptor_Tests.cs new file mode 100644 index 0000000000..159e2ff241 --- /dev/null +++ b/framework/test/Volo.Abp.Features.Tests/Volo/Abp/Features/FeatureInterceptor_Tests.cs @@ -0,0 +1,76 @@ +using Shouldly; +using System; +using System.Threading.Tasks; +using Volo.Abp.Authorization; +using Volo.Abp.MultiTenancy; +using Xunit; + +namespace Volo.Abp.Features +{ + public class FeatureInterceptor_Tests : FeatureTestBase + { + private readonly ClassFeatureTestService _classFeatureTestService; + private readonly IMethodFeatureTestService _methodFeatureTestService; + private readonly ICurrentTenant _currentTenant; + + public FeatureInterceptor_Tests() + { + _classFeatureTestService = GetRequiredService(); + _methodFeatureTestService = GetRequiredService(); + _currentTenant = GetRequiredService(); + } + + [Theory] + [InlineData(null)] //Features were not enabled for null tenantid + [InlineData(TestFeatureStore.Tenant2IdValue)] //Features were not enabled for Tenant 2 + public async Task Should_Not_Allow_To_Method_Calls_If_Related_Features_Were_Not_Enabled(string tenantIdValue) + { + using (_currentTenant.Change(ParseNullableGuid(tenantIdValue))) + { + Assert.Throws(() => + { + _classFeatureTestService.NoAdditionalFeature(); + }); + + Assert.Throws(() => + { + _classFeatureTestService.Feature2(); + }); + + await Assert.ThrowsAsync(async () => + { + await _methodFeatureTestService.Feature1Async(); + }); + } + } + + [Fact] + public async Task Should_Allow_To_Method_Calls_If_Related_Features_Were_Enabled() + { + //Features were enabled for Tenant 1 + using (_currentTenant.Change(TestFeatureStore.Tenant1Id)) + { + _classFeatureTestService.NoAdditionalFeature(); + _classFeatureTestService.Feature2().ShouldBe(42); + (await _methodFeatureTestService.Feature1Async()).ShouldBe(42); + } + } + + [Theory] + [InlineData(null)] + [InlineData(TestFeatureStore.Tenant1IdValue)] + [InlineData(TestFeatureStore.Tenant2IdValue)] + public async Task Should_Allow_To_Method_Calls_For_Those_Have_No_RequiresFeature_Attributes(string tenantIdValue) + { + using (_currentTenant.Change(ParseNullableGuid(tenantIdValue))) + { + await _methodFeatureTestService.NonFeatureAsync(); + } + } + + private static Guid? ParseNullableGuid(string tenantIdValue) + { + return tenantIdValue == null ? (Guid?)null : new Guid(tenantIdValue); + } + } +} diff --git a/framework/test/Volo.Abp.Features.Tests/Volo/Abp/Features/FeatureTestBase.cs b/framework/test/Volo.Abp.Features.Tests/Volo/Abp/Features/FeatureTestBase.cs new file mode 100644 index 0000000000..b12aa9f526 --- /dev/null +++ b/framework/test/Volo.Abp.Features.Tests/Volo/Abp/Features/FeatureTestBase.cs @@ -0,0 +1,10 @@ +namespace Volo.Abp.Features +{ + public class FeatureTestBase : AbpIntegratedTest + { + protected override void SetAbpApplicationCreationOptions(AbpApplicationCreationOptions options) + { + options.UseAutofac(); + } + } +} diff --git a/framework/test/Volo.Abp.Features.Tests/Volo/Abp/Features/IMethodFeatureTestService.cs b/framework/test/Volo.Abp.Features.Tests/Volo/Abp/Features/IMethodFeatureTestService.cs new file mode 100644 index 0000000000..7bc16b7e04 --- /dev/null +++ b/framework/test/Volo.Abp.Features.Tests/Volo/Abp/Features/IMethodFeatureTestService.cs @@ -0,0 +1,11 @@ +using System.Threading.Tasks; + +namespace Volo.Abp.Features +{ + public interface IMethodFeatureTestService + { + Task Feature1Async(); + + Task NonFeatureAsync(); + } +} \ No newline at end of file diff --git a/framework/test/Volo.Abp.Features.Tests/Volo/Abp/Features/MethodFeatureTestService.cs b/framework/test/Volo.Abp.Features.Tests/Volo/Abp/Features/MethodFeatureTestService.cs new file mode 100644 index 0000000000..e627e0c5f1 --- /dev/null +++ b/framework/test/Volo.Abp.Features.Tests/Volo/Abp/Features/MethodFeatureTestService.cs @@ -0,0 +1,23 @@ +using System.Threading.Tasks; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.Features +{ + public class MethodFeatureTestService : ITransientDependency, IMethodFeatureTestService + { + /* Since this class is used over an interface (IMethodFeatureTestService), + * no need to virtual keywords, dynamic proxy can work. + */ + + [RequiresFeature("BooleanTestFeature1")] + public Task Feature1Async() + { + return Task.FromResult(42); + } + + public Task NonFeatureAsync() + { + return Task.CompletedTask; + } + } +} diff --git a/framework/test/Volo.Abp.Features.Tests/Volo/Abp/Features/TestFeatureDefinitionProvider.cs b/framework/test/Volo.Abp.Features.Tests/Volo/Abp/Features/TestFeatureDefinitionProvider.cs new file mode 100644 index 0000000000..0554120309 --- /dev/null +++ b/framework/test/Volo.Abp.Features.Tests/Volo/Abp/Features/TestFeatureDefinitionProvider.cs @@ -0,0 +1,13 @@ +namespace Volo.Abp.Features +{ + public class TestFeatureDefinitionProvider : FeatureDefinitionProvider + { + public override void Define(IFeatureDefinitionContext context) + { + var group = context.AddGroup("Test Group"); + group.AddFeature("BooleanTestFeature1"); + group.AddFeature("BooleanTestFeature2"); + group.AddFeature("IntegerTestFeature1", defaultValue: "1"); + } + } +} diff --git a/framework/test/Volo.Abp.Features.Tests/Volo/Abp/Features/TestFeatureStore.cs b/framework/test/Volo.Abp.Features.Tests/Volo/Abp/Features/TestFeatureStore.cs new file mode 100644 index 0000000000..87fef33a8d --- /dev/null +++ b/framework/test/Volo.Abp.Features.Tests/Volo/Abp/Features/TestFeatureStore.cs @@ -0,0 +1,56 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.Features +{ + public class TestFeatureStore : IFeatureStore, ISingletonDependency + { + public const string Tenant1IdValue = "f460fcf7-f944-469a-967b-3b2463323dfe"; + public const string Tenant2IdValue = "e10428ad-4608-4c34-a304-6f82502156f2"; + + public static Guid Tenant1Id = new Guid(Tenant1IdValue); + public static Guid Tenant2Id = new Guid(Tenant2IdValue); + + private readonly List _settingRecords; + + public TestFeatureStore() + { + _settingRecords = new List + { + new SettingRecord("BooleanTestFeature1", TenantFeatureValueProvider.ProviderName, Tenant1Id.ToString(), "true"), + new SettingRecord("BooleanTestFeature2", TenantFeatureValueProvider.ProviderName, Tenant1Id.ToString(), "true"), + new SettingRecord("IntegerTestFeature1", TenantFeatureValueProvider.ProviderName, Tenant2Id.ToString(), "34") + }; + } + + public Task GetOrNullAsync(string name, string providerName, string providerKey) + { + return Task.FromResult( + _settingRecords.FirstOrDefault(sr => + sr.Name == name && + sr.ProviderName == providerName && + sr.ProviderKey == providerKey + )?.Value + ); + } + + private class SettingRecord + { + public string Name { get; } + public string ProviderName { get; } + public string ProviderKey { get; } + public string Value { get; } + + public SettingRecord(string name, string providerName, string providerKey, string value) + { + Name = name; + ProviderName = providerName; + ProviderKey = providerKey; + Value = value; + } + } + } +} diff --git a/framework/test/Volo.Abp.Settings.Tests/Volo.Abp.Settings.Tests.csproj b/framework/test/Volo.Abp.Settings.Tests/Volo.Abp.Settings.Tests.csproj index 47c93c6bf4..7f83c36c9a 100644 --- a/framework/test/Volo.Abp.Settings.Tests/Volo.Abp.Settings.Tests.csproj +++ b/framework/test/Volo.Abp.Settings.Tests/Volo.Abp.Settings.Tests.csproj @@ -13,6 +13,7 @@ + diff --git a/framework/test/Volo.Abp.Settings.Tests/Volo/Abp/Settings/AbpSettingsTestModule.cs b/framework/test/Volo.Abp.Settings.Tests/Volo/Abp/Settings/AbpSettingsTestModule.cs index dc4ab057a0..09a50daa5d 100644 --- a/framework/test/Volo.Abp.Settings.Tests/Volo/Abp/Settings/AbpSettingsTestModule.cs +++ b/framework/test/Volo.Abp.Settings.Tests/Volo/Abp/Settings/AbpSettingsTestModule.cs @@ -1,8 +1,10 @@ -using Volo.Abp.Modularity; +using Volo.Abp.Autofac; +using Volo.Abp.Modularity; namespace Volo.Abp.Settings { [DependsOn( + typeof(AbpAutofacModule), typeof(AbpSettingsModule), typeof(AbpTestBaseModule) )] @@ -13,7 +15,6 @@ namespace Volo.Abp.Settings Configure(options => { options.ValueProviders.Add(); - options.DefinitionProviders.Add(); }); } } diff --git a/framework/test/Volo.Abp.Settings.Tests/Volo/Abp/Settings/SettingProvider_Tests.cs b/framework/test/Volo.Abp.Settings.Tests/Volo/Abp/Settings/SettingProvider_Tests.cs index 6b95b0f755..c3c22f76f9 100644 --- a/framework/test/Volo.Abp.Settings.Tests/Volo/Abp/Settings/SettingProvider_Tests.cs +++ b/framework/test/Volo.Abp.Settings.Tests/Volo/Abp/Settings/SettingProvider_Tests.cs @@ -13,6 +13,11 @@ namespace Volo.Abp.Settings _settingProvider = GetRequiredService(); } + protected override void SetAbpApplicationCreationOptions(AbpApplicationCreationOptions options) + { + options.UseAutofac(); + } + [Fact] public async Task Should_Get_Null_If_No_Value_Provided_And_No_Default_Value() { diff --git a/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Testing/HasExtraPropertiesExtensions_Tests.cs b/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Testing/HasExtraPropertiesExtensions_Tests.cs new file mode 100644 index 0000000000..cc80c24bc0 --- /dev/null +++ b/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Testing/HasExtraPropertiesExtensions_Tests.cs @@ -0,0 +1,33 @@ +using System; +using Shouldly; +using Volo.Abp.Data; +using Volo.Abp.TestApp.Domain; +using Xunit; + +namespace Volo.Abp.TestApp.Testing +{ + public class HasExtraPropertiesExtensions_Tests + { + [Fact] + public void Basic_Tests() + { + var city = new City(Guid.NewGuid(), "Adana"); + + city.HasProperty("UnknownProperty").ShouldBeFalse(); + city.GetProperty("UnknownProperty").ShouldBeNull(); + city.GetProperty("UnknownProperty").ShouldBe(0); + + city.SetProperty("IsHot", true); + city.HasProperty("IsHot").ShouldBeTrue(); + city.GetProperty("IsHot").ShouldBeTrue(); + + city.SetProperty("IsHot", false); + city.HasProperty("IsHot").ShouldBeTrue(); + city.GetProperty("IsHot").ShouldBeFalse(); + + city.RemoveProperty("IsHot"); + city.HasProperty("IsHot").ShouldBeFalse(); + city.GetProperty("IsHot").ShouldBeFalse(); + } + } +} \ No newline at end of file diff --git a/modules/account/src/Volo.Abp.Account.Web.IdentityServer/AbpAccountWebIdentityServerModule.cs b/modules/account/src/Volo.Abp.Account.Web.IdentityServer/AbpAccountWebIdentityServerModule.cs index 132d7a81db..6a68ebc91a 100644 --- a/modules/account/src/Volo.Abp.Account.Web.IdentityServer/AbpAccountWebIdentityServerModule.cs +++ b/modules/account/src/Volo.Abp.Account.Web.IdentityServer/AbpAccountWebIdentityServerModule.cs @@ -1,5 +1,6 @@ using Volo.Abp.IdentityServer; using Volo.Abp.Modularity; +using Volo.Abp.VirtualFileSystem; namespace Volo.Abp.Account.Web { @@ -9,6 +10,12 @@ namespace Volo.Abp.Account.Web )] public class AbpAccountWebIdentityServerModule : AbpModule { - + public override void ConfigureServices(ServiceConfigurationContext context) + { + Configure(options => + { + options.FileSets.AddEmbedded("Volo.Abp.Account.Web"); + }); + } } } diff --git a/modules/account/src/Volo.Abp.Account.Web.IdentityServer/Pages/Consent.cshtml b/modules/account/src/Volo.Abp.Account.Web.IdentityServer/Pages/Consent.cshtml new file mode 100644 index 0000000000..fa3efda621 --- /dev/null +++ b/modules/account/src/Volo.Abp.Account.Web.IdentityServer/Pages/Consent.cshtml @@ -0,0 +1,113 @@ +@page +@using Volo.Abp.Account.Web.Pages +@using Volo.Abp.Account.Web.Pages.Account +@model ConsentModel + + +
+
+

+ @if (Model.ClientInfo.ClientLogoUrl != null) + { + + } + + @Model.ClientInfo.ClientName + is requesting your permission +

+
+
+
+ +
+ + + +
Uncheck the permissions you do not wish to grant.
+ + @if (Model.ConsentInput.IdentityScopes.Any()) + { +

Personal Information

+ +
    + @for (var i = 0; i < Model.ConsentInput.IdentityScopes.Count; i++) + { +
  • +
    + +
    + @* TODO: Use attributes on the view model instead of using hidden here *@ + @if (Model.ConsentInput.IdentityScopes[i].Description != null) + { + + } +
  • + } +
+ } + + @if (Model.ConsentInput.ApiScopes.Any()) + { +

Application Access

+ +
    + @for (var i = 0; i < Model.ConsentInput.ApiScopes.Count; i++) + { +
  • +
    + +
    + @* TODO: Use attributes on the view model instead of using hidden here *@ + @if (Model.ConsentInput.ApiScopes[i].Description != null) + { + + } +
  • + } +
+ } + + @if (Model.ClientInfo.AllowRememberConsent) + { +
+ +
+ } + +
+ + + @if (Model.ClientInfo.ClientUrl != null) + { + + @Model.ClientInfo.ClientName + + } +
+ +
+ +
+
+
\ No newline at end of file diff --git a/modules/account/src/Volo.Abp.Account.Web.IdentityServer/Pages/Consent.cshtml.cs b/modules/account/src/Volo.Abp.Account.Web.IdentityServer/Pages/Consent.cshtml.cs new file mode 100644 index 0000000000..3fb68feaca --- /dev/null +++ b/modules/account/src/Volo.Abp.Account.Web.IdentityServer/Pages/Consent.cshtml.cs @@ -0,0 +1,240 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Threading.Tasks; +using IdentityServer4.Models; +using IdentityServer4.Services; +using IdentityServer4.Stores; +using Microsoft.AspNetCore.Mvc; +using Volo.Abp.AspNetCore.Mvc.UI.RazorPages; +using Volo.Abp.UI; + +namespace Volo.Abp.Account.Web.Pages +{ + //TODO: Move this into the Account folder!!! + public class ConsentModel : AbpPageModel + { + [HiddenInput] + [BindProperty(SupportsGet = true)] + public string ReturnUrl { get; set; } + + [HiddenInput] + [BindProperty(SupportsGet = true)] + public string ReturnUrlHash { get; set; } + + [BindProperty] + public ConsentModel.ConsentInputModel ConsentInput { get; set; } + + public ClientInfoModel ClientInfo { get; set; } + + private readonly IIdentityServerInteractionService _interaction; + private readonly IClientStore _clientStore; + private readonly IResourceStore _resourceStore; + + public ConsentModel( + IIdentityServerInteractionService interaction, + IClientStore clientStore, + IResourceStore resourceStore) + { + _interaction = interaction; + _clientStore = clientStore; + _resourceStore = resourceStore; + } + + public virtual async Task OnGet() + { + var request = await _interaction.GetAuthorizationContextAsync(ReturnUrl); + if (request == null) + { + throw new ApplicationException($"No consent request matching request: {ReturnUrl}"); + } + + var client = await _clientStore.FindEnabledClientByIdAsync(request.ClientId); + if (client == null) + { + throw new ApplicationException($"Invalid client id: {request.ClientId}"); + } + + var resources = await _resourceStore.FindEnabledResourcesByScopeAsync(request.ScopesRequested); + if (resources == null || (!resources.IdentityResources.Any() && !resources.ApiResources.Any())) + { + throw new ApplicationException($"No scopes matching: {request.ScopesRequested.Aggregate((x, y) => x + ", " + y)}"); + } + + ClientInfo = new ClientInfoModel(client); + ConsentInput = new ConsentInputModel + { + RememberConsent = true, + IdentityScopes = resources.IdentityResources.Select(x => CreateScopeViewModel(x, true)).ToList(), + ApiScopes = resources.ApiResources.SelectMany(x => x.Scopes).Select(x => CreateScopeViewModel(x, true)).ToList() + }; + + if (resources.OfflineAccess) + { + ConsentInput.ApiScopes.Add(GetOfflineAccessScope(true)); + } + } + + public virtual async Task OnPost(string userDecision) + { + var result = await ProcessConsentAsync(); + + if (result.IsRedirect) + { + return Redirect(result.RedirectUri); + } + + if (result.HasValidationError) + { + //ModelState.AddModelError("", result.ValidationError); + throw new ApplicationException("Error: " + result.ValidationError); + } + + throw new ApplicationException("Unknown Error!"); + } + + protected virtual async Task ProcessConsentAsync() + { + var result = new ConsentModel.ProcessConsentResult(); + + ConsentResponse grantedConsent; + + if (ConsentInput.UserDecision == "no") + { + grantedConsent = ConsentResponse.Denied; + } + else + { + if (ConsentInput.IdentityScopes.Any() || ConsentInput.ApiScopes.Any()) + { + grantedConsent = new ConsentResponse + { + RememberConsent = ConsentInput.RememberConsent, + ScopesConsented = ConsentInput.GetAllowedScopeNames() + }; + } + else + { + throw new UserFriendlyException("You must pick at least one permission"); //TODO: How to handle this + } + } + + if (grantedConsent != null) + { + var request = await _interaction.GetAuthorizationContextAsync(ReturnUrl); + if (request == null) + { + return result; + } + + await _interaction.GrantConsentAsync(request, grantedConsent); + + result.RedirectUri = ReturnUrl; //TODO: ReturnUrlHash? + } + + return result; + } + + protected virtual ConsentModel.ScopeViewModel CreateScopeViewModel(IdentityResource identity, bool check) + { + return new ConsentModel.ScopeViewModel + { + Name = identity.Name, + DisplayName = identity.DisplayName, + Description = identity.Description, + Emphasize = identity.Emphasize, + Required = identity.Required, + Checked = check || identity.Required + }; + } + + protected virtual ConsentModel.ScopeViewModel CreateScopeViewModel(Scope scope, bool check) + { + return new ConsentModel.ScopeViewModel + { + Name = scope.Name, + DisplayName = scope.DisplayName, + Description = scope.Description, + Emphasize = scope.Emphasize, + Required = scope.Required, + Checked = check || scope.Required + }; + } + + protected virtual ConsentModel.ScopeViewModel GetOfflineAccessScope(bool check) + { + return new ConsentModel.ScopeViewModel + { + Name = IdentityServer4.IdentityServerConstants.StandardScopes.OfflineAccess, + DisplayName = "Offline Access", //TODO: Localize + Description = "Access to your applications and resources, even when you are offline", + Emphasize = true, + Checked = check + }; + } + + public class ConsentInputModel + { + public List IdentityScopes { get; set; } + + public List ApiScopes { get; set; } + + [Required] + public string UserDecision { get; set; } + + public bool RememberConsent { get; set; } + + public List GetAllowedScopeNames() + { + return IdentityScopes.Union(ApiScopes).Where(s => s.Checked).Select(s => s.Name).ToList(); + } + } + + public class ScopeViewModel + { + [Required] + [HiddenInput] + public string Name { get; set; } + + public bool Checked { get; set; } + + public string DisplayName { get; set; } + + public string Description { get; set; } + + public bool Emphasize { get; set; } + + public bool Required { get; set; } + } + + public class ProcessConsentResult + { + public bool IsRedirect => RedirectUri != null; + public string RedirectUri { get; set; } + + public bool HasValidationError => ValidationError != null; + public string ValidationError { get; set; } + } + + public class ClientInfoModel + { + public string ClientName { get; set; } + + public string ClientUrl { get; set; } + + public string ClientLogoUrl { get; set; } + + public bool AllowRememberConsent { get; set; } + + public ClientInfoModel(Client client) + { + //TODO: Automap + ClientName = client.ClientId; + ClientUrl = client.ClientUri; + ClientLogoUrl = client.LogoUri; + AllowRememberConsent = client.AllowRememberConsent; + } + } + } +} \ No newline at end of file diff --git a/modules/account/src/Volo.Abp.Account.Web.IdentityServer/Volo.Abp.Account.Web.IdentityServer.csproj b/modules/account/src/Volo.Abp.Account.Web.IdentityServer/Volo.Abp.Account.Web.IdentityServer.csproj index 6c81672b40..724ecc38ef 100644 --- a/modules/account/src/Volo.Abp.Account.Web.IdentityServer/Volo.Abp.Account.Web.IdentityServer.csproj +++ b/modules/account/src/Volo.Abp.Account.Web.IdentityServer/Volo.Abp.Account.Web.IdentityServer.csproj @@ -20,8 +20,10 @@
+ + + - diff --git a/modules/account/src/Volo.Abp.Account.Web/AbpAccountWebModule.cs b/modules/account/src/Volo.Abp.Account.Web/AbpAccountWebModule.cs index 5d136bd329..e999cf1450 100644 --- a/modules/account/src/Volo.Abp.Account.Web/AbpAccountWebModule.cs +++ b/modules/account/src/Volo.Abp.Account.Web/AbpAccountWebModule.cs @@ -31,11 +31,6 @@ namespace Volo.Abp.Account.Web public override void ConfigureServices(ServiceConfigurationContext context) { - Configure(options => - { - options.DefinitionProviders.Add(); - }); - Configure(options => { options.FileSets.AddEmbedded("Volo.Abp.Account.Web"); diff --git a/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml b/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml index 8c54ce8005..9c438d8141 100644 --- a/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml +++ b/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml @@ -3,6 +3,7 @@ @model Volo.Abp.Account.Web.Pages.Account.LoginModel @inherits Volo.Abp.Account.Web.Pages.Account.AccountPage @inject Volo.Abp.Settings.ISettingProvider SettingProvider +

@L["Login"]

@if (Model.EnableLocalLogin) {
diff --git a/modules/blogging/app/Volo.BloggingTestApp/BloggingTestAppModule.cs b/modules/blogging/app/Volo.BloggingTestApp/BloggingTestAppModule.cs index 6a7ca9ec6d..aec8044d3b 100644 --- a/modules/blogging/app/Volo.BloggingTestApp/BloggingTestAppModule.cs +++ b/modules/blogging/app/Volo.BloggingTestApp/BloggingTestAppModule.cs @@ -31,6 +31,7 @@ using Volo.Abp.Threading; using Volo.Abp.UI; using Volo.Abp.VirtualFileSystem; using Volo.Blogging; +using Volo.Blogging.Files; using Volo.BloggingTestApp.EntityFrameworkCore; using Volo.BloggingTestApp.MongoDb; @@ -109,6 +110,11 @@ namespace Volo.BloggingTestApp { options.DefaultThemeName = BasicTheme.Name; }); + + Configure(options => + { + options.FileUploadLocalFolder = Path.Combine(hostingEnvironment.WebRootPath, "files"); + }); } public override void OnApplicationInitialization(ApplicationInitializationContext context) diff --git a/modules/blogging/app/Volo.BloggingTestApp/Startup.cs b/modules/blogging/app/Volo.BloggingTestApp/Startup.cs index afeb27a51f..e4ccc31b60 100644 --- a/modules/blogging/app/Volo.BloggingTestApp/Startup.cs +++ b/modules/blogging/app/Volo.BloggingTestApp/Startup.cs @@ -3,7 +3,6 @@ using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; -using Serilog; using Volo.Abp; namespace Volo.BloggingTestApp @@ -22,14 +21,6 @@ namespace Volo.BloggingTestApp public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) { - loggerFactory - .AddConsole() - .AddDebug() - .AddSerilog(new LoggerConfiguration() - .Enrich.FromLogContext() - .WriteTo.File("Logs/logs.txt") - .CreateLogger() - ); app.InitializeApplication(); } diff --git a/modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/BloggingApplicationContractsModule.cs b/modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/BloggingApplicationContractsModule.cs index 29deef3457..e9ecdbf581 100644 --- a/modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/BloggingApplicationContractsModule.cs +++ b/modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/BloggingApplicationContractsModule.cs @@ -12,11 +12,6 @@ namespace Volo.Blogging { public override void ConfigureServices(ServiceConfigurationContext context) { - Configure(options => - { - options.DefinitionProviders.Add(); - }); - Configure(options => { options.FileSets.AddEmbedded(); diff --git a/modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/BloggingPermissions.cs b/modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/BloggingPermissions.cs index f77e25f570..e73dc334da 100644 --- a/modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/BloggingPermissions.cs +++ b/modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/BloggingPermissions.cs @@ -11,7 +11,6 @@ public const string Delete = Default + ".Delete"; public const string Update = Default + ".Update"; public const string Create = Default + ".Create"; - } public static class Posts diff --git a/modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/Blogs/IBlogAppService.cs b/modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/Blogs/IBlogAppService.cs index fffc9cdd5b..fbac86d827 100644 --- a/modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/Blogs/IBlogAppService.cs +++ b/modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/Blogs/IBlogAppService.cs @@ -8,8 +8,6 @@ namespace Volo.Blogging.Blogs { public interface IBlogAppService : IApplicationService { - Task> GetListPagedAsync(PagedAndSortedResultRequestDto input); - Task> GetListAsync(); Task GetByShortNameAsync(string shortName); diff --git a/modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/Files/BloggingWebConsts.cs b/modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/Files/BloggingWebConsts.cs new file mode 100644 index 0000000000..ae4eb32dff --- /dev/null +++ b/modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/Files/BloggingWebConsts.cs @@ -0,0 +1,14 @@ +using System; + +namespace Volo.Blogging +{ + public class BloggingWebConsts + { + public class FileUploading + { + public const int MaxFileSize = 5242880; //5MB + + public static int MaxFileSizeAsMegabytes => Convert.ToInt32((MaxFileSize / 1024f) / 1024f); + } + } +} \ No newline at end of file diff --git a/modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/Files/FileUploadInputDto.cs b/modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/Files/FileUploadInputDto.cs new file mode 100644 index 0000000000..c0686835fd --- /dev/null +++ b/modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/Files/FileUploadInputDto.cs @@ -0,0 +1,13 @@ +using System.ComponentModel.DataAnnotations; + +namespace Volo.Blogging.Files +{ + public class FileUploadInputDto + { + [Required] + public byte[] Bytes { get; set; } + + [Required] + public string Name { get; set; } + } +} \ No newline at end of file diff --git a/modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/Files/FileUploadOutputDto.cs b/modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/Files/FileUploadOutputDto.cs new file mode 100644 index 0000000000..a5e49eee48 --- /dev/null +++ b/modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/Files/FileUploadOutputDto.cs @@ -0,0 +1,9 @@ +namespace Volo.Blogging.Files +{ + public class FileUploadOutputDto + { + public string Name { get; set; } + + public string WebUrl { get; set; } + } +} \ No newline at end of file diff --git a/modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/Files/IFileAppService.cs b/modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/Files/IFileAppService.cs new file mode 100644 index 0000000000..a949c848cd --- /dev/null +++ b/modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/Files/IFileAppService.cs @@ -0,0 +1,12 @@ +using System.Threading.Tasks; +using Volo.Abp.Application.Services; + +namespace Volo.Blogging.Files +{ + public interface IFileAppService : IApplicationService + { + Task GetAsync(string name); + + Task CreateAsync(FileUploadInputDto input); + } +} diff --git a/modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/Files/RawFileDto.cs b/modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/Files/RawFileDto.cs new file mode 100644 index 0000000000..56a9653b73 --- /dev/null +++ b/modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/Files/RawFileDto.cs @@ -0,0 +1,7 @@ +namespace Volo.Blogging.Files +{ + public class RawFileDto + { + public byte[] Bytes { get; set; } + } +} \ No newline at end of file diff --git a/modules/blogging/src/Volo.Blogging.Application/Volo.Blogging.Application.csproj b/modules/blogging/src/Volo.Blogging.Application/Volo.Blogging.Application.csproj index a769735b31..95682a2a9e 100644 --- a/modules/blogging/src/Volo.Blogging.Application/Volo.Blogging.Application.csproj +++ b/modules/blogging/src/Volo.Blogging.Application/Volo.Blogging.Application.csproj @@ -10,6 +10,7 @@ + diff --git a/modules/blogging/src/Volo.Blogging.Application/Volo/Blogging/Blogs/BlogAppService.cs b/modules/blogging/src/Volo.Blogging.Application/Volo/Blogging/Blogs/BlogAppService.cs index 790d47dff2..f06864a2d6 100644 --- a/modules/blogging/src/Volo.Blogging.Application/Volo/Blogging/Blogs/BlogAppService.cs +++ b/modules/blogging/src/Volo.Blogging.Application/Volo/Blogging/Blogs/BlogAppService.cs @@ -18,17 +18,6 @@ namespace Volo.Blogging.Blogs _blogRepository = blogRepository; } - public async Task> GetListPagedAsync(PagedAndSortedResultRequestDto input) - { - var blogs = await _blogRepository.GetListAsync(input.Sorting, input.MaxResultCount, input.SkipCount ); - - var totalCount = await _blogRepository.GetTotalCount(); - - var dtos = ObjectMapper.Map, List>(blogs); - - return new PagedResultDto(totalCount, dtos); - } - public async Task> GetListAsync() { var blogs = await _blogRepository.GetListAsync(); diff --git a/modules/blogging/src/Volo.Blogging.Application/Volo/Blogging/Comments/CommentAppService.cs b/modules/blogging/src/Volo.Blogging.Application/Volo/Blogging/Comments/CommentAppService.cs index 32b31da23a..0ff7bc46cc 100644 --- a/modules/blogging/src/Volo.Blogging.Application/Volo/Blogging/Comments/CommentAppService.cs +++ b/modules/blogging/src/Volo.Blogging.Application/Volo/Blogging/Comments/CommentAppService.cs @@ -5,7 +5,6 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Authorization; using Volo.Abp.Application.Services; using Volo.Abp.Guids; -using Volo.Abp.Users; using Volo.Blogging.Comments.Dtos; using Volo.Blogging.Posts; using Volo.Blogging.Users; @@ -81,7 +80,7 @@ namespace Volo.Blogging.Comments ObjectMapper.Map, List>(comments)); } - //[Authorize(BloggingPermissions.Comments.Create)] TODO: Temporary removed + [Authorize] public async Task CreateAsync(CreateCommentDto input) { var comment = new Comment(_guidGenerator.Create(), input.PostId, input.RepliedCommentId, input.Text); @@ -91,6 +90,7 @@ namespace Volo.Blogging.Comments return ObjectMapper.Map(comment); } + [Authorize] public async Task UpdateAsync(Guid id, UpdateCommentDto input) { var comment = await _commentRepository.GetAsync(id); @@ -104,6 +104,7 @@ namespace Volo.Blogging.Comments return ObjectMapper.Map(comment); } + [Authorize] public async Task DeleteAsync(Guid id) { var comment = await _commentRepository.GetAsync(id); diff --git a/modules/blogging/src/Volo.Blogging.Application/Volo/Blogging/CommonOperations.cs b/modules/blogging/src/Volo.Blogging.Application/Volo/Blogging/CommonOperations.cs index 855b504ae4..01e3fefd36 100644 --- a/modules/blogging/src/Volo.Blogging.Application/Volo/Blogging/CommonOperations.cs +++ b/modules/blogging/src/Volo.Blogging.Application/Volo/Blogging/CommonOperations.cs @@ -1,7 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Text; -using Microsoft.AspNetCore.Authorization.Infrastructure; +using Microsoft.AspNetCore.Authorization.Infrastructure; namespace Volo.Blogging { diff --git a/modules/blogging/src/Volo.Blogging.Application/Volo/Blogging/Files/BlogFileOptions.cs b/modules/blogging/src/Volo.Blogging.Application/Volo/Blogging/Files/BlogFileOptions.cs new file mode 100644 index 0000000000..7243595802 --- /dev/null +++ b/modules/blogging/src/Volo.Blogging.Application/Volo/Blogging/Files/BlogFileOptions.cs @@ -0,0 +1,11 @@ +namespace Volo.Blogging.Files +{ + /* TODO: + * - It is not to have different options for all different modules. We should find a more generic way. + * - Actually, it is not good to assume to save to a local folder. Instead, use file storage once implemented. + */ + public class BlogFileOptions + { + public string FileUploadLocalFolder { get; set; } + } +} diff --git a/modules/blogging/src/Volo.Blogging.Application/Volo/Blogging/Files/FileAppService.cs b/modules/blogging/src/Volo.Blogging.Application/Volo/Blogging/Files/FileAppService.cs new file mode 100644 index 0000000000..d8b9273f0c --- /dev/null +++ b/modules/blogging/src/Volo.Blogging.Application/Volo/Blogging/Files/FileAppService.cs @@ -0,0 +1,79 @@ +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.IO; +using System.Threading.Tasks; +using Microsoft.Extensions.Options; +using Volo.Abp; +using Volo.Abp.Application.Services; +using Volo.Abp.Validation; +using Volo.Blogging.Areas.Blog.Helpers; + +namespace Volo.Blogging.Files +{ + public class FileAppService : ApplicationService, IFileAppService + { + public BlogFileOptions Options { get; } + + public FileAppService(IOptions options) + { + Options = options.Value; + } + + public virtual Task GetAsync(string name) + { + Check.NotNullOrWhiteSpace(name, nameof(name)); + + var filePath = Path.Combine(Options.FileUploadLocalFolder, name); + + return Task.FromResult( + new RawFileDto + { + Bytes = File.ReadAllBytes(filePath) + } + ); + } + + public virtual Task CreateAsync(FileUploadInputDto input) + { + if (input.Bytes.IsNullOrEmpty()) + { + ThrowValidationException("Bytes can not be null or empty!", "Bytes"); + } + + if (input.Bytes.Length > BloggingWebConsts.FileUploading.MaxFileSize) + { + throw new UserFriendlyException($"File exceeds the maximum upload size ({BloggingWebConsts.FileUploading.MaxFileSizeAsMegabytes} MB)!"); + } + + if (!ImageFormatHelper.IsValidImage(input.Bytes, FileUploadConsts.AllowedImageUploadFormats)) + { + throw new UserFriendlyException("Not a valid image format!"); + } + + var uniqueFileName = GenerateUniqueFileName(Path.GetExtension(input.Name)); + var filePath = Path.Combine(Options.FileUploadLocalFolder, uniqueFileName); + + File.WriteAllBytes(filePath, input.Bytes); //TODO: Previously was using WriteAllBytesAsync, but it's only in .netcore. + + return Task.FromResult(new FileUploadOutputDto + { + Name = uniqueFileName, + WebUrl = "/api/blogging/files/www/" + uniqueFileName + }); + } + + private static void ThrowValidationException(string message, string memberName) + { + throw new AbpValidationException(message, + new List + { + new ValidationResult(message, new[] {memberName}) + }); + } + + protected virtual string GenerateUniqueFileName(string extension, string prefix = null, string postfix = null) + { + return prefix + GuidGenerator.Create().ToString("N") + postfix + extension; + } + } +} diff --git a/modules/blogging/src/Volo.Blogging.Application/Volo/Blogging/Files/FileUploadConsts.cs b/modules/blogging/src/Volo.Blogging.Application/Volo/Blogging/Files/FileUploadConsts.cs new file mode 100644 index 0000000000..28cb3491f3 --- /dev/null +++ b/modules/blogging/src/Volo.Blogging.Application/Volo/Blogging/Files/FileUploadConsts.cs @@ -0,0 +1,20 @@ +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Drawing.Imaging; +using System.Linq; + +namespace Volo.Blogging.Files +{ + public class FileUploadConsts + { + public static readonly ICollection AllowedImageUploadFormats = new Collection + { + ImageFormat.Jpeg, + ImageFormat.Png, + ImageFormat.Gif, + ImageFormat.Bmp + }; + + public static string AllowedImageFormatsJoint => string.Join(",", AllowedImageUploadFormats.Select(x => x.ToString())); + } +} diff --git a/modules/blogging/src/Volo.Blogging.Web/Areas/Blog/Helpers/ImageFormatHelper.cs b/modules/blogging/src/Volo.Blogging.Application/Volo/Blogging/Files/ImageFormatHelper.cs similarity index 100% rename from modules/blogging/src/Volo.Blogging.Web/Areas/Blog/Helpers/ImageFormatHelper.cs rename to modules/blogging/src/Volo.Blogging.Application/Volo/Blogging/Files/ImageFormatHelper.cs diff --git a/modules/blogging/src/Volo.Blogging.Application/Volo/Blogging/Posts/PostAppService.cs b/modules/blogging/src/Volo.Blogging.Application/Volo/Blogging/Posts/PostAppService.cs index 69e325f0c7..34488f77a1 100644 --- a/modules/blogging/src/Volo.Blogging.Application/Volo/Blogging/Posts/PostAppService.cs +++ b/modules/blogging/src/Volo.Blogging.Application/Volo/Blogging/Posts/PostAppService.cs @@ -13,11 +13,6 @@ using Volo.Blogging.Users; namespace Volo.Blogging.Posts { - /* TODO: Custom policy with configuration. - * We should create a custom policy to see the blog as read only if the blog is - * configured as 'public' or the current user has the related permission. - */ - //[Authorize(BloggingPermissions.Posts.Default)] public class PostAppService : ApplicationService, IPostAppService { protected IBlogUserLookupService UserLookupService { get; } @@ -41,19 +36,19 @@ namespace Volo.Blogging.Posts var userDictionary = new Dictionary(); var postDtos = new List(ObjectMapper.Map, List>(posts)); - if (tag != null) + foreach (var postDto in postDtos) { - postDtos = await FilterPostsByTag(postDtos, tag); + postDto.Tags = await GetTagsOfPost(postDto.Id); } - foreach (var postDto in postDtos) + if (tag != null) { - postDto.CommentCount = await _commentRepository.GetCommentCountOfPostAsync(postDto.Id); + postDtos = await FilterPostsByTag(postDtos, tag); } foreach (var postDto in postDtos) { - postDto.Tags = await GetTagsOfPost(postDto.Id); + postDto.CommentCount = await _commentRepository.GetCommentCountOfPostAsync(postDto.Id); } foreach (var postDto in postDtos) @@ -79,24 +74,6 @@ namespace Volo.Blogging.Posts return new ListResultDto(postDtos); } - private async Task> FilterPostsByTag(List allPostDtos, Tag tag) - { - var filteredPostDtos = new List(); - var posts = await _postRepository.GetListAsync(); - - foreach (var postDto in allPostDtos) - { - if (!postDto.Tags.Any(p=> p.Id == tag.Id)) - { - continue; - } - - filteredPostDtos.Add(postDto); - } - - return filteredPostDtos; - } - public async Task GetForReadingAsync(GetPostInput input) { var post = await _postRepository.GetPostByUrl(input.BlogId, input.Url); @@ -135,6 +112,7 @@ namespace Volo.Blogging.Posts return postDto; } + [Authorize(BloggingPermissions.Posts.Delete)] public async Task DeleteAsync(Guid id) { var post = await _postRepository.GetAsync(id); @@ -272,5 +250,12 @@ namespace Volo.Blogging.Posts } return new List(tags.Split(",").Select(t => t.Trim())); } + + private Task> FilterPostsByTag(List allPostDtos, Tag tag) + { + var filteredPostDtos = allPostDtos.Where(p => p.Tags?.Any(t => t.Id == tag.Id) ?? false).ToList(); + + return Task.FromResult(filteredPostDtos); + } } } diff --git a/modules/blogging/src/Volo.Blogging.Application/Volo/Blogging/Tagging/TagAppService.cs b/modules/blogging/src/Volo.Blogging.Application/Volo/Blogging/Tagging/TagAppService.cs index 473a8698df..0e6e75e071 100644 --- a/modules/blogging/src/Volo.Blogging.Application/Volo/Blogging/Tagging/TagAppService.cs +++ b/modules/blogging/src/Volo.Blogging.Application/Volo/Blogging/Tagging/TagAppService.cs @@ -2,17 +2,11 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; -using Microsoft.AspNetCore.Authorization; using Volo.Abp.Application.Services; using Volo.Blogging.Tagging.Dtos; namespace Volo.Blogging.Tagging { - /* TODO: Custom policy with configuration. - * We should create a custom policy to see the blog as read only if the blog is - * configured as 'public' or the current user has the related permission. - */ - //[Authorize(BloggingPermissions.Tags.Default)] public class TagAppService : ApplicationService, ITagAppService { private readonly ITagRepository _tagRepository; @@ -28,7 +22,6 @@ namespace Volo.Blogging.Tagging .WhereIf(input.MinimumPostCount != null, t=>t.UsageCount >= input.MinimumPostCount) .Take(input.ResultCount).ToList(); - return new List( ObjectMapper.Map, List>(postTags)); } diff --git a/modules/blogging/src/Volo.Blogging.Domain/Volo/Blogging/Blogs/IBlogRepository.cs b/modules/blogging/src/Volo.Blogging.Domain/Volo/Blogging/Blogs/IBlogRepository.cs index 62ef6be7b3..27e01a0262 100644 --- a/modules/blogging/src/Volo.Blogging.Domain/Volo/Blogging/Blogs/IBlogRepository.cs +++ b/modules/blogging/src/Volo.Blogging.Domain/Volo/Blogging/Blogs/IBlogRepository.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.Threading.Tasks; using Volo.Abp.Domain.Repositories; @@ -9,8 +8,6 @@ namespace Volo.Blogging.Blogs { Task FindByShortNameAsync(string shortName); - Task> GetListAsync(string sorting, int maxResultCount, int skipCount); - Task GetTotalCount(); } } diff --git a/modules/blogging/src/Volo.Blogging.Domain/Volo/Blogging/Users/BlogUser.cs b/modules/blogging/src/Volo.Blogging.Domain/Volo/Blogging/Users/BlogUser.cs index 0ae80c8d42..9bdbb2fc0d 100644 --- a/modules/blogging/src/Volo.Blogging.Domain/Volo/Blogging/Users/BlogUser.cs +++ b/modules/blogging/src/Volo.Blogging.Domain/Volo/Blogging/Users/BlogUser.cs @@ -4,7 +4,7 @@ using Volo.Abp.Users; namespace Volo.Blogging.Users { - public class BlogUser : AggregateRoot, IUser + public class BlogUser : AggregateRoot, IUser, IUpdateUserData { public virtual Guid? TenantId { get; protected set; } @@ -30,17 +30,45 @@ namespace Volo.Blogging.Users public BlogUser(IUserData user) : base(user.Id) { - Email = user.Email; - Name = user.Name; - Surname = user.Surname; - EmailConfirmed = user.EmailConfirmed; - PhoneNumber = user.PhoneNumber; - PhoneNumberConfirmed = user.PhoneNumberConfirmed; - UserName = user.UserName; TenantId = user.TenantId; + UpdateInternal(user); + } + + public virtual bool Update(IUserData user) + { + if (Id != user.Id) + { + throw new ArgumentException($"Given User's Id '{user.Id}' does not match to this User's Id '{Id}'"); + } + + if (TenantId != user.TenantId) + { + throw new ArgumentException($"Given User's TenantId '{user.TenantId}' does not match to this User's TenantId '{TenantId}'"); + } + + if (Equals(user)) + { + return false; + } + + UpdateInternal(user); + return true; + } + + protected virtual bool Equals(IUserData user) + { + return Id == user.Id && + TenantId == user.TenantId && + UserName == user.UserName && + Name == user.Name && + Surname == user.Surname && + Email == user.Email && + EmailConfirmed == user.EmailConfirmed && + PhoneNumber == user.PhoneNumber && + PhoneNumberConfirmed == user.PhoneNumberConfirmed; } - public void Update(IUserData user) + protected virtual void UpdateInternal(IUserData user) { Email = user.Email; Name = user.Name; diff --git a/modules/blogging/src/Volo.Blogging.Domain/Volo/Blogging/Users/BlogUserLookupService.cs b/modules/blogging/src/Volo.Blogging.Domain/Volo/Blogging/Users/BlogUserLookupService.cs index 7ef29a026d..9bf0025570 100644 --- a/modules/blogging/src/Volo.Blogging.Domain/Volo/Blogging/Users/BlogUserLookupService.cs +++ b/modules/blogging/src/Volo.Blogging.Domain/Volo/Blogging/Users/BlogUserLookupService.cs @@ -12,6 +12,7 @@ namespace Volo.Blogging.Users userRepository, unitOfWorkManager) { + } protected override BlogUser CreateUser(IUserData externalUser) diff --git a/modules/blogging/src/Volo.Blogging.Domain/Volo/Blogging/Users/BlogUserSynchronizer.cs b/modules/blogging/src/Volo.Blogging.Domain/Volo/Blogging/Users/BlogUserSynchronizer.cs index 6000d121f9..899f5c3d8d 100644 --- a/modules/blogging/src/Volo.Blogging.Domain/Volo/Blogging/Users/BlogUserSynchronizer.cs +++ b/modules/blogging/src/Volo.Blogging.Domain/Volo/Blogging/Users/BlogUserSynchronizer.cs @@ -13,7 +13,9 @@ namespace Volo.Blogging.Users protected IBlogUserRepository UserRepository { get; } protected IBlogUserLookupService UserLookupService { get; } - public BlogUserSynchronizer(IBlogUserRepository userRepository, IBlogUserLookupService userLookupService) + public BlogUserSynchronizer( + IBlogUserRepository userRepository, + IBlogUserLookupService userLookupService) { UserRepository = userRepository; UserLookupService = userLookupService; @@ -24,7 +26,6 @@ namespace Volo.Blogging.Users var user = await UserRepository.FindAsync(eventData.Entity.Id); if (user == null) { - //TODO: Why needed (ask to @ebicoglu)? user = await UserLookupService.FindByIdAsync(eventData.Entity.Id); if (user == null) { @@ -32,8 +33,10 @@ namespace Volo.Blogging.Users } } - user.Update(eventData.Entity); - await UserRepository.UpdateAsync(user); + if (user.Update(eventData.Entity)) + { + await UserRepository.UpdateAsync(user); + } } } } diff --git a/modules/blogging/src/Volo.Blogging.EntityFrameworkCore/Volo/Blogging/Blogs/EfCoreBlogRepository.cs b/modules/blogging/src/Volo.Blogging.EntityFrameworkCore/Volo/Blogging/Blogs/EfCoreBlogRepository.cs index e0e70578b1..5c9b43e8ed 100644 --- a/modules/blogging/src/Volo.Blogging.EntityFrameworkCore/Volo/Blogging/Blogs/EfCoreBlogRepository.cs +++ b/modules/blogging/src/Volo.Blogging.EntityFrameworkCore/Volo/Blogging/Blogs/EfCoreBlogRepository.cs @@ -1,7 +1,4 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Linq.Dynamic.Core; using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; using Volo.Abp.Domain.Repositories.EntityFrameworkCore; @@ -23,15 +20,6 @@ namespace Volo.Blogging.Blogs return await DbSet.FirstOrDefaultAsync(p => p.ShortName == shortName); } - public async Task> GetListAsync(string sorting, int maxResultCount, int skipCount) - { - var auditLogs = await DbSet.OrderBy(sorting ?? "creationTime desc") - .PageBy(skipCount, maxResultCount) - .ToListAsync(); - - return auditLogs; - } - public async Task GetTotalCount() { return await DbSet.CountAsync(); diff --git a/modules/blogging/src/Volo.Blogging.HttpApi/Volo/Blogging/BlogFilesController.cs b/modules/blogging/src/Volo.Blogging.HttpApi/Volo/Blogging/BlogFilesController.cs new file mode 100644 index 0000000000..818435a78c --- /dev/null +++ b/modules/blogging/src/Volo.Blogging.HttpApi/Volo/Blogging/BlogFilesController.cs @@ -0,0 +1,81 @@ +using System.IO; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Volo.Abp; +using Volo.Abp.AspNetCore.Mvc; +using Volo.Abp.Http; +using Volo.Blogging.Areas.Blog.Models; +using Volo.Blogging.Files; + +namespace Volo.Blogging +{ + [RemoteService] + [Area("blogging")] + [Route("api/blogging/files")] + public class BlogFilesController : AbpController, IFileAppService + { + private readonly IFileAppService _fileAppService; + + public BlogFilesController(IFileAppService fileAppService) + { + _fileAppService = fileAppService; + } + + [HttpGet] + [Route("{name}")] + public Task GetAsync(string name) //TODO: output cache would be good + { + return _fileAppService.GetAsync(name); + } + + [HttpGet] + [Route("www/{name}")] + public async Task GetForWebAsync(string name) //TODO: output cache would be good + { + var file = await _fileAppService.GetAsync(name); + return File( + file.Bytes, + MimeTypes.GetByExtension(Path.GetExtension(name)) + ); + } + + [HttpPost] + public Task CreateAsync(FileUploadInputDto input) + { + return _fileAppService.CreateAsync(input); + } + + [HttpPost] + [Route("images/upload")] + public async Task UploadImage(IFormFile file) + { + //TODO: localize exception messages + + if (file == null) + { + throw new UserFriendlyException("No file found!"); + } + + if (file.Length <= 0) + { + throw new UserFriendlyException("File is empty!"); + } + + if (!file.ContentType.Contains("image")) + { + throw new UserFriendlyException("Not a valid image!"); + } + + var output = await _fileAppService.CreateAsync( + new FileUploadInputDto + { + Bytes = file.GetAllBytes(), + Name = file.FileName + } + ); + + return Json(new FileUploadResult(output.WebUrl)); + } + } +} diff --git a/modules/blogging/src/Volo.Blogging.HttpApi/Volo/Blogging/BlogsController.cs b/modules/blogging/src/Volo.Blogging.HttpApi/Volo/Blogging/BlogsController.cs index 9fbb70f849..084afb958a 100644 --- a/modules/blogging/src/Volo.Blogging.HttpApi/Volo/Blogging/BlogsController.cs +++ b/modules/blogging/src/Volo.Blogging.HttpApi/Volo/Blogging/BlogsController.cs @@ -4,7 +4,6 @@ using Microsoft.AspNetCore.Mvc; using Volo.Abp; using Volo.Abp.Application.Dtos; using Volo.Abp.AspNetCore.Mvc; -using Volo.Abp.Auditing; using Volo.Blogging.Blogs; using Volo.Blogging.Blogs.Dtos; @@ -23,13 +22,6 @@ namespace Volo.Blogging } [HttpGet] - public async Task> GetListPagedAsync(PagedAndSortedResultRequestDto input) - { - return await _blogAppService.GetListPagedAsync(input); - } - - [HttpGet] - [Route("all")] public async Task> GetListAsync() { return await _blogAppService.GetListAsync(); diff --git a/modules/blogging/src/Volo.Blogging.Web/Areas/Blog/Models/FileUploadResult.cs b/modules/blogging/src/Volo.Blogging.HttpApi/Volo/Blogging/FileUploadResult.cs similarity index 100% rename from modules/blogging/src/Volo.Blogging.Web/Areas/Blog/Models/FileUploadResult.cs rename to modules/blogging/src/Volo.Blogging.HttpApi/Volo/Blogging/FileUploadResult.cs diff --git a/modules/blogging/src/Volo.Blogging.MongoDB/Volo.Blogging.MongoDB.csproj b/modules/blogging/src/Volo.Blogging.MongoDB/Volo.Blogging.MongoDB.csproj index 4969dd630f..0d299c1f3d 100644 --- a/modules/blogging/src/Volo.Blogging.MongoDB/Volo.Blogging.MongoDB.csproj +++ b/modules/blogging/src/Volo.Blogging.MongoDB/Volo.Blogging.MongoDB.csproj @@ -1,7 +1,9 @@  + + - netcoreapp2.2 + netstandard2.0 Volo.Blogging.MongoDB Volo.Blogging.MongoDB diff --git a/modules/blogging/src/Volo.Blogging.MongoDB/Volo/Blogging/Blogs/MongoBlogRepository.cs b/modules/blogging/src/Volo.Blogging.MongoDB/Volo/Blogging/Blogs/MongoBlogRepository.cs index 9319eb64af..8a3b759730 100644 --- a/modules/blogging/src/Volo.Blogging.MongoDB/Volo/Blogging/Blogs/MongoBlogRepository.cs +++ b/modules/blogging/src/Volo.Blogging.MongoDB/Volo/Blogging/Blogs/MongoBlogRepository.cs @@ -1,12 +1,9 @@ using System; -using System.Collections.Generic; using System.Threading.Tasks; using MongoDB.Driver.Linq; using Volo.Abp.Domain.Repositories.MongoDB; using Volo.Abp.MongoDB; using Volo.Blogging.MongoDB; -using System.Linq; -using System.Linq.Dynamic.Core; namespace Volo.Blogging.Blogs { @@ -21,15 +18,6 @@ namespace Volo.Blogging.Blogs return await GetMongoQueryable().FirstOrDefaultAsync(p => p.ShortName == shortName); } - public async Task> GetListAsync(string sorting, int maxResultCount, int skipCount) - { - var auditLogs = GetMongoQueryable().OrderBy(sorting ?? "creationTime desc").As>() - .PageBy(skipCount, maxResultCount) - .ToList(); - - return auditLogs; - } - public async Task GetTotalCount() { return await GetMongoQueryable().CountAsync(); diff --git a/modules/blogging/src/Volo.Blogging.Web/Areas/Blog/Controllers/CommentsController.cs b/modules/blogging/src/Volo.Blogging.Web/Areas/Blog/Controllers/CommentsController.cs index 89ed0f1968..80ef132c21 100644 --- a/modules/blogging/src/Volo.Blogging.Web/Areas/Blog/Controllers/CommentsController.cs +++ b/modules/blogging/src/Volo.Blogging.Web/Areas/Blog/Controllers/CommentsController.cs @@ -7,6 +7,8 @@ using Volo.Blogging.Comments.Dtos; namespace Volo.Blogging.Areas.Blog.Controllers { + //TODO: Is that being used? + [Area("Blog")] [Route("Blog/[controller]/[action]")] public class CommentsController : AbpController diff --git a/modules/blogging/src/Volo.Blogging.Web/Areas/Blog/Controllers/FilesController.cs b/modules/blogging/src/Volo.Blogging.Web/Areas/Blog/Controllers/FilesController.cs deleted file mode 100644 index 1f8b932a83..0000000000 --- a/modules/blogging/src/Volo.Blogging.Web/Areas/Blog/Controllers/FilesController.cs +++ /dev/null @@ -1,31 +0,0 @@ -using System.Threading.Tasks; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Mvc; -using Volo.Abp.AspNetCore.Mvc; -using Volo.Blogging.Areas.Blog.Models; -using Volo.Blogging.Hosting; - -namespace Volo.Blogging.Areas.Blog.Controllers -{ - [Area("Blog")] - [Route("Blog/[controller]/[action]")] - public class FilesController : AbpController - { - private readonly IFileService _fileService; - - public FilesController(IFileService fileService) - { - _fileService = fileService; - } - - [HttpPost] - public async Task UploadImage(IFormFile file) - { - file.ValidateImage(out var fileBytes); - - var fileUrl = await _fileService.SaveFileAsync(fileBytes, file.FileName); - - return Json(new FileUploadResult(fileUrl)); - } - } -} \ No newline at end of file diff --git a/modules/blogging/src/Volo.Blogging.Web/Areas/Blog/Controllers/PostsController.cs b/modules/blogging/src/Volo.Blogging.Web/Areas/Blog/Controllers/PostsController.cs index 55a96f6ccc..383f08284f 100644 --- a/modules/blogging/src/Volo.Blogging.Web/Areas/Blog/Controllers/PostsController.cs +++ b/modules/blogging/src/Volo.Blogging.Web/Areas/Blog/Controllers/PostsController.cs @@ -6,6 +6,8 @@ using Volo.Blogging.Posts; namespace Volo.Blogging.Areas.Blog.Controllers { + //TODO: Is that being used? + [Area("Blog")] [Route("Blog/[controller]/[action]")] public class PostsController : AbpController diff --git a/modules/blogging/src/Volo.Blogging.Web/BloggingMenuContributor.cs b/modules/blogging/src/Volo.Blogging.Web/BloggingMenuContributor.cs index f20c946232..cf1f12ed02 100644 --- a/modules/blogging/src/Volo.Blogging.Web/BloggingMenuContributor.cs +++ b/modules/blogging/src/Volo.Blogging.Web/BloggingMenuContributor.cs @@ -1,7 +1,7 @@ -using System.Threading.Tasks; -using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Authorization; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Localization; +using System.Threading.Tasks; using Volo.Abp.UI.Navigation; using Volo.Blogging.Localization; @@ -22,23 +22,17 @@ namespace Volo.Blogging var authorizationService = context.ServiceProvider.GetRequiredService(); var l = context.ServiceProvider.GetRequiredService>(); - if (await authorizationService.IsGrantedAsync(BloggingPermissions.Blogs.Default)) + if (await authorizationService.IsGrantedAsync(BloggingPermissions.Blogs.Management)) { - var rootMenuItem = new ApplicationMenuItem("Blogs", l["Menu:Blogs"], "/Blog"); - - context.Menu.AddItem(rootMenuItem); + var managementRootMenuItem = new ApplicationMenuItem("BlogManagement", l["Menu:BlogManagement"]); + //TODO: Using the same permission. Reconsider. if (await authorizationService.IsGrantedAsync(BloggingPermissions.Blogs.Management)) { - var managementRootMenuItem = new ApplicationMenuItem("BlogManagement", l["Menu:BlogManagement"]); - - if (await authorizationService.IsGrantedAsync(BloggingPermissions.Blogs.Management)) - { - managementRootMenuItem.AddItem(new ApplicationMenuItem("BlogsAdmin", l["Menu:Blogs"], "/Admin/Blogs")); - } - - context.Menu.AddItem(managementRootMenuItem); + managementRootMenuItem.AddItem(new ApplicationMenuItem("BlogManagement.Blogs", l["Menu:Blogs"], "/Admin/Blogs")); } + + context.Menu.AddItem(managementRootMenuItem); } } } diff --git a/modules/blogging/src/Volo.Blogging.Web/BloggingWebConsts.cs b/modules/blogging/src/Volo.Blogging.Web/BloggingWebConsts.cs deleted file mode 100644 index 491ea9d67b..0000000000 --- a/modules/blogging/src/Volo.Blogging.Web/BloggingWebConsts.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Drawing.Imaging; -using System.Linq; - -namespace Volo.Blogging -{ - public class BloggingWebConsts - { - public class FileUploading - { - public const string DefaultFileUploadFolderName = "files"; - - public static readonly ICollection AllowedImageUploadFormats = new Collection - { - ImageFormat.Jpeg, - ImageFormat.Png, - ImageFormat.Gif, - ImageFormat.Bmp - }; - - public static string AllowedImageFormatsJoint => string.Join(",", AllowedImageUploadFormats.Select(x => x.ToString())); - - public const int MaxFileSize = 5242880; //5MB - - public static int MaxFileSizeAsMegabytes => Convert.ToInt32((MaxFileSize / 1024f) / 1024f); - } - } -} \ No newline at end of file diff --git a/modules/blogging/src/Volo.Blogging.Web/Hosting/FileService.cs b/modules/blogging/src/Volo.Blogging.Web/Hosting/FileService.cs deleted file mode 100644 index accb424215..0000000000 --- a/modules/blogging/src/Volo.Blogging.Web/Hosting/FileService.cs +++ /dev/null @@ -1,72 +0,0 @@ -using System.IO; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Http; -using Volo.Abp; -using Volo.Abp.DependencyInjection; -using Volo.Abp.Guids; - -namespace Volo.Blogging.Hosting -{ - public class FileService : IFileService, ITransientDependency - { - private readonly IHostingEnvironment _hostingEnvironment; - private readonly IGuidGenerator _guidGenerator; - - public FileService(IHostingEnvironment hostingEnvironment, IGuidGenerator guidGenerator) - { - _hostingEnvironment = hostingEnvironment; - _guidGenerator = guidGenerator; - } - - public string FileUploadDirectory - { - get - { - var uploadDirectory = Path.Combine(_hostingEnvironment.WebRootPath, BloggingWebConsts.FileUploading.DefaultFileUploadFolderName); - if (!Directory.Exists(uploadDirectory)) - { - Directory.CreateDirectory(uploadDirectory); - } - - return uploadDirectory; - } - } - - public string GenerateUniqueFileName(string extension, string prefix = null, string postfix = null) - { - return prefix + _guidGenerator.Create().ToString("N") + postfix + extension; - } - - public async Task SaveFormFileAndGetUrlAsync(IFormFile file) - { - var uniqueFileName = await SaveFileInternalAsync(file.FileName, file.AsBytes()); - return GetFileUrl(uniqueFileName); - } - - public async Task SaveFileAsync(byte[] fileBytes, string originalFileName) - { - if (fileBytes == null || fileBytes.Length == 0) - { - throw new UserFriendlyException("File is empty!"); - } - - var uniqueFileName = await SaveFileInternalAsync(originalFileName, fileBytes); - return GetFileUrl(uniqueFileName); - } - - private static string GetFileUrl(string uniqueFileName) - { - return "/" + BloggingWebConsts.FileUploading.DefaultFileUploadFolderName + "/" + uniqueFileName; - } - - private async Task SaveFileInternalAsync(string originalFileName, byte[] fileBytes) - { - var uniqueFileName = GenerateUniqueFileName(Path.GetExtension(originalFileName)); - var filePath = Path.Combine(FileUploadDirectory, uniqueFileName); - File.WriteAllBytes(filePath, fileBytes); //TODO: Previously was using WriteAllBytesAsync, but it's only in .netcore. - return uniqueFileName; - } - - } -} \ No newline at end of file diff --git a/modules/blogging/src/Volo.Blogging.Web/Hosting/FormFileExtensions.cs b/modules/blogging/src/Volo.Blogging.Web/Hosting/FormFileExtensions.cs deleted file mode 100644 index 669958d086..0000000000 --- a/modules/blogging/src/Volo.Blogging.Web/Hosting/FormFileExtensions.cs +++ /dev/null @@ -1,57 +0,0 @@ -using System.IO; -using JetBrains.Annotations; -using Microsoft.AspNetCore.Http; -using Volo.Abp; -using Volo.Blogging.Areas.Blog.Helpers; - -namespace Volo.Blogging.Hosting -{ - public static class FormFileExtensions - { - public static byte[] AsBytes(this IFormFile file) - { - byte[] fileBytes; - using (var stream = file.OpenReadStream()) - { - fileBytes = stream.GetAllBytes(); - } - - return fileBytes; - } - - public static void ValidateImage([CanBeNull] this IFormFile file, out byte[] fileBytes) - { - fileBytes = null; - - if (file == null) - { - throw new UserFriendlyException("No file found!"); - } - - if (file.Length <= 0) - { - throw new UserFriendlyException("File is empty!"); - } - - if (!file.ContentType.Contains("image")) - { - throw new UserFriendlyException("Not a valid image!"); - } - - using (var stream = file.OpenReadStream()) - { - fileBytes = stream.GetAllBytes(); - } - - if (!ImageFormatHelper.IsValidImage(fileBytes, BloggingWebConsts.FileUploading.AllowedImageUploadFormats)) - { - throw new UserFriendlyException("Not a valid image format!"); - } - - if (file.Length > BloggingWebConsts.FileUploading.MaxFileSize) - { - throw new UserFriendlyException($"File exceeds the maximum upload size ({BloggingWebConsts.FileUploading.MaxFileSizeAsMegabytes} MB)!"); - } - } - } -} \ No newline at end of file diff --git a/modules/blogging/src/Volo.Blogging.Web/Hosting/IFileService.cs b/modules/blogging/src/Volo.Blogging.Web/Hosting/IFileService.cs deleted file mode 100644 index 66516135bd..0000000000 --- a/modules/blogging/src/Volo.Blogging.Web/Hosting/IFileService.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System.Threading.Tasks; -using Microsoft.AspNetCore.Http; - -namespace Volo.Blogging.Hosting -{ - public interface IFileService - { - string FileUploadDirectory { get; } - - string GenerateUniqueFileName(string extension, string prefix = null, string postfix = null); - - Task SaveFormFileAndGetUrlAsync(IFormFile file); - - Task SaveFileAsync(byte[] fileBytes, string originalFileName); - } -} \ No newline at end of file diff --git a/modules/blogging/src/Volo.Blogging.Web/Localization/Resources/Blogging/Web/en.json b/modules/blogging/src/Volo.Blogging.Web/Localization/Resources/Blogging/Web/en.json index b9e7b543b9..444c3d80af 100644 --- a/modules/blogging/src/Volo.Blogging.Web/Localization/Resources/Blogging/Web/en.json +++ b/modules/blogging/src/Volo.Blogging.Web/Localization/Resources/Blogging/Web/en.json @@ -1,7 +1,7 @@ { "culture": "en", "texts": { - "Menu:Blogs": "Blog", + "Menu:Blogs": "Blogs", "Menu:BlogManagement": "Blog Management", "Title": "Title", "Delete": "Delete", diff --git a/modules/blogging/src/Volo.Blogging.Web/Localization/Resources/Blogging/Web/pt-BR.json b/modules/blogging/src/Volo.Blogging.Web/Localization/Resources/Blogging/Web/pt-BR.json index 81740c65db..cf98e56b06 100644 --- a/modules/blogging/src/Volo.Blogging.Web/Localization/Resources/Blogging/Web/pt-BR.json +++ b/modules/blogging/src/Volo.Blogging.Web/Localization/Resources/Blogging/Web/pt-BR.json @@ -1,7 +1,7 @@ { "culture": "pt-BR", "texts": { - "Menu:Blogs": "Blog", + "Menu:Blogs": "Blogs", "Menu:BlogManagement": "Gerenciar Blog", "Title": "Título", "Delete": "Excluir", diff --git a/modules/blogging/src/Volo.Blogging.Web/Localization/Resources/Blogging/Web/tr.json b/modules/blogging/src/Volo.Blogging.Web/Localization/Resources/Blogging/Web/tr.json index a5a705d506..7d8386e517 100644 --- a/modules/blogging/src/Volo.Blogging.Web/Localization/Resources/Blogging/Web/tr.json +++ b/modules/blogging/src/Volo.Blogging.Web/Localization/Resources/Blogging/Web/tr.json @@ -1,7 +1,7 @@ { "culture": "tr", "texts": { - "Menu:Blogs": "Blog", + "Menu:Blogs": "Bloglar", "Title": "Başlık", "Delete": "Sil", "Reply": "Yanıtla", diff --git a/modules/blogging/src/Volo.Blogging.Web/Pages/Admin/Blogs/index.js b/modules/blogging/src/Volo.Blogging.Web/Pages/Admin/Blogs/index.js index a7604b9d41..c370f10a67 100644 --- a/modules/blogging/src/Volo.Blogging.Web/Pages/Admin/Blogs/index.js +++ b/modules/blogging/src/Volo.Blogging.Web/Pages/Admin/Blogs/index.js @@ -7,12 +7,13 @@ var _dataTable = $('#BlogsTable').DataTable(abp.libs.datatables.normalizeConfiguration({ processing: true, serverSide: true, - paging: true, + paging: false, + info: false, searching: false, autoWidth: false, scrollCollapse: true, order: [[3, "desc"]], - ajax: abp.libs.datatables.createAjax(volo.blogging.blogs.getListPaged), + ajax: abp.libs.datatables.createAjax(volo.blogging.blogs.getList), columnDefs: [ { rowAction: { diff --git a/modules/blogging/src/Volo.Blogging.Web/Pages/Blog/BloggingPage.cs b/modules/blogging/src/Volo.Blogging.Web/Pages/Blog/BloggingPage.cs index 90395d763a..03214d1ee3 100644 --- a/modules/blogging/src/Volo.Blogging.Web/Pages/Blog/BloggingPage.cs +++ b/modules/blogging/src/Volo.Blogging.Web/Pages/Blog/BloggingPage.cs @@ -28,7 +28,7 @@ namespace Volo.Blogging.Pages.Blog return title; } - public string GetShortContent(string content) + public string GetShortContent(string content) //TODO: This should be moved to its own place! { var openingTag = "

"; var closingTag = "

"; diff --git a/modules/blogging/src/Volo.Blogging.Web/Pages/Blog/Posts/Detail.cshtml.cs b/modules/blogging/src/Volo.Blogging.Web/Pages/Blog/Posts/Detail.cshtml.cs index 83edb281b0..20f281f8ae 100644 --- a/modules/blogging/src/Volo.Blogging.Web/Pages/Blog/Posts/Detail.cshtml.cs +++ b/modules/blogging/src/Volo.Blogging.Web/Pages/Blog/Posts/Detail.cshtml.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Threading.Tasks; using System.Web; using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.RazorPages; using Volo.Abp.AspNetCore.Mvc.UI.RazorPages; using Volo.Blogging.Blogs; using Volo.Blogging.Blogs.Dtos; diff --git a/modules/blogging/src/Volo.Blogging.Web/Pages/Blog/Posts/edit.js b/modules/blogging/src/Volo.Blogging.Web/Pages/Blog/Posts/edit.js index 2d646de304..9fb4929d30 100644 --- a/modules/blogging/src/Volo.Blogging.Web/Pages/Blog/Posts/edit.js +++ b/modules/blogging/src/Volo.Blogging.Web/Pages/Blog/Posts/edit.js @@ -17,7 +17,7 @@ $.ajax({ type: "POST", - url: "/Blog/Files/UploadImage", + url: "/api/blogging/files/images/upload", data: formData, contentType: false, processData: false, @@ -42,7 +42,7 @@ $.ajax({ type: "POST", - url: "/Blog/Files/UploadImage", + url: "/api/blogging/files/images/upload", data: formData, contentType: false, processData: false, @@ -52,7 +52,6 @@ }); }; - console.log($form.find("input[name='Post.Content']").val() + "asda"); var newPostEditor = $editorContainer.tuiEditor({ usageStatistics: false, initialEditType: 'markdown', @@ -82,7 +81,6 @@ var postText = newPostEditor.getMarkdown(); $postTextInput.val(postText); - console.log(postText); $submitButton.buttonBusy(); $(this).off('submit').submit(); diff --git a/modules/blogging/src/Volo.Blogging.Web/Pages/Blog/Posts/new.js b/modules/blogging/src/Volo.Blogging.Web/Pages/Blog/Posts/new.js index f28cb48d44..519d3f41f4 100644 --- a/modules/blogging/src/Volo.Blogging.Web/Pages/Blog/Posts/new.js +++ b/modules/blogging/src/Volo.Blogging.Web/Pages/Blog/Posts/new.js @@ -19,7 +19,7 @@ $.ajax({ type: "POST", - url: "/Blog/Files/UploadImage", + url: "/api/blogging/files/images/upload", data: formData, contentType: false, processData: false, @@ -43,7 +43,7 @@ $.ajax({ type: "POST", - url: "/Blog/Files/UploadImage", + url: "/api/blogging/files/images/upload", data: formData, contentType: false, processData: false, diff --git a/modules/blogging/src/Volo.Blogging.Web/Volo.Blogging.Web.csproj b/modules/blogging/src/Volo.Blogging.Web/Volo.Blogging.Web.csproj index 6000771e97..8feb2057d7 100644 --- a/modules/blogging/src/Volo.Blogging.Web/Volo.Blogging.Web.csproj +++ b/modules/blogging/src/Volo.Blogging.Web/Volo.Blogging.Web.csproj @@ -19,7 +19,6 @@ -
diff --git a/modules/docs/app/Volo.DocsTestApp/Program.cs b/modules/docs/app/Volo.DocsTestApp/Program.cs index 57ff6be5ea..470b5e6a98 100644 --- a/modules/docs/app/Volo.DocsTestApp/Program.cs +++ b/modules/docs/app/Volo.DocsTestApp/Program.cs @@ -1,13 +1,37 @@ -using System.IO; +using System; +using System.IO; using Microsoft.AspNetCore.Hosting; +using Serilog; +using Serilog.Events; namespace Volo.DocsTestApp { public class Program { - public static void Main(string[] args) + public static int Main(string[] args) { - BuildWebHostInternal(args).Run(); + Log.Logger = new LoggerConfiguration() + .MinimumLevel.Debug() //TODO: Should be configurable! + .MinimumLevel.Override("Microsoft", LogEventLevel.Information) + .Enrich.FromLogContext() + .WriteTo.File("Logs/logs.txt") + .CreateLogger(); + + try + { + Log.Information("Starting web host."); + BuildWebHostInternal(args).Run(); + return 0; + } + catch (Exception ex) + { + Log.Fatal(ex, "Host terminated unexpectedly!"); + return 1; + } + finally + { + Log.CloseAndFlush(); + } } public static IWebHost BuildWebHostInternal(string[] args) => @@ -16,6 +40,7 @@ namespace Volo.DocsTestApp .UseContentRoot(Directory.GetCurrentDirectory()) .UseIISIntegration() .UseStartup() + .UseSerilog() .Build(); } } diff --git a/modules/docs/app/Volo.DocsTestApp/Startup.cs b/modules/docs/app/Volo.DocsTestApp/Startup.cs index e989f904cf..e6708d26e8 100644 --- a/modules/docs/app/Volo.DocsTestApp/Startup.cs +++ b/modules/docs/app/Volo.DocsTestApp/Startup.cs @@ -3,7 +3,6 @@ using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; -using Serilog; using Volo.Abp; namespace Volo.DocsTestApp @@ -22,15 +21,6 @@ namespace Volo.DocsTestApp public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) { - loggerFactory - .AddConsole() - .AddDebug() - .AddSerilog(new LoggerConfiguration() - .Enrich.FromLogContext() - .WriteTo.File("Logs/logs.txt") - .CreateLogger() - ); - app.InitializeApplication(); } } diff --git a/modules/docs/app/Volo.DocsTestApp/Volo.DocsTestApp.csproj b/modules/docs/app/Volo.DocsTestApp/Volo.DocsTestApp.csproj index d1065535b9..b24f84e537 100644 --- a/modules/docs/app/Volo.DocsTestApp/Volo.DocsTestApp.csproj +++ b/modules/docs/app/Volo.DocsTestApp/Volo.DocsTestApp.csproj @@ -11,7 +11,7 @@ - + diff --git a/modules/docs/src/Volo.Docs.Admin.Application.Contracts/Volo/Docs/Admin/DocsAdminApplicationContractsModule.cs b/modules/docs/src/Volo.Docs.Admin.Application.Contracts/Volo/Docs/Admin/DocsAdminApplicationContractsModule.cs index 545fcb25b8..e37a732a0d 100644 --- a/modules/docs/src/Volo.Docs.Admin.Application.Contracts/Volo/Docs/Admin/DocsAdminApplicationContractsModule.cs +++ b/modules/docs/src/Volo.Docs.Admin.Application.Contracts/Volo/Docs/Admin/DocsAdminApplicationContractsModule.cs @@ -15,11 +15,6 @@ namespace Volo.Docs.Admin { public override void ConfigureServices(ServiceConfigurationContext context) { - Configure(options => - { - options.DefinitionProviders.Add(); - }); - Configure(options => { options.FileSets.AddEmbedded(); diff --git a/modules/docs/src/Volo.Docs.Admin.Web/DocsAdminWebAutoMapperProfile.cs b/modules/docs/src/Volo.Docs.Admin.Web/DocsAdminWebAutoMapperProfile.cs index 1105fe94aa..9bf81c2140 100644 --- a/modules/docs/src/Volo.Docs.Admin.Web/DocsAdminWebAutoMapperProfile.cs +++ b/modules/docs/src/Volo.Docs.Admin.Web/DocsAdminWebAutoMapperProfile.cs @@ -14,7 +14,7 @@ namespace Volo.Docs.Admin CreateMap().Ignore(x => x.ExtraProperties); CreateMap () - .Ignore(x => x.GitHubAccessToken).Ignore(x => x.GitHubRootUrl); + .Ignore(x => x.GitHubAccessToken).Ignore(x => x.GitHubRootUrl).Ignore(x => x.GitHubUserAgent); } } } diff --git a/modules/docs/src/Volo.Docs.Admin.Web/Pages/Docs/Admin/Projects/Create.cshtml.cs b/modules/docs/src/Volo.Docs.Admin.Web/Pages/Docs/Admin/Projects/Create.cshtml.cs index 3b8f8836f2..7df02eec0c 100644 --- a/modules/docs/src/Volo.Docs.Admin.Web/Pages/Docs/Admin/Projects/Create.cshtml.cs +++ b/modules/docs/src/Volo.Docs.Admin.Web/Pages/Docs/Admin/Projects/Create.cshtml.cs @@ -60,6 +60,7 @@ namespace Volo.Docs.Admin.Pages.Docs.Admin.Projects dto.ExtraProperties = new Dictionary { {nameof(GithubProject.GitHubRootUrl), GithubProject.GitHubRootUrl}, + {nameof(GithubProject.GitHubUserAgent), GithubProject.GitHubUserAgent}, {nameof(GithubProject.GitHubAccessToken), GithubProject.GitHubAccessToken} }; @@ -109,6 +110,10 @@ namespace Volo.Docs.Admin.Pages.Docs.Admin.Projects [DisplayOrder(10001)] [StringLength(512)] public string GitHubAccessToken { get; set; } + + [DisplayOrder(10002)] + [StringLength(64)] + public string GitHubUserAgent { get; set; } } } } \ No newline at end of file diff --git a/modules/docs/src/Volo.Docs.Admin.Web/Pages/Docs/Admin/Projects/Edit.cshtml.cs b/modules/docs/src/Volo.Docs.Admin.Web/Pages/Docs/Admin/Projects/Edit.cshtml.cs index ddcda61058..77c5321ad0 100644 --- a/modules/docs/src/Volo.Docs.Admin.Web/Pages/Docs/Admin/Projects/Edit.cshtml.cs +++ b/modules/docs/src/Volo.Docs.Admin.Web/Pages/Docs/Admin/Projects/Edit.cshtml.cs @@ -62,6 +62,7 @@ namespace Volo.Docs.Admin.Pages.Docs.Admin.Projects dto.ExtraProperties = new Dictionary { {nameof(GithubProject.GitHubRootUrl), GithubProject.GitHubRootUrl}, + {nameof(GithubProject.GitHubUserAgent), GithubProject.GitHubUserAgent}, {nameof(GithubProject.GitHubAccessToken), GithubProject.GitHubAccessToken} }; @@ -74,6 +75,7 @@ namespace Volo.Docs.Admin.Pages.Docs.Admin.Projects GithubProject.GitHubAccessToken = (string) dto.ExtraProperties[nameof(GithubProject.GitHubAccessToken)]; GithubProject.GitHubRootUrl = (string) dto.ExtraProperties[nameof(GithubProject.GitHubRootUrl)]; + GithubProject.GitHubUserAgent = (string) dto.ExtraProperties[nameof(GithubProject.GitHubUserAgent)]; } public abstract class EditProjectViewModelBase @@ -116,6 +118,11 @@ namespace Volo.Docs.Admin.Pages.Docs.Admin.Projects [DisplayOrder(10001)] [StringLength(512)] public string GitHubAccessToken { get; set; } + + + [DisplayOrder(10002)] + [StringLength(64)] + public string GitHubUserAgent { get; set; } } } } \ No newline at end of file diff --git a/modules/docs/src/Volo.Docs.Application/Volo/Docs/Documents/DocumentAppService.cs b/modules/docs/src/Volo.Docs.Application/Volo/Docs/Documents/DocumentAppService.cs index 8a76afe357..4aaedd579b 100644 --- a/modules/docs/src/Volo.Docs.Application/Volo/Docs/Documents/DocumentAppService.cs +++ b/modules/docs/src/Volo.Docs.Application/Volo/Docs/Documents/DocumentAppService.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; using Microsoft.Extensions.Caching.Distributed; +using Microsoft.Extensions.Logging; using Volo.Abp.Application.Services; using Volo.Abp.Caching; using Volo.Docs.Projects; @@ -94,9 +95,10 @@ namespace Volo.Docs.Documents cacheKey, async () => { + Logger.LogInformation($"Not found in the cache. Requesting {documentName} from the store..."); var store = _documentStoreFactory.Create(project.DocumentStoreType); var document = await store.GetDocumentAsync(project, documentName, version); - + Logger.LogInformation($"Document retrieved: {documentName}"); return CreateDocumentWithDetailsDto(project, document); }, () => new DistributedCacheEntryOptions diff --git a/modules/docs/src/Volo.Docs.Domain/Volo/Docs/GitHub/Documents/GithubDocumentStore.cs b/modules/docs/src/Volo.Docs.Domain/Volo/Docs/GitHub/Documents/GithubDocumentStore.cs index 59f19f6198..5baa073b9c 100644 --- a/modules/docs/src/Volo.Docs.Domain/Volo/Docs/GitHub/Documents/GithubDocumentStore.cs +++ b/modules/docs/src/Volo.Docs.Domain/Volo/Docs/GitHub/Documents/GithubDocumentStore.cs @@ -50,7 +50,8 @@ namespace Volo.Docs.GitHub.Documents Format = project.Format, LocalDirectory = localDirectory, FileName = fileName, - Contributors = !isNavigationDocument ? await GetContributors(commitHistoryUrl, token, userAgent): new List(), + Contributors = new List(), + //Contributors = !isNavigationDocument ? await GetContributors(commitHistoryUrl, token, userAgent): new List(), Version = version, Content = await DownloadWebContentAsStringAsync(rawDocumentUrl, token, userAgent) }; diff --git a/modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/Index.cshtml b/modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/Index.cshtml index a070bddecc..55c1752994 100644 --- a/modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/Index.cshtml +++ b/modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/Index.cshtml @@ -45,7 +45,7 @@