Unable To Load View Asynchronously - ASP.NET Core MVC - c#

I am currently working on a website that uses a Redis cache implementation, EF Core, and async/await. I'm running into a strange problem when trying to load a view with a view model from an async controller method.
Controller method:
public async Task<ActionResult> Events()
{
var clientEvents = await _eventService.GetEvents();
var model = new EventsListViewModel(clientEvents.FutureEvents, clientEvents.PastEvents, false) //Change this)
{
ClientName = _tenantService.Client.Name,
HasSearch = clientEvents.SearchConfig.IsDisplayed
};
return View(model);
}
Service method:
public async Task<ClientEvents> GetEvents()
{
var cacheValue = await _distributedCache.GetStringAsync($"{REDIS_EVENTS_PREFIX}{_clientId}");
ClientEvents clientEvents = null;
if (string.IsNullOrWhiteSpace(cacheValue))
{
var config = await _activateDbContext
.ClientEventSearchConfigurations
.AsNoTracking()
.Where(ec => ec.ClientId == _clientId)
.FirstOrDefaultAsync();
var futureEvents = await _activateDbContext
.Events
.AsNoTracking()
.Include(e => e.Venue)
.Include(e => e.EventType)
.Where(e => e.ClientId == _clientId)
.Where(e => e.IsActive)
.Where(e => (e.StartDate <= DateTime.UtcNow && e.EndDate >= DateTime.UtcNow) || e.StartDate >= DateTime.UtcNow)
.OrderBy(e => e.StartDate)
.ToListAsync();
var pastEvents = await _activateDbContext
.Events
.AsNoTracking()
.Include(e => e.Venue)
.Include(e => e.EventType)
.Where(e => e.ClientId == _clientId)
.Where(e => e.IsActive)
.Where(e => e.EndDate < DateTime.UtcNow)
.OrderByDescending(e => e.StartDate)
.Take(5)
.ToListAsync();
clientEvents = new ClientEvents
{
SearchConfig = config,
FutureEvents = futureEvents,
PastEvents = pastEvents
};
var options = new DistributedCacheEntryOptions().SetSlidingExpiration(TimeSpan.FromDays(1));
await _distributedCache.SetStringAsync($"{REDIS_EVENTS_PREFIX}{_clientId}", JsonConvert.SerializeObject(clientEvents, Formatting.Indented,
new JsonSerializerSettings()
{
ReferenceLoopHandling = ReferenceLoopHandling.Ignore
}), options);
}
else
{
clientEvents = JsonConvert.DeserializeObject<ClientEvents>(cacheValue);
}
return clientEvents;
}
With this implementation, the return View(model); line in the controller method is showing that the model has been properly populated, but once that line is ran, I see this exception:
IFeatureCollection has been disposed. Object name: 'Collection'
Strangely, if I simply change the service method to use
var cacheValue = _distributedCache.GetString($"{REDIS_EVENTS_PREFIX}{_clientId}");
everything works as expected. I'm banging my head into my desk trying to figure this out, any help is appreciated.
EDIT: Adding code from Startup.cs -
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ActivateDBContext>(options =>
options.UseSqlServer(_configuration[ConfigurationConstants.ACTIVATE_DB_CONNECTION_STRING]));
services.AddStackExchangeRedisCache(options =>
{
options.Configuration = _configuration[ConfigurationConstants.REDIS_CONNECTION_STRING];
options.InstanceName = _configuration[ConfigurationConstants.REDIS_INSTANCE_NAME];
});
services.AddRazorPages();
services.AddControllersWithViews();
services.AddDistributedMemoryCache();
services.AddIPStackApi();
services.AddResponseCompression();
services.AddPortableObjectLocalization(options => options.ResourcesPath = "Localization");
services.Configure<RequestLocalizationOptions>(options =>
{
var supportedCultures = new List<CultureInfo>
{
new CultureInfo("en-US"),
new CultureInfo("en"),
new CultureInfo("fr-FR"),
new CultureInfo("fr"),
new CultureInfo("es"),
new CultureInfo("es-ES"),
};
options.DefaultRequestCulture = new RequestCulture("en-US");
options.SupportedCultures = supportedCultures;
options.SupportedUICultures = supportedCultures;
});
services.AddScoped<ITenantService, TenantService>();
services.AddScoped<ILocationService, LocationService>();
services.AddScoped<IEventService, EventService>();
services.AddScoped<IAncillarySearchService, AncillarySearchService>();
services.AddMvc().AddViewLocalization(LanguageViewLocationExpanderFormat.Suffix);
}

Related

Chained Where causing Null Reference Exception

I have a problem with Entity Framework Plus. Ctx works, ctx2 works but when I execute ctx3 I get : System.NullReferenceException: Object reference not set to an instance of an object. Any ideas what I might be doing wrong ? GetQueryableContext has been reduced to _context.Set.
Thanks.
var ctx = await GetQueryableContext(request);
ctx = ctx.Where(x => x.DepotNo >= request.DepotNo);
ctx.Update(x => new Order() { LorryLoadingListPrinted = 1111 });
var ctx2 = await GetQueryableContext(request);
ctx2 = ctx2.Where(x => x.DepotNo <= request.DepotNo);
ctx2.Update(x => new Order() { LorryLoadingListPrinted = 2222 });
var ctx3 = await GetQueryableContext(request);
ctx3 = ctx3.Where(x => x.DepotNo >= request.DepotNo).Where(x => x.DepotNo <= request.DepotNo);
ctx3.Update(x => new Order() { LorryLoadingListPrinted = 3333 });

Why do I get an error when I add a new object?

This is how i add new object
var dateTime = DateTime.ParseExact(date, "d/MM/yyyy", CultureInfo.InvariantCulture);
var student = await _db.Students.AsNoTracking()
.Include(s => s.Marks)
.ThenInclude(m => m.Subject)
.Include(s => s.Marks)
.ThenInclude(m => m.Teacher)
.FirstAsync(s => s.Id == studentId);
var userId = await GetUserId();
var teacher = await _db.Teachers.Include(t => t.Subjects).AsNoTracking()
.FirstOrDefaultAsync(t => t.Id == userId);
var dbSubject = await _db.Subjects.AsNoTracking().FirstOrDefaultAsync(s => s.Name == subject);
if (!student.Marks.Any(m => m.Date == dateTime && m.Subject.Name == subject))
{
var mark = new Mark
{
Subject = dbSubject,
Value = value,
Teacher = teacher,
Date = dateTime
};
await _db.Marks.AddAsync(mark); // <==== Exception
await _db.SaveChangesAsync();
}
But this method executes with an exception "The instance of entity type 'Subject' cannot be tracked because another instance with the key value '{Id: 6}' is already being tracked."
Where do I attach the subject? I get it with AsNoTracking().
So how to fix this problem?

C# unit test mocking DbSet errors with IQueryable not implementing IAsyncEnumerable

I have the test below and am using standard mocking on a DbSet/Context. When the test runs it fails as it states that "The source IQueryable doesn't implement IAsyncEnumerable Team. Only sources that implement IAsyncEnumerable can be used for Entity Framework asynchronous operations".
public async Task Get_team_name_with_valid_search_term_returns_team_names()
{
// Arrange
var data = new List<Team>
{
new Team {Name = "Leeds"},
new Team {Name = "Glasgow"}
}.AsQueryable();
var mockSet = new Mock<DbSet<Team>>();
mockSet.As<IQueryable<Team>>().Setup(m => m.Provider).Returns(data.Provider);
mockSet.As<IQueryable<Team>>().Setup(m => m.Expression).Returns(data.Expression);
mockSet.As<IQueryable<Team>>().Setup(m => m.ElementType).Returns(data.ElementType);
mockSet.As<IQueryable<Team>>().Setup(m => m.GetEnumerator()).Returns(data.GetEnumerator());
var mockContext = new Mock<RadContext>();
mockContext.Setup(c => c.Team).Returns(mockSet.Object);
var service = new ITeamSearchService(mockContext.Object);
// Act
var result = await service.GetTeamName("Gla");
// Assert
}
The service itself is quite simple
public async Task<List<SearchTeamResponse>> GetTeamName(string searchTerm)
{
if (searchTerm == null)
{
throw new ArgumentNullException(nameof(searchTerm));
}
var query = await _radContext.Team.Where(x => x.Name.StartsWith(searchTerm))
.OrderBy(x => x.Name)
.ToListAsync();
var json = JsonConvert.SerializeObject(query);
var result = JsonConvert.DeserializeObject<List<SearchTeamResponse>>(json);
return result;
}

Web Api from React component returns the json in string format

I have the following React code where I make a call to ASP.NET Web Api controller to fetch some data. The problem is that response.data returns the array json data as string (e.g., "[{\"id\":7,\"......). I am using the identical approach for many other Web Api calls and they are working fine. This one just works unexpectedly different. Any ideas why this might be happening?
export function FetchOverallParticipation(reviewRoundId) {
var url = 'api/A/B';
return dispatch => {
dispatch(fetchOverallParticipationBegin());
axios.get(url, { params: { reviewRoundId } })
.then(response => {
const participationAnalytics = new schema.Entity("participationAnalytics");
const normalizedData = normalize(response.data, [participationAnalytics]);
dispatch(fetchOverallParticipationSuccess(normalizedData));
})
.catch(error => { fetchOverallParticipationFailure(error) });
}
}
Below is the Web Api method.
[HttpGet]
[Route("api/A/B")]
public IEnumerable<OverallParticipationDTO> FetchOverallParticipation(int reviewRoundId)
{
try
{
IEnumerable<OverallParticipationDTO> result =
_context.Submissions
.Where(s => s.ReviewRoundId == reviewRoundId)
.Select(s => new OverallParticipationDTO
{
Id = s.Id,
GoogleDriveDialogueFileId = s.GoogleDriveDialogueFileId,
GoogleDriveReadFileId = s.GoogleDriveReadFileId,
GoogleDriveReviseFileId = s.GoogleDriveReviseFileId,
ReviewedStudents = s.StudentGroup.GroupMemberships
.Select(gm => gm.User)
.Select(u => new ApplicationUserDto
{
Id = u.Id,
FullName = u.FullName
}),
ReviewingStudents = s.StudentGroup.AsServingReviewer.SelectMany(asr => asr.GroupReviewing.GroupMemberships)
.Select(gm => gm.User)
.Select(u => new ApplicationUserDto
{
Id = u.Id,
FullName = u.FullName
})
});
return result;
}
catch (Exception e)
{
return null;
}
}
This was associated with the error inside the linq query. OfType() was not working for the conversion. I was supposed to use is to filter by the derived class:
LearningActionDiscussions = s.GeneralComments
.Where(c=> c is LearningActionComment)
.OfType<LearningActionComment>()
.GroupBy(c => c.UserId)
.Select(fc => new ApplicationUserDto
{
Id = fc.Key,
FullName = fc.Select(a => a.User.FullName).ElementAt(0),
Count = fc.Count()
})
I hope it helps someone.

Is there a way I could change a LINQ query orderBy depending on a parameter?

I coded two LINQ statements with different orderBy. Is there a way that I could change the orderBy without having to code twice like this:
if (param == "s")
{
var result = await db.PhraseCategories
.OrderBy(p => p.SortOrder)
.Select(p => new
{
Id = p.PhraseCategoryShortId,
Name = p.Name
})
.AsNoTracking()
.ToListAsync();
return Ok(result);
}
if (param == "n")
{
var result = await db.PhraseCategories
.OrderBy(p => p.Name)
.Select(p => new
{
Id = p.PhraseCategoryShortId,
Name = p.Name
})
.AsNoTracking()
.ToListAsync();
return Ok(result);
}
Other option can be to work on query like:
IQueryable<PhraseCategory> query = db.PhraseCategories;
// Order as needed
if(param == "s")
query = query.OrderBy(m => m.SortOrder);
else if(param == "n")
query = query.OrderBy(m => m.Name);
var result = await query
.Select(p => new
{
Id = p.PhraseCategoryShortId,
Name = p.Name
})
.AsNoTracking()
.ToListAsync();
return Ok(result);
Additionally, if you can get param in the form of property name to order according to, then you can write extension method to dynamically order according to property name as shown in this answer.
var result = await db.PhraseCategories
.OrderBy(p => param == "s" ? p.SortOrder : p.Name)
.Select(p => new
{
Id = p.PhraseCategoryShortId,
Name = p.Name
})
.AsNoTracking()
.ToListAsync();
return Ok(result);

Categories