Developing Project Essentials Applications using WCF

Code Samples, Project Essentials, Project Server, SDK

1. Intro – CMSI Overview

 

All Windows-based and Web-based client applications for UMT Project Essentials use the Cost Module Service Interface (CMSI), a set of Web services built on the Microsoft .NET Framework 3.5 and the Windows Communication Foundation (WCF). The CMSI exposes the functionality and data that developers can use to extend Project Essentials and to integrate other applications with Project Essentials. Parameters of CMSI methods typically include DataSet objects.

The CMSI in Project Essentials has a dual interface. The Web service (ASMX) interface operates in the following way:

  • You must use the ?wsdl URL option when setting an ASMX reference. For example: http://ServerName/ProjectServerName/_vti_bin/Cost/ProjectCost.asmx?wsdl
  • An ASMX reference must be made through the Project Web App address. You cannot set an ASMX reference to the CMSI through the back-end Project Essentials service application. For applications that must run on the local Project Essentials computer, you should use the WCF interface.
  • When you are developing on the Project Essentials computer, you cannot use localhost for the server name in the URL of an ASMX reference. Use the computer name.

The Project Essentials Web extension client uses the WCF service interface for enhanced performance and security. Custom applications can use either interface; the ASMX and WCF interfaces have an identical object model for the public Web services. We recommend using the WCF interface for new applications.

 

2. Generating the WCF proxy classes

 

The first thing you need to do is to generate the classes for the WCF proxy. This can be done in more ways. The first method would be by running the GenWCFProxyAssembly.cmd script available with this post. Before running the script you need to make a few modifications:

On line 19 you will have to input the server name and the Service Application GUID:

(set VDIR=http://ServerName:32843/8813747409d94599974b24602f230a0e/CMSI)

Replace the server name with the full computer (ie. project.contoso.umt.local). To get the Guid for the Service application you can either get it by running the IIS Manager:

image

Or you can open SharePoint 2010 management Shell and use Get-SPServiceApplication | Select ID,DisplayName

image

The GenWCFProxyAssembly.cmd script generates output files for the WCF services listed in the ClassList_WCF.txt, and then compiles the assembly. To run the script on a test installation of Project Essentials, do the following:

  • Make a copy of the web.config file for the back-end Project Essentials application in [Program Files] UMT Consulting GroupUMT Project EssentialsWebServicesCMSI.
  • Copy the web.config.temp (available with this post) to the back-end PE application directory, and then rename the file as web.config.
  • In a Command Prompt window, run iisreset.
  • Run GenWCFProxyAssembly.cmd. After you run the script, restore the original web.config file.
  • Run iisreset again.

Note: The script uses svcutil.exe so instead of hardcoding the path you can run it from the Visual Studio Command Prompt

 

The other method to create the proxy classes is by using svcutil.exe to manually generate the proxy class for the desired WCF service. Here’s a sample of how to use it:

svcutil.exe /tcv:Version35 /serializer:XmlSerializer /nologo /t:code /l:CS  http://project.contoso.umt.local:32843/41003eae452148dea374336005ddf3ad/CMSI/Granularity.svc?wsdl

Parameters used with this sample:

  • /tcv – short for  / targetClientVersion , specifies which version of .NET Framework the application is targetting.
  • /serializer:XmlSerializer – generates data types that use the XmlSerializer for serialization and deserialization.
  • /nologo – Suppress the copyright and banner message.
  • /t – short for /target , specifies the output to be generated by the tool.
  • /l – short for /language , specifies the programming language to use for code generation.

To use the generated classes you can either add a reference to the newly created ProjectEssentialsservices.dll or add the generated classes from the Source folder created by the script.

 

3. Configuring the Services

3.1.Configuring the WCF services programmatically

If it is used only for WCF configuration, exclude the app.config file provided.

To create a binding for the client endpoints, add the following code:

const int MAXSIZE = 500000000; // Set the final part of the URL address of the // front-end ProjectfinancialServer.svc router. const string svcRouter = "_vti_bin/PSI/ProjectFinancialServer.svc"; pwaUrl = pwaUri.Scheme + Uri.SchemeDelimiter + pwaUri.Host + ":" + pwaUri.Port + pwaUri.AbsolutePath; Console.WriteLine("URL: {0}", pwaUrl); // Create a basic binding that can be used for HTTP or HTTPS. BasicHttpBinding binding = null; if (pwaUri.Scheme.Equals(Uri.UriSchemeHttps)) { // Initialize the HTTPS binding. binding = new BasicHttpBinding(BasicHttpSecurityMode.Transport); } else { // Initialize the HTTP binding. binding = new BasicHttpBinding( BasicHttpSecurityMode.TransportCredentialOnly); }

Set properties of the binding to enable use by the PSI-based application. The SendTimeout property should be a large enough time span to handle latency in accessing Project Server. The maximum message size property and the name table character count property should be large enough to handle very large datasets in Project Server. The MessageEncoding property must be set for text data, and the ClientCredentialType property must be set for NTLM credentials.

binding.Name = "basicHttpConf"; binding.SendTimeout = TimeSpan.MaxValue; binding.MaxReceivedMessageSize = MAXSIZE; binding.ReaderQuotas.MaxNameTableCharCount = MAXSIZE; binding.MessageEncoding = WSMessageEncoding.Text; binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Ntlm;

Create an endpoint address that points to the front-end ProjectFinancialServer.svc router in Project Web App. Finally, initialize each of the client variables with the WCF binding and endpoint address. The channel factory includes security and other properties of the underlying transport mechanism of the WCF service model.

// The endpoint address is the ProjectfinalcialServer.svc router for all public CMSI calls. EndpointAddress address = new EndpointAddress(pwaUrl + svcRouter); SvcGranularity.GranularityClient granularityClient = new SvcGranularity.GranularityClient(binding, address); granularityClient.ChannelFactory.Credentials.Windows.AllowedImpersonationLevel = TokenImpersonationLevel.Impersonation; granularityClient.ChannelFactory.Credentials.Windows.AllowNtlm = true;

3.2 Configuring the WCF services with app.config

 

In Visual Studio, on the Tools menu, click WCF Service Configuration Editor. In the Microsoft Service Configuration Editor application window, on the File menu, click Open, click Config File, and then navigate to the app.config file for the solution. When you directly set a service reference, Visual Studio creates two default bindings for each WCF service in your application. Visual Studio also creates two default endpoints for each service, and these endpoints use the default bindings. For example, the CustomBinding_Granularity endpoint uses the HTTPS protocol and the CustomBinding_Granularity1 endpoint uses the NET.TCP protocol.

In the Microsoft Service Configuration Editor window, in the Configuration pane, right-click Bindings, and then click New Binding Configuration. In the Create a New Binding dialog box, click basicHttpBinding, and then click OK. Rename the binding in the basicHttpBinding pane. For example, type basicHttpConf in the Name text box.

Change the general properties in the Binding tab, as listed below:

  • MaxBufferSize = 500000000
  • MaxReceivedMessageSize = 500000000
  • SendTimeout = 01:00:00
  • MaxArrayLength = 16384
  • MaxBytesPerRead = 4096
  • MaxDepth = 32
  • MaxNameTableCharCount = 500000000
  • MaxStringcontentLength= 8192

 

On the basicHttpBinding pane, click the Security tab, and then change the property values as listed below:

  • Mode = TransportCredentialOnly (The TransportCredentialOnly mode passes user credentials without encrypting or signing the messages. If the Realm URI uses the HTTPS protocol, set the Mode attribute to Transport. For more information, see Programming WCF Security.)
  • Realm = [http://SecurityDomain]
  • TransportClientCredentialType = Ntlm

In the Configuration pane, expand the Advanced node, click Endpoint Behaviors, and then click New Endpoint Behavior Configuration. In the Behavior pane, change the Name field to basicHttpBehavior.

In the Configuration pane, under basicHttpBehavior, click the clientCredentials child node, and then change the AllowedImpersonationLevel to Impersonation. Leave the default values for the other properties (SupportInteractive = True, ImpersonationLevel= Identification, and AllowNtlm = True). Leave the child nodes under clientCredentials with the default values.

Create an endpoint that uses the basicHttpBinding, for the Granularity WCF service in the application:

  • In the Configuration pane, right-click the Endpoints node, and then click New Client Endpoint. In the Client Endpoint pane, type a name for the endpoint; for example, type basicHttp_Granularity for the Project service.
  • In the Address field, type the URL of the ProjectFinancialServer.svc router in Project Web App, for example, http://servername/ProjectServerName/_vti_bin/cost/ProjectFinancialServer.svc.
  • In the BehaviorConfiguration field, in the drop-down list, select the name of the custom endpoint behavior that you previously created. For example, select basicHttpBehavior
  • In the Binding field, select the type of binding. In this case, select basicHttpBinding.
  • In the BindingConfiguration field, select the binding that you previously created. In this case, select basicHttpConf.
  • Click the Contract field, and then click the button to browse to the executable that contains the WCF contract. In the Contract Type Browser dialog box, navigate to the executable file in the binDebug subdirectory of the project directory, click the [output].exe file, and then click Open. The Contract Type Browser shows the service classes in the executable click SvcGranularity.Granularity, and then click Open.
  • In the Microsoft Service Configuration Editor, on the File menu, click Save, and then close the editor.
  • Check the contents of the behaviors, bindings, and endpoints elements in the app.config file.

 

<?xml version="1.0" encoding="utf-8" ?> <configuration> <startup> <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0"/> </startup> <system.serviceModel> <behaviors> <endpointBehaviors> <behavior name="basicHttpBehavior"> <clientCredentials> <windows allowedImpersonationLevel="Impersonation"/> </clientCredentials> </behavior> </endpointBehaviors> </behaviors> <bindings> <basicHttpBinding> <binding name="BasicHttpBinding_Ntlm" sendTimeout="01:00:00" maxBufferSize="500000000" maxReceivedMessageSize="500000000"> <readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="16384" maxBytesPerRead="4096" maxNameTableCharCount="500000000"/> <security mode="TransportCredentialOnly"> <transport clientCredentialType="Ntlm" realm=""/> </security> </binding> </basicHttpBinding> </bindings> <client> <endpoint address="http://ServerName/ProjectServerName/_vti_bin/cost/ProjectFinancialServer.svc" binding="basicHttpBinding" bindingConfiguration="BasicHttpBinding_Ntlm" behaviorConfiguration="basicHttpBehavior" contract="SvcGranularity.Granularity" name="basicHttp_Granularity" /> </client> </system.serviceModel> </configuration>

 

4.Sample

The following program will display the UID, start date, end date and the period name for the first opened actual for a project, using the app.config configuration. You need to add a reference to UMT.CostModule.Library in order to use have access to the PE classes.

Sample explained:

  • We will use the Granularity WCF service for this example so the first thing we need to do is to create an instance of a client for the Granularity Service using basicHttp_Granularity endpoint (defined in the app.config file)
  • Once we have the client successfully created, we need to read the granularity periods for this project that are Actual Values. This method takes 3 paramters: the project GUID, the value type, the last one is a flag stating if it’s a cost value or benefits (true for cost).
  • The result will be an array of TimePeriods defined for this project.
  • The next thing we want to do is to find what periods are opened. The result will be a GranularityInfo dataset containing the opened periods if any.
  • For this example we want to get the first opened period which means that it will be the first row in the GranularityInfo dataset (if there are any opened actuals).
  • We’ll display some period related data so we’ll write the uid, start date, end date and period name for the first opened Actual.
  • Close the granularity service client when we’re done

 

using System; using System.Collections.Generic; using System.Linq; using System.Text; using SvcGranularity; using UMT.CostModule.Library.Core; using UMT.CostModule.Library.Error; namespace UMT.ProjectEssentials.SDK.GetActualsForProject { class Program { static void Main(string[] args) { //set the UID for the project you want to read the actuals Guid projectGuid = new Guid("0898C874-B482-4EE2-A38E-C31C656C3473"); //instatiate a client for the Granularity Service GranularityClient granularityClient = new GranularityClient("basicHttp_Granularity"); //read the granularity periods for this project that are Actual Values SvcGranularity.TimePeriod[] periods = granularityClient.ReadGranularityPeriodsForProject(projectGuid, ValueTypes.ActualValue, true); //find what periods are opened GranularityInfo granInfo = granularityClient.ReadProjectActualsPeriods(projectGuid, periods.First<SvcGranularity.TimePeriod>().StartDate, periods.Last<SvcGranularity.TimePeriod>().StartDate, SvcGranularity.PeriodStatus.OPENED, true); if (granInfo.cmReportingPeriods.Rows.Count > 0) { GranularityInfo.cmReportingPeriodsRow firstPeriod = granInfo.cmReportingPeriods.Rows[0] as GranularityInfo.cmReportingPeriodsRow; //write the uid, start date,end date and period name for the first opened actual Console.WriteLine(String.Format("The first opened period : UID={0}; Start Date={1}; End Date={2}; Period Name={3}", firstPeriod.GranularityGUID, firstPeriod.StartDate, firstPeriod.EndDate, firstPeriod.PeriodName)); } //close the granularity service client when we're done granularityClient.Close(); Console.ReadLine(); } } }

You can download the files here:

Sample files

What do you think?