dotnet / vblang

The home for design of the Visual Basic .NET programming language and runtime library.
290 stars 63 forks source link

XML literals Enhancements to be more supportive to Vazor: the built-in VB syntax Razor! #397

Open VBAndCs opened 5 years ago

VBAndCs commented 5 years ago

In this proposal https://github.com/aspnet/AspNetCore/issues/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:

Friend Class View1
    Dim Model As IEnumerable(Of MvcMusicStore.Models.Genre)

    Public Sub New(model As IEnumerable(Of MvcMusicStore.Models.Genre))
        Me.Model = model
        ViewBag.Title = "Store"
    End Sub

    'ViewBag.Title = "Store"
    Public ReadOnly Property Razor
        Get
            Return _
    <html>
        <h3> Browse Genres</h3>
        <p>
    Select from <%= Model.Count() %> genres:
    </p>
        <ul>
            <%= (From genre In Model Select <li><%= genre.Name %></li>) %>
        </ul>
    </html>
        End Get
    End Property

End Class

In this code, I used LinQ because I need to return a value to be embedded in the xml literal. I tried another way:

        <ul>
            <%= (Iterator Function()
                     For Each genre In Model
                         Yield <li><%= genre.Name %></li>
                     Next
                 End Function)() %>
        </ul>

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:

        <ul>
            <%= For Each genre In Model
                         Yield <li><%= genre.Name %></li>
                     Next %>
        </ul>

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 inside

([Iterator] Function( )

End Function)()

Where [Iterator] is added if Yield is used in code. Another example:

        <ul>
            <%= 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 %>
        </ul>

Which is a shortcut for:

        <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>

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!

VBAndCs commented 5 years ago

Finally: A working VB.NET ASP.NET MVC Core Razor sample! https://github.com/VBAndCs/VB.NET-Razor I implemented a simple VBRazorViewEngine in the VbRazor project. To use VBRazorViewEngine in the project, I added these two statements to the Startup.ConfigureServices method:

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: VBRazor

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 first thing to do, it to create a VB.NET template for ASP.NET MVC Core. I had to create a C# project then convert it to VB!

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.

DualBrain commented 5 years ago

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). ;-)

VBAndCs commented 5 years ago

@DualBrain I am waiting. Wish you safe landing :)

KathleenDollard commented 5 years ago

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).

VBAndCs commented 5 years ago

Thanks @KathleenDollard We can choose any suitable name instead of Razor, like XML views or something. This is not the main issue at this point. I am also ok with changing the model, put I am still alone, and walking in a new territory according to my experiences, so, it is easier to prove the concept by plugging in a custom view engine and let MVC do all the work. At least it does the parsing and rendering of HTML and its scripts, so I can focus only on delivering the right html format. If I have the community support, we can make any changes to the model in the right moment. I am building things up, so I didn't try view inheritance and adding page header and footer and so. I just tried adding a script today, and it works fine in the browser, but vb editor doesn't add any support for the scripts in the XML literal. This is not big issue now, because I can over he script to a separate file so it can have the full editor support for java script or other script languages. If the script depends on some model values, we can pass them to a function call, so the script in xml will just be a one line to call this function, and this will need a little support from the editor to auto complete the function name and view its params info. Luckily, the solution explorer is clever enough, so it added the script file (IndexView.vb.js) as a sub element of the view file (IndexView.vb) Same goes for CSS. It is not suitable to mix VB code with JavaScript and CSS. But the really needed support now, is the editor support for HTML tags and their attributes. we need to borrow this from the .html editor in VS.NET or VScode.

VBAndCs commented 5 years ago

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! I expect that VB will not change this strange behavior not to break any existing code! So, To get red of this probable error, I changed the definition of the Razor property to return an XElement nad made change to the VBRazorView to call its .ToString. I also changed the Razor to be a function to save some indent consumed by Get… End Get.

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 ViewBag.Title = "VB Razor Sample" has no effect, so, we need to write the layout view class. I will try this.

I didn't upload these changes to the repo, since I am still experimenting.

VBAndCs commented 5 years ago

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 s you said, no need to search for file locations. We just use the name LayoutView for the layout razor class.

Now, the output page is shown like this:

4

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 :)

VBAndCs commented 5 years ago

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: 1- Simplify my job (no need to re-invent the Razor View Engine) 2- Eliminate the need to do any future development, as MS are the responsible for improving the Razor. Ti achieve this, these are the steps: If the View class contains this Vazor function:

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). Also, we auto generate this cshtml file:

<h3> Browse Students</h3>
<p>Select from @ViewBag.Vazor_Value1 students:</p>
<ul>
    @ViewBag.Vazor_Value2
</ul>
<script>
   var x = 5;
   document.writeln('students count = @ViewBag.Vazor_Value1');
</script>

@KathleenDollard , @AnthonyDGreen I can do parsing and generate the chtml file, put what I need help is: 1- How do this at the building time of the project (before compiling the View classes). 2- How I read the get all classes that implement IVazor interlace (the view classes) and how I read the Vazor function code? Is it better to read it as a plain text and do the usual search for <%= %> blocks, or it is easier to get the syntax tree and work with it? 2- How to generate code and add it to existing code file? Can I generate a temp or in-memory vb code file that contains a partial definition of the View class and add the methods to it? and how I make the compiler see and compile it? I need some links about this to start read and work. I don't want to read huge books to only do this task. How to do articles or articles about these specific topics will more practical. and I will be glad if any one wants to participate in this.

KathleenDollard commented 5 years ago

@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.

VBAndCs commented 5 years ago

@KathleenDollard
Thanks for your support.

Do you imagine the vb and cs code existing in the same project? That is probably not going to work.

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 : https://github.com/mevdschee/aspnetcorevb It works fine in ASP.NET Core 2.1, but when I tried the same in ASP.NET core 3.0 preview, I got an error page saying it can't find Index.cshtml! This can be fixed later, but the main concept is still applied: VB code an cshtml file can co-exist in one project.

prior to build in a separate step

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.

What problems did you find with the straight XML Literal approach?

1- Need to resolve paths to js and image files and other components. 2- Need to process some asp tags 3- Need to consider user credentials, security and encryption. and so on. The code is there in C# Razor, but input is different. Razor expected a path to a cshtml raw file to parse, compile and generate html object element tree, while I deliver a string containing the processed Html. I need to skip the first steps and jump to the step where Razor processes HTML contents and resolve paths and asp tags and other stuff. Another issue, is that Razor uses buffers. I expect it is a more efficient way to handle large pages. By the way, I have some thoughts to optimize the VBXML code. For example: if the layout differs only by the title, then we can cash the html layout and only change the title for each view. I expect razor to do something like that in some stage. I succeeded to build the Razor.dll today, but I still facing problems in adding a web project to the solution and make it use the Razor project so I can trace how it processes chtml files. The code depends on interfaces, and I wasted a lot of time trying to trace functions calls, and failed! I need to understand how the Razor works, so I can call the right methods to process my html content, or if it is not possible (because internal accessibility or incompatible inputs), I can manually copy and modify the code to make a fully functional Vazor view engine. Thanks.

ericmutta commented 5 years ago

@VBAndCs The suggestion is simply to write any vb.net code directly inside the <%= %> marks

As a heavy user of XML literals I definitely support this suggestion :+1: :+1: though I am not yet sure about the specifics. For example, the For Each scenario is already easy to implement using LINQ queries like this (actual example from shipping code):

        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 XElements, I don't think we need anything special for the For Each type scenarios.

However, the Select Case scenario is impossible to do directly with current language features and perhaps a broader feature we could consider is an expression version of Select Case (similar to the one from T-SQL) which would be useful in general (i.e without using XML literals) and could then be used with XML literals too.

Some food for thought!

VBAndCs commented 5 years ago

@ericmutta Thanks for your support. I already showed how to use lambda expressions to embed any code inside XML literals. This is how you use Select case:

        <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. Please take some time to read all the discussion here, maybe you can help in some issues mensioned. Thanks.

gilfusion commented 5 years ago

I'll just note that #109 (Block expressions) from a while ago seems to fit in well with some of the ideas here.

VBAndCs commented 5 years ago

@gilfusion Yes, but I don't like more { } blocks in VB, espetially it can be confued with array and object initializers. I already poposed to add property lambda expressions using Get ... End Get in #398 . Thanks.

VBAndCs commented 5 years ago

I saw this chtml

@{
    var value = "Value 1";
}

<p>@value</p>

@{
    value = "Value 2";
}

<p>@value</p>

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! Even If my proposal excepted and we get red of the lamda body:

<Vazor>
    <p><%= value %></p>
    <%= value = "Value 2"
        Return ""
     %>
    <p><%= value %></p>
</Vazor>)

It is still feels strange! So, this brings me back to my rejected proposal #393 ! Using Let to define a scope can allow us to use code blocks as in Razor views :

<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 Get… End Get read-only lambda property, maybe a Set … End Set block that acts as a write-only lambda property is considered.

<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>)
VBAndCs commented 5 years ago

Another issue with Xaml literals, is that it doesn't allow write the & even inside quotes! <x note="&Note">&Note</x> VB refuses both &s, saying:

XML entity references are not allowed

I hope to allow escaping & like this \& or any other way. The only solution is to use any text like __amp and replace it in when we get the string representing xml.

Surprise: While I was looking for a solution for this, I found this old project that uses XML literals to create the Views! https://archive.codeplex.com/?p=vbmvc The project is before razor pages (talking about ASPX pages) and I couldn't load the sample in VB.NET 2019!

View Engine for ASP.NET MVC using VB.NET XML Literals instead of traditional ASP.NET pages. Each view is a VB.NET class and master pages are base classes. Views are compiled into the assembly, so no ASPX files to deploy. Based on code by Dmitry Robsman

pricerc commented 5 years ago

Another issue with Xaml literals, is that it doesn't allow write the & even inside quotes! <x note="&Note">&Note</x>

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 <x note="&amp;Note">&amp;Note</x>

VBAndCs commented 5 years ago

Ok. VB accepts &amp;
I was afraid it will not work inside attributes:

Dim param1Value as String = "test"
Dim html = 
<div>
   <a 
      id="myLink" 
      href=<%= "/mysite/myfile?param1=" + param1Value + "amp;param2=test2" %>>
      link
      </a>                       
</div>

But it worked! The &amp; becomes & in the url. Thanks :)

VBAndCs commented 5 years ago

But, there is still a problem: <x>&copy;MyCompany</x> VB didn't allow & here. It seems it is not aware of &copy; which is the symbol ©, and need to be updated. I tried: <x><%= "&copy;" %>MyCompany</x> but it yields <x><%= "&amp;copy;" %>MyCompany</x> which is wrong, because browsers will view it as &copy; not © ! The only solution I know is this:

        Dim amp = "_vb_amp_"
        Dim s = <div>
                    <x><%= amp %>copy;MyCompany</x>
                </div>.ToString().Replace(amp, "&")

but inside quotes, the string _vbamp must inserted directly not because <%= amp %> is not allowed inside quotes.

jasonmalinowski commented 5 years ago

It seems it is not aware of &copy; which is the symbol ©, and need to be updated.

VB XML literals are following XML character entities, which is a restricted list and doesn't include things like &copy;:

https://en.wikipedia.org/wiki/List_of_XML_and_HTML_character_entity_references#Predefined_entities_in_XML

HTML defines a bunch of extra ones like &copy; but that's not something you can put in an arbitrary XML document -- it's up to the language atop XML (like HTML, but it could be anything) that's specifying what those extra entities mean.

VBAndCs commented 5 years ago

@jasonmalinowski OK. We don't want to change VB specifications about XML. We only want a way to escap & char so we can use extra & terms as we need. For example, I will be OK with using &&& as an escape for &, and I think &&& is unusual equence to frear breake anything.

pricerc commented 5 years ago

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.

VBAndCs commented 5 years ago

@pricerc What about the cheap solution al the parser level: escape &&& and output it as & ?

pricerc commented 5 years ago

@pricerc What about the cheap solution al the parser level: escape &&& and output it as & ?

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 &amp; &gt; &gt; &apos; and &quot; are understood and handled. Anything else that looks like an XML IDENTITY should be ignored (e.g. &copy; &nbsp;, 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!)

KathleenDollard commented 5 years ago

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

VBAndCs commented 5 years ago

I am excited to announce that: My Work Is Complete! I found an easy solution to make Razor all its magic for our Vazor! All I had to do, is to use the IFileProvider to define a virtual file system that delivers the html output produced from Vazor to Razor as if it is the cshtml view that Razor expects! So, Razor resolves the tag helpers, paths, combine the layout and sections, and any other stuff! There is one issue here, that is Vazor delivers different compiled pages for each View, so I used a Mapper class that appends a unique ID the name of each page, so that multiple synchronous requests to the same view can happen safely. The amazing thing here is that we have a mixed Vazor/Razor! This means we can write some views as Vazor classes, and write some other as Razor views and they will integrate an co-work smoothly! This is important to save us unnecessary effort to convert Razor views that doesn't contain any code (like the layout page and View imports pages.. etc) to Vazor classes! If we want to use the Vazor Layout, we must map it in the configure method like this: Vazor.VazorViewMapper.Push(New LayoutView(), False)

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: Return View(Vazor.VazorViewMapper.Push(New IndexView(Students, ViewBag)))

and that is all!

This image shows the rendered Page resulted of:

untitled1

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 Now, the main concern is to make writing Vazor code pleasant. We have no support for tag names, attributes and of course Tag Helpers in XML literals. I hope to write direct code inside <%= %> without any lambda expression tricks. and of course there is the & issue. I can workaround Tag Helpers lake of support by providing wrapper classes with shared methods, but this is a huge work, and will not be bratty to use <%= %> to add tag helpers!

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. Thanks all.

VBAndCs commented 5 years ago

Vazor 1.0: https://github.com/VBAndCs/Vazor

VBAndCs commented 5 years ago

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: Dim html = Partial "IndexView.vbxml.vb"

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.

VBAndCs commented 5 years ago

Vazor processes the view in two stages: 1- VB processes xml literals and evaluates expressions to deliver html page. 2- Razor processes the html page to resolve tag helpers and other stuff, to deliver the page to the browser.

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: https://github.com/VBAndCs/Vazor/blob/master/Vazor/VazorExtensions.vb

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: Dim html = GetVbXml().ParseTemplate(students) It works fine, but I wish XML literals support this template model, so: 1- It provide editor intellesense when writing m. This may require adding the type of the var like this: <li Temp.ForEach="m" Temp.Type="Student"> 2- Allow using nested objects like m.Grades.Math. The ParseTemplate method doesn't support this, so now it can only done by normal VB code. In fact, I am wishing this simple template formula be used in XAML instead of current DataTemplate which is rather complex and verbose! (Now I am thinking how Xaml make use of VB literals!.. VB literals can change the way we program!).

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.

VBAndCs commented 5 years ago

@DualBrain @KathleenDollard @ericmutta @gilfusion @jasonmalinowski I created a VB.NET Razor Page app and used vbxml code to render the page! https://github.com/VBAndCs/VB.NET-Razor/tree/master/RazorPages1

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! This means that we can not get rid of the cshtml files! So, I used a simple trick: I put the vbxml code in a property named VbXml in the IndexModel class, and used it in the Razor page like this:

@page
@model IndexModel

<div>
    @Html.Raw(Model.VbXml)
</div> 

And that is all! There is nothing C# in this Razor page, except it has the cshtml extension! And we even don't want the Vazor engine! This is a pure VB.NET/Razor project! VB.NET was capable on its own to write Razor pages and fully run on ASP.NET Core without any further work! Just a small workarounds! It is really a big surprise! By the way, the same approach can be used in MVC apps. I added the Vazor virtual file system to get rid of cshtml files. I think it is ok to keep them as just names, and inject vbxml code into them. I did that in this MVC Core project: https://github.com/VBAndCs/VB.NET-Razor/tree/master/WebApp1

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! Still, we can add more functionality in the middle stage as I mentioned before, and of course we need the xml literals enhancements I mentioned in this thread, and I hope that we can have a .vbxml file to contain the view code and treated as a vb file that contains only xml literals, that can be loaded in the XElement by some method that accepts the file name as a parameter.

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!

VBAndCs commented 5 years ago

Using @html.Raw() has a limitation: it doesn't resolve TagHelpers. So, this method valid only for pure vbxml that doesn't use Tag helpers! I used another approach in this Razor Pages app: https://github.com/VBAndCs/Vazor/tree/master/VazorPages1 I used the virtual file provider again. This will allow to write a vbxm layout, and with a small trick I injected the Index.vbxml page as a partial view in the Index.cshtml like this:

@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:

  1. use c# razor in cshtml files.
  2. use vbxml without tag helpers and inject it in cshtml files.
  3. use vbxml with tag helpers and pass it as a virtual pages using Vazor File provider. This doesn't need cshtml files except for Razor page body, where we inject vbxml code as a partial view.
  4. all three methods can be used together with different views/pages in the same app! vbxml code itself can contain razor @code, and even c# blocks!
  5. Writing rge view with vb code in a regular class, opens endless possibilities, like adding a middle stage between vbxml and cshtml. I already saw how to use a new ForEach = "m" template.

This is the most flexible view engine ever, yet it came out of the box!

VBAndCs commented 5 years ago

Unfortunately, the xsd schema for XML literals doesn't work currently in Roslyn (seems it was forgotten while moving to Roslyn) as reported here: https://github.com/dotnet/roslyn/issues/34816 I was trying to add html auto complete using html5.xsd I got. Currently, I added a project and item templates for Vazor: https://github.com/VBAndCs/Vazor/blob/master/VazorTemplateSetup.zip?raw=true Refer to ReadMe.md to how to use them. The reaming work is to enhance XML as I requested in this issue. This is wher I can't move forward alone. I will focus on testing Vazor on web projects.

VBAndCs commented 5 years ago

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 already implemented it in Vazor 1.5 and hope it can be implemented in .NET, so please support this proposal (all details are there): https://github.com/aspnet/AspNetCore/issues/9270 Thanks.

VBAndCs commented 5 years ago

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! This expression can't be used in VB (or in any XML parser!):

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:

  1. It is written in a homogeneous xml tags without any strange language.
  2. It is compact in most cases than vbxml which uses the lambda expression. This will be true until xml literal allows writing vb code directly inside <%= %>
  3. No need to use virtual file provider, so it is possible to pre-compile cshtml files.
  4. It uses HTML 5 editor without needing to switch between it and vb editor. But the editor doesn't offer intellisnse fot ZML tags yet. I will try to solve this in the future.
AdamSpeight2008 commented 5 years ago

You are required to escape them

 &amp; <-> &
 &lt; <-> <
 &gt; <-> >
 &quot; <-> "
 &apos; <-> '
VBAndCs commented 5 years ago

@AdamSpeight2008 I know, but I am seeking for a friendly way for writing vbxml code, becuase it is not a one time job, but a way to designh web views and pages. Embeding thse symbols in <%= %> forces vb to automatically escape them, so it does the trick and keep readable. Thanks.

VBAndCs commented 5 years ago

XElement has a formating issue with xml containing literal strings as explained here: https://github.com/dotnet/corefx/issues/36871 krwq offered a solution by using: XElement.Parse(s, System.Xml.Linq.LoadOptions.PreserveWhitespace); but this works only with xml written in string literals. XML literals are parsed by VB, so can vb solve this issue by using the PreserveWhitespace option?

pricerc commented 5 years ago

@AdamSpeight2008 I know, but I am seeking for a friendly way for writing vbxml code, becuase it is not a one time job, but a way to designh web views and pages. Embeding thse symbols in <%= %> forces vb to automatically escape them, so it does the trick and keep readable. Thanks.

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.

VBAndCs commented 5 years ago

I suggested a way to embed code blocks directly in xml literals here https://github.com/dotnet/vblang/issues/422

VBAndCs commented 4 years ago

The second thing to do, is to add intellisense support for html attributes in xml literals in VB!

Finally done :). Details in: https://github.com/dotnet/vblang/issues/527#issuecomment-610232087