MVC Partial View Validation - asp.net

I am trying to understand partial views in MVC...
What I am trying to accomplish is to have a master View which renders, say, two partial views.
Each partial view contains a different ViewModel (with DataAnnotations). When I submit the form of one of those partial views, in case there is a server-side validation error, I would like to have the master View show up again with the validation messages on that partial.
Any tips in the right way would be deeply appreciated.

Here you go with the sample solution -
Let create a complex model in following way -
public class Person
{
public Contact contact { get; set; }
public Vehicle vehicle { get; set; }
}
public class Contact
{
[Required]
public string Email { get; set; }
}
public class Vehicle
{
[Required]
public string Name { get; set; }
}
Then lets create a Main controller with an Index action in following way, this action is going to create a simple dummy model and bind it to the Index view -
public class MainController : Controller
{
public ActionResult Index()
{
Person p = new Person();
p.contact = new Contact();
p.vehicle = new Vehicle();
return View(p);
}
}
And Index view is going to be -
#model MVC.Controllers.Person
#{
ViewBag.Title = "Index";
}
<h2>Index</h2>
#using (Html.BeginForm("Submit","Main",FormMethod.Post))
{
#Html.EditorFor(x => x.contact, "~/Views/Main/EditorTemplates/Contact.cshtml")
#Html.EditorFor(x => x.vehicle, "~/Views/Main/EditorTemplates/Vehicle.cshtml")
<input type="submit" value="click"/>
}
Here in the above view, instead of using Partial Views, I used Editor Views. Reason is that Partial views gives very hard experience in Model binding the Complex models.
So I created EditorTemplated folder in Main View folder and placed following files in there.
Contact.cshtml -
#model MVC.Controllers.Contact
#Html.LabelFor(model => model.Email, new { #class = "control-label col-md-2" })
#Html.EditorFor(model => model.Email)
#Html.ValidationMessageFor(model => model.Email)
Vehicle.cshtml -
#model MVC.Controllers.Vehicle
#Html.LabelFor(model => model.Name, new { #class = "control-label col-md-2" })
#Html.EditorFor(model => model.Name)
#Html.ValidationMessageFor(model => model.Name)
With the above setup, we can go and run the application and following screen should show up -
And this form is going to POSTed to Submit Action of Main controller, so this is going to be my submit action -
public ActionResult Submit(Person p)
{
if (!ModelState.IsValid)
return View("Index", p);
else
{
// do something
return View();
}
}
When we click button without entering any value, then validation will trigger and we should see error messages as below -
And in normal valid situations, you can submit the form and then run your business logic.

Related

How to incorporate another controller's view and behavior into "this" controller's view?

I have a jQueryUI tabbed html page, and in its content area for one of the tabs, I have put as follows:
<div id="tabs-1ua">
#RenderPage("~/Views/Admin/Create.cshtml")
</div>
The Create.cshtml page does correctly appear within my tab, however when I create the user (this view is a basic user creation page) and click the button, nothing happens. No user is created and no error is presented. The "this" html with the tabs is in a different controller which does not have any model associations. The user creation is inside the AdminController, pertinent methods shown below:
public ActionResult Create()
{
return View();
}
[HttpPost]
public async Task<ActionResult> Create(CreateModel model)
{
if (ModelState.IsValid)
{
AppUser user = new AppUser { UserName = model.Name, Email = model.Email};
IdentityResult result = await UserManager.CreateAsync(user,
model.Password);
if (result.Succeeded)
{
return RedirectToAction("Index");
}
else
{
AddErrorsFromResult(result);
}
}
return View(model);
}
I put a breakpoint at the beginning of the Post method, but it was never hit when I accessed the create page from within my other page.
When I access this page directly and create a user, I get the expected behavior for new creation and validation. The model is as follows:
public class CreateModel
{
[Required]
public string Name { get; set; }
[Required]
public string Email { get; set; }
[Required]
public string Password { get; set; }
}
And the Create.cshtml view is as follows:
#model IdentityDevelopment.Models.CreateModel
#{ ViewBag.Title = "Create User";}
<h2>Create User</h2>
#Html.ValidationSummary(false)
#using (Html.BeginForm())
{
<div class="form-group">
<label>Name</label>
#Html.TextBoxFor(x => x.Name, new { #class = "form-control" })
</div>
<div class="form-group">
<label>Email</label>
#Html.TextBoxFor(x => x.Email, new { #class = "form-control" })
</div>
<div class="form-group">
<label>Password</label>
#Html.PasswordFor(x => x.Password, new { #class = "form-control" })
</div>
<button type="submit" class="btn btn-primary">Create</button>
#Html.ActionLink("Cancel", "Index", null, new { #class = "btn btn-default" })
}
My questions are, is it possible to do what I am trying to do? If so what changes do I need to make in order to reuse the existing available code?
Thank you.
You may explcitly specify which action method the form should post to when submit button is clicked.
You can use this overload of Html.BeginForm method to do so.
public static MvcForm BeginForm(
this HtmlHelper htmlHelper,
string actionName,
string controllerName
)
So update your Create view.
#model IdentityDevelopment.Models.CreateModel
#using (Html.BeginForm("Create","Admin"))
{
#Html.TextBoxFor(x => x.Name, new { #class = "form-control" })
<button type="submit" class="btn btn-primary">Create</button>
}
Now nomatter where you include this view, it will always post to Admin/Create
You should move your create form into a partial view which you can then render with RenderPartial. Then in your parent html page form do an ajax post to the partial views controller that defines the create method. Then you can use your partial anywhere you like with the logic centralized into the partial views controller.

asp.net mvc how to work with model (update in post controller)

I'm new to mvc and I'm struggling with this model stuff.
My understanding is that I can only use one model per action.
public class TestModel
{
public string foo1 { get; set; }
public string foo2 { get; set; }
public string foo3 { get; set; }
}
I want to partially load my model in the normal action:
public ActionResult Index()
{
TestModel model = new TestModel();
model.foo1 = "foo1";
return View(model);
}
Then the user should add data to the model from the view.
#model SAS_MVC.Models.Test.TestModel
#using (Html.BeginForm())
{
#Html.EditorFor(model => model.foo1, new { htmlAttributes = new { #class = "form-control" } })
#Html.EditorFor(model => model.foo2, new { htmlAttributes = new { #class = "form-control" } })
#Html.EditorFor(model => model.foo3, new { htmlAttributes = new { #class = "form-control" } })
<input type="submit" value="submit" />
}
According to the user's data I have to add further data in the post controller:
[HttpPost]
public ActionResult Index(MyModel model, FormCollection form)
{
// get some data from DB
model.foo3 = 123;
return View(model);
}
How can I save this model permanently? I have problems with e.g. foo3 is empty in the view. I want to pass the model between the post-controller and view several times without losing data.
I did try with TempData and ViewBag but for me this is very uncomfortable to work with... no intellisense.
So how can I do it right? Thanks for help!
Update with EF6:
public class MyEntity
{
public int Id { get; set; }
public string Name { get; set; }
}
public class TestController : Controller
{
DB03SASModel dbModel = new DB03SASModel();
// GET: Test
public ActionResult Index()
{
MyEntity model = new MyEntity();
model.Name = "AAAA";
dbModel.MyEntities.Add(model);
dbModel.SaveChanges();
return View(model);
}
[HttpPost]
public ActionResult Index(MyEntity model)
{
model.Name = "BBBB";
dbModel.SaveChanges();
//UpdateModel(model);
return View(model);
}
}
View
#model SAS_MVC.MyEntity
#using (Html.BeginForm())
{
#Html.EditorFor(model => model.Name, new { htmlAttributes = new { #class = "form-control" } })
#Html.DisplayFor(model => model.Id, new { htmlAttributes = new { #class = "form-control" } })
#Html.HiddenFor(model => model.Id, new { htmlAttributes = new { #class = "form-control" } })
<input type="submit" value="submit" />
}
Now I save the model using EF Code First and I checked it in the DB --> every thing is well saved.
But: Again the view take the wrong value and I still struggle.
I found out the the #Html.HiddenFor provide me the current ID of the entity in the post controler. Than I changed the value to "BBBB" and than I pass the exact same entity back to the view but the view never did an update!
I don't get it sorry. When I try with UpdateModel(model); "AAAA" is again my value! Where did this value come from? In the DB there is no such value at this time!! What did I wrong??
Saving the model should happen in the post action, and to save the model permanently so you should save it to the database, that requires you to map your model to a database table, and to do that you should create a database and create a table that will hold your model data then use Entity framework or any other ORM to map your model with the database table.
Update 1:
You are saving the model to the database in two places, first in the get action and then in the post action, so every time you save "BBB" in the post action it will get overridden in the get action to "AAAA", so here is how your code should be :
public class TestController : Controller
{
TestEntities dbModel = new TestEntities();
public ActionResult Index(int? id)
{
MyEntity model = new MyEntity();
if (id.HasValue)
model = dbModel.MyEntity.First(m => m.Id == (id ?? 0));
return View(model);
}
[HttpPost]
public ActionResult Index(MyEntity model)
{
dbModel.MyEntity.Add(model);
dbModel.SaveChanges();
return RedirectToAction("Index", new { id = model.Id });
}
}
as you see saving the data to the database only happen in the post action, the get action is to get the data back from the database.

#Html.ValidationMessageFor displays error message before user input

I have the following code:
In the model:
public class Student {
[Required(ErrorMessage = "name is a required field")]
public string Name { get; set; }
[Required(ErrorMessage = "school is a required field")]
public string School { get; set; }
}
In the controller:
public ActionResult StudentView()
{
return View();
}
[HttpPost]
public ActionResult StudentViewPost(Student model)
{
if(ModelState.IsValid)
{
....
....
}
}
And in my view, i have:
#using (Html.BeginForm()){
#Html.TextBoxFor(model => model.Name)
#Html.ValidationMessageFor(model => model.Name)
#Html.TextBoxFor(model => model.School)
#Html.ValidationMessageFor(model => model.School)
}
But when i go to the view page, the validation error messages are already displayed (on load), even before i get a chance to enter any input. Is there any reason why this could be happening? Could .NET be seeing this GET page as a POST page on load somehow and therefore display the error message? I'm not sure why this is happening and any thoughts/ideas would be great.
I have seen some issues in your Html.Beginform()
Normally you should write this begin blog as follow,
Example if your controller name is Student then,
#using (Html.BeginForm("StudentViewPost", "Student", FormMethod.Post))
{
#Html.TextBoxFor(model => model.Name)
#Html.ValidationMessageFor(model => model.Name)
#Html.TextBoxFor(model => model.School)
#Html.ValidationMessageFor(model => model.School)
}
You have not specifed the form method which will invoke the validations on Action
use following syntax
#using (Html.BeginForm("ActionName", "Controller", FormMethod.Post))
{
}

Asp.net MVC 3 "parameter conversion from type 'System.String' to type failed" when using SelectList Dropdown box

I'm stuck and after looking this up for hours, I think I need more eyeballs.
The situation is the following:
It's an Asp.Net MVC3 with Entity Framework 4 project. And I have two classes. One ConfigurationFile and another one Action. There is a one-to-many relationship between the two. Here is a simplified view on the code:
public class ConfigurationFile
{
[Key, Required]
[Column(TypeName = "uniqueidentifier")]
public Guid Id { get; set; }
[Required]
public string Name { get; set; }
[Column(TypeName = "uniqueidentifier")]
[Required]
public Guid ActionId { get; set; }
public virtual Models.Action Action { get; set; }
}
public class Action
{
[Key, Required]
[Column(TypeName = "uniqueidentifier")]
public Guid Id { get; set; }
[Required]
public string ActionValue { get; set; }
}
Then I want to create a new ConfigurationFile, and are my two controller methods (and at this point, this is 95% Visual Studio 10 generated code):
// db is my context class.
//
// GET: /Configuration/Create
public ActionResult Create()
{
ViewBag.ActionId = new SelectList(db.Actions, "Id", "ActionValue");
return View();
}
//
// POST: /Configuration/Create
[HttpPost]
public ActionResult Create(Models.ConfigurationFile configurationfile)
{
if (ModelState.IsValid)
{
configurationfile.Id = Guid.NewGuid();
db.ConfigurationFiles.Add(configurationfile);
db.SaveChanges();
return RedirectToAction("Index");
}
ViewBag.ActionId = new SelectList(db.Actions, "Id", "ActionValue", configurationfile.ActionId);
return View(configurationfile);
}
And here is a snippet of my Create view:
#model MyProject.Areas.ConfigurationFile.Models.ConfigurationFile
#using (Html.BeginForm()) {
#Html.ValidationSummary(true)
<fieldset>
<legend>Configuration File</legend>
<div class="editor-label">
#Html.LabelFor(model => model.Name)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Name)
#Html.ValidationMessageFor(model => model.Name)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.ActionId, "Action")
</div>
<div class="editor-field">
#Html.DropDownList("ActionId", String.Empty)
#Html.ValidationMessageFor(model => model.ActionId)
</div>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}
When I open the Create page, I can clearly see that my dropdown for the Action class is fine (correct value -- the Action.Id -- and text -- Action.ActionValue -- ) but when I submit the form, I have the following error: "The parameter conversion from type 'System.String' to type 'MyProject.Models.Action' failed because no type converter can convert between these types."
Help please !!
Right now MVC has no way of connecting your dropdownlist from your view to the ActionId of your ConfigurationFile object.
I would try replacing this line:
#Html.DropDownList("ActionId", String.Empty)
for this
#Html.DropDownListFor(model => model.ActionId, ViewBag.ActionId)
Other than that, I can't think of what else you might have done wrong.
I hope that helps!
This is how I did to circumvent the problem. I just changed my controller this way:
Models.Action act = db.Actions.Find(configurationfile.ActionId);
ModelState.Clear();
configurationfile.Action = act;
TryValidateModel(configurationfile);
And after that, the validation was Ok. A bit hacky (and another possible hit on the DB), but at least, I can keep going.

populate view based on multi-level model

Suppose, I have models:
public class Person
{
public sting Name {get;set;}
public List<Book> Books {get;set;}
}
public class Book
{
public sting NameBook {get;set;}
}
How represent view for Edit method based on Person model (MVC 3)?
You may try something along the lines of:
#model Person
#using (Html.BeginForm())
{
<div>
#Html.LabelFor(x => x.Name)
#Html.EditorFor(x => x.Name)
</div>
#Html.EditorFor(x => x.Book)
<button type="submit">Edit</button>
}
and then you will define an editor template for the Book type (~/Views/Shared/EditorTemplates/Book.cshtml) which will be rendered for each element of the Book property collection (which by the way you would have named Books in order to follow standard conventions) on your view model:
#model Book
<div>
#Html.LabelFor(x => x.NameBook)
#Html.EditorFor(x => x.NameBook)
</div>
As far as your controller actions are concerned, it's pretty standard stuff:
public ActionResult Edit(int id)
{
var person = _personRepository.Get(id);
return View(model);
}
[HttpPost]
public ActionResult Edit(Person person)
{
if (!ModelState.IsValid)
{
return View(person);
}
_personRepository.Update(person);
return RedirectToAction("Success");
}

Resources