I trie to pass my item Reach_DeclarationBc
public class Reach_DeclarationBc
{
public int ArticleId { get; set; }
public DateTime DateDeclaration { get; set; }
}
I use this code for call my api
var client = new HttpClient();
client.BaseAddress = new Uri("http://localhost:3001/");
Reach_DeclarationBc reach_DeclarationBc = new Reach_DeclarationBc
{
ArticleId = 129,
DateDeclaration = DateTime.Now
};
Reach_DeclarationBc result = await client.PostJsonAsync<Reach_DeclarationBc>("http://localhost:3009/reach", reach_DeclarationBc);
But a this line this give me an error
Reach_DeclarationBc result = await client.PostJsonAsync<Reach_DeclarationBc>("http://localhost:3009/reach", reach_DeclarationBc);
The error is : "TypeLoadException: Could not load type 'Microsoft.JSInterop.Json' from assembly 'Microsoft.JSInterop, Version=3.1.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60'."
The using in my class is
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Projet_Airbus.Data;
using Projet_Airbus.Models;
using Projet_Airbus.Models.Declaration;
using Microsoft.AspNetCore.Blazor;
using System.Net.Http;
For solve i tri to use asp.net core 3.1 but this not work
This is how I usually do it.
I inject a singleton HttpClient and logger with a dependency injector like Ninject.
I make a generic SendAsync which can handle PUT/DELETE/POST/GET.
I make ApiResponse class that contains the properties I'm interested in.
I create a Request class (InitRequest) that I use for the request.
I create a Response class (InitResponse) that I use for the response.
I have TimeOutInMs to set the time out for the api call.
I have a logger that logs error.
using System;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Web.Mvc;
using Newtonsoft.Json;
public class HomeController : Controller
{
private readonly WebApi _webApi;
//Inject singleton httpclient and logger
public HomeController(HttpClient httpClient, ILogger logger)
{
_webApi = new WebApi(httpClient, logger);
_webApi.BaseAddress = "https://www.webapi.com/";
_webApi.TimeOutInMs = 2000;
}
public async Task<ActionResult> Index()
{
//You might want to move the web api call to a service class
var method = "init";
var request = new InitRequest
{
Id = 1,
Name = "Bob"
};
var response = await _webApi.SendAsync<InitResponse>(method, request, HttpMethod.Post);
if (response.StatusCode == HttpStatusCode.OK)
{
var viewModel = response.Data.ToIndexViewModel();
}
else
{
//Handle Error
}
return View(viewModel);
}
}
public class IndexViewModel
{
public string Name { get; set; }
public string Address { get; set; }
}
public static class ModelMapper
{
public static IndexViewModel ToIndexViewModel(this InitResponse response)
{
return new IndexViewModel
{
Name = response.Name,
Address = response.Address
};
}
}
public class InitRequest
{
public int Id { get; set; }
public string Name { get; set; }
}
public class InitResponse
{
public string Name { get; set; }
public string Address { get; set; }
}
public class WebApi
{
private readonly HttpClient _httpClient;
private readonly ILogger _logger;
public Uri BaseAddress { get; set; }
public int TimeOutInMs { get; set; }
public WebApi(HttpClient httpClient, ILogger logger)
{
_logger = logger ?? throw new Exception($"Missing constructor ceference - {nameof(logger)} can not be null!");
_httpClient = httpClient ?? throw new Exception($"Missing constructor ceference - {nameof(httpClient)} can not be null!");
}
public async Task<ApiResponse<TOut>> SendAsync<TOut>(string method, object param, HttpMethod httpMethod)
{
if (string.IsNullOrWhiteSpace(BaseAddress.ToString()))
throw new Exception($"{nameof(BaseAddress)} can not be null or empty.");
if (string.IsNullOrWhiteSpace(method))
throw new Exception($"{nameof(method)} can not be null or empty.");
var paramListForLog = JsonConvert.SerializeObject(param);
//Set timeout
if (TimeOutInMs <= 0)
{
TimeOutInMs = (int)TimeSpan.FromSeconds(100.0).TotalMilliseconds;
}
var cts = new CancellationTokenSource();
cts.CancelAfter(TimeSpan.FromMilliseconds(TimeOutInMs));
var cancellationToken = cts.Token;
var url = new Uri($"{BaseAddress}{method}", UriKind.Absolute);
try
{
HttpResponseMessage response;
using (var request = new HttpRequestMessage(httpMethod, url))
{
//Add content
if (param != null)
{
var content = JsonConvert.SerializeObject(param);
request.Content = new StringContent(content, Encoding.UTF8, "application/json");
}
//Add headers
request.Headers.Accept.Clear();
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
_logger.Info($"Calling {httpMethod} for {url}", paramListForLog);
//Send the request
response = await _httpClient.SendAsync(request, cancellationToken);
}
//If success
if (response.IsSuccessStatusCode)
{
_logger.Info($"Successfully called {httpMethod} for {url}", paramListForLog);
var data = await response.Content.ReadAsAsync<TOut>(cancellationToken);
return new ApiResponse<TOut>
{
StatusCode = response.StatusCode,
Data = data
};
}
//If failure
var error = await response.Content.ReadAsStringAsync();
_logger.Error($"An error occured calling {httpMethod} for {url}. Error was {error}", paramListForLog);
return new ApiResponse<TOut>
{
StatusCode = response.StatusCode,
Message = error
};
}
//If timeout
catch (OperationCanceledException ex)
{
var message = cancellationToken.IsCancellationRequested ?
$"Request timed out after {TimeOutInMs} ms occured calling {httpMethod} for {url}. Error was: {ex.Message}" :
$"An error occured calling {httpMethod} for {url}. Error was: {ex.Message}";
var webEx = new Exception(message, ex);
_logger.Error(webEx, webEx.Message, paramListForLog);
return new ApiResponse<TOut>
{
StatusCode = HttpStatusCode.RequestTimeout,
Message = message
};
}
//If unknown error
catch (Exception ex)
{
var webEx = new Exception($"An error occured calling {httpMethod} for {url}. Error was: {ex.Message}", ex);
_logger.Error(webEx, webEx.Message, paramListForLog);
throw webEx;
}
}
}
public interface ILogger
{
void Info(string message, string param);
void Error(string message, string param);
void Error(Exception e, string message, string param);
}
public class ApiResponse<T>
{
public HttpStatusCode StatusCode { get; set; }
public string Message { get; set; }
public T Data { get; set; }
}
Related
In my ASP.NET Core-6 Web API, I am given a third party API to consume and then return the account details. I am using HttpClient.
api:
https://api.thirdpartycompany.com:2233/UserAccount/api/AccountDetail?accountNumber=
In appsettings.json I have:
"Endpoints": {
"customerUrl": "https://api.thirdpartycompany.com:2233/UserAccount/api/AccountDetail?accountNumber="
}
DTO:
public class GetCustomerDetailDto
{
public class CustomerDetail
{
public string AccountNumber { get; set; }
public string Fullname { get; set; }
public string EmailAddress { get; set; }
}
}
Then I have this Data Util:
public class DataUtil : IDataUtil
{
private readonly IConfiguration _config;
private readonly ILogger<DataUtil> _logger;
private readonly HttpClient _myClient;
public DataUtil
(
IConfiguration config,
ILogger<DataUtil> logger,
HttpClient myClient
)
{
_config = config;
_logger = logger;
_myClient = myClient;
}
public CustomerDetail GetCustomerDetail(string accountNumber)
{
var responseResults = new CustomerDetail();
try
{
string custAccountNoUrl = _config.GetSection("Endpoints").GetValue<string>("customerUrl") + accountNumber;
_myClient.DefaultRequestHeaders.Accept.Clear();
_myClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
HttpResponseMessage response = _myClient.GetAsync(custAccountNoUrl).Result;
if (response.IsSuccessStatusCode)
{
var stringResult = response.Content.ReadAsStringAsync().Result;
responseResults = JsonConvert.DeserializeObject<CustomerDetail>(stringResult);
}
}
catch (Exception ex)
{
_logger.LogError($"An Error occured " + ex.ToString());
}
return responseResults;
}
}
Then this is the implementation:
public async Task<Response<string>> CreateCustomerDetailAsync(CreateDto requestDto)
{
var response = new Response<string>();
var accDetail = _dataAccess.GetCustomerDetail(requestDto.DrAccountNumber);
if (accDetail.EmailAddress != null)
{
var accountName = accDetail.Fullname;
var emailAddress = accDetail.EmailAddress.ToLower();
var mailBody = await EmailBodyBuilder.GetCustomerEmailBody(accountName, emailTempPath: "wwwroot/files/Html/CustomerEmail.html");
var mailRequest = new MailRequest()
{
Subject = "Customer Notification",
Body = mailBody,
ToEmail = emailAddress
};
bool emailResult = await _mailService.SendEmailAsync(mailRequest);
if (emailResult)
{
response.StatusCode = (int)HttpStatusCode.OK;
response.Successful = true;
response.Message = "Successful!";
return response;
}
}
}
From the third-party API given, there are moments when the customer does not have email address, then the EmailAddress field will not even appear at all from the response.
So that made me to get error here:
bool emailResult = await _mailService.SendEmailAsync(mailRequest);
I tried
if (accDetail.EmailAddress != null)
but it's not solving the problem.
Making use of
public async Task<Response<string>> CreateCustomerDetailAsync(CreateDto requestDto)
How do I make the application not to send email at all whenever EmailAddress field does not exist or it's null?
I am trying to capture the exception when the route is not found and wrap the exception with the generic response model.
I tried to implement, as given in the answer to the question, but this solution also doesn't seem to work in my use case.
Because the status code 404 is also added to the response when the resource is not found, like when Id is not found.
app.UseStatusCodePages(new StatusCodePagesOptions()
{
HandleAsync = (ctx) =>
{
if (ctx.HttpContext.Response.StatusCode == 404)
{
throw new RouteNotFoundException("Route not found");
}
return Task.FromResult(0);
}
})
RouteNotFoundException
public class RouteNotFoundException : Exception
{
public RouteNotFoundException()
: base()
{
}
public RouteNotFoundException(string message)
: base(message)
{
}
}
ApiExceptionFilterAttribute
public class ApiExceptionFilterAttribute : ExceptionFilterAttribute
{
private readonly IDictionary<Type, Action<ExceptionContext>> _exceptionHandlers;
public ApiExceptionFilterAttribute()
{
// Register known exception types and handlers.
_exceptionHandlers = new Dictionary<Type, Action<ExceptionContext>>
{
{ typeof(RouteNotFoundException), HandleNotFoundException }
};
}
public override void OnException(ExceptionContext context)
{
HandleException(context);
base.OnException(context);
}
private void HandleException(ExceptionContext context)
{
Type type = context.Exception.GetType();
if (_exceptionHandlers.ContainsKey(type))
{
_exceptionHandlers[type].Invoke(context);
return;
}
HandleUnknownException(context);
}
private void HandleNotFoundException(ExceptionContext context)
{
var exception = context.Exception as RouteNotFoundException;
var details = new ProblemDetails()
{
Type = "https://tools.ietf.org/html/rfc7231#section-6.5.4",
Title = "The specified resource was not found.",
Detail = exception.Message
};
context.Result = new NotFoundObjectResult(details);
context.ExceptionHandled = true;
}
private void HandleUnknownException(ExceptionContext context)
{
var details = new ProblemDetails
{
Status = StatusCodes.Status500InternalServerError,
Title = "An error occurred while processing your request.",
Type = "https://tools.ietf.org/html/rfc7231#section-6.6.1"
};
context.Result = new ObjectResult(details)
{
StatusCode = StatusCodes.Status500InternalServerError
};
context.ExceptionHandled = true;
}
}
ResponseWrapperMiddleware
public class ResponseWrapperMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger<ResponseWrapperMiddleware> _logger;
public ResponseWrapperMiddleware(RequestDelegate next, ILoggerFactory loggerFactory)
{
_next = next ?? throw new ArgumentNullException(nameof(next));
_logger = loggerFactory?.CreateLogger<ResponseWrapperMiddleware>() ?? throw new ArgumentNullException(nameof(loggerFactory));
}
public async Task Invoke(HttpContext httpContext)
{
try
{
var currentBody = httpContext.Response.Body;
using (var memoryStream = new MemoryStream())
{
//set the current response to the memorystream.
httpContext.Response.Body = memoryStream;
await _next(httpContext);
//reset the body
httpContext.Response.Body = currentBody;
memoryStream.Seek(0, SeekOrigin.Begin);
var readToEnd = new StreamReader(memoryStream).ReadToEnd();
var objResult = JsonConvert.DeserializeObject(readToEnd);
var result = CommonApiResponse.Create((HttpStatusCode)httpContext.Response.StatusCode, objResult, null);
await httpContext.Response.WriteAsync(JsonConvert.SerializeObject(result));
}
}
catch (Exception ex)
{
if (httpContext.Response.HasStarted)
{
_logger.LogWarning("The response has already started, the http status code middleware will not be executed.");
throw;
}
return;
}
}
}
// Extension method used to add the middleware to the HTTP request pipeline.
public static class ResponseWrapperMiddlewareExtensions
{
public static IApplicationBuilder UseResponseWrapperMiddleware(this IApplicationBuilder builder)
{
return builder.UseMiddleware<ResponseWrapperMiddleware>();
}
}
Generic Response Model
public class CommonApiResponse
{
public static CommonApiResponse Create(HttpStatusCode statusCode, object result = null, string errorMessage = null)
{
return new CommonApiResponse(statusCode, result, errorMessage);
}
public string Version => "1.2.3";
public int StatusCode { get; set; }
public string RequestId { get; }
public string ErrorMessage { get; set; }
public object Result { get; set; }
protected CommonApiResponse(HttpStatusCode statusCode, object result = null, string errorMessage = null)
{
RequestId = Guid.NewGuid().ToString();
StatusCode = (int)statusCode;
Result = result;
ErrorMessage = errorMessage;
}
}
How to handle the error if the route is not found and capture the error in the generic model? What is the workaround for this case?
I have been following some tutorials (e.g., https://learn.microsoft.com/en-us/aspnet/web-api/overview/advanced/calling-a-web-api-from-a-net-client) and trying to retrieve a list of jobs and print them out in my .Net console app.
The test data I am using is located at https://boards-api.greenhouse.io/v1/boards/vaulttec/jobs and supplied it to client.BaseAddress.
Since I am able to compile and run the tutorial succesfully, I simply used the same code and changed some of it to run the above test data (see below).
using System;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;
namespace GreenhouseJobs
{
public class Job
{
public string Id { get; set; }
public string Title { get; set; }
public string Location { get; set; }
public DateTime LastUpdated { get; set; }
}
class GreenhouseJobsClient
{
static HttpClient client = new HttpClient();
static void ShowJob(Job job)
{
Console.WriteLine($"Id: {job.Id}\tTitle: " +
$"{job.Title}\tLocation: {job.Location}\tLast Updated: {job.LastUpdated}");
}
static async Task<Uri> CreateJobAsync(Job job)
{
HttpResponseMessage response = await client.PostAsJsonAsync(
"vaulttec/jobs", job);
response.EnsureSuccessStatusCode();
// return URI of the created resource.
return response.Headers.Location;
}
static async Task<Job> GetJobAsync(string path)
{
Job job = null;
HttpResponseMessage response = await client.GetAsync(path);
if (response.IsSuccessStatusCode)
{
job = await response.Content.ReadAsAsync<Job>();
}
return job;
}
//static async Task<Product> UpdateProductAsync(Product product)
//{
// HttpResponseMessage response = await client.PutAsJsonAsync(
// $"api/products/{product.Id}", product);
// response.EnsureSuccessStatusCode();
// // Deserialize the updated product from the response body.
// product = await response.Content.ReadAsAsync<Product>();
// return product;
//}
//static async Task<HttpStatusCode> DeleteProductAsync(string id)
//{
// HttpResponseMessage response = await client.DeleteAsync(
// $"api/products/{id}");
// return response.StatusCode;
//}
static void Main()
{
RunAsync().GetAwaiter().GetResult();
Console.ReadLine();
}
static async Task RunAsync()
{
// Update port # in the following line.
client.BaseAddress = new Uri("https://boards-api.greenhouse.io/v1/boards/");
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(
new MediaTypeWithQualityHeaderValue("application/json"));
try
{
// Create a new product
Job job = new Job
{
Id = "323232",
Title = "Test",
Location = "Test",
LastUpdated = DateTime.Now
};
var url = await CreateJobAsync(job);
Console.WriteLine($"Created at {url}");
// Get the product
job = await GetJobAsync(url.PathAndQuery);
ShowJob(job);
// Update the product
//Console.WriteLine("Updating price...");
//product.Price = 80;
//await UpdateProductAsync(product);
// Get the updated product
//product = await GetProductAsync(url.PathAndQuery);
//ShowProduct(product);
// Delete the product
//var statusCode = await DeleteProductAsync(product.Id);
//Console.WriteLine($"Deleted (HTTP Status = {(int)statusCode})");
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
Console.ReadLine();
}
}
}
The problem is, when I run the app, it does not return anything. Error message: "Response status code does not indicate success: 404 (Not Found)".
There are couple issues here
You are not just trying to retrieve but also create first. I am not sure if this api supports a POST or not, it probably doesn't, hence a 404
The Job entity is incorrect. Location needs to be an object itself and not a mere string if you need it deserialized correctly. Here is a working version of get call
https://pastebin.com/85NnAQY9
namespace ConsoleApp3
{
public class JobsJson
{
public List<Job> Jobs { get; set; }
}
public class Job
{
public string Id { get; set; }
public string Title { get; set; }
public Location Location { get; set; }
public DateTime LastUpdated { get; set; }
}
public class Location
{
public string Name { get; set; }
}
class GreenhouseJobsClient
{
static HttpClient client = new HttpClient();
static void ShowJobs(List<Job> jobs)
{
foreach (var job in jobs)
{
Console.WriteLine($"Id: {job.Id}\tTitle: " +
$"{job.Title}\tLocation: {job.Location}\tLast Updated: {job.LastUpdated}");
}
}
static async Task<List<Job>> GetJobAsync(string path)
{
var jobs = new List<Job>();
HttpResponseMessage response = await client.GetAsync(path);
if (response.IsSuccessStatusCode)
{
var stringResponse = await response.Content.ReadAsStringAsync();
var re = JsonConvert.DeserializeObject<JobsJson>(stringResponse);
jobs = re.Jobs;
}
return jobs;
}
static void Main()
{
RunAsync().GetAwaiter().GetResult();
Console.ReadLine();
}
static async Task RunAsync()
{
// Update port # in the following line.
client.BaseAddress = new Uri("https://boards-api.greenhouse.io/v1/boards/");
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
try
{
// Get the product
var jobs = await GetJobAsync("vaulttec/jobs");
ShowJobs(jobs);
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
Console.ReadLine();
}
}
}
The 404 is being returned by this line of code:
HttpResponseMessage response = await client.PostAsJsonAsync("vaulttec/jobs", job);
This is because the URL https://boards-api.greenhouse.io/v1/boards/vaulttec/jobs returns 404 when you try and make a POST request. Probably you need to be authorised to create jobs.
You can however make a GET request to this URL just fine.
I currently only have the External API credentials as a result access token in Postman but cannot implement the URL and credentials into c# code using ASP .NET Web API which comes with sample code in the controller but currently have nothing inside because I don't know how to redirect the external url:https://api.elliemae.com/oauth2/v1/token
towards my localhost URL
postman result
Currently i only have created my model class assuming I will need it.
Class Name: Credentials.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
namespace APICredential.Models
{
public class Credentials
{
public string grant_type { get; set; } = "password";
public string username { get; set; } = "admin#encompass:BE11200822";
public string password { get; set; } = "Sh**********";
public string client_id {get;set;} = "gpq4sdh";
public string client_secret { get; set; } = "dcZ42Ps0lyU0XRgpDyg0yXxxXVm9#A5Z4ICK3NUN&DgzR7G2tCOW6VC#HVoZPBwU";
public string scope { get; set; } = "lp";
}
}
Controller:
using APICredential.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using System.Web.Http;
using System.Web.Script.Serialization;
namespace APICredential.Controllers
{
[RoutePrefix("api")]
public class ValuesController : ApiController
{
[HttpPost, Route("post")]
public async Task<string> Post([FromBody]Credentials cred)
{
using (HttpClient client = new HttpClient())
{
client.BaseAddress = new Uri("https://api.elliemae.com/oauth2/");
HttpRequestMessage request = new HttpRequestMessage
(HttpMethod.Post, "v1/token")
{
Content = new StringContent(new JavaScriptSerializer().Serialize(cred), Encoding.UTF8, "application/json")
};
HttpResponseMessage response = await client.SendAsync(request);
//for now, see what response gets you and adjust your code to return the object you need, if the api is returning a serialized json string.. then we can return a string here... like so
string result = await response.Content.ReadAsStringAsync();
return result;
}
}
// GET api/values/5
public string Get(int id)
{
return "value";
}
you're going to need to create a dto that has similar properties to the stuff you're sending to this api end point. and also a model to map the data that's being returned
public class Credentials
{
public string grant_type { get; set; }
public string username { get; set; }
public string password { get; set; }
public string client_id {get;set;}
public string client_secret { get; set; }
public string scope { get; set; }
}
then you're going to instantiate it and start assigning the values before sending it off
this is going to go in your api controller method
[RoutePrefix("api")]
public class ValuesController : ApiController
{
// POST api/values
[HttpPost, Route("values")]
public async Task<string> Post()
{
Credentials cred = new Credentials()
{
grant_type = "password",
username = "admin#encompass:BE11200822",
password = "Shmmarch18",
client_id = "gpq4sdh",
client_secret = "dcZ42Ps0lyU0XRgpDyg0yXxxXVm9#A5Z4ICK3NUN&DgzR7G2tCOW6VC#HVoZPBwU",
scope = "lp"
};
try
{
using (HttpClient client = new HttpClient())
{
client.BaseAddress = new Uri("https://api.elliemae.com/oauth2/");
HttpRequestMessage request = new HttpRequestMessage
(HttpMethod.Post, "v1/token")
{
Content = new StringContent(new JavaScriptSerializer().Serialize(cred), Encoding.UTF8, "application/x-www-form-urlencoded")
};
HttpResponseMessage response = await client.SendAsync(request);
//for now, see what response gets you and adjust your code to return the object you need, if the api is returning a serialized json string.. then we can return a string here... like so
string result = await response.Content.ReadAsStringAsync();
return result;
}
}
catch(Exception ex)
{
throw new Exception(ex.Message);
}
}
}
There's a chance you might have to serialize the object that's returning from the API call, if that's the case:
[RoutePrefix("api")]
public class ValuesController : ApiController
{
// POST api/values
[HttpPost, Route("values")]
public string Post([FromBody]Credentials cred)
{
using (HttpClient client = new HttpClient())
{
client.BaseAddress = new Uri("https://api.elliemae.com/oauth2/");
HttpRequestMessage request = new HttpRequestMessage
(HttpMethod.Post, "v1/token")
{
Content = new StringContent(new JavaScriptSerializer().Serialize(cred), Encoding.UTF8, "application/json")
};
HttpResponseMessage response = await client.SendAsync(request);
string result = new JavaScriptSerializer().Serialize(response.Content);
return result;
}
}
}
Nice work! Ok, let's test your API, let's pump your Get Method up and make it an easy one to work with to increase moral hehe
// GET api/values
[HttpGet, Route("values")]
public string Get(int id)
{
return "Nice! At least we got this one working, Fay... the Id value you entered is: " + id.ToString();
}
I've searched this intensive but can't get it to work.
I've haven an Web AP2 OData API and need to return a custom error class.
Here is what I have:
public class Error
{
public string Code { get; set; }
public string Message { get; set; }
}
In Global.asax.cs
protected void Application_Start()
{
GlobalConfiguration.Configure(WebApiConfig.Register);
GlobalConfiguration.Configuration.MessageHandlers.Add(new MessageInterceptor());
}
public class MessageInterceptor : DelegatingHandler
{
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
return base.SendAsync(request, cancellationToken).ContinueWith(
task =>
{
var body = task.Result.Content.ReadAsStringAsync().Result;
var resultObj = JsonConvert.DeserializeObject(body.Replace("value", "Results"));
task.Result.Content = new ObjectContent(typeof(object), resultObj, new JsonMediaTypeFormatter());
return task.Result;
}, cancellationToken);
}
}
In WebApiConfig,cs
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.Filters.Add(new HandleApiExceptionAttribute());
}
}
public class HandleApiExceptionAttribute : ExceptionFilterAttribute, IExceptionFilter
{
public override void OnException(HttpActionExecutedContext context)
{
if (context.Exception is Error)
{
var res = context.Exception.Message;
//Define the Response Message
HttpResponseMessage response = new HttpResponseMessage(HttpStatusCode.InternalServerError)
{
Content = new StringContent(res),
ReasonPhrase = res
};
//Create the Error Response
context.Response = response;
}
}
public class OrdersController : ODataControllerA controller
{
private Context db = new Context();
// GET: odata/Orders
[Queryable]
public IQueryable<Orders> GetOrders(ODataQueryOptions<Orders> opts)
{
<some code producing error>
Error theError = new Error()
{
Code = "1000",
Message = "Geen filter gespecificeerd"
};
HttpResponseMessage response = Request.CreateResponse(HttpStatusCode.BadRequest, theError);
//return Request.CreateResponse(HttpStatusCode.BadRequest, theError);
throw new HttpResponseException(response);
}
When I try this this crashes in the MessageInterceptor.
This is there because a third party consuming the API want's it in the specific format.
When the code runs correct it returns results{}
On error it should return Error{code:, message: }
Anyone ideas?
Jeroen
This is what I came up with:
I changed the controller to:
[Queryable, BasicAuthentication]
public IHttpActionResult GetOrders(ODataQueryOptions<Orders> opts)
{
else
{
Error theError = new Error()
{
Code = "1000",
Message = "Geen filter gespecificeerd"
};
return new ErrorResult(theError, Request);
}
return Ok(resultList);
}
Using this class:
public class ErrorResult : IHttpActionResult
{
Error _error;
HttpRequestMessage _request;
public ErrorResult(Error error, HttpRequestMessage request)
{
_error = error;
_request = request;
}
public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
{
List<Error> _errorList = new List<Error>();
_errorList.Add(_error);
error err = new error()
{
errors = _errorList
};
var response = new HttpResponseMessage(HttpStatusCode.BadRequest)
{
Content = new ObjectContent<error>(err, new JsonMediaTypeFormatter()),
RequestMessage = _request
};
return Task.FromResult(response);
}
}
public class Error
{
public string Code { get; set; }
public string Message { get; set; }
}
public class error
{
public List<Error> errors { get; set; }
}