The NetForge is a library that provides a user-friendly and intuitive user interface for performing CRUD operations on your database entities within your .NET applications.
- NetForge - Admin Panel for ASP.NET Core 6 & 7 & 8
- How to Use
- Global Configurations
- Customizing Entities
- Customizing Entity Properties
- Fluent API
- Data Attributes
- Display Formatting
- Data Sorting
- Calculated Properties
- Display Properties as Title Case
- Default Values
- Navigation Properties
- Exclude All Properties and Include Specific Only
- Exclude Property from Query
- Formatting Property as HTML
- Generated Properties
- Rich Text Field
- Image Properties
- Read-only Properties
- String Truncate
- Multiline Text Field Property
Add NetForge to your service collection in Program.cs
:
appBuilder.Services.AddNetForge(optionsBuilder =>
{
optionsBuilder.UseEntityFramework(efOptionsBuilder =>
{
efOptionsBuilder.UseDbContext<MyDbContext>();
});
...
});
Make your application to use the admin panel:
app.UseNetForge();
By default, NetForge Admin is running on /admin but you can configure to use your custom endpoint like this:
appBuilder.Services.AddNetForge(optionsBuilder =>
{
optionsBuilder.UseEndpoint("/manage");
...
});
You can customize the header title like this:
appBuilder.Services.AddNetForge(optionsBuilder =>
{
optionsBuilder.SetHeaderTitle("title");
...
});
And customize the HTML title like this:
appBuilder.Services.AddNetForge(optionsBuilder =>
{
optionsBuilder.SetHtmlTitle("title");
...
});
You can customize the access policy by requiring specific Identity roles:
appBuilder.Services.AddNetForge(optionsBuilder =>
{
optionsBuilder.AddAccessRoles("Role1", "Role2", "Role3");
...
});
Alternatively, you can use a custom function to perform authorization checks. Example:
appBuilder.Services.AddNetForge(optionsBuilder =>
{
optionsBuilder.ConfigureAuth(serviceProvider =>
{
// Allow all authenticated users to see the Admin Panel.
var httpContext = serviceProvider.GetRequiredService<IHttpContextAccessor>().HttpContext;
return Task.FromResult(httpContext?.User.Identity?.IsAuthenticated ?? false);
});
});
You can read about search here.
Located in the top right corner of the admin panel is a "View Site" link, configurable to direct users to the website URL. The default URL is "/". You can customize this value using the Fluent API:
services.AddNetForge(optionsBuilder =>
{
optionsBuilder.ConfigureUrl("https://www.example.com/");
});
Group rows of entities into categories and make it easier for users to navigate and understand the data presented.
You can override the default layout of the admin panel. To do this, create a new layout in your host project and specify its type in the configuration.
services.AddNetForge(optionsBuilder =>
{
optionsBuilder.SetCustomLayout(typeof(CustomLayout));
});
Your custom layout should inherit from the AdminBaseLayout
class.
The example of the custom component with navigation bar and footer:
@using MudBlazor
@inherits Saritasa.NetForge.Blazor.Shared.AdminBaseLayout
<MudThemeProvider />
<MudDialogProvider />
<MudSnackbarProvider />
<MudAppBar Color="Color.Primary" Elevation="4">
<MudText Typo="Typo.h6">My Application</MudText>
<MudSpacer />
<MudNavMenu>
<MudNavLink Href="/about">About</MudNavLink>
<MudNavLink Href="/contact">Contact</MudNavLink>
</MudNavMenu>
</MudAppBar>
@Body
<footer>
<MudPaper Class="pa-4" Elevation="4">
<MudText Typo="Typo.body2">My Application</MudText>
<MudSpacer />
<MudNavMenu>
<MudNavLink Href="/privacy">Privacy Policy</MudNavLink>
<MudNavLink Href="/terms">Terms of Service</MudNavLink>
</MudNavMenu>
</MudPaper>
</footer>
Before assigning entities to specific groups, users need to define the groups to which the entities will belong.
To create a new group, utilize the Fluent API through AdminOptionsBuilder
. A name is required for each group, and a description is optional.
services.AddNetForge(optionsBuilder =>
{
optionsBuilder.UseEntityFramework(efOptionsBuilder =>
{
efOptionsBuilder.UseDbContext<ShopDbContext>();
}).AddGroups(new List<EntityGroup>
{
new EntityGroup{ Name = "Product", Description = "Contains all information related to products" },
new EntityGroup{ Name = "Shop"}
}).ConfigureEntity<Shop>(entityOptionsBuilder =>
{
entityOptionsBuilder.SetDescription("The base Shop entity.");
});
});
By default, entities are assigned to the "empty" group. Grouping can be customized either through the Fluent API or by using attributes. When assigning entities to a group, users only need to specify the group's name. If user specifies a group that does not exists for an entity, that entity will belong to the default group.
Fluent API:
By utilizing EntityOptionsBuilder
, user can set group for entity using group's name.
services.AddNetForge(optionsBuilder =>
{
optionsBuilder.UseEntityFramework(efOptionsBuilder =>
{
efOptionsBuilder.UseDbContext<ShopDbContext>();
}).AddGroups(new List<EntityGroup>
{
new EntityGroup{ Name = "Product", Description = "Contains all information related to products" },
new EntityGroup{ Name = "Shop"}
}).ConfigureEntity<Shop>(entityOptionsBuilder =>
{
entityOptionsBuilder.SetGroup("Shop");
entityOptionsBuilder.SetDescription("The base Shop entity.");
});
});
Data Attribute:
[NetForgeEntity(GroupName = "Product")]
public class ProductTag
You can customize expanded header of all groups. By default, all groups are expanded.
services.AddNetForge(optionsBuilder =>
{
optionsBuilder.SetGroupHeadersExpanded(true);
});
You can exclude all entities and include only specific ones.
services.AddNetForge(optionsBuilder =>
{
optionsBuilder.ExcludeAllEntities();
optionsBuilder.IncludeEntities(typeof(Shop), typeof(Product));
});
Or you can include specific entities using the data attribute:
[NetForgeEntity]
public class Shop
In the admin panel, you can customize the way entities are displayed using the Fluent API or special attribites. This enables you to set various properties for your entities, such as their name, description, plural name, etc.
appBuilder.Services.AddNetForge(optionsBuilder =>
{
optionsBuilder.UseEntityFramework(efOptionsBuilder =>
{
efOptionsBuilder.UseDbContext<MyDbContext>();
});
// Set the description to display for the Shop entity.
optionsBuilder.ConfigureEntity<Shop>(entityOptionsBuilder =>
{
entityOptionsBuilder.SetDescription("Represents the shop.");
});
// Hide ProductTag from the admin panel.
optionsBuilder.ConfigureEntity<ProductTag>(entityOptionsBuilder =>
{
entityOptionsBuilder.SetIsHidden(true);
});
});
To reduce the amount of the code all configuration for an entity type can also be extracted to a separate class.
To create an entity configuration for a specific entity type, create a new class that implements the IEntityAdminConfiguration<TEntity>
interface, where TEntity
is the type of the entity you want to configure. For example, if you want to configure the Product
entity, your class might look like this:
public class ProductAdminConfiguration : IEntityAdminConfiguration<Product>
{
public void Configure(EntityOptionsBuilder<Product> builder)
{
// Define entity-specific settings here.
}
}
// Add this to your Program.cs.
appBuilder.Services.AddNetForge(optionsBuilder =>
{
optionsBuilder.ConfigureEntity(new ProductAdminConfiguration());
// Other settings...
});
You can also customize your entities by applying special attributes directly to your entity classes.
NetForgeEntityAttribute
[NetForgeEntity(DisplayName = "Entity", PluralName = "Entities", Description = "This is an entity description.")]
public class Entity
{
// Entity properties...
}
You can use the built-in System.ComponentModel.DescriptionAttribute
and System.ComponentModel.DisplayNameAttribute
to specify descriptions and display names for your entity classes.
For example:
[Description("Custom entity description.")]
[DisplayName("Custom Entity Display Name")]
public class AnotherEntity
{
// Entity properties...
}
You can configure your query for specific entity.
.ConfigureEntity<Shop>(entityOptionsBuilder =>
{
entityOptionsBuilder.ConfigureCustomQuery((serviceProvider, query) =>
{
return query.Where(e => e.IsOpen == true);
});
})
You can configure action that will be performed after entity update.
.ConfigureEntity<Address>(entityOptionsBuilder =>
{
entityOptionsBuilder.SetAfterUpdateAction((serviceProvider, originalEntity, modifiedEntity) =>
{
var dbContext = serviceProvider!.GetRequiredService<ShopDbContext>();
const string country = "Germany";
if (originalEntity.Country == country)
{
return;
}
if (modifiedEntity.Country == country)
{
modifiedEntity.PostalCode = "99998";
}
dbContext.SaveChanges();
});
})
You can use ServiceProvider
to access your services.
You can customize entity properties as well. For example, you can change display name, add description, hide it or change property column order.
optionsBuilder.ConfigureEntity<Address>(entityOptionsBuilder =>
{
entityOptionsBuilder.ConfigureProperty(address => address.Id, propertyBuilder =>
{
propertyBuilder
.SetDescription("Item identifier.")
.SetOrder(2);
});
entityOptionsBuilder.ConfigureProperty(address => address.ContactPhone, propertyBuilder =>
{
propertyBuilder
.SetDisplayName("Phone")
.SetDescription("Address contact phone.")
.SetOrder(1);
});
entityOptionsBuilder.ConfigureProperty(address => address.PostalCode, propertyBuilder =>
{
propertyBuilder.SetIsHidden(true);
});
entityOptionsBuilder.ConfigureProperty(address => address.City, propertyBuilder =>
{
propertyBuilder.SetDisplayName("Town");
});
});
Properties also customizable via attributes.
NetForgeEntityAttribute
[NetForgeProperty(DisplayName = "Custom property display name", Description = "Custom property description.", Order = 5)]
public string Property { get; set; }
Built-in Description
and DisplayName
Attributes
[Description("Custom property description.")]
[DisplayName("Custom property display name")]
public string Property { get; set; }
You can configure the display format for the properties values. See string.Format.
Using Data Attributes
You can apply the [NetForgeProperty]
attribute to an entity property and specify the display format:
[NetForgeProperty(DisplayFormat = "{0:C}")]
public decimal Price { get; set; }
In this example, the Price property will be displayed using the currency format.
Using Fluent API
Alternatively, you can use the Fluent API to configure the display format and format provider for an entity property:
services.AddNetForge(optionsBuilder =>
{
optionsBuilder.ConfigureEntity<Product>(entityOptionsBuilder =>
{
entityOptionsBuilder.ConfigureProperty(product => product.Price, propertyBuilder =>
{
propertyBuilder.SetDisplayFormat("{0:C}");
// Use Euro as a currency.
propertyBuilder.SetFormatProvider(CultureInfo.GetCultureInfo("fr-FR"));
});
});
// Other settings...
});
You can apply alphabet sorting to some properties. By default, they are not sortable.
It is configurable via [NetForgeProperty]
and Fluent API
.
Using Data Attribute
[NetForgeProperty(IsSortable = true)]
public string Name { get; set; }
Using Fluent API
entityOptionsBuilder.ConfigureProperty(shop => shop.OpenedDate, builder =>
{
builder.SetIsSortable(true);
});
You can sort multiple properties at once. It can be achieved by pressing sort buttons with CTRL
.
Sorting can be cancelled by pressing on it with ALT
.
Calculated properties are properties that don't have a direct representation in your database but are computed based on other existing properties. These properties can be useful for displaying calculated values in the admin panel.
You can add calculated properties to your entities using the Fluent API:
services.AddNetForge(optionsBuilder =>
{
optionsBuilder.ConfigureEntity<User>(entityOptionsBuilder =>
{
entityOptionsBuilder.AddCalculatedProperties(user => user.FullName, user => user.Age);
});
// Other settings...
});
By default, all entity properties are displayed in Title Case.
For example, the Product
entity will have the property StockQuantity
. By default, it will be displayed as Stock Quantity
in the admin panel.
This behavior can be disabled, and the entities will use CamelCase display instead.
services.AddNetForge(optionsBuilder =>
{
optionsBuilder.DisableTitleCaseProperties();
});
Users can customize the value used for displaying the empty record values. By default, it will be displayed as "-" (a dash).
Using Fluent API
optionsBuilder.ConfigureEntity<User>(entityOptionsBuilder =>
{
entityOptionsBuilder.ConfigureProperty(user => user.DateOfBirth,
propertyBuilder => propertyBuilder.SetEmptyValueDisplay("N/A"));
});
Using Attribute
[NetForgeProperty(EmptyValueDisplay = "N/A")]
public string Property { get; set; }
You can read about navigation properties here.
You can exclude all properties and include only specific ones. It works with both ordinary and navigation properties.
Using Fluent API
optionsBuilder.ConfigureEntity<User>(entityOptionsBuilder =>
{
entityOptionsBuilder.ExcludeAllProperties();
entityOptionsBuilder.IncludeProperties(user => user.Id, user => user.Name);
});
Or you can include specific properties using the Attribute:
[NetForgeProperty]
public string Property { get; set; }
You can explicitly control whether a property should be excluded from the data query.
Using Fluent API
optionsBuilder.ConfigureEntity<User>(entityOptionsBuilder =>
{
entityOptionsBuilder.ConfigureProperty(user => user.DateOfBirth,
propertyBuilder => propertyBuilder.SetIsExcludedFromQuery(true));
});
Using Data Attribute
[NetForgeProperty(IsExcludeFromQuery = true)]
public string Property { get; set; }
You can configure certain entity properties to be rendered as HTML content in the data grid. This feature ensures that HTML tags within these properties are not escaped, allowing for richer and more dynamic data presentation.
Using Fluent API
optionsBuilder.ConfigureEntity<User>(entityOptionsBuilder =>
{
entityOptionsBuilder.ConfigureProperty(user => user.Id,
propertyBuilder => propertyBuilder.SetDisplayAsHtml(true));
});
Using Data Attribute
[NetForgeProperty(DisplayAsHtml = true)]
public string Property { get; set; }
Generated properties will not be displayed on the create or edit entity pages. Entity framework example of generated property:
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public DateTime CreatedAt { get; set; }
RTF provides some common text formatting options like paragraphs, links, tables, etc. The ClassicEditor of CKEditor 5 is used by the admin panel.
The configuration is the following:
Using Data Attribute
[NetForgeProperty(IsRichTextField = true)]
public required string Description { get; set; }
Using Fluent API
optionsBuilder.ConfigureEntity<Product>(entityOptionsBuilder =>
{
entityOptionsBuilder.ConfigureProperty(product => product.Description,
propertyBuilder => propertyBuilder.SetIsRichTextField(true));
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public DateTime CreatedAt { get; set; }
You can add properties that will be displayed as images.
Using Fluent API
You can create your own implementaion of IUploadFileStrategy
interface and pass it to SetUploadFileStrategy
configuration method.
This interface has methods UploadFileAsync
that is calling when file is uploaded and GetFileSource
that is calling when file should be displayed.
We have some examples of strategies here.
entityOptionsBuilder.ConfigureProperty(shop => shop.BuildingPhoto, builder =>
{
builder
.SetIsImage(true)
.SetUploadFileStrategy(new UploadBase64FileStrategy());
});
You can set max image size in the application. Default value for max image size is 10 MB.
services.AddNetForge(optionsBuilder =>
{
optionsBuilder.SetMaxImageSize(15);
});
You can mark a property as read only. Such property cannot be changed on create and edit pages.
Using Fluent API
entityOptionsBuilder.ConfigureProperty(product => product.UpdatedDate, builder =>
{
builder.SetIsReadOnly(true);
});
Using Data Attribute
[NetForgeProperty(IsReadOnly = true)]
public string Property { get; set; }
You can set the max characters amount for string properties.
Global
You can set max characters for the all strings. Default value is 50.
services.AddNetForge(optionsBuilder =>
{
optionsBuilder.SetTruncationMaxCharacters(60);
});
You can disable this behavior in this way:
services.AddNetForge(optionsBuilder =>
{
optionsBuilder.DisableCharactersTruncation();
});
For the Property
You can set max characters to each property individually.
Using Data Attribute
[NetForgeProperty(TruncationMaxCharacters = 20)]
public string Name { get; set; }
Using Fluent API
entityOptionsBuilder.ConfigureProperty(shop => shop.Name, builder =>
{
builder.SetTruncationMaxCharacters(25);
});
You can mark a property as multiline text field. It allows input of text in several text rows. Disabled by default.
Using Fluent API
entityOptionsBuilder.ConfigureProperty(address => address.Street, builder =>
{
builder.SetIsMultiline();
});
Using Data Attribute
[MultilineText]
public required string Street { get; set; }
Also, there are some auxiliary properties that are used in the multiple text field.
Number of lines
Using Fluent API
entityOptionsBuilder.ConfigureProperty(address => address.Street, builder =>
{
builder.SetIsMultiline(lines: 15); // sets the lines value as 15
});
Using Data Attribute
[MultilineText(Lines = 15)]
public required string Street { get; set; }
Max Number of Lines
Using Fluent API
entityOptionsBuilder.ConfigureProperty(address => address.Street, builder =>
{
builder.SetIsMultiline(maxLines: 15); // sets the max lines value as 15
});
Using Data Attribute
[MultilineText(MaxLines = 15)]
public required string Street { get; set; }
Auto Grow
IsAutoGrow
property identifies whether the height of the text field automatically changes with the number of lines of text.
Using Fluent API
entityOptionsBuilder.ConfigureProperty(address => address.Street, builder =>
{
builder.SetIsMultiline(autoGrow: true);
});
Using Data Attribute
[MultilineText(IsAutoGrow = true)]
public required string Street { get; set; }
- Saritasa http://www.saritasa.com
The project is licensed under the terms of the BSD license. Refer to LICENSE.txt for more information.