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

fix: [DI] Resolve IMessageInspector2 Using Scope Resolver #982

Closed
81 changes: 60 additions & 21 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,7 +178,7 @@ 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;
});
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
Loading