|
 Friday, August 03, 2007

Presenting at Arizona .NET User Group - Dependency Injection & Inversion of Control

I will be presenting at the AZ .NET User Group on August 14th. I'm going to cover how to use Dependency Injection along with Inversion of Control to decouple your applications. I will also demonstrate how these techniques can help ease unit testing.

When: August 14th

Time: 6pm

Location: Microsoft Corp 2929 N Central Ave # 1400 Phoenix, AZ 85012 (map).

See AZGROUPS.COM for more info.

See ya there!

#    Comments [2] |
 Thursday, August 02, 2007

IIS 6 & PHP - Mimicking .htaccess User/Pass Functionality

If you happen to be running PHP on a Windows Server machine you'll probably have to port over some functionality. One of the key things that you'll have to port over is something that mimics the .htaccess functionality of prompting users for user/pass based upon the directory.

In your application you might have a directory that is for admin, such as www.myexample.com/admin. This directory might only be accessible to users that you want to specify. In a *NIX environment you can do this with an .htaccess file (and its corresponding .htpasswd) file. Unfortunately when  porting this to a Windows environment, these files don't do anything for you (unless you're running Apache on Windows).

Solution

Here's what you'll need.

  • a login page, login.aspx
  • a web.config file
  • ASP.NET 2.0 installed
  • A minor adjustment to IIS 6

Steps

1. Install PHP.

2. Create a web.config with the following in it.

<?xml version="1.0"?>
<configuration>
    <appSettings/>
    <connectionStrings/>
    <system.web>

      <compilation debug="true" />

      <authentication mode="Forms">
          <forms protection="All" loginUrl="~/login.aspx">
            <credentials passwordFormat="Clear" >
              <user name="admin" password="abc123"/>
              <user name="jdoe" password="123xyz" />
            </credentials>
          </forms>
        </authentication>
    </system.web>

        
  <location path="admin">
    <system.web>
      <authorization>
        <deny users="?"/>
        <allow users="*"/>
      </authorization>
    </system.web>    
  </location>
  
</configuration>

2. Create a login page with a login control. In that code behind (or in the page itself as I've done) set up the Authenticate Event to use Forms Authentication. This will ensure that its reading from the web.config file and not the default membership provider (SqlMembershipProvider).

<%@ Page Language="C#" AutoEventWireup="true"  %>
<script runat="server">

    protected void Page_Load(EventArgs e)
    {
        Page.SetFocus(loginControl); 
    }

    protected void loginControl_Authenticate(object sender, AuthenticateEventArgs e)
    {
        if (FormsAuthentication.Authenticate(loginControl.UserName, loginControl.Password))
        {
            FormsAuthentication.RedirectFromLoginPage(loginControl.UserName, false);
        }
    }
</script>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
    <title>Untitled Page</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
        <asp:Login ID="loginControl" runat="server" DisplayRememberMe="False" OnAuthenticate="loginControl_Authenticate">
        </asp:Login>
    
    </div>
    </form>
</body>
</html>

 

3. Now, remember, this site is a PHP site and it is being run with PHP. Therefore the first document that is being loaded is the index.php file. Therefore the ISAPI or CGI module for PHP Is being called before ASP.NET is ever initialized. Therefore, if we try to access the admin site, ASP.NET will NOT catch this and it will allow access. In order to get around this, we have to set the aspnet_isapi.dll module to execute first, therefore IIS will load ASP.NET which in turn will check the web.config file and not allow access to restricted directories.

Here's how to do it.

3a. Start IIS Manager (start --> Run --> inetmgr) Open the sites properties. Click on "Home Directory"

(click for larger)

iis6Config1 

Once on the Home Directory tab, click on the Configuration button.

(click for larger)

iis6Config2

Now click on "Insert" and enter the path to the aspnet_isapi.dll:

(%systemroot%/Microsoft.Net/Framework/v2.0.50727/aspnet_isapi.dll)

Then click OK, and it will look like this: (click for larger)

iis6Config3

Restart IIS and then give your application a test.

Visit the Admin directory and now you should be redirected to the login page.

Log in with the admin credentials that are in the web.config you'll then be authenticated.

What this does is force IIS to load ASP.NET first, regardless of what type of file is loaded. In this situation this is exactly what I want it to do. Load ASP.NET and run anything in the Web.config file. Therefore if the user attempts to load the /admin directory ASP.NET will notice that its a protected directory and it will deny access.

 

Adding a New Directory

If you want to add a new secured directory, you can add another <location> in the web.config.

Like so:

<?xml version="1.0"?>
<configuration>
    <appSettings/>
    <connectionStrings/>
    <system.web>

      <compilation debug="true" />

      <authentication mode="Forms">
          <forms protection="All" loginUrl="~/login.aspx">
            <credentials passwordFormat="Clear" >
              <user name="admin" password="abc123"/>
              <user name="jdoe" password="123xyz" />
            </credentials>
          </forms>
        </authentication>
    </system.web>

        
  <location path="admin">
    <system.web>
      <authorization>
        <deny users="?"/>
        <allow users="*"/>
      </authorization>
    </system.web>    
  </location>
  
  <location path="secretfolder">
    <system.web>
      <authorization>
        <deny users="?"/>
        <allow users="*"/>
      </authorization>
    </system.web>    
  </location>
  
  
</configuration>

Explanation

I've added a "secretfolder" location. Therefore, if a user wants to go to either the "admin" or "secretfolder" they will be authenticated. If they authenticate at the "admin" directory, they will automatically be allowed into the "secretfolder" directory.

You can set folder access to certain users, to learn how see this is accomplished, take a look at the great list of resources that ScottGu has set up .

 

Note, in IIS 7 this problem does not exist, but you do need to include this into your web.config to enable forms authentication. Here's the source of this info.

    <system.webServer>
        <modules>
            <remove name="FormsAuthenticationModule" />    
            <add name="FormsAuthenticationModule" type="System.Web.Security.FormsAuthenticationModule" />    
            <remove name="UrlAuthorization" />    
            <add name="UrlAuthorization" type="System.Web.Security.UrlAuthorizationModule" />    
            <remove name="DefaultAuthentication" />    
            <add name="DefaultAuthentication" type="System.Web.Security.DefaultAuthenticationModule" />    
        </modules>
    </system.webServer>

 

Hopefully this helps anyone doing the same thing.

#    Comments [0] |
 Sunday, July 29, 2007

Tip: DirectorySeparatorChar

If you look at most file access code you'll see the following somewhere:

string filePath = myDirectory + "\\" + myOtherDirectory + "\\" + myfile;

Developers will build their file paths with hard coded values such as "\\". This isn't the best way to accomplish things. For example, what happens if your code needs to get run on Mono? *NIX paths are different than that of Windows. When I see a string literal with a directory separator in it, or even in a constant, I cringe. How do we get around this? Easy...

Enter DirectorySeparatorChar

In the .NET Framework, in the System.IO Namespace in the Path class you'll find a public static field by the name of DirectorySeparatorChar.

Here's what it does: In a Windows and Mac environment it will return the backslash character. In a *NIX environment it will return a slash "/".

No more hard coding separator characters!

You would now build the same path above, like this:

string filePath = myDirectory + Path.DirectorySeparatorChar + myOtherDirectory + 
                 Path.DirectorySeparatorChar + myfile;
The code is now much more stable and is much easier to adapt to a *NIX type of environment.
#    Comments [2] |
 Friday, July 27, 2007

Ubuntu and Virtual PC 2007 Mouse Issues

ubuntulogo I recently ran into an issue while installing Ubuntu to test Mono.

Apparently when you install Ubuntu 7.04 on Virtual PC 2007 the system doesn't recognize the mouse. MAJOR SUCKAGE. Luckily I found a link that helped me through this and eventually got my mouse working for Ubuntu on VPC 2007.

This is mainly a post to log it for my own purposes, but hopefully it helps you as well. A big thanks to the guys who figured this out.

Links

#    Comments [1] |
 Thursday, July 26, 2007

Returning XML Data as a Record Sets through the CLR in SQL Server 2005

The Problem

I needed to write something like this ...

SELECT * FROM CustomerTest WHERE Customer_Id = 1;

The data returned needed to return the row as well as the data inside of the XML Column in that row, in the same set. (The data in the XML column needed to be returned as sets of records as well.) The XML Contained was simple XML that looked like this:

(click for larger image)

xmlExample

The XML Column was called "Extra_Info". This is where the above XML was stored.

image

Ok, lets get to the point... here's what its supposed to look like when the query is run:

(click for larger image)

image

A little more about the problem domain..

Awhile back I had a client who wanted to store data in an XML Field, but the XML for each row could be extremely different in each row. One row might have a XML Document that has 1 set of data, while the next might have 10. These XML documents that were being stored did not share a common schema -- not at all. But we needed to return the data in an SQL Query or SPROC that would allow the user to view the data as if it were tables in the database.

Solution

So how do we do that with one Query?

Easy - SQL CLR Integration.

We can create a CLR Stored Procedure to do this for us.
Here are the steps the CLR SPROC will go through to return the data.

  1. Query the DB for the record.
  2. Get the XML from the column.
  3. Load the XML into a DataSet object.
  4. Send the original record results of the row to the client.
  5. Create record sets off of the DataSet using SqlDataRecord object.
  6. Send each record back to the client.

Code time baby...

using System;
using System.Data;
using System.Data.SqlClient;
using System.Data.SqlTypes;
using Microsoft.SqlServer.Server;
using System.IO;
using System.Collections.Generic;
using System.Data.Common;
using System.Xml;


public partial class StoredProcedures
{
    [Microsoft.SqlServer.Server.SqlProcedure]
    public static void TestProcedure(SqlInt32 recordId)
    {
        DataSet dataset = null; 

        using (SqlConnection connection = new SqlConnection("context connection=true"))
        {
            string xml = string.Empty;
            using (SqlCommand command = new SqlCommand("SELECT * FROM dbo.CustomerTest WHERE Customer_Id = @recordId"))
            {
                command.Parameters.Add(new SqlParameter("@recordId", recordId));
                command.Connection = connection;
                if (connection.State != ConnectionState.Open)
                {
                    connection.Open();
                }

                using (DbDataReader reader = command.ExecuteReader())
                {
                    if (reader.Read())
                    {
                        xml = reader["Extra_Info"].ToString();
                        
                        // Load in the XML. 
                        XmlReader xmlReader = new XmlTextReader(xml, XmlNodeType.Document, null);
                        dataset = new DataSet();
                        dataset.ReadXml(xmlReader);
                    }

                }
                SqlContext.Pipe.ExecuteAndSend(command);

            }

            
        }

        if (dataset != null)
        {
            foreach (DataTable table in dataset.Tables)
            {
                // Set up the record 
                List<SqlMetaData> metaData = new List<SqlMetaData>();
                foreach (DataColumn column in table.Columns)
                {
                    metaData.Add(new SqlMetaData(column.ColumnName, SqlDbType.Variant));
                }
                SqlDataRecord record = new SqlDataRecord(metaData.ToArray());

                // Mark the begining of the result-set.
                SqlContext.Pipe.SendResultsStart(record);

                foreach (DataRow row in table.Rows)
                {
                    foreach (DataColumn column in table.Columns)
                    {
                        record.SetValue(table.Columns.IndexOf(column), row[table.Columns.IndexOf(column)]);
                    }

                    // Send the row back 
                    SqlContext.Pipe.SendResultsRow(record);
                }
                // Mark the end of the result-set.
                SqlContext.Pipe.SendResultsEnd();
            }
        }

    }

}
Code Review

The sproc is created with the code above.

If we find a record we load the xml into a variable and then load the DataSet with the XML using the ReadXml Method of the DataSet.

if (reader.Read())
{
  xml = reader["Extra_Info"].ToString();
                      
  // Load in the XML. 
  XmlReader xmlReader = new XmlTextReader(xml, XmlNodeType.Document, null);
  dataset = new DataSet();
  dataset.ReadXml(xmlReader);
}

Next, we loop through each data table and each record in the data table to create a SqlDataRecord. To build an SqlDataRecord we need a Record definition.These record definitions are defined as SqlMetaData objects. Therefore we need populate an array of SqlMetaData objects by looping through the columns to get the column information. At this point we're building the columns in which the data will exist in.

// Set up the record 
List<SqlMetaData> metaData = new List<SqlMetaData>();
foreach (DataColumn column in table.Columns)
{
  metaData.Add(new SqlMetaData(column.ColumnName, SqlDbType.Variant));
}
SqlDataRecord record = new SqlDataRecord(metaData.ToArray());

From here, we will build the actual records. This is done by the following method:

  • Inform the context that we're going to begin sending a record by using the SendResultsStart method of the Pipe class.
  • When a record is built, we want to send it to the client by calling the SendResultsRow method. This sends the row to the client. We will do this for each record built.
  • After the records have been sent we want to tell the client we're done sending records for this set. We do this by calling the SendResultsEnd method.

This happens for each DataTable in the DataSet. And then finally the results are sent back to the client.

Resolution

Once deployed, the sproc (which I've named "TestProcedure") will show up under the StoredProcedures area of SQL Server, as shown here:

image

And now you have a stored procedure that will return results of the row as well as the data that is in the XML column, all in the same result set.

Uses: You could use this as an extensibility point in your application  by allowing users to add custom fields to the application at run time and then storing the application fields and field data in an XML file which is stored in an SQL Server column. Think of it as a database inside of a database. You could then use it to query the row/column to get the results into a readable form for Crystal or Reporting Services.

#    Comments [1] |