Just to visit back on this post after 13 years and I am suprised this still exists! Hopefully all my friends and visitors to this page a great day ahead!👋👋
Wednesday, 26 May 2021
Thursday, 21 February 2008
ProfileBase.Save() doesn't save any changes after deployed on server
I have this method to save all the changes to current profile (because I am using Membership security by ASP.net 2 ). you first call
ProfileBase currentProfile = ProfileBase.Create(username);
in a separate class library and then we call ProfileBase.Save() to save the changes we made in the form. This will be all fine when the code is running on my local development server, but once I put it on staging/testing server, this won't save any changes I made. The reason is because Membership will treat this two version as two separate web applications regardless what you specified in provider application name of the web.config file. Users created in one application will still be able to log in in another application, but the profile data for that user won't be shown because it's not the same application. So I do it in webroot level instead of class library, I would be able to get hold of ProfileCommon class and the save method in that works fine on . But still this will just be a imperfect solution for the problem we have. I wonder if there's anything we can do to specify the application name in run time...
Posted by
Jay
at
13:15
0
comments
Labels: membership, profile
Wednesday, 13 February 2008
Update of the site
Because the nature of my job, this blog's title will be mainly about .net c# and asp.net from now on. Hopefully I can have updated content posted soon ! :)
Posted by
Jay
at
16:41
0
comments
Saturday, 15 December 2007
Christmas Greetings!
It's been a long time since I posted last time... The job is slowly taking over life and of course including blogging :) I will keep posting as soon as I got a chance from work. In the mean time, Hope everyone who sees this post a very happy christmas!
Posted by
Jay
at
22:58
0
comments
Wednesday, 26 September 2007
Infopath Development Tips 1 - Infopath repeating table manipulation
Infopath forms are the essential part of sharepoint workflow development. Repeating table is also very tempting because it can solve many UI problems when presenting group of data. So the problem is , how do we populate repeating table from the data we have. It's easy. AppendChild method of DOM using managed code and Infopath's powerful conditional formatting.
1. First thing first, let's specify the conditional formatting for repeating table. right click the table, then properties->conditional formatting-> add. Then you can specify when the value of element of table, say , text box is blank, tick "hide this control". This will make sure your table doesn't show extra blank row when inserting the rows. We will see why we might need to hide this row later on.
2. Secondly, we need to use a little code behind to populate the table we have in the IP form. Say the name of the table we have is called docList and the collection of data we have is docArray. so the code will be
//repeating table
relDocListTable = mainRoot.SelectSingleNode("/my:myFields/my:docList", this.NamespaceManager);
//repeating table row
relDocListRow = mainRoot.SelectSingleNode("/my:myFields/my:docList/my:docItem", this.NamespaceManager);
for (int index = 0; index < newrow =" relDocListRow.Clone();" innerxml =" docNameArr[index];">
Posted by
Jay
at
15:52
0
comments
Labels: InfoPath 2007, Workflow
Wednesday, 12 September 2007
Sharepoint Development Tips 6 - Developing Visual Studio Workflow for MOSS
pitfall 1:
there's a good article (well, for starters ) on msdn you guys can read to start your very first workflow using Visual Studio. But after firing up VS 2005 and ready to go with the instructions of this article, you found there's no onworkflowactivated1 as your necessary starter of the dish... so basically that's how the init data will kick in from MOSS and your workflow won't work without it. Even if you add those 3 missing references already and are able to see the chart , it will be the same.
Solution: Don't trying to be clever, install ECM kit on your development server with sharepoint on, copy around the dll files needed by your project is not good enough this time.
pitfall 2:
The snippet is essential to have when completing your workflow.xml and feature.xml in order to install your workflow and all related Infopath forms. Rather than google on the Internet, as we know, MS provides this snippet coming with the ECM kit. But when you press ^K+X, the WSS snippet described in MSDN is not there.
Solution:
Serge Luka's blog did save us a lot of time to tell us where this snippet is. Simply press ^K+B to navigate to the path of "C:\Program Files\Microsoft Visual Studio 8\Xml\1033\Snippets\Windows SharePoint Services Workflow" and add the snippet code into your code snippet manager. There should be another one for MOSS but the workflow.xml and feature.xml should be the same.
pitfall 3
SendEmail activity always seems tempting when developing workflows using Visual Studio because email can be a very important process. While a lot of people complaining the activity does not do what it is supposed to even though there's no error in event viewer of Windows or ULS log of MOSS. Can it be security issues or central admin's outgoing email settings, etc... confusing
Solution:
Wake up, the MailMessage utility in System.net still works as well as in .net 2. If you know the smtp server's address , this can be perfect to use in this scenario. Anyway, even if SendEmail Activity works, you still have to specify the to, from, and body so on to make it send emails you want. MailMessage object that can be sent by SmtpClient is easier to handle. Just drag a code activity instead and put your SmtpClient+MailMessage inside the handler of that code activity. It's guaranteed working if you get your smtp outbound server's address.
Posted by
Jay
at
14:24
0
comments
Labels: sharepoint tips, Workflow
Tuesday, 14 August 2007
Sharepoint Administration Tips 1 - Pitfalls in Moving Sharepoint DB
At some point we have to move database for sharepoint configuration either because the DB file / log is on system drive or the fast expanding of your sharpeoint DB log. If DB is full, you can still view the website hosted by sharepoint, but a lot of functions will stop working.
First thing first, do a full backup or differential backup if you have a full backup before. After that, to move the database, please read the relevant links in here;To move the database to another server, read here.
Note below:
After you have done the moving, you might run into problems like search settings changed , timer services stopped working correctly. For Search settings, we need to make sure the indexer is assigned to the SSP since moving the sharepoint will remove this. If you have enabled incoming email services for your sharepoint lists. It will stop working because the relationship between web app and sharepoint timer called Windows Sharepoint Incoming Email Timer is broken. To restore this, Go to Operation->Incoming Email Config->Select No on "
Enable sites on this server to receive e-mail? "->click ok and then come back to click "yes". After this, make sure to disable and re-enable the Application Server Administration Service Timer Job " and " Application Server Timer Job "this should fix the problem on incoming email.
Posted by
Jay
at
08:21
0
comments
Labels: sharepoint tips
Thursday, 19 July 2007
Sharepoint Development Tips 5- Customize MOSS TimerJob
No intention to steal the posts from other people here since Andrew Connell has posted a very nice and detailed article on his blog introducing how to create custom timer job on MOSS. He is kind enough to post his sample project as well.
Posted by
Jay
at
15:33
0
comments
Labels: sharepoint timer
Thursday, 5 July 2007
Sharepoint Development Tips 4- SPD workflow madness
As I introduced SPD workflow in my previous post,if all you need is simply customized activity and it's supposed to be stand-alone, i.e. doesn't need to be reused to another list in sharepoint, and you certainly don't want the hassle with init,complete forms with InfoPath, customized activity/condition with SPD's workflow designer is almost perfect choice. But when any offer looks too good to be true, it probably just is. Like the line in Supernatural, "This world just ain't coming without perks...", well, in the bad way in our case of course !-_-...
The catch is when you want to update your code using SPD, you will find the change you made will not be used by sharepoint. And as you come this far with it already, you know there's NO debugging support with SPD workflow. You really want to make sure it "somehow" just works anyway before you deploy it to the server. So how on earth we can make our updated dll work with the SPD workflow on the server? Well, you will have to live with the fact that you can't debug given the fact your dll will not contain huge amount of code. But to update the dll on the server, do the following.
1. Remove the workflow from your list in sharepoint
2. Shut down your SPD
3. Go to your website cache, on my XP pro the path for this is : "C:\Documents and Settings\%user%\Local Settings\Application Data\Microsoft\WebsiteCache", in vista , it's under different path:%System Drive%\Users\%user%AppData\Local\Microsoft\WebSiteCache(You will find it's a mistake to use Vista for professional development for now anyway). Delete everything you see there.
4. Copy the updated dll to the GAC on your server
5. IISRESET on your server
6. reopen SPD
7. Redeploy your workflow.
In this way, you will update your SPD workflow with latest dll. But I have run into the occasions that even above method will not update your dll. I ended up restarting the sharepoint server to get it updated(too much for a real-world software solution , is it?). Hopefully this will help you.
Update:
now every time I have to restart the sharepoint server to make changes. :(
Posted by
Jay
at
13:31
0
comments
Labels: sharepoint tips, Workflow
Sharepoint Development Tips 3- Dealing with SPUser- the slippery object
Most of the time you will want to obtain the SPUser object from your sharepoint list. One thing that will defintely make you wonder is how you cast the listitem column containing user info to the SPUser object. If you go
SPUser thisUser = (SPUser)thisItem["User_Column"];
VS will not let you compile because the underlying type of your "User_Column" is just a string, so the cast will fail. So what you can do is to search & compare the user collection in your web site, and get the SPUser from the compared result like following:
private SPUser GetOwnerFromString(string _userString, SPWeb thisWeb)
{
//parse the string after the delimiter '#'
int delimIndex = _userString.IndexOf("#");
string userStr = _userString.Substring(delimIndex + 1);
SPUser owner = null;
foreach (SPUser thisUser in thisWeb.Users)
{
//if this is current user
if (thisUser.Name.ToLower().IndexOf(userStr.ToLower()) > -1)
{
owner = thisUser;
break;
}
}
return owner;
}
As you might have noticed, the delimiter to read the string is set to "#", that's because when you try to get the string from a column containing the user information, the string format will be scrambled up like "10283744#System Account". So the format is a random number + "#" + your user's display name.
This method will work if you populate the user list of sharepoint by users rather than by AD groups. I didn't try the AD group case but will do later.
Posted by
Jay
at
11:24
0
comments
Labels: Sharepoint Technologies, sharepoint tips
Thursday, 21 June 2007
Sharepoint Development Tips 2-CAML Power!
CAML(Collaborative Application Markup Language) is the tool you want to use before you mess around with querying your sharepoint list and views with foreach(s). Experience prooved that foreach the whole list or in case you have to use embedded foreach statement, the performance is quite disappointing. The alternative is to use CAML which is used by sharepoint by default when creating views for lists.
The usage is easy. It's like the NHibernate's HQL if you use it before.
In here, I have a list called "Team List". Now I want to obtain the item that has the Job No.(job number)of 12 and Customer Name(customer's name )is equal to "Your Name", the CAML statement will be like following
the syntax is relatively easy apart from one wicked requirement, the ASCII characters are needed to be encoded to hexidecimal value and with both prefix and suffix of '_', in our case the space character is encoded as x0020, and the '.' as x002e.
So the usage of above CAML will be by putting the CAML statement into a string e.g. teamQueryString:
SPQuery query = new SPQuery();
SPListItem resultItem =null;
query.Query= teamQueryString;
//in our case the result is unique
resultItem = teamList.GetItems(query);
By this way , you get the team with job number is 12 and customer name is "Your Name"
If you wonder how you could figure out what hex value to encode all the characters out there or you are too lazy to write the CAML at all, I recommend using U2U's CAMLBuilder, they are pretty handy and most importantly, it's free! (You can tell that I am that lazy by sensing the tone :P )
Posted by
Jay
at
08:56
0
comments
Labels: sharepoint tips
Tuesday, 19 June 2007
Sharepoint Development Tips 1-Workflow status code
When you want to retrieve the value of your sharepoint inbuilt workflow and use it somewhere else, say Infopath client/browser forms, the result of getting the string value of them are not like what you see in the web page as "in progress","Rejected",etc, it's all integer status code representing different status. So this is what I found out until now.
In Progress: 2
Approved: 16
Rejected: 17
Canceled: 4
Posted by
Jay
at
07:27
0
comments
Labels: sharepoint tips
Friday, 8 June 2007
Customize Sharepoint Designer Workflow activity
Document management is the soul of all sharepoint project which is content management based or simple intranet application. Sharepoint Designer 2007 shipped along with MOSS is a powful tool which allows greater user involvment to customize the site and more importantly, build customized workflow without much coding effort. This shares the same thought with Expression Web that complies with .net 3.5 technologies which web designers can accomplish the tasks that had to be done by developers. This following piece of text will demonstrate how to use sharepoint designer's workflow designer to augment the existing actions provided by WSS 3.0.
As said above, sharepoint developers will run into implement document handling across different list through the lifecycle of document management whatever the project is like. Copy or move file items /list item from one to another according to different event (document change, document created etc.) is almost a must-have functionality. Workflow built in sharepoint designer can do this without a single line of code.
and if you want to move item, simply add another action "delete item in this list".
However, almost certainly you will want to have more actions or conditions when building the workflow in realworld. For example, above action will only copy the item to the list's root folder, what if you want to add it to certain folder or create one if not exist? This is where the customized workflow comes in, there are two alternatives: Customized Workflow created by Visual Studio and deployed as feature, this kind of workflow will not be restricted to certain list and therefore reusable;Workflow created by visual studio but deployed by using sharpeoint designer. This one will be tied up with certain list and not as reusable, yet easier to implement & deploy. This post will introduce how to create sharepoint designer workflow custom activity using based upon the copy item to folder action.
- First of all, you will need to install Visual Studio 2005 Extensions for WF, after that, you will have the work flow library template for you to start build your new project on. Give it a name, let's call it "DispatchActivity"
- On the design view of the workflow, drag and drop a code activity for your first and only activity.from your toolbar, give it a name of "CopyListItemAct"
- Double click the code activity to edit the code-behide , the CopyListItemAct_ExecuteCode event handler will be one the code to be executed in this step. You can find the event wire-up in InitializeComponent() as expected in designer.cs.
- Find the Microsoft.sharepoint.workflowactions.dll in ISAPI folder in your MOSS's c:\program files\common files\Microsoft shared\web server extension\12\ISAPI folder, and add it to your project as reference. The workflow context property will need it later
A SPD activity or a condition needs a textual descrition to represent the literal meaning of action, which is called a "sentence". In our case, the sentence will be "Copy this Item in one list to a folder in another list". The bold keywords will be the properties that are needed in our custom activity. So in order to "promote" these properties, we need to have properties called DependencyProperties which is necessary for promoting properties to SPD workflow. This is a requirement from windows workflow foundation, See MSDN for more information.
So by looking at the sentence that is needed in SPD, we need to have the context of sharepoint so we can get the list item and list objects from the site. Also, we need to have the reference of current listitem and the target list. So the dependency properties will look like following.
//current sharepoint context
public static DependencyProperty __ContextProperty =
DependencyProperty.Register("__Context", typeof(WorkflowContext), typeof(DispatchActivity));
[Description("context")]
[ValidationOption(ValidationOption.Required)]
[Browsable(true)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
public WorkflowContext __Context
{
get { return ((WorkflowContext)(base.GetValue(DispatchActivity.__ContextProperty))); }
set { base.SetValue(DispatchActivity.__ContextProperty, value); }
}
//list id
public static DependencyProperty ListIdProperty =
DependencyProperty.Register("ListId", typeof(string), typeof(DispatchActivity));
[Description("List Id")]
[ValidationOption(ValidationOption.Required)]
[Browsable(true)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
public string ListId
{
get { return ((string)(base.GetValue(DispatchActivity.ListIdProperty))); }
set { base.SetValue(DispatchActivity.ListIdProperty, value); }
}
//list item
public static DependencyProperty ListItemProperty =
DependencyProperty.Register("ListItem", typeof(int), typeof(DispatchActivity));
[Description("List Item")]
[ValidationOption(ValidationOption.Required)]
[Browsable(true)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
public int ListItem
{
get { return ((int)(base.GetValue(DispatchActivity.ListItemProperty))); }
set { base.SetValue(DispatchActivity.ListItemProperty, value); }
}
//TolistId item
public static DependencyProperty ToListIdProperty =
DependencyProperty.Register("ToListId", typeof(string), typeof(DispatchActivity));
[Description("Target List Name")]
[ValidationOption(ValidationOption.Required)]
[Browsable(true)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
public string ToListId
{
get { return ((string)(base.GetValue(DispatchActivity.ToListIdProperty))); }
set { base.SetValue(DispatchActivity.ToListIdProperty, value); }
}- Before we start coding on CopyListItemAct_ExecuteCode to copy item over to the folder in the target list, we need to create a .ACTION file to map all the dependency properties in the class so Sharepoint can recogize the custom sentence and present the sentence to the user to fill it in . The xml file should look like as following.
What is worth mentioning is that you should always keep in mind putting the __context property in front of other properties, either in ACTIONS file or in your class. I will explain this later. Also, in the first fieldBind, we notice that we should use the format string of "ListId,ListItem" to get the current item in the current list. The result in SPD will be a dropdownbox prompting to confirm/select other list items to copy from.
Now we code the CopyListItemAct_ExecuteCode() in your activity .cs file for folder create and copying activity.
private void CopyListItemAct_ExecuteCode(object sender, EventArgs e)
{
try
{
/*setup context and get reference
of current list SPD is attached to
and the item we are dealing with*/
SPSite thisSite = __Context.Site;
SPWeb thisWeb = __Context.Web;
SPList thisList = thisWeb.Lists[new Guid(this.ListId)];
SPListItem thisItem = thisList.GetItemById(this.ListItem);
//get the target list
SPList targetList = thisWeb.Lists[new Guid(this.ToListId)];
/*e.g. we can use the name of curent item to create folder
in target list*/
if(targetList !=null)
{
string folderName = thisItem.Text;
//check if the folder exist in target list
if (!ExistingOrder(folderName, targetList))
{
//create a folder with the folderName in the target list
string listURL = thisWeb.Url+"/" + targetList.Title;
//the folder in Sharepoint can only be created this way
//by specifying the leafname as the folder name
SPListItem newFolder = targetList.Items.Add(listURL.Trim(), SPFileSystemObjectType.Folder, folderName);
if (newFolder != null)
{
//reflect the change
newFolder.Update();
targetList.Update();
//copy item over to the folder we just created
foreach (SPListItem targetItem in targetList.Folders)
{
if (targetItem.Folder.Name == orderNumber)
{
AddFileToFolder(thisItem.File, targetItem.Folder);
}
}
}
}
else // if the folder is there already, copy item
{
//copy item over to the folder we just created
foreach (SPListItem targetItem in targetList.Folders)
{
if (targetItem.Folder.Name == orderNumber)
{
AddFileToFolder(thisItem.File, targetItem.Folder);
}
}
}
}
}
catch(Exception)
{...}
}
Then we code the AddFileToFolder method, which is only couple of lines of code, breezy...
private void AddFileToFolder(SPFile _thisFile, SPFolder _thisFolder)
{
string fileURL = _thisFolder.Url + "/" + _thisFile.Name;
try
{
byte[] fileByte = _thisFile.OpenBinary();
_thisFolder.Files.Add(fileURL, fileByte);
}
catch (IOException ioe)
{
throw new IOException(ioe.Message);
}
}
As for how to check if the folder exists, the code is very straightforward, it will not to be posted here to save space.
After signing your assembly, we need to make sure it is deployed correctly.
1. After you make the ACTION file, make sure you put it either in a independent xml file with extension of .ACTIONS under the server's folder %SYS_VOL%:\Program Files\Common Files\Microsoft Shared\web server extensions\12\TEMPLATE\1033\Workflow, or simply just copy the part ofelements to the WSS.ACTIONS file under the same folder as above. The reason why I said that the __context should go first is that sharepoint will automatically change your properties order in ACTIONS file, while making sure it's the same order as the one in your activity class file, the __context always comes the first. (This is true when you put your custom activity in WSS.ACTIONS, never tried to prove if it's true in separated file. )
2. In your web.config file, you need to specify your assembly is an authorized control as others in Sharepoint, you can achieve this by adding in following line
in,
You will need to make sure above is 100% correct against your assembly name and pubilc key, otherwise, you might find out that the action is not showing up in workflow designer when you try to add it to the SPD action in steps.
Last thing to do is to deploy the dll you made to the GAC, and do an IISRESET so the sharepoint will pick up your new workflow activity.
These properties will simply be used as normal c# properties.
Now if you open up Sharepoint designer, when you try to add in your action, you can click "more actions"->choose "Custom SPD Activities" in the category dropdown , you should be able to see your own custom activity if everything in this article is followed. Good Luck!
Posted by
Jay
at
17:04
4
comments
Labels: SPD custom activity, Workflow
Monday, 4 June 2007
Impersonation with Sharepoint Portal Server
Still another one for sharepoint security. In order to let our code to run, we have to know CAS(Code Access Security) , (see another one in my blog about CAS) to configure the permission of assemblies we made properly so sharepoint will know which assembly should access which part and how much of the system resources. Another level of this will be sharepoint in-build access control which is enforced by the sharepoint group and users. Take webpart for instance, if we put a webpart on the sites under the portal (assuming you are using SPS) and just access the content(list, document lib etc) but not in any subsites which doesn't use the same permission as this site, you will be fine without any impersonation. However, if you do want to use cross-site data to complete the logic in your webpart, say, you want to put your webpart in your portal front page and show the subsites data which you might not have permission. The purpose of this might be an overview or statistics for site information.You will have to come across the override of permission issues so you will get your code running without being stopped by security guard :) .
If you are using MOSS, you are lucky enough to be able to take advantage of in-build API method: RunWithElevatedPrivilege like this:
//using directives
using Microsoft.Sharepoint;
using Microsoft.Sharepoint.WebControls;
SPSecurity.CodeToRunElevated code = new SPSecurity.CodeToRunElevated(method_need_elevation);
SPSecurity.RunWithElevatedPrivilege(code);
public void method_need_elevation()
{...}
By using that, you can easily obtain the application pool identity.
In SPS, you have to hijack the system dlls in order to get hold of the windows identity which will be pointing to app pool. First of all, you need to make a class to contain this logic. Please note that we need to implement IDisposable interface so when the code exits the method, the impersonation will be restored to previous state. Also because we are using DLLImport, the system resource will not be garbage collected if we don't explicitly manually recycle the used objects. This will be mentioned later.
Namespaces needed in this article
we need to create the Windows Identity to represent the application pool account. WindowsIdentity object can be found within System.Security.Principal namespace, besides, as we need the entry point to the system dlls, System.Runtime.InteropServices namespace is needed for using DllImport attributed methods. At last we add in System.ComponentModel namespace to support win32Exceptions and application exceptions.
Code Explanation
As we discussed above, add in windows identity account variable _appPoolIdentity, and also the identitycontext objects for both current user's and app pool's . The reason of promoting these as member variables are later on we need to GC the unmanaged objects using these contexts as the reference.
using System;
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Security.Principal;
public sealed class Identity: IDisposable
{
private static WindowsIdentity _appPoolIdentity;
private WindowsImpersonationContext _WindowsImpContext,_AppPoolImpSelfContext;
}
Then we need to import the dll and use the attributed methods DuplicateToken to copy the token of app pool to impersonate app pool's account.
//duplicate a token for code to impersonate
[DllImport("advapi32.dll", CharSet=CharSet.Auto, SetLastError=true)]
private static extern bool DuplicateToken(IntPtr hToken_, int impersonationLevel_, ref IntPtr hNewToken_);
and of course, we need to manually release the resource used by token copying.
//free unmanaged allocated resources
[DllImport("kernel32.dll", CharSet=CharSet.Auto)]
private static extern bool CloseHandle(IntPtr handle);
now we are ready to make the application pool account property
private static WindowsIdentity AppPoolIdentity
{
get
{
lock(typeof(Identity))
{
if(_appPoolIdentity==null)
{
//get token for this identity
IntPtr token = WindowsIdentity.GetCurrent().Token;
if(token == IntPtr.Zero)
{
throw new ApplicationException("Cannot fetch the token of this application pool");
}
//duplicate token in order to impersonate
if(! DuplicateToken(token, 2, ref token))
{
throw new Win32Exception(Marshal.GetLastWin32Error(), "Unable to duplicate AppPool's identity token !");
}
//if didn't get the token, throw exception
if(token == IntPtr.Zero)
{
throw new ApplicationException("Unable to duplicate AppPool's identity token !");
}
// Store app pool's identity with the retrieved token
_appPoolIdentity = new WindowsIdentity(token);
//free the unmanaged resources
CloseHandle(token);
}
return _appPoolIdentity;
}
}
}
Then we make a call to execute impersonation using this apppool account identity. We can make a public constructor and write impersonation logic. Or We can make a static method to call a private constructor, which is much more elegant when using impersonation. Here it was using the second method.
//private constructor which will be called by the static impersonateAdmin method to impersonate
private Identity()
{
try
{
_AppPoolImpSelfContext= WindowsIdentity.Impersonate(IntPtr.Zero);
_WindowsImpContext = AppPoolIdentity.Impersonate();
}
catch
{
UndoImpersonate();
throw;
}
}
Note above that in case of exception occurs, the impersonate should be undo and therefore revert to previous user account identiy. This will be called in the manual GC process by simply recycle all the windowsidentitycontext we got from the return of impersonation. Notice not only the context needs to be set to null, but also the Undo() method of windowsidentycontext needs to be called to revert back to previous account.
private void UndoImpersonate()
{
//clear the context
if(_WindowsImpContext!=null)
{
_WindowsImpContext.Undo();
_WindowsImpContext = null;
}
if(_AppPoolImpSelfContext!=null)
{
_AppPoolImpSelfContext.Undo();
_AppPoolImpSelfContext = null;
}
}
We are almost there to finish the identity class, second to last thing is to implement IDisposable methods to carry out GC
public void Dispose()
{
CustDispose(true);
//self-controled Gabage collection
GC.SuppressFinalize(this);
}
public void CustDispose(bool _disposing)
{
if(_disposing)
{
this.UndoImpersonate();
}
}
and finally we write a public static method to execute the whole impersonation process, which is easy, we simply create a object of Identity class which calls the constructor.
public static Identity impersonateAdmin()
{
return new Identity();
}
Using Impersonation class we just made!
By using "Using" keyword, we surround the code that needs the permission of app pool account in the namespace we created using the class above and GC occurs after exits the namespace , the impersonation is done with breeze.
using (Identity impersonation = Identity.impersonateAdmin())
{
//code to run under impersonation context
}
Posted by
Jay
at
12:26
0
comments
Labels: DllImport Attributed Methods, Impersonation, Sharepoint Technologies
Monday, 14 May 2007
developing an embedded control in SPS 2003 webpart
over the last week I tried to develop a data control like a datalist or repeater that will have a child control which shares the same functionality as its parent i.e. another datalist or repeater. The goal of this is when the user clicks on the item on the parent level, the child level repeater will show up.
As we all know that programmers developing web part for sharepoint won't be able to enjoy the laziness of drag & drop as in ASP.net design view unless you use smartpart. (However I've seen someone on the net has said something that we shouldn't use smartpart for commercial app development, therefore, we stick to the grafting handcode :D ) We need the customized template class of repeater which derives from ITemplate interface. And easily you can implement this by minimum requirement of InistantiateIn which you create all your child controls and add them to the repeater control tree referred as container in the following part.
So the class might look like
using Sytem.Collections;
using System.Web.UI.WebControls;
namespace whatever...
{
public class MyTemplate: ITemplate
{
private string columnName;
private Repeater childRepeater;
public MyTemplate(string _columnName)
{
//constructor logic here
columnName = _columnName;
}
public void Instantiate(Control container)
{
LinkButton btnName = new LinkButton();
//style up your links in here if you like
//the embedded or possible recursive magic goes here
// in the databinding event.
btnName.DataBinding+= new EventHandler(btnName_DataBinding);
container.Controls.Add(btnName);
}
}
Then you start coding your event handler by using the object which initiates the event, i.e. your linkbutton and add in your command arg which you get the id or whatever you specify to know which button actually has been clicked.
public void btnName_DataBinding(object sender, EventArgs e)
{
LinkButton thisButton = (LinkButton)sender;
RepeaterItem container = (RepeaterItem)thisButton.NamingContainer;
//get the clicked data from datasource
string strVariable = Convert.ToString(DataBinder.Eval(((RepeaterItem)container).DataItem,columnName));
thisButton.CommandArgument = strVariable ;
thisButton.Text = strVariable;
//then you bind the child repeater that will call back the //recursive method
BindChildRepeater(container, strVariable);
}
Then you need to decide one thing. Do you or can you make your template recusive. Because sometimes your child repeater is simply different from the parent repeater in terms of control structure.like it got buttons , image or hyperlinks in it, if you use recursive method, it doesn't really make any sense because they are not the same thing... But if you are sure it's the same control type, as in this example, the child repeater will be having linkbuttons as well, just do it :)
///summary: then in the bindchildrepeater method, you bind embedded repeater
private void BindChildRepeater(RepeaterItem container, string colName)
{
//say you get a customized list somewhere from sharepoint data
MyList mySPList = new MyList();
//populate your list
if(mySPList !=null)
{
childRepeater = new Repeater();
//recursive data bind method called
childRepeater.ItemTemplate = new MyTemplate ("FirstName");
// or you just can't be bothered and use something //else, it's equally cool like following
// childRepeater.ItemTemplate = new ChildTemplate("FirstName","SecondName","Contact");
childRepeater.ItemCommand+= new RepeaterEventHandler(childRepeater_ItemCommand);
childRepeater.DataSource = mySPList;
childRepeater.ID = "childRepeater"+colName;
childRepeater.DataBind();
childRepeater.Visible=false;
// add this repeater to it's parent
container.Parent.Controls.Add(childRepeater);
}
}
There , you have a repeater inside a repeater(or whatever levels you have in the control tree, it will repeat until it reaches the leaf if recursive). So as you have seen, I set up an ID for this child repeater(s) so I will be able to find it through e.g. FindControl("childRepeaterJohhson"). Then when I click the control, I simply sert the control's visibility to true in order to show it after the postback.
Above works no problem at all, but there's one problem (big one)when I bind my child repeater this way, the ItemCommand event when you click the child repeater seems not bubbling up to the parent repeater , therefore, when you click the child repeater item i.e. a linkbutton, the ItemCommand Event won't fire in the web part. If anyone care to tell me where I did wrong and correct me, it will be highly appreciated. Thanks!
Posted by
Jay
at
16:03
0
comments
Labels: c#, Customize Webparts, Sharepoint Technologies
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
Posted by
Jay
at
15:52
0
comments
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.
- 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. - 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
- 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.
Posted by
Jay
at
12:08
0
comments
Labels: Sharepoint Technologies
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
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;
}
}
Posted by
Jay
at
12:19
2
comments
Labels: VSTO/VSTA
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.
Posted by
Jay
at
12:07
0
comments
Labels: Sharepoint Technologies
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
...
}
Posted by
Jay
at
07:31
0
comments
Labels: InfoPath 2007

