How to handle exceptions in Odata V4 client? - asp.net

Asp.Net Web API Odata Controller Action:
public async Task<IHttpActionResult> Post(Product product)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
db.Products.Add(product);
await db.SaveChangesAsync();
return Created(product);
}
Odata client code:
(Odata v4 client code generator v4)
static void AddProduct(Default.Container container, ProductService.Models.Product product)
{
container.AddToProducts(product);
var serviceResponse = container.SaveChanges();
foreach (var operationResponse in serviceResponse)
{
Console.WriteLine("Response: {0}", operationResponse.StatusCode);
}
}
I would like to handle exception in a proper way inside AddProducts() Method while saving the changes.
How can I catch process the ModelState error which is sent from server return BadRequest(ModelState);?
Finally I just want to show the error message to the end uses which was sent from server.
Example:
"Product category is required."
What is the use of ODataException class? Will this help me?
Please help me.

if I understood well, you want to intercept that the ModelState is not valid, and customize the OData error that is shown to the user.
If you just want that the errors of the invalid model show up in the returned payload, you can use:
if (!ModelState.IsValid)
{
return Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState);
}
If you want to fully control the exceptions handling and messages shown, I'd suggest several action points for you to accomplish this:
Intercept ModelState is not valid: you can do this with a custom ActionFilterAttribute. In there, you can override the method OnActionExecuting(HttpActionContext actionContext). You can access the ModelState through actionContext.ModelState, check if it is valid, check the fields that have errors, check the nature of these errors and the generated messages for these errors, etc. The ModelState may be not valid for different reasons, like different types than the expected, not meet requirements specified by DataAnnotations, etc. You can check more on Model validation in here. For your case, I guess the Product entity will have a Required data annotation in the Category field.
After checking all errors, you can throw a custom Exception with the error/list of errors with the messages you want. This is necessary to later intercept your custom exception and be able to return your custom message in the error payload.
Intercept your custom exception: create a custom ExceptionFilterAttribute to intercept your thrown exceptions. Overriding the
OnException(HttpActionExecutedContext filterContext) you will have access to the exception, and inspecting it you will be able to build your proper OdataError:
In here you should return the HttpResponseMessage with the BadRequest http status code and the created ODataError as a payload. As an example of very simple code (you can see that it would depend on how you build your custom exception):
public override void OnException(HttpActionExecutedContext filterContext)
{
Exception ex = filterContext.Exception;
HttpRequestMessage currentRequest = filterContext.Request;
if (filterContext.Exception.GetType() == typeof(YourCustomValidationException))
{
var oDataError = new ODataError()
{
ErrorCode = "invalidModel",
Message = "Your model is not valid.",
InnerError = new ODataInnerError()
{
TypeName = ex.TheEntityThatHasErrors
},
};
foreach (var validationError in ex.ValidationErrors)
{
oDataError.InnerError.Message += validationError + ", ";
}
HttpResponseMessage response = new HttpResponseMessage(HttpStatusCode.BadRequest);
response.RequestMessage = currentRequest;
response.Content = new StringContent(JsonConvert.SerializeObject(oDataError));
filterContext.Response = response;
}
}
Finally, you will have to setup the custom ActionFilterAttribute and the custom ErrorFilterAttribute to be used each time that a request reach your controller. You can decorate your actions, controllers, or you can set the filters for all your API controllers in the WebApiConfig, with config.Filters.Add(...);
You can find more information about all of this in here. In the end, the error and exception handling is the same for ASP.Net Web API, with or without OData; difference is that if you have an OData API, you should return errors in OData style.
Hope all this info is understandable and helps you somehow.

Related

How to retrieve JSON data from HttpContent

I'm buildin a console Web API to communicate with a localhost server, hosting computer games and highscores for them. Every time I run my code, I get this charming error:
fail:
Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware[1]
An unhandled exception has occurred while executing the request.
System.NotSupportedException: Deserialization of types without a
parameterless constructor, a singular parameterized constructor, or a
parameterized constructor annotated with 'JsonConstructorAttribute' is
not supported. Type 'System.Net.Http.HttpContent'. Path: $ |
LineNumber: 0 | BytePositionInLine: 1.
This is the method I'm using to post to the database. Note that this method is not in the console application. It is in the ASP.NET Core MvC application opening a web browser and listening for HTTP requests (which can come from the console application).
[HttpPost]
public ActionResult CreateHighscore(HttpContent requestContent)
{
string jasonHs = requestContent.ReadAsStringAsync().Result;
HighscoreDto highscoreDto = JsonConvert.DeserializeObject<HighscoreDto>(jasonHs);
var highscore = new Highscore()
{
Player = highscoreDto.Player,
DayAchieved = highscoreDto.DayAchieved,
Score = highscoreDto.Score,
GameId = highscoreDto.GameId
};
context.Highscores.Add(highscore);
context.SaveChanges();
return NoContent();
}
I'm sending POST requests in a pure C# console application, with information gathered from user input, but the result is exactly the same when using Postman for post requests - the above NotSupportedException.
private static void AddHighscore(Highscore highscore)
{
var jasonHighscore = JsonConvert.SerializeObject(highscore);
Uri uri = new Uri($"{httpClient.BaseAddress}highscores");
HttpContent requestContent = new StringContent(jasonHighscore, Encoding.UTF8, "application/json");
var response = httpClient.PostAsync(uri, requestContent);
if (response.IsCompletedSuccessfully)
{
OutputManager.ShowMessageToUser("Highscore Created");
}
else
{
OutputManager.ShowMessageToUser("Something went wrong");
}
}
I'm new to all this HTTP requests stuff, so if you spot some glaring errors in my code, that would be appreciated. Though, the most important question is, what am I missing, and how can I read from the HttpContent object, to be able to create a Highscore object to send to the database?
It seems to be the string jasonHs... line that is the problem, since the app crashed in exactly the same way, when I commented out the rest of the ActionResult method.
Based on your code, we can find that you make a HTTP Post request with a json string data (serialized from a Highscore object) from your console client to Web API backend.
And in your action method, you create an instance of Highscore manually based on received data, so why not make your action accept a Highscore type parameter, like below. Then the model binding system would help bind data to action parameter(s) automatically.
[HttpPost]
public ActionResult CreateHighscore([FromBody]Highscore highscore)
{
//...

HTTP Get or Post returns empty JSON array

when I call my API Webservice its returns an empty array.
In my Header request, i have only a jwt token for authenticating
In Angular:
getSheets(): Observable<Sheet[]> {
return this.http.get(this.config.apiUrl + '/api/SheetsRelationAPI', this.jwt())
.map(this.extractData)
.do(data => console.log('SheetsData:', data)) // debug
.catch(this.handleError);
In Asp.net MVC 5:
[HostAuthentication("bearer")]
[System.Web.Http.Authorize]
public class SheetsRelationAPIController : ApiController
{
private GSheetsContext db = new GSheetsContext();
// GET: api/SheetsRelation
[ResponseType(typeof(SheetsRelationView))]
public IQueryable<SheetsRelationView> GetSheetsRelation()
{
var claims = (User.Identity as System.Security.Claims.ClaimsIdentity).Claims;
var username = "";
foreach (var claim in claims)
if (claim.Type.ToString() == "sub")
{
username = claim.Value.ToString();
}
//var tasks = from tsk in db.SheetsRelation.Include(s => s.SheetsContent.id )
//select tsk;
var sheetsRelation = db.SheetsRelationView.Where(jt => jt.Username == username);
return sheetsRelation;
}
}
UPDATE 1:
It seems it's worked in PostMan and I have a JSON in response But in Angular, i haven't any JSON in response.
Three things you may wish to try -
Not related to this issue, but I always decorate my APIs with the specific http method, to ensure there isnt any confusion on my part - [HttpGet] in this case.
Refactor your API class so that it doesnt have direct dependencies on GSheetsContext and User.Identity (make yourself an injectable service to handle that, so that you can mock the behavior.)
Unit test the controller method with mocked dependencies, so that you can be sure that your controller is behaving as it is expected to.
Edit, if that sounds like too much work
Comment out your existing controller logic and put a stub method there that just returns something like return db.SheetsRelationView.Create()
If that works, then you know your issue is not with your API, and is in the logic. Then refer back to steps 2 & 3 above ;)

Questions about implementing an API on asp.net webapi and HTTP verbs

I'm creating api using ASP.NET Web API
I have a method in repository which adds worker to company.
Here is the method:
public void AddToCompanyBy(Guid workerId, Guid companyId)
{
var worker = GetById(workerId);
var company = DbContext.Set<Company>().Find(companyId);
if (worker == null)
throw new Exception("This worker does not exist");
if (company == null)
throw new Exception("This company does not exist");
company.Workers.Add(worker);
}
And I have an ApiController action that invokes this method.
Here is this action:
public IHttpActionResult AddToCompany(Guid workerId, Guid companyId)
{
try
{
UoW.Workers.AddToCompanyBy(workerId, companyId);
return Ok();
}
catch (Exception ex)
{
return BadRequest();
}
}
So my questions are:
am I right returning response as OK or I have to choose another type for response?
Should I also return entity?
How do I have mark the action (PUT or PATCH or GET)?
am I right returnign response as OK or I have to choose another type for response
It is ok to return OK response. You shouldn't specify return type because of Content Negotiation in Web API.
Should I also return entity?
It depends on your API and what do consumers of your API expect to get as a result. If it is enough just to know that everything was correct you can leave just OK message. If they need some data that was generated by your server, for exmaple incremented ID, you should return this data or even full entity.
How do I have mark the action (PUT or PATCH or GET)?
[HttpPost]
public IHttpActionResult AddToCompany(Guid workerId, Guid companyId)
[HttpGet]
public IHttpActionResult AddToCompany(Guid workerId, Guid companyId)
UPDATED according to comment:
I mean which verb should I choose in this particular case?
Both PUT and POST can be used for creating. For sure you shouldn't use GET verb for creation or update.
General Http verbs specification: w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.3
Post with this discussion: stackoverflow.com/questions/630453/put-vs-post-in-rest

How to return a custom view OR a JSON when the Authorization fail, instead of showing a username and password dialog

I am working on an asp.net mvc 4 web application , and i wrote the following custom authorization class:-
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public class CheckUserPermissionsAttribute : AuthorizeAttribute
{
public string Model { get; set; }
public string Action { get; set; }
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
if (!httpContext.Request.IsAuthenticated)
return false;
//code goes here
if (!repository.can(ADusername, Model, value)) // implement this method based on your tables and logic
{
return false;
//base.HandleUnauthorizedRequest(filterContext);
}
return true;
// base.OnAuthorization(filterContext);
}
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
if (filterContext.HttpContext.Request.IsAjaxRequest())
{
var viewResult = new JsonResult();
viewResult.JsonRequestBehavior = JsonRequestBehavior.AllowGet;
viewResult.Data = (new { IsSuccess = "Unauthorized", description = "Sorry, you do not have the required permission to perform this action." });
filterContext.Result = viewResult;
}
else
{
var viewResult = new ViewResult();
viewResult.ViewName = "~/Views/Errors/_Unauthorized.cshtml";
filterContext.Result = viewResult;
}
base.HandleUnauthorizedRequest(filterContext);
}
}
}
but the only problem i am facing now is that if the authorization fail then the user will be prompted to enter username and password, although i have override the HandleUnauthorizedRequest to return a view or JSON based on if the request is AJAX or not. so can you advice why the user is being prompted to enter his username and password when the authorization fail, instead of receiving the _unauthorized view or the JSON containing an error message
but the only problem i am facing now is that if the authorization fail
then the user will be prompted to enter username and password,
although i have override the HandleUnauthorizedRequest to return a
view or JSON based on if the request is AJAX or not.
That's because you are absolutely always hitting the following line in your HandleUnauthorizedRequest method:
base.HandleUnauthorizedRequest(filterContext);
You know what this line do? It calls the base method. You know what the base method do? It returns 401 status code. You know what happens when 401 status response code is returned in an ASP.NET application in which you are using Forms Authentication? You get the login page.
So yeah, if you are using AJAX or something and intend to be returning some JSON or something make sure that the base stuff is never called. By the way in your else condition you seem to be attempting to render some ~/Views/Errors/_Unauthorized.cshtml view which obviously is useless once again because you are also calling the base method which will simply redirect to the login page.
I think that at this stage of my answer you already know what to do: get rid of this last line of your HandleUnauthorizedRequest method in which you are throwing all your efforts into the trash by calling the base method.
And if you want to do things properly and return 401 status code and not get the login page but instead return some custom JSON you could use the SuppressFormsAuthenticationRedirect property on the Response object. And if you are using some legacy version of the .NET framework which doesn't have this property you might find the following blog post useful in which Phil Haack explains how to handle this case.

Web API: No MediaTypeFormatter is available to read an object of type 'IEnumerable`1' from content with media type 'text/plain'

I get this error in my client (an ASP.NET MVC application) from a call to my ASP.NET Web API. I checked and the Web API is returning the data alright.
No MediaTypeFormatter is available to read an object of type
'IEnumerable`1' from content with media type 'text/plain'.
I believe that I can inherit from DataContractSerializer and implement my own serializer which can attach the Content-Type HTTP header as text/xml.
But my question is: is that necessary?
Because if it was, it would mean that the default DataContractSerializer does not set this essential header. I was wondering if Microsoft could leave such an important thing out. Is there another way out?
Here's the relevant client side code:
public ActionResult Index()
{
HttpClient client = new HttpClient();
var response = client.GetAsync("http://localhost:55333/api/bookreview/index").Result;
if (response.IsSuccessStatusCode)
{
IEnumerable<BookReview> reviews = response.Content.ReadAsAsync<IEnumerable<BookReview>>().Result;
return View(reviews);
}
else
{
ModelState.AddModelError("", string.Format("Reason: {0}", response.ReasonPhrase));
return View();
}
}
And here's the server side (Web API) code:
public class BookReviewController : ApiController
{
[HttpGet]
public IEnumerable<BookReview> Index()
{
try
{
using (var context = new BookReviewEntities())
{
context.ContextOptions.ProxyCreationEnabled = false;
return context.BookReviews.Include("Book.Author");
}
}
catch (Exception ex)
{
var responseMessage = new HttpResponseMessage
{
Content = new StringContent("Couldn't retrieve the list of book reviews."),
ReasonPhrase = ex.Message.Replace('\n', ' ')
};
throw new HttpResponseException(responseMessage);
}
}
}
I believe (because I don't have time to test it now) that you need to explicitly set the Status Code on the responseMessage you are passing to HttpResponseException. Normally, HttpResponseException will set the status code for you, but because you are providing a responsemessage explicitly, it will use the status code from that. By default, `HttpResponseMessage has a status code of 200.
So what is happening is you are getting an error on the server, but still returning a 200. Which is why your client is trying to deserialize the text/plain body produced by StringContent, as if it were an IEnumerable.
You need to set
responseMessage.StatusCode = HttpStatusCode.InternalServerError
in your exception handler on the server.
How about just using ReadAsStringAsync if your WebAPI is expecting to return content in plain text?
response.Content.ReadAsStringAsync().Result;

Resources