Title: Web Method To Display Server Directory Tree Remotely on Mobile Device Author: Greg Dubinovskiy Email: [email protected] Environment: VB.NET/C# Keywords: Web Service, File Object, DirectoryInfo Level: Intermediate Description: Web Method To Display Server Directory Tree Remotely on Mobile Device Section Miscellaneous SubSection General
One of the features available in Siccolo - Management Tool For SQL Server - ability to backup a SQL Server database from a Windows Mobile device (see my previous article at Web Method to Backup Database Remotely at Siccolo Articles). To make backup process experience friendlier and more similiar to Microsoft SQL Server Enterprise Manager, I needed to allow user select where - which directory - actual database backup file is to be saved.
So, if MS Enterprise Manager shows this: | |
My Siccolo Mobile Management Tool gives user this: | |
create table #tmp (directory_name varchar(255))
insert into #tmp
exec master.dbo.xp_cmdshell "dir c:\ /B /A:D /S"
select * from #tmp -- and we can produce XML out of this with "for xml auto" clause!
drop table #tmp
see also - "How to Create a Directory (Folder) at Siccolo Articles)
First, web method to allow client to retrieve list of logical disks on the server:
<WebMethod()> Public Function ExplorerFoldersTree(ByVal ServerAddress As String, _
ByVal DiskPath As String, _
ByVal UseImpersonation As Boolean, _
ByVal IncludeWindowsDirectory As Boolean, _
ByRef ErrorInfo As String) As String
Try
Dim FoldersTree_XML As String = ""
Dim objTree As DirectoryTree = New DirectoryTree(ServerAddress, DiskPath)
Dim impersonationContext As System.Security.Principal.WindowsImpersonationContext
If UseImpersonation Then
impersonationContext = _
CType(CType(User, WindowsPrincipal).Identity, _
System.Security.Principal.WindowsIdentity).Impersonate()
End If
If objTree.PopulateFoldersTree_XML(FoldersTree_XML, IncludeWindowsDirectory, ErrorInfo) Then
FoldersTree_XML = Replace(FoldersTree_XML, "\\" & ServerAddress & "\" & Replace(DiskPath, ":", "$"), _
DiskPath)
ErrorInfo = ""
If UseImpersonation Then
impersonationContext.Undo()
End If
Return FoldersTree_XML
Else
If UseImpersonation Then
impersonationContext.Undo()
End If
Return ""
End If
Catch ex As Exception
ErrorInfo = ex.Message
Return ""
End Try
End Function
As you can notice, I'm using impersonationContext
, because web service needs to
"assume" an identity of authenticated user to be able to access resources on the machine other than web service host.
On the server side, to retrieve authenticated user, we need to use System.Web.Services.WebService.User and then
"impersonate" - Impersonate()
- to "Impersonate the user represented by the WindowsIdentity object."
m_objFoldersTree
- dataset with "structured list" of folders:
...
<Folder>
<f>c:\CA_LIC</f>
<p>c:</p>
</Folder>
<Folder>
<f>c:\Clients</f>
<p>c:</p>
</Folder>
<Folder>
<f>c:\Clients\Map</f>
<p>c:\Clients</p>
</Folder>
...
For every "folder" element - there's a folder name (attribute <f>) and name of parent folder (attribute <p>)
m_objFoldersTree
created:
m_objFoldersTree = New DataSet("QueryResults")
m_objFoldersTree.Tables.Add("Folder")
m_objFoldersTree.Tables(0).Columns.Add("f", System.Type.GetType("System.String")) 'folder path
m_objFoldersTree.Tables(0).Columns.Add("p", System.Type.GetType("System.String")) 'folder parent
then, PopulateFoldersTree_XML()
creates XML String based on directory structure, from m_objFoldersTree
:
Public Function PopulateFoldersTree_XML(ByRef FoldersTree_XML As String, _
ByVal IncludeWindowsDirectory As Boolean, _
ByRef ErrorInfo As String) As Boolean
Dim ServerWindowsDirectory As String = ""
If Not IncludeWindowsDirectory Then
'get windows directory
ServerWindowsDirectory = Replace(GetWindowsDirectory_UsingWMI(), ":", "$")
'exclude it from funal result
End If
If Not PopulateFoldersTree(ServerWindowsDirectory, ErrorInfo) Then Return False
Dim objStringWriter As New System.IO.StringWriter()
m_objFoldersTree.WriteXml(objStringWriter, XmlWriteMode.WriteSchema)
FoldersTree_XML = "" & objStringWriter.ToString()
Return True
End Function
In this method, PopulateFoldersTree_XML()
, I'm using Dataset
class to create XML string using WriteXML() method:
...
m_objFoldersTree = New DataSet("QueryResults")
...
...
Dim objStringWriter As New System.IO.StringWriter()
m_objFoldersTree.WriteXml(objStringWriter, XmlWriteMode.WriteSchema)
...
where WriteXml() Writes the current data, and the schema, for the DataSet using the specified System.IO.Stream and
XmlWriteMode. XmlWriteMode.WriteSchema tells application to Write the current contents of the DataSet as XML data
with the relational structure as inline XSD schema.
...
m_objFoldersTree.Tables(0).Columns.Add("f", System.Type.GetType("System.String"))
...
Columns.Add() - Creates and adds a DataColumn object to the DataColumnCollection.
Here, Add() creates a DataColumn object with the specified name and type to the DataColumnCollection.
To let Add() know type of column, we need to pass a parameter of type DataColumn.DataType.
To do that, we can use GetType() method which gets Type with the specified name - System.Type.GetType("System.String")
GetWindowsDirectory_UsingWMI()
retrieves windows directory name using WMI
(can also be retrieved from registry):
Public Function GetWindowsDirectory_UsingWMI() As String
Try
Dim WindowsDirectory As String = ""
Dim query As ManagementObjectSearcher
Dim queryCollection As ManagementObjectCollection
Dim query_command As String = "SELECT * FROM Win32_OperatingSystem"
Dim msc As ManagementScope = New ManagementScope("\\" & m_ServerAddress & _
"\root\cimv2")
Dim select_query As SelectQuery = New SelectQuery(query_command)
query = New ManagementObjectSearcher(msc, select_query)
queryCollection = query.Get()
Dim management_object As ManagementObject
For Each management_object In queryCollection
WindowsDirectory = management_object("WindowsDirectory")
Next
Return WindowsDirectory
Catch ex As Exception
Return ""
End Try
End Function
m_objFoldersTree
dataset (I used Recursive Hierarchical DataSet Techniques):
Public Function PopulateFoldersTree(ByVal ExcludeThisDirectory As String, _
ByRef ErrorInfo As String) As Boolean
Try
m_objFoldersTree.Tables(0).Rows.Clear()
m_FullPathParentFolder = "\\" & m_ServerAddress & "\" & Replace(m_ParentFolder, ":", "$")
Dim TopFolder As DirectoryInfo = New DirectoryInfo(m_FullPathParentFolder)
Dim r As Object = New Object() {m_FullPathParentFolder, ""}
m_objFoldersTree.Tables(0).Rows.Add(r)
GetSubFolders(m_FullPathParentFolder, TopFolder, ExcludeThisDirectory)
Dim objTable = m_objFoldersTree.Tables(0)
m_objFoldersTree.Relations.Add("Self", objTable.Columns("f"), _
objTable.Columns("p"), False)
Return True
Catch ex As Exception
ErrorInfo = ex.Message
Return False
End Try
End Function
Private Sub GetSubFolders(ByVal ParentFolder As String, _
ByVal Folder As DirectoryInfo, _
ByVal ExcludeThisDirectory As String)
Dim collFolders As DirectoryInfo() = Folder.GetDirectories()
Dim subFolder As DirectoryInfo
For Each subFolder In collFolders
Try
If subFolder.Attributes = FileAttributes.Encrypted Or _
subFolder.Attributes = FileAttributes.Hidden Or _
subFolder.Attributes = FileAttributes.Offline Or _
subFolder.Attributes = FileAttributes.ReadOnly Or _
subFolder.Attributes = FileAttributes.System Or _
subFolder.Attributes = FileAttributes.Temporary Or _
InStr(subFolder.FullName, ExcludeThisDirectory, CompareMethod.Text) > 0 Then
'skip this folder
Else
Dim r As Object = New Object() {subFolder.FullName, ParentFolder}
m_objFoldersTree.Tables(0).Rows.Add(r)
GetSubFolders(subFolder.FullName, subFolder, ExcludeThisDirectory)
End If
Catch ex As Exception 'System.Security.SecurityException
End Try
Next
End Sub
Because, this code is used to allow user select where database backup file is to be saved,
I'm excluding server Windows directory (and all it's subfolders) and any folder that is Hidden, System etc.
Now the client.
User interface (form frmServerDiskExplorer, in my application) contains TreeView control
trwFolders to display server directory structure:
So, when user selects a drive (C, D etc) - application calls GetServerExplorerFoldersData_PartialFillTree_Async()
to:
1. get complete directory structure from web service via asynchronous web method call
2. display only top folder in TreeView
Private Sub GetServerExplorerFoldersData_PartialFillTree_Async()
If cboDiskList.Text = "" Then
MessageBox.Show("Please select a drive", _
"Siccolo - Server Disk Explorer", _
MessageBoxButtons.OK, _
MessageBoxIcon.Exclamation, _
MessageBoxDefaultButton.Button1)
Exit Sub
End If
Cursor.Current = Cursors.WaitCursor
trwFolders.Nodes.Clear()
Dim ErrorInfo As String = ""
objSQLManager.ServerExplorerFoldersTreeForm = Me
objSQLManager.GetServerExplorerFoldersData_Async(cboDiskList.Text, ErrorInfo)
End Sub
Where objSQLManager
- class on the client, handling all interactions with web service.
...
...
Private m_objUIServerExplorerFoldersTreeForm As frmServerDiskExplorer
Friend WriteOnly Property ServerExplorerFoldersTreeForm() As frmServerDiskExplorer
Set(ByVal value As frmServerDiskExplorer)
m_objUIServerExplorerFoldersTreeForm = value
End Set
End Property
And GetServerExplorerFoldersData_Async
is the actual asynchronous method call:
Friend Sub GetServerExplorerFoldersData_Async(ByVal DiskPath As String, _
ByRef ErrorInfo As String)
Try
If m_objUIServerExplorerFoldersTreeForm Is Nothing Then
Throw New Exception("User Interface Form is not set!")
End If
ErrorInfo = ""
If m_objSiccoloProcessorAsync Is Nothing Then
Dim objNetworkCredential As System.Net.NetworkCredential = _
New System.Net.NetworkCredential(objLoginInfo.NetworkUserName, _
objLoginInfo.NetworkUserPwd, _
objLoginInfo.NetworkUserDomain)
m_objSiccoloProcessorAsync = New siccolo.Siccolo()
m_objSiccoloProcessorAsync.Credentials = objNetworkCredential
m_objSiccoloProcessorAsync.PreAuthenticate = True
Dim URL As String = ""
If GetWebServiceURL(m_WebServiceSQLServerASMX, URL, ErrorInfo) Then
m_objSiccoloProcessorAsync.Url = URL
Else
Throw New Exception(ErrorInfo)
End If
m_objSiccoloProcessorAsync.Timeout = System.Threading.Timeout.Infinite
End If
m_objSiccoloProcessorAsync.BeginExplorerFoldersTree(objLoginInfo.ServerAddress, _
DiskPath, _
True, _
False, _
ErrorInfo, _
New AsyncCallback(AddressOf _
Me.GetServerExplorerFoldersData_Async_CallBack), _
Nothing)
Catch ex As Exception
ErrorInfo = ex.Message
m_objUIServerExplorerFoldersTreeForm.Invoke(New AsyncCallHandler_GotServerExplorerFoldersData _
(AddressOf m_objUIServerExplorerFoldersTreeForm.GetServerExplorerFoldersData_PartialFillTree_Async_CallBack), _
False, _
"", _
ErrorInfo)
End Try
End Sub
Retrieving server directory structure asynchronous operation is implemented as two methods
named BeginExplorerFoldersTree and
EndExplorerFoldersTree that begin and end the asynchronous operation ExplorerFoldersTree respectively.
'taken from Reference.vb:
...
'''
_
Public Function ExplorerFoldersTree(ByVal ServerAddress As String, _
ByVal DiskPath As String, _
ByVal UseImpersonation As Boolean, _
ByVal IncludeWindowsDirectory As Boolean,
ByRef ErrorInfo As String) As String
Dim results() As Object = Me.Invoke("ExplorerFoldersTree", _
New Object() {ServerAddress, _
DiskPath, _
UseImpersonation, _
IncludeWindowsDirectory, ErrorInfo})
ErrorInfo = CType(results(1),String)
Return CType(results(0),String)
End Function
'''
Public Function BeginExplorerFoldersTree(ByVal ServerAddress As String, _
ByVal DiskPath As String, _
ByVal UseImpersonation As Boolean, _
ByVal IncludeWindowsDirectory As Boolean, _
ByVal ErrorInfo As String, _
ByVal callback As System.AsyncCallback, _
ByVal asyncState As Object) As System.IAsyncResult
Return Me.BeginInvoke("ExplorerFoldersTree", _
New Object() {ServerAddress, _
DiskPath, _
UseImpersonation, _
IncludeWindowsDirectory, _
ErrorInfo}, callback, asyncState)
End Function
'''
Public Function EndExplorerFoldersTree(ByVal asyncResult As System.IAsyncResult, _
ByRef ErrorInfo As String) As String
Dim results() As Object = Me.EndInvoke(asyncResult)
ErrorInfo = CType(results(1),String)
Return CType(results(0),String)
End Function
...
BeginExplorerFoldersTree method signature also includes two additional parameters -
first of these defines an AsyncCallback delegate that references a method
GetServerExplorerFoldersData_Async_CallBack
that is called
when the asynchronous operation completes:
Private Sub GetServerExplorerFoldersData_Async_CallBack(ByVal result As IAsyncResult)
Try
Dim ErrorInfo As String = ""
Dim FoldersTree_XML As String = ""
Dim CallBackResult As Boolean = True
FoldersTree_XML = m_objSiccoloProcessorAsync.EndExplorerFoldersTree(result, _
ErrorInfo)
If ErrorInfo <> "" Or FoldersTree_XML = "" Then
CallBackResult = False
Else
CallBackResult = True
End If
m_objUIServerExplorerFoldersTreeForm.Invoke(New AsyncCallHandler_GotServerExplorerFoldersData _
(AddressOf m_objUIServerExplorerFoldersTreeForm.GetServerExplorerFoldersData_PartialFillTree_Async_CallBack), _
CallBackResult, _
FoldersTree_XML, _
ErrorInfo)
Catch ex_callback As Exception
m_objUIServerExplorerFoldersTreeForm.Invoke(New AsyncCallHandler_GotServerExplorerFoldersData _
(AddressOf m_objUIServerExplorerFoldersTreeForm.GetServerExplorerFoldersData_PartialFillTree_Async_CallBack), _
False, _
"", _
"GetServerExplorerFoldersData_Async_CallBack(): " & ex_callback.Message)
End Try
End Sub
The second additional parameter is a user-defined object.
This object can be used to pass application-specific state information to the method invoked
when the asynchronous operation completes.
GetServerExplorerFoldersData_Async
-> BeginExplorerFoldersTree()
GetServerExplorerFoldersData_Async_CallBack
is executed
GetServerExplorerFoldersData_Async_CallBack
passes control back to form via Invoke() -> m_objUIServerExplorerFoldersTreeForm.Invoke()
Friend Sub GetServerExplorerFoldersData_PartialFillTree_Async_CallBack _
(ByVal CallBackResult As Boolean, _
ByVal FoldersData_XML As String, _
ByVal ErrorInfo As String)
Try
If Not CallBackResult Then
Throw New Exception(ErrorInfo)
End If
m_ServerFoldersData = New DataSet
Dim Reader As New System.IO.StringReader(FoldersData_XML.ToString())
Dim XMLReader As New System.Xml.XmlTextReader(Reader)
XMLReader.Read()
m_ServerFoldersData.ReadXml(XMLReader, XmlReadMode.Auto)
m_ServerFoldersData.AcceptChanges()
XMLReader.Close()
AddTopFolder()
Catch ex As Exception
MessageBox.Show("Failed to retrieve server folders data (async):" & vbCrLf & _
"-----------------------------------" & vbCrLf & _
ErrorInfo & vbCrLf & _
"-----------------------------------", _
"Siccolo - Server Disk Explorer", _
MessageBoxButtons.OK, _
MessageBoxIcon.Exclamation, MessageBoxDefaultButton.Button1)
Finally
Cursor.Current = Cursors.Default
End Try
End Sub
Private Sub trwFolders_AfterSelect(ByVal sender As System.Object, _
ByVal e As System.Windows.Forms.TreeViewEventArgs) _
Handles trwFolders.AfterSelect
If trwFolders.SelectedNode Is Nothing Then Exit Sub
Cursor.Current = Cursors.WaitCursor
txtSelectedFolder.Text = trwFolders.SelectedNode.Tag
m_SelectedPath = trwFolders.SelectedNode.Tag
If trwFolders.SelectedNode.Nodes.Count = 0 Then
ShowSubFolders(trwFolders.SelectedNode)
End If
Cursor.Current = Cursors.Default
End Sub
Private Sub ShowSubFolders(ByRef ParentFolderNode As Windows.Forms.TreeNode)
'find record in m_Folders where folder_parent = ParentFolder
Dim objView As Data.DataView = _
m_ServerFoldersData.Tables(0).DefaultView
Dim strParentFolder As String = ParentFolderNode.Tag
objView.RowFilter = "p='" & strParentFolder & "'" 'p is folder parent
Dim newNode As Windows.Forms.TreeNode = New TreeNode
Dim strNode As String
For index As Integer = 0 To objView.Count - 1
strNode = objView(index)("f") 'f is folder_path
newNode = ParentFolderNode.Nodes.Add(Replace(strNode, strParentFolder, ""))
newNode.Tag = strNode
Next
If objView.Count <> 0 Then ParentFolderNode.Expand()
End Sub
To show only sub folders for a given folder, we can use "filtering" feature with DataViews:
...
Dim objView As Data.DataView = _
m_ServerFoldersData.Tables(0).DefaultView
Dim strParentFolder As String = ParentFolderNode.Tag
objView.RowFilter = "p='" & strParentFolder & "'" 'p is folder parent
...
.RowFilter
- Gets or sets the expression used to filter which rows are viewed in the DataView.
If you would like to read more on this story - please take a look at Siccolo - Free Mobile Management Tool For SQL Server
and more articles at Siccolo Articles
So, as we can see Web Services is quickly finding its way into major system integration development efforts.
In back office and legacy systems alike,
Web Servicess has become a main focus in providing mechanisms to make data move more freely between and across long-closed boundaries.
An entire industry has joined forces and pushes forward in overcoming both technical hurdles such as security and market specific hurdles such as the need of standardized message formats.
While these efforts continue, an entirely different market is rapidly maturing: the mobile devices, software and services market.
Very interesting solution options arise in the cross section, where Web Services meets mobility.
The Microsoft® Windows® .NET Compact Framework opens up the world of Web Services to Pocket PCs, and the wireless nature of Pocket PCs opens up the world of mobility to Web Services.
This means that new fuel is added to the expansion of the XML-based Web Services provider market.
In this article, by using simple Web Service Windows Mobile device is able to backup SQL Server database!