views:

183

answers:

5

I've seen a couple examples out there that could possibly help me, but I don't have that much time to explore them as I just found out today that my bosses have to demo this a week earlier than planned, and I want to add this new functionality. I'll try and keep this short and sweet.

Ok, this is like my 10th time trying to right this to make it clear, hopefully it is. This is an application. Rows of data need to be displayed in a DataGridView (done). Some rows are highlighted differently based on reports (done). Most reports have their own SQL file and are implemented at runtime from an INI file (done). However, some reports need to call a Function. The application is using an SQLite database. I would like to have DLLs that are reports, all of the same format, and all of them return a List of ReportRecord. ReportRecord is a class defined in my main application but I would also define it in each DLL when they are created. I want to instantiate the DLL, call it's "GetRecords" function, and use it in my main application. Here is some psuedocode. If you guys can tell me if it's possible, or give me an idea of a better way to do this, I'd appreciate it.

PSUEDOCODE

 foreach (string str in System.IO.Directory.GetFiles("C:\\ReportDlls", "*.dll"))
 {
   //Instantiate DLL e.g. newReport
   //_lstReportRecords.AddRange(newReport.GetReportRecords());
 }    

Is there anyway to do this?

Currently, I have the following to supplement until I find this out:

        private void RefreshReports(string strReportTitle)
        {
            _lstReportRecords = _lstReportRecords.Where(rr => rr.Description != strReportTitle).ToList<ReportRecord>();
            string strColumn = iniFile.GetString(strReportTitle, "Column", "");


            if (strColumn != null)
            {
                _lstReportRecords.AddRange(_dataController.BuildReportList(strColumn, strReportTitle, GetReportSQL(strReportTitle)));
            }
            else
            {
                switch (strReportTitle)
                {
                    case "Improper Indenture":
                        _lstReportRecords.AddRange(_dataController.ImproperIndenture());
                        break;
                    case "Skipping Figure":
                        _lstReportRecords.AddRange(_dataController.SkippingFigure());
                        break;
                    default: break;
                }
            }
            FormatCells();
        }

Thanks everyone.

Edit: Sorry guys, looking at that stuff is honestly making me feel stupid. Like, my mind is going blank and all and can't concentrate on it. :) What you guys have provided is probably the best way, but since I have to have a quality Demo ready by Tuesday and there shouldn't be any more reports added needing functions until then, I'm going to keep this open. Once my boss is out of town to demo it, I'll work on implementing this. But right now, it's going to go unanswered unless I see an example that is very very (for 2 year olds) straight forward.

A: 

Don't look at this in terms of DLL's, which are the raw files, but Assemblies, which is how .NET sees things. You can load an Assembly using Assembly.Load. Having said this, have you considered a more generic solution, such as inversion of control?

Steven Sudit
The Assemblies part I kind of understood, but the "inversion of control" kind of went over my head. Could you explain a little more?
XstreamINsanity
@Xstream: In this context, Inversion of Control is a way to pass dependencies to your report writer. Instead of using a `switch` statement to determine the data source, you would simply pass the data source to the `RefreshReports` method as a parameter.
Robert Harvey
I added a link. Basically, the need to figure out at runtime just which implementation to use is a problem that people have created solutions to already. For .NET in specific, take a look at: http://eduncan911.com/blog/managed-extensibility-framework-mef-microsofts-official-inversion-of-control-container.aspx
Steven Sudit
I feel one of two things. Either lost, or that it will be a lot of rewrite to get it that way. I'm not sure. :) There is a ListView (of checkboxes) and they choose the reports they want to see, sometimes multiple at a time. Each report has it's own SQL statement. That's why this function removes items from a member list and then adds back the still offending records. Sorry if I'm making this more confusing than it already was. I know what I want to do in my head, don't know how to say it.
XstreamINsanity
Loop through the DLL's in a particular directory, perhaps filtering on the basis of the name, and load each one up as an assembly. Use reflection to walk through the classes and find the ones which are tagged with some attribute and/or implement a particular interface. For each class, instantiate it and call a method. Rinse, repeat.
Steven Sudit
A: 

Consider a plugin architecture, such as the Managed Extensibility Framework, for managing your report modules.

Robert Harvey
Thanks for providing the canonical link to MEF. The link I gave was to a tutorial.
Steven Sudit
A: 

You may want to consider using the Managed Extensibility Framework for this. It makes this type of operation trivial.

You can download it above for .NET 3.5 (it's in the framework in .NET 4 already). By using MEF, you could just import the collection of all exported "reports" in one shot via a DirectoryCatalog, and it will take care of all of the wiring for you.

For details, see the help on Importing Collections here.

Reed Copsey
+1  A: 

I'm not really understanding what exactly you're trying to do, and IOC is probably the way to go here. But from what I understand, you could do this with pure reflection.

Mind you, this is far from the ideal way of doing things like this, but you're asking for it :)

Here it goes (top of my head, so don't shoot me if anything is wrong, it should be pretty close, though not foolproof)

// load assembly
var assemblyWithReport = Assembly.LoadFrom("Path of your assembly"); 

// or another Loadxx to get the assembly you'd 
// like, whether it's referenced or not

// load type
var reportType = assemblyWithReport.GetTypes().ToList()
   .Where(t => t.Name == "ReportRecord").Single();

// create instance of type
var instance = Activator.CreateInstanceOf(reportType);

// get getrecords method of the type
var getRecordsMethod = reportType.GetMethod("GetRecords");

// invoke getrecords method on the instance
object result = getRecordsMethod.Invoke(instance, null);
Bertvan
+1  A: 

You can simply create a C# library project implementing the interfaces below and store the binary file in the database or on the file system. You could then load the assembly from raw assembly bytes/file path an instantiate an object. With reflection you can also call the constructor directly, but i prefer the factory pattern for such tasks.

public interface IReportModule
{
}

public interface IReportModuleFactory
{
    IReportModule Create();
}

private static IReportModule CreateReportModuleFromRawAssemby(byte[] rawAssembly)
{
    var reportModule = Assembly.Load(rawAssembly);
    var factoryType = reportModule.GetExportedTypes()
        .FirstOrDefault(x => x.IsAssignableFrom(typeof(IReportModuleFactory)));
    if (factoryType != null)
    {
        var reportModuleFactory = (IReportModuleFactory)
            reportModule.CreateInstance(factoryType.FullName);
        return reportModuleFactory.Create();
    }
    else
        throw new NotImplementedException("rawAssembly does not implement IReportModuleFactory");
}
Nappy