I have written a dynamic web service client. That works with SOAP 1.1 but fails with SOAP 1.2
When I use ServiceDescriptionImporter.Import I get the following warning:
OptionalExtensionsIgnored
Below is the code to prepare the web service:
using (var client = new WebClient())
{
//Trust all certificates
System.Net.ServicePointManager.ServerCertificateValidationCallback = ((sender, certificate, chain, sslPolicyErrors) => true);
client.Credentials = new NetworkCredential(#"domain\user","password");
using (var stream = client.OpenRead(url))
{
// Get a WSDL file describing the service.
ServiceDescription description = ServiceDescription.Read(stream);
// Initialize a service description importer.
ServiceDescriptionImporter importer = new ServiceDescriptionImporter();
importer.ProtocolName = webServiceProtocol.ToString();
importer.Style = ServiceDescriptionImportStyle.Client;
importer.AddServiceDescription(description, null, null);
// Report on the service descriptions.
Console.WriteLine("Importing {0} service descriptions with {1} associated schemas.",
importer.ServiceDescriptions.Count, importer.Schemas.Count);
// Add any imported files
foreach (System.Xml.Schema.XmlSchema wsdlSchema in description.Types.Schemas)
{
foreach (System.Xml.Schema.XmlSchemaObject externalSchema in wsdlSchema.Includes)
{
if (externalSchema is System.Xml.Schema.XmlSchemaImport)
{
Uri baseUri = new Uri(url);
Uri schemaUri = new Uri(baseUri, ((System.Xml.Schema.XmlSchemaExternal)externalSchema).SchemaLocation);
using (var schemaStream = client.OpenRead(schemaUri))
{
System.Xml.Schema.XmlSchema schema = System.Xml.Schema.XmlSchema.Read(schemaStream, null);
importer.Schemas.Add(schema);
}
Console.WriteLine(((System.Xml.Schema.XmlSchemaExternal)externalSchema).SchemaLocation);
}
}
}
// Generate a proxy client.
importer.Style = ServiceDescriptionImportStyle.Client;
// Generate properties to represent primitive values.
importer.CodeGenerationOptions = System.Xml.Serialization.CodeGenerationOptions.GenerateProperties;
// Initialize a Code-DOM tree into which we will import the service.
CodeNamespace nmspace = new CodeNamespace();
CodeCompileUnit unit1 = new CodeCompileUnit();
unit1.Namespaces.Add(nmspace);
// Import the service into the Code-DOM tree. This creates proxy code
// that uses the service.
ServiceDescriptionImportWarnings warning = importer.Import(nmspace, unit1);
Console.WriteLine("Warning: " + warning);
if (warning == 0 || warning == ServiceDescriptionImportWarnings.OptionalExtensionsIgnored)
{
// Generate and print the proxy code in C#.
CodeDomProvider provider1 = CodeDomProvider.CreateProvider("CSharp");
// Compile the assembly with the appropriate references
string[] assemblyReferences = new string[2] { "System.Web.Services.dll", "System.Xml.dll" };
CompilerParameters parms = new CompilerParameters(assemblyReferences);
CompilerResults results = provider1.CompileAssemblyFromDom(parms, unit1);
assembly = results.CompiledAssembly;
foreach (CompilerError oops in results.Errors)
{
Console.WriteLine("========Compiler error============");
Console.WriteLine(oops.ErrorText);
}
}
else
{
// Print an error message.
Console.WriteLine("Warning: " + warning);
}
}
}
If I ignore the warning and compile the code using CodeDomProvider it compiles with no errors. The problem is when I call a method from the web service I then get the following error:
Exception has been thrown by the target of an invocation. SOAP header
Action was not understood.
The code to call the method is below:
//Invoke the web service method
object service = GetAssembly().CreateInstance("BizTalkServiceInstance");
Type serviceType = service.GetType();
PropertyInfo propInfo = serviceType.GetProperty("Credentials");
propInfo.SetValue(service, new NetworkCredential("user", "pass", "domain"), null);
object request = GetObjectFromString(requestName, requestValue);
object response = serviceType.InvokeMember(methodName, System.Reflection.BindingFlags.InvokeMethod, null, service, new object[] { request });
Console.WriteLine(GetValueFromObject(responseName,response));
Console.ReadLine();
return null;
I really cannot work out what I am missing.
It looks like I was doing this the old way. I have switched over to ServiceContractGenerator. This now creates a Service Client object which provides a constructor which allows you to set the binding.
It now throws compile errors on one of my services but at least I have both 1.1 and 1.2 services working.
You can see the code and my problem with the new way of doing it here:
ServiceContractGenerator CodeDomProvider Compile Errors
Related
My goal is to be able to post and retrieve from the endpoint which uses a SOAP based API
structure of my project
I generated a client with the WSDL file to target cucm 11.5, then
I followed the example on github by creating all the classes and interfaces as done on the repo
thirdly, my solution consist of two project a class library and a console project, the class library contains the generated client from the WSDL file and the console project consist of the class and interfaces to interact with the class library project
I have the following class to perform an operation
public class TestAxl
{
public void CreateUsers()
{
var axlClient = new AxlClient(new AxlClientConfiguration
{
Server = "Ip to the publish server",
User = "administrator",
Password = "password provided"
});
var addUserResult = axlClient.ExecuteAsync(async client =>
{
var userId = Guid.NewGuid().ToString();
var request = new AddUserReq
{
user = new XUser
{
userid = userId,
userIdentity = userId,
password = "P#ssw0rd",
firstName = "test",
lastName = "test"
}
};
var response = await client.addUserAsync(request);
return response.addUserResponse1.#return;
});
}
}
and i call it from the main class like so
class Program
{
static void Main(string[] args)
{
var letsDoSomeTesting = new TestAxl();
try
{
letsDoSomeTesting.CreateUsers();
}
catch (Exception e)
{
Console.WriteLine("The following is the exceeption from calling final class ", e.Message);
}
}
}
when i try to run the console project it starts and exit with 0,
then i go back to CUCM sandbox environment and nothing has changed, what could be the possible cause of this operation not working
FYI: Runtime netCore 3.1
I was able to get a sample project together including AXL/addUser, with DotNet Core 3.1 on Linux: https://github.com/CiscoDevNet/axl-dotnet-samples
This is the main section:
// Create a custom binding so we can allow the client to use cookies with AXL
BasicHttpsBinding binding = new BasicHttpsBinding();
binding.AllowCookies = true;
// Specify the CUCM AXL API location for the SOAP client
EndpointAddress address = new EndpointAddress( $"https://{ System.Environment.GetEnvironmentVariable( "CUCM_ADDRESS" ) }:8443/axl/" );
//Class generated from AXL WSDL
AXLPortClient client = new AXLPortClient( binding, address );
// To disable HTTPS certificate checking, uncomment the below lines
// NOT for production use!
// client.ChannelFactory.Credentials.ServiceCertificate.SslCertificateAuthentication = new X509ServiceCertificateAuthentication
// {
// CertificateValidationMode = X509CertificateValidationMode.None,
// RevocationMode = X509RevocationMode.NoCheck
// };
// client.ChannelFactory.Credentials.ServiceCertificate.Authentication.CertificateValidationMode = X509CertificateValidationMode.None;
// client.ClientCredentials.ServiceCertificate.Authentication.CertificateValidationMode = X509CertificateValidationMode.None;
// Incantation to force alternate serializer reflection behaviour due to complexities in the AXL schema
// See https://github.com/dotnet/wcf/issues/2219
MethodInfo method = typeof( XmlSerializer ).GetMethod( "set_Mode", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static );
method.Invoke( null, new object[] { 1 } );
// Base64 encode AXL username/password for Basic Auth
var encodedUserPass = Convert.ToBase64String( Encoding.ASCII.GetBytes(
System.Environment.GetEnvironmentVariable( "CUCM_USERNAME" ) + ":" +
System.Environment.GetEnvironmentVariable( "CUCM_PASSWORD" )
) );
// Incantation to create and populate a Basic Auth HTTP header
// This must be done to force SoapCore to include the Authorization header on the first attempt
// rather than in challenge/response fashion
HttpRequestMessageProperty requestProperty = new HttpRequestMessageProperty();
requestProperty.Headers[ "Authorization" ] = "Basic " + encodedUserPass;
// Creating context block apparently allows attaching custom HTTP headers to the request
var scope = new OperationContextScope( client.InnerChannel );
OperationContext.Current.OutgoingMessageProperties[ HttpRequestMessageProperty.Name ] = requestProperty;
//Create the request object
AddUserReq addUserReq = new AddUserReq();
addUserReq.user = new XUser();
addUserReq.user.lastName = "TestUser";
addUserReq.user.userid = "testUser";
addUserReq.user.password = "Cisco!1234";
string userPkid = "";
//Try the addUser request
try
{
addUserResponse addUserResp = await client.addUserAsync( addUserReq );
userPkid = addUserResp.addUserResponse1.#return;
}
catch ( Exception ex )
{
Console.WriteLine( $"\nError: addUser: { ex.Message }" );
Environment.Exit( -1 );
}
A few notes:
SoapCore generates elements with default values when it can, e.g. nil for string elements. This causes a problem with <addUser>, as the <customerName> element should only be sent to HCS CUCMs. A modification to the AXLSoap.xsd before running svcutil was able to workaround it:
sed -i 's/name=\"customerName\" nillable=\"true\"/name=\"customerName\" nillable=\"false\"/g' schema/AXLSoap.xsd
Requests will fail due to HTTPS certification validation of the CUCM self-signed certificate, unless it is installed to the OS CA trust store or disabled (see the commented section in the code above)
The following curious code was required to avoid a "Compiling JScript/CSharp scripts is not supported" error on making a request (per here):
MethodInfo method = typeof( XmlSerializer ).GetMethod( "set_Mode", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static );
method.Invoke( null, new object[] { 1 } );
I want to use this WcfCoreMtomEncoder lib here in my .Net Core project but I'm not sure how to use it syntactically.
I have this code below but can't use MessageEncoding because I'm in a .Net Core project (no mtom support):
var binding = new BasicHttpBinding(BasicHttpSecurityMode.Transport)
{
// MessageEncoding = WSMessageEncoding.Mtom, not supported in .Net Core
TransferMode = TransferMode.Streamed
};
EndpointAddress endpoint = new EndpointAddress(url);
var channelFactory = new ChannelFactory<T>(binding, endpoint);
var webService = channelFactory.CreateChannel();
user.UserName = await webService.EncryptValueAsync(userName);
user.Password = await webService.EncryptValueAsync(password);
var documentAddResult = webService.DocumentAdd(document);
channelFactory.Close();
From what I read I can replace it with this library code below and I see from the documentation for the encoder lib that the usage looks like this:
var encoding = new MtomMessageEncoderBindingElement(new TextMessageEncodingBindingElement());
var transport = new HttpTransportBindingElement();
var customBinding = new CustomBinding(encoding, transport);
var client = new MtomEnabledServiceClient(customBinding);
but I'm not sure what's what here?
How would it be used to perform the document upload I'm trying to achieve? And is the library doing this or am I misunderstanding what it does?
If anyone can provide me an example of how to use this library to perform the document upload it would be appreciated.
Proceed as follows:
Generate the service client using WCF, this will result in a namespace and partial class, say DocumentWebServiceClient,
in the same project and namespace, create a file to extend the partial class and implement the ConfigureEndpoint method which is intended for endpoint configuration tasks:
using System.Collections.Generic;
using System.Linq;
using System.ServiceModel.Channels;
using System.ServiceModel.Description;
using WcfCoreMtomEncoder;
public partial class DocumentWebServiceClient // DocumentWebServiceClient is generated by the WCF
{
static partial void ConfigureEndpoint(ServiceEndpoint serviceEndpoint, ClientCredentials clientCredentials)
{
var messageEncodingBindingElementType = typeof(MessageEncodingBindingElement);
var elements = serviceEndpoint.Binding.CreateBindingElements();
IEnumerable<BindingElement> elementsWithoutEncodingElement = elements.Where(item => !messageEncodingBindingElementType.IsAssignableFrom(item.GetType()));
var existingEncodingElement = (MessageEncodingBindingElement)elements.Where(item => messageEncodingBindingElementType.IsAssignableFrom(item.GetType())).First();
var newEncodingElement = new MtomMessageEncoderBindingElement(existingEncodingElement);
// Encoding is before transport, so we prepend the MTOM message encoding binding element
// https://learn.microsoft.com/en-us/dotnet/framework/wcf/extending/custom-bindings
var cb = new CustomBinding(elementsWithoutEncodingElement.Prepend(newEncodingElement));
serviceEndpoint.Binding = cb;
}
}
After some struggles, I managed to create a WCF service that could be consumed by the class library. But it only supports the Custombinding. Please refer to the below example.
Server-side (a console application based on Dotnet Framework 4.7.2)
class Program
{
static void Main(string[] args)
{
Uri uri = new Uri("http://localhost:21011");
MtomMessageEncodingBindingElement encoding = new MtomMessageEncodingBindingElement();
var transport = new HttpTransportBindingElement();
transport.TransferMode = TransferMode.Streamed;
var binding = new CustomBinding(encoding, transport);
using (ServiceHost sh = new ServiceHost(typeof(MyService), uri))
{
sh.AddServiceEndpoint(typeof(IService), binding, "");
ServiceMetadataBehavior smb;
smb = sh.Description.Behaviors.Find<ServiceMetadataBehavior>();
if (smb == null)
{
smb = new ServiceMetadataBehavior()
{
HttpGetEnabled = true
};
sh.Description.Behaviors.Add(smb);
}
Binding mexbinding = MetadataExchangeBindings.CreateMexHttpBinding();
sh.AddServiceEndpoint(typeof(IMetadataExchange), mexbinding, "mex");
sh.Opened += delegate
{
Console.WriteLine("Service is ready");
};
sh.Closed += delegate
{
Console.WriteLine("Service is clsoed");
};
sh.Open();
Console.ReadLine();
//pause
sh.Close();
Console.ReadLine();
}
}
}
[ServiceContract]
public interface IService
{
[OperationContract]
string Test();
}
public class MyService : IService
{
public string Test()
{
return DateTime.Now.ToLongTimeString();
}
}
Client-side (Core-based Console application with WcfCoreMtomEncoder nuget package, calling the service by using ChannelFactory).
class Program
{
static void Main(string[] args)
{
var encoding = new MtomMessageEncoderBindingElement(new TextMessageEncodingBindingElement());
var transport = new HttpTransportBindingElement();
transport.TransferMode = TransferMode.Streamed;
var binding = new CustomBinding(encoding, transport);
EndpointAddress endpoint = new EndpointAddress("http://vabqia969vm:21011");
ChannelFactory<IService> channelFactory = new ChannelFactory<IService>(binding, endpoint);
var webService = channelFactory.CreateChannel();
Console.WriteLine(webService.Test());
}
}
[ServiceContract]
public interface IService
{
[OperationContract]
string Test();
}
One more thing we pay attention to is that we should manually bind a certificate to the particular port on the server-side if the server using Transport security mode to secure the communication.
Netsh http add sslcert ipport=0.0.0.0
certhash=0102030405060708090A0B0C0D0E0F1011121314
appid={00112233-4455-6677-8899-AABBCCDDEEFF}
In the above example, I bind a certificate that has a named vabqia969vm subject(DNS) to the machine(hostname is vabqia969vm). Here are some official links.
https://learn.microsoft.com/en-us/windows/win32/http/add-sslcert
https://learn.microsoft.com/en-us/dotnet/framework/wcf/feature-details/how-to-configure-a-port-with-an-ssl-certificate
On the client-side, before making a call to the service, we should establish a trust relationship so that communication is available between the client-side and the server-side. Therefore, I install the server certificate on the client-side LocalCA(Trusted Root Certification Authorities in the certification store). Alternatively, we could manually add a certificate validation process.
ChannelFactory<IService> channelFactory = new ChannelFactory<IService>(binding, endpoint);
channelFactory.Credentials.ServiceCertificate.SslCertificateAuthentication = new System.ServiceModel.Security.X509ServiceCertificateAuthentication()
{
CertificateValidationMode = System.ServiceModel.Security.X509CertificateValidationMode.None,
RevocationMode = System.Security.Cryptography.X509Certificates.X509RevocationMode.NoCheck
};
Feel free to let me know if there is anything I can help with.
Updated.
I have updated the above example which works properly over HTTP protocol.
your .net core client code should be something like this
var encoding = new MtomMessageEncoderBindingElement(new TextMessageEncodingBindingElement());
var transport = new HttpTransportBindingElement();
var customBinding = new CustomBinding(encoding, transport);
EndpointAddress endpoint = new EndpointAddress(url);
var channelFactory = new ChannelFactory<T>(customBinding, endpoint);
var webService = channelFactory.CreateChannel();
user.UserName = await webService.EncryptValueAsync(userName);
user.Password = await webService.EncryptValueAsync(password);
var documentAddResult = webService.DocumentAdd(document);
channelFactory.Close();
Expanding on #divyang4481's answer. For those still struggling with this, depending on the service you are interacting with, you may have to change your encoding to something like the following:
var encoding = new MtomMessageEncoderBindingElement(new TextMessageEncodingBindingElement
{
MessageVersion = MessageVersion.CreateVersion(EnvelopeVersion.Soap12, AddressingVersion.None)
});
var transport = new HttpsTransportBindingElement();
var customBinding = new CustomBinding(encoding, transport);
The web service I was calling threw an error, i.e. mustUnderstand headers with default TextMessageEncodingBindingElement, so, I had to set the AddressVersion to none to solve the issue.
I have a WCF service with a class that implements IContractBehavior and IWsdlExportExtension that generates a WSDL with a user's allowed operations and excludes operations and types they do not have access to.
The limitation of this is however is that for each user, I have to manually change which user I am generating the WSDL for.
I'd like to fix this limitation by passing in the user as part of the request for the metadata for example.
localhost/service.svc?user=me
or
localhost:9766/service.svc?singleWsdl&user=me
Alternatively I could use svcutil would also work as long as the resulting WSDL is flattened.
I was able to get this to work by doing the following
I host the service in console application.
I added an endpoint with the IContractBehavior added to it, passing in the desired parameter into the IContractBehavior class.
After the service is open, I use WsdlExporter to export the Metatadaset
Finally I use the WsdlHelper to generate the Wsdl file as
described here
http://www.shulerent.com/2013/03/14/generating-a-single-flattened-wsdl-from-an-existing-wcf-service/
The code
const string BASE_ADDRESS =
"http://localhost:8731/Design_Time_Addresses/CalcService";
var uri = new Uri(BASE_ADDRESS);
var user = "userName";
using (var serviceHost = new ServiceHost(typeof(Calc), uri))
{
var exporter = new WsdlExporter();
var endpoint = serviceHost.AddServiceEndpoint(typeof(ICalc),
new BasicHttpBinding(), "");
endpoint.Contract.Behaviors.Add(new
RestrictedOperationsWsdlExportExtensionAttribute(user));
serviceHost.Open();
Console.WriteLine("The service is ready: " + user);
exporter.ExportEndpoint(endpoint);
if (exporter.Errors.Count == 0)
{
var metadataSet = exporter.GetGeneratedMetadata();
var asy= Assembly.GetAssembly(typeof(WsdlExporter));
var t = asy.GetType("System.ServiceModel.Description.WsdlHelper",
true);
var method = t.GetMethod("GetSingleWsdl",
System.Reflection.BindingFlags.Public
| System.Reflection.BindingFlags.Static);
var serviceDescription =
method.Invoke(null, new object[] {metadataSet})
as System.Web.Services.Description.ServiceDescription;
if (serviceDescription != null)
{
serviceDescription.Name = "Calc";
serviceDescription.Write(user + ".wsdl");
}
}
}
I use a third-party server written in Java.
WSDL is taken with the style of rpc/literal.
Connection to the service is initialized as follows:
private static MLPortChannel GetMerlionClient()
{
BasicHttpsBinding binding = new BasicHttpsBinding(BasicHttpsSecurityMode.Transport);
binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Basic;
binding.MaxReceivedMessageSize = 4096000;
EndpointAddress adress = new EndpointAddress(new Uri(#"https://apitest.merlion.com/rl/mlservice3"));
ChannelFactory<MLPortChannel> factory = new ChannelFactory<MLPortChannel>(binding, adress);
factory.Credentials.UserName.UserName = mlLogin;
factory.Credentials.UserName.Password = mlPassword;
return factory.CreateChannel();
}
It is works correctly only for one method and returns the correct data type and the data.
When I call other methods, they returns error as:
"Can not convert an object of type " ... MLService3RLTest.CatalogResult [] " of the type " ... MLService3RLTest.ShipmentDatesResult []"
In this example return type must be ShipmentDatesResult[].
I tested the service via special tool. All requests and responses is correct and returned correct XML.
What may be the cause of this error? Perhaps something needs to be configured for SOAP service. Maybe some magic option with right value?
If, instead of referring to the service, make a web link which uses the technology of web services .Net FrameWork 2.0 what works
var client = new WpfApplication1.com.merlion.apitest.MLService();
var myCredentials = new System.Net.NetworkCredential(Логин, Пароль);
// Create a webrequest with the specified URL.
var url = "https://apitest.merlion.com/rl/mlservice3";;
client.Credentials = myCredentials.GetCredential(new Uri(url), "Basic");
textBox.AppendText(client.helloWorld("Привет"));
var ответ = client.getCatalog("N1");
var массив = new string[] { "" };
var rz = client.getItems("N10100", массив, "", 0, 2, "");
add
client.PreAuthenticate = true;
In the code below I get an error. I guess it is something simple or impossible to do, because I wasn't able to find anything with Google. Anyway the error I am getting is:
The contract name 'SchippersStop.Wcf.Balie.Interfaces.IBalie' could not be found in the list of contracts implemented by the service 'System.ServiceModel.Routing.RoutingService'.
The error occurs when I try to add a ServiceEndpoint to the host.
ServiceHost host = new ServiceHost(typeof(RoutingService));
string path = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
foreach (string dll in Directory.GetFiles(path, "*.dll"))
{
Assembly assembly = Assembly.LoadFile(dll);
foreach (Type type in assembly.GetTypes())
{
if (type.GetInterfaces().Contains(typeof(ISchippersStopSvc)))
{
DiscoveryClient discoveryClient = new DiscoveryClient(new UdpDiscoveryEndpoint());
FindCriteria findCriteria = new FindCriteria(type);
findCriteria.Duration = TimeSpan.FromSeconds(10);
FindResponse findResponse = discoveryClient.Find(findCriteria);
EndpointDiscoveryMetadata discoveryMetadata = findResponse.Endpoints.First();
Uri uri = discoveryMetadata.Address.Uri;
string routerAddress = uri.AbsoluteUri.Replace(uri.Host, "0.0.0.0");
DiscoveryClientBindingElement discoveryBindingElement = new DiscoveryClientBindingElement();
discoveryBindingElement.FindCriteria = findCriteria;
discoveryBindingElement.DiscoveryEndpointProvider = new UdpDiscoveryEndpointProvider();
CustomBinding binding = new CustomBinding();
binding.Elements.Insert(0, discoveryBindingElement);
ContractDescription contract = ContractDescription.GetContract(type);
RoutingConfiguration rc = new RoutingConfiguration();
rc.RouteOnHeadersOnly = true;
string groupName = Guid.NewGuid().ToString();
foreach (EndpointDiscoveryMetadata endpoint in findResponse.Endpoints)
{
List<ServiceEndpoint> endpointList = new List<ServiceEndpoint>();
ServiceEndpoint client = new ServiceEndpoint(contract, binding, new EndpointAddress(endpoint.Address.Uri.AbsoluteUri));
endpointList.Add(client);
endpointList.AddRange(findResponse.Endpoints.Except(new List<EndpointDiscoveryMetadata>() { endpoint }).Select(s => new ServiceEndpoint(contract, binding, new EndpointAddress(s.Address.Uri.AbsoluteUri))).ToList());
endpointList.ForEach(f => f.EndpointBehaviors.Add(new ClientTrackerEndpointBehavior() { }));
rc.FilterTable.Add(new CustomMessageFilter(groupName), endpointList);
}
RoutingBehavior routing = new RoutingBehavior(rc);
host.Description.Behaviors.Add(routing);
// Here the error occurs
host.AddServiceEndpoint(type, binding, routerAddress);
}
}
}
The goal is to add WCF services that are discovered on the network to the router. However I would like to set it up in such a way that I don't need to know anything about the services and the router can detect all the necessary configurations after which it can dynamically load them. The only thing I want to provide to the router is the ServiceContract. The router can detect the assemblies with a ServiceContract based on the common interface they inherit from.
Can someone help me to solve this error?