Thursday, November 26, 2009

Read Page.IsValid from javascript (alternate way)

This was my scenario:
In a form there are few fields and 2 buttons :"Save" and "Cancel"
If user presses save button, and if the form validation is true, then immediately disable the "Cancel" button, so that the user cannot press the "Cancel" button. Here is the form:















"Name" field is a required field. So there is a "RequiredFieldValidator" attached with it. I have to disable the cancel button from javascript if:
1. User presses "Save" button.
2. Name field has some value (page validation passed).

To do this, I had to add a customvalidator with the name field, and do the javascript coding inside its client side function. Here is the code for the field and the validators:

<td>
<asp:TextBox ID="uxName" runat="server" MaxLength="50" width="180px"></asp:TextBox>

<asp:RequiredFieldValidator ID="RequiredFieldValidator1" runat="server" ControlToValidate="uxName" SetFocusOnError="true"
ErrorMessage="Department name cannot be empty" ValidationGroup="DepartmentEditValidation">*</asp:RequiredFieldValidator>

<asp:CustomValidator runat="server" ID="uxCustomValidator" ClientValidationFunction="checkNames" ControlToValidate="uxName" ValidationGroup="DepartmentEditValidation" ErrorMessage="" SetFocusOnError="True" Display="Dynamic">
</asp:CustomValidator>

</td>

Here is the code for Save, and Cancel button. Validation Group has to be same for the Save button.

<div class="editPanelButton">
<asp:Button ID="uxSave" runat="server" Text="<%$Resources:Texts,Save %>" ValidationGroup="DepartmentEditValidation"
OnClick="uxSave_Click" />
<asp:Button ID="uxCancel" runat="server" Text="<%$Resources:Texts,Cancel %>" OnClick="uxCancel_Click" />
<</div>

Now we need to do the javascript checking inside the clientside function of the customfield validator. We declared: ClientValidationFunction="checkNames", so here is the function:

<script type="text/javascript" >
function checkNames(source, args){

if(args.IsValid){

if(event.srcElement.id==uxSaveButtonClientId){
document.getElementById(uxCancelButtonClientId).disabled=true;
}
args.isValid=true;
}
}

</script>

args.IsValid is the alternate way of checking whether the page validation is true or not. Then I checked the validation was fired by pressing the "save" button. validation also fires when focus goes out from the "uxname" text field. So, I needed to be specific. If the conditions pass the I disabled the cancel button. This solved my problem.

Tuesday, August 25, 2009

Disable asynchronous postbacks while one asynchronous postback is active.

Asynchronous postbacks are partial postbacks occured by Ajax (or any other 3rd party) update panels. Ajax has 5 stages to handle the life cycle of an asynchronous postback. They are:

1. initializeRequest
2. beginRequest
3. pageLoading
4. pageLoaded
5. endRequest

For our work, we need to handle the initializeRequest only. First make sure that your page contains ScriptManager, UpdatePanel, and UpdateProgress controls.

Now, if you want to keep the existing async postback alive, and kill all new async postbacks that are occuring, then add this script:

<script type="text/javascript">
var prm = Sys.WebForms.PageRequestManager.getInstance();
prm.add_initializeRequest(InitializeRequest);

function
InitializeRequest(sender, args) {
if (prm.get_isInAsyncPostBack()) {
args.set_cancel(true);
}
}
</script>

if you want to kill the existing async postback and start the new one, then replace the above function with this one:

function InitializeRequest(sender, args) {
if(prm.get_isInAsyncPostBack(){
prm.abortPostBack();
}
}



Monday, July 27, 2009

Read from DB while clientside validation using asp:CustomValidator (Bypass)

Ok, by saying bypass, I mean I didnt read data from SQL server table while the client side validation. I read it earlier, stored data in a hidden listbox, and used customvalidator to perform the client side validation . Here's how:

1. Declare a hidden list box that can be readable by javascript:

<asp:ListBox ID="uxHiddenListBoxForPermissionName" runat="server" Style="display:none">
</asp:ListBox>

Style="display:none" keeps the item hidden as well as readable by javascript. For my work I needed to add another hidden item, just to make sure that it is an update or add operation:

<asp:TextBox ID="uxTextBoxLog" runat="server" Style="display:none">
</asp:TextBox>

2. Populate data into the listbox from the code behind file:

Put this method in the code behind file (.cs):
private void GenerateHiddenListBox()
{
uxHiddenListBoxForPermissionName.Items.Clear();
ListItem item;
DataRowCollection rowCollection = Manager.GetManager().RoleManager.GetPermissions().Rows;
foreach (DataRow row in rowCollection)
{
item = new ListItem();
item.Value = row["Id"].ToString();
item.Text = row["Name"].ToString();
uxHiddenListBoxForPermissionName.Items.Add(item);
}
}

Manager.GetManager().RoleManager.GetPermissions() is the method that is used to read the table from DB. Replace it with yours. You should run this method each time there has been any change in the table (ex: new row added/ existing row modified/ deleted)

3. Add the customValidator to the field that you want to validate, along with other validators (if any):

<tr valign="top">
<td style="width: 20%">
<asp:Label ID="Label7" runat="server" Text="<%$Resources:Texts,Name %>"></asp:Label>
</td>
<td style="width: 40%">
<asp:TextBox ID="uxName" runat="server" MaxLength="50" Width="97%"></asp:TextBox>
</td>
<td style="width: 40%">

<asp:RequiredFieldValidator ID="RequiredFieldValidator1" runat="server" ErrorMessage="*"
ValidationGroup="PermissionValidation" ControlToValidate="uxName" SetFocusOnError="true" Display="Dynamic"></asp:RequiredFieldValidator>


<asp:CustomValidator runat="server" ID="uxCustomValidator" ClientValidationFunction="checkNames"
ControlToValidate="uxName" ValidationGroup="PermissionValidation" ErrorMessage="
<%$Resources:Texts,PermissionNameAlreadyExists %>" SetFocusOnError="True" Display="Dynamic">
</asp:CustomValidator>

</td>
</tr>

here I am validaing the uxName field. I'm using the asp:RequiredFieldValidator to check the field is nonempty, and using the asp:CustomValidator to check whether the entry already exists or not.

4. Write the client side function to perform the validation (this function is referred from asp:customvalidator):

<script type="text/javascript" >

function checkNames(source, args){
var permissionNameList= document.getElementById(uxPermissionListClientId);
var nameField= document.getElementById(uxNameClientId);
//alert(permissionNameList.length);
// alert(nameField.value);
var logText= document.getElementById(uxTextBoxLogClientId).value;
//alert(logText);
if(logText=='New'){ //I've set the value of textbox to 'New' while performing add operation
for(var i=0;i
<permissionNameList.length;i++){
//alert(permissionNameList.options[i].text);

if(permissionNameList.options[i].text==nameField.value){
// alert('validation is false, brother');
args.IsValid=false;
}
}
}
else{ //else I've set the value of the hidden text field to the current item to exclude it from comparing.
for(var i=0;i
<permissionNameList.length;i++){
//alert(permissionNameList.options[i].text);

if(permissionNameList.options[i].text==nameField.value&&nameField.value!=logText){
// alert('validation is false, brother');
args.IsValid=false;
}
}
}
}
</script>

uxPermissionListClientId is the client id for list box where data is stored reading from table, uxNameClientId is the client id of the textbox for which the validation is being performed, and uxTextBoxLogClientId is the client id of uxTextBoxLog, the hidden text box where I keep track whether an adding or updating event is going on. setting args.IsValid=false means there is an error, validation will not pass, and postback will not occure.

Tuesday, June 30, 2009

Drag and Drop UltraWebTree nodes in Mozilla Firefox using Infragistics Drag and Drop Framework

Infragistics dont support drag and drop tree nodes for UltraWebTree in Firefox. To go around this, you need to use Infragistics drag and drop Framework.

First, in your project/ website, add this reference: "Infragistics2.Web.v9.1" if Infra CLR 2.0 is installed. For CLR 3.5, you should add: "Infragistics35.Web.v9.1". Its a bit tricky to add it in websites. For websites, you may need to copy the dll into the deploy folder first. Then add the reference in the website.

Then, in your usercontrol, or page, or in master page (whichever applicable) add these assembly:

<asp:ScriptManager ID="ScriptManager1" runat="server" EnableScriptGlobalization="true"
AsyncPostBackTimeout="9600">
<Scripts>
<asp:ScriptReference Assembly="Infragistics2.Web.v9.1, Version=9.1.20091.2040, Culture=neutral, PublicKeyToken=7dd5c3163f2cd0cb" Name="Infragistics.Web.UI.SharedScripts.igDragDrop.js" />
<asp:ScriptReference Assembly="Infragistics2.Web.v9.1, Version=9.1.20091.2040, Culture=neutral, PublicKeyToken=7dd5c3163f2cd0cb" Name="Infragistics.Web.UI.Scripts.5_igObjects.js" />
</Scripts>
</asp:ScriptManager>

You need to be careful about the CLR and version of the infragistics. Here, the version is 2040. But you can check it from your web.config, which version of infragistics is installed in your PC. Use your own version in the string.

In the page/ usercontrol, add this script:

<script type="text/javascript">
Sys.Application.add_load(app_loaded);

function app_loaded()
{
InitiateDragDropFrameWorkToNodes();
}
</script>

This must follow the script manager.

now add scripts in a separate javascript file, and reference it in the page/ usercontrol. I always prefer to use a separate javascript file, because it allows you to debug the script. Add this in the .js file:

var ddR = new $IG.DragDropBehavior();
var dropLoop=true;


function InitiateDragDropFrameWorkToNodes()
{
var treeInstance = igtree_getTreeById(UltraWebTreeClientID); //tree client id passed from pageload.


var firstlevel=treeInstance.getNodes();



//loops through all the first level nodes, usually there wont be more than 1 in our project

for(var i=0;i<firstlevel.length;i++)
{
var treeNode= firstlevel[i];
ddR.addSourceElement(treeNode.getElement());//this line was not needed, but if the root node is not included, then infra's built in drag drop starts, and gives different look.
ddR.addTargetElement(treeNode.getElement());
if(treeNode.hasChildren())
{
AssaignDragSource(treeNode);
}

}
if(ddR._events._handlers.Drop==null||ddR._events._handlers.Drop.length<1){ //this is the most important line, handler should be added only once and not again.
ddR.get_events().addDropHandler(drop);
}

}


function AssaignDragSource( treeNode)
{

var children=treeNode.getChildNodes();

for(var j=0;j<children.length;j++)
{

var element=children[j].getElement();
ddR.addSourceElement(element);
ddR.addTargetElement(element, true);

if(children[j].hasChildren())
{
AssaignDragSource(children[j]); //recursively call all children to add dragdropbehavior


}

}
}

function drop(sender, eventArgs) {
if ( dropLoop==true){

var treeInstance = igtree_getTreeById(UltraWebTreeClientID);
var source = eventArgs.get_manager().get_source().element;
var startNode=treeInstance.getNodeById(source.id);
var startNodes = startNode.getChildNodes();
var target= eventArgs.get_manager().get_target().element;
var endNode=treeInstance.getNodeById(target.id);
if(startNode==endNode){

return;
}

// alert('I am here');

var parentNode = endNode.getParent();
while (parentNode != null) {
if (parentNode == startNode) {
dropLoop=false;//whenever an alert is shown, it reenters the drop event even return is called, its fixed here
alert(msgCannotMoveParentUnderChild);
return;
}
parentNode = parentNode.getParent();
}

CopyNode(endNode, startNode);
//endNode.addChild(startNode.getText());
if (startNode.hasChildren()) {
addDroppedChildren(endNode.getChildNodes()[endNode.getChildNodes().length - 1], startNodes);
}


startNode.remove();


igtree_needPostBack(UltraWebTreeClientID);
var ts = igtree_treeState[UltraWebTreeClientID];
__doPostBack(ts.UniqueId, endNode.element.id + ":Update");
}
dropLoop=true; //allow to enter drop function after the alert message hastle is over.
}

function CopyNode(toNode, fromNode) {

newNode = toNode.addChild(fromNode.getText());
newNode.setTag(fromNode.getTag());
}

function addDroppedChildren(endNode, startNodes) {

for (var i = 0; i < startNodes.length; i++) {
CopyNode(endNode, startNodes[i]);
//endNode.addChild(startNodes[i].getText());
if (startNodes[i].hasChildren()) {
addDroppedChildren(endNode.getChildNodes()[endNode.getChildNodes().length - 1], startNodes[i].getChildNodes());
}
}

}

This will allow you to drag and drop tree nodes (including children nodes) in firefox. These two posts was really helpful with drag and drop framework:
Drop it Like its Windows
Introduction to the Infragistics Web Drag and Drop Framework

Wednesday, June 10, 2009

Use RegularExpressionValidator to validate a URL / web address

This is a nice RegularExpressionValidator to validate URLs:

<asp:RegularExpressionValidator ID="RegularExpressionValidator2" runat="server"
ControlToValidate="uxRssUrl" ErrorMessage="<%$ Resources:Texts, InvalidUrl %>"
ValidationExpression="^(ht|f)tp(s?)\:\/\/([0-9a-zA-Z]([-\.\w]*[0-9a-zA-Z]))*\.([0-9a-zA-Z]([-\.\w]*[0-9a-zA-Z]))*\.(:(0-9)*)*(\/?)([a-zA-Z0-9\-\.\?\,\'\/\\\+&%\$#_]*)?$"
ValidationGroup="ResellerValidation"></asp:RegularExpressionValidator>">

This RegularExpressionValidator will check whether the content starts with:
(ftp:// ftps:// http:// https://) and before the next '/' comes whether there is at least 2 '.' in it.

This site is helpful for RegularExpressionValidator:
How To: Use Regular Expressions to Constrain Input in ASP.NET

Tuesday, June 2, 2009

How to check whether a variable has been declared from Javascript

Sometimes we declare javascript variables from serverside pageload method and use it in a common javascript file's method. The problem is, when that common javasript method is used by another serverside file, it might find that variable undefined. The best way to check is use typeof method of javascript. Consider the following method:

if(typeof(uxDocumentsOrImagesRadioButton)!= 'undefined'){
if(document.getElementById(uxDocumentsOrImagesRadioButton)!=null){

var thisradioGroup = document.getElementById(uxDocumentsOrImagesRadioButton);
if(thisradioGroup.disabled==true){
thisradioGroup.disabled=false;
}
}
}

uxDocumentsOrImagesRadioButton variable is declared from a page's pageload method. But if another page wants to use this method before that variable has been declared, then it will throw error. So, we need to check whether the variable has been declared previously.

Monday, May 11, 2009

Perform left outer join using LinQ

For my work, I had to perform a left outer join in LinQ. Here is the scenario:
There are 2 tables:










Auto text is the left table. Its customer column holds the Id for Customer table's elements. My task was, if there is a value in the Autotext.Customer field, then I have to show the relevant customer name. And if the value is null then it would show n\a. Only performing a left outer join can solve this. And this is how I solved it:

public IEnumerable GetAutotextsWithCustomerNameLeftJoin(int customerId)
{
try
{
IEnumerable AutotextsWithCustomerName = from AutotextTable in this.Context.AutoTexts
join CustomerTable in this.Context.Customers
on AutotextTable.Customer equals CustomerTable.Id into temporaryTable
from xTable in temporaryTable.DefaultIfEmpty()
select new { AutotextTable.Id, AutotextTable.Key, AutotextTable.Value, CustomerName = xTable.Id==null?"n\a":xTable.Name };
return AutotextsWithCustomerName;
}
catch (Exception ex)
{
return null;
}
}

Here, temporaryTable is the table that is created after the join operation is performed. xTable is the table that is created after left outer join is performed.

This solved the problem. I took help from different online posts. But, this post helped me the most:
LEFT OUTER JOIN in LINQ To SQL

Wednesday, March 25, 2009

Solving compile errors: CS0006, CS0009 in Visual Studio 2008

Recently, when I was trying to run any web application project, I was getting the following error message:

CS0006: Metadata file 'C:\WINDOWS\assembly\GAC_32\System.EnterpriseServices\2.0.0.0__b03f5f7f11d50a3a\System.EnterpriseServices.dll' could not be found

So, I searched the net for solution, and this is how I was able to solve it:

First, check whether "System.EnterpriseServices.dll" file actually exists at that folder or not. You cannot navigate to the GAC_32 folder using windows explorer. You have to use command line to go there. From start menu, select 'Run' > type 'cmd' press enter. then directly go there:

cd C:\WINDOWS\assembly\GAC_32\System.EnterpriseServices\2.0.0.0__b03f5f7f11d50a3a

press enter. then use dir /p command to see all files in it and search for the "System.EnterpriseServices.dll" file if you cannot find it there, then again go to this location:

cd C:\Windows\Microsoft.NET\Framework\v2.0.50727

press enter. From here you have to copy the file to the desired location. Use this command:

copy System.EnterpriseServices.dll C:\WINDOWS\assembly\GAC_32\System.EnterpriseServices\2.0.0.0__b03f5f7f11d50a3a

Now try to run that web project from visual studio, and it should work. If it doesnt work, and gives you another error: CS0009 compile error, then you have to copy all the files to that folder. First navigate to that source folder:

cd C:\Windows\Microsoft.NET\Framework\v2.0.50727

press enter. Then use this command to copy all DLLs:

copy *.dll C:\WINDOWS\assembly\GAC_32\System.EnterpriseServices\2.0.0.0__b03f5f7f11d50a3a

You dont need to overwrite any file that already exists at the destination folder. This should solve CS0009 error.

Sunday, March 15, 2009

How to email all AD (active directory) users from sharepoint event handlers

Sharepoint is normally (or should be) installed into server PCs. And the event handler features are also installed into servers. But AD users use sharepoint from their client PCs. In this case, program fails to log into the domain if you try to retrieve information directly. Normally this code is good enough to retrive all AD users:

ArrayList employeeEmailAddresses = new ArrayList();
string filter = "(&(objectCategory=person)(objectClass=user)(sn=*))";
DirectorySearcher search = new DirectorySearcher(filter);
foreach (SearchResult result in search.FindAll())
{
DirectoryEntry entry = result.GetDirectoryEntry();
if (entry.Properties["mail"].Value != null)
{
string str = entry.Properties["mail"].Value.ToString();
employeeEmailAddresses.Add(str);
}

}

But if you are trying to get information from sharepoint event handlers using the above code block, program will throw exception at search.FindAll() :

COMEXCEPTION was caught: {"An operations error occurred.\r\n"}

In order to solve this, you need to manually log in the program into the AD using the credentials of an existing AD user. Then, the program can read all the informations from AD.

you can use this method to get all the users from AD:

private ArrayList getEmployeeMailAddresses()
{
ArrayList employeeEmailAddresses = new ArrayList();
try
{
string filter = "(&(objectCategory=person)(objectClass=user)(sn=*))";

using (DirectoryEntry root = new DirectoryEntry("LDAP://domainName","userName","password", AuthenticationTypes.Secure))
{
using (DirectorySearcher searcher = new DirectorySearcher(root))
{
searcher.ReferralChasing = ReferralChasingOption.All;
searcher.SearchScope = SearchScope.Subtree;
searcher.Filter = filter;

foreach (SearchResult result in searcher.FindAll())
{
DirectoryEntry entry = result.GetDirectoryEntry();
if (entry.Properties["mail"].Value != null)
{
string str = entry.Properties["mail"].Value.ToString();
employeeEmailAddresses.Add(str);
}

}
}

}
}
catch (Exception ex)
{
}

return employeeEmailAddresses;
}

This line: DirectoryEntry("LDAP://domainName","userName","password", AuthenticationTypes.Secure)) will login the program into the appropriate domain, then you can get informations for all AD users. After getting the arraylist that contains all the users, you can mail all of them using this method:

private void sendEmail(string htmlBody, ArrayList employeeEmailAddresses, string itemCreator, string subject)
{
AlternateView avHtml = AlternateView.CreateAlternateViewFromString(htmlBody, null, MediaTypeNames.Text.Html);
MailMessage m1 = new MailMessage();
m1.AlternateViews.Add(avHtml);

for (int i = 0; i < employeeEmailAddresses.Count; i++)
{
m1.To.Add(new MailAddress(employeeEmailAddresses[i].ToString()));
}
m1.Subject = subject;


SmtpClient client = new SmtpClient("smtpclientaddress");
client.Send(m1);

}

Sunday, March 8, 2009

How to find a user (SPUser or domain user) belongs to a AD (active Directory) group / domain group from sharepoint

First, Lets think a set of domain users belong to a Active Directory/ domain group named $DotNet-Developers. Of course, you have added users from active directory, not from sharepoint. Now, you want to find out from sharepoint whether a user belongs to that group. To do this, what you need to do is:

1. create a sharepoint group (SPGroup) in the site, for example named DotNetSPGroup.
2. Add the Domain group ($DotNet-Developers) inside that sharepoint Group.
3. Give that SPGroup at least "read permission" in the site.
4. Search for user under that SPGroup using group.ContainsCurrentUser method.

Now, while running any specific program, you want to find out whether a specific user belongs to that active directory group or not. Lets think, you are getting the user from a "Person or group field" from sharepoint. Now retrieve the SPUser from that field. after that just use this method:


private bool IsUserInGroup(SPUser targetUser, SPItemEventProperties properties, string groupName)
{
bool containsUser=false;


using(SPSite targerSite= new SPSite(properties.SiteId,targetUser.UserToken))
{
SPWeb targetWeb=targerSite.OpenWeb(properties.RelativeWebUrl);

SPGroupCollection LMSGroups = properties.OpenWeb().Groups;
try
{
SPGroup groupForDotNetDev = LMSGroups[groupName];
if (groupForDotNetDev.ContainsCurrentUser)
{
containsUser=true;
}
}
catch (Exception e)
{
}
}
return containsUser;
}

This should do the trick.

using(SPSite targerSite= new SPSite(properties.SiteId,targetUser.UserToken)) changes the current user to that user. Then, you can use groupName.ContainsCurrentUser user method to check whether the user is inside that group or not. This method even searches inside AD groups, if that AD group is added inside an SPGroup. So, it works just fine.

I must thank El Blanco for his post.

Wednesday, February 11, 2009

Create or update an SPListitem in an SPList from event handlers where current user has read (or no) permission

Scenario: User is adding/updating/deleting an item in a list (listX), where he has adequate permission. But as soon as he does it, I have to change the content of an item which belongs to a list (listY), where he has read permission only (also works if he has no permission at all) . This is how I did it:

In the event handler of listX, add this:

public override void ItemAdded(SPItemEventProperties properties)
{
if (properties.ListTitle != "listX")
{
return;
}
breakListPermission(properties, "listY");
//add new item, or update, or delete an existing item in "listY"
restoreListPermission(properties, "listY");
}

Here is the method for changing list permission to give current user adequate permission:

private void breakListPermission(SPItemEventProperties properties, string strTargetList)
{
base.ItemAdded(properties);
this.DisableEventFiring();
SPSecurity.RunWithElevatedPrivileges(delegate()
{

using (SPSite targetSite = new SPSite(properties.OpenWeb().Site.ID))
{

SPWeb targetWeb = targetSite.OpenWeb(properties.RelativeWebUrl);
targetWeb.AllowUnsafeUpdates = true;
targetWeb.Update();
SPList targetList = targetWeb.Lists[strTargetList];
SPRoleDefinition adminRoleDefinition = targetWeb.RoleDefinitions.GetByType(SPRoleType.Administrator);

if (!targetList.HasUniqueRoleAssignments)
{
SPRoleAssignmentCollection targetWebRoleCollections = targetWeb.RoleAssignments;
SPRoleAssignment targetWebRole = targetWebRoleCollections[2];
if(!targetWebRole.RoleDefinitionBindings.Contains(adminRoleDefinition))
{
targetWebRole.RoleDefinitionBindings.Add(adminRoleDefinition);
targetWebRole.Update();
}
}

else{
SPRoleAssignmentCollection myRoleCollection = targetList.RoleAssignments;
SPRoleAssignment currentRoleAssignment = myRoleCollection[2];

if (!currentRoleAssignment.RoleDefinitionBindings.Contains(adminRoleDefinition))
{
currentRoleAssignment.RoleDefinitionBindings.Add(adminRoleDefinition);
currentRoleAssignment.Update();
}
}
}
}
);
}

First of all, It is really important to declare new SPSite, SPWeb... objects inside the RunWithElevatedPrivileges block. If you use the objects that you declared earlier, simply wont work.
Then you need to update the website to allow unsafe updates by this code:

targetWeb.AllowUnsafeUpdates = true;
targetWeb.Update();

otherwise, you will get exception like this:
"The security validation for this page is invalid. Click Back in your Web browser, refresh the page, and try your operation again."
Next you need to check whether "listY: inherit permission that has been assigned to the website or not. If it inherits the website's permission, then you have to change the permission for the website, otherwise you have to change the permission for the list. Use this line to check it:

if (!targetList.HasUniqueRoleAssignments){

You can also break the inheritence, and create new roleassignments for current list:

targetList.BreakRoleInheritance(false);

For my work, it was good enough just to change the permission of the web. Since, the list inherits permission from the web.
I have used this block to retrieve the SPRoleAssignment relevant to current user:

SPRoleAssignmentCollection myRoleCollection = targetList.RoleAssignments;
SPRoleAssignment currentRoleAssignment = myRoleCollection[2];

I have used hardcode here, because I knew that the 3rd roleassignment of both site and "listY" would be the roleassignment that contains current user. If you dont know where the current user is, then you have to do this:
(You must run this following code block before the RunWithElevatedPrivileges starts. When the process enters RunWithElevatedPrivileges code block, the current user will change into "System Account".)

SPGroupCollection lmsGroups = targetWeb.Groups; //this targetWeb was initiated before RunWithElevatedPrivileges

SPGroup lmsMember = lmsGroups[0]; //just initiate

foreach (SPGroup spgroup in lmsGroups)
{
if (spgroup.ContainsCurrentUser)
{
lmsMember = spgroup;
}
}

(Run this following block inside the RunWithElevatedPrivileges:)

SPRoleAssignmentCollection myRoleCollection = targetList.RoleAssignments;
SPRoleAssignment currentRoleAssignment = myRoleCollection.GetAssignmentByPrincipal((SPPrincipal)lmsMember);

If the current user/ group doesn't belong to that list, then you have to create, and add it with the list. then you have to give them permission by using SPRoleDefinition. If you try to give them permission before you add it to the list you will get exception (at least, I got the exception):
"Cannot update a permission level assignment that is not part of a permission level assignment collection."
Here's how (they should belong to the web, at least.. or this wont work):

get the group where the user belongs before the RunWithElevatedPrivileges block, or create a single user SPRoleAssignment:
(for user:)

SPRoleAssignment currentRoleAssignment = new SPRoleAssignment(targetWeb.CurrentUser.LoginName, targetWeb.CurrentUser.Email, targetWeb.CurrentUser.Name, targetWeb.CurrentUser.Notes);

(for group, after getting the group:)

SPRoleAssignment currentRoleAssignment = new SPRoleAssignment((SPPrincipal)lmsMember);

Now inside the RunWithElevatedPrivileges block do this:
you should check the value for currentRoleAssignment.Parent . If it is null, and you add a roledefinition with it, and update the roleassignment, then surely you will heat the preceeding exception when executing: currentRoleAssignment.Update(); line. But if currentRoleAssignment.Parent is not null, and its value is equal to either the list name, or the web name, then its ok to add a roledefinition with it. Else, you need to associate it with the list first:

targetList.RoleAssignments.Add(currentRoleAssignment);
targetList.Update();

If the adding is successful, then retrieve this new roleassignment from role assignment collection:

currentRoleAssignment= targetList.RoleAssignments[targetList.RoleAssignments.Count - 1];

And give roledefinition (permission) according to this way:

SPRoleDefinition adminRoleDefinition = targetWeb.RoleDefinitions.GetByType(SPRoleType.Administrator);
if (!currentRoleAssignment.RoleDefinitionBindings.Contains(adminRoleDefinition))
{
currentRoleAssignment.RoleDefinitionBindings.Add(adminRoleDefinition);
currentRoleAssignment.Update();
}

That should do the trick. And when removing permission, just do the opposite:

private void restoreListPermission(SPItemEventProperties properties, string strTargetList)
{
SPSecurity.RunWithElevatedPrivileges(delegate()
{
using (SPSite targetSite = new SPSite(properties.OpenWeb().Site.ID))
{

SPWeb targetWeb = targetSite.OpenWeb(properties.RelativeWebUrl);
SPList targetList = targetWeb.Lists[strTargetList];

SPRoleDefinition adminRoleDefinition = targetWeb.RoleDefinitions.GetByType(SPRoleType.Administrator);

if (!targetList.HasUniqueRoleAssignments)
{

SPRoleAssignmentCollection targetWebRoleCollections = targetWeb.RoleAssignments;
SPRoleAssignment targetWebRole = targetWebRoleCollections[2];
if (targetWebRole.RoleDefinitionBindings.Contains(adminRoleDefinition))
{
targetWebRole.RoleDefinitionBindings.Remove(adminRoleDefinition);
targetWebRole.Update();
}
}
else{
SPRoleAssignmentCollection myRoleCollection = targetList.RoleAssignments;
SPRoleAssignment currentRoleAssignment = myRoleCollection[2];

if (currentRoleAssignment.RoleDefinitionBindings.Contains(adminRoleDefinition))
{
currentRoleAssignment.RoleDefinitionBindings.Remove(adminRoleDefinition);
currentRoleAssignment.Update();
}
}

targetWeb.AllowUnsafeUpdates = false;
targetWeb.Update();

}
}
);
this.EnableEventFiring();
}

There you go. It did the job for me. Hope it might help you too. But Sharepoint is so unpredictable sometimes... I don't promise anything.. :)

Tuesday, January 20, 2009

Attaching a database without (.ldf) file

If you don't have the log (.ldf) file for an SQL server database, and you need to attach the database into sql server 2005, then run this sql query from SQL server 2005:

sp_attach_single_file_db N'OMS', N'E:\DataBase\OMS.mdf'

The first part is the name of the database and the second part is the physical address of the (.mdf) file. "N" prefix represents National Language Character Set. Which means the subsequent string is in unicode.

Alternatively, you can directly add a Database into a visual studio project. For this, you need to add an entry in the app.config file:

<add name="WpfDataEntry.Properties.Settings.OMSConnectionString"
connectionString="Data Source=.\SQLEXPRESS;
AttachDbFilename=|DataDirectory|\OMS.mdf;
Integrated Security=True;
User Instance=True"
providerName="System.Data.SqlClient" />

The |Datadirectory| is a variable which holds the location of the root folder for databases. For ASP .NET web applications, the name of the folder is App_data. for desktop applications, the folder is the "Bin" folder

Credit for this post goes to Tanveer vai (Tanveer Ibn Haresh, Software Engineer, Bording Vista Ltd.)

While editing an ultrawebtree node, stop users to input escape characters

In order to stop users, from giving input of escape characters, you have to handle the editkeydown client side event. in the designer file inside the client side events add this:

EditKeyDown="UltraWebTree1_EditKeyDown"

In the javascript file add this function:

function UltraWebTree1_EditKeyDown(treeId, nodeId, keycode) {
if (keycode == 220) {
var treeInstance = igtree_getTreeById(UltraWebTreeClientID)
treeInstance.endEdit(false);
}
}

when the user presses "\" then the value of keycode becomes 220. endEdit(false) ends the editing process of a node, and goes back to the text that was there before editing began. endEdit(true) stops the editing but keeps the updated text that was while the tree node was being edited. So, its always a good idea to use endEdit(false) method. UltraWebTreeClientID was passed from the server side.

If you need to block user from pressing other keys too, then debug the process and get the value of keycode for corresponding characters, then add those values in the if condition.

Sunday, January 18, 2009

Backup and Restore a sharepoint site from 1 pc to another with installed features

In order to backup and restore a sharePoint site say from pc1 to pc2 using command prompt, First open the command prompt in pc1:
start... Run... type cmd... press enter

In the command prompt, type:

cd C:\Program Files\Common Files\Microsoft Shared\web server extensions\12\BIN
...press enter

Then run this command to take backup of an existing site:

stsadm -o backup -url http://svradm002:11226/ -filename c:\backUpSite.dat -overwrite
replace the url and file location with the data you need

In Pc2, copy and paste the backupSite.dat file to any location (ex: in c drive). Open command prompt and go to the bin folder in preceding way. Then use this command to restore from a backup to an existing site. Remember, you have to create the site on the following location (http://vpc-wss:30322/) first, then you can restore from the dat file on that site. (Assume that the address of our target site is: http://vpc-wss:30322/)

stsadm -o restore -url http://vpc-wss:30322/ -filename c:\backupSite.dat -overwrite

U will find that the feature is not available with the new site that you created. If you added the feature manually, by creating feature folder, element & manifest file then you have to do again in the new PC. Check my previous blog post. But if you created a feature using Visual Studio with vsewss (visual studio extension for windows sharePoint server) installed, then you have to do this:
-In PC1, open the project folder
-copy the debug folder
-paste it anywhere in pc2
-then edit the setup.bat file inside the debug folder. replace old addresses http://localhost:11226/ with new one: http://localhost:30322/
-then run the setup.bat file.
this will add the feature in the new site.

Monday, January 12, 2009

Use a single event to traverse back and forth between server-side and client-side 2 times to complete a task

I placed a UltraWebTree in a Usercontrol(.ascx) file, and used a (.cs) file to handle event and logic. Then, reference a Javascript where I handled the client side events. I had to add a new node to a tree from client-side, rename it, add it into the database from server-side, then select it from the client side, and go back to server side to open folder associated with that new node. I did these in 4 steps:

Step1. Create a new node from client side, rename it:

Add this code into the Javascript file to add a new node:

var currentNodeId;
var newNodeAdded = false;
var newNodeId;

function addChild() {
var tree = igtree_getTreeById(UltraWebTreeClientID);
var node = tree.getNodeById(currentNodeId);
if (node.hasChildren()) {
if (node.getExpanded() == false) {
node.setExpanded(true);
}
}
if (node) {
newnode = node.addChild(newFolderName);
newNodeId = newnode.Id;
newnode.edit();
newNodeAdded = true;
}
}

CurrentNodeID is the id of the node that is currently selected taken from the nodeSelectionChanged mathod. UltraWebTreeClientID is the id of the tree that was passed from the (.cs) file's pageLoad section's If(!Page.isPostback) portion:

String Script = "var UltraWebTreeClientID = '" + UltraWebTree1.ClientID.Replace("_", "") + "';";
this.Page.ClientScript.RegisterClientScriptBlock(this.GetType(), "InitVariable", Script, true);


Step2: Go back to server-side to add a record into the DB:

After a new node has been added, the nodeAdded event will be fired, which you need to handle:

void UltraWebTree1_NodeAdded(object sender, Infragistics.WebUI.UltraWebNavigator.WebTreeNodeEventArgs e)
if (uxTreeEventLog.Text == "")
{
TreeNodeChangedArgs args = new TreeNodeChangedArgs();
args.Name = e.Node.Text;
args.Parent = e.Node.Parent.Tag.ToString();
args.TreeNodeAction = TreeNodeChangeAction.Added;
if (TreeNodeChanged != null)
TreeNodeChanged(sender, args);
e.Node.DataKey = Convert.ToInt32(args.ID);
e.Node.Tag = Convert.ToInt32(args.ID);
}

TreeNodeChangedArgs is a class declared globally to handle the event properties:


public class TreeNodeChangedArgs
{
public String ID;
public String Name;
public String Parent;
public TreeNodeChangeAction TreeNodeAction;
public Boolean Cancel;
}

TreeNodeChangedAction is an enum declared globally:


public enum TreeNodeChangeAction
{
Added,
Changed,
Removed
}

TreeNodeChange is a variable of a deligate also declared globally:

public delegate void TreeNodeChangedEventHandler(object sender, TreeNodeChangedArgs e);
public TreeNodeChangedEventHandler TreeNodeChanged;

It will take the event outside the usercontrol and let you handle the event from the page where you have placed this usercontrol. There you will add a new record using Business Layer in the DB taking info of the node (ex: id, name, parent, etc...) that was passed from here.

Step 3: Go back to client-side to select the node:

To go back to client-side again, you need to use end request method from the (.cs) file's pageLoad section's If(!Page.isPostback) portion:

this.Page.ClientScript.RegisterStartupScript(this.GetType(), "RegisterFunciton", "Sys.WebForms.PageRequestManager.getInstance().add_endRequest(TreeControl_EndRequestHandler);", true);

Now, you need to write a function in the javascript file with the name TreeControl_EndRequestHandler:

function TreeControl_EndRequestHandler(sender, args) {
if (newNodeAdded == true) {
var tree = igtree_getTreeById(UltraWebTreeClientID);
var node = tree.getNodeById(newNodeId);
node.setSelected(true);
newNodeAdded = false;
}
}

Step 4: Open associated folder from server-side:

After selecting the new node from the client-side, the selectionChanged event will be fired which you have to handle in the (.cs) file:


void UltraWebTree1_NodeSelectionChanged(object sender, WebTreeNodeEventArgs e)
{
if (NodeClicked != null)
NodeClicked(sender, e);
}

Again, NodeClicked is a variable of a delicate that was declared globally:

public delegate void NodeClickedEventHandler(object sender, WebTreeNodeEventArgs e);
public NodeClickedEventHandler NodeClicked;

You can take the event outside of this control and handle it on the page, where you can search for associate folder in the DB, and open it (in a different panel beside to the tree)

Step 5: Create chain effect:

If you have a debugging point set at the TreeControl_EndRequestHandler function, then you will notice that the process has come back to client-side again. In fact, after every server-side events, process will come back to here, from where you can chain it back to server side again.

Most part of this code(server-side part) was done by Ranku vai (Anupam Ranku, Senior Software Engineer, Bording Vista Ltd.) Thank you very much, Ranku vai.

Check whether all the checkboxes of a tree are unchecked from client side (Javascript)

We are using infragestics for our develpment. so we had to use the UltraWebTree provided by infragestics. Add this code in client side:

First, declare a global varriable:

var allUnchecked = true;

This part of the code should be written inside a javascript function where u will handle the event. For my work, I used the event handling of UltraWebMenu.


function ultraMenu1_itemClicked(MenuId, ItemId) {
if (ItemId == UltraWebMenuClientID + "_4") {
var treeInstance = igtree_getTreeById(UltraWebTreeClientID);
var testNodes = treeInstance.getNodes();
allUnchecked = true;
checkNodes(testNodes);
if (allUnchecked == true) {
alert('All are unchecked');
}
}
}

UltraWebTreeClienID holds the client id of infragestics ultrawebtree that was passed from the server side (a .cs code behind file where the ultrawebtree has been placed) in following way:


String Script = "var UltraWebTreeClientID = '" + UltraWebTree1.ClientID.Replace("_", "") + "';";
this.Page.ClientScript.RegisterClientScriptBlock(this.GetType(), "InitVariable", Script, true);


UltraWebMenuClientID was passed from that .cs file in the same way, and _4 checks whether the 4th item of the ultrawebmenu is clicked.
checknodes is the function that checks all the nodes whether they are checked or not. Place it anywhere in the javascript file:

function checkNodes(testNodes){
for (var i = 0; i < testNodes.length; i++) {

if (testNodes[i].getChecked() == true) {
allUnchecked = false;
}
if (testNodes[i].hasChildren() == true) {
checkNodes(testNodes[i].getChildNodes())

}

}

}




After running checknodes recursively for all the nodes, it will go back to the main function and will show the alert if all nodes are unchecked.