Search code examples
c#genericsblazornullable

blazor http generic client what is proper way to handle nulable type like this?


I build some blazor app

and i want to have HttpGet like

person? x = await _httpClient.Get<person?>("url");

So from api if i return person then i want x to be it value, if from api i return NULL want to x be NULL

How to do this properly?

i have some httpclient with get like for example

public async Task<T> Get<T>(string uri, int timeOut = 100)
{           
        using var response = await _httpClient.SendAsync(new HttpRequestMessage(HttpMethod.Get, uri), linkedCts.Token);
        return await response.Content.ReadFromJsonAsync<T>();
    }
}

but the compilator on this response.Content.ReadFromJsonAsync<T>() says that possible null return

But still if I return NULL from api then it throws The input does not contain any JSON tokens. Expected the input to start with a valid JSON token, when isFinalBlock is true. Path: $ | LineNumber: 0 | BytePositionInLine: 0.'

so does i need it to change to be of Task<T?> so it may return null? what is right path here?

what is best approach for this ? or am I missing something and this is not right way ?

Thank You and regards!


Solution

  • In any call into a data pipeline - Server or API - you should consider returning a result object, not a T. This solves the problem of returning a null, you always return a result object, and you can include status information, such as success/failure and a failure message.

    Here's what I use:

    public sealed record ItemQueryResult<TRecord> : IDataResult
    {
        public TRecord? Item { get; init;} 
        public bool Successful { get; init; }
        public string Message { get; init; } = string.Empty;
    
        private ItemQueryResult() { }
    
        public static ItemQueryResult<TRecord> Success(TRecord Item, string? message = null)
            => new ItemQueryResult<TRecord> { Successful=true, Item= Item, Message= message ?? string.Empty };
    
        public static ItemQueryResult<TRecord> Failure(string message)
            => new ItemQueryResult<TRecord> { Message = message};
    }
    

    For the record a list query result looks like this:

    public sealed record ListQueryResult<TRecord> : IDataResult
    {
        public IEnumerable<TRecord> Items { get; init;} = Enumerable.Empty<TRecord>();  
        public bool Successful { get; init; }
        public string Message { get; init; } = string.Empty;
        public long TotalCount { get; init; }
    
        private ListQueryResult() { }
    
        public static ListQueryResult<TRecord> Success(IEnumerable<TRecord> Items, long totalCount, string? message = null)
            => new ListQueryResult<TRecord> {Successful=true,  Items= Items, TotalCount = totalCount, Message= message ?? string.Empty };
    
        public static ListQueryResult<TRecord> Failure(string message)
            => new ListQueryResult<TRecord> { Message = message};
    }
    

    IDataResult provides a common interface for all results.

    public interface IDataResult
    {
        public bool Successful { get; }
        public string Message { get; }
    }