View with multiple models only POST 1 model - c#

This is the model I'm using for a view
public class MainRegisterViewModel
{
public RegisterViewModel RegisterModel { get; set; }
public RegisterFormValuesViewModel RegisterValues { get; set; }
}
RegisterFormValuesViewModel contains all the values for the controls (list of countries, states and stuff like that) and RegisterViewModel contains the information for a user.
Then I load the controls like this.
#model ProjetX.Models.MainRegisterViewModel
#{
IEnumerable<SelectListItem> countries = Model.RegisterValues.Countries.Select(x => new SelectListItem()
{
Text = x.Country,
Value = x.Id.ToString()
});
IEnumerable<SelectListItem> states = Model.RegisterValues.States.Select(x => new SelectListItem()
{
Text = x.State,
Value = x.Id.ToString()
});
}
<div class="container">
<div class="row">
<div class="col-md-12">
#using (Html.BeginForm("Register", "Account", FormMethod.Post, new { #class = "form-horizontal", role = "form" }))
{
....
<div class="form-group">
#Html.LabelFor(m => m.RegisterModel.Country, new { #class = "col-md-2 control-label" })
<div class="col-md-10">
#Html.DropDownListFor(m => m.RegisterModel.Country, countries, new { #class = "form-control" })
</div>
</div>
....
Also the Register function takes a MainRegisterViewModel as parameter.
public async Task<ActionResult> Register(MainRegisterViewModel model)
The problem is when I submit the form, RegisterFormValuesViewModel is NULL.
Any ideas why?
Thank you
Context
I'm doing this because I load the RegisterFormValuesViewModel from an API and I'm trying to call it only once. The problem was when a user POST a form with errors and you return the view back, I had to call the API again to get RegisterFormValuesViewModel.
Before this it was only one model and a viewbag for RegisterFormValuesViewModel but I had to call the API every time the form was loaded because the viewbag wasn't posted. That's why I thought I could use 2 models and POST them both.

If you want the values of RegisterFormValuesViewModel to be posted, they need to included in the form or other location that the ModelBinder looks for values. The (default) model binder will pick up values from the action params, Request.Form, route data, and Request.QueryString (I think Request.Files is included too).
If your RegisterFormValuesViewModel is expensive to create, you can add it's values as hidden fields that are posted with the form or implement a custom ValueProviderFactory that works with application or session state.

Related

How to send the Model to a partial view and change it's value in the controller before it arrives to the partial view and update with ajax [duplicate]

The problem I will be describing is very similar to ones I already found (e.g. this post with nearly identical name) but I hope that I can make it into something that is not a duplicate.
I have created a new ASP.NET MVC 5 application in Visual Studio. Then, I defined two model classes:
public class SearchCriterionModel
{
public string Keyword { get; set; }
}
public class SearchResultModel
{
public int Id { get; set; }
public string FirstName { get; set; }
public string Surname { get; set; }
}
Then I created the SearchController as follows:
public class SearchController : Controller
{
public ActionResult Index()
{
return View();
}
public ActionResult DisplaySearchResults()
{
var model = new List<SearchResultModel>
{
new SearchResultModel { Id=1, FirstName="Peter", Surname="Pan" },
new SearchResultModel { Id=2, FirstName="Jane", Surname="Doe" }
};
return PartialView("SearchResults", model);
}
}
as well as views Index.cshtml (strongly typed with SearchCriterionModel as model and template Edit) and SearchResults.cshtml as a partial view with model of type IEnumerable<SearchResultModel> (template List).
This is the Index view:
#model WebApplication1.Models.SearchCriterionModel
#{
ViewBag.Title = "Index";
}
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>SearchCriterionModel</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(model => model.Keyword, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Keyword, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Keyword, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="button" id="btnDisplaySearchResults" value="Search" onclick="location.href='#Url.Action("DisplaySearchResults", "SearchController")'" />
</div>
</div>
</div>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
<div id="searchResults">
</div>
As you can see, I added a div with id="searchResults" below the standard template and edited the button. What I want is to display the partial view SearchResults.cshtml in the div on the bottom, but only after the button is clicked. I have succeeded in showing a partial view there by using #Html.Partial("SearchResults", ViewBag.MyData), but it is rendered when the parent view is loaded for the first time and I set ViewBag.MyData in the Index() method already, which is not what I want.
Summary: On clicking the button, I will obtain some List of SearchResultModel instances (via database access) and then the partial view should be rendered, using this newly obtained data as model. How can I accomplish this? I already seem fail at the first step, that is reacting to the button click with the above code. Right now, I navigate to the URL ~/Search/DisplaySearchResults, but of course there's nothing there and no code-behind method is called.
In traditional ASP.NET I'd just have added a server-side OnClick handler, set the DataSource for a grid and show the grid. But in MVC I already fail with this simple task...
Update: Changing the button to #Html.ActionLink I can finally enter the controller method. But naturally since it returns the partial view, it's displayed as the whole page content. So the question is: How do I tell the partial view to be rendered inside a specific div on the client side?
Change the button to
<button id="search">Search</button>
and add the following script
var url = '#Url.Action("DisplaySearchResults", "Search")';
$('#search').click(function() {
var keyWord = $('#Keyword').val();
$('#searchResults').load(url, { searchText: keyWord });
})
and modify the controller method to accept the search text
public ActionResult DisplaySearchResults(string searchText)
{
var model = // build list based on parameter searchText
return PartialView("SearchResults", model);
}
The jQuery .load method calls your controller method, passing the value of the search text and updates the contents of the <div> with the partial view.
Side note: The use of a <form> tag and #Html.ValidationSummary() and #Html.ValidationMessageFor() are probably not necessary here. Your never returning the Index view so ValidationSummary makes no sense and I assume you want a null search text to return all results, and in any case you do not have any validation attributes for property Keyword so there is nothing to validate.
Edit
Based on OP's comments that SearchCriterionModel will contain multiple properties with validation attributes, then the approach would be to include a submit button and handle the forms .submit() event
<input type="submit" value="Search" />
var url = '#Url.Action("DisplaySearchResults", "Search")';
$('form').submit(function() {
if (!$(this).valid()) {
return false; // prevent the ajax call if validation errors
}
var form = $(this).serialize();
$('#searchResults').load(url, form);
return false; // prevent the default submit action
})
and the controller method would be
public ActionResult DisplaySearchResults(SearchCriterionModel criteria)
{
var model = // build list based on the properties of criteria
return PartialView("SearchResults", model);
}
So here is the controller code.
public IActionResult AddURLTest()
{
return ViewComponent("AddURL");
}
You can load it using JQuery load method.
$(document).ready (function(){
$("#LoadSignIn").click(function(){
$('#UserControl').load("/Home/AddURLTest");
});
});
source code link

MVC5 - Posting a view which contains Html.Action

My application needs to CRUD contracts, and I can attach documents to each contract.
So, in my Edit/Update page, I have three forms:
one to update the contract properties (Edit.cshtml)
one to add document(s) to the contract (AddDocument.cshtml)
one to remove a document from the contract (not necessary to show)
and it looks like this:
Edit.cshtml
#model ContractViewModel
#Html.Action("AddDocument", "Contracts", new { id = Model.IdContract })
#Html.Action("RemoveDocument", "Contracts", new { id = Model.IdContract })
#using (Html.BeginForm("Edit", "Contracts", FormMethod.Post, new { #class = "form-horizontal", enctype = "multipart/form-data" }))
{
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
#Html.HiddenFor(model => model.IdContract)
<div class="form-group">
#Html.LabelFor(model => model.ContractNumber, htmlAttributes: new { #class = "control-label col-md-4" })
<div class="col-md-8">
#Html.EditorFor(model => model.ContractNumber, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.ContractNumber)
</div>
</div> [...]
<input type="submit" value="Update"/>
}
AddDocument.cshtml
#model DocumentViewModel
#using (Html.BeginForm("AddDocument","Contracts", FormMethod.Post, new { #class = "form-horizontal", enctype="multipart/form-data" }))
{
#Html.AntiForgeryToken()
#Html.HiddenFor(model => model.IdContract)
<div class="form-group">
#Html.LabelFor(model => model.DocHttp, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.TextBoxFor(x => x.DocHttp, htmlAttributes: new { #class = "form-control", data_style = "btn-primary", type = "file", multiple = "multiple" })
#Html.ValidationMessageFor(model => model.DocHttp)
</div>
</div>
<input type="submit" value="Add"/>
}
ContractController.cs
public ActionResult Edit(int? id)
{
if (id == null)
{
throw new HttpException(400, "Bad request");
}
Contract contract = business.Get<Contract>(x => x.IdContract == id);
ContractViewModel vm = new ContractViewModel(contract);
if (contract == null)
{
throw new HttpException(404, "Not found");
}
return View(vm);
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(ContractViewModel vm)
{
Contract contract = business.Get<Contract>(x => x.IdContract == id);
if (ModelState.IsValid)
{
[...]
}
return View(vm);
}
public ActionResult AddDocument(int id)
{
DocumentViewModel vm = new DocumentViewModel();
vm.IdContract = id;
return PartialView(vm);
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult AddDocument(DocumentViewModel vm)
{
Contract contract = business.Get<Contract>(x => x.IdContract == vm.IdContract);
if (ModelState.IsValid)
{
[...]
}
return RedirectToAction("Edit", "Contracts", new { id = vm.IdContract });
//return View(vm);
}
Firstly, the issue is, when I submit the Edit form, the [HttpPost]Edit method is naturally called, but also the [HttpPost]AddDocument. Is that due to the Html.Action used instead of Html.RenderPartial?
If I'm right, the Html.Action is called when you have to do a treatment before generating the partial view, while Html.RenderPartial passes just parameters.
Why is the [HttpPost]AddDocument method called? and who called it?
Secondly, to bypass the problem, I have to Redirect to the Edit page instead of calling the View method. But, I'm losing the data entered. How can I manage this problem?
Thanks.
[...] but also the [HttpPost]AddDocument. Is that due to the Html.Action used instead of Html.RenderPartial?
You can have multiple forms in a view and each form will only call the correspondent controller method.
If I'm right, the Html.Action is called when you have to do a treatment before generating the partial view, while Html.RenderPartial passes just parameters.
Check this post
Basically when calling #html.Partial you are importing the html without a controller call. If that partial view is strongly typed you need to make sure that the current model of the view where you are making the call has the model needed to that partial.
Because you have a partial view that has a diferent model from the calling view model you have two choices:
1- same as your solution, call the action method and create the model to that view
2- the model you pass to the view that calls #Html.renderPartial or #Html.Partial must include the model you need in the partial.
Usage example #Html.Partial("SomePartialview",Model.thePartialViewModel)
Secondly, to bypass the problem, I have to Redirect to the Edit page instead of calling the View method. But, I'm losing the data entered. How can I manage this problem?
Data is not persisted between redirects.
You could do this and/or read this

How to create new record with a combination of values being assigned from user input and being generated in ASP.net MVC 4?

I am trying to add a new record to a database that I've created. The database is called QUESTIONNAIRES and it has the columns: QuestionnaireUID, UserUID, QuestionnaireName, DateCreated, Link, and Image.
I want the user to specify the QuestionnaireName and provide an Image and I want to generate myself the QuestionnaireUID, UserUID, DateCreated, and Link. So far, this is my View() that represents this creation process:
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
// Hide QuestionnaireUID, UserUID, and Link from user. These fields will be generated instead of assigned by user input.
#Html.HiddenFor(model => model.QuestionnaireUID)
#Html.HiddenFor(model => model.UserUID)
#Html.HiddenFor(model => model.Link)
<div class="form-group"> <!-- Questionnaire name. -->
<h2>Name</h2>
<p> Please provide a name for your decision tree.<p>
<div class="col-md-10">
#Html.EditorFor(model => model.QuestionnaireName, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.QuestionnaireName, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group"> <!-- Questionnaire image. -->
<h2>Image</h2>
<p> Please provide a background image for your decision tree.</p>
<!-- ADD FILE IMAGES & ENCODE IN BINARY. -->
<div class="col-md-10">
#Html.EditorFor(model => model.Image, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Image, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group btn_next"> <!-- Save and continue button. -->
<input type="submit" value="Save and Continue" class="btn">
</div>
}
The Questionnaire Controller methods that are being used are displayed below as well:
// GET: Questionnaires/Create
public ActionResult Create()
{
ViewBag.UserUID = new SelectList(db.Users, "UserUID", "FirstName");
return View();
}
// POST: Questionnaires/Create
// To protect from overposting attacks, please enable the specific properties you want to bind to, for
// more details see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "QuestionnaireUID, UserUID, QuestionnaireName, DateCreated, Link, Image")] QUESTIONNAIRE questionnaire)
{
if (ModelState.IsValid)
{
db.QUESTIONNAIRES.Add(questionnaire);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(questionnaire);
}
As you can see, I've hidden away the three attributes that I want to generate in my View(). I now don't know where I generate and assign these values. Do I do this in my View() or in the Controller method? And what does this assignment look like?
I'd generate those values in the Controller on HttpGet, and I'd use a ViewModel.
Echoing mituw16's comment, using a ViewModel is a good way of keeping everything logically consistent and separated. There's some pretty good in-depth discussions and explanations of ViewModels elsewhere that are worth reading.
Your ViewModel could, for instance, look something (roughly) like this:
public class QuestionnaireViewModel
{
public Guid QuestionnaireUID { get; set; }
public Guid UserUID { get; set; }
public string QuestionnaireName { get; set; }
public DateTime DateCreated { get; set; }
public string Link { get; set; }
public Image Image { get; set; }
}
And it could be passed to the View like this:
[HttpGet]
public ActionResult Create()
{
var vm = new QuestionnaireViewModel();
vm.QuestionnaireUID = Guid.NewGuid();
vm.UserUID = Guid.NewGuid();
return View(vm);
}
When the form is posted, MVC can automatically interpret the incoming data as a QuestionnaireViewModel:
[HttpPost]
public ActionResult Create(QuestionnaireViewModel vm)
{
if (ModelState.IsValid)
{
// map the viewmodel properties onto the domain model object here
db.SaveChanges();
return RedirectToAction("Index");
}
return View(questionnaire);
}
A couple more points:
In this example, you'll see that it may not even be necessary to include the UID stuff in the ViewModel, as the ViewModel only cares about data gathered from/presented to the user. Further, unless the #Html.HiddenFors have some sort of functional purpose on the view, you might be able to leave them out and generate them on HttpPost.
If you're looking to "create new record with a combination of values being assigned from user input and being generated in ASP.NET MVC 4" (a.k.a. creating a form in MVC), then the more complex your model/viewmodel gets, the more I'd stay away from using ViewBag for these purposes.

Validation for ViewModels different from the page ViewModel

Assume I have a page (View) that takes a certain ViewModel:
#model IEnumerable<MyProject.ViewModels.MyViewModel>
In this page, I have a form that posts data through another ViewModel (let's call it a PostModel):
#using (Html.BeginForm("Order", "Order", FormMethod.Post, new { #class = "form-horizontal", role = "form" }))
{
#Html.AntiForgeryToken()
<h4>Give your order info</h4>
<hr />
#Html.ValidationSummary()
<div class="form-group">
<label for="Order.Name" class="col-md-2 control-label">Name:</label>
<div class="col-md-10">
#Html.TextBox("Order.Name", null, new { #class = "form-control" })
#Html.ValidationMessage("Order.Name")
</div>
</div>
...
}
This is processed on the controller in an Order HttpPost action method that takes an argument of my PostModel's type.
I can display validation messages in the style I have above. My question is, how (if possible) can I make this strongly typed for my PostModel? Something like:
#Html.TextBox<MyPostModel>(t => t.Order.Name, ...)
#Html.ValidationMessageFor<MyPostModel>(t => t.Order.Name)
Is this at all possible, without changing the ViewModel of the page?
you can simply use a different partial-view for that form and in that partial-view you can specify it to be of any type you want, in this case, as i see in your code example, Order
Lets say you have a model called Order with the following definition
public class Order
{
public string Name { get; set; }
}
and also a partial-view called _MyPostPartialView.cshtml with its definition
#model Order
#using (Html.BeginForm("Order", "Order", FormMethod.Post, new { #class = "form-horizontal", role = "form" }))
{
#Html.AntiForgeryToken()
<h4>Give your order info</h4>
<hr />
#Html.ValidationSummary()
<div class="form-group">
#Html.Label(m => m.Name, "Name:")
<div class="col-md-10">
#Html.TextBox(m => m.Name, null, new { #class = "form-control" })
#Html.ValidationMessage(m => m.Name)
</div>
</div>
...
}
and you're done!
try import javascript bellow in your view
jquery.validate.min.js
jquery.validate.unobtrusive.min.js
In your form view
#Html.LabelFor(model => model.ClientDocument[0].Number)
#Html.TextBoxFor(model => model.ClientDocument[0].Number, new { #class = "form-control" })
In your controller
[HttpPost]
public ActionResult Create(YourViewModel model){
if (ModelState.IsValid)//to complete validate server side
}
In your ViewModel *Try use DataAnnotation
[Display(Name = "Name of Field")]
[Required(ErrorMessage="your message error")]
public string Name { get; set; }
public DocumentViewModel[] ClientDocument { get; set; }
The simple answer is don't use a different view model for GET vs POST. There's no reason to. Anything you POST should be rendered through the view model used for the GET request. If you are for some reason posting something that was not initially on the view model used for the GET request, then frankly, stop it.

MVC EditorFor model binding for multiple edit forms on one page

I have a class called CategoryModel, one of the properties of which is a list of objects of the same type. So CategoryModel.Categories is of type List<CategoryModel>.
On the category index page, I display an editor for each category so that the user can change any of the category names without having to go to a dedicated page to do so. Like so:
<ul id="categories>
#Html.EditorFor(model => model.Categories)
</ul>
And the editor template for CategoryModel looks like this:
<li class="folder">
#using (Html.BeginForm("Edit", "Category", new { id = Model.Key }, FormMethod.Post, new { #class = "ajaxoff"})) {
#Html.ValidationSummary(true)
#Html.HiddenFor(model => model.Key)
#Html.HiddenFor(model => model.ParentKey)
#Html.HiddenFor(model => model.Sequence)
#Html.HiddenFor(model => model.IncludeDeleted)
#Html.TextBoxFor(model => model.Name, null, new { #class = "catName" })
#Html.ValidationMessageFor(model => model.Name)
<input type="submit" value="Save" class="icon save" />
}
</li>
The problem I have is that submitting the form does not bind correctly to the Edit action of the CategoryController:
[HttpPost]
public ActionResult Edit(CategoryModel category)
{
// At this point all properties in category are null
}
If I check the names on the hidden fields and textboxes, they are labelled based on their position in the current category (e.g. Categories[0].Name). If I create a dedicated edit view, however, they are simply named according to the field name (e.g. Name).
I have tried changing the controller to accept a list of Categories:
[HttpPost]
public ActionResult Edit(List<CategoryModel> categories)
{
var category = categories.First();
}
This works if I submit the very first category, but none of the others (in those cases Categories is null).
I have also tried changing how I display my EditorFor, by doing this:
<ul id="categories>
#foreach (var cat in Model.Categories)
{
#Html.EditorFor(model => cat);
}
</ul>
Which changes the field names to be the same for each category (e.g. all category names are called cat.Name), which I believe is a step in the right direction.
So how do I bind correctly to my controller? I realise that I could submit the whole parent category and then save each subcategory, but this seems like a very inefficient way to submit a single change.
I discovered how to do this. There is an overload for Html.EditorFor that allows you to specify the htmlFieldName property (the third parameter in the example below):
#foreach (var cat in Model.Categories)
{
#Html.EditorFor(model => cat, null, "");
}
This renders all of the field names without any prefix and allows me to submit any single category successfully.
Your Edit action accept CategoryModel category so you need to reset model prefix for right binding. For this you need to create your own extension method for HtmlHelper like this:
public class BeginHtmlScope : IDisposable
{
private readonly TemplateInfo templateInfo;
private readonly string previousHtmlFieldPrefix;
public BeginHtmlScope(TemplateInfo templateInfo, string htmlFieldPrefix)
{
this.templateInfo = templateInfo;
previousHtmlFieldPrefix = templateInfo.HtmlFieldPrefix;
templateInfo.HtmlFieldPrefix = htmlFieldPrefix;
}
public void Dispose()
{
templateInfo.HtmlFieldPrefix = previousHtmlFieldPrefix;
}
}
public static class MyHtmlExtensions
{
public static IDisposable BeginHtmlScope(this HtmlHelper html, string htmlFieldPrefix)
{
return new BeginHtmlScope(html.ViewData.TemplateInfo, htmlFieldPrefix);
}
}
And then use it inside your editor template:
#using (Html.BeginHtmlScope(""))
{
<li class="folder">
#using (Html.BeginForm("Edit", "Category", new { id = Model.Key }, FormMethod.Post, new { #class = "ajaxoff"})) {
#Html.ValidationSummary(true)
#Html.HiddenFor(model => model.Key)
#Html.HiddenFor(model => model.ParentKey)
#Html.HiddenFor(model => model.Sequence)
#Html.HiddenFor(model => model.IncludeDeleted)
#Html.TextBoxFor(model => model.Name, null, new { #class = "catName" })
#Html.ValidationMessageFor(model => model.Name)
<input type="submit" value="Save" class="icon save" />
}
</li>
}

Categories