.NET 2 the Max

Debug Partially Trusted .NET Apps

Learn how to debug applications running on the Internet, embed resources in the assembly manifest, and use regular expressions to filter out duplicates.

Technology Toolbox: VB.NET, C#

The ability to run Windows Forms applications from the intranet or the Internet is one of the .NET Framework's great features, albeit one relatively unknown to many developers. You can see this feature in action by copying a Windows Forms executable to a network share and running it from there, or by deploying it in the c:\inetpub\wwwroot folder and launching it by typing http://localhost/appname.exe in the Internet Explorer address bar.

In this column, we'll show you a few techniques for working effectively with these partially trusted applications and embedding resources right in the assembly, plus give you a tip for using regular expressions to filter out duplicates in a document.

Debug Partially Trusted WinForms Apps
Executables launched from locations other than local disks run as partially trusted code by default, and they aren't granted all the permissions standard applications have. For example, they can't access the client's file system (except for a restricted area known as isolated file storage, which they can use to store users' preferences), and they can't access any ADO.NET objects. You can view these restrictions as limitations, but client administrators appreciate that executables running from unverifiable sources are guaranteed not to damage their systems. As a matter of fact, you can consider these Windows Forms applications a viable alternative to ASP.NET Web Forms apps because they're easy to deploy, provide automatic updates, and have the rich interface all Win32 programs have.

The Code Access Security (CAS) permission set associated with the zone the application is launched from (My Computer, Local intranet, Internet, Trusted, or Restricted) controls what a .NET application can or cannot do. System administrators can use the Microsoft .NET 1.1 Configuration snap-in, found in the Administrative Tools menu (see Figure 1), to modify these permission sets or create new custom zones if necessary. For example, even if assemblies marked with your company's strong name are loaded from the Internet, system administrators can grant them more permissions.

The problem you incur when you're building a Windows Forms application that will be downloaded from an intranet or the Internet is that Visual Studio .NET doesn't offer a specific tool to test and debug the app in a partially trusted context. You have a few clever ways to do this.

The simplest way to debug an app you intend to launch from the intranet is to change the project's output path to make it point to a network share. You'll find this setting in the Build page of the Properties Page window in both Visual Basic .NET and C# projects.

However, you might be wondering what to do if you're working on a computer that isn't connected to a local network. Fear not. You simply need to map a drive name (for example, Z:) to a folder in your local hard disk and specify that drive name in the project's output path. Even if the executable is actually stored locally, it appears to .NET to be launched from the intranet, and the LocalIntranet zone grants it CAS permission.

You'll need to use a different trick to debug a project if it's launched from the Internet, though. First, modify the project's output path to point to c:\inetpub\wwwroot, the local Internet Information Server's root directory. Next, in the Debugging tab of the project's Properties Page window, set the Start Action equal to Program (in C#) or Start external program (in VB.NET) and click on Apply. Then enter this path in the Start Application field (C#) or in the textbox that becomes enabled to the right of the radio button you've selected (VB.NET):

C:\Windows\Microsoft.NET\Framework\v.1.1
.4322\IEExec.exe

Finally, type this string in the command-line arguments field:

http://127.0.0.1/myapp.exe

Of course, myapp.exe is the executable name of the current project. You can now compile and debug the project as usual, but it will receive the permissions associated with the Internet zone. You can switch quickly among Internet and LocalIntranet permissions by replacing the command-line arguments field:

http://localhost/myapp.exe

Or you can save each output configuration as a distinct project configuration so you can quickly test as the program behaves in the My Computer, LocalIntranet, and Internet zones (see Figure 2).

Determine Which CAS Permissions You Need
Windows Forms applications launched from the intranet or the Internet aren't the only .NET assemblies that can run in a partially trusted environment. Visual Studio Tools for Office (VSTO) projects, Windows Forms controls hosted in an HTML page, and code running in SQL Server 2005 (code-named Yukon) are other examples of applications that require special treatment on your part.

A recurring problem is determining exactly which CAS permissions these apps require to run correctly, and how they can degrade gracefully (by disabling some features, for example) when .NET doesn't grant them these permissions. I'll illustrate three techniques you can adopt to grant or deny specific permissions to your current project. (These techniques complement the methods for debugging partially trusted apps.)

In the first approach, you bind a specific set of permissions to assemblies loaded from a specific local directory. First, create a new permission set in the Machine-level policy and add to it the permissions you want to test the assembly against. Next, add a new code group as a child of the My_Computer_Zone code group, name it SandBox (or any name you like), and assign the newly created permission set to the new code group. Give this SandBox code group a URL membership condition that specifies the project's path and its subdirectories; for example:

file://D:/NET_Demos/MyApp/bin/*

Finally, mark the code group as exclusive so that all other permissions that would be granted by the Machine-level policy are removed (see Figure 1).

In the second approach, you can assign specific CAS permissions to an assembly based on the RequestOptional and RequestRefuse actions you specify with permission attributes at the assembly level. More specifically, you can use the RequestRefuse action to test how the assembly behaves when the CLR doesn't grant it a given permission:

' [VB] Refuse the permission to invoke
' unmanaged code
<Assembly: 
SecurityPermission(SecurityAction.Reques
tRefuse, UnmanagedCode:=True)>

Or you can use one or more attributes with RequestOptional actions to list which permissions you want (all other permissions are implicitly refused):

' [VB] Run only with UI and registry 
' permissions
<Assembly: 
UIPermission(SecurityAction.RequestOptional,
Unrestricted:=True)>
<Assembly: 
RegistryPermission(SecurityAction.Reques
tOptional)>

(If you specify both RequestOptional and RequestRefuse actions, the CLR grants your assembly all the permissions listed with the RequestOptional action minus those listed with the RequestRefuse action.)

The last approach for assigning specific permissions to the assembly you're authoring is the most flexible as well as the most complex of the lot. To understand how it works, you must keep in mind that an assembly receives only the permissions that are common to the CAS policies at the Enterprise, Machine, User, and Application level. Enterprise- and User-level default policies always grant FullTrust permission, as does the Machine-level policy when the assembly loads from the local hard disk. Therefore, in practice, the policy you set for a specific application determines the permissions assigned to that assembly completely if it is run from a local folder.

The only way to assign Application-level permission is by loading the assembly in a new AppDomain through code. Start by preparing an XML file that defines the application policy (see Listing 1 for a portion of such a file). When specifying the permissions for the assembly, you might be tempted to define a policy with a single code group whose MembershipCondition is set to All_Code, but this approach won't work because the code .NET assemblies in the Global Assembly Cache (GAC) would also receive this limited set of permissions. You work around this issue by defining a child code group that provides full trust to assemblies loaded from the GAC directory. The code to create an AppDomain and load an application policy from a file is trivial (see Listing 2).

Unfortunately, there are no tools to manipulate an application policy file easily, so you'll have to edit the file manually with an XML or a text editor.

Each technique I discuss here has pros and cons. For example, the first and the third techniques let you modify the permission sets granted to the assembly without recompiling it. Choose the technique that fits your needs best according to the characteristics of your development and testing processes. —E.S.

Use Manifest Resources to Simplify Deployment
You can greatly simplify the deployment of a .NET executable by embedding all the ancillary files the application reads (but doesn't modify) in the assembly manifest. These files include bitmaps, text or RTF files, HTML pages, and many others. Resources that are embedded in the manifest can be located and loaded faster (especially if the assembly is run from the intranet or Internet) and are protected from accidental deletion. However, you shouldn't use manifest resources for locale-dependent strings and images.

You can embed any data file—including text files and images—in the assembly's manifest by following a few simple steps. First, drag the file from Windows Explorer into the Solution Explorer window to include the file in the project. If the file is already in the project's directory, click on the Show All Files button in Solution Explorer's toolbar, right-click on the file icon, and select the Include in Project command. Next, select the file in the Solution Explorer and press the F4 key (or click on the Properties button in the Solution Explorer's toolbar) to display that file's properties. Finally, change the Build Action property from Content to Embedded Resource (see Figure 3). Reading a resource stored in the manifest requires little code (see Listing 3).

It's critical to use the correct namespace when referencing a resource. The name of a resource is formed by the assembly's default namespace (C#) or root namespace (Visual Basic), followed by the filename and extension (without the path). However, if the resource file is stored in a project's folder, the rule for forming the resource name depends on the language in use.

The C# compiler generates a resource name that includes the folder's name, whereas the Visual Basic compiler ignores the folder and generates the name as if the resource were stored in the project's root folder. For example, if you move the Data.txt file to a project folder named TextFiles, the Visual Basic code snippet in Listing 3 continues to work, but you should edit the C# code:

string resFile = 
   "CodeArchitects.TextFiles.Data.txt";

An important detail: Resource names are compared in a case-sensitive mode. You can check the exact names of embedded resources using the Ildasm tool or programmatically with the Assembly.GetManifestResourceNames method, which returns a string array containing all the files you've embedded in the assembly, as well as one resource file for each Windows Form class in the application. —F.B.

Find Duplicates With a Little Regex Magic
The power of regular expressions continues to astonish me, even though I've used them for a few years now. For example, I've often used this snippet to find all the unique words in a text file quickly:

Dim text As String = _
   "one two three two zone four three"
Dim re As New Regex("\w+")
' \w+ means "any word"
Dim words As New Hashtable
For Each m As Match in re.Matches(text)
   If Not words.Contains(m.Value) Then
      Console.Write (m.Value & " ")
      words.Add(m.Value, Nothing)
   End If
Next

The code displays this text in the console window:

one two three zone four

By going deeper into regular expressions, I discovered you don't even need the Hashtable and you can find unique words with a regular expression:

Dim p as String = _
   "(?<word>\b\w+\b)(?!.+\b\k<word>\b)"
Dim re As New Regex(p) 
For Each m As Match in re.Matches(text)
   Console.Write (m.Value & " ")
Next

The regular expression is quite complex, so I'll explain it one piece at a time. The expression (?<word>\b\w+\b) matches a sequence of alphanumerical characters (\w) on a word boundary (\b) and assigns this sequence the name "word." The (?!) construct means the word just matched must not be followed by another occurrence of itself (the backreference /k<word>), even if there are any number of characters (.+) in between. Translated to plain English, the regular expression says, "Match any word in the text that isn't followed by another instance of the same word." Or, more simply, "Match the sole occurrence of unique words or the last occurrence of a repeated word." The result in the command line is:

one two zone four three

As you see, the regular expression finds all the unique words, even though their order differs from the previous example. Note that the \b characters in the regular expression prevent partial matches ("one" doesn't match the trailing portion of "zone").

You don't have to limit your search to words. For example, you can display all the unique dates in a document in the mm-dd-yy format with this regular expression:

(?<date>\d\d-\d\d-\d\d)(?!.+\k<date>)

You can also solve different but related problems by simply changing the regular expression. For instance, this regular expression finds words that are duplicated in a document:

(?<word>\b\w+\b)(?=.+\b\k<word>\b)

The (?=) construct means that the word match must be followed by another instance of itself. Notice that this Regex finds all the duplicates, meaning it finds two duplicates if there are three occurrences of a given word. —F.B.

Portions of this column have been excerpted from Francesco's forthcoming book, Practical Guidelines and Best Practices for Microsoft Visual Basic and Microsoft Visual C# [Microsoft Press, 2005, ISBN: 0735621721].

comments powered by Disqus

Featured

  • Compare New GitHub Copilot Free Plan for Visual Studio/VS Code to Paid Plans

    The free plan restricts the number of completions, chat requests and access to AI models, being suitable for occasional users and small projects.

  • Diving Deep into .NET MAUI

    Ever since someone figured out that fiddling bits results in source code, developers have sought one codebase for all types of apps on all platforms, with Microsoft's latest attempt to further that effort being .NET MAUI.

  • Copilot AI Boosts Abound in New VS Code v1.96

    Microsoft improved on its new "Copilot Edit" functionality in the latest release of Visual Studio Code, v1.96, its open-source based code editor that has become the most popular in the world according to many surveys.

  • AdaBoost Regression Using C#

    Dr. James McCaffrey from Microsoft Research presents a complete end-to-end demonstration of the AdaBoost.R2 algorithm for regression problems (where the goal is to predict a single numeric value). The implementation follows the original source research paper closely, so you can use it as a guide for customization for specific scenarios.

  • Versioning and Documenting ASP.NET Core Services

    Building an API with ASP.NET Core is only half the job. If your API is going to live more than one release cycle, you're going to need to version it. If you have other people building clients for it, you're going to need to document it.

Subscribe on YouTube