ASP.NET Web API Generate all parameters from model - help pages
Asked Answered
B

2

24

I'm busy creating a Web API (Inside a asp mvc4 application). I am using the library suggested on the asp.net site for generating documentation (http://www.asp.net/web-api/overview/creating-web-apis/creating-api-help-pages).

My problem is that if my parameter is a model, then I can't specify what properties the model contains in the generated help pages.

Here is an example:

MODEL:

public class TestModel
{
    property String FirstName {get;set;}
    property String Surname {get; set;}
    property Boolean Active {get;set;} 
}

ACTION:

/// <summary>
/// This is a test action
/// </summary>
/// <param name="model">this is the model</param> <-- this works
/// <param name="FirstName">This is the first name </param>  <-- doesn't work
/// <param name ="model.Surname">This is the surname</param> <-- doesn't work
public HttpResponseMessage Post(my.namespace.models.TestModel model)
{
  ...
}

Only the parameter for model gets generated.

I took a look at the xml document that gets generated for the documentation and it does add the other parameters.

<member name="my.namespace.api.Post(my.namespace.models.TestModel)">
     <summary>
         this is a test action
     </summary>
     <param name="model>this is the model</param>
     <param name="FirstName">This is the first name </param>
     <param name="model.Surname">This is the surname</param>
</member>

But on the help pages it only generates the parameter model.

I have traced it down to the method where it gets the parameters from the xml.

Collection<ApiDescription> apiDescriptions = config.Services.GetApiExplorer().ApiDescriptions;

This is located in the HelpPageConfigurationExtentions.cs which is auto generated.

Am i approaching this the wrong way? Does anyone know of a work around?

Any suggestions or solutions will be appreciated.

Brokaw answered 4/9, 2013 at 6:20 Comment(2)
Have a read of this answer https://mcmap.net/q/583363/-web-api-help-pages-customizing-property-documentationFollett
Have you tried documenting you model class also?Czarra
M
27

The MVC Web API documentation feature walks through your API classes and methods using reflection. This will build the structure of the documentation but will result in more or less empty (and useless) documentation unless you have added documentation comments.

The body of the documentation is filled using the XML file that is generated using /// documentation comments which has a specific structure that must be followed. That means that you can't fill your xml with whatever you want it to display, it actually has to be connected to something that is in your API and must follow the structure of your classes and properties.

So in your case you can't put model property documentation in an api method. You have to put it into the Model where the property exists.

MODEL:

  public class TestModel
  {
  /// <summary>This is the first name </summary>
      property String FirstName {get;set;}
  /// <summary>This is the surname</summary>
      property String Surname {get; set;}
      property Boolean Active {get;set;} 
  }

ACTION:

  /// <summary>
  /// This is a test action
  /// </summary>
  /// <param name="model">this is the model</param> 
  public HttpResponseMessage Post(my.namespace.models.TestModel model)
  {
    ...
  }

Modify Help Pages

The default Help pages that are automatically generated do not include Model documentation only the api methods are documented. In order to display more information about the parameters in your api a customisation is required. The instruction that follow are one way to add parameter documentation.

Create two new types in Areas/HelpPage/Models

public class TypeDocumentation
{
    public TypeDocumentation()
    {
        PropertyDocumentation = new Collection<PropertyDocumentation>();
    }

    public string Summary { get; set; }
    public ICollection<PropertyDocumentation> PropertyDocumentation { get; set; } 
}

public class PropertyDocumentation
{
    public PropertyDocumentation(string name, string type, string docs)
    {
        Name = name;
        Type = type;
        Documentation = docs;
    }
    public string Name { get; set; }
    public string Type { get; set; }
    public string Documentation { get; set; }
}

Add a new property to HelpPageApiModel.cs

public IDictionary<string, TypeDocumentation> ParameterModels{ get; set; } 

Create a new interface

internal interface IModelDocumentationProvider
{
    IDictionary<string, TypeDocumentation> GetModelDocumentation(HttpActionDescriptor actionDescriptor);
}

Modify XmlDocumentationProvider to implement the new interface

public class XmlDocumentationProvider : IDocumentationProvider, IModelDocumentationProvider
{
    private const string TypeExpression = "/doc/members/member[@name='T:{0}']";
    private const string PropertyExpression = "/doc/members/member[@name='P:{0}']";
///...
///... existing code
///...

    private static string GetPropertyName(PropertyInfo property)
    {
        string name = String.Format(CultureInfo.InvariantCulture, "{0}.{1}", property.DeclaringType.FullName, property.Name);
        return name;
    }

    public IDictionary<string, TypeDocumentation> GetModelDocumentation(HttpActionDescriptor actionDescriptor)
    {
        var retDictionary = new Dictionary<string, TypeDocumentation>();
        ReflectedHttpActionDescriptor reflectedActionDescriptor = actionDescriptor as ReflectedHttpActionDescriptor;
        if (reflectedActionDescriptor != null)
        {
            foreach (var parameterDescriptor in reflectedActionDescriptor.GetParameters())
            {
                if (!parameterDescriptor.ParameterType.IsValueType)
                {
                    TypeDocumentation typeDocs = new TypeDocumentation();


                    string selectExpression = String.Format(CultureInfo.InvariantCulture, TypeExpression, GetTypeName(parameterDescriptor.ParameterType));
                    var typeNode = _documentNavigator.SelectSingleNode(selectExpression);

                    if (typeNode != null)
                    {
                        XPathNavigator summaryNode;
                        summaryNode = typeNode.SelectSingleNode("summary");
                        if (summaryNode != null)
                            typeDocs.Summary = summaryNode.Value;
                    }

                    foreach (var prop in parameterDescriptor.ParameterType.GetProperties())
                    {
                        string propName = prop.Name;
                        string propDocs = string.Empty;
                        string propExpression = String.Format(CultureInfo.InvariantCulture, PropertyExpression, GetPropertyName(prop));
                        var propNode = _documentNavigator.SelectSingleNode(propExpression);
                        if (propNode != null)
                        {
                            XPathNavigator summaryNode;
                            summaryNode = propNode.SelectSingleNode("summary");
                            if (summaryNode != null) propDocs = summaryNode.Value;
                        }
                        typeDocs.PropertyDocumentation.Add(new PropertyDocumentation(propName, prop.PropertyType.Name, propDocs));

                    }
                    retDictionary.Add(parameterDescriptor.ParameterName, typeDocs);
                }

            }

        }

        return retDictionary;
    }
}

Add code to HelpPageConfigurationExtension in GenerateApiModel method

IModelDocumentationProvider modelProvider =
            config.Services.GetDocumentationProvider() as IModelDocumentationProvider;
if (modelProvider != null)
{
    apiModel.ParameterModels = modelProvider.GetModelDocumentation(apiDescription.ActionDescriptor);
}

Modify HelpPageApiModel.cshtml adding to following where you want the Model documentation to be displayed.

bool hasModels = Model.ParameterModels.Count > 0;
if (hasModels)
{
     <h2>Parameter Information</h2>
  @Html.DisplayFor(apiModel => apiModel.ParameterModels, "Models")

}

Add a Models.cshtml to DisplayTemplates

@using System.Web.Http
@using System.Web.Http.Description
@using MvcApplication2.Areas.HelpPage.Models
@model IDictionary<string, TypeDocumentation>

@foreach (var modelType in Model)
{
    <h3>@modelType.Key</h3>
    if (modelType.Value.Summary != null)
    {
    <p>@modelType.Value.Summary</p>
    }
    <table class="help-page-table">
        <thead>
            <tr>
                <th>Property</th>

                <th>Description</th>
            </tr>
        </thead>
        <tbody>
            @foreach (var propInfo in modelType.Value.PropertyDocumentation)
            {
                <tr>
                    <td class="parameter-name"><b>@propInfo.Name</b> (@propInfo.Type)</td>

                    <td class="parameter-documentation">
                        <pre>@propInfo.Documentation</pre>
                    </td>
                </tr>
            }
        </tbody>
    </table>
}
Madame answered 1/11, 2013 at 21:2 Comment(6)
this worked! the only problem was the code I had to place in the GenerateApiModel method, there is no config object. I fixed this by adding a parameter for it in the GenerateApiModel, then passed the config object in the GetHelpPageApiModel methodBrokaw
I must have missed a step in the instructions, anyone else who wants to do this should look at Jeandre's comment on the config object.Madame
Has anyone come up with an answer for the case where the models live in an external project, or is that not common practice?Cotemporary
This api documentation setup will read from a single XmlDocument.xml file, it doesn't care which assemblies the documentation is related to. You can easily merge the documentation XML from your external model project into the XmlDocument.xml file to get documentation for types outside your project.Madame
Just in case: I was getting a null when trying to get the DocumentationProvider here config.Services.GetDocumentationProvider(). To solve that uncomment config.SetDocumentationProvider in Areas\HelpPage\App_Start\HelpPageConfig.cs. More info here: blogs.msdn.com/b/yaohuang1/archive/2012/12/10/…Cowbind
This works. But I found out this feature already included in at least 5.2.3 published on Feb 9, 2015. My xml document not showing up as description is caused by Generics.Pluralism
H
0

josant's answer works great. I did find it was a bit-over zealous however. I found it was reporting simple things like strings as models and reporting them to be a Char array with a length field!

We only needed this for models, so I added this code to the end of the GetModelDocumentation method:

if (parameterDescriptor.ParameterName == "value" || parameterDescriptor.ParameterName == "model")
{
    retDictionary.Add(parameterDescriptor.ParameterName, typeDocs);
}

Now it only returns parameter details for non-simple types.

Heed answered 17/12, 2013 at 4:22 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.