1.0 添加WEB API项目并按注释生成文档(多项目结构)
1.新建ASP.NET 项目,模板选择如图
2.选择Web API,并选择不进行身份验证方式
成功后我们看到这个结果。
至于其它三种身份验证方式,不太适合我的使用。而且这种方式也可以在代码里去实现身份验证,比较符合已有数据库的情况,这里不写。
3.给项目进行配置修改
3.1 修改对应项目生成路径。
这是单项目结构的,如果有多个项目并且有引用,把其它项目生成XML的地址改为接口项目的地址,当然也可以生成后再拷贝过去。
3.2修改WebAPITest\Areas\HelpPage\App_Start\HelpPageConfig.cs里面的代码。
反注释
1 | config.SetDocumentationProvider( new XmlDocumentationProvider(HttpContext.Current.Server.MapPath( "~/App_Data/XmlDocument.xml" ))); |
里面的文档改成我们上面生成的文档名称,打开help接口就能看到注释了。
如果是引用了多个项目,就麻烦点。
先把刚刚这句改一下
1 2 | config.SetDocumentationProvider( new XmlDocumentationProvider( HttpContext.Current.Server.MapPath( "~/App_Data" ))); |
看见XmlDocumentationProvider这个类没有?转到定义过去看看,下面是原来的代码,不用看了。

/// <summary> /// A custom <see cref="IDocumentationProvider"/> that reads the API documentation from an XML documentation file. /// </summary> public class XmlDocumentationProvider : IDocumentationProvider, IModelDocumentationProvider { private XPathNavigator _documentNavigator; private const string TypeExpression = "/doc/members/member[@name='T:{0}']"; private const string MethodExpression = "/doc/members/member[@name='M:{0}']"; private const string PropertyExpression = "/doc/members/member[@name='P:{0}']"; private const string FieldExpression = "/doc/members/member[@name='F:{0}']"; private const string ParameterExpression = "param[@name='{0}']"; /// <summary> /// Initializes a new instance of the <see cref="XmlDocumentationProvider"/> class. /// </summary> /// <param name="documentPath">The physical path to XML document.</param> public XmlDocumentationProvider(string documentPath) { if (documentPath == null) { throw new ArgumentNullException("documentPath"); } XPathDocument xpath = new XPathDocument(documentPath); _documentNavigator = xpath.CreateNavigator(); } public string GetDocumentation(HttpControllerDescriptor controllerDescriptor) { XPathNavigator typeNode = GetTypeNode(controllerDescriptor.ControllerType); return GetTagValue(typeNode, "summary"); } public virtual string GetDocumentation(HttpActionDescriptor actionDescriptor) { XPathNavigator methodNode = GetMethodNode(actionDescriptor); return GetTagValue(methodNode, "summary"); } public virtual string GetDocumentation(HttpParameterDescriptor parameterDescriptor) { ReflectedHttpParameterDescriptor reflectedParameterDescriptor = parameterDescriptor as ReflectedHttpParameterDescriptor; if (reflectedParameterDescriptor != null) { 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; } public string GetResponseDocumentation(HttpActionDescriptor actionDescriptor) { XPathNavigator methodNode = GetMethodNode(actionDescriptor); return GetTagValue(methodNode, "returns"); } public string GetDocumentation(MemberInfo member) { 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, "summary"); } public string GetDocumentation(Type type) { XPathNavigator typeNode = GetTypeNode(type); return GetTagValue(typeNode, "summary"); } private XPathNavigator GetMethodNode(HttpActionDescriptor actionDescriptor) { ReflectedHttpActionDescriptor reflectedActionDescriptor = actionDescriptor as ReflectedHttpActionDescriptor; if (reflectedActionDescriptor != null) { string selectExpression = String.Format(CultureInfo.InvariantCulture, MethodExpression, GetMemberName(reflectedActionDescriptor.MethodInfo)); return _documentNavigator.SelectSingleNode(selectExpression); } return null; } private static string GetMemberName(MethodInfo method) { string name = String.Format(CultureInfo.InvariantCulture, "{0}.{1}", GetTypeName(method.DeclaringType), method.Name); ParameterInfo[] parameters = method.GetParameters(); if (parameters.Length != 0) { string[] parameterTypeNames = parameters.Select(param => GetTypeName(param.ParameterType)).ToArray(); name += String.Format(CultureInfo.InvariantCulture, "({0})", String.Join(",", parameterTypeNames)); } return name; } private static string GetTagValue(XPathNavigator parentNode, string tagName) { if (parentNode != null) { XPathNavigator node = parentNode.SelectSingleNode(tagName); if (node != null) { return node.Value.Trim(); } } return null; } private XPathNavigator GetTypeNode(Type type) { string controllerTypeName = GetTypeName(type); string selectExpression = String.Format(CultureInfo.InvariantCulture, TypeExpression, controllerTypeName); return _documentNavigator.SelectSingleNode(selectExpression); } private static string GetTypeName(Type type) { string name = type.FullName; if (type.IsGenericType) { // Format the generic type name to something like: Generic{System.Int32,System.String} Type genericType = type.GetGenericTypeDefinition(); Type[] genericArguments = type.GetGenericArguments(); string genericTypeName = genericType.FullName; // Trim the generic parameter counts from the name genericTypeName = genericTypeName.Substring(0, genericTypeName.IndexOf('`')); string[] argumentTypeNames = genericArguments.Select(t => GetTypeName(t)).ToArray(); name = String.Format(CultureInfo.InvariantCulture, "{0}{{{1}}}", genericTypeName, String.Join(",", argumentTypeNames)); } if (type.IsNested) { // Changing the nested type name from OuterType+InnerType to OuterType.InnerType to match the XML documentation syntax. name = name.Replace("+", "."); } return name; } }
我们把它改下,整个类替换掉,没引用的自己右键引用。

public class XmlDocumentationProvider : IDocumentationProvider, IModelDocumentationProvider { // private XPathNavigator _documentNavigator; private List<XPathNavigator> _documentNavigators = new List<XPathNavigator>(); private const string TypeExpression = "/doc/members/member[@name='T:{0}']"; private const string MethodExpression = "/doc/members/member[@name='M:{0}']"; private const string PropertyExpression = "/doc/members/member[@name='P:{0}']"; private const string FieldExpression = "/doc/members/member[@name='F:{0}']"; private const string ParameterExpression = "param[@name='{0}']"; /// <summary> /// Initializes a new instance of the <see cref="XmlDocumentationProvider"/> class. /// </summary> /// <param name="documentPath">The physical path to XML document.</param> //public XmlDocumentationProvider(string documentPath) //{ // if (documentPath == null) // { // throw new ArgumentNullException("documentPath"); // } // XPathDocument xpath = new XPathDocument(documentPath); // _documentNavigator = xpath.CreateNavigator(); //} /// <summary> /// Initializes a new instance of the <see cref="XmlDocumentationProvider"/> class. /// </summary> /// <param name="appDataPath">The physical path to XML document.</param> public XmlDocumentationProvider(string appDataPath) { if (appDataPath == null) { throw new ArgumentNullException("appDataPath"); } var files = new[] { "AuthSystem.xml", "AuthSystem.SQL.xml", "AuthSystem.Common.XML" };//这里是你其它dll的说明路径,自己可以改写。例如 var files = Directory.GetFiles(appDataPath, "TLSC.*.xml");//获取所有类似的xml foreach (var file in files) { XPathDocument xpath = new XPathDocument(Path.Combine(appDataPath, file)); _documentNavigators.Add(xpath.CreateNavigator()); } } private XPathNavigator SelectSingleNode(string selectExpression) { foreach (var navigator in _documentNavigators) { var propertyNode = navigator.SelectSingleNode(selectExpression); if (propertyNode != null) return propertyNode; } return null; } public string GetDocumentation(HttpControllerDescriptor controllerDescriptor) { XPathNavigator typeNode = GetTypeNode(controllerDescriptor.ControllerType); return GetTagValue(typeNode, "summary"); } public virtual string GetDocumentation(HttpActionDescriptor actionDescriptor) { XPathNavigator methodNode = GetMethodNode(actionDescriptor); return GetTagValue(methodNode, "summary"); } public virtual string GetDocumentation(HttpParameterDescriptor parameterDescriptor) { ReflectedHttpParameterDescriptor reflectedParameterDescriptor = parameterDescriptor as ReflectedHttpParameterDescriptor; if (reflectedParameterDescriptor != null) { 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; } public string GetResponseDocumentation(HttpActionDescriptor actionDescriptor) { XPathNavigator methodNode = GetMethodNode(actionDescriptor); return GetTagValue(methodNode, "returns"); } public string GetDocumentation(MemberInfo member) { 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 = SelectSingleNode(selectExpression); return GetTagValue(propertyNode, "summary"); } public string GetDocumentation(Type type) { XPathNavigator typeNode = GetTypeNode(type); return GetTagValue(typeNode, "summary"); } private XPathNavigator GetMethodNode(HttpActionDescriptor actionDescriptor) { ReflectedHttpActionDescriptor reflectedActionDescriptor = actionDescriptor as ReflectedHttpActionDescriptor; if (reflectedActionDescriptor != null) { string selectExpression = String.Format(CultureInfo.InvariantCulture, MethodExpression, GetMemberName(reflectedActionDescriptor.MethodInfo)); return SelectSingleNode(selectExpression); } return null; } private static string GetMemberName(MethodInfo method) { string name = String.Format(CultureInfo.InvariantCulture, "{0}.{1}", GetTypeName(method.DeclaringType), method.Name); ParameterInfo[] parameters = method.GetParameters(); if (parameters.Length != 0) { string[] parameterTypeNames = parameters.Select(param => GetTypeName(param.ParameterType)).ToArray(); name += String.Format(CultureInfo.InvariantCulture, "({0})", String.Join(",", parameterTypeNames)); } return name; } private static string GetTagValue(XPathNavigator parentNode, string tagName) { if (parentNode != null) { XPathNavigator node = parentNode.SelectSingleNode(tagName); if (node != null) { return node.Value.Trim(); } } return null; } private XPathNavigator GetTypeNode(Type type) { string controllerTypeName = GetTypeName(type); string selectExpression = String.Format(CultureInfo.InvariantCulture, TypeExpression, controllerTypeName); return SelectSingleNode(selectExpression); } private static string GetTypeName(Type type) { string name = type.FullName; if (type.IsGenericType) { // Format the generic type name to something like: Generic{System.Int32,System.String} Type genericType = type.GetGenericTypeDefinition(); Type[] genericArguments = type.GetGenericArguments(); string genericTypeName = genericType.FullName; // Trim the generic parameter counts from the name genericTypeName = genericTypeName.Substring(0, genericTypeName.IndexOf('`')); string[] argumentTypeNames = genericArguments.Select(t => GetTypeName(t)).ToArray(); name = String.Format(CultureInfo.InvariantCulture, "{0}{{{1}}}", genericTypeName, String.Join(",", argumentTypeNames)); } if (type.IsNested) { // Changing the nested type name from OuterType+InnerType to OuterType.InnerType to match the XML documentation syntax. name = name.Replace("+", "."); } return name; } }
原理就是从只读一个文档改成读取多个文档。这样就能在help中看到我们写的注释(summary)了。
效果图就不贴了,免得浪费大家流量哈哈。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!