Wednesday 25 April 2007

notes when deploying webpart to SPS2003 server

It's sometimes confusing when you are trying to deploy a webpart to a box where there's no remote debug installed and no Visual Studio either...-_-! Not much of a choice for you if there's anything wrong with your webparts and sharepoint simply just tells you that it's causing a problem , please remove it...

what you need to do is to set the property of from "CallStack" to "true" and set the "CustomErrors = Off" in your web.config. By doing that, you will be able to see the descent error messages as you would in normal ASP.net application .

Monday 23 April 2007

Sharepoint permission exception when adding in your assembly

To me, Sharepoint technology as a derivative from ASP.net 1.1 or 2, the only difference is couple of new libraries and mostly pain will be from deployment of your webparts and features, etc. The coding itself sees no difference with normal C#/VB + ASP.net process when customizing your own ASP.net app. The worst nightmare might be the tackling with CAS(Code Access Security). Yes, that's right, the one who gives you rubbish like "request for SPPermission ... blah blah failed" etc.



You might enjoy a lot in coding sharepoint webparts and features. It won't bother you that much if you just play around with simple label controls on your webparts and not accessing other system resources. Once you want to do more with your controls on webparts and even , say do a databind to your datagrid which calls another object in your assembly which might retrieve data from sharepoint list or local system resource, the error message could be saying SPPermission request failed(roughly speaking). Obviously, sharepoint is restricting your code from executing to protect the system integrity or w/e the reason you might hate to guts... but anyway, There's a couple of things you might want to try from the level of least radical to the other end.

  1. If you open up the web.config file which you will see the node with its value set to "WSS_Minimal" by default, literally you might know already why the sharepoint won't let you access some of its resources like list data etc. So if you do a search for keyword in this web.config file for "WSS_Minimal",the security policy section contains both minimal and medium security policy. Which points to each own xml policy file. If you want to adventure into the policy file and customize it, the words like minimal and medium will not stop you from doing silly things on this computer. So, suit yourself, try medium first before any drastic solutions.
  2. When you deploy a webpart, we know that we can deploy it as a safe control and configure this assembly's CAS, or just put in in GAC(Global Assembly Cache,i.e. sytemdrive\windows\assembly) to give it much wider access to your system which won't be under control of your WSS policy control. This will solve most of your problem
  3. If none of above works for you, you might start thinking give your assembly a full-trust , which means it can access all resources in your system and do whatever it wants to(basically what you or the hacker wants it to ). To do this, go to control panel->administrative tools->Microsoft .Net Framework 1.1/2.0 Configuration->Runtime Security Policy->increase assembly trust. then you will see a slider bar which lets you define different level of trust, suit yourself in this action , however, referring to previous failures made in step 1 and 2, I think you might want to go for full-trust anyway...

Any of above might solve your security /permission related exception thrown by sharepoint or .net framework according to individual case. Just bear in mind that CAS is the essential part that you can't avoid during sharepoint development.



Tuesday 17 April 2007

Task Pane & Ribbon Combination on Outlook 2007 Walkthrough with VSTO 2005 SE Add-in project






If you are looking for beginner guide on developing either customized ribbon or task pane. This article might not be the one for you. The easiest and also the quickest way to jump start is to watch the MSDN nugget Customising the Ribbon in 2007 Microsoft Office Applications,Dynamically Updating the Ribbon in the 2007 Microsoft Office System, and Working with Images in the 2007 Microsoft Office System Ribbon and Building a Custom Task Pane for the 2007 Microsoft Office System by Martin Perry.

So to start, please click the picture on top of the first paragraph,in this article, I just want to introduce the combination of ribbon and task pane for Outlook only. It contains only one toggle button on the ribbon to control show/hide of my task pane which contains only a treeview and couple of buttons. Joanna Bichsel has a posted an interesting article about ribbon/task pane combination with a cute name of "Mr. Task Pane, meet Mrs. Ribbon" ;) If you are looking for a MS Word 2007 solution, that's definitely the one for you. However, Outlook , as a maverick species in Office System , holds its ribbon only when you open up the mail item in the main explorer window. Therefore,you somehow need to obtain the handle of the mail item window-- Inspector window, so MS calls it. So by doing this, you can attach the task pane to individual mail item instead of the main explorer window. So we start by creating our own Inspector Wrapper class to contain both our own customized taskpane in project and the Outlook.Inspector object. Please note we need a inspector collection as well as the custom task pane collection to contain all the mail items in Outlook explorer so we can handle the init and cleaning up for every individual mail item. Later on , we use a dictionary to implement inspector collection.

So we add a new class in our project , say MyInspectorWrapper, and put in following reference before your class definition(I assume you know how to create an add-in project using VSTO SE already so you get the correct reference):



using Microsoft.Office.Tools;
using Outlook = Microsoft.Office.Interop.Outlook;


then in your class, add in following private member variables for our Inspector and our customtaskpane object which will point to our own task pane where the useful controls are living in



private Outlook.Inspector myInspector;
private CustomTaskPane myTaskPane ;


Then we pass in the runtime inspector through an overloaded constructor:



public InspectorWrapper(Outlook.Inspector insp)
{
myInspector = insp;
//handle the form closing event of the inspector window
((Outlook.InspectorEvents_Event)myInspector).Close += new Outlook.InspectorEvents_CloseEventHandler(InspectorWrapper_Close);

//this is where you pass in your own task pane to //customitaskpane object at runtime
myTaskPane = Globals.ThisAddIn.CustomTaskPanes.Add(new YourTaskPane(), "Your Pane", myInspector);

//update the visual appearance of ribbon button when the
//task pane is shown or hidden

myTaskPane.VisibleChanged += new EventHandler(MyTask_VisibleChanged);

}

then we handle the close event of the run time inspector.



public void InspectorWrapper_Close()
{
if (myTaskPane != null)
{
Globals.ThisAddIn.CustomTaskPanes.Remove(myTaskPane);

}
//destroy the taskPane
myTaskPane = null;

Globals.ThisAddIn.inspectorCols.Remove(myInspector);

//unload the close event of inspector
((Outlook.InspectorEvents_Event)myInspector).Close -= new Outlook.InspectorEvents_CloseEventHandler(InspectorWrapper_Close);

myInspector = null;

}

then handle the visual appearance change, i.e. when you hide the task pane from certain mail item inspector window, we need to popup the toggle button pressed in beforehand.



public void MyTask_VisibleChanged(object sender, EventArgs e)
{
//method to refresh button will be written in the ribbon //class, the dangerous bit is using public access ribbon class object to use your RefreshControl method
Globals.ThisAddIn.ribbon.RefreshControl("MyToggleButton");

}

And naturally, you might be wondering where the "ribbon" property is from. It's actually just a public member variable in your ThisAddin partial class together within your ribbon cs file

you can put it in like



public YourRibbon ribbon;


at last, expose your custom task pane, so it will be called out when press in the ribbon toggle button.



public CustomTaskPane CustTaskPane
{
get { return myTaskPane; }
}


At this point, you have a wrapper contains your runtime inspector window object and your task pane to be shown in the individual mail item. Now we need to handle the new inspector event in the insepctor collection that we are going to make in the ThisAddin.cs file

add these namespaces in front of your partial class ThisAddin



using System;
using System.Windows.Forms;
using System.Collections.Generic;
using Microsoft.VisualStudio.Tools.Applications.Runtime;
using Outlook = Microsoft.Office.Interop.Outlook;
using Office = Microsoft.Office.Core;

create the runtime inspectors object and the collection of all inspectors corresponding to mail items in your inbox.



private Outlook.Inspectors inspectors;

public Dictionary inspectorCols = new Dictionary();




in the start_up method of the addin, write code to wire up the event of new inspector event of Outlook.Inspectors and bind all the mail items inspector window to the inspector collection.




private void ThisAddIn_Startup(object sender, System.EventArgs e)
{
inspectors = this.Application.Inspectors;
inspectors.NewInspector += new Outlook.InspectorsEvents_NewInspectorEventHandler(Inspectors_NewInspector);

//bind all messages with inspector event handler
foreach (Outlook.Inspector insp in inspectors)
{

Inspectors_NewInspector(insp);
}

}


and clean up in the addin's shutdown event handler



//clean up the memory allocated in startup
private void ThisAddIn_Shutdown(object sender, System.EventArgs e)
{
inspectors.NewInspector -= new Outlook.InspectorsEvents_NewInspectorEventHandler(Inspectors_NewInspector);
inspectors = null;
inspectorCols = null;
}

then we write the event handler for newinspector event



public void Inspectors_NewInspector(Outlook.Inspector insp)
{
//initiate inspector
if (insp.CurrentItem is Outlook.MailItem)
{
inspectorCols.Add(insp, new InspectorWrapper(insp));

//if ribbon is initialized,create new task pane for each email item
if (ribbon != null && ribbon.RibbonUI != null)
{
ribbon.RefreshControl("MyToggleButton");
}
}
}


The RefreshControl method will be written in your ribbon class to update the appearance of your togglebutton, and the content of this method will be simply calling invalidate control on your IRibbonUI's control, i.e. your toggle button



public void RefreshControl(string controlID)
{
//refresh the toggle button
ribbon.InvalidateControl(controlID); ;

}

and write your toggle button callback to handle the onAction event



public void onTogglebuttonClick(Office.IRibbonControl control, bool isPressed)
{


Outlook.Inspector insp = (Outlook.Inspector)control.Context;
InspectorWrapper thisWrapper = null;

if (Globals.ThisAddIn.inspectorCols.ContainsKey(insp))
//get the inspector containing the taskpane
thisWrapper = Globals.ThisAddIn.inspectorCols[insp];

if (thisWrapper != null)
{
//get your own task pane's instance and show it when button is pressed
Microsoft.Office.Tools.CustomTaskPane individualTaskPane = thisWrapper.CustTaskPane;
if (individualTaskPane != null)
{
individualTaskPane.Visible = isPressed;
}

}

}

Now we have event handler for toggle button, our own inspector instance which is implemented using an Instance wrapper, we just need to tell the ribbon class when to fireup the ribbon UI. In our case, I only want to show it when you click on the existing email item , so the magic word is "Microsoft.Outlook.Mail.Read" and let's put that check conditin into our GetCustomUI callback which is created by the VS. Bear with me a moment, we are almost there...



/**only show it when reading mails.
*if your want it in new email as well. put in check for "Microsoft.Outlook.Mail.Compose"
*/

public string GetCustomUI(string ribbonID)
{
string ribbonXML = String.Empty;

if (ribbonID == "Microsoft.Outlook.Mail.Read")
{

ribbonXML = GetResourceText("RibbonAndCTP.YourRibbon.xml");


}

return ribbonXML;



}

That's it. All done. Now if you compile your project and you should have a neat task pane with your useful controls when you press down the toggle button in every email window. BTW, if you have a "Close" button on the task pane (9 out of 10 you have)to close/hide the task pane, you will need to handle the pressstate event, so you can write GetPressedState callback in your ribbon class to handle the property in your ribbon xml "getPressed" for a final touch-up.



public bool GetPressedState(Office.IRibbonControl control)
{
Outlook.Inspector insp = (Outlook.Inspector)control.Context;
//if inspector window is in inpector collection, return the current state of taks pane,
//otherwise pop up the toggle button

if (Globals.ThisAddIn.inspectorCols.ContainsKey(insp))
{
//get the inspector wrapper contains the task pane
InspectorWrapper thisWrapper = Globals.ThisAddIn.inspectorCols[insp];
Microsoft.Office.Tools.CustomTaskPane thisPane = thisWrapper.CustTaskPane;
return thisPane.Visible;


}
else
{
return false;
}

}

Friday 13 April 2007

Add a new look-up type list item to your list programmatically

It's easy to add a new item into your list by simply assigning string values to your list which is , actually just a string. But if you want to add a listitem which actually is a reference or look-up to another list data in your sharepoint site? if you pass it in with a string, you will see error message like this. :

Invalid data has been used to update the list item. The field you are trying to update may be read only.


in your debugging message either in your event viewer or your IDE. It's as you expected , the value you inserted needs to be casted into correct type. So for example, your field is a look-up field referencing people and group list data in your site. You should use SPUser to cast the type, I use the current user for demo purpose.


SPSite mySite = new SPSite("http://yoursite");

//get your top-level site collection
//or fill in the param if you want to specify sub-site
SPWeb web = mySite.OpenWeb();
SPList targetList = web.Lists["Target List"];

//initiate new list item as usual
SPListItem newItem = targetList.Add();

//create a SPUser for current login
SPUser approver = web.Users["User_Name"];
newItem["Approver"] = approver;




this way you will be safe from casting errors.

Thursday 12 April 2007

Check if current form client is rich or thin by code

Since you can make the InfoPath form both web/Infopath compatible, sometimes you might need to tell if the user is using a thin-client with a web browser to fill in the form or a rich client instead because functionalities like some data validation, popup message, custom task pane etc, will not appear in the web browser at all. It's easy to tell by using property IsBrowser


//if it's rich client user environment
if(! Application.Environment.IsBrowser)
{
//do something that is only available for client with Infopath installed
...
}

Wednesday 11 April 2007

dynamic data using XPath functions for your InfoPath 2007 form elements

As MS advertises VSTO(Visual Studio Tools for Office) and its little bro VSTA(VST for Application) which is the conversion of VBA into vb.net/c# solution(which is good, I personally dislike VBA :p), InfoPath form can be developed by accessing form data dynamically using XPath.XPathNavigator such as following :


XML.XPath.XPathNavigator root,myField;
root = this.MainDataSource.CreateNavigator();
myField = root.SelectSingleNode("//my:myField",this.NamespaceManager);




then you can using myField.SetValue() /myField.Value to set/get the value of your textbox, dropdownlist and more...

In here, I want to introduce an interesting use of XPath functions built in to InfoPath. It's a scenario that you want to use a hyperlink to call out an email client installed on your local machine and fill out the form data on the client,say Outlook to prepare an email for your disposal in which case, VSTA/VSTO might be a bad/harder solution to implement(you don't get the address your hyperlink is pointing to since your hyperlink doesn't appear to be a xml node not in xml file at all...)

we all know that the RFC standard URL mailto: will get you the client :e.g. Outlook Express or Outlook for you with the subject, cc or whatever you append in the mailto string. But in InfoPath form, after you inserted a hyperlink and try to call out an email client, you might wonder how on earth you gonna have to type in this url in the text box, and the fatal shortcoming for this is that you don't get dynamic datafrom your form. Well, it's easy, using XPath fn : concat(string, string, my:myfield) in your data source field in the hyperlink property box. so you can use both static and dynamic data this way. see illustration


Hope this will save someone's time.Enjoy!

Tuesday 10 April 2007

traps in enabling the publishing feature

all dudes note following. If you fancy making your site a open-to-public Internet site using WSS 3.0/MOSS, you better watch out the check in/check out and publishing traps you might fall into since this is what I got. Basically I forgot to check in the default.aspx and ... most importantly the css file I modified -_-! (I hate these days, really)so when different user logs in, he/she will either miss your default.aspx file therefore get stuck in the void or sees the old style... remember, remember , to check in your files in Sharepoint designer first! To be honest,I think I have disabled the publishing feature for my site collection already!

Wednesday 4 April 2007

MOSS Enterprise Form Service

If you are in an corporate environment, you will be very likely to use MOSS Enterprise edition , which offers web representation of your InfoPath forms. So users won't have to have InfoPath client installed on local machine. A thin-client infrastructure comes to realization by this technology.

So in order to enable this feature, go to central admin-> Operation ->Upgrade and Migration ->Enable Enterprise Features , then you need to input the product key for enterprise license. This would be a one way trip, you cannot revert back to standard edition once you have your MOSS enterprise enabled. After this, you will see an extra section in your Application Management tab called "InfoPath Forms Services ", click the link in this section called "Configure InfoPath Forms Services", make sure "Allow users to browser-enable form templates " and "Render form templates that are browser-enabled by users " are ticked so now you can have browser-enabled infopath form published to your MOSS site. Do be aware that not all the infopath forms functionality are provided in this kind of web forms, such as complicated validation warning, placeholder message in control... I will keep this coming once I get a clear idea on what the catches are so you will decide if you really need this or not.

work flow enabled content type

As we all know , InfoPath forms contains managed code (c# or vb.net) will not be able to be published to MOSS libraries directly. Additional approval from site admin is required to publish. So I publish the template to local harddrive and use central admin to upload such template so template is appeared as a new site wide content type which can be used as default open/new template in the form library.However,after uploading the template to MOSS, if you go to manage form templates, you will always see the status of the newly uploaded template as "upgrading". I know it's timered job in MOSS and there should be somewhere you could set the interval of job scheduling. But I just couldn't find it... anyway, back to the content type with work flow enabled. You can either use infopath work flow using VSTA code, or using this work flow provided by sharepoint to bind it to your sharepoint form lib with content type you customized. You can capture the event of insert and update on the list to trigger your workflow and appoint the approver(s)