Tuesday, September 14, 2004
This is my first blog entry from within InfoPath.  Upon reading Aaron Skonnard's blog entry today I couldn't help but jump on the bandwagon.  I too have been bitten on more than one occasion by the lack of XML support in the .Text admin section or by an expiring session while entering a new blog entry.  This is a welcome change.
 
Kudos to Quoqiang Wu for his most excellent InfoPath SimpleBlogger!
Tuesday, September 14, 2004 6:11:00 PM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [0]  |  Trackback

Here's a code snippet that I wrote the other day.  I had a need to discover the various SQL Server instances on the network.  I couldn't reliably use SQL DMO for a variety of reasons; for one, there might not be any client tools installed locally.  Therefore, I decided to dive down into ODBC and use its built-in functionality that I am pretty confident will be on the users' machines.

There are a few items I'd like to state before we get to the code.

  • This code will not work (that is, it will return null) if you don't currently have an active network connection
  • I'm working on getting this to work regardless of connectivity - so stay tuned.
  • Similar functionality is expected in VS 2005, but this gets the job done.

using System;
using System.Text;
using System.Text.RegularExpressions;
using System.Runtime.InteropServices;

namespace Devstone.Tools.DataAccess.Discovery {

   public sealed class DbEnumerator {
      [DllImport("odbc32.dll")]
      private static extern short SQLAllocHandle(short hType, IntPtr inputHandle, out IntPtr outputHandlePtr);
      [DllImport("odbc32.dll")]
      private static extern short SQLSetEnvAttr(IntPtr hEnv, int attribute, IntPtr valuePtr, int stringLength);
      [DllImport("odbc32.dll")]
      private static extern short SQLFreeHandle(short hType, IntPtr handle);
      [DllImport("odbc32.dll",CharSet=CharSet.Ansi)]
      private static extern short SQLBrowseConnect(IntPtr hconn, string inString, short inStringLength, StringBuilder outString, short outStringLength, out short outLengthNeeded);

      private const short SQL_HANDLE_ENV = 1;
      private const short SQL_HANDLE_DBC = 2;
      private const int SQL_ATTR_ODBC_VERSION = 200;
      private const int SQL_OV_ODBC3 = 3;
      private const short SQL_SUCCESS = 0;
      private const short SQL_SUCCESS_WITH_INFO = 1;
      private const short SQL_NEED_DATA = 99;

      private DbEnumerator() { }

      public static string[] GetSqlServers() {
        
string[] ret = null;
        
IntPtr hEnv = IntPtr.Zero;
        
IntPtr hConn = IntPtr.Zero;
        
const string CONNECTION_STRING = "DRIVER=SQL SERVER";
        
const short DEFAULT_RESULT_SIZE = 1024;
        
StringBuilder outString = new StringBuilder(DEFAULT_RESULT_SIZE);
        
int retCode;

         try {
           
// allocate the environment handle
           
retCode = SQLAllocHandle(SQL_HANDLE_ENV, hEnv, out hEnv);
           
if ( SQL_SUCCESS != retCode && SQL_SUCCESS_WITH_INFO != retCode ) return null;

            // set the proper version environment attribute
           
retCode = SQLSetEnvAttr(hEnv, SQL_ATTR_ODBC_VERSION, (IntPtr)SQL_OV_ODBC3, 0);
           
if ( SQL_SUCCESS != retCode && SQL_SUCCESS_WITH_INFO != retCode ) return null;

            // allocate the connection handle
           
retCode = SQLAllocHandle(SQL_HANDLE_DBC, hEnv, out hConn);
           
if ( SQL_SUCCESS != retCode && SQL_SUCCESS_WITH_INFO != retCode ) return null;

            short lenNeeded = (short)outString.Capacity;
           
retCode = SQLBrowseConnect(hConn, CONNECTION_STRING, (short)CONNECTION_STRING.Length, outString, DEFAULT_RESULT_SIZE, out lenNeeded);
           
if ( SQL_NEED_DATA == retCode && lenNeeded > DEFAULT_RESULT_SIZE ) {
              
// try again with the new length only if more data is needed and the string needs to be longer
              
// otherwise we'll just end up with the same result again
              
retCode = SQLBrowseConnect(hConn, CONNECTION_STRING, (short)CONNECTION_STRING.Length, outString, (short)(lenNeeded + 1), out lenNeeded);
           
}

            if ( SQL_SUCCESS != retCode && SQL_NEED_DATA != retCode && SQL_SUCCESS_WITH_INFO != retCode ) return null;

            // extract the servers out of the returned string and split them on the commas into the return value
           
Regex servers = new Regex(@"^SERVER:Server={(?'SERVERS'.*)};", RegexOptions.Singleline);
           
Match m = servers.Match(outString.ToString());

            if ( m.Success )
              
ret = m.Groups["SERVERS"].Value.Split(new char[] {','});
        
}
        
catch ( Exception ) { /* swallow the exception */ }
         finally {
           
if ( IntPtr.Zero != hConn ) SQLFreeHandle(SQL_HANDLE_DBC, hConn);
           
if ( IntPtr.Zero != hEnv ) SQLFreeHandle(SQL_HANDLE_ENV, hEnv);
        
}

        
return ret;
      }

   } // DbEnumerator class

} // Devstone.Tools.DataAccess namespace

Tuesday, September 14, 2004 9:59:00 AM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [2]  |  Trackback
 Friday, September 10, 2004

In preparing my custom server control (webEdit and webMenu) for general distribution, I've taken the time to make the package more of a complete offering.  That is, I would not generally be comfortable simply releasing my code without any explanation of how to use it.  As a direct result of this inexplicable compulsiveness, I've enhanced the package to support design time intellisense.  I am one of  the (few/many?) developers that actually prefers to do all of my design work in the raw .aspx/.ascx environment, rarely if ever relying on the visual designers.  In fact, one of the first things I do on a fresh VS.NET install is turn off the setting to default to design view for HTML files.

In this article, I'd like to venture into the world of creating our own custom Intellisense for your web server controls that is supported in the IDE.  Note: from what I understand, this will not be necessary in the Whidbey timeframe (yipee!), but it will suffice for the time being.

I'll start at the beginning, assuming no knowledge of how to add support for Intellisense.

VS developers have been accustomed (spoiled?) by the prevalence of the code-completion support built into the IDE.  Open any code document and begin typing and you'll quickly see the help appear (e.g. <asp:…).  This assistance appears based on context and can display classes, properties, values, methods, events, and much more.  The Intellisense that appears within the IDE for .cs or .vb files is driven off of reflection.  You don't have to do any work to get it to appear other than change your code.  The Intellisence within the HTML designer, on the otherhand, is driven by metadata.  This metadata file is found in C:\Program Files\Microsoft Visual Studio .NET 2003\Common7\Packages\schemas\xml\asp.xsd in a default installation.

If we want to add our own Intellisense, we have two options 1) edit asp.xsd or 2) create our own.  Needless to say, you SHOULD NOT edit asp.xsd.  No, the programming police will not come knock down your door and drag you off (though perhaps they should), but if in a patch or update the file ever gets replaced you'll lose your changes.

Instead we should create our own file and plop it in the same directory.  I start to cringe at this thought, because I don't want to have to maintain two files (one in my \dev folder and one in the VS folder).  I am loathe to have my only file in the VS folder.  If you're developing your software on Windows XP or higher on an NTFS partition (which you should be doing if you're not), here's a cool tip.  If you only want to maintain a single source file on your system (e.g. the one in your \dev folder), and still have it referenced elsewhere without creating a copy (which is easy to forget to do):


fsutil hardlink create "C:\Program Files\Microsoft Visual Studio .NET 2003\Common7\Packages\schemas\xml\devstone.xsd" "C:\Dev\......\devstone.xsd"


The FSUTIL tool (File System Utility) allows us to create a hard link so that the file appears in another directory.  ACLs are shared and changes made in one file are reflected in the other because, well, it's the same file.

So to facilitate my development, I created my devstone.xsd file in my \Dev folder and a batch file called 'Register Intellisense.bat' that performs the FSUTIL operation (I only need to run it once, but if I ever get my source to another computer, it'll be there for me to run and I'll be set).

Now to creating our devstone.xsd file:

Begin by creating a new .xsd file and place the following Xml elements therein, changing the namespace to something appropriate for your situation:

<?xml version="1.0" encoding="utf-8"?>
<xsd:schema targetNamespace='urn:http://www.devstone.com/schemas'
   elementFormDefault='qualified'
   xmlns='urn:http://www.devstone.com/schemas'
   xmlns:xsd='http://www.w3.org/2001/XMLSchema'
   xmlns:vs='http://schemas.microsoft.com/Visual-Studio-Intellisense'
   vs:friendlyname='Devstone Web Forms Controls'
   vs:ishtmlschema='false'
   vs:iscasesensitive='false'
   vs:requireattributequotes='false'>

<xsd:annotation>
   <xsd:documentation>
      Devstone ASP.NET Web Forms Controls schema.
   </xsd:documentation>
</xsd:annotation>

</xsd:schema>

In order to create your Intellisense then, you create <xsd:element /> entries that define your valid 'top-level' objects.  Each <xsd:element /> is tagged with a type attribute that identifies an <xsd:complexType /> defined elsewhere in the document:

<xsd:element name="WebEdit" type="WebEditDef" />

<xsd:complexType name="WebEditDef" vs:noambientcontentmodel="true">
   <xsd:attribute name="Runat" type="runat" />
   <xsd:attribute name="Enabled" type="xsd:boolean" />
   <xsd:attribute name="Height" type="ui4" />
   <xsd:attribute name="ID" type="xsd:string" />
   <xsd:attribute name="Text" type="xsd:string" />
   <xsd:attribute name="Width" type="ui4" />
   <xsd:choice>
      <xsd:element name="Toolbar" form="unqualified" type="WebEditToolbarDef" />
   </xsd:choice>
</xsd:complexType>

There are a few things to notice here.  First of all, I've decorated the <xsd:complexType /> with a schema annotation vs:noambientcontentmodel.  That's a special attribute that VS.NET reads and understands that children of this type when entered on the .aspx/.ascx should be limited to the set of valid children as defined in the <xsd:choice />.  By default, you'd get a comprehensive set of the children of the page as well.  In this case we want to see only the <toolbar> element.

Now to get this tied onto our WebForm, we add the @Register directive to the top of the page and then add a reference to the Xml namespace within an element that will (directly or indirectly) be parent to our WebControl.  The <body> tag is a good candidate for .aspx, but since .ascx pages (a.k.a Web UserControls, HTML Controls) don't have a <body> tag, you'll need to find another element, such as a <div>, <td>, or something like that.

<%@ Register tagPrefix="devstone" Namespace="Devstone.Web.WebControls" Assembly="Devstone.Web.WebControls.WebSuite" %>
<body xmlns:devstone="urn:http://www.devstone.com/schemas">

Notice that the Namespace matches identically to the targetNamespace of our schema file.  Also, the devstone namespace matches the tagPrefix within the @Register directive.  This ties our control's Intellisense to that element.  Now we would get autocompletion for our <devstone:webEdit /> control on the page.

There's a lot more to this than what I've posted here in this blog entry, but I've also included my .xsd file for download if you'd like to check it out.

Enjoy!

Friday, September 10, 2004 8:21:00 AM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [0]  |  Trackback
 Thursday, September 09, 2004

Well, here we are again.  It's the second Thursday of the month and it's time once again for our monthly .NET User Group meeting.  Last month went quite well.  I had the opportunity to speak on Design Patterns.  Specifically, we covered such patterns as Adapter, Facade, Singleton, and Observer.  I got lots of great feedback on the presentation and lots of demand for a second part of the talk.

Which brings me to the topic at hand.  Tonight it will again be my opportunity to present to our local Salt Lake City .NET User Group a Part 2 of the Design Patterns talk.  Tonight will focus specifically on the Proxy, Flyweight, Builder, and Factory Method patterns.  I hope it goes as well as my previous presentations which have historically been received very well.

Our local website contains details on the meeting place and time.  Come one and all...pizza and drinks are being provided!...oh yeah, and a cool presentation too.

Thursday, September 09, 2004 8:02:00 AM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [1]  |  Trackback
 Wednesday, September 08, 2004
Don't forget to get the latest .NET Framework 1.1 Service Pack 1 and install it.  It contains lots of security updates as well as some performance enhancements.
Wednesday, September 08, 2004 4:29:00 AM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [0]  |  Trackback
 Tuesday, September 07, 2004

Over the past while (mostly but not entirely in my spare time), I've been working on creating a web-based input control.  This control behaves like a standard INPUT or TEXTAREA control would except that it allows for custom formattings (e.g. bolding, italicizing, indending text, etc) and what not.  You may already be familiar with some controls out there that do pretty much the same thing (such as FreeTextBox, et al).  While these controls are all fine and dandy, and no doubt their authors put in lots of time to get them to work properly, I couldn't bring myself to use them for a variety of reasons: 1) I don't usually like to use 3rd party components and 2) they didn't offer all the functionality that I desired in the control.

I've pretty much completed the control; I've finished it enough at least for version 1.0.  I have a lot of additional bits of functionality that I strongly desire to get into the control in a version 2.0 timeframe, but for the time being, I'll have to let it suffice as is.  The UI is pretty much nailed down as demonstrated in these screen shots.  I might make some very very minor tweaks here before I actually release the code, but it's pretty much frozen.  In a future article I'll go into the technical aspects of the control and how it was written and how I accomplished the various aspects of its design and development.

Each and every aspect of this control is customizable: from the buttons, to their appearance (image, or image/text), the dropdown menus, the emoticons, everything.  And it's all done via markup in your .ASPX page so it makes it very nice to display and code.

Let me know what you think!







Tuesday, September 07, 2004 1:36:00 AM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [1]  |  Trackback
 Saturday, September 04, 2004
I've always been a Media Player user; never really getting into WinAmp and MusicMatch - though I've had my share of using them.  Just this morning I installed the latest version of Microsoft's Media Player 10 and I must say I'm very impressed.  I very much enjoy the new look and feel as well as several of the enhancements that were made.  The transition to version 10 from 9 was seamless.  Very cool stuff!
Saturday, September 04, 2004 4:51:00 AM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [0]  |  Trackback
 Thursday, September 02, 2004

For the longest time I've been writing software (be it small apps, utilities, components, controls, or even large ambitious application) pretty much by myself.  Sure, on a weekly basis I gather with some friends and we have a coding bash and accomplish some great things.  I've never known what to call these endeavors.  Today I made a discovery that impressed me.

While perusing some blogs I read, I found a link to an article by Eric Sink of SourceGear.  He posted an article on msdn.microsoft.com in which he discusses the concept of a Micro-ISV; that is, a small one-man company focused on delivering products.  I feel that I very much fall into this category of Micro-ISV-ness.  Very soon I'll have my products website up with some downloadable products and tools for people to use within their applications.

Eric offers several hypotheses about what it takes to be a successful Micro-ISV.  I wholeheartedly agree with his “Don't start too big” section.  It's far too easy when designing and coding a product to not stick with your initial goals and concepts and allow scope creep to enter the picture or simply to dream too big.  I've done this on more than one occasion...it's a hard habit to break.

Anyway, good article, Eric...I look forward to reading more articles on the 'business of software'.

Thursday, September 02, 2004 6:12:00 PM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [0]  |  Trackback