diff --git a/samples/MicroserviceDemo/.dockerignore b/.dockerignore similarity index 100% rename from samples/MicroserviceDemo/.dockerignore rename to .dockerignore diff --git a/abp_io/src/Volo.AbpWebSite.Web/AbpWebSiteWebModule.cs b/abp_io/src/Volo.AbpWebSite.Web/AbpWebSiteWebModule.cs index bbbce26cee..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; @@ -140,6 +151,8 @@ namespace Volo.AbpWebSite var app = context.GetApplicationBuilder(); var env = context.GetEnvironment(); + app.UseCorrelationId(); + app.UseAbpRequestLocalization(); if (env.IsDevelopment()) diff --git a/abp_io/src/Volo.AbpWebSite.Web/CorrelationIdLogEventEnricher.cs b/abp_io/src/Volo.AbpWebSite.Web/CorrelationIdLogEventEnricher.cs new file mode 100644 index 0000000000..ee7b0bd248 --- /dev/null +++ b/abp_io/src/Volo.AbpWebSite.Web/CorrelationIdLogEventEnricher.cs @@ -0,0 +1,28 @@ +using Serilog.Core; +using Serilog.Events; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Tracing; + +namespace Volo.AbpWebSite +{ + //This is for trial for now + public class CorrelationIdLogEventEnricher : ILogEventEnricher, ITransientDependency + { + private readonly ICorrelationIdProvider _correlationIdProvider; + + public CorrelationIdLogEventEnricher(ICorrelationIdProvider correlationIdProvider) + { + _correlationIdProvider = correlationIdProvider; + } + + public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory) + { + logEvent.AddOrUpdateProperty( + new LogEventProperty( + "CorrelationId", + new ScalarValue("CorrId:" + _correlationIdProvider.Get()) + ) + ); + } + } +} \ No newline at end of file diff --git a/abp_io/src/Volo.AbpWebSite.Web/Program.cs b/abp_io/src/Volo.AbpWebSite.Web/Program.cs index 0ae7a19900..7b458c66de 100644 --- a/abp_io/src/Volo.AbpWebSite.Web/Program.cs +++ b/abp_io/src/Volo.AbpWebSite.Web/Program.cs @@ -1,13 +1,37 @@ -using System.IO; +using System; +using System.IO; using Microsoft.AspNetCore.Hosting; +using Serilog; +using Serilog.Events; namespace Volo.AbpWebSite { public class Program { - public static void Main(string[] args) + public static int Main(string[] args) { - BuildWebHostInternal(args).Run(); + Log.Logger = new LoggerConfiguration() + .MinimumLevel.Debug() //TODO: Should be configurable! + .MinimumLevel.Override("Microsoft", LogEventLevel.Information) + .Enrich.FromLogContext() + .WriteTo.File("Logs/logs.txt") + .CreateLogger(); + + try + { + Log.Information("Starting web host."); + BuildWebHostInternal(args).Run(); + return 0; + } + catch (Exception ex) + { + Log.Fatal(ex, "Host terminated unexpectedly!"); + return 1; + } + finally + { + Log.CloseAndFlush(); + } } internal static IWebHost BuildWebHostInternal(string[] args) => @@ -16,6 +40,7 @@ namespace Volo.AbpWebSite .UseContentRoot(Directory.GetCurrentDirectory()) .UseIISIntegration() .UseStartup() + .UseSerilog() .Build(); } } diff --git a/abp_io/src/Volo.AbpWebSite.Web/Startup.cs b/abp_io/src/Volo.AbpWebSite.Web/Startup.cs index efbfda982a..ce76e2aa8c 100644 --- a/abp_io/src/Volo.AbpWebSite.Web/Startup.cs +++ b/abp_io/src/Volo.AbpWebSite.Web/Startup.cs @@ -3,7 +3,6 @@ using System.Text; using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; -using Serilog; using Volo.Abp; namespace Volo.AbpWebSite @@ -23,15 +22,6 @@ namespace Volo.AbpWebSite public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory) { - loggerFactory - .AddConsole() - .AddDebug() - .AddSerilog(new LoggerConfiguration() - .Enrich.FromLogContext() - .WriteTo.File("Logs/logs.txt") - .CreateLogger() - ); - Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); app.InitializeApplication(); diff --git a/abp_io/src/Volo.AbpWebSite.Web/Volo.AbpWebSite.Web.csproj b/abp_io/src/Volo.AbpWebSite.Web/Volo.AbpWebSite.Web.csproj index 8fbbbfd711..f73fb7942f 100644 --- a/abp_io/src/Volo.AbpWebSite.Web/Volo.AbpWebSite.Web.csproj +++ b/abp_io/src/Volo.AbpWebSite.Web/Volo.AbpWebSite.Web.csproj @@ -15,7 +15,7 @@ - + diff --git a/build/build.ps1 b/build/build.ps1 deleted file mode 100644 index c594ce2280..0000000000 --- a/build/build.ps1 +++ /dev/null @@ -1,50 +0,0 @@ -# COMMON PATHS - -$buildFolder = (Get-Item -Path "./" -Verbose).FullName -$slnFolder = Join-Path $buildFolder "../" -$outputFolder = Join-Path $buildFolder "outputs" -$abpDeskFolder = Join-Path $slnFolder "src/AbpDesk" -$abpDeskWebFolder = Join-Path $abpDeskFolder "AbpDesk.Web.Mvc" - -## CLEAR ###################################################################### - -Remove-Item $outputFolder -Force -Recurse -New-Item -Path $outputFolder -ItemType Directory - -## RESTORE NUGET PACKAGES ##################################################### - -Set-Location $slnFolder -dotnet restore - -## PUBLISH ASPDESK WEB ######################################################## - -Set-Location $abpDeskWebFolder -dotnet publish --output (Join-Path $outputFolder "AbpDesk/Web") - -New-Item -Path (Join-Path $outputFolder "AbpDesk/Web/PlugIns") -ItemType Directory -Copy-Item (Join-Path $abpDeskFolder "Web_PlugIns/*") (Join-Path $outputFolder "AbpDesk/Web/PlugIns/") - -## PUBLISH IDENTITY HTTP API HOST ############################################# - -Set-Location (Join-Path $slnFolder "src/Volo.Abp.Identity.HttpApi.Host") -dotnet publish --output (Join-Path $outputFolder "AbpIdentity/HttpApiHost") - -## CREATE DOCKER IMAGES ####################################################### - -Set-Location (Join-Path $outputFolder "AbpDesk/Web") - -docker rmi abpdesk/web -f -docker build -t abpdesk/web . - -Set-Location (Join-Path $outputFolder "AbpIdentity/HttpApiHost") - -docker rmi abpidentity/httpapihost -f -docker build -t abpidentity/httpapihost . - -## DOCKER COMPOSE FILES ####################################################### - -Copy-Item (Join-Path $slnFolder "docker/*.*") $outputFolder - -## FINALIZE ################################################################### - -Set-Location $outputFolder \ No newline at end of file diff --git a/common.props b/common.props index 040172c6d4..380276a7e1 100644 --- a/common.props +++ b/common.props @@ -1,7 +1,7 @@ latest - 0.13.0 + 0.14.0 $(NoWarn);CS1591 https://abp.io/assets/abp_nupkg.png https://abp.io diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml deleted file mode 100644 index 314feb5709..0000000000 --- a/docker/docker-compose.yml +++ /dev/null @@ -1,28 +0,0 @@ -version: '2' - -services: - - mongodb: - image: tutum/mongodb - environment: - - AUTH=no - ports: - - "27017:27017" - - "28017:28017" - - abpidentity_httpapihost: - image: abpidentity/httpapihost - environment: - - ASPNETCORE_ENVIRONMENT=Staging - - abpdesk_web: - image: abpdesk/web - environment: - - ASPNETCORE_ENVIRONMENT=Staging - - load_balancer: - image: haproxy:1.7.1 - volumes: - - "./haproxy.cfg:/usr/local/etc/haproxy/haproxy.cfg" - ports: - - "9005:8080" \ No newline at end of file diff --git a/docker/down.ps1 b/docker/down.ps1 deleted file mode 100644 index 508a7c4d74..0000000000 --- a/docker/down.ps1 +++ /dev/null @@ -1 +0,0 @@ -docker-compose down -v --rmi local \ No newline at end of file diff --git a/docker/haproxy.cfg b/docker/haproxy.cfg deleted file mode 100644 index 276ad980c8..0000000000 --- a/docker/haproxy.cfg +++ /dev/null @@ -1,18 +0,0 @@ -global - maxconn 4096 - -defaults - mode http - timeout connect 5s - timeout client 50s - timeout server 50s - -listen http-in - bind *:8080 - - server web-1 outputs_abpdesk_web_1:80 - server web-2 outputs_abpdesk_web_2:80 - - stats enable - stats uri /haproxy - stats refresh 1s \ No newline at end of file diff --git a/docker/up.ps1 b/docker/up.ps1 deleted file mode 100644 index 49ee1ee616..0000000000 --- a/docker/up.ps1 +++ /dev/null @@ -1,8 +0,0 @@ -docker rm $(docker ps -aq) -docker-compose up -d mongodb -docker-compose up -d abpidentity_httpapihost -docker-compose up -d abpdesk_web -sleep 2 -docker-compose scale abpdesk_web=2 -sleep 2 -docker-compose up -d load_balancer \ No newline at end of file diff --git a/docs/en/Blog-Posts/2019-02-22/Post.md b/docs/en/Blog-Posts/2019-02-22/Post.md new file mode 100644 index 0000000000..35f8219014 --- /dev/null +++ b/docs/en/Blog-Posts/2019-02-22/Post.md @@ -0,0 +1,54 @@ +# Microservice Demo, Projects Status and Road Map + +After [the first announcement](https://abp.io/blog/abp/Abp-vNext-Announcement) on the ABP vNext, we have a lot of improvements on the codebase (1100+ commits on the [GitHub repository](https://github.com/abpframework/abp)). We've created features, samples, documentation and much more. In this post, I want to inform you about some news and the status of the project. + +## Microservice Demo Solution + +One of the major goals of the ABP framework is to provide a [convenient infrastructure to create microservice solutions](https://abp.io/documents/abp/latest/Microservice-Architecture). + +We've been working to develop a microservice solution demo. Initial version was completed and [documented](https://abp.io/documents/abp/latest/Samples/Microservice-Demo). This sample solution aims to demonstrate a simple yet complete microservice solution; + +- Has multiple, independent, self-deployable **microservices**. +- Multiple **web applications**, each uses a different API gateway. +- Has multiple **gateways** / BFFs (Backend for Frontends) developed using the [Ocelot](https://github.com/ThreeMammals/Ocelot) library. +- Has an **authentication service** developed using the [IdentityServer](https://identityserver.io/) framework. It's also a SSO (Single Sign On) application with necessary UIs. +- Has **multiple databases**. Some microservices has their own database while some services/applications shares a database (to demonstrate different use cases). +- Has different types of databases: **SQL Server** (with **Entity Framework Core** ORM) and **MongoDB**. +- Has a **console application** to show the simplest way of using a service by authenticating. +- Uses [Redis](https://redis.io/) for **distributed caching**. +- Uses [RabbitMQ](https://www.rabbitmq.com/) for service-to-service **messaging**. +- Uses [Docker](https://www.docker.com/) & [Kubernates](https://kubernetes.io/) to **deploy** & run all services and applications. +- Uses [Elasticsearch](https://www.elastic.co/products/elasticsearch) & [Kibana](https://www.elastic.co/products/kibana) to store and visualize the logs (written using [Serilog](https://serilog.net/)). + +See [its documentation](https://abp.io/documents/abp/latest/Samples/Microservice-Demo) for a detailed explanation of the solution. + +## Improvements/Features + +We've worked on so many features including **distributed event bus** (with RabbitMQ integration), **IdentityServer4 integration** and enhancements for almost all features. We are continuously refactoring and adding tests to make the framework more stable and production ready. It is [rapidly growing](https://github.com/abpframework/abp/graphs/contributors). + +## Road Map + +There are still too much work to be done before the first stable release (v1.0). You can see [prioritized backlog items](https://github.com/abpframework/abp/issues?q=is%3Aopen+is%3Aissue+milestone%3ABacklog) on the GitHub repo. + +According to our estimation, we have planned to release v1.0 in Q2 of 2019 (probably in May or June). So, not too much time to wait. We are also very excited for the first stable release. + +We will also work on [the documentation](https://abp.io/documents/abp/latest) since it is far from complete now. + +First release may not include a SPA template. However, we want to prepare a simple one if it can be possible. Haven't decided yet about the SPA framework. Alternatives: **Angular, React and Blazor**. Please write your thought as a comment to this post. + +## Chinese Web Site + +There is a big ABP community in China. They have created a Chinese version of the abp.io web site: https://cn.abp.io/ They are keeping it up to date. Thanks to the Chinese developers and especially to [Liming Ma](https://github.com/maliming). + +## NDC {London} 2019 + +It was a pleasure to be in [NDC {London}](https://ndc-london.com/) 2019 as a partner. We've talked to many developers about the current ASP.NET Boilerplate and the ABP vNext and we got good feedbacks. + +We also had a chance to talk with [Scott Hanselman](https://twitter.com/shanselman) and [Jon Galloway](https://twitter.com/jongalloway). They visited our booth and we talked about the ideas for ABP vNext. They liked features, approaches and the goal of new ABP framework. See some photos and comments on twitter: + +![scott-and-jon](scott-and-jon.png) + +## Follow It + +* You can star and follow the **GitHub** repository: https://github.com/abpframework/abp +* You can follow the official **Twitter** account for news: https://twitter.com/abpframework \ No newline at end of file diff --git a/docs/en/Blog-Posts/2019-02-22/scott-and-jon.png b/docs/en/Blog-Posts/2019-02-22/scott-and-jon.png new file mode 100644 index 0000000000..79ad21aee7 Binary files /dev/null and b/docs/en/Blog-Posts/2019-02-22/scott-and-jon.png differ diff --git a/docs/en/Getting-Started-AspNetCore-Application.md b/docs/en/Getting-Started-AspNetCore-Application.md index fe1ff3450b..8d3c898787 100644 --- a/docs/en/Getting-Started-AspNetCore-Application.md +++ b/docs/en/Getting-Started-AspNetCore-Application.md @@ -152,6 +152,27 @@ services.AddApplication(options => }); ```` +4. Update `Program.cs` to not use the `WebHost.CreateDefaultBuilder()` method since it uses the default DI container: + +````csharp +public class Program +{ + public static void Main(string[] args) + { + BuildWebHostInternal(args).Run(); + } + + public static IWebHost BuildWebHostInternal(string[] args) => + new WebHostBuilder() + .UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) + .UseIISIntegration() + .UseStartup() + .Build(); +} +```` + ## Source Code Get source code of the sample project created in this tutorial from [here](https://github.com/abpframework/abp/tree/master/samples/BasicAspNetCoreApplication). + 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.Theme.Basic/AbpAspNetCoreMvcUIBasicThemeModule.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/AbpAspNetCoreMvcUIBasicThemeModule.cs index 07f24165e3..08d04cfed2 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/AbpAspNetCoreMvcUIBasicThemeModule.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/AbpAspNetCoreMvcUIBasicThemeModule.cs @@ -52,7 +52,9 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic .ScriptBundles .Add(BasicThemeBundles.Scripts.Global, bundle => { - bundle.AddBaseBundles(StandardBundles.Scripts.Global); + bundle + .AddBaseBundles(StandardBundles.Scripts.Global) + .AddContributors(typeof(BasicThemeGlobalScriptContributor)); }); }); } diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Bundling/BasicThemeGlobalScriptContributor.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Bundling/BasicThemeGlobalScriptContributor.cs new file mode 100644 index 0000000000..76ce8b2c05 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Bundling/BasicThemeGlobalScriptContributor.cs @@ -0,0 +1,12 @@ +using Volo.Abp.AspNetCore.Mvc.UI.Bundling; + +namespace Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Bundling +{ + public class BasicThemeGlobalScriptContributor : BundleContributor + { + public override void ConfigureBundle(BundleConfigurationContext context) + { + context.Files.Add("/themes/basic/layout.js"); + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Components/Menu/Default.cshtml b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Components/Menu/Default.cshtml index 2e7e7f3f4c..7bfe8f1730 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Components/Menu/Default.cshtml +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Components/Menu/Default.cshtml @@ -5,39 +5,36 @@ var elementId = string.IsNullOrEmpty(menuItem.ElementId) ? string.Empty : $"id=\"{menuItem.ElementId}\""; var cssClass = string.IsNullOrEmpty(menuItem.CssClass) ? string.Empty : menuItem.CssClass; var disabled = menuItem.IsDisabled ? "disabled" : string.Empty; - if (menuItem.IsLeaf) { - if (menuItem.Url == null) + @if (menuItem.Url != null) { - continue; - } - - + @menuItem.DisplayName + + + } } else { - } -} +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Components/Menu/_MenuItem.cshtml b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Components/Menu/_MenuItem.cshtml new file mode 100644 index 0000000000..fb8feb2ad8 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Components/Menu/_MenuItem.cshtml @@ -0,0 +1,36 @@ +@using Volo.Abp.UI.Navigation +@model ApplicationMenuItem +@{ + var elementId = string.IsNullOrEmpty(Model.ElementId) ? string.Empty : $"id=\"{Model.ElementId}\""; + var cssClass = string.IsNullOrEmpty(Model.CssClass) ? string.Empty : Model.CssClass; + var disabled = Model.IsDisabled ? "disabled" : string.Empty; +} +@if (Model.IsLeaf) +{ + @if (Model.Url != null) + { + + @Model.DisplayName + + } +} +else +{ + +} diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/wwwroot/themes/basic/layout.css b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/wwwroot/themes/basic/layout.css index ffcc7a2dae..3b5cc467fc 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/wwwroot/themes/basic/layout.css +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/wwwroot/themes/basic/layout.css @@ -10,3 +10,27 @@ body { text-decoration: none; color: #fff; } + +/* Main Menu */ + +.navbar .dropdown-submenu { + position: relative; +} + + .navbar .dropdown-submenu a { + padding: 0.25rem 1.4rem; + } + + .navbar .dropdown-submenu a::after { + transform: rotate(-90deg); + position: absolute; + right: 16px; + top: 18px; + } + + .navbar .dropdown-submenu .dropdown-menu { + top: 0; + left: 100%; + margin-left: .1rem; + margin-right: .1rem; + } diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/wwwroot/themes/basic/layout.js b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/wwwroot/themes/basic/layout.js new file mode 100644 index 0000000000..8a5b94c7c6 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/wwwroot/themes/basic/layout.js @@ -0,0 +1,16 @@ +$(function () { + $('.dropdown-menu a.dropdown-toggle').on('click', function (e) { + if (!$(this).next().hasClass('show')) { + $(this).parents('.dropdown-menu').first().find('.show').removeClass("show"); + } + + var $subMenu = $(this).next(".dropdown-menu"); + $subMenu.toggleClass('show'); + + $(this).parents('li.nav-item.dropdown.show').on('hidden.bs.dropdown', function (e) { + $('.dropdown-submenu .show').removeClass("show"); + }); + + return false; + }); +}); \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI/Volo/Abp/AspNetCore/Mvc/UI/Layout/ContentLayout.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI/Volo/Abp/AspNetCore/Mvc/UI/Layout/ContentLayout.cs index e5b169486f..bd54aef4d9 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI/Volo/Abp/AspNetCore/Mvc/UI/Layout/ContentLayout.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI/Volo/Abp/AspNetCore/Mvc/UI/Layout/ContentLayout.cs @@ -1,4 +1,7 @@ -namespace Volo.Abp.AspNetCore.Mvc.UI.Layout +using System; +using System.Linq; + +namespace Volo.Abp.AspNetCore.Mvc.UI.Layout { public class ContentLayout { @@ -12,5 +15,20 @@ { BreadCrumb = new BreadCrumb(); } + + public virtual bool ShouldShowBreadCrumb() + { + if (BreadCrumb.Items.Any()) + { + return true; + } + + if (BreadCrumb.ShowCurrent && !Title.IsNullOrEmpty()) + { + return true; + } + + return false; + } } } \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Threading/HttpContextCancellationTokenProvider.cs b/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Threading/HttpContextCancellationTokenProvider.cs index 4757ce2f93..c7c961176c 100644 --- a/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Threading/HttpContextCancellationTokenProvider.cs +++ b/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Threading/HttpContextCancellationTokenProvider.cs @@ -8,7 +8,7 @@ namespace Volo.Abp.AspNetCore.Threading [Dependency(ReplaceServices = true)] public class HttpContextCancellationTokenProvider : ICancellationTokenProvider, ITransientDependency { - public CancellationToken Token => _httpContextAccessor.HttpContext?.RequestAborted ?? default; + public CancellationToken Token => _httpContextAccessor.HttpContext?.RequestAborted ?? CancellationToken.None; private readonly IHttpContextAccessor _httpContextAccessor; diff --git a/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Tracing/AspNetCoreCorrelationIdProvider.cs b/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Tracing/AspNetCoreCorrelationIdProvider.cs index 78383d1252..e62f1ed2a4 100644 --- a/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Tracing/AspNetCoreCorrelationIdProvider.cs +++ b/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Tracing/AspNetCoreCorrelationIdProvider.cs @@ -27,18 +27,21 @@ namespace Volo.Abp.AspNetCore.Tracing return CreateNewCorrelationId(); } - lock (HttpContextAccessor.HttpContext.Request.Headers) - { - string correlationId = HttpContextAccessor.HttpContext.Request.Headers[Options.HttpHeaderName]; + string correlationId = HttpContextAccessor.HttpContext.Request.Headers[Options.HttpHeaderName]; - if (correlationId.IsNullOrEmpty()) + if (correlationId.IsNullOrEmpty()) + { + lock (HttpContextAccessor.HttpContext.Request.Headers) { - correlationId = CreateNewCorrelationId(); - HttpContextAccessor.HttpContext.Request.Headers[Options.HttpHeaderName] = correlationId; + if (correlationId.IsNullOrEmpty()) + { + correlationId = CreateNewCorrelationId(); + HttpContextAccessor.HttpContext.Request.Headers[Options.HttpHeaderName] = correlationId; + } } - - return correlationId; } + + return correlationId; } protected virtual string CreateNewCorrelationId() diff --git a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/IPermissionDefinitionContext.cs b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/IPermissionDefinitionContext.cs index 00680e0398..84530e2aa6 100644 --- a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/IPermissionDefinitionContext.cs +++ b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/IPermissionDefinitionContext.cs @@ -9,5 +9,7 @@ namespace Volo.Abp.Authorization.Permissions PermissionGroupDefinition GetGroupOrNull(string name); PermissionGroupDefinition AddGroup([NotNull] string name, ILocalizableString displayName = null); + + void RemoveGroup(string name); } } \ No newline at end of file diff --git a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/PermissionDefinitionContext.cs b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/PermissionDefinitionContext.cs index 59be1c2ad5..2fdb60b75f 100644 --- a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/PermissionDefinitionContext.cs +++ b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/PermissionDefinitionContext.cs @@ -36,5 +36,16 @@ namespace Volo.Abp.Authorization.Permissions return Groups[name]; } + public virtual void RemoveGroup(string name) + { + Check.NotNull(name, nameof(name)); + + if (!Groups.ContainsKey(name)) + { + throw new AbpException($"Not found permission group with name: {name}"); + } + + Groups.Remove(name); + } } } \ No newline at end of file diff --git a/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/CacheNameAttribute.cs b/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/CacheNameAttribute.cs index a9dbe670bb..d02bc76da5 100644 --- a/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/CacheNameAttribute.cs +++ b/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/CacheNameAttribute.cs @@ -15,5 +15,20 @@ namespace Volo.Abp.Caching Name = name; } + + public static string GetCacheName(Type cacheItemType) + { + var cacheNameAttribute = cacheItemType + .GetCustomAttributes(true) + .OfType() + .FirstOrDefault(); + + if (cacheNameAttribute != null) + { + return cacheNameAttribute.Name; + } + + return cacheItemType.FullName.RemovePostFix("CacheItem"); + } } } \ No newline at end of file diff --git a/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/DistributedCache.cs b/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/DistributedCache.cs index 708633b715..477b0c764b 100644 --- a/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/DistributedCache.cs +++ b/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/DistributedCache.cs @@ -127,7 +127,7 @@ namespace Volo.Abp.Caching return value; } - using (AsyncLock.Lock()) + using (AsyncLock.Lock(CancellationTokenProvider.Token)) { value = Get(key, hideErrors); if (value != null) @@ -326,13 +326,7 @@ namespace Volo.Abp.Caching protected virtual void SetDefaultOptions() { - //CacheName - var cacheNameAttribute = typeof(TCacheItem) - .GetCustomAttributes(true) - .OfType() - .FirstOrDefault(); - - CacheName = cacheNameAttribute != null ? cacheNameAttribute.Name : typeof(TCacheItem).FullName; + CacheName = CacheNameAttribute.GetCacheName(typeof(TCacheItem)); //IgnoreMultiTenancy IgnoreMultiTenancy = typeof(TCacheItem).IsDefined(typeof(IgnoreMultiTenancyAttribute), true); diff --git a/framework/src/Volo.Abp.Data/Volo/Abp/Data/HasExtraPropertiesExtensions.cs b/framework/src/Volo.Abp.Data/Volo/Abp/Data/HasExtraPropertiesExtensions.cs new file mode 100644 index 0000000000..a16373eedc --- /dev/null +++ b/framework/src/Volo.Abp.Data/Volo/Abp/Data/HasExtraPropertiesExtensions.cs @@ -0,0 +1,43 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using Volo.Abp.Reflection; + +namespace Volo.Abp.Data +{ + public static class HasExtraPropertiesExtensions + { + public static bool HasProperty(this IHasExtraProperties source, string name) + { + return source.ExtraProperties.ContainsKey(name); + } + + public static object GetProperty(this IHasExtraProperties source, string name) + { + return source.ExtraProperties?.GetOrDefault(name); + } + + public static TProperty GetProperty(this IHasExtraProperties source, string name) + { + var value = source.GetProperty(name); + if (value == default) + { + return default; + } + + if (TypeHelper.IsPrimitiveExtended(typeof(TProperty), includeEnums: true)) + { + return (TProperty)Convert.ChangeType(value, typeof(TProperty), CultureInfo.InvariantCulture); + } + + throw new AbpException("GetProperty does not support non-primitive types. Use non-generic GetProperty method and handle type casting manually."); + } + + public static TSource SetProperty(this TSource source, string name, object value) + where TSource : IHasExtraProperties + { + source.ExtraProperties[name] = value; + 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.Threading/Volo/Abp/Threading/NullCancellationTokenProvider.cs b/framework/src/Volo.Abp.Threading/Volo/Abp/Threading/NullCancellationTokenProvider.cs index 1d02ecb1e9..ab6320546c 100644 --- a/framework/src/Volo.Abp.Threading/Volo/Abp/Threading/NullCancellationTokenProvider.cs +++ b/framework/src/Volo.Abp.Threading/Volo/Abp/Threading/NullCancellationTokenProvider.cs @@ -6,7 +6,7 @@ namespace Volo.Abp.Threading { public static NullCancellationTokenProvider Instance { get; } = new NullCancellationTokenProvider(); - public CancellationToken Token { get; } = default; + public CancellationToken Token { get; } = CancellationToken.None; private NullCancellationTokenProvider() { diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Program.cs b/framework/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Program.cs index 7375376a06..e786c24f29 100644 --- a/framework/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Program.cs +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Program.cs @@ -1,4 +1,4 @@ -using Microsoft.AspNetCore; +using System.IO; using Microsoft.AspNetCore.Hosting; namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo @@ -11,7 +11,10 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo } public static IWebHost BuildWebHostInternal(string[] args) => - WebHost.CreateDefaultBuilder(args) + new WebHostBuilder() + .UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) + .UseIISIntegration() .UseStartup() .Build(); } diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Startup.cs b/framework/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Startup.cs index dd134465ee..52c11d7896 100644 --- a/framework/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Startup.cs +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Startup.cs @@ -1,8 +1,8 @@ using System; +using Autofac.Extensions.DependencyInjection; using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; -using Serilog; namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo { @@ -20,15 +20,6 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory) { - loggerFactory - .AddConsole() - .AddDebug() - .AddSerilog(new LoggerConfiguration() - .Enrich.FromLogContext() - .WriteTo.File("Logs/logs.txt") - .CreateLogger() - ); - app.InitializeApplication(); } } diff --git a/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/Authorization_Tests.cs b/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/Authorization_Tests.cs index 7c04a9cf1b..fa069348cd 100644 --- a/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/Authorization_Tests.cs +++ b/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/Authorization_Tests.cs @@ -50,7 +50,7 @@ namespace Volo.Abp.Authorization [Fact] public void Should_Permission_Definition_GetGroup() { - _permissionDefinitionManager.GetGroups().Count.ShouldBe(2); + _permissionDefinitionManager.GetGroups().Count.ShouldBe(1); } } } \ No newline at end of file diff --git a/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/TestServices/AuthorizationTestPermissionDefinitionProvider.cs b/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/TestServices/AuthorizationTestPermissionDefinitionProvider.cs index fdfd77b986..bb28b4ea99 100644 --- a/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/TestServices/AuthorizationTestPermissionDefinitionProvider.cs +++ b/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/TestServices/AuthorizationTestPermissionDefinitionProvider.cs @@ -13,6 +13,8 @@ namespace Volo.Abp.Authorization.TestServices } PermissionGroupDefinition group = context.AddGroup("TestGroup"); group.AddPermission("MyAuthorizedService1"); + + context.RemoveGroup("TestGetGroup"); } } } diff --git a/framework/test/Volo.Abp.Caching.Tests/Volo/Abp/Caching/AbpCachingTestModule.cs b/framework/test/Volo.Abp.Caching.Tests/Volo/Abp/Caching/AbpCachingTestModule.cs index 32a954d430..0f790a03a3 100644 --- a/framework/test/Volo.Abp.Caching.Tests/Volo/Abp/Caching/AbpCachingTestModule.cs +++ b/framework/test/Volo.Abp.Caching.Tests/Volo/Abp/Caching/AbpCachingTestModule.cs @@ -13,13 +13,14 @@ namespace Volo.Abp.Caching { option.CacheConfigurators.Add(cacheName => { - if (cacheName == typeof(Sail.Testing.Caching.PersonCacheItem).FullName) + if (cacheName == CacheNameAttribute.GetCacheName(typeof(Sail.Testing.Caching.PersonCacheItem))) { return new DistributedCacheEntryOptions() { AbsoluteExpiration = DateTime.Parse("2099-01-01 12:00:00") }; } + return null; }); diff --git a/framework/test/Volo.Abp.Caching.Tests/Volo/Abp/Caching/DistributedCache_ConfigureOptions_Test.cs b/framework/test/Volo.Abp.Caching.Tests/Volo/Abp/Caching/DistributedCache_ConfigureOptions_Test.cs index 9531711932..711e1e0bce 100644 --- a/framework/test/Volo.Abp.Caching.Tests/Volo/Abp/Caching/DistributedCache_ConfigureOptions_Test.cs +++ b/framework/test/Volo.Abp.Caching.Tests/Volo/Abp/Caching/DistributedCache_ConfigureOptions_Test.cs @@ -1,9 +1,7 @@ using Microsoft.Extensions.Caching.Distributed; using Shouldly; using System; -using System.Collections.Generic; using System.Reflection; -using System.Text; using System.Threading.Tasks; using Xunit; @@ -12,20 +10,11 @@ namespace Volo.Abp.Caching public class DistributedCache_ConfigureOptions_Test : AbpIntegratedTest { [Fact] - public async Task Configure_CacheOptions() + public void Configure_CacheOptions() { var personCache = GetRequiredService>(); - - var cacheKey = Guid.NewGuid().ToString(); - //Get (not exists yet) - var cacheItem = await personCache.GetAsync(cacheKey); - - cacheItem.ShouldBeNull(); - GetDefaultCachingOptions(personCache).SlidingExpiration.ShouldBeNull(); - GetDefaultCachingOptions(personCache).AbsoluteExpiration.ShouldBe(new DateTime(2099, 1, 1, 12, 0, 0)); - } [Fact] diff --git a/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/Domain/ExtraProperties_Tests.cs b/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/Domain/ExtraProperties_Tests.cs new file mode 100644 index 0000000000..eea827fbee --- /dev/null +++ b/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/Domain/ExtraProperties_Tests.cs @@ -0,0 +1,9 @@ +using Volo.Abp.TestApp.Testing; + +namespace Volo.Abp.EntityFrameworkCore.Domain +{ + public class ExtraProperties_Tests : ExtraProperties_Tests + { + + } +} diff --git a/framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/Domain/ExtraProperties_Tests.cs b/framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/Domain/ExtraProperties_Tests.cs new file mode 100644 index 0000000000..b389ad61b9 --- /dev/null +++ b/framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/Domain/ExtraProperties_Tests.cs @@ -0,0 +1,9 @@ +using Volo.Abp.TestApp.Testing; + +namespace Volo.Abp.MongoDB.Domain +{ + public class ExtraProperties_Tests : ExtraProperties_Tests + { + + } +} diff --git a/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/TestDataBuilder.cs b/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/TestDataBuilder.cs index f1bb42a344..25886f77a6 100644 --- a/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/TestDataBuilder.cs +++ b/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/TestDataBuilder.cs @@ -36,7 +36,7 @@ namespace Volo.Abp.TestApp { _cityRepository.Insert(new City(Guid.NewGuid(), "Tokyo")); _cityRepository.Insert(new City(Guid.NewGuid(), "Madrid")); - _cityRepository.Insert(new City(LondonCityId, "London")); + _cityRepository.Insert(new City(LondonCityId, "London") {ExtraProperties = { { "Population", 10_470_000 } } }); _cityRepository.Insert(new City(IstanbulCityId, "Istanbul")); _cityRepository.Insert(new City(Guid.NewGuid(), "Paris")); _cityRepository.Insert(new City(Guid.NewGuid(), "Washington")); diff --git a/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Testing/ExtraProperties_Tests.cs b/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Testing/ExtraProperties_Tests.cs new file mode 100644 index 0000000000..67eff13572 --- /dev/null +++ b/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Testing/ExtraProperties_Tests.cs @@ -0,0 +1,53 @@ +using System.Threading.Tasks; +using Shouldly; +using Volo.Abp.Data; +using Volo.Abp.Modularity; +using Volo.Abp.TestApp.Domain; +using Xunit; + +namespace Volo.Abp.TestApp.Testing +{ + public abstract class ExtraProperties_Tests : TestAppTestBase + where TStartupModule : IAbpModule + { + protected readonly ICityRepository CityRepository; + + protected ExtraProperties_Tests() + { + CityRepository = GetRequiredService(); + } + + [Fact] + public async Task Should_Get_An_Extra_Property() + { + var london = await CityRepository.FindByNameAsync("London"); + london.HasProperty("Population").ShouldBeTrue(); + london.GetProperty("Population").ShouldBe(10_470_000); + } + + [Fact] + public async Task Should_Add_An_Extra_Property() + { + var london = await CityRepository.FindByNameAsync("London"); + london.SetProperty("AreaAsKm", 1572); + await CityRepository.UpdateAsync(london); + + var london2 = await CityRepository.FindByNameAsync("London"); + london2.HasProperty("AreaAsKm").ShouldBeTrue(); + london2.GetProperty("AreaAsKm").ShouldBe(1572); + } + + [Fact] + public async Task Should_Update_An_Existing_Extra_Property() + { + var london = await CityRepository.FindByNameAsync("London"); + + london.ExtraProperties["Population"] = 11_000_042; + await CityRepository.UpdateAsync(london); + + var london2 = await CityRepository.FindByNameAsync("London"); + london2.HasProperty("Population").ShouldBeTrue(); + london2.GetProperty("Population").ShouldBe(11_000_042); + } + } +} 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/app/Volo.BloggingTestApp/Startup.cs b/modules/blogging/app/Volo.BloggingTestApp/Startup.cs index afeb27a51f..e4ccc31b60 100644 --- a/modules/blogging/app/Volo.BloggingTestApp/Startup.cs +++ b/modules/blogging/app/Volo.BloggingTestApp/Startup.cs @@ -3,7 +3,6 @@ using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; -using Serilog; using Volo.Abp; namespace Volo.BloggingTestApp @@ -22,14 +21,6 @@ namespace Volo.BloggingTestApp public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) { - loggerFactory - .AddConsole() - .AddDebug() - .AddSerilog(new LoggerConfiguration() - .Enrich.FromLogContext() - .WriteTo.File("Logs/logs.txt") - .CreateLogger() - ); app.InitializeApplication(); } diff --git a/modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/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 0ae80c8d42..9bdbb2fc0d 100644 --- a/modules/blogging/src/Volo.Blogging.Domain/Volo/Blogging/Users/BlogUser.cs +++ b/modules/blogging/src/Volo.Blogging.Domain/Volo/Blogging/Users/BlogUser.cs @@ -4,7 +4,7 @@ using Volo.Abp.Users; namespace Volo.Blogging.Users { - public class BlogUser : AggregateRoot, IUser + public class BlogUser : AggregateRoot, IUser, IUpdateUserData { public virtual Guid? TenantId { get; protected set; } @@ -30,17 +30,45 @@ namespace Volo.Blogging.Users public BlogUser(IUserData user) : base(user.Id) { - Email = user.Email; - Name = user.Name; - Surname = user.Surname; - EmailConfirmed = user.EmailConfirmed; - PhoneNumber = user.PhoneNumber; - PhoneNumberConfirmed = user.PhoneNumberConfirmed; - UserName = user.UserName; TenantId = user.TenantId; + UpdateInternal(user); + } + + public virtual bool Update(IUserData user) + { + if (Id != user.Id) + { + throw new ArgumentException($"Given User's Id '{user.Id}' does not match to this User's Id '{Id}'"); + } + + if (TenantId != user.TenantId) + { + throw new ArgumentException($"Given User's TenantId '{user.TenantId}' does not match to this User's TenantId '{TenantId}'"); + } + + if (Equals(user)) + { + return false; + } + + UpdateInternal(user); + return true; + } + + protected virtual bool Equals(IUserData user) + { + return Id == user.Id && + TenantId == user.TenantId && + UserName == user.UserName && + Name == user.Name && + Surname == user.Surname && + Email == user.Email && + EmailConfirmed == user.EmailConfirmed && + PhoneNumber == user.PhoneNumber && + PhoneNumberConfirmed == user.PhoneNumberConfirmed; } - public void Update(IUserData user) + protected virtual void UpdateInternal(IUserData user) { Email = user.Email; Name = user.Name; diff --git a/modules/blogging/src/Volo.Blogging.Domain/Volo/Blogging/Users/BlogUserLookupService.cs b/modules/blogging/src/Volo.Blogging.Domain/Volo/Blogging/Users/BlogUserLookupService.cs index 7ef29a026d..9bf0025570 100644 --- a/modules/blogging/src/Volo.Blogging.Domain/Volo/Blogging/Users/BlogUserLookupService.cs +++ b/modules/blogging/src/Volo.Blogging.Domain/Volo/Blogging/Users/BlogUserLookupService.cs @@ -12,6 +12,7 @@ namespace Volo.Blogging.Users userRepository, unitOfWorkManager) { + } protected override BlogUser CreateUser(IUserData externalUser) diff --git a/modules/blogging/src/Volo.Blogging.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/modules/docs/app/Volo.DocsTestApp/Program.cs b/modules/docs/app/Volo.DocsTestApp/Program.cs index 57ff6be5ea..470b5e6a98 100644 --- a/modules/docs/app/Volo.DocsTestApp/Program.cs +++ b/modules/docs/app/Volo.DocsTestApp/Program.cs @@ -1,13 +1,37 @@ -using System.IO; +using System; +using System.IO; using Microsoft.AspNetCore.Hosting; +using Serilog; +using Serilog.Events; namespace Volo.DocsTestApp { public class Program { - public static void Main(string[] args) + public static int Main(string[] args) { - BuildWebHostInternal(args).Run(); + Log.Logger = new LoggerConfiguration() + .MinimumLevel.Debug() //TODO: Should be configurable! + .MinimumLevel.Override("Microsoft", LogEventLevel.Information) + .Enrich.FromLogContext() + .WriteTo.File("Logs/logs.txt") + .CreateLogger(); + + try + { + Log.Information("Starting web host."); + BuildWebHostInternal(args).Run(); + return 0; + } + catch (Exception ex) + { + Log.Fatal(ex, "Host terminated unexpectedly!"); + return 1; + } + finally + { + Log.CloseAndFlush(); + } } public static IWebHost BuildWebHostInternal(string[] args) => @@ -16,6 +40,7 @@ namespace Volo.DocsTestApp .UseContentRoot(Directory.GetCurrentDirectory()) .UseIISIntegration() .UseStartup() + .UseSerilog() .Build(); } } diff --git a/modules/docs/app/Volo.DocsTestApp/Startup.cs b/modules/docs/app/Volo.DocsTestApp/Startup.cs index e989f904cf..e6708d26e8 100644 --- a/modules/docs/app/Volo.DocsTestApp/Startup.cs +++ b/modules/docs/app/Volo.DocsTestApp/Startup.cs @@ -3,7 +3,6 @@ using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; -using Serilog; using Volo.Abp; namespace Volo.DocsTestApp @@ -22,15 +21,6 @@ namespace Volo.DocsTestApp public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) { - loggerFactory - .AddConsole() - .AddDebug() - .AddSerilog(new LoggerConfiguration() - .Enrich.FromLogContext() - .WriteTo.File("Logs/logs.txt") - .CreateLogger() - ); - app.InitializeApplication(); } } diff --git a/modules/docs/app/Volo.DocsTestApp/Volo.DocsTestApp.csproj b/modules/docs/app/Volo.DocsTestApp/Volo.DocsTestApp.csproj index d1065535b9..b24f84e537 100644 --- a/modules/docs/app/Volo.DocsTestApp/Volo.DocsTestApp.csproj +++ b/modules/docs/app/Volo.DocsTestApp/Volo.DocsTestApp.csproj @@ -11,7 +11,7 @@ - + diff --git a/modules/docs/src/Volo.Docs.Admin.Web/DocsAdminWebAutoMapperProfile.cs b/modules/docs/src/Volo.Docs.Admin.Web/DocsAdminWebAutoMapperProfile.cs index 1105fe94aa..9bf81c2140 100644 --- a/modules/docs/src/Volo.Docs.Admin.Web/DocsAdminWebAutoMapperProfile.cs +++ b/modules/docs/src/Volo.Docs.Admin.Web/DocsAdminWebAutoMapperProfile.cs @@ -14,7 +14,7 @@ namespace Volo.Docs.Admin CreateMap().Ignore(x => x.ExtraProperties); CreateMap () - .Ignore(x => x.GitHubAccessToken).Ignore(x => x.GitHubRootUrl); + .Ignore(x => x.GitHubAccessToken).Ignore(x => x.GitHubRootUrl).Ignore(x => x.GitHubUserAgent); } } } diff --git a/modules/docs/src/Volo.Docs.Admin.Web/Pages/Docs/Admin/Projects/Create.cshtml.cs b/modules/docs/src/Volo.Docs.Admin.Web/Pages/Docs/Admin/Projects/Create.cshtml.cs index 3b8f8836f2..7df02eec0c 100644 --- a/modules/docs/src/Volo.Docs.Admin.Web/Pages/Docs/Admin/Projects/Create.cshtml.cs +++ b/modules/docs/src/Volo.Docs.Admin.Web/Pages/Docs/Admin/Projects/Create.cshtml.cs @@ -60,6 +60,7 @@ namespace Volo.Docs.Admin.Pages.Docs.Admin.Projects dto.ExtraProperties = new Dictionary { {nameof(GithubProject.GitHubRootUrl), GithubProject.GitHubRootUrl}, + {nameof(GithubProject.GitHubUserAgent), GithubProject.GitHubUserAgent}, {nameof(GithubProject.GitHubAccessToken), GithubProject.GitHubAccessToken} }; @@ -109,6 +110,10 @@ namespace Volo.Docs.Admin.Pages.Docs.Admin.Projects [DisplayOrder(10001)] [StringLength(512)] public string GitHubAccessToken { get; set; } + + [DisplayOrder(10002)] + [StringLength(64)] + public string GitHubUserAgent { get; set; } } } } \ No newline at end of file diff --git a/modules/docs/src/Volo.Docs.Admin.Web/Pages/Docs/Admin/Projects/Edit.cshtml.cs b/modules/docs/src/Volo.Docs.Admin.Web/Pages/Docs/Admin/Projects/Edit.cshtml.cs index ddcda61058..77c5321ad0 100644 --- a/modules/docs/src/Volo.Docs.Admin.Web/Pages/Docs/Admin/Projects/Edit.cshtml.cs +++ b/modules/docs/src/Volo.Docs.Admin.Web/Pages/Docs/Admin/Projects/Edit.cshtml.cs @@ -62,6 +62,7 @@ namespace Volo.Docs.Admin.Pages.Docs.Admin.Projects dto.ExtraProperties = new Dictionary { {nameof(GithubProject.GitHubRootUrl), GithubProject.GitHubRootUrl}, + {nameof(GithubProject.GitHubUserAgent), GithubProject.GitHubUserAgent}, {nameof(GithubProject.GitHubAccessToken), GithubProject.GitHubAccessToken} }; @@ -74,6 +75,7 @@ namespace Volo.Docs.Admin.Pages.Docs.Admin.Projects GithubProject.GitHubAccessToken = (string) dto.ExtraProperties[nameof(GithubProject.GitHubAccessToken)]; GithubProject.GitHubRootUrl = (string) dto.ExtraProperties[nameof(GithubProject.GitHubRootUrl)]; + GithubProject.GitHubUserAgent = (string) dto.ExtraProperties[nameof(GithubProject.GitHubUserAgent)]; } public abstract class EditProjectViewModelBase @@ -116,6 +118,11 @@ namespace Volo.Docs.Admin.Pages.Docs.Admin.Projects [DisplayOrder(10001)] [StringLength(512)] public string GitHubAccessToken { get; set; } + + + [DisplayOrder(10002)] + [StringLength(64)] + public string GitHubUserAgent { get; set; } } } } \ No newline at end of file diff --git a/modules/docs/src/Volo.Docs.Application/Volo/Docs/Documents/DocumentAppService.cs b/modules/docs/src/Volo.Docs.Application/Volo/Docs/Documents/DocumentAppService.cs index b25ef2d76e..4aaedd579b 100644 --- a/modules/docs/src/Volo.Docs.Application/Volo/Docs/Documents/DocumentAppService.cs +++ b/modules/docs/src/Volo.Docs.Application/Volo/Docs/Documents/DocumentAppService.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; using Microsoft.Extensions.Caching.Distributed; +using Microsoft.Extensions.Logging; using Volo.Abp.Application.Services; using Volo.Abp.Caching; using Volo.Docs.Projects; @@ -94,15 +95,16 @@ namespace Volo.Docs.Documents cacheKey, async () => { + Logger.LogInformation($"Not found in the cache. Requesting {documentName} from the store..."); var store = _documentStoreFactory.Create(project.DocumentStoreType); - var document = await store.GetDocument(project, documentName, version); - + var document = await store.GetDocumentAsync(project, documentName, version); + Logger.LogInformation($"Document retrieved: {documentName}"); return CreateDocumentWithDetailsDto(project, document); }, () => new DistributedCacheEntryOptions { //TODO: Configurable? - AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(6), + AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(2), SlidingExpiration = TimeSpan.FromMinutes(30) } ); diff --git a/modules/docs/src/Volo.Docs.Application/Volo/Docs/Projects/ProjectAppService.cs b/modules/docs/src/Volo.Docs.Application/Volo/Docs/Projects/ProjectAppService.cs index 20970ade0d..02ffac9ee9 100644 --- a/modules/docs/src/Volo.Docs.Application/Volo/Docs/Projects/ProjectAppService.cs +++ b/modules/docs/src/Volo.Docs.Application/Volo/Docs/Projects/ProjectAppService.cs @@ -68,7 +68,7 @@ namespace Volo.Docs.Projects protected virtual async Task> GetVersionsAsync(Project project) { var store = _documentStoreFactory.Create(project.DocumentStoreType); - var versions = await store.GetVersions(project); + var versions = await store.GetVersionsAsync(project); if (!versions.Any()) { diff --git a/modules/docs/src/Volo.Docs.Domain/Volo/Docs/Documents/IDocumentStore.cs b/modules/docs/src/Volo.Docs.Domain/Volo/Docs/Documents/IDocumentStore.cs index 8ab0b3f332..1b001303b5 100644 --- a/modules/docs/src/Volo.Docs.Domain/Volo/Docs/Documents/IDocumentStore.cs +++ b/modules/docs/src/Volo.Docs.Domain/Volo/Docs/Documents/IDocumentStore.cs @@ -7,9 +7,9 @@ namespace Volo.Docs.Documents { public interface IDocumentStore : IDomainService { - Task GetDocument(Project project, string documentName, string version); + Task GetDocumentAsync(Project project, string documentName, string version); - Task> GetVersions(Project project); + Task> GetVersionsAsync(Project project); Task GetResource(Project project, string resourceName, string version); } diff --git a/modules/docs/src/Volo.Docs.Domain/Volo/Docs/FileSystem/Documents/FileSystemDocumentStore.cs b/modules/docs/src/Volo.Docs.Domain/Volo/Docs/FileSystem/Documents/FileSystemDocumentStore.cs index a8366554dc..87a74d1702 100644 --- a/modules/docs/src/Volo.Docs.Domain/Volo/Docs/FileSystem/Documents/FileSystemDocumentStore.cs +++ b/modules/docs/src/Volo.Docs.Domain/Volo/Docs/FileSystem/Documents/FileSystemDocumentStore.cs @@ -14,7 +14,7 @@ namespace Volo.Docs.FileSystem.Documents { public const string Type = "FileSystem"; - public async Task GetDocument(Project project, string documentName, string version) + public async Task GetDocumentAsync(Project project, string documentName, string version) { var projectFolder = project.GetFileSystemPath(); var path = Path.Combine(projectFolder, documentName); @@ -41,7 +41,7 @@ namespace Volo.Docs.FileSystem.Documents }; } - public Task> GetVersions(Project project) + public Task> GetVersionsAsync(Project project) { return Task.FromResult(new List()); } diff --git a/modules/docs/src/Volo.Docs.Domain/Volo/Docs/GitHub/Documents/GithubDocumentStore.cs b/modules/docs/src/Volo.Docs.Domain/Volo/Docs/GitHub/Documents/GithubDocumentStore.cs index c9d8b63dd9..5baa073b9c 100644 --- a/modules/docs/src/Volo.Docs.Domain/Volo/Docs/GitHub/Documents/GithubDocumentStore.cs +++ b/modules/docs/src/Volo.Docs.Domain/Volo/Docs/GitHub/Documents/GithubDocumentStore.cs @@ -22,7 +22,7 @@ namespace Volo.Docs.GitHub.Documents { public const string Type = "GitHub"; - public virtual async Task GetDocument(Project project, string documentName, string version) + public virtual async Task GetDocumentAsync(Project project, string documentName, string version) { var token = project.GetGitHubAccessTokenOrNull(); var rootUrl = project.GetGitHubUrl(version); @@ -50,13 +50,14 @@ namespace Volo.Docs.GitHub.Documents Format = project.Format, LocalDirectory = localDirectory, FileName = fileName, - Contributors = !isNavigationDocument ? await GetContributors(commitHistoryUrl, token, userAgent): new List(), + Contributors = new List(), + //Contributors = !isNavigationDocument ? await GetContributors(commitHistoryUrl, token, userAgent): new List(), Version = version, Content = await DownloadWebContentAsStringAsync(rawDocumentUrl, token, userAgent) }; } - public async Task> GetVersions(Project project) + public async Task> GetVersionsAsync(Project project) { List versions; try @@ -148,13 +149,18 @@ namespace Volo.Docs.GitHub.Documents { try { - using (var webClient = new WebClient()) + Logger.LogInformation("Downloading content from Github (DownloadWebContentAsStringAsync): " + rawUrl); + + using (var webClient = new GithubWebClient()) { if (!token.IsNullOrWhiteSpace()) { webClient.Headers.Add("Authorization", "token " + token); } + webClient.Headers.Add("User-Agent", userAgent ?? ""); + + //TODO: SET TIMEOUT? return await webClient.DownloadStringTaskAsync(new Uri(rawUrl)); } @@ -171,7 +177,9 @@ namespace Volo.Docs.GitHub.Documents { try { - using (var webClient = new WebClient()) + Logger.LogInformation("Downloading content from Github (DownloadWebContentAsByteArrayAsync): " + rawUrl); + + using (var webClient = new GithubWebClient()) { if (!token.IsNullOrWhiteSpace()) { @@ -219,8 +227,7 @@ namespace Volo.Docs.GitHub.Documents { Logger.LogWarning(ex.Message); } - - + return contributors; } @@ -230,5 +237,21 @@ namespace Volo.Docs.GitHub.Documents .Replace("github.com", "raw.githubusercontent.com") .ReplaceFirst("/tree/", "/"); } + + private class GithubWebClient : WebClient + { + protected override WebRequest GetWebRequest(Uri address) + { + var webRequest = base.GetWebRequest(address); + if (webRequest == null) + { + return null; + } + + webRequest.Timeout = 15000; + + return webRequest; + } + } } } \ No newline at end of file diff --git a/modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/Index.cshtml b/modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/Index.cshtml index a070bddecc..55c1752994 100644 --- a/modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/Index.cshtml +++ b/modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/Index.cshtml @@ -45,7 +45,7 @@