Single Assembly Design Reinstated; Stay of Execution for .NET 3.5 Support

The last post discussed a future design whereby Gepsio would ship as two assemblies and that, as a result of this work, would no longer be able to support .NET 3.5.

Never mind.

MissingManifestResourceException Thrown In UWP Calls To PCL Resources

Some work has gone into implementing the design discussed in that last post. The design centered around a Portable Class Library (PCL) which held Gepsio’s string resources as well as a small class that returned strings from the PCL to the caller. In the solution structure, a separate PCL called JeffFerguson.Gepsio.Resources which targeted Windows 10 and .NET 4 was created, and all of the Gepsio assemblies referenced that PCL project. The Gepsio RESX string table was added as a resource in the PCL, and a simple class was added to the PCL:

public class AssemblyResources
{
    public static string GetName(string Key)
    {
        return Gepsio.ResourceManager.GetString(Key);
    }

    public static string BuildMessage(string Key, params object[] Parameters)
    {
        var Message = new StringBuilder();
        string MessageFormat = GetName(Key);
        Message.AppendFormat(MessageFormat, Parameters);
        return Message.ToString();
    }
}

From there, the main Gepsio code would call this PCL class when string resources were needed.

For Gepsio’s .NET assemblies, this all worked well. Gepsio called into the PCL, which accessed a string resource and returned it to Gepsio. Perfect!

For Gepsio’s UWP assembly, however, the same code path crashed. Gepsio called into the PCL, but the call to the Resource Manager’s GetString() method threw an exception:

Result StackTrace:
at System.Resources.ResourceManager.GetString(String name, CultureInfo culture)
   at System.Resources.ResourceManager.GetString(String name)
   at JeffFerguson.Gepsio.Resources.AssemblyResources.GetName(String Key)
   at JeffFerguson.Gepsio.XbrlSchema..ctor(XbrlFragment ContainingXbrlFragment, String SchemaFilename, String BaseDirectory)
   at JeffFerguson.Gepsio.XbrlFragment.ReadTaxonomySchemaReference(INode SchemaRefNode)
   at JeffFerguson.Gepsio.XbrlFragment.ReadTaxonomySchemaReferences()
   at JeffFerguson.Gepsio.XbrlFragment..ctor(XbrlDocument ParentDocument, INamespaceManager namespaceManager, INode XbrlRootNode)
   at JeffFerguson.Gepsio.XbrlDocument.Parse(IDocument doc)
   at JeffFerguson.Gepsio.XbrlDocument.Load(String Filename)
   at XbrlConformanceSuiteTests.ConformanceSuiteUnitTests.TestVariation(String testcaseFolderName, String instance, String description, Boolean shouldBeValid)
   at XbrlConformanceSuiteTests.ConformanceSuiteUnitTests.<ExecuteTestcase>d__2.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.GetResult()
   at XbrlConformanceSuiteTests.ConformanceSuiteUnitTests.<XbrlConfCR520120124>d__1.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
Result Message:	Test method XbrlConformanceSuiteTests.ConformanceSuiteUnitTests.XbrlConfCR520120124 threw exception:
System.Resources.MissingManifestResourceException: Unable to load resources for resource file "JeffFerguson.Gepsio.Resources.Gepsio" in package "bc7a23a2-a1ca-4481-8eb3-74680ae1e9c8".

This issue was brought up in a Stack Overflow question, but no answers were offered.

So much for the PCL being portable.

Resource Management Plan Going Forward

The backup plan is to maintain two separate string tables. One string table will be maintained in the Gepsio .NET projects as an RESX file, and a second string table will be maintained in the Gepsio UWP projects as an RESW file. It’s a shame that the string tables will need to be maintained in two places, but, if a single portable location isn’t going to work, then there is not much choice. Hopefully, it won’t be a huge issue.

Retaining Support for .NET 3.5

The last post mentioned that PCLs do not support .NET 3.5 and, as a result of the proposed Gepsio design making use of PCLs, future versions of Gepsio would not be able to support .NET 3.5. Now that the PCL design has proven to be unworkable, Gepsio can and will support .NET 3.5 for the foreseeable future.

Advertisements

Next Release: Two Assemblies; .NET Support Changes to 4.0 and Above

Gepsio‘s support for the Windows 10 Universal Windows Platform (UWP) will bring two changes to Gepsio that will be new to the platform:
  • Gepsio will ship as two assemblies, rather than a single assembly.
  • Gepsio will no longer be able to support .NET 3.5 and will support only .NET 4.x.
This blog post will examine both of those changes and why they are necessary to be sacrificed to allow Gepsio to support UWP.

A Tale of Two Assemblies

Let’s first take a look at the need for Gepsio to be implemented in two assemblies, rather than just one as in the past.

Gepsio’s Use of String Resources

Gepsio not only parses XBRL, but also validates the contents of an XBRL instance against the XBRL specification. Validation errors are available for inspection after the document instance is loaded, as in the following example:
var myDoc = new XbrlDocument();
myDoc.Load("MyXbrlDocument.xml");
if(myDoc.IsValid == false)
{
    foreach(var currentValidationError in myDoc.ValidationErrors)
    {
        // the currentValidationError.Message property
        // provides more information
        // about the validation error
    }
}
The messages are built into the Gepsio code base through a resource (RESX) file. This RESX file, which is actually an XML document, contains the strings used to build the validation messages. Building these messages into a separate RESX file, rather than embedding them into the source code directly, allows the strings to be translated into various languages by providing a separate XML file for the new language, without having to change the source code itself. Since XBRL is an international specification, it makes sense to keep strings segregated where they can be easily located and changed when Gepsio needs to support languages other than English.
Microsoft changed the way string resources work for UWP assemblies. Resources are now stored in a RESW file, which has the same underlying XML structure as an RESX file but is incompatible with the tooling for .NET applications. Moreover, the code needed to access strings in an RESW file differs from the code needed to access strings in an RESX file.

Possible Design Approaches

The difference in approach between RESX files and RESW files impacts Gepsio, which strives to have a code base that supports .NET as well as UWP. There are several possible approaches to this problem, and they are described below.

One RESX for .NET, One RESW for UWP

With this approach, two resource files would be maintained: an RESX file for the .NET projects, and a RESW for the UWP project. This would cause a maintenance headache, as a developer working on Gepsio would have to remember that any strings added or modified in the RESX would have to be done again in the RESW file, and forgetting to do so will cause an incompatibility between Gepsio’s .NET implementation and its UWP implementation. If a string was found in one file and not the other, Gepsio may even crash at runtime when trying to find a string resource that doesn’t actually exist. Furthermore, since the code needed to access a string from an RESX file differs from the code needed to access a string from an RESW file, the code would also need to be maintained twice. Generally, making two modifications in two files can be more error-prone than making one modification in one file.

One RESX for .NET, Copied And Renamed for UWP

With this approach, one RESX file would be maintained in Gepsio’s .NET project, and a custom step in the build workflow would copy the RESX file over to the UWP project, rename it with an RESW extension, and allow the UWP project to build with the new RESW file. This approach would have the advantage of only needing to maintain one resource file. However, this would also introduce new complexities into the build process and would still require that two separate codebases be maintained: one to access the RESX file and another to access the RESW file.

Resources in a Portable Class Library

With this approach, the project would maintain a Portable Class Library (PCL) containing Gepsio’s string resources. The PCL would be targeted to support .NET as well as UWP. The PCL project would maintain the string resources as part of the project, as a single RESX file in a single location. The PCL would also contain code to access and return strings and Gepsio, in both its .NET and UWP personalities, would call this portable code when strings are needed.

Approach Going Forward

After considering all of the options (and lamenting the fact that the RESX/RESW discrepancy exists in the first place) it was decided to go down the PCL route. This means that, starting with the next release, Gepsio will ship as two assemblies:

  • the main Gepsio assembly
  • the Gepsio string resources assembly

The disadvantage here is that there are now two assemblies to distribute, rather than just the single assembly shipped up this point. The mitigation here is that distribution tools such as NuGet make distribution easier and more automatic, and the chances that a user would ship one Gepsio assembly and forget the other is reduced thanks to these tools.

The other disadvantage to the PCL approach leads us into the next change for the Gepsio platform.

End of Support for .NET 3.5

The “resources in PCL” approach described above signals the end of Gepsio’s support for .NET 3.5 and restricts Gepsio to supporting only .NET 4.0 and above. PCLs do not support .NET 3.5 and their support for .NET goes back only to .NET 4.0. This means that future versions of Gepsio will support the following platforms:
  • .NET 4.0
  • .NET 4.5
  • .NET 4.5.1
  • .NET 4.5.2
  • .NET 4.6
  • .NET 4.6.1
  • Universal Windows
Though .NET 3.5 will be left off of this list, it is most likely not widely used these days. .NET 3.5 was released in November of 2007 and its replacement, .NET 4.0, was released in April of 2010. It is therefore possible than any installations using .NET 3.5 at one time have been upgraded some time in the last six years to one of the .NET 4.x runtimes.