1

Programmatically type an object

C# mvc4 Project

I have two similar ViewModels, that contain over a dozen complex objects, that I want to call a common method from my Create and Edit Actions to populate the ViewModels.

Something along the lines of this

private void loadMdlDtl(CreateViewModel  cvM, EditViewModel evM)
{
  If (vM1 != null) { var vM = vM1}
  If (vM2 != null) { var vM = vM2}

  // about two dozen complex objects need to be populated
  vM.property1 = …;
  vM.property2 = …;
  …
}

This doesn’t work because vM isn’t in scope.

Is there any way to Programmatically type the vM object so that I don't have to create two loadModel methods or otherwise duplicate a lot of code ?

SOLUTION:

Create an Interface:

public interface IViewModels
{
    string property1 { get; set; }
    int property2 { get; set; }
    IEnumerable<ValidationResult> Validate(ValidationContext validationContext);
}

Have View Models inherit from interface:

public class CreateViewModel : IViewModels, IValidatableObject
{
  string property1 { get; set; }
  int property2 { get; set; }
  IEnumerable<ValidationResult> Validate(ValidationContext validationContext);
  {
    // implementation
  }
}

public class EditViewModel : IViewModels, IValidatableObject
{
  string property1 { get; set; }
  int property2 { get; set; }
  IEnumerable<ValidationResult> Validate(ValidationContext validationContext);
  {
    // implementation
  }
}

Call the method from Actions passing the View Model:

public ActionResult Create()
{
  var vM = new CreateViewModel();
  ...
  loadMdlDtl(vM);
  ...
}

But now accept the interface rather than the View Model into the method:

private void loadMdlDtl(IViewModel vM)
{
  // implementation
}
Joe
  • 4,143
  • 8
  • 37
  • 65

2 Answers2

4

Since you want to access properties and/or methods that are the same across all objects, you can define an interface with such properties and methods. Have each object implement that interface.

public interface IMyCommonStuff
{
    string property1 { get; set; }
    int property2 { get; set; }
    int SomeMethod();
}

UPDATE

If some of the methods and/or properties have identical implementations, that implementation can be done in a common base type. I would suggest still using an interface definition when acting on your objects. Example:

public class MyCommonImplementation : IMyCommonStuff
{
    public virtual int SomeMethod()
    {
        // Implementation goes here.
    }

    public string property1 { get; set; }

    public int property2 { get; set; }
}

public class MyConcreteSubclass : MyCommonImplementation, IMyCommonStuff
{
    // Add only the things that make this concrete subclass special.  Everything
    // else is inherited from the base class
}
Eric J.
  • 147,927
  • 63
  • 340
  • 553
  • Perhaps you could elaborate more but that seems to generate even more code. Wouldn’t the loadModelDtl method have to be then implemented separately in each object? Wouldn’t it be simpler just to create a second loadModelDtl method without the interface? My goal here was to be able to re-use code. – Joe Dec 17 '13 at 05:01
  • The only substantial extra code created with the interface approach is the interface definition itself. The benefit of having your objects that are similar to each other expressing that similarity through an interface are substantial. Interface use leads to code re-use. – Eric J. Dec 17 '13 at 18:51
  • I'm not following. How do I re-use `loadModelDtl's` code? With an interface I would have to implement it twice. If doable could you please provide a code sample. – Joe Dec 18 '13 at 00:26
  • Oh, if the implementation is exactly the same, use a common base type instead. I'll update my answer. – Eric J. Dec 18 '13 at 02:06
  • Finally had a chance to implement your answer. See OP SOLUTION for my implementation. – Joe May 21 '14 at 16:02
1

Eric's answer is the standard way of doing it, but if you want to save time, you could use dynamic keyword to define vM, such as:

dynamic vM;
if (vM1 != null) vM = vM1;
if (vM2 != null) vM = vM2;

//about two dozen complex objects need to be populated
vM.property1 = …;
vM.property2 = …;
…
dotNET
  • 33,414
  • 24
  • 162
  • 251
  • 1
    Just a note: `dynamic` can carry quite a performance penalty if you're not careful! See http://stackoverflow.com/questions/7478387/dynamic-and-performance – Xcelled Dec 17 '13 at 03:34
  • @Joe: Didn't work? What didn't work? What was the error? The code you have posted in the question is totally compatible with the one in this answer. – dotNET Dec 17 '13 at 04:19
  • @Joe: First warning can be avoided by changing declaration to `dynamic vM = null`. The second one is confusing me. Is this `FirstOrDefault` line before or after the two `if` statements? – dotNET Dec 17 '13 at 04:57
  • @Joe: Interestingly it compiles for me. – dotNET Dec 17 '13 at 05:14
  • @Joe: Ahh, this is MVC. You need to add `@[MODEL] [CLASSNAME]` at the top of your file. See this for details: http://stackoverflow.com/questions/4155392/razor-view-engine-an-expression-tree-may-not-contain-a-dynamic-operation – dotNET Dec 17 '13 at 05:15
  • Sorry, comment posted before done writing it. It seemed like a simple and elegant solution but it didn't work. First line of code in `loadModelDtl` after declaring `vM` (including the if statements) is `vM.Office = _db.Offices.FirstOrDefault(i => i.AcntId == vM.Acnt.Id && i.Number == 1);` and it generates two errors: ‘Use of unassigned local variable ‘vM’’ and ‘An Expression tree may not contain a dynamic operation’ `dynamic vM = null;` clears up the first error. The `loadModelDtl` method is in the Controller not the View. Views already correctly & strongly typed. – Joe Dec 17 '13 at 05:55
  • Marcind’s comment in your link states that “lambdas do not support dynamic members”. Perhaps that renders your `dynamic` solution not workable in my implementation. – Joe Dec 17 '13 at 06:04