One of the nice things about SharePoint 2010 is that the developers did not seal the classes of the common web parts, so we can now create our own web parts that derive and extent the functionality. I thought it might be fun to play with the search refiner web part that I blogged about recently, so I took the standard search refiner and plugged in the excellent tag cloud implementation from codeplex. So instead of rendering the normal list of refiners with counts against them, it instead uses the count to determine the tag cloud size:  The code for creating this is below:
public class VisualRefiner : Microsoft.Office.Server.Search.WebControls.RefinementWebPart
{
protected override void Render(System.Web.UI.HtmlTextWriter writer)
{
var xmlDoc = this._RefinementManager.GetRefinementXml();
var filters = xmlDoc.GetElementsByTagName("Filter");
Dictionary tags = new Dictionary();
foreach (XmlNode filter in filters)
{
var valueNode = filter.SelectSingleNode("Value");
var countNode = filter.SelectSingleNode("Count");
if (countNode != null && valueNode != null)
{
if (!string.IsNullOrEmpty(countNode.InnerText))
{
int tagCount;
if (int.TryParse(countNode.InnerText, out tagCount))
{
if (!tags.ContainsKey(valueNode.InnerText))
{
tags.Add(valueNode.InnerText, tagCount);
}
}
}
}
}
//TODO: Make the url useable
var tagCloud = new TagCloud(tags,
new TagCloudGenerationRules { Order = TagCloudOrder.Random, TagUrlFormatString = "/pages/search.aspx" });
writer.Write(tagCloud);
}
}
I’m not saying that this view adds any more value than the standard refiner view, in the screenshot above it actually looks pretty poor.
The interesting part about the web part is the use of the refinement manager, it returns the results of the refinement process in xml form. This is really powerful, it means that we could potentially create some really creative visuals around the search results. It will be interesting to watch the community to see what types of variations come out.
One of the more interesting features of SharePoint 2010 is the activity feed, it’s kind of like the news feed in Facebook a whole range of system wide events are collated in a users my site, these can include events generated by colleagues of a user. I really think this is cool and we will see lots of applications make use of this feature. Even in a business environment the concept of a status feed is useful, SharePoint 2010 provides what seems like a nice enterprise wide service for collecting these events. I thought it might be fun to have a play with some of the activity feed API’s to create something like a Twitter web part for SharePoint 2010, just to be upfront, this code isn’t really useable in a real sense, its just me playing around. First I’ve created a visual web part for the user to enter a tweet: Now if your a colleague, you can visit your ‘MySite’ and view the aggregated activities of your colleagues: Here you can see that the user ‘administrator’ (this person’s colleague) has ‘tweeted’ something. The SharePoint team have made this whole status/activity feed quite extensible, the first step is to define your own activity application Setup: private ActivityApplication actApplication = null;
private ActivityManager actMgr = null;
private UserProfile currentUserProfile = null;
private ActivityType tweetActivityType = null;
private ActivityTemplate tweetActTemplate = null;
private void SetupActivities()
{
Microsoft.Office.Server.UserProfiles.UserProfileManager pm = new UserProfileManager(SPServiceContext.Current);
currentUserProfile = pm.GetUserProfile(Page.User.Identity.Name);
actMgr = new ActivityManager(currentUserProfile);
tweetActivityType = null;
if (actMgr.PrepareToAllowSchemaChanges())
{
if (actMgr.ActivityApplications["SP2010Twitter"] == null)
{
actApplication = actMgr.ActivityApplications.Create("SP2010Twitter");
actApplication.Commit();
}
else
{
actApplication = actMgr.ActivityApplications["SP2010Twitter"];
}
tweetActivityType = actApplication.ActivityTypes["SP2010Tweeting"];
if (tweetActivityType == null)
{
tweetActivityType = actApplication.ActivityTypes.Create("SP2010Tweeting");
tweetActivityType.ActivityTypeNameLocStringName = "ActivityName";
tweetActivityType.ActivityTypeNameLocStringResourceFile = "SP2010Twitter";
tweetActivityType.IsPublished = true;
tweetActivityType.IsConsolidated = true;
tweetActivityType.AllowRollup = true;
tweetActivityType.Commit();
}
tweetActTemplate = tweetActivityType.ActivityTemplates[ActivityTemplatesCollection.CreateKey(false)];
if (tweetActTemplate == null)
{
tweetActTemplate = tweetActivityType.ActivityTemplates.Create(false);
tweetActTemplate.TitleFormatLocStringResourceFile = "SP2010Twitter";
tweetActTemplate.TitleFormatLocStringName = "Activity_Created";
tweetActTemplate.Commit();
}
}
else
{
//not a profile admin
var activities = actMgr.GetActivitiesForMe();
foreach (var act in activities)
{
if (act.Name == "SP2010Twitter")
{
actID = act.ActivityTypeId;
}
}
}
}
The code above needs to be run once, to setup the application and to define the templates etc.
Creating an event: public ActivityEvent GenerateActivityEvent(string tweet, long activityId)
{
Entity owner = new MinimalPerson(CurrentUserProfile).CreateEntity(ActManager);
Entity publisher = new MinimalPerson(CurrentUserProfile).CreateEntity(ActManager);
ActivityEvent activityEvent = ActivityEvent.CreateActivityEvent(ActManager, activityId, owner, publisher);
activityEvent.Name = "SP2010Twitter";
activityEvent.ItemPrivacy = (int)Privacy.Public;
activityEvent.Owner = owner;
activityEvent.Publisher = publisher;
activityEvent.Value = tweet;
activityEvent.Commit();
return activityEvent;
}
Publishing the event to the user's colleagues:
public void MulticastPublishedEvents()
{
if (activityEvents.Count == 0)
return;
List publishers = new List();
foreach (ActivityEvent activityEvent in activityEvents)
{
if (!publishers.Contains(activityEvent.Owner.Id))
publishers.Add(activityEvent.Owner.Id);
}
Dictionary owners;
Dictionary> colleaguesOfOwners;
ActivityFeedGatherer.GetUsersColleaguesAndRights(ActManager, publishers, out owners, out colleaguesOfOwners);
Dictionary> eventsPerOwner;
ActivityFeedGatherer.MulticastActivityEvents(ActManager, activityEvents, colleaguesOfOwners, out eventsPerOwner);
List eventsToMulticast;
ActivityFeedGatherer.CollectActivityEventsToConsolidate(eventsPerOwner, out eventsToMulticast);
WriteEvents(eventsToMulticast);
}
private void WriteEvents(List events)
{
int startIndex = 0;
while (startIndex + ActManager.MaxEventsPerBatch < events.Count)
{
ActivityFeedGatherer.BatchWriteActivityEvents(events, startIndex, ActManager.MaxEventsPerBatch);
startIndex += ActManager.MaxEventsPerBatch;
}
ActivityFeedGatherer.BatchWriteActivityEvents(events, startIndex, events.Count - startIndex);
}
Publishing an event is a little more involved and this code was drawn heavily from the SDK sample found here.
The main classes that get used here are:
ActivityApplication
An activity application is simply the name of the application, the ‘NewsFeed Settings’ on the my site gives the end user the ability to participate with your application:
ActivityManager
Simple manager class that gets initialised with the current user profile, this class gets passed around to the other classes that create events.
ActivityType
This class provides us with the ability to actually define the activity, things like the time to live, is rolled up? and the template to use. This class also requires the developer to define the resources file that will be used to display the activity name.
ActivityTemplate
The display of the activity event gets managed by this class, you need to specify a resources file and key that contains the elements to create the display text. This class and associated resources file controls the UI presented to the user.
ActivityEvent
This is the actual event, here we define the publisher and other things like the item privacy level, it’s possible to create public and private events.
ActivityFeedGatherer
The gatherer is probably the least intuitive class, it provides the ability to batch write the events, this is handy because often we will want to publish the events to all of the colleagues of the current user.
Resources File:
Like I mentioned above, all of the templates and UI elements must be in a resx file. This file must be deployed to:
{SharePointRoot}\Resources\SP2010Twitter.resx
And will contain something like (note how the ActivityName and Activity_Created are used in the code above):
<data name="ActivityName" xml:space="preserve"> <value>SP2010Twitter</value> </data> <data name="Activity_Created" xml:space="preserve"> <value>{Publisher} tweeted: {Value}</value></data>
Some of the Default Template Variables:
{Link}
{Value}
{Publisher}
{Size}
So above I made use of the {Value} variable and populated the value property of the event object with the tweet value. I could change the resx definition which would change the way the event is shown to the user.
Problems:
I’ve come across a few little problems, firstly it seems like the user creating the event needs to be a profile admin, even a call to ActivityEvent.CreateActivityEvent seems to require the profile admin property. Unless I’m missing something, I hope this changes in the RTM environment.
You can download the sample code here
Ever since the sneak peak developer videos were released months ago I’ve been wondering about the implementation of SharePoint 2010’s visual web parts. If your not sure what I mean, with SP 2010 and Visual Studio 2010 you can now create a web part with a design time experience, so you can drag and drop controls etc:  Now that the beta is upon us I can finally take a look under the covers at a visual web part, the default project structure looks like: The project contains a number of new items: Features and Package both relate to the deployment features of Visual Studio 2010 in that you can create SharePoint solutions (aka the Package) and features which can be activated, visual studio will automatically deploy and activate your web parts using this solution and features. Next we move on to the VisualWebPart1.cs file which contains the secret sauce: public class VisualWebPart1 : WebPart
{
// Visual Studio might automatically update this path when you change the Visual Web Part project item.
private const string _ascxPath = @"~/_CONTROLTEMPLATES/TestVisualWebPart/VisualWebPart1/VisualWebPart1UserControl.ascx";
public VisualWebPart1()
{
}
protected override void CreateChildControls()
{
Control control = this.Page.LoadControl(_ascxPath);
Controls.Add(control);
base.CreateChildControls();
}
protected override void RenderContents(HtmlTextWriter writer)
{
base.RenderContents(writer);
}
As you can see, the web part still derives from WebPart, no special VisualWebPart base class, nothing special going on here.
In fact we are using the same techniques and approach that would have worked in Visual Studio 2008, the only difference now is that Visual Studio 2010 has better tooling support for SharePoint 2010 and will deploy the ascx file automatically for us to the _CONTROLTEMPLATES directory as part of the solution.
There are still a few things a web part developer should know, lets look at the case where we want to expose some custom properties on a web part that we want a user to configure via the web interface:
[System.Web.UI.WebControls.WebParts.WebBrowsable(true),
System.Web.UI.WebControls.WebParts.WebDisplayName("Custom Prop"),
System.Web.UI.WebControls.WebParts.WebDescription(""),
System.Web.UI.WebControls.WebParts.Personalizable(
System.Web.UI.WebControls.WebParts.PersonalizationScope.Shared),
System.ComponentModel.Category("Settings"),
System.ComponentModel.DefaultValue("")
]
public string CustomProp
{
get { return customProp; }
set { customProp = value; }
}
Now if we put this property and attributes on the VisualWebPart1UserControl (in VisualWebPart1UserControl.ascx.cs) we will find that the custom property builder won’t appear (the web interface that lets us set a value to this property).
We have to add the custom property on the VisualWebPart1 class (in VisualWebPart1.cs) :
public class VisualWebPart1 : WebPart
{
// Visual Studio might automatically update this path when you change the Visual Web Part project item.
private const string _ascxPath = @"~/_CONTROLTEMPLATES/TestVisualWebPart/VisualWebPart1/VisualWebPart1UserControl.ascx";
protected override void CreateChildControls()
{
Control control = this.Page.LoadControl(_ascxPath);
Controls.Add(control);
base.CreateChildControls();
}
protected override void RenderContents(HtmlTextWriter writer)
{
base.RenderContents(writer);
}
[System.Web.UI.WebControls.WebParts.WebBrowsable(true),
System.Web.UI.WebControls.WebParts.WebDisplayName("Custom Prop"),
System.Web.UI.WebControls.WebParts.WebDescription(""),
System.Web.UI.WebControls.WebParts.Personalizable(
System.Web.UI.WebControls.WebParts.PersonalizationScope.Shared),
System.ComponentModel.Category("Settings"),
System.ComponentModel.DefaultValue("")
]
public string CustomProp
{
get { return customProp; }
set { customProp = value; }
}
}
Now we get our custom property builder:
Lets assume that we want to pass the user entered value to the Visual component (the usercontrol) we now need to change the visual studio generated code to cast the user control to our visual user control class, rather than the more generic base Control: protected override void CreateChildControls()
{
//user control is of type VisualWebPart1UserControl and defined with private scope
userControl = (VisualWebPart1UserControl)this.Page.LoadControl(_ascxPath);
Controls.Add(control);
base.CreateChildControls();
}
From here we can set properties on the userControl variable as normal.
The same principles apply to web part connections, so the connection points need to be defined on the web part class (not the usercontrol). Visual Studio will take care of deploying the ascx file which is still a big win.
I doubt an experienced web part developer would have any issues, but I wonder how many new web part developers will not know that they can make there web parts configurable and connectable given that they will likely only use the Visual Studio lie presented to them?
I’ve been working on a project that involves implementing a blogging system inside SharePoint, the out of the box SharePoint blogging system was unsuitable and the Community Kit for SharePoint didn’t solve our issues. So the system we developed was lightly based on the open source .NET Blog Engine which includes comprehensive support for the MetaWebBlog API. This API allows a interaction with a blogging system so that blog posts can be added, deleted and updated, but it also provides provisions for managing things like categories and comments. The main motivation for supporting the MetaWebBlog API is Windows Live Writer (WLW) this great tool really makes publishing to a blog a simple task. So I happily added the MetaWebBlog API and quickly found that windows live writer doesn’t support windows authentication. WLW supports a nice feature called Really Simple Discovery (RSD) where it can just be pointed to a blog’s home page and it can get all the configuration needs. By not supporting windows authentication we lost the auto discovery features. So I could still enable anonymous access to the MetaWebBlog.axd and wlwriter.xml (a file that tells WLW what capabilities your blog has), so now with some manual steps in WLW I could select the MetaWebBlog API and give it the url to use. My next problem was that my blogging system was all based around windows authentication. Every user has a blog that is based around their network credentials, I needed my metawebblog API implementation to validate the user to the windows network: With the following P/Invoke definitions: [DllImport("advapi32.dll", SetLastError = true)]
public static extern bool LogonUser(String lpszUsername, String lpszDomain, String lpszPassword,
int dwLogonType, int dwLogonProvider, ref IntPtr phToken);
[DllImport("kernel32.dll", CharSet = CharSet.Auto)]
public extern static bool CloseHandle(IntPtr handle);
[DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public extern static bool DuplicateToken(IntPtr ExistingTokenHandle,
int SECURITY_IMPERSONATION_LEVEL, ref IntPtr DuplicateTokenHandle);
Now in all of my metawebblog API calls I could use a method like:
string username = string.Empty;
string password = input.Password;
string domain = string.Empty;
if (input.UserName.Contains(\\))
{
string[] usernameParts = input.UserName.Split('\\');
if (usernameParts.Length > 1)
{
domain = usernameParts[0];
username = usernameParts[1];
}
}
else
{
username = input.UserName;
}
IntPtr tokenHandle = IntPtr.Zero;
bool returnValue = LogonUser(username, domain, password,3, 0, ref tokenHandle);
if (returnValue == false)
{
//error not a valid user .. return
throw new MetaWeblogException("11", "User authentication failed");
}
if (tokenHandle != IntPtr.Zero)
CloseHandle(tokenHandle);
This code will return an invalid authentication message to WLW if the user doesn’t provide the correct domain, username and password (you could easily remove the need for a domain, I needed to keep it).
I’d like to say that its all good but really end users won’t go to the effort of following steps to configure WLW, the RSD is a killer feature in making web blogs accessible to end users. Fingers crossed that WLW will support windows authentication in a future version.
By default SharePoint will use impersonation, so the web.config file will have the setting: <identity impersonate="true" />
This means that if you try and connect to a SQL server database from say a custom web part, the connection will appear to SQL server as the user that requested the page. This is very useful if you care about the actual user, so for example if the database has permissions set based on this assumption.
However it is often useful to have just the application pool account connect to the SQL Server database or you may wish to give the application pool account permissions to connect to active directory but not every user.
It this case you will need to impersonate the application pool account. There is a bit of code floating around the web that uses P/Invoke to call ReverToSelf() from the advapi32.dll. It turns out it’s simpler than that:
using (HostingEnvironment.Impersonate()) { // access external resource as app pool account }
The HostingEnvironment.Impersonate() will do the same thing that as a call to RevertToSelf().
If you like me and organise your data access calls into nice methods that perform a discrete action such as:
public static void DeleteUser(int userID) { //do some database access }
You might be really loathed to wrap all these methods with the same code like:
public static void DeleteUser(int userID) { using(HostingEnvironment.Impersonate()) { //do some database access } }
Instead you might like to take a look at what a project such as PostSharp can offer:
"You can make your own custom attributes that will really add new behaviors to your code! This is sometimes called aspect-oriented programming (AOP) or policy injection."
This means that you can define your own attribute that will have code that will run on any invocation of your code:
public class ImpersonateAttribute : OnMethodInvocationAspect { public override void OnInvocation(MethodInvocationEventArgs eventArgs) { using(HostingEnvironment.Impersonate()) { eventArgs.Proceed(); } } }
So our code then looks like this:
[ImpersonateAttribute] public static void DeleteUser(int userID) { //do some database access }
Much easier to maintain. Now every bit of code with this attribute will run as the application pool account.
A lot of functionality can be added to CRM with JavaScript, if your like me and do the bulk of your normal web development JavaScript with a toolkit like JQuery, you will quickly miss the power and ease of doing things without it. The good news is that it is pretty easy to load JQuery into the page: var script = document.createElement('script');script.type = 'text/javascript'; script.src = '/ISV/jquery.js'; script.onreadystatechange = function() {if(this.readyState == "complete" || this.readyState == "loaded") setupPage(); }; var head = document.getElementsByTagName('head')[0]; head.appendChild(script);
Now you can use the setupPage() function with all your familiar JQuery goodness.
We needed the basic ability to redirect specific users to a different page. I first took a look at Adam Buenz excellent Redirection web part but we thought that it might be a little bit complex for our content authors to use, so I put together a really simple web part that gives the user the ability to select the users or groups via the standard SharePoint people picker interface. The idea is that you can select a number of groups or users to apply the redirection to, this could be as broad as all authenticated users or refined to a single user. Of course you need a way edit the web part, so another people picker dialog allows the user to select users or groups that this redirect does not apply to. If they match the exclusion, the web part will present them with the text ‘This account was excluded from redirection to <url>’. This exclusion group is normally the content authors. It is possible to put a number of these web parts on the same page, so if for example you wanted to redirect to a number of different sites based on AD group or user accounts, this should work fine. First step, as always is to add the solution to SharePoint, once the solution is deployed, be sure to activate the ‘Redirector web part’ feature at the site level: Once that is done, it’s just a matter of selecting the ‘Redirect web part’ from the web part gallery: Hopefully you’ll find some use for it. The source code can be downloaded here and the WSP can be found here. The code for this web part is fairly straight forward, I have a tool part that uses the SharePoint PeopleEditor control. This control has a property to return the selection of groups or accounts as a comma separated string. With this string I can then do a search to see if the current user is in the group like: private bool IsUserIn(string commaSeperatedList) { bool isMatch = false;
if (commaSeperatedList != string.Empty) { foreach (string group in commaSeperatedList.Split(',')) { if (Page.User.IsInRole(group) || Page.User.Identity.Name == group) { isMatch = true; } } } return isMatch; } So the web parts main logic looks like: bool redirectMatch = false; bool exclusionMatch = false;
redirectMatch = IsUserIn(selectedADVal); exclusionMatch = IsUserIn(excludeADVal);
if (redirectMatch == true && exclusionMatch == false) { Page.Response.Redirect( redirectUrl); } else { if (redirectMatch) { outputCtrl.Text = "This account was excluded from redirection to " + redirectUrl; } }
CRM 4 has lots of excellent customization options, one of which is the auto-numbering of entities: The Settings – Administration section allows you to setup some options for auto-numbering your entities, you can choose the prefix, and the suffix length: This is pretty powerful, but how would approach this problem if you needed your entities to have a more complex numbering scheme? Being a developer, everything looks like a development task to me. So I would start with a plugin that implements the IPlugin interface: public class AutoNumber : IPlugin Then we need to implement the Execute method such as: public void Execute(IPluginExecutionContext context) { if (context.InputParameters.Properties.Contains("Target")) { DynamicEntity entity = (DynamicEntity)context.InputParameters.Properties["Target"];
if (entity != null) { if (!entity.Properties.Contains("AttributeName")) { entity.Properties.Add(new StringProperty("AttributeName", GetAutoNumber()));
} } } } So the above code will check to make sure our input properties contains the ‘Target’ parameter, this will be the entity that is affected by the plugin. From here we can cast it to a DynamicEntity which will make it easier to work with. Once we have an instance of DynamicEntity the whole task of updating and attributes becomes trivial, the above code just creates a new property and calls some method that will generate the complex AutoNumber. Once the plugin has been developed, the next important step is to register the plugin. The best tool for the job is probably the CRM Plugin Registration Tool: The first step is to register the plugin assembly: This will prompt you with a form that allows you to select the assembly and the location it will be stored i.e. Database, file system or GAC. Once the assembly has been registered we can register a ‘new step’, this is done from the same menu as the assembly registration. You will be prompted to fill out the following form: For our plugin we will choose the following options: Message: Create (since we are doing an auto number, it makes sense to only run the plugin at the create stage) Primary Entity: Your choice Plugin: Your assembly that was loaded at the previous step Post Stage: we want to run it after the entity has been created, otherwise the built in CRM auto numbering will overwrite our value. Synchronous: lets fire the event synchronously, no reason not to, the end user will be presented with a consistent interface this way. Step Deployment: your choice Triggering Pipeline: Parent As you can see its all fairly simple and straight forward, now you can have an auto number that be what ever you need, it might be simple like some combination of a financial year or complex where a lookup to some external system needs to take place, but at the end of the day at least there is a nice mechanism in place to help you out.
By default when you edit the tool part settings of a SharePoint web part and you click on the ‘…’ if the property is a string, you will get the default property builder: Clicking the above ‘…’ will launch the default string property builder: This popup is actually the ‘zoombldr.aspx’ page located in the _layouts virtual directory. There are a few problems with this builder, well it’s not so much the builder, the string property’s of the web part are serialised in a manner that strips out carriage returns. So if you were to spend time formatting the contents of this dialog, it will get wiped out when you save the contents. This is most apparent when you edit the XSLT of the core search results web part. How can you override this property? The key is to make use of the HtmlDesigner attribute on your web part property. There are two ways to make use of the attribute, the first way is to explicitly define the page which you want to load (i.e. change ‘url to page’): [System.Web.UI.WebControls.WebParts.WebBrowsable(true), System.Web.UI.WebControls.WebParts.WebDisplayName("Template"), System.Web.UI.WebControls.WebParts.WebDescription(""), System.Web.UI.WebControls.WebParts.Personalizable( System.Web.UI.WebControls.WebParts.PersonalizationScope.Shared), System.ComponentModel.Category("Settings"), System.ComponentModel.DefaultValue("") ] [HtmlDesignerAttribute("url to page", DialogFeatures = "dialogHeight:500px;dialogWidth:650px;help:no;status:no;resizable:yes")] public string CustomProp { get { return customProp; } set { customProp = value; } } Notice that you could also modify the parameters that get passed into the JavaScript popup window creation script, in the case above I have made the popup window larger than the default size. The problem with the above approach is that the URL that gets passed as the first parameter to the HtmlDesignerAttribute must be a constant value, since it’s used in the attribute declaration. However Microsoft have provided us with a nice way to change this behaviour. [System.Web.UI.WebControls.WebParts.WebBrowsable(true), System.Web.UI.WebControls.WebParts.WebDisplayName("Template"), System.Web.UI.WebControls.WebParts.WebDescription(""), System.Web.UI.WebControls.WebParts.Personalizable( System.Web.UI.WebControls.WebParts.PersonalizationScope.Shared), System.ComponentModel.Category("Settings"), System.ComponentModel.DefaultValue("") ] [HtmlDesignerAttribute(BrowserBuilderType.Dynamic, DialogFeatures = dialogHeight:500px;dialogWidth:650px;help:no;status:no;resizable:yes")] public string CustomProp { get { return customProp; } set { customProp = value; } } I’ve now added the BrowserBuilderType.Dynamic option. To get this to work, we now need to override the GetCustomBuilder method of your web part: protected override string GetCustomBuilder(stringpropertyName) { if(propertyName == "CustomProp") { return"url.aspx?"+ "some custom params"; } return base.GetCustomBuilder(propertyName); } Now every property that has BrowserBuilderType.Dynamic passed into the HtmlDesignerAttribute will get passed into the GetCustomBuilder method, this gives you the chance to create a URL that passes parameters to your custom builder page. Ok, so now you got the web part covered, what about the JavaScript that needs to run on your custom property builder page? The best thing to do is to look at the current zoomdldr.aspx page, when the page loads you can get the current arguments from: window.dialogArguments Then to save the arguments, save the string value back to: window.returnValue Now that you’ve got a fair idea about creating custom property builders, you can create builders that are specific to you needs, below is a screenshot of a property builder form that lets the user enter some c# code that gets compiled an injected into the web part, the need was to provide a nice interface that the end user (i.e. a programmer) could use. The property builder uses the EditArea control to format the code nicely. The user can then click the compile button which will do a compilation of the code and report any errors. I’ve also added some JavaScript code to get around the removal of the carriage return characters, which was a major pain point for us. This approach will give you the power to create property builders that more closely suit your needs and goals.
A little while ago, I was working with a customer who wanted to have the ability to write some annotations on a graph that was presented in a SharePoint site. The idea was that the graph was quite complex and it would help the end user understand it more if it was annotated by a domain expert. So I put together a little proof of concept web part that makes use of all that JQuery goodness. I figured that it didn’t matter if you were annotating complex graphs, or tagging someone’s face like the way flickr and facebook does, the concept was the same: So like I said the code is proof of concept, not production ready, but I thought I’d post it here anyway, if only to inspire ideas. I’ve stored the data in a list with the schema of Title: default setting (single line of text) JSON: single line of text Or: The data will look like this once some annotations have been added: So I’ve just stored the JSON serialised object as is. The web part will iterate over each item on that list and pump that JSON into some JavaScript that is written to the page, so when the page load’s it will insatiate a JavaScript array of annotations and then render them. Which looks something like: notes = [{ "x1": "11", "y1": "28", "height": "91", "width": "73", "note": "This is bill", "imgID": "0"}, { "x1": "86", "y1": "54", "height": "81", "width": "95", "note": "This is Kate", "imgID": "0"}]; You might ask why I have the imgID property, this is used if more than one image on the page is annotated. To have a play around with the web part, you can download the WSP here, you just need to deploy the solution in the normal manner (stsadm –o addsolution –filename <path to httpcode.imageannotate.wsp> Next step is to activate the Image Annotation web part feature on the site: Add the web part as normal from the web part menu: Once the web part is on the page, you need to set List setting to the SharePoint list that will store the JSON data (this list must conform to the schema above): The client ID is the most technical part, this is a selector that gets used directly by JQuery, so any valid JQuery selector will work. A couple of examples: This will find the default image on the homepage of a standard collabration portal: img[src*='/PublishingImages/newsarticleimage.jpg'] All the images produced by a Dundas chart: img[src*='SharePointChartAxd.axd'] Hopefully you get the idea. I haven’t added any way to delete the annotation from the image itself, rather you need to go to the custom list and delete the item from there. The web part is smart enough to not allow users who can’t add items to the list. Credit should also go to odyniec.net for the imgAreaSelect JQuery plugin which I’ve made use of. The web part injects lots of JavaScript into the page, so the code might look a little messy because of this, but the basic premise is pretty straight forward. The source code can be found here. It includes a HttpHandler (ImageAnnotate.ashx) for handling all the ajax calls from the client side script. Remember this is just some sample code, use it at your own risk.
I came across an interesting problem the other day, I needed to do some string manipulation with SQL Server, normally dealing with this sort of stuff sucks, I wanted to split a string like ‘item1, item1, item3’ .. so splitting on a given character. The first thing that popped to my mind was a Table Valued function, something like this: -- Splits a column based on a specified delimiter CREATE FUNCTION dbo.Split ( @List nvarchar(2000), @SplitOn nvarchar(5) ) RETURNS @RtnValue table (
Id int identity(1,1), Value nvarchar(2000) ) AS BEGIN
While (Charindex(@SplitOn,@List)>0) Begin Insert Into @RtnValue (value) Select Value = ltrim(rtrim(Substring(@List,1,Charindex(@SplitOn,@List)-1))) Set @List = Substring(@List,Charindex(@SplitOn,@List)+len(@SplitOn),len(@List)) End
Insert Into @RtnValue (Value) Select Value = ltrim(rtrim(@List))
Return END There are many examples of this type of function on web, my point isn’t the actual implementation, rather the way you can use it. I can now run the query: select Value from dbo.Split('item1, item2, item3',',') But my problem was a little different from the trivial example above, I wanted to pass data into this function that was the result of my query, rather than just passing arguments in statically like above and using the function as a table, I really wanted to apply the function to the data from my table, on a row by row level. After a little searching I found the CROSS APPLY SQL syntax, with this I can now do exactly what I want: select SF.Value from TestTable TT cross apply dbo.Split(TT.Value, ',') SF The above query simple selects the data from the TestTable, then cross apply’s the Split function passing it the TestTable’s value column (which contains the data ‘Item1, Item2, Item3, Item4’, also note that I’m selecting the column from the Split function. This Produces the desired results: Given a table like: And data like: I thought this was pretty cool. SQL can be fun.
A while ago I wrote about a simple method to give web parts an easy to configure design experience. The method I came up with made use of the ASP.NET Page method ParseControl. In my original example, the developer had to do some manual work like finding the templated control once it was added to the page, this was a tedious and ugly design. At the time it was more of an idea rather than something that I had build seriously, but recently I’ve put a bit more thought into the idea of web parts with a configurable UI template. I’ve built on the original example, but with the goal of abstracting the ParseControl and ControlBinding implementation. This example simply writes a title and description: We want to give end users the ability to change the way this title and description look without any web part code changes. This is done via a template, which is a property of the web part: Notice this is ASP.NET syntax The template can be easily changed: To produce a different rendering, without the need for any developer assistance: From a developers point of view, we really want to just declare some variables and be able to use them as if they were part of the web part, without thinking about FindControl or any implementation detail. I put together a base web part that looks for any private variables with a custom attribute called ControlBinding and via reflection sets this variable to the control instance specified in the attribute, so for the above example the code for the web part would look like: public class TemplateTest : WebPartTemplateBase { [ControlBinding("titleLabel")] Label titleLabel = null;
[ControlBinding("descriptionLabel")] Label descriptionLabel = null;
protected override void OnPreRender(EventArgs e) {
if (titleLabel != null) { titleLabel.Text = "Some text set from code"; }
if (descriptionLabel != null) { descriptionLabel.Text = "some text description that was set in code"; } base.OnPreRender(e); } } So now all the developer needs to do is add an attribute to private variables with the name of the templated control they want to obtain an instance to. This fits very much with the code behind / partial class philosophy that web developers are used to working with. If we look at some more complex scenario’s we start finding a few little issues, for example lets say that we want to use a GridView to do some data binding, in a normal aspx page we would use code nuggets to do the data binding: <asp:GridView ID="GridView1" runat="server"> <Columns> <asp:TemplateField> <ItemTemplate> <asp:label id="dataItemLabel" runat="server" Text='<%DataBinder.Eval(Container, "SomeValue"%>'></asp:label> </ItemTemplate> </asp:TemplateField> </Columns> </asp:GridView> If we used this in our template we would find that it doesn’t work. There is however a work around: We can subscribe to the RowDataBound event and we can find the controls we wish to data bind to: [ControlBinding("GridView1")] GridView gridView = null;
protected override voidOnPreRender(EventArgs e) { if(gridView != null) { gridView.RowDataBound += newGridViewRowEventHandler(gridView_RowDataBound); gridView.DataSource = GetData(); gridView.DataBind(); } base.OnPreRender(e); }
void gridView_RowDataBound(objectsender, GridViewRowEventArgs e) { Label itemLabel = e.Row.FindControl("dataItemLabel") as Label;
if(itemLabel != null) { itemLabel.Text = e.Row.DataItem.ToString(); //cast and use the data item } } In which case the template would look like: <asp:GridView ID="GridView1" runat="server"> <Columns> <asp:TemplateField> <ItemTemplate> <asp:label id="dataItemLabel" runat="server"></asp:label> </ItemTemplate> </asp:TemplateField> </Columns> </asp:GridView> I’m still in the early stages of experimenting with this pattern, so it could be the case that it’s totally unsuitable for some scenario’s. If your interested in exploring this pattern a little more, you can download the code to the base class here.
My last post described a really simple scenario where I used a HttpHandler to configure StructureMap, then I showed some code that is used to replace the ‘new’ operation used to create a new instance like: ObjectFactory.GetInstance<NumberPrinter>() This might seem a little clunky because you need to remember to use the StructureMap ObjectFactory, not all programmers are created equal, so it might slip through the cracks. What would a framework look like if it embraced the concept of DI? Well it turns out that the ASP.MVC framework was designed from the ground up (or a mile up as the case may be, it was started on a plane flight). To configure StructureMap with ASP.NET MVC, firstly it’s OK to modify the global.asax (since this isn’t a SharePoint solution): protected void Application_Start() { StructureMapConfiguration.AddRegistry(new DIRegistry());
ControllerBuilder.Current.SetControllerFactory( typeof(StructureMapControllerFactory) );
} Here we configure StructureMap with the same method as my last post by using the StructureMapConfiguration.AddRegistry method. The second line is more interesting, we tell the MVC framework that we want StructureMapControllerFactory to be the factory class that creates our controllers. So the code to this class looks like: public class StructureMapControllerFactory : DefaultControllerFactory { protected override IController GetControllerInstance(Type controllerType) { try { return ObjectFactory.GetInstance(controllerType) as System.Web.Mvc.Controller;
} catch (StructureMapException x) { System.Diagnostics.Debug.WriteLine(ObjectFactory.WhatDoIHave()); throw; } } } That’s pretty cool, so what it means now is that we can create our controllers to take an instance of anything we want to inject. Continuing from the example with a simple Number interface: public class TestController : Controller { INumber _number; public TestController(INumber number) { _number = number; }
public ActionResult Index() { return View(_number.GetNumber()); }
} This means that the controller has been passed an instance of INumber that has been created by StructureMap. Once you do the initial setup, it’s now pretty hard to make any mistakes in regards to not calling the proper Create methods.
One of my favourite Dependency Injection (DI) containers is Jeremy Miller’s StructureMap, it’s full featured and light weight, but what I really like the most is the fluent configuration API which means we can write code like this: ForRequestedType<INumber>() .TheDefaultIsConcreteType<Number>();
I also like that you don’t need to put custom attributes in your code. It’s all pretty abstract so lets assume we have a simple interface, I’m deliberately keeping it simple: public interface INumber { int GetNumber(); } A simple implementation of this interface: public class Number : INumber { public int GetNumber() { return 999; } } I’ve built a really simple (read: don’t use this in anything other than an experiment, the DI container in SharePoint gets configured for every request, including for images and scripts). This handler calls StructureMapConfiguration.AddRegistry with a class that derives from Registry which contains our fluent configuration information. Why have I implemented this as a HTTPHandler? If you read the Guidance on How to enable Unity in a SharePoint Application (unity is Microsoft’s DI container), the first step is to modify the Global.asax file. I have a problem with doing this for the simple reason that your touching the SharePoint system files, any upgrade will likely cause problems. It really is a shame that you can’t access the application onstart event using another mechanism. I most certainly agree that the application onstart event is the perfect place to setup your DI container and if your happy to modify and deploy your changes to a global.asax file then go right ahead, put the code below into this file. So it got me thinking, would a DI container like StructureMap work if it was setup in the begin request method of a HttpHandler? so my test code is: namespace DIHttpModule { public class DIHttpModule : IHttpModule { public void Dispose() { }
public void Init(HttpApplication context) { context.BeginRequest += new EventHandler(context_BeginRequest); }
void context_BeginRequest(object sender, EventArgs e) { StructureMapConfiguration.AddRegistry(new DIServiceRegistry());
INumber numberGen = ObjectFactory.GetInstance<INumber>();
((HttpApplication)sender).Response.Write("Number: " + numberGen.GetNumber().ToString()); } }
public class DIServiceRegistry : Registry { protected override void configure() { ForRequestedType<INumber>() .TheDefaultIsConcreteType<Number>(); } } } So after I deploy and configure my HttpHandler I do in fact see the number ‘999’ display at the top of each page (it also breaks JavaScript etc. because it writes this value into every request) What has happened is that the call ObjectFactory.GetInstance<INumber>() has created an instance of Number(), we didn’t have to explicitly new up a Number() object. That is a pretty basic example, lets expand it a little: public class NumberPrinter { private INumber _number;
public NumberPrinter(INumber number) { _number = number; }
public string PrintNumber() { return _number.GetNumber().ToString(); } } Notice the constructor, it takes an INumber interface, the constructor is special as far as StructureMap is concerned, if StructureMap is asked to create an instance of NumberPrinter it will see that the constructor takes an INumber parameter and will attempt to pass in an instance of that type. So assuming we changed the HttpModule to the following code: NumberPrinter printer = ObjectFactory.GetInstance<NumberPrinter>(); ((HttpApplication)sender).Response.Write(" Number : " + printer.PrintNumber()); We can see that again our output is ‘999’, StuctureMap was smart enough to create an Instance of INumber and pass it in, if you think about it, that’s pretty cool. It means that you can replace any concrete Instance of INumber with a single configuration change, that is powerful when we want to test our object in isolation, which is a whole other topic. That’s a pretty trivial example, we did the StructureMap configuration and called ObjectFactory.GetInstance in the same method, now lets try it out in a web part which will be invoked later in the page life cycle (also remove the response.write from the HttpModule): public class NumberWebPart : System.Web.UI.WebControls.WebParts.WebPart { protected override void RenderContents(System.Web.UI.HtmlTextWriter writer) { NumberPrinter printer = ObjectFactory.GetInstance<NumberPrinter>(); writer.Write("Web Part: " + printer.PrintNumber()); } } Running this web part gives the expected result: Again this is a really trivial example, that’s the point, you can clearly see that we are using a DI tool called StructureMap to create instances of our objects. Sure you could do away with the HttpHandler and move the configuration into the web.config file. But I do think the HttpHandler method warrants more thought however, because its useful when you introduce NHibernate to the mix, which is another framework that needs to do some expensive start up, which is also suited to the global.asax methods, but it does have some characteristics that should be managed per request, but that’s another post. For now, hopefully I’ve proved some thought around DI and how can we manage the SharePoint deployments that really yell out for Application OnStart events without modifying the system file.
I thought it might be fun to play around with the people search of MOSS. I wanted to have an auto complete like experience, where you could start typing someone’s name and a list of suggestions are displayed below: I started by including JQuery and the Autocomplete JQuery plugin into a style library. Next I created a Handler called PeopleSearch.ashx, I placed this in the _layouts directory (or 12 hive \TEMPLATE\LAYOUTS ) The code for this handler is simple: 1: <%@ WebHandler Language="C#" Class="GenericHandler1" %> 2: <%@ Assembly Name="Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %> 3: <%@ Assembly Name="Microsoft.Office.Server.Search, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %> 4: <%@ Assembly Name="Microsoft.Office.Server, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %> 5: 6: using System; 7: using System.Web; 8: using System.Data; 9: using Microsoft.SharePoint; 10: using Microsoft.Office.Server.Search.Query; 11: 12: public class GenericHandler1 : IHttpHandler { 13: 14: public void ProcessRequest (HttpContext context) { 15: context.Response.ContentType = "text/plain"; 16: 17: string prefixText = context.Request["q"]; 18: using (SPSite siteCollection = SPContext.Current.Site) 19: { 20: // create a new FullTextSqlQuery class 21: FullTextSqlQuery query = new FullTextSqlQuery(siteCollection); 22: query.QueryText = string.Format("SELECT Title FROM SCOPE() WHERE FREETEXT(defaultproperties, '{0}*') AND \"Scope\"='People' ", prefixText); 23: query.ResultTypes = ResultType.RelevantResults; 24: query.RowLimit = 10; 25: 26: // execute the query 27: ResultTableCollection queryResults = query.Execute(); 28: ResultTable queryResultsTable = queryResults[ResultType.RelevantResults]; 29: 30: 31: while(queryResultsTable.Read()){ 32: context.Response.Write(queryResultsTable.GetString(0) + Environment.NewLine); 33: } 34: } 35: 36: } 37: 38: public bool IsReusable { 39: get { 40: return false; 41: } 42: } 43: 44: }
It simply executes a full text query on the People Search Scope with the current text as the argument. Download the handler here.
Next I added a content Editor web part to the PeopleSearch.aspx page and included the following JavaScript (in the HTML source view):
1: <script type="text/javascript" language="javascript" src="http://server/Style%20Library/jquery-1.2.6.min.js"/> 1: 2: <script type="text/javascript" language="javascript" src="http://server/Style%20Library/jquery.autocomplete.min.js"/> 3: 4: <script type="text/javascript"> 5: 6: $(document).ready(function() { 7: 8: $("input[id*='_InputKeywords']").autocomplete("/_layouts/PeopleSearch.ashx"); 9: $("input[id*='_InputKeywords']").attr("autocomplete","off"); 10: 11: }); 12: </script>
The first thing I do is add a new attribute to prevent IE (or Firefox) from showing the previous search items. The next thing is to add the call the autocomplete plugin on the search textbox (which is found with because we know the id always ends with _InputKeywords).
I also added some styles from the autocomplete css to the css of the site.
It’s really that simple to get look ahead searching on people in MOSS. In fact this same approach could work on all the search pages.
Recently I had to write a SQL Server Reporting Services Report that used a web service for the data source, the web service returned a horrible .NET DataSet object which I had no control over. To use a web service from Reporting Services, the first step is to create a new Data Source of type: XML: Then enter the URL to the web service in the connection string section. The next step is to create a new DataSet for the report. In the Query Designer enter the following: <Query> <Method Name="<web service method name>" Namespace="<webservice namespace url>”> </Method> <ElementPath IgnoreNamespaces="True"> <web service method name>Response/<web service method name>Result/diffgram/NewDataSet/Table1 (replace Table1 with the dataset name if used) </ElementPath> </Query> If you need to pass parameters to the web service, this can be done by adding something like: <Parameters> <Parameter Name="<parameter name>"> <DefaultValue></DefaultValue> </Parameter> </Parameters> But be sure that the case of the parameter name is exactly the same as the parameter in the report that you wish to pass in.
The following code will create a site based on a template, then it will add a new contributor group: //create the subsite .. subSite is an SPWeb object
SPWeb createdWeb = subSite.Webs.Add("Url", "Title", "Project Subsite", 1033, "Template Name", true, false);
createdWeb.BreakRoleInheritance(true);
SPMember member = createdWeb.Users[createdWeb.Author.LoginName];
//create the user groups ...
createdWeb.SiteGroups.Add(createdWeb.Title + " Contributors", member, createdWeb.Author, "Contributors to the site");
SPGroup newContribGroup = createdWeb.SiteGroups[createdWeb.Title + " Contributors"];
SPRoleDefinition contribRole = createdWeb.RoleDefinitions.GetByType(SPRoleType.Contributor);
SPRoleAssignment contribRoleAssignment = new SPRoleAssignment(newContribGroup);
contribRoleAssignment.RoleDefinitionBindings.Add(contribRole);
createdWeb.RoleAssignments.Add(contribRoleAssignment);
The above code will break the permission inheritance, so the created site will have unique permissions, this code could be refactored to also create Owners and Reader’s groups.
I was playing around with setting up the navigation created by a legacy custom site definition. Information posted for my reference more than anything else: <WebFeatures> <Feature ID="541F5F57-C847-4e16-B59A-B31E90E6F9EA"> <Properties xmlns="http://schemas.microsoft.com/sharepoint/"> <Property Key="InheritGlobalNavigation" Value="true"/> <Property Key="IncludeSubSites" Value="true"/> <Property Key="IncludePages" Value="false"/> <Property Key="InheritCurrentNavigation" Value="false"/> </Properties> </Feature> </WebFeatures>
Some of the settable properties are:
The boolean properties:
- IncludeInGlobalNavigation
- IncludeInCurrentNavigation
- InheritGlobalNavigation
- InheritCurrentNavigation
- ShowSiblings
- IncludeSubSites
- IncludePages
- SortAscending
The next properties take an enum:
- OrderingMethod - Automatic, ManualWithAutomaticPageSorting, Manual
- AutomaticSortingMathod - Title, CreatedDate, LastModifiedDate
I’ve written a web part that can help you insert JavaScript into a SharePoint page. Currently there is nothing stopping you using a content editor web part, but it has a few limitations. First is the fact that the JavaScript doesn’t stand out, people may think that the content inside the editor is blank, when in fact it contains JavaScript. By having a dedicated web part for JavaScript it becomes clearer that JavaScript lives on the page, also we can add a few features that make working with JavaScript a little easier. I’ve made the chrome state set to None by default, so you won’t see the web part at all during normal render time (only design time). The properties: Page load JavaScript: This can be any JavaScript that you want to run when JQuery loads, that is any code you want to live inside of: $(document).ready(function(){});
Something cool to try out (from EndUserSharePoint) try adding: $('#LeftNavigationAreaCell').toggle(); This will remove the left hand navigation.
Page level JavaScript: This is JavaScript that you just want to live on the page, it could be globally scoped variables or some functions that you have defined.
Script Includes: Each new line can be the URL to a JavaScript file to be included in the page, this is particularly useful for including JQuery plugins.
Use Google Libraries: Just a little novelty, it will use the Google Ajax API’s to load JQuery instead of the embedded JQuery resource.
You can have multiple web parts on the same page, the best bit about this is that all the code will be output into one place, so if you have one web part with some page load JavaScript that has say: alert(‘load’); and anther that includes the left nav cell hide from above, the result in the page would be:
$(document).ready(function(){alert’load’); $('#LeftNavigationAreaCell').toggle(); });
After you deploy the solution, be sure to activate the feature under ‘site features’:
You can download the solution package from here.
A while ago I wrote a little tag cloud web part, I’ve updated that web part so that it has its own solution package and can be used stand alone. After you install and deploy the solution, make sure you activate the tag cloud feature in ‘site features’ Lets take the example of adding tags to a standard events calendar. First add a new column named ‘tags’ to the events list. Add the tag cloud web part to the page and set the following properties: You can specify the link that each tag will link to as well. By default it will link to the search center and try to search on the metadata property of the tag field. i.e: "/SearchCenter/Pages/Results.aspx?k={tagfield}:{tag}", using this format however you could link to any page and then maybe use a query string filter web part to pull the tag from the url. The final web part looks like: You can download the solution from here.
I ran across an interesting bug with the JQuery thickbox, I pointed it to a URL which was an ASP.NET ashx handler that generates thumbnail images, the result in the browser was this garbled response: Using Firebug we see that it expects the result to be text/html Looking at the code it is obvious what the problem is: 1: var urlString = /\.jpg$|\.jpeg$|\.png$|\.gif$|\.bmp$/; 2: var urlType = baseURL.toLowerCase().match(urlString); 3: 4: if (urlType == '.jpg' || urlType == '.jpeg' || urlType == '.png' || urlType == '.gif' || urlType == '.bmp') {//code to show images
The code looks at the extension of what it is calling, if it finds any of the common image types, it will send a different request type, so adding the .ashx extension will fix the issue:
1: var urlString = /\.jpg$|\.jpeg$|\.png$|\.ashx$|\.gif$|\.bmp$/; 2: var urlType = baseURL.toLowerCase().match(urlString); 3: 4: if (urlType == '.jpg' || urlType == '.jpeg' || urlType == '.ashx' || urlType == '.png' || urlType == '.gif' || urlType == '.bmp') {//code to show images
I’ve just simply added the .ashx as a file extension that should be treated as an image type, this fixed the issue.
Last night I started having a bit of a play with the Azure blob storage system. The first thing to do is install the SDK and get the development storage and development fabric setup. The SDK gives you the ability to go File –> New –> Cloud Project, I selected the web role for my demo (the other options include a stand alone worker, or combined web and worker roles). From here I included the StorageClient project from the Azure SDK samples, this has a bunch of wrapper classes that are helpful. Then I added some appSettings entries that contain URL’s for the development tools: 1: <appSettings> 2: <add key = "AccountName" value="devstoreaccount1"/> 3: <add key = "AccountSharedKey" value="Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw=="/> 4: <add key="BlobStorageEndpoint" value="http://127.0.0.1:10000/"/> 5: <add key="QueueStorageEndpoint" value="http://127.0.0.1:10001"/> 6: <add key="TableStorageEndpoint" value="http://127.0.0.1:10002"/> 7: </appSettings>
The only thing that I was interested in was putting stuff into the blob storage, so I put together a simple asp.net page that contained an upload file control and a button.
I wired up the button event with the following code:
1: protected void btnTest_Click(object sender, EventArgs e) 2: { 3: StorageAccountInfo blobAccount = StorageAccountInfo.GetDefaultBlobStorageAccountFromConfiguration(); 4: 5: BlobStorage blobStorage = BlobStorage.Create(blobAccount); 6: blobStorage.RetryPolicy = RetryPolicies.RetryN(1, TimeSpan.FromMilliseconds(100)); 7: 8: BlobContainer container = blobStorage.GetBlobContainer(Guid.NewGuid().ToString()); 9: 10: NameValueCollection containerMetadata = new NameValueCollection(); 11: containerMetadata.Add("Name", "TestContainer"); 12: container.CreateContainer(containerMetadata, ContainerAccessControl.Public); 13: 14: string blobName = Path.GetFileName(testFile.PostedFile.FileName); 15: BlobProperties properties = new BlobProperties(blobName); 16: container.CreateBlob(properties, new BlobContents(testFile.PostedFile.InputStream), true); 17: }
To prove that the uploaded file was inserted into the blob, I used the CloudDrive project that is also provided by the Azure SDK, this project provides some PowerShell magic to allow you to mount the blob storage as a drive (the drive is blob:) so in the command window I could do the following:
This just lists all the containers that have been created and then the blob’s that live inside those containers.
Now the interesting part is that we can use the browser to also view our blob:
The MSDN pages give us the details, but basically you browse to http://127.0.0.1/devstoreaccount/<container>/<blobname>
Of course you’ll only be able to browse like this unauthenticated if your container is publically viewable, these attributes were set above with the ContainerAccessControl.Public parameter on the CreateContainer method call.
I previously blogged about using the BDC with MOSS user profiles and how to set that whole process up. Well I thought that I might write a little about the BDC application definition file (ADF) that is imported into MOSS and is used by the BDC to generate the meta-data and to ultimately connect to the data source. An ADF file contains metadata describing entities and methods to populate those entities. These are the methods that are of interest to us, all have nice relevant names: IDEnumerator – These methods can perform filtering and can be passed parameters, the idea is that it returns an ID (and a timestamp if possible), as it’s name implies it is used to enumerate all the ID’s (or primary keys). In the context of the profile import, if your key is say, an Active Directory email address, then the IDEnumerator should return the email address field. Specific Finder – This method accepts an ID and returns just the information related to that ID. You’ll probably create a number of these using different filter descriptors. Now that you have an idea of what the methods are and how they operate you can design web services that are low friction for the BDC. You’ll need a web service that returns a list of ID’s (for the IDEnumerator), you’ll need a second webservice that accepts the same ID’s that were returned by the first method, this second method will comprise your Specific Finder methods. Also don’t create trouble for yourself by building webservices that accept a large number of parameters, you’ll regret it, just keep it simple. The guys that developed the fantastic BDC Meta-Man product also have the same advice. I’ve provided the SQL create statement and ADF file here>.
I’ve previously posted a demo that made use of a tag suggestion web part, as you type the web part will make an ajax call which will return the tags that match the current input. The user can click on the suggestion tag and it will populate the textbox, multiple tags can be entered into the textbox.  The most interesting part of this web part is the server side call, which I’ve implemented as a HttpHandler in the Tags.ashx file: public class Handler : IHttpHandler {
public void ProcessRequest(HttpContext context)
{
context.Response.ContentType = "text";
I’ve made use of the System.Web.Extensions JavaScriptSerializer to render the string array of tags to JSON:
1: List<string> tagList = new List<string>(); 2: 3: //add all the tags to a collection, JSON serialize the list 4: 5: System.Web.Script.Serialization.JavaScriptSerializer serializer = new System.Web.Script.Serialization.JavaScriptSerializer(); 6: 7: context.Response.Write(serializer.Serialize(tagList.ToArray())); 8:
I’ve put this handler in the /_vti_bin/ directory of SharePoint which maps to the ISAPI folder under the 12 hive.
I’ve used the same code to generate the tags as I did with the tag cloud web part, so once again the generation of these tags won’t scale to large lists, this is just an example of how to implement a JQuery based Tag Suggestion web part.
The source code for this web part can be found here.
I recently had a project where I needed to bind some business objects to a SPGridView in a web part, which on its own is trivial, but I also needed to create the columns for the SPGridView on the fly, since a number of business objects could be rendered in this control. The main problem was the naming of these columns, if I just bound them directly to the Grid, I would get the ugly naming of my properties (I say it’s ugly, it’s actually pretty awesome if your a developer, but those pesky end users like pretty names). The sample grid looks like this by default: So what I came up with isn’t exactly new or innovative, I created a custom attribute called Alias: public class AliasAttribute : System.Attribute
{
protected string[] aliasName;
public AliasAttribute(params string[] alias)
{
this.aliasName = alias;
}
public string[] Alias
{
get { return aliasName; }
set { aliasName = value; }
}
}
This attribute gets applied to each property on your business object with a pretty name:
[Alias("Internet URL")]
public string InternetAddress
{
get { return internetAddress; }
set { internetAddress = value; }
}
Now the code that performs the binding looks for properties with a custom attribute and pulls out the alias as the HeaderText:
BoundField newBoundField = new BoundField();
newBoundField.HeaderText = Helpers.GetPropertyAlias(field.Trim());
newBoundField.DataField = field.Trim();
grid.Columns.Add(newBoundField);
public static string GetPropertyAlias(string propertyName)
{
//Change the next line for your business object ..
PropertyInfo[] propInfos = typeof(BusinessObject).GetProperties();
foreach (PropertyInfo propInfo in propInfos)
{
if (propInfo.Name.ToUpper().Equals(propertyName.ToUpper()))
{
object[] attribs = propInfo.GetCustomAttributes(typeof(AliasAttribute), true);
foreach (object attrib in attribs)
{
if (attrib is AliasAttribute)
{
AliasAttribute alias = (AliasAttribute)attrib;
//return the alias
return alias.Alias[0];
}
}
}
}
//if we don't find the custom attribute, just return the same value we passed in
return propertyName;
}
The end result is a nice pretty grid:
A sample project can be found here.
I found this fantastic post today that I just had to comment on: | In a SharePoint entry form the user can select multiple values from a list. This should include an “All” option so in a long list the user can select or de-select all values with one click. | The idea is to use JQuery to perform a select 'All' on all the checkboxes in a list. DannyE posted this JavaScript: $(”.ms-RadioText[title=’All’] :checkbox”).click(function(){
var otherids = (this.id).substring(0, (this.id).length-2 );
$(”input[id^=’”+otherids+”‘]”).attr( “checked” , (this.checked)?”checked”:”" );
});
It looks for all the items with the ms-RadioText class that also have a Title attribute that equals 'All', it adds a click handler that will then toggle the checked status of the checkbox, its extremely cool, way better than the latest block buster movie ...
Anyway, that's all well and good, I'm not adding much value reposting his stuff, so I'd just like to improve on his deployment recommendation.
Instead of adding a script reference to the master page and then inserting a content editor region to paste the JavaScript in, I would suggest using the JQuery Script Manager:
So the idea is to use SharePoint Designer to register the control with the JavaScript. Note here to use the ScriptTemplate you'll have to grab my latest script manager control from here.
<cc1:jQueryManager ID="JQueryManager1" runat="server">
<ScriptTemplate>
$(".ms-RadioText[title='All'] :checkbox").click(function(){
var otherids = (this.id).substring(0, (this.id).length-2 );
$("input[id^='"+otherids+"']").attr( "checked" , (this.checked)?"checked":"" );});
</ScriptTemplate>
</cc1:jQueryManager>
This will insert the JQuery Script Manager and declaratively add the JavaScript code to the page. This way you don't need to worry about the script resources or having multiple client side load functions.
How can you not love JQuery after seeing the power of the above code? it's just that awesome!
I've previously posted a demo that illustrated a tag cloud web part, I've now posted the code. First let me warn you that the method I've used to get the tag list will not scale, so please disregard the TagBuilder method, you'll need to implement that yourself. I've based this tag cloud control loosely on the tag cloud control from codeproject, I've refactored most of it, but have kept the maths behind generating a weighting. Using the control is pretty simple, you just need to measure the tags and then pass in a collection of CloudItems: //tagList is a dictionary with the tag and count
Dictionary<string, int> tagList = new Dictionary<string, int>();
foreach (string tag in tagList.Keys)
{
CloudItem cloudItem = new CloudItem();
cloudItem.Text = tag;
cloudItem.Weight = tagList[tag];
cloudItem.Href = "/SearchCenter/Pages/Results.aspx?k=tags:" + tag;
Items.Add(cloudItem);
}
There are a number of ways to perform the measurement, if you were to count the number of times a tagged resource was accessed, you would end up with a heat map of popular items. Alternatively you could simply count up the number of times a tag has been used (I think this is the most common use, I've done it this way in my demo). In any case the exact implementation details have been left up to you.
The web part that I'm presenting today is more of a building block, it doesn't do much on its own. It makes use of the JQuery Corners Plugin. To set the corner types you can modify the Corner Type setting: The above example will cause the following JavaScript to be output: jQuery('cornerDiv').corner('tr 25px');
This web part makes use of the JQuery Script Manager that I've posted about previously.
As usual the source can be found here.
A screencast of the control in action can also be found here.
I've posted a couple of articles on JQuery and it's use in SharePoint and also provided some sample web parts. Recently I was putting together these web parts into a single project and I ran into a problem. Each web part was adding the JQuery script resources to the page as if it was the only control on the page, for example the ImageCarousel and the Tag Suggestion web parts both had code that needed to be run on the JQuery load event, this problem would also crop up when multiple instances of the same web part were added to the page. What was outputted to the page was something like: $(document).ready(function(){
//function call for webpart1
}
$(document).ready(function(){
//function call for webpart2
}
$(document).ready(function(){
//function call for webpart2 instance 2
}
So I started developing a Script Manager control that I could add to the page that would output the JQuery load event just once, but with all the calls for the page inside this event. As with anything programming related these days, I found what I was looking for at http://codeplex.com/JQueryScriptManager/ I've changed a bit of the implementation, but the basic intent has remained the same. Hopefully I'll be able to get these changes updated to codeplex.
Looking at the revised code for the ImageCarousel web part:
jQueryManager jqueryManager = null;
protected override void OnInit(EventArgs e)
{
jqueryManager = jQueryManager.GetCurrent(Page);
if (jqueryManager == null)
{
jqueryManager = new jQueryManager();
Page.Controls.Add(jqueryManager);
}
Page.ClientScript.RegisterClientScriptInclude("JCarousel", Page.ClientScript.GetWebResourceUrl(this.GetType(),
"JQueryWebParts.js.jquery.jcarousel.pack.js"));
base.OnLoad(e);
}
Now inside the PageInit we check for the presence of a JQueryManager script control, if we don't find one, we add it to the controls collection. Just like the ASP.NET Ajax ScriptManager control, there can only be one per page.
Now to add code that is globally scoped which also resides in the same script block where our OnLoad event lives:
jqueryManager.RegisterScript("-- javascript code without <script> blocks");
//example:
jqueryManager.RegisterScript("var someItems = [5,4,3]");
To get javascript to run when the page is loaded (this will bundle calls into a single load event):
jqueryManager.ReadyFunctions.Add(new RegisterStartFunction("jQuery('#carousel').jcarousel();"));
Use the ReadyFunctions collection which takes a RegisterStartFunction object as a parameter.
Internally the JQuery script manager control is performing the following:
StringBuilder Start = new StringBuilder();
if (Scripts != null)
{
foreach (RegisterScriptBlock r in Scripts)
Start.Append(r.ScriptBlock + Environment.NewLine);
}
if (ReadyFunctions != null)
{
Start.Append("$(document).ready(function(){");
foreach (RegisterStartFunction r in ReadyFunctions)
Start.Append(r.FunctionName + Environment.NewLine);
Start.Append("});\n\n");
}
Page.ClientScript.RegisterClientScriptBlock(this.GetType(), "JQuery", Start.ToString(), true);
The source code can be downloaded here. This includes the updated JQuery Script Manager and the ImageCarousel web part that I've previously posted about.
I think this approach will provide a nice platform to continue building web parts based on JQuery Plugins, have fun.
One of my favourite features of SQL Server 2008 is the Merge or 'upsert' Statement. Lets say we have an ETL process that prepares a data stage table, without the merge statement you might first delete all the records from the staging table and then insert them all again. If you have large data sets this can become quite time consuming and well frankly it doesn't seem very elegant. Another alternative is to use a cursor to iterate over each record and look for changes, you might also have data that has a timestamp which might help you write a set based operation, both of these approaches are less than ideal. Well as of SQL 2008 you can use the Merge Statement: Lets say you have a simple table like: CREATE TABLE People
(
ID INT PRIMARY KEY,
Name Varchar(50) NOT NULL,
Position Varchar(50) NOT NULL,
Age INT NOT NULL
);
Assuming our StagedPeople table is the same structure the merge statement would look like:
MERGE INTO People
USING StagedPeople
ON People.ID = StagedPeople.ID
WHEN MATCHED THEN
-- if we find a match, then perform an update
UPDATE SET
Age = StagedPeople.Age,
Name = StagedPeople.Name,
Position = StagedPeople.Position
WHEN NOT MATCHED THEN
-- if we don't find a match, then insert a new record
INSERT (ID, Name, Position, Age)
VALUES (StagedPeople.keycol, StagedPeople.Name, StagedPeople.Position, StagedPeople.Age)
WHEN SOURCE NOT MATCHED THEN
-- we could delete the record, since it's not found in the Staged table
DELETE;
OUTPUT $Action
The $action variable returns a varchar(20) of the operation that has taken place, i.e. 'INSERT', 'UPDATE', 'DELETE'
The operations and full reference can be found here.
It seems like a pretty fundamental operation, glad it only took till 2008 for us to have it :)
This week I picked up this simple tip for spatial enabled applications. Just say you have an existing database that has the latitude and longitude stored as separate columns such as a Latitude and Longitude as you would like to make these columns available so that spatial operations can be performed on them. Create a view on the table with the Lat, Long as parameters to the Geography::Point function which creates a spatial point. Your table might look like: The following SQL will do the trick to convert the points to spatial column: CREATE VIEW SpatialView
AS
-- Create a view over a table without a spatial column
SELECT Name, geography::Point(Latitude, Longitude, 4326) as Location FROM UnSpatialTable
So now you have a spatial column that you can include in your spatial queries such as, finding the distance between points:
DECLARE @location GEOGRAPHY
SELECT @Location = Location from SpatialView where name = 'test'
SELECT @Location.STDistance(Location) as DistanceToOther from SpatialView
Creating a buffer around a point:
SELECT Location.STBuffer(20) as BufferedLocation from SpatialView where Name = 'test'
This approach might be useful if you collecting data on a mobile device running the compact edition of SQL which doesn't have any spatial features. You can collect your data as lat,long values and then do the spatial manipulation via the view. Don't forget to think about spatial indexes!
Recently I posted about using JQuery in SharePoint web parts in this post I wrapped up the JCarousel plugin. Instead of requiring the JCarousel.js file to be included into the master page, I instead made the web part inject the HTML to cause the JavaScript file to be loaded. The first step was to include the packed version of jquery.jcarousel.pack.js into my project: Notice the Embedded Resource setting for the build action. Now if we add the following code to our assembly.cs file: [assembly: System.Web.UI.WebResource("SPImageCarousel.jquery.jcarousel.pack.js", "text/javascript")]
The above line now makes our embedded resource available for use in our web part which is done by the following code:
protected override void OnInit(EventArgs e)
{
Page.ClientScript.RegisterClientScriptInclude("JCarousel", Page.ClientScript.GetWebResourceUrl(this.GetType(), "SPImageCarousel.jquery.jcarousel.pack.js"));
base.OnLoad(e);
}
This code will ensure that the script is only added to the page once since the key "JCarousel" is the first parameter, remember that more than one web part can be added to a page at the same time, so we need to consider this as we develop our web parts. OnInit is the perfect event to call RegisterClientScriptInclude it's nice and early in the page lifecycle.
So hopefully you'll find this a nice and easy way to include your JavaScript resources, it does have the disadvantage of requiring a recompile to include a later version of the JavaScript file, so your mileage may vary if this is a concern.
Better still if your using .NET 3.5 Service Pack 1, you could make use of the CompsiteScript element of the script manager to combine the javascript files so the browser makes fewer calls to the server.
I've mentioned before that I'm partial to JQuery, I consider it to be the Jessica Alba of JavaScript. With all the work that has been done creating awesome JQuery based UI components, it seems like such a shame that us SharePoint developers aren't making more use of them. So I present to you my first JQuery based web part, the Image Carousel: Based on JCarousel, this web part presents the contents of a picture library in a carousel that can be scrolled by the navigation buttons. The web part simply imports the jcarousel.js file, the web part then injects a JavaScript array of the images that reside in the selected picture library along with some javascript that initialises the carousel. The code to find the SharePoint picture library list is trivial: SPSite site = SPContext.GetContext(HttpContext.Current).Site;
SPWeb web = site.OpenWeb();
foreach (SPList list in web.Lists)
{
if (list.BaseTemplate == SPListTemplateType.PictureLibrary)
{
//grab the images from this list
jsArray.Append(ProcessList(list));
}
}
The web part makes use of custom toolpart for the selection of the picture list:
I think this provides a good template and starting point to start putting more of those JQuery plugins to use inside SharePoint
A small screencast of the control in action can be found here.
The end effect is some exceptional functionality for little development effort, which is really what JQuery is all about.
Some of the things that you need to think about when integrating:
- Make sure that the injected div tag that will be used at the host has a unique id, remember that more than one webpart can be added to a page, if you hard code the html to have specific ID's, you will run into problems.
- Make sure the JavaScript gets injected into the right part of the page, also just like the above point, ensure that each injected bits of code have a unique name, so that multiple webparts on the same page don't cause problems.
- Use the packed version of the component, SharePoint is bloated enough already, use the smallest possible footprint you can.
The source code can be found here.
In my first post dissecting the portal connection code, I showed the following code: PortalConfig portalConfig =
(PortalConfig)curSite.WebApplication.Farm.GetObject
(
new Guid(curSite.WebApplication.Properties[PROP_KEY].ToString())
);
Notice the line: curSite.WebApplication.Properties[PROP_KEY].ToString()
The properties collection is scoped at the web application level. So if someone was to build a tool that called the Clear() method on this collection it would cause issues inside the Portal Connection tool.
So my point is to use the web application properties with other applications in mind, only clear your own state.
If your profile properties includes the organisation hierarchy, you may have noticed that when an AD account is deleted or deactivated the manager of that person is sent an email notifying the manager that the site is scheduled for deletion and that they now have permissions to the mysite so they can clean it up. Of course the MySite will only be deleted if the Automatic Site Deletion is enabled. Be aware that if the AD account is deleted and Automatic Site Deletion is enabled, the manager won't have access because the site will be deleted.  Lets say you work in an organisation that wants to suppress this email (I know it sounds silly), how would you go about that? The first thing to understand is the process that caused that email to be sent in the first place. The first step is the profile import will detect if the AD account has been deleted or disabled and will change the bDeleted flag to 1 in the UserProfile_Full table of the SSP database. The next step of the process is handled by the My Site Cleanup Job:  If you disable this job then you will prevent the notification email being sent. However you will still need to have a process in place to clean out the mysites and the profiles. The crawler will still find the profiles and index them (so your people searches will include outdated people). The MySites will still be around taking up disk space. One potential solution is to call the profile stored procedure manually: DECLARE @UserID uniqueidentifier
DECLARE @NTName nvarchar(400)
DECLARE @SID varbinary(512)
DECLARE userCursor CURSOR FOR
SELECT Userid, NTName, SID from userprofile _full where bdeleted = 1
OPEN userCursor
FETCH NEXT FROM userCursor INTO @UserID, @NTName, @SID
WHILE @@FETCH_STATUS = 0
BEGIN
EXEC profile_RemoveUser @UserID, @NTName, @SID
FETCH NEXT FROM userCursor INTO @UserID, @NTName, @SID
END
CLOSE userCursor
DEALLOCATE userCursor
In a previous post I introduced the Automatic Portal Connection tool, which can be used to automatically setup the portal connection on new and existing sites. I thought I would take a peek at how I've made this happen: Firstly I'd like to introduce the SPPersistedObject: The description MSDN uses is: | The SPPersistedObject class provides a base class for all administration objects. It serializes all fields marked with the Persisted attribute to XML and writes the XML blob to the configuration database. The SPPersistedObject class contains code to serialize all its members that are base types, other persisted objects, and collections of persisted objects. Configuration data that is stored in persisted objects is automatically made available to every process on every server in the farm. | The Portal Connection tool has a class called PortalConfig which derives from SPPersistedObject this class holds the configuration information such as the portal name and portal URL: /// <summary>
/// Simple persisted object that stores the portal connection name and the portal connection url
/// </summary>
public class PortalConfig : SPPersistedObject
{
[Persisted]
private string portalName = string.Empty;
public string PortalName
{
get { return portalName; }
set { portalName = value; }
}
[Persisted]
private string portalUrl = string.Empty;
public string PortalUrl
{
get { return portalUrl; }
set { portalUrl = value; }
}
public PortalConfig()
{
}
public PortalConfig(string name, SPPersistedObject parent, Guid guid)
: base(name, parent, guid)
{
}
}
SPPersistedObject is a very handy class, especially for configuration type scenario that is used in this solution, a method that makes use of this object is:
/// <summary>
/// Simple method to persist our settings to the site
/// </summary>
/// <param name="curWeb"></param>
/// <param name="curSite"></param>
public static void ApplyPortalConnectionSettings(SPWeb curWeb, SPSite curSite)
{
PortalConfig portalConfig = (PortalConfig)curSite.WebApplication.Farm.GetObject(
new Guid(curSite.WebApplication.Properties[PROP_KEY].ToString())
);
if (curWeb.IsRootWeb)
{
curSite.PortalName = portalConfig.PortalName;
curSite.PortalUrl = portalConfig.PortalUrl;
}
curWeb.Update();
}
This object is filled out and persisted by the central admin page, then a feature staple gets this information when the site is created and applies it, but I'll expand on this in future posts.
The portal connection is pretty useful, say you have a web application for team sites and have self service site creation turned on you would probably like the portal connection to be set to your portal homepage. Just as a refresher the portal connection is the link at the top of your site: I've put together a tool that will give you the ability to set the portal connection for new sites and also for newly created sites. Once the solution has been deployed and the features activated, from central admin you will see a new link: Following this link you will be presented with the following page: From here you can select the web application to work with and you can set the portal name and URL. By selecting the 'Apply To Existing Sites' checkbox, you will set portal connections to all the sites on that web application. The SharePoint solution can be downloaded here, I'll be releasing the code soon, I want to release it to Codeplex, I just need to set it up first. Here is a small screencast showing the tool in use Edit: I've now setup the Codeplex project: http://codeplex.com/SPPortalConnection
JQuery is my all time favorite JavaScript library, it's so simple to use and incredibly powerful, but if you include all the plugins that are available, I just don't think that you would find a better tool for client side scripting. My favorite validation plugin is the 'Validation Plugin', let me give you an example of how it is used: Lets say you have a signup form, you want the user to select a user name, a password with confirmation and an email address, assume all of this is inside a form tag named 'signup'. All of the HTML input element ids are 'Password', 'ConfirmPassword', 'Email'. The JavaScript would be: <script type="text/javascript">
$().ready(function() {
$("#signup").validate({
rules: {
UserName: {
required: true,
minLength: 2,
remote: "/Account/CheckUserName"
},
Password: {
required: true,
minLength: 2
},
ConfirmPassword: {
required: true,
minLength: 5,
equalTo: "#Password"
},
Email:{
required: true,
email: true,
remote: "/Account/CheckEmail"
}
},
messages: {
UserName: {
required: "Please enter a username",
minLength: "Your username must consist of at least 2 characters",
remote: jQuery.format("{0} is already in use")
},
Password: {
required: "Please enter a password",
minLength: "Your password must consist of at least 2 characters"
},
ConfirmPassword: {
required: "Please provide a password",
minLength: "Your password must be at least 2 characters long",
equalTo: "Please enter the same password as above"
},
Email: {
required: "Please enter your email address",
minLength: "Please enter a valid email address",
remote: jQuery.format("{0} is already in use")
}
}
});
});
</script>
Now notice how I used the remote property with '/Account/CheckEmail' and '/Account/CheckUserName', these are URL's that will be called by the client side JavaScript, they will pass along the current value of the textbox, so you could write server side code to validate the input, an ASP.NET MVC Controller Method could look like:
public JsonResult CheckUserName(string username)
{
return Json(CheckValidUsername(username));
}
Notice the use of a JsonResult, The ASP.NET MVC framework is great for this type of thing, combine both JQuery and the MVC framework and you have a powerful combination, Jeff Atwood has come to the same conclusion and used both of these tools to build stackoverflow.com.
All of the above code is so clean and easy to read, can you ever remember saying that about JavaScript code?
Something a little less SharePoint. If you wanted to consume an RSS feed (such as my homepage) and your using .NET framework 3.5+, then you could use the following code: XDocument rssFeed = XDocument.Load(@"http://httpcode.com/blogs/SyndicationService.asmx/GetRss");
var posts = from item in rssFeed.Descendants("item")
select new
{
Title = item.Element("title").Value,
Published = DateTime.Parse(item.Element("pubDate").Value),
Url = item.Element("link").Value,
};
//BlogPosts is a ListView
BlogPosts.DataSource = posts;
BlogPosts.DataBind();
How about taking my last 5 twitter posts, but lets remove my name:
XDocument twitterFeed = XDocument.Load(@"http://twitter.com/statuses/user_timeline/14554389.rss");
var titterPosts = from item in twitterFeed.Descendants("item")
select new
{
Title = item.Element("title").Value.Replace("DanielPollard:","")
};
//TwitterPosts is a ListView - Notice the Take(5) below
TwitterPosts.DataSource = titterPosts.Take(5);
TwitterPosts.DataBind();
There's a lot to love about Linq.
I was reading this MS KB article 'How to point to a custom 404 error page in Windows SharePoint Services 3.0 or in MOSS'. I thought I'd throw together a simple administrative page to pull it all together. I've packaged this up in this solution file. Once you active the 'Manage Custom 404 Page' feature, you will get a new menu item in the Application Management page in Central Admin: From here you get a simple page that you can type the 404 page name for the selected web application: The code is super simple: using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.SharePoint.WebControls;
using Microsoft.SharePoint.Portal.WebControls;
using Microsoft.SharePoint.Administration;
using System.Web.UI.WebControls;
namespace SP404Handler
{
public class SP404HandlerPage : LayoutsPageBase
{
protected WebApplicationSelector lstWebApps;
protected PageLevelError pageLevelError;
protected Button ButtonDoneOK;
protected TextBox txt404PageName;
protected Microsoft.SharePoint.WebControls.InputFormSection I404Section;
protected void lstWebApps_OnContextChange(object sender, EventArgs e)
{
try
{
SPWebApplication webApp = lstWebApps.CurrentItem;
BindData(webApp);
}
catch (Exception err)
{
pageLevelError.ErrorText = err.Message;
}
}
protected void DoneButtonOkClick(object sender, EventArgs e)
{
SPWebApplication webApp = lstWebApps.CurrentItem;
try
{
webApp.FileNotFoundPage = txt404PageName.Text;
webApp.Update();
}
catch (Exception ex)
{
pageLevelError.ErrorText = ex.Message;
}
}
private void BindData(SPWebApplication webApp)
{
txt404PageName.Text = webApp.FileNotFoundPage;
}
}
}
The source can be downloaded from here, or a complied solution from here.
I had the task of writing a web part that had a lot of UI code and one of the requirements was that a designer could change the look and layout as a toolpart setting. The smartpart stuff is OK, but it doesn't really provide the kind of flexibility that I was after. I came up with a neat solution that I think is pretty powerful. We've all seen the example webpart code that has lots of layout controls like tables defined in the CreateChildControls method, it's awful to write and its not easy to change. What we really need is a way to define a template that is provided by an external source and then to hookup our code to perform operations on it. Lets have a look at some sample code: using System;
using System.Web.UI;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.WebControls;
namespace Template
{
public class Template : System.Web.UI.WebControls.WebParts.WebPart
{
//string with UI code to be added to the page
protected string templateHTML = string.Empty;
//Control IDs, the template must use these ID's
private string searchButtonID = "searchImgBtn";
private string allTextID = "allTxt";
//controls that will be found from the template
protected TextBox allTxt = null;
protected ImageButton searchBtn = null;
public Template()
{
}
protected override void CreateChildControls()
{
if (templateHTML != string.Empty)
{
try
{
//create a control that has been parsed by asp.net
Control template = Page.ParseControl(templateHTML);
//add it to the page
this.Controls.Add(template);
//search for the known controls
allTxt = template.FindControl(allTextID) as TextBox;
searchBtn = template.FindControl(searchButtonID) as ImageButton;
//hook up any events
if (searchBtn != null)
{
searchBtn.Click += new ImageClickEventHandler(btnSearch_Click);
}
}
catch (Exception err)
{
this.Controls.Add(
new LiteralControl(string.Format("Error applying template: {0}", err.Message))
);
}
}
else
{
this.Controls.Add(new LiteralControl("Please enter a template in the toolpart settings"));
}
base.CreateChildControls();
}
void btnSearch_Click(object sender, ImageClickEventArgs e)
{
//if the template included the textbox, show its results
if (allTxt != null)
{
this.Controls.Add(new LiteralControl("Searched On: " + allTxt.Text));
}
}
[System.Web.UI.WebControls.WebParts.WebBrowsable(true),
System.Web.UI.WebControls.WebParts.WebDisplayName("Layout Template"),
System.Web.UI.WebControls.WebParts.WebDescription("Template that is used for layout"),
System.Web.UI.WebControls.WebParts.Personalizable(
System.Web.UI.WebControls.WebParts.PersonalizationScope.Shared),
System.ComponentModel.Category("Template Settings"),
System.ComponentModel.DefaultValue("")
]
public string TemplateHTML
{
get
{return templateHTML;}
set
{templateHTML = value;}
}
}
}
The template that is set with the toolpart should look like this:
<table>
<tr>
<td>Search On:</td>
<td><asp:TextBox ID="allTxt" runat="server" />
</tr>
<tr>
<td colspan="2"><asp:ImageButton runat="server" ID="searchImgBtn" ImageUrl="Search.gif"/>
</tr>
<table>
Notice how the control IDs match the values in the code, that is important, because we use FindControl to bind the code to the UI.
So what we end up with is a flexible way to define our UI code in a way that most of us are used to. I've used this in a number of places now and haven't seen any issues, drop me a line if you start using this approach.
I've written a Sharepoint feature that will modify the web.config file and add the entries needed to enable the ASP.NET AJAX Control Toolkit. I've made the feature scoped to the web application level, so to activate the feature (which will write to the web.config file) go to Application Management in central admin, then to Manage Web application features: Next step is to activate the feature: I was thinking that I would just provide the source code and that you could include it in your solution project as this functionality doesn't do much as a stand alone feature. To use this feature you will need the source, I'm thinking about uploading to CodePlex, I'm just not sure if one code file is enough to warrant this. To add this feature to your existing project you should include the following information in your feature.xml: <?xml version="1.0" encoding="utf-8" ?>
<Feature xmlns="http://schemas.microsoft.com/sharepoint/"
Id="YOUR GUID ID"
Scope="WebApplication"
Title="ASP.NET Ajax web.config update tool"
Description="Adds web.config entries to enable ASP.NET Ajax"
ReceiverAssembly="Full name and version of your assembly"
ReceiverClass="Features.AjaxWebConfigInstaller"
Hidden="False"
>
</Feature>
|