I am using XML Documentation for my ASP.NET Web API Help Page as shown here.
I would like to know if there is a way to include html in the comments such that it will be rendered on the web page, instead of it being removed/ignored/escaped.
Specifically, I am looking for a way to create a newline, but being able to create bulleted lists, etc. would be great!
Ex/ I would like to be able to do something like this:
/// <summary>
/// CRUD operations for SalesDocument<br/>
/// This is a new line
/// </summary>
[RoutePrefix("api/SalesDocument")]
public partial class SalesDocumentController : ApiController
And have it show on the help page like this:
CRUD operations for SalesDocument
This is a new line.
Instead of this: (in this case, <br/> gets removed somehow - if I try using <p> tags, they are just escaped)
CRUD operations for SalesDocument This is a new line.
*I have already tried the <para> tag as suggested by multiple posts for tooltips, but this does not work on my help page.
Any suggestions are greatly appreciated!
In the installed XmlDocumentationProvider.cs file at Areas\HelpPage, you can look for a method called GetTagValue. Here modify the return value from node.Value.Trim() to node.InnerXml.
private static string GetTagValue(XPathNavigator parentNode, string tagName)
{
if (parentNode != null)
{
XPathNavigator node = parentNode.SelectSingleNode(tagName);
if (node != null)
{
return node.InnerXml;
}
}
return null;
}
Now open the installed file Areas\HelpPage\Views\Help\DisplayTemplates\ApiGroup.cshtml and modify the following line from:
<p>#controllerDocumentation</p>
to
<p>#Html.Raw(controllerDocumentation)</p>
#controllerDocumentation does not work for me, but changing the line to#api.Documentation works. i.e. #html.raw(api.Documentation).
Related
I am attempting to add primefaces to an existing Spring/JSF (myfaces) project. I have the actual timeline component working (or at least loading with the correct data), but the event listeners don't seem to work correctly. I have tried a number of configurations but can't seem to resolve this issue.
<p:timeline id="timeline1"
value="#{timelineView.getModel(searchFacade.dataModel)}"
editable="true" eventMargin="10" eventMarginAxis="0"
start="#{timelineView.start}"
end="#{timelineView.end}"
showNavigation="true" showButtonNew="true"
axisOnTop="true" stackEvents="false"
oncomplete="styleEvents();">
<p:ajax event="changed" listener="#{timelineView.onEdit()}" oncomplete="restyleTimeline()" />
</p:timeline>
In the above code the changed event is triggered and there is an AJAX call made. In the network pane of the browser I can see that the AJAX call is always to the URL of the current page (so like /mypage.jsf?conversationCode=a - I'm also using Apache Orchestra obviously)
The actual form data being sent for the changed event looks like this:
javax.faces.partial.ajax:true
javax.faces.source:timeline1
javax.faces.partial.execute:timeline1
javax.faces.behavior.event:changed
javax.faces.partial.event:changed
timeline1_eventIdx:0
timeline1_startDate:1498881600000
timeline1_endDate:1510894800000
timeline1_group:<div class='timeline-group'><div class='timeline-row timeline-row-group'>Group One/div><div class='timeline-row timeline-row-sub'>Group One subtitle</div><div class='timeline-row timeline-row-details'>Group One details</div></div>
mainForm:j_id_mt_SUBMIT:1
javax.faces.ViewState: Big hash as usual
The bizarrely long group name is because I am formatting the output group names into a more detailed title for the row btw.
The bean in the back (imports and package omitted):
public class TimelineView<T> implements Serializable {
private static final long serialVersionUID = 1L;
TimelineView<T> timelineView;
public TimelineModel getModel() {
return timelineView.getModel();
}
public TimelineModel getModel(ListDataModel<? extends Row<T>> results) {
return timelineView.getModel(results);
}
public void setTimelineView(TimelineView<T> timelineView) {
this.timelineView = timelineView;
}
public Date getStart() {
return timelineView.getStart();
}
public Date getEnd() {
return timelineView.getEnd();
}
public void onEdit(TimelineModificationEvent e) {
timelineView.onEdit(e);
}
public void onSelect(TimelineSelectEvent e) {
timelineView.onSelect(e);
}
}
The functionality and code inside these methods doesn't matter because when I set a breakpoint I can see that the onEdit method is never called and neither was the onSelect method when I had a select event in the timeline.
Since I have Spring handling all beans, my configuration was just this, located in the ApplicationContext.xml file:
<bean id="timelineView" class="com.mycompany.projectone.view.timeline.TimelineView" />
I have tried adding a form around the timeline, which made no difference, and have also tried adding process=":form_id", which caused the error "Cannot find component for expression ":form_id"".
As an alternative that would also suit my needs, does anyone know how to send primefaces events to a javascript function to be handled? For example, if the user moves the start and end points of the item in the timeline I would like to update the displayed start and end dates.
I would also like to change or intercept the delete behavior and modify what occurs when the delete link is clicked.
Any help would be hugely appreciated. Thanks.
EDIT
The suggested potential duplicate does not address the symptoms nor the actual solution - see answer below.
Okay, so the answer turns out to be that the Primfaces timeline component must have a widgetVar set in order for the p:ajax and pe:javascript calls to work. Incredibly simple solution but so infuriatingly difficult to find.
I've created a very neat way of implementing a PATCH method for my Web.API project by making use of an ExpandoObject as a parameter. As illustrated below:
[HttpPatch, Route("api/employee/{id:int}")]
public IHttpActionResult Update(int id, [FromBody] ExpandoObject employee)
{
var source = Repository.FindEmployeeById(id);
Patch(employee, source);
Repository.SaveEmployee(source);
return Ok(source);
}
However, when generating documentation ApiExplorer is at a loss as to what to do with the ExpandoObject, which is totally understandable. Would anyone have any ideas on how to manipulate the ApiExplorer to provide some sensible documentation?
My idea was to maybe introduce an new attribute which points to the actual Type that is expected:
public IHttpActionResult Update(int id, [FromBody, Mimics(typeof(Employee))] ExpandoObject employee)
{
...
}
But I have no idea where to start, any ideas or suggestions are welcome.
So this has been the source of some late evenings in order to get the Api Explorer to play along with our developed Http Patch mechanism. Truth be told, I'd probably should do a bit of a proper write up to full explain the mechanics behind the whole idea. But for those of you who landed on this page because you want the Api explorer to use a different type in the documentation, this is where you need to look:
Open HelpPageConfigurationExtensions.cs and locate the following method:
//File: Areas/HelpPage/HelpPageConfigurationExtensions.cs
private static void GenerateRequestModelDescription(HelpPageApiModel apiModel, ModelDescriptionGenerator modelGenerator, HelpPageSampleGenerator sampleGenerator)
{
....
}
this is the location where the parameter information is available to you and also provides you with the ability to replace/substitute parameter information with something else. I ended up doing the following to handle my ExpandoObject parameter issue:
if (apiParameter.Source == ApiParameterSource.FromBody)
{
Type parameterType = apiParameter.ParameterDescriptor.ParameterType;
// do something different when dealing with parameters
// of type ExpandObject.
if (parameterType == typeof(ExpandoObject))
{
// if a request-type-attribute is defined, assume the parameter
// is the supposed to mimic the type defined.
var requestTypeAttribute = apiParameter.ParameterDescriptor.GetCustomAttributes<RequestTypeAttribute>().FirstOrDefault();
if (requestTypeAttribute != null)
{
parameterType = requestTypeAttribute.RequestType;
}
}
}
Just, note that the RequestTypeAttribute is something I devised. My WebApi endpoint looks like this now:
public IHttpActionResult Update(int id,
[FromBody, RequestType(typeof(Employee))] ExpandoObject employee)
Thank you to everyone who took time to look into the problem.
I'm having trouble figuring out how to get the Help page in my Web Api to show anything for Resource Description other than None. It has IHttpActionResult linked and then "None." after that. I got my samples working by adding config.SetActualResponseType(typeof(ComplexType), "Controller", "Action"); to HelpPageConfig.cs. My controller looks like this:
/// <summary>
/// My description
/// </summary>
[Route("MyRoute")]
public IHttpActionResult Get()
{
try
{
//throw new Exception("TEST");
return Ok(returnValue);
}
catch (Exception ex)
{
*Company Log Method*
return NotFound();
}
}
I don't know what I'm missing from HelpPageConfig.cs or any place else. Maybe a fresh set of eyes can catch something. Thanks in advance for the help!
I just needed to add [ResponseType(typeof(MyModel))] above my action after including using System.Web.Http.Description; at the top of my controller. I'm getting the description of my model now instead of IHttpActionResult and "None."
(This probably ought to be a comment but I don't have enough rep yet)
I'd start with break pointing the various points in Areas\HelpPage - e.g. XmlDocumentationProvider - to see if that gives you any clues. Nothing leaps out at me.
Could it be that you've changed where the XML help file is output and not updated the document path passed to XmlDocumentationProvider? The path is set through the Properties page - Output XML Documentation File.
In my source code documentation I often use:
Get a <see cref="Quote"/> for the specified <see cref="apiOrder"/> object.
And this translates nicely into the below string in the XmlDocument.xml, which contains the compiled web api help pages.
Get a <see cref="T:Supertext.API.POCO.Quote"/> for the specified <see cref="!:apiOrder"/> object.
But for some reasons, all these references are not being displayed.
What we get is this:
Get a for the specified object
We found a few sources, but nothing seems to work. Does not help:
Web Api Help Page- don't escape html in xml documentation
Outdated:
http://thesoftwaredudeblog.wordpress.com/2014/01/04/using-microsoft-asp-net-web-api-2-help-page-part-2/
Any ideas?
In the WebAPI 2 Help, there is a class called, XmlDocumentationProvider. In this class there is a method named, GetTagValue which handles the Summary and Returns tags. There is also a method named GetDocumentation (there are multiples, but it is the one with the HttpParameterDescriptor parameter) which handles the Param tags.
I wrote a function that uses a RegEx to find all "See Cref"s and replace them with the last object name found.
The RegEx:
private static Regex SeeCodeReferenceRegEx = new Regex("<see cref=\\\"\\w:([\\w]+\\.)*(\\w+)\\\" */>", RegexOptions.Compiled);
The function:
private static string CleanValue(string value)
{
value = value.Trim();
var matches = SeeCodeReferenceRegEx.Matches(value);
foreach (Match match in matches)
value = value.Replace(match.Groups[0].Value, match.Groups[2].Value);
return value;
}
In GetTagValue, replace:
return node.Value.Trim();
with:
return CleanValue(node.InnerXml);
In GetDocumentation replace:
return parameterNode.Value.Trim();
with:
return CleanValue(parameterNode.InnerXml);
I'm currently implementing a Web API using Web API 2's attribute routing (http://www.asp.net/web-api/overview/web-api-routing-and-actions/attribute-routing-in-web-api-2). I am also using the Help Pages module in order to automatically generate documentation from XML comments (http://www.asp.net/web-api/overview/creating-web-apis/creating-api-help-pages).
For this API I am providing support for optional return format extensions, so that every API method has a pair of routes defined on it like so:
[HttpGet]
[Route("Path/Foo")]
[Route("Path/Foo.{ext}")]
public HttpResponseMessage DoFoo()
{
// Some API function.
}
This allows a user to hit any of these and get a result:
www.example.com/api/Controller/Path/Foo
www.example.com/api/Controller/Path/Foo.json
www.example.com/api/Controller/Path/Foo.xml
My issue is that when Help Pages uses MapHttpAttributeRoutes() to generate documentation, it is picking up both routes for each method. So right now I see help for:
api/Controller/Foo
api/Controller/Foo.{ext}
But I want to only see:
api/Controller/Foo.{ext}
I would prefer to hide the non-extension route on each method, so that every method only shows a single Help Page entry.
Has anyone else tried something similar? Is there a work around that I am missing?
My question would be is that, would consumers of your api figure out easily that the {ext} is optional?...personally, I would prefer the default behavior...but anyways following are some workarounds that I can think of:
A quick and dirty workaround. Split the DoFoo into 2 actions like DoFoo() and DoFooWithExt maybe. Notice that I am using an attribute called ApiExplorerSettings, which is for HelpPage purposes. Example below:
[HttpGet]
[Route("Path/Foo")]
[ApiExplorerSettings(IgnoreApi=true)]
public HttpResponseMessage DoFoo()
{
return DoFooHelper();
}
[HttpGet]
[Route("Path/Foo.{ext}")]
public HttpResponseMessage DoFooWithExt()
{
return DoFooHelper();
}
private HttpResponseMessage DoFooHelper()
{
//do something
}
Create a custom ApiExplorer (which HelpPage feature uses internally) and check for specific routes like the following and can decide whether to show the action or not for that particular route.
// update the config with this custom implementation
config.Services.Replace(typeof(IApiExplorer), new CustomApiExplorer(config));
public class CustomApiExplorer : ApiExplorer
{
public CustomApiExplorer(HttpConfiguration config) : base(config)
{
}
public override bool ShouldExploreAction(string actionVariableValue, HttpActionDescriptor actionDescriptor, IHttpRoute route)
{
if (route.RouteTemplate.EndsWith("Path/Foo", StringComparison.OrdinalIgnoreCase))
{
return false;
}
return base.ShouldExploreAction(actionVariableValue, actionDescriptor, route);
}
}
Get list of all ApiDescription from the default ApiExplorer and then filter out the descriptions which you do not like. Example:
Configuration.Services.GetApiExplorer().ApiDescriptions.Where((apiDesc) => !apiDesc.RelativePath.EndsWith("Path/Foo", StringComparison.OrdinalIgnoreCase))