I'm new to ABP, and I did this oficial tutorial succesfully.
The thing is that then I added another class (Planta) and followed the tutorial again (without deleting The Book class), but even when I can create the table and feed data on it (verified), the application fails to load the table, and when I checked the swagger, I found this...
I was expecting it to be Planta instead of BookAppServicePlanta, and I can't find where did I messed things up.
Things I've tryed to solve this
Here is what I did (details below):
Acme.BookStore.Domain/Planta/Planta.cs
:Acme.BookStore.EntityFrameworkCore/EntityFrameworkCore/BookStoreDbContext.cs
Acme.BookStore.EntityFrameworkCore/EntityFrameworkCore/BookStoreDbContextModelCreatingExtensions.cs
Acme.BookStore.Domain/BookStoreDataSeederContributor_Plant.cs
Acme.BookStore.DbMigrator
Acme.BookStore.Application.Contracts/PlantDto.cs
Acme.BookStore.Application/BookStoreApplicationAutoMapperProfile.cs
Acme.BookStore.Application.Contracts/CreateUpdatePlantDto.cs
(and added it too to the automapper as shown on 8) )Acme.BookStore.Application.Contracts/IBookAppServicePlanta.cs
Acme.BookStore.Application/BookAppServicePlanta.cs
Extra Info: I created the pages for Planta and its forms (tutorial part 2 and 3), but even I've double checked those files, I dont belive the problem is on those files, since swagger problem.
I created the class planta on Acme.BookStore.Domain/Planta/Planta.cs
:
using System;
using Volo.Abp.Domain.Entities.Auditing;
namespace Acme.BookStore.Plantas
{
public class Planta : AuditedAggregateRoot<Guid>
{
public string Nombre { get; set; }
public string Descripcion { get; set; }
public string Dirección { get; set; }
public string Lat { get; set; }
public string Long { get; set; }
public string Extra1 { get; set; }
public string Extra2 { get; set; }
public string Extra3 { get; set; }
}
}
Added the entity to Acme.BookStore.EntityFrameworkCore/EntityFrameworkCore/BookStoreDbContext.cs
using Microsoft.EntityFrameworkCore;
using Acme.BookStore.Users;
using Volo.Abp.Data;
using Volo.Abp.EntityFrameworkCore;
using Volo.Abp.EntityFrameworkCore.Modeling;
using Volo.Abp.Identity;
using Volo.Abp.Users.EntityFrameworkCore;
using Acme.BookStore.Books;
using Acme.BookStore.Plantas;
namespace Acme.BookStore.EntityFrameworkCore
{
/* This is your actual DbContext used on runtime.
* It includes only your entities.
* It does not include entities of the used modules, because each module has already
* its own DbContext class. If you want to share some database tables with the used modules,
* just create a structure like done for AppUser.
*
* Don't use this DbContext for database migrations since it does not contain tables of the
* used modules (as explained above). See BookStoreMigrationsDbContext for migrations.
*/
[ConnectionStringName("Default")]
public class BookStoreDbContext : AbpDbContext<BookStoreDbContext>
{
public DbSet<AppUser> Users { get; set; }
public DbSet<Book> Books { get; set; }
public DbSet<Planta> Plantas { get; set; }
/* Add DbSet properties for your Aggregate Roots / Entities here.
* Also map them inside BookStoreDbContextModelCreatingExtensions.ConfigureBookStore
*/
public BookStoreDbContext(DbContextOptions<BookStoreDbContext> options)
: base(options)
{
}
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
/* Configure the shared tables (with included modules) here */
builder.Entity<AppUser>(b =>
{
b.ToTable(AbpIdentityDbProperties.DbTablePrefix + "Users"); //Sharing the same table "AbpUsers" with the IdentityUser
b.ConfigureByConvention();
b.ConfigureAbpUser();
/* Configure mappings for your additional properties
* Also see the BookStoreEfCoreEntityExtensionMappings class
*/
});
/* Configure your own tables/entities inside the ConfigureBookStore method */
builder.ConfigureBookStore();
}
}
}
Mapped the entity to the table on Acme.BookStore.EntityFrameworkCore/EntityFrameworkCore/BookStoreDbContextModelCreatingExtensions.cs
using Acme.BookStore.Books;
using Acme.BookStore.Plantas;
using Microsoft.EntityFrameworkCore;
using Volo.Abp;
using Volo.Abp.EntityFrameworkCore.Modeling;
namespace Acme.BookStore.EntityFrameworkCore
{
public static class BookStoreDbContextModelCreatingExtensions
{
public static void ConfigureBookStore(this ModelBuilder builder)
{
Check.NotNull(builder, nameof(builder));
/* Configure your own tables/entities inside here */
builder.Entity<Book>(b =>
{
b.ToTable(BookStoreConsts.DbTablePrefix + "Books",
BookStoreConsts.DbSchema);
b.ConfigureByConvention(); //auto configure for the base class props
b.Property(x => x.Name).IsRequired().HasMaxLength(128);
});
builder.Entity<Planta>(p =>
{
p.ToTable(BookStoreConsts.DbTablePrefix + "Plantas",
BookStoreConsts.DbSchema);
p.ConfigureByConvention(); //auto configure for the base class props
p.Property(y => y.Nombre).IsRequired().HasMaxLength(128);
});
}
}
}
Dropped the database, and deleted previous migrations
Created a Data Seeder Acme.BookStore.Domain/BookStoreDataSeederContributor_Plant.cs
using System;
using System.Threading.Tasks;
using Acme.BookStore.Plantas;
using Volo.Abp.Data;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Domain.Repositories;
namespace Acme.BookStore
{
public class BookStoreDataSeederContributor_Plant
: IDataSeedContributor, ITransientDependency
{
private readonly IRepository<Planta, Guid> _plantaRepository;
public BookStoreDataSeederContributor_Plant(IRepository<Planta, Guid> plantaRepository)
{
_plantaRepository = plantaRepository;
}
public async Task SeedAsync(DataSeedContext context)
{
if (await _plantaRepository.GetCountAsync() > 0)
{
return;
}
await _plantaRepository.InsertAsync(
new Planta
{
Nombre = "Armijo Guajardo",
Descripcion = "excel god",
Dirección = "las lilas 123",
Lat = "564.765.98",
Long = "100.102.04",
Extra1 = "bla",
Extra2 = "bla bla",
Extra3 = "bla bla bla"
},
autoSave: true
);
}
}
}
Added a new migration, and ran Acme.BookStore.DbMigrator
Created Acme.BookStore.Application.Contracts/PlantDto.cs
using System;
using Volo.Abp.Application.Dtos;
namespace Acme.BookStore.Plantas
{
public class PlantDto : AuditedEntityDto<Guid>
{
public string Nombre { get; set; }
public string Descripcion { get; set; }
public string Dirección { get; set; }
public string Lat { get; set; }
public string Long { get; set; }
public string Extra1 { get; set; }
public string Extra2 { get; set; }
public string Extra3 { get; set; }
}
}
Added it to the Acme.BookStore.Application/BookStoreApplicationAutoMapperProfile.cs
using Acme.BookStore.Books;
using Acme.BookStore.Plantas;
using AutoMapper;
namespace Acme.BookStore
{
public class BookStoreApplicationAutoMapperProfile : Profile
{
public BookStoreApplicationAutoMapperProfile()
{
CreateMap<Book, BookDto>();
CreateMap<CreateUpdateBookDto, Book>();
CreateMap<Planta, PlantDto>();
CreateMap<CreateUpdatePlantDto, Planta>();
}
}
}
created Acme.BookStore.Application.Contracts/CreateUpdatePlantDto.cs
(and added it too to the automapper as shown on 8) )
using System;
using System.ComponentModel.DataAnnotations;
namespace Acme.BookStore.Plantas
{
public class CreateUpdatePlantDto
{
[Required]
[StringLength(128)]
public string Nombre { get; set; }
[Required]
[StringLength(128)]
public string Descripcion { get; set; }
[Required]
[StringLength(128)]
public string Dirección { get; set; }
[Required]
[StringLength(128)]
public string Lat { get; set; }
[Required]
[StringLength(128)]
public string Long { get; set; }
[Required]
[StringLength(128)]
public string Extra1 { get; set; }
[Required]
[StringLength(128)]
public string Extra2 { get; set; }
[Required]
[StringLength(128)]
public string Extra3 { get; set; }
}
}
created the interface Acme.BookStore.Application.Contracts/IBookAppServicePlanta.cs
using System;
using Volo.Abp.Application.Dtos;
using Volo.Abp.Application.Services;
namespace Acme.BookStore.Plantas
{
public interface IBookAppServicePlanta :
ICrudAppService< //Defines CRUD methods
PlantDto, //Used to show books
Guid, //Primary key of the book entity
PagedAndSortedResultRequestDto, //Used for paging/sorting
CreateUpdatePlantDto> //Used to create/update a book
{
}
}
Implemented it on Acme.BookStore.Application/BookAppServicePlanta.cs
using System;
using Volo.Abp.Application.Dtos;
using Volo.Abp.Application.Services;
using Volo.Abp.Domain.Repositories;
namespace Acme.BookStore.Plantas
{
public class BookAppServicePlanta :
CrudAppService<
Planta, //The Book entity
PlantDto, //Used to show books
Guid, //Primary key of the book entity
PagedAndSortedResultRequestDto, //Used for paging/sorting
CreateUpdatePlantDto>, //Used to create/update a book
IBookAppServicePlanta //implement the IBookAppService
{
public BookAppServicePlanta(IRepository<Planta, Guid> repository)
: base(repository)
{
}
}
}
Ran the application
[EDIT]
Acme.BookStore.Web/BookStoreWebAutoMapperProfile.cs
looks like this
using Acme.BookStore.Books;
using Acme.BookStore.Plantas;
using AutoMapper;
namespace Acme.BookStore.Web
{
public class BookStoreWebAutoMapperProfile : Profile
{
public BookStoreWebAutoMapperProfile()
{
CreateMap<BookDto, CreateUpdateBookDto>();
CreateMap<PlantDto, CreateUpdatePlantDto>();
}
}
}
[EDIT]
I created a teting file Acme.BookStore.Application.Tests/BookAppServicePlanta_test.cs
, and they all succeded.
using System;
using System.Linq;
using System.Threading.Tasks;
using Shouldly;
using Volo.Abp.Application.Dtos;
using Volo.Abp.Validation;
using Xunit;
namespace Acme.BookStore.Plantas
{
public class BookAppService_Tests : BookStoreApplicationTestBase
{
private readonly IBookAppServicePlanta _plantaAppService;
public BookAppService_Tests()
{
_plantaAppService = GetRequiredService<IBookAppServicePlanta>();
}
[Fact]
public async Task Should_Get_List_Of_Books()
{
//Act
var result = await _plantaAppService.GetListAsync(
new PagedAndSortedResultRequestDto()
);
//Assert
result.TotalCount.ShouldBeGreaterThan(0);
result.Items.ShouldContain(b => b.Nombre == "Armijo Guajardo");
}
[Fact]
public async Task Should_Create_A_Valid_Planta()
{
//Act
var result = await _plantaAppService.CreateAsync(
new CreateUpdatePlantDto
{
Nombre = "Pedro Cano",
Descripcion = "Cirujano",
Dirección = "Pedro de Valdivia",
Lat = "123213213",
Long = "456456456",
Extra1 = "emmmm",
Extra2 = "no se",
Extra3 = "que poner"
}
);
//Assert
result.Id.ShouldNotBe(Guid.Empty);
result.Nombre.ShouldBe("Pedro Cano");
}
[Fact]
public async Task Should_Not_Create_A_Planta_Without_Name()
{
var exception = await Assert.ThrowsAsync<AbpValidationException>(async () =>
{
await _plantaAppService.CreateAsync(
new CreateUpdatePlantDto
{
Descripcion = "Cirujano",
Dirección = "Pedro de Valdivia",
Lat = "123213213",
Long = "456456456",
Extra1 = "emmmm",
Extra2 = "no se",
Extra3 = "que poner"
}
);
});
exception.ValidationErrors
.ShouldContain(err => err.MemberNames.Any(mem => mem == "Nombre"));
}
}
}
I am not familiar with ABP, but from a quick view to the documentation, it appears that you are not following the naming convention.
The application services should follow this naming convention: Entity
AppService
But it appears you copied/pasted the previous class BookAppService
and just added Planta
to the end. It should be PlantaAppService
instead.
using System;
using Volo.Abp.Application.Dtos;
using Volo.Abp.Application.Services;
namespace Acme.BookStore.Plantas
{
public interface IPlantaAppService :
ICrudAppService< //Defines CRUD methods
PlantDto, //Used to show books
Guid, //Primary key of the book entity
PagedAndSortedResultRequestDto, //Used for paging/sorting
CreateUpdatePlantDto> //Used to create/update a book
{
}
}
using System;
using Volo.Abp.Application.Dtos;
using Volo.Abp.Application.Services;
using Volo.Abp.Domain.Repositories;
namespace Acme.BookStore.Plantas
{
public class PlantaAppService:
CrudAppService<
Planta, //The Book entity
PlantDto, //Used to show books
Guid, //Primary key of the book entity
PagedAndSortedResultRequestDto, //Used for paging/sorting
CreateUpdatePlantDto>, //Used to create/update a book
IPlantaAppService //implement the IPlantaAppService
{
public BookAppServicePlanta(IRepository<Planta, Guid> repository)
: base(repository)
{
}
}
}