views:

196

answers:

7

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 ;)

A: 

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
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
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
A: 

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
Tried it with same result :(Thx though!
HenriM
I would actually have guesses the C# compiler would warn about a useless cast directive.
Dykam
+1  A: 

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.

AZ
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
Depending on your performance constraints you could write a generic mapper using reflection
AZ
A: 

Try WCF, there is [KnownType].

Don't know, however, if thats possible with oldskool SOAP webservices, though.

stormianrootsolver
I'll have to look into that, but from what I googled it seems a lot like XmlInclude and SoapInclude. Might be wrong though.
HenriM
Of course it is, sorry. Seems to me as if you are stuck with either the less than elegant cast or with trying the XmlRoot - solution (which looks better to me).I'm not really experienced with the old webservice style, to be honest. Used them for a while but since Microsoft introduced the amazing, heavenly, divine WCF... ;-)
stormianrootsolver
Hehe I'll need to look into WCF I understand.Didn't get XmlRoot to work, so for now I need some sort of convert like Clone etc.Thanks for your reply.
HenriM
A: 

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
A: 

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
Thanks, but see comment on Nix's answer, I do need Sportstuff sometimes.
HenriM
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
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
+1  A: 

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
Agreed, I like this the most of the "non"-solutions :)+1 for you
HenriM