Solved

Is there a way to override a property in an interface when the interface is inherited?

Posted on 2014-09-16
12
111 Views
Last Modified: 2014-09-29
Environment:
MVC4
C#
VS2010

Problem:
I have an interface that I am considering my "Base" Interface that is defined as follows:
    public interface ITestModel
    {
        TestViewModel model { get; set; }

        void PopulateNewReferralRequestModel(Int32 ReferralTypeID, Int32 profileid, string UniqueKeyValues);
        void Save();
    }

    [MetadataType(typeof(MetaData_TestViewModel))]
    public partial class TestViewModel
    {
        public Int32 id { get; set; }
        public string name { get; set; }
        public string comments { get; set; }
    }

    public class MetaData_TestViewModel
    {
        [Required(ErrorMessage = "ID is required")]
        [DisplayName("Primary Key")]
        public Int32 id { get; set; }
        [Required(ErrorMessage = "Name is required")]
        [DisplayName("New Name")]
        public string name { get; set; }
        [DisplayName("Comments")]
        public string comments { get; set; }
    }

Open in new window


Using the interface:

If I do the following everything works as I want:
    public class BaseViewModel : ITestModel
    {
        private TestViewModel _model;
        public TestViewModel model
        {
            get { return _model; }
            set { _model = value; }
        }

        public void PopulateNewReferralRequestModel(Int32 ReferralTypeID, Int32 profileid, string UniqueKeyValues)
        {
            model = new TestViewModel();
            model.id = ReferralTypeID;
            model.name = "TEst";
            model.comments = "This is a comment";
        }

        public void Save()
        {
            int i = 1;
            return;
        }
    }

Open in new window


Now I want to inherit the "Base" Interface but change the definition of model property with a definition that is inherited from the TestViewModel as follows:
    public interface ITestModelB : ITestModel
    {
        new TestViewModelB model { get; set; }

    }

    public class TestViewModelB : TestViewModel
    {
        public int anotherfield { get; set; }
        public string anotherstringfield { get; set; }
    }

Open in new window


And use the interface as follows:
    public class AViewModel : ITestModelB
    {

        private TestViewModelB _model;
        public TestViewModelB model
        {
            get
            {
                return _model;
            }
            set
            {
                _model = value;
            }
        }

        public void PopulateNewReferralRequestModel(Int32 ReferralTypeID, Int32 profileid, string UniqueKeyValues)
        {
            model = new TestViewModelB();
            model.comments = "New model created";
            model.id = 1;
            model.name = "Referral Type 1";
            model.anotherfield = profileid;
            model.anotherstringfield = UniqueKeyValues;
        }
        public void Save()
        {
            int i = 1;
            return;
        }
    }    

Open in new window


The problem is when I build I get the following error:
Error	1	'InterfaceTest.Models.AViewModel' does not implement interface member 'InterfaceTest.Models.ITestModel.model'. 'InterfaceTest.Models.AViewModel.model' cannot implement 'InterfaceTest.Models.ITestModel.model' because it does not have the matching return type of 'InterfaceTest.Models.TestViewModel'.	

Open in new window


Why I want to do this:
I have a referral system that I am trying to build where all referrals have the same base properties and actions.  Each referral type also has properties and actions specific to the referral type.  I want the referrals to flow through the same controllers so that when a new referral is added, I only have to create a new interface to handle the referral.

Here is what I want to be able to do in my controllers:
ITestModel model = businessLogic.GetModel(referraltypeid);
model.PopulateNewReferralRequestModel(1, 1234, "DRC Policy, 12345678, 2/14/2014");

Open in new window

I would use a method in my businessLogic class to determine which model type to return based on the referral type.  All models would implement the ITestModel interface.  Doing this would allow me to handle all referrals through the same controller rather than having to have multiple controllers for each referral type.

What I need
Can anyone tell me how to overcome the error I'm getting when I build or suggest another solution for what I want to accomplish?

Any help is greatly appreciated!
0
Comment
Question by:dyarosh
  • 6
  • 5
12 Comments
 
LVL 74

Expert Comment

by:käµfm³d 👽
ID: 40327267
The problem you face is that your "base" interface defines a more generic base class than your "derived" interface provides. This would work fine for the get, but you would run into problems on the set. Think about it like this:  Can the compiler know for certain that everything you assign to TestViewModelB.model will be of type TestViewModelB? The "base" interface says that model assignments will be of type TestViewModel. But can the compiler be assured that the only things which inherit from TestViewModel are TestViewModelBs?

You can explicitly implement the "base" interface (lines 33 - 43):

public class AViewModel : ITestModelB
{

    private TestViewModelB _model;
    public TestViewModelB model
    {
        get
        {
            return _model;
        }
        set
        {
            _model = value;
        }
    }

    public void PopulateNewReferralRequestModel(Int32 ReferralTypeID, Int32 profileid, string UniqueKeyValues)
    {
        model = new TestViewModelB();
        model.comments = "New model created";
        model.id = 1;
        model.name = "Referral Type 1";
        model.anotherfield = profileid;
        model.anotherstringfield = UniqueKeyValues;
    }

    public void Save()
    {
        int i = 1;
        return;
    }

    TestViewModel ITestModel.model
    {
        get
        {
            return _model;
        }
        set
        {
            _model = (TestViewModelB)value;
        }
    }
}

Open in new window


...but you're really just masking the problem. If you ever assign something to model that is not of type TestViewModelB, then you will get an InvalidCastException on the set.
0
 

Author Comment

by:dyarosh
ID: 40327679
I see your point but let me explain things a different way which may illustrate my problem better.

In my example I define the model in the interface as follows:
 TestViewModel model { get; set; }

Open in new window


To make things easier to understand forget about that definition.  How would I define my interface if model could be an int in one case, a string in another case, a float in another case, etc.

I basically need to be able to support different types for the model but have the same methods operate on model regardless of type.  In other words, the PopulateNewReferralRequestModel method would set the the value of the model to an int if the model was an int, a string if the model was a string, etc.

I need to have a method (outside of the interface) that will return a class that implements the interface based on the referraltypeid.  So for example if the referraltypeid = 1, the class returned would be the interface that  implements the model as an int; referraltyped = 2 would return the interface that implements the model as a string; etc.
0
 
LVL 25

Expert Comment

by:apeter
ID: 40328858
Keep the type in your interface as "object".    Based on your referraltypeid you can cast to
Int or string etc.
0
 
LVL 74

Expert Comment

by:käµfm³d 👽
ID: 40329062
Keep the type in your interface as "object"
Eeek! That's a terrible suggestion, at least IMO. You lose any kind of type safety in switching to object. It's certainly feasible to do, but I would say to only fall back to that if nothing else works.

I would say that you should make the interface have a generic definition for the defined member(s). For instance:

public interface ITestModel<T>
{
    T model { get; set; }

    void PopulateNewReferralRequestModel(Int32 ReferralTypeID, Int32 profileid, string UniqueKeyValues);
    void Save();
}

Open in new window


Then when you define the class:

public class BaseViewModel : ITestModel<TestViewModel>
    {
        private TestViewModel _model;
        public TestViewModel model
        {
            get { return _model; }
            set { _model = value; }
        }

        public void PopulateNewReferralRequestModel(Int32 ReferralTypeID, Int32 profileid, string UniqueKeyValues)
        {
            model = new TestViewModel();
            model.id = ReferralTypeID;
            model.name = "TEst";
            model.comments = "This is a comment";
        }

        public void Save()
        {
            int i = 1;
            return;
        }
    }

Open in new window

0
 

Author Comment

by:dyarosh
ID: 40329225
kaufmed - I actually was going down that path but have a problem that I'm not sure how to resolve.

1. If I use your suggestion the model always has to be type TestViewModel.  How do I define it so the _model and model properties take the <T> type?

2. In my controller, how would I dynamically determine the <T> type based on a referral type id?  For example, I would want to do something like this but can't work out the syntax:
<????> model = GetModelType(referraltypeid);
model.PopulateModel(123, 456, "some keys");

public <????> GetModelType(int referraltypeid)
{ 
    switch(referraltypeid)
    {
        case 1:
            return new BaseViewModel<TestViewModelA>();
       case 2:
           return new BaseViewModel<TestViewModelB>();

Open in new window

0
 
LVL 74

Expert Comment

by:käµfm³d 👽
ID: 40329287
1. If I use your suggestion the model always has to be type TestViewModel.  How do I define it so the _model and model properties take the <T> type?
Assuming I correctly understand what you are asking, you can make the base class generic as well:

e.g.

public class BaseViewModel<T> : ITestModel<T>
{
    private T _model;
    public T model
    {
        get { return _model; }
        set { _model = value; }
    }

    public void PopulateNewReferralRequestModel(Int32 ReferralTypeID, Int32 profileid, string UniqueKeyValues)
    {
        model = new T();
        model.id = ReferralTypeID;
        model.name = "TEst";
        model.comments = "This is a comment";
    }

    public void Save()
    {
        int i = 1;
        return;
    }
}

Open in new window


However, this code alone won't work because the compiler cannot guarantee that what you pass in for T actually has those id, name, and comments properties. A generic type constraint and most likely another interface (or base class) would be necessary.

2. In my controller, how would I dynamically determine the <T> type based on a referral type id?  For example, I would want to do something like this but can't work out the syntax:
Give me a little while to think on this.
0
What Should I Do With This Threat Intelligence?

Are you wondering if you actually need threat intelligence? The answer is yes. We explain the basics for creating useful threat intelligence.

 

Author Comment

by:dyarosh
ID: 40335541
Kaufmed is still thinking about a solution to my problem.  I am waiting for his suggestions if any.
0
 
LVL 74

Expert Comment

by:käµfm³d 👽
ID: 40338177
My apologies, I lost track of this one. I am looking at it now. Thanks for prodding me  = )
0
 
LVL 74

Expert Comment

by:käµfm³d 👽
ID: 40338257
OK, here's some modified code. It's slightly different from the earlier version in that I carry the generic typing all the way down the inheritance chain.

using System;

namespace _28519523
{
    class Program
    {
        static void Main(string[] args)
        {
            Program p = new Program();
            ITestModel<TestViewModel> model = p.GetModelType(2);

            model.PopulateNewReferralRequestModel(123, 456, "some keys");
        }

        public ITestModel<TestViewModel> GetModelType(int referraltypeid)
        {
            switch (referraltypeid)
            {
                case 1:
                    return new BaseViewModel<TestViewModel>();
                case 2:
                    return new BaseViewModel<TestViewModelB>();
                default:
                    throw new ArgumentException("Invalid value.", "referraltypeid");
            }
        }

        public interface ITestModel<out T>
        {
            T model { get; }

            void PopulateNewReferralRequestModel(Int32 ReferralTypeID, Int32 profileid, string UniqueKeyValues);
            void Save();
        }

        public partial class TestViewModel
        {
            public Int32 id { get; set; }
            public string name { get; set; }
            public string comments { get; set; }
        }

        public class BaseViewModel<T> : ITestModel<T>
            where T : TestViewModel, new()
        {
            private T _model;
            public T model
            {
                get { return _model; }
                set { _model = value; }
            }

            public void PopulateNewReferralRequestModel(Int32 ReferralTypeID, Int32 profileid, string UniqueKeyValues)
            {
                model = new T();
                model.id = ReferralTypeID;
                model.name = "TEst";
                model.comments = "This is a comment";
            }

            public void Save()
            {
                int i = 1;
                return;
            }
        }

        public interface ITestModelB<T> : ITestModel<T>
        {
            new T model { get; set; }

        }

        public class TestViewModelB : TestViewModel
        {
            public int anotherfield { get; set; }
            public string anotherstringfield { get; set; }
        }

        public class AViewModel<T> : ITestModelB<T>
            where T : TestViewModelB, new()
        {

            private T _model;
            public T model
            {
                get
                {
                    return _model;
                }
                set
                {
                    _model = value;
                }
            }

            public void PopulateNewReferralRequestModel(Int32 ReferralTypeID, Int32 profileid, string UniqueKeyValues)
            {
                model = new T();
                model.comments = "New model created";
                model.id = 1;
                model.name = "Referral Type 1";
                model.anotherfield = profileid;
                model.anotherstringfield = UniqueKeyValues;
            }

            public void Save()
            {
                int i = 1;
                return;
            }
        }
    }
}

Open in new window


I've made a couple of other modifications also. First, I've made the interface's generic type covariant. This tells the runtime that the BaseViewModel<TestViewModelB> being returned in line 22 is kosher. In order to satisfy this covariance requirement, I also had to remove the set in line 30. It doesn't look like you're using this anyway. If you need it, you won't be able to add it back, but you can add a setter method to achieve the same. The backing field can be written to all you want--just the property itself cannot be assigned to.

Try that out and see if it fits your need. It appears to work for this small example.
0
 

Author Comment

by:dyarosh
ID: 40338967
Getting closer.  I ran into 2 problems.

1.             ITestModel<TestViewModel> model = p.GetModelType(2);
When executing this line, it returns TestModelB but when this line executes
               model.PopulateNewReferralRequestModel(123, 456, "some keys");
It executes the PopulateNewReferralRequestModel in the TestViewModel and not in TestViewModelB so the new fields are not set nor can I see them in the model.

2. I use this line to send the data to the View:  return View(referral3.model);  In the View I have the following: @model InterfaceTest.Models.TestViewModel.  Data displays as I want.  The problem is when I post the form I get the following error: Cannot create an instance of an interface.   Here is how I have the [HttpPost] defined:
        [HttpPost]
        public ActionResult Index(ITestModel<TestViewModel> model)
        {
            return View();
        }

Open in new window

How do I define the post parameter so I can have a single [HttpPost] method and not one for each model type?
0
 
LVL 74

Accepted Solution

by:
käµfm³d   👽 earned 500 total points
ID: 40348671
...It executes the PopulateNewReferralRequestModel in the TestViewModel and not in TestViewModelB
That's probably my fault. The factory method is returning the wrong object type. It should be AViewMode instead of BaseViewModel:

switch (referraltypeid)
{
    case 1:
        return new BaseViewModel<TestViewModel>();
    case 2:
        return new AViewModel<TestViewModelB>();
    default:
        throw new ArgumentException("Invalid value.", "referraltypeid");
}

Open in new window


How do I define the post parameter so I can have a single [HttpPost] method and not one for each model type?
I don't know much about the nitty-gritty details of model binding, but I suspect you may have to write your own custom model binder.
0
 

Author Closing Comment

by:dyarosh
ID: 40349697
Thank you for your help.
0

Featured Post

Better Security Awareness With Threat Intelligence

See how one of the leading financial services organizations uses Recorded Future as part of a holistic threat intelligence program to promote security awareness and proactively and efficiently identify threats.

Join & Write a Comment

Suggested Solutions

Title # Comments Views Activity
How can I use this extension method? 8 36
C# Reverse int in fast ways 6 28
Round up to 100% in .NET 10 46
XML & .net 5 21
Article by: Ivo
C# And Nullable Types Since 2.0 C# has Nullable(T) Generic Structure. The idea behind is to allow value type objects to have null values just like reference types have. This concerns scenarios where not all data sources have values (like a databa…
Real-time is more about the business, not the technology. In day-to-day life, to make real-time decisions like buying or investing, business needs the latest information(e.g. Gold Rate/Stock Rate). Unlike traditional days, you need not wait for a fe…
In this seventh video of the Xpdf series, we discuss and demonstrate the PDFfonts utility, which lists all the fonts used in a PDF file. It does this via a command line interface, making it suitable for use in programs, scripts, batch files — any pl…
Illustrator's Shape Builder tool will let you combine shapes visually and interactively. This video shows the Mac version, but the tool works the same way in Windows. To follow along with this video, you can draw your own shapes or download the file…

746 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

11 Experts available now in Live!

Get 1:1 Help Now