I have implemented an image upload child action form for an application. I have a strongly typed partial view.
public class ImageViewModel{
public long ImageId{get;set;}
public long OwnerId{get;set;}
public string ImageName{get;set;}
public string ImageDescription{get;set;}
public IEnumerable<HttpPostedFileBase> Files { get; set; }
}
Razor code looks something like this:
<form action="UploadImage" method="post" enctype="multipart/form-data">
#Html.ValidationSummary()
#Html.HiddenFor(m => m.OwnerId)
#Html.HiddenFor(m => m.ImageId)
#HtmlEditorFor(m=>m.ImageName)
<input type="file" name="Files" id="file0" />
<input type="submit" value="Upload" />
</form>
Here is the problem. When form is posted back, the model has uploaded file and ImageName value in it. But values that were bound using HiddenFor are missing.
[HttpPost]
public ActionResult UploadImage(ImageViewModel model)
{ ...}
I have checked HTML source. Hidden fields are rendered corrected with Id and names matching to property named of model. On post back I checked the raw request. Both hidden fields are carried in Form collection. But model binding is not setting the values of these fields in properties.
Is there something that I am missing about these hidden fields?
Thanks
Related
I've seen thousands of examples using the razor BeginForm and I know that it auto binds the controller input to the submitted info... anyway I want to know if the same auto bind exists in a normal
<form method="get" action="#Url.Action("Index","Home")">
<input type="text" name="foo">
</form>
and if it doesn't ... how can I bind it... and please don't tell me to use html helpers...
yes you can bind model with normal html but the value of the name attribute has to be same as name of the property of the model e.g
public class viewmodel
{
public string FirstName {get; set;}
public string LastName {get; set;}
}
so your html will be
#model viewmodel
<form method="get" action="#Url.Action("Index","Home")">
<input type="text" name="FirstName" value="#model.FirstName"> //name should be of same name as property name
<input type="text" name="LastName" value="#model.LastName">
<input type='submit' value='Submit'/>
</form>
and finally your action will be
public ActionResult Index(viewmodel model)
{
return View(model);
}
I am using RazorGenerator to embed a view into a shared DLL. I have a HiddenFor that is generating an invalid name attribute and a missing id attribute.
I have a view model:
public class InsuredPartiesViewModel
{
public List<OrganisationViewModel> OrganisationWithBranches { get; set; }
...
public class OrganisationViewModel : IInsuredPartyViewModel
{
public bool CanQueryIVTRForBranches { get; set; }
...
In the Razor view I am using a #helper to iterate through:
#helper RenderHiddenFieldsForInsuredParty(int insuredPartyIndex, bool branchesAreVisible)
{
#Html.HiddenFor(x => Model.OrganisationWithBranches[insuredPartyIndex].CanQueryIVTRForBranches)
...
The output is:
<input data-val="true" data-val-required="The HasQueryIVTRForBranches field is required." name="<>4__this.Model.OrganisationWithBranches[0].HasQueryIVTRForBranches" type="hidden" value="False" />
The expected output is (I can see this from an older code branch):
<input data-val="true" data-val-required="The HasQueryIVTRForBranches field is required." name="<>4__this.Model.OrganisationWithBranches[0].HasQueryIVTRForBranches" type="hidden" value="False" />
The id attribute is missing and the name attribute of <>4__this.Model.OrganisationWithBranches[0].HasQueryIVTRForBranches looks like Razor's parser is getting stuck.
Have you seen this before? How do I debug the Razor output?
Identical behaviour in VS2013 and VS2015, ASP.NET MVC 5. The data is correct going into the View.
Update
The helper method NameFor also produces the error result.
#Html.NameFor(x => Model.OrganisationWithBranches[insuredPartyIndex].CanQueryIVTRForBranches)
I have a main view which contains a partial view. The model of partial view has 1 property called "HttpPostedFileBase file", together with other properties
However when the main-view get posted, all the other properties in that model get correct value, but the "HttpPostedFileBase file" is null. I already set the name of to be the same as parameter. Also even Request.Files give me 0 number of files.
What have I done wrong?
P.S. My main-view actually has 2 partial views. One PV has the same model as main-view. The 2nd one is what I mentioned above. The model contains a list of objects and HttpPostedFileBase file. Code like this:
public class MyPartialViewModel
{
public List<MyObject> objInfos { get; set; }
public ICollection<HttpPostedFileBase> file { get; set; }
}
And in the PV I looply use #Html.EditFor(model=>model.objInfos[i]) to bind it to a template.
So in main-view post method, I could get "objInfos" list & all ojects' value correct. But just NULL for "file".
Try adding enctype = "multipart/form-data" as one of the htmlAttributes in the #Html.BeginForm() helper.
EDIT: To not make a visual full postback:
AJAX FILE Upload in MVC3 using iFrame
In this right moment I'm using this approach, actually implementing this as we write.
I've just come across the same problem, and have worked around it by placing an extra parameter in the ActionResult to take the file upload parameter.
[Authorize]
[HttpPost]
public ActionResult MyActionResult(MyViewModel viewModel, HttpPostedFileBase myFileToUpload)
{
myViewModel.PartialViewModel.MyFileToUpload = myFileToUpload;
//manually bind MyFileToUpload;
//for some reason, because it's in the Partial View and it's of type HttpPostedFileBase, it won't post.
return View(viewModel);
}
Just for context:
My PartialViewModel contains the code:
public HttpPostedFileBase MyFileToUpload{ get; set; }
and PartialView the code:
<div class="editor-item">
<div class="editor-label">
#Html.LabelFor(model => model.MyFileToUpload)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.MyFileToUpload)
</div>
</div>
Finally, I have an EditorTemplate Views/Shared/EditorTemplates/HttpPostedFileBase.cshtml:
#model HttpPostedFileBase
<input type="file" name="#ViewData.ModelMetadata.PropertyName" />
It's a workaround, not a solution, but does enable you to get the file uploaded while leaving the HttpPostedFileBase in the PartialViewModel.
I have created view page in MVC like
<%using (Html.BeginForm())
{ %>
<%=LabelHelpers.Label("firstname", "FirstName:")%>
<br/>
<%=Html.TextBox("firstname")%>
<br/><br/>
<%=LabelHelpers.Label("lastname", "Lastname:")%>
<br/>
<%=Html.TextBox("lastname")%>
<br/><br/>
<input type="Button" value="Register"/>
<%} %>
Here I want to write Buttonclick Event ...How and Where should i write?
Your input is of type button - these don't do anything without additional client side code.
If you want to handle the 'event' on the server in a similar way that you would have in ASP.NET, you should convert it to a submit button. Assuming your controller is called 'Account' and your action is called 'Register' your current code would look something like this:
public ViewResult Register()
{
return View();
}
You want to start by passing a model to the view:
public ViewResult Register()
{
var registerModel = new RegisterModel();
return View(registerModel);
}
Your current view is using loosely typed inputs. Since you're passing it a model you can use strongly typed views. Your model should look something like this:
public class RegisterMode
{
public string Firstname { get; set; }
public string Surname { get; set; }
}
To use strongly typed views, change your view to look like this:
<%using (Html.BeginForm())
{ %>
<%=Html.LabelFor(x => x.Firstname)%>
<br/>
<%=Html.TextBoxFor(x => x.Firstname)%>
<br/><br/>
<%=Html.LabelFor(x => x.Surname)%>
<br/>
<%=Html.TextBoxFor(x => x.Surname)%>
<br/><br/>
<input type="submit" value="Register"/>
<%} %>
What we've done is told the view to build labels and text boxes for your RegisterModel type. This will allow the model values to be automatically mapped when you POST the form to the controller.
Do accept the post, we need to add a new Action to the controller, with the same name, but accepting a parameter of type RegisterModel:
public ActionResult Register(RegisterModel model)
{
// do something with the model, such as inserting it into the database.
// model.Firstname will contain the value of the firstname textbox
// model.Surname will contain the value of the surnaem textbox
return RedirectToAction("Success");
}
One last thing to do, to be safe, is to add the [HttpGet] and [HttpPost] attributes to your controller actions to control the methods they accept:
[HttpGet]
public ViewResult Register()
and
[HttpPost]
public ActionResult Register(RegisterModel model)
I suggest you read up on MVC at http://www.asp.net/mvc and read the NerdDinner tutorial chapter in Professional MVC (available for free online in PDF format).
joining to the question, want to make it more concrete
i have a form, the form already has a submit button, but i need to bind an additional action to another button.
yes, i do know that MVC does not support events 'cause HTML forms doesn't support them.
so the solution i've came to is to create to hidden inputs inside the form and bind an 'onclick' event (jquery 'live' method) to every... oh, what the hell? here is the code:
html:
<input type="hidden" id="SenderControlID" name="SenderControlID" value="-1" />
<input type="hidden" id="SenderControlValue" name="SenderControlValue" value="-1" />
js:
if ($('#SenderControlID')[0]) {
$('input[type="submit"], input[type="button"], input[type="checkbox"], input[type="radio"]').live('click', function () {
$('#SenderControlID').val($(this).attr('name'));
$('#SenderControlValue').val($(this).val());
});
}
but maybe there is more elegant solution?
I have some problems with ASP.NET MVC’s default model binder. The View contains HTML like this:
<input name="SubDTO[0].Id" value="1" type="checkbox">
<input name="SubDTO[1].Id" value="2" type="checkbox">
This is my simplified ‘model’:
public class SubDTO
{
public virtual string Id { get; set; }
}
public class DTO
{
public List<SubDTO> SubDTOs { get; set; }
public DTO()
{
SubDTOs = new List< SubDTO>();
}
}
All this works fine if the user selects at least the first checkbox (SubDTO[0].Id). The controller ‘receives’ a nicely initialised/bound DTO. However, if the first check box is not selected but only, for example, SubDTO[1].Id the object SubDTOs is null. Can someone please explain this ‘strange’ behaviour and how to overcome it? Thanks.
Best wishes,
Christian
PS:
The controller looks like this:
[Transaction]
[AcceptVerbs(HttpVerbs.Post)]
public RedirectToRouteResult Create(DTO DTO)
{
...
}
PPS:
My problem is that if I select checkbox SubDTO[0].Id, SubDTO[1].Id, SubDTO[2].Id SubDTOs is initialised. But if I just select checkbox SubDTO[1].Id, SubDTO[2].Id (NOT the first one!!!) SubDTOs remains null. I inspected the posted values (using firebug) and they are posted!!! This must be a bug in the default model binder or might be missing something.
This behavior is "by design" in html. If a check-box is checked its value is sent to the server, if it is not checked nothing is sent. That's why you get null in your action and you'll not find value in the posted form either. The way to workaround this is to add a hidden field with the same name and some value AFTER the check-box like this:
<input name="SubDTO[0].Id" value="true" type="checkbox">
<input name="SubDTO[0].Id" value="false" type="hidden">
<input name="SubDTO[1].Id" value="true" type="checkbox">
<input name="SubDTO[1].Id" value="false" type="hidden">
In this way if you check the check-box both values will be sent but the model binder will take only the first. If the check-box is not checked only the hidden field value will be sent and you\ll get it in the action instead of null.
I think this post on Scott Hanselman's blog will explain why. The relevant line is:
The index must be zero-based and unbroken. In the above example, because there was no people[2], we stop after Abraham Lincoln and don’t continue to Thomas Jefferson.
So, in your case because the first element is not returned (as explained by others as the default behaviour for checkboxes) the entire collection is not being initialized.
Change the markup as follows:
<input name="SubDTOs" value="<%= SubDTO[0].Id %>" type="checkbox">
<input name="SubDTOs" value="<%= SubDTO[1].Id %>" type="checkbox">
What's being returned by your original markup is an unrelated set of parameters, i.e. like calling RedirectToRouteResult Create(SubDTO[0].id, SubDTO[1].id, ..., SubDTO[n].id) which is clearly not what you want, you want an array returned into your DTO object so by giving all the checkboxes the same name the return value to your function will be an array of ids.
EDIT
Try this:
<input name="SubDTO[0].Id" value="<%= SubDTO[0].Id %>" type="checkbox">
<input name="SubDTO[0].Id" value="false" type="hidden">
<input name="SubDTO[1].Id" value="<%= SubDTO[1].Id %>" type="checkbox">
<input name="SubDTO[1].Id" value="false" type="hidden">
You have to return something to make sure there is an element for each index, I suspect that any gap will cause a problem so I'd suggest using a 'null' ID, for example 0 or -1 and then process that out later in your code. Another answer would be a custom model binder.
There is always the alternate option of adding a property to your class that takes an array of strings and creates the SubDTO array from that.
public List<string> SubDTOIds
{
get { return SubDTO.Select(s=>s.Id).ToList(); }
set
{
SubDTOs = new List< SubDTO>();
foreach (string id in value)
{
SubDTOs.Add(new SubDTO { Id = id });
}
}
}
or something like that