Thursday, December 20, 2007

Derived classes and events

Events are a useful feature of the C# language and allow for clients to be notified when the state of a server changes. You create an event by declaring it in the class that will raise the event:

public event EventHandler NotifyClient;

Here, I used the generic event template to eliminate the need to define a separate delegate. To register a callback method with the event, we do the following in the class that will receive the event notification from the class that raised the event:

message.NotifyClient += new EventHandler(message_NotifyClient);

static void message_NotifyClient(object sender, EventArgs e)
{
throw new Exception("The method or operation is not implemented.");
}

When the event is raised the message_NotifyClient method will execute. To raise the event we add the following to the class that raises the event:

protected virtual void OnNotifyClient(EventArgs e)
{
EventHandler handler = NotifyClient;

if (handler != null)
handler(this, e);
}

Here, we make a copy of the event since access to the event is not thread-safe (i.e. another thread could delete/modify the event before the if (handler != null) completes). When the client wants to raise the event, it does so by executing the OnNotifyClient method:

private void DoSomething()
{
// Do something...
OnNotifyClient(new EventArgs());
}

When OnNotifyClient is executed, the message_NotifyClient method is executed.

Derived classes and events
An event can only be raised from methods declare within the class that declared the event. So events declared in class A can only be raised from methods within class A. By default, events are not inherited by derived classes, so in the following base class definition:

public class Base
{
public event EventHander NotifyClient;
}

with the derived class definition

public class Derived : Base
{
private void DoSomething()
{
EventHandler handler = NotifyClient;

if (handler != null)
handler(this, new EventArgs());
}
}

The derived class will not have access to the NotifyClient event since the NotifyClient event was not declared in the Derived class. There are two ways to workaround this requirement:
You'll notice above that the OnNotifyClient method is declared with the virtual keyword. This allows the Derived class to override the OnNotifyClient method and access the NotifyClient directly. So, we could do this in the Derived class:

protected override void OnNotifyClient(EventArgs e)
{
EventHandler handler = NotifyClient;

if (handler != null)
handler(this, e);
}

protected void DoSomething()
{
// Do something Derived-class specific, then call OnNotifyClient
OnNotifyClient(new EventArgs());
}

The second approach is to declare the event virtual in the Base class, then override the event in the Derived class:

public class Base
{
public virtual event EventHandler NotifyClient;
...
}

public class Derived : Base
{
public override event EventHandler NotifyClient;

public void DoSomething()
{
EventHandler handler = NotifyClient;

// Here, we can access the NotifyClient directly since we've overridden access
if (handler != null)
handler(this, new EventArgs());
}
}

Which approach you take is up to you. Both approaches give the Derived class the flexibility to perform Derived-specific tasks around the decision to raise the event. It really comes down to which approach is preferred.

Wednesday, November 28, 2007




When the pros of using reflection outweigh the cons, you can take advantage of using it when iterating over properties of classes.  To help with debugging, you can override the ToString() of your classes to return a string that contains the name of any public properties along with their corresponding values:

        public override string ToString()
        {
            StringBuilder sb = new StringBuilder();
            Type t = this.GetType();

            PropertyInfo[] pInfo = t.GetProperties();

            sb.Append(t.ToString() + "\r\n");

            foreach (PropertyInfo p in pInfo)
            {
                string fmt = string.Format("{0} = {1}", p.Name, p.GetValue(this, null));

                sb.Append(fmt + "\r\n");
            }

            return sb.ToString();
        }

The function uses reflection to get the public properties of the class and it iterates over the collection of properties and builds a string that represents the state of the class.  The string returned from ToString() would look like this:

Property1 = Value1
Property2 = Value2
.
.
.

The PropertyInfo.GetValue() function takes two parameters; the first parameter is the reference to this, while the second parameter is an object array that is used when the PropertyInfo references an indexed property.  When using reflection to enumerate properties on a class that contains other classes, you must accomodate the case where a class might contain an indexed property:

public class Details
{
        public EDIProcess.Model.EstimateDetailType this[int index]
        {
            get { return (EDIProcess.Model.EstimateDetailType) DetailCollection[index]; }
        }
}

In this case, the Details class contains an indexer that is used to retrieve EstimateDetailType objects by index from the DetailCollection property.  You can use the GetValue() function and pass in an object array that contains a single entry that is the value of the offset into the collection, which will reference the indexed item at that position within the collection.

public class Details
{
        public override string ToString()
        {
            StringBuilder sb = new StringBuilder();

            try
            {
                Type t = this.GetType();
                string fmt;

                PropertyInfo[] pInfo = t.GetProperties();


                sb.Append(t.ToString() + "\r\n");

                foreach (PropertyInfo p in pInfo)
                {
                    if (p.Name == "Item")
                    {
                        for (int i = 0; i < Count; i++)
                        {
                            fmt = string.Format("{0} = {1}", p.Name, p.GetValue(this, new object[] { i }));
                            sb.Append(fmt + "\r\n");
                        }
                    }
                    else
                    {
                        fmt = string.Format("{0} = {1}", p.Name, p.GetValue(this, null));
                        sb.Append(fmt + "\r\n");
                    }
                }
            }
            catch (Exception ex)
            {
                log.Error(ex.Message);
            }

            return sb.ToString();
    }
}

The items in bold represent the changes to support the indexed properties.  The Details class contains a DetailCollection class (which derives from ArrayList), and the Details class contains an indexer to retrieve an item from the collection as well as a property that returns the number of items stored in the collection.  The modification looks for a property named Item in the Details class, and it then retrieves the count of objects in the collection and for each object in the collection it calls the GetValue(), passing in a reference to the Details class (this) and a new object[] array that contains a single member that stores the integer value of the item within the collection.




Friday, September 21, 2007

Threading in C#

I found an interesting online resource on Threading in C#

Friday, November 03, 2006

On the project that I am leading, I ran across an interesting issue using a combination of a stored procedure and the ISNULL SQL function. If we have the following table definition:

CREATE TABLE [dbo].[TEST_TABLE] (
[Data] [varchar] (5) COLLATE Latin1_General_BIN NOT NULL
)


and this table contains a row that contains:

Data
=====

ABCDE


Now, if we have a stored procedure that updates the row called UpdateData:

CREATE PROCEDURE UpdateData
@Data varchar(3) = null
AS

Begin Tran
update TEST_TABLE
set Data = ISNULL(@Data, Data)
IF @@ERROR <> 0
Rollback Tran
else
Commit Tran

The stored procedure sets the @Data variable to null by defualt, otherwise it will use the value passed to it via the @Data variable. The update statement uses the ISNULL function to check if the @Data variable is the default value of null and if it is, then it should use the current value in the Data column on the table. If we call this stored procedure, without passing in the @Data parameter, the stored procedure will set the value of the @Data parameter to null, and this is what we'll get:

Data
=====
ABC


What happened, why did my data get truncated? Think about how the ISNULL function works, it sets the variable specified in the first parameter to the value of the variable specified in the second parameter if the first parameter is null. So, it should set the @Data variable to the value in the Data column. Also note that the stored procedure is implicitly creating the @Data variable in the procedure declaration and also note that the @Data variable has a width of 3 varchars instead of the 5 varchars in the TEMP_TABLE Data column declaration. Essentially what happens is that the ISNULL function sees that the @Data variable is null, reads the Data column from the table and then copies the value from the Data column into the @Data variable. Since the @Data variable is only 3 varchars wide, only 3 varchars are copied to it. The set clause then updates the Data column with these 3 varchars from the @Data variable. That's why the data gets truncated. So, the moral of the story is to make sure of the following:

Wednesday, October 04, 2006

SQL Server pseudo nested transactions

SQL Server 2000 does not support nested transactions. So, if we have the following two stored procedures:

SP1:

create procedure sp1
as

begin tran
exec sp2

if (@@error <> 0)
goto on_error

commit tran

on_error:
rollback tran



SP1:
create procedure sp2
as

begin tran
insert table values ('sp2')

if (@@error <> 0)
goto on_error

commit tran

on_error:
rollback tran


The rules for nested begin transactions is:
Basically, I think of nested transactions as really just one main transaction that could contain multiple time points where the state of the transaction (committed, rollback) can be modified.

When using the ADO.NET SqlTransaction object you need to be aware of how transactions are committed and rolled back. When the SqlTransaction.BeginTransaction method is executed, it implicitly executes the BEGIN TRAN SQL statement. Any SQL statements executed will then execute on this main transaction. If the SQL statements that are executed perform a commit or rollback, the main transaction is affected based on the rules above. There may be certain cases where a servere SQL error will cause the main transaction to rollback, and if the main transaction is created by the SqlTransaction.BeginTransaction method, when the exception is caught, you will need to check if the transacion was actually already rolled back before executing the SqlTransaction.Rollback method:


try
{
try
{
SqlTransaction txn = new SqlTransaction();

txn.BeginTransaction();

Data.CallSproc1();
Data.CallSproc2(); // throws an exception and rolls back the transaction
txn.CommitTransaction();
}
catch(SqlException ex)
{
txn.RollbackTransaction(); // If the transaction is already rolled back, this will throw an exception
}
}
catch(SqlException ex)
{
// Transaction was already rolled back, do something
}
}

Monday, October 02, 2006

NTLM Explained

If you want a good description of NTLM Authentication (Windows authentication), you can find a good resource here

Tuesday, September 26, 2006

Dan Ciruli has a good post on software as a service (SAAS) in the trenches here. In it he talks about a number of concerns that CIO's raise. One of the concerns revolves around data security. Companies want to be able to own the only copy of their data (not in some vendor's database halfway across the country) and they want to have the ability to apply different levels of access to the data based on how the data is used. Having been involved in more than one SaaS projects, the discussions I've heard the same things over and over from clients: Where will my data live, and how will you secure it? In general, discussions usually go like this:

Project Lead: Your users can login to your own custom-branded site and use the system.
Customer: How do I limit access to the site?
Project Lead: We have an admin screen that you can use to setup users.

Customer: What if I want this integrated with my ActiveDirectory security? When a user quits, I want one way to turn off access to the application. Can I do that?

Project Lead: No, you'd have to use the admin tool to remove the user. We're working on the ActiveDirctory integration for the next release

Customer: Well, OK, so how do I prevent users outside of the company from going to the site and hacking into it?

Project Lead: We have a very secure data center.

Customer: What about data, where will it live?

Project Lead: At our data center.

Customer: So, if something happens to the data center, I won't have access to the data? What if your site is down, and what if this is our accounting system, then we can't use the system.

Project Lead: Well, yes, but we do have a service level agreement that we'd have to adhere to or your subscription will get reduced. Also, our data center also has a service level agreement.

Customer: I honestly don't really care about the reduced subscription rate, but at your prices this application needs to be bulletproof. I need access to this data 24-7. So, do you have a backup plan for my data?

Project Lead: of course.

Customer: OK, let me think about it. I'll get back to you.

The project lead goes back to the team and tells them to firgure out a way to integrate their application with ActiveDirectory. The project lead also has some conversations with their hosting site. The customer discusses purchasing the service with the executive management team. The customer calls the project lead up:

Customer: Well, I talked with the management here, and they had some concerns over data access.

Project Lead: Our security policies are very comprehensive, your data will be secure.

Customer: I can't guarantee that my data will be secure, unless I own the data, behind my firewall.

Project Lead: Well I see your point, but you won't be able to get the advantages of automatic feature upgrades and centralized administration if we are unable to access the application behind your firewall.

Customer: Access and control of our data is our biggest concern, the other 'features' are not essential. Also, how do we get the data out of your system? Can we export the data into Excel?

Project Lead: Well, we don't have an easy way of doing this, I suppose you could export most of the data to Excel, but some of the data will take a little more work to get to it.

Project Lead: What databases do you have available for use?

Customer: Oracle. Some MySQL.

Project Lead: Well, currently our application only works with SQL Server, and we'd need to make some modifications to give you the ability to host the application behind your firewall.

Customer: We like the features of the application, but having a system that we can host ourselves is a requirement. Let me know when you have an application that will integrate with our security model and our network architecture.

Project Lead: OK, I'll see what I can do...

I've seen most conversations with customers turn out like this, especially when you are trying to sell to the first handful of customers. As the number of customers increases, there's usually trust in numbers, so it becomes less of a concern if a potential client can see that other clients trust you with their data. I'm sure the first few customers that SalesForce tried to get had issues around data access and security. If you are unable to convince them to trust you with their data, you have two choices:
  1. Convert your application into a hosted solution that the customer can install on their own servers and manage. Don't forget to think about how they will install and upgrade the application. And security. And data migration from legacy systems. And browser support. And rollback strategies.
  2. Try and convince some other large enterprise customer that trusting you with their data is the only way to go.

This page is powered by Blogger. Isn't yours?