-2

I'm sure someone has tried to do something like this before, but I'm unsure if what I'm finding in my searches fits what I'm trying to do.

In my .Net 6 Web API I have a class to get data passed by the request:

public abstract class QueryStringParameters {
    private readonly int _maxPageSize = Constants.DefaultPageSizeMax;

    private int _pageSize = Constants.DefaultPageSize;

    public int? PageNumber { get; set; } = 1;

    public int? PageSize {
        get => _pageSize;
        set => _pageSize = value > _maxPageSize ? _maxPageSize : value ?? Constants.DefaultPageSize;
    }

    public string OrderBy { get; set; }

    public string Fields { get; set; }
}

For each controller I create a view model which inherits from this:

public class ProgramParameters : QueryStringParameters {
    public bool MapDepartment { get; set; } = true;
    public bool MapAnother1 { get; set; } = true;
    public bool MapAnother2 { get; set; } = true;
    
    ...

    public ProgramParameters() {
        // Default OrderBy
        OrderBy = "Id";
    }
}

This works fine when calling an endpoint expecting multiple results and single results. However, I want to split the QueryStringParameters properties that are for pagination, something like this:

public abstract class QueryStringParameters {
    public string Fields { get; set; }
}

public abstract class QueryStringParametersPaginated : QueryStringParameters {
    private readonly int _maxPageSize = Constants.DefaultPageSizeMax;

    private int _pageSize = Constants.DefaultPageSize;

    public int? PageNumber { get; set; } = 1;

    public int? PageSize {
        get => _pageSize;
        set => _pageSize = value > _maxPageSize ? _maxPageSize : value ?? Constants.DefaultPageSize;
    }

    public string OrderBy { get; set; }
}

The problem is that then my view modal looks like this:

public class ProgramParameters : QueryStringParameters {
    public bool MapDepartment { get; set; } = true;
    public bool MapAnother1 { get; set; } = true;
    public bool MapAnother2 { get; set; } = true;
    
    ...

    public ProgramParameters() {
    }
}
public class ProgramParametersPaginated : QueryStringParametersPaginated {
    public bool MapDepartment { get; set; } = true; // repeated
    public bool MapAnother1 { get; set; } = true; // repeated
    public bool MapAnother2 { get; set; } = true; // repeated
    
    ...

    public ProgramParameters() {
        // Default OrderBy
        OrderBy = "Id";
    }
}

How can I rewrite this so that ProgramParameters and ProgramParametersPaginated don't have to have the same properties (MapDepartment, MapAnother1, MapAnother2) defined in both?

I tried something like this but that's not allowed and I am unsure how to proceed.

public class ProgramParametersPaginated : ProgramParameters, QueryStringParametersPaginated {

    public ProgramParametersPaginated() {
        // Default OrderBy
        OrderBy = "Id";
    }
}
Mauro Bilotti
  • 5,628
  • 4
  • 44
  • 65
RoLYroLLs
  • 3,113
  • 4
  • 38
  • 57
  • 1
    What if you just use interfaces for that? – merlinabarzda Nov 01 '22 at 19:41
  • C# doesn't support multiple inheritance. For doing something like this you should use interfaces not classes. Nevertheless you should read about accessibility levels to understand how to define a virtual, protected or whatever you need to achive here with your properties. – Mauro Bilotti Nov 01 '22 at 19:41
  • Does this answer your question? [How to achieve Multiple inheritance in C#?](https://stackoverflow.com/questions/17470982/how-to-achieve-multiple-inheritance-in-c) – Mauro Bilotti Nov 01 '22 at 19:46
  • The thing about Interfaces is you'll have to "redefine" (not really, but kinda) the properties in your actual class right? For that I guess I can keep what I had initially. let me know if I misunderstood. – RoLYroLLs Nov 01 '22 at 19:56

1 Answers1

1

If I understood correctly, you need to extract interfaces instead of using classes as you did, so you can apply multiple implementation.

First define the interfaces and constants for you filter properties:

public enum Constants
{
    DefaultPageSizeMax = 500,
    DefaultPageSize = 100
}

public interface IQueryStringParameters
{
    string Fields { get; set; }
}

public interface IQueryStringParametersPaginated : IQueryStringParameters
{
    string OrderBy { get; set; }
    int PageSize { get; set; }
    int MaxPageSize { get; set; }
    int? PageNumber { get; set; }
}

Then you create an abstract class that inherit from both interfaces defined so you can write some behaviour like you did with the setters and getters:

public abstract class BaseProgramParameters : IQueryStringParameters, IQueryStringParametersPaginated
{
    public string Fields { get; set; }
    public string OrderBy { get; set; }

    private int _pageSize = (int)Constants.DefaultPageSize;
    private int _maxPageSize = (int)Constants.DefaultPageSizeMax;
    public int PageSize
    {
        get => _pageSize;
        set => _pageSize = value > _maxPageSize ? _maxPageSize : value;
    }
    public int MaxPageSize { get; set; }
    public int? PageNumber { get; set; }

    public bool MapDepartment { get; set; } = true;
    public bool MapAnother1 { get; set; } = true;
    public bool MapAnother2 { get; set; } = true;

    public BaseProgramParameters()
    {
    }

    public BaseProgramParameters(string orderBy)
    {
        this.OrderBy = orderBy;
    }
}

Since you may want to define a different value on MapDeparment, MapAnother, etc, you can use the constructor on the child classes:

public class ProgramParametersPaginated : BaseProgramParameters
{
    public ProgramParametersPaginated() : base("Id")
    {
    }
}

public class ProgramParameters : BaseProgramParameters
{
    public ProgramParameters()
    {
        this.MapAnother1 = false;
    }
}

Let me know if you have any further doubts.

Mauro Bilotti
  • 5,628
  • 4
  • 44
  • 65
  • Wow! This looks amazing! Would have never thought of doing it like this. I do have a question: I'd like to avoid the getters/setters in the `BaseProgramParameters` related to pagination because I'll have to do it to way too many classes. Is there a way to make it even more abstracted? – RoLYroLLs Nov 02 '22 at 15:32
  • Why would you have to apply a different logic if you have it on the base class? Every child class that inherit from this child class will have that implementation. Otherwise, what you can do is to create a protected virtual property and in every child make the override that you need. – Mauro Bilotti Nov 02 '22 at 17:02
  • What I meant to say was: in `BaseProgramParameters` is for the `Program` view model. Now for the `Department` and `Division` classes (`BaseDepartmentParameters, `BaseDivisionParameters` respectively) I'd have to make 2 more abstract classes, or N-classes amount of abstracts. What I'm trying to avoid is if I wanted to add a new property to `IQueryStringParametersPaginated` I'd have to go to all the classes that implements the interface and add the new property there. Also, I'm trying to avoid having the pagination section in `ProgramParameters`. – RoLYroLLs Nov 02 '22 at 17:27