I have a ASP.NET Core v2.1 with Swashbuckle.AspNetCore package.
I have the following model for error response:
public class ErrorResponse
{
[JsonProperty(PropertyName = "error")]
public Error Error { get; set; }
}
public class Error
{
[JsonProperty(PropertyName = "code")]
public string Code { get; set; }
[JsonProperty(PropertyName = "message")]
public string Message { get; set; }
[JsonProperty(PropertyName = "target")]
public string Target { get; set; }
[JsonProperty(PropertyName = "details")]
public List<ErrorDetail> Details { get; set; }
[JsonProperty(PropertyName = "innererror")]
public InnerError InnerError { get; set; }
}
public class ErrorDetail
{
[JsonProperty(PropertyName = "code")]
public string Code { get; set; }
[JsonProperty(PropertyName = "message")]
public string Message { get; set; }
[JsonProperty(PropertyName = "target")]
public string Target { get; set; }
}
public class InnerError
{
[JsonProperty(PropertyName = "code")]
public string Code { get; set; }
[JsonProperty(PropertyName = "innererror")]
public InnerError NestedInnerError { get; set; }
}
so, for example, if something goes wrong, my API endpoint returns object of type ErrorResponse with appropriate StatusCode:
if (String.IsNullOrWhiteSpace(token))
{
ErrorResponse errorResponse = new ErrorResponse() { Error = new Error() };
errorResponse.Error.Code = "InvalidToken";
errorResponse.Error.Target = "token";
errorResponse.Error.Message = "Token is not specified";
return new BadRequestObjectResult(errorResponse);
}
How can i generate appropriate documentations using Swashbuckle.AspNetCore, so, client will know format of response if something goes wrong?
Take a look at the readme:
https://github.com/domaindrivendev/Swashbuckle.AspNetCore#explicit-responses
Explicit Responses
If you need to specify a different status code and/or additional responses, or your actions return IActionResult instead of a response DTO, you can describe explicit responses with the ProducesResponseTypeAttribute that ships with ASP.NET Core. For example ...
[HttpPost("{id}")]
[ProducesResponseType(typeof(Product), 200)]
[ProducesResponseType(typeof(IDictionary<string, string>), 400)]
[ProducesResponseType(500)]
public IActionResult GetById(int id)
So in your case you should add:
[ProducesResponseType(typeof(ErrorResponse), 400)]
To those actions that return the error, here is some good reading:
https://learn.microsoft.com/en-us/aspnet/core/web-api/advanced/conventions
Related
I want to use mapbox matching in ASP.NET Core. This link you can get response https://api.mapbox.com/matching/v5/mapbox/driving/..
I want to convert this response to dynamic json in Asp.net core, I use this line
var jsonResponse = JsonConvert.DeserializeObject(mapResponse);
but I get empty values. Any help?
Firstly, The API you have shared I got follwing response using
postman:
If its same what you are getting then I follow below steps to retrive
the value from the API response in C# asp.net core controller
Model You should have :
public class Admin
{
public string iso_3166_1_alpha3 { get; set; }
public string iso_3166_1 { get; set; }
}
public class Leg
{
public List<object> via_waypoints { get; set; }
public List<Admin> admins { get; set; }
public double weight { get; set; }
public double duration { get; set; }
public List<object> steps { get; set; }
public double distance { get; set; }
public string summary { get; set; }
}
public class Matching
{
public double confidence { get; set; }
public string weight_name { get; set; }
public double weight { get; set; }
public double duration { get; set; }
public double distance { get; set; }
public List<Leg> legs { get; set; }
public string geometry { get; set; }
}
public class Tracepoint
{
public int matchings_index { get; set; }
public int waypoint_index { get; set; }
public int alternatives_count { get; set; }
public double distance { get; set; }
public string name { get; set; }
public List<double> location { get; set; }
}
public class MapResponseClass
{
public List<Matching> matchings { get; set; }
public List<Tracepoint> tracepoints { get; set; }
public string code { get; set; }
public string uuid { get; set; }
}
Asp.net core Controller:
public async Task<IActionResult> CallMapAPI()
{
try
{
HttpClient client = new HttpClient();
HttpResponseMessage response = await client.GetAsync("https://api.mapbox.com/matching/v5/mapbox/driving/-117.17282,32.71204;-117.17288,32.71225;-117.17293,32.71244;-117.17292,32.71256;-117.17298,32.712603;-117.17314,32.71259;-117.17334,32.71254?access_token=pk.eyJ1Ijoibm92ZXJzbWFwIiwiYSI6ImNreTdwc3ppNTE3dzkyb3B2MnVzNXpueTUifQ.csYTL2GKkl99Yqk_TQjr5w");
response.EnsureSuccessStatusCode();
string mapAPIjson = await response.Content.ReadAsStringAsync();
var data = JsonConvert.DeserializeObject<MapResponseClass>(mapAPIjson);
}
catch (Exception ex)
{
throw;
}
return data;
}}
Output:
Note:
You should bound your class as per your API response. What I am
assuming of your empty values is you haven't converted the relevant
class accordingly. I hope above steps guided you accordingly.
I'm developing a Blazor WASM project and I'm stuck in this point.
I'm using a DataAccess Service to make the requests to EndPoints;
The endpoints return a ResultList, that is a Generic Object that needs to be parsed in Client side. The object definition:
public class ResultList
{
public ResultList(List<object> resultados, string codigoErro = null, string mensagemErro = null)
{
this.Resultados = resultados;
this.CodigoErro = codigoErro;
this.MensagemErro = mensagemErro;
}
public string MensagemErro { get; set; }
public List<object> Resultados { get; set; }
public string CodigoErro { get; set; }
}
In the client side, I receive the same type:
public async Task<ResultList> GetEmpresas()
{
try
{
ResultList Result = await _httpClient.GetFromJsonAsync<ResultList>("api/EmpCadBasico/GetEmpresas");
return Result;
}
catch (Exception ex)
{
return new ResultList(null, null, ex.Message);
}
}
The problem is: I can't convert the List<Object> to other type like List<Empresa>.
The C# compilation doesn't notify bug, but in execution time, it happens.
I tried Serialize and Deserialize, and it doesn't work too:
public async Task GetEmpresas()
{
ResultList Resultado = await _dataAccess.GetEmpresas();
if (await RetornoOk(Resultado))
{
string x = JsonSerializer.Serialize(Resultado.Resultados); // Here, that's fine.
List<Empresa> y = JsonSerializer.Deserialize<List<Empresa>>(x); // Here, it finds the objects, but all of them with null values.
}
}
The X value: '[{"id":1,"nomeEmpresa":"Alamo","cnpj":"00072619000101","dataCadastro":"2020-01-01T00:00:00","colaborador":[],"marca":[]}]'
The Y value: Y value after Deserialization
According to the json return data you provided, I did the following restoration and successfully returned the data, you can refer to it.
Model:
public class TestModel
{
public int id { get; set; }
public string nomeEmpresa { get; set; }
public string cnpj { get; set; }
public string dataCadastro { get; set; }
public List<colaborador> colaborador { get; set; }
public List<marca> marca { get; set; }
}
public class colaborador
{
public int id { get; set; }
public string test { get; set; }
}
public class marca
{
public int id { get; set; }
public string test { get; set; }
}
Then I gave values to individual attributes, and the results are as follows:
I am working on a new ASP.NET Core 3.1.1 API with Microsoft.AspNetCore.OData v 7.3.0, AutoMapper v9.0.0 and Microsoft.AspNetCore.Mvc.NewtonsoftJson v3.1.1
I am getting the following error when I make a POST to the Accounts endpoint using Postman v7.18.0;
AutoMapper.AutoMapperMappingException: Missing type map configuration or unsupported mapping.
I have reviewed the similar questions list when creating this question but was unable to find a solution.
In reviewing google searches for AutoMapper OData Enums all I could find were the recommendation to decorate my dto class with...
[AutoMap(typeof(Account))]
... and to decorate my dto enum properties with ...
[JsonConverter(typeof(StringEnumConverter))]
However, I still get the error. I found references to using an AutoMapperProfile class with a mapper defined as
CreateMap<Account, AccountModel>().ReverseMap();
But it appears that AutoMapper v9.0.0 no longer has a CreateMap method. My understanding was that adding the [AutoMap(typeof(Account))] to the dto class had the same effect as creating the map in the profile class.
I feel like I am going in circles at this point here so I though I would reach out to the SO community. I am sure it is something simple, I am just not seeing it.
Here is my POST request body from Postman;
{
"#odata.context": "https://localhost:44367/v1/$metadata#Accounts",
"AccountName": "Test Provider",
"AccountType": "Provider",
"IsTaxExempt": false,
"Status": "Active"
}
Here is my AccountsController Post method;
[ODataRoute]
[Produces("application/json;odata.metadata=minimal")]
[ProducesResponseType(typeof(AccountModel), Status201Created)]
[ProducesResponseType(Status400BadRequest)]
public async Task<IActionResult> Post([FromBody] AccountModel record)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
record.Id = new Guid();
var entity = _mapper.Map<Account>(record);
_context.Add(entity);
await _context.SaveChangesAsync();
var createdRecord = _mapper.Map<AccountModel>(entity);
return Created(createdRecord);
}
Here is my Account entity class;
public class Account : EntityBase
{
[Required]
[Column(TypeName = "nvarchar(50)")]
[MaxLength(50)]
public string AccountName { get; set; }
public AccountTypes AccountType { get; set; }
public bool IsTaxExempt { get; set; }
}
Here is the EntityBase class;
public class EntityBase
{
[Required]
public Guid Id { get; set; }
public DateTimeOffset? DateTimeCreated { get; set; } = DateTime.UtcNow;
public DateTimeOffset? DateTimeLastModified { get; set; }
[JsonConverter(typeof(StringEnumConverter))]
public StatusTypes Status { get; set; }
public bool DeleteFlag { get; set; }
}
Here is my Account DTO class;
[Filter, Count, Expand, OrderBy, Page, Select]
[AutoMap(typeof(Account))]
public class AccountModel : BaseModel
{
[Required]
[MaxLength(50)]
public string AccountName { get; set; }
[JsonConverter(typeof(StringEnumConverter))]
public AccountTypes AccountType { get; set; }
public bool IsTaxExempt { get; set; }
}
Here is my BaseModel class;
[Select, Filter]
public class BaseModel
{
public Guid Id { get; set; }
public DateTimeOffset DateTimeCreated { get; set; } = DateTime.UtcNow;
public DateTimeOffset DateTimeLastModified { get; set; }
[JsonConverter(typeof(StringEnumConverter))]
public StatusTypes Status { get; set; }
public bool DeleteFlag { get; set; }
}
And here are my Enums for AccountTypes and StatusTypes
public enum AccountTypes
{
Customer = 0,
Reseller = 1,
Provider = 2,
}
public enum StatusTypes
{
Active = 0,
Inactive = 1,
}
Any ideas?
It turns out that I needed to create an instance of an AutoMapper MapperConfiguration and assign it to the mapper.
I ended up putting in in the constructor of the Controller, for example;
public AccountsController(CdContext context, IMapper mapper)
{
_context = context ?? throw new ArgumentNullException(nameof(context));
_mapper = mapper ?? throw new ArgumentNullException(nameof(mapper));
var config = new MapperConfiguration(cfg => cfg.CreateMap<Account, AccountModel>().ReverseMap());
_mapper = new Mapper(config);
}
After I did this, everything worked as expected.
Here is a link to AutoMappers docs on the subject.
I am trying to call a 3rd party web service
Their REST API uses the following URL style.
http://www.VoiceBase.com/services?version=1.0&apikey=your-apikey&password=secret&action=list&status=processing
All of their service calls go to the same /services
How do I create a class so the following would work?
var client = new JsonServiceClient("http://www.voicebase.com");
var response = client.Get<ResponseVoiceBaseListClass>(new VoiceBaseListClass());
Additional classes I have created but I am not quite there yet
public class VoiceBaseBaseClass
{
public string version { get; set; }
public string apikey { get; set; }
public string password { get; set; }
public VoiceBaseBaseClass()
{
this.version = "1.0";
this.apikey = "API";
this.password = "password";
}
}
public class VoiceBaseListClass : VoiceBaseBaseClass, IReturn<ResponseVoiceBaseListClass>
{
public string action { get; set; }
public string status { get; set; }
public VoiceBaseListClass()
: base()
{
this.action = "list";
this.status = "processing";
}
}
public class ResponseVoiceBaseListClass
{
public string requestStatus { get; set; }
public string statusMessage { get; set; }
public string fileStatus { get; set; }
public List<string> mediaIds { get; set; }
public ResponseVoiceBaseListClass()
{
this.mediaIds = new List<string>();
}
}
Using the above classes the call that goes to the server is
/json/syncreply/VoiceBaseListClass?action=list&status=processing&version=1.0&apikey=API&Password=password
Is there a way I can force the service stack client to go to the
/Services
instead of
/json/syncreply/VoiceBaseListClass
I found a way to do this and it is working great for me.
[RestService("/services", "GET")]
public class VoiceBaseListClass : VoiceBaseBaseClass, IReturn<ResponseVoiceBaseListClass>
{
}
Although this is a deprecated attribute - the new attribute is called Route
https://github.com/ServiceStack/ServiceStack/wiki/Release-Notes
Chris
I have and entity framework project exposed via a data service:
public class VersionContext : DbContext
{
public DbSet<VersionTreeEntry> VersionTreeEntries { get; set; }
public DbSet<PluginState> PluginStates { get; set; }
public static void SetForUpdates()
{
Database.SetInitializer(new MigrateDatabaseToLatestVersion<VersionContext, Configuration>());
}
}
public class VersionTreeEntry
{
public VersionTreeEntry()
{
Children = new List<VersionTreeEntry>();
PluginStates = new List<PluginState>();
}
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public long Id { get; set; }
public virtual ICollection<VersionTreeEntry> Children { get; set; }
public virtual ICollection<PluginState> PluginStates { get; set; }
public virtual VersionTreeEntry Ancestor { get; set; }
/// <summary>
/// Links to the ProtoBufDataItem Id for the session state.
/// </summary>
public int DataId { get; set; }
public string Notes { get; set; }
[Required]
public DateTime TimeStamp { get; set; }
[MinLength(1, ErrorMessage = "Tag cannot have a zero length")]
[MaxLength(20, ErrorMessage = "A tag name cannot contain over 20 characters")]
public string Tag { get; set; }
public bool IsUiNodeExpanded { get; set; }
[Required]
public string Version { get; set; }
[Required]
public string SessionName { get; set; }
}
public class PluginState
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public long Id { get; set; }
public string PluginName { get; set; }
[Required]
public byte[] Data { get; set; }
}
As far as I can see, the data classes are defined correctly. I try to create some new objects and add them into the context, with their relations intact:
var session = new Session();
session.SessionName = "My new session";
VersionTreeEntry versionTreeEntry = new VersionTreeEntry();
versionTreeEntry.SessionName = session.SessionName;
versionTreeEntry.Version = Assembly.GetExecutingAssembly().GetName().Version.ToString();
versionTreeEntry.TimeStamp = DateTime.Now;
_versionContext.AddToVersionTreeEntries(versionTreeEntry);
foreach (var plugin in session.Plugins)
{
using (var ms = new MemoryStream())
{
plugin.SaveState(ms);
PluginState state = new PluginState();
state.PluginName = plugin.PluginName;
state.Data = ms.ToArray();
versionTreeEntry.PluginStates.Add(state);
}
}
_versionContext.SaveChanges();
The problem is that the PluginState instances never actually get added to the database. If I add code to add them manually to the context, they do get added, but the foreign key pointing back to the VersionTreeEntry is null.
Again, this is a WCF DataService rather than vanilla EF, any idea what might be wrong?
Cheers
Posting the answer here from the comment section.
Agreed. The best way to do this is to call the following API:
_versionContext.AddRelatedObject(versionTreeEntry, "PluginStates", state);
Thanks
Pratik