-
Notifications
You must be signed in to change notification settings - Fork 64
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
XML literals Enhancements to be more supportive to Vazor: the built-in VB syntax Razor! #397
Comments
Finally: A working VB.NET ASP.NET MVC Core Razor sample! services.AddTransient(Of IConfigureOptions(Of MvcViewOptions), VBRazor.VBRazorMvcViewOptionsSetup)()
services.AddSingleton(Of IViewEngine, VBRazor.VBRazorViewEngine)() The VBRazor is just a VB class that implements the IVBRazor Interface: Public Interface IVBRazor
ReadOnly Property Razor As String
End Interface The Razor property uses the xml literals to compose the HTML code and returns it as a string.. Example: Imports VbRazor
Public Class IndexView
Implements IVBRazor
Dim students As List(Of Student)
Public Sub New(students As List(Of Student))
Me.students = students
End Sub
Public ReadOnly Property Razor As String Implements IVBRazor.Razor
Get
Dim x = <html>
<h3> Browse Students</h3>
<p>Select from <%= students.Count() %> students:</p>
<ul>
<%= (Iterator Function()
For Each std In students
Yield <li><%= std.Name %></li>
Next
End Function)() %>
</ul>
</html>
Return x.ToString()
End Get
End Property
End Class
To use the IndexView from the Controller, I passed it to the View method as the model data in the action method, and passed the actual model data to its constructor: Public Function Index() As IActionResult
Return View(New IndexView(Students))
End Function That’s all!! If you run the project, you will see this web page: This was really easy, but needs more work, so I hope you start contribute to this project to make it a real productive tool! The second thing to do, is to add intellisense support for html attributes in xml literals in VB! We need to try more advanced pages with JavaScript and other components. I hope VB team give us the support wee need to make the most of xml literals. |
This is AWESOME! Now I might actually take some time to wrap my head around Razor (when, of course, I can come up from air from my current workload). ;-) |
@DualBrain |
This is a really interesting direction. My main question at this point is what benefit the infrastructure of Razor Views brings? If you took the base concept, using XML Literals instead of .cshtml, and didn't try to use any of the rest of the pattern, could you get an even simpler version of MVC. For example, could you give the .vb file responsibility for calling a common subroutine for header and footer (possibly with a delegate for the body), then the call from the controller is a simple method call - and it becomes just a matter of naming conventions (rather than physical file location) how you find the view. In the simplest case, the view just has a unique name. I'm explaining that to clarify that it is a serious question to ask what any part of razor brings to an XML literal based approach - beyond the controller routing and action results. Or put another way - can an XML Literal approach be better/simpler than Razor. (and regardless, I think there may be benefit in not calling it Razor as that is often associated with the interpretation of pages, which invites an unnecessary comparison). |
Thanks @KathleenDollard |
There is a strange behaiviour of XElement: It is implicit conversion operator returns XElement.Value not XElement.ToString( ). This gives unexpected results such in this example: Dim s = <p>Test</p>
Console.WriteLine(s) ' <p>Test</p>
Dim s2 As String = s
Console.WriteLine(s2) 'Test I fall into this in the Razor property, when I accidentally erased the .ToString() from the end of return statement, and got a plain text in the html page! Public Interface IVBRazor
Property ViewBag As Object
Property ModelState As ModelStateDictionary
Function Razor() As XElement
End Interface I added two new properties to pass the ViewBag and ModelState to the View This is the modified VBRazorView.RenderAsync Public Async Function RenderAsync(ByVal context As ViewContext) As Task Implements IView.RenderAsync
Dim vbRazor = CType(context.ViewData.Model, IVBRazor)
vbRazor.ViewBag = context.ViewBag
vbRazor.ModelState = context.ModelState
Dim r = Await Task.Run(AddressOf vbRazor.Razor.ToString).ConfigureAwait(False)
context.Writer.Write(r)
End Function And this is the IndexView Class: Imports Microsoft.AspNetCore.Mvc.ModelBinding
Imports VbRazor
Public Class IndexView
Implements IVBRazor
Dim students As List(Of Student)
Public Sub New(students As List(Of Student))
Me.students = students
End Sub
Public Property ViewBag As Object Implements IVBRazor.ViewBag
Public Property ModelState As ModelStateDictionary Implements IVBRazor.ModelState
Public Function Razor() As XElement Implements IVBRazor.Razor
ViewBag.Title = "VB Razor Sample"
Return _
<html>
<h3> Browse Students</h3>
<p>Select from <%= students.Count() %> students:</p>
<ul>
<%= (Iterator Function()
For Each std In students
Yield <li><%= std.Name %></li>
Next
End Function)() %>
</ul>
<script>
var x = 5;
document.writeln("students count = <%= students.Count() %>");
</script>
</html>
End Function
End Class The I didn't upload these changes to the repo, since I am still experimenting. |
I made changes to use a layout View Class.. I used the same layout provided in the C# project template to create this layout class Things are easy. The layout has a Body property that uses to insert the body in the XML representation of the layout: <main role="main" class="pb-3">
<%= Body %>
</main> It is the job of each View to render itself as the body of the layout. This is done by the RenderView function: Public Function RenderView() As XElement Implements IRazor.RenderView
Dim layout As New LayoutView(Razor(), ViewBag, ModelState)
Return layout.Razor
End Function Note: We need a command to create new View with the basic code like this one: Imports Microsoft.AspNetCore.Mvc.ModelBinding
Imports VbRazor
Public Class <<ViewName>>
Implements IRazor
Dim <<ModelVarName>> As <<ModelType>>
Public Sub New(<<ModelVarName>> As <<ModelType>>)
Me.<<ModelVarName>> = <<ModelVarName>>
End Sub
Public Property ViewBag As Object Implements IRazor.ViewBag
Public Property ModelState As ModelStateDictionary Implements IRazor.ModelState
Public Function RenderView() As XElement Implements IRazor.RenderView
Dim layout As New LayoutView(Razor(), ViewBag, ModelState)
Return layout.Razor
End Function
Public Function Razor() As XElement Implements IRazor.Razor
ViewBag.Title = "VB Razor Sample"
Return _
<div>
' Write you vbxml view here
</div>
End Function
End Class Note That I had to add a div or p tag to contain the vbxml code. I want to avoid this if possible but have no ideas now. The same can be done to generate the prototype for the layout class. @KathleenDollard Now, the output page is shown like this: There is more work to do to render sections and other things. I will see also why home and privacy are not shown as links, but now I need to sleep :) |
I want to use a new approach. I can generate a simplified cshtml file from the vbxml code , so the C# Razor an carry out all the work as usual! This will: Public Function Vazor() As XElement
ViewBag.Title = "VB Razor Sample"
Return _
<vazor>
<h3> Browse Students</h3>
<p>Select from <%= students.Count() %> students:</p>
<ul>
<%= (Iterator Function()
For Each std In students
Yield <li><%= std.Name %></li>
Next
End Function)() %>
</ul>
<script>
var x = 5;
document.writeln("students count = <%= students.Count() %>");
</script>
</vazor>
End Function I want to generate this code inside the view class: Dim Vazor_Value1 As String = students.Count().ToString()
Dim Vazor_Value2 = <vb_xml>
<%= (Iterator Function()
For Each std In students
Yield <li><%= std.Name %></li>
Next
End Function)() %>
</vb_xml>.ToString().
Replace("<vb_xml>" + vbCrLf, "").
Replace(vbCrLf + "</vb_xml>", "")
Public Sub PassData()
ViewBag.Vazor_Value1 = Vazor_Value1
ViewBag.Vazor_Value2 = Vazor_Value2
End Sub and PassData() must be called somewhere (say at the end of the constructor of the view class).
@KathleenDollard , @AnthonyDGreen |
@VBAndCs Do you imagine the vb and cs code existing in the same project? That is probably not going to work. To answer some specific questions, you could use a custom build task as part of the build - but this won't happen as part of design time build - no IntelliSense. I would try to avoid parsing the Vazor code yourself if you can just use the XML. Generating code and adding to an existing code file during the build is tricky - has anyone else done that? It's been a super long time as most of my generation was prior to build in a separate step. What problems did you find with the straight XML Literal approach? I imagined that we could hand back HTML and bypass large parts of the Razor complexity. Of course with some pretty involved headers and such. |
@KathleenDollard
Typically, they are not in the same project, because C# in cshtml files is just a script and Razor (which is another project) is responsible for compiling it. This already working and I tried it :
Yes, I want to do this. Or, it can be done after saving the changes of each view class (that is free of bugs), so we grantee that the chml files are in place b4 the build starts.
1- Need to resolve paths to js and image files and other components. |
As a heavy user of XML literals I definitely support this suggestion 👍 👍 though I am not yet sure about the specifics. For example, the Dim TaskInfoEntries =
<order-summary-tasks>
<%= From task In Me.GetPendingTasks() Select task.ToXML() %>
</order-summary-tasks> By combinining LINQ queries and function calls you can express pretty much anything you want. Since XML literals already support taking collections of However, the Some food for thought! |
@ericmutta <ul>
<%= (Function()
Select Case Model.Count
Case 1
Return <li>One Item</li>
Case 2
Return <li>Two Items</li>
Case Else
Return <li>Many Items</li>
End Select
End Function)() %>
</ul> So, there is nothing you can'y do. |
I'll just note that #109 (Block expressions) from a while ago seems to fit in well with some of the ideas here. |
@gilfusion |
I saw this chtml
So, I tried to write it with xml literals. I got this: Dim value = "Value 1"
Dim html = GetVazorContent(
<Vazor>
<p><%= value %></p>
<%= (Function()
value = "Value 2"
Return ""
End Function)() %>
<p><%= value %></p>
</Vazor>) It is a long workaround just to set a value inside xml! <Vazor>
<p><%= value %></p>
<%= value = "Value 2"
Return ""
%>
<p><%= value %></p>
</Vazor>) It is still feels strange! <Vazor>
<p><%= value %></p>
<%= Let
value = "Value 2"
End Let %>
<p><%= value %></p>
</Vazor>) And when it is just one statement, we can do it in one line as in LinQ: <Vazor>
<p><%= value %></p>
<%= Let value = "Value 2"%>
<p><%= value %></p>
</Vazor>) Or another thought: to be compatible with <Vazor>
<p><%= value %></p>
<%= Set
value = "Value 2"
End Set %>
<p><%= value %></p>
</Vazor>) And when it is just one statement: <Vazor>
<p><%= value %></p>
<%= Set value = "Value 2"%>
<p><%= value %></p>
</Vazor>) |
Another issue with Xaml literals, is that it doesn't allow write the & even inside quotes!
I hope to allow escaping & like this & or any other way. Surprise:
|
can you give more context? I don't do much XAML, but I do a fair bit of XML, and I'd normally expect the XML for that to look more like |
Ok. VB accepts
But it worked! The |
But, there is still a problem:
but inside quotes, the string vb_amp must inserted directly not because |
VB XML literals are following XML character entities, which is a restricted list and doesn't include things like HTML defines a bunch of extra ones like |
@jasonmalinowski |
So the bigger problem is that VB XML literals are only XML 1.0, and don't support DTDs, and what you're after is adding HTML5 support (the HTML5 DTD) to your XML. I would say that there is a good argument for expanding VB's XML literals to support this, but that may require expanding LINQ to XML to support them properly (LINQ to XML has only limited DTD support). StackOverflow was helpful here Interestingly, good 'ol System.Xml has it - an XmlEntityReference class. I guess the System.Xml.Linq folks didn't see the value . But then I think LINQ was focused on reading XML, not writing it, whereas System.Xml was from the days when XML was still cool and so needs to do both. |
@pricerc |
I prefer good solutions to cheap ones. Expanding XML support to include DTD's would be a much more powerful option and allow the XML literal to 'pretty print' any markup built on top of XML, including HTML5. I just don't know 1) if it would need LINQ-to-XML expanded, or if there another workaround (e.g. deserializing any DTD separately) and 2) how the compiler converts from literals to LINQ-to-XML, so I don't know how much work there is. To be honest, I think &&& is awful. I don't want to be counting punctuation when figuring out code. If there were going to be a 'cheap' solution, I think I'd prefer to see any XML identity that literals don't understand 'ignored'. So & > > ' and " are understood and handled. Anything else that looks like an XML IDENTITY should be ignored (e.g. © , etc) and passed through for serialization (which is presumably what's happening to your code). But once again, I don't know how much work is involved. I'd love to start digging into the compiler myself, but unfortunately, being self-employed means that earning takes precedence over learning, and I don't currently have the luxury of time to commit to learning a whole new set of tricks. (edit: fixed those identities!) |
If someone has the time to put together a proposal on this, that would be very helpful. I'm not sure we need full DTD support, but understand the request for more than a one-off solution for ampersand. Is it only literals (where a "don't escape this" gesture might be enough). Is there more in XHTML that will affect the XML literal MVC ("Vazor") work. Doesn't need to be fancy, just the start of a list of problems like &xcopy |
I am excited to announce that: My Work Is Complete! To use the Vazor IndexView calss, we map it in the Home.Index acctiom method and pass the unique name generated by the mapper to the view method like this: and that is all! This image shows the rendered Page resulted of:
I will wrap somethings up and publish this work at a new repo called Vazor. It seems like a few lines of code, but it is a big step for VB.NET : ) @KathleenDollard So, the next level is to work on xml and intellisense. This is where I can't go on alone. I need the help of the team an community. |
Vazor 1.0: |
I moved the vxml code to a pratial class to be in its own file, whicvh I named IndexView.vbxml.vb Partial Public Class IndexView
Private Function GetVbXml() As XElement Implements Vazor.IVazorView.GetVbXml
ViewBag.Title = "Vazor Sample"
Return _
<vbxml>
<h3> Browse Students</h3>
<p>Select from <%= students.Count() %> students:</p>
<ul>
<%= (Iterator Function()
For Each std In students
Yield <li><%= std.Name %></li>
Next
End Function)() %>
</ul>
<script>
var x = 5;
document.writeln("students count = <%= students.Count() %>");
</script>
</vbxml>
End Function
End Class I wanted to put the signature of the GetVbXml method in the other prat of the class, but I figured out that partial functions are now allowed! Why? Anyway, this is not the only thing I want. I want to be able to insert the mehod body alone in the partial file without the header, So, we can have a pure vbxml code, while VB still treats it as the code of the body of the GetVbXml function! This is how I want to see in the Indexview.vbxml.vb file: <vbxml>
<h3> Browse Students</h3>
<p>Select from <%= students.Count() %> students:</p>
<ul>
<%= (Iterator Function()
For Each std In students
Yield <li><%= std.Name %></li>
Next
End Function)() %>
</ul>
<script>
var x = 5;
document.writeln("students count = <%= students.Count() %>");
</script>
</vbxml> and this is what the how should the function look like: Private Function GetVbXml() As XElement Implements Vazor.IVazorView.GetVbXml
ViewBag.Title = "Vazor Sample"
Return Partial "IndexView.vbxml.vb"
End Funcrion In fact this new use of Partial keyword can be generalized as Partial " code file that contains the Expression" so we can use it anywhere in the class, such as: The implementation should be easy: the compiler just substitute the partial part with the expression. But the work needed is to make the editor treat the partial file as a part of the main class in design time. |
Vazor processes the view in two stages: In fact, we can add a third stage in the middle, to do ant thing we want with the html page. for example, I decided to add this sort of data templates, which uses the model as the data source by default: <ul>
<li ForEach="m">
<p>Id: <m.Id/></p>
<p>Name: <m.Name/></p>
<p>Grade: <m.Grade/></p>
</li>
</ul> Note: you can use any var name other than m. The above code is a shortcut for: <ul>
<%= (Iterator Function()
For Each std In students
Yield <li>
<p>Id: <%= std.ID %></p>
<p>Name: <%= std.Name %></p>
<p>Grade: <%= std.Grade %></p>
</li>
Next
End Function)() %>
</ul> I manage to do it by using Reflection in the function ParseTemplate: ParseTemplate is an extension method to the XElement class, and it only receives the model object (I assume it is an IEnumerable(Of T)), so, It can be used as this in our IndexView class in the sample project: In Vazor, we have endless possibilities to add any features we want in the middle stage, to make vbxml code easier and more powerful. So, if you have any ideas, please share it. |
@DualBrain @KathleenDollard @ericmutta @gilfusion @jasonmalinowski I didn't use the virtual IFileProvider, because the routing system used in Razor pages opens the cshtml pages directly to get info about the model class! @page
@model IndexModel
<div>
@Html.Raw(Model.VbXml)
</div> And that is all! Note: The start pages and layout must be pure cshtml. any part of them that contains C# code can be moved to a section or a partial view hence we can use the vbxml trick with it. So, here is the fact: Vazor is just a technique not a real app! Now You can start to use VB.NET Web apps in production, and write pages with vbxml code! All we need is a template for VB MVC Core and VB Razor Pages apps. And I hope VB team provide pull some strings with the ASP.NET Core team to add it in VS.NET 2019. This vbxml technique is built on the existing Razor, and will benefit from any future development of it (like Blazor), without adding any cost or effort on the ASP team! There will be some coast on VB team of course if they agreed to enhance xml literals, which is not a pressing matter at the moment, because every thing is already up and running! VB was a real player on .net core from the beginning, but the coach didn't see his potential and kept away with no reason at all! |
Using @page
@model IndexModel
<div>
@Html.Partial(Model.ViewName + ".cshtml")
</div> Note that I have to map the each vbxml instance to have a unique name, which means that the partial view has no static name. So I put name returned by the mapper in the ViewName property, in the OnGet method of the IndexModel class: Public Class IndexModel : Inherits PageModel
Public Property ViewName As String
Public Sub OnGet()
Dim iv = IndexView.CreateInstance(Students, ViewData)
ViewName = Vazor.VazorViewMapper.Add(iv)
End Sub
End Class Note I reported the unexpected behavior of the Razor Pages with the razor pages, and hope they solve this issue. Now, we can write MVC and Razor Pages apps using VB.NET, and create the views in three ways:
This is the most flexible view engine ever, yet it came out of the box! |
Unfortunately, the xsd schema for XML literals doesn't work currently in Roslyn (seems it was forgotten while moving to Roslyn) as reported here: dotnet/roslyn#34816 |
I have a new idea to implement Vazor view, called ZML. vbxml code is not compatable with Tag Helpers. I used workarounds to make it work, but the resulted code is longer than it should! So, I decided to expand my data template idea but in another direction: Writing structural code as xml tags, so we have a new ZML Razor layer built on tip of C# Razor! |
I faced a new xml literals limitation: Is refuses to use > and < in attr values in many cases! This is a limitation of XML specification itself which doesn't exist in HTML5! Dim x = <if condition="a>3 and y<5">
<p>x = 4</p>
</if> I use this workaround: x = <if condition=<%= "a>3 and y<5" %>>
<p>x = 4</p>
</if> But with all xml literals limitations, I find using ZML pages better:
|
You are required to escape them
|
@AdamSpeight2008 |
XElement has a formating issue with xml containing literal strings as explained here: https://github.com/dotnet/corefx/issues/36871 |
The thing to remember is that XML has rules, which VB's literals have to comply with, otherwise their usefulness is grossly undermined. As programmers, we sometimes have to live with external constraints, and learn how to deal with them. This is an example of one. |
I suggested a way to embed code blocks directly in xml literals here #422 |
Finally done :). Details in: #527 (comment) |
In this proposal dotnet/aspnetcore#8674 (please all VB.NET fans, support it), I suggested to use xml literals to implement ASP.NET MVC razor in VB.NET instead of vbhtml files, like this:
In this code, I used LinQ because I need to return a value to be embedded in the xml literal. I tried another way:
Using inline lambdas like this, makes it possible to evaluate any expression with any VB code we want, but I think this can be simplified and shortened as this:
This is more like a vbhtml razor page code!
The suggestion is simply to write any vb.net code directly inside the
<%= %>
marks, and use return or Yield to return the desired value that will be used in the xml literal. Return and Yield here are scoped to the Xml literal, and all what VB.NET needs to do, is to wrap all this insideWhere [Iterator] is added if Yield is used in code.
Another example:
Which is a shortcut for:
I hope these proposal are taken seriously. VB.NET is too powerful and has many precious hidden treasures, and it is really unfortunate that MS decided to left VB.NET behind to save some effort and money, while VB.NET could have been saved too much time , effort and money spent to develop Razor syntax, while it is already supported in VB syntax!
The text was updated successfully, but these errors were encountered: