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
Related
I have a view that was scaffolded from a model. I need to retrieve the values from the "editfor" helper tags, do a bunch of calculations, then pass back the results (multiple)back to the view. I created a small example to clarify.
public class OpticalcTestViewModel
{
public double OD_Sphere { get; set; }
public double OD_Cylinder { get; set; }
public int Axis { get; set; }
}
Which creates this scaffold:
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>OpticalcTestViewModel</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(model => model.OD_Sphere, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.OD_Sphere, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.OD_Sphere, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.OD_Cylinder, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.OD_Cylinder, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.OD_Cylinder, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.Axis, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Axis, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Axis, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Create" class="btn btn-default" />
</div>
</div>
</div>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
#section Scripts {
#Scripts.Render("~/bundles/jqueryval")
}
This is my controller:
public class OpticalcTestController : Controller
{
// GET: OpticalcTest
public ActionResult Index()
{
return View();
}
}
How do I get the values out of the "editfor" boxes, perform calculations on them, then pass the results of those calculations(multiple results, not just one)back to some labels in the view?
This is such a simple thing in winforms, which is what I usually work with, but I'm at my wits end trying to find an answer to this. It seems like the other 9,000 results I get from searches are always about writing the data (as a whole model) to a database. This will have no database. It's just a form that takes numeric values, does calculations and spits out results.
To be more specific, how would I pull those values into the controller, Add sphere to cylinder, then add cylinder to axis and pass back both results separately to labels (or some other way to view them)?
Thanks,
R
First thing you need to do is add an action to your controller that accepts a parameter of type OpticalcTestViewModel and is tagged with the [HttpPost] attribute:
[HttpPost]
public ActionResult Index(OpticalcTestViewModel model)
{
//perform calculations
return View(model);
}
As you notice above, after you have performed calculations you will need to modify the model variable to add in your new calculations, then you just send it back to the view (return View(model)).
Your form in the view is performing a POST by default. Since you have no actions that are capable of handling a POST request, you will never be able to service those calls. The above code should fix all that.
In either case, I would highly recommend taking some more tutorials on ASP.NET MVC. Microsoft has a couple of decent tutorials, but there are also a lot of free resources online.
You'll need an HttpPost method to accept the model from the view and access the properties bound to the EditorFor helper elements as maccettura wrote.
Then you can perform calculations like so inside the method:
double sphereRadius = model.OD_Sphere / 2; // demo calc
Are the calculation results going to be on the same view? Same model?
Assuming yes, I'd recommend new model properties for the calculated values, then bind those to your view. You can use Razor to show/hide the inputs and calculated values as required.
Example assuming you use the calculation above w/ a new model property:
public class OpticalcTestViewModel
{
public double OD_Sphere { get; set; }
public double OD_Cylinder { get; set; }
public int Axis { get; set; }
public double sphereRadius { get; set; } // new calculated property
}
[HttpPost]
public ActionResult Index(OpticalcTestViewModel model)
{
double sphereRadius = model.OD_Sphere / 2; // demo calc
model.sphereRadius = sphereRadius;
return View(model);
}
Note that if you attempt to edit a model property which has already been bound to the view, it will retain its old value. That's because the value is actually retained in the ModelState, and the default model binder will check there first and use values if they exist. To override this you'll have to clear the ModelState property, but that gets a little messy.
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
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.
I am working in an ASP.NET Web Application, using MVC template.
I am trying send or pass an ObjectViewModel from a View.cshtml to a Controller, but in the Controller the ObjectViewModel arrived as null.
The idea is the follow:
I show the view IndexUsersInActiveDirectory.cshtml with a collection of object of type RegisterViewModel. Into this view there is an #Html.ActionLink for send a element of the collection to the Register controller:
IndexUsersInActiveDirectory.csthml
#model IEnumerable<SecureEscuelaWithIdentity.Models.RegisterViewModel>
#{
ViewBag.Title = "IndexUsersInActiveDirectory";
}
<h2>IndexUsersInActiveDirectory</h2>
<p>
#Html.ActionLink("Create New", "Create")
</p>
<table class="table">
<tr>
<th>
#Html.DisplayNameFor(model => model.UserName)
</th>
<th>
#Html.DisplayNameFor(model => model.Email)
</th>
<th></th>
</tr>
#foreach (var item in Model) {
<tr>
<td>
#Html.DisplayFor(modelItem => item.UserName)
</td>
<td>
#Html.DisplayFor(modelItem => item.Email)
</td>
<td>
#Html.ActionLink("Select", "Register", new { item })
</td>
</tr>
}
</table>
The controller Register is the next:
//
// GET: /Account/Register
[Authorize(Roles = "Admin")]
public ActionResult Register(RegisterViewModel model)
{
return View(model);
}
The class RegisterViewModel is:
public class RegisterViewModel
{
[Required]
[Display(Name = "User name")]
public string UserName { get; set; }
[Required]
[Display(Name = "Email")]
public string Email { get; set; }
}
The view Register.cshtml
#model SecureEscuelaWithIdentity.Models.RegisterViewModel
#{
ViewBag.Title = "Register";
}
<h2>#ViewBag.Title.</h2>
#using (Html.BeginForm("RegisterInApp", "Account", FormMethod.Post, new { #class = "form-horizontal", role = "form" }))
{
#Html.AntiForgeryToken()
<h4>Create a new account.</h4>
<hr />
#Html.ValidationSummary()
<li>#Html.ActionLink("ActiveDirectory", "IndexUsersInActiveDirectory", "Account")</li>
<div class="form-group">
#Html.LabelFor(m => m.UserName, new { #class = "col-md-2 control-label" })
<div class="col-md-10">
#Html.TextBoxFor(m => m.UserName, new { #class = "form-control" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(m => m.Email, new { #class = "col-md-2 control-label" })
<div class="col-md-10">
#Html.TextBoxFor(m => m.Email, new { #class = "form-control" })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" class="btn btn-default" value="Register" />
</div>
</div>
}
#section Scripts {
#Scripts.Render("~/bundles/jqueryval")
}
How to send the entire collection element (as model) to the controller?
There are 2 problems
1) You are missing a null on the 4th parameter of ActionLink
#Html.ActionLink("Select", "Register", new { item }, null)
Without that 4th parameter it assumes the 3rd parameter is the HTML attributes and not the querystring parameters.
I find this little 4th parameter "quirk" to be one of the most annoying overrides in MVC.
2) Your item needs to be simple data values that can be passed via the querystring
As your item is not a simple value type, you need to pass back its unique values. e.g.:
#Html.ActionLink("Select", "Register", new { name = item.Name, email = item.Email }, null)
Action links do not do a postback, they just create anchor links in the page, so any values have to be passed in the query string.
Change the receiving action to match:
[Authorize(Roles = "Admin")]
public ActionResult Register(string name, string email)
{
RegisterViewModel model = new RegisterViewModel()
{
Name = name,
Email = email
};
return View(model);
}
But I leave those details up to you :)
The solution proposed by TrueBlueAussie is suitable for scenarios where there is no problem to show querystring in the address bar of the browser.
If so, the #Html.ActionLink could be like this:
#Html.ActionLink("Select", "Register", new {item}, null)
since the controller will take 'item' as a RegisterViewModel object (although in reality be a querystring). Note: not forgetting the 4th parameter that contributed TrueBlueAussie "null".
The Register controller looks like this:
[Authorize (Roles = "Admin")]
public ActionResult Register (RegisterViewModel model)
{
return View (model);
}
It works!
As mention above, this displays the querystring in the Register view, which is not correct in my implementation. I do not want to show this querystring in the address bar.
To achieve this, I added an intermediate controller responsible of receiving the 'item' (from IndexUsersInActiveDirectory.csthml) as a RegisterViewModel object (actually it is a querystring) and add this Object to TempData dictionary with a key called "model".
Here is the controller RegisterTemp:
[Authorize (Roles = "Admin")]
public ActionResult RegisterTemp (RegisterViewModel model)
{
TempData ["model"] = model;
return RedirectToAction ("Register");
}
In the view 'IndexUsersInActiveDirectory.csthml' the #ActionLink point to controller RegisterTemp rather than the controller Register, being as follows:
# Html.ActionLink ("Select", "RegisterTemp", new {item}, null)
Importantly RegisterTemp returns a "RedirectToAction" to Register view.
And finally, when the RegisterTemp controller returns RedirectToAction to Register controller, it takes the element with the "model" key from the TempData dictionary in order to return it to the view. Thus, the Register controller is:
//
// GET: / Account / Register
[Authorize (Roles = "Admin")]
public ActionResult Register ()
{
return View (TempData ["model"]);
}
I mentioned that, the solution proposed by #TrueBlueAussie is fully functional for the scenario that is not important to show the querystring.
The solution that I have just detailed now, is for avoid showing that querystring.
If there is a more correct solution to solve my scenario, is welcome.
You must iterate the elements with a "for" loop, as in for
(int i; i <= elements.Length -1; i++)
elements[i].etc;
So the collection don't get lost on post.
Read this for more detail:
http://haacked.com/archive/2008/10/23/model-binding-to-a-list.aspx/
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.