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
...
}