Check out my new blog at https://shibumiware.blogspot.com

Friday, August 14, 2009

Browser Download Sites – Markup Validation Service Score Roundup

I needed a quick break from the slog and I just happened to read an article about RockMelt, a forthcoming browser from the brain that brought us Netscape.  Just on a whim I ran the site through W3C’s markup validation service to see how much this group is paying attention.  There were 16 errors on the page.  Just for grins, I ran some other browser provider’s download pages through the validator.  Here are the results:

Firefox: 0 Errors

Opera: 0 Errors

SeaMonkey: 1 Error

Safari: 4 Errors

Google: 19 Errors

Internet Explorer: 75 Errors

I admit I was a little surprised at Google’s results because the site is so tidy.  Okay, out to dinner before I go back to work!

Friday, August 07, 2009

Reading Lookup Table Structures and Values for a Specific List of Lookup Tables

I am writing a fairly complicated piece of code that uses lookup tables heavily.  As I am ramping down on this part of the project, I took one more look at performance.  Originally I was reading all of the lookup tables to have the structures and values handy for processing.  The problem is the client has HUGE lookup tables and many of them, so I was a little concerned.  I occurred to me early on that I should be reading the lookup tables just for project custom fields that are backed by a lookup table.  In this case, I am only processing project custom fields so I don’t need the lookup tables associated with task or resource custom fields.

How to read lookup table structure and values for a subset of the lookup tables?  It took me a few minutes to figure it out so I thought I would share it with you.

The filtering capability in the PSI is a bit under documented.  Take a look at this as a primer.

When you create a filter, you can only specify a single table to filter rows on in the dataset, so you really can’t achieve what I am trying to achieve by creating a single filter, because I need lookup table structures and values, which reside in two separate tables.  You have to create two filters and make two separate calls to ReadLookupTablesMultiLang and then merge the resultant datasets to get the structures and the values in the same dataset.  Here is how it works:

LookupTableMultiLangDataSet tableStructures = lookupTableProxy.ReadLookupTablesMultiLang(
LookupTableFilters.LookupTableStructures(lookupTableIDs),
false);

LookupTableMultiLangDataSet tableValues = lookupTableProxy.ReadLookupTablesMultiLang(
LookupTableFilters.LookupTableValues(lookupTableIDs),
false);

tableStructures.Merge(tableValues);


return tableStructures;
 
The key here is to read the lookup tables structure and values and then merge the datasets!  Here is what LookupTableFilters.LookupTableStructures and LookupTableFilters.LookupTableValues look like:
 
public static string LookupTableValues(string[] lookupTableGuids)
{
Filter filter = new Filter();


using (LookupTableMultiLangDataSet lookupTable = new LookupTableMultiLangDataSet())
{
filter.FilterTableName = lookupTable.LookupTableValues.TableName;
filter.Fields.Add(new Filter.Field(lookupTable.LookupTableValues.LT_UIDColumn.ColumnName));
filter.Fields.Add(new Filter.Field(lookupTable.LookupTableValues.LT_STRUCT_UIDColumn.ColumnName));
filter.Fields.Add(new Filter.Field(lookupTable.LookupTableValues.LT_VALUE_TEXTColumn.ColumnName));
filter.Fields.Add(new Filter.Field(lookupTable.LookupTableValues.LT_VALUE_DATEColumn.ColumnName));
filter.Fields.Add(new Filter.Field(lookupTable.LookupTableValues.LT_VALUE_DURColumn.ColumnName));
filter.Fields.Add(new Filter.Field(lookupTable.LookupTableValues.LT_VALUE_NUMColumn.ColumnName));


Filter.IOperator[] operators = new Filter.IOperator[lookupTableGuids.Length];


for (int i = 0; i < lookupTableGuids.Length; i++)
{
string lookupTableGuid = lookupTableGuids[i];

operators[i] = new Filter.FieldOperator(Filter.FieldOperationType.Equal,
lookupTable.LookupTableValues.LT_UIDColumn.ColumnName,
lookupTableGuid);
}


Filter.LogicalOperator logicalOperator = new Filter.LogicalOperator(Filter.LogicalOperationType.Or, operators);

filter.Criteria = logicalOperator;
string filterString = filter.GetXml();
return filterString;
}
}

public static string LookupTableStructures(string[] lookupTableGuids)
{
Filter filter = new Filter();


using (LookupTableMultiLangDataSet lookupTable = new LookupTableMultiLangDataSet())
{
filter.FilterTableName = lookupTable.LookupTableStructures.TableName;
filter.Fields.Add(new Filter.Field(lookupTable.LookupTableStructures.LT_UIDColumn.ColumnName));
filter.Fields.Add(new Filter.Field(lookupTable.LookupTableStructures.LT_STRUCT_UIDColumn.ColumnName));
filter.Fields.Add(new Filter.Field(lookupTable.LookupTableStructures.LT_PARENT_STRUCT_UIDColumn.ColumnName));
filter.Fields.Add(new Filter.Field(lookupTable.LookupTableStructures.LT_STRUCT_COOKIEColumn.ColumnName));


Filter.IOperator[] operators = new Filter.IOperator[lookupTableGuids.Length];

for (int i = 0; i < lookupTableGuids.Length; i++)
{
string lookupTableGuid = lookupTableGuids[i];

operators[i] = new Filter.FieldOperator(Filter.FieldOperationType.Equal,
lookupTable.LookupTableValues.LT_UIDColumn.ColumnName,
lookupTableGuid);
}


Filter.LogicalOperator logicalOperator = new Filter.LogicalOperator(Filter.LogicalOperationType.Or, operators);

filter.Criteria = logicalOperator;


string filterString = filter.GetXml();
return filterString;
}
}

Notice the use of the logical “or” operators in both methods.  This allows me to filter only on the LT_UIDs I am interested in.
 
Hope this helps somebody!

Microsoft Project Server Error Codes

A resource (.resx) file with the Project Server error codes AND their descriptions might exist somewhere else but I couldn’t find it.  So, here you go.  The first link is to an Excel 2007 workbook that contains the error information and the second is a link to a .resx file that you can include in your PSI projects.

Workbook (use Save\Target As and save it as a “.xlsm” file otherwise it might try to open it as a zip file!)

Resource File

Enjoy!

Monday, July 27, 2009

Machine Crashes, Rebuilds, and Deadlines

Nothing like having your primary and secondary development environments flat line on you AND have crazy deadlines.   I hope Microsoft continues to invest in setup across the board.  Windows Server, SharePoint, SQL, Project Server… A better install experience would go a long way to help us non-infrastructure guys get things up quickly.  Now I am virtualized with backups of everything…

Friday, June 12, 2009

Help Wanted – Core .NET, SharePoint Technologies, and WCF

Company Description

A long-time Microsoft Partner, forProject Technology has a suite of companion products for Microsoft Project Server and Project Professional.   The company is breaking ground on a new product suite targeting large businesses and government agencies concerned with earned value management and integrating data from disparate systems into their Microsoft Project implementation.  But that’s just the beginning…  Our team of visionaries have more than 50 years combined experience in the enterprise project management market with a pipeline of future product ideas.

Headquartered in Texas, but with virtual offices across the United States and India, forProject Technology offers a unique work experience with a team of some of the best minds in the project management software industry.  In addition to our India-based development center, forProject Technology is building a distributed development team here in the United States.  We need very smart people with great technical skills who want to work hard to ship a 1.0 product that will likely shakeup our little corner of the enterprise project management market.

Job Descriptions

Senior Software Design Engineer - SharePoint Technologies + 2 (TWO JOB OPENINGS)

Senior design engineer responsible for designing and implementing a shared library of reusable SharePoint components including application pages, web parts, SharePoint-ready controls, and a custom view rendering framework.   Reporting to the Chief Architect.

· Shared requirements below

· 8+ years of .NET software development experience (.NET Base Class Libraries & C#)

· 5+ years of SharePoint development experience

· 8+ years of ASP.NET development experience

· 5+ years of database design and T-SQL experience

Senior Software Design Engineer - Windows Communication Foundation

Senior design engineer responsible for designing and implementing a services framework to support a specialized extract, transfer, and load system as well as other present and future service-oriented products.  Work closely with a group of design engineers who will help implement your vision and designs.  Reporting to the Chief Architect.

· Shared requirements below

· 8+ years of .NET software development experience (.NET Base Class Libraries & C#)

· 5+ years of web services development

· 3+ years of WCF development

Senior Software Design Engineer - .NET Core

Senior software design engineer responsible for implementing core system APIs and libraries to support a services-oriented, mathematically-heavy enterprise earned value management system.  Reporting to the Chief Architect.

· Shared requirements below

· 8+ years of .NET software development experience (.NET Base Class Libraries & C#)

· Expert API design skills

· Strong service-oriented design skills

· Mathematical or financial background

Technical Writer

Technical writer responsible for designing and writing product help for online and other media for a suite of enterprise project management products.  As technical writer, you will work closely with product development during all aspects of the development cycle to ensure help and system user interfaces are linguistically correct and conceptually absorbable by our end users and  customer IT staff.

· Shared requirements below

· 5+ years experience as a technical writer in a software development or IT environment

· 5+ years experience designing online help, system help, wikis, FAQs, and knowledge bases

· Expert Microsoft Word skills

· Strong understanding of the software development lifecycle

· Excellent illustration\diagramming skills using tools such as Illustrator and Visio 

Computer-Based Training/Web-Based Training Content Developer

Training content developer responsible for building primarily web-based training materials for a suite of enterprise project management products.   As content developer, you will be responsible for the entire content development lifecycle, from requirements to delivery, to ensure the company’s training materials are professional, appealing, enterprise-ready, and properly instruct our customers.   You will be responsible for evaluating CBT/WBT/e-Learning tools and technologies and making recommendations regarding our learning platform.

· Shared requirements below

· 3+ years experience as an e-Learning content developer (CBT/WBT/Others)

· Expert writing and presentation skills

· Expert in CBT/WBT/e-Learning tools and technologies

Shared Requirements

· Able to self-manage

· Excellent written and verbal skills

· Able to work from home with occasional travel

· Excellent code craftsmanship, attention to detail, and precision thinking

· Bachelors degree or equivalent work experience

· Familiar with Scrum and other agile methodologies

· Experience with Microsoft Project Server a plus

Benefits

· Medical, Dental, Eye, and Life insurance

· 401K Investment Program

· Paid Vacation

Contact me here: http://www.colbyafrica.com/forms/contact.htm

Thank you!

Monday, June 08, 2009

forProject Technology, Inc. – Help Wanted!

About a month ago I left Microsoft to take on the role of chief architect at forProject Technology, Inc (FTI).   FTI has an existing suite of products for Microsoft Project that provide advanced earned value management capabilities.   FTI is moving its product suite from the desktop to the enterprise and I am helping them move to a modern, service-oriented architecture.

The company is distributed across two continents, with management and professional services located in the US and a development center in India.  Unlike many companies that outsource to India, FTI owns the development center, which offers a great deal more stability, efficiency, and frankly quality than other arrangements.  That being said, the scope of what needs to be done has made it very apparent that we need to increase development capacity in both India and here in the US.

So, I am putting out the word to my small but loyal readership that I am looking for a couple of Ninjas, star developers, hotshots (minus the attitude), and gurus to help me.  In particular, I am looking for expert SharePoint and Windows Communication Foundation developers.  I have a team of developers in India who are very smart but they need guidance and mentoring. 

If you are interested in being a senior technical lead on my team, let me know ASAP.  You will work from home some weeks and travel to our temporary development center in Overland Park, Kansas.  More information available upon request!

Contact me here: http://www.colbyafrica.com/forms/contact.htm

Colby

Friday, May 08, 2009

Returning to BlogSpot

This blog will become my primary blog for the time being.

Thursday, January 29, 2009

MpFx Walkthrough: Creating the ProjectServer Object & Enumerating Project Information

I am taking a little break from my current project.  Whew. Only such much you can cram into your brain about SharePoint in one unbroken period of time.

Today’s topic starts at the beginning of mpFx:  How to create a ProjectServer object.  I will also demonstrate enumerating project information.  The code sample and an update to the core mpFx library are available on Code Gallery.  Here is the updated mpFx core library and here is the walkthrough.

The following references are required to build and execute:

  • Mcs.Epm.MicrosoftProject.mpFx
  • System
  • System.Core
  • System.Data
  • System.Xml

NOTE: mpFx 1.0 PREVIEW is intended to be a learning aide.  There are problems with parts of the code related to resource acquisition and clean up, performance, and generally some design decisions I made well before I had a full understanding of what the PSI was up to.  I am slowly purging those areas that need purging…

NOTE II: Parts of the PREVIEW edition implement IDisposable where it isn’t really a benefit.  DataSets implement IDispose as part of MarshalByValueComponent, but the source of IDisposable in this case is IComponent!    It is recommended that you call Dispose on any object the implements it, but in this case I can’t see any reason to.  No resources are freed as part of the call, as far as I can tell.

Okay, on to the sample!

Walkthrough

Let’s begin with the sample:

   1: using System;
   2: using System.IO;
   3: using Mcs.Epm.MicrosoftProject.mpFx;
   4: using Mcs.Epm.MicrosoftProject.mpFx.ProjectsWebService;
   5:  
   6: namespace CreateProjectServerObject
   7: {
   8:     class Program
   9:     {
  10:         static void Main()
  11:         {
  12:             /*  There are four constructors for the ProjectServer object.  This demo describes 
  13:              *  the constructor most commonly used in my projects: 
  14:              *  
  15:              *  ProjectServer(string projectServerUrl, DataStoreEnum store, ILog log)
  16:              *  
  17:              *  The constructor takes three parameters:
  18:              *  
  19:              *  1.) projectServerUrl: The full path to the Project Server PWA site
  20:              *  2.) DataStorEnum: The store on which operations will be performed
  21:              *  3.) ILog: An object implementing the ILog interface, which 
  22:              *      performs logging operations.
  23:              *      
  24:              *  At the time of writing, a single class implements ILog and implements
  25:              *  logging to the file system.  Other classes might implement ILog and 
  26:              *  persist events to the Event Log or a database.            
  27:              */
  28:  
  29:             string logDirectory = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "MpFx");
  30:  
  31:             try
  32:             {
  33:                 using (Log log = new Log(logDirectory, "Demo", LogPeriod.Hourly, true))
  34:                 using (ProjectServer projectServer = new ProjectServer("http://projectserver/pwa", DataStoreEnum.WorkingStore, log))
  35:                 {
  36:                     ProjectCollection projects = projectServer.Projects;
  37:  
  38:                     foreach (EnterpriseProject project in projects)
  39:                     {
  40:                         string projectInformation = string.Format("Project Name: {0} - Start Date: {1}",
  41:                                                                   project.Name,
  42:                                                                   EnterpriseProject.StandardInfo(project).PROJ_INFO_START_DATE);
  43:                                                 
  44:                         Console.WriteLine(projectInformation);                     
  45:                     }
  46:                 }
  47:             }
  48:             catch (MpFxException exception)
  49:             {
  50:                 Console.Write(Errors.ProcessMpFxException(exception));
  51:             }
  52:             catch (Exception exception)
  53:             {
  54:                 Console.Write(exception.Message);
  55:             }
  56:  
  57:             Console.WriteLine("Projects enumerated  Press any key to close.");
  58:             Console.ReadKey();
  59:         }
  60:     }
  61: }

We will skip the details on logging for this post.  To begin, let’s look at one of the four constructors implemented on ProjectServer (I chose the one I most commonly use).  In the sample above, the constructor is called on line 34.  Here is the constructor source:

   1: public ProjectServer(string projectServerUrl, DataStoreEnum store, ILog log)
   2: {            
   3:     try
   4:     {
   5:         Log = log;
   6:  
   7:         if (!Utilities.IsValidUrl(projectServerUrl))
   8:         {
   9:             throw new ArgumentException(LibraryResources.InvalidProjectServerUrl);
  10:         }
  11:  
  12:         Settings = new ProjectServerSettings();
  13:         WebServices = new WebServices(this);
  14:         Site = new Uri(projectServerUrl);
  15:         Store = store;
  16:  
  17:         NetworkCredential = CredentialCache.DefaultNetworkCredentials;
  18:         AuthenticationType = AuthenticationType.Windows;
  19:  
  20:         WebServices.LoginWindows = new LoginWindows();
  21:  
  22:         WebServices.LoginWindows.Url = WebServices.AppendPath(projectServerUrl, ServicePaths.WindowsLoginService);
  23:         WebServices.LoginWindows.UseDefaultCredentials = true;
  24:  
  25:         WriteLogEntry(LogArea.Constructor, 
  26:                       LogEntryType.Information, 
  27:                       string.Format(LibraryResources.LogConstructor, AuthenticationType, projectServerUrl));
  28:  
  29:         WebServices.LoginWindows.Login();
  30:  
  31:         Settings.ListSeparator = WebServices.Projects.ReadServerListSeparator();
  32:  
  33:     }
  34:     catch (SoapException exception)
  35:     {
  36:         throw MpFxException.Create(exception, Log, LogArea.Constructor, LogEntryType.Error);
  37:     }
  38:     catch (ArgumentException exception)
  39:     {
  40:         throw MpFxException.Create(exception, Log, LogArea.Constructor, LogEntryType.Error);
  41:     }
  42:     catch (Exception exception)
  43:     {
  44:         throw MpFxException.Create(exception, Log, LogArea.Constructor, LogEntryType.Error);
  45:     }
  46: }

Here are a few elaborations and considerations:

  • Lines 12-15:  mpFx attempts to build an object model from the disparate web services and data of the PSI.  From the ProjectServer object, you can directly access the underlying web services, by accessing the WebServices property (projectServer.WebServices.[webServiceName]) or you can access the encapsulation objects and use the helper methods I have created.  For example, to create a project you can populate a ProjectDataSet object, directly access the project web service, call QueueCreateProject, wait on the job, and deal with the potential exceptions yourself, or you can call projectServer.Projects.Create, which looks like this:
   1: /// <summary>
   2: /// Create a project in Project Server.
   3: /// </summary>
   4: /// <param name="project">Project</param>
   5: /// <param name="validateOnly">Indicates whether the operation should validate only or perform the creation</param>
   6: /// <param name="wait">Indicates whether the call should wait on the queued job.</param>
   7: /// <returns>Queue job GUID</returns>
   8: public Guid Create(ProjectDataSet project, bool validateOnly, bool wait)
   9: {
  10:    try
  11:    {
  12:        Guid jobGuid = Guid.NewGuid();
  13:  
  14:        Parent.WebServices.Projects.QueueCreateProject(jobGuid, project, validateOnly);                
  15:  
  16:        if (wait)
  17:        {
  18:            string errorMessage;
  19:  
  20:            Parent.Queue.WaitOnJobStatus(jobGuid,
  21:                                         JobState.Success,
  22:                                         Parent.Settings.QueueStatusRetryCount,
  23:                                         Parent.Settings.QueueStatusSleepDuration,
  24:                                         out errorMessage);
  25:  
  26:            MpFxException.ThrowIfError(errorMessage, Parent.Log, LogArea.CreateProject, LogEntryType.Error);                    
  27:            
  28:        }
  29:  
  30:        return jobGuid;
  31:  
  32:    }
  33:    catch (SoapException exception)
  34:    {
  35:        throw MpFxException.Create(exception, Parent.Log, LogArea.CreateProject, LogEntryType.Error);
  36:    }
  37:    catch (Exception exception)
  38:    {
  39:        throw MpFxException.Create(exception, Parent.Log, LogArea.CreateProject, LogEntryType.Error);
  40:    }
  41: }

  • The particular constructor we used in the sample uses Windows authentication when communicating with the PSI.  NOTE: Forms authentication has NOT been tested with mpFx 1.0 PREVIEW

Returning to the sample:

  36:                     ProjectCollection projects = projectServer.Projects;

The Projects property returns a ProjectCollection object. Looking at the property implementation uncovers another design decision:

   1: public ProjectCollection Projects
   2: {
   3:     get
   4:     {
   5:         if (_ProjectCollection == null)
   6:         {
   7:             _ProjectCollection = new ProjectCollection(this);
   8:         }
   9:  
  10:         return _ProjectCollection;
  11:     }
  12: }

The lazy load pattern is widely employed (with various degrees of success and cleanliness!) throughout mpFx.   Examining the internals of the next line of code further illustrates this point:

  38:                     foreach (EnterpriseProject project in projects)

Let’s take a look at the enumerator implementation.  First, the ProjectCollection implements two IEnumerables:

   1: public class ProjectCollection : IEnumerable<EnterpriseProject>, IEnumerable<Guid>

The first enumerates EnterpriseProjects (we will get to this later) and the second enumerates Guids.  Let’s take a look at the EnterpriseProject enumerator, which is the one used in the sample:

   1: public IEnumerator<EnterpriseProject> GetEnumerator()
   2: {
   3:     return ((IEnumerable<EnterpriseProject>)this).GetEnumerator();
   4: }

And:

   1: IEnumerator<EnterpriseProject> IEnumerable<EnterpriseProject>.GetEnumerator()
   2: {
   3:     if (_projectsCollection == null)
   4:     {
   5:         LoadProjectCollection();
   6:     }
   7:  
   8:     ThrowLoadCollectionLoadException();
   9:  
  10:     foreach (KeyValuePair<Guid, EnterpriseProject> pair in _projectsCollection)
  11:     {
  12:         yield return pair.Value;
  13:     }
  14: }

Note that the backing field is first checked for nullness.  If it is null, the project collection is loaded:

   1: internal void LoadProjectCollection()
   2: {
   3:     if (_projectsCollection == null)
   4:     {
   5:         _projectsCollection = new Dictionary<Guid, EnterpriseProject>();
   6:     }
   7:     else
   8:     {
   9:         _projectsCollection.Clear();
  10:     }
  11:  
  12:     using (ProjectDataSet projectDataSet = Parent.WebServices.Projects.ReadProjectStatus(Guid.Empty, Parent.Store, string.Empty, (int)Project.ProjectType.Project))
  13:     {
  14:         foreach (ProjectDataSet.ProjectRow project in projectDataSet.Project.Rows)
  15:         {
  16:             if (project.PROJ_UID != Guid.Empty)
  17:             {
  18:                 _projectsCollection.Add(project.PROJ_UID, new EnterpriseProject(this, project.PROJ_UID, project.PROJ_NAME, Parent.Store));
  19:             }
  20:         }
  21:     }
  22: }

imagePretty straightforward so far.  The ProjectCollection contains EnterpriseProjects.  Rather than build a full model of all of the ProjectDataSet.ProjectRow members, it exposes data and helper methods.  See the class diagram to the left.

The properties and methods that are currently implement reflect the learning requirements I experienced while developing mpFx in the early days.  Essentially, every time I needed to read or act on project data in a new way, I would implement a property or method appropriate to the requirement. 

Returning to the sample:

  40:                         string projectInformation = string.Format("Project Name: {0} - Start Date: {1}",
  41:                                                                   project.Name,
  42:                                                                   EnterpriseProject.StandardInfo(project).PROJ_INFO_START_DATE);
  43:                                                 

The project name is one of a few properties that wrap elements of the ProjectDataSet.ProjectRow data row class.  The helper method EnterpriseProject.StandardInfo is short hand for project.StandardInformation.Project[0].  Accessing the StandardInformation property causes a call to be made to Project Server:

   1: public ProjectDataSet StandardInformation
   2: {
   3:     get
   4:     {
   5:         return Parent.Parent.WebServices.Projects.ReadProjectEntities(ProjectGuid, (int)ProjectEntityType.Project, Parent.Parent.Store);
   6:     }
   7: }

That’s pretty much it!

More later.  Back to SharePoint.

Disclaimer

Content on this site is provided "AS IS" with no warranties and confers no rights. Additionally, all content on this site is my own personal opinion and does not represent my employer's view in any way.