This is the continuing section on the subject matter. For review, refer to WebAPI Thoughts - Introduction article.
PM > Install-Package WebAPI-DocumentX |
Extending Web API Documentation
The other coolest feature of Web API is its ability to document Web API service methods. The feature helps to document the service methods by listing them, describing and generating samples to their corresponding request/response values. Though the existing feature provides good API documentation, it's not capable of documenting certain request/response data types and it doesn't inform the user what exactly happens to the documentation.
Problem
Suppose you define a service method called AddInternalPhysician
that accepts Physician
class types. But this Physician
class is an abstract
class. Then, you tried to view the documentation of the method through the API documentation help section, but you couldn't view it even if the method is defined in your ApiContoller
. You don't even know what happened to the method API documentation. Same is true for the method that returns HttpResponseMessage
object and method that accepts HttpRequestMessage
object.
Visual Studio 2012 update 1(SP1) tries to address this issue by adding a ResponseType
attribute (located System.Web.Http.Description
namespace) that will be used to define a custom response type. Still, the issues persists as is for various reasons which I already explained previously.
Solution
One possible solution is to extend the API documentation provider so that Web API methods will be attributed with actual request/response classes. The concept is similar to what is being tried by VS team but more generic one and it resolves the problem described above.
The library will document Web API method that has:
- Multiple request parameters
- An Abstract/Interface for request/response type
HttpRequestMessage
and HttpResponseMessage
for request/response type
- A response that returns a Success Response and an Error Response depending on the service method logic
- Provides an explanation what happened regarding to the request/response type of the method
Participating Projects
WebAPICommonLibrary
WebAPIDocumentationDemo
WebAPIDocumentationExtenderLibrary
POCOLibrary
How To Use
- Add/Reference
WebAPIDocumentationExtenderLibrary
to your Web API project
- Reference the library under Global.asax.cs or WebApiConfig.cs
using WebAPIDocumentationExtenderLibrary;
- For Global.asax.cs
protected void Application_Start()
{
GlobalConfiguration.Configuration.RegisterRequestResponseHelp();
}
For WebApiConfig.cs
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.RegisterRequestResponseHelp();
}
}
- Once the library is referenced (Step 2) to the desired service controller class, add
RequestTypeAttribute
and ResponseTypeAttribute
to the operation with the appropriate request/response types. Example:
[RequestType(typeof(InternalPhysicianBase), "physicianRequest")]
[ResponseType(typeof(bool), typeof(Message))] [HttpPost]
public HttpResponseMessage AddInternalPhysician(HttpRequestMessage physicianRequest)
{
InternalPhysician internalPhysician = physicianRequest.Content.ReadAsAsync<InternalPhysician>().Result;
return base.AddPhysician(internalPhysician);
}
Here is what AddInternalPhysician
method documentation output looks like:
How the Web API Documentation Extender Library Works
The basic idea behind the solution is to instrument the Web API project assembly through reflection, then build samples for each method defined on the Web API project and finally register the custom message and sample to the Web API documentation section. The library is composed of many classes and interfaces that facilitate the solution. It also uses existing Web API documentation classes through .NET Reflection to generate the desired documentation sample and to register the Web API methods to the appropriate documentation places. These classes/interfaces are shown in the following class diagram:
As you can see from the diagram, there are three core interfaces that facilitate the generation of the documentation sample. These interfaces are defined as follows:
internal interface IFluentBuilder
{
string Sample { get; }
IFluentBuilder BuildSample(string input);
}
internal interface IFluentRequestBuilder : IFluentBuilder
{
IFluentRequestBuilder BuildSample(Type type, string parameterName);
}
internal interface IFluentResponseBuilder : IFluentBuilder
{
IFluentResponseBuilder BuildSample(Type type);
IFluentResponseBuilder BuildSample(Type successResponseType, Type errorResponseType);
}
Base abstract
class called APISampleBuilder
implements two interfaces namely IFluentRequestBuilder
and IFluentResponseBuilder
for building the required sample output for the request and response of a method. It also contains private
methods that facilitate the sample output. Two derived classes namely JSONSampleBuilder
and XMLSampleBuilder
are defined to facilitate JSON and XML sample output respectively. Each of these classes implements an abstract
method called BuildSample
that accepts an object instance input.
internal abstract class APISampleBuilder : IFluentRequestBuilder, IFluentResponseBuilder
{
public abstract IFluentBuilder BuildSample(object instance);
public IFluentBuilder BuildSample(string input)
{
if (!string.IsNullOrWhiteSpace(input))
sampleStringBuilder.AppendLine(input);
return this;
}
public IFluentRequestBuilder BuildSample(Type type, string parameterName)
{
string header = "Request";
string messageHeader = string.Empty;
if (!string.IsNullOrWhiteSpace(parameterName))
{
messageHeader = string.Format("{0} sample for {1} ", header, parameterName);
BuildSample(messageHeader)
.BuildSample(messageLiner);
}
else
BuildSample(messageLiner);
return BuilderSample(type, header) as IFluentRequestBuilder;
}
public IFluentResponseBuilder BuildSample(Type type)
{
string header = "Response";
BuildSample(messageLiner);
return BuilderSample(type, header) as IFluentResponseBuilder;
}
public IFluentResponseBuilder BuildSample(Type successResponseType, Type errorResponseType)
{
return ((BuildSample("Success response message sample") as IFluentResponseBuilder)
.BuildSample(successResponseType)
.BuildSample("Error response message sample") as IFluentResponseBuilder)
.BuildSample(errorResponseType);
}
}
sealed class JSONSampleBuilder : APISampleBuilder
{
public override IFluentBuilder BuildSample(object instance)
{
string json = string.Empty;
try
{
JsonSerializerSettings jss = new JsonSerializerSettings();
if (_jsonFormatter != null && _jsonFormatter.SerializerSettings != null &&
_jsonFormatter.SerializerSettings.TypeNameHandling != TypeNameHandling.None)
{
jss = _jsonFormatter.SerializerSettings;
}
else
{
jss.TypeNameHandling = TypeNameHandling.Auto;
};
json = JsonConvert.SerializeObject(instance, Formatting.Indented, jss);
}
catch (Exception)
{
}
return base.BuildSample(json);
}
}
sealed class XMLSampleBuilder : APISampleBuilder
{
public override IFluentBuilder BuildSample(object instance)
{
string xml = string.Empty;
try
{
using (Stream streamWriter = new MemoryStream())
using (StreamReader streamReader = new StreamReader(streamWriter))
{
DataContractSerializer xmlSerializer = new DataContractSerializer(instance.GetType());
xmlSerializer.WriteObject(streamWriter, instance);
streamWriter.Position = 0;
xml = streamReader.ReadToEnd();
xml = XElement.Parse(xml).ToString(); }
}
catch (Exception)
{
}
return base.BuildSample(xml);
}
}
RequestTypeAttribute
and ResponseTypeAttibute
will help to decorate the desired request/response type for the Web API controller methods. RequestTypeAttribute
accepts two parameters through its constructor. One for the actual types to be documented and the other to show the type is defined to which parameter among the operation parameters.
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
public class RequestTypeAttribute : Attribute
{
public RequestTypeAttribute(Type type, string parameterName)
{
if (type == null)
throw new ArgumentNullException("Request type value is null !");
Type = type;
_parameterName = parameterName;
}
}
ResponseTypeAttribute
has two overloaded constructor definitions which are used for:
- Generic response object
- Same like the first one except user expected to pass two types namely,
SuccessResponse
and ErrorResponse
. for a success and error response respectively
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
public class ResponseTypeAttribute : Attribute
{
public ResponseTypeAttribute(Type type)
{
if (type == null)
throw new ArgumentNullException("Response type value is null !");
Type = type;
}
public ResponseTypeAttribute(Type successResponseType, Type errorResponseType)
{
if (successResponseType == null)
throw new ArgumentNullException("Success Response type value is null !");
if (errorResponseType == null)
throw new ArgumentNullException("Error Response type value is null !");
SuccessResponseType = successResponseType;
ErrorResponseType = errorResponseType;
}
}
For clarity, refer How to use section.
Once all these classes are defined, the sample document will be generated in the following manner:
- Load the Web API project assembly
- Validate the assembly
- It should contain at least one
ApiController
type.
- It must contain a
HelpPageConfigurationExtensions
class.
- It must contain an
ObjectGenerator
class.
HelpPageConfigurationExtensions
class should define SetSampleRequest
and SetSampleResponse
methods with proper input parameters.
ObjectGenerator
class should define GenerateObject
method with proper input parameter.
- Collect methods applicable for a request and response API documentation.
- Filter out any duplicate request and response methods per
ApiController
.
- Finally register these methods to generate a sample for API documentation.
So to perform the above procedures, three classes participate, namely RegisterRequestTypes
, RegisterResponseTypes
and RegisterAPIHelp
. As the name indicates, the first two are responsible for request and response sample documentation and the remaining one is responsible for validating and registering the request/response sample API documentation. The operation to perform their duty are shown in the following code:
internal sealed class RegisterRequestTypes
{
internal static void Register(HttpConfiguration httpConfiguration, MethodInfo setSampleRequest,
string controllerName, IEnumerable<methodinfo> requestActions, MethodInfo generateObject)
{
}
}
internal sealed class RegisterResponseTypes
{
internal static void Register(HttpConfiguration httpConfiguration, MethodInfo setSampleResponse,
string controllerName, IEnumerable<methodinfo> responseActions,
MethodInfo generateObject)
{
}
}
public static class RegisterAPIHelp
{
public static void RegisterRequestResponseHelp
(this HttpConfiguration httpConfiguration, Assembly assembly = null)
{
}
}
Point of interest
Even though the main point of the article is to show how to extend the Web API documentation, there are lot of things to learn from the code along the way such as:
- Method invocation through reflection
- Attribute usage
- Serialization
- Different design principles and patterns. Like Fluent Interface
History and GitHub Version