Verifying all the parts of a Sharepoint application

Trying to build a 5,000 piece Lego Death Star while simultaneously balancing on a highwire would not be the easiest act to perform nor probably the smartest. However I am doing just that as I am currently working on a SP 2007 application consisting of over 30 development projects, and at the same time trying to migrate the project from TFS 2008 to 2010, perform the first Sprint of a newly conceived Agile development process, and deploy the new sprint to a test environment with no standard deployment policies in place. Although it wouldn’t be smart or easy to build the Lego Death Star on a highwire, small private companies like mine do what they need to do and nimbly turn on a dime for the customer so this SP project is being done regardless of difficulty.

When I came on to this project, we began to move the single Visual Studio solution containing +30 projects to 7 solutions each containing a handful of projects. At the same time, we worked to migrate from TFS 2008 to TFS 2010. After spending long hours of daylight and evening time with the lead developer, working thorough all of these issues and becoming more knowledgable about the system, I became the lead on one of the major SP sites named YMWCB (actual name withheld to protect the guilty.) YMWCB is that 5,000 piece Lego Death Star.

We work in a standard virtualized SP development environment where each person has their own virtual machine complete with SP, SQL Server, and Visual Studio. So after the migration of the codebase, seperating the 32 project solution into smaller solutions, and after working out all the kinks, I was tasked with setting up a few VMs each containing an installation of YMWCB  for the testers and a developer.   Installing YMWCB requires running some Powershell scripts, batch files, a command line executable, and performing some manual steps in SP. After doing all of that, the site will usually still blow up at which time Visual Studio is used to attach its debugger to the W3WP process to figure out what’s going on. After this debugging, another round of scripts is ran and more manual SP steps are performed. This process of site explosions, debugger, and scripts goes on until finally everything is in place and YMWCB runs without trouble.

I’ve been through this process so many times, that I’ve come up with an acronym for it- EXDES (EXplosion, DEbugger, Script). This acronym is said with the same pronunciation a deep southern Baptist would use to pronounce the word EXODUS. And BTW I have a license to talk about southerners as I spent 15 years in Arkansas.

So, back to my story of putting together my Lego Death Star while trying not to fall to my death: After experiencing the 3rd EXDES process, it dawned on me that I should write a utility which validates the YMWCB installation and report which pieces were missing or mis-configured. I wrote the utility as a C# command line application with a base class named BaseVerificationModule and multiple child verification module classes which would each verify some portion of the YMWCB installation.

I wrote a verification module class for each of these:

  1. Verify each subsite of the YMWCB site has the proper groups.
  2. Verify each of the above groups are given the proper permissions.
  3. Verify each YMWCB subsite’s document libraries have the appropriate event handler registered for the Added, Updated, and CheckedIn actions.
  4. Ensure the event handler registered above is the current version available in the GAC.
  5. Verify the YMWCB top level site contains the proper custom permissions and that these custom permissons have the correct attributes (e.g. OpenItems, CreateAlerts, ViewPages, etc.)
  6. Verify the top level YMWCB site contains the SP lists used to hold lookup information (such as region or area) used by the main application/web parts and ensure these lists contain the proper data.
  7. Verify each YMWCB subsite has 2 lists named ‘Draft Work’ and ‘Archived Work’ (names changed, again to protect the guilty.)
  8. Verify the YMWCB top level site has the correct lists in it as well.
  9. Perform a GAC verification on each of the 18 application assemblies. This includes:
    1. The assembly is in the GAC.
    2. There is only one version in the GAC.
    3. The assembly can be loaded from the GAC.
  10. Verifies certain lists contain certain custom added columns.
  11. Verify the web.config in the SP virtual directory. This includes:
    1. Ensure the AjaxToolkit is set to 1.0 in the <assemblies> section.
    2. Ensure the necessary custom web parts are configured int he <SafeControls> section.
    3. Verify the database connection string in the <connectionStrings> section can be used to open a connection to the database.

Finally! An 4.0 XBAP can call a Windows Authenticated WCF Service! Part 2

March 30, 2010 Leave a comment

In the first part to this subject, I wrote about the problem I ran into, the hotfix from Microsoft, the hotfix’s failure, and finally Microsoft’s promise to fix the bug in .Net 4.0. Now that 4.0 is in RC mode and proved itself to fix this problem, I’m going to take a look at the source code to see just how it did fix this problem. But before I look at the 4.0 source code, I’m going to look at the 3.5 pre-hotfix source code and then the 3.5 post hotfix source code to get a feel for what Microsoft was doing which caused my problem. By the way, I’m using RedGate’s Reflector product to look at the source code.   

.Net 3.5 Service Pack 1, Pre-Hotfix

Here is the exception stack thrown when my problem first surfaced in the 3.5 pre-hotfix era:
Pre-Hotfix Exception

 The exception came from HttpChannelFactor.GetConnectionGroupName():   

 

[SecurityTreatAsSafe, SecurityCritical]
private string GetConnectionGroupName(HttpWebRequest httpWebRequest, NetworkCredential credential, AuthenticationLevel authenticationLevel, TokenImpersonationLevel impersonationLevel, SecurityTokenContainer clientCertificateToken)
{
    if (this.credentialHashCache == null)
    {
        lock (base.ThisLock)
        {
            if (this.credentialHashCache == null)
            {
                this.credentialHashCache = new MruCache<string, string>(5);
            }
        }
    }
    string inputString = TransferModeHelper.IsRequestStreamed(this.TransferMode) ? "streamed" : string.Empty;
    if (AuthenticationSchemesHelper.IsWindowsAuth(this.AuthenticationScheme))
    {
        httpWebRequest.UnsafeAuthenticatedConnectionSharing = true;
        inputString = this.AppendWindowsAuthenticationInfo(inputString, credential, authenticationLevel, impersonationLevel);
    }
    inputString = this.OnGetConnectionGroupPrefix(httpWebRequest, clientCertificateToken) + inputString;
    string str3 = null;
    if (!string.IsNullOrEmpty(inputString))
    {
        lock (this.credentialHashCache)
        {
            if (!this.credentialHashCache.TryGetValue(inputString, out str3))
            {
                byte[] bytes = new UTF8Encoding().GetBytes(inputString);
                str3 = Convert.ToBase64String(this.HashAlgorithm.ComputeHash(bytes));
                this.credentialHashCache.Add(inputString, str3);
            }
        }
    }
    return str3;
}

The GetConnectionGroupName() function is attempting to set the UnsafeAuthenticatedConnectionSharing property and according to the MSDN reference for the property, an application must have unrestricted web permission to set it. However I used the Intranet Zone permission set for my XBAP which did not have the web permission security privlege and so this is why the above exception was thrown. I did not dig into the actual source code when this problem arose last summer, I simply opened a ticket with Microsoft because I figured that I should be able to call a WCF service secured with Windows Integrated security from an XBAP. After a few days, I received feedback from Microsoft that they had released a hotfix.   

.Net 3.5 Service Pack 1, Post-Hotfix

Here is how Microsoft fixed the above problem in the hotfix. The below code is the same HttpChannelFactory.GetConnectionGroupName() function shown above except that it has the hotfix changes in it:    


[SecurityCritical, SecurityTreatAsSafe]
private string GetConnectionGroupName(HttpWebRequest httpWebRequest, NetworkCredential credential, AuthenticationLevel authenticationLevel, TokenImpersonationLevel impersonationLevel, SecurityTokenContainer clientCertificateToken)
{
    if (this.credentialHashCache == null)
    {
        lock (base.ThisLock)
        {
            if (this.credentialHashCache == null)
            {
                this.credentialHashCache = new MruCache<string, string>(5);
            }
        }
    }
    string inputString = TransferModeHelper.IsRequestStreamed(this.TransferMode) ? "streamed" : string.Empty;
    if (AuthenticationSchemesHelper.IsWindowsAuth(this.AuthenticationScheme))
    {
        if (!httpWebRequestWebPermissionDenied)
        {
            try
            {
                httpWebRequest.UnsafeAuthenticatedConnectionSharing = true;
            }
            catch (SecurityException)
            {
                httpWebRequestWebPermissionDenied = true;
            }
        }
        inputString = this.AppendWindowsAuthenticationInfo(inputString, credential, authenticationLevel, impersonationLevel);
    }
    inputString = this.OnGetConnectionGroupPrefix(httpWebRequest, clientCertificateToken) + inputString;
    string str3 = null;
    if (!string.IsNullOrEmpty(inputString))
    {
        lock (this.credentialHashCache)
        {
            if (!this.credentialHashCache.TryGetValue(inputString, out str3))
            {
                byte[] bytes = new UTF8Encoding().GetBytes(inputString);
                str3 = Convert.ToBase64String(this.HashAlgorithm.ComputeHash(bytes));
                this.credentialHashCache.Add(inputString, str3);
            }
        }
    }
    return str3;
}

The httpWebRequestWebPermissionDenied flag is checked to guard against setting the UnsafeAuthenticatedConnectionSharing property. So if this flag is set, the code will not try to set the connection sharing property. However, where is the httpWebRequestWebPermissionDenied flag set? It is set to true if an exception is thrown in the above code on line 25 and it is initialized to false in the constructor but the only other place it is set is in the OnOpen() function:    


protected override void OnOpen(TimeSpan timeout)
{
    if (this.IsSecurityTokenManagerRequired())
    {
        this.InitializeSecurityTokenManager();
    }
    if (this.AllowCookies)
    {
        this.cookieContainer = new CookieContainer();
    }
    if (!httpWebRequestWebPermissionDenied && (HttpWebRequest.DefaultMaximumErrorResponseLength != -1))
    {
        int num;
        if (this.MaxBufferSize >= 0x7ffffbff)
        {
            num = -1;
        }
        else
        {
            num = this.MaxBufferSize / 0x400;
            if ((num * 0x400) < this.MaxBufferSize)
            {
                num++;
            }
        }
        if ((num == -1) || (num > HttpWebRequest.DefaultMaximumErrorResponseLength))
        {
            try
            {
                HttpWebRequest.DefaultMaximumErrorResponseLength = num;
            }
            catch (SecurityException exception)
            {
                httpWebRequestWebPermissionDenied = true;
                if (DiagnosticUtility.ShouldTraceWarning)
                {
                    DiagnosticUtility.ExceptionUtility.TraceHandledException(exception, TraceEventType.Warning);
                }
            }
        }
    }
}

 

This function uses pretty much the same logic to set the httpWebRequestWebPermissionDenied flag. If a SecurityException is thrown, then the flag is set to true. That seems like a quick and dirty way to set the permission flag’s value and I guess it works but it seems to me that it should be set during class initialization by reading the XBAP’s associated manifest file to see if that permission is in the XBAP’s permission set. I looked at the .Net 4.0 code to see if the flag was initialized in some way but found that it uses the same logic to figure out if the XBAP doesn’t have web permission. Strange, I thought there’d be a more elegant way.    

However ugly the above hotfix is, it did get the code past that point and on down the callstack. But farther down in the callstack another exception was thrown:    

Post-Hotfix Exception

This next exception was thrown because SecurityUtils.AppendWindowsAuthenticationInfo() called WindowsIdentity.GetCurrent():    


[SecurityCritical]
internal static string AppendWindowsAuthenticationInfo(string inputString, NetworkCredential credential, AuthenticationLevel authenticationLevel, TokenImpersonationLevel impersonationLevel)
{
    if (IsDefaultNetworkCredential(credential))
    {
        using (WindowsIdentity identity = WindowsIdentity.GetCurrent())
        {
            SecurityIdentifier user = identity.User;
            return (inputString + "\0" + user.Value + "\0" + AuthenticationLevelHelper.ToString(authenticationLevel) + "\0" + TokenImpersonationLevelHelper.ToString(impersonationLevel));
        }
    }
    return (inputString + "\0" + NetworkCredentialHelper.UnsafeGetDomain(credential) + "\0" + NetworkCredentialHelper.UnsafeGetUsername(credential) + "\0" + NetworkCredentialHelper.UnsafeGetPassword(credential) + "\0" + AuthenticationLevelHelper.ToString(authenticationLevel) + "\0" + TokenImpersonationLevelHelper.ToString(impersonationLevel));
}

However, in order to call WindowsIdentity.GetCurrent, the app must have SecurityPermissionFlag.ControlPrincipal as the WindowsIdentity.GetCurrent() method demands it:    


[SecurityPermission(SecurityAction.Demand, Flags=SecurityPermissionFlag.ControlPrincipal)]
public static WindowsIdentity GetCurrent()
{
    return GetCurrentInternal(TokenAccessLevels.MaximumAllowed, false);
}

Since my clickonce XBAP did not have this security, this Demand caused another exception.    

.Net 4.0

The .Net 4.0 code was the same as the hotfix code for dealing with the UnsafeAuthenticatedConnectionSharing property problem. However it handled the WindowsIdentity.GetCurrent() somewhat differently. Here is the SecurityUtils.AppendWindowsAuthenticationInfo() function in .Net 4.0:  

</pre>
 

[SecurityCritical]
internal static string AppendWindowsAuthenticationInfo(string inputString, NetworkCredential credential, AuthenticationLevel authenticationLevel, TokenImpersonationLevel impersonationLevel)
{
    if (IsDefaultNetworkCredential(credential))
    {
        string str = UnsafeGetCurrentUserSidAsString();
        return (inputString + "\0" + str + "\0" + AuthenticationLevelHelper.ToString(authenticationLevel) + "\0" + TokenImpersonationLevelHelper.ToString(impersonationLevel));
    }
    return (inputString + "\0" + NetworkCredentialHelper.UnsafeGetDomain(credential) + "\0" + NetworkCredentialHelper.UnsafeGetUsername(credential) + "\0" + NetworkCredentialHelper.UnsafeGetPassword(credential) + "\0" + AuthenticationLevelHelper.ToString(authenticationLevel) + "\0" + TokenImpersonationLevelHelper.ToString(impersonationLevel));
}

 

The logic for this version of SecurityUtils.AppendWindowsAuthenticationInfo() is about the same as the logic in .Net 3.5 except for the call to the new member UnsafeGetCurrentUserSidAsString() on line 11. The code to this new member function is shown below:

[SecurityCritical, SecurityPermission(SecurityAction.Assert, Flags=SecurityPermissionFlag.ControlPrincipal)]
private static string UnsafeGetCurrentUserSidAsString()
{
    using (WindowsIdentity identity = WindowsIdentity.GetCurrent())
    {
        return identity.User.Value;
    }
}

On line 1 of the above code there is an Assert for the ControlPrincipal flag, the same flag which WindowsIdentity.GetCurrent() demands. This Assert will satisfy the stack walk caused by the Demand and no exception will be thrown. So that is how Microsoft fixed this bug in .Net 4.0. 

 

Finally! An 4.0 XBAP can call a Windows Authenticated WCF Service! Part 1

March 16, 2010 Leave a comment

The Problem

Last year, I was on a project where I designed a solution with an XBAP and an IIS hosted WCF data service. The problem came out because the WCF service was secured with Windows integrated authentication and the XBAP, being a partial trust application, did not have the appropriate privileges needed to call the necessary CLR methods needed to package and issue a call to the secured WCF service. 

The first hint of the problem came out when I got a security exception from calling the WCF service from inside the XBAP. The security exception was because the WCF service proxy was trying to set the HttpWebRequest.UnsafeAuthenticatedConnectionSharing property. However, unrestricted web permission is required to set that property and the XBAP, which was running with the Intranet zone privleleges, did not have that permission. Here is the stack trace of the exception:

I opened a ticket with Microsoft concerning this problem and after a few days, received hotfix 959546 which was supposed to address this problem:

This hotfix did address the problem and allowed the code to proceed past the point of setting the HttpWebRequest.UnsafeAuthenticatedConnectionSharing property but a new exception was thrown higher in the callstack:

It took quite a bit of time after I first opened the ticket, worked throught he hotfix, and before Microsoft was able to clearly address this issue and by this time in my ticket lifecycle, I was working with an escalation engineer. After reporting the above problem to him, I received the below email email in which he indicating that this bug was to be fixed in .Net 4.0:

So at this point, I’m left with a problem: I can’t use the WCF service proxy to call a secured WCF service so what should I do? Should I re-implement the WCF data service as a 2.0 web service? ~~SHIVER!!~~. Grasping at straws, I decided to try using a 2.0 web service proxy to call my secured WCF data service and see what would happen. What happened was that it worked! So I released this project as an XBAP, a secure WCF data service, and a 2.0 web service proxy inside the XBAP to call the secure data service.

And that brings us to today. As you can see from the dates in the email chain above, this all happened last summer and today the .Net framework release 4.0 is in RC mode so I decided to see if this problem was fixed in 4.0. To try out this fix, I setup two quick hyper-v VMs:

  1. Win7 + Visual Studio 2010 RC
  2. 2008 R2 + IIS 7.5

To model the environment of last year’s project, I wrote a quick WCF service and an XBAP to call the service. I ran the XBAP and sure enough, everything worked just fine in 4.0, the XBAP was able to call the WCF service which was being hosted on IIS 7.5 using Windows Integrated Authentication. Just for kicks, I regressed the build’s target framework back to .Net 3.5 and ran it again just to see what would happen. What happened was I got the same exception I did last summer, so that proved to me there were no shenanigans going and that nogthing had crept in to my problem domain (you never know after a year long wait) as the outcome of my proofs were what I expected.

After going through this test with 4.0, I became curious about how exactly Microsoft fixed this problem. To satisfy this curiosity, I’ve decided to write a second part of this post and take a look at the version 3.5 CLR code using Reflector to see what exactly the code looks like that was causing this problem. And then I’ll take look at the 4.0 CLR code to see how the problem was fixed.

 

 

Why SvcUtil creates duplicate named classes when generating the proxy – Part 2

February 2, 2010 Leave a comment

Part 1 of this post is here.

I wanted to see what the SOAP body looked like between these two UploadDetails classes. SvcUtil generated one UploadDetails class in the global namespace and the other class in a namespace which I called ‘Bubba’ and both had to be serialized to XML for the SOAP body, depending on which one I used in the generated proxy client call. I was able to get the generated SOAP body of the message by using Fiddler. The global namespace UploadDetails XML is shown below with lines 15-52 removed:

Global Namespace UploadDetails

The Bubba namespace UploadDetails XML is shown below with lines 14-41 removed:

Bubba Namespace UploadDetails

So how was it that I was able to choose which UploadDetails class I wanted to send? To explain that, I’ll explain the steps I went through at the beginning of this odyssey.

When I used Visual Studio’s ‘Add Service Reference’ to generate a proxy for this partner’s WSDL, the generated proxy put both UploadDetails classes in the global namespace. This resulted in a name collision compile time error so I decided to use the command line SvcUtil which did seperate the UploadDetails classes between 2 namespaces. One UploadDetails class was placed in the global namespace and the other in our partner’s namespace which I’ll refer to as Partner.A.B.C.D. SvcUtil generated the proxy client to accept the global namespace UploadDetails which left the Partner.A.B.C.D.UploadDetails class just hanging around with nothing referencing it. But I found out that if I ran SvcUtil with the /namespace option and told it to assign the classes in the partner’s namespace Partner.A.B.C.D to a namespace I called ‘Bubba’, then the SvcUtil generated the proxy client to accept the Bubba namespace UploadDetails. And that is how I was able to run the proxy client with both namespace UploadDetails classes to see how they were serialized to XML. Below is a screen show which summarizes the important points of this. It shows the class diagrams for each UploadDetails and the key differences in the serialized XML for both classes:

Serialization Differences

So basically, an object[] array is serialized into <data> elements and a List<object> is serialized into <anyType> elements. Which is strange because I thought it’d be the other way around but I double checked it and those are the correct elements. I know that WCF uses the DataContractXMLSerializer by default. I could have used the /serializer parameter to SvcUtil to have it use the XmlSerializer which would have given me more control over how exactly the object was serialized to XML but I stopped at this point.

In my first post I indicated that I was getting a timeout error when calling this partner’s web service. Lo and behold, the partner sent me an updated WSDL which fixed that problem. Apparently their first WSDL was in error.

After getting the new WSDL and being able to successfully call the web service, I checked to see which set of XML would be accepted and found that they wanted the XML generated from the global namespace UploadDetails. I got an exception when I sent the other UploadDetails. Since at this point, I could call the partner’s web service and since I had many more things to work on, I chose to stop pursuing why SvcUtil generated two classes.

In summary, SvcUtil generated 2 classes with the same name: UploadDetails. I was able to direct the namespace each class was generated into by using the /namespace parameter which also drove which class the generated proxy client would accept. I fed both namespace classes into the web service call to see which one the web service would accept and found that it accepted the global namespace UploadDetails. Their web service did not accept the XML generated from serializing the Bubba (or the Partner.A.B.C.D) namespace UploadDetails.

Categories: WCF Development Tags: , ,

Why SvcUtil creates duplicate named classes when generating the proxy – Part 1

January 28, 2010 Leave a comment

I appended ‘Part 1′ to this post when I started it because I know good and well that I’m not going to figure this out in one post.

On my current project, I need to communicate with a web service provided by a new partner company. I do not have access to the code behind this web service (and in a perfect world, I wouldn’t need to) and after my initial conversations with them, I got the feeling that they wouldn’t be very amenable to assisting me. So, here I am with a URL to their WSDL and SvcUtil which creates duplicate class names during proxy generation. I should say also that the web service times out when I call it but that is a seperate issue that I’m tracking down with this partner.

One duplicate class is a class named UploadDetails. When I added a service reference to my Visual Studio 2008 project, it generated duplicate class names in the Reference.cs file for all +20 data classes. The specifc class that I’m working with is called UploadDetails but I could work with any of their +20 classes to try and figure this out, I simply chose the UploadDetails class.

The duplicately named classes are all generated in the same namespace when I use the ‘Add Service Reference’ in Visual Studio. I’m not speaking of XML namespaces, I’m speaking of  the C# namespaces. However when I generate the proxy by using SvcUtil from the command line, it creates one UploadDetails class in the global namespace and one UploadDetails class in the partner’s namespace (which I will refer to as the Bubba namespace) so there are no name collision problems. Since I have more control over the options in the SvcUtil command line, I am going to use it to figure this out.

Here are the class diagrams of the 2 UploadDetails classes. One class was generated in the default namespace and one class in the Bubba namespace:

UploadDetails Class Diagrams

 Therer are several things to note about the similarities and differences in these 2 classes:

  1. The Bubba.UploadDetails class implements IExtensibleDataObject and the other class does not.
  2. The Fields and Properties of the 2 classes are the same except for the ones used to implement IExtensibleDataObject.
  3. The Delete, Insert, and Update properties are of different data types in the 2 classes.
    1. The global namespace UploadDetails class properties are an object array.
    2. The Bubba.UploadDetails class properties are of types which inherit from List<object>. These types are classes named Delete, Insert, and Update and are shown at the bottom of the above image.

In my next post I’m going to see what the XML SOAP body looks like when I use each of these 2 UploadDetails classes. I think they’ll both serialize to the same XML but I want to see for myself.

Categories: WCF Development Tags: , ,

Does Kerberos authentication affect SQL Server connection pooling? Part 2

January 23, 2010 Leave a comment

The Problem

In my first post on this subject, I wrote about a problem that occurs in the ADO.Net connection pool when a .Net application authenticates its callers to SQL Server using their Kerberos ticket. I am now going to present a solution to this problem and an actual implementation of the solution.

Most applications today are n-tiered applications with a data tier which accesses the database on behalf of the frontend user. The n-tiered application uses Kerberos so that the frontend user’s identity is passed through the application’s tiers to the data tier and used to authenticate the user to the database.

Since the data tier’s database connection is established under the credentials of each frontend user, the application receives two important benefits: data auditing accuracy and a tighter more focused security model. SQL Server audit trails are able to record data operations and tie them to each individual user. In addition, each user’s operations are performed under their specific database permission set.

 

The problem with this pattern is in the connection pool. The ADO.Net connection pools are indexed by the user and by the database so in the above example, there would be 3 connection pools. One pool for User1, one pool for User2, and one pool for User3 which drastically affects the application’s scalability since a database connection would have to be created for each user.

The Solution

One solution to this problem is to use Microsoft’s Trusted Subsystem pattern. In this pattern, the data tier would authenticate to the database using its own service account and operate against the database on behalf of each frontend user. By using this pattern, the connection pooling problem is solved because the data tier’s service account is the only account which authenticates to the database. However, there is at least one drawback to using the data tier’s service account to authenticate to the database:  The SQL Server auditing module can no longer tie the actions it records to the individual frontend users. In addition, the data tier’s service account would need enough permissions in the database to accommodate all users from the least privileged user to the most privileged user.

One way around the audit problem is to not use the SQL Server auditing module and replace it with one at the data tier level. Since the data tier gets the Kerberos ticket, it knows who is requesting the data and can therefore write the audit trail entries itself. But why re-invent the wheel when SQL Server has a tried and true enterprise level auditing system?

In order to solve both the audit trail and the permissions problem, the Trusted Subsystem approach should be used and the data tier’s service account should be granted impersonate privileges on each frontend user account. Each time the service account accesses the database for a user, it should issue an Execute As Login=xxx statement, perform the necessary operations, and then issue a Revert statement. This way, the SQL Server auditing module can accurately record operations per user and the database operations the service account performs are constrained by each user’s permissions.

 

Solution Implementation

In order to prove this solution in a way as close to the real world as possible, I setup an environment with the following virtual machines in a development domain I named POPEDEV:

Host Operating System Role
DC1 2008 R2 x64 Domain controller – POPEDEV domain
WEB1 2008 R2 x64 IIS
WEB2 2008 R2 x64 IIS
WEB3 2008 R2 x64 IIS
SQL1 2008 R2 x64 SQL Server 2008 (running under Network Service)
WORK1 Win7 x86 Development and user workstation

 

My test involved a web service to return a set of products from a SQL Server Products table. Each of the web services had a service operation named GetProducts(). I created three web services: Hop1, Hop2, and BDE. The client called Hop1.GetProducts() which called Hop2.GetProducts() which called BDE.GetProducts(). I created three web services and therefore three hops to exercise the Kerberos ticket delegation process. The Kerberos ticket which was created on the workstation would be passed to Hop1 and then to Hop2 and finally to BDE.

I deployed the following web services to each web server:

Host Web Service Application Pool Identity Role
WEB1 Hop1 Network Service Proxy web service to call Hop2.
WEB2 Hop2 Network Service Proxy web service to call the BDE.
WEB3 BDE POPEDEV\BDE Business Data Engine. This is the data tier.

 

The client application on WORK1 would call the Hop1 web service on WEB1 which would do nothing except call the Hop2 web service on WEB2. The Hop2 web service would do nothing except call the BDE web service on WEB3. The BDE web service would be my data tier and would connect to the database as a trusted subsystem to return the requested data to Hop2 which would return the data to Hop1 which would return the data to the client on WORK1.

I wrote my web services in WCF and configured them to use the WsHttpBinding with message security and Kerberos authentication.

 

For my testing, I created 5 domain users: USER1 – USER5 and wrote the client application to be able to impersonate one of those 5 domain users before calling Hop1. This way, I could simulate any of the 5 users being the frontend user. In addition, I created the domain user POPEDEV\BDE which would be the data tier’s service account user.

Constrained Delegation

When the Kerberos ticket is passed from one the Hop1 web service to the Hop2 web service, Hop1 is basically delegating to Hop2 the user’s request for data. By the same token, when Hop2 called the BDE and passed it the Kerberos service ticket, it was delegating to the BDE web service. However, the ability to delegate from one machine/service default ability in Active Directory.  Each machine/service must be given explicit delegation permissions.

This is called constrained delegation and is a security feature of Active Directory. The opposite would be open delegation where, when a process received a user’s identity in a Kerberos ticket, could call any other service local or remote on that user’s behalf. This could present a whole host of security problems if an attacker were able to launch a service within the organization and induce users to call it.

So I needed to setup the delegation settings on WEB1, WEB2, and WEB3 to be able to delegate to WEB2, WEB3, and SQL Server service as shown below:

 

In the screen shot above, I gave WEB1 permission to delegate to the service HTTP/web2.popedev.com because the HOP2 web service on WEB2 was running under the Network Service account.


Delegating to the BDE Web Service

The last set of delegation permissions was for the Hop2 web service on WEB2 to be able to delegate to the BDE web service on WEB3. This wasn’t quite as straight forward as permitting WEB1 to delegate to WEB2 because the BDE web service was running under the service account POPEDEV\BDE.

First I added the service principal name HTTP/web3.popedev.com to the POPEDEV\BDE account:

Next I enabled WEB2 to delegate to that same service name:

 

Running the WhoAmI Test

The first test I wanted to perform was to verify that the Kerberos ticket was being passed between the web servers and would make it all the way to the BDE web service.

I created a method named WhoAmI() in each web service which would return the current thread, windows, and other identities. It returned 4 identities as follows:

  • ServiceSecurityContext.Current.PrimaryIdentity.Name
  • ServiceSecurityContext.Current.WindowsIdentity.Name
  • WindowsIdentity.GetCurrent().Name
  • Thread.CurrentPrincipal.Identity.Name

If the Kerberos service ticket was being passed from web service to web service, then these four methods should return the name of the frontend user (e.g. POPEDEV\User2). Except that the BDE web service, which was not impersonating its users to the SQL Server would return POPEDEV\BDE for the Windows identity because POPEDEV\BDE was its service account identity.

In addition, I ran NetMon on WEB1 and WEB2 to watch for Kerberos packets as another verification step.

I ran my console tester and the opening menu asked which operation I wanted to run. I chose option 0, the WhoAmI operation.

After choosing the WhoAmI operation, the console tester asked which user to impersonate. I chose POPEDEV\User4.

After choosing which user to impersonate, the console tester called the WhoAmI method on Hop1. In the screen shot above, you can see that the Hop1 and Hop2 web services see the user as POPEDEV\User4. The BDE web service shows the windows identity as POPEDEV\BDE and the thread identity as POPEDEV\User4.

The next screen shot shows the NetMon captures with the packet filter set to watch for Kerberos frames. As you can see, WEB1 requested http/WEB2.POPEDEV.COM and WEB2 requested http/WEB3.POPEDEV.COM which is exactly what I would have expected.

WEB1 NetMon trace:

WEB2 NetMon trace:

After seeing the results of the WhoAmI test and the captured frames in NetMon, I was confident that Kerberos was being used to authenticate all the way to the BDE web service.

Running The Full Test

For the full test, I wanted to prove that any user could request data from the data tier and the audit trail would record the select operation under that user and I wanted to prove that the data tier was using a single connection pool.

The data which the BDE web service returned was a set of products in a table I named Products. Here is a sample of what the BDE needed to run in order to return the products correctly:

Execute as Login= ‘Popedev\User3′

select * from Product

revert

The first line ‘Execute as…’ enabled the BDE to run the select statement underneath the identity of the frontend user whose identity came across in the Kerberos ticket. Remember that the BDE connected as its service account so that a single connection pool would be used. In addition, the BDE web service needed to run the SQL underneath the frontend user’s identity so that the audit trail would be accurate and that the SQL operations would be run under the frontend user’s security.

The last line ‘Revert’ reverted the connection back to the BDE web service’s service account identity.

Here is a shot of the current database connections before I ran the test. There are 2 connections to the master database and 1 connection to the tempdb database. After running the test, I should see a new connection to my test database which I named kerbtest.

I ran the console tester and told it I wanted operation 1 – GetProducts and to impersonate POPEDEV\User3.

The console tester next asked whether I wanted to use LINQ or ADO.Net to get the products. I chose LINQ and it returned 7 products.

The SQL Server activity monitor now shows process 54 which was my BDE web service’s connection. Notice that process 54 is under the BDE web service’s service account POPEDEV\BDE and not POPEDEV\User3 who is the frontend user in my test.

This next screen shot shows the audit trail for session 54. Notice the SESSION_SERVER_PRINCIPAL_NAME is POPEDEV\BDE because that is who opened connection 54. The DATABASE_PRINCIPAL_NAME is POPEDEV\User3 because that is who the console tester was running under.

I then ran my test again but this time I told the tester to impersonate POPEDEV\User5 and to use ADO.Net instead of LINQ:

Here is a screen shot of the SQL Server activity monitor. Notice that there is still one session – 54:

The next screen shot shows the audit trail. The 2 entries reflect session id 54 as excepted and the DATABASE_PRINCIPAL_NAME reflects POPEDEV\User5 and POPEDEV\User3. In addition, the statement is different because I used LINQ the first time and ADO.Net the second time.

So what exactly *is* the difference between the logical architecture and the physical?

December 23, 2009 Leave a comment
In IT, we’ve all read a technical document and have come across some diagram labeled ‘Logical Architecture’:
Some random logical diagram I pulled off of a Google search
And then later on in the same document, we have come across a diagram labeled ‘Physical Architecture’:

Some random physical diagram I pulled off of a Google search

What exactly is the difference between the logical and the physical design? There are a plethora of logical and physical architecture diagrams throughout the IT world, so many in fact that we expect to find them in our literature. Just as we expect to see a Wal-Mart, a Cracker Barrell, and a Baptist Church at every exit on Interstate 40, we expect to find logical and phsyical designs in most IT documents. I don’t know when the idea of logical vs. physical first started, maybe it started with the a team of peasants in the 12th century who were tasked with building a moat around some castle but I was sitting and reading yet another Systems Design Document for yet another project here at work, when the answer to this question occurred to me.
The logical design is what we’re supposed to do and the physical design is what we’re doing. I plan to look at every logical and physical design I see from now on with this revelation.
Categories: Software Development

Does Kerberos authentication affect SQL Server connection pooling?

December 17, 2009 Leave a comment

The Question Submitted To Me

A manager at my company asked me this question. I told him that I didn’t know for sure but I didn’t *think* Kerberos authentication would affect connection pooling. I put on my thinking cap at this point and told him that the connection pool in an ADO.Net application (he was concerned with only .Net apps) was keyed on the connection string so, I told him, if the connection string didn’t change between users then a single connection pool would be used for all users.   

I went on to tell this manager that with Kerberos authenticated users, the connection string would be a trusted connection string without specific credentials for each user. Since the connection string would be the same trusted connection string for all users, there would be one connection pool for all of the Kerberos authenticated users.   

After telling him the above information, he asked me to put together a test to document my findings so I set about to create a test which could prove this one way or the other. However, at this point I was pretty sure I was right because it *seemed* right given everything I knew.   

Boy, was I in for an eye opener.   

Overview Of My Test

The test I came up with was to write a test client which connected to a web service on another machine. This web service would then connect to another web service on a second machine. This second web service would then connect to a SQL Server database on a third machine:   

   

I setup these machines in my trusty hyper-v environment I used for the Sharepoint 2010 farm.   

To validate my theory, I decided to create 5 domain users: user1 – user5 and run each of them through my test one by one. I figured at the end of the test, the SQL Server would have either 5 seperate connections for each user or it would have one session which was shared by each user in the connection pool.   

At this point, I had been doing a lot of reading on Kerberos and I ran across a document from Microsoft which flat out told me that Kerberos authentication would defeat the connection pool. However I still thought I was right and decided that I’d ignore that document, maybe they were taking about an issue that didn’t apply to this environment. Besides, I was in the midde of setting up this test and I wanted to finish it.   

Running The Test

My test client presented a menu and gave the user an option of calling one of two operations:

WhoAmI()  – Returns the identity of the caller. Returns both the thread identity and current windows identity.

Here is a screen shot of the WhoAmI() test:

WhoAmI() Test Results

ExecuteSQLServerDBCommand()  – Accepts 2 paramaters: Database Connection String and a SQL string. This method connects using  the database connection string, executes the SQL, and returns the SPID of the current DB connection and the number of rows affected by the executed SQL.
Here is a screen shot of the ExecuteSQLServerDBCommand() test:

ExecuteSQLServerDBCommand() Test Resuts

 

SQL Server Configuration

I’ll start with the SQL Server endpoint of my test because its going to be easier to explain each endpoint by starting at the end and working my way back to the test client.   

My SQL Server was a SQL Server 2008 instance running on Windows Server Standard 2008R2. I created a simple database named KERBTEST and gave all 5 domain users access to it:   

KERBTEST SQL Server Database

WCF Web Service 2

I wrote this web service as a WCF web service that was configured for Kerberos authentication. This web service was the final endpoint before the SQL Server and it contains the two operations WhoAmI() and ExecuteSQLServerDBCommand(). A partial block of the code in the WCF Web Service 2 is shown below:


        #region IService2 Members

        [OperationBehavior(Impersonation = ImpersonationOption.Required)]
        string IService2.WhoAmI()
        {
            string result = String.Format("{3}System.Security.Principal.WindowsIdentity.GetCurrent().Name = {1}{0}System.Threading.Thread.CurrentPrincipal.Identity.Name = {2}{0}"
                , Environment.NewLine
                , System.Security.Principal.WindowsIdentity.GetCurrent().Name
                , System.Threading.Thread.CurrentPrincipal.Identity.Name
                , GetDecoratedFunctionName("WhoAmI"));

            return result;
        }

        [OperationBehavior(Impersonation = ImpersonationOption.Required)]
        string IService2.ExecuteSQLServerDBCommand(string dbcs, string commandSQL)
        {
            StringBuilder result = new StringBuilder();

            result.Append(GetDecoratedFunctionName("ExecuteDBCommand"));
            try
            {
                using (SqlConnection conn = new SqlConnection(dbcs))
                {
                    conn.Open();

                    SqlCommand cmd = new SqlCommand("select @@SPID", conn);
                    string spid = cmd.ExecuteScalar().ToString();

                    cmd = new SqlCommand(commandSQL, conn);
                    int rowsEffected = cmd.ExecuteNonQuery();
                    result.Append(String.Format("SQL Server connection established:{0}\tSPID = {1}{0}\tCommand executed. Rows effected = {2}{0}"
                        , Environment.NewLine
                        , spid
                        , rowsEffected));

                    conn.Close();
                }

            }
            catch (Exception ex)
            {
                result.Append(String.Format("{0}{1}", Environment.NewLine, ex.ToString()));
            }

            return result.ToString();
        }

        #endregion

WCF Web Service 1

I wrote this web service as a WCF web service also that was configured for Kerberos authentication. This web service had the same operations as Web Service 2 (WhoAmI() and ExecuteSQLServerDBCommand()).

The WhoAmI() operation did the same as the WhoAmI() operation in the second web service and then called WhoAmI() on the second web service.

The ExecuteSQLServerDBCommand() operation did nothing in this web service except call the ExecuteSQLServerDBCommand() operatin in the second web service.

A partial block of the code in the WCF Web Service 1 is shown below: 

 

        #region IService1 Members 

        [OperationBehavior(Impersonation = ImpersonationOption.Required)]
        string IService1.WhoAmI()
        {
            ServiceReference2.Service2Client svc = new WcfService1.ServiceReference2.Service2Client();
            svc.ClientCredentials.Windows.AllowedImpersonationLevel = TokenImpersonationLevel.Impersonation; 

            string result = String.Format("{3}System.Security.Principal.WindowsIdentity.GetCurrent().Name = {1}{0}System.Threading.Thread.CurrentPrincipal.Identity.Name = {2}{0}{4}"
                , Environment.NewLine
                , System.Security.Principal.WindowsIdentity.GetCurrent().Name
                , System.Threading.Thread.CurrentPrincipal.Identity.Name
                , GetDecoratedFunctionName("WhoAmI")
                , svc.WhoAmI()
                ); 

            return result;
        } 

        [OperationBehavior(Impersonation = ImpersonationOption.Required)]
        string IService1.ExecuteSQLServerDBCommand(string dbcs, string commandSQL)
        {
            StringBuilder result = new StringBuilder();
            result.Append(GetDecoratedFunctionName("ExecuteSQLServerDBCommand"));
            try
            {
                ServiceReference2.Service2Client svc = new WcfService1.ServiceReference2.Service2Client();
                svc.ClientCredentials.Windows.AllowedImpersonationLevel = TokenImpersonationLevel.Impersonation;
                result.Append(svc.ExecuteSQLServerDBCommand(dbcs, commandSQL));
            }
            catch (Exception ex)
            {
                result.Append(String.Format("{0}{1}", Environment.NewLine, ex.ToString()));
            } 

            return result.ToString();
        } 

        #endregion 

Results

I ran my test and executed 2 ExecuteSQLServerDBCommand() operations. One for user1 and one for user2. Remember that ExecuteSQLServerDBCommand() would return the SPID of the current connection so if I was right and Kerberos authentication did not affect the connection pool, then the same SPID would be returned for each user in my test.

Here is a screen shot of that test:

Full Test Results

The test proved that Kerberos authentication affected the ADO.Net connection pool. I have a friend who often uses the single word phrase “sigh…” to appropriately sum up most situations. So that’s what I said at this point. Sigh…

The reason Kerberos authentication affects the connection pool is that ADO.Net keys the connection pool on not just the connection string, but also the user identity. So if 100 users connect to the database with a secure connection string, then 100 pools will get created, each with a single connection string.
I told the manager who tasked me with settling this issue my findings. He and I then presented them to a VP who promptly told us that in Oracle, this would not happen. I didn’t believe the VP but didn’t tell him. After all, I was zero and one in my competitions to prove my theories over someone more knowledgable.
I wont go into my research on the Oracle issue, but I did quite a bit of research and found an answer. Unfortunately after finding the answer, I moved from zero and one to zero and two. Oracle has the ability to allow User2 to connect through User1′s connection and perform operations on it so there is no need for 2 seperate connections.

Setting up a Sharepoint 2010 Hyper-V Lab

December 1, 2009 Leave a comment

Recently Microsoft released Sharepoint 2010 beta and quite a few other pieces of software: Visual Studio 2010 beta, 2008 R2 RTM, and Windows 7 RTM which made it possible to deploy an SP2010 farm and do some development against it with the latest server and application technology. So the time is right to setup Sharepoint 2010 and see what it’s got. My plan was to setup a Sharepoint 2010 farm so I could get down into the details of a farm deployment and see what was new there and I wanted to do some development against the farm with the new Visual Studio and Win7 to see what changed or had been introduced there.

 In order to setup this farm and development workstation, I figured I’d setup a hyper-v lab. In order to do this, I knew I’d need two machines: a hyper-v server and a Vista workstation which I would use to continue the project work I am tasked with at my company. I had a Dell Precision workstation with a decent amount of memory, disk space, and CPU horsepower so I decided use it as my hyper-v server, and I had a Vista laptop for my daily project work. 

2008 R2 Core Or Full?

My first question was: should I install the core or the full installation of 2008 R2 for my hyper-v host? At first I chose core because I didn’t want to waste any of the resources on my Precision workstation which could be used for the farm or for development so I went ahead and installed core and setup my vista laptop to manage the core hyper-v installation. This all went fairly straightforward until I got the bright idea to use the System Center Virtual Machine Manager to help me manage my virtual lab. I wanted to give the SCVMM a spin because in the past I had used Virtual Server 2005 and SCVMM’s predecessor: Virtual Machine Manager and so I wanted to see what was new in virtual machine management in hyper-v.

I figured I could run the SCVMM management console on my Vista workstation and use it from there, but after downloading the SCVMM and attempting install it on my Vista workstation, I found that I could not. I found out that the management console has to be installed on server 2008 which meant that I could either deploy a guest VM for the SCVMM or I could re-install my hyper-v server as a full installation and use it for the SCVMM. I chose to re-install my hyper-v server as a full installation since that seemed like less of a waste of my Precision workstation’s physical resources.

How To Back It All Up?

 The next item I wanted to address was how to backup my guest VMs. I knew that since I would be installing beta software and working through some hotfixes and what not, that I’d need a good backp strategy. After all, the only way I knew of to learn something really well was to break it and then have to fix it and restoring from a backup made fixing things that much easier. There are 2basic ways in which a hyper-v guest VM can be backed up:

  1. Windows Server Backup (WSB)
  2. Data Protection Manager (DPM)

Checkpoints

Checkpoints are kind of a light weight backup solution so I didn’t see checkpoints as a long term backup solution, I seen them as a way to make a quick save of the VM before attempting something which may break it. In addition, my experience with VS2005 checkpoints left me with two bad experiences: you couldn’t restore a checkpoint without losing all checkpoints after the restored one and constantly checkpointing/restoring a domain member server would inevitably result in that member server’s domain association getting corrupted.

However, when using the new SCVMM checkpoints, I realized that I could not only restore to a checkpoint without losing all checkpoints later in time but that I could also fork the checkpoints. I could create child checkpoints and sibling checkpoints as well:

SCVMM Checkpoints

So checkpoints for me became a good and quick backup tool but still didn’t satisfy my need for a full backup.

Windows Server Backup (WSB)

WSB is the successor of NTBackup and can do what you’d expect of it: it backs up and restores folders and files. But it can also operate at a higher level and use an application’s Volume Snapshot Service (VSS) to backup/restore at the application level. Hyper-v has a VSS which through a registry edit, can be used by WSB.I had an external 500GB drive laying around that I was using as the SCVMM library for my ISO and other installation images so I figured I would also use it for my backup media. After I got WSB configured with the hyper-v VSS writer I got to the place in the WSB backup wizard to select the backup destination, it didn’t show my external drive:

WSB Wizard

I went hunting online to see what I could do and found some articles about backing up to an external drive with WSB  but it seemed I had to reformat the disk first. Since this external drive also hosted the VM library of ISOs and other binaries I needed for my hyper-v lab, formatting it was out of the question and so this ended my backup effort with WSB.

Data Protection Manager (DPM)

The next backup method was DPM which is a more enterprise level backup solution from Microsoft. I downloaded and installed DPM 2007 service pack 1 which meant that I had to also install SQL 2005 along with it’s necessary service packs to get it running on 2008 R2.

I didn’t have too much trouble getting DPM installed and configured, it was all pretty much what I would expect of a backup solution. However, when I got to the point where I was adding disks to the storage pool to be used for the backup destination, I got a pleasant surprise: DPM did not show me my external drive so I could add it to the storage pool:

DPM Disk Selection

At this point, I went back to the Internet to see what was going on and found out that DPM does not support backing up to external USB drives.

So at that point, I had no backup solution for my hyper-v installation. I do have plans to find another external drive and use WSB to backup to it.

Clones, Templates, and Virtual Machines In the SCVMM

My virtual Sharepoint lab will eventually consist of 7 guest VMs:

  1. Domain Controller
  2. SQL Server
  3. Index Server
  4. Web Server
  5. Web Server
  6. Development workstation
  7. Development workstation

Guest VMs

Cloning

When I began to create my guest VMs, I wanted the ability to store a base 2008 R2 server in my SCVMM library that was fully patched and ready to deploy. I envisioned being able to deploy multiple guest VMs from this one copy in the library. After installing and patching my first server guest VM, I didn’t store it directly to my library. Instead I used the ‘clone’ option on the context menu to create 4 additional servers: IDX1, SQL1, WEB1, and WEB2. I knew that this probably wasn’t right, that it couldn’t be that easy but I did it anyway and I was correct, it wasn’t right. Each of my 5 guest VM servers had the same SID which I verified by using the psgetsid utility. I think clones are useful only for backing up the machine and storing it in the SCVMM library.

Storing a VM in the SCVMM library

The next thing I tried to do was to take that first fully installed and patched server and run sysprep on it and store it into the SCVMM library. I figured that having a sysprepped machine in my library would allow me to deploy it into a running guest VM multiple times and each of those guest VMs would have a different SID. But when I went to pull the VM which I stored in the library and deploy it as a guest VM, I found 2 problems:
  1. I couldn’t rename the machine. I had it named something like ‘Base 2008 R2  x64′  so I couldn’t rename it SQL1, WEB1, etc.
  2. When deploying the VM from the library, it was not copied out of the library, it was moved. So this was a one time operation and it pretty much defeated storing a single copy in my library so I didn’t have to install and patch 5 seperate servers.

 Creating a template in the SCVMM library

My 3rd and last attempt at accomplishing my vision turned out to be the charm. I took that first server which was fully patched and would serve as my base server and told SCVMM to create a template from it. Before creating the template, I figured I needed to run sysprep on it since that seemed correct but while watching the SCVMM jobs to create the template, I noticed that one of the steps was to sysprep the machine:

Sysprepping The Template

So creating the template also sysprepped the machine. Very nice, I now had the ability to store a 2008 R2 server template in my library which I could pull and deploy mutliple times as needed.

Binding updates to a WPF View from outside of its ViewModel – part 1

November 21, 2009 Leave a comment

Application Overview

I’ve been working on an MVVM WPF app that has a view of metrics collected by some background threads.  A screen shot is shown below:

As I mentioned, I’m using the MVVM pattern for this app so I have a view, a view model, and a model layer. The model layer consists of a ThreadMetrics class which contains the metrics you see for one row of the screen shot above. The view model (ingeniously named the ThreadMetricsViewModel) contains an instance of a ThreadMetrics class as well as the necessary get accessors for the view’s binding targets. The class layout and interactions are shown below:

MVVM Class Interactions

Where this app starts to differ from the other MVVM apps I’ve written is that the updates to the view model’s data do not come from the UI user or a command handler inside the view model. Instead they start with a background thread which updates counts inside the ThreadMetrics instance within the ThreadMetricsViewModel. From there, the view binding will pick them up from Get accessors within the ThreadMetricsViewModel.

Since in this application, data is changed outside of the view model and needs to be reflected on the view, my problem became how to trigger the ThreadMetricsViewModel’s property changed events everytime the values in its associated ThreadMetrics instance were changed. There was also a problem with how often these values changed which caused the view to become unresponsive to user manipulations and I’ll cover that too. 

Triggering view model property changes from the background thread

For the first problem of triggering the property changed events, I’m using the Mediator v2 solution which allows the ThreadMetrics instance to advise the ThreadMetricsViewModel of changes to its metric counts by the background thread. Each time the ThreadMetricsViewModel handles an update notification for one of these counts, it will issue a property changed event which causes the view binding to pick up that value and update it on the view. The image below shows the interaction which solved this problem:

Triggering The ViewModel's PropertyChange Events

Triggering The ViewModel's PropertyChange Events

Keeping the view from becoming unresponsive

The next problem was that the PropertyChange events were happening so fast due to the the high rate of activity by the background threads that the UI became unresponsive. I solved this problem by only sending the PropertyChange events periodically (but often enough to keep the view continuously updated.) The image below shows the UpdateUI method which does this:
The UpdateUI Method

Summary

So that is how I handled updating the view in my MVVM app from changes made outside of the view model, but I need to say that I still don’t get a ‘good’ gut feeling from this implementation.  The ThreadMetricsViewModel doesn’t really serve any purpose other than being a go-between or a proxy to the ThreadMetrics instance which holds the actual values and the background thread class is where the actual logic takes place. It seems that there should be more logic in the ThreadMetricsViewModel, that maybe the ThreadMetrics class should go away and the background thread should reach into the ThreadMetricsViewModel instance to change values there. Or maybe that the ThreadMetrics and the background thread classes both should go away and all the thread and view model logic should occur inside the ThreadMetricsViewModel.

Causing a rift in the WPF coding space-time continuum

At the beginning of developing this app, I feared I may have caused a rift in the space-time continuum of WPF coding best practices because  I did indeed have all of the thread/view model logic and data inside the ThreadMetricsViewModel instances. However this didn’t seem right either because I knew I was mixing backend service operations with view layer operations. It seemed that mixing everything like this into only two layers: the view and view model was violating some basic law of WPF  coding best practices. I began to fear I had created an enormous rift in space and time so I refactored the logic into my current ThreadMetricsViewModel, ThreadMetrics, and background thread classes. Unfortunately it may already be too late and I’m writing this blog from another space-time realm of WPF development and do not know it.

My next post will talk about how I handled updating the aggregate metrics from all my ThreadMetrics instances. That is, the total number of Create and other operations.

Categories: WPF Development Tags: , ,
Follow

Get every new post delivered to your Inbox.