Using Basic File I/O in Visual Basic 2010

All riches are multiplied by the simple process of sharing them where they may serve others.
--Proverb

Introduction

I get so caught up in writing about the latest and greatest features that I have to remind myself that everyday there are new developers popping up and the seemingly simple is not always simple to them, yet. Going to User Group meetings and conferences helps remind me of this. After a typical presentation on Cloud Computing, WCF, Dynamic Objects or LINQ, people will often ask basic questions or when queried they will ask for presentations on some of the basics.

This article will cover managing directories, using temporary files, copying, deleting, and renaming files, and reading and writing from text files. As a bonus I will include a new feature, DynamicObjects, which will show you a cool way to dynamically map text file data to objects at runtime. The last example is a feature of .NET 4 and the Dynamic Language Runtime (DLR).

Managing Directories

I could easily write something like, it doesn't seem that long ago that one had to drag out a copy of PC Interrupts: A Programmer's Reference to BIOS, DOS, and Third-Party Calls a Programmer's Reference to BIOS, DOS, and Third-Party Calls by Jim Kyle and Ralf Brown to write low-level calls to manage the file system. In truth though, file management has been part of most Windows-based programming frameworks for better than fifteen years now. That's a good thing because writing low-level interrupt handlers is time consuming and often very error prone. One still needs to manage the file system of PCs, but the .NET framework is rich in file system management tools.

To reiterate the scenario described, I want to query Internet data, store it locally in text files, and use that data to create a knowledgebase. The skills you will need include managing folders, files, queried XML results, and using those results in a meaningful way. There are a lot of ways to do this-use a database or use the XML queried results directly with LINQ to XML. To demonstrate File IO I chose to demonstrate a file-based solution. To spice the solution up I also elected to use DynamicObjects, which is a new part of the Dynamic Language Runtime (DLR) in Visual Studio 2010 and .NET 4.0. The content that follows walks you through folder management, reading and writing files, querying XML with LINQ, and converting the data results into manageable objects with the DLR. The first step is basic; you will want to create a location for the downloaded data.

You can create a location on a computer's file system by importing System.IO and creating a folder. The following code fragment demonstrates how to determine if a folder exists and creating the folder if it isn't already present.

Const filepath As String = "c:\temp\iodemo"

' Create a directory
If (Not Directory.Exists(filepath)) Then
  Directory.CreateDirectory(filepath)
  Console.WriteLine(filepath + " created")
End If

Directory.Exists and Directory.CreateDirectory are shared methods, so they are called with the Directory class as opposed to an instance of the Directory class.

Using Temporary Files

The next thing you will want to do is create a file to store the data. A unique file name is useful. You can combine shared method calls to Path.GetTempFileName, File.Move, File.Exists, and FileSystem.Rename to create and re-locate a new file. The following method--in Listing 1--completes creating a new file in a pre-determined folder.

Listing 1: Create a new folder and a uniquely named file at a pre-determined location.

Private Function GetNewFile() As String
    Const filepath As String = "c:\temp\iodemo"

    ' Create a directory
    If (Not Directory.Exists(filepath)) Then
      Directory.CreateDirectory(filepath)
      Console.WriteLine(filepath + " created")
    End If


    ' Create a temporary file
    Dim temp As String = Path.GetTempFileName()
    Console.WriteLine(temp + " created")

    ' Move the file to my temp
    Dim originalFile As String = filepath + "\" + Path.GetFileName(temp)
    File.Move(temp, originalFile)
    If (File.Exists(originalFile)) Then
      Console.WriteLine(originalFile + " exists")
    End If

    ' Rename the file in place
    Dim newFile As String = Path.ChangeExtension(originalFile, "txt")
    FileSystem.Rename(originalFile, newFile)

    If (File.Exists(newFile)) Then
      Console.WriteLine("{0} was changed to {1}", originalFile, newFile)
    End If
    Return newFile
End Function

The first five lines repeat the code for creating the folder. Path.GetFileName creates a file in the folder designated as the current user's temporary file location with the extension .tmp. You could leave the file there, leave the default extension, but I find these files too hard to find in Windows Explorer. For instance, Path.GetFileName on my workstation created the following file: C:\Users\pkimmel.SOFTCONCEPTS\AppData\Local\Temp\tmpCE6B.tmp. The remaining code moves the file to my created folder and changes the extension to .txt. (Again, you don't have to do this, but it demonstrates more features of System.IO.

The variable originalFile is set to the created folder and just the filename part of the temporary file. File.Move moves the temporary file to the created folder. The variable newFile is set to the original file and Path.ChangeExtension sets the temporary filename string to txt. The value of newFile will be c:\temp\iodemo\tempfile.txt. FileSystem.Rename changes the .tmp file in the created folder to the same name except with the .txt extension. Finally, the newFile is returned from the GetNewFile function.



Using Basic File I/O in Visual Basic 2010

Copying, Deleting, and Renaming Files

GetNewFile demonstrates how to copy and rename a file. When you are all done with the temporary file the following cleanup method will delete the temporary file.

Private Sub Cleanup(ByVal newFile As String)
  ' Cleanup
  If (File.Exists(newFile)) Then
    File.Delete(newFile)
    Console.WriteLine("Temporary file {0} cleaned up", newFile)
  End If
End Sub

In practice, if the actual file cleaned up everything you could shorten this whole process by simply using the same filename and path every time-c:\temp\iodemo\data.txt. Doing so would require a whole lot less code, but demonstrate much less of the file system. The reasons for creating the unique file every time may entail a multi-user system where data files are co-located or kept around for a while.

Reading and Writing File Data

The next step in our scenario is to obtain some data, extract the salient bits, and store the data in our created text file. The method WriteData-see Listing 2-using LINQ to XML to query Twitter for specific tweets, extract URL, Content, and Author information, and write that information to a text file in a per-determined format. The sample uses name=value pairs, making a self-describing text dictionary.

Listing 2: Writing queried Tweet information to a text file.

Private Sub WriteData(ByVal newFile As String)
  ' Get some data from the Internet
  Dim doc As XDocument = 
    XDocument.Load("http://search.twitter.com/search.atom?lang=en&q=DevExpress")
  Dim xmlns As XNamespace = "http://www.w3.org/2005/Atom"

  ' Write a  LINQ query to read some values
  Dim results = From entry In doc.Descendants(xmlns + "entry")
                Select New With {
                  .Url = entry.Element(xmlns + "link").Attribute("href").Value,
                  .Content = entry.Element(xmlns + "title").Value,
                  .Author = entry.Element(xmlns + "author").Element(xmlns + "name").Value
                }

  ' Write CSV Text File - use array.foreach and Sub Lambda

  Dim allLines As List(Of String) = New List(Of String)()

  Array.ForEach(results.ToArray(), Sub(item)
    allLines.Add(String.Format("Url={0},Content={1},Author={2}",
    item.Url, item.Content, item.Author))
    End Sub)

  ' Write all lines - just to show how it is done
  File.AppendAllLines(newFile, allLines)
End Sub

There is a lot going on in WriteData, so let's break it down. XDocument.Load is part of LINQ to XML. XDocument.Load loads the results of an XML document into the XDocument variable. The URL represents a Twitter query; in the example the URL is querying Twitter about queries related to DevExpress.

The next statement starting with Dim results is a LINQ to XML query, querying the XML document for URL, Content, and Author values in the XML. The Select New With clause is referred to as a projection. The Select clause is creating a brand new anonymous type that is equivalent to a class containing the properties URL, Content, and Author. The actual values are read from the various Element and Attribute values of the returned XML document. Array.ForEach is shorthand for a For Each statement. The second parameter Sub(item) is a subroutine, multi-line Lambda expressions that converts the object values into name and value pairs and stores them in allLines. The shared method File.AppendAllLines writes all of the text data to the text file created in Listing 1. If you don't know, a Lambda expression is simply a very short, condensed notation for a function or subroutine call and variously provides values for functions like Array.ForEeach that have an expressed argument type of Func(Of T), Action(Of T), or Predicate(Of T).

Using Basic File I/O in Visual Basic 2010

Mapping Text Data to Dynamic Objects

In the good old days programmers often read and parsed XML or legacy text data manually and populated a pre-defined class with the read values. You can still do that, but Dynamic Objects make it easy to use late-bound objects and treat such data as an object without pre-defining said object. The object is implicit in that you define how the data is treated at runtime rather than pre-defining constructors and properties. That is, you can inherit from System.Dynamic.DynamicObject and write code that says how to treat the comma-delimited name and value pairs.

The TweetObject class demonstrates a DynamicObject that permits you to access TweetObject.Url, TweetObject.Author, and TweetObject.Content as if these were actual properties in the TweetObject class, as you will see in Listing 3 they are not.

Listing 3: A DynamicObject that uses late binding to permit access to the text file values URL, Content, and Author as if they were members of a class.

Public Class TweetObject
  Inherits DynamicObject

  Private line As String
  Private url As String
  Private author As String
  Private content As String


  Public Sub New(ByVal line As String)
    Me.line = line
    Dim items As String() = line.Split(",")

    Dim s As String

    For Each s In items
      If (s.StartsWith("Url")) Then
        url = s
      ElseIf (s.StartsWith("Content")) Then
        content = s
      ElseIf (s.StartsWith("Author")) Then
        author = s
      End If
    Next
  End Sub


  Public Overrides Function TryGetMember(
    ByVal binder As System.Dynamic.GetMemberBinder, ByRef result As Object) As Boolean

    If (binder.Name = "Url") Then
      result = url.Substring(url.IndexOf("=") + 1)
      Return True
    ElseIf (binder.Name = "Content") Then
      result = content.Substring(content.IndexOf("=") + 1)
      Return True
    ElseIf (binder.Name = "Author") Then
      result = content.Substring(author.IndexOf("=") + 1)
      Return True
    Else
      result = ""
      Return False
    End If

  End Function
End Class

TweetObject accepts a line of text as input. The line of text is from the text file written in WriteData. The URL, Content, and Author sub-strings are stored. When the code encounters tweetObject.Url for example, TryGetMember is called by the DLR and the actual value is extracted from the string and returned. TryGetMember returns a Boolean true if successful and the actual data is returned in the ByRef result parameter. All of the code is put together to orchestrate a solution that treats Tweet information as instances of TweetObjects.

Listing 4: All of the code-from creating the file folder, querying Twitter, writing the desired data, and then using the data in the text file as if it represented instances of TweetObject.

Imports System.IO
Imports System.Xml.Linq
Imports System.Dynamic

Module Module1

    Sub Main()

      Dim newFile As String = GetNewFile()
      WriteData(newFile)

      
      ' Read all lines
      Dim readLines As String() = File.ReadAllLines(newFile)
      Array.ForEach(readLines, Sub(line) Console.WriteLine(line))

      ' Convert to a DynamicObject
      Dim str As String
      Dim tweets As List(Of TweetObject) = New List(Of TweetObject)

      For Each str In readLines

        Dim Tweet As Object = New TweetObject(str)
        Console.WriteLine("=======================================")
        Console.WriteLine(Tweet.Url)
        Console.WriteLine(Tweet.Content)
        Console.WriteLine(Tweet.Author)
        Console.WriteLine("=======================================")
        tweets.Add(Tweet)
      Next

      Cleanup(newFile)
      
      Console.ReadLine()
    End Sub

    Private Function GetNewFile() As String
      Const filepath As String = "c:\temp\iodemo"

      ' Create a directory
      If (Not Directory.Exists(filepath)) Then
        Directory.CreateDirectory(filepath)
        Console.WriteLine(filepath + " created")
      End If


      ' Create a temporary file
      Dim temp As String = Path.GetTempFileName()
      Console.WriteLine(temp + " created")

      ' Move the file to my temp
      Dim originalFile As String = filepath + "\" + Path.GetFileName(temp)
      File.Move(temp, originalFile)
      If (File.Exists(originalFile)) Then
        Console.WriteLine(originalFile + " exists")
      End If

      ' Rename the file in place
      Dim newFile As String = Path.ChangeExtension(originalFile, "txt")
      FileSystem.Rename(originalFile, newFile)

      If (File.Exists(newFile)) Then
        Console.WriteLine("{0} was changed to {1}", originalFile, newFile)
      End If
      Return newFile

    End Function

    Private Sub WriteData(ByVal newFile As String)
      ' Get some data from the Internet
      Dim doc As XDocument = XDocument.Load(
        "http://search.twitter.com/search.atom?lang=en&q=DevExpress")
      Dim xmlns As XNamespace = "http://www.w3.org/2005/Atom"

      ' Write a  LINQ query to read some values
      Dim results = From entry In doc.Descendants(xmlns + "entry")
                    Select New With {
                      .Url = entry.Element(xmlns + "link").Attribute("href").Value,
                      .Content = entry.Element(xmlns + "title").Value,
                      .Author = entry.Element(xmlns + "author").Element(
                        xmlns + "name").Value
                    }

      ' Write CSV Text File - use array.foreach and Sub Lambda

      Dim allLines As List(Of String) = New List(Of String)()

      Array.ForEach(results.ToArray(), Sub(item)
        allLines.Add(String.Format("Url={0},Content={1},Author={2}", 
          item.Url, item.Content, item.Author))
      End Sub)

      ' Write all lines - just to show how it is done
      File.AppendAllLines(newFile, allLines)
    End Sub

    Private Sub Cleanup(ByVal newFile As String)
      ' Cleanup
      If (File.Exists(newFile)) Then
        File.Delete(newFile)
        Console.WriteLine("Temporary file {0} cleaned up", newFile)
      End If
    End Sub


End Module

Public Class TweetObject
  Inherits DynamicObject

  Private line As String
  Private url As String
  Private author As String
  Private content As String


  Public Sub New(ByVal line As String)
    Me.line = line
    Dim items As String() = line.Split(",")

    Dim s As String

    For Each s In items
      If (s.StartsWith("Url")) Then
        url = s
      ElseIf (s.StartsWith("Content")) Then
        content = s
      ElseIf (s.StartsWith("Author")) Then
        author = s
      End If
    Next
  End Sub


  Public Overrides Function TryGetMember(
    ByVal binder As System.Dynamic.GetMemberBinder, ByRef result As Object) As Boolean

    If (binder.Name = "Url") Then
      result = url.Substring(url.IndexOf("=") + 1)
      Return True
    ElseIf (binder.Name = "Content") Then
      result = content.Substring(content.IndexOf("=") + 1)
      Return True
    ElseIf (binder.Name = "Author") Then
      result = content.Substring(author.IndexOf("=") + 1)
      Return True
    Else
      result = ""
      Return False
    End If

  End Function
End Class

In C# you use the new keyword dynamic to indicate that a variable is a dynamic object. In VB2010 you declare the instance of the DynamicObject as an Object and initialize it to an instance of your class that inherits from DynamicObject, as demonstrated in the statement Dim Tweet As Object = New tweetObject(str).

From the second half of the Main method you can see that the individual lines of tweet text data is actually treated as if the class TweetObject contained the properties URL, Content, and Author. These are dynamic, late bound properties rather than static, pre-defined, early bound members.

Summary

Knowing as much about the .NET framework as possible yields choices. Rather than just figuring out a way of solving a problem, mastering the .NET Framework and VB lets you choose the best way to solve a problem in a given set of circumstances. When writing a column it is often useful to explore verbose ways of solving problems, which is what this article does. You learned about File IO-folder and file management-LINQ to XML, and the DLR and DynamicObjects. Could you write the solution with just the Twitter query and LINQ to XML? Absolutely. You may or may not want to depending on your circumstances. That part of the puzzle is up to you.





About the Author

Paul Kimmel

Paul Kimmel is the VB Today columnist for CodeGuru and has written several books on object-oriented programming and .NET. Check out his upcoming book Professional DevExpress ASP.NET Controls (from Wiley) now available on Amazon.com and fine bookstores everywhere. Look for his upcoming book Teach Yourself the ADO.NET Entity Framework in 24 Hours (from Sams). You may contact him for technology questions at pkimmel@softconcepts .com. Paul Kimmel is a Technical Evangelist for Developer Express, Inc, and you can ask him about Developer Express at paulk@devexpress.com and read his DX blog at http:// community.devexpress.com/blogs/paulk.

Comments

  • There are no comments yet. Be the first to comment!

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • The explosion in mobile devices and applications has generated a great deal of interest in APIs. Today's businesses are under increased pressure to make it easy to build apps, supply tools to help developers work more quickly, and deploy operational analytics so they can track users, developers, application performance, and more. Apigee Edge provides comprehensive API delivery tools and both operational and business-level analytics in an integrated platform. It is available as on-premise software or through …

  • Live Webinar Tuesday, August 26, 2014 1:00 PM EDT Customers are more empowered and connected than ever, and the customer's journey has grown more complex. Their expectations are growing and trust is diminishing as they may interact with multiple brands through web, mobile and social channels. Considering 70% of the buying process in a complex sale is already complete before prospects are willing to engage with a live salesperson -- it's critical to understand your customers and anticipate their needs.* …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds