-1

I have a base controller that should return a List of objects (and map them from DTO to business)

If the child controller decides to apply a specification (filter or include something) it can do it by overriding the GetSpecification() method.

But by default, in the base class I don't want to filter the objects.

[Route("api/[controller]")]
[ApiController]
public class BaseApiController<TBusinessModel, TApiModel, 
                               TBaseRepository> : BaseController<TBaseRepository>
                         where TBusinessModel   : BaseEntity
                         where TBaseRepository  : IBaseRepository
{
    public BaseApiController(TBaseRepository repository, 
                             IMapper mapper) : base(repository, mapper)
     { }

     // GET: api/Bars
     [HttpGet]
     public virtual async Task<IActionResult> List()
     {
        var spec = GetSpecification();
        var items = await _repository.ListAsync<TBusinessModel>(spec);
        var apiItems = _mapper.Map<List<TApiModel>>(items);                
        return Ok(apiItems);
     }

     protected virtual ISpecification<TBusinessModel> GetSpecification() 
     {
     // how to get an empty specification that does not filter or do something?
            return new Specification<TBusinessModel>(); 
      }
}

I use ardalis specifications, but it could be any generic IQueryable thing...

Actually it says:

enter image description here

Error CS0144 Cannot create an instance of the abstract type or interface 'Specification'

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
serge
  • 13,940
  • 35
  • 121
  • 205

3 Answers3

0

The error message is enough, you can't instantiate interfaces or abstract classes.That's because it wouldn't have any logic to it. Details, you could refer to this document.

If you want to create a new Specification class, I suggest you could try to use SpecificationBuilder class.

More details about how to use it, you could refer to this github link.

Brando Zhang
  • 22,586
  • 6
  • 37
  • 65
  • my question is rather about what kind of specification to use, in order do not change the initial query, as default one, by eg. – serge Feb 02 '21 at 18:43
0

Having an empty specification is a totally valid construct. Once evaluated will return all records. In the below example, it will return all customers

public class CustomerSpec : Specification<Customer>
{
    public CustomerSpec()
    {
    }
}

But, the issue here is not the empty specification. You're trying to add another layer of abstraction, and you're trying to instantiate Specification<T> directly. The Specification is an abstract class, thus you won't be able to create an instance. If you are eager to implement that infrastructure, then just add your own base class inherited from Specification<T>, and then use that one as a base for all the other specifications in your app.

Note: We made the class abstract, exactly to discourage users to do this :) But, sure you can go ahead and use it in that manner.

public class AppSpecification<T> : Specification<T>
{

}
Fati Iseni
  • 311
  • 2
  • 5
  • I don't need to instantiate a specification, I just need, by default, in the base class, the specification should not filter anything. In the child class, if there is a need, a specification could filter when a specific behavior is needed, but the base abstract class should return an empty (non filtering) specification – serge Feb 02 '21 at 18:47
  • Ok then, use new inherited base specification class, as described above. Does that work for you? – Fati Iseni Feb 02 '21 at 21:07
  • I added my solution, finally I see now it is similar to your last example (don't know if the empty constructor is really needed) – serge Feb 02 '21 at 23:20
  • I'm glad it worked out. Since the ctor is empty, u don't need to add it explicitly. I'd still sugest to name the base specification differently (not EmptySpecification). Name it AppSpecification or anything else, and use this spec as base for all the other specs u will have in your app. – Fati Iseni Feb 03 '21 at 10:32
0

Finally, as such a thing like empty specification does not exist in the provided library, I created it myself:

public class EmptySpecification<T> : Specification<T>
{
    public EmptySpecification()
    {
        // does nothing
    }
}

Then used in the BaseController (last lines) :

[Route("api/[controller]")]
[ApiController]
public class BaseApiController<TBusinessModel, TDto, TBaseRepository> : BaseController<TBusinessModel, TDto, TBaseRepository>
    where TBusinessModel : BaseEntity
    where TBaseRepository : IBaseRepository
{
    public BaseApiController(TBaseRepository repository, IMapper mapper) : base(repository, mapper) { }

    [HttpGet]
    public virtual async Task<IActionResult> List()
    {
        var spec = ListSpecification();
        var items = await _repository.ListAsync<TBusinessModel>(spec);
        var apiItems = ToDto(items);
        return Ok(apiItems);
    }

    protected virtual ISpecification<TBusinessModel> ListSpecification()
    {
        return new EmptySpecification<TBusinessModel>();
    }
}
marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
serge
  • 13,940
  • 35
  • 121
  • 205