I have created a strongly typed MVC3 Razor view using the scaffolding code.
The model is a POCO with a base type of PersistentEntity which defines a property called Created, Updated and Id.
Id is an int, Created and Updated are DateTime.
I am using Html.HiddenFor to create the hidden field on the view.
#Html.HiddenFor(model => model.Id)
#Html.HiddenFor(model => model.Created)
#Html.HiddenFor(model => model.Updated)
On the page, the hidden input is being rendered properly, with the Id being set in the value.
<input data-val="true" data-val-number="The field Id must be a number." data-val-required="The Id field is required." id="Id" name="Id" type="hidden" value="12">
However when the page is submitted to the controller [HttpPost]Edit(Model model) the Id property is always 0. Created and Updated are correctly populated with the values from the View.
This should be 12 in the case of the example in this post. What is going wrong?
I am aware that I can change the method signature to [HttpPost]Edit(int personID, Person model) as the personID is in the get string, however why does the model not get populated with the hidden field?
Update
The problem was that the setter on PersistentEntity was protected, ASP could not set the property, and swallowed it. Changing this to public has solved the problem.
public abstract class PersistentEntity
{
public virtual int Id { get; protected set; }
public virtual DateTime Created { get; set; }
public virtual DateTime Updated { get; set; }
}
public virtual int Id { get; protected set; }
protected set; <!-- That's your problem. You need a public setter if you want the default model binder to be able to assign the value.
Related
I have a view that shows a single item with all of its fields. But I'm getting confused trying to figure out how to allow one specific field ("Status") to be updated from this view, without necessarily going into a whole other "edit" view first.
How do I http-post to the same action (Details) but saving only the "Status" field, without saving all of the other properties which are part of the view model (for display only)? Do I need a separate view model just for the Status? New to ASP.NET MVC and getting confused.
View Model
public string FirstName { get; set; }
public string LastName { get; set; }
public string Birthdate{ get; set; }
public string Status { get; set; }
//etc.
View
<div>
#Html.DisplayFor(model => model.FirstName)
#Html.DisplayFor(model => model.LastName)
#Html.DisplayFor(model => model.Birthdate)
//etc.
</div>
<div>
#TextBoxFor(model => model.Status)
<button type="submit" value="Save Status" />
</div>
Controller
[HttpGet]
public ActionResult Details(int id)
{
var person = personRepo.GetById(id);
var vm = BuildPersonDetailsViewModel(person);
return View(vm);
}
[HttpPost]
public ActionResult Details(PersonDetailsViewModel vm)
{
var person = personRepo.GetById(vm.PersonID);
person.Status = vm.Status;
personRepo.Update(person);
}
So I solved this by ensuring that the primary key field PersonID is included in the View. I didn't think I needed it originally because it started off as a read-only Details view, and PersonID wasn't needed to be displayed. But when posting data back, I needed to add it as a hidden field, so it could be passed to the controller on HttpPost. Then it can be used to locate and update the record in the database.
Furthermore, I added another method in the repository to allow for updating of just the "Status" field, since that's the only value being updated. If I use the sample code above, my solution looks something like this:
View Model
public int PersonID { get; set; }
public string Status { get; set; }
public string FirstName { get; set; }
//etc.
View
#Html.HiddenFor(model => model.PersonID)
#Html.DisplayFor(model => model.FirstName)
//etc. (all the display fields)
#TextBoxFor(model => model.Status)
<button type="submit" value="Save Status" />
Controller
[HttpPost]
public ActionResult Details(PersonDetailsViewModel vm)
{
personRepo.UpdateStatus(vm.PersonID, vm.Status);
}
This is probably not the case but since i can't tell from the sample code, do you have the Html.BeginForm(){} block around the model that you are trying to post? Also maybe try changing the <button> tag to <input type='submit' value='save status'/> instead
I have my model as follows
public class PlaceOrder
{
public int orderCode { set; get; }
public string Order_ID { set; get; }
public int orderDetailCode { set; get; }
[Required]
public string Topic { set; get; }
//50 more fields are there
}
Using editorforModel displays all the fields in the model. I want to have a editor helper which takes the property name and only shows editor for that specific property.
I wrote a create/edit/details actions for my model and working fine. What my final goals is that I want to have edit button next to every field on the details view. As soon I click on edit it allows to update and validate the input as well
EDIT
I am using following snippet for edit link
#(Html.Awe().PopupFormActionLink()
.LinkText("Edit")
.Name("editP")
.Url(Url.Action("PropertyEdit", "PlaceOrder", new
{
PropertyName = Html.NameFor(model => model.SubjectCategoryCode),
propertyValue = Html.IdFor(model => model.SubjectCategoryCode),
ordercode = Model.orderCode
})
)
.Title("Editor for " + Html.NameFor(model => model.SubjectCategoryCode))
and I want something that I pass the field name and it dispalys the relevant fields and do the validation
You could just use an EditorFor and a form for each field:
#using Html.BeginForm("action", "controller")
{
#Html.EditorFor(m => m.ordercode)
<input type="submit" />
}
#using Html.BeginForm("action", "controller")
{
#Html.EditorFor(m => m.orderDetailCode)
<input type="submit" />
}
Of course, you would need a different action for each item and you need a way to get the other values as well, since you're only posting one value to the controller. To achieve this you could include a hidden field with the id and retrieve the other values on the server.
There's the Html.EditorFor(m => m.Property) method for this (your model should be set to PlaceOrder to use this helper, as with any statically typed helpers).
Edit: Bah, Kenneth was faster :-).
I have some HTML in my Razor view that is basically 3 rows of a pair of Select and Input elements:
(There could be more than 3)
<select name="SocialNetwork[1]">
<option>Skype</option>
<option>Twitter</option>
<option>Facebook</option>
</select>
<input name="SocialNetworkUsername[1]" type="text" />
<select name="SocialNetwork[2]">
<option>Skype</option>
<option>Twitter</option>
<option>Facebook</option>
</select>
<input name="SocialNetworkUsername[2]" type="text" />
<select name="SocialNetwork[3]">
<option>Skype</option>
<option>Twitter</option>
<option>Facebook</option>
</select>
<input name="SocialNetworkUsername[3]" type="text" />
When the form is posted to my controller method:
public ActionResult SaveDetails(MyModel model)
{
}
How can I 'map' the values of SocialNetwork | SocialNetworkUsername to a model?
Something like:
public class MyModel
{
public string SomeProperty { get; set; }
public string SomeOtherProperty { get; set; }
public Dictionary<string,string> SocialNetworks { get; set; }
}
I'm not 'stuck' using a dictionary if there is something more appropriate
You can use an editor template to achieve this.
the editor template will allow you to loop through a list of social networks in your main view and use the #Html.EditorFor helper to render out your dropdown lists and make model binding easy.
To start with your model should represent a single Social Network
In your model you need properties to contain the dropdown list values, and a property for the selected value. You'll also need a property to contain the SocialNetworkUsername
public IEnumerable<SelectListItem> SocialNetworks { get; set; }
public string SelectedSocialNetwork { get; set; }
public string SocialNetworkUserName { get; set; }
To make your editor template add a new folder /Views/Shared/EditorTemplates
In the EditorTemplates folder create a new view with the name MyClass (or whatever your model class name is)
You need to follow these naming conventions as there is no other configuration required for the editor template to work.
In MyClass.cshtml you need to allow editing of a single social network
#model MyModel
#Html.DropDownListFor(m => m.SelectedSocialNetwork, Model.SocialNetworks)
#Html.TextBoxFor(m => m.SocialNetworkUserName)
Then in your main view you use a List of MyModel as the model for the view and use a for loop to render out your editor templates.
#model List<MyModel>
#using(Html.BeginForm())
{
for (var i = 0; i < Model.Count; i++)
{
Html.EditorFor(Model[i])
}
}
In your controller just change your action method parameter to accept a List of MyClass and model binding should just magically work ;)
I am trying to use a simple form to allow authorized users to modify content on select pages on an MVC3 Razor site that I'm building. I am unable to get the edit form to post correctly though.
My model is as follows:
public class WebContent
{
public virtual UInt32 id { get; set; }
public virtual String page { get; set; }
public virtual String section { get; set; }
[UIHint("tinymce_jquery_full"), AllowHtml]
public virtual String content { get; set; }
}
My Controller:
[Authorize]
public ActionResult Edit(String page, String section)
{
WebContent content = _WebContent.GetSection(page,section);
return View(content);
}
[Authorize]
[HttpPost]
public ActionResult Edit(WebContent content)
{
if (ModelState.IsValid)
{
_WebContent.Update(content);
return View("Index");
}
else return View("Index");
}
And my View:
#model SongbirdsStudios.Models.WebContent
#{
ViewBag.Title = "Edit '"+Model.page+"'Page Content";
}
<div>
<h2>Edit</h2>
#using (Html.BeginForm())
{
<fieldset>
<legend>Page Content</legend>
#Html.HiddenFor(m => m.id)
#Html.HiddenFor(m => m.page)
#Html.HiddenFor(m => m.section)
<div class="editor-label">
Content
</div>
<div class="editor-field">
#Html.EditorFor(m => m.content)
</div>
<p>
<input type="submit" value="Update" />
</p>
</fieldset>
}
</div>
The view renders correctly and displays the expected elements. The UIHint("tinymce_jquery_full") is getting picked up correctly and the TinyMCE editor appears on the page. But, when the form submits, I get an exception.
System.Web.HttpRequestValidationException: A potentially dangerous Request.Form value was detected from the client (content=...)
Everything I've read indicates that the AllowHTML attribute should allow this to post, but it's not for some reason.
I can get around this by adding the [ValidateInput(false)] attribute to the HttpPost controller method. If I do that, then this exception does not occur, but the model still does not get passed to the controller. It just passes null instead. Examining the HttpContext in the debugger indicates that it is passing 4 separate values - one for each property in my model instead of passing the model class back to the controller. I can't figure out what I need to change to make this work correctly.
I'm hoping it's something simple that I missed, and someone with a better eye can see what it is.
So after further investigation into how ASP MVC maps form fields to the model class and examining the HTML emitted to the browser, I found that this was an issue with the name of the property in my WebContent class.
public virtual String content { get; set; }
The TinyMCE editor uses a content variable to define certain characteristics associated with the editor interface. This was apparently causing the HTML 'content' generated by the user input in the editor to not get mapped back to the Model property.
Simply changing the name of the property in the model class (and of course fixing the corresponding database mapping and view references) immediately fixed the problem.
public virtual String web_data_content { get; set; }
Everything else being identical, this worked perfectly with the UIHint and AllowHTML attributes.
Add this attribute on your action
[ValidateInput(false)]
This should solve your problem
if you use ie7
this may has some err
<input type="submit" value="Update" />
give the button a name
Note: I'm using MVC3+Razor, EF4, CF-CTP5
How can you allow the view to have the ability to add multiple Address classes per Organization dynamically on the client, and bound strongly to the model on post?
How can you have the view parse values in the model if the (ModelState.IsValid == false) such that if you enter 3 addresses and post an invalid model, it re-populates the number addresses and with their appropriate values?
Here are my models:
public class Organization
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Address> Addresses { get; set; }
public virtual ICollection<PhoneNumber> PhoneNumbers { get; set; }
...
}
public class Address
{
public int Id { get; set; }
public string Line1 { get; set; }
public string Line2 { get; set; }
public string City { get; set; }
public string State { get; set; }
public string PostalCode { get; set; }
public string Country { get; set; }
public int Type { get; set; }
}
I'm trying to figure out how you can have the Create action for Organization (/Organization/Create) handle the create like thus (such that addresses and phone numbers are part of the submitted model):
[HttpPost]
public ActionResult Create(Organization organization)
{
if (ModelState.IsValid)
{
_db.Organizations.Add(organization);
_db.SaveChanges();
return RedirectToAction("Details", organization.Id);
}
return View(organization);
}
Your question is quite vaste :)
This is just one of the way your requirement can be achieved and I am sure there are better than mine.
I am going to start from your second question:
How can you have the view parse values
in the model if the
(ModelState.IsValid == false) such
that if you enter 3 addresses and post
an invalid model, it re-populates the
number addresses and with their
appropriate values?
If I correctly understand your request it looks very simple to me. The answer is simply code your view to render a Model class content and return the invalid model to the client exactly as you are doing in your Create action.
If your form (and its fields) have been decorated with the ValidationSummary/ValidationMessage html helpers, you are going to see also validation messages.
How can you allow the view to have the ability to add multiple Address
classes per Organization dynamically
on the client, and bound strongly to
the model on post?
You can have a main view showing Organization attributes and then have another view showing related addresses. Here you can place a hyperlink or a button that open a dialog for adding a new address object and then refresh the address list when done. At the same way you can have edit and delete buttons as icons on the list.
The address list is a piece of markup completely handled at client side that, to be correctly binded to the server side Model class should adhere to some simple naming rules for it's input attributes.
To make the Default Model Binder class bind correctly your form use the following snippet for your Organization class
#using (Html.BeginForm()) {
#Html.HiddenFor(o => o.Id)
#Html.ValidationSummary( true )
<fieldset>
<legend>My Organization</legend>
<div class="editor-label">#Html.LabelFor( model => model.Name )</div>
<div class="editor-field">
#Html.EditorFor( model => model.Name )
#Html.ValidationMessageFor( model => model.Name )
</div>
<br />
<div id="container">
<div>Address List</div>
#foreach (Address a in Model.Addresses ) {
Html.EditorFor(a);
}
</div>
<div style="text-align:right;margin-top:14px;">
<input type="submit" id="btnSubmit" value="Save" />
</div>
</fieldset>
}
To be automatically bindable the resultant code for the form should look as the following
<form action="..." id="..." method="post">
<input type="hidden" name="Id" value="2">
<input type="hidden" name="Name" value="Acme Corporation">
<!-- markup for each address -->
<input type="hidden" name="Addresses[0].Id" value="1">
<input type="hidden" name="Addresses[0].Line1" value="Line 1">
<input type="hidden" name="Addresses[0].Line2" value="Line 2">
... and so on...
</form>
having it's properties named as Addresses[index].PropertyName.
If you add new addresses on the client it does'nt matter so much: as long as your code respect this rule you can have the default Model Binder do the job for you.
Hope this helps
I'm not sure if I understand your question correctly but with respect to question 1 I think you are looking for a ViewModel. Like this perhaps..
OrganizationViewModel.cs
public class OrganizationViewModel
{
public OrganizationViewModel(Organization org, IList<Address> addresses)
{
this.Organization = org;
this.Addresses = addresses
}
public Organization Organization {get;set;}
public IList<Address> Addresses {get;set;}
}
OrganizationController.cs
public class OrganizationController : Controller
{
private readonly IOrganizationService _organizationService: //or whatever method you use
public OrganizationController(IOrganizationService orgService)
{
this._organizationService = orgService;
}
public ActionResult Item(int id)
{
var org = _organizationService.GetOrganizationById(id);
var addresses = _organizationService.GetOrgAddressesByOrgId(id);
return View(new OrganizationViewModel(program, addresses));
}
}
Item.cshtml
#model OrganizationViewModel
<h1>#Model.Organization.Name</h1>
<ul>
#foreach(var a in Model.Addresses)
{
<li>#a.Line1</li>
<li>#a.Line2</li>}
</ul>
Before I try and answer number 2 maybe you should indicate whether I am correctly understanding question 1. Hope this helps.
I managed to do this using LINQ to SQL. Now I'm trying to use Entity Framework instead, but it really makes everything more complicated. So I don't have a solution for you, but perhaps my L2S solution might help?
Using models generated from my database I could in my view do this:
#for (int i = 0; i < Model.Contact.EmailAddresses.Count; ++i)
{
<li>
#Html.TextBoxFor(x => x.Contact.EmailAddresses[i].EmailAddress)
#Html.HiddenFor(x => x.Contact.EmailAddresses[i].EmailAddressID)
</li>
}
I had a view model class:
class ContactViewModel
{
Contact contact { get; set; }
}
This worked fine and in my controller action I got my Contact object with it's Contact.ContactEmailAddresses list filled just like I expected.
But with EF, I cannot use the [i] on the EmailAddresses property generated from the database anymore. The best I have come up with is:
#Html.TextBox("Contact.EmailAddresses[" + i + "].EmailAddress", Model.Contact.EmailAddresses.ElementAt(i).EmailAddress)