Switch data model structure based upon property c#

446 Views Asked by At

I am using a client to call an API. In the API - I want the model to populate from the request body - but I want the model to be structured differently depending upon the name of a single property. Basically I want to create something like a switch/case scenario with a data model, but am unsure how to implement this. The last model contains pseudo code based upon what I want to acheive (obviously generic type won't work in the way I described, but I feel it completes my example). Here's my example:

Controller:

[HttpPost("customer", Name = "Submit Customer")]
public IActionResult ActivateCustomer([FromBody]Customer customer)
{
    //Do something with the Customer object.
    return Ok();
}

Customer Model:

public class Customer
 {
     public CustomerInfo customerInfo { get; set; }
     public SponserInfo sponserInfo { get; set; }
 }

CustomerInfo:

public class CustomerInfo
{
    public int CustomerId { get; set; }
    public string CustomerName { get; set; }

    //etc.
}

SponserA:

public class SponserA
{
    public int ReferenceId { get; set; }
    public string Password { get; set; }
}

SponserB:

public class SponserB
{
    public string UserName{ get; set; }
    public string Relation { get; set; }
    public string Department { get; set; }
}

SponserInfo: (pseudo-code of what I would like)

public class SponserInfo
{
    public string SponserName { get; set; }
    public T SponserInfo { get; set; }

    switch(this.SponserName)
    {
        case "Sponser A's Name":
            T = SponserA;
            break;
        case "Sponser B's Name":
            T = SponserB;
            break;
    }
}
3

There are 3 best solutions below

1
On BEST ANSWER

Here's one extensible way.

An attribute maps the sponsor name to the subclass, so SponsorInfo doesn't have to be aware of all subclasses.

It uses an abstract base class (Sponsor) for all Sponsor types (as also recommended by @Flydog57).

When SponsorInfo.SponsorName is assigned, the instance of a subclass of this is created (so you have to assign SponsorName first).

You can adjust that depending on how you actually map the properties from your model.

using System;
using System.Linq;
using System.Reflection;


/// <summary>
/// Attribute to indicate the name mapped to a <see cref="Sponsor"/> subclass.
/// </summary>
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
public class SponsorAttribute : Attribute
{
    public SponsorAttribute(string name)
    {
        this.Name = name;
    }

    /// <summary>
    /// The value that <see cref="SponserInfo.SponserName"/> must match for the attribute class to be used.
    /// </summary>
    public virtual string Name { get; set; }
}

public abstract class Sponsor
{
    public int ReferenceId { get; set; }
    public string Password { get; set; }
}

[Sponsor("Sponser A's Name")]
public class SponsorA : Sponsor
{
}

[Sponsor("Sponser B's Name")]
public class SponsorB : Sponsor
{
    public string Department { get; set; }
}

// More subclasses can be added.

public class SponsorInfo
{
    /// <summary>
    /// The Sponsor name.
    /// Changing this sets <see cref="Sponsor"/> to a new instance of the corresponding class.
    /// </summary>
    public string SponsorName
    {
        get { return _sponsorName; }
        set
        {
            if (_sponsorName != value)
            {
                _sponsorName = value;

                // Find a Sponsor subclass with a SponsorAttribute.Name matching the given value:
                Type sponsorType = Assembly.GetExecutingAssembly().GetTypes()   // you might want to also scan other assemblies
                    .Where(t =>
                        t.IsSubclassOf(typeof(Sponsor))
                        && (t.GetCustomAttribute<SponsorAttribute>()?.Name?.Equals(_sponsorName) ?? false)
                    ).FirstOrDefault();   // null if none is found

                if (sponsorType == null)
                    Sponsor = null;     // no matching class
                else
                    Sponsor = (Sponsor)Activator.CreateInstance(sponsorType);  // new instance of the matching class
            }
        }
    }
    private string _sponsorName;

    public Sponsor Sponsor { get; set; }   // renamed from "SponsorInfo" because that's the name of this class
}


This is dual licensed as public domain (CC0) and the normal licensing of Stack Overflow.

1
On

How about something like this:

public abstract class SponsorInfo
{
    public string SponserName { get; set; }

    protected SponsorInfo(string sponserName)
    {
        SponserName = sponserName;
    }
}

public class SponsorA : SponsorInfo
{
    public int ReferenceId { get; set; }
    public string Password { get; set; }

    public SponsorA(string sponserName, int referenceId, string password) 
        : base(sponserName)
    {
        ReferenceId = referenceId;
        Password = password;
    }
}

public class SponsorB : SponsorInfo
{
    public string UserName { get; set; }
    public string Relation { get; set; }
    public string Department { get; set; }

    public SponsorB(string sponsorName, string userName, string relation, string department) 
        : base(sponsorName)
    {
        UserName = userName;
        Relation = relation;
        Department = department;
    }
}

Then, leave your Customer class alone (but fix the typo):

public class Customer
{
    public CustomerInfo customerInfo { get; set; }
    public SponsorInfo sponsorInfo { get; set; }
}

and in your controller, add the switch statement and construct either a SponsorA or a SponsorB depending on what the data looks like. Either of those is a SponsorInfo, so you can attach it as the sponsorInfo in your Customer object.

3
On

Why not create a model called Sponsor that has all your fields, and then if ReferenceId is null, you'll know which kind of sponsor it is?

public class SponsorInfo 
{
    public int? ReferenceId { get; set; }
    public string Password { get; set; }
    public string UserName { get; set; }
    public string Relation { get; set; }
    public string Department { get; set; }
}

[HttpPost("customer", Name = "Submit Customer")]
public IActionResult ActivateCustomer([FromBody]Customer customer)
{
    //Do something with the Customer object.
    if (customer.sponsorInfo.ReferenceId == null || !customer.sponsorInfo.ReferenceId.HasValue)
    {
        //is SponsorB
    }
    else
    {
        //is SponsorA
    }
    return Ok();
}