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.
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.
View generates 3 file input fields. Here is the screenshot:
However, when I add EditorFor template for HttpPostedFileBase, then it works perfectly. I want to know why is this happening.
This is my model:
public class UploadFileViewModel
{
[Required]
[Display(Name ="Select Excel File")]
public HttpPostedFileBase ExcelFile { get; set; }
}
The controller:
public class HomeController : Controller
{
public ActionResult UploadFileData()
{
return View();
}
}
The view:
#model DemoProject.Models.UploadFileViewModel
#{
ViewBag.Title = "Upload File Data";
}
<h2>Upload File Data</h2>
<p class="alert-success">#ViewBag.Success</p>
#using (Html.BeginForm(null, null, FormMethod.Post, new { enctype = "multipart/form-data" }))
{
#Html.AntiForgeryToken();
#Html.ValidationSummary("", new { #class = "text-danger" });
<div class="form-horizontal">
<div class="form-group">
#Html.LabelFor(model=>model.ExcelFile, htmlAttributes: new {#class="control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model=>model.ExcelFile, new { htmlAttributes = new { type = "file" } })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Upload" class="btn btn-default" />
</div>
</div>
</div>
}
Using EditorFor() on a complex object generates the default template (which includes a label, form control and validation message placeholder) for each property of the object. HttpPostedFileBase contains properties for ContentType, ContentLength and FileName so 3 inputs are generated. Because you have included type="file" in the additionalViewData, the inputs generated are type="file".
You can simply use
#Html.TextBoxFor(m => m.ExcelFile, new { type = "file" })
or you could create a custom EditorTemplate for typeof HttpPostedFileBase so that EditorFor() uses that template, rather than the default template. But that template would need to include #Html.TextBoxFor(m => m, new { type = "file" }) so there is probably not much point, other than to include the LabelFor() and ValidationMessageFor() and enclosing <div> elements so that all that's needed in the view is #Html.EditorFor(m => m.ExcelFile) to generate all the html. This can simplify the main view, but the disadvantage is that you cannot for example, have col-md-10 in one view and col-md-8 in another.
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
I am trying to build a form using Razor template engine with ASP.NET MVC 5.
I have the following encapsulated call which I pass to the main view
public class ManagePresenter
{
public ActionFormViewModel ActionForm { get; set; }
public List<SelectListItem> Users { get; set; }
}
Then I have this view
#using(Html.BeginForm("Index", "Manage", FormMethod.Post, new { #class = "form-inline", Id = "ManageEmployeeForm" }))
{
Html.AntiForgeryToken();
Html.HiddenFor(m => m.ActionForm.From);
Html.HiddenFor(m => m.ActionForm.To);
<div class="form-group">
#{
Html.LabelFor(m => m.ActionForm.UserId, new { #class = "sr-only" });
Html.DropDownListFor(x => x.ActionForm.UserId, Model.Users, new { #Class = "form-control" });
Html.ValidationMessageFor(m => m.ActionForm.UserId);
}
</div>
<div class="checkbox">
<label>
<label>
#Html.CheckBoxFor(m => m.ActionForm.ShowAlternateEntries, new { Title = "Alternate entries are entries from the telephony system that will help you compare against manual entries" })
</label>
</label>
</div>
<button type="submit" class="btn btn-primary" id="ManageEmployeeGetTime">View Records</button>
}
but every time I run my application I get the following error
CS1928: 'System.Web.Mvc.HtmlHelper'
does not contain a definition for 'DropDownListFor' and the best
extension method overload
'System.Web.Mvc.Html.SelectExtensions.DropDownListFor<TModel,TProperty>(System.Web.Mvc.HtmlHelper<TModel>,
System.Linq.Expressions.Expression>,
System.Collections.Generic.IEnumerable<System.Web.Mvc.SelectListItem>,
object)' has some invalid arguments
What could be causing this error? Model.Users is a List<SelectListItem>
What am I missing here?
The error means (in your case) that one of the arguments of your DropDownListFor() method is invalid, and logically this can only be the 2nd parameter (for IEnumerable<SelectListItem>).
You have claimed that the model contains a property public List<SelectListItem> Users { get; set; } so either you have created you own class named SelectListItem or you have included a reference to System.Web.WebPages.Html and are using the SelectListItem class of that assembly rather than the SelectListItem class of System.Web.Mvc.
Generally, you should not need System.Web.WebPages.Html, but if for some reason you do, then use the fully qualified name of class in you model
public List<System.Web.Mv.SelectListItem> Users { get; set; }
As a side note, your view will not generate any form controls (except the ckeckbox and submit button). Your have not prefixed the HtmlHelper methods with # which means the method will be executed, but it results will not be output in the view. You need to change the view to
#using(Html.BeginForm("Index", "Manage", FormMethod.Post, new { #class = "form-inline", Id = "ManageEmployeeForm" }))
{
#Html.AntiForgeryToken();
#Html.HiddenFor(m => m.ActionForm.From);
#Html.HiddenFor(m => m.ActionForm.To);
<div class="form-group">
#Html.LabelFor(m => m.ActionForm.UserId, new { #class = "sr-only" });
#Html.DropDownListFor(x => x.ActionForm.UserId, Model.Users, new { #class = "form-control" });
#Html.ValidationMessageFor(m => m.ActionForm.UserId);
</div>
....
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.