mardi 14 juillet 2015

C# Mapping XML Response into Unknown Class

I'm trying to achieve a generic solution for sending and receiving XML via any means(IP, serial textfiles, etc)

All appears to work fine until i get the response String.

What i need to do is cast this response sting into the correct class type (LoginResponse, LogoffResponse, CardPaymentResponse) etc.

I cant get a generic way to cast this XMl back into the object in an efficient manner.

Heres what i have got so far:

Sample Response Strings:

XML for LoginResponse:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<ServiceResponse RequestType="Login" ApplicationSender="POSsel01"     WorkstationID="1" RequestID="1254" ProtocolVersion="000000001234" DeviceType="113" SWChecksum="AC3F" CommunicationProtocol="000000000432" Model="011" ApplicatioSoftwareVersion="000000000100" Manufacturer_Id="023" OverallResult="Success" xmlns="http://ift.tt/1Huh18D" xmlns:IFSF="http://www.ifsf.org/" xmlns:xsi="http://ift.tt/ra1lAU" xsi:schemaLocation="http://ift.tt/1Huh18D C:\Schema\ServiceResponse.xsd"/>

XML for LogoffResponse:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<ServiceResponse RequestType="Logoff" ApplicationSender="POSsel01" WorkstationID="1" RequestID="1254" OverallResult="Success"
xmlns="http://ift.tt/1Huh18D" xmlns:IFSF="http://www.ifsf.org/" xmlns:xsi="http://ift.tt/ra1lAU"
xsi:schemaLocation="http://ift.tt/1Huh18D C:\Schema\ServiceResponse.xsd"/>

XML for CardPaymentResponse:

<CardServiceResponse 
xmlns:xsi="http://ift.tt/ra1lAU" xmlns:xsd="http://ift.tt/tphNwY" 
RequestType="CardPayment" 
ApplicationSender="1" 
WorkstationID="1" 
RequestID="1254" 
OverallResult="LoggedOut">
<Terminal 
    TerminalID="1234" 
    TerminalBatch="1" 
    STAN="55" />
<Tender>
    <TotalAmount 
        Currency="EUR">0.00</TotalAmount>
</Tender>
</CardServiceResponse>

Code for Base class to contain common fields

public abstract class IFSFResponseBase
{
    [XmlAttribute()]
    public string RequestType { get; set; }

    [XmlAttribute()]
    public string ApplicationSender { get; set; }

    [XmlAttribute()]
    public string WorkstationID { get; set; }

    [XmlAttribute()]
    public string RequestID { get; set; }

    [XmlAttribute()]
    public string OverallResult { get; set; }
}

ServiceResponse Type, no additional fields here

public class ServiceResponse : IFSFResponseBase
{

}

CardServiceResponse Type, Common Fields here

public class CardServiceResponse : IFSFResponseBase
{
    [XmlElement("Terminal")]
    public CardServiceResponseTerminal Terminal
    { get; set; }

    [XmlElement("Tender")]
    public CardServiceResponseTender Tender
    {
        get; set;
    }
}

CardServiceResponse Helper Classes

public class CardServiceResponseTerminal
{
    [XmlAttribute()]
    public string TerminalID { get; set; }

    [XmlAttribute()]
    public string TerminalBatchField { get; set; }

    [XmlAttribute()]
    public string STANField { get; set; }

}

public class CardServiceResponseTender
{
    [XmlElement("TotalAmount")]
    public CardServiceResponseTenderTotalAmount TotalAmount { get; set; }
}

public class CardServiceResponseTenderTotalAmount
{
    private string valueField;

    [XmlAttribute()]
    public string CashBackAmount
    {
        get;
        set;
    }

    [XmlAttribute()]
    public string Currency
    {
        get;
        set;
    }

    [XmlText()]
    public string Value
    {
        get
        {
            return this.valueField;
        }
        set
        {
            this.valueField = value;
        }
    }
}

Concrete LogoffResponse Class, no additional fields

[XmlRoot("ServiceResponse")]
public class LogoffResponse : ServiceResponse
{

}

Concrete LoginResponse Class, extra fields handled

[XmlRoot("ServiceResponse")]
public class LoginResponse : ServiceResponse
{
    [XmlAttribute()]
    public string POPID { get; set; }

    [XmlAttribute()]
    public string ReferenceNumber { get; set; }

    [XmlAttribute()]
    public string ProtocolVersion { get; set; }

    [XmlAttribute()]
    public string DeviceType { get; set; }

    [XmlAttribute()]
    public string SWChecksum { get; set; }

    [XmlAttribute()]
    public string CommunicationProtocol { get; set; }

    [XmlAttribute()]
    public string Model { get; set; }

    [XmlAttribute()]
    public string ApplicatioSoftwareVersion { get; set; }

    [XmlAttribute()]
    public string Manufacturer_Id { get; set; }

}

Concrete CardPaymentResponse Class, no additional fields

[XmlRoot("CardServiceResponse")]
public class CardPaymentResponse : CardServiceResponse
{

}

Concrete CardPaymentRefundResponse Class, Some extra data needed

[XmlRoot("CardServiceResponse")]
public class CardPaymentRefundResponse : CardServiceResponse
{
    [XmlAttribute()]
    public string ExtraValue { get; set; }
}

Helper class to remove Name space from response

public class NamespaceIgnorantXmlTextReader : XmlTextReader
{
    public NamespaceIgnorantXmlTextReader(System.IO.TextReader reader) : base(reader) { }

    public override string NamespaceURI
    {
        get { return ""; }
    }
}

With the code below, I don't know the actual response I will receive until its parsed, so I don't know what i can use in place of the concrete class (in this case ServiceResponse).

try
{
    XmlSerializer serializer = new XmlSerializer(typeof(ServiceResponse));

    StringReader sr = new StringReader(XMLResponse);

    NamespaceIgnorantXmlTextReader XMLWithoutNamespace = new NamespaceIgnorantXmlTextReader(sr);

    return (ServiceResponse)serializer.Deserialize(XMLWithoutNamespace);

}
catch (Exception ex)
{
    //Breakpoint code here for debugging ATM, To BE REMOVED
    throw;
}

That is fine for Logoff Response type, but not if it was a LoginResponse Type.

So I could Use LoginResponse but then if the response is a CardServiceResponse this will fail with an exception since the Root element is not SERVICERESPONSE but CardServiceResponse

try
{
    XmlSerializer serializer = new XmlSerializer(typeof(LoginResponse));

    StringReader sr = new StringReader(XMLResponse);

    NamespaceIgnorantXmlTextReader XMLWithoutNamespace = new NamespaceIgnorantXmlTextReader(sr);

    return (LoginResponse)serializer.Deserialize(XMLWithoutNamespace);

}
catch (Exception ex)
{
    //Breakpoint code here for debugging ATM, To BE REMOVED
    throw;
}

I tried the following hack and it will work but I'm wondering if there is a better way to achieve this

private object InternalParse(string sXML)
    {
        object oRetVal = null;

        try
        {
            XmlDocument xmlDoc = new XmlDocument();
            xmlDoc.LoadXml(sXML);
            XmlNode ReqType = xmlDoc.DocumentElement.Attributes.GetNamedItem("RequestType");

            Assembly asm = Assembly.GetExecutingAssembly();

            Type type = asm.GetType(asm.GetName().Name + "." + ReqType.Value + "Response");

            object instance = null;

            try
            {
                instance = (object)Activator.CreateInstance(type);
            }
            catch
            {
                //problem creating type from RequestType Name + Response appended on, the class
                //probably does not exist. Lets create the parent class
                type = asm.GetType(asm.GetName().Name + "." + xmlDoc.DocumentElement.Name);
                instance = (object)Activator.CreateInstance(type);
            }

            XmlSerializer serializer = new XmlSerializer(instance.GetType());

            StringReader sr = new StringReader(sXML);

            NamespaceIgnorantXmlTextReader XMLWithoutNamespace = new NamespaceIgnorantXmlTextReader(sr);

            oRetVal = serializer.Deserialize(XMLWithoutNamespace);
        }
        catch (Exception ex)
        {
            //Log ex here
        }

        return oRetVal;
    }

Thanks in Advance





Aucun commentaire:

Enregistrer un commentaire