This question already has answers here:
getting the values from a nested complex object that is passed to a partial view
(4 answers)
Closed 6 years ago.
This question has been asked but none could solve my problem.
The problem is missing or null data from the partial view is not submittied (POST) along with the main view data.
I have a typed partial view called _Address.cshtml that I include in another view called Site.cshtml.
The typed site view binds to a view model called SiteEditModel.cs
public class SiteEditModel
{
...properties
public AddressEditModel Address {get;set;}
public SiteEditModel()
{
Address = new AddressEditModel();
}
}
The Site view has a form:
#model Insight.Pos.Web.Models.SiteEditModel
...
#using ( Html.BeginForm( "Edit", "Site", FormMethod.Post ) )
{
#Html.HiddenFor( m => m.SiteId )
...
#Html.Partial( "~/Views/Shared/Address.cshtml", this.Model.Address )
...
#Html.SaveChangesButton()
}
The partial Address view is just a bunch of #Html... calls that bind to the Address model.
#model Insight.Pos.Web.Models.AddressEditModel
#{
Layout = null;
}
<div>
#Html.HiddenFor(...)
#Html.HiddenFor(...)
#Html.HiddenFor(...)
#hmtl.LabelFor(...)
</div>
In the controller action Edit I can see the SiteEditModel is populated correctly, the Address property of that model is not.
Where do I go wrong?
Thank you so much.
http://davybrion.com/blog/2011/01/prefixing-input-elements-of-partial-views-with-asp-net-mvc/
#Html.Partial("~/Views/Shared/_Address.cshtml", Model.Address, new ViewDataDictionary
{
TemplateInfo = new System.Web.Mvc.TemplateInfo { HtmlFieldPrefix = "Address" }
})
The key to fix this is with naming of the partialviews input-elements. The Render partial dont know it's a part of something bigger.
I've make an simple example on how you can fix this in a way that you can have multiple Addresses using the same partial view:
public static class HtmlHelperExtensions
{
public static MvcHtmlString PartialWithPrefix(this HtmlHelper html, string partialViewName, object model, string prefix)
{
var viewData = new ViewDataDictionary(html.ViewData)
{
TemplateInfo = new TemplateInfo
{
HtmlFieldPrefix = prefix
}
};
return html.Partial(partialViewName, model, viewData);
}
}
And use this extensions in the view like this:
#using (Html.BeginForm("Edit", "Site", FormMethod.Post))
{
#Html.HiddenFor(m => m.SiteId)
#Html.PartialWithPrefix("_Adress", this.Model.Address, "Address")
<input type="submit" />
}
You can of course make this a bit more fancy with expressions and reflection but that's another question ;-)
Your SiteEditModel Address property is not marked as public, change it to this instead:
public AddressEditModel Address {get;set;}
I would also change your partial to use SiteEditModel instead:
#model Insight.Pos.Web.Models.SiteEditModel
#{
Layout = null;
}
<div>
#Html.HiddenFor(m => m.Address.FooProperty)
...
</div>
This would mean that your properties would end up being named correctly in order for the model binder to pick them up.
Using the above example it would be Name"=Address.FooProperty".
As I remember correctly, the problem is that Html.Partial doesn't populate inputs names correctly. You should have something like:
<input id="Address.Street" name="Address.Street" />
but I assume you have following HTML:
<input id="Street" name="Street" />
You have few solutions:
Insert input name manually:
#Html.HiddenFor(x => x.Street, new { Name = "Address.Street" })
Use Html.EditorFor()
Override names resolving in Html.Partial()
The downside of first solution is that you are hardcoding property name, what isn't ideal. I'd recommend using Html.EditorFor() or Html.DisplayFor() helpers, cause they populate inputs names correctly.
Model binder could not bind child models correctly if you are populating them in partial view. Consider using editor templates instead which is implemented for this reason.
Put your AddressEditModel.cshtml file in \Views\Shared\EditorTemplates\ folder and in your main view use like this:
#using ( Html.BeginForm( "Edit", "Site", FormMethod.Post ) )
{
// because model type name same as template name MVC automatically picks our template
#Html.EditorFor(model=>model.Address)
// or if names do not match set template name explicitly
#Html.EditorFor(model=>model.Address,"NameOfTemplate")
}
Related
I know it would be a basic question but I'm a newbie to ASP.Net MVC. I have fetched data from database using LINQ but there is an issue. I wanna bind that data with input fields of a customized webform. (I'm using MVC). I wanna populate the input fields of webform with fetched data. I'm using EF Database first approach.
My Controller and view is attached.
Controller ActionMethod
public class HomeController : Controller
{
public ActionResult Index()
{
AutoRTGSEntities_1 dc = new AutoRTGSEntities_1();
//dc.policies.Where(cb => cb.Section_Key.Contains("SenderBIC"));
return View(dc.policies.Where(cb => cb.Policy_Section.Contains("RTGS")).ToList()); //get RTGS policy section data
}
}
View
#model IEnumerable<Swift_MT_103.policy>
#{
ViewBag.Title = "Home Page";
}
<div> #Model{ #Html.TextBoxFor(x => x.data_Value)) } </div>
<div> <input type="text" name="ReceiverBIC" id="ReceiverBIC" /> </div>
Rest is HTML and CSS. Snap is attached.
Here's a very basic example of how to this. Let's say you have following class:
public class User
{
public int Id { get; set; }
[Display(Name = "Name")]
public string Name { get; set; }
[Display(Name = "E-mailaddress")]
public string E-mail { get; set; }
}
In the controller you get the user:
public ActionResult Index(int id)
{
var user = Db.Users.FirstOrDefault(x => x.Id == id);
if(user != null)
{
return View(user);
}
//Return to the 'Error' view as no user was found
return View("Error");
}
You also need a View to show everything on screen. Make it a strongly typed view, this way you can pass a Model to it. This class will hold all data you want to pass to the view. Code of the view:
//This line lets the view know which class represents the model
#model User
#Html.LabelFor(m => m.Name)
#Html.TextBoxFor(m => m.Name)
#Html.LabelFor(m => m.Name)
#Html.TextBoxFor(m => m.Name)
Using the Razor syntax instead of plain HTML it is very easy to construct and bind your form elements to the corresponding data. In this case the label will show the value of the Display attribute in the User class and the values of the user will be filled in the textboxes.
More reading:
Getting started with ASP.NET MVC 5
ASP.NET MVC Overview
Update:
In case you have a list of objects, you need to enumerate them in the view:
#model IEnumerable<string>
#foreach (var value in Model)
{
<div>#value</div>
}
And if the model is a class and has a property that is a list:
//Let's say a user has lots of names
public class User
{
public int Id { get; set; }
public List<string> Names { get; set; }
}
//View:
#model User
#Html.TextBoxFor(m => m.Id)
#foreach (var name in Model.Names)
{
<div>#name</div>
}
Try to implement a correct ASP.NET MVC architecture. To get this completed, you'll need to use proper Razor (.cshtml type) Syntax in your Views. Best practice:
Create a dedicated ViewModel class in the Model directory. You might call it CustomerCreditTransferViewModel for example. It should contain all Properties you want to display/edit anywhere on the page.
Once you selected your data from your DBContext in your Action, create an instance of CustomerCreditTransferViewModel and populate all fields from the result.
Update your View to use #model CustomerCreditTransferViewModel instead of Swift_MT_103.policy (believe me, this is going to make your live much easier in future)
Copy-paste your raw HTML Code into the page and start looking for all Fields you want to bind, e.g. Text fields (<input type="text" name="accountno" value="" />) and replace them with the Razor Syntax for Data Binding (#Html.TextBoxFor(x => x.AccountNo)). If done correctly, they should be populated now.
Next step is probably the POST. Follow the base MVC Post technique from the Tutorials. Ensure that the Posted Value is of type CustomerCreditTransferViewModel) again, so you can easily validate values and map back to type of Swift_MT_103.policy.
I am uploading a file using a simple form. The issue I am having is the controller is given a view model as its only argument (The form is based on this view model). When I try to access the posted file through the view model argument it is null. However, when I access the file using Request.Files["FormUpload.File"] I am able to access the posted file.
I understand that in reality the form is attaching an id FormUpload.File which I can access. What I don't understand is how to use the view model to access the file instead. Are my controller arguments correct?
I've put the relevant bits of code below. Any explanation would be greatly appreciated.
The UploadView View Model:
public class UploadViewModel : ListViewModel
{
public HttpPostedFileBase File { get; set; }
}
Controller:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Upload(UploadViewModel model)
{
HttpPostedFileBase requestFile = Request.Files["FormUpload.File"];
// Reference to View Model File
// model.File = null
// Reference to Request.Files
// requestFile = true
}
View (Consider that FormUpload is an instance of an UploadView view model):
#using (Html.BeginForm("Upload", "RelevantController" , new { enctype = "multipart/form-data" }))
{
#Html.AntiForgeryToken()
<fieldset>
<legend>Upload a file.</legend>
#Html.TextBoxFor(m => m.FormUpload.File, new { type = "file" })
<input type="submit" name="Submit" id="Submit" value="Create" class="btn btn-default" />
</fieldset>
}
Cheers and thank you.
As an aside there are countless other questions that relate to "How do I upload this file. I keep getting null". My current workaround seems to be a popular answer.
The fact that you view has #Html.TextBoxFor(m => m.FormUpload.File, new { type = "file" }) means the model in the view is not UploadViewModel, but rather a model containing a property named FormUpload which is typeof UploadViewModel.
The reason it wont bind is because UploadViewModel does not contain a property named FormUpload which in turn contains a property named File.
You need to either
Change the parameter in the POST method to use the same model as
declared in the view, for example, if its #model MyModel then the
method needs to be public ActionResult Upload(MyModel model), or
Use the Prefix property of the BindAttribute which effectively
strips the prefix when binding - public ActionResult Upload([Bind(Prefix="FormUpload")]UploadViewModel model)
I am using nested view models to display views based on user roles.
Model:
public class MainVM {
//some properties
public OneVM One {get; set;}
public TwoVM Two {get; set;}
}
public class OneVM {
//properties
}
public class TwoVM {
//properties
}
As written here that only main model is need to be sent controller. I am using Automapper to map properties from received model.
Controller:
public ActionResult EditAction(MainVM model){
var item = db.Table.Find(model.Id);
//automapper to map
AutoMapper.Mapper.Map(model.One, item); //does not work
db.Entry(item).State = EntityState.Modified;
db.SaveChanges();
}
Is this the right way to do that? What am I doing wrong here.
Update:
This was the view I was using to render nested view models from partial views
View:
#model MainVM
#Html.RenderPartial("_OnePartial", Model.One)
This answer https://stackoverflow.com/a/6292180/342095 defines an Html helper which will generate the partial view with right names.
The value of property One will be empty because you are passing an instance of OneVM to the partial (not the main model) so the form controls are not correctly named with the prefix (which need to be name="One.SomeProperty").
You have included a link to a PartialFor() helper (which works) but don't use it. In the main view it needs to be
#Html.PartialFor(m => m.One, "_OnePartial")
Which is the equivalent of
#Html.Partial("_OnePartial", Model.One,
new ViewDataDictionary { TemplateInfo = new TemplateInfo { HtmlFieldPrefix = "One" }})
The problem probably lies in your HTML. If a model is nested, then the input fields of properties should be like this:
<input type="text" name="SubModel.PropertyName" />
Using HTML helpers, it would look something like this:
#Html.EditorFor(model => model.SubModel.PropertyName)
The ASP.NET MVC Action cannot know, that you want to fill your submodel if it's not in your HTML.
I have a problem concerning Partial views in MVC Razor. Any help is highly appreciated, it's most likely something I've missed, but I could find nothing while searching that had the same problem replicated.
So I'm binding my view to a view model.
public class Person
{
public string Name { get; set; }
public virtual ContactInformation ContactInformation { get; set; }
}
And then I have a view with a partial to render the contact information model.
<div>
#Model.Name
</div>
<div>
#Html.Partial("_ContactInformation", Model.ContactInformation)
</div>
However, the "_ContactInformation" view is rendered without ContactInformation in the nameattribute of the <input>s
Usually razor binds the name attribute to something like: name="ContactInformation.Address". But since it's a partial it gets rendered as name="Address".
Am I missing something or is this the intended way for it to work?
You have two options. Option 1 - specify the prefix explicitly:
#Html.Partial("_ContactInformation", Model.ContactInformation, new ViewDataDictionary
{
TemplateInfo = new System.Web.Mvc.TemplateInfo { HtmlFieldPrefix = "ContactInformation" }
})
Options 2 is to turn partial view into an editor template for your model and than use EditorFor helper method, that should be able to add prefixes for you:
#Html.EditorFor(m => m.ContactInformation)
I have a strongly-typed partial view whose model contains a property with the same name as the parent page's view model. For some reason the rendering engine is rendering the parent view model value, not the expected value (well, the value I expect at least!)
Parent page view model extract:
public class ParentPageViewModel
{
public int Id { get; set; } // problem property
...
public IEnumerable<ChildViewModel> Children { get; set; }
}
Child page view model extract:
public class ChildViewModel
{
public int Id { get; set; } // problem property
...
}
Parent page extract (Razor):
#model ParentPageViewModel
...
#foreach (var item in Model.Children)
{
#Html.Partial("MyPartialView", item)
}
...
Partial view extract:
#model ChildViewModel
...
<form ...>
#Html.HiddenFor(m => m.Id) // problem here - get ParentPageViewModel.ID not ChildViewModel.Id
</form>
...
So basically in my rendered output, my hidden field has the value of the parent view model element, NOT the value passed to the partial view. It's definitely being caused by the name, as changing the name of #ChildViewModel.Id# to something like #ChildViewModel.ChildId# makes it work as expected. Interestingly, when inspecting the view model values in the debugger I do see the correct values; it's only the rendered output that's wrong.
Is there a way round this or 'correct' way of doing what I'm trying to do (I'm rendering mini forms in a table for ajax validation/posting of updates to table rows)
Thanks,
Tim
I think changing your call to this will solve the problem:
#Html.Partial("MyPartialView", item, new ViewDataDictionary())
The child view is picking up the value from the ViewData dictionary - so this passes in a new dictionary to the child view (hattip danludwig).
Found a solution, just manually creating the hidden field, e.g.:
<input type="hidden" name="Id" value="#Model.Id" />
instead of using Html.HiddenFor.
(I won't mark this as answered for a while in case there are any other solutions or anyone can explain the problem)
I know this an old post. But I figured since I landed here when I was facing the same problem, I might as well contribute.
My issue was a little different. In my case, the main view's Id was incorrect after a partial view action that required the whole page/view to be refreshed was triggered. I solved the problem with ModelState.Clear
ModelState.Clear();
return View("MyPartialView", model); //call main view from partial view action
Create a file named ChildViewModel.cshtml in Views/Shared/EditorTemplates. Put your partial view into that file:
in ~/Views/Shared/EditorTemplates/ChildViewModel.cshtml
#model ChildViewModel
...
<form ...>
#Html.HiddenFor(m => m.Id)
</form>
...
Then, render it like this:
#model ParentPageViewModel
...
#foreach (var item in Model.Children)
{
#Html.EditorFor(m => item)
}
...
Or, if you'd rather keep the view as a partial and not as an editor template, use Simon's answer.