I have this controller in my asp.net-core web-api project:
[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase
{
[HttpGet]
public ActionResult<List<ActualModel>> Get()
{
return new List<ActualModel>()
{
new ActualModel { StringValue1 = "SomeString" },
new ActualModel { StringValue1 = "MoreString" }
};
}
}
The only method in this controller returns a list of "ActualModel". The definition of it looks like this:
public class ActualModel
{
public string StringValue1 { get; set; }
}
And my swashbuckle generated Swagger-UI shows it like this:
But I would like the Swashbuckle to generate the Swagger-Specification so that it not contains the "ActualModel" but the following model instead:
public class OtherModel
{
public string StringValue2 { get; set; }
public int IntValue1 { get; set; }
}
I've noticed there is a function that I think I can use for this:
services.AddSwaggerGen(c =>
{
c.MapType<ActualModel>(() => new OpenApiSchema { Type = "string" });
But I can't figure out how I get the "OtherModel" to be generated into an OpenApiSchema so that it replaces the "ActualModel" nor can I find information or examples about it.
I tried giving it the full class name with namespace and without the namespace, I googled for "Swashbuckle replace Type with other Type" and "Swashbuckle MapType" but I cant find any examples which show how to simply replace one type with another type that is defined in the application.
Background: The above is just an example of course. In a "real" project I have generic and hierarchical class-definitions that I want to return in my controller but I have a Serializer in between the value the controller returns and the actual network output which changes the data structure to another, more simple structure for use on a Typescript side. That's why I want to replace the swashbuckle generated model definitions.
Well, you could just tell swagger what you are producing if for example it's not obvious because of templates or a hierarchy:
[HttpGet]
[ProducesResponseType(typeof(OtherModel[]), 200)] // <--- This line
public ActionResult<List<ActualModel>> Get()
{
return new List<ActualModel>()
{
new ActualModel { StringValue1 = "SomeString" },
new ActualModel { StringValue1 = "MoreString" }
};
}
However, it's your job to make sure they are actually compatible, there are no checks.
Related
I am trying to implement a controller method inside of NopCommerce under ASP.NET Core 3.1 that looks like this:
[HttpPost]
[Route("CustomerSchool/Update")]
public IActionResult Update(CustomerSchoolMappingModel model)
{
}
my model looks like this:
public class CustomerSchoolMappingModel : BaseNopModel
{
public int CustomerId { get; set; }
public List<int> SchoolIds { get; set; } = new List<int>();
}
When I submit a http POST with the following form data:
CustomerId: 1
SchoolIds[0]: 2
SchoolIds[1]: 3
My properties are successfully binding but not the SchoolIds property collection which is always returning empty.
I suspect that NopCommerce has replaced the default modelbinders (which I believe support this functionality by default in ASP.NET) but I am not 100% sure how to correctly replace/implement this functionality.
I believe using a custom IModelBinderProvider to fix this issue could work but I am wondering what is the correct/best way to fix this problem and ensure I can correctly bind generic lists using the name[x] form schema in my ASP.NET Core application?
I think you are missing [FromForm] attribute on method argument in the controller and the argument name in your request. I've never used NopCommerce. Here are correct collection formats when sending form data: https://learn.microsoft.com/en-us/aspnet/core/mvc/models/model-binding?view=aspnetcore-3.1#collections. This request's body should work:
model.CustomerId=1&model.SchoolIds[]=12&model.SchoolIds[]=321
This code works fine for me without any custom model binders:
public class FormDto
{
public int Id { get; set; }
public List<int> ListOfIds { get; set; }
}
[Route("api/[controller]")]
[ApiController]
public class FormController : ControllerBase
{
[HttpPost]
[Route("post")]
public ActionResult Post([FromForm] FormDto dto)
{
Console.WriteLine(JsonConvert.SerializeObject(dto, Formatting.Indented));
return Ok();
}
}
I tested it with Invoke-WebRequest in Powershell:
iwr -uri http://localhost:5000/api/form/post -Method Post -Body 'dto.Id=13&dto.ListOfIds[]=1&dto.ListOfIds[]=2&dto.ListOfIds[]=34' -ContentType 'application/x-www-form-urlencoded'
Output in console:
{
"Id": 13,
"ListOfIds": [
1,
2,
34
]
}
So for anyone interested it seems to boil down to Nop not liking using Generic lists.
I reviewed the default binders used by Nop from here and
I was able to successfully resolve my problem by changing my model to this:
public class CustomerSchoolMappingModel : BaseNopModel
{
public int CustomerId { get; set; }
public ICollection<int> SchoolIds { get; set; } = new List<int>();
}
I'm trying to construct a request body for a REST api call, and I need to create a JSON object with the list of properties I want to get back.
For eg: I have this C# object that I want to get back:
public class SomeProperties
{
public string TicketNumber { get; set; }
public Driver Driver { get; set; }
}
public class Driver
{
public string Name { get; set; }
}
To get this back, I need to put these properties in a JSON request body like this:
"properties": [
"ticketNumber",
"driver.name"
]
My attempt looks like this:
private string FetchProperties()
{
var fetchProperties = new
{
properties = new List<string>
{
"ticketNumber",
"driver.name"
}
};
var jsonResult = JsonConvert.SerializeObject(fetchProperties, Formatting.None);
return jsonResult;
}
But I don't want to hard code the properties like that.
So is there any way I can use property names from the object I want, to put in the list of strings that I made in the method above?
Thank You!
If I understand correctly,you need Metadata of model.
if you use EntityFramework, you can get metadata of your model
from this Code
and call BuildJsonMetadata() function
and if you use other mapper, I dont see any exist tool for generate metadata of model and you must generate it handly
somthing like this
First of, if you serialize the class you have (SomeProperties), you will not get driver.name. Instead you will get a string like this one that shows driver as an object,
{
properties : {
"ticketNumber" : "stringvalue",
"driver" : {
"name" : "stringValue"
}
}
}
That said, if you are interested in getting a json like this,
"properties": [
"ticketNumber",
"driver.name"
]
you will need a class (very simple one at that) that contains only a list of strings. properties is not an array of objects, but simply strings. From the looks of the FetchProperties method, you are creating an object with fetchProperties as the RootObject. Try something like this,
public class MyClass
{
[JsonProperty("fetchProperties")]
public Fetch FetchProperties { get; set; }
}
public class Fetch
{
[JsonProperty("properties")]
public List<string> Properties { get; set; }
}
private string FetchProperties()
{
MyClass obj = new MyClass()
{
FetchProperties = new Fetch()
{
Properties = new List<string>() { "ticketNumber", "driver.Name" }
}
};
return JsonConvert.SerializeObject(obj); // Formatting.None is by default
}
Now its your choice to hard code these values or, pass them as arguments or use a local variable that contains a list of all the strings you intend to store as "properties". You cant use enums because of violation in naming convention (driver.name) so these options should suffice.
Sorry if this has been asked an answered a million times already, but I can't seem to find a solution on Google or SO.
In ASP.NET Core, is it possible to write a custom model binder or some such, that will allow me to have some inheritance support in a web API?
Basically I have a class like:
public class Order {
public Guid Id { get; set; }
public PaymentContainer PaymentParameters { get; set; }
}
And a container class like
public class PaymentContainer
{
public string Type { get; set; }
public IPaymentParameters Value { get; set; }
}
And some classes that implement the IPaymentParameters interface, and a controller method such as:
[HttpPost]
public IActionResult CreateOrder([FromBody]Order order)
{
}
I would very much like the client to be able to send in json such as:
{
"id" : "1234-..-1234",
"paymentParameters" : {
"type": "SpecialParameters1",
"value" : { /* instance of specialparameters1 here */ }
}
}
And then have the "value" property be an instance of "SpecialParameters1" once it arrives in the controller method.
I would think it is possible to write a modelbinder, but i can't quite wrap my head around how it would be done.
Note: I am aware of the approach where one can change the Json.Net TypeNameHandling, but I would rather NOT mess with all the other stuff that relies on the json serializer, and just setting it to Auto or All will open up some avenues for remote code execution.
Clarification: A small update after the first answer
The goal is to have multiple instance of the parameters, such that the following input also "works"
{
"id" : "1234-..-1234",
"paymentParameters" : {
"type": "SpecialParameters2",
"value" : { /* instance of specialparameters2 here */ }
}
}
And of course I would have classes
public class SpecialParameters1 : IPaymentParameters{}
public class SpecialParameters2 : IPaymentParameters{}
Yes, it ASP.NET Core is quite good at model binding, I will setup example for you (just had refactored a little your classes - not sure in details what you have to do, but as a demonstration of a principle I think it will suit well)
In View you setup something like this:
<h2>orders with ajax</h2>
<dl>
<dt>Order Id:</dt>
<dd id="order-id">D3BCDEEE-AE26-4B20-9170-D1CA01B52CD4</dd>
<dt>Payment container - Type</dt>
<dd id="order-paymentcontainer-type">Card payment</dd>
<dt>Payment Parameters - Name</dt>
<dd id="order-paymentcontainer-paymentParameters-name">Card payment nr. 1</dd>
<dt>Payment Parameters - Provider</dt>
<dd id="order-paymentcontainer-paymentParameters-provider">Big Bank</dd>
</dl>
<a id="submit-button">Process order</a>
#section Scripts {
<script src="~/js/paymentTest.js"></script>
}
Then you setup classes:
public class Order
{
public Guid Id { get; set; }
public PaymentContainer PaymentContainer { get; set; }
}
public class PaymentContainer
{
public string Type { get; set; }
public PaymentParameters PaymentParameters { get; set; }
}
public class PaymentParameters
{
public string Name { get; set; }
public string Provider { get; set; }
}
Then write in your paymentTest.cs
var order = {
Id: "",
PaymentContainer: {
Type: "",
PaymentParameters: {
Name: "",
Provider: ""
}
}
};
order.Id = document.getElementById("order-id").innerHTML;
order.PaymentContainer.Type = document.getElementById("order-paymentcontainer-type").innerHTML;
order.PaymentContainer.PaymentParameters.Name = document.getElementById("order-paymentcontainer-paymentParameters-name").innerHTML;
order.PaymentContainer.PaymentParameters.Provider = document.getElementById("order-paymentcontainer-paymentParameters-provider").innerHTML;
$("#submit-button").click(function () {
$.ajax({
url: 'api/orders',
type: 'POST',
dataType: "json",
contentType: 'application/json',
data: JSON.stringify(order),
success: function (data) {
location.reload();
}
});
});
And finally in controller setup method for binding
[Route("api/orders")]
[HttpPost]
public async Task ProcessOrder([FromBody] Order order)
{
var orders = new List<Order>();
orders.Add(order);
// and some other code ...
}
Please correct me If I'm wrong - the main problem here is that you cant bind data from View returned to the controller?
You can't bind because your Order class contains interface (indirectly), and MVC can't bind interfaces.
That's O.K. since interfaces abstract behaviors not data.
Potential workaround for this problem is to use OrderViewModel instead of Order type in CreateOrder method.
i.e.:
public IActionResult CreateOrder([FromBody]OrderViewModel order)
Then OrderViewModel will have PaymentContainerViewModel instead of PaymentContainer,
and it would have concrete PaymentParameters Value
instead of interface IPaymentParameters Value, and than binding is good to go.
I am writing an application where I do need to handle some scenarios beforehand in my controller class like certain property must have been provided otherwise status code would be BadRequest. Here is my class lookalike.
public class MyClass
{
[Required]
public IEnumerable<NewObject> NewObjects { get; set; }
}
public class NewObject : INewObject
{
public NewObject(string typeName, IEnumerable<Property> properties)
{
TypeName = typeName;
Properties = properties;
}
[JsonProperty(Required = Required.Always)]
public string TypeName { get; }
public IEnumerable<IProperty> Properties { get; }
}
public interface IProperty
{
string Name { get; }
object Value { get; }
}
Now though I have marked TypeName as required property and if I do not pass that in json content while sending request from swagger, json deserialization doesn't fail. I tried to search but I got an answer that setting Required to Always should work.
Below is the Json Content I am passing through swagger:
{
"NewObjects": [
{
"Properties": [
{
"Name": "string",
"Value": ''
}
]
}
]
}
I wrote below piece of code too by looking at one of the solution:
var config = new HttpConfiguration();
var jsonFormatter = config.Formatters.JsonFormatter;
jsonFormatter.SerializerSettings.MissingMemberHandling = MissingMemberHandling.Error;
config.MapHttpAttributeRoutes();
Still it's not working:
Note: I am using Newtonsoft.Json version 11.0.1
This seems to be swagger issue because when I serialize input C# object and when again deserialize it, I am getting proper error.
For example in my controller class if I say:
var input2 = JsonConvert.DeserializeObject<MyClass>(JsonConvert.SerializeObject(input))
Then input2 throws an exception.
You can take a look at FluentValidation. If I am not mistaken it is designed to validate data in jsons forms specifically.
using FluentValidation;
public CertainActionValidator()
{
RuleFor(x => x.PropertyName).NotEmpty()
}
You can add plenty of additional conditions in there.
I have a simple JavaScript string and object:
var name = "Scarlett Johansson";
var args = { arg1: "foo", arg2: "bar" };
And I want to pass them via $.ajax to a Web API controller:
public string Get([FromUri]TestClass input) {
// would like Args model-bound
}
And my TestClass is:
public class TestClass
{
public string Name { get; set; }
public Dictionary<string, string> Args { get; set; }
}
The Name property is bound as expected, but I haven't found a way to bind Args. I've tried JSON.stringify(args), $.param(args), using a List<KeyValuePair<string,string>> on TestClass instead of Dictionary, nothing has worked.
I was hoping I could achieve this via model binding instead of manually de-serializing the JSON. Is this possible?
Clarification: the number of keys/values would vary in "args" from call to call, hence my need for a Dictionary.
the default model binding wont work like that, it attempts to bind to public properties on objects. in this example, you would need a class containing like :
public class ArgClass
{
public string Arg1 { get; set; }
public string Arg2 { get; set; }
}
public class TestClass
{
public string Name { get; set; }
public List<ArgClass> Args { get; set; }
}
the alternative, which seems like you would want to do, is write a custom model binder, or a quick google search turns up this DefaultDictionaryBinder someone seems to have implemented already
https://github.com/loune/MVCStuff/blob/master/Extensions/DefaultDictionaryBinder.cs
Update: just realized you are using web api, which is i guess slightly different. Here's a blog post explaining how the binding works for web api: http://blogs.msdn.com/b/jmstall/archive/2012/04/16/how-webapi-does-parameter-binding.aspx
Let's extend your method with implementation (to see the result of what we've passed) like this:
public HttpResponseMessage Get([FromUri]TestClass input)
{
return Request.CreateResponse<TestClass>(HttpStatusCode.OK, input);
}
And if we would like to see this:
{
"Name":"MyName",
"Args":
{
"FirstKey":"FirstValue",
"SecondKey":"SecondValue"
}
}
Other words the testClass.Name == "MyName" and testClass.Args["FirstKey"] == "FirstValue"... we can call the API like this:
api/MyService/?name=MyName&args[0].key=FirstKey&args[0].value=FirstValue&args[1].key=SecondKey&args[1].value=SecondValue
The params on separated lines, just for clarity (URI will be without line breaks!):
api/MyService/
?name=MyName
&args[0].key=FirstKey
&args[0].value=FirstValue
&args[1].key=SecondKey
&args[1].value=SecondValue