Web API Help pages - customizing Property documentation
Asked Answered
F

3

10

I have my web api and I added the web api help pages to auto-generate my documentation. It's working great for methods where my parameters are listed out, but I have a method like this:

public SessionResult PostLogin(CreateSessionCommand request)

And, on my help page, it is only listing the command parameter in the properties section. However, in the sample request section, it lists out all of the properties of my CreateSessionCommand class.

Parameters

Name | Description | Additional information

request | No documentation available. | Define this parameter in the request body.

I would like it instead to list all of the properties in my CreateSessionCommand class. Is there an easy way to do this?

Furfural answered 15/8, 2013 at 15:30 Comment(0)
L
6

this should go as an addition to @Josh answer. If you want not only to list properties from the model class, but also include documentation for each property, Areas/HelpPage/XmlDocumentationProvider.cs file should be modified as follows:

public virtual string GetDocumentation(HttpParameterDescriptor parameterDescriptor)
{
    ReflectedHttpParameterDescriptor reflectedParameterDescriptor = parameterDescriptor as ReflectedHttpParameterDescriptor;
    if (reflectedParameterDescriptor != null)
    {
        if (reflectedParameterDescriptor.ParameterInfo is CustomParameterInfo)
        {
            const string PropertyExpression = "/doc/members/member[@name='P:{0}']";
            var pi = (CustomParameterInfo) reflectedParameterDescriptor.ParameterInfo;

            string selectExpression = String.Format(CultureInfo.InvariantCulture, PropertyExpression, pi.Prop.DeclaringType.FullName + "." + pi.Prop.Name); 
            XPathNavigator methodNode = _documentNavigator.SelectSingleNode(selectExpression);
            if (methodNode != null)
            {
                return methodNode.Value.Trim();
            }
        }
        else
        {
            XPathNavigator methodNode = GetMethodNode(reflectedParameterDescriptor.ActionDescriptor);
            if (methodNode != null)
            {
                string parameterName = reflectedParameterDescriptor.ParameterInfo.Name;
                XPathNavigator parameterNode = methodNode.SelectSingleNode(String.Format(CultureInfo.InvariantCulture, ParameterExpression, parameterName));
                if (parameterNode != null)
                {
                    return parameterNode.Value.Trim();
                }
            }                    
        }
    }

    return null;
}

and CustomParameterInfo class should keep property info as well:

internal class CustomParameterInfo : ParameterInfo
{
    public PropertyInfo Prop { get; private set; }

    public CustomParameterInfo(PropertyInfo prop)
    {
        Prop = prop;
        base.NameImpl = prop.Name;
    }
}
Lumberman answered 8/11, 2013 at 13:35 Comment(0)
F
7

So, I managed to devise a workaround for this problem, in case anyone is interested.

In HelpPageConfigurationExtensions.cs I added the following extension method:

public static void AlterApiDescription(this ApiDescription apiDescription, HttpConfiguration config)
{
    var docProvider = config.Services.GetDocumentationProvider();
    var addParams = new List<ApiParameterDescription>();
    var removeParams = new List<ApiParameterDescription>();

    foreach (var param in apiDescription.ParameterDescriptions)
    {
        var type = param.ParameterDescriptor.ParameterType;

        //string is some special case that is not a primitive type
        //also, compare by full name because the type returned does not seem to match the types generated by typeof
        bool isPrimitive = type.IsPrimitive || String.Compare(type.FullName, typeof(string).FullName) == 0;

        if (!isPrimitive)
        {
            var properties = from p in param.ParameterDescriptor.ParameterType.GetProperties() 
                               let s = p.SetMethod 
                               where s.IsPublic 
                               select p;

            foreach (var property in properties)
            {
                var documentation = docProvider.GetDocumentation(new System.Web.Http.Controllers.ReflectedHttpParameterDescriptor()
                {
                    ActionDescriptor = param.ParameterDescriptor.ActionDescriptor,
                    ParameterInfo = new CustomParameterInfo(property)
                });

                addParams.Add(new ApiParameterDescription()
                {
                    Documentation = documentation,
                    Name = property.Name,
                    Source = ApiParameterSource.FromBody,
                    ParameterDescriptor = param.ParameterDescriptor
                });
            }

            //since this is a complex type, select it to be removed from the api description
            removeParams.Add(param);
        }
    }

    //add in our new items
    foreach (var item in addParams)
    {
        apiDescription.ParameterDescriptions.Add(item);
    }

    //remove the complex types
    foreach (var item in removeParams)
    {
        apiDescription.ParameterDescriptions.Remove(item);
    }
}

And here is the Parameter info instanced class I use

internal class CustomParameterInfo : ParameterInfo
{
    public CustomParameterInfo(PropertyInfo prop)
    {
        base.NameImpl = prop.Name;
    }
}

Then, we call the extension in another method inside the extensions class

public static HelpPageApiModel GetHelpPageApiModel(this HttpConfiguration config, string apiDescriptionId)
{
    object model;
    string modelId = ApiModelPrefix + apiDescriptionId;
    if (!config.Properties.TryGetValue(modelId, out model))
    {
        Collection<ApiDescription> apiDescriptions = config.Services.GetApiExplorer().ApiDescriptions;
        ApiDescription apiDescription = apiDescriptions.FirstOrDefault(api => String.Equals(api.GetFriendlyId(), apiDescriptionId, StringComparison.OrdinalIgnoreCase));
        if (apiDescription != null)
        {
            apiDescription.AlterApiDescription(config);

            HelpPageSampleGenerator sampleGenerator = config.GetHelpPageSampleGenerator();
            model = GenerateApiModel(apiDescription, sampleGenerator);
            config.Properties.TryAdd(modelId, model);
        }
    }

    return (HelpPageApiModel)model;
}

The comments that are used for this must be added to the controller method and not the properties of the class object. This might be because my object is part of an outside library

Furfural answered 21/8, 2013 at 19:49 Comment(0)
L
6

this should go as an addition to @Josh answer. If you want not only to list properties from the model class, but also include documentation for each property, Areas/HelpPage/XmlDocumentationProvider.cs file should be modified as follows:

public virtual string GetDocumentation(HttpParameterDescriptor parameterDescriptor)
{
    ReflectedHttpParameterDescriptor reflectedParameterDescriptor = parameterDescriptor as ReflectedHttpParameterDescriptor;
    if (reflectedParameterDescriptor != null)
    {
        if (reflectedParameterDescriptor.ParameterInfo is CustomParameterInfo)
        {
            const string PropertyExpression = "/doc/members/member[@name='P:{0}']";
            var pi = (CustomParameterInfo) reflectedParameterDescriptor.ParameterInfo;

            string selectExpression = String.Format(CultureInfo.InvariantCulture, PropertyExpression, pi.Prop.DeclaringType.FullName + "." + pi.Prop.Name); 
            XPathNavigator methodNode = _documentNavigator.SelectSingleNode(selectExpression);
            if (methodNode != null)
            {
                return methodNode.Value.Trim();
            }
        }
        else
        {
            XPathNavigator methodNode = GetMethodNode(reflectedParameterDescriptor.ActionDescriptor);
            if (methodNode != null)
            {
                string parameterName = reflectedParameterDescriptor.ParameterInfo.Name;
                XPathNavigator parameterNode = methodNode.SelectSingleNode(String.Format(CultureInfo.InvariantCulture, ParameterExpression, parameterName));
                if (parameterNode != null)
                {
                    return parameterNode.Value.Trim();
                }
            }                    
        }
    }

    return null;
}

and CustomParameterInfo class should keep property info as well:

internal class CustomParameterInfo : ParameterInfo
{
    public PropertyInfo Prop { get; private set; }

    public CustomParameterInfo(PropertyInfo prop)
    {
        Prop = prop;
        base.NameImpl = prop.Name;
    }
}
Lumberman answered 8/11, 2013 at 13:35 Comment(0)
J
3

This is currently not supported out of the box. Following bug is kind of related to that: http://aspnetwebstack.codeplex.com/workitem/877

Jaymie answered 15/8, 2013 at 17:40 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.