Link to home
Start Free TrialLog in
Avatar of amr-it
amr-it

asked on

Best Practice for integration against a WebService

Hello there experts!

To start off with, I am not very experienced with professional development, but I guess we've all been there.

I am trying to develop and integrate to a fairly large system, offering more than 10 Webservice "Managers" with an average of 50 methods per manager. Just to skip the details about the system, let's call it a device communication / management system.

The system, as mentioned, offers more than 10 webservices (managers) containing lots of different methods. Each method takes different parameters, specific to the method, and aslo a webservice security key which can be retrieved from the LoginManager.Login method.

The parameters are specified in a string formatted XML.
Example:

<PARAMETERS><DEVICE><ID>123456</ID></DEVICE></PARAMETERS>

Open in new window


I do know I have a lot of work to cover all the methods and to be able to parse all the returned data. But what I do want help with, is what is the best practice working with something like this? The process from building the parameters in a smart way, passing them to the method, validating the received data and serialize it to objects.

The system provider, do not offer any XSDs, but examples of the return data.
I have been playing around with VS 2010 xsd.exe to create classes and schemas, but this is very time consuming.

Should I typically create a base class, with subclasses for each manager and their methods? Along with a class for the method parameters?

To illustrate an example, the following is from the actual documentation I have.
Due to privacy and confidentially I have shortened the parameters, but you will get the idea.
My apologies if the XML broke while editing.

RetrieveList
public string RetrieveList(string sKey, string sXmlParameters)

Retrieves a list of devices based on a set of restriction criteria. This API also supports
paging through items that match the restriction criteria.

Parameters:
Name: sKey Type: string
Description: The API security key. A valid API security key is required for every API call.
Name: sXmlParameters Type: string
Description: Device fields and their values used to restrict the retrieval. See the Additional
Information section for XML format.

Return Value:
Type: string
Description: Returns XML containing the status of the list retrieval and if the status is
ReturnCodes.SUCCEEDED, a list of devices matching the
search criteria and information used for paging through data. The devices are sorted by
device name in ascending order, then by device ID in ascending order. See the
Additional Information section for the return API payload format

sXmlParameters format:
<PARAMETERS>
	<DATACONTENTRETURNTYPEID>
		string from the DataContentReturnTypes class. Specifies
		what data content (list of devices only, list of devices and
		count, or count of devices only) should be returned in the return XML.
		</DATACONTENTRETURNTYPEID> (optional - if not specified a value of Constants.DataContentReturnTypes.LIST_ONLY will be used)
	<DEVICES>
		<DEVICE>
			<ID>
				string - 32-character GUID. 
			</ID> (optional - if specified, the Name and Serial Number
			nodes cannot be included. If not specified, either the
			Name node or the Serial Number node must be specified.)
			<NAME>
			<VALUE>
				string - up to 64 characters. The device name to use to restrict the retrieve. 
				If the value specified in the Match Type ID node is ValueMatchTypes.CONTAINS,
				certain characters can be included and used as wildcard	characters. 
			</VALUE>
			<MATCHTYPEID>
				string from	ValueMatchTypes class.
				Indicates whether the devices returned should have a name that
				equals the value in the Value node or just contains the value in the Value node.
			</MATCHTYPEID> (optional - if not specified, ValueMatchTypes.EQUALS is used)
			</NAME> (optional - if specified, the ID and Serial Number nodes cannot be included. If not specified, either the
			Serial Number node or the ID node must be specified.)
			<SERIALNUMBER>
				<VALUE>
					blank or string - up to 64 characters.
					The device serial number to use
					to restrict the retrieve. If the value
					specified in the Match Type ID node
					ValueMatchTypes.CONTAINS,
					certain characters can be
					included and used as wildcard
					characters. 
				</VALUE>
				<MATCHTYPEID>
					string from
					ValueMatchTypes class.
				</MATCHTYPEID> (optional - if not specified,
				ValueMatchTypes.EQUALS is used)
			</SERIALNUMBER> (optional - if specified, the ID and Name
			nodes cannot be included. If not specified, either the Name
			node or the ID node must be specified.)
			</DEVICE>
		...
	</DEVICES> (optional, if included at least one Device node must be included)
	<TYPEID>
		string from DeviceTypes class. The device type ID to use to restrict the retrieve.
	</TYPEID> (optional)
	<STATUSTYPES>
		<STATUSTYPE>
		<ID>
			string from DeviceStatus class. The device
			status type ID to use to restrict the retrieve.
		</ID>
		</STATUSTYPE>
		...
	</STATUSTYPES> (optional)
	<STARTINSTALLATIONDATETIME>
		date time in UTC - greater than or equal to 1753-01-01 00:00:00.000
		and less than or equal to the end date time, if included, otherwise
		less than or equal to 9999-12-31 23:59:59.998. Devices returned
		will have an installation date time that occurred on or after the
		date time specified.
	</STARTINSTALLATIONDATETIME> (optional)
	<ENDINSTALLATIONDATETIME>
		date time in UTC - greater than or equal to the start date time, if
		included, otherwise greater than or equal to 1753-01-01 00:00:00.000
		and less than or equal to 9999-12-31 23:59:59.998. Devices returned
		will have an installation date time that occurred on or before the
		date time specified.
	</ENDINSTALLATIONDATETIME> (optional)
	<MAXCOUNT>
		integer - greater than 0 and less than or equal to 2,147,483,647. The
		maximum number of devices to return.
	</MAXCOUNT> (optional - if not specified, the value of the Retrieve
	<SORTOPTIONS>
		<SORTOPTION>
			<SORTBYTYPEID>
			string from DeviceRetrieveListSortByTypes class. 
			Indicates what the devices returned should be sorted by.
			</SORTBYTYPEID>
			<SORTORDERTYPEID>
			string from SortOrderTypes class. 
			Indicates whether the sort by type specified is ordered ascending or descending.
			</SORTORDERTYPEID>
		</SORTOPTION>*
	</SORTOPTIONS> (optional, if not included and the Data Content Return Type
	ID node is not included or is included with the value of
	DataContentReturnTypes.LIST_ONLY
	or DataContentReturnCodes.LIST_AND_COUNT,
	<DATASET>
		<NEXT>
			<DEVICEID>
			string - up to 32 characters. The device ID used to retrieve the next
			set of devices matching the restriction criteria in sXmlParameters.
			</DEVICEID>
		<!--If the Sort Options node is not included or if the sort by type ID is
		specified as GatewayRetrieveListSortByTypes.DEVICE_NAME
		the following node is required-->
		<DEVICENAME>
			string - up to 64 characters. The device name used to retrieve the
			next set of devices matching the restriction criteria in sXmlParameters.
		</DEVICENAME>
		<!--If the Sort Options node is specified as
		GatewayRetrieveListSortByTypes.GATEWAY_NAME the
		following nodes are required-->
		<DEVICENAME>
			string - up to 64 characters. The device name used to retrieve the
			next set of devices matching the restriction criteria in
			sXmlParameters.
		</DEVICENAME>
		<DATETIME>
			date time in UTC. The last Device Delta Load Profile result
			received date time used to retrieve the next set of devices
			matching the restriction criteria in sXmlParameters. 
		</DATETIME>
		</LASTRECEIVEDRESULT>
		<DEVICENAME>
			string - up to 64 characters. The device name used to retrieve the
			next set of devices matching the restriction criteria in sXmlParameters.
		</DEVICENAME>
		</NEXT>
	</DATASET>(Optional)
</PARAMETERS>

Open in new window



Return API Payload format:

<COUNT>
	integer - the total number of devices matching the search criteria
	independent of the maximum numbers of devices to be
	returned in the list. For example, if the maximum number of
	devices to be returned is 50, but 200 match the search
	criteria in the XML parameters, the value in this node will
	be 200.
</COUNT> (returned only if the value specified in the Data Content Return
Type ID node in the XML parameters was
DataContentReturnTypes.COUNT_ONLY or
DataContentReturnTypes.LIST_AND_COUNT)
<DEVICES>
	<DEVICE>
		<ID>string - 32-character GUID. The device ID.</ID>
		<NAME>string - up to 64 characters. The device name.</NAME>
		<TYPEID>
			string from DeviceTypes class. The device type.
		</TYPEID>
		<STATUSTYPEID>
			string from DeviceStatus class. The status of the
			device
		</STATUSTYPEID>
		<SERIALNUMBER>
			string - up to 64 characters. The device serial number.
		</SERIALNUMBER>
	</DEVICE>
...
</DEVICES> (returned only if the value specified in the Data Content Return Type ID
node in the XML parameters was DataContentReturnTypes.LIST_ONLY or
DataContentReturnTypes.LIST_AND_COUNT or if the Data Content Return
Type ID node was not included in the XML parameters)
<DATASET>
	<NEXT>
	<ADDITIONAL>
		string of StandardAPIOptions.YES or
		StandardAPIOptions.NO. If the number of devices
		meeting the restriction criteria specified in
		sXmlParameters is less than or equal to the maximum count
		of items to be retrieved, the value returned in this node
		is StandardAPIOptions.NO. If the number of devices
		meeting the restriction criteria specified in sXmlParameters
		is more than the maximum count of items to be retrieved the
		value returned in this node is
		StandardAPIOptions.YES.
	</ADDITIONAL>
	<DEVICEID>
		string - up to 32 characters. The device ID used to retrieve the next
		set of devices matching the restriction criteria in sXmlParameters.
	</DEVICEID>
	<!--If the Sort Options node is not included or if the sort by type ID is
	specified as GatewayRetrieveListSortByTypes.DEVICE_NAME
	the following node is required-->
	<DEVICENAME>
		string - up to 64 characters. The device name used to retrieve the
		next set of devices matching the restriction criteria in sXmlParameters.
	</DEVICENAME>
	</NEXT>
</DATASET>

Open in new window


Please let me know if you want more details.

Thank you,
amr-it
Avatar of Bob Learned
Bob Learned
Flag of United States of America image

This "best practices" question gets asked so much, I started thinking about a canned response:

https://www.experts-exchange.com/blogs/TheLearnedOne/B_4708-The-Best-We-Can-Do-Is-Better.html

The "best" I can do is to offer what I believe is the "better" choice given my understanding of your situation.  I have to admit that I am a "bigger picture" kind of guy, so sometimes these questions where I get too much detail up front are confusing (I get stuck in all the words).  

I would like to try from about 10000 meters, to get an understanding of your goal, given your admission that you are "not very experienced with professional development".  That way, I can try to find one of a possible set of solutions.

Bob
Avatar of amr-it
amr-it

ASKER

Best practice may not have been the best explanation of my problem.
But to wrap it into an understandable question, I'd say what would be a good and maintainable way to handle a webservice offering so many possibilities, to gather them into methods that returns serialized objects rather than xml strings, that verifies the parameters, that verifies the response, all in one package where one easily could access the needed method in a quick way when needed.

I might update my question and change the "Best Practice" part. The goal here is to use and re-use the webservice in different applications, big and small without having to develop much more in the core methods.

A returned meter, would always be a meter with its properties according to the webservice/system.

I hope this is better formulated.

Thank you for your feedback.
Cheers
amr-it
It sounds like you are not in control of the web services, you just need to manage how you connect to these web services.  It sounds like, on the surface, that you might want to think about a dynamic library, that defines wrappers classes to provide a centralized interface to the web services that translates the calls into something more understandable.  I would look into a plug-in interface, where you could easily extend the web service integration library.

(If I understand your requirements)...
Avatar of amr-it

ASKER

That sounds about right, I have been looking at different wrapper solutions for other purposes.
Where it gave me the idea to build a wrapper class for each manager, where each method would be included.

And for each manager create the classes needed for the parameters, for simpler re-use and to know what different parameters that can be used for the different methods and to create classes for the return XML to deserialize it to objects.

Some methods would be common for the different managers such as the serialization and deserialization, setting the webservice url and so on.

I would appreciate recommendations for the hierarchy of this wrapper.
Depending on the architechture, going with static classes, all methods could be accessed without creating an instance of the wrapper, but they can't be inherited and so on.

I believe that a strategy has to be made before starting to work with it, creating xsd schemas, going with a static wrapper and so on. Do you have any plug-in/hierarchy recommendations you can give? Some references where i can read more to support me in choosing the more correct approach for my needs?

Cheers,
amr-it
Can you give me some idea of what you are working with?  Web methods, return values, expectations, ...  The key to good design is to recognize a pattern, and apply the right tool for the right job.
Avatar of amr-it

ASKER

Hi,

I can give you an example of how I have managed it so far:

To use any web method, I have to make a call to the UserManager.Login web method to retrieve a valid APIkey.

To make the call, I have to pass 3 parameters to the web method (string): username, password, authType.

The API would return a XML string similar to:

<RETURN>
<STATUS>Successful</STATUS>
<APIPAYLOAD>
<APIKEY>validAPIkey</APIKEY>
</APIPAYLOAD>
</RETURN>

Open in new window


The key must be retrieved and passed to all other calls. The key is valid for 1 hour.
Example of how I have managed it until now:

Within the Wrapper, I have a property for the APIKey (securityToken) which is assigned from the Login() method (which calls the UserManager.Login() and a property for the webservice url which must be set before any calls. (coreServicesURL).

public void Login()
        {
            UserManager.UserManager userManager = new UserManager.UserManager();
            userManager.Url = coreServicesURL + "/UserManager.asmx";                       
            
            //get the return XML from the web call
            string result = userManager.Login(username, password, UserAuthenticationTypes.DEFAULT);

            XmlNode apiPayLoad = null;
            
            //Pass the resul to GetResult helper.
            if (GetResult(result, out apiPayLoad))
            //If it was successful, get the APIKEY node inner text (key).
                securityToken = apiPayLoad.SelectSingleNode("APIKEY").InnerText;            
        }

public bool GetResult(string result, out XmlNode apiPayLoad)
        {
            XmlDocument xmlDocument = new XmlDocument();
            apiPayLoad = null;
            xmlDocument.LoadXml(result);

            //If the result indicates it was successful
            if (xmlDocument.SelectSingleNode("RETURNS/STATUS").InnerText == ExternalServiceReturnCodes.SUCCEEDED)
            {
                //Return the APIPayLoad (Could be w/e)
                apiPayLoad = xmlDocument.SelectSingleNode("RETURNS/APIPAYLOAD");
                return true;
            }
            else
            {
                apiPayLoad = xmlDocument.SelectSingleNode("RETURNS/STATUS");
                return false;
            }
            
        }

Open in new window


As a second example, let's say I'd like to update a device using the API.
The DeviceManager.Update takes two parameters, the APIkey and a string XML containing multiple parameters.
This method only returns SUCCESS or a guid to indicate if the call was successful or not. No data is returned.

To make it easier, to know what parameters the API takes, I created a class for the method, to create a serializable object that I can set my parameters to.

public class UpdateDeviceParameters
            {
                /// <summary>
                /// Name of the device (optional)
                /// </summary>
                public string NAME { get; set; }
                /// <summary>
                /// Description of the Device (optional)
                /// </summary>
                public string DESCRIPTION { get; set; }
                /// <summary>
                /// Installation date time of the Device (optional)
                /// Date time in UTC - Greater than or equal to 9999-12-31 23:59:59.998. The new installation date time for the device.
                /// </summary>
                public string INSTALLATIONDATETIME { get; set; }
                /// <summary>
                /// TimeZone of the Device (optional)
                /// The new time zone ID for the device. Call the TimeZoneManager.RetrieveList API to get a list of valid time zone IDs.
                /// </summary>
                public string TIMEZONEID { get; set; }
                /// <summary>
                /// Hardware version for the device (optional)
                /// </summary>
                public string HARDWAREVERSION { get; set; }
            }

Open in new window


Before using the UpdateDevice method, I'd serialize my UpdParameters object to XML and pass it as a string to the method.

public string UpdateDevice(string DeviceID, string UpdParameters)
            {
                XmlNode apiPayLoad = null;
                DeviceManager devMgr = new DeviceManager();
                devMgr.Url = getcoreServicesURL() + "/DeviceManager.asmx";
                
                //getsecurityToken() gets the securitytoken received from the Login method.
                string result = devMgr.Update(getsecurityToken(), DeviceID, UpdParameters);
                
                if(GetResult(result, out apiPayLoad))
                {
                    return "Success!";
                }
                else
                {
                    XmlDocument xmlDocument = new XmlDocument();
                    xmlDocument.LoadXml(result);

                    return xmlDocument.SelectSingleNode("RETURNS/STATUS").InnerText;
                }
            }

public bool GetResult(string result, out XmlNode apiPayLoad)
        {
            XmlDocument xmlDocument = new XmlDocument();
            apiPayLoad = null;
            xmlDocument.LoadXml(result);

            if (xmlDocument.SelectSingleNode("RETURNS/STATUS").InnerText == ExternalServiceReturnCodes.SUCCEEDED)
            {
                apiPayLoad = xmlDocument.SelectSingleNode("RETURNS/APIPAYLOAD");
                return true;
            }
            else
            {
                apiPayLoad = xmlDocument.SelectSingleNode("RETURNS/STATUS");
                return false;
            }
            
        }

Open in new window


Please let me know if you need more information.
The above example was a simpler one, retrieving lists of devices, would need a de-serialization of the result XML to return devices in those methods that would expect it.

My main goal is just to manage all web methods and the entities around these methods in a good and maintainable way. How to hierarchically build the class and wrapper methods.

Somehow, I want to fit everything under one umbrella, in a good design:

Class Wrapper
	Common Property
	Common Property
	Common Property

Class DeviceManagerWrapper : Wrapper
	DevManager	Property
	DevManager	Property
	DevManager	Property
	
	Method	Create()
		Class CreateParameters
	Method	Update()
		Class UpdateParameters
	Method	Delete()
		Class DeleteParameters

Class UserManager : Wrapper
	UserManager Property
	UserManager Property
	UserManager Property
	
	Method Login()
	
Common Entities
	Device
	Etc..
	
	

Open in new window


cheers
amr-it
ASKER CERTIFIED SOLUTION
Avatar of Bob Learned
Bob Learned
Flag of United States of America image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
I've requested that this question be deleted for the following reason:

This question has been classified as abandoned and is closed as part of the Cleanup Program. See the recommendation for more details.
Ahem, I believe that I answered this question.
Answer = #35736899