Programming.Team Framework Explanation

We are database first on Entity Framework Core so I will start there.

All table have various common properties on it such as Id, CreateDate, UpdateDate, CreatedByUserId, UpdatedByUserId and IsDeleted as you can see on the CertificateIssuers table:

CREATE TABLE [dbo].[CertificateIssuers](
	[Id] [uniqueidentifier] NOT NULL,
	[Name] [nvarchar](500) NOT NULL,
	[Description] [nvarchar](max) NULL,
	[Url] [nvarchar](1000) NULL,
	[CreatedByUserId] [uniqueidentifier] NOT NULL,
	[UpdatedByUserId] [uniqueidentifier] NOT NULL,
	[CreateDate] [datetime] NOT NULL,
	[UpdateDate] [datetime] NOT NULL,
	[IsDeleted] [bit] NOT NULL,
 CONSTRAINT [PK_CertificateIssuers] PRIMARY KEY CLUSTERED 
(
	[Id] ASC
)WITH (STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
GO

ALTER TABLE [dbo].[CertificateIssuers] ADD  CONSTRAINT [DF_CertificateIssuers_Id]  DEFAULT (newid()) FOR [Id]
GO

ALTER TABLE [dbo].[CertificateIssuers] ADD  DEFAULT (getutcdate()) FOR [CreateDate]
GO

ALTER TABLE [dbo].[CertificateIssuers] ADD  DEFAULT (getutcdate()) FOR [UpdateDate]
GO

ALTER TABLE [dbo].[CertificateIssuers] ADD  DEFAULT ((0)) FOR [IsDeleted]
GO

ALTER TABLE [dbo].[CertificateIssuers]  WITH CHECK ADD  CONSTRAINT [FK_CertificateIssuers_Users_CreatedByUserId] FOREIGN KEY([CreatedByUserId])
REFERENCES [dbo].[Users] ([Id])
GO

ALTER TABLE [dbo].[CertificateIssuers] CHECK CONSTRAINT [FK_CertificateIssuers_Users_CreatedByUserId]
GO

ALTER TABLE [dbo].[CertificateIssuers]  WITH CHECK ADD  CONSTRAINT [FK_CertificateIssuers_Users_UpdatedByUserId] FOREIGN KEY([UpdatedByUserId])
REFERENCES [dbo].[Users] ([Id])
GO

ALTER TABLE [dbo].[CertificateIssuers] CHECK CONSTRAINT [FK_CertificateIssuers_Users_UpdatedByUserId]
GO

In the Programming.Team.Core project you would then define the Entity's interface and class.

public interface ICertificateIssuer : IEntity<Guid>, INamedEntity
{

    string? Description { get; set; }

    string? Url { get; set; }
}
public partial class CertificateIssuer : Entity<Guid>, ICertificateIssuer
{

    public string Name { get; set; } = null!;

    public string? Description { get; set; }

    public string? Url { get; set; }

    public virtual ICollection<Certificate> Certificates { get; set; } = new List<Certificate>();
}

Here's a look at the base interfaces (IEntity<TKey> and INamedEntity) and class (Entity<TKey>)

public interface IEntity<TKey>
    where TKey : struct
{
    TKey Id { get; set; }
}
public interface INamedEntity
{
    string Name { get; set; }
}

public abstract class Entity<TKey> : IEntity<TKey>
    where TKey : struct
{
    public TKey Id { get; set; }
    public Guid? CreatedByUserId { get; set; }

    public Guid? UpdatedByUserId { get; set; }

    public DateTime CreateDate { get; set; }

    public DateTime UpdateDate { get; set; }

    public bool IsDeleted { get; set; }

    public virtual User? CreatedByUser { get; set; } = null!;

    public virtual User? UpdatedByUser { get; set; } = null!;
}

Note the interface does not have the common columns (except for Id) on them, we do this because our ViewModels do not necessarily need to expose those fields.

The next step is to wire up the Entity in the ResumesContext class found in Programming.Team.Data.

public partial class ResumesContext : DbContext
{
    public ResumesContext()
    {
    }

    public ResumesContext(DbContextOptions<ResumesContext> options)
        : base(options)
    {
    }

    public virtual DbSet<CertificateIssuer> Certificates { get; set; }

    public virtual DbSet<CertificateIssuer> CertificateIssuers { get; set; }
    
    //Rest of publically exposed DbSet's
    
     protected override void OnModelCreating(ModelBuilder modelBuilder)
 {

     modelBuilder.Entity<CertificateIssuer>(entity =>
     {
         entity.Property(e => e.Id).HasDefaultValueSql("(newid())");
         entity.Property(e => e.CreateDate)
             .HasDefaultValueSql("(getutcdate())")
             .HasColumnType("datetime");
         entity.Property(e => e.Name).HasMaxLength(500);
         entity.Property(e => e.UpdateDate)
             .HasDefaultValueSql("(getutcdate())")
             .HasColumnType("datetime");
         entity.Property(e => e.Url).HasMaxLength(1000);

         entity.HasOne(d => d.CreatedByUser).WithMany(p => p.CertificateIssuerCreatedByUsers)
             .HasForeignKey(d => d.CreatedByUserId)
             .OnDelete(DeleteBehavior.ClientSetNull);

         entity.HasOne(d => d.UpdatedByUser).WithMany(p => p.CertificateIssuerUpdatedByUsers)
             .HasForeignKey(d => d.UpdatedByUserId)
             .OnDelete(DeleteBehavior.ClientSetNull);
          entity.HasQueryFilter(d => !d.IsDeleted);
         entity.ToTable("CertificateIssuers");
     });
     
     //rest of entity configurations
     
     }
 }

Also you will need to add the User collections on the User class in Programming.Team.Core

The Repository and BusinessRepositoryFacade base classes are generally used and wired up in the Program.cs file found in Programming.Team.Web, for instance:

builder.Services.AddScoped<IRepository<CertificateIssuer, Guid>, Repository<CertificateIssuer, Guid>>();

builder.Services.AddScoped<IBusinessRepositoryFacade<CertificateIssuer, Guid>, BusinessRepositoryFacade<CertificateIssuer, Guid, IRepository<CertificateIssuer, Guid>>>();

Then you need to construct a series of ViewModels in the Programming.Team.ViewModels project, for example:

 public class AddCertificateIssuerViewModel : AddEntityViewModel<Guid, CertificateIssuer>, ICertificateIssuer
 {
     public AddCertificateIssuerViewModel(IBusinessRepositoryFacade<CertificateIssuer, Guid> facade, ILogger<AddEntityViewModel<Guid, CertificateIssuer, IBusinessRepositoryFacade<CertificateIssuer, Guid>>> logger) : base(facade, logger)
     {
     }

     private string name = string.Empty;
     public string Name
     {
         get => name;
         set => this.RaiseAndSetIfChanged(ref name, value);
     }

     private string? description;
     public string? Description
     {
         get => description;
         set => this.RaiseAndSetIfChanged(ref description, value);
     }

     private string? url;
     public string? Url
     {
         get => url;
         set => this.RaiseAndSetIfChanged(ref url, value);
     }


     protected override Task Clear()
     {
         Name = string.Empty;
         Description = null;
         Url = null;
         return Task.CompletedTask;
     }
     public override void SetText(string text)
     {
         Name = text;
     }
     protected override Task<CertificateIssuer> ConstructEntity()
     {
         return Task.FromResult(new CertificateIssuer()
         {
             Id = Id,
             Name = Name,
             Description = Description,
             Url = Url

         });
     }
 }
 public class SearchSelectCertificateIssuerViewModel : EntitySelectSearchViewModel<Guid, CertificateIssuer, AddCertificateIssuerViewModel>
 {
     public SearchSelectCertificateIssuerViewModel(IBusinessRepositoryFacade<CertificateIssuer, Guid> facade, AddCertificateIssuerViewModel addViewModel, ILogger<EntitySelectSearchViewModel<Guid, CertificateIssuer, IBusinessRepositoryFacade<CertificateIssuer, Guid>, AddCertificateIssuerViewModel>> logger) : base(facade, addViewModel, logger)
     {
     }

     protected override async Task<IEnumerable<CertificateIssuer>> DoSearch(string? text, CancellationToken token = default)
     {
         if (string.IsNullOrWhiteSpace(text))
             return [];
         SearchString = text;
         var result = await Facade.Get(page: new Pager() { Page = 1, Size = 5 },
             filter: q => q.Name.StartsWith(text), token: token);
         if (result != null)
             return result.Entities;
         return [];
     }
 }
 public class CertificateIssuerViewModel : EntityViewModel<Guid, CertificateIssuer>, ICertificateIssuer
 {
     public CertificateIssuerViewModel(ILogger logger, IBusinessRepositoryFacade<CertificateIssuer, Guid> facade, Guid id) : base(logger, facade, id)
     {
     }

     public CertificateIssuerViewModel(ILogger logger, IBusinessRepositoryFacade<CertificateIssuer, Guid> facade, CertificateIssuer entity) : base(logger, facade, entity)
     {
     }

     private string name = string.Empty;
     public string Name
     {
         get => name;
         set => this.RaiseAndSetIfChanged(ref name, value);
     }

     private string? description;
     public string? Description
     {
         get => description;
         set => this.RaiseAndSetIfChanged(ref description, value);
     }

     private string? url;
     public string? Url
     {
         get => url;
         set => this.RaiseAndSetIfChanged(ref url, value);
     }

     internal override Task<CertificateIssuer> Populate()
     {
         return Task.FromResult(new CertificateIssuer()
         {
             Id = Id,
             Name = Name,
             Description = Description,
             Url = Url,
         });
     }

     internal override Task Read(CertificateIssuer entity)
     {
         Description = entity.Description;
         Url = entity.Url;
         Name = entity.Name;
         Id = entity.Id;
         return Task.CompletedTask;
     }
 }

Note that both the <Entity>AddViewModel and <Entity>ViewModel implement the I<Entity> interface so that we can be confident the ViewModel exposes all the properties on said contract.

Then you need to write views for the ViewModels:

The Add View would look something like:

@inherits ReactiveComponentBase<AddCertificateIssuerViewModel>
@if (ViewModel != null)
{
    <AlertView Alert="ViewModel.Alert" />
    <MudPopover Open="ViewModel.IsOpen" Fixed="true" Class="px-4 pt-4">
        <div class="d-flex flex-column">
            <MudStack>
                <MudTextField @bind-Value="ViewModel.Name" Label="Name" Variant="Variant.Outlined" />
                <MudTextField @bind-Value="ViewModel.Description" Label="Description" Variant="Variant.Outlined" Lines="10" />
                <MudTextField @bind-Value="ViewModel.Url" Label="URL" Variant="Variant.Outlined" />
                <MudStack Row="true">
                    <MudButton OnClick="ViewModel.Add.BindCommand<MouseEventArgs>()">Add Issuer</MudButton>
                    <MudButton OnClick="ViewModel.Cancel.BindCommand<MouseEventArgs>()">Cancel</MudButton>
                </MudStack>
            </MudStack>
        </div>
    </MudPopover>
}

@code {
    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender && ViewModel != null)
            await ViewModel.Init.Execute().GetAwaiter();
        await base.OnAfterRenderAsync(firstRender);
    }
}

The SearchSelectView is implemented in a templated view:

@typeparam TKey where TKey : struct
@typeparam TEntity where TEntity : Entity<TKey>, INamedEntity, new()
@typeparam TAddViewModel where TAddViewModel : class, IAddEntityViewModel<TKey, TEntity>
@typeparam TSearchSelectViewModel where TSearchSelectViewModel : EntitySelectSearchViewModel<TKey, TEntity, TAddViewModel>
@typeparam TAddView where TAddView : ReactiveComponentBase<TAddViewModel>
@inherits ReactiveComponentBase<TSearchSelectViewModel>

@if (ViewModel != null)
{
    <AlertView Alert="ViewModel.Alert" />
    <MudStack>
        <MudAutocomplete T="TEntity" CoerceText="true" Label="@typeof(TEntity).Name" ToStringFunc="c => c.Name"
                         @bind-Value="ViewModel.Selected" DebounceInterval="250" SearchFunc="ViewModel.Search.BindCommand()" />
        @if (ViewModel.CanAdd)
        {
            <MudButton OnClick="ViewModel.StartAdd.BindCommand<MouseEventArgs>()" Class="fw-bold" Variant="Variant.Filled"
                       Color="Color.Primary">Add @typeof(TEntity).Name</MudButton>
            <DynamicComponent Type="typeof(TAddView)" Parameters="@(new Dictionary<string, object> { { "ViewModel", ViewModel.AddViewModel } })" />
        }
    </MudStack>

}

Calling it looks like:

<SearchSelectEntityView 
    TAddView="AddCertificateIssuerView" 
    TEntity="CertificateIssuer" TKey="Guid" TAddViewModel="AddCertificateIssuerViewModel" 
    TSearchSelectViewModel="SearchSelectCertificateIssuerViewModel" ViewModel="ViewModel.CertificateIssuer"/>