Skip to content

Migrate ASP.NET MVC 5 / .NET 4.5.1 app to ASP.NET Core on .NET 9#22

Open
devin-ai-integration[bot] wants to merge 7 commits into
masterfrom
devin/1780674307-aspnetcore-net9-migration
Open

Migrate ASP.NET MVC 5 / .NET 4.5.1 app to ASP.NET Core on .NET 9#22
devin-ai-integration[bot] wants to merge 7 commits into
masterfrom
devin/1780674307-aspnetcore-net9-migration

Conversation

@devin-ai-integration

@devin-ai-integration devin-ai-integration Bot commented Jun 5, 2026

Copy link
Copy Markdown

Summary

Full-framework migration of the SampleMvcWebApp blogging demo from ASP.NET MVC 5 / .NET Framework 4.5.1 to ASP.NET Core on net9.0. All 5 projects are now SDK-style, EF6 is replaced by EF Core 9, and the legacy GenericServices 1.0.9 package is replaced by an in-repo compat library so controllers/views/DTOs keep their original shape.

Why a compat library instead of EfCore.GenericServices

The app is tightly coupled to the old GenericServices surface: EfGenericDto override hooks (SetupSecondaryData/CreateDataFromDto/UpdateDataFromDto), action-parameter service injection, and the ISuccessOrErrors result type used throughout controllers. EfCore.GenericServices exposes a fundamentally different ICrudServices API and cannot reproduce these patterns. Per the task note ("you may need to implement some service methods manually"), a faithful EF Core + AutoMapper reimplementation of the original interfaces was the lowest-risk path. New project: GenericServices/.

Key changes by layer

  • Projects/config: all .csproj → SDK-style net9.0; removed every packages.config, Web.config, App.config, Global.asax, App_Start/, Autofac modules, AssemblyInfo.cs. Added appsettings.json + launchSettings.json.
  • DataLayer (EF Core): SampleWebAppDb : DbContext, IGenericServicesDbContext with constructor DbContextOptions, Fluent API in OnModelCreating (Post→Blog FK, Post↔Tag many-to-many), SaveChanges hook for LastUpdated, and ExtraValidation enforcing Tag.Slug uniqueness (was EF6 ValidateEntity). DataLayerInitialise now uses EnsureCreated() + XML seed.
  • GenericServices compat lib: EfGenericDto<TData,TDto> (+ async), ISuccessOrErrors/<T>, SaveChangesWithChecking (DataAnnotations + IValidatableObject + ExtraValidation), 13 service interfaces/impls projecting via AutoMapper ProjectTo.
  • Web: Program.cs minimal hosting; built-in DI via AddServiceLayer(); DB provider selectable through config (Sqlite default for local dev, SqlServer available). Controllers inherit Core MVC and use [FromServices] for action-parameter service injection; MvcHtmlString→raw string + @Html.Raw.
  • Views: added _ViewImports.cshtml (tag helpers + usings), _Layout uses direct <link>/<script> instead of bundles, removed Views/Web.config, static assets moved to wwwroot/.
  • Tests: converted to net9.0 xUnit on EF Core InMemory; old Autofac/SignalR/System.Web tests (which tested removed infrastructure) replaced with focused data-layer + service-layer CRUD tests. 12 passing.

Verification

  • dotnet build clean across the solution; dotnet test → 12/12 pass.
  • App runs (dotnet run), seeds SQLite, and all pages return 200 (Home, Posts/Tags/Blogs + Async variants, Create/Edit/Details, secondary-data dropdowns populate).

Notes

  • Bootstrap 3 / jQuery 1.10 assets retained to keep the existing Razor markup working (a Bootstrap 5 upgrade would require rewriting all view markup); deferred as out-of-scope.
  • DelegateDecompiler/Mono.Reflection dropped — the compat library computes aggregate fields (e.g. PostsCount) via AutoMapper projection conventions instead.

Link to Devin session: https://app.devin.ai/sessions/97ae13da2ed64c0c821a10a57d4cf380
Requested by: @dillonvargo


Devin Review

Status Commit
⚪ Not started

Run Devin Review

💡 Connect your GitHub account to enable automatic code reviews.

Open in Devin Review (Staging)

…ET 9

- Convert all projects to SDK-style net9.0; remove packages.config/Web.config/App.config
- Replace EF6 with EF Core 9 (DbContext, Fluent API, EnsureCreated + seed)
- Add GenericServices compat library (EF Core + AutoMapper) preserving the
  original EfGenericDto/ISuccessOrErrors service surface
- Replace Global.asax/OWIN/Autofac/SignalR with Program.cs minimal hosting + built-in DI
- Migrate controllers to ASP.NET Core MVC ([FromServices] action injection)
- Migrate Razor views (_ViewImports, tag helpers, direct link/script tags, wwwroot)
- Convert Tests to net9.0 xUnit using EF Core in-memory (12 passing)
- Use SQLite for local dev (SqlServer provider selectable via config)

Co-Authored-By: Dillon Vargo <dillonvargo@gmail.com>
@devin-ai-integration

Copy link
Copy Markdown
Author

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment, CI, and merge conflict monitoring

devin-ai-integration Bot and others added 2 commits June 5, 2026 16:03
ASP.NET Core's validation visitor invokes all model getters during model
binding. On the posted-back Post DTO the Tags collection is null, so the
TagNames computed getter (Tags.Select(...)) threw ArgumentNullException on
Post Create/Edit. Guard the getter to return empty when Tags is null.

Co-Authored-By: Dillon Vargo <dillonvargo@gmail.com>
Co-Authored-By: Dillon Vargo <dillonvargo@gmail.com>
@devin-ai-integration

devin-ai-integration Bot commented Jun 5, 2026

Copy link
Copy Markdown
Author

Runtime test results — migrated app on .NET 9

Ran the migrated app locally (dotnet run, SQLite seeded on startup) at http://localhost:5000 and exercised the golden-path CRUD flows through the browser UI. All assertions passed. One real bug was found and fixed during testing (now covered by a regression test).

Post Create with secondary data (the previously-broken area) — PASS

The Create form populates the Bloggers dropdown (4) + Tags multiselect (8); submitting creates the post with the correct blogger and tag names.

Post create form
Post created in list

Tag CRUD (create / validate / edit / delete) — PASS

Tags list
Tag created

Post details persisted + regression (Blogs list, PostsAsync) — PASS

Post details
Posts async

Assertions

  • It should perform full Tag CRUD (create, validate, edit, delete) — passed
  • It should create a Post with blogger dropdown + tag multiselect — passed
  • It should show Post details persisted correctly — passed
  • It should edit a Post, preserving blogger + tags — passed
  • It should delete a Post — passed
  • It should render Blogs list + async PostsAsync controller (regression) — passed

Bug found & fixed during testing

Post Create/Edit initially crashed with NullReferenceException: ASP.NET Core's validation visitor invokes the TagNames computed getter on the posted-back DTO where Tags is null. Fixed by null-guarding the getter on all 4 Post DTOs + added a regression test. All 13 xUnit tests pass.

No CI is configured on this repo, so there were no checks to wait on.

Devin session: https://app.devin.ai/sessions/97ae13da2ed64c0c821a10a57d4cf380

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 1 potential issue.

Open in Devin Review

Comment on lines +95 to +105
public string ErrorsAsHtml()
{
var sb = new StringBuilder();
foreach (var error in _errors)
{
if (sb.Length > 0)
sb.Append("<br/>");
sb.Append(error.ErrorMessage);
}
return sb.ToString();
}

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 XSS vulnerability: ErrorsAsHtml() does not HTML-encode user-controlled content before embedding in HTML

The newly written ErrorsAsHtml() method at GenericServices/Core/SuccessOrErrors.cs:95-105 builds an HTML string with <br/> separators but appends raw error.ErrorMessage without HTML-encoding. Error messages can contain user-controlled data — for example, SampleWebAppDb.ExtraValidation (DataLayer/DataClasses/SampleWebAppDb.cs:64) interpolates tagToCheck.Name directly into the message. Controllers then store this in TempData (e.g., SampleWebApp/Controllers/TagsController.cs:111), and multiple views render it with @Html.Raw(TempData["errorMessage"]) (e.g., SampleWebApp/Views/Tags/Index.cshtml:15). A user who enters a tag name like <script>alert('xss')</script> would have that script executed in the browser of any user viewing the error page.

Prompt for agents
The ErrorsAsHtml() method in GenericServices/Core/SuccessOrErrors.cs (lines 95-105, and the duplicate at lines 165-175 in the generic SuccessOrErrors<T> class) appends error.ErrorMessage directly into an HTML string without encoding. Error messages can contain user-supplied data (e.g. tag names from SampleWebAppDb.ExtraValidation). The result is rendered via @Html.Raw() in multiple Razor views (Blogs/Index, Posts/Index, PostsAsync/Index, Tags/Index, TagsAsync/Index).

To fix: HTML-encode each error message before appending it. Use System.Net.WebUtility.HtmlEncode(error.ErrorMessage) instead of bare error.ErrorMessage in both ErrorsAsHtml() implementations. This ensures the <br/> separators are still treated as markup while the message text is safely escaped.
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirmed and fixed in 0f39a9b. This is a real stored XSS: SampleWebAppDb.ExtraValidation interpolates the user-supplied tag name into the error message → ErrorsAsHtml() concatenated it raw → controllers store it in TempData["errorMessage"] → the index views render it with @Html.Raw(...). So a tag named <script>...</script> would execute.

Fix: both ErrorsAsHtml() overloads (non-generic + generic) now WebUtility.HtmlEncode each error.ErrorMessage before appending, while keeping the <br/> separators as literal markup. Added a regression test (ErrorsAsHtmlEncodesMessageButKeepsLineBreaks) asserting <script> is escaped to &lt;script&gt; but <br/> is preserved. All 14 tests pass.

devin-ai-integration Bot and others added 4 commits June 16, 2026 15:05
Error messages can contain user input (e.g. tag names) and are rendered
via @Html.Raw in the index views. Encode each message with
WebUtility.HtmlEncode while keeping the <br/> separators as markup.

Co-Authored-By: Dillon Vargo <dillonvargo@gmail.com>
…olled Recursion (SNYK-DOTNET-AUTOMAPPER-15602868)

Snyk flagged AutoMapper 13.0.1 for an Uncontrolled Recursion vuln; the
fixed range starts at 15.1.3. Updated both project references and adapted
to the AutoMapper 15 API: use the built-in AddAutoMapper DI extension in
ServiceLayerConfig and pass NullLoggerFactory to MapperConfiguration in
the test helper (the ctor now requires an ILoggerFactory).

Co-Authored-By: Dillon Vargo <dillonvargo@gmail.com>
Snyk's Git-based PR checks (security/snyk, license/snyk) error out on the
SDK-style PackageReference projects because, after dropping packages.config
in the migration, there was no committed dependency graph for Snyk to parse
(it does not run a restore). Enable RestorePackagesWithLockFile via
Directory.Build.props and commit the generated packages.lock.json for each
project so the resolved dependency tree is available to scanners.

Co-Authored-By: Dillon Vargo <dillonvargo@gmail.com>
The lock files did not resolve the Snyk PR-check error: Snyk's Git-based
importer does not recognize packages.lock.json as a NuGet manifest and
never runs a restore, so it still has no resolved dependency graph. Reverting
to avoid the RestorePackagesWithLockFile maintenance burden; the real fix is
on the Snyk integration side (scan via the CLI after dotnet restore).

Co-Authored-By: Dillon Vargo <dillonvargo@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants