Visual Studio Macros

Long time no see! I recently discovered the use of Macros in Visual Studio. Yes, I have known ABOUT them since the release of the IDE, but I haven't actually used them. Until recently. I must say that I find them extremely useful for helping with repetitive tasks. In earlier posts, you've read about various time saving techniques, including building your own form builder application. But instead of building dozens of specialized applications, you could instead create Macros IN the IDE that you are using and USING that IDE to perform your work. Saves some copying and pasting plus that you will not easily loose track of them since they are nicely listed in your Macro Explorer. But enough babble ...

A quick introduction The Macro Explorer is accessible from the View - Other Windows - Macro Explorer (or by simply pressing ALT+F8). In the Explorer, you get a project listing which contains documents (modules) which, in turn, contains Visual Basic Sub procedures that are the actual macros. Double-clicking on a macro executes it.

To create a new macro project, right-click Macros and choose New Macro Project. However, if you already have the MyMacros project available, you can use that instead.

Presuming that you either have or can create the Module1 module of MyMacros, you easilly double-click on it to open the Macro IDE. Here is where the fun begins ...

String_Format We all know that string concatenation is a performance hog. Still many people use it, because it's convenient for them. To fix string concatenations and turning them into String.Format()-expressions can save a lot of CPU cycles especially in a Web scenario. Having been in a few web projects now (All Visual Basic.NET, mind you), I decided to create a macro to do the work for me. It looks like this (view the source of this page to copy the complete code example):

Sub String_Format()
  DTE.UndoContext.Open("String_Format")
  Dim selection As TextSelection = DTE.ActiveDocument.Selection

  If String.IsNullOrEmpty(selection.Text) Then
    selection.SelectLine()
  End If

  Dim oldCode As String = selection.Text
  Dim newCode As New StringBuilder
  Dim params As New List(Of String)
  Dim iof As Integer = oldCode.IndexOf("=")
  
  newCode.Append(oldCode.Substring(0, iof + 1))
  newCode.Append(" String.Format(""")

  For Each fragment As String In oldCode.Substring(iof + 1).Split("&")
    fragment = fragment.Trim()
    If fragment.StartsWith("""") Then
      newCode.Append(fragment.Replace("""", ""))
    Else
      newCode.AppendFormat("{{{0}}}", params.Count)
      params.Add(fragment)
    End If
  Next

  newCode.AppendFormat(""", {0}){1}", Join(params.ToArray(), ", "), Environment.NewLine)
  selection.Text = newCode.ToString()

  DTE.UndoContext.Close()
End Sub

First off, I create an UndoContext, meaning that a single CTRL+Z reverts all changes done by the macro. This is a good custom.

Then, I retrieve the current selection in the editor. If the programmer hasn't selected anything, I assume that (s)he wants to act on the current row. I therefore select the entire row and go from there.

Having a proper selection, I start working on the code, restructuring it into a String.Format()-statement. Now, this code is not perfect. In fact, it has some serious limitations. First off, it works only on Visual Basic code. Furthermore, it assumes that it's a statment that looks something like this:

Dim d As String = "I, " & Request("firstname") & " " & Request("lastname")  & " like candy"

However, most of the string concatenations in my current project looks like the one above, why the macro saves me a lot of time.

Back to the code: After "doing the magic", I set the text selection to the code I have created, whereafter I Close() the UndoContext.

It's that simple! And it will save you a lot of time! What I have not experiemtned with thus far, is recording macros. I guess I'm too much of a control freak for that. But that is an option too.

Here's another macro that could, potentially, save you time:

Sub LabelAndInputWithValidation()
  DTE.UndoContext.Open("Create label and input control with validation")
  Dim selection As TextSelection = DTE.ActiveDocument.Selection
  Dim controlBaseId As String
  Dim inputControlType As String
  Dim inputMaxLength As Integer = -1
  Dim validationPattern As String = Nothing
  Dim isRequired As Boolean = False
  Dim controlPrefix As String
  
  If String.IsNullOrEmpty(selection.Text) Then
    selection.SelectLine()
  End If

  If Regex.IsMatch(selection.Text, ".*?\:.*?") Then
    Dim split1() As String = Split(selection.Text, ":")
    controlBaseId = split1(0).Trim()
    inputControlType = split1(1).Trim()

    If inputControlType.Contains(",") Then
      Dim split2() As String = Split(inputControlType, ",")
      inputControlType = split2(0).Trim()
      
      If split2.Length > 1 Then
        If Not IsNumeric(split2(1).Trim()) Then
          MsgBox("Expected format: BASE_ID:TYPE[,MAX_LENGTH,VALIDATION_PATTERN[!]]", , "Invalid Format")
          Exit Sub
        End If

        inputMaxLength = CInt(split2(1).Trim())
      End If

      If split2.Length > 2 Then
        validationPattern = split2(2).Trim()
        
        If validationPattern.EndsWith("!") Then
          isRequired = True
        End If
      End If
    End If
  Else
    controlBaseId = InputBox("The Control Base Id is the Id suffix of all controls, i.e. without any 'lbl' or 'txt' prefix (Example: FirstName)", "Control Base Id")
    inputControlType = InputBox("The Control type is the tag name, e.g. TextBox or   DropDownList", "Control Type")

    If LCase(inputControlType) = "textbox" Then
      inputMaxLength = CInt(InputBox("Max Length?"))
    End If

    isRequired = (MsgBox("Is required?", MsgBoxStyle.YesNo) = MsgBoxResult.Yes)
    validationPattern = InputBox("Enter validation pattern (regular expressions), or leave empty to disable pattern matching: ", "Validation pattern")
  End If

  Select Case LCase(inputControlType)
    Case "textbox"
      inputControlType = "loc:TextBox"
      controlPrefix = "txt"
    Case "dropdownlist"
      inputControlType = "asp:DropDownList"
      controlPrefix = "ddl"
    Case Else
      controlPrefix = "ctl"
  End Select

  Dim code As New StringBuilder
  code.AppendFormat("{1}", controlBaseId, Environment.NewLine)
  code.AppendFormat("<{0} Id=""{1}{2}"" RunAt=""server""", inputControlType, controlPrefix, controlBaseId)
  
  If inputMaxLength > -1 Then
    code.AppendFormat(" MaxLength=""{0}""", inputMaxLength)
  End If
  code.AppendFormat("/>{0}", Environment.NewLine)
  
  If isRequired Then
    code.AppendFormat("{0}", Environment.NewLine)
  End If

  If Not validationPattern Is Nothing Then
    Select Case LCase(validationPattern)
      Case "personnummer", "pnr", "organisationsnummer", "orgnummer", "orgnr", "orgno" : validationPattern = "^\d{6}-\d{4}\d{10}$"
      Case "telefonnummer", "tel", "telno", "telnr" : validationPattern = "^[\+]\d+$"
      Case "epost", "e-mail", "email" : validationPattern = "^\w+@\w+\.\w+$"
      Case "url", "webbadress", "webbaddress" : validationPattern = "^.*?\.\w+$"
    End Select
    code.AppendFormat("{0}", Environment.NewLine)
  End If
  selection.Text = code.ToString()
  DTE.UndoContext.Close()
End Sub

Have fun! ;-)

Comments

Popular posts from this blog

Auto Mapper and Record Types - will they blend?

Unit testing your Azure functions - part 2: Queues and Blobs

Testing WCF services with user credentials and binary endpoints