diff --git a/Masa.Framework.sln b/Masa.Framework.sln
index d4ad33263..c694fc2a3 100644
--- a/Masa.Framework.sln
+++ b/Masa.Framework.sln
@@ -549,6 +549,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Masa.Contrib.Authentication
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Masa.Contrib.Authentication.OpenIdConnect.EFCore.Test", "src\Contrib\Authentication\Tests\Masa.Contrib.Authentication.OpenIdConnect.EFCore.Test\Masa.Contrib.Authentication.OpenIdConnect.EFCore.Test.csproj", "{F242EF1B-6951-4EE5-AF9C-CDC69D9A0260}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Masa.Utils.Extensions.DotNet.Tests", "src\Utils\Extensions\Tests\Masa.Utils.Extensions.DotNet.Tests\Masa.Utils.Extensions.DotNet.Tests.csproj", "{9E9122FD-8E27-4524-862F-FFEFA97E404A}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -1957,6 +1959,14 @@ Global
{F242EF1B-6951-4EE5-AF9C-CDC69D9A0260}.Release|Any CPU.Build.0 = Release|Any CPU
{F242EF1B-6951-4EE5-AF9C-CDC69D9A0260}.Release|x64.ActiveCfg = Release|Any CPU
{F242EF1B-6951-4EE5-AF9C-CDC69D9A0260}.Release|x64.Build.0 = Release|Any CPU
+ {9E9122FD-8E27-4524-862F-FFEFA97E404A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {9E9122FD-8E27-4524-862F-FFEFA97E404A}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {9E9122FD-8E27-4524-862F-FFEFA97E404A}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {9E9122FD-8E27-4524-862F-FFEFA97E404A}.Debug|x64.Build.0 = Debug|Any CPU
+ {9E9122FD-8E27-4524-862F-FFEFA97E404A}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {9E9122FD-8E27-4524-862F-FFEFA97E404A}.Release|Any CPU.Build.0 = Release|Any CPU
+ {9E9122FD-8E27-4524-862F-FFEFA97E404A}.Release|x64.ActiveCfg = Release|Any CPU
+ {9E9122FD-8E27-4524-862F-FFEFA97E404A}.Release|x64.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -2227,6 +2237,7 @@ Global
{583ECD6A-5960-4A60-833A-DB6DD93BE9C3} = {94D15C26-7204-4299-BC23-B89F5A0B0BF9}
{60A02980-398C-40CD-9FAA-ACAD7A9A866C} = {94D15C26-7204-4299-BC23-B89F5A0B0BF9}
{F242EF1B-6951-4EE5-AF9C-CDC69D9A0260} = {94D15C26-7204-4299-BC23-B89F5A0B0BF9}
+ {9E9122FD-8E27-4524-862F-FFEFA97E404A} = {C2FAC276-9D6E-498A-BBA2-F3F14ADF4D0D}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {40383055-CC50-4600-AD9A-53C14F620D03}
diff --git a/src/Contrib/Service/Masa.Contrib.Service.MinimalAPIs/Attributes/ExcludeMappingAttribute.cs b/src/Contrib/Service/Masa.Contrib.Service.MinimalAPIs/Attributes/IgnoreRouteAttribute.cs
similarity index 64%
rename from src/Contrib/Service/Masa.Contrib.Service.MinimalAPIs/Attributes/ExcludeMappingAttribute.cs
rename to src/Contrib/Service/Masa.Contrib.Service.MinimalAPIs/Attributes/IgnoreRouteAttribute.cs
index dfb039db0..2a3cbd3db 100644
--- a/src/Contrib/Service/Masa.Contrib.Service.MinimalAPIs/Attributes/ExcludeMappingAttribute.cs
+++ b/src/Contrib/Service/Masa.Contrib.Service.MinimalAPIs/Attributes/IgnoreRouteAttribute.cs
@@ -1,10 +1,10 @@
-// Copyright (c) MASA Stack All rights reserved.
+// Copyright (c) MASA Stack All rights reserved.
// Licensed under the MIT License. See LICENSE.txt in the project root for license information.
namespace Microsoft.AspNetCore.Mvc;
[AttributeUsage(AttributeTargets.Method)]
-public class ExcludeMappingAttribute: Attribute
+public class IgnoreRouteAttribute: Attribute
{
}
diff --git a/src/Contrib/Service/Masa.Contrib.Service.MinimalAPIs/Attributes/RoutePatternAttribute .cs b/src/Contrib/Service/Masa.Contrib.Service.MinimalAPIs/Attributes/RoutePatternAttribute .cs
new file mode 100644
index 000000000..c440c44a0
--- /dev/null
+++ b/src/Contrib/Service/Masa.Contrib.Service.MinimalAPIs/Attributes/RoutePatternAttribute .cs
@@ -0,0 +1,23 @@
+// Copyright (c) MASA Stack All rights reserved.
+// Licensed under the MIT License. See LICENSE.txt in the project root for license information.
+
+namespace Microsoft.AspNetCore.Mvc;
+
+[AttributeUsage(AttributeTargets.Method)]
+public class RoutePatternAttribute : Attribute
+{
+ public string Pattern { get; set; }
+
+ ///
+ /// The request method, the default is null (the request method is automatically identified according to the method name prefix)
+ ///
+ public string? HttpMethod { get; set; }
+
+ public bool StartWithBaseUri { get; set; }
+
+ public RoutePatternAttribute(string pattern, bool startWithBaseUri = false)
+ {
+ Pattern = pattern;
+ StartWithBaseUri = startWithBaseUri;
+ }
+}
diff --git a/src/Contrib/Service/Masa.Contrib.Service.MinimalAPIs/Helpers/ServiceBaseHelper.cs b/src/Contrib/Service/Masa.Contrib.Service.MinimalAPIs/Helpers/ServiceBaseHelper.cs
new file mode 100644
index 000000000..76c232d53
--- /dev/null
+++ b/src/Contrib/Service/Masa.Contrib.Service.MinimalAPIs/Helpers/ServiceBaseHelper.cs
@@ -0,0 +1,31 @@
+// Copyright (c) MASA Stack All rights reserved.
+// Licensed under the MIT License. See LICENSE.txt in the project root for license information.
+
+namespace Masa.Contrib.Service.MinimalAPIs;
+
+public static class ServiceBaseHelper
+{
+ public static Delegate CreateDelegate(MethodInfo methodInfo, object targetInstance)
+ {
+ var type = Expression.GetDelegateType(methodInfo.GetParameters().Select(parameterInfo => parameterInfo.ParameterType)
+ .Concat(new List
+ { methodInfo.ReturnType }).ToArray());
+ return Delegate.CreateDelegate(type, targetInstance, methodInfo);
+ }
+
+ public static string CombineUris(params string[] uris) => string.Join("/", uris.Select(u => u.Trim('/')));
+
+ public static string TrimEndMethodName(string methodName)
+ => methodName.TrimEnd("Async", StringComparison.OrdinalIgnoreCase);
+
+ public static string ParseMethodPrefix(string[] prefixes, string methodName)
+ {
+ var newMethodName = methodName;
+ var prefix = prefixes.FirstOrDefault(prefix => newMethodName.StartsWith(prefix, StringComparison.OrdinalIgnoreCase));
+
+ if (prefix is not null)
+ return prefix;
+
+ return string.Empty;
+ }
+}
diff --git a/src/Contrib/Service/Masa.Contrib.Service.MinimalAPIs/Masa.Contrib.Service.MinimalAPIs.csproj b/src/Contrib/Service/Masa.Contrib.Service.MinimalAPIs/Masa.Contrib.Service.MinimalAPIs.csproj
index 5b49091be..0148a036b 100644
--- a/src/Contrib/Service/Masa.Contrib.Service.MinimalAPIs/Masa.Contrib.Service.MinimalAPIs.csproj
+++ b/src/Contrib/Service/Masa.Contrib.Service.MinimalAPIs/Masa.Contrib.Service.MinimalAPIs.csproj
@@ -12,9 +12,9 @@
-
+
diff --git a/src/Contrib/Service/Masa.Contrib.Service.MinimalAPIs/MasaService.cs b/src/Contrib/Service/Masa.Contrib.Service.MinimalAPIs/MasaService.cs
deleted file mode 100644
index df99f640b..000000000
--- a/src/Contrib/Service/Masa.Contrib.Service.MinimalAPIs/MasaService.cs
+++ /dev/null
@@ -1,9 +0,0 @@
-// Copyright (c) MASA Stack All rights reserved.
-// Licensed under the MIT License. See LICENSE.txt in the project root for license information.
-
-namespace Masa.Contrib.Service.MinimalAPIs;
-
-public static class MasaService
-{
- public static bool DisableRestful { get; set; }
-}
diff --git a/src/Contrib/Service/Masa.Contrib.Service.MinimalAPIs/README.md b/src/Contrib/Service/Masa.Contrib.Service.MinimalAPIs/README.md
index 08c62d7ce..7b5df99b7 100644
--- a/src/Contrib/Service/Masa.Contrib.Service.MinimalAPIs/README.md
+++ b/src/Contrib/Service/Masa.Contrib.Service.MinimalAPIs/README.md
@@ -7,7 +7,7 @@ Original usage:
```C#
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
-app.MapGet("/api/v1/helloworld", ()=>"Hello World");
+app.MapGet("/api/v1/Demo/HelloWorld", () => "Hello World");
app.Run();
```
@@ -21,20 +21,14 @@ Install-Package Masa.Contrib.Service.MinimalAPIs
```c#
var builder = WebApplication.CreateBuilder(args);
-var app = builder.Services
- .AddServices(builder);
+var app = builder.Services.AddServices(builder);
```
2. Custom Service and inherit ServiceBase
```c#
-public class IntegrationEventService : ServiceBase
+public class DemoService : ServiceBase
{
- public IntegrationEventService(IServiceCollection services) : base(services)
- {
- App.MapGet("/api/v1/payment/HelloWorld", HelloWorld);
- }
-
public string HelloWorld()
{
return "Hello World";
diff --git a/src/Contrib/Service/Masa.Contrib.Service.MinimalAPIs/README.zh-CN.md b/src/Contrib/Service/Masa.Contrib.Service.MinimalAPIs/README.zh-CN.md
index 294d0cab0..535ffdd33 100644
--- a/src/Contrib/Service/Masa.Contrib.Service.MinimalAPIs/README.zh-CN.md
+++ b/src/Contrib/Service/Masa.Contrib.Service.MinimalAPIs/README.zh-CN.md
@@ -7,7 +7,7 @@
```C#
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
-app.MapGet("/api/v1/helloworld", ()=>"Hello World");
+app.MapGet("/api/v1/Demo/HelloWorld", () => "Hello World");
app.Run();
```
@@ -21,20 +21,14 @@ Install-Package Masa.Contrib.Service.MinimalAPIs
```c#
var builder = WebApplication.CreateBuilder(args);
-var app = builder.Services
- .AddServices(builder);
+var app = builder.Services.AddServices(builder);
```
2. 自定义Service并继承ServiceBase,如:
```c#
-public class IntegrationEventService : ServiceBase
+public class DemoService : ServiceBase
{
- public IntegrationEventService(IServiceCollection services) : base(services)
- {
- App.MapGet("/api/v1/payment/HelloWorld", HelloWorld);
- }
-
public string HelloWorld()
{
return "Hello World";
diff --git a/src/Contrib/Service/Masa.Contrib.Service.MinimalAPIs/ServiceBase.cs b/src/Contrib/Service/Masa.Contrib.Service.MinimalAPIs/ServiceBase.cs
index d6e81871f..f24170d4e 100644
--- a/src/Contrib/Service/Masa.Contrib.Service.MinimalAPIs/ServiceBase.cs
+++ b/src/Contrib/Service/Masa.Contrib.Service.MinimalAPIs/ServiceBase.cs
@@ -3,138 +3,164 @@
namespace Microsoft.AspNetCore.Builder;
-public abstract class ServiceBase : ServiceBaseOptions, IService
+public abstract class ServiceBase : IService
{
public WebApplication App => MasaApp.GetRequiredService();
public string BaseUri { get; init; }
+ public ServiceRouteOptions RouteOptions { get; } = new();
+
public string? ServiceName { get; init; }
- public bool DisableRestful { get; init; } = MasaService.DisableRestful;
+ ///
+ /// Based on the RouteHandlerBuilder extension, it is used to extend the mapping method, such as
+ /// RouteHandlerBuilder = routeHandlerBuilder =>
+ /// {
+ /// routeHandlerBuilder.RequireAuthorization("AtLeast21");
+ /// };
+ ///
+ public Action? RouteHandlerBuilder { get; init; }
public IServiceCollection Services => MasaApp.Services;
+#pragma warning disable S4136
+ protected ServiceBase() { }
+
protected ServiceBase(string baseUri)
{
BaseUri = baseUri;
}
+#pragma warning restore S4136
public TService? GetService() => GetServiceProvider().GetService();
public TService GetRequiredService() where TService : notnull
=> GetServiceProvider().GetRequiredService();
+#pragma warning disable CA2208
protected virtual IServiceProvider GetServiceProvider()
- => MasaApp.GetService()?.HttpContext?.RequestServices ?? MasaApp.RootServiceProvider;
+ => MasaApp.GetService()?.HttpContext?.RequestServices ?? throw new MasaException("Failed to get ServiceProvider of current request");
+#pragma warning restore CA2208
+
+ internal void AutoMapRoute(ServiceGlobalRouteOptions globalOptions, PluralizationService pluralizationService)
+ {
+ var type = GetType();
+
+ var methodInfos = type
+ .GetMethods(BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.Instance)
+ .Where(methodInfo => methodInfo.CustomAttributes.All(attr => attr.AttributeType != typeof(IgnoreRouteAttribute)))
+ .Concat(type.GetMethods(BindingFlags.Public | BindingFlags.Instance)
+ .Where(methodInfo => methodInfo.CustomAttributes.Any(attr => attr.AttributeType == typeof(RoutePatternAttribute))))
+ .Distinct();
- protected virtual string GetBaseUri(ServiceBaseOptions globalOptions, PluralizationService pluralizationService)
+ foreach (var method in methodInfos)
+ {
+ var handler = ServiceBaseHelper.CreateDelegate(method, this);
+
+ string? pattern = null;
+ string? httpMethod = null;
+ string? methodName = null;
+ var attribute = method.GetCustomAttribute();
+ if (attribute != null)
+ {
+ httpMethod = attribute.HttpMethod;
+ if (attribute.StartWithBaseUri)
+ methodName = attribute.Pattern;
+ else
+ pattern = attribute.Pattern;
+ }
+
+ string newMethodName = method.Name;
+
+ if (httpMethod == null || pattern == null)
+ {
+ var result = ParseMethod(globalOptions, newMethodName);
+ httpMethod ??= result.HttpMethod;
+ newMethodName = result.MethodName;
+ }
+
+ pattern ??= ServiceBaseHelper.CombineUris(GetBaseUri(globalOptions, pluralizationService),
+ methodName ?? GetMethodName(method, newMethodName, globalOptions));
+ var routeHandlerBuilder = MapMethods(pattern, httpMethod, handler);
+ (RouteHandlerBuilder ?? globalOptions.RouteHandlerBuilder)?.Invoke(routeHandlerBuilder);
+ }
+ }
+
+ RouteHandlerBuilder MapMethods(string pattern, string? httpMethod, Delegate handler)
{
- if (DisableRestful) return string.Empty;
+ if (httpMethod != null)
+ return App.MapMethods(pattern, new[] { httpMethod }, handler);
- return string.IsNullOrWhiteSpace(BaseUri) ? GetUrl(globalOptions, pluralizationService) : BaseUri;
+ return App.Map(pattern, handler);
}
- private string GetUrl(ServiceBaseOptions globalOptions, PluralizationService pluralizationService)
+ protected virtual string GetBaseUri(ServiceRouteOptions globalOptions, PluralizationService pluralizationService)
{
+ if (!string.IsNullOrWhiteSpace(BaseUri))
+ return BaseUri;
+
var list = new List()
{
- Prefix ?? globalOptions.Prefix ?? string.Empty,
- Version ?? globalOptions.Version ?? string.Empty,
- ServiceName ??
- GetServiceName((PluralizeServiceName ?? globalOptions.PluralizeServiceName) is true ? pluralizationService : null)
+ RouteOptions.Prefix ?? globalOptions.Prefix ?? string.Empty,
+ RouteOptions.Version ?? globalOptions.Version ?? string.Empty,
+ ServiceName ?? GetServiceName(RouteOptions.PluralizeServiceName ?? globalOptions.PluralizeServiceName ?? false ?
+ pluralizationService :
+ null)
};
- return string.Join('/', list.Where(x => !string.IsNullOrWhiteSpace(x)));
+ return string.Join('/', list.Where(x => !string.IsNullOrWhiteSpace(x)).Select(u => u.Trim('/')));
}
private string GetServiceName(PluralizationService? pluralizationService)
{
- var typeName = GetType().Name;
- var index = typeName.LastIndexOf("Service", StringComparison.OrdinalIgnoreCase);
- var serviceName = typeName.Remove(index);
+ var serviceName = GetType().Name.TrimEnd("Service", StringComparison.OrdinalIgnoreCase);
if (pluralizationService == null)
return serviceName;
return pluralizationService.Pluralize(serviceName);
}
- internal void AutoMapRoute(ServiceBaseOptions globalOptions, PluralizationService pluralizationService)
+ [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
+ protected virtual string GetMethodName(MethodInfo methodInfo, string methodName, ServiceRouteOptions globalOptions)
{
- var type = GetType();
+ if (!(RouteOptions.AutoAppendId ?? globalOptions.AutoAppendId ?? false))
+ return ServiceBaseHelper.TrimEndMethodName(methodName);
- var methodInfos = type
- .GetMethods(BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.Instance)
- .Where(methodInfo => methodInfo.CustomAttributes.All(attr => attr.AttributeType != typeof(ExcludeMappingAttribute)));
-
- foreach (var method in methodInfos)
+ var idParameter = methodInfo.GetParameters().FirstOrDefault(p => p.Name!.Equals("id", StringComparison.OrdinalIgnoreCase));
+ if (idParameter != null)
{
- var @delegate = CreateDelegate(method, this);
-
- var pattern = CombineUris(GetBaseUri(globalOptions, pluralizationService), GetMethodName(method));
- var httpMethod = GetHttpMethod(globalOptions, method.Name);
-
- if (httpMethod != null)
- App.MapMethods(pattern, new[] { httpMethod }, @delegate);
- else
- App.Map(pattern, @delegate);
+ var id = idParameter.ParameterType.IsNullableType() || idParameter.HasDefaultValue ? "{id?}" : "{id}";
+ return $"{ServiceBaseHelper.TrimEndMethodName(methodName)}/{id}";
}
- }
-
- protected virtual string? GetHttpMethod(ServiceBaseOptions globalOptions, string methodName)
- {
- if (ExistPrefix(GetPrefixs ?? globalOptions.GetPrefixs!, methodName))
- return "GET";
-
- if (ExistPrefix(PostPrefixs ?? globalOptions.PostPrefixs!, methodName))
- return "POST";
-
- if (ExistPrefix(PutPrefixs ?? globalOptions.PutPrefixs!, methodName))
- return "PUT";
-
- if (ExistPrefix(DeletePrefixs ?? globalOptions.DeletePrefixs!, methodName))
- return "DELETE";
- return null;
+ return ServiceBaseHelper.TrimEndMethodName(methodName);
}
- public static bool ExistPrefix(string[] prefixs, string methodName)
- => prefixs.Any(prefix => methodName.StartsWith(prefix, StringComparison.OrdinalIgnoreCase));
-
- private static Delegate CreateDelegate(MethodInfo methodInfo, object targetInstance)
+ protected virtual (string? HttpMethod, string MethodName) ParseMethod(ServiceRouteOptions globalOptions, string methodName)
{
- var type = Expression.GetDelegateType(methodInfo.GetParameters().Select(parameterInfo => parameterInfo.ParameterType)
- .Concat(new List
- { methodInfo.ReturnType }).ToArray());
- return Delegate.CreateDelegate(type, targetInstance, methodInfo);
- }
+ var prefix = ServiceBaseHelper.ParseMethodPrefix(RouteOptions.GetPrefixes ?? globalOptions.GetPrefixes!, methodName);
+ if (!string.IsNullOrEmpty(prefix))
+ return ("GET", methodName.Substring(prefix.Length));
- [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
- protected virtual string GetMethodName(MethodInfo methodInfo)
- {
- var parameters = methodInfo.GetParameters();
- if (parameters.Length >= 1 && parameters.Any(parameter => parameter.Name != null && parameter.Name.Equals("id", StringComparison.OrdinalIgnoreCase)))
- return parameters[0].ParameterType.IsNullableType() ? "{id?}" : "{id}";
+ prefix = ServiceBaseHelper.ParseMethodPrefix(RouteOptions.PostPrefixes ?? globalOptions.PostPrefixes!, methodName);
+ if (!string.IsNullOrEmpty(prefix))
+ return ("POST", methodName.Substring(prefix.Length));
- return FormatMethodName(methodInfo.Name);
- }
+ prefix = ServiceBaseHelper.ParseMethodPrefix(RouteOptions.PutPrefixes ?? globalOptions.PutPrefixes!, methodName);
+ if (!string.IsNullOrEmpty(prefix))
+ return ("PUT", methodName.Substring(prefix.Length));
- public static string FormatMethodName(string methodName)
- {
- if (methodName.EndsWith("Async"))
- {
- var i = methodName.LastIndexOf("Async", StringComparison.Ordinal);
- methodName = methodName[..i];
- }
+ prefix = ServiceBaseHelper.ParseMethodPrefix(RouteOptions.DeletePrefixes ?? globalOptions.DeletePrefixes!, methodName);
+ if (!string.IsNullOrEmpty(prefix))
+ return ("DELETE", methodName.Substring(prefix.Length));
- return methodName;
+ return (null, string.Empty);
}
- public static string CombineUris(params string[] uris)
- => string.Join("/", uris.Select(u => u.Trim('/')));
-
#region Obsolete
+#pragma warning disable S4136
[Obsolete("service can be ignored")]
protected ServiceBase(IServiceCollection services)
{
@@ -145,6 +171,7 @@ protected ServiceBase(IServiceCollection services, string baseUri) : this(servic
{
}
+#pragma warning restore S4136
#region [Obsolete] Map GET,POST,PUT,DELETE
@@ -155,14 +182,14 @@ protected ServiceBase(IServiceCollection services, string baseUri) : this(servic
/// The delegate executed when the endpoint is matched. It's name is a part of pattern if is null.
/// The custom uri. It is a part of pattern if it is not null.
/// Determines whether to remove the string 'Async' at the end.
- /// A that can be used to further customize the endpoint.
+ /// A that can be used to further customize the endpoint.
[Obsolete("It is recommended to map according to the automatic mapping rules")]
[System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
protected RouteHandlerBuilder MapGet(Delegate handler, string? customUri = null, bool trimEndAsync = true)
{
- customUri ??= FormatMethodName(handler.Method.Name);
+ customUri ??= ServiceBaseHelper.TrimEndMethodName(handler.Method.Name);
- var pattern = CombineUris(BaseUri, customUri);
+ var pattern = ServiceBaseHelper.CombineUris(BaseUri, customUri);
return App.MapGet(pattern, handler);
}
@@ -174,14 +201,14 @@ protected RouteHandlerBuilder MapGet(Delegate handler, string? customUri = null,
/// The delegate executed when the endpoint is matched. It's name is a part of pattern if is null.
/// The custom uri. It is a part of pattern if it is not null.
/// Determines whether to remove the string 'Async' at the end.
- /// A that can be used to further customize the endpoint.
+ /// A that can be used to further customize the endpoint.
[Obsolete("It is recommended to map according to the automatic mapping rules")]
[System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
protected RouteHandlerBuilder MapPost(Delegate handler, string? customUri = null, bool trimEndAsync = true)
{
- customUri ??= FormatMethodName(handler.Method.Name);
+ customUri ??= ServiceBaseHelper.TrimEndMethodName(handler.Method.Name);
- var pattern = CombineUris(BaseUri, customUri);
+ var pattern = ServiceBaseHelper.CombineUris(BaseUri, customUri);
return App.MapPost(pattern, handler);
}
@@ -193,14 +220,14 @@ protected RouteHandlerBuilder MapPost(Delegate handler, string? customUri = null
/// The delegate executed when the endpoint is matched. It's name is a part of pattern if is null.
/// The custom uri. It is a part of pattern if it is not null.
/// Determines whether to remove the string 'Async' at the end.
- /// A that can be used to further customize the endpoint.
+ /// A that can be used to further customize the endpoint.
[Obsolete("It is recommended to map according to the automatic mapping rules")]
[System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
protected RouteHandlerBuilder MapPut(Delegate handler, string? customUri = null, bool trimEndAsync = true)
{
- customUri ??= FormatMethodName(handler.Method.Name);
+ customUri ??= ServiceBaseHelper.TrimEndMethodName(handler.Method.Name);
- var pattern = CombineUris(BaseUri, customUri);
+ var pattern = ServiceBaseHelper.CombineUris(BaseUri, customUri);
return App.MapPut(pattern, handler);
}
@@ -212,14 +239,14 @@ protected RouteHandlerBuilder MapPut(Delegate handler, string? customUri = null,
/// The delegate executed when the endpoint is matched. It's name is a part of pattern if is null.
/// The custom uri. It is a part of pattern if it is not null.
/// Determines whether to remove the string 'Async' at the end.
- /// A that can be used to further customize the endpoint.
+ /// A that can be used to further customize the endpoint.
[Obsolete("It is recommended to map according to the automatic mapping rules")]
[System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
protected RouteHandlerBuilder MapDelete(Delegate handler, string? customUri = null, bool trimEndAsync = true)
{
- customUri ??= FormatMethodName(handler.Method.Name);
+ customUri ??= ServiceBaseHelper.TrimEndMethodName(handler.Method.Name);
- var pattern = CombineUris(BaseUri, customUri);
+ var pattern = ServiceBaseHelper.CombineUris(BaseUri, customUri);
return App.MapDelete(pattern, handler);
}
diff --git a/src/Contrib/Service/Masa.Contrib.Service.MinimalAPIs/ServiceCollectionExtensions.cs b/src/Contrib/Service/Masa.Contrib.Service.MinimalAPIs/ServiceCollectionExtensions.cs
index ed5bea294..72bcaa8f1 100644
--- a/src/Contrib/Service/Masa.Contrib.Service.MinimalAPIs/ServiceCollectionExtensions.cs
+++ b/src/Contrib/Service/Masa.Contrib.Service.MinimalAPIs/ServiceCollectionExtensions.cs
@@ -1,8 +1,6 @@
// Copyright (c) MASA Stack All rights reserved.
// Licensed under the MIT License. See LICENSE.txt in the project root for license information.
-using Microsoft.Extensions.Options;
-
namespace Microsoft.Extensions.DependencyInjection;
public static class ServiceCollectionExtensions
@@ -28,7 +26,7 @@ public static WebApplication AddServices(
///
public static WebApplication AddServices(
this WebApplicationBuilder builder,
- Action action)
+ Action action)
=> builder.Services.AddServices(builder, action);
///
@@ -51,18 +49,16 @@ public static WebApplication AddServices(
public static WebApplication AddServices(
this IServiceCollection services,
WebApplicationBuilder builder,
- Action action)
+ Action action)
{
if (services.All(service => service.ImplementationType != typeof(MinimalApisMarkerService)))
{
services.AddSingleton();
+ services.AddHttpContextAccessor();
services.Configure(action);
-#pragma warning disable CA1822
- //todo: Version 1.0 will be removed
- services.TryAddScoped(sp => services);
-#pragma warning restore CA1822
+ services.TryAddScoped(sp => services);// Version 1.0 will be removed
services.AddSingleton(new Lazy(builder.Build, LazyThreadSafetyMode.ExecutionAndPublication))
.AddTransient(serviceProvider => serviceProvider.GetRequiredService>().Value);
@@ -70,11 +66,14 @@ public static WebApplication AddServices(
MasaApp.Services = services;
MasaApp.Build(services.BuildServiceProvider());
- var serviceMapOptions = MasaApp.GetRequiredService>().Value;
+ var serviceMapOptions = MasaApp.GetRequiredService>().Value;
services.AddServices(true, (_, serviceInstance) =>
{
var instance = (ServiceBase)serviceInstance;
- if (!instance.DisableRestful) instance.AutoMapRoute(serviceMapOptions, serviceMapOptions.Pluralization);
+ if (instance.RouteOptions.DisableAutoMapRoute ?? serviceMapOptions.DisableAutoMapRoute ?? false)
+ return;
+
+ instance.AutoMapRoute(serviceMapOptions, serviceMapOptions.Pluralization);
}, serviceMapOptions.Assemblies);
}
diff --git a/src/Contrib/Service/Masa.Contrib.Service.MinimalAPIs/ServiceGlobalRouteOptions.cs b/src/Contrib/Service/Masa.Contrib.Service.MinimalAPIs/ServiceGlobalRouteOptions.cs
new file mode 100644
index 000000000..f132c6d5f
--- /dev/null
+++ b/src/Contrib/Service/Masa.Contrib.Service.MinimalAPIs/ServiceGlobalRouteOptions.cs
@@ -0,0 +1,28 @@
+// Copyright (c) MASA Stack All rights reserved.
+// Licensed under the MIT License. See LICENSE.txt in the project root for license information.
+
+namespace Masa.Contrib.Service.MinimalAPIs;
+
+public class ServiceGlobalRouteOptions : ServiceRouteOptions
+{
+ public Assembly[] Assemblies { get; set; }
+
+ public Action? RouteHandlerBuilder { get; set; }
+
+ internal PluralizationService Pluralization { get; set; }
+
+ public ServiceGlobalRouteOptions()
+ {
+ DisableAutoMapRoute = false;
+ Prefix = "api";
+ Version = "v1";
+ AutoAppendId = true;
+ PluralizeServiceName = true;
+ GetPrefixes = new[] { "Get", "Select" };
+ PostPrefixes = new[] { "Post", "Add", "Upsert", "Create" };
+ PutPrefixes = new[] { "Put", "Update", "Modify" };
+ DeletePrefixes = new[] { "Delete", "Remove" };
+ Assemblies = AppDomain.CurrentDomain.GetAssemblies();
+ Pluralization = PluralizationService.CreateService(CultureInfo.CreateSpecificCulture("en"));
+ }
+}
diff --git a/src/Contrib/Service/Masa.Contrib.Service.MinimalAPIs/ServiceMapOptions.cs b/src/Contrib/Service/Masa.Contrib.Service.MinimalAPIs/ServiceMapOptions.cs
deleted file mode 100644
index 1c706f177..000000000
--- a/src/Contrib/Service/Masa.Contrib.Service.MinimalAPIs/ServiceMapOptions.cs
+++ /dev/null
@@ -1,24 +0,0 @@
-// Copyright (c) MASA Stack All rights reserved.
-// Licensed under the MIT License. See LICENSE.txt in the project root for license information.
-
-namespace Masa.Contrib.Service.MinimalAPIs;
-
-public class ServiceMapOptions : ServiceBaseOptions
-{
- public Assembly[] Assemblies { get; set; }
-
- internal PluralizationService Pluralization { get; set; }
-
- public ServiceMapOptions()
- {
- Assemblies = AppDomain.CurrentDomain.GetAssemblies();
- Prefix = "api";
- Version = "v1";
- PluralizeServiceName = false;
- GetPrefixs = new[] { "GET", "SELECT" };
- PostPrefixs = new[] { "POST", "ADD", "Upsert" };
- PutPrefixs = new[] { "PUT", "Update", "Modify" };
- DeletePrefixs = new[] { "DELETE", "REMOVE" };
- Pluralization = PluralizationService.CreateService(CultureInfo.CreateSpecificCulture("en"));
- }
-}
diff --git a/src/Contrib/Service/Masa.Contrib.Service.MinimalAPIs/ServiceBaseOptions.cs b/src/Contrib/Service/Masa.Contrib.Service.MinimalAPIs/ServiceRouteOptions.cs
similarity index 63%
rename from src/Contrib/Service/Masa.Contrib.Service.MinimalAPIs/ServiceBaseOptions.cs
rename to src/Contrib/Service/Masa.Contrib.Service.MinimalAPIs/ServiceRouteOptions.cs
index fb68f801a..4cd4e730f 100644
--- a/src/Contrib/Service/Masa.Contrib.Service.MinimalAPIs/ServiceBaseOptions.cs
+++ b/src/Contrib/Service/Masa.Contrib.Service.MinimalAPIs/ServiceRouteOptions.cs
@@ -1,10 +1,12 @@
-// Copyright (c) MASA Stack All rights reserved.
+// Copyright (c) MASA Stack All rights reserved.
// Licensed under the MIT License. See LICENSE.txt in the project root for license information.
namespace Masa.Contrib.Service.MinimalAPIs;
-public class ServiceBaseOptions
+public class ServiceRouteOptions
{
+ public bool? DisableAutoMapRoute { get; set; }
+
///
/// The prefix, the default is null
/// Formatter is $"{Prefix}/{Version}/{ServiceName}", any one IsNullOrWhiteSpace would be ignored.
@@ -17,13 +19,15 @@ public class ServiceBaseOptions
///
public string? Version { get; set; }
+ public bool? AutoAppendId { get; set; }
+
public bool? PluralizeServiceName { get; set; }
- public string[]? GetPrefixs { get; set; }
+ public string[]? GetPrefixes { get; set; }
- public string[]? PostPrefixs { get; set; }
+ public string[]? PostPrefixes { get; set; }
- public string[]? PutPrefixs { get; set; }
+ public string[]? PutPrefixes { get; set; }
- public string[]? DeletePrefixs { get; set; }
+ public string[]? DeletePrefixes { get; set; }
}
diff --git a/src/Contrib/Service/Masa.Contrib.Service.MinimalAPIs/_Imports.cs b/src/Contrib/Service/Masa.Contrib.Service.MinimalAPIs/_Imports.cs
index 5563a4a34..9c5591190 100644
--- a/src/Contrib/Service/Masa.Contrib.Service.MinimalAPIs/_Imports.cs
+++ b/src/Contrib/Service/Masa.Contrib.Service.MinimalAPIs/_Imports.cs
@@ -10,6 +10,7 @@
global using Microsoft.AspNetCore.Routing;
global using Microsoft.Extensions.DependencyInjection;
global using Microsoft.Extensions.DependencyInjection.Extensions;
+global using Microsoft.Extensions.Options;
global using System;
global using System.Collections.Generic;
global using System.Globalization;
diff --git a/src/Contrib/Service/Tests/Masa.Contrib.Service.MinimalAPIs.Tests/MinimalAPITest.cs b/src/Contrib/Service/Tests/Masa.Contrib.Service.MinimalAPIs.Tests/MinimalAPITest.cs
index cab67d769..092a9a31a 100644
--- a/src/Contrib/Service/Tests/Masa.Contrib.Service.MinimalAPIs.Tests/MinimalAPITest.cs
+++ b/src/Contrib/Service/Tests/Masa.Contrib.Service.MinimalAPIs.Tests/MinimalAPITest.cs
@@ -38,8 +38,7 @@ public void AddService()
Assert.ReferenceEquals(customService.Services, _builder.Services);
- Assert.IsNotNull(customService.GetRequiredService());
- Assert.IsNotNull(customService.GetService());
+ Assert.ThrowsException(() => customService.GetRequiredService());
Assert.IsTrue(customService.GetTest2() == 1);
diff --git a/src/Contrib/Service/Tests/Masa.Contrib.Service.MinimalAPIs.Tests/ServiceBaseTest.cs b/src/Contrib/Service/Tests/Masa.Contrib.Service.MinimalAPIs.Tests/ServiceBaseTest.cs
index ac4f7f784..552d80c21 100644
--- a/src/Contrib/Service/Tests/Masa.Contrib.Service.MinimalAPIs.Tests/ServiceBaseTest.cs
+++ b/src/Contrib/Service/Tests/Masa.Contrib.Service.MinimalAPIs.Tests/ServiceBaseTest.cs
@@ -9,15 +9,12 @@ public class ServiceBaseTest
[TestMethod]
public void TestGetBaseUri()
{
- var serviceMapOptions = new ServiceMapOptions();
+ var serviceMapOptions = new ServiceGlobalRouteOptions();
var serviceBase = GetCustomService();
- Assert.AreEqual("api/v1/Custom", serviceBase.TestGetBaseUri(serviceMapOptions));
+ Assert.AreEqual("api/v1/Customs", serviceBase.TestGetBaseUri(serviceMapOptions));
serviceBase = GetUserService();
- Assert.AreEqual("api/v1/User", serviceBase.TestGetBaseUri(serviceMapOptions));
-
- serviceBase = GetOrderService();
- Assert.AreEqual(string.Empty, serviceBase.TestGetBaseUri(serviceMapOptions));
+ Assert.AreEqual("api/v1/Users", serviceBase.TestGetBaseUri(serviceMapOptions));
serviceBase = GetGoodsService();
Assert.AreEqual("api/v2/Goods", serviceBase.TestGetBaseUri(serviceMapOptions));
@@ -29,7 +26,7 @@ public void TestGetBaseUri()
[DataRow("Order/Get", "Order/Get")]
public void TestFormatMethods(string methodName, string result)
{
- Assert.AreEqual(result, ServiceBase.FormatMethodName(methodName));
+ Assert.AreEqual(result, ServiceBaseHelper.TrimEndMethodName(methodName));
}
[TestMethod]
@@ -41,7 +38,16 @@ public void TestCombineUris()
"v1",
"order"
};
- Assert.AreEqual("api/v1/order", ServiceBase.CombineUris(uris));
+ Assert.AreEqual("api/v1/order", ServiceBaseHelper.CombineUris(uris));
+ }
+
+ [DataTestMethod]
+ [DataRow("Update,Modify,Put", "AddGoods", "")]
+ [DataRow("Add, Upsert, Create, AddGoods", "AddGoods", "Add")]
+ public void TestTryParseHttpMethod(string prefixes, string methodName, string prefix)
+ {
+ var result = ServiceBaseHelper.ParseMethodPrefix(prefixes.Split(','), methodName);
+ Assert.AreEqual(prefix, result);
}
#region private methods
@@ -52,9 +58,6 @@ private static CustomServiceBase GetCustomService()
private static CustomServiceBase GetUserService()
=> new UserService();
- private static CustomServiceBase GetOrderService()
- => new OrderService();
-
private static CustomServiceBase GetGoodsService()
=> new GoodsService();
diff --git a/src/Contrib/Service/Tests/Masa.Contrib.Service.MinimalAPIs.Tests/Services/CustomServiceBase.cs b/src/Contrib/Service/Tests/Masa.Contrib.Service.MinimalAPIs.Tests/Services/CustomServiceBase.cs
index 7802dcf0c..e31fa6370 100644
--- a/src/Contrib/Service/Tests/Masa.Contrib.Service.MinimalAPIs.Tests/Services/CustomServiceBase.cs
+++ b/src/Contrib/Service/Tests/Masa.Contrib.Service.MinimalAPIs.Tests/Services/CustomServiceBase.cs
@@ -5,7 +5,7 @@ namespace Masa.Contrib.Service.MinimalAPIs.Tests.Services;
public abstract class CustomServiceBase : ServiceBase
{
- protected CustomServiceBase() : base(new ServiceCollection())
+ protected CustomServiceBase()
{
}
@@ -13,6 +13,6 @@ protected CustomServiceBase(string baseUri) : base(baseUri)
{
}
- public string TestGetBaseUri(ServiceBaseOptions globalOptions) => base.GetBaseUri(globalOptions,
+ public string TestGetBaseUri(ServiceRouteOptions globalOptions) => base.GetBaseUri(globalOptions,
PluralizationService.CreateService(System.Globalization.CultureInfo.CreateSpecificCulture("en")));
}
diff --git a/src/Contrib/Service/Tests/Masa.Contrib.Service.MinimalAPIs.Tests/Services/GoodsService.cs b/src/Contrib/Service/Tests/Masa.Contrib.Service.MinimalAPIs.Tests/Services/GoodsService.cs
index 4d4c15b4f..ee966805f 100644
--- a/src/Contrib/Service/Tests/Masa.Contrib.Service.MinimalAPIs.Tests/Services/GoodsService.cs
+++ b/src/Contrib/Service/Tests/Masa.Contrib.Service.MinimalAPIs.Tests/Services/GoodsService.cs
@@ -7,7 +7,7 @@ public class GoodsService : CustomServiceBase
{
public GoodsService()
{
- Prefix = "api";
- Version = "v2";
+ RouteOptions.Prefix = "api";
+ RouteOptions.Version = "v2";
}
}
diff --git a/src/Contrib/Service/Tests/Masa.Contrib.Service.MinimalAPIs.Tests/Services/OrderService.cs b/src/Contrib/Service/Tests/Masa.Contrib.Service.MinimalAPIs.Tests/Services/OrderService.cs
deleted file mode 100644
index 3c3e3ff34..000000000
--- a/src/Contrib/Service/Tests/Masa.Contrib.Service.MinimalAPIs.Tests/Services/OrderService.cs
+++ /dev/null
@@ -1,12 +0,0 @@
-// Copyright (c) MASA Stack All rights reserved.
-// Licensed under the MIT License. See LICENSE.txt in the project root for license information.
-
-namespace Masa.Contrib.Service.MinimalAPIs.Tests.Services;
-
-public class OrderService: CustomServiceBase
-{
- public OrderService()
- {
- DisableRestful = true;
- }
-}
diff --git a/src/Utils/Extensions/Masa.Utils.Extensions.DotNet/StringExtensions.cs b/src/Utils/Extensions/Masa.Utils.Extensions.DotNet/StringExtensions.cs
new file mode 100644
index 000000000..e6624a39b
--- /dev/null
+++ b/src/Utils/Extensions/Masa.Utils.Extensions.DotNet/StringExtensions.cs
@@ -0,0 +1,33 @@
+// Copyright (c) MASA Stack All rights reserved.
+// Licensed under the MIT License. See LICENSE.txt in the project root for license information.
+
+namespace System;
+
+public static class StringExtensions
+{
+ public static string TrimStart(this string value, string trimParameter)
+ => value.TrimStart(trimParameter, StringComparison.CurrentCulture);
+
+ public static string TrimStart(this string value,
+ string trimParameter,
+ StringComparison stringComparison)
+ {
+ if (!value.StartsWith(trimParameter, stringComparison))
+ return value;
+
+ return value.Substring(trimParameter.Length);
+ }
+
+ public static string TrimEnd(this string value, string trimParameter)
+ => value.TrimEnd(trimParameter, StringComparison.CurrentCulture);
+
+ public static string TrimEnd(this string value,
+ string trimParameter,
+ StringComparison stringComparison)
+ {
+ if (!value.EndsWith(trimParameter, stringComparison))
+ return value;
+
+ return value.Substring(0, value.Length - trimParameter.Length);
+ }
+}
diff --git a/src/Utils/Extensions/Tests/Masa.Utils.Extensions.DotNet.Tests/Masa.Utils.Extensions.DotNet.Tests.csproj b/src/Utils/Extensions/Tests/Masa.Utils.Extensions.DotNet.Tests/Masa.Utils.Extensions.DotNet.Tests.csproj
new file mode 100644
index 000000000..56749d35b
--- /dev/null
+++ b/src/Utils/Extensions/Tests/Masa.Utils.Extensions.DotNet.Tests/Masa.Utils.Extensions.DotNet.Tests.csproj
@@ -0,0 +1,28 @@
+
+
+
+ net6.0
+ enable
+ enable
+ false
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Utils/Extensions/Tests/Masa.Utils.Extensions.DotNet.Tests/StringTest.cs b/src/Utils/Extensions/Tests/Masa.Utils.Extensions.DotNet.Tests/StringTest.cs
new file mode 100644
index 000000000..9a9220ef3
--- /dev/null
+++ b/src/Utils/Extensions/Tests/Masa.Utils.Extensions.DotNet.Tests/StringTest.cs
@@ -0,0 +1,24 @@
+// Copyright (c) MASA Stack All rights reserved.
+// Licensed under the MIT License. See LICENSE.txt in the project root for license information.
+
+namespace Masa.Utils.Extensions.DotNet.Tests;
+
+[TestClass]
+public class StringTest
+{
+ [DataTestMethod]
+ [DataRow("abcdawDf", "abc", "dawDf")]
+ [DataRow("abcdawDf", "abd", "abcdawDf")]
+ public void TestTrimStart(string value, string trimParameter, string result)
+ {
+ Assert.AreEqual(result, value.TrimStart(trimParameter, StringComparison.OrdinalIgnoreCase));
+ }
+
+ [DataTestMethod]
+ [DataRow("abcdawDf", "df", "abcdaw")]
+ [DataRow("abcdawDf", "adf", "abcdawDf")]
+ public void TestTrimEnd(string value, string trimParameter, string result)
+ {
+ Assert.AreEqual(result, value.TrimEnd(trimParameter, StringComparison.OrdinalIgnoreCase));
+ }
+}
diff --git a/src/Utils/Extensions/Tests/Masa.Utils.Extensions.DotNet.Tests/_Imports.cs b/src/Utils/Extensions/Tests/Masa.Utils.Extensions.DotNet.Tests/_Imports.cs
new file mode 100644
index 000000000..3004a9f37
--- /dev/null
+++ b/src/Utils/Extensions/Tests/Masa.Utils.Extensions.DotNet.Tests/_Imports.cs
@@ -0,0 +1,4 @@
+// Copyright (c) MASA Stack All rights reserved.
+// Licensed under the MIT License. See LICENSE.txt in the project root for license information.
+
+global using Microsoft.VisualStudio.TestTools.UnitTesting;