0

I have a class Employee, and FulltimeEmployee and ParttimeEmployee classes derived from it. I apply a [KnownType] attribute on the Employee class. The Employee class is used as a DataContract for a WCF Service.

I have an MVC application as a WCF client. I successfully retrieve either FulltimeEmployee or ParttimeEmployee as needed, and use it as a model for the view. The model is of a base type Employee. The hierarchy is preserved:

[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.CodeDom.Compiler.GeneratedCodeAttribute("dotnet-svcutil", "0.5.0.0")]
[System.Runtime.Serialization.DataContractAttribute(Name="FullTimeEmployee", Namespace="http://schemas.datacontract.org/2004/07/EmployeeService")]
public partial class FullTimeEmployee : EmployeeService.Employee
{
    ...

But when I try to save a new employee, I get an exception:

InvalidCastException: Unable to cast object of type 'EmployeeService.Employee' to type 'EmployeeService.PartTimeEmployee'.

Here is the code:

Employee emp = null;
if(employee.Type == EmployeeType.FullTimeEmployee)
{
     ...
}
else
{
           emp = new PartTimeEmployee
            {
                Name = employee.Name,
                Gender = employee.Gender,
                Type = employee.Type,
                DateOfBirth = employee.DateOfBirth,
                HourlyPay = ((PartTimeEmployee)employee).HourlyPay,
                HoursWorked = ((PartTimeEmployee)employee).HoursWorked
            };

Why does this happen?

halfer
  • 19,824
  • 17
  • 99
  • 186
David Shochet
  • 5,035
  • 11
  • 57
  • 105
  • 1
    "when I try to save a new employee", I don't see anything related to saving. Where exactly does it fail? – Camilo Terevinto Mar 01 '18 at 14:45
  • 4
    Usually when such a construct is used (checking if type == x etc) indicates bad design – Neijwiert Mar 01 '18 at 14:48
  • I guess it fails on WCF side? If so, then you didn't show any WCF code. – Evk Mar 01 '18 at 14:49
  • 1
    There's not enough information here about the classes and their hierarchies, and the actual cast code you mention isn't even there. If `Employee` is not an abstract class, and you have an actual instantiated object of this type, then obviously you can't just cast it to a sub-type. Because it isn't. – Nyerguds Mar 01 '18 at 14:50
  • @Camilo Terevinto It is in the controller, [HttpPost] Edit() – David Shochet Mar 01 '18 at 14:52
  • @Evk No, it is on client side, in the controller. – David Shochet Mar 01 '18 at 14:53
  • And... where do you expect me to be able to see that code? As it was mentioned before, you didn't post any code that would throw such exception – Camilo Terevinto Mar 01 '18 at 14:54
  • @Nyegruds I showed the cast on this line: emp = new PartTimeEmployee ... As emp is of base type Employee, and I am trying to assign to it an object of a derived type, I don't understand why it doesn't work. – David Shochet Mar 01 '18 at 14:56
  • Are you sure that's the line where the error is at? because the error you posted indicates that a cast from `Employee` to `PartTimeEmployee` is being done, not the other way around which is what would happen on that line you say the problem is at – Neijwiert Mar 01 '18 at 14:58
  • Please don't lie to us... https://dotnetfiddle.net/kUn1X8 – Camilo Terevinto Mar 01 '18 at 14:59
  • @Camilo Terevinto Sorry, I added code. It is on line HourlyPay = ((PartTimeEmployee)employee).HourlyPay – David Shochet Mar 01 '18 at 15:00
  • So... you see what happens when you don't create a [mcve]? This has nothing to do with WCF nor with ASP.NET MVC. It's a simple OOP problem – Camilo Terevinto Mar 01 '18 at 15:01
  • You are right, I was not attentive enough. But is there any work around this problem? I do pass a derived class object to the view, but I guess it is lost on the way back to the controller. – David Shochet Mar 01 '18 at 15:02
  • Am I seeing correctly that you made either A: a property that holds the object's type or B: a property that holds an enum for object types? Cause both just seem really stupid.. – Neijwiert Mar 01 '18 at 15:03
  • Either way you're not setting the property correctly somewhere or the check is failing because you might be doing a reference compare on the type object which might not do what you think it does – Neijwiert Mar 01 '18 at 15:05
  • I think this question (at least its answer) is quite relevant to your situation: https://stackoverflow.com/q/48880134/5311735 – Evk Mar 01 '18 at 15:07
  • @Neijwiert, a property holds an enum specifying the type of the object, but this property is seen correctly, as it is in the base class. It may be stupid, but I am just trying to follow a WCF tutorial, and this issue is just a side effect. – David Shochet Mar 01 '18 at 15:07
  • 1
    You said that model for view in MVC is the base type `Employee`. That means view will submit an instance of `Employee` back to the controller. At that point of time you don't have any information about the child type of `Employee` so it can not be casted to child type. That's the basic OOP funda. – Chetan Mar 01 '18 at 15:09
  • Are you sure that `FullTimeEmployee` and `PartTimeEmployee` are the only types that can be instantiated of base type `Employee`? If not then that would be the problem or again, you're not setting the property properly. Cause your error seems to indicate that `Employee` is not an abstract class. – Neijwiert Mar 01 '18 at 15:10
  • @Chetan Ranpariya, I understand this. I just wonder if there is any work around this. – David Shochet Mar 01 '18 at 15:11
  • @Evk, thank you, it seems like this just cannot be done the way I tried to do it... – David Shochet Mar 01 '18 at 15:12
  • 1
    Basically you lose the properties of child types with this approach. So You need to have different views and post actions for each child type as model. – Chetan Mar 01 '18 at 15:16
  • You can't read properties that simply don't exist on the object... that's pretty obvious. Why are these two lines even there? If the object is _possibly_ of the `PartTimeEmployee` subclass, the answer is simple... **check it in advance before assigning these.** `if (employee is PartTimeEmployee) { ... }` – Nyerguds Mar 01 '18 at 15:20
  • 1
    Other way is to have a model class of your own in MVC project which will have all the properties of Emploee class and its child classes. You populate this class object from the object coming from WCF service and render all the properties in the view. On post controller you check the Type property and create object of either of child classes and populate it's properties from the model object and send to WCF. – Chetan Mar 01 '18 at 15:25
  • @Nyerguds, the problem is that with this approach employee will never be anything but the base Employee object... – David Shochet Mar 01 '18 at 16:46
  • @DavidShochet That's your own problem. Your code doesn't give any clues where the `employee` object comes from. If it really always is just an `Employee` and not a `PartTimeEmployee`, you'll never have these properties anyway. So what do you want to do then? Magically make up properties that don't exist? As a rule you _always_ check before casting. – Nyerguds Mar 01 '18 at 19:12

1 Answers1

1

I will not enter into detail as it was already mentioned in the comments that this is horrible design, and since you mentioned that you are following a tutorial, I will not go hard on you.

A quick, dirty fix is to load all properties regardless of type:

public class BadPractice
{
    ... full time properties
    ... part time properties
}

Then, load it:

var badPractice = new BadPractice();

if (...)
{
    badPractice.PartTimeProperty = ...;
}
else
{
    badPractice.FullTimeProperty = ...;
}

return View(badPractice);

And ensure it's returned to the Controller:

public ActionResult HorriblyBad(BadPractice badPractice)
{
    if (badPractice.Type == EmployeeType.FullTime)
    {
        ...
    }
}

Notice all the work you have to do due to a bad design. I wouldn't continue to watch those tutorials.

Camilo Terevinto
  • 31,141
  • 6
  • 88
  • 120
  • Thank you for the answer. It is actually a good tutorial on WCF. May I ask as a bonus what design would you suggest in such a case? – David Shochet Mar 01 '18 at 16:48