Home > Back-end >  blazor generic api that returns list of T
blazor generic api that returns list of T

Time:09-15

im building some blazor app and would like to have some generic methods that can returns any type from shared project

so idea is that i would like to do

 List<GSPparam> parameters.... // stored proc params
 List<person> ret = await this._ihttp.Post<List<person>>("api/...",parameters )

and in api

 [HttpPost]
 [Route("...")]
 public async Task<T> ExecProcTable<T>( [FromBody] List<GSPparam> parameters)
{  
 ....
 somehow KNOWN WHAT THE T IS ?
 in this case List<person>
 so
 List<person> ret = ..... ;
 return ok (ret)
}

but i have no idea how to pass this Type from shared project via string ? and some reflection? please advice

regards

CodePudding user response:

It's not easy as json has no concept of T.

Either use one of the systems out there suggested in the various comments or code your own.

My approach is to create a base controller. This one is for a CQS data pipeline rather than the good old repository pattern but it demonstrates the concepts:

[ApiController]
public abstract class AppControllerBase<TRecord>
    : ControllerBase
    where TRecord : class, new()
{
    protected ICQSDataBroker _dataBroker;

    public AppControllerBase(ICQSDataBroker dataBroker)
        => _dataBroker = dataBroker;

    [Mvc.Route("/api/[controller]/listquery")]
    [Mvc.HttpPost]
    public async Task<ListProviderResult<TRecord>> ListQuery([FromBody] ListQuery<TRecord> query)
        => await _dataBroker.ExecuteAsync<TRecord>(query);

    [Mvc.Route("/api/[controller]/addrecordcommand")]
    [Mvc.HttpPost]
    public async Task<CommandResult> AddRecordCommand([FromBody] AddRecordCommand<TRecord> command)
        => await _dataBroker.ExecuteAsync<TRecord>(command);
/....
}

And then implement real controllers like this:

[ApiController]
public class DboWeatherForecastController : AppControllerBase<DboWeatherForecast>
{
    public DboWeatherForecastController(ICQSDataBroker dataBroker)
        : base(dataBroker)
    { }
}

The API databroker end for a call looks like this:


    public async ValueTask<ListProviderResult<TRecord>> ExecuteAsync<TRecord>(ListQuery<TRecord> query) where TRecord : class, new()
    {
        ListProviderResult<TRecord>? result = null;

        var entityname = (new TRecord()).GetType().Name;
        var response = await _httpClient.PostAsJsonAsync<ListQuery<TRecord>>($"/api/{entityname}/listquery", query);

        if (response.IsSuccessStatusCode)
            result = await response.Content.ReadFromJsonAsync<ListProviderResult<TRecord>>();

        return result ?? new ListProviderResult<TRecord>();
    }

    public async ValueTask<CommandResult> ExecuteAsync<TRecord>(AddRecordCommand<TRecord> command) where TRecord : class, new()
    {
        CommandResult? result = null;

        var entityname = (new TRecord()).GetType().Name;
        var response = await _httpClient.PostAsJsonAsync<AddRecordCommand<TRecord>>($"/api/{entityname}/addrecordcommand", command);

        if (response.IsSuccessStatusCode)
            result = await response.Content.ReadFromJsonAsync<CommandResult>();

        return result ?? new CommandResult();
    }

On the client side I have an API DataBroker with methods that look like this:

    public async ValueTask<ListProviderResult<TRecord>> ExecuteAsync<TRecord>(ListQuery<TRecord> query) where TRecord : class, new()
    {
        ListProviderResult<TRecord>? result = null;

        var entityname = (new TRecord()).GetType().Name;
        this.SetHTTPClientSecurityHeader();
        var response = await _httpClient.PostAsJsonAsync<ListQuery<TRecord>>($"/api/{entityname}/listquery", query);

        if (response.IsSuccessStatusCode)
            result = await response.Content.ReadFromJsonAsync<ListProviderResult<TRecord>>();

        return result ?? new ListProviderResult<TRecord>();
    }

    public async ValueTask<CommandResult> ExecuteAsync<TRecord>(AddRecordCommand<TRecord> command) where TRecord : class, new()
    {
        CommandResult? result = null;

        var entityname = (new TRecord()).GetType().Name;
        this.SetHTTPClientSecurityHeader();
        var response = await _httpClient.PostAsJsonAsync<AddRecordCommand<TRecord>>($"/api/{entityname}/addrecordcommand", command);

        if (response.IsSuccessStatusCode)
            result = await response.Content.ReadFromJsonAsync<CommandResult>();

        return result ?? new CommandResult();
    }

    protected virtual void SetHTTPClientSecurityHeader()
    {
        // add your security headers
    }

(I've not included all the security, exception handling and bad response stuff to keep it simple).

CodePudding user response:

I will answer my solution that i came with help of some other my stackoverflow topic using reflection only ;)

using reflection execute generic method into list of type passed by string

im doing like

_ihttp.Post<IEnumerable<T>>("api/GSP/ExecProc/List/"   ProcedureName   "/"  typeof(T).Name  "/"   CommandTimeout.ToString(), _parameters, CommandTimeout   5, cts);

so sending this T type as string in url

and in api controller

private static async Task<IEnumerable> AwaitQueryAsyncResult<T>(Task<IEnumerable<T>> task)
 => await task;


[HttpPost]
[Route("ExecProc/List/{SPname}/{TypeName}/{Timeout:int=30}")]
public async Task<IActionResult> ExecProcList(string SPname,string TypeName, int timeout , [FromBody] List<GSPparam> parameters)
{

 Type type = type = Type.GetType("XXX.Shared.Classes."   TypeName   ", XXX.Shared")!;

if (type is null) throw new ArgumentException(string.Format("type {0} not found", TypeName));


   IEnumerable l = (IEnumerable)Activator.CreateInstance(typeof(List<>).MakeGenericType(type))!;

      
   System.Reflection.MethodInfo method = _sql.GetType().GetMethods().Where(c => c.Name == ("QueryAsync") && c.GetParameters()[1].ParameterType == typeof(List<GSPparam>)).First().MakeGenericMethod(new Type[] { type });
       
object[] args = { SPname, parameters, CommandType.StoredProcedure, timeout, "Default" };

        var task = method.Invoke(this._sql, args);

        var miAwaitQueryAsyncResult = typeof(GSPController).GetMethod(nameof(AwaitQueryAsyncResult), System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static)!
            .MakeGenericMethod(type);

        var resultAsEnumerable = await (Task<IEnumerable>)miAwaitQueryAsyncResult.Invoke(null, new[] { task })!;

        return Ok(resultAsEnumerable);

please let me known what do You think about this kind of solution please. the type must be in sharec classes namespace and thats it ? Best regards

  • Related