I'm new to MVC 3 and I have a question regarding the correct approach.
Imagine I have a model:
public class MyCustomModel
{
[Required]
public string UserName { get; set; }
[Required]
public DateTime? Birthdate { get; set; }
[Required]
public string City {get;set;} //To partial view
[Required]
public string Street {get;set;} //To partial view
}
And here I have a view
#Html.TextBoxFor(m => m.UserName)
#Html.TextBoxFor(m => m.BirthDate)
#Html.Action("LocationGroup", "Home") //In this should the city and street be rendered
My Partial View will have somethign like that:
#Html.TextBoxFor(m => m.City)
#Html.TextBoxFor(m => m.Street)
And this the action in the controller:
[ChildActionOnly]
public ActionResult LocationGroup()
{
MyCustomModel model = new MyCustomModel (); //Should i really instantiate a new instace of the model??? and pass it to the partial view
return PartialView("_TempView", model);
}
Basically my general view will have all the field with texboxex, but now in my partial view i also would like to have few of those propeties from my model be rendered correctly and after submiting the form should be available in the same model as all other properties.
So my question, in the action which send the partial view back, should i really instantiate a new instace of the model? But then the data will be split between 2 instances of the model no?
How to arrange that, how can i then ass the data to the general views model from partial view?
i didnt get your question but you can annotate the ActionResults with HttpGet and HttpPost having same names (but different signatures, because they are methods after all) like
[HttpGet]
[ChildActionOnly]
public ActionResult LocationGroup()
{
Model model = new Model();
return PartialView("_TempView", model);
}
in the view you must be doing something like
#model YOURMODELNAME
#using(Html.BeginForm("LocationGroup","Controller",FormMethod.POST)){
#Html.TextBoxFor(x=>x.UserName)
#Html.TextBoxFor(x=>x.Birthdate )
<input type="submit" value="submit" />
}
now define a post type ActionResult
[HttpPost]
[ChildActionOnly]
public ActionResult LocationGroup(YOUR_MODEL_TYPE model)
{
if(ModelState.IsValid){
//do something
}
}
the default model binder will look into the HttpContext for the match between the posted value names and the properties of your model and bind the value automatically
Related
I'm implementing asp.net core 3.1. I have three radio buttons in my razor view and with the following code, I want to send the selected radio button value to Index action in controller in order to show its related data. My problem is after choosing a radio button and after that select the button. it send null value to the Index method.
Here my radio button code in razor
#model CSD.ChartObjects
<form method="post">
#foreach (var year in Model.Years)
{
<input type="radio" asp-for="Year" value="#year" />#year<br />
}
<input type="submit" asp-action="Index" />
</form>
Here is my model object that is read in razor
public class ChartObjects
{
public List<ChartModel> Percent { get; set; }
public List<ChartModel> Time { get; set; }
public List<ChartModel> Avg { get; set; }
public List<ChartModel> Total { get; set; }
[BindProperty]
public string Year { get; set; }
public string[] Years = new[] { "1398", "1399", "1400" };
}
And here is the body of my HomeController:
[HttpGet]
public IActionResult Index()
{
return (BuildIndexModel("1399"));
}
[HttpPost]
public IActionResult Index([FromForm] string currentYear)
{
return (BuildIndexModel(currentYear));
}
public IActionResult BuildIndexModel([FromForm] string currentYear)
{
...
}
The problem you're having is because of this:
[HttpPost]
public IActionResult Index([FromForm] string currentYear)
On your form, the value you want to bind is called Year, but in your POST action, you're specifying a parameter called currentYear. MVC doesn't know that you want to bind the value of Year to currentYear. Model binding is all based on the convention of property names on your model being the same as the data being sent from a form.
So all you need to do is change your POST method to this:
[HttpPost]
public IActionResult Index(string year)
Notice how I've also removed the [FromForm] attribute. You don't need to specify that here in order for it to bind properly. I'm guessing you added it while trying to figure out why it wasn't working. For the same reason, you also don't need the [BindProperty] attribute on your viewmodel - it's the name of the property that is important.
I'm developing a small app in order to better understand how MVC3 anda Razor works. I'm using MVC3, all code was generated automatically (dbContext via T4, Controller via Add Controller, Databese from EDMX model...).
In my model I have this simple model:
http://i.stack.imgur.com/nyqu4.png
public partial class Application
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ApplicationType ApplicationType { get; set; }
}
public partial class ApplicationType
{
public int Id { get; set; }
public string Type { get; set; }
}
As you can see, ApplicationType is basically an enum (shame that EF 4 has no support for enums). So, in my ApplicationController I have this:
public ActionResult Create()
{
ViewBag.AppTypes = new SelectList(db.ApplicationTypes.OrderBy(c => c.Type), "Id", "Type");
return View();
}
[HttpPost]
public ActionResult Create(Application application)
{
if (ModelState.IsValid)
{
db.Applications.Add(application);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(application);
}
And in my view:
#Html.DropDownListFor(model => model.ApplicationType.Id, (SelectList)ViewBag.AppTypes, "Choose...")
Now I'm facing two problems:
1) ApplicationType not being populated:
As #Html.DropDownListFor renders only a simple select, it fills the ID, but does not fill Type property as you can see below (sorry, I can't post images as I'm new here):
http://i.stack.imgur.com/96IR1.png
In the picture you can see that the ID is ok, but Type is empty.
What I'm doing wrong?
2) Duplicated Data
The second problem is that if I fill the Type property manually during debug (simulating a correct workflow scenario), ApplicationType is being duplicated in the database, instead of only referring to an old registry.
So, how can I make #Html.DropDownListFor refer to a previous existing item instead of creating a new one?
Thanks for your help!
I believe the mistake you're making is using your domain models in the view and assuming that on post the entire model should be completely binded and ready to store in the database. While it is possible to use domain models in the view, it's better practice to create separate View Models.
For example :
public class ApplicationViewModel
{
public int Id { get; set; }
public string Name { get; set; }
public SelectList ApplicationTypeList { get; set; }
public string ApplicationTypeId { get; set; }
}
In your view:
#Html.DropDownListFor(model => model.ApplicationTypeId, Model.ApplicationTypeList , "Choose...")
In your controller
[HttpPost]
public ActionResult Create(ApplicationViewModel model)
{
if (ModelState.IsValid)
{
Application application = new Application()
{
Id = model.Id,
Name = model.Name,
ApplicationType = db.ApplicationTypes
.First(a => a.Id == model.ApplicationTypeId);
};
db.Applications.Add(application);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(model);
}
You can then make verifying that your View Model's ApplicationTypeId corresponds to a real application type part of your modelstate's verification. You can use AutoMapper to speed up the process of converting view models to domain models.
Have you tried:
#Html.DropDownListFor(model => model.ApplicationType.Id, m => m.ApplicationType.Type, "Choose...")
Note the second parameter change.
I am developing an ASP.Net MVC 3 Web application with Entity Framework 4.1 and I am getting a bit confused with regards using Data Annotations for form validation.
I always return a ViewModel to a View as opposed to passing the actual object as I realise this is poor practice. For example:
public class ViewModelTeam
{
public Team Team { get; set; }
}
My View might then have something like this
#model UI.ViewModels.ViewModelTeam
#Html.HiddenFor(model => model.Team.teamID)
<div class="editor-label">
#Html.LabelFor(model => model.Team.description)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Team.description)
#Html.ValidationMessageFor(model => model.Team.description)
</div>
To validate this View, I have created Data Annotations in a partial class like so
[MetadataType(typeof(TeamMetaData))]
public partial class Team
{
public class TeamMetaData
{
[DisplayName("Team Name")]
[Required(ErrorMessage = "Please enter a Team Name")]
public object description { get; set; }
And then in my create Controller I have this
[HttpPost]
public ActionResult Create(Team team)
{
if (ModelState.IsValid)
{
//Add team and redirect
}
//Got this far then errors have happened
//Add Model State Errors
ViewModelTeam viewModel = new ViewModelTeam
{
Team = team
};
return View(viewModel);
}
Now, this works fine, however, the more I read about ViewModels and validation, the more it seems that it is the ViewModel that should be validated, because at the end of the day, it is the ViewModel that is being displayed in the View, not the object.
Therefore, I changed my ViewModel to look like the following
public class ViewModelListItem
{
public int teamID { get; set; }
[DisplayName("Item Name")]
[Required(ErrorMessage = "Please enter a Team Name")]
public string description { get; set; }
And I also changed my create Controller to this
[HttpPost]
public ActionResult Create(Team team)
{
if (ModelState.IsValid)
{
//Add team and redirect
}
//Got this far then errors have happened
//Add Model State Errors
ViewModelTeam viewModel = new ViewModelTeam();
viewModel.description = team.description;
return View(viewModel);
}
Again, this works, but I just get the feeling the 2nd method is a bit messy or not as efficient at the first way of doing this.
I would be interested to hear other people’s thoughts on this. Thank you for your help and I apologise for such a long post.
I always use view models and AutoMapper to help me simplify the mapping between my domain and view models.
view model:
public class TeamViewModel
{
[DisplayName("Team Name")]
[Required(ErrorMessage = "Please enter a Team Name")]
public string Description { get; set; }
}
and then a commonly used pattern:
public class TeamsController: Controller
{
public ActionResult Create()
{
var model = new TeamViewModel();
return View(model);
}
[HttpPost]
public ActionResult Create(TeamViewModel model)
{
if (!ModelState.IsValid)
{
return View(model);
}
Team team = Mapper.Map<TeamViewModel, Team>(model);
Repository.DoSomethingWithTeam(team);
return RedirectToAction("Success");
}
}
I have a number of child ViewModel classes which inherit from an base ViewModel class.
I pass my child ViewModel into my View, which then passes itself into a partial view.
The main view takes the child type, but the partial view takes the Parent type.
Everything displays correctly when I manually populate the properties.
However, when I Submit the form, my controller action only has properties for the Child class - none of the base class properties are completed?
e.g.
public abstract class BaseDetails
{
public string Name { get; set; }
public BaseDetails()
{ }
public BaseDetails(string name)
{
Name = name;
}
}
public class LocalDetails:BaseDetails
{
public int Visits { get; set; }
public LocalDetails()
{ }
public LocalDetails(int visits, string name)
:base(name)
{
Visits = visits;
}
}
With the View as simple as:
#using (Html.BeginForm())
{
#Html.TextBoxFor(m => m.Visits)
<br />
#Html.Partial("Name", Model)
<input id="Submit1" type="submit" value="submit" />
}
The partial view has a single textbox on it.
In IE: ViewSource shows the form tag is around both tetxboxes.
However, when I submit to the controller method:
[HttpPost]
public ActionResult EditLocal (LocalDetails model)
model.Visits is correctly populated, but model.Name is null?
Any ideas?
I added parameterless constructors to the classes because I got an exception upon submission if I didn't.
Unable to reproduce. Works fine for me. Notice that I am not using any constructors with my view models because the model binder wouldn't be able to call them and that all the properties must to have public getters and setters.
Model:
public abstract class BaseDetails
{
public string Name { get; set; }
}
public class LocalDetails : BaseDetails
{
public int Visits { get; set; }
}
Controller:
public class HomeController : Controller
{
public ActionResult Index()
{
return View(new LocalDetails());
}
[HttpPost]
public ActionResult Index(LocalDetails model)
{
return View(model);
}
}
View (~/Views/Home/Index.cshtml):
#model LocalDetails
#using (Html.BeginForm())
{
#Html.TextBoxFor(m => m.Visits)
<br />
#Html.Partial("Name", Model)
<input type="submit" value="submit" />
}
Partial (~/Views/Home/Name.cshtml):
#model BaseDetails
#Html.TextBoxFor(x => x.Name)
When I submit the form inside the POST Index action both model properties Name and Visits are correctly bound with values from the form.
I have created MVC3 application using Entity Framework Code First method. My model is very simple:
public class Employee
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public int YearsAtCompany { get; set; }
}
and context class is
public class EmployeeDB : DbContext
{
public DbSet<Employee> Employees { get; set; }
}
and controller looks like this:
EmployeeDB context = new EmployeeDB();
public ActionResult Index()
{
return View(context.Employees);
}
}
I have created EmployeesDb.mdf and Employee table.
but I get this error:
The model item passed into the dictionary is of type 'System.Data.Entity.DbSet`1[DFEmployees.Models.Employee]', but this dictionary requires a model item of type 'DFEmployees.Models.Employee'.
[Updated]
#model DFEmployees.Models.Employee
#{
ViewBag.Title = "Index";
}
<h2>Index</h2>
Please suggest solution.
It's looks like your view are waiting for a single employee, and you are triying to fill the view with a DBSet of employees.
To solve it, you can set the #model of the view to an IEnumerable of employees, or send only one employee to the view, depending of what are you showing in the view.
EDIT: I think this problem is not related with the previous one. Check this link, I hope it helps you: LINK
Your controller action returns a list of employees so adapt your model respectively in the view:
#model IEnumerable<DFEmployees.Models.Employee>
Or if you wanted to use a single employee make sure you pass a single employee to the view:
public ActionResult Index()
{
return View(context.Employees.FirstOrDefault());
}
and then you can have:
#model DFEmployees.Models.Employee