1

We have a web application using ASP.NET MVC, which supports controller methods that take a class object as a parameter. There is automatic binding of the posted form values in order to construct and populate the class object, which occurs before the actual code of the controller method is even invoked. So far so good, but here is my problem: in the course of constructing the class object, the ASP.NET binding engine is invoking every public property of the class. Some of these properties involve some expensive calculation (iterating internal lists, doing counts and so forth), and it is irritating to have them called for no reason whatsoever and the values thrown away. These are read-only properties with only a 'get' and no 'set', so the binder cannot possibly be touching them for purposes of assigning values to them. How can we keep this from happening?

Here is what has been tried so far without success:

  1. Use a [Bind(Include = "...")] in the controller method declaration to limit to the other (non-read-only) properties that can actually be assigned to.
  2. Use a [BindNever] annotation on the read-only properties within the class definition.

Our current workaround is, we just abandon the read-only property implementation altogether and rewrite them all as methods. The binder code does not invoke methods, so this works, but we would still rather have them as properties, and it seems like a problem that should be capable of solution. Any thoughts anyone?

== EDIT =============

Additional things tried, in response to answers here, that still did not work:

  1. Use a [Bind(Exclude = "...")] in the controller method declaration specifying the properties we do not want to invoke. (They are still invoked anyway.)

== EDIT 2 =============

Additional details per request. I am using VS 2015, .NET Framework 4.5.2. Just now I created a sample program to demonstrate the problem:

  1. File -> New -> Project -> Web -> ASP.NET Web Application
  2. Under "ASP.NET 4.5.2 Templates", choose "MVC"
  3. In ManageViewModels.cs, there is a class called "AddPhoneNumberViewModel". This class occurs as a parameter of the method ManageController.AddPhoneNumber (HttpPost version). Add a public property to the class called "PropertyThatShouldNeverBeCalled" and place a breakpoint within it (see code sample below).
  4. Compile and run the application in Debug mode. Attempt to access the endpoint /Manage/AddPhoneNumber, you will have to create an account, then access the endpoint again, enter a phone number, and click "Submit".
  5. Observe that you have hit your breakpoint in PropertyThatShouldNeverBeCalled.
  6. Try one of the unsuccessful fixes described above (e.g. add [Bind(Exclude="PropertyThatShouldNeverBeCalled")] to the definition of ManageController.AddPhoneNumber).
  7. Repeat step 4 above. Observe that you have still hit your breakpoint.

Code sample 1 (from ManageViewModel.cs):

public class AddPhoneNumberViewModel
{
    [Required]
    [Phone]
    [Display(Name = "Phone Number")]
    public string Number { get; set; }

    public bool PropertyThatShouldNeverBeCalled
    {
        get
        {
            bool returnVal = true;  // place breakpoint here
            return returnVal;
        }
    }
}

Code sample 2 (from ManageController.cs):

    //
    // POST: /Manage/AddPhoneNumber
    [HttpPost]
    [ValidateAntiForgeryToken]
    public async Task<ActionResult> AddPhoneNumber([Bind(Exclude = "PropertyThatShouldNeverBeCalled")]AddPhoneNumberViewModel model)
    {
         // etc.
    }

P.S. The problem also occurs when the project is compiled in release mode and run without debugging. I have used logging to confirm this in my original project. For purposes of the sample project, it is just simpler to observe the problem using a breakpoint.

Robert N
  • 1,156
  • 2
  • 14
  • 32
  • Review https://stackoverflow.com/questions/11064316/what-is-viewmodel-in-mvc. A quote from link: _"The key thing to remember is that the view model only represents the data that you want to use, nothing else. You can imagine all the unnecessary code and validation if you have a domain model with 30 properties and you only want to update a single value."_ – Jasen May 04 '18 at 19:45
  • The properties in question *are* properties needed in the view. That is why I want to add them to the ViewModel. What I would *not* like is to have them needlessly invoked when the class is instantiated, but rather, to have them invoked only within the view where they are needed. – Robert N May 04 '18 at 19:53
  • The model for "viewing" does not need to be the same model for transporting edit data between client and server. Once you get past the binding step you can construct any class you desire. – Jasen May 04 '18 at 19:55

2 Answers2

1

You should use a POCO model such as a DTO (Domain Transfer Object) or a View Model that only contains the properties you need to bind (with any validation you want to have ASP.NET handle as well) and then feed that model into the rest of your application to consume as needed.

Kit
  • 20,354
  • 4
  • 60
  • 103
Michael Dunlap
  • 4,300
  • 25
  • 36
  • Thank you for the suggestion, but if I am understanding you correctly, this solution is basically the same as our workaround, amounting to "Just expect all public properties to be accessed when the class is instantiated". What I am looking for is a solution in which such properties are *not* needlessly invoked when the class is instantiated.. Thank you anyway though. – Robert N May 04 '18 at 19:55
0

You can use the [Bind(Exclude="...")] attribute on the object type as the parameter to the Controller:

Example:

[HttpPost]
public ActionResult Post([Bind(Exclude = "PropertyName")] MyClass dataIn)
{
... 
}

MyClass being the object that is being bound on the data coming in to the Controller Action. The Exclude= accepts a comma delimited string for all properties you do not want to be part of the model binding. https://msdn.microsoft.com/en-us/library/system.web.mvc.bindattribute.exclude(v=vs.118).aspx

Bret Lipscomb
  • 480
  • 2
  • 7
  • Thanks for the suggestion! Unfortunately, I went and tried it just now and it was no more effective than using an "Include" of all the other properties-- the binding engine still invoked the "Exclude" properties while constructing the class exactly the same as before. – Robert N May 04 '18 at 18:33
  • Remember that if you're looking at the object in debug and you inspect the object / property it will invoke the getter and run your code for that. That doesn't mean it's binding when the data is posted to the controller though. – Bret Lipscomb May 04 '18 at 18:35
  • True, but that's not what I'm doing. What I am doing is putting a breakpoint inside the property 'get' code. It gets hit in every case described above except when implemented as a method rather than a property... then it only gets hit when the method is actually called (as it should be). – Robert N May 04 '18 at 18:40
  • Hmm, I created a test web app just for this earlier, and I did not get that behavior. Think you can post a little bit more of your solution? The controller and model? – Bret Lipscomb May 04 '18 at 18:41
  • Sure, will add details to the OP. – Robert N May 04 '18 at 19:14