razor throws an exception while rendering - c#

I have a razor statement:
#{
var renderColumn = new Action<OlapReportColumn>(col =>
{
if (col.IsSpaned)
{
#<th colspan="#col.Columns.Count">#col.Caption</th>;
}
});
}
Here is a code for render html table's header. So while I trying to call view I get an exception (translated from russian) as:
an operator can be used only assignment expression, call, increment, decrement and expectations
Here is a razor generated code part with an error:
var renderColumn = new Action<OlapReportColumn>(col =>
{
if (col.IsSpaned)
{
#line default
#line hidden
item => new System.Web.WebPages.HelperResult(__razor_template_writer => { // ERROR HERE!
BeginContext(__razor_template_writer, "~/Areas/Report/Views/ReportsOlap/ReportTableGenerator.cshtml", 324, 3, true);
Here a part of razor code called renderColumn
<table id="reportGrid">
<thead>
<tr>
#foreach (var h in report.Header)
{
renderColumn(h);
}
</tr>
What I am doing wrong in here?

The Action that you've defined is behaving just like any other method in C#. The line #<th colspan="#col.Columns.Count">#col.Caption</th>; is not simply output to the output stream; instead the compiler is seeing a statement that it doesn't understand resulting in the error:
CS0201: Only assignment, call, increment, decrement, await, and new object expressions can be used as a statement
In order to write the <th colspan="#col.Columns.Count">#col.Caption</th> to the output stream you can use the WriteLiteral method:
var renderColumn = new Action<OlapReportColumn>(col =>
{
WriteLiteral("<th colspan=" + col.Columns.Count + ">" + col.Caption + "</th>");
});
Perhaps more idiomatic than that though would be to use a Func that returns what you'd like to ouptut and then output that at the call site:
var renderColumn = new Func<OlapReportColumn, object>(col =>
{
return Html.Raw("<th colspan=" + col.Columns.Count + ">" + col.Caption + "</th>");
});
The call would then need to be changed very slightly to tell razor that you wish to output the results:
#foreach (var h in report.Header)
{
#(renderColumn(h))
}
Going further, there is built-in support for Funcs of this nature as described in this blog by Phil Haack. Using this method your call stays the same as the call just above but the Func becomes:
Func<OlapReportColumn, object> renderColumn2
= #<th colspan="#item.Columns.Count">#item.Caption</th>;
From Phil Haack's blog
Note that the delegate that’s generated is a Func<T, HelperResult>. Also, the #item parameter is a special magic parameter.

Try changing your HTML table header code from this :
#<th colspan="#col.Columns.Count">#col.Caption</th>;
to this :
#:<th colspan="#col.Columns.Count">#col.Caption</th>;
Insert that colon : immediately after # delimeter and check if this works.

Related

How do I create and manipulate a Table property for an IFC object using Xbim?

I am working off of the basic example for amending data (https://github.com/xBimTeam/XbimEssentials). The only thing I'm changing is within the code below, where I want to add an IfcPropertyTableValue instead of an IfcPropertySingleValue.
This code runs, but in XbimXplorer under the object's properties, nothing's there - it's blank.
To make sure, the example code, as well as other property types do work and do show up in Xplorer under properties.
var pSetRel = model.Instances.New<IfcRelDefinesByProperties>(r =>
{
r.GlobalId = Guid.NewGuid();
r.RelatingPropertyDefinition = model.Instances.New<IfcPropertySet>(pSet =>
{
pSet.Name = "Points";
// FOR EACH POINT i :
pSet.HasProperties.Add(model.Instances.New<IfcPropertyTableValue>(p =>
{
p.Name = "Points " + i;
// FOR EACH COORDINATE x :
p.DefiningValues.Add(new IfcText(x));
p.DefinedValues.Add(new IfcReal(-3.25));
}));
});
});
How can I make this work?
I have also tried using code to read the property, in case XbimXplorer just doesn't display tables. This code runs and prints zero lines (but works for other properties that are displayed in Xplorer):
// Try to read and print the new property
var nameObj = "my_object_name";
var checkObj = model.Instances.FirstOrDefault<IIfcBuildingElement>(d => d.Name == nameObj);
if (checkObj == null)
{
outputBox.AppendText(newLine + "Object: " + nameObj + " not found");
}
else
{
var properties = checkObj.IsDefinedBy
.Where(r => r.RelatingPropertyDefinition is IIfcPropertySet)
.SelectMany(r => ((IIfcPropertySet)r.RelatingPropertyDefinition).HasProperties)
.OfType<IIfcPropertySingleValue>();
foreach (var property in properties)
outputBox.AppendText($"Property: {property.Name}, Value: {property.NominalValue}");
}
It would also be convenient if I could add several defining/defined value pairs at once, for instance like this (similar to normal C# lists):
IEnumerable<IfcValue> definingValues = new IfcText() {"x", "y", "z", "k"};
p.DefinedValues.AddRange(definingValues);
IEnumerable<IfcValue> definedValues = new IfcReal() {0.0, 1.6, -2.5, 3.33};
p.DefinedValues.AddRange(definedValues);
However, {"x", "y", "z", "k"} is then marked with the error Cannot initialize type 'IfcText' with a collection initializer because it does not implement 'System.Collections.IEnumerable'.
I don't think xbim Xplorer displays IfcPropertyTableValues. Probably because very few BIM tools currently output TableValues so I guess it never got implemented (and you'd need to establish how to display the table in the Xplorer view - do you nest a table in a property grid?).
If you look at the Xplorer code you'll see it only supports SingleValues, ComplexValues and EnumeratedValues. You might be able to use Complex Values as a collection of multiple IfcPropertySingleValues as a workaround.
In your 2nd code sample to output the values, you're filtering out any IfcPropertyTableValues with the .OfType<IIfcPropertySingleValue>() clause so you'll never see the output. Take a look at the IFC specs for SimpleProperties: https://standards.buildingsmart.org/IFC/RELEASE/IFC4_1/FINAL/HTML/link/ifcsimpleproperty.htm
On the last question you're initializing the array with the wrong syntax. Try something like this:
var labels = new Ifc4.MeasureResource.IfcText[] { "x", "y" };
p.DefiningValues.AddRange(labels.Cast<IIfcValue>());

Pass list from cshtml view to jquery function

Im trying to pass List that is generated from the following function;
public List<long> GetIDs()
{
var ids = new List<long>();
foreach(var s in student)
{
ids.Add(s.ID);
}
return ids;
}
and passing the list through the razor view and access the list in the jquery.
Following is the CSHTML code :
<a href="#" class="color-blue" data-studentids = "#schoolLicense.GetIDs()" onclick="sendWelcomeEmail(this)">
and this is the jquery code where I want to access the list and do actions from the ids I get ;
function sendWelcomeEmail(elem) {
$.each($(elem).attr("data-studentids"), function (index, value) {
alert(index + ": " + value);
});
}
But I'm not getting the Ids from the list instead of that I'm getting error as
TypeError: invalid 'in' operand obj
var length = !!obj && "length" in obj && obj.length,
Can anyone please let me know where Im going wrong?
Your problem is because by outputting the List<string> returned from your GetIDs method you're just coercing it to a string. Therefore your output is:
<a href="#" data-studentids="System.Collections.Generic.List`1[System.Int64]" ...
To fix this you can Join() the data together and then split() it back in to an array in your JS. Try this:
public List<long> GetIDs()
{
return student.Select(s => s.ID).ToList();
}
<a href="#" class="color-blue" data-studentids="#String.Join(",", schoolLicense.GetIDs())">
$('.colour-blue').click(function(e) {
e.preventDefault();
$(this).data('studentids').split(',').forEach(v, i) {
console.log(i + ": " + v);
});
});
Note the simplified use of LINQ in the GetIDs method, and the use of the unobtrusive event handler in the JS.
You can amend the .colour-blue selector in the jQuery object to better match your needs. I simply used it in this example as it was the only relevant attribute on the element.

Webgrid: Cannot convert lambda expression to type 'object' because it is not a delegate type

Im trying to add in the webgrid column an html image with this code:
#model List<LoUCore.Models.Artifact>
#{
var grid = new WebGrid(Model);
List<WebGridColumn> column = new List<WebGridColumn>();
column.Add(new WebGridColumn { ColumnName = "Filepath", Header = "Sprite",Format = (x => #<text><img src="#x.Filepath"></img></text>) });
}
#grid.GetHtml(columns: grid.Columns(column.ToArray()))
But im getting the following error:
CS1660: Cannot convert lambda expression to type 'object' because it is not a delegate type
Any ideas?
You can't use embedded razor strings within the lambda expression, as #Alessandro D'Andra suggests you have to use x => "<img src='" + someString + "'></img>"; instead.
You might also have to wrap it all in an MvcHtmlString to prevent Razor from escaping the string once it is used by the formatter, but I don't know exactly how the formatter works - you have to try it.
I made a small test file to find out exactly what the Razor compiler would do with your code. This is the razor file:
#{
string someString = "somestring";
Func<object, object> a = x => "<text><img src='" + someString + "'></img></text>";
Func<object, object> b = x => #<text><img src="#someString"></img></text>);
}
The ASP.NET compiler creates this C# code out of it (only relevant parts included):
string someString = "somestring";
Func<object, object> a = x => "<text><img src='" + someString + "'></img></text>";
Func<object, object> b = x =>
#line default
#line hidden
item => new System.Web.WebPages.HelperResult(__razor_template_writer => {
BeginContext(__razor_template_writer, "~/Views/Home/Index.cshtml", 210, 4, true);
WriteLiteralTo(__razor_template_writer, "<img");
EndContext(__razor_template_writer, "~/Views/Home/Index.cshtml", 210, 4, true);
WriteAttributeTo(__razor_template_writer, "src", Tuple.Create(" src=\"", 214), Tuple.Create("\"", 231)
#line 7 "c:\temp\MvcApplication1\Views\Home\Index.cshtml"
, Tuple.Create(Tuple.Create("", 220), Tuple.Create<System.Object, System.Int32>(someString
#line default
#line hidden
, 220), false)
);
BeginContext(__razor_template_writer, "~/Views/Home/Index.cshtml", 232, 7, true);
WriteLiteralTo(__razor_template_writer, "></img>");
EndContext(__razor_template_writer, "~/Views/Home/Index.cshtml", 232, 7, true);
Using the embedded text syntax #<text> within the lambda expression creates obviously incorrect C# code. It is not smart enough to make the embedded tags part of the lambda expression, instead it breaks the lambda expression by inserting code to emit the embedded text right away.
Everything is possible, try this:
#model List<LoUCore.Models.Artifact>
#{
var grid = new WebGrid(Model);
}
#grid.GetHtml(columns: grid.Columns(
grid.Column("Filepath", header: "Sprite", format: x =>
((Func<dynamic, object>)(#<text><img src="#x.Filepath"></img></text>)).Invoke(x)
)
))
or, even shorter:
#grid.GetHtml(columns: grid.Columns(
grid.Column("Filepath", header: "Sprite", format: #<img src="#item.Filepath"></img>)))

Local variables in Runtime Compiled Expressions

In this slice of code i'm adding ColumnInfo's to a list,
In my view the getter expression passed to the ColumnInfo get's called upon my rows.
This all works fine except for the local variable "childt.Naam" that get's used in my lambda expression.
The runtime evaluation causes childt.Naam to always be the one of the last childt passed in the foreach.
How can I make sure these "local variables" get passed correctly to the expression
foreach (var childt in itemt.ItemTemplates)
{
columns.Add(new ColumnInfo<Item>(model => level(model).GetV(childt.Naam, model.Taal) + childt.Naam, childt.Naam, new TextPropertyRenderer(), editable));
}
Here's the relevant parts of constructor of the ColumnInfo class
public ColumnInfo(Expression<Func<TModel, object>> getter, string title, IPropertyRenderer renderer, bool editable = false)
{
this.renderer = renderer;
this.Editable = editable;
this.Getter = getter.Compile();
}
It is because of the deferred aspect of LINQ so you need to create separate space for values.
foreach (var childt in itemt.ItemTemplates)
{
var naam = childt.Naam;
columns.Add(new ColumnInfo<Item>(model =>
level(model).GetV(naam, model.Taal) + naam,
naam,
new TextPropertyRenderer(),
editable));
}
http://msdn.microsoft.com/en-us/library/bb882641.aspx
You are closing over the loop variable - just make local copy:
foreach (var childt in itemt.ItemTemplates)
{
var localChildt = childt;
columns.Add(new ColumnInfo<Item>(model => level(model).GetV(localChildt.Naam, model.Taal) + localChildt.Naam, localChildt.Naam, new TextPropertyRenderer(), editable));
}
Also see "Closing over the loop variable considered harmful"
This issue is known as 'access to modified closure'.
To fix it, create a local variable in the loop body, assign childt to it, and use it in the lambda expressions:
foreach (var childt in itemt.ItemTemplates)
{
var c = childt;
columns.Add(new ColumnInfo<Item>(model => level(model).GetV(c.Naam, model.Taal) + c, c.Naam, new TextPropertyRenderer(), editable));
}

returning a list from a mvc view

When I look in my DetailsReport(List<> filteredList) method, it's Count = 0. Is it possible to send a List to your controller or is my code bad?
#Html.ActionLink("Print Results to Report",
"DetailsReport",
new {filteredList = Model} )
Your code is bad. You can't pass a Model as part of the Route like that. You need to serialize your Model and send it as either part of the request payload or in the query string. Try something like this (not sure if my serialization is correct with the Razor syntax as I haven't used it):
#Html.ActionLink("Print Results to Report", "DetailsReport", null, new {#id = "printreport")
$(function() {
$('#printreport').click(function() {
e.preventDefault();
$(this).wrap('<form action="' + $(this).attr('href') + '?' +
$.param(#new JavaScriptSerializer().Serialize(Model)) +
'" style="display: inline" method="GET" />');
$(this).parent('form').submit();
});
});

Categories