Is there any way which can map my response of API into the required format in azure APIM
e.g. if my API gave a response like
customer: {
"id" : 123
}
the output I will be needing is
customer: {
"customerID": 123
}
Is there any way without using a set-body policy?
or is there any way where I can define logic which can map Id value to customerID
There are two options:
Simple JSON
If you have simple JSON as you have shown in the question, then Find and Replace transformation policy can be helpful.
Please note that this policy would just perform string replace. It does not know anything about JSON format. So you have to be extra careful while replacing anything.
But I assume the JSON might not be that simple in real world scenarios.
Complex JSON
You can implement your custom logic by using Set Body Transformation Policy.
Code would be something like below. You can get the JObject and then query and update it.
<choose>
<when condition="#(context.Response.StatusCode == 200">
<set-body>
#{
JObject inBody = context.Request.Body.As<JObject>();
// Razor view syntax so you can write code as per your need
JObject customer= (JObject)inBody["customer"];
var customerIdValue = ((string)customer["id"]);
customer["CustomerId"] = customerId;
customer.Property("id").Remove();
// assuming customer has a description property
customer.Property("description").AddAfterSelf(new JProperty("customerId", customerIdValue));
return inBody.ToString();
}
</set-body>
</when>
</choose>
You could use the Find and Replace String in Body policy for a simple solution.
Could also use set-body with liquid template in outbound of your operation:
<set-body template="liquid">{
"customer": {
"customerID": {{body.customer.id}}
}
}</set-body>
Related
We have an application that requires some fields to be present. If those fields aren't present we will return a 400 response explaining what is missing in a proper error message. Adding APIM to the mix complicates it a lot it seems. Since APIM know that the field is required it looks like it will short curcuit and return 404 with a generic message instead of our self explanatory message of what is wrong.
Is it a way to turn of this functionality for APIM?
I'm getting the same issue and I endup changing my approach. What I did was to configure it on the Application side and use FluentValidation to make the querystring parameters required. So, my model now looks something like this:
using FluentValidation;
public class UrlQueryParameters
{
public string PropertyA { get; set; }
public string PropertyB { get; set; }
}
public class UrlQueryParametersValidator : AbstractValidator<UrlQueryParameters>
{
public UrlQueryParametersValidator()
{
RuleFor(o => o.PropertyA)
.NotEmpty()
.WithMessage("The 'PropertyA' parameter was missing or a value was not provided.");
RuleFor(o => o.PropertyB)
.NotEmpty()
.WithMessage("The 'PropertyB' parameter was missing or a value was not provided.");
}
}
The preceding code defines a couple of validation rules with custom messages for PropertyA and PropertyB properties.
Now, enable FluentValidation as the default validation mechanism for our application by adding the following code at ConfigureServices method of Startup.cs file:
public void ConfigureServices(IServiceCollection services) {
// Rest of the code omitted for brevity
services
.AddControllers()
.AddFluentValidation(fv =>
{
fv.DisableDataAnnotationsValidation = true;
// The following line registers ALL public validators in the assembly containing UrlQueryParametersValidator
// There is no need to register additional validators from this assembly.
fv.RegisterValidatorsFromAssemblyContaining<UrlQueryParametersValidator>(lifetime: ServiceLifetime.Singleton);
});
}
At this point, your API endpoints should validate the required parameters from the request and APIM should not short-circuit the request by throwing 404 Not Found when you try to access /api/foo/{id}.
The reason why this works because Swashbuckle doesn't automatically import validation rules from FluentValidation. Meaning, the properties PropertyA and PropertyB won't be marked as required when viewing them in the Swagger UI. This is the downside for this approach as the required querystring parameters from the Swagger UI will not be marked as required which could be confusing to consumers. But to me, returning the correct StatusCode with meaningful message to consumers is more important and that's why I will stick to this workaround for the time being. You could try using the MicroElements.Swashbuckle.FluentValidation to altleast set/marked the parameters as required in the Swagger UI schema. But that's just about it.
I blogged about this it here: Dirty Hack on Making the Required QueryString Params to Work in Azure APIM
At API/Product/Global level policy add on-error section, use choose policy to check if operation was found or not:
<choose>
<when condition="#(context.LastError.Source == "configuration" && context.LastError.Reason == "OperationNotFound")">
<return-response>
<set-status code="400" reason="Bad Request" />
</return-response>
</when>
</choose>
I have a URL with template parameters.
https://test.azure-api.net/HelperFunction/{siteId}/lots/InventoryItem/{itemId}
https://test.azure-api.net/HelperFunction/122/lots/InventoryItem/12
I wanted to read the template/path parameters in inbound policy section.
I am trying as below. But it will only fetch the query string parameters. I wanted to get the path parameters.
<inbound>
<set-body>#{
JObject transBody = new JObject();
transBody.Add("Arguments",
new JObject
{
{"method", context.Request.Method},
{"parameters", context.Request.Url.QueryString},
});
//Add all json properties as arg
transBody.Add("UriPath", context.Request.Url.Path);
return transBody.ToString();
}</set-body>
<base />
</inbound>
My requirement would be to read route parameters as
"siteId" : 122,
"itemId" : 12
Any help would be appreciated.
context.Request.MatchedParameters["siteId"] and context.Request.MatchedParameters["itemId"]
Rest of context variable can be found here: https://learn.microsoft.com/en-us/azure/api-management/api-management-policy-expressions#ContextVariables
I want to fetch a value from json and store it in a variable in Azure API Management.
JSON Example which is coming in request Body is
{
"ItemCode": 1,
"ItemName": "USA",
"typeCode": "REG"
}
I need to fetch value of ItemCode and typeCode and store it in a variable.
I have check on Microsoft Docs and all it gives me to transform body by using liquid template which I suppose is of no use in my requirement.
I have stored a JSON in a variable like
set-variable name="varItemCode" value="#(context.Request.Body.As<String>(preserveContent:true))" />
Since this is stored a string I am not able to traverse the JSON Object.
I was able to do it
<kbd>set-variable name="varTypeCode" value="#{
JObject json = JObject.Parse(context.Variables.GetValueOrDefault<string>("varBody"));
var typeCode = json.GetValue("typeCode");
return typeCode;
}" />
this is working
<set-variable name="validationResults" value="#(context.Request.Body.As<JObject>())" />
<set-variable name="operation" value="#((string)((JObject)context.Variables["validationResults"])["operation"])" />
I have an Azure Custom Connector to a SOAP API that is configured with SOAP to REST. One of the methods have datetime as input:
I am genereting the DateTime with the following expression:
formatDateTime(addDays(utcNow(), -1), 's')
With the following raw input from Logic Apps, I get datetime format exception
{
"method": "post",
"path": "/MethodWithDates",
"retryPolicy": {
"type": "None"
},
"body": {
"MethodWithDates": {
"timefrom": "2019-03-18T15:59:03",
"timeto": "2019-03-19T15:59:03"
}
}
Errormessage from API:
The value '3/18/2019 3:59:03 PM' cannot be parsed as the type 'DateTime'.'
Notice how the datetime format has changed from raw output to recieved in the API. This leads me to believe the custom connector somehow changes the time format.
If I call the same endpoint with SOAP UI with the following SOAP request I get correct response. Notice the Datetime format is same as in RAW input from Logic app:
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:tem="http://tempuri.org/">
<soapenv:Header/>
<soapenv:Body>
<tem:MethodWithDates>
<tem:timefrom>2019-03-18T15:13:31</tem:timefrom>
<tem:timeto>2019-03-19T15:13:31</tem:timeto>
</tem:MethodWithDates>
</soapenv:Body>
</soapenv:Envelope>
Interestingly, this seems only to happen for the "s" format specifier, if I format the value in any other way it is passed through in the format I specify. I still get an error in the API as its a WCF API and it seems to require the "s" format.
I can reproduce the same error when SOAP service has a Datetime input which I believe is not parsing correctly.
I am able to make this work by changing the input Datetime fields in Soap Service to string.
Non Working SOAP Service code:
public string GetDaysBetweenDates(DateTime timefrom, DateTime timeto)
{
double value = (timeto - timefrom).TotalDays;
return string.Format("Difference is: {0}", value);
}
Working WSDL Code
public string GetDaysBetweenDates(string timefrom, string timeto)
{
DateTime fromdate = DateTime.Parse(timefrom);
DateTime toDate = DateTime.Parse(timeto);
double value = (fromdate - toDate).TotalDays;
return string.Format("Difference is: {0}", value);
}
u/KetanChawda-MSFT answer is good enough, if you are able to actually change the webservice, but since this was out of our control on this one we had to do something else.
We created a separate SOAP custom connector, just for this one method with SOAP pass through.
The connector has one method, configured like this, with a default WCF API:
Url - http://hostname/Service1.svc/SoapPassThrough
Add two custom headers: Content-Type text/xml and SOAPAction methodname (ours: http://tempuri.org/IService1/methodname where tempuri is namespace
Set body to {} (Empty JSON Object)
In your logic app, you can then create a variable that contain all of the XML for a standard Soap Request. I Used SOAP UI to create a SOAP request and just pasted in the XML from the generated request. This variable can be used as body in the logic app when you consume the service.
This resource can be helpful for this: https://blogs.msdn.microsoft.com/david_burgs_blog/2018/05/03/friendlier-soap-pass-through-with-logic-app-designer-ux/
From what we have concluded, it seems like the custom connector actually sends a string datatype instead of a datetime. Creating the XML request ourself seems to fix this issue.
In the Youtube API, there is the power to request a "partial feed".
This allows the app developer to tailor the size and sturcture of the data returned, by specifying which "fields" to return.
i.e. GET api/person/1?fields=(id,email) would return a DTO containing only the id and the email fields, not the whole person response.
How would you attempt this using ServiceStack? Is there some way to attach a callback to the serialiser to control which properties to include in the response object?
From my experience servicestack only returns fields that actually has data. If my experience is correct then all you would need to do is figure out the best way to architect the request so that it is asking for specific data to return, this way you would only populate the response with data requested thus servicestack would only return that.
I implemented this for an API that only returns JSON.
First I created two structs to (de)serialize and interpret the "fields" query argument recursive syntax:
FieldSelector, which specifies a field and possibly its children FieldSelection enclosed between parenthesis;
FieldsSelection, which is a comma-separated list of FieldSelector.
I've used structs instead of classes because, AFAIK, you can't override class (de)serialization from/to URLs in ServiceStack. With structs you can do it by overriding ToString (serializer) and providing a constructor accepting a string as parameter (deserializer).
Then you include this on every request DTO that returns JSON:
FieldsSelection Fields { get; set; }
On a custom ServiceRunner<T>.OnAfterExecute you serialize the response DTO to JSON, parse it with ServiceStack.Text's JsonObject and apply the fields selection recursively with a method like this:
private static JsonObject Apply(this JsonObject json, FieldsSelection fieldMask)
{
IEnumerable<string> keysToRemove = json.Keys.ToList().Except(fieldMask.Keys);
foreach (var key in keysToRemove)
json.Remove(key);
foreach (var selector in fieldMask.Selectors.Values.Where(s => s.HasSubFieldsSelection))
{
var field = json[selector.Field];
if (field == null)
continue;
switch (field[0])
{
case '{':
json[selector.Field] = Apply(json.Object(selector.Field), selector.SubFieldsSelection).ToJson();
break;
case '[':
var itensArray = json.ArrayObjects(selector.Field);
for (int i = 0; i < itensArray.Count; i++)
itensArray[i] = Apply(itensArray[i], selector.SubFieldsSelection);
json[selector.Field] = itensArray.ToJson();
break;
default:
throw new ArgumentException("Selection incompatible with object structure");
}
}
return json;
}
Then you return the result as your response DTO. I've also implemented negative fields selectors (fields=-foo selects all DTO fields except foo), but you get the idea.
Look at ServiceStack.Text.JsConfig properties, they have all the hooks and customizations ServiceStack's text serializers support. Specifically the hooks that allow you to custom deserialization are:
JsConfig<T>.DeserializeFn
JsConfig<T>.RawDeSerializeFn
JsConfig<T>.OnDeserializedFn
We were able to implement said filtering by adding custom service runner and using some reflection in it to construct ExpandoObject with required field set by response DTO. See this for more info on service runners.