<CharlieDigital /> Programming, Politics, and uhh…pineapples

24Apr/08Off

Automating Remote GAC Installs On Build

Posted by Charles Chen

When working with SharePoint, you'll find yourself working with the GAC quite often during development.

If you've seen the light and you're working with SharePoint on a VM, one tricky problem is how to update the GAC using a post-build event.  The following is a little script that I use:

::----------------------- GAC binaries code ------------------------

:: Check if the share is available on the server

IF EXIST "\\server-name\temp" GOTO COPYFILES

GOTO SHOWNOTICE

:: Copy file to the server

:COPYFILES

ECHO Found \\server-name\temp; proceeding to copy files...

SET SN=\\server-name\temp

:: Copy the binary

XCOPY "$(TargetPath)" "%SN%" /Y /I

:: Use PsExec to execute gacutil

PATH=$(SolutionDir)Tools;%windir%\Microsoft.Net\Framework\v2.0.50727;%programfiles%\Microsoft SDKs\Windows\v6.0\Bin;%path%

psexec \\server-name -u Administrator -p password -w "c:\temp" gacutil.exe /i "$(TargetFileName)" /f

GOTO END

:SHOWNOTICE

ECHO Your VM image is not sharing the directory "c:\temp"

GOTO END

:END

ECHO Completed

The batch script makes use of Sysinternal's PsExec.  I've included the binary executable in my solution tree under the directory "Tools".

The script only has one assumption: the VM (or remote machine, really) is sharing the c:\temp directory (okay, and that the path to gacutil.exe has been added to the remote machine's PATH environment variable).  Here's a breakdown of the logic:

  1. The first step is to check if the directory exists and can be reached.  If not, we go to the end and show a notice about sharing the directory.
  2. If the directory is shared, the output dll from the build is copied to the shared directory using plain old XCOPY.
  3. Once it's copied over, we use PsExec to execute gacutil on the VM (or remote machine).  The -w argument specifies the working directory on the remote machine.

Enjoy!

Filed under: Dev No Comments
24Apr/08Off

FOR XML Needs More Love

Posted by Charles Chen

I'm constantly amazed by the number of developers who have never worked with FOR XML EXPLICIT and the new FOR XML PATH.  If I were designing data access, it would be my go-to commands for building queries for complex data structures (nested DataReaders?  yuck!).


In the past, to support paging using FOR XML EXPLICIT queries took tons of lines to accomplish (although there is something about the whole explicitness that makes it surprisingly legible).  Now with the fancy pants ROW_NUMBER function in SQL along with CTEs, a hundred line query can be written with maybe 15-20 lines.


Here's a simple example that you can copy+paste and run:


/* 
Demonstrates usage of ROW_NUMBER and FOR XML PATH to create
pageable XML results queries.

In this case, the key is to page only on the Route objects.
*/

--// Mock Route table
DECLARE @Route TABLE (
Id int,
Title varchar(100)
)

--// Mock Step table
DECLARE @Step TABLE (
Id int,
RouteId int,
Title varchar(100),
Sequence int
)

--// Insert mock data
INSERT INTO @Route VALUES (1, 'Route 1')
INSERT INTO @Route VALUES (2, 'Route 2')
INSERT INTO @Route VALUES (3, 'Route 3')
INSERT INTO @Route VALUES (4, 'Route 4')
INSERT INTO @Route VALUES (5, 'Route 5')

--// Route 1 Steps
INSERT INTO @Step VALUES (1, 1, 'Step 1.1', 1)
INSERT INTO @Step VALUES (2, 1, 'Step 1.2', 2)
INSERT INTO @Step VALUES (3, 1, 'Step 1.3', 3)
INSERT INTO @Step VALUES (4, 1, 'Step 1.4', 4)
INSERT INTO @Step VALUES (5, 1, 'Step 1.5', 5)

--// Route 2 Steps
INSERT INTO @Step VALUES (6, 2, 'Step 2.1', 1)
INSERT INTO @Step VALUES (7, 2, 'Step 2.2', 2)
INSERT INTO @Step VALUES (8, 2, 'Step 2.3', 3)

--// Route 3 Steps
INSERT INTO @Step VALUES (9, 3, 'Step 3.1', 1)

--// Route 4 Steps
INSERT INTO @Step VALUES (10, 4, 'Step 4.1', 1)

--// Route 5 Steps
INSERT INTO @Step VALUES (11, 5, 'Step 5.1', 1)
INSERT INTO @Step VALUES (12, 5, 'Step 5.2', 2)
INSERT INTO @Step VALUES (13, 5, 'Step 5.3', 3)
INSERT INTO @Step VALUES (14, 5, 'Step 5.4', 4)

/*
Define the page size.
-- Add sorting and ordering later
*/

DECLARE @PageSize int
DECLARE @CurrentPage int

SET @CurrentPage = 0
SET @PageSize = 3

/*
Calculate starting and ending row.
*/
DECLARE @StartIndex int
DECLARE @EndIndex int

SET @StartIndex = @CurrentPage * @PageSize
SET @EndIndex = @StartIndex + @PageSize

; --// Need to terminate with a semicolon for CTE
/*
Perform core XML select
*/

WITH Routes AS (
SELECT
*,
ROW_NUMBER() OVER (ORDER BY Id) AS 'RowNumber'
FROM
@Route
)
SELECT
Routes.Id AS "@Id",
Routes.Title AS "@Title",
(
SELECT
Step.Id AS '@Id',
Step.Title AS '@Title'
FROM
@Step AS Step
WHERE
Step.RouteId = Routes.Id
ORDER BY
Step.Sequence ASC
FOR XML PATH('Step'), TYPE
) AS 'Steps'
FROM
Routes
WHERE
RowNumber > @StartIndex AND RowNumber <= @EndIndex --// BETWEEN Results in improper paging
FOR XML PATH('Route'), ROOT('Routes')


What's great about this is that if your object model is properly designed, it's just a matter of deserializing the XML (using precompiled serialization binaries, of course) to rehydrate your data model.


In this case, the output XML looks like this:


<Routes>
<Route Id="1" Title="Route 1">
<Steps>
<Step Id="1" Title="Step 1.1" />
<Step Id="2" Title="Step 1.2" />
<Step Id="3" Title="Step 1.3" />
<Step Id="4" Title="Step 1.4" />
<Step Id="5" Title="Step 1.5" />
</Steps>
</Route>
<Route Id="2" Title="Route 2">
<Steps>
<Step Id="6" Title="Step 2.1" />
<Step Id="7" Title="Step 2.2" />
<Step Id="8" Title="Step 2.3" />
</Steps>
</Route>
<Route Id="3" Title="Route 3">
<Steps>
<Step Id="9" Title="Step 3.1" />
</Steps>
</Route>
</Routes>

Next, you'll need some simple code to deserialize the XML to make it useful:


using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Xml.Serialization;

namespace XmlDeserialization {
internal class Program {
private static void Main(string[] args) {
string xml = ...; // XML string here

RouteList routes = new RouteList(xml);

Console.Out.WriteLine(routes.Count);

foreach(Route route in routes) {
Console.Out.WriteLine(" + Route: {0}", route.Id);

foreach(Step step in route.Steps) {
Console.Out.WriteLine(" + Step: {0}", step.Id);
}
}
}
}

public abstract class XmlDeserializingList<Titem> : List<Titem> {
protected XmlDeserializingList() { }

protected XmlDeserializingList(string xml) {
StringReader reader = new StringReader(xml);

XmlSerializer serializer = new XmlSerializer(GetType());
XmlDeserializingList<Titem> items = (XmlDeserializingList<Titem>)serializer.Deserialize(reader);

AddRange(items);
}
}

[XmlRoot("Routes")]
public class RouteList : XmlDeserializingList<Route> {
public RouteList() {}

public RouteList(string xml) : base(xml) {

}
}

[Serializable]
public class Route {
private int id;
private string title;
private Collection<Step> steps;

[XmlAttribute]
public int Id {
get { return id; }
set { id = value; }
}

[XmlAttribute]
public string Title {
get { return title; }
set { title = value; }
}

[XmlArray("Steps"), XmlArrayItem("Step", typeof(Step))]
public Collection<Step> Steps {
get { return steps; }
set { steps = value; }
}
}

[Serializable]
public class Step {
private int id;
private string title;

[XmlAttribute]
public int Id {
get { return id; }
set { id = value; }
}

[XmlAttribute]
public string Title {
get { return title; }
set { title = value; }
}
}
}

It might even be useful to add some abstract methods (and properties to support it) to GetNextPage()

Filed under: SQL Server No Comments
24Apr/08Off

Nuking SharePoint Styles For Layout Page Applications

Posted by Charles Chen

SharePonit layout apps (as I call them) are the missing link in most developer's understanding of SharePoint and why it seems like it can be a pain in the butt as a development platform.  As I've discussed previously, SharePoint development only deviates from regular ASP.NET development ever so slightly.  A bit of creativity and ingenuity easily overcomes most hurdles.


As I'm doing development in the area, I'd like to help shed some light on the mystery of it all and show how developers and SharePoint can work together in harmony :-)


One of the first steps to making happy with SharePoint when working with layout pages is to nuke the SharePoint CSS which creates weird padding around the main work area.  I'm sure it may have some purpose, but it's irrelevant for us at the moment.


Let's take a look.


In the following screen, I've added a <div/> element with a one pixel, black border with width and height set to 100% (this is in IE7 and it looks about the same in FF2).



Notice the fat white padding on the top and the right.  This is useless and ugly.  Not only that, we didn't acheive the 100% height that we'd like to.  We can nuke it with some CSS:


/*--- override WSS styles ---*/
body { height: 100%; }
.ms-formareaframe { padding: 2px 4px 4px 4px; background: #fff; height:100%; }
.ms-propertysheet { height: 100%; }
#onetidMainBodyPadding { height: 0px; }
#onetidMainBodyPadding img { height: 0px; display: none; }
#onetidYPadding { width: 0px; }
#onetidYPadding img { width: 0px; display: none; }

And here's the result:



Bingo!  It looks kind of the same in FF2 (FF2 overdraws the 100% declaration and cuts off the bottom and right borders; this does require some CSS hackery to fix).


But the big picture is that now we have a blank slate with which to integrate with SharePoint.  You get all of the benefits of SharePoint authentication, document management, profiling, and so on just by putting your ASP.NET application into SharePoint as a layout page.  Development is easier than it would seem since the package, during development, only has to be deployed once.  Otherwise, all of the files can be deployed to SharePoint using an automated XCOPY or manually.  In some cases, for example working with ASPX page layout, the page can be accessed directly via file share (this is how I do all of my layout).



For more info on creating layout apps, check out the following articles on packaging SharePoint solutions:


Filed under: SharePoint No Comments