Post a form with multiple partial views - asp.net-mvc

I'm currently trying to post a form composed of two strongly typed views. This question is similar but it doesn't have an answer:
MVC 3 Razor Form Post w/ Multiple Strongly Typed Partial Views Not Binding
When I submit form the model submitted to the controller is always null. I've spent a couple of hours trying to get this to work. This seems like it should be simple. Am I missing something here? I don't need to do ajax just need to be able to post to the controller and render a new page.
Thanks
Here's my view code:
<div>
#using (Html.BeginForm("TransactionReport", "Reports", FormMethod.Post, new {id="report_request"}))
{
ViewContext.FormContext.ValidationSummaryId = "valSumId";
#Html.ValidationSummary(false, "Please fix these error(s) and try again.", new Dictionary<string, object> { { "id", "valSumId" } });
#Html.Partial("_ReportOptions", Model.ReportOptions);
#Html.Partial("_TransactionSearchFields", new ViewDataDictionary(viewData) { Model = Model.SearchCriteria });
}
Here's the code in the controller:
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult TransactionReport(TransactionReportRequest reportRequest)
{
var reportInfo = new List<TransactionReportItem>();
if (ModelState.IsValid)
{
var reportData = _reportDataService.GetReportData(Search.MapToDomainSearchCriteria(reportRequest.SearchCriteria));
if (reportData!=null)
{
reportInfo = reportData.ToList();
}
return View(reportInfo);
}
return View(reportInfo);
}
The partial views themselves are pretty irrelevant since all they are doing is biding and displaying their models.

Partials are not the way to go here. You are looking for EditorTemplates, these are made for what you want. This case, your properties will be nicely bound to your model (that you will submit).
Your main View will have this form (note that you only have to use EditorFor instead of Partial; in this case, you probably will need to put that viewData parameter in the ViewBag or so):
#using (Html.BeginForm("TransactionReport", "Reports", FormMethod.Post, new {id="report_request"}))
{
ViewContext.FormContext.ValidationSummaryId = "valSumId";
#Html.ValidationSummary(false, "Please fix these error(s) and try again.", new Dictionary<string, object> { { "id", "valSumId" } });
#Html.EditorFor(model => model.ReportOptions);
#Html.EditorFor(model = Model.SearchCriteria });
}
Now you only have to drag your partials to the folder ~/Shared/EditorTemplates/ and rename them to match the model name they are the editor templates for.
In the ~/Shared/EditorTemplates/ folder, make a new "view", example "SearchCriteria.cshtml". Inside, put as "model" the type of class you which to create an editor template for. Example (example class has properties Name and OtherCriteria):
#model MyNamespace.SearchCriteria
<ul>
<!-- Note that I also use EditorFor for the properties; this way you can "nest" editor templates or create custom editor templates for system types (like DateTime or String or ...). -->
<li>#Html.LabelFor(m => m.Name): #Html.EditorFor(m => m.Name)</li>
<li>#Html.LabelFor(m => OtherCriteria): #Html.EditorFor(m => m.OtherCriteria</li>
</ul>
Some good reading about them:
https://www.exceptionnotfound.net/asp-net-mvc-demystified-display-and-editor-templates/
https://www.hanselman.com/blog/ASPNETMVCDisplayTemplateAndEditorTemplatesForEntityFrameworkDbGeographySpatialTypes.aspx

You should add prefix to the PartialView's fields. That will let binding data correctly.
So instead:
#Html.Partial("_ReportOptions", Model.ReportOptions);
Use:
#Html.Partial("_ReportOptions", Model.ReportOptions, new ViewDataDictionary { TemplateInfo = new TemplateInfo { HtmlFieldPrefix = "ReportOptions" }})

I agree with #Styxxy and #Tony, Editor Templates are the better solution. However, your problem is that that you are feeding a sub-model to the partial views. Thus, when the partial view renders it doesn't know that it's part of a larger model and does not generate the correct name attributes.
If you insist on using Partials rather than Editor Templates, then I suggest only passing the Model to the partials, then having each partial do Model.Whatever.Foo and it will generate the correct name attributes for binding.

Try using EditorTemplates instead of Partials http://coding-in.net/asp-net-mvc-3-how-to-use-editortemplates/.

#Html.Partial("_ReportOptions", Model.Contact, new ViewDataDictionary()
{
TemplateInfo = new TemplateInfo()
{
HtmlFieldPrefix = "Contact"
}
})
)
#Html.Partial("_TransactionSearchFields", Model.SearchCriteria, new
ViewDataDictionary()
{
TemplateInfo = new TemplateInfo()
{
HtmlFieldPrefix = "SearchCriteria"
}
})

Related

MVC Razor Modelbinding for partial view (post)

For a postrequest the model binding does not seem to work, if I'm using a partial view.
The main view's model is MainViewModel.
public class MainViewModel
{
ModelPart Model {get;set;}
}
public class ModelPart
{
Public string MyVariable {get; set;}
}
If I pass the main viewmodel to the partial view, then the binding is successfull and any input in MyVariable is found in the Controller Post method.
MainPage
#model TEST.MainViewModel
#using (Html.BeginForm(FormMethod.Post, new { data_ajax = "false", enctype = "multipart/form-data" }))
{
#Html.Partial("~/Areas/Views/Shared/_Input.cshtml", Model.Model)
}
Partial
#model TEST.ModelPart
#Html.EditorFor(m => m.MyVariable)
This works!
If I pass only the ModelPart to the partial view, then the binding is not successful.
MainPage
#model TEST.MainViewModel
#using (Html.BeginForm(FormMethod.Post, new { data_ajax = "false", enctype = "multipart/form-data" }))
{
#Html.Partial("~/Areas/Views/Shared/_Input.cshtml", Model)
}
Partial
#model MainViewModel
#Html.EditorFor(m => m.Model.MyVariable)
This does not work. As I would like to use partials to re-use input forms, this issue is something I would like to resolve.
Do I have to do a manual bind of the properties in the partial view? Another workaround is to pass values using javascript, but is there anyway I can get the default binding to pick this up?
When using #Html.Partial , the names generated won't be prefixed by the the parents. Thus while posting the model binder won't be able to map post object.
There are 2 workaround :
Manually set the prefix of the context while rendering partial view (not a good idea).
Use editor templates (better alternative).
Editor templates are used the same way as partial view, but they will bind correctly.
To do so:
Main view:
#model TEST.MainViewModel
#using (Html.BeginForm(FormMethod.Post, new { data_ajax = "false", enctype ="multipart/form-data" }))
{
#Html.EditorFor(m=>m)
}
Editor template (in Views/Shared/EditorTemplates/MainViewModel.cshtml )
#model MainViewModel
#Html.EditorFor(m => m.Model.MyVariable)

Setting TAB Order on MVC View with DropDownListFor()/EditorFor()?

Visually speaking, on one of my MVC Views I have about 20 fields in standard vertical order, with the first 8 or so having optional [Create] boxes in same <div> group over on the right.
My default tab order currently hits my first dropdown, then goes right to [Create], down to the next, then right, etc. What I would like to do set the TAB order to where it goes straight down my various fields and leave the [Create] boxes as optional for the user (or at the end of the tab order). While there seems to be a lot of discussion on this with a quick search, there seems to be inconsistent answers; a lot of them seemingly from a couple years ago regarding setting TAB Order in an EditorFor() but being forced to use Custom Editor Templates or switch to TextBoxFor()?
Hoping someone can weigh in on this. The below somewhat details my fields:
(8 of these DropDownListFor()):
#Html.DropDownListFor(model => model.STATUS_ID, (SelectList)ViewBag.Model_List, htmlAttributes: new { #class = "form-control dropdown", #id = "selectStatus" })
(12 of these EditorFor()):
#Html.EditorFor(model => model.NOTE, new { htmlAttributes = new { #class = "form-control" } })
To set the tab order, all you need to do is be able to add an extra attribute, tabindex to the generated field. That's easy enough with something like TextBoxFor or DropDownListFor, since they actually take an htmlAttributes parameter specifically for this purpose:
#Html.TextBoxFor(m => m.Foo, new { tabindex = 1 })
In the past, the same could not be said for EditorFor. Since it's a "templated" helper, the editor template, not the method call, effects what's generated. You can see this in the definition of EditorFor, as there's no htmlAttributes param like the other helpers have, but rather additionalViewData.
Starting with MVC 5.1, Microsoft made it possible to pass additional HTML attributes to EditorFor, via a specially named ViewData key, "htmlAttributes". As a result, you can achieve the same thing as when using something like TextBoxFor, although it's a little more verbose:
#Html.EditorFor(m => m.Foo, new { htmlAttributes = new { tabindex = 1 } })
See, you're still actually passing additionalViewData here, but that additional view data contains an anonymous object keyed to htmlAttributes. The built-in editor templates, then, know how to utilize ViewData["htmlAttributes"] to add additional attributes to the generated element. However, this only applies to the default editor templates because Microsoft has specifically programmed them to use this. As soon as you add your own custom editor templates, you're right back to where you started.
There's a number of ways you could approach this with custom editor templates. First, you could just pass the tab index directly as view data, and utilize that in your template:
#Html.EditorFor(m => m.Foo, new { tabindex = 1 })
Then, in your editor template:
#Html.TextBox("", ViewData.TemplateInfo.FormattedModelValue, new { tabindex = ViewData["tabindex"]})
Second, you could mimic EditorFor's behavior with the default templates:
#Html.EditorFor(m => m.Foo, new { htmlAttributes = new { tabindex = 1 } })
Then, in your editor template:
#Html.TextBox("", ViewData.TemplateInfo.FormattedModelValue, ViewData["htmlAttributes"])
However, that option doesn't allow you to have "default" attributes. It's an all or nothing approach. To truly be able to utilize ViewData["htmlAttributes"] as the built-in editor templates do, you'll need to combine the default attributes with the passed in ones, first, and then pass the whole shebang to htmlAttributes. I've got a blog post that discusses that in depth, but TL;DR: you'll need the following extension:
using System.Collections.Generic;
using System.Linq;
using System.Web.Mvc;
using System.Web.Routing;
public static partial class HtmlHelperExtensions
{
public static IDictionary<string, object> MergeHtmlAttributes(this HtmlHelper helper, object htmlAttributesObject, object defaultHtmlAttributesObject)
{
var concatKeys = new string[] { "class" };
var htmlAttributesDict = htmlAttributesObject as IDictionary<string, object>;
var defaultHtmlAttributesDict = defaultHtmlAttributesObject as IDictionary<string, object>;
RouteValueDictionary htmlAttributes = (htmlAttributesDict != null)
? new RouteValueDictionary(htmlAttributesDict)
: HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributesObject);
RouteValueDictionary defaultHtmlAttributes = (defaultHtmlAttributesDict != null)
? new RouteValueDictionary(defaultHtmlAttributesDict)
: HtmlHelper.AnonymousObjectToHtmlAttributes(defaultHtmlAttributesObject);
foreach (var item in htmlAttributes)
{
if (concatKeys.Contains(item.Key))
{
defaultHtmlAttributes[item.Key] = (defaultHtmlAttributes[item.Key] != null)
? string.Format("{0} {1}", defaultHtmlAttributes[item.Key], item.Value)
: item.Value;
}
else
{
defaultHtmlAttributes[item.Key] = item.Value;
}
}
return defaultHtmlAttributes;
}
}
And then you'll need to add the following to top of your custom editor templates:
#{
var defaultHtmlAttributesObject = new { type = "date", #class = "form-control" };
var htmlAttributesObject = ViewData["htmlAttributes"] ?? new { };
var htmlAttributes = Html.MergeHtmlAttributes(htmlAttributesObject, defaultHtmlAttributesObject);
}
You'd change the defaultHtmlAttributesObject variable depending on what attributes the generated input should have by default for that particular template.

MVC - Passing Parameters from View to Popup

I'm having a problem passing parameters from my view to my popup.
In my view, I have the following razor code to render an Action Link wherein when I click it, a pop-up will appear:
#Html.ActionLink("[Edit Product]", "Edit", "Products", new { ProductCode = #Model.ProductCode}, new { #class = "editLink" })
I'm not quite sure if this is correct or if the part new { ProductCode = #Model.ProductCode} makes any sense (please explain to me what that part does, anybody hihi).
Anyway, my pop-up code accepts a parameter like this:
#model MySuperStore.Models.ViewModel.ProductsModel
Whenever I try to display the ProductCode via #Mode.ProductCode, I always receive an error saying the reference not set to an instance of an object.
I have tried placing the ProductCode in a ViewData through the MainView and accessing it on the pop-up but that doesn't seem to work either.
Can somebody please help me? Thanks. Cheers!
Your code looks fine:
#Html.ActionLink(
"[Edit Product]",
"Edit",
"Products",
new { ProductCode = Model.ProductCode },
new { #class = "editLink" }
)
Just ensure that the view you are putting this code in is strongly typed and that the controller action that rendered it passed an actual model to it.
As far as the Edit action is concerned you should also ensure you are invoking you are passing a non-null model to the view:
public ActionResult Edit(int productCode)
{
ProductsModel model = ... fetch your model using the productCode
return View(model);
}
Now inside your Edit.cshtml view (or partial view if you are opening this using jQuery UI dialog or something in a pop-up) you could use the properties of the model:
#model ProductsModel
#Html.DisplayFor(x => x.ProductCode)

ModelMetadata.Watermark and MVC View Models

I'm struggling to display a watermark (so called placeholder) on my MVC3 form inputs.
There is already few posts down here talking including the quite focused one here:
Html5 Placeholders with .NET MVC 3 Razor EditorFor extension?
In these posts, advice is made to create tweaked html.TextBox templates.
In my case, my asset is that I should not need any editor template as I'm tweaking them inline.
Better than long talks, here is the relevant part of the actual code:
~/Models/myModel.cs
namespace myProject.Models {
public class myFormModel {
...
[Display(Name = "firstFieldName", Prompt = "firstFieldPrompt")]
public string firstFieldValue { get; set; }
...
}
}
~/Controllers/myFormSurfaceController.cs
namespace myProject.Controllers {
public class myFormSurfaceController : SurfaceController {
...
[ChildActionOnly]
public PartialViewResult myForm()
{
return PartialView("myPartialView", new myFormModel());
}
...
[HttpPost, ValidateAntiForgeryToken]
public ActionResult handleMyFormSubmit(myFormModel model) {...}
...
}
}
~/Views/myProject/Partial/myPartialView.cshtml
#model myFormModel
#{
using (Html.BeginUmbracoForm("handleMyFormSubmit", "myFormSurface", null, new Dictionary<string, object> { { "class", "myFormStyle" }, { "id", "myFormId" } }))
{
...
#Html.TextBoxFor(x => x.firstFieldValue, new { #class = "myInputStyle", #placeholder = ViewData.ModelMetadata.Watermark })
...
}
}
Result is that the placeholder html tag is showing up correctly on my rendered webpage but is empty though Name tag is filled up correctly, even without DisplayName decoration set on my view model's property.
http://localhost/testpage
...
<input type="text" value="" placeholder="" name="firstFieldName" id="firstFieldName" class="myInputStyle">
...
What am I missing here ? I did try indeed to create both editor templates (MultilineText and String) in the correct folder (~/Views/Shared/EditorTemplates/) but I assume they are never called as I'm using "Html.TextBoxFor" and not "Html.TextBox"...
Other thing, if I remove "#placeholder = ViewData.ModelMetadata.Watermark" from the #Html.TextBoxFor call, I don't have any "placeholder" displayed on the rendered webpage. Which is good, this part of the call is definitively fine.
Thanks in advance for any help on that point...
Nicolas.
Edit:
What about if I create more variable in my model.
For instance:
public string firstFieldPrompt { get { return "bla"; } set { } }
and then
#Html.TextBoxFor(x => x.firstFieldValue, new { #class = "myInputStyle", #placeholder = x => x.FirstFieldPrompt })
?
I realise this is an oldie, but you can use the ModelMetadata.FromLambdaExpression() method from within your view (without using templates), i.e.
#Html.TextBoxFor(x => x.firstFieldValue,
new {
#class = "myInputStyle",
#placeholder = ModelMetadata.FromLambdaExpression(x => x.firstFieldValue, ViewData).Watermark
})
Hope this helps someone :-)
The reason you get an empty watermark is that in your case (i.e. not using templates) ViewData actually refers to myFormModel (not myFormModel.firstFieldValue); you are essentially retrieving the watermark of your view model. Since models can't have watermarks ([Display] can't be applied to classes) ViewData.ModelMetadata.Watermark will always be empty for views.
As far as I can see, your only option here (if you don't want to use templates) is doing the watermark inline:
#Html.TextBoxFor(x => x.firstFieldValue, new { #class = "myInputStyle", placeholder = "Your watermark text here" })
By the way, if want to use templates, you need to use the templated helpers #Html.EditorFor() and #Html.DisplayFor(). #Html.TextBoxFor() is just the strongly-typed version of #Html.TextBox(). It is not templated.

How to pass data to the view in mvc asp.net?

let me ask the question first.
Where is the correct place to call a function that load a list of values to be display on a view?
I create a controller like this
public ActionResult Create()
{
SeaModel newSea = new SeaModel();
return View("Season/CreateSea", newSea);
}
//I not quite sure if this should go here or in another place
partial class seaDataContext
{
public List<string> getSeaSettings()
{
var seaSettings = from p in settings
where p.setting == "periods"
select p.value;
return seaSettings.ToList<string>();
}
}
The model is like
public class SeaModel
{
[Required(ErrorMessage="*")]
[Display(Name = "Period Name")]
public string periods { get; set; }
}
Which create a view like
#using (Html.BeginForm()) {
#Html.ValidationSummary(true, "Please correct the following errors.")
<fieldset>
<legend>Fields</legend>
<div class="editor-label">
#Html.LabelFor(model => model.periods)
</div>
<div class="editor-field">
#Html.Select(model => model.periods, ****My doubt comes here****)
#Html.ValidationMessageFor(model => model.periods)
</div>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}
so, How and where do I pass the return of getSeaSettings() to the view?
Thanks
best practice is to make a Selectlist in your Model for this dropdown.
however you also can use the more easy option: using ViewData
public ActionResult Create()
{
SeaModel newSea = new SeaModel();
ViewData["myDropDown"] = new SelectList(listOfObjects, "valueOfTheObjectLikeID", "NameYouWantToShowInDropdown");
return View("Season/CreateSea", newSea);
}
then:
#Html.Select(model => model.periods, ViewData["myDropDown"] as SelectList)
dont forget in your [HttpPost] method to also fill in the viewdata if you'r validation fails, so the dropdown can be rebuilt.
You need to look at repository pattern. Have a look at this tutorial at asp.net site
http://www.asp.net/mvc/tutorials/creating-model-classes-with-linq-to-sql-cs
Stefanvds's approach was what I used to do.
But I found out there is a better way using additionalViewData.
Use this EditorFor HTML Helper extension method.
http://msdn.microsoft.com/en-us/library/ff406462.aspx
Instead of passing Select List Items into ViewData in the Controller, you do this in your View.
Pass in your list items as an anonymous object for the additionalViewData parameter.
Important thing is to use the same name as your Property Name.
#Html.EditorFor(
m => m.MyPropertyName,
new { MyPropertyName = Model.ListItemsForMyPropertyName }
);
Of course, you are passing in a View Model object.
public class MyViewModel
{
public int MyPropertyName;
public IList<SelectListItem> ListItemsForMyPropertyName;
}
EditorFor method uses your existing Editor View Templates.
So you don't need to specify CSS class names and HTML attributes again like when you use the Html.DropDown( ) method.
For example,
//------------------------------
// additionalViewData
//------------------------------
#Html.EditorFor(
m => m.MyPropertyName,
new { MyPropertyName = Model.ListItemsForMyPropertyName }
)
//------------------------------
// traditional approach requires to pass your own HTML attributes
//------------------------------
#Html.DropDown(
"MyPropertyName",
Model.ListItemsForMyPropertyName,
new Dictionary<string, object> {
{ "class", "myDropDownCssClass" }
}
);
//------------------------------
// DropDownListFor still requires you to pass in your own HTML attributes
//------------------------------
#Html.DropDownListFor(
m => m.MyPropertyName,
Model.ListItemsForMyPropertyName,
new Dictionary<string, object> {
{ "class", "myDropDownCssClass" }
}
);
That is why I like the additionalViewData approach more.
Because, the HTML code rendered relies on the Editor Templates completely.
Also, using specialized View Models make your code cleaner and easier to maintain.
Hope it helps.

Resources