GenericCrud.Core 1.0.1

dotnet add package GenericCrud.Core --version 1.0.1
                    
NuGet\Install-Package GenericCrud.Core -Version 1.0.1
                    
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="GenericCrud.Core" Version="1.0.1" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="GenericCrud.Core" Version="1.0.1" />
                    
Directory.Packages.props
<PackageReference Include="GenericCrud.Core" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add GenericCrud.Core --version 1.0.1
                    
#r "nuget: GenericCrud.Core, 1.0.1"
                    
#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.
#:package GenericCrud.Core@1.0.1
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=GenericCrud.Core&version=1.0.1
                    
Install as a Cake Addin
#tool nuget:?package=GenericCrud.Core&version=1.0.1
                    
Install as a Cake Tool

GenericCrud.Core

A NuGet package to simplify CRUD operations in ASP.NET Core applications using a generic controller, repository, and table generator.

Overview

GenericCrud.Core enables developers to perform Create, Read, Update, and Delete (CRUD) operations with minimal boilerplate code. By defining a model that implements the IEntity interface, the package handles database operations, file uploads, and dynamic dropdowns, with reusable Razor views for forms and lists.

Installation

  1. Install the package via NuGet:
    dotnet add package GenericCrud.Core
    
  2. Add a connection string in appsettings.json:
    {
      "ConnectionStrings": {
        "DefaultConnection": "Server=your_server;Database=your_database;Trusted_Connection=True;"
      }
    }
    
  3. Configure Program.cs to create database tables:
    using Microsoft.Data.SqlClient;
    using GenericCrud.Core;
    
    var builder = WebApplication.CreateBuilder(args);
    builder.Services.AddControllersWithViews();
    var app = builder.Build();
    
    if (app.Environment.IsDevelopment())
    {
        using (var con = new SqlConnection(builder.Configuration.GetConnectionString("DefaultConnection")))
        {
            con.Open();
            string sql = TableGenerator.GenerateOrUpdateTable<Student>();
            new SqlCommand(sql, con).ExecuteNonQuery();
        }
    }
    
    app.UseStaticFiles();
    app.UseRouting();
    app.MapControllerRoute(name: "default", pattern: "{controller=Home}/{action=Index}/{id?}");
    app.Run();
    

Usage

Define a Model

Create a model implementing IEntity. Example:

public class Student : IEntity
{
    public int Id { get; set; }
    [Dropdown("Classes", "Id", "ClassName")]
    public int ClassId { get; set; }
    [Required(ErrorMessage = "Name is required")]
    [StringLength(50)]
    public string Name { get; set; }
    [Required(ErrorMessage = "Father Name is required")]
    [StringLength(50)]
    public string FatherName { get; set; }
    [Required]
    [DataType(DataType.Date)]
    public DateTime DOB { get; set; } = DateTime.Now;
    [Required(ErrorMessage = "Mobile number is required")]
    [RegularExpression(@"^[0-9]{10}$", ErrorMessage = "Mobile number must be 10 digits")]
    public string MobileNo { get; set; }
    [ScaffoldColumn(false)]
    [HiddenInput(DisplayValue = false)]
    public string? ClassName { get; set; }
    [FileUpload("StudentPhotos")]
    public IFormFile Photo { get; set; }
    [HiddenInput(DisplayValue = false)]
    public string? PhotoPath { get; set; }
}

Create a GenericController

public class GenericController<T> : Controller where T : IEntity, new()
{
    private GenericRepo<T> _repo;

    public GenericController(IConfiguration config)
    {
        var conn = config.GetConnectionString("DefaultConnection");
        _repo = new GenericRepo<T>(conn);
    }

    public IActionResult Index()
    {
        var list = _repo.GetAll() ?? new List<T>();
        return View("~/Views/Generic/Index.cshtml", list);
    }

    public IActionResult Create()
    {
        BindDropdowns();
        return View("~/Views/Generic/Form.cshtml", new T());
    }

    [HttpPost]
    public IActionResult Create(T entity)
    {
        if (ModelState.IsValid)
        {
            var type = typeof(T);
            var tableName = type.Name.ToLower();
            var props = type.GetProperties().Where(x=>x.PropertyType == typeof(IFormFile)).ToList();
            foreach (var prop in props)
            {
                if (prop.PropertyType == typeof(IFormFile))
                {
                    var file = prop.GetValue(entity) as IFormFile;
                    if (file != null && file.Length > 0)
                    {
                        var fileName = DateTime.Now.ToString("ddMMyyyyhhmmssfff") + Path.GetExtension(file.FileName);
                        var uploadPath = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", $"upload/{tableName}");

                        if (!Directory.Exists(uploadPath))
                            Directory.CreateDirectory(uploadPath);

                        var filePath = Path.Combine(uploadPath, fileName);

                        using (var stream = new FileStream(filePath, FileMode.Create))
                        {
                            file.CopyTo(stream);
                        }

                        var pathProp = type.GetProperty(prop.Name + "Path");
                        if (pathProp != null)
                        {
                            pathProp.SetValue(entity, $"/upload/{tableName}/" + fileName);
                        }
                    }
                }
            }


            _repo.Insert(entity);
            return RedirectToAction("Index");
        }
        else
        {
            BindDropdowns();
            return View("~/Views/Generic/Form.cshtml", entity);
        }
    }

    public IActionResult Edit(int id)
    {
        BindDropdowns();
        var entity = _repo.GetAll().FirstOrDefault(x => x.Id == id);
        return View("~/Views/Generic/Form.cshtml",entity);
    }

    [HttpPost]
    public IActionResult Edit(T entity)
    {
        var type = typeof(T);
        var tableName = type.Name.ToLower();
        var props = type.GetProperties().Where(x => x.PropertyType == typeof(IFormFile)).ToList();
        foreach (var prop in props)
        {
            if (prop.PropertyType == typeof(IFormFile))
            {
                var file = prop.GetValue(entity) as IFormFile;
                if (file != null && file.Length > 0)
                {
                    var fileName = DateTime.Now.ToString("ddMMyyyyhhmmssfff") + Path.GetExtension(file.FileName);
                    var uploadPath = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", $"upload/{tableName}");

                    if (!Directory.Exists(uploadPath))
                        Directory.CreateDirectory(uploadPath);

                    var filePath = Path.Combine(uploadPath, fileName);

                    using (var stream = new FileStream(filePath, FileMode.Create))
                    {
                        file.CopyTo(stream);
                    }

                    var pathProp = type.GetProperty(prop.Name + "Path");
                    if (pathProp != null)
                    {
                        pathProp.SetValue(entity, $"/upload/{tableName}/" + fileName);
                    }
                }
            }
        }


        _repo.Update(entity);
        return RedirectToAction("Index");
    }

    public IActionResult Delete(int id)
    {
        _repo.Delete(id);
        return RedirectToAction("Index");
    }
    private void BindDropdowns()
    {
        var type = typeof(T);
        var props = type.GetProperties();

        foreach (var prop in props)
        {
            var dropdownAttr = prop.GetCustomAttributes(typeof(DropdownAttribute), false)
                                   .FirstOrDefault() as DropdownAttribute;

            if (dropdownAttr != null)
            {

                var selectList = _repo.GetDropdownList(dropdownAttr.TableName, dropdownAttr.ValueField, dropdownAttr.TextField);
                ViewData[dropdownAttr.ViewBagKey] = selectList;
            }
        }
    }
}

Create a Controller

public class StudentController : GenericController<Student>
{
    public StudentController(IConfiguration config) : base(config)
    {
    }
}

Set Up Views

Ensure /Views/Generic/Form.cshtml and /Views/Generic/Index.cshtml exist. Customize as needed (see user manual for examples).

Form.cshtml
@using GenericCrud.Core.Attributes
@model object

@{
    var type = Model?.GetType();
    var props = type?.GetProperties()?.Where(p => !Attribute.IsDefined(p, typeof(ScaffoldColumnAttribute)) || !((ScaffoldColumnAttribute)Attribute.GetCustomAttribute(p, typeof(ScaffoldColumnAttribute))).Scaffold).ToArray();
    var idProp = type?.GetProperty("Id");
    var isEdit = idProp != null && Convert.ToInt32(idProp.GetValue(Model) ?? 0) > 0;
    var actionName = isEdit ? "Edit" : "Create";
    var fileVal = string.Empty;
}

@if (type == null || props == null)
{
    <div class="alert alert-danger">Error: Model or properties are not available.</div>
    return;
}

<div class="container mt-4">
    <h2 class="mb-3">@(isEdit ? "Edit" : "Create") @type.Name</h2>

    <form asp-action="@actionName" method="post" enctype="multipart/form-data">
        <div asp-validation-summary="ModelOnly" class="text-danger mb-3"></div>

        @if (idProp != null)
        {
            <input type="hidden" name="Id" value="@idProp.GetValue(Model)" />
        }

        <div class="row">
            @foreach (var prop in props.Where(p => p.Name != "Id"))
            {
                var hiddenAttr = prop.GetCustomAttribute<HiddenInputAttribute>();
                if (hiddenAttr?.DisplayValue == false)
                {
                    continue;
                }

                var dropdownAttr = prop.GetCustomAttribute<DropdownAttribute>();
                var fileAttr = prop.GetCustomAttribute<FileUploadAttribute>();

                <div class="mb-3 col-md-6">
                    <label class="form-label">@prop.Name</label>

                    @if (dropdownAttr != null)
                    {
                        var selectList = ViewData[dropdownAttr.ViewBagKey] as List<SelectListItem>;
                        if (selectList != null)
                        {
                            @Html.DropDownList(prop.Name, selectList, "-- Select --", new { @class = "form-control" })
                        }
                        else
                        {
                            <p class="text-danger">Dropdown data for @prop.Name is not available.</p>
                        }
                    }
                    else if (fileAttr != null && prop.PropertyType == typeof(IFormFile))
                    {
                        var pathPropName = prop.Name + "Path";
                        var pathProp = type.GetProperty(pathPropName);
                        if (pathProp != null)
                        {
                            fileVal = pathProp.GetValue(Model)?.ToString() ?? string.Empty;
                        }
                        <input type="file" name="@prop.Name" class="form-control" />
                        @if (!string.IsNullOrEmpty(fileVal))
                        {
                            <div class="mt-2">
                                <small>Current File:</small>
                                <img src="@fileVal" class="img-fluid" style="max-width: 200px;" alt="Current file" />
                            </div>
                        }
                    }
                    else
                    {
                        string inputType = "text";
                        if (prop.PropertyType == typeof(int) || prop.PropertyType == typeof(decimal) || prop.PropertyType == typeof(double))
                        {
                            inputType = "number";
                        }
                        else if (prop.PropertyType == typeof(DateTime))
                        {
                            inputType = "date";
                        }
                        else if (prop.PropertyType == typeof(bool))
                        {
                            inputType = "checkbox";
                        }

                        string attempted = string.Empty;
                        object modelVal = prop.GetValue(Model);
                        if (ViewData.ModelState.TryGetValue(prop.Name, out var entry) && entry?.AttemptedValue != null)
                        {
                            attempted = entry.AttemptedValue;
                        }
                        else if (modelVal != null)
                        {
                            attempted = prop.PropertyType == typeof(DateTime)
                                ? ((DateTime)modelVal).ToString("yyyy-MM-dd")
                                : modelVal.ToString();
                        }

                        if (inputType == "checkbox")
                        {
                            <input type="checkbox" name="@prop.Name" class="form-check-input" @(modelVal != null && (bool)modelVal ? "checked" : "") />
                        }
                        else
                        {
                            <input type="@inputType" name="@prop.Name" class="form-control" value="@attempted" />
                        }
                    }

                    <span asp-validation-for="@prop.Name" class="text-danger"></span>
                </div>
            }
        </div>

        <button type="submit" class="btn btn-success">@(isEdit ? "Update" : "Save")</button>
        <a asp-action="Index" class="btn btn-secondary">Cancel</a>
    </form>
</div>

@section Scripts {
    <partial name="_ValidationScriptsPartial" />
}
Index.cshtml
@model IEnumerable<object>

@{
    var type = Model?.FirstOrDefault()?.GetType();
    var props = type?.GetProperties()?.Where(p => !Attribute.IsDefined(p, typeof(ScaffoldColumnAttribute)) || !((ScaffoldColumnAttribute)Attribute.GetCustomAttribute(p, typeof(ScaffoldColumnAttribute))).Scaffold).ToArray();
    bool hasData = Model != null && Model.Any();
}

<div class="container mt-4">
    <div class="d-flex justify-content-between align-items-center mb-3">
        <h2>@(type?.Name ?? "Items") List</h2>
        <a asp-action="Create" class="btn btn-success">Create New</a>
    </div>

    <table class="table table-bordered table-striped table-hover">
        <thead class="table-dark">
            <tr>
                @if (props != null)
                {
                    <th>Sr.No.</th>
                    @foreach (var prop in props.Where(p => !p.Name.ToLower().Contains("id") && p.PropertyType != typeof(IFormFile)))
                    {
                        <th>@prop.Name</th>
                    }
                    <th>Actions</th>
                }
                else
                {
                    <th>Data</th>
                }
            </tr>
        </thead>
        <tbody>
            @if (hasData && props != null)
            {
                int i = 0;
                foreach (var item in Model)
                {
                    i++;
                    <tr>
                        <td>@i</td>
                        @foreach (var prop in props.Where(p => !p.Name.ToLower().Contains("id") && p.PropertyType != typeof(IFormFile)))
                        {
                            var value = prop.GetValue(item)?.ToString() ?? "-";
                            if (prop.Name.ToLower().Contains("path"))
                            {
                                <td>
                                    <img src="@(string.IsNullOrEmpty(value) ? "/img/default.png" : value)" style="max-width: 100px;" alt="@prop.Name" />
                                </td>
                            }
                            else
                            {
                                <td>@Html.Raw(value)</td>
                            }
                        }
                        <td>
                            <a asp-action="Edit" asp-route-id="@item.GetType().GetProperty("Id")?.GetValue(item)" class="btn btn-sm btn-warning">Edit</a>
                            <a asp-action="Delete" asp-route-id="@item.GetType().GetProperty("Id")?.GetValue(item)" class="btn btn-sm btn-danger">Delete</a>
                        </td>
                    </tr>
                }
            }
            else
            {
                <tr>
                    <td colspan="@(props?.Length + 2 ?? 2)" class="text-center text-muted">
                        No data found
                    </td>
                </tr>
            }
        </tbody>
    </table>
</div>

Access CRUD Operations

  • Create: /Student/Create
  • Read: /Student/Index
  • Update: /Student/Edit/{id}
  • Delete: /Student/Delete/{id}

Features

  • Dynamic Table Generation: Automatically creates/updates database tables based on model properties.
  • File Uploads: Supports file uploads with [FileUpload] attribute, storing files in wwwroot/upload/.
  • Dropdowns: Populates dropdowns using [Dropdown] attribute from specified tables.
  • Validation: Supports Required, StringLength, RegularExpression, and more.
  • Hidden Fields: Excludes fields from forms/tables using ScaffoldColumn and HiddenInput.

Requirements

  • ASP.NET Core 8.0+
  • SQL Server or ADO.NET-compatible database
  • Visual Studio or compatible IDE

Support

Contact the maintainer for issues or feature requests:

For detailed instructions, refer to the User Manual.

Product Compatible and additional computed target framework versions.
.NET 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.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.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
1.0.1 336 9/16/2025
1.0.0 324 9/16/2025