I am confused as to how to implement the following in my current foreach:
#foreach
(var post in Model."table".Where(w => w.Private_ID == 1).OrderBy(o => o.Date))
{
<div class ="post">
<fieldset>
<p class="post_details">At #post.Post_Date By #post.Username</p>
#post.Post_Desc
</fieldset>
</div>
}
so that post.Username will NOT show if #post.anon is TRUE (and so that it will say "Anonymous")
Thanks in advance for any advice/help/suggestions.
You should be able to do something along the lines of:
#(post.anon ? "Anonymous" : post.Username)
Though I would consider doing most of this logic in the C#, rather than leaving it to the view (therefore, creating a specific view model with all of the logic already done. Meaning you can just loop through and not have to do any additional thinking:
#foreach(var post in Model.Posts)
{
<div class ="post">
<fieldset>
<p class="post_details">At #post.Post_Date By #post.Poster</p>
#post.Post_Desc
</fieldset>
</div>
}
Where #post.Poster in the above example is already preset with anonymous if it is required.
Try this:
#foreach(var post in Model."table".Where(w => w.Private_ID == 1).OrderBy(o => o.Date))
{
<div class ="post">
<fieldset>
<p class="post_details">At #post.Post_Date By (#post.Anon == true ? "Anonymous" : #post.Username)</p>
#post.Post_Desc
</fieldset>
</div>
}
EDIT: Sorry, the line should have said: #(post.Anon == true ? "Anonymous" : post.Post_Desc)
Related
I am unit testing a blazor app. I get a ElementNotFoundException. I think the cause for this is an if statement in the the index.razor page. see code below:
<div class="row">
<div class="col-12">
#if ((challenges != null) && (challenges.Count > 0))
{
<MultiStepComponent Id="MultiStepContainer" Challenges="#challenges">
<div class="row p-3">
<div class="col-6" id="challengeContainer">
#foreach(var c in challenges)
{
<MultiStepNavigation Name="#c.Title">
<h1>#c.Title</h1>
<img class="float-left" src="#c.ImagePath" width="200" />
#foreach(var sentence in c.Description)
{
<p>#sentence</p>
}
</MultiStepNavigation>
}
</div>
<div class="col-6">
<textarea rows="26" cols="120" #bind="input" id="input"></textarea>
<button class="btn" id="runBtn" #onclick="RunAsync">Run</button>
<br />
<textarea rows="10" cols="120" id="output" readonly>#((MarkupString)Output)</textarea>
</div>
</div>
</MultiStepComponent>
}
</div>
</div>
The code behind of this page (index.razor.cs) has the following initialization code:
protected override async Task OnInitializedAsync()
{
jsonRepository = new JSONChallengeRepository();
challenges = await jsonRepository.GetChallengesAsync();
}
The test for this page is here:
[Test]
public async Task Compile_code_Success()
{
_codingChallengeService.Setup(c => c.SendInputToCompilerAsync("50+50")).ReturnsAsync("100");
_testContext.Services.AddScoped(x => _codingChallengeService.Object);
var razorComponent = _testContext.RenderComponent<Index>();
razorComponent.Instance.challenges = GetChallenges();
if ((razorComponent.Instance.challenges != null) && (razorComponent.Instance.challenges.Count > 0))
{
var runBtn = razorComponent.FindAll("button").FirstOrDefault(b => b.OuterHtml.Contains("Run"));
var input = razorComponent.Find("#input");
input.Change("50+50");
runBtn.Click();
var outputArea = razorComponent.Find("#output");
var outputAreaText = outputArea.TextContent;
Assert.AreEqual("100", outputAreaText);
}
Assert.IsNotNull(razorComponent.Instance.challenges);
}
The #input is missing..Why??
Thanks in advance!
I am guessing the problem is that you do not cause the component under test to re-render when you assign razorComponent.Instance.challenges property/field, and if the component does not re-render, then the markup inside #if ((challenges != null) && (challenges.Count > 0)) block in the component is not displayed.
In general, dont mutate properties (parameters) of components through the razorComponent.Instance. If you really have to do so, make sure to trigger a render after.
Instead, pass parameters to the component through the RenderComponent or SetParametersAndRender methods, or through services injected into components. That will cause the component to go through its normal render life-cycle methods.
I am getting this error, I believe that it is because I have one two many Html.RenderAction(); on my home page. On top of a few #Html.Partial() My site is fine if I remove the last one I put in. Which is bringing up a list of Operating hours from a table. I have 2 on the footer one to show recent posts and the hours. The Footer is on a Partial page. In total there are Login Partial, Cart Summary -- RenderAction, and Footer Partial on the main Layout page plus of course RenderBody(). The Index page has 2 partials on it so far and was planning on using more. All of these have models to tables attached to them from tables. I get the error on the login Partial and sometimes on the blog posts. If I comment out the one I just made it works fine. And navigating to the page itself works with no errors. At first I had it as a partial page in the shared folder and it just doesn't show up. I have done all of these,
public ActionResult _Hours()
{
var hrs = db.OperatingHours.OrderBy(x => x.SortOrder).ToList();
return PartialView(hrs);
}
public ActionResult Hours()
{
var hrs = db.OperatingHours.OrderBy(x => x.SortOrder).ToList();
return View(hrs);
}
Is there a work around for this?
The issue was as David mentioned above in the comments, I had an infinite loop in my view code. Below is how it originally was written.
<ul class="list-border">
#foreach (var item in Model)
{
<li class="clearfix">
#if (item.SelOpen == true)
{
<span>#if (item.ShowSelect == true)
{<text>Select</text>} #item.Day : </span>
<div class="value pull-right flip"> #item.OpenTime #if (item.ShowBreak == true)
{<text>-</text> #item.BreakTime} - #item.CloseTime </div>
}
#if (item.SelClosed == true)
{
<span> #item.Day : </span>
<div class="value pull-right flip"> Closed </div>
}
</li>
}
</ul>
Below is the code that it was changed to for it to work.
<ul class="list-border">
#foreach (var item in Model)
{
<li class="clearfix">
#if (item.SelOpen == true)
{
<span>#if (item.ShowSelect == true)
{<text>Select</text>} #item.Day : </span>
<div class="value pull-right flip"> #item.OpenTime #if (item.ShowBreak == true)
{<text>-</text> #item.BreakTime} - #item.CloseTime </div>
}
else
{
<span> #item.Day : </span>
<div class="value pull-right flip"> Closed </div>
}
</li>
}
</ul>
I normally code the above way with `if(){ }else{}. And I guess I have done it the other way as well, but not in a foreach loop.. Lesson learned.
I'm trying to understand how to make partial views. So far I have the following for the partial view, called "_News":
#model Site.Services.News.NewsItem
<div class="bs-callout bs-callout-primary">
<h2>
#Html.DisplayFor(model => item.Title)
</h2>
</div>
And then in the controller I have:
#model IEnumerable<Site.Services.News.NewsItem> - Does this belong here?
...other controller code here...
#foreach(var item in Model)
{
Html.Partial("_News", item);
}
But I'm getting "NullReferenceException" when I try to run the application. What am I doing wrong?
Edit as per comments:
public ActionResult Index()
{
NewsReader newsReader = new NewsReader();
var newsItems = newsReader.GetNewsItems();
return View(newsItems);
}
#model Site.Services.News.NewsItem
<div class="bs-callout bs-callout-primary">
<h2>
#Html.DisplayFor(model => model.Title)
</h2>
</div>
There is a syntax error in your code, it should read model.Title, not item.Title because you are referring to the the model as model in the lambda expression.
I.e. this is the same:
#Html.DisplayFor(x => x.Title)
EDIT:
You also need to put an # symbol before the Html.Partial
#foreach(var item in Model)
{
#Html.Partial("_News", item);
}
See: Html.Partial not rendering partial view
Okay so my mistake, I've solved it, I was missing # on the Html.Partial!
I was surprised that this didn't throw up either an exception or actually display "Html.Partial..." as actual text.
Anyone know why neither of these were the case? If it's not thrown an exception then why wasn't it displayed as plain text?
I have an MVC controller where the model on the post method always comes back as null. I'm not sure if this is because I am using a partial view within the form.
Any idea why the model is not being returned to the controller?
Model
Loading the model
public List<Group> GetStaticMeasures(int businessUnitID)
{
List<Group> groups = ctx.Groups
.Include("Datapoints")
.Where(w => w.BusinessUnitID.Equals(businessUnitID))
.OrderBy(o => o.SortOrder).ToList();
groups.ForEach(g => g.Datapoints = g.Datapoints.OrderBy(d => d.SortOrder).ToList());
return groups;
}
Controller
public ActionResult Data()
{
ViewBag.Notification = string.Empty;
if (User.IsInRole(#"xxx\yyyyyy"))
{
List<Group> dataGroups = ctx.GetStaticMeasures(10);
return View(dataGroups);
}
else
{
throw new HttpException(403, "You do not have access to the data.");
}
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Data(List<Group> model)
{
ViewBag.Notification = string.Empty;
if (User.IsInRole(#"xxx\yyyyyy"))
{
if (ModelState.IsValid)
{
ctx.SaveChanges(model);
ViewBag.Notification = "Save Successful";
}
}
else
{
throw new HttpException(403, "You do not have access to save the data.");
}
return View(model);
}
Main view
#model List<Jmp.StaticMeasures.Models.Group>
<div class="row">
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
<div class="large-12">
<div class="large-8 large-centered columns panel">
#foreach (var g in #Model)
{
<h2>#g.Name</h2>
foreach (var d in g.Datapoints)
{
#Html.Partial("Measures", d)
}
<hr />
}
<input type="submit" class="button" value="Save Changes"/>
</div>
</div>
}
</div>
Partial View
#model Jmp.StaticMeasures.Models.Datapoint
#Html.HiddenFor(d => d.ID)
#Html.HiddenFor(d => d.Name)
#Html.HiddenFor(d => d.SortOrder)
#Html.DisplayTextFor(d => d.Name)
#Html.EditorFor(d => d.StaticValue)
#Html.ValidationMessageFor(d => d.StaticValue)
Rendered Html showing consecutive IDs
As you've rightly noted, this is because you're using a partial. This is happening because Html.Partial has no idea that it's operating on a collection, so it doesn't generate the names for your form elements with your intention of binding to a collection.
However, the fix in your case appears to be fairly straightforward. Rather than using Html.Partial, you can simply change your partial into an EditorTemplate and call Html.EditorFor on that template instead. Html.EditorFor is smart enough to know when it's handling a collection, so it will invoke your template for each item in the collection, generating the correct names on your form.
So to do what you need, follow these steps:
Create an EditorTemplates folder inside your view's current folder (e.g. if your view is Home\Index.cshtml, create the folder Home\EditorTemplates). The name is important as it follows a convention for finding templates.
Place your partial view in that folder. Alternatively, put it in the Shared\EditorTemplates folder.
Rename your partial view to Datapoint.cshtml (this is important as template names are based on the convention of the type's name).
Now the relevant view code becomes:
// Note: I removed # from Model here.
#foreach (var g in Model)
{
<h2>#g.Name</h2>
#Html.EditorFor(m => g.DataPoints)
<hr />
}
This ensures the separation of your views, as you had originally intended.
Update per comments
Alright, so as I mentioned below, the problem now is that the model binder has no way of associating a DataPoint with the correct Group. The simple fix is to change the view code to this:
for (int i = 0; i < Model.Count; i++)
{
<h2>#Model[i].Name</h2>
#Html.EditorFor(m => m[i].DataPoints)
<hr />
}
That will correctly generate the names, and should solve the model binding problem.
OP's addendum
Following John's answer I also included the missing properties on the Group table as HiddenFor's which game me the model back on the post.
#for (int i = 0; i < Model.Count(); i++)
{
#Html.HiddenFor(t => Model[i].ID)
#Html.HiddenFor(t => Model[i].BusinessUnitID)
#Html.HiddenFor(t => Model[i].SortOrder)
#Html.HiddenFor(t => Model[i].Name)
<h2>#Model[i].Name</h2>
#Html.EditorFor(m => Model[i].Datapoints)
<hr />
}
Update 2 - Cleaner solution
My advice for using an EditorTemplate for each DataPoint also applies to each Group. Rather than needing the for loop, again sprinkling logic in the view, you can avoid that entirely by setting up an EditorTemplate for Group. Same steps apply as above in terms of where to put the template.
In this case, the template would be Group.cshtml, and would look as follows:
#model Jmp.StaticMeasures.Models.Group
<h2>#Model.Name</h2>
#Html.EditorFor(m => m.DataPoints)
<hr />
As discussed above, this will invoke the template for each item in the collection, which will also generate the correct indices for each Group. Your original view can now be simplified to:
#model List<Jmp.StaticMeasures.Models.Group>
#using (Html.BeginForm())
{
// Other markup
#Html.EditorForModel();
}
Binder can't bind to list of objects if it is returned like this. Yes, partial is your problem. You need to specify a number within your form for ID's.
Do something like this:
// pseudocode
#model List<Jmp.StaticMeasures.Models.Group>
<div class="row">
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
<div class="large-12">
<div class="large-8 large-centered columns panel">
for(int i; i<Model.Count; i++)
{
<h2>#g.Name</h2>
#Html.HiddenFor(d => Model[i].Id)
#Html.HiddenFor(d => Model[i].Name)
#Html.HiddenFor(d => Model[i].SortOrder)
#Html.DisplayTextFor(d => Model[i].Name)
#Html.EditorFor(d => Model[i].StaticValue)
#Html.ValidationMessageFor(d => Model[i].StaticValue)
<hr />
}
<input type="submit" class="button" value="Save Changes"/>
</div>
</div>
}
</div>
See more details about binding to a list in Haack's blog
You are getting a null model because of the way the model binder handles collections.
Your partial view is rendering those inputs as for example:
<input type="hidden" name="ID" value="1"/>
...
And then repeating that for each entry in your List<Group>. Unfortunately the model binder won't know how to handle that and you'll get a null value.
The way your inputs have to look is:
<input type="hidden" name="groups[0].ID" value="1"/>
...
<input type="hidden" name="groups[1].ID" value="2"/>
There can't be a break in the numbering. One way to get this is to rewrite the way you use the Html.xxxFor methods, e.g.: iterate over the list and do this:
#Html.HiddenFor(d => Model[i].Id)
Here are two resources that explain this in detail and provide yet other examples of how to make the model binder work with collections:
http://www.hanselman.com/blog/ASPNETWireFormatForModelBindingToArraysListsCollectionsDictionaries.aspx
http://haacked.com/archive/2008/10/23/model-binding-to-a-list.aspx/
I'm using a generic Razor view to allow any entity framework object to be edited. Here's a cut down version of it:
#model Object
#using (Html.BeginForm())
{
#foreach (var property in Model.VisibleProperties())
{
#Html.Label(property.Name.ToSeparatedWords())
#Html.Editor(property.Name, new { #class = "input-xlarge" })
}
}
And the VisibleProperties() function goes like this:
public static PropertyInfo[] VisibleProperties(this Object model)
{
return model.GetType().GetProperties().Where(info =>
(info.PropertyType.IsPrimitive || info.PropertyType.Name == "String") &&
info.Name != model.IdentifierPropertyName()).OrderedByDisplayAttr().ToArray();
}
(I'm reusing code from https://github.com/erichexter/twitter.bootstrap.mvc/)
One of my sample controllers goes as follows:
public ActionResult Edit(int id = 0)
{
TaskTemplate tasktemplate = db.TaskTemplates.Single(t => t.TaskTemplateID == id);
return View(tasktemplate);
}
Now the problem:
It all works fine except for where there's an ID property that relates to a 'parent' table, such as UserID. For these fields, the output of the #Html.Editor is simply:
FalseFalseFalseTrueFalse.
The True seems to correspond to the user in question - in this case the 4th user in the database.
Why is it not ouputting a nice textbox with the number 4 (or whatever the UserID) is in it?
I hope I've explained this clearly.
The reason for that is because editor/display templates are not recursing into complex child objects. If you want this to happen you could write a custom editor template for the object type (~/Views/Shared/Object.cshtml) as illustrated by Brad Wilson in this blog post (more specifically the Shallow Dive vs. Deep Dive section towards the end).
So:
<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">
#Html.Editor(prop.PropertyName)
#Html.ValidationMessage(prop.PropertyName, "*")
</div>
</td>
</tr>
}
}
</table>