Changing title of documents being uploaded - Event Receiver - c#

Ok so I have a request from a user and I want to achieve it using and Event Receiver.
Basically what she wants is to have a Document Library where users can upload documents. Each document will have a file name like this "UserA - Customization.docx". Now you can imagine that a user can upload many documents that will have an identical name so what we want to do is number the files automatically. So if if UserA uploads the first document, SharePoint will add a number after the name so the file will be called "UserA - Customization - 1.docx" and then he uploads a second document and it will be called "UserA - Customization - 2.docx".
However, now if UserB wants to upload his first document it must be numbered "UserB - Customization - 1.docx" so basically the counter needs to restart if its a new documet and continue from the highest number if the document name already exists.
So basically SharePoint needs to check if the name of the current document exists in the list and if it does add a number next to it, but the number must be 1 greater than the highest document so it will just increase.
Is there any way to do this? Any ideas?
This is what I have come up with so far for simply changing the name of the file adding "-xx" to the filename but this is not working.
public override void ItemAdded(SPItemEventProperties properties)
{
SPFile spf = properties.ListItem.File;
string url = properties.AfterUrl;
int positionOfSlash = url.LastIndexOf("/");
string pathBeforeFileName = url.Substring(0, positionOfSlash);
string newFileName = createNewFileName(url.Substring(positionOfSlash));
string myNewUrl = pathBeforeFileName + newFileName;
DisableEventFiring();
spf.MoveTo(myNewUrl);
spf.Update();
EnableEventFiring();
}
static string createNewFileName(string oldFileName)
{
int positionOfPeriod = oldFileName.LastIndexOf(".");
string fileName = oldFileName.Substring(0, positionOfPeriod);
string fileExtension = oldFileName.Substring(positionOfPeriod);
string newFileName = fileName + "-xx" + fileExtension;
return newFileName;
}
Where am I going wrong with this code? Thanks for any help!
EDIT: this is the code I am using in the console app in Visual Studio to attach the EventReceiver to the Document Library.
using (SPSite site = new SPSite("http://servername:port/subsite1/subsite2/"))
{
using (SPWeb web = site.OpenWeb())
{
SPList list = web.Lists["My Doc Library"];
SPEventReceiverDefinition def = list.EventReceivers.Add();
def.Assembly = "DocumentLibrary_ClassLib, Version=1.0.0.0, Culture=Neutral, PublicKeyToken=611205b34d18f14d";
def.Class = "DocumentLibrary_ClassLib.EventReceiver";
def.Type = SPEventReceiverType.ItemAdded;
def.Update();
}
}
EDIT #2:
Ok how about something like this?
//this will get just the name of the file without the extension and I will send that to the
//query builder which will count how many files there are with that name and return
int positionOfPeriod = oldFileName.LastIndexOf(".");
string tempFileName = oldFileName.Substring(0, positionOfPeriod);
SPQuery query = BuildArbitraryQuery(properties.List, "Name", tempFileName, true);
But now I don't really understand the query in the BuildArbitraryQuery, how can I change this to give me the desired behavior? (sorry if its a total noob question but I've never dealt with C# and EventReceivers before)
- Well after looking at BuildArbitraryQuery for a while I think I made some sense of it, basically I don't need to change anything? Since it receives the name of the file and the name of the column as a parameter it should be fine right?
Also since items within the list will be something like ClientA Request - 3.docx and I am sending the filename to the BuildArbitraryQuery will be able to find part matches rather than the full match. So for example if the filename BuildArbitraryQuery receives is ClientA Request.docx will it be able to find all other requests from that ClientA? So would ClientA Request - 1.docx, ClientA Request - 2.docx all be included in the calculation?

Here you go, tested it and it works.
/// <summary>
/// An item was added.
/// </summary>
public override void ItemAdded(SPItemEventProperties properties)
{
base.ItemAdded(properties);
SPListItem item = properties.ListItem;
if (item["Name"] == null)
return; //or better yet, log
string oldFileName = item["Name"].ToString();
SPQuery query = BuildArbitraryQuery(properties.List, "Created By", properties.UserDisplayName, true);
int count = properties.List.GetItems(query).Count;
int positionOfPeriod = oldFileName.LastIndexOf(".");
if (positionOfPeriod == -1)
{
fileName = oldFileName;
fileExtension = "";
}
else
{
fileName = oldFileName.Substring(0, positionOfPeriod);
fileExtension = oldFileName.Substring(positionOfPeriod);
}
string newFileName = fileName + "-xx" + count.ToString() + fileExtension;
item["Name"] = newFileName;
try
{
properties.Web.AllowUnsafeUpdates = true;
EventFiringEnabled = false;
item.Update();
}
finally
{
properties.Web.AllowUnsafeUpdates = false;
EventFiringEnabled = true;
}
}
/// <summary>
/// Builds an arbitrary SPQuery which filters by a single column value.
/// </summary>
/// <param name="list">The list you will run the query against.</param>
/// <param name="columnDisplayName">The Display Name of the column you want to filter.</param>
/// <param name="value">The value to filter against.</param>
/// <returns>A new SPQuery object ready to run against the list.</returns>
public static SPQuery BuildArbitraryQuery(SPList list, string columnDisplayName, string value, bool deepSearch)
{
if (list == null)
throw new ArgumentNullException("You cannot pass a null list to Helper.BuildArbitraryQuery.");
if (!list.Fields.ContainsField(columnDisplayName))
throw new ArgumentException("The SharePoint List \"" + list.Title + "\" does not contain the Field \"" + columnDisplayName + "\".");
string internalName = list.Fields[columnDisplayName].InternalName;
SPQuery query = new SPQuery();
query.Query = "<Where><Eq><FieldRef Name=\"" + internalName + "\"/><Value Type=\"Text\">" + value + "</Value></Eq></Where>";
if (deepSearch)
query.ViewAttributes += "Scope='RecursiveAll'";
return query;
}

Related

Fetch all the Queries under a azure projects using REST call

I am trying to fetch all the available queries under a project using the below REST call
https://learn.microsoft.com/en-us/rest/api/azure/devops/wit/queries/list?view=azure-devops-rest-5.0#uri-parameters
It needs a depth parameter to be passed if not only the First level of queries are returned and it seems the maximum allowed value for depth is 2 .
If i have 3 levels of folder structure in queries even this depth wont help.
So how to retrieve all the queries irrespective of levels ?
TIA
As workaround you can use Microsoft.TeamFoundationServer.Client and explore queries structure with depth 1. Example:
static void GetAllWorkItemQueries(string project)
{
List<QueryHierarchyItem> rootQueries = WitClient.GetQueriesAsync(project, QueryExpand.All).Result;
GetFolderContent(project, rootQueries);
}
/// <summary>
/// Get Content from Query Folders
/// </summary>
/// <param name="project">Team Project Name</param>
/// <param name="queries">Folder List</param>
static void GetFolderContent(string project, List<QueryHierarchyItem> queries)
{
foreach(QueryHierarchyItem query in queries)
{
if (query.IsFolder != null && (bool)query.IsFolder)
{
Console.WriteLine("Folder: " + query.Path);
if ((bool)query.HasChildren)
{
QueryHierarchyItem detiledQuery = WitClient.GetQueryAsync(project, query.Path, QueryExpand.All, 1).Result;
GetFolderContent(project, detiledQuery.Children.ToList());
}
}
else
Console.WriteLine("Query: " + query.Path);
}
}
Full sample project here: https://github.com/ashamrai/TFRestApi/tree/master/04.TFRestApiAppWorkItemQueries
You also can do it with client API, simple code:
static void GetQueryClientAPI()
{
VssCredentials Credentials = new VssCredentials(new Microsoft.VisualStudio.Services.Common.VssBasicCredential(string.Empty, "Personal access token"));
TfsTeamProjectCollection tpc = new TfsTeamProjectCollection(new Uri("devops url"), Credentials);
tpc.EnsureAuthenticated();
WorkItemStore wis = tpc.GetService(typeof(WorkItemStore)) as WorkItemStore;
QueryHierarchy qh = wis.Projects["project name"].QueryHierarchy;
foreach(QueryItem q in qh)
{
GetChildQuery(q);
}
Console.Read();
}
static void GetChildQuery(QueryItem query)
{
if (query is QueryFolder)
{
QueryFolder queryFolder = query as QueryFolder;
foreach (var q in queryFolder)
{
GetChildQuery(q);
}
}
else
{
QueryDefinition querydef = query as QueryDefinition;
Console.WriteLine(querydef.Name + " -- " + querydef.Path);
}
}
Result:

When using MergeField FieldCodes in OpenXml SDK in C# why do field codes disappear or fragment?

I have been working successfully with the C# OpenXml SDK (Unofficial Microsoft Package 2.5 from NuGet) for some time now, but have recently noticed that the following line of code returns different results depending on what mood Microsoft Word appears to be in when the file gets saved:
var fields = document.Descendants<FieldCode>();
From what I can tell, when creating the document in the first place (using Word 2013 on Windows 8.1) if you use the Insert->QuickParts->Field and choose MergeField from the Field names left hand pane, and then provide a Field name in the field properties and click OK then the field code is correctly saved in the document as I would expect.
Then when using the aforementioned line of code I will receive a field code count of 1 field. If I subsequently edit this document (and even leave this field well alone) the subsequent saving could mean that this field code no longer is returned in my query.
Another case of the same curiousness is when I see the FieldCode nodes split across multiple items. So rather than seeing say:
" MERGEFIELD Author \\* MERGEFORMAT "
As the node name, I will see:
" MERGEFIELD Aut"
"hor \\* MERGEFORMAT"
Split as two FieldCode node values. I have no idea why this would be the case, but it certainly makes my ability to match nodes that much more exciting. Is this expected behaviour? A known bug? I don't really want to have to crack open the raw xml and edit this document to work until I understand what is going on. Many thanks all.
I came across this very problem myself, and found a solution that exists within OpenXML: a utility class called MarkupSimplifier which is part of the PowerTools for Open XML project. Using this class solved all the problems I was having that you describe.
The full article is located here.
Here are some pertinent exercepts :
Perhaps the most useful simplification that this performs is to merge adjacent runs with identical formatting.
It goes on to say:
Open XML applications, including Word, can arbitrarily split runs as necessary. If you, for instance, add a comment to a document, runs will be split at the location of the start and end of the comment. After MarkupSimplifier removes comments, it can merge runs, resulting in simpler markup.
An example of the utility class in use is:
SimplifyMarkupSettings settings = new SimplifyMarkupSettings
{
RemoveComments = true,
RemoveContentControls = true,
RemoveEndAndFootNotes = true,
RemoveFieldCodes = false,
RemoveLastRenderedPageBreak = true,
RemovePermissions = true,
RemoveProof = true,
RemoveRsidInfo = true,
RemoveSmartTags = true,
RemoveSoftHyphens = true,
ReplaceTabsWithSpaces = true,
};
MarkupSimplifier.SimplifyMarkup(wordDoc, settings);
I have used this many times with Word 2010 documents using VS2015 .Net Framework 4.5.2 and it has made my life much, much easier.
Update:
I have revisited this code and have found it clears upon runs on MERGEFIELDS but not IF FIELDS that reference mergefields e.g.
{if {MERGEFIELD When39} = "Y???" "Y" "N" }
I have no idea why this might be so, and examination of the underlying XML offers no hints.
Word will often split text runs with into multiple text runs for no reason I've ever understood. When searching, comparing, tidying etc. We preprocess the body with method which combines multiple runs into a single text run.
/// <summary>
/// Combines the identical runs.
/// </summary>
/// <param name="body">The body.</param>
public static void CombineIdenticalRuns(W.Body body)
{
List<W.Run> runsToRemove = new List<W.Run>();
foreach (W.Paragraph para in body.Descendants<W.Paragraph>())
{
List<W.Run> runs = para.Elements<W.Run>().ToList();
for (int i = runs.Count - 2; i >= 0; i--)
{
W.Text text1 = runs[i].GetFirstChild<W.Text>();
W.Text text2 = runs[i + 1].GetFirstChild<W.Text>();
if (text1 != null && text2 != null)
{
string rPr1 = "";
string rPr2 = "";
if (runs[i].RunProperties != null) rPr1 = runs[i].RunProperties.OuterXml;
if (runs[i + 1].RunProperties != null) rPr2 = runs[i + 1].RunProperties.OuterXml;
if (rPr1 == rPr2)
{
text1.Text += text2.Text;
runsToRemove.Add(runs[i + 1]);
}
}
}
}
foreach (W.Run run in runsToRemove)
{
run.Remove();
}
}
I tried to simplify the document with Powertools but the result was a corrupted word file. I make this routine for simplify only fieldcodes that has specifics names, works in all parts on the docs (maindocumentpart, headers and footers):
internal static void SimplifyFieldCodes(WordprocessingDocument document)
{
var masks = new string[] { Constants.VAR_MASK, Constants.INP_MASK, Constants.TBL_MASK, Constants.IMG_MASK, Constants.GRF_MASK };
SimplifyFieldCodesInElement(document.MainDocumentPart.RootElement, masks);
foreach (var headerPart in document.MainDocumentPart.HeaderParts)
{
SimplifyFieldCodesInElement(headerPart.Header, masks);
}
foreach (var footerPart in document.MainDocumentPart.FooterParts)
{
SimplifyFieldCodesInElement(footerPart.Footer, masks);
}
}
internal static void SimplifyFieldCodesInElement(OpenXmlElement element, string[] regexpMasks)
{
foreach (var run in element.Descendants<Run>()
.Select(item => (Run)item)
.ToList())
{
var fieldChar = run.Descendants<FieldChar>().FirstOrDefault();
if (fieldChar != null && fieldChar.FieldCharType == FieldCharValues.Begin)
{
string fieldContent = "";
List<Run> runsInFieldCode = new List<Run>();
var currentRun = run.NextSibling();
while ((currentRun is Run) && currentRun.Descendants<FieldCode>().FirstOrDefault() != null)
{
var currentRunFieldCode = currentRun.Descendants<FieldCode>().FirstOrDefault();
fieldContent += currentRunFieldCode.InnerText;
runsInFieldCode.Add((Run)currentRun);
currentRun = currentRun.NextSibling();
}
// If there is more than one Run for the FieldCode, and is one we must change, set the complete text in the first Run and remove the rest
if (runsInFieldCode.Count > 1)
{
// Check fielcode to know it's one that we must simplify (for not to change TOC, PAGEREF, etc.)
bool applyTransform = false;
foreach (string regexpMask in regexpMasks)
{
Regex regex = new Regex(regexpMask);
Match match = regex.Match(fieldContent);
if (match.Success)
{
applyTransform = true;
break;
}
}
if (applyTransform)
{
var currentRunFieldCode = runsInFieldCode[0].Descendants<FieldCode>().FirstOrDefault();
currentRunFieldCode.Text = fieldContent;
runsInFieldCode.RemoveAt(0);
foreach (Run runToRemove in runsInFieldCode)
{
runToRemove.Remove();
}
}
}
}
}
}
Hope this helps!!!

How do I read the Common Name from the client certificate?

Our application needs a piece of data that it is included in the client cert's common name. Currently, I'm trying to get it from HttpContext.Current.Request.ClientCertificate. How do I read this out? Sadly, I'm trying to code this blind while I figure out why SoapUI isn't sending the cert, so I haven't tried much other than reading about the object on MSDN and poking through the empty properties but I'm not sure what I'm looking for. So to recap, what do I need to do to pull out the common name from this cert? TIA
I am maybe too late to answer your question but i hope this would help others who are looking for the way to get common name from certificate.
If you use 'Subject', you might need to trim away other unnecessary information.
For example, CN = localhost,OU = DepartmentName,O = CompanyName,L = Location,S = State,C = Country
Dim store As New X509Store(StoreName.My, StoreLocation.LocalMachine)
store.Open(OpenFlags.ReadOnly)
store.Certificates(0).Subject
But if you use the code below, you will get 'localhost' which directly give you the common name of the certificate.
Dim store As New X509Store(StoreName.My, StoreLocation.LocalMachine)
store.Open(OpenFlags.ReadOnly)
store.Certificates(0).GetNameInfo(X509NameType.SimpleName, False)
Here's the link for reference:-
https://msdn.microsoft.com/en-us/library/system.security.cryptography.x509certificates.x509certificate2.getnameinfo(v=vs.110).aspx
I know a tiny bit about certificates. Here was my workflow:
I started at:
HttpRequest.ClientCertificate
Which lead me to:
HttpClientCertificate (as the return type).
It appears to have a few properties, but none that explicitly are named common name.
Googled: HttpClientCertificate Common Name:
Problem with extracting X509 certificate from Context on web service side
Which had some code:
//extracting Common name from certificate
Subject = cert.Subject.ToString();
Then went to:
HttpClientCertificate.Subject
which remarks:
If String is specified without a subfield, the HttpClientCertificate collection returns a comma-separated list of subfields. For example, C=US, O= Msft.
And with the extreme limited knowledge I do have, I know the Common Name = is in this list. I have no actual way to test this at the moment, but it shouldn't be hard to parse this value for the name you are looking for.
It's good question (+1), I am happy you asked it as it will probably be useful for future readers.
I created a DotNetFiddle Example and even though it uses HttpWebRequest to get an X509Certificate class, it does have a Subject property that returned the following value for https on www.google.com:
CN=www.google.com, O=Google Inc, L=Mountain View, S=California, C=US
So I'm inclined to believe that Subject on the HttpClientCertificate would be the same values (knowing that CN means CommonName).
Just a oneliner in Linq.
var kvs = cert.Subject.Split(',').Select(x => new KeyValuePair<string, string>(x.Split('=')[0], x.Split('=')[1])).ToList();
Returns a generic list. Dont use dictionary here, because Subject can contain duplicate fields.
You may have to adjust due to differences in certificate formats.
Here is some code to do this:
HttpClientCertificate theHttpCertificate = HttpContext.Current.Request.ClientCertificate;
// split the subject into its parts
string[] subjectArray = theHttpCertificate.Subject.Split(',');
string[] nameParts;
string CN = string.Empty;
string firstName = string.Empty;
string lastName = string.Empty;
foreach (string item in subjectArray)
{
string[] oneItem = item.Split('=');
// Split the Subject CN information
if (oneItem[0].Trim() == "CN")
{
CN = oneItem[1];
if (CN.IndexOf(".") > 0)
{// Split the name information
nameParts = CN.Split('.');
lastName = nameParts[0];
firstName = nameParts[1];
}
}
}
The best thing to do is to use the build-in GetNameInfo methods using the name type and the boolean flag
Lets assume you use this extension method:
[return:MaybeNull]
public static X509Certificate2? GetCodeSignCertificate(this Assembly asm)
{
if (asm is null)
{
throw new ArgumentNullException(nameof(asm));
}
if (!File.Exists(asm.Location))
{
return null;
}
using var cert=System.Security.Cryptography.X509Certificates.X509Certificate.CreateFromSignedFile(asm.Location);
if (cert is null)
return null;
return new X509Certificate2(cert);
}
You can then use a type in the class to get the certificate like so:
[TestMethod()]
public void TryGetCodeSigning()
{
//var item = new Walter.CallStack();
var item = new List<string>();
using var cert = item.GetType().Assembly.GetCodeSignCertificate();
Assert.IsNotNull(cert.GetNameInfo(X509NameType.SimpleName,true));
}
to get the name of the certificate signing authority
cert.GetNameInfo(X509NameType.SimpleName,true)
to get the name for who the certificate was signed
cert.GetNameInfo(X509NameType.SimpleName,false)
Have a look at X509NameType and look if this works for you.
I created this extension to handle all individual elements of a subject name.
public enum SubjectNameElements {CN,O,L,OU,S,C,}
public static readonly Dictionary<SubjectNameElements, string> SubNameSybms =
new Dictionary<SubjectNameElements, string>{
{ SubjectNameElements.CN,"CN="},
{ SubjectNameElements.O ,"O="},
{ SubjectNameElements.L ,"L="},
{ SubjectNameElements.OU,"OU="},
{ SubjectNameElements.S ,"S="},
{ SubjectNameElements.C ,"C="},
};
/// <summary>
/// Retrieve CN from subject Name of a certificate
/// </summary>
/// <param name="subjName"></param>
/// <param name="symb"></param>
/// <remarks>
/// Example:
/// GetOneElementFromSubjectName("C=IT, S=Italy, L=Torino, O=Example Italy S.p.A., OU=Example-Client, CN=www.google.com",SubjectNameElements.CN) => www.google.com
/// </remarks>
/// <returns> a string value or empty string in case of invalid options </returns>
public static string GetOneElementFromSubjectName(this X500DistinguishedName subjName, SubjectNameElements symb=SubjectNameElements.CN)
{
string subjNameString = subjName.Name;
try
{
string Symb = SubNameSybms[symb];
string CName = subjNameString.Substring(subjNameString.IndexOf(Symb)).Split(",").First().Replace(Symb, string.Empty).Trim();
return CName;
}
catch (Exception ex)
{
Log.Error("Error in GetOneElementFromSubjectName. Ex.Message: '" + ex.Message + "'. Ex.StackTrace: '" + ex.StackTrace + "'");
return string.Empty;
}
}

Null values gathering data for Aspose Dynamic Slide Generation CRM 2011

I am having with using Aspose to generate dynamic content in slides within Microsoft CRM Dynamics 2011. The data is not populating correctly for several fields and relationals even though the logic in the code seems to be correct and should not yield a null or empty value, and I have confirmed there is indeed data for the fields in the database (The data does populate correctly for some of the fields and relationals). All of these issues occur before the powerpoint itself even starts to be generated, so in reality the issues may not be “Aspose” issues they may just be Web Service / CRM issues with retrieving the data asynchronously.
Aspose Slide Workflow:
1) An HTML web resource includes a Silverlight button to download powerpoint report
2) On button click the Silverlight application Asyncronosously collects all the data needed from the Project entity in CRM through web service requests
3) The data is compiled into a Project class that is then passed over to the ReportGenerator Service and uses the data to populate the parts of the powerpoint (This is the part where we use the custom Aspose stuff)
4) Aspose functionality goes through each specified slide and parses them adding in the data where required.
5) Powerpoint is completed and spit out for the user with the data populated.
These are two examples of where I am having troubles getting data to populate. The project.Pwins value below ends up being null which tells me the lambda expression is not executing appropriately, it remains null at the end of the method which tells me the highlighted line is not even being executed at all.
Example 1:
/// <summary>
/// Retrieves and stores information required for P(win) report, and then tries to send a web service request.
/// </summary>
/// <param name="service"></param>
private void LoadPwin(ReportServiceClient service)
{
string filter = string.Format("?$filter=sfa_Project/Id eq guid'{0}'", GetProjectId());
string url = GetEntitySetAddress("sfa_pwin_competitor") + filter;
WebClient client = new WebClient();
client.DownloadStringCompleted += (sender, args) =>
{
if (args.Error == null)
{
StringReader stream = new StringReader(args.Result);
XmlReader reader = XmlReader.Create(stream);
List<PWin> pwins = new List<PWin>();
List<Dictionary<string, string>> dictionaries = LoadEntitiesFromXml(reader);
foreach (Dictionary<string, string> dictionary in dictionaries)
{
PWin pwin = new PWin();
pwin.CompanyName = ParseDictionaryValue(dictionary["sfa_competitor"]);
pwin.IsBoeing = (ParseDictionaryValue(dictionary["sfa_is_boeing"]) == "true");
pwin.IsDomestic = (ParseDictionaryValue(dictionary["sfa_domestic_or_international"]) == "true");
pwin.AffordabilityWeight = ParseDictionaryValueToNumber(dictionary["sfa_affordability_weight"]);
pwin.AffordabilityScore = ParseDictionaryValueToNumber(dictionary["sfa_affordability_score"]);
pwin.CustomerRelationshipWeight = ParseDictionaryValueToNumber(dictionary["sfa_customer_relationship_weight"]);
pwin.CustomerRelationshipScore = ParseDictionaryValueToNumber(dictionary["sfa_customer_relationship_score"]);
pwin.CustomerAdvantageWeight = ParseDictionaryValueToNumber(dictionary["sfa_customer_advantage_weight"]);
pwin.CustomerAdvantageScore = ParseDictionaryValueToNumber(dictionary["sfa_customer_advantage_score"]);
pwin.CompetitionWeight = ParseDictionaryValueToNumber(dictionary["sfa_competition_weight"]);
pwin.CompetitionScore = ParseDictionaryValueToNumber(dictionary["sfa_competition_score"]);
pwin.CPOBCWeight = ParseDictionaryValueToNumber(dictionary["sfa_cpobc_weight"]);
pwin.CPOBCScore = ParseDictionaryValueToNumber(dictionary["sfa_cpobc_score"]);
pwin.CompanyResourcesWeight = ParseDictionaryValueToNumber(dictionary["sfa_company_resources_weight"]);
pwin.CompanyResourcesScore = ParseDictionaryValueToNumber(dictionary["sfa_company_resources_score"]);
pwin.CompanyResourcesInvestmentWeight = ParseDictionaryValueToNumber(dictionary["sfa_company_resources_investment_weight"]);
pwin.CompanyResourcesInvestmentScore = ParseDictionaryValueToNumber(dictionary["sfa_company_resources_investment_score"]);
pwin.ProgramBackgroundWeight = ParseDictionaryValueToNumber(dictionary["sfa_program_background_weight"]);
pwin.ProgramBackgroundScore = ParseDictionaryValueToNumber(dictionary["sfa_program_background_score"]);
pwin.ContinuityOfEffortWeight = ParseDictionaryValueToNumber(dictionary["sfa_continuity_of_effort_weight"]);
pwin.ContinuityOfEffortScore = ParseDictionaryValueToNumber(dictionary["sfa_continuity_of_effort_score"]);
pwin.ExecutionWeight = ParseDictionaryValueToNumber(dictionary["sfa_execution_weight"]);
pwin.ExecutionScore = ParseDictionaryValueToNumber(dictionary["sfa_execution_score"]);
pwin.TechnicalSolutionWeight = ParseDictionaryValueToNumber(dictionary["sfa_technical_solution_weight"]);
pwin.TechnicalSolutionScore = ParseDictionaryValueToNumber(dictionary["sfa_technical_solution_score"]);
pwin.StrategyToWinWeight = ParseDictionaryValueToNumber(dictionary["sfa_strategy_to_win_weight"]);
pwin.StrategyToWinScore = ParseDictionaryValueToNumber(dictionary["sfa_strategy_to_win_score"]);
pwin.ManagementStrengthWeight = ParseDictionaryValueToNumber(dictionary["sfa_management_strength_weight"]);
pwin.ManagementStrengthScore = ParseDictionaryValueToNumber(dictionary["sfa_management_strength_score"]);
pwin.CustomerPercievedCommitmentWeight = ParseDictionaryValueToNumber(dictionary["sfa_customers_percieved_commitment_weight"]);
pwin.CustomerPercievedCommitmentScore = ParseDictionaryValueToNumber(dictionary["sfa_customers_percieved_commitment_score"]);
pwin.PastPerformanceWeight = ParseDictionaryValueToNumber(dictionary["sfa_past_performance_weight"]);
pwin.PastPerformanceScore = ParseDictionaryValueToNumber(dictionary["sfa_past_performance_score"]);
pwin.RawPWin = ParseDictionaryValueToDecimal(dictionary["sfa_pwin_score"]);
pwin.RelativePWin = ParseDictionaryValueToDecimal(dictionary["sfa_relative_pwin_score"]);
pwins.Add(pwin);
}
project.PWins = new ObservableCollection<PWin>(pwins);
PwinReady = true;
reader.Close();
TrySendRequest(service);
}
};
client.DownloadStringAsync(new Uri(url));
}
Example 2 the project.TeamMembers value below ends up being null:
/// <summary>
/// Retrieves and stores information required for Capture Team Roster report, and then tries to send a web service request.
/// </summary>
/// <param name="service"></param>
private void LoadCaptureTeamRoster(ReportServiceClient service)
{
string filter = string.Format("?$select=sfa_name,sfa_Role&$filter=sfa_Project/Id eq guid'{0}'", GetProjectId());
string url = GetEntitySetAddress("sfa_team_roster") + filter;
WebClient client = new WebClient();
client.DownloadStringCompleted += (sender, args) =>
{
if (args.Error == null)
{
StringReader stream = new StringReader(args.Result);
XmlReader reader = XmlReader.Create(stream);
List<TeamMember> members = new List<TeamMember>();
List<Dictionary<string, string>> dictionaries = LoadEntitiesFromXml(reader);
foreach (Dictionary<string, string> dictionary in dictionaries)
{
TeamMember member = new TeamMember();
member.Name = ParseDictionaryValue(dictionary["sfa_name"]);
member.Role = ParseDictionaryValue(dictionary["sfa_role"]);
members.Add(member);
}
project.TeamMembers = new ObservableCollection<TeamMember>(members);
CaptureTeamRosterReady = true;
reader.Close();
TrySendRequest(service);
}
};
client.DownloadStringAsync(new Uri(url));
}
Here is another example that is not an issue with a relational, but instead is an issue with a field populated on a project entity in CRM that shows up empty after retrieving the data. Both CaptureTeamLeader and ProgramManager end up being empty strings, however the ProjectName and ProjectNumber have no troubles populating
private void LoadCampaignTitle(ReportServiceClient service)
{
project.ProjectName = GetAttributeValue("sfa_name");
project.ProjectNumber = GetAttributeValue("sfa_project_number");
project.CaptureTeamLeader = GetAttributeValue("sfa_capture_team_leader_emp");
project.ProgramManager = GetAttributeValue("sfa_program_manager_emp");
CampaignTitleReady = true;
TrySendRequest(service);
}
Any help would be greatly appreciated. Thanks in advance!
EDIT:
This is the AttributeValue method asked for:
/// <summary>
/// Gets the value of provided attribute from the current page's Xrm data. If for any reason the retrieval fails, returns an empty string.
/// </summary>
/// <param name="attributeName"></param>
/// <returns></returns>
private string GetAttributeValue(string attributeName)
{
// NOTE: This is the preferred way to retrieve data from the CRM. However for some unknown issue,
// this always returns NULL. It will be replaced with directly calling .eval to the window object.
// dynamic Xrm = (ScriptObject)HtmlPage.Window.GetProperty("window.parent.Xrm");
try
{
return HtmlPage.Window.Invoke("getAttributeValue", attributeName).ToString();
}
catch (ArgumentNullException)
{
return string.Empty;
}
catch (ArgumentException)
{
return string.Empty;
}
catch (InvalidOperationException)
{
return string.Empty;
}
}
The solution to this problem was that number 1 The downloadstringasync and other places in my code were doing things asynchronously and therefore the debugger was throwing me off and showing things as null when they were really just being executed later. The actual fix required some changes to a client.xml file in the folder the webservice was being hosted from.

Programmatically modifying an embedded resource before registering/referencing it on the page

Firstly, "Modifying" may be the wrong term, I see a few people have posted online just asking whether they can actually modify an embedded resource. What I am wanting to to, is use a resource in my assembly as a kind of template which I would do a find and replace on before registering it on the page - is this possible?
For example; say I have a few lines of jQuery as an embedded resource in my assembly and in this script I am referencing a CSS class name that can be set by the front-end programmer. Since I do not know what the CSS class will be until implementation, is there a way of going through the embedded resource and replacing, say, $myclass$ with ThisClassName.
Any help would be appreciated, if it's not possible then at least tell me so I can stop chasing my tail.
I have solved my little issue by creating an HTTP Handler. In this instance it's called DynamicClientScript.axd.
I have taken a few cuts from my code to give you an idea. The code below gets the standard embedded resource URL and takes the query string from it to add to the path to my handler.
/// <summary>
/// Gets the dynamic web resource URL to reference on the page.
/// </summary>
/// <param name="type">The type of the resource.</param>
/// <param name="resourceName">Name of the resource.</param>
/// <returns>Path to the web resource.</returns>
public string GetScriptResourceUrl(Type type, string resourceName)
{
this.scriptResourceUrl = this.currentPage.ClientScript.GetWebResourceUrl(type, resourceName);
string resourceQueryString = this.scriptResourceUrl.Substring(this.scriptResourceUrl.IndexOf("d="));
DynamicScriptSessionManager sessMngr = new DynamicScriptSessionManager();
Guid paramGuid = sessMngr.StoreScriptParameters(this.Parameters);
return string.Format("/DynamicScriptResource.axd?{0}&paramGuid={1}", resourceQueryString, paramGuid.ToString());
}
/// <summary>
/// Registers the client script include.
/// </summary>
/// <param name="key">The key of the client script include to register.</param>
/// <param name="type">The type of the resource.</param>
/// <param name="resourceName">Name of the resource.</param>
public void RegisterClientScriptInclude(string key, Type type, string resourceName)
{
this.currentPage.ClientScript.RegisterClientScriptInclude(key, this.GetScriptResourceUrl(type, resourceName));
}
The handler then takes its query string to build the URL to the standard resource. Reads the resource and replaces each key with it's value within the dictionary collection (DynamicClientScriptParameters).
The paramGuid is an identifier used to get the correct script parameter collection.
What the handler does...
public void ProcessRequest(HttpContext context)
{
string d = HttpContext.Current.Request.QueryString["d"];
string t = HttpContext.Current.Request.QueryString["t"];
string paramGuid = HttpContext.Current.Request.QueryString["paramGuid"];
string urlFormatter = "http://" + HttpContext.Current.Request.Url.Host + "/WebResource.axd?d={0}&t={1)";
// URL to resource.
string url = string.Format(urlFormatter, d, t);
string strResult = string.Empty;
WebResponse objResponse;
WebRequest objRequest = System.Net.HttpWebRequest.Create(url);
objResponse = objRequest.GetResponse();
using (StreamReader sr = new StreamReader(objResponse.GetResponseStream()))
{
strResult = sr.ReadToEnd();
// Close and clean up the StreamReader
sr.Close();
}
DynamicScriptSessionManager sessionManager = (DynamicScriptSessionManager)HttpContext.Current.Application["DynamicScriptSessionManager"];
DynamicClientScriptParameters parameters = null;
foreach (var item in sessionManager)
{
Guid guid = new Guid(paramGuid);
if (item.SessionID == guid)
{
parameters = item.DynamicScriptParameters;
}
}
foreach (var item in parameters)
{
strResult = strResult.Replace("$" + item.Key + "$", item.Value);
}
// Display results to a webpage
context.Response.Write(strResult);
}
Then in my code where I want to reference my resource I use the following.
DynamicClientScript dcs = new DynamicClientScript(this.GetType(), "MyNamespace.MyScriptResource.js");
dcs.Parameters.Add("myParam", "myValue");
dcs.RegisterClientScriptInclude("scriptKey");
Then say my script resource contains:
alert('$myParam$');
It will output as if it was:
alert('myValue');
My code also does some caching (using the DynamicScriptSessionManager) but you get the idea...
Cheers
In you codebehind you could read the contents of the embedded resource, switch out whatever you want, and then write the new contents to the response. Something like this:
protected void Page_Load(object sender, EventArgs e)
{
string contents = ReadEmbeddedResource("ClassLibrary1", "ClassLibrary1.TestJavaScript.js");
//replace part of contents
//write new contents to response
Response.Write(String.Format("<script>{0}</script>", contents));
}
private string ReadEmbeddedResource(string assemblyName, string resouceName)
{
var assembly = Assembly.Load(assemblyName);
using (var stream = assembly.GetManifestResourceStream(resouceName))
using(var reader = new StreamReader(stream))
{
return reader.ReadToEnd();
}
}

Categories