Conclusion

Exposing Proprietary Data

To expose custom data in a format that can be easily consumed by a .NET application, you can take any of the following routes:

  • Develop a made-to-measure XML schema for the data and build a .NET class around it.

  • Write a COM based OLE DB provider.

  • Write a simple .NET data provider.

  • Architect and develop a full-fledged .NET data provider.

Where should you concentrate your efforts? Your answer depends on the nature of the data you are providing and the audience you expect to reach. Each approach has pros and cons, and choosing the one that best fits your data is guess what up to you!

Using Tailor-Made Classes

XML lets you reach the same level of universality as OLE DB. In addition, XML is free from the problems that prevented OLE DB from becoming a recognized and widely accepted technology for data access and data sharing. XML is terrifically simple, capable of being read and authored by anyone and, last but not least, ubiquitous (that is, available on all platforms).

In .NET, you have at your disposal many classes and facilities for manipulating XML documents, from XmlReader to XmlDataDocument, and from XmlWriter to XPathNavigator. When you publish a basic XML schema, prospective clients can easily understand it, but bear in mind that using more advanced XML-related technologies could limit your potential audience. For example, the parser used to process your documents on a certain platform might not be updated to support XML schemas, namespaces, XPath, and so on.

In addition to using XML, your .NET applications have other ways to make data accessible and consumable. For example, you can use the classes in the System.Data namespace as a kind of super-array data structure. These classes include the DataTable class and the DataSet class. Serializing a DataSet object to XML output is particularly suitable in situations where interoperability is critical. It does not matter whether you persist data as XML to a file or remote it as a string in a SOAP packet. What does matter is that you serialize your data to XML, as discussed in Chapter 8. When the target of your data is another .NET application, what could be better than using a native .NET data structure such as DataTable?

To verify the practicality of this approach, let s see it in action and write a class that grabs some directory information and then exposes it as XML. We ll make available a DirectoryListing class that returns information about directories in a variety of formats including an XML string, an XML file, and a Data Table class.

The DirectoryListing Class

The DirectoryListing class works like an in-memory version of the old faithful MS-DOS dir command. It collects most of the typical directory information including name, size, type, attributes, date and time of the last access, and user comments. The class considers all the files and subdirectories of a given path. The output is obtained in tabular format by using a DataTable object as well as by using an XML string or an XML file.

Like many other classes in the .NET Framework, the DirectoryListing class has two constructors, one of which is the default.

// Default class constructor public DirectoryListing() {  } // Ad-hoc class constructor public DirectoryListing(String path) { // Stores the path to work on m_path = path;  }

Aside from the constructors, the class has one property named Path and one method named Fill. The Fill method has several overloads so that it can deal with the various outputs. The Path property represents the file system path to the directory for which you are running the query. The class stores the path name into a private data member, which is read and written by using the property s get and set accessors. The property defaults to the root of the C: drive.

private String m_path = "c:\\"; public String Path { get {return m_path;} set {m_path = value;} }

The Fill method triggers the process that retrieves directory and file information and serializes it into the specified output format. The method has four overloads, as shown here:

public String Fill() public String Fill(String path) public int Fill(String path, String filename) public int Fill(String path, DataTable dt)

The first two overloads return an XML string that represents the directory information. If no path is explicitly indicated, the method works for the path currently stored in the Path property. In terms of practical implementation, the first overload works as a special case of the second:

public String Fill() { return Fill(m_path); }

After the class ascertains that the specified path exists, it creates and populates two distinct DataTable objects, one for directory information and one for file information. Next, the two tables are added to a temporary DataSet object, and the corresponding XML representation is returned. This final step is necessary to automatically save the DataTable objects into a single XML stream. You can decide whether to use a temporary DataSet object; you can write down your own XML schema and your own serializer without resorting to the ADO.NET schema for DataSet objects.

public String Fill(String path) { // Make sure the path is correct if (!_VerifyPath(path)) return ""; // Create and fill the table FOR DIRECTORIES DataTable dt1 = _PrepareDataTable("Folder"); _FillDataTableDirectoryInfo(dt1, new DirectoryInfo(path)); // Create and fill the table FOR FILES DataTable dt2 = _PrepareDataTable("File"); _FillDataTableFileInfo(dt2, new DirectoryInfo(path)); // Serialize to XML via a temporary DataSet object DataSet ds = new DataSet("DirectoryListing"); ds.Tables.Add(dt1); ds.Tables.Add(dt2); return ds.GetXml(); }

In the implementation of the DirectoryListing class, located in the DirectoryListing.cs sample file on the companion CD, the third overload of the Fill method writes the XML representation of the contents to a disk file:

public int Fill(String path, String filename) { // Make sure the path is correct if (!_VerifyPath(path)) return -1; // Create and fill the table FOR DIRECTORIES DataTable dt1 = _PrepareDataTable("Folder"); _FillDataTableDirectoryInfo(dt1, new DirectoryInfo(path)); // Create and fill the table FOR FILES DataTable dt2 = _PrepareDataTable("File"); _FillDataTableFileInfo(dt2, new DirectoryInfo(path)); // Add the table to a temporary DataSet object DataSet ds = new DataSet("DirectoryListing"); ds.Tables.Add(dt1); ds.Tables.Add(dt2); // Serialize the DataSet to an XML stream StreamWriter sw = new StreamWriter(filename); ds.WriteXml(sw); sw.Close(); // Return the table row count return dt1.Rows.Count + dt2.Rows.Count; }

The Fill method returns the total number of rows added to the temporary DataSet object, including those representing a subdirectory and those representing a file. The layout of the final XML code looks like the following sample script:

<DirectoryListing> <Folder> <Name>My Apps</Name> <Size>0</Size> <Created>2001-09-05T11:28:18.8048560+02:00</Created> <Attributes>ReadOnly, Directory</Attributes> <Type>&lt;DIR&gt;</Type> <Comment>My favorite handcrafted applications</Comment> </Folder> <Folder> <Name>My Clients</Name> <Size>0</Size> <Created>2001-09-05T11:28:35.9795520+02:00</Created> <Attributes>ReadOnly, Directory</Attributes> <Type>&lt;DIR&gt;</Type> <Comment>Projects I'm taking over for some of my clients</Comment> </Folder> <Folder> <Name>My Lab</Name> <Size>0</Size> <Created>2001-09-05T11:28:44.8222672+02:00</Created> <Attributes>ReadOnly, Directory</Attributes> <Type>&lt;DIR&gt;</Type> <Comment>Results of genetic manipulation of source code</Comment> </Folder> <File> <Name>ToDo.txt</Name> <Size>449</Size> <Created>2001-11-07T17:07:09.6498992+01:00</Created> <Attributes>Archive</Attributes> <Type>Text Document</Type> <Comment /> </File> </DirectoryListing>

As you might have noticed, the XML schema uses a distinct tag name for information representing a directory and for information representing a file. The <Folder> tag is used for directories, and the <File> tag wraps file information. Using two tag names is based on the idea of using two separate tables, one named Folder and one named File. To keep the XML code as simple as possible, I deliberately did not return schema information. When you want to include schema information, modify the Fill method to make the DataSet object send out XML with the schema embedded:

StreamWriter sw = new StreamWriter(filename); ds.WriteXml(sw, XmlWriteMode.WriteSchema); sw.Close();

When the Fill method of the DirectoryListing class is used to get a unique DataTable object, the distinction between folders and files ceases to make sense. The following code shows how to implement the Fill method to retrieve a unique DataTable object:

public int Fill(String path, DataTable dt) { if (!_VerifyPath(path)) return -1; _PrepareDataTable("FolderItem", dt); DirectoryInfo dir = new DirectoryInfo(path); _FillDataTableDirectoryInfo(dt, dir); _FillDataTableFileInfo(dt, dir); return dt.Rows.Count; }

File and folder information are written to a new or existing DataTable object whose name is set as FolderItem. In our example, the method returns the number of rows found in the table at the end of the operation.

Now you have an example of the programming interface for a data container class. Let s see how to create and manipulate data in memory using ADO.NET container objects.

Creating In-Memory Tables

A DataTable object is a sort of heterogeneous, multidimensional array with extra methods that facilitate data access and retrieval. Although you will likely use it to store data originating from a database, the DataTable object (and the DataSet object) is a general-purpose data container that simply holds data, no matter where that data originates. Like a database, the DataTable object knows how to index, filter, select, and sort its child rows. These functionalities are built into the code of the DataTable class and in no way rely on a back-end data source. (It is no accident that objects such as DataSet, DataTable, DataColumn, DataRow, and DataRelation belong to a super namespace such as System.Data and are not part of a provider-specific namespace such as System.Data.Sql Client and System.Data.OleDb.) The DataTable object is just a .NET class good at storing in memory any data that can be described as a table by using a collection of rows and columns, so don t be too scared to use it to expose proprietary data or volatile information.

You create a new DataTable object by using one of its two constructors:

DataTable dt = new DataTable(); DataTable dt = new DataTable("NameOfTheTable");

At this point you have only an empty object with no columns or rows. Prior to adding rows, you must define the columns. The next listing demonstrates how to add the columns needed to properly represent directory and file information:

private void _AddColumnsToTable(DataTable dt) { DataColumn dc1 = new DataColumn(); dc1.DataType = typeof(String); dc1.ColumnName = "Name"; dt.Columns.Add(dc1); dc1 = new DataColumn(); dc1.DataType = typeof(int); dc1.ColumnName = "Size"; dt.Columns.Add(dc1); dc1 = new DataColumn(); dc1.DataType = typeof(DateTime); dc1.ColumnName = "Created"; dt.Columns.Add(dc1); dc1 = new DataColumn(); dc1.DataType = typeof(String); dc1.ColumnName = "Attributes"; dt.Columns.Add(dc1); dc1 = new DataColumn(); dc1.DataType = typeof(String); dc1.ColumnName = "Type"; dt.Columns.Add(dc1); dc1 = new DataColumn(); dc1.DataType = typeof(String); dc1.ColumnName = "Comment"; dt.Columns.Add(dc1); }

To add a column, you need to create a new instance of a DataColumn object and give it at least a data type and a name. You must also add the column object to the Columns collection of the DataTable object. During this process, all data pertaining to the column gets stored internally in the DataTable object. After the object is added, you can blissfully null it out or reassign to it a new instance of the DataColumn class.

A defined set of columns enables a table to accept and store rows of data. Rows are stored using the Rows collection. A DataRow object does not have a public constructor that clients can directly call. You must create rows by using the NewRow method of the DataTable object. The NewRow method takes care of mirroring the columns metadata into the newly created row object.

Directory Information

The following listing shows how to populate a given row with directory-specific information. The work is not adding and filling out the row but getting file system information for the specified directory name.

private void _FillDataTableDirectoryInfo(DataTable dt, DirectoryInfo dir) { // Loops through subdirectories foreach (DirectoryInfo d in dir.GetDirectories()) { DataRow dr = dt.NewRow(); dr["Name"] = d.Name; dr["Size"] = 0; dr["Comment"] = _ReadDirComment(d.FullName); dr["Created"] = d.CreationTime; dr["Type"] = "<DIR>"; dr["Attributes"] = d.Attributes.ToString(); dt.Rows.Add(dr); } }

The .NET Framework provides two powerful classes for identifying files and directories: DirectoryInfo and FileInfo. You find them in the System.IO namespace. The DirectoryInfo structure for the directory being searched is passed on to the helper method shown in the preceding code. This helper method loops internally through the array of subdirectories returned by the GetDirectories method. Each element in this array is another DirectoryInfo object. Accessing the standard properties of a directory is straightforward. The directory attributes are rendered by using an object of type FileAttributes, which is exposed via the Attributes property. Note that when you call a member of the FileAttributes class, the ToString method nicely translates bitwise enumeration values into readable and comma-separated strings such as "ReadOnly, System, Archive".

In the schema we designed for the table, some columns represent information that, although pertinent to the directory, is not natively exposed by the DirectoryInfo class. This information includes the size of the directory (intended as the recursive sum of the size of child files and subdirectories), the user comment, and the item type. Size and item type are specific to each file, so let s set them to static text for directories. The user comment is directory-specific. It is not part of the DirectoryInfo interface because it originates from a local file named desktop.ini, which is not supported on all platforms where the .NET Framework is available.

The Desktop.ini File

The desktop.ini file is a configuration file that is resident in the folder to which its contents are applied. The desktop.ini file is not supported on all versions of Microsoft Windows 95 and Microsoft Windows NT 4. Nothing prevents you from creating a desktop.ini file in any folder on a Windows NT 4 machine, but the operating system won t be giving the file the special meaning it has on machines running Microsoft Windows 98, Microsoft Windows 2000, or Microsoft Windows XP.

A desktop.ini file is a short text file that can contain a comment about the contents of the folder. It can also indicate a nonstandard icon for Microsoft Windows Explorer to use when rendering the contents of the folder. The Web-like shell available on Windows 98 and later systems attributes a special meaning to this file when the containing folder is marked read-only. When the folder is read-only, Explorer draws the folder with a custom icon. In addition, it uses the comment to display a tooltip when the mouse hovers over the folder in the shell view. (See Figure 10-1.)

Figure 10-1

The tooltip resulting from a desktop.ini file.

The typical contents of a desktop.ini file are shown here:

[.ShellClassInfo] Infotip=Results of genetic manipulation of source codeIconFileChemistry.ico IconIndex=0

This file feature is broadly used in the Windows XP shell, which also provides a system dialog box for creating user comments without resorting to modifying desktop.ini files. Incidentally, in Windows XP, desktop.ini files are marked as system files. No method in the .NET Framework or in the Microsoft Win32 and Windows XP SDKs provides a function for extracting the description of a folder. Nevertheless, both in Windows 2000 and in Windows XP, you can display the Comment column in the shell view and know at a glance about the contents of a given folder. (See Figure 10-2.)

Figure 10-2

The content of the desktop.ini file used in the Windows shell.

Desktop.ini is a plain old .INI file, and the entry we re mainly interested in is Infotip. Getting at that information is as easy as reading some bytes from a text file: you use the private method _ReadDirComment. (The complete source code for DirectoryListing class is in DirectoryListing.cs, available on the companion CD.)

File Information

The .NET class that returns system information about a particular file is FileInfo. All files contained in a folder can be accessed by using the GetFiles method on the DirectoryInfo object, which represents the parent directory. The following code shows how to populate a table row associated with a file in the folder:

private void _FillDataTableFileInfo(DataTable dt, DirectoryInfo dir) { // Loops through files foreach (FileInfo f in dir.GetFiles()) { DataRow dr = dt.NewRow(); dr["Name"] = f.Name; dr["Size"] = f.Length; dr["Comment"] = ""; dr["Created"] = f.CreationTime; dr["Type"] = _ReadItemType(f.Extension); dr["Attributes"] = f.Attributes.ToString(); dt.Rows.Add(dr); } }

User comments are not supported for files through the desktop.ini interface. However, as long as you use an NTFS file system, you can associate extra streams with each file in the system and store comments and other normally invisible information there.

Both files and directories can have an extension, but the Windows shell uses file extensions to catalog files into classes. For example, all files with a .BMP extension belong to the same logical group and share the same shell settings such as the icon, context menu, tooltip, and Properties dialog box. Each class of files has a name and a description. Windows Explorer displays the description in the Type column. Can you get this setting information programmatically? You can when you access the system registry.

Getting File Type Information

To get hold of the string displayed in the Type column, you first need to read the default value of the following key:

HKEY_CLASSES_ROOT\.bmp

Next you use that string to gain further access to the registry. Typically, the string you read is Paint.Picture. (This name is machine-specific.) The next access attempt reads the default value from the following:

HKEY_CLASSES_ROOT\Paint.Picture

The next code listing shows how to read the default value by using the .NET Registry class. You must import the namespace Microsoft.Win32.

private String _ReadItemType(String sFileExtension) { String strType = "File"; if (sFileExtension == "") return strType; // Open the .EXT key (i.e., .BMP) RegistryKey reg = Registry.ClassesRoot.OpenSubKey(sFileExtension); try { // Read the indexer key (i.e., Paint.Picture) String indexerKey = reg.GetValue(null).ToString(); reg.Close(); // Access the indexer key and read the value reg = Registry.ClassesRoot.OpenSubKey(indexerKey); strType = reg.GetValue(null).ToString(); reg.Close(); } catch {} return strType; }

At this point in the code, all pieces of the DirectoryListing jigsaw puzzle are in the right place. Now let s see how DirectoryListing actually works.

Using the DirectoryListing Class

You use the DirectoryListing class whenever you need to process or display groups of files. Let s consider a Windows Forms application that selects and displays all files under a certain root folder. Figure 10-3 shows the final result.

Figure 10-3

The sample application using the DirectoryListing class.

When the Go button is clicked, the following code runs:

private void btnGo_Click(object sender, System.EventArgs e) { DirectoryListing dir = new DirectoryListing(); DataTable dt = new DataTable(); // Fill the table status.Text = String.Format("Loading from {0} ...", textBox1.Text); int nItems = dir.Fill(textBox1.Text, dt); if (nItems <0) { status.Text = String.Format("An error occurred accessing the path: {0}.", textBox1.Text); return; } status.Text = String.Format("{0} item(s) found.", nItems); // Shows the XML text textBox2.Text = dir.Fill(textBox1.Text); // Take care of the UI Form1.ActiveForm.Text = String.Format("DIR - {0}", dir.Path); dataGrid1.DataSource = dt.DefaultView; }

The Fill method takes the path name and populates a newly created DataTable object with the structure described in the preceding code. In addition to having a sophisticated programmable interface, a DataTable object is a valid source for data bound controls. The following line of code is all you need to populate the grid of Figure 10-3:

grid.DataSource = dt;

You can apply a mask to the rows and filter them based, say, on the file extension. To do that, you simply create a DataView object on top of the table. Any data bound application that builds views of working data is better off using DataView objects throughout, even when no row filter is actually needed. In light of this, the preceding line of code should be rewritten like this:

grid.DataSource = dt.DefaultView;

Creating a view based on the file extension is as easy as writing the following code. The strFilter variable contains any file spec string with wildcards (e.g., *.exe). Figure 10-4 shows how the sample application handles filtered views.

// Casting requires that you always use DataView objects to bind data DataView dv = (DataView) grid.DataSource; dv.RowFilter = "Name Like '" + strFilter + "'";

Figure 10-4

A filtered view of the executables in the Windows system folder.

Rendering a Data View to XML

In ADO.NET, the DataSet object is the only object that can save file contents to XML. To render to XML only the subset of rows included in a given view, you have to create a temporary DataSet object filled with only the rows in the view. The following code demonstrates how to do this:

private String _DataViewToXml(DataView dv) { DataSet ds = new DataSet("DirectoryListing"); DataTable dt = dv.Table.Clone(); ds.Tables.Add(dt); foreach(DataRowView drv in dv) dt.ImportRow(drv.Row); return ds.GetXml(); }

You first clone the structure of the table behind the DataView object. You then add the new DataTable object to the DataSet object and fill it with all rows in the current view. The ImportRow method automatically creates a duplicate of the DataRow object.

A Web Forms Application

The DirectoryListing class works as an extremely simple data adapter object and is independent of any database. Since the class exposes its data through either XML or a DataTable object, nothing prevents the data from being used over the Web in the context of an ASP.NET application. Figure 10-5 shows the class at work in an ASP.NET page.

Figure 10-5

A Web Forms application using the DirectoryListing class.

You link the class to the page using the @ Assembly page directive and then use it as follows:

<%@ Assembly src="/books/2/368/1/html/2/DirectoryListing.cs" %>  public DataView CreateDataSource(String strDir) { DirectoryListing dir = new DirectoryListing(); DataTable dt = new DataTable(); dir.Fill(strDir, dt); return dt.DefaultView; }

The full source code for the Windows Forms and Web Forms applications using the DirectoryListing class is available on the companion CD in the Win DirList and WebDirList folders, respectively.

note

So far I have deliberately skipped over how to apply and persist changes. But don t panic I ll be discussing this topic soon.

Using OLE DB Providers

Creating an ad-hoc .NET class and/or a customized XML schema is definitely the first and probably the simplest option to consider when you want to expose proprietary data to .NET applications. Depending on your target audience, a .NET-to-.NET solution might not be optimal, even when you base it on XML.

OLE DB providers are fully supported in .NET by the generic managed provider for OLE DB data sources. The managed provider works for .NET applications in much the same way ADO works for COM applications. The common model is shown in Figure 10-6.

Figure 10-6

OLE DB providers in the context of .NET applications.

The OLE DB .NET Data Provider is made of all data access classes having the OleDb prefix and defined in the System.Data.OleDb namespace. This managed provider takes advantage of COM Interop Services to connect to the specified OLE DB provider for a given data source. Any interaction between a .NET application and an OLE DB provider takes place outside the CLR because such an interaction is actually between a .NET module and a COM module.

The OLE DB .NET Data Provider provides a bridge that enables a .NET application to access data through the same channels as a Win32 COM application. It gives you immediate access to all the existing OLE DB providers and saves your current investments so that applications can continue to successfully call into existing OLE DB providers.

If you have to design a software mechanism to make proprietary data available to the widest possible audience, writing an OLE DB provider is a valuable approach. Although its overall design is fairly complex and it is strictly COM-based, the OLE DB provider still represents your only option for writing a single module to make the wrapped data accessible to all.

In terms of interoperability and overall simplicity, only XML can surpass OLE DB. OLE DB has in its favor, however, a more thorough representation of the capabilities of the data source than any you could design using XML data structures. XML is only about data description, whereas OLE DB is about a common API for accessing data. To write OLE DB providers, you can use the ATL classes available from Microsoft Visual Studio 6.

caution

Not all OLE DB providers supply the same set of features, so the .NET data provider for OLE DB is not guaranteed to work with any OLE DB provider. You can safely use it with the provider for SQL Server (versions 6.5 and 7), Oracle (MSDAORA), and the Microsoft Jet engine. You cannot use it with the OLE DB provider for ODBC (MSDASQL). Test it carefully with any other provider you plan to use.

Although supported by .NET, OLE DB providers are no longer the preferred way to access data in the .NET platform. A different breed of components natively integrated in the .NET universe has taken their place: .NET data providers.



Building Web Solutions with ASP. NET and ADO. NET
Building Web Solutions with ASP.Net and ADO.NET
ISBN: 0735615780
EAN: 2147483647
Year: 2002
Pages: 75
Authors: Dino Esposito

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net