I have a ASP.NET Web API which returns a template class but I can't get the Web API Help Page to provide documentation for the return type correctly.
Let's say I have the following Model classes:
public class MyType<T>
{
/// <summary>A list of T</summary>
public List<T> MyList { get; set; }
}
public class Foo
{
/// <summary>Bar string</summary>
public string Bar { get; set; }
}
and my API action looks as follows
[ResponseType(typeof(MyType<Foo>))]
public IHttpActionResult Get()
{
return Ok<MyType<Foo>>(new MyType<Foo>());
}
The resulting Web API Help Page then declares that the Get action returns a MyTypeOfFoo and do not provide the XML documentation for MyType, it just lists the parameters it contains. Probably because it don't understand that MyTypeOfFoo is the same as MyType<Foo>.
Are there any known solutions to this problem?
Update
Creating a pseudo-class and returning it instead does not work either. E.g.
/// <summary>My Foo Type</summary>
public class MyFooType : MyType<Foo>
{
}
[ResponseType(typeof(MyFooType)]
public IHttpActionResult Get()
{
return Ok<MyFooType>(new MyFooType());
}
The documentation output for the above code lacks the comments available on the inherited properties.
Related
Im using
<PackageReference Include="NSwag.AspNetCore" Version="13.8.2" />
In an asp.net core web api 3.1 project, and Im interested in having sample request payloads display within the swaggerUI.
How exactly can I add sample requests when using this framework? Is this even possible with this framework? My best guess is that adding attributes to controller actions. Any insight is appreciated here. Perhaps the answer for this is a simple annotation, which I am hoping for.
Do you mean you want to add sample data to action?If so,you can use:
/// <example>xxx</example>
Here is a demo:
Sample.cs:
public class Sample
{
/// <example>1</example>
public int Id { get; set; }
/// <example>name</example>
public string Name { get; set; }
/// <example>address</example>
public string Address { get; set; }
}
Controller:
[ApiController]
[Route("[controller]")]
public class ApiController : ControllerBase
{
[HttpPost("Index")]
public Sample Index(Sample sample)
{
return sample;
}
}
result:
I have this API
public ActionResult AddDocument([FromBody]AddDocumentRequestModel documentRequestModel)
{
AddDocumentStatus documentState = _documentService.AddDocument(documentRequestModel, DocumentType.OutgoingPosShipment);
if (documentState.IsSuccess)
return Ok();
return BadRequest();
}
And this is my request model
public class AddDocumentRequestModel
{
public AddDocumentRequestModel(int partnerId, List<ProductRequestModel> products)
{
PartnerId = partnerId;
Products = products;
}
[Range(1, int.MaxValue, ErrorMessage = "Value for {0} must be between {1} and {2}.")]
public int PartnerId { get; private set; }
[Required, MustHaveOneElement(ErrorMessage = "At least one product is required")]
public List<ProductRequestModel> Products { get; private set; }
}
so when I'm trying to hit the API with this body
{
"partnerId": 101,
"products": [{
"productId": 100,
"unitOfMeasureId": 102,
"quantity":5
}
]
}
this is the request : System.NotSupportedException: Deserialization of reference types without parameterless constructor is not supported. Type 'Alati.Commerce.Sync.Api.Controllers.AddDocumentRequestModel'
I don't need parameterless constructor,because it doesn't read the body parameters.Is there any other way for deserialization?
You can achieve your desired result. You need to switch to NewtonsoftJson serialization (from package Microsoft.AspNetCore.Mvc.NewtonsoftJson)
Call this in Startup.cs in the ConfigureServices method:
services.AddControllers().AddNewtonsoftJson();
After this, your constructor will be called by deserialization.
Extra info: I am using ASP Net Core 3.1
Later Edit: I wanted to give more info on this, as it seems that this can also be achieved by using System.Text.Json, although custom implementation is necessary. The answer from jawa states that Deserializing to immutable classes and structs can be achieved with System.Text.Json, by creating a custom converter (inherit from JsonConverter) and registering it to the converters collection (JsonSerializerOptions.Converters) like so:
public class ImmutablePointConverter : JsonConverter<ImmutablePoint>
{
...
}
and then...
var serializeOptions = new JsonSerializerOptions();
serializeOptions.Converters.Add(new ImmutablePointConverter());
serializeOptions.WriteIndented = true;
Just in case someone have the same issue I had, I was using abstract class, once removed the abstract key word, it all worked just fine.
Just Add [JsonConstructor] before your constructor
like this
public class Person
{
public string Name { get; set; }
public int LuckyNumber { get; private set; }
[JsonConstructor]
public Person(int luckyNumber)
{
LuckyNumber = luckyNumber;
}
public Person() { }
}
There are still some limitations using System.Text.Json - have a look here: https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json-migrate-from-newtonsoft-how-to#table-of-differences-between-newtonsoftjson-and-systemtextjson
Deserialization without parameterless constructor using a parameterized constructor is not supported yet (but it's on their plan). You can implement your custom JsonConverter (like in this example: https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json-migrate-from-newtonsoft-how-to#deserialize-to-immutable-classes-and-structs) or - like Adrian Nasul above suggested: use Newtonsoft.Json and then you can use the [JsonConstructor] attribute
In my case I had set a class as internal and when I made it public it worked. The error message was really of little help with this specific circumstance.
Old (actual class name changed to ClassName in the example
internal class Rootobject
{
[JsonConstructor]
public Rootobject(ClassName className)
{
ClassName = className?? throw new ArgumentNullException(nameof(className));
}
public ClassName ClassName { get; set; }
}
New:
public class Rootobject
{
[JsonConstructor]
public Rootobject(ClassName className)
{
ClassName = branding ?? throw new ArgumentNullException(nameof(className));
}
public ClassName ClassName { get; set; }
}
In my case error, caused was inside InnerException. There is my class had a field with a custom class type that did not have a parameterless constructor. I've added a parameterless constructor to the inner class and the problem has gone away.
Not sure what is going on here.
I am exposing the Identity functionality through a Web API project. The CRUD aspect will be exposed to an admin app and the login, registration in a public facing app.
Right now I am just trying to return a list of all users in the database through a Web Api controller action. I am getting nothing output to the response, but I do get back data from the service:
/// <summary>
///
/// </summary>
/// <returns></returns>
[HttpGet]
[Route("")]
public async Task<IHttpActionResult> GetAllUsers()
{
var model = await _userService.GetAllUsers(); //<---Gets List<AppUser> here?
return Ok(model);
}
This action shows nothing on fiddler or Postman?
Any ideas?
public class AppUser : IdentityUser
{
public DateTime Created { get; set; }
}
Is there something special about the IdentityUser class that prevents it from being serialized?
Here is the web api serialization config:
config.Formatters.JsonFormatter.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
config.Formatters.Add(new JsonFormatter());
}
public class JsonFormatter : JsonMediaTypeFormatter
{
public JsonFormatter()
{
this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/html"));
this.SerializerSettings.Formatting = Formatting.Indented;
}
public override void SetDefaultContentHeaders(Type type, HttpContentHeaders headers, MediaTypeHeaderValue mediaType)
{
base.SetDefaultContentHeaders(type, headers, mediaType);
headers.ContentType = new MediaTypeHeaderValue("application/json");
}
}
Found my answer. The IdentityUser class is not really meant to be exposed over an API; lots of sensitive data and all.
However this is will sit behind a firewall and I do not feel like writing a DTO and mapper just to make this work.
The answer is explained here
Basically you just need to override the properties you want exposed and decorate them with a DataMember attribute for serialization.
ASP.NET MVC (or rather Html.Helpers and base page implementation) assumes that there will be one type for both rendering and posting (namely Model).
This is a violation of ISP, isn't it?
I am tempted to derive my Edit views (those that have different render-data, and post-data) from a custom EditPageBaseView<TViewModel, TFormData>.
The problem is I want my validation and post work against FormData instance (stored inside ViewModel), but MVC assumes that entire ViewModel will be POSTed back.
Is there an OOB way to facilitate that? (I didn't find one if there is).
Is it a bad idea (in concept) to have separate data types for different operations exposed by a service (a view in this case).
I tend to follow the CQRS model when constructing my view models. All rendering is done with ViewModel classes and all posting back is done with Command classes. Here's a contrived example. Let's say we have a View with a small form for creating users.
The ViewModel and Command classes looks like this:
public abstract class ViewModel {}
public abstract class Command: ViewModel
public class CreateUserViewModel : ViewModel
{
public string Username { get; set; }
public string Password { get; set; }
public string PasswordConfirm { get; set; }
}
public class CreateUserCommand : Command
{
public string Username { get; set; }
public string Password { get; set; }
public string PasswordConfirm { get; set; }
}
The UserController creates a CreateUserViewModel as the model for the Get request and expects a CreateUserCommand for the Post request:
public ActionResult CreateUser()
{
// this should be created by a factory of some sort that is injected in
var model = new CreateUserViewModel();
return View(model);
}
[HttpPost]
public ActionResult CreateUser(CreateUserCommand command)
{
// validate and then save the user, create new CreateUserViewModel and re-display the view if validation fails
}
Model binding takes care of ensuring that the properties of the Posted CreateUserCommand are populated properly, even though the Get View is bound to a CreateUserViewModel.
They don't have to match, but they do match by default.
If you don't want them to match, you can specify a different model in your Form or ActionLink:
Example of a Mismatch using Razor and C#:
Index.chtml:
#model FirstModel
<div>
#using (Html.BeginForm("Action", "ControllerName", new { ParameterName = new SecondModel { First = "First", Second = "Second" } }, FormMethod.Post)) {
<input type="submit" value="Submit Button" />
}
</div>
The Controller:
public class ControllerName : Controller {
public ActionResult Index() {
return View(new FirstModel());
}
public ActionResult Action(SecondModel ParameterName) {
return View() // Where to now?
}
The parameter request is always null using Web API. Am I missing something with using a strongly typed object as a parameter instead of simple types as the parameters.
Url
http://localhost:2222/api/v1/divisions?EventId=30
Controller Action
public virtual ApiDivisionsResponse Get(ApiDivisionsRequest request)
{
return _apiDivisionsService.GetDivisions(request);
}
Object
public class ApiDivisionsRequest : ApiAuthorizedRequest
{
public ApiDivisionsRequest()
{
Page = 1;
PageSize = 10;
}
public int EventId { get; set; }
public int PageSize { get; set; }
public int Page { get; set; }
public string[] Includes { get; set; }
}
I very strongly invite you to read the following article to better understand how parameter binding works in the Web API. After reading it you will understand that by default the Web API binds query string parameters to primitive types and request body content to complex types.
So if you need to bind query string parameters to complex types you will need to override this default behavior by decorating your parameter with the [FromUri] parameter:
public virtual ApiDivisionsResponse Get([FromUri] ApiDivisionsRequest request)
{
...
}
And yeah, I agree with you - that's a hell of a mess - model binding was so easy in plain ASP.NET MVC and they created a nightmare in the Web API. But once you know how it works you will avoid the gotchas.