3

I have a use case where I used different models for the GET and POST actions in my controller. This works great for my view, because most of the data goes into labels. The model for the GET method contains 10 properties, but the model for the POST method only needs 3.

This GET view renders a form, which only needs 3 of these properties, not all 10. Thus, the model for the POST method accepts a model class which contains only these 3 properties. Therefore, the ASP.Net MVC model binder populates the model class parameter on my POST method with only these 3 necessary properties, and all is well.

Here's the question: When I encounter some business rule violation in the POST method, and want to use ModelState.AddModelError, and re-display the original view, I no longer have the 7 properties that were not POSTed, as they were not part of the form, and are not part of the model class which this method takes as its parameter.

Currently, I'm calling into a builder to return an instance of the model class for the POST method, and have the GET method itself delegating to the same builder. So, in these cases, when there is some business rule violation in the POST method, I return a View("OriginalGetView", originalGetModel). How can I use ModelState.AddModelError in this case, in the POST method, if I want to send custom messages back to the view, using a completely different model class?

It seemed way too lazy to use the same model class for both the GET and POST methods, given that their needs were so different. What is the best practice here? I see a lot of people recommending to use the same model for both methods, and to POST all of the fields back from hidden form fields, but that just seems like a waste of bandwidth in the majority of cases, and it feels ugly to be sending things like "VendorName" back to the server, when I already have "VendorId".

Pittsburgh DBA
  • 6,672
  • 2
  • 39
  • 68

3 Answers3

1

I may be misunderstanding what you are trying to do, but make sure you aren't being penny-wise and pound foolish. I see you may only want to post the identifiers and not necessarily the descriptors. But it sounds like you have to re-display the view after posting...if so you can just access the model properties if you post the same model that is in the get. If you only post the identifiers, you have to spend time re-accessing the database to get the description values(i.e. vendorname as you describe) using the vendor id no? Wouldn't that also be extra processing? Like I said, I could be misunderstanding your post, but for consistency using the same view model for your view to get and post makes the most sense to me.

jasnooze
  • 11
  • 1
  • Right, but this means that I would have to embed the description values in hidden fields, and ship them up the wire with the identifiers. I don't mind using compute resources in the cloud in lieu of placing burden on slow wireless networks or underpowered mobile devices. This application demands the best performance, at scale, so economizing on the client can easily be compensated for in the cloud. You're correct; we could just round-trip this data each time, but 99% of the time, it's waste on the POST. – Pittsburgh DBA Jun 01 '15 at 23:05
1

Hidden Inputs maybe the best solution here still I think, even on 2g you shouldn't create any lag unless unless the values of your Model properties are long strings or something encrypted or xml.

So your .cshtml would have this in it for the 4 properties not currently included in the form:

<form>
    @Html.HiddenFor(m => m.Property1)
    @Html.HiddenFor(m => m.Property2)
    @Html.HiddenFor(m => m.Property3)
    @Html.HiddenFor(m => m.Property4)

But you could also get the model state errors from the original posted model and recreate the ModelError state in your response model to get around using hidden inputs.

I just found this guide (not the answer with Green Checkmark but the highest upped Answer: ASP.NET MVC - How to Preserve ModelState Errors Across RedirectToAction?

Note: if you need to copy model properties from Model to another Model (of the same type or different type), in a cleaner way, check out AutoMapper.

Community
  • 1
  • 1
Brian Ogden
  • 18,439
  • 10
  • 97
  • 176
  • This is what the code currently does (your "OR" option). That's why I have the builder pattern in place. I updated my post (because I see that I accidentally used GET twice in the same sentence. One of those should have said POST). My question is corrected. Thank you for the sample code; it is a concise outline of what I am already doing. – Pittsburgh DBA Jun 01 '15 at 22:32
  • OK, there is nothing wrong with this solution, it is not lazy, nor does it "waste bandwidth" as you stated in your question. No need to have two models, you are not saving any "bandwidth" with two models but you are adding additional code, and a code file by having a second model, that has all the same properties and works with the same view as your single model setup. – Brian Ogden Jun 01 '15 at 22:37
  • Not saving any bandwidth? Really? Most of what I'm saving is String fields, which are only needed in the POST method if there is some kind of business rule violation, and even then only for display. 99% of the time, the POST will succeed, and the user sees a result view. So, you're saying that sending 7 descriptive String fields up the wire on each POST, when they are not germane to the operation, is not waste? If not, what is it? – Pittsburgh DBA Jun 01 '15 at 22:40
  • No I am not saying Post entire model back, that would just be ideal. I am saying the code solution I shared, the same as yours you already have, is not a performance issue. The PostBack would not contain the validation text or the descriptors by the way, post the whole form back and check out the PostData in Fiddler, should only contain the Model Values, the post back is a standard HTML submit that ASP.NET parses, it only posts back HTML such as , its all about HTML elements that contain name="something" with a parent
    – Brian Ogden Jun 01 '15 at 22:48
  • Yes, I agree. My current tactic is the most efficient in terms of bandwidth, and quite friendly to mobiles and low-bandwidth Edge/3g connections, because it is very concise in what it sends to the POST. The challenge comes when we want to re-render this exact view, to show errors. In this case, we have lost the "state", if you will, since it was not POSTed back, and it needs to be rebuilt. This is a small price to pay. Additionally, for cases where some views need to show multiple forms, then Model Validation attributes get in the way with what is actually 'required' for the given operation. – Pittsburgh DBA Jun 01 '15 at 22:54
  • My comment about "lazy" was about constantly sending 10 fields up the wire when only 3 are needed for 99% of the use case outcomes. That's just plain gross, IMO. I used to develop apps on 14.4kbps hitting RDBMS behind firewalls before broadband was even a thought on someone's radar. I still have the same economies in mind. Waste not, want not. I believe that we have too many profligate "spenders" of bandwidth and CPU in the wild. – Pittsburgh DBA Jun 01 '15 at 22:56
  • See my latest answer, I think if I am understanding you fully, you are misunderstanding what is posted back when you postback the model. You can postback the entire model, only the model values are postedback, no validation text, no label text is posted back. – Brian Ogden Jun 01 '15 at 22:57
  • I am not misunderstanding. I have 10 fields on my model. Most of those 10 go into labels. When I craft my form, I am only putting 3 of the fields (integers) into the form. My point is that, whether I "recycle" the model class, or make a dedicated one, the other 7 fields are not posted back, so they are not available for a re-render unless I retrieve them again. I was looking for best practice guidance. I see people saying things like "put the other 7 fields in hidden form fields". That's what I'm saying. I will refine my question to include the hidden fields so it makes more sense. – Pittsburgh DBA Jun 01 '15 at 23:00
  • Ya hidden fields is the solution, I would think on 2g this is not an issue. – Brian Ogden Jun 01 '15 at 23:07
  • 1
    Thank you for analyzing this case, and for your thoughtful engagement. I hope not everyone thinks like this, in terms of not bothering to economize on resources; it's no wonder the VZW towers in my town are saturated 24/7. There was a time when developers and vendors alike cared about economization. Look at facebook - they have over 1.44 billion users. Do you think they waste bandwidth? The toll would be enormous. – Pittsburgh DBA Jun 01 '15 at 23:10
  • 1
    I changed my answer, an alternative to hidden inputs, you can recreate the model error state in your HttpPost – Brian Ogden Jun 01 '15 at 23:11
  • Upvoted. Thank you. I do wish to use AddModelError (original subject!). Does this mean I can use it, so long as the property names on both models are the same? Also, thanks for the AutoMapper reminder. – Pittsburgh DBA Jun 01 '15 at 23:13
  • No, you will need to copy the Model State errors to the new instance, I just updated my answer again, its a bit pseudo code, but it should be enough that you can use a foreach loop through the errors from the original model and just add them to the new model you are returning. – Brian Ogden Jun 01 '15 at 23:20
  • I have update my answer, I think I found a better answer for you, see link – Brian Ogden Jun 01 '15 at 23:42
0

Perhaps this could help with what you were trying to achieve - 'Model' level errors - which wouldn't need to attach to a specific field/property - but can be displayed in a Global area.

https://stackoverflow.com/a/53716648/10257093

yeti_c
  • 135
  • 2
  • 8