I have a JavaScript client which makes Ajax call to a .net service (Lets call it First service). First service then makes call to another .net Controller (Call it Second Service). In this controller, I am throwing some exception. On the first line I am saying:
//Code from Second Service
[HttpPost]
public HttpResponseMessage Results(ParamsModel data)
{
throw new Exception("Exception for testing purpose");
}
//Code from First Service
[HttpPost]
public ActionResult Results(ParamsModel data)
{
var client = new HttpClient();
var task = client.PostAsJsonAsync(urlTemplate, data);
var result = task.Result.Content.ReadAsStringAsync().Result;
return Content(result, "application/json");
}
Problem: Though the Second Service is throwing error & returning 500 status code, The first servcie returns 200 status code to the JavaScript client. I am also not able to read the satus code returned by Second service as I only get string output.
Please suggest. I want to return 500 status code when there is an error.
You can implement error handling as follows in the HttpClient.
if (!task.Result.IsSuccessStatusCode)
{
if (task.Result.StatusCode == HttpStatusCode.InternalServerError)
{
return Request.CreateErrorResponse(HttpStatusCode.InternalServerError, "An error has occured.");
}
else
{
// Check for other status codes and handle the responses
}
}
else
{
// Success status code. Return success response.
}
Hope this helps.
Why can't you do an asynchronous action method?
[HttpPost]
public async Task<ActionResult> Results(ParamsModel data)
{
try
{
var client = new HttpClient();
var response = await client.PostAsJsonAsync(urlTemplate, data);
var json = await response.Content.ReadAsStringAsync()
return Content(result, "application/json");
}
catch(WebException ex)
{
//do note that the Response property might be null due to
// connection issues etc. You have to handle that by yourself.
var remoteErrorCode = ((HttpWebResponse)ex.Response).StatusCode;
Request.CreateErrorResponse(remoteErrorCode, "An error just happened");
}
}
But the thing is, with the layout of the first method it doesn't matter how you handle exceptions in the second one, as the first will always return "Internal Server Error".
To make it useful, you should typically return different error codes in the first method too.
You can return a HttpResponseException like this:
[HttpPost]
public ActionResult Results(ParamsModel data)
{
try
{
var client = new HttpClient();
var task = client.PostAsJsonAsync(urlTemplate, data);
var result = task.Result.Content.ReadAsStringAsync().Result;
return Content(result, "application/json");
}
catch (HttpResponseException ex)
{
return new HttpStatusCodeResult(ex.Response.StatusCode);
}
}
You will need to throw the right exception from your WebAPI controller:
[HttpPost]
public HttpResponseMessage Results(ParamsModel data)
{
throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.NotFound));
}
There are several status codes that can be thrown:
https://msdn.microsoft.com/en-us/library/system.net.httpstatuscode.aspx
Related
I have a lot of controllers in an application.
Controllers called from UI with JS/ajax.
Almost all of them use similar "template". Please see the code sample.
Check income model for null.
Check model is valid.
Try catch with action.
Difference only in error messages, names and which injected helper used.
Is it possible to reduce repeat code?
Some kind of DRY?
How to achieve this? Template method, delegates, use MVC specific things?
[HttpPost]
public async Task<IActionResult> TestMethod([FromBody] TestModel model)
{
if (null == model)
{
return StatusCode((int)HttpStatusCode.BadRequest, "Empty model provided");
}
if (false == ModelState.IsValid)
{
var message = "Not all parameters provided correctly.";
_logger.WriteWithCallerAndMethodName(LogLevel.Debug, nameof(TestController), nameof(TestMethod), message);
return StatusCode((int)HttpStatusCode.BadRequest, message);
}
try
{
var result = await _testHelper.CreateDataAsync(model);
return Ok(result);
// return PartialView("_TestPart", result);
}
catch (Exception ex)
{
var message = $"Can't retrieve data. Error: ({ex.Message})";
_logger.WriteWithCallerAndMethodName(ex, nameof(TestController), nameof(TestMethod), message);
return StatusCode((int)HttpStatusCode.InternalServerError, message);
}
}
I have the following in the TreeController controller in a small web API:
[HttpGet("GetDirectories")]
public IActionResult GetDirectories()
{
var baseDir = _config["QuickShare:BaseDir"];
if (string.IsNullOrWhiteSpace(baseDir))
{
throw new InvalidOperationException("'QuickShare:BaseDir' is not configured");
}
var ret = GetDirectories(baseDir); ;
return Json(ret);
}
private List<DirectoryInfo> GetDirectories(string parentDir)
{
var dirInfo = new DirectoryInfo(parentDir);
return dirInfo.GetDirectories("*", SearchOption.TopDirectoryOnly).ToList();
}
When I try and call this action from Postman, I get told
Could not get any response There was an error connecting to
http://localhost:59243/api/Tree/GetDirectories.
Now the default, test, controller that comes with the project template is unchanged:
[Route("api/[controller]")]
public class ValuesController : Controller
{
// GET api/values
[HttpGet]
public IEnumerable<string> Get()
{
return new string[] {"value1", "value2"};
}
...
}
And when I have the API running in Visual Studio, I can get a proper response from the Values controller, but not from the TreeController, yet they are almost exactly the same. And, when I call the Tree/GetDirectories` action, a breakpoint in that action method is hit, and I can single step through the very few lines that my method has, and they all execute fine.
The problem only becomes apparent when that last line of the action executes:
return Json(ret);
Then I get shown that Postman Could not get any response despite no exception being raised; while debugging the code, it looks like everything should work fine, and the requests to the Values controller work fine.
Your action method should return either the specific result type (JsonResult when you return Json ) or IActionResult.
Example:
[HttpGet("GetDirectories")]
public JsonResult GetDirectories()
{
var baseDir = _config["QuickShare:BaseDir"];
if (string.IsNullOrWhiteSpace(baseDir))
{
throw new InvalidOperationException("'QuickShare:BaseDir' is not configured");
}
var ret = GetDirectories(baseDir);
return Json(ret);
}
OR
[HttpGet("GetDirectories")]
public IActionResult GetDirectories()
{
var baseDir = _config["QuickShare:BaseDir"];
if (string.IsNullOrWhiteSpace(baseDir))
{
throw new InvalidOperationException("'QuickShare:BaseDir' is not configured");
}
var ret = GetDirectories(baseDir) ;
return Ok(ret);
}
You can get more help from Microsoft Documentation: Formatting Response Data
I have this MVC WebApi action:
PostTrips(List<Trip> trips)
When a list of trips is sent through everything works fine. If, however, someone is trying to post incorrect data, e.g just an object {} then trips is null - this is fine, but I would like to log the data that the user tried to push.
I tried to get it using string requestData = Request.Content.ReadAsStringAsync().Result; but it can only be called once, and I guess the default model binder is calling it to try an map it to my List<Trip>, as when I call it, the result is always null, even though I know I'm passing something in.
Does anyone know of another way to get the posted data again?
I got around this my removing the parameter List<Trip> trips from the action so I had:
public async Task<HttpResponseMessage> PostTrips()
{
}
This bypasses the default model binder and allows you to get the unmodified request content using:
string requestContent = await Request.Content.ReadAsStringAsync();
You can then do what ever you need with this - I wanted to log the data for error tracking.
To create the actual List<Trip> trips I then used Newtonsoft.Json to deserialise the string into a list:
List<TravelTrackerTrip> appTrips = JsonConvert.DeserializeObject<List<TravelTrackerTrip>>(requestContent);
Full example:
public async Task<HttpResponseMessage> PostTrips()
{
HttpResponseMessage httpResponseMessage = new HttpResponseMessage();
List<Trip> appTrips = null;
string requestContent = await Request.Content.ReadAsStringAsync();
try
{
appTrips = JsonConvert.DeserializeObject<List<Trip>>(requestContent);
}
catch(Exception ex)
{
//ERROR LOGGING HERE...
//QUIT - Return failure response
}
try
{
//Success - do whatever we need
}
catch(Exception ex)
{
//ERROR LOGGING HERE...
//QUIT - Return failure response
}
//Return success response
}
I have the following POST edit action method, which mainly perform two Update actions:-
Edit the object on the external system suing API calls.
Edit the object on our system database.
[HttpPost]
public ActionResult Create(RackJoin rj, FormCollection formValues)
{string controllername = RouteData.Values["controller"].ToString();
if (ModelState.IsValid)
{ var message = "";
var status = "";
long assetid = new long();
XmlDocument doc = new XmlDocument();
using (var client = new WebClient())
{
var query = HttpUtility.ParseQueryString(string.Empty);
foreach (string key in formValues)
{
query[key] = this.Request.Form[key];
}
query["username"] = System.Web.Configuration.WebConfigurationManager.AppSettings["ApiUserName"];
query["password"] = System.Web.Configuration.WebConfigurationManager.AppSettings["ApiPassword"];
string apiurl = System.Web.Configuration.WebConfigurationManager.AppSettings["ApiURL"];
var url = new UriBuilder(apiurl);
url.Query = query.ToString();
try
{
string xml = client.DownloadString(url.ToString());
doc.LoadXml(xml);
status = doc.SelectSingleNode("/operation/operationstatus").InnerText;
message = doc.SelectSingleNode("/operation/message").InnerText;
}
catch (WebException ex)
{
ModelState.AddModelError(string.Empty, "Error occurred:" + ex.InnerException);
}
}
if (status.ToUpper() == "SUCCESS")
{
repository.InsertOrUpdateRack(rj.Rack, User.Identity.Name, rj.Resource.RESOURCEID);
repository.Save();
return RedirectToAction("Index");
}
else
{
ModelState.AddModelError(string.Empty, message.ToString());
}
}
}
catch (DbUpdateConcurrencyException ex)
{
As shown in the above code I will not do a repository.save() to update the object on our system, unless the API return a “success”.
But currently I am facing the following problem:-
If the API return a “success” but a concurrency exception occurred, then the API will update the object on the external system, but the object will not be updated on our system?
So is there a way to handle this situation?
There's no easy way to solve this situation. One way to handle it would be to ask the designers of the external API expose a method allowing to commit the transaction done in a previous call. Basically your first call will make modifications to the external system but with some boolean flag indicating that those changes are still pending. Then you update your system and in case of success you would call the external API to flag the data from pending to valid.
If you have no control over the external API and it makes the changes to the data from the first call irreversible, then I am afraid that you do not have much choices left. You might remember the state of the object you are modifying on the external system before calling the API and in case of an exception on your system, revert back to the previous state by calling the API with the previous values.
I understand that WEB API uses content negotiation for Accept - Content-Type to return json or xml.
This is not good enough and I need to be able pragmatically decide if I want to return json or xml.
The internet is flooded with obsolete examples of using HttpResponseMessage<T>, which is no longer present in MVC 4.
tokenResponse response = new tokenResponse();
response.something = "gfhgfh";
if(json)
{
return Request.CreateResponse(HttpStatusCode.OK, response, "application/json");
}
else
{
return Request.CreateResponse(HttpStatusCode.OK, response, "application/xml");
}
How do I change the above code so that it works?
Try like this:
public HttpResponseMessage Get()
{
tokenResponse response = new tokenResponse();
response.something = "gfhgfh";
if(json)
{
return Request.CreateResponse(HttpStatusCode.OK, response, Configuration.Formatters.JsonFormatter);
}
else
{
return Request.CreateResponse(HttpStatusCode.OK, response, Configuration.Formatters.XmlFormatter);
}
}
or even better, to avoid cluttering your controller with such plumbing infrastructure code you could also write a custom media formatter and perform this test inside it.