Social Icons

twitter google plus linkedin rss feed

Pages

Showing posts with label SharePoint 2010. Show all posts
Showing posts with label SharePoint 2010. Show all posts

11.6.15

Migrating SharePoint Users to a New Domain

I have been dreading this type of migration for years... so many years that I already had planned a couple of ways of solving the issue. It finally happened.

Scenario:

Someone decides we need to change the farm from one environment to a new one completely different with a new AD and in a new city.

Well, let's get to it. We have created a new SharePoint farm in the new environment and we have backed up and restored the content databases. We have manually changed the admin of the site collection to the new admin in the new AD in the new farm and we can access the site and see the data. Fantastic.

Fantastic?

The users in the list items are the users from the old farm. And we have several lists with a lot of user fields. And some of our lists have tens or hundreds of thousands of rows. Changing them manually is not an option.

First idea: Go refined and try stsadm -o migrateuser:

Ohh so easy... we change the login name of the user to something else and we are good because the user IDs are still the same... NO.

This is a new domain and we don't have access to the old domain users so the migrateuser parameter throws a nice User not found error.

Second idea: Go berserk and change the strings in the list items

And that worked. Oh the beauty of a simple idea. The process is pure brute force... beautiful in its barbarity... If you have read up to here you are probably desperate for a solution.

Step One:
Get all of the users from the old farm in an XML file or something really high tech (a csv could work too).

static void Main(string[] args)
{
    using (SPSite site = new SPSite(args[0]))
    {
        using (SPWeb web = site.OpenWeb())
        {
            XElement users = new XElement("Users");

            foreach (SPUser user in web.SiteUsers)
            {
                XElement xmlUser = new XElement("User");
                xmlUser.Add(new XAttribute("Name", user.Name));
                xmlUser.Add(new XAttribute("LoginName", user.LoginName));
                xmlUser.Add(new XAttribute("ID", user.ID));

                users.Add(xmlUser);
            }

            users.Save("SiteUsers.xml");
        }
   
    }

    Console.WriteLine("\nProcess finished...");
    Console.ReadLine();
}


Step Two:
Make sure all the users you need are in the new AD. As you have a list in XML you can pass it to someone with privileges in the AD.

Step Three:
Ensure the users in SharePoint, add them to a group with reading permissions and then iterate through all the items in the list changing the users from the old domain to the users in the new one.

static void Main(string[] args)
{
    XElement users = XElement.Load("SiteUsers.xml");
    string newDomain = "XXXXXXXX";

    string ListName = string.Empty;
    if (args.Length == 2) ListName = "Stratex Framework";
    else ListName = args[2];

    //Args are SiteUrl VisitorsGroup ListName
    using (SPSite site = new SPSite(args[0]))
    {
        using (SPWeb web = site.OpenWeb())
        {
            SPList listToUpdate = web.Lists[ListName];

            Dictionary<string, SPUser> NewUsers = new Dictionary<string, SPUser>();

            foreach (XElement user in users.Descendants("User"))
            {
                string LoginName = FixDomain(user.Attribute("LoginName").Value, newDomain);
                SPUser spUser = null;
                try
                {
                    //We try to ensure all the users from the XML file. We'll probably need them
                    spUser = web.EnsureUser(LoginName);
                }
                catch
                { Logger.WriteLine("The user {0} could not be found.", LoginName); }

                if (spUser != null)
                {
                    SPGroup viewers = web.Groups[args[1]];

                    viewers.AddUser(spUser);
                    //Finally we add them to a group with read permissions
                    //We can worry about restricting this further after the migration

                    if (!NewUsers.ContainsKey(LoginName)) NewUsers.Add(user.Attribute("ID").Value, spUser);
                }
            }

            web.Update();


            UpdateUsersInList(listToUpdate, NewUsers);
        }

    }

    Logger.WriteLine("\nProcess finished...");
    Console.ReadLine();
}

private static void UpdateUsersInList(SPList list, Dictionary<string, SPUser> NewUsers)
{
    int itemsInList = list.ItemCount;
    Logger.WriteLine("Updating users at {0}. {1} items.", list.Title, itemsInList.ToString());

    SPQuery qry = new SPQuery();
    qry.ViewAttributes = "Scope=\"RecursiveAll\"";
    SPListItemCollection allItems = list.GetItems(qry);
    int count = 0;

    UpdateCount(count++, itemsInList);

    foreach (SPListItem item in allItems)
    {
        try
        {
            bool changed = false;
            SPFieldCollection allFields;
            //If the item has content type it has usualy less fields
            if (item.ContentType == null)
                allFields = item.Fields;
            else
                allFields = item.ContentType.Fields;

            foreach (SPField field in allFields)
            {
                if (field is SPFieldUser)
                    changed = ChangeUserToNewDomain(item, field, NewUsers) || changed;
            }

            changed = ChangeUserToNewDomain(item, item.Fields.GetFieldByInternalName("Author"), NewUsers) || changed;
            changed = ChangeUserToNewDomain(item, item.Fields.GetFieldByInternalName("Editor"), NewUsers) || changed;

            if (changed) item.SystemUpdate(false); //if the item has not been changed we won't update it to save time
        }
        catch (Exception ex) { Logger.WriteLine("Failed to update item {0}. Exception {1}", item.Title, ex.Message); }

        UpdateCount(count++, itemsInList);
    }

    UpdateCount(count++, 0);
}

private static bool ChangeUserToNewDomain(SPListItem item, SPField field, Dictionary<string, SPUser> NewUsers)
{
    bool changed = false;
    string fieldContent = item[field.InternalName] == null ? null : item[field.InternalName].ToString();

    if (string.IsNullOrEmpty(fieldContent)) return false;

    List<string> oldUserIds = GetUserIDs(fieldContent.Split(new string[] { ";#" }, StringSplitOptions.RemoveEmptyEntries));

    if (oldUserIds.Count == 1)
    {   //The field has only one user in it
        SPFieldUserValue foundUser = FindUser(NewUsers, oldUserIds[0]);

        if (foundUser != null)
        {
            item[field.InternalName] = foundUser;
            changed = true;
        }
    }
    else if (oldUserIds.Count > 1)
    {   //The field has several users in it
        SPFieldUserValueCollection usersInField = new SPFieldUserValueCollection();
        foreach (string oldUser in oldUserIds)
        {
            SPFieldUserValue foundUser = FindUser(NewUsers, oldUser);

            if (foundUser != null)
                usersInField.Add(foundUser);
        }

        if (usersInField.Count > 0)
        {
            item[field.InternalName] = usersInField;
            changed = true;
        }
    }
            
            
    return changed;
}

private static List<string> GetUserIDs(string[] UserTokens)
{   //We do not care about the login name. The ID is gold
    List<string> result = new List<string>();

    if (UserTokens.Length > 0)
    {
        for (int i = 0; i < UserTokens.Length; i++)
        {
            int id;

            if (i % 2 == 0 && int.TryParse(UserTokens[i], out id))
                result.Add(id.ToString());
        }
    }

    return result;
}

private static SPFieldUserValue FindUser(Dictionary<string, SPUser> NewUsers, string oldUser)
{
    SPUser foundUser = null;

    if (NewUsers.ContainsKey(oldUser)) foundUser = NewUsers[oldUser];
    else
    {
        //If we can't find the ID of the user we will still try with the login or even with the Display Name
        foreach (SPUser newUser in NewUsers.Values)
        {
            if (newUser.Name == oldUser || newUser.LoginName == oldUser) { foundUser = newUser; break; }
        }
    }

    if (foundUser != null)
        return new SPFieldUserValue(foundUser.ParentWeb, foundUser.ID, foundUser.Name);
    else
        return null;
}

private static string FixDomain(string loginName, string newDomain)
{
    //Here we change the users from XXXXX\\User to YYYYY\\User
    //The source domain was claim based
    if (loginName.Contains("|")) loginName = loginName.Split('|')[1];

    string[] tokens = loginName.Split('\\');

    tokens[0] = newDomain;

    return string.Join("\\", tokens);
}

private static void UpdateCount(int currentItem, int itemsInList)
{
    int percentage;

    if (currentItem == 0) percentage = 0;
    else if (itemsInList == 0) percentage = 100;
    else
    {
        //We will only change the value every 10 times to make the process faster.
        if (currentItem % 10 != 0) return;
        percentage = currentItem * 100 / itemsInList;
    }
    Console.Write("\r");
    if (percentage >= 0 && percentage < 10)
        Console.Write("  ");
    else if (percentage >= 10 && percentage < 100)
        Console.Write(" ");

    Console.Write("{0}%", percentage);
}

This is a first prototype that has worked as expected but it's not fully tested (by far) if you need it you can use it as a base to develop your own tool.

The one who possesses the strings has the power.

No comments:

Post a Comment

11.2.15

No more recursive functions to define CAML Queries thanks to Camlex

Some times you have a random number of conditions to check in a CAML query and in those cases I used to define the queries using a random recursive function that I usually have to debug a few times works perfectly on the first go.

The code for those queries would be something like this (and this is a simple one):

public List<string> GetSomeInfo(string fieldsToSearch, string contentTypesToSearch)
{
    ...

    var queryval = string.Empty;
    if (contentTypesToSearch.IsNullOrEmpty())
        queryval = string.Format("<Where>" + GenerateFieldsQuery(fieldsToSearch.Split(','), 0) + "</Where>", text);
    else
        queryval = string.Format("<Where><And>" + GenerateCTypesQuery(contentTypesToSearch.Split(','), 0) + GenerateFieldsQuery(fieldsToSearch.Split(','), 0) + "</And></Where>", text);

    var scope = "Scope=\"RecursiveAll\"";

    ...
}

private static string GenerateFieldsQuery(string[] fields, int index)
{
    if (fields.Length == 0) return string.Empty;

    if (fields.Length == index + 1)
        return "<Contains><FieldRef Name='" + fields[index] + "' /><Value Type='Text'>{0}</Value></Contains>";

    return "<Or><Contains><FieldRef Name='" + fields[index] + "' /><Value Type='Text'>{0}</Value></Contains>" + GenerateFieldsQuery(fields, ++index) + "</Or>";
}

private static string GenerateCTypesQuery(string[] cTypes, int index)
{
    if (cTypes.Length == 0) return string.Empty;

    if (cTypes.Length == index + 1)
        return "<Eq><FieldRef Name='ContentType' /><Value Type='Choice'>" + cTypes[index] + "</Value></Eq>";

    return "<Or><Eq><FieldRef Name='ContentType' /><Value Type='Choice'>" + cTypes[index] + "</Value></Eq>" + GenerateCTypesQuery(cTypes, ++index) + "</Or>";
}

That was until now... Thanks to Camlex (and thanks to Luis for showing it to me), that code can be written like this:

public List<string> GetSomeInfo(string fieldsToSearch, string contentTypesToSearch)
{
    ...

    var queryVal = string.Empty;
    var fieldExtensions = new List<Expression<Func<SPListItem, bool>>>();
    var cTypeExtensions = new List<Expression<Func<SPListItem, bool>>>();

    if (!contentTypesToSearch.IsNullOrEmpty())
    {
        foreach (var cType in contentTypesToSearch.Split(','))
            cTypeExtensions.Add(x => (string)x["ContentType"] == cType);
    }

    foreach (var field in fieldsToSearch.Split(','))
        fieldExtensions.Add(x => ((string)x[field]).Contains(text));

    var expressions = new List<Expression<Func<SPListItem, bool>>>();
    expressions.Add(ExpressionsHelper.CombineOr(cTypeExtensions));
    expressions.Add(ExpressionsHelper.CombineOr(fieldExtensions));

    queryVal = Camlex.Query().WhereAll(expressions).ToString();

    ...
}

I'll miss the recursive methods though... they made me feel special...

No comments:

Post a Comment

19.9.13

Get the fields available in an SPListItem

When you retrieve the items from a CAML query with the ViewFields parameters set there’s no way (or at least i don’t know it) to find out which fields you have available and populated with data.

The usual way of getting the list of fields from the content type doesn’t work because the content type is null in that kind of items…

The answer, my friend, is sitting in the Xml.

static List<string> ExcludedFields = new List<string> { "z", "ows_ServerRedirected", "ows_FileRef", "ows_PermMask", "ows_FSObjType", "ows__Level", "ows__ModerationStatus" };
/// <summary>
/// Gets a dictionary with the available fields and its values.
/// </summary>
public static Dictionary<string, string> getAvailableFields(this SPListItem item)
{
    XElement row = XElement.Parse(item.Xml);
    Dictionary<string, string> Fields = new Dictionary<string, string>();

    foreach (XAttribute field in row.Attributes())
    {
        if (!ExcludedFields.Contains(field.Name.LocalName))
            Fields.Add(field.Name.LocalName.Substring(4), field.Value);
    }

    return Fields;
}

Using this I have been able to reduce the traffic on a web service method in a 65%.

No comments:

Post a Comment

23.8.13

Scopes in a CAML Query

I have been working for quite a while now with CAML queries and the scope is always something very important to bear in mind. How many times my queries returned nothing when I was sure they should bring back something…

Basically we have two modifiers Recursive and All and nothing, could we call nothing a modifier? All will bring back folders and files. Recursive will repeat the query in all the folders under the one we are working with.

If you don’t set the scope to All it will only bring files. If you don’t set it to recursive it will only retrieve items from the folder you are at. There are not that many variants so let’s make an example of each.

Let’s imagine we have a SharePoint folder like this one and we want to query it:

CamlScopeTreeSample

I am not good at paint, I know but what I want to show here is a tree where we have a Root folder (the root of the queries) and two sub-folders with files. For each scope possible I’ll highlight what you can expect to retrieve.

Just before we start, allow me to remind you that the scope is set in the property ViewAttributes of the SPQuery item.

To add a bit more of clarity I have also painted the levels:

CamlScopeTreeSampleLevels

The green line marks what’s inside the root folder, the blue line marks the contents of SubFolder1 and the red line SubFolder2.

ViewAttributes left by default:

CamlScopeByDefault
This is just the files under the root folder.

ViewAttributes = "Scope='Recursive'"

CamlScopeRecursive

This means all the files in all the folders.

ViewAttributes = "Scope='All'"

CamlScopeAll

This scope will bring folders and files under root.

ViewAttributes = "Scope='RecursiveAll'"

CamlScopeRecursiveAll

And finally with RecursiveAll you can bring back everything under the root entity.

Good luck with your queries.

No comments:

Post a Comment

30.4.13

How Much Are you Bringing Back In Your CAML Queries?

I don't know you but I am bringing back too much.

This is one of those things you don't notice until it's too late. I thought just adding very strict filters to the CAML Queries to bring back just the items you needed was enough but there's one more thing you can do.

You can restrict the fields you are retrieving... and you should.

The SQL Queries that SharePoint generates for retrieving the items when you let the ViewFields parameter of the CAML empty almost doubles the complexity of the one it generates when you specify the fields you want to query, or use a view.

Two simple lines like these:

query.ViewFields = "<FieldRef Name=\"Value\" />";
query.ViewFieldsOnly = true;
Can make your query better.

Who could resist to do it right when it's this simple?

No comments:

Post a Comment

Disposing every SPWeb you use... Is it necessary?

I am becoming a bit paranoid about disposing lately. In example.

Until now I would have used things like Web.ParentWeb and then forgotten about them. As I was not "Opening" them manually I was not disposing them either.

Now I usually do things like this:
using (SPWeb web = anotherWeb.ParentWeb)
{
      //Do Whatever you need here with the parent web
 }
I haven't had the time to check whether this is reducing the memory leaks or just making my application more unstable (if I close the web while I am using it somewhere else it could cause an exception it the other part of the code)

Do you think when you dispose a web it automatically disposes every parent web automatically?
It could be...



I need to test this and once I do it I'll post about it.

No comments:

Post a Comment

25.4.13

customErrors mode Doesn't Work in my Server

I have been receiving the usual:

Server Error in '/' Application.

Runtime Error

Description: An application error occurred on the server. The current custom error settings for this application prevent the details of the application error from being viewed.

Details: To enable the details of this specific error message to be viewable on the local server machine, please create a <customErrors> tag within a "web.config" configuration file located in the root directory of the current web application. This <customErrors> tag should then have its "mode" attribute set to "RemoteOnly". To enable the details to be viewable on remote machines, please set "mode" to "Off".


<!-- Web.Config Configuration File -->

<configuration>
    <system.web>
        <customErrors mode="RemoteOnly"/>
    </system.web>
</configuration>


Good, I now how to solve this one, it's easy, you just have to go to C:\inetpub\wwwroot\wss\VirtualDirectories\80 and change the customErrors mode in the web.confing

Well it didn't work.

After trying a thousand things I found out that there's a more obscure web.config you need to change in SharePoint 2010, it's at c:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\TEMPLATE\LAYOUTS\

After changing the set there everything worked as expected.

It's funny it only happens in some servers I suppose it's one of the reasons why SharePoint is so funny.


No comments:

Post a Comment

22.4.13

Editing SPListItems from SPWeb.GetSiteData

In my solution I have an unknown number of subsites that contain an unknown number of SPListItems that need to be updated. Looks like the perfect way of testing GetSiteData.

The documentation is useful and you'll be able to test the queries without problems really? yes, the documentation is ok but this method brings back a DataTable and what I needed was to update the SPListItems.

Well if you look at the DataTable you'll see that you have all the fields you need there to retrieve the SPListItem easily.

I have created an extension method, well two really... I love extension methods.
public static SPListItem GetListItemFromSiteData(this DataRow ItemRow, SPSite ParentSite)
{
    using (SPWeb Web = ItemRow.GetWebSiteData(ParentSite))
    {
        return Web.Lists[new Guid(ItemRow["ListId"].ToString())].GetItemById(Convert.ToInt32(ItemRow["ID"]));
    }
}

public static SPWeb GetWebSiteData(this DataRow ItemRow, SPSite ParentSite)
{
    return ParentSite.OpenWeb(new Guid(ItemRow["WebId"].ToString()));
}

Using these two you will be able to iterate through the collection of rows of the DataTable, select which elements need to be updated and update them.

I haven't tested how fast is this compared with bringing the items with a CAML query. Not having to create the SPListItemCollection could make this method faster and more convenient for some things... I'll give it a go.

No comments:

Post a Comment

8.4.13

SharePoint List Names Localization

You can’t call a list by its internal name by default in SharePoint or I don’t know how, you have to use the display name or the GUID and that’s an issue when it comes to localization.

In order to solve this issue I have created a couple of wrappers based on the URL. The idea is simple, I use a set of constants to store the “internal names” of the lists and always retrieve them using this constants. So far it has worked fine.
/// <summary>
/// Returns null if the list is not found
/// </summary>
public static SPList GetListByInternalName(this SPListCollection Lists, string InternalName)
{
    if (string.IsNullOrEmpty(InternalName)) return null;

    Guid ListGuid = Lists.GetListIdByInternalName(InternalName);

    if (ListGuid != Guid.Empty)
        return Lists[ListGuid];

    return null;
}

/// <summary>
/// Returns the UIDC of the list
/// </summary>
static Guid GetListIdByInternalName(this SPListCollection Lists, string InternalName)
{
    if (string.IsNullOrEmpty(InternalName)) return Guid.Empty;

    foreach (SPList list in Lists)
        if (list.GetInternalName().ToLower() == InternalName.ToLower())
            return list.ID;

    return Guid.Empty;
}

/// <summary>
/// Gets the Url of the list. That's what we consider its internal name
/// </summary>
public static string GetInternalName(this SPList list)
{
    string Url = list.RootFolder.ServerRelativeUrl;

    string[] split = Url.Split('/');

    return split[split.Length - 1];
}

After this the display name of the list is not relevant any more and localization is simple or, at least, a bit simpler.

No comments:

Post a Comment

7.4.13

The 5 Most stupid (and common) errors of SharePoint developers

If you are a seasoned developer you'll probably agree, if you are not so experienced you'd have probably suffered at lest one on the last week and lost an hour before figuring out what was happening.

Updating the wrong web
You are changing something, the code works, no exception is triggered but you go to the SharePoint site and nothing has changed... Check the web you are pointing at, you might be updating the wrong one.

Wrong field name
Your CAML query is fine but you get an exception saying that the list's fields are wrong. Go to the list definition and to the field definition, there in the URL you have the field internal name as SharePoint expects it, yeah you got it wrong. I like to have them as constants somewhere, spell them one properly and let the intellisense remind you of the name for the rest of the project's life.

Wrong list
The item you are trying to create doesn't appear in the list or it complains about the field names and they are right... Check that you are connected to the right list. (I also have constants for the list names)

Wrong DLL
You change something in your code and still you get the same error. Depending on what you are doing you might need to deploy the DLL to the GAC or the BIN folder, the right DLL, and then reset the right service, sometimes it's not the IIS. Try changing a string or something very obvious to spot so you are certain the code you are running is the code you want to run.

My web services won't connect to the server
You need to create a clientaccesspolicy.xml file or a crossdomain.xml and put in in the web application's inetpub folder, so simple and yet so annoying.

[BONUS] The changes I am making do nor appear in SharePoint
Right list, right web, right fields, right everything and yet nothing happens when I change the object... You forgot to update it. And this goes for a lot of different items in SharePoint, not only list items. In doubt I always try to add a .Update after modifiying. Beware, the updates are usually slow and should be kept to a minimum.

No comments:

Post a Comment

5.4.13

SharePoint Transparent Data Encryption

SharePoint implements security at a user level, if you want to access a file and you don’t have the required permissions you will be blocked and that’s enough for the vast majority of companies, but…

- What happens if someone gains access to your database server?
+ They will be easily able to download the content database, connect it to one of their SharePoint servers and access everything even the branding.
- What can I do about it as a SharePoint developer?
+ Nothing.
- OMG! OMG!
+ Chill out SharePoint guy, if you want to encrypt the databases ask the database guy.

Yes, database administrators can encrypt whole instances of SQL using TDE. And it looks even easy although I’m happy I don’t have to do it.

- But encrypting everything will affect the performance of my farm!
+ Microsoft reckons it will be only a 3 – 5% increase.

You can get more info about this topic here and here (i.e.).

No comments:

Post a Comment

3.4.13

Sharing Data Between Web Front Ends With Thread Safe Web Properties

Remember back in the old days when you only had one frontend? Remember when you thought that was complex?

Times have changed and now for almost every production environment you have several frontends and that’s good, the performance is boosted and with SharePoint it’s simple to add one more if you feel you are falling short but this has also taken some tasks to a new level of complexity.

There is software out there like AppFabric that will allow you to persist data and share it between all your servers and they are good… but would you install and configure them everywhere just for sharing a 10 character string?

Well you don’t have to. You can use SharePoint Web Properties to do so. They are fast to write, to read, to code and you don’t need to configure anything.

SharePoint Web Properties are a bit tricky to use though, just a bit, particularly when you are using them in a multithreaded process but I hope these wrappers save you some time. (I have used them in a couple scenarios and work as expected but I can’t assure they are completely safe.)
static object PropertyWriteLock = new object();

/// <summary>
/// Sets a web property to a given value. This method is thread safe.
/// </summary>
/// <param name="Key">The Key for the web property (case insensitive)</param>
/// <param name="Value">Value to set the property to</param>
public static void SetWebProperty(this SPWeb Web, string Key, string Value)
{
    Key = Key.ToLower();
    lock (PropertyWriteLock) //It's better to have a lock just for the key we are working with. I'll post the trick maybe tomorrow.
    {
        if (GetWebPropertyThreadSafe(Web, Key) != Value)
        {
            Web.Properties[Key] = Value;

            Web.Properties.Update();
        }
    }
}

/// <summary>
/// Returns the web property with the given key. This method is thread safe.
/// </summary>
/// <param name="Key">The Key for the web property (case insensitive)</param>
/// <returns>Returns null if not found</returns>
public static string GetWebPropertyThreadSafe(this SPWeb Web, string Key)
{
    Key = Key.ToLower();
    using (SPSite site = new SPSite(Web.Site.ID))
    {
        using (SPWeb newWeb = site.OpenWeb(Web.ID))
        {
            return newWeb.GetWebProperty(Key);
        }
    }
}

/// <summary>
/// Returns the web property with the given key.
/// </summary>
/// <param name="Key">The Key for the web property (case insensitive)</param>
/// <returns>Returns null if not found</returns>
public static string GetWebProperty(this SPWeb Web, string Key)
{
    Key = Key.ToLower();

    return Web.Properties[Key];
}

/// <summary>
/// Removes the web property from the web
/// </summary>
/// <param name="Key">The Key for the web property (case insensitive)</param>
/// <remarks>The web property will remain there but set to null.</remarks>
public static void RemoveWebProperty(this SPWeb Web, string Key)
{
    if (Web.Properties.ContainsKey(Key))
        Web.Properties.Remove(Key);

    Web.Properties.Update();

    if (Web.AllProperties.ContainsKey(Key))
        Web.AllProperties.Remove(Key);

    Web.Update();
}

They have simplified some parts of my application a lot and, I’ll  say that again, they are fast. Give them a go and let me know your thoughts.

No comments:

Post a Comment

2.4.13

Iterating Lists in Small Chunks And Performance

A couple of months ago I created a post about it and in the conclusion I stated that it would probably be faster to act on the whole list by chunks than it is using the big query… It’s not.

I have done the test with a list of 100K items so you don’t have to and, depending on the size of the chunks it can take slightly longer or a lot longer.

If you are using a very small RowLimit (say 5) the number of times you have to perform the query in order to retrieve the whole list will make the process take twice as long or even longer. If you are using a bigger number (like 1000) the performance will be similar to the one achieved retrieving the whole list and processing it in just one query.

In summary, if you are looking for a small number of values and they are likely to be found in the first iterations of the paginated query then the performance boost will be obvious, in some cases I have noticed an average improvement of up to a 95%, but bear in mind that if you are using very small chunks and you are likely to go through the whole list to get the values back your query will be slower, up to an 400% slower or even more.

No comments:

Post a Comment

4.2.13

Iterating Big SharePoint Lists In Small Chunks

Why would anyone want to iterate SharePoint lists in small chunks? because of two main reasons, the first one is because if the results of one of your queries is too big (say 2000 items) it gets really slow to work with it and second because you might just need the top 20 items that fulfil a condition and that condition is too complex to be represented in the first CAML query.

I am using query pagination to do it and it’s about 75% faster than processing all of the items in the first query (this depends on the amount of data you have in the list)

It basically goes as follows, you set the RowLimit to a sensible number (you also want to keep the number of round trips to a minimum) then you query the SharePoint List until the number of items that fulfil the condition hits the target or you run out of items. (I still haven’t tested if it’s faster to go through every item in a big list using this method or retrieving all at once but something tells me this will be faster)

/// <summary>
/// Returns the top MaxResults items in the query
/// </summary>
internal static List<SPListItem> GetTopItems(SPWeb web, uint MaxResults = 0)
{
    SPList ListToQuery = web.Lists.GetListByInternalName("ListInternalName");


    List<SPListItem> result = new List<SPListItem>();

    SPQuery query = new SPQuery();
    query.Query = string.Format(@"<Where><Eq><FieldRef Name='State' /><Value Type='Choice'>Live</Value></Eq></Where>");
    query.ViewAttributes = "Scope='RecursiveAll'";
    query.RowLimit = MaxResults + 1;

    SPListItemCollection QueryResults;
    string PagingInfo = string.Empty;

    while (result.Count < MaxResults && PagingInfo != null)
    {
        //We set the beginning of the query to the last item in the previous page
        query.ListItemCollectionPosition = new SPListItemCollectionPosition(PagingInfo);

        //We get the page of items
        QueryResults = ListToQuery.GetItems(query);

        foreach (SPListItem item in QueryResults)
        {
            //We process the items and add them to the result list
            if (ComplexProcessing(item))
                result.Add(item);
        }

        //We set PagingInfo to the last item retrieved in the query
        if (QueryResults.ListItemCollectionPosition != null)
            PagingInfo = QueryResults.ListItemCollectionPosition.PagingInfo.ToStringSafe();
        else //PagingInfo will be null if we reach the end of the pagination
            PagingInfo = null;
    }

    return result;
}

This approach to paginated CAML queries have improved the performance of my site hugely, I hope it serves you as well.

No comments:

Post a Comment

3.10.12

A Thread.Sleep that does not freeze the thread

I have been trying to add unit tests to my SharePoint code and it’s not an easy task. One of the main points I wanted to test was the ItemEventReceivers on a list. This works if you use the synchronous events such as ItemAdding or ItemUpdating but when it comes to test if the asynchronous events have taken place as expected you need to wait a bit.

Putting the test thread to sleep prevents the asynchronous events from happening (it’s on the same thread as the SharePoint code) and, even though your code is working when you are executing it manually, all of the tests fail so I have created a new class based on timers that will let the asynchronous events to trigger and execute. I call it Waiter.
public class Waiter : IDisposable
{
    public enum WaiterState
    {
        Waiting,
        TimedOut,
        Success,
        Error
    };

    System.Timers.Timer WaitTimer;
    ManualResetEvent manualResetEvent;
    int WaitCounter;

    private Waiter(int interval)
    {
        WaitCounter = 0;

        manualResetEvent = new ManualResetEvent(true);
        WaitTimer = new System.Timers.Timer() { AutoReset = false, Interval = interval };
        WaitTimer.Elapsed += new ElapsedEventHandler(WaitTimer_Elapsed);
    }

    void WaitTimer_Elapsed(object sender, ElapsedEventArgs e)
    {
        WaitCounter++;
        manualResetEvent.Set();
    }

    /// 
    /// Waits for the interval in milliseconds times number of times or once by default.
    /// 
    public static WaiterState Wait(int interval, int times)
    {
        try
        {
            using (Waiter WaiterClass = new Waiter(interval))
            {
                while (WaiterClass.WaitCounter <= times)
                {
                    WaiterClass.WaitTimer.Start();
                    WaiterClass.manualResetEvent.WaitOne();
                }
            }
        }
        catch
        {
            return WaiterState.Error;
        }

        return WaiterState.Success;
    }

    /// 
    /// Waits for the interval in milliseconds once.
    /// 
    public static WaiterState Wait(int interval)
    {
        return Wait(interval, 0);
    }

    void Dispose()
    {
        WaitTimer.Dispose();
    }
}
Give it a go.

No comments:

Post a Comment

Restarting SharePoint 2010 Timer Service Programmatically

I need to restart the Timer Service from an application and it took me some time to find the answer. It’s as easy as one could think.

Anyway here's the code:
/// 
/// Stops the SharePoint timer.
/// 
public static void TimerStop()
{
    ServiceController timerService = new ServiceController(Constants.SPTimerName);

    if (timerService.Status == ServiceControllerStatus.Running)
    {
        timerService.Stop();
        timerService.WaitForStatus(ServiceControllerStatus.Stopped, Constants.WaitingTimeout);
    }
}

/// 
/// Starts the SharePoint timer.
/// 
public static void TimerStart()
{
    ServiceController timerService = new ServiceController(Constants.SPTimerName);

    if (timerService.Status == ServiceControllerStatus.Stopped)
    {
        timerService.Start();
        timerService.WaitForStatus(ServiceControllerStatus.Running, Constants.WaitingTimeout);
    }
}
And by the way the constants are:
public static TimeSpan WaitingTimeout = new TimeSpan(0, 1, 30);
public static string SPTimerName = "SharePoint 2010 Timer";

No comments:

Post a Comment

6.9.12

Authenticating an ASMX Web Service in SharePoint from a WinRT app

Where’s the CookieContainer?

Yes mate, I’ve had that question too.

We are creating a new application for Windows 8 and we need it to connect to our web services. Connecting to the anonymous methods was easy, but when it comes to authenticating the user the situation changes.

I was pretty confident, in this context confident is an euphemism for ignorant and naive,  I could use the same approach I used when I was creating my apps for WP7 I have used it a thousand times and WP7 is more or less the same as WinRT so who would have thought it won’t work?
Well WinRT is not the same as WP7.

After creating the SPAuthBridge class I tried to add the authentication cookie to my SoapClient as usual but as you probably now if you are reading this post this line doesn’t work:

MySoapClient.CookieContainer = SharePointAuth.cookieJar;

In WinRT the SoapClient object doesn’t have a CookieContainer object so we have to find another way of adding the cookie in the calls.

The SPAuthBridge is OK. I have just added a new method to it to get the authentication cookie as string from the cookie container.
public string GetAuthenticationCookie()
{
    return string.Format("{0}={1}", "FedAuth",
            cookieJar.GetCookies(new System.Uri("https://www.mySite.com"))["FedAuth"].Value);
}

Then all you have to do is to follow the steps in the previous post to authenticate and after you have successfully authenticated to SharePoint you have to change the way you add the cookie to your SoapClient.

I have found two ways:

The quick one

Every time you need to call the web service you have to add the cookie to the header and then call the web service in the same OperationContextScope. It looks like this:
using (new OperationContextScope(MyWS.InnerChannel))
{
    HttpRequestMessageProperty httpRequestProperty = new HttpRequestMessageProperty();

    httpRequestProperty.Headers[System.Net.HttpRequestHeader.Cookie] = SharePointAuth.GetAuthenticationCookie();

    OperationContext.Current.OutgoingMessageProperties[HttpRequestMessageProperty.Name] = httpRequestProperty;

    UserInfo = await MyWS.GetInfoForUserAsync();
}

That’s easy but it’s not very readable and if you have to call a lot of web services you’ll probably get bored of this approach.

The elegant one

Instead of manually adding the cookie every time we can also create a behaviour that does it automatically.

In order to do that you need to create a CookieBehavior class. I copied 99% of mine from here and it looks like this:
class CookieBehaviour: IEndpointBehavior
{
    private string cookie;

    public CookieBehaviour(string cookie)
    {
        this.cookie = cookie;
    }

    public void AddBindingParameters(ServiceEndpoint serviceEndpoint,
        BindingParameterCollection bindingParameters) { }

    public void ApplyClientBehavior(ServiceEndpoint serviceEndpoint, ClientRuntime behavior)
    {
        behavior.ClientMessageInspectors.Add(new CookieMessageInspector(cookie));
    }

    public void ApplyDispatchBehavior(ServiceEndpoint serviceEndpoint,
        EndpointDispatcher endpointDispatcher) { }

    public void Validate(ServiceEndpoint serviceEndpoint) { }
}

public class CookieMessageInspector : IClientMessageInspector
{
    private string cookie;

    public CookieMessageInspector(string cookie)
    {
        this.cookie = cookie;
    }

    public void AfterReceiveReply(ref Message reply,
        object correlationState) { }

    public object BeforeSendRequest(ref Message request, IClientChannel channel)
    {
        HttpRequestMessageProperty httpRequestMessage;
        object httpRequestMessageObject;
        if (request.Properties.TryGetValue(HttpRequestMessageProperty.Name
            , out httpRequestMessageObject))
        {
            httpRequestMessage = httpRequestMessageObject as HttpRequestMessageProperty;
        }
        else
            httpRequestMessage = new HttpRequestMessageProperty();

        httpRequestMessage.Headers[System.Net.HttpRequestHeader.Cookie] = cookie;
        request.Properties.Add(HttpRequestMessageProperty.Name, httpRequestMessage);

        return null;
    }
}

Once you have this class the only thing you have to do is to add this behaviour to your web service on the initialization and your cookie will be added automatically every time.
public MyWSSoapClient InitializeMyWS()
{
    BasicHttpBinding Bind = new BasicHttpBinding();

    //////////////////////Transport///////////////////////
    Bind.Security.Mode = BasicHttpSecurityMode.Transport;
    //////////////////////////////////////////////////////

    /////////////////////MessageSize//////////////////////
    Bind.MaxReceivedMessageSize = Int32.MaxValue;
    //////////////////////////////////////////////////////

    EndpointAddress oAddress = new EndpointAddress(SiteUrl + "/_vti_bin/MyWS.asmx");
    MyWSSoapClient MyWS = new MyWSSoapClient(Bind, oAddress);

    CookieBehaviour AuthCookieBehaviour = new CookieBehaviour(SharePointAuth.GetAuthenticationCookie());
    MyWS.Endpoint.EndpointBehaviors.Add(AuthCookieBehaviour);

    return MyWS;
}

After you have initialized the web service like this you can call your methods without worrying about the authentication any more:
private async Task<ObservableCollection<Info>> GetInfo()
{
    GetInfoForUserResponse Information = await MyWS.GetInfoForUserAsync();

    return Information.Body.GetInfoForUserResult;
}
The transport is necessary in my case because I am calling a web service hosted in an HTTPS web application by default is set to None and I was getting an ArgumentException like this:

The provided URI scheme 'https' is invalid; expected 'http'.

And the MaxReceivedMessageSize could also be a wee bit excessive. Tune it to suit your needs but be aware that if you set it too small you’ll get a nice CommunicationException saying that:

The maximum message size quota for incoming messages (65536) has been exceeded. To increase the quota, use the MaxReceivedMessageSize property on the appropriate binding element.

Have fun with the tablet if you have one, I am still waiting for mine.

9 comments:

Post a Comment

14.2.12

Extension Methods for Null Objects

Is it possible to use an extension method on something that is null?

How beautiful would it be to do something like:
if (!MyString.IsNull()) return MyString.ToString();
I read somewhere a couple of years ago that would be impossible because if the object was null you’d not be allowed to call the method or something like that… But in my head it makes perfect sense.

So I have been refraining myself of doing this for a long time but today I felt brave enough.

The Method Extensions:
public static bool IsNull(this string str)
{
    return str == null;
}

public static string ToStringSafe(this object obj)
{
    return (obj ?? string.Empty).ToString();
}
The main method obviously every good test program should be a console application:
static void Main(string[] args)
{
    string str = null;

    if (str.IsNull())
        Console.WriteLine("It was null...");
    else
        Console.WriteLine("It wasn't null...");

    Console.WriteLine(str.ToStringSafe());

    Console.ReadKey();
}
And it works! This just opens a new dimension to my spaghetti recipes.

The first useful example that comes to my mind apart from the ToStringSafe and ToInt, ToDateTime... Look at me, I can't stop! is:

/// 
/// Disposes the object if it's not null.
/// 
public static void DisposeSafe(this IDisposable DisposableObject)
{
    if (DisposableObject != null)
        DisposableObject.Dispose();
}
With this method I won’t have to worry if the object is null or not. Everything is disposed properly!

It frightens me sometimes to get so excited about this things…

No comments:

Post a Comment

2.2.12

Extending, Overriding and Base Classes

A couple of days ago my cousin asked me about the meaning of the word override. I tried to explain it with a couple of stupid examples and both sounded dull… Well this is a real life example.

We want to create Silverlight web parts and all of them share the same basic properties and the same render method. So extending Microsoft.SharePoint.WebPartPages.WebPart we will create a BaseSilverlightWebPart with this features (overriding the render method of the WebPart class) and then we will use it to create the other web parts easily.

The base class is like this:
    public abstract class BaseSilverlightWebpart : Microsoft.SharePoint.WebPartPages.WebPart
    {
        #region Web Part Properties
        [Personalizable(PersonalizationScope.Shared)]
        [WebBrowsable(true)]
        [System.ComponentModel.Category("Stratex")]
        [WebDisplayName("XAP List URL")]
        [Description("Select the URL of the XAP list.")]
        public string XAPListUrl { get; set; }
        #endregion

        #region Private Properties
        Dictionary<string, string> InitParams;
        #endregion

        #region Abstract
        /// <summary>
        /// Setup here the of the XAP you will use
        /// </summary>
        public abstract string XAPName { get; }

        /// <summary>
        /// Setup here the initial parameters you will use in your Silverlight Web Part
        /// </summary>
        public abstract void SetUpParameters();
        #endregion

        #region Methods
        public void AddParameter(string Name, string Value)
        {
            if (InitParams == null)
                InitParams = new Dictionary<string, string>();

            if (InitParams.ContainsKey(Name))
                InitParams[Name] = Value;
            else
                InitParams.Add(Name, Value);
        }
        #endregion

        #region Overrides
        protected override void CreateChildControls()
        {
            SetUpParameters();

            if (string.IsNullOrEmpty(XAPListUrl))
                XAPListUrl = string.Format("{0}/Lists/XAPLibrary/", SPContext.Current.Web.ServerRelativeUrl);

            //Sometimes when you create the web part it's 0px by 0px... ¬ ¬
            if (string.IsNullOrEmpty(Height)) Height = "150px";
            if (string.IsNullOrEmpty(Width)) Width = "150px";

            LiteralControl obj = new LiteralControl();
            obj.Text = "<object id='silverlightHost' height='" + Height + "' width='" + Width +
                @"' data='data:application/x-silverlight-2,' type='application/x-silverlight-2' style='display:block' class='ms-dlgDisable'>
                            <param name='Source' value='" + XAPListUrl + XAPName + @"' />
                            <param name='MinRuntimeVersion' value='3.0.40624.0' />
                            <param name='Background' value='#00FFFFFF' />
                            <param name='windowless' value='true' />
                            <param name='autoUpgrade' value='true' />
                            ";

            if (InitParams.Count > 0)
            {
                obj.Text +="<param name='initParams' value='";

                int i = 0;
                foreach (var param in InitParams)
                {
                    if (i++ == 0)
                        obj.Text += string.Format("{0}={1}", param.Key, param.Value);
                    else
                        obj.Text += string.Format(", {0}={1}", param.Key, param.Value);
                }

                obj.Text += @"' />
                ";
            }
            obj.Text += @"<a href='http://go.microsoft.com/fwlink/?LinkID=149156&v=3.0.40624.0' style='text-decoration: none;'>
                <img src='http://go.microsoft.com/fwlink/?LinkId=108181' alt='Click here to install Silverlight' style='border-style: none'/>
                </a>
                </object>";

            this.Controls.Add(obj);
        }
        #endregion
    }
Extending it we can create a Silverlight Web Part as easy as:
    public class DynamicStrategyMap : BaseSilverlightWebpart
    {
        #region Overrides
        public override string XAPName
        {
            get { return "StratexPointStrategyMap.xap"; }
        }

        public override void SetUpParameters()
        {
            AddParameter("site", HttpUtility.UrlEncode(SPContext.Current.Web.ServerRelativeUrl));
        }
        #endregion
    }
Note that wou dont have to override nothing if you don't need to. The render method is exactly the same as in the base class so we don't have to name it. The class will use its base class method automatically. Look how we are saving code not writing again all the properties or the big render method from the base class here, we just change what we really need to change.
We can also add new properties if we want as in:
    public class StratexHeartBeat : BaseSilverlightWebpart
    {
        #region WebPartProperties
        [Personalizable(PersonalizationScope.Shared)]
        [WebBrowsable(true)]
        [System.ComponentModel.Category("Stratex")]
        [WebDisplayName("NumberOfEvents")]
        public string NumberOfEvents { get; set; }

        [Personalizable(PersonalizationScope.Shared)]
        [WebBrowsable(true)]
        [System.ComponentModel.Category("Stratex")]
        [WebDisplayName("TimerLapse")]
        public string TimerLapse { get; set; }

        [Personalizable(PersonalizationScope.Shared)]
        [WebBrowsable(true)]
        [System.ComponentModel.Category("Stratex")]
        [WebDisplayName("Indicator Summary Url")]
        public string IndicatorSummaryUrl { get; set; }
        #endregion

        #region Overrides

        public override string XAPName
        {
            get { return "StratexHeartBeat.xap"; }
        }

        public override void SetUpParameters()
        {
            AddParameter("site", HttpUtility.UrlEncode(SPContext.Current.Web.Url));

            if (string.IsNullOrEmpty(IndicatorSummaryUrl))
                IndicatorSummaryUrl = string.Format("{0}/Lists/WebPartPages/IndicatorSummary.aspx", SPContext.Current.Web.ServerRelativeUrl);

            AddParameter("indicatorsummaryurl", IndicatorSummaryUrl);

            if (Common.ConvertToInt(NumberOfEvents) < 1) NumberOfEvents = "1";

            AddParameter("numberofnews", NumberOfEvents);

            if (Common.ConvertToInt(TimerLapse) < 1) TimerLapse = "1";

            AddParameter("timerlapse", TimerLapse);
        }
        #endregion
    }
Or we can ever add more code to the render overriding the overrided render method.
    public class Commentary : BaseSilverlightWebpart
    {
        #region Overrides
        public override string XAPName
        {
            get { return "Commentary.xap"; }
        }

        public override void SetUpParameters()
        {
            AddParameter("site", HttpUtility.UrlEncode(SPContext.Current.Web.ServerRelativeUrl));
        }

        protected override void CreateChildControls()
        {
            base.CreateChildControls();

            //We are using this code to be able to close the explorer window from Silverlight
            LiteralControl JavaScript = new LiteralControl();

            JavaScript.Text = @"<script type='text/javascript'>
                                function doCloseLocal() 
                                {
                                    var version = parseFloat(navigator.appVersion.split('MSIE')[1]);

                                    if (version >= 7) {window.open('', '_parent', ''); }

                                    else { window.opener = self;  }

                                    window.close();
                                }
                                </script>";
            this.Controls.Add(JavaScript);
        }

        #endregion
    }
Note that before adding the new control we call the base.Render so the web part is rendered in the page.
So this is how overriding looks like in real life. Now it make sense… or does it?

No comments:

Post a Comment

12.1.12

About Windows Phone 7 and SharePoint authentication

Connecting the phone to our SharePoint environment was my Christmas project, and it took me a while... In the end I managed to do it and I thought it could be a good idea to share the joy. Besides, I have read a lot of blogs about this matter all of them? and I want to add my contribution to it.

Let's go to the matter. I created a class called SPAuthBridge from the code in the SDK that can be used to set up the connections to the SharePoint server.
using System;
using System.IO;
using System.Net;
using System.Text;

namespace PhoneUtils.Code
{
    public class SPAuthBridge
    {
        #region Properties
        public CookieContainer cookieJar = new CookieContainer();

        string SiteUrl, User;
        string Password; //This should be securestring, but I don't think it's available in WP7
        #endregion

        #region Constructors
        public SPAuthBridge(string SiteUrl, string User, string Password)
        {
            this.SiteUrl = SiteUrl;
            this.User = User;
            this.Password = Password;
        }
        #endregion

        #region Public Methods
        public void Authenticate()
        {
            try
            {
                if (string.IsNullOrEmpty(SiteUrl)) throw new ArgumentOutOfRangeException("The SPAuthBridge was not properly initialized");

                System.Uri authServiceUri = new Uri(string.Format("{0}/_vti_bin/authentication.asmx", SiteUrl));

                HttpWebRequest spAuthReq = HttpWebRequest.Create(authServiceUri) as HttpWebRequest;
                spAuthReq.CookieContainer = cookieJar;
                spAuthReq.Headers["SOAPAction"] = "http://schemas.microsoft.com/sharepoint/soap/Login";
                spAuthReq.ContentType = "text/xml; charset=utf-8";
                spAuthReq.Method = "POST";

                //add the soap message to the request
                spAuthReq.BeginGetRequestStream(new AsyncCallback(spAuthReqCallBack), spAuthReq);
            }
            catch
            {
                TriggerOnAuthenticated(false);
            }
        }
        #endregion

        #region Private Methods
        private void spAuthReqCallBack(IAsyncResult asyncResult)
        {
            string envelope =
                    @"<?xml version=""1.0"" encoding=""utf-8""?>
                    <soap:Envelope xmlns:xsi=""http://www.w3.org/2001/XMLSchema-instance"" xmlns:xsd=""http://www.w3.org/2001/XMLSchema"" xmlns:soap=""http://schemas.xmlsoap.org/soap/envelope/"">
                      <soap:Body>
                        <Login xmlns=""http://schemas.microsoft.com/sharepoint/soap/"">
                          <username>{0}</username>
                          <password>{1}</password>
                        </Login>
                      </soap:Body>
                    </soap:Envelope>";

            UTF8Encoding encoding = new UTF8Encoding();
            HttpWebRequest request = (HttpWebRequest)asyncResult.AsyncState;
            Stream _body = request.EndGetRequestStream(asyncResult);
            envelope = string.Format(envelope, User, Password);
            byte[] formBytes = encoding.GetBytes(envelope);

            _body.Write(formBytes, 0, formBytes.Length);
            _body.Close();

            request.BeginGetResponse(new AsyncCallback(ResponseCallback), request);
        }

        private void ResponseCallback(IAsyncResult asyncResult)
        {
            try
            {
                HttpWebRequest request = (HttpWebRequest)asyncResult.AsyncState;
                HttpWebResponse response = (HttpWebResponse)request.EndGetResponse(asyncResult);
                Stream content = response.GetResponseStream();

                if (request != null && response != null)
                {
                    if (response.StatusCode == HttpStatusCode.OK)
                    {
                        using (StreamReader reader = new StreamReader(content))
                        {
                            //Put debugging code here
                            string _responseString = reader.ReadToEnd();
                            reader.Close();
                        }
                    }
                }

                //authentication complete
                TriggerOnAuthenticated(true);
            }
            catch
            {
                TriggerOnAuthenticated(false);
            }
        }
        #endregion

        #region Events
        public delegate void OnAuthenticatedHandler(bool Success);

        public event OnAuthenticatedHandler OnAuthenticated;

        protected virtual void TriggerOnAuthenticated(bool Success)
        {
            if (OnAuthenticated != null)
                OnAuthenticated(Success);
        }
        #endregion
    }
}

The way to use this class is fairly simple, you create the SPAuthBridge object, you call the Authenticate method and you are ready to go, something like this:

        StratexWP7SoapClient StratexWP7 = new StratexWP7SoapClient(); //This is my web service
        SPAuthBridge SharePointAuth;

        public MainPage()
        {
            InitializeComponent();

            (...)

            SharePointAuth = new SPAuthBridge(SiteUrl, Username, Password);
            SharePointAuth.OnAuthenticated += new SPAuthBridge.OnAuthenticatedHandler(SharePointAuth_OnAuthenticated);

            if (!string.IsNullOrEmpty(Password))
                SharePointAuth.Authenticate();
            else
                MessageBox.Show("The application should be configured before use.");
        }

        void SharePointAuth_OnAuthenticated(bool Success)
        {
            if (!Success)
            {
                Deployment.Current.Dispatcher.BeginInvoke(() =>
                    { MessageBox.Show("There was an error on the authentication procedure. Please check the configuration."); });

                return;
            }

            StratexWP7.CookieContainer = SharePointAuth.cookieJar; //This is all you have to do to connect your web service. \m/ O.O \m/

            HookEvents();

            RequestData();
        }

It looks great when it works…

clip_image002clip_image002[4]
(By the way the charts are from AmCharts)

No comments:

Post a Comment