I have a Table class that inherits a property from its base class DbTableBase, and hides the base class property MegaList<DbColumnBase> Columns with a more specific property MegaList<TableColumn> Columns.

Originally my app targeted Oracle only, but a recent refactoring into a common base so I could add SQLServer and other targets caused the Velocity templates to fail. My NVelocity template module is failing on any collections that have base implementations that are hidden by a derivation because it evaluates the base property.

  context.Put("table", table);  // table is Table, not TableBase

  ...

  #foreach ($c in $table.Columns)
  $c.Name
  #end

My container declaration is:

 public class MegaList<T> : ObservableCollection<T>
 {
     public MegaList() {} 
     // etc. etc.
 }

My Table type is has a _columns field that when exposed as a MegaList, NVelocity fails to iterate the leaf Table.Columns list:

public class Table : TableBase {

   MegaList<ColumnBase> _columns = new MegaList<ColumnBase>();

   public MegaList<ColumnBase> Columns   // template takes this one, even though it is hidden
   {
       get { return _columns; }
   }
}

public class Table : TableBase {

   new public MegaList<TableColumn> Columns   // Velocity ignores this, chooses base Columns
   {
      get { return _columns.DownCast<TableColumn>(); }
      set { _columns = value.UpCast<DbTableColumn, TableColumn>(); }
   }

   public MegaList<TableColumn> Columns2  // Velocity sees this 
   {
       get { return _columns;  }   
   }
 }

NVelocity is evaluating the base TableBase.Columns, yet if my template references $table.Columns2, NVelocity evaluates Table.Columns2.

I can't make the property virtual because the type is different, albeit a more specific Thinking out loud, I assume this behaviour is because VelocityContext holds "object" references, and has some sort of problem with choosing the correct property in the case of multiples, though I would assume it should choose the leaf property.

有帮助吗?

解决方案 2

I proved that this is a bug / limitation in NVelocity. I downloaded the source for NVelocity 1.1.0 (where is 1.1.1 from Nuget?) and linked into my project and traced to the issue. The introspection code is a bit naive and returns the first thing it finds regarding a property, not checking to see if there is a better candidate. With .NET reflection, there may be a list of properties for a type with the same name but different DeclaringType. Especially if a property is not virtual, and has hidden a base property, it must check the propInfo.DeclaringType == type of object or correct OO is out the window.

I patched NVelocity to work correctly with this aspect of C# inheritance. I will go through the codebase and see what else may need addressing regarding this. I will publish my patched version somewhere with improved documentation I hope.

I am boggled/bothered by how slim the docs are out there regarding NVelocity, and how little evidence of work or maintenance is going on, nor where the latest source code resides. I've had enough frustration with lack of movement with this and other projects that I may fork it for my own sake since I have commercial products depending on it.

其他提示

I have been unable to reproduce the error you are seeing, I've included my test code below if that helps, however...

One common reason you'll see a NVelocity.Exception.ResourceNotFoundException is if you have catch all CLR exceptions turned on in Visual Studio and you run your code under the debugger. If the exception message states that it cannot find the resource VM_global_library.vm, this is a harmless exception, NVelocity is trying to load up an optional resource file for global macros. Because NVelocity is a port from the Java Velocity, exceptions are commonly used for control flow in Java libraries.

[Test]
public void CustomObservableCollectionT()
{
    VelocityEngine velocityEngine = new VelocityEngine();
    velocityEngine.Init();

    Table table = new Table();
    table.Columns.Add(new TableColumn { Name = "Full Name" });
    table.Columns.Add(new TableColumn { Name = "Email Address" });
    table.Columns.Add(new TableColumn { Name = "DOB" });

    VelocityContext context = new VelocityContext();
    context.Put("table", table);

    using (StringWriter sw = new StringWriter())
    {
        velocityEngine.Evaluate(context, sw, "",
            "#foreach ($c in $table.Columns)\r\n" +
            "$c.Name\r\n" +
            "#end"
        );

        Assert.AreEqual(
            "Full Name\r\n" +
            "Email Address\r\n" +
            "DOB\r\n",
            sw.ToString());
    }
}
public class MegaList<T> : ObservableCollection<T>
{
}
public class Table
{
    private readonly MegaList<TableColumn> _columns = new MegaList<TableColumn>();

    public MegaList<TableColumn> Columns
    {
        get { return _columns; }
    }

    public List<TableColumn> ColumnList
    {
        get { return _columns.ToList(); }
    }

    public ObservableCollection<TableColumn> ColumnCollection
    {
        get { return new ObservableCollection<TableColumn>(_columns); }
    }
}
public class TableColumn
{
    public string Name { get; set; }
}
许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top