Get actually used (overridden) stylesheets

Topics: Writing modules
Developer
Oct 18, 2011 at 12:05 AM

I'm trying to further develop the Combinator module. The problem I'm currently facing is the following:

If a theme has a base theme, than it can override among others also its parent's style sheets. Now the work of resolving these overrides happens in src\Orchard\DisplayManagement\Descriptors\ResourceBindingStrategy\StylesheetBindingStrategy.cs in the Discover method. For me it looks that there is no possible way for a module to get the list of the actual stylesheets. There is no way to override the Discover method (it's not virtual) by suppressing the Orchard dependency. There is no way to get the result out of the Discover method since it's void and it only calls ResourceManager.WriteResource() with the resolved paths, which being a static method can't be overridden either.

I'm kind of stuck here. Any thoughts what could be done? It seems to me there is no way without modifying core classes.

Coordinator
Oct 18, 2011 at 7:53 PM

Well, if that's what it takes, please file a bug and submit a patch :)

Developer
Oct 18, 2011 at 11:02 PM

I've opened an issue.

Let's make a deal: I'll implement and submit a patch if you assist me with architectural questions :-).

I've thought along the lines of the following: StylesheetBindingStrategy should notify the ResourceManager. StylesheetBindingStrategy would keep a track of the actual stylesheets (making a list of resources) and then call a method on the IResourceManager instance like _resourceManager.OverrideStylesheets(IList<ResourceDefinition> resources) where OverrideStylesheets() is virtual. This would overwrite the styles stored in the ResourceManager.

What do you think?

Coordinator
Oct 18, 2011 at 11:45 PM

Well, sure but I would have done that anyways ;)

Sounds ok, but I'll summon Sébastien and Dave just in case.

Developer
Oct 19, 2011 at 10:38 AM
Edited Oct 19, 2011 at 2:22 PM

Thank you, I'm looking forward to your collective advise!

Developer
Oct 22, 2011 at 10:34 PM

Any news? Should I start with the approach I mentioned?

Coordinator
Oct 22, 2011 at 11:00 PM

Looks like my summoning wasn't very efficient. I'll try to resort to a higher level spell.

Developer
Oct 23, 2011 at 10:21 AM
Edited Oct 23, 2011 at 2:45 PM

Hi!

Use the spell "Have project, will implement", Piedone does that to us all the time, when he wants to discuss stuff about our common projects/studies (like our project related to Orchard). :)

For the Code!

Coordinator
Oct 24, 2011 at 8:20 PM

Help me understand the goal here? That StylesheetBindingStrat is discovering stylesheets and turning them into Shapes. Any template or module that is active can override the stylesheet by simply having one with the same file name, since it will also be turned into a shape with the same name.

I'm not sure but it may be possible to enumerate all the shapes in the system and poke and prod them to find out what they are.

Developer
Oct 24, 2011 at 9:21 PM

AFAIK it's not possible since the actually used stylesheets (so the overridden ones) are only written to the output, the result is not stored anywhere else.

Developer
Nov 2, 2011 at 7:49 PM

I started to experiment with the approach I described above. Now it won't work, as there is a problem getting an IResourceManager in StylesheetBindingStrategy's constructor (causes "No scope matching the expression 'value(Autofac.Builder.RegistrationBuilder`3+<>c__DisplayClass0[System.Object,Autofac.Builder.ConcreteReflectionActivatorData,Autofac.Builder.SingleRegistrationStyle]).lifetimeScopeTag.Equals(scope.Tag)' is visible from the scope in which the instance was requested."). So it's not possible to notify the IResourceManager instance there (I guess it's already disposed there).

Developer
Nov 2, 2011 at 9:41 PM
Edited Nov 8, 2011 at 9:28 AM

I've implemented a possible solution in the fork OverriddenResources. This is far from being thoroughly tested, but it shows a basic approach: ResourceManager saves every resource written to the output (in the WriteResource method) in a static property named OverriddenResources. Now since this static property lives as long the shell is alive, it eventually will keep all of the written resources.

Now it will be the task of the users (like my Combinator module) to process this data as they wish. I came to the conclusion that resources (namely stylesheets) should be named uniquely, despite being in different folders (like module folders), otherwise they won't be written. This simplifies the task of keeping track of actually used resources, so the OverriddenResources property can be a dictionary where the key is the resource's file name. That said, e.g.  if one would like to get the actually used stylesheets for a page, one would do the following:

  1. Get the included/required resources (from the IResourceManager instance)
  2. For every resource present get the corresponding actually used, overridden resource (by file name) from OverriddenResources

It could lead to problems that naturally OverriddenResources is only populated fully after all the resources have been written. This means that most possibly all of the resources used on a page can only be retrieved on a subsequent page load, but I haven't investigated this in detail.

Developer
Nov 8, 2011 at 9:35 AM

Could somebody from the Orchard team please take a look at the approach?

Coordinator
Nov 8, 2011 at 7:35 PM

Thanks for doing that. Now for some questions and constructive criticism ;)

I don't think we can impose such a rule of uniqueness. What's wrong with the full path?

I don't like the name "OverridenResources" as at this point they have not been overridden and this may be used for something else for all you know.

What do you store? Is it just the name of the resources?

What happens in the transition period when all resources have not been used?

Can you describe the whole lifecycle of this thing, including the usage in your module?

Developer
Nov 8, 2011 at 8:44 PM
Edited Nov 8, 2011 at 8:47 PM

Thanks for the reply! Of course constructive criticism is always warmly welcome!

 I don't think we can impose such a rule of uniqueness. What's wrong with the full path?

Sorry, I've phrased my sentence not clear enough: this is the rule currently. I think the cause is that in ResourceManager's Require() the key used to identify a resource is it's name (i.e. the file name) and it's type. So now no resources of the same type and with the same file name can be used concurrently. As I've looked at Require() for this only now, the implementation for keeping track of overridden resources should really be the same

I don't like the name "OverridenResources" as at this point they have not been overridden and this may be used for something else for all you know.

Sure, I haven't thought too long about this, so the name could be changed to something that is more meaningful to all of us.

What do you store? Is it just the name of the resources?

Stored are the ResourceDefinition objects that WriteResource() gets from StylesheetBindingStrategy's Discover(), their urls changed with the actual one.

What happens in the transition period when all resources have not been used?

If I understand correctly, the answer for this is also described below.

Can you describe the whole lifecycle of this thing, including the usage in your module?

The life cycle, including where my module would kick in. I've underlined the parts where the code I've shown changes something and marked with an arrow if the following sentences have to do with the Combinator module.

  1. Orchard instance gets created, ResourceManager's static constructor runs somewhere, so OverriddenResources gets initialized.
  2. On a page view all the resources get included/required through the ResourceManager instance. The resources now stored are not necessarily the ones that will get used, as they could be overridden later.
  3. Somehow with all the magic we get to StylesheetBindingStrategy's Discover(). Here the code which is responsible for get the overrides of stylesheets sits in a lambda expression, not yet evaluated. (Actually it is almost possible to get the actually used stylesheet urls from the stylesheet links' displaycontext, but the key value - hit.fileVirtualPath - is not shared with the context.)
  4. ResourceManager.BuildRequiredResources() is first called (from CoreShapes.WriteResources()), to build the stylesheets. -> Since Combinator suppresses ResourceManager, here its method is called, but now nothing can be done with overridden styles since the actual stylesheets are not known yet (this resolving comes later). Therefore, the list of included stylesheets is returned, which we know, not necessarily contains the actually used stylesheets.
  5. ResourceManager.WriteResource() (a static method!) gets called for every stylesheet. This is now where we can get the actually used stylesheet urls, the method gets them in the argument named url. OverriddenStylesheets gets filled up one by one with the actually used stylesheets.
  6. ResourceManager.BuildRequiredResources() is called for scripts. -> This is where Combinator can get the list of the actually used stylesheets and process them as described in my Nov 2 post to make a list of them for the current page. Although it's late for this page view, but for subsequent page views with the same set of stylesheets later the list of actually used stylesheet can be used. Since OverriddenStylesheets gets filled incrementally with every new page view, if all the pages get viewed in the life time of the shell, it will contain all actually used resources. This is not an issues when determining which ones to use on a specific page, when using the approach written in the post.
  7. ResourceManager.WriteResource() gets called for script files.
  8. ResourceManager.BuildRequiredResources() is called again for scripts. I guess this is because it gets called separately when detecting head and foot scripts, which is actually unnecessary, since as BuildRequiredResources() doesn't handle script locations, it returns all the scripts both times.
  9. ResourceManager.WriteResource() gets called again for script files (so these should be the foot scripts).
Developer
Nov 8, 2011 at 9:03 PM

I've uploaded a new version. I figure out that I've actually forgotten to implement saving of the actually used stylesheets' urls. Now the essence of all the above is in these lines in ResourceManager.WriteResource():

            var key = new Tuple<String, String>(resource.Type, resource.Name);
            resource.SetUrl(url);
            OverriddenResources[key] = resource;

Coordinator
Nov 9, 2011 at 10:50 PM

OK then, thanks for the explanation. Personally, I don't see anything wrong with the core resource management gathering this data and making it available for modules like yours to use. What do others think?