Sunday, February 28, 2010

Read Rich Text Fields inside IP from workflow code

If your form contains a rich text field and you need to use this data inside your workflow code , may be you will use property promotion to publish your data source to the form library But you will notice that rich text fields published as plain text and you will find all of your formatting lost.

The solution is to read the inner html of the eich text so as you can will not loose the formatting.

Follow these steps
1- inside your form add your rich text control
2- then add another field that will hold the source html inside the rich text.
3- add a sumbit event handler then add the following code.

public void FormEvents_Loading(object sender, LoadingEventArgs e)
{
FileSubmitConnection dc =
(FileSubmitConnection)this.DataConnections["Submit"];

XPathNavigator root = MainDataSource.CreateNavigator();

string notes = root.SelectSingleNode("/my:myFields/my:MemoContents",
NamespaceManager).InnerXml;

root.SelectSingleNode("/my:myFields/my:MemoContentSource",
NamespaceManager).SetValue(notes);

dc.Execute();
e.CancelableArgs.Cancel = false;
}


the Idead behind reading the InnerXml from rich text field not Value which will read the plain text of the field

4- publish the field that contains the html source of the control.
5- then you can read this field from your workflow and format it as you like.



Thanks

Thursday, January 14, 2010

Using ASPX as a workflow task form

First of all thanks to Robert Shelton who wrote a very good workshop on how to use aspx forms for all Assocciation,Initaion, and Task Edit form .
The Issue I were facing is how to pass data from the workfolow to the task form and then how to read them.
First there is no problem passing the data from the workflow by using the ExtendeProperties
MyTask.TaskProperties.ExtendedProperties["Comments"] = "Hellow";
but the problem is how to read them from the aspx form.
First I decided to the ExtendedProperties for the task as will by calling this method
HashTable extendedProp = SPWorkflowTask.GetExtendedPropertiesAsHashTable(myTaskAsListIetm);
but the returned collection is always empty, after some searches I found that when I create a new content type for the Task List the workflow will not automatically create an ExtendedProperties.
Then I tried to access the data as ordiniary properties Like this
label1.Text = myTaskAsListItem["Comments"].ToString();
and this worked fine for me.

Happy Coding.
Thanks

Tuesday, December 22, 2009

The file has been modified by SHAREPOINT\system

Sometimes your project architecture includes multiple solutions like using Event Handlers , Timer Jobs , workflows ....
You need all these componets to update a certain ListItem so imagine the following case :

Inside your workflow you get a reference to your working list item using
SPListItem cItem = WorkflowProperties.List.GetItemByID(WorkflowProperties.ItemID);

Then you Made some modifications
cItem["My Field1"]="PlaPla";
cItem["My Field2"]="PlaPla";
cItem["My Field3"]="PlaPla";
.
.
.
then Another code in the Task

then you called
cItem.Update();

and this problem has been occured
The file has been modified by SHAREPOINT\system

Cause
After you get a reference to the SPListItem and before you call the Update() method another component modified the List Item so SharePoint tells you that you have an old version of the file and there may be a conflict exists.

Solution
Put your updates inside a while loop and catch this exception if it occured try making your updates again but getting the current version of the ListItem

Code Sample

SPListItem req = CurrentRequest;
bool tryAgain = false;
do
{
try
{
req["field1"] = "value1";
req["field2"] = "value2";
req.Update();
tryAgain = false;
}
catch (Exception ex)
{
WriteToLog(ex.StackTrace, ex.Message);
if (ex.Message.Contains("has been modified by SHAREPOINT"))
{
req = CurrentRequest;
System.Threading.Thread.Sleep(1000 * 5);
tryAgain = true;
}
}
} while (tryAgain == true);


While CurrentRequest is a private proberty to get a new ListItem at every call.

private SPListItem CurrentRequest
{
get
{
SPList list = _wfp.Web.Lists[_wfp.ListId];
SPListItem req = list.GetItemById(_wfp.ItemId);
return req;
}
}



Thanks

Tuesday, July 21, 2009

Sending document in a document library as a mail attachment

SharePoint dcument library contains "Email as a link" item in the individual items context menu that -from its name-enables you to send the link of the document using the outlook express email,



One day i asked to make a feature that can send the document itself as an attachment inside email with.

I thougt that i can make a new context menu item in the doucment library that redirect you to a page in which you can wirte your email and send the mail via smtp containing the original docuemt as an attachment,

The main issue in this idea is how to know the source docuemt in the mail page, with some crawling behind the query string of similar functionality i found that sharepoint pages uses some javascript variables that read the id of the source and the source url (to go back after sending mail.) etc..,

Now do the following steps to do so:




  1. Click Site Actions Menu the Edit Page


  2. Add Web Part choose "Content Editor Web Part".


  3. Open properties pane in the source editor cope and paste the following code inside script tag:


    function Custom_AddDocLibMenuItems(m, ctx)
    {
    var strDisplayText = ""; var strAction = ""; var strImagePath = "";
    strDisplayText = "Send Document To People";
    strAction = "window.location= '" + ctx.HttpRoot + "/_layouts/SndItmPpl.aspx?ID="+currentItemID+"&Source="+ GetSource()+"&list="+ctx.listName+"'";
    // Add our new menu item
    CAMOpt(m, strDisplayText, strAction, strImagePath);
    // add a separator to the menu
    CAMSep(m);
    // false means that the standard menu items should also be rendered
    return false;
    }
  4. From Layout property check "Hidden".
  5. the appove code add "Send Document To People" menu item to the docuemt library which open the page SndItmPpl.aspx passing source url, Item Id, parent list name to it in the query string.


  6. now copy and paste the attached SndItmPpl.aspx to your "Template" folder in the following path in your server "\Program Files\Common Files\Microsoft Shared\web server extensions\12\TEMPLATE\LAYOUTS".
  7. You can find the aspx file Here

  8. This page like the "Gmail compose mail" page but it will contain the original document by defualt.


thanks

Submitting The InfoPath From to the Current From Library Folder

Have you ever faced the case that you want to organize your form library items to folders , you may be using a submit data connection to submit your form to the library including the meta data and also to benefit from the overwrite check in the submit data connection to enable or disable overwriting forms with the same name ?. if this the

case you will face to problems.


  1. Submit using data connection sumbits only to the root folder

  2. Overwrite check can only check for dublicated forms in the current directory (so you can't guarantee uniqunuess in the form library entierly)

Solution


The solution for the first problem is to handle the submit event and update the url for the Main Submit data connection with the current working directory.


The Solution for second problem is to search all the folders inside the the form library for the field you want to be unique which will be shown in the Title Field, if it exists before cancel submittion.


The Code


1- Add New Submit Data Connecntion to your form library
2- From Submit Options Choose to run custom code when sumbiting the form.
3- This will open the code behind project for this form template, Add the following variables to the FormState dictionary




private string RootFolder
{
get { return FormState["RootFolder"].ToString(); }
set { FormState["RootFolder"] = value; }
}
private Guid SiteID
{
get{return (Guid)FormState["SiteID"];}
set{FormState["SiteID"] = value;}
}
private Guid WebID
{
get{return (Guid)FormState["WebID"];}
set{FormState["WebID"] = value;}
}

private Boolean ProjectExist
{
get{return (Boolean)FormState["ProjectExist"];}
set{FormState["ProjectExist"] = value;}
}


4- You need to handle the load event to read the current folder.
Note that you should handle the two cases if the form is opend in the browser or usnig the infopath client




void FormEvents_Loading(object sender, LoadingEventArgs e)
{
XPathNavigator xnDoc = this.MainDataSource.CreateNavigator();
if (this.New) // New Request
{
char[] splitter = new char[] { '/' };
// Read The Root folder from the url if form shown in browser
if (Application.Environment.IsBrowser)
{
string[] all = e.InputParameters["SaveLocation"].Trim(splitter).Split(splitter);
RootFolder = all[all.Length - 1];
}
else // if the form shown in the infopath read the folder from the uri
{
string strUri = this.Template.Uri.ToString();
string strPath = strUri.Substring(0, strUri.IndexOf("Forms") - 1);
RootFolder = strPath.Substring(strPath.LastIndexOf("/") + 1);
}
XPathNavigator isNew =
xnDoc.SelectSingleNode("/my:myFields/my:New", this.NamespaceManager);
isNew.SetValue("1");
XPathNavigator root = xnDoc.SelectSingleNode("/my:myFields/my:root", this.NamespaceManager);
root.SetValue(RootFolder);
}
else // Modification To Existing Item
{
XPathNavigator isNew =
xnDoc.SelectSingleNode("/my:myFields/my:New", this.NamespaceManager);
isNew.SetValue("0");
}
}


5- Before sumbitting the form you will need the following:


  • Get the current folder.

  • Loop inside all the fodler in our form library to search for the choosen field to be unique if it is existing before.
    Note that you should run the search method with elavated privilages to get the results regardless of the current logged user permissions.

  • Concatenate the current folder to the Main Submit connention folder.

  • Then Submit your form.




public void FormEvents_Submit(object sender, SubmitEventArgs e)
{
SPWeb contextWeb = SPContext.Current.Web;
SiteID = contextWeb.Site.ID;
WebID = contextWeb.ID;
// Invoke the CheckAvailablity method with elavated privilage to
// get the search results regardless the privilages of the logged user
// becuse we search subfolders that may include special permissions
SPSecurity.CodeToRunElevated codeToRunElevated = new SPSecurity.CodeToRunElevated(CheckAvailablity);
SPSecurity.RunWithElevatedPrivileges(codeToRunElevated);
try
{
FileSubmitConnection dc =
(FileSubmitConnection)this.DataConnections["SharePoint Library Submit"];
if (ProjectExist)
{
throw new Exception("The Project Name Exists before, It must be unique.");
}
if (dc != null)
{
string listName = "listname";
if (RootFolder != listName)
{
dc.FolderUrl = dc.FolderUrl + "/" + RootFolder;
}
else
{
dc.FolderUrl = dc.FolderUrl;
}
dc.Execute();
e.CancelableArgs.Cancel = false;
}
}
catch (Exception ex)
{
e.CancelableArgs.Message = @"There was a problem submitting the
form: " + ex.Message;
e.CancelableArgs.Cancel = true;
}
}




///


/// This Method Used to search for the filed that should be unique inside all the folders
/// for the form library because of the limitation for the (built in overwrite check)
/// that can gurantee uniquiness only in the current folder
///

void CheckAvailablity()
{
// Read The Current List by opening the web explicitly
// to get rid of "Operation is not valid due to the current state of the object"
// if we used SPContext.Current.Web directly
SPSite site = new SPSite(SiteID);
SPWeb web = site.OpenWeb(WebID);
SPList list = web.Lists["listname"];
XPathNavigator xnDoc = this.MainDataSource.CreateNavigator();
XPathNavigator pName =
xnDoc.SelectSingleNode("/my:myFields/my:project_name", this.NamespaceManager);
string projectName = pName.Value;
SPQuery q = new SPQuery();
q.Query = @"" + projectName + "";
// Search in the root folder first
bool found = false;
SPListItemCollection outerColl = list.GetItems(q);
if (outerColl.Count > 0)
{
found = true;
}
else
{
// then search inside subfolders
foreach (SPListItem flder in list.Folders)
{
q.Folder = flder.Folder;
SPListItemCollection coll = list.GetItems(q);
if (coll.Count > 0)
{
found = true;
break;
}
}
}
ProjectExist = found;
}



I hope this helps, and Appreciate any comments

Monday, July 20, 2009

Pre-build / Post-build Events

In many cases your solution contains many projects that its output in the form of dll, after building your solution you may do some routine work by copying your dlls into a separate fodler without the need to change the defult bin\debug folder.

The Solution is to use Post-build events to copy your output dll into your seprate folder.

Steps:

  1. Open your project properties page.
  2. Select Build Events tab.
  3. Write yuor command in the Post-build Events dialouge.
  4. Click Edit-Post build to use Macros.
  5. Use the sample command copy "$(TargetPath)" "$(SolutionDir)\Dlls\$(TargetFileName)"





Visual Studio 2008 macros are essentially environment variables contained within parentheses, preceded by the $ symbol—much like Perl.


The Following is a table of all available macros:





I hope this hepls
Thanks