IContentRepository
The recommended way to access Cofoundry data is by using one of our repository abstractions, which offer an easily discoverable fluent API enriched with inline documentation to help you choose the most suitable query or command for you needs.
We'd recommend getting started with IContentRepository
, which should be injected into your class using dependency injection:
using Cofoundry.Domain;
public class ExampleController : Controller
{
private readonly IContentRepository _contentRepository;
public ExampleController(
IContentRepository contentRepository
)
{
_contentRepository = contentRepository;
}
[Route("/api/categories-example")]
public async Task<IActionResult> Categories()
{
var categories = await _contentRepository
.CustomEntities() // select entity type
.GetByDefinition<CategoryCustomEntityDefinition>() // select query type
.AsRenderSummaries() // select projection
.ExecuteAsync(); // execute
return Json(categories);
}
}
In this example we're using an MVC controller, but this will work anywhere that supports dependency injection. Note that XML comments are provided to help guide you towards the most suitable query or projection.
This fluent style of querying might look a little like the flexible querying used in Linq or Entity Framework, but this is different. Cofoundry data access is based around strongly typed queries and commands, and here we are actually just building a strongly typed query object and executing it.
While this approach isn't as flexible as a dynamic query builder framework like Entity Framework, it does allow us to provide a strong contract for what parameters we expect and what result you receive. This in turn enable us to hide the complexities of the underlying storage structure, provide more reliable performance, be more explicit about what we support and more easily communicate breaking changes.
Mapping
A mapping function can be chained using a call to Map
. If the result type is nullable you can use MapWhenNotNull
to skip mapping when the result is null and avoid hanlding it in your mapping code. Note that mapping functions are invoked after query execution, so this is really just a neater way of writing queries with mapped results.
var entity = await _contentRepository
.CustomEntities()
.GetById(1)
.AsRenderSummary()
.MapWhenNotNull(e => new
{
e.CustomEntityId,
e.Title
})
.ExecuteAsync();
Items in collections and dictionaries can also be mapped with a call to MapItem
, and Map
can also be used to alter the collection. For dictionaries results, FilterAndOrderByKeys
can be used to set the ordering of the results to match an existing key set:
var ids = new int[] { 3, 1, 6 };
var entities = await _contentRepository
.CustomEntities()
.GetByIdRange(ids)
.AsRenderSummaries()
.MapItem(i => new { i.UrlSlug, i.Title })
.FilterAndOrderByKeys(ids)
.Map(r => r.Reverse())
.ExecuteAsync();
IAdvancedContentRepository
IAdvancedContentRepository
includes everything that can be found in IContentRepository
, but also includes additional queries and commands that are not needed by most sites.
await _advancedContentRepository
.PageDirectories()
.AddAsync(new()
{
Name = "Products",
UrlPath = "products",
ParentPageDirectoryId = 1
});
Influencing execution with ModelState
The Cofoundry.Web
namespace includes extensions that make it easier to work with queries or command execution in ASP.NET controllers and Razor Pages by making use of ModelState
to work with validation errors:
- Using
WithModelState(Controller|PageModel)
in the call chain wraps query and command execution with error handling code that pushes validation exceptions and error results into model state. Additionally if the model state is invalid prior to execution, then execution will be skipped. - If using transactions,
scope.CompleteIfValidAsync(ModelState)
can be used to conditionally complete the transaction if the model state is valid.
Query Example
In this ASP.NET MVC example, executing AuthenticateCredentials
returns a model derived from ValidationQueryResult
, WithModelState
will detect this result and automatically add any validation errors to the model state:
using Cofoundry.Domain;
using Cofoundry.Web;
public class ExampleController : Controller
{
// …constructor (omitted)
[HttpPost("sign-in")]
public async Task<IActionResult> SignIn(SignInViewModel viewModel)
{
// Wrap the query execution in WithModelState(this)
var authResult = await _advancedContentRepository
.WithModelState(this)
.Users()
.Authentication()
.AuthenticateCredentials(new()
{
UserAreaCode = CustomerUserArea.Code,
Username = viewModel.Username,
Password = viewModel.Password
})
.ExecuteAsync();
if (!ModelState.IsValid)
{
// If the result isn't successful, the the ModelState will be populated
// with the error
return View(viewModel);
}
// …sign in and redirect (omitted)
}
}
Command example
In this ASP.NET API controller example, WithModelState
is used to wrap command execution, reducing the boilerplate for handling errors. Additionally, scope.CompleteIfValidAsync(ModelState)
is used to conditionally complete the transaction:
using Cofoundry.Domain;
using Cofoundry.Web;
[Route("api/members")]
[ApiController]
public class MembersApiController : ControllerBase
{
// …constructor (omitted)
[HttpPost("register")]
public async Task<IActionResult> Register(RegisterUserDto registerUserDto)
{
using (var scope = _advancedContentRepository.Transactions().CreateScope())
{
// If there are any model validation errors, execution is skipped
// If any validation exceptions occur during execution they are added to model state
var userId = await _advancedContentRepository
.WithModelState(this)
.WithElevatedPermissions()
.Users()
.AddAsync(new()
{
UserAreaCode = CustomerUserArea.Code,
RoleCode = CustomerRole.Code,
Email = registerUserDto.Email,
Password = registerUserDto.Password,
});
// If ModelState is not valid, then execution is skipped
// If any validation exceptions occur during execution they are added to model state
await _advancedContentRepository
.Users()
.AccountVerification()
.EmailFlow()
.InitiateAsync(new()
{
UserId = userId
});
// The scope is only completed if the ModelState is valid
await scope.CompleteIfValidAsync(ModelState);
}
// ModelState can be used to determine success
if (ModelState.IsValid)
{
return Ok();
}
else
{
return BadRequest(ModelState);
}
}
}
Further Reading
Both content repositories inherit from IDomainRepository
. This base interface includes a number of other features including:
Read more about these features in the IDomainRepository docs