Tuesday, December 9, 2008

Delete Command Twice in GridView

The delete command is being called twice for each click on delete. What do I mean by this? With this code:

<asp:GridView runat="server" DeleteMethod="DeleteRowById" ...>
    <asp:CommandField ShowDeleteButton="True" />
</asp:GridView>

I am getting the DeleteRowById method called which when the delete button in the CommandField is clicked. Doing some Internet research I found this:

http://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=105123

However, the workaround isn't right, basically all you need to do is change the <asp:CommandField to:

<asp:TemplateField>
 <ItemTemplate>
     <asp:ImageButton runat=server id="ImageButton1" CommandName="Delete" ImageUrl="..."
    CommandArgument='<%# DataBinder.Eval(Container, "RowIndex") %>' />
 </ItemTemplate>
</asp:TemplateField>

You don't need to add a code behind event handler for the ImageButton Command event.

{6230289B-5BEE-409e-932A-2F01FA407A92}

Tuesday, November 25, 2008

Shutting Down a Running COM+ Instance

If you have an instance of object running under COM+ (in the dllhost.exe process space) and you want to shut it down from C#, you can use the code below.  Note that ShutdownApplication only intializes the shutdown of the COM+ applicance, it will return sometimes before the application is finished shuting down.  So you need to check to make sure the applicatio is shut down before continue.  I use this code in my unit testing to kill the instance-- making sure that it is stateless.  Also note that you need to send the package id for the application -- this is the guid you used when installing the application into COM+.  It can be found on the first property page of the application properties in component manager.

internal static void ShutDown()
{
    Int32 count = 0;
    COMAdmin.COMAdminCatalog objAdmin = new COMAdmin.COMAdminCatalog();

    // WWB: ShutDown The Application (All Instances) Using teh Package Id
    objAdmin.ShutdownApplication(ConfigurationManager.AppSettings["packageId"]);

    // WWB: Iterate Over All Instances Making Sure They Are Shutdown
    do
    {
        count = 0;

        COMAdmin.COMAdminCatalogCollection  applicationInstances = 
            (COMAdmin.COMAdminCatalogCollection)
                objAdmin.GetCollection("ApplicationInstances");
        applicationInstances.Populate();

        foreach (COMAdmin.COMAdminCatalogObject applicationInstance in applicationInstances)
        {
            if (new Guid(applicationInstance.Name.ToString()) ==
                new Guid(ConfigurationManager.AppSettings["packageId"]))
                count++;
        }

        // WWB: Lets Wait Before Checking Again
        if (count>0)
            System.Threading.Thread.Sleep(100);

    }while (count > 0);
}

{6230289B-5BEE-409e-932A-2F01FA407A92}

Sunday, November 16, 2008

Mesh and IE Favorates

I have been playing around with Mesh a bit of late. Mostly good other than on a few of my machines, Mesh refused to install. Either the install itself failed or it just never allowed me to sign in.

But, I figured out one use for Mesh this morning that made it all come together: sharing IE Favorites. After installing Mesh, share the following folder on each machine:

Xp, 2003:
C:\Documents and Settings\%USERNAME%\Favorites\Links

Vista, 2008:
C:\Users\%USERNAME%\Favorites\Links

Make sure when resolving the share location of the folder on subsiquent machines, you browse to the location above.

Now your IE links are synced across machines.

 

Monday, November 3, 2008

Vista Administrator

In Vista there is an Administrator account, however it is disabled on install. If you join the domain you can login with any domain user you want, however if your domain connection is lost, your computer account or the domain lost then you can only login with the local user you created at install -- which can't be the Administrator. If you forget the the password to this user there is a problem. So to prevent a reinstall you need to enable the Administrator account with this:

Net user administrator /active:yes

at the command line under elevated permissions (Run As Administrator). The administrator password is blank (which means you don't have to login) by default.

{6230289B-5BEE-409e-932A-2F01FA407A92}

Thursday, October 30, 2008

Azure Storage - CloudDrive SDK

If you are the CloudDrive Powershell extension in the CTP of Azure Storage SDK, the easiest way to map a drive onto the cloud is to add this line to the MountDrive1.ps1 file:

MountDrive -ServiceUrl "http://blob.core.windows.net" -DriveName "BigDrive" -ProviderName "BlobDrive" -Account "accountName" -Key "XXXXXXXXXX"

Note that the accountName is the name of your project on the Azure web site, the storage project name. And the Key is the primary key you are given from the Azure web site. Also, the SeriveURL doesn't contain your account name at the beginning. Once you have this line added to the bottom of the file and save, you can run the runme.cmd batch file from the command line and your drive will be mapped.

The other two lines look like this:

MountDrive -ServiceUrl "http://127.0.0.1:10000" -DriveName "Blob" -ProviderName "BlobDrive"

MountDrive -ServiceUrl "http://127.0.0.1:10001" -DriveName "Queue" -ProviderName "QueueDrive"

and represent the local storage, which is used to test your storage application -- in this case CloudDrive. CloudDrive is a powershell extension. As an example, it is very limited -- a better example of how to write a power shell extension then how to interact with CloudDrive storage.

{6230289B-5BEE-409e-932A-2F01FA407A92}

Wednesday, October 8, 2008

varchar(max) and varbinary(max) Questions and Answers


How big of an allocation does max represent in the case of varchar(max) and varbinary(max)?

2 ^ 31 - 1 bytes or just about 2gb.

Can I specify a varchar(9000) or varbinary(12000)?
No.  If you are going to specify an actual number instead of (max) then your limit is 8000.  The size can be 1 to 8000, or max, there is nothing between 8000 and max.

Is a varchar(max) or varbinary(max) column stored in the data row?
If the size of the varchar or varbinary item is less than 8000 then it is stored in the data row. 
If it is greater than 8000 then the item is moved out of the data row into a special storage location for large objects.

Should varchar(max) be used to replace the TEXT column type?

Yes.  varchar(max) can handle larger strings than the text type can.  varchar(max) is easier to update than text with standard SQL statements.


Monday, September 29, 2008

Mutual Exclusive CollapsiblePanelExtender

The following piece of code will allow you to have multiple ASP.NET 2.0 Ajax CollapsiblePanelExtender controls on a single page and only one of them will be expanded at a time. You can use this to keep the page length short. You must set the BehaviorId of the CollapsiblePanelExtender, that is what the $find javascript function looks for. Make sure to add all the BehaviorIds to the array in the pageLoad function.

<script type="text/javascript">

// WWB: Handles Mutual Exclusive Expansion.  Only One Extender Panel 
// Can Be Open At A Time

var extenders = [];
   
function pageLoad(sender, args)
{
    extenders[0] = $find("PanelExtender1");
    extenders[1] = $find("PanelExtender2");
    extenders[2] = $find("PanelExtender3");


    // WWB: Hook All The Extenders, If extenders[i] is null then
    // the extenders is not being displayed -- i.e. not visible
    for (var i=0; i< extenders.length; i++)
        if (extenders[i] != null)
            extenders[i].add_expandComplete( expandHandler );
}

function expandHandler(sender, args)
{
    for (var i=0; i< extenders.length; i++)
    {
        if ((extenders[i] != null) && (extenders[i] != sender))
            extenders[i]._doClose();
    }
}
</script>

6230289B-5BEE-409e-932A-2F01FA407A92}

Wednesday, September 17, 2008

Happier Coding

Dina says I am happier when I code and not just during coding.

Some of my friends my age, and yes I am getting older, are moving to management jobs where they don’t code at all. They might be development leads that train and deploy new technologies in bigger companies, or design systems and have others code.

Some of the same friends that interviewed at our smaller company and we didn’t hire because they just didn’t seem right (Where I work now you can’t just design or train since the company is so small). These friends fit right in with the bigger companies; in fact they got jobs as developers however aren’t really doing any development at all. I have a hard time calling these jobs management since they don’t have a lot of people under them; I really just want to call them a “Non-Productive” job – which is reflection of my perspective on how I like coding. I wonder if the bigger companies will “wake up” and make them do something, or force them to do development which they were hired for, or just fire them.

I am not jealous, in fact I tell them about the stuff I am coding in my spare time and they don’t really care at all. Which lead me to realize they really aren’t happier coding?

P.S.

Along with this profound thought, I was out for my afternoon walk in a park near where I work and there was a women spinning wool to yarn in the sun. The first thought is what a waste of time. If I had that time, I would be coding something. Why doesn’t she code, what is the point of spinning yarn – someone in China and do it faster, better with a machine. Doesn’t she realize that we are living in a quickly changing Internet world and she could make her mark on our era with just a single web site. Then again the screen glare on the laptop prevents me from coding in the park on a sunny day. I might be sick.

{6230289B-5BEE-409e-932A-2F01FA407A92}

Monday, September 15, 2008

Indoubt Transaction in MSDTC

For days I have been battling with MSDTC (Microsoft Distributed Transaction Server) and transaction that have the status of indoubt.  We are using the .NET System.Transactions.TransactionScope class to wrap many SQL Server calls together in a single transaction, which requires MSDTC to manage the transaction.  The application would run for 30 minutes then hang, timing out, and the table on SQL Server would end up being row locked -- hanging all selects, inserts, updates and deletes.  It took me a while to realize that the Kerbose error in the application log on SQL Server was the reason the MSDTC communication was failing -- by comparing the crash time with the error log.  Then doing some research I realized that all those virtual machines running our web servers that we copied around on our virtual servers had the same SID.  Even though we had renamed them and readded them to the domain, that didn't generate a new SID.  So I used Microsoft's NewSID utility to give them different SIDs and I am back in business.

NewId Application:

http://technet.microsoft.com/en-us/sysinternals/bb897418.aspx

Kerbose Error:

Event Type: Error
Event Source: Kerberos
Event Category: None
Event ID: 4
Date:  9/12/2008
Time:  12:10:29 PM
User:  N/A
Computer: LE79TEST
Description:
The kerberos client received a KRB_AP_ERR_MODIFIED error from the server
XXXXX.  The target name used was
XXXX. This indicates that the
password used to encrypt the kerberos service ticket is different than that
on the target server. Commonly, this is due to identically named  machine
accounts in the target realm (XXXX), and the client realm.
Please contact your system administrator.

{6230289B-5BEE-409e-932A-2F01FA407A92}

Friday, September 12, 2008

DataBind() on Postback

Please junior developers don't Databind() your GridView on Postback.  You only need to databind your GridView once, when the page is first loaded (and anytime the data changes).  However, if you are lazy then you will DataBind on every postback forcing the GridView to go to the data source every postback and making your page slow. 

Your junior code:

protected void Page_Load(object sender, EventArgs e)
{
    ERSGridView1.DataBind();
}

My code:

protected void Page_Load(object sender, EventArgs e)
{
    if (!Page.IsPostBack)
        ERSGridView1.DataBind();
}

But, But...

Yes DataItem will not be accessible on Postback, so this code will not work:

protected void ERSGridView1_SelectedIndexChanged(object sender, EventArgs e)
{
   object myData = ERSGridView1.SelectedRow.DataItem;
}
You will have to do this:
protected void ERSGridView1_SelectedIndexChanged(object sender, EventArgs e)
{
   Int32 primaryKey = (Int32)ERSGridView1.DataKeys[ERSGridView1.SelectedIndex].Value;
   // Fetch Object From Data Source
}

I know, you have to fetch the object if the row is selected, however this is much better performance and more scaliable then fetching the all the objects on every post back.  Yes, the gridview will display all the rows if you don't DataBind, that is becuase it saves that information in ViewState and repopulates the row.

Two years ago this might not be as important.  However when you start Ajaxing your page (with Microsoft's ATLAS Extenders) there ends up being a lot more PostBacks that don't involve data changes.

{6230289B-5BEE-409e-932A-2F01FA407A92}

 

 

RaisePostBackEvent and RegisterRequiresRaiseEvent

ASP.NET 2.0

You do not have to call Page.RegisterRequiresRaiseEvent() in order for IPostBackEventHandler.RaisePostBackEvent() event to be called. In fact calling Page.RegisterRequiresRaiseEvent() will interfer with all other controls on the page that need post back events. The only way calling Page.RegisterRequiresRaiseEvent() actually works is if that is the only control on the page. GridView doesn't have to call Page.RegisterRequiresRaiseEvent().

However, implementing just IPostBackEventHandler isn't good enough, you need to implement: IPostBackDataHandler also on your control. If you don't then IPostBackEventHandler.RaisePostBackEvent() will never be called.

{6230289B-5BEE-409e-932A-2F01FA407A92}

Monday, September 8, 2008

/useenv on the command line.

/useenv as s command line switch in Visual Studio 2005 is described as: "Use the following command line switches to display the integrated development environment and perform the described task." as opposed to: "Use the following command line switches to perform the described task. These command line switches do not display the IDE."  Reference: http://msdn.microsoft.com/en-us/library/xee0c8y7(VS.80).aspx  Which would make you think that /useenv is a switch for the IDE, however if you don't use it on the command line your settings for INCLUDE and LIB enviormental variables will not be used, making it very difficult to switch between platform SDK include files for your different OS versions.

{6230289B-5BEE-409e-932A-2F01FA407A92}

 

Sunday, September 7, 2008

Warning As Error CS1668 -- Not the Usually Excuse

You can get CS1668 if you set your LIB path like this in your .bat file that builds your project using devenv.exe:

SET LIB="c:\Program Files\Microsoft SDKs\Windows\v6.0\Lib\"

instead of like this (the right way):

SET LIB=c:\Program Files\Microsoft SDKs\Windows\v6.0\Lib\

Note the quotes. Time Wasted: 2 hours.

The usual suspect:

http://msdn.microsoft.com/en-us/library/tcbs315h(VS.80).aspx

{6230289B-5BEE-409e-932A-2F01FA407A92}

Friday, September 5, 2008

Google Chrome And IIS 5.0

Don't get me wrong, I like Google Chrome, and it's application shortcut that makes a web application appear like a windows application is an eye opener.  However, it reminds me of IIS 5.0.  When IIS 5.0 was released everyone was so excited that you could run the web site application in a separate process, giving you one process per web site.  This meant if a web site crashed it wouldn't take down the rest of the sites on the server.  However, it was soon realized that windows can only handle a finite amount of threads somewhere between 500 - 800 before it slows to a crawl.  This is why you need to be very careful when multi-threading. All the IIS 5.0 web sites with 5-10 threads a piece where chocking the OS.   So IIS 5.0 was replaced with IIS 6.0 where you could group web sites into a single process.  Back to Chrome: 26 threads just to run Pandora.com in one process.  The other processes are running 9 threads a piece.  Just for you that don't know, each tab in Chrome runs in a separate window process and closing a tab closes the process, freeing the OS resources, threads, memory, etc... A noble idea to contain and isolate web applications that have run away javascript.  However, this isn't Linux -- you can't just fork as much as you want.

{6230289B-5BEE-409e-932A-2F01FA407A92}

Wednesday, September 3, 2008

Mapping in SQL Server Reporting Services

n honor of William Vaugh showing up at our local .NET user group, I am posting a SSRS article about how to add maps to your SQL Server Reporting Service reports. Google Map has static mapping functionality in their API set. Static maps are the ability to call a URL on the Google server and get back a single image of the map. This is different then using the Ajax objects in your web page, since those objects need the javascript engine to excute. With SSRS you don't get a javascript engine and you can't be guareenteed that it will be a web page, the export might be a PDF. Google Maps API for static maps takes a Query string like this example: http://maps.google.com/staticmap?center=40.714728,-73.998672&zoom=14&size=512x512&maptype=mobile\&markers=40.702147,-74.015794,blues%7C40.711614,-74.012318,greeng%7C40.718217,-73.998284,redc\&key=MAPS_API_KEY With good eyes you can see that it sets the center, zoom, and some markets on the map. First thing to do is drop the zoom and center, the map will auto center with the markers. Here are the steps: 1) Create a new Report in SSRS, try to avoid the wizard, however if you can't use some Random T-SQL to get past it. 2) Delete the table or matrix the wizard created for you. 3) Create a Image, using a Web image, and add the URL above. 4) Create a new Data Set, Here is the T-SQL I used to create my markers:
DECLARE @Markers varchar(max) 
DECLARE @latitude float 
DECLARE @longitude float 

SET @Markers = 'markers=' 

DECLARE _cursor CURSOR FOR 
SELECT DISTINCT TOP 50 Latitude, Longitude 
FROM MapTable 
WHERE (NOT Latitude IS NULL) AND (NOT Longitude IS NULL) 

OPEN _cursor 
FETCH NEXT FROM _cursor 
INTO @latitude, @longitude 

WHILE @@FETCH_STATUS = 0 
BEGIN 

IF ((NOT (@latitude IS NULL)) AND (NOT (@longitude IS NULL))) 
SET @Markers = @Markers + CONVERT(varchar(max),@latitude) + ',' + CONVERT(varchar(max),@longitude) + ',tinyred%7C' 

FETCH NEXT FROM _cursor 
INTO @latitude, @longitude 

END 

CLOSE _cursor 
DEALLOCATE _cursor 

SELECT @Markers AS Marker 
5) Click on the Image in your preview window and open the properties. Change the Value from the static URL to this dynamic URL: ="http://maps.google.com/staticmap?size=640x640&maptype=mobile&" + Fields!Marker.Value + "&key=YourKey" Notes: - Google Static Map API only allows you to map 50 markers, that is why the SELECT is TOP 50 - There are two property windows for SSRS reports, you need the one that is dockable, not the pop-up property dialog. The Value property is only in the dockable one. Use the tool bar, choose View, Then Property Window. - You will need to insert your Google Key, note that with static image it isn't related to the web site the image on, since the image might be in a PDF, it is just a way to track your usage. More Info: http://code.google.com/apis/maps/documentation/staticmaps/ {6230289B-5BEE-409e-932A-2F01FA407A92}

Wednesday, August 20, 2008

Role Enabled Custom Controls

So we did all this work to a membership provider and a role provider for ASP.NET and it was a lesson in patients as we figured out how it was all suppose to work.  However, now we have it I found out that the ASP.NET controls are not enabled for roles.  It works well with our Forms authentication for blocking access to pages where the user doesn't have the right role.  However, I want to display controls and not display controls based on the user's role.  I love to create custom controls that work just like we want them to work.  So I subclassed the Panel control and "roles" enabled it.  Code:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Text;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;

namespace WebUtility
{
    [DefaultProperty("Text")]
    [ToolboxData("<{0}:RolePanel runat=server></{0}:RolePanel>")]
    public class RolePanel : Panel
    {
        /// <summary>
        /// Gets or sets the roles
        /// </summary>
        /// <value>The on click.</value>
        [Bindable(true)]
        [DefaultValue("")]
        [Localizable(true)]
        public string Roles
        {
            get
            {
                String s = (String)ViewState["Roles"];
                return s ?? string.Empty;
            }
            set
            {
                ViewState["Roles"] = value;
            }
        }

        protected override void OnLoad(EventArgs e)
        {
            Boolean bFound = false;
            foreach (String role in Roles.Split(','))
                if (Page.User.IsInRole(role.Trim()))
                {
                    bFound = true;
                    break;
                }

            if ((Visible == true) && (bFound == false))
                Visible = false;

            base.OnLoad(e);
        }
    }
}

Now all I have to do is to all this ASP.NET code to our pages:

<webutility:RolePanel runat="server" ID="adminRoleHyperLink" Roles="AdministrationAccess">Admin</webutility:RolePanel>

The new Roles property is a comma delimited list of "roles" that are checked against the users permissions when the page loads, if they have one role in the list they can see everything in the panel.

Note that the same code will work for most all ASP.NET controls without changing it, just modify the name of the control you are subclassing.

{6230289B-5BEE-409e-932A-2F01FA407A92}

 

 

Tuesday, August 19, 2008

Dynamic Javascript in an Update Panel - A Better Way

Last post was about how dynamic javascript in the update panel was not being re-executed on a partial postback.  I have a better way of making it execute -- parse the controls in the UpdatePanel and call the javascript method eval().

Here is the hook:

Page.ClientScript.RegisterStartupScript(this.GetType(), "LoadHandler", "function LoadHandler(){EvalScript($get('" + updatePanel.ClientID + "'),'scriptId');};\r\n", true);

Page.ClientScript.RegisterStartupScript(this.GetType(), "AddLoadHandler", "Sys.Application.add_load(LoadHandler);\r\n", true);

EvalScript looks like this:

function EvalScript(control, id)
{
 if(!control){return;}
 for(var n=0;n<control.children.length;n++)
 {
     if((control.children[n].tagName == 'SCRIPT') && (control.children[n].id == id)){
         var scriptElement = control.removeChild(control.children[n]);
         eval(scriptElement.innerHTML);
     }else{
         EvalScript(control.children[n], id);
     }
 }
}

Basically EvalScript searchs client side all the controls in the updatepanel control for one with the name specified, then calls Eval on it's innerHTML.  Just a note, you don't want inside comments in your <SCRIPT> tag, they don't eval() well.

{6230289B-5BEE-409e-932A-2F01FA407A92}

Friday, August 15, 2008

Dynamic Javascript in an Update Panel

If you are using an Ajax UpdatePanel in ASP.NET and you have a control that renders javascript, that javascript will not change when on a partial postback.  For example, I have a Repeater control that loops though so GEO points and outputs javascript to add PushPin to a Virtual Earth map.  If a partial postback is called, and the Repeater rebinds it recreates the javascript, however on the client side the ScriptManager just replaces the UpdatePanel innerHTML with then new code.  It doesn't tell the javascript engine to reparse that code and recreate the functions that are being replaced.  To solve this problem I had to write some javascript client code that would parse the return, and move my <SCRIPT> tags to the head of the page.  This "woke up" the javascript engine and the new functions where recompiled.

How to hook it up:

1) The first thing to do is to tell the ScriptManager to parse the innerHTML of the UpdatePanel and move all script blocks to the head of the page (note: you need to have a page head).  You can do this like this:

Sys.WebForms.PageRequestManager.getInstance().add_endRequest(EndRequestHandler);

2) Next you need to add a function that call EndRequestHandler that will call the parser, like this:

function EndRequestHandler(sender,args){ParseScript($get('UpdatePanelId'));};

Mine looks like this:

Page.ClientScript.RegisterStartupScript(this.GetType(), "EndRequestHandler", "function EndRequestHandler(sender,args){ParseScript($get('" + updatePanel.ClientID + "_Container'));};\r\n", true);

Not that the ParseScript function traverses the controls in the page and find childern with the tag name of <SCRIPT>, so I wanted to start with a UpdatePanel parent, and since the id is dynamically generated, I need to add the EndRequestHandler client side javascript from the server.

3) Next write ParseScript to traverse the childern and move all SCRIPT tags to the head, I will let you write your own code, cause this is QED.

{6230289B-5BEE-409e-932A-2F01FA407A92}

Thursday, August 14, 2008

I am back...

I took off 4 months to remodel my kitchen, in that time I did no programming.  Now I am back to work for at least a week now.  I havn't forgotten anything (except for the SQL Server sa password -- which means that it is a good one.)  I can program just as well as before I left. 

{6230289B-5BEE-409e-932A-2F01FA407A92}

Monday, August 11, 2008

DAL using subsonic

I was investigating a starter website and noticed that it mentioned subsonic. Since third-party components can be tricky for implementation and security, I went to see what it was. Subsonic is an open source Data Access Layer. Someone has probably mentioned it to me before but it didn't register. The website has videos using subsonic which are great. Subsonic connects to the database and creates a DAL for a .Net project which uses intellisense for discovery. The only thing that worries me is that the connection isn't through SPs they create but they say they parse the SQL commands so that SQL injection isn't a problem. I also watched another video of theirs about their REST handler on their tips and tricks page. While I still like .NetTiers for my DAL (which requires a purchase of CodeSmith), I liked the easy of use and the fact that CodeSmith is not required. Just as I was grumbling to myself about a startersite for this, I found the link on the front page of their site for just that. It's a content management site that includes login, page generation, etc. The video cut out on me halfway through but I got the main idea and I liked it. I also noticed that the video mentioned FCKeditor which I hadn't heard of before.

I like this stuff but will probably use it for my own admin pages. It's not that the product is bad but I want to go through generated sp's and I like that .NetTiers gives me an admin site and web service. .NetTiers is also a layer I know who to code to and go farther with in terms of specificity and inheritance. But I really like this subsonic stuff.

 

 

Monday, June 30, 2008

Blackberry thows exception

My blackberry just threw an exception. The consumer in me thinks oh, $%^@#$. The programmer in me thinks that's so cool, I wonder what did it. A hard boot got the thing going again.

Wednesday, June 25, 2008

User Login, Create User, Send Password close to done

The basics for the login pages are done. A few more tweaks and that will be done.

It might seem like I take forever but in truth, I don't have many hours a week to give to this pet project right now. I hope to have more time this fall.

 

Friday, June 6, 2008

The Dark Side of Extension Methods

In my quest to find a suitable generic update method for a generic LINQ Data Access class I have found a decent amount of code and even more theories (sans code).  Some good, some verbose, and a lot bad.  It seems that Extension Methods are a fan favorite for just about everything nowadays and I wasn't suprised to see Extension Methods being used for LINQ to SQL updates.

In reading about Extension Methods I have heard good and bad feedback.  The good usually involves the convenience of bypassing a utility class and the bad involving code readability / maintability.  Forgive me for I am the William Hung of LINQ as I have had no formal training in this arena, but IMHO Extension Methods are a pain-in-the-ass when ExtensionMethodA returns the result of ExtensionMethodB which depends on the output of ExtensionMethodC which ..... Here is an example of what I am talking about in pseudo-code:

public static bool Update<T>(this Table<T> table, T instance) where T: class
{
   try
   
{
      // do stuff here with parameters...
      
return table.Foo() > 0;
   }
   catch (Exception)
   {
      return false;
   }
}

public static int Foo<T>(this Table<T> table) where T: class
{
   return table.GetEnumerator().Bar();
}

public static int Bar<T>(this IEnumerator<T> e) where T : class
{
   return 1;

Horrible example yes and for that I apologize.  Hopefully you see where I am going with this and that if you are given a similar codebase it can be quite confusing.  When I first heard of extension methods I though they were for simple utility tasks such as outputting a decimal value as a dollar amount (again, doesn't REALLY warrant an extension method but...):

public static string PrintWithDollarSign(this decimal input)
{
   return input.ToString("c");
}

Simple. Effective. Here's how you would use:

Console.WriteLine(instance.Price.PrintWithDollarSign());

Cheap. No frills.  I could have just bypassed this extension method altogether but it provides a very "Hello World"'y introduction to extension methods for those of you not hip to them.  In my previous example if you had an object with a decimal property and the value was 5.00 the output would be $5.00. Easy.

Anyone care to chime in on extension methods relying on other extension methods? I'd love to get some feedback.

Thursday, June 5, 2008

Adding unique indexes to SQL Tables

Now that I'm diggging a little deeper into my pet project web site, I'm learning how I really use the data. The Get() methods provided by .netTiers are great but I thought I would have more. I started looking at my database starting with the aspnet_Membership tables. I would expect unique indexes on something as obvious as the email column but it is not there. Same goes for other obvious columns in these microsoft-provided tables. So I add unique indexes to the membership tables. Of course, I do the same for my own custom tables and find that some of the text or varchar(max) have to be changed. No biggie but another step I have to take.

Now that the database is set, I need to regen the .netTiers templates, rerun the GrantUser stored proc, build the .netTiers stuff and copy it into the /bin of my project. Not hard work but just tedious to get each step completed and move on to the next one. All this so my .Get methods are consistent with my usage of the library.

All this so I can get my validators working - I really hate validators.

Wednesday, June 4, 2008

New SQL security finds web.config hole

Yesterday, I was reading the MSDN FLASH email newsletter and saw a listing for another SQL injection article. Since one of Wayne's sites was recently hacked, I thought I would read it. The article is clear and gives explicit steps to take. Since I had only done one of the three things (SQL stored procs), I thought I would mess with the last one: execute permissions only.

Wayne pointed me to a stored proc to grant permissions to a user. Since users and roles are one of those areas that after you have them set up correctly, you don't mess with them, I stumbled my way through creating a user. I ran the stored proc (which will have to be run after every .netTiers generation), modified my sql connection in web.config and tried my pet project site. I get an error about SELECT statements and permissions. .netTiers was supposed to be configured in web.config to use stored procs but apparently not. So I change the file, build, and still get the error. Someone, somewhere is caching something. Close everything done. Reopen. Change web.config and checkin with comment. Test again and it works.

This is a really bad way to find a problem. It's getting to the point that I need someone to check my security changes so this type of thing isn't missed.

Monday, June 2, 2008

Free Personal Websites

Volume 14 of Make Magazine has an interesting article on free web sites - web site builders that also host the end resulting web site.

Friday, May 30, 2008

15seconds.com article: netTiers

Here is my latest article on 15seconds.com: http://www.15seconds.com/issue/080529.htm titled "Implementing the .netTiers Template Library as a .NET Website's Data Layer - Part I"
 

Thursday, May 29, 2008

Crosspost from DasBlog to DasBlog

Now I'm trying to get all my blogs tied together. Most are DasBlog so no big deal only what is the endpoint? The documentation lists blogger.aspx off the root but the file isn't there. Apparently from the forums I'm not the only one with the issue. However, I thought I bet it is there virtual. So I enter that in the address bar and I get a response of web service methods available. Nice - why doesn't the documentation say the page isn't there but the dll will respond.

Friday, May 9, 2008

Cool clock on neighborhood-kids.com

I get an email every friday about the new events for families on the weekend on www.neighborhood-kids.com. The design is great. I love it, but my eyes and mind do skip over things in the page just assuming they are pretty but useless. Then I noticed the clock at the top of the page was right about the time. Wow, that is cool. I'm going to have to pay closer attention to what is technically happening on web sites.

Saturday, May 3, 2008

The sun is out

Living in the northwest is wonderful but when spring and summer start to show up (meaning the sun makes a regular appearance), I totally loose all motivation. My brother from England worked a summer for Boeing in Seattle a few years ago. He was shocked that people didn't do any work in the summer. I was always luck at Microsoft to not have a summer ship date. That would have totally sucked.

How can I think about writing code and the project when the sun is out just laughing at me, taunting me to come out.

Friday, May 2, 2008

Time goes on

There are a few times in my life when a project phase just goes on forever. Back in my Microsoft days, I hated the last stages of the ship cycle. People were burned out, higher-ups were already on to the next phase. Front-line devs, testers, pms, and writers were either too burned out to function or too hyped up to listen. The mid-level guys were running interference every which way. The bell-curve was watched and the marketing dicks were breathing down everyone's throat and lord help you if you broke the build. I was thrilled when my boss finally made the rounds to ask if I thought the product was ready to ship. He asked everyone personally instead of some meeting where the team bully could badger me into saying something I would regret.

And here I am with my own project in the end of one of the first phases and I trudge on. I'm not near enough done. No ship award in my near future.

Wednesday, April 23, 2008

Null vs. Void

I'm moving some duplicate code from a codebehind file to a class file so I can reuse it. I'm tired and it's a bit late for my best programming so I'm trying to remember is the c# return type also null now. Or maybe no return type needs to be specified if a variable isn't returned (is that vb?). If null works in SQL and it works an an object value to compare against in c#, why can't null be a valid return type instead of void. What does void really say under the covers that is so different from null. Isn't void a carryover from c and c++. Perhaps it's older than that. Why do I need to remember yet another keyword that has no value. No, that's not a pun. I have too many numbers and words in my head that have to be pulled out at a moment's notice. Why add one more? Get rid of void. Just rip it right out.

Tuesday, April 22, 2008

Exception Handling for NetTiers

I'm still cleaning up code. One of the things that has never worked in NetTiers is the Microsoft Enterprise Library Exception Handling. I would get an exception thrown in the NetTiers code and the line was an exception with no data. I would get the error on a SQL insert via the NetTiers layers but no information about what was wrong with the data. Since I'm using the Insert where the param is the base type such as

   userService.Insert(newUser);

I knew that datatyping from a C#/.Net perspective wasn't the problem. I knew it was at a SQL level but I didn't know what it could be. So I finally had to fix the .NetTiers ExceptionHandling. I'm not sure why doesn't work right off the bat like the rest of NetTiers but I also didn't care enough to investigate. Once I get the ExceptionHandling working, I don't ever touch so I haven't had to REALLY understand it.

But now it has to work, it's hard to move forward without inserting data on this particular table. I knew I didn't have anything in web.config for this so that's where I started. I opened the ExceptionHandling Basic Quickstart and looked at the app.config. There were two different sections I added and then the true SQL error was displayed. While this isn't the entire work I'll have to do with Exception Handling on my pet project, at least I can keep going.

The SQL problem turned out to be that a datetime value was not being passed for a column where NULL was not allowed. Once the error handling showed the problem of datetime out of range, and I looked at the column definitions, I realized the problem.

Here are the sections I added to web.config:

1) in the <configSections>

<section name="exceptionHandling" type="Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.Configuration.ExceptionHandlingSettings, Microsoft.Practices.EnterpriseLibrary.ExceptionHandling" />

2) the last entry before the end <Configuration> tag:

<exceptionHandling>

<exceptionPolicies>

<add name="Global Policy">

<exceptionTypes>

<add name="Exception" type="System.Exception, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" postHandlingAction="None">

<exceptionHandlers>

<!--<add name="Wrap Handler" type="Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.WrapHandler, Microsoft.Practices.EnterpriseLibrary.ExceptionHandling" exceptionMessage="Global Message." wrapExceptionType="ExceptionHandlingQuickStart.BusinessLayer.BusinessLayerException, ExceptionHandlingQuickStart.BusinessLayer" />-->

<add name="Custom Handler" type="ExceptionHandlingQuickStart.AppMessageExceptionHandler, ExceptionHandlingBasicQuickStart"/>

</exceptionHandlers>

</add>

</exceptionTypes>

</add>

<add name="Handle and Resume Policy">

<exceptionTypes>

<add name="Exception" type="System.Exception, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" postHandlingAction="None">

<exceptionHandlers />

</add>

</exceptionTypes>

</add>

<add name="Propagate Policy">

<exceptionTypes>

<add name="Exception" type="System.Exception, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" postHandlingAction="NotifyRethrow">

<exceptionHandlers />

</add>

</exceptionTypes>

</add>

<add name="Replace Policy">

<exceptionTypes>

<add name="SecurityException" type="System.Security.SecurityException, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" postHandlingAction="ThrowNewException">

<exceptionHandlers>

<add name="Replace Handler" type="Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.ReplaceHandler, Microsoft.Practices.EnterpriseLibrary.ExceptionHandling" exceptionMessage="Replaced Exception: User is not authorized to peform the requested action." replaceExceptionType="System.ApplicationException, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />

</exceptionHandlers>

</add>

</exceptionTypes>

</add>

<add name="Wrap Policy">

<exceptionTypes>

<add name="DBConcurrencyException" type="System.Data.DBConcurrencyException, System.Data, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" postHandlingAction="ThrowNewException">

<exceptionHandlers>

<add name="Wrap Handler" type="Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.WrapHandler, Microsoft.Practices.EnterpriseLibrary.ExceptionHandling" exceptionMessage="Wrapped Exception: A recoverable error occurred while attempting to access the database." wrapExceptionType="ExceptionHandlingQuickStart.BusinessLayer.BusinessLayerException, ExceptionHandlingQuickStart.BusinessLayer" />

</exceptionHandlers>

</add>

</exceptionTypes>

</add>

</exceptionPolicies>

</exceptionHandling>