How to deal with the result that i wanted in asp.net web api - asp.net

I'm trying to write web api for app developers,and i want the api result like the sample below
When Exception:
{
"StatusCode": "0",
"Message": "There's exception when calling web api"
}
Normal: the Result in the json string was the return type in the web api action.
{
"StatusCode": "1",
"Message": "Action completed successful",
"Result": {}
}
If the action is:
public DemoController : ApiController
{
public class DemoModel
{
public string X {get;set;}
public int Y {get;set;}
}
[HttpGet]
public DemoModel GetModel(int id)
{
return new DemoModel() { X = "Demo return string" , Y = 1234};
}
}
The Json string should be the sample below when calling the action successfully.
{
"StatusCode": "1",
"Message": "Action completed successful",
"Result": {
"X": "Demo return string",
"Y": 1234
}
}
and when exception, should be :
{
"StatusCode": "0",
"Message": "There's exception when calling web api"
}
So,the app developers could see the return type details in the web api help page.
Is that easy to implement?and how to do (no detail,just logic,also detail is better.)
thanks for everyone !

You should create DelegatingHandler to wrapper your all response from server:
public class WrappingResponseHandler : DelegatingHandler
{
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request,
CancellationToken cancellationToken)
{
HttpResponseMessage response = await base.SendAsync(request, cancellationToken);
return BuildApiResponse(request, response);
}
private static HttpResponseMessage BuildApiResponse(HttpRequestMessage request, HttpResponseMessage response)
{
object result;
string message = null;
int status;
if (response.TryGetContentValue(out result) == false || response.IsSuccessStatusCode == false)
{
var error = result as HttpError;
if (error != null)
{
result = null;
}
message = "There's exception when calling web api";
status = 0;
}
else
{
message = "Action completed successful";
status = 1;
}
HttpResponseMessage newResponse = request.CreateResponse(response.StatusCode,
new ApiResponse() { Message = message, Result = result, StatusCode = status });
foreach (KeyValuePair<string, IEnumerable<string>> header in response.Headers)
{
newResponse.Headers.Add(header.Key, header.Value);
}
return newResponse;
}
public class ApiResponse
{
public int StatusCode { get; set; }
public string Message { get; set; }
public object Result { get; set; }
}
}
And add this handler in WebApiConfig:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.MessageHandlers.Add(new WrappingResponseHandler()); //here
// Web API configuration and services
// Configure Web API to use only bearer token authentication.
config.SuppressDefaultHostAuthentication();
config.Filters.Add(new HostAuthenticationFilter(OAuthDefaults.AuthenticationType));
// Web API routes
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
}
And nothing would have to change and add controllers.

Using IHttpActionResult would really helpful esp if your thinking of app developers. It works very with Http response code like 200(Ok), 500(Internal Server Error), 404(Not Found) etc
Here is simple code example, where your getting product and returning appropriate response based on returns
public IHttpActionResult Get (int id)
{
Product product = _repository.Get (id);
if (product == null)
{
return NotFound(); // Returns a NotFoundResult
}
return Ok(product); // Returns an OkNegotiatedContentResult
}
More on this Action Results on Web Api 2, you can even write custom action result.
When app client consumes, it gets proper HTTP response code, any response object or message along.

Related

.Net Core Handle exceptions that returned from web api

I'm using .Net 5.0 as backend and .Net 5.0 for client-side.
I want to know how to handle exceptions that returned from web api in Client Side and show them to client.
The api result on exception is like :
{
"Version": "1.0",
"StatusCode": 500,
"ErrorMessage": "User not found!"
}
How to handle this type of exception globally in the client side (using .Net Core MVC)?
According to your description, I suggest you could use try catch on the server-side to capture the exception and return as a json response.
In the client side, you could use deserlize the response and create a new view named Error to show the response message.
More details, you could refer to below codes:
Error Class:
public class APIError
{
public string Version { get; set; }
public string StatusCode { get; set; }
public string ErrorMessage { get; set; }
}
API:
[HttpGet]
public IActionResult Get()
{
try
{
throw new Exception("UserNotFound");
}
catch (Exception e)
{
return Ok(new APIError { Version="1.0", ErrorMessage=e.Message, StatusCode="500" });
}
}
Application:
var request = new HttpRequestMessage(HttpMethod.Get,
"https://localhost:44371/weatherforecast");
var client = _clientFactory.CreateClient();
var response = await client.SendAsync(request);
if (response.IsSuccessStatusCode)
{
var responseStream = await response.Content.ReadAsStringAsync();
APIError re = JsonSerializer.Deserialize<APIError>(responseStream, new JsonSerializerOptions
{
PropertyNameCaseInsensitive = true,
});
if (re.StatusCode == "500")
{
return View("Error", new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier, Version = re.Version, StatusCode = re.StatusCode, ErrorMessage = re.ErrorMessage });
}
}
else
{
// Hanlde if request failed issue
}
Notice: I created a new Error view, you could create it by yourself or modify the default error view.
Error Viewmodel:
public class ErrorViewModel
{
public string RequestId { get; set; }
public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
public string Version { get; set; }
public string StatusCode { get; set; }
public string ErrorMessage { get; set; }
}
Error view:
#model ErrorViewModel
#{
ViewData["Title"] = "Error";
}
<h1 class="text-danger">Error.</h1>
<h2 class="text-danger">An error occurred while processing your request.</h2>
#if (Model.ShowRequestId)
{
<p>
<strong>Request ID:</strong> <code>#Model.RequestId</code>
</p>
}
<h3>#Model.StatusCode</h3>
<p>
#Model.ErrorMessage
</p>
Result:
If you don't want to use exceptions in the backend, you could just send the http status code to the client. Here is an example of reaching out to an external api via service and returning that status to the backend controller. You would then just GET this result via client side. You could also just send over the full http response to the client, instead of solely the HttpStatusCode if needed.
A little more elaboration here: https://learn.microsoft.com/en-us/aspnet/web-api/overview/advanced/calling-a-web-api-from-a-net-client
//Backend Service..
private const string baseUrl = "https://api/somecrazyapi/";
public async Task<HttpStatusCode> GetUserStatusAsync(string userId)
{
var httpResponse = await client.GetAsync(baseUrl + "userId");
return httpResponse.StatusCode;
}
//Backend Controller
[ApiController]
[Route("[controller]")]
public class UserController
{
private readonly IUserService service;
public UserController(IUserService service)
{
this.service = service;
}
......
[HttpGet("{userId}")]
public HttpStatusCode GetUserStatus(string userId)
{
return service.GetUserStatusAsync(userId).Result;
}
}

ApiController HttpPost parameters

The httpPost transfer with parameters fails in the apiconroller.
It is trying to communicate from Android to Web server.
I succeeded in communicating with Get and Post, which had no parameters.
However, if parameter is added in Post transmission, it fails. I certainly think there is a problem with the Web server code.
The tutorial only contains information about the Model. I want to exchange strings.
Global.asax.cs
protected void Application_Start()
{
GlobalConfiguration.Configure(WebApiConfig.Register);
//....
}
WebApiConfig.cs
public class WebApiConfig
{
public const string UrlPrefix = "api";
public const string UrlPrefixRelative = "~/" + UrlPrefix;
public static void Register(HttpConfiguration config)
{
// Web API configuration and services
var httpControllerRouteHandler = typeof(HttpControllerRouteHandler).GetField("_instance",
System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic);
if (httpControllerRouteHandler != null)
{
httpControllerRouteHandler.SetValue(null,
new Lazy<HttpControllerRouteHandler>(() => new SessionHttpControllerRouteHandler(), true));
}
// Web API routes
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: UrlPrefix + "/{controller}/{action}/{sn}",
defaults: new { action = "Index", sn = RouteParameter.Optional }
);
}
public class SessionControllerHandler : HttpControllerHandler, IRequiresSessionState
{
public SessionControllerHandler(RouteData routeData) : base(routeData) { }
}
public class SessionHttpControllerRouteHandler : HttpControllerRouteHandler
{
protected override IHttpHandler GetHttpHandler(RequestContext requestContext)
=> new SessionControllerHandler(requestContext.RouteData);
}
}
ApiController.cs
public class LicenseController : ApiController
{
[HttpPost]
public HttpResponseMessage GetLicense([FromBody]string data)
{
return Request.CreateResponse(HttpStatusCode.OK, data);
}
[HttpGet]
public HttpResponseMessage GetLicense2(string data)
{
string udid = data;
string license = AES.Encrypt(udid);
return Request.CreateResponse(HttpStatusCode.OK, license);
}
[HttpPost]
public HttpResponseMessage GetLicense3()
{
return Request.CreateResponse(HttpStatusCode.OK, "ABC");
}
}
android code
new Thread(new Runnable() {
#Override
public void run() {
try{
// Defined URL where to send data
URL url = new URL("http://192.1.1.1:80/api/License/GetLicense/");
// Send POST data request
URLConnection conn = url.openConnection();
conn.setDoOutput(true);
OutputStreamWriter wr = new OutputStreamWriter(conn.getOutputStream());
//wr.write(URLEncoder.encode("data=3434", "UTF-8") );
wr.write("data=3434");
wr.flush();
// Get the server response
BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream()));
StringBuilder sb = new StringBuilder();
String line = null;
// Read Server Response
while((line = reader.readLine()) != null)
{
// Append server response in string
sb.append(line + "\n");
}
}
catch(Exception ex)
{
}
}
}).start();
For a web api POST method accepting a single string parameter you can do:
[HttpPost]
public HttpResponseMessage GetLicense([FromBody]string data)
And then post the data from client like:
wr.write("=3434");
For multiple post parameters, create a model class in Web API:
public class DataModel {
public string data1 {get;set;}
public string data2 {get;set;}
}
Update api endpoint parameter type:
[HttpPost]
public HttpResponseMessage GetLicense([FromBody]DataModel dataModel)
Then post json string from client with content-type: "application/json"
{
"data1": "Data1 contents",
"data2": "Data2 contents"
}

How to configure route in Asp.Net WebApi to use action method name as a query parameter?

I want to invoke diffrent action methods based on the query string parameter, for example, webapi/mycontroller?action=getuser&id=10 should invoke mycontroller.getuser(10) action method and webapi/mycontroller?action=getallusers should invoke mycontroller.getallusers() action method. I tried to write the routing in the following way:
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "webapi/{controller}?action={action}"
);
But this is not allowed, visual studio gives me the error The route URL cannot start with a '/' or '~' character and it cannot contain a '?' character.
So I've knocked something together that might help you get started
First create a route with custom handler
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}",
defaults: null,
constraints: null,
handler: new CustomHttpControllerDispatcher(config)
);
public class CustomHttpControllerDispatcher : HttpMessageHandler
{
private IHttpControllerSelector _controllerSelector;
private readonly HttpConfiguration _configuration;
public CustomHttpControllerDispatcher(HttpConfiguration configuration)
{
_configuration = configuration;
}
public HttpConfiguration Configuration
{
get { return _configuration; }
}
private IHttpControllerSelector ControllerSelector
{
get
{
if (_controllerSelector == null)
{
_controllerSelector = _configuration.Services.GetHttpControllerSelector();
}
return _controllerSelector;
}
}
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
return SendAsyncInternal(request, cancellationToken);
}
private Task<HttpResponseMessage> SendAsyncInternal(HttpRequestMessage request, CancellationToken cancellationToken)
{
IHttpRouteData routeData = request.GetRouteData();
Contract.Assert(routeData != null);
HttpControllerDescriptor httpControllerDescriptor = ControllerSelector.SelectController(request);
IHttpController httpController = httpControllerDescriptor.CreateController(request);
foreach (var queryParam in request.GetQueryNameValuePairs())
{
routeData.Values.Add(queryParam.Key, queryParam.Value);
}
// Create context
HttpControllerContext controllerContext = new HttpControllerContext(_configuration, routeData, request);
controllerContext.Controller = httpController;
controllerContext.ControllerDescriptor = httpControllerDescriptor;
return httpController.ExecuteAsync(controllerContext, cancellationToken);
}
}
Then set your methods to get in the controller
public class MyController : ApiController
{
[HttpGet]
public IHttpActionResult GetUser([FromUri]int userId)
{
return Ok();
}
[HttpGet]
public IHttpActionResult DoSomething([FromUri]string test)
{
return Ok();
}
}
I've only tried with GET methods, POSTs may just work, but I haven't tested.

return the object on GET request Exception filters

I am trying to return object with json data through a ASP.NET WEb API call.
I want to return the data in the form IHttpActionResult
{
"message": "success",
"statuscode": "SuccessCode",
"statusmessage": "SuccessMsg",
"data": [
{
"corrBD1": null,
"flagType1": null,
"status1": null,
"mstrRep1": null,
"repname1": null,
"nameAccount1": null,
"acDate1": "0001-01-01T00:00:00",
"preAddress1": null,
"currAddress1": null
}]
}
I can return this data on a valid GET request as:
return OK(employee);
but when there is an exception occurs i need to return the data in the same form
{
"message": "fail",
"statuscode": "400",
"statusmessage": "failmsg",
"data": null
}
but the exception filters forcing me to send the respond in the form of HttpResponseMessage (which unfortunately not able to return in required form)
how to accomplish this?
If I understand the question correctly, you are simply trying to use your consistent message syntax in the result regardless of whether you are handling the response in the controller or the exception filter. You should be able to do what you want by creating your own IHttpActionResult:
private class MyResult : IHttpActionResult
{
public HttpRequestMessage Request { get; set; }
public string Content { get; set; }
public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
{
HttpResponseMessage response =
new HttpResponseMessage(HttpStatusCode.InternalServerError); // or whatever
response.Content = new StringContent(Content); // your content here
response.RequestMessage = Request;
return Task.FromResult(response);
}
}
In your handler you just set ExceptionHandlerContext.Request to the instance you created above. Does that work for your scenario?
Thanks Jeremy !! I have implemented your solution. I have done some changes in it and able to implement the desired result. please check the code and give suggestions.
public class CustomExceptionResult : IHttpActionResult
{
private HttpRequestMessage _request;
private HttpResponseMessage _httpResponseMessage;
public CustomExceptionResult(HttpRequestMessage request, HttpResponseMessage httpResponseMessage, Employee data)
{
_request = request;
_httpResponseMessage = request.CreateResponse(HttpStatusCode.InternalServerError, data);
}
public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
{
return Task.FromResult(_httpResponseMessage);
}
}
This is my exception handler code.
public class NotFoundExceptionFilter:ExceptionFilterAttribute
{
public override void OnException(HttpActionExecutedContext context)
{
if (context.Exception is KeyNotFoundException)
{
var msg = context.Exception.Message;
//class for creating standard Json object
JsonWrapper data= new JsonWrapper("There is some problem with the key you are looking for","400","Bad Request",null);
Controllers.NotFoundExceptionResult result = new Controllers.NotFoundExceptionResult(context.Request, data);
context.Response = context.Request.CreateResponse(HttpStatusCode.BadRequest, result);
}
else
{
base.OnException(context);
}
}
}
I am getting the below Json in Result:
{
"message": "There is some problem with the key you are looking for",
"statuscode": "400",
"statusmessage": "Bad request",
"data": null
}

Return custom HTTP code from ActionFilterAttribute

I use the code below to throttle my ASP.NET Web Api:
public class Throttle : ActionFilterAttribute
{
public override async Task OnActionExecutingAsync(HttpActionContext context, CancellationToken cancellationToken)
{
// ...
if (throttle)
{
throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.Conflict));
}
}
}
However, I cannot return error code 429, because it's not in HttpStatusCode enum. Is there a way to return a custom error code?
I found this over here.
var response = new HttpResponseMessage
{
StatusCode = (HttpStatusCode)429,
ReasonPhrase = "Too Many Requests",
Content = new StringContent(string.Format(CultureInfo.InvariantCulture, "Rate limit reached. Reset in {0} seconds.", data.ResetSeconds))
};
response.Headers.Add("Retry-After", data.ResetSeconds.ToString(CultureInfo.InvariantCulture));
actionContext.Response = response;
Hope this helps
This is what I did based on another response on StackOverflow.
Create Class (in controller file worked for me)
public class TooManyRequests : IHttpActionResult
{
public TooManyRequests()
{
}
public TooManyRequests(string message)
{
Message = message;
}
public string Message { get; private set; }
public HttpResponseMessage Execute()
{
HttpResponseMessage response = new HttpResponseMessage((HttpStatusCode)429);
if (!string.IsNullOrEmpty(Message))
{
response.Content = new StringContent(Message); // Put the message in the response body (text/plain content).
}
return response;
}
public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
{
return Task.FromResult(Execute());
}
}
Use in controller
public IHttpActionResult Get()
{
// with message
return new TooManyRequests("Limited to 5 request per day. Come back tomorrow.");
// without message
// return new TooManyRequests();
}

Resources