Browse Source

Merge pull request #12896 from abpframework/integration-services

Introduce Integration Service Infrastructure
pull/14001/head
maliming 4 years ago
committed by GitHub
parent
commit
c78497a4ce
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 10
      framework/src/Volo.Abp.AspNetCore.Mvc/Microsoft/AspNetCore/Mvc/Abstractions/ActionDescriptorExtensions.cs
  2. 20
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Auditing/AbpAuditActionFilter.cs
  3. 2
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Auditing/AbpAuditPageFilter.cs
  4. 30
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Conventions/ConventionalRouteBuilder.cs
  5. 7
      framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/AbpAspNetCoreConsts.cs
  6. 2
      framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Auditing/AbpAspNetCoreAuditingOptions.cs
  7. 19
      framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Auditing/AbpAuditingMiddleware.cs
  8. 6
      framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/AbpAuditingOptions.cs
  9. 4
      framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/AuditingHelper.cs
  10. 4
      framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/AuditingInterceptor.cs
  11. 9
      framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/AuditingInterceptorRegistrar.cs
  12. 2
      framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/IAuditingHelper.cs
  13. 2
      framework/src/Volo.Abp.Core/Volo/Abp/IRemoteService.cs
  14. 9
      framework/src/Volo.Abp.Core/Volo/Abp/IntegrationServiceAttribute.cs
  15. 5
      framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Services/IApplicationService.cs
  16. 8
      framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/AbpDddApplicationModule.cs
  17. 4
      framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/ApplicationService.cs
  18. 14
      framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Auditing/AuditIntegrationServiceTestController.cs
  19. 58
      framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Auditing/AuditIntegrationServiceTestController_Tests.cs
  20. 3
      framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Auditing/AuditTestController_Tests.cs
  21. 15
      framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/PeopleIntegrationService_Tests.cs
  22. 24
      framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/Auditing_Tests.cs
  23. 9
      framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Application/IPeopleIntegrationService.cs
  24. 13
      framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Application/PeopleIntegrationService.cs

10
framework/src/Volo.Abp.AspNetCore.Mvc/Microsoft/AspNetCore/Mvc/Abstractions/ActionDescriptorExtensions.cs

@ -43,4 +43,14 @@ public static class ActionDescriptorExtensions
{
return actionDescriptor is PageActionDescriptor;
}
public static PageActionDescriptor AsPageAction(this ActionDescriptor actionDescriptor)
{
if (!actionDescriptor.IsPageAction())
{
throw new AbpException($"{nameof(actionDescriptor)} should be type of {typeof(PageActionDescriptor).AssemblyQualifiedName}");
}
return actionDescriptor as PageActionDescriptor;
}
}

20
framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Auditing/AbpAuditActionFilter.cs

@ -70,7 +70,9 @@ public class AbpAuditActionFilter : IAsyncActionFilter, ITransientDependency
}
var auditingHelper = context.GetRequiredService<IAuditingHelper>();
if (!auditingHelper.ShouldSaveAudit(context.ActionDescriptor.GetMethodInfo(), true))
if (!auditingHelper.ShouldSaveAudit(
context.ActionDescriptor.GetMethodInfo(),
defaultValue: GetDefaultAuditBehavior(options, context.ActionDescriptor)))
{
return false;
}
@ -85,4 +87,20 @@ public class AbpAuditActionFilter : IAsyncActionFilter, ITransientDependency
return true;
}
private static bool GetDefaultAuditBehavior(
AbpAuditingOptions abpAuditingOptions,
ActionDescriptor actionDescriptor)
{
if (!abpAuditingOptions.IsEnabledForIntegrationServices &&
actionDescriptor
.AsControllerActionDescriptor()
.ControllerTypeInfo
.IsDefined(typeof(IntegrationServiceAttribute), true))
{
return false;
}
return true;
}
}

2
framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Auditing/AbpAuditPageFilter.cs

@ -75,7 +75,7 @@ public class AbpAuditPageFilter : IAsyncPageFilter, ITransientDependency
}
var auditingHelper = context.GetRequiredService<IAuditingHelper>();
if (!auditingHelper.ShouldSaveAudit(context.HandlerMethod.MethodInfo, true))
if (!auditingHelper.ShouldSaveAudit(context.HandlerMethod.MethodInfo, defaultValue: true))
{
return false;
}

30
framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Conventions/ConventionalRouteBuilder.cs

@ -26,9 +26,11 @@ public class ConventionalRouteBuilder : IConventionalRouteBuilder, ITransientDep
string httpMethod,
[CanBeNull] ConventionalControllerSetting configuration)
{
var controllerNameInUrl = NormalizeUrlControllerName(rootPath, controllerName, action, httpMethod, configuration);
var apiRoutePrefix = GetApiRoutePrefix(action, configuration);
var controllerNameInUrl =
NormalizeUrlControllerName(rootPath, controllerName, action, httpMethod, configuration);
var url = $"api/{rootPath}/{NormalizeControllerNameCase(controllerNameInUrl, configuration)}";
var url = $"{apiRoutePrefix}/{rootPath}/{NormalizeControllerNameCase(controllerNameInUrl, configuration)}";
//Add {id} path if needed
var idParameterModel = action.Parameters.FirstOrDefault(p => p.ParameterName == "id");
@ -69,6 +71,16 @@ public class ConventionalRouteBuilder : IConventionalRouteBuilder, ITransientDep
return url;
}
protected virtual string GetApiRoutePrefix(ActionModel actionModel, ConventionalControllerSetting configuration)
{
if (actionModel.Controller.ControllerType.IsDefined(typeof(IntegrationServiceAttribute), true))
{
return AbpAspNetCoreConsts.DefaultIntegrationServiceApiPrefix;
}
return AbpAspNetCoreConsts.DefaultApiPrefix;
}
protected virtual string NormalizeUrlActionName(string rootPath, string controllerName, ActionModel action,
string httpMethod, [CanBeNull] ConventionalControllerSetting configuration)
{
@ -108,7 +120,8 @@ public class ConventionalRouteBuilder : IConventionalRouteBuilder, ITransientDep
);
}
protected virtual string NormalizeControllerNameCase(string controllerName, [CanBeNull] ConventionalControllerSetting configuration)
protected virtual string NormalizeControllerNameCase(string controllerName,
[CanBeNull] ConventionalControllerSetting configuration)
{
if (configuration?.UseV3UrlStyle ?? Options.UseV3UrlStyle)
{
@ -120,7 +133,8 @@ public class ConventionalRouteBuilder : IConventionalRouteBuilder, ITransientDep
}
}
protected virtual string NormalizeActionNameCase(string actionName, [CanBeNull] ConventionalControllerSetting configuration)
protected virtual string NormalizeActionNameCase(string actionName,
[CanBeNull] ConventionalControllerSetting configuration)
{
if (configuration?.UseV3UrlStyle ?? Options.UseV3UrlStyle)
{
@ -132,13 +146,15 @@ public class ConventionalRouteBuilder : IConventionalRouteBuilder, ITransientDep
}
}
protected virtual string NormalizeIdPropertyNameCase(PropertyInfo property, [CanBeNull] ConventionalControllerSetting configuration)
protected virtual string NormalizeIdPropertyNameCase(PropertyInfo property,
[CanBeNull] ConventionalControllerSetting configuration)
{
return property.Name;
}
protected virtual string NormalizeSecondaryIdNameCase(ParameterModel secondaryId, [CanBeNull] ConventionalControllerSetting configuration)
protected virtual string NormalizeSecondaryIdNameCase(ParameterModel secondaryId,
[CanBeNull] ConventionalControllerSetting configuration)
{
return secondaryId.ParameterName;
}
}
}

7
framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/AbpAspNetCoreConsts.cs

@ -0,0 +1,7 @@
namespace Volo.Abp.AspNetCore;
public static class AbpAspNetCoreConsts
{
public const string DefaultApiPrefix = "api";
public const string DefaultIntegrationServiceApiPrefix = "integration-api";
}

2
framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Auditing/AbpAspNetCoreAuditingOptions.cs

@ -10,5 +10,5 @@ public class AbpAspNetCoreAuditingOptions
/// <see cref="AbpAuditingMiddleware"/> will be disabled for URLs
/// starting with an ignored URL.
/// </summary>
public List<string> IgnoredUrls { get; } = new List<string>();
public List<string> IgnoredUrls { get; } = new();
}

19
framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Auditing/AbpAuditingMiddleware.cs

@ -94,8 +94,23 @@ public class AbpAuditingMiddleware : IMiddleware, ITransientDependency
private bool IsIgnoredUrl(HttpContext context)
{
return context.Request.Path.Value != null &&
AspNetCoreAuditingOptions.IgnoredUrls.Any(x => context.Request.Path.Value.StartsWith(x));
if (context.Request.Path.Value == null)
{
return false;
}
if (!AuditingOptions.IsEnabledForIntegrationServices &&
context.Request.Path.Value.StartsWith($"/{AbpAspNetCoreConsts.DefaultIntegrationServiceApiPrefix}/"))
{
return true;
}
if (AspNetCoreAuditingOptions.IgnoredUrls.Any(x => context.Request.Path.Value.StartsWith(x)))
{
return true;
}
return false;
}
private async Task<bool> ShouldWriteAuditLogAsync(AuditLogInfo auditLogInfo, HttpContext httpContext, bool hasError)

6
framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/AbpAuditingOptions.cs

@ -37,6 +37,12 @@ public class AbpAuditingOptions
/// Default: true.
/// </summary>
public bool AlwaysLogOnException { get; set; }
/// <summary>
/// Disables/enables audit logging for integration services.
/// Default: false.
/// </summary>
public bool IsEnabledForIntegrationServices { get; set; }
public List<Func<AuditLogInfo, Task<bool>>> AlwaysLogSelectors { get; }

4
framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/AuditingHelper.cs

@ -52,7 +52,7 @@ public class AuditingHelper : IAuditingHelper, ITransientDependency
CorrelationIdProvider = correlationIdProvider;
}
public virtual bool ShouldSaveAudit(MethodInfo methodInfo, bool defaultValue = false)
public virtual bool ShouldSaveAudit(MethodInfo methodInfo, bool defaultValue = false, bool ignoreIntegrationServiceAttribute = false)
{
if (methodInfo == null)
{
@ -77,7 +77,7 @@ public class AuditingHelper : IAuditingHelper, ITransientDependency
var classType = methodInfo.DeclaringType;
if (classType != null)
{
var shouldAudit = AuditingInterceptorRegistrar.ShouldAuditTypeByDefaultOrNull(classType);
var shouldAudit = AuditingInterceptorRegistrar.ShouldAuditTypeByDefaultOrNull(classType, ignoreIntegrationServiceAttribute);
if (shouldAudit != null)
{
return shouldAudit.Value;

4
framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/AuditingInterceptor.cs

@ -62,7 +62,9 @@ public class AuditingInterceptor : AbpInterceptor, ITransientDependency
return false;
}
if (!auditingHelper.ShouldSaveAudit(invocation.Method))
if (!auditingHelper.ShouldSaveAudit(
invocation.Method,
ignoreIntegrationServiceAttribute: options.IsEnabledForIntegrationServices))
{
return false;
}

9
framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/AuditingInterceptorRegistrar.cs

@ -22,7 +22,7 @@ public static class AuditingInterceptorRegistrar
return false;
}
if (ShouldAuditTypeByDefaultOrNull(type) == true)
if (ShouldAuditTypeByDefaultOrNull(type, ignoreIntegrationServiceAttribute: true) == true)
{
return true;
}
@ -36,7 +36,7 @@ public static class AuditingInterceptorRegistrar
}
//TODO: Move to a better place
public static bool? ShouldAuditTypeByDefaultOrNull(Type type)
public static bool? ShouldAuditTypeByDefaultOrNull(Type type, bool ignoreIntegrationServiceAttribute)
{
//TODO: In an inheritance chain, it would be better to check the attributes on the top class first.
@ -52,7 +52,10 @@ public static class AuditingInterceptorRegistrar
if (typeof(IAuditingEnabled).IsAssignableFrom(type))
{
return true;
if (ignoreIntegrationServiceAttribute || !type.IsDefined(typeof(IntegrationServiceAttribute), true))
{
return true;
}
}
return null;

2
framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/IAuditingHelper.cs

@ -7,7 +7,7 @@ namespace Volo.Abp.Auditing;
//TODO: Move ShouldSaveAudit & IsEntityHistoryEnabled and rename to IAuditingFactory
public interface IAuditingHelper
{
bool ShouldSaveAudit(MethodInfo methodInfo, bool defaultValue = false);
bool ShouldSaveAudit(MethodInfo methodInfo, bool defaultValue = false, bool ignoreIntegrationServiceAttribute = false);
bool IsEntityHistoryEnabled(Type entityType, bool defaultValue = false);

2
framework/src/Volo.Abp.Core/Volo/Abp/IRemoteService.cs

@ -1,5 +1,5 @@
namespace Volo.Abp;
public interface IRemoteService //TODO: Can we move this to another package?
public interface IRemoteService
{
}

9
framework/src/Volo.Abp.Core/Volo/Abp/IntegrationServiceAttribute.cs

@ -0,0 +1,9 @@
using System;
namespace Volo.Abp;
[AttributeUsage(AttributeTargets.Class)]
public class IntegrationServiceAttribute : Attribute
{
}

5
framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Services/IApplicationService.cs

@ -3,8 +3,7 @@
/// <summary>
/// This interface must be implemented by all application services to register and identify them by convention.
/// </summary>
public interface IApplicationService :
IRemoteService
public interface IApplicationService : IRemoteService
{
}
}

8
framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/AbpDddApplicationModule.cs

@ -1,5 +1,7 @@
using System.Collections.Generic;
using Volo.Abp.Application.Services;
using Volo.Abp.Aspects;
using Volo.Abp.Auditing;
using Volo.Abp.Authorization;
using Volo.Abp.Domain;
using Volo.Abp.Features;
@ -33,10 +35,12 @@ public class AbpDddApplicationModule : AbpModule
{
Configure<AbpApiDescriptionModelOptions>(options =>
{
//TODO: Should we move related items to their own projects?
options.IgnoredInterfaces.AddIfNotContains(typeof(IRemoteService));
options.IgnoredInterfaces.AddIfNotContains(typeof(IRemoteService));
options.IgnoredInterfaces.AddIfNotContains(typeof(IApplicationService));
options.IgnoredInterfaces.AddIfNotContains(typeof(IUnitOfWorkEnabled));
options.IgnoredInterfaces.AddIfNotContains(typeof(IAuditingEnabled));
options.IgnoredInterfaces.AddIfNotContains(typeof(IValidationEnabled));
options.IgnoredInterfaces.AddIfNotContains(typeof(IGlobalFeatureCheckingEnabled));
});
}
}

4
framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/ApplicationService.cs

@ -41,9 +41,9 @@ public abstract class ApplicationService :
[Obsolete("Use LazyServiceProvider instead.")]
public IServiceProvider ServiceProvider { get; set; }
public static string[] CommonPostfixes { get; set; } = { "AppService", "ApplicationService", "Service" };
public static string[] CommonPostfixes { get; set; } = { "AppService", "ApplicationService", "IntService", "IntegrationService", "Service" };
public List<string> AppliedCrossCuttingConcerns { get; } = new List<string>();
public List<string> AppliedCrossCuttingConcerns { get; } = new();
protected IUnitOfWorkManager UnitOfWorkManager => LazyServiceProvider.LazyGetRequiredService<IUnitOfWorkManager>();

14
framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Auditing/AuditIntegrationServiceTestController.cs

@ -0,0 +1,14 @@
using Microsoft.AspNetCore.Mvc;
namespace Volo.Abp.AspNetCore.Mvc.Auditing;
[Route("integration-api/audit-test")]
[IntegrationService]
public class AuditIntegrationServiceTestController : AbpController
{
[HttpGet]
public IActionResult Get()
{
return Ok();
}
}

58
framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Auditing/AuditIntegrationServiceTestController_Tests.cs

@ -0,0 +1,58 @@
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Options;
using NSubstitute;
using Shouldly;
using Volo.Abp.Auditing;
using Xunit;
namespace Volo.Abp.AspNetCore.Mvc.Auditing;
public class AuditIntegrationServiceTestController_Tests : AspNetCoreMvcTestBase
{
private readonly AbpAuditingOptions _options;
private IAuditingStore _auditingStore;
public AuditIntegrationServiceTestController_Tests()
{
_options = ServiceProvider.GetRequiredService<IOptions<AbpAuditingOptions>>().Value;
_auditingStore = ServiceProvider.GetRequiredService<IAuditingStore>();
}
protected override void ConfigureServices(HostBuilderContext context, IServiceCollection services)
{
_auditingStore = Substitute.For<IAuditingStore>();
services.Replace(ServiceDescriptor.Singleton(_auditingStore));
base.ConfigureServices(context, services);
}
[Fact]
public async Task Should_Write_Audit_Log_For_Controllers_With_IntegrationService_Attribute_If_IsEnabledForIntegrationServices()
{
_options.IsEnabledForGetRequests = true;
_options.IsEnabledForIntegrationServices = true;
await GetResponseAsync("/integration-api/audit-test/");
await _auditingStore
.Received()
.SaveAsync(
Arg.Is<AuditLogInfo>(
x => x.Actions.Any(
a =>
a.MethodName == nameof(AuditIntegrationServiceTestController.Get) &&
a.ServiceName == typeof(AuditIntegrationServiceTestController).FullName
)
)
);
}
[Fact]
public async Task Should_Not_Write_Audit_Log_For_Controllers_With_IntegrationService_Attribute()
{
_options.IsEnabledForGetRequests = true;
await GetResponseAsync("/integration-api/audit-test/");
await _auditingStore.DidNotReceive().SaveAsync(Arg.Any<AuditLogInfo>());
}
}

3
framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Auditing/AuditTestController_Tests.cs

@ -1,5 +1,4 @@
using System;
using System.Linq;
using System.Linq;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Hosting;

15
framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/PeopleIntegrationService_Tests.cs

@ -0,0 +1,15 @@
using System.Threading.Tasks;
using Shouldly;
using Xunit;
namespace Volo.Abp.AspNetCore.Mvc;
public class PeopleIntegrationService_Tests : AspNetCoreMvcTestBase
{
[Fact]
public async Task GetValueAsync()
{
var result = await GetResponseAsStringAsync("/integration-api/app/people/value");
result.ShouldBe("42");
}
}

24
framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/Auditing_Tests.cs

@ -55,6 +55,16 @@ public class Auditing_Tests : AbpAuditingTestBase
await _auditingStore.Received().SaveAsync(Arg.Any<AuditLogInfo>());
}
[Fact]
public async Task Should_Not_Write_AuditLog_For_Classes_With_IntegrationService_Attribute()
{
var myAuditedObject1 = GetRequiredService<MyNotAuditedIntegrationService1>();
await myAuditedObject1.DoItAsync(new InputObject { Value1 = "forty-two", Value2 = 42 });
await _auditingStore.DidNotReceive().SaveAsync(Arg.Any<AuditLogInfo>());
}
public interface IMyAuditedObject : ITransientDependency, IAuditingEnabled
{
@ -72,6 +82,20 @@ public class Auditing_Tests : AbpAuditingTestBase
});
}
}
/* Integration services should not be audited by default */
[IntegrationService]
public class MyNotAuditedIntegrationService1 : IMyAuditedObject
{
public virtual Task<ResultObject> DoItAsync(InputObject inputObject)
{
return Task.FromResult(new ResultObject
{
Value1 = inputObject.Value1 + "-result",
Value2 = inputObject.Value2 + 1
});
}
}
public class ResultObject
{

9
framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Application/IPeopleIntegrationService.cs

@ -0,0 +1,9 @@
using System.Threading.Tasks;
using Volo.Abp.Application.Services;
namespace Volo.Abp.TestApp.Application;
public interface IPeopleIntegrationService : IApplicationService
{
Task<string> GetValueAsync();
}

13
framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Application/PeopleIntegrationService.cs

@ -0,0 +1,13 @@
using System.Threading.Tasks;
using Volo.Abp.Application.Services;
namespace Volo.Abp.TestApp.Application;
[IntegrationService]
public class PeopleIntegrationService : ApplicationService, IPeopleIntegrationService
{
public async Task<string> GetValueAsync()
{
return "42";
}
}
Loading…
Cancel
Save