Saritasa.NetForge 0.4.1-alpha

This is a prerelease version of Saritasa.NetForge.
There is a newer prerelease version of this package available.
See the version list below for details.
dotnet add package Saritasa.NetForge --version 0.4.1-alpha                
NuGet\Install-Package Saritasa.NetForge -Version 0.4.1-alpha                
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Saritasa.NetForge" Version="0.4.1-alpha" />                
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add Saritasa.NetForge --version 0.4.1-alpha                
#r "nuget: Saritasa.NetForge, 0.4.1-alpha"                
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
// Install Saritasa.NetForge as a Cake Addin
#addin nuget:?package=Saritasa.NetForge&version=0.4.1-alpha&prerelease

// Install Saritasa.NetForge as a Cake Tool
#tool nuget:?package=Saritasa.NetForge&version=0.4.1-alpha&prerelease                

NetForge - Admin Panel for ASP.NET Core 6 & 7 & 8

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.

How to Use

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();

Global Configurations

Customizing the Endpoint

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");
    ...
});

Customizing the Title

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");
    ...
});

Configuring Authorization

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.

View Site URL

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/");
});

Grouping

Group rows of entities into categories and make it easier for users to navigate and understand the data presented.

Customizing the UI

Main Layout Overriding

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>

Create Groups for Entities

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.");
    });
});

Configuration

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

Headers Expansion

You can customize expanded header of all groups. By default, all groups are expanded.

services.AddNetForge(optionsBuilder =>
{
    optionsBuilder.SetGroupHeadersExpanded(true);
});

Exclude All Entities and Include Specific Only

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

Customizing Entities

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.

Fluent API

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);
     });
});

Creating an Entity Configuration Class

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...
});

Data Attributes

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...
}

Custom Query

You can configure your query for specific entity.

.ConfigureEntity<Shop>(entityOptionsBuilder =>
{
    entityOptionsBuilder.ConfigureCustomQuery((serviceProvider, query) =>
    {
        return query.Where(e => e.IsOpen == true);
    });
})

After Update Action

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.

Customizing Entity Properties

You can customize entity properties as well. For example, you can change display name, add description, hide it or change property column order.

Fluent API

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");
    });
});

Data Attributes

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; }

Display Formatting

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...
});

Data Sorting

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

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...
});

Display Properties as Title Case

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();
});

Default Values

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.

Exclude All Properties and Include Specific Only

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; }

Exclude Property from Query

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; }

Formatting Property as HTML

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

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; }

Rich Text Field

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; }

Image Properties

You can add properties that will be displayed as images.

Configuration

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());
});

Max image size

You can set max image size in the application. Default value for max image size is 10 MB.

services.AddNetForge(optionsBuilder =>
{
    optionsBuilder.SetMaxImageSize(15);
});

Read-only Properties

You can mark a property as read only. Such property cannot be changed on create and edit pages.

Configuration

Using Fluent API

entityOptionsBuilder.ConfigureProperty(product => product.UpdatedDate, builder =>
{
    builder.SetIsReadOnly(true);
});

Using Data Attribute

[NetForgeProperty(IsReadOnly = true)]
public string Property { get; set; }

String Truncate

You can set the max characters amount for string properties.

Configuration

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);
});

Multiline Text Field Property

You can mark a property as multiline text field. It allows input of text in several text rows. Disabled by default.

Configuration

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; }

Contributors

License

The project is licensed under the terms of the BSD license. Refer to LICENSE.txt for more information.

Product Compatible and additional computed target framework versions.
.NET net6.0 is compatible.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 is compatible.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last updated
0.4.4-alpha 50 11/11/2024
0.4.3-alpha 102 10/18/2024
0.4.2-alpha 46 10/17/2024
0.4.1-alpha 46 10/15/2024
0.4.0-alpha 40 10/15/2024