Rebecca
Rebecca

Reputation: 14402

Swagger for ServiceStack POST empty body

I'm having some problems with the Swagger plugin to ServiceStack. I have configured the route descriptions for my service, but the resulting POST does not contain a body.

My Service looks like this:

/// <summary>
/// Define your ServiceStack web service request (i.e. Request DTO).
/// </summary>
/// <remarks>The route is defined here rather than in the AppHost.</remarks>
[Api("GET or DELETE a single movie by Id. Use POST to create a new Movie and PUT to update it")]
[Route("/movie", "POST", Summary = @"POST a new movie", Notes = "Send a movie here")]
[Route("/movie/{Id}", "GET,PUT,DELETE", Summary = @"GET, PUT, or DELETE a movie", Notes = "GET a specific movie by Id, or PUT a thing, or delete a movie")]
public class Movie
{
    /// <summary>
    /// Initializes a new instance of the movie.
    /// </summary>
    public Movie()
    {
        this.Genres = new List<string>();
    }

    /// <summary>
    /// Gets or sets the id of the movie. The id will be automatically incremented when added.
    /// </summary>
    //[AutoIncrement]
    [ApiMember(Name = "Id", Description = "The Id of this movie", ParameterType = "body", DataType = "string", IsRequired = false)]
    public string Id { get; set; }

    [ApiMember(Name = "ImdbId", Description = "The ImdbId of this movie", ParameterType = "body", DataType = "string", IsRequired = false)]
    public string ImdbId { get; set; }

    [ApiMember(Name = "Title", Description = "The Title of this movie", ParameterType = "body", DataType = "string", IsRequired = false)]
    public string Title { get; set; }

    [ApiMember(Name = "Rating", Description = "The Rating of this movie", ParameterType = "body", DataType = "decimal", IsRequired = false)]
    public decimal Rating { get; set; }

    [ApiMember(Name = "Director", Description = "The Director of this movie", ParameterType = "string", DataType = "string", IsRequired = false)]
    public string Director { get; set; }

    [ApiMember(Name = "ReleaseDate", Description = "The ReleaseDate of this movie", ParameterType = "string", DataType = "Date", IsRequired = false)]
    public DateTime ReleaseDate { get; set; }

    [ApiMember(Name = "TagLine", Description = "The TagLine of this movie", ParameterType = "string", DataType = "string", IsRequired = false)]
    public string TagLine { get; set; }

    [ApiMember(Name = "Genres", Description = "The Genres of this movie", ParameterType = "string", DataType = "string", IsRequired = false)]
    public List<string> Genres { get; set; }
}

/// <summary>
/// Define your ServiceStack web service response (i.e. Response DTO).
/// </summary>
public class MovieResponse
{
    /// <summary>
    /// Gets or sets the movie.
    /// </summary>
    public Movie Movie { get; set; }
}

/// <summary>
/// Create your ServiceStack restful web service implementation. 
/// </summary>
public class MovieService : Service
{
    public IMovieRepository MovieRepository { get; set; }

    /// <summary>
    /// GET /movies/{Id} 
    /// </summary>
    public MovieResponse Get(Movie movie)
    {
        var item = MovieRepository.FindOne(new ObjectId(movie.Id));

        return new MovieResponse
        {
            Movie = item,
        };
    }

    /// <summary>
    /// POST /movies
    /// 
    /// returns HTTP Response => 
    ///     201 Created
    ///     Location: http://localhost/ServiceStack.MovieRest/movies/{newMovieId}
    ///     
    ///     {newMovie DTO in [xml|json|jsv|etc]}
    /// 
    /// </summary>
    public object Post(Movie movie)
    {
        MovieRepository.Save(movie);
        var newMovieId = movie.Id;

        var newMovie = new MovieResponse
        {
            Movie = MovieRepository.FindOne(new ObjectId(movie.Id))
        };

        return new HttpResult(newMovie)
        {
            StatusCode = HttpStatusCode.Created,
            Headers = {
                { HttpHeaders.Location, base.Request.AbsoluteUri.CombineWith(newMovieId) }
            }
        };
    }

    /// <summary>
    /// PUT /movies/{id}
    /// </summary>
    public object Put(Movie movie)
    {
        MovieRepository.Save(movie);

        return new HttpResult
        {
            StatusCode = HttpStatusCode.NoContent,
            Headers = {
                { HttpHeaders.Location, this.RequestContext.AbsoluteUri.CombineWith(movie.Id) }
            }
        };
    }

    /// <summary>
    /// DELETE /movies/{Id}
    /// </summary>
    public object Delete(Movie request)
    {
        MovieRepository.Remove(new ObjectId(request.Id));

        return new HttpResult
        {
            StatusCode = HttpStatusCode.NoContent,
            Headers = {
                { HttpHeaders.Location, this.RequestContext.AbsoluteUri.CombineWith(request.Id) }
            }
        };
    }
}

/// <summary>
/// Define your ServiceStack web service request (i.e. Request DTO).
/// </summary>
/// <remarks>The route is defined here rather than in the AppHost.</remarks>
[Api("Find movies by genre, or all movies if no genre is provided")]
[Route("/movies", "GET, OPTIONS")]
[Route("/movies/genres/{Genre}")]
public class Movies
{
    public string Genre { get; set; }
}

/// <summary>
/// Define your ServiceStack web service response (i.e. Response DTO).
/// </summary>    
public class MoviesResponse
{
    /// <summary>
    /// Gets or sets the list of movies.
    /// </summary>

    public List<Movie> Movies { get; set; }
}

/// <summary>
/// Create your ServiceStack RESTful web service implementation. 
/// </summary>
public class MoviesService : Service
{
    public IMovieRepository MovieRepository { get; set; }

    /// <summary>
    /// GET /movies 
    /// GET /movies/genres/{Genre}
    /// </summary>
    public object Get(Movies request)
    {
        return new MoviesResponse
        {
            Movies = MovieRepository.FindAll().ToList()
        };
    }
}

The Swagger interface appears to have picked up the elements correctly:

enter image description here

The results is a 500 error:

POST http://localhost:57853/movie HTTP/1.1
Host: localhost:57853
Connection: keep-alive
Content-Length: 0
Accept: application/json, text/javascript, */*; q=0.01
Origin: http://localhost:57853
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.17 (KHTML, like Gecko) Chrome/24.0.1312.57 Safari/537.17
Content-Type: application/json
Referer: http://localhost:57853/swagger-ui/index.html
Accept-Encoding: gzip,deflate,sdch
Accept-Language: en-GB,en;q=0.8,en-US;q=0.6
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.3

The POST body is not attached by Swagger. The Content-Length:0. The results is an empty request object, which results in a NullReferenceException.

Can anyone see anything that I am doing wrong?

Upvotes: 2

Views: 8214

Answers (2)

Mike Mertsock
Mike Mertsock

Reputation: 12015

Note that the latest version of ServiceStack.Api.Swagger has much improved support for request body documentation in Swagger. To follow the current best practice, ensure that you update ServiceStack.Api.Swagger (and all the other ServiceStack packages) from NuGet. Ensure that you merge in the HTML/JS/CSS files in the Api.Swagger update. Replace all of your ApiMember attributes with simple Description attributes (System.ComponentModel.DescriptionAttribute). You don't need the Name or DataType properties from the ApiMember attributes anymore because the Swagger code will auto-detect this by doing reflection on your request DTO.

Note that with the latest code you do not need to have any ApiMember attributes with ParameterType = "body" at all. If you have no such attributes, it will automatically generate a Swagger request body textarea with the correct data type and documentation.

You may need to add one ApiMember attribute back in for the Id property, with ParameterType = "path" and Verb = "PUT" to properly document your PUT request.

Upvotes: 3

paaschpa
paaschpa

Reputation: 4816

A couple issues I see...

  • I think you're trying to mimic a Form post using Swagger-UI. This feature was just added to swagger (https://github.com/wordnik/swagger-core/issues/69) within the last month so I don't think it's available in the Nuget download.

  • You're seeing an empty body because your ParameterType is 'body' for each Property on your DTO. Swagger wants a single 'body' value which should contain the entire request body contents. It is not looping through each of your 'body' properties to compose the body contents. If you populate the Rating text box you should see it's value in the request (Rating is your last property with 'body' ParameterType).

see example here http://petstore.swagger.wordnik.com/#!/pet/addPet_post_1

Using Chrome or Firebug you can set a breakpoint around line #1182 in swagger-ui.js to see how it's building up the request body contents (bodyParam variable)

Upvotes: 4

Related Questions