What I need is to display Area_Name in various links within my view. Rather than repeat the same loop below each time, can I assign Area_Name to a variable and then display that variable in each link within my view?
ViewData
filterContext.Controller.ViewData["Categories"] = from m in _dataContext.Categories where m.Area_ID == SectionID select m;
View Page
Stylesheet
<%
foreach (var c in (IEnumerable<Categories>)ViewData["Categories"]) { %>
<link href="../../Content/<%= c.Area_Name %>/Site.css" rel="stylesheet" type="text/css" />
<% } %>
Image
<%
foreach (var c in (IEnumerable<Categories>)ViewData["Categories"]) { %>
<img src="../../Content/images/<%= c.Area_Name %>/slide1.jpg" />
<img src="../../Content/images/<%= c.Area_Name %>/slide2.jpg" />
<img src="../../Content/images/<%= c.Area_Name %>/slide3.jpg" />
<% } %>
perhaps create a strongly typed viewData file with a property for each AreaName that you have. The downside of this is that when you add a new category the viewData class and the view will not pick up those changes in the same way they will with your current code.
Use a second for-each or a for-loop?
Like this:
<% foreach (var c in (IEnumerable<Categories>)ViewData["Categories"]) {
for ( int i = 0 ; i < 100; i ++ )
{
%>
<img src="../../Content/images/<%= c.Area_Name %>/slide<%= i %>.jpg" />
<%
}
<% } %>
It's hard to know exactly what you mean though..
Related
It's a bit complicated so bear with me.
Let's say I've got an example of a controller edit action defined like:
Node nd = _repo.getNode(id);
List<Category> ac = new List<Category>();
ac.AddRange(_repo.getCategories());
SelectList acl = new SelectList(ac, "category_id", "category_name", ac.Where(cat => cat.category_id == nd.category_id).First());
ViewData["category_id"] = acl;
return View(nd);
The view is templated like so:
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<Myapp.Models.Node>" %>
<% if (ViewData.TemplateInfo.TemplateDepth > 1)
{ %>
<%= ViewData.ModelMetadata.SimpleDisplayText %>
<% }
else
{ %>
<table cellpadding="0" cellspacing="0" border="0">
<% foreach (var prop in ViewData.ModelMetadata.Properties.Where(pm => pm.ShowForEdit && !ViewData.TemplateInfo.Visited(pm)))
{ %>
<% if (prop.HideSurroundingHtml)
{ %>
<%= Html.Editor(prop.PropertyName) %>
<% }
else
{ %>
<tr>
<td>
<div class="editor-label" style="text-align: right;">
<%= prop.IsRequired ? "*" : ""%>
<%= Html.Label(prop.PropertyName)%>
</div>
</td>
<td>
<div class="editor-field">
<% if (ViewData.Keys.Contains(prop.PropertyName))
{
if ((ViewData[prop.PropertyName]).GetType().Name == "SelectList")
{ %>
<%= Html.DropDownList(prop.PropertyName, (SelectList)ViewData[prop.PropertyName])%>
<% }
else
{ %>
<%= Html.Editor(prop.PropertyName)%>
<% } %>
<% }
else
{ %>
<%= Html.Editor(prop.PropertyName)%>
<% } %>
<%= Html.ValidationMessage(prop.PropertyName, "*")%>
</div>
</td>
</tr>
<% } %>
<% } %>
</table>
<% } %>
So, what the template does is display a dropdown list for every property for which ViewData["property_name"] exists.
I've also defined DisplayName metadata attributes for every property of my Node class.
Now, the dropdown lists display fine and are being populated correctly, but:
The first value from a list is always selected, even though the SelectList selected value predicate is fine and does set a proper value (in the debugger at least).
Html.Label in the template returns a proper DisplayName for properties, but when I define a ViewData for them so as to display the dropdown list, the label resets to normal property name (ie. category_id instead of Category).
What gives? Can you think of any "neater" way to accomplish this functionality?
Allright, no one's answering so there's my answer, maybe it comes in handy for someone:
Do not use your property names for ViewData keys! It messes up with the view model, so your views get confused and start to behave strangely.
Actually, best avoid the magic strings mess entirely, but if you insist, just use something like ex.: ViewData[prop.PropertyName+"_list"]. Your views are going to be fine now.
I will restrict this to the three tables I am trying to work with Problem, Communications, and ProbComms. The scenario is that a Student may have many Problems concurrently which may affect their studies. Lecturers may have future communications with a student after an initial problem is logged, however as a Student may have multiple Problems the Lecturer may decide that the discussion they had is related to more than one Problem.
Here is a screenshot of the LINQ-To-Sql representation of my DB:
LINQ-To-Sql Screenshot
At the moment in my StudentController I have a StudentFormViewModel Class:
//
//ViewModel Class
public class StudentFormViewModel
{
IProbCommRepository probCommRepository;
// Properties
public Student Student { get; private set; }
public IEnumerable<ProbComm> ProbComm { get; private set; }
//
// Dependency Injection enabled constructors
public StudentFormViewModel(Student student, IEnumerable<ProbComm> probComm)
: this(new ProbCommRepository())
{
this.Student = student;
this.ProbComm = probComm;
}
public StudentFormViewModel(IProbCommRepository pRepository)
{
probCommRepository = pRepository;
}
}
When I go to the Students Detail Page this runs:
public ActionResult Details(string id)
{
StudentFormViewModel viewdata = new StudentFormViewModel(studentRepository.GetStudent(id),
probCommRepository.FindAllProblemComms(id));
if (viewdata == null)
return View("NotFound");
else
return View(viewdata);
}
The GetStudent works fine and returns an instance of the student to output on the page, below the student I output all problems logged against them, but underneath these problems I want to show the communications related to the Problem.
The LINQ I am using for ProbComms is This is located in the Model class ProbCommRepository, and accessed via a IProbCommRepository interface:
public IQueryable<ProbComm> FindAllProblemComms(string studentEmail)
{
return (from p in db.ProbComms
where p.Problem.StudentEmail.Equals(studentEmail)
orderby p.Problem.ProblemDateTime
select p);
}
However for example if I have this data in the ProbComms table:
ProblemID CommunicationID
1 1
1 2
The query returns two rows so I assume I somehow have to groupby Problem or ProblemID but I am not too sure how to do this with the way I have built things as the return type has to be ProbComm for the query as thats what Model class its located in.
When it comes to the view the Details.aspx calls two partial views each passing the relevant view data through, StudentDetails works fine page:
<%# Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<MitigatingCircumstances.Controllers.StudentFormViewModel>" %>
<% Html.RenderPartial("StudentDetails", this.ViewData.Model.Student); %>
<% Html.RenderPartial("StudentProblems", this.ViewData.Model.ProbComm); %>
StudentProblems uses a foreach loop to loop through records in the Model and I am trying another foreach loop to output the communication details:
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<IEnumerable<MitigatingCircumstances.Models.ProbComm>>" %>
<script type="text/javascript" language="javascript">
$(document).ready(function() {
$("DIV.ContainerPanel > DIV.collapsePanelHeader > DIV.ArrowExpand").toggle(
function() {
$(this).parent().next("div.Content").show("slow");
$(this).attr("class", "ArrowClose");
},
function() {
$(this).parent().next("div.Content").hide("slow");
$(this).attr("class", "ArrowExpand");
});
});
</script>
<div class="studentProblems">
<% var i = 0;
foreach (var item in Model) { %>
<div id="ContainerPanel<%= i = i + 1 %>" class="ContainerPanel">
<div id="header<%= i = i + 1 %>" class="collapsePanelHeader">
<div id="dvHeaderText<%= i = i + 1 %>" class="HeaderContent"><%= Html.Encode(String.Format("{0:dd/MM/yyyy}", item.Problem.ProblemDateTime))%></div>
<div id="dvArrow<%= i = i + 1 %>" class="ArrowExpand"></div>
</div>
<div id="dvContent<%= i = i + 1 %>" class="Content" style="display: none">
<p>
Type: <%= Html.Encode(item.Problem.CommunicationType.TypeName) %>
</p>
<p>
Problem Outline: <%= Html.Encode(item.Problem.ProblemOutline)%>
</p>
<p>
Mitigating Circumstance Form: <%= Html.Encode(item.Problem.MCF)%>
</p>
<p>
Mitigating Circumstance Level: <%= Html.Encode(item.Problem.MitigatingCircumstanceLevel.MCLevel)%>
</p>
<p>
Absent From: <%= Html.Encode(String.Format("{0:g}", item.Problem.AbsentFrom))%>
</p>
<p>
Absent Until: <%= Html.Encode(String.Format("{0:g}", item.Problem.AbsentUntil))%>
</p>
<p>
Requested Follow Up: <%= Html.Encode(String.Format("{0:g}", item.Problem.RequestedFollowUp))%>
</p>
<p>Problem Communications</p>
<% foreach (var comm in Model) { %>
<p>
<% if (item.Problem.ProblemID == comm.ProblemID)
{ %>
<%= Html.Encode(comm.ProblemCommunication.CommunicationOutline)%>
<% } %>
</p>
<% } %>
</div>
</div>
<br />
<% } %>
</div>
The issue is that using the example data before the Model has two records for the same problem as there are two communications for that problem, therefore duplicating the output.
Any help with this would be gratefully appreciated.
Thanks,
Jon
I have recently struggled greatly with many to many relationships in MVC. I've finally gotten mine working. I'd love to help you out, but do not fully understand your problem.
When you say that the query returns 2 rows, I would think that it should since there are 2 communications for that problem. If you want to return problems for a student, you do not need to query the ProbComms table, just the Problem table. Then if you want to return communications for those problems, query the ProbComms table.
For your loop, you need to loop through the problems and then within that loop, loop through the communications specific to that problem. Something very loosely like:
foreach (var p in Model.Student.Problem)
{
<%= Html.Encode(p.ProblemInfoField) %>
...
foreach (var c in p.ProbComms)
{
<%= Html.Encode(c.Communications.CommunicationInfoField) %>
...
}
}
You do not need to select the ProbComms in the controller and send them to the view. They should already be linked. Only the student needs to be sent to the view.
I got a controller action like
public class Question {
public int Id { get;set; }
public string Question { get;set; }
public string Answer { get;set; }
}
public ActionResult Questions()
{
return View(GetQuestions());
}
public ActionResult SaveAnswers(List<Question> answers)
{
...
}
the view> looks like:
<% for (int i = 0; i < Model.Count; i++) { %>
<div>
<%= Html.Hidden(i.ToString() + ".Id") %>
<%= Model[i].Question %>
<%= Html.TextBox(i.ToString() + ".Answer") %>
</div>
<% } %>
Obviously this view doesn't work. I'm just not able access the list in the view.
The documentation for this also is outdated, it seem a lot of the functionality around modelbinding lists where changed in the beta.
I think that Scott Hanselman's post probably holds the answer. However it appears that you are trying to tie you view references to an anonymous object by returning in the post ...0.Answer=answer...
You should instead I believe be tying your fields to the `List answers refering to the answers[index].Answer.
Try the following:
<% for (int i = 0; i < Model.Count; i++) { %>
<div>
<%= Html.Hidden("answer["+i.ToString() + "].Id", Model["+i.ToString() + "].Id) %>
<%= Model[i].Question %>
<%= Html.TextBox("answer["+i.ToString() + "].Answer", Model["+i.ToString() + "].Answer) %>
</div>
<% } %>
Richard
Take a look at this and this question. Also this blog post.
Edit : As for accessing the model in the view. Are you sure you declared your with the following attribute?
<%# Page Language="C#"
Inherits="System.Web.Mvc.ViewPage<List<Namespace.Question>>" %>
//Assuming the GetQuestions() method returns a list of question objects.
the answer is not to use the html helpers.
<% for (int i = 0; i < Model.Count; i++) { %>
<div>
<input type="hidden" name="answers[<%= i %>].Id" id="answers_<%= i %>_Id" value="<%= Model[i].Id %>" />
<input type="text" name="answers[<%= i %>].Answer" id="answers_<%= i %>_Answer" value="<%= Model[i].Answer %>" />
</div>
<% } %>
Not very pretty, but works. The important thing is that Name and Id need to be different.
Name is allowed to have "[", "]" but id isn't.
This is likely a very simple question with a straightforward answer but I'm something of a newbie when it comes to ASP.NET (MVC).
I am returning an address (in pieces) from my model. Some of components are null. Is there a simple or fluent-like way to check for that null value without a lot of extra code to determine whether or not to display the associated surrounding HTML (not just the value)?
Example:
<% foreach (var item in Model)
{ %>
<h3>
<%= Html.ActionLink(item.name, "Details", new { id = item.ID})%></h3>
<div>
<%= Html.Encode(item.address) %><br />
<%= Html.Encode(item.city) %>,
<%= Html.Encode(item.state) %>
<%= Html.Encode(item.zip) %>
</div>
<% } %>
In the above example, if there is a null value for item.address, I want the <br/> tag to be hidden as well so that only the city, state zip string is displayed.
I'm looking for something more elegant than just putting a <% if () { %> conditional out there. Thanks.
You could write an extension method for HtmlHelper that checked to see if it was null or not, and would output nothing if it was, or field + <br /> if it wasn't.
public static string FieldLine(this HtmlHelper helper, object value, bool addBr)
{
if (value == null)
{
return string.Empty;
}
else if (addBr)
{
return helper.Encode(value) + "<br />";
}
else
{
return helper.Encode(value);
}
}
Remember to import the namespace of your extension class into your View aspx. For this example, if my namespace was "MvcApplication1.Extensions", I would use
<%# Import Namespace="MvcApplication1.Extensions" %>
at the top of my View. Then to use it, it would simply be:
<%= Html.FieldLine(item.address, true) %>
<%= Html.FieldLine(item.city, true) %>
etc.
I'm adding another answer based on what womp described above.. I'd make the helper a bit more generic than he did, and still honor the origional Encode as well...
public static string EncodeWithHtml(this HtmlHelper helper, object value, string html)
{
if (value == null)
{
return string.Empty;
}
else
{
return helper.Encode(value) + html;
}
}
This would allow you to do something like:
<%= Html.EncodeWithHtml(item.address, "<br />") %>
or
<%= Html.EncodeWithHtml(item.address, "<img src=\"images\home.gif\"><br />") %>
Assuming item.address is a string...
<%= Html.Encode(item.address) %>
<% if (!string.IsNullOrEmpty(item.address)) { %>
<br />
<% } %>
of course, this is typed out in the little comment box, so be wary of spelling, case, etc, etc.
My question is similar to Engram's here, but my question goes a bit further. The way i intend it to work is I have a textbox asking how many entries a user is going to make. After they input the number, I need to create that many more textboxes to allow for entries (and then repeat the same process with those textboxes, but baby steps first...) I tried collecting the keys on the post, but it only returns the initial textbox asking for the number of entries. I'm still trying to get a grasp on MVC and the tutorials/videos so far don't delve this deep into it yet. Then again, I know this is probably something I could handle using JQuery, but I'd still be stuck in the same situation.
This is the controller I'm using:
[AcceptVerbsAttribute("POST")]
public ActionResult Create(int tbxNumberOfExercises)
{
ViewData["number"] = tbxNumberOfExercises;
foreach (var key in Request.Form.Keys)
{
string keyString = key.ToString();
if (keyString.StartsWith("tbox_exercise", StringComparison.OrdinalIgnoreCase))
{
string recNum = keyString.Substring(13, keyString.Length - 13);
string approvedKey = Request.Form["tbox_exercise" + recNum];
int number;
int.TryParse(approvedKey, out number);
}
}
return View("Create");
}
And this is my aspx:
<form action="/CreateWorkout/Create" method="post">
Number of Exercises:
<%= Html.TextBox("tbxNumberOfExercises") %>
<br />
<br />
<input type="submit" value="Set Exercise Number" />
</form>
<% if (ViewData["number"] != null)%>
There are this many:<%=Html.Encode(ViewData["number"])%>
<br />
and this line should show up
<% if (ViewData["number"] != null)
{
int max = (int)ViewData["number"];
for (int i = 0; i < max; i++)
{%>
<br />
<br />
<%= Html.TextBox("tbox_exercise" + i) %>
<% }
} %>
<% if (ViewData["s"] != null) %>
<%=Html.Encode(ViewData["s"]) %>
Is there something I'm overlooking, not comprehending, or should I quit while I'm at it because it seems like I'll never get it?
Thanks in advance for any help -- I'm just trying to learn as most I can.
I'd break this up in stages, you'll need to add a "Save" view someplace depending on what you want.
Scott
<form action="/Demo01/Create" method="post">
Number of Exercises:
<%= Html.TextBox("tbxNumberOfExercises") %>
<br />
<br />
<input type="submit" value="Set Exercise Number" />
</form>
<% if (ViewData["number"] != null) {%>
<form action="/Demo01/Save" method="post">
There are this many:<%=Html.Encode(ViewData["number"])%>
<br />
and this line should show up
<% if (ViewData["number"] != null) {
int max = (int)ViewData["number"];
for (int i = 0; i < max; i++) {%>
<br />
<br />
<%= Html.TextBox("tbox_exercise" + i) %>
<% }
} %>
<% if (ViewData["s"] != null) %>
<%=Html.Encode(ViewData["s"]) %>
<input type="submit" value="Save Exercises" />
<% } %>
</form>
And then in your controller something like this:
public class Demo01Controller : Controller {
public ActionResult Create() {
return View();
}
[AcceptVerbsAttribute("POST")]
public ActionResult Create(int tbxNumberOfExercises) {
ViewData["number"] = tbxNumberOfExercises;
return View("Create");
}
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Save() {
foreach (var key in Request.Form.Keys) {
string keyString = key.ToString();
if (keyString.StartsWith("tbox_exercise", StringComparison.OrdinalIgnoreCase)) {
string recNum = keyString.Substring(13, keyString.Length - 13);
string approvedKey = Request.Form["tbox_exercise" + recNum];
int number;
int.TryParse(approvedKey, out number);
}
}
return View(); // return/redirect to wherever you want
}
}
I would consider adding the textboxes client-side via javascript rather than posting back to the server to have the form redrawn, assuming that you can live with javascript as a requirement for using the application. If not, then #Scott's approach should work. As a matter of preference I would probably have the Save method take a FormCollection parameter rather than deal with the Request object directly.
The javascript solution would be to have a single textbox and a button to add another. The user could continue to add textboxes until they have enough.
The problem is that your </form> ending tag needs to come at the end of your view.
Try this modified view:
<form action="/CreateWorkout/Create" method="post">
Number of Exercises:
<%= Html.TextBox("tbxNumberOfExercises") %>
<br />
<br />
<input type="submit" value="Set Exercise Number" />
<% if (ViewData["number"] != null)%>
There are this many:<%=Html.Encode(ViewData["number"])%>
<br />
and this line should show up
<% if (ViewData["number"] != null)
{
int max = (int)ViewData["number"];
for (int i = 0; i < max; i++)
{%>
<br />
<br />
<%= Html.TextBox("tbox_exercise" + i) %>
<% }
} %>
<% if (ViewData["s"] != null) %>
<%=Html.Encode(ViewData["s"]) %>
</form>
I would recommend Scott's approach as far as best practice. This answer is about getting your exact scenario working.
Thanks for the help guys. I realized at 5 this morning that my issue is that the form didn't include the new textboxes / I needed another form. I'll have to seriously look into Javascript and actually modifying the DOM as it would be better to keep it client side.
Thanks so much.