OdeToCode IC Logo

Conditional Compilation In ASP.NET 2.0

Friday, December 2, 2005 by scott

Conditional compilation allows you to select sections of code to include or exclude from compilation depending on the presence of a constant. This feature is available in both C# and VB.NET. The following page needs DEBUG and MYCONSTANT defined to execute both Response.Write methods at runtime.

protected void Page_Load(object sender, EventArgs e)
{
    #if DEBUG
    Response.Write("DEBUG is defined <br/>");
    #endif

    #if
MYCONSTANT
    Response.Write(
"MYCONSTANT is defined <br />");
    #endif
}

In 1.1 it was easy to define constants for a web project, but there is no project property page with this option in 2.0. The ASP.NET platform controls compilation, so we need to feed ASP.NET the information.

First, ASP.NET will define a DEBUG constant when debug=”true” in the <compilation> section of web.config. Unfortunately, the IDE isn't smart enough to detect when DEBUG is defined. If the IDE thinks code is excluded from compilation, it greys out the text.

For your own constants, it gets a little trickier. For example, to define MYCONSTANT there are two options. One option is to define MYCONSTANT for a single page at a time using CompilerOptions in the page directive like so (/d works for csc and vbc compilers):

<%@ Page Language="C#" ... CompilerOptions="/d:MYCONSTANT" %>

Of course, you probably want all code in the web project to see the constant, so you can change the compiler options in web.config.

<system.codedom>
   <
compilers>
      <
compiler             
         
language="c#;cs;csharp" extension=".cs" 
         
compilerOptions="/d:MYCONSTANT"
         type="Microsoft.CSharp.CSharpCodeProvider, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />           
   </
compilers>
</
system.codedom>

Having said all this, the default settings for medium trust do not allow you to fiddle with compiler options (I imagine this is to prevent someone from throwing the /unsafe switch, for example). Keep this in mind if you use conditional compilation and rely on dynamic compilation in a hosted environment.

Sitemap Macro

Wednesday, November 30, 2005 by scott

I wrote a Visual Studio macro to walk an ASP.NET 2.0 project and create a web.sitemap based on the physical layout of files.

Download.

The macro requires a reference to System.Xml.dll. The macro won’t overwrite or synchronize an existing web.sitemap file, it’s only meant to save some typing if you have an existing project and want to add a sitemap. Once the macro creates the sitemap, you can go in and modify the titles, descriptions, and layout.

Update: Fixed the macro to work with IIS based projects, and added Dan Kahler's suggestion to prompt user to overwrite existing file.

Building A New Mouse Trap V: Workflow

Monday, November 28, 2005 by scott
Windows Workflow Designer

When I first heard about Windows Workflow Foundation, I wasn’t particularly excited. “Workflow” has long been a buzzword used by vendors, product managers, and venture capitalists in mind numbing phrases like: “Our workflow solution will leverage your strategic business assets and put you in the fast lane of the information superhighway”.

Yawn.

Nevertheless, I knew some people who were taking Workflow seriously, so I decided to give the preview bits  a try. Workflow is a success. The designer works well – both the user interface and the programming interface are intuitive. For a good description of the feature set, which includes state machine workflows and long running workflows, see David Chappell’s “Introducing Windows Workflow Foundation”.

Here is a code activity that Workflow invokes. It would be nice if the Parameters turned into strongly typed properties.

private void Fetch_ExecuteCode(object sender, EventArgs e)

{

  FetchSettings settings = new FetchSettings();

  settings.ConnectionString = Parameters["ConnectionString"].Value as string;

  settings.LocalDataPath = Parameters["LocalDataPath"].Value as string;

 

  // ...

 

  FetchProcessor processor;

  processor = new FetchProcessorFactory.GetConfiguredFetchProcessor(settings);

  _files = processor.Process();          

}

To kick off a workflow...

private void StartImport()

{

  if (!workflow.IsStarted)

  {

    workflow.StartRuntime();

    workflow.WorkflowCompleted +=

           new EventHandler<WorkflowCompletedEventArgs>

                        (workflow_WorkflowCompleted);

    workflow.WorkflowTerminated +=

           new EventHandler<WorkflowTerminatedEventArgs>

                        (workflow_WorkflowTerminated);

 

  }

 

  Type importType = typeof(ImportWorkflow);

 

  Dictionary<string, object> parameters = new Dictionary<string, object>();

  // ...

  parameters.Add("LocalDataPath", Path.GetTempPath());

  parameters.Add("ConnectionString", Settings.Default.wfStatsConnectionString);

 

  workflow.StartWorkflow(importType, parameters);           

}

Membership and Roles

Monday, November 28, 2005 by scott

I like to write about a topic before I give a presentation. Writing is my way of organizing random thoughts into an arbitrary collection of opinions.

When I signed up to do a presentation at the last local code camp, I got behind on writing about membership and role providers in ASP.NET 2.0. I finally finished the writing this weekend (Part I, Part II).

Miguel Castro also covered membership features at the last code camp. Miguel concentrated on the login controls and UI customization while I stuck more to the configuration and other details. Miguel knows a great deal about ASP.NET server controls –just listen to his .NET Rocks appearance. Two thumbs up!

Spot the Authorization Bug

Sunday, November 27, 2005 by scott

The following is an excerpt from PAG’s “How To: Use Role Manager in ASP.NET 2.0”.

You can control access to pages or folders to members of one of the built-in Windows groups by specifying the role in the format BUILTIN\groupName. The following example allows users in the built-in administrators group to view pages in the folder named memberPages and denies access to anyone else.


  
      
           
              
              
           

         

       

  

The problem is, the default authorization rule is: 

  

The ASP.NET module responsible for authorization checks iterates through rules starting with the local web.config file, and ending with the “allow all users” default rule. As soon as the module finds a rule matching the current user, it stops evaluating rules.

The PAG example is only denying access to anonymous users. If a user is authenticated, but not in the Administrator role, they'll still get access by falling through to the allow users="*" rule.

To really keep out non-Administrators, you want to use:

 

 

Building a New Mouse Trap IV: XML

Saturday, November 26, 2005 by scott
snowflake request schema

I had a star schema and an XML document of creamy data to stuff inside. What’s the best approach to updating the dimension tables with new values? There are many tried and true approaches to evaluate.

Let’s take the specific case of the Referers table. This table stores a RefererID, and a Referer string. I have an XML document full of Request elements, each with a Referer attribute. I needed to look at each element and find any Referer values that do not exist in the database to insert only new values.

<Request Date="2005-11-15T07:59:59Z"

        Uri="/Articles/rss.aspx"

        Referer=""

        Status="200"

        ResponseTime="609"

    />

One approach would be to parse the file, lookup each referrer in the database, and execute an INSERT statement if the value isn’t found. This approach uses at least two database roundtrips per request, so processing a large number of records will crawl.

Another approach is to pass the entire document to SQL Server 2005 and work with the new XML capabilities.

CREATE PROCEDURE [Stats].[ImportReferers]

    @WebStatistics AS XML

AS

BEGIN

    SET NOCOUNT ON;

 

    WITH XMLNAMESPACES(DEFAULT 'https://odetocode.com/wfStatsImport.xsd')

 

    INSERT INTO Stats.Referers

 

        SELECT DISTINCT request.value('@Referer', 'varchar(900)')

 

        FROM @WebStatistics.nodes('//Request') AS Request(request)

            LEFT JOIN Stats.Referers R ON

                R.Referer = request.value('@Referer', 'varchar(900)')

 

        WHERE R.Referer IS NULL

END

In SQL 2005 there are at least two approaches to transforming XML into a rowset. Once approach is OPENXML, but the code still gets a messy with pre and post-processing. A cleaner approach is to take advantage of the native XML data type in 2005, which offers query(), value(), exist(), modify() and nodes() methods. The nodes() method is just what I needed to transform XML elements into a relational table. The value() method can pull out an attribute to act like a column. All I needed then was a LEFT JOIN to find just the Referer values that didn’t already exist. Performance has been acceptable for a server running on Virtual PC.

Building a New Mouse Trap III: SSIS

Saturday, November 26, 2005 by scott

Once the web log files were downloaded and unzipped, I transformed them into an XML document and looked for a way to get the records into SQL Server. My first thought was to learn more about SQL Server Integration Services, the successor to DTS.

Integration Services at Work

I’ve used DTS in the past to fast-load millions of records into data marts. Although the performance numbers were excellent, I’ve always held misgivings about DTS because the packages were opaque, and difficult to maintain and use in generic solutions.

Although I made a fair amount of progress with SSIS, I ultimately ditched the approach for the following reasons:

  • The designer was painfully slow
  • The designer required too much ‘mouse’
  • The designer has bugs

I wanted to like SSIS. SSIS has made vast improvements over DTS. The packages are now XML files, meaning you can look at them, diff them, and check them into source control as text. There are more tasks available, and you can plug-in your own code.

The biggest obstacle was the Script Task would not let me design a script. The reason I wanted to execute a Script Task was to find an elegant solution to inserting a parent record, retrieving an identity value, and inserting child records with the identity as an FKEY. This common task seems to require an inordinate amount of work in SSIS.