abpframework / abpframework/abp

REPR Pattern | CreateAppService, UpdateAppService etc. instead of CrudAppService

Open
#26,183 2 comments 0 reactions 1 assignee View on GitHub

@maliming is already working on this.

Since Sep 17, 2026.

feature-request
Dominant language
C#
Stars
14.4k
Forks
3.7k
Avg merge
15h 32m
Merged PRs (30d)
106

Description

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
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);
    }
}

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.