0

I am attempting to access the concrete implementation of an abstract object within the bound model of an ASP.NET MVC view, but I don't know how to bind to that property's properties that are part of its concrete implementation.

public abstract class BasePageVO
{
    public string DisplayText { get; set; }
    public BaseFoo FooItem { get; set; }
}

public class ConcretePageVO : BasePageVO
{
    // some properties

    // in a concrete page, the concrete implementation of BaseFoo is known at compile time.
}

public abstract class BaseFoo
{
    public string FooText { get; set; }
}

public class ConcreteFoo : BaseFoo
{
    public string ConcreteProperty { get; set; }
}

The kicker, and why I have adopted such an unusual class structure, is that the shared partial also needs to know about the FooItem, but only its abstract properties. A much-simplified outline of the structure is below:

DisplayFoo.cshtml:

@model ConcretePageVO

@using (Html.BeginForm("Submit", "Foo", FormMethod.Post)
{
    @Html.Partial("DisplayFooShared", @Model)
    @Html.EditorFor(x => x.FooItem.ConcreteProperty) @* This fails *@
}

DisplayFooShared.cshtml:

@model BasePageVO

<div>
    @Html.DisplayFor(x => x.DisplayText)
    @Html.EditorFor(x => x.FooItem.FooText)
    @* More properties... *@
</div>

Is there a way to indicate to Razor that the BaseFoo object is of an expected concrete type, and still benefit from how I perform model binding in the shared partial view? I thought I was on the right track with creating my own custom model binding for BaseFoo, like in Darin's answer here, but ASP.NET throws a compilation error that Razor doesn't know what to do with the property name, since it isn't defined.

Is there a way to accomplish binding to these implementation-specific properties and still benefit from the strong typing ASP.NET MVC affords? Was I on the right track with custom binding, but merely botched the implementation? Thanks in advance for any advice.

EDIT: I replaced @Html.EditorFor(x => x.FooItem.ConcreteProperty) with @Html.EditorFor(x => (x.FooItem as ConcreteFoo).ConcreteProperty), which causes the binding to succeed. Is there still a better way to do this, though?

c0nn
  • 317
  • 3
  • 18

2 Answers2

2
    public abstract class BaseFoo
    {
        public virtual string FooText { get; set; }
    }

    public class ConcreteFoo : BaseFoo
    {
        public override string FooText { get; set; }
    }
Michal Ciechan
  • 13,492
  • 11
  • 76
  • 118
  • This works, but I would have concerns about implementing it for any concrete classes of sufficient complexity. What if there are a dozen unique attributes for several concrete implementations? I wouldn't want to have dozens of virtual attributes on the base class, that it doesn't need to know about. – c0nn Mar 02 '15 at 22:50
  • then cast would be the way forward. What about setting it as @model ConcreteFoo in the Razor .cshtml? – Michal Ciechan Mar 02 '15 at 23:51
  • A better answer would be for FootText in BaseFoo to be abstract instead of virtual, thereby forcing you to override, as opposed to making it optional – TruthOf42 Jul 10 '18 at 20:03
1

What you are doing violates the principles of object oriented programming. You've got a base class and you're trying to treat it like a derived class, and that's simply a huge red flag. If you need the derived class, then you should have a derived class in your model.

Your only option otherwise is to cast, and that is a huge code smell, and potential flaw if the object isn't actually the derived object you think it is.

More than likely, your real problem is that you're trying to use some kind of domain model as your view model. You should customize your view model to be exactly the model you need for that view. Then you should map your domain model to your view model and do any conversions that are necessary at that point.

Erik Funkenbusch
  • 92,674
  • 28
  • 195
  • 291
  • In order to reduce code duplication across similar views, I had hoped to be able to reliably enforce this abstraction. There are several views with large amounts of functionality shared in partials, that depend on the properties in the base page model. I suppose, in line with your suggestion, I should abstract that functionality to a sub-model that exactly matches what is needed in those partials. Thanks for your help. – c0nn Mar 03 '15 at 14:43
  • @c0nn - reducing duplication is not a good reason to break the rules of OOP (such as LSP). There are a number of better ways to achieve reduced duplication without doing so, but it's very hard to suggest alternatives with contrived examples like you've shown. – Erik Funkenbusch Mar 03 '15 at 15:33