Replacement for big switch?

柔情痞子 提交于 2019-12-05 00:20:32


I have a page named "ReportController.aspx" whose purpose is to instantiate a report (class) based on query string parameters

        switch (Request.QueryString["Report"])
            case "ReportA":
                CreateReportAReport("ReportA's Title");
            case "ReportB":
                CreateReportBReport("ReportB's Title");
            case "ReportC":
                CreateReportCReport("ReportC's Title");
            case "ReportD":
                CreateReportDReport("ReportD's Title");

Basically, each time a new report is needed there will be this overhead of adding a case and adding a method. This switch statement could get very very long. I read that is is possible to use a Dictionary to map a Report to ?. How would this look using a Dictionary (assuming this is a better way).

Also, CreateReportXReport method basically passes a bunch of additional QueryString values to the report class's constructor (each report class has a different constructor).


Assuming that all reports implement IReport, you can do it using Func<IReport>, like this:

IDictionary<string,Func<IReport>> dictToReport = new Dictionary {
    {"ReportA", () => CreateReportAReport("ReportA's Title") }
,   {"ReportB", () => CreateReportBReport("ReportB's Title") }
,   ...

You can then replace the switch with this code:

var myReport = dictToReport[Request.QueryString["Report"]]();


There's no getting around having to type in the new information somewhere; the key is to get it out of the code, to avoid recompiling and redeploying for such a trivial change.

Some good options are to list these value in an XML config file, or better yet, your database.

You'll probably want to fill out a dictionary with this data, whatever the source. This will:

  • Make it easy to cache
  • Make for clean, fast code

When the time comes to pull your data out of configuration into code, you'd add items to the dictionary like so:

Dictionary<string, IReportCreator> = configDataGetter.GetReportDataFromDB().
    ToDictionary(r => r.Name, myReportCreatorFactory(r => r.ReportID))

This example assumes your getting data as entity object of some kind, and using a factory that would use a strategy pattern for your code that creates reports. There's a bagillion ways your could be doing this of course.

I assume the reports are just too extensive, varied, and different in nature that you can't just put sql and styling building block in the db?

Edit based on op's comments:

Ah, gotcha. Well, I don't know how much time you have, but as much as you push everything into some sort of factory, you have better options you'll later. I'm going to give you some thoughts that will hopefully help, from similar things I've done. Each step is an improvement in itself, but also a baby step to really separating your report logic from this shell code. Further, I can see you already know what you're doing and I'm sure know some of what I'll say below, but I don't know what you know, and it will be helpful for others.

First, pull out any and every bit of information from code to db (if you haven't already), and you'll add more db fields (and a table or two) as you improve your setup.

You might know about it already, but I'll mention it for others, to check out the strategy pattern I reference above. You can have the custom logic of each "report function" actually be in the constructor of your various strategy classes. They would all inherit from your base ReportGenerator (or sport a common IReportGenerator interface). They can and should share the same constructor; varying report parameters would be handled by a parameter of type dictionary. Each class's constructor implementation would know the types of the variables is needs (from db configuration), and would cast/use them accordingly.

Next step might be to really get rid of your select statement in your factory, using reflection. You'd have to have the name of the class as part of your reports configuration data in the db (and have a common constructor).

At this point, the way to add a new report is pretty clean, even though you've got to add a new class each time. That good. It fulfills the single responsibility and open-closed principals.

Now, there's just the final step of removing the classes from your app, so they can be added/edited on the fly. Check out MEF. This is what it's made for. Some things you might find on the internet that you probably shouldn't use are CodeDom (great when there was nothing else, but MEF is better) and the compilation-as-a-service features coming in .NET 5. MEF is the way to go.


I think is better re-design this code and convert it into some database table ("Reports") to keep there the list of reports and ID of each report.

That's it.


To do this with a Dictionary<string, string> you would simply build one up as a static cache in the containing type

public class Container {
  private static Dictionary<string, Func<Report>> ReportMap = 
    new Dictionary<string, Func<Report>>();
  static Container() {
    ReportMap["ReportA"] = () => CreateReportAReport("ReportA's Title");
    ReportMap["ReportB"] = () => CreateReportBReport("ReportB's Title");
    // etc ...

Now that the map is built you simply do a lookup in the function instead of a switch

Func<Report> func;
if (!ReportMap.TryGetValue(Request.QueryString["Report"), out func)) {
  // Handle it not being present
  throw new Exception(..);

Report report = func();