Wednesday, December 30, 2009

Upgrading SharePoint Portal Server 2003 to Microsoft Office SharePoint Server 2007 (MOSS 2007) using Database Migration

Assumptions

This post assumes that you are currently running Microsoft SharePoint Portal Server 2003 (SPS 2003) and that you want to upgrade to Microsoft Office SharePoint Server 2007 (MOSS 2007). I assume you are running in a pretty simple configuration such that you have a separate database server, and the rest of your SPS2003 installation is on one server.

One thing that I have that many people may not have that I have is two installations and versions of SharePoint on one server. I have SPS 2003 and MOSS 2007 on the same server. There are separate urls for both instances of SharePoint. I don’t want to do anything to the existing MOSS 2007 installation, but I DO want to upgrade SPS 2003 to MOSS 2007. In the end, my goal is to have one instance of MOSS 2007 with two different web applications (one that already existed and one that was converted from SPS2003) and no instances of SPS2003. FYI, this is all because I inherited the MOSS 2007 instance after I already had SPS2003, and I only had the one server to run both instances.

You should be running Service Pack 3 for SPS2003 and WSS 2.0. Click here for instructions.

The biggest assumption is that you want to do a database migration. The advantage is that you can work at the upgrade and still have your old installation available. Read this page before you decide if this strategy meets your needs:

If you don’t want to do a database migration, I read that you can use psconfig –upgrade to do an inplace or side by side (gradual) upgrade. You can use stsadm –o upgrade to force the the upgrade again from where it left of (assuming it fails) and I assume if you didn’t start it yet also. In my case I am doing a database migration and need to make it run again. I think this is the same as doing an inplace and giving it the new url that points to MOSS site we create (later). 

NOTE: Once a site definition has been converted you need to start the conversion over again on a virgin SPS2003 content database. It doesn’t reconvert the site definitions. In other words any custom mapping file you created can only be applied once before you have to start over. For more information on migrating Custom Site Definitions, see my other blog entry.

.

Summary of Actions

Below is a quick overview of what you need to do. Please see the sections after this section for more details on pre-requisites, more details, etc for each of these steps

On SharePoint Server

Run PRESCAN

cd C:\Program Files\Common Files\Microsoft Shared\web server extensions\12\BIN>

PRESCAN.EXE /c preupgradescanconfig.xml /ALL

Review log / summary generated by it. Search for word error and review end of file for summary counts.

On Database Server (or any SQL Server Management Studio)

Backup content database

Restore content database under another name (i.e. CompanyName_WSS_Content)

On MOSS 2007 Central Administration

Create new web application in MOSS

WARNING: Do NOT specify the content database as the one from above (i.e. CompanyName_WSS_Content)

WARNING: Do NOT click on the Create the Site Collection link

On SharePoint Server

Install Custom Site Definitions, Mapping files, etc

Make sure the following paths have your necessary files

C:\Program Files\Common Files\Microsoft Shared\web server extensions\12\TEMPLATE\SiteTemplates\MyCustomSiteDefinition

C:\Program Files\Common Files\Microsoft Shared\web server extensions\12\TEMPLATE\1033\XML\WEBTEMPMyCustomSiteDefinition.xml

C:\Program Files\Common Files\Microsoft Shared\web server extensions\12\CONFIG\UPGRADE\MyCustomSiteDefinitionWssUpgrade.xml

Add Content Database

cd C:\Program Files\Common Files\Microsoft Shared\web server extensions\12\BIN

stsadm.exe -o addcontentdb -url http://hostname:port -databasename CompanyName_WSS_Content -databaseserver host\instancename

Review C:\Program Files\Common Files\Microsoft Shared\web server extensions\12\LOGS\Upgrade.log for errors, exceptions, warnings, etc. These are good keywords to search on

Remove Temp Content Database

Pre-upgrade requirements

Since I am running MOSS 2007 already, I by definition have the .NET Framework 3.0 and ASP.NET 2.0 enabled in IIS. For more information on how to install these, see here.

According to this page you also should make sure that the minimum permissions for the account that you use to run Setup and the SharePoint Products and Technologies Configuration Wizard are:

  • Member of the Administrators group on the local computer that runs Office SharePoint Server 2007.

  • In SQL Server, the account must be:

  • Authorized to access all SharePoint Products and Technologies databases.

  • Granted the Database Creators (dbcreator) fixed server role.

  • Granted the Security Administrators (securityadmin) fixed server role.

I would also recommend reviewing  this: http://technet.microsoft.com/en-us/library/cc303417.aspx for more details on pre-upgrade steps.

If you are doing this on a production system, PLEASE backup your stuff; at least the databases. Please understand, I can’t begin to explain everything in one blog post that takes Microsoft 238 pages in this ebook. This is just what I did.

Backing up your Content database

NOTE: This step is optional, but is always a good idea. Backup up your content database BEFORE you run the Prescan tool since it does change your content database a little.

The content database in SharePoint Portal Server typically end in _SITE. For example, SPS2003_SITE. Just use the typical database tools (For example Enterprise Manager or MS SQL Server Management Studio) to do the backup.

Install Custom Site Definitions, Mapping files, etc

Make sure the following paths have your necessary files

C:\Program Files\Common Files\Microsoft Shared\web server extensions\12\TEMPLATE\SiteTemplates\MyCustomSiteDefinition

C:\Program Files\Common Files\Microsoft Shared\web server extensions\12\TEMPLATE\1033\XML\WEBTEMPMyCustomSiteDefinition.xml

C:\Program Files\Common Files\Microsoft Shared\web server extensions\12\CONFIG\UPGRADE\MyCustomSiteDefinitionWssUpgrade.xml

Run Prescan

If you didn’t do this, do it now. Open a command prompt and execute the following (assuming you installed MOSS 2007 in the default location, otherwise you’ll need to adjust the path to your location).

cd C:\Program Files\Common Files\Microsoft Shared\web server extensions\12\BIN>

PRESCAN.EXE /c preupgradescanconfig.xml /ALL

NOTE: The /c preupgradescanconfig.xml must be used if you are upgrading from SPSS 2003 like we are.

Running it with the /ALL will not affect your MOSS 2007 installation. It only scans the older versions of SharePoint (SPS2003) from what I can tell. At least that is all it did for me.

You can also use the /V to specify the path. Use /? switch to get all the command line options

Search the generated file for the word error to see if you have any big errors

I recommend keeping the command prompt open. We’ll need it later.

Set the previous version databases to be read-only

Be sure you capture all of the data in your backup so that you are restoring and upgrading the current state of your environment. Therefore, set the previous version databases to read-only so that users cannot add or change information in the sites. With the databases set to read-only, users can continue to view content, but they are not able to add or change content.

clip_image001Important:

Before you perform these procedures, be sure that you have run the pre-upgrade scan tool on the sites stored in the databases. If you do not run the pre-upgrade scan tool on the data before you back the databases up, you will not be able to upgrade the data on your new server farm.

clip_image002Set a database to read-only in SQL Server 2000

1. In Microsoft SQL Server Enterprise Manager, right-click the name of the database that you want to set to read-only, and then click Properties.

2. In the Properties dialog box, click the Options tab.

3. Under Access, select the Read-only check box, and then click OK.

clip_image002[1]Set a database to read-only in SQL Server 2005

1. In SQL Server Management Studio, right-click the name of the database that you want to set to read-only, and then click Properties.

2. In the Select a page section, click Options.

3. In the right pane, under Other options, in the State section, next to Database Read-Only, click the down arrow, and then select True.

NOTE: This section was taken directly from age 138 of Upgrading to Office SharePoint Server 2007 (Free eBook).

 

Backing up your Content database

NOTE: This backup is a MUST because it will be used to restore from. Run prescan BEFORE you do this backup. You will now have one or two back ups depending if you did the optional backup or not. This is the backup you will use for the restore and upgrade. The other backup is just incase you need to rollback to SharePoint Portal Server 2003.

The content database in SharePoint Portal Server typically end in _SITE. For example, SPS2003_SITE. Just use the typical database tools (For example Enterprise Manager or MS SQL Server Management Studio) to do the backup.

NOTE: While backing up the database the web front end will likely fail when loading a page.

Restore your Content Database

Use the typical database tools (For example Enterprise Manager or MS SQL Server Management Studio) to restore the database you just backed up. Be sure to restore it as a different name from the database name. This will change the filename as well, which is good. Be sure to uncheck the Overwrite the existing database checkbox so you don’t accidentally forget to change the database name and overwrite the original or other database.

You will want to give some thought as to what the name of this will be since it will be your new content database. I like CompanyName_WSS_Content.

NOTE: Be sure to change the database (that you just restored) from read-only to read write again. Otherwise the upgrade probably won’t work too well.

Create a new web application in MOSS 2007

NOTE: If you want to save yourself some steps later, you can stop your SharePoint Portal Server 2003 IIS web application (using IIS). Then when you create your application, you can use the old url that you had for SPS2003. Be aware that this has the downside of everyone seeing the new site immediately, and the old site being down longer. If you have scheduled downtime and good communication this method may be acceptable, but it does pose the opportunity for users to make change when you are not ready for it to go live yet.

Go to SharePoint Central Administration | Application Management tab | SharePoint Web Application Management section | Create or extend Web application link. Click that link. Now, click the Create a new Web application link. Set the url to a new url (it can’t be the existing one).  Set the Database Name to Temp_WSS_Content or something since we won’t keep it for long. In particular, do not set it to what the name of the database we restored above. After filling in all the fields, click OK.

When the web application is finished being created, you will see a page like the following:

image

Do NOT create a new Site Collection! This is VERY important. In other words do NOT click the Create Site Collection link shown above

Add Content Database

Now we need to add the Content database ( the one we restored) to SharePoint so it knows about it. This is also the point where the database is converted from SPS2003 content database to a MOSS2007 content database. This step may take a while since this is were the actual conversion takes place. To see the status of the conversion you can check the log file (C:\Program Files\Common Files\Microsoft Shared\web server extensions\12\LOGS\Upgrade.log)

You MUST use stsadm to add the content database. The UI will let you try but tell you it can’t because the operation could timeout if you are using a browser and that you should use stsadm instead.

Be sure to specify the Web Application is the one you just created above when you created the web application!

Be sure you specify the information to the database that we restored.

cd C:\Program Files\Common Files\Microsoft Shared\web server extensions\12\BIN

stsadm.exe -o addcontentdb -url http://hostname:port -databasename CompanyName_WSS_Content -databaseserver host\instancename

NOTE: \instance name for the databaseserver parameter is optional and only needed if your database is not on the default instance of SQL Server. This is common for a Clustered environment or where you have more than one SQL Server installed on one ser

The databaseuser and databasepassword are for SQL database users only. If you are using Windows Authentication, you don’t need these parameters since they will be passed from the context that you execute stsadm. So, make sure that user has the correct permissions.

Depending on the size of your content database the addcontentdb command may take a while. I have a 10GB on a low end server and it took well over 35 minutes (I had to leave while it was processing after 35, so I don’t know how long it actually took). Your mileage may vary.

For more info see here.

Remove the Content database

Now we need to remove the temporary Content database that was created in the above step from SharePoint and also delete it. You can use stsadm to do this, but I like the UI. Your choice, to use the UI do the following. Go to SharePoint Central Administration | Application Management tab | SharePoint Web Application Management section | Content databases link.

Be sure to change the Web Application to the one you just created above!

Click the Content database that you specified (Temp_WSS_Content ) when you created the web application above.

Click the Remove content database checkbox. This won’t delete the database from SQL Server though. You will need to do that once you have removed it. You may also want to wait until you are finished with the upgrade.

Estimated Time

There is no real way for me to say how long it will take you, but my test server is fairly old and my content database is 10GB. It roughly took me 1 hr 43 minutes to do it. Below is the breakdown of the time per step. Don’t forget to leave time to review the conversion. Also, the content will need to be indexed again so make sure customers are aware of it.

Prescan - 5 minutes
Review prescan - 3 minutes
Backup db (11GB) - 7 minutes
Restore db (11GB) - 10 minutes
Web App Creation - 8 minutes
Install Site Definitions - 8 minutes (approximately)
Add Content DB - 60 minutes (approximately)
Delete Temp Content DB - 2 minutes

Troubleshooting

I did run the prescan tool, but it says I didn’t.

Q: I get the following message:

The pre-upgrade scan tool has not yet been run on this database SPContentDatabase Name=CompanyName_WSS_Content Parent=SPDatabaseServiceInstance Name=dbhostname. You must run the pre-upgrade scan tool before you can continue with the upgrade process. Run the tool from the following path: C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\12\bin\prescan.exe.

Troubleshoot issues with Windows SharePoint Services.

A: I got this problem when I applied MOSS 2007 Service Pack 3 then backed up and restored the database. I guess Service Pack 3 changes the database. It is also possible I forgot to run prescan.exe on the database before I backed it up.

 

References

Converting a Site Definition from SharePoint Portal Server 2003 to MOSS 2007

Unfortunately, the Site Definitions from SharePoint Portal Server 2003 (SPS2003) to Microsoft Office SharePoint Server 2007 (MOSS 2007) have changed quite a bit. Well, unfortunately because it means more work for the conversion. The good news is that it is a much better model and easier to work with.

There are two things to consider here. One thing is that you will need an updated Site Definition, and the other is that you will need to map all previous standard SPS2003 paths to the ones used by MOSS 2007. The two things are related, but understanding the differences can help in troubleshooting any problems you may have.

Let’s start by looking at the files we need to have to do each.

Site Definition Files

C:\Program Files\Common Files\Microsoft Shared\web server extensions\12\TEMPLATE\SiteTemplates\MyCustomSiteDefinition

C:\Program Files\Common Files\Microsoft Shared\web server extensions\12\TEMPLATE\1033\XML\WEBTEMPMyCustomSiteDefinition.xml

Mapping files for Site Definition

C:\Program Files\Common Files\Microsoft Shared\web server extensions\12\CONFIG\UPGRADE\MyCustomSiteDefinitionWssUpgrade.xml

Your Site Definition files can be added to any MOSS 2007 installation and then you will be able to create new sites that use your Site Definition. If you can do that and the site you create works as expected then this means that your Site Definition files are good. These are all you would need if you were just creating a new Site Definition for MOSS 2007.

The Mapping files are needed for when the site is migrated by the upgrade process from SPS2003 to MOSS2007. It needs to map all path that were used in SPS2003 to MOSS2007 compatible paths. This file is only applied once when the upgrade process runs. In fact, once it is applied I can’t find a way to apply it again without starting over with a virgin SPS2003 content database.

Creating a new Site Definition

I am by no means an expert at getting site definitions to work. All I have done is in SPS2003, I copied the STS Site Definition and made some very minor tweaks to it. Now I am in MOSS2007 and want to again make those very basic tweaks so that I can have all the functionality of MOSS2007 team sites. This means in my scenario the easiest way to continue is to not try to convert my existing Site Definition from SPS2003, but to just copy the STS Site Definition from MOSS2007 and make my tweaks to it.

Here is what I did.

  1. Go to C:\Program Files\Common Files\Microsoft Shared\web server extensions\12\TEMPLATE\SiteTemplates and copy the sts directory and call it something unique like MySts. Make your tweaks as needed.
  2. Go to C:\Program Files\Common Files\Microsoft Shared\web server extensions\12\TEMPLATE\1033\XML\ and create a file called something like WEBTEMPMySts.xml. It MUST MUST MUST start with WEBTEMP and end in .xml, the rest is not important but should be meaningful to you. If you are copying the STS Site Definition like I am, here is all you need in that file. If you are doing something else, you’ll have to decide what you put in it.

    <?xml version="1.0" encoding="utf-8" ?>
    <!-- _lcid="1033" _version="12.0.4518" _dal="1" -->
    <!-- _LocalBinding -->
    <Templates xmlns:ows="Microsoft SharePoint">
    <Template Name="MySts" ID="30001">
    <Configuration ID="0" Title="My Team Site" Hidden="FALSE" ImageUrl="/_layouts/images/stsprev.png" Description="This template creates a site for teams to create, organize, and share information quickly and easily. It includes a Document Library, and basic lists such as Announcements, Events, Contacts, and Quick Links." DisplayCategory="Collaboration"> </Configuration>
    <Configuration ID="1" Title="My Blank Site" Hidden="FALSE" ImageUrl="/_layouts/images/stsprev.png" Description="This template creates a Windows SharePoint Services-enabled Web site with a blank home page. You can use a Windows SharePoint Services-compatible Web page editor to add interactive lists or any other Windows SharePoint Services features." DisplayCategory="Collaboration"> </Configuration>
    <Configuration ID="2" Title="My Document Workspace" Hidden="FALSE" ImageUrl="/_layouts/images/dwsprev.png" Description="This template creates a site for colleagues to work together on documents. It provides a document library for storing the primary document and supporting files, a Task list for assigning to-do items, and a Links list for resources related to the document." DisplayCategory="Collaboration"> </Configuration>
    </Template>
    </Templates>
    You don’t have to use 30001, but it does have to be unique with regards to Microsoft Standard ids (so don’t use 1, etc). I think it is anything over 10000 is for user created Site Definitions, but 30000+ is definitely a safe range.
    Check out Upgrading SharePoint Server 2003 Customizations to SharePoint Server 2007 under the Upgrading Custom Site Templates section more information.

Creating the mapping file

The mapping file is used for conversion only.

Go to the C:\Program Files\Common Files\Microsoft Shared\web server extensions\12\CONFIG\UPGRADE directory

Since we are doing the STS site, all we have to do is copy the WssUpgrade.xml to another file and call it something like MyStsWssUpgrade.xml. The name is not important, but must be in this directory in order for the upgrade process to find it.

I then did the following

  1. Replace All: FromPath="{LocaleId}\STS with FromPath="{LocaleId}\MySts
  2. Replace All: ToPath=  "SiteTemplates\STS with ToPath=  "SiteTemplates\MySts
  3. Replace All: FromPath="{LocaleId}\STS\Lists with FromPath="{LocaleId}\MySts\Lists
  4. Change ID=”1” to ID=”30001”

In the end all paths should reference your MySts directory, not the STS directory

For more information on how to migrate from SPS2003 to MOSS2007, check out my other blog entry.

Verifying your conversion

For starters the site definition should look like you expect it to. In our scenario, it should look and act like all other MOSS 2007 sites, not like it used to.

Also, go to the MOSS 2007 content database using SQL Management Studio. Run the following query.

select fullurl, webtemplate, ProductVersion from webs order by productversion

If all the sites where converted to MOSS2007, they will have a ProductVersion = 3. If there are any that are not converted, they will have ProductVersion = 2. This will cause you grief down the road if you don’t get it to convert to version 3 and will also cause MOSS to run those sites in compatibility mode and there is a negative performance hit with that.

You can also review the log carefully. It should say that it upgraded each site. The log file is usually C:\Program Files\Common Files\Microsoft Shared\web server extensions\12\LOGS

Monday, December 28, 2009

Reset password for Remote Desktop Connection

Newer versions of Remote Desktop don’t save the password in the .rdp file anymore. I don’t know where it is stored. If anyone knows, please let me know.

I have a bunch of .rdp files that I want to not prompt me for a password, but instead just open the Remote Desktop session. The problem is that when my password changes in Active Directory, where ever my password is saved for these .rdp files, it is not updated automatically. This means that when I open the .rdp file it says the remote server says I have not entered the correct password, check my username, domain, etc.

The workaround to this is to just wait for the alert to pop up, click ok, and then type in my new password. The problem is that it does not remember the new password.

The easiest solution I have found is to do the following.

  1. Select all your .rdp files in Windows Explorer.
  2. Right-click on of them and choose Edit. This will open the edit window for each of these .rdp files.
  3. For each window click the edit link as shown below.

    image
  4. You will be prompted to enter your username and password. Be sure the username has the domain\username as the username if you are using Active Directory username. Otherwise, the local login on that server will be assumed.
  5. Click Cancel. No need to Save unless you change other values on the window shown above.
     

While it is not automated, it should only take about 5 seconds per .rdp file once they are all open which should not take long either. This assumes you can type your password really faster or better yet, open up notepad type your password, copy it, and then instead of typing your password for each .rdp file, you can just paste it. This will reduce your errors and make your life easier.

Thursday, December 10, 2009

FieldValue in DynamicData ForeignKeyField is null, but it should not be

I am using ADO.NET Entity Framework and Dynamic Data in a ASP.NET Formview. It works great! Let’s pretend I am working with a Person entity. Then I added a Foreign key to my table (and EF Model) to another table called it Gender. I add the DynamicControl to my formview for the foreign key by setting the DataField to the name of the relationship from Person to Gener, in this case it is called Gender.

I added this to EditItemTemplate

<asp:DynamicControl ID="GenderDynamicControl" runat="server" DataField="Gender" Mode="Edit" ValidationGroup="Edit"  />

The two-way binding works great.

Then I go to the ItemTemplate and add

<asp:DynamicControl ID="GenderDynamicControl" runat="server" DataField="Gender" Mode="ReadOnly" ValidationGroup="ReadOnly"  />

I then test, and see that the ItemTemplate / Read only version of this doesn’t show any value. After debugging and looking at the FieldValue property in ForeignKey.ascx.cs I see that it is null when it should not be since the row I was looking at had a GenderID specified in the Person table.

Then I looked at the CRUD interface that you get for free when you create a Dynamic Data Web Site. It works great! That made no sense to me, but then I realized that the EntityDataSource I was using did NOT include the Gender entity, so this entity was not loaded. This is VERY characteristic of ADO.NET Entity Framework since it does lazy loading and requires that you specify what you want loaded. If you don’t, you don’t get the data.

So, the real solution was to just modify my EntityDataSource so that I include the Gender table as shown below. Some of the properties have been removed for simplicity in the code.

<asp:EntityDataSource ID="dsPerson" runat="server" 
EntitySetName="Person" 
Include="Gender">

Wednesday, December 9, 2009

Change Max file upload size in MOSS 2007

If you can’t upload a file larger than say 50MB or 150MB for then it is likely you are running into the max file upload size that SharePoint sets.

Microsoft Office SharePoint Server 2007 (MOSS 2007) supports file upload sizes of 2GB. This is really a SQL Server limitation since files are written to the database. There are some related settings in web.config for the upload.aspx that you could change, but mine are already set to 2GB so that would not do any good.

For additional information on uploading file in general and this point, you may want to check out this post.

To check or change what the max size set in MOSS is, just open your Central Administration for the installation you want to check. Then go to Application Management tab | Web application general settings link and you will get the Web Application General Settings page.

In the example below you can see that I set mine to 1.5GB or really 1500MB.

image

Keep in mind this is set at the site collection level, not the site level.

Tuesday, November 24, 2009

Getting SqlConnection from EntityConnection

I love ADO.NET Entity Framework. The problem I ran into is I wanted to call a stored procedure, but I can’t really do that unless the stored procedure returns entity information, etc. I really just wanted to call a stored procedure, though it could have been embedded SQL also (if you do that sort of thing :), but I needed a SqlConnection to do that. All I had was a DbConnection which can be cast to and EntityConnection since I am using the EF. The question was how do I get to the SqlConnection that EF uses. Sure, I could have created another entry in my web.config or app.config and added another connection string. I don’t like to have some many connection strings though. A little looking around in the debugger and the solution became obvious.

Below is a simple method that takes my DbConnection and casts it to a EntityConnection. It then accesses the StoreConnection which returns a DbConnection and is cast to a SqlConnection. It then just gets the connection string from the SqlConnection,. From that point I can open a new connection of my own. Alternatively, I could have checked the state of that connection and opened and closed it appropriately. I didn’t want to worry about the state of the connection and messing up the EF, so I just create a new connection. Though both seem to work for my basic testing.

using System.Data.EntityClient;
using System.Data.SqlClient;
...
private string GetADOConnectionString()
{
SalesSyncEntities ctx = new SalesSyncEntities();
EntityConnection ec = (EntityConnection)ctx.Connection;
SqlConnection sc = (SqlConnection)ec.StoreConnection;
string adoConnStr = sc.ConnectionString;
return adoConnStr;
}

Tuesday, November 17, 2009

How to use the Ajax Toolkit AutoCompleteExtender

I can never seem to remember the basics of how to use the AJAX Toolkit AutoCompleteExtender. It isn’t that it is all that difficult, it is just that there are things that I forget. The sample page is actually quite useful.

Step 1: Create a Web Service that you can call.

First be sure to uncomment or add the ScriptService attribute to your web service. It should look like this and be above the class declaration.

[System.Web.Script.Services.ScriptService]

The second thing to know is when you create your web method you MUST use very specific parameters for your web method. The one exception (sort of) is if you use the ContextKey property, but even then you are only adding a parameter. The name of the method is NOT important because you specify that in the extender properties. Your method must have two parameters named EXACTLY as shown below and in the order below. The return type can be string[] or List<string> because they both end up as string[] when sent as xml.

[WebMethod]
public List<string> GetEmails(string prefixText, int count)
{

}

Step 2: Add the Extender to the page or user control where you want to use it.

I would start by pasting the below on your page or user control.

<ajaxToolkit:AutoCompleteExtender
runat="server"
ID="myAutoComplete"
TargetControlID="txtSomeField"
ServicePath="~/MyWebService.asmx"
ServiceMethod="GetEmails"
MinimumPrefixLength="3"
CompletionInterval="300"
EnableCaching="true"
CompletionSetCount="50"
DelimiterCharacters=";, :" />
Then paste the following under your <%@ Control… > or <%@ Page …> tags (see the first line in the file).
<%@ Register Assembly="AjaxControlToolkit" Namespace="AjaxControlToolkit" TagPrefix="ajaxToolkit" %>
You will notice that my example doesn’t contain any animations, javascript, etc. I personally like it with no animation. The default seems zippier when I type. It also has the big advantage of making it very simple to put this in a user control and have that user control used multiple times on a page without issue. You can apparently still get it to work, but I don’t think it is worth it. Check out the solution posted here to see how to make it work. The sample shown on the sample page provided with the toolkit just won’t work if you use the extender multiple times on a page.

You may also notice I removed the stylessheet references, and the BehaviorID (which is in the sample project, but not in the documentation) is also not there. You do NOT want to set this if you use this multiple times on a page.

You can tweak the other parameters if you like. The ~ in the ServicePath only works for Web Sites, not Web Applications from what I understand. Please let me know if I am wrong since I have not actually done so. I did find that I needed the ~ if everthing is not at the top level in the web site. For example, I used the extender on a user control that is in a Controls directory under the root. The MyWebService.asmx is a the top root, so some kind path is needed. http://….MyWebService.asmx will also work, but that is difficult to make production and dev difficult to work with.

Step 3: Add ScriptManager

You will need either the ToolkitScriptManager or the ScriptManager on the page or a master page, etc for any AJAX stuff to work. Just remember, you can only have one of these on a page when the page is rendered. That means you can’t have it on a master page and on a user control.

Monday, November 16, 2009

DropDownList in a GridView with no code-behind / events handling needed

So, you have a GridView and you want to add a DropDownList to one of the columns. Typically, I would have populated the data and selected the proper item in the DropDownList. This code always seemed silly to me. They I saw in a Microsoft article how they did it. No code-behind or event handling needed. One note, I didn’t use the SqlDataSource and instead used the ObjectDataSource.

I could not do step 20 where they bind using the Field Binding radio button for City. I was however able to set the Custom Binding. Just typed it in there. Or you can do it from .aspx/.ascx page directly.

I did add my ObjectDataSource (you only need to worry about the select method) to the page and wired it up to the DropDownList.

Here is my ObjectDataSource

<asp:ObjectDataSource ID="dsMyDataSource" runat="server" SelectMethod="GetDropDownListItems" TypeName="DataModel.DAL" />

Let’s assume you have converted your field to a template already (if you haven’t you’ll have to). This means you have something similar to this.

<asp:TemplateField HeaderText="City">
<ItemTemplate>
<asp:Label ID="txtCity" runat="server" Text='<%# Bind("City") %>'/>
</ItemTemplate>
</asp:TemplateField>

Now you want to make that into a DropDownList of Cities. The process change is very minimal actually. Just make it look like the following:

<asp:TemplateField HeaderText="City">
<ItemTemplate>
<asp:DropDownList ID="ddlCity" runat="server" SelectedValue='<%# Bind("txtCity") %>'
DataSourceID="dsMyDataSource"
DataTextField="City"
DataValueField="City"/>
</ItemTemplate>
</asp:TemplateField>

So, you can go through all that document says, or you can make a few changes as noted here. Of course, the really crazy way now is to use code-behind. In all honesty, there is always a reason for different ways. This is a quite and powerful way to do this.

Thursday, November 12, 2009

Creating a Decimal Dynamic Data control that allows commas

I love the Dynamic Data architecture. I won’t get into how it all works, and assume you understand how to use the Decimal Dynamic Data control. For the most part, you never have to worry about it except maybe decorate your Entity with [UIHint (“Decimal”)].

Just open up your project that is a Dynamic Data project in Visual Studio 2008 and go to the DynamicData directory it created for you. Download the my two files (DecimalAllowingCommas_Edit.ascx.cs and DecimalAllowingCommas_edit.ascx) and put them in the FieldTemplates directory. Don’t forget to change your namespace and references to it.

The two files are slightly modified versions of the Decimal_Edit.ascx and Decimal_Edit.ascx.cs files that are already in that directory.

The difference is that I removed the CompareValidator and replaced it with a RegularExpressionValidator since the CompareValidator doesn’t allow for commas in the textfield, but my regular expression does. I also add a line in the Page_Load for this control that sets the ValidationGroup to the same as the other validators in that control. That gives us basically the same validation as we had before, but with commas allowed.

That handled the JavaScript validation and even server side validation, but when the values are automatically pushed to the properties on the entity which are of type double, .Net throws an exception that basically says it can’t convert the string to a double because it has commas in it. Seems a bit silly to me that it can’t do that, but we’ll fix that. I just removed the contents of the ExtractValues method and replaced them with my own that use the Decimal.TryParse() method to do the parsing. This method happily accepts the commas.

Now that we have support for our new type of Dynamic Data control, we need to use it. That is easy. In your entity where you had [UIHint(“Decimal”)], just replace that with [UIHint(“DecimalAllowingCommas”)]. Now when the UI renders editable version of that field it will use the DecimalAllowingCommas_Edit instead of the Decimal_Edit Dynamic Data Control.

On last thing, you will need make sure the ApplyFormatInEditMode property is set to true for the declaration of the DynamicControl in your parent control or page. Here is a the idea of what I am saying talking about.

<asp:FormView>
<EditItemTemplate>
<asp:DynamicControl
ID="CostPerYearDynamicControl" runat="server"
DataField="CostPerYear" Mode="Edit"
ValidationGroup="Edit"
ApplyFormatInEditMode="True"/>
</EditItemTemplate>
</asp:FormView>

 

Here is the source for the .ascx in case you don’t want to download the file.

<%@ Control Language="C#" CodeFile="DecimalAllowingCommas_Edit.ascx.cs" Inherits="MyApp.DecimalAllowingCommas_EditField" %>

<asp:TextBox ID="TextBox1" runat="server" CssClass="droplist" Text='<%# FieldValueEditString %>' Columns="10"></asp:TextBox>

<asp:RequiredFieldValidator runat="server" ID="RequiredFieldValidator1" CssClass="droplist" ControlToValidate="TextBox1" Display="Dynamic" Enabled="false" />
<asp:RegularExpressionValidator runat="server" ID="RegularExpressionValidator1" CssClass="droplist" ControlToValidate="TextBox1" Display="Dynamic" Enabled="false" />
<asp:RangeValidator runat="server" ID="RangeValidator1" CssClass="droplist" ControlToValidate="TextBox1" Type="Double"
Enabled="false" EnableClientScript="true" MinimumValue="0" MaximumValue="100" Display="Dynamic" />
<asp:DynamicValidator runat="server" ID="DynamicValidator1" CssClass="droplist" ControlToValidate="TextBox1" Display="Dynamic" />
<asp:RegularExpressionValidator ID="moneyRegexValidator" runat="server"
ControlToValidate="TextBox1" ErrorMessage="Must be of the format #,###.##."
ValidationExpression="^([1-9]{1}[0-9]{0,2}(\,[0-9]{3})*(\.[0-9]{0,2})?|[1-9]{1}[0-9]{0,}(\.[0-9]{0,2})?|0(\.[0-9]{0,2})?|(\.[0-9]{1,2})?)$"></asp:RegularExpressionValidator>

Here is the code for the .ascx.cs

using System;
using System.Data;
using System.Configuration;
using System.Collections;
using System.Collections.Specialized;
using System.Linq;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
using System.Xml.Linq;
using System.Web.DynamicData;

namespace MyApp
{
public partial class DecimalAllowingCommas_EditField : System.Web.DynamicData.FieldTemplateUserControl
{
protected void Page_Load(object sender, EventArgs e)
{
TextBox1.ToolTip = Column.Description;

SetUpValidator(RequiredFieldValidator1);
SetUpValidator(RegularExpressionValidator1);
SetUpValidator(RangeValidator1);
SetUpValidator(DynamicValidator1);

// copy it from one of the validators that knows what group we are in. :)
moneyRegexValidator.ValidationGroup = DynamicValidator1.ValidationGroup;

}

protected override void ExtractValues(IOrderedDictionary dictionary)
{
decimal result;
bool successful = Decimal.TryParse(TextBox1.Text, out result);
if (successful)
{
dictionary[Column.Name] = result;
}
}

public override Control DataControl
{
get
{
return TextBox1;
}
}
}
}

Tuesday, November 10, 2009

Use Reflection to Get and Set Properties on a class


Sometimes it is useful to access a property on a class that you may or may not have created. Use the code below to set or get a value by name for any class.

using System;
using System.Data;
using System.Configuration;
using System.Reflection;

namespace MyApp
{

public class ReflectionHelper
{
public ReflectionHelper()
{

}

public static Object GetProperty(System.Object obj, System.String propertyName)
{

if (propertyName == null)
{
throw new System.ArgumentException("No name specified");
}

PropertyInfo pi = obj.GetType().GetProperty(propertyName);

if (pi == null)
{
throw new Exception("Object does not have a property named: " + propertyName);
}

return pi.GetValue(obj, null);

}

public static void SetProperty(System.Object obj, System.String propertyName, System.Object propertyValue)
{
if (obj == null)
{
throw new System.ArgumentException("No target specified");
}
if (propertyName == null)
{
throw new System.ArgumentException("No name specified");
}

PropertyInfo pi = obj.GetType().GetProperty(propertyName);

if (pi == null)
{
throw new Exception("Object does not have a property named: " + propertyName);
}

pi.SetValue(obj, propertyValue, null);

}
}
}

Monday, November 9, 2009

Make Windows Offline Files faster over slower connections

Here is the scenario I have to work with. I have a laptop running Windows XP. My profile is roaming and My Documents is mapped to a network drive. My Documents and the network drive are synchronized using a Windows feature called Offline Files. I personally don’t like it in my scenario, but understand it has it’s place. IMHO, roaming profiles have no place when I am the ONLY one that uses the computer and never log onto another computer or if I did, I don’t need my user profile. Regardless, I work in corporate America and there is nothing I can do to change that.

Well, there is something I can do about it.

Here is what I found out.

The big thing figured out is that I needed to find a way to tell the Offline Files feature that I want it to consider my laptop to be disconnect / offline so that it will not try to access the files directly on the network. The solution to this is to use a tool called CSCCMD.exe 1.1. It is a WONDERFUL command line tool for working with Offline Files. It allows you to do everything you can in the UI and more.

To get to the point, to tell the Offline Files feature to not use the network and instead use the local copy of offline files, you need CSCMD.exe 1.1 (1.0 is missing lots of features). You can get it from the link below. Please not the Windows Server 2003 Resource Kit Tools only has version 1.0.

Download

Now that you have the file, unzip it and put it in your system32 or other location that is in your executable path.

Type csccmd.exe /DISCONNECT:”\\server\share

Be sure to adjust to point to your network share that is being used by Offline Files.

If you don’t know what that path should be, you are in luck, the tool will tell you that. The following command actually shows all the files that are being cached, but you can see what the share path is.

csccmd.exe  /RESID

Type csccmd.exe /RESID

For me, the first line it returned was the path. You’ll see the pattern after you look at all the lines. The on that they all have in common is probably the one you want. Basically, you need to find \\server\share where server is the name of the server and share is the name of the share.

For more information, check of the references at the bottom of this page or try csccmd.exe /help.

Please note that you must manually synchronize now. For example, when I get back to the office, I’ll synchronize.

Additional Speed boost

I also found out that if disconnect the network that shows in My Computer that My Computer and other things work much faster as well. You can do that via the command line. In the example below the network share is mapped to the h drive.

net use h: /delete

Reference:

CSCCMD : Tool to manage Offline files in Windows

Working with network files when you are offline

Friday, October 30, 2009

Disable AutoRecover in Microsoft SQL Server Management Studio

According to numerous posts and my own poking around, Microsoft did not make the Auto Recovery feature configurable. This means you can’t turn it off in the UI or change the frequency of the saves. This is a bit of a pain when I work from home because I have a roaming profile and the location the Auto Recovery feature writes to is on the network and makes SSMS hang.

Thankfully, there are two registry keys that you change.

Put the contents below in a file with a .reg extension, double-click it and this will turn off the auto recovery feature.

Windows Registry Editor Version 5.00

[HKEY_CURRENT_USER\Software\Microsoft\Microsoft SQL Server\90\Tools\Shell\General\AutoRecover]
"AutoRecover Always Enabled"=dword:00000000
"AutoRecover Enabled"=dword:00000000

To turn it back on, just change the AutoRecovery Enabled = 00000001.

Monday, October 26, 2009

Make Windows Firewall tell you when it is blocking something

Depending on how Windows Firewall is configured, it may or may not tell you that it blocked something. If you want to configure it to notify you when it blocks something, just do one of the following.

netsh firewall set notifications ENABLE ALL

or

Use regedit and set the value to zero

HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\WindowsFirewall\DomainProfile\DisableNotifications = 0

You have to stop and start the Windows Firewall service for these settings to take effect. A reboot would also do the trick. :)

Unblock a program that is blocked by Windows Firewall

Sometimes Windows Firewall blocks a program that you need to have network access. Corporate IT sometimes blocks UI access to Windows Firewall so that you can’t add a program to Windows Firewall. Thank goodness you can still do it via the command prompt though.

In the example below I am adding MS SQL Server Management Studio as an Allowed Program to Windows Firewall.

netsh firewall set allowedprogram "C:\Program Files\Microsoft SQL Server\90\Tools\Binn\VSShell\Common7\IDE\SqlWb.exe"

Now I can connect to my SQL Server database running on another server.

Thursday, October 22, 2009

Error: Please wait while Explorer View is loaded. If Explorer View does not appear, your browser may not support it.

Internet Explorer 8, and Windows XP combination breaks Explorer View in SharePoint. If you are running IE8 and an OS such as Windows XP and then open a Document Library in Microsoft Office SharePoint Services (MOSS 2007) and then choose the Explorer view you get the message:

Please wait while Explorer View is loaded. If Explorer View does not appear, your browser may not support it.

Some things you can try:

1. Make sure that when you are on the SharePoint site that it shows you are on a Trusted Site or an Intranet Site. In particular, not an Internet site.

2. Go to your Start Menu | Run... and then type services.msc. Scroll to Web Client and make sure it is set to Automatically start, and that it is started.

3. Instead of going to Explorer view, when you are on the All Documents view, click on the Action menu and choose Open with Windows Explorer. This should open a Windows Explorer view. From here, it will act like a regular Windows Explorer window which is what the Explorer view tries to do, but in the browser.

4. Make sure you have at least Office 2003 installed on your computer. You can also install FrontPage. This will make sure the web components are installed.

5. Install this security patch for IE. The actual link for downloading is: http://www.microsoft.com/downloads/details.aspx?familyid=8799159d-df69-49f6-9db5-49147690ce0c&displaylang=en It is explained here: http://support.microsoft.com/kb/974455/  Follow instructions here: http://support.microsoft.com/kb/972584/ to enable the patch.

6. Go to Start Menu | Control Panel | Add or Remove Programs. Is there an item for Microsoft Office 2003 Web Components? There should be if you are running Office 2003. There should be a similar one for Office 2007

7. Run IE8 in compatibility mode. To do this, open IE8 and go to the Tools menu | Developer tools (F12) | Browser Mode | and then select Internet Explorer 7. This site shows how to do this.

If none of this helps, I recommend down-grading to IE 7 and see if that fixes your issue.

If you still have no luck. Un-install Office 2003 and IE8. Then Re-install Office 2003 or greater. Test to see if it works with IE 6 or IE 7 depending on what what you install. Then if everything works, install IE8. If it stops working, pray for a MS fix.

This thread talks about some of the scenarios and OS’s that are affected. http://social.technet.microsoft.com/Forums/en-US/sharepointgeneral/thread/99d923cd-50e2-4a32-a45d-5df4f8627f37

Tuesday, October 20, 2009

Enable Windows Update

Many times group policy disables Windows Update so that Corporate IT has the ability to control what gets pushed to a corporate computer. Sometimes it is useful to get around this.

As with many hacks to group policies you just need to edit your registry.

Delete the following keys in the registry using regedit

HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Policies\Explorer\NoWindowsUpdate

HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Policies\Windows Update\DisableWindowsUpdateAccess

Do this at your own risk of course. This KB from Microsoft explains different reason why you may be getting the message in the first place. http://support.microsoft.com/default.aspx?kbid=326686

Monday, October 19, 2009

Allowing Windows Scheduled Tasks to be editable

If you have a company computer the Group Policy may not allow you to edit or add new Scheduled Tasks. Luckily, you can override those settings until the Group Policy is refreshed. Below is what needs to be changed in the Windows Registry to allow you to edit or add the Scheduled Tasks (under Start Menu | Control Panels).

Windows Registry Editor Version 5.00

[HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Windows\Task Scheduler5.0] "Disable Advanced"=dword:00000000 "Property Pages"=dword:00000000 "Execution"=dword:00000000 "Allow Browse"=dword:00000000 "DragAndDrop"=dword:00000000 "Task Creation"=dword:00000000 "Task Deletion"=dword:00000000

[HKEY_CURRENT_USER\Software\Policies\Microsoft\Windows\Task Scheduler5.0] "Allow Browse"=dword:00000000

To create a double-clickable file just open up your favorite text editor such as NotePad++ or NotePad. Paste the contents above into your text editor, and save it in a file of any name, as long as the extension is .reg.

Double-click the file to merge the settings into the registry. You should now be able to add or edit your scheduled tasks.

Thursday, October 8, 2009

Volume Shadow Copy (VSS) failing

I was getting all kinds of errors that occurred when Volume Shadow Copy (VSS) was called. I originally saw this in the Windows Event log on my SharePoint server. I then noticed that the Volume Shadow Copy service would not start. I noticed there were errors like “Volume Shadow Copy Service error: Unexpected error LookupAccountName(NULL, domain\username, NULL, p, NULL, p, p). hr = 0x800706fc”. I right-clicked on the my c drive and went to the Shadow Copies tab and it gave me an error of “Initialization failed. Error 0x80080005 Server execution failed.

And finally the easy test to reproduce the error is to go to a command prompt and type vssadmin List Writers. When I run this, I get the following error: “Unexpected failure: Catastrophic failure”.

All this points to VSS being broken and having serious issues. The last command points to the list of writers for VSS not being invalid. In my case, the server was moved from another domain to a new domain. I found out that one of the writers only existed in the old domain and thus could not be found in the current active directory. This makes sense that the LookupAccountName() was generating an error.

To determine what the writers for VSS are, you need to open up the registry and look at the following path:

HKLM\SYSTEM\CurrentControlSet\Services\VSS\VssAccessControl

When I went to the key, I saw that there was a key for a user that does not exist anymore because we switched domains. After I deleted the key, I ran the vssadmin List Writer command line and it worked great. Also, everything else started working also.

Tuesday, October 6, 2009

Add JavaScript file to Master Page in ASP.NET using code-behind

Sometimes it is necessary to include a JavaScript file in your Master Page or any ASP.NET page. There is no problem if all your pages are in the same directory or not using dynamic data and routing. When you start messing with the pattern of the url, hard coded references to a JavaScript file quickly get broken.

Thankfully, there is a relatively easy way to add the JavaScript file using code-behind.

Page.ClientScript.RegisterClientScriptInclude("Validation.js", ResolveClientUrl("~/jscripts/Validation.js"));

In the above example, I have set the key to “Valiadtion.js”, but this could be anything unique. The second parameter uses the ~ so that the proper path is used when developing in a virtual directory and deploying to a separate web site the url works in both cases.

When the html page is generated and sent to the browser, you will see that path changes depending on what directory your page is in. Which is exactly what we needed; a smart path to the JavaScript file.

Friday, October 2, 2009

A bit of understanding how SharePoint Meeting Workspaces link to calendar events

SharePoint allows you to create as many calendars as you like. Each calendar has one or more events. An event is basically an appointment or a series of appointments or calendar item if that term makes more sense. A series just means that it repeats every so often. That could be once a week, daily, monthly, etc. Once you have an event or event series you can may find that you want to track information such as who attended the meeting, the agenda, objectives, etc. You can track all this in what is called a Meeting Workspace. The workspace will show all the information related to an event. Please note, a workspace is NOT linked to a calendar itself. A calendar can have multiple events and each event can have one workspace. If the workspace is related to a event series then the workspace still is linked to an event, but also shows all the events that are in the series in a list of dates on the left side of the workspace.

You can link an event or event series to a Meeting workspace by opening that event or event series on a given calendar. This link describes how to link to a new or existing Meeting workspace. What it does NOT tell is that you can ONLY link to Meeting Workspaces that are BELOW the site that contains your calendar that has the event you are linking to the Meeting workspace. What this means is that if you have a calendar defined in the Meeting workspace itself then you can’t link events on that calendar to that Meeting workspace. You should be able to create a new Meeting workspace below that Meeting workspace, but that doesn’t change the rule I just noted.

A question that came up is “Why is the next event come up by default when I go to the Meeting workspace directly, instead of the last one.” Basically, SharePoint shows the current event if it is the same a today, and will show the next event if the last event has already passed. In general, SharePoint tries to show you the most recent information it has. While this is not always the best, it is by design.

Friday, September 18, 2009

Be careful using hard coded ValidationGroup in a User Control in ASP.NET

Validation Groups are great in ASP.NET. They allow you to validate a group of controls. You tell ASP.NET what Validation group they are in by specifying a ValidationGroup property on each of the controls that you want in that group.

Simple enough. However, I found out the hard way that if you use the ValidationGroup in a user control you have just made it difficult if not impossible to use your user control more than once on a page.

After I realized that I had the issue, and thought about it for a minute it made sense. Let’s say I have two instances of my user control on my page. There are now twice as many controls in the group than intended. This means unless all the fields in both the user controls pass the validation you will have issues such as submit buttons not submitting (assuming they cause validation to fire).

You can verify this, by looking at the html source for your page. In the JavaScript code, search for “ValidationGroup”. You will see that the validation group is just as you typed it your user control. This means that unlike most other things in ASP.NET, the ValidationGroup is NOT automatically adjusted to take its location in the hierarchy of controls.

What does that mean? That means you have to do this yourself. :) All you have to do is set the ValidationGroup dynamically. The easiest way to do this is to create a property in your code behind.

protected string UniqueValidationGroup { get { return ClientID + "MyValidationGroup"; } }

I chose to use take advantage of the ClientID always having the full path from the page to user control as the ClientID. This means that it is unique from JavaScript. I concatenate that with my ValidationGroup name to give a 100% unique ValidationGroup name. That way, no matter where or how many times your user control is used, the ValidationGroup will be unique.

Now that we have have a unique ValidationGroup name, we need to set it dynamically instead of hardcoding it. You can do this in the code-behind, but I think that can be hard to maintain. I like doing it in the code-behind. Where ever you have ValidationGroup hardcoded, just change it to something like the following (depending on what you called your property in your code-behind).

ValidationGroup='<%# UniqueValidationGroup %>'

Check the JavaScript that is generated from this. You will see that the ValidationGroup is set to a long unique name.

Wednesday, September 16, 2009

Sending Email using C#

While it is not difficult to send email via C#, there tends to be a lot of code that I put in place every time. For starts I like to have a class or at least one method that all email in the application go through. The main reason is so that I can quickly see where the method is called and see where emails are sent. This also aids in debugging, and understanding of code when someone comes onto the project.

One of the first things asked is, how do I turn off emails during development. This provides one location that can be commented out. I take it one step further and make it web.config setting. When email is disabled, I still would like to see a log that it would have been sent. So, I have a log file that the email is written to instead of actually sending the email. The log is only used for development purposes.

I also like to have default email to/from incase the unexpected case of no to/from is encounter when I am not expecting it to. This way I am notified. I also pull the smtp server from the web.config instead of hard coding it. You may also notice I call something I call Config instead of accessing the web.config directly. This is because, I again like to have all access my app settings in one class that can be easily changed if needed. For example, in certain instances configuration may need to be stored in a database instead of the config file. This makes that change very easy to make just by changing and testing one class.

Below is the method / code snippet I use:

public void SendEmail(string fromEmailAddress, string toEmailAddress, string subject, string body, HttpPostedFile attachment)
{
// this should never happen, but just so I will know about it
if (string.IsNullOrEmpty(toEmailAddress))
{
toEmailAddress = "myemail@mydomain.com";
}
// this should never happen, but just so I will know about it
if (string.IsNullOrEmpty(fromEmailAddress))
{
fromEmailAddress = "myemail@mydomain.com";
}

MailMessage msg = new MailMessage(fromEmailAddress, toEmailAddress, subject, body);
Attachment attachmentItem;
if (attachment != null)
{
attachmentItem = new Attachment(attachment.InputStream, attachment.FileName);
msg.Attachments.Add(attachmentItem);
}

msg.IsBodyHtml = false;

SmtpClient smtp = new SmtpClient();
smtp.Host = Config.SmtpServer;

if (Config.AllowEmailsToBeSent)
{
smtp.Send(msg);
}
else
{

StreamWriter writer = new StreamWriter(@"c:\Email.log", true);
using (writer)
{
writer.WriteLine("\r\n\r\nDate/Time Stamp: " + DateTime.Now.ToLocalTime());
writer.WriteLine("From: " + msg.From.Address);
writer.WriteLine("To: " + msg.To[0].Address);
writer.WriteLine("Subject: " + msg.Subject);
writer.WriteLine("Body: " + msg.Body);
if (attachment != null)
{
writer.WriteLine(string.Format("Attachment: {0}", msg.Attachments[0].Name));
}
}
}
}

Tuesday, September 15, 2009

What type of objects are in your Outlook Inbox?

I personally find programming against Outlook from .Net (C# in my case) a bit of trial and error. I recently found out that it is not good to assume that any type of folder only has a particular kind of items in it. In particular, I found out that Undeliverable messages are not of type MailItem, they are of type ReportItem. Go figure! The clue for me was that the icon for these messages in Outlook are different than a normal email message.

Below is a snippet of code that will loop through all the items in your inbox and try to cast it to all the types I could find, and prints out some interesting information about the item. If someone knows of other types, please let me know. If an item is of an unknown type it will write that line with “ERROR: Unknown Item at Index:” message.

There are obviously lots of ways you could write this, but this code was my first thought, and it allows me to actually see the exception if one occurs.

// Create an Outlook Application object. 
Microsoft.Office.Interop.Outlook.Application outLookApp = new Microsoft.Office.Interop.Outlook.Application();

// Print all emails.
NameSpace outlookNS = outLookApp.GetNamespace("MAPI");
MAPIFolder theEmails = outlookNS.GetDefaultFolder(OlDefaultFolders.olFolderInbox);

for (int i = 1; i <= theEmails.Items.Count; i++)
{
object obj = theEmails.Items[i];

// **********************
try
{
AppointmentItem item = (AppointmentItem)obj;
WriteLine(item.Subject);
}
catch (System.Exception ex1)
{
// **********************
try
{
ContactItem item = (ContactItem)obj;
WriteLine(item.FirstName);
}
catch (System.Exception ex2)
{
// **********************
try
{
DistListItem item = (DistListItem)obj;
WriteLine(item.Subject);
}
catch (System.Exception ex3)
{
// **********************
try
{
JournalItem item = (JournalItem)obj;
WriteLine(item.Subject);
}
catch (System.Exception ex4)
{
// **********************
try
{
MailItem item = (MailItem)obj;
WriteLine(item.Subject);
}
catch (System.Exception ex5)
{
// **********************
try
{
NoteItem item = (NoteItem)obj;
WriteLine(item.Subject);
}
catch (System.Exception ex6)
{
// **********************
try
{
PostItem item = (PostItem)obj;
WriteLine(item.Subject);
}
catch (System.Exception ex7)
{
// **********************
try
{
TaskItem item = (TaskItem)obj;
WriteLine(item.Subject);
}
catch (System.Exception ex8)
{
// **********************
try
{
ReportItem item = (ReportItem)obj;
WriteLine(item.Subject);
}
catch (System.Exception ex9)
{
WriteLine(string.Format("ERROR: Unknown Item at Index: {0}", i));
}
}
}
}
}
}
}
}

}

}


A good starting point is: http://msdn.microsoft.com/en-us/library/aa289167(VS.71).aspx#ol03cs_topic11

Also see: http://www.outlook-code.com

Friday, September 4, 2009

Custom Event for your ASP.NET User Control

Custom Event are very useful when you want your ASP.NET User Control to have its own custom events or expose the events of controls within the User Control itself. This allows you to for example create a User Control that has events like MyOkButtonClickEvent and instead of implementing the functionality of that button in your User Control, you can leave it up to the Page or other calling object (such as another User Control) to implement the functionality.

To illustrate my example a little better, let’s assume you have a User Control that you created called UserInputPanel. This control has a textfield and two buttons on it. One button called Cancel and another called OK. We don’t want to implement what happens when either of the buttons is clicked in the User Control itself. Instead, we want the calling object (the Page in this example) to.

There are at least two easy ways to implement this. The difference between the two is that in one case you have a chance to partially implement the functionality in our User Control, in the other, you don’t have that option.

Let’s do the one that give you the least amount of control, but requires the least amount of code.

public partial class UserInputPanel : UserControl
{
public event EventHandler OkButtonClicked;
public event EventHandler CancelButtonClicked;

protected void Page_Load(object sender, Event args e)
{
btnOk.Click += new EventHandler(OkButtonClicked);
btnCancel.Click += new EventHandler(OkButtonClicked);
}
}

In the .aspx page all you have to do is implement the event handlers like you would any other event handlers.

<uc1:UserInputPanel ID="input1" runat="server" OnOkButtonClicked="UserInputOKButton_Click" OnCancelButtonClicked="UserInputCancelButton_Click"/>

In the code-behind (C#) for the Page, you will have two methods that implement the desired functionality.

protected void UserInputOKButton_Click(object sender, EventArgs e)
{
// do stuff here
}

protected void UserInputCancelButton_Click(object sender, EventArgs e)
{
// do stuff here
}


This works great if you want to give the entire implementation details to the calling object, but what if you want to do some validation, provide default behavior, etc how can you do this? Luckily, it is quite easy to extend what we already did. All the code is the same from the calling objects perspective. The only difference is that instead of directly wiring the EventHandlers together, we will create a new one which will give us an opportunity to do what we want to do.

public partial class UserInputPanel : UserControl
{
public event EventHandler OkButtonClicked;
public event EventHandler CancelButtonClicked;

protected void Page_Load(object sender, EventArgs e)
{
btnOk.Click += new EventHandler(btnOk_Click);
btnCancel.Click += new EventHandler(btnCancel_Click);
}

protected void btnCancel_Click(object sender, EventArgs e)
{
// do stuff here

if (CancelButtonClicked != null)
{
CancelButtonClicked(sender, e);
}

// do stuff here
}

protected void btnOk_Click(object sender, EventArgs e)
{
// do stuff here

if (OkButtonClicked != null)
{
OkButtonClicked(sender, e);
}

// do stuff here
}

}

In the above example, we could have use the .aspx page to wire up the click events, but I did it in the Page_Load to be consistent with the other way. The nice thing about this is you can create new events also. You could for example create a new event called OkButtonBeforeClick or OkButtonAfterClick. These are useful when you have partially implemented functionality in the User Control and want to give the calling object more opportunities to fine-tune their functionality.

A side note: Please note that am exposing the EventHandlers as public member variables. This is technically not the Object Oriented way to do it. You should create properties for each event and make the event private instead of what I did, but it just seems so much cleaner this way. Less code :)