TimGeyssens / UIOMatic

Auto generate an integrated crud UI in Umbraco for a db table based on a petapoco poco (and more)
https://timgeyssens.gitbook.io/ui-o-matic/
Other
67 stars 54 forks source link

Resolve DI services in repositories #214

Open ABrizmohun opened 1 year ago

ABrizmohun commented 1 year ago

I'm using UIOmatic 5.1.4 with Umbraco 10.4. I have my data in a database separate from the Umbraco DB. I have an Entity Framework project to access data from that database with services registered into DI on startup.

services.AddScoped<IsbnService, IsbnService>();
services.AddScoped<TitleService, TitleService>();

How can I resolve those services in my AbstractUIOMaticRepository?

Example npoco

[TableName("isbn")]
[UIOMatic("isbns" , "ISBNs" , "ISBN" , FolderIcon = "icon-users" , ItemIcon = "icon-user" , RenderType =UIOMatic.Enums.UIOMaticRenderType.List , HideFromTree = true , RepositoryType = typeof(ISBNRepository) ,Order =10)]
public class ISBN
{

    [PrimaryKeyColumn(AutoIncrement = true)]
    public int Id { get; set; }

    [Required]
    [UIOMaticField(Name = "TitleId", Order = 1, View =UIOMatic.Constants.FieldEditors.Label)]
    public int TitleId { get; set; }

    [Required]
    [ISBNUnique(ErrorMessage = "ISBN already exists.")]
    [RegularExpression(@"^(\d{13})?$", ErrorMessage = "Please enter 13 digits")]
    [UIOMaticListViewField]
    [UIOMaticField(Name = "Code", Description = "13 digit ", View = Constants.FieldEditors.DirtyTextfield)]
    public string Isbn { get; set; }

    [UIOMaticField(Name = "BindingType", View = Constants.FieldEditors.DirtyDropdown, Config = "{'typeAlias': 'BindingType', 'valueColumn': 'Id', 'sortColumn': 'Description', 'textTemplate' : '{{Description}} '}")]
    public string BindingType { get; set; }

    [UIOMaticField(Name = "Usage Instruction", View = UIOMatic.Constants.FieldEditors.Rte)]
    public string UsageInstruction { get; set; }

    public override string ToString()
    {
        return Isbn;
    }
}

Example repository

public class ISBNRepository : AbstractUIOMaticRepository<ISBN, int>
{
    private readonly IsbnService _isbnService;
    private readonly TitleService _titleService;
    public ISBNRepository(IsbnService isbnService, TitleService titleService)
    {
        _isbnService = isbnService;
        _titleService = titleService;
    }

    public override IEnumerable<ISBN> GetAll(string sortColumn = "", string sortOrder = "")
    {
        return _isbnService.GetAll().Select(q =>
            MapToEntity(q)); ;
    }

    public override UIOMaticPagedResult<ISBN> GetPaged(int pageNumber, int itemsPerPage, string searchTerm = "", IDictionary<string, string> filters = null, string sortColumn = "", string sortOrder = "")
    {
        UIOMaticPagedResult<ISBN> paged = new UIOMaticPagedResult<ISBN>();

        if (filters.ContainsKey("TitleId")
            && int.TryParse (filters["TitleId"], out int  TitleId))
        {
            DomainTitle title = _titleService.GetById(TitleId);
            paged.Items = title.Isbns.Select(q => MapToEntity(q));
        }
        else
        {
            paged.Items = GetAll();
        }

        int offset = (pageNumber - 1) * itemsPerPage;

        //Search by SearchTerm
        paged.Items = paged.Items.Where(x =>
            {
                if (string.IsNullOrEmpty(searchTerm))
                    return true;
                else
                    return x.Isbn.Contains(searchTerm) || x.Isbn.Contains(searchTerm);
            })

            .Skip(offset)
            .Take(itemsPerPage)
            .ToList();

        int total = paged.Items.Count();
        int totalpages = total / itemsPerPage + 1;

        paged.TotalPages = totalpages;
        paged.CurrentPage = pageNumber;
        paged.ItemsPerPage = itemsPerPage;

        return paged;
    }

    public override ISBN Get(int id)
    {
        var isbn = _isbnService.GetById(id);
        return MapToEntity(isbn);
    }

    public override ISBN Create(ISBN entity)
    {
        DomainIsbn isbn = new DomainIsbn();
        isbn.TitleId = entity.TitleId;
        isbn.BookNumber = entity.Isbn;
        isbn.UsageInstruction = entity.UsageInstruction;

        if (int.TryParse(entity.BindingType, out int bt))
        {
            isbn.BindingTypeId = bt;
        }

        isbn.LastModifiedBy = -1;

        var id = _isbnService.Insert(isbn);
        entity.Id = id;
        return entity;
    }

    public override ISBN Update(ISBN entity)
    {
        DomainIsbn t = _isbnService.GetById(entity.Id);
        t.BookNumber = entity.Isbn;
        t.UsageInstruction = entity.UsageInstruction;

        if (int.TryParse(entity.BindingType, out int bt))
        {
            t.BindingTypeId = bt;
        }
        else
        {
            t.BindingTypeId = null;
        }
        _isbnService.Update(t);
        return entity;
    }

    public override void Delete(int[] ids)
    {
        _isbnService.DeleteByIds(ids);
    }

    public override long GetTotalRecordCount()
    {
        return GetAll().Count();
    }

    private ISBN MapToEntity(DomainIsbn _isbn)
    {
        return new ISBN()
        {
            Id = _isbn.Id,
            Isbn = _isbn.BookNumber,
            TitleId = _isbn.TitleId,
            BindingType = _isbn.BindingTypeId == null ? null : _isbn.BindingTypeId.ToString(),
            UsageInstruction = _isbn.UsageInstruction
        };
    }
}

Currently, I get a Constructor on type System.MissingMethodException: [Redacted].ISBNRepository not found.

Stack Trace:

at System.RuntimeType.CreateInstanceImpl(BindingFlags bindingAttr, Binder binder, Object[] args, CultureInfo culture)
   at System.Activator.CreateInstance(Type type, Object[] args)
   at UIOMatic.UIOMaticHelper.GetRepository(UIOMaticAttribute attr, UIOMaticTypeInfo typeInfo)
   at UIOMatic.Services.NPocoObjectService.GetPaged(Type type, Int32 itemsPerPage, Int32 pageNumber, String sortColumn, String sortOrder, IDictionary`2 filters, String searchTerm)
   at UIOMatic.Web.Controllers.ObjectController.GetPaged(String typeAlias, Int32 itemsPerPage, Int32 pageNumber, String sortColumn, String sortOrder, String filters, String searchTerm)
Lamborobin commented 1 year ago

I have encountered the same error as you in my Umbraco 10 and 11 solutions. I haven't had the time to test it yet without PetaPoco ORM but could this be helpful? (from the uiomatic docs): https://timgeyssens.gitbook.io/ui-o-matic/12.advanced#iuiomaticobjectservice

huzzi commented 1 year ago

I'm facing the same problem.

JasonEleventeen commented 1 year ago

This should really be resolved by getting the service from the container but this used to work (not sure about newer c# versions) as a workaround

HttpContext.RequestServices.GetService(typeof(ISomeService));

ABrizmohun commented 1 year ago

I ended up forking this project and injecting IServiceProvider into UIOMaticHelper. Then in GetRepository, I resolve my IUIOMaticRepository from DI if it exists otherwise, it creates a new instance.

namespace UIOMatic
{
    public class UIOMaticHelper : IUIOMaticHelper
    {
        private readonly AppCaches _appCaches;
        private readonly IHostingEnvironment _hostingEnvironment;
        private readonly IScopeProvider _scopeProvider;
        private readonly UIOMaticObjectService _uioMaticObjectService;
        private readonly IServiceProvider _serviceProvider;
        private readonly ILogger<IUIOMaticHelper> _logger;

        public UIOMaticHelper(AppCaches appCaches,
            IHostingEnvironment hostingEnvironment,
            IScopeProvider scopeProvider,
            UIOMaticObjectService uioMaticObjectService,
            IServiceProvider serviceProvider,
            ILogger<IUIOMaticHelper> logger)
        {
            _appCaches = appCaches;
            _hostingEnvironment = hostingEnvironment;
            _scopeProvider = scopeProvider;
            _uioMaticObjectService = uioMaticObjectService;
            _serviceProvider = serviceProvider;
            _logger = logger;
        }

        public  IUIOMaticRepository GetRepository(UIOMaticAttribute attr, UIOMaticTypeInfo typeInfo)
        {
            var existingRepositories = _serviceProvider.GetServices<IUIOMaticRepository>();
            var existingRepository = existingRepositories.FirstOrDefault(x => x.GetType() == attr.RepositoryType);
            if (existingRepository is not null)
            {
                return (IUIOMaticRepository)existingRepository;
            }
            return typeof(DefaultUIOMaticRepository).IsAssignableFrom(attr.RepositoryType)
                ? (IUIOMaticRepository)Activator.CreateInstance(attr.RepositoryType, attr, typeInfo, _scopeProvider, _uioMaticObjectService)
                : (IUIOMaticRepository)Activator.CreateInstance(attr.RepositoryType, _scopeProvider);
        }

Then in my project, I register my IUIOMaticRepository into DI.

builder.Services.AddTransient<IUIOMaticRepository, ISBNRepository>();
builder.Services.AddTransient<IUIOMaticRepository, TitleRepository>();