Skip to content

Commit

Permalink
Merge pull request #1017 from vidrenning/soapaction
Browse files Browse the repository at this point in the history
Add option to generate soapAction without contract name
  • Loading branch information
andersjonsson authored Feb 14, 2024
2 parents 22ecb5c + aa57065 commit ad81c5b
Show file tree
Hide file tree
Showing 10 changed files with 85 additions and 36 deletions.
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
Expand Up @@ -136,5 +136,12 @@ public class SoapCoreOptions
/// 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
Expand Up @@ -70,6 +70,8 @@ public class SoapOptions
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 @@ public static SoapOptions FromSoapCoreOptions(SoapCoreOptions opt, Type serviceT
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

0 comments on commit ad81c5b

Please sign in to comment.