Tuesday, June 11, 2013

Project Server 2010 - Bug when uploading image on PDP

When adding new Enterprise Custom Fields to Project Server, there aren't many options to choose from what that field is going to be. You can set it to be text, number, duration, flag, cost and date field. You can also set that field to get data from Lookup Table or to be a formula (calculated field).

But, when you need to have a field that will contain an image, you have limited options. One way to get image on your PDP is to create Enterprise Custom Field of type "Text" and for its Custom Attributes choose "Multiple lines of text".

When you add this Custom Field to your PDP, you will have rich textbox in which you can format your text in a way you want and you have the tool (in Ribbon) to upload images to that textbox.



But, if just upload image and click on "Save" button, your image will disappear from the page and it will not be saved!

I've tried this couple of times, and every time, image is not saved.

But, when I add some text below the image (in the same textbox), then the image is saved in that textbox.

I consider this as a bug of Project Server 2010.

Thursday, May 16, 2013

SharePoint - Iterate through large list

Retrieving list items from SharePoint list from code is the most basic thing, and every SharePoint developer is familiar with it.

This is the easiest way for retrieving items of a SharePoint list from code:


using(SPSite siteColl = new SPSite("SiteName"))
{
    using(SPWeb web = siteColl.OpenWeb())

        SPList list = web.RootWeb.Lists["ListName"];
        SPListItemCollection items = list.Items;
 
        foreach (SPListItem listItem in items)
        {
            Response.Write("Item title: " + listItem["Title"].ToString());
        } 
    }
}

But, this is a very poor solution, because this row 
SPListItemCollection items = list.Items; 
returns all items of a list at once. If your list has large amount of items (for example, more then 2000), then this can be a serious performance issue.If you have extremely large list, like 1 000 000 items, this code will certainly crash your server.


The solution is to using SPQuery class and its RowLimit property like this:

using(SPSite siteColl = new SPSite("SiteName"))
{
    using(SPWeb web = siteColl.OpenWeb())

        SPList list = web.RootWeb.Lists["ListName"];
 
        SPQuery query = new SPQuery();
        query.Query = "<Query><OrderBy><FieldRef Name='ID' /></OrderBy></Query>";
        //Scope="Recursive" retrieves items from all folders and subfolders in a list
        query.ViewAttributes = "Scope=\"Recursive\"";
        query.RowLimit = 100;

        do
        {
            SPListItemCollection items = list.GetItems(query);

            foreach (SPListItem listItem in items)
            {
                Response.Write("Item title: " + listItem["Title"].ToString());
            }

            query.ListItemCollectionPosition = items.ListItemCollectionPosition;

        } while (query.ListItemCollectionPosition != null);
        
    }
}


With SPQuery, execution is faster. This query returns all fields from a list, but you can make it even faster if you define only the fields that you need, and not all of them. RowLimit property of SPQuery class ensures that only specific number of items will be processed in one iteration.

Tuesday, May 7, 2013

Project Server 2010 Workflow - AppSettings Part II

In one of my previous posts, I've shown how to read Application Settings from workflow. 

But, that isn't always the best practice. It isn't recommended for users to edit machine.config, especially if you need to store a large amount of data in Application Settings.

Instead of tampering with machine.config, you can just open new SharePoint site in your workflow and instance configuration file of that Web Application. Here is how:





using (SPSite site = new SPSite(contextInfo.SiteGuid))
{
      System.Configuration.Configuration _config = WebConfigurationManager.OpenWebConfiguration("/", site.WebApplication.Name);
      
      string _MySetting = _config.AppSettings.Settings["MySettingName"].Value;
                   
}


Now, in a very simple way, we have read value of setting ("MySettingName") stored in Application Settings of our Web Application.

Wednesday, April 10, 2013

Project Server - Restart workflow from code

When you want to restart workflow in Project Server, you can do that manually in Project Server in two ways:

1. When you open one of your project, in SharePoint Ribbon you will see "Options" button and its submenu "Restart Workflow". By clicking this option, you can restart your workflow, but you can not select the stage to which you want to restart workflow. This option will always restart workflow to the first stage, to the beginning. 




2. Second option better in sense that you can select particular stage to which you want to restart your workflow. You can also select multiple projects and some other options.

This option is located in: Server settings --> Change or Restart Workflows (in section Workflow and Project Detail Pages).




 Downside of this two options is that it has to be done manually, and that is not very convenient when you have to do this on large number of projects. So the best way to do this is from code behind.



 RESTART FROM CODE:

Workflow can be restarted to any stage using SubmitStage function from FluentPS library (which I described in one of my previous posts).

Here is the example of usage of that function:




public void RestartStage(Guid projectUID, string stageName)
{
      var logService = new LogService();

      var sessionService = new PSSessionService()
      {
           HostName = "serverName", // your PWA host name
           SiteName = "pwa" // your PWA site name
      };

      PsiContextService psiContextService = new PsiContextService();
      PSISvcsFactory psiSvcsFactory = new PSISvcsFactory(sessionService, psiContextService);

      Project svcProject = psiSvcsFactory.CreateSvcClient<Project>();
 
      Workflow svcWf = psiSvcsFactory.CreateSvcClient<Workflow>();

      SPSPagesService svcPage = new SPSPagesService(sessionService);
      SPSharepointService svcSP = new SPSharepointService(sessionService, svcPage, logService);
      PSWorkflowService svcPSWf= new PSWorkflowService(logService, svcWf, svcSP);

      WorkflowDataSet workflowDS = svcWf.ReadAvailableEnterpriseProjectTypes();
      List<PSWorkflowStatus> wfStatus = svcPSWf.GetProjectWorkflowStatus(projectUID);

      //Enteprise project template UID
      Guid _eptUID = new Guid();//

      //Stage UID
      Guid _stageGuid = new Guid();

      //Getting the stage GUID
      string _phaseName = "";

      for (int i = 0; i < wfStatus.Count; i++)
      {
           if (wfStatus[i].StageName == stageName)
           {
                _stageGuid = wfStatus[i].StageUid;
                _phaseName = wfStatus[i].PhaseName;
                break;
           }
      }
     
      //getting the enterprise project type GUID
      for (int i = 0; i < workflowDS.EnterpriseProjectType.Count; i++)
      {
          if (workflowDS.EnterpriseProjectType[i].ENTERPRISE_PROJECT_TYPE_NAME == _phaseName)
          {
                _eptUID = workflowDS.EnterpriseProjectType[i].ENTERPRISE_PROJECT_TYPE_UID;
                break;
          }
      }

           //RESTART to the stage
      Guid _restartGuid = svcPSWf.SubmitStage(projectUID, _eptUID, false, _stageGuid);
     
}