System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification calling Autorest generated code - autorest

I have a unit test, calling a service that makes use of Autorest generated code to call my Api.
I want my unit test to display the error that my Api is throwing, but there seems to be an error in the service's error handling.
I am using the following command to generate code to consume my api.
autorest --input-file=https://mywebsite.com.au:4433/myapi/api-docs/v1/swagger.json --output-folder=generated --csharp --namespace=MyConnector
The generated "client code" contains
/// <param name='request'>
/// </param>
/// <param name='customHeaders'>
/// Headers that will be added to request.
/// </param>
/// <param name='cancellationToken'>
/// The cancellation token.
/// </param>
/// <exception cref="HttpOperationException">
/// Thrown when the operation returned an invalid status code
/// </exception>
/// <exception cref="SerializationException">
/// Thrown when unable to deserialize the response
/// </exception>
/// <return>
/// A response object containing the response body and response headers.
/// </return>
public async Task<HttpOperationResponse<GetAvailableCarriersResponse>> GetAvailableCarriersByJobHeaderIdWithHttpMessagesAsync(GetAvailableCarriersRequest request = default(GetAvailableCarriersRequest), Dictionary<string, List<string>> customHeaders = null, CancellationToken cancellationToken = default(CancellationToken))
{
// Tracing
bool _shouldTrace = ServiceClientTracing.IsEnabled;
string _invocationId = null;
if (_shouldTrace)
{
_invocationId = ServiceClientTracing.NextInvocationId.ToString();
Dictionary<string, object> tracingParameters = new Dictionary<string, object>();
tracingParameters.Add("request", request);
tracingParameters.Add("cancellationToken", cancellationToken);
ServiceClientTracing.Enter(_invocationId, this, "GetAvailableCarriersByJobHeaderId", tracingParameters);
}
// Construct URL
var _baseUrl = BaseUri.AbsoluteUri;
var _url = new System.Uri(new System.Uri(_baseUrl + (_baseUrl.EndsWith("/") ? "" : "/")), "api/shipping-management/Get-Available-Carriers").ToString();
// Create HTTP transport objects
var _httpRequest = new HttpRequestMessage();
HttpResponseMessage _httpResponse = null;
_httpRequest.Method = new HttpMethod("POST");
_httpRequest.RequestUri = new System.Uri(_url);
// Set Headers
if (customHeaders != null)
{
foreach(var _header in customHeaders)
{
if (_httpRequest.Headers.Contains(_header.Key))
{
_httpRequest.Headers.Remove(_header.Key);
}
_httpRequest.Headers.TryAddWithoutValidation(_header.Key, _header.Value);
}
}
// Serialize Request
string _requestContent = null;
if(request != null)
{
_requestContent = SafeJsonConvert.SerializeObject(request, SerializationSettings);
_httpRequest.Content = new StringContent(_requestContent, System.Text.Encoding.UTF8);
_httpRequest.Content.Headers.ContentType =System.Net.Http.Headers.MediaTypeHeaderValue.Parse("application/json-patch+json; charset=utf-8");
}
// Send Request
if (_shouldTrace)
{
ServiceClientTracing.SendRequest(_invocationId, _httpRequest);
}
cancellationToken.ThrowIfCancellationRequested();
_httpResponse = await HttpClient.SendAsync(_httpRequest, cancellationToken).ConfigureAwait(false);
if (_shouldTrace)
{
ServiceClientTracing.ReceiveResponse(_invocationId, _httpResponse);
}
HttpStatusCode _statusCode = _httpResponse.StatusCode;
cancellationToken.ThrowIfCancellationRequested();
string _responseContent = null;
if ((int)_statusCode != 200)
{
var ex = new HttpOperationException(string.Format("Operation returned an invalid status code '{0}'", _statusCode));
if (_httpResponse.Content != null) {
_responseContent = await _httpResponse.Content.ReadAsStringAsync().ConfigureAwait(false);
}
else {
_responseContent = string.Empty;
}
ex.Request = new HttpRequestMessageWrapper(_httpRequest, _requestContent);
ex.Response = new HttpResponseMessageWrapper(_httpResponse, _responseContent);
if (_shouldTrace)
{
ServiceClientTracing.Error(_invocationId, ex);
}
_httpRequest.Dispose();
if (_httpResponse != null)
{
_httpResponse.Dispose();
}
throw ex;
}
// Create Result
var _result = new HttpOperationResponse<GetAvailableCarriersResponse>();
_result.Request = _httpRequest;
_result.Response = _httpResponse;
// Deserialize Response
if ((int)_statusCode == 200)
{
_responseContent = await _httpResponse.Content.ReadAsStringAsync().ConfigureAwait(false);
try
{
_result.Body = SafeJsonConvert.DeserializeObject<GetAvailableCarriersResponse>(_responseContent, DeserializationSettings);
}
catch (JsonException ex)
{
_httpRequest.Dispose();
if (_httpResponse != null)
{
_httpResponse.Dispose();
}
throw new SerializationException("Unable to deserialize the response.", _responseContent, ex);
}
}
if (_shouldTrace)
{
ServiceClientTracing.Exit(_invocationId, _result);
}
return _result;
}
I have a unit test to call the generated code using
var api = MakeApi();
var task=api.GetAvailableCarriersByJobHeaderIdWithHttpMessagesAsync(req);
var carriers = task.Result.Body.Carriers;
where
private static MyApiService MakeApi()
{
var setting = new MyAPISettings(false);
var api = new MyApiService(setting);
return api;
}
and MyApiService contains (with altered namespaces)
public Task<HttpOperationResponse<GetAvailableCarriersResponse>> GetAvailableCarriersByJobHeaderIdWithHttpMessagesAsync(
GetAvailableCarriersRequest request = default(GetAvailableCarriersRequest), Dictionary<string, List<string>> customHeaders = null,
CancellationToken cancellationToken = default(CancellationToken))
{
return ApiCaller.ExecuteAsync(
async headers => await API.GetAvailableCarriersByJobHeaderIdWithHttpMessagesAsync(request, headers, cancellationToken),
async () => await GetTokenHeadersAsync(customHeaders));
}
where apicaller is
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace MyServices
{
public static class ApiCaller
{
private static Dictionary<string, List<string>> Headers { get; set; }
private static string GetHeadersMessage()
{
string ret = "";
if (Headers != null)
{
foreach (string key in Headers.Keys)
{
if (Headers[key] != null)
{
foreach (string value in Headers[key])
{
ret = $"{key}-{value}\n";
}
}
}
}
return ret;
}
public async static Task<T> ExecuteAsync<T>(Func<Dictionary<string, List<string>>, Task<T>> f,
Func<Task<Dictionary<string, List<string>>>> getHeaders)
{
T ret = default(T);
try
{
try
{
if (getHeaders != null && Headers == null)
{
Headers = await getHeaders();
}
ret = await f(Headers);
}
catch (Microsoft.Rest.HttpOperationException ex1)
{
if (ex1.Response?.StatusCode == System.Net.HttpStatusCode.Unauthorized && getHeaders != null)
{
Headers = await getHeaders();
ret = await f(Headers);
}
else
{
throw;
}
}
}
catch (Exception ex)
{
//Log.Error(ex, $"... API CALL ERROR ...\nHEADERS:{GetHeadersMessage()}");
throw new Exception($"Error calling the API. {ex.Message}", ex);
}
return ret;
}
}
}
My Api throws an InternalServerError
However when I run the unit test, I get an error in the client code.
The error occurs at
// Create Result
var _result = new HttpOperationResponse<GetAvailableCarriersResponse>();
And is
System.Exception: Error calling the API. Operation returned an invalid status code 'InternalServerError' ---> Microsoft.Rest.HttpOperationException: Operation returned an invalid status code 'InternalServerError'
at MyConnector.MyApi.<GetAvailableCarriersByJobHeaderIdWithHttpMessagesAsync>d__49.MoveNext()
How can I work around this?
I note that the code for HttpOperationResponse is
namespace Microsoft.Rest
{
/// <summary>
/// Represents the base return type of all ServiceClient REST operations.
/// </summary>
public class HttpOperationResponse<T> : HttpOperationResponse, IHttpOperationResponse<T>, IHttpOperationResponse
{
/// <summary>Gets or sets the response object.</summary>
public T Body { get; set; }
}
}
Here is the structure for GetAvailableCarriersResponse
using Newtonsoft.Json;
using System.Collections.Generic;
public partial class GetAvailableCarriersResponse
{
public GetAvailableCarriersResponse()
{
CustomInit();
}
public GetAvailableCarriersResponse(IList<DeliverBy> carriers = default(IList<DeliverBy>))
{
Carriers = carriers;
CustomInit();
}
partial void CustomInit();
[JsonProperty(PropertyName = "carriers")]
public IList<DeliverBy> Carriers { get; set; }
}
[Update]
In ApiCaller ExecuteAsync the following executes.
throw;
If I catch the error at this point, it's (edited) ToString() returns
"Microsoft.Rest.HttpOperationException: Operation returned an invalid status code 'InternalServerError' at MyAPI.
<GetAvailableCarriersByJobHeaderIdWithHttpMessagesAsync>d__49.MoveNext() in
MyConnector\\generated\\MyAPI.cs:line 4018
End of stack trace from previous location where exception was thrown at
System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() at
System.Runtime.CompilerServices.TaskAwaiter.
HandleNonSuccessAndDebuggerNotification(Task task)
at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
at MyApiService.<>c__DisplayClass39_0.<<GetAvailableCarriersByJobHeaderIdWithHttpMessagesAsync>b__0>d.MoveNext()
in MyApiService.cs:line 339
End of stack trace from previous location where exception was thrown
at System.Runtime.ExceptionServices.ExceptionDispatch
Info.Throw() at System.Runtime.CompilerServices.TaskAwaiter.
HandleNonSuccessAndDebuggerNotification(Task task)
at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
at MyServices.ApiCaller.<ExecuteAsync>d__5`1.MoveNext()
in ApiCaller.cs:line 50"
I edited some of the names in the above code to simplify and obfuscate.
[Update]
The problem seems to be to do with the getHeaders parameter to ApiCaller.ExecuteAsync
[Update]
If I examine ex1 thrown in ExecuteAsync, I can get my Api Error type using
ex1.Response.StatusCode
but how do I get the error description?

What I did to get error description is to cast it to one of the error types generated by Autorest.
if (myRawResponseObject is My422Response my422Response)
{
// Response has HTTP Status Code 422
Console.WriteLine($"Error Response Type: {my422Response.ToString()}");
}
If you OpenAPI document defines error properties for a 422 response, then you will find them on the My422Response object.

Related

How to pass a value from a synchronous method to asynchronous method within an apex class?

I have made a REST call to a url and need to pass the jsonresponse obtained from the synchronous method to the below asynchronous method.I have used a static variable - strresponse to do the same.But i am getting a null value when i call this value in the asynchronous method.Could this be because asynchronous methods do not remember the value given by a synchronous method? is there any alternate way this could be done?
code below:
public with sharing class myController {
public Static String strResponse ;
#AuraEnabled
public static Map<String, Object> retriveNews(){
HttpRequest httpRequest = new HttpRequest();
httpRequest.setEndpoint('endpoint URL');
httpRequest.setMethod('GET');
try{
Http http = new Http();
HttpResponse httpResponse = http.send(httpRequest);
if(httpResponse.getStatusCode() == 200){
newsController.strResponse = httpResponse.getBody();
} else {
throw new CalloutException(httpResponse.getBody());
}
} catch(Exception ex){
throw ex;
}
Map<String, Object> newsJsonData = new Map<String, Object>();
if(!String.isBlank(newsController.strResponse)){
System.debug('jsonstring:'+newsController.strResponse);
newsJsonData = (Map<String, Object>)JSON.deserializeUntyped(newsController.strResponse);
}
System.debug('jsonstring in retrivenews:'+newsController.strResponse);
if(!newsJsonData.isEmpty()){
return newsJsonData;
} else {
return null;
}
}
#future(callout=true)
public static void insertnews()
{
List<Newsroom__c> nrmlist = new List<Newsroom__c>();
System.debug('jsonstring in insertnews:'+newsController.strResponse);
JSONParser parser = JSON.createParser(newsController.strResponse);
while (parser.nextToken() != null) {
// Start at the array of invoices.
if (parser.getCurrentToken() == JSONToken.START_ARRAY) {
while (parser.nextToken() != null) {
// Advance to the start object marker to
// find next invoice statement object.
if (parser.getCurrentToken() == JSONToken.START_OBJECT) {
objectne nrm = (objectne)parser.readValueAs(objectne.class);
objectne__c nroom = new objectne__c(Author__c = nrm.Author,Description__c = nrm.Description);
String s = JSON.serialize(nrm);
system.debug('Serialized object: ' + s);
nrmlist.add(nroom);
system.debug('list of nrooms:'+ nrmlist);
}
}
}
}
Database.SaveResult[] lsr = Database.insert(nrmlist, false) ;
System.debug(lsr);
}
Error - when newscontroller.insertnews() is called - null string to parser

How to change default SOAP-ENV and ns2 namespace prefixes in spring-ws

Following is the response I'm getting and I need to override SOAP-ENV and ns2 in this payload.
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP-ENV:Header/>
<SOAP-ENV:Body>
<ns2:Response xmlns:ns2="">
<ns2:RequestReturn>
<ns2:COUNTRY_CODE>1</ns2:COUNTRY_CODE>
</ns2:RequestReturn>
</ns2:Response>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
expected output
<soapns:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
<soapns:Header/>
<soapns:Body>
<tns:Response xmlns:tns="">
<tns:RequestReturn>
<tns:COUNTRY_CODE>1</tns:COUNTRY_CODE>
</tns:RequestReturn>
</tns:Response>
</soapns:Body>
</soapns:Envelope>
We need to implement the following Interceptor
#Component
public class CustomEndpointInterceptor extends EndpointInterceptorAdapter {
private static final Log LOG = LogFactory.getLog(CustomEndpointInterceptor.class);
private static final String SOAP_ENV_NAMESPACE = "http://schemas.xmlsoap.org/soap/envelope/";
private static final String PREFERRED_PREFIX = "infasoapns";
#Override
public boolean handleResponse(MessageContext messageContext, Object endpoint) throws Exception {
LOG.info("Endpoint Response Handling");
SaajSoapMessage soapResponse = (SaajSoapMessage) messageContext.getResponse();
alterSoapEnvelope(soapResponse);
return super.handleResponse(messageContext, endpoint);
}
#Override
public boolean handleFault(MessageContext messageContext, Object endpoint) throws Exception {
LOG.info("Endpoint Response Handling");
SaajSoapMessage soapResponse = (SaajSoapMessage) messageContext.getResponse();
alterSoapEnvelope(soapResponse);
return super.handleFault(messageContext, endpoint);
}
private void alterSoapEnvelope(SaajSoapMessage soapResponse) {
try {
SOAPMessage soapMessage = soapResponse.getSaajMessage();
SOAPPart soapPart = soapMessage.getSOAPPart();
SOAPEnvelope envelope = soapPart.getEnvelope();
SOAPHeader header = soapMessage.getSOAPHeader();
SOAPBody body = soapMessage.getSOAPBody();
SOAPFault fault = body.getFault();
envelope.removeNamespaceDeclaration(envelope.getPrefix());
envelope.addNamespaceDeclaration(PREFERRED_PREFIX, SOAP_ENV_NAMESPACE);
envelope.setPrefix(PREFERRED_PREFIX);
header.setPrefix(PREFERRED_PREFIX);
body.setPrefix(PREFERRED_PREFIX);
addDesiredBodyNamespaceEntries(body.getChildElements());
removeUndesiredBodyNamespaceEntries(body.getChildElements());
if (fault != null) {
fault.setPrefix(PREFERRED_PREFIX);
}
} catch (SOAPException e) {
e.printStackTrace();
}
}
private void addDesiredBodyNamespaceEntries(Iterator childElements) {
while (childElements.hasNext()) {
final Object childElementNode = childElements.next();
if (childElementNode instanceof SOAPElement) {
SOAPElement soapElement = (SOAPElement) childElementNode;
// set desired namespace body element prefix
soapElement.setPrefix("tns");
// recursively set desired namespace prefix entries in child elements
addDesiredBodyNamespaceEntries(soapElement.getChildElements());
}
}
}
private void removeUndesiredBodyNamespaceEntries(Iterator childElements) {
while (childElements.hasNext()) {
final Object childElementNode = childElements.next();
if (childElementNode instanceof SOAPElement) {
SOAPElement soapElement = (SOAPElement) childElementNode;
// we remove any prefix/namespace entries added by JAX-WS in the body element that is not the one we want
for (String prefix : getNamespacePrefixList(soapElement.getNamespacePrefixes())) {
if (prefix != null && ! "tns".equals(prefix)) {
soapElement.removeNamespaceDeclaration(prefix);
}
}
// recursively remove prefix/namespace entries in child elements
removeUndesiredBodyNamespaceEntries(soapElement.getChildElements());
}
}
}
private Set<String> getNamespacePrefixList(Iterator namespacePrefixIter) {
Set<String> namespacePrefixesSet = new HashSet<>();
while (namespacePrefixIter.hasNext()) {
namespacePrefixesSet.add((String) namespacePrefixIter.next());
}
return namespacePrefixesSet;
}
}
and add the following code segment to the Webservice config
#Override
public void addInterceptors(List<EndpointInterceptor> interceptors) {
// register global interceptor
interceptors.add(new CustomEndpointInterceptor());
// register endpoint specific interceptor
interceptors.add(new PayloadRootSmartSoapEndpointInterceptor(
new CustomEndpointInterceptor(),
ValidationEndpoint.NAMESPACE_URI,
ValidationEndpoint.LOCAL_PART));
}

InvokeApiAsync<HttpResponseMessage> returns null

Can someone explain my why that client (Xamarin.Forms PCL) call returns null?
HttpResponseMessage response = await OfflineSyncStoreManager.Instance.MobileAppClient.InvokeApiAsync<HttpResponseMessage>("ResetTruckAuftragWorkflow");
response is null. When I execute that in a console app it returns the
valid http response.
I use the latest stable ZUMO nugets in client and backend. There is my ZUMO backend code:
[Authorize]
[MobileAppController]
public class ResetTruckAuftragWorkflowController : ApiController
{
private readonly RcsMobileContext _rcsMobileContext;
private readonly TruckFahrerInfo _truckFahrerInfo;
public ResetTruckAuftragWorkflowController()
{
_rcsMobileContext = new RcsMobileContext();
_truckFahrerInfo = new TruckFahrerInfo(this.User as ClaimsPrincipal);
}
// POST api/ResetTruckAuftragWorkflow
[HttpPost]
public async Task<IHttpActionResult> PostAsync()
{
if (ModelState.IsValid)
{
using (var transaction = _rcsMobileContext.Database.BeginTransaction())
{
try
{
var truckAuftragList = _rcsMobileContext.TruckAuftrags.PerUserFilter(_truckFahrerInfo.FahrerId);
var truckAppIds = truckAuftragList?.Select(ta => ta.TruckAppId).ToArray();
if (truckAppIds != null)
{
foreach (var truckAppId in truckAppIds)
{
await _rcsMobileContext.Database.ExecuteSqlCommandAsync(_tawQueryTaskStatus10, truckAppId);
await _rcsMobileContext.Database.ExecuteSqlCommandAsync(_tawQueryTaskStatus5, truckAppId);
await _rcsMobileContext.Database.ExecuteSqlCommandAsync(_talQuery, truckAppId);
await _rcsMobileContext.Database.ExecuteSqlCommandAsync(_taQuery, truckAppId);
}
}
await _rcsMobileContext.Database.ExecuteSqlCommandAsync(_taQuery, _truckFahrerInfo.FahrerId);
transaction.Commit();
}
catch (Exception e)
{
transaction.Rollback();
return BadRequest($"Transaction failed: {e}");
}
}
return Ok();
}
else
{
return BadRequest(ModelState);
}
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
_rcsMobileContext.Dispose();
}
base.Dispose(disposing);
}
}
thanks
Eric
InvokeApiAsync decodes the body that is returned and de-serializes the JSON into type T. You should not use HttpResponseMessage for this purpose as it is not serializable.
If you don't care about the body, use the non-generic form of InvokeApiAsync.

Why after adding a header to HttpClientRequestMessage CancellationToken.IsCancellationRequested changes to true

I use custom HttpClientHandler to authorize test if it is not authorized.
Windows Store Unit Test App project type is used
using Microsoft.WindowsAzure.MobileServices;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Windows.Security.Credentials;
using Windows.UI.Popups;
using XperiAndri.Efficiency.Common.Http.Handlers;
namespace XperiAndri.Efficiency.Common.Tests.Online
{
public class AuthenticationHandler : DelegatingHandler
{
private const string user = "";
private const string password = "";
private const string MobileServiceAuthenticationTokenHeader = "X-ZUMO-AUTH";
private readonly PasswordVault vault = new PasswordVault();
public IMobileServiceClient Client { get; set; }
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
HttpResponseMessage response = await base.SendAsync(request, cancellationToken);
if (response.StatusCode == HttpStatusCode.Unauthorized)
{
var headers = request.Headers;
PasswordCredential credential = FindCredential();
if (credential != null)
{
Client.CurrentUser = new MobileServiceUser(user) { MobileServiceAuthenticationToken = credential.Password };
headers.Remove(MobileServiceAuthenticationTokenHeader);
credential.RetrievePassword();
headers.Add(MobileServiceAuthenticationTokenHeader, credential.Password);
response = await base.SendAsync(request, cancellationToken);
}
if (response.StatusCode == HttpStatusCode.Unauthorized)
{
JObject content = new JObject { { "user", user }, { "password", password } };
var result = await Client.InvokeApiAsync("UnitTestLogin", content, HttpMethod.Post, null);
string token = result["token"].Value<string>();
if (Client.CurrentUser != null)
Client.CurrentUser.MobileServiceAuthenticationToken = token;
else
Client.CurrentUser = new MobileServiceUser(user) { MobileServiceAuthenticationToken = token };
headers.Remove(MobileServiceAuthenticationTokenHeader);
headers.Add(MobileServiceAuthenticationTokenHeader, token); // After execution of this line cancellationToken.IsCancellationRequested changes to true
// try again!
response = await base.SendAsync(request, cancellationToken);
if (response.StatusCode != HttpStatusCode.Unauthorized)
{
if (credential != null)
credential.Password = token;
else
{
credential = new PasswordCredential(Client.ApplicationUri.ToString(), user, token);
vault.Add(credential);
}
}
}
}
return response;
}
private PasswordCredential FindCredential()
{
try
{
return vault.FindAllByResource(Client.ApplicationUri.ToString()).FirstOrDefault();
}
catch
{
return null;
}
}
}
}
Look at the comment at the line
headers.Add(MobileServiceAuthenticationTokenHeader, token);
the second time it is executed.
Can somebody explain why does it happened? Why request becomes cancelled?
You're trying to add header to request after actually receiving response (since you already awaited SendAsync) which logically is not possible and causes cancellation of original request.

MVC 2.0 - Custom handling of all errors to return json

I have an MVC 2 app that I want all requests to return json. I have overridden a HandleErrorAttribute and an AuthorizeAttribute. My goal is that all errors (even 403 and 404) are returned as json.
Here is my error handler. ExceptionModel is a simple class defining any error returned by my application. The Exception handler is a class that translates the error details into a formatted e-mail and sends it to me.
public class HandleErrorJsonAttribute : System.Web.Mvc.HandleErrorAttribute
{
public override void OnException(ExceptionContext context)
{
context.ExceptionHandled = true;
RaiseErrorSignal(context.Exception);
context.RequestContext.HttpContext.Response.ContentType = "application/json";
JsonSerializer serializer = new JsonSerializer();
serializer.Serialize(context.HttpContext.Response.Output, new ExceptionModel(context.Exception));
}
private static void RaiseErrorSignal(Exception ex)
{
IExceptionHandler handler = Resolve();
handler.HandleError(ex.GetBaseException());
}
private static IExceptionHandler Resolve()
{
return ServiceLocator.Locate<IExceptionHandler>();
}
}
Here is the Exception model for clarification
public class ExceptionModel
{
public int ErrorCode { get; set; }
public string Message { get; set; }
public ExceptionModel() : this(null)
{
}
public ExceptionModel(Exception exception)
{
ErrorCode = 500;
Message = "An unknown error ocurred";
if (exception != null)
{
if (exception is HttpException)
ErrorCode = ((HttpException)exception).GetHttpCode();
Message = exception.Message;
}
}
public ExceptionModel(int errorCode, string message)
{
ErrorCode = errorCode;
Message = message;
}
}
and finally, my custom authorize attribute. I an using forms auth, but I did not want any of the automatic redirection. I simply want the error to show on the screen and stop any further processing.
public class AuthorizeTokenAttribute : System.Web.Mvc.AuthorizeAttribute
{
public bool SuperAdminOnly { get; set; }
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
bool authorized = base.AuthorizeCore(httpContext);
if(!SuperAdminOnly)
return authorized;
if(!authorized)
return authorized;
return SessionHelper.UserIsSuperAdmin(httpContext.User.Identity.Name);
}
protected override void HandleUnauthorizedRequest(System.Web.Mvc.AuthorizationContext filterContext)
{
throw new HttpException(403, "Access Denied");
}
}
This all works great for most errors, but it is missing one thing. I have a controller action like this.
[AuthorizeToken]
[HttpPost]
public JsonResult MyAction()
{
return new JsonResult();
}
It works fine when you submit via post, but on a get I receive an unhandled 404 error.
Server Error in '/' Application.
The resource cannot be found.
Description: HTTP 404. The resource
you are looking for (or one of its
dependencies) could have been removed,
had its name changed, or is
temporarily unavailable. Please
review the following URL and make sure
that it is spelled correctly.
Requested URL: /MyController/MyAction
Version Information: Microsoft .NET
Framework Version:4.0.30319; ASP.NET
Version:4.0.30319.1
This happens on a GET, which is to be expected as default behavior. However, how can I handle for this condition so that I could instead return json like this
{"ErrorCode":404,"Message":"Page Not Found"}
To handle errors personally I prefer the Application_Error event in Global.asax:
protected void Application_Error(object sender, EventArgs e)
{
var exception = Server.GetLastError();
Response.Clear();
Server.ClearError();
var httpException = exception as HttpException;
var routeData = new RouteData();
routeData.Values["controller"] = "Errors";
routeData.Values["action"] = "Index";
routeData.Values["error"] = exception;
IController errorController = new ErrorsController();
errorController.Execute(new RequestContext(new HttpContextWrapper(Context), routeData));
}
and then have an ErrorsController:
public class ErrorsController : Controller
{
public ActionResult Index(Exception exception)
{
var errorCode = 500;
var httpException = exception as HttpException;
if (httpException != null)
{
errorCode = httpException.ErrorCode;
}
return Json(new
{
ErrorCode = errorCode,
Message = exception.Message
}, JsonRequestBehavior.AllowGet);
}
}