MVC Model Binding - Maintaining values during a partial edit - c#

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

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.

ASP.Net MVC Postback and Models

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.

IEnumerable Model and Create Field on the Same Page

(This post is candy-obfuscated; obfuscated because I have to, candy for the lulz. Trust me, the real thing is actually worthwhile.)
Using ASP.NET MVC 4 and EF 5, I'm trying to create a page that simultaneously shows a list of entities that currently exist in the database and a simple create field at the bottom. I have a functioning way of doing it, but I'm wondering if there's a better way, because my current one feels very roundabout. I'd upload an image of what I've got, but I have to have at least ten reputation, so... on to the post.
The model I'm passing in looks like so:
public class CandyBrand
{
public int ID { get; set; }
[Required(ErrorMessage = "Brand name is required.")]
[Display(Name = "Brand Name")]
public string Name { get; set; }
}
The controller looks like this, including both GET and POST methods:
public ActionResult CandyBrands()
{
return View(context.CandyBrands);
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult CandyBrands(CandyBrand brand)
{
if (ModelState.IsValid)
{
context.CandyBrands.Add(brand);
context.SaveChanges(); //try/catch block removed for brevity
}
return View(db.CandyBrands);
}
And my view:
#model IEnumerable<CandyDatabase.Models.CandyBrands>
#{
ViewBag.Title = "Brands";
}
<h2>Brands</h2>
<p>#Html.DisplayNameFor(m => m.Name)</p>
#foreach (var brand in Model)
{
<p>#Html.DisplayFor(m => brand.Name)</p>
}
<h3>Create New</h3>
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<p>#Html.EditorFor(m => m.FirstOrDefault().Name) <input type="submit" value="Create" /></p>
<p>#Html.ValidationMessageFor(m => m.FirstOrDefault().Name)</p>
}
<p>#Html.ActionLink("Back to Candy List", "Index", "Home")</p>
#section Scripts{
#Scripts.Render("~/bundles/jqueryval")
#Scripts.Render("/Scripts/clear-inputs.js")
}
Because I'm passing in a list of candy brands, the model takes on the type IEnumerable. This isn't a problem for the first half - the foreach loop takes care of this. However, this creates other problems further down the page. Because the model is an IEnumerable, Html.EditorFor doesn't like it. Currently, to solve this, I'm calling FirstOrDefault, which brings it down to one entry. This is annoying in and of itself, but it doesn't stop there; MVC then automatically (and unwantedly (which may or may not be a word)) fills the editor with the data from the first entity in the model! If you notice at the bottom, there's a call to a 'clear-inputs' script; this script exists SOLELY to fix this problem by running
$("input[type!='submit']").val("");
to clear all the fields when the page loads.
Is there a better way to do this? I'm not above accepting the answer "Use a view model, dangit!", but it seems silly to have a view model that has a CandyBrand entity and then a list of CandyBrand entities.
So, it appears, according to Henk Holterman
A ViewModel is the actual, simple and clear solution here. Nothing silly about it.
and Darin Dimitrov
Dude, use a view model, dangit! How else are you even thinking in being able to write an ASP.NET MVC application that actually works? Without a view model? I have never seen such a beast released in the wild yet. Coz that ain't gonna happen. The very first thing you should start thinking before even writing a single line of code from your application is how your view models are gonna look like.
that the answer is a view model. Thank you both for your help; henceforth, I will use view models, dangit!

asp.net mvc Html helper hidden field behaving strangely

The behavior of the HtmlHelper.Hidden eludes me.
This is from a template inside Views/Shared/DisplayTemplates/Test.cshtml. Idea was to output a delete button next to the element in the list. So the view accepts a model of IEnumerable and then uses #Html.EditorForModel() to output each test item.
So if i put this into Test.cshtml:
<input type="hidden" name="Name" value="#Model.Name"/>
#Html.Hidden("Name2", Model.Name)
That yields this:
<input type="hidden" name="Name" value="test"/>
<input id="RoleList_12__Name2" name="RoleList[12].Name2" type="hidden" value="test" />
Why does the name of the Html helper render something different from what i tell it to? I assume that this is intended, but i don't understand why.
Update
How would i go ahead and retrieve RoleList[12].Name in my controller?
My delete function accepts:
[HttpPost]
public ActionResult DeleteRole(Roles.Test model)
{
}
How can that one accept a RoleList[12] name item? It always returns null if i try.
This is happening because your view accepts IEnumerable of some model type. You basically have a list of objects passed to the view and since they all have a property named Name the binder is trying to distinguish between different instances by giving them names like model[index]property.
If you're trying to delete an instance you should have an Html.ActionLink link that invokes some action of your controller which takes the unique ID of the instance you're trying to delete. Something like this in your view:
#foreach (var item in model)
{
#Html.ActionLink("linkText", "DeleteRole", "controllerName", new {id = item.RoleId})
}
and then your controller should have an action like this:
public ActionResult DeleteRole(int id)
{
// Logic to delete role based on provided role ID.
return View();
}
It's so that the (default) model binding will work; i.e. when posted the field will be resolved to RoleList[12].Name in the Controller that takes a parameter equivalent to the model class.
If your controller action doesn't reference the model class the data will silently be discarded, so that you don't have to post back entire data - but that which is posted back will be realized for you.
You can override the default model binding; see The Features and Foibles of ASP.NET MVC Model Binding
The problem with your delete is that it's at the wrong level; either you bind something to the instance of the enumerable - wrapped up in its own form (ok for small amounts of data) or you decide to have an #ActionLink into which the ID of the enumerable is passed. I generally do the second, something like:
#Html.ActionLink("Del", "DeleteRole", "Controller", new { Id = item.Id}, new { #class = "btn"})
Of course the above doesn't need a [Post] action so that'd need changing on your controller.
The above would sit in your display or edit template as appropriate.

Link Editor Template to TextBox

i have the following situation.
I am developing a MVC 3 asp.net application. I want to make a searh form with datetime range filter. I wan to use a datepicker so the user can select the date range.
I have followed this mvc tutorial
http://www.asp.net/mvc/tutorials/javascript/using-the-html5-and-jquery-ui-datepicker-popup-calendar-with-aspnet-mvc/using-the-html5-and-jquery-ui-datepicker-popup-calendar-with-aspnet-mvc-part-4
My problem is that i can not get to link the editor template to a NON MODEL #editotfor or input or #textbox, i`ll put some code so you get my point.
I have the following EditorTemplate in my Shared folder (file Date.cshtml)
#model DateTime
Using Date Template
#Html.TextBox("", String.Format("{0:d}", Model.ToShortDateString()),
new { #class = "datefield", type = "date" })
if I use this line in my view everything works
#Html.EditorFor(m => m.fecha_Modificacion,"Date")
But i want to use the datepicker in a NON model value just a simpletextbox textbox
I have been trying this :
#Html.TextBox("fechaInicio", "", new { #class = "datefield" })
But not working
I´d apreciate any help
UPDATE
I must explain my problem in more detail
I have a
class Foo{
private stirng name;
private DateTime dateOfBirthday;
}
In the Foo view i am editing the list.cshtml so i can searh by dateOfBirthday by from to.
So i want to make two textBoxes so the user can select with a date time picker the from anf to date range.
I´d like to use a template so i can re-use it.
But the list.cshtml view is already typed to #Model Foo
but the two textboxes are not model fields
The EditTemplate i have made work perfectly on model typed #EditorFor filds so i am trying to use on my from to date range textboxes
For what i have read i can create a EditorTemplate but that does not mean that i use everywhere i can use just where i want
Thank you very much
From design perspective don't do non model things. Create view model that will contain everything the view needs to display and post back to controller, so you have everything you need in one class. This way strongly typed class is so much easier to work with.
With regards to your concrete question, in order for you to use Editor Template, all you need to do is to create folder EditorTemplates in the following hierarchy: Views/<ControllerName>/EditorTemplates
and put the <TypeYouWantToUseThisEditorTemplate>.cshtml inside editor templates folder and have #model <<TypeYouWantToUseThisEditorTemplate> as one of the first lines in that editor template.
The link to editor template is the type of the property. For example if in the model you have property of type MyType, editor template needs to be called MyType.cshtml.
That is all you have to do.
This will also work with primitive types, so if you create file string.cshtml under editor templates folder any string that your view isusing will go to that editor template.
Note that if your editor template will be used across multiple views and controllers, you can create your editor templates under /Views/Shared/EditorTemplates so its shared across application.
More about editor templates here: ASP.NET MVC 3 – How to use EditorTemplates
Hope this helps.
UPDATE
following your request, here's simple example of editor template used with view model (give you the idea how the whole construction works)
View Model:
public class Book
{
public int BookId { get; set; }
public string BookName { get; set; }
public string Description { get; set; }
}
EditorTemplate called Book.cshtml and located under /Views/<ControllerName or Shared/EditorTemplates:
#model <MyAppName>.Models.Book
#Html.DisplayFor(p => p.BookId)
#Html.EditorFor(p => p.BookId)
#Html.DisplayFor(p => p.BookName)
#Html.EditorFor(p => p.BookName)
#Html.DisplayFor(p => p.Description)
#Html.EditorFor(p => p.Description)
No error handling, no validation, no nothing is done inside this editor template for sake of brevity.
View page that is using editor templates with type Book will have to do no work at all (all the work will be done by editor template):
#Html.EditorFor(model => model.Book)
You can use a ViewModel to help you
Sometimes they can save your day and its considered a best practice to use them when you are using models from EF (Entity Framework) or other persistance aware classes that have important properties that can't be changed by the user on post for example.
Something like this may help you:
public class FooSearchViewModel
{
public Foo Foo { get; set; }
public DateTime From { get; set; }
public DateTime To { get; set }
}
Then, you have to bind the view to this ViewModel instead of the model and use it like:
Name: #Model.Foo.name
From: #Html.EditorFor(model => model.From, "DateWithCalendar")
To: #Html.EditorFor(model => model.To, "DateWithCalendar")
The template can be archieved with a EditorTemplate, like the one #Display Name explained in his answer:
/Views/Shared/EditorTemplates/DateWithCalendar.cshtml
#model System.DateTime
#Html.TextBoxFor(model => model.Date, new { class = "date datepicker" })
Then you can use CSS to customize the input and apply the jQuery UI datepicker to it or wethever you like.
You can also define the template to use with DataAnnotations attributes in the ViewModel:
[UIHint("DateWithCalendar")]
public DateTime From { get; set; }
References:
http://blogs.msdn.com/b/simonince/archive/2010/01/26/view-models-in-asp-net-mvc.aspx
http://kazimanzurrashid.com/posts/asp-dot-net-mvc-viewmodel-usage-and-pick-your-best-pattern
ViewModel Best Practices
Try using partial views:
#* Views\Shared\_HandlingMyString.cshtml *#
#model string
This is my partial view to handle the string: '#Model'
Than you will call it this way from your main view (and I think this is the link you're looking for):
#model MyType // that has public string MyProperty that holds your date (start and end)
#Html.Partial("_HandlingMyString", Model.MyProperty)
Something along these lines. You might need to do some twicking, I didn't compile this code, but its is what you seems needing.
Please let me know if this helps.
De solution proposed by rcdmk did the work.
I think that polutae a class for search porposes is the right way to do it.
I think that working with ASP.net MVC we must use string typed views as Display Name pointed.
Thank you for your help.

Categories

Resources