0

I am using VB.Net 12, so multi-line strings are not allowed as they are in C#.

I have following code in VB.Net, which I need to convert into a single AppendFormat. This is very easy in C# since the long string can be spread over multiple lines for better readability/maintenance, but it doesn't seem possible in VB.Net without concatenating string using the & operator; but then the purpose of using StringBuilder gets defeated.

Question

Can I convert following VB.Net to use a single AppendFormat without using string concatenation operator &?

'use a stringbuilder for better string performance
Dim sb As New StringBuilder()
sb.Append("<html>")
sb.Append("<head><style>body {font-size:10pt; font-family:Arial, Sans Serif;} table{margin:0; padding:0; border-collapse:collapse;} td{border:solid 1px #eee; padding:2px;}")
sb.Append(".m{color:#f00; font-weight:bold; font-size:14pt; margin-bottom:9px;} .t{width:120px; font-weight:bold; font-size:8pt;} .d{width:500px;}")
sb.Append("</style></head>")
sb.Append("<body>")
sb.AppendFormat("<div class='m'> {0} </div><table>", ex.Message.ToString())
sb.AppendFormat("<tr><td class='t'>REQUEST URL</td><td class='d'> {0} </td></tr>", currentContext.Request.Url.OriginalString)
sb.AppendFormat("<tr><td class='t'>REQUEST PATH</td><td class='d'> {0} </td></tr>", currentContext.Request.Path)
sb.AppendFormat("<tr><td class='t'>QUERY STRING</td><td class='d'> {0} </td></tr>", currentContext.Request.QueryString.ToString())
sb.AppendFormat("<tr><td class='t'>TARGET SITE</td><td class='d'> {0} </td></tr>", ex.TargetSite.Name)
sb.AppendFormat("<tr><td class='t'>STACK TRACE</td><td class='d'> {0} </td></tr>", ex.StackTrace)

sb.AppendFormat("<tr><td class='t'>USERID</td><td class='d'> {0} </td></tr>", userId)
sb.AppendFormat("<tr><td class='t'>USER</td><td class='d'> {0} </td></tr>", userName)
sb.Append("</table>")
sb.Append("</body>")
sb.Append("</html>")

UPDATE

I got it to work using Steve's answer in which he suggested to use XML element. The only thing I needed to do was to escape curly braces by preceding each curly brace in expression with the same curly brace. The working code is as below.

   Dim s As XElement = <html>
                            <head>
                                <style>
          body{{font-size:10pt; font-family:Arial, Sans Serif;}} 
           table{{margin:0; padding:0; border-collapse:collapse;}}
            td{{border:solid 1px #eee; padding:2px;}}
            .m{{color:#f00; font-weight:bold; font-size:14pt; margin-bottom:9px;}}
           .t{{width:120px; font-weight:bold; font-size:8pt;}}
           .d{{width:500px;}}
         </style>
                            </head>
                            <body>
                                <div class='m'>{0}</div>
                                <table>
                                    <tr><td class='t'>REQUEST URL</td><td class='d'>{1}</td></tr>
                                    <tr><td class='t'>REQUEST PATH</td><td class='d'>{2}</td></tr>"

<tr><td class='t'>QUERY STRING</td><td class='d'>{3}</td></tr>"
<tr><td class='t'>TARGET SITE</td><td class='d'>{4}</td></tr>
                                        <tr><td class='t'>STACK TRACE</td><td class='d'>{5}</td></tr>
                                        <tr><td class='t'>USERID</td><td class='d'>{6}</td></tr>
                                        <tr><td class='t'>USER</td><td class='d'>{7}</td></tr>
                                    </table>
                                </body>
                            </html>
        email.Body = sb.AppendFormat(s.ToString(), ex.Message.ToString(), _
                                 currentContext.Request.Url.OriginalString, _
                                 currentContext.Request.Path, _
                                 currentContext.Request.QueryString.ToString(), _
                                 ex.TargetSite.Name, ex.StackTrace, userId, userName).ToString()

Performance Update

I tried all 3 approaches discussed here to find which one is best performing in my scenario. So, I ran my ASP.Net scenario with each of these approaches (only one was implemented for each test)

  • TEST 1: XElement with StringBuiler.Format
  • TEST 2: XElement with inline expressions
  • TEST 3: StringBuilder with multiple Appends and AppendFormat as in code given in my question

What I found using ticks elapsed of Stopwatch object for each run are as below. I ran my ASP.Net scenario 4 times for each approach. And it seems that the third approach of StringBuilder is the fastest.

  • TEST 1: XElement with StringBuilder format : 6505, 657, 426, 446
  • TEST 2: XElement with inline expressions : 6326, 414, 422, 635
  • TEST 3: StringBuilder appends/appendformats : 6588, 351,345, 280

I would think XElement provides better readability when someone is reading/maintaining your code, but the XElement approaches fail to beat StringBuilder in performance.

Sunil
  • 20,653
  • 28
  • 112
  • 197
  • 4
    https://stackoverflow.com/questions/706382/multiline-strings-in-vb-net – Steve Jan 12 '19 at 14:30
  • You can use a single `AppendFormat` to insert linebreaks, but that would also require you to put everything in a single, long line. I think your best bet would be to put the entire string in the application resources. **EDIT:** Or use XML literals, as Steve suggested. – Visual Vincent Jan 12 '19 at 14:34
  • @Visual Vincent, It seems it's not possible without using `&` operator. But as you suggested, I could store the string in a text file and read from it; that would enable using a single AppendFormat. – Sunil Jan 12 '19 at 14:38
  • @Steve, Why did you delete your answer? I was looking at it when it suddenly got removed. – Sunil Jan 12 '19 at 14:40
  • @Sunil because it doesn't work. Tested on LinqPad but some pieces of your code text are missing. Don't know why. However it is just your html text without the quotes around the string. The compiler accepts the input but when you run that code the resulting string is trimmed – Steve Jan 12 '19 at 14:43
  • @Steve, Let me also try it out. – Sunil Jan 12 '19 at 14:45
  • @Sunil I have found a workaround. Check if my answer works – Steve Jan 12 '19 at 14:51
  • 1
    You don't necessarily need it to be a file. You could store it as a string embedded directly into your application via the [Application Resources](https://learn.microsoft.com/en-us/dotnet/visual-basic/language-reference/objects/my-resources-object) (if you for some reason do not want to use Steve's answer, which is a good solution). – Visual Vincent Jan 12 '19 at 15:13
  • Why you are still using `AppendFormat`? With Xml Literals (as in Steve's sample) you can put all dynamic values into XElement and then append to the builder with `XElement.ToString()`. – Fabio Jan 13 '19 at 02:39
  • @Fabio, I tried that option and it did work, but look at the performance results that I have posted in my question update. String builder approach seems to be fastest, but XElement can't be beaten in easier readability. – Sunil Jan 13 '19 at 05:56
  • 1
    @Sunil, I bet performance difference in this case will not become a bottle neck. But better readability can save few minutes for next developer or even for you after few weeks ;) – Fabio Jan 13 '19 at 06:23
  • @Fabio, I agree with you 100%. I was only trying to understand this feature out of curiosity since I am new to this XElement feature for string handling in VB.Net and that led me to seeing how they perform in my scenario. – Sunil Jan 13 '19 at 07:37

1 Answers1

4

Here your code with XML Literals. I have tried to use directly a string but for some reason the result gets truncated and some part of the original text are missing. However using an XElement it works correctly

Dim s As XElement = 
<html>  
    <head>  
        <style>  
            body {font-size:10pt; font-family:Arial, Sans Serif;} 
            table {margin:0; padding:0; border-collapse:collapse;} 
            td{border:solid 1px #eee; padding:2px;} 
            .m{color:#f00; font-weight:bold; font-size:14pt; margin-bottom:9px;}  
            .t{width:120px; font-weight:bold; font-size:8pt;} 
            .d{width:500px;} 
        </style> 
    </head> 
    <body> 
        <div class='m'><%= ex.Message %></div> 
        <table> 
            <tr> 
                <td class='t'>REQUEST URL</td> 
                <td class='d'><%= currentContext.Request.Url.OriginalString %></td> 
            </tr> 
            <tr> 
                <td class='t'>REQUEST PATH</td> 
                <td class='d'><%= currentContext.Request.Path %></td> 
            </tr> 
            <tr> 
                <td class='t'>QUERY STRING</td> 
                <td class='d'><%= currentContext.Request.QueryString.ToString() %></td> 
            </tr> 
            <tr> 
                <td class='t'>TARGET SITE</td> 
                <td class='d'><%= ex.TargetSite.Name %></td> 
            </tr> 
            <tr> 
                <td class='t'>STACK TRACE</td> 
                <td class='d'><%= ex.StackTrace %></td> 
            </tr> 
            <tr> 
                <td class='t'>USERID</td> 
                <td class='d'><%= userId %></td> 
            </tr> 
            <tr> 
                <td class='t'>USER</td> 
                <td class='d'><%= userName %></td> 
            </tr> 
        </table> 
    </body> 
</html> 

We can use a double curly braces around the braces in the style block and then use string.Format to replace the parameters. (Partial block taken from the code above)

....
body {{font-size:10pt; font-family:Arial, Sans Serif;}} 
....

At this point we could use the String.Format to resolve in the code behind the formatting for the whole text

Dim text = String.Format(s.ToString(), ex.Message, .........)
Steve
  • 213,761
  • 22
  • 232
  • 286
  • If we're at it, the values may be embedded directly, e.g. `
    <%= ex.Message %>
    `. And VB that does not support implicit line continuations will require a `_` after the `=`.
    – GSerg Jan 12 '19 at 15:06
  • `XElement` is the way to go, they are suitable in this case to constructing the tree. – Trevor Jan 12 '19 at 15:07
  • @Steve, I am getting an error when I use `sb.AppendFormat(s.ToString(),....)`. I will come back later and try to check the exact exception. Need to go now. But thanks. I will update after an hour or so. I did not try with `String.Format` so far. The compiler gives no errors, so its a run-time error. – Sunil Jan 12 '19 at 15:25
  • 1
    You cannot pass that XML to `String.Format` because the CSS block looks like a bunch of invalid placeholders (`{margin:0` etc) and will raise a `FormatException`. You need to embed the variables directly. – GSerg Jan 12 '19 at 15:28
  • 1
    It worked using an XMLElement, when I replaced single curly braces within my original string with double curly braces, so they are escaped when used in Format expression. I will post the update. – Sunil Jan 12 '19 at 15:51
  • Ahhh, I have tried the double but got an error. Now I see that the error was not related to the double curly braces. Definitively, double curly braces works in formatting – Steve Jan 12 '19 at 15:54
  • @Steve, Does XMLElement provide only programming convenience in my scenario, or you would think it might perform better than my original stringbuilder Append code ? I am not sure. May be they are same in my case. – Sunil Jan 12 '19 at 15:56
  • @GSerg, The curly braces in html string were part of CSS declarations, so they are normal in html. – Sunil Jan 12 '19 at 15:59
  • 1
    I am not sure as well. Probably you need to test the performance in your actual scenario. However, a single XElement initialization seems to be better than repeated calls to StringBuilder.AppendFormat – Steve Jan 12 '19 at 15:59
  • 1
    @Steve, plus it provides programming convenience. – Sunil Jan 12 '19 at 16:00
  • 1
    @Steve You got it backwards. It's the `{` and `}` in the CSS block that need to be escaped, not the ones around actual parameters. You also have to have a space after `<%=` and before `%>`, otherwise it's a syntax error. – GSerg Jan 12 '19 at 16:08
  • 1
    @Sunil I know the `{` is a part of HTML. I'm saying that this makes the string no good for composite formatting, and you don't really want to redo your entire HTML corpus doubling up any `{` or `}` that they have. Embedding the parameters with `<%= %>` looks like a better option to me. – GSerg Jan 12 '19 at 16:10
  • @GSerg, That was something new for me. Thanks. I tried it and it also works. – Sunil Jan 12 '19 at 18:45