Skip to content

Re-platform SampleMvcWebApp from ASP.NET MVC 5 / .NET 4.5.1 to ASP.NET Core MVC / .NET 10 - #29

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

Re-platform SampleMvcWebApp from ASP.NET MVC 5 / .NET 4.5.1 to ASP.NET Core MVC / .NET 10#29
devin-ai-integration[bot] wants to merge 7 commits into
masterfrom
devin/1785411367-aspnetcore-net10-migration

Conversation

@devin-ai-integration

@devin-ai-integration devin-ai-integration Bot commented Jul 30, 2026

Copy link
Copy Markdown

Summary

Full re-platform of all five projects to net10.0: EF6 → EF Core 10, GenericServices 1.0.9 → EfCore.GenericServices 10.0.0, System.Web.Mvc → ASP.NET Core MVC, Autofac + DiModelBinder → the built-in container + [FromServices], packages.config → SDK-style projects. MIGRATION_NOTES.md (Phase 0) records the audit behind every decision here.

Solution builds clean on the .NET 10 SDK (only NU1903, see below), 45/45 tests pass against SQL Server, and a recorded browser pass over the Blogs/Posts/Tags CRUD flows in a VM found no failures — see the Phase 2 comment for the video and §12 of the notes.

The two things that aren't mechanical

1. EfCore.GenericServices 10.0.0 is an API rewrite, not an upgrade. All thirteen EF6-era service interfaces collapse into two stateful scoped services, and DTOs lose their base class:

-public class DetailPostDto : EfGenericDto<Post, DetailPostDto>
+public class DetailPostDto : ILinkToEntity<Post>

-public ActionResult Index(IListService service)      => service.GetAll<SimplePostDto>()
+public IActionResult Index([FromServices] ICrudServices service) => service.ReadManyNoTracked<SimplePostDto>()
-service.GetDetail<T>(id) / .Update(dto) / .Create(dto) / .Delete<TEntity>(id)
+service.ReadSingle<T>(id) / .UpdateAndSave(dto) / .CreateAndSave(dto) / .DeleteAndSave<TEntity>(id)

ICrudServices carries the status on itself (IsValid/Errors/Message), so controllers must inspect it immediately after the call rather than reading a returned ISuccessOrErrors.

The old DTO hooks (SetupSecondaryData, CreateDataFromDto, UpdateDataFromDto, ResetDto) have no equivalent, and they were doing real work for Posts: populating the blogger drop-down and the tag multi-select, validating the selections, and writing the chosen tags back. That logic now lives in a hand-written ServiceLayer/PostServices/PostDtoService.cs (+ PostDtoServiceAsync) behind IPostDtoService, which the Posts controllers use for create/edit while everything else stays generic. PerDtoConfig classes stop the read-only UI properties mapping back on save.

2. EF Core has no ValidateEntity and does not validate at all on save. EF6 ran data annotations + IValidatableObject inside SaveChanges, and this app leans on that (Post title !/? rules, the "no sentence ending in cow." rules, Tag.Slug uniqueness). SampleWebAppDb now does it explicitly:

public override int SaveChanges(bool acceptAllChangesOnSuccess)
{
    HandleChangeTracking();                              // TrackUpdate.UpdateTrackingInfo, was `return`-ing early on the first non-TrackUpdate entity
    ThrowIfInvalid(ValidateChangedEntities());           // Validator.TryValidateObject + the Tag.Slug uniqueness query
    return base.SaveChanges(acceptAllChangesOnSuccess);
}
public IStatusGeneric SaveChangesWithValidation();       // same, but returns the errors instead of throwing

Tag.Slug also gets a real unique index; the pre-save query is what produces the friendly "The Slug on tag 'x' must be unique and is already being used." instead of a SQL constraint violation.

Other notable points

  • AutoMapper is pinned to 14.0.0 and this raises NU1903. 15.x throws MethodAccessException inside EfCore.GenericServices' SetupDtosAndMappings.CreateConfigAndMapper at runtime (reproduced), and 13.x/14.x are covered by GHSA-rvv3-g6hj-g44x. No version is both patched and compatible; 14.0.0 is the newest that works. Revisit when upstream moves to AutoMapper 15.
  • No OWIN/Identity or SignalR migration was needed. Both were referenced but never wired up — <authentication mode="None" />, no IdentityDbContext/ApplicationUser/account controller, and no Hub subclass or MapSignalR anywhere. The packages and the orphaned jquery.signalR-2.0.3.js client are removed rather than ported; §5/§6 of the notes document this.
  • Schema is preserved. The initial migration reproduces the EF6 layout including the TagPosts join table with its Tag_TagId/Post_PostId columns, which EF Core convention would otherwise have named PostTag/PostsPostId.
  • Seeding replaces the database initializer. DataLayerInitialise.MigrateAndSeed (Database.Migrate() + seed if empty) runs at startup from Program.cs; ResetBlogs keeps the Posts → Tags → Blogs delete order.
  • No connection string in appsettings.json — the app fails fast with a pointed message unless one is supplied; the local Docker SQL Server string lives in appsettings.Development.json and is documented in the README.
  • Bundling → plain <link>/<script> tags over wwwroot/; MvcHtmlString in TempData → raw strings rendered with @Html.Raw; SampleWebApp.sln regenerated (it was also missing BizLayer).

Tests

Tests moves to NUnit 4 / Microsoft.NET.Test.Sdk; 45 pass. Test10DiSimple.cs was deleted — its 11 tests only exercised Autofac's own container (Test01AutoFacSimple, Test04AutoFacLifeTimeScope, …), which is no longer a dependency. Test11AutoFacModules.csTest11ServiceRegistration.cs, same test names, now asserting the AddServiceLayer registrations resolve per scope.

Phase 2 verification

Ran on a VM against SQL Server 2022 in Docker and drove every CRUD flow through the browser. All assertions passed; the server log had zero Unhandled exception/DbUpdateException/SqlException/HTTP/1.1 500 over the whole run (64×200, 11×302, 22×304, no 4xx or 5xx). Video, screenshots and the per-flow table are in the Phase 2 comment and MIGRATION_NOTES.md §12. The run also surfaced stale "Entity Framework 6"/"MVC5"/Autofac copy in the views, fixed here; the long CodeView essays still describe the EF6-era design and are left as follow-up.

Link to Devin session: https://app.devin.ai/sessions/d5054a98ded8483289c0bfbebc691af2
Requested by: @sameerhusain81


Devin Review

Status Commit
⚪ Not started

Run Devin Review

Open in Devin Review (Staging)

devin-ai-integration Bot and others added 5 commits July 30, 2026 11:37
…+ EF Core 10

- SDK-style net10.0 projects, packages.config/App.config/AssemblyInfo removed
- SampleWebAppDb rewritten for EF Core: DbContextOptions ctor, TagPosts join
  table and unique Tag.Slug index kept, EF6 ValidateEntity replaced by
  validation inside the SaveChanges overrides plus SaveChangesWithValidation
- initial EF Core migration, design-time factory and MigrateAndSeed helper
- Autofac modules replaced by AddDataLayer/AddBizLayer/AddServiceLayer
- ServiceLayer DTOs moved to EfCore.GenericServices ILinkToEntity<T>, with
  hand-written IPostDtoService/IPostDtoServiceAsync for the blogger and tags
  handling that GenericServices no longer provides

Co-Authored-By: sameer.husain <sameer.husain@cognition.ai>
…MVC on .NET 10

- SDK-style Microsoft.NET.Sdk.Web project targeting net10.0; drop packages.config,
  Global.asax, App_Start, Web*.config, AssemblyInfo, Settings.settings and Log4Net.xml
- Program.cs minimal hosting replaces Global.asax.cs/App_Start/WebUiInitialise, registers
  AddServiceLayer and migrates+seeds the database in a guarded scope on startup
- appsettings(.Development).json + launchSettings.json replace Web.config; HostTypes is
  bound from AppSettings:HostType via IOptions and injected into _Layout
- all six controllers moved to Microsoft.AspNetCore.Mvc/IActionResult with [FromServices]
  action injection (replaces DiModelBinder) and EfCore.GenericServices ICrudServices(Async)
- ValidationHelper rewritten over StatusGeneric.IStatusGeneric and the built-in JsonResult,
  keeping the {"errorsDict":{...}} shape; TempData now holds plain html strings
- static assets moved to wwwroot/, SignalR 2 client dropped, bundles replaced by script/link tags
- InternalsInfo no longer uses the Windows-only PerformanceCounter so /Home/Internals
  renders on Linux

Co-Authored-By: sameer.husain <sameer.husain@cognition.ai>
Converts Tests to an SDK-style net10.0 project (NUnit 4.6.1, NUnit3TestAdapter 6,
Microsoft.NET.Test.Sdk 18.8.1, EF Core 10 SqlServer) and ports every fixture:

- NUnit 2 classic asserts -> constraint model, [TestFixtureSetUp] -> [OneTimeSetUp],
  all fixtures made public so NUnit 4 discovers them
- new Tests/Helpers/TestDbHelper builds DbContextOptions from the SampleWebAppDb
  environment variable (localhost SQL Server default) and replaces new SampleWebAppDb()
- DbSnapShot uses Database.SqlQueryRaw over the still-named TagPosts join table
- ModelStateTester uses ASP.NET Core IObjectModelValidator + ModelStateDictionary,
  JsonHelper uses System.Text.Json
- SaveChangesWithChecking -> SaveChangesWithValidation, ISuccessOrErrors -> StatusGeneric
- Autofac module tests replaced by Test11ServiceRegistration, which checks the
  AddDataLayer/AddServiceLayer registrations resolve from a scope

Co-Authored-By: sameer.husain <sameer.husain@cognition.ai>
@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

Copy link
Copy Markdown
Author

Phase 2 — verified end-to-end in a VM

Ran the migrated app on this branch (.NET SDK 10.0.301, EF Core 10, EfCore.GenericServices 10.0.0, SQL Server 2022 in Docker) and drove one continuous browser pass over every CRUD flow that goes through the rewritten stack. All assertions passed — no 500s, no unhandled exceptions, no SQL errors. Server log across the whole run: 0 occurrences of Unhandled exception | DbUpdateException | SqlException | HTTP/1.1 500; status distribution 64×200, 11×302, 22×304.

Full recorded pass over the Blogs/Posts/Tags CRUD flows

The two hand-rewritten paths, which is where a re-platform like this actually breaks

1. Duplicate tag slug. EF Core has no ValidateEntity, so the check now lives in SampleWebAppDb.ValidateChangedEntities(). It produces the friendly message rather than letting the new unique index throw:

Duplicate slug rejected with the friendly message on a 200 page

2. !-in-title validation with secondary data intact. PostDtoService.ResetSecondaryData is hand-written code replacing a GenericServices hook that no longer exists. The error redisplays and the blogger drop-down + tag multi-select are still fully populated — empty controls here would be the classic regression:

Blogger drop-down still populated on the redisplayed error form

Posts CRUD — drop-down and many-to-many round-trip

Created a post picking a blogger from the drop-down (which contained a blogger created moments earlier, so it is built from live data) and ctrl-clicking two tags; the row shows exactly those tags. Reopening Edit shows the saved many-to-many pre-selected, so the TagPosts relationship round-trips through the new EF Core mapping.

Posts list showing the new post with its two tags
Edit form with the saved tags pre-selected

Results per flow
Flow Result
Home + navigation, Bootstrap served from the new wwwroot pipeline pass
Tags CRUD — create / edit / details / delete pass
Tag duplicate-slug validation pass
Blogs create + edit pass
Posts CRUD with drop-down + multi-select pass
Post ! validation + secondary-data repopulation pass
Delete post pass
Async ICrudServicesAsync (TagsAsync CRUD, PostsAsync list) pass
Persistence across fresh reads pass

Persistence was verified by navigating away and back: tags/posts returned to their seeded counts with no leftovers, while an intentionally-retained edited blogger survived.

Blogs list with the persisted edited blogger

Coverage is the golden paths, not exhaustive regression — the Blogs Analyse business-method link, Delay endpoints and CodeView pages were only confirmed to load. The stale "Entity Framework 6" / "MVC5" / Autofac copy the run surfaced in the views is fixed in 1be8538; the long CodeView essays still describe the EF6-era design and are called out as follow-up in MIGRATION_NOTES.md §12.

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