#26183·abp

REPR Pattern | CreateAppService, UpdateAppService etc. instead of CrudAppService

Author: nazem0Created Sep 15, 2026Updated Sep 17, 2026
Labelsfeature-request

Is there an existing issue for this?

  • I have searched the existing issues

Is your feature request related to a problem? Please describe the problem.

I had the need to create a no logic CRUD APIs, and I normally used CrudAppService. But I had the case to just make a single Api out of them, but I found out it was not flexable, it's CRUD or non

Describe the solution you'd like

I suggest we separate it through (Create/Update/Delete/Get/GetList/GetPaged) service, that would give the dev the flexability to make 1 no logic Create API, maybe there is no Delete API

Solution would be making the chain of AbstractKeyCrudAppService->CrudAppService use composition to composite the needed apis to not break any changes so it stays the same but each api has its separate class, so users can easily do the described flow

Additional context

csharp
using Volo.Abp.Application.Services;
using Volo.Abp.Domain.Entities;
using Volo.Abp.Domain.Repositories;
using Volo.Abp.MultiTenancy;

public class CreateAppService<TEntity, TEntityDto>(
    IRepository<TEntity> repository)
    : CreateAppService<TEntity, TEntityDto, TEntityDto>(repository)
    where TEntity : class, IEntity;


public class CreateAppService<TEntity, TInput, TOutput>(
    IRepository<TEntity> repository)
    : ApplicationService, ICreateAppService<TOutput, TInput>
    where TEntity : class, IEntity
{
    protected IRepository<TEntity> Repository { get; } = repository;

    protected virtual string? CreatePolicyName { get; set; }
    public async Task<TOutput> CreateAsync(TInput input)
    {
        await CheckCreatePolicyAsync();

        var entity = await MapToEntityAsync(input);

        SetIdForGuids(entity);
        TryToSetTenantId(entity);

        await PersistAsync(entity);

        return await MapToOutputAsync(entity);
    }

    protected virtual Task CheckCreatePolicyAsync()
    {
        return CheckPolicyAsync(CreatePolicyName);
    }

    protected virtual Task<TEntity> MapToEntityAsync(TInput input)
    {
        return Task.FromResult(MapToEntity(input));
    }

    protected virtual TEntity MapToEntity(TInput input)
    {
        return ObjectMapper.Map<TInput, TEntity>(input);
    }

    protected virtual void SetIdForGuids(TEntity entity)
    {
        if (entity is IEntity<Guid> entityWithGuidId &&
            entityWithGuidId.Id == Guid.Empty)
        {
            EntityHelper.TrySetId(
                entityWithGuidId,
                GuidGenerator.Create,
                true);
        }
    }

    protected virtual void TryToSetTenantId(TEntity entity)
    {
        if (entity is not IMultiTenant)
        {
            return;
        }

        var tenantId = CurrentTenant.Id;

        if (!tenantId.HasValue)
        {
            return;
        }

        var propertyInfo = entity
            .GetType()
            .GetProperty(nameof(IMultiTenant.TenantId));

        if (propertyInfo?.GetSetMethod(true) == null)
        {
            return;
        }

        propertyInfo.SetValue(entity, tenantId);
    }

    protected virtual Task PersistAsync(TEntity entity)
    {
        return Repository.InsertAsync(entity, autoSave: true);
    }

    protected virtual Task<TOutput> MapToOutputAsync(TEntity entity)
    {
        return Task.FromResult(MapToOutput(entity));
    }

    protected virtual TOutput MapToOutput(TEntity entity)
    {
        return ObjectMapper.Map<TEntity, TOutput>(entity);
    }
}