Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add option to generate soapAction without contract name #1017

Merged
merged 1 commit into from
Feb 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -11,89 +11,89 @@ public class OperationDescriptionTests
[Fact]
public void TestProperUnrappingOfGenericResponses()
{
ServiceDescription serviceDescription = new ServiceDescription(typeof(IServiceWithMessageContract));
ContractDescription contractDescription = new ContractDescription(serviceDescription, typeof(IServiceWithMessageContract), new ServiceContractAttribute());
ServiceDescription serviceDescription = new ServiceDescription(typeof(IServiceWithMessageContract), false);
ContractDescription contractDescription = new ContractDescription(serviceDescription, typeof(IServiceWithMessageContract), new ServiceContractAttribute(), false);

System.Reflection.MethodInfo method = typeof(IServiceWithMessageContract).GetMethod(nameof(IServiceWithMessageContract.GetMyClass));

OperationContractAttribute contractAttribute = new OperationContractAttribute();

ServiceModel.OperationDescription operationDescription = new ServiceModel.OperationDescription(contractDescription, method, contractAttribute);
ServiceModel.OperationDescription operationDescription = new ServiceModel.OperationDescription(contractDescription, method, contractAttribute, false);

Assert.True(operationDescription.IsMessageContractResponse);
}

[Fact]
public void TestSupportXmlRootForParameterName()
{
ServiceDescription serviceDescription = new ServiceDescription(typeof(IServiceWithMessageContract));
ContractDescription contractDescription = new ContractDescription(serviceDescription, typeof(IServiceWithMessageContract), new ServiceContractAttribute());
ServiceDescription serviceDescription = new ServiceDescription(typeof(IServiceWithMessageContract), false);
ContractDescription contractDescription = new ContractDescription(serviceDescription, typeof(IServiceWithMessageContract), new ServiceContractAttribute(), false);

System.Reflection.MethodInfo method = typeof(IServiceWithMessageContract).GetMethod(nameof(IServiceWithMessageContract.GetClassWithXmlRoot));

OperationContractAttribute contractAttribute = new OperationContractAttribute();

ServiceModel.OperationDescription operationDescription = new ServiceModel.OperationDescription(contractDescription, method, contractAttribute);
ServiceModel.OperationDescription operationDescription = new ServiceModel.OperationDescription(contractDescription, method, contractAttribute, false);

Assert.Equal("test", operationDescription.AllParameters.FirstOrDefault()?.Name);
}

[Fact]
public void TestSupportXmlRootForParameterNameWithEmptyStringAsRootElementNameUsesParameterInfoToExtractAName()
{
ServiceDescription serviceDescription = new ServiceDescription(typeof(IServiceWithMessageContractAndEmptyXmlRoot));
ContractDescription contractDescription = new ContractDescription(serviceDescription, typeof(IServiceWithMessageContractAndEmptyXmlRoot), new ServiceContractAttribute());
ServiceDescription serviceDescription = new ServiceDescription(typeof(IServiceWithMessageContractAndEmptyXmlRoot), false);
ContractDescription contractDescription = new ContractDescription(serviceDescription, typeof(IServiceWithMessageContractAndEmptyXmlRoot), new ServiceContractAttribute(), false);

System.Reflection.MethodInfo method = typeof(IServiceWithMessageContractAndEmptyXmlRoot).GetMethod(nameof(IServiceWithMessageContractAndEmptyXmlRoot.GetClassWithEmptyXmlRoot));

OperationContractAttribute contractAttribute = new OperationContractAttribute();

ServiceModel.OperationDescription operationDescription = new ServiceModel.OperationDescription(contractDescription, method, contractAttribute);
ServiceModel.OperationDescription operationDescription = new ServiceModel.OperationDescription(contractDescription, method, contractAttribute, false);

Assert.Equal("classWithXmlRoot", operationDescription.AllParameters.FirstOrDefault()?.Name);
}

[Fact]
public void TestProperUnrappingOfNonGenericResponses()
{
ServiceDescription serviceDescription = new ServiceDescription(typeof(IServiceWithMessageContract));
ContractDescription contractDescription = new ContractDescription(serviceDescription, typeof(IServiceWithMessageContract), new ServiceContractAttribute());
ServiceDescription serviceDescription = new ServiceDescription(typeof(IServiceWithMessageContract), false);
ContractDescription contractDescription = new ContractDescription(serviceDescription, typeof(IServiceWithMessageContract), new ServiceContractAttribute(), false);

System.Reflection.MethodInfo method = typeof(IServiceWithMessageContract).GetMethod(nameof(IServiceWithMessageContract.GetMyOtherClass));

OperationContractAttribute contractAttribute = new OperationContractAttribute();

ServiceModel.OperationDescription operationDescription = new ServiceModel.OperationDescription(contractDescription, method, contractAttribute);
ServiceModel.OperationDescription operationDescription = new ServiceModel.OperationDescription(contractDescription, method, contractAttribute, false);

Assert.True(operationDescription.IsMessageContractResponse);
}

[Fact]
public void TestProperUnrappingOfNonMessageContractResponses()
{
ServiceDescription serviceDescription = new ServiceDescription(typeof(IServiceWithMessageContract));
ContractDescription contractDescription = new ContractDescription(serviceDescription, typeof(IServiceWithMessageContract), new ServiceContractAttribute());
ServiceDescription serviceDescription = new ServiceDescription(typeof(IServiceWithMessageContract), false);
ContractDescription contractDescription = new ContractDescription(serviceDescription, typeof(IServiceWithMessageContract), new ServiceContractAttribute(), false);

System.Reflection.MethodInfo method = typeof(IServiceWithMessageContract).GetMethod(nameof(IServiceWithMessageContract.GetMyStringClass));

OperationContractAttribute contractAttribute = new OperationContractAttribute();

ServiceModel.OperationDescription operationDescription = new ServiceModel.OperationDescription(contractDescription, method, contractAttribute);
ServiceModel.OperationDescription operationDescription = new ServiceModel.OperationDescription(contractDescription, method, contractAttribute, false);

Assert.False(operationDescription.IsMessageContractResponse);
}

[Fact]
public void TestProperUnwrappingOfSoapFaults()
{
ServiceDescription serviceDescription = new ServiceDescription(typeof(IServiceWithMessageContract));
ContractDescription contractDescription = new ContractDescription(serviceDescription, typeof(IServiceWithMessageContract), new ServiceContractAttribute());
ServiceDescription serviceDescription = new ServiceDescription(typeof(IServiceWithMessageContract), false);
ContractDescription contractDescription = new ContractDescription(serviceDescription, typeof(IServiceWithMessageContract), new ServiceContractAttribute(), false);

System.Reflection.MethodInfo method = typeof(IServiceWithMessageContract).GetMethod(nameof(IServiceWithMessageContract.ThrowTypedFault));

OperationContractAttribute contractAttribute = new OperationContractAttribute();

ServiceModel.OperationDescription operationDescription = new ServiceModel.OperationDescription(contractDescription, method, contractAttribute);
ServiceModel.OperationDescription operationDescription = new ServiceModel.OperationDescription(contractDescription, method, contractAttribute, false);

var faultInfo = Assert.Single(operationDescription.Faults);
Assert.Equal("TypedSoapFault", faultInfo.Name);
Expand All @@ -106,17 +106,36 @@ public void TestProperUnwrappingOfSoapFaults()
[Fact]
public void TestProperNamingOfAsyncMethods()
{
ServiceDescription serviceDescription = new ServiceDescription(typeof(IServiceWithMessageContract));
ContractDescription contractDescription = new ContractDescription(serviceDescription, typeof(IServiceWithMessageContract), new ServiceContractAttribute());
ServiceDescription serviceDescription = new ServiceDescription(typeof(IServiceWithMessageContract), false);
ContractDescription contractDescription = new ContractDescription(serviceDescription, typeof(IServiceWithMessageContract), new ServiceContractAttribute(), false);

System.Reflection.MethodInfo method = typeof(IServiceWithMessageContract).GetMethod(nameof(IServiceWithMessageContract.GetMyAsyncClassAsync));

OperationContractAttribute contractAttribute = new OperationContractAttribute();

ServiceModel.OperationDescription operationDescription = new ServiceModel.OperationDescription(contractDescription, method, contractAttribute);
ServiceModel.OperationDescription operationDescription = new ServiceModel.OperationDescription(contractDescription, method, contractAttribute, false);

Assert.True(operationDescription.IsMessageContractResponse);
Assert.Equal("GetMyAsyncClass", operationDescription.Name);
}

[Fact]
public void TestGeneratedSoapActionName()
{
ServiceDescription serviceDescription = new ServiceDescription(typeof(IServiceWithMessageContract), true);
ContractDescription contractDescription = new ContractDescription(serviceDescription, typeof(IServiceWithMessageContract), new ServiceContractAttribute(), true);

System.Reflection.MethodInfo method = typeof(IServiceWithMessageContract).GetMethod(nameof(IServiceWithMessageContract.GetMyOtherClass));

OperationContractAttribute contractAttribute = new OperationContractAttribute();

ServiceModel.OperationDescription operationDescription1 = new ServiceModel.OperationDescription(contractDescription, method, contractAttribute, true);

Assert.Equal("http://tempuri.org/GetMyOtherClass", operationDescription1.SoapAction);

ServiceModel.OperationDescription operationDescription2 = new ServiceModel.OperationDescription(contractDescription, method, contractAttribute, false);

Assert.Equal("http://tempuri.org/IServiceWithMessageContract/GetMyOtherClass", operationDescription2.SoapAction);
}
}
}
4 changes: 2 additions & 2 deletions src/SoapCore.Tests/ServiceDescription/ServiceContractTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,15 @@ public class ServiceContractTests
[Fact]
public void TestFallbackOfServiceNameToTypeName()
{
ServiceDescription serviceDescription = new ServiceDescription(typeof(IServiceWithoutName));
ServiceDescription serviceDescription = new ServiceDescription(typeof(IServiceWithoutName), false);

Assert.Equal("IServiceWithoutName", serviceDescription.ServiceName);
}

[Fact]
public void TestExplicitlySetServiceName()
{
ServiceDescription serviceDescription = new ServiceDescription(typeof(IServiceWithName));
ServiceDescription serviceDescription = new ServiceDescription(typeof(IServiceWithName), false);

Assert.Equal("MyServiceWithName", serviceDescription.ServiceName);
}
Expand Down
2 changes: 1 addition & 1 deletion src/SoapCore.Tests/Wsdl/WsdlTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1156,7 +1156,7 @@ private string GetWsdlFromAsmx()

private async Task<string> GetWsdlFromMetaBodyWriter<T>(SoapSerializer serializer, string bindingName = null, string portName = null, bool useMicrosoftGuid = false)
{
var service = new ServiceDescription(typeof(T));
var service = new ServiceDescription(typeof(T), false);
var baseUrl = "http://tempuri.org/";
var xmlNamespaceManager = Namespaces.CreateDefaultXmlNamespaceManager(useMicrosoftGuid);
var defaultBindingName = !string.IsNullOrWhiteSpace(bindingName) ? bindingName : "BasicHttpBinding";
Expand Down
15 changes: 11 additions & 4 deletions src/SoapCore/Extensibility/AuthorizeOperationMessageProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,20 @@ public class AuthorizeOperationMessageProcessor : ISoapMessageProcessor
/// </summary>
private readonly Dictionary<string, Type> _pathTypes;

/// <summary>
/// Whether to generate the soapAction without the contract name. This is also specified in the SoapOptions.
/// </summary>
private readonly bool _generateSoapActionWithoutContractName;

/// <summary>
/// Initializes a new instance of the <see cref="AuthorizeOperationMessageProcessor"/> class.
/// </summary>
/// <param name="pathAndTypes">A dictionary that has the path of the endpoint as Key and the corresponding type as Value. Similar to using the UseSoapEndpoint extension function.</param>
public AuthorizeOperationMessageProcessor(Dictionary<string, Type> pathAndTypes)
/// <param name="generateSoapActionWithoutContractName">Whether to generate the soapAction without the contract name. Default is false.</param>
public AuthorizeOperationMessageProcessor(Dictionary<string, Type> pathAndTypes, bool generateSoapActionWithoutContractName = false)
{
_pathTypes = pathAndTypes;
_generateSoapActionWithoutContractName = generateSoapActionWithoutContractName;
}

public async Task<Message> ProcessMessage(Message requestMessage, HttpContext httpContext, Func<Message, Task<Message>> next)
Expand All @@ -43,7 +50,7 @@ public async Task<Message> ProcessMessage(Message requestMessage, HttpContext ht
throw new ArgumentException("Unable to handle request without a valid action parameter. Please supply a valid soap action.");
}

if (!_pathTypes.TryGetValue(httpContext.Request.Path.Value.ToLowerInvariant(), out Type serviceType) || !TryGetOperation(soapAction, serviceType, out var operation))
if (!_pathTypes.TryGetValue(httpContext.Request.Path.Value.ToLowerInvariant(), out Type serviceType) || !TryGetOperation(soapAction, serviceType, _generateSoapActionWithoutContractName, out var operation))
{
throw new InvalidOperationException($"No operation found for specified action: {soapAction}");
}
Expand Down Expand Up @@ -141,9 +148,9 @@ public async Task<Message> ProcessMessage(Message requestMessage, HttpContext ht
}
}

private static bool TryGetOperation(string methodName, Type serviceType, out OperationDescription operation)
private static bool TryGetOperation(string methodName, Type serviceType, bool generateSoapActionWithoutContractName, out OperationDescription operation)
{
var service = new ServiceDescription(serviceType);
var service = new ServiceDescription(serviceType, generateSoapActionWithoutContractName);
operation = service.Operations.FirstOrDefault(o => o.SoapAction.Equals(methodName, StringComparison.Ordinal)
|| o.Name.Equals(HeadersHelper.GetTrimmedSoapAction(methodName), StringComparison.Ordinal)
|| methodName.Equals(HeadersHelper.GetTrimmedSoapAction(o.Name), StringComparison.Ordinal));
Expand Down
4 changes: 2 additions & 2 deletions src/SoapCore/ServiceModel/ContractDescription.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ namespace SoapCore.ServiceModel
{
public class ContractDescription
{
public ContractDescription(ServiceDescription service, Type contractType, ServiceContractAttribute attribute)
public ContractDescription(ServiceDescription service, Type contractType, ServiceContractAttribute attribute, bool generateSoapActionWithoutContractName)
{
Service = service;
ContractType = contractType;
Expand All @@ -20,7 +20,7 @@ public ContractDescription(ServiceDescription service, Type contractType, Servic
{
foreach (var operationContract in operationMethodInfo.GetCustomAttributes<OperationContractAttribute>())
{
operations.Add(new OperationDescription(this, operationMethodInfo, operationContract));
operations.Add(new OperationDescription(this, operationMethodInfo, operationContract, generateSoapActionWithoutContractName));
}
}

Expand Down
17 changes: 15 additions & 2 deletions src/SoapCore/ServiceModel/OperationDescription.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,24 @@ namespace SoapCore.ServiceModel
{
public class OperationDescription
{
public OperationDescription(ContractDescription contract, MethodInfo operationMethod, OperationContractAttribute contractAttribute)
public OperationDescription(ContractDescription contract, MethodInfo operationMethod, OperationContractAttribute contractAttribute, bool generateSoapActionWithoutContractName)
{
Contract = contract;
Name = contractAttribute.Name ?? GetNameByAction(contractAttribute.Action) ?? GetNameByMethod(operationMethod);
SoapAction = contractAttribute.Action ?? $"{contract.Namespace.TrimEnd('/')}/{contract.Name}/{Name}";

if (contractAttribute.Action != null)
{
SoapAction = contractAttribute.Action;
}
else if (generateSoapActionWithoutContractName)
{
SoapAction = $"{contract.Namespace.TrimEnd('/')}/{Name}";
}
else
{
SoapAction = $"{contract.Namespace.TrimEnd('/')}/{contract.Name}/{Name}";
}

IsOneWay = contractAttribute.IsOneWay;
DispatchMethod = operationMethod;

Expand Down
4 changes: 2 additions & 2 deletions src/SoapCore/ServiceModel/ServiceDescription.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ namespace SoapCore.ServiceModel
{
public class ServiceDescription
{
public ServiceDescription(Type serviceType)
public ServiceDescription(Type serviceType, bool generateSoapActionWithoutContractName)
{
ServiceType = serviceType;
ServiceKnownTypes = serviceType.GetCustomAttributes<ServiceKnownTypeAttribute>(inherit: false);
Expand All @@ -21,7 +21,7 @@ public ServiceDescription(Type serviceType)
{
foreach (var serviceContract in contractType.GetTypeInfo().GetCustomAttributes<ServiceContractAttribute>())
{
var contractDescription = new ContractDescription(this, contractType, serviceContract);
var contractDescription = new ContractDescription(this, contractType, serviceContract, generateSoapActionWithoutContractName);

contracts.Add(contractDescription);

Expand Down
7 changes: 7 additions & 0 deletions src/SoapCore/SoapCoreOptions.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
using SoapCore.Extensibility;
using System;

Check warning on line 2 in src/SoapCore/SoapCoreOptions.cs

View workflow job for this annotation

GitHub Actions / Analyze

Using directive for 'System' should appear before directive for 'SoapCore.Extensibility' (https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1208.md)
using System.Collections.Generic;

Check warning on line 3 in src/SoapCore/SoapCoreOptions.cs

View workflow job for this annotation

GitHub Actions / Analyze

Using directive for 'System.Collections.Generic' should appear before directive for 'SoapCore.Extensibility' (https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1208.md)
using System.ServiceModel.Channels;

Check warning on line 4 in src/SoapCore/SoapCoreOptions.cs

View workflow job for this annotation

GitHub Actions / Analyze

Using directive for 'System.ServiceModel.Channels' should appear before directive for 'SoapCore.Extensibility' (https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1208.md)
using System.Xml;

Check warning on line 5 in src/SoapCore/SoapCoreOptions.cs

View workflow job for this annotation

GitHub Actions / Analyze

Using directive for 'System.Xml' should appear before directive for 'SoapCore.Extensibility' (https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1208.md)

namespace SoapCore
{
Expand Down Expand Up @@ -136,5 +136,12 @@
/// Sets additional namespace declaration attributes in envelope
/// </summary>
public Dictionary<string, string> AdditionalEnvelopeXmlnsAttributes { get; set; }

/// <summary>
/// By default, the soapAction that is generated if not explicitely specified is
/// {namespace}/{contractName}/{methodName}. If set to true, the service name will
/// be omitted, so that the soapAction will be {namespace}/{methodName}.
/// </summary>
public bool GenerateSoapActionWithoutContractName { get; set; } = false;
}
}
2 changes: 1 addition & 1 deletion src/SoapCore/SoapEndpointMiddleware.cs
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ public SoapEndpointMiddleware(ILogger<SoapEndpointMiddleware<T_MESSAGE>> logger,

_serializerHelper = new SerializerHelper(options.SoapSerializer);
_pathComparisonStrategy = options.CaseInsensitivePath ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal;
_service = new ServiceDescription(options.ServiceType);
_service = new ServiceDescription(options.ServiceType, options.GenerateSoapActionWithoutContractName);

if (options.EncoderOptions is null)
{
Expand Down
5 changes: 4 additions & 1 deletion src/SoapCore/SoapOptions.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
using SoapCore.Extensibility;
using SoapCore.Meta;
using System;

Check warning on line 3 in src/SoapCore/SoapOptions.cs

View workflow job for this annotation

GitHub Actions / Analyze

Using directive for 'System' should appear before directive for 'SoapCore.Meta' (https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1208.md)
using System.Collections.Generic;
using System.ServiceModel.Channels;
using System.Xml;
Expand Down Expand Up @@ -70,6 +70,8 @@
public WsdlFileOptions WsdlFileOptions { get; set; }
public Dictionary<string, string> AdditionalEnvelopeXmlnsAttributes { get; set; }

public bool GenerateSoapActionWithoutContractName { get; set; } = false;

[Obsolete]
public static SoapOptions FromSoapCoreOptions<T>(SoapCoreOptions opt)
{
Expand Down Expand Up @@ -99,7 +101,8 @@
WsdlFileOptions = opt.WsdlFileOptions,
AdditionalEnvelopeXmlnsAttributes = opt.AdditionalEnvelopeXmlnsAttributes,
CheckXmlCharacters = opt.CheckXmlCharacters,
UseMicrosoftGuid = opt.UseMicrosoftGuid
UseMicrosoftGuid = opt.UseMicrosoftGuid,
GenerateSoapActionWithoutContractName = opt.GenerateSoapActionWithoutContractName,
};

#pragma warning disable CS0612 // Type or member is obsolete
Expand Down
Loading