1

Note: This is a contrived example I came up with to illustrate what I'm after as simply as possible.

I have an HTML report with a single dropdown box at the top that lets the user select (or filter) the data that will be returned. However, the dropdown box doesn't contain a single type. It contains multiple types, that when sent back to the server, need to be parsed, and then ultimately any one of a number of different types need to be returned. Once that type has been generated, then that type can be sent to the proper function/method that will retrieve the data that the user wants.

So let me provide an example to clarify what I'm after. Lets say we have a database of automobiles. There are different types (car, truck, van). There are different makes (Chevy, BMW, Honda). And there are different numbers of seats in each car (2, 4, 7).

My HTML dropdown box might look like this:

<select name="ViewType" id="ViewType">
    <option value="type-car">Cars</option>
    <option value="type-truck">Trucks</option>
    <option value="type-van">Vans</option>
    <option value="make-chevy">Chevys</option>
    <option value="make-bmw">BMWs</option>
    <option value="make-honda">Hondas</option>
    <option value="seats-2">2 Seaters</option>
    <option value="seats-4">4 Seaters</option>
    <option value="seats-7">7 Seaters</option>
</select>

When the user makes a selection, the page/form gets posted back to the server, where my C# controller will parse the selected value of the dropdown box.

The idea here is that the parser be able to generate an object that matches the type of filter/view that the user wants to see, and that object can then be passed on to an overloaded method/function that can retrieve the proper data and generate the report.

So I have three different objects representing the view/filter type:

public class AutomobileType() {
    public string Type { get; set; }
}

public class AutomobileMake() {
    public string Make { get; set; }
}

public class AutomobileSeats() {
    public byte Seats { get; set; }
}

I want the parser to generate one of those three types, based upon the value of the dropdown, so that it can be passed directly in to an overloaded method/function:

public void GenerateReport(AutomobileType type) { ... }

public void GenerateReport(AutomobileMake make) { ... }

public void GenerateReport(AutomobileSeats seats) { ... }

Note: There are actually many different parts to this report, and many of them change depending on the view type selected, even though my simplified example doesn't show that. In other words, there isn't just one GenerateReport function that gets called, there are several different overloaded functions called. I prefer to parse once and feed several different functions the same way.

PROBLEM

I need this dropdown parser to return an object that could be any one of those types. And I can't figure out how to do this cleanly. These "view types" are just data bags. The type describes what is in the bag. They don't share any functionality, because there is none. There isn't an interface to inherit. And there aren't any shared or overridable properties or functions or methods.

public ReturnWhat??? ParseDropdown(string dropdownValue) { ... }

How can I structure some types and a parser that can return any one of these "view types" that can then be fed directly to another method/function? I haven't been able to figure out how to do it without runtime type checking and explicit casting.

2
  • 1
    I think the real issue here is that you have a drop-down box in the HTML which allows the user to pick things which should be orthogonal. In what kind of UI / use case would it make sense to select inside one drop-down from car types, car vendors and seat numbers?
    – Doc Brown
    Aug 7 at 10:39
  • @DocBrown If I didn't know the domain and I saw someone ask a question like this, I'd have made exactly this same comment. I've designed the UI with radio buttons and separate drop down boxes, and for my domain/use case, it just looks cluttered and confusing, and is a bit clunky to use. In my case, the single dropdown with different types actually looks and works better (and is intuitive). Aug 10 at 18:59

3 Answers 3

2

Create an expression map

The idea here is that the parser be able to generate an object that matches the type of filter/view that the user wants to see, and that object can then be passed on to an overloaded method/function that can retrieve the proper data and generate the report.

The problem is that overloads are selected at compile time, not runtime, so this idea wouldn't work unless you were to do something very odd.

Here's my suggestion. Data posted over HTTP needs to be validated anyway, and it is best to validate using a white list. So you need to define a white list somewhere regardless. While declaring the list, you can pair each code with an expression that can be used in a query.

var map = new Dictionary<string, Expression<Func<Vehicle,bool>>>
{
    { "type-car",   x => x.VehicleType == "car" },
    { "type-truck", x => x.VehicleType == "truck" },
    { "seats-2",    x => x.Seats == 2 },
    { "seats-4",    x => x.Seats == 4 }
    //Etc....
};

Then when you receive the user input you can map it to a query very easily:

var expression = map[userInput];
var resultset = dbContext.Vehicles.Where( expression );

And if you are not using EF you can use any other kind of lamdba expression that generates the type-specific results that you want. For example, if you really wanted to keep your original idea you could do this:

var map = new Dictionary<string, Func<IEnumerable<Vehicle>>>
{
    { "type-car",   () => GenerateReportByType(new AutomobileType { Type == "car" }) },
    { "type-truck", () => GenerateReportByType(new AutomobileType { Type == "truck" }) },
    { "seats-2",    () => GenerateReportBySeats(new AutomobileSeats { Seats == 2 }) },
    { "seats-4",    () => GenerateReportBySeats(new AutomobileSeats { Seats == 4 }) },
    //Etc....
};

var action = map[userInput];
var resultset = action();
1
  • This is an awesome answer. In my OOP journey, I haven't used references to functions very often, and I forget that I have that ability. Not using EF but your second example would fit what I was after, although the map would have to be generated based on values from the database in my case. Even if I don't go this route, its a very valuable answer! Aug 10 at 19:28
1

So there is a fundamental problem in that overloaded methods only work within the OOP framework of the programming language you are using, any serialisation wont pick that up by magic, you need to invent a protocol.

With HTTP calls like this the binding will only work on the path, so instead of having all the calls going to the same path, you have to split it up by expected type. ie in your controller you need:

public void GenerateReportByType(AutomobileType type) { ... }

public void GenerateReportBymake(AutomobileMake make) { ... }

public void GenerateReportBySeats(AutomobileSeats seats) { ... }

Your client side JavaScript can know which function is needed for each selection option in the drop down, (add an extra data attribute or something) and make the correct call.

Obviously you could add some code where the same path used for all the calls and you then try and determine the type from the data sent, but you are just fighting the system at that point.

2
  • Very nice answer. This sparked an idea... What if my dropdown values just contain the URL to access the selected report, e.g. /Automobiles/Type/Truck or /Automobiles/Seats/2. Then I have three routes/controllers (Type, Make, Seats), and each controller can then generate the correct type of object and call the correct Generate Report overload. I've got some playing to do. Aug 10 at 19:10
  • Ewan I didn't accept this answer because I felt like John Wu 's answer actually answered my question. However, your answer is what got me thinking about my architecture BEFORE I got to the problem I was having, and I ended up restructuring my URLs and controllers so that I didn't have to have a "Parser". Thx! Aug 12 at 16:12
1

These "view types" are just data bags. The type describes what is in the bag. They don't share any functionality, because there is none. There isn't an interface to inherit. And there aren't any shared or overridable properties or functions or methods.

In such a case, it is probably cleanest to make your ParseDropdown return a C# object. If you need a function which can return objects of different, mostly unrelated types, and you cannot introduce a common interface at that level, using an object is the canonical option.

I haven't been able to figure out how to do it without runtime type checking and explicit casting.

Sometimes, this is actually what you have to do. Of course, you should avoid this to happen in multiple places scattered all over your code. Ideally, you concentrate such runtime checks into factories. For example, instead of having these 3 reporting overloads, you could have 3 different report classes with a common Generate method. A report factory then takes the object returned from ParseDropdown and decides about creating the corresponding reporting object.

If you want to prevent this, you would probably have to redesign your HTML and avoid to mix up things in the same drop-down which refer to almost unrelated types. But as long as the UI looks like this, you will probably have to abandon some type-safety in a restricted area of your code.

1
  • The thought of using an object as the return value and then doing type checking later is basically the code smell I was trying to get rid of. If it was the ONLY way to do what I was after, then so be it. But was really hoping someone smarter than me had a better idea! Aug 10 at 19:15

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.