Monday, June 29, 2009

Hotmail.com and Live.com email access to your iPhone

So being the proud new owner of an iPhone 3GS after years of dealing with the inferior Windows Mobile and Palm platforms, I'm also learning the ins and outs of "things that should be easy".

Take for instance the fact that MSFT only offers crippled POP3 access to Hotmail/Live.com, thus making those nearly worthless on the iPhone. Thankfully the fine folks at FluentFactory make just the thing to make Hotmail/Live.com mail on the iPhone nearly what it should be (for those of us with a many, many year history with our Hotmail accounts).

http://fluentfactory.com/mboxmail/

But that said, looking at what Google has to offer for synching to the iPhone, I can't help myself from laughing!

http://www.google.com/mobile/apple/sync.html

The following paragraph copied from that page is the key:

Important! Google Sync uses the Microsoft© Exchange ActiveSync© protocol. When setting up a new Exchange ActiveSync account on your iPhone, all existing Contacts and Calendar events will be removed from your phone. Please make sure to back up any important data before you set up Google Sync.

Pretty sweet when Google licenses ActiveSync from MSFT for connecting GMail for use on the iPhone, while MSFT refuses to offer the same functionality for Hotmail/LiveMail users, even the paid ones like me! WTF MSFT?!?!?!

Tuesday, October 28, 2008

Touch Typing - i.e., eaking seconds and minutes out of your day

After being forwarded a link to Stevey's Blog Rants: Programming's Dirtiest Little Secret today, I figured I'd do a blog post on this topic too. IMHO, if you sit in front of a computer all day long, especially as a computer programmer, and can't touch type faster than 60wpm, start practicing for 30 minutes a day with one of these tools until you can.
http://www.keybr.com/ (This is the one that I like the best, but a few more follow.)

Tuesday, September 30, 2008

Incorrectly Storing Objects In The ASP.NET Session

In an effort to keep this from happening to someone else, I figured I'd write about it today. I'm surely not the first person to ever write about this, so I'm not claiming to have found something novel. I've personally never written a web application that needed to utilize the ASP.NET Session object, but apparently other folks have not learned that.

Anyway, the following chunk of code was being used to store a reference to the "domain object" currently being viewed/edited.

namespace Your.Web
{
/// <summary>
/// Summary description for ContainerBase.
/// </summary>
public class ContainerBase : Page
{
public virtual IDomainObject DomainObject
{
get
{
if (Session["DomainObject"] != null)
{
return (IDomainObject) Session["DomainObject"];
}
else
{
return null;
}
}
set { Session["DomainObject"] = value; }
}
}
}

The result of doing this is that by opening one "domain object" (DO1) for editing in a window (W1), hitting CTRL-N to open a new window (W2) that by default will still show DO1, navigating to a new domain object (DO2) in W2, then switching back to W1 and clicking the Save button without any other operations, the edits in W1 that should be being applied to DO1 are actually being applied to DO2. Dooh!!!! That eliminates a user's ability to have two or more web browser windows open at a time. MS-DOS, here we come!

Moral of the story, don't use the Session object, unless you really truly have read everything there is about using it and are still convinced that you should still use it. And even then, you're probably still wrong!

For further reading, see the ASP.NET Session State Overview on MSDN.

Friday, September 26, 2008

The Overuse of the StringBuilder class in .NET

So this friend of mine Adam wrote this post today. The main point of the post I love, as it correctly abstracts the creation of the query string behind a class and away from the rest of the code that is consuming the query results. Why I'm writing this post is that while his post was not directly having to do with the StringBuilder class, IMHO, he is displaying what I believe is indicative of the over zealous use of the StringBuilder class.

For the sake of convenience, this is how he wrote it:

private static string BuildDefaultViewQuery()
{
var builder = new StringBuilder();
builder.Append("<Where>");
builder.Append("<Eq><FieldRef Name='DefaultView' /><Value Type='Boolean'>");
builder.Append("1");
builder.Append("</Value></Eq></Where>");
return builder.ToString();
}

This is how I would have written the guts of this method:

private static string BuildDefaultViewQuery()
{
return String.Format(@"
<Where>
<Eq>
<FieldRef Name='DefaultView' />
<Value Type='Boolean'>{1}</Value>
</Eq>
</Where>",
1);
}
I just happen to think that there is too much "noise" when using a StringBuilder for this kind of stuff. Yes, if you're looping through a potentially large set of data and producing a potentially large string as a result, then by all means, use a StringBuilder, as that is what you should be doing. But if you're just building a relatively static string, with a handful of "variables" inserted into the string, use String.Format() in conjunction with a string that supports line breaks (i.e., using the @"" syntax). The resulting code is so much easier to read and understand what is really going on besides the building of a string.

Wednesday, September 24, 2008

SharePoint/MOSS 2007 External Third Party Desktop Application Integration

Today I was presented a scenario where a client wanted to integrate a non-Microsoft Office desktop application into their Microsoft Office SharePoint Server 2007 (MOSS) environment such that documents created/edited in that app and stored in MOSS document libraries were able to be directly opened into that app ready for editing, and ultimately saved back to the document library.

Since Google'ing the answer to this scenario took me more than 30 seconds to find, here is the answer.

Inside SharePoint - Integrating "Office" Applications
http://technet.microsoft.com/en-us/magazine/cc565180.aspx

And here are some more useful links:

Text Files, Associations, and SharePoint
http://weblogs.asp.net/bsimser/archive/2005/01/24/359911.aspx

Sharepoint Beef #3 : Melt-in-your-mouth Native Apps support issue resolved
http://weblogs.asp.net/dburke/archive/2004/01/13/58330.aspx

SharePoint.OpenDocuments Control
http://msdn.microsoft.com/en-us/library/ms454230.aspx

How to enable the “Edit in Microsoft Office XXXXX” feature for Office 2007 documents
http://www.syntergy.com/sharepoint/products/tips/

DocIcon.xml - Adding Notepad and WordPerfect as applications
http://www.sharepointblogs.com/korry/archive/2006/03/16/docicon-xml-adding-notepad-and-wordperfect-as-applications.aspx

Adding a Document Template, File Type, and Editing Application to a Site Definition
http://msdn.microsoft.com/en-us/library/ms868275.aspx

PDF Integration in SharePoint
http://www.specialinvestigations.org/blog/CommentView,guid,1a6f8f2b-55a1-4421-bf11-b1256ea418dc.aspx
http://www.portalsolutions.net/Blog/Lists/Posts/Post.aspx?ID=40

Customizing the Shortcut Menu for List Items
http://msdn.microsoft.com/en-us/library/ms868274.aspx

Customizing SharePoint Context Menus
http://blogs.msdn.com/bowerm/articles/175691.aspx

Thursday, December 13, 2007

HDD and Thumb Drive Performance Comparison

So, since I tend to be a speed freak of sorts, I wanted to see just how well, or not, my storage devices perform. In my Dell Precision M90 laptop, I've got a Seagate - Momentus 100GB drive, then a Western Digital Passport 250GB USB external drive, and finally a PNY Attaché 16GB USB thumb drive.

The following are the screen shots from HD Tune that indicate how each performed:









All in all, I'm fairly pleased. My primary desire is to simply have capacity. But when you start storing GB sized database files and virtual machines, better performance can mean a lot!

Tuesday, October 23, 2007

Automatic Updating of AssemblyFileVersion in C# Projects

So my good buddy and co-worker Stacy Draper and I put together a solution today to handle versioning of .NET assemblies used for SharePoint web part development. Maybe this has been done before, maybe even better than this, but a quick Google search didn't turn up squat for us, or at least anything we liked, so we created our own solution.

The general problem revolves around the fact that the AssemblyVersion attribute in .NET assemblies is used by other assemblies when referencing strong named/signed assemblies stored in the GAC. Now you don't have to put your SharePoint assemblies in the GAC, but in most all cases, it simply makes the most sense. If you change the AssemblyVersion of an already installed and used over and over and over again web part, you're in a world of hurt if you arbitrarily change the AssemblyVersion to something new. At that point, you're faced with having to update 10's to 100's to 1000's of web part pages that reference the original web part. That's not to say there aren't times when it makes sense to do so, but for day to day development and deployment scenarios where it really isn't a new version of the web part, but itis an updated version of the code, you still want to be able to have a very noticeable way to see what version is currently installed on your portal.

So that takes us to what Stacy and I whipped up today. I created a script that updates your AssemblyInfo.cs file, setting the AssemblyFileVersion attribute to a time-stamped version number, based off of the AssemblyVersion attribute's value, of when the assembly was built. Stacy created a script that updates the feature.xml file that is part of the SharePoint feature with the new version number of the AssemblyFileVersion to be displayed within the description of the feature. This is so you can also see that version info from within SharPoint, in addition to viewing the file properties. Both of these scripts are then configured to run in your pre-build events for your C# project. What follows is my pre-build commands and the content of my VBS script. I place my VBS file in a folder named UTIL inside my C# project. See Stacy's blog for his stuff.


CD "$(ProjectDir)UTIL"
cscript /nologo UpdateAssemblyFileVersion.vbs "$(ProjectDir)Properties\AssemblyInfo.cs"

'------------------------------------------------------------------------------
'--- UpdateAssemblyFileVersion.vbs
'------------------------------------------------------------------------------
'--- Author - Jim Sally
'--- Date - 2007.10.23
'------------------------------------------------------------------------------
'--- This updates the AssemblyFileVersion attribute in the AssemblyInfo.cs file
'--- of a given C# project. The AssemblyFileVersion is updated to contain the
'--- first two digits of the AssemblyVersion, with YMMDD.HHmm as the last two
'--- digits.
'------------------------------------------------------------------------------
Option Explicit

Dim fileHeader
fileHeader = "UpdateAssemblyFileVersion.vbs :: "

EnsureCommandLineArgumentExists
EnsureAssemblyInfoFileExists WScript.Arguments(0)
UpdateAssemblyFileVersion WScript.Arguments(0)

Private Sub EnsureCommandLineArgumentExists()
If WScript.Arguments.Count <> 1 Then
WScript.Echo fileHeader & "You must supply the path to the AssemblyInfo.cs file as the only argument on the command line"
WScript.Quit 1
End If
End Sub

Private Sub EnsureAssemblyInfoFileExists(AssemblyInfoFileName)
Dim fso

Set fso = CreateObject("Scripting.FileSystemObject")
If Not fso.FileExists(AssemblyInfoFileName) Then
WScript.Echo fileHeader & "AssemblyInfo.cs does not exist at the path provided -> " & AssemblyInfoFileName
WScript.Quit 1
End If

Set fso = Nothing
End Sub

Private Sub UpdateAssemblyFileVersion(AssemblyInfoFileName)
Dim assemblyInfo, newAssemblyInfo

assemblyInfo = GetContentsOfAssemblyInfoFile(AssemblyInfoFileName)
newAssemblyInfo = UpdateAssemblyInfoText(assemblyInfo)

If newAssemblyInfo <> assemblyInfo Then
SaveContentsOfAssemblyInfoFile AssemblyInfoFileName, newAssemblyInfo
End If
End Sub

Private Function GetContentsOfAssemblyInfoFile(AssemblyInfoFileName)
Dim fso, file

Set fso = CreateObject("Scripting.FileSystemObject")
Set file = fso.OpenTextFile(AssemblyInfoFileName)
GetContentsOfAssemblyInfoFile = file.ReadAll()
file.Close

Set file = Nothing
Set fso = Nothing
End Function

Private Sub SaveContentsOfAssemblyInfoFile(AssemblyInfoFileName, infoText)
Dim fso, file

Set fso = CreateObject("Scripting.FileSystemObject")
Set file = fso.CreateTextFile(AssemblyInfoFileName, true)
file.Write(infoText)
file.Close

Set file = Nothing
Set fso = Nothing
End Sub

Private Function UpdateAssemblyInfoText(AssemblyInfoText)
Dim posAV, posAFV, posEolAV, posEolAFV, aNAV
Dim originalAssemblyVersionText, originalAssemblyFileVersionText, newAssemblyFileVersionText

posAV = InStr(AssemblyInfoText, "AssemblyVersion")
posAFV = InStr(AssemblyInfoText, "AssemblyFileVersion")

If Not IsNull(posAV) And Not IsNull(posAFV) And posAV > 0 And posAFV > 0 Then
posEolAV = InStr(posAV, AssemblyInfoText, "]")
posEolAFV = InStr(posAFV, AssemblyInfoText, "]")

originalAssemblyVersionText = Mid(AssemblyInfoText, posAV, posEolAV - posAV)
originalAssemblyFileVersionText = Mid(AssemblyInfoText, posAFV, posEolAFV - posAFV)

aNAV = Split(Split(originalAssemblyVersionText, """")(1), ".")
newAssemblyFileVersionText = "AssemblyFileVersion(""" & GetNewFileVersionNumber(aNAV) & """)"

If originalAssemblyFileVersionText <> newAssemblyFileVersionText Then
UpdateAssemblyInfoText = Replace(AssemblyInfoText, originalAssemblyFileVersionText, newAssemblyFileVersionText)

WScript.Echo fileHeader & "Updated attribute from '" & originalAssemblyFileVersionText & "' to '" & newAssemblyFileVersionText & "'"
WScript.Echo
Else
WScript.Echo fileHeader & "AssemblyFileVersion attribute already '" & newAssemblyFileVersionText & "'"
WScript.Echo
WScript.Quit
End If
Else
WScript.Echo fileHeader & "AssemblyVersion or AssemblyFileVersion attribute not found"
WScript.Echo
WScript.Quit 1
End If
End Function

Private Function GetNewFileVersionNumber(versionArray)
GetNewFileVersionNumber = versionArray(0) & "." & versionArray(1) & "." & GetVersionYMMDD() & "." & GetVersionHHmm()
End Function

Private Function GetVersionYMMDD()
Dim y, m, d

y = right(year(now()),1)
If y = 0 Then
y = 10
ElseIf y > 6 Then
m = (y - 6) * 12
y = 6
End If

m = Right("00" & (m + Month(now)), 2)
d = Right("00" & Day(now), 2)

GetVersionYMMDD = y & m & d
End Function

Private Function GetVersionHHmm()
GetVersionHHmm = Right("00" & Hour(Now()), 2) & Right("00" & Minute(Now()), 2)
End Function

Friday, October 5, 2007

Missing MOSS 2007 Functionality

So having been using MOSS 2007 for the past 3 months now, I figured I'd put my own stake in the ground as to some things that would be quite handy to be built into the product. Yes, there are probably ways to do some custom webparts to handle some of these, but IMHO, these are pieces of functionality that should be part of the base product. Hopefully they make it into the next version...
  1. List View Security
    • This follows some of the same logic behind the use of a database VIEW. An administrator should be able to create a SharePoint list view with a row-level filter and then secure the list of views to the appropriate folks (to the point of hiding views a user doesn't have access to use).
    • This would allow various business scenarios to be simply handled via view security. For instance
      • hiding items based on the state/status code of an item to augment workflows
      • storing heterogeneous content types, but securing which are visible to whom
  2. List Item Column Security
    • This is very similar to the view security above, but at a column-level, rather than row-level (vertical security versus horizontal security). In fact, this could work very similarly to how views currently work, but could be called item views, instead of list views.
    • Yes, you could write custom ASPX pages to handle this, but the point of this functionality is to give a non-developer the ability to create this kind of functionality.
  3. Custom Attributes On Lists
    • This is more or less a property bag (key/value pair bucket) that can be populated via the GUI by an end user, or via the API.
    • This would be primarily useful to a developer who wants to track/manipulate additional details about a list, but enable end-users to make changes to those values when necessary.

Thursday, August 30, 2007

Programmatically Accessing Excel Data via .NET on 64-bit servers

So it turns out that using ADO.NET to access MS Excel worksheet data in an ASP.NET application running on the 64-bit version of Windows Server 2003 does not quite work as expected. This also goes for any 64-bit .NET code. In short, the Microsoft.Jet.OLEDB.4.0 provider is a 32-bit driver that does not work when directly used by 64-bit code. The following post from a Microsoft employee describes the issue:

Connect to Excel. Using x64 (64-bit) platform. Compiled as x64

"Since there are no 64-bit OleDb drivers for anything other than SQL Server, therefore, we cannot write 64-bit apps which interface with databases directly.

What you need to do is split your application into a 32 bit part and a 64 bit part, use COM interop to cross the 64/32 bit boundary. For instance, drop the code (just a simple class library compiled as 32 bit) that retrieves the Excel data into a COM+ (System.EnterpriseServices) as a "server type" application, and call those server methods from your 64 bit Windows service. This is exactly why System.EnterpriseServices are made for."

To restate the above in a list of to do items:
  • Refactor the Excel access code into a class by itself in a class library by itself
  • Make sure that this new class inherits from System.EnterpriseServices.ServicedComponent so that it can be hosted within COM+ (Component Services)
  • Make sure the new ServicedComponent's class library is compiled in 32-bit mode
  • Alter the calling code (ASP.NET web page, control, web part, etc.) to call the new ServicedComponent for the Excel data retrieval routines
  • Install the new ServicedComponent into Component Services

Using Log4Net in 4 Simple Steps

First off, yes, this is a complete rip-off from this blog post, but with value added in that this represents my personal preference when using log4net. This is a simple four step process of adding logging to an application. I prefer log4net over the MS logging application block, primarily as log4net has zero dependencies and NHibernate already references it. IMHO, if you're doing ANY kind of logging, reference log4net into your project and use it instead of Trace.WriteLine() or Debug.WriteLine().

Step 1. Add the following in the AssemblyInfo.cs

[assembly: log4net.Config.XmlConfiguratorAttribute(
ConfigFile = "log4net.xml", Watch = true)]
Step 2. Create the log4net.xml file and add the following to the new file

With respect to the location of this file and SharePoint, this file can exist in the root of the wss root, next to the relevant web.config file for the given site. In general, this should sit next to the app.config or web.config file for the given application.

<log4net>
<appender name="GeneralLog" type="log4net.Appender.RollingFileAppender">
<file value="${TEMP}\\Logs\\AppName_${COMPUTERNAME} " />
<appendToFile value="true" />
<rollingStyle value="Composite" />
<staticLogFileName value="false" />
<datePattern value=".yyyyMMdd.'log'" />
<maxSizeRollBackups value="10" />
<maximumFileSize value="5MB" />
<lockingModel type="log4net.Appender.FileAppender+MinimalLock" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%d{HH:mm:ss.fff} [%t] %-5p %c - %m%n" />
</layout>
</appender>
<appender name="DebugAppender" type="log4net.Appender.DebugAppender">
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%d [%t] %-5level %logger - %message%newline" />
</layout>
</appender>
<root>
<level value="ALL" />
<appender-ref ref="GeneralLog" />
<appender-ref ref="DebugAppender" />
</root>
<!-- Print only messages of level ERROR or above in the package NHibernate -->
<logger name="NHibernate" additivity="true">
<level value="ERROR" />
</logger>
</log4net>

Step 3. Add the following at the top of your class
private static readonly log4net.ILog log = log4net.LogManager.GetLogger(
System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);

Step 4. Add the following line in the code where you want logging to be done

log.Info("Program Started " + DateTime.Now.ToString ());
References

Apache log4net Home Page - http://logging.apache.org/log4net/