12 years of work within the Sharepoint world : let's talk about technical and functional thoughts around Sharepoint.
Friday, December 18, 2009
SharePoint 2010 Beta Documentation
I have found this doc published a few days ago on technet :
http://technet.microsoft.com/en-us/library/cc303422%28office.14%29.aspx
That's a good starting point !!!
SharePoint 2010, and InfoPath 2010 for Form customization
I have a really good feeling with the product that has some really good improvment :
- I love the taxonomy system
- I love the ribbon
- I love the form displayed on a layer (finally!!!)
- I love the performance services...
I have a VS2010 Solution containing new types (Located in 14/templates/XML/fldTypes_xxx.xml) that have a custom rendering (let's say two texteboxes). Those types are used in a content type I have developped too. This content type is used in a list.
Because it makes a big form, I have tried the form customization feature with Infopath...
I have been a bit disapointed...
All my custom fields that have a non standard type cannot be added on the infopath form (it mentions that when infopath is starting). So it means that I can customize my form but only without thos fields.. Useless for me... Sh**
I'm trying to find a way to integrate thos types in infopath 2010.. So far.. nothing...
Wednesday, November 18, 2009
Sharepoint 2010 Beta Download from today!!
Download SharePoint Beta 2010 (on the Technet site).
I'll install it tomorrow...
Friday, August 21, 2009
Coachs SharePoint
How many time I have been asked to create a document to explain how to do this or that to begginer developper.
I have been waiting for a good and complete document for a while. On the msdn site, a really good one has been posted this spring (Yeah, I know I'm a bit late, but it's still a good thing to reference such a good link).
It is composed by 4 workshops. They are well made, and contain essential tips
Try it on http://msdn.microsoft.com/fr-fr/office/msdn.coach.sharepoint.aspx
Tuesday, July 21, 2009
SharePoint 2010 is coming
Wednesday, June 10, 2009
Send a document by email (from a document library)
Useless to write another one but usefull to reference its blog !
Wednesday, April 22, 2009
MOSS Administrator
I splitted the tool in three part : "Solutions" , "Features" and "Site hierarchy"
- Install solution
- Uninstall solution
- Deploy solutions
- Retract solution
- GetInformations on his farm and to get some information that the administration site doesn't give (information that you can also have using stsadm command lines..
The second one "Features"list everything about features :
- You can check basic information about your features. (order by scope)
- You can see on which element each of the feature is deployed (For instance, you can see all the sites on which you rfeature is deployed)
- A usefull functionnality is to be able to deploy a feature in a wider scope than the original scope (for example, deploying w site feature on all site of a webapplication (or even a farm!))
- You can activate Feature on One or some elements.
The third one is the big one :) It gives you, many information about your farm structure (from object ids, to content DBs for web apps, passing by activated feature listing...) and much much more. You can interact with object. For instance, you can set read or write lock mode.
Of course cause, it's a big thing, so far, not everything have been developped, but i work on it. I'll keep you informed.
The codeplex project for this : http://mossadministration.codeplex.com/Some screen shot (official version so far : 0.2). I hope to
- Features tab :
- WSP Solutions tab :
Tuesday, March 10, 2009
Replace a webpart by another
I have created a Codeplex for this : http://webpartreplacer.codeplex.com The next versions will be uploaded there soon !!!
Basically, How does it work? On a specific application, I try to load the assembly that contains the webparts (source and destination) using reflection :
System.Reflection.Assembly asm = System.Reflection.Assembly.Load(AssemblySrc);
System.Web.UI.WebControls.WebParts.WebPart wpS = (System.Web.UI.WebControls.WebParts.WebPart) asm.CreateInstance(ClassSrc);
Then I look inside the Default page of each web and subweb trying to find the webpart (by checking the type of each webparts on the page) and if I find it, I replace the webpart using the SPLimitedWebPartManager.
I know that I should also parse all the document libraries to find other webpart pages in order to parse them as well... But in my current project, it wasn't a need, and I was kind of lazy!
Below is the iteration how I check whether the webparts on the page are to be replaced.
public void ReplaceWebpart(SPWeb web)
{
try
{
SPLimitedWebPartManager theMan =
web.Files["Default.aspx"].GetLimitedWebPartManager(System.Web.UI.WebControls.WebParts.PersonalizationScope.Shared);
System.Reflection.Assembly asm = System.Reflection.Assembly.Load(AssemblySrc);
System.Web.UI.WebControls.WebParts.WebPart wpS = (System.Web.UI.WebControls.WebParts.WebPart) asm.CreateInstance(ClassSrc);
System.Reflection.Assembly asmD = System.Reflection.Assembly.Load(AssemblyDest);
for (int i = 0; i < theMan.WebParts.Count; i++)
{
System.Web.UI.WebControls.WebParts.WebPart wp = theMan.WebParts[i];
if (wp.GetType().Equals(wpS.GetType()))
{
System.Web.UI.WebControls.WebParts.WebPart wpD = (System.Web.UI.WebControls.WebParts.WebPart)asmD.CreateInstance(ClassDest);
wpD.Title = wp.Title;
theMan.AddWebPart(wpD, theMan.GetZoneID(wp), wp.ZoneIndex);
theMan.WebParts[wpD.ID].
theMan.DeleteWebPart(wp);
Result++;
i--;
}
}
foreach (SPWeb Sweb in web.Webs)
{
ReplaceWebpart(Sweb);
Sweb.Dispose();
}
}
catch (Exception ex)
{
}
finally
{
}
}
OK, I have created a ObjectCollection object containing a list of KeyValuePair
Then before the AddWebPart([...]) code line, I have added the following code :
foreach (Object LstItem in PropCollection)
{
try
{
System.Reflection.PropertyInfo prop = wpD.GetType().GetProperty(((KeyValuePair<String, String>)LstItem).Key);
if (prop != null)
{
String[] propValues = new String[1] { ((KeyValuePair<String, String>)LstItem).Value };
if (prop.PropertyType.Equals(String.Empty.GetType()))
{
prop.SetValue(wpD,((KeyValuePair<String, String>)LstItem).Value,null);
}
else
{
Type[] types = new Type[1] { String.Empty.GetType() };
prop.SetValue(wpD, prop.PropertyType.GetMethod("Parse", types, null).Invoke(prop, propValues), null);
}
}
}
catch (Exception exceptionProp)
{}
}
What happens here? We just try to get The property we try to set contained in our newly instanced WebPart. If found, we try to Use the Parse method of this property (Yes this only work with simple types (as Int32, Bool, String), and then we invoke it! Simple!!
That's it. You just have to make an iteration on each SPSite of you application and here we are!
Once again, be aware that this code only checks the default.aspx page !!
Sorry for that!!
Go to check in the codeplex project.
Thursday, February 26, 2009
Bug on XML Site definition
<View List="$Resources:core,lists_Folder;/$Resources:core,announce_Folder;"
BaseViewID="0"
WebPartZoneID="Left">
<RowLimit Paged="TRUE">5</RowLimit>
</View>
but, it didn't work...WTF??
How sweet it is when you try to use a "standard" attribute and when you realize that...Gasp It hasn't been implemented By MS (That was the real answer that they gave us!)
We have spent a few days on this bug, so I just blog it hoping that the one who is looking for solution find it!! There is no solutions but creating a feature that will replace the announcementList feature. This feature that you can call "CustomeAnnouncementList" will be a copy/Paste of the basic Anouncement List feature amended (in the element.xml file) with the rowlimit attribute inside. Then you have to activate this feature on all sites. Bingo! Of course, if you don't want to see 2 anouncement list in your available lists, you will also have to deactivate the old feature.
Cheers & Good luck
Wednesday, February 25, 2009
Dynamically creat a SPView with checkbox that will be display in a dynamically created ListViewe
My problem was this one :
"I need to display on a specific page the default view of a list, BUT beside each items of this list must be displayed a checkbox in order to be able to select items on this list"
huuu....
The solution I chose was to recreate a clone of the defaultview that will never be displayed to the users but that will be selected as SPView of the ListView I create on my page.
Here we go...
1/ My Page takes in parameter the ID of the list I'm working on, i call the parameter "List":
Guid ListId = new Guid(this.Page.Request["List"]);2/ Then whenever I reach the page, i check whether the view already exists, if not i create it cloning the default view and adding to it a checbox field. If the clone of the actual defaultview exists i just use it! Easy! In my code below, the listview object is called Select_List
bool creatingView = false;
SPView newView = null;
SPSecurity.RunWithElevatedPrivileges(delegate()
{
using (SPSite Mysite = new SPSite(siteID))
{
SPWeb MyWeb = Mysite.OpenWeb(webID);
MyWeb.AllowUnsafeUpdates = true;
SPList MyList = MyWeb.Lists[ListId];
foreach (SPView view in MyList.Views)
{
if (view.Title.Equals(MyList.DefaultView.Title+ "_CloneForSelect"))
{
newView = view;
}
}
if (newView == null)
{
if (!MyList.Fields.ContainsField("Select"))
{
MyList.Fields.AddFieldAsXml("<Field Type=\"Computed\" ReadOnly=\"TRUE\" Name=\"ListItemSelection\" DisplayName=\"Select\" Sortable=\"FALSE\" Filterable=\"FALSE\" EnableLookup=\"FALSE\" SourceID=\"http://schemas.microsoft.com/sharepoint/v3\" StaticName=\"ListItemSelection\">" +"<FieldRefs> <FieldRef Name=\"ID\" /> </FieldRefs>" + "<DisplayPattern> <HTML><![CDATA[<input id=\"chk_select]]></HTML><Column Name=\"ID\" HTMLEncode=\"TRUE\" /><HTML><![CDATA[\" name=\"chk_select\" type=\"checkbox\" ]]></HTML>" + "<HTML><![CDATA[LItemId=\"]]></HTML>" + "<Column Name=\"ID\" HTMLEncode=\"TRUE\" />" + "<HTML><![CDATA[\"/> ]]></HTML>" + "</DisplayPattern></Field>");
}
SPFieldComputed fieldComp = (SPFieldComputed)MyList.Fields["Select"];
newView = MyList.DefaultView.Clone(MyList.DefaultView.Title + "_CloneForSelect", 1000, true, false);
newView.Hidden = true;
newView.Scope = SPViewScope.FilesOnly;
newView.ViewFields.Add(fieldComp);
newView.Update();
creatingView = true;
}
MyWeb.Dispose();
}
});
if (!creatingView)
{
Select_List.ViewId = newView.ID.ToString();
}
else
{
Response.Write("");
}
Select_List.ListId = this.Page.Request["List"];
1/ How to create a column Checkbox in a list?
It's quite tricky cause you must create the column using a CAML definition.
if (!MyList.Fields.ContainsField("Select"))
{
MyList.Fields.AddFieldAsXml("<Field Type=\"Computed\" ReadOnly=\"TRUE\" Name=\"ListItemSelection\" DisplayName=\"Select\" Sortable=\"FALSE\" Filterable=\"FALSE\" EnableLookup=\"FALSE\" SourceID=\"http://schemas.microsoft.com/sharepoint/v3\" StaticName=\"ListItemSelection\">" +"<FieldRefs> <FieldRef Name=\"ID\" /> </FieldRefs>" +
"<DisplayPattern> <HTML><![CDATA[<input id=\"chk_select]]></HTML><Column Name=\"ID\" HTMLEncode=\"TRUE\" /><HTML><![CDATA[\"
name=\"chk_select\" type=\"checkbox\" ]]></HTML>" +
"<HTML><![CDATA[LItemId=\"]]></HTML>" +
"<Column Name=\"ID\" HTMLEncode=\"TRUE\" />" +
"<HTML><![CDATA[\"/> ]]></HTML>" + "</DisplayPattern></Field>");
}
2/ OK, but now, i have the display How do I get my checkboxes values after postback ?
Well, let's continue to be tricky!! I just create two Hidden field on my page
<asp:HiddenField runat="server" id="listSrv_items" />
<asp:HiddenField runat="server" id="checked_ids" />
<asp:Button ID="Btn_valid" OnClientClick="fill_ids();" OnClick="Btn_valid_Click" runat="server"/>
here we go for the js that i manually add to the page in the render method:
protected override void Render(HtmlTextWriter writer)
{
this.ClientScript.RegisterClientScriptBlock(this.GetType(), "MyScript", @"
<script>
function fill_ids()
{
var items= document.getElementById('" + listSrv_items.ClientID + @"').value;
var checkedIds= document.getElementById('" + checked_ids.ClientID + @"');
checkedIds.value='';
var SParray = items.split(';');
for (ix=0; ix< SParray.length-1; ix++)
{
var item = document.getElementById(""chk_select""+SParray[ix])
if (item!=null && item.checked)
{
checkedIds.value += SParray[ix] + ';';
}
}
if (checkedIds.value.length>0)
checkedIds.value = checkedIds.value.substr(0,checkedIds.value.length-1);
}
</script>");
listSrv_items.Value = "";
foreach (SPListItem item in _rootList.Items)
{
listSrv_items.Value += item.ID + ";";
}
}
Get SPList.ID From URL
Some Regexp, some recursivity and here we are :
public SPList getListFromUrl(string url)
{
try
{
if (!url.EndsWith("/")) url = url + "/";
//match list
Regex listExpReg = new Regex("(?.+)\\/Sites\\/(?.+)\\/Lists\\/(?.+)\\/.*", RegexOptions.IgnoreCase);
if (listExpReg.IsMatch(url))
{
Match siteMatch = listExpReg.Match(url);
String racine = siteMatch.Groups["Racine"].Value;
String siteToFind = siteMatch.Groups["Sites"].Value;
String listeToFind = siteMatch.Groups["LName"].Value;
String[] splitedSiteList = siteToFind.Split('/');
SPSite rootsite = new SPSite(racine + "/Sites/" + splitedSiteList[0]);
if (rootsite != null)
{
using (SPWeb webNavigated = rootsite.RootWeb)
{
foreach (string s in splitedSiteList)
{
if (s != splitedSiteList[0])
{
SPWeb TempWeb = webNavigated.Webs[s];
webNavigated = TempWeb;
TempWeb.Dispose();
}
}
SPList listsearched = webNavigated.GetList(url);
_destinationWeb = webNavigated;
rootsite.Dispose();
}
return listsearched;
}
else
{
return null;
}
}
else
return null;
}
catch (Exception exc)
{
//BUUUUUUUUP
}
}
Try it!
Hope it helps
Tuesday, February 24, 2009
Rebrand SharePoint Error using HTTPModule and add this HttpModule to SharePoint web.config
But I have never really found a full answer. I'm gonna try to give a full one!
First, be aware, that there are many ways to catch the SharePoint errors, but to my knowledge, only this one is fully supporter by Microsoft, cause it uses an HTTPModule which is a supported way of working!
1/ Create your HTTPModule
The class you are about to create inherits System.Web.IHttpModule
During the Init of the module, add an event handler that will listen to the Error. It should be something like
public void Init(HttpApplication Context)
{
Context.Error += new EventHandler(Application_Error);
}
And now declare your event handler function:
public void Application_Error(object sender, EventArgs e)
{
HttpContext Context = HttpContext.Current;
Exception MyExc;
for (MyExc= Context.Server.GetLastError();
exception.InnerException != null;
exception = exception.InnerException) { }
String errorCode = DateTime.Now.ToString("ddMMyyyy-HHmmfff");
String errorMessage = MyExc.Message.ToString();
String errorStackTrace = MyExc.StackTrace.ToString();
HttpContext.Current.Server.ClearError();
HttpContext.Current.Response.Clear();
HttpContext.Current.Application.Clear();
HttpContext.Current.Application.Add("LastException", MyExc);
HttpContext.Current.Application.Add("ErrorMessage", errorMessage);
HttpContext.Current.Application.Add("ErrorCode", errorCode);
HttpContext.Current.Application.Add("ErrorStackTrace", errorStackTrace);
((System.Web.HttpApplication)(sender)).Response.Redirect("/_layouts/ErrorBranding/MyErrorHandler.aspx");
}
I was basically doing it differently, using an arrey of AllErrors, but it didn't give always the good exception, so I used a part of the code created by Anupam Ranku on his blog http://www.codeproject.com/KB/sharepoint/Handling_Error_Centrally.aspx.
From him, I took the fact that using
for (MyExc= Context.Server.GetLastError();exception.InnerException != null;exception = exception.InnerException) { }
2/ Create an Error page
On the Page_Load of this page, just verify that the last exception exists
if (!Page.IsPostBack)In this page, you can use your already defined
{
if (HttpContext.Current.Application.Get("LastException") != null)
{
//your handler
}
}HttpContext.Current.Application.Get("errorCode");
HttpContext.Current.Application.Get("errorMessage");
HttpContext.Current.Application.Get("errorStackTrace");
Design your page the way you want it to be!
3/ Modify your web.config
For this, let's assume that you have created a feature, and you have already a classe inheriting
SPFeatureReceiver.
So of course you have already created your 4 overidden functions (
FeatureActivated,
FeatureDeactivating,
FeatureInstalled,
FeatureUninstalling)
The FeatureActivated method will be launched after activation of the feature (smart name for a method that do so!!!), so what you want to do is to add your http module to the web.config (Carreful: it must be added to ALL the web servers of your farm), to do this let's use this :
private string _OWNER = "FTErrorBranding";
private string httpModuleValue = "MyNamespace.MyClass, MyAssembly, Version=1.0.0.0, Culture=neutral, PublicKeyToken=bce1c40c0905a9da";
public override void FeatureActivated(SPFeatureReceiverProperties properties)
{
try
{
SPWebApplication webApp = properties.Feature.Parent as SPWebApplication;
webApp.WebConfigModifications.Clear();
AddNewHttpModule(webApp.WebConfigModifications, "ErrorCustomization", httpModuleValue);
webApp.Update();
webApp.Farm.Services.GetValue().ApplyWebConfigModifications();
}
catch (Exception ex)
{
throw new Exception(ex.Message);
}
}
private void AddNewHttpModule(Collection allModifications, string name, string value)
{
SPWebConfigModification modification = new SPWebConfigModification(string.Format
("add[@name='{0}']", name), "configuration/system.web/httpModules");
modification.Type = SPWebConfigModification.SPWebConfigModificationType.EnsureChildNode;
modification.Owner = _OWNER;
modification.Value = string.Format(CultureInfo.InvariantCulture, string.Format("", name, value));
if (allModifications.Contains(modification))
{
allModifications.Remove(modification);
}
allModifications.Add(modification);
}
To conclude, See the image of my VS project, in order to understand the wya the project is built (I'm addicted to WSPBuilder!)
4/ Addendum -April 2009 - Catch WebParts Error
So far, the feature worked almost perfectly. I only add one issue with Webpart errors. SharePoint, on the error page sometime add a link to go to the Webpart management page. The HttpModule can't see the difference between those kind of errors and others...(until now) !!
I have added a function in the httpModule that recognize whether an exception has been thrown from a webpart or not... here it is!
private bool IsWebPartOnExceptionStack(Exception e)
{
StackTrace trace = new StackTrace(e);
int index = 0;
while (index <>
{
Type declaringType = trace.GetFrame(index).GetMethod().DeclaringType;
if (!typeof(WebPart).IsAssignableFrom(declaringType))
{
index++;
continue;
}
else
return true;
}
}
}
At the end of the Application_Error method, just add a new Application variable :
HttpContext.Current.Application.Add("ISWPError", IsWebPartOnExceptionStack(exception));
In your error page, you just have to add a if then else, to manage this case.