I'm using WebApi2 and OData. I want add custom action, and use it by GET method
GET /odata/Providers(2)/DoSth
but I dont understand how it works exactly. Here is code for one of my controller:
public class ProvidersController : ODataController
{
private Entities db = new Entities();
// GET: odata/Providers
[Queryable]
public IQueryable<PROVIDER> GetProviders()
{
return db.PROVIDER;
}
//... OTHER GENERATED METHODS
//MY TEST METHOD SHOULD BE inoked: GET /odata/Providers(2)/DoSth
public int DoSth()
{
return 22;
}
}
and WebApiConfigFile:
ODataConventionModelBuilder builder = new ODataConventionModelBuilder();
builder.EntitySet<PROVIDER>("Providers").EntityType.HasKey(o => o.P_ID);
//others entities ...
//my custom action without any parameters, returns int:
ActionConfiguration getTest = builder.Entity<PROVIDER>().Action("DoSth");
getTest.Returns<int>();
Method existing in /odata/$metadata
but cant run this method from the url (still showing 404: "No HTTP resource was found that matches the request URI").
Any ideas how to improve this issue?
In OData an action can only be invoked by the POST method. So just change the request from GET to POST.
If it doesn't work, add an attribute to the method in the controller:
[HttpPost]
public int DoSth()
{
return 22;
}
If you just start to play with OData, I recommend you start from OData V4, which is an OASIS standard. Here is a sample about actions: https://aspnet.codeplex.com/SourceControl/latest#Samples/WebApi/OData/v4/ODataActionsSample/ .
I solved the problem in a different way... I am not a deep dive programmer only an intermediate... I do however solve problems in any way possible as they arise...
I required a search capability that could not be handled by standard $filter functionality and I needed to return an IQueryable just like any OData 4 controller would (less a get function).
First in my appropriate controller... I took the exact same signature of my Get"Entity" call and added a parameter.
[EnableQuery]
public IQueryable<detail> Getdetails([FromODataUri] int key)
{
return db.masters.Where(m => m.masterid == key).SelectMany(m => m.details);
}
// custom function goes here...
[EnableQuery]
public IQueryable<detail> GetSearchDetails([FromODataUri] int key, [FromODataUri] IEnumerable<int> search)
{
1) do your own function logic here... mine happens to be a very complex search...
2) make sure to return iQueryable as result... I use standard linq queries and then the final return is toList() as IQueryable
3) I also did a simple return search.Count = 0 ? return all results : return queried results.
}
In the WebAPi Config this is the signature;
1) the first line says place the code in the MasterController.
2) the second line tells me what to call the function.
3) the third line tells me what to return.
4) the fourth line tells me what to call the parameter and what type it is...
5) the fifth line is VERY important if you want to avoid having to have to call "://.../namespace.function(param='value')". This removes the dotted namespace constraint. see:this
builder.EntityType<master>()
.Function("GetSearchDetails")
.ReturnsCollectionFromEntitySet<detail>("details")
.CollectionParameter<int>("search");
config.EnableUnqualifiedNameCall(unqualifiedNameCall: true);
This approach solved many of my problems on the client side... Now, i can call h!!p://domain/odata/master(n)/GetSearchDetails(search=[2,10,31]) or if it were an array of string h!!p://domain/odata/master(n)/GetSearchDetails(search=['two','ten','thirtyone']) and it returns an IQueryable just like calling the underlying entity... However, the added benifit is that ALL the standard OData v4 functionality is still there $filter, $select... etc...
Related
I currently have a web API that
fetches a row of data using FromSqlRaw(...).ToListAsync() within a repository
returns this data as Ok(data.ToArray()) as Task<ActionResult<IEnumerable<MyClass>>> through a controller.
Now I am wondering whether I should or can use IAsyncEnumerable as a return type. The idea was to use this in the repository and the controller. However, in this (now decrepit) thread it states it should not be used. the proposed solution here would be something like:
FromSqlRaw(...).AsNoTracking().AsAsyncEnumerable()
As for the Controller I want keep the response wrapped with ActionResult to explicitly set the return code. However, that currently doesn't seem to work.
Should I just apply the solution for the repository and consume the result as a List in my controller or just keep it as it is?
The IAsyncEnumerable gives you an interface for pull-based asynchronous data retrieval. In other words this API represents an iterator where the next item is fetched asynchronously.
This means that you are receiving the data in several rounds and each in an asynchronous fashion.
Prior IAsyncEnumerable you could use IEnumerable<Task<T>>, which represents a bunch of asynchronous operations with return type T.
Whereas Task<IEnumerable<T>> represents a single asynchronous operation with a return type IEnumerable<T>.
Let's apply these knowledge to a WebAPI:
From an HTTP consumer point of view there is no difference between Task<ActionResult<T>> and ActionResult<T>. It is an implementation detail from users` perspective.
A WebAPI Controller's action implements a request-response model. Which means a single request is sent and a single response is received on the consumer-side.
If a consumer calls the same action again then a new controller will be instantiated and will process that request.
This means that the consumer of your API can't take advantage of IAsyncEnumerable if it is exposed as an action result type.
In .net 6 IAsyncEnumerable handling for MVC was changed when using System.Text.Json:
MVC no longer buffers IAsyncEnumerable instances. Instead, MVC relies on the support that System.Text.Json added for these types.
It means that controller will start sending output immediately and a client may start process it as it receives chunks of the response.
Here is an example with help of new minimal API:
Endpoint binding:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
// this endpoint return IAsyncEnumerable<TestData>
app.MapGet("/asyncEnumerable/{count}", (int count) => GetLotsOfDataAsyncEnumerable(count));
// and this one returns Task<IEnumerable<TestData>>
app.MapGet("/{count}", async (int count) => await GetLotsOfDataAsync(count));
app.Run();
Controller methods:
async Task<IEnumerable<TestData>> GetLotsOfDataAsync(int count)
{
var list = new List<TestData>();
for (int i = 0; i < count; i++)
{
await Task.Delay(10);
list.Add(new TestData($"{i}"));
}
return list;
}
async IAsyncEnumerable<TestData> GetLotsOfDataAsyncEnumerable(int count)
{
for (int i = 0; i < count; i++)
{
await Task.Delay(10);
yield return new TestData($"{i}");
}
}
class TestData
{
public string Field { get; }
public TestData(string field)
{
Field = field;
}
}
count path variable allows to control how many data we want to retrieve in a single call.
I've tested it with curl command on a windows machine (here is the answer explaining how to measure performance with curl), results for 100 entries:
/100 /asyncEnumerable/100
time_namelookup: 0.000045s 0.000034s
time_connect: 0.000570s 0.000390s
time_appconnect: 0.000000s 0.000000s
time_pretransfer: 0.000648s 0.000435s
time_redirect: 0.000000s 0.000000s
time_starttransfer: 1.833341s 0.014880s
---------------------
time_total: 1.833411s 1.673477s
Important here to see is time_starttransfer, from curl manpage
The time, in seconds, it took from the start until the first byte was just about to be transferred. This includes time_pretransfer and also the time the server needed to calculate the result.
As you can see /asyncEnumerable endpoint started responding instantly, of course, clients of such endpoints have to be aware of such behavior to make good use of it.
Here how it looks in a cmdline:
I am just learning about WebAPIs and curious if we can reuse the Post method inside get method or it just violates the coding standards. How can we test if this violation is already done by someone?
// GET api/values/5
public string Get(int id)
{
var value= vc.Values.Where(v => v.Id == id).Select(v => v.Value1).SingleOrDefault();
if (value==null) Post("New Value",id);
return vc.Values.Where(v => v.Id == id).Select(v => v.Value1).SingleOrDefault();
}
// POST api/values
public void Post([FromBody]string value, int id = 0)
{
vc.Values.Add(new Value { Id=id,Value1 = value });
vc.SaveChanges();
}
These are 2 questions, not one.
Reusing code like this is a recipe for disaster.
You can keep your endpoints very slim by moving the code into a library for example. Then you can simply call these new methods from the endpoints and this takes care of the code reuse part.
In terms of how you detect such issues, well, I wouldn't expect a tool to do it for you. You need a mature SDLC, you need code reviews and analysis on what you have already.
I'm trying to combine multiple different ViewComponents from a controller. The ActionResult of all combined viewcomponents will be rendered to the browser.
This is based on an article which does this with PartialViews and updates the PartialViews with ajax. That article is based on previous version of MVC. For more info see: https://www.simple-talk.com/dotnet/asp.net/revisiting-partial-view-rendering-in-asp.net-mvc/
After many hours I came to the following code example. But the problem is that it works only for the first viewComponent. When I change the order of viewcomponents it still renders the first one. So it doesn't seem to have anything with my viewcomponents. Always at second loop it ends at "vc.ExecuteResultAsync(context);" with no errors. So rendering the first one is always successful.
By the way I'm using VS 2015 Enterprise with Beta7 of MVC6 and all other dependencies.
Please help!
public async Task<IActionResult> Dashboard()
{
// Combine multiple viewcomponents
return new MultipleViewResult(
ViewComponent(typeof(OrdersViewComponent))
, ViewComponent(typeof(AccountsViewComponent))
);
}
public class MultipleViewResult : ActionResult
{
public const string ChunkSeparator = "---|||---";
public IList<ViewComponentResult> ViewComponentResults { get; private set; }
public MultipleViewResult(params ViewComponentResult[] views)
{
if (ViewComponentResults == null)
{
ViewComponentResults = new List<ViewComponentResult>();
}
foreach (var v in views)
ViewComponentResults.Add(v);
}
public override async Task ExecuteResultAsync(ActionContext context)
{
if (context == null)
throw new ArgumentNullException(nameof(context));
byte[] chunkSeparatorBytes = System.Text.Encoding.UTF8.GetBytes(ChunkSeparator);
var total = ViewComponentResults.Count;
for (var index = 0; index < total; index++)
{
var vc = ViewComponentResults[index];
// No matter which viewcomponent, this line works only with the first viewcomponent.
await vc.ExecuteResultAsync(context);
if (index < total - 1)
{
await context.HttpContext.Response.Body.WriteAsync(chunkSeparatorBytes, 0, chunkSeparatorBytes.Length);
}
}
}
}
This fails because the first vc.ExecuteResultAsync(context) will set the ContentType property and flush the response. Any succeeding call to vc.ExecuteResultAsync(context) will also try to set the ContentType property but will fail because the response has already been streamed back to the client.
I can't think of any better workaround other than creating your own instance of HttpContext that would allow you write to the ContentType property even if part of the response has already been sent back but this is messy.
If you believe this is a bug (I personally think it is because the ViewComponentResult should check if the response has already been sent before setting the ContentType), then you can always submit a bug report.
I have xamarin forms application. I use odata simple client to manipulate app database.
I am trying to add data to many-to-many tables.
This is my first entity
public class Genre : BaseGenre
{
List<User> Users { get; set; }
}
And my other one
public class User : BaseUser
{
List<Genre> Genres { get; set; }
}
And this the function I am trying to link them
public async void AddGenresAsnyc(User u, List<Genre> Genres)
{
u.Genres = Genres;
try {
//await client.For<User> (CollectionName).Key(u).LinkEntryAsync(us => us.Genres, Genres);
await client.For<User> (CollectionName).Key(u.id).Set(u).UpdateEntriesAsync();
} catch (Exception e) {
Exception ex = e;
}
}
The first one, linkentryasync throws the exception
Number of parameter does not match expected count.
And the second one throws
Linked collection for type [Genre] not found
Any help would be great. I am stuck at work. Thanks in advance.
One immediate thing that you need to change is make properties Genre.Users and User.Genres public. Simple.OData.Client uses reflection to assign property values and is not capable of assigning values for private properties/fields. I tested your code with the schema you sent me and as long as the properties were public, the request went through.
Regarding the next example (using LinkEntryAsync), if you want to update links in a single call, you should use UpdateEntryAsync, because LinkEntryAsync does it for a single link. So either use:
var user = await client.For<User>("ApiUser").Key(1).FindEntryAsync();
user.Genres = genres;
await client.For<User>("ApiUser").Key(user).Set(user).UpdateEntryAsync();
or
foreach (var genre in genres)
{
await client.For<User>("ApiUser").Key(user).LinkEntryAsync(genre);
}
The first operation could have been written in a more efficient way:
await client.For<User>("ApiUser").Key(1).Set(new {Genres = genres}).UpdateEntryAsync();
That will generate HTTP PATCH instead of PUT with only Genres updated, but it looks like your OData service requires all mandatory properties to be sent on the entity being updated, so this won't work.
Last but not least: get the latest version (4.9.1) of Simple.OData.Client. It has a fix that is important for your scenario.
UPDATE. I tested your OData service, and it doesn't seem to have a proper support for addressing links. For example, if I test sample OData service, I can execute requests like http://services.odata.org/V4/OData/%28S%28ygi3rwu514y0a4ooybn3d1gc%29%29/OData.svc/Products%284002%29/Categories/$ref (note $ref segment that addresses Caterogories link so this URI can be used to post link updates). But if I execute request http://{your_service_uri}/ApiUsers%281%29/Genres/$ref then I get an error "No HTTP resource was found that matches the request URI 'http://partymag.azurewebsites.net/ApiUsers(1)/Genres/$ref'." As long as this link doesn't work on a server side you won't be able to use LinkEntryAsync or UnlinkEntryAsync but you can still use UpdateEntryAsync as I showed above.
UPDATE2. The version that uses UpdateEntryAsync executes fine but service doesn't update links, here is the result from Fiddler:
Generated URI: PATCH http://{your_service_uri}/ApiUsers(1)
PATCH payload:
{ "#odata.type":"#PMWeb.Models.Models.User",
"id":1,"Name":"Ege",
"LastName":"Aydin",
"Email":"{removed}",
"Password":"{removed}",
"Genres#odata.bind":[
"http://{your_service_uri}/Genre(31)","http://{your_service_uri}/Genre(32)"
]
}
Response:
{
"#odata.context":"http://{your_service_uri}/$metadata#ApiUsers/$entity",
"id":1,
"Name":"Ege",
"LastName":"Aydin",
"Email":"{removed}",
"Password":"{removed}"
}
If I now check the content of User's genres, they are the same. Since generated payload is correct and the service accepted it, it must be something on the server that is not executed properly.
I'm using the RestSharp library to access a REST API.
I want all the API requests to go through the same method, so I can add headers, handle errors and do other stuff in a central place.
So I made a method that accepts a generic Func<> and that solves most of my problems, but I don't know how to handle the case where I don't have a return type.
private T PerformApiCall<T>(RestRequest restRequest, Func<RestRequest, IRestResponse<T>> restMethod)
{
var response = restMethod.Invoke(restRequest);
//handle errors
....
return response.Data;
}
I call it like this:
var apples = PerformApiCall(new RestRequest('/api/apples'), req => Client.Execute<List<Apple>>(req));
But I came across a problem, a bunch of API calls don't have a return type because they don't return data. So I used Client.Execute(req) and I get the error saying the type arguments cannot be inferred, I tried to pass , but that failed because it couldn't convert the non-generic IRestResponse to the typed one.
Any ideas on how to tackle this in a nice way?
One thing you could try is to add an overload to your PerformApiCall function that takes a Func with a non-generic result type, and returns nothing:
// Notice the `Func` has `IRestResponse`, not `IRestResponse<T>`
public void PerformApiCall(RestRequest restRequest,
Func<RestRequest, IRestResponse> restMethod)
...
Then, depending on how complex your error checking/logic is, you could move it out to a separate method (which returns the response), and call it from both overloads of PerformApiCall:
private T PerformRequestWithChecks<T>(RestRequest restRequest,
Func<RestRequest, T> restMethod)
where T : IRestResponse
{
var response = restMethod.Invoke(restRequest);
// Handle errors...
return response;
}
// You can use it from both versions of `PerformApiCall` like so:
//
// // From non-generic version
// var response =
// PerformRequestWithChecks<IRestResponse>(restRequest, restMethod);
//
// // From generic version
// var response =
// PerformRequestWithChecks<IRestResponse<T>>(restRequest, restMethod);
// return response.Data;
You were getting a compiler error because it is sound to treat a subtype as if it was an instance of its supertype, but it is not sound to do it in the other direction (which is what was happening when you changed your calling code to just Client.Execute(req), returning a non-generic).
Here's an ideone paste illustrating this: http://ideone.com/T2mQfl