I has a Controller that serves up Events. The Controller provides the route GET => /Events that will return an array of Events.
This Controller can serve Events to 3 different types of authentication. Admin, Api, and User.
If the requester is Authenticated as a User I want to return the Event object but scoped to the user. e.g.
class Event {
public string Title { get; set; }
}
class EventView {
public string Title { get; set; }
public bool RSVPed { get; set; }
}
How can I make this possible in my Controller-
[RoutePrefix("Events")]
class EventsController {
[#Authorize(AuthenticationType.Admin, AuthenticationType.Api)]
[HttpGet]
[Route("")]
public async Task<IHttpActionResult> Get() { }
[#Authorize(AuthenticationType.User)]
[HttpGet]
[Route("")]
public async Task<IHttpActionResult> Get() { }
}
Have one action with all the allowed permissions. within the action you perform desired behavior based on authorized principal.
[RoutePrefix("Events")]
public class EventsController : ApiController {
[Authorize(AuthenticationType.Admin, AuthenticationType.Api, AuthenticationType.User)]
[HttpGet]
[Route("")] //Matches GET => /Events
public async Task<IHttpActionResult> Get() {
var user = User.Identity;
if(user.AuthenticationType == AuthenticationType.User) {
//...User specific code
} else {
//...Admin, API specific code
}
}
}
Other wise you have to make the routes unique so as not to conflict with each other.
[RoutePrefix("Events")]
public class EventsController : ApiController {
[Authorize(AuthenticationType.Admin, AuthenticationType.Api)]
[HttpGet]
[Route("")] //Matches GET => /Events
public async Task<IHttpActionResult> Get() {
//...
}
[Authorize(AuthenticationType.User)]
[HttpGet]
[Route("{id:int}")] //Matches GET => /Events/12345
public async Task<IHttpActionResult> Get(int id) {
//...
}
}
Related
I created ASP.net core 3.0 web API application and added HttpPost endpoint to it.
When I post using postman to this post endpoint, the endpoint doesn't get the JSON I pass to it and instead gets null.
Is there something that has changed in .NET Core 3.0 that has changed/broken HTTP post endpoints?
The JSON I posted:
{
"status": "0",
"operation":"",
"filter":"",
"currentOrderList": [
]
}
Controller code:
[Route("api/[controller]")]
public class ValuesController : Controller
{
// GET: api/<controller>
[HttpGet]
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
// GET api/<controller>/5
[HttpGet("{id}")]
public string Get(int id)
{
return "value";
}
// POST api/<controller>
[HttpPost]
public void Post([FromBody]string value)
{
}
// PUT api/<controller>/5
[HttpPut("{id}")]
public void Put(int id, [FromBody]string value)
{
}
// DELETE api/<controller>/5
[HttpDelete("{id}")]
public void Delete(int id)
{
}
}
The url I am posting to is https://localhost:44336/api/values. I can see that the endpoint is being hit by the fact that the method is being hit during debugging in visual studio. The only issue is that the parameter is coming in as null
Create a model to match the given data
public class MyClass {
[JsonProperty("status")]
public int Status { get; set; }
[JsonProperty("operation")]
public string Operation { get; set; }
[JsonProperty("filter")]
public string Filter { get; set; }
[JsonProperty("currentOrderList")]
public string[] CurrentOrderList { get; set; }
}
Then update the controller action to expect the desired type
//POST api/values
[HttpPost]
public IActionResult Post([FromBody]MyClass value) {
if(ModelState.IsValid) {
//...
return Ok();
}
return BadRequest();
}
In addition to adding the [JsonProperty] annotations as Nkosi said, I also had to add the nuget package
Microsoft.AspNetCore.Mvc.NewtonsoftJson
and append .AddNewtonsoftJson() into Startup.cs. I found that in another stackoverflow question, but neither change by itself was enough to get my models to hydrate. It took both to get it working.
services.AddMvc().AddRazorRuntimeCompilation().AddNewtonsoftJson();
Using ASP.NET Core 2.2 I have the following ApiController action:
[ApiController]
public class PostController : Controller
{
[HttpGet("posts/{postId:int:min(1)}")]
public async Task<IActionResult> GetByPostId([FromQuery]GetByPostIdRequest request)
{
}
}
Where GetByPostIdRequest is the following:
public class GetByPostIdRequest
{
[FromRoute]
public Int32 PostId { get; set; }
public String LanguageCode { get; set; }
public IncludeExpression Include { get; set; }
}
The only way all parameters get values are:
Have FromQuery in action so I don't have the error Unsupported Media Type
Have FromRoute inside the Request class to bind the PostId.
Isn't there another way to do this?
I tried the following, which is logic to me, but does not work:
[ApiController]
public class PostController : Controller
{
[HttpGet("posts/{postId:int:min(1)}")]
public async Task<IActionResult> GetByPostId([FromRoute, FromQuery]GetByPostIdRequest request)
{
}
}
This is the package what you might be looking for: HybridModelBinding.
It solves the problem of mixed model binding in ASP.NET by using the [FromHybrid] attribute. Your model will get bound with data from the body of a request, and then get updated with data from route or querystring-attributes.
[ApiController]
public class PostController : Controller
{
[HttpGet("posts/{postId:int:min(1)}")]
public async Task<IActionResult> GetByPostId([FromHybrid]GetByPostIdRequest request)
{
}
}
For example:
api/file/occurrence?sha256=...
[HttpGet]
[Route("api/file/")]
public async Task<IHttpActionResult> GetFileBySha256Async([FromUri] FilesBySha256RequestDTO requestDTO)
{
}
api/file/occurrence?sha256=...&from_date=..&to_date=..
[HttpGet]
[Route("api/file/")]
public async Task<IHttpActionResult> GetFileBySha256AndDateAsync([FromUri] FilesBySha256AndDateRequestDTO requestDTO)
{
}
And the DTOs:
public class FilesBySha256RequestDTO
{
public string sha256 { get; set; }
}
public class FilesBySha256AndDateRequestDTO
{
public string sha256 { get; set; }
public DateTime? from_date { get; set; }
public DateTime? to_date { get; set; }
}
How can I accomplish this behavior? I am getting the following exception:
"ExceptionMessage": "Multiple actions were found that match the request: \r\nGetFileBySha256Async on type Cynet.Client.WebAPI.Controllers.FileController\r\nGetFileOccurrencesSha256 on type Cynet.Client.WebAPI.Controllers.FileController
It is not possible to distinguish the route between two because api/file/occurrence?sha256=...&from_date=..&to_date=.. and api/file/occurrence?sha256=... is the same thing for the framework. The first thing you can do is changing the second route like api/fileOnDate/. If it is impossible to do it, you can define a third function and use it as a manual router such as;
[HttpGet]
[Route("api/file/")]
public async Task<IHttpActionResult> GetFileBy([FromUri] FilesBySha256AndDateRequestDTO requestDTO)
{
if (!requestDTO.from_date.HasValue && !requestDTO.to_date.HasValue)
{
return await this.GetFileBySha256Async(new FilesBySha256RequestDTO() { sha256 = requestDTO.sha256 });
}
else
{
return await this.GetFileBySha256AndDateAsync(requestDTO);
}
}
private async Task<IHttpActionResult> GetFileBySha256Async(FilesBySha256RequestDTO requestDTO)
{
}
private async Task<IHttpActionResult> GetFileBySha256AndDateAsync(FilesBySha256AndDateRequestDTO requestDTO)
{
}
hope it helps.
I've read a few SO posts and none of them quite cover my scenario so I'm going to post here.
Given the following route config registration:
public static void Register(HttpConfiguration config)
{
// Web API configuration and services
// Web API routes
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
and these controller actions in a controller that inherits from ApiController:
public GetDocumentsResponse Post([FromBody]GetDocumentsRequest request)
{
}
public FinishDocumentsResponse Post([FromBody] FinishDocumentsRequest request)
{
}
public class GetDocumentsRequest
{
public string CorrelationId { get; set; }
public int Id { get; set; }
public string ObjectId { get; set; }
public string BusinessArea { get; set; }
public string UserId { get; set; }
public string SystemName { get; set; }
public string SystemToken { get; set; }
public Letter LetterDetails { get; set; }
public List<KeyValuePair<string, string>> KeyValue { get; set; }
}
public class FinishDocumentsRequest
{
public string CorrelationId { get; set; }
public string[] Documents { get; set; }
}
I thought doing it this way would be enough disambiguation for the IHttpActionSelector to correctly choose the route, but unfortunately it is not.
So my questions is "Is there a way to make this code work correctly, and keep it in the same controller?"
Thank you,
Stephen
You could use attribute routing for this.
Define the route as a string in the Route attribute ontop of the methods as this
[Route("api/controller/Post1")]
[HttpPost]
public GetDocumentsResponse Post([FromBody]GetDocumentsRequest request)
{
}
[Route("api/controller/Post2")]
[HttpPost]
public FinishDocumentsResponse Post([FromBody] FinishDocumentsRequest request)
{
}
The request routing pipeline isn't smart enough to determine if the body of the request matches the parameter type (aka overloading). (The compiler is smart enough, which is why this compiles and you have runtime issues.)
You have a couple of different options.
You can either add an [Route(<ActionName>)] attribute on both of your posts.
Make two controllers, one for GetDocuments and one for FinishDocuments
Make one Post method that is ambiguous. (I'd avoid this)
If you choose option 1, your API uri will have to be .../api/MyController/MyActionName rather than .../api/MyController/. It's also advisable to add [HttpGet] and [HttpPost] attributes on your methods.
Sample:
public class DocumentController : ApiController
{
// POST /api/Document/GetDocuments
[HttpPost]
[Route("GetDocuments")]
public GetDocumentsResponse Post([FromBody]GetDocumentsRequest request) { ... }
// POST /api/Document/FinishDocuments
[HttpPost]
[Route("FinishDocuments")]
public FinishDocumentsResponse Post([FromBody] FinishDocumentsRequest request){ ...}
}
If you choose option 2, you have to maintain an additional code file.
public class GetDocumentsController : ApiController
{
// POST /api/GetDocuments
[HttpPost]
public GetDocumentsResponse Post([FromBody]GetDocumentsRequest request) { ... }
}
public class FinishDocumentsController : ApiController
{
// POST /api/FinishDocuments/
[HttpPost]
public FinishDocumentsResponse Post([FromBody] FinishDocumentsRequest request){ ...}
}
If you choose option 3, may God have mercy on your soul you're going to have a bad time maintaining it.
Add the Route attribute decoration to your web api functions and that will assit the selector to choose the route:
[Route("Post1")]
public GetDocumentsResponse Post([FromBody]GetDocumentsRequest request)
{
}
[Route("Post2")]
public FinishDocumentsResponse Post([FromBody] FinishDocumentsRequest request)
{
}
I also recommend adding the http method decoration such as [HttpPost] or [HttpGet]
What's the right way to POST an Entity with a SPATIAL PROPERTY on ASP.NET Web API OData (v4)?
Serialization on GET works fine, but everything I try in the POST causes the model to go null.
Is POST supported at all?
Thanks in advance.
public class PlacesController : ODataController
{
[HttpGet]
[EnableQuery]
public virtual async Task<IHttpActionResult> Get([FromODataUri] string key)
{
var place = new Place()
{
Id = Guid.NewGuid().ToString(),
Location = GeographyPoint.Create(1, 1)
};
return Ok(place);
}
[HttpPost]
[EnableQuery]
public virtual async Task<IHttpActionResult> Post(Place place)
{
if (place == null)
{
return BadRequest();
}
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
return Created(place);
}
}
[DataContract]
public class Place
{
[DataMember]
public string Id
{
get;
set;
}
[DataMember]
public GeographyPoint Location
{
get;
set;
}
}
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.MapHttpAttributeRoutes();
var edmBuilder = new ODataConventionModelBuilder();
edmBuilder.EntitySet<Place>("Places");
var model = edmBuilder.GetEdmModel();
config.MapODataServiceRoute(routeName: "ODataRoute", routePrefix: "api", model: model);
}
}
After reading through below mentioned article it seems possible. You will need to use oDataActionParameters type as input to the post and later in your method cast it to the required type. Mehtod signature will something like below:
public async Task<IHttpActionResult> RateProduct(ODataActionParameters parameters)
check this link for examples and deep dive on this