I have the following method in my BaseApiController class:
public virtual HttpResponseMessage GetById(int id)
{
var entity = repository.GetById(id);
if (entity == null)
{
var message = string.Format("No {0} with ID = {1}", GenericTypeName, id);
return ErrorMsg(HttpStatusCode.NotFound, message);
}
return Request.CreateResponse(HttpStatusCode.OK, SingleResult.Create(repository.Table.Where(t => t.ID == id)));
}
I'm using SingleResult for OData request (because $expand for single entity not works if I not create SingleResult).
But now I have problem with UnitTests of this method on concrete controller (e.g. AddressApiController). I always get NULL in result:
[TestMethod]
public void Get_By_Id()
{
//Arrange
var moq = CreateMockRepository();
var controller = new AddressApiController(moq);
controller.Request = new HttpRequestMessage()
controller.Request.SetConfiguration(new HttpConfiguration())
// Action
HttpResponseMessage response = controller.GetById(1);
var result = response.Content.ReadAsAsync<T>().Result;
// Accert
Assert.IsNotNull(result);
}
I checked and debug GetById() and find out that repository.Table.Where(t => t.ID == id)) return proper value, but after SingleResult.Create I'm getting NULL.
How can I solve this problem? How can I read content from SingleResult or using something else?
I have not had a chance to mock up an api but from the docs here:
Here are some rules for the method signatures: http://www.asp.net/web-api/overview/odata-support-in-aspnet-web-api/odata-routing-conventions
Try change id to key and attribute, then you probably won't need to use SingleResult.
If the path contains a key, the action should have a parameter named key.
If the path contains a key into a navigation property, the action should have a parameter named relatedKey.
Decorate key and relatedKey parameters with the [FromODataUri] parameter.
POST and PUT requests take a parameter of the entity type.
PATCH requests take a parameter of type Delta, where T is the entity type.
I would be interested to see if that changes the test outcome.
I created extensions:
public static class HttpResponseMessageExtensions
{
public static IQueryable<T> ContentToQueryable<T>(this HttpResponseMessage response) where T : BaseEntity
{
var objContent = response.Content as ObjectContent;
return objContent?.Value as IQueryable<T>;
}
public static T ContentToEntity<T>(this HttpResponseMessage response) where T : BaseEntity
{
var objContent = response.Content as ObjectContent;
return objContent?.Value as T;
}
}
and then:
var result = response.ContentToEntity<T>();
Related
so for now it will match both of the actions by the route api/comments, I want the second one to be api/comments?blogId=1, but I don't want it to be api/comments/{blogId}.
//Get api/comments
[HttpGet]
[Route("/")]
public async Task<IActionResult> GetAll()
{
var comments = await _context.Comments.ToListAsync();
if(comments != null)
return Ok(new { status = 200, comments });
return NotFound();
}
//Get api/comments?blogId=1
[HttpGet]
public async Task<IActionResult> GetCommentsBy(int blogId)
{
var allComments = await _context.Comments.ToListAsync();
if (allComments != null)
{
var commentsByBlogId = allComments.Where(c => c.BlogId == blogId);
return Ok(new { status = 200, comments = commentsByBlogId });
}
return NotFound();
}
Routes are unique by looking at there themplate. Even if you're using blogId as query parameter, the two actions are using the same route template which is this api/comments.
To do what you're trying to do is to using only have one action that will return result when you send a blogId or not.
So just add one action Get and the logic should look like below:
[HttpGet]
public async Task<IActionResult> GetComments(int? blogId /* blogId i nullable so it is not required */)
{
// Gets the comments query but don't execute it yet. So no call to ToListAsync() here.
var commentsQuery = _context.Comments;
if (blogId.HasValue)
{
// When you've a blogId set in the query then add a filter to the query.
commentsQuery = commentsQuery.Where(c => c.BlogId == blogId);
}
var comments = await commentsQuery.ToListAsync();
// When the list is empty just send it as it is. the caller should be able to handle the case where the comments list is empty and status code is 200
// You also don't need to set the status code in your responde body. The caller should be able to get the response status first before checking the response body.
return Ok(comments);
}
I'm trying to post anonymous object via httpclient, however orderId is null and collection empty when it hits controller.
public async Task<Response> CancelOrderAsync(int orderId, ICollection<int> ids)
{
Response result = null;
using (IHttpClient client = HttpClientFactory.CreateHttpClient())
{
var obj = new {OrderId = orderId, Ids = ids};
string json = JsonConvert.SerializeObject(obj);
HttpContent postContent = new StringContent(json, Encoding.UTF8, "application/json");
using (var response = await client.PostAsync($"{url}/admin/cancel", postContent).ConfigureAwait(false))
{
if (response != null && response.IsSuccessStatusCode)
{
...
}
}
}
return result;
}
// Controller
[HttpPost]
[ActionName("cancel")]
public async Task<Response> Cancel(int orderId, ICollection<int> ids)
{
// order is null, collection empty
...
EDIT:
Changed my controller to this for simplicity
[HttpPost]
[ActionName("cancel")]
public async Task<SimpleResponse> Cancel(int orderId)
Via Postman, i'm posting this body:
{
"orderId": "12345"
}
Still, orderId comes in as 0 (zero) ??
The controller action on the server side will need a concrete type to read the entire body of the request
public class Order {
public int OrderId { get; set; }
public int[] Ids { get; set; }
}
This is primarily because the action can only read from the body once.
Update action to...
[HttpPost]
[ActionName("cancel")]
public async Task<Response> Cancel([FromBody]Order order) {
if(ModelState.IsValid) {
int orderId = order.OrderId;
int[] ids = order.Ids;
//...
}
//...
}
the original code used to send the request in the example will work as is, but as mentioned it can be improved.
The HttpClient can do the serialisation for you. See if
var response = await client.PostAsJsonAsync($"{url}/admin/cancel", obj);
works better. Then you don't need to write the serialisation code yourself.
If you still have a problem, use a tool such as Fiddler to monitor the actual request and see what parameter and values are submitted in the request body, to see if they match what's expected by the endpoint.
Currently, my ApiControllers are returning XML as a response, but for a single method, I want to return JSON. i.e. I can't make a global change to force responses as JSON.
public class CarController : ApiController
{
[System.Web.Mvc.Route("api/Player/videos")]
public HttpResponseMessage GetVideoMappings()
{
var model = new MyCarModel();
return model;
}
}
I tried doing this, but can't seem to convert my model to a JSON string correctly:
var jsonString = Json(model).ToString();
var response = this.Request.CreateResponse(HttpStatusCode.OK);
response.Content = new StringContent(jsonString, Encoding.UTF8, "application/json");
return response;
If you can't make a global change to force responses as JSON,
then try:
[Route("api/Player/videos")]
public HttpResponseMessage GetVideoMappings()
{
var model = new MyCarModel();
return Request.CreateResponse(HttpStatusCode.OK,model,Configuration.Formatters.JsonFormatter);
}
OR
[Route("api/Player/videos")]
public IHttpActionResult GetVideoMappings()
{
var model = new MyCarModel();
return Json(model);
}
If you want to change globally, then first go to YourProject/App_Start/WebApiConfig.cs and add:
config.Formatters.XmlFormatter.SupportedMediaTypes.Remove(
config.Formatters.XmlFormatter.SupportedMediaTypes.FirstOrDefault(t => t.MediaType == "application/xml"));
at the bottom of the Register method.
Then try:
[Route("api/Player/videos")]
public IHttpActionResult GetVideoMappings()
{
var model = new MyCarModel();
return Ok(model);
}
The XML is returned instead JSON because the caller is requesting XML. The returned format can be forced to JSON using a filter that adds the header you need and lets MVC resolve the JSON.
public class AcceptHeaderJsonAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(System.Web.Http.Controllers.HttpActionContext actionContext)
{
actionContext.Request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
}
}
So you can decorate the method you want to force a JSON response with this attribute and keep the same global JSON configuration and serialization as any other method.
Try this ApiController.Ok.
You just do return Ok(model) and change the return type to IHttpActionResult.
Example:
public class CarController : ApiController
{
[System.Web.Mvc.Route("api/Player/videos")]
public IHttpActionResult GetVideoMappings()
{
var model = new MyCarModel();
return Ok(model);
}
}
For API controllers it is up to the caller to determine how the response is created. Unless you specifically add code to force only one type of response. Here is a simple example of an API method and what happens when called requesting XML, or JSON.
public class XmlEampleController : ApiController
{
[HttpPost]
[ActionName("MyOrderAction")]
public HttpResponseMessage MyOrder([FromBody]MyOder order)
{
if (order != null)
{
return Request.CreateResponse<MyOder>(HttpStatusCode.Created, order);
}
return Request.CreateResponse(HttpStatusCode.BadRequest);
}
[Serializable]
public partial class MyOder
{
private string dataField;
public string MyData
{
get
{
return this.dataField;
}
set
{
this.dataField = value;
}
}
}
}
Sample:
Maybe the issue is with WebApiConfig file.
At the end of the file add these 2 lines
var appXmlType = config.Formatters.XmlFormatter.SupportedMediaTypes.FirstOrDefault(t => t.MediaType == "application/xml");
config.Formatters.XmlFormatter.SupportedMediaTypes.Remove(appXmlType);
It is in Project/App_Start/WebApiConfig.cs For asp.net MVC
If I have controller GET methods, such as:
[HttpGet]
[Route("accountrecv({accountid})/promisepay", Name = "GetPromisePay")]
public HttpResponseMessage GetPromisePay(int accountid)
{
var query = Request.RequestUri.Query;
var uri = new Uri(Client.Instance.BaseAddress.ToString() + accountid + "/promisepay" + query);
var request = new HttpRequestMessage { RequestUri = uri, Method = HttpMethod.Get };
var response = Client.Instance.SendAsync(request);
return response.Result;
}
How can I impose behavior on all responses where the HttpStatusCode is NOT ok?
I imagine I would create an attribute at the method level, such as:
[NonOKResponse]
[HttpGet]
[Route.......]
public HttpResponseeMessage GetPromisePay(int accountid)
{
//my code
return response.Result //but force it here to return 404 for EVERY response other than 200
}
How can I define an attribute that I can use on all GETs to force a specific response based on some criteria?
I believe this article answers your question. You can use the ExceptionFilterAttribute to handle and manage exceptions. For example:
public class GeneralExceptionFilterAttribute : ExceptionFilterAttribute
{
public override void OnException(HttpActionExecutedContext context)
{
context.Response = new HttpResponseMessage(HttpStatusCode.NotFound);
}
}
It sounds like you want a Result Filter.
https://learn.microsoft.com/en-us/aspnet/mvc/overview/older-versions-1/controllers-and-routing/understanding-action-filters-cs
I'm trying to figure out a way to return an object in my response while still maintaining an understandable return type.
So for starters, I know this works as expected.
public async Task<HttpResponseMessage> DoMyThing(MyObject myObject)
{
var result = await _myService.CreateMyThingAsync(myObject);
return Request.CreateResponse(HttpStatusCode.Created, result);
}
But what I really want is for this pseudo code to work... somehow.
public Task<MyObject> DoMyThing(MyObject myObject)
{
var result = _myService.CreateMyThingAsync(myObject);
return Request.CreateResponse<Task<MyObject>>(HttpStatusCode.Created, result);
// or better yet
return Request.CreateResponse<MyObject>(HttpStatusCode.Created, result);
}
Is there any magic in the framework that'll make this happen? Or are there any third party libraries that can do this?
Essentially I need to return the Task<MyObject> instead of the Task<HttpResponseMessage>
I'm also open to other suggestions on how to return a non 200 response while still returning the Task<MyObject>
The issue with specifying the type as the return type is that you restrict yourself to having to return that type. That may sound strange but actually there will be many cases where you need to be able to support multiple response, such as 404, 200 201 and so on.
To handle the documentation of this you can use the ResponseType attribute, like so:
[ResponseType(typeof(BookDto))]
public async Task<IHttpActionResult> GetBook(int id)
{
BookDto book = await db.Books.Include(b => b.Author)
.Where(b => b.BookId == id)
.Select(AsBookDto)
.FirstOrDefaultAsync();
if (book == null)
{
return NotFound();
}
return Ok(book);
}
Take a look here
Edit:
In Asp.Net Core you use the ProducesResponseType attribute which can be used multiple times per method
See here
Example
[ProducesResponseType(typeof(BookDto), 200)]
[ProducesResponseType(typeof(object), 201)]
public async Task<IActionResult> GetBook(int id)
{
BookDto book = await db.Books.Include(b => b.Author)
.Where(b => b.BookId == id)
.Select(AsBookDto)
.FirstOrDefaultAsync();
if (book == null)
{
return NotFound();
}
return Ok(book);
}
EDIT: Multiple response attributes prior to dot net core
You can use Swagger to help document / describe your API, they have a custom attribute called SwaggerResponse
The .Net port of Swagger is Swashbuckle, take a look here
This would be the best pattern in WebApi.
public async Task<IHttpActionResult> DoMyThing(MyObject myObject)
{
try
{
var result = await _myService.CreateMyThingAsync(myObject);
return new JsonStreamHttpActionResult<MyObject>(Request, System.Net.HttpStatusCode.Created, result);
}
catch (Exception ex)
{
Logger.Instance.Error(ex);
return new HttpActionResult(HttpStatusCode.InternalServerError, "An error has occured");
}
}
with a generic serializer. You can then use the "better" IHttpActionResult instead of a real return value.
public class JsonStreamHttpActionResult<T> : IHttpActionResult
{
private T responseData;
private HttpRequestMessage request;
private HttpStatusCode statusCode;
public JsonStreamHttpActionResult(HttpRequestMessage request, System.Net.HttpStatusCode code, T responseData)
{
this.responseData = responseData;
this.request = request;
this.statusCode = code;
}
public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
{
HttpResponseMessage response = request.CreateResponse(statusCode);
response.Content =
new PushStreamContent((stream, content, context) =>
{
var serializer = new Newtonsoft.Json.JsonSerializer();
using (var writer = new System.IO.StreamWriter(stream))
{
serializer.Serialize(writer, responseData);
stream.Flush();
}
});
return Task.FromResult(response);
}
}