Search code examples
c#asp.net-mvcasp.net-web-apidesign-patternsanonymous-types

Generating different results from Web API call using an Anonymous type


I am trying to figure out the best way to return scatterplot data from API call. This is a graph that plots scores for the selected employees.

My requirement are that the caller can specify one of a number of scores for each axis, either a named result score, an attributeId score or a domainId score.

So in total there would be 30 total possibilities for the returned results.

enter image description here

API Call:

[HttpPost("{id}/scatterplot")]
public ActionResult Scatterplot([FromRoute] int id, [FromBody] ScatterplotAxis axis)
    

ScatterplotAxis .cs

public class ScatterplotAxis
{
    public Axis XAxis { get; set; }
    public Axis YAxis { get; set; }
}

public class Axis
{
    public int? TeamId { get; set; }  // optional
    public string NamedScore { get; set; }
    public int? AttributeId { get; set; }
    public int? DomainId { get; set; }
}

So the idea is that you can call it with data such as:

{
  "xAxis": {
    "teamId": 1362,
    "namedScore": "NamedScore2",
    "attributeId": null,
    "domainId": null
  },
  "yAxis": {
    "teamId": 1362,
    "namedScore": "",
    "attributeId": 35,
    "domainId": 0
  }
}

So in the above example I would want to return a result with the score for 'NamedResult2' on X axis and the score for the given attributeId on the Y axis.

My problem is that I am trying to get what has been selected from the posted json and shape the results:

So far I have:

 var employees = ... // omitted for brevity
 // get employees and filter by teamId if it is supplied...

 var xAxisSelection = FindAxisSelection(axis.XAxis);
 var yAxisSelection = FindAxisSelection(axis.YAxis);

 if (xAxisSelection == "NotFound" || yAxisSelection == "NotFound")
     return BadRequest("Axis Selection not Found");

        // e.g. would have to do something like this for all 30 combinations using if/else
        // if(xAxisSelection == "NamedScore2" && yAxisSelection == "Attribute")

        var results = employees.Select(e => new
        {
            Id = e.Id,
            FullName = e.FullName,
            TeamName = e.MemberTeams.FirstOrDefault() == null ? "" : e.MemberTeams.FirstOrDefault().Name,

            // how to do this for all combinations???
            XAxis = e.NamedScores.NamedResult2,  
            YAxis = e.AttributeResults.Where(a => a.AttributeId == axis.YAxis).Score
        }).ToArray();

        return Ok(results);
    }

    private string FindAxisSelection(Axis axis)
    {
        if (axis.AttributeId != null || axis.AttributeId > 0)
            return "Attribute";
        else if(axis.DomainId != null || axis.DomainId > 0)
            return "Domain";
        else if(axis.NamedScore == "NamedScore1")
            return "NamedScore1";
        else if (axis.NamedScore == "NamedScore2")
            return "NamedScore2";
        else if (axis.NamedScore == "NamedScore3")
            return "NamedScore3";
        else if (axis.NamedScore == "NamedScore4")
            return "NamedScore4";

        return "NotFound";
    }

So my question is regarding generating the results. I do not really want to use a massive if else statement block for each of the 30 combinations. Are there any design patterns I should use to make this cleaner and more efficient.

I think its best to use anonymous type so that I don't have to create concrete types for each of the 30 combinations?


Solution

  • Rules design pattern is a perfect fit for this use case. Have a look at the following link.

    http://www.michael-whelan.net/rules-design-pattern/

    In short, what you can do is create numbers of objects with their own requirements(if condition in your case) and if a condition is met within the object they will execute their code otherwise it will call the next object. This approach is also known as chain of responsibility

    Requested sample to give you an idea on how to so something to avoid multiple if conditions:-

    public class Program
    {
        public static void Main()
        {
            new AllSteps().Process(1);
        }
        public interface IStep
        {
            void Process(int x);
        }
        public class StepOne:IStep
        {
            public void Process(int x)
            {
                // do something with x
            }
        }
        public class StepTwo:IStep
        {
            public void Process(int x)
            {
                // do something with x
            }
        }
        public class AllSteps:IStep
        {
            readonly List<IStep> steps= new List<IStep>();
    
            public AllSteps()
            {
                this.steps.Add(new StepOne());
                this.steps.Add(new StepTwo());
    
            }
    
            public void Process(int x)
            {
                steps.ForEach(y=>y.Process(x));
            }
        }
    }