6

I have a class Car and a derived SportsCar: Car
Something like this:

public class Car
{
    public int TopSpeed{ get; set; }
}


public class SportsCar : Car
{
    public string GirlFriend { get; set; }
}

I have a webservice with methods returning Cars i.e:

[WebMethod]
public Car GetCar()
{
    return new Car() { TopSpeed = 100 };
}

It returns:

<Car>
<TopSpeed>100</TopSpeed>
</Car>

I have another method that also returns cars like this:

[WebMethod]
public Car GetMyCar()
{
    Car mycar = new SportsCar() { GirlFriend = "JLo", TopSpeed = 300 };
    return mycar;
}

It compiles fine and everything, but when invoking it I get:
System.InvalidOperationException: There was an error generating the XML document. ---> System.InvalidOperationException: The type wsBaseDerived.SportsCar was not expected. Use the XmlInclude or SoapInclude attribute to specify types that are not known statically.

I find it strange that it can't serialize this as a straight car, as mycar is a car.

Adding XmlInclude on the WebMethod of ourse removes the error:

[WebMethod]
[XmlInclude(typeof(SportsCar))]
public Car GetMyCar()
{
    Car mycar = new SportsCar() { GirlFriend = "JLo", TopSpeed = 300 };
    return mycar;
}

and it now returns:

<Car xsi:type="SportsCar">
    <TopSpeed>300</TopSpeed>
    <GirlFriend>JLo</GirlFriend>
</Car>

But I really want the base class returned, without the extra properties etc from the derived class.

Is that at all possible without creating mappers etc?

Please say yes ;)

HenriM
  • 151
  • 2
  • 4
  • What do you mean creating mappers? are you talking about annotating the data contract? – Nix May 12 '10 at 11:54
  • Hi! sorry to be unclear, I mean without creating manual conversion methods to/from car/sportscar. i.e private static Car Map(SportsCar sc) { return new Car() { TopSpeed = sc.TopSpeed } } or something ugly like that :) – HenriM May 12 '10 at 12:13

6 Answers6

2

I would implement a copy constructor in the base class.

    public class Car
    {
        public int TopSpeed { get; set; }

        public Car(Car car)
        {
            TopSpeed = car.TopSpeed;
        }

        public Car()
        {
            TopSpeed = 100;
        }
    }

    public class SportsCar : Car
    {
        public string GirlFriend { get; set; }
    }

Then you can return a new Car based on the SportsCar in the GetMyCar-method. I think this way clearly express the intent of the method.

    public Car GetMyCar()
    {
        var sportsCar = new SportsCar { GirlFriend = "JLo", TopSpeed = 300 };
        return new Car(sportsCar);
    }
Olsenius
  • 313
  • 1
  • 2
  • 7
1

Do this:

[WebMethod]
public Car GetMyCar()
{
    Car mycar = new SportsCar() { GirlFriend = "JLo", TopSpeed = 300 };
    return new Car() {TopSpeed = mycar.TopSpeed};
}

The reason is that XMLSerializer inspects the GetType() type of the object and expects it to be the same as the declared one.

I know it's a pain but i don't know of an alternative.

Adrian Zanescu
  • 7,907
  • 6
  • 35
  • 53
  • Thanks, and yes that will work. But it is essentially the same as using a Mapper method which I called it, and although I will survive with this and the Car-object, it is quite tedious and ugly with a real-world object. I was looking for a more general reusable solution though. – HenriM May 12 '10 at 12:50
  • Depending on your performance constraints you could write a generic mapper using reflection – Adrian Zanescu May 14 '10 at 14:33
0

Either use XmlIgnoreAttribute on the attributes you want to ignore. Or the even more painful way would be to implement a custom serialization for your attributes.

Good luck.

public class SportsCar : Car
{
  [XmlIgnoreAttribute]
  public string GirlFriend { get; set; }
}
Nix
  • 57,072
  • 29
  • 149
  • 198
  • Thanks, but the trouble is that in real-world I have other methods that actually returns the "SuperCar", and then the extra attributes are needed. – HenriM May 12 '10 at 12:33
  • Web services work best in a "document exchange" type approach. Then you ahve classes for the document - and map. Anything else just has problems. – TomTom May 14 '10 at 11:12
0

Just a stab, but have you tried this? Cast on the return.

[WebMethod]
public Car GetMyCar()
{
    Car mycar = new SportsCar() { GirlFriend = "JLo", TopSpeed = 300 };
    return (Car)mycar;
}
Jonathan
  • 651
  • 8
  • 14
0

The other comments and answers here got me thinking, and if I need to make a Mapper-method so be it:

public class Car: ICloneable
{
    public int TopSpeed{ get; set; }
    public object Clone()
    {
        return new Car() { TopSpeed = this.TopSpeed };
    }

}

and the webmethod:

[WebMethod]
public Car GetMyNewCar()
{
    Car mycar = new SportsCar() { GirlFriend = "HiLo", TopSpeed = 300 };            
    return (Car)mycar.Clone();
}

This works as expected:

<Car>
    <TopSpeed>300</TopSpeed>
</Car>

This defeats the purpose of having a derived object in my view, but until someone comes up with another solution thats the way I'll fly...

HenriM
  • 151
  • 2
  • 4
0

If you want it to always serialize as "Car" instead of "SportsCar" add an [XmlRoot("Car")] to it.

public class Car {
  //stuff
}

[XmlRoot("Car")]
public class SportsCar {
  //Sporty stuff
}

Edit: Using the XmlSerializer like this:

XmlSerializer ser = new XmlSerializer(typeof(SportsCar));
SportsCar car = new SportsCar()
//stuff
ser.Serialize(Console.out, car)

you should get

<Car>
  <TopSpeed>300</TopSpeed>
  <GirlFriend>JLo</GirlFriend>
</Car>
Josh Sterling
  • 838
  • 7
  • 12
  • Thanks, but see comment on Nix's answer, I do need Sportstuff sometimes. – HenriM May 12 '10 at 13:29
  • Hmm. tried this like this: [XmlRoot("Car")] public class SportsCar : Car {... Got the same exception I initially had, InvalidOperationSomething... Might be doing it wrong though. – HenriM May 12 '10 at 13:54
  • Did you create the serializer with the SportsCar type or the Car type? Regardless, if you DO need the sportscar sometimes (not just it's elements), this wont work unless you wanna mess with XmlAttributeOverrides when you create your serializer (huge pita) This is of course assuming your using the XmlSerializer, if your usinc wcf ignore everything ive said :P – Josh Sterling May 12 '10 at 14:03