diff --git a/abp_io/src/Volo.AbpWebSite.Web/AbpWebSiteWebModule.cs b/abp_io/src/Volo.AbpWebSite.Web/AbpWebSiteWebModule.cs index e6d888813c..e1c30e0a26 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,34 @@ 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"); + options.FileUploadUrlRoot = "/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 +106,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 +137,9 @@ namespace Volo.AbpWebSite } } - private void ConfigureTheme(IServiceCollection services) + private void ConfigureTheme() { - services.Configure(options => + Configure(options => { options.Themes.Add(); options.DefaultThemeName = AbpIoTheme.Name; 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 f73fb7942f..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,7 +9,7 @@ true true false - true + c140514f-e488-4c99-8b9a-fabee0f53ce0 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/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/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..f99d6cebbc 100644 --- a/framework/Volo.Abp.sln +++ b/framework/Volo.Abp.sln @@ -222,9 +222,11 @@ 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("{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("{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("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.Features", "src\Volo.Abp.Features\Volo.Abp.Features.csproj", "{01E3D389-8872-4EB1-9D3D-13B6ED54DE0E}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -640,6 +642,10 @@ 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 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -749,6 +755,7 @@ 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} 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/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.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.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/IPermissionValueProvider.cs b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/IPermissionValueProvider.cs index 73ba3bfd17..bf19132e23 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 @@ -7,6 +7,7 @@ namespace Volo.Abp.Authorization.Permissions { 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/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..914bd4af34 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 @@ -13,6 +13,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.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.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.Features/Volo.Abp.Features.csproj b/framework/src/Volo.Abp.Features/Volo.Abp.Features.csproj new file mode 100644 index 0000000000..e626a34a9f --- /dev/null +++ b/framework/src/Volo.Abp.Features/Volo.Abp.Features.csproj @@ -0,0 +1,21 @@ + + + + + + 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..f83797334f --- /dev/null +++ b/framework/src/Volo.Abp.Features/Volo/Abp/Features/AbpFeaturesModule.cs @@ -0,0 +1,18 @@ +using Volo.Abp.Localization; +using Volo.Abp.Modularity; +using Volo.Abp.MultiTenancy; + +namespace Volo.Abp.Features +{ + [DependsOn( + typeof(AbpLocalizationAbstractionsModule), + typeof(AbpMultiTenancyAbstractionsModule) + )] + public class AbpFeaturesModule : AbpModule + { + public override void ConfigureServices(ServiceConfigurationContext context) + { + + } + } +} 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..17bd05db6d --- /dev/null +++ b/framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureChecker.cs @@ -0,0 +1,86 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.Features +{ + public class FeatureChecker : IFeatureChecker, ITransientDependency + { + protected IFeatureDefinitionManager FeatureDefinitionManager { get; } + protected Lazy> Providers { get; } + protected FeatureOptions Options { get; } + + public FeatureChecker( + IOptions options, + IServiceProvider serviceProvider, + IFeatureDefinitionManager featureDefinitionManager) + { + FeatureDefinitionManager = featureDefinitionManager; + + Options = options.Value; + + Providers = new Lazy>( + () => Options + .ValueProviders + .Select(type => serviceProvider.GetRequiredService(type) as IFeatureValueProvider) + .ToList(), + true + ); + } + + public virtual async Task GetOrNullAsync(string name) + { + var featureDefinition = FeatureDefinitionManager.Get(name); + var providers = Enumerable + .Reverse(Providers.Value); + + if (featureDefinition.AllowedProviders.Any()) + { + providers = providers.Where(p => featureDefinition.AllowedProviders.Contains(p.Name)); + } + + return await GetOrNullValueFromProvidersAsync(providers, featureDefinition); + } + + public 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 + ); + } + } + + 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/FeatureCheckerExtensions.cs b/framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureCheckerExtensions.cs new file mode 100644 index 0000000000..05122e8e6a --- /dev/null +++ b/framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureCheckerExtensions.cs @@ -0,0 +1,37 @@ +using System; +using System.Threading.Tasks; +using JetBrains.Annotations; +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)); + } + } +} \ 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..675697334f --- /dev/null +++ b/framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureDefinition.cs @@ -0,0 +1,150 @@ +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using JetBrains.Annotations; +using Volo.Abp.Localization; + +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. + /// + 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. + /// + public List AllowedProviders { get; } + + /// + /// Can be used to get/set custom properties for this feature. + /// + [NotNull] + public Dictionary Properties { get; } + + //TODO: Implement input type like old ABP! + + public FeatureDefinition( + string name, + string defaultValue = null, + ILocalizableString displayName = null, + ILocalizableString description = null, + bool isVisibleToClients = true) + { + Name = name; + DefaultValue = defaultValue; + IsVisibleToClients = isVisibleToClients; + DisplayName = displayName ?? new FixedLocalizableString(name); + Description = description; + + 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, + bool isVisibleToClients = true) + { + var feature = new FeatureDefinition( + name, + defaultValue, + displayName, + description, + 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..d14695b30a --- /dev/null +++ b/framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureDefinitionContext.cs @@ -0,0 +1,38 @@ +using System.Collections.Generic; +using System.Collections.Immutable; + +namespace Volo.Abp.Features +{ + public class FeatureDefinitionContext : IFeatureDefinitionContext + { + protected Dictionary Features { get; } + + public FeatureDefinitionContext(Dictionary features) + { + Features = features; + } + + public virtual FeatureDefinition GetOrNull(string name) + { + return Features.GetOrDefault(name); + } + + public virtual IReadOnlyList GetAll() + { + return Features.Values.ToImmutableList(); + } + + public virtual void Add(params FeatureDefinition[] definitions) + { + if (definitions.IsNullOrEmpty()) + { + return; + } + + foreach (var definition in definitions) + { + Features[definition.Name] = definition; + } + } + } +} \ 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..bf36c114d3 --- /dev/null +++ b/framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureDefinitionManager.cs @@ -0,0 +1,76 @@ +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 Lazy> Providers { get; } + + protected Lazy> FeatureDefinitions { get; } + + protected FeatureOptions Options { get; } + + private readonly IServiceProvider _serviceProvider; + + public FeatureDefinitionManager( + IOptions options, + IServiceProvider serviceProvider) + { + _serviceProvider = serviceProvider; + Options = options.Value; + + Providers = new Lazy>(CreateFeatureProviders, true); + FeatureDefinitions = new Lazy>(CreateFeatureDefinitions, 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.Value.Values.ToImmutableList(); + } + + public virtual FeatureDefinition GetOrNull(string name) + { + return FeatureDefinitions.Value.GetOrDefault(name); + } + + protected virtual List CreateFeatureProviders() + { + return Options + .DefinitionProviders + .Select(p => _serviceProvider.GetRequiredService(p) as IFeatureDefinitionProvider) + .ToList(); + } + + protected virtual IDictionary CreateFeatureDefinitions() + { + var features = new Dictionary(); + + foreach (var provider in Providers.Value) + { + provider.Define(new FeatureDefinitionContext(features)); + } + + return features; + } + } +} \ 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/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..b93b6903e2 --- /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 + { + 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..8762b9d34b --- /dev/null +++ b/framework/src/Volo.Abp.Features/Volo/Abp/Features/IFeatureDefinitionContext.cs @@ -0,0 +1,9 @@ +namespace Volo.Abp.Features +{ + public interface IFeatureDefinitionContext + { + FeatureDefinition GetOrNull(string name); + + void Add(params FeatureDefinition[] definitions); + } +} \ 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/NullFeatureStore.cs b/framework/src/Volo.Abp.Features/Volo/Abp/Features/NullFeatureStore.cs new file mode 100644 index 0000000000..2a4fcee0c6 --- /dev/null +++ b/framework/src/Volo.Abp.Features/Volo/Abp/Features/NullFeatureStore.cs @@ -0,0 +1,22 @@ +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.Features +{ + 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/TenantFeatureValueProvider.cs b/framework/src/Volo.Abp.Features/Volo/Abp/Features/TenantFeatureValueProvider.cs new file mode 100644 index 0000000000..73b07b6e88 --- /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()); + } + } +} \ No newline at end of file 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.Settings/Volo/Abp/Settings/AbpSettingsModule.cs b/framework/src/Volo.Abp.Settings/Volo/Abp/Settings/AbpSettingsModule.cs index 8c711e12c2..d86f6ca524 100644 --- a/framework/src/Volo.Abp.Settings/Volo/Abp/Settings/AbpSettingsModule.cs +++ b/framework/src/Volo.Abp.Settings/Volo/Abp/Settings/AbpSettingsModule.cs @@ -2,7 +2,6 @@ using Volo.Abp.Modularity; using Volo.Abp.MultiTenancy; using Volo.Abp.Security; -using Volo.Abp.Users; namespace Volo.Abp.Settings { 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..5906306e93 100644 --- a/framework/src/Volo.Abp.Settings/Volo/Abp/Settings/NullSettingStore.cs +++ b/framework/src/Volo.Abp.Settings/Volo/Abp/Settings/NullSettingStore.cs @@ -1,5 +1,4 @@ -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; @@ -19,21 +18,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/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/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.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.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/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..1945c6ac31 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,12 @@ namespace Volo.BloggingTestApp { options.DefaultThemeName = BasicTheme.Name; }); + + Configure(options => + { + options.FileUploadLocalFolder = Path.Combine(hostingEnvironment.WebRootPath, "files"); + options.FileUploadUrlRoot = "/files/"; + }); } public override void OnApplicationInitialization(ApplicationInitializationContext context) 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..1c3b1a1a94 --- /dev/null +++ b/modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/Files/FileUploadOutputDto.cs @@ -0,0 +1,7 @@ +namespace Volo.Blogging.Files +{ + public class FileUploadOutputDto + { + public string Url { 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..6fed8bc62f --- /dev/null +++ b/modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/Files/IFileAppService.cs @@ -0,0 +1,10 @@ +using System.Threading.Tasks; +using Volo.Abp.Application.Services; + +namespace Volo.Blogging.Files +{ + public interface IFileAppService : IApplicationService + { + Task UploadAsync(FileUploadInputDto input); + } +} 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..a06bdafc8d --- /dev/null +++ b/modules/blogging/src/Volo.Blogging.Application/Volo/Blogging/Files/BlogFileOptions.cs @@ -0,0 +1,13 @@ +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; } + + public string FileUploadUrlRoot { 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..8f18608080 --- /dev/null +++ b/modules/blogging/src/Volo.Blogging.Application/Volo/Blogging/Files/FileAppService.cs @@ -0,0 +1,65 @@ +using System; +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 UploadAsync(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 + { + Url = Options.FileUploadUrlRoot.EnsureEndsWith('/') + 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..8799b4e0d0 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; } @@ -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,22 @@ namespace Volo.Blogging.Posts } return new List(tags.Split(",").Select(t => t.Trim())); } + + private Task> FilterPostsByTag(List allPostDtos, Tag tag) + { + var filteredPostDtos = new List(); + + foreach (var postDto in allPostDtos) + { + if (postDto.Tags.All(p => p.Id != tag.Id)) + { + continue; + } + + filteredPostDtos.Add(postDto); + } + + 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 fe4ce41e6a..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 @@ -30,29 +30,46 @@ 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 bool Update(IUserData user) + public virtual bool Update(IUserData user) { - if (UserName == user.UserName && - Name == user.Name && - Surname == user.Surname && - Email == user.Email && - EmailConfirmed == user.EmailConfirmed && - PhoneNumber == user.PhoneNumber && - PhoneNumberConfirmed == user.PhoneNumberConfirmed) + 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; + } + + protected virtual void UpdateInternal(IUserData user) + { Email = user.Email; Name = user.Name; Surname = user.Surname; @@ -60,8 +77,6 @@ namespace Volo.Blogging.Users PhoneNumber = user.PhoneNumber; PhoneNumberConfirmed = user.PhoneNumberConfirmed; UserName = user.UserName; - - return true; } } } 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..e6e961b8a7 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 @@ -32,8 +32,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/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.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/FilesController.cs b/modules/blogging/src/Volo.Blogging.Web/Areas/Blog/Controllers/FilesController.cs index 1f8b932a83..7d3e12f3cd 100644 --- a/modules/blogging/src/Volo.Blogging.Web/Areas/Blog/Controllers/FilesController.cs +++ b/modules/blogging/src/Volo.Blogging.Web/Areas/Blog/Controllers/FilesController.cs @@ -1,31 +1,55 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; +using Volo.Abp; using Volo.Abp.AspNetCore.Mvc; using Volo.Blogging.Areas.Blog.Models; +using Volo.Blogging.Files; using Volo.Blogging.Hosting; namespace Volo.Blogging.Areas.Blog.Controllers { + //TODO: This may be moved to HttpApi project since it may be needed by a SPA too. [Area("Blog")] [Route("Blog/[controller]/[action]")] public class FilesController : AbpController { - private readonly IFileService _fileService; + private readonly IFileAppService _fileAppService; - public FilesController(IFileService fileService) + public FilesController(IFileAppService fileAppService) { - _fileService = fileService; + _fileAppService = fileAppService; } [HttpPost] public async Task UploadImage(IFormFile file) { - file.ValidateImage(out var fileBytes); + //TODO: localize exception messages - var fileUrl = await _fileService.SaveFileAsync(fileBytes, file.FileName); + if (file == null) + { + throw new UserFriendlyException("No file found!"); + } - return Json(new FileUploadResult(fileUrl)); + 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.UploadAsync( + new FileUploadInputDto + { + Bytes = file.AsBytes(), + Name = file.FileName + } + ); + + return Json(new FileUploadResult(output.Url)); } } } \ No newline at end of file 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 index 669958d086..aab0832bbf 100644 --- a/modules/blogging/src/Volo.Blogging.Web/Hosting/FormFileExtensions.cs +++ b/modules/blogging/src/Volo.Blogging.Web/Hosting/FormFileExtensions.cs @@ -2,56 +2,22 @@ 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) + public static byte[] AsBytes(this IFormFile file) //TODO: Move to the framework (rename to GetBytes) { - byte[] fileBytes; using (var stream = file.OpenReadStream()) { - fileBytes = stream.GetAllBytes(); + return stream.GetAllBytes(); } - - return fileBytes; } - public static void ValidateImage([CanBeNull] this IFormFile file, out byte[] fileBytes) + public static void ValidateImage([CanBeNull] this IFormFile file) { - 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/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..32bd7873ca 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 @@ -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/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/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/PermissionValueProviderGrantInfo.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/PermissionValueProviderGrantInfo.cs similarity index 78% rename from framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/PermissionValueProviderGrantInfo.cs rename to modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/PermissionValueProviderGrantInfo.cs index 3c1fc47799..bf3265643d 100644 --- a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/PermissionValueProviderGrantInfo.cs +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/PermissionValueProviderGrantInfo.cs @@ -1,8 +1,8 @@ using JetBrains.Annotations; -namespace Volo.Abp.Authorization.Permissions +namespace Volo.Abp.PermissionManagement { - public class PermissionValueProviderGrantInfo + public class PermissionValueProviderGrantInfo //TODO: Rename to PermissionGrantInfo { public static PermissionValueProviderGrantInfo NonGranted { get; } = new PermissionValueProviderGrantInfo(false); diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/PermissionManagementModal.cshtml b/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/PermissionManagementModal.cshtml index 0859cd9a76..beb39387b8 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/PermissionManagementModal.cshtml +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/PermissionManagementModal.cshtml @@ -9,22 +9,18 @@ Layout = null; } - - - - - - + - @for (var i = 0; i < Model.Groups.Count; i++) { -

@Model.Groups[i].DisplayName

+

@Model.Groups[i].DisplayName

+
+ @for (var j = 0; j < Model.Groups[i].Permissions.Count; j++) { }
-
- -
- - + \ No newline at end of file diff --git a/modules/setting-management/src/Volo.Abp.SettingManagement.Web/Pages/SettingManagement/Index.cshtml b/modules/setting-management/src/Volo.Abp.SettingManagement.Web/Pages/SettingManagement/Index.cshtml index 0e7a9a70bf..c14c416a36 100644 --- a/modules/setting-management/src/Volo.Abp.SettingManagement.Web/Pages/SettingManagement/Index.cshtml +++ b/modules/setting-management/src/Volo.Abp.SettingManagement.Web/Pages/SettingManagement/Index.cshtml @@ -14,24 +14,21 @@ } - - - - - @*@L["Settings"]*@Settings - - - - - - @foreach (var group in Model.SettingPageCreationContext.Groups) - { - +
+ + -

@group.DisplayName

- @await Component.InvokeAsync(group.ComponentType) - - } - -
-
\ No newline at end of file + + @foreach (var group in Model.SettingPageCreationContext.Groups) + { + + +

@group.DisplayName

+
+ @await Component.InvokeAsync(group.ComponentType) +
+ } +
+ + +
\ No newline at end of file diff --git a/npm/lerna.json b/npm/lerna.json index 7d85235b1a..ce492f2519 100644 --- a/npm/lerna.json +++ b/npm/lerna.json @@ -1,5 +1,5 @@ { - "version": "0.5.1", + "version": "0.5.2", "packages": [ "packs/*" ], diff --git a/npm/packs/flag-icon-css/abp.resourcemapping.js b/npm/packs/flag-icon-css/abp.resourcemapping.js new file mode 100644 index 0000000000..bd26d124f5 --- /dev/null +++ b/npm/packs/flag-icon-css/abp.resourcemapping.js @@ -0,0 +1,6 @@ +module.exports = { + mappings: { + "@node_modules/flag-icon-css/css/*": "@libs/flag-icon-css/css", + "@node_modules/flag-icon-css/flags/1x1/*": "@libs/flag-icon-css/flags/1x1" + } +} \ No newline at end of file diff --git a/npm/packs/flag-icon-css/package.json b/npm/packs/flag-icon-css/package.json new file mode 100644 index 0000000000..9a151e3b41 --- /dev/null +++ b/npm/packs/flag-icon-css/package.json @@ -0,0 +1,10 @@ +{ + "version": "0.5.2", + "name": "@abp/flag-icon-css", + "publishConfig": { + "access": "public" + }, + "dependencies": { + "flag-icon-css": "^3.3.0" + } +} diff --git a/nupkg/common.ps1 b/nupkg/common.ps1 index 5a4aeb71e8..b6a589c0ba 100644 --- a/nupkg/common.ps1 +++ b/nupkg/common.ps1 @@ -60,6 +60,7 @@ $projects = ( "framework/src/Volo.Abp.EntityFrameworkCore.PostgreSql", "framework/src/Volo.Abp.EventBus", "framework/src/Volo.Abp.EventBus.RabbitMQ", + "framework/src/Volo.Abp.Features", "framework/src/Volo.Abp.Guids", "framework/src/Volo.Abp.HangFire", "framework/src/Volo.Abp.Http", diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/AuthServer.Host.csproj b/samples/MicroserviceDemo/applications/AuthServer.Host/AuthServer.Host.csproj index 89752802a7..0b938960f5 100644 --- a/samples/MicroserviceDemo/applications/AuthServer.Host/AuthServer.Host.csproj +++ b/samples/MicroserviceDemo/applications/AuthServer.Host/AuthServer.Host.csproj @@ -8,7 +8,7 @@ true true false - true + diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/AuthServerHostModule.cs b/samples/MicroserviceDemo/applications/AuthServer.Host/AuthServerHostModule.cs index d147a49254..56d6c4900c 100644 --- a/samples/MicroserviceDemo/applications/AuthServer.Host/AuthServerHostModule.cs +++ b/samples/MicroserviceDemo/applications/AuthServer.Host/AuthServerHostModule.cs @@ -70,6 +70,7 @@ namespace AuthServer.Host options.ApplicationName = "AuthServer"; }); + //TODO: ConnectionMultiplexer.Connect call has problem since redis may not be ready when this service has started! var redis = ConnectionMultiplexer.Connect(configuration["Redis:Configuration"]); context.Services.AddDataProtection() .PersistKeysToStackExchangeRedis(redis, "MsDemo-DataProtection-Keys"); diff --git a/samples/MicroserviceDemo/applications/BackendAdminApp.Host/BackendAdminApp.Host.csproj b/samples/MicroserviceDemo/applications/BackendAdminApp.Host/BackendAdminApp.Host.csproj index 8da540107a..d2f0d16257 100644 --- a/samples/MicroserviceDemo/applications/BackendAdminApp.Host/BackendAdminApp.Host.csproj +++ b/samples/MicroserviceDemo/applications/BackendAdminApp.Host/BackendAdminApp.Host.csproj @@ -8,7 +8,7 @@ true true false - true + diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/PublicWebSite.Host.csproj b/samples/MicroserviceDemo/applications/PublicWebSite.Host/PublicWebSite.Host.csproj index 169b3d9e8d..158e41f576 100644 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/PublicWebSite.Host.csproj +++ b/samples/MicroserviceDemo/applications/PublicWebSite.Host/PublicWebSite.Host.csproj @@ -8,7 +8,7 @@ true true false - true + diff --git a/samples/MicroserviceDemo/databases/MsDemo_Identity.zip b/samples/MicroserviceDemo/databases/MsDemo_Identity.zip index 6b3bab8b1d..f31c9e4f3d 100644 Binary files a/samples/MicroserviceDemo/databases/MsDemo_Identity.zip and b/samples/MicroserviceDemo/databases/MsDemo_Identity.zip differ diff --git a/samples/MicroserviceDemo/docker-compose.migrations.yml b/samples/MicroserviceDemo/docker-compose.migrations.yml index 16d112e2b7..b91aa104c4 100644 --- a/samples/MicroserviceDemo/docker-compose.migrations.yml +++ b/samples/MicroserviceDemo/docker-compose.migrations.yml @@ -8,16 +8,16 @@ services: ports: - "1433" - migrations: - image: 'microservice-demo/migrations:${TAG:-latest}' - build: - context: ../.. - dockerfile: samples/MicroserviceDemo/databases/Dockerfile - depends_on: - - sqlserver + # migrations: + # image: 'volosoft/microservice-demo-migrations:${TAG:-latest}' + # build: + # context: ../.. + # dockerfile: samples/MicroserviceDemo/databases/Dockerfile + # depends_on: + # - sqlserver restore-database: - image: 'microservice-demo/restore-database:${TAG:-latest}' + image: 'volosoft/microservice-demo-restore-database:${TAG:-latest}' build: context: ../.. dockerfile: samples/MicroserviceDemo/databases/restore/Dockerfile diff --git a/samples/MicroserviceDemo/docker-compose.yml b/samples/MicroserviceDemo/docker-compose.yml index 9a3e4e8191..41ee157a43 100644 --- a/samples/MicroserviceDemo/docker-compose.yml +++ b/samples/MicroserviceDemo/docker-compose.yml @@ -31,7 +31,7 @@ services: - elasticsearch internal-gateway: - image: 'microservice-demo/internal-gateway:${TAG:-latest}' + image: 'volosoft/microservice-demo-internal-gateway:${TAG:-latest}' build: context: ../../ dockerfile: samples/MicroserviceDemo/gateways/InternalGateway.Host/Dockerfile @@ -44,7 +44,7 @@ services: - blogging-service backend-admin-app-gateway: - image: 'microservice-demo/backend-admin-app-gateway:${TAG:-latest}' + image: 'volosoft/microservice-demo-backend-admin-app-gateway:${TAG:-latest}' build: context: ../../ dockerfile: samples/MicroserviceDemo/gateways/BackendAdminAppGateway.Host/Dockerfile @@ -56,7 +56,7 @@ services: - product-service public-website-gateway: - image: 'microservice-demo/public-website-gateway:${TAG:-latest}' + image: 'volosoft/microservice-demo-public-website-gateway:${TAG:-latest}' build: context: ../../ dockerfile: samples/MicroserviceDemo/gateways/PublicWebSiteGateway.Host/Dockerfile @@ -67,7 +67,7 @@ services: - product-service blogging-service: - image: 'microservice-demo/blogging-service:${TAG:-latest}' + image: 'volosoft/microservice-demo-blogging-service:${TAG:-latest}' build: context: ../../ dockerfile: samples/MicroserviceDemo/microservices/BloggingService.Host/Dockerfile @@ -77,7 +77,7 @@ services: - redis identity-service: - image: 'microservice-demo/identity-service:${TAG:-latest}' + image: 'volosoft/microservice-demo-identity-service:${TAG:-latest}' build: context: ../../ dockerfile: samples/MicroserviceDemo/microservices/IdentityService.Host/Dockerfile @@ -88,7 +88,7 @@ services: - sqlserver product-service: - image: 'microservice-demo/product-service:${TAG:-latest}' + image: 'volosoft/microservice-demo-product-service:${TAG:-latest}' build: context: ../../ dockerfile: samples/MicroserviceDemo/microservices/ProductService.Host/Dockerfile @@ -98,7 +98,7 @@ services: - redis auth-server: - image: 'microservice-demo/auth-server:${TAG:-latest}' + image: 'volosoft/microservice-demo-auth-server:${TAG:-latest}' build: context: ../../ dockerfile: samples/MicroserviceDemo/applications/AuthServer.Host/Dockerfile @@ -109,7 +109,7 @@ services: - identity-service backend-admin-app: - image: 'microservice-demo/backend-admin-app:${TAG:-latest}' + image: 'volosoft/microservice-demo-backend-admin-app:${TAG:-latest}' build: context: ../../ dockerfile: samples/MicroserviceDemo/applications/BackendAdminApp.Host/Dockerfile @@ -118,7 +118,7 @@ services: - backend-admin-app-gateway public-website: - image: 'microservice-demo/public-website:${TAG:-latest}' + image: 'volosoft/microservice-demo-public-website:${TAG:-latest}' build: context: ../../ dockerfile: samples/MicroserviceDemo/applications/PublicWebSite.Host/Dockerfile diff --git a/samples/MicroserviceDemo/gateways/BackendAdminAppGateway.Host/BackendAdminAppGateway.Host.csproj b/samples/MicroserviceDemo/gateways/BackendAdminAppGateway.Host/BackendAdminAppGateway.Host.csproj index abaca0567b..28bb533c46 100644 --- a/samples/MicroserviceDemo/gateways/BackendAdminAppGateway.Host/BackendAdminAppGateway.Host.csproj +++ b/samples/MicroserviceDemo/gateways/BackendAdminAppGateway.Host/BackendAdminAppGateway.Host.csproj @@ -8,7 +8,7 @@ true true false - true + diff --git a/samples/MicroserviceDemo/gateways/InternalGateway.Host/InternalGateway.Host.csproj b/samples/MicroserviceDemo/gateways/InternalGateway.Host/InternalGateway.Host.csproj index 80b4b6ebb0..dafa931f24 100644 --- a/samples/MicroserviceDemo/gateways/InternalGateway.Host/InternalGateway.Host.csproj +++ b/samples/MicroserviceDemo/gateways/InternalGateway.Host/InternalGateway.Host.csproj @@ -8,7 +8,7 @@ true true false - true + diff --git a/samples/MicroserviceDemo/gateways/PublicWebSiteGateway.Host/PublicWebSiteGateway.Host.csproj b/samples/MicroserviceDemo/gateways/PublicWebSiteGateway.Host/PublicWebSiteGateway.Host.csproj index cd41412f26..c04e6bdfaa 100644 --- a/samples/MicroserviceDemo/gateways/PublicWebSiteGateway.Host/PublicWebSiteGateway.Host.csproj +++ b/samples/MicroserviceDemo/gateways/PublicWebSiteGateway.Host/PublicWebSiteGateway.Host.csproj @@ -8,7 +8,7 @@ true true false - true + diff --git a/samples/MicroserviceDemo/microservices/BloggingService.Host/BloggingService.Host.csproj b/samples/MicroserviceDemo/microservices/BloggingService.Host/BloggingService.Host.csproj index 5d3dc6cddb..88671a50f4 100644 --- a/samples/MicroserviceDemo/microservices/BloggingService.Host/BloggingService.Host.csproj +++ b/samples/MicroserviceDemo/microservices/BloggingService.Host/BloggingService.Host.csproj @@ -8,7 +8,7 @@ true true false - true + diff --git a/samples/MicroserviceDemo/microservices/BloggingService.Host/BloggingServiceHostModule.cs b/samples/MicroserviceDemo/microservices/BloggingService.Host/BloggingServiceHostModule.cs index cd4fec848f..da076cf6e0 100644 --- a/samples/MicroserviceDemo/microservices/BloggingService.Host/BloggingServiceHostModule.cs +++ b/samples/MicroserviceDemo/microservices/BloggingService.Host/BloggingServiceHostModule.cs @@ -1,4 +1,5 @@ using System; +using System.IO; using System.Threading.Tasks; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.DataProtection; @@ -23,6 +24,7 @@ using Volo.Abp.SettingManagement.EntityFrameworkCore; using Volo.Abp.Threading; using Volo.Blogging; using Volo.Blogging.Blogs; +using Volo.Blogging.Files; using Volo.Blogging.MongoDB; namespace BloggingService.Host @@ -45,6 +47,7 @@ namespace BloggingService.Host public override void ConfigureServices(ServiceConfigurationContext context) { var configuration = context.Services.GetConfiguration(); + var hostingEnvironment = context.Services.GetHostingEnvironment(); context.Services.AddAuthentication("Bearer") .AddIdentityServerAuthentication(options => @@ -79,6 +82,12 @@ namespace BloggingService.Host options.UseSqlServer(); }); + Configure(options => + { + options.FileUploadLocalFolder = Path.Combine(hostingEnvironment.WebRootPath, "files"); + options.FileUploadUrlRoot = "/files/"; + }); + context.Services.AddDistributedRedisCache(options => { options.Configuration = configuration["Redis:Configuration"]; diff --git a/samples/MicroserviceDemo/microservices/IdentityService.Host/IdentityService.Host.csproj b/samples/MicroserviceDemo/microservices/IdentityService.Host/IdentityService.Host.csproj index 4be0500443..810c69a198 100644 --- a/samples/MicroserviceDemo/microservices/IdentityService.Host/IdentityService.Host.csproj +++ b/samples/MicroserviceDemo/microservices/IdentityService.Host/IdentityService.Host.csproj @@ -8,7 +8,7 @@ true true false - true + diff --git a/samples/MicroserviceDemo/microservices/ProductService.Host/ProductService.Host.csproj b/samples/MicroserviceDemo/microservices/ProductService.Host/ProductService.Host.csproj index 2b19692585..ce42a3f22e 100644 --- a/samples/MicroserviceDemo/microservices/ProductService.Host/ProductService.Host.csproj +++ b/samples/MicroserviceDemo/microservices/ProductService.Host/ProductService.Host.csproj @@ -8,7 +8,7 @@ true true false - true + diff --git a/templates/mvc/src/MyCompanyName.MyProjectName.Web/MyCompanyName.MyProjectName.Web.csproj b/templates/mvc/src/MyCompanyName.MyProjectName.Web/MyCompanyName.MyProjectName.Web.csproj index 37f3fceff8..846b262f32 100644 --- a/templates/mvc/src/MyCompanyName.MyProjectName.Web/MyCompanyName.MyProjectName.Web.csproj +++ b/templates/mvc/src/MyCompanyName.MyProjectName.Web/MyCompanyName.MyProjectName.Web.csproj @@ -9,7 +9,7 @@ true true false - true +