I came across a problem with ambient form data, which I've tried to isolate into the following example.
View model:
public class ViewModel
{
public int Value { get; set; }
}
Controller:
public class TestController
{
public ViewResult Test1()
{
return(View(new ViewModel {Value = 1}));
}
[HttpPost]
public ViewResult Test2(ViewModel vm)
{
vm.Value = 2;
return(View(vm));
}
}
Test 1 view:
#model ViewModel
#using(Html.BeginForm("Test2", "Test"))
{
#Html.TextBoxFor(m => m.Value)
<button type="submit">Send</button>
}
Test 2 view:
#model ViewModel
#Model.Value
#Html.TextBoxFor(m => m.Value)
Submitting the form in view 1 gives the, at least to me, surprising result in view 2:
Seems like the posted value gets used in the TextBoxFor().
Anyone knows what's going on here and how to avoid it?
Related
I'm new to MVC 5 with .net
Basically I'm just trying to get my bearings and I want to display some generic queries (disregard the fact that im using the master db, I just want to get the functions working right now). I'm using the authentication 2.0 which has an applicatindbcontext, but I created my own context but since I'm not really wanting to create a model (which could be the problem) I didn't know what to create for properties:
public class MasterModel : DbContext
{
public MasterModel() : base("MasterDatabaseConnection")
{ }
}
I created the controller like:
public class MasterController : Controller
{
private MasterModel db = new MasterModel();
// GET: Statistics
public ActionResult Index()
{
return View();
}
public ActionResult GetVersion()
{
string query = "SELECT ##VERSION AS Version";
IEnumerable<MasterModel> data = db.Database.SqlQuery<MasterModel>(query);
return View(data.ToList());
}
}
And finally I'm trying to figure out how to display the results in the view...and I'm completely lost (although it's possible I was lost in one of the previous steps).
#model IEnumerable<IdentitySample.Models.MasterModel>
#{
ViewBag.Title = "Index";
}
#WTF.Am.I.SupposedToPutHere
I've followed some tutorials where I've created CRUD style model view controllers, but I guess I'm not drawing the connection on how to just submit informational queries and display the results.
Create a Context:
public class MasterModel : DbContext
{
public MasterModel() : base("MasterDatabaseConnection")
{ }
public DbSet<MyModel> ModelOBJ { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<ModelOBJ>().ToTable("tblModelOBJ");
}
}
Create a Model:
Public cLass MyModel
{
public int ID {get;set;}
public string Name {get;set;}
}
Public cLass MyModelRepository
{
public List<MyModel> GetALlModelFromDB()
{
MasterModel md = new MasterModel();
return md.ModelTosend.toList();
}
}
In your Controller:
public ActionResult Index()
{
return View(new MyModelRepository().GetALlModelFromDB());
}
In your View:
#model IEnumerable<IdentitySample.Models.MyModel>
#{
ViewBag.Title = "Index";
}
#foreach(var item in Model)
{
#:<div>#item.ID #item.Name </div>
}
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.
I have two models: OuterModel and InnerModel. There is a one to many relationship between OuterModel and InnerModel, respectively. To clarify my question, my model is of type IEnumerable<OuterModel>. I'm passing a random number of OuterModels to the view and the user creates any number of InnerModels for each OuterModel. Then on submission, I want the controller to receive the list of OuterModels so that the InnerModels can be added to the database to their intended OuterModels.
I believe I have the naming convention correct to make use of MVC's built in model binding. Here's what that looks like:
OuterModel[i].InnerModel[j].Property
My problem is, I don't really know how to get a list of OuterModels passed to the controller. Here's what I've tried in my View:
#model IEnumerable<OuterModel>
#using (Html.BeginForm("Create", "Controller", new { OuterModels = Model }, FormMethod.Post))
{
//Code to create the InnerModels here
}
And here's what I have in my Controller:
[HttpPost]
public ActionResult Create(IEnumerable<OuterModel> OuterModels, FormCollection fc)
{
String[] keys = fc.AllKeys;
if(ModelState.IsValid){
//Add to db
}
}
Keys shows that all of my properties are following the naming convention that I specified earlier, but ModelState.IsValid is returning false. It shows that OuterModels' count is 0.
Even though I'm telling the form to submit OuterModels = Model before any InnerModels are created, you would think there would still be data in OuterModels considering it's passed to the view. I am really tired today, so I'm guessing I'm looking over one (or many) small detail(s). Any suggestions?
--EDIT--
Passing a list of OuterModels to the controller may not be the best approach. If anybody has a better suggestion, please share.
As long as indexes are used properly, then this should not be an issue. Here is how I would envision the form names.
Model[0].foo
Model[0].Inner[0].bar
Model[0].Inner[1].bar
Where outer model has a property called foo and
Outer model has a property called inner which is a collection of inner objects. Inner object has a property called bar. If your form is rendered with the correct indexes then the model binding should work. Things can get tricky if form fields are generated client side. I recommended going back to server in order to manipulate the model. There are some extra round trips, but you can make them via Ajax request.
Here are some more details in a more fleshed out example.
public class InnerModel{
public string Name{get; set;}
}
public class OuterModel{
public List<InnerModel> InnerModels{get; set;}
public string Name{get; set;}
}
Here is what I would envision my view would look like:
#model IEnumerable<OuterModel>
<ul>
#{int i = 0;}
#foreach(var item in Model){
<li>
Outer Name : #Html.TextBoxFor(m=>Model[i].Name)
<br />
#{int j = 0;}
<ul>
#foreach(var innerItem in Model[i].InnerModels){
<li>Inner Name : #Html.TextBoxFor(m=> Model[i].InnerModels[j].Name)</li>
j++;
}
</ul>
i++;
</li>
}
</ul>
If this is wrapped in a form--- and the controller action looks like this:
public ActionResult Action(List<OuterModel> model){
}
then I would think model would be populated correctly.
I noticed your form.. it doesn't look right to me... I wouldn't think that the passing the OuterModels like that is going to work-- although frankly I might be wrong.
#using (Html.BeginForm("Create", "Controller", new { OuterModels = Model }, FormMethod.Post))
{
//Code to create the InnerModels here
}
Here is an example I did for the class I teach.. that definitely works..
public class Author
{
public string Name { get; set; }
}
public class Book
{
public string Name { get; set; }
public List<Author> Authors { get; set; }
}
Controller:
public class BookController : Controller
{
public static List<Book> _model = null;
public List<Book> Model
{
get
{
if (_model == null)
{
_model = new List<Book>
{
new Book{
Name = "Go Dog Go",
Authors = new List<Author>{
new Author{Name = "Dr. Seuss"}
}},
new Book{
Name = "All the Presidents Men",
Authors = new List<Author>{
new Author{Name = "Woodward"},
new Author{Name = "Bernstein"}
}},
new Book{
Name = "Pro ASP.NET MVC Framework",
Authors = new List<Author>{
new Author{Name = "Sanderson"},
new Author{Name = "Stewart"},
new Author {Name = "Freeman"}
}}
};
}
return _model;
}
}
public ActionResult Index()
{
return View(Model);
}
public ActionResult Edit()
{
return View(Model);
}
[HttpPost]
public ActionResult Edit(List<Book> books)
{
_model = books;
return RedirectToAction("Index");
//return View(books);
}
}
and View:
#model List<AmazonWeb.Models.Book>
#{
ViewBag.Title = "Index";
}
<div class="content">
#Html.ActionLink("Index", "Index")
#using (Html.BeginForm())
{
<input type="submit" value="save" />
<ul class="book-list">
#for (var i = 0; i < Model.Count; i++ )
{
<li>
<label>Book Name</label> : #Html.TextBoxFor(m => Model[i].Name)
<ul>
#for (var j = 0; j < Model[i].Authors.Count; j++ )
{
<li><label>Author Name</label> : #Html.TextBoxFor(m => Model[i].Authors[j].Name)</li>
}
</ul>
</li>
}
</ul>
<input type="submit" value="save" />
}
</div>
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");
}
I have the following model :
public class ContratoDetailsViewModel
{
[StringLength(50)]
[RegularExpression("^[a-z0-9_\\+-]+(\\.[a-z0-9_\\+-]+)*#[a-z0-9-]+(\\.[a-z0-9-]+)*\\.([a-z]{2,4})$")]
[DisplayName("E-Mail Adm.")]
public string emailAdm { get; set; }
}
public class ContratoDetailContainerViewModel
{
public ContratoDetailsViewModel contrato { get; set; }
public IList<ContratoModels.CCasinoViewModel> ccasinos { get; set; }
}
public class CCasinoViewModel
{
public short codigo { get; set; }
public List<SelectListItem> listCasinos { get; set; }
}
the following view :
#model ContratoModels.ContratoDetailContainerViewModel
#{
...
}
#using (Html.BeginForm(new { currentaction = ViewBag.mode }))
{
...
#Html.EditorFor(m => m.contrato.emailAdm, state1)<br />
#Html.EditorFor(m => m.ccasinos,"test")
<input type="submit" value="Save" />
}
in the folder "EditorTemplates" i have a template called "test.cshtml" :
#model List<ContratoModels.CCasinoViewModel>
#for (int i = 0; i < Model.Count(); i++)
{
#Html.DropDownListFor(m => m[i].codigo,Model[i].listCasinos)
}
My Controller post action is like this :
[HttpPost]
public ActionResult Details(ContratoModels.ContratoDetailContainerViewModel model, FormCollection form)
{
var contrato = model.contrato;
var casinos = model.ccasinos;
}
Before send the view ccasinos,codigo and listCasinos are initialised
when i am in debug mode i see the value of them... the form display work like a charm.
BUT ... when i submit the form the model.ccasinos is always null !! why ?
thank you very much for your reply.
note : I use a EditorFor with the child of my main model but if there is a better solution
for display and submit with MCV 3 I am interested ...
Try replacing:
#Html.EditorFor(m => m.ccasinos, "test")
with this:
#Html.EditorFor(m => m.ccasinos)
and then rename your test.cshtml template to CCasinoViewModel.cshtml and replace its contents with this:
#model CCasinoViewModel
#Html.DropDownListFor(x => x.codigo, Model.listCasinos)
Because the editor template is now named the same way as the type of the list, ASP.NET MVC will automatically render it for each element of this list so that you don't have to write loops.
Also you can safely remove the FormCollection argument from your action. It's completely useless when you are working with view models:
[HttpPost]
public ActionResult Details(ContratoModels.ContratoDetailContainerViewModel model)
{
var contrato = model.contrato;
var casinos = model.ccasinos;
...
}