Author Message
RasmusNeander2
Joined: Nov 13, 2013
Messages: 9
Offline
Does anyone have sample code on how to add a new station through webservices? Preferably in .NET
matt4923
Joined: Oct 31, 2014
Messages: 2
Offline
Because I spent weeks of trial/error beating my head and pouring over the limited Avaya documentation and examples of this, I hope this can help others. I am able to use this base class for any SMS api call. There is a lot of clean up I can do, (certificate handling, etc) but this works in my environment. But, my conspiracy theory is that Avaya will remove this thread in order for you to pay them for support on this... hopefully not... hopefully this helps someone.

BASE CLASS:

public abstract class AvayaSmsApiCallBase<ModelType> {

//system management service (SMS) is a soap api see "Web_Services_Programmers_Guid.pdf" page 21 for more details
//test tool: https://XX.XX.X.XXX/sms/sms_test.php
//https://www.devconnectprogram.com/forums/posts/listByUser/15/9085.page
//https://www.devconnectprogram.com/forums/posts/list/20180.page

protected abstract string Model { get; } //"Station", "StationStatus", etc
protected abstract string Operation { get; } //"list", "status", "display", etc
protected abstract ModelType InstantiateNewModel(); //instantiates new model type

protected virtual string ObjectName => string.Empty; //"Extension", etc
protected virtual string FieldsString => "*";
protected virtual string Qualifier => ""; //specific extension, etc. (ie 2124501 in "status station 2124501"). This also handles the Count method on lists, (eg. "2021000 Count 1000")
protected virtual List<ModelType> FormatReturn(Dictionary<string, ModelType>.ValueCollection values) {
//default... just convert final dictionary into return list.
return new List<ModelType>(values);
}
protected virtual bool IsList => false;
protected virtual string GetLastNumberFromList(List<ModelType> responseList) {
//default to just return blank... if the derived class doesn't override this, the response doesn't need to be paged.
return string.Empty;
}

protected AvayaSwitchInstance MySwitch { get; set; }

bool _ReturnTypeIsList { get; set; }
protected AvayaSmsApiCallBase(AvayaSwitchInstance s, string CallNameString, bool returnList = false, int batchLimit = 1000) {
MySwitch = s;
_ReturnTypeIsList = returnList;
BatchLimit = batchLimit;
SetConnectionForAesAndCmApiCalls(MySwitch, CallNameString);
}


internal List<ModelType> DoApiCall() {
BasicHttpBinding binding = GetBinding();
EndpointAddress endpointAddr = new EndpointAddress($"https://{MySwitch.AesConnection.IPAddress}/sms/SystemManagementService.php");

SystemManagementPort req = new SystemManagementPortClient(binding, endpointAddr);
SetPropertyValue(req, "ClientCredentials/UserName/UserName", $"{MySwitch.CmConnection.UserName}@{MySwitch.CmConnection.IPAddress}"); // CM connection credentials -- user needs to be in the format of "matt@XX.X.XX.XXX"
SetPropertyValue(req, "ClientCredentials/UserName/Password", Utilities.Scrambler.DecryptPassword(MySwitch.CmConnection.AccessCode));

var reqData = new submitRequestType();
reqData.model = Model;
reqData.operation = Operation;
reqData.objectname = ObjectName;
reqData.qualifier = Qualifier;
reqData.fields = FieldsString;

//trust everything
System.Net.ServicePointManager.ServerCertificateValidationCallback = ((sender, certificate, chain, sslPolicyErrors) => true);
List<ModelType> returnList = null; //list of response data to return... multiple responses are added to this if records exceed the batch count.
List<ModelType> responseList; //individual parsed response in model form

string startingNumberForList = "0";
do {
if(IsList && startingNumberForList != string.Empty) {
//if response needs to be paged, qualifier should be in the format: <startingNumberForList> count <BatchLimit> ie qualifier = "2021000 count 1000" -> should return 1000 records starting with 2021000
reqData.qualifier = $"{startingNumberForList} count {BatchLimit}";
}
var res = req.submitRequest(new submitRequestRequest(string.Empty, reqData));
responseList = CheckForErrorGetModelList(req, res);
if(returnList == null) {
returnList = responseList;
} else {
if(responseList.Count > 0) {
responseList.RemoveAt(0); //if it hits here, the first record in the response was already added in the last call
returnList.AddRange(responseList);
}
}
} while(!IsFinishedGatheringAllData(responseList, ref startingNumberForList));

return returnList;
}

internal int BatchLimit { get; set; }
private bool IsFinishedGatheringAllData(List<ModelType> responseList, ref string startingNumberForList) {
if(!IsList || responseList == null || responseList.Count == 0 || responseList.Count + 1 < BatchLimit) return true;

//get last record in list to start with that next request
startingNumberForList = GetLastNumberFromList(responseList);
return false;
}

private List<ModelType> CheckForErrorGetModelList(SystemManagementPort req, submitRequestResponse res) {
ReleaseRequest(req, res.sessionID);
if(res.submitRequestResponse1.@return.result_code == 1) {
//error
//to do: use error handling descriptions in web services pdf document (end of doc)
throw new Exception($"Error from Avaya SMS API: {res.submitRequestResponse1.@return.message_text}");
}
var responseArray = res.submitRequestResponse1.@return.result_data.Split('|');
//return GetModel(responseArray);
return ParseAndReturn(responseArray);
}

private List<ModelType> ParseAndReturn(string[] responseArray) {
Dictionary<string, ModelType> dict = new Dictionary<string, ModelType>();
string ConstFieldName = "FieldName";
string ConstIndex = "Index";
string ConstValue = "Value";
string ConstSingle = "single";

//List pattern example part: Extension[4]=202-4422 (return multiple records) or Button_Data_1[3]=call-appr (when array field in single record)
//List pattern: FieldName=Extension, Index=4, Value=202-4422
//this can represent a single record with an array field (ie button_data) or a single record if response is multiple records (ie list station)
string arrayValuePattern = $@"(?<{ConstFieldName}>\w+)\[(?<{ConstIndex}>\d+)\]=(?<{ConstValue}>.*)";

//single pattern example part: Extension=202-4422
//single pattern: FieldName=Extension, Value=202-4422
string singleFieldValuePattern = $@"(?<{ConstFieldName}>\w+)=(?<{ConstValue}>.*)";

foreach(string part in responseArray) {
string key;
string fieldName;
string fieldValue;
uint fieldArrayIndex = 0;
Match m;
if(Regex.IsMatch(part, arrayValuePattern)) {
m = Regex.Match(part, arrayValuePattern);
if(!IsList) {
//single record return, this is an array field
key = ConstSingle;
uint.TryParse(m.Groups[ConstIndex].Value, out fieldArrayIndex);
} else {
//returning multiple records
key = m.Groups[ConstIndex].Value;
uint.TryParse(key, out fieldArrayIndex); //this var will be used if returning multiple records and in each record there's an array field... haven't seen this yet.
}
} else if(Regex.IsMatch(part, singleFieldValuePattern)) {
m = Regex.Match(part, singleFieldValuePattern);
key = ConstSingle;
} else {
//regex didn't match anything???
continue;
}

ModelType model;
if(dict.ContainsKey(key)) {
model = dict[key];
} else {
model = InstantiateNewModel();
}

fieldName = m.Groups[ConstFieldName].Value;
fieldValue = m.Groups[ConstValue].Value;
UpdateSetWithValue(model, fieldName, fieldValue, fieldArrayIndex);
dict[key] = model;
}

return FormatReturn(dict.Values);
}


private void UpdateSetWithValue(ModelType modelObj, string FieldName, string FieldValue, uint fieldArrayIndex) {
//http://technico.qnownow.com/how-to-set-property-value-using-reflection-in-c/
Type type = modelObj.GetType();
System.Reflection.PropertyInfo pInfo = type.GetProperty(FieldName);
var fValue = FieldValue.Replace("pi;", "|");

if(pInfo.PropertyType.IsArray) {
var arrayType = GetNewArrayType(pInfo, modelObj, fValue, fieldArrayIndex);
pInfo.SetValue(modelObj, arrayType, null);
} else {
pInfo.SetValue(modelObj, fValue, null); //a pipe(|) is an SMS keyword and needs to be replaced if found in a fields value.
}
}

private models.arrayType[] GetNewArrayType(System.Reflection.PropertyInfo pInfo, ModelType modelObj, string fieldValue, uint fieldArrayIndex) {
//Avaya uses weird logic to delimit multiple sets of data on a single field. ie for button data logic it comes in like:
//Button_Data_1[1]=call-appr|Button_Data_1[2]=call-appr|Button_Data_1[3]=call-appr|Button_Data_1[7]=auto-cback
//"Button_Data_1" is the field in modelObj, the [x] is the actual set button number (fieldArrayIndex)
//in this case there are Button_data_1-7 fields... probably for up to 7 features/sets of info on each button.
var existingArray = (models.arrayType[])pInfo.GetValue(modelObj, null);
models.arrayType[] saveArray;

if(existingArray == null) {
saveArray = new models.arrayType[1];
saveArray[0] = new models.arrayType {
position = fieldArrayIndex,
Value = fieldValue
};
} else {
saveArray = new models.arrayType[existingArray.Length + 1];
for(int i = 0; i < existingArray.Length; i++) {
saveArray[i] = existingArray[i];
}
saveArray[existingArray.Length] = new models.arrayType {
position = fieldArrayIndex,
Value = fieldValue
};
}

return saveArray;
}

private void ReleaseRequest(SystemManagementPort req, string sessionID) {
try {
if(sessionID != null && sessionID != string.Empty) {
var relResponse = req.release(new releaseRequest() { sessionID = sessionID });
var resCode = relResponse.releaseResponse1.@return.result_code; //0 is success, 1 is false and should have an error with it.... but this will timeout anyways so let's not worry about it.
}
} catch(Exception) {
//do nothing because this will timeout eventually, (30 sec I think).
}
}

private void SetPropertyValue(object obj, string propertyPath, object value) {
System.Reflection.PropertyInfo currentProperty = null;
string[] pathSteps = propertyPath.Split('/');
object currentObj = obj;
for(int i = 0; i < pathSteps.Length; ++i) {
Type currentType = currentObj.GetType();
string currentPathStep = pathSteps[i];
var currentPathStepMatches = System.Text.RegularExpressions.Regex.Match(currentPathStep, @"(\w+)(?:\[(\d+)\])?");
currentProperty = currentType.GetProperty(currentPathStepMatches.Groups[1].Value);
int arrayIndex = currentProperty.PropertyType.IsArray ? int.Parse(currentPathStepMatches.Groups[2].Value) : -1;
if(i == pathSteps.Length - 1) {
currentProperty.SetValue(currentObj, value, arrayIndex > -1 ? new object[] { arrayIndex } : null);
} else {
if(currentProperty.PropertyType.IsArray) {
currentObj = (currentProperty.GetValue(currentObj) as Array).GetValue(arrayIndex);
} else {
currentObj = currentProperty.GetValue(currentObj);
}
}
}
}

private BasicHttpBinding GetBinding() {
return new BasicHttpBinding() {
Security = new BasicHttpSecurity() {
Mode = BasicHttpSecurityMode.Transport,
Transport = new HttpTransportSecurity() {
//needs to be here in order to get away from putting these settings in the app.config file.
ClientCredentialType = HttpClientCredentialType.Basic,
ProxyCredentialType = HttpProxyCredentialType.Basic,
Realm = string.Empty
}
}
};
}

private void ThrowSwitchConnectionErrorForCmAndAesConnections(string NameOfCall) {
throw new Exception($"The '{NameOfCall}' Avaya api call requires switch connection keys of: '{CAIRS.Common.StateItems.AvayaAES}' and '{CAIRS.Common.StateItems.AvayaCM}'. Please verify BOTH of these Switch Connections exists in the Switch record and try again.");
}

private void SetConnectionForAesAndCmApiCalls(AvayaSwitchInstance mySwitch, string callName) {
//For SMS calls both AES and CM connx settings must exist. Set the main connection to AES, (doesn't matter since we explicitly call the AES connx) but make sure CM connection exists.
if(!mySwitch.SetConnectionForAesApi() || !mySwitch.SetOrCheckConnectionForCommunicationManagerApi()) {
ThrowSwitchConnectionErrorForCmAndAesConnections(callName);
}
}
}


DERIVED CLASSES:

public class GetAllStations : AvayaSmsApiCallBase<models.StationType>{
public GetAllStations(AvayaSwitchInstance s) : base(s, "GetAllSatStations", true) { }

protected override string Model => "Station";
protected override string Operation => "list";
//protected override string ObjectName => "*";
protected override bool IsList => true;

protected override StationType InstantiateNewModel() {
return new StationType();
}

protected override string GetLastNumberFromList(List<StationType> responseList) {
return responseList[responseList.Count - 1].Extension.Replace("-","");
}
}


public class StatusStation : AvayaSmsApiCallBase<models.StationStatusType> {
string _Extension;
public StatusStation(AvayaSwitchInstance s, string extension) : base(s, $"StatusStation ({extension})") {
_Extension = extension;
}

protected override string Model => "StationStatus";
protected override string Operation => "status";
protected override string ObjectName => "Extension";
protected override string Qualifier => _Extension;
//protected override bool IsList => true;

protected override StationStatusType InstantiateNewModel() {
return new models.StationStatusType();
}
}

public class DisplayStation : AvayaSmsApiCallBase<StationType> {
string _Extension;
public DisplayStation(AvayaSwitchInstance s, string extension) : base(s, $"DisplayStation ({extension})") {
_Extension = extension;
}

protected override string Model => "Station";
protected override string Operation => "display";
//protected override string ObjectName => "Extension";
protected override string Qualifier => _Extension;
//protected override bool IsList => true;

protected override StationType InstantiateNewModel() {
return new StationType();
}
}

public class ChangeStation : AvayaSmsApiCallBase<object> {
string _Extension;
string _FieldChanges;
public ChangeStation(AvayaSwitchInstance s, string extension, string fieldChanges) : base(s, $"Change Station ({extension})") {
_Extension = extension;
_FieldChanges = fieldChanges;
}

protected override string FieldsString => _FieldChanges;
protected override string Qualifier => _Extension;
protected override string Model => "Station";
protected override string Operation => "change";
protected override object InstantiateNewModel() {
return new object();
}
}

public class AddStation : AvayaSmsApiCallBase<object> {
string _Extension;
string _FieldChanges;
public AddStation(AvayaSwitchInstance s, string extension, string fieldChanges) : base(s, $"Add Station ({extension})") {
_Extension = extension;
_FieldChanges = fieldChanges;
}

protected override string FieldsString => _FieldChanges;
protected override string Qualifier => _Extension;
protected override string Model => "Station";
protected override string Operation => "add";
protected override object InstantiateNewModel() {
return new object();
}
}

public class RemoveStation : AvayaSmsApiCallBase<object> {
string _Extension;
public RemoveStation(AvayaSwitchInstance s, string extension):base(s, $"Remove Station ({extension}") {
_Extension = extension;
}

protected override string Qualifier => _Extension;
protected override string Model => "Station";
protected override string Operation => "remove";
protected override object InstantiateNewModel() {
return new object();
}
}

EXAMPLE CALLS:

public static List<models.StationType> GetAllSatStations(AvayaSwitchInstance mySwitch, int batchSize = 1000) {
var station = new aes.GetAllStations(mySwitch); //List Station
station.BatchLimit = batchSize;
return station.DoApiCall();
}

public static models.StationStatusType DoStatusStation(AvayaSwitchInstance mySwitch, string extension) {
var statusStation = new aes.StatusStation(mySwitch, extension);
var list = statusStation.DoApiCall();
if(list.Count > 0) {
//item was found
return list[0];
} else {
//no item found
return null;
}
}

public static models.StationType DoDisplayStation(AvayaSwitchInstance mySwitch, string extension) {
var dispStat = new aes.DisplayStation(mySwitch, extension);
var list = dispStat.DoApiCall();
if(list.Count > 0) {
//item was found
return list[0];
} else {
//no item found
return null;
}
}

public static void DoChangeStation(AvayaSwitchInstance mySwitch, string extension, string fieldsAndValues) {
//fieldsAndValues format of: <fieldName>=<fieldValue>... delimiter is pipe(|) (ex. Name=Name test|Coverage_Path_1=99)
var chgStat = new aes.ChangeStation(mySwitch, extension, fieldsAndValues);
chgStat.DoApiCall();
//return something??
}

public static void DoAddStation(AvayaSwitchInstance mySwitch, string extension, string fieldsAndValues) {
//fieldsAndValues format of: <fieldName>=<fieldValue>... delimiter is pipe(|) (ex. Type=9611sip|SIP_Trunk=ars)
//the base fields that need to be populated are Extension and Port, (if not IP/sip). If sip, also need to populate SIP_Trunk (ars, arr or a route pattern)
var addStat = new aes.AddStation(mySwitch, extension, fieldsAndValues);
addStat.DoApiCall();
//return something??
}

public static void DoRemoveStation(AvayaSwitchInstance mySwitch, string extension) {
var remStat = new aes.RemoveStation(mySwitch, extension);
remStat.DoApiCall();
}
matt4923
Joined: Oct 31, 2014
Messages: 2
Offline
The Add Station entry point is at the bottom of the code I pasted. If you need to return data, you've got to use the .NET XSD tool and xsd file, provided by Avaya, (ie. Station.xsd, Stationstatus.xsd, etc) to generate the c# code that creates all the respective models you need.
Go to:   
Mobile view