@ -0,0 +1,14 @@ |
|||
node_modules |
|||
npm-debug.log |
|||
Dockerfile* |
|||
docker-compose* |
|||
.dockerignore |
|||
.git |
|||
.gitignore |
|||
.env |
|||
*/bin |
|||
*/obj |
|||
README.md |
|||
LICENSE |
|||
.vscode |
|||
.vs |
|||
@ -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 |
|||
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 40 KiB |
|
After Width: | Height: | Size: 45 KiB |
|
After Width: | Height: | Size: 3.2 KiB |
|
After Width: | Height: | Size: 77 KiB |
|
After Width: | Height: | Size: 53 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 28 KiB |
|
After Width: | Height: | Size: 10 KiB |
@ -0,0 +1,3 @@ |
|||
# ABP Documentation |
|||
|
|||
待添加 |
|||
@ -0,0 +1,3 @@ |
|||
## Dynamic Proxying / Interceptors |
|||
|
|||
待添加 |
|||
@ -0,0 +1,3 @@ |
|||
## Guid 生成 |
|||
|
|||
待添加 |
|||
@ -0,0 +1,3 @@ |
|||
## Unit of Work |
|||
|
|||
待添加 |
|||
@ -0,0 +1,3 @@ |
|||
## Validation |
|||
|
|||
待添加 |
|||
@ -0,0 +1,13 @@ |
|||
using Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations; |
|||
using Volo.Abp.Threading; |
|||
|
|||
namespace Volo.Abp.AspNetCore.Mvc.Client |
|||
{ |
|||
public static class CachedApplicationConfigurationClientExtensions |
|||
{ |
|||
public static ApplicationConfigurationDto Get(this ICachedApplicationConfigurationClient client) |
|||
{ |
|||
return AsyncHelper.RunSync(client.GetAsync); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,70 @@ |
|||
using System.Collections.Generic; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using Microsoft.Extensions.Localization; |
|||
using Microsoft.Extensions.Logging; |
|||
using Microsoft.Extensions.Logging.Abstractions; |
|||
using Volo.Abp.Localization; |
|||
|
|||
namespace Volo.Abp.AspNetCore.Mvc.Client |
|||
{ |
|||
public class RemoteLocalizationContributor : ILocalizationResourceContributor |
|||
{ |
|||
private LocalizationResource _resource; |
|||
private ICachedApplicationConfigurationClient _applicationConfigurationClient; |
|||
private ILogger<RemoteLocalizationContributor> _logger; |
|||
|
|||
public void Initialize(LocalizationResourceInitializationContext context) |
|||
{ |
|||
_resource = context.Resource; |
|||
_applicationConfigurationClient = context.ServiceProvider.GetRequiredService<ICachedApplicationConfigurationClient>(); |
|||
_logger = context.ServiceProvider.GetService<ILogger<RemoteLocalizationContributor>>() |
|||
?? NullLogger<RemoteLocalizationContributor>.Instance; |
|||
} |
|||
|
|||
public LocalizedString GetOrNull(string cultureName, string name) |
|||
{ |
|||
var resource = GetResourceOrNull(); |
|||
if (resource == null) |
|||
{ |
|||
return null; |
|||
} |
|||
|
|||
var value = resource.GetOrDefault(name); |
|||
if (value == null) |
|||
{ |
|||
return null; |
|||
} |
|||
|
|||
return new LocalizedString(name, value); |
|||
} |
|||
|
|||
public void Fill(string cultureName, Dictionary<string, LocalizedString> dictionary) |
|||
{ |
|||
var resource = GetResourceOrNull(); |
|||
if (resource == null) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
foreach (var keyValue in resource) |
|||
{ |
|||
dictionary[keyValue.Key] = new LocalizedString(keyValue.Key, keyValue.Value); |
|||
} |
|||
} |
|||
|
|||
private Dictionary<string, string> GetResourceOrNull() |
|||
{ |
|||
var resource = _applicationConfigurationClient |
|||
.Get() |
|||
.Localization.Values |
|||
.GetOrDefault(_resource.ResourceName); |
|||
|
|||
if (resource == null) |
|||
{ |
|||
_logger.LogWarning($"Could not find the localization resource {_resource.ResourceName} on the remote server!"); |
|||
} |
|||
|
|||
return resource; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,33 @@ |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.DependencyInjection; |
|||
using Volo.Abp.Settings; |
|||
|
|||
namespace Volo.Abp.AspNetCore.Mvc.Client |
|||
{ |
|||
public class RemoteSettingProvider : ISettingProvider, ITransientDependency |
|||
{ |
|||
protected ICachedApplicationConfigurationClient ConfigurationClient { get; } |
|||
|
|||
public RemoteSettingProvider(ICachedApplicationConfigurationClient configurationClient) |
|||
{ |
|||
ConfigurationClient = configurationClient; |
|||
} |
|||
|
|||
public async Task<string> GetOrNullAsync(string name) |
|||
{ |
|||
var configuration = await ConfigurationClient.GetAsync(); |
|||
return configuration.Setting.Values.GetOrDefault(name); |
|||
} |
|||
|
|||
public async Task<List<SettingValue>> GetAllAsync() |
|||
{ |
|||
var configuration = await ConfigurationClient.GetAsync(); |
|||
return configuration |
|||
.Setting.Values |
|||
.Select(s => new SettingValue(s.Key, s.Value)) |
|||
.ToList(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,11 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
|
|||
namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations |
|||
{ |
|||
[Serializable] |
|||
public class ApplicationSettingConfigurationDto |
|||
{ |
|||
public Dictionary<string, string> Values { get; set; } |
|||
} |
|||
} |
|||
@ -0,0 +1,20 @@ |
|||
using System; |
|||
using System.Reflection; |
|||
using Microsoft.AspNetCore.Mvc.ViewFeatures; |
|||
using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form; |
|||
|
|||
namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Extensions |
|||
{ |
|||
public static class ModelExplorerExtensions |
|||
{ |
|||
public static T GetAttribute<T>(this ModelExplorer property) where T : Attribute |
|||
{ |
|||
return property?.Metadata?.ContainerType?.GetTypeInfo()?.GetProperty(property.Metadata.PropertyName)?.GetCustomAttribute<T>(); |
|||
} |
|||
|
|||
public static int GetDisplayOrder(this ModelExplorer explorer) |
|||
{ |
|||
return GetAttribute<DisplayOrder>(explorer)?.Number ?? DisplayOrder.Default; |
|||
} |
|||
} |
|||
} |
|||
@ -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; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,20 @@ |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using Microsoft.AspNetCore.Razor.TagHelpers; |
|||
using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form; |
|||
|
|||
namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Extensions |
|||
{ |
|||
public static class TagHelperContextExtensions |
|||
{ |
|||
public static T GetValue<T>(this TagHelperContext context, string key) |
|||
{ |
|||
if (!context.Items.ContainsKey(key)) |
|||
{ |
|||
return default(T); |
|||
} |
|||
|
|||
return (T)context.Items[key]; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,41 @@ |
|||
using System; |
|||
using System.Threading.Tasks; |
|||
using Microsoft.AspNetCore.Razor.TagHelpers; |
|||
using System.Text.Encodings.Web; |
|||
using Volo.Abp.Threading; |
|||
|
|||
namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Extensions |
|||
{ |
|||
public static class TagHelperExtensions |
|||
{ |
|||
public static TagHelperOutput ProcessAndGetOutput(this TagHelper tagHelper, TagHelperAttributeList attributeList, TagHelperContext context, string tagName = "div", TagMode tagMode = TagMode.SelfClosing, bool runAsync = false) |
|||
{ |
|||
var innerOutput = new TagHelperOutput(tagName, attributeList, (useCachedResult, encoder) => Task.Run<TagHelperContent>(() => new DefaultTagHelperContent())) |
|||
{ |
|||
TagMode = tagMode |
|||
}; |
|||
|
|||
var innerContext = new TagHelperContext(attributeList, context.Items, Guid.NewGuid().ToString()); |
|||
|
|||
tagHelper.Init(context); |
|||
|
|||
if (runAsync) |
|||
{ |
|||
AsyncHelper.RunSync(() => tagHelper.ProcessAsync(innerContext, innerOutput)); |
|||
} |
|||
else |
|||
{ |
|||
tagHelper.Process(innerContext, innerOutput); |
|||
} |
|||
|
|||
return innerOutput; |
|||
} |
|||
|
|||
public static string Render(this TagHelper tagHelper, TagHelperAttributeList attributeList, TagHelperContext context, HtmlEncoder htmlEncoder, string tagName = "div", TagMode tagMode = TagMode.SelfClosing, bool runAsync = false) |
|||
{ |
|||
var innerOutput = tagHelper.ProcessAndGetOutput(attributeList, context, tagName, tagMode, runAsync); |
|||
|
|||
return innerOutput.Render(htmlEncoder); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,18 @@ |
|||
using Microsoft.AspNetCore.Razor.TagHelpers; |
|||
using System.IO; |
|||
using System.Text.Encodings.Web; |
|||
|
|||
namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Extensions |
|||
{ |
|||
public static class TagHelperOutputExtensions |
|||
{ |
|||
public static string Render(this TagHelperOutput output, HtmlEncoder htmlEncoder) |
|||
{ |
|||
using (var writer = new StringWriter()) |
|||
{ |
|||
output.WriteTo(writer, htmlEncoder); |
|||
return writer.ToString(); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -1,11 +0,0 @@ |
|||
namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Label |
|||
{ |
|||
public class AbpLabelTagHelper : AbpTagHelper<AbpLabelTagHelper, AbpLabelTagHelperService> |
|||
{ |
|||
public AbpLabelTagHelper(AbpLabelTagHelperService tagHelperService) |
|||
: base(tagHelperService) |
|||
{ |
|||
|
|||
} |
|||
} |
|||
} |
|||
@ -1,12 +0,0 @@ |
|||
using Microsoft.AspNetCore.Razor.TagHelpers; |
|||
|
|||
namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Label |
|||
{ |
|||
public class AbpLabelTagHelperService : AbpTagHelperService<AbpLabelTagHelper> |
|||
{ |
|||
public override void Process(TagHelperContext context, TagHelperOutput output) |
|||
{ |
|||
//TODO: fill
|
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,58 @@ |
|||
using Microsoft.AspNetCore.Http; |
|||
using Microsoft.Extensions.Options; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.Tracing; |
|||
|
|||
namespace Volo.Abp.AspNetCore.Tracing |
|||
{ |
|||
public class AbpCorrelationIdMiddleware |
|||
{ |
|||
private readonly RequestDelegate _next; |
|||
|
|||
public AbpCorrelationIdMiddleware(RequestDelegate next) |
|||
{ |
|||
_next = next; |
|||
} |
|||
|
|||
public async Task Invoke( |
|||
HttpContext httpContext, |
|||
IOptions<CorrelationIdOptions> options, |
|||
ICorrelationIdProvider correlationIdProvider) |
|||
{ |
|||
var correlationId = correlationIdProvider.Get(); |
|||
var optionsValue = options.Value; |
|||
|
|||
try |
|||
{ |
|||
await _next(httpContext); |
|||
} |
|||
finally |
|||
{ |
|||
CheckAndSetCorrelationIdOnResponse(httpContext, optionsValue, correlationId); |
|||
} |
|||
} |
|||
|
|||
protected virtual void CheckAndSetCorrelationIdOnResponse( |
|||
HttpContext httpContext, |
|||
CorrelationIdOptions options, |
|||
string correlationId) |
|||
{ |
|||
if (httpContext.Response.HasStarted) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
if (!options.SetResponseHeader) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
if (httpContext.Response.Headers.ContainsKey(options.HttpHeaderName)) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
httpContext.Response.Headers[options.HttpHeaderName] = correlationId; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,52 @@ |
|||
using System; |
|||
using Microsoft.AspNetCore.Http; |
|||
using Microsoft.Extensions.Options; |
|||
using Volo.Abp.DependencyInjection; |
|||
using Volo.Abp.Tracing; |
|||
|
|||
namespace Volo.Abp.AspNetCore.Tracing |
|||
{ |
|||
[Dependency(ReplaceServices = true)] |
|||
public class AspNetCoreCorrelationIdProvider : ICorrelationIdProvider, ITransientDependency |
|||
{ |
|||
protected IHttpContextAccessor HttpContextAccessor { get; } |
|||
protected CorrelationIdOptions Options { get; } |
|||
|
|||
public AspNetCoreCorrelationIdProvider( |
|||
IHttpContextAccessor httpContextAccessor, |
|||
IOptions<CorrelationIdOptions> options) |
|||
{ |
|||
HttpContextAccessor = httpContextAccessor; |
|||
Options = options.Value; |
|||
} |
|||
|
|||
public virtual string Get() |
|||
{ |
|||
if (HttpContextAccessor.HttpContext?.Request?.Headers == null) |
|||
{ |
|||
return CreateNewCorrelationId(); |
|||
} |
|||
|
|||
string correlationId = HttpContextAccessor.HttpContext.Request.Headers[Options.HttpHeaderName]; |
|||
|
|||
if (correlationId.IsNullOrEmpty()) |
|||
{ |
|||
lock (HttpContextAccessor.HttpContext.Request.Headers) |
|||
{ |
|||
if (correlationId.IsNullOrEmpty()) |
|||
{ |
|||
correlationId = CreateNewCorrelationId(); |
|||
HttpContextAccessor.HttpContext.Request.Headers[Options.HttpHeaderName] = correlationId; |
|||
} |
|||
} |
|||
} |
|||
|
|||
return correlationId; |
|||
} |
|||
|
|||
protected virtual string CreateNewCorrelationId() |
|||
{ |
|||
return Guid.NewGuid().ToString("N"); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,35 @@ |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.Security.Claims; |
|||
|
|||
namespace Volo.Abp.Authorization.Permissions |
|||
{ |
|||
public class ClientPermissionValueProvider : PermissionValueProvider |
|||
{ |
|||
public const string ProviderName = "Client"; |
|||
|
|||
public override string Name => ProviderName; |
|||
|
|||
public ClientPermissionValueProvider(IPermissionStore permissionStore) |
|||
: base(permissionStore) |
|||
{ |
|||
|
|||
} |
|||
|
|||
public override async Task<PermissionValueProviderGrantInfo> CheckAsync(PermissionValueCheckContext context) |
|||
{ |
|||
var clientId = context.Principal?.FindFirst(AbpClaimTypes.ClientId)?.Value; |
|||
|
|||
if (clientId == null) |
|||
{ |
|||
return PermissionValueProviderGrantInfo.NonGranted; |
|||
} |
|||
|
|||
if (await PermissionStore.IsGrantedAsync(context.Permission.Name, Name, clientId)) |
|||
{ |
|||
return new PermissionValueProviderGrantInfo(true, clientId); |
|||
} |
|||
|
|||
return PermissionValueProviderGrantInfo.NonGranted; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,9 @@ |
|||
namespace Volo.Abp.Tracing |
|||
{ |
|||
public class CorrelationIdOptions |
|||
{ |
|||
public string HttpHeaderName { get; set; } = "X-Correlation-Id"; |
|||
|
|||
public bool SetResponseHeader { get; set; } = true; |
|||
} |
|||
} |
|||
@ -0,0 +1,18 @@ |
|||
using System; |
|||
using Volo.Abp.DependencyInjection; |
|||
|
|||
namespace Volo.Abp.Tracing |
|||
{ |
|||
public class DefaultCorrelationIdProvider : ICorrelationIdProvider, ISingletonDependency |
|||
{ |
|||
public string Get() |
|||
{ |
|||
return CreateNewCorrelationId(); |
|||
} |
|||
|
|||
protected virtual string CreateNewCorrelationId() |
|||
{ |
|||
return Guid.NewGuid().ToString("N"); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,10 @@ |
|||
using JetBrains.Annotations; |
|||
|
|||
namespace Volo.Abp.Tracing |
|||
{ |
|||
public interface ICorrelationIdProvider |
|||
{ |
|||
[NotNull] |
|||
string Get(); |
|||
} |
|||
} |
|||
@ -0,0 +1,43 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Globalization; |
|||
using Volo.Abp.Reflection; |
|||
|
|||
namespace Volo.Abp.Data |
|||
{ |
|||
public static class HasExtraPropertiesExtensions |
|||
{ |
|||
public static bool HasProperty(this IHasExtraProperties source, string name) |
|||
{ |
|||
return source.ExtraProperties.ContainsKey(name); |
|||
} |
|||
|
|||
public static object GetProperty(this IHasExtraProperties source, string name) |
|||
{ |
|||
return source.ExtraProperties?.GetOrDefault(name); |
|||
} |
|||
|
|||
public static TProperty GetProperty<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; |
|||
} |
|||
} |
|||
} |
|||
@ -1,98 +1,61 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Reflection; |
|||
|
|||
namespace Volo.Abp.Domain.Values |
|||
{ |
|||
//Inspired from https://blogs.msdn.microsoft.com/cesardelatorre/2011/06/06/implementing-a-value-object-base-class-supertype-patternddd-patterns-related/
|
|||
|
|||
/// <summary>
|
|||
/// Base class for value objects.
|
|||
/// </summary>
|
|||
/// <typeparam name="TValueObject">The type of the value object.</typeparam>
|
|||
public abstract class ValueObject<TValueObject> : IEquatable<TValueObject> |
|||
where TValueObject : ValueObject<TValueObject> |
|||
//Inspired from https://docs.microsoft.com/en-us/dotnet/standard/microservices-architecture/microservice-ddd-cqrs-patterns/implement-value-objects
|
|||
|
|||
public abstract class ValueObject |
|||
{ |
|||
public bool Equals(TValueObject other) |
|||
protected static bool EqualOperator(ValueObject left, ValueObject right) |
|||
{ |
|||
if ((object)other == null) |
|||
if (ReferenceEquals(left, null) ^ ReferenceEquals(right, null)) |
|||
{ |
|||
return false; |
|||
} |
|||
return ReferenceEquals(left, null) || left.Equals(right); |
|||
} |
|||
|
|||
var publicProperties = GetType().GetTypeInfo().GetProperties(); |
|||
if (!publicProperties.Any()) |
|||
{ |
|||
return true; |
|||
} |
|||
|
|||
return publicProperties.All(property => Equals(property.GetValue(this, null), property.GetValue(other, null))); |
|||
protected static bool NotEqualOperator(ValueObject left, ValueObject right) |
|||
{ |
|||
return !(EqualOperator(left, right)); |
|||
} |
|||
|
|||
protected abstract IEnumerable<object> GetAtomicValues(); |
|||
|
|||
public override bool Equals(object obj) |
|||
{ |
|||
if (obj == null) |
|||
if (obj == null || obj.GetType() != GetType()) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
var item = obj as ValueObject<TValueObject>; |
|||
return (object)item != null && Equals((TValueObject)item); |
|||
} |
|||
|
|||
public override int GetHashCode() |
|||
{ |
|||
//TODO: Can we cache the hash value assuming value objects are always immutable? We can make a Reset-like method to reset it's mutated.
|
|||
|
|||
const int index = 1; |
|||
const int initialHasCode = 31; |
|||
|
|||
var publicProperties = GetType().GetTypeInfo().GetProperties(); |
|||
|
|||
if (!publicProperties.Any()) |
|||
ValueObject other = (ValueObject)obj; |
|||
IEnumerator<object> thisValues = GetAtomicValues().GetEnumerator(); |
|||
IEnumerator<object> otherValues = other.GetAtomicValues().GetEnumerator(); |
|||
while (thisValues.MoveNext() && otherValues.MoveNext()) |
|||
{ |
|||
return initialHasCode; |
|||
} |
|||
|
|||
var hashCode = initialHasCode; |
|||
var changeMultiplier = false; |
|||
|
|||
foreach (var property in publicProperties) |
|||
{ |
|||
var value = property.GetValue(this, null); |
|||
|
|||
if (value == null) |
|||
if (ReferenceEquals(thisValues.Current, null) ^ |
|||
ReferenceEquals(otherValues.Current, null)) |
|||
{ |
|||
//support {"a",null,null,"a"} != {null,"a","a",null}
|
|||
hashCode = hashCode ^ (index * 13); |
|||
continue; |
|||
return false; |
|||
} |
|||
|
|||
hashCode = hashCode * (changeMultiplier ? 59 : 114) + value.GetHashCode(); |
|||
changeMultiplier = !changeMultiplier; |
|||
} |
|||
|
|||
return hashCode; |
|||
} |
|||
|
|||
public static bool operator ==(ValueObject<TValueObject> x, ValueObject<TValueObject> y) |
|||
{ |
|||
if (ReferenceEquals(x, y)) |
|||
{ |
|||
return true; |
|||
} |
|||
|
|||
if (((object)x == null) || ((object)y == null)) |
|||
{ |
|||
return false; |
|||
if (thisValues.Current != null && |
|||
!thisValues.Current.Equals(otherValues.Current)) |
|||
{ |
|||
return false; |
|||
} |
|||
} |
|||
|
|||
return x.Equals(y); |
|||
return !thisValues.MoveNext() && !otherValues.MoveNext(); |
|||
} |
|||
|
|||
public static bool operator !=(ValueObject<TValueObject> x, ValueObject<TValueObject> y) |
|||
public override int GetHashCode() |
|||
{ |
|||
return !(x == y); |
|||
return GetAtomicValues() |
|||
.Select(x => x != null ? x.GetHashCode() : 0) |
|||
.Aggregate((x, y) => x ^ y); |
|||
} |
|||
// Other utilility methods
|
|||
} |
|||
} |
|||
|
|||