Thursday, November 19, 2009

SharePoint 2007 – FIVE very helpful (yet unsupported) stored procedures for querying membership data

I do quite a bit of SharePoint development and have always relied on the object model or web services. Unfortunately, I have ran into situations where using either option was less than optimal because obtaining the data I needed might cause performance problems since they required me to get recursive data. One of these situations is pulling membership data out of SharePoint for reporting or authentication purposes. Some of these examples were gleaned off of other examples I found on the web, but were cleaned up or added additional information. In all cases I have ensured that they on SELECT data, and always use NOLOCK for all tables.

1. Select Groups By URL

USE [WSS_Content]

CREATE PROCEDURE [dbo].[usp_SelectGroupsByUrl]

@url NVARCHAR(255)

AS

BEGIN

SELECT Groups.ID AS "GroupID", Groups.Title AS "GroupTitle", Webs.Title AS WebTitle, Webs.FullURL AS WebURL, Roles.Title AS "RoleTitle"

FROM RoleAssignment WITH (NOLOCK)

INNER JOIN Roles WITH (NOLOCK) ON Roles.SiteId = RoleAssignment.SiteId

AND Roles.RoleId = RoleAssignment.RoleId

INNER JOIN Groups WITH (NOLOCK) ON Groups.SiteId = RoleAssignment.SiteId

AND Groups.ID = RoleAssignment.PrincipalId

INNER JOIN Sites WITH (NOLOCK) ON Sites.Id = RoleAssignment.SiteId

INNER JOIN Perms WITH (NOLOCK) ON Perms.SiteId = RoleAssignment.SiteId

AND Perms.ScopeId = RoleAssignment.ScopeId

INNER JOIN Webs WITH (NOLOCK) ON Webs.Id = Perms.WebId

WHERE ((Webs.FullUrl = @url) OR (@url IS NULL))

ORDER BY

Groups.Title, Webs.Title, Perms.ScopeUrl

END

Sample Query

EXEC usp_SelectGroupsByUrl 'Sites/HR/Pay'

Results

GroupID

GroupTitle

WebTitle

WebURL

RoleTitle

71

Manager

Accounting

Sites/HR/PAY

View Only

74

Acct Sr Manager

Accounting

Sites/HR/PAY

Full Control

75

Member

Accounting

Sites/HR/PAY

View Only

76

Viewer

Accounting

Sites/HR/PAY

View Only

2. Select Groups By URL and User

USE [WSS_Content]

CREATE PROCEDURE [dbo].[usp_SelectGroupsByUrlUser]

@login NVARCHAR(255),

@url NVARCHAR(255)

AS

BEGIN

SELECT G.ID AS "GroupID", G.Title AS "GroupTitle", W.Title AS WebTitle, W.FullURL AS WebURL, R.Title AS "RoleTitle"

FROM RoleAssignment AS RA WITH (NOLOCK)

INNER JOIN Roles AS R WITH (NOLOCK) ON R.SiteId = RA.SiteId

AND R.RoleId = RA.RoleId

INNER JOIN Groups AS G WITH (NOLOCK) ON G.SiteId = RA.SiteId

AND G.ID = RA.PrincipalId

INNER JOIN Sites AS S WITH (NOLOCK) ON S.Id = RA.SiteId

INNER JOIN Perms AS P WITH (NOLOCK) ON P.SiteId = RA.SiteId

AND P.ScopeId = RA.ScopeId

INNER JOIN Webs AS W WITH (NOLOCK) ON W.Id = P.WebId

WHERE ((W.FullUrl = @url) OR (@url IS NULL))

AND G.ID IN

(

SELECT Groups.ID FROM GroupMemberShip WITH (NOLOCK)

INNER JOIN Groups ON GroupMembership.GroupID = Groups.ID

INNER JOIN UserInfo ON GroupMembership.MemberID = UserInfo.tp_ID

WHERE ((tp_Login = @login) OR (@login IS NULL))

)

ORDER BY

G.Title, W.Title, P.ScopeUrl

END

Sample Query

EXEC usp_SelectGroupsByUrlUser 'acmedomain\jdoe', 'Sites/HR/Pay'

Results

GroupID

GroupTitle

WebTitle

WebURL

RoleTitle

74

Sr Manager

Accounting

Sites/HR/PAY

Full Control

3. Select Groups By User Login

USE [WSS_Content]

CREATE PROCEDURE [dbo].[usp_SelectGroupsByUserLogin]

@login NVARCHAR(255)

AS

BEGIN

SELECT DISTINCT Groups.ID AS "GroupID", Groups.Title AS "GroupTitle"

FROM GroupMemberShip WITH (NOLOCK)

INNER JOIN Groups WITH (NOLOCK) ON GroupMembership.GroupID = Groups.ID

INNER JOIN UserInfo WITH (NOLOCK) ON GroupMembership.MemberID = UserInfo.tp_ID

WHERE ((tp_Login = @login) OR (@login IS NULL))

ORDER BY Groups.Title

END

Sample Query

EXEC usp_SelectGroupsByUserLogin 'acmedomain\jdoe'

Results

GroupID

GroupTitle

74

Sr Manager

52

Corporate Viewer

14

HR Team Member

4. Select Users By Group

USE [WSS_Content]

CREATE PROCEDURE [dbo].[usp_SelectUsersByGroup]

@groupTitle NVARCHAR(255)

AS

BEGIN

SELECT dbo.UserInfo.tp_ID AS UserID, dbo.UserInfo.tp_Title AS UserTitle, dbo.UserInfo.tp_Login AS UserLogin, dbo.UserInfo.tp_Email AS UserEmail, dbo.Groups.ID AS GroupsID, dbo.Groups.Title AS GroupsTitle

FROM UserInfo WITH (NOLOCK)

INNER JOIN GroupMembership WITH (NOLOCK) ON UserInfo.tp_ID = GroupMembership.MemberID

INNER JOIN Groups WITH (NOLOCK) ON GroupMembership.GroupID = Groups.ID

WHERE ((dbo.Groups.Title = @groupTitle) OR (@groupTitle IS NULL))

ORDER by dbo.UserInfo.tp_Login

END

Sample Query

EXEC usp_SelectUsersByGroup 'HR Team Member'

Results

UserID

UserTitle

UserLogin

UserEmail

GroupsID

GroupsTitle

23

Doe, John

acmedomain\jdoe

jdoe@acmedomain.com

14

HR Team Member

20

Doe, Jane

acmedomain\jadoe

jadoe@acmedomain.com

14

HR Team Member

2

Smith, Bob

acmedomain\bsmith

bsmith@acmedomain.com

14

HR Team Member

24

Yamamoto, Kazue

acmedomain\kyamamoto

kyamamoto@acmedomain.com

14

HR Team Member

46

Nakamura, Ichiro

acmedomain\inakamura

inakamura@acmedomain.com

14

HR Team Member

5 Select Users By Groups

USE [WSS_Content]

CREATE PROCEDURE [dbo].[usp_SelectUsersByGroups]

@groupTitles VARCHAR(1000)

AS

BEGIN

DECLARE @idoc INT

EXEC sp_xml_preparedocument @idoc OUTPUT, @groupTitles

SELECT dbo.UserInfo.tp_ID AS UserID, dbo.UserInfo.tp_Title AS UserTitle, dbo.UserInfo.tp_Login AS UserLogin, dbo.UserInfo.tp_Email AS UserEmail, Groups.Title AS GroupTitle

FROM UserInfo WITH (NOLOCK)

INNER JOIN GroupMembership WITH (NOLOCK) ON UserInfo.tp_ID = GroupMembership.MemberID

INNER JOIN Groups WITH (NOLOCK) ON GroupMembership.GroupID = Groups.ID

JOIN OPENXML(@idoc, '/Root/Group', 0) WITH (Name VARCHAR(100)) AS g

ON dbo.Groups.Title = g.Name

ORDER by dbo.Groups.Title, dbo.UserInfo.tp_Title

END

Sample Query

EXEC usp_SelectUsersByGroups '<Root><Group Name="HR Team Member"></Group><Group Name="Acct Sr Managers"></Group></Root>'

Results

UserID

UserTitle

UserLogin

UserEmail

GroupsID

GroupsTitle

23

Doe, John

acmedomain\jdoe

jdoe@acmedomain.com

14

HR Team Member

20

Doe, Jane

acmedomain\jadoe

jadoe@acmedomain.com

14

HR Team Member

2

Smith, Bob

acmedomain\bsmith

bsmith@acmedomain.com

14

HR Team Member

24

Yamamoto, Kazue

acmedomain\kyamamoto

kyamamoto@acmedomain.com

14

HR Team Member

46

Nakamura, Ichiro

acmedomain\inakamura

inakamura@acmedomain.com

14

HR Team Member

7

Horowitz, Rick

acmedomain\rhorowitz

rhorowitz@acmedomain.com

15

Acct Sr Managers

3

Davis, Jiro

acmedomain\jdavis

jdavis@acmedomain.com

15

Acct Sr Managers

Silverlight - Get the top-level UserControl root element using two different approaches

I recently found a situation where I needed to get the root element of a Silverlight control. Unfortunately, Silverlight controls only have a Parent member and not a Top or Root member. If your control is embedded in multiple controls, you cannot easily get the root and will probably end up writing some ugly casting code that looks like this.

DO NOT USE THIS CODE...KEEP READING!

private UserControl GetParent()
{
return (UserControl)((Grid)((Border)((StackPanel)((UserControl)((Grid)((StackPanel)this.Parent).Parent).Parent).Parent).Parent).Parent).Parent;
}

The problem with this code (besides being ugly) is that if you ever change the containing hierarchy, you’re going to end up having to update this method. After finding a couple of different posts that didn’t work for me, I finally came up with two possible solutions:

Solution 1 – Implement a recursive lookup that finds the top parent and returns it as a UserControl.

1.) Add a method extensions that returns the parent UserControl.

public static class SilverlightExtensions
{
public static UserControl GetRoot(this FrameworkElement child)
{
var parent = child.Parent as FrameworkElement;
if (parent == null)
if (child is UserControl)
{
return (UserControl)child;
}
else
{
throw new Exception("The root element is an unexpected type. The root element should be a UserControl.");
}
return parent.GetRoot();
}}

2.) Access the parent by using the method extension:

this.GetRoot().Resources["SampleText"] = "x y z";
((TextBlock)this.GetRoot().FindName("TitlePage")).Text = "New Title Page";

Solution 2 – Add a property to the child control that references the parent. (Recommended)

This is less expensive than a recursive loop, but you do have to always remember step 1 or else it will not work.

1.) Add a MyParent property of type UserControl in the child control’s class.

public UserControl MyParent { get; set; }
2.) In the parent control’s constructor, add a line that populates the child control’s MyParent property with itself.

public ParentUserControl()
{
InitializeComponent();

((ChildUserControl)this.FindName("childControl")).MyParent = (UserControl)this;
}
3.) Access the parent root using the MyParent property.

MyParent.Resources["SampleText"] = "x y z";
((TextBlock)MyParent.FindName("TitlePage")).Text = "New Title Page";

Note: You should be able to take a similar approach with WPF. One other alternative is to raise an event to the parent UserControl. Unfortunately, I haven’t seen any easy or clear-cut examples on how to do this.

Tuesday, November 3, 2009

Silverlight 3 + SharePoint: A wrapper web part that makes SharePoint user information available to Silverlight.

A few weeks ago I posted a “quick and dirty” way to integrate a Silverlight application into SharePoint. This was great for basic apps, but has no way of accessing a user’s information. After reviewing a number of products and a bunch of different approaches, I believe I have come up with a solution that works.

Disclaimer: This is mostly code and I haven’t included too many comments. This post assumes you are fairly familiar with Silverlight and SharePoint development. I use WSPBuilder for deploying my SharePoint web parts.

Web Part Code

This can also work with a standard ASP.NET Web Part, but you will need to derive from the System.Web.UI.WebControls.WebParts.WebPart base class and change SPContext references to HttpContext.Current.User.Identity.

Note: Instead of a bunch of crazy string concatenation, you could build all of the objects individually and then set their respective properties. You may also opt to omit or edit the style and script text.

using System;
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Text;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using Microsoft.SharePoint;

namespace Acme.SharePoint.WebPart
{
[Guid("B7BD8441-FA27-43ab-8F92-708094D3FC28")]
public class SilverlightHostWp : Microsoft.SharePoint.WebPartPages.WebPart
{
[Personalizable(PersonalizationScope.Shared), WebBrowsable(true), Category("Silverlight Settings"), WebDisplayName("XAP Path")]
public string XapPath { get; set; }

[Personalizable(PersonalizationScope.Shared), WebBrowsable(true), Category("Silverlight Settings"), WebDisplayName("Parent Width")]
public string ParentWidth { get; set; }

[Personalizable(PersonalizationScope.Shared), WebBrowsable(true), Category("Silverlight Settings"), WebDisplayName("Parent Height")]
public string ParentHeight { get; set; }

[Personalizable(PersonalizationScope.Shared), WebBrowsable(true), Category("Silverlight Settings"), WebDisplayName("Parent Border Enabled")]
public bool ParentBorderEnabled { get; set; }

public SilverlightHostWp()
{
this.ExportMode = WebPartExportMode.All;
}

protected override void CreateChildControls()
{
try
{
base.CreateChildControls();
if (string.IsNullOrEmpty(XapPath))
{
this.Controls.Add(new LiteralControl() { Text = "Update \"XAP Path\" in Silverlight Settings." });
}
else
{
SPUser user = SPContext.Current.Web.CurrentUser;
string fullName = user.Name.Replace(", ", "_"); //Silverlight expects a comma delimited value. This is parsed back in the Silverlight Appplication_Startup method. See below.
string loginName = user.LoginName.ToLower();
string email = user.Email.ToLower();
string userParams = "FullName=" + fullName + ",LoginName=" + loginName + ",Email=" + email;
if (string.IsNullOrEmpty(ParentHeight))
{
ParentHeight = "100"; //Defaults to 100 since otherwise nothing will be visible.
}
if (string.IsNullOrEmpty(ParentWidth))
{
ParentWidth = "100"; //Defaults to 100 since otherwise nothing will be visible.
}
StringBuilder styleScriptText = new StringBuilder();
styleScriptText.Append("<style type=\"text/css\">" +
"html, body" +
" {" +
" height: 100%;" +
" overflow: auto;" +
" }" +
"body" +
" {" +
" padding: 0;" +
" margin: 0;" +
" }" +
"</style>" +
"<script type=\"text/javascript\">" +
" function onSilverlightError(sender, args) {" +
" var appSource = \"\";" +
" if (sender != null && sender != 0) {" +
" appSource = sender.getHost().Source;" +
" }" +
" var errorType = args.ErrorType;" +
" var iErrorCode = args.ErrorCode;" +
" if (errorType == \"ImageError\" || errorType == \"MediaError\") {" +
" return;" +
" }" +
" var errMsg = \"Unhandled Error in Silverlight Application \" + appSource + \"\n\";" +
" errMsg += \"Code: \" + iErrorCode + \" \n\";" +
" errMsg += \"Category: \" + errorType + \" \n\";" +
" errMsg += \"Message: \" + args.ErrorMessage + \" \n\";" +
" if (errorType == \"ParserError\") {" +
" errMsg += \"File: \" + args.xamlFile + \" \n\";" +
" errMsg += \"Line: \" + args.lineNumber + \" \n\";" +
" errMsg += \"Position: \" + args.charPosition + \" \n\";" +
" }" +
" else if (errorType == \"RuntimeError\") {" +
" if (args.lineNumber != 0) {" +
" errMsg += \"Line: \" + args.lineNumber + \" \n\";" +
" errMsg += \"Position: \" + args.charPosition + \" \n\";" +
" }" +
" errMsg += \"MethodName: \" + args.methodName + \" \n\";" +
" }" +
" throw new Error(errMsg);" +
" }" +
"</script>");
StringBuilder objectText = new StringBuilder();
objectText.Append(styleScriptText.ToString() +
"<div id=\"silverlightControlHost\" style=\"width:" + ParentWidth + "; height:" + ParentHeight + ";");
if (ParentBorderEnabled)
{
objectText.Append(" border: silver 1px solid; ");
}
objectText.Append("\"><object data=\"data:application/x-silverlight,\" type=\"application/x-silverlight-2\"" +
" width=\"100%\" height=\"100%\">" +
"<param name=\"source\" value=\"" + XapPath + "\" />" +
"<param name=\"onerror\" value=\"onSilverlightError\" />" +
"<param name=\"background\" value=\"white\" />" +
"<param name=\"minRuntimeVersion\" value=\"3.0.40624.0\" />" +
"<param name=\"autoUpgrade\" value=\"true\" />" +
"<param name=\"initParams\" value=\"" + userParams + "\" />" +
"<a href=\"http://go.microsoft.com/fwlink/?LinkID=149156&v=3.0.40624.0\" style=\"text-decoration: none;\">" +
"<img src=\"http://go.microsoft.com/fwlink/?LinkId=108181\" alt=\"Get Microsoft Silverlight\" style=\"border-style: none\" />" +
"</a>" +
"</object>" +
"<iframe id='_sl_historyFrame' style='visibility: hidden; height: 0; width: 0; border: 0px'>" +
"</iframe>" +
"</div>");
Literal literal = new Literal()
{
Text = objectText.ToString()
};
this.Controls.Clear();
this.Controls.Add(literal);
}
}
catch (Exception ex)
{
//Handle error…


            }
}
}
}


App.xaml.cs Application_Startup method



private void Application_Startup(object sender, StartupEventArgs e)
{
if (e.InitParams != null)
{
foreach (var item in e.InitParams)
{

if (item.Key == "FullName")
{
Resources.Add(item.Key, item.Value.Replace("_", ", ")); //Silverlight expected InitParams to be a comma delimited value, so this was changed to an underscore in the Web Part code. See above.
}
else
{
Resources.Add(item.Key, item.Value);
}
}
}
this.RootVisual = new UserNameControl();
}


Accessing App resource properties within a User Control



In my case, I have created a TextBox that toggles between a prompt to sign and their full name.



public class UserNameTextBox : TextBox
{
protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
{
if (Text == "Click To Sign!")
{
if (App.Current.Resources.Contains("FullName"))
{
Text = App.Current.Resources["FullName"].ToString();
Tag = App.Current.Resources["LoginName"].ToString();
}
}
else
{
Text = "Click To Sign!";
}
}
}


XAML



Include something like the following in your UserControl tag.



xmlns:local="clr-namespace:Acme.SilverlightControls"




Add your control XAML like this.




<local:UserNameTextBox Name="UserName" Text="Click To Sign!" Background="Yellow" Width="200" Height="100"></local:UserNameTextBox>



Screenshots



For simplicity, I have added the XAP file and a Web Part Page to the same Document Library. Your path will need to be a relative or absolute URI otherwise. When you add the web part to the page, you have four properties to pick from. I included a property for Parent Border Enabled so that you can have a visual way to tweak the width and height.



image



When the user clicks, their full name appears.



image

Monday, October 5, 2009

Integrate Silverlight with SharePoint – The quick and dirty way

This is ridiculously easy, even easier than deploying a SharePoint web part. Basically you add a Silverlight generated xap file to a SharePoint Document Library, add a Content Editor Web Part to a page, then add object tags in the source that point to the xap file. Integrating it into a web part as a feature will not be much more difficult. Building connectable web parts will be another story and may require the DOM bridge.

Note: I got this tip from Tim Heuer in a Silverlight Deep Dive session hosted by the San Diego .NET User Group. It almost sounded to easy, so I had to verify this for myself.

1. Create your Silverlight application and compile. My page has a simple TextBlock with the text set to Hello World!.

image

image

2. Create a Document Library and Site or Web Part Page in SharePoint.

3. Upload your Silverlight xap file from the ClientBin directory in your Silverlight web project to the Document Library you created in SharePoint.

(Note: I created a Document Library called Silverlight Test and added a Web Part Page called “Test” and added my xap file called “HelloWorld”.

image

4. Open the Web Part Page and add a Content Editor Web Part to the page.

5. Click on Edit –> Modify Web Part, and click the Source Editor… button.

6. Open the sample html page that was generated in the Silverlight web project. Copy and paste everything from the style, script, and div tags into the Content Editor Web Part Source Editor.

image

7. Update the param with the name “source” and make sure the path used in value is relative to where the web part page is. In my case they were in the same Document Library.

image

8. Click Save and you’re done!

image

Sunday, September 27, 2009

Microsoft IntelliType 7.0 for Windows 7 Volume OSD

I own both a Microsoft keyboard and mouse, but you can download this and take advantage of the volume control even if you don't have a Microsoft Keyboard. I like knowing what my volume is at any given moment or have a visual clue when I mute or un-mute.







Wednesday, August 26, 2009

WSPBuilder – Adding multiple web parts to a single feature

One small limitation I found with WSPBuilder is that there is not a default way of adding new web parts to existing features. Fortunately, there is an easy way to do this.

1. Create a WSPBuilder project in Visual Studio.

2. Add a new WSPBuilder Web Part Feature to the project. (Ctrl+Shift+A)

image 

3. Now add another Web Part Feature. At this point, your project should look something like this:

image

4. Drag the web part you want combined into the parent folder of the other feature and then delete the feature folder from where you dragged it.

image

5. Open the elements.xml file and add another File node with the information from the second web part.

<?xml version="1.0" encoding="utf-8" ?>
<
Elements xmlns="http://schemas.microsoft.com/sharepoint/">
<
Module Name="WebPartPopulation" Url="_catalogs/wp" RootWebOnly="TRUE">
<
File Url="WebPartFeature1.webpart" Type="GhostableInLibrary">
<
Property Name="Group" Value="MyGroup"></Property>
<
Property Name="QuickAddGroups" Value="MyGroup" />
</
File>
<
File Url="WebPartFeature2.webpart" Type="GhostableInLibrary">
<
Property Name="Group" Value="MyGroup"></Property
>
<
Property Name="QuickAddGroups" Value="MyGroup"
/>
</
File
>
</
Module>
</
Elements>







6. Open the feature.xml file and add an ElementFile entry for the second web part.



<?xml version="1.0" encoding="utf-8"?>
<
Feature Id="a942cf13-5e3b-48e6-9862-6f98dc931375"
Title="WebPartFeature1"
Description="Description for WebPartFeature1"
Version="12.0.0.0"
Hidden="FALSE"
Scope="Web"
DefaultResourceFile="core"
xmlns="http://schemas.microsoft.com/sharepoint/">
<
ElementManifests>
<
ElementManifest Location="elements.xml"/>
<
ElementFile Location="WebPartFeature1.webpart" />
<
ElementFile Location="WebPartFeature2.webpart" />
</
ElementManifests>
</
Feature>

7. You can now build the project (Right-click Project –> Build), build the WSP (Right-click Project –> WSPBuilder –> Build WSP) and deploy the feature (Right-click Project –> WSPBuilder –> Deploy). If you look in hive under TEMPLATE\Features\YourFeature, you will see that both web parts are there.

I’m digging WSPBuilder for SharePoint project deployment!

After playing around with this tool for a couple days, I’m think I’m sold on it. I’m going to start refactoring all of my SharePoint projects this way. Once you get it figured out, it makes deployment and migration way easier. The nice thing is that it uses standard SharePoint Feature deployment concepts. I also see a lot of recent builds on CodePlex and blogposts, so I think it is pretty well supported unlike other tools I found like STSDEV or SPDeploy.

I included a sample project I made, but you will need to install the WSP Builder Extensions download. Of course, it will need to be on a machine that has SharePoint and Visual Studio. Of course, hopefully this will all be a moot point with SharePoint 2010.

Download

http://www.codeplex.com/wspbuilder

Documentation

http://keutmann.blogspot.com/2009/04/wspbuilder-documentation.html

DEV Screenshot

clip_image001

clip_image002

Once you are done, you can easily create a setup application! It makes remote deployment much easier as you just have a setup.exe.

image

image

image

image

You do have to perform this one step, but maybe there’s some way to automate this. Go to http://[portalname]/_layouts/newdwp.aspx

image

That’s it. Web parts are ready to be added.

image

Notes:

Changing to a Web Application

The first thing I did was change it from a class library to a web application project. That way I could debug it using user controls in VS before deploying. The link will provide further instructions. Basically the ascx page needs to inherit from the assembly, not the code behind.

1.)           Install WSPBuilder.

2.)           Open Visual Studio and create a WSPBuilder Project.

3.)           Right-click on the project and click “Unload Project”.

4.)           Using notepad, open the csproj file for the project, and replace the following:

<ProjectTypeGuids>{349c5851-65df-11da-9384-00065b846f21};{fae04ec0-301f-11d3-bf4b-00c04f79efbc}</ProjectTypeGuids>

5.)           Save the file.

6.)           Right-click the project from within Visual Studio and click “Reload Project”.

There are a few gotchas that I found when doing this. One is that debugging doesn’t work right away and you must create a new entry in the Configuration Manager for it to work. You can can access this from the Build menu in Visual Studio. I don’t really know why this is the case, but it works and you can even change your configuration back to Debug or Release when you are done. Another is that if you want to debug and have installed it on that server you are going to want to uninstall (right-click project –> WSPBuilder –> Uninstall). Then rebuild it before your hit F5. You also will need to toggle between the following control directives. I’m sure there’s a better way to do this.

Using User Controls (ascx) for Development

Loading a User Control from your web part code

The key here is to update your web part class CreateChildControls method to look something like this:

base.CreateChildControls();
UserControl uc = (UserControl)Page.LoadControl(@"~/_controltemplates/WspUserControl1.ascx");
this.Controls.Add(uc);


Control Directives for debugging and deployment



During Debugging



<%@ Control Language="C#" AutoEventWireup="true" CodeBehind="WebUserControl1.ascx.cs" Inherits="WSPBuilderProject1.WspUserControl1" %>



Before Deployed



<%@ Control Language="C#" AutoEventWireup="true" Inherits="WSPBuilderProject1.WspUserControl1, WSPBuilderProject1, Version=1.0.0.0, Culture=neutral, PublicKeyToken=fa545836959293eb" %>



WSPBuilder Links



Wrap a User Control inside a Web Part using WSPBuilder



http://oidatsmyleg.wordpress.com/2009/07/23/wrap-a-user-control-inside-a-web-part-using-wspbuilder/



Wrap a User Control inside a Web Part using WSPBuilder



http://oidatsmyleg.wordpress.com/2009/07/23/wrap-a-user-control-inside-a-web-part-using-wspbuilder/



WSS: Development – Quick Start with WSPBuilder



http://rasor.wordpress.com/2008/10/12/wss-dev-quickstart/



WSS Dev: HowTo debug a WebPart



http://rasor.wordpress.com/2008/10/24/wss-howto-debug-a-webpart/

Thursday, August 6, 2009

Compare the content of two files

Use the following snippet:

using System.IO;

. . .


private static bool FileCompare(string filePath1, string filePath2)
{
    int file1byte;
    int file2byte;
    FileStream fileStream1;
    FileStream fileStream2;
    if (filePath1 == filePath2)
    {
        return true;
    }
    FileInfo fileInfo = new FileInfo(filePath1);
    fileInfo.IsReadOnly = false;
    fileInfo = new FileInfo(filePath2);
    fileInfo.IsReadOnly = false;
    fileStream1 = new FileStream(filePath1, FileMode.Open);
    fileStream2 = new FileStream(filePath2, FileMode.Open);
    if (fileStream1.Length != fileStream2.Length)
    {
        fileStream1.Close();
        fileStream2.Close();
        return false;
    }
    do
   
{
        file1byte = fileStream1.ReadByte();
        file2byte = fileStream2.ReadByte();
    }
    while ((file1byte == file2byte) && (file1byte != -1));
    fileStream1.Close();
    fileStream2.Close();
    return ((file1byte - file2byte) == 0);
}

 

Kill a Windows Service or Process Thread

Use one of the following code snippets:

using System.Diagnostics;

. . .

private static void KillProcessThread(string processName)
{
    Process[] processes;
    processes = Process.GetProcessesByName(processName);
    foreach(Process proc in processes)
    {
        proc.Kill();
    }
}

 

using System.ServiceProcess;

. . .

private static void KillWindowsService(string serviceName)
{
        ServiceController sc = new ServiceController();
        sc.ServiceName = serviceName;
        sc.Stop();
}

Get a recursive list of files in a given directory

Here is a simple code snippet:

using System.Collections.Generic;
usingSystem.IO;
usingSystem.Text;

. . .

static void Main(string[] args)
{
List<FileSystemInfo> fileInfo = GetDirectoryInfo(@"c:\temp", true);
StringBuilder directoryCsv = new StringBuilder();
directoryCsv.Append("Name,Full Name,Parent Folder,Creation Time,Last Access Time,Last Write Time,Length, Extension,Attributes\r\n");
foreach (FileSystemInfo di in fileInfo)
{
if (di.GetType() == typeof(FileInfo))
{
directoryCsv.Append(string.Format("{0},{1},{2},{3},{4},{5},{6},{7},{8}\r\n", di.Name, di.FullName, di.FullName.Replace(di.Name, ""), di.CreationTime, di.LastAccessTime, di.LastWriteTime, ((FileInfo)di).Length.ToString(), di.Extension, di.Attributes.ToString()));
}
else
{
directoryCsv.Append(string.Format("{0},{1},{2},{3},{4},{5},{6},{7},{8}\r\n", di.Name, di.FullName, di.FullName.Replace(di.Name, ""), di.CreationTime, di.LastAccessTime, di.LastWriteTime, "", "", di.Attributes.ToString()));
}
}
SaveCsvFile(directoryCsv.ToString(), @"c:\temp\test.csv");
}
public List<FileSystemInfo> GetDirectoryInfo(string directoryPath, bool includeDirectoryInfo)
{
List<FileSystemInfo> directoryList = new List<FileSystemInfo>();
DirectoryInfo di = new DirectoryInfo(directoryPath);
foreach (DirectoryInfo childDi in di.GetDirectories())
{
directoryList.AddRange(GetDirectoryInfo(childDi.FullName, includeDirectoryInfo));
}
if (includeDirectoryInfo)
{
directoryList.Add(di);
}
directoryList.AddRange(GetFileInfo(directoryPath));
return directoryList;
}

public List<FileSystemInfo> GetFileInfo(string filePath)
{
DirectoryInfo di = new DirectoryInfo(filePath);
FileInfo[] rgFiles = di.GetFiles("*.*");
List<FileSystemInfo> fileList = new List<FileSystemInfo>();
foreach (FileInfo fi in rgFiles)
{
fileList.Add(fi);
}
return fileList;
}
private void SaveCsvFile(string fileText, string fileName)
{
try
{
Stream myStream;
SaveFileDialog saveFileDialog1 = new SaveFileDialog();
saveFileDialog1.Filter = "CSV Files (*.csv)|*.csv";
saveFileDialog1.FileName = fileName + ".csv";
if (saveFileDialog1.ShowDialog() == DialogResult.OK)
{
if ((myStream = saveFileDialog1.OpenFile()) != null)
{
StreamWriter wText = new StreamWriter(myStream);
wText.Write(fileText);
wText.Close();
}
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
 

Update:  I found a nice solution using LINQ for filtering on multiple file extensions.

http://msdn.microsoft.com/en-us/library/wz42302f.aspx

using System.Linq;

string folder = @"C:\Projects";

IEnumerable<string> fileNames =  Directory.GetFiles(folder).Where(f => f.EndsWith( ".vb" ) || f.EndsWith( ".xml" ) || f.EndsWith( ".aspx" ));

I have a whopping 693 programs on my XP development machine. How many do you have?

You can simply find out by creating a C# console application, cutting the following, and hitting F5.

using System;
using System.Collections.Generic;
using Microsoft.Win32;


. . .


static void Main(string[] args)
{
List<string> programs = GetInstalledPrograms();
foreach (string program in programs)
{
Console.WriteLine(program);
}
Console.WriteLine("Total - " + programs.Count.ToString());
Console.Read();
}

private static List<string> GetInstalledPrograms()
{
string installKey = @"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall";
List<string> displayNames = new List<string>();
string[] subkeyNames = Registry.LocalMachine.OpenSubKey(installKey).GetSubKeyNames();
foreach (string subkeyName in subkeyNames)
{
string displayName = (string)Registry.LocalMachine.OpenSubKey(installKey + "\\" + subkeyName).GetValue("DisplayName");
if (!string.IsNullOrEmpty(displayName))
{
displayName += "\t" + subkeyName;
}
else
{
displayName = subkeyName;
}
displayNames.Add(displayName);
}
displayNames.Sort();
return displayNames;
}

Monday, June 29, 2009

Create Windows startup shortcut for ClickOnce application

ClickOnce does not provide an option for adding the application to start automatically when starting Windows. You can use the concepts in the following method to add or remove this option. You will need to make sure “Allow URL parameters to be passed to the application” is checked. This if found within Project Properties –> Publish tab –> Options –> Manifests.

using System;
using System.Deployment.Application; //Requires System.Deployment assembly
using System.IO;
using System.Text;
using System.Windows.Forms;
...
private void CreateWindowsStartup(bool create)
{
string startupFileName = Environment.GetFolderPath(System.Environment.SpecialFolder.Startup) + "\\" + Application.ProductName + ".appref-ms";
string applicationShortcut = ApplicationDeployment.CurrentDeployment.ActivationUri.ToString();
if (create)
{
if (!File.Exists(startupFileName))
{
using (FileStream fs = new FileStream(startupFileName, FileMode.Create))
{
using (StreamWriter sw = new StreamWriter(fs, Encoding.Unicode))
{
sw.WriteLine(applicationShortcut);
}
}
}
}
else
{
if (File.Exists(startupFileName))
{
File.Delete(startupFileName);
}
}
}



 




Friday, June 26, 2009

C# SMTP Snippet

I recently provided this simple snippet to a colleague and thought I’d share it out. It includes a way to attach a local file or attach one generated on the fly.
using System;
using System.IO;
using System.Net.Mail;
using System.Text;
...
public static void SendEmail()
{
try
{
bool createFile = true;
string fileName = @"c:\boot.ini";
MailMessage mail = new MailMessage();
mail.IsBodyHtml = true;
mail.From = new MailAddress("you@domain.com");
mail.To.Add(new MailAddress("you@domain.com"));
mail.Subject = "Subject";
mail.Body = "Body";
if (createFile)
{
ASCIIEncoding encoding = new ASCIIEncoding();
byte[] bytes = encoding.GetBytes("File Content Blah Blah Blah");
MemoryStream ms = new MemoryStream(bytes);
mail.Attachments.Add(new Attachment(ms, "FileTitle" + ".txt", "text/plain"));
}
else
{
mail.Attachments.Add(new Attachment(fileName));
}
SmtpClient smtpClient = new SmtpClient();
smtpClient.Host = "SMTPSERVERNAME";
smtpClient.Send(mail);
}
catch (Exception ex)
{

}
}

Tuesday, June 23, 2009

IIS Basics: Create a URL Redirect

Sometimes you may end up very long URLs like http://internal/sites/hr/benefits/default.aspx.  You can easily create a redirect which allows users to have a URL like http://benefits by using the following steps:

1. Create a DNS entry that contains the name you want used and the server you want it mapped to. You may need to request this from your network team. See the following for more details: Add an alias (CNAME) resource record to a zone

2. Open Start --> Run and type: inetmgr

clip_image002

3. Click OK.

4. Open the tree view navigation on the left pane until you find Web Sites.

5. Right-click on Web Sites and click on New –> Web Site…

clip_image004

6. Click Next when the dialog opens.

clip_image006

7. Type in a description and click Next.

clip_image008

8. Enter the host header and click Next.

clip_image010

9. For the time being, enter c:\ and click Next. This gets changed later on when this is changed to a redirect.

clip_image012

10. Click Next.

clip_image014

11. Click Finish.

clip_image016

12. Right-click on the name you used in step 7 and click Properties.

clip_image018

13. Click on the Home Directory tab, change the top radio button to A redirection to a URL.

14. Enter the URL into the Redirect to textbox and click the The exact URL entered above checkbox.

clip_image020

15. Click OK.

16. Open a browser and type http://yourredirectname to test.

Friday, June 5, 2009

Deep Zoom Batch Processing using DeepZoomTools.dll

The following code will open a folder browser dialog and allow you to recursively batch process multiple images using the DeepZoomTools.dll that is included when you download Deep Zoom Composer. I also added additional methods for creating a CSV of your directory structure so you can filter out your dzi files and export into a data source.

Note: This is part of the April 2009 version of Deep Zoom Composer.

Steps:

1. Create a Window Form Application in Visual Studio.

2. Make a reference to the DeepZoomTools.dll found in C:\Program Files\Microsoft Expression\Deep Zoom Composer.

3. Add the following code and call the GenerateDeepZoomImages method.

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Windows.Forms;
using Microsoft.DeepZoomTools;
public void GenerateDeepZoomImages()
{
FolderBrowserDialog folderBrowser = new FolderBrowserDialog();
DialogResult result = folderBrowser.ShowDialog();
if (result == DialogResult.OK)
{
List<string> extensionFilter = new List<string> { ".tiff", ".tif", ".jpg", ".bmp", ".png" }; //DeepZoom (April 2009) only supports these extensions
List<FileSystemInfo> fileList = GetFileSystemInfo(folderBrowser.SelectedPath, false, true, true, extensionFilter);
CollectionCreator collectionCreator = new CollectionCreator();
ImageCreator imageCreator = new ImageCreator();
List<Microsoft.DeepZoomTools.Image> images = new List<Microsoft.DeepZoomTools.Image>();
foreach (FileInfo fsInfo in fileList)
{
string imageFileName = fsInfo.FullName;
string imageFolderName = Path.GetDirectoryName(fsInfo.FullName);
string destinationFolder = imageFolderName + "\\" + fsInfo.Name + "_dzdata";
imageCreator.Create(fsInfo.FullName, destinationFolder);
Microsoft.DeepZoomTools.Image img = new Microsoft.DeepZoomTools.Image(imageFileName);
//You can manipulate this Image object before adding
images.Add(img);
collectionCreator.Create(images, destinationFolder);
}
}
}

public List<FileSystemInfo> GetFileSystemInfo(string directoryPath, bool includeFolders, bool includeFiles, bool recursive, List<string> extensionFilter)
{
extensionFilter = (from e in extensionFilter select e.ToLower()).ToList<string>();
List<FileSystemInfo> fileSystemList = new List<FileSystemInfo>();
DirectoryInfo directory = new DirectoryInfo(directoryPath);
if (recursive)
{
foreach (DirectoryInfo childDirectory in directory.GetDirectories())
{
fileSystemList.AddRange(GetFileSystemInfo(childDirectory.FullName, includeFolders, includeFiles, recursive, extensionFilter));
}
}
if (includeFolders)
{
fileSystemList.Add(directory);
}
FileInfo[] files = directory.GetFiles("*.*");
foreach (FileInfo file in files)
{
if (extensionFilter.Count != 0)
{
if (extensionFilter.Contains(file.Extension.ToLower()))
{
fileSystemList.Add(file);
}
}
else
{
fileSystemList.Add(file);
}
}
return fileSystemList;
}
public static string GetFileSystemCsv(List<FileSystemInfo> fileSystemList)
{
StringBuilder sb = new StringBuilder();
sb.Append("Name,Full Name,Parent Folder,Creation Time,Last Access Time,Last Write Time,Length, Extension,Attributes\r\n");
foreach (FileSystemInfo fileSystemItem in fileSystemList)
{
if (fileSystemItem.GetType() == typeof(FileInfo))
{
sb.Append(string.Format("{0},{1},{2},{3},{4},{5},{6},{7},{8}\r\n", fileSystemItem.Name, fileSystemItem.FullName, fileSystemItem.FullName.Replace(fileSystemItem.Name, ""), fileSystemItem.CreationTime, fileSystemItem.LastAccessTime, fileSystemItem.LastWriteTime, ((FileInfo)fileSystemItem).Length.ToString(), fileSystemItem.Extension, fileSystemItem.Attributes.ToString()));
}
else
{
sb.Append(string.Format("{0},{1},{2},{3},{4},{5},{6},{7},{8}\r\n", fileSystemItem.Name, fileSystemItem.FullName, fileSystemItem.FullName.Replace(fileSystemItem.Name, ""), fileSystemItem.CreationTime, fileSystemItem.LastAccessTime, fileSystemItem.LastWriteTime, "", "", fileSystemItem.Attributes.ToString()));
}
}
return sb.ToString();
}

private void SaveCsvFile(string fileText)
{
Stream myStream;
SaveFileDialog saveFileDialog1 = new SaveFileDialog();
saveFileDialog1.Filter = "CSV Files (*.csv)*.csv";
if (saveFileDialog1.ShowDialog() == DialogResult.OK)
{
if ((myStream = saveFileDialog1.OpenFile()) != null)
{
StreamWriter wText = new StreamWriter(myStream);
wText.Write(fileText);
wText.Close();
}
}
}