MultiValue Parameter - asp.net

I'm working on a web application that renders a reporting services report as a PDF and one of my requirements is to allow for multiple locations to be included on the same report. To handle this, I have created a multi-value report parameter which seems to do the trick when using the RS UI, etc. But, I am using the webservice for reporting services and cannot for the life of me figure out how to set the value of the parameter to be identified as having multiple values.
I've tried simply setting it as "LOC1,LOC2", but that is being picked up as a single value. I have also tried "LOC1" + System.Environment.NewLine + "Loc2".

You can send it through as a comma-delimited string if you're willing to parse it on the other end. A lot of languages have a String.Split(",") style method you can use for that.
Either that, or you can construct an array (or list, or collection) and pass that through as the parameter, though this would involve changing the contract on the webservice method.

Figured it out, you have to each value separately under the same name, snippet:
//Register parameters
ArrayList<ParameterValue> parmValues;
for(Map.Entry<String,String> entry:reportParams.entrySet()) {
//is it multi-value?
if(entry.getValue().contains(",")) {
//yes, add multiple ParameterValues under the same name
// with each different value
for(String mval:entry.getValue().split(",")) {
ParameterValue pv = new ParameterValue();
pv.setName(entry.getKey());
pv.setValue(mval.trim());
parmValues.add(pv);
}
} else {
//no, just a single value
ParameterValue pv = new ParameterValue();
pv.setName(entry.getKey());
pv.setValue(entry.getValue());
parmValues.add(pv);
}
}

Related

Set PurchReqLine.BuyingLegalEntity default value to blank

I encountered a problem in the development, requesting a new purchase request line of a purchase with a legal person with a default value of empty
I tried a variety of methods, the default value can not be overriden.
The following is my code.
[ExtensionOf(formDataSourceStr(PurchReqTable, PurchReqLine))]
final class IWS_PurchReqTable_FDS_Extension
{
public void initValue()
{
next initValue();
//ttsbegin;
PurchReqLine purchReqLine = this.cursor();
purchReqLine.BuyingLegalEntity = 0;
purchReqLine.modifiedField(fieldNum(PurchReqLine,BuyingLegalEntity));
this.rereadReferenceDataSources(); //Refresh value
this.reread();
this.research(1);
FormReferenceGroupControl BuyingLegalEntity = this.formRun().design().controlName(formControlStr(PurchReqTable, PurchReqLine_BuyingLegalEntity));
FormStringControl BuyingLegalEntity_DataArea = this.formRun().design().controlName(formControlStr(PurchReqTable, PurchReqLine_BuyingLegalEntity_DataArea));
BuyingLegalEntity.value(0);
BuyingLegalEntity.resolveChanges();
BuyingLegalEntity.referenceDataSource().research(1);
BuyingLegalEntity.modified();
//BuyingLegalEntity_DataArea.text('');
//BuyingLegalEntity_DataArea.modified();
purchReqLine.BuyingLegalEntity = 0;
purchReqLine.modifiedField(fieldNum(PurchReqLine,BuyingLegalEntity));
//purchReqLine.update();
//purchReqLine.insert();
//this.rereadReferenceDataSources();
//this.refresh();
//this.reread();
//this.resetLine();
//ttscommit;
}
//End
}
It is not totally clear to me what you are trying to do.
Most values are "born" zero or blank and if that is not the case for this field, something else is setting the field, maybe after your code in initValue is called. The cross reference may be of good value here to find the code that references the field.
First of, you should definitely not reference the controls, also calling modifiedField and research from here is a total no-go.
For a start try this:
public void initValue()
{
next initValue();
purchReqLine.BuyingLegalEntity = 0;
}
It simply sets the field to zero. Do not worry about the field control, it will be rendered from the buffer value after the call to initValue.
If that does not solve your problem, something else is setting the field. You can set a breakpoint here, then follow to code until the field is set. Also add the value to the watch list, maybe do conditional debugging.
If another extension for this datasource exist it may override your behaviour as the execution order of extensions is arbitrary.

Handling reads of Cosmos DB container with multiple types?

I'd like to store several different object types in a single Cosmos DB container, as they are all logically grouped and make sense to read together by timestamp to avoid extra HTTP calls.
However, the Cosmos DB client API doesn't seem to provide an easy way of doing the reads with multiple types. The best solution I've found so far is to write your own CosmosSerializer and JsonConverter, but that feels clunky: https://thomaslevesque.com/2019/10/15/handling-type-hierarchies-in-cosmos-db-part-2/
Is there a more graceful way to read items of different types to a shared base class so I can cast them later, or do I have to take the hit?
Thanks!
The way I do this is to create the ItemQueryIterator and FeedResponse objects as dynamic and initially read them untyped so I can inspect a "type" property that tells me what type of object to deserialize into.
In this example I have a single container that contains both my customer data as well as all their sales orders. The code looks like this.
string sql = "SELECT * from c WHERE c.customerId = #customerId";
FeedIterator<dynamic> resultSet = container.GetItemQueryIterator<dynamic>(
new QueryDefinition(sql)
.WithParameter("#customerId", customerId),
requestOptions: new QueryRequestOptions
{
PartitionKey = new PartitionKey(customerId)
});
CustomerV4 customer = new CustomerV4();
List<SalesOrder> orders = new List<SalesOrder>();
while (resultSet.HasMoreResults)
{
//dynamic response. Deserialize into POCO's based upon "type" property
FeedResponse<dynamic> response = await resultSet.ReadNextAsync();
foreach (var item in response)
{
if (item.type == "customer")
{
customer = JsonConvert.DeserializeObject<CustomerV4>(item.ToString());
}
else if (item.type == "salesOrder")
{
orders.Add(JsonConvert.DeserializeObject<SalesOrder>(item.ToString()));
}
}
}
Update:
You do not have to use dynamic types if want to create a "base document" class and then derive from that. Deserialize into the documentBase class, then check the type property check which class to deserialize the payload into.
You can also extend this pattern when you evolve your data models over time with a docVersion property.

Relational Query - 2 degrees away

I have three models:
Timesheets
Employee
Manager
I am looking for all timesheets that need to be approved by a manager (many timesheets per employee, one manager per employee).
I have tried creating datasources and prefetching both Employee and Employee.Manager, but I so far no success as of yet.
Is there a trick to this? Do I need to load the query and then do another load? Or create an intermediary datasource that holds both the Timesheet and Employee data or something else?
You can do it by applying a query filter to the datasource onDataLoad event or another event. For example, you could bind the value of a dropdown with Managers to:
#datasource.query.filters.Employee.Manager._equals
- assuming that the datasource of the widget is set to Timesheets.
If you are linking to the page from another page, you could also call a script instead of using a preset action. On the link click, invoke the script below, passing it the desired manager object from the linking page.
function loadPageTimesheets(manager){
app.showPage(app.pages.Timesheets);
app.pages.Timesheets.datasource.query.filters.Employee.Manager._equals = manager;
app.pages.Timesheets.datasource.load();
}
I would recommend to redesign your app a little bit to use full power of App Maker. You can go with Directory Model (Manager -> Employees) plus one table with data (Timesheets). In this case your timesheets query can look similar to this:
// Server side script
function getTimesheets(query) {
var managerEmail = query.parameters.ManagerEmail;
var dirQuery = app.models.Directory.newQuery();
dirQuery.filters.PrimaryEmail._equals = managerEmail;
dirQuery.prefetch.DirectReports._add();
var people = dirQuery.run();
if (people.length === 0) {
return [];
}
var manager = people[0];
// Subordinates lookup can look fancier if you need recursively
// include everybody down the hierarchy chart. In this case
// it also will make sense to update prefetch above to include
// reports of reports of reports...
var subortinatesEmails = manager.DirectReports.map(function(employee) {
return employee.PrimaryEmail;
});
var tsQuery = app.models.Timesheet.newQuery();
tsQuery.filters.EmployeeEmail._in = subortinatesEmails;
return tsQuery.run();
}

Keyword has incorrect values in custom meta

I have a category with keywords which in their tern have metadata schema. That schema consist of two fields and each of them is category. Very simple structure, but during publishing it resolves those metadata keyword fields into wrong tcm uris instead of title of the keyword, like the following:
2) Content of the deployer package
<tcmc:Topic rdf:about="tcm:10-11325-1024">
<rdfs:label>Analytics and optimization</rdfs:label>
<rdfs:comment>Analytics and optimization</rdfs:comment>
<tcmt:key>Analytics and optimization</tcmt:key>
<tcmt:isAbstract>false</tcmt:isAbstract>
<tcmt:isRoot>true</tcmt:isRoot>
<tcmt:metadata rdf:parseType="Literal">
<Metadata xmlns="uuid:a30b06d3-b6c5-4c2e-a53b-2b88771370ed">
<Divisions xlink:title="cma" xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="tcm:0-17737-1024">cma</Divisions>
<InterestProfile xlink:title="CMAAnalytics" xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="tcm:0-11175-1024">CMAAnalytics</InterestProfile>
</Metadata>
</tcmt:metadata>
</tcmc:Topic>
3) In code where I query Tridion it returns these uris:
TaxonomyFactory taxonomyFactory = new TaxonomyFactory();
TKeyword taxonomy = taxonomyFactory.GetTaxonomyKeywords(“tcm_of_the_category”);
if (taxonomy != null && taxonomy.KeywordChildren != null)
{
foreach (var item in taxonomy.KeywordChildren) //keyword metadata contains tcm uri with zero instead of title
{
Keyword keywordChildren = item as Keyword;
if (keywordChildren != null)
{
. . .
}
}
}
Does anyone have any ideas what might cause such an issue?
At a glance, my guess is that the internal template used to transform the categories is reading the metadata field data directly from the DB (or near enough in the BL layer) and not applying any blueprinting rules to it (likely for performance).
If you look at TCM Uris in content, when stored in the database, they all use 0 as their publication ID, and this ID is modified at "read" time.
Your call: You can call this a defect, ask Tridion to fix it, and it will degrade the performance of publishing a category, or you can deal with it in the delivery side - you know the publication Uri is 0, and you know you need to replace it with the current publication ID if you intend to use it for any purpose.
EDIT
So I went back and did some quick hacking. Indeed you can't load the keyword's content because, according to Tridion, the "Value" of field "Divisions" is the keyword URI. No way around that.
Quick way around this: load the keyword in question:
TaxonomyFactory tf = new TaxonomyFactory();
Keyword taxonomy = tf.GetTaxonomyKeywords("tcm:5-369-512");
if(taxonomy != null && taxonomy.KeywordChildren != null)
{
foreach (Keyword item in taxonomy.KeywordChildren)
{
NameValuePair key = (NameValuePair) item.KeywordMeta.NameValues["Field1"];
string correctUri = key.Value.ToString().Replace("tcm:0-", "tcm:5-");
Keyword theOtherKeyword = tf.GetTaxonomyKeyword(correctUri);
string title = theOtherKeyword.KeywordName;
}
}
Now... you probably want to be a bit smarter than me on that creative publication ID rewrite :)
You can see the field as a Component Link, you link to a specific Keyword item (object). Therefor you primarily get the URI, and I don't think that it resolves automatically to the Value property.
So the next step would be to obtain the Keyword object using the URI, and possibly construct the URI to include the right publication context.

Change Single URL query string value

I have an ASP.NET page which takes a number of parameters in the query string:
search.aspx?q=123&source=WebSearch
This would display the first page of search results. Now within the rendering of that page, I want to display a set of links that allow the user to jump to different pages within the search results. I can do this simply by append &page=1 or &page=2 etc.
Where it gets complicated is that I want to preserve the input query string from the original page for every parameter except the one that I'm trying to change. There may be other parameters in the url used by other components and the value I'm trying to replace may or may not already be defined:
search.aspx?q=123&source=WebSearch&page=1&Theme=Blue
In this case to generate a link to the next page of results, I want to change page=1 to page=2 while leaving the rest of the query string unchanged.
Is there a builtin way to do this, or do I need to do all of the string parsing/recombining manually?
You can't modify the QueryString directly as it is readonly. You will need to get the values, modify them, then put them back together. Try this:
var nameValues = HttpUtility.ParseQueryString(Request.QueryString.ToString());
nameValues.Set("page", "2");
string url = Request.Url.AbsolutePath;
string updatedQueryString = "?" + nameValues.ToString();
Response.Redirect(url + updatedQueryString);
The ParseQueryString method returns a NameValueCollection (actually it really returns a HttpValueCollection which encodes the results, as I mention in an answer to another question). You can then use the Set method to update a value. You can also use the Add method to add a new one, or Remove to remove a value. Finally, calling ToString() on the name NameValueCollection returns the name value pairs in a name1=value1&name2=value2 querystring ready format. Once you have that append it to the URL and redirect.
Alternately, you can add a new key, or modify an existing one, using the indexer:
nameValues["temp"] = "hello!"; // add "temp" if it didn't exist
nameValues["temp"] = "hello, world!"; // overwrite "temp"
nameValues.Remove("temp"); // can't remove via indexer
You may need to add a using System.Collections.Specialized; to make use of the NameValueCollection class.
You can do this without all the overhead of redirection (which is not inconsiderable). My personal preference is to work with a NameValueCollection which a querystring really is, but using reflection:
// reflect to readonly property
PropertyInfo isReadOnly = typeof(System.Collections.Specialized.NameValueCollection).GetProperty("IsReadOnly", BindingFlags.Instance | BindingFlags.NonPublic);
// make collection editable
isReadOnly.SetValue(this.Request.QueryString, false, null);
// remove
this.Request.QueryString.Remove("foo");
// modify
this.Request.QueryString.Set("bar", "123");
// make collection readonly again
isReadOnly.SetValue(this.Request.QueryString, true, null);
Using this QueryStringBuilder helper class, you can grab the current QueryString and call the Add method to change an existing key/value pair...
//before: "?id=123&page=1&sessionId=ABC"
string newQueryString = QueryString.Current.Add("page", "2");
//after: "?id=123&page=2&sessionId=ABC"
Use the URIBuilder Specifically the link textQuery property
I believe that does what you need.
This is pretty arbitrary, in .NET Core at least. And it all boils down to asp-all-route-data
Consider the following trivial example (taken from the "paginator" view model I use in virtually every project):
public class SomeViewModel
{
public Dictionary<string, string> NextPageLink(IQueryCollection query)
{
/*
* NOTE: how you derive the "2" is fully up to you
*/
return ParseQueryCollection(query, "page", "2");
}
Dictionary<string, string> ParseQueryCollection(IQueryCollection query, string replacementKey, string replacementValue)
{
var dict = new Dictionary<string, string>()
{
{ replacementKey, replacementValue }
};
foreach (var q in query)
{
if (!string.Equals(q.Key, replacementKey, StringComparison.OrdinalIgnoreCase))
{
dict.Add(q.Key, q.Value);
}
}
return dict;
}
}
Then to use in your view, simply pass the method the current request query collection from Context.Request:
<a asp-all-route-data="#Model.NextPageLink(Context.Request.Query)">Next</a>

Resources