I have a web app where I want to show a form for a Test object. Different Test instances can have different schemas. I can display this pretty nicely, but it won't populate all the data from the form back into my model.
Here are my model classes:
public class EnterTestData
{
public string StudyId { get; set; }
public Test Test { get; set; }
}
public sealed class Test
{
public string Identity { get; set; }
public string Name { get; set; }
public IEnumerable<TestField> Fields { get; set; }
}
public sealed class TestField
{
public string Identity { get; set; }
public string Name { get; set; }
public string Value { get; set; }
public string Type { get; set; }
}
Here is the relevant portion of my View:
<% Html.BeginForm("PostTestData", "StudiesUserInterface"); %>
<table>
<%
foreach (var testField in Model.Test.Fields)
Html.RenderPartial("UserControls/TestFieldUserControlascx", testField);
foreach (var category in Model.Test.Categories)
{
%>
<tr>
<td colspan="2" style="font-style: italic; text-align: center;">
<%=category.Name %>
</td>
</tr>
<%
foreach (var testField in category.Fields)
Html.RenderPartial("UserControls/TestFieldUserControlascx", testField);
}
%>
<tr>
<td colspan="2" style="text-align: right;">
<input type="submit" name="newsletter" value="Enter Result" />
</td>
</tr>
</table>
<% Html.EndForm(); %>
And the partial view for the actual text boxes:
<tr>
<td>
<%= Model.Name %>
</td>
<td>
<%
switch (Model.Type)
{
case "date":
case "text":
case "number":
%>
<%= Html.TextBox(Model.name, Model.Value) %>
<% break;
default: %><%= Html.Label("Unknown data type") %><% break;
}
%>
</td>
</tr>
Update Controller methods:
public ActionResult EnterTestData(string studyId, string testId)
{
var testDefinition = ServiceKitLocator.GetStudyService().GetTestDefinition(testId);
return View(new EnterTestData { StudyId = studyId, Test = testDefinition });
}
public ActionResult PostTestData(EnterTestData model)
{
//I'm just putting a break point here and checking the model in the debugger for now
return RedirectToAction("Index");
}
The problem is that Test is null when it comes back to my Controller. How do I get it to be populated? Why is it null?
I think the problem is the Html.TextBox element.
If it's correct that the model in your view is type of "Test" but in your controller action you want to bind to type of "EnterTestData" which has a property of type "Test" named "Test"
Then your TextBox should be initializied like
Html.TextBox("Test.Name", Model.Value)
The important part is the name parameter. The modelbinder matches this name with the properties of the model type in your post action, in your case "EnterTestData".
You can also use an editor template view. Which does the same as your partial view.
In your project go to Views\Shared\ and create a folder named EditorTemplates, if not exists.
In this folder create a partial view and name it like the class/type the template should be for. In your case "TestField.ascx". Actually you can copy and rename your existing partial view.
In you main view you have to change 2 things:
- use for instead of foreach
- call the editor template in the loops
like:
for(int i = 0; i < Model.Test.Fields.Count(); i++)
Html.EditorFor(Model => Model.Test.Fields[i]);
In the template view you have to change one thing:
- user TextBoxFor instead of TextBox
like:
Html.TextBoxFor(Model => Model.Value)
I use this pattern often. It makes it easy to bind complex models.
You may have a look at the generated HTML
Related
I'm using paging with ASP.NET MVC, but I'm losing model data when navigating to next page.
Here is the code:
Partial view:
#using PagedList;
#using PagedList.Mvc;
#model Models.MyObject
<link href="~/Content/PagedList.css" rel="stylesheet" type="text/css" />
<table class="table table-striped table-hover sortable">
<thead>
<tr>
<th width="240">#Model.HeaderNames[0]</th>
<th width="240">#Model.HeaderNames[1]</th>
<th width="240">#Model.HeaderNames[2]</th>
<th width="240">#Model.HeaderNames[3]</th>
<th width="120" class="no-sort"></th>
</tr>
</thead>
<tbody>
#foreach (var member in Model.PagedModelList)
{
<tr>
<td><span class="sort-field-value">#member.Thead1</span></td>
<td><span class="sort-field-value">#member.Thead2</span></td>
<td><span class="sort-field-value">#member.Thead3</span></td>
<td><span class="sort-field-value">#member.Thead4</span></td>
</tr>
}
</tbody>
</table>
<footer>
<div>
Page #(Model.PagedModelList.PageCount < Model.PagedModelList.PageNumber
? 0 : Model.PagedModelList.PageNumber) of #Model.PagedModelList.PageCount
#Html.PagedListPager(Model.PagedModelList, page =>Url.Action("Reports",new { #page = page, FirstLoad = false }))
</div>
</footer>
Controller :
public ActionResult Reports(MyObject model, int? page, bool FirstLoad = true)
{
model.pageSize = 4;
model.pageNumber = (page ?? 1);
if (FirstLoad)
{
// getting the data from database here
// ... code
// assigning pagemodelist
model.PagedModelList = model.ModelList.ToPagedList(model.pageNumber, model.pageSize);
}
// here sending the model and all is good
return PartialView("_MyView", model);
}
Model
public class MyObject
{
public int SelectedPeriod { get; set; }
public List<SecondObject> ModelList = new List<Secondobject>();
public IPagedList<Secondobject> PagedModelList ;
public int pageSize { get; set; }
public int pageNumber { get; set; }
}
Second model class:
public class SecondObject
{
public string Thead1 { get; set; }
public string Thead2 { get; set; }
public string Thead3 { get; set; }
public string Thead4 { get; set; }
}
Expected to get next page but all I get is empty model which causes null reference when sending again to view from controller. What am I doing wrong here?
I'm getting right data in model the first time, I show the table with correct data, then when clicking on next page I debug in the controller so I got empty model.PagedModelList, even some other empty model properties .
Any help appreciated
Maybe, try to pass your model in parameters like :
#Html.PagedListPager(Model.PagedModelList, page =>Url.Action("Reports",new {#model = Model, #page = page, FirstLoad = false }))
Edit :
Another solution is to remove MyObject from parameters.
Load each time your data from your database. You can follow the example in
GitHub project
I can't seem to find how to build a proper solution for a one to many checkbox.
So what do i have :
I have an article and an admin user can set user rights to that article.
So in my article right page you have an overview with all the users.
My article:
public partial class Artikel
{
public Artikel()
{
this.ArtikelLinks = new HashSet<ArtikelLink>();
this.ArtikelRights = new HashSet<ArtikelRight>();
}
public int id { get; set; }
public string code { get; set; }
public string naam { get; set; }
public virtual ICollection<ArtikelLink> ArtikelLinks { get; set; }
public virtual ICollection<ArtikelRight> ArtikelRights { get; set; }
}
My rights class
public partial class ArtikelRight
{
public int id { get; set; }
public System.Guid userId { get; set; }
public int artikelId { get; set; }
public bool hasRight { get; set; }
public virtual Artikel Artikel { get; set; }
}
How do i build my view? i tried several ways but i can't seem to save my data.
this is my rights view at the moment:
#using (Html.BeginForm()) {
<table width="90%" align="center" class="table table-striped">
<thead>
<tr>
<th align="left">Gebruiker</th>
<th align="left">Heeft toegang</th>
</tr>
</thead>
#Html.EditorFor(x => Model.ArtikelRights, "ArtikelRight")
</table>
<br />
<div class="pull-right">
<input type="submit" value="Opslaan" class="btn btn-sm beige" />
</div>
}
And this is my partial Artikel right view:
#model IEnumerable<GtiProducts.Models.ArtikelRight>
#foreach (var item in Model) {
<tr>
<td>
#Membership.GetUser(item.userId).UserName
</td>
<td>
#Html.EditorFor(x => item.hasRight)
</td>
</tr>
}
My save action is:
public ActionResult Rights(Artikel art)
{
repo.SaveChanges();
return View(art);
}
When i debug my art.ArtikelRights is null.
How can i fix this and what's the best solution to do this with the entity framework?
Rename the partial to make it an EditorTemplate- /Views/Shared/EditorTemplates/ArtikelRight.cshtml, then make the following modifications
#model GtiProducts.Models.ArtikelRight // not IEnumerable
<tr>
<td>
#Html.CheckBoxFor(m => m.hasRight)</td>
#Html.LabelFor(m => m.hasRight, "....") // the users name?
#Html.HiddenFor(m > m.id) // or what ever property you need to identify the ArtikelRight.cshtml
</td>
</tr>
Then in the main view
<tbody>
#Html.EditorFor(x => Model.ArtikelRights)
</tbody>
This will correctly name you controls with indexers so they can be bound to your model on post back, for example
<input type="checkbox" name="ArtikelRights[0].hasRight" ...>
<input type="checkbox" name="ArtikelRights[1].hasRight" ...>
Side note: You should not be using #Membership.GetUser(item.userId).UserName in the view. Instead create a view model with only those properties you need in the view (which appears to be id and hasright, and include a property for the users display name (and populate it in the controller).
I know that If I want to see data from just one table in my Index View, I have to do this:
public class LocalidadesController : Controller
{
// GET: /Localidades/
private Entities db = new Entities();
//
// GET: /Concessao/
[Authorize(Roles = "ADMINISTRADOR")]
public ActionResult Index()
{
return View(db.SINCO_LOCALIDADE_CONCESSAO.ToList());
}
For two tables (in my case, views), I'm doing the changes below (localidades_view and municipios_view are the
views that I want to select data).
Model:
namespace SINCO_MVC.Models
{
[MetadataType(typeof(SincoLocalidadeConcessaoMetaData))]
public partial class SINCO_LOCALIDADE_CONCESSAO
{
}
public class SincoLocalidadeConcessaoMetaData
{
[Display(Name = "ID LOCALIDADE:")]
public int[] IDLOCALIDADE { get; set; }
[Display(Name = "ID:")]
public int IDCONCESSAO { get; set; }
[Display(Name = "Localidade:")]
public virtual LOCALIDADES_VIEW LOCALIDADES_VIEW { get; set; }
public virtual MUNICIPIOS_VIEW MUNICIPIOS_VIEW { get; set; }
}
}
View:
<%# Page Title="SINCO - Localidades" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<IEnumerable<SINCO_MVC.Models.SINCO_LOCALIDADE_CONCESSAO>>" %>
<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
SINCO - Localidades
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
<h2>SINCO - Localidades</h2>
<table>
<tr>
<th>
<%: Html.DisplayNameFor(model => model.IDCONCESSAO) %>
</th>
<th>
<%: Html.DisplayNameFor(model => model.IDLOCALIDADE) %>
</th>
<th></th>
</tr>
<% foreach (var item in Model) { %>
<tr>
<td>
<%: Html.DisplayFor(modelItem => item.MUNICIPIOS_VIEW.NOME_MUNICIPIO) %>
</td>
<td>
<%: Html.DisplayFor(modelItem => item.LOCALIDADES_VIEW.NOME_LOCALIDADE) %>
</td>
I did something like this, but it didn't work, because this fields in index stays empty.
How can I access the data of this views(oracle views)??
*Sorry for my bad english.
Make a ViewModel which contains two fields, one for each table, Done...something like
public class MyViewModel
{
public MyType Table1 {get;set;}
public MyType2 Table2 {get;set;}
}
The problem it was because of LazyLoad. I added the tables in my model but because of LazyLoad nothing returned. I had to add this code below in my index:
[Authorize(Roles = "ADMINISTRADOR")]
public ActionResult Index(SINCO_LOCALIDADE_CONCESSAO model)
{
return View("Index", db.SINCO_LOCALIDADE_CONCESSAO.Include(s => s.LOCALIDADES_VIEW).Include(s => s.MUNICIPIOS_VIEW));
}
Now it's working fine!
I am new in MVC and learning MVC. Now I do not want to use any grid extension, rather I want to generate tabular UI just by HTML table. So I was looking for code and found a hint.
This code is not full code. Here is what I got.
<% if (Model.Count() > 0)
{ %>
<table width="35%">
<thead><tr><th>Species</th><th>Length</th></tr></thead>
<% foreach (var item in Model)
{ %>
<tr>
<td><%= item.FishSpecies%></td>
<td align="center"><%=item.Length%></td>
</tr>
<% } %>
</table>
<% }
else
{ %>
No fish collected.
<%} %>
The problem is I am not being able to visualize how the model class should look like and how it is populated from controller. Viewbag is not used in code, rather generating table directly from model class.
So can anyone write a small complete code for me just to create a HTML table and populate with model directly without using viewbag?
Need code for model, controller and view too.
Your model actually needs to be a IEnumerable<Model>. So you might have a model:
public class Model
{
public string FishSpecies { get; set; }
public int Length { get; set; }
public static IEnumerable<Model> Load() { ... }
}
and then in your controller's action:
var list = Model.Load();
return View(list);
and then in the view you need to define the model at the very top:
#model System.Collections.IEnumerable<My.Namespace.Model>
Now, these two lines aren't going to work:
<td><%= item.FishSpecies%></td>
<td align="center"><%=item.Length%></td>
they need to be something more like this:
<td>#Html.DisplayFor(m => item.FishSpecies)</td>
<td>#Html.DisplayFor(m => item.Length)</td>
If you want to iterate through your model and create your table, first of all change the #model of your view like this:
#model IEnumerable<myPrj.Models.EntityName>
Then, you should change your action method to supply model items to your view:
public ActionResult Index()
{
return View(db.EntityNames.ToList());
}
and finally, iterate your model and create your table:
<table id="tblNews">
<tr>
<th>
#Html.DisplayNameFor(model => model.Property1)
</th>
// ...
<th>
#Html.DisplayNameFor(model => model.Propertyn)
</th>
</tr>
#foreach (var item in Model) {
<tr>
<td>
#Html.DisplayFor(modelItem => item.Property1)
</td>
// ...
<td>
#Html.DisplayFor(modelItem => item.Propertyn)
</td>
</tr>
}
</table>
and about the model? Model is nothing but a class with some properties:
public class MyModel
{
[Display(Name = "Property 1")]
[Required(ErrorMessage = "Property cannot be empty")]
public int Property1 { get; set; }
// ...
[Display(Name = "Property n")]
[Required(ErrorMessage = "Property cannot be empty")]
public string Propertyn { get; set; }
}
I have a collection of ViewModels that I send back to from controller to the view
public class CourseTableViewModel
{
public string Prefix { get; set; }
public bool OwnerPremission { get; set; }
public bool AddPermission { get; set; }
public bool EditPermission { get; set; }
public bool DeletePermission { get; set; }
public bool ViewPermission { get; set; }
}
So I have a list of these
#foreach (var item in Model)
{
<tr>
<td>#Html.CheckBoxFor(x => item.OwnerPermission) </td>
//.... rest here //
</tr>
}
This will loop around X amount of times depending on how VM's is being sent back but they all with have the same id and I want them different.
So I did this
<td>#Html.CheckBoxFor(x => item.OwnerPremission,new {#disabled = "disabled", #id = PermissionTypes.Owner.ToString() + counter})</td>
I am wondering if there is a better way.
Edit
So I did the template way(display not edit though) and have in it this
<td>#Html.CheckBoxFor(x => x.OwnerPremission, new { #disabled = "disabled"})</td>
It renders this for the first one.
<td><input type="checkbox" value="true" name="[0].OwnerPremission" disabled="disabled" checked="checked"><input type="hidden" value="false" name="[0].OwnerPremission"></td>
There is no id just name. Why does it not show up?
You could use an editor template for this view model which will ensure correct id and name attributes. So assuming your main view model is a collection of CourseTableViewModel or has a property that is a collection of CourseTableViewModel you could do the following in your main view:
#model IEnumerable<AppName.Models.CourseTableViewModel>
<table>
<tr>
#Html.EditorForModel()
</tr>
</table>
and in the corresponding editor template (~/Views/Shared/EditorTemplates/CourseTableViewModel.cshtml):
#model AppName.Models.CourseTableViewModel
<td>
#Html.CheckBoxFor(x => x.OwnerPremission, new { #disabled = "disabled" })
</td>
...
The editor template will be executed for each item in the view model collection and avoids you the need of writing any loops in your views. You just need to follow the naming conventions of those templates (it must be called CourseTableViewModel.cshtml because that's the type name used in the main view model collection).
UPDATE:
In the example I provided no id is being generated at all for the checkboxes. You could do the following to generate an unique id for each checkbox:
#model AppName.Models.CourseTableViewModel
<td>
#Html.CheckBoxFor(
x => x.OwnerPremission,
new {
#disabled = "disabled",
id = HtmlHelper.GenerateIdFromName("OwnerPremission." + ViewData.TemplateInfo.GetFullHtmlFieldName(""))
}
)
</td>