mirror of https://github.com/abpframework/abp.git
committed by
GitHub
234 changed files with 3525 additions and 972 deletions
@ -0,0 +1,28 @@ |
|||
using Serilog.Core; |
|||
using Serilog.Events; |
|||
using Volo.Abp.DependencyInjection; |
|||
using Volo.Abp.Tracing; |
|||
|
|||
namespace Volo.AbpWebSite |
|||
{ |
|||
//This is for trial for now
|
|||
public class CorrelationIdLogEventEnricher : ILogEventEnricher, ITransientDependency |
|||
{ |
|||
private readonly ICorrelationIdProvider _correlationIdProvider; |
|||
|
|||
public CorrelationIdLogEventEnricher(ICorrelationIdProvider correlationIdProvider) |
|||
{ |
|||
_correlationIdProvider = correlationIdProvider; |
|||
} |
|||
|
|||
public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory) |
|||
{ |
|||
logEvent.AddOrUpdateProperty( |
|||
new LogEventProperty( |
|||
"CorrelationId", |
|||
new ScalarValue("CorrId:" + _correlationIdProvider.Get()) |
|||
) |
|||
); |
|||
} |
|||
} |
|||
} |
|||
@ -1,50 +0,0 @@ |
|||
# COMMON PATHS |
|||
|
|||
$buildFolder = (Get-Item -Path "./" -Verbose).FullName |
|||
$slnFolder = Join-Path $buildFolder "../" |
|||
$outputFolder = Join-Path $buildFolder "outputs" |
|||
$abpDeskFolder = Join-Path $slnFolder "src/AbpDesk" |
|||
$abpDeskWebFolder = Join-Path $abpDeskFolder "AbpDesk.Web.Mvc" |
|||
|
|||
## CLEAR ###################################################################### |
|||
|
|||
Remove-Item $outputFolder -Force -Recurse |
|||
New-Item -Path $outputFolder -ItemType Directory |
|||
|
|||
## RESTORE NUGET PACKAGES ##################################################### |
|||
|
|||
Set-Location $slnFolder |
|||
dotnet restore |
|||
|
|||
## PUBLISH ASPDESK WEB ######################################################## |
|||
|
|||
Set-Location $abpDeskWebFolder |
|||
dotnet publish --output (Join-Path $outputFolder "AbpDesk/Web") |
|||
|
|||
New-Item -Path (Join-Path $outputFolder "AbpDesk/Web/PlugIns") -ItemType Directory |
|||
Copy-Item (Join-Path $abpDeskFolder "Web_PlugIns/*") (Join-Path $outputFolder "AbpDesk/Web/PlugIns/") |
|||
|
|||
## PUBLISH IDENTITY HTTP API HOST ############################################# |
|||
|
|||
Set-Location (Join-Path $slnFolder "src/Volo.Abp.Identity.HttpApi.Host") |
|||
dotnet publish --output (Join-Path $outputFolder "AbpIdentity/HttpApiHost") |
|||
|
|||
## CREATE DOCKER IMAGES ####################################################### |
|||
|
|||
Set-Location (Join-Path $outputFolder "AbpDesk/Web") |
|||
|
|||
docker rmi abpdesk/web -f |
|||
docker build -t abpdesk/web . |
|||
|
|||
Set-Location (Join-Path $outputFolder "AbpIdentity/HttpApiHost") |
|||
|
|||
docker rmi abpidentity/httpapihost -f |
|||
docker build -t abpidentity/httpapihost . |
|||
|
|||
## DOCKER COMPOSE FILES ####################################################### |
|||
|
|||
Copy-Item (Join-Path $slnFolder "docker/*.*") $outputFolder |
|||
|
|||
## FINALIZE ################################################################### |
|||
|
|||
Set-Location $outputFolder |
|||
@ -1,28 +0,0 @@ |
|||
version: '2' |
|||
|
|||
services: |
|||
|
|||
mongodb: |
|||
image: tutum/mongodb |
|||
environment: |
|||
- AUTH=no |
|||
ports: |
|||
- "27017:27017" |
|||
- "28017:28017" |
|||
|
|||
abpidentity_httpapihost: |
|||
image: abpidentity/httpapihost |
|||
environment: |
|||
- ASPNETCORE_ENVIRONMENT=Staging |
|||
|
|||
abpdesk_web: |
|||
image: abpdesk/web |
|||
environment: |
|||
- ASPNETCORE_ENVIRONMENT=Staging |
|||
|
|||
load_balancer: |
|||
image: haproxy:1.7.1 |
|||
volumes: |
|||
- "./haproxy.cfg:/usr/local/etc/haproxy/haproxy.cfg" |
|||
ports: |
|||
- "9005:8080" |
|||
@ -1 +0,0 @@ |
|||
docker-compose down -v --rmi local |
|||
@ -1,18 +0,0 @@ |
|||
global |
|||
maxconn 4096 |
|||
|
|||
defaults |
|||
mode http |
|||
timeout connect 5s |
|||
timeout client 50s |
|||
timeout server 50s |
|||
|
|||
listen http-in |
|||
bind *:8080 |
|||
|
|||
server web-1 outputs_abpdesk_web_1:80 |
|||
server web-2 outputs_abpdesk_web_2:80 |
|||
|
|||
stats enable |
|||
stats uri /haproxy |
|||
stats refresh 1s |
|||
@ -1,8 +0,0 @@ |
|||
docker rm $(docker ps -aq) |
|||
docker-compose up -d mongodb |
|||
docker-compose up -d abpidentity_httpapihost |
|||
docker-compose up -d abpdesk_web |
|||
sleep 2 |
|||
docker-compose scale abpdesk_web=2 |
|||
sleep 2 |
|||
docker-compose up -d load_balancer |
|||
@ -0,0 +1,3 @@ |
|||
# Audit Logging |
|||
|
|||
TODO |
|||
@ -0,0 +1,54 @@ |
|||
# Microservice Demo, Projects Status and Road Map |
|||
|
|||
After [the first announcement](https://abp.io/blog/abp/Abp-vNext-Announcement) on the ABP vNext, we have a lot of improvements on the codebase (1100+ commits on the [GitHub repository](https://github.com/abpframework/abp)). We've created features, samples, documentation and much more. In this post, I want to inform you about some news and the status of the project. |
|||
|
|||
## Microservice Demo Solution |
|||
|
|||
One of the major goals of the ABP framework is to provide a [convenient infrastructure to create microservice solutions](https://abp.io/documents/abp/latest/Microservice-Architecture). |
|||
|
|||
We've been working to develop a microservice solution demo. Initial version was completed and [documented](https://abp.io/documents/abp/latest/Samples/Microservice-Demo). This sample solution aims to demonstrate a simple yet complete microservice solution; |
|||
|
|||
- Has multiple, independent, self-deployable **microservices**. |
|||
- Multiple **web applications**, each uses a different API gateway. |
|||
- Has multiple **gateways** / BFFs (Backend for Frontends) developed using the [Ocelot](https://github.com/ThreeMammals/Ocelot) library. |
|||
- Has an **authentication service** developed using the [IdentityServer](https://identityserver.io/) framework. It's also a SSO (Single Sign On) application with necessary UIs. |
|||
- Has **multiple databases**. Some microservices has their own database while some services/applications shares a database (to demonstrate different use cases). |
|||
- Has different types of databases: **SQL Server** (with **Entity Framework Core** ORM) and **MongoDB**. |
|||
- Has a **console application** to show the simplest way of using a service by authenticating. |
|||
- Uses [Redis](https://redis.io/) for **distributed caching**. |
|||
- Uses [RabbitMQ](https://www.rabbitmq.com/) for service-to-service **messaging**. |
|||
- Uses [Docker](https://www.docker.com/) & [Kubernates](https://kubernetes.io/) to **deploy** & run all services and applications. |
|||
- Uses [Elasticsearch](https://www.elastic.co/products/elasticsearch) & [Kibana](https://www.elastic.co/products/kibana) to store and visualize the logs (written using [Serilog](https://serilog.net/)). |
|||
|
|||
See [its documentation](https://abp.io/documents/abp/latest/Samples/Microservice-Demo) for a detailed explanation of the solution. |
|||
|
|||
## Improvements/Features |
|||
|
|||
We've worked on so many features including **distributed event bus** (with RabbitMQ integration), **IdentityServer4 integration** and enhancements for almost all features. We are continuously refactoring and adding tests to make the framework more stable and production ready. It is [rapidly growing](https://github.com/abpframework/abp/graphs/contributors). |
|||
|
|||
## Road Map |
|||
|
|||
There are still too much work to be done before the first stable release (v1.0). You can see [prioritized backlog items](https://github.com/abpframework/abp/issues?q=is%3Aopen+is%3Aissue+milestone%3ABacklog) on the GitHub repo. |
|||
|
|||
According to our estimation, we have planned to release v1.0 in Q2 of 2019 (probably in May or June). So, not too much time to wait. We are also very excited for the first stable release. |
|||
|
|||
We will also work on [the documentation](https://abp.io/documents/abp/latest) since it is far from complete now. |
|||
|
|||
First release may not include a SPA template. However, we want to prepare a simple one if it can be possible. Haven't decided yet about the SPA framework. Alternatives: **Angular, React and Blazor**. Please write your thought as a comment to this post. |
|||
|
|||
## Chinese Web Site |
|||
|
|||
There is a big ABP community in China. They have created a Chinese version of the abp.io web site: https://cn.abp.io/ They are keeping it up to date. Thanks to the Chinese developers and especially to [Liming Ma](https://github.com/maliming). |
|||
|
|||
## NDC {London} 2019 |
|||
|
|||
It was a pleasure to be in [NDC {London}](https://ndc-london.com/) 2019 as a partner. We've talked to many developers about the current ASP.NET Boilerplate and the ABP vNext and we got good feedbacks. |
|||
|
|||
We also had a chance to talk with [Scott Hanselman](https://twitter.com/shanselman) and [Jon Galloway](https://twitter.com/jongalloway). They visited our booth and we talked about the ideas for ABP vNext. They liked features, approaches and the goal of new ABP framework. See some photos and comments on twitter: |
|||
|
|||
 |
|||
|
|||
## Follow It |
|||
|
|||
* You can star and follow the **GitHub** repository: https://github.com/abpframework/abp |
|||
* You can follow the official **Twitter** account for news: https://twitter.com/abpframework |
|||
|
After Width: | Height: | Size: 477 KiB |
File diff suppressed because it is too large
|
After Width: | Height: | Size: 3.2 KiB |
|
After Width: | Height: | Size: 154 KiB |
|
After Width: | Height: | Size: 77 KiB |
|
After Width: | Height: | Size: 53 KiB |
|
After Width: | Height: | Size: 12 KiB |
@ -0,0 +1,25 @@ |
|||
using Microsoft.AspNetCore.Razor.TagHelpers; |
|||
using System.Collections.Generic; |
|||
|
|||
namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Extensions |
|||
{ |
|||
public static class TagHelperAttributeExtensions |
|||
{ |
|||
public static string ToHtmlAttributeAsString(this TagHelperAttribute attribute) |
|||
{ |
|||
return attribute.Name + "=\"" + attribute.Value + "\""; |
|||
} |
|||
|
|||
public static string ToHtmlAttributesAsString(this List<TagHelperAttribute> attributes) |
|||
{ |
|||
var attributesAsString = ""; |
|||
|
|||
foreach (var attribute in attributes) |
|||
{ |
|||
attributesAsString += attribute.ToHtmlAttributeAsString() + " "; |
|||
} |
|||
|
|||
return attributesAsString; |
|||
} |
|||
} |
|||
} |
|||
@ -1,14 +1,17 @@ |
|||
@using Volo.Abp.AspNetCore.Mvc.UI.MultiTenancy.Components.TenantSwitch |
|||
@model TenantSwitchViewComponent.TenantSwitchViewModel |
|||
<li class="nav-item"> |
|||
<a abp-button="Link" id="TenantSwitchToolbarLink" href="#"> |
|||
@if (Model.Tenant == null) |
|||
{ |
|||
<text>@@host</text> |
|||
} |
|||
else |
|||
{ |
|||
<text>@@@Model.Tenant.Name</text> |
|||
} |
|||
</a> |
|||
</li> |
|||
@if (!Model.CurrentUser.IsAuthenticated) |
|||
{ |
|||
<li class="nav-item"> |
|||
<a abp-button="Link" id="TenantSwitchToolbarLink" href="#"> |
|||
@if (Model.Tenant == null) |
|||
{ |
|||
<text>@@host</text> |
|||
} |
|||
else |
|||
{ |
|||
<text>@@@Model.Tenant.Name</text> |
|||
} |
|||
</a> |
|||
</li> |
|||
} |
|||
@ -0,0 +1,13 @@ |
|||
using System.Collections.Generic; |
|||
using Volo.Abp.AspNetCore.Mvc.UI.Bundling; |
|||
|
|||
namespace Volo.Abp.AspNetCore.Mvc.UI.Packages.FlagIconCss |
|||
{ |
|||
public class FlagIconCssStyleContributor : BundleContributor |
|||
{ |
|||
public override void ConfigureBundle(BundleConfigurationContext context) |
|||
{ |
|||
context.Files.AddIfNotContains("/libs/flag-icon-css/css/flag-icon.min.css"); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,12 @@ |
|||
using Volo.Abp.AspNetCore.Mvc.UI.Bundling; |
|||
|
|||
namespace Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Bundling |
|||
{ |
|||
public class BasicThemeGlobalScriptContributor : BundleContributor |
|||
{ |
|||
public override void ConfigureBundle(BundleConfigurationContext context) |
|||
{ |
|||
context.Files.Add("/themes/basic/layout.js"); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,36 @@ |
|||
@using Volo.Abp.UI.Navigation |
|||
@model ApplicationMenuItem |
|||
@{ |
|||
var elementId = string.IsNullOrEmpty(Model.ElementId) ? string.Empty : $"id=\"{Model.ElementId}\""; |
|||
var cssClass = string.IsNullOrEmpty(Model.CssClass) ? string.Empty : Model.CssClass; |
|||
var disabled = Model.IsDisabled ? "disabled" : string.Empty; |
|||
} |
|||
@if (Model.IsLeaf) |
|||
{ |
|||
@if (Model.Url != null) |
|||
{ |
|||
<a class="dropdown-item @cssClass @disabled" href="@(Model.Url ?? "#")" @Html.Raw(elementId)> |
|||
@Model.DisplayName |
|||
</a> |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
<div class="dropdown-submenu"> |
|||
<a role="button" class="btn dropdown-toggle" data-toggle="dropdown" |
|||
aria-haspopup="true" aria-expanded="false"> |
|||
<span class="lp-icon"> |
|||
<i class="@(Model.Icon ?? "")"></i> |
|||
</span> |
|||
<span class="lp-text"> |
|||
@Model.DisplayName |
|||
</span> |
|||
</a> |
|||
<div class="dropdown-menu"> |
|||
@foreach (var childMenuItem in Model.Items) |
|||
{ |
|||
@await Html.PartialAsync("~/Themes/Basic/Components/Menu/_MenuItem.cshtml", childMenuItem) |
|||
} |
|||
</div> |
|||
</div> |
|||
} |
|||
@ -0,0 +1,16 @@ |
|||
$(function () { |
|||
$('.dropdown-menu a.dropdown-toggle').on('click', function (e) { |
|||
if (!$(this).next().hasClass('show')) { |
|||
$(this).parents('.dropdown-menu').first().find('.show').removeClass("show"); |
|||
} |
|||
|
|||
var $subMenu = $(this).next(".dropdown-menu"); |
|||
$subMenu.toggleClass('show'); |
|||
|
|||
$(this).parents('li.nav-item.dropdown.show').on('hidden.bs.dropdown', function (e) { |
|||
$('.dropdown-submenu .show').removeClass("show"); |
|||
}); |
|||
|
|||
return false; |
|||
}); |
|||
}); |
|||
@ -1,20 +0,0 @@ |
|||
using System.Security.Claims; |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace Volo.Abp.Authorization.Permissions |
|||
{ |
|||
public static class PermissionCheckerExtensions |
|||
{ |
|||
public static async Task<bool> IsGrantedAsync(this IPermissionChecker permissionChecker, string name) |
|||
{ |
|||
return (await permissionChecker.CheckAsync(name)).IsGranted; |
|||
} |
|||
|
|||
public static async Task<bool> IsGrantedAsync(this IPermissionChecker permissionChecker, ClaimsPrincipal principal, string name) |
|||
{ |
|||
return (await permissionChecker.CheckAsync(principal, name)).IsGranted; |
|||
} |
|||
|
|||
//TODO: Add sync extensions
|
|||
} |
|||
} |
|||
@ -0,0 +1,9 @@ |
|||
namespace Volo.Abp.Authorization.Permissions |
|||
{ |
|||
public enum PermissionGrantResult |
|||
{ |
|||
Undefined, |
|||
Granted, |
|||
Prohibited |
|||
} |
|||
} |
|||
@ -0,0 +1,16 @@ |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace Volo.Abp.Threading |
|||
{ |
|||
public static class TaskCache |
|||
{ |
|||
public static Task<bool> TrueResult { get; } |
|||
public static Task<bool> FalseResult { get; } |
|||
|
|||
static TaskCache() |
|||
{ |
|||
TrueResult = Task.FromResult(true); |
|||
FalseResult = Task.FromResult(false); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,50 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Globalization; |
|||
using Volo.Abp.Reflection; |
|||
|
|||
namespace Volo.Abp.Data |
|||
{ |
|||
public static class HasExtraPropertiesExtensions |
|||
{ |
|||
public static bool HasProperty(this IHasExtraProperties source, string name) |
|||
{ |
|||
return source.ExtraProperties.ContainsKey(name); |
|||
} |
|||
|
|||
public static object GetProperty(this IHasExtraProperties source, string name) |
|||
{ |
|||
return source.ExtraProperties?.GetOrDefault(name); |
|||
} |
|||
|
|||
public static TProperty GetProperty<TProperty>(this IHasExtraProperties source, string name) |
|||
{ |
|||
var value = source.GetProperty(name); |
|||
if (value == default) |
|||
{ |
|||
return default; |
|||
} |
|||
|
|||
if (TypeHelper.IsPrimitiveExtended(typeof(TProperty), includeEnums: true)) |
|||
{ |
|||
return (TProperty)Convert.ChangeType(value, typeof(TProperty), CultureInfo.InvariantCulture); |
|||
} |
|||
|
|||
throw new AbpException("GetProperty<TProperty> does not support non-primitive types. Use non-generic GetProperty method and handle type casting manually."); |
|||
} |
|||
|
|||
public static TSource SetProperty<TSource>(this TSource source, string name, object value) |
|||
where TSource : IHasExtraProperties |
|||
{ |
|||
source.ExtraProperties[name] = value; |
|||
return source; |
|||
} |
|||
|
|||
public static TSource RemoveProperty<TSource>(this TSource source, string name) |
|||
where TSource : IHasExtraProperties |
|||
{ |
|||
source.ExtraProperties.Remove(name); |
|||
return source; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,21 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk"> |
|||
|
|||
<Import Project="..\..\..\common.props" /> |
|||
|
|||
<PropertyGroup> |
|||
<TargetFramework>netstandard2.0</TargetFramework> |
|||
<AssemblyName>Volo.Abp.Features</AssemblyName> |
|||
<PackageId>Volo.Abp.Features</PackageId> |
|||
<AssetTargetFallback>$(AssetTargetFallback);portable-net45+win8+wp8+wpa81;</AssetTargetFallback> |
|||
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute> |
|||
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute> |
|||
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute> |
|||
<RootNamespace /> |
|||
</PropertyGroup> |
|||
|
|||
<ItemGroup> |
|||
<ProjectReference Include="..\Volo.Abp.Localization.Abstractions\Volo.Abp.Localization.Abstractions.csproj" /> |
|||
<ProjectReference Include="..\Volo.Abp.MultiTenancy.Abstractions\Volo.Abp.MultiTenancy.Abstractions.csproj" /> |
|||
</ItemGroup> |
|||
|
|||
</Project> |
|||
@ -0,0 +1,18 @@ |
|||
using Volo.Abp.Localization; |
|||
using Volo.Abp.Modularity; |
|||
using Volo.Abp.MultiTenancy; |
|||
|
|||
namespace Volo.Abp.Features |
|||
{ |
|||
[DependsOn( |
|||
typeof(AbpLocalizationAbstractionsModule), |
|||
typeof(AbpMultiTenancyAbstractionsModule) |
|||
)] |
|||
public class AbpFeaturesModule : AbpModule |
|||
{ |
|||
public override void ConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
|
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,86 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Threading.Tasks; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using Microsoft.Extensions.Options; |
|||
using Volo.Abp.DependencyInjection; |
|||
|
|||
namespace Volo.Abp.Features |
|||
{ |
|||
public class FeatureChecker : IFeatureChecker, ITransientDependency |
|||
{ |
|||
protected IFeatureDefinitionManager FeatureDefinitionManager { get; } |
|||
protected Lazy<List<IFeatureValueProvider>> Providers { get; } |
|||
protected FeatureOptions Options { get; } |
|||
|
|||
public FeatureChecker( |
|||
IOptions<FeatureOptions> options, |
|||
IServiceProvider serviceProvider, |
|||
IFeatureDefinitionManager featureDefinitionManager) |
|||
{ |
|||
FeatureDefinitionManager = featureDefinitionManager; |
|||
|
|||
Options = options.Value; |
|||
|
|||
Providers = new Lazy<List<IFeatureValueProvider>>( |
|||
() => Options |
|||
.ValueProviders |
|||
.Select(type => serviceProvider.GetRequiredService(type) as IFeatureValueProvider) |
|||
.ToList(), |
|||
true |
|||
); |
|||
} |
|||
|
|||
public virtual async Task<string> GetOrNullAsync(string name) |
|||
{ |
|||
var featureDefinition = FeatureDefinitionManager.Get(name); |
|||
var providers = Enumerable |
|||
.Reverse(Providers.Value); |
|||
|
|||
if (featureDefinition.AllowedProviders.Any()) |
|||
{ |
|||
providers = providers.Where(p => featureDefinition.AllowedProviders.Contains(p.Name)); |
|||
} |
|||
|
|||
return await GetOrNullValueFromProvidersAsync(providers, featureDefinition); |
|||
} |
|||
|
|||
public async Task<bool> IsEnabledAsync(string name) |
|||
{ |
|||
var value = await GetOrNullAsync(name); |
|||
if (value == null) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
try |
|||
{ |
|||
return bool.Parse(value); |
|||
} |
|||
catch (Exception ex) |
|||
{ |
|||
throw new AbpException( |
|||
$"The value '{value}' for the feature '{name}' should be a boolean, but was not!", |
|||
ex |
|||
); |
|||
} |
|||
} |
|||
|
|||
protected virtual async Task<string> GetOrNullValueFromProvidersAsync( |
|||
IEnumerable<IFeatureValueProvider> providers, |
|||
FeatureDefinition feature) |
|||
{ |
|||
foreach (var provider in providers) |
|||
{ |
|||
var value = await provider.GetOrNullAsync(feature); |
|||
if (value != null) |
|||
{ |
|||
return value; |
|||
} |
|||
} |
|||
|
|||
return null; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,37 @@ |
|||
using System; |
|||
using System.Threading.Tasks; |
|||
using JetBrains.Annotations; |
|||
using Volo.Abp.Threading; |
|||
|
|||
namespace Volo.Abp.Features |
|||
{ |
|||
public static class FeatureCheckerExtensions |
|||
{ |
|||
public static async Task<T> GetAsync<T>([NotNull] this IFeatureChecker featureChecker, [NotNull] string name, T defaultValue = default) |
|||
where T : struct |
|||
{ |
|||
Check.NotNull(featureChecker, nameof(featureChecker)); |
|||
Check.NotNull(name, nameof(name)); |
|||
|
|||
var value = await featureChecker.GetOrNullAsync(name); |
|||
return value?.To<T>() ?? defaultValue; |
|||
} |
|||
|
|||
public static string GetOrNull([NotNull] this IFeatureChecker featureChecker, [NotNull] string name) |
|||
{ |
|||
Check.NotNull(featureChecker, nameof(featureChecker)); |
|||
return AsyncHelper.RunSync(() => featureChecker.GetOrNullAsync(name)); |
|||
} |
|||
|
|||
public static T Get<T>([NotNull] this IFeatureChecker featureChecker, [NotNull] string name, T defaultValue = default) |
|||
where T : struct |
|||
{ |
|||
return AsyncHelper.RunSync(() => featureChecker.GetAsync(name, defaultValue)); |
|||
} |
|||
|
|||
public static bool IsEnabled([NotNull] this IFeatureChecker featureChecker, [NotNull] string name) |
|||
{ |
|||
return AsyncHelper.RunSync(() => featureChecker.IsEnabledAsync(name)); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,150 @@ |
|||
using System.Collections.Generic; |
|||
using System.Collections.Immutable; |
|||
using System.Linq; |
|||
using JetBrains.Annotations; |
|||
using Volo.Abp.Localization; |
|||
|
|||
namespace Volo.Abp.Features |
|||
{ |
|||
public class FeatureDefinition |
|||
{ |
|||
/// <summary>
|
|||
/// Unique name of the feature.
|
|||
/// </summary>
|
|||
[NotNull] |
|||
public string Name { get; } |
|||
|
|||
[NotNull] |
|||
public ILocalizableString DisplayName |
|||
{ |
|||
get => _displayName; |
|||
set => _displayName = Check.NotNull(value, nameof(value)); |
|||
} |
|||
private ILocalizableString _displayName; |
|||
|
|||
[CanBeNull] |
|||
public ILocalizableString Description { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Parent of this feature, if one exists.
|
|||
/// If set, this feature can be enabled only if the parent is enabled.
|
|||
/// </summary>
|
|||
public FeatureDefinition Parent { get; private set; } |
|||
|
|||
/// <summary>
|
|||
/// List of child features.
|
|||
/// </summary>
|
|||
public IReadOnlyList<FeatureDefinition> Children => _children.ToImmutableList(); |
|||
private readonly List<FeatureDefinition> _children; |
|||
|
|||
/// <summary>
|
|||
/// Default value of the feature.
|
|||
/// </summary>
|
|||
[CanBeNull] |
|||
public string DefaultValue { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Can clients see this feature and it's value.
|
|||
/// Default: true.
|
|||
/// </summary>
|
|||
public bool IsVisibleToClients { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// A list of allowed providers to get/set value of this feature.
|
|||
/// An empty list indicates that all providers are allowed.
|
|||
/// </summary>
|
|||
public List<string> AllowedProviders { get; } |
|||
|
|||
/// <summary>
|
|||
/// Can be used to get/set custom properties for this feature.
|
|||
/// </summary>
|
|||
[NotNull] |
|||
public Dictionary<string, object> Properties { get; } |
|||
|
|||
//TODO: Implement input type like old ABP!
|
|||
|
|||
public FeatureDefinition( |
|||
string name, |
|||
string defaultValue = null, |
|||
ILocalizableString displayName = null, |
|||
ILocalizableString description = null, |
|||
bool isVisibleToClients = true) |
|||
{ |
|||
Name = name; |
|||
DefaultValue = defaultValue; |
|||
IsVisibleToClients = isVisibleToClients; |
|||
DisplayName = displayName ?? new FixedLocalizableString(name); |
|||
Description = description; |
|||
|
|||
Properties = new Dictionary<string, object>(); |
|||
AllowedProviders = new List<string>(); |
|||
_children = new List<FeatureDefinition>(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Sets a property in the <see cref="Properties"/> dictionary.
|
|||
/// This is a shortcut for nested calls on this object.
|
|||
/// </summary>
|
|||
public virtual FeatureDefinition WithProperty(string key, object value) |
|||
{ |
|||
Properties[key] = value; |
|||
return this; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Sets a property in the <see cref="Properties"/> dictionary.
|
|||
/// This is a shortcut for nested calls on this object.
|
|||
/// </summary>
|
|||
public virtual FeatureDefinition WithProviders(params string[] providers) |
|||
{ |
|||
if (!providers.IsNullOrEmpty()) |
|||
{ |
|||
AllowedProviders.AddRange(providers); |
|||
} |
|||
|
|||
return this; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Adds a child feature.
|
|||
/// </summary>
|
|||
/// <returns>Returns a newly created child feature</returns>
|
|||
public FeatureDefinition CreateChild( |
|||
string name, |
|||
string defaultValue = null, |
|||
ILocalizableString displayName = null, |
|||
ILocalizableString description = null, |
|||
bool isVisibleToClients = true) |
|||
{ |
|||
var feature = new FeatureDefinition( |
|||
name, |
|||
defaultValue, |
|||
displayName, |
|||
description, |
|||
isVisibleToClients) |
|||
{ |
|||
Parent = this |
|||
}; |
|||
|
|||
_children.Add(feature); |
|||
return feature; |
|||
} |
|||
|
|||
public void RemoveChild(string name) |
|||
{ |
|||
var featureToRemove = _children.FirstOrDefault(f => f.Name == name); |
|||
if (featureToRemove == null) |
|||
{ |
|||
throw new AbpException($"Could not find a feature named '{name}' in the Children of this feature '{Name}'."); |
|||
} |
|||
|
|||
featureToRemove.Parent = null; |
|||
_children.Remove(featureToRemove); |
|||
} |
|||
|
|||
public override string ToString() |
|||
{ |
|||
return $"[{nameof(FeatureDefinition)}: {Name}]"; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,38 @@ |
|||
using System.Collections.Generic; |
|||
using System.Collections.Immutable; |
|||
|
|||
namespace Volo.Abp.Features |
|||
{ |
|||
public class FeatureDefinitionContext : IFeatureDefinitionContext |
|||
{ |
|||
protected Dictionary<string, FeatureDefinition> Features { get; } |
|||
|
|||
public FeatureDefinitionContext(Dictionary<string, FeatureDefinition> features) |
|||
{ |
|||
Features = features; |
|||
} |
|||
|
|||
public virtual FeatureDefinition GetOrNull(string name) |
|||
{ |
|||
return Features.GetOrDefault(name); |
|||
} |
|||
|
|||
public virtual IReadOnlyList<FeatureDefinition> GetAll() |
|||
{ |
|||
return Features.Values.ToImmutableList(); |
|||
} |
|||
|
|||
public virtual void Add(params FeatureDefinition[] definitions) |
|||
{ |
|||
if (definitions.IsNullOrEmpty()) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
foreach (var definition in definitions) |
|||
{ |
|||
Features[definition.Name] = definition; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,76 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Collections.Immutable; |
|||
using System.Linq; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using Microsoft.Extensions.Options; |
|||
using Volo.Abp.DependencyInjection; |
|||
|
|||
namespace Volo.Abp.Features |
|||
{ |
|||
public class FeatureDefinitionManager : IFeatureDefinitionManager, ISingletonDependency |
|||
{ |
|||
protected Lazy<List<IFeatureDefinitionProvider>> Providers { get; } |
|||
|
|||
protected Lazy<IDictionary<string, FeatureDefinition>> FeatureDefinitions { get; } |
|||
|
|||
protected FeatureOptions Options { get; } |
|||
|
|||
private readonly IServiceProvider _serviceProvider; |
|||
|
|||
public FeatureDefinitionManager( |
|||
IOptions<FeatureOptions> options, |
|||
IServiceProvider serviceProvider) |
|||
{ |
|||
_serviceProvider = serviceProvider; |
|||
Options = options.Value; |
|||
|
|||
Providers = new Lazy<List<IFeatureDefinitionProvider>>(CreateFeatureProviders, true); |
|||
FeatureDefinitions = new Lazy<IDictionary<string, FeatureDefinition>>(CreateFeatureDefinitions, true); |
|||
} |
|||
|
|||
public virtual FeatureDefinition Get(string name) |
|||
{ |
|||
Check.NotNull(name, nameof(name)); |
|||
|
|||
var feature = GetOrNull(name); |
|||
|
|||
if (feature == null) |
|||
{ |
|||
throw new AbpException("Undefined feature: " + name); |
|||
} |
|||
|
|||
return feature; |
|||
} |
|||
|
|||
public virtual IReadOnlyList<FeatureDefinition> GetAll() |
|||
{ |
|||
return FeatureDefinitions.Value.Values.ToImmutableList(); |
|||
} |
|||
|
|||
public virtual FeatureDefinition GetOrNull(string name) |
|||
{ |
|||
return FeatureDefinitions.Value.GetOrDefault(name); |
|||
} |
|||
|
|||
protected virtual List<IFeatureDefinitionProvider> CreateFeatureProviders() |
|||
{ |
|||
return Options |
|||
.DefinitionProviders |
|||
.Select(p => _serviceProvider.GetRequiredService(p) as IFeatureDefinitionProvider) |
|||
.ToList(); |
|||
} |
|||
|
|||
protected virtual IDictionary<string, FeatureDefinition> CreateFeatureDefinitions() |
|||
{ |
|||
var features = new Dictionary<string, FeatureDefinition>(); |
|||
|
|||
foreach (var provider in Providers.Value) |
|||
{ |
|||
provider.Define(new FeatureDefinitionContext(features)); |
|||
} |
|||
|
|||
return features; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,9 @@ |
|||
using Volo.Abp.DependencyInjection; |
|||
|
|||
namespace Volo.Abp.Features |
|||
{ |
|||
public abstract class FeatureDefinitionProvider : IFeatureDefinitionProvider, ISingletonDependency |
|||
{ |
|||
public abstract void Define(IFeatureDefinitionContext context); |
|||
} |
|||
} |
|||
@ -0,0 +1,17 @@ |
|||
using Volo.Abp.Collections; |
|||
|
|||
namespace Volo.Abp.Features |
|||
{ |
|||
public class FeatureOptions |
|||
{ |
|||
public ITypeList<IFeatureDefinitionProvider> DefinitionProviders { get; } |
|||
|
|||
public ITypeList<IFeatureValueProvider> ValueProviders { get; } |
|||
|
|||
public FeatureOptions() |
|||
{ |
|||
DefinitionProviders = new TypeList<IFeatureDefinitionProvider>(); |
|||
ValueProviders = new TypeList<IFeatureValueProvider>(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,19 @@ |
|||
using System; |
|||
|
|||
namespace Volo.Abp.Features |
|||
{ |
|||
[Serializable] |
|||
public class FeatureValue : NameValue |
|||
{ |
|||
public FeatureValue() |
|||
{ |
|||
|
|||
} |
|||
|
|||
public FeatureValue(string name, string value) |
|||
{ |
|||
Name = name; |
|||
Value = value; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,19 @@ |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.DependencyInjection; |
|||
|
|||
namespace Volo.Abp.Features |
|||
{ |
|||
public abstract class FeatureValueProvider : IFeatureValueProvider, ISingletonDependency |
|||
{ |
|||
public abstract string Name { get; } |
|||
|
|||
protected IFeatureStore FeatureStore { get; } |
|||
|
|||
protected FeatureValueProvider(IFeatureStore featureStore) |
|||
{ |
|||
FeatureStore = featureStore; |
|||
} |
|||
|
|||
public abstract Task<string> GetOrNullAsync(FeatureDefinition feature); |
|||
} |
|||
} |
|||
@ -0,0 +1,12 @@ |
|||
using JetBrains.Annotations; |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace Volo.Abp.Features |
|||
{ |
|||
public interface IFeatureChecker |
|||
{ |
|||
Task<string> GetOrNullAsync([NotNull] string name); |
|||
|
|||
Task<bool> IsEnabledAsync(string name); |
|||
} |
|||
} |
|||
@ -0,0 +1,9 @@ |
|||
namespace Volo.Abp.Features |
|||
{ |
|||
public interface IFeatureDefinitionContext |
|||
{ |
|||
FeatureDefinition GetOrNull(string name); |
|||
|
|||
void Add(params FeatureDefinition[] definitions); |
|||
} |
|||
} |
|||
@ -0,0 +1,15 @@ |
|||
using System.Collections.Generic; |
|||
using JetBrains.Annotations; |
|||
|
|||
namespace Volo.Abp.Features |
|||
{ |
|||
public interface IFeatureDefinitionManager |
|||
{ |
|||
[NotNull] |
|||
FeatureDefinition Get([NotNull] string name); |
|||
|
|||
IReadOnlyList<FeatureDefinition> GetAll(); |
|||
|
|||
FeatureDefinition GetOrNull(string name); |
|||
} |
|||
} |
|||
@ -0,0 +1,7 @@ |
|||
namespace Volo.Abp.Features |
|||
{ |
|||
public interface IFeatureDefinitionProvider |
|||
{ |
|||
void Define(IFeatureDefinitionContext context); |
|||
} |
|||
} |
|||
@ -0,0 +1,14 @@ |
|||
using System.Threading.Tasks; |
|||
using JetBrains.Annotations; |
|||
|
|||
namespace Volo.Abp.Features |
|||
{ |
|||
public interface IFeatureStore |
|||
{ |
|||
Task<string> GetOrNullAsync( |
|||
[NotNull] string name, |
|||
[CanBeNull] string providerName, |
|||
[CanBeNull] string providerKey |
|||
); |
|||
} |
|||
} |
|||
@ -0,0 +1,12 @@ |
|||
using System.Threading.Tasks; |
|||
using JetBrains.Annotations; |
|||
|
|||
namespace Volo.Abp.Features |
|||
{ |
|||
public interface IFeatureValueProvider |
|||
{ |
|||
string Name { get; } |
|||
|
|||
Task<string> GetOrNullAsync([NotNull] FeatureDefinition feature); |
|||
} |
|||
} |
|||
@ -0,0 +1,22 @@ |
|||
using System.Threading.Tasks; |
|||
using Microsoft.Extensions.Logging; |
|||
using Microsoft.Extensions.Logging.Abstractions; |
|||
using Volo.Abp.DependencyInjection; |
|||
|
|||
namespace Volo.Abp.Features |
|||
{ |
|||
public class NullFeatureStore : IFeatureStore, ISingletonDependency |
|||
{ |
|||
public ILogger<NullFeatureStore> Logger { get; set; } |
|||
|
|||
public NullFeatureStore() |
|||
{ |
|||
Logger = NullLogger<NullFeatureStore>.Instance; |
|||
} |
|||
|
|||
public Task<string> GetOrNullAsync(string name, string providerName, string providerKey) |
|||
{ |
|||
return Task.FromResult((string) null); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,25 @@ |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.MultiTenancy; |
|||
|
|||
namespace Volo.Abp.Features |
|||
{ |
|||
public class TenantFeatureValueProvider : FeatureValueProvider |
|||
{ |
|||
public const string ProviderName = "Tenant"; |
|||
|
|||
public override string Name => ProviderName; |
|||
|
|||
protected ICurrentTenant CurrentTenant { get; } |
|||
|
|||
public TenantFeatureValueProvider(IFeatureStore featureStore, ICurrentTenant currentTenant) |
|||
: base(featureStore) |
|||
{ |
|||
CurrentTenant = currentTenant; |
|||
} |
|||
|
|||
public override async Task<string> GetOrNullAsync(FeatureDefinition feature) |
|||
{ |
|||
return await FeatureStore.GetOrNullAsync(feature.Name, Name, CurrentTenant.Id?.ToString()); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,9 @@ |
|||
using Volo.Abp.TestApp.Testing; |
|||
|
|||
namespace Volo.Abp.EntityFrameworkCore.Domain |
|||
{ |
|||
public class ExtraProperties_Tests : ExtraProperties_Tests<AbpEntityFrameworkCoreTestModule> |
|||
{ |
|||
|
|||
} |
|||
} |
|||
@ -0,0 +1,9 @@ |
|||
using Volo.Abp.TestApp.Testing; |
|||
|
|||
namespace Volo.Abp.MongoDB.Domain |
|||
{ |
|||
public class ExtraProperties_Tests : ExtraProperties_Tests<AbpMongoDbTestModule> |
|||
{ |
|||
|
|||
} |
|||
} |
|||
@ -0,0 +1,53 @@ |
|||
using System.Threading.Tasks; |
|||
using Shouldly; |
|||
using Volo.Abp.Data; |
|||
using Volo.Abp.Modularity; |
|||
using Volo.Abp.TestApp.Domain; |
|||
using Xunit; |
|||
|
|||
namespace Volo.Abp.TestApp.Testing |
|||
{ |
|||
public abstract class ExtraProperties_Tests<TStartupModule> : TestAppTestBase<TStartupModule> |
|||
where TStartupModule : IAbpModule |
|||
{ |
|||
protected readonly ICityRepository CityRepository; |
|||
|
|||
protected ExtraProperties_Tests() |
|||
{ |
|||
CityRepository = GetRequiredService<ICityRepository>(); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_Get_An_Extra_Property() |
|||
{ |
|||
var london = await CityRepository.FindByNameAsync("London"); |
|||
london.HasProperty("Population").ShouldBeTrue(); |
|||
london.GetProperty<int>("Population").ShouldBe(10_470_000); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_Add_An_Extra_Property() |
|||
{ |
|||
var london = await CityRepository.FindByNameAsync("London"); |
|||
london.SetProperty("AreaAsKm", 1572); |
|||
await CityRepository.UpdateAsync(london); |
|||
|
|||
var london2 = await CityRepository.FindByNameAsync("London"); |
|||
london2.HasProperty("AreaAsKm").ShouldBeTrue(); |
|||
london2.GetProperty<int>("AreaAsKm").ShouldBe(1572); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_Update_An_Existing_Extra_Property() |
|||
{ |
|||
var london = await CityRepository.FindByNameAsync("London"); |
|||
|
|||
london.ExtraProperties["Population"] = 11_000_042; |
|||
await CityRepository.UpdateAsync(london); |
|||
|
|||
var london2 = await CityRepository.FindByNameAsync("London"); |
|||
london2.HasProperty("Population").ShouldBeTrue(); |
|||
london2.GetProperty<int>("Population").ShouldBe(11_000_042); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,33 @@ |
|||
using System; |
|||
using Shouldly; |
|||
using Volo.Abp.Data; |
|||
using Volo.Abp.TestApp.Domain; |
|||
using Xunit; |
|||
|
|||
namespace Volo.Abp.TestApp.Testing |
|||
{ |
|||
public class HasExtraPropertiesExtensions_Tests |
|||
{ |
|||
[Fact] |
|||
public void Basic_Tests() |
|||
{ |
|||
var city = new City(Guid.NewGuid(), "Adana"); |
|||
|
|||
city.HasProperty("UnknownProperty").ShouldBeFalse(); |
|||
city.GetProperty("UnknownProperty").ShouldBeNull(); |
|||
city.GetProperty<int>("UnknownProperty").ShouldBe(0); |
|||
|
|||
city.SetProperty("IsHot", true); |
|||
city.HasProperty("IsHot").ShouldBeTrue(); |
|||
city.GetProperty<bool>("IsHot").ShouldBeTrue(); |
|||
|
|||
city.SetProperty("IsHot", false); |
|||
city.HasProperty("IsHot").ShouldBeTrue(); |
|||
city.GetProperty<bool>("IsHot").ShouldBeFalse(); |
|||
|
|||
city.RemoveProperty("IsHot"); |
|||
city.HasProperty("IsHot").ShouldBeFalse(); |
|||
city.GetProperty<bool>("IsHot").ShouldBeFalse(); |
|||
} |
|||
} |
|||
} |
|||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue