ASMX sample response message using serialized XML doesn't account for derived members
I've noticed something in the sample response XML generated by an .ASMX file in ASP.NET 1.x (at the very least, in 1.0). Specifically, when using serialization for custom XML, the Web service apparently does not take into account data members derived from a base class when reporting what a response message will look like. Don't get me wrong, the final XML message itself is perfect, but I've found inaccuracies in the .ASMX response message.
For instance, here's an example I ran into when working on a statistic service for a local football league. I used a base class "Player", containing only properties. The classes "Offense", "Defense" and "Special Teams", all contain statistical information, and inherit from Player to get the shared properties Name, Position, JerseyNumber and Team:
public class Player
{
private string _name;
private string _position;
private string _jerseyNumber;
private string _team;
public string Name
{
get { return this._name; }
set { this._name = value; }
}
public string Position
{
get { return this._position; }
set { this._position = value; }
}
public string JerseyNumber
{
get { return this._jerseyNumber; }
set { this._jerseyNumber = value; }
}
public string Team
{
get { return this._team; }
set { this._team = value; }
}
}
[XmlRoot("OffensiveStats")]
public class Offense : Player
{
// implementation...removed for brevity
}
[XmlRoot("DefenseStats")]
public class Defense : Player
{
// implementation...removed for brevity
}
[XmlRoot("SpecialTeamsStats")]
public class SpecialTeams : Player
{
public int Punts
{
get { return this._punts; }
set { this._punts = value; }
}
public int PuntReturns
{
get { return this._puntReturns; }
set { this._puntReturns = value; }
}
public int PuntReturnTDs
{
get { return this._puntReturnTDs; }
set { this._puntReturnTDs = value; }
}
public int KickoffReturns
{
get { return this._kickoffReturns; }
set { this._kickoffReturns = value; }
}
public int KickoffReturnTDs
{
get { return this._kickoffReturnTDs; }
set { this._kickoffReturnTDs = value; }
}
// default class constructor...required here for serialization
public SpecialTeams()
{}
// overloaded class constructor
public SpecialTeams(string name,string position,string jerseynumber,string team,int punts,int puntReturns,int puntReturnTDs,int kickoffReturns,int kickoffReturnTDs)
{
base.Name = name;
base.Position = position;
base.JerseyNumber = jerseynumber;
base.Team = team;
this._punts = punts;
this._puntReturns = puntReturns;
this._puntReturnTDs = puntReturnTDs;
this._kickoffReturns = kickoffReturns;
this._kickoffReturnTDs = kickoffReturnTDs;
}
}
[XmlRoot("Leaderboard")]
public class Leaderboard
{
[XmlArray("Offense")]
[XmlArrayItem("Player")]
public Offense[] offensePlayers;
[XmlArray("Defense")]
[XmlArrayItem("Player")]
public Defense[] defensePlayers;
[XmlArray("SpecialTeams")]
[XmlArrayItem("Player")]
public SpecialTeams[] specialteamsPlayers;
}
As you can see, the Leaderboard class contains arrays of Offense, Defense and SpecialTeams objects to generate rosters of the statistical leaders in those respective categories, and it's this latter class that's returned by the Web service. Pretty cut-and-dry stuff, and far from groundbreaking. But here's where I noticed a gotcha: while the eventual XML generated contains all the fields and properties from the subclasses, as one would expect, the .ASMX file does not include the fields within the base class.
In my particular implementation, I'm reading values from a DB, which are read into the dataset and then passed as arguments to overloaded constructors of the Offense, Defense and Special Teams classes.
However, this is the sample response the .ASMX file generates:
<?xml version="1.0" encoding="utf-8"?>
<Leaderboard xmlns="http://stats">
<Offense>
<Player>
<TotalTDs>int</TotalTDs>
<RushingAttempts>int</RushingAttempts>
<RushingYardage>int</RushingYardage>
<RushingTDs>int</RushingTDs>
<PassingAttempts>int</PassingAttempts>
<PassingYardage>int</PassingYardage>
<PassingTDs>int</PassingTDs>
<Receptions>int</Receptions>
<ReceivingYardage>int</ReceivingYardage>
<ReceivingTDs>int</ReceivingTDs>
</Player>
<Player>
<TotalTDs>int</TotalTDs>
<RushingAttempts>int</RushingAttempts>
<RushingYardage>int</RushingYardage>
<RushingTDs>int</RushingTDs>
<PassingAttempts>int</PassingAttempts>
<PassingYardage>int</PassingYardage>
<PassingTDs>int</PassingTDs>
<Receptions>int</Receptions>
<ReceivingYardage>int</ReceivingYardage>
<ReceivingTDs>int</ReceivingTDs>
</Player>
</Offense>
<Defense>
<Player>
<Interceptions>int</Interceptions>
<InterceptionTDs>int</InterceptionTDs>
<FumbleRecoveryTDs>int</FumbleRecoveryTDs>
<Tackles>int</Tackles>
<Sacks>int</Sacks>
<Safeties>int</Safeties>
</Player>
<Player>
<Interceptions>int</Interceptions>
<InterceptionTDs>int</InterceptionTDs>
<FumbleRecoveryTDs>int</FumbleRecoveryTDs>
<Tackles>int</Tackles>
<Sacks>int</Sacks>
<Safeties>int</Safeties>
</Player>
</Defense>
<SpecialTeams>
<Player>
<Punts>int</Punts>
<PuntReturns>int</PuntReturns>
<PuntReturnTDs>int</PuntReturnTDs>
<KickoffReturns>int</KickoffReturns>
<KickoffReturnTDs>int</KickoffReturnTDs>
</Player>
<Player>
<Punts>int</Punts>
<PuntReturns>int</PuntReturns>
<PuntReturnTDs>int</PuntReturnTDs>
<KickoffReturns>int</KickoffReturns>
<KickoffReturnTDs>int</KickoffReturnTDs>
</Player>
</SpecialTeams>
</Leaderboard>
Note that the fields inherent to each class are represented, but the inherited fields (Name, Position, JerseyNumber, and Team) aren't there. This is consistent through the sample response content generated for requests made through SOAP, HTTP-GET and HTTP-POST. However, everything comes out perfectly, as expected, when executing the method and examining the XML. Certainly, a consumer of the Web service can see the true XML returned by invoking the method, but it makes for some unexpected surprises and misdirection when the true data to be returned isn't reported.
If this was the result of ignorant on my part or a genuine flaw in the .NET Framework, I'd just like to know which. Either way, it didn't produce the results I expected, although fortunately, it still worked perfectly in the final wash. I researched this for awhile, and tried a few different approaches, and I thought I got it right.
Anyone else run into this?