GenericCrud.Core
1.0.1
dotnet add package GenericCrud.Core --version 1.0.1
NuGet\Install-Package GenericCrud.Core -Version 1.0.1
<PackageReference Include="GenericCrud.Core" Version="1.0.1" />
<PackageVersion Include="GenericCrud.Core" Version="1.0.1" />
<PackageReference Include="GenericCrud.Core" />
paket add GenericCrud.Core --version 1.0.1
#r "nuget: GenericCrud.Core, 1.0.1"
#:package GenericCrud.Core@1.0.1
#addin nuget:?package=GenericCrud.Core&version=1.0.1
#tool nuget:?package=GenericCrud.Core&version=1.0.1
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
- Install the package via NuGet:
dotnet add package GenericCrud.Core - Add a connection string in
appsettings.json:{ "ConnectionStrings": { "DefaultConnection": "Server=your_server;Database=your_database;Trusted_Connection=True;" } } - Configure
Program.csto 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 inwwwroot/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
ScaffoldColumnandHiddenInput.
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:
- Name: Hemant, Software Developer
- Email: dev-hemant@outlook.com
For detailed instructions, refer to the User Manual.
| Product | Versions 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. |
-
net8.0
- Microsoft.AspNetCore.Mvc (>= 2.3.0)
- Microsoft.Data.SqlClient (>= 6.1.1)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.