Setting custom fields using the PSI - Microsoft Project Server - custom-fields

I'm new to Project Server development and was wondering if you can use the PSI to set resource custom field values.
I can't seem to find any information for a beginner at this.
I currently have web references set up for the CustomFields and the Resource web service, but am unsure how to set a custom field for a particular resource.
Any help would be really appreciated!
Thanks!

I know your problem. Microsoft has really bad examples on MSDN. Lot of stuff doesn't work or has just been copied from the 2007 manual. I've started yesterday to develop with the Webservice for Project Server 2010.
Here is a simple (maybe not the best) solution for setting custom fields:
Complete source code: http://pastebin.com/tr7CGJsW
Prepare your solution
First I've added a Service Reference to:
http: //servername/instance/_vti_bin/PSI/Project.asmx?wsdl
After that, I've edited the app.config because I had to use special credentials to login into the project server (maybe this is not necessarily for you):
...
<security mode="TransportCredentialOnly">
<transport clientCredentialType="Ntlm" proxyCredentialType="Ntlm" realm="" />
<message clientCredentialType="UserName" algorithmSuite="Default" />
</security>
<!--<security mode="None">
<transport clientCredentialType="None" proxyCredentialType="None"
realm="" />
<message clientCredentialType="UserName" algorithmSuite="Default" />
</security>-->
</binding>
The commented code has been created by Visual Studio
Connect and Update
Now we can create a new SoapClient which communicates with the Project Server:
//Creating a new service client object
ProjectSoapClient projectSvc = new ProjectSoapClient();
//Just if you need to authenticate with another account!
projectSvc.ClientCredentials.Windows.ClientCredential = new NetworkCredential("test", "test", "demo");
projectSvc.ClientCredentials.Windows.AllowedImpersonationLevel = TokenImpersonationLevel.Impersonation;
Now I have declared two Guids, that we know, what we want to update. The other variables are used later and are comment in the code:
//Guid of my project
Guid myProjectId = new Guid("{610c820f-dc74-476c-b797-1e61a77ed6c6}");
//Guid of the custom field
Guid myCustomFieldId = new Guid("{cd879634-b3ee-44eb-87f7-3063a3523f45}");
//creating a new sessionId and a new jobId
Guid sessionId = Guid.NewGuid(); //the sessionId stays for the whole updating process
Guid jobId = Guid.NewGuid(); //for each job, you give to the server, you need a new one
//indicator if you have to update the project
Boolean updatedata = false;
Then we are ready to load the ProjectDataSet from the server, find the CustomField and updating the data. It's really simple:
loading the ProjectDataSet
iterate trough the CustomFieldsRow
checking if the CustomField matches with our Guid
updating the value
setting the indicator to update
//loading project data from server
//Every change on this dataset will be updated on the server!
ProjectDataSet project = projectSvc.ReadProject(myProjectId, DataStoreEnum.WorkingStore);
//To find your custom field, you have to search for it in the CustomFieldsRow
foreach (ProjectServerCSVImport.PSS.Project.ProjectDataSet.ProjectCustomFieldsRow row in project.ProjectCustomFields)
{
//check if the GUID is the same
if (row.MD_PROP_UID == myCustomFieldId)
{
//if yes, write it into the container
row.NUM_VALUE = 12345;
//and set the indicater
updatedata = true;
}
}
If we changed a value, we have to send the ProjectDataSet to the ProjectServer now. It will update the changed values in the ProjectDataSet. For this, we have to check out our project, update it and check in again:
//update if you have changed anything
if (updatedata)
{
//check out the project first
projectSvc.CheckOutProject(myProjectId, sessionId, "custom field update checkout");
//send the dataset to the server to update the database
bool validateOnly = false;
projectSvc.QueueUpdateProject(jobId, sessionId, project, validateOnly);
//wait 4 seconds just to be sure the job has been done
System.Threading.Thread.Sleep(4000);
//create a new jobId to check in the project
jobId = Guid.NewGuid();
//CheckIn
bool force = false;
string sessionDescription = "updated custom fields";
projectSvc.QueueCheckInProject(jobId, myProjectId, force, sessionId, sessionDescription);
//wait again 4 seconds
System.Threading.Thread.Sleep(4000);
But now we just have updated the database. The server will still show us the "old" value and not the new one. This is because we didn't have published our project yet:
//again a new jobId to publish the project
jobId = Guid.NewGuid();
bool fullPublish = true;
projectSvc.QueuePublish(jobId, myProjectId, fullPublish, null);
//maybe we should wait again ;)
System.Threading.Thread.Sleep(4000);
Finally we're done and our project is updated!
Enhancements
I'm very new to this platform (Project Server 2010), so this code maybe is not the best example. So there are some enhancements, which would made the solution better:
The Sleep function is a very bad workaround, but I couldn't find an example which handles the same thing (like here) with a QueueSystem for 2010?!
If you don't know the Guid of your project, but the name, you can resolve it by following function:
/// <summary>
/// Returns the GUID for a specified project
/// and sets the guid for this class
/// </summary>
/// <param name="client">soap service client</param>
/// <param name="projectname">name of the project</param>
/// <returns>Project GUID</returns>
public Guid GetGuidByProjectName(ProjectSoapClient client, string projectname)
{
Guid pguid = new Guid();
ProjectDataSet data = client.ReadProjectList();
foreach (DataRow row in data.Tables[0].Rows)
{
if (row[1].ToString() == projectname) //compare - case sensitive!
{
pguid = new Guid(row[0].ToString());
}
}
return pguid;
}

Related

Connect Azure MI SQL View to MVC app as read only code first

One aspect of an ASP.net core (6) MVC app I am working on needs to query an SQL View that already resides in an Azure SQL MI.
I need to be able to query this SQL View to be able to retrieve the data based on user input but with the following conditions.
I cannot use Entity Framework.
The connection has to be read only.
This has to be database first.
As of yet I do not have access to this View or any of the tables it draws from. However I am expected to have code ready to plug a connection string into.
Unfortunately any resources I have been able to find don't seem to apply to my specific conditions. So any advice in what direction or approach would work best would be appreciated.
Those are by no means "silly" conditions. You didn't specify the language or the database but I'll make assumptions
I cannot use Entity Framework
Just use standard ado.net
https://learn.microsoft.com/en-us/dotnet/framework/data/adonet/ado-net-code-examples#sqlclient
(I know that Link only answers are frowned upon)
The connection has to be read only.
Ensure that the account you connect under is read only. In SQL Server this is achieved by making you a member of the db_datareader group. This is something that should be enforced by the DBA that gives you an account
This has to be database first.
That's not really relevant. Just use the linked sample code to read from the existing view.
Literal copy paste of code at the link above:
using System;
using System.Data;
using System.Data.SqlClient;
class Program
{
static void Main()
{
string connectionString =
"Data Source=(local);Initial Catalog=Northwind;"
+ "Integrated Security=true";
// Provide the query string with a parameter placeholder.
string queryString =
"SELECT ProductID, UnitPrice, ProductName from dbo.products "
+ "WHERE UnitPrice > #pricePoint "
+ "ORDER BY UnitPrice DESC;";
// Specify the parameter value.
int paramValue = 5;
// Create and open the connection in a using block. This
// ensures that all resources will be closed and disposed
// when the code exits.
using (SqlConnection connection =
new SqlConnection(connectionString))
{
// Create the Command and Parameter objects.
SqlCommand command = new SqlCommand(queryString, connection);
command.Parameters.AddWithValue("#pricePoint", paramValue);
// Open the connection in a try/catch block.
// Create and execute the DataReader, writing the result
// set to the console window.
try
{
connection.Open();
SqlDataReader reader = command.ExecuteReader();
while (reader.Read())
{
Console.WriteLine("\t{0}\t{1}\t{2}",
reader[0], reader[1], reader[2]);
}
reader.Close();
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
Console.ReadLine();
}
}

Time out 500 error on Edmx

I developed a website using Asp.Net MVC and Edmx database and I published this website on azure and my database is also on azure and I've a functionality on website that uploads excel record into database and that excel sheet contain almost 18000 records every time I upload that sheet it throw Timeout error after some time so what should I do.
Initially I was not using any command Timeout but after doing some research I'm using this in constructor
public ProfessionalServicesEntities()
: base("name=ProfessionalServicesEntities")
{
this.Database.CommandTimeout = 10000;
//this.Database.CommandTimeout = 0; //I tried this too.
//((IObjectContextAdapter)this).ObjectContext.CommandTimeout = 3600;
}
Here is the code of
function :-
public void SaveEquipments(IEnumerable<EquipSampleEntity> collection)
{
using (ProfessionalServicesEntities db = new ProfessionalServicesEntities())
{
string modelXml = XmlSerialization.ListToXml(collection.Where(x=>x.Type == Model).ToList());
string accessoryXml = XmlSerialization.ListToXml(collection.Where(x => x.Type == Accessory).ToList());
db.ImportEquipmentFile(modelXml, accessoryXml);
}
}
here is context file code for SP:-
public virtual int ImportEquipmentFile(string modelXml, string accessoryXml)
{
var modelXmlParameter = modelXml != null ?
new ObjectParameter("ModelXml", modelXml) :
new ObjectParameter("ModelXml", typeof(string));
var accessoryXmlParameter = accessoryXml != null ?
new ObjectParameter("AccessoryXml", accessoryXml) :
new ObjectParameter("AccessoryXml", typeof(string));
return ((IObjectContextAdapter)this).ObjectContext.ExecuteFunction("ImportEquipmentFile", modelXmlParameter, accessoryXmlParameter);
}
You may be processing the excel on upload itself and processing it row by row. You have two options, one is to schedule a background job to pickup the upload file and insert it to DB and complete the request.
Next option is to read the whole file in one go and do a single bulk insert into the DB.
There are too many things that can cause this. In Azure App Service there is a Front-end which has a timeout of 240 seconds. If your application takes more time, then you might run into this. This could be one of the probable causes.
In order to understand what is happening. Enabled Web Server Logging and Failed Request Tracing.
See this for how to proceed further: https://learn.microsoft.com/en-us/azure/app-service-web/web-sites-enable-diagnostic-log

MachineKeyDataProtector - Invalid link when confirmation email sent through background job

I've been pulling my hair out over this. Anytime a user registration email is sent out via my windows service (background task), I get an "Invalid link".
My setup
I'm using Hangfire as a windows service on our development server. This is where the problematic GenerateEmailConfirmationToken call is happening. It's in a completely different context, outside of the ASP.NET pipeline. So I have setup machineKey values to correspond with that in the web.config of the MVC application:
In the app.config of the Windows Service Console project, which transforms to MyApp.exe.config, I have a machineKey element
In the MVC 5 project - I have a machineKey element that matches the MyApp.exe.config machineKey element.
I've verified that BOTH of these have the same machine key element data.
The Problem
When I generate a user using the ASP.NET MVC context and pipeline (IE without going through the Hangfire Background job processing), the link works fine.
When I use the background job processor, I always get invalid link. I'm all out of ideas here.
Why is this happening? Is it because the token is being generated in a different thread? How do I get around this?
Relevant code for the various projects
IoC Bootstrapping
Gets called by both applications (Windows Service and MVC Web App)
container.Register<IUserTokenProvider<AppUser, int>>(() => DataProtector.TokenProvider, defaultAppLifeStyle);
DataProtector.cs
public class DataProtector
{
public static IDataProtectionProvider DataProtectionProvider { get; set; }
public static DataProtectorTokenProvider<AppUser, int> TokenProvider { get; set; }
static DataProtector()
{
DataProtectionProvider = new MachineKeyProtectionProvider();
TokenProvider = new DataProtectorTokenProvider<AppUser, int>(DataProtectionProvider.Create("Confirmation", "ResetPassword"));
}
}
Things I've Tried
Using a DpapiDataProtectionProvider
Custom MachineKeyProtectionProvider from Generating reset password token does not work in Azure Website
The MachineKeyProtectionProvider.cs code is exactly as the linked post above.
I've also tried other purposes like "YourMom" and "AllYourTokensAreBelongToMe" to no avail. Single purposes, multiple purposes - it doesn't matter - none work.
I'm also calling HttpUtility.UrlEncode(code) on the code that gets generated in both places (Controller and Background Job).
Solution
igor got it right, except it was not a code issue. It was because of a rogue service picking up the job, which had a different machine key. I had been staring at the problem so long that I did not see a second service running.
As I understand your problem there are 2 possible places where failure could occur.
1. MachineKey
It could be that the MachineKey itself is not producing a consistent value between your 2 applications. This can happen if your machineKey in the .config file is not the same in both applications (I did read that you checked it but a simple type-o, added space, added to the wrong parent element, etc. could lead to this behavior.). This can be easily tested to rule it out as a point of failure. Also the behavior might be different depending on the referenced .net framework, MachineKey.Protect
The configuration settings that are required for the MachineKeyCompatibilityMode.Framework45 option are required for this method even if the MachineKeySection.CompatibilityMode property is not set to the Framework45 option.
I created a random key pair for testing and using this key I generated a test value I assigned to variable validValue below in the code. If you copy/paste the following section into your web.config and app.config the Unprotect of that keyvalue will work.
web.config / app.config
<system.web>
<httpRuntime targetFramework="4.6.1"/>
<machineKey decryption="AES" decryptionKey="9ADCFD68D2089D79A941F9B8D06170E4F6C96E9CE996449C931F7976EF3DD209" validation="HMACSHA256" validationKey="98D92CC1E5688DB544A1A5EF98474F3758C6819A93CC97E8684FFC7ED163C445852628E36465DB4E93BB1F8E12D69D0A99ED55639938B259D0216BD2DF4F9E73" />
</system.web>
Service Application Test
class Program
{
static void Main(string[] args)
{
// should evaluate to SomeTestString
const string validValue = "03AD03E75A76CF13FDDA57425E9D362BA0FF852C4A052FD94F641B73CEBD3AC8B2F253BB45550379E44A4938371264BFA590F9E68E59DB57A9A4EB5B8B1CCC59";
var unprotected2 = MachineWrapper.Unprotect(validValue);
}
}
Mvc Controller (or Web Api controller) Test
public class WebTestController : Controller
{
// GET: WebTest
public ActionResult Index()
{
// should evaluate to SomeTestString
const string validValue = "03AD03E75A76CF13FDDA57425E9D362BA0FF852C4A052FD94F641B73CEBD3AC8B2F253BB45550379E44A4938371264BFA590F9E68E59DB57A9A4EB5B8B1CCC59";
var unprotected2 = MachineWrapper.Unprotect(validValue);
return View(unprotected2);
}
}
Common Code
using System;
using System.Linq;
using System.Text;
using System.Web.Security;
namespace Common
{
public class MachineWrapper
{
public static string Protect()
{
var testData = "SomeTestString";
return BytesToString(MachineKey.Protect(System.Text.Encoding.UTF8.GetBytes(testData), "PasswordSafe"));
}
public static string Unprotect(string data)
{
var bytes = StringToBytes(data);
var result = MachineKey.Unprotect(bytes, "PasswordSafe");
return System.Text.Encoding.UTF8.GetString(result);
}
public static byte[] StringToBytes(string hex)
{
return Enumerable.Range(0, hex.Length)
.Where(x => x % 2 == 0)
.Select(x => Convert.ToByte(hex.Substring(x, 2), 16))
.ToArray();
}
public static string BytesToString(byte[] bytes)
{
var hex = new StringBuilder(bytes.Length * 2);
foreach (byte b in bytes)
hex.AppendFormat("{0:x2}", b);
return hex.ToString().ToUpper();
}
}
}
If this passes both Console and the Web Application will get the same value and not throw a CryptographicException message Error occurred during a cryptographic operation. If you want to test with your own keys just run Protect from the common MachineWrapper class and record the value and re-execute for both apps.
2. UserManager uses Wrong Type
I would start with the previous section BUT the other failure point is that your custom machine key provider is not being used by the Microsoft.AspNet.Identity.UserManager. So here are some questions/action items that can help you figure out why this is happening:
Is container.Register the Unity IoC framework or are you using another framework?
Are you sure that your Di framework is also injecting that instance in the Microsoft.AspNet.Identity.UserManager in both the Service application as well as the Web application?
Have put a break point in public byte[] Protect of your MachineKeyDataProtector class to see if this is called in both the Service application as well as the Web application?
From examples I have seen so far (including the one you posted with the custom MachineKey solution) you need to manually bootstrap the type during application startup but then again I have not ever tried to hook into the Identity framework to replace this component using DI.
If you look at the default Visual Studio template code that is provided when you create a new MVC application the code file App_Start\IdentityConfig.cs would be the place to add this new provider.
Method:
public static ApplicationUserManager Create(IdentityFactoryOptions<ApplicationUserManager> options, IOwinContext context)
Replace
var dataProtectionProvider = options.DataProtectionProvider;
if (dataProtectionProvider != null)
{
manager.UserTokenProvider = new DataProtectorTokenProvider<ApplicationUser>(dataProtectionProvider.Create("ASP.NET Identity"));
}
With this
var provider = new MachineKeyProtectionProvider();
manager.UserTokenProvider = new DataProtectorTokenProvider<ApplicationUser>(provider.Create("ResetPasswordPurpose"));
And this has to be configured for both applications if you are not using a common library where this is configured.

How to get company's Active Directory data to display on a razor view(webgrid)?

I need to build a simple asp.net mvc web apps to display the detailed data from Active Directory and show it in a webgrid feature , is that possible? I appreciate if anybody can provide any good examples, thanks in advance.
I found this example: http://www.dotnetcodesg.com/Article/UploadFile/2/223/Get%20List%20of%20Active%20Directory%20Users%20in%20ASP.NET%20Csharp.aspx
but when I execute the codes, the program stopped on this line of code:
MembershipUser myUser = Membership.GetAllUsers()[searchResult...
I got error : "Unable to connect to SQL Server database. "
The codes stopped on :
MembershipUser myUser = Membership.GetAllUsers()[searchResult.Properties["sAMAccountName"][0].ToString()];
It seems I need to connect Membership database, do I have to connect membership database in order to get all employee data?
I only want to get all employee information such as : name, ID, email, phone , ,, and display them in a view (better in webgrid or other format easy to read).
I tried these codes :
DirectoryEntry myLdapConnection = new DirectoryEntry(LDAP://company.domin);
// DirectorySearcher search = new DirectorySearcher(myLdapConnection) { Filter = ("(objectClass=user)") };
DirectorySearcher search = new DirectorySearcher();
// search.CacheResults = true;
search.SearchRoot = myLdapConnection;
search.SearchScope = SearchScope.Subtree;
SearchResultCollection allResults = search.FindAll();
DataTable resultsTable = new DataTable();
resultsTable.Columns.Add("sAMAccountName");
.....
Basically, I added the data to a datatable and show them in a razor view, I got the data and displayed them on the razor view, but the data is not complete data, some employee information is missing, can anybody tell me what is wrong with my codes for the missing data? there must be something wrong with the codes which get the partial data. What I want to get is complete data in my company's Active Directory which includes all employee's name and group name, etc.
The database error message suggests that your site is configured for Forms Authentication. You need to set the authentication mode to Windows in your web.config file to use Active directory.
<system.web>
<authentication mode="Windows" />
</system.web>

How to initialize and persist Castle ActiveRecordStarter per session for multi tenancy apps?

I am using Castle ActiveRecord in my Asp.net / MVC 2 / Multi-tenancy application with SQL Server as my backend.
For every user logging in, the app loads the corresponding DB, dynamically at run time like below:
IDictionary<string, string> properties = new Dictionary<string, string>();
properties.Add("connection.driver_class", "NHibernate.Driver.SqlClientDriver");
properties.Add("dialect", "NHibernate.Dialect.MsSql2005Dialect");
properties.Add("connection.provider", "NHibernate.Connection.DriverConnectionProvider");
properties.Add("proxyfactory.factory_class", "NHibernate.ByteCode.Castle.ProxyFactoryFactory, NHibernate.ByteCode.Castle");
properties.Add("connection.connection_string", strDBConnection);
InPlaceConfigurationSource source = new InPlaceConfigurationSource();
source.Add(typeof(ActiveRecordBase), properties);
ActiveRecordStarter.Initialize(new System.Reflection.Assembly[] { asm1 }, source);
The strDBConnection string comes from another small database that holds the user info, corresponding DB, etc.
Scenario:
When a user logs in, his DB gets loaded, he can do his CRUD jobs -- No Probs !
Another user logs in (from another remote machine) his DB gets loaded -- No Probs !
Now, when the first user reads from DB, he sees new data from the second user's DB
My little understanding for this behavious is : ActiveRecordStarter is a Static object.
Could someone help me with a solution for this situation ?
The expected behaviour:
each user should access his own DB only, securely, in parallel / at the same time.
Thanks a lot !
ActiveRecordStarter.Initialize should only be called once in your app (in Application_Start in Global.asax).
To achieve what you want, create a class that inherits from NHibernate.Connection.DriverConnectionProvider:
public class MyCustomConnectionProvider : DriverConnectionProvider
{
protected override string GetNamedConnectionString(IDictionary<string, string> settings)
{
return string.Empty;
}
public override IDbConnection GetConnection()
{
// Get your connection here, based on the request
// You can use HttpContext.Current to get information about the current request
var conn = Driver.CreateConnection();
conn.ConnectionString = ... // Retrieve the connection string here;
conn.Open();
return conn;
}
}
Then set the connection.provider property to the name of your class:
properties.Add("connection.provider", "MyCompany.Domain.MyCustomConnectionProvider, MyCompany.AssemblyName");

Resources