How to get an empty specification that does not filter or do something?

936 Views Asked by At

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'

3

There are 3 best solutions below

4
On

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>
{

}
0
On

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>();
    }
}
1
On

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.