I am using .netcore to create a simple REST API and have used the swashbuckle package (swashbuckle.aspnetcore\1.1.0) so that I can devtest it easily.
ConfigureServices:
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new Info
{
Title = "Meetings Calculator API",
Version = "v1",
Description = "An API to provide pricing information related to journeys",
});
});
Configure
app.UseSwagger();
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API v1");
});
All is well except for a post call I have developed. It appears that the dictionary I have defined to be passed in the body is not served over to the service correctly.
Method Signature
[HttpPost("CalculateCost", Name = "CalculateCost")]
public IActionResult getJourneyCalculation([FromBody] IDictionary<String, String> locations)
Generated json config
"parameters":[
{
"name":"locations",
"in":"body",
"required":false,
"schema":{
"type":"object",
"additionalProperties":{
"type":"string"
}
}
}
],
I think that is the point where the dictionary is supposed to be so I think the config side of this is not working correctly. Does anyone know what I need to do to get this to pick up the IDictionary correctly?
I came across a post that discussed this which stated that there was a bug (or at least inconsitency) with the spelling causing problems "s" v "S" in String but I have tried both with no change. I think this was also old and quoted as fixed. I also tried a few suggestions altering the UseSwaggerUI endpoint but all that did for me was to leave me with a blank screen.
[UPDATE]
I seem to be getting just one entry from the dictionary so for instance if I use the intput:
{
"2":"London",
"2":"Bristol",
"2":"London",
"2":"Newcastle"
}
the last entry will make it to the controller.
[UPDATE]
I have tried it with and it will work intermittently:
with arguments ([""]) - ModelState is Invalid
with arguments (["System.Collections.Generic.Dictionary`2[System.String,System.Int32]"]) - ModelState is Valid
with the same inputs:
{
"London":2,
"Bristol:2",
"London:2",
"Newcastle":2
}
Intermittent is a bit of a claim as it worked twice and not since.
Ok I have found my way around this by defining my own class to hold the two values instead.
public class FromLocation
{
[Required]
public string Location { get; set; }
[Required]
public int Attendees { get; set; }
}
Changed the controllers signature to:
public IActionResult getJourneyCalculation([FromBody] IList<FromLocation> locations)
now the yellow schema box lists the type and I can send the information over as required. Not sure what was going on with the dictionary but life is too short.
Related
I am trying to set up dependency injection in a small blazor-based web app.
My current code is:
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddRazorPages();
builder.Services.AddServerSideBlazor();
AddDeliveryStats(builder.Services, builder.Configuration);
// ...blazor template things...
void AddDeliveryStats(IServiceCollection services, ConfigurationManager config)
{
services.Configure<BigQuerySettings>(config.GetSection("BigQuery"));
services.AddTransient<IBigQueryClient, BigQueryClient>();
// ...other stuff not pertinent to the error...
}
where BigQuerySettings is given as
public class BigQuerySettings
{
public string ProjectId { get; set; }
public string DataSetId { get; set; }
public string AuthFilePath { get; set; }
}
and BigQueryClient has the following constructor:
public BigQueryClient(
BigQuerySettings bigQuerySettings,
ILogger<BigQueryClient> logger) { /* ... */ }
and my appsettings.json contains the following:
{
// ...
"BigQuery": {
"ProjectId": "<project-identifier>",
"DataSetId": "",
"AuthFilePath": "BigQueryAuthProd.json"
}
}
and if this looks pretty much like a tutorial example, that's because it basically is. It does not work and it is not obvious why. I get the following error:
Some services are not able to be constructed
(Error while validating the service descriptor 'ServiceType: IBigQueryClient Lifetime: Transient ImplementationType: BigQueryClient': Unable to resolve service for type 'BigQuerySettings' while attempting to activate 'BigQueryClient'.)
I have copied this code from online tutorial examples and adapted it as appropriate to my own classes, and I have read every piece of documentation I have been able to find (much of which I can't understand) and googled at least ten different permutations of the keywords in the error message. Nothing really points to what I am doing wrong.
By default, a call to services.Configure will only allow injecting an IOption<BigQuerySettings> into your consumers.
If, however, you wish to inject BigQuerySettings directly into your consumer (which I would argue you should), you should do the following:
BigQuerySettings settings =
Configuration.GetSection("BigQuery").Get<BigQuerySettings>();
// TODO: Verify settings here (if required)
// Register BigQuerySettings as singleton in the container.
services.AddSingleton<BigQuerySettings>(settings);
This allows BigQuerySettings to be injected into BigQueryClient.
In my Net 6 Web-API project I'm using OData and Swagger (it was added automatically when project was created).
It works out of the box, but there is an issue with some URLs generated by Swagger.
Here is my OData controller:
public class UsersController : ODataController
{
// skipped for brevity
[EnableQuery]
public IActionResult Get()
{
return Ok(_dbContextRepo.Select<DbModel.User>());
}
[EnableQuery]
public SingleResult<User> Get([FromODataUri] int key)
{
return SingleResult.Create(_dbContextRepo.Select<User>().Where(u => u.Id == key));
}
}
SwaggerUI output:
When I try to run query that gets entity by Id from Swagger it fails because of wrong url.
By some reason Swagger generates query parameter and URL like on picture above. For OData URL has to be like this (path parameter, https://swagger.io/docs/specification/describing-parameters/):
https://localhost:7250/api/Users/1
In swagger.json parameter described as
Spent all day trying to figure this out. Will appreciate any help.
Found solution myself. I'm using OData 8 and it seems there is no need to mark method parameter as [FromODataUri].
[EnableQuery]
public SingleResult<User> Get(int key)
{
//...
}
Whithout it Swagger generates correct links.
I can't see what's missing here.
I have a model that looks like this:
public class ModelDto
{
public string X { get; set; }
// ...
public IList<IFormFile> Attachments { get; set; }
}
It is used for a POST endpoint in Web Api, like this:
[HttpPost()]
public async Task<ActionResult<ResponseModel>> PostEndpoint([FromForm] ModelDto modelDto)
{
// ...
}
When I POST with Postman, with Form-Data, and fields, including Attachment[0] as a file, I receive a model, complete with all other fields, but without any file. But I do receive the file in the request. If I look at this.Request.Form.Files, it's there, but it was not loaded as part of the model.
So, obviously, I can manually re-attach the list of files to the appropriate part of the model. But why doesn't the binding work?
Thanks
Well, I finally got it, so the problem was on the Postman side of how I wrote the request.
For arrays of text keys, the [] notation is used. It must not be used for arrays of files.
So instead of sending Attachments[0] or Attachments[], I just had to send Attachments as the key to each file. Then the binding works fine.
Topic needs to configrable via config file.
Below is the code. I wonder if this is the best solution or any built-in support, or better solution?
public class TopicNameConvention : ITopicNameConvention
{
private readonly Config _config;
public TopicNameConvention(Config config)
{
_config= config;
}
public string GetTopic(Type eventType)
{
switch (eventType.Name)
{
case nameof(Order):
return _config.Topic.Name;
//... more
}
throw new Exception("Topic name not exist");
}
}
If you intention is to manually be able to configure the topic name for each of your event types, then please go for it.
My own personal opinions is that this seems pretty rigid, and I would fear that you would have to update too many things at the same time, whenever you introduce a new event type.
Why not just go with
public string GetTopic(Type eventType) => eventType.Name;
which can handle all .NET types in the world (just be sure that you do not have two event types with the same name in two different namespaces).
I have a PUT method in web api which accepts a JSON data and a route data as follows.
[Route("api/v1/Orders/{orderId}/active")]
public HttpResponseMessage Put(Guid? orderId,List<ActiveRequest> activeRequests)
{
}
public class ActiveRequest
{
public int Id { get; set; }
public bool IsActive { get; set; }
}
Now is it possible to simplify the method signature as:
[Route("api/v1/Orders/{orderId}/active")]
public HttpResponseMessage Put(ActiveRequestModel model)
{
}
public class ActiveRequestModel
{
public Guid OrderId { get; set; }
public List<ActiveRequest> ActiveRequests {get; set;}
}
I tried writing a custom ModelBinder by implementing the System.Web.Http.ModelBinding.IModelBinder interface but could'nt find a way to read the JSON data that is coming inside the Request object.
I doubt that is there a way by which I can bind my model with data coming from three different places i.e. from route data, json & form.
You cannot simplify the parameter as described.
Unlike MVC model binding, beacuse of how the Web API formatter works, in Web API you only can have a single parameter that is deserialized from the payload, and a number of simple type parameters coming from route parameters or url query string. The reason is that the creation of the parameter coming from the payload is done in a single pass deserialization of the payload.
So, for your example you need the two parameters in your original version, i.e.:
public HttpResponseMessage Put(Guid? orderId, List<ActiveRequest> activeRequests)
If you want to use the ActiveRequestModel you need to include a payload which has exactly the same structure, so you should include the orderId in the payload, because it will not be recovered from the url (even if the name matches).
Please, read this article which explains how parameter binding works in Web API:
Parameter Binding in ASP.NET Web API
If you read it thoroughly you'll see that you can create and register your own model binder to make it work the same way that an MVC controller, but I think it's not worth the effort (so I include it only in this last paragraph), and it's not the standard way of working.