Skip to content

Commit

Permalink
Merge branch 'develop' into fix/imessageinspector1-di-scole-resolver
Browse files Browse the repository at this point in the history
  • Loading branch information
ankitkmrpatel committed Jan 12, 2024
2 parents 58afbb9 + bf1a394 commit 0266440
Show file tree
Hide file tree
Showing 22 changed files with 438 additions and 120 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/codeql-analysis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ jobs:
- name: Setup .NET
uses: actions/setup-dotnet@v1
with:
dotnet-version: '6.0.x'
dotnet-version: '8.0.x'

- name: Build with .NET
run: dotnet build src/SoapCore.sln --configuration Release
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/continuous-integration-workflow.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ jobs:
steps:
- uses: actions/checkout@v1
- name: prepare deps
run: apk add bash icu-libs krb5-libs libgcc libintl libssl1.1 libstdc++ zlib curl
run: apk add bash icu-libs krb5-libs libgcc libintl libssl3 libstdc++ zlib curl
- name: install sdks
run: cd /tmp && curl -L https://dot.net/v1/dotnet-install.sh --output dotnet-install.sh && chmod +x dotnet-install.sh && ./dotnet-install.sh -c 5.0 && ./dotnet-install.sh -c 3.1 && ./dotnet-install.sh -c 6.0 && ln -s /github/home/.dotnet/dotnet /usr/bin/dotnet
run: cd /tmp && curl -L https://dot.net/v1/dotnet-install.sh --output dotnet-install.sh && chmod +x dotnet-install.sh && ./dotnet-install.sh -c 7.0 && ./dotnet-install.sh -c 8.0 && ln -s /github/home/.dotnet/dotnet /usr/bin/dotnet
- name: build && test
run: dotnet test src/SoapCore.Tests
run: dotnet test src/SoapCore.Tests
2 changes: 1 addition & 1 deletion .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ jobs:
runs-on: ubuntu-latest

container:
image: docker://mcr.microsoft.com/dotnet/sdk:6.0-alpine
image: docker://mcr.microsoft.com/dotnet/sdk:8.0-alpine
steps:

- uses: actions/checkout@v1
Expand Down
83 changes: 61 additions & 22 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ There are 2 different ways of adding SoapCore to your ASP.NET Core website. If y

In Startup.cs:


```csharp
public void ConfigureServices(IServiceCollection services)
{
Expand All @@ -42,7 +41,7 @@ public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerF
app.UseEndpoints(endpoints => {
endpoints.UseSoapEndpoint<ServiceContractImpl>("/ServicePath.asmx", new SoapEncoderOptions(), SoapSerializer.DataContractSerializer);
});

}
```

Expand All @@ -61,6 +60,37 @@ public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerF
}
```

### Using with custom implementation of Serialization

There is an optional feature included where you can implment the ISoapCoreSerializer to built your own custom serializar for body.

In Startup.cs:

```csharp
public void ConfigureServices(IServiceCollection services)
{
...
services.AddSoapCore();
services.TryAddSingleton<ServiceContractImpl>();
services.AddCustomSoapMessageSerializer<CustomeBodyMessageSerializerImpl>(); //Add Your Custom Implementation or Extend Default Serializer
services.AddMvc();
...
}

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
app.UseSoapEndpoint<ServiceContractImpl>(soapCoreOptions =>
{
soapCoreOptions.Path = "/ServicePath.asmx";
soapCoreOptions.UseCustomSerializer<CustomeBodyMessageSerializerImpl>(); //Specify the Service to Use Service Soap Message Serializer
soapCoreOptions.SoapSerializer = SoapSerializer.DataContractSerializer;
...
});
}

```

### Using with legacy WCF/WS

It is possible to use SoapCore with .NET legacy WCF and Web Services, both as client and service.
Expand All @@ -87,21 +117,19 @@ To use it, add a setting like this to appsettings
}
```

* UrlOverride - can be used to override the URL in the service description. This can be useful if you are behind a firewall.
* VirualPath - can be used if you like to add a path between the base URL and service.
* WebServiceWSDLMapping
* UrlOverride - can be used to override the URL for a specific WSDL mapping. This can be useful if you want to host different services under different folder.
* Service.asmx - is the endpoint of the service you expose. You can have more than one.
* WsdlFile - is the name of the WSDL on disc.
* SchemaFolder - if you import XSD from WSDL, this is the folder where the Schemas are stored on disc.
* WsdlFolder - is the folder that the WSDL file is stored on disc.

- UrlOverride - can be used to override the URL in the service description. This can be useful if you are behind a firewall.
- VirualPath - can be used if you like to add a path between the base URL and service.
- WebServiceWSDLMapping
- UrlOverride - can be used to override the URL for a specific WSDL mapping. This can be useful if you want to host different services under different folder.
- Service.asmx - is the endpoint of the service you expose. You can have more than one.
- WsdlFile - is the name of the WSDL on disc.
- SchemaFolder - if you import XSD from WSDL, this is the folder where the Schemas are stored on disc.
- WsdlFolder - is the folder that the WSDL file is stored on disc.

To read the setting you can do the following

In Startup.cs:


```csharp
var settings = Configuration.GetSection("FileWSDL").Get<WsdlFileOptions>();

Expand All @@ -118,21 +146,23 @@ If the WsdFileOptions parameter is supplied then this feature is enabled / used.

### References

* [stackify.com/soap-net-core](https://stackify.com/soap-net-core/)
- [stackify.com/soap-net-core](https://stackify.com/soap-net-core/)
### Tips and Tricks

#### Extending the pipeline

In your ConfigureServices method, you can register some additional items to extend the pipeline:
* services.AddSoapMessageInspector() - add a custom MessageInspector. This function is similar to the `IDispatchMessageInspector` in WCF. The newer `IMessageInspector2` interface allows you to register multiple inspectors, and to know which service was being called.
* services.AddSingleton<MyOperatorInvoker>() - add a custom OperationInvoker. Similar to WCF's `IOperationInvoker` this allows you to override the invoking of a service operation, commonly to add custom logging or exception handling logic around it.
* services.AddSoapMessageProcessor() - add a custom SoapMessageProcessor. Similar to ASP.NET Cores middlewares, this allows you to inspect the message on the way in and out. You can also short-circuit the message processing and return your own custom message instead. Inspecting and modifying HttpContext is also possible

- services.AddSoapMessageInspector() - add a custom MessageInspector. This function is similar to the `IDispatchMessageInspector` in WCF. The newer `IMessageInspector2` interface allows you to register multiple inspectors, and to know which service was being called.
- services.AddSingleton<MyOperatorInvoker>() - add a custom OperationInvoker. Similar to WCF's `IOperationInvoker` this allows you to override the invoking of a service operation, commonly to add custom logging or exception handling logic around it.
- services.AddSoapMessageProcessor() - add a custom SoapMessageProcessor. Similar to ASP.NET Cores middlewares, this allows you to inspect the message on the way in and out. You can also short-circuit the message processing and return your own custom message instead. Inspecting and modifying HttpContext is also possible

#### Using ISoapMessageProcessor()

```csharp
//Add this to ConfigureServices in Startup.cs
services.AddSoapMessageProcessor(async (message, httpcontext, next) =>
{
var bufferedMessage = message.CreateBufferedCopy(int.MaxValue);
Expand All @@ -148,10 +178,10 @@ services.AddSoapMessageProcessor(async (message, httpcontext, next) =>
var responseMessage = await next(message);

//Inspect and modify the contents of returnMessage in the same way as the incoming message.
//finish by returning the modified message.
//finish by returning the modified message.
return responseMessage;
}
});
```

#### How to get custom HTTP header in SoapCore service
Expand All @@ -160,9 +190,10 @@ Use interface IServiceOperationTuner to tune each operation call.

Create class that implements IServiceOperationTuner.
Parameters in Tune method:
* httpContext - current HttpContext. Can be used to get http headers or body.
* serviceInstance - instance of your service.
* operation - information about called operation.

- httpContext - current HttpContext. Can be used to get http headers or body.
- serviceInstance - instance of your service.
- operation - information about called operation.

```csharp
public class MyServiceOperationTuner : IServiceOperationTuner
Expand Down Expand Up @@ -222,8 +253,11 @@ public class MyService : IMyServiceService
}
}
```

#### Additional namespace declaration attributes in envelope

Adding additional namespaces to the **SOAP Envelope** can be done by populating `SoapEncoderOptions.AdditionalEnvelopeXmlnsAttributes` parameter.

```csharp
....
endpoints.UseSoapEndpoint<IService>(opt =>
Expand All @@ -237,7 +271,9 @@ endpoints.UseSoapEndpoint<IService>(opt =>
});
...
```

This code will put `xmlns:myNS="...` and `xmlns:arr="...` attributes in `Envelope` and message will look like:

```xml
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" ... xmlns:myNS="http://schemas.someting.org" xmlns:arr="http://schemas.microsoft.com/2003/10/Serialization/Arrays">
...
Expand All @@ -247,7 +283,9 @@ This code will put `xmlns:myNS="...` and `xmlns:arr="...` attributes in `Envelop
</fin:StringList>
...
```

instead of:

```xml
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" ... >
...
Expand All @@ -263,6 +301,7 @@ instead of:
See [Contributing guide](CONTRIBUTING.md)

### Contributors

<a href="https://github.com/digdes/soapcore/graphs/contributors">
<img src="https://contributors-img.web.app/image?repo=digdes/soapcore" />
</a>
Expand Down
4 changes: 2 additions & 2 deletions samples/Sample.sln
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.30717.126
# Visual Studio Version 17
VisualStudioVersion = 17.7.34031.279
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Client", "Client\Client.csproj", "{EBFDF95D-F7CC-457F-8924-3CB6069FBA3B}"
EndProject
Expand Down
4 changes: 2 additions & 2 deletions src/SoapCore.Tests/InvalidXMLTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ public async Task MissingNamespace()
SoapSerializer = SoapSerializer.DataContractSerializer
};

var soapCore = new SoapEndpointMiddleware<CustomMessage>(logger, (innerContext) => Task.CompletedTask, options);
var soapCore = new SoapEndpointMiddleware<CustomMessage>(logger, (innerContext) => Task.CompletedTask, options, serviceCollection.BuildServiceProvider());

var context = new DefaultHttpContext();
context.Request.Path = new PathString("/Service.svc");
Expand All @@ -63,7 +63,7 @@ public async Task MissingNamespace()
context.Request.Body = new MemoryStream(Encoding.UTF8.GetBytes(request), false);
context.Request.ContentType = "text/xml; charset=utf-8";

await soapCore.Invoke(context, serviceCollection.BuildServiceProvider());
await soapCore.Invoke(context);

// Assert
Assert.IsTrue(context.Response.Body.Length > 0);
Expand Down
150 changes: 150 additions & 0 deletions src/SoapCore.Tests/SerializerProviderTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.Text;
using System.Threading.Tasks;
using System.Xml;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using SoapCore.Serializer;

namespace SoapCore.Tests
{
[TestClass]
public class SerializerProviderTests
{
[Timeout(500)]
[TestMethod]
public async Task UsingDefaultSerializer()
{
// Arrange
var logger = NullLoggerFactory.Instance.CreateLogger<SoapEndpointMiddleware<CustomMessage>>();

var serviceCollection = new ServiceCollection();
serviceCollection.AddSingleton<DenialOfServiceProofOfConcept>();

var options = new SoapOptions()
{
Path = "/Service.svc",
EncoderOptions = new[]
{
new SoapEncoderOptions
{
MessageVersion = MessageVersion.Soap11,
WriteEncoding = Encoding.UTF8,
ReaderQuotas = XmlDictionaryReaderQuotas.Max
}
},
ServiceType = typeof(DenialOfServiceProofOfConcept),
SoapModelBounder = new MockModelBounder(),
SoapSerializer = SoapSerializer.DataContractSerializer
};

var soapCore = new SoapEndpointMiddleware<CustomMessage>(logger, (innerContext) => Task.CompletedTask, options, serviceCollection.BuildServiceProvider());

var context = new DefaultHttpContext();
context.Request.Path = new PathString("/Service.svc");
context.Request.Method = "POST";
context.Response.Body = new MemoryStream();

// Act
var request = @"<?xml version=""1.0"" encoding=""utf-8"" ?>
<soap:Envelope xmlns:xsi=""http://www.w3.org/2001/XMLSchema-instance"" xmlns:xsd=""http://www.w3.org/2001/XMLSchema"" xmlns:soapenc=""http://schemas.xmlsoap.org/soap/encoding/"" xmlns:tns=""https://dos.brianfeucht.com/"" xmlns:types=""https://dos.brianfeucht.com/encodedTypes"" xmlns:soap=""http://schemas.xmlsoap.org/soap/envelope/"">
<soap:Body soap:encodingStyle=""http://schemas.xmlsoap.org/soap/encoding/"">
<tns:SpinTheThread>
<a xsi:type=""xsd:string"">a</a>
<b xsi:type=""xsd:string"">b</b>
</tns:SpinTheThread>
</soap:Body>
</soap:Envelope>";
context.Request.Body = new MemoryStream(Encoding.UTF8.GetBytes(request), false);
context.Request.ContentType = "text/xml; charset=utf-8";

await soapCore.Invoke(context);

// Assert
Assert.IsTrue(context.Response.Body.Length > 0);
}

[Timeout(500)]
[TestMethod]
public async Task UsingCustomXMLSerializer()
{
// Arrange
var logger = NullLoggerFactory.Instance.CreateLogger<SoapEndpointMiddleware<CustomMessage>>();

var serviceCollection = new ServiceCollection();
serviceCollection.AddSoapCore();
serviceCollection.AddSingleton<DenialOfServiceProofOfConcept>();
serviceCollection.AddCustomSoapMessageSerializer<DenialOfServiceProofOfConceptRequestSerializer>();

var options = new SoapOptions()
{
Path = "/Service.svc",
EncoderOptions = new[]
{
new SoapEncoderOptions
{
MessageVersion = MessageVersion.Soap11,
WriteEncoding = Encoding.UTF8,
ReaderQuotas = XmlDictionaryReaderQuotas.Max
}
},
ServiceType = typeof(DenialOfServiceProofOfConcept),
SoapModelBounder = new MockModelBounder(),
SoapSerializer = SoapSerializer.DataContractSerializer
};

var soapCore = new SoapEndpointMiddleware<CustomMessage>(logger, (innerContext) => Task.CompletedTask, options, serviceCollection.BuildServiceProvider());

var context = new DefaultHttpContext();
context.Request.Path = new PathString("/Service.svc");
context.Request.Method = "POST";
context.Response.Body = new MemoryStream();

// Act
var request = @"<?xml version=""1.0"" encoding=""utf-8"" ?>
<soap:Envelope xmlns:xsi=""http://www.w3.org/2001/XMLSchema-instance"" xmlns:xsd=""http://www.w3.org/2001/XMLSchema"" xmlns:soapenc=""http://schemas.xmlsoap.org/soap/encoding/"" xmlns:tns=""https://dos.brianfeucht.com/"" xmlns:types=""https://dos.brianfeucht.com/encodedTypes"" xmlns:soap=""http://schemas.xmlsoap.org/soap/envelope/"">
<soap:Body soap:encodingStyle=""http://schemas.xmlsoap.org/soap/encoding/"">
<tns:SpinTheThread>
<a xsi:type=""xsd:string"">a</a>
<b xsi:type=""xsd:string"">b</b>
</tns:SpinTheThread>
</soap:Body>
</soap:Envelope>";
context.Request.Body = new MemoryStream(Encoding.UTF8.GetBytes(request), false);
context.Request.ContentType = "text/xml; charset=utf-8";

await soapCore.Invoke(context);

// Assert
Assert.IsTrue(context.Response.Body.Length > 0);
}

[ServiceContract(Namespace = "https://dos.brianfeucht.com/")]
public class DenialOfServiceProofOfConcept
{
[OperationContract]
public Task<string> SpinTheThread(string a, string b)
{
return Task.FromResult("Hello World");
}
}

public class DenialOfServiceProofOfConceptRequestSerializer : ISoapCoreSerializer
{
public object DeserializeInputParameter(XmlDictionaryReader xmlReader, Type parameterType, string parameterName, string parameterNs, ICustomAttributeProvider customAttributeProvider, IEnumerable<Type> knownTypes = null)
{
var serializer = new DataContractSerializer(parameterType, parameterName, parameterNs, (IEnumerable<Type>)knownTypes);
return serializer.ReadObject(xmlReader, true);
}
}
}
}
Loading

0 comments on commit 0266440

Please sign in to comment.