What I have:
I have an API where all non-success responses return an "IApiException"
public class ApiException : Exception, IApiException
{
public ExceptionType Type { get; set; }
public int ErrorCode { get; set; }
public HttpStatusCode Status { get; set; }
public string Code { get; set; }
public List<Error> Errors { get; set; } = new List<Error>();
public ApiException(ExceptionType type, Reason reason, string message, HttpStatusCode httpStatus)
{
Type = type;
Errors.Add(new Error { Message = message, Reason = reason.ToString() });
Status = httpStatus;
Code = httpStatus.ToString();
}
}
If I set the example in swashbuckle through an ISchemaFilter
it will put the same example on all model examples, so a 401 will return the same example as a 429.
What I want:
I want to be able to return different examples based on the status code.
Wha I've tried:
I tried creating an IOperationsFilter looking for the response keys and then setting either the response schema example or the response examples.
But it's is not displaying the models, at all.
public void Apply(Operation operation, SchemaRegistry schemaRegistry, ApiDescription apiDescription)
{
foreach (var response in operation.responses)
{
if (response.Key == "401")
{
response.Value.description = "Error";
response.Value.schema.example = new
{
Type = ExceptionType.Error.ToString(),
Errors = new {Message = "Unauthorized", Reason = Reason.Unauthorized},
Status = HttpStatusCode.Unauthorized,
Code = HttpStatusCode.Unauthorized.ToString()
};
}
}
}
Related
I have the following bit of code whihc sends a Http POST request to the server. The server reurns a 400 Bad request response along with a error object in the form of Json:
namespace MyApp.Shared.Dtos.Response
{
public class ErrorItem
{
public string Message { get; set; }
public string Tag { get; set; }
}
public class ErrorDto
{
public string Title { get; set; }
public List<ErrorItem> Errors { get; set; } = new();
}
}
namespace Accounting.Web.Services
{
public interface IHttpService
{
Task<T> Get<T>(string uri);
Task<T> Post<T>(string uri, object value, bool addBearerToken = false);
public ErrorDto Error { get; set; }
}
public class HttpService: IHttpService
{
private HttpClient _httpClient;
public ErrorDto Error { get; set; }
public HttpService(HttpClient httpClient)
{
_httpClient = httpClient;
_stateService = stateService;
}
public async Task<T> Post<T>(string uri, object value)
{
var request = new HttpRequestMessage(HttpMethod.Post, uri);
request.Content = new StringContent(JsonSerializer.Serialize(value), Encoding.UTF8, "application/json");
return await sendRequest<T>(request, addBearerToken);
}
private async Task<T> sendRequest<T>(HttpRequestMessage request)
{
using var response = await _httpClient.SendAsync(request);
if (response.StatusCode == System.Net.HttpStatusCode.BadRequest)
{
var result = await response.Content.ReadAsStringAsync();
Error = JsonSerializer.Deserialize<ErrorDto>(result);
//..
}
else
{
//..
}
}
}
}
The result correctly recieves the following response from the server as a JSON string:
{"title":"Username or password is incorrect","errors":[]}
And I can confirm by inspecting var result, it has the above value.
However, It doesn't seem deserialize into the ErrorDto class as one would expect it to:
Error = JsonSerializer.Deserialize(result);
But I simply cannot see any problems with the code, it looks like it should be working.
*** UPDATE ***
My server API code returrns the JSOn using the same DTO class (It's a shared class) using the following code:
[HttpPost("authenticate")]
public ActionResult Authenticate(AuthenticateRequest loginRequest)
{
var auth = _userService.Authenticate(loginRequest);
ErrorDto error = new()
{
Title = "Username or password is incorrect"
};
if (auth.user == null || auth.token == null)
{
return BadRequest(error);
}
return Ok(auth.user.ConvertToDto(auth.token));
}
By default System.Text.Json is case-sensitive. There are multiple options to handle this, for example by providing corresponding JsonSerializerOptions:
var json = #"{""title"":""Username or password is incorrect"",""errors"":[]}";
var errorDto = JsonSerializer.Deserialize<ErrorDto>(json, new JsonSerializerOptions
{
PropertyNameCaseInsensitive = true
});
Or marking properties with corresponding JsonPropertyNameAttribute:
public class ErrorItem
{
[JsonPropertyName("message")]
public string Message { get; set; }
[JsonPropertyName("tag")]
public string Tag { get; set; }
}
public class ErrorDto
{
[JsonPropertyName("title")]
public string Title { get; set; }
[JsonPropertyName("errors")]
public List<ErrorItem> Errors { get; set; } = new();
}
UPD
From How to customize property names and values with System.Text.Json doc:
Note
The web default is camel case.
If you want to switch from camel case to the naming policy used for DTOs you can do the following:
builder.Services.AddControllers()
.AddJsonOptions(opts => opts.JsonSerializerOptions.PropertyNamingPolicy = null);
I have the following class
public class Customer
{
public Customer()
{
Project = new HashSet<Project>();
}
public uint Id { get; set; }
public string Name { get; set; }
public string Address { get; set; }
public string Phone { get; set; }
public HashSet<Project> Project { get; set; }
}
And the following method
protected async Task<IRestResponse> ApplyRequest<T>(string resource,
Method method, T data) where T: class, new()
{
var client = new RestClient(_connectionConfig.BaseUrl);
client.FollowRedirects = false;
var request = new RestRequest(resource, method);
request.RequestFormat = RestSharp.DataFormat.Json;
request.AddParameter("application/json",
JsonConvert.SerializeObject(data), ParameterType.RequestBody);
//request.AddJsonBody(data);
//This also doesn't work
var response2 = await client.ExecuteTaskAsync<T>(request);
return response2;
}
Now if I call this method with Post method, the return statuscode is "created" (and it is actually created). However, the property IsSuccessful gives "false" and the error message is
"Unable to cast object of type 'SimpleJson.JsonArray' to type
'System.Collections.Generic.IDictionary`2[System.String,System.Object]'."
Is this usual, or am I doing something wrong?
Thanks
It's not successful because you had a serialisation error. I also see you logged it on GiTHub and got the same response. https://github.com/restsharp/RestSharp/issues/1064
I am developing Web API for my client. They have suggestion that all response should be a common JSON structure.
{ Data:"", Status:true, Message:"" }
If error means
{ Error:"", Status:false, Message:"" }
Which is the best method to create a common JSON structure as returns.
Now I created a class having these properties. And created 2 classes from IHttpActionResult,Error.cs and Success.cs, From that the response is created and returned from the controller.
The thing is in my controller,
public IHttpActionResult GetNewsAndAnnouncements()
{
var data = newsAndAnnouncementsDataServices.NewsAndAnnouncements();
if (data != null && data.Count() > 0)
{
return new Success(Request, "News and Announcements Retrieved Successfully", data);
}
return new Error(Request, "No News and Announcements Found");
}
Error.cs
public class Error : IHttpActionResult
{
private readonly string _message;
private readonly HttpRequestMessage _request;
private IErrorResponseModel errorResponse;
public Error(HttpRequestMessage request, string message)
{
_message = message;
_request = request;
errorResponse = new ErrorResponseModel();
}
public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
{
errorResponse.Message = _message;
errorResponse.Status = false;
errorResponse.Error = _message;
var response = new HttpResponseMessage(HttpStatusCode.BadRequest)
{
Content = new ObjectContent<object>(errorResponse, new JsonMediaTypeFormatter()),
RequestMessage = _request
};
return Task.FromResult(response);
}
}
Success.cs
public class Success : IHttpActionResult
{
private readonly string _message;
private readonly object _data;
private readonly HttpRequestMessage _request;
private IDataResponseModel dataResponse = new DataResponseModel();
public Success(HttpRequestMessage request, string message, object data)
{
_message = message;
_request = request;
_data = data;
}
public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
{
dataResponse.Message = _message;
dataResponse.Status = true;
dataResponse.Data = _data;
var response = new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new ObjectContent<object>(dataResponse, new JsonMediaTypeFormatter()),
RequestMessage = _request
};
return Task.FromResult(response);
}
}
DataResponseModel.cs
public class DataResponseModel : Mobility.Common.IDataResponseModel
{
public object Data { get; set; }
public string Message { get; set; }
public bool Status { get; set; }
}
ErrorResponseModel.cs
public class ErrorResponseModel : Mobility.Common.IErrorResponseModel
{
public object Error { get; set; }
public string Message { get; set; }
public bool Status { get; set; }
}
Is this a right method. I need suggestion. Is there any other way to achieve this. I heard about delegating handler something. But I don't have much idea on these.
Please help me.
Another solution to this problem is to hook into the ASP.NET pipeline using custom handlers to build a common response object.
For instance:
[DataContract]
public class ApiResponse
{
[DataMember]
public string Version { get { return "1.2.3"; } }
[DataMember]
public int StatusCode { get; set; }
[DataMember(EmitDefaultValue = false)]
public string ErrorMessage { get; set; }
[DataMember(EmitDefaultValue = false)]
public object Result { get; set; }
public ApiResponse(HttpStatusCode statusCode, object result = null, string errorMessage = null)
{
StatusCode = (int)statusCode;
Result = result;
ErrorMessage = errorMessage;
}
}
Have a look at this post for a reference implementation http://www.devtrends.co.uk/blog/wrapping-asp.net-web-api-responses-for-consistency-and-to-provide-additional-information
I am trying to create a Wcf Rest service using c#
The idea is to handle transactions/User/cards informations and so I've created the following structure:
[DataContract]
public class Result : Response
{
public List<IObject> data { get; set; }
}
[DataContract]
public class UserData : IObject
{
[DataMember]
public int id { get; set; }
//...
}
[DataContract]
public class CardData : IObject
{
[DataMember]
public int id { get; set; }
//...
}
[DataContract]
public class Error : Response
{
[DataMember]
public string errorID { get; set; }
[DataMember]
public string errorMessage { get; set; }
}
public abstract class Response
{
}
public abstract class IObject
{
}
public class Status : Response
{ public bool status { get; set; } }
The idea is that Cards/users are IObjects and that my Result has a list of them. Then result, status and error would be on the Reponse.
I've create the method to handle the get request and the response
public Dictionary<String, Response> GetUsers()
{
using (var db = new Context())
{
try
{
var query = from result in db.Users
select result;
List<IObject> r = query.Select(state => new UserData
{
id = state.AccountNumber,
//...
} as IObject
).ToList();
Result res = new Result { data = r };
Dictionary<String, Response> dic = new Dictionary<String, Response> { };
dic.Add("Error", new Error
{
errorID = "0",
errorMessage = "success"
});
dic.Add("Status", new Status { status = true });
dic.Add("response", res);
return teste;
}
catch (Exception e)
{
var ErrorMessage = e.Message.ToString();
throw;
}
}
}
Although it seems a little messy, iam able to compile it. When I call a get request of getUsers i get the following error:
"An exception of type 'System.Security.VerificationException' occurred in RestService.dll but was not handled in user code
Additional information: Operation could destabilize the runtime."
The ideia of response i wanted was something like:
{"status": true,"error": { "errorID" 0,
"errorMessage": "success"},"result": "GetUsersResult": [ .... ]}
I understand this is very sloppy coded, so i ask you the proper way to handle this.
I have a method that ts returning Array. I have problem with one class that cant convert the return type to array.
This is my method:
public Model.BaseType[] returnPayment_Gateway()
{
IncomingWebRequestContext request = WebOperationContext.Current.IncomingRequest;
WebHeaderCollection headers = request.Headers;
var settings = new DataContractJsonSerializerSettings { EmitTypeInformation = EmitTypeInformation.Never };
MemoryStream stream1 = new MemoryStream();
DataContractJsonSerializer serializer = new DataContractJsonSerializer(typeof(Model.BaseType),settings);
Model.Payment_Gateway[] allRecords = null;
if (headers["ServiceAuthentication"] != null)
{
string ServiceAuthentication = headers["ServiceAuthentication"].ToString();
bool serviceAuth = Service_Authentication(ServiceAuthentication);
DAL.DataManager dal = new DAL.DataManager();
if (serviceAuth == true)
{
allRecords = dal.Get_Payment_Gateway();
}
}
else
{
// Create a new customer to return
return new Model.ReturnResponse() { StatusCode = 201, StatusDescription = "Authentication Fails" };
}
return allRecords;
}
My problem is in else part, not sure how I can convert Model.ReturnResponse() to array now I am getting this error:
cannot implicitly convert type ReturnResponse to Model.BaseType[]
in case you like to see my 3 classes:
This is Base class:
[Serializable]
[DataContract]
[KnownType(typeof(Payment_Gateway))]
[KnownType(typeof(ReturnResponse))]
public class BaseType
{
}
this is Payment_Gateway class:
[DataContract]
public class Payment_Gateway:BaseType
{
[DataMember]
public int Payment_Gateway_ID { get; set; }
[DataMember]
public string Payment_Gateway_Name { get; set; }
[DataMember]
public string Payment_Gateway_URL { get; set; }
[DataMember]
public string Payment_Gateway_Description { get; set; }
and this is ReturnResponse class:
[DataContract]
public class ReturnResponse:BaseType
{
[DataMember]
public int StatusCode { get; set; }
[DataMember]
public string StatusDescription { get; set; }
}
Instead of trying to return a single ReturnResponse:
return new Model.ReturnResponse()
{ StatusCode = 201, StatusDescription = "Authentication Fails" };
Return an array, with the single element in it, since that's what your method is expected to return:
return new[] {
new Model.ReturnResponse()
{ StatusCode = 201, StatusDescription = "Authentication Fails" }
}
Instead of the hierarchy you have designed here, it would be easier if you encapsulate your return data in a response object that indicate a response type (success or failure) and the resulting data.
that class could look like the following
public enum ResponseTypes
{
Success,
Failure
}
public class Response
{
public ResponseTypes ResponseType {get; set;}
public Exception Error {get;set;}
public BaseType[] Data {get;set;}
}
this way it works like an envelope to your actual data and provides state to the caller to know if the call succeeded or not, and maybe for what reason, before reading the data (result)
its the same way WCF acts under the hood (in SOAP, at least) that it surrounds your data contract with a message envelope/enclosure to add more metadata for the caller to interpret the call status.