Web API GET with array of complex type - rest

I want to allow users of an API to send GET request that will run multiple queries on db and then return the result.
I have a query model like this
public class QueryModel
{
public int A {get;set;}
public int B {get;set;}
public int C {get;set;}
}
I have a controller with a Get method like this -
public async Task<IActionResult> Get(List<QueryModel> queryModels)
{
foreach(var queryModel in queryModels)
{
// some logic to search the db
}
// combine the results and return
}
This is not working for me, but I don't know if my query string is wrong or the method is wrong.
I have tried a number of variations of
?model={[{1,2,3},{1,2,3}]}
But they do not work.

You can use a construction like this:
\Get?model[0].A=1&model[0].B=2&model[0].C=3&model[1].A=4&model[1].B=5&model[1].C=6
Almost forgot, add FromUri:
public async Task<IActionResult> Get([FromUri] List<QueryModel> model)
{
...
}
Please let me know if this works for you.

Related

Rest API architecture special cases

Using the Web API in .net 5 and conforming an api in rest gives me some challenges about how much to split up the methods in different controllers and the naming conventions.
I have read that if I have users in my system and doing a rest architecture, my controller would be named UserController and method for getting a user would be:
[HttpGet("{id}")]
public string Get(int id)
{
return "value";
}
For getting a list of users it would be:
[HttpGet]
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
Updating would be:
[HttpPut("{id}")]
public void Put(int id, [FromBody] string value)
{
}
Delete would have a [HttpDelete], and so on. But what about the special cases?
What if I wanted GetUserByUsername? Would it then be in the same UserController and just be the following, or would it break the REST pattern?:
[HttpGet("{username}")]
public Task GetByUsername(string username)
{
}
What if I needed a call to the api get some data to populate the "createuser page", lets say I need the roles that the user could be created as and some other information and would like to do it in one call. Could I then just create a "InitCreateUser" in the UserController and not break the REST pattern?
[HttpPost]
public Task InitCreateUser()
{
}
What if I needed Login and Logout methods, would it be AutenticationController and just have the two methods: (It's just so far from the other pattern when its not called just Get() Post() and so on)
[HttpPost]
public Task Login(LoginRequest request)
{
}
[HttpPost]
public Task Logout(LogoutRequest request)
{
}
Yes you would put it in the same controller as it is still dealing with the user, and it is good habit to keep functions involving a specific entity in the same location.
But that obviously has the issue of having different endpoints such as Id and Username. A simple way to do this is to indicate which you want to use:
[HttpGet("Id/{id}")]
public string Get([FromRoute] int id) {...}
-> api/User/Id/293
[HttpGet("Username/{username}")]
public string Get([FromRoute] string username) {...}
-> api/User/Username/Tom
Another way is to have a base request and have other functions be offshoots of it
[HttpGet("{id}")]
public string Get([FromRoute] int id) {...}
-> api/User/293
[HttpGet("Username/{username}")]
public string Get([FromRoute] string username) {...}
-> api/User/Username/Tom
[HttpGet("Customer/{custNo}")]
public string Get([FromRoute] string custNo) {...}
-> api/User/Customer/5DtU22D
But lets say you have the Id of the user and you wish to do other functions like get relevant data from the user to display, but not all of it. Or You want to check all the relating data towards a specific funciton,
then you can do something like this:
[HttpGet("{id}/Permissions")]
public PermissionModel GetUserPermission([FromRoute] int id) {...}
[HttpPut("{id}/Permissions")]
public bool UpdateUserPermission([FromRoute] int id, [FromBody] PermissionModel permission) {...}
Or even further derived functionalities
[HttpGet("{id}/Account")]
public string GetUserAccount([FromRoute] int id) {...}
[HttpGet("{id}/Account/Funds")]
public double GetUserAccountTotal([FromRoute] int id) {...}
if you are accessing list properties, for example the user has many accounts for example, you can add a secondary key:
[HttpGet("{id}/Accounts")]
public IEnumerable<string> GetUserAccount([FromRoute] int id) {...}
[HttpGet("{id}/Accounts/{accountId}/Funds")]
public double GetUserAccountTotal([FromRoute] int id, [FromRoute] int accountId) {...}

POST parameter is null in when it's a hand made type

I have a asp.net 4.5.2 web api and one POST method receive a DTO parameter I've coded by hand like these:
using System;
namespace WebApi.Models.DTO
{
[Serializable]
public class MyModelDto
{
public MyModelDto()
{
}
public int Id { get; set; }
public string Code { get; set; }
public int Type { get; set; }
}
}
When I call this method from Postman the parameter is always null, but if I design the same DTO class in EntityFramewok (edmx model) and use it as parameter I could receive the data inside the post method as it was send.
I could not realize what I'm missing in my hand made class?
Why it works using EF and not with my class?
Setting the parameter as dynamic also works...
[HttpPost]
[JwtAuthentication]
[Route("api/bills/invoice")]
public JsonResult<WebApi.Models.DTO.MyModelDto> Post(WebApi.Models.DTO.MyModelDto param)
{
// Here param is always NULL
}
[HttpPost]
[JwtAuthentication]
[Route("api/bills/invoice")]
public JsonResult<Data.MyModelDto> Post(Data.MyModelDto param)
{
// Here param wors! based on the EF class
}
public JsonResult<Data.MyModelDto> Post(dynamic param)
{
// Here param wors! using dynamic data type
}
it depends how you send the data via Postman.
I don't have Postman here to test but make sure you select Json Type when sending data and then your sent data needs to match your DTO. Something like:
{
"Id":1,
"Type":"whatever",
"Code":"whatever"
}
This data goes in the body of the request since you are sending a Post request.
One extra observation, your DTO does not need an empty constructor, you can get rid of it. There is nothing wrong with:
public class MyModelDto
{
public int Id { get; set; }
public string Code { get; set; }
public int Type { get; set; }
}
For testing purposes get rid of your Jwt authentication, until you get the calls running correctly

How to pass query string parameter to asp.net web api 2

How can I pass more than 1 parameters as part of query string to my asp.net web api 2.
This is my asp.net web api 2 method, I am not able to figure out that how can I decorate this method so that it accepts the id and a complex type which is CustomerRequest, I want to use Url something like
http://localhost/api/Customer/?Mobile0012565987&Email=abcxyz.com&IsEmailVerified=true
[ResponseType(typeof(Customer))]
public IHttpActionResult GetCustomer(long id, [FromUri]CustomerRequest request)
{
var customer = db.Customers.Find(request.CustomerId);
if (customer == null)
{
return NotFound();
}
return Ok(customer);
}
This is CustomerRequest class
public class CustomerRequest
{
public string Mobile { get; set; }
public string Email { get; set; }
public Nullable<bool> IsEmailVerified { get; set; }
}
Otherwise pleaase guide me if there is a better way to do it.
Thanks
Based on your code, you need to pass 'id' as well, like this:
http://localhost/api/Customer/?id=12345&Mobile=0012565987&Email=abcxyz.com&IsEmailVerified=true
if you want to make 'id' optional, you can make your method signature look like this:
public IHttpActionResult GetCustomer([FromUri]CustomerRequest request, long id = 0)
this will set id to 0 by default, if you dont pass it in the URL. So you will be able to access your URL like you originally did:
http://localhost/api/Customer/?Mobile=0012565987&Email=abcxyz.com&IsEmailVerified=true

Projecting from an IDbAsyncQueryProvider

I have a project in which we use Automapper to Project() entities from our data access layer into our domain classes which are consumed by external callers (i.e. WebAPI, windows services, etc). The idea is that we want to abstract our domain model from the actual database implementation, but often the domain model is comprised of at least the members from the database, and so Automapper makes constructing those domain models a lot easier using it's projection features.
This results in code that looks something like:
public class DbTask
{
public int Id { get; set; }
public string Name { get; set; }
}
public class Task
{
public int Id { get; set; }
public string Name { get; set; }
public User Assignee { get; set; }
public Priority CurrentPriority { get; set; }
}
And then a business service implementation that looks like:
public IQueryable<Task> QueryTasksByUser(int userId)
{
return dbContext
.Where(x => x.Assignee.Id == userId)
.Project()
.To<Task>();
}
So then at the API layer we want to leverage async/await as much as possible, and so I'm wondering if Automapper has any support for this. If I write my method like:
public async Task<IHttpActionResult> GetTopTaskForCurrentUser()
{
var task = await _taskService
.GetTasksByUser(Thread.CurrentPrincipal.AsUser().UserId)
.Where(x => x.CurrentPriority == Priority.Top)
.SingleOrDefaultAsync();
return Ok(task);
}
I get the following exception:
System.InvalidOperationException : The provider for the source IQueryable doesn't implement IDbAsyncQueryProvider. Only providers that implement IDbAsyncQueryProvider can be used for Entity Framework asynchronous operations. For more details see http://go.microsoft.com/fwlink/?LinkId=287068.
Is this something that Automapper just doesn't have support for [yet], or is there another method or extension that I'm missing?
You just need to make sure you call these methods in the correct order:
return await dbContext.Users
.Where(user => user.Id == id)
.Project().To<UserDto>()
.ToListAsync();
Put Project.To in the same place you'd put "Select" and it works fine.
I typically create an extension method to wrap the projection and async'ing:
public static async Task<List<TDestination>>
ToListAsync<TDestination>(
this IProjectionExpression projectionExpression)
{
return await projectionExpression.To<TDestination>().ToListAsync();
}
return await dbContext.Users
.Where(u => u.Id == id)
.Project().ToListAsync<UserDto>();

Entity Framework / MVC Remove Item from Collection

What are some ways I can delete an item from a collection? (I am using MVC 4 and EF.)
As an example:
public class Birthday
{
public string Name { get; set; }
public virtual ICollection<Gift> Gifts { get; set; }
}
public class Gift
{
public string Name { get; set; }
public double Price { get; set; }
}
I'm using Editing a variable length list, ASP.NET MVC 2-style to create a dynamic list of Gifts.
The example is shows how to "Delete" a row. This will delete the row from the page and the correct Gifts are sent to the controller.
When I update the Birthday / Gifts everything new is updated properly, but anything deleted is still there.
So my question is what are some preferred ways to remove Gifts?
Two ways I've thought of already:
Get a Birthday from the DB and compare the Gifts removing as needed. I don't love this idea because it seems heavy handed.
Use WebApi / Ajax and delete the Gift from the list and the DB when the user pushes the delete link. I like this better than #1 but does this put too much business logic in the presentation layer?
I'm guessing that other people have had this similar problem and have a clever solution I haven't thought of yet.
Thanks in advance!
Make a Gifts api controller.
Let it have a Delete method accepting an Id of whatever type your Id is.
And do something like this in it:
public class GiftsController: ApiController
{
public void Delete(Guid Id)
{
var context = new MyContext();
var giftToDelete = context.Gifts.FirstOrDefault(g=> g.Id == Id);
if(giftToDelete != null)
{
context.Gifts.Remove(giftToDelete);
context.SaveChanges();
}
}
}
Make sure you make a DELETE request to this api in your JS delete function.
You may also replace the body of this method with some Service.DeleteGift(Id) if you're too concerned about doing things in the right place.
Like this:
public class ValuesController : ApiController
{
private List<string> list = new List<string>{"Item1","Item2","Item3","Item4","Item5"};
// DELETE api/values/5
public List<string> DeleteItem(int id)
{
list.Remove(list.Find((i => i.ToString().Contains(id.ToString()))));
return list;
}
}