I tried to add item.DetailPageURL which is an url to href in this view:
<h2>Search for #ViewBag.Keyword</h2>
#for(int i = 0; i < 13; i++)
{
var item = ViewBag.SearchedItems[i];
<div>
<h2>#item.Title</h2>
<img src="#item.SmallImage" alt="Image is missing"/>
<p>Items price:</p>
<p>#item.Price</p>
<p>#item.CurrencyCode</p>
<p><a href='#item.DetailPageURL'></a>Details</p>
<p>#item.CustomerReview</p>
</div>
<hr>
}
but it does not work. What am I doing wrong?
You are talking about the below line. Did you noticed that you have used single quote '. In which case, variable substitution won't happen actually.
<p><a href='#item.DetailPageURL'></a>Details</p>
You need to use double quote " like
<p>Details</p>
Also, I would cast the ViewBag.SearchedItems to it's actual type and use a foreach loop rather like below (assuming it's List<string> type)
foreach(var item in (List<string>)ViewBag.SearchedItems)
{
// add to the view
}
As You can see, the problem is in html.
<p>Details</p>
The link is active, but it isn't appear, because "Details" is out of tag.
Should be like this:
<p>Details</p>
Thank You in advance!
Related
Let's say I have an entity "Cars" with two fields "Brand" and "Model".
In a c# template, is it possible to dynamically get the name of the fields inside "Cars" to a list? The output would meed to be {"Brand", "Model"}.
Even further, is it possible to get the description and a specific translation of the field name and description?
Using Daniel's comments and circling around to just answer your original question, here it is simplified and things are split up a little to see the parts:
#inherits ToSic.Sxc.Dnn.RazorComponent
#using Newtonsoft.Json
#{
var myData = AsList(Data);
var myDatum = AsEntity(myData.First());
var myFieldNames = (myDatum.Type.Attributes as IEnumerable<dynamic>).Select(a => a.Name);
}
<pre>
myDatum.Type.Name = #myDatum.Type.Name
myFieldNames = #JsonConvert.SerializeObject(myFieldNames)
</pre>
Which then outputs just:
myDatum.Type.Name = Cars
myFieldNames = ["Name","Brand","Model"]
I think what you are trying to do is covered in the tutorials pretty well.
https://2sxc.org/dnn-tutorials/en/razor
In particular take a look at the LINQ examples; numbers 6, 7, and 8.
I #Joao
Basically you should check Jeremys answer for most of your question.
I believe you're also asking about showing the labels like Brand in the Razor using the field-label from the ContentType specs. This is possible, but it's a bit harder as it's not a common use case. So let me just point you in the right direction...
Each entity has a property called Type. In Razor you would get this using
var someType = AsEntity(yourThing).Type;
This is an IContentType https://docs.2sxc.org/api/dot-net/ToSic.Eav.Data.IContentType.html.
To get the properties and the names of them you would go to
var attr = someType.Attributes["TheName"];
which gives you an IContentTypeAttribute https://docs.2sxc.org/api/dot-net/ToSic.Eav.Data.IContentTypeAttribute.html
This has Metadata - so
var attr = someType.Attributes["TheName"].Metadata;
The metadata is an IMetadataOf https://docs.2sxc.org/api/dot-net/ToSic.Eav.Metadata.IMetadataOf.html
So using this you can find everything you want - but as you can see it's quite a hoop to jump through.
Here is a simple working example. I am sorta hoping Daniel chimes in and reveals an easy way to go from myType.Attributes and convert straight to a Json string??
Create a new View, Enable List, point it to your Cars Content-Type, fix the last few lines so that the "And the data..." part matches your CT's actual fields.
#inherits ToSic.Sxc.Dnn.RazorComponent
#{
var myData = AsList(Data);
var myType = AsEntity(myData.First()).Type;
var myFields = new List<string>();
foreach(var field in myType.Attributes) {
myFields.Add(field.Name);
}
}
<div #Edit.TagToolbar(Content)>
<h3>View Heading</h3>
<h4>Table (Content Type) Name: #myType.Name</h4>
<p>has the following fields</p>
<div class="d-flex flex-row bd-highlight mb-3">
#foreach(var field in myType.Attributes) {
<p class="p-2 bd-highlight"><strong>#field.Name</strong></p>
}
</div>
<h4>As a comma separated list?</h4>
<p>#string.Format("{{\"{0}\"}}", string.Join("\",\"", myFields))</p>
<h4>And the data...</h4>
#foreach(var cont in AsList(Data)) {
<div class="d-flex flex-row bd-highlight mb-3"
#Edit.TagToolbar(cont)>
<div class="p-2 bd-highlight">#cont.EntityId</div>
<div class="p-2 bd-highlight">#cont.Name</div>
<div class="p-2 bd-highlight">#cont.Brand</div>
<div class="p-2 bd-highlight">#cont.Model</div>
</div>
}
</div>
The Cars Content type with only 3 fields
And the output of the View looks like this
For an update on what I needed to achieve, this outputs the field name and the label for an easy loop:
var myData = AsList(App.Data["Stages"]);
var myDatum = AsEntity(myData.First());
var myFields = (myDatum.Type.Attributes as IEnumerable<dynamic>);
// var myFieldNames = myFields.Select(a => a.Name);
// var myFieldLabels = myFields.Select(a => (a.Metadata as IEnumerable<dynamic>).First().Title.TypedContents);
var myFieldNamesAndLabels = myFields.Select(i => new
{
i.Name,
(i.Metadata as IEnumerable<dynamic>).First().GetBestTitle()
});
If there is an easier way to achieve this, please let me know.
Thanks #Jeremy Farrance and #iJungleBoy
I'm trying to access the properties of a class dynamically in ASP.NET Razor when generating an HTML Table. This problem is normally easily solved with reflection, but the #Html.DisplayFor method is giving me issues.
I am attempting to generate an HTML table that has 3 cells per row, with the title of the item in bold as the first line of the cell, and the value of the item in the second line of the cell. The contents of the table should not include cells which are on the 'Excluded Fields' list, and I do not want to have to statically reference each column.
<table class="blpSecurityTable">
<tr>
#{
int _rowCount = 0;
foreach (var property in item.GetType().GetProperties())
{
#if (!Model.ExcludedFields.Contains(#property.Name))
{
dynamic test = #property.GetValue(item);
<td><b>#Html.DisplayFor(m => #property.Name)</b><br />#Html.DisplayFor(m => #test)</td>
_rowCount++;
}
#if (_rowCount % numCols == 0)
{
#:</tr><tr>
}
}
}
</tr>
</table>
I've tried calling #Html.DisplayFor(m => #property.GetValue(item)) but that just creates a runtime error. I can simply call #property.GetValue(item) and the value displays, but this is not ideal because I use display templates to do things like set dates to the ShortDateString format.
I understand that DisplayFor is using reflection to determine the type of the property, and that is why I am trying to use the dynamic variable to facilitate reflection for the method. However, when I run the method, it throws errors indicating the variable is not a generic parameter, and therefore cannot share its attributes. The resulting page has mostly blank values, and some cells filled in with unexpected descriptive information.
I feel like I'm getting close, but I don't know how to proceed. The page won't look right if I don't pass the values into an HTML display method, and I cannot think of any other way to get the type of table I want to be generated. Thoughts?
The issue is solved by creating a Display Template for the Security object, which then allowed me to properly use the #Html.Display Method, because the Model for the Display Template has an entry for the property.
Here is what the page code looks like now:
Display Template
#model Interface.Models.Security
#{int numCols = 3;}
<table class="blpSecurityTable">
<tr>
#{
int _rowCount = 0;
foreach (var property in Model.GetType().GetProperties())
{
#if (!BLPDLModel.ExcludedFields.Contains(#property.Name))
{
<td><b>#Html.DisplayFor(m => #property.Name)</b><br />#Html.Display(property.Name)</td>
_rowCount++;
}
#if (_rowCount % numCols == 0)
{
#:</tr><tr>
}
}
}
</tr>
</table>
Razor Page
#foreach (var item in Model.Security)
{
<div class="blpSecurityItem">
<button type="button" class="collapsible">{button text}</button>
#{
<div class="collapsible-content">
<hr />
#Html.DisplayFor(m => item)
</div>
}
</div>
Replace #Html.DisplayFor(m => #test) with #Html.Display(property.Name)
and your model property for date should have [DisplayFormat(DataFormatString = "{0:yyyy/MM/dd}")] or anything you like
<span class="text-danger">
#{
if (Model.uploadErrorMessage != null)
{
if (!string.IsNullOrEmpty(Model.uploadErrorMessage))
{
Model.uploadErrorMessage;
}
}
}
</span>
Given code above, how do I display Model.uploadErrorMessage inside <span> tag?
Currently it give me this error on Model.uploadErrorMessage:
Only assignment, call, increment, decrement, and new object expressions can be used as a statement
Put your span tag inside if statement
#{
if (Model.uploadErrorMessage != null)
{
if (!string.IsNullOrEmpty(Model.uploadErrorMessage))
{
<span class="text-danger">#Model.uploadErrorMessage</span>
}
}
}
All of those if statements are completely unnecessary anyway, you can simplify the entire thing to this:
<span class="text-danger">#Model.uploadErrorMessage</span>
use the "#"-symbol to write your variable value to the view:
for example:
<span>This is the error: #(Model.uploadErrorMessage)</span>
#Html.Raw(Model.uploadErrorMessage)
you can simply put #Model.uploadErrorMessage
Edit: XSS vulnerability fix, based on #DavidG's comment.
I was working on a project and came to a point where I need to do something like this:
foreach(var item in Model)
{
#:<div class="my-class"> this is some content </div>
#:<div class="my-other-class">this is some more content </div>
}
But, of course, it would be stupid to do it like this. So my question is:
-Is there a notation to escape several lines of html code like the example given above?
E.g.
#: <div>
<div>
<div> :#
Your help is much appreciated. Thanks :)
#{ <text><div></div></text> }
use <text></text>
Use #{ }
#{
<div>
<div>
<div>
}
Although you're not using the Razor engine for anything useful in this context, you could simply omit them.
As an alternative you could simply add an # before the foreach:
#foreach(var item in Model)
{
<div class="my-class"> #item.Property </div>
<div class="my-other-class">this is some more content </div>
}
You must use # only to scope access or delimit c-sharp/vb code in the view. Pure HTML can live alone as pure HTML, for exemple:
Delimits a valiable assignment:
#{
int x = 123;
string y = "because.";
}
Use the variable x into the code
<div>#x</div>
Use the variable y into the code
<div>#y</div>
Use x and y at the same time
#{
<div>#x</div>
<div>#y</div>
}
For:
#foreach(var item in Model)
{
<div class="my-class"> #item.SomeProperty </div>
<div class="my-other-class">this is some more content </div>
}
Ah yes, the problem was my structure. It's true that html code persists on its own.
The problem was I had a block in which I would insert a different starting tag according to a condition
if(cond) { insert <div> } else {insert other div}
And so I guess the CLR couldn't see the closing } .
Fixed it by putting the whole <div></div> structure inside the condition blocks.
Thanks for your replies tho, cheers!
I am using MVC + EF
I have a Feed xml file url that gets updated every 7 minute with items, every time a new item gets added I retrieve all the items to a list variable and then I add these varible to my database table. After that I fill a new list variable which is my ViewModel from the database table. Then I declare the ViewModel inside my view which is a .cshtml file and loop throught all of the objects and display them.
How can I make sure that the newest items get placed on the top and not in the bottom and also the numbers displays in correct order?
This is how I display the items inside my cshtml note that I use a ++number so the newest item needs to be 1 and so on ::
#model Project.Viewmodel.ItemViewModel
#{
int number = 0;
}
<div id="news-container">
#foreach (var item in Model.NewsList.OrderByDescending(n => n.PubDate))
{
<div class="grid">
<div class="number">
<p class="number-data">#(++number)</p>
</div>
<p class="news-title">#(item.Title)</p>
<div class="item-content">
<div class="imgholder">
<img src="#item.Imageurl" />
<p class="news-description">
#(item.Description)
<br />#(item.PubDate) |
Source
</p>
</div>
</div>
</div>
}
</div>
This is how I fill the viewmodel which I use inside the .cshtml file to iterate throught and display the items
private void FillProductToModel(ItemViewModel model, News news)
{
var productViewModel = new NewsViewModel
{
Description = news.Description,
NewsId = news.Id,
Title = news.Title,
link = news.Link,
Imageurl = news.Image,
PubDate = news.Date,
};
model.NewsList.Add(productViewModel);
}
If you check this image thats how it gets displayed with the numbers, thats incorrect.
If you see the arrows thats how it should be, how can I accomplish that?
Any kind of help is appreciated :)
note: When I remove .OrderByDescending, the numbers are correctly on each grid. But I need the .OrderByDescending beacuse i want the latest added item in the top.
Try this:
#model Project.Viewmodel.ItemViewModel
#{
int number = 0;
var NewsItems=Model.NewsList.OrderByDescending(n => n.PubDate).ToList();
}
<div id="news-container">
#foreach (var item in NewsItems)
{
<div class="grid">
<div class="number">
<p class="number-data">#(++number)</p>
</div>
<p class="news-title">#(item.Title)</p>
<div class="item-content">
<div class="imgholder">
<img src="#item.Imageurl" />
<p class="news-description">
#(item.Description)
<br />#(item.PubDate) |
Source
</p>
</div>
</div>
</div>
}
</div>
Looking at your sketch I assume you have float: left or display: inline-block for a grid class. Adding float: right might do the trick.
If that does not help please post CSS you have.
just a quick word..
you are passing NewsViewModel to the view and performing iteration on ItemViewModel ..y?
do u think this may be the cause of the problem..
Regards
You could sort your news list using the CompareTo method:
model.NewsList.Sort((a, b) => b.PubDate.Date.CompareTo(a.PubDate.Date));
Once you have the list sorted correctly, you can simply use CSS to display the news list two items per row. See this fiddle.
The fiddle is a revised one which was provided to me in a similar question I asked before.
Try this one
private void FillProductToModel(ItemViewModel model, News news)
{
var newList = list.OrderByDescending(x => x.News.Date).toList();
var productViewModel = new NewsViewModel
{
Description = newList .Description,
NewsId = newList .Id,
Title = newList .Title,
link = newList .Link,
Imageurl = newList .Image,
PubDate = newList .Date,
};
model.NewsList.Add(productViewModel);