Adding remarks to .Net Web Api Help documentation

While http://www.asp.net/web-api has certainly been helpful, I hadn't found a way to add additional details to my help documentation, like the notes stored in the <remarks> tags.

If you don't know how to auto-generate help documentation for your .Net Web Api, first read through http://www.asp.net/web-api/overview/creating-web-apis/creating-api-help-pages

As you can see in those links, documentation for your API is auto-generated from the XML comments you leave above classes, methods, properties, etc. Now that you're an expert on .Net Web API and auto-generated help files, let me share with you my solution for adding additional details to the help documentation like the <remarks> tags above a property.

I will skip the reasoning on how I got here since my mind travels in TDD style loops constantly refactoring methods, adding arguments, removing them until I get the final working solution. You will not see any tests here, and that is unfortunately because the process of adding tests to the Help framework (which lacks tests) would have been far too cumbersome just to refactor this classes into something testable. I wanted to leave these classes as close to the way they were when I first got them in a nuget package.

Step 1) Add one line to the ModelDescriptionGenerator class in the GenerateComplexTypeModelDescription method:

        private ModelDescription GenerateComplexTypeModelDescription(Type modelType)
        {
            ComplexTypeModelDescription complexModelDescription = new ComplexTypeModelDescription
            {
                Name = ModelNameHelper.GetModelName(modelType),
                ModelType = modelType,
                Documentation = CreateDefaultDocumentation(modelType)
            };
            GeneratedModels.Add(complexModelDescription.Name, complexModelDescription);
            bool hasDataContractAttribute = modelType.GetCustomAttribute<DataContractAttribute>() != null;
            PropertyInfo[] properties = modelType.GetProperties(BindingFlags.Public | BindingFlags.Instance);
            foreach (PropertyInfo property in properties)
            {
                if (ShouldDisplayMember(property, hasDataContractAttribute))
                {
                    ParameterDescription propertyModel = new ParameterDescription
                    {
                        Name = GetMemberName(property, hasDataContractAttribute)
                    };
                    if (DocumentationProvider != null)
                    {
                        propertyModel.Documentation = DocumentationProvider.GetDocumentation(property);
                        AddAnnotations(property, propertyModel);>
                    }
                    GenerateAnnotations(property, propertyModel);
                    complexModelDescription.Properties.Add(propertyModel);
                    propertyModel.TypeDescription = GetOrCreateModelDescription(property.PropertyType);
                }
            }
            FieldInfo[] fields = modelType.GetFields(BindingFlags.Public | BindingFlags.Instance);
            foreach (FieldInfo field in fields)
            {
                if (ShouldDisplayMember(field, hasDataContractAttribute))
                {
                    ParameterDescription propertyModel = new ParameterDescription
                    {
                        Name = GetMemberName(field, hasDataContractAttribute)
                    };
                    if (DocumentationProvider != null)
                    {
                        propertyModel.Documentation = DocumentationProvider.GetDocumentation(field);
                    }
                    complexModelDescription.Properties.Add(propertyModel);
                    propertyModel.TypeDescription = GetOrCreateModelDescription(field.FieldType);
                }
            }
            return complexModelDescription;
        }

Step 2) Add the AddAnnotations method to the same class:

        private void AddAnnotations(PropertyInfo property, ParameterDescription propertyModel)
        {
            string additionalInformation = DocumentationProvider.GetAdditionalInformation(property);
            if (additionalInformation != null)
            {
                propertyModel.Annotations.Add(new ParameterAnnotation
                {
                    Documentation = additionalInformation
                });
            }
        }

Step 3) Add GetAdditionalInformation to IModelDocumentationProvider

    public interface IModelDocumentationProvider
    {
        string GetDocumentation(MemberInfo member);
        string GetAdditionalInformation(MemberInfo member);
        string GetDocumentation(Type type);
    }

Step 4) Refactor GetDocumentation in XmlDocumentationProvider

        public string GetDocumentation(MemberInfo member)
        {
            return GetMemberTag(member, "summary");
        }
        public string GetAdditionalInformation(MemberInfo member)
        {
            return GetMemberTag(member, "remarks");
        }
        private string GetMemberTag(MemberInfo member, string tagName)
        {
            string memberName = String.Format(CultureInfo.InvariantCulture, "{0}.{1}", GetTypeName(member.DeclaringType),
                member.Name);
            string expression = member.MemberType == MemberTypes.Field ? FieldExpression : PropertyExpression;
            string selectExpression = String.Format(CultureInfo.InvariantCulture, expression, memberName);
            XPathNavigator propertyNode = _documentNavigator.SelectSingleNode(selectExpression);
            return GetTagValue(propertyNode, tagName);
        }

Now your help pages will include the remarks tags in the Additional Information column of the help pages.

Additional information

You can of course build on this to include other fields, or apply this same logic to things other than complex types properties.

Comments