Showing posts with label C#. Show all posts
Showing posts with label C#. Show all posts

Monday, January 26, 2015

Strange user login name in SharePoint when using Claims?

Hey, if you are like me and use claims to login to your SharePoint, you probably noticed the user names went nuts.

Having a user name like domain\user will now return something more like i:0#.w|domain\user if you are lucky, but I’ve seen stranger formats as well…

Now, say a user sends you a login name (domain\user) and you need to get this value and set it to the assigned to of a task, or set permissions for this user – you will quickly find out you cannot get an SPUser object from this standard login name.

Also, when you want to send the login name to another system or work with it in your code – you might want to get just the domain\user part, without the claims decorations.

True, the claims login formats are documented and you can hope to parse the format yourself to extract the user login from it, but I recently found a hidden (to me at least) gem in SharePoint API that can help with this task.

First, start using this namespace: Microsoft.SharePoint.Administration.Claims;

Now, you can work with SPClaimProviderManager object to check if a string is a claims encoded user name, and convert it to a standard user name, and the other way around.

I’ve created this simple utility class for me to use in my code for now, here is a code sample explaining how to use this object:

public class ClaimsHelper
{
    public static string ClaimsToLogin(string login)
    {
        try{
            if(SPClaimProviderManager.IsEncodedClaim(login))
                return SPClaimProviderManager.Local.ConvertClaimToIdentifier(login);
        }
        catch{
            //log error, return the origina value we got
        }
        return login;
    }
    public static string LoginToClaims(string login)
    {
        try
        {
            if (!SPClaimProviderManager.IsEncodedClaim(login))
                return SPClaimProviderManager.Local.ConvertIdentifierToClaim(login, SPIdentifierTypes.WindowsSamAccountName).ToEncodedString();
        }
        catch
        {
            //log error, return the origina value we got
        }
        return login;
    }
}

(Code is for example purposes only, use at own risk, feel free to change the names of the helpers)


Notice I’m expecting to get a windows login user name (SPIdentifierTypes.WindowsSamAccountName) but you can change this to support your own authentication.


Good luck, hope this help.

Wednesday, September 17, 2014

properties.AfterProperties[“Title”] always null in document libraries

Like the title says,

If you are writing an event handler for document libraries / lists, properties.AfterProperties is usually a good way for you to get the new values that the user modified.

Now, we all know the fact that in lists you have to use internal name and in libraries you have to use display name, right?

So, it appears it is not the only trick up their sleeve…

If you ever tried getting the title or name columns in a document library you will quickly notice AfterProperties and BeforeProperties will always return null.

Now, I came across this technet discussion: http://social.technet.microsoft.com/Forums/sharepoint/en-US/ef6e1b63-c821-4c6c-b05f-0b1e32ebf073/beforeproperties-and-afterproperties-returns-null-value-in-itemupdating-itemupdated-event?forum=sharepointdevelopmentprevious

And at the very bottom, there is an answer by Helm Ifort that I found very interesting.

It appears the title column value is available in AfterProperties, only you have to use the “vti_title” name for it! (STS 2001 anyone?)

In my book, this is a bug, and an undocumented one at the very least… I hope someone fixes it soon, but for now it is one of those things seasoned SharePoint sharks need to remember.

Hope this helps

Friday, November 8, 2013

SPFieldMultiLineText.RichText = true does not update

Interesting thing happened to me today, and I guess I’ve seen it happening at least 10 times in the past but I keep forgetting what the solution was every time I see it again.

Say you create a multiple lines of text field in a list (Note field type of class SPFieldMultiLineText).

Now, you want to make sure it supports HTML but you want to do it via code.

The simple thing to do appears to be:

SPFieldMultiLineText myField = …;
myField.RichText = true;
myField.RichTextMode = FullHtml;
myField.Update();

Right? Wrong!

Run that code again, you will find that the RichText and RichTextMode properties were not modified.

Even calling list.Update() doesn’t help.

Apparently, the simple solution is to edit and set the myField.SchemaXml property directly, without calling myField.Update() after. Simple, yet annoying.

Here is an example of a working code:

XDocument xSchema = XDocument.Parse(field.SchemaXml);
var xField = xSchema.Root;
bool needUpdate = false;
var xRichText = xField.Attribute("RichText");
if (xRichText == null)
{
    needUpdate = true;
    xField.SetAttributeValue("RichText", "TRUE");
}
else if(xRichText.Value != "TRUE")
{
    needUpdate = true;
    xRichText.SetValue("TRUE");
}
var xRichTextMode = xField.Attribute("RichTextMode");
if (xRichTextMode == null)
{
    needUpdate = true;
    xField.SetAttributeValue("RichTextMode", "FullHtml");
}
else if (xRichTextMode.Value != "FullHtml")
{
    needUpdate = true;
    xRichTextMode.SetValue("FullHtml");
}
                                                                        
if( needUpdate )
    field.SchemaXml = xSchema.ToString();

Tuesday, May 14, 2013

One solution to rule them all

My prezi is ready for SharePoint Saturday LA!
This session will be heavy on the visual studio live demo, hope that part goes well :)
But for the first part of the talk I thought I could use prezi to explain the challenge in building a solution that can be deployed on both SharePoint 2010 and 2013, without doubling the R&D efforts, QA efforts and overall maintenance.
We had a very long discussion about this in house, and we are still not entirely convinced this is the best solution to this problem, but it is the best we’ve got so far.
In the past we used to have 2 different solutions for each version of the product. This forced us to constantly sync code between the two versions, having to fix and retest every issue on both versions, which of course wasn’t done perfectly so we ended up with bugs that were fixed in one version only, features that were missing from the other version and basically it was very hard to maintain them.
Now, with the introduction of versioned root folders in SharePoint 2013 this have become a bit more challenging since the code would be different and target different folders in 2010 and 2013. While in server code its rather simple to test the version and use the right path, doing it in config xml files (like web part gallery icon, feature icon for example) is not something we can change in runtime.
This is why we came up with a set of tools that allows us to keep working on one solution, and producing 2 packages from the same source code in one build. One for SharePoint 2010 and the other for SharePoint 2013.
It didn’t take a long time before we then learned about the challenges in trying to debug this code on the SharePoint 2013 machine. Since it wasn’t build on that machine, and didn’t target the .NET framework 4 – this proved to be a bit more complex than what we expected.
To find out more on how we got everything working – come see my session!

Please feel free to leave a comment here if you want more info, or if you been to my session and would like me to add or change something.
Code sample:
http://sdrv.ms/14kRdt3

More info on deferred site collection upgrade:
http://kwizcom.blogspot.ca/2014/03/sharepoint-2010-deferred-site.html

Since I get asked a lot during this session, "are FTC solutions dead?"
Here is a short presentation with points to consider:


Thursday, March 7, 2013

Upgrading SharePoint 2010 VS2010 solution to VS2012

Hey,

I *was* going to write a long post on how to upgrade a SharePoint 2010 project and solution from VS2010 to VS2012,

based on past experience, we all know upgrading visual studio was a bit of a pain…

Well, it turns out the guys at Microsoft did such a great job – I have nothing to write about!!!

You open your VS2010 solution in VS2012, it runs a short upgrade and shows you the results, and boom – you got yourself an upgraded solution, everything works beautifully – and the best part is: the upgraded solution works on both VS2010 and VS2012 with no problems!

After upgrading about 10 different solutions, I am confident to say I have nothing else to say.

So… It’s snowing outside… Nice… Ok, ok – its not that kind of a blog…

Coming up soon: Upgrading your full trust solutions to SP2013 – here I do have some insight!

Have a wonderful week!

Tuesday, October 2, 2012

Traps in SharePoint 2013 API

So, SharePoint 2013 has almost full API backward compatibility? Sure. That’s great!

I mean, Microsoft did a great job making our lives easy in upgrades.

But I did find some traps while upgrading to 2013 I want to share with your guys,

I am sure these things won’t be documented anywhere – which is why I call them “traps”…

I’ll try to collect more info and add it here as I move forward.

Note: these issues are found on a pre release version and might change before production is released.

Trap #1:

We run a query on tasks list, using the default all tasks view from that list. Thing is, we got “Object not set to an instance” error coming from our query parser.

Digging in code, I found SPListView.Query used to be an empty string in 2007/2010. In some lists in 2013 this changed to null now… So, any code using or parsing the query should now check if the view query is null before accessing it, like so:

string query = listview.Query ?? “”; //Takes listview.Query if not null, otherwise takes empty string.
… work and parse query as you would before

Trap #2:

We had some code that allows the user to select a list template and aggregate all items from that list type (in our list aggregator web part). So, I set it up to run on tasks lists, after fixing Trap #1, there was no error but still no items were returned.

Reason: I added a tasks “App” in 2013, which I assumed was the same as a tasks list. but to my surprise, tasks list type is 107, while the tasks App creates a list that uses type 151 (TasksWithTimelineAndHierarchy)

In our code, I collected all available list templates on the current site, but I had a code that prevented duplicate items from being added. I checked the SPListTemplate.Name and only added the first.

It appears, that in SharePoint 2013, both list types (107 and 151) have the same name “Tasks”! So, my code was only adding the first one, 107.

I had to modify my code to take the SPListTemplate.Type.ToString() in case of a duplicate entry, which seems to do the trick, Only added a space before every capital letter so it would be formatted nicer for the user.

 

Comment if you found more interesting upgrade traps!

Saturday, October 29, 2011

Developer's Guide: How to enhance your SharePoint performance - Understand Caching

I have just finished my presentation at SharePoint Saturday Twin Cities!

I talked with developers and ITPro on how to enhance their SharePoint and SharePoint customization performance using some of the built in caching features, such as:
- BLOB cache
- Ghost files
- Object cache
- Page output cache
As well as other coding practices, such as:
- Query pagination
- Indexed columns
- Throttling
- Code cache in multi-threaded environment

Here is the complete presentation, download to read comments in each slide:

Monday, May 30, 2011

Setting default value to column in document library

Hi,

One question I hear a lot from developers and system designers is “How can I set a default value to a column in a document library”?

The same goes for form libraries in SharePoint, Apparently SharePoint does not support default values for document libraries.

The first thing that comes to mind, as a solution for this predicament, is to build an event handler that will set a default value to the fields you need whenever a new item (document) is added to the library.

An experienced developer will tell you you should use the ItemAdding event, in order to set the default values to the field during the same update that the user initiated, and under his credentials.

To do that, you will have to use the properties.AfterProperties["FieldName"] = “Default Value”; to set your value.

But – you will be surprised that document libraries, although they do support the ItemAdding event, do not support properties.AfterProperties use. This collection is always empty and ignored in document libraries!

So, the only solution to set a default value to a field in a document library is by using the ItemAdded event.

During this event, the SPListItem was already created and is accessible through properties.ListItem property (unlike ItemAdding, that happens before the item was created).

So, setting the field should be rather easy: properties.ListItem[“FieldName”] = “Default value”;

But you will have to update the file manually yourself, since this happens after SharePoint has already processed the update of the item.

Naturally, you will not want to update the modified date/time, modified by user, and also you will not want to create a new version or emails alerts to be sent out.

So, instead of using the properties.ListItem.Update() method, use these lines of code:

base.DisableEventFiring();
properties.ListItem.SystemUpdate(false);
base.EnableEventFiring();



Or the SharePoint 2010 code equivalent:



this.EventFiringEnabled = false;
properties.ListItem.SystemUpdate(false);
this.EventFiringEnabled = true;


This will allow you to use custom code and programmatically set a default value to any SharePoint library column. Note that the same code should also work for SharePoint Lists, but this is supported through the UI or during the ItemAdding event.



For those of you who are not developers, or would rather a 3rd party solution, you can use KWizCom List Forms Extension solution version 2.1.60 or higher, where you can find a settings page that allows you to set advanced default values using this workaround.



Thanks, Shai.

Tuesday, February 22, 2011

Assembly redirection in OWSTimer

You may have read my older post on how to add BindingRedirect for assemblies into web.config in SharePoint, which allows you to change the DLL version number of your SharePoint customizations.

One thing I did not mention was, what if you need to use one of these redirected DLL’s in an SPTimerJob?

You will notice that assembly BindingRedirect does not apply to OWSTimer.exe, and it will throw errors such as “Could not load file or assembly 'AssmeblyName, Version=1.2.60.0, Culture=neutral, PublicKeyToken=xxxxxxxxx' or one of its dependencies. The system cannot find the file specified”, while you alraedy have a newer version deployed.

So, how do I make OWSTimer.exe aware of the new version and have my SPTimerJobs running with the latest version every time?

First, we need to understand why this doesn’t work.

Timer jobs run from OWSTimer.exe file, which is not effected by the web.config settings, so our BindingRedirect in web.config file will not help our code to find the correct assembly when running of the OWSTimer.exe file.

Now, for the solution

The bad news are that although a WSP natively allows you to add BindingRedirect to your web.config, it does not allow the same for other config files.

So, we need to do this manually.

Not to fear though! There is a rather simple way of doing that, still within the WSP with no need to come out of the WSP deployment.

It is a little know fact, that a farm feature event handler, with the “FeatureInstalled” event will have 2 special effects:

1. It will run for each web front end server, and not only on one of them

2. It will allow sufficient access to add files to the SharePoint Root.

Just make sure you do not edit / remove any of the out of the box files!

Well, taking this into consideration makes our problem a very simple one to solve.

We just need to create a config file for OWSTimer.exe and add our BindingRedirect statements there, and that’s it – the OWSTimer will know which version of our DLL to look for.

Here is a code example on how to add or update 2 BindingRedirect nodes in that config file, the same method can apply to any other config file during WSP deployment stage:

public void UpdateOWSTimerBindingRedirect()
{
try
{
string configFile = SPUtility.GetGenericSetupPath("TEMPLATE").ToLower().Replace("\\template", "\\bin") + "\\OWSTIMER.EXE.CONFIG";
//string XMLData = System.IO.File.ReadAllText(configFile, Encoding.UTF8);
XmlDocument config = new XmlDocument();
config.Load(configFile);

//ensure assemblyBinding exists
XmlNode assemblyBinding = config.SelectSingleNode("configuration/runtime/*[local-name()='assemblyBinding' and namespace-uri()='urn:schemas-microsoft-com:asm.v1']");

if (assemblyBinding == null)
{
assemblyBinding = config.CreateNode(XmlNodeType.Element, "assemblyBinding", "urn:schemas-microsoft-com:asm.v1");
config.SelectSingleNode("configuration/runtime").AppendChild(assemblyBinding);
}

//Delete old entrees if exist
XmlElement current = assemblyBinding.FirstChild as XmlElement;
while (current != null)
{
XmlElement elmToRemove = null;
if (current.FirstChild != null)
{
var asmIdn = (current.FirstChild as XmlElement);
if (asmIdn.GetAttribute("name").ToLower().Equals("kwizcom.sharepoint.foundation") ||
asmIdn.GetAttribute("name").ToLower().Equals("kwizcom.foundation"))
elmToRemove = current;
}

current = current.NextSibling as XmlElement;

if (elmToRemove != null)
assemblyBinding.RemoveChild(elmToRemove);
}

XmlElement dependentAssembly = null;
if (dependentAssembly == null)//create it
{
dependentAssembly = config.CreateElement("dependentAssembly");
dependentAssembly.InnerXml = "<assemblyIdentity name=\"KWizCom.SharePoint.Foundation\" publicKeyToken=\"30fb4ddbec95ff8f\" culture=\"neutral\" />"+
"<bindingRedirect oldVersion=\"1.0.0.0-20.0.0.00\" newVersion=\"13.2.62.0\" />";
assemblyBinding.AppendChild(dependentAssembly);
}

dependentAssembly = null;
if (dependentAssembly == null)//create it
{
dependentAssembly = config.CreateElement("dependentAssembly");
dependentAssembly.InnerXml = "<assemblyIdentity name=\"KWizCom.Foundation\" publicKeyToken=\"30fb4ddbec95ff8f\" culture=\"neutral\" />" +
"<bindingRedirect oldVersion=\"1.0.0.0-20.0.0.00\" newVersion=\"13.2.62.0\" />";
assemblyBinding.AppendChild(dependentAssembly);
}

config.LoadXml(config.OuterXml.Replace("xmlns=\"\"",""));
config.Save(configFile);
}
catch { }
}

Simply call this method during the FeatureInstalled event and update the code with your DLL name and version number, and you are done.

Hope this helps you with file versioning on SharePoint, which can be a rather difficult task sometimes.


Thanks, Shai.

Friday, October 1, 2010

Most annoying error message in SharePoint development

How much do you hate this error message: "One of the properties of the Web Part has an incorrect format. Microsoft SharePoint Foundation cannot deserialize the Web Part. Check the format of the properties and try again."?

If you are like me, creating lots of SharePoint web parts - you must have seen this hundreds of times.

See my old post on this issue: unable to add selected web part(s)

It does not give you any useful information as to what went wrong, you cannot debug it and basically you were blind folded to resolve this issue on your own.



What if I tell you, that there is a way to get the internal error message that will let you know exactly what you did wrong in code, DWP, or anywhere else?

Well, as I have learned today - there is such a way, and it is rather simple.

First, you have to make sure your server shows complete error messages and not these nice custom error page. Simply edit your web.config (back it up first!) and change CallStack="false" to CallStack="true", and also change the to

Now, you install your web part, try to add it to a web part page and boom – this happens…
The trick is, to go to site collection settings -> Galleries -> Web parts and find your web part definition there.

Once found – click on the file name link to get a detailed error page with stack trace and everything!

Note, that you will get a complete error message the first time:



And the same limited information all other times:



So be sure to keep the error page open first time until you got all the info you need!

Strange, but at least we got something!

Monday, March 22, 2010

Feature * for list template * is not installed in this farm error

Ok,
So I got this error few times now I thought it is about time to post on it.

I always fall for the same "trap", I get this error when trying to access the list.Forms collection, which is useful for getting the display, edit or new form of a list or library.

But - if some one happened to delete, move or rename one of the default forms of your list - you will get this exception trying to get to this collection:
"Feature * for list template * is not installed in this farm error"


So, keep that in mind and call this collection in a try catch block, and feel free to display this error message in the "catch" block:
"Don't delete built-in list forms!"


What you should do it add new custom forms and redirect to them - but never touch the default ones in SharePoint Designer!

Good luck to you guys,
Shai.

Thursday, March 11, 2010

How to get SharePoint KPI value in C#

It's been a while since I had anything smart to say, and then I had to go and play with KPIs...

KPIs in SharePoint 2007 are pretty cool, they allow you to work in several ways basically: Either you enter the KPI info manually, or you connect it to some sort of a data source like a list, excel file or other providers.

in the end you find your self with a nice list item that has some KPI fields to hold the goal, warning and value levels (in C#: double).

the problem is that when you try to access these fields in code you find that only the manual KPI actually holds the information for you to display:

value = (double)item[list.Fields.GetFieldByInternalName(MobileConstants.KPIHelper.Field_Value).Id];
goal = (double)item[list.Fields.GetFieldByInternalName(MobileConstants.KPIHelper.Field_Goal).Id];
warning = (double)item[list.Fields.GetFieldByInternalName(MobileConstants.KPIHelper.Field_Warning).Id];


Meaning, if you have a KPI that loads from a SharePoint list or an excel file... don't hope to get anything from these fields...

Well, after hours of diggin into SharePoint dll's, I have found the solution finally.

It appears that the entire KPI API ( :) ) is either internal or private to these DLL's, which means of course - reflection!

So I dug in and found some cool examples in MS code that loads KPIs and display them, which in turn exposed some key classes and methods that would save the day.

First one - is the KpiFactory. This guy will in turn get us a Kpi object based on a KPI list item (yes, simple SPList.Item object).

Once we got the Kpi - it is safe to call GetKpiData with no parametes to get another object named KpiData, which finally holds a Value, Goal and Warning properties!

So the final code should look like so:
Assembly asm = System.Reflection.Assembly.Load("Microsoft.SharePoint.Portal, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c");
Type t = asm.GetType("Microsoft.SharePoint.Portal.WebControls.KpiFactory");
MethodInfo mi = t.GetMethod("GetKpi", BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static, null, new Type[] { item.GetType() }, null);
object kpi = mi.Invoke(null, new object[] { item });
mi = kpi.GetType().GetMethod("GetKpiData", BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance, null, new Type[] { }, null);
object kpidata = mi.Invoke(kpi, new object[] { });

value = (double)ReflectionUtility.GetPropertyValue(kpidata, "Value");//double
warning = (double)ReflectionUtility.GetPropertyValue(kpidata, "Warning");//double
goal = (double)ReflectionUtility.GetPropertyValue(kpidata, "Goal");//double


Of course there are some unclear parameters that have to do with filter and other things, I will update as soon as i figure out what they are for... no idea for now - but at least our mobile solution will have nice KPI's to show after all!

Friday, January 15, 2010

Encode in JS Decode in C# problem

Just a quick one guys,

Recently we had a customer who complained about French tags that are not working with our tagging feature (field type, tag cloud for SP 2007 - pretty cool actually).

Well, apperantly we were doing some encoding of text in javascript using "escape()" and trying to decode it in C# on the server side using UrlDecode with no luck.

the text: "de l'entité" was not decoding correctly.

After trying to figure our whats wrong on the server side decoding function (I tried several alternatives) I almost gave up with no success whatsoever :(

So, I turned to look for alternatives in the Javascript encoding and found this new method called "encodeURI()". Apperantly this method can safely replace our "escape/unescape" in Javascript only it supports proper encoding and decoding with C# with no problems.

Works like a charm - from now on, use "encodeURI/decodeURI"!

Cheers, Shai.

Tuesday, September 15, 2009

Performance issue using SPUtility.GetFullNameFromLogin

Recently we had a strange support call regarding our rating solution.

The customer experienced very bad performance using our rating field type, when opening the comments page for an item.

everything else was working very fast, pages loaded within a blink of an eye, only opening the popup that displays the comments and ratings from other users took anywhere from 10 to 40 seconds...

So, we created a debug version that prints out the time stamp of every stage of the page.
What the page does is basically, load all comments and ratings into a list and binds them into a repeater control.

So, we found out that while loading the comments we get the user login name, and since we want to show the user display name we use this SharePoint API method to get the display name from the login name:
SPUtility.GetFullNameFromLogin(site, "domain\\user_login");

well, to keep it simple, calling the method above to get the user display name took 8 seconds every time we called it, so for every comment on the item the page load time would take 8 more seconds!!!

The customer opened a support call to microsoft while we were investigating other alternatives for getting the user display name.

The answer the customer got from microsoft team was simple and did resolve the issue.
Simply instead of using SPUtility.GetFullNameFromLogin, they recommend using:

web.EnsureUser("domain\\user_login").Name;

so, the web.EnsureUser method works much faster and the performance problem was resolved!

Since I did not see any post about this on the web I thought I might as well write one myself - hope this helps some of you in the future.

Shai Petel.

Tuesday, September 8, 2009

Tired of "Obsolete" warnings on your project?

If you are like me, find yourself marking old code bits as "Obsolete" but have to keep them in your project for backward compatibility - you must be tired of all the visual studio warnings regarding using obsolete code - especially if it is your own code.

In most cases I have an "Upgrade" method that identifies old version installations and upgrade them to newer version constructs, but in this upgrade code I have to use some of the old obsolete code generating these annoying warning.

BUT - I do not wish to disable all Obsolete warnings - some are very important and I do wish VS to keep warning me about them.

So, the proper way to go is to mark specific code bits not to throw any warnings about using obsolete code. Like: telling the VS that within a specific code block I am aware that I am using some obsolete code and I don't want it to show in my build results.

I came accross the simple solution here that saved me a lot of time figuring out the correct pragma statement for this:
http://stackoverflow.com/questions/344630/ignore-obsoleteattribute-compiler-error

Here, Nick Bolton simple says:
"
What about using #pragma to disable the warning around the specfic code?
#pragma warning disable 0612
// Call obsolete type/enum member here
#pragma warning restore 0612
"


And guess what? It did the trick for me!

Tuesday, July 21, 2009

How to register safe control to web.config manually

Here is a little code example I found at MSDN that allows you to add a safe control to the web application web.config file throughout the SharePoint farm.

It uses the SPWebConfigModification class to add the change and will, make sure your changes gets re-applied whenever a new web application is created in the farm!

Note, you can add all sorts of modifications using this class - not only safe controls.
As Daniel Larson wrote in the comments for the MSDN article, for web controls - if possible you should use other methods of deployment and update the safe control in the manifest xml file.

taken from: msdn SPWebConfigModification

SPWebService myService = SPWebService.ContentService;

SPWebConfigModification myModification = new SPWebConfigModification();
myModification.Path = "configuration/SharePoint/SafeControls";
myModification.Name = "SafeControl[@Assembly='MyCustomAssembly'][@Namespace='MyCustomNamespace'][@TypeName='*'][@Safe='True']";
myModification.Sequence = 0;
myModification.Owner = WebConfigModificationFeatureReceiver.OwnerId;
myModification.Type = SPWebConfigModification.SPWebConfigModificationType.EnsureChildNode;
myModification.Value = "";

myService.WebConfigModifications.Add(myModification);
myService.Update();
myService.ApplyWebConfigModifications();


I found that usefull when a customer of ours wanted to use a custom field inside a publishing page layout... the original installer did not add the safe control since you dont need it when you are inside list item form (create/edit/view), but inside a publishing page layout we got a message saying the control is not registered as safe.

Hope you do to, Shai.

Thursday, May 14, 2009

Creating a field in a List and getting the error: "Index was outside the bounds of the array"

Today, while I was code reviewing a new release of our rating field type I got a weird error message...

We added a new feature that adds 2 read only fields with the total number of ratings and number of comments.

Code example for addnig new fields was simple:
if (!list.Fields.ContainsField(ratingField.FieldName_NumOfCommentsInternalName))
{
list.Fields.Add(ratingField.FieldName_NumOfCommentsDecodedName, SPFieldType.Number, false);
//Some more logic here
}


Pretty simple, only when this code runs this time (3rd unit testing sessions before release to customer review) suddenly I get the error message above "Index was outside the bounds of the array".

Well... I ran it in debug few time, scratching my head few times more, but nothing - same error every time!

OK, time for drilling into SharePoint code I guess...
The exception stack trace was as following:
at Microsoft.SharePoint.Utilities.SPStringUtility.EscapedCodeToLower(String str)
at Microsoft.SharePoint.SPFieldCollection.FixFieldV3Attrs(XmlDocument xd, XmlElement xe, Guid& fid, String& name)
at Microsoft.SharePoint.SPFieldCollection.TranslateFieldSchema(String schemaXml, Guid& fid, String& strDisp, String& strStaticNew)
at Microsoft.SharePoint.SPFieldCollection.AddFieldAsXmlInternal(String schemaXml, Boolean addToDefaultView, SPAddFieldOptions op)
at Microsoft.SharePoint.SPFieldCollection.AddFieldAsXml(String schemaXml, Boolean addToDefaultView, SPAddFieldOptions op)
at Microsoft.SharePoint.SPFieldCollection.AddFieldAsXml(String strXml)
at Microsoft.SharePoint.SPFieldCollection.AddInternal(String strDisplayName, SPFieldType type, Boolean bRequired, Boolean bCompactName, Guid lookupListId, Guid lookupWebId, StringCollection choices)
at Microsoft.SharePoint.SPFieldCollection.Add(String strDisplayName, SPFieldType type, Boolean bRequired, Boolean bCompactName, StringCollection choices)
at Microsoft.SharePoint.SPFieldCollection.Add(String strDisplayName, SPFieldType type, Boolean bRequired)
at KWizCom.SharePoint.Rating.ListRatingUtilities.CreateRatingCountFieldsInList(SPWeb web, SPList list, RatingField ratingField)


Revealing that the exception was raised from a method called:
Microsoft.SharePoint.Utilities.SPStringUtility.EscapedCodeToLower(String str)

So far - makes sense... string parsing is always the number one cause for index bound exceptions, right?

So, I wanted to check what was wrong with the field name I was trying to create...
ratingField.FieldName_NumOfCommentsDecodedName "2Rating Site Column_x" string


See that my field name ends with "_x"...

The code I extracted from "EscapedCodeToLower" had the following line in it:
if (((str[i] == '_') && (i < (str.Length - 1))) && (str[i + 1] == 'x'))


Well, now everything was clear to me.

Microsoft uses "XmlConvert.EncodeName" in order to encode fields internal names in SharePoint. This encoding method replaces illegal characters with "_x0020_" for example. Apparently this method looks up every "_" that was followed by "x" and assumes it is a token it needs to decode... But what happens if my text ends with "_x"?

Aha! You get your "Index was outside the bounds of the array" exception!

So, I guess now I have to add a check to my code. I Doubt anyone would notice it or bother to fix it.

Well, I googled that error message regarding SPList.Fields.Add and got nothing - hopefully this will help someone in need in the future...

Shai.

Monday, October 6, 2008

Error using web part connection in list item form

Ever go this error when using a connection?
"Web Part Error: This page has exceeded its data fetch limit for connected Web Parts. Try disconnecting one or more Web Parts to correct the problem."

Well, it is known that the filter connections sometimes invoke this, and there are ways to fix it... but - I just found a "fluke" in the SharePoint connections infrastructure that is not caused by bad-coding at all.

As a matter of fact - I can reproduce it using only out of the box web parts and no custom code at all!

We wanted to have in our customers list, when a user go in to the display form of a customer, to get a list of contacts associated with this customer from the contact list.

So - we added the List View web part of Contacts list into the DispForm.aspx of the Customer list.

Than we used Custom Item Property (more information on www.kwizcom.com) that gets the current customer's title and sends it as a filter to the List View web part...

Sounds simple, only to find out that using List View web part on a display form, with a filter connection - is apparently (and unspoken) not supported!!!

It will always give an error "Web Part Error: This page has exceeded its data fetch limit for connected Web Parts. Try disconnecting one or more Web Parts to correct the problem.".

This has nothing to do with my code ( I found out couple of hours later ) since if I try it using the Query string filter web part - I get the same result every time.

Ok, after a while, I started looking for other creative solutions and found a work-around for this issue.

Converting the List View web part to a Data View web part will solve this issue and will allow you to add a filtered view of related information!!!

(Sorry for the !!!, just excited that it is not my code that caused the issue :) )

(* hint: to convert list view into data view - open the page in SharePoint Designer, right click on the list view and select the "convert to data view" option... )

Well, hope this helps save some lives, keep me posted if you have additions / questions,

Shai Petel,
VP Professional Services
KWizCom

Tuesday, September 30, 2008

Export SharePoint list to excel using DataGrid

From time to time I learn that the OOTB export to excel capabilities of SharePoint are missing some edge.

For instance - the latest thing my colleague found is that if you do not have excel 2007 but earlier versions, it does not export some column types correctly, or are completely missing.

So, I have created a simple ASP.NET web part that will allow the user to export the list items / list view into excel, that does not require a specific version of excel to be installed on the client.

The concept is simple:
Connect to a list / view,
Get items as DataTable
Connect DataTable to a DataGrid
Export DataGrid to excel.

So, to get the list items / view items into a Data Table, I used one of these code samples:
For list URL + view ID:
using (SPSite site = new SPSite(txtListUrl.Text))
{
  using (SPWeb web = site.OpenWeb())
  {
    SPList list = web.GetListFromUrl(txtListUrl.Text);
    DataTable items = list.GetItems(list.Views[new Guid(cmbListView.SelectedValue)]).GetDataTable();

    ExportToExcel(items);
  }
}
Or, if you plan to place the WP in the list view page - here it is working with the SPContext.Current.List:
SPList list = SPContext.Current.List;
DataTable items = list.Items.GetDataTable();

ExportToExcel(items);

Ok, so now we have the DataTable, all we have to do is implement the code that will export the DataTable to excel using Data Grid:
private void ExportToExcel(DataTable items)
{
  theGrid = new DataGrid();
  theGrid.DataSource = items;
  theGrid.DataBind();

  Page.Response.Clear();
  Page.Response.ContentType = "application/vnd.ms-excel";
  Page.Response.Charset = "utf-8";
  Page.Response.AddHeader("Content-Disposition", "attachment;filename="+DateTime.Now.ToString("yyyyMMdd_hhmmss")+".xls");

  System.IO.StringWriter oStringWriter = new System.IO.StringWriter();
  System.Web.UI.HtmlTextWriter oHtmlTextWriter = new   System.Web.UI.HtmlTextWriter(oStringWriter);
  theGrid.RenderControl(oHtmlTextWriter);

  Page.Response.Write(oStringWriter.ToString());
  Page.Response.End();
}

And guess what? It is that simple!

Enjoy,

Shai Petel
KWizCom VP Professional Services

Friday, June 27, 2008

CRM 4 Web Service 401: Unauthorized error

Hi all,

Been upgrading some of our CRM 3.0 components to CRM 4.0 version, just to find out a lot was changed in the web services access.

For one, now that CRM supports hosted mode, more than one organization could be configured so now you have to set up the organization you are working on with your web services serquests.

This did not took me long to find out - just call the CrmDiscoveryService, create a new RetrieveOrganizationsRequest request, and execute it.
Code:
crmDisco.CrmDiscoveryService ds = new KWizCom.CRM.Utility.crmDisco.CrmDiscoveryService();
ds.Url = CrmWebServicesUrl + "AD/CrmDiscoveryService.asmx";
ds.PreAuthenticate = true;
ds.UseDefaultCredentials = true;
//create request
crmDisco.RetrieveOrganizationsRequest r = new KWizCom.CRM.Utility.crmDisco.RetrieveOrganizationsRequest();
crmDisco.RetrieveOrganizationsResponse rr = (crmDisco.RetrieveOrganizationsResponse)ds.Execute(r);
SetOrganization(rr.OrganizationDetails[0]);


And now that you got your organization (first one in the array), you need to use it when calling all other web service (example: creating CrmService and MetadataService
Code:
crmMeta.CrmAuthenticationToken token = new crmMeta.CrmAuthenticationToken();
token.OrganizationName = this.Organization.OrganizationName;
token.AuthenticationType = Enums.AuthenticationType.AD;

this.metadataService = new crmMeta.MetadataService();
this.metadataService.Url = this.Organization.CrmMetadataServiceUrl;
this.metadataService.CrmAuthenticationTokenValue = token;
this.metadataService.PreAuthenticate = true;
this.metadataService.UseDefaultCredentials = true;

crm.CrmAuthenticationToken token2 = new crm.CrmAuthenticationToken();
token2.OrganizationName = this.Organization.OrganizationName;
token2.AuthenticationType = Enums.AuthenticationType.AD;

this.crmService = new crm.CrmService();
this.crmService.Url = this.Organization.CrmServiceUrl;
this.crmService.CrmAuthenticationTokenValue = token2;
this.crmService.PreAuthenticate = true;
this.crmService.UseDefaultCredentials = true;


Now you are all set for using the web services Under The Current User Account!!!.

What if, like me, you need to call the web service using a user name and password?

The logical to do is impersonation in web services 101:
Create a NetworkCredential Object, and set it to your crmService.Credentials,
like so:
ICredentials WebServiceCredentials = new System.Net.NetworkCredential(userName,password,domain);
this.crmService.UseDefaultCredentials = false;
this.crmService.Credentials = WebServiceCredentials;


Right? Wrong!

CRM web service will return 401 error (unauthorized access) every time you do it.

The solution for this was found in a great msdn post:
http://msdn.microsoft.com/en-us/library/cc151049.aspx

The I found that the web service credentials must not be changed at all.
What needs to be done it to make the request under a user with permissions to the CRM (like administrator or something), and pass the user name and password in the request itself.

For getting the organization, you will need to modify your code like this:
crmDisco.CrmDiscoveryService ds = new KWizCom.CRM.Utility.crmDisco.CrmDiscoveryService();
ds.Url = CrmWebServicesUrl + "AD/CrmDiscoveryService.asmx";
ds.PreAuthenticate = true;
ds.UseDefaultCredentials = true;
//create request
crmDisco.RetrieveOrganizationsRequest r = new KWizCom.CRM.Utility.crmDisco.RetrieveOrganizationsRequest();
if (!string.IsNullOrEmpty(UserName) && !string.IsNullOrEmpty(Password))
{
r.UserId = this.Domain + "\\" + this.UserName;
r.Password = this.Password;
}

crmDisco.RetrieveOrganizationsResponse rr = (crmDisco.RetrieveOrganizationsResponse)ds.Execute(r);

SetOrganization(rr.OrganizationDetails[0]);


And also - you will have to get a ticket for the other web services authentication:
RetrieveCrmTicketRequest ticketRequest = new RetrieveCrmTicketRequest();
ticketRequest.OrganizationName = Organization.OrganizationName;
ticketRequest.UserId = this.Domain + "\\" + this.UserName;
ticketRequest.Password = this.Password;
ticketResponse = (RetrieveCrmTicketResponse)ds.Execute(ticketRequest);


Using the response later when you configure your other web services like so:

crmMeta.CrmAuthenticationToken token = new crmMeta.CrmAuthenticationToken();
token.OrganizationName = this.Organization.OrganizationName;
token.AuthenticationType = Enums.AuthenticationType.AD;
if( ticketResponse != null )
token.CrmTicket = ticketResponse.CrmTicket;

this.metadataService = new crmMeta.MetadataService();
this.metadataService.Url = this.Organization.CrmMetadataServiceUrl;
this.metadataService.CrmAuthenticationTokenValue = token;
this.metadataService.PreAuthenticate = true;
this.metadataService.UseDefaultCredentials = true;

crm.CrmAuthenticationToken token2 = new crm.CrmAuthenticationToken();
token2.OrganizationName = this.Organization.OrganizationName;
token2.AuthenticationType = Enums.AuthenticationType.AD;
if (ticketResponse != null)
token2.CrmTicket = ticketResponse.CrmTicket;

this.crmService = new crm.CrmService();
this.crmService.Url = this.Organization.CrmServiceUrl;
this.crmService.CrmAuthenticationTokenValue = token2;
this.crmService.PreAuthenticate = true;
this.crmService.UseDefaultCredentials = true;


Now, you should be able to call the web services with impersonation to other CRM accounts...

Hope this helps you, I sure got stuck on this for a while.

Thanks, Shai.