11

I seem to be running into a weird issue and after hours of head scratching, I seem to have narrowed the issue down to a combination of partial classes and virtual properties. When I override a property that's in a partial class, sitting in a separate file, MVC duplicates the fields on my view. I am using Visual Studio 2013 and the issue can be duplicated by following these steps:

  1. Open Visual Studio and create a new Project. Choose Web under the categories, then choose "ASP.NET Web Application". I am targeting .NET 4.5.
  2. Choose "Empty" from the template selection, then check the MVC checkbox so it adds the core folders and references.
  3. Once the project is created, right-click on the Models folder and create a new class called MyModel.cs.

Add these lines to the new file:

public abstract partial class MyOriginalModel
{
    public virtual string FirstName { get; set; }
    public virtual string LastName { get; set; }
}

public partial class MyModel : MyOriginalModel
{

}
  1. Now right click on the Models folder again and create another new class called MyModelCustom.cs.

Add these lines to the file:

public partial class MyModel
{
    [System.ComponentModel.DisplayName("First Name")]
    [System.ComponentModel.DataAnnotations.Required]
    public override string FirstName
    {
        get
        {
            return base.FirstName;
        }
        set
        {
            base.FirstName = value;
        }
    }

    [System.ComponentModel.DisplayName("Last Name")]
    [System.ComponentModel.DataAnnotations.Required]
    public override string LastName
    {
        get
        {
            return base.LastName;
        }
        set
        {
            base.LastName = value;
        }
    }
}
  1. Now build the project, then right click on the Controllers folder and add a new controller. Choose "MVC 5 Controller with read/write actions" and call it NamesController. Right click on the Create method and go to "Add View". Under the template dropdown, choose Create and for the Model Class dropdown, choose MyModel.

Once MVC creates the template, you will see that it adds First Name and Last Name twice. The issue seems to be related to partial classes because if I move the contents of MyModelCustom.cs into MyModel.cs, everything works fine. However, its not just partial classes. If I create a new property (versus overloading one) in the partial class, it does not duplicate that property. So it seems to be a combination of partial classes and overriding virtual properties.

Can someone please confirm if this is a bug or if I am doing something wrong?

Icemanind
  • 47,519
  • 50
  • 171
  • 296
  • Yes, MVC uses reflection. You intuitively understand it must. You would expect that the Scaffolding engine would analyze your models by type, recursively creating a meta-data map for scaffolding. But the framework tries to draw as much as possible from templates and meta-data. The framework seems less concerned about model accuracy than with mapping your model to natural expressions (Most Razor helper use **uncompiled functions** -- expressions). – Dave Alperovich May 26 '15 at 07:16
  • 1
    And therein lies your problem. The scaffolding engine scans each of your partials and maps their properties with expressions. It is missing the fact that virtuals can be, and are being overidden. BC they can be overriden, the simple-minded pre-compiler isn't raising any flags. But it's only **Pre-Compiling** -- doing half the job. I suspect the framework would understand better if you didn't throw so many curve balls. Problems with partials is nothing new. Partials and inheritance get common complaints. But the virtuals may be the worst of it. Maybe making the virtuals abstract would fix it... – Dave Alperovich May 26 '15 at 07:24
  • 1
    I agree with Dave completely. Only one thing I would like to give a try to see if it works is to inherit your class MyOriginalModel on MyModel where the properties are being overridden. And have the empty MyModel class not inherit anything. – Shashank Chaturvedi May 29 '15 at 12:37
  • @ShashankChaturvedi, I'm just as curious how what would happen. I would bet on it working. – Dave Alperovich May 29 '15 at 15:48
  • I'm heading for a meeting here in a little bit. But later on today, I will try it and let both you guys know if it works. – Icemanind May 29 '15 at 15:57

2 Answers2

3

It is a bit of both. Bug or not, if MVC is scaffolding incorrectly, you will either have to constantly fight the framework or change your approach to the problem.

As a general rule, I've found that when you have to fight the MVC framework to make it behave the way you want, then it is far easier to change your approach to the problem. Otherwise, you will end up fighting that particular battle repeatedly until you eventually comply. Take it from someone who's learned that lesson the hard way.

With easier approaches in mind, here are a few things you could try instead:

  1. If you are overwriting a lot of properties, create separate classes with common names for properties (FirstName, LastName). Then use Best way to clone properties of disparate objects to marshall the data between objects.

  2. You could also use Fody PropertyChange listeners to handle whatever logic is needed when these values are changed thereby eliminating the need for the partial overrides entirely.

  3. A final option would be to override the scaffolding templates to skip overridden properties. Not sure how you would detect that though.

Community
  • 1
  • 1
B2K
  • 2,541
  • 1
  • 22
  • 34
  • I'll admit, I don't know a lot about how MVC scaffolding works under the hood, but my assumption has always been that MVC uses reflection to reflect my model class to determine how to scaffold and create a templated view. If that is the case, I am confused as to why moving code into a partial class would cause it to be duplicated because there is nothing in reflection to determine that it came from a partial class. Partial classes just inform the compiler how to compile and assembly the complete class. – Icemanind May 21 '15 at 22:30
2

Take a look at CodePlex source for MvcScaffolding EnvDTETypeLocator.cs

    /// <summary>
    /// Out of a set of CodeType instances, some of them may be different partials of the same class.
    /// This method filters down such a set so that you get only one partial per class.
    /// </summary>
    private static List<CodeType> PickArbitraryRepresentativeOfPartialClasses(IEnumerable<CodeType> codeTypes)
    {
        var representatives = new List<CodeType>();
        foreach (var codeType in codeTypes) {
            var codeClass2 = codeType as CodeClass2;
            if (codeClass2 != null) {
                var matchesExistingRepresentative = (from candidate in representatives.OfType<CodeClass2>()
                                                     let candidatePartials = candidate.PartialClasses.OfType<CodeClass2>()
                                                     where candidatePartials.Contains(codeClass2)
                                                     select candidate).Any();
                if (!matchesExistingRepresentative)
                    representatives.Add(codeType);
            } else {
                // Can't have partials because it's not a CodeClass2, so it can't clash with others
                representatives.Add(codeType);
            }
        }
        return representatives;
    }
}

:

:

1) PickArbitraryRepresentativeOfPartialClasses, the method uses Linq any() to confirm that the codeType as CodeClass2 has members.

CodeClass2 is the partial class type of EnvDTE, Visual Studio's core Automation library responsible for IDE code generation (Design Time Reflection).

2) If the class cast as CodeClass2 does have members, the class is added to the representatives

3) When the partial class is evaluated, each file will be visited within a distinct context (often leading to a consolidation of elements that should be overridden)


An interesting distinction between Run Time Reflection and Design Time Reflection: sic

An ASP.NET control has two distinct sets of functionality for when it is executed at run-time inside a page or used at design-time inside a host designer. Run-time capabilities determine, based on configuration, the markup that is output by the control. The design-time capabilities, instead, benefit of a visual designer such as Microsoft Visual Studio 2005. Design-time capabilities let the page author configure the control for run-time in a declarative and WYSIWYG (what-you-see-is-what-you-get) manner.

Conclusion:

MVC Scaffolding does use reflection, but it is the much less reliable Design Time Reflection.

Design Time Reflection is not the same as Run Time reflection. A fully compiled Class is a final result of inheritance resolves and partials combined and priority resolved. Design Time Reflection makes best guesses about how to work with complex, multi-part types.

If you want to rely on Scaffolding, better not to push it's limits. When you get errors like this, try simplifying your ViewModels:

  • Try consolidating your partial classes
  • Try removing your abstracts / virtuals
Dave Alperovich
  • 32,320
  • 8
  • 79
  • 101