WebApiCompatShim - how to configure for a REST api with MVC 6 - migration

I was having a look at this link that shows how to migrate from Web API 2 to MVC 6.
I am trying to have Action methods in my controllers with the HttpRequestMessage bound. This works in Web Api 2.
[Route("", Name = "AddTaskRoute")]
[HttpPost]
public Task AddTask(HttpRequestMessage requestMessage, [FromBody]NewTask newTask)
{
var task = _addTaskMaintenanceProcessor.AddTask(newTask);
return task;
}
and the requestMessage contains the details about the Http request such as headers, verb, etc.
I am trying to get the same with MVC 6 but the requestMessage seems to be incorrectly bound and it shows details such as the method being GET when the action is actually a POST. I believe I haven't configured the WebApiCompatShim as per the article suggests so the binding is not properly done. But I do not have the extension method services.AddWebApiConventions(); available in the version "Microsoft.AspNet.Mvc.WebApiCompatShim": "6.0.0-rc1-final"
Anybody has succeed when trying this?
PS: The Request property available in my controller seems to have details about the http request, but I'd like to have the HttpRequestMessage instance.

In MVC6, You should be able to use the Request object to get header information.
var contentTypeHeader = Request.Headers["Content-Type"];
It is true that they removed some of the nice methods like Request.CreateResponse() and OK() etc.. But there are some alternatives you can use.
All of these classes we will be using to create a response are inheriting from the ObjectResult base class. So you can use ObjectResult as the return type of your Web api method.
HttpOKObjectResult
In MVC6, You can use create an object of HttpOKObjectResult class and use that as your return value instead of Request.CreateResponse(). This will produce the status code 200 OK for the response.
Web API2 code
public HttpResponseMessage Post([FromBody]string value)
{
var item = new { Name= "test", id = 1 };
return Request.CreateResponse(HttpStatusCode.OK,item);
}
MVC 6 code
[HttpPost]
public ObjectResult Post([FromBody]string value)
{
var item = new {Name= "test", id=1};
return new HttpOkObjectResult(item);
}
Or simply use the OK() method.
[HttpPost]
public ObjectResult Post([FromBody]string value)
{
var item = new {Name= "test", id=1};
return Ok(item);
}
CreatedAtRouteResult
You can use CreatedAtRouteResult class to send a response with 201 Created status code with a location header.
MVC 6 code
[HttpPost]
public ObjectResult Post([FromBody]string value)
{
var item = new { Name= "test", id=250};
return new CreatedAtRouteResult(new { id = 250}, item);
}
The client will receive a location header in the response which will point to the api route with 250 as the value for the id parameter.
HttpNotFoundObjectResult
You can use this class to return a 404 Not found response.
Web API2 code
public HttpResponseMessage Post([FromBody]string value)
{
return Request.CreateResponse(HttpStatusCode.NotFound);
}
MVC 6 code
[HttpPost]
public ObjectResult Post([FromBody]string value)
{
return new HttpNotFoundObjectResult("Some");
}

I found that to use the Microsoft.AspNetCore.Mvc.WebApiCompatShim, it should be services.AddMvc().AddWebApiConventions() see this example instead of services.AddWebApiConventions() as shown in the docs.
I'm putting in a feedback item on their docs.

Related

415 Unsupported Media Type in ASP.NET core web api

I am trying to experiment with asp.net core web api so I made some simple api with a controller like this:
[ApiController]
[Route("MyController")]
public class MyController : ControllerBase
{
[HttpGet]
[Route("GetResult")]
public IActionResult GetResult(string param1, string param2= null, SomeClassObj obj = null)
{ .... }
}
I ran the api locally and sent this postman GET request:
https://localhost:5001/MyController/GetResult?param1=someString
I got the error: 415 Unsupported Media Type
What am I missing here so it could work?
I was getting the same error after invoking the WEB API from .NET MVC.
As suggested by #zhulien, I have changed from [FromBody] to [FromForm] in WebAPI, it works fine for me.
.NET Core WebAPI method.
public async Task<IActionResult> Login([FromForm] LoginModel loginInfo)
{ // JWT code here }
.Net Core MVC Action Method.
public async void InvokeLoginAPIAsync(string endPoint, string userName, string pwd)
{
configuration = new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
.Build();
baseUrl = configuration["Application:BaseAPI"] ?? throw new Exception("Unable to get the configuration with key Application:BaseAPI");
string targetUrl = string.Format("{0}/{1}", baseUrl, endPoint);
using (HttpClient deviceClient = new HttpClient())
{
var request = new HttpRequestMessage(HttpMethod.Post, targetUrl);
var data = new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>("userName", userName),
new KeyValuePair<string, string>("password", pwd)
};
request.Content = new FormUrlEncodedContent(data);
using (var response = await deviceClient.SendAsync(request))
{
if (response.StatusCode == HttpStatusCode.OK)
{
TempData["Response"] = JsonConvert.SerializeObject(response.Content);
}
}
}
}
Which version of .NET Core are you using?
Try doing the request from the browser and see if you have the same result.
Also, are you sure you're doing a GET and not a POST request in Postman? You shouldn't get 415 errors for GET requests, especially when you're not sending any body.
This error mainly occurs when you try to send a body and you haven't specified the media-type through the Content-Type header.
Ensure that the request is GET and your body is empty.
Solution after post edit:
As you're trying to parse a DTO object(SomeClassObj), you should specify where the values should come from. In order to fix your specific case, add the [FromQuery] attribute before SomeClassObj.
Your code should look like this:
[ApiController]
[Route("MyController")]
public class MyController : ControllerBase
{
[HttpGet]
[Route("GetResult")]
public IActionResult GetResult(string param1, string param2= null, [FromQuery]SomeClassObj obj = null)
{ .... }
}
This tells the parser to fetch the data from the query string. This will fix the 415 issue. However, if you want to bind to complex types, especially on get, checkout those topics: ASP.NET CORE 3.1 Model Binding and this issue as you will most probably encounter issues with parsing your DTO object.
Use [FromForm] attribute before each argument in the controller function.

ASP.NET Core Web API FromHeader binding x-api-key header

I have a x-api-key header that I want to bind to my controller paramter. I tried the below code but the parameter is still null.
[HttpGet]
public ActionResult Get([FromHeader] xApiKey) {
var apikey = xApiKey;
}
After 5 minutes of posting this question I found the answer here. So using the name property of FromHeader.
[HttpGet]
public ActionResult Get([FromHeader(Name = "x-api-key")] apiKey) {
// code here
}

Add a message to your HTTP 400 Bad Request in ASP.NET Core 3.1

Is there a way I can add a message to a BadRequest action result, and for that message to be visible to external clients, such as Postman? I am using ASP.NET Core 3.1.
Part of my code is included below. I want to say what the problem is—e.g., that the id sent in the body is not the same as the one taken from the URL. For now, I am using an Error object that I’ve made, which has the error code and message. But those aren't visible in Postman when I send the request.
public ActionResult PutColour(int id, Colour colour)
{
if (id != colour.Id)
{
return BadRequest(new Error("IDNotTheSame","ID from URL is not the same as in the body."));
}
}
What you pass to BadRequest is serialized and returned as the response body. If nothing is coming through, the only explanation is that you don't have any public properties on Error that can be serialized. For example, if you had something like:
public class Error
{
public Error(string type, string description)
{
Type = type;
Description = description;
}
public string Type { get; private set }
public string Description { get; private set; }
}
Then, you get a response like:
{
"type": "IDNotTheSame",
"description": "ID from URL is not the same as in the body."
}
Not sure what your Error class currently does. However, this is probably unnecessary anyways, as you can just use ModelState:
ModelState.AddModelError("Id", "ID from URL is not the same as in the body.");
return BadRequest(ModelState);
Finally, it should probably be said that this is a pointless validation in the first place. You shouldn't be sending an id with the model at all (always use a view model, not an entity class), and even if you do send it, you can simply just overwrite it with the value from the URL:
model.Id = id;
Done. No issues and you don't need to worry about sending an error back.
Please follow the following steps to show Bad Request with your custom Class.
Create constructor here of that class. And add manual description and many more
Step 1
Add a custom class and inherit this class with ValidationProblemDetails. After that initialize below base class constructor by passing context.ModelState.
public ValidationProblemDetails(ModelStateDictionary modelState)
Here is implementation-
public class CustomBadRequest : ValidationProblemDetails
{
public CustomBadRequest(ActionContext context) : base(context.ModelState)
{
Detail = "add details here";
Instance = "add extension here";
Status = 400;
Title = "add title here";
Type = "add type here";
}
}
Step 2
Add this Custom Bad request class in ConfigureServices method in Startup.cs.
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().ConfigureApiBehaviorOptions(options =>
{
options.InvalidModelStateResponseFactory = context =>
{
var problems = new CustomBadRequest(context);
return new BadRequestObjectResult(problems);
};
});
}
Step 3
Build and run.
and if any bad request come then you will see output like below-

MVC 6 Web Api: Resolving the location header on a 201 (Created)

In Web Api 2.2, we could return the location header URL by returning from controller as follows:
return Created(new Uri(Url.Link("GetClient", new { id = clientId })), clientReponseModel);
Url.Link(..) would resolve the resource URL accordingly based on the controller name GetClient:
In ASP.NET 5 MVC 6's Web Api, Url doesn't exist within the framework but the CreatedResult constructor does have the location parameter:
return new CreatedResult("http://www.myapi.com/api/clients/" + clientId, journeyModel);
How can I resolve this URL this without having to manually supply it, like we did in Web API 2.2?
I didn't realise it, but the CreatedAtAction() method caters for this:
return CreatedAtAction("GetClient", new { id = clientId }, clientReponseModel);
Ensure that your controller derives from MVC's Controller.
In the new ASP.NET MVC Core there is a property Url, which returns an instance of IUrlHelper. You can use it to generate a local URL by using the following:
[HttpPost]
public async Task<IActionResult> Post([FromBody] Person person)
{
_DbContext.People.Add(person);
await _DbContext.SaveChangesAsync();
return Created(Url.RouteUrl(person.Id), person.Id);
}
There is an UrlHelper class which implements IUrlHelper interface.
It provides the requested functionality.
Source code
There is also CreatedAtRoute:
public async Task<IActionResult> PostImpl([FromBody] Entity entity)
{
...
return CreatedAtRoute(entity.Id, entity);
//or
return CreatedAtRoute(new { id = entity.Id }, entity);
}
My GET action has a route name
[HttpGet("{id:int}", Name = "GetOrganizationGroupByIdRoute")]
public async Task<IActionResult> Get(int id, CancellationToken cancellationToken = default(CancellationToken))
{
...
}
And my POST action uses that route name to return the URL
[HttpPost]
public async Task<HttpStatusCodeResult> Post([FromBody]OrganizationGroupInput input, CancellationToken cancellationToken = default(CancellationToken))
{
...
var url = Url.RouteUrl("GetOrganizationGroupByIdRoute", new { id = item.Id }, Request.Scheme, Request.Host.ToUriComponent());
Context.Response.Headers["Location"] = url;
...
}
Resulting response using Fiddler
Hope that helps.
I use this simple approximation based on the Uri being served at the web server:
[HttpPost]
[Route("")]
public IHttpActionResult AddIntervencion(MyNS.MyType myObject) {
return Created<MyNS.MyType>(Request.RequestUri + "/" + myObject.key, myObject);
}

How to Access different methods in Web API from console Application?

How do i acces my methods in my webapi when there are multiple get,post and delete methods from my console application, how do i differentiate them this is my api controller
public IQueryable<Store> GetAll()
{
return StoreRepository.All;
}
//GetAll Stores including all relation tables
public IQueryable<Store> GetAllIncluding()
{
return StoreRepository.AllIncluding();
}
//Get store by id/id=5
public Store Find(long storeid)
{
stores = StoreRepository.Find(storeid);
return stores;
}
//Insert or Update Store
public void InsertorUpdateWithGraph(Store store)
{
StoreRepository.InsertOrUpdateWithGraph(store);
}
//Insert or Update StoreDetail
public void InsertOrUpdateStoreDetail(StoreDetail storedetail)
{
StoreRepository.InsertOrUpdateStoreDetail(storedetail);
}
//Get StoreDetail by id/id=5
public StoreDetail FindStoreDetail(long storedetailid)
{
storedetail = StoreRepository.FindStoreDetail(storedetailid);
return storedetail;
}
public List<StoreDetail> GetAllStoreDetails(long storedetailid)
{
List<StoreDetail> storedetails = StoreRepository.GetAllStoreDetails(storedetailid);
return storedetails;
}
public Sage FindSage(long sageid)
{
return StoreRepository.FindSage(sageid);
}
like this i may have more than two get,post,insert or update methods i have to acces this methods from my console application how can i map the methods i want,cana any one help me here how will i define the routes for this
You can have multiple "Get..." actions and you can get away without HttpGet attribute because they start with "Get". "Find..." methods need to be decorated with HttpGet
Those "Insert..." you need to decorate with HttpPost or HttpPut attributes.
Parameters to these methods can be configured in two ways. You can POST object like {id:"ddd",name:"nnn"} to action like
MyAction(int id, string name)
Web APi framework threats any methods that start with Post..., Delete..., Get..., Put... as corresponding Http Verbs. But you can name them the way you with and then decorate with Http attributes.
When it comes to parameters, it is about a correlation of your controller actions to the routes.
And now, to run it from the console application you can use HttpClient
string _webSiteUrl = "www.ffsdfds.com"
HttpClient client = new HttpClient();
client.BaseAddress = new Uri(_webSiteUrl);
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); // for posting
HttpResponseMessage resp = httpClient.GetAsync("/api/area/getall").Result;