Search code examples
c#wcfsoapciscocisco-axl

unable to post to AXL endpoint


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


Solution

  • 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 } );