Linking to 3rd party online docs

Topics: User Forum
Feb 14, 2008 at 2:07 PM
I am trying to link from within my XML doc comments to 3rd party online docs without having to hard code the URL in my source code.
This means, I am not including these docs as "additional content" (which would mean including the physical files).

So far I have not found a way to configure this with the SHFB GUI.

My first approach was to simply do it by including an entity reference (to be expanded later) in my doc comments, and then manually inserting
XML entity declarations in the XML file generated by the C# compiler. Unfortunately, the C# compiler refuses to precess such comments as it expects the entities to have been declared already, which - of course - cannot be done in the doc comments.

So my question is, has anyone dealt with - and solved - a similar problem already, preferably within SHFB?
I am afraid I might have to write a plugin to post-process the generated XML files.

Karl
Coordinator
Feb 14, 2008 at 10:50 PM
For anyone that's interested, the thread is discussed here: http://www.codeplex.com/Sandcastle/Thread/View.aspx?ThreadId=22152.

Eric
Feb 21, 2008 at 7:23 PM

To report the conclusion: Dave Sexton wrote a very nice build component http://www.codeplex.com/DocProject/Release/ProjectReleases.aspx?ReleaseId=10831 to provide external linking. It has a few dependencies on DocProject (they only exist for the build, but not at runtime) and the GUI is missing a tiny bit that normally the DocProject GUI would provide.

I made some changes, adding the missing editing of global settings to the GUI editor, and I also removed all dependencies on DocProject from the source so that one can build it as a standalone project. If anyone is interested, drop me an e-mail.

Karl
Feb 22, 2008 at 3:51 AM
Hi Karl,

FYI, I'm planning to release an update soon for DocProject (to fix some bugs in the last release). The update will ship with the component as part of the solution and automatically add it to new projects.

The features that you requested (setting <component> level attributes in the editor and extending the <seealso> tag) will be included as well. To build the component from the source code you'll need DocProject installed so you'll have to strip out that stuff manually, again, if you'd like.

- Dave
Feb 22, 2008 at 6:44 PM
Hi Dave,


davedev wrote:
FYI, I'm planning to release an update soon for DocProject (to fix some bugs in the last release). The update will ship with the component as part of the solution and automatically add it to new projects.

The features that you requested (setting <component> level attributes in the editor and extending the <seealso> tag) will be included as well.


I added these myself, as I didn't want to bug you.


To build the component from the source code you'll need DocProject installed so you'll have to strip out that stuff manually, again, if you'd like.


I wonder if one could not change the design (maybe using the mediator pattern) so that there is no build time dependency.

Anyway, Thanks again for your work,

Karl
Feb 22, 2008 at 7:48 PM
Hi Karl,


I added these myself, as I didn't want to bug you.

Cool, but you weren't bothering me with those requests. I improved the component anyway since I'd like to use it too :)


I wonder if one could not change the design (maybe using the mediator pattern) so that there is no build time dependency.

Sounds interesting. Care to elaborate?

From my POV, there's always going to be a build-time dependency since no matter the design pattern that's chosen, a contract must be defined that doesn't already exist in the FCL for the components to be able to use custom services (in this case, IDocProjectHost). Even if Eric and I developed a global interface that could be used, the most we'll have in common is probably just going to be IServiceProvider anyway, and the current design that I'm using already relies on that interface heavily.

- Dave
Feb 22, 2008 at 11:43 PM
Hi Dave,


davedev wrote:

I wonder if one could not change the design (maybe using the mediator pattern) so that there is no build time dependency.

Sounds interesting. Care to elaborate?


In this case it would probably not be too complicated. I had a brief look at the dependencies (not much time...) and from what I can see, the build component wants three things from IDocProjectHost: a settings directory, a current directory (both not absolutely necessary) and a list of topic ids.

One way to do this is to define an interface (as part of the build component's definitions) that has methods to retrieve such properties, like getSettingsDirectory(), etc.
The build component itself has a property of that interface type, so that a host (or IOC container) can assign to it. There is no dependency on the host here.

The "mediator" would have to be in a separate assembly (maybe part of an IOC container) and it would implement that interface, but also depend on the host (e.g. DocProject) to forward/translate the call-backs to the host. You could also call this an "adapter". So, the build component would check if the call-back interface is set, and then call its methods to get the settings directory, etc. This call would trigger a method in the mediator, which in turn calls the host.

You likely need an adapter for each host that can provide these extra features, but as long as they are optional, you can do without an adapter.

There are newer approaches (AOP, mixins) that I haven't really studied too much. Check out http://postsharp.org, http://www.dcl.hpi.uni-potsdam.de/research/loom/, http://www.re-motion.org/blogs/team/Default.aspx, http://code.google.com/p/autofac/ for a few interestin g reads.

Karl
Feb 23, 2008 at 2:01 AM
Hi Karl,

Thanks for the reply.

I see where you're going, but there are multiple problems with an IoC approach.

For one thing, DocProject would have to be built against the component library to use its interface. To solve that problem, moving the dependency to another library and adding adapter support just adds complexity to the distribution of the component. It's adding another assembly for deployment. And more importantly, it adds a new dependency to DocProject, which currently has no dependency at all on ResolveExternalLinksComponent. I really don't think DocProject should have to be built against an optional component library.

It's also less flexible than the current implementation, where the host provides services and the component optionally consumes them. Yes, it may be a little bit harder now for you to pull out the code that depends on DocProject, but my goal for the architecture wasn't so that components could be easily refactored and rebuilt for different hosts. The goal was to be able to easily encapsulate support for multiple host services in a single item template. I think I've accomplished that, don't you?

I'd even argue that it's not too difficult in the current implementation to remove DocProject dependencies. It can be done by deleting the host class, building and then deleting the broken references (there's not that many of them). I could improve the architecture, however, by moving the InitializeForDocProject method into the host class instead of the configuration class.

But to be honest, I'm not sure that it's even worth it to make the architecture more flexible seeing that DocProject is the only tool, of which I'm aware, that even provides a public API to component editors. So really I'd just be doing all of this extra work so you can pull it out! :D

I don't think AOP is going to help in this situation because, again, the IoC approach doesn't seem to be worth the effort. The problem stems from having to define a specific interface for the component that the host must be able to use. Similarly, with AOP, the aspects would have to be defined in the item template in order to provide the same benefit that you wanted to achieve with your adapter solution.

Although I've actually considered using AOP (ContextBoundObject) for something else. I'm currently in the process of creating an AOP framework for things like change buffering and notification that can be used by the DocProjectOptions class. Although the framework itself will not actually be part of DocProject - it will just be a new dependency. (If I ever complete it ;)

Thanks,
Dave
Feb 23, 2008 at 3:47 AM


davedev wrote:
For one thing, DocProject would have to be built against the component library to use its interface. To solve that problem, moving the dependency to another library and adding adapter support just adds complexity to the distribution of the component. It's adding another assembly for deployment. And more importantly, it adds a new dependency to DocProject, which currently has no dependency at all on ResolveExternalLinksComponent. I really don't think DocProject should have to be built against an optional component library.


I am not sure where you think the dependency is. It's only the mediator that depends on the component interface. DocProject does not depend on the mediator, and therefore it also does not depend on _ResolveExternalLinksComponent.


It's also less flexible than the current implementation, where the host provides services and the component optionally consumes them.


How? Nothing about optionally consuming the host services would change. The mediator knows about these services and provides them to the component, optionally.


Yes, it may be a little bit harder now for you to pull out the code that depends on DocProject, but my goal for the architecture wasn't so that components could be easily refactored and rebuilt for different hosts. The goal was to be able to easily encapsulate support for multiple host services in a single item template. I think I've accomplished that, don't you?


Well, my goal would be different, as I think the component should be decoupled from its potential hosts.
Anyway, it ws just a friendly suggestion. It will actually be a nice exercise to do this for myself.

Karl
Feb 23, 2008 at 4:45 AM
Edited Feb 23, 2008 at 4:46 AM
Hi Karl,


I am not sure where you think the dependency is. It's only the mediator that depends on the component interface. DocProject does not depend on the mediator, and therefore it also does not depend on _ResolveExternalLinksComponent.

Then I must have misunderstood you. The component is already using a similar pattern, so I assumed that you were suggesting that I use IoC to change the way the host communicates with the component.

Currently, the host provides services and the component consumes them. IoC would be the other way around - the component provides a specific interface for the host, but to decouple it requires an adapter. That's what I thought you were suggesting.

So now I'm not sure what value there is in your solution over my original architecture; although, as I acknowledged already in my last post, I could have provided a better implementation by encapsulating all of the host-specific interfaces in the host class, which is what it was originally intended for.

Still though, adding another library and an adapter doesn't seem to add any value. If I did encapsulate everything in the host class, which was my original intention, wouldn't that be similar to what you suggested and serve the same purpose?



It's also less flexible than the current implementation, where the host provides services and the component optionally consumes them.

How? Nothing about optionally consuming the host services would change. The mediator knows about these services and provides them to the component, optionally.

Again, I misunderstood your comment about IoC then.


Well, my goal would be different, as I think the component should be decoupled from its potential hosts

Yea, me too. That's the point of the host class :)


Anyway, it ws just a friendly suggestion. It will actually be a nice exercise to do this for myself.

I know, and I appreciate it :)

Please don't take my criticism the wrong way. I respect your ideas and I was just comparing them to my current solution, which I put a lot of thought into.

- Dave
Feb 23, 2008 at 1:04 PM
Hi Dave,


davedev wrote:

I am not sure where you think the dependency is. It's only the mediator that depends on the component interface. DocProject does not depend on the mediator, and therefore it also does not depend on _ResolveExternalLinksComponent.

Then I must have misunderstood you. The component is already using a similar pattern, so I assumed that you were suggesting that I use IoC to change the way the host communicates with the component.

Currently, the host provides services and the component consumes them. IoC would be the other way around - the component provides a specific interface for the host, but to decouple it requires an adapter. That's what I thought you were suggesting.


I guess there was a misunderstanding. I thought what you meant with host was the implementation of IDocProjectHost, not the ResolveExternalLinksComponentHost class.


So now I'm not sure what value there is in your solution over my original architecture; although, as I acknowledged already in my last post, I could have provided a better implementation by encapsulating all of the host-specific interfaces in the host class, which is what it was originally intended for.


Yes, if you moved all IDocProjectHost dependencies into the ResolveExternalLinksComponentHost class and then separated it out into its own assembly, then you actually have your adapter/mediator. You might choose to use just one assembly for all adapters, if there were more than one.

Short side note: the interface I suggested is only necessary if the build component needs to dynamically query the host for certain information. It is not needed if all that needs to be done is initializing the build component, in which case the adapter/mediator can simply set the respective properties on the component.


Still though, adding another library and an adapter doesn't seem to add any value. If I did encapsulate everything in the host class, which was my original intention, wouldn't that be similar to what you suggested and serve the same purpose?


Yes, once it is moved out of the build component's assembly this becomes pretty much the adapter I suggested. As it stands, if you want to modify the build component you have to drag in the whole DocProject to rebuild it. So the value for me is that I could rebuild the component without having to download and install another project.

It may well be that I start using DocProject in the future, however right now I am still used to the NDoc style of doc processing represented by SHFB.



Well, my goal would be different, as I think the component should be decoupled from its potential hosts

Yea, me too. That's the point of the host class :)


Anyway, it ws just a friendly suggestion. It will actually be a nice exercise to do this for myself.

I know, and I appreciate it :)

Please don't take my criticism the wrong way. I respect your ideas and I was just comparing them to my current solution, which I put a lot of thought into.


I can see that!

Best wishes,

Karl
Feb 23, 2008 at 3:15 PM
Hi Karl,

Well then I'll take the blame for the ambiguity. I used host and host class to mean ResolveExternalLinksComponentHost since I was also using configuration and configuration class to mean ResolveExternalLinksComponentConfiguration, based on the component template that I developed.

The only problem now that I have is moving the host class into another assembly. The item template and the component should be simple I think and adding another assembly seems like overkill.

But I don't want to make it any more difficult for you than it has to be. So I think I'll just use a simpler approach where you'll only have to delete a single class and a single reference to that class instead of having to look for dependencies in multiple places.

To accomplish that I'll move the InitializeForDocProject method into the host class, rename it Initialize and make it abstract. Then I'll create a specialization that overrides Initialize to provide the InitializeForDocProject implementation and call it, ResolveExternalLinksComponentDocProjectHost. The configuration class will query a static factory method on the base host class for a host implementation. The base host class will ask the new DocProject host class if it's usable (i.e., IDocProjectHost is available) and if not, will just fall back to null state like it currently does. If it is usable, it will be returned and the configuration class will initialize the host and then read from new properties to gather its state during initialization (note that this means the required properties have to be defined three times, which is something that I was trying to avoid in my original design). Now DocProject's host services will actually be encapsulated, so you'll only have to delete the new host specialization and a single reference in the base host class (the static factory method). Technically, you could even create your own specialization to gather the same information if it's available using a different approach in SHFB.

Does that sound better for you than the current implementation?

Remember that I'd like to think about the architecture in terms of my item template, which I'll probably update with these change as well, so keeping it simple is a priority; e.g., a single item template, a single assembly.

- Dave
Feb 23, 2008 at 4:14 PM
Hi Dave,


davedev wrote:
Well then I'll take the blame for the ambiguity. I used host and host class to mean ResolveExternalLinksComponentHost since I was also using configuration and configuration class to mean ResolveExternalLinksComponentConfiguration, based on the component template that I developed.

No need to take the blame, naqtural language just isn't precise enough to always avoid misunderstandings.
At least we were smart enough to figure out. ;-)

The only problem now that I have is moving the host class into another assembly. The item template and the component should be simple I think and adding another assembly seems like overkill.

But I don't want to make it any more difficult for you than it has to be. So I think I'll just use a simpler approach where you'll only have to delete a single class and a single reference to that class instead of having to look for dependencies in multiple places.

Sure, fine with me. You really don't need to cater to me individually. One reason I mentioned it is that decoupling makes the component easier to integrate with other service providers/hosts.

To accomplish that I'll move the InitializeForDocProject method into the host class, rename it Initialize and make it abstract. Then I'll create a specialization that overrides Initialize to provide the InitializeForDocProject implementation and call it, ResolveExternalLinksComponentDocProjectHost. The configuration class will query a static factory method on the base host class for a host implementation. The base host class will ask the new DocProject host class if it's usable (i.e., IDocProjectHost is available) and if not, will just fall back to null state like it currently does. If it is usable, it will be returned and the configuration class will initialize the host and then read from new properties to gather its state during initialization (note that this means the required properties have to be defined three times, which is something that I was trying to avoid in my original design). Now DocProject's host services will actually be encapsulated, so you'll only have to delete the new host specialization and a single reference in the base host class (the static factory method). Technically, you could even create your own specialization to gather the same information if it's available using a different approach in SHFB.

And that last point is exactly what I was aiming at.

Does that sound better for you than the current implementation?

Yes.

Remember that I'd like to think about the architecture in terms of my item template, which I'll probably update with these change as well, so keeping it simple is a priority; e.g., a single item template, a single assembly.


As long as your license allows for re-using your code with other hosts... :-)

Regards,

Karl
Feb 24, 2008 at 4:45 AM
Hi Karl,

Nice that we're in agreement then :)


Sure, fine with me. You really don't need to cater to me individually. [snip]

I know, but you're the only one providing feedback! Thanks BTW :)

But really I'm just trying to make the item template usable for everyone, and my links component was meant to be a working example. If that doesn't work properly for you then the item template isn't finished, IMO. You've brought up a unique concern that I didn't account for: being able to refactor-out host code. Obviously a good design will make that easy, and as you can see from my original design I had that in mind, but when implementing the links component I decided that I didn't want to make the design any more complex than it needed to be; e.g., three sets of the same public properties: configuration, abstract base host, specialization host.

I'm fine with this change though because it will make the item template easier to use for other hosts.



Remember that I'd like to think about the architecture in terms of my item template, which I'll probably update with these change as well, so keeping it simple is a priority; e.g., a single item template, a single assembly.

As long as your license allows for re-using your code with other hosts... :-)

Yep, it does. It's a true open source General Public License.

The point of creating and publishing the template is so that anyone can use it and for any purpose :)

- Dave