diff --git a/src/GetDocumentInsider/Commands/GetDocumentCommand.cs b/src/GetDocumentInsider/Commands/GetDocumentCommand.cs
index bf80df802b..11c381aac6 100644
--- a/src/GetDocumentInsider/Commands/GetDocumentCommand.cs
+++ b/src/GetDocumentInsider/Commands/GetDocumentCommand.cs
@@ -15,7 +15,7 @@ namespace Microsoft.Extensions.ApiDescription.Tool.Commands
internal class GetDocumentCommand : ProjectCommandBase
{
internal const string FallbackDocumentName = "v1";
- internal const string FallbackMethod = "Generate";
+ internal const string FallbackMethod = "GenerateAsync";
internal const string FallbackService = "Microsoft.Extensions.ApiDescription.IDocumentProvider";
private CommandOption _documentName;
@@ -139,7 +139,7 @@ namespace Microsoft.Extensions.ApiDescription.Tool.Commands
AssemblyName = Path.GetFileNameWithoutExtension(assemblyPath),
DocumentName = _documentName.Value(),
Method = _method.Value(),
- Output = _output.Value(),
+ OutputPath = _output.Value(),
Service = _service.Value(),
};
diff --git a/src/GetDocumentInsider/Commands/GetDocumentCommandContext.cs b/src/GetDocumentInsider/Commands/GetDocumentCommandContext.cs
index 208139c12f..0cd0bd7f57 100644
--- a/src/GetDocumentInsider/Commands/GetDocumentCommandContext.cs
+++ b/src/GetDocumentInsider/Commands/GetDocumentCommandContext.cs
@@ -18,7 +18,7 @@ namespace Microsoft.Extensions.ApiDescription.Tool.Commands
public string Method { get; set; }
- public string Output { get; set; }
+ public string OutputPath { get; set; }
public string Service { get; set; }
}
diff --git a/src/GetDocumentInsider/Commands/GetDocumentCommandWorker.cs b/src/GetDocumentInsider/Commands/GetDocumentCommandWorker.cs
index 752d65861f..af02bd7cc3 100644
--- a/src/GetDocumentInsider/Commands/GetDocumentCommandWorker.cs
+++ b/src/GetDocumentInsider/Commands/GetDocumentCommandWorker.cs
@@ -4,8 +4,8 @@
using System;
using System.IO;
using System.Reflection;
+using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting;
-using Microsoft.Extensions.DependencyInjection;
namespace Microsoft.Extensions.ApiDescription.Tool.Commands
{
@@ -56,41 +56,91 @@ namespace Microsoft.Extensions.ApiDescription.Tool.Commands
try
{
- var serviceType = Type.GetType(serviceName, throwOnError: true);
- var method = serviceType.GetMethod(methodName, new[] { typeof(TextWriter), typeof(string) });
- var service = services.GetRequiredService(serviceType);
-
- var success = true;
- using (var writer = File.CreateText(context.Output))
+ Type serviceType = null;
+ foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
{
- if (method.ReturnType == typeof(bool))
+ serviceType = assembly.GetType(serviceName, throwOnError: false);
+ if (serviceType != null)
{
- success = (bool)method.Invoke(service, new object[] { writer, documentName });
- }
- else
- {
- method.Invoke(service, new object[] { writer, documentName });
+ break;
}
}
- if (!success)
+ // As part of the aspnet/Mvc#8425 fix, make all warnings in this method errors unless the file already
+ // exists.
+ if (serviceType == null)
{
- // As part of the aspnet/Mvc#8425 fix, make this an error unless the file already exists.
- var message = Resources.FormatMethodInvocationFailed(methodName, serviceName, documentName);
- Reporter.WriteWarning(message);
+ Reporter.WriteWarning(Resources.FormatServiceTypeNotFound(serviceName));
+ return false;
}
- return success;
+ var method = serviceType.GetMethod(methodName, new[] { typeof(string), typeof(TextWriter) });
+ if (method == null)
+ {
+ Reporter.WriteWarning(Resources.FormatMethodNotFound(methodName, serviceName));
+ return false;
+ }
+ else if (!typeof(Task).IsAssignableFrom(method.ReturnType))
+ {
+ Reporter.WriteWarning(Resources.FormatMethodReturnTypeUnsupported(
+ methodName,
+ serviceName,
+ method.ReturnType,
+ typeof(Task)));
+ return false;
+ }
+
+ var service = services.GetService(serviceType);
+ if (service == null)
+ {
+ Reporter.WriteWarning(Resources.FormatServiceNotFound(serviceName));
+ return false;
+ }
+
+ // Create the output FileStream last to avoid corrupting an existing file or writing partial data.
+ var stream = new MemoryStream();
+ using (var writer = new StreamWriter(stream))
+ {
+ var resultTask = (Task)method.Invoke(service, new object[] { documentName, writer });
+ if (resultTask == null)
+ {
+ Reporter.WriteWarning(
+ Resources.FormatMethodReturnedNull(methodName, serviceName, nameof(Task)));
+ return false;
+ }
+
+ var finished = Task.WhenAny(resultTask, Task.Delay(TimeSpan.FromMinutes(1)));
+ if (!ReferenceEquals(resultTask, finished))
+ {
+ Reporter.WriteWarning(Resources.FormatMethodTimedOut(methodName, serviceName, 1));
+ return false;
+ }
+
+ writer.Flush();
+ stream.Position = 0L;
+ using (var outStream = File.Create(context.OutputPath))
+ {
+ stream.CopyTo(outStream);
+ }
+ }
+
+ return true;
+ }
+ catch (AggregateException ex) when (ex.InnerException != null)
+ {
+ foreach (var innerException in ex.Flatten().InnerExceptions)
+ {
+ Reporter.WriteWarning(FormatException(innerException));
+ }
}
catch (Exception ex)
{
- var message = FormatException(ex);
-
- // As part of the aspnet/Mvc#8425 fix, make this an error unless the file already exists.
- Reporter.WriteWarning(message);
-
- return false;
+ Reporter.WriteWarning(FormatException(ex));
}
+
+ File.Delete(context.OutputPath);
+
+ return false;
}
// TODO: Use Microsoft.AspNetCore.Hosting.WebHostBuilderFactory.Sources once we have dev feed available.
diff --git a/src/GetDocumentInsider/Properties/Resources.Designer.cs b/src/GetDocumentInsider/Properties/Resources.Designer.cs
index eaec18f2fe..488d4ae93c 100644
--- a/src/GetDocumentInsider/Properties/Resources.Designer.cs
+++ b/src/GetDocumentInsider/Properties/Resources.Designer.cs
@@ -220,6 +220,90 @@ namespace Microsoft.Extensions.ApiDescription.Tool
internal static string FormatMissingEntryPoint(object p0)
=> string.Format(CultureInfo.CurrentCulture, GetString("MissingEntryPoint"), p0);
+ ///
+ /// Unable to find service type '{0}' in loaded assemblies.
+ ///
+ internal static string ServiceTypeNotFound
+ {
+ get => GetString("ServiceTypeNotFound");
+ }
+
+ ///
+ /// Unable to find service type '{0}' in loaded assemblies.
+ ///
+ internal static string FormatServiceTypeNotFound(object p0)
+ => string.Format(CultureInfo.CurrentCulture, GetString("ServiceTypeNotFound"), p0);
+
+ ///
+ /// Unable to find method named '{0}' in '{1}' implementation.
+ ///
+ internal static string MethodNotFound
+ {
+ get => GetString("MethodNotFound");
+ }
+
+ ///
+ /// Unable to find method named '{0}' in '{1}' implementation.
+ ///
+ internal static string FormatMethodNotFound(object p0, object p1)
+ => string.Format(CultureInfo.CurrentCulture, GetString("MethodNotFound"), p0, p1);
+
+ ///
+ /// Unable to find service of type '{0}' in dependency injection container.
+ ///
+ internal static string ServiceNotFound
+ {
+ get => GetString("ServiceNotFound");
+ }
+
+ ///
+ /// Unable to find service of type '{0}' in dependency injection container.
+ ///
+ internal static string FormatServiceNotFound(object p0)
+ => string.Format(CultureInfo.CurrentCulture, GetString("ServiceNotFound"), p0);
+
+ ///
+ /// Method '{0}' of service '{1}' returned null. Must return a non-null '{2}'.
+ ///
+ internal static string MethodReturnedNull
+ {
+ get => GetString("MethodReturnedNull");
+ }
+
+ ///
+ /// Method '{0}' of service '{1}' returned null. Must return a non-null '{2}'.
+ ///
+ internal static string FormatMethodReturnedNull(object p0, object p1, object p2)
+ => string.Format(CultureInfo.CurrentCulture, GetString("MethodReturnedNull"), p0, p1, p2);
+
+ ///
+ /// Method '{0}' of service '{1}' has unsupported return type '{2}'. Must return a '{3}'.
+ ///
+ internal static string MethodReturnTypeUnsupported
+ {
+ get => GetString("MethodReturnTypeUnsupported");
+ }
+
+ ///
+ /// Method '{0}' of service '{1}' has unsupported return type '{2}'. Must return a '{3}'.
+ ///
+ internal static string FormatMethodReturnTypeUnsupported(object p0, object p1, object p2, object p3)
+ => string.Format(CultureInfo.CurrentCulture, GetString("MethodReturnTypeUnsupported"), p0, p1, p2, p3);
+
+ ///
+ /// Method '{0}' of service '{1}' timed out. Must complete execution within {2} minute.
+ ///
+ internal static string MethodTimedOut
+ {
+ get => GetString("MethodTimedOut");
+ }
+
+ ///
+ /// Method '{0}' of service '{1}' timed out. Must complete execution within {2} minute.
+ ///
+ internal static string FormatMethodTimedOut(object p0, object p1, object p2)
+ => string.Format(CultureInfo.CurrentCulture, GetString("MethodTimedOut"), p0, p1, p2);
+
private static string GetString(string name, params string[] formatterNames)
{
var value = _resourceManager.GetString(name);
diff --git a/src/GetDocumentInsider/Resources.resx b/src/GetDocumentInsider/Resources.resx
index fffabb44f3..facc644154 100644
--- a/src/GetDocumentInsider/Resources.resx
+++ b/src/GetDocumentInsider/Resources.resx
@@ -162,4 +162,22 @@
Assembly '{0}' does not contain an entry point.
+
+ Unable to find service type '{0}' in loaded assemblies.
+
+
+ Unable to find method named '{0}' in '{1}' implementation.
+
+
+ Unable to find service of type '{0}' in dependency injection container.
+
+
+ Method '{0}' of service '{1}' returned null. Must return a non-null '{2}'.
+
+
+ Method '{0}' of service '{1}' has unsupported return type '{2}'. Must return a '{3}'.
+
+
+ Method '{0}' of service '{1}' timed out. Must complete execution within {2} minute.
+
\ No newline at end of file
diff --git a/src/Microsoft.Extensions.ApiDescription.Design/build/Microsoft.Extensions.ApiDescription.Design.props b/src/Microsoft.Extensions.ApiDescription.Design/build/Microsoft.Extensions.ApiDescription.Design.props
index 3a3177cdc9..88a40992af 100644
--- a/src/Microsoft.Extensions.ApiDescription.Design/build/Microsoft.Extensions.ApiDescription.Design.props
+++ b/src/Microsoft.Extensions.ApiDescription.Design/build/Microsoft.Extensions.ApiDescription.Design.props
@@ -97,7 +97,7 @@