June 2008 - Posts
When you want to implement a custom stsadm for SharePoint, all you realy have to do is implement the Microsoft.SharePoint.StsAdmin.ISPStsadmCommand interface. Because I develop on a machine with no SharePoint installed, I (only) copy the assemblies I need to develop locally. For the stsadm command I was working on recently (more on this in a future post) I only needed the Microsoft.SharePoint and Microsoft.Office.Server assemblies. Or at least I thought so...
After implementing the functionality I tried running code analysis, and I got two errors:
CA0052 : No targets were selected.
CA0055 : Could not load AssemblyName.
After trying several ways to fix this, I finally compiled the project on a machine with SharePoint installed. At that point, compiling and running code analysis both worked fine. Code analysis then gave a warning that pointed me to the solution of the problem:
CA2123 : Microsoft.Security : Add the following security attribute to 'ClassName.MethodName(string)' in order to match a LinkDemand on base method 'ISPStsadmCommand.GetHelpMessage(string)': [SharePointPermission(SecurityAction.LinkDemand, ObjectModel = true)].
Apparently the attribute requires an extra reference. When I copied that assembly (Microsoft.SharePoint.Security) to my non-SharePoint development machine, all worked correctly. FYI: I did add the attribute code analysis suggested by the way.
While writing a custom stsadm command for SharePoint (more on that in a future post) and having to do something based on the command the user typed in, I used ToLower(CultureInfo.InvariantCulture) on the command string to be able to check without the hassle of differences in casing. When I ran code analysis on my project, I got this somewhat cryptic message:
CA1308 : Microsoft.Globalization : In method 'CommandParser.Run(string, StringDictionary, out string)', replace the call to 'string.ToLower(CultureInfo)' with String.ToUpperInvariant().
So in order to solve a code analysis warning I should change lowercasing a string to a call to ToUpperInvariant? That seems a bit strange, right? Because I was almost sure this could not be a typo, I searched around a bit. When you search for the error number, you get an explanation from Microsoft through the MSDN article Normalize strings to uppercase [1]. Apparently there's an issue with a small group of characters which are unable to round trip when converted to lowercase. For more information: have a look at Michael Kaplan's post Get off my (lower) case! (or: Casing, the 1st) which explains it pretty well [2].
Hope this helps some of you ;)
[1] http://msdn.microsoft.com/en-us/library/bb386042.aspx
[2] http://blogs.msdn.com/michkap/archive/2004/12/02/273619.aspx
One of the posts that is being read quite frequently over here is HOWTO: Encode a password using MD5 in C# (or: howto calculate the MD5 hash for a string). One of the (many) reactions to that post was from one Simucal, stating:
Also, to the original poster, Rick van den Bosch... shame on you for using ASCIIEncoding.Default.GetBytes(). You do realize that would seriously restrict the usefulness of your method to working only in languages that ASCII has the character sets for, right?
I suggest you read this article entitled:
The Absolute Minimum Every Software Developer Absolutely, Positively Must Know About Unicode and Character Sets (No Excuses!)
Yes, I do realize that using ASCIIEncoding.Default.GetBytes() would seriously restrict the usefulness of the method to working only in languages that ASCII has the character sets for. The primary reason for the post was to illustrate the way to calculate the hash. Representing it as a string was only there to be complete for a specific scenario. I did not try to write a hashing function for every language ;)
That being said, the post Simucal refers to is a good one, and it truly is the absolute minimum every software developer absolutely, positively must know about unicode and character sets. And I guess there are no excuses ;). So if you haven't done so already, go and read it.
The situation I talked about in my previous post had one very big issue: LINQ sometimes just is too dynamic... ;)
The fact that I changed the value for the Ranking property for each SpecificObject that got matched, made the primary LINQ query have a different outcome. LINQ uses lazy evaluation and because of that, the contents of finalGroups and exitGroups changed every time I changed the Ranking value for one of the SpecificObjects. Each time the variable containing the outcome of the LINQ query is iterated through, the query is evaluated again. This made that the matching was all off, and I ended up with a bunch of exit groups that got matched!
To have a simple solution, I did the following. This holds the final and exit objects in a 'static' list in stead of a dynamic query result. This way, the SpecificObjects in the finalObjects and exitObjects lists stay the same, regardless of the changes you make to any of the SpecificObjects.
int count;
int numberOfPossibilities;
IEnumerable<SpecificObject> temp;
List<SpecificObject> finalObjects;
List<SpecificObject> exitObjects;
count = 0;
finalObjects = new List<SpecificObject>();
exitObjects = new List<SpecificObject>();
temp = from SpecificObject specificObject in AllSpecificObjects
orderby specificObject.Ranking
select specificObject;
foreach (SpecificObject specificObject in temp)
{
if (count < numberOfPossibilities)
{
finalObjects.Add(specificObject);
}
else
{
exitObjects.Add(specificObject);
}
count++;
}
By the way, we have a winner. Ruud Campsteijn was pretty quick with the right answer!
While developing an algorithm to match preferences to possibilities, I had to sort a generic list of a specific object type (SpecificObject). The first x objects would be matched, the rest would be excluded because of the number of available places. To determine the group of objects that would be matched and the group that would be excluded, I did something like this:
IEnumerable<SpecificObject> sortedObjects;
IEnumerable<SpecificObject> finalObjects;
IEnumerable<SpecificObject> exitObjects;
sortedObjects = from SpecificObject specificObject in AllSpecificObjects
orderby specificObject.Ranking
select specificObject;
finalObjects = sortedObjects.Take<SpecificObject>(numberOfPossibilities);
exitObjects = sortedObjects.Skip<SpecificObject>(numberOfPossibilities);
Next I would iterate through the possibilities and look for a match in (a subset of) the 'finalObjects' list. To be sure the matching would be done honestly during this process, I changed the value for the Ranking property after a positive match. This way, SpecificObjects that had no match yet would move up in the ranking and would be matched before SpecificObjects that already had one or more positive matches. Unfortunately, this had a very unwanted side-effect.
Can you think of it? Let me know and put it in the comments. The answer can be found in my next post.
After developing a custom Web Service to be hosted in SharePoint (based on this HowTo article on MSDN) and deploying it on a testing environment, I got some entries in the EventLog over there, stating:
"Unable to connect publishing custom string handler for output caching. IIS Instance Id is 'xxxxxxxxx', url is 'http://internalsharepointserver/somesubfolder/services.asmx"
The PublishingHttpModule trying to cache an unmanaged path seems to be the problem here. When enabled for a custom web application (such as my web service), it causes the eventlog entry each time certain requests like web service calls or static CSS files are made.
The quick solution is to simply remove the PublishingHttpModule for the custom web application. You can do so by editing the web.config file:
<httpModules>
<remove name="PublishingHttpModule" />
...
</httpModules>
To completely switch back to all default ASP.NET modules, use these settings in the web.config file:
<httpModules>
<clear/>
<add name="OutputCache" type="System.Web.Caching.OutputCacheModule" />
<add name="Session" type="System.Web.SessionState.SessionStateModule" />
<add name="WindowsAuthentication" type="System.Web.Security.WindowsAuthenticationModule" />
<add name="FormsAuthentication" type="System.Web.Security.FormsAuthenticationModule" />
<add name="PassportAuthentication" type="System.Web.Security.PassportAuthenticationModule" />
<add name="UrlAuthorization" type="System.Web.Security.UrlAuthorizationModule" />
<add name="FileAuthorization" type="System.Web.Security.FileAuthorizationModule" />
...
</httpModules>