2

Brief: I'm creating an MVC application in which I need to display a variety of types documents, some containing more author information than others.

What I wanna do: My approach is to have a generic "view document" view, which dynamically displays the document in a format dictated by the shape/type of the object passed to it.

Example: A simple document would be loaded into a SimpleDocumentViewModel, and display as such. However I'd like to load a larger type of document into an ExtendedDocumentViewModel, bringing with it additional information about both the document and the author. The view(s) would then display the appropriate data based on the object it receives.

Where I'm at now: In this vein I've created the following interfaces and classes, but I'm stuck as to how to return/identify the more specific return types in their derived classes.

abstract class BaseDocumentViewModel : DocumentViewModel, IDocumentViewModel
{
    public int DocumentId { get; set; }
    public string Body { get; set; }
    public IAuthorViewModel Author { get; set; }
}

class SimpleDocumentViewModel : BaseDocumentViewModel
{
}

class ExtendedDocumentViewModel : BaseDocumentViewModel
{
    public new IAuthorExtendedViewModel Author { get; set; }
}

interface IAuthorViewModel
{
    int PersonId { get; set; }
    string Name { get; set; }
}

interface IAuthorExtendedViewModel : IAuthorViewModel
{
    int ExtraData { get; set; }
    int MoreExtraData { get; set; }
}

Question: So my question is; how best can I get the specific types from the fully implemented classes, or do I need to return the base types and query it all in the view? Or am I off my head and need to go back to the drawing board?

Edits:

I know that c# doesn't support return type covarience, but hoped that there may be another way of returning/identifying the derived types so that I don't have to query them all in the view.

My current solution would be to always return the base types, and have a separate view for each concrete type that simply casts each object to the correct type, only querying those that could differ. Perhaps this is the best solution end of, but it feels very inelegant.

Matthew Hudson
  • 1,306
  • 15
  • 36

3 Answers3

1

Usually you can do a simple "is" check. So you can have conditional rendering in your views, for example:

@if(Model is ExtendedDocumentViewModel)
{
  // render ExtendedDocumentViewModel html here
}

Type checking is usually considered an anti pattern, however I am not sure if there is a much better approach to this problem. If you are using .NET Core you can also check the subclass tag here http://examples.aspnetcore.mvc-controls.com/InputExamples/SubClass .

Halcyon
  • 611
  • 7
  • 7
  • 1
    thankyou for your response, I'm aware of this and do intend to use it where necessary. However the goal of this question is to try to return/identify these derived types in a better way so that I use that as little as possible. I know that C# doesn't support return type covariance, but hoped there may be another way round it. – Matthew Hudson Jun 12 '18 at 16:23
  • 1
    I think this is essentially the correct answer, and that I'm being a moron. I want to have the dynamic type of an object dictate the way it displays, but don't want to dynamically type check everything... In hindsight I probably can't have one without the other. Doh! Will continue investigating for now, but think you've hit the nail on the head. Thanks again! :) – Matthew Hudson Jun 12 '18 at 16:45
0

Possible cleaner option is to just have a signature in the interface called GetView that each document has to implement. This way each document type has their own way of implementing the function and the calling function knows that each document has a function GetView. This method will work well if every document has a unique way of viewing the document. However if some documents share the same way of getting views, then may I suggest creating each View type into their own class and you can assign the views types to each document. I suggest looking into the strategy pattern.

First suggestion:

class SimpleDocumentViewModel : IAuthorViewModel
{
      view GetView()
      {
          ... do document specific stuff
          ... return view
      }
}

class ExtendedDocumentViewModel : IAuthorViewModel
{
      int ExtraData { get; set; }
      int MoreExtraData { get; set; }

      view GetView()
      {
          ... do document specific stuff
          ... return view
      }
}

interface IAuthorViewModel
{
    view GetView();
}

Second suggestion:

class SimpleDocumentViewModel : IAuthorViewModel
{
      public viewType1 view {get;set;}

      public SimpleDocumentViewModel(viewType1 viewIn,etc...)
      {
          view = viewIn;
      } 
      view GetView()
      {
          return view.GetView();
      }
}

class ExtendedDocumentViewModel : IAuthorViewModel
{
      int ExtraData { get; set; }
      int MoreExtraData { get; set; }
      public viewType2 view {get;set;}

      public ExtendedDocumentViewModel(viewType2 viewIn,etc...)
      {
          view = viewIn;
      } 
      view GetView()
      {
          return view.GetView(ExtraData,MoreExtraData);
      }
}

interface IAuthorViewModel
{
    view GetView();
}
A.sharif
  • 1,977
  • 1
  • 16
  • 27
  • thankyou for your suggestions, they look interesting but don't think it will help me here. In my case each document displays in an almost identical way, and they're all the same sort of document. (they're not really documents, that's just an obfuscation) In some types of document I need to display additional information about the author. Mostly everything else will be in the same place, but with the addition of this new author data. Different types of document will have different sets of additional information needing displayed. – Matthew Hudson Jun 12 '18 at 16:42
0

I may be way off base here, but as I understand your question... why not just throw the return types in an object and pass that to your view?

You could look at the desired method and use reflection to pull out whatever info you want. Modify this and the object class hold whatever you want it to.

public class DiscoverInternalClass
{
    public List<InternalClassObject> FindClassMethods(Type type)
    {
        List<InternalClassObject> MethodList = new List<InternalClassObject>();

        MethodInfo[] methodInfo = type.GetMethods();

        foreach (MethodInfo m in methodInfo)
        {
            List<string> propTypeList = new List<string>();
            List<string> propNameList = new List<string>();

            string returntype = m.ReturnType.ToString();

            foreach (var x in m.GetParameters())
            {
                propTypeList.Add(x.ParameterType.Name);
                propNameList.Add(x.Name);

            }
            InternalClassObject ICO = new InternalClassObject(c.Name, propNameList, propTypeList);
            MethodList.Add(ICO);
        }
        return MethodList;
    }
}

he object class could be something like this or modify it however you want:

public class InternalClassObject
{
    public string Name { get; set; }
    public List<string> ParameterNameList { get; set; }
    public List<string> ParameterList { get; set; }

    public InternalClassObject(string iName,List<string> iParameterNameList, List<string> iParameterList)
    {
        Name = iName;
        ParameterNameList = iParameterNameList;
        ParameterList = iParameterList;
    }
}

You could call the method like this with the desired class.

public static List<InternalClassObject> MethodList = new List<InternalClassObject>();

DiscoverInternalClass newDiscover= new DiscoverInternalClass();
MethodList = newDiscover.FindClassMethods(typeof(ExtendedDocumentViewModel));

Now you can have your GetView build based on what is in MethodList

Hope this helps!

DannyP
  • 25
  • 6