Saturday, 22 November 2008

Dell D630 Problem - Cannot enable LAN Card - Fixed

I normally connect my laptop to my home network via wireless on a Belkin Router (F1PI241EGau) from iiNet. Tonight, however, I tried to connect via a LAN port because it appears my router's wireless capability has been freezing every day with Smurf attacks - and I wanted to see the log before hard rebooting it. Rebooting has the unwanted effect of clearing out any diagnostic messages I might get right before the failure of wireless.

Problem was - I just couldn't enable the LAN adapter on my laptop - even through device manager. When I tried to enable it it would respond with a shocked "Connection Failed!" or "Windows could not enable your device" - and would show "Device is disabled (Code 22)". Nothing at all was showing up in the event log under Application/System categories - so the event log was about as useful as a screen door on a submarine in this situation.

I tried to disable some the Dell Quickset (f)utility and disabled power management to try to fix the problem. Dell has a lovely (for some) service that disables the network card when you are not on mains power called "NICCONFIGSVC" (C:\Program Files\Dell\QuickSet\NICCONFIGSVC.exe) - but still could not enable my laptop LAN card.

The simple fix was to uninstall the problematic network card and restarted. I now have a brand spanking new Local Area Network Adapter 3 which is fully functional and I can now proceed to diagnose the nasty Smurf DoS attacks.

Tuesday, 4 November 2008

List of Alternative Options for Deploying or Hosting Applications to SharePoint 2007 or WSS 3.0

There are several different ways you can deploy an application to MOSS:
  1. Create Custom built Web Parts and deploy them as features
  2. Drop an ASP.NET application in c:\program files\common files\microsoft shared\web server extensions\12\template\layouts that gets virtualized to every site on your server
    e.g. to http://servername/sites/sitename/_layouts/MyApp/SomePage.aspx
  3. Use User Controls and the Son of SmartPart (See http://www.smartpart.info/default.aspx)
  4. Using ASPX pages added to SharePoint Site (this involves deploying to the bin folder of your SharePoint site such as C:\Inetpub\wwwroot\wss\VirtualDirectories\moss.litwareinc.com80\bin, Adding SafeControls Web.Config entries to allow your assemblies to load, and deploying your aspx to the relevant site.

For more info see

http://blogs.msdn.com/cjohnson/archive/2006/09/05/application-development-on-moss-2007-amp-wss-v3.aspx. It also has a very handy decision matrix when you are having trouble deciding which deployment model/architecture you should use.

There is also a list of 2 other options described here:

http://sharenotes.wordpress.com/2008/02/21/add-custom-aspx-pages-or-asp-net-pages-in-sharepoint/

  1. Using features and WSP package: Following some steps as recommended by Andrew Connell (MOSS MVP). Here is the blog. I believe this is the standard approach users are using in the SharePoint developer community.
  2. Using VSeWSS (Visual Studio extensions for WSS): This is yet another and latest solution. Microsoft recently released the VSeWSS 1.1 RTM. Using this, we can deploy all the asp .net pages into SharePoint by setting up a new project in Visual Studio. VSeWSS creates a solution package using features. Setup the project and hit ‘Deploy’ and it is done.

Monday, 3 November 2008

Using Attributes, Reflection and GetCustomAttributes() to support field mappings between systems

My task today was to generate MS word documents using Aspose.Word and merge fields. The problem was, the names of the merge fields did not match exactly with the names of the properties of my objects - and I didn't want to map all fields in my objects to the external components in a separate method call. I approached this with a mechanism I have used several times before - using CustomAttributes and Reflection to drive the property mappings between application entities and external components such as Documents or Emails. See below for details on this. Note the use of the GetCustomAttributes() for retrieving custom attributes on your properties.

The attribute class:



/// <summary>
/// Supports the mapping between Aspose.Word Merge fields and the Fields
/// in the Data Transfer Object
/// </summary>
[AttributeUsage(AttributeTargets.All)]
public class DocumentMergeFieldMappingAttribute : System.Attribute
{
public string MergeFieldName { get; set; }

public DocumentMergeFieldMappingAttribute(string mergeFieldName)
{
MergeFieldName = mergeFieldName;
}
}


The mapping class which builds up a Dictionary (name, value pairs)


/// <summary>
/// Gets dictionary of field values for merge into documents - uses the
/// DocumentMergeFieldMappingAttribute to determine which dto properties
/// should render to the Document itself.
/// </summary>
/// <param name="inputDto"></param>
/// <returns></returns>
private IDictionary<string, IFieldValueDto> GetDtoFieldValues(object inputDto)
{
Dictionary<string, IFieldValueDto> dictionary = new Dictionary<string, IFieldValueDto>();

Type objectType = inputDto.GetType();
PropertyInfo[] properties = objectType.GetProperties();

foreach(PropertyInfo property in properties)
{
foreach (Attribute attribute in property.GetCustomAttributes(true))
{
if (attribute is DocumentMergeFieldMappingAttribute)
{
string fieldValue = property.GetValue(inputDto, null) as string ?? string.Empty;
//Set property value and the mapped document field name
dictionary.Add(((DocumentMergeFieldMappingAttribute)attribute)
.MergeFieldName, new FieldValueDto<string>(fieldValue));
}
}
}
return dictionary;
}

Thursday, 30 October 2008

Uploading Files to MOSS 2007 via the SharePoint Object Model

I've blogged on a similar topic about a year ago, but the previous post was using the Copy.asmx web service directly (see http://ddkonline.blogspot.com/2008/01/uploading-files-to-moss-2007-via.html). Here is the equivalent using the SharePoint object model when you have the luxury of being hosted on the WSS/SharePoint box. One thing to note is that you will chew up memory if you don't dispose your SPWeb and SPSite objects correctly (either via using or by doing explicit Dispose() calls) - you cannot rely on .NET Framework garbage collection to clean them up for you. See inline comments below on this.

Background: The current Management App I'm working on has an IIS application hosted in Atlanta as the frontend to a Windows SharePoint Services (WSS) document store hosted in Australia. The problem with this was that the upload would do a massive round-trip overseas and back again - so a large file (say 20MB would take well over an hour). The workaround to this was to host the page as an IFrame in our application and to host it on a WSS Server so uploads of large files could be done directly to WSS rather than via the IIS Server overseas. I used the following code in the hosted page to perform the uploads using the SharePoint 2007 object model.

/// <summary>
/// Upload documents using MOSS Object Model Directly, updates database with document information
/// After the upload of each document is complete.
/// </summary>
/// <param name="projectId"></param>
/// <param name="sourceStream"></param>
/// <param name="fileName"></param>
/// <returns></returns>
public static VoidResponse UploadDocument(UploadedFile uploadedFile,
string projectNumber, int projectId, int activityId, int documentTypeLookupId,
int constructionAuthorisationId, string subfolderPath)
{
//Core document information to be passed into MOSS
//and into the .NET Web Service to update the database
DocumentDto newDocument = new DocumentDto();
newDocument.ProjectNumber = projectNumber;
newDocument.Name = uploadedFile.GetName();
newDocument.SharepointListName = ConstructSharepointListName(newDocument.ProjectNumber);
//Snapshot path as combination of both the
newDocument.SharepointSnapshotPath = CurrentSnapshotPath;

if (string.IsNullOrEmpty(newDocument.SharepointFileRelativePath))
{
newDocument.SharepointFileRelativePath = newDocument.Name;
}

string queryOptions = FormatHelper.GetQueryOptions(projectNumber, CurrentSnapshotPath, subfolderPath);

//To Allow return of uploaded file details
VoidResponse response = new VoidResponse();

//Housing SPSite and SPWeb in using blocks so they are disposed correctly as per best practices
//in http://msdn.microsoft.com/en-us/library/aa973248.aspx
using (SPSite site = new SPSite(SharePointParameter.SharePointSiteUrl))
{
using (SPWeb web = site.OpenWeb()) //The SPSite already has the site url value.
{
try
{
web.AllowUnsafeUpdates = true; //Using AllowUnsafeUpdates because we are creating an SPWeb -
//otherwise will result in exception
//as per http://hristopavlov.wordpress.com/2008/05/16/what-you-need-to-know-about-allowunsafeupdates/
SPList list = web.Lists[newDocument.SharepointListName];

SPFileCollection fileCollection = null;

//Using query options as recommended by
//http://msdn.microsoft.com/en-us/library/microsoft.sharepoint.splist.aspx,
//same as the Web Services do in the LL framework.
SPQuery spQuery = new SPQuery();
spQuery.Query = queryOptions;
SPListItemCollection folderCollection = list.GetItems(spQuery);

//Folders are really just SPListItems with type of FilSPFileSystemObjectType.Folder
//Defensive: Folder not found exception
if (folderCollection.Count == 0) //Error condition - folder not found
throw new ConfigurationErrorsException(string.Format(Messages.Pcp_MOSS_FolderNotFoundError,
newDocument.SharepointSnapshotPath));

fileCollection = folderCollection[0].Folder.Files;

//Only perform duplicate check when it is turned on in configuration settings.
if (SharePointParameter.CheckForDuplicateFilesInMOSS)
{
//Check if file doesn't already exist.
foreach (SPFile file in fileCollection)
{
if (file.Name == newDocument.Name)
{
//Duplicate detected
response.IsSuccessful = false;
response.Messages.Add(new Message(string.Format(Messages.Pcp_DuplicateFileDetected,
file.Name)));
return response;
}
}
}

Stream fileStream = uploadedFile.InputStream;
byte[] contents = new byte[fileStream.Length];
fileStream.Read(contents, 0, (int)fileStream.Length);
fileStream.Close();

//Upload document itself. This will also give us the new Id to store in the database.
SPFile currentFile = null;

currentFile = fileCollection.Add(newDocument.Name, contents);
System.Security.Principal.WindowsImpersonationContext impersonationContext = null;

try
{
if (GetUseCurrentUserCredentialsForMOSSFlag()) //Impersonate if uploading document
{
impersonationContext =
((System.Security.Principal.WindowsIdentity)
HttpContext.Current.User.Identity).Impersonate();
}

//Upload file details to main PCPData database
PcpDataService.PcpDataService pcpDataService = new PcpDataService.PcpDataService();
pcpDataService.Url = DDKOnline.WssHostedWeb.Shared.SystemParameter.DDKOnlineDataServiceUrl;
pcpDataService.AddProjectDocument(projectId, activityId, constructionAuthorisationId,
currentFile.Item.ID.ToString(), newDocument.SharepointListName,
newDocument.SharepointSnapshotPath, newDocument.SharepointFileRelativePath,
documentTypeLookupId, newDocument.Name);
}
finally
{
if (GetUseCurrentUserCredentialsForMOSSFlag() && impersonationContext != null)
{
impersonationContext.Undo();
}
}
//Successful upload completed.
response.IsSuccessful = true;
response.Messages.Add(new Message(Messages.Pcp_SharePointUploadComplete));
}
catch (SPException spException)
{
//Error ocurred during upload
response.Errors.Add(new DDKOnline.Framework.Common.Response.Error(spException.Message));

response.IsSuccessful = false;
}
catch (Exception ex)
{
response.Errors.Add(
new Error(string.Format(Messages.Pcp_SharePointAccessFailed)));
ExceptionPolicy.HandleException(ex, SERVICE_EXCEPTION_POLICY);
response.IsSuccessful = false;
}
finally
{
if (web != null) web.AllowUnsafeUpdates = false;
}
}
}
return response;
}


/// <summary>
/// Generate query options to support storage within subfolders of current list
/// </summary>
public class FormatHelper
{
internal static string GetQueryOptions(string projectNumber, string CurrentSnapshotPath, string subFolderPath)
{
string folderPath = VirtualPathUtility.RemoveTrailingSlash(string.Format("{0}/{1}/{2}", projectNumber, CurrentSnapshotPath, subFolderPath));
string queryOptions = string.Format("<Query/><QueryOptions><Folder>{0}</Folder></QueryOptions>", folderPath);
return queryOptions;
}
}

Tuesday, 28 October 2008

How do I get the underlying type of a Generic List?

The Type.GetGenericArguments() method will let you determine the underlying type of the elements of a list object at runtime. See this article for more information.

http://msdn.microsoft.com/en-us/library/system.type.getgenericarguments.aspx

For example:


if (t.IsGenericType)
{
// If this is a generic type, display the type arguments.
//
Type[] typeArguments = t.GetGenericArguments();
Console.WriteLine("\tList type arguments ({0}):", typeArguments.Length);
    foreach (Type tParam in typeArguments)
{
// If this is a type parameter, display its
// position.
//

        if (tParam.IsGenericParameter)
{
Console.WriteLine("\t\t{0}\t(unassigned - parameter position {1})",tParam,
tParam.GenericParameterPosition);
}
else
{
Console.WriteLine("\t\t{0}", tParam);
}
}
}

Wednesday, 15 October 2008

Zach William Klein arrives!

Our fabulous new baby boy Zach William Klein ("Zacky Zack") was born 10/10/2008 10:19am with a championship weigh-in of 3.855kg. We have just brought him home from hospital and after a shocker of a night, he has calmed down to 3 sleeps a night on day 2. Welcome home Zach!

For many more first piccies see http://www.ddkonline.com/Gallery/2008_10_October_ZachKlein_Day1/gallery.html or my most recent FaceBook photo gallery.










Tuesday, 7 October 2008

How to do remote debugging with the Visual Studio 2008 Remote Debugger Service (Msvsmon.exe)

There is always a problem that will crop up on one of your servers that you just CANNOT reproduce at all. To solve pesky problems like this, you can make use of the remote debugger service of Visual Studio. What's more, you can debug without running a setup package at all on dev or production servers. Instead, you can just run msvsmon.exe from a file share without even installing anything. This will definitely keep the network guys happy!

Typically, you can get msvsmon.exe file from the following path on your development machines that already have Visual Studio 2008 installed:
C:\Program Files\Microsoft Visual Studio 9.0\Common7\IDE\Remote Debugger\x86\msvsmon.exe

When you run Msvsmon.exe, it just shows up on the server as a windows application on your server (See screenshot). You can also run it as a Windows Service so you don't need to log onto the server to start it up.




In Visual Studio on your local machine, you then just put in a fully qualified remote server name in "Attach to Process" dialog. Now you can debug the remote machine to your hearts content (make sure you're a debugger user or remote admin otherwise you will get access denied errors).

Just a reminder - if you are debugging ASP.NET on a 2003 server and above, there will be 1 w3wp.exe process for each of your application pools. It may be hard to find out which w3wp.exe process you want to debug as there may be many application pools. You can find out which w3wp.exe process is running your pool by a process of deduction (ie just by stopping the application pools you are not using. The one left is yours).

For the full goss, see http://support.microsoft.com/default.aspx/kb/910448. There is even a 17 minute tutorial on setting it up. See http://www.microsoft.com/uk/msdn/screencasts/screencast/313/visual-studio-2008-remote-debugging-with-msvsmonexe.aspx

Just keep in mind - you may be up for a long wait if the connection to your server is slow - especially if attaching a process in Atlanta! :o). There is a timeout option if you get timeout errors (you only get timeout errors in "No Authentication" mode as the "Windows Authentication" mode has an infinite timeout.)


System.Web.VirtualPathUtility to get File Names from Urls and Converting Relative Paths to Absolute Urls (without ResolveUrl)

One of the things that appeared to be lacking in ASP.NET 2.0 and above (or so I thought) is a utility library to parse and extract paths and get the file name from a Url. Here are some examples of the url parsing code that I and others have done without knowledge of this library: http://www.thejackol.com/2007/04/10/get-file-name-from-url-cnet/
and http://www.west-wind.com/Weblog/posts/154812.aspx

One class that does just this (and which most developers don't know about) is System.Web.VirtualPathUtility(). Everyone knows it's brother utility library System.Web.HttpUtility but VirtualPathUtility should be an essential part of any ASP.NET developer's toolkit. See http://msdn.microsoft.com/en-us/library/system.web.virtualpathutility.aspx for more information.