ASP.Net MVC Postback and Models - c#

This is mostly a follow-up to a comment in this issu, but I don't have enough reputation to comment ...
ASP.Net MVC Postback a label value to your controller
Let's say I have a simple model:
public class SimpleClass
{
public String Label { get; set; }
public String FirstName { get; set; }
}
Label is changed based on user/client so it can't be a DataAttribute. If when posted back processing problems occur, we need to redraw the entire page. This is the crux of the problem of the previous post. The accepted solution is to do this:
#Html.DisplayTextFor(model => model.Label)
#Html.HiddenFor(model => model.Label)
#Html.EditorFor(model => model.FirstName)
That makes sense in that it works. But our models are much more complicated and extensive. This method will result in a ton of hidden fields which seems like a very dirty solution.
This brings me to JP's comment:
ASP.Net MVC Postback a label value to your controller
The solution there is to reload the model. But it's not just a reload, it's also a merge since you want to preserve any client-side data changes.
default: SimpleClass { Label="TheLabel", FirstName="Rob"}
postedback: SimpleClass { Label="", FirstName="Steve" }
we want: SimpleClass { Label="TheLabel", "FirstName="Steve" }
My question is does MVC have a good way to know what fields were postedback so it merges correctly? We would need to only merge postedback fields not blank properties.
Or is it better to just ajaxify the entire postback and not do a form submit? This avoids all model reload issues on submit.
Update
To give Pablo credit I accepted his solution. To see my simple example of his solution, check Robert Harvey's comment in the Answers below:
ASP.Net MVC Postback and Models

The main problem here is in trying to fit WebForms' PostBack concepts into MVC. There is no such thing as a stateful postback where things just automatically retain their state.
You only have ViewModels that are bound to the view, and ViewModels that are posted by the view to the Controller. They don't even necessarily need to be of the same Type. Meaning, the controller should only receive the data that the user indeed can change, not large objects with many properties that were part of the initial ViewModel but are read-only.
Labels commonly represent read-only texts and they are not editable form elements. Which is why you have to use hidden fields for that.
And yes, sometimes that implies that you have to reload the original data in the controller, and sync up with new data that you posted, which isn't necessarily a bad thing. If you bind read-only data to a view, which the user can't manually edit, you shouldn't really trust that data coming back in a post afterwards. Just because your html might try to make it read-only doesn't mean I can't manipulate the post and ultimately change your "read-only" data without you knowing.
I just read the second question you mentioned, and from the looks of it, his main problem was that he was trying to reuse the same ViewModel again, so all the data was missing and the model wasn't valid. The solution to that is indeed quite simple, ONLY post what you need, as a new ViewModel type, and have the controller take care of the rest.

[Moved from OP]
I think this is what Pablo is suggesting for those who are wondering. It seems to be a good pattern to resolve this problem.
Models:
public class SimpleClass : SimpleClassPostBack
{
public String Label { get; set; }
public SimpleClass()
{
// simulate default loading
Label = "My Label";
FirstName = "Rob";
}
}
// contains only editable by the user fields
public class SimpleClassPostBack
{
public String FirstName { get; set; }
}
Controller Actions:
[HttpGet]
public ActionResult SimpleClassExample3()
{
SimpleClass simpleClass = new SimpleClass();
return View(simpleClass);
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult SimpleClassExample3(SimpleClassPostBack postBackSimpleClass)
{
Boolean errorOccurred = true;
if (!errorOccurred)
{
// do whatever success action is necessary
}
// redraw the page, an error occurred
// reload the original model
SimpleClass simpleClass = new SimpleClass();
// move the posted back data into the model
// can use fancy reflection to automate this
simpleClass.FirstName = postBackSimpleClass.FirstName;
// bind the view
return View(simpleClass);
}
View:
#model SimpleClass
#{
ViewBag.Title = "Simple Class Example3";
}
<h2>Simple Class Example3</h2>
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<label for="FirstName">#Html.DisplayFor(m => m.Label)</label>
#Html.EditorFor(m => m.FirstName)
<br/>
<button>Submit</button>
}

You should only send data from the client to the server that the server can't "figure out" on its own. If the server knows what the labels were when the user first navigated to that view then if the user cannot modify them, the server will be able to know what the labels are when reloading the view.
Use hidden fields to identify the database objects. So your SimpleClass should probably have some sort of Id which you will use in the hidden input. Use the EditorFor for FirstName. Now when the form is posted, use the sent Id to find the correct SimpleClass from the database and modify its FirstName property with the value posted. The Label property will be null which is ok since you don't need to save it. Now if there's a problem in the post and you want to send the same view back like it was, you need to repopulate the Label the same way you did when the user arrived to the view for the first time. The values of Id and FirstName properties will be automatically sent back to the view with the model state.
In summary:
Only post data that is needed to identify something and what the user
can edit in that view.
Don't trust the client to send you anything valid. The user can change the values of the hidden field labels to anything.

Related

What is the best pratice to change a field value when a form is submitted in MVC?

I have a lot of fields in my View that are masked with jquery MaskedInput. They're masked because the user need to see it with a mask, but I need to remove these masks before commiting the value in my database.
I don't want to mess up the code, since this is one of the most importants Views in my project.
So what's the best pratice to do this?
Suppose I have this Model:
public class MyViewModel {
public string MyMaskedProperty { get; set;}
}
And this code in View:
#Html.TextboxFor(x=> x.MyMaskedProperty, new { #class="myMask"} )
Should I:
Remove the mask on my View, using javascript, before the form is subimitted
Remove the mask on my Model, changing the get of MyMaskedProperty to return an unmasked value
Remove the mask on my Controller, since it need to be unmasked only from here and beyond
Something better than the 3 solutions above.
Thanks in advance!
Similar to the second option, you might simply add a read-only field to your view model:
public class MyViewModel {
public string MyMaskedProperty { get; set;}
public string MyUnmaskedProperty
{
get
{
// return an "unmasked" version of MyMaskedProperty
}
}
}
Even if you were to modify the value with JavaScript, you can't really implicitly trust that and would want to validate server-side anyway. And doing it in the controller isn't ideal because that's just procedural code that would need to be repeated all over the place. Following the advice to "keep your controllers light and your models heavy", this logic really belongs on the model.
The model itself can be constructed from any version of the data, really. As long as the information used to construct the model can effectively construct it, then the model can expose all sorts of operations and properties for manipulating and viewing that information.

MVC Model Binding - Maintaining values during a partial edit

I have a model class that goes like
public class Mod
{
public string StaticProp1 {get; set;}
public string StaticProp2 {get; set;}
public string EditableProp1 {get; set;}
}
I want a view in which I can edit EditableProp1 but where StaticProp1, StaticProp2 are displayed but not editable.
I created a strongly-typed view :
#model Mod
#using (Html.BeginForm())
{
<p>#Model.StaticProp1</p>
<p>#Model.StaticProp2</p>
#Html.TextBoxFor(m => m.EditableProp1)
<input type="submit" value="Save" />
}
In my Controller, when I deal with the action I find the EditableProp1 fine.
[HttpPost]
public ActionResult Edit(Mod model, FormCollection collection)
{
string editableProp = model.EditableProp1; //This works fine
string staticProp1 = model.StaticProp1; //Missing
return View(model);
}
This causes a problem if I post back for some reason as staticProp1 will now be null and the view won't be able to display it.
I know that I can add
#Html.HiddenFor(m => m.StaticProp1)
#Html.HiddenFor(m => m.StaticProp2)
to my view and that it will work fine, but I am wondering if there is another better way.
My values are already on the form (<p>#Model.StaticProp1</p>). Is there a way to bind the model to un-editable tags like that? Is there an HTML helper that does something like this?
By the way, if it isn't obvious, I am just starting out with MVC so if I am completely missing the point please let me know!
Every property of a model you want to persist has to be in the form (in an editor or hidden field). You can use, as you propose, Html.HiddenFor() for this. If you want to avoid overloading your view with hidden fields, you could store only the id of an entity in the hidden field and fetch the rest of the data based on the id in the Post action. Or use Html.TextBoxFor() with a readonly attribute, see this question for more information about it (I like the approach in the second answer as well).
I think the question relates more to model binding and how it works. If you don't want to use hidden field here (which I think fits your scenario), you can custom Model Bind by inheriting a class from:
DefaultModelBinder

General approach for displaying a message on any page

I have a fairly simple (to explain) requirement for my ASP.NET MVC web application:
On any page, be able to display a message based on an event that happened on a preceding page. Messages may include dynamic content, such as information entered by the user in a form submission.
Similar to this...
The approach must work in POST-REDIRECT-GET scenarios.
If the user has two browser tabs (which share session state) open on the application, then any message should only be displayed in the tab in which the related event happened.
For example:
After submitting a feedback form, a message thanking the user (by name)
When logging in, a message telling the user the date/time when they last logged in to the application
My first thought was to use a strongly-typed view with an appropriate model (with a string property) for this. However this would mean changing every single existing non-strongly-typed view to use this model, and updating all existing models to inherit from this new model.
This seems like overkill.
An alternative is to store the message in TempData:
// In my controller
TempData["Message"] = "Some kind of message";
// In my shared master view
#if (TempData["Message"] != null)
{
<div class="message">#TempData["Message"]</div>
}
Using TempData is covered in a bit more detail in this blog posting. Interestingly, the author also suggests using a custom HTTP header as an alternative. An interesting idea, but does not work with POST-REDIRECT-GET.
Is using TempData the preferred option, or is there a "better" alternative? (e.g. something more strongly typed)
My first thought was to use a strongly-typed view with an appropriate model (with a string property) for this. However this would mean changing every single existing non-strongly-typed view to use this model, and updating all existing models to inherit from this new model.
This is the only consistent, reusable, testable solution to do this that I can imagine, despite the extra work it may cause.
It is best practice to use ViewModels to communicate between View and Controllers. You can have a base View Model and all other View Models derived from that as below:
public class BaseVM
{
public string Message{ get; set;}
}
public class CreateViewModel: BaseVM
{
public string CustoomerName{ get; set;}
}
You can populate the Message property while returning the model to the controller as below:
public ActionResult Step2()
{
//Some Logic
step2Model.Message = "Yes, Success..!!";
return View(step2Model);
}
After that, on each view page, you can check if that property has something in it.You can do so as below:
#if(!string.IsNullOrEmpty(Model.Message))
{
//Show message
}
EDIT:
OP is well aware of this approach, but still keeping this answer as it has a snippet to show how to do this in code. Secondly, when it comes to use ViewModels, I agree with following comment by CodeCaster in his answer.
This is the only consistent, reusable, testable solution to do this that I can imagine, despite the extra work it may cause.
You could make a convention in your project that a message to display would always go into ViewBag.InfoMessage dynamic variable. So, in your layout file you would display it if it's passed into the view from a controller.
More strict way would be to create a base model class with InfoMessage property and derive all other models / viewmodels from that base.
To persist the message through POST-REDIRECT-GET scenario, use a session variable which you'd clear once the value is sent to the view - to avoid displaying on more than one page.
Here some code:
public class BaseViewModel
{
public string InfoMessage { get; set; }
}
public class SpecificViewModel : BaseViewModel
{
// other model properties
}
In your controller then:
SpecificViewModel vm = new SpecificViewModel();
vm.InfoMessage = Session["InfoMessage"] as string;
Session["InfoMessage"] = null;
// other code
return View(vm);
Of course your view will have strongly typed model: SpecificViewModel.

MVC3 Razor Help required

I'm hoping you guys can answer me a question?
I've only just started out using ASP.NET MVC3 have come unstuck at a certain point. I've been learning this stuff from books and I'm slightly unsure on a few things.
Can a VIEW, only have one #Model reference?
At the moment I have a VIEW setup with a HTTP-POST on a ViewResult, that validates the data in the View, entered by the user and then "on post", passes this info to a method that writes it back to a database(ADO.NET - Access). Now I need to change my VIEW, so that I can replace a couple of my text boxes for Dropdownlistfor controls. The Data to populate these controls will need to be passed in from the Database.
Would I be correct in saying that this data needs to be passed in the HTTP-GET Viewresult of the page, and if so, can i reference more than one #Model in this same View (*.cshtml).
I have a class that takes in the user response, and this is referenced in the View. But will i need to create a new class for the dropdownlist data and reference that too. So that in the background I populate the data into a SelectListItem, pass it to the View and then populate each drop down control within the view?
I'm sorry if this is poorly written, very hard to explain, I find learning from books hard and I'm pretty stuck now. Any help would be appreciated. Just to give me an understanding of how this all wraps around. I'm comfortable with the C# syntax, but very unsure of MVC3!
There are two ways you can handle this.
Use a View Model.
In this scenario you have a class that contains your data model as well as other things required by the view, so something like this:
public class ViewModel
{
public MyDomainModel Model { get; set; }
public IEnumerable<SelectListItem> SelectListItems { get; set; }
}
Use ViewBag.
In this case you add everything extra into the ViewBag dictionary. So in the controller, you'd have stuff like this:
ViewBag.SelectListItems = new SelectListItem[] { ... };
Then you can reference in the view itself
#Html.DropDownList("myselectlist", ViewBag.SelectListItems)
I think that this will help you pluralsight mvc3 intro. It sure helped me

ASP.NET MVC - posting back only a half of typed view's model?

Let's say you're developing your own stackoverflow in ASP.NET MVC :-)
You'd like to have a functionality to answer questions. So, the view should contain original question details (title, text, etc) and it should also contain the form to submit the answer. In order to render the view, you should provide both original question object and new empty answer object. You then want only answer to be posted back.
How would you define the type for this view? Would you implement a view model specific for this view that would contain both the question and an empty answer? What's the common approach here?
Ideas I have are:
Using a special view model object that contains both question and answer. View is bound to this object. When rendering the view, question details are used. When posting back, the object I get at controller only has answer-related fields populated.
Using ViewBag to pass question object. View is bound to answer model.
?
(no AJAX please, just the very basic scenario)
Your question doesn't make much sense. Your question would not be posted back because only form controls are posted, such as inputs, button values, etc.. Your question is just html text, and does not get posted.
You're going to return the question, and any answers as your model to the page. At the bottom, you're going to want a form that submits back an AnswerModel with a given question ID.
You might create a default AnswerModel that gets passed down when you render the view, but you're not going to work (or worry about) question and answer 'objects' just models. You might create a ThreadViewModel that contains a question and a list of current answers.
What I do is create four Interfaces:
public IQuestionDetail
{
string QuestionText { get; }
// Question Details
}
public IQuestionView
{
IQuestionDetail QuestionDetail { get; }
}
public IAnswerDetail
{
int/guid QuestionID { get; }
string AnwerText { get; }
// Anwer Details
}
public IAnswerView
{
IAnswerDetail AnswerDetail { get; }
}
Then I derive 4 classes
public QuestionDetail : IQuestionDetail
{ //implemented }
public AnswerDetail : IAnswerDetail
{ //implemented }
public QuestionViewModel : IQuestionView, IAnswerView
{ //implemented }
public AnswerModel : IAnswerView
{ //implemented }
A single view (Question/Detail.cs)
#model Models.QuestionViewModel
#html.Partial("partial-questionDetails", Model)
#html.Partial("partial-answerDetails", Model)
Two Partial Views
//partial-questionDetails.cshtml
#model Models.IQuestionView
#Html.DisplayFor(Model.QuestionDetail)
//partial-answerDetails.cshtml
#model Models.IAnswerView
#Html.BeginForm()
{
Html.EditorFor(Model.AnswerDetail)
}
QuestionController
public ActionResult Detail(IAnswerView AnswerModel)
{
// AnswerModel is only populated with the Answer Fields
// Do Stuff with AnswerModel
QuestionViewModel viewModel = new QuestionViewModel()
View(viewModel);
}
When building forms in ASP.NET MVC, I usually create two separate classes, one for populating the view (the ViewModel), and another for receiving data on submit. The ViewModel (usually read-only) may contain additional information required to populate the view, but the data object only has properties (with public setters) for submitting the form.
These classes will have several properties in common, so that the model binding in ASP.NET MVC will wire up properly. For simple forms, I don't always extract separate interfaces for each view, but if you want to guarantee that the properties match, then interfaces is an easy way to do it.
To do this, you would create a single interface, whose properties represent the form fields. Then, have each of your classes implement the interface, which will give you a compile-time guarantee that the form fields exist on both classes.

Categories

Resources