How can I add .NET namespace information in DocFX - docfx

The title says it all ...
In Sandcastle Help File Builder we added the NamespaceDoc class to each namespace to create namespace documentation.
How do we to the same using DocFX?

Here's how I did it:
In the root folder of your documentation project, add a folder named namespaces.
Update your docfx.json file to include markup files added to the namespaces folder. You need to update the overwrite property in the build section. It will look something like this:
"overwrite": [
{
"files": [
"apidoc/**.md",
"namespaces/**.md"
],
"exclude": [
"obj/**",
"_site/**"
]
}
],
Create a markdown file in the namespaces folder for every namespace you want to add documentation to. It is probably best to name these files the same as the namespace.
The files should have a YAML header with a UID that matches the namespace's name. The summary: *content line tells docfx to overwrite the summary of the namespace with the contents of this file.
The rest of the page is standard markdown that will become the namespace's summary. For example:
---
uid: My.Groovy.Namespace
summary: *content
---
The My.Groovy.Namespace namespace contains a bunch of classes and interfaces.

There isn't a way to add it directly in source code. You can use overwrite files to add summary for namespace type.

I'm probably extremely late to this question, but i faced a similar problem and the solution i found invovles modifying docfx from source and adding helping classes, much like Sandcastle's solution.
Disclamer:
I do not claim that the solution i'm showcasing is programmatically
stable, safe or even correct. I do not claim that this solution will
work in any scenario or for any use. I only verify that, for me, it
worked perfectly fine, even though i recongize it's just a fast-put-up
workaround.
Steps :
I downloaded the source code of docfx from their github's releases page (2.59.2, as of today)
After extracting the solution, I opened the file docfx-2.59.2\src\Microsoft.DocAsCode.Metadata.ManagedReference\ExtractMetadataWorker.cs
The class implemented within this file contains a method named GetMetadataFromProjectLevelCache that, at some point, extracts the metadata
from the referenced project in a tree form.
private Tuple<MetadataItem, bool> GetMetadataFromProjectLevelCache(IBuildController controller, IInputParameters key){
// [...]
projectMetadata = controller.ExtractMetadata(key); // THIS line
// [...]
}
After this line, I appended the following line containing a method which I also had to implement.
private Tuple<MetadataItem, bool> GetMetadataFromProjectLevelCache(IBuildController controller, IInputParameters key){
// [...]
projectMetadata = controller.ExtractMetadata(key);
ExtractNamespaceDocumentation(projectMetadata); // THIS line
// [...]
}
The implementation was the following:
private void ExtractNamespaceDocumentation(MetadataItem node)
{
// Terminal nodes are not of our interest in any case
// Even if it's a namespace, it does not contain documentation
if (node.Items is not { Count: > 0 }) return;
// If it is namespace
if (node.Type == MemberType.Namespace)
{
// Get (if any), the child that is class and is named "_NamespaceDoc"
var doc = node.Items.FirstOrDefault(x =>
x.Type == MemberType.Class && x.Name.Split('.').Last() == "_NamespaceDoc");
// If we didn't found such class, the namespace does not contain documentation.
// Leave and don't go further.
if (doc is null) return;
// Else, assign the class' Summary and remarks to the Namespace and remove the class from the tree.
node.Summary = doc.Summary;
node.Remarks = doc.Remarks;
node.Items.Remove(doc);
// job finished for this namespace, we do not want to go further down the tree.
return;
}
// For non-namespace intermediate nodes (IE assembly nodes), visit the children.
foreach (var child in node.Items) ExtractNamespaceDocumentation(child);
}
Lastly, I compiled the solution and, by using the newly created docfx.exe located at docfx-2.59.2\src\docfx\bin\Debug\net472, i was able to detect all classes named _NamespaceDoc and use their <summary> tags to fill the namespaces they resided.
For the record, i decided to create a new .cs file at the root of my project to contain all _NamespaceDoc classes, so it would be easier for me to disable the whole file when i want to release the project. This file looked like this:
namespace RootNamespace
{
/// <summary>
/// Documentation for the Root Namespace
/// </summary>
public static class _NamespaceDoc { }
}
namespace RootNamespace.SubFolder
{
/// <summary>
/// Documentation for the Root Namespace's `SubFolder` Sub-Namespace.
/// </summary>
public static class _NamespaceDoc { }
}
// [...]
Hopefully, this may help other fellows seaking for such solution, or even the docfx devs and contributors to implement this feature more reliably.
Update:
For more info about this approach, I've started a discussion
on docfx's github repository

Related

Magnolia 5.7.9 module configuration

In the example for the magnolia module configuration in the documentation I am not sure why the private boolean colorsEnabled;for the FooBar class is not in the YAML configuration. Where does the module configuration get the colorsEnabled property from?
maxSize: 25
welcomeMessage: Hello world
fooBar:
colors: [red, green, blue]
Also the when I programmatically retrieve the List<String> list = fooBar.getColors(); I get null for the list.
I am running Magnolia 5.7.9.
UPDATE:
My module class is done the same way as described in the documentation and the example described above.
public class XxxVersioning implements ModuleLifecycle {
private Excludes excludes;
public Excludes getExcludes() {
return excludes;
}
public void setExcludes(Excludes excludes) {
this.excludes = excludes;
}
public class Excludes {
private String red;
private String green;
public String getRed() {
return red;
}
public void setRed(String red) {
this.red = red;
}
public String getGreen() {
return green;
}
public void setGreen(String green) {
this.green = green;
}
}
I have a class that programmatically inquires if a string value is in the list. The the list appears to be null.
/**
* Find if template name for the component in the module exception list
* #param String templateName
* #return Boolean true if templateName in the module exception-list
*/
public Boolean isComponentException(String templateName) {
// get the (singleton) instance of the module
// On the module class instance call the getter methods of the module bean properties.
XxxVersioning moduleInstance = xxxVersionProvider.get();
// access the modules RenderingExcludes bean
XxxVersioning.Excludes excludes = moduleInstance.getExcludes();
String green = excludes.getGreen();
return true;
}
SOLUTION:
For bootstrapping I found the document [here][5] According to this document
All bootstrap files are only imported once!
Webapp-based bootstrap files are imported during the first run of the webapp when the Magnolia instance gets installed.
Module-based bootstrap files are imported during the installation of the module.
If you want to import bootstrap files on every start up of the Magnolia instance or of a module, you must use custom installation tasks which are executed by the Module version handler or Module start up classes.
FooBar class is not in the YAML configuration. Where does the module
configuration get the colorsEnabled property from?
First YAML/FS is checked, then from the JCR configuration (config workspace in the repo). Or if property of that name doesn't exist it would take the default value.
More on the order in which resources are checked here.
The list appears to be always null.
yes, because you override the value from yaml with the one in config workspace (or you have just one in the workspace and nothing in yaml?) and there you still write the property list in yaml format instead of using syntax appropriate for jcr (which would be contentNode called excludes with 4 properties under that node each being name/value pair representing the individual colors. Unfortunately documentation doesn't show how that differs so clearly so it's easy to make a mistake there.
Anyway, as a good practice you should choose where you will store your configuration - either in repo or in yaml. I would suggest yaml config in FS as it allows you to change the configuration from outside even if your app gets corrupted. Plus it's easier to keep that file in git or other VCS and have proper history of changes on it tracked.

Prefix path for FXML files in TornadoFX

Is there a way to provide a path for FXML files, used by TornadoFX, with using its convention by fxml()?
Normally, TornadoFX conventionally tries to locate FXML resources in src/main/resources, however, our application is large and this might now be the best idea. In our case, we would like to maintain the files in a subdirectory, i.e. src/main/resources/fxml.
We would like to set it up during application startup. Is this possible?
I've added an FXML locator function to the framework so that you can override this to change the resource location. The declaration and default implementation looks like this:
var fxmlLocator: (component: UIComponent, location: String?) -> URL = { component, location ->
val targetLocation = location ?: component.javaClass.simpleName + ".fxml"
requireNotNull(component.resources.url(targetLocation)) { "FXML not found for ${component.javaClass} in $targetLocation" }
}
You can override this in app.init() for example, like this:
FX.fxmlLocator = { component, _ ->
val targetLocation = "/fxml/${component.javaClass.simpleName}.fxml"
requireNotNull(component.resources.url(targetLocation)) { "FXML not found for ${component.javaClass} in $targetLocation" }
}
However, if you go this route you must pay attention to your class names, as the same class name in different packages would look for the same resource in /fxml. Alternatively, change the implementation to observe the package names as well.
I'm committing the feature now, you can try it out tomorrow using the latest snapshot release from sonatype.

Symfony 2 get Sonata media file path in Entity class

When implementing a __clone() method in media entity, I need to get the absolute path of a file to be able to make a copy of the file itself. I've been searching for a long time and I have not found any documentation to get this. Any ideas?
Here it is the __clone() method where I need the file path:
// Norwalk\StoreBundle\Entity\ProductHasMedia
public function __clone() {
if ($this->id) {
$this->media = clone $this->media;
$this->media->setProviderReference('clone_'.$this->media->getProviderReference());
$this->media->setName('clone_'.$this->media->getName());
$providerMeta = array('filename' => $this->media->getName());
$this->media->setProviderMetadata($providerMeta);
// Clone the physical image file too
$fs = new Filesystem();
$fs->copy( "original_image_path/".$this->media->getName(), "original_image_path/"."clone_".$this->media->getName());
}
}
I don't think it is possible without giving the Entity class to much responsibilities since you can't tell the absolute path name without having some services ('sonata.media.manager.media', 'sonata.media.provider.image' or 'sonata.media.twig.extension')..
The proper way to go is to build a clone function into an controller. The controller can have all the magic (services and entitymanagers) to do the cloning for you.
See this stackoverflow question for an example.

Get instance of type inheriting from base class, implementing interface, using StructureMap

Continuing on my quest for a good plugin implementation I have been testing the StructureMap assembly scanning features.
All plugins will inherit from abstract class PluginBase. This will provide access to common application services such as logging. Depending on it's function, each plugin may then implement additional interfaces, for example, IStartUpTask.
I am initializing my plugins like so:
Scan(x => {
x.AssembliesFromPath(HttpContext.Current.Server.MapPath("~/Plugins"),
assembly => assembly.GetName().Name.Contains("Extension"));
x.AddAllTypesOf<PluginBase>();
});
The difficulty I am then having is how to work against the interface (not the PluginBase) in code. It's easy enough to work with PluginBase:
var plugins = ObjectFactory.GetAllInstances<PluginBase>();
foreach (var plugin in plugins)
{
}
But specific functionality (e.g. IStartUpTask.RunTask) is tied to the interface, not the base class.
I appreciate this may not be specific to structuremap (perhaps more a question of reflection).
Thanks,
Ben
Do you know all of the specific interfaces at registration time? If so, you can make a custom registration convention that registers each type with the plugin "family" of the interface it implements. An IRegistrationConvention gets each type, one at a time. You could do a simple check to see if the current type implements the desired interface, and if so, add it.
if (typeof(IStartUpTask).IsAssignableFrom(currentType)){
For<IStartUpTask>().Add(currentType);
}
Then later in the code, you can retrieve plugins for each specific interface individually:
var startupTasks = ObjectFactory.GetAllInstances<IStartUpTask>();
This approach has the benefit of allowing you to inject an enumerable of your custom interface plugins into a class that needs them, instead of making the service location call.
Alternatively, if you don't want to make a registration convention, you can just do the filtering at runtime using the handy OfType linq extension method:
var startupTasks = ObjectFactory.GetAllInstances<PluginBase>().OfType<IStartupTask>();
In case it helps others, I followed Joshua's advice and added my own registration convention:
public class PluginConvention : IRegistrationConvention
{
public void Process(Type type, Registry registry) {
if (type.BaseType == null) return;
if (type.BaseType.Equals(typeof(PSAdmin.Core.Domain.PluginBase))) {
if (typeof(IStartUpTask).IsAssignableFrom(type)) {
registry.For<IStartUpTask>()
.TheDefault.Is.OfConcreteType(type);
}
}
}
}
I couldn't get the .Add method to work, no matter what I tried, so had to use TheDefault.Is.OfConcreteType(type).
Then in my bootstrapper I am scanning like so:
Scan(x => {
x.AssembliesFromPath(HttpContext.Current.Server.MapPath("~/Plugins"),
assembly => assembly.GetName().Name.Contains("Extension"));
x.Convention<PluginConvention>();
});
I can then grab my IStartUp task types like so:
var plugins = ObjectFactory.GetAllInstances<IStartUpTask>();
foreach (var plugin in plugins)
{
plugin.Configure();
}
That said, after reading up on some of the new features of StructureMap, I'm not sure I need to do any of the above. For example I could just change my Scan delegate function to:
Scan(x => {
x.AssembliesFromPath(HttpContext.Current.Server.MapPath("~/Plugins"),
assembly => assembly.GetName().Name.Contains("Extension"));
x.AddAllTypesOf<PluginBase>();
});
And to use my interface concrete types (that inherit from PluginBase):
var tasks = ObjectFactory.Model.GetAllPossible<IStartUpTask>();
foreach (var task in tasks)
{
task.Configure();
}
Both methods seem to achieve the same thing.

Override a resource from standard assembly in ASP.NET

I want to override a string from a System.ComponentModel.DataAnnotations for an ASP.NET project. Do I need to make a satellite assembly, messing with custom build tasks, al.exe etc.? Even if yes, I couldn't find how to convert .resx to .resources to feed it to al.exe. And if no, where to put the .resx. and how to name it?
UPD: To make it clear: I wanted to use a custom resource string instead of one from the default resource from the assembly. I didn't want to make changes in the every place that uses that string. After all, the resources exist just for overriding them.
Phil Haack has an excellent article Localizing ASP.Net MVC Validation which specifically guides you through overriding your strings. This article applies more to DataAnnotations than it does ASP.net MVC. So, this will help however your are using DataAnnotattions.
Below I have listed the simplest steps to add Localized Resources in Visual Studio.
Open the Project Properties dialog.
Select the Resources tab.
Click to create a new default
resources file.
This will create two files in your Properties folder.
Resources.resx
Resources.Designer.cs
When Resources.resx has
opened, change it's Access Modifier
to Public.
Add your strings.
To add additional resource files for specific cultures you will need to.
Right click your Project in the
Solution Explorer.
Select Add -> New Item -> Resource
File.
Name it Resources.en-us.resx.
(replace 'en-us' with appropriate
code)
Click Add
Drag it into the Properties folder.
Open Resources.en-us.resx and change it's Access Modifier
to Public.
Add your strings.
Repeat for each Culture you need to
support.
During the build VS will convert the .resx files to .resource files and build wrapper classes for you. You can then access via the namespace YourAssembly.Properties.Resources.
With this using statement.
using YourAssembly.Properties;
You can decorate with attributes like this:
[Required(ErrorMessageResourceType = typeof(Resources), ErrorMessageResourceName = "MyStringName")]
Note: I used the Properties folder for consistency. To use the App_GlobalResources move your .resx files there and change your using statement to match the directory name. Like this:
using YourAssembly.App_GlobalResources;
Edit: The closest that you can get to Strongly Typed resource names would be to do something like this:
public class ResourceNames
{
public const string EmailRequired = "EmailRequired";
}
You can then decorate with attributes like this.
[Required(ErrorMessageResourceType = typeof(Resources), ErrorMessageResourceName = ResourceNames.EmailRequired)]
To enable automatic client culture detection add the globalizationsection to the web.config file.
<configuration>
<system.web>
<globalization enableClientBasedCulture="true" culture="auto:en-us" uiCulture="auto:en-us"/>
</system.web>
<configuration>
Here I have enabled a client based culture and set the culture and the uiculture to "auto" with a default of "en-us".
Creating Separate Satellite Assemblies:
The MSDN Creating Satellite Assemblies article will help as well.
If you are new to satellite assemblies make sure you read Packaging and Deploying Resources.
When creating satellite assemblies in the past, I have found it useful to use VS build events. These are the steps I would take.
Create a separate Class Library project in my solution.
Create or Add my .resx files to this project.
Add a Post-Build Event to the Project Properties dialog. (Like the one below)
Sample VS Post-Build Script:
set RESGEN="C:\Program Files\Microsoft SDKs\Windows\v6.0A\bin\resgen.exe"
set LINKER="C:\Program Files\Microsoft SDKs\Windows\v6.0A\bin\al.exe"
set ASSEMBLY=$(TargetName)
set SOURCEDIR=$(ProjectDir)
Set OUTDIR=$(TargetDir)
REM Build Default Culture Resources (en)
%RESGEN% %SOURCEDIR%en\%ASSEMBLY%.en.resx %SOURCEDIR%en\%ASSEMBLY%.resources
REM Embed Default Culture
%LINKER% /t:lib /embed:%SOURCEDIR%en\%ASSEMBLY%.resources /culture:en /out:%OUTDIR%%ASSEMBLY%.resources.dll
REM Embed English Culture
IF NOT EXIST %OUTDIR%en\ MKDIR $%OUTDIR%en\
%LINKER% /t:lib /embed:%SOURCEDIR%en\%ASSEMBLY%.resources /culture:en /out:%OUTDIR%en\%ASSEMBLY%.resources.dll
REM These are just a byproduct of using the project build event to run the resource build script
IF EXIST %OUTDIR%%ASSEMBLY%.dll DEL %OUTDIR%%ASSEMBLY%.dll
IF EXIST %OUTDIR%%ASSEMBLY%.pdb DEL %OUTDIR%%ASSEMBLY%.pdb
If you would prefer not to use ResGen.exe to convert your .resx files, you could do something like this.
using System;
using System.Collections;
using System.IO;
using System.Resources;
namespace ResXConverter
{
public class ResxToResource
{
public void Convert(string resxPath, string resourcePath)
{
using (ResXResourceReader resxReader = new ResXResourceReader(resxPath))
using (IResourceWriter resWriter = new ResourceWriter(
new FileStream(resourcePath, FileMode.Create, FileAccess.Write)))
{
foreach (DictionaryEntry entry in resxReader)
{
resWriter.AddResource(entry.Key.ToString(), entry.Value);
}
resWriter.Generate();
resWriter.Close();
}
}
}
}
One of the potential draw backs to doing the conversion this way is the need to reference the System.Windows.Forms.dll. You will still need to use Assembly Linker.
Edit: As wRAR has reminded us if you are signing your assemblies your keys must match.
While this is strange, especially for people familiar with open source localization technologies, one cannot build a satellite assembly for any system assembly or even a 3rd-party signed one:
If your main assembly uses strong
naming, satellite assemblies must be
signed with the same private key as
the main assembly. If the
public/private key pair does not match
between the main and satellite
assemblies, your resources will not be
loaded.
Whether the same is possible automatically, but without a satellite assembly, is unknown, though I doubt that.
If the server doesn't have .NET language packs installed then no matter what CurrentUICulture is set to, you'll always get English in DataAnnotations validation messages. This epic hack works for us.
Go to "Microsoft .NET Framework 4.6.1 Language Pack" download page https://www.microsoft.com/en-us/download/details.aspx?id=49977
Select language and download
Extract NDP461-KB3102436-x86-x64-AllOS-{LANG}.exe with 7-Zip
Extract CAB file x64-Windows10.0-KB3102502-x64.cab with 7-Zip
Locate "msil_system.componentmod..notations.resources_...."
... in which you'll find "system.componentmodel.dataannotations.resources.dll"
Open .resources.dll with ILSpy, locate Resources and click Save button above String Table to save as System.ComponentModel.DataAnnotations.Resources.DataAnnotationsResources.{LANGUAGE}.resources
Add to your project under say a "Resources"
Ensure the files Build Action property of the resources files is set to "Embedded Resource"
Then in a PreStart method of your project you overwrite the System.ComponentModel.DataAnnotations.Resources.DataAnnotationsResources.resourceMan private static field (told you it was a hack) with the ones you have in your project.
using System;
using System.Linq;
using System.Reflection;
using System.Resources;
[assembly: WebActivator.PreApplicationStartMethod(typeof(ResourceManagerUtil), nameof(ResourceManagerUtil.PreStart))]
class ResourceManagerUtil
{
public static void PreStart()
{
initDataAnnotationsResourceManager();
}
/// <summary>
/// If the server doesn't have .NET language packs installed then no matter what CurrentUICulture is set to, you'll always get English in
/// DataAnnotations validation messages. Here we override DataAnnotationsResources to use a ResourceManager that uses language .resources
/// files embedded in this assembly.
/// </summary>
static void initDataAnnotationsResourceManager()
{
var embeddedResourceNamespace = "<YourProjectDefaultNamespace>.<FolderYouSavedResourcesFilesIn>";
var dataAnnotationsResourcesName = "System.ComponentModel.DataAnnotations.Resources.DataAnnotationsResources";
var thisAssembly = typeof(ResourceManagerUtil).Assembly;
var dataAnnotationsAssembly = typeof(System.ComponentModel.DataAnnotations.ValidationAttribute).Assembly;
var resourceManager = new ResourceManager(embeddedResourceNamespace + "." + dataAnnotationsResourcesName, thisAssembly);
// Set internal field `DataAnnotationsResources.resourceMan`
var dataAnnotationsResourcesType = dataAnnotationsAssembly.GetType(dataAnnotationsResourcesName);
var resmanProp = dataAnnotationsResourcesType.GetField("resourceMan", BindingFlags.NonPublic | BindingFlags.Static);
resmanProp.SetValue(null, resourceManager);
}
}
Assuming that you want to override the default error message strings in the validation attributes, you can do that by setting the ErrorMessageResourceName and the ErrorMessageResourceType properties like this:
[Required(ErrorMessageResourceName = "Required_Username", ErrorMessageResourceType = typeof(MyResourceFile)]
public string Username { get; set; }
You can create a resource file called MyResourceFile.resx that contains Required_Username with the error message you want.
Hope this helps.
I want to provide an answer with the same idea as by Duncan Smart, but for .NET Core 2.2 instead of .NET Framework 4.x.
Here it is.
using System;
using System.Linq;
using System.Reflection;
using System.Resources;
public static class ResourceManagerHack
{
/// <summary>
/// If the server doesn't have .NET language packs installed then no matter what CurrentUICulture is set to, you'll always get English in
/// DataAnnotations validation messages. Here we override DataAnnotationsResources to use a ResourceManager that uses language .resources
/// files embedded in this assembly.
/// </summary>
public static void OverrideComponentModelAnnotationsResourceManager()
{
EnsureAssemblyIsLoaded();
FieldInfo resourceManagerFieldInfo = GetResourceManagerFieldInfo();
ResourceManager resourceManager = GetNewResourceManager();
resourceManagerFieldInfo.SetValue(null, resourceManager);
}
private static FieldInfo GetResourceManagerFieldInfo()
{
var srAssembly = AppDomain.CurrentDomain
.GetAssemblies()
.First(assembly => assembly.FullName.StartsWith("System.ComponentModel.Annotations,"));
var srType = srAssembly.GetType("System.SR");
return srType.GetField("s_resourceManager", BindingFlags.Static | BindingFlags.NonPublic);
}
internal static ResourceManager GetNewResourceManager()
{
return new ResourceManager($"{typeof(<YourResource>).Namespace}.Strings", typeof(<YourResource>).Assembly);
}
private static void EnsureAssemblyIsLoaded()
{
var _ = typeof(System.ComponentModel.DataAnnotations.RequiredAttribute);
}
}
And I call this like so:
public static void Main(string[] args)
{
ResourceManagerHack.OverrideComponentModelAnnotationsResourceManager();
CreateWebHostBuilder(args).Build().Run();
}
Furthermore, I created a ~/Resources/<YourResource>.resx file and populated it with the default values and changed them at will. Lastly I created a public empty class <YourResource>.

Resources