OnTopicCMS/OnTopic-Editor-AspNetCore

`ReflexiveAttribute`?

Closed this issue · 4 comments

Create an attribute type which invokes an instance of the attribute it is placed on, along with its attributes, in order to allow e.g., a DefaultValue, ImplicitValue, or other value attributes to be defined using the same type and settings as the attribute. This would be an improvement over the current approach, which effectively only supports scalar values, and requires knowledge of how those types will be serialized.

That said, this introduces some challenges—and notably, how to dynamically set the EditorType in order to load the value from the correct source—which may not be practical, and will require additional thought.

Establishing the ModelType shouldn’t be too difficult. We know it’s grandparent should be a ContentTypeDescriptor describing the type it’s intended to map. It can use that information to construct a new AttributeDescriptor derivative using TopicFactory.Create(), and then relay that ModelType.

The only caveat is it won’t be able to do this if the grandparent is the ReflexiveAttributeDescriptor as that would cause an infinite loop. In that case, we can hard code ModelType.ScalarValue.

Update: The above information is incorrect. The grandparent will always return the ContentTypeDescriptor the ReflexiveAttribute was added to, but this may not be the type of the EditingTopicViewModel since AttributeDescriptors can be inherited from parent content types.

We’ll also need to handle this type of scenario in the view, so it doesn’t attempt to invoke a ReflexiveAttributeViewModel, which would likewise result in an infinite loop. In that case, we can just return a message explaining the situation. It doesn’t make sense in our case to have e.g., a DefaultValue on a DefaultValue regardless, as that’s highly specific to the individual AttributeDescriptor, so this shouldn’t be a major limitation.

The second challenge is how to get access to the appropriate view model for invoking the target view component. This seems easy in that he AttributeDescriptorViewModel will also be the EditingTopicViewModel.

Unfortunately, though, the type of object will be incorrect, as we need a derivative of AttributeDescriptorViewModel, not EditingTopicViewModel, which the EditorController explicitly maps the currentTopic to.

Options

EditorController

Explicitly handle this scenario in the EditorController by mapping to an AttributeDescriptorViewModel if the CurrentTopic is an AttributeDescriptor. This would require that AttributeDescriptorViewModel derive from EditingTopicViewModel. This is less than ideal, as the editor shouldn’t be aware of the needs of any one attribute type.

ITopicMappingService

Inject an ITopicMappingService into the ReflexiveViewComponent so that’s the currentTopic can be remapped as the specific AttributeDescriptorViewModel derivative identified in the ITypeLookupService. This introduces a dependency on ITopicMappingService and ITypeLookupService, which the StandardEditorComposer doesn’t currently have. This would either need to break backward compatibility by exposing those dependencies to the constructor, or be hardcoded, which isn’t ideal in terms of dependency injection.

Note: Since the OnTopic Editor is an application, it might seem to make sense for it to define its own dependency graph. But as it is embedded in other applications, we’d expect it to take dependencies supplied to the StandardEditorComposer, as it does today. Ultimately, as well-known dependencies that can optionally be constructed independently of the StandardEditorComposer, this might be tolerable. But it’s less-than-ideal, and should be resolved in OnTopic 6.0.0 (since we don’t want to make a breaking change to a minor release).

The above proposal regarding the ModelType is incorrect. Unfortunately, there isn’t an easy way around this without making the EditorController aware of the ReflexiveAttribute (e.g., to get the ModelType off of the CurrentTopic if the attribute is a ReflexiveAttributeDescriptor). I’d really like to avoid making the editor aware of any one attribute type—which is why we currently store them as plugins in a separate library.

Without this, the value will always be stored as in Topic.Attributes instead of e.g., Topic.Relationships or Topic.References.

This is accomplished by mapping the CurrentTopic as an AttributeDescriptorViewModel in the ReflexiveViewComponent (aebcc6f), so that the view can invoke the CurrentTopic as an attribute view component using the AttributeDescriptorViewModel it expects, with the current values configured for the AttributeDescriptorViewModel (2f44fc1).

This required that a dependency on the ITypeLookupService and ITopicMappingService (aebcc6f) be introduced, which have been added to the StandardEditorComposer (3efa14f).

Unfortunately, adding these to the constructor, as we'd usually do with dependencies—and which is a best practice for dependency injection!—isn't possibly without breaking the OnTopic Editor 5.0.0 contract, and thus this change will need to wait until OnTopic Editor 6.0.0. Positively, these are well-known objects which will always be available—since one is local, and the other is from the core OnTopic library—and these aren't dependencies we'd expect implementors to need to swap out. Therefore, for backward compatibility, they're being hard-coded into the StandardEditorComposer. As this operates as part of the composition root, and implementors can always wire up attribute view components using their own custom logic without relying on the StandardEditorComposer, this isn't a huge issue, but it's still a bit _ugly), and something we'll want to fix with OnTopic Editor 6.0.0.

This functionality was merged into develop as part of ebcc2b3.