Monday, December 24, 2007

Performing Operations on Infopath 2007 Contact Selector Control using Managed Code

Infopath 2007 provides an out of the box contact selector control to select the user and validate against the Active Directory.


In this blog, We will see, how to get more out of this control. Performing some advanced functions using managed code.


For basic usage of this control, see this blog entry on infopath blog:

http://blogs.msdn.com/infopath/archive/2007/02/28/using-the-contact-selector-control.aspx


To start with basics, this control has predefined schema, since it simultaneously stores the display name, account id and account type.

  user display name
  DOMAIN/user account
  user or group type
 
It is interesting to note that this control behaves like a repeating control, in a sense,
the user can select multiple users from the same control. Internally, the XML schema
shown above is repeated for multiple users.
 
2. Get the Display Names and Login Names for all users in contact Selector Control
 
To get the display names and login names, we just need to parse the generated XML
schema. We will store the names and login names as comma separated values.
Assuming that our control name is gpContactSelector, the code below extracts the
display names and login names.
 

XPathNavigator xNavMain = this.XmlFormView2.XmlForm.MainDataSource.CreateNavigator();

XmlNamespaceManager xNameSpace = new XmlNamespaceManager(new NameTable());

xNameSpace.AddNamespace("my", "http://schemas.microsoft.com/office/infopath/2003/myXSD/2007-11-20T20:01:12");

XPathNodeIterator[] nodes = new XPathNodeIterator[4];

nodes[0] = xNavMain.Select("/my:myFields/my:gpContactSelector/my:Person/my:DisplayName", xNameSpace);

nodes[1] = xNavMain.Select("/my:myFields/my:gpContactSelector/my:Person/my:AccountId", xNameSpace);

nodes[2] = xNavMain.Select("/my:myFields/my:gpContactSelector/my:Person/my:AccountType", xNameSpace);

string names=string.Empty;

string accid=string.Empty;

for (int j = 0; j <>

{

for (int i = 0; i <>

nodes[i].MoveNext();

if (nodes[2].Current.ToString() == "User")

{

names = names + nodes[0].Current.ToString()+";";

accid = accid + nodes[1].Current.ToString()+";";

}

        }
 
The code above is pretty self explanatory. It parses the generated XML Schema
and stores the Names and login ids in two variables, names and accid as semicolon
separated values. Further operations can be then performed on these.
 
2. Sending Emails to All users selected in Contact Selector
 
To send emails, we obviously need email addresses of the contacts selected.
However, contact selector does not automatically grabs out the email addresses
of the contacts. To get the email addresses, we will first extract the login names
from the XML schema and then use the Microsoft.SharePoint.Utilities.SPUtility.GetFullNameandEmailfromLogin
class to get the email addresses.
 
The code below accepts the login names as semicolon separated values and builds
a string containing email addresses as semicolon separated values.
 

private string GetEmails(string final)

{

char[] a = { ';' };

string[] loginIds = final.Split(a, StringSplitOptions.RemoveEmptyEntries);

string[] emailids = new string[loginIds.Length];

for (int i = 0; i <>

{

Microsoft.SharePoint.Administration.SPGlobalAdmin ga = new Microsoft.SharePoint.Administration.SPGlobalAdmin();

string dispname, email;

Microsoft.SharePoint.Utilities.SPUtility.GetFullNameandEmailfromLogin(ga, loginIds[i], out dispname, out email);

emailids[i] = email;

}

string finalstring = string.Empty;

for (int i = 0; i <>

finalstring = finalstring + emailids[i] + ";";

return finalstring;

}

 
 
 
Now, we can use using System.Net.Mail namespace to send mails. This namespace
overrides the System.Web.Mail used in .NET 1.1. For those who are new to this
namespace, below is the sample code given to send mail.

private void SendMail()

{

MailMessage mail = new MailMessage();

mail.From = new MailAddress("Admin@domain.com", "Administrator");

char[] a = { ';' };

string[] emailIds = to.Split(a, StringSplitOptions.RemoveEmptyEntries);

for (int i = 0; i < style="">

mail.To.Add(new MailAddress(emailIds[i]));

mail.Subject = "New Meeting Request";

mail.Priority = MailPriority.Normal;

mail.IsBodyHtml = true;

mail.Body = GetBody();

new SmtpClient("smtpserver").Send(mail);

}

Sunday, December 16, 2007

Doing Data Validations in Infopath 2007 Programmatically

Infopath 2007 provides rich validation function for each of its controls. For ex, you can validate a text box or a field

For conditions such as equal to, is blank, is greater than and if that field fails the validation, we can display a message box.


One important thing to be noted is that, Infopath 2007 would not submit the form unless all the controls pass the validation.


Now, let’s talk about the complex validation, let’s say, I want to validate whether or not the textbox contains email address or not.

Surprisingly, this can be also done out of the box using pattern matching. If we look at the validation options in Infopath, one finds the options matches pattern, does not matches pattern. Here one can specify a complex regular expression to be matched.


Custom Validation


Now we come to our main topic, custom validation, means validation using some business logic which Infopath cannot provide out of the box.

To implement custom validation, each control in Infopath provides a validating event, which is usually called when the value of the control Changes. This is almost same as Changed event, with one difference that during the execution of validating event, the whole document tree is read only. That is, we cannot write to any field in Infopath while validating event is being executed.


The reason for this is that, the values of Infopath fields can change due to the change in values of other fields, which will result in Validating event to be called again and again, until the stack runs out of memory.


Now, coming to our main point, validating event, here we will see how to validate a simple field in Infopath, programmatically.

To write managed code in Infopath, you must have either VSTO or VSTA installed.


To write a validating event for a field, right click the field and choose Programming ->validating event


The code editor will open up the desired function for inserting the code for the validating event for particular field.

Below is an example of one of the typical implementation of validating event




public void OtherAttendees_Validate(object sender, XmlEventArgs e)


{


try


{


this.Errors.Delete("Invalid Value");


}

catch (Exception) { }


string str = e.Site.Value;

bool flag = CheckAttendees(str);



if (flag == false)

{


this.Errors.Add(e.Site, "Invalid Value", "The value of the "+ e.Site.LocalName + " field must be email addresses seperated by ;");


}


}



To understand the above code, we must first realize that this function will be called as soon as the value of the field will be changed.


We clean up the Errors collection prior to validating our field using this.Errors.Delete();

CheckAttendees function implements the business logic which determines that weather the field is valid or not. The input to the function is nothing but the value of the field.


Now, if the decide that field validation has failed, we add an error into the Error Collection using this.Errors.Add(), indicating the value and the custom error message we want to be displayed.


You can find out about the Errors collection here:

http://msdn2.microsoft.com/en-us/library/bb229714.aspx


Now the point is, if the Errors Collection contains an item, the Infopath will not allow to submit the form. And, if you try to submit it, it will highlight the particular field and display the custom error message.



Tip about Validation and Submit in Browser Forms


This sort of validation works fine in client form since everything is executed on client. However in browser based forms, the user would not come to know about the failure in validation until he clicks submit and this would cost an expensive server page postback.


To overcome this limitation, we can implement the same logic in Changed event handler and set a value of hidden field which would indicate the validity of the form at the time of submission. Now on submit button, we can use custom rules to check the value of the hidden field and prevent the submission at the client side only. This prevents considerable overhead of page postback.

Monday, November 26, 2007

Train Signal Sharepoint 2007 Training Kit – A review

Last week, I received a complimentary copy of the Train Signal’s Sharepoint 2007 raining kit. The training I found it to be very interactive one with audio/video tutorials.


The training covers almost all of these topics comprehensively:


1. Lab Setup
2. Installation of SharePoint Services 3.0
3. Web Apps and Site Collections
4. Site Creation
5. Site Building Blocks
6. More Site Building Blocks
7. Adding Users and Groups
8. Document Libraries
9. Custom Content Items and Lists
10. Alerts and RSS
11. Monitoring SharePoint
12. Back Up and Restore
13. Setting Up an Extranet Environment
14. SSL Configuration
15. Microsoft Office SharePoint Server 2007 Installation
16. Workflows
17. Personalization and MySite
18. Excel Services and Report Center
19. Search Configuration for MOSS
20. Site Template and Publishing Portal


The training starts with guiding you to setup your own Sharepoint 2007 environment in Virtual Server 2005. Installation of Sharepoint 2007 given is also comprehensive.

The training in beginning starts from simple things like creating document libraries, configuring users and then goes on to advanced concepts like Creating custom content types and configuring email for Sharepoint 2007. In fact, I was impressed by some of the topics covered like securing a site using SSL, setting up Extranet which have not been usually covered by other training kits and bloggers.


The instructor manages to get straight to the point on most all the subjects and can have you successfully administering the Sharepoint 2007 server in a short amount of time. The class seems aimed at the administrator for the most part but developers would also benefit from it. In my view, this training kit could prove a boon for a complete beginner in Sharepoint as it does not assume any previous knowledge in Sharepoint and takes things from scratch.


The training kit covers from set up to detailed creation of web applications, sites, document libraries and work flow. There is a load of information in the presentations. The presentations are then followed up with demonstrations. The presentations are split up in easily accessible videos integrated using a Flash program to review and re watch the segments you need to use as a refresher.

The good thing about training is that the instructor explains each and every option like Host headers, Quota templates, which can be quite confusing for a beginner.



To summarize the review of this training kit:



This is a great learning aid. Takes you from scratch to quite advanced SPS/WSS administration and customization quickly if you go straight at it, or a little at a time if that's all the time you have. The presentation style is relaxed and adds enormously to the rate at which concepts are picked up. I have often been disappointed by the depth of knowledge that some 'trainers' actually have, but this package is supported by vast in-depth knowledge of the product.


Score: 9/10


Pros:

  • Interactive with audio video
  • Each and every topic explained in detail
  • Very useful for a complete beginner in sharepoint


Cons:

  • More Admin oriented rather than developer

Wednesday, August 08, 2007

How to put a latest post of your blog on sharepoint homepage

Recently, we had a requirement to create a blog site and also put a latest blog entry on the homepage of the

sharepoint site. To make things difficult, customer wanted only the preview of the latest post i.e. first few lines

and an out of the box solution :)

I was stuck with an awesome idea to accomplish this with DataForm webpart in MOSS 2007 and bingo, it worked ! Here is how to do it :

Open the page on which you want to place the blog entry in Sharepoint Designer. If its a publishing page, SPD wont allow you to directly open it, but instead it will open the page layout instead. To circumvent this, create a webpart page through sharepoint and open it in sharepoint Designer.

Once the page is open in sharpoint designer, Go to Insert -> Data View. It will open a data sources pane to select the data source. However our data source is a seperate blog site. Thus, Choose Connect to another library, Enter a display name and enter the URL of the blog site.

Now you will get the lists of all lists and libraries from the blog site. Choose the list Posts as it is the list in which blog posts are contained by default.

Once you have inserted the dataview, now we have to customize the XSLT of the DataForm webpart to make it show us only a preview of single post.

For this,

* Strip of the headers of the list, called Title, Body etc. enclosed inside the <th> tags, we dont want to show the headers obviously.

* Now to show the preview, we will make use of the function

<xsl:value-of select="substring-before(@Body,'<p')" disable-output-escaping="yes"/>

What this function does is, gives us all of the string upto the string <p. Now since @Body is in actual an HTML representation of the body post,

we will get the first paragraph of the blog in the result. disable-output-escaping="yes" will make sure that it does not renders the HTML as it is.

* You can also sort it by Modified to show the latest entry automatically.

For those, who does not want to make these modifications themselves, I am giving the snippet of the XSL which I used here :


<xsl:stylesheet xmlns:x="http://www.w3.org/2001/XMLSchema" xmlns:d="http://schemas.microsoft.com/sharepoint/dsp" version="1.0" exclude-result-prefixes="xsl msxsl ddwrt" xmlns:ddwrt="http://schemas.microsoft.com/WebParts/v2/DataView/runtime" xmlns:asp="http://schemas.microsoft.com/ASPNET/20" xmlns:__designer="http://schemas.microsoft.com/WebParts/v2/DataView/designer" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt" xmlns:SharePoint="Microsoft.SharePoint.WebControls" xmlns:ddwrt2="urn:frontpage:internal">
<xsl:output method="html" indent="no"/>
<xsl:decimal-format NaN=""/>
<xsl:param name="dvt_apos">'</xsl:param>
<xsl:variable name="dvt_1_automode">0</xsl:variable>
<xsl:template match="/" xmlns:x="
http://www.w3.org/2001/XMLSchema" xmlns:d="http://schemas.microsoft.com/sharepoint/dsp" xmlns:asp="http://schemas.microsoft.com/ASPNET/20" xmlns:__designer="http://schemas.microsoft.com/WebParts/v2/DataView/designer" xmlns:SharePoint="Microsoft.SharePoint.WebControls">
<xsl:call-template name="dvt_1"/>
</xsl:template>

<xsl:template name="dvt_1">
<xsl:variable name="dvt_StyleName">Table</xsl:variable>
<xsl:variable name="Rows" select="/dsQueryResponse/Rows/Row"/><table border="0" width="100%" cellpadding="2" cellspacing="0">
<tr valign="top">
<xsl:if test="$dvt_1_automode = '1'" ddwrt:cf_ignore="1">
<th class="ms-vh" width="1%" nowrap="nowrap"></th>
</xsl:if></tr>
<xsl:call-template name="dvt_1.body">
<xsl:with-param name="Rows" select="$Rows"/>
</xsl:call-template>
</table>
</xsl:template>
<xsl:template name="dvt_1.body">
<xsl:param name="Rows"/>
<xsl:for-each select="$Rows">
<xsl:call-template name="dvt_1.rowview"/>
</xsl:for-each>
</xsl:template>
<xsl:template name="dvt_1.rowview">
<tr>
<td class="ms-vb">
Posted : <xsl:value-of select="ddwrt:FormatDate(string(@Created),1033,3)"></xsl:value-of> by <xsl:value-of select="@Editor" disable-output-escaping="yes" />
<p/>
<b><xsl:value-of select="@Title"></xsl:value-of></b><p/>
<xsl:value-of select="substring-before(@Body,'<p')" disable-output-escaping="yes"/>
<p/>
</td><td class="ms-vb">
</td><xsl:if test="$dvt_1_automode = '1'" ddwrt:cf_ignore="1">
<td class="ms-vb" width="1%" nowrap="nowrap">
<span ddwrt:amkeyfield="ID" ddwrt:amkeyvalue="ddwrt:EscapeDelims(string(@ID))" ddwrt:ammode="view"></span>
</td>
</xsl:if>
</tr>
<tr>
<td><a class="ms-vb" href="
http://sp.sony.com/sel/CorporateMarketing/multichannel/blog">Read More</a></td>
</tr>
</xsl:template></xsl:stylesheet>

Wednesday, July 11, 2007

SharePoint 2007: using ASP.NET server side code in your pages

This is fairly undocumented piece of information which I have got from this blog

http://www.bluedoglimited.com/SharePointThoughts/ViewPost.aspx?ID=242

I havn't tried it myself, so I cannot gaurantee its completeness. I am posting it

here for the sake of interest and community .....

In the web.config file in the Sharepoint virtual directory contains the following section:

<Sharepoint>
<SafeMode MaxControls="200" CallStack="false" DirectFileDependencies="10" TotalFileDependencies="50" AllowPageLevelTrace="false">
<PageParserPaths>
</PageParserPaths>
</SafeMode>
:
</Sharepoint>

By default the node <PageParserPaths> is empty. You can add <PageParserPath> nodes to specify the

virtual paths where you want to allow server side scripts:

<PageParserPaths>
<PageParserPath VirtualPath="/pages/*" CompilationMode="Always" AllowServerSideScript="true" IncludeSubFolders="true"/>
</PageParserPaths>

Where CompilationMode is one of the following values:

Always

The page should always be compiled (default value)

Auto

ASP.NET will not compile the page, if possible.

Never

The page or control should never be dynamically compiled.

I assume that the AllowServerSideScript and IncludeSubFolders flags speak for themselves.

Be careful with the virtual paths you specify in your PageParserPaths. Anyone that can modify or add a page to the virtual path can insert code that will be executed server side with no restrictions.

A good location to specify as a PageParserPath is the location where you store your masterpages, for example /_catalogs/masterpage. You can now add server side script to your masterpages, which makes it available in all pages using this masterpage.

<PageParserPaths>
<PageParserPath VirtualPath="/_layouts/masterpage/*" CompilationMode="Always" AllowServerSideScript="true" IncludeSubFolders="true"/>
</PageParserPaths>

Preventing caching of page : Strategies in ASP.NET 2.0 and MOSS 2007

Here I would discuss strategies to disable caching in your pages especially related to ASP.net and MOSS 2007.

If you wanted to prevent caching in plain old HTML, you would use these directives

<meta http-equiv="Expires" content="0">
<meta http-equiv="Pragma" content="no-cache">
<meta http-equiv="Cache-Control" content="no-cache">

I wont explain these tags here. You can refer to W3C HTML specification. Rather I would focus on how

to insert these HTML tags while developing in ASP.NET and MOSS 2007 i.e. master - content pages.

1) If you using plain aspx pages you can directly put these tags under the

<head> section and you are done.

If you are using master - content pages, you can add this to master page but beware, this will prevent

caching of all the pages.


Usually you are looking to prevent the caching of single page which contains dynamic data.

In that case, you can use strategies given below.


If you are using MOSS 2007, and want to prevent the caching of a content page, there are two choices:

  • Create a new page layout which contains these tags and bind that page to this page layout.
    This way you can, create more pages which do not get cached using this layout.

  • Entirely detach the page from its layout using Sharepoint Designer. This way, the markup is copied to the
    aspx page itself and you can add these tags easily. However this is not a recommended procedure as this
    is not scalable.

2) Add a contentplaceholder inside the <head> in your master page. Then in
any pages that need these headers, populate the content control with
these meta tags. Other pages don't need to populate (or even have) the
content control.

I do this a lot and it's much easier

If you want to do this programmatically, you can put this line in a webpart or custom control :

Page.Master.FindControl("Content PlaceHolder").InnerHtml = < no caching tags >

3)

This is a small piece of code, which programmatically adds the <meta http-equiv="refresh" ... />

to the <head> page.

void DisableBrowserCaching(HtmlHead hdr)
{
Control ctrl = (Control)hdr;

string currGMT =
DateTime.Now.ToUniversalTime().ToLongDateString() +
" " + DateTime.Now.ToUniversalTime().ToLongTimeString() + "
GMT";

HtmlMeta meta = new HtmlMeta();
meta.Name = "expires";
meta.Content = currGMT;
ctrl.Controls.Add(meta);

meta = new HtmlMeta();
meta.HttpEquiv = "pragma";
meta.Content = "no-cache";
ctrl.Controls.Add(meta);

meta = new HtmlMeta();
meta.HttpEquiv = "cache-control";
meta.Content = "no-cache";
ctrl.Controls.Add(meta);

}

You can use this code like this, in your controls or webparts

HtmlHead hdr = Master.Page.Header;
util.DisableBrowserCaching(hdr);

or if you are not using master pages, you can directly pass the Page.Header.

This works and, I think, is more general for the different browser
behaviors. Also, it allows me to set the behavior for any page, whether a
master page is involved or not.