Used Only Once... Yet Still Worth Making a Function
Not long ago, a seemingly small but critical bug occurred in the POCU Academy codebase. All we did was store a number in TempData, but it ended up causing a 500 Internal Server Error in production.
Looking back, the problem was that the compiler—something we usually trust—couldn't help us here. The issue only surfaced at runtime.
What is TempData?
In ASP.NET Core MVC, TempData is a short-lived storage used to pass data between Controllers and Views.
Its characteristics are:
- Internally backed by Session or Cookies.
- The value is kept until it's read, and it only lasts until the next request.
- Commonly used for data that must persist after a redirect (e.g., a user name, a status message).
For example, in a Controller:
public IActionResult Save()
{
TempData["Message"] = "Save completed!";
return RedirectToAction("Complete");
}
And in a View:
<p>@TempData["Message"]</p>
It looks simple—just pass data from Controller to View. But here's the catch: TempData actually goes through a serialization/deserialization process.
How the Bug Happened
The problematic code looked like this:
TempData["something"] = longValue;
Checking the commit history, the original code used to look like this:
TempData["something"] = longValue.ToString();
A recent large refactoring had been done, and during that process, one developer removed the .ToString() thinking it was "unnecessary code."
This decision is understandable. Of course, it seems better to pass the actual data type instead of unnecessarily converting it to a string. It's natural to think: "Why not just store the long directly? That looks cleaner."
But this is where the problem arose. ASP.NET Core's TempData relies on string-based serialization. Passing the raw long was actually the wrong approach—it led straight to runtime exceptions.
Why Was This a Problem?
As explained above, TempData relies on string-based serialization.
- Types like
stringandintare supported. - But
longis not supported out of the box.
So storing a long directly caused a serialization error, while converting it with .ToString() worked perfectly fine.
The first developer had discovered this, added .ToString(), but left no comments explaining why. Later, another developer, unaware of the context, saw it as redundant and removed it.
Why Didn't the Compiler Catch It?
The most frustrating part is that the compiler can't catch this kind of mistake.
TempData is essentially an IDictionary<string, object?>.
TempData["something"] = longValue; // valid
TempData["something"] = longValue.ToString(); // also valid
Since anything can be stored in object, the compiler happily accepts both. The failure only shows up at runtime.
The Fix: Creating a Helper Function
To prevent this from happening again, we created a helper function. Fortunately, in our codebase, every API controller inherits from ApiControllerBase, so we could add the helper there.
[ApiController]
public abstract class ApiControllerBase : Controller
{
...
protected void SetTempData(string key, long value)
{
// .NET doesn't support serializer for long value. so we save it as string
TempData[key] = value.ToString(CultureInfo.InvariantCulture);
}
...
}
Now, no one on the team will accidentally put a long directly into TempData. The explicit method signature (contract) makes the intent clear.
Used Only Once… Yet Still Worth It
Normally, we follow the rule: "Only extract a function if it's used multiple times."
But this case was different.
- Right now, this function is only called once in the entire POCU Academy codebase.
- By my usual rule, it wouldn't qualify for its own function.
However, this code was far too easy to misuse, and the compiler offered no protection. So this wasn't about reuse or DRY—it was about preventing mistakes with a safe contract.
This function wasn't created to reduce duplication. It was created as a safety guard to stop future accidents. And that makes it not only justified, but something I'm a little proud of.
Lessons Learned
- Even a small bug can cause a serious production failure.
- If the compiler can't enforce it, we should enforce it with contracts and abstractions.
- Functions aren't just for reuse—they can also prevent mistakes.
Conclusion
Bugs often start small. This one began with removing a simple .ToString(), yet it brought down production.
But from this, we learned an important lesson: functions can serve not only reuse but also safety. And that's why sometimes, even a function that's only called once can be worth creating.