Trivial exit from an method that may perform an asynchronous call - c#

I've got an issue calling a method that may return a Task<T> or null depending on the result of an initial synchronous lookup call (this itself might be an anti-pattern so please let me know).
I kind of want to return null if a trivial exit condition occurs but this is causing the calling (outer) method to fail because the outer call expects a Task<T> response (trivial exit) which gets pushed through to ConfigureAwait(true) which subsequently produces a NullReferenceException.
Outer calling method:
var res = await MyService.GetUserCourseStatusAsync(userID, productID).ConfigureAwait(true);
Intermediate method:
public Task<IGetUserCourseResponse> GetUserCourseStatusAsync(int userID, int productID)
{
// Look up User's ID (if exists)
var userCredentials = GetUserCredentials(userID);
if (userCredentials?.UserID == null)
return null; // Trivial return of null ("User not registered"). *** This causes exception on ConfigureAwait(true) above ***
// Another synchronous call
var courseId = GetCourseID(productID);
if (courseId == null)
throw new InvalidOperationException($"Product #{productID} is not a course");
// Asynchronous call to inner method (this bit works fine)
return GetUserCourseAsync(userCredentials.UserID.Value, courseId.Value);
}
So my thought then is that we should always return a Task<T> instead of null.
However, all of these cause compile errors:
//return null; // Trivial return of null ("User not registered"). *** This causes exception
// Compile error: CS0029: Cannot implicitly convert type 'GetUserCourseInner' to 'System.Threading.Tasks.Task<IGetUserCourseResponse>'
return new GetUserCourseInner(); // Not registered
// Compile error: CS1503 Argument 1: cannot convert from 'GetUserCourseInner' to 'System.Func<IGetUserCourseResponse>'
return new Task<IGetUserCourseResponse>(new GetUserCourseInner()); // Not registered
How do I return a dummy Task<T> that isn't a result of a async call?
Is this even the correct approach?

It would be better, as you suggested, to return a Task<IGetUserCourseResponse> which contains null (or some other sentinel value). You can create such a completed Task with Task.FromResult((IGetUserCourseResponse)null):
public Task<IGetUserCourseResponse> GetUserCourseStatusAsync(int userID, int productID)
{
// Look up User's ID (if exists)
var userCredentials = GetUserCredentials(userID);
if (userCredentials?.UserID == null)
return Task.FromResult((IGetUserCourseResponse)null);
// Another synchronous call
var courseId = GetCourseID(productID);
if (courseId == null)
throw new InvalidOperationException($"Product #{productID} is not a course");
// Asynchronous call to inner method (this bit works fine)
return GetUserCourseAsync(userCredentials.UserID.Value, courseId.Value);
}
Alternatively, you could make your outer method async. Note however that this changes its behaviour in the case where it throws an InvalidOperationException: instead of the method throwing this exception directly, it will instead return a Task which contains this exception. This may or may not be what you want:
public async Task<IGetUserCourseResponse> GetUserCourseStatusAsync(int userID, int productID)
{
// Look up User's ID (if exists)
var userCredentials = GetUserCredentials(userID);
if (userCredentials?.UserID == null)
return null;
// Another synchronous call
var courseId = GetCourseID(productID);
if (courseId == null)
throw new InvalidOperationException($"Product #{productID} is not a course");
// Asynchronous call to inner method (this bit works fine)
return await GetUserCourseAsync(userCredentials.UserID.Value, courseId.Value);
}

Just return a Task that holds a null value as result
return Task.FromResult<IGetUserCourseResponse>(null);

Related

IMemoryCache.GetOrCreateAsync returns null

I have a lot of NullReferenceException exceptions on the line if (foosDictionary.TryGetValue(id, out var foo)) and I cannot in any way reproduce the issue.
Stack trace:
System.NullReferenceException: Object reference not set to an instance of an object.
at MyProject.Service.Baz(Guid id) in CustomClass.cs:line 900
at MyProject.Service.BazFeature(Guid id) in CustomClassCaller.cs:line 288
line 900 is the line in question. It is a direct reference to the dictionary: if (foosDictionary.TryGetValue(id, out var foo)).
Below is the code that leads to the problem line:
public Foo Baz(Guid id)
{
Func<Task<Dictionary<Guid, Foo>>> createDictionary =
async () => (await repository.GetFoos()).ToDictionary(sm => sm.Id);
var foosDictionary = await _cache.GetOrCreateAsync<Dictionary<Guid, Foo>>(
"cacheKey",
async cacheEntry =>
{
cacheEntry.AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(15);
return await createDictionary();
});
if (foosDictionary.TryGetValue(id, out var foo))
{
return foo;
}
}
// also there's the remaining code, but it shouldn't execute and it is lower than line 900. Anyway, here it is.
var newDictionary = await createDictionary();
if (newDictionary.TryGetValue(id, out var newFoo))
{
fooDictionary = newDictionary;
return newFoo;
}
throw new Exception("Error");
The Usual Suspects are:
await repository.GetFoos() - it uses Dapper.QueryAsync method to get IEnumerable<Foo> and it can't return null. Anyway, I tried hardcoding null return - and goes the next step
.ToDictionary() - it will throw on null. And it does if I hardcode the null return. Also it has different stacktrace and points to GetOrCreateAsync, not the line of code in question.
GetOrCreateAsync - all the other suspects will throw and give different stack trace so this must be it. This method returns null, but I can't figure out how and why.
Like, if there is null on the cache key, it should go and create the value. createDictionary can't return null so it's out of the question. So...how does that even happen? Why?
Memory cache is registered in Startup like this: services.AddMemoryCache(). I don't know whether it's Scoped or Singleton and the Microsoft docs don't address it in a straightforward way.
The app is ASP.NET Core 3.1, GetOrCreateAsync extension method is from v6.0.0, but the actual implementation (or so it seems) gets passed as v3.1.0.
How can I resolve the issue? Can GetOrCreateAsync really return null in some cases? If so, how to reproduce them?
PS. I've thought about thread 1 getting the entry and then thread 2 somehow nullifying it, but that shouldn't work since thread 1 already has the reference to the dictionary. Right?

Mock: SetReturnsDefault for async methods always return null

I want to mock a service to always return a default object.
I tried this but it still always return null:
var service = new Mock<MyService>();
service.SetReturnsDefault(Task.FromResult(new ServiceResult<object>
{
Status = ResultStatus.OK,
Value = null
}));
The methods are async and return Task<IServiceResult<object>> where object can be any object (also a collection of objects).
ServiceResult implement the IServiceResult interface.
Why is it not working? I would like to avoid setup every method...
Thanks for your help!
EDIT:
I use for example a method like this but it result is always null instead of the default ServiceResult:
var result = await _service.GetEntityAssetUsagesAsync(EntityTypeEnum.Radio, radio.MediaChannelId);
Here is (a piece of) my interface:
Task<IServiceResult<List<EntityAssetUsageDto>>> GetEntityAssetUsagesAsync(EntityTypeEnum entityType, int entityId);
Task<IServiceResult<List<AssetDto>>> GetAssetsAsync(List<long> assetIds, bool withoutException = false);

Return (RecordNotFound) Exception or null if record is not found in the database?

I'm not really sure what is the prefered way when dealing with record not found in the database. Is it better to write Find method which returns null or Get method which returns RecordNotFoundException?
[AuthenticateFilter(UsernameAndSecretKey)]
[Route("api/v1/activities/emails/{id}")]
[HttpGet]
public IHttpActionResult GetEmailActivity(int id)
{
try
{
// business logic service, could use only db service but this way we can do unit tests (just fill bl service method with fake objects)
var service = new EmailActivityBlService();
// 1. use Find method which returns null in case record with provided id does not exist in db
var model = service.FindActivity(id);
if( model != null )
return Ok(model);
return NotFound();
// 2. or is this approach better
// throws RecordNotFoundException in case row by id is not found in database
return Ok(service.GetActivity(id));
}
catch(RecordNotFoundException e) { return NotFound(); }
catch(Exception e) { return InternalServerError(e); }
}
EmailActivityBlService has next code in case anyone interested (showing only the important part):
private EmailActivityDbService _dbService;
public EmailActivityModel GetActivity(int id)
{
var model = this._dbService.GetActivity(id);
if( model == null )
throw new RecordNotFoundException(); // I suppose System.Data.ObjectNotFound is also suitable
return model;
}
public EmailActivityModel FindActivity(int id)
{
// typical entity framework query
// using(var context = new ..) { return contect.EmailActivity.Where()..SingleOrDefault().ConvertToModel();
return this._dbService.GetActivity(id);
}
UPDATE
Talked with my colleagues, we decided to go with this solution. As why GetActivity returns null instead of throwing Exception, I prefer answer from rboe:
So return null if it is can happen in your domain, that records do not exist (in my experience this is most often the case). If you expect a record to exist and it is not there, then it is valid to throw an exception.
[AuthenticateFilter(UsernameAndSecretKey)]
[Route("api/v1/activities/emails/{id}")]
[HttpGet]
public IHttpActionResult GetEmailActivity(int id)
{
var service = new EmailActivityBlService();
var model = service.GetActivity(id); // returns null in case activity is not found
if( model != null )
return Ok(model);
return NotFound();
}
We avoided any try-catch in the methods and put global filter when Exception occurs:
File: App_Start\WebApiConfig.cs
public class WebApiExceptionFilter : ExceptionFilterAttribute
{
public override void OnException(HttpActionExecutedContext actionExecutedContext)
{
actionExecutedContext.Response = actionExecutedContext.Request.CreateErrorResponse(HttpStatusCode.InternalServerError, actionExecutedContext.Exception.Message, actionExecutedContext.Exception);
}
}
Both ways are valid ones.
It is a different emphasis whether you use exceptions or the return value null to indicate non existing records.
Exceptions exist to signal an error state (something happened that is abnormal). The code in the catch-handler is focused on how to deal with an error and not to contain business logic.
If you return null then it will be a normal and 'non exceptional' state in your model.
So return null if it is can happen in your domain, that records do not exist (in my experience this is most often the case). If you expect a record to exist and it is not there, then it is valid to throw an exception.
I disagree with the other answer. In the case of a GetyById method, I wouldn't say to return null instead of throwing because you could argue it was "expected" that there might not be a record with the requested id. This "exceptions for exceptional situations," while often stated, I don't really think is the best way to think about the method's contracts. APIs should make semantic sense, ideally.
Instead, I suggest to throw exceptions whenever the method cannot do what it was told to do. So GetById methods should thrown an exception in the event there is no such record with the requested id in the system. The Find method should probably return an enumerable, which of course could be empty in the event no records match the criteria given.
An API which has a FindById method strikes me as odd; if you are giving the API an ID, that implies the caller could somehow have learned the ID in a previous API call, and so the API shouldn't need to "find" an already known to exist record. It should provide a way to get the record directly by its id. Instead Find should be for locating records when you aren't sure they exist, and using some other criteria.
Given the web service call, I would go with the service calling the GetById method, as the web service caller also learned the id somehow. If the id turns out not to exist, the library can throw the RecordNotFoundException, which causes the service call to return 404.

Two simultaneous AJAX requests to same action causing error

I have the following action:
public async Task<ActionResult> Cart(bool refresh = false, bool mobile = false)
{
var user = await Auth.GetUserAsync();
//rest of the code
}
Which is being called twice by 2 AJAX calls at the same time (one to render mobile partial, other normal page).
$(document).ready(function () {
$("basket").html('<i class="fa fa-spinner fa-spin"></i>');
$('basket').load(PartialURL + "/Cart");
$('MobileBasket').load(PartialURL + "/Cart?mobile=true");
});
The real problem occurs in Auth.GetUserAsync() function.
Code:
public static async Task<User> GetUserAsync()
{
if (HttpContext.Current == null || HttpContext.Current.Request == null
|| HttpContext.Current.User == null || HttpContext.Current.Request.IsAuthenticated == false)
return null;
//if session does not exist, but user is logged in, fill session information
if (HttpContext.Current.Session[sessionKey] == null && HttpContext.Current.Request.IsAuthenticated)
using (var dal = new DAL.DAL())
{
//load user from DB
var user = await dal.SingleOrDefaultAsync<User>(m => m.Email == HttpContext.Current.User.Identity.Name);
HttpContext.Current.Session[sessionKey] = user;
}
return HttpContext.Current.Session[sessionKey] as User;
}
In one of those calls, the function returns user normally, but the other call produces following error (screenshot from fiddler). Notice how the first call was successful.
Code for SingleOrDefaultAsync is following:
public Task<T> SingleOrDefaultAsync<T>(Expression<Func<T, bool>> predicate) where T : class
{
return _context.Set<T>().SingleOrDefaultAsync(predicate);
}
I have checked while debugging, and email is present in both of the requests, _context is not null, and the user with requested email exists, but one of those always returns an error. Error is returned randomly. Sometimes in first, sometimes in second AJAX call.
Can someone please tell me what is causing this error? Any help is greatly appreciated.
I would assume, because of the other checks you have in place, that HttpContext.Current.User.Identity.Name (or even HttpContext.Current.User.Identity) is null, and that that's what's causing the error.
This might be so if the first request has caused the authentication process to start, but that process is not yet complete - so that the null checks you have succeed, but the subsequent member access fails. In other words, you have a timing issue / race condition. That's just a guess, though.
Update: following my suggestion to store the name in a variable and use the variable in the lambda, everything worked. But why?
My theory is that the expression passed to your SingleOrDefaultAsync method included the expression HttpContext.User.Identity.Name, rather than the value of that expression.
This expression was then evaluated in your DAL, where HttpContext.Current was (presumably) null (assuming your DAL is not in your web project). QED?

Accessing Result property of a Task

I am trying to retrieve the ID of an album using the C# Facebook SDK. However I am getting the below error:
System.Threading.Tasks.Task' does not contain a definition for 'Result' and no extension method 'Result' accepting a first argument of type 'System.Threading.Tasks.Task' could be found
Please see the below code, the error occurs on the foreach line
try
{
string wallAlbumID = string.Empty;
FacebookClient client = new FacebookClient(accessToken);
client.GetTaskAsync(pageID + "/albums")
.ContinueWith(task =>
{
if (!task.IsFaulted)
{
foreach (dynamic album in task.Result.data)
{
if (album["type"] == "wall")
{
wallAlbumID = album["id"].ToString();
}
}
}
else
{
throw new DataRetrievalException("Failed to retrieve wall album ID.", task.Exception.InnerException);
}
});
return wallAlbumID;
}
For the record, the FacebookClient.GetTaskAsync method returns Task<object>
I don't know the Facebook API, but the error seems to indicate, that you're dealing with Task class (non-generic) which does not have Result property. It is the generic Task<T> derived from non-generic Task class that has the property. They both allow to run code asynchronously, but the generic class is able to run methods that return values.
If GetTaskAsync returns Task and not Task<T>, then it means you can't get the result from it, as the operation it runs in the background does not return anything.
When I compile your code, I get two errors, the first one is the one you mentioned, and the second one is:
'object' does not contain a definition for 'data' and no extension method 'data' accepting a first argument of type 'object' could be found
This second error is your actual error: task.Result is an object, but (I assume) you want to treat it as dynamic. Because of this error, the compiler also tries to use the overload of ContinueWith() that uses just Task, not Task<object>, which is why you're also getting the first error.
To fix this error, you should cast task.Result to dynamic:
dynamic result = task.Result;
foreach (dynamic album in result.data)
This will compile fine, but it won't actually work, because you set the local variable after you return from the enclosing method.
If you're using C# 5.0, you should use await here, instead of ContinueWith():
try
{
dynamic result = await client.GetTaskAsync(pageID + "/albums");
foreach (dynamic album in result.data)
{
if (album["type"] == "wall")
{
return (string)album["id"].ToString();
}
}
return string.Empty;
}
catch (Exception e) // you should use a specific exception here, but I'm not sure which
{
throw new DataRetrievalException("Failed to retrieve wall album ID.", e);
}
If you can't use C# 5.0, then your whole method should return a Task<string> that's returned by ContinueWith():
return client.GetTaskAsync(pageID + "/albums")
.ContinueWith(
task =>
{
if (!task.IsFaulted)
{
dynamic result = task.Result;
foreach (dynamic album in result.data)
{
if (album["type"] == "wall")
{
return (string)album["id"].ToString();
}
}
return string.Empty;
}
else
{
throw new DataRetrievalException(
"Failed to retrieve wall album ID.", task.Exception.InnerException);
}
});

Categories