ASP.NET Web API Help page under separate project - c#

I have ASP.NET Web API project and I want to add a Help page, but I want it to be in a separate project.
Is it possible ?

You can re-write XmlDocumentationProvider constructor to something like that:
public XmlDocumentationProvider(string appDataPath)
{
if (appDataPath == null)
{
throw new ArgumentNullException(nameof(appDataPath));
}
var files = new[] { "MyWebApiProject.xml" /*, ... any other projects */ };
foreach (var file in files)
{
var xpath = new XPathDocument(Path.Combine(appDataPath, file));
_documentNavigators.Add(xpath.CreateNavigator());
}
}
It will include all the xml documentation files that you list in the array. You should also make sure your target Web API project (and all the model projects if needed) generates documentation on build and copies it to the right location.

You should call WebApiConfig.Register from your Web API project in your help project:
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
RouteConfig.RegisterRoutes(RouteTable.Routes);
GlobalConfiguration.Configure(MyApiProjectNamespace.WebApiConfig.Register);
}

Related

ASP.NET load views from another assembly

I have a C# Solution containing two projects : Client (parent) and RestAPI (child). From the client project, I'm loading a ASP.NET server. Currently, my RestAPI is successfully loading controllers from the client project using services.AddControllersWithViews().AddApplicationPart(assembly);. Currently, only the controllers can be loaded, though, I want to use some views too.
To summarize, what I'm trying to do is to use views that are located into my Client project by the RestAPI project.
What I've tried to do :
services.AddControllersWithViews().AddApplicationPart(assembly); => It loads only the controllers
services.AddMvc().AddApplicationPart(assembly); => Same results
Returning a static path to the view file (i.e. : return View("C:\Simon\...");)
Project structure :
In the controller DevicesController.cs, I have to following function:
[Route("show")]
[HttpGet]
public ViewResult Show()
{
return View("Home");
}
I want to return the view in the Views folder located under Client project. However, It only returns the one located under RestAPI. If I remove the one in RestAPI, the application crashes with the following error logs :
System.InvalidOperationException: The view 'Home' was not found. The following locations were searched:
/Views/Devices/Home.cshtml
/Views/Shared/Home.cshtml
I think you have to consider adding relatedAssemblies.
private static void AddApplicationPart(IMvcBuilder mvcBuilder, Assembly assembly)
{
var partFactory = ApplicationPartFactory.GetApplicationPartFactory(assembly);
foreach (var part in partFactory.GetApplicationParts(assembly))
{
mvcBuilder.PartManager.ApplicationParts.Add(part);
}
var relatedAssemblies = RelatedAssemblyAttribute.GetRelatedAssemblies(assembly, throwOnError: false);
foreach (var relatedAssembly in relatedAssemblies)
{
partFactory = ApplicationPartFactory.GetApplicationPartFactory(relatedAssembly);
foreach (var part in partFactory.GetApplicationParts(relatedAssembly))
{
mvcBuilder.PartManager.ApplicationParts.Add(part);
}
}
}

Swashbuckle IDocumentFilter implementation - how to link the ActionDescriptor.MethodInfo to the Operation

Project: ASP Net Core 2.2, Web API
Packages: Swashbuckle.AspNetCore (4.0.1)
I am writing an implementation Swashbuckle.AspNetCore.SwaggerGen.IDocumentFilter which adds x-summary value at the path level in my swagger config file. To do this it needs access to the following two pieces of information for each web method
ApiDescription.ActionDescriptor.MethodInfo - to get access to attributes on the method
Operation.Summary - the method's Xml comment
It seems I can get #1 from the context and #2 from the swaggerDoc that are supplied to the IDocumentFilter implementation, but I can't find a nice way to link them except for using the path.
Is there a neater way ?
An simplified example of what I am doing is below.
public void Apply(SwaggerDocument swaggerDoc, DocumentFilterContext context)
{
// Create a map from path (prepended with "/") to the custom attribute
var methodsByPath = context.ApiDescriptions
.ToDictionary(
m => $"/{m.RelativePath}",
m => ((ControllerActionDescriptor)m.ActionDescriptor).MethodInfo.GetCustomAttribute<MyCustomAttribute>());
// Add x-summary to each path
foreach (var pathItem in swaggerDoc.Paths)
{
var customAttribute = methodsByPath[pathItem.Key];
pathItem.Value.Extensions["x-summary"]
= GeneratePathDescription(pathItem.Value.Post.Summary, customAttribute);
}
}
string GeneratePathDescription(string methodSummary, MyCustomAttribute attr)
{
[snip]
}
Your implementation looks pretty neat to me, but If you are looking for examples of how to "best" implement an IDocumentFilter look in the code of Swashbuckle:
https://github.com/domaindrivendev/Swashbuckle.AspNetCore/blob/e2f30e04f412b821a5a989338a186e422c776cc4/src/Swashbuckle.AspNetCore.Annotations/AnnotationsDocumentFilter.cs
https://github.com/domaindrivendev/Swashbuckle.AspNetCore/blob/b64b8fd6fbc7959849445be676f5e3d4a8e947bf/src/Swashbuckle.AspNetCore.SwaggerGen/XmlComments/XmlCommentsDocumentFilter.cs

Reading txt file and import as a c# code in a class

I wonder to know if it's possible. For example bootstrap has new version and i need to change cdn url and re-build project for RegisterBundles in asp.net web forms. It's simple url that we don't need to re-build project for that issue.
Is it possible that reading a *.txt file and using as c# code in a class. Class will be same as before and we will survive.
Example RegisterBundles code:
public class stylescriptbundle
{
public void RegisterBundles(BundleCollection bundles)
{
BundleTable.Bundles.Add(new ScriptBundle("/mybundle").Include(
"https://code.jquery.com/jquery-3.4.1.slim.min.js",
"https://cdn.jsdelivr.net/npm/popper.js#1.16.0/dist/umd/popper.min.js",
"https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js"
);
BundleTable.Bundles.UseCdn = true;
}
}
First, similar to what robbpriestley said, what you should do is read the strings from a file so you can include them in the bundle. Configuration file is best, but if you're hellbent on a .txt, try below:
var scripts = File.ReadAllLines("someConfigurationFile.txt");
var bundle = new ScriptBundle("/myBundle");
foreach(var script in scripts)
{
bundle.Include(script);
}
BundleTable.Bundles.UseCdn = true;
BundleTable.Bundles.Add(bundle);
(the above is just off the top of my head, you may have to tweak it to get it to work.)
That said, if you still want to compile code at run time, you can use the Microsoft.CSharp and Microsoft.CodeDom.Compiler namespaces, according to this tutorial. I'll try and summarize it here, for archival reasons.
Get your code into a string: var codeStr = File.ReadAllText("runtime compiled.cs");
Create a provider and compiler: var provider = new CSharpCodeProvider(); var parameters = new CodeParameters();
Define parameters of the compiler. It sounds like you'll definitely want to compile it into memory, and you'll need to reference any assemblies you use in that code: parameters.GenerateInMemory = true; parameters.ReferencedAssemblies.Add("necessaryAssembly.dll");
compile: var results = provider.CompileAssemblyFromSource(parameters, code);
Check errors
Get your assembly: var assembly = results.CompiledAssembly; so you can get your type: var program = assembly.GetType("first.program"); so you can get your method: var main = program.GetMethod("Main");
And then you can call your method with Invoke: main.Invoke(null, null); (check out reference on MethodInfo.)
Don't use a TXT file for this. Put the strings as settings into your web.config and then use the provided ASP.NET ConfigurationManager to reference them.
For example, see: https://stackoverflow.com/a/12892083/1348592

Call Swagger generator for plugins

i want to generate the swagger document for plugins.
I point the endpoint for the api to a plugincontroller. In this i have a method to create the documentation for a particular version. While loading the plugin all items are already registered in the swagger tooling.
(somehow the new documents don't get picked up by the swagger middleware that is why i need this workaround.)
[HttpGet("api/plugins/swaggerdoc/{version}")]
public IActionResult GetSwaggerDoc(string version)
{
SwaggerDocument gen = new SwaggerGenerator(apiDescriptionGroupCollectionProvider, schemaRegistryFactory, Swagger.SwaggerElements.GeneratorOptions.SwaggerGeneratorOptions).GetSwagger(version);
return Ok(gen);
}
but it fails to generate the document properly. It shows to much information about the properties. e.g.
"parameters":[
{
"name":"api-version",
"in":"query",
"description":null,
"required":false,
"type":"string",
"format":null,
"items":null,
"collectionFormat":null,
"default":null,
"maximum":null,
"exclusiveMaximum":null,
"minimum":null,
"exclusiveMinimum":null,
"maxLength":null,
"minLength":null,
"pattern":null,
"maxItems":null,
"minItems":null,
"uniqueItems":null,
"enum":null,
"multipleOf":null
}
how can i resolve this issue?
I found a solution:
All elements needed can be retrieved through dependency injection, or make a static reference in the startup to get a hold on it. Like generatoroptions.
public PluginsController(IActionDescriptorChangeProvider changeProvider, IOptions<MvcJsonOptions> mvcJsonOptionsAccessor, ISchemaRegistryFactory schemaRegistryFactory, IApiDescriptionGroupCollectionProvider apiDescriptionGroupCollectionProvider)
{
this.changeProvider = changeProvider;
this.schemaRegistryFactory = schemaRegistryFactory;
this.apiDescriptionGroupCollectionProvider = apiDescriptionGroupCollectionProvider;
this.mvcJsonOptionsAccessor = mvcJsonOptionsAccessor;
}
the implementation of the method will be something like this:
SwaggerDocument gen = new SwaggerGenerator(apiDescriptionGroupCollectionProvider, schemaRegistryFactory, Swagger.SwaggerElements.GeneratorOptions.SwaggerGeneratorOptions).GetSwagger(version);
var jsonBuilder = new StringBuilder();
var _swaggerSerializer = SwaggerSerializerFactory.Create(mvcJsonOptionsAccessor);
using (var writer = new StringWriter(jsonBuilder))
{
_swaggerSerializer.Serialize(writer, gen);
return Ok(jsonBuilder.ToString());
}
This will work for the 4.0.1 version of Swashbuckle.AspNetCore... The upcomping version of Swashbuckle.AspNetCore (5.x) will have to have another implementation because of the support of openapi 2 and 3.

mefcontrib interceptingCatalog export error

I am trying to test mef and mefcontrib in asp.net mvc2 app but i got an error:
Cannot cast the underlying exported value of type LoggerExtSys.Domain.WebLogger
(ContractName="LoggerExtSys.Domain.IWebLogger") to type LoggerExtSys.Domain.IWebLogger.
My test project here
code in Global.asax:
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
RegisterRoutes(RouteTable.Routes);
var catalog = new CatalogBuilder()
.ForAssembliesInDirectory(HttpRuntime.BinDirectory, "*ExtSys.dll")
.Build();
// Create interception configuration
var cfg = new InterceptionConfiguration()
.AddInterceptor(new StartableStrategy());
// Create the InterceptingCatalog with above configuration
var interceptingCatalog = new InterceptingCatalog(catalog, cfg);
// Create the container
var container = new CompositionContainer(interceptingCatalog);
// exception here
var barPart = container.GetExportedValue<IWebLogger>();
barPart.Debug("Test");
}
Exception when i try to get GetExportedValue
code in WebLogger:
[Export(typeof(IWebLogger))]
public class WebLogger : IWebLogger
{
#region IWebLogger Members
public void Debug(string str)
{
}
#endregion
#region ICoreExtension Members
public void Initialize()
{
}
#endregion
}
But in desktop app all working good.
How to fix it? Thanks for all
Ok, the problem was in code block which load assemblies:
public AggregateCatalog ForAssembliesInDirectory(string directory, string pattern)
{
IList<ComposablePartCatalog> _catalogs = new List<ComposablePartCatalog>();
var dir = new DirectoryInfo(directory);
Assembly assembly;
foreach (var file in dir.GetFiles(pattern))
{
assembly = Assembly.LoadFile(file.FullName);
_catalogs.Add(new AssemblyCatalog(assembly));
}
return new AggregateCatalog(_catalogs);
}
After all test i remove it and use DirectoryCatalog. I dont know why but its work in desktop and web app.
Who will tell me why my old code not working in web app will get accepted answer and 50 bounty. Thanks for all
I think the problem is either here:
[Export(typeof(IWebLogger))]
public class WebLogger : IWebLogger
{
or in the way you handle type referencing and resolution.
I would try to change the line:
var barPart = container.GetExportedValue<IWebLogger>();
into:
var barPart = container.GetExportedValue<WebLogger>();
or you can also try to always use fully qualified names so not only IWebLogger but put its full namespace before.
you say this works well in windows based application, what assemblies did you reference in that project or how do you write in there the content of your Application_Start event handler? Are you sure it's absolutely the same?

Categories