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