 |
|
If you have a question or suggestion, please contact me through Windows Live Messenger. My status:
 . If I am not online, please send me an e-mail. |
|
|
|
|
|
|
 |
|
|
|
|
|
|
|
|
9/3/2008I am pretty sure you all have customers/users that use Word, Excel and PowerPoint 2003 in combination with SharePoint 2007. And you probably explained them all the when they want to edit a document, they need to open the context menu and select "Edit in ....". Instead of just clicking on the name in the view. This opens the document as readonly on Office2003. Users then make changes to the document, hit the save button and get the Save As dialog. If 'Require checkout' is enabled, they need to save the document using a new name, because the document cannot be overwritten. It is pretty hard to explain and for users it is hard to get used to it. I have created a custom document library schema that has 2 extra viewfields. These fields, when clicked make the Office applications behave just like when users click the link "Edit in Microsoft Word" for a Word document. After you installed the template and you open a Word document in Word 2003, Words asks if you want to check-out the document (if this is enabled, otherwise it just opens the document in edit mode). I have called the document library an "Edit Document Library". You can download it from our CodePlex site. Before you install it, please note that you will only benefit from this when you use Office 2003. Office 2007 behaves like you would expect. After running the installer, and deploying the solution to your web application(s), your top level sites have a new site collection feature called "Edit Document Library": After activating this feature, all subsites now have a new document library template available: After you created your new document library, you are done. If you look at the view definition of the All Documents view, you will notice the new field that is selected as the "Name" field: In fact, your library has 2 new fields: - Name (linked to Edit document)
- Name (linked to Edit document, with edit menu)
The last part of this post is for those of you who are interested in how I created this library. In the schema.xml of the list template, I created the 2 new field definitions. I changed the DisplayPattern of the Name (linked to Edit document) field. The CAML part that actually renders the link now looks like this: It renders a link to the document and when the user clicks this link, it calls the javascript function "editDocumentWithProgID2". It checks the ForceCheckout property of the list and the value of the Checkoutuser to find out the correct value for the check-out behavior. <HTML><![CDATA[<A onfocus="OnLink(this)" HREF="#]]></HTML>
<HTML><![CDATA[" onclick="javascript:editDocumentWithProgID2(']]></HTML>
<Field Name="EncodedAbsUrl"/>
<HTML><![CDATA[', '', 'SharePoint.OpenDocuments',]]></HTML>
<FieldSwitch>
<Expr>
<ListProperty Select="ForceCheckout"/>
</Expr>
<Case Value="1">
<IfEqual>
<Expr1>
<Column Name="CheckoutUser" />
</Expr1>
<Expr2></Expr2>
<Then>
<HTML><![CDATA[1]]></HTML>
</Then>
<Else>
<HTML><![CDATA[0]]></HTML>
</Else>
</IfEqual>
</Case>
<Default>
<HTML><![CDATA[0]]></HTML>
</Default>
</FieldSwitch>
<HTML><![CDATA[, ']]></HTML>
<HttpVDir />
<HTML><![CDATA[', 0);">]]></HTML>
<UrlBaseName HTMLEncode="TRUE">
<LookupColumn Name="FileLeafRef"/>
</UrlBaseName>
<HTML><![CDATA[</A>]]></HTML>
<IfNew Name="Created_x0020_Date">
<HTML><![CDATA[<IMG SRC="/_layouts/[%=System.Threading.Thread.CurrentThread.CurrentUICulture.LCID%]/images/new.gif" alt="]]></HTML>
<HTML>$Resources:core,new_gif_alttext</HTML>
<HTML><![CDATA[">]]></HTML>
</IfNew>
Please download the code from the CodePlex site if you want to see this snippet in the context.
In SharePoint 2007 users have several options to include or hide pages in the navigation of a publishing site. In one of my projects I had to set these options from code. It took me some time to find out how to do it in a way that worked in all their sites, so I decided to write it down in this blog post. All code samples below use the CurrentNavigation. Setting navigation options for pages is done in a PublishingWeb object. Below you will find the code to find the navigation options for your site:
using (SPSite site = new SPSite("http://moss/navigation"))
{
using (SPWeb web = site.OpenWeb())
{
string pageUrl = "Page2.aspx";
bool show = false;
PublishingWeb pw = Microsoft.SharePoint.Publishing.PublishingWeb.GetPublishingWeb(web);
PublishingPageCollection webpages = pw.GetPublishingPages();
PublishingPage page = webpages[string.Format("Pages/{0}", pageUrl)];
}
}
Hide a page from the navigation
Suppose we have created a new Publishing site that has 3 pages, and the Navigation option "Show Pages" is switched on:
If we now want to hide Page 2 from the navigation, the easiest thing to do is call "ExcludeFromNavigation" on the publishingweb:
pw.ExcludeFromNavigation(false, page.ListItem.UniqueId); pw.Update();
This has the same effect as setting the property IncludeInCurrentNavigation of your PublishingPage to false. Using ExcludeFromNavigation, however is faster, as documented on MSDN. Another advantage is that you do not have to worry about the checked out state and publishing state of your page. If you change the property of you PublishingPage, you need to call Update(). If you have versioning switched on, you first need to check out the page. After modifiying the page options, you need to check-in and re-publish the page. And you need to take into account that a page can already be checked out. So using ExcludeFromNavigation is much easier.
The strange thing here that confused me is that your PublishingWeb also has a collection of navigation nodes in the property CurrentNavigationNodes. If you never touched any of the navigation options in the user interface, this collection is, and remains, empty when you hide pages from code. If you hide a page from the navigation using the user interface, SharePoint adds a SPNavigationNode object to the CurrentNavigationNodes for each page.
Each navigation node has a property IsVisible and it seemed logical to me that this is used when you hide a page from the navigation. Well, it is not. It also sets the IncludeInCurrentNavigation property of the publishing page. My advise is not to worry about these navigation nodes, let SharePoint take care of that.
Show a page in the navigation
If you want to include a page in the navigation, you use the IncludeInNavigation method of the PublishingWeb. The first boolean parameter adds your page back to the current navigation. If you pass a true, it will be added back to the global nav.
pw.IncludeInNavigation(false, page.ListItem.UniqueId); pw.Update();
If pages are not included in the navigation (you can check this using the PublishingWeb property IncludePagesInNavigation, you need to add a new SPNavigationNode to the navigation nodes:
bool found = false;
foreach (SPNavigationNode navNode in pw.CurrentNavigationNodes)
{
if (navNode.Url.ToLower().EndsWith(page.Url.ToLower()))
{
found = true;
break;
}
}
if (!found)
{
SPNavigationNode newNode = new SPNavigationNode(page.Title, string.Format("{0}/{1}", web.ServerRelativeUrl, page.Url), false);
pw.CurrentNavigationNodes.AddAsLast(newNode);
pw.Update();
}
Please note that you now have a custom navigation node in the navigation options, which is different from the default way of using pages in the navigation:
Although the user interface does not show the navigation nodes for your pages, they still are present (if they were there because you manually changed something in the user interface). If IncludePagesInNavigation is false, you are never able to show the node for your page. The drawback of adding a new node in this case is that when you switch on pages in the navigation, you will end up with 2 navigation nodes to your page.
Hope this makes playing with the navigation options of pages in MOSS a bit clearer. In this blogpost, I wrote about other navigation options.
If you now switch 7/17/2008In a previous article I showed how you can create MOSS publishing pages using SharePoint's FP-RPC. In this post I will describe how you can create a sub site using RPC. In this case we're using a combination of Frontpage RPC and WSS RPC. The first step is to create the site using RPC. After this, the site needs to be provisioned. In this step we will use WSS RPC to apply the site template. I am running the sample code below in a WinForms application. In the UI I can enter the URL for the parent site, the new url of the sub site, and the name of the site template. The UI looks like this: You will find the code in this zip file. This zip also contains the sample code for the first article about RPC. Step 1 - Create the site (FP RPC) In the code snippet below you will find the code to create the site. After initializing the variables, the rpcString is encoded (in GetRPCCall) before posting the RPC command using a WebClient. private string CreateSite(string siteUrl, string parentUrl, string newUrl)
{
string method = "create+service";
string service = string.Format("/{0}/{1}", parentUrl, newUrl);
string target = "_vti_bin/_vti_adm/admin.dll";
string path = string.Format("{0}/{1}/", siteUrl, parentUrl);
string rpcString = "method={0}&service_name={1}";
// Get the RPC call data
rpcString = String.Format(rpcString, method, service);
byte[] callData = GetRPCCall(rpcString);
string url = path + target;
// Send the request.
byte[] result = null;
using (WebClient client = new WebClient())
{
client.Credentials = System.Net.CredentialCache.DefaultCredentials;
client.Headers.Add("Content-Type", "application/x-www-form-urlencoded");
client.Headers.Add("X-Vermeer-Content-Type", "application/x-www-form-urlencoded");
result = client.UploadData(url, "POST", callData);
}
// Return the result.
return System.Text.Encoding.UTF8.GetString(result);
}
After running this code succesfully, you will get a HTML response that looks like this (it just shows the first few lines):
<html>
<head>
<title>vermeer RPC packet</title>
</head>
\n<body>\n<p>
method=create service:12.0.0.6219\n<p>
message=successfully created service '/News/Demo1'\n<p>
service=\n<ul>
\n<li>service_name=/News/Demo1\n<li>meta_info=\n<ul>
\n<li>vti_defaultlanguage\n<li>SW|en-us\n<li>vti_servercharsets\n<li>VX|windows-1257
When you navigate to your new site, it will look like this:
So we now have a site, but the template still needs to be applied.
Step 2 - Apply the template (WSS RPC)
First we initialize our variables and get a valid RequestDigest. This is a value that is used for security validation. The SharePoint object model contains a function to get a valid requestdigest for the Central Administration pages, but I didn't get it to work using that. Instead I request the parent site of our new site in code and extract the ID from the HTML that is returned by SharePoint.
string target = "_vti_bin/owssvr.dll";
string path = string.Format("{0}/{1}/{2}/", siteUrl, parentUrl, newUrl);
string rpcString = string.Empty;
// Get valid RequestDigest by getting the homepage of the parent site and parsing the HTML.
string digest = GetRequestDigest(siteUrl, parentUrl);
This digest string looks like this: 0x926E57EB23D64181BE56EA7C614FFDCD79206C697E290C588231A5359C6CA9975065D871616C185AC635A2BD5960D33B8EAFB00A3E7E8BDF279D365DBB31A5D8,17 Jul 2008 18:44:23 -0000
Here is the code to get a valid RequestDigest:
private string GetRequestDigest(string siteUrl, string parentUrl)
{
string search = "id=\"__REQUESTDIGEST\"";
string url = string.Format("{0}/{1}", siteUrl, parentUrl);
using (WebClient client = new WebClient())
{
client.Credentials = System.Net.CredentialCache.DefaultCredentials;
string digest = client.DownloadString(url);
int p = digest.ToLower().IndexOf(search.ToLower());
if (p > 0)
{
digest = digest.Substring(p+search.Length);
digest = digest.Substring(digest.IndexOf("\"") + 1);
digest = digest.Substring(0, digest.IndexOf("\""));
return digest;
}
}
return string.Empty;
}
I am not extremely happy about this implementation. If you have a better suggestion, please let me know. If you do not pass a valid value to the WSS RPC call, your call will fail and you will get this WebException:
{"The remote server returned an error: (404) Not Found."}
Next thing to do is initialize the parameters for our RPC call, encode them and turn them into a byte array:
rpcString += string.Format("__REQUESTDIGEST={0}&", digest);
rpcString += "Cmd=DisplayPost&PostBody=";
rpcString += "<Method ID='Text'>";
rpcString +="<SetVar Name='Cmd'>SiteProvision</SetVar>";
rpcString +="<SetVar Name='CreateLists'>True</SetVar>";
rpcString += "<SetVar Name='SiteTemplate'>STS#0</SetVar>";
rpcString += string.Format("<SetVar Name='__REQUESTDIGEST'>{0}</SetVar>", digest);
rpcString += "</Method>";
// Get the RPC call data
byte[] callData = System.Text.Encoding.UTF8.GetBytes(rpcString);
byte[] data = new byte[callData.Length];
callData.CopyTo(data, 0);
Last thing to do is setup a new WebClient that will handle our request and return the result:
string url = path + target;
// Send the request.
byte[] result = null;
using (WebClient client = new WebClient())
{
client.Credentials = System.Net.CredentialCache.DefaultCredentials;
client.Headers.Add("Content-Type", "application/x-www-form-urlencoded");
result = client.UploadData(url, "POST", data);
}
// Return the result.
return System.Text.Encoding.UTF8.GetString(result);
The result of our second step looks (if succesfull) like this:
<Result ID="Text" Code="0">
</Result>
And when we now navigate to the site, we will have a normal SharePoint site:
Click here to download the source code. 7/11/2008In several internal applications we reference 32 bits assemblies in our Visual Studio projects. I always had a problem running these applications on my 64 bit Vista. The applications were not able to resolve the referenced DLLs, resulting in a "File not found" exception in these assemblies. This happened in our applications that use the TeamExplorer access to TFS and the apps that use the Communicator assemblies. On of the examples was the Presence Screensaver that my colleague Michiel published on CodePlex. Thanks to the help of Serge, we were able to solve this by setting the Platform Target in the Visual Studio project options to X86. And don't forget to change this for all your build configurations (debug, release, etc.) I have just update the ZIP file that contains the screensaver, so if you are using Vista 64 and Communicator, you can use this nice screensaver. 
7/5/2008I just added my Content By Type webpart to CodePlex. You can find it in this project. I have removed the installers from the Content By Type site, And they are now available from this CodePlex release page. And if you are wondering what this web part is about, please read these blog postings: Enjoy! 6/30/2008In a lot of places in SharePoint you can select a list (or other objects like a site) in a nice popup dialog. It turns out it is really easy to do that yourself. In this example I will show how to let your users select a list in the webpart toolpart. The code is attached to this post (see link at the bottom of the post). The webpart itself is nothing special, it just renders the relative url to the list that the user has selected: In the CreateChildControls of the EditorPart, we will add a readonly textbox and a button to the controls collection. When clicked, the button calls the javascript function that we will add later. private TextBox _listUrl;
private Button _selectList;
public ListSelectEditorPart(string webPartID)
{
this.ID = "ListSelectEditorPart" + webPartID;
this.Title = "Select a list";
}
protected override void CreateChildControls()
{
base.CreateChildControls();
_listUrl = new TextBox();
_listUrl.Enabled = false;
Controls.Add(_listUrl);
_selectList = new Button();
_selectList.OnClientClick = "javascript:launchPicker();";
_selectList.Text = "...";
Controls.Add(_selectList);
}
Please note the ID that I set in the constructor. This blog post contains an explanation of why I do that. When you create an editorpart you will need to implement SyncChanges and ApplyChanges to read the value to your control and to store your values in the webpart:
public override void SyncChanges()
{
EnsureChildControls();
ListSelectWebPart webPart = WebPartToEdit as ListSelectWebPart;
if (webPart != null)
{
_listUrl.Text = webPart.ListUrl;
}
}
public override bool ApplyChanges()
{
EnsureChildControls();
ListSelectWebPart webPart = WebPartToEdit as ListSelectWebPart;
if (webPart != null)
{
webPart.ListUrl = _listUrl.Text;
}
return true;
}
In the OnLoad for the editorpart, we register the script that will show the popup. You also need to include the PickerTreeDialog.js file (which is localized, so in an en-US site, it is in the 1033 folder).
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
string webLocale = SPContext.Current.Web.Locale.LCID.ToString();
Page.ClientScript.RegisterClientScriptInclude("PickerTreeDialog", string.Format("/_layouts/{0}/PickerTreeDialog.js", webLocale));
RegisterSelectListScript();
}
private void RegisterSelectListScript()
{
StringBuilder launchPicker = new StringBuilder();
launchPicker.Append("<SCRIPT LANGUAGE='JavaScript' >");
launchPicker.Append("function launchPicker()\n");
launchPicker.Append("{\n");
launchPicker.Append(" var listurlfield = document.getElementById(\"" + _listUrl.ClientID + "\");\n");
launchPicker.AppendFormat(" var defaulturl = '{0}';\n", SPContext.Current.Web.ServerRelativeUrl);
launchPicker.Append(" var url = defaulturl;\n");
launchPicker.Append(" if(listurlfield != null && listurlfield.value != '')\n");
launchPicker.Append(" {\n");
launchPicker.Append(" url = listurlfield.value.substring(0,listurlfield.value.lastIndexOf('/'));");
launchPicker.Append(" }\n");
launchPicker.Append(" var callback=function(arr)\n");
launchPicker.Append(" {\n");
launchPicker.Append(" if(arr==null || arr==undefined)\n");
launchPicker.Append(" return;\n");
launchPicker.Append(" var site=arr[1];\n");
launchPicker.Append(" var list=arr[2];\n");
launchPicker.Append(" if(list != '')\n");
launchPicker.Append(" {\n");
launchPicker.Append(" listurlfield.value = site + (site == '/' ? '' : '/') + list;\n");
launchPicker.Append(" }\n");
launchPicker.Append(" " + Page.ClientScript.GetPostBackEventReference(_listUrl, string.Empty) + ";\n");
launchPicker.Append(" }\n");
launchPicker.Append("LaunchPickerTreeDialog(\'CbqPickerSelectListTitle','CbqPickerSelectListTitle','listsOnly',\"\",url,null,\"\",\"\",\"/_layouts/images/generic.png\", 0, callback );\n");
launchPicker.Append("}\n");
launchPicker.Append("</SCRIPT>");
if (!Page.ClientScript.IsClientScriptBlockRegistered("launchPicker"))
Page.ClientScript.RegisterClientScriptBlock(this.GetType(), "launchPicker", launchPicker.ToString());
}
After building and installing your webpart and opening the editorpart, your users will see this after they click the button:
You can find the code in this zip file. 6/15/2008In SharePoint you can limit the site templates the site templates that are available to users when they create a subsite. To do this you navigate to the page "Page layouts and site templates" from the site settings. For WSS there is no link to the UI to do this. You can limit the templates by manually entering the URL to the AreaTemplateSettings.aspx page. In code there are 2 ways to do it. You can make a template available for all languages that are installed on the SharePoint server, or you can make it available just for a specific lanugage. The examples below show you how to do this, both for a publishing site and for a teamsite. Example 1 This code snippet forces the users of our site to just use the "Team Site" template. The team site will be available in any language installed on the server. The code first finds the template using the GetWebTemplates of our SPSite object. To make it available for all languages, we use the method SetAvailableCrossLanguageWebTemplates. This example works for both publishing sites and team sites. Although team sites do not have the site settings link to this, you can limit the number of available site templates by using the same method as on a publishingweb. You will have to set one of the properties in the property bag of the SPWeb to make it work. using (SPSite site = new SPSite("http://moss/news"))
{
using (SPWeb web = site.OpenWeb())
{
// Find the Team Site template.
SPWebTemplate templateToAdd = null;
foreach (SPWebTemplate template in site.GetWebTemplates(web.Language))
{
if (string.Compare("STS#0", template.Name, true) == 0)
{
templateToAdd = template;
break;
}
}
if (templateToAdd != null)
{
// Create a new collection of available templates
Collection<SPWebTemplate> templates = new Collection<SPWebTemplate>();
templates.Add(templateToAdd);
// Update the web and save the changes
if (PublishingWeb.IsPublishingWeb(web))
{
PublishingWeb publishingWeb = PublishingWeb.GetPublishingWeb(web);
publishingWeb.SetAvailableCrossLanguageWebTemplates(templates, false);
}
else
{
web.SetAvailableCrossLanguageWebTemplates(templates);
web.AllProperties["__InheritWebTemplates"] = false.ToString();
web.Update();
}
}
}
}
After running this, the "Page layouts and site templates" page looks like this:
Example 2
The second example does the same thing, but users are now forced to use the Dutch Team Site template. Instead of using SetAvailableCrossLanguageWebTemplates, we now use SetAvailableWebTemplates:
using (SPSite site = new SPSite("http://moss/news"))
{
using (SPWeb web = site.OpenWeb())
{
// Find the Team Site template.
SPWebTemplate templateToAdd = null;
foreach (SPWebTemplate template in site.GetWebTemplates(1043))
{
if (string.Compare("STS#0", template.Name, true) == 0)
{
templateToAdd = template;
break;
}
}
if (templateToAdd != null)
{
// Create a new collection of available templates
Collection<SPWebTemplate> templates = new Collection<SPWebTemplate>();
templates.Add(templateToAdd);
// Update the web and save the changes
if (PublishingWeb.IsPublishingWeb(web))
{
PublishingWeb publishingWeb = PublishingWeb.GetPublishingWeb(web);
publishingWeb.SetAvailableWebTemplates(templates, 1043, false);
}
else
{
web.SetAvailableWebTemplates(templates, 1043);
web.AllProperties["__InheritWebTemplates"] = false.ToString();
web.Update();
}
}
}
}
After running this sample, the "Page layouts and site templates" page looks like this:

6/1/2008Two more days of SharePoint and then I will be on my way to do something completely different. This Thursday 5 colleagues and me will drive off to France to cycle tot the top of the Dutch Mountain, Alpe d'Huez. We do this for Stichting Alpe d'Huzes. Their mission is "To facilitate and inspire people to lead Happy and Healthy lives in Harmony with cancer." The second half of this page contains more information in English, the rest of the site is in Dutch. Thursday June 5th, 100 individual cyclists and 50 teams will at least climb the mountain 6 times on one day. The last few months I have been pretty busy preparing for that. During the day we will post the progress of Team e-office on this site: http://www.tonstegeman.com/alp The organization is posting their information during the day on this site: http://www.opgevenisgeenoptie.nl/ Both will be in Dutch. De name of the second site is the motto of the organization, "Never, ever quit!", which we will try to do on Thursday. Hope you willl check out our site and the official event site to see how everything progresses. On the event site they will probably post the final amount of money that all people have raised. The goal is to raise 3 million euro. If you want to add money to that, send me a message and we will work out how to get the money on their account. 5/20/2008
In one of my current projects, we have a publication process in which we read content from Lotus Notes. Users do some editting and then publish the items to MOSS. In a previous item, I showed how I read the content from Notes. In this post I will show you how we publish the items to MOSS. At first it sounds easy, but a big requirement we had was to do this without deploying server side code. To do this, we created a Windows application. This application posts pages in SharePoint using Frontpage RPC. The Windows application creates a publishing page of the specified page layout, and adds the content to that page.
A good place for a quick start on RPC is the RPC test page by the IW Kid. You can find it here. The referenced zip file WSS RPC.zip contains a test page with several examples of RPC calls. This should get you started pretty quickly. The sample code below is a simplified version of the code in the testpage. I stripped it just for demonstration purposes.
Another way to get a better understanding of how RPC works it to install Fiddler and use SharePoint Designer. In Fiddler you will find the calls and the responses.
The rest of this post decribes the most important parts of the code. You can find the full code in this zip file.
Step 1 - Create the user interface and the MOSSPage object.
The first thing I did was to create a user interface for the windows application. It is shown in the screenshot below. The user enters a name for the page, a title and the content for the page. The url of the site to which the page will be published is entered. The last stap is to edit a reference to a page layout. In SharePoint, the between a page and its page layout is a Hyperlink field on the Pages library. Therefore you need to supply the reference to the page layout as a hyperlink value, which looks like this:
http://moss/_catalogs/masterpage/ArticleLeft.aspx, Article page with image on left
It is composed of the full links to the aspx, followed by a "," separator and the name of the page layout.
When the user clicks the "Publish" button, a new object of type MOSSPage is created and the properties are set:
After that the Publish method is called:
MOSSPage page = new MOSSPage();
page.Name = textBoxName.Text;
page.PageLayout = textBoxPageLayout.Text;
page.Title = textBoxTitle.Text;
page.Content = textBoxContent.Text;
textBoxResult.Text = page.Publish(textBoxSite.Text);
Step 2 - Publication
The publish method of the MOSSPage object generates a dictionary of properties that will be added to the RPC request when it is submitted. The key of the property is the name of the property as it needs to be supplied to the RPC request.
Dictionary<string, string> properties = new Dictionary<string, string>();
properties.Add("vti_title", Title);
properties.Add("PublishingPageLayout", PageLayout);
properties.Add("PublishingPageContent", Content);
byte[] fileContents = System.Text.Encoding.ASCII.GetBytes(emptyASPX);
return UploadDocument(siteUrl, string.Format("{0}.aspx", Name), fileContents, properties);
After creating the dictionary it creates a byte array that contains the content of the page. A page in SharePoint is nothing more than an ASPX document in a document library, with custom metadata. Therefore you need to supply the contents of the ASPX file that the RPC request will submit. For a publishing page, this looks like this:
private const string emptyASPX = "<%@ Page Inherits=\"Microsoft.SharePoint.Publishing.TemplateRedirectionPage, Microsoft.SharePoint.Publishing,Version=12.0.0.0,Culture=neutral,PublicKeyToken=71e9bce111e9429c\" %> <%@ Reference VirtualPath=\"~TemplatePageUrl\" %> <%@ Reference VirtualPath=\"~masterurl/custom.master\" %>";
It calls the UploadDocument method, that creates and submits the RPC call.
Step 3 - Upload
UploadDocument first creates the RPC request. First it initializes and encodes a number of variables.
Uri uri = new Uri(url);
// Init variables.
string method = "put document:12.0.4518.1016";
string service = uri.AbsolutePath;
string document = "[document_name={0};meta_info=[{1}]]";
string documentName = string.Format("{0}/{1}", pagesLibrary, fileName);
document = String.Format(document, documentName, GetMetaInfo(metaInfo));
string rpcString = "method={0}&service_name={1}&document={2}&put_option=createdir";
// Encode variables.
method = HttpUtility.UrlEncode(method);
service = HttpUtility.UrlEncode(service);
document = HttpUtility.UrlEncode(document);
// Get the RPC call data
rpcString = String.Format(rpcString, method, service, document);
byte[] callData = GetRPCCall(rpcString, fileContents);
In this snippet, the variable metaInfo contains the dictionary of properties that was created in the previous step. The are added to the rpcString by the function "GetMetaInfo":
private static string GetMetaInfo(Dictionary<string, string> properties)
{
if (properties == null)
return string.Empty;
StringBuilder sb = new StringBuilder();
foreach (KeyValuePair<string, string> property in properties)
{
if (property.Value != null)
{
string data = EscapeVectorChars(property.Value.ToString());
sb.AppendFormat("{0};SW|{1};", property.Key, data);
}
}
return sb.ToString().TrimEnd(';');
}
This function escapes a number of characters and returns one big string that contains all property names and values. For one property, this string looks like this:
meta_info=[vti_title;SW|Testpage published by using Frontpage RPC]"
It contains the name of the property (please note, for this "Title" this is "vti_title" instead of "Title"). After the pipe symbol, it contains the value. In between the two you will find (in this case) the value "SW". The S is used for the datatype (always string in my simplified example). You can find the code to escape the characters in the ZIP file referenced before.
After setting up the rpcString, our UploadMethod function combines the contents of the ASPX file that will be submitted and the RPC call to a byte array:
private byte[] GetRPCCall(string rpcString, byte[] fileContents)
{
string callString = rpcString;
callString.Replace(".", "%2e");
callString.Replace("_", "%5f");
byte[] callBytes = System.Text.Encoding.UTF8.GetBytes(callString);
byte[] data = new byte[callBytes.Length + fileContents.Length + 1];
callBytes.CopyTo(data, 0);
data[callBytes.Length] = 0x0A;
fileContents.CopyTo(data, callBytes.Length + 1);
return data;
}
Now our call is ready to be submitted. We do this by using a WebClient object:
// Send the request.
byte[] result = null;
using (WebClient client = new WebClient())
{
client.Credentials = System.Net.CredentialCache.DefaultCredentials;
client.Headers.Add("Content-Type", "application/x-vermeer-urlencoded");
client.Headers.Add("X-Vermeer-Content-Type", "application/x-vermeer-urlencoded");
result = client.UploadData(url + "/_vti_bin/_vti_aut/author.dll", "POST", callData);
}
// Return the result.
return System.Text.Encoding.UTF8.GetString(result);
The result of the call returned to the user interface.
Step 4 - Test
Now that we have completed our publication process, we can test the application. After clicking the Publish button we will find the page and its content in the Pages library that we specified:
Below you will find an example of the output that is returned by our POST command. Just to give you an idea of what is looks like.
<html><head><title>vermeer RPC packet</title></head>
<body>
<p>method=put document:12.0.0.6219
<p>message=successfully put document 'Pages/Testpage2.aspx' as 'Pages/Testpage2.aspx'
<p>document=
<ul>
<li>document_name=Pages/Testpage2.aspx
<li>meta_info=
<ul>
<li>PublishingPageContent
<li>SW|content
<li>vti_rtag
<li>SW|rt:C45F9E3B-4F82-46D8-8757-1577D94E4A9D@00000000001
<li>vti_etag
<li>SW|"{C45F9E3B-4F82-46D8-8757-1577D94E4A9D},1"
<li>vti_filesize
<li>IR|279
<li>vti_parserversion
<li>SR|12.0.0.6219
<li>vti_modifiedby
<li>SR|TST_MOSS2007\administrator
<li>vti_timecreated
<li>TR|20 May 2008 19:55:14 -0000
<li>vti_title
<li>SW|Title for second test page
<li>vti_charset
<li>SR|windows-1252
<li>ContentTypeId
<li>SW|0x010100C568DB52D9D0A14D9B2FDCC96666E9F2007948130EC3DB064584E219954237AF3900EF933E554CA5E54FAC89BEC91C15C93D
<li>vti_sourcecontrolmultiuserchkoutby
<li>VR|TST_MOSS2007\\administrator
<li>vti_timelastmodified
<li>TR|20 May 2008 19:55:14 -0000
<li>PublishingPageLayout
<li>SW|http://moss/_catalogs/masterpage/ArticleLeft.aspx, Article page with image on left
<li>vti_author
<li>SR|TST_MOSS2007\administrator
<li>vti_sourcecontrolcheckedoutby
<li>SR|TST_MOSS2007\administrator
<li>vti_sourcecontroltimecheckedout
<li>TR|20 May 2008 19:55:14 -0000
<li>vti_sourcecontrolversion
<li>SR|V0.1
<li>vti_sourcecontrolcookie
<li>SR|fp_internal
<li>vti_linkinfo
<li>VX|UJUS|/_catalogs/masterpage/ArticleLeft.aspx UHUS|~masterurl/custom.master UHUS|~TemplatePageUrl
</ul>
</ul>
</body>
</html>
5/9/2008I finally finished configuring my new Lenovo T61p laptop. I have used it for a week as my production development machine. It has 4 Gb of memory and I am running Vista 64 bits. I expected some issues when moving to Vista 64, bit it all was very smooth. The only issues I have had until so far are the VPN clients that I use to connect to our corporate network and to the networks of our clients. I created a virtual machine running on Windows XP that has all the VPN software. Works really well. I am not doing any development directly on my host OS, so I don't have any experience with developing on the machine itself. All my development work is done in virtual machines. I just upgraded to version 6.0.3 of VMWare Workstation. I was always a fan of VMWare Workstation (I like cloning and teaming of the VMs), but the setup that I am running now really rocks! The virtual machines are performing very well. And loading / suspending them is much faster. I can now even run 2 SharePoint development machines at the same time, without my machine becoming extremely slow. The images of my VMs are all on a separate disk, that I have in a bay in the DVD slot. If you are looking for a performance boost of your VMs, that probably is the easiest thing to do. For me, moving to a new laptop with 2Gb extra memory and Vista 64 bit also worked really well. And what I lke about the Lenovo is the screen. It is the first laptop with a wide screen and I reallly like it.
|
|
|
|
|
|
|
|