Power Tools Custom Code - MVC
ASP.NET Zero Power Tools generates code that can be fully regenerated whenever you change an entity. The Custom Code feature lets you add your own code to the generated pages and classes so that your additions survive regeneration.
This page covers how Custom Code works in ASP.NET Core MVC projects. For other UI frameworks, see Custom Code - Angular and Custom Code - React. For the MAUI mobile app, see Custom Code - MAUI.
Enabling Custom Code
Open Power Tools, select your entity, and turn on the Generate Overridable Entity switch in the Entity Information section.
When this switch is on, Power Tools generates additional extension files that you own. These files are created once and never overwritten, even when you regenerate the entity.
The switch affects both the server side and the client side.
Requirements
Project version. The Controller and ViewModel extension files are available on all supported project
versions. The az:begin / az:end region slots live in the current MVC template set, which Power Tools uses
for v15.3+ projects; older projects get the extension files but no region slots.
Limitations
Master-detail child entities. The switch has no effect on the server layer of a child entity in a
master-detail pair - none of the server-side extension files are generated for it, and neither is
<Entity>Controller.Extended.cs. The ViewModel extension files are still generated.
How It Works
MVC uses two complementary mechanisms to preserve your code during regeneration:
| Mechanism | How It Works | Applies To |
|---|---|---|
| Extension files (SkipIfExists) | A base class is regenerated every time. A developer-owned subclass is generated once and never overwritten. | Server-side C# files and MVC Controller / ViewModel classes |
Preserved region slots (az:begin / az:end) |
Named regions inside regenerated files. Power Tools preserves any code you place between the markers. | Razor views (.cshtml) and JavaScript files (.js) |
Razor views and JavaScript files cannot use the base/subclass pattern, so they rely entirely on preserved region slots.
Server Side
When Generate Overridable Entity is enabled, the following server-side extension files are generated. These are shared across all UI frameworks.
Power Tools splits each generated class in two: the generated half keeps the original file name and gains a
Base suffix on the class name, and your half is a new .Extended.cs file holding a subclass at the
original class name. Existing references keep resolving to your class, so nothing else in the solution has to
change.
For a Product entity, the generated files are:
| Your file (never overwritten) | Regenerated file | Purpose |
|---|---|---|
ProductsAppService.Extended.cs |
ProductsAppService.cs |
Override application service methods |
IProductsAppService.Extended.cs |
IProductsAppService.cs |
Extend the service interface |
Product.cs |
ProductBase.cs |
Add custom properties or methods to the entity |
CreateOrEditProductDto.Extended.cs |
CreateOrEditProductDto.cs |
Extend the input DTO |
ProductDto.Extended.cs |
ProductDto.cs |
Extend the list DTO |
GetAllProductsInput.Extended.cs |
GetAllProductsInput.cs |
Add custom filter parameters |
GetAllProductsForExcelInput.Extended.cs |
GetAllProductsForExcelInput.cs |
Add custom Excel export filter parameters |
GetProductForViewDto.Extended.cs |
GetProductForViewDto.cs |
Extend the view/list output DTO |
GetProductForEditOutput.Extended.cs |
GetProductForEditOutput.cs |
Extend the edit output DTO |
ProductXLookupTableDto.Extended.cs |
ProductXLookupTableDto.cs |
Extend the lookup DTO for navigation property X |
ProductsController.Extended.cs |
ProductsController.cs |
Extend the Web.Host API controller |
The entity is the one exception to the naming pattern. Because EF Core maps the class by name, the file is renamed rather than the class:
ProductBase.csis regenerated, andProduct.csis yours and is never overwritten. Do not expect aProduct.Extended.cs, and do not treatProduct.csas generated.
Each .Extended.cs file contains:
// Write your custom code here.
// ASP.NET Zero Power Tools will not overwrite this class
// when you regenerate the related entity.
The base files (without .Extended) are fully regenerated on every run. New entity properties automatically flow into them. Your customizations in the extension files are preserved because Power Tools never touches them after the first generation.
MVC C# - Extension Files
When you enable Generate Overridable Entity, the MVC Controller and ViewModel classes are split into base and extension files:
| Generated File | Description |
|---|---|
*Controller.cs (base) |
Abstract base controller with all generated logic. Regenerated every time. |
*Controller.Extended.cs |
Your controller subclass. Generated once, never overwritten. |
*ViewModel.cs (base) |
Abstract base view model. Regenerated every time. |
*ViewModel.Extended.cs |
Your view model subclass. Generated once, never overwritten. |
If the entity has a view-only page enabled, a *ViewEntityViewModel.Extended.cs is also generated.
Example for a Product Entity
ProductsController.cs ← regenerated (abstract base)
ProductsController.Extended.cs ← yours (extends base)
ProductsViewModel.cs ← regenerated (abstract base)
ProductsViewModel.Extended.cs ← yours (extends base)
Override any method in the extension file:
public class ProductsController : ProductsControllerBase
{
// Write your custom code here.
// ASP.NET Zero Power Tools will not overwrite this class
// when you regenerate the related entity.
}
MVC Razor & JavaScript - Preserved Region Slots
Razor views and JavaScript files use named az:begin / az:end markers. You can place custom markup or code between these markers and it will be preserved on regeneration.
Index Page - Razor Slots
<!-- az:begin(filters) -->
<!-- Add custom advanced filter controls here -->
<!-- az:end(filters) -->
<!-- az:begin(column-headers) -->
<!-- Add custom <th> column headers here -->
<!-- az:end(column-headers) -->
Index Page Toolbar - Razor Slot
<!-- az:begin(toolbar-actions) -->
<!-- Add custom toolbar buttons here -->
<!-- az:end(toolbar-actions) -->
Index Page - JavaScript Slot
// az:begin(columns)
// Add custom DataTables columnDefs entries here
// az:end(columns)
Create/Edit Modal - Razor Slot
<!-- az:begin(form-fields) -->
<!-- Add custom form fields here -->
<!-- az:end(form-fields) -->
View Entity Modal - Razor Slot
<!-- az:begin(detail-fields) -->
<!-- Add custom detail fields here -->
<!-- az:end(detail-fields) -->
Slot Reference
| Slot Name | File | Comment Style | Purpose |
|---|---|---|---|
filters |
Index Razor (.cshtml) |
HTML | Custom advanced filter controls |
column-headers |
Index Razor (.cshtml) |
HTML | Custom table column headers |
toolbar-actions |
Index Razor (.cshtml) |
HTML | Custom toolbar buttons |
columns |
Index JavaScript (.js) |
JS | Custom DataTables column definitions |
form-fields |
Create/edit Razor (.cshtml) |
HTML | Custom form fields |
detail-fields |
View entity Razor (.cshtml) |
HTML | Custom detail display fields |
The
column-headersslot in the Razor file and thecolumnsslot in the JavaScript file are paired. Adding a custom column requires entries in both files.
Example - Adding a Custom Column
In the Razor file (.cshtml):
<!-- az:begin(column-headers) -->
<th>Custom Score</th>
<!-- az:end(column-headers) -->
In the JavaScript file (.js):
// az:begin(columns)
{
data: "product.customScore",
name: "customScore",
render: function(data) { return data || "-"; }
},
// az:end(columns)
Example - Adding a Custom Form Field
<!-- az:begin(form-fields) -->
<div class="form-group">
<label asp-for="InternalNotes" class="control-label"></label>
<textarea asp-for="InternalNotes" class="form-control" rows="3"></textarea>
</div>
<!-- az:end(form-fields) -->
Example - Adding a Custom Toolbar Button
<!-- az:begin(toolbar-actions) -->
<button class="btn btn-outline-primary" onclick="exportCustomReport()">
<i class="fa fa-file-export"></i> Custom Report
</button>
<!-- az:end(toolbar-actions) -->
Preserved Region Rules
The az:begin / az:end markers follow these rules:
| Rule | Description |
|---|---|
| Comment wrapper matches the file type | HTML: <!-- az:begin(name) -->, JavaScript: // az:begin(name) |
| Region names | Letters, digits, underscores, hyphens, and dots only |
| No nesting | Regions cannot be placed inside other regions |
| No duplicates | Each region name must be unique within a file |
| Empty regions are no-ops | If you don't add anything between the markers, the region is ignored |
Safety Features
Power Tools includes safety mechanisms to prevent code loss:
- Abort on malformed markers: If region markers are broken (unclosed, nested, duplicated, mismatched), the entire file is left untouched. No partial merge ever occurs.
- Orphan rescue: If a region name existed in the old file but not in the newly generated output (for example, if a template update removes a slot), the content is saved to a sidecar file (
<filename>.orphaned.txt) and a warning is shown. Content is never silently dropped. - Ownership guard: Toggling the Generate Overridable Entity switch on an entity that already has
generated files is handled in both directions:
- Turning it off points a fully generated template back at the file that holds your code. Power Tools detects your code and refuses to overwrite it.
- Turning it on makes a path that used to be fully generated developer-owned. The file already sitting
there is old generated code, so Power Tools deletes it and lets the template write the new split -
saving a copy as
<filename>.bakbeside it first. If you had hand-edited that file, your changes are in the.bak, not in the regenerated one.