Web API OData function with TimeSpan - c#

I have a Web API OData v4 service that I've created. I'm trying to create a bound function that has a TimeSpan parameter having a signature defined in the OData controller similar to the below:
public IQueryable<ProductionRecordDTO> GetProduction(
[FromODataUri]DateTimeOffset startDate,
[FromODataUri]DateTimeOffset endDate,
[FromODataUri]TimeSpan sampleInterval)
It is configured in the OData model builder as below:
var getProduction = builder.EntitySet<ProductDTO>("Products").EntityType.Collection.Function("GetProduction");
getProduction.Namespace = "ProductsService";
getProduction.ReturnsCollection<ProductionRecordDTO>();
getProduction.Parameter<DateTimeOffset>("StartDate");
getProduction.Parameter<DateTimeOffset>("EndDate");
getProduction.Parameter<TimeSpan>("SampleInterval");
When run, the model is seemingly properly created, the metadata description show that the "SampleInterval" is properly defined as an Edm.Duration type.
When I attempt to call this method however with a URL such as:
http://dev-pc/odata/Products/ProductsService.GetProduction(StartDate=2014-01-01T00:00:00Z, EndDate=2017-01-01T00:00:00Z, SampleInterval=P1Y)
An ODataException is thrown with the message 'SampleInterval=P1Y' is not in scope. The same is true for every ISO 8601 duration format variation I give it.
Using:
Microsoft.OData.Core - v6.15.0
Microsoft.AspNet.OData - v5.9.1
Any assistance offered would be greatly appreciated.

I found the cause. Parameters of Edm.Duration apparently cannot be interpreted litterally and need to have a type wrapped around them, e.g. duration'P1D' In this case a correct call would have been:
http://dev-pc/odata/Products/ProductsService.GetProduction(StartDate=2014-01-01T00:00:00Z, EndDate=2017-01-01T00:00:00Z, SampleInterval=duration'P1D')
That said, Microsoft's implementation doesn't seem to accept periodic kinds larger than days. P1W, P1M and P1Y are all rejected.

Related

Force OData DateTime format

Is it possible to force the way DateTimes are displayed in OData while keeping all functionnalities of IQueryable<> ?
Actually, I created a basic endpoint exposing data from my Database. One of the property is a DateTime and is displayed like that :
"MyDate": "2017-01-07T00:00:00",
However, I'd like to display it with another DateTime format, like dd/MM/yyyy
I tried this promising answer but my property has still the same undesired format when I call my endpoint with Postman.
How to force DateTime format in OData, while preserving OData filtering on this column and let the client decides to query, for example, all data with MyDate>= n days ?
However, I'd like to display it with another DateTime format, like dd/MM/yyyy
Then do not display it directly. The date format in odata is defined in the specs do your code knows what to expect and can then format it for public consumption IF it desires to show it. The whole concept of a standard is that people consuming it know what to expect. In particular - if you write your classes manually to consume them you do something wrong, the idea behind odata is that a code generator generates the client code, and it need to know waht to parse (into a javascript odata class).
Odata is NOT supposed to necessariyl be presented directly to the user. The date format is part of the standard. If you change it, it is not odata.
Oh, and another note: The datetime foramt you see there is actually a standard for which Javascript has methods and libraries to manipulate them. WHich is another reason you WANT this. Your custom format is something you have to write all the libraries for yourself.
I am adding my solution here as this was my first hit on Google.
I ran into the same issue, and tried the same solution with no success.
Note that this answer is based on an Asp.NET Core Web Api.
I based my answer on WebAPI OData Datetime serialization
but the way they added the Custom payload value converter doesn't seem applicable anymore.
You have to create a class derived from ODataPayloadValueConverter on which you override the ConvertToPayloadValue function:
namespace [YOUR_NAMESPACE_HERE];
using Microsoft.OData;
using Microsoft.OData.Edm;
public class CustomODataPayloadConverter : ODataPayloadValueConverter
{
public override object ConvertToPayloadValue(object value, IEdmTypeReference edmTypeReference)
{
if (edmTypeReference.PrimitiveKind() == EdmPrimitiveTypeKind.DateTimeOffset)
{
var dateTimeOffset = (DateTimeOffset)value;
return dateTimeOffset.ToString("dd/MM/yyyy");
}
return base.ConvertToPayloadValue(value, edmTypeReference);
}
}
You add the payload value converter in the Action<IServiceCollection> which is the 3rd parameter in ODataOptions.AddRouteComponents.
builder.AddOData(options =>
{
options.AddRouteComponents("api/v1", modelBuilder.GetEdmModel(), actions =>
{
actions.AddSingleton(typeof(ODataPayloadValueConverter), new CustomODataPayloadConverter());
});
});
I am accessing the ODataOptions inside IMVCBuilder.AddOData which in turn is an extension method.
using Microsoft.AspNetCore.OData;
Which is found in the 'Microsoft.AspNetCore.OData.Mvc' NuGet package.
My Postman response:
{
"#odata.context": "http://localhost:5267/api/v1/$metadata#ENTITY_NAME",
"value": [
{
"Id": "98aa1283-f17f-4d12-8e51-59a4ead2a23e",
"myDateTime": "28/09/2022"
}
]
}
where myDateTime is ofcourse, a DateTime.

How to configure swagger to make complex [FromUri] GET parameters show up nicely in Swagger

I've been trying to figure this out for about a week now. It's time to ask S.O.
I have 4 overall goals here:
The controller code needs to use ViewModel request inputs for validation reasons. (Controller Snippet)
Client code for my API should use a nice model syntax. (Client Code Snippet)
For the swagger UI page, I would like the "Try me" interface to be usable. Either a bunch of text boxes, or a text area for a json blob to serialize and send over.
GET request
Client Code Snippet:
var response = client.GetUserProductHistory(new Models.UserProductHistoryRequest() {
Locale = "en-US",
UserPuid = "FooBar"
});
Controller Snippet
[HttpGet]
[HasPermission(Permissions.CanViewUserProductHistory)]
public JsonPayload<UserProductHistoryResponse> GetUserProductHistory([FromUri]UserProductHistoryRequest model)
{
JsonPayload<UserProductHistoryResponse> output = new JsonPayload<UserProductHistoryResponse>();
return output;
}
I have tried using [FromBody]. It looks great, but I get an error that says 'GET requests do not support FromBody'.
I tried using [FromUri], but then the generated client gives me like 15 method parameters per call in the generated client.
I tried using [FromUri], and operation filters so that the parameters would be condensed into Ref parameters (complex objects as defined by the spec). This actually worked decently for the client generation and the server side. Problem is, the UI for swagger looks really lame. A single TEXT box that you can't actually use very well. If I can figure out how to get the Swagger UI to change the appearance of the [FromUri] request to more closely match the [FromBody] UI, I will be in good shape here. Any ideas or pre-existing content that would point me in the right direction here?
Swagger is not the limitation - REST itself is. By definition of REST, web servers should ignore the incoming request body on all HTTP GET methods. ASP.NET enforces this convention, which is why you it doesn't allow you to use [FromBody] on the GET method.
When designing a REST API, the better practice is to use POST methods for an actual search. This will allow to use [FromBody], and as a bonus, Swagger will behave the way you want it to. See here for a supporting opinion: https://stackoverflow.com/a/18933902/66101

REST API different resources for GET and POST/PUT?

I'm currently in the process of designing as RESTful of an API as I can using Microsoft's Web API 2 in C#. What I'm struggling on is how best to represent resources or the proper way to do it where the GET call and POST/PUT are very different.
For example say I have something calls states that have an id, name, status, etc., these can be assigned to a document. So I have a route like this /documents/{id}/states/ . If I call a GET here I need to get the full list of all assigned states including their id, name, etc.
However, in order to change which states are assigned to the document I simply need to pass the id. I cannot do this individually, it must be an array that gets sent up since users may be interacting with hundreds or thousands at a time.
So in this case I have a few issues. I don't even know if POST or PUT is correct here, and second whichever one it is can I just take in an array of integers?
In your case, I would suggest PUT is the method you would be wanting to use, as you know the location of the resource that you are updating. For more info, see here: http://restcookbook.com/HTTP%20Methods/put-vs-post/
In ASP.NET Web API 2 you can use the [FromBody] parameter attribute, so that your method signature would be:
public void UpdateStates(int id, [FromBody]List<int> states) {}
More info on parameter attributes can be found here: http://www.asp.net/web-api/overview/formats-and-model-binding/parameter-binding-in-aspnet-web-api

ASMX Webservice, Test Form only available on local machine for one WebMethod only

I have an ASMX WebService that I am testing, and on most of the methods I am able to use the test form just fine for testing. I do however have one method for which the test form says:
The test form is only available for requests from the local machine.
This method is declared exactly the same way the other methods, but it does have a noticeably longer parameter list (most of these methods only have 2 or 3 params):
[WebMethod]
public ActionSuccessResponse makeDestinationRequest(String ownerID, String destinationRegion, String destinationCountry, DateTime desiredTravelDate1, String destinationCity = "", DateTime? desiredTravelDate2 = null, DateTime? desiredTravelDate3 = null) {
Any ideas? I'm stumped.
If you must use the older ASMX files, you should test them with something like SOAPUI.
The built-in test page only handles very basic parameter entry (and probably has a limit on the number of parameters before it gives up). Basically don't use it.
As it turns out, the problem was actually occurring due to the DateTime typed parameters in the method definition. Web Services should use primitive data types for all parameters to allow for compatibility with other languages.

Is this a namespacing problem?

I am experiencing a strange behavior with very basic web service development. This question might be dumb but I think someone would be able to explain this observation.
I am developing a web service with a web method, MyWebMethod
MyWebMethod(MyEnum Param, .....)
Where,
public enum MyEnum : int
{
Type_1 =1;
Type_2 =2;
Type_3 =3;
}
Now I am using my client to communicate with this service but for every request type, Type_1, Type_2 etc the service captures it as Type_1. As an example, if I create a break point at MyWebMethod in my web service, I see Type_1 as param1 type. I guess this is a problem with Namespacing. I cannot see any other defects on the code. Any Idea based on the experiences?
When enum is serialized, only its string representation is transferred through wire (names), not the values. I believe thats the reason you are getting the wrong values.
Check out this 2 articles for more info
WebServices_and_Enums
Using enum in web service parameter
If this is a WCF web service and a .NET 2.0 client generated with wsdl.exe for each value type in the method signature there will be a boolean parameter added called XXXSpecified which you need to set to true. Check this blog post for more details.
I guess your enum does not need to inherit from int. You are providing name and value in the enumeration, that should suffice. I am assuming all your code is .NET 2.0. As test , return an enumeration value from the webservice. Just to make sure XML Serialization is working as expected when the service is hit directly by the browser.

Categories

Resources