return decimal value in web api - c#

I have following method in repository project , and I'm trying to get that value via web api,
Method
public decimal findBookPrice(int book_id)
{
var bookprice = (
from r in context.Books
where r.Book_Id == book_id
select r.Price
).FirstOrDefault();
return bookprice;
}
Book Class
public class Book
{
[Key]
public int Book_Id { get; set; }
[Required]
public string Book_Title { get; set; }
[DataType("decimal(16 ,3")]
public decimal Price { get; set; }
...
}
}
Web API method
// GET: api/BookPrice/3
[ResponseType(typeof(decimal))]
public IHttpActionResult GetBooksPriceById(int id)
{
decimal bookprice = db.findBookPrice(id);
return Ok(bookprice);
}
but once I direct to url which is http://localhost:13793/api/BookPrice/2
I'm getting following output not the decimal value

The shown error message is caused by a routing problem. The ASP.NET MVC framework was not able to find the right controller or action for the URL
http://localhost:13793/api/BookPrice/2
The default routing rule in ASP.NET MVC takes BookPriceand tries to find the BookPriceController. As you stated in your comment, the action is in a BooksWithAuthersController. Therefore the URL has to be (if you want to use the default routing rule):
http://localhost:13793/api/BooksWithAuthers/2
Have a look at article if you want to read more about this topic.
EDIT:
Looking at the whole controller code you will find the two action methods called GetBooksWithAuthersById and GetBooksPriceById. Because both start with get and have got the same parameter list (int id), the ASP.NET MVC framework has got two possible action methods for the URL /api/BooksWithAuthors/2. To solve this ambiguity you can give the GetBooksPriceById action a separate route via the [Route] annotation.
Like in this slightly adjusted BooksWithAuthersController:
public class BooksWithAuthersController : ApiController
{
[ResponseType(typeof(BookWithAuther))]
public IHttpActionResult GetBooksWithAuthersById(int id)
{
...
}
[ResponseType(typeof(decimal))]
[Route("api/bookswithauthers/{id}/price")]
public IHttpActionResult GetBooksPriceById(int id)
{
...
}
}
In order get the price of a book, the URL http://localhost:13793/api/BooksWithAuthers/2/price will return the decimal value.

Related

How to pass multiple parameters to Get Method and Route them in .NET Core Web API?

I'm making a (restful) Web API in .NET Core and stumbled among some problems.
I cannot seem to find how to pass multiple subscription ID's... I need to be able to show multiple periods(invoices) of multiple subscriptions.
My route at the moment is
[Route("tenants/{tenantId:long}/subscriptions/{subscriptionId:long}/invoices/{invoiceId:long}/categories")]
From this way it seems impossible for me to pass more subscription IDs.
Some terms I found but not fully understand are:
Model Binding
[FromQuery]
My classes:
public class Subscription
{
public string Name { get; set; }
public long TenantId { get; set; }
public string Guid { get; set; }
}
public class Invoice
{
public long SubscriptionId { get; set; }
public DateTime StartDate { get; set; }
public DateTime EndDate { get; set; }
public long PortalId { get; set; }
}
My controllers with routes [Route("tenants/{tenantId:long}/subscriptions")] and [Route("tenants/{tenantId:long}/subscriptions/{subscriptionId:long}/invoices")]
[HttpGet]
public IEnumerable<SubscriptionViewModel> Find(long tenantId)
{
var subscriptionList = _subscriptionManager.Find(tenantId);
...
return subscriptionViewModels;
}
[HttpGet]
public IEnumerable<InvoiceViewModel> Find(long subscriptionId)
{
var invoiceList = _invoiceManager.Find(subscriptionId);
...
return invoiceViewModels;
}
Please note that i'm using a Mapper for my data (which is why i'm using ViewModels).
The currently written code is for a specific subscription.
I am looking for a Route like /api/invoices?subscriptionId=x,y,z
I understand(?) I need the [FromQuery] for that, but I cannot seem to find out how, especially if my parameter (subscriptionId) stays the same.
for the requirement which you have mentioned as:
I am looking for a Route like /api/invoices?subscriptionId=x,y,z
You can do couple of things:
pass the subscriptionIds one after the other separated by & in the query string of the URL and change the input parameter of action method to accept array of subscriptionIds
example of route:
/api/invoices/find?subscriptionId=x&subscriptionId=y&subscriptionId=z
example of action method parameter accepting array of subscriptionIds:
public IEnumerable<InvoiceViewModel> Find([FromQuery]long[] subscriptionId)
pass the comma separated string as querystring in the URL and write a piece of logic in the action method to split the string based on comma to get an array of subscriptionIds
example of route:
/api/invoices/find?subscriptionIds=x,y,z
example of action method:
public IEnumerable<InvoiceViewModel> Find([FromQuery]string subscriptionIds)
{
var ids = subscriptionIds.Split(',').Select(int.Parse).ToArray();
// do the logic on multiple subscriptionIds
}
Apart from this, you can go for creating custom model binders as well as suggested in other answers.
Hope this helps.
There can be many ways to achieve this task (I can think of two-three for now).
1) instead of long subscriptionid take a string as an input and validate it before proceeding further.
[HttpGet]
public IEnumerable<InvoiceViewModel> Find(string subscriptionIds)
{
var list = validateInput(subscriptionIds);
var invoiceList = _invoiceManager.FindList(list);
...
return invoiceViewModels;
}
public IList<long> validateInput(string subscriptionIds)
{
var list = subscriptionIds.Split(",");
... // Code to convert each element in long and throw if it is not long
return longlist;
}
2) Create custom model binders.
Steps are mentioned here:
https://learn.microsoft.com/en-us/aspnet/web-api/overview/formats-and-model-binding/parameter-binding-in-aspnet-web-api
=> [FromUri] attribute can be used to bind the Complex types from query string parameters but i am not sure how i would use that.
If you ask me, i would go for approach-1 (not to increase complexity).
You can create a specific Request view model which accepts a collection of invoice ids:
public class InvoiceRequestModel
{
IEnumerable<long> InvoiceIDS { get; set; }
}
and use it for your action method:
[Route("tenants/{tenantId:long}/subscriptions/{subscriptionId:long}/invoices")]
[HttpGet]
public IEnumerable<InvoiceViewModel> Get(InvoiceRequestModel requestModel)
{
}
In the case you want to use query parameters, mark your action parameter with the [FromQuery] attribute:
[Route("tenants/{tenantId:long}/subscriptions/{subscriptionId:long}/invoices")]
[HttpGet]
public IEnumerable<InvoiceViewModel> Get([FromQuery]IEnumerable<long> invoiceIDs)
{
}
and on creating the request, pass each value with the same key in the query string:
invoiceIDs=1&invoiceIDs=2&invoiceIDs=3
Finally, it will look like this:
tenants/{tenantId}/subscriptions/{subscriptionId}/invoices?invoiceIDs=1&invoiceIDs=2&invoiceIDs=3

Odata Controller not working with multiple Keys

I work with an ASP.NET webapi with entity framework and odataV4 controller
First i had one [Key] tag in my model and every controllerAction was shown in the swagger UI
Now i have 3 [Key] tags in my model and only controllerActions with no arguments are shown in the swagger UI
I changed my Model from
//Some other stuff
[Key]
public byte first{ get; set; }
public int second{ get; set; }
public long third{ get; set; }
//Some other stuff
to
//Some other stuff
[Key]
public byte first{ get; set; }
[Key]
public int second{ get; set; }
[Key]
public long third{ get; set; }
//Some other stuff
and the controllerAction from
public async Task<IHttpActionResult> Put([FromODataUri] byte key, Delta<lists> patch)
to
public async Task<IHttpActionResult> Put([FromODataUri] byte first, [FromODataUri] int second, [FromODataUri] long third, Delta<lists> patch)
with one key the Endpoints are shown in the swagger UI and they work, with multiple keys the Endpoints are not shown in the swagger UI and everytime i try to reach the endpoint this error shows up
"No action was found on the controller 'lists' that matches the request."
I send a PUT Request with .../myController(first=1,second=1,third=10) in the URL
What i am missing or doing wrong?
Which version do you use?
As I know, the latest 5.x and 6.x version do support the multiple keys, no matter attribute routing or convention routing you are using.
See the comments in https://github.com/OData/WebApi/blob/maintenance-V4/src/System.Web.OData/OData/Routing/Conventions/ProcedureRoutingConventionHelpers.cs#L133-L137
So for your scenario, if in convention routing, the parameter name in the method of controller should be prefixed with "key".
public async Task<IHttpActionResult> Put([FromODataUri] byte keyfirst, [FromODataUri] int keysecond, [FromODataUri] long keythird, Delta<lists> patch)
Hope it can help you.
Thanks!
I solved it creating a new EMD Class like this:
namespace ODataService.Edm
{
public static class SampleModelBuilder
{
public static IEdmModel GetEdmModel()
{
ODataConventionModelBuilder builder = new ODataConventionModelBuilder();
builder.EntityType<EntityTableName>().HasKey(c => new { c.first, c.second, c.third});
builder.EntitySet<EntityTableName>("EntityTableName");
return builder.GetEdmModel();
}
}
}
And then I repleace the MapODataServiceRoute in the WebApiConfig.cs to invoke my EdmModel:
config.MapODataServiceRoute("ODataRoute", null, Edm.SampleModelBuilder.GetEdmModel());
You can refer more info abot EDM here:
https://learn.microsoft.com/en-us/odata/odatalib/edm/build-basic-model
At last, I use some attribute-routing on the controller like this:
[EnableQuery]
[ODataRoute("EntityTableName({first},{second},{third})")]
public SingleResult<EntityTableName> Get([FromODataUri] byte first, [FromODataUri] int second, [FromODataUri] long third)
{
IQueryable<EntityTableName> result = dbContext.EntityTableName.Where(p => p.first == first && p.second == second && p.third == third);
return SingleResult.Create(result);
}
To extend the info about attribute routing check this link:
https://learn.microsoft.com/es-es/odata/webapi/attribute-routing
Let me know if this help you.
Good luck and great coding!

Azure Table Controllers - Get records by to parameters

I am working on an Azure Mobile Apps project. Where I have to define a Table Controller with that can accept two parameters and give a list of values. I have a DataObject for ProductItem, which is
public class ProductItem : EntityData
{
public string Name { get; set; }
public string Details { get; set; }
public double Price { get; set; }
public string Image { get; set; }
public Merchant Merchant { get; set; }
}
I need to get a specific Product item, filter by its Price and Merchant. Already in the ProductItemContoller, I have scaffolded
// GET tables/ProductItem
public IQueryable<ProductItem> GetAllProductItems()
{
return Query();
}
// GET tables/ProductItem/48D68C86-6EA6-4C25-AA33-223FC9A27959
public SingleResult<ProductItem> GetProductItem(string id)
{
return Lookup(id);
}
by looking at existing examples. But in examples, we have not called any of the given methods from Client. Rather, IEnumerable<ProductItem> items = await productTable.ToEnumerableAsync(); was called.
My question is why can't we call GetAllProductItems() which was already defined in the controller to the client. If we can call, how to do it.
And also, I need to have a controller method, I need to have a GetAllProductByMerchat(string merchantId). How can I make this possible.
The Table controllers are called automatically by the client SDKs on your behalf, allowing you to work with LINQ queries on the client. You can use something like:
var items = productTable.Where(p => p.Price < 100).ToListAsync();
This gets translated into an OData query across the wire, then translated back into a LINQ query on the server, where it then gets translated into SQL and executed on the SQL Azure instance.
For more information, see chapter 3 of http://aka.ms/zumobook
Did you mean this?
// Server method:
[HttpGet]
[Route("GetAllProductItems")]
public IQueryable<ProductItem> GetAllProductItems()
{
return Query();
}
// Client call
var result = await MobileService.InvokeApiAsync<IQueryable<ProductItem>>("ProductItem/GetAllProductItems", HttpMethod.Get, null);
Remember to add these attribute before the ProductItemController:
[MobileAppController]
[RoutePrefix("api/ProductItem")]
You can do the same thing to your GetAllProductByMerchat(string merchantId) method.

How to pass dictionary parameter to web-api method?

I've read all similar questions here, but i can't come up with solution. I'm trying to call web-api method:
[HttpGet]
public SearchViewModel Get(SearchTypes type, string text, [FromUri]Dictionary<string, string> toyParams)
{
//Code here
}
and i want to get last parameter from uri. I've tried
http://localhost:39101/#!/search/toys/fox?someParameter=123
and
http://localhost:39101/#!/search/toys/fox?toyParams[0].Key=someParameter&toyParams[0].Value=123
but toyParams Dictionary always empty.
Just found out it is implicitly answered at another question here.
The solution it points at redirects to Model Binding in ASP.NET Core.
Or, short answer, you just compose your request like so:
http://localhost:39101/#!/search/toys/fox?toyParams[someParameter1]=123&toyParams[someParameter2]=456
Even though its quite late but following method is available to return query parameters as a name/value pair -
this.Url.Request.GetQueryNameValuePairs()
I have following Url -
http://localhost:49980/api/MyRestAPI/postSomeData/?a=v&d=e
Following is my method, written in Web API 2 -
[HttpPost]
public IHttpActionResult SaveDataFromRestToDB(string dataKey) {
var parameters = this.Url.Request.GetQueryNameValuePairs();
//parameters returns KeyValuePair<string, string>[2], containing values passed in Url Query
}
Hope this helps!!
One Simple way, instead of dictionary:
//DTO
public class SearchDTO
{
public int MyProperty1 { get; set; }
public int MyProperty2 { get; set; }
public int MyProperty3 { get; set; }
}
Where MyProperty1, MyProperty2, MyProperty3 are the params based on which something has to be searched.
//GET Method
public string Get([FromUri]SearchDTO searchDTO)
{
return "search result";
}
Below is the Calling URL :
http://localhost:56880/api/values?myproperty1=1&myproperty2=2&myproperty3=3

ASP.NET Web API model binding non-sequential list of complex objects

I am attempting to model bind a complex object with a non-sequential list using an ApiController. All of the fields except the list are set correctly, but the list contains one element (even though two list elements were posted) and the element is null. If I take the exact same code and point it to an MVC Controller using the same parameter type in my action method, everything works as expected.
Since I am using a non-sequential list, I am using the hidden ".Index" input as described by Phil Haack (http://haacked.com/archive/2008/10/23/model-binding-to-a-list.aspx)
The ApiController also binds the list correctly if I remove the ".Index" input and send the list as a sequential list starting at 0. (This option work for testing, but is not a great option in production as the list items can be added and removed by the user, which is why I want to use the non-sequential list.)
I understand that Web API Controllers do parameter binding differently than MVC Controllers as discussed here, but it seems like non-sequential lists should bind correctly in Web API Controllers. Am I missing something? Why does the same code work for an MVC Controller and not a Web API Controller? How can I get non-sequential lists to bind correctly in Web API?
Here are my Post parameters:
Parameters application/x-www-form-urlencoded
BatchProductLots.Index 1
BatchProductLots.Index 2
BatchProductLots[1].BrandId 1
BatchProductLots[1].ContainerId 9
BatchProductLots[1].ContainerLot 123
BatchProductLots[1].PackageId 2
BatchProductLots[1].PlannedQuantity 0
BatchProductLots[1].ProducedQuantity 20
BatchProductLots[2].BrandId 1
BatchProductLots[2].ContainerId 9
BatchProductLots[2].ContainerLot 123
BatchProductLots[2].PackageId 1
BatchProductLots[2].PlannedQuantity 0
BatchProductLots[2].ProducedQuantity 1
BatchStatusId 1
LotNumber 070313
ProductionDate 07/03/2013
RecipeId 1
RecipeQuantity 1
SauceId 22
X-Requested-With XMLHttpRequest
Here is my Web API Controller Action:
(request.BatchProductLots list is set to one element (even though two elements were posted) and that one element is null)
public Response Create(BatchCreateRequest request)
{
Response response = new Response();
try
{
Batch batch = Mapper.Map<Batch>(request);
batchService.Save(batch);
response.Success = true;
}
catch (Exception ex)
{
response.Message = ex.Message;
response.Success = false;
}
return response;
}
Here is the complex object with the list that I am attempting to bind to:
public class BatchCreateRequest
{
public int BatchStatusId { get; set; }
public DateTime ProductionDate { get; set; }
public string LotNumber { get; set; }
public int SauceId { get; set; }
public int RecipeId { get; set; }
public int RecipeQuantity { get; set; }
public List<BatchProductLot> BatchProductLots { get; set; }
public class BatchProductLot
{
public int BrandId { get; set; }
public int ContainerId { get; set; }
public string ContainerLot { get; set; }
public int PackageId { get; set; }
public int PlannedQuantity { get; set; }
public int ProducedQuantity { get; set; }
}
}
Short answer, it's not possible using Web Api's Model Binder. MVC and Web Api use different model binders and the Web Api model binder only works on simple types.
See this answer for links that explain further as well as possible solutions.
Longer answer, create a custom implementation of System.Web.Http.ModelBinding.IModelBinder and change your Action's signature to the following
public Response Create([ModelBinder(CustomModelBinder)]BatchCreateRequest request)
Do you really need Index to be set? In that case, one possible solution could be to make Index part of the BatchProductLot class. The sequence of list won't matter then and Web Api should be able to bind it.
Another idea would be to use application/json content type and send JSON. You can use Json.Net to deserialize and model binding would work.
Read Using an alternate JSON Serializer in ASP.NET Web API and even use this Nuget Package WebApi Json.NET MediaTypeFormatter if you don't want to do the hand wiring.

Categories

Resources