diff --git a/docs/en/Entity-Framework-Core-MySQL.md b/docs/en/Entity-Framework-Core-MySQL.md index d1fa66e1a4..a04d6073fc 100644 --- a/docs/en/Entity-Framework-Core-MySQL.md +++ b/docs/en/Entity-Framework-Core-MySQL.md @@ -25,23 +25,6 @@ MySQL connection strings are different than SQL Server connection strings. So, c You typically will change the `appsettings.json` inside the `.DbMigrator` and `.Web` projects, but it depends on your solution structure. -## Change the Migrations DbContext - -MySQL DBMS has some slight differences than the SQL Server. Some module database mapping configuration (especially the field lengths) causes problems with MySQL. For example, some of the the [IdentityServer module](Modules/IdentityServer.md) tables has such problems and it provides an option to configure the fields based on your DBMS. - -The startup template contains a *YourProjectName*MigrationsDbContext which is responsible to maintain and migrate the database schema. This DbContext basically calls extension methods of the depended modules to configure their database tables. - -Open the *YourProjectName*MigrationsDbContext and change the `builder.ConfigureIdentityServer();` line as shown below: - -````csharp -builder.ConfigureIdentityServer(options => -{ - options.DatabaseProvider = EfCoreDatabaseProvider.MySql; -}); -```` - -Then `ConfigureIdentityServer()` method will set the field lengths to not exceed the MySQL limits. Refer to related module documentation if you have any problem while creating or executing the database migrations. - ## Re-Generate the Migrations The startup template uses [Entity Framework Core's Code First Migrations](https://docs.microsoft.com/en-us/ef/core/managing-schemas/migrations/). EF Core Migrations depend on the selected DBMS provider. So, changing the DBMS provider will cause the migration fails. diff --git a/docs/en/Entity-Framework-Core-Oracle.md b/docs/en/Entity-Framework-Core-Oracle.md new file mode 100644 index 0000000000..c6b520bde7 --- /dev/null +++ b/docs/en/Entity-Framework-Core-Oracle.md @@ -0,0 +1,67 @@ +# Switch to EF Core Oracle Provider + +This document explains how to switch to the **Oracle** database provider for **[the application startup template](Startup-Templates/Application.md)** which comes with SQL Server provider pre-configured. + +> This document uses a paid library of [Devart](https://www.devart.com/dotconnect/oracle/) company, because it is the only library for Oracle that supports EF Core 3.x. + +## Replace the Volo.Abp.EntityFrameworkCore.SqlServer Package + +`.EntityFrameworkCore` project in the solution depends on the [Volo.Abp.EntityFrameworkCore.SqlServer](https://www.nuget.org/packages/Volo.Abp.EntityFrameworkCore.SqlServer) NuGet package. Remove this package and add the same version of the [Volo.Abp.EntityFrameworkCore.Oracle.Devart](https://www.nuget.org/packages/Volo.Abp.EntityFrameworkCore.Oracle.Devart) package. + +## Replace the Module Dependency + +Find ***YourProjectName*EntityFrameworkCoreModule** class inside the `.EntityFrameworkCore` project, remove `typeof(AbpEntityFrameworkCoreSqlServerModule)` from the `DependsOn` attribute, add `typeof(AbpEntityFrameworkCoreOracleDevartModule)` (also replace `using Volo.Abp.EntityFrameworkCore.SqlServer;` with `using Volo.Abp.EntityFrameworkCore.Oracle.Devart;`). + +## UseOracle() + +Find `UseSqlServer()` calls in your solution, replace with `UseOracle()`. Check the following files: + +* *YourProjectName*EntityFrameworkCoreModule.cs inside the `.EntityFrameworkCore` project. +* *YourProjectName*MigrationsDbContextFactory.cs inside the `.EntityFrameworkCore.DbMigrations` project. + + +In the `CreateDbContext()` method of the *YourProjectName*MigrationsDbContextFactory.cs, replace the following code block + +``` +var builder = new DbContextOptionsBuilder() + .UseSqlServer(configuration.GetConnectionString("Default")); +``` + +with this one +``` +var builder = (DbContextOptionsBuilder) + new DbContextOptionsBuilder().UseOracle + ( + configuration.GetConnectionString("Default") + ); +``` + +> Depending on your solution structure, you may find more code files need to be changed. + +## Change the Connection Strings + +Oracle connection strings are different than SQL Server connection strings. So, check all `appsettings.json` files in your solution and replace the connection strings inside them. See the [connectionstrings.com]( https://www.connectionstrings.com/mysql/ ) for details of Oracle connection string options. + +You typically will change the `appsettings.json` inside the `.DbMigrator` and `.Web` projects, but it depends on your solution structure. + +A sample connection string for Oracle: +``` +Data Source=localhost;User Id=myuser;Password=mypassword; +``` + +## Re-Generate the Migrations + +The startup template uses [Entity Framework Core's Code First Migrations](https://docs.microsoft.com/en-us/ef/core/managing-schemas/migrations/) by default. +EF Core Migrations depend on the selected DBMS provider. Changing the DBMS provider, may not work with the existing migrations. + +* Delete the `Migrations` folder under the `.EntityFrameworkCore.DbMigrations` project and re-build the solution. +* Run `Add-Migration "Initial"` on the Package Manager Console window (select the `.DbMigrator` (or `.Web`) project as the startup project in the Solution Explorer and select the `.EntityFrameworkCore.DbMigrations` project as the default project in the Package Manager Console). + +This will scaffold a new migration for Oracle. + +Run the `.DbMigrator` project to create the database, apply the changes and seed the initial data. + +## Run the Application + +It is ready. Just run the application and enjoy coding. + diff --git a/docs/en/Entity-Framework-Core-Other-DBMS.md b/docs/en/Entity-Framework-Core-Other-DBMS.md index d902a8d93e..d9190df844 100644 --- a/docs/en/Entity-Framework-Core-Other-DBMS.md +++ b/docs/en/Entity-Framework-Core-Other-DBMS.md @@ -6,6 +6,7 @@ ABP framework provides **integration packages** for some common DBMSs to make th * [MySQL](Entity-Framework-Core-MySQL.md) * [PostgreSQL](Entity-Framework-Core-PostgreSQL.md) +* [Oracle](Entity-Framework-Core-Oracle.md) * [SQLite](Entity-Framework-Core-SQLite.md) However, you can configure your DBMS provider **without** these integration packages. While using the integration package is always recommended (it also makes standard for the depended version across different modules), you can do it manually if there is no integration package for your DBMS provider. diff --git a/docs/en/UI/Angular/Localization.md b/docs/en/UI/Angular/Localization.md index d627f466ba..c8b73b08f3 100644 --- a/docs/en/UI/Angular/Localization.md +++ b/docs/en/UI/Angular/Localization.md @@ -130,6 +130,68 @@ this.store.selectSnapshot( Localization resources are stored in the `localization` property of `ConfigState`. +## RTL Support + +As of v2.9 ABP has RTL support. If you are generating a new project with v2.9 and above, everything is set, you do not need to do any changes. If you are migrating your project from an earlier version, please follow the 2 steps below: + +#### Step 1. Create Chunks for Bootstrap LTR and RTL + +Find [styles configuration in angular.json](https://angular.io/guide/workspace-config#style-script-config) and make sure the chunks in your project has `bootstrap-rtl.min` and `bootstrap-ltr.min` as shown below. + +```json +{ + "projects": { + "MyProjectName": { + "architect": { + "build": { + "options": { + "styles": [ + { + "input": "node_modules/@abp/ng.theme.shared/styles/bootstrap-rtl.min.css", + "inject": false, + "bundleName": "bootstrap-rtl.min" + }, + { + "input": "node_modules/bootstrap/dist/css/bootstrap.min.css", + "inject": true, + "bundleName": "bootstrap-ltr.min" + }, + { + "input": "node_modules/@fortawesome/fontawesome-free/css/all.min.css", + "inject": true, + "bundleName": "fontawesome-all.min" + }, + { + "input": "node_modules/@fortawesome/fontawesome-free/css/v4-shims.min.css", + "inject": true, + "bundleName": "fontawesome-v4-shims.min" + }, + "apps/dev-app/src/styles.scss" + ], + } + } + } + } + } +} + +#### Step 2. Clear Lazy Loaded Fontawesome in AppComponent + +If you have created and injected chunks for Fontawesome as seen above, you no longer need the lazy loading in the `AppComponent` which was implemented before v2.9. Simply remove them. The `AppComponent` in the template of the new version looks like this: + +```js +import { Component } from '@angular/core'; + +@Component({ + selector: 'app-root', + template: ` + + + `, +}) +export class AppComponent {} +``` + ## See Also diff --git a/docs/en/docs-nav.json b/docs/en/docs-nav.json index 58e477cec9..9db7916f11 100644 --- a/docs/en/docs-nav.json +++ b/docs/en/docs-nav.json @@ -440,6 +440,10 @@ "text": "To PostgreSQL", "path": "Entity-Framework-Core-PostgreSQL.md" }, + { + "text": "To Oracle", + "path": "Entity-Framework-Core-Oracle.md" + }, { "text": "To SQLite", "path": "Entity-Framework-Core-SQLite.md" diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/ApplicationConfigurationDto.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/ApplicationConfigurationDto.cs index da99a3e948..0001f56c1c 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/ApplicationConfigurationDto.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/ApplicationConfigurationDto.cs @@ -1,6 +1,7 @@ using System; using Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations.ObjectExtending; using Volo.Abp.AspNetCore.Mvc.MultiTenancy; +using Volo.Abp.Timing; namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations { @@ -21,6 +22,10 @@ namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations public CurrentTenantDto CurrentTenant { get; set; } + public TimingDto Timing { get; set; } + + public ClockDto Clock { get; set; } + public ObjectExtensionsDto ObjectExtensions { get; set; } } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/ClockDto.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/ClockDto.cs new file mode 100644 index 0000000000..2cff97f814 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/ClockDto.cs @@ -0,0 +1,7 @@ +namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations +{ + public class ClockDto + { + public string Kind { get; set; } + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/TimingDto.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/TimingDto.cs new file mode 100644 index 0000000000..bd95bfba0d --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/TimingDto.cs @@ -0,0 +1,24 @@ +namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations +{ + public class TimingDto + { + public TimeZone TimeZone { get; set; } + } + + public class TimeZone + { + public IanaTimeZone Iana { get; set; } + + public WindowsTimeZone Windows { get; set; } + } + + public class WindowsTimeZone + { + public string TimeZoneId { get; set; } + } + + public class IanaTimeZone + { + public string TimeZoneName { get; set; } + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/bootstrap/dom-event-handlers.js b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/bootstrap/dom-event-handlers.js index 6b8ed10120..b9e1880e36 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/bootstrap/dom-event-handlers.js +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/bootstrap/dom-event-handlers.js @@ -50,20 +50,28 @@ scriptObject.initDom && scriptObject.initDom($el); }); } - - abp.dom.onNodeAdded(function (args) { - args.$el.findWithSelf('[data-toggle="tooltip"]').tooltip({ + + function initializeToolTip($tooltips){ + $tooltips.tooltip({ container: 'body' }); + } - args.$el.findWithSelf('[data-toggle="popover"]').popover({ + function initializePopover($popovers){ + $popovers.popover({ container: 'body' }); + } - args.$el.findWithSelf('.timeago').timeago(); + function initializeTimeAgo($timeagos){ + $timeagos.timeago(); + } + abp.dom.onNodeAdded(function (args) { + initializeToolTip(args.$el.findWithSelf('[data-toggle="tooltip"]')); + initializePopover(args.$el.findWithSelf('[data-toggle="popover"]')); + initializeTimeAgo(args.$el.findWithSelf('.timeago')); enableFormFeatures(args.$el.findWithSelf('form'), true); - initializeScript(args.$el); }); @@ -74,18 +82,10 @@ }); $(function () { + initializeToolTip($('[data-toggle="tooltip"]')); + initializePopover($('[data-toggle="popover"]')); + initializeTimeAgo($('.timeago')); enableFormFeatures($('form')); - - $('[data-toggle="tooltip"]').tooltip({ - container: 'body' - }); - - $('[data-toggle="popover"]').popover({ - container: 'body' - }); - - $('.timeago').timeago(); - $('[data-auto-focus="true"]').first().findWithSelf('input,select').focus(); }); diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/AbpApplicationConfigurationAppService.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/AbpApplicationConfigurationAppService.cs index a68a38b274..f68d4d1450 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/AbpApplicationConfigurationAppService.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/AbpApplicationConfigurationAppService.cs @@ -15,6 +15,7 @@ using Volo.Abp.Features; using Volo.Abp.Localization; using Volo.Abp.MultiTenancy; using Volo.Abp.Settings; +using Volo.Abp.Timing; using Volo.Abp.Users; namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations @@ -31,6 +32,8 @@ namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations private readonly ISettingDefinitionManager _settingDefinitionManager; private readonly IFeatureDefinitionManager _featureDefinitionManager; private readonly ILanguageProvider _languageProvider; + private readonly ITimezoneProvider _timezoneProvider; + private readonly AbpClockOptions _abpClockOptions; private readonly ICachedObjectExtensionsDtoService _cachedObjectExtensionsDtoService; public AbpApplicationConfigurationAppService( @@ -44,6 +47,8 @@ namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations ISettingDefinitionManager settingDefinitionManager, IFeatureDefinitionManager featureDefinitionManager, ILanguageProvider languageProvider, + ITimezoneProvider timezoneProvider, + IOptions abpClockOptions, ICachedObjectExtensionsDtoService cachedObjectExtensionsDtoService) { _serviceProvider = serviceProvider; @@ -54,6 +59,8 @@ namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations _settingDefinitionManager = settingDefinitionManager; _featureDefinitionManager = featureDefinitionManager; _languageProvider = languageProvider; + _timezoneProvider = timezoneProvider; + _abpClockOptions = abpClockOptions.Value; _cachedObjectExtensionsDtoService = cachedObjectExtensionsDtoService; _localizationOptions = localizationOptions.Value; _multiTenancyOptions = multiTenancyOptions.Value; @@ -74,6 +81,8 @@ namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations Setting = await GetSettingConfigAsync(), MultiTenancy = GetMultiTenancy(), CurrentTenant = GetCurrentTenant(), + Timing = await GetTimingConfigAsync(), + Clock = GetClockConfig(), ObjectExtensions = _cachedObjectExtensionsDtoService.Get() }; @@ -226,5 +235,36 @@ namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations return result; } + + protected virtual async Task GetTimingConfigAsync() + { + var result = new TimingDto(); + + var windowsTimeZoneId = await _settingProvider.GetOrNullAsync(TimingSettingNames.TimeZone); + if (!windowsTimeZoneId.IsNullOrWhiteSpace()) + { + result.TimeZone = new TimeZone + { + Windows = new WindowsTimeZone + { + TimeZoneId = windowsTimeZoneId + }, + Iana = new IanaTimeZone() + { + TimeZoneName = _timezoneProvider.WindowsToIana(windowsTimeZoneId) + } + }; + } + + return result; + } + + protected virtual ClockDto GetClockConfig() + { + return new ClockDto + { + Kind = Enum.GetName(typeof(DateTimeKind), _abpClockOptions.Kind) + }; + } } } diff --git a/framework/src/Volo.Abp.Core/System/AbpObjectExtensions.cs b/framework/src/Volo.Abp.Core/System/AbpObjectExtensions.cs index 36896ea59a..49e0d6dea6 100644 --- a/framework/src/Volo.Abp.Core/System/AbpObjectExtensions.cs +++ b/framework/src/Volo.Abp.Core/System/AbpObjectExtensions.cs @@ -42,5 +42,50 @@ namespace System { return list.Contains(item); } + + /// + /// Can be used to conditionally perform a function + /// on an object and return the modified or the original object. + /// It is useful for chained calls. + /// + /// An object + /// A condition + /// A function that is executed only if the condition is true + /// Type of the object + /// + /// Returns the modified object (by the if the is true) + /// or the original object if the is false + /// + public static T If(this T obj, bool condition, Func func) + { + if (condition) + { + return func(obj); + } + + return obj; + } + + /// + /// Can be used to conditionally perform an action + /// on an object and return the original object. + /// It is useful for chained calls on the object. + /// + /// An object + /// A condition + /// An action that is executed only if the condition is true + /// Type of the object + /// + /// Returns the original object. + /// + public static T If(this T obj, bool condition, Action action) + { + if (condition) + { + action(obj); + } + + return obj; + } } } diff --git a/framework/src/Volo.Abp.EntityFrameworkCore.Oracle.Devart/Volo/Abp/EntityFrameworkCore/AbpDbContextConfigurationContextOracleDevartExtensions.cs b/framework/src/Volo.Abp.EntityFrameworkCore.Oracle.Devart/Volo/Abp/EntityFrameworkCore/AbpDbContextConfigurationContextOracleDevartExtensions.cs index 9a768bb5ca..482ee8b9a8 100644 --- a/framework/src/Volo.Abp.EntityFrameworkCore.Oracle.Devart/Volo/Abp/EntityFrameworkCore/AbpDbContextConfigurationContextOracleDevartExtensions.cs +++ b/framework/src/Volo.Abp.EntityFrameworkCore.Oracle.Devart/Volo/Abp/EntityFrameworkCore/AbpDbContextConfigurationContextOracleDevartExtensions.cs @@ -10,9 +10,10 @@ namespace Volo.Abp.EntityFrameworkCore { public static DbContextOptionsBuilder UseOracle( [NotNull] this AbpDbContextConfigurationContext context, - [CanBeNull] Action oracleOptionsAction = null) + [CanBeNull] Action oracleOptionsAction = null, + bool useExistingConnectionIfAvailable = false) { - if (context.ExistingConnection != null) + if (useExistingConnectionIfAvailable && context.ExistingConnection != null) { return context.DbContextOptions.UseOracle(context.ExistingConnection, oracleOptionsAction); } diff --git a/framework/src/Volo.Abp.EntityFrameworkCore.Oracle.Devart/Volo/Abp/EntityFrameworkCore/AbpDbContextOptionsOracleDevartExtensions.cs b/framework/src/Volo.Abp.EntityFrameworkCore.Oracle.Devart/Volo/Abp/EntityFrameworkCore/AbpDbContextOptionsOracleDevartExtensions.cs index 22a8c1f7ab..20887ceb79 100644 --- a/framework/src/Volo.Abp.EntityFrameworkCore.Oracle.Devart/Volo/Abp/EntityFrameworkCore/AbpDbContextOptionsOracleDevartExtensions.cs +++ b/framework/src/Volo.Abp.EntityFrameworkCore.Oracle.Devart/Volo/Abp/EntityFrameworkCore/AbpDbContextOptionsOracleDevartExtensions.cs @@ -8,22 +8,24 @@ namespace Volo.Abp.EntityFrameworkCore { public static void UseOracle( [NotNull] this AbpDbContextOptions options, - [CanBeNull] Action oracleOptionsAction = null) + [CanBeNull] Action oracleOptionsAction = null, + bool useExistingConnectionIfAvailable = false) { options.Configure(context => { - context.UseOracle(oracleOptionsAction); + context.UseOracle(oracleOptionsAction, useExistingConnectionIfAvailable); }); } public static void UseOracle( [NotNull] this AbpDbContextOptions options, - [CanBeNull] Action oracleOptionsAction = null) + [CanBeNull] Action oracleOptionsAction = null, + bool useExistingConnectionIfAvailable = false) where TDbContext : AbpDbContext { options.Configure(context => { - context.UseOracle(oracleOptionsAction); + context.UseOracle(oracleOptionsAction, useExistingConnectionIfAvailable); }); } } diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs index 1f1cf4953e..2c49f6408a 100644 --- a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs @@ -129,7 +129,7 @@ namespace Volo.Abp.EntityFrameworkCore return EfCoreDatabaseProvider.PostgreSql; case "Pomelo.EntityFrameworkCore.MySql": return EfCoreDatabaseProvider.MySql; - case "Devart.Data.Oracle.EFCore": + case "Devart.Data.Oracle.Entity.EFCore": return EfCoreDatabaseProvider.Oracle; case "Microsoft.EntityFrameworkCore.Sqlite": return EfCoreDatabaseProvider.Sqlite; diff --git a/framework/src/Volo.Abp.Timing/Volo.Abp.Timing.csproj b/framework/src/Volo.Abp.Timing/Volo.Abp.Timing.csproj index 4f9eb21f2f..53ef23fd62 100644 --- a/framework/src/Volo.Abp.Timing/Volo.Abp.Timing.csproj +++ b/framework/src/Volo.Abp.Timing/Volo.Abp.Timing.csproj @@ -14,8 +14,19 @@ + + + + + + + - + + + + + diff --git a/framework/src/Volo.Abp.Timing/Volo/Abp/Timing/AbpTimingModule.cs b/framework/src/Volo.Abp.Timing/Volo/Abp/Timing/AbpTimingModule.cs index d48cc01ef7..3db42c2e55 100644 --- a/framework/src/Volo.Abp.Timing/Volo/Abp/Timing/AbpTimingModule.cs +++ b/framework/src/Volo.Abp.Timing/Volo/Abp/Timing/AbpTimingModule.cs @@ -1,9 +1,32 @@ -using Volo.Abp.Modularity; +using Volo.Abp.Localization; +using Volo.Abp.Localization.Resources.AbpLocalization; +using Volo.Abp.Modularity; +using Volo.Abp.Settings; +using Volo.Abp.Timing.Localization.Resources.AbpTiming; +using Volo.Abp.VirtualFileSystem; namespace Volo.Abp.Timing { + [DependsOn( + typeof(AbpVirtualFileSystemModule), + typeof(AbpSettingsModule) + )] public class AbpTimingModule : AbpModule { + public override void ConfigureServices(ServiceConfigurationContext context) + { + Configure(options => + { + options.FileSets.AddEmbedded(); + }); + Configure(options => + { + options + .Resources + .Add("en") + .AddVirtualJson("/Volo/Abp/Timing/Localization"); + }); + } } } diff --git a/framework/src/Volo.Abp.Timing/Volo/Abp/Timing/ITimezoneProvider.cs b/framework/src/Volo.Abp.Timing/Volo/Abp/Timing/ITimezoneProvider.cs new file mode 100644 index 0000000000..3ee1566803 --- /dev/null +++ b/framework/src/Volo.Abp.Timing/Volo/Abp/Timing/ITimezoneProvider.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; + +namespace Volo.Abp.Timing +{ + public interface ITimezoneProvider + { + List GetWindowsTimezones(); + + List GetIanaTimezones(); + + string WindowsToIana(string windowsTimeZoneId); + + string IanaToWindows(string ianaTimeZoneName); + + TimeZoneInfo GetTimeZoneInfo(string windowsOrIanaTimeZoneId); + } +} diff --git a/framework/src/Volo.Abp.Timing/Volo/Abp/Timing/Localization/AbpTimingResource.cs b/framework/src/Volo.Abp.Timing/Volo/Abp/Timing/Localization/AbpTimingResource.cs new file mode 100644 index 0000000000..71f32d17cb --- /dev/null +++ b/framework/src/Volo.Abp.Timing/Volo/Abp/Timing/Localization/AbpTimingResource.cs @@ -0,0 +1,10 @@ +using Volo.Abp.Localization; + +namespace Volo.Abp.Timing.Localization.Resources.AbpTiming +{ + [LocalizationResourceName("AbpTiming")] + public class AbpTimingResource + { + + } +} diff --git a/framework/src/Volo.Abp.Timing/Volo/Abp/Timing/Localization/en.json b/framework/src/Volo.Abp.Timing/Volo/Abp/Timing/Localization/en.json new file mode 100644 index 0000000000..c017c3ebea --- /dev/null +++ b/framework/src/Volo.Abp.Timing/Volo/Abp/Timing/Localization/en.json @@ -0,0 +1,7 @@ +{ + "culture": "en", + "texts": { + "DisplayName:Abp.Timing.Timezone": "Timezone", + "Description:Abp.Timing.Timezone": "Application time zone" + } +} diff --git a/framework/src/Volo.Abp.Timing/Volo/Abp/Timing/Localization/tr.json b/framework/src/Volo.Abp.Timing/Volo/Abp/Timing/Localization/tr.json new file mode 100644 index 0000000000..18139d6080 --- /dev/null +++ b/framework/src/Volo.Abp.Timing/Volo/Abp/Timing/Localization/tr.json @@ -0,0 +1,7 @@ +{ + "culture": "tr", + "texts": { + "DisplayName:Abp.Timing.Timezone": "saat dilimi", + "Description:Abp.Timing.Timezone": "Uygulama saat dilimi" + } +} diff --git a/framework/src/Volo.Abp.Timing/Volo/Abp/Timing/Localization/zh-Hans.json b/framework/src/Volo.Abp.Timing/Volo/Abp/Timing/Localization/zh-Hans.json new file mode 100644 index 0000000000..41315bbfb0 --- /dev/null +++ b/framework/src/Volo.Abp.Timing/Volo/Abp/Timing/Localization/zh-Hans.json @@ -0,0 +1,7 @@ +{ + "culture": "zh-Hans", + "texts": { + "DisplayName:Abp.Timing.Timezone": "时区", + "Description:Abp.Timing.Timezone": "应用程序的时区" + } +} diff --git a/framework/src/Volo.Abp.Timing/Volo/Abp/Timing/Localization/zh-Hant.json b/framework/src/Volo.Abp.Timing/Volo/Abp/Timing/Localization/zh-Hant.json new file mode 100644 index 0000000000..5836792e3d --- /dev/null +++ b/framework/src/Volo.Abp.Timing/Volo/Abp/Timing/Localization/zh-Hant.json @@ -0,0 +1,7 @@ +{ + "culture": "zh-Hant", + "texts": { + "DisplayName:Abp.Timing.Timezone": "時區", + "Description:Abp.Timing.Timezone": "應用程序的時區" + } +} diff --git a/framework/src/Volo.Abp.Timing/Volo/Abp/Timing/TZConvertTimezoneProvider.cs b/framework/src/Volo.Abp.Timing/Volo/Abp/Timing/TZConvertTimezoneProvider.cs new file mode 100644 index 0000000000..551b26e488 --- /dev/null +++ b/framework/src/Volo.Abp.Timing/Volo/Abp/Timing/TZConvertTimezoneProvider.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using TimeZoneConverter; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.Timing +{ + public class TZConvertTimezoneProvider : ITimezoneProvider, ITransientDependency + { + public virtual List GetWindowsTimezones() + { + return TZConvert.KnownIanaTimeZoneNames.OrderBy(x => x).Select(x => new NameValue(x, x)).ToList(); + } + + public virtual List GetIanaTimezones() + { + return TZConvert.KnownIanaTimeZoneNames.OrderBy(x => x).Select(x => new NameValue(x, x)).ToList(); + } + + public virtual string WindowsToIana(string windowsTimeZoneId) + { + return TZConvert.WindowsToIana(windowsTimeZoneId); + } + + public virtual string IanaToWindows(string ianaTimeZoneName) + { + return TZConvert.IanaToWindows(ianaTimeZoneName); + } + + public virtual TimeZoneInfo GetTimeZoneInfo(string windowsOrIanaTimeZoneId) + { + return TZConvert.GetTimeZoneInfo(windowsOrIanaTimeZoneId); + } + } +} diff --git a/framework/src/Volo.Abp.Timing/Volo/Abp/Timing/TimingSettingNames.cs b/framework/src/Volo.Abp.Timing/Volo/Abp/Timing/TimingSettingNames.cs new file mode 100644 index 0000000000..15ba1bc9f9 --- /dev/null +++ b/framework/src/Volo.Abp.Timing/Volo/Abp/Timing/TimingSettingNames.cs @@ -0,0 +1,7 @@ +namespace Volo.Abp.Timing +{ + public static class TimingSettingNames + { + public const string TimeZone = "Abp.Timing.TimeZone"; + } +} diff --git a/framework/src/Volo.Abp.Timing/Volo/Abp/Timing/TimingSettingProvider.cs b/framework/src/Volo.Abp.Timing/Volo/Abp/Timing/TimingSettingProvider.cs new file mode 100644 index 0000000000..c2ebf73cfd --- /dev/null +++ b/framework/src/Volo.Abp.Timing/Volo/Abp/Timing/TimingSettingProvider.cs @@ -0,0 +1,25 @@ +using Volo.Abp.Localization; +using Volo.Abp.Settings; +using Volo.Abp.Timing.Localization.Resources.AbpTiming; + +namespace Volo.Abp.Timing +{ + public class TimingSettingProvider : SettingDefinitionProvider + { + public override void Define(ISettingDefinitionContext context) + { + context.Add( + new SettingDefinition(TimingSettingNames.TimeZone, + "UTC", + L("DisplayName:Abp.Timing.Timezone"), + L("Description:Abp.Timing.Timezone"), + isVisibleToClients: true) + ); + } + + private static LocalizableString L(string name) + { + return LocalizableString.Create(name); + } + } +} diff --git a/framework/test/Volo.Abp.Core.Tests/System/ObjectExtension_Test.cs b/framework/test/Volo.Abp.Core.Tests/System/ObjectExtension_Test.cs index 4345c60491..be429658ed 100644 --- a/framework/test/Volo.Abp.Core.Tests/System/ObjectExtension_Test.cs +++ b/framework/test/Volo.Abp.Core.Tests/System/ObjectExtension_Test.cs @@ -52,5 +52,22 @@ namespace System str = null; str.IsIn("a", "b", "c").ShouldBe(false); } + + [Fact] + public void If_Tests() + { + var value = 0; + + value = value.If(true, v => v + 1); + value.ShouldBe(1); + + value = value.If(false, v => v + 1); + value.ShouldBe(1); + + value = value + .If(true, v => v + 3) + .If(false, v => v + 5); + value.ShouldBe(4); + } } } diff --git a/modules/audit-logging/src/Volo.Abp.AuditLogging.Domain.Shared/Volo/Abp/AuditLogging/AuditLogConsts.cs b/modules/audit-logging/src/Volo.Abp.AuditLogging.Domain.Shared/Volo/Abp/AuditLogging/AuditLogConsts.cs index 1a592fc8b8..504e7451e0 100644 --- a/modules/audit-logging/src/Volo.Abp.AuditLogging.Domain.Shared/Volo/Abp/AuditLogging/AuditLogConsts.cs +++ b/modules/audit-logging/src/Volo.Abp.AuditLogging.Domain.Shared/Volo/Abp/AuditLogging/AuditLogConsts.cs @@ -16,6 +16,9 @@ public const int MaxExceptionsLength = 4000; + //TODO: Replace with MaxExceptionsLength in v3.0 + public static int MaxExceptionsLengthValue { get; set; } = MaxExceptionsLength; + public const int MaxCommentsLength = 256; public const int MaxUrlLength = 256; diff --git a/modules/audit-logging/src/Volo.Abp.AuditLogging.Domain/Volo/Abp/AuditLogging/AuditLog.cs b/modules/audit-logging/src/Volo.Abp.AuditLogging.Domain/Volo/Abp/AuditLogging/AuditLog.cs index 752ccb953c..63dbd4802b 100644 --- a/modules/audit-logging/src/Volo.Abp.AuditLogging.Domain/Volo/Abp/AuditLogging/AuditLog.cs +++ b/modules/audit-logging/src/Volo.Abp.AuditLogging.Domain/Volo/Abp/AuditLogging/AuditLog.cs @@ -99,7 +99,7 @@ namespace Volo.Abp.AuditLogging Exceptions = auditInfo .Exceptions? .JoinAsString(Environment.NewLine) - .Truncate(AuditLogConsts.MaxExceptionsLength); + .Truncate(AuditLogConsts.MaxExceptionsLengthValue); Comments = auditInfo .Comments? diff --git a/modules/audit-logging/src/Volo.Abp.AuditLogging.EntityFrameworkCore/Volo/Abp/AuditLogging/EntityFrameworkCore/AbpAuditLoggingDbContextModelBuilderExtensions.cs b/modules/audit-logging/src/Volo.Abp.AuditLogging.EntityFrameworkCore/Volo/Abp/AuditLogging/EntityFrameworkCore/AbpAuditLoggingDbContextModelBuilderExtensions.cs index 619eed057c..b44242e511 100644 --- a/modules/audit-logging/src/Volo.Abp.AuditLogging.EntityFrameworkCore/Volo/Abp/AuditLogging/EntityFrameworkCore/AbpAuditLoggingDbContextModelBuilderExtensions.cs +++ b/modules/audit-logging/src/Volo.Abp.AuditLogging.EntityFrameworkCore/Volo/Abp/AuditLogging/EntityFrameworkCore/AbpAuditLoggingDbContextModelBuilderExtensions.cs @@ -36,7 +36,10 @@ namespace Volo.Abp.AuditLogging.EntityFrameworkCore b.Property(x => x.HttpMethod).HasMaxLength(AuditLogConsts.MaxHttpMethodLength).HasColumnName(nameof(AuditLog.HttpMethod)); b.Property(x => x.Url).HasMaxLength(AuditLogConsts.MaxUrlLength).HasColumnName(nameof(AuditLog.Url)); b.Property(x => x.HttpStatusCode).HasColumnName(nameof(AuditLog.HttpStatusCode)); - b.Property(x => x.Exceptions).HasMaxLength(AuditLogConsts.MaxExceptionsLength).HasColumnName(nameof(AuditLog.Exceptions)); + + if (builder.IsUsingOracle()) { AuditLogConsts.MaxExceptionsLengthValue = 2000; } + b.Property(x => x.Exceptions).HasMaxLength(AuditLogConsts.MaxExceptionsLengthValue).HasColumnName(nameof(AuditLog.Exceptions)); + b.Property(x => x.Comments).HasMaxLength(AuditLogConsts.MaxCommentsLength).HasColumnName(nameof(AuditLog.Comments)); b.Property(x => x.ExecutionDuration).HasColumnName(nameof(AuditLog.ExecutionDuration)); b.Property(x => x.ImpersonatorTenantId).HasColumnName(nameof(AuditLog.ImpersonatorTenantId)); diff --git a/modules/blob-storing-database/Volo.Abp.BlobStoring.Database.sln b/modules/blob-storing-database/Volo.Abp.BlobStoring.Database.sln new file mode 100644 index 0000000000..37fafbe265 --- /dev/null +++ b/modules/blob-storing-database/Volo.Abp.BlobStoring.Database.sln @@ -0,0 +1,81 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.29001.49 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.BlobStoring.Database.Domain.Shared", "src\Volo.Abp.BlobStoring.Database.Domain.Shared\Volo.Abp.BlobStoring.Database.Domain.Shared.csproj", "{D64C1577-4929-4B60-939E-96DE1534891A}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.BlobStoring.Database.Domain", "src\Volo.Abp.BlobStoring.Database.Domain\Volo.Abp.BlobStoring.Database.Domain.csproj", "{F2840BC7-0188-4606-9126-DADD0F5ABF7A}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{649A3FFA-182F-4E56-9717-E6A9A2BEC545}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{CCD2960C-23CC-4AB4-B84D-60C7AAA52F4D}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.BlobStoring.Database.EntityFrameworkCore", "src\Volo.Abp.BlobStoring.Database.EntityFrameworkCore\Volo.Abp.BlobStoring.Database.EntityFrameworkCore.csproj", "{0CE86223-D31D-4315-A1F5-87BA3EE1B844}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.BlobStoring.Database.MongoDB", "src\Volo.Abp.BlobStoring.Database.MongoDB\Volo.Abp.BlobStoring.Database.MongoDB.csproj", "{F1C58097-4C08-4D88-8976-6B3389391481}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.BlobStoring.Database.TestBase", "test\Volo.Abp.BlobStoring.Database.TestBase\Volo.Abp.BlobStoring.Database.TestBase.csproj", "{C5BB573D-3030-4BCB-88B7-F6A85C32766C}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.BlobStoring.Database.EntityFrameworkCore.Tests", "test\Volo.Abp.BlobStoring.Database.EntityFrameworkCore.Tests\Volo.Abp.BlobStoring.Database.EntityFrameworkCore.Tests.csproj", "{527F645C-C1FC-406E-8479-81386C8ECF13}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.BlobStoring.Database.MongoDB.Tests", "test\Volo.Abp.BlobStoring.Database.MongoDB.Tests\Volo.Abp.BlobStoring.Database.MongoDB.Tests.csproj", "{D0AD9179-125C-40B2-A8EE-CD4C1EE24BB6}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.BlobStoring.Database.Domain.Tests", "test\Volo.Abp.BlobStoring.Database.Domain.Tests\Volo.Abp.BlobStoring.Database.Domain.Tests.csproj", "{E60895E5-79C4-447D-88B7-85CB5BA336A4}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {D64C1577-4929-4B60-939E-96DE1534891A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D64C1577-4929-4B60-939E-96DE1534891A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D64C1577-4929-4B60-939E-96DE1534891A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D64C1577-4929-4B60-939E-96DE1534891A}.Release|Any CPU.Build.0 = Release|Any CPU + {F2840BC7-0188-4606-9126-DADD0F5ABF7A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F2840BC7-0188-4606-9126-DADD0F5ABF7A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F2840BC7-0188-4606-9126-DADD0F5ABF7A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F2840BC7-0188-4606-9126-DADD0F5ABF7A}.Release|Any CPU.Build.0 = Release|Any CPU + {0CE86223-D31D-4315-A1F5-87BA3EE1B844}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0CE86223-D31D-4315-A1F5-87BA3EE1B844}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0CE86223-D31D-4315-A1F5-87BA3EE1B844}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0CE86223-D31D-4315-A1F5-87BA3EE1B844}.Release|Any CPU.Build.0 = Release|Any CPU + {F1C58097-4C08-4D88-8976-6B3389391481}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F1C58097-4C08-4D88-8976-6B3389391481}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F1C58097-4C08-4D88-8976-6B3389391481}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F1C58097-4C08-4D88-8976-6B3389391481}.Release|Any CPU.Build.0 = Release|Any CPU + {C5BB573D-3030-4BCB-88B7-F6A85C32766C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C5BB573D-3030-4BCB-88B7-F6A85C32766C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C5BB573D-3030-4BCB-88B7-F6A85C32766C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C5BB573D-3030-4BCB-88B7-F6A85C32766C}.Release|Any CPU.Build.0 = Release|Any CPU + {527F645C-C1FC-406E-8479-81386C8ECF13}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {527F645C-C1FC-406E-8479-81386C8ECF13}.Debug|Any CPU.Build.0 = Debug|Any CPU + {527F645C-C1FC-406E-8479-81386C8ECF13}.Release|Any CPU.ActiveCfg = Release|Any CPU + {527F645C-C1FC-406E-8479-81386C8ECF13}.Release|Any CPU.Build.0 = Release|Any CPU + {D0AD9179-125C-40B2-A8EE-CD4C1EE24BB6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D0AD9179-125C-40B2-A8EE-CD4C1EE24BB6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D0AD9179-125C-40B2-A8EE-CD4C1EE24BB6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D0AD9179-125C-40B2-A8EE-CD4C1EE24BB6}.Release|Any CPU.Build.0 = Release|Any CPU + {E60895E5-79C4-447D-88B7-85CB5BA336A4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E60895E5-79C4-447D-88B7-85CB5BA336A4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E60895E5-79C4-447D-88B7-85CB5BA336A4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E60895E5-79C4-447D-88B7-85CB5BA336A4}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {D64C1577-4929-4B60-939E-96DE1534891A} = {649A3FFA-182F-4E56-9717-E6A9A2BEC545} + {F2840BC7-0188-4606-9126-DADD0F5ABF7A} = {649A3FFA-182F-4E56-9717-E6A9A2BEC545} + {0CE86223-D31D-4315-A1F5-87BA3EE1B844} = {649A3FFA-182F-4E56-9717-E6A9A2BEC545} + {F1C58097-4C08-4D88-8976-6B3389391481} = {649A3FFA-182F-4E56-9717-E6A9A2BEC545} + {C5BB573D-3030-4BCB-88B7-F6A85C32766C} = {CCD2960C-23CC-4AB4-B84D-60C7AAA52F4D} + {527F645C-C1FC-406E-8479-81386C8ECF13} = {CCD2960C-23CC-4AB4-B84D-60C7AAA52F4D} + {D0AD9179-125C-40B2-A8EE-CD4C1EE24BB6} = {CCD2960C-23CC-4AB4-B84D-60C7AAA52F4D} + {E60895E5-79C4-447D-88B7-85CB5BA336A4} = {CCD2960C-23CC-4AB4-B84D-60C7AAA52F4D} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {4324B3B4-B60B-4E3C-91D8-59576B4E26DD} + EndGlobalSection +EndGlobal diff --git a/modules/blob-storing-database/Volo.Abp.BlobStoring.Database.sln.DotSettings b/modules/blob-storing-database/Volo.Abp.BlobStoring.Database.sln.DotSettings new file mode 100644 index 0000000000..cb0b2c919f --- /dev/null +++ b/modules/blob-storing-database/Volo.Abp.BlobStoring.Database.sln.DotSettings @@ -0,0 +1,23 @@ + + True + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + Required + Required + Required + Required + False + True + False + False + True + False + False + SQL + \ No newline at end of file diff --git a/modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.Domain.Shared/FodyWeavers.xml b/modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.Domain.Shared/FodyWeavers.xml new file mode 100644 index 0000000000..be0de3a908 --- /dev/null +++ b/modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.Domain.Shared/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.Domain.Shared/FodyWeavers.xsd b/modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.Domain.Shared/FodyWeavers.xsd new file mode 100644 index 0000000000..3f3946e282 --- /dev/null +++ b/modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.Domain.Shared/FodyWeavers.xsd @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file diff --git a/modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.Domain.Shared/Volo.Abp.BlobStoring.Database.Domain.Shared.csproj b/modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.Domain.Shared/Volo.Abp.BlobStoring.Database.Domain.Shared.csproj new file mode 100644 index 0000000000..4178ed3659 --- /dev/null +++ b/modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.Domain.Shared/Volo.Abp.BlobStoring.Database.Domain.Shared.csproj @@ -0,0 +1,20 @@ + + + + + + + netstandard2.0 + + + + + + + + + + + + + diff --git a/modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.Domain.Shared/Volo/Abp/BlobStoring/Database/BlobConsts.cs b/modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.Domain.Shared/Volo/Abp/BlobStoring/Database/BlobConsts.cs new file mode 100644 index 0000000000..f0edc35777 --- /dev/null +++ b/modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.Domain.Shared/Volo/Abp/BlobStoring/Database/BlobConsts.cs @@ -0,0 +1,9 @@ +namespace Volo.Abp.BlobStoring.Database +{ + public static class BlobConsts + { + public const int MaxNameLength = 256; + + public const int MaxContentLength = 2_000_000_000; // 2 GB + } +} \ No newline at end of file diff --git a/modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.Domain.Shared/Volo/Abp/BlobStoring/Database/BlobStoringDatabaseDomainSharedModule.cs b/modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.Domain.Shared/Volo/Abp/BlobStoring/Database/BlobStoringDatabaseDomainSharedModule.cs new file mode 100644 index 0000000000..4dc89c9911 --- /dev/null +++ b/modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.Domain.Shared/Volo/Abp/BlobStoring/Database/BlobStoringDatabaseDomainSharedModule.cs @@ -0,0 +1,37 @@ +using Volo.Abp.Modularity; +using Volo.Abp.Localization; +using Volo.Abp.BlobStoring.Database.Localization; +using Volo.Abp.Localization.ExceptionHandling; +using Volo.Abp.Validation; +using Volo.Abp.Validation.Localization; +using Volo.Abp.VirtualFileSystem; + +namespace Volo.Abp.BlobStoring.Database +{ + [DependsOn( + typeof(AbpValidationModule) + )] + public class BlobStoringDatabaseDomainSharedModule : AbpModule + { + public override void ConfigureServices(ServiceConfigurationContext context) + { + Configure(options => + { + options.FileSets.AddEmbedded("Volo.Abp.BlobStoring.Database"); + }); + + Configure(options => + { + options.Resources + .Add("en") + .AddBaseTypes(typeof(AbpValidationResource)) + .AddVirtualJson("/Localization/BlobStoringDatabase"); + }); + + Configure(options => + { + options.MapCodeNamespace("BlobStoringDatabase", typeof(BlobStoringDatabaseResource)); + }); + } + } +} diff --git a/modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.Domain.Shared/Volo/Abp/BlobStoring/Database/BlobStoringDatabaseErrorCodes.cs b/modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.Domain.Shared/Volo/Abp/BlobStoring/Database/BlobStoringDatabaseErrorCodes.cs new file mode 100644 index 0000000000..fe066e74e5 --- /dev/null +++ b/modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.Domain.Shared/Volo/Abp/BlobStoring/Database/BlobStoringDatabaseErrorCodes.cs @@ -0,0 +1,7 @@ +namespace Volo.Abp.BlobStoring.Database +{ + public static class BlobStoringDatabaseErrorCodes + { + //Add your business exception error codes here... + } +} diff --git a/modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.Domain.Shared/Volo/Abp/BlobStoring/Database/ContainerConsts.cs b/modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.Domain.Shared/Volo/Abp/BlobStoring/Database/ContainerConsts.cs new file mode 100644 index 0000000000..383883eca6 --- /dev/null +++ b/modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.Domain.Shared/Volo/Abp/BlobStoring/Database/ContainerConsts.cs @@ -0,0 +1,7 @@ +namespace Volo.Abp.BlobStoring.Database +{ + public static class ContainerConsts + { + public const int MaxNameLength = 128; + } +} \ No newline at end of file diff --git a/modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.Domain.Shared/Volo/Abp/BlobStoring/Database/Localization/BlobStoringDatabase/cs.json b/modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.Domain.Shared/Volo/Abp/BlobStoring/Database/Localization/BlobStoringDatabase/cs.json new file mode 100644 index 0000000000..6a060a54c1 --- /dev/null +++ b/modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.Domain.Shared/Volo/Abp/BlobStoring/Database/Localization/BlobStoringDatabase/cs.json @@ -0,0 +1,6 @@ +{ + "culture": "cs", + "texts": { + + } +} \ No newline at end of file diff --git a/modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.Domain.Shared/Volo/Abp/BlobStoring/Database/Localization/BlobStoringDatabase/en.json b/modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.Domain.Shared/Volo/Abp/BlobStoring/Database/Localization/BlobStoringDatabase/en.json new file mode 100644 index 0000000000..171d9a1220 --- /dev/null +++ b/modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.Domain.Shared/Volo/Abp/BlobStoring/Database/Localization/BlobStoringDatabase/en.json @@ -0,0 +1,6 @@ +{ + "culture": "en", + "texts": { + "ManageYourProfile": "Manage your profile" + } +} \ No newline at end of file diff --git a/modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.Domain.Shared/Volo/Abp/BlobStoring/Database/Localization/BlobStoringDatabase/pl-PL.json b/modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.Domain.Shared/Volo/Abp/BlobStoring/Database/Localization/BlobStoringDatabase/pl-PL.json new file mode 100644 index 0000000000..3ea7b190ee --- /dev/null +++ b/modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.Domain.Shared/Volo/Abp/BlobStoring/Database/Localization/BlobStoringDatabase/pl-PL.json @@ -0,0 +1,6 @@ +{ + "culture": "pl-PL", + "texts": { + + } +} \ No newline at end of file diff --git a/modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.Domain.Shared/Volo/Abp/BlobStoring/Database/Localization/BlobStoringDatabase/pt-BR.json b/modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.Domain.Shared/Volo/Abp/BlobStoring/Database/Localization/BlobStoringDatabase/pt-BR.json new file mode 100644 index 0000000000..6d746df04c --- /dev/null +++ b/modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.Domain.Shared/Volo/Abp/BlobStoring/Database/Localization/BlobStoringDatabase/pt-BR.json @@ -0,0 +1,6 @@ +{ + "culture": "pt-BR", + "texts": { + + } +} \ No newline at end of file diff --git a/modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.Domain.Shared/Volo/Abp/BlobStoring/Database/Localization/BlobStoringDatabase/sl.json b/modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.Domain.Shared/Volo/Abp/BlobStoring/Database/Localization/BlobStoringDatabase/sl.json new file mode 100644 index 0000000000..687d42579c --- /dev/null +++ b/modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.Domain.Shared/Volo/Abp/BlobStoring/Database/Localization/BlobStoringDatabase/sl.json @@ -0,0 +1,6 @@ +{ + "culture": "sl", + "texts": { + "ManageYourProfile": "Upravljajte svojim profilom" + } +} \ No newline at end of file diff --git a/modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.Domain.Shared/Volo/Abp/BlobStoring/Database/Localization/BlobStoringDatabase/tr.json b/modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.Domain.Shared/Volo/Abp/BlobStoring/Database/Localization/BlobStoringDatabase/tr.json new file mode 100644 index 0000000000..2b48193353 --- /dev/null +++ b/modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.Domain.Shared/Volo/Abp/BlobStoring/Database/Localization/BlobStoringDatabase/tr.json @@ -0,0 +1,6 @@ +{ + "culture": "tr", + "texts": { + "ManageYourProfile": "Profil yönetimi" + } +} \ No newline at end of file diff --git a/modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.Domain.Shared/Volo/Abp/BlobStoring/Database/Localization/BlobStoringDatabase/vi.json b/modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.Domain.Shared/Volo/Abp/BlobStoring/Database/Localization/BlobStoringDatabase/vi.json new file mode 100644 index 0000000000..d8eb5f3c75 --- /dev/null +++ b/modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.Domain.Shared/Volo/Abp/BlobStoring/Database/Localization/BlobStoringDatabase/vi.json @@ -0,0 +1,6 @@ +{ + "culture": "vi", + "texts": { + + } +} diff --git a/modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.Domain.Shared/Volo/Abp/BlobStoring/Database/Localization/BlobStoringDatabase/zh-Hans.json b/modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.Domain.Shared/Volo/Abp/BlobStoring/Database/Localization/BlobStoringDatabase/zh-Hans.json new file mode 100644 index 0000000000..99586f01c9 --- /dev/null +++ b/modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.Domain.Shared/Volo/Abp/BlobStoring/Database/Localization/BlobStoringDatabase/zh-Hans.json @@ -0,0 +1,6 @@ +{ + "culture": "zh-Hans", + "texts": { + "ManageYourProfile": "管理个人资料" + } +} \ No newline at end of file diff --git a/modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.Domain.Shared/Volo/Abp/BlobStoring/Database/Localization/BlobStoringDatabase/zh-Hant.json b/modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.Domain.Shared/Volo/Abp/BlobStoring/Database/Localization/BlobStoringDatabase/zh-Hant.json new file mode 100644 index 0000000000..ceea055597 --- /dev/null +++ b/modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.Domain.Shared/Volo/Abp/BlobStoring/Database/Localization/BlobStoringDatabase/zh-Hant.json @@ -0,0 +1,6 @@ +{ + "culture": "zh-Hant", + "texts": { + "ManageYourProfile": "管理個人資料" + } +} \ No newline at end of file diff --git a/modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.Domain.Shared/Volo/Abp/BlobStoring/Database/Localization/BlobStoringDatabaseResource.cs b/modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.Domain.Shared/Volo/Abp/BlobStoring/Database/Localization/BlobStoringDatabaseResource.cs new file mode 100644 index 0000000000..6ee2f79810 --- /dev/null +++ b/modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.Domain.Shared/Volo/Abp/BlobStoring/Database/Localization/BlobStoringDatabaseResource.cs @@ -0,0 +1,10 @@ +using Volo.Abp.Localization; + +namespace Volo.Abp.BlobStoring.Database.Localization +{ + [LocalizationResourceName("BlobStoringDatabase")] + public class BlobStoringDatabaseResource + { + + } +} diff --git a/modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.Domain/FodyWeavers.xml b/modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.Domain/FodyWeavers.xml new file mode 100644 index 0000000000..be0de3a908 --- /dev/null +++ b/modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.Domain/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.Domain/FodyWeavers.xsd b/modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.Domain/FodyWeavers.xsd new file mode 100644 index 0000000000..3f3946e282 --- /dev/null +++ b/modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.Domain/FodyWeavers.xsd @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file diff --git a/modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.Domain/Volo.Abp.BlobStoring.Database.Domain.csproj b/modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.Domain/Volo.Abp.BlobStoring.Database.Domain.csproj new file mode 100644 index 0000000000..9e52acf207 --- /dev/null +++ b/modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.Domain/Volo.Abp.BlobStoring.Database.Domain.csproj @@ -0,0 +1,17 @@ + + + + + + + netstandard2.0 + + + + + + + + + + diff --git a/modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.Domain/Volo/Abp/BlobStoring/Database/Blob.cs b/modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.Domain/Volo/Abp/BlobStoring/Database/Blob.cs new file mode 100644 index 0000000000..6f3e8c7806 --- /dev/null +++ b/modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.Domain/Volo/Abp/BlobStoring/Database/Blob.cs @@ -0,0 +1,44 @@ +using JetBrains.Annotations; +using System; +using Volo.Abp.Domain.Entities; +using Volo.Abp.MultiTenancy; + +namespace Volo.Abp.BlobStoring.Database +{ + public class Blob : AggregateRoot, IMultiTenant + { + public virtual Guid ContainerId { get; protected set; } + + public virtual Guid? TenantId { get; protected set; } + + public virtual string Name { get; protected set; } + + public virtual byte[] Content { get; protected set; } + + public Blob(Guid id, Guid containerId, [NotNull] string name, [NotNull] byte[] content, Guid? tenantId = null) + : base(id) + { + Name = Check.NotNullOrWhiteSpace(name, nameof(name), BlobConsts.MaxNameLength); + ContainerId = containerId; + Content = CheckContentLength(content); + TenantId = tenantId; + } + + public virtual void SetContent(byte[] content) + { + Content = CheckContentLength(content); + } + + protected virtual byte[] CheckContentLength(byte[] content) + { + Check.NotNull(content, nameof(content)); + + if (content.Length >= BlobConsts.MaxContentLength) + { + throw new AbpException($"Blob content size cannot be more than {BlobConsts.MaxContentLength} Bytes."); + } + + return content; + } + } +} \ No newline at end of file diff --git a/modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.Domain/Volo/Abp/BlobStoring/Database/BlobStoringDatabaseDbProperties.cs b/modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.Domain/Volo/Abp/BlobStoring/Database/BlobStoringDatabaseDbProperties.cs new file mode 100644 index 0000000000..b673ad853f --- /dev/null +++ b/modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.Domain/Volo/Abp/BlobStoring/Database/BlobStoringDatabaseDbProperties.cs @@ -0,0 +1,13 @@ +using Volo.Abp.Data; + +namespace Volo.Abp.BlobStoring.Database +{ + public static class BlobStoringDatabaseDbProperties + { + public static string DbTablePrefix { get; set; } = AbpCommonDbProperties.DbTablePrefix; + + public static string DbSchema { get; set; } = AbpCommonDbProperties.DbSchema; + + public const string ConnectionStringName = "AbpBlobStoring"; + } +} diff --git a/modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.Domain/Volo/Abp/BlobStoring/Database/BlobStoringDatabaseDomainModule.cs b/modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.Domain/Volo/Abp/BlobStoring/Database/BlobStoringDatabaseDomainModule.cs new file mode 100644 index 0000000000..e49f9ce63b --- /dev/null +++ b/modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.Domain/Volo/Abp/BlobStoring/Database/BlobStoringDatabaseDomainModule.cs @@ -0,0 +1,15 @@ +using Volo.Abp.Domain; +using Volo.Abp.Modularity; + +namespace Volo.Abp.BlobStoring.Database +{ + [DependsOn( + typeof(AbpDddDomainModule), + typeof(AbpBlobStoringModule), + typeof(BlobStoringDatabaseDomainSharedModule) + )] + public class BlobStoringDatabaseDomainModule : AbpModule + { + + } +} diff --git a/modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.Domain/Volo/Abp/BlobStoring/Database/Container.cs b/modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.Domain/Volo/Abp/BlobStoring/Database/Container.cs new file mode 100644 index 0000000000..ebe8bbf20b --- /dev/null +++ b/modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.Domain/Volo/Abp/BlobStoring/Database/Container.cs @@ -0,0 +1,21 @@ +using System; +using JetBrains.Annotations; +using Volo.Abp.Domain.Entities; +using Volo.Abp.MultiTenancy; + +namespace Volo.Abp.BlobStoring.Database +{ + public class Container : AggregateRoot, IMultiTenant //TODO: Rename to BlobContainer + { + public virtual Guid? TenantId { get; protected set; } + + public virtual string Name { get; protected set; } + + public Container(Guid id, [NotNull] string name, Guid? tenantId = null) + : base(id) + { + Name = Check.NotNullOrWhiteSpace(name, nameof(name), ContainerConsts.MaxNameLength); + TenantId = tenantId; + } + } +} \ No newline at end of file diff --git a/modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.Domain/Volo/Abp/BlobStoring/Database/DatabaseBlobContainerConfigurationExtensions.cs b/modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.Domain/Volo/Abp/BlobStoring/Database/DatabaseBlobContainerConfigurationExtensions.cs new file mode 100644 index 0000000000..c302e00634 --- /dev/null +++ b/modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.Domain/Volo/Abp/BlobStoring/Database/DatabaseBlobContainerConfigurationExtensions.cs @@ -0,0 +1,31 @@ +using System; + +namespace Volo.Abp.BlobStoring.Database +{ + public static class DatabaseBlobContainerConfigurationExtensions + { + public static DatabaseBlobProviderConfiguration GetDatabaseConfiguration( + this BlobContainerConfiguration containerConfiguration) + { + return new DatabaseBlobProviderConfiguration(containerConfiguration); + } + + public static BlobContainerConfiguration UseDatabase( + this BlobContainerConfiguration containerConfiguration, + Action databaseConfigureAction) + { + containerConfiguration.ProviderType = typeof(DatabaseBlobProvider); + + databaseConfigureAction(new DatabaseBlobProviderConfiguration(containerConfiguration)); + + return containerConfiguration; + } + + public static BlobContainerConfiguration UseDatabase(this BlobContainerConfiguration containerConfiguration) + { + containerConfiguration.ProviderType = typeof(DatabaseBlobProvider); + + return containerConfiguration; + } + } +} \ No newline at end of file diff --git a/modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.Domain/Volo/Abp/BlobStoring/Database/DatabaseBlobProvider.cs b/modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.Domain/Volo/Abp/BlobStoring/Database/DatabaseBlobProvider.cs new file mode 100644 index 0000000000..f749410429 --- /dev/null +++ b/modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.Domain/Volo/Abp/BlobStoring/Database/DatabaseBlobProvider.cs @@ -0,0 +1,87 @@ +using System; +using System.IO; +using System.Threading.Tasks; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.BlobStoring.Database +{ + public class DatabaseBlobProvider : BlobProviderBase, ITransientDependency + { + protected IBlobRepository BlobRepository { get; } + protected IContainerRepository ContainerRepository { get; } + + public DatabaseBlobProvider(IBlobRepository blobRepository, IContainerRepository containerRepository) + { + BlobRepository = blobRepository; + ContainerRepository = containerRepository; + } + + public override async Task SaveAsync(BlobProviderSaveArgs args) + { + var container = await ContainerRepository.CreateIfNotExistAsync(args.ContainerName, args.TenantId, args.CancellationToken); + + var blob = await BlobRepository.FindAsync(container.Id, args.BlobName, args.TenantId, args.CancellationToken); + + var content = await args.BlobStream.GetAllBytesAsync(args.CancellationToken); + + if (blob != null) + { + if (!args.OverrideExisting) + { + throw new BlobAlreadyExistsException($"Saving BLOB '{args.BlobName}' does already exists in the container '{args.ContainerName}'! Set {nameof(args.OverrideExisting)} if it should be overwritten."); + } + + blob.SetContent(content); + await BlobRepository.UpdateAsync(blob); + } + else + { + blob = new Blob(Guid.NewGuid(), container.Id, args.BlobName, content, args.TenantId); + await BlobRepository.InsertAsync(blob); + } + } + + public override async Task DeleteAsync(BlobProviderDeleteArgs args) + { + var container = await ContainerRepository.FindAsync(args.ContainerName, args.TenantId, args.CancellationToken); + + if (container == null) + { + return false; + } + + return await BlobRepository.DeleteAsync(container.Id, args.BlobName, args.TenantId, args.CancellationToken); + } + + public override async Task ExistsAsync(BlobProviderExistsArgs args) + { + var container = await ContainerRepository.FindAsync(args.ContainerName, args.TenantId, args.CancellationToken); + + if (container == null) + { + return false; + } + + return await BlobRepository.ExistsAsync(container.Id, args.BlobName, args.TenantId, args.CancellationToken); + } + + public override async Task GetOrNullAsync(BlobProviderGetArgs args) + { + var container = await ContainerRepository.FindAsync(args.ContainerName, args.TenantId, args.CancellationToken); + + if (container == null) + { + return null; + } + + var blob = await BlobRepository.FindAsync(container.Id, args.BlobName, args.TenantId, args.CancellationToken); + + if (blob == null) + { + return null; + } + + return new MemoryStream(blob.Content); + } + } +} \ No newline at end of file diff --git a/modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.Domain/Volo/Abp/BlobStoring/Database/DatabaseBlobProviderConfiguration.cs b/modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.Domain/Volo/Abp/BlobStoring/Database/DatabaseBlobProviderConfiguration.cs new file mode 100644 index 0000000000..878c0c48c1 --- /dev/null +++ b/modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.Domain/Volo/Abp/BlobStoring/Database/DatabaseBlobProviderConfiguration.cs @@ -0,0 +1,12 @@ +namespace Volo.Abp.BlobStoring.Database +{ + public class DatabaseBlobProviderConfiguration + { + private readonly BlobContainerConfiguration _containerConfiguration; + + public DatabaseBlobProviderConfiguration(BlobContainerConfiguration containerConfiguration) + { + _containerConfiguration = containerConfiguration; + } + } +} \ No newline at end of file diff --git a/modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.Domain/Volo/Abp/BlobStoring/Database/IBlobRepository.cs b/modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.Domain/Volo/Abp/BlobStoring/Database/IBlobRepository.cs new file mode 100644 index 0000000000..e12603c772 --- /dev/null +++ b/modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.Domain/Volo/Abp/BlobStoring/Database/IBlobRepository.cs @@ -0,0 +1,17 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using JetBrains.Annotations; +using Volo.Abp.Domain.Repositories; + +namespace Volo.Abp.BlobStoring.Database +{ + public interface IBlobRepository : IBasicRepository + { + Task FindAsync(Guid containerId, [NotNull] string name, Guid? tenantId = null, CancellationToken cancellationToken = default); + + Task ExistsAsync(Guid containerId, [NotNull] string name, Guid? tenantId = null, CancellationToken cancellationToken = default); + + Task DeleteAsync(Guid containerId, [NotNull] string name, Guid? tenantId = null, CancellationToken cancellationToken = default); + } +} \ No newline at end of file diff --git a/modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.Domain/Volo/Abp/BlobStoring/Database/IContainerRepository.cs b/modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.Domain/Volo/Abp/BlobStoring/Database/IContainerRepository.cs new file mode 100644 index 0000000000..f3358f2520 --- /dev/null +++ b/modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.Domain/Volo/Abp/BlobStoring/Database/IContainerRepository.cs @@ -0,0 +1,15 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using JetBrains.Annotations; +using Volo.Abp.Domain.Repositories; + +namespace Volo.Abp.BlobStoring.Database +{ + public interface IContainerRepository : IBasicRepository + { + Task CreateIfNotExistAsync([NotNull] string name, Guid? tenantId = null, CancellationToken cancellationToken = default); + + Task FindAsync([NotNull] string name, Guid? tenantId = null, CancellationToken cancellationToken = default); + } +} \ No newline at end of file diff --git a/modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.EntityFrameworkCore/FodyWeavers.xml b/modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.EntityFrameworkCore/FodyWeavers.xml new file mode 100644 index 0000000000..be0de3a908 --- /dev/null +++ b/modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.EntityFrameworkCore/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.EntityFrameworkCore/FodyWeavers.xsd b/modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.EntityFrameworkCore/FodyWeavers.xsd new file mode 100644 index 0000000000..3f3946e282 --- /dev/null +++ b/modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.EntityFrameworkCore/FodyWeavers.xsd @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file diff --git a/modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.EntityFrameworkCore/Volo.Abp.BlobStoring.Database.EntityFrameworkCore.csproj b/modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.EntityFrameworkCore/Volo.Abp.BlobStoring.Database.EntityFrameworkCore.csproj new file mode 100644 index 0000000000..20c155c894 --- /dev/null +++ b/modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.EntityFrameworkCore/Volo.Abp.BlobStoring.Database.EntityFrameworkCore.csproj @@ -0,0 +1,16 @@ + + + + + + + netstandard2.0 + + + + + + + + + diff --git a/modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.EntityFrameworkCore/Volo/Abp/BlobStoring/Database/EntityFrameworkCore/BlobStoringDatabaseDbContext.cs b/modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.EntityFrameworkCore/Volo/Abp/BlobStoring/Database/EntityFrameworkCore/BlobStoringDatabaseDbContext.cs new file mode 100644 index 0000000000..13b5c12429 --- /dev/null +++ b/modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.EntityFrameworkCore/Volo/Abp/BlobStoring/Database/EntityFrameworkCore/BlobStoringDatabaseDbContext.cs @@ -0,0 +1,28 @@ +using Microsoft.EntityFrameworkCore; +using Volo.Abp.Data; +using Volo.Abp.EntityFrameworkCore; + +namespace Volo.Abp.BlobStoring.Database.EntityFrameworkCore +{ + [ConnectionStringName(BlobStoringDatabaseDbProperties.ConnectionStringName)] + public class BlobStoringDatabaseDbContext : AbpDbContext, IBlobStoringDatabaseDbContext + { + public DbSet Containers { get; set; } + + public DbSet Blobs { get; set; } + + public BlobStoringDatabaseDbContext(DbContextOptions options) + : base(options) + { + + } + + protected override void OnModelCreating(ModelBuilder builder) + { + base.OnModelCreating(builder); + + builder.ConfigureDatabaseBlobStoring(); + } + + } +} \ No newline at end of file diff --git a/modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.EntityFrameworkCore/Volo/Abp/BlobStoring/Database/EntityFrameworkCore/BlobStoringDatabaseDbContextModelCreatingExtensions.cs b/modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.EntityFrameworkCore/Volo/Abp/BlobStoring/Database/EntityFrameworkCore/BlobStoringDatabaseDbContextModelCreatingExtensions.cs new file mode 100644 index 0000000000..9a3d52c4c9 --- /dev/null +++ b/modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.EntityFrameworkCore/Volo/Abp/BlobStoring/Database/EntityFrameworkCore/BlobStoringDatabaseDbContextModelCreatingExtensions.cs @@ -0,0 +1,47 @@ +using Microsoft.EntityFrameworkCore; +using System; +using Volo.Abp.EntityFrameworkCore.Modeling; + +namespace Volo.Abp.BlobStoring.Database.EntityFrameworkCore +{ + public static class BlobStoringDatabaseDbContextModelCreatingExtensions + { + public static void ConfigureDatabaseBlobStoring( + this ModelBuilder builder, + Action optionsAction = null) + { + Check.NotNull(builder, nameof(builder)); + + var options = new BlobStoringDatabaseModelBuilderConfigurationOptions( + BlobStoringDatabaseDbProperties.DbTablePrefix, + BlobStoringDatabaseDbProperties.DbSchema + ); + + optionsAction?.Invoke(options); + + builder.Entity(b => + { + b.ToTable(options.TablePrefix + "Containers", options.Schema); + + b.ConfigureByConvention(); + + b.Property(p => p.Name).IsRequired().HasMaxLength(ContainerConsts.MaxNameLength); + + b.HasIndex(x => x.Name); + }); + + builder.Entity(b => + { + b.ToTable(options.TablePrefix + "Blobs", options.Schema); + + b.ConfigureByConvention(); + + b.Property(p => p.Name).IsRequired().HasMaxLength(BlobConsts.MaxNameLength); + b.Property(p => p.ContainerId).IsRequired(); + b.Property(p => p.Content).HasMaxLength(BlobConsts.MaxContentLength); + + b.HasIndex(x => x.Name); + }); + } + } +} \ No newline at end of file diff --git a/modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.EntityFrameworkCore/Volo/Abp/BlobStoring/Database/EntityFrameworkCore/BlobStoringDatabaseEntityFrameworkCoreModule.cs b/modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.EntityFrameworkCore/Volo/Abp/BlobStoring/Database/EntityFrameworkCore/BlobStoringDatabaseEntityFrameworkCoreModule.cs new file mode 100644 index 0000000000..7e2d00521e --- /dev/null +++ b/modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.EntityFrameworkCore/Volo/Abp/BlobStoring/Database/EntityFrameworkCore/BlobStoringDatabaseEntityFrameworkCoreModule.cs @@ -0,0 +1,23 @@ +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.EntityFrameworkCore; +using Volo.Abp.Modularity; + +namespace Volo.Abp.BlobStoring.Database.EntityFrameworkCore +{ + [DependsOn( + typeof(BlobStoringDatabaseDomainModule), + typeof(AbpEntityFrameworkCoreModule) + )] + public class BlobStoringDatabaseEntityFrameworkCoreModule : AbpModule + { + public override void ConfigureServices(ServiceConfigurationContext context) + { + context.Services.AddAbpDbContext(options => + { + options.AddRepository(); + + options.AddRepository(); + }); + } + } +} \ No newline at end of file diff --git a/modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.EntityFrameworkCore/Volo/Abp/BlobStoring/Database/EntityFrameworkCore/BlobStoringDatabaseModelBuilderConfigurationOptions.cs b/modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.EntityFrameworkCore/Volo/Abp/BlobStoring/Database/EntityFrameworkCore/BlobStoringDatabaseModelBuilderConfigurationOptions.cs new file mode 100644 index 0000000000..324eb46c34 --- /dev/null +++ b/modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.EntityFrameworkCore/Volo/Abp/BlobStoring/Database/EntityFrameworkCore/BlobStoringDatabaseModelBuilderConfigurationOptions.cs @@ -0,0 +1,18 @@ +using JetBrains.Annotations; +using Volo.Abp.EntityFrameworkCore.Modeling; + +namespace Volo.Abp.BlobStoring.Database.EntityFrameworkCore +{ + public class BlobStoringDatabaseModelBuilderConfigurationOptions : AbpModelBuilderConfigurationOptions + { + public BlobStoringDatabaseModelBuilderConfigurationOptions( + [NotNull] string tablePrefix = "", + [CanBeNull] string schema = null) + : base( + tablePrefix, + schema) + { + + } + } +} \ No newline at end of file diff --git a/modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.EntityFrameworkCore/Volo/Abp/BlobStoring/Database/EntityFrameworkCore/EfCoreBlobRepository.cs b/modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.EntityFrameworkCore/Volo/Abp/BlobStoring/Database/EntityFrameworkCore/EfCoreBlobRepository.cs new file mode 100644 index 0000000000..a6ac7ae2c1 --- /dev/null +++ b/modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.EntityFrameworkCore/Volo/Abp/BlobStoring/Database/EntityFrameworkCore/EfCoreBlobRepository.cs @@ -0,0 +1,47 @@ +using Microsoft.EntityFrameworkCore; +using System; +using System.Threading; +using System.Threading.Tasks; +using Volo.Abp.Domain.Repositories.EntityFrameworkCore; +using Volo.Abp.EntityFrameworkCore; + +namespace Volo.Abp.BlobStoring.Database.EntityFrameworkCore +{ + public class EfCoreBlobRepository : EfCoreRepository, IBlobRepository + { + public EfCoreBlobRepository(IDbContextProvider dbContextProvider) : base(dbContextProvider) + { + } + + public virtual async Task FindAsync(Guid containerId, string name, Guid? tenantId = null, CancellationToken cancellationToken = default) + { + return await DbSet.FirstOrDefaultAsync( + x => x.ContainerId == containerId && + x.Name == name && + x.TenantId == tenantId, + GetCancellationToken(cancellationToken)); + } + + public virtual async Task ExistsAsync(Guid containerId, string name, Guid? tenantId = null, CancellationToken cancellationToken = default) + { + return await DbSet.AnyAsync( + x => x.ContainerId == containerId && + x.Name == name && + x.TenantId == tenantId, + GetCancellationToken(cancellationToken)); + } + + public virtual async Task DeleteAsync(Guid containerId, string name, Guid? tenantId = null, CancellationToken cancellationToken = default) + { + var blob = await FindAsync(containerId, name, tenantId, cancellationToken); + + if (blob == null) + { + return false; + } + + await base.DeleteAsync(blob.Id, cancellationToken: GetCancellationToken(cancellationToken)); + return true; + } + } +} \ No newline at end of file diff --git a/modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.EntityFrameworkCore/Volo/Abp/BlobStoring/Database/EntityFrameworkCore/EfCoreContainerRepository.cs b/modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.EntityFrameworkCore/Volo/Abp/BlobStoring/Database/EntityFrameworkCore/EfCoreContainerRepository.cs new file mode 100644 index 0000000000..1c35bac904 --- /dev/null +++ b/modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.EntityFrameworkCore/Volo/Abp/BlobStoring/Database/EntityFrameworkCore/EfCoreContainerRepository.cs @@ -0,0 +1,37 @@ +using Microsoft.EntityFrameworkCore; +using System; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Volo.Abp.Domain.Repositories.EntityFrameworkCore; +using Volo.Abp.EntityFrameworkCore; + +namespace Volo.Abp.BlobStoring.Database.EntityFrameworkCore +{ + public class EfCoreContainerRepository : EfCoreRepository, IContainerRepository + { + public EfCoreContainerRepository(IDbContextProvider dbContextProvider) : base(dbContextProvider) + { + } + + public virtual async Task CreateIfNotExistAsync(string name, Guid? tenantId = null, CancellationToken cancellationToken = default) + { + var container = await FindAsync(name, tenantId, cancellationToken); + if (container != null) + { + return container; + } + + container = new Container(Guid.NewGuid(), name, tenantId); + await base.InsertAsync(container, true, GetCancellationToken(cancellationToken)); + + return container; + } + + public virtual async Task FindAsync(string name, Guid? tenantId = null, CancellationToken cancellationToken = default) + { + return await DbSet.WhereIf(tenantId != null, x => x.TenantId == tenantId) + .FirstOrDefaultAsync(x => x.Name == name, GetCancellationToken(cancellationToken)); + } + } +} \ No newline at end of file diff --git a/modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.EntityFrameworkCore/Volo/Abp/BlobStoring/Database/EntityFrameworkCore/IBlobStoringDatabaseDbContext.cs b/modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.EntityFrameworkCore/Volo/Abp/BlobStoring/Database/EntityFrameworkCore/IBlobStoringDatabaseDbContext.cs new file mode 100644 index 0000000000..201efcb27a --- /dev/null +++ b/modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.EntityFrameworkCore/Volo/Abp/BlobStoring/Database/EntityFrameworkCore/IBlobStoringDatabaseDbContext.cs @@ -0,0 +1,14 @@ +using Microsoft.EntityFrameworkCore; +using Volo.Abp.Data; +using Volo.Abp.EntityFrameworkCore; + +namespace Volo.Abp.BlobStoring.Database.EntityFrameworkCore +{ + [ConnectionStringName(BlobStoringDatabaseDbProperties.ConnectionStringName)] + public interface IBlobStoringDatabaseDbContext : IEfCoreDbContext + { + DbSet Containers { get; } + + DbSet Blobs { get; } + } +} \ No newline at end of file diff --git a/modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.MongoDB/FodyWeavers.xml b/modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.MongoDB/FodyWeavers.xml new file mode 100644 index 0000000000..be0de3a908 --- /dev/null +++ b/modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.MongoDB/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.MongoDB/FodyWeavers.xsd b/modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.MongoDB/FodyWeavers.xsd new file mode 100644 index 0000000000..3f3946e282 --- /dev/null +++ b/modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.MongoDB/FodyWeavers.xsd @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file diff --git a/modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.MongoDB/Volo.Abp.BlobStoring.Database.MongoDB.csproj b/modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.MongoDB/Volo.Abp.BlobStoring.Database.MongoDB.csproj new file mode 100644 index 0000000000..dee196df48 --- /dev/null +++ b/modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.MongoDB/Volo.Abp.BlobStoring.Database.MongoDB.csproj @@ -0,0 +1,16 @@ + + + + + + + netstandard2.0 + + + + + + + + + diff --git a/modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.MongoDB/Volo/Abp/BlobStoring/Database/MongoDB/BlobStoringDatabaseMongoDbContext.cs b/modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.MongoDB/Volo/Abp/BlobStoring/Database/MongoDB/BlobStoringDatabaseMongoDbContext.cs new file mode 100644 index 0000000000..6dd9f11979 --- /dev/null +++ b/modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.MongoDB/Volo/Abp/BlobStoring/Database/MongoDB/BlobStoringDatabaseMongoDbContext.cs @@ -0,0 +1,21 @@ +using MongoDB.Driver; +using Volo.Abp.Data; +using Volo.Abp.MongoDB; + +namespace Volo.Abp.BlobStoring.Database.MongoDB +{ + [ConnectionStringName(BlobStoringDatabaseDbProperties.ConnectionStringName)] + public class BlobStoringDatabaseMongoDbContext : AbpMongoDbContext, IBlobStoringDatabaseMongoDbContext + { + public IMongoCollection Containers => Collection(); + + public IMongoCollection Blobs => Collection(); + + protected override void CreateModel(IMongoModelBuilder modelBuilder) + { + base.CreateModel(modelBuilder); + + modelBuilder.ConfigureDatabaseBlobStoring(); + } + } +} \ No newline at end of file diff --git a/modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.MongoDB/Volo/Abp/BlobStoring/Database/MongoDB/BlobStoringDatabaseMongoDbContextExtensions.cs b/modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.MongoDB/Volo/Abp/BlobStoring/Database/MongoDB/BlobStoringDatabaseMongoDbContextExtensions.cs new file mode 100644 index 0000000000..fa9442cd65 --- /dev/null +++ b/modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.MongoDB/Volo/Abp/BlobStoring/Database/MongoDB/BlobStoringDatabaseMongoDbContextExtensions.cs @@ -0,0 +1,31 @@ +using System; +using Volo.Abp.MongoDB; + +namespace Volo.Abp.BlobStoring.Database.MongoDB +{ + public static class BlobStoringDatabaseMongoDbContextExtensions + { + public static void ConfigureDatabaseBlobStoring( + this IMongoModelBuilder builder, + Action optionsAction = null) + { + Check.NotNull(builder, nameof(builder)); + + var options = new BlobStoringDatabaseMongoModelBuilderConfigurationOptions( + BlobStoringDatabaseDbProperties.DbTablePrefix + ); + + optionsAction?.Invoke(options); + + builder.Entity(b => + { + b.CollectionName = options.CollectionPrefix + "Containers"; + }); + + builder.Entity(b => + { + b.CollectionName = options.CollectionPrefix + "Blobs"; + }); + } + } +} \ No newline at end of file diff --git a/modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.MongoDB/Volo/Abp/BlobStoring/Database/MongoDB/BlobStoringDatabaseMongoDbModule.cs b/modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.MongoDB/Volo/Abp/BlobStoring/Database/MongoDB/BlobStoringDatabaseMongoDbModule.cs new file mode 100644 index 0000000000..868dff97a5 --- /dev/null +++ b/modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.MongoDB/Volo/Abp/BlobStoring/Database/MongoDB/BlobStoringDatabaseMongoDbModule.cs @@ -0,0 +1,22 @@ +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.Modularity; +using Volo.Abp.MongoDB; + +namespace Volo.Abp.BlobStoring.Database.MongoDB +{ + [DependsOn( + typeof(BlobStoringDatabaseDomainModule), + typeof(AbpMongoDbModule) + )] + public class BlobStoringDatabaseMongoDbModule : AbpModule + { + public override void ConfigureServices(ServiceConfigurationContext context) + { + context.Services.AddMongoDbContext(options => + { + options.AddRepository(); + options.AddRepository(); + }); + } + } +} diff --git a/modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.MongoDB/Volo/Abp/BlobStoring/Database/MongoDB/BlobStoringDatabaseMongoModelBuilderConfigurationOptions.cs b/modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.MongoDB/Volo/Abp/BlobStoring/Database/MongoDB/BlobStoringDatabaseMongoModelBuilderConfigurationOptions.cs new file mode 100644 index 0000000000..27133ac6dc --- /dev/null +++ b/modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.MongoDB/Volo/Abp/BlobStoring/Database/MongoDB/BlobStoringDatabaseMongoModelBuilderConfigurationOptions.cs @@ -0,0 +1,14 @@ +using JetBrains.Annotations; +using Volo.Abp.MongoDB; + +namespace Volo.Abp.BlobStoring.Database.MongoDB +{ + public class BlobStoringDatabaseMongoModelBuilderConfigurationOptions : AbpMongoModelBuilderConfigurationOptions + { + public BlobStoringDatabaseMongoModelBuilderConfigurationOptions( + [NotNull] string collectionPrefix = "") + : base(collectionPrefix) + { + } + } +} \ No newline at end of file diff --git a/modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.MongoDB/Volo/Abp/BlobStoring/Database/MongoDB/IBlobStoringDatabaseMongoDbContext.cs b/modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.MongoDB/Volo/Abp/BlobStoring/Database/MongoDB/IBlobStoringDatabaseMongoDbContext.cs new file mode 100644 index 0000000000..7f90312a6d --- /dev/null +++ b/modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.MongoDB/Volo/Abp/BlobStoring/Database/MongoDB/IBlobStoringDatabaseMongoDbContext.cs @@ -0,0 +1,14 @@ +using MongoDB.Driver; +using Volo.Abp.Data; +using Volo.Abp.MongoDB; + +namespace Volo.Abp.BlobStoring.Database.MongoDB +{ + [ConnectionStringName(BlobStoringDatabaseDbProperties.ConnectionStringName)] + public interface IBlobStoringDatabaseMongoDbContext : IAbpMongoDbContext + { + IMongoCollection Containers { get; } + + IMongoCollection Blobs { get; } + } +} diff --git a/modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.MongoDB/Volo/Abp/BlobStoring/Database/MongoDB/MongoDbBlobRepository.cs b/modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.MongoDB/Volo/Abp/BlobStoring/Database/MongoDB/MongoDbBlobRepository.cs new file mode 100644 index 0000000000..185751d1a5 --- /dev/null +++ b/modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.MongoDB/Volo/Abp/BlobStoring/Database/MongoDB/MongoDbBlobRepository.cs @@ -0,0 +1,47 @@ +using MongoDB.Driver.Linq; +using System; +using System.Threading; +using System.Threading.Tasks; +using Volo.Abp.Domain.Repositories.MongoDB; +using Volo.Abp.MongoDB; + +namespace Volo.Abp.BlobStoring.Database.MongoDB +{ + public class MongoDbBlobRepository : MongoDbRepository, IBlobRepository + { + public MongoDbBlobRepository(IMongoDbContextProvider dbContextProvider) : base(dbContextProvider) + { + } + + public virtual async Task FindAsync(Guid containerId, string name, Guid? tenantId = null, CancellationToken cancellationToken = default) + { + return await GetMongoQueryable().FirstOrDefaultAsync( + x => x.ContainerId == containerId && + x.Name == name && + x.TenantId == tenantId, + GetCancellationToken(cancellationToken)); + } + + public virtual async Task ExistsAsync(Guid containerId, string name, Guid? tenantId = null, CancellationToken cancellationToken = default) + { + return await GetMongoQueryable().AnyAsync( + x => x.ContainerId == containerId && + x.Name == name && + x.TenantId == tenantId, + GetCancellationToken(cancellationToken)); + } + + public virtual async Task DeleteAsync(Guid containerId, string name, Guid? tenantId = null, CancellationToken cancellationToken = default) + { + var blob = await FindAsync(containerId, name, tenantId, cancellationToken); + + if (blob == null) + { + return false; + } + + await base.DeleteAsync(blob, cancellationToken: GetCancellationToken(cancellationToken)); + return true; + } + } +} \ No newline at end of file diff --git a/modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.MongoDB/Volo/Abp/BlobStoring/Database/MongoDB/MongoDbContainerRepository.cs b/modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.MongoDB/Volo/Abp/BlobStoring/Database/MongoDB/MongoDbContainerRepository.cs new file mode 100644 index 0000000000..47911d685e --- /dev/null +++ b/modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.MongoDB/Volo/Abp/BlobStoring/Database/MongoDB/MongoDbContainerRepository.cs @@ -0,0 +1,35 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using Volo.Abp.Domain.Repositories.MongoDB; +using Volo.Abp.MongoDB; + +namespace Volo.Abp.BlobStoring.Database.MongoDB +{ + public class MongoDbContainerRepository : MongoDbRepository, IContainerRepository + { + public MongoDbContainerRepository(IMongoDbContextProvider dbContextProvider) : base(dbContextProvider) + { + } + + public virtual async Task CreateIfNotExistAsync(string name, Guid? tenantId = null, CancellationToken cancellationToken = default) + { + var container = await FindAsync(name, tenantId, cancellationToken); + + if (container != null) + { + return container; + } + + container = new Container(Guid.NewGuid(), name, tenantId); + await InsertAsync(container, true, GetCancellationToken(cancellationToken)); + + return container; + } + + public virtual async Task FindAsync(string name, Guid? tenantId = null, CancellationToken cancellationToken = default) + { + return await base.FindAsync(x => x.Name == name, cancellationToken: GetCancellationToken(cancellationToken)); + } + } +} \ No newline at end of file diff --git a/modules/blob-storing-database/test/Volo.Abp.BlobStoring.Database.Domain.Tests/BlobStoringDatabaseDomainTestBase.cs b/modules/blob-storing-database/test/Volo.Abp.BlobStoring.Database.Domain.Tests/BlobStoringDatabaseDomainTestBase.cs new file mode 100644 index 0000000000..405ac84008 --- /dev/null +++ b/modules/blob-storing-database/test/Volo.Abp.BlobStoring.Database.Domain.Tests/BlobStoringDatabaseDomainTestBase.cs @@ -0,0 +1,10 @@ +namespace Volo.Abp.BlobStoring.Database +{ + /* Inherit from this class for your domain layer tests. + * See SampleManager_Tests for example. + */ + public abstract class BlobStoringDatabaseDomainTestBase : BlobStoringDatabaseTestBase + { + + } +} \ No newline at end of file diff --git a/modules/blob-storing-database/test/Volo.Abp.BlobStoring.Database.Domain.Tests/BlobStoringDatabaseDomainTestModule.cs b/modules/blob-storing-database/test/Volo.Abp.BlobStoring.Database.Domain.Tests/BlobStoringDatabaseDomainTestModule.cs new file mode 100644 index 0000000000..a7ad7427f8 --- /dev/null +++ b/modules/blob-storing-database/test/Volo.Abp.BlobStoring.Database.Domain.Tests/BlobStoringDatabaseDomainTestModule.cs @@ -0,0 +1,13 @@ +using Volo.Abp.BlobStoring.Database.EntityFrameworkCore; +using Volo.Abp.Modularity; + +namespace Volo.Abp.BlobStoring.Database +{ + [DependsOn( + typeof(BlobStoringDatabaseEntityFrameworkCoreTestModule) + )] + public class BlobStoringDatabaseDomainTestModule : AbpModule + { + + } +} diff --git a/modules/blob-storing-database/test/Volo.Abp.BlobStoring.Database.Domain.Tests/Volo.Abp.BlobStoring.Database.Domain.Tests.csproj b/modules/blob-storing-database/test/Volo.Abp.BlobStoring.Database.Domain.Tests/Volo.Abp.BlobStoring.Database.Domain.Tests.csproj new file mode 100644 index 0000000000..20de7c154e --- /dev/null +++ b/modules/blob-storing-database/test/Volo.Abp.BlobStoring.Database.Domain.Tests/Volo.Abp.BlobStoring.Database.Domain.Tests.csproj @@ -0,0 +1,13 @@ + + + + netcoreapp3.1 + Volo.Abp.BlobStoring.Database + + + + + + + + diff --git a/modules/blob-storing-database/test/Volo.Abp.BlobStoring.Database.EntityFrameworkCore.Tests/EntityFrameworkCore/BlobStoringDatabaseEntityFrameworkCoreTestBase.cs b/modules/blob-storing-database/test/Volo.Abp.BlobStoring.Database.EntityFrameworkCore.Tests/EntityFrameworkCore/BlobStoringDatabaseEntityFrameworkCoreTestBase.cs new file mode 100644 index 0000000000..d465616047 --- /dev/null +++ b/modules/blob-storing-database/test/Volo.Abp.BlobStoring.Database.EntityFrameworkCore.Tests/EntityFrameworkCore/BlobStoringDatabaseEntityFrameworkCoreTestBase.cs @@ -0,0 +1,7 @@ +namespace Volo.Abp.BlobStoring.Database.EntityFrameworkCore +{ + public abstract class BlobStoringDatabaseEntityFrameworkCoreTestBase : BlobStoringDatabaseTestBase + { + + } +} \ No newline at end of file diff --git a/modules/blob-storing-database/test/Volo.Abp.BlobStoring.Database.EntityFrameworkCore.Tests/EntityFrameworkCore/BlobStoringDatabaseEntityFrameworkCoreTestModule.cs b/modules/blob-storing-database/test/Volo.Abp.BlobStoring.Database.EntityFrameworkCore.Tests/EntityFrameworkCore/BlobStoringDatabaseEntityFrameworkCoreTestModule.cs new file mode 100644 index 0000000000..617c9373b9 --- /dev/null +++ b/modules/blob-storing-database/test/Volo.Abp.BlobStoring.Database.EntityFrameworkCore.Tests/EntityFrameworkCore/BlobStoringDatabaseEntityFrameworkCoreTestModule.cs @@ -0,0 +1,41 @@ +using Microsoft.Data.Sqlite; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage; +using Volo.Abp.EntityFrameworkCore; +using Volo.Abp.Modularity; + +namespace Volo.Abp.BlobStoring.Database.EntityFrameworkCore +{ + [DependsOn( + typeof(BlobStoringDatabaseTestBaseModule), + typeof(BlobStoringDatabaseEntityFrameworkCoreModule) + )] + public class BlobStoringDatabaseEntityFrameworkCoreTestModule : AbpModule + { + public override void ConfigureServices(ServiceConfigurationContext context) + { + var sqliteConnection = CreateDatabaseAndGetConnection(); + + Configure(options => + { + options.Configure(abpDbContextConfigurationContext => + { + abpDbContextConfigurationContext.DbContextOptions.UseSqlite(sqliteConnection); + }); + }); + } + + private static SqliteConnection CreateDatabaseAndGetConnection() + { + var connection = new SqliteConnection("Data Source=:memory:"); + connection.Open(); + + new BlobStoringDatabaseDbContext( + new DbContextOptionsBuilder().UseSqlite(connection).Options + ).GetService().CreateTables(); + + return connection; + } + } +} diff --git a/modules/blob-storing-database/test/Volo.Abp.BlobStoring.Database.EntityFrameworkCore.Tests/EntityFrameworkCore/EfCoreContainer_Test.cs b/modules/blob-storing-database/test/Volo.Abp.BlobStoring.Database.EntityFrameworkCore.Tests/EntityFrameworkCore/EfCoreContainer_Test.cs new file mode 100644 index 0000000000..3d46a033d8 --- /dev/null +++ b/modules/blob-storing-database/test/Volo.Abp.BlobStoring.Database.EntityFrameworkCore.Tests/EntityFrameworkCore/EfCoreContainer_Test.cs @@ -0,0 +1,7 @@ +namespace Volo.Abp.BlobStoring.Database.EntityFrameworkCore +{ + public class EfCoreContainer_Test : BlobContainer_Tests + { + + } +} \ No newline at end of file diff --git a/modules/blob-storing-database/test/Volo.Abp.BlobStoring.Database.EntityFrameworkCore.Tests/Volo.Abp.BlobStoring.Database.EntityFrameworkCore.Tests.csproj b/modules/blob-storing-database/test/Volo.Abp.BlobStoring.Database.EntityFrameworkCore.Tests/Volo.Abp.BlobStoring.Database.EntityFrameworkCore.Tests.csproj new file mode 100644 index 0000000000..8bdc5080aa --- /dev/null +++ b/modules/blob-storing-database/test/Volo.Abp.BlobStoring.Database.EntityFrameworkCore.Tests/Volo.Abp.BlobStoring.Database.EntityFrameworkCore.Tests.csproj @@ -0,0 +1,17 @@ + + + + netcoreapp3.1 + Volo.Abp.BlobStoring.Database + + + + + + + + + + + + diff --git a/modules/blob-storing-database/test/Volo.Abp.BlobStoring.Database.MongoDB.Tests/MongoDB/BlobStoringDatabaseMongoDbTestBase.cs b/modules/blob-storing-database/test/Volo.Abp.BlobStoring.Database.MongoDB.Tests/MongoDB/BlobStoringDatabaseMongoDbTestBase.cs new file mode 100644 index 0000000000..c96746cbb2 --- /dev/null +++ b/modules/blob-storing-database/test/Volo.Abp.BlobStoring.Database.MongoDB.Tests/MongoDB/BlobStoringDatabaseMongoDbTestBase.cs @@ -0,0 +1,7 @@ +namespace Volo.Abp.BlobStoring.Database.MongoDB +{ + public abstract class BlobStoringDatabaseMongoDbTestBase : BlobStoringDatabaseTestBase + { + + } +} \ No newline at end of file diff --git a/modules/blob-storing-database/test/Volo.Abp.BlobStoring.Database.MongoDB.Tests/MongoDB/BlobStoringDatabaseMongoDbTestModule.cs b/modules/blob-storing-database/test/Volo.Abp.BlobStoring.Database.MongoDB.Tests/MongoDB/BlobStoringDatabaseMongoDbTestModule.cs new file mode 100644 index 0000000000..03eb96f6d3 --- /dev/null +++ b/modules/blob-storing-database/test/Volo.Abp.BlobStoring.Database.MongoDB.Tests/MongoDB/BlobStoringDatabaseMongoDbTestModule.cs @@ -0,0 +1,25 @@ +using System; +using Volo.Abp.Data; +using Volo.Abp.Modularity; + +namespace Volo.Abp.BlobStoring.Database.MongoDB +{ + [DependsOn( + typeof(BlobStoringDatabaseTestBaseModule), + typeof(BlobStoringDatabaseMongoDbModule) + )] + public class BlobStoringDatabaseMongoDbTestModule : AbpModule + { + public override void ConfigureServices(ServiceConfigurationContext context) + { + var connectionString = MongoDbFixture.ConnectionString.EnsureEndsWith('/') + + "Db_" + + Guid.NewGuid().ToString("N"); + + Configure(options => + { + options.ConnectionStrings.Default = connectionString; + }); + } + } +} \ No newline at end of file diff --git a/modules/blob-storing-database/test/Volo.Abp.BlobStoring.Database.MongoDB.Tests/MongoDB/MongoDbFixture.cs b/modules/blob-storing-database/test/Volo.Abp.BlobStoring.Database.MongoDB.Tests/MongoDB/MongoDbFixture.cs new file mode 100644 index 0000000000..9473e5274d --- /dev/null +++ b/modules/blob-storing-database/test/Volo.Abp.BlobStoring.Database.MongoDB.Tests/MongoDB/MongoDbFixture.cs @@ -0,0 +1,16 @@ +using System; +using Mongo2Go; + +namespace Volo.Abp.BlobStoring.Database.MongoDB +{ + public class MongoDbFixture : IDisposable + { + private static readonly MongoDbRunner MongoDbRunner = MongoDbRunner.Start(); + public static readonly string ConnectionString = MongoDbRunner.ConnectionString; + + public void Dispose() + { + MongoDbRunner?.Dispose(); + } + } +} \ No newline at end of file diff --git a/modules/blob-storing-database/test/Volo.Abp.BlobStoring.Database.MongoDB.Tests/MongoDB/MongoTestCollection.cs b/modules/blob-storing-database/test/Volo.Abp.BlobStoring.Database.MongoDB.Tests/MongoDB/MongoTestCollection.cs new file mode 100644 index 0000000000..b9778249e4 --- /dev/null +++ b/modules/blob-storing-database/test/Volo.Abp.BlobStoring.Database.MongoDB.Tests/MongoDB/MongoTestCollection.cs @@ -0,0 +1,10 @@ +using Xunit; + +namespace Volo.Abp.BlobStoring.Database.MongoDB +{ + [CollectionDefinition(Name)] + public class MongoTestCollection : ICollectionFixture + { + public const string Name = "MongoDB Collection"; + } +} \ No newline at end of file diff --git a/modules/blob-storing-database/test/Volo.Abp.BlobStoring.Database.MongoDB.Tests/Volo.Abp.BlobStoring.Database.MongoDB.Tests.csproj b/modules/blob-storing-database/test/Volo.Abp.BlobStoring.Database.MongoDB.Tests/Volo.Abp.BlobStoring.Database.MongoDB.Tests.csproj new file mode 100644 index 0000000000..9b37676068 --- /dev/null +++ b/modules/blob-storing-database/test/Volo.Abp.BlobStoring.Database.MongoDB.Tests/Volo.Abp.BlobStoring.Database.MongoDB.Tests.csproj @@ -0,0 +1,15 @@ + + + + netcoreapp3.1 + Volo.Abp.BlobStoring.Database + + + + + + + + + + diff --git a/modules/blob-storing-database/test/Volo.Abp.BlobStoring.Database.TestBase/BlobStoringDatabaseTestBase.cs b/modules/blob-storing-database/test/Volo.Abp.BlobStoring.Database.TestBase/BlobStoringDatabaseTestBase.cs new file mode 100644 index 0000000000..7b426f55dc --- /dev/null +++ b/modules/blob-storing-database/test/Volo.Abp.BlobStoring.Database.TestBase/BlobStoringDatabaseTestBase.cs @@ -0,0 +1,59 @@ +using System; +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp; +using Volo.Abp.Modularity; +using Volo.Abp.Uow; +using Volo.Abp.Testing; + +namespace Volo.Abp.BlobStoring.Database +{ + public abstract class BlobStoringDatabaseTestBase : AbpIntegratedTest + where TStartupModule : IAbpModule + { + protected override void SetAbpApplicationCreationOptions(AbpApplicationCreationOptions options) + { + options.UseAutofac(); + } + + protected virtual Task WithUnitOfWorkAsync(Func func) + { + return WithUnitOfWorkAsync(new AbpUnitOfWorkOptions(), func); + } + + protected virtual async Task WithUnitOfWorkAsync(AbpUnitOfWorkOptions options, Func action) + { + using (var scope = ServiceProvider.CreateScope()) + { + var uowManager = scope.ServiceProvider.GetRequiredService(); + + using (var uow = uowManager.Begin(options)) + { + await action(); + + await uow.CompleteAsync(); + } + } + } + + protected virtual Task WithUnitOfWorkAsync(Func> func) + { + return WithUnitOfWorkAsync(new AbpUnitOfWorkOptions(), func); + } + + protected virtual async Task WithUnitOfWorkAsync(AbpUnitOfWorkOptions options, Func> func) + { + using (var scope = ServiceProvider.CreateScope()) + { + var uowManager = scope.ServiceProvider.GetRequiredService(); + + using (var uow = uowManager.Begin(options)) + { + var result = await func(); + await uow.CompleteAsync(); + return result; + } + } + } + } +} diff --git a/modules/blob-storing-database/test/Volo.Abp.BlobStoring.Database.TestBase/BlobStoringDatabaseTestBaseModule.cs b/modules/blob-storing-database/test/Volo.Abp.BlobStoring.Database.TestBase/BlobStoringDatabaseTestBaseModule.cs new file mode 100644 index 0000000000..96e3bcaa90 --- /dev/null +++ b/modules/blob-storing-database/test/Volo.Abp.BlobStoring.Database.TestBase/BlobStoringDatabaseTestBaseModule.cs @@ -0,0 +1,30 @@ +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.Authorization; +using Volo.Abp.Autofac; +using Volo.Abp.Modularity; + +namespace Volo.Abp.BlobStoring.Database +{ + [DependsOn( + typeof(AbpAutofacModule), + typeof(AbpTestBaseModule), + typeof(AbpAuthorizationModule), + typeof(BlobStoringDatabaseDomainModule), + typeof(AbpBlobStoringTestModule) + )] + public class BlobStoringDatabaseTestBaseModule : AbpModule + { + public override void ConfigureServices(ServiceConfigurationContext context) + { + context.Services.AddAlwaysAllowAuthorization(); + + Configure(options => + { + options.Containers.ConfigureAll((containerName, containerConfiguration) => + { + containerConfiguration.UseDatabase(); + }); + }); + } + } +} diff --git a/modules/blob-storing-database/test/Volo.Abp.BlobStoring.Database.TestBase/Security/FakeCurrentPrincipalAccessor.cs b/modules/blob-storing-database/test/Volo.Abp.BlobStoring.Database.TestBase/Security/FakeCurrentPrincipalAccessor.cs new file mode 100644 index 0000000000..e1b9aa1813 --- /dev/null +++ b/modules/blob-storing-database/test/Volo.Abp.BlobStoring.Database.TestBase/Security/FakeCurrentPrincipalAccessor.cs @@ -0,0 +1,43 @@ +using System.Collections.Generic; +using System.Security.Claims; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Security.Claims; + +namespace Volo.Abp.BlobStoring.Database.Security +{ + [Dependency(ReplaceServices = true)] + public class FakeCurrentPrincipalAccessor : ThreadCurrentPrincipalAccessor + { + public override ClaimsPrincipal GetClaimsPrincipal() + { + return GetPrincipal(); + } + + private ClaimsPrincipal _principal; + + private ClaimsPrincipal GetPrincipal() + { + if (_principal == null) + { + lock (this) + { + if (_principal == null) + { + _principal = new ClaimsPrincipal( + new ClaimsIdentity( + new List + { + new Claim(AbpClaimTypes.UserId,"2e701e62-0953-4dd3-910b-dc6cc93ccb0d"), + new Claim(AbpClaimTypes.UserName,"admin"), + new Claim(AbpClaimTypes.Email,"admin@abp.io") + } + ) + ); + } + } + } + + return _principal; + } + } +} diff --git a/modules/blob-storing-database/test/Volo.Abp.BlobStoring.Database.TestBase/Volo.Abp.BlobStoring.Database.TestBase.csproj b/modules/blob-storing-database/test/Volo.Abp.BlobStoring.Database.TestBase/Volo.Abp.BlobStoring.Database.TestBase.csproj new file mode 100644 index 0000000000..8dfa603e98 --- /dev/null +++ b/modules/blob-storing-database/test/Volo.Abp.BlobStoring.Database.TestBase/Volo.Abp.BlobStoring.Database.TestBase.csproj @@ -0,0 +1,22 @@ + + + + netcoreapp3.1 + Volo.Abp.BlobStoring.Database + + + + + + + + + + + + + + + + + diff --git a/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/IdentityDbContextModelBuilderExtensions.cs b/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/IdentityDbContextModelBuilderExtensions.cs index 7dfe662884..74cc2ce995 100644 --- a/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/IdentityDbContextModelBuilderExtensions.cs +++ b/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/IdentityDbContextModelBuilderExtensions.cs @@ -28,14 +28,25 @@ namespace Volo.Abp.Identity.EntityFrameworkCore b.ConfigureByConvention(); b.ConfigureAbpUser(); - b.Property(u => u.NormalizedUserName).IsRequired().HasMaxLength(IdentityUserConsts.MaxNormalizedUserNameLength).HasColumnName(nameof(IdentityUser.NormalizedUserName)); - b.Property(u => u.NormalizedEmail).IsRequired().HasMaxLength(IdentityUserConsts.MaxNormalizedEmailLength).HasColumnName(nameof(IdentityUser.NormalizedEmail)); - b.Property(u => u.PasswordHash).HasMaxLength(IdentityUserConsts.MaxPasswordHashLength).HasColumnName(nameof(IdentityUser.PasswordHash)); - b.Property(u => u.SecurityStamp).IsRequired().HasMaxLength(IdentityUserConsts.MaxSecurityStampLength).HasColumnName(nameof(IdentityUser.SecurityStamp)); - b.Property(u => u.TwoFactorEnabled).HasDefaultValue(false).HasColumnName(nameof(IdentityUser.TwoFactorEnabled)); - b.Property(u => u.LockoutEnabled).HasDefaultValue(false).HasColumnName(nameof(IdentityUser.LockoutEnabled)); - b.Property(u => u.AccessFailedCount).HasDefaultValue(0).HasColumnName(nameof(IdentityUser.AccessFailedCount)); - + b.Property(u => u.NormalizedUserName).IsRequired() + .HasMaxLength(IdentityUserConsts.MaxNormalizedUserNameLength) + .HasColumnName(nameof(IdentityUser.NormalizedUserName)); + b.Property(u => u.NormalizedEmail).IsRequired() + .HasMaxLength(IdentityUserConsts.MaxNormalizedEmailLength) + .HasColumnName(nameof(IdentityUser.NormalizedEmail)); + b.Property(u => u.PasswordHash).HasMaxLength(IdentityUserConsts.MaxPasswordHashLength) + .HasColumnName(nameof(IdentityUser.PasswordHash)); + b.Property(u => u.SecurityStamp).IsRequired().HasMaxLength(IdentityUserConsts.MaxSecurityStampLength) + .HasColumnName(nameof(IdentityUser.SecurityStamp)); + b.Property(u => u.TwoFactorEnabled).HasDefaultValue(false) + .HasColumnName(nameof(IdentityUser.TwoFactorEnabled)); + b.Property(u => u.LockoutEnabled).HasDefaultValue(false) + .HasColumnName(nameof(IdentityUser.LockoutEnabled)); + + b.Property(u => u.AccessFailedCount) + .If(!builder.IsUsingOracle(), p => p.HasDefaultValue(0)) + .HasColumnName(nameof(IdentityUser.AccessFailedCount)); + b.HasMany(u => u.Claims).WithOne().HasForeignKey(uc => uc.UserId).IsRequired(); b.HasMany(u => u.Logins).WithOne().HasForeignKey(ul => ul.UserId).IsRequired(); b.HasMany(u => u.Roles).WithOne().HasForeignKey(ur => ur.UserId).IsRequired(); @@ -55,7 +66,7 @@ namespace Volo.Abp.Identity.EntityFrameworkCore b.ConfigureByConvention(); b.Property(x => x.Id).ValueGeneratedNever(); - + b.Property(uc => uc.ClaimType).HasMaxLength(IdentityUserClaimConsts.MaxClaimTypeLength).IsRequired(); b.Property(uc => uc.ClaimValue).HasMaxLength(IdentityUserClaimConsts.MaxClaimValueLength); @@ -68,12 +79,12 @@ namespace Volo.Abp.Identity.EntityFrameworkCore b.ConfigureByConvention(); - b.HasKey(ur => new { ur.UserId, ur.RoleId }); + b.HasKey(ur => new {ur.UserId, ur.RoleId}); b.HasOne().WithMany().HasForeignKey(ur => ur.RoleId).IsRequired(); b.HasOne().WithMany(u => u.Roles).HasForeignKey(ur => ur.UserId).IsRequired(); - b.HasIndex(ur => new { ur.RoleId, ur.UserId }); + b.HasIndex(ur => new {ur.RoleId, ur.UserId}); }); builder.Entity(b => @@ -82,13 +93,16 @@ namespace Volo.Abp.Identity.EntityFrameworkCore b.ConfigureByConvention(); - b.HasKey(x => new { x.UserId, x.LoginProvider }); + b.HasKey(x => new {x.UserId, x.LoginProvider}); - b.Property(ul => ul.LoginProvider).HasMaxLength(IdentityUserLoginConsts.MaxLoginProviderLength).IsRequired(); - b.Property(ul => ul.ProviderKey).HasMaxLength(IdentityUserLoginConsts.MaxProviderKeyLength).IsRequired(); - b.Property(ul => ul.ProviderDisplayName).HasMaxLength(IdentityUserLoginConsts.MaxProviderDisplayNameLength); + b.Property(ul => ul.LoginProvider).HasMaxLength(IdentityUserLoginConsts.MaxLoginProviderLength) + .IsRequired(); + b.Property(ul => ul.ProviderKey).HasMaxLength(IdentityUserLoginConsts.MaxProviderKeyLength) + .IsRequired(); + b.Property(ul => ul.ProviderDisplayName) + .HasMaxLength(IdentityUserLoginConsts.MaxProviderDisplayNameLength); - b.HasIndex(l => new { l.LoginProvider, l.ProviderKey }); + b.HasIndex(l => new {l.LoginProvider, l.ProviderKey}); }); builder.Entity(b => @@ -97,9 +111,10 @@ namespace Volo.Abp.Identity.EntityFrameworkCore b.ConfigureByConvention(); - b.HasKey(l => new { l.UserId, l.LoginProvider, l.Name }); + b.HasKey(l => new {l.UserId, l.LoginProvider, l.Name}); - b.Property(ul => ul.LoginProvider).HasMaxLength(IdentityUserTokenConsts.MaxLoginProviderLength).IsRequired(); + b.Property(ul => ul.LoginProvider).HasMaxLength(IdentityUserTokenConsts.MaxLoginProviderLength) + .IsRequired(); b.Property(ul => ul.Name).HasMaxLength(IdentityUserTokenConsts.MaxNameLength).IsRequired(); }); @@ -111,7 +126,9 @@ namespace Volo.Abp.Identity.EntityFrameworkCore b.Property(r => r.Name).IsRequired().HasMaxLength(IdentityRoleConsts.MaxNameLength); b.Property(r => r.NormalizedName).IsRequired().HasMaxLength(IdentityRoleConsts.MaxNormalizedNameLength); - b.Property(u => u.ConcurrencyStamp).IsRequired().IsConcurrencyToken().HasMaxLength(IdentityRoleConsts.MaxConcurrencyStampLength).HasColumnName(nameof(IdentityRole.ConcurrencyStamp)); + b.Property(u => u.ConcurrencyStamp).IsRequired().IsConcurrencyToken() + .HasMaxLength(IdentityRoleConsts.MaxConcurrencyStampLength) + .HasColumnName(nameof(IdentityRole.ConcurrencyStamp)); b.Property(r => r.IsDefault).HasColumnName(nameof(IdentityRole.IsDefault)); b.Property(r => r.IsStatic).HasColumnName(nameof(IdentityRole.IsStatic)); b.Property(r => r.IsPublic).HasColumnName(nameof(IdentityRole.IsPublic)); @@ -141,11 +158,14 @@ namespace Volo.Abp.Identity.EntityFrameworkCore b.ConfigureByConvention(); - b.Property(uc => uc.Name).HasMaxLength(IdentityClaimTypeConsts.MaxNameLength).IsRequired(); // make unique + b.Property(uc => uc.Name).HasMaxLength(IdentityClaimTypeConsts.MaxNameLength) + .IsRequired(); // make unique b.Property(uc => uc.Regex).HasMaxLength(IdentityClaimTypeConsts.MaxRegexLength); b.Property(uc => uc.RegexDescription).HasMaxLength(IdentityClaimTypeConsts.MaxRegexDescriptionLength); b.Property(uc => uc.Description).HasMaxLength(IdentityClaimTypeConsts.MaxDescriptionLength); - b.Property(uc => uc.ConcurrencyStamp).IsRequired().IsConcurrencyToken().HasMaxLength(IdentityClaimTypeConsts.MaxConcurrencyStampLength).HasColumnName(nameof(IdentityClaimType.ConcurrencyStamp)); + b.Property(uc => uc.ConcurrencyStamp).IsRequired().IsConcurrencyToken() + .HasMaxLength(IdentityClaimTypeConsts.MaxConcurrencyStampLength) + .HasColumnName(nameof(IdentityClaimType.ConcurrencyStamp)); }); builder.Entity(b => @@ -154,8 +174,10 @@ namespace Volo.Abp.Identity.EntityFrameworkCore b.ConfigureByConvention(); - b.Property(ou => ou.Code).IsRequired().HasMaxLength(OrganizationUnitConsts.MaxCodeLength).HasColumnName(nameof(OrganizationUnit.Code)); - b.Property(ou => ou.DisplayName).IsRequired().HasMaxLength(OrganizationUnitConsts.MaxDisplayNameLength).HasColumnName(nameof(OrganizationUnit.DisplayName)); + b.Property(ou => ou.Code).IsRequired().HasMaxLength(OrganizationUnitConsts.MaxCodeLength) + .HasColumnName(nameof(OrganizationUnit.Code)); + b.Property(ou => ou.DisplayName).IsRequired().HasMaxLength(OrganizationUnitConsts.MaxDisplayNameLength) + .HasColumnName(nameof(OrganizationUnit.DisplayName)); b.HasMany().WithOne().HasForeignKey(ou => ou.ParentId); b.HasMany(ou => ou.Roles).WithOne().HasForeignKey(our => our.OrganizationUnitId).IsRequired(); @@ -169,11 +191,11 @@ namespace Volo.Abp.Identity.EntityFrameworkCore b.ConfigureByConvention(); - b.HasKey(ou => new { ou.OrganizationUnitId, ou.RoleId }); + b.HasKey(ou => new {ou.OrganizationUnitId, ou.RoleId}); b.HasOne().WithMany().HasForeignKey(ou => ou.RoleId).IsRequired(); - b.HasIndex(ou => new { ou.RoleId, ou.OrganizationUnitId }); + b.HasIndex(ou => new {ou.RoleId, ou.OrganizationUnitId}); }); builder.Entity(b => @@ -182,12 +204,12 @@ namespace Volo.Abp.Identity.EntityFrameworkCore b.ConfigureByConvention(); - b.HasKey(ou => new { ou.OrganizationUnitId, ou.UserId }); + b.HasKey(ou => new {ou.OrganizationUnitId, ou.UserId}); b.HasOne().WithMany().HasForeignKey(ou => ou.OrganizationUnitId).IsRequired(); - b.HasIndex(ou => new { ou.UserId, ou.OrganizationUnitId }); + b.HasIndex(ou => new {ou.UserId, ou.OrganizationUnitId}); }); } } -} +} \ No newline at end of file diff --git a/modules/identityserver/src/Volo.Abp.IdentityServer.Domain.Shared/Volo/Abp/IdentityServer/Clients/ClientPostLogoutRedirectUriConsts.cs b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain.Shared/Volo/Abp/IdentityServer/Clients/ClientPostLogoutRedirectUriConsts.cs index 8fe55ba018..3092232d47 100644 --- a/modules/identityserver/src/Volo.Abp.IdentityServer.Domain.Shared/Volo/Abp/IdentityServer/Clients/ClientPostLogoutRedirectUriConsts.cs +++ b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain.Shared/Volo/Abp/IdentityServer/Clients/ClientPostLogoutRedirectUriConsts.cs @@ -3,5 +3,6 @@ public class ClientPostLogoutRedirectUriConsts { public const int PostLogoutRedirectUriMaxLength = 2000; + public static int PostLogoutRedirectUriMaxLengthValue { get; set; } = 2000; } } \ No newline at end of file diff --git a/modules/identityserver/src/Volo.Abp.IdentityServer.Domain.Shared/Volo/Abp/IdentityServer/Clients/ClientRedirectUriConsts.cs b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain.Shared/Volo/Abp/IdentityServer/Clients/ClientRedirectUriConsts.cs index d67bbea509..e972388487 100644 --- a/modules/identityserver/src/Volo.Abp.IdentityServer.Domain.Shared/Volo/Abp/IdentityServer/Clients/ClientRedirectUriConsts.cs +++ b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain.Shared/Volo/Abp/IdentityServer/Clients/ClientRedirectUriConsts.cs @@ -3,5 +3,6 @@ public class ClientRedirectUriConsts { public const int RedirectUriMaxLength = 2000; + public static int RedirectUriMaxLengthValue { get; set; } = 2000; } } \ No newline at end of file diff --git a/modules/identityserver/src/Volo.Abp.IdentityServer.Domain.Shared/Volo/Abp/IdentityServer/Grants/PersistedGrantConsts.cs b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain.Shared/Volo/Abp/IdentityServer/Grants/PersistedGrantConsts.cs index ab46d03ece..6d0836ab17 100644 --- a/modules/identityserver/src/Volo.Abp.IdentityServer.Domain.Shared/Volo/Abp/IdentityServer/Grants/PersistedGrantConsts.cs +++ b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain.Shared/Volo/Abp/IdentityServer/Grants/PersistedGrantConsts.cs @@ -6,9 +6,7 @@ public const int TypeMaxLength = 50; public const int SubjectIdMaxLength = 200; public const int ClientIdMaxLength = 200; - - // 50000 chosen to be explicit to allow enough size to avoid truncation, yet stay beneath the MySql row size limit of ~65K - // apparently anything over 4K converts to nvarchar(max) on SqlServer public const int DataMaxLength = 50000; + public static int DataMaxLengthValue { get; set; }= 50000; } } \ No newline at end of file diff --git a/modules/identityserver/src/Volo.Abp.IdentityServer.Domain.Shared/Volo/Abp/IdentityServer/SecretConsts.cs b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain.Shared/Volo/Abp/IdentityServer/SecretConsts.cs index b21f901f25..76635e322e 100644 --- a/modules/identityserver/src/Volo.Abp.IdentityServer.Domain.Shared/Volo/Abp/IdentityServer/SecretConsts.cs +++ b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain.Shared/Volo/Abp/IdentityServer/SecretConsts.cs @@ -4,6 +4,7 @@ { public const int TypeMaxLength = 250; public const int ValueMaxLength = 4000; + public static int ValueMaxLengthValue { get; set; } = ValueMaxLength; public const int DescriptionMaxLength = 2000; } } \ No newline at end of file diff --git a/modules/identityserver/src/Volo.Abp.IdentityServer.EntityFrameworkCore/Volo/Abp/IdentityServer/EntityFrameworkCore/IdentityServerDbContextModelCreatingExtensions.cs b/modules/identityserver/src/Volo.Abp.IdentityServer.EntityFrameworkCore/Volo/Abp/IdentityServer/EntityFrameworkCore/IdentityServerDbContextModelCreatingExtensions.cs index 5eff3195c9..8acb8a9539 100644 --- a/modules/identityserver/src/Volo.Abp.IdentityServer.EntityFrameworkCore/Volo/Abp/IdentityServer/EntityFrameworkCore/IdentityServerDbContextModelCreatingExtensions.cs +++ b/modules/identityserver/src/Volo.Abp.IdentityServer.EntityFrameworkCore/Volo/Abp/IdentityServer/EntityFrameworkCore/IdentityServerDbContextModelCreatingExtensions.cs @@ -65,7 +65,7 @@ namespace Volo.Abp.IdentityServer.EntityFrameworkCore b.ConfigureByConvention(); - b.HasKey(x => new { x.ClientId, x.GrantType }); + b.HasKey(x => new {x.ClientId, x.GrantType}); b.Property(x => x.GrantType).HasMaxLength(ClientGrantTypeConsts.GrantTypeMaxLength).IsRequired(); }); @@ -76,16 +76,14 @@ namespace Volo.Abp.IdentityServer.EntityFrameworkCore b.ConfigureByConvention(); - b.HasKey(x => new { x.ClientId, x.RedirectUri }); + b.HasKey(x => new {x.ClientId, x.RedirectUri}); - if (IsMySql(builder, options)) + if (IsDatabaseProvider(builder, options, EfCoreDatabaseProvider.MySql)) { - b.Property(x => x.RedirectUri).HasMaxLength(300).IsRequired(); - } - else - { - b.Property(x => x.RedirectUri).HasMaxLength(ClientRedirectUriConsts.RedirectUriMaxLength).IsRequired(); - } + ClientRedirectUriConsts.RedirectUriMaxLengthValue = 300; + } + + b.Property(x => x.RedirectUri).HasMaxLength(ClientRedirectUriConsts.RedirectUriMaxLengthValue).IsRequired(); }); builder.Entity(b => @@ -94,16 +92,16 @@ namespace Volo.Abp.IdentityServer.EntityFrameworkCore b.ConfigureByConvention(); - b.HasKey(x => new { x.ClientId, x.PostLogoutRedirectUri }); + b.HasKey(x => new {x.ClientId, x.PostLogoutRedirectUri}); - if (IsMySql(builder, options)) - { - b.Property(x => x.PostLogoutRedirectUri).HasMaxLength(300).IsRequired(); - } - else + if (IsDatabaseProvider(builder, options, EfCoreDatabaseProvider.MySql)) { - b.Property(x => x.PostLogoutRedirectUri).HasMaxLength(ClientPostLogoutRedirectUriConsts.PostLogoutRedirectUriMaxLength).IsRequired(); - } + ClientPostLogoutRedirectUriConsts.PostLogoutRedirectUriMaxLengthValue = 300; + } + + b.Property(x => x.PostLogoutRedirectUri) + .HasMaxLength(ClientPostLogoutRedirectUriConsts.PostLogoutRedirectUriMaxLengthValue) + .IsRequired(); }); builder.Entity(b => @@ -112,7 +110,7 @@ namespace Volo.Abp.IdentityServer.EntityFrameworkCore b.ConfigureByConvention(); - b.HasKey(x => new { x.ClientId, x.Scope }); + b.HasKey(x => new {x.ClientId, x.Scope}); b.Property(x => x.Scope).HasMaxLength(ClientScopeConsts.ScopeMaxLength).IsRequired(); }); @@ -123,18 +121,16 @@ namespace Volo.Abp.IdentityServer.EntityFrameworkCore b.ConfigureByConvention(); - b.HasKey(x => new { x.ClientId, x.Type, x.Value }); + b.HasKey(x => new {x.ClientId, x.Type, x.Value}); b.Property(x => x.Type).HasMaxLength(SecretConsts.TypeMaxLength).IsRequired(); - if (IsMySql(builder, options)) - { - b.Property(x => x.Value).HasMaxLength(300).IsRequired(); - } - else + if (IsDatabaseProvider(builder, options, EfCoreDatabaseProvider.MySql, EfCoreDatabaseProvider.Oracle)) { - b.Property(x => x.Value).HasMaxLength(SecretConsts.ValueMaxLength).IsRequired(); + SecretConsts.ValueMaxLengthValue = 300; } + + b.Property(x => x.Value).HasMaxLength(SecretConsts.ValueMaxLengthValue).IsRequired(); b.Property(x => x.Description).HasMaxLength(SecretConsts.DescriptionMaxLength); }); @@ -145,7 +141,7 @@ namespace Volo.Abp.IdentityServer.EntityFrameworkCore b.ConfigureByConvention(); - b.HasKey(x => new { x.ClientId, x.Type, x.Value }); + b.HasKey(x => new {x.ClientId, x.Type, x.Value}); b.Property(x => x.Type).HasMaxLength(ClientClaimConsts.TypeMaxLength).IsRequired(); b.Property(x => x.Value).HasMaxLength(ClientClaimConsts.ValueMaxLength).IsRequired(); @@ -157,7 +153,7 @@ namespace Volo.Abp.IdentityServer.EntityFrameworkCore b.ConfigureByConvention(); - b.HasKey(x => new { x.ClientId, x.Provider }); + b.HasKey(x => new {x.ClientId, x.Provider}); b.Property(x => x.Provider).HasMaxLength(ClientIdPRestrictionConsts.ProviderMaxLength).IsRequired(); }); @@ -168,7 +164,7 @@ namespace Volo.Abp.IdentityServer.EntityFrameworkCore b.ConfigureByConvention(); - b.HasKey(x => new { x.ClientId, x.Origin }); + b.HasKey(x => new {x.ClientId, x.Origin}); b.Property(x => x.Origin).HasMaxLength(ClientCorsOriginConsts.OriginMaxLength).IsRequired(); }); @@ -179,7 +175,7 @@ namespace Volo.Abp.IdentityServer.EntityFrameworkCore b.ConfigureByConvention(); - b.HasKey(x => new { x.ClientId, x.Key }); + b.HasKey(x => new {x.ClientId, x.Key}); b.Property(x => x.Key).HasMaxLength(ClientPropertyConsts.KeyMaxLength).IsRequired(); b.Property(x => x.Value).HasMaxLength(ClientPropertyConsts.ValueMaxLength).IsRequired(); @@ -197,18 +193,16 @@ namespace Volo.Abp.IdentityServer.EntityFrameworkCore b.Property(x => x.ClientId).HasMaxLength(PersistedGrantConsts.ClientIdMaxLength).IsRequired(); b.Property(x => x.CreationTime).IsRequired(); - if (IsMySql(builder, options)) + if (IsDatabaseProvider(builder, options, EfCoreDatabaseProvider.MySql)) { - b.Property(x => x.Data).HasMaxLength(10000).IsRequired(); - } - else - { - b.Property(x => x.Data).HasMaxLength(PersistedGrantConsts.DataMaxLength).IsRequired(); + PersistedGrantConsts.DataMaxLengthValue = 10000; //TODO: MySQL accepts 20.000. We can consider to change in v3.0. } + + b.Property(x => x.Data).HasMaxLength(PersistedGrantConsts.DataMaxLengthValue).IsRequired(); b.HasKey(x => x.Key); //TODO: What about Id!!! - b.HasIndex(x => new { x.SubjectId, x.ClientId, x.Type }); + b.HasIndex(x => new {x.SubjectId, x.ClientId, x.Type}); b.HasIndex(x => x.Expiration); }); @@ -234,7 +228,7 @@ namespace Volo.Abp.IdentityServer.EntityFrameworkCore b.ConfigureByConvention(); - b.HasKey(x => new { x.IdentityResourceId, x.Type }); + b.HasKey(x => new {x.IdentityResourceId, x.Type}); b.Property(x => x.Type).HasMaxLength(UserClaimConsts.TypeMaxLength).IsRequired(); }); @@ -263,19 +257,17 @@ namespace Volo.Abp.IdentityServer.EntityFrameworkCore b.ConfigureByConvention(); - b.HasKey(x => new { x.ApiResourceId, x.Type, x.Value }); + b.HasKey(x => new {x.ApiResourceId, x.Type, x.Value}); b.Property(x => x.Type).HasMaxLength(SecretConsts.TypeMaxLength).IsRequired(); b.Property(x => x.Description).HasMaxLength(SecretConsts.DescriptionMaxLength); - if (IsMySql(builder, options)) - { - b.Property(x => x.Value).HasMaxLength(300).IsRequired(); - } - else + if (IsDatabaseProvider(builder, options, EfCoreDatabaseProvider.MySql, EfCoreDatabaseProvider.Oracle)) { - b.Property(x => x.Value).HasMaxLength(SecretConsts.ValueMaxLength).IsRequired(); - } + SecretConsts.ValueMaxLengthValue = 300; + } + + b.Property(x => x.Value).HasMaxLength(SecretConsts.ValueMaxLengthValue).IsRequired(); }); builder.Entity(b => @@ -284,7 +276,7 @@ namespace Volo.Abp.IdentityServer.EntityFrameworkCore b.ConfigureByConvention(); - b.HasKey(x => new { x.ApiResourceId, x.Type }); + b.HasKey(x => new {x.ApiResourceId, x.Type}); b.Property(x => x.Type).HasMaxLength(UserClaimConsts.TypeMaxLength).IsRequired(); }); @@ -295,13 +287,13 @@ namespace Volo.Abp.IdentityServer.EntityFrameworkCore b.ConfigureByConvention(); - b.HasKey(x => new { x.ApiResourceId, x.Name }); + b.HasKey(x => new {x.ApiResourceId, x.Name}); b.Property(x => x.Name).HasMaxLength(ApiScopeConsts.NameMaxLength).IsRequired(); b.Property(x => x.DisplayName).HasMaxLength(ApiScopeConsts.DisplayNameMaxLength); b.Property(x => x.Description).HasMaxLength(ApiScopeConsts.DescriptionMaxLength); - b.HasMany(x => x.UserClaims).WithOne().HasForeignKey(x => new { x.ApiResourceId, x.Name }).IsRequired(); + b.HasMany(x => x.UserClaims).WithOne().HasForeignKey(x => new {x.ApiResourceId, x.Name}).IsRequired(); }); builder.Entity(b => @@ -310,7 +302,7 @@ namespace Volo.Abp.IdentityServer.EntityFrameworkCore b.ConfigureByConvention(); - b.HasKey(x => new { x.ApiResourceId, x.Name, x.Type }); + b.HasKey(x => new {x.ApiResourceId, x.Name, x.Type}); b.Property(x => x.Type).HasMaxLength(UserClaimConsts.TypeMaxLength).IsRequired(); b.Property(x => x.Name).HasMaxLength(ApiScopeConsts.NameMaxLength).IsRequired(); @@ -329,18 +321,27 @@ namespace Volo.Abp.IdentityServer.EntityFrameworkCore b.Property(x => x.Expiration).IsRequired(); b.Property(x => x.Data).HasMaxLength(50000).IsRequired(); - b.HasIndex(x => new { x.UserCode }).IsUnique(); + b.HasIndex(x => new {x.UserCode}).IsUnique(); b.HasIndex(x => x.DeviceCode).IsUnique(); b.HasIndex(x => x.Expiration); }); } - private static bool IsMySql( + private static bool IsDatabaseProvider( ModelBuilder modelBuilder, - IdentityServerModelBuilderConfigurationOptions options) + IdentityServerModelBuilderConfigurationOptions options, + params EfCoreDatabaseProvider[] providers) { - return options.DatabaseProvider == EfCoreDatabaseProvider.MySql || - modelBuilder.IsUsingMySQL(); + foreach (var provider in providers) + { + if (options.DatabaseProvider == EfCoreDatabaseProvider.MySql || + modelBuilder.GetDatabaseProvider() == provider) + { + return true; + } + } + + return false; } } -} +} \ No newline at end of file diff --git a/modules/setting-management/src/Volo.Abp.SettingManagement.Domain.Shared/Volo/Abp/SettingManagement/SettingConsts.cs b/modules/setting-management/src/Volo.Abp.SettingManagement.Domain.Shared/Volo/Abp/SettingManagement/SettingConsts.cs index 0b92e24ece..44a21f3ccb 100644 --- a/modules/setting-management/src/Volo.Abp.SettingManagement.Domain.Shared/Volo/Abp/SettingManagement/SettingConsts.cs +++ b/modules/setting-management/src/Volo.Abp.SettingManagement.Domain.Shared/Volo/Abp/SettingManagement/SettingConsts.cs @@ -4,6 +4,7 @@ { public const int MaxNameLength = 128; public const int MaxValueLength = 2048; + public static int MaxValueLengthValue { get; set; } = MaxValueLength; public const int MaxProviderNameLength = 64; public const int MaxProviderKeyLength = 64; } diff --git a/modules/setting-management/src/Volo.Abp.SettingManagement.EntityFrameworkCore/Volo/Abp/SettingManagement/EntityFrameworkCore/SettingManagementDbContextModelBuilderExtensions.cs b/modules/setting-management/src/Volo.Abp.SettingManagement.EntityFrameworkCore/Volo/Abp/SettingManagement/EntityFrameworkCore/SettingManagementDbContextModelBuilderExtensions.cs index 715d7abfd0..6f0bd5c3f8 100644 --- a/modules/setting-management/src/Volo.Abp.SettingManagement.EntityFrameworkCore/Volo/Abp/SettingManagement/EntityFrameworkCore/SettingManagementDbContextModelBuilderExtensions.cs +++ b/modules/setting-management/src/Volo.Abp.SettingManagement.EntityFrameworkCore/Volo/Abp/SettingManagement/EntityFrameworkCore/SettingManagementDbContextModelBuilderExtensions.cs @@ -41,7 +41,10 @@ namespace Volo.Abp.SettingManagement.EntityFrameworkCore b.ConfigureByConvention(); b.Property(x => x.Name).HasMaxLength(SettingConsts.MaxNameLength).IsRequired(); - b.Property(x => x.Value).HasMaxLength(SettingConsts.MaxValueLength).IsRequired(); + + if (builder.IsUsingOracle()) { SettingConsts.MaxValueLengthValue = 2000; } + b.Property(x => x.Value).HasMaxLength(SettingConsts.MaxValueLengthValue).IsRequired(); + b.Property(x => x.ProviderName).HasMaxLength(SettingConsts.MaxProviderNameLength); b.Property(x => x.ProviderKey).HasMaxLength(SettingConsts.MaxProviderKeyLength); diff --git a/npm/ng-packs/angular.json b/npm/ng-packs/angular.json index 39a10f827b..2b876fe21c 100644 --- a/npm/ng-packs/angular.json +++ b/npm/ng-packs/angular.json @@ -440,18 +440,27 @@ "assets": ["apps/dev-app/src/favicon.ico", "apps/dev-app/src/assets"], "extractCss": true, "styles": [ - "apps/dev-app/src/styles.scss", - "node_modules/bootstrap/dist/css/bootstrap.min.css", + { + "input": "node_modules/@abp/ng.theme.shared/styles/bootstrap-rtl.min.css", + "inject": false, + "bundleName": "bootstrap-rtl.min" + }, + { + "input": "node_modules/bootstrap/dist/css/bootstrap.min.css", + "inject": true, + "bundleName": "bootstrap-ltr.min" + }, { "input": "node_modules/@fortawesome/fontawesome-free/css/all.min.css", - "lazy": true, + "inject": true, "bundleName": "fontawesome-all.min" }, { "input": "node_modules/@fortawesome/fontawesome-free/css/v4-shims.min.css", - "lazy": true, + "inject": true, "bundleName": "fontawesome-v4-shims.min" - } + }, + "apps/dev-app/src/styles.scss" ], "scripts": [] }, diff --git a/npm/ng-packs/apps/dev-app/src/app/app.component.ts b/npm/ng-packs/apps/dev-app/src/app/app.component.ts index 8187365734..bf2a27962a 100644 --- a/npm/ng-packs/apps/dev-app/src/app/app.component.ts +++ b/npm/ng-packs/apps/dev-app/src/app/app.component.ts @@ -1,6 +1,4 @@ -import { LazyLoadService, LOADING_STRATEGY } from '@abp/ng.core'; -import { Component, OnInit } from '@angular/core'; -import { forkJoin } from 'rxjs'; +import { Component } from '@angular/core'; @Component({ selector: 'app-root', @@ -9,17 +7,4 @@ import { forkJoin } from 'rxjs'; `, }) -export class AppComponent implements OnInit { - constructor(private lazyLoadService: LazyLoadService) {} - - ngOnInit() { - forkJoin( - this.lazyLoadService.load( - LOADING_STRATEGY.PrependAnonymousStyleToHead('fontawesome-v4-shims.min.css'), - ), - this.lazyLoadService.load( - LOADING_STRATEGY.PrependAnonymousStyleToHead('fontawesome-all.min.css'), - ), - ).subscribe(); - } -} +export class AppComponent {} diff --git a/npm/ng-packs/packages/core/src/lib/services/lazy-load.service.ts b/npm/ng-packs/packages/core/src/lib/services/lazy-load.service.ts index cec9d4a6cf..4ba89949fd 100644 --- a/npm/ng-packs/packages/core/src/lib/services/lazy-load.service.ts +++ b/npm/ng-packs/packages/core/src/lib/services/lazy-load.service.ts @@ -8,7 +8,7 @@ import { uuid } from '../utils'; providedIn: 'root', }) export class LazyLoadService { - readonly loaded = new Set(); + readonly loaded = new Map(); loadedLibraries: { [url: string]: ReplaySubject } = {}; @@ -45,7 +45,7 @@ export class LazyLoadService { throwError(new CustomEvent('error')), ), ), - tap(() => this.loaded.add(strategy.path)), + tap(() => this.loaded.set(strategy.path, strategy.element)), delay(100), shareReplay({ bufferSize: 1, refCount: true }), ); @@ -113,4 +113,14 @@ export class LazyLoadService { }); }); } + + remove(path: string): boolean { + const element = this.loaded.get(path); + + if (!element) return false; + + element.parentNode.removeChild(element); + this.loaded.delete(path); + return true; + } } diff --git a/npm/ng-packs/packages/core/src/lib/strategies/loading.strategy.ts b/npm/ng-packs/packages/core/src/lib/strategies/loading.strategy.ts index be89a751ed..f1f3abe30c 100644 --- a/npm/ng-packs/packages/core/src/lib/strategies/loading.strategy.ts +++ b/npm/ng-packs/packages/core/src/lib/strategies/loading.strategy.ts @@ -5,6 +5,8 @@ import { CrossOriginStrategy, CROSS_ORIGIN_STRATEGY } from './cross-origin.strat import { DomStrategy, DOM_STRATEGY } from './dom.strategy'; export abstract class LoadingStrategy { + element: T; + constructor( public path: string, protected domStrategy: DomStrategy = DOM_STRATEGY.AppendToHead(), @@ -14,10 +16,10 @@ export abstract class LoadingStrategy(): Observable { + this.element = this.createElement(); + return of(null).pipe( - switchMap(() => - fromLazyLoad(this.createElement(), this.domStrategy, this.crossOriginStrategy), - ), + switchMap(() => fromLazyLoad(this.element, this.domStrategy, this.crossOriginStrategy)), ); } } diff --git a/npm/ng-packs/packages/core/src/lib/tests/lazy-load.service.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/lazy-load.service.spec.ts index 3596da2591..4868fb78f3 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/lazy-load.service.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/lazy-load.service.spec.ts @@ -49,7 +49,7 @@ describe('LazyLoadService', () => { it('should emit a custom load event if loaded if resource is loaded before', done => { const loadEvent = new CustomEvent('load'); - service.loaded.add(strategy.path); + service.loaded.set(strategy.path, null); service.load(strategy).subscribe(event => { expect(event).toEqual(loadEvent); @@ -57,6 +57,32 @@ describe('LazyLoadService', () => { }); }); }); + + describe('#remove', () => { + const service = new LazyLoadService(); + + it('should remove an already lazy loaded element and return true', () => { + const script = document.createElement('script'); + document.body.appendChild(script); + service.loaded.set('x', script); + + expect(document.body.lastElementChild).toBe(script); + + const result = service.remove('x'); + + expect(document.body.lastElementChild).toBeNull(); + expect(service.loaded.has('x')).toBe(false); + expect(result).toBe(true); + }); + + it('should return false when path not found', () => { + service.loaded.set('foo', null); + + const result = service.remove('bar'); + + expect(result).toBe(false); + }); + }); }); describe('LazyLoadService (Deprecated)', () => { diff --git a/npm/ng-packs/packages/core/src/lib/tests/loading.strategy.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/loading.strategy.spec.ts index 4b950cf533..4215e6eee5 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/loading.strategy.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/loading.strategy.spec.ts @@ -39,6 +39,7 @@ describe('ScriptLoadingStrategy', () => { const strategy = new ScriptLoadingStrategy(path, domStrategy, crossOriginStrategy); strategy.createStream().subscribe(event => { + expect(strategy.element.tagName).toBe('SCRIPT'); expect(event.detail.crossOrigin).toBe('use-credentials'); done(); }); @@ -78,6 +79,7 @@ describe('StyleLoadingStrategy', () => { const strategy = new StyleLoadingStrategy(path, domStrategy, crossOriginStrategy); strategy.createStream().subscribe(event => { + expect(strategy.element.tagName).toBe('LINK'); expect(event.detail.crossOrigin).toBe('use-credentials'); done(); }); diff --git a/npm/ng-packs/packages/core/src/lib/tests/localization-utils.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/localization-utils.spec.ts index f5319edc2d..1409c0da21 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/localization-utils.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/localization-utils.spec.ts @@ -2,9 +2,53 @@ import { createLocalizationPipeKeyGenerator, createLocalizer, createLocalizerWithFallback, + getLocaleDirection, } from '../utils/localization-utils'; describe('Localization Utils', () => { + describe('#getLocaleDirection', () => { + test.each` + locale | expected + ${undefined} | ${'ltr'} + ${null} | ${'ltr'} + ${''} | ${'ltr'} + ${'en'} | ${'ltr'} + ${'en-US'} | ${'ltr'} + ${'pt'} | ${'ltr'} + ${'pt-PT'} | ${'ltr'} + ${'ar'} | ${'rtl'} + ${'ar-AE'} | ${'rtl'} + ${'ar-QA'} | ${'rtl'} + ${'ckb'} | ${'rtl'} + ${'ckb-IR'} | ${'rtl'} + ${'fa'} | ${'rtl'} + ${'fa-AF'} | ${'rtl'} + ${'he'} | ${'rtl'} + ${'ks'} | ${'rtl'} + ${'ksb'} | ${'ltr'} + ${'ksf'} | ${'ltr'} + ${'ksh'} | ${'ltr'} + ${'lrc'} | ${'rtl'} + ${'lrc-IQ'} | ${'rtl'} + ${'mzn'} | ${'rtl'} + ${'pa'} | ${'ltr'} + ${'pa-Arab'} | ${'rtl'} + ${'ps'} | ${'rtl'} + ${'ps-PK'} | ${'rtl'} + ${'sd'} | ${'rtl'} + ${'ug'} | ${'rtl'} + ${'ur'} | ${'rtl'} + ${'ur-IN'} | ${'rtl'} + ${'uz'} | ${'ltr'} + ${'uz-Arab'} | ${'rtl'} + ${'yi'} | ${'rtl'} + ${'zh'} | ${'ltr'} + ${'zh-Hans'} | ${'ltr'} + `('should return $expected when $locale is given as parameter', ({ locale, expected }) => { + expect(getLocaleDirection(locale)).toBe(expected); + }); + }); + describe('#createLocalizer', () => { const localize = createLocalizer({ values: { foo: { bar: 'baz' }, x: { y: 'z' } }, diff --git a/npm/ng-packs/packages/core/src/lib/utils/localization-utils.ts b/npm/ng-packs/packages/core/src/lib/utils/localization-utils.ts index ed094f7fc7..09478c8ed8 100644 --- a/npm/ng-packs/packages/core/src/lib/utils/localization-utils.ts +++ b/npm/ng-packs/packages/core/src/lib/utils/localization-utils.ts @@ -1,5 +1,14 @@ import { ApplicationConfiguration } from '../models/application-configuration'; +// This will not be necessary when only Angukar 9.1+ is supported +export function getLocaleDirection(locale: string): 'ltr' | 'rtl' { + return /^(ar(-[A-Z]{2})?|ckb(-IR)?|fa(-AF)?|he|ks|lrc(-IQ)?|mzn|pa-Arab|ps(-PK)?|sd|ug|ur(-IN)?|uz-Arab|yi)$/.test( + locale, + ) + ? 'rtl' + : 'ltr'; +} + export function createLocalizer(localization: ApplicationConfiguration.Localization) { return (resourceName: string, key: string, defaultValue: string) => { if (resourceName === '_') return key; diff --git a/npm/ng-packs/packages/theme-basic/src/lib/constants/styles.ts b/npm/ng-packs/packages/theme-basic/src/lib/constants/styles.ts index 0d9ebcc300..2de9931873 100644 --- a/npm/ng-packs/packages/theme-basic/src/lib/constants/styles.ts +++ b/npm/ng-packs/packages/theme-basic/src/lib/constants/styles.ts @@ -2,7 +2,6 @@ export default ` .content-header-title { font-size: 24px; } - .entry-row { margin-bottom: 15px; } @@ -26,17 +25,25 @@ export default ` border-radius: 0.25rem; min-height: 44px; } +[dir=rtl] .navbar .dropdown-menu a { + text-align: right!important; +} .navbar .dropdown-submenu a::after { transform: rotate(-90deg); position: absolute; right: 16px; top: 18px; } +[dir=rtl] .navbar .dropdown-submenu a::after { + transform: rotate(90deg); + left: 16px; + right: auto; + top: 20px; +} .navbar .dropdown-submenu .dropdown-menu { top: 0; left: 100%; } - .card-header .btn { padding: 2px 6px; } diff --git a/npm/ng-packs/packages/theme-shared/src/lib/components/table/table.component.html b/npm/ng-packs/packages/theme-shared/src/lib/components/table/table.component.html index eeaf0d40d9..6cbea56304 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/components/table/table.component.html +++ b/npm/ng-packs/packages/theme-shared/src/lib/components/table/table.component.html @@ -21,7 +21,7 @@
-
+
@@ -31,7 +31,7 @@
diff --git a/npm/ng-packs/packages/theme-shared/src/lib/components/table/table.component.ts b/npm/ng-packs/packages/theme-shared/src/lib/components/table/table.component.ts index da17aab136..a30b4464fc 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/components/table/table.component.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/components/table/table.component.ts @@ -1,9 +1,9 @@ import { - AfterViewInit, Component, ElementRef, EventEmitter, Input, + OnInit, Output, TemplateRef, TrackByFunction, @@ -17,7 +17,7 @@ import { styleUrls: ['table.component.scss'], encapsulation: ViewEncapsulation.None, }) -export class TableComponent { +export class TableComponent implements OnInit { private _totalRecords: number; bodyScrollLeft = 0; @@ -84,7 +84,23 @@ export class TableComponent { return this.value.slice(start, start + this.rows); } + marginCalculator: MarginCalculator; + trackByFn: TrackByFunction = (_, value) => { return typeof value === 'object' ? value[this.trackingProp] || value : value; }; + + ngOnInit() { + this.marginCalculator = document.body.dir === 'rtl' ? rtlCalculator : ltrCalculator; + } +} + +function ltrCalculator(div: HTMLDivElement): string { + return `0 auto 0 -${div.scrollLeft}px`; +} + +function rtlCalculator(div: HTMLDivElement): string { + return `0 ${-(div.scrollWidth - div.clientWidth - div.scrollLeft)}px 0 auto`; } + +type MarginCalculator = (div: HTMLDivElement) => string; diff --git a/npm/ng-packs/packages/theme-shared/src/lib/constants/styles.ts b/npm/ng-packs/packages/theme-shared/src/lib/constants/styles.ts index 4458117d16..a691f96ead 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/constants/styles.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/constants/styles.ts @@ -13,6 +13,10 @@ export default ` text-align: right; } +[dir=rtl] .data-tables-filter { + text-align: left; +} + .pointer { cursor: pointer; } @@ -155,3 +159,5 @@ export default ` /* (selector); + return { href, link }; + } + + private listenToLanguageChanges(injector: Injector) { + const l10n = injector.get(LocalizationService); + + // will always listen, no need to unsubscribe + l10n.languageChange + .pipe( + map(({ payload }) => payload), + startWith(l10n.currentLang), + ) + .subscribe(locale => { + this.dir = getLocaleDirection(locale); + }); + } + + private setBodyDir(dir: LocaleDirection) { + document.body.dir = dir; + } + + private setLazyLoad(injector: Injector) { + this.lazyLoad = injector.get(LazyLoadService); + const { href, link } = this.getLoadedBootstrap(); + this.lazyLoad.loaded.set(href, link); + } + + private setStyles(injector: Injector) { + this.styles = injector.get(LAZY_STYLES, [BOOTSTRAP]); + } + + private switchCSS(dir: LocaleDirection) { + this.styles.forEach(style => { + const oldHref = createLazyStyleHref(style, this.dir); + const newHref = createLazyStyleHref(style, dir); + + const strategy = LOADING_STRATEGY.PrependAnonymousStyleToHead(newHref); + this.lazyLoad.load(strategy).subscribe(() => this.lazyLoad.remove(oldHref)); + }); + } +} + +export function createLazyStyleHref(style: string, dir: string): string { + return style.replace(/{{\s*dir\s*}}/g, dir); +} + +export function initLazyStyleHandler(injector: Injector) { + return () => new LazyStyleHandler(injector); +} + +interface LoadedStyle { + href: string; + link: HTMLLinkElement; +} diff --git a/npm/ng-packs/packages/theme-shared/src/lib/models/common.ts b/npm/ng-packs/packages/theme-shared/src/lib/models/common.ts index d75c854476..09171179c4 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/models/common.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/models/common.ts @@ -14,3 +14,5 @@ export interface HttpErrorConfig { hideCloseIcon?: boolean; }; } + +export type LocaleDirection = 'ltr' | 'rtl'; diff --git a/npm/ng-packs/packages/theme-shared/src/lib/tests/lazy-style.handler.spec.ts b/npm/ng-packs/packages/theme-shared/src/lib/tests/lazy-style.handler.spec.ts new file mode 100644 index 0000000000..5836bee474 --- /dev/null +++ b/npm/ng-packs/packages/theme-shared/src/lib/tests/lazy-style.handler.spec.ts @@ -0,0 +1,66 @@ +import { LazyLoadService, LOADING_STRATEGY, LocalizationService } from '@abp/ng.core'; +import { createServiceFactory, SpectatorService } from '@ngneat/spectator/jest'; +import { EMPTY, of } from 'rxjs'; +import { BOOTSTRAP } from '../constants/styles'; +import { createLazyStyleHref, initLazyStyleHandler, LazyStyleHandler } from '../handlers'; + +const languageChange = of({ payload: 'en' }); + +describe('LazyStyleHandler', () => { + let spectator: SpectatorService; + let handler: LazyStyleHandler; + let lazyLoad: LazyLoadService; + + const createService = createServiceFactory({ + service: LazyStyleHandler, + providers: [ + { + provide: LocalizationService, + useValue: { currentLang: 'en', languageChange }, + }, + ], + }); + + beforeEach(() => { + spectator = createService(); + handler = spectator.service; + lazyLoad = handler['lazyLoad']; + }); + + describe('#dir', () => { + it('should initially be "ltr"', () => { + expect(handler.dir).toBe('ltr'); + }); + + it('should set bootstrap to rtl', () => { + const oldHref = createLazyStyleHref(BOOTSTRAP, 'ltr'); + const newHref = createLazyStyleHref(BOOTSTRAP, 'rtl'); + lazyLoad.loaded.set(newHref, null); // avoid actual loading + const load = jest.spyOn(lazyLoad, 'load'); + const remove = jest.spyOn(lazyLoad, 'remove'); + const strategy = LOADING_STRATEGY.PrependAnonymousStyleToHead(newHref); + + handler.dir = 'rtl'; + + expect(load).toHaveBeenCalledWith(strategy); + expect(remove).toHaveBeenCalledWith(oldHref); + }); + }); +}); + +describe('initLazyStyleHandler', () => { + it('should return a LazyStyleHandler factory', () => { + const generator = (function*() { + yield undefined; // LAZY_STYLES + yield { loaded: new Map() }; // LazyLoadService + yield { currentLang: 'en', languageChange: EMPTY }; // LocalizationService + })(); + + const injector = { + get: () => generator.next().value as any, + }; + const factory = initLazyStyleHandler(injector); + + expect(factory()).toBeInstanceOf(LazyStyleHandler); + }); +}); diff --git a/npm/ng-packs/packages/theme-shared/src/lib/theme-shared.module.ts b/npm/ng-packs/packages/theme-shared/src/lib/theme-shared.module.ts index 5d53ed2124..72ec96f569 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/theme-shared.module.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/theme-shared.module.ts @@ -22,6 +22,7 @@ import styles from './constants/styles'; import { LoadingDirective } from './directives/loading.directive'; import { TableSortDirective } from './directives/table-sort.directive'; import { ErrorHandler } from './handlers/error.handler'; +import { initLazyStyleHandler } from './handlers/lazy-style.handler'; import { RootParams } from './models/common'; import { THEME_SHARED_APPEND_CONTENT } from './tokens/append-content.token'; import { httpErrorConfigFactory, HTTP_ERROR_CONFIG } from './tokens/http-error.token'; @@ -104,6 +105,12 @@ export class ThemeSharedModule { deps: [THEME_SHARED_APPEND_CONTENT], useFactory: noop, }, + { + provide: APP_INITIALIZER, + multi: true, + deps: [Injector], + useFactory: initLazyStyleHandler, + }, { provide: HTTP_ERROR_CONFIG, useValue: options.httpErrorConfig }, { provide: 'HTTP_ERROR_CONFIG', diff --git a/npm/ng-packs/packages/theme-shared/src/lib/tokens/index.ts b/npm/ng-packs/packages/theme-shared/src/lib/tokens/index.ts index 1bdd74c53e..a923c8fc23 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/tokens/index.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/tokens/index.ts @@ -1,2 +1,3 @@ export * from './append-content.token'; export * from './http-error.token'; +export * from './lazy-styles.token'; diff --git a/npm/ng-packs/packages/theme-shared/src/lib/tokens/lazy-styles.token.ts b/npm/ng-packs/packages/theme-shared/src/lib/tokens/lazy-styles.token.ts new file mode 100644 index 0000000000..73ca3044fc --- /dev/null +++ b/npm/ng-packs/packages/theme-shared/src/lib/tokens/lazy-styles.token.ts @@ -0,0 +1,3 @@ +import { InjectionToken } from '@angular/core'; + +export const LAZY_STYLES = new InjectionToken('LAZY_STYLES'); diff --git a/npm/ng-packs/packages/theme-shared/src/public-api.ts b/npm/ng-packs/packages/theme-shared/src/public-api.ts index 9df78be2d9..df27b079f4 100644 --- a/npm/ng-packs/packages/theme-shared/src/public-api.ts +++ b/npm/ng-packs/packages/theme-shared/src/public-api.ts @@ -5,8 +5,9 @@ export * from './lib/theme-shared.module'; export * from './lib/animations'; export * from './lib/components'; +export { BOOTSTRAP } from './lib/constants/styles'; export * from './lib/directives'; -export * from './lib/handlers'; +export { ErrorHandler } from './lib/handlers'; export * from './lib/models'; export * from './lib/services'; export * from './lib/tokens'; diff --git a/npm/packs/core/src/abp.js b/npm/packs/core/src/abp.js index fa42828306..190143da03 100644 --- a/npm/packs/core/src/abp.js +++ b/npm/packs/core/src/abp.js @@ -663,4 +663,60 @@ var abp = abp || {}; return abp.utils.getCookieValue(abp.security.antiForgery.tokenCookieName); }; -})(); \ No newline at end of file + /* CLOCK *****************************************/ + abp.clock = abp.clock || {}; + + abp.clock.kind = 'Unspecified'; + + abp.clock.supportsMultipleTimezone = function () { + return abp.clock.kind === 'Utc'; + }; + + var toLocal = function (date) { + return new Date( + date.getUTCFullYear(), + date.getUTCMonth(), + date.getUTCDate(), + date.getUTCHours(), + date.getUTCMinutes(), + date.getUTCSeconds(), + date.getUTCMilliseconds() + ); + }; + + var toUtc = function (date) { + Date.UTC( + date.getUTCFullYear(), + date.getUTCMonth(), + date.getUTCDate(), + date.getUTCHours(), + date.getUTCMinutes(), + date.getUTCSeconds(), + date.getUTCMilliseconds() + ); + }; + + abp.clock.now = function () { + if (abp.clock.kind === 'Utc') { + return toUtc(new Date()); + } + return new Date(); + }; + + abp.clock.normalize = function (date) { + var kind = abp.clock.kind; + + if (kind === 'Unspecified') { + return date; + } + + if (kind === 'Local') { + return toLocal(date); + } + + if (kind === 'Utc') { + return toUtc(date); + } + }; + +})(); diff --git a/nupkg/common.ps1 b/nupkg/common.ps1 index 3b5ae4b1df..cb0faceb51 100644 --- a/nupkg/common.ps1 +++ b/nupkg/common.ps1 @@ -71,6 +71,7 @@ $projects = ( "framework/src/Volo.Abp.Emailing", "framework/src/Volo.Abp.EntityFrameworkCore", "framework/src/Volo.Abp.EntityFrameworkCore.MySQL", + "framework/src/Volo.Abp.EntityFrameworkCore.Oracle.Devart", "framework/src/Volo.Abp.EntityFrameworkCore.PostgreSql", "framework/src/Volo.Abp.EntityFrameworkCore.Sqlite", "framework/src/Volo.Abp.EntityFrameworkCore.SqlServer", diff --git a/templates/app/angular/angular.json b/templates/app/angular/angular.json index 567b8f95c7..dfcaa5b9d3 100644 --- a/templates/app/angular/angular.json +++ b/templates/app/angular/angular.json @@ -26,22 +26,27 @@ "extractCss": true, "assets": ["src/favicon.ico", "src/assets"], "styles": [ + { + "input": "node_modules/@abp/ng.theme.shared/styles/bootstrap-rtl.min.css", + "inject": false, + "bundleName": "bootstrap-rtl.min" + }, { "input": "node_modules/bootstrap/dist/css/bootstrap.min.css", "inject": true, - "bundleName": "bootstrap.min" + "bundleName": "bootstrap-ltr.min" }, - "src/styles.scss", { "input": "node_modules/@fortawesome/fontawesome-free/css/all.min.css", - "inject": false, + "inject": true, "bundleName": "fontawesome-all.min" }, { "input": "node_modules/@fortawesome/fontawesome-free/css/v4-shims.min.css", - "inject": false, + "inject": true, "bundleName": "fontawesome-v4-shims.min" - } + }, + "src/styles.scss" ], "scripts": [] }, diff --git a/templates/app/angular/src/app/app.component.ts b/templates/app/angular/src/app/app.component.ts index 963806c4f4..bf2a27962a 100644 --- a/templates/app/angular/src/app/app.component.ts +++ b/templates/app/angular/src/app/app.component.ts @@ -1,6 +1,4 @@ -import { LazyLoadService, LOADING_STRATEGY } from '@abp/ng.core'; -import { Component, OnInit } from '@angular/core'; -import { forkJoin } from 'rxjs'; +import { Component } from '@angular/core'; @Component({ selector: 'app-root', @@ -9,17 +7,4 @@ import { forkJoin } from 'rxjs'; `, }) -export class AppComponent implements OnInit { - constructor(private lazyLoadService: LazyLoadService) {} - - ngOnInit() { - forkJoin( - this.lazyLoadService.load( - LOADING_STRATEGY.PrependAnonymousStyleToHead('fontawesome-v4-shims.min.css') - ), - this.lazyLoadService.load( - LOADING_STRATEGY.PrependAnonymousStyleToHead('fontawesome-all.min.css') - ) - ).subscribe(); - } -} +export class AppComponent {}